linux/drivers/clk/samsung/clk-exynos5-subcmu.c
<<
>>
Prefs
   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);
 196