162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Copyright (c) 2018 Samsung Electronics Co., Ltd. 462306a36Sopenharmony_ci// Author: Marek Szyprowski <m.szyprowski@samsung.com> 562306a36Sopenharmony_ci// Common Clock Framework support for Exynos5 power-domain dependent clocks 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/io.h> 862306a36Sopenharmony_ci#include <linux/of.h> 962306a36Sopenharmony_ci#include <linux/platform_device.h> 1062306a36Sopenharmony_ci#include <linux/pm_domain.h> 1162306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "clk.h" 1462306a36Sopenharmony_ci#include "clk-exynos5-subcmu.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic struct samsung_clk_provider *ctx; 1762306a36Sopenharmony_cistatic const struct exynos5_subcmu_info **cmu; 1862306a36Sopenharmony_cistatic int nr_cmus; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic void exynos5_subcmu_clk_save(void __iomem *base, 2162306a36Sopenharmony_ci struct exynos5_subcmu_reg_dump *rd, 2262306a36Sopenharmony_ci unsigned int num_regs) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci for (; num_regs > 0; --num_regs, ++rd) { 2562306a36Sopenharmony_ci rd->save = readl(base + rd->offset); 2662306a36Sopenharmony_ci writel((rd->save & ~rd->mask) | rd->value, base + rd->offset); 2762306a36Sopenharmony_ci rd->save &= rd->mask; 2862306a36Sopenharmony_ci } 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void exynos5_subcmu_clk_restore(void __iomem *base, 3262306a36Sopenharmony_ci struct exynos5_subcmu_reg_dump *rd, 3362306a36Sopenharmony_ci unsigned int num_regs) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci for (; num_regs > 0; --num_regs, ++rd) 3662306a36Sopenharmony_ci writel((readl(base + rd->offset) & ~rd->mask) | rd->save, 3762306a36Sopenharmony_ci base + rd->offset); 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic void exynos5_subcmu_defer_gate(struct samsung_clk_provider *ctx, 4162306a36Sopenharmony_ci const struct samsung_gate_clock *list, int nr_clk) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci while (nr_clk--) 4462306a36Sopenharmony_ci samsung_clk_add_lookup(ctx, ERR_PTR(-EPROBE_DEFER), list++->id); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* 4862306a36Sopenharmony_ci * Pass the needed clock provider context and register sub-CMU clocks 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * NOTE: This function has to be called from the main, CLK_OF_DECLARE- 5162306a36Sopenharmony_ci * initialized clock provider driver. This happens very early during boot 5262306a36Sopenharmony_ci * process. Then this driver, during core_initcall registers two platform 5362306a36Sopenharmony_ci * drivers: one which binds to the same device-tree node as CLK_OF_DECLARE 5462306a36Sopenharmony_ci * driver and second, for handling its per-domain child-devices. Those 5562306a36Sopenharmony_ci * platform drivers are bound to their devices a bit later in arch_initcall, 5662306a36Sopenharmony_ci * when OF-core populates all device-tree nodes. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_civoid exynos5_subcmus_init(struct samsung_clk_provider *_ctx, int _nr_cmus, 5962306a36Sopenharmony_ci const struct exynos5_subcmu_info **_cmu) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci ctx = _ctx; 6262306a36Sopenharmony_ci cmu = _cmu; 6362306a36Sopenharmony_ci nr_cmus = _nr_cmus; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci for (; _nr_cmus--; _cmu++) { 6662306a36Sopenharmony_ci exynos5_subcmu_defer_gate(ctx, (*_cmu)->gate_clks, 6762306a36Sopenharmony_ci (*_cmu)->nr_gate_clks); 6862306a36Sopenharmony_ci exynos5_subcmu_clk_save(ctx->reg_base, (*_cmu)->suspend_regs, 6962306a36Sopenharmony_ci (*_cmu)->nr_suspend_regs); 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int __maybe_unused exynos5_subcmu_suspend(struct device *dev) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 7662306a36Sopenharmony_ci unsigned long flags; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci spin_lock_irqsave(&ctx->lock, flags); 7962306a36Sopenharmony_ci exynos5_subcmu_clk_save(ctx->reg_base, info->suspend_regs, 8062306a36Sopenharmony_ci info->nr_suspend_regs); 8162306a36Sopenharmony_ci spin_unlock_irqrestore(&ctx->lock, flags); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci return 0; 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int __maybe_unused exynos5_subcmu_resume(struct device *dev) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 8962306a36Sopenharmony_ci unsigned long flags; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci spin_lock_irqsave(&ctx->lock, flags); 9262306a36Sopenharmony_ci exynos5_subcmu_clk_restore(ctx->reg_base, info->suspend_regs, 9362306a36Sopenharmony_ci info->nr_suspend_regs); 9462306a36Sopenharmony_ci spin_unlock_irqrestore(&ctx->lock, flags); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return 0; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic int __init exynos5_subcmu_probe(struct platform_device *pdev) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 10262306a36Sopenharmony_ci struct exynos5_subcmu_info *info = dev_get_drvdata(dev); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci pm_runtime_set_suspended(dev); 10562306a36Sopenharmony_ci pm_runtime_enable(dev); 10662306a36Sopenharmony_ci pm_runtime_get(dev); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci ctx->dev = dev; 10962306a36Sopenharmony_ci samsung_clk_register_div(ctx, info->div_clks, info->nr_div_clks); 11062306a36Sopenharmony_ci samsung_clk_register_gate(ctx, info->gate_clks, info->nr_gate_clks); 11162306a36Sopenharmony_ci ctx->dev = NULL; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci pm_runtime_put_sync(dev); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic const struct dev_pm_ops exynos5_subcmu_pm_ops = { 11962306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(exynos5_subcmu_suspend, 12062306a36Sopenharmony_ci exynos5_subcmu_resume, NULL) 12162306a36Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 12262306a36Sopenharmony_ci pm_runtime_force_resume) 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic struct platform_driver exynos5_subcmu_driver __refdata = { 12662306a36Sopenharmony_ci .driver = { 12762306a36Sopenharmony_ci .name = "exynos5-subcmu", 12862306a36Sopenharmony_ci .suppress_bind_attrs = true, 12962306a36Sopenharmony_ci .pm = &exynos5_subcmu_pm_ops, 13062306a36Sopenharmony_ci }, 13162306a36Sopenharmony_ci .probe = exynos5_subcmu_probe, 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int __init exynos5_clk_register_subcmu(struct device *parent, 13562306a36Sopenharmony_ci const struct exynos5_subcmu_info *info, 13662306a36Sopenharmony_ci struct device_node *pd_node) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct of_phandle_args genpdspec = { .np = pd_node }; 13962306a36Sopenharmony_ci struct platform_device *pdev; 14062306a36Sopenharmony_ci int ret; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci pdev = platform_device_alloc("exynos5-subcmu", PLATFORM_DEVID_AUTO); 14362306a36Sopenharmony_ci if (!pdev) 14462306a36Sopenharmony_ci return -ENOMEM; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci pdev->dev.parent = parent; 14762306a36Sopenharmony_ci platform_set_drvdata(pdev, (void *)info); 14862306a36Sopenharmony_ci of_genpd_add_device(&genpdspec, &pdev->dev); 14962306a36Sopenharmony_ci ret = platform_device_add(pdev); 15062306a36Sopenharmony_ci if (ret) 15162306a36Sopenharmony_ci platform_device_put(pdev); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return ret; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int __init exynos5_clk_probe(struct platform_device *pdev) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct device_node *np; 15962306a36Sopenharmony_ci const char *name; 16062306a36Sopenharmony_ci int i; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { 16362306a36Sopenharmony_ci if (of_property_read_string(np, "label", &name) < 0) 16462306a36Sopenharmony_ci continue; 16562306a36Sopenharmony_ci for (i = 0; i < nr_cmus; i++) 16662306a36Sopenharmony_ci if (strcmp(cmu[i]->pd_name, name) == 0) 16762306a36Sopenharmony_ci exynos5_clk_register_subcmu(&pdev->dev, 16862306a36Sopenharmony_ci cmu[i], np); 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci return 0; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic const struct of_device_id exynos5_clk_of_match[] = { 17462306a36Sopenharmony_ci { .compatible = "samsung,exynos5250-clock", }, 17562306a36Sopenharmony_ci { .compatible = "samsung,exynos5420-clock", }, 17662306a36Sopenharmony_ci { .compatible = "samsung,exynos5800-clock", }, 17762306a36Sopenharmony_ci { }, 17862306a36Sopenharmony_ci}; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic struct platform_driver exynos5_clk_driver __refdata = { 18162306a36Sopenharmony_ci .driver = { 18262306a36Sopenharmony_ci .name = "exynos5-clock", 18362306a36Sopenharmony_ci .of_match_table = exynos5_clk_of_match, 18462306a36Sopenharmony_ci .suppress_bind_attrs = true, 18562306a36Sopenharmony_ci }, 18662306a36Sopenharmony_ci .probe = exynos5_clk_probe, 18762306a36Sopenharmony_ci}; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int __init exynos5_clk_drv_init(void) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci platform_driver_register(&exynos5_clk_driver); 19262306a36Sopenharmony_ci platform_driver_register(&exynos5_subcmu_driver); 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_cicore_initcall(exynos5_clk_drv_init); 196