1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Copyright (c) 2018 Samsung Electronics Co., Ltd. 4 // Author: Marek Szyprowski <m.szyprowski@samsung.com> 5 // Common Clock Framework support for Exynos5 power-domain dependent clocks 6 7 #include <linux/io.h> 8 #include <linux/of_platform.h> 9 #include <linux/platform_device.h> 10 #include <linux/pm_domain.h> 11 #include <linux/pm_runtime.h> 12 13 #include "clk.h" 14 #include "clk-exynos5-subcmu.h" 15 16 static struct samsung_clk_provider *ctx; 17 static const struct exynos5_subcmu_info *cmu; 18 static int nr_cmus; 19 20 static void exynos5_subcmu_clk_save(void __iomem *base, 21 struct exynos5_subcmu_reg_dump *rd, 22 unsigned int num_regs) 23 { 24 for (; num_regs > 0; --num_regs, ++rd) { 25 rd->save = readl(base + rd->offset); 26 writel((rd->save & ~rd->mask) | rd->value, base + rd->offset); 27 rd->save &= rd->mask; 28 } 29 }; 30 31 static void exynos5_subcmu_clk_restore(void __iomem *base, 32 struct exynos5_subcmu_reg_dump *rd, 33 unsigned int num_regs) 34 { 35 for (; num_regs > 0; --num_regs, ++rd) 36 writel((readl(base + rd->offset) & ~rd->mask) | rd->save, 37 base + rd->offset); 38 } 39 40 static void exynos5_subcmu_defer_gate(struct samsung_clk_provider *ctx, 41 const struct samsung_gate_clock *list, int nr_clk) 42 { 43 while (nr_clk--) 44 samsung_clk_add_lookup(ctx, ERR_PTR(-EPROBE_DEFER), list++->id); 45 } 46 47 /* 48 * Pass the needed clock provider context and register sub-CMU clocks 49 * 50 * NOTE: This function has to be called from the main, OF_CLK_DECLARE- 51 * initialized clock provider driver. This happens very early during boot 52 * process. Then this driver, during core_initcall registers two platform 53 * drivers: one which binds to the same device-tree node as OF_CLK_DECLARE 54 * driver and second, for handling its per-domain child-devices. Those 55 * platform drivers are bound to their devices a bit later in arch_initcall, 56 * when OF-core populates all device-tree nodes. 57 */ 58 void exynos5_subcmus_init(struct samsung_clk_provider *_ctx, int _nr_cmus, 59 const struct exynos5_subcmu_info *_cmu) 60 { 61 ctx = _ctx; 62 cmu = _cmu; 63 nr_cmus = _nr_cmus; 64 65 for (; _nr_cmus--; _cmu++) { 66 exynos5_subcmu_defer_gate(ctx, _cmu->gate_clks, 67 _cmu->nr_gate_clks); 68 exynos5_subcmu_clk_save(ctx->reg_base, _cmu->suspend_regs, 69 _cmu->nr_suspend_regs); 70 } 71 } 72 73 static int __maybe_unused exynos5_subcmu_suspend(struct device *dev) 74 { 75 struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 76 unsigned long flags; 77 78 spin_lock_irqsave(&ctx->lock, flags); 79 exynos5_subcmu_clk_save(ctx->reg_base, info->suspend_regs, 80 info->nr_suspend_regs); 81 spin_unlock_irqrestore(&ctx->lock, flags); 82 83 return 0; 84 } 85 86 static int __maybe_unused exynos5_subcmu_resume(struct device *dev) 87 { 88 struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 89 unsigned long flags; 90 91 spin_lock_irqsave(&ctx->lock, flags); 92 exynos5_subcmu_clk_restore(ctx->reg_base, info->suspend_regs, 93 info->nr_suspend_regs); 94 spin_unlock_irqrestore(&ctx->lock, flags); 95 96 return 0; 97 } 98 99 static int __init exynos5_subcmu_probe(struct platform_device *pdev) 100 { 101 struct device *dev = &pdev->dev; 102 struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 103 104 pm_runtime_set_suspended(dev); 105 pm_runtime_enable(dev); 106 pm_runtime_get(dev); 107 108 ctx->dev = dev; 109 samsung_clk_register_div(ctx, info->div_clks, info->nr_div_clks); 110 samsung_clk_register_gate(ctx, info->gate_clks, info->nr_gate_clks); 111 ctx->dev = NULL; 112 113 pm_runtime_put_sync(dev); 114 115 return 0; 116 } 117 118 static const struct dev_pm_ops exynos5_subcmu_pm_ops = { 119 SET_RUNTIME_PM_OPS(exynos5_subcmu_suspend, 120 exynos5_subcmu_resume, NULL) 121 SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 122 pm_runtime_force_resume) 123 }; 124 125 static struct platform_driver exynos5_subcmu_driver __refdata = { 126 .driver = { 127 .name = "exynos5-subcmu", 128 .suppress_bind_attrs = true, 129 .pm = &exynos5_subcmu_pm_ops, 130 }, 131 .probe = exynos5_subcmu_probe, 132 }; 133 134 static int __init exynos5_clk_register_subcmu(struct device *parent, 135 const struct exynos5_subcmu_info *info, 136 struct device_node *pd_node) 137 { 138 struct of_phandle_args genpdspec = { .np = pd_node }; 139 struct platform_device *pdev; 140 int ret; 141 142 pdev = platform_device_alloc("exynos5-subcmu", PLATFORM_DEVID_AUTO); 143 if (!pdev) 144 return -ENOMEM; 145 146 pdev->dev.parent = parent; 147 platform_set_drvdata(pdev, (void *)info); 148 of_genpd_add_device(&genpdspec, &pdev->dev); 149 ret = platform_device_add(pdev); 150 if (ret) 151 platform_device_put(pdev); 152 153 return ret; 154 } 155 156 static int __init exynos5_clk_probe(struct platform_device *pdev) 157 { 158 struct device_node *np; 159 const char *name; 160 int i; 161 162 for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { 163 if (of_property_read_string(np, "label", &name) < 0) 164 continue; 165 for (i = 0; i < nr_cmus; i++) 166 if (strcmp(cmu[i].pd_name, name) == 0) 167 exynos5_clk_register_subcmu(&pdev->dev, 168 &cmu[i], np); 169 } 170 return 0; 171 } 172 173 static const struct of_device_id exynos5_clk_of_match[] = { 174 { .compatible = "samsung,exynos5250-clock", }, 175 { .compatible = "samsung,exynos5420-clock", }, 176 { .compatible = "samsung,exynos5800-clock", }, 177 { }, 178 }; 179 180 static struct platform_driver exynos5_clk_driver __refdata = { 181 .driver = { 182 .name = "exynos5-clock", 183 .of_match_table = exynos5_clk_of_match, 184 .suppress_bind_attrs = true, 185 }, 186 .probe = exynos5_clk_probe, 187 }; 188 189 static int __init exynos5_clk_drv_init(void) 190 { 191 platform_driver_register(&exynos5_clk_driver); 192 platform_driver_register(&exynos5_subcmu_driver); 193 return 0; 194 } 195 core_initcall(exynos5_clk_drv_init); 196