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