clk-exynos5-subcmu.c (5471B)
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 16static struct samsung_clk_provider *ctx; 17static const struct exynos5_subcmu_info **cmu; 18static int nr_cmus; 19 20static 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 31static 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 40static 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 */ 58void 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 73static 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 86static 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 99static 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 118static 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 125static 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 134static 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 156static 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 173static 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 180static 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 189static 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} 195core_initcall(exynos5_clk_drv_init);