forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.3 KiB
195 lines
5.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// |
|
// Copyright (c) 2018 Samsung Electronics Co., Ltd. |
|
// Author: Marek Szyprowski <[email protected]> |
|
// Common Clock Framework support for Exynos5 power-domain dependent clocks |
|
|
|
#include <linux/io.h> |
|
#include <linux/of_platform.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/pm_domain.h> |
|
#include <linux/pm_runtime.h> |
|
|
|
#include "clk.h" |
|
#include "clk-exynos5-subcmu.h" |
|
|
|
static struct samsung_clk_provider *ctx; |
|
static const struct exynos5_subcmu_info **cmu; |
|
static int nr_cmus; |
|
|
|
static void exynos5_subcmu_clk_save(void __iomem *base, |
|
struct exynos5_subcmu_reg_dump *rd, |
|
unsigned int num_regs) |
|
{ |
|
for (; num_regs > 0; --num_regs, ++rd) { |
|
rd->save = readl(base + rd->offset); |
|
writel((rd->save & ~rd->mask) | rd->value, base + rd->offset); |
|
rd->save &= rd->mask; |
|
} |
|
}; |
|
|
|
static void exynos5_subcmu_clk_restore(void __iomem *base, |
|
struct exynos5_subcmu_reg_dump *rd, |
|
unsigned int num_regs) |
|
{ |
|
for (; num_regs > 0; --num_regs, ++rd) |
|
writel((readl(base + rd->offset) & ~rd->mask) | rd->save, |
|
base + rd->offset); |
|
} |
|
|
|
static void exynos5_subcmu_defer_gate(struct samsung_clk_provider *ctx, |
|
const struct samsung_gate_clock *list, int nr_clk) |
|
{ |
|
while (nr_clk--) |
|
samsung_clk_add_lookup(ctx, ERR_PTR(-EPROBE_DEFER), list++->id); |
|
} |
|
|
|
/* |
|
* Pass the needed clock provider context and register sub-CMU clocks |
|
* |
|
* NOTE: This function has to be called from the main, OF_CLK_DECLARE- |
|
* initialized clock provider driver. This happens very early during boot |
|
* process. Then this driver, during core_initcall registers two platform |
|
* drivers: one which binds to the same device-tree node as OF_CLK_DECLARE |
|
* driver and second, for handling its per-domain child-devices. Those |
|
* platform drivers are bound to their devices a bit later in arch_initcall, |
|
* when OF-core populates all device-tree nodes. |
|
*/ |
|
void exynos5_subcmus_init(struct samsung_clk_provider *_ctx, int _nr_cmus, |
|
const struct exynos5_subcmu_info **_cmu) |
|
{ |
|
ctx = _ctx; |
|
cmu = _cmu; |
|
nr_cmus = _nr_cmus; |
|
|
|
for (; _nr_cmus--; _cmu++) { |
|
exynos5_subcmu_defer_gate(ctx, (*_cmu)->gate_clks, |
|
(*_cmu)->nr_gate_clks); |
|
exynos5_subcmu_clk_save(ctx->reg_base, (*_cmu)->suspend_regs, |
|
(*_cmu)->nr_suspend_regs); |
|
} |
|
} |
|
|
|
static int __maybe_unused exynos5_subcmu_suspend(struct device *dev) |
|
{ |
|
struct exynos5_subcmu_info *info = dev_get_drvdata(dev); |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(&ctx->lock, flags); |
|
exynos5_subcmu_clk_save(ctx->reg_base, info->suspend_regs, |
|
info->nr_suspend_regs); |
|
spin_unlock_irqrestore(&ctx->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int __maybe_unused exynos5_subcmu_resume(struct device *dev) |
|
{ |
|
struct exynos5_subcmu_info *info = dev_get_drvdata(dev); |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(&ctx->lock, flags); |
|
exynos5_subcmu_clk_restore(ctx->reg_base, info->suspend_regs, |
|
info->nr_suspend_regs); |
|
spin_unlock_irqrestore(&ctx->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int __init exynos5_subcmu_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct exynos5_subcmu_info *info = dev_get_drvdata(dev); |
|
|
|
pm_runtime_set_suspended(dev); |
|
pm_runtime_enable(dev); |
|
pm_runtime_get(dev); |
|
|
|
ctx->dev = dev; |
|
samsung_clk_register_div(ctx, info->div_clks, info->nr_div_clks); |
|
samsung_clk_register_gate(ctx, info->gate_clks, info->nr_gate_clks); |
|
ctx->dev = NULL; |
|
|
|
pm_runtime_put_sync(dev); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct dev_pm_ops exynos5_subcmu_pm_ops = { |
|
SET_RUNTIME_PM_OPS(exynos5_subcmu_suspend, |
|
exynos5_subcmu_resume, NULL) |
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
|
pm_runtime_force_resume) |
|
}; |
|
|
|
static struct platform_driver exynos5_subcmu_driver __refdata = { |
|
.driver = { |
|
.name = "exynos5-subcmu", |
|
.suppress_bind_attrs = true, |
|
.pm = &exynos5_subcmu_pm_ops, |
|
}, |
|
.probe = exynos5_subcmu_probe, |
|
}; |
|
|
|
static int __init exynos5_clk_register_subcmu(struct device *parent, |
|
const struct exynos5_subcmu_info *info, |
|
struct device_node *pd_node) |
|
{ |
|
struct of_phandle_args genpdspec = { .np = pd_node }; |
|
struct platform_device *pdev; |
|
int ret; |
|
|
|
pdev = platform_device_alloc("exynos5-subcmu", PLATFORM_DEVID_AUTO); |
|
if (!pdev) |
|
return -ENOMEM; |
|
|
|
pdev->dev.parent = parent; |
|
platform_set_drvdata(pdev, (void *)info); |
|
of_genpd_add_device(&genpdspec, &pdev->dev); |
|
ret = platform_device_add(pdev); |
|
if (ret) |
|
platform_device_put(pdev); |
|
|
|
return ret; |
|
} |
|
|
|
static int __init exynos5_clk_probe(struct platform_device *pdev) |
|
{ |
|
struct device_node *np; |
|
const char *name; |
|
int i; |
|
|
|
for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { |
|
if (of_property_read_string(np, "label", &name) < 0) |
|
continue; |
|
for (i = 0; i < nr_cmus; i++) |
|
if (strcmp(cmu[i]->pd_name, name) == 0) |
|
exynos5_clk_register_subcmu(&pdev->dev, |
|
cmu[i], np); |
|
} |
|
return 0; |
|
} |
|
|
|
static const struct of_device_id exynos5_clk_of_match[] = { |
|
{ .compatible = "samsung,exynos5250-clock", }, |
|
{ .compatible = "samsung,exynos5420-clock", }, |
|
{ .compatible = "samsung,exynos5800-clock", }, |
|
{ }, |
|
}; |
|
|
|
static struct platform_driver exynos5_clk_driver __refdata = { |
|
.driver = { |
|
.name = "exynos5-clock", |
|
.of_match_table = exynos5_clk_of_match, |
|
.suppress_bind_attrs = true, |
|
}, |
|
.probe = exynos5_clk_probe, |
|
}; |
|
|
|
static int __init exynos5_clk_drv_init(void) |
|
{ |
|
platform_driver_register(&exynos5_clk_driver); |
|
platform_driver_register(&exynos5_subcmu_driver); |
|
return 0; |
|
} |
|
core_initcall(exynos5_clk_drv_init);
|
|
|