diff options
Diffstat (limited to 'drivers/clk/samsung/clk.c')
| -rw-r--r-- | drivers/clk/samsung/clk.c | 432 |
1 files changed, 250 insertions, 182 deletions
diff --git a/drivers/clk/samsung/clk.c b/drivers/clk/samsung/clk.c index cd3c40ab50f3..c149ca6c2217 100644 --- a/drivers/clk/samsung/clk.c +++ b/drivers/clk/samsung/clk.c @@ -1,117 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013 Samsung Electronics Co., Ltd. * Copyright (c) 2013 Linaro Ltd. * Author: Thomas Abraham <thomas.ab@samsung.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * * This file includes utility functions to register clocks to common * clock framework for Samsung platforms. -*/ + */ +#include <linux/slab.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/of_address.h> #include <linux/syscore_ops.h> -#include "clk.h" -static DEFINE_SPINLOCK(lock); -static struct clk **clk_table; -static void __iomem *reg_base; -#ifdef CONFIG_OF -static struct clk_onecell_data clk_data; -#endif +#include "clk.h" -#ifdef CONFIG_PM_SLEEP -static struct samsung_clk_reg_dump *reg_dump; -static unsigned long nr_reg_dump; +static LIST_HEAD(clock_reg_cache_list); -static int samsung_clk_suspend(void) +void samsung_clk_save(void __iomem *base, + struct samsung_clk_reg_dump *rd, + unsigned int num_regs) { - struct samsung_clk_reg_dump *rd = reg_dump; - unsigned long i; - - for (i = 0; i < nr_reg_dump; i++, rd++) - rd->value = __raw_readl(reg_base + rd->offset); + for (; num_regs > 0; --num_regs, ++rd) + rd->value = readl(base + rd->offset); +} - return 0; +void samsung_clk_restore(void __iomem *base, + const struct samsung_clk_reg_dump *rd, + unsigned int num_regs) +{ + for (; num_regs > 0; --num_regs, ++rd) + writel(rd->value, base + rd->offset); } -static void samsung_clk_resume(void) +struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump( + const unsigned long *rdump, + unsigned long nr_rdump) { - struct samsung_clk_reg_dump *rd = reg_dump; - unsigned long i; + struct samsung_clk_reg_dump *rd; + unsigned int i; - for (i = 0; i < nr_reg_dump; i++, rd++) - __raw_writel(rd->value, reg_base + rd->offset); -} + rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL); + if (!rd) + return NULL; -static struct syscore_ops samsung_clk_syscore_ops = { - .suspend = samsung_clk_suspend, - .resume = samsung_clk_resume, -}; -#endif /* CONFIG_PM_SLEEP */ + for (i = 0; i < nr_rdump; ++i) + rd[i].offset = rdump[i]; -/* setup the essentials required to support clock lookup using ccf */ -void __init samsung_clk_init(struct device_node *np, void __iomem *base, - unsigned long nr_clks, unsigned long *rdump, - unsigned long nr_rdump, unsigned long *soc_rdump, - unsigned long nr_soc_rdump) + return rd; +} + +/** + * samsung_clk_init() - Create and initialize a clock provider object + * @dev: CMU device to enable runtime PM, or NULL if RPM is not needed + * @base: Start address (mapped) of CMU registers + * @nr_clks: Total clock count to allocate in clock provider object + * + * Setup the essentials required to support clock lookup using Common Clock + * Framework. + * + * Return: Allocated and initialized clock provider object. + */ +struct samsung_clk_provider * __init samsung_clk_init(struct device *dev, + void __iomem *base, unsigned long nr_clks) { - reg_base = base; + struct samsung_clk_provider *ctx; + int i; -#ifdef CONFIG_PM_SLEEP - if (rdump && nr_rdump) { - unsigned int idx; - reg_dump = kzalloc(sizeof(struct samsung_clk_reg_dump) - * (nr_rdump + nr_soc_rdump), GFP_KERNEL); - if (!reg_dump) { - pr_err("%s: memory alloc for register dump failed\n", - __func__); - return; - } + ctx = kzalloc(struct_size(ctx, clk_data.hws, nr_clks), GFP_KERNEL); + if (!ctx) + panic("could not allocate clock provider context.\n"); - for (idx = 0; idx < nr_rdump; idx++) - reg_dump[idx].offset = rdump[idx]; - for (idx = 0; idx < nr_soc_rdump; idx++) - reg_dump[nr_rdump + idx].offset = soc_rdump[idx]; - nr_reg_dump = nr_rdump + nr_soc_rdump; - register_syscore_ops(&samsung_clk_syscore_ops); - } -#endif + ctx->clk_data.num = nr_clks; + for (i = 0; i < nr_clks; ++i) + ctx->clk_data.hws[i] = ERR_PTR(-ENOENT); - clk_table = kzalloc(sizeof(struct clk *) * nr_clks, GFP_KERNEL); - if (!clk_table) - panic("could not allocate clock lookup table\n"); + ctx->dev = dev; + ctx->reg_base = base; + spin_lock_init(&ctx->lock); - if (!np) - return; + return ctx; +} -#ifdef CONFIG_OF - clk_data.clks = clk_table; - clk_data.clk_num = nr_clks; - of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); -#endif +void __init samsung_clk_of_add_provider(struct device_node *np, + struct samsung_clk_provider *ctx) +{ + if (np) { + if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, + &ctx->clk_data)) + panic("could not register clk provider\n"); + } } /* add a clock instance to the clock lookup table used for dt based lookup */ -void samsung_clk_add_lookup(struct clk *clk, unsigned int id) +void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, + struct clk_hw *clk_hw, unsigned int id) { - if (clk_table && id) - clk_table[id] = clk; + if (id) + ctx->clk_data.hws[id] = clk_hw; } /* register a list of aliases */ -void __init samsung_clk_register_alias(struct samsung_clock_alias *list, - unsigned int nr_clk) +void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx, + const struct samsung_clock_alias *list, + unsigned int nr_clk) { - struct clk *clk; + struct clk_hw *clk_hw; unsigned int idx, ret; - if (!clk_table) { - pr_err("%s: clock table missing\n", __func__); - return; - } - for (idx = 0; idx < nr_clk; idx++, list++) { if (!list->id) { pr_err("%s: clock id missing for index %d\n", __func__, @@ -119,14 +118,15 @@ void __init samsung_clk_register_alias(struct samsung_clock_alias *list, continue; } - clk = clk_table[list->id]; - if (!clk) { + clk_hw = ctx->clk_data.hws[list->id]; + if (!clk_hw) { pr_err("%s: failed to find clock %d\n", __func__, list->id); continue; } - ret = clk_register_clkdev(clk, list->alias, list->dev_name); + ret = clk_hw_register_clkdev(clk_hw, list->alias, + list->dev_name); if (ret) pr_err("%s: failed to register lookup %s\n", __func__, list->alias); @@ -134,149 +134,118 @@ void __init samsung_clk_register_alias(struct samsung_clock_alias *list, } /* register a list of fixed clocks */ -void __init samsung_clk_register_fixed_rate( - struct samsung_fixed_rate_clock *list, unsigned int nr_clk) +void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx, + const struct samsung_fixed_rate_clock *list, + unsigned int nr_clk) { - struct clk *clk; - unsigned int idx, ret; + struct clk_hw *clk_hw; + unsigned int idx; for (idx = 0; idx < nr_clk; idx++, list++) { - clk = clk_register_fixed_rate(NULL, list->name, + clk_hw = clk_hw_register_fixed_rate(ctx->dev, list->name, list->parent_name, list->flags, list->fixed_rate); - if (IS_ERR(clk)) { + if (IS_ERR(clk_hw)) { pr_err("%s: failed to register clock %s\n", __func__, list->name); continue; } - samsung_clk_add_lookup(clk, list->id); - - /* - * Unconditionally add a clock lookup for the fixed rate clocks. - * There are not many of these on any of Samsung platforms. - */ - ret = clk_register_clkdev(clk, list->name, NULL); - if (ret) - pr_err("%s: failed to register clock lookup for %s", - __func__, list->name); + samsung_clk_add_lookup(ctx, clk_hw, list->id); } } /* register a list of fixed factor clocks */ -void __init samsung_clk_register_fixed_factor( - struct samsung_fixed_factor_clock *list, unsigned int nr_clk) +void __init samsung_clk_register_fixed_factor(struct samsung_clk_provider *ctx, + const struct samsung_fixed_factor_clock *list, unsigned int nr_clk) { - struct clk *clk; + struct clk_hw *clk_hw; unsigned int idx; for (idx = 0; idx < nr_clk; idx++, list++) { - clk = clk_register_fixed_factor(NULL, list->name, + clk_hw = clk_hw_register_fixed_factor(ctx->dev, list->name, list->parent_name, list->flags, list->mult, list->div); - if (IS_ERR(clk)) { + if (IS_ERR(clk_hw)) { pr_err("%s: failed to register clock %s\n", __func__, list->name); continue; } - samsung_clk_add_lookup(clk, list->id); + samsung_clk_add_lookup(ctx, clk_hw, list->id); } } /* register a list of mux clocks */ -void __init samsung_clk_register_mux(struct samsung_mux_clock *list, - unsigned int nr_clk) +void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx, + const struct samsung_mux_clock *list, + unsigned int nr_clk) { - struct clk *clk; - unsigned int idx, ret; + struct clk_hw *clk_hw; + unsigned int idx; for (idx = 0; idx < nr_clk; idx++, list++) { - clk = clk_register_mux(NULL, list->name, list->parent_names, - list->num_parents, list->flags, reg_base + list->offset, - list->shift, list->width, list->mux_flags, &lock); - if (IS_ERR(clk)) { + clk_hw = clk_hw_register_mux(ctx->dev, list->name, + list->parent_names, list->num_parents, list->flags, + ctx->reg_base + list->offset, + list->shift, list->width, list->mux_flags, &ctx->lock); + if (IS_ERR(clk_hw)) { pr_err("%s: failed to register clock %s\n", __func__, list->name); continue; } - samsung_clk_add_lookup(clk, list->id); - - /* register a clock lookup only if a clock alias is specified */ - if (list->alias) { - ret = clk_register_clkdev(clk, list->alias, - list->dev_name); - if (ret) - pr_err("%s: failed to register lookup %s\n", - __func__, list->alias); - } + samsung_clk_add_lookup(ctx, clk_hw, list->id); } } /* register a list of div clocks */ -void __init samsung_clk_register_div(struct samsung_div_clock *list, - unsigned int nr_clk) +void __init samsung_clk_register_div(struct samsung_clk_provider *ctx, + const struct samsung_div_clock *list, + unsigned int nr_clk) { - struct clk *clk; - unsigned int idx, ret; + struct clk_hw *clk_hw; + unsigned int idx; for (idx = 0; idx < nr_clk; idx++, list++) { if (list->table) - clk = clk_register_divider_table(NULL, list->name, - list->parent_name, list->flags, - reg_base + list->offset, list->shift, - list->width, list->div_flags, - list->table, &lock); + clk_hw = clk_hw_register_divider_table(ctx->dev, + list->name, list->parent_name, list->flags, + ctx->reg_base + list->offset, + list->shift, list->width, list->div_flags, + list->table, &ctx->lock); else - clk = clk_register_divider(NULL, list->name, - list->parent_name, list->flags, - reg_base + list->offset, list->shift, - list->width, list->div_flags, &lock); - if (IS_ERR(clk)) { + clk_hw = clk_hw_register_divider(ctx->dev, list->name, + list->parent_name, list->flags, + ctx->reg_base + list->offset, list->shift, + list->width, list->div_flags, &ctx->lock); + if (IS_ERR(clk_hw)) { pr_err("%s: failed to register clock %s\n", __func__, list->name); continue; } - samsung_clk_add_lookup(clk, list->id); - - /* register a clock lookup only if a clock alias is specified */ - if (list->alias) { - ret = clk_register_clkdev(clk, list->alias, - list->dev_name); - if (ret) - pr_err("%s: failed to register lookup %s\n", - __func__, list->alias); - } + samsung_clk_add_lookup(ctx, clk_hw, list->id); } } /* register a list of gate clocks */ -void __init samsung_clk_register_gate(struct samsung_gate_clock *list, - unsigned int nr_clk) +void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx, + const struct samsung_gate_clock *list, + unsigned int nr_clk) { - struct clk *clk; - unsigned int idx, ret; + struct clk_hw *clk_hw; + unsigned int idx; for (idx = 0; idx < nr_clk; idx++, list++) { - clk = clk_register_gate(NULL, list->name, list->parent_name, - list->flags, reg_base + list->offset, - list->bit_idx, list->gate_flags, &lock); - if (IS_ERR(clk)) { + clk_hw = clk_hw_register_gate(ctx->dev, list->name, list->parent_name, + list->flags, ctx->reg_base + list->offset, + list->bit_idx, list->gate_flags, &ctx->lock); + if (IS_ERR(clk_hw)) { pr_err("%s: failed to register clock %s\n", __func__, list->name); continue; } - /* register a clock lookup only if a clock alias is specified */ - if (list->alias) { - ret = clk_register_clkdev(clk, list->alias, - list->dev_name); - if (ret) - pr_err("%s: failed to register lookup %s\n", - __func__, list->alias); - } - - samsung_clk_add_lookup(clk, list->id); + samsung_clk_add_lookup(ctx, clk_hw, list->id); } } @@ -284,37 +253,136 @@ void __init samsung_clk_register_gate(struct samsung_gate_clock *list, * obtain the clock speed of all external fixed clock sources from device * tree and register it */ -#ifdef CONFIG_OF -void __init samsung_clk_of_register_fixed_ext( +void __init samsung_clk_of_register_fixed_ext(struct samsung_clk_provider *ctx, struct samsung_fixed_rate_clock *fixed_rate_clk, unsigned int nr_fixed_rate_clk, - struct of_device_id *clk_matches) + const struct of_device_id *clk_matches) { const struct of_device_id *match; - struct device_node *np; + struct device_node *clk_np; u32 freq; - for_each_matching_node_and_match(np, clk_matches, &match) { - if (of_property_read_u32(np, "clock-frequency", &freq)) + for_each_matching_node_and_match(clk_np, clk_matches, &match) { + if (of_property_read_u32(clk_np, "clock-frequency", &freq)) continue; - fixed_rate_clk[(u32)match->data].fixed_rate = freq; + fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq; } - samsung_clk_register_fixed_rate(fixed_rate_clk, nr_fixed_rate_clk); + samsung_clk_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk); +} + +#ifdef CONFIG_PM_SLEEP +static int samsung_clk_suspend(void *data) +{ + struct samsung_clock_reg_cache *reg_cache; + + list_for_each_entry(reg_cache, &clock_reg_cache_list, node) { + samsung_clk_save(reg_cache->reg_base, reg_cache->rdump, + reg_cache->rd_num); + samsung_clk_restore(reg_cache->reg_base, reg_cache->rsuspend, + reg_cache->rsuspend_num); + } + return 0; +} + +static void samsung_clk_resume(void *data) +{ + struct samsung_clock_reg_cache *reg_cache; + + list_for_each_entry(reg_cache, &clock_reg_cache_list, node) + samsung_clk_restore(reg_cache->reg_base, reg_cache->rdump, + reg_cache->rd_num); +} + +static const struct syscore_ops samsung_clk_syscore_ops = { + .suspend = samsung_clk_suspend, + .resume = samsung_clk_resume, +}; + +static struct syscore samsung_clk_syscore = { + .ops = &samsung_clk_syscore_ops, +}; + +void samsung_clk_extended_sleep_init(void __iomem *reg_base, + const unsigned long *rdump, + unsigned long nr_rdump, + const struct samsung_clk_reg_dump *rsuspend, + unsigned long nr_rsuspend) +{ + struct samsung_clock_reg_cache *reg_cache; + + reg_cache = kzalloc(sizeof(struct samsung_clock_reg_cache), + GFP_KERNEL); + if (!reg_cache) + panic("could not allocate register reg_cache.\n"); + reg_cache->rdump = samsung_clk_alloc_reg_dump(rdump, nr_rdump); + + if (!reg_cache->rdump) + panic("could not allocate register dump storage.\n"); + + if (list_empty(&clock_reg_cache_list)) + register_syscore(&samsung_clk_syscore); + + reg_cache->reg_base = reg_base; + reg_cache->rd_num = nr_rdump; + reg_cache->rsuspend = rsuspend; + reg_cache->rsuspend_num = nr_rsuspend; + list_add_tail(®_cache->node, &clock_reg_cache_list); } #endif -/* utility function to get the rate of a specified clock */ -unsigned long _get_rate(const char *clk_name) +/** + * samsung_cmu_register_clocks() - Register all clocks provided in CMU object + * @ctx: Clock provider object + * @cmu: CMU object with clocks to register + */ +void __init samsung_cmu_register_clocks(struct samsung_clk_provider *ctx, + const struct samsung_cmu_info *cmu) { - struct clk *clk; - unsigned long rate; + if (cmu->pll_clks) + samsung_clk_register_pll(ctx, cmu->pll_clks, cmu->nr_pll_clks); + if (cmu->mux_clks) + samsung_clk_register_mux(ctx, cmu->mux_clks, cmu->nr_mux_clks); + if (cmu->div_clks) + samsung_clk_register_div(ctx, cmu->div_clks, cmu->nr_div_clks); + if (cmu->gate_clks) + samsung_clk_register_gate(ctx, cmu->gate_clks, + cmu->nr_gate_clks); + if (cmu->fixed_clks) + samsung_clk_register_fixed_rate(ctx, cmu->fixed_clks, + cmu->nr_fixed_clks); + if (cmu->fixed_factor_clks) + samsung_clk_register_fixed_factor(ctx, cmu->fixed_factor_clks, + cmu->nr_fixed_factor_clks); + if (cmu->cpu_clks) + samsung_clk_register_cpu(ctx, cmu->cpu_clks, cmu->nr_cpu_clks); +} + +/* + * Common function which registers plls, muxes, dividers and gates + * for each CMU. It also add CMU register list to register cache. + */ +struct samsung_clk_provider * __init samsung_cmu_register_one( + struct device_node *np, + const struct samsung_cmu_info *cmu) +{ + void __iomem *reg_base; + struct samsung_clk_provider *ctx; - clk = clk_get(NULL, clk_name); - if (IS_ERR(clk)) { - pr_err("%s: could not find clock %s\n", __func__, clk_name); - return 0; + reg_base = of_iomap(np, 0); + if (!reg_base) { + panic("%s: failed to map registers\n", __func__); + return NULL; } - rate = clk_get_rate(clk); - clk_put(clk); - return rate; + + ctx = samsung_clk_init(NULL, reg_base, cmu->nr_clk_ids); + samsung_cmu_register_clocks(ctx, cmu); + + if (cmu->clk_regs) + samsung_clk_extended_sleep_init(reg_base, + cmu->clk_regs, cmu->nr_clk_regs, + cmu->suspend_regs, cmu->nr_suspend_regs); + + samsung_clk_of_add_provider(np, ctx); + + return ctx; } |
