diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/clk/mvebu | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/clk/mvebu')
25 files changed, 5386 insertions, 0 deletions
diff --git a/drivers/clk/mvebu/Kconfig b/drivers/clk/mvebu/Kconfig new file mode 100644 index 000000000..557d62137 --- /dev/null +++ b/drivers/clk/mvebu/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-only +config MVEBU_CLK_COMMON + bool + +config MVEBU_CLK_CPU + bool + +config MVEBU_CLK_COREDIV + bool + +config ARMADA_AP_CP_HELPER + bool + +config ARMADA_370_CLK + bool + select MVEBU_CLK_COMMON + select MVEBU_CLK_CPU + +config ARMADA_375_CLK + bool + select MVEBU_CLK_COMMON + +config ARMADA_38X_CLK + bool + select MVEBU_CLK_COMMON + +config ARMADA_39X_CLK + bool + select MVEBU_CLK_COMMON + +config ARMADA_37XX_CLK + bool + +config ARMADA_XP_CLK + bool + select MVEBU_CLK_COMMON + select MVEBU_CLK_CPU + +config ARMADA_AP806_SYSCON + bool + select ARMADA_AP_CP_HELPER + +config ARMADA_AP_CPU_CLK + bool + select ARMADA_AP_CP_HELPER + +config ARMADA_CP110_SYSCON + bool + select ARMADA_AP_CP_HELPER + +config DOVE_CLK + bool + select MVEBU_CLK_COMMON + +config KIRKWOOD_CLK + bool + select MVEBU_CLK_COMMON + +config ORION_CLK + bool + select MVEBU_CLK_COMMON diff --git a/drivers/clk/mvebu/Makefile b/drivers/clk/mvebu/Makefile new file mode 100644 index 000000000..04464cef0 --- /dev/null +++ b/drivers/clk/mvebu/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MVEBU_CLK_COMMON) += common.o +obj-$(CONFIG_MVEBU_CLK_CPU) += clk-cpu.o +obj-$(CONFIG_MVEBU_CLK_COREDIV) += clk-corediv.o +obj-$(CONFIG_ARMADA_AP_CP_HELPER) += armada_ap_cp_helper.o + +obj-$(CONFIG_ARMADA_370_CLK) += armada-370.o +obj-$(CONFIG_ARMADA_375_CLK) += armada-375.o +obj-$(CONFIG_ARMADA_38X_CLK) += armada-38x.o +obj-$(CONFIG_ARMADA_39X_CLK) += armada-39x.o +obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-xtal.o +obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-tbg.o +obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-periph.o +obj-$(CONFIG_ARMADA_XP_CLK) += armada-xp.o mv98dx3236.o +obj-$(CONFIG_ARMADA_AP806_SYSCON) += ap806-system-controller.o +obj-$(CONFIG_ARMADA_AP_CPU_CLK) += ap-cpu-clk.o +obj-$(CONFIG_ARMADA_CP110_SYSCON) += cp110-system-controller.o +obj-$(CONFIG_DOVE_CLK) += dove.o dove-divider.o +obj-$(CONFIG_KIRKWOOD_CLK) += kirkwood.o +obj-$(CONFIG_ORION_CLK) += orion.o diff --git a/drivers/clk/mvebu/ap-cpu-clk.c b/drivers/clk/mvebu/ap-cpu-clk.c new file mode 100644 index 000000000..1e44ace7d --- /dev/null +++ b/drivers/clk/mvebu/ap-cpu-clk.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell Armada AP CPU Clock Controller + * + * Copyright (C) 2018 Marvell + * + * Omri Itach <omrii@marvell.com> + * Gregory Clement <gregory.clement@bootlin.com> + */ + +#define pr_fmt(fmt) "ap-cpu-clk: " fmt + +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include "armada_ap_cp_helper.h" + +#define AP806_CPU_CLUSTER0 0 +#define AP806_CPU_CLUSTER1 1 +#define AP806_CPUS_PER_CLUSTER 2 +#define APN806_CPU1_MASK 0x1 + +#define APN806_CLUSTER_NUM_OFFSET 8 +#define APN806_CLUSTER_NUM_MASK BIT(APN806_CLUSTER_NUM_OFFSET) + +#define APN806_MAX_DIVIDER 32 + +/* + * struct cpu_dfs_regs: CPU DFS register mapping + * @divider_reg: full integer ratio from PLL frequency to CPU clock frequency + * @force_reg: request to force new ratio regardless of relation to other clocks + * @ratio_reg: central request to switch ratios + */ +struct cpu_dfs_regs { + unsigned int divider_reg; + unsigned int force_reg; + unsigned int ratio_reg; + unsigned int ratio_state_reg; + unsigned int divider_mask; + unsigned int cluster_offset; + unsigned int force_mask; + int divider_offset; + int divider_ratio; + int ratio_offset; + int ratio_state_offset; + int ratio_state_cluster_offset; +}; + +/* AP806 CPU DFS register mapping*/ +#define AP806_CA72MP2_0_PLL_CR_0_REG_OFFSET 0x278 +#define AP806_CA72MP2_0_PLL_CR_1_REG_OFFSET 0x280 +#define AP806_CA72MP2_0_PLL_CR_2_REG_OFFSET 0x284 +#define AP806_CA72MP2_0_PLL_SR_REG_OFFSET 0xC94 + +#define AP806_CA72MP2_0_PLL_CR_CLUSTER_OFFSET 0x14 +#define AP806_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET 0 +#define AP806_PLL_CR_CPU_CLK_DIV_RATIO 0 +#define AP806_PLL_CR_0_CPU_CLK_DIV_RATIO_MASK \ + (0x3f << AP806_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET) +#define AP806_PLL_CR_0_CPU_CLK_RELOAD_FORCE_OFFSET 24 +#define AP806_PLL_CR_0_CPU_CLK_RELOAD_FORCE_MASK \ + (0x1 << AP806_PLL_CR_0_CPU_CLK_RELOAD_FORCE_OFFSET) +#define AP806_PLL_CR_0_CPU_CLK_RELOAD_RATIO_OFFSET 16 +#define AP806_CA72MP2_0_PLL_RATIO_STABLE_OFFSET 0 +#define AP806_CA72MP2_0_PLL_RATIO_STATE 11 + +#define STATUS_POLL_PERIOD_US 1 +#define STATUS_POLL_TIMEOUT_US 1000000 + +#define to_ap_cpu_clk(_hw) container_of(_hw, struct ap_cpu_clk, hw) + +static const struct cpu_dfs_regs ap806_dfs_regs = { + .divider_reg = AP806_CA72MP2_0_PLL_CR_0_REG_OFFSET, + .force_reg = AP806_CA72MP2_0_PLL_CR_1_REG_OFFSET, + .ratio_reg = AP806_CA72MP2_0_PLL_CR_2_REG_OFFSET, + .ratio_state_reg = AP806_CA72MP2_0_PLL_SR_REG_OFFSET, + .divider_mask = AP806_PLL_CR_0_CPU_CLK_DIV_RATIO_MASK, + .cluster_offset = AP806_CA72MP2_0_PLL_CR_CLUSTER_OFFSET, + .force_mask = AP806_PLL_CR_0_CPU_CLK_RELOAD_FORCE_MASK, + .divider_offset = AP806_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET, + .divider_ratio = AP806_PLL_CR_CPU_CLK_DIV_RATIO, + .ratio_offset = AP806_PLL_CR_0_CPU_CLK_RELOAD_RATIO_OFFSET, + .ratio_state_offset = AP806_CA72MP2_0_PLL_RATIO_STABLE_OFFSET, + .ratio_state_cluster_offset = AP806_CA72MP2_0_PLL_RATIO_STABLE_OFFSET, +}; + +/* AP807 CPU DFS register mapping */ +#define AP807_DEVICE_GENERAL_CONTROL_10_REG_OFFSET 0x278 +#define AP807_DEVICE_GENERAL_CONTROL_11_REG_OFFSET 0x27c +#define AP807_DEVICE_GENERAL_STATUS_6_REG_OFFSET 0xc98 +#define AP807_CA72MP2_0_PLL_CR_CLUSTER_OFFSET 0x8 +#define AP807_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET 18 +#define AP807_PLL_CR_0_CPU_CLK_DIV_RATIO_MASK \ + (0x3f << AP807_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET) +#define AP807_PLL_CR_1_CPU_CLK_DIV_RATIO_OFFSET 12 +#define AP807_PLL_CR_1_CPU_CLK_DIV_RATIO_MASK \ + (0x3f << AP807_PLL_CR_1_CPU_CLK_DIV_RATIO_OFFSET) +#define AP807_PLL_CR_CPU_CLK_DIV_RATIO 3 +#define AP807_PLL_CR_0_CPU_CLK_RELOAD_FORCE_OFFSET 0 +#define AP807_PLL_CR_0_CPU_CLK_RELOAD_FORCE_MASK \ + (0x3 << AP807_PLL_CR_0_CPU_CLK_RELOAD_FORCE_OFFSET) +#define AP807_PLL_CR_0_CPU_CLK_RELOAD_RATIO_OFFSET 6 +#define AP807_CA72MP2_0_PLL_CLKDIV_RATIO_STABLE_OFFSET 20 +#define AP807_CA72MP2_0_PLL_CLKDIV_RATIO_STABLE_CLUSTER_OFFSET 3 + +static const struct cpu_dfs_regs ap807_dfs_regs = { + .divider_reg = AP807_DEVICE_GENERAL_CONTROL_10_REG_OFFSET, + .force_reg = AP807_DEVICE_GENERAL_CONTROL_11_REG_OFFSET, + .ratio_reg = AP807_DEVICE_GENERAL_CONTROL_11_REG_OFFSET, + .ratio_state_reg = AP807_DEVICE_GENERAL_STATUS_6_REG_OFFSET, + .divider_mask = AP807_PLL_CR_0_CPU_CLK_DIV_RATIO_MASK, + .cluster_offset = AP807_CA72MP2_0_PLL_CR_CLUSTER_OFFSET, + .force_mask = AP807_PLL_CR_0_CPU_CLK_RELOAD_FORCE_MASK, + .divider_offset = AP807_PLL_CR_0_CPU_CLK_DIV_RATIO_OFFSET, + .divider_ratio = AP807_PLL_CR_CPU_CLK_DIV_RATIO, + .ratio_offset = AP807_PLL_CR_0_CPU_CLK_RELOAD_RATIO_OFFSET, + .ratio_state_offset = AP807_CA72MP2_0_PLL_CLKDIV_RATIO_STABLE_OFFSET, + .ratio_state_cluster_offset = + AP807_CA72MP2_0_PLL_CLKDIV_RATIO_STABLE_CLUSTER_OFFSET +}; + +/* + * struct ap806_clk: CPU cluster clock controller instance + * @cluster: Cluster clock controller index + * @clk_name: Cluster clock controller name + * @dev : Cluster clock device + * @hw: HW specific structure of Cluster clock controller + * @pll_cr_base: CA72MP2 Register base (Device Sample at Reset register) + */ +struct ap_cpu_clk { + unsigned int cluster; + const char *clk_name; + struct device *dev; + struct clk_hw hw; + struct regmap *pll_cr_base; + const struct cpu_dfs_regs *pll_regs; +}; + +static unsigned long ap_cpu_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ap_cpu_clk *clk = to_ap_cpu_clk(hw); + unsigned int cpu_clkdiv_reg; + int cpu_clkdiv_ratio; + + cpu_clkdiv_reg = clk->pll_regs->divider_reg + + (clk->cluster * clk->pll_regs->cluster_offset); + regmap_read(clk->pll_cr_base, cpu_clkdiv_reg, &cpu_clkdiv_ratio); + cpu_clkdiv_ratio &= clk->pll_regs->divider_mask; + cpu_clkdiv_ratio >>= clk->pll_regs->divider_offset; + + return parent_rate / cpu_clkdiv_ratio; +} + +static int ap_cpu_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ap_cpu_clk *clk = to_ap_cpu_clk(hw); + int ret, reg, divider = parent_rate / rate; + unsigned int cpu_clkdiv_reg, cpu_force_reg, cpu_ratio_reg, stable_bit; + + cpu_clkdiv_reg = clk->pll_regs->divider_reg + + (clk->cluster * clk->pll_regs->cluster_offset); + cpu_force_reg = clk->pll_regs->force_reg + + (clk->cluster * clk->pll_regs->cluster_offset); + cpu_ratio_reg = clk->pll_regs->ratio_reg + + (clk->cluster * clk->pll_regs->cluster_offset); + + regmap_read(clk->pll_cr_base, cpu_clkdiv_reg, ®); + reg &= ~(clk->pll_regs->divider_mask); + reg |= (divider << clk->pll_regs->divider_offset); + + /* + * AP807 CPU divider has two channels with ratio 1:3 and divider_ratio + * is 1. Otherwise, in the case of the AP806, divider_ratio is 0. + */ + if (clk->pll_regs->divider_ratio) { + reg &= ~(AP807_PLL_CR_1_CPU_CLK_DIV_RATIO_MASK); + reg |= ((divider * clk->pll_regs->divider_ratio) << + AP807_PLL_CR_1_CPU_CLK_DIV_RATIO_OFFSET); + } + regmap_write(clk->pll_cr_base, cpu_clkdiv_reg, reg); + + + regmap_update_bits(clk->pll_cr_base, cpu_force_reg, + clk->pll_regs->force_mask, + clk->pll_regs->force_mask); + + regmap_update_bits(clk->pll_cr_base, cpu_ratio_reg, + BIT(clk->pll_regs->ratio_offset), + BIT(clk->pll_regs->ratio_offset)); + + stable_bit = BIT(clk->pll_regs->ratio_state_offset + + clk->cluster * + clk->pll_regs->ratio_state_cluster_offset); + ret = regmap_read_poll_timeout(clk->pll_cr_base, + clk->pll_regs->ratio_state_reg, reg, + reg & stable_bit, STATUS_POLL_PERIOD_US, + STATUS_POLL_TIMEOUT_US); + if (ret) + return ret; + + regmap_update_bits(clk->pll_cr_base, cpu_ratio_reg, + BIT(clk->pll_regs->ratio_offset), 0); + + return 0; +} + +static int ap_cpu_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int divider = req->best_parent_rate / req->rate; + + divider = min(divider, APN806_MAX_DIVIDER); + + req->rate = req->best_parent_rate / divider; + + return 0; +} + +static const struct clk_ops ap_cpu_clk_ops = { + .recalc_rate = ap_cpu_clk_recalc_rate, + .determine_rate = ap_cpu_clk_determine_rate, + .set_rate = ap_cpu_clk_set_rate, +}; + +static int ap_cpu_clock_probe(struct platform_device *pdev) +{ + int ret, nclusters = 0, cluster_index = 0; + struct device *dev = &pdev->dev; + struct device_node *dn, *np = dev->of_node; + struct clk_hw_onecell_data *ap_cpu_data; + struct ap_cpu_clk *ap_cpu_clk; + struct regmap *regmap; + + regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(regmap)) { + pr_err("cannot get pll_cr_base regmap\n"); + return PTR_ERR(regmap); + } + + /* + * AP806 has 4 cpus and DFS for AP806 is controlled per + * cluster (2 CPUs per cluster), cpu0 and cpu1 are fixed to + * cluster0 while cpu2 and cpu3 are fixed to cluster1 whether + * they are enabled or not. Since cpu0 is the boot cpu, then + * cluster0 must exist. If cpu2 or cpu3 is enabled, cluster1 + * will exist and the cluster number is 2; otherwise the + * cluster number is 1. + */ + nclusters = 1; + for_each_of_cpu_node(dn) { + u64 cpu; + + cpu = of_get_cpu_hwid(dn, 0); + if (WARN_ON(cpu == OF_BAD_ADDR)) { + of_node_put(dn); + return -EINVAL; + } + + /* If cpu2 or cpu3 is enabled */ + if (cpu & APN806_CLUSTER_NUM_MASK) { + nclusters = 2; + of_node_put(dn); + break; + } + } + /* + * DFS for AP806 is controlled per cluster (2 CPUs per cluster), + * so allocate structs per cluster + */ + ap_cpu_clk = devm_kcalloc(dev, nclusters, sizeof(*ap_cpu_clk), + GFP_KERNEL); + if (!ap_cpu_clk) + return -ENOMEM; + + ap_cpu_data = devm_kzalloc(dev, struct_size(ap_cpu_data, hws, + nclusters), + GFP_KERNEL); + if (!ap_cpu_data) + return -ENOMEM; + + for_each_of_cpu_node(dn) { + char *clk_name = "cpu-cluster-0"; + struct clk_init_data init; + const char *parent_name; + struct clk *parent; + u64 cpu; + + cpu = of_get_cpu_hwid(dn, 0); + if (WARN_ON(cpu == OF_BAD_ADDR)) { + of_node_put(dn); + return -EINVAL; + } + + cluster_index = cpu & APN806_CLUSTER_NUM_MASK; + cluster_index >>= APN806_CLUSTER_NUM_OFFSET; + + /* Initialize once for one cluster */ + if (ap_cpu_data->hws[cluster_index]) + continue; + + parent = of_clk_get(np, cluster_index); + if (IS_ERR(parent)) { + dev_err(dev, "Could not get the clock parent\n"); + of_node_put(dn); + return -EINVAL; + } + parent_name = __clk_get_name(parent); + clk_name[12] += cluster_index; + ap_cpu_clk[cluster_index].clk_name = + ap_cp_unique_name(dev, np->parent, clk_name); + ap_cpu_clk[cluster_index].cluster = cluster_index; + ap_cpu_clk[cluster_index].pll_cr_base = regmap; + ap_cpu_clk[cluster_index].hw.init = &init; + ap_cpu_clk[cluster_index].dev = dev; + ap_cpu_clk[cluster_index].pll_regs = of_device_get_match_data(&pdev->dev); + + init.name = ap_cpu_clk[cluster_index].clk_name; + init.ops = &ap_cpu_clk_ops; + init.num_parents = 1; + init.parent_names = &parent_name; + + ret = devm_clk_hw_register(dev, &ap_cpu_clk[cluster_index].hw); + if (ret) { + of_node_put(dn); + return ret; + } + ap_cpu_data->hws[cluster_index] = &ap_cpu_clk[cluster_index].hw; + } + + ap_cpu_data->num = cluster_index + 1; + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, ap_cpu_data); + if (ret) + dev_err(dev, "failed to register OF clock provider\n"); + + return ret; +} + +static const struct of_device_id ap_cpu_clock_of_match[] = { + { + .compatible = "marvell,ap806-cpu-clock", + .data = &ap806_dfs_regs, + }, + { + .compatible = "marvell,ap807-cpu-clock", + .data = &ap807_dfs_regs, + }, + { } +}; + +static struct platform_driver ap_cpu_clock_driver = { + .probe = ap_cpu_clock_probe, + .driver = { + .name = "marvell-ap-cpu-clock", + .of_match_table = ap_cpu_clock_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ap_cpu_clock_driver); diff --git a/drivers/clk/mvebu/ap806-system-controller.c b/drivers/clk/mvebu/ap806-system-controller.c new file mode 100644 index 000000000..948bd1e71 --- /dev/null +++ b/drivers/clk/mvebu/ap806-system-controller.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada AP806 System Controller + * + * Copyright (C) 2016 Marvell + * + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + */ + +#define pr_fmt(fmt) "ap806-system-controller: " fmt + +#include "armada_ap_cp_helper.h" +#include <linux/clk-provider.h> +#include <linux/mfd/syscon.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define AP806_SAR_REG 0x400 +#define AP806_SAR_CLKFREQ_MODE_MASK 0x1f + +#define AP806_CLK_NUM 6 + +static struct clk *ap806_clks[AP806_CLK_NUM]; + +static struct clk_onecell_data ap806_clk_data = { + .clks = ap806_clks, + .clk_num = AP806_CLK_NUM, +}; + +static int ap806_get_sar_clocks(unsigned int freq_mode, + unsigned int *cpuclk_freq, + unsigned int *dclk_freq) +{ + switch (freq_mode) { + case 0x0: + *cpuclk_freq = 2000; + *dclk_freq = 600; + break; + case 0x1: + *cpuclk_freq = 2000; + *dclk_freq = 525; + break; + case 0x6: + *cpuclk_freq = 1800; + *dclk_freq = 600; + break; + case 0x7: + *cpuclk_freq = 1800; + *dclk_freq = 525; + break; + case 0x4: + *cpuclk_freq = 1600; + *dclk_freq = 400; + break; + case 0xB: + *cpuclk_freq = 1600; + *dclk_freq = 450; + break; + case 0xD: + *cpuclk_freq = 1600; + *dclk_freq = 525; + break; + case 0x1a: + *cpuclk_freq = 1400; + *dclk_freq = 400; + break; + case 0x14: + *cpuclk_freq = 1300; + *dclk_freq = 400; + break; + case 0x17: + *cpuclk_freq = 1300; + *dclk_freq = 325; + break; + case 0x19: + *cpuclk_freq = 1200; + *dclk_freq = 400; + break; + case 0x13: + *cpuclk_freq = 1000; + *dclk_freq = 325; + break; + case 0x1d: + *cpuclk_freq = 1000; + *dclk_freq = 400; + break; + case 0x1c: + *cpuclk_freq = 800; + *dclk_freq = 400; + break; + case 0x1b: + *cpuclk_freq = 600; + *dclk_freq = 400; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ap807_get_sar_clocks(unsigned int freq_mode, + unsigned int *cpuclk_freq, + unsigned int *dclk_freq) +{ + switch (freq_mode) { + case 0x0: + *cpuclk_freq = 2000; + *dclk_freq = 1200; + break; + case 0x6: + *cpuclk_freq = 2200; + *dclk_freq = 1200; + break; + case 0xD: + *cpuclk_freq = 1600; + *dclk_freq = 1200; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ap806_syscon_common_probe(struct platform_device *pdev, + struct device_node *syscon_node) +{ + unsigned int freq_mode, cpuclk_freq, dclk_freq; + const char *name, *fixedclk_name; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regmap *regmap; + u32 reg; + int ret; + + regmap = syscon_node_to_regmap(syscon_node); + if (IS_ERR(regmap)) { + dev_err(dev, "cannot get regmap\n"); + return PTR_ERR(regmap); + } + + ret = regmap_read(regmap, AP806_SAR_REG, ®); + if (ret) { + dev_err(dev, "cannot read from regmap\n"); + return ret; + } + + freq_mode = reg & AP806_SAR_CLKFREQ_MODE_MASK; + + if (of_device_is_compatible(pdev->dev.of_node, + "marvell,ap806-clock")) { + ret = ap806_get_sar_clocks(freq_mode, &cpuclk_freq, &dclk_freq); + } else if (of_device_is_compatible(pdev->dev.of_node, + "marvell,ap807-clock")) { + ret = ap807_get_sar_clocks(freq_mode, &cpuclk_freq, &dclk_freq); + } else { + dev_err(dev, "compatible not supported\n"); + return -EINVAL; + } + + if (ret) { + dev_err(dev, "invalid Sample at Reset value\n"); + return ret; + } + + /* Convert to hertz */ + cpuclk_freq *= 1000 * 1000; + dclk_freq *= 1000 * 1000; + + /* CPU clocks depend on the Sample At Reset configuration */ + name = ap_cp_unique_name(dev, syscon_node, "pll-cluster-0"); + ap806_clks[0] = clk_register_fixed_rate(dev, name, NULL, + 0, cpuclk_freq); + if (IS_ERR(ap806_clks[0])) { + ret = PTR_ERR(ap806_clks[0]); + goto fail0; + } + + name = ap_cp_unique_name(dev, syscon_node, "pll-cluster-1"); + ap806_clks[1] = clk_register_fixed_rate(dev, name, NULL, 0, + cpuclk_freq); + if (IS_ERR(ap806_clks[1])) { + ret = PTR_ERR(ap806_clks[1]); + goto fail1; + } + + /* Fixed clock is always 1200 Mhz */ + fixedclk_name = ap_cp_unique_name(dev, syscon_node, "fixed"); + ap806_clks[2] = clk_register_fixed_rate(dev, fixedclk_name, NULL, + 0, 1200 * 1000 * 1000); + if (IS_ERR(ap806_clks[2])) { + ret = PTR_ERR(ap806_clks[2]); + goto fail2; + } + + /* MSS Clock is fixed clock divided by 6 */ + name = ap_cp_unique_name(dev, syscon_node, "mss"); + ap806_clks[3] = clk_register_fixed_factor(NULL, name, fixedclk_name, + 0, 1, 6); + if (IS_ERR(ap806_clks[3])) { + ret = PTR_ERR(ap806_clks[3]); + goto fail3; + } + + /* SDIO(/eMMC) Clock is fixed clock divided by 3 */ + name = ap_cp_unique_name(dev, syscon_node, "sdio"); + ap806_clks[4] = clk_register_fixed_factor(NULL, name, + fixedclk_name, + 0, 1, 3); + if (IS_ERR(ap806_clks[4])) { + ret = PTR_ERR(ap806_clks[4]); + goto fail4; + } + + /* AP-DCLK(HCLK) Clock is DDR clock divided by 2 */ + name = ap_cp_unique_name(dev, syscon_node, "ap-dclk"); + ap806_clks[5] = clk_register_fixed_rate(dev, name, NULL, 0, dclk_freq); + if (IS_ERR(ap806_clks[5])) { + ret = PTR_ERR(ap806_clks[5]); + goto fail5; + } + + ret = of_clk_add_provider(np, of_clk_src_onecell_get, &ap806_clk_data); + if (ret) + goto fail_clk_add; + + return 0; + +fail_clk_add: + clk_unregister_fixed_factor(ap806_clks[5]); +fail5: + clk_unregister_fixed_factor(ap806_clks[4]); +fail4: + clk_unregister_fixed_factor(ap806_clks[3]); +fail3: + clk_unregister_fixed_rate(ap806_clks[2]); +fail2: + clk_unregister_fixed_rate(ap806_clks[1]); +fail1: + clk_unregister_fixed_rate(ap806_clks[0]); +fail0: + return ret; +} + +static int ap806_syscon_legacy_probe(struct platform_device *pdev) +{ + dev_warn(&pdev->dev, FW_WARN "Using legacy device tree binding\n"); + dev_warn(&pdev->dev, FW_WARN "Update your device tree:\n"); + dev_warn(&pdev->dev, FW_WARN + "This binding won't be supported in future kernel\n"); + + return ap806_syscon_common_probe(pdev, pdev->dev.of_node); + +} + +static int ap806_clock_probe(struct platform_device *pdev) +{ + return ap806_syscon_common_probe(pdev, pdev->dev.of_node->parent); +} + +static const struct of_device_id ap806_syscon_legacy_of_match[] = { + { .compatible = "marvell,ap806-system-controller", }, + { } +}; + +static struct platform_driver ap806_syscon_legacy_driver = { + .probe = ap806_syscon_legacy_probe, + .driver = { + .name = "marvell-ap806-system-controller", + .of_match_table = ap806_syscon_legacy_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ap806_syscon_legacy_driver); + +static const struct of_device_id ap806_clock_of_match[] = { + { .compatible = "marvell,ap806-clock", }, + { .compatible = "marvell,ap807-clock", }, + { } +}; + +static struct platform_driver ap806_clock_driver = { + .probe = ap806_clock_probe, + .driver = { + .name = "marvell-ap806-clock", + .of_match_table = ap806_clock_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ap806_clock_driver); diff --git a/drivers/clk/mvebu/armada-370.c b/drivers/clk/mvebu/armada-370.c new file mode 100644 index 000000000..5c6bbee39 --- /dev/null +++ b/drivers/clk/mvebu/armada-370.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada 370 SoC clocks + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +/* + * Core Clocks + */ + +#define SARL 0 /* Low part [0:31] */ +#define SARL_A370_SSCG_ENABLE BIT(10) +#define SARL_A370_PCLK_FREQ_OPT 11 +#define SARL_A370_PCLK_FREQ_OPT_MASK 0xF +#define SARL_A370_FAB_FREQ_OPT 15 +#define SARL_A370_FAB_FREQ_OPT_MASK 0x1F +#define SARL_A370_TCLK_FREQ_OPT 20 +#define SARL_A370_TCLK_FREQ_OPT_MASK 0x1 + +enum { A370_CPU_TO_NBCLK, A370_CPU_TO_HCLK, A370_CPU_TO_DRAMCLK }; + +static const struct coreclk_ratio a370_coreclk_ratios[] __initconst = { + { .id = A370_CPU_TO_NBCLK, .name = "nbclk" }, + { .id = A370_CPU_TO_HCLK, .name = "hclk" }, + { .id = A370_CPU_TO_DRAMCLK, .name = "dramclk" }, +}; + +static const u32 a370_tclk_freqs[] __initconst = { + 166000000, + 200000000, +}; + +static u32 __init a370_get_tclk_freq(void __iomem *sar) +{ + u8 tclk_freq_select = 0; + + tclk_freq_select = ((readl(sar) >> SARL_A370_TCLK_FREQ_OPT) & + SARL_A370_TCLK_FREQ_OPT_MASK); + return a370_tclk_freqs[tclk_freq_select]; +} + +static const u32 a370_cpu_freqs[] __initconst = { + 400000000, + 533000000, + 667000000, + 800000000, + 1000000000, + 1067000000, + 1200000000, +}; + +static u32 __init a370_get_cpu_freq(void __iomem *sar) +{ + u32 cpu_freq; + u8 cpu_freq_select = 0; + + cpu_freq_select = ((readl(sar) >> SARL_A370_PCLK_FREQ_OPT) & + SARL_A370_PCLK_FREQ_OPT_MASK); + if (cpu_freq_select >= ARRAY_SIZE(a370_cpu_freqs)) { + pr_err("CPU freq select unsupported %d\n", cpu_freq_select); + cpu_freq = 0; + } else + cpu_freq = a370_cpu_freqs[cpu_freq_select]; + + return cpu_freq; +} + +static const int a370_nbclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 2}, {2, 2}, + {1, 2}, {1, 2}, {1, 1}, {2, 3}, + {0, 1}, {1, 2}, {2, 4}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {2, 2}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {2, 3}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int a370_hclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 6}, {2, 3}, + {1, 3}, {1, 4}, {1, 2}, {2, 6}, + {0, 1}, {1, 6}, {2, 10}, {0, 1}, + {1, 4}, {0, 1}, {0, 1}, {2, 5}, + {0, 1}, {0, 1}, {0, 1}, {1, 2}, + {2, 6}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int a370_dramclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 3}, {2, 3}, + {1, 3}, {1, 2}, {1, 2}, {2, 6}, + {0, 1}, {1, 3}, {2, 5}, {0, 1}, + {1, 4}, {0, 1}, {0, 1}, {2, 5}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {2, 3}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static void __init a370_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + u32 opt = ((readl(sar) >> SARL_A370_FAB_FREQ_OPT) & + SARL_A370_FAB_FREQ_OPT_MASK); + + switch (id) { + case A370_CPU_TO_NBCLK: + *mult = a370_nbclk_ratios[opt][0]; + *div = a370_nbclk_ratios[opt][1]; + break; + case A370_CPU_TO_HCLK: + *mult = a370_hclk_ratios[opt][0]; + *div = a370_hclk_ratios[opt][1]; + break; + case A370_CPU_TO_DRAMCLK: + *mult = a370_dramclk_ratios[opt][0]; + *div = a370_dramclk_ratios[opt][1]; + break; + } +} + +static bool a370_is_sscg_enabled(void __iomem *sar) +{ + return !(readl(sar) & SARL_A370_SSCG_ENABLE); +} + +static const struct coreclk_soc_desc a370_coreclks = { + .get_tclk_freq = a370_get_tclk_freq, + .get_cpu_freq = a370_get_cpu_freq, + .get_clk_ratio = a370_get_clk_ratio, + .is_sscg_enabled = a370_is_sscg_enabled, + .fix_sscg_deviation = kirkwood_fix_sscg_deviation, + .ratios = a370_coreclk_ratios, + .num_ratios = ARRAY_SIZE(a370_coreclk_ratios), +}; + +/* + * Clock Gating Control + */ + +static const struct clk_gating_soc_desc a370_gating_desc[] __initconst = { + { "audio", NULL, 0, 0 }, + { "pex0_en", NULL, 1, 0 }, + { "pex1_en", NULL, 2, 0 }, + { "ge1", NULL, 3, 0 }, + { "ge0", NULL, 4, 0 }, + { "pex0", "pex0_en", 5, 0 }, + { "pex1", "pex1_en", 9, 0 }, + { "sata0", NULL, 15, 0 }, + { "sdio", NULL, 17, 0 }, + { "crypto", NULL, 23, CLK_IGNORE_UNUSED }, + { "tdm", NULL, 25, 0 }, + { "ddr", NULL, 28, CLK_IGNORE_UNUSED }, + { "sata1", NULL, 30, 0 }, + { } +}; + +static void __init a370_clk_init(struct device_node *np) +{ + struct device_node *cgnp = + of_find_compatible_node(NULL, NULL, "marvell,armada-370-gating-clock"); + + mvebu_coreclk_setup(np, &a370_coreclks); + + if (cgnp) { + mvebu_clk_gating_setup(cgnp, a370_gating_desc); + of_node_put(cgnp); + } +} +CLK_OF_DECLARE(a370_clk, "marvell,armada-370-core-clock", a370_clk_init); + diff --git a/drivers/clk/mvebu/armada-375.c b/drivers/clk/mvebu/armada-375.c new file mode 100644 index 000000000..a7157c690 --- /dev/null +++ b/drivers/clk/mvebu/armada-375.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada 375 SoC clocks + * + * Copyright (C) 2014 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +/* + * Core Clocks + */ + +/* + * For the Armada 375 SoCs, the CPU, DDR and L2 clocks frequencies are + * all modified at the same time, and not separately as for the Armada + * 370 or the Armada XP SoCs. + * + * SAR1[21:17] : CPU frequency DDR frequency L2 frequency + * 6 = 400 MHz 400 MHz 200 MHz + * 15 = 600 MHz 600 MHz 300 MHz + * 21 = 800 MHz 534 MHz 400 MHz + * 25 = 1000 MHz 500 MHz 500 MHz + * others reserved. + * + * SAR1[22] : TCLK frequency + * 0 = 166 MHz + * 1 = 200 MHz + */ + +#define SAR1_A375_TCLK_FREQ_OPT 22 +#define SAR1_A375_TCLK_FREQ_OPT_MASK 0x1 +#define SAR1_A375_CPU_DDR_L2_FREQ_OPT 17 +#define SAR1_A375_CPU_DDR_L2_FREQ_OPT_MASK 0x1F + +static const u32 armada_375_tclk_frequencies[] __initconst = { + 166000000, + 200000000, +}; + +static u32 __init armada_375_get_tclk_freq(void __iomem *sar) +{ + u8 tclk_freq_select; + + tclk_freq_select = ((readl(sar) >> SAR1_A375_TCLK_FREQ_OPT) & + SAR1_A375_TCLK_FREQ_OPT_MASK); + return armada_375_tclk_frequencies[tclk_freq_select]; +} + + +static const u32 armada_375_cpu_frequencies[] __initconst = { + 0, 0, 0, 0, 0, 0, + 400000000, + 0, 0, 0, 0, 0, 0, 0, 0, + 600000000, + 0, 0, 0, 0, 0, + 800000000, + 0, 0, 0, + 1000000000, +}; + +static u32 __init armada_375_get_cpu_freq(void __iomem *sar) +{ + u8 cpu_freq_select; + + cpu_freq_select = ((readl(sar) >> SAR1_A375_CPU_DDR_L2_FREQ_OPT) & + SAR1_A375_CPU_DDR_L2_FREQ_OPT_MASK); + if (cpu_freq_select >= ARRAY_SIZE(armada_375_cpu_frequencies)) { + pr_err("Selected CPU frequency (%d) unsupported\n", + cpu_freq_select); + return 0; + } else + return armada_375_cpu_frequencies[cpu_freq_select]; +} + +enum { A375_CPU_TO_DDR, A375_CPU_TO_L2 }; + +static const struct coreclk_ratio armada_375_coreclk_ratios[] __initconst = { + { .id = A375_CPU_TO_L2, .name = "l2clk" }, + { .id = A375_CPU_TO_DDR, .name = "ddrclk" }, +}; + +static const int armada_375_cpu_l2_ratios[32][2] __initconst = { + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {1, 2}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 2}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {1, 2}, {0, 1}, {0, 1}, + {0, 1}, {1, 2}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int armada_375_cpu_ddr_ratios[32][2] __initconst = { + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {1, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {2, 3}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {2, 3}, {0, 1}, {0, 1}, + {0, 1}, {1, 2}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static void __init armada_375_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + u32 opt = ((readl(sar) >> SAR1_A375_CPU_DDR_L2_FREQ_OPT) & + SAR1_A375_CPU_DDR_L2_FREQ_OPT_MASK); + + switch (id) { + case A375_CPU_TO_L2: + *mult = armada_375_cpu_l2_ratios[opt][0]; + *div = armada_375_cpu_l2_ratios[opt][1]; + break; + case A375_CPU_TO_DDR: + *mult = armada_375_cpu_ddr_ratios[opt][0]; + *div = armada_375_cpu_ddr_ratios[opt][1]; + break; + } +} + +static const struct coreclk_soc_desc armada_375_coreclks = { + .get_tclk_freq = armada_375_get_tclk_freq, + .get_cpu_freq = armada_375_get_cpu_freq, + .get_clk_ratio = armada_375_get_clk_ratio, + .ratios = armada_375_coreclk_ratios, + .num_ratios = ARRAY_SIZE(armada_375_coreclk_ratios), +}; + +static void __init armada_375_coreclk_init(struct device_node *np) +{ + mvebu_coreclk_setup(np, &armada_375_coreclks); +} +CLK_OF_DECLARE(armada_375_core_clk, "marvell,armada-375-core-clock", + armada_375_coreclk_init); + +/* + * Clock Gating Control + */ +static const struct clk_gating_soc_desc armada_375_gating_desc[] __initconst = { + { "mu", NULL, 2 }, + { "pp", NULL, 3 }, + { "ptp", NULL, 4 }, + { "pex0", NULL, 5 }, + { "pex1", NULL, 6 }, + { "audio", NULL, 8 }, + { "nd_clk", "nand", 11 }, + { "sata0_link", "sata0_core", 14 }, + { "sata0_core", NULL, 15 }, + { "usb3", NULL, 16 }, + { "sdio", NULL, 17 }, + { "usb", NULL, 18 }, + { "gop", NULL, 19 }, + { "sata1_link", "sata1_core", 20 }, + { "sata1_core", NULL, 21 }, + { "xor0", NULL, 22 }, + { "xor1", NULL, 23 }, + { "copro", NULL, 24 }, + { "tdm", NULL, 25 }, + { "crypto0_enc", NULL, 28 }, + { "crypto0_core", NULL, 29 }, + { "crypto1_enc", NULL, 30 }, + { "crypto1_core", NULL, 31 }, + { } +}; + +static void __init armada_375_clk_gating_init(struct device_node *np) +{ + mvebu_clk_gating_setup(np, armada_375_gating_desc); +} +CLK_OF_DECLARE(armada_375_clk_gating, "marvell,armada-375-gating-clock", + armada_375_clk_gating_init); diff --git a/drivers/clk/mvebu/armada-37xx-periph.c b/drivers/clk/mvebu/armada-37xx-periph.c new file mode 100644 index 000000000..3123771b9 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-periph.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell Armada 37xx SoC Peripheral clocks + * + * Copyright (C) 2016 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * + * Most of the peripheral clocks can be modelled like this: + * _____ _______ _______ + * TBG-A-P --| | | | | | ______ + * TBG-B-P --| Mux |--| /div1 |--| /div2 |--| Gate |--> perip_clk + * TBG-A-S --| | | | | | |______| + * TBG-B-S --|_____| |_______| |_______| + * + * However some clocks may use only one or two block or and use the + * xtal clock as parent. + */ + +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/jiffies.h> + +#define TBG_SEL 0x0 +#define DIV_SEL0 0x4 +#define DIV_SEL1 0x8 +#define DIV_SEL2 0xC +#define CLK_SEL 0x10 +#define CLK_DIS 0x14 + +#define ARMADA_37XX_DVFS_LOAD_1 1 +#define LOAD_LEVEL_NR 4 + +#define ARMADA_37XX_NB_L0L1 0x18 +#define ARMADA_37XX_NB_L2L3 0x1C +#define ARMADA_37XX_NB_TBG_DIV_OFF 13 +#define ARMADA_37XX_NB_TBG_DIV_MASK 0x7 +#define ARMADA_37XX_NB_CLK_SEL_OFF 11 +#define ARMADA_37XX_NB_CLK_SEL_MASK 0x1 +#define ARMADA_37XX_NB_TBG_SEL_OFF 9 +#define ARMADA_37XX_NB_TBG_SEL_MASK 0x3 +#define ARMADA_37XX_NB_CONFIG_SHIFT 16 +#define ARMADA_37XX_NB_DYN_MOD 0x24 +#define ARMADA_37XX_NB_DFS_EN 31 +#define ARMADA_37XX_NB_CPU_LOAD 0x30 +#define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3 +#define ARMADA_37XX_DVFS_LOAD_0 0 +#define ARMADA_37XX_DVFS_LOAD_1 1 +#define ARMADA_37XX_DVFS_LOAD_2 2 +#define ARMADA_37XX_DVFS_LOAD_3 3 + +struct clk_periph_driver_data { + struct clk_hw_onecell_data *hw_data; + spinlock_t lock; + void __iomem *reg; + + /* Storage registers for suspend/resume operations */ + u32 tbg_sel; + u32 div_sel0; + u32 div_sel1; + u32 div_sel2; + u32 clk_sel; + u32 clk_dis; +}; + +struct clk_double_div { + struct clk_hw hw; + void __iomem *reg1; + u8 shift1; + void __iomem *reg2; + u8 shift2; +}; + +struct clk_pm_cpu { + struct clk_hw hw; + void __iomem *reg_mux; + u8 shift_mux; + u32 mask_mux; + void __iomem *reg_div; + u8 shift_div; + struct regmap *nb_pm_base; + unsigned long l1_expiration; +}; + +#define to_clk_double_div(_hw) container_of(_hw, struct clk_double_div, hw) +#define to_clk_pm_cpu(_hw) container_of(_hw, struct clk_pm_cpu, hw) + +struct clk_periph_data { + const char *name; + const char * const *parent_names; + int num_parents; + struct clk_hw *mux_hw; + struct clk_hw *rate_hw; + struct clk_hw *gate_hw; + struct clk_hw *muxrate_hw; + bool is_double_div; +}; + +static const struct clk_div_table clk_table6[] = { + { .val = 1, .div = 1, }, + { .val = 2, .div = 2, }, + { .val = 3, .div = 3, }, + { .val = 4, .div = 4, }, + { .val = 5, .div = 5, }, + { .val = 6, .div = 6, }, + { .val = 0, .div = 0, }, /* last entry */ +}; + +static const struct clk_div_table clk_table1[] = { + { .val = 0, .div = 1, }, + { .val = 1, .div = 2, }, + { .val = 0, .div = 0, }, /* last entry */ +}; + +static const struct clk_div_table clk_table2[] = { + { .val = 0, .div = 2, }, + { .val = 1, .div = 4, }, + { .val = 0, .div = 0, }, /* last entry */ +}; + +static const struct clk_ops clk_double_div_ops; +static const struct clk_ops clk_pm_cpu_ops; + +#define __reg(__x) ((void __iomem __force *)(__x)) + +#define PERIPH_GATE(_name, _bit) \ +struct clk_gate gate_##_name = { \ + .reg = __reg(CLK_DIS), \ + .bit_idx = _bit, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_gate_ops, \ + } \ +}; + +#define PERIPH_MUX(_name, _shift) \ +struct clk_mux mux_##_name = { \ + .reg = __reg(TBG_SEL), \ + .shift = _shift, \ + .mask = 3, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_mux_ro_ops, \ + } \ +}; + +#define PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2) \ +struct clk_double_div rate_##_name = { \ + .reg1 = __reg(_reg1), \ + .reg2 = __reg(_reg2), \ + .shift1 = _shift1, \ + .shift2 = _shift2, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_double_div_ops, \ + } \ +}; + +#define PERIPH_DIV(_name, _reg, _shift, _table) \ +struct clk_divider rate_##_name = { \ + .reg = __reg(_reg), \ + .table = _table, \ + .shift = _shift, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_divider_ro_ops, \ + } \ +}; + +#define PERIPH_PM_CPU(_name, _shift1, _reg, _shift2) \ +struct clk_pm_cpu muxrate_##_name = { \ + .reg_mux = __reg(TBG_SEL), \ + .mask_mux = 3, \ + .shift_mux = _shift1, \ + .reg_div = __reg(_reg), \ + .shift_div = _shift2, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_pm_cpu_ops, \ + } \ +}; + +#define PERIPH_CLK_FULL_DD(_name, _bit, _shift, _reg1, _reg2, _shift1, _shift2)\ +static PERIPH_GATE(_name, _bit); \ +static PERIPH_MUX(_name, _shift); \ +static PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2); + +#define PERIPH_CLK_FULL(_name, _bit, _shift, _reg, _shift1, _table) \ +static PERIPH_GATE(_name, _bit); \ +static PERIPH_MUX(_name, _shift); \ +static PERIPH_DIV(_name, _reg, _shift1, _table); + +#define PERIPH_CLK_GATE_DIV(_name, _bit, _reg, _shift, _table) \ +static PERIPH_GATE(_name, _bit); \ +static PERIPH_DIV(_name, _reg, _shift, _table); + +#define PERIPH_CLK_MUX_DD(_name, _shift, _reg1, _reg2, _shift1, _shift2)\ +static PERIPH_MUX(_name, _shift); \ +static PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2); + +#define REF_CLK_FULL(_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ "TBG-A-P", \ + "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ + .num_parents = 4, \ + .mux_hw = &mux_##_name.hw, \ + .gate_hw = &gate_##_name.hw, \ + .rate_hw = &rate_##_name.hw, \ + } + +#define REF_CLK_FULL_DD(_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ "TBG-A-P", \ + "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ + .num_parents = 4, \ + .mux_hw = &mux_##_name.hw, \ + .gate_hw = &gate_##_name.hw, \ + .rate_hw = &rate_##_name.hw, \ + .is_double_div = true, \ + } + +#define REF_CLK_GATE(_name, _parent_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ _parent_name}, \ + .num_parents = 1, \ + .gate_hw = &gate_##_name.hw, \ + } + +#define REF_CLK_GATE_DIV(_name, _parent_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ _parent_name}, \ + .num_parents = 1, \ + .gate_hw = &gate_##_name.hw, \ + .rate_hw = &rate_##_name.hw, \ + } + +#define REF_CLK_PM_CPU(_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ "TBG-A-P", \ + "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ + .num_parents = 4, \ + .muxrate_hw = &muxrate_##_name.hw, \ + } + +#define REF_CLK_MUX_DD(_name) \ + { .name = #_name, \ + .parent_names = (const char *[]){ "TBG-A-P", \ + "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ + .num_parents = 4, \ + .mux_hw = &mux_##_name.hw, \ + .rate_hw = &rate_##_name.hw, \ + .is_double_div = true, \ + } + +/* NB periph clocks */ +PERIPH_CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13); +PERIPH_CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7); +PERIPH_CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0); +PERIPH_CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6); +PERIPH_CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12); +PERIPH_CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, clk_table6); +static PERIPH_GATE(avs, 11); +PERIPH_CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0); +PERIPH_CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24); +static PERIPH_GATE(i2c_2, 16); +static PERIPH_GATE(i2c_1, 17); +PERIPH_CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, clk_table2); +PERIPH_CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12); +PERIPH_CLK_FULL(trace, 22, 18, DIV_SEL0, 20, clk_table6); +PERIPH_CLK_FULL(counter, 23, 20, DIV_SEL0, 23, clk_table6); +PERIPH_CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19); +static PERIPH_PM_CPU(cpu, 22, DIV_SEL0, 28); + +static struct clk_periph_data data_nb[] = { + REF_CLK_FULL_DD(mmc), + REF_CLK_FULL_DD(sata_host), + REF_CLK_FULL_DD(sec_at), + REF_CLK_FULL_DD(sec_dap), + REF_CLK_FULL_DD(tscem), + REF_CLK_FULL(tscem_tmx), + REF_CLK_GATE(avs, "xtal"), + REF_CLK_FULL_DD(sqf), + REF_CLK_FULL_DD(pwm), + REF_CLK_GATE(i2c_2, "xtal"), + REF_CLK_GATE(i2c_1, "xtal"), + REF_CLK_GATE_DIV(ddr_phy, "TBG-A-S"), + REF_CLK_FULL_DD(ddr_fclk), + REF_CLK_FULL(trace), + REF_CLK_FULL(counter), + REF_CLK_FULL_DD(eip97), + REF_CLK_PM_CPU(cpu), + { }, +}; + +/* SB periph clocks */ +PERIPH_CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9); +PERIPH_CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21); +PERIPH_CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9); +static PERIPH_GATE(gbe1_50, 0); +static PERIPH_GATE(gbe0_50, 1); +static PERIPH_GATE(gbe1_125, 2); +static PERIPH_GATE(gbe0_125, 3); +PERIPH_CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, clk_table1); +PERIPH_CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, clk_table1); +PERIPH_CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, clk_table1); +PERIPH_CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6); +PERIPH_CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12); +PERIPH_CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18); +static PERIPH_GATE(pcie, 14); + +static struct clk_periph_data data_sb[] = { + REF_CLK_MUX_DD(gbe_50), + REF_CLK_MUX_DD(gbe_core), + REF_CLK_MUX_DD(gbe_125), + REF_CLK_GATE(gbe1_50, "gbe_50"), + REF_CLK_GATE(gbe0_50, "gbe_50"), + REF_CLK_GATE(gbe1_125, "gbe_125"), + REF_CLK_GATE(gbe0_125, "gbe_125"), + REF_CLK_GATE_DIV(gbe1_core, "gbe_core"), + REF_CLK_GATE_DIV(gbe0_core, "gbe_core"), + REF_CLK_GATE_DIV(gbe_bm, "gbe_core"), + REF_CLK_FULL_DD(sdio), + REF_CLK_FULL_DD(usb32_usb2_sys), + REF_CLK_FULL_DD(usb32_ss_sys), + REF_CLK_GATE(pcie, "gbe_core"), + { }, +}; + +static unsigned int get_div(void __iomem *reg, int shift) +{ + u32 val; + + val = (readl(reg) >> shift) & 0x7; + if (val > 6) + return 0; + return val; +} + +static unsigned long clk_double_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_double_div *double_div = to_clk_double_div(hw); + unsigned int div; + + div = get_div(double_div->reg1, double_div->shift1); + div *= get_div(double_div->reg2, double_div->shift2); + + return DIV_ROUND_UP_ULL((u64)parent_rate, div); +} + +static const struct clk_ops clk_double_div_ops = { + .recalc_rate = clk_double_div_recalc_rate, +}; + +static void armada_3700_pm_dvfs_update_regs(unsigned int load_level, + unsigned int *reg, + unsigned int *offset) +{ + if (load_level <= ARMADA_37XX_DVFS_LOAD_1) + *reg = ARMADA_37XX_NB_L0L1; + else + *reg = ARMADA_37XX_NB_L2L3; + + if (load_level == ARMADA_37XX_DVFS_LOAD_0 || + load_level == ARMADA_37XX_DVFS_LOAD_2) + *offset += ARMADA_37XX_NB_CONFIG_SHIFT; +} + +static bool armada_3700_pm_dvfs_is_enabled(struct regmap *base) +{ + unsigned int val, reg = ARMADA_37XX_NB_DYN_MOD; + + if (IS_ERR(base)) + return false; + + regmap_read(base, reg, &val); + + return !!(val & BIT(ARMADA_37XX_NB_DFS_EN)); +} + +static unsigned int armada_3700_pm_dvfs_get_cpu_div(struct regmap *base) +{ + unsigned int reg = ARMADA_37XX_NB_CPU_LOAD; + unsigned int offset = ARMADA_37XX_NB_TBG_DIV_OFF; + unsigned int load_level, div; + + /* + * This function is always called after the function + * armada_3700_pm_dvfs_is_enabled, so no need to check again + * if the base is valid. + */ + regmap_read(base, reg, &load_level); + + /* + * The register and the offset inside this register accessed to + * read the current divider depend on the load level + */ + load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; + armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); + + regmap_read(base, reg, &div); + + return (div >> offset) & ARMADA_37XX_NB_TBG_DIV_MASK; +} + +static unsigned int armada_3700_pm_dvfs_get_cpu_parent(struct regmap *base) +{ + unsigned int reg = ARMADA_37XX_NB_CPU_LOAD; + unsigned int offset = ARMADA_37XX_NB_TBG_SEL_OFF; + unsigned int load_level, sel; + + /* + * This function is always called after the function + * armada_3700_pm_dvfs_is_enabled, so no need to check again + * if the base is valid + */ + regmap_read(base, reg, &load_level); + + /* + * The register and the offset inside this register accessed to + * read the current divider depend on the load level + */ + load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; + armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); + + regmap_read(base, reg, &sel); + + return (sel >> offset) & ARMADA_37XX_NB_TBG_SEL_MASK; +} + +static u8 clk_pm_cpu_get_parent(struct clk_hw *hw) +{ + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); + u32 val; + + if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base)) { + val = armada_3700_pm_dvfs_get_cpu_parent(pm_cpu->nb_pm_base); + } else { + val = readl(pm_cpu->reg_mux) >> pm_cpu->shift_mux; + val &= pm_cpu->mask_mux; + } + + return val; +} + +static unsigned long clk_pm_cpu_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); + unsigned int div; + + if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base)) + div = armada_3700_pm_dvfs_get_cpu_div(pm_cpu->nb_pm_base); + else + div = get_div(pm_cpu->reg_div, pm_cpu->shift_div); + return DIV_ROUND_UP_ULL((u64)parent_rate, div); +} + +static int clk_pm_cpu_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); + struct regmap *base = pm_cpu->nb_pm_base; + unsigned int div = req->best_parent_rate / req->rate; + unsigned int load_level; + /* only available when DVFS is enabled */ + if (!armada_3700_pm_dvfs_is_enabled(base)) + return -EINVAL; + + for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) { + unsigned int reg, val, offset = ARMADA_37XX_NB_TBG_DIV_OFF; + + armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); + + regmap_read(base, reg, &val); + + val >>= offset; + val &= ARMADA_37XX_NB_TBG_DIV_MASK; + if (val == div) { + /* + * We found a load level matching the target + * divider, switch to this load level and + * return. + */ + req->rate = req->best_parent_rate / div; + + return 0; + } + } + + /* We didn't find any valid divider */ + return -EINVAL; +} + +/* + * Workaround when base CPU frequnecy is 1000 or 1200 MHz + * + * Switching the CPU from the L2 or L3 frequencies (250/300 or 200 MHz + * respectively) to L0 frequency (1/1.2 GHz) requires a significant + * amount of time to let VDD stabilize to the appropriate + * voltage. This amount of time is large enough that it cannot be + * covered by the hardware countdown register. Due to this, the CPU + * might start operating at L0 before the voltage is stabilized, + * leading to CPU stalls. + * + * To work around this problem, we prevent switching directly from the + * L2/L3 frequencies to the L0 frequency, and instead switch to the L1 + * frequency in-between. The sequence therefore becomes: + * 1. First switch from L2/L3 (200/250/300 MHz) to L1 (500/600 MHz) + * 2. Sleep 20ms for stabling VDD voltage + * 3. Then switch from L1 (500/600 MHz) to L0 (1000/1200 MHz). + */ +static void clk_pm_cpu_set_rate_wa(struct clk_pm_cpu *pm_cpu, + unsigned int new_level, unsigned long rate, + struct regmap *base) +{ + unsigned int cur_level; + + regmap_read(base, ARMADA_37XX_NB_CPU_LOAD, &cur_level); + cur_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; + + if (cur_level == new_level) + return; + + /* + * System wants to go to L1 on its own. If we are going from L2/L3, + * remember when 20ms will expire. If from L0, set the value so that + * next switch to L0 won't have to wait. + */ + if (new_level == ARMADA_37XX_DVFS_LOAD_1) { + if (cur_level == ARMADA_37XX_DVFS_LOAD_0) + pm_cpu->l1_expiration = jiffies; + else + pm_cpu->l1_expiration = jiffies + msecs_to_jiffies(20); + return; + } + + /* + * If we are setting to L2/L3, just invalidate L1 expiration time, + * sleeping is not needed. + */ + if (rate < 1000*1000*1000) + goto invalidate_l1_exp; + + /* + * We are going to L0 with rate >= 1GHz. Check whether we have been at + * L1 for long enough time. If not, go to L1 for 20ms. + */ + if (pm_cpu->l1_expiration && time_is_before_eq_jiffies(pm_cpu->l1_expiration)) + goto invalidate_l1_exp; + + regmap_update_bits(base, ARMADA_37XX_NB_CPU_LOAD, + ARMADA_37XX_NB_CPU_LOAD_MASK, + ARMADA_37XX_DVFS_LOAD_1); + msleep(20); + +invalidate_l1_exp: + pm_cpu->l1_expiration = 0; +} + +static int clk_pm_cpu_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); + struct regmap *base = pm_cpu->nb_pm_base; + unsigned int div = parent_rate / rate; + unsigned int load_level; + + /* only available when DVFS is enabled */ + if (!armada_3700_pm_dvfs_is_enabled(base)) + return -EINVAL; + + for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) { + unsigned int reg, mask, val, + offset = ARMADA_37XX_NB_TBG_DIV_OFF; + + armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); + + regmap_read(base, reg, &val); + val >>= offset; + val &= ARMADA_37XX_NB_TBG_DIV_MASK; + + if (val == div) { + /* + * We found a load level matching the target + * divider, switch to this load level and + * return. + */ + reg = ARMADA_37XX_NB_CPU_LOAD; + mask = ARMADA_37XX_NB_CPU_LOAD_MASK; + + /* Apply workaround when base CPU frequency is 1000 or 1200 MHz */ + if (parent_rate >= 1000*1000*1000) + clk_pm_cpu_set_rate_wa(pm_cpu, load_level, rate, base); + + regmap_update_bits(base, reg, mask, load_level); + + return rate; + } + } + + /* We didn't find any valid divider */ + return -EINVAL; +} + +static const struct clk_ops clk_pm_cpu_ops = { + .get_parent = clk_pm_cpu_get_parent, + .determine_rate = clk_pm_cpu_determine_rate, + .set_rate = clk_pm_cpu_set_rate, + .recalc_rate = clk_pm_cpu_recalc_rate, +}; + +static const struct of_device_id armada_3700_periph_clock_of_match[] = { + { .compatible = "marvell,armada-3700-periph-clock-nb", + .data = data_nb, }, + { .compatible = "marvell,armada-3700-periph-clock-sb", + .data = data_sb, }, + { } +}; + +static int armada_3700_add_composite_clk(const struct clk_periph_data *data, + void __iomem *reg, spinlock_t *lock, + struct device *dev, struct clk_hw **hw) +{ + const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, + *rate_ops = NULL; + struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL; + + if (data->mux_hw) { + struct clk_mux *mux; + + mux_hw = data->mux_hw; + mux = to_clk_mux(mux_hw); + mux->lock = lock; + mux_ops = mux_hw->init->ops; + mux->reg = reg + (u64)mux->reg; + } + + if (data->gate_hw) { + struct clk_gate *gate; + + gate_hw = data->gate_hw; + gate = to_clk_gate(gate_hw); + gate->lock = lock; + gate_ops = gate_hw->init->ops; + gate->reg = reg + (u64)gate->reg; + gate->flags = CLK_GATE_SET_TO_DISABLE; + } + + if (data->rate_hw) { + rate_hw = data->rate_hw; + rate_ops = rate_hw->init->ops; + if (data->is_double_div) { + struct clk_double_div *rate; + + rate = to_clk_double_div(rate_hw); + rate->reg1 = reg + (u64)rate->reg1; + rate->reg2 = reg + (u64)rate->reg2; + } else { + struct clk_divider *rate = to_clk_divider(rate_hw); + const struct clk_div_table *clkt; + int table_size = 0; + + rate->reg = reg + (u64)rate->reg; + for (clkt = rate->table; clkt->div; clkt++) + table_size++; + rate->width = order_base_2(table_size); + rate->lock = lock; + } + } + + if (data->muxrate_hw) { + struct clk_pm_cpu *pmcpu_clk; + struct clk_hw *muxrate_hw = data->muxrate_hw; + struct regmap *map; + + pmcpu_clk = to_clk_pm_cpu(muxrate_hw); + pmcpu_clk->reg_mux = reg + (u64)pmcpu_clk->reg_mux; + pmcpu_clk->reg_div = reg + (u64)pmcpu_clk->reg_div; + + mux_hw = muxrate_hw; + rate_hw = muxrate_hw; + mux_ops = muxrate_hw->init->ops; + rate_ops = muxrate_hw->init->ops; + + map = syscon_regmap_lookup_by_compatible( + "marvell,armada-3700-nb-pm"); + pmcpu_clk->nb_pm_base = map; + } + + *hw = clk_hw_register_composite(dev, data->name, data->parent_names, + data->num_parents, mux_hw, + mux_ops, rate_hw, rate_ops, + gate_hw, gate_ops, CLK_IGNORE_UNUSED); + + return PTR_ERR_OR_ZERO(*hw); +} + +static int __maybe_unused armada_3700_periph_clock_suspend(struct device *dev) +{ + struct clk_periph_driver_data *data = dev_get_drvdata(dev); + + data->tbg_sel = readl(data->reg + TBG_SEL); + data->div_sel0 = readl(data->reg + DIV_SEL0); + data->div_sel1 = readl(data->reg + DIV_SEL1); + data->div_sel2 = readl(data->reg + DIV_SEL2); + data->clk_sel = readl(data->reg + CLK_SEL); + data->clk_dis = readl(data->reg + CLK_DIS); + + return 0; +} + +static int __maybe_unused armada_3700_periph_clock_resume(struct device *dev) +{ + struct clk_periph_driver_data *data = dev_get_drvdata(dev); + + /* Follow the same order than what the Cortex-M3 does (ATF code) */ + writel(data->clk_dis, data->reg + CLK_DIS); + writel(data->div_sel0, data->reg + DIV_SEL0); + writel(data->div_sel1, data->reg + DIV_SEL1); + writel(data->div_sel2, data->reg + DIV_SEL2); + writel(data->tbg_sel, data->reg + TBG_SEL); + writel(data->clk_sel, data->reg + CLK_SEL); + + return 0; +} + +static const struct dev_pm_ops armada_3700_periph_clock_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(armada_3700_periph_clock_suspend, + armada_3700_periph_clock_resume) +}; + +static int armada_3700_periph_clock_probe(struct platform_device *pdev) +{ + struct clk_periph_driver_data *driver_data; + struct device_node *np = pdev->dev.of_node; + const struct clk_periph_data *data; + struct device *dev = &pdev->dev; + int num_periph = 0, i, ret; + + data = of_device_get_match_data(dev); + if (!data) + return -ENODEV; + + while (data[num_periph].name) + num_periph++; + + driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL); + if (!driver_data) + return -ENOMEM; + + driver_data->hw_data = devm_kzalloc(dev, + struct_size(driver_data->hw_data, + hws, num_periph), + GFP_KERNEL); + if (!driver_data->hw_data) + return -ENOMEM; + driver_data->hw_data->num = num_periph; + + driver_data->reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(driver_data->reg)) + return PTR_ERR(driver_data->reg); + + spin_lock_init(&driver_data->lock); + + for (i = 0; i < num_periph; i++) { + struct clk_hw **hw = &driver_data->hw_data->hws[i]; + if (armada_3700_add_composite_clk(&data[i], driver_data->reg, + &driver_data->lock, dev, hw)) + dev_err(dev, "Can't register periph clock %s\n", + data[i].name); + } + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, + driver_data->hw_data); + if (ret) { + for (i = 0; i < num_periph; i++) + clk_hw_unregister(driver_data->hw_data->hws[i]); + return ret; + } + + platform_set_drvdata(pdev, driver_data); + return 0; +} + +static void armada_3700_periph_clock_remove(struct platform_device *pdev) +{ + struct clk_periph_driver_data *data = platform_get_drvdata(pdev); + struct clk_hw_onecell_data *hw_data = data->hw_data; + int i; + + of_clk_del_provider(pdev->dev.of_node); + + for (i = 0; i < hw_data->num; i++) + clk_hw_unregister(hw_data->hws[i]); +} + +static struct platform_driver armada_3700_periph_clock_driver = { + .probe = armada_3700_periph_clock_probe, + .remove = armada_3700_periph_clock_remove, + .driver = { + .name = "marvell-armada-3700-periph-clock", + .of_match_table = armada_3700_periph_clock_of_match, + .pm = &armada_3700_periph_clock_pm_ops, + }, +}; + +builtin_platform_driver(armada_3700_periph_clock_driver); diff --git a/drivers/clk/mvebu/armada-37xx-tbg.c b/drivers/clk/mvebu/armada-37xx-tbg.c new file mode 100644 index 000000000..1a16f9c0b --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-tbg.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell Armada 37xx SoC Time Base Generator clocks + * + * Copyright (C) 2016 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + */ + +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define NUM_TBG 4 + +#define TBG_CTRL0 0x4 +#define TBG_CTRL1 0x8 +#define TBG_CTRL7 0x20 +#define TBG_CTRL8 0x30 + +#define TBG_DIV_MASK 0x1FF + +#define TBG_A_REFDIV 0 +#define TBG_B_REFDIV 16 + +#define TBG_A_FBDIV 2 +#define TBG_B_FBDIV 18 + +#define TBG_A_VCODIV_SE 0 +#define TBG_B_VCODIV_SE 16 + +#define TBG_A_VCODIV_DIFF 1 +#define TBG_B_VCODIV_DIFF 17 + +struct tbg_def { + char *name; + u32 refdiv_offset; + u32 fbdiv_offset; + u32 vcodiv_reg; + u32 vcodiv_offset; +}; + +static const struct tbg_def tbg[NUM_TBG] = { + {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF}, + {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF}, + {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE}, + {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE}, +}; + +static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) +{ + u32 val; + + val = readl(reg + TBG_CTRL0); + + return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2; +} + +static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) +{ + u32 val; + unsigned int div; + + val = readl(reg + TBG_CTRL7); + + div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK; + if (div == 0) + div = 1; + val = readl(reg + ptbg->vcodiv_reg); + + div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK); + + return div; +} + + +static int armada_3700_tbg_clock_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct clk_hw_onecell_data *hw_tbg_data; + struct device *dev = &pdev->dev; + const char *parent_name; + struct clk *parent; + void __iomem *reg; + int i; + + hw_tbg_data = devm_kzalloc(&pdev->dev, + struct_size(hw_tbg_data, hws, NUM_TBG), + GFP_KERNEL); + if (!hw_tbg_data) + return -ENOMEM; + hw_tbg_data->num = NUM_TBG; + platform_set_drvdata(pdev, hw_tbg_data); + + parent = clk_get(dev, NULL); + if (IS_ERR(parent)) { + dev_err(dev, "Could get the clock parent\n"); + return -EINVAL; + } + parent_name = __clk_get_name(parent); + clk_put(parent); + + reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + for (i = 0; i < NUM_TBG; i++) { + const char *name; + unsigned int mult, div; + + name = tbg[i].name; + mult = tbg_get_mult(reg, &tbg[i]); + div = tbg_get_div(reg, &tbg[i]); + hw_tbg_data->hws[i] = clk_hw_register_fixed_factor(NULL, name, + parent_name, 0, mult, div); + if (IS_ERR(hw_tbg_data->hws[i])) + dev_err(dev, "Can't register TBG clock %s\n", name); + } + + return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, hw_tbg_data); +} + +static void armada_3700_tbg_clock_remove(struct platform_device *pdev) +{ + int i; + struct clk_hw_onecell_data *hw_tbg_data = platform_get_drvdata(pdev); + + of_clk_del_provider(pdev->dev.of_node); + for (i = 0; i < hw_tbg_data->num; i++) + clk_hw_unregister_fixed_factor(hw_tbg_data->hws[i]); +} + +static const struct of_device_id armada_3700_tbg_clock_of_match[] = { + { .compatible = "marvell,armada-3700-tbg-clock", }, + { } +}; + +static struct platform_driver armada_3700_tbg_clock_driver = { + .probe = armada_3700_tbg_clock_probe, + .remove = armada_3700_tbg_clock_remove, + .driver = { + .name = "marvell-armada-3700-tbg-clock", + .of_match_table = armada_3700_tbg_clock_of_match, + }, +}; + +builtin_platform_driver(armada_3700_tbg_clock_driver); diff --git a/drivers/clk/mvebu/armada-37xx-xtal.c b/drivers/clk/mvebu/armada-37xx-xtal.c new file mode 100644 index 000000000..ca88e5e78 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-xtal.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada 37xx SoC xtal clocks + * + * Copyright (C) 2016 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * + */ + +#include <linux/clk-provider.h> +#include <linux/mfd/syscon.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define NB_GPIO1_LATCH 0x8 +#define XTAL_MODE BIT(9) + +static int armada_3700_xtal_clock_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const char *xtal_name = "xtal"; + struct device_node *parent; + struct regmap *regmap; + struct clk_hw *xtal_hw; + unsigned int rate; + u32 reg; + int ret; + + xtal_hw = devm_kzalloc(&pdev->dev, sizeof(*xtal_hw), GFP_KERNEL); + if (!xtal_hw) + return -ENOMEM; + + platform_set_drvdata(pdev, xtal_hw); + + parent = np->parent; + if (!parent) { + dev_err(&pdev->dev, "no parent\n"); + return -ENODEV; + } + + regmap = syscon_node_to_regmap(parent); + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "cannot get regmap\n"); + return PTR_ERR(regmap); + } + + ret = regmap_read(regmap, NB_GPIO1_LATCH, ®); + if (ret) { + dev_err(&pdev->dev, "cannot read from regmap\n"); + return ret; + } + + if (reg & XTAL_MODE) + rate = 40000000; + else + rate = 25000000; + + of_property_read_string_index(np, "clock-output-names", 0, &xtal_name); + xtal_hw = clk_hw_register_fixed_rate(NULL, xtal_name, NULL, 0, rate); + if (IS_ERR(xtal_hw)) + return PTR_ERR(xtal_hw); + ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, xtal_hw); + + return ret; +} + +static void armada_3700_xtal_clock_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); +} + +static const struct of_device_id armada_3700_xtal_clock_of_match[] = { + { .compatible = "marvell,armada-3700-xtal-clock", }, + { } +}; + +static struct platform_driver armada_3700_xtal_clock_driver = { + .probe = armada_3700_xtal_clock_probe, + .remove = armada_3700_xtal_clock_remove, + .driver = { + .name = "marvell-armada-3700-xtal-clock", + .of_match_table = armada_3700_xtal_clock_of_match, + }, +}; + +builtin_platform_driver(armada_3700_xtal_clock_driver); diff --git a/drivers/clk/mvebu/armada-38x.c b/drivers/clk/mvebu/armada-38x.c new file mode 100644 index 000000000..ef2ab81f0 --- /dev/null +++ b/drivers/clk/mvebu/armada-38x.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada 380/385 SoC clocks + * + * Copyright (C) 2014 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +/* + * SAR[14:10] : Ratios between PCLK0, NBCLK, HCLK and DRAM clocks + * + * SAR[15] : TCLK frequency + * 0 = 250 MHz + * 1 = 200 MHz + */ + +#define SAR_A380_TCLK_FREQ_OPT 15 +#define SAR_A380_TCLK_FREQ_OPT_MASK 0x1 +#define SAR_A380_CPU_DDR_L2_FREQ_OPT 10 +#define SAR_A380_CPU_DDR_L2_FREQ_OPT_MASK 0x1F + +static const u32 armada_38x_tclk_frequencies[] __initconst = { + 250000000, + 200000000, +}; + +static u32 __init armada_38x_get_tclk_freq(void __iomem *sar) +{ + u8 tclk_freq_select; + + tclk_freq_select = ((readl(sar) >> SAR_A380_TCLK_FREQ_OPT) & + SAR_A380_TCLK_FREQ_OPT_MASK); + return armada_38x_tclk_frequencies[tclk_freq_select]; +} + +static const u32 armada_38x_cpu_frequencies[] __initconst = { + 666 * 1000 * 1000, 0, 800 * 1000 * 1000, 0, + 1066 * 1000 * 1000, 0, 1200 * 1000 * 1000, 0, + 1332 * 1000 * 1000, 0, 0, 0, + 1600 * 1000 * 1000, 0, 0, 0, + 1866 * 1000 * 1000, 0, 0, 2000 * 1000 * 1000, +}; + +static u32 __init armada_38x_get_cpu_freq(void __iomem *sar) +{ + u8 cpu_freq_select; + + cpu_freq_select = ((readl(sar) >> SAR_A380_CPU_DDR_L2_FREQ_OPT) & + SAR_A380_CPU_DDR_L2_FREQ_OPT_MASK); + if (cpu_freq_select >= ARRAY_SIZE(armada_38x_cpu_frequencies)) { + pr_err("Selected CPU frequency (%d) unsupported\n", + cpu_freq_select); + return 0; + } + + return armada_38x_cpu_frequencies[cpu_freq_select]; +} + +enum { A380_CPU_TO_DDR, A380_CPU_TO_L2 }; + +static const struct coreclk_ratio armada_38x_coreclk_ratios[] __initconst = { + { .id = A380_CPU_TO_L2, .name = "l2clk" }, + { .id = A380_CPU_TO_DDR, .name = "ddrclk" }, +}; + +static const int armada_38x_cpu_l2_ratios[32][2] __initconst = { + {1, 2}, {0, 1}, {1, 2}, {0, 1}, + {1, 2}, {0, 1}, {1, 2}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {1, 2}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int armada_38x_cpu_ddr_ratios[32][2] __initconst = { + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {7, 15}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static void __init armada_38x_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + u32 opt = ((readl(sar) >> SAR_A380_CPU_DDR_L2_FREQ_OPT) & + SAR_A380_CPU_DDR_L2_FREQ_OPT_MASK); + + switch (id) { + case A380_CPU_TO_L2: + *mult = armada_38x_cpu_l2_ratios[opt][0]; + *div = armada_38x_cpu_l2_ratios[opt][1]; + break; + case A380_CPU_TO_DDR: + *mult = armada_38x_cpu_ddr_ratios[opt][0]; + *div = armada_38x_cpu_ddr_ratios[opt][1]; + break; + } +} + +static const struct coreclk_soc_desc armada_38x_coreclks = { + .get_tclk_freq = armada_38x_get_tclk_freq, + .get_cpu_freq = armada_38x_get_cpu_freq, + .get_clk_ratio = armada_38x_get_clk_ratio, + .ratios = armada_38x_coreclk_ratios, + .num_ratios = ARRAY_SIZE(armada_38x_coreclk_ratios), +}; + +static void __init armada_38x_coreclk_init(struct device_node *np) +{ + mvebu_coreclk_setup(np, &armada_38x_coreclks); +} +CLK_OF_DECLARE(armada_38x_core_clk, "marvell,armada-380-core-clock", + armada_38x_coreclk_init); + +/* + * Clock Gating Control + */ +static const struct clk_gating_soc_desc armada_38x_gating_desc[] __initconst = { + { "audio", NULL, 0 }, + { "ge2", NULL, 2 }, + { "ge1", NULL, 3 }, + { "ge0", NULL, 4 }, + { "pex1", NULL, 5 }, + { "pex2", NULL, 6 }, + { "pex3", NULL, 7 }, + { "pex0", NULL, 8 }, + { "usb3h0", NULL, 9 }, + { "usb3h1", NULL, 10 }, + { "usb3d", NULL, 11 }, + { "bm", NULL, 13 }, + { "crypto0z", NULL, 14 }, + { "sata0", NULL, 15 }, + { "crypto1z", NULL, 16 }, + { "sdio", NULL, 17 }, + { "usb2", NULL, 18 }, + { "crypto1", NULL, 21 }, + { "xor0", NULL, 22 }, + { "crypto0", NULL, 23 }, + { "tdm", NULL, 25 }, + { "xor1", NULL, 28 }, + { "sata1", NULL, 30 }, + { } +}; + +static void __init armada_38x_clk_gating_init(struct device_node *np) +{ + mvebu_clk_gating_setup(np, armada_38x_gating_desc); +} +CLK_OF_DECLARE(armada_38x_clk_gating, "marvell,armada-380-gating-clock", + armada_38x_clk_gating_init); diff --git a/drivers/clk/mvebu/armada-39x.c b/drivers/clk/mvebu/armada-39x.c new file mode 100644 index 000000000..674ccfd62 --- /dev/null +++ b/drivers/clk/mvebu/armada-39x.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada 39x SoC clocks + * + * Copyright (C) 2015 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +/* + * SARL[14:10] : Ratios between CPU, NBCLK, HCLK and DCLK. + * + * SARL[15] : TCLK frequency + * 0 = 250 MHz + * 1 = 200 MHz + * + * SARH[0] : Reference clock frequency + * 0 = 25 Mhz + * 1 = 40 Mhz + */ + +#define SARL 0 +#define SARL_A390_TCLK_FREQ_OPT 15 +#define SARL_A390_TCLK_FREQ_OPT_MASK 0x1 +#define SARL_A390_CPU_DDR_L2_FREQ_OPT 10 +#define SARL_A390_CPU_DDR_L2_FREQ_OPT_MASK 0x1F +#define SARH 4 +#define SARH_A390_REFCLK_FREQ BIT(0) + +static const u32 armada_39x_tclk_frequencies[] __initconst = { + 250000000, + 200000000, +}; + +static u32 __init armada_39x_get_tclk_freq(void __iomem *sar) +{ + u8 tclk_freq_select; + + tclk_freq_select = ((readl(sar + SARL) >> SARL_A390_TCLK_FREQ_OPT) & + SARL_A390_TCLK_FREQ_OPT_MASK); + return armada_39x_tclk_frequencies[tclk_freq_select]; +} + +static const u32 armada_39x_cpu_frequencies[] __initconst = { + [0x0] = 666 * 1000 * 1000, + [0x2] = 800 * 1000 * 1000, + [0x3] = 800 * 1000 * 1000, + [0x4] = 1066 * 1000 * 1000, + [0x5] = 1066 * 1000 * 1000, + [0x6] = 1200 * 1000 * 1000, + [0x8] = 1332 * 1000 * 1000, + [0xB] = 1600 * 1000 * 1000, + [0xC] = 1600 * 1000 * 1000, + [0x12] = 1800 * 1000 * 1000, + [0x1E] = 1800 * 1000 * 1000, +}; + +static u32 __init armada_39x_get_cpu_freq(void __iomem *sar) +{ + u8 cpu_freq_select; + + cpu_freq_select = ((readl(sar + SARL) >> SARL_A390_CPU_DDR_L2_FREQ_OPT) & + SARL_A390_CPU_DDR_L2_FREQ_OPT_MASK); + if (cpu_freq_select >= ARRAY_SIZE(armada_39x_cpu_frequencies)) { + pr_err("Selected CPU frequency (%d) unsupported\n", + cpu_freq_select); + return 0; + } + + return armada_39x_cpu_frequencies[cpu_freq_select]; +} + +enum { A390_CPU_TO_NBCLK, A390_CPU_TO_HCLK, A390_CPU_TO_DCLK }; + +static const struct coreclk_ratio armada_39x_coreclk_ratios[] __initconst = { + { .id = A390_CPU_TO_NBCLK, .name = "nbclk" }, + { .id = A390_CPU_TO_HCLK, .name = "hclk" }, + { .id = A390_CPU_TO_DCLK, .name = "dclk" }, +}; + +static void __init armada_39x_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + switch (id) { + case A390_CPU_TO_NBCLK: + *mult = 1; + *div = 2; + break; + case A390_CPU_TO_HCLK: + *mult = 1; + *div = 4; + break; + case A390_CPU_TO_DCLK: + *mult = 1; + *div = 2; + break; + } +} + +static u32 __init armada_39x_refclk_ratio(void __iomem *sar) +{ + if (readl(sar + SARH) & SARH_A390_REFCLK_FREQ) + return 40 * 1000 * 1000; + else + return 25 * 1000 * 1000; +} + +static const struct coreclk_soc_desc armada_39x_coreclks = { + .get_tclk_freq = armada_39x_get_tclk_freq, + .get_cpu_freq = armada_39x_get_cpu_freq, + .get_clk_ratio = armada_39x_get_clk_ratio, + .get_refclk_freq = armada_39x_refclk_ratio, + .ratios = armada_39x_coreclk_ratios, + .num_ratios = ARRAY_SIZE(armada_39x_coreclk_ratios), +}; + +static void __init armada_39x_coreclk_init(struct device_node *np) +{ + mvebu_coreclk_setup(np, &armada_39x_coreclks); +} +CLK_OF_DECLARE(armada_39x_core_clk, "marvell,armada-390-core-clock", + armada_39x_coreclk_init); + +/* + * Clock Gating Control + */ +static const struct clk_gating_soc_desc armada_39x_gating_desc[] __initconst = { + { "pex1", NULL, 5 }, + { "pex2", NULL, 6 }, + { "pex3", NULL, 7 }, + { "pex0", NULL, 8 }, + { "usb3h0", NULL, 9 }, + { "usb3h1", NULL, 10 }, + { "sata0", NULL, 15 }, + { "sdio", NULL, 17 }, + { "xor0", NULL, 22 }, + { "xor1", NULL, 28 }, + { } +}; + +static void __init armada_39x_clk_gating_init(struct device_node *np) +{ + mvebu_clk_gating_setup(np, armada_39x_gating_desc); +} +CLK_OF_DECLARE(armada_39x_clk_gating, "marvell,armada-390-gating-clock", + armada_39x_clk_gating_init); diff --git a/drivers/clk/mvebu/armada-xp.c b/drivers/clk/mvebu/armada-xp.c new file mode 100644 index 000000000..8d31a595a --- /dev/null +++ b/drivers/clk/mvebu/armada-xp.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada XP SoC clocks + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +/* + * Core Clocks + * + * Armada XP Sample At Reset is a 64 bit bitfield split in two + * registers of 32 bits + */ + +#define SARL 0 /* Low part [0:31] */ +#define SARL_AXP_PCLK_FREQ_OPT 21 +#define SARL_AXP_PCLK_FREQ_OPT_MASK 0x7 +#define SARL_AXP_FAB_FREQ_OPT 24 +#define SARL_AXP_FAB_FREQ_OPT_MASK 0xF +#define SARH 4 /* High part [32:63] */ +#define SARH_AXP_PCLK_FREQ_OPT (52-32) +#define SARH_AXP_PCLK_FREQ_OPT_MASK 0x1 +#define SARH_AXP_PCLK_FREQ_OPT_SHIFT 3 +#define SARH_AXP_FAB_FREQ_OPT (51-32) +#define SARH_AXP_FAB_FREQ_OPT_MASK 0x1 +#define SARH_AXP_FAB_FREQ_OPT_SHIFT 4 + +enum { AXP_CPU_TO_NBCLK, AXP_CPU_TO_HCLK, AXP_CPU_TO_DRAMCLK }; + +static const struct coreclk_ratio axp_coreclk_ratios[] __initconst = { + { .id = AXP_CPU_TO_NBCLK, .name = "nbclk" }, + { .id = AXP_CPU_TO_HCLK, .name = "hclk" }, + { .id = AXP_CPU_TO_DRAMCLK, .name = "dramclk" }, +}; + +/* Armada XP TCLK frequency is fixed to 250MHz */ +static u32 __init axp_get_tclk_freq(void __iomem *sar) +{ + return 250000000; +} + +static const u32 axp_cpu_freqs[] __initconst = { + 1000000000, + 1066000000, + 1200000000, + 1333000000, + 1500000000, + 1666000000, + 1800000000, + 2000000000, + 667000000, + 0, + 800000000, + 1600000000, +}; + +static u32 __init axp_get_cpu_freq(void __iomem *sar) +{ + u32 cpu_freq; + u8 cpu_freq_select = 0; + + cpu_freq_select = ((readl(sar + SARL) >> SARL_AXP_PCLK_FREQ_OPT) & + SARL_AXP_PCLK_FREQ_OPT_MASK); + /* + * The upper bit is not contiguous to the other ones and + * located in the high part of the SAR registers + */ + cpu_freq_select |= (((readl(sar + SARH) >> SARH_AXP_PCLK_FREQ_OPT) & + SARH_AXP_PCLK_FREQ_OPT_MASK) << SARH_AXP_PCLK_FREQ_OPT_SHIFT); + if (cpu_freq_select >= ARRAY_SIZE(axp_cpu_freqs)) { + pr_err("CPU freq select unsupported: %d\n", cpu_freq_select); + cpu_freq = 0; + } else + cpu_freq = axp_cpu_freqs[cpu_freq_select]; + + return cpu_freq; +} + +static const int axp_nbclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 2}, {2, 2}, + {1, 2}, {1, 2}, {1, 1}, {2, 3}, + {0, 1}, {1, 2}, {2, 4}, {0, 1}, + {1, 2}, {0, 1}, {0, 1}, {2, 2}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {2, 3}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int axp_hclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 6}, {2, 3}, + {1, 3}, {1, 4}, {1, 2}, {2, 6}, + {0, 1}, {1, 6}, {2, 10}, {0, 1}, + {1, 4}, {0, 1}, {0, 1}, {2, 5}, + {0, 1}, {0, 1}, {0, 1}, {1, 2}, + {2, 6}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int axp_dramclk_ratios[32][2] __initconst = { + {0, 1}, {1, 2}, {2, 3}, {2, 3}, + {1, 3}, {1, 2}, {1, 2}, {2, 6}, + {0, 1}, {1, 3}, {2, 5}, {0, 1}, + {1, 4}, {0, 1}, {0, 1}, {2, 5}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {2, 3}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static void __init axp_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + u32 opt = ((readl(sar + SARL) >> SARL_AXP_FAB_FREQ_OPT) & + SARL_AXP_FAB_FREQ_OPT_MASK); + /* + * The upper bit is not contiguous to the other ones and + * located in the high part of the SAR registers + */ + opt |= (((readl(sar + SARH) >> SARH_AXP_FAB_FREQ_OPT) & + SARH_AXP_FAB_FREQ_OPT_MASK) << SARH_AXP_FAB_FREQ_OPT_SHIFT); + + switch (id) { + case AXP_CPU_TO_NBCLK: + *mult = axp_nbclk_ratios[opt][0]; + *div = axp_nbclk_ratios[opt][1]; + break; + case AXP_CPU_TO_HCLK: + *mult = axp_hclk_ratios[opt][0]; + *div = axp_hclk_ratios[opt][1]; + break; + case AXP_CPU_TO_DRAMCLK: + *mult = axp_dramclk_ratios[opt][0]; + *div = axp_dramclk_ratios[opt][1]; + break; + } +} + +static const struct coreclk_soc_desc axp_coreclks = { + .get_tclk_freq = axp_get_tclk_freq, + .get_cpu_freq = axp_get_cpu_freq, + .get_clk_ratio = axp_get_clk_ratio, + .ratios = axp_coreclk_ratios, + .num_ratios = ARRAY_SIZE(axp_coreclk_ratios), +}; + +/* + * Clock Gating Control + */ + +static const struct clk_gating_soc_desc axp_gating_desc[] __initconst = { + { "audio", NULL, 0, 0 }, + { "ge3", NULL, 1, 0 }, + { "ge2", NULL, 2, 0 }, + { "ge1", NULL, 3, 0 }, + { "ge0", NULL, 4, 0 }, + { "pex00", NULL, 5, 0 }, + { "pex01", NULL, 6, 0 }, + { "pex02", NULL, 7, 0 }, + { "pex03", NULL, 8, 0 }, + { "pex10", NULL, 9, 0 }, + { "pex11", NULL, 10, 0 }, + { "pex12", NULL, 11, 0 }, + { "pex13", NULL, 12, 0 }, + { "bp", NULL, 13, 0 }, + { "sata0lnk", NULL, 14, 0 }, + { "sata0", "sata0lnk", 15, 0 }, + { "lcd", NULL, 16, 0 }, + { "sdio", NULL, 17, 0 }, + { "usb0", NULL, 18, 0 }, + { "usb1", NULL, 19, 0 }, + { "usb2", NULL, 20, 0 }, + { "xor0", NULL, 22, 0 }, + { "crypto", NULL, 23, 0 }, + { "tdm", NULL, 25, 0 }, + { "pex20", NULL, 26, 0 }, + { "pex30", NULL, 27, 0 }, + { "xor1", NULL, 28, 0 }, + { "sata1lnk", NULL, 29, 0 }, + { "sata1", "sata1lnk", 30, 0 }, + { } +}; + +static void __init axp_clk_init(struct device_node *np) +{ + struct device_node *cgnp = + of_find_compatible_node(NULL, NULL, "marvell,armada-xp-gating-clock"); + + mvebu_coreclk_setup(np, &axp_coreclks); + + if (cgnp) { + mvebu_clk_gating_setup(cgnp, axp_gating_desc); + of_node_put(cgnp); + } +} +CLK_OF_DECLARE(axp_clk, "marvell,armada-xp-core-clock", axp_clk_init); diff --git a/drivers/clk/mvebu/armada_ap_cp_helper.c b/drivers/clk/mvebu/armada_ap_cp_helper.c new file mode 100644 index 000000000..e7005de66 --- /dev/null +++ b/drivers/clk/mvebu/armada_ap_cp_helper.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell Armada AP and CP110 helper + * + * Copyright (C) 2018 Marvell + * + * Gregory Clement <gregory.clement@bootlin.com> + * + */ + +#include "armada_ap_cp_helper.h" +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_address.h> + +char *ap_cp_unique_name(struct device *dev, struct device_node *np, + const char *name) +{ + struct resource res; + + /* Do not create a name if there is no clock */ + if (!name) + return NULL; + + of_address_to_resource(np, 0, &res); + return devm_kasprintf(dev, GFP_KERNEL, "%llx-%s", + (unsigned long long)res.start, name); +} diff --git a/drivers/clk/mvebu/armada_ap_cp_helper.h b/drivers/clk/mvebu/armada_ap_cp_helper.h new file mode 100644 index 000000000..810af1e5d --- /dev/null +++ b/drivers/clk/mvebu/armada_ap_cp_helper.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __ARMADA_AP_CP_HELPER_H +#define __ARMADA_AP_CP_HELPER_H + +struct device; +struct device_node; + +char *ap_cp_unique_name(struct device *dev, struct device_node *np, + const char *name); +#endif diff --git a/drivers/clk/mvebu/clk-corediv.c b/drivers/clk/mvebu/clk-corediv.c new file mode 100644 index 000000000..ae48293b0 --- /dev/null +++ b/drivers/clk/mvebu/clk-corediv.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MVEBU Core divider clock + * + * Copyright (C) 2013 Marvell + * + * Ezequiel Garcia <ezequiel.garcia@free-electrons.com> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "common.h" + +#define CORE_CLK_DIV_RATIO_MASK 0xff + +/* + * This structure describes the hardware details (bit offset and mask) + * to configure one particular core divider clock. Those hardware + * details may differ from one SoC to another. This structure is + * therefore typically instantiated statically to describe the + * hardware details. + */ +struct clk_corediv_desc { + unsigned int mask; + unsigned int offset; + unsigned int fieldbit; +}; + +/* + * This structure describes the hardware details to configure the core + * divider clocks on a given SoC. Amongst others, it points to the + * array of core divider clock descriptors for this SoC, as well as + * the corresponding operations to manipulate them. + */ +struct clk_corediv_soc_desc { + const struct clk_corediv_desc *descs; + unsigned int ndescs; + const struct clk_ops ops; + u32 ratio_reload; + u32 enable_bit_offset; + u32 ratio_offset; +}; + +/* + * This structure represents one core divider clock for the clock + * framework, and is dynamically allocated for each core divider clock + * existing in the current SoC. + */ +struct clk_corediv { + struct clk_hw hw; + void __iomem *reg; + const struct clk_corediv_desc *desc; + const struct clk_corediv_soc_desc *soc_desc; + spinlock_t lock; +}; + +static struct clk_onecell_data clk_data; + +/* + * Description of the core divider clocks available. For now, we + * support only NAND, and it is available at the same register + * locations regardless of the SoC. + */ +static const struct clk_corediv_desc mvebu_corediv_desc[] = { + { .mask = 0x3f, .offset = 8, .fieldbit = 1 }, /* NAND clock */ +}; + +static const struct clk_corediv_desc mv98dx3236_corediv_desc[] = { + { .mask = 0x0f, .offset = 6, .fieldbit = 27 }, /* NAND clock */ +}; + +#define to_corediv_clk(p) container_of(p, struct clk_corediv, hw) + +static int clk_corediv_is_enabled(struct clk_hw *hwclk) +{ + struct clk_corediv *corediv = to_corediv_clk(hwclk); + const struct clk_corediv_soc_desc *soc_desc = corediv->soc_desc; + const struct clk_corediv_desc *desc = corediv->desc; + u32 enable_mask = BIT(desc->fieldbit) << soc_desc->enable_bit_offset; + + return !!(readl(corediv->reg) & enable_mask); +} + +static int clk_corediv_enable(struct clk_hw *hwclk) +{ + struct clk_corediv *corediv = to_corediv_clk(hwclk); + const struct clk_corediv_soc_desc *soc_desc = corediv->soc_desc; + const struct clk_corediv_desc *desc = corediv->desc; + unsigned long flags = 0; + u32 reg; + + spin_lock_irqsave(&corediv->lock, flags); + + reg = readl(corediv->reg); + reg |= (BIT(desc->fieldbit) << soc_desc->enable_bit_offset); + writel(reg, corediv->reg); + + spin_unlock_irqrestore(&corediv->lock, flags); + + return 0; +} + +static void clk_corediv_disable(struct clk_hw *hwclk) +{ + struct clk_corediv *corediv = to_corediv_clk(hwclk); + const struct clk_corediv_soc_desc *soc_desc = corediv->soc_desc; + const struct clk_corediv_desc *desc = corediv->desc; + unsigned long flags = 0; + u32 reg; + + spin_lock_irqsave(&corediv->lock, flags); + + reg = readl(corediv->reg); + reg &= ~(BIT(desc->fieldbit) << soc_desc->enable_bit_offset); + writel(reg, corediv->reg); + + spin_unlock_irqrestore(&corediv->lock, flags); +} + +static unsigned long clk_corediv_recalc_rate(struct clk_hw *hwclk, + unsigned long parent_rate) +{ + struct clk_corediv *corediv = to_corediv_clk(hwclk); + const struct clk_corediv_soc_desc *soc_desc = corediv->soc_desc; + const struct clk_corediv_desc *desc = corediv->desc; + u32 reg, div; + + reg = readl(corediv->reg + soc_desc->ratio_offset); + div = (reg >> desc->offset) & desc->mask; + return parent_rate / div; +} + +static int clk_corediv_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + /* Valid ratio are 1:4, 1:5, 1:6 and 1:8 */ + u32 div; + + div = req->best_parent_rate / req->rate; + if (div < 4) + div = 4; + else if (div > 6) + div = 8; + + req->rate = req->best_parent_rate / div; + + return 0; +} + +static int clk_corediv_set_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_corediv *corediv = to_corediv_clk(hwclk); + const struct clk_corediv_soc_desc *soc_desc = corediv->soc_desc; + const struct clk_corediv_desc *desc = corediv->desc; + unsigned long flags = 0; + u32 reg, div; + + div = parent_rate / rate; + + spin_lock_irqsave(&corediv->lock, flags); + + /* Write new divider to the divider ratio register */ + reg = readl(corediv->reg + soc_desc->ratio_offset); + reg &= ~(desc->mask << desc->offset); + reg |= (div & desc->mask) << desc->offset; + writel(reg, corediv->reg + soc_desc->ratio_offset); + + /* Set reload-force for this clock */ + reg = readl(corediv->reg) | BIT(desc->fieldbit); + writel(reg, corediv->reg); + + /* Now trigger the clock update */ + reg = readl(corediv->reg) | soc_desc->ratio_reload; + writel(reg, corediv->reg); + + /* + * Wait for clocks to settle down, and then clear all the + * ratios request and the reload request. + */ + udelay(1000); + reg &= ~(CORE_CLK_DIV_RATIO_MASK | soc_desc->ratio_reload); + writel(reg, corediv->reg); + udelay(1000); + + spin_unlock_irqrestore(&corediv->lock, flags); + + return 0; +} + +static const struct clk_corediv_soc_desc armada370_corediv_soc = { + .descs = mvebu_corediv_desc, + .ndescs = ARRAY_SIZE(mvebu_corediv_desc), + .ops = { + .enable = clk_corediv_enable, + .disable = clk_corediv_disable, + .is_enabled = clk_corediv_is_enabled, + .recalc_rate = clk_corediv_recalc_rate, + .determine_rate = clk_corediv_determine_rate, + .set_rate = clk_corediv_set_rate, + }, + .ratio_reload = BIT(8), + .enable_bit_offset = 24, + .ratio_offset = 0x8, +}; + +static const struct clk_corediv_soc_desc armada380_corediv_soc = { + .descs = mvebu_corediv_desc, + .ndescs = ARRAY_SIZE(mvebu_corediv_desc), + .ops = { + .enable = clk_corediv_enable, + .disable = clk_corediv_disable, + .is_enabled = clk_corediv_is_enabled, + .recalc_rate = clk_corediv_recalc_rate, + .determine_rate = clk_corediv_determine_rate, + .set_rate = clk_corediv_set_rate, + }, + .ratio_reload = BIT(8), + .enable_bit_offset = 16, + .ratio_offset = 0x4, +}; + +static const struct clk_corediv_soc_desc armada375_corediv_soc = { + .descs = mvebu_corediv_desc, + .ndescs = ARRAY_SIZE(mvebu_corediv_desc), + .ops = { + .recalc_rate = clk_corediv_recalc_rate, + .determine_rate = clk_corediv_determine_rate, + .set_rate = clk_corediv_set_rate, + }, + .ratio_reload = BIT(8), + .ratio_offset = 0x4, +}; + +static const struct clk_corediv_soc_desc mv98dx3236_corediv_soc = { + .descs = mv98dx3236_corediv_desc, + .ndescs = ARRAY_SIZE(mv98dx3236_corediv_desc), + .ops = { + .recalc_rate = clk_corediv_recalc_rate, + .determine_rate = clk_corediv_determine_rate, + .set_rate = clk_corediv_set_rate, + }, + .ratio_reload = BIT(10), + .ratio_offset = 0x8, +}; + +static void __init +mvebu_corediv_clk_init(struct device_node *node, + const struct clk_corediv_soc_desc *soc_desc) +{ + struct clk_init_data init; + struct clk_corediv *corediv; + struct clk **clks; + void __iomem *base; + const char *parent_name; + const char *clk_name; + int i; + + base = of_iomap(node, 0); + if (WARN_ON(!base)) + return; + + parent_name = of_clk_get_parent_name(node, 0); + + clk_data.clk_num = soc_desc->ndescs; + + /* clks holds the clock array */ + clks = kzalloc_objs(struct clk *, clk_data.clk_num); + if (WARN_ON(!clks)) + goto err_unmap; + /* corediv holds the clock specific array */ + corediv = kzalloc_objs(struct clk_corediv, clk_data.clk_num); + if (WARN_ON(!corediv)) + goto err_free_clks; + + spin_lock_init(&corediv->lock); + + for (i = 0; i < clk_data.clk_num; i++) { + of_property_read_string_index(node, "clock-output-names", + i, &clk_name); + init.num_parents = 1; + init.parent_names = &parent_name; + init.name = clk_name; + init.ops = &soc_desc->ops; + init.flags = 0; + + corediv[i].soc_desc = soc_desc; + corediv[i].desc = soc_desc->descs + i; + corediv[i].reg = base; + corediv[i].hw.init = &init; + + clks[i] = clk_register(NULL, &corediv[i].hw); + WARN_ON(IS_ERR(clks[i])); + } + + clk_data.clks = clks; + of_clk_add_provider(node, of_clk_src_onecell_get, &clk_data); + return; + +err_free_clks: + kfree(clks); +err_unmap: + iounmap(base); +} + +static void __init armada370_corediv_clk_init(struct device_node *node) +{ + return mvebu_corediv_clk_init(node, &armada370_corediv_soc); +} +CLK_OF_DECLARE(armada370_corediv_clk, "marvell,armada-370-corediv-clock", + armada370_corediv_clk_init); + +static void __init armada375_corediv_clk_init(struct device_node *node) +{ + return mvebu_corediv_clk_init(node, &armada375_corediv_soc); +} +CLK_OF_DECLARE(armada375_corediv_clk, "marvell,armada-375-corediv-clock", + armada375_corediv_clk_init); + +static void __init armada380_corediv_clk_init(struct device_node *node) +{ + return mvebu_corediv_clk_init(node, &armada380_corediv_soc); +} +CLK_OF_DECLARE(armada380_corediv_clk, "marvell,armada-380-corediv-clock", + armada380_corediv_clk_init); + +static void __init mv98dx3236_corediv_clk_init(struct device_node *node) +{ + return mvebu_corediv_clk_init(node, &mv98dx3236_corediv_soc); +} +CLK_OF_DECLARE(mv98dx3236_corediv_clk, "marvell,mv98dx3236-corediv-clock", + mv98dx3236_corediv_clk_init); diff --git a/drivers/clk/mvebu/clk-cpu.c b/drivers/clk/mvebu/clk-cpu.c new file mode 100644 index 000000000..24196e288 --- /dev/null +++ b/drivers/clk/mvebu/clk-cpu.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell MVEBU CPU clock handling. + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/mvebu-pmsu.h> +#include <asm/smp_plat.h> + +#define SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET 0x0 +#define SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL 0xff +#define SYS_CTRL_CLK_DIVIDER_CTRL_RESET_SHIFT 8 +#define SYS_CTRL_CLK_DIVIDER_CTRL2_OFFSET 0x8 +#define SYS_CTRL_CLK_DIVIDER_CTRL2_NBCLK_RATIO_SHIFT 16 +#define SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET 0xC +#define SYS_CTRL_CLK_DIVIDER_MASK 0x3F + +#define PMU_DFS_RATIO_SHIFT 16 +#define PMU_DFS_RATIO_MASK 0x3F + +#define MAX_CPU 4 +struct cpu_clk { + struct clk_hw hw; + int cpu; + const char *clk_name; + const char *parent_name; + void __iomem *reg_base; + void __iomem *pmu_dfs; +}; + +static struct clk **clks; + +static struct clk_onecell_data clk_data; + +#define to_cpu_clk(p) container_of(p, struct cpu_clk, hw) + +static unsigned long clk_cpu_recalc_rate(struct clk_hw *hwclk, + unsigned long parent_rate) +{ + struct cpu_clk *cpuclk = to_cpu_clk(hwclk); + u32 reg, div; + + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET); + div = (reg >> (cpuclk->cpu * 8)) & SYS_CTRL_CLK_DIVIDER_MASK; + return parent_rate / div; +} + +static int clk_cpu_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + /* Valid ratio are 1:1, 1:2 and 1:3 */ + u32 div; + + div = req->best_parent_rate / req->rate; + if (div == 0) + div = 1; + else if (div > 3) + div = 3; + + req->rate = req->best_parent_rate / div; + + return 0; +} + +static int clk_cpu_off_set_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long parent_rate) + +{ + struct cpu_clk *cpuclk = to_cpu_clk(hwclk); + u32 reg, div; + u32 reload_mask; + + div = parent_rate / rate; + reg = (readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET) + & (~(SYS_CTRL_CLK_DIVIDER_MASK << (cpuclk->cpu * 8)))) + | (div << (cpuclk->cpu * 8)); + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET); + /* Set clock divider reload smooth bit mask */ + reload_mask = 1 << (20 + cpuclk->cpu); + + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET) + | reload_mask; + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); + + /* Now trigger the clock update */ + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET) + | 1 << 24; + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); + + /* Wait for clocks to settle down then clear reload request */ + udelay(1000); + reg &= ~(reload_mask | 1 << 24); + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); + udelay(1000); + + return 0; +} + +static int clk_cpu_on_set_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long parent_rate) +{ + u32 reg; + unsigned long fabric_div, target_div, cur_rate; + struct cpu_clk *cpuclk = to_cpu_clk(hwclk); + + /* + * PMU DFS registers are not mapped, Device Tree does not + * describes them. We cannot change the frequency dynamically. + */ + if (!cpuclk->pmu_dfs) + return -ENODEV; + + cur_rate = clk_hw_get_rate(hwclk); + + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL2_OFFSET); + fabric_div = (reg >> SYS_CTRL_CLK_DIVIDER_CTRL2_NBCLK_RATIO_SHIFT) & + SYS_CTRL_CLK_DIVIDER_MASK; + + /* Frequency is going up */ + if (rate == 2 * cur_rate) + target_div = fabric_div / 2; + /* Frequency is going down */ + else + target_div = fabric_div; + + if (target_div == 0) + target_div = 1; + + reg = readl(cpuclk->pmu_dfs); + reg &= ~(PMU_DFS_RATIO_MASK << PMU_DFS_RATIO_SHIFT); + reg |= (target_div << PMU_DFS_RATIO_SHIFT); + writel(reg, cpuclk->pmu_dfs); + + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); + reg |= (SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL << + SYS_CTRL_CLK_DIVIDER_CTRL_RESET_SHIFT); + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); + + return mvebu_pmsu_dfs_request(cpuclk->cpu); +} + +static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long parent_rate) +{ + if (clk_hw_is_enabled(hwclk)) + return clk_cpu_on_set_rate(hwclk, rate, parent_rate); + else + return clk_cpu_off_set_rate(hwclk, rate, parent_rate); +} + +static const struct clk_ops cpu_ops = { + .recalc_rate = clk_cpu_recalc_rate, + .determine_rate = clk_cpu_determine_rate, + .set_rate = clk_cpu_set_rate, +}; + +static void __init of_cpu_clk_setup(struct device_node *node) +{ + struct cpu_clk *cpuclk; + void __iomem *clock_complex_base = of_iomap(node, 0); + void __iomem *pmu_dfs_base = of_iomap(node, 1); + int ncpus = num_possible_cpus(); + int cpu; + + if (clock_complex_base == NULL) { + pr_err("%s: clock-complex base register not set\n", + __func__); + return; + } + + if (pmu_dfs_base == NULL) + pr_warn("%s: pmu-dfs base register not set, dynamic frequency scaling not available\n", + __func__); + + cpuclk = kzalloc_objs(*cpuclk, ncpus); + if (WARN_ON(!cpuclk)) + goto cpuclk_out; + + clks = kzalloc_objs(*clks, ncpus); + if (WARN_ON(!clks)) + goto clks_out; + + for_each_possible_cpu(cpu) { + struct clk_init_data init; + struct clk *clk; + char *clk_name = kzalloc(5, GFP_KERNEL); + + if (WARN_ON(!clk_name)) + goto bail_out; + + sprintf(clk_name, "cpu%d", cpu); + + cpuclk[cpu].parent_name = of_clk_get_parent_name(node, 0); + cpuclk[cpu].clk_name = clk_name; + cpuclk[cpu].cpu = cpu; + cpuclk[cpu].reg_base = clock_complex_base; + if (pmu_dfs_base) + cpuclk[cpu].pmu_dfs = pmu_dfs_base + 4 * cpu; + cpuclk[cpu].hw.init = &init; + + init.name = cpuclk[cpu].clk_name; + init.ops = &cpu_ops; + init.flags = 0; + init.parent_names = &cpuclk[cpu].parent_name; + init.num_parents = 1; + + clk = clk_register(NULL, &cpuclk[cpu].hw); + if (WARN_ON(IS_ERR(clk))) + goto bail_out; + clks[cpu] = clk; + } + clk_data.clk_num = MAX_CPU; + clk_data.clks = clks; + of_clk_add_provider(node, of_clk_src_onecell_get, &clk_data); + + return; +bail_out: + kfree(clks); + while(ncpus--) + kfree(cpuclk[ncpus].clk_name); +clks_out: + kfree(cpuclk); +cpuclk_out: + iounmap(clock_complex_base); +} + +CLK_OF_DECLARE(armada_xp_cpu_clock, "marvell,armada-xp-cpu-clock", + of_cpu_clk_setup); + +static void __init of_mv98dx3236_cpu_clk_setup(struct device_node *node) +{ + of_clk_add_provider(node, of_clk_src_simple_get, NULL); +} + +CLK_OF_DECLARE(mv98dx3236_cpu_clock, "marvell,mv98dx3236-cpu-clock", + of_mv98dx3236_cpu_clk_setup); diff --git a/drivers/clk/mvebu/common.c b/drivers/clk/mvebu/common.c new file mode 100644 index 000000000..0f9ae5f5c --- /dev/null +++ b/drivers/clk/mvebu/common.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell EBU SoC common clock handling + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/syscore_ops.h> + +#include "common.h" + +/* + * Core Clocks + */ + +#define SSCG_CONF_MODE(reg) (((reg) >> 16) & 0x3) +#define SSCG_SPREAD_DOWN 0x0 +#define SSCG_SPREAD_UP 0x1 +#define SSCG_SPREAD_CENTRAL 0x2 +#define SSCG_CONF_LOW(reg) (((reg) >> 8) & 0xFF) +#define SSCG_CONF_HIGH(reg) ((reg) & 0xFF) + +static struct clk_onecell_data clk_data; + +/* + * This function can be used by the Kirkwood, the Armada 370, the + * Armada XP and the Armada 375 SoC. The name of the function was + * chosen following the dt convention: using the first known SoC + * compatible with it. + */ +u32 kirkwood_fix_sscg_deviation(u32 system_clk) +{ + struct device_node *sscg_np = NULL; + void __iomem *sscg_map; + u32 sscg_reg; + s32 low_bound, high_bound; + u64 freq_swing_half; + + sscg_np = of_find_node_by_name(NULL, "sscg"); + if (sscg_np == NULL) { + pr_err("cannot get SSCG register node\n"); + return system_clk; + } + + sscg_map = of_iomap(sscg_np, 0); + if (sscg_map == NULL) { + pr_err("cannot map SSCG register\n"); + goto out; + } + + sscg_reg = readl(sscg_map); + high_bound = SSCG_CONF_HIGH(sscg_reg); + low_bound = SSCG_CONF_LOW(sscg_reg); + + if ((high_bound - low_bound) <= 0) + goto out; + /* + * From Marvell engineer we got the following formula (when + * this code was written, the datasheet was erroneous) + * Spread percentage = 1/96 * (H - L) / H + * H = SSCG_High_Boundary + * L = SSCG_Low_Boundary + * + * As the deviation is half of spread then it lead to the + * following formula in the code. + * + * To avoid an overflow and not lose any significant digit in + * the same time we have to use a 64 bit integer. + */ + + freq_swing_half = (((u64)high_bound - (u64)low_bound) + * (u64)system_clk); + do_div(freq_swing_half, (2 * 96 * high_bound)); + + switch (SSCG_CONF_MODE(sscg_reg)) { + case SSCG_SPREAD_DOWN: + system_clk -= freq_swing_half; + break; + case SSCG_SPREAD_UP: + system_clk += freq_swing_half; + break; + case SSCG_SPREAD_CENTRAL: + default: + break; + } + + iounmap(sscg_map); + +out: + of_node_put(sscg_np); + + return system_clk; +} + +void __init mvebu_coreclk_setup(struct device_node *np, + const struct coreclk_soc_desc *desc) +{ + const char *tclk_name = "tclk"; + const char *cpuclk_name = "cpuclk"; + void __iomem *base; + unsigned long rate; + int n; + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + return; + + /* Allocate struct for TCLK, cpu clk, and core ratio clocks */ + clk_data.clk_num = 2 + desc->num_ratios; + + /* One more clock for the optional refclk */ + if (desc->get_refclk_freq) + clk_data.clk_num += 1; + + clk_data.clks = kzalloc_objs(*clk_data.clks, clk_data.clk_num); + if (WARN_ON(!clk_data.clks)) { + iounmap(base); + return; + } + + /* Register TCLK */ + of_property_read_string_index(np, "clock-output-names", 0, + &tclk_name); + rate = desc->get_tclk_freq(base); + clk_data.clks[0] = clk_register_fixed_rate(NULL, tclk_name, NULL, 0, + rate); + WARN_ON(IS_ERR(clk_data.clks[0])); + + /* Register CPU clock */ + of_property_read_string_index(np, "clock-output-names", 1, + &cpuclk_name); + rate = desc->get_cpu_freq(base); + + if (desc->is_sscg_enabled && desc->fix_sscg_deviation + && desc->is_sscg_enabled(base)) + rate = desc->fix_sscg_deviation(rate); + + clk_data.clks[1] = clk_register_fixed_rate(NULL, cpuclk_name, NULL, 0, + rate); + WARN_ON(IS_ERR(clk_data.clks[1])); + + /* Register fixed-factor clocks derived from CPU clock */ + for (n = 0; n < desc->num_ratios; n++) { + const char *rclk_name = desc->ratios[n].name; + int mult, div; + + of_property_read_string_index(np, "clock-output-names", + 2+n, &rclk_name); + desc->get_clk_ratio(base, desc->ratios[n].id, &mult, &div); + clk_data.clks[2+n] = clk_register_fixed_factor(NULL, rclk_name, + cpuclk_name, 0, mult, div); + WARN_ON(IS_ERR(clk_data.clks[2+n])); + } + + /* Register optional refclk */ + if (desc->get_refclk_freq) { + const char *name = "refclk"; + of_property_read_string_index(np, "clock-output-names", + 2 + desc->num_ratios, &name); + rate = desc->get_refclk_freq(base); + clk_data.clks[2 + desc->num_ratios] = + clk_register_fixed_rate(NULL, name, NULL, 0, rate); + WARN_ON(IS_ERR(clk_data.clks[2 + desc->num_ratios])); + } + + /* SAR register isn't needed anymore */ + iounmap(base); + + of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); +} + +/* + * Clock Gating Control + */ + +DEFINE_SPINLOCK(ctrl_gating_lock); + +struct clk_gating_ctrl { + spinlock_t *lock; + int num_gates; + void __iomem *base; + u32 saved_reg; + struct clk *gates[] __counted_by(num_gates); +}; + +static struct clk_gating_ctrl *ctrl; + +static struct clk *clk_gating_get_src( + struct of_phandle_args *clkspec, void *data) +{ + int n; + + if (clkspec->args_count < 1) + return ERR_PTR(-EINVAL); + + for (n = 0; n < ctrl->num_gates; n++) { + struct clk_gate *gate = + to_clk_gate(__clk_get_hw(ctrl->gates[n])); + if (clkspec->args[0] == gate->bit_idx) + return ctrl->gates[n]; + } + return ERR_PTR(-ENODEV); +} + +static int mvebu_clk_gating_suspend(void *data) +{ + ctrl->saved_reg = readl(ctrl->base); + return 0; +} + +static void mvebu_clk_gating_resume(void *data) +{ + writel(ctrl->saved_reg, ctrl->base); +} + +static const struct syscore_ops clk_gate_syscore_ops = { + .suspend = mvebu_clk_gating_suspend, + .resume = mvebu_clk_gating_resume, +}; + +static struct syscore clk_gate_syscore = { + .ops = &clk_gate_syscore_ops, +}; + +void __init mvebu_clk_gating_setup(struct device_node *np, + const struct clk_gating_soc_desc *desc) +{ + struct clk *clk; + void __iomem *base; + const char *default_parent = NULL; + int n; + + if (ctrl) { + pr_err("mvebu-clk-gating: cannot instantiate more than one gateable clock device\n"); + return; + } + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + return; + + clk = of_clk_get(np, 0); + if (!IS_ERR(clk)) { + default_parent = __clk_get_name(clk); + clk_put(clk); + } + + /* Count, allocate, and register clock gates */ + for (n = 0; desc[n].name;) + n++; + + ctrl = kzalloc_flex(*ctrl, gates, n); + if (WARN_ON(!ctrl)) + goto ctrl_out; + + ctrl->num_gates = n; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; + + ctrl->base = base; + + for (n = 0; n < ctrl->num_gates; n++) { + const char *parent = + (desc[n].parent) ? desc[n].parent : default_parent; + ctrl->gates[n] = clk_register_gate(NULL, desc[n].name, parent, + desc[n].flags, base, desc[n].bit_idx, + 0, ctrl->lock); + WARN_ON(IS_ERR(ctrl->gates[n])); + } + + of_clk_add_provider(np, clk_gating_get_src, ctrl); + + register_syscore(&clk_gate_syscore); + + return; +ctrl_out: + iounmap(base); +} diff --git a/drivers/clk/mvebu/common.h b/drivers/clk/mvebu/common.h new file mode 100644 index 000000000..d1ab79b43 --- /dev/null +++ b/drivers/clk/mvebu/common.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Marvell EBU SoC common clock handling + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#ifndef __CLK_MVEBU_COMMON_H_ +#define __CLK_MVEBU_COMMON_H_ + +#include <linux/kernel.h> + +extern spinlock_t ctrl_gating_lock; + +struct device_node; + +struct coreclk_ratio { + int id; + const char *name; +}; + +struct coreclk_soc_desc { + u32 (*get_tclk_freq)(void __iomem *sar); + u32 (*get_cpu_freq)(void __iomem *sar); + void (*get_clk_ratio)(void __iomem *sar, int id, int *mult, int *div); + u32 (*get_refclk_freq)(void __iomem *sar); + bool (*is_sscg_enabled)(void __iomem *sar); + u32 (*fix_sscg_deviation)(u32 system_clk); + const struct coreclk_ratio *ratios; + int num_ratios; +}; + +struct clk_gating_soc_desc { + const char *name; + const char *parent; + int bit_idx; + unsigned long flags; +}; + +void __init mvebu_coreclk_setup(struct device_node *np, + const struct coreclk_soc_desc *desc); + +void __init mvebu_clk_gating_setup(struct device_node *np, + const struct clk_gating_soc_desc *desc); + +/* + * This function is shared among the Kirkwood, Armada 370, Armada XP + * and Armada 375 SoC + */ +u32 kirkwood_fix_sscg_deviation(u32 system_clk); +#endif diff --git a/drivers/clk/mvebu/cp110-system-controller.c b/drivers/clk/mvebu/cp110-system-controller.c new file mode 100644 index 000000000..fa95eb719 --- /dev/null +++ b/drivers/clk/mvebu/cp110-system-controller.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Armada CP110 System Controller + * + * Copyright (C) 2016 Marvell + * + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + */ + +/* + * CP110 has 6 core clocks: + * + * - PLL0 (1 Ghz) + * - PPv2 core (1/3 PLL0) + * - x2 Core (1/2 PLL0) + * - Core (1/2 x2 Core) + * - SDIO (2/5 PLL0) + * + * - NAND clock, which is either: + * - Equal to SDIO clock + * - 2/5 PLL0 + * + * CP110 has 32 gateable clocks, for the various peripherals in the IP. + */ + +#define pr_fmt(fmt) "cp110-system-controller: " fmt + +#include "armada_ap_cp_helper.h" +#include <linux/clk-provider.h> +#include <linux/mfd/syscon.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define CP110_PM_CLOCK_GATING_REG 0x220 +#define CP110_NAND_FLASH_CLK_CTRL_REG 0x700 +#define NF_CLOCK_SEL_400_MASK BIT(0) + +enum { + CP110_CLK_TYPE_CORE, + CP110_CLK_TYPE_GATABLE, +}; + +#define CP110_MAX_CORE_CLOCKS 6 +#define CP110_MAX_GATABLE_CLOCKS 32 + +#define CP110_CLK_NUM \ + (CP110_MAX_CORE_CLOCKS + CP110_MAX_GATABLE_CLOCKS) + +#define CP110_CORE_PLL0 0 +#define CP110_CORE_PPV2 1 +#define CP110_CORE_X2CORE 2 +#define CP110_CORE_CORE 3 +#define CP110_CORE_NAND 4 +#define CP110_CORE_SDIO 5 + +/* A number of gateable clocks need special handling */ +#define CP110_GATE_AUDIO 0 +#define CP110_GATE_COMM_UNIT 1 +#define CP110_GATE_NAND 2 +#define CP110_GATE_PPV2 3 +#define CP110_GATE_SDIO 4 +#define CP110_GATE_MG 5 +#define CP110_GATE_MG_CORE 6 +#define CP110_GATE_XOR1 7 +#define CP110_GATE_XOR0 8 +#define CP110_GATE_GOP_DP 9 +#define CP110_GATE_PCIE_X1_0 11 +#define CP110_GATE_PCIE_X1_1 12 +#define CP110_GATE_PCIE_X4 13 +#define CP110_GATE_PCIE_XOR 14 +#define CP110_GATE_SATA 15 +#define CP110_GATE_SATA_USB 16 +#define CP110_GATE_MAIN 17 +#define CP110_GATE_SDMMC_GOP 18 +#define CP110_GATE_SLOW_IO 21 +#define CP110_GATE_USB3H0 22 +#define CP110_GATE_USB3H1 23 +#define CP110_GATE_USB3DEV 24 +#define CP110_GATE_EIP150 25 +#define CP110_GATE_EIP197 26 + +static const char * const gate_base_names[] = { + [CP110_GATE_AUDIO] = "audio", + [CP110_GATE_COMM_UNIT] = "communit", + [CP110_GATE_NAND] = "nand", + [CP110_GATE_PPV2] = "ppv2", + [CP110_GATE_SDIO] = "sdio", + [CP110_GATE_MG] = "mg-domain", + [CP110_GATE_MG_CORE] = "mg-core", + [CP110_GATE_XOR1] = "xor1", + [CP110_GATE_XOR0] = "xor0", + [CP110_GATE_GOP_DP] = "gop-dp", + [CP110_GATE_PCIE_X1_0] = "pcie_x10", + [CP110_GATE_PCIE_X1_1] = "pcie_x11", + [CP110_GATE_PCIE_X4] = "pcie_x4", + [CP110_GATE_PCIE_XOR] = "pcie-xor", + [CP110_GATE_SATA] = "sata", + [CP110_GATE_SATA_USB] = "sata-usb", + [CP110_GATE_MAIN] = "main", + [CP110_GATE_SDMMC_GOP] = "sd-mmc-gop", + [CP110_GATE_SLOW_IO] = "slow-io", + [CP110_GATE_USB3H0] = "usb3h0", + [CP110_GATE_USB3H1] = "usb3h1", + [CP110_GATE_USB3DEV] = "usb3dev", + [CP110_GATE_EIP150] = "eip150", + [CP110_GATE_EIP197] = "eip197" +}; + +static unsigned long gate_flags(const u8 bit_idx) +{ + switch (bit_idx) { + case CP110_GATE_PCIE_X1_0: + case CP110_GATE_PCIE_X1_1: + case CP110_GATE_PCIE_X4: + /* + * If a port had an active link at boot time, stopping + * the clock creates a failed state from which controller + * driver can not recover. + * Prevent stopping this clock till after a driver has taken + * ownership. + */ + return CLK_IGNORE_UNUSED; + default: + return 0; + } +}; + +struct cp110_gate_clk { + struct clk_hw hw; + struct regmap *regmap; + u8 bit_idx; +}; + +#define to_cp110_gate_clk(hw) container_of(hw, struct cp110_gate_clk, hw) + +static int cp110_gate_enable(struct clk_hw *hw) +{ + struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); + + regmap_update_bits(gate->regmap, CP110_PM_CLOCK_GATING_REG, + BIT(gate->bit_idx), BIT(gate->bit_idx)); + + return 0; +} + +static void cp110_gate_disable(struct clk_hw *hw) +{ + struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); + + regmap_update_bits(gate->regmap, CP110_PM_CLOCK_GATING_REG, + BIT(gate->bit_idx), 0); +} + +static int cp110_gate_is_enabled(struct clk_hw *hw) +{ + struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); + u32 val; + + regmap_read(gate->regmap, CP110_PM_CLOCK_GATING_REG, &val); + + return val & BIT(gate->bit_idx); +} + +static const struct clk_ops cp110_gate_ops = { + .enable = cp110_gate_enable, + .disable = cp110_gate_disable, + .is_enabled = cp110_gate_is_enabled, +}; + +static struct clk_hw *cp110_register_gate(const char *name, + const char *parent_name, + struct regmap *regmap, u8 bit_idx) +{ + struct cp110_gate_clk *gate; + struct clk_hw *hw; + struct clk_init_data init; + int ret; + + gate = kzalloc_obj(*gate); + if (!gate) + return ERR_PTR(-ENOMEM); + + memset(&init, 0, sizeof(init)); + + init.name = name; + init.ops = &cp110_gate_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = gate_flags(bit_idx); + + gate->regmap = regmap; + gate->bit_idx = bit_idx; + gate->hw.init = &init; + + hw = &gate->hw; + ret = clk_hw_register(NULL, hw); + if (ret) { + kfree(gate); + hw = ERR_PTR(ret); + } + + return hw; +} + +static void cp110_unregister_gate(struct clk_hw *hw) +{ + clk_hw_unregister(hw); + kfree(to_cp110_gate_clk(hw)); +} + +static struct clk_hw *cp110_of_clk_get(struct of_phandle_args *clkspec, + void *data) +{ + struct clk_hw_onecell_data *clk_data = data; + unsigned int type = clkspec->args[0]; + unsigned int idx = clkspec->args[1]; + + if (type == CP110_CLK_TYPE_CORE) { + if (idx >= CP110_MAX_CORE_CLOCKS) + return ERR_PTR(-EINVAL); + return clk_data->hws[idx]; + } else if (type == CP110_CLK_TYPE_GATABLE) { + if (idx >= CP110_MAX_GATABLE_CLOCKS) + return ERR_PTR(-EINVAL); + return clk_data->hws[CP110_MAX_CORE_CLOCKS + idx]; + } + + return ERR_PTR(-EINVAL); +} + +static int cp110_syscon_common_probe(struct platform_device *pdev, + struct device_node *syscon_node) +{ + struct regmap *regmap; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const char *ppv2_name, *pll0_name, *core_name, *x2core_name, *nand_name, + *sdio_name; + struct clk_hw_onecell_data *cp110_clk_data; + struct clk_hw *hw, **cp110_clks; + u32 nand_clk_ctrl; + int i, ret; + char *gate_name[ARRAY_SIZE(gate_base_names)]; + + regmap = syscon_node_to_regmap(syscon_node); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_read(regmap, CP110_NAND_FLASH_CLK_CTRL_REG, + &nand_clk_ctrl); + if (ret) + return ret; + + cp110_clk_data = devm_kzalloc(dev, struct_size(cp110_clk_data, hws, + CP110_CLK_NUM), + GFP_KERNEL); + if (!cp110_clk_data) + return -ENOMEM; + cp110_clk_data->num = CP110_CLK_NUM; + + cp110_clks = cp110_clk_data->hws; + + /* Register the PLL0 which is the root of the hw tree */ + pll0_name = ap_cp_unique_name(dev, syscon_node, "pll0"); + hw = clk_hw_register_fixed_rate(NULL, pll0_name, NULL, 0, + 1000 * 1000 * 1000); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_pll0; + } + + cp110_clks[CP110_CORE_PLL0] = hw; + + /* PPv2 is PLL0/3 */ + ppv2_name = ap_cp_unique_name(dev, syscon_node, "ppv2-core"); + hw = clk_hw_register_fixed_factor(NULL, ppv2_name, pll0_name, 0, 1, 3); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_ppv2; + } + + cp110_clks[CP110_CORE_PPV2] = hw; + + /* X2CORE clock is PLL0/2 */ + x2core_name = ap_cp_unique_name(dev, syscon_node, "x2core"); + hw = clk_hw_register_fixed_factor(NULL, x2core_name, pll0_name, + 0, 1, 2); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_eip; + } + + cp110_clks[CP110_CORE_X2CORE] = hw; + + /* Core clock is X2CORE/2 */ + core_name = ap_cp_unique_name(dev, syscon_node, "core"); + hw = clk_hw_register_fixed_factor(NULL, core_name, x2core_name, + 0, 1, 2); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_core; + } + + cp110_clks[CP110_CORE_CORE] = hw; + /* NAND can be either PLL0/2.5 or core clock */ + nand_name = ap_cp_unique_name(dev, syscon_node, "nand-core"); + if (nand_clk_ctrl & NF_CLOCK_SEL_400_MASK) + hw = clk_hw_register_fixed_factor(NULL, nand_name, + pll0_name, 0, 2, 5); + else + hw = clk_hw_register_fixed_factor(NULL, nand_name, + core_name, 0, 1, 1); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_nand; + } + + cp110_clks[CP110_CORE_NAND] = hw; + + /* SDIO clock is PLL0/2.5 */ + sdio_name = ap_cp_unique_name(dev, syscon_node, "sdio-core"); + hw = clk_hw_register_fixed_factor(NULL, sdio_name, + pll0_name, 0, 2, 5); + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_sdio; + } + + cp110_clks[CP110_CORE_SDIO] = hw; + + /* create the unique name for all the gate clocks */ + for (i = 0; i < ARRAY_SIZE(gate_base_names); i++) + gate_name[i] = ap_cp_unique_name(dev, syscon_node, + gate_base_names[i]); + + for (i = 0; i < ARRAY_SIZE(gate_base_names); i++) { + const char *parent; + + if (gate_name[i] == NULL) + continue; + + switch (i) { + case CP110_GATE_NAND: + parent = nand_name; + break; + case CP110_GATE_MG: + case CP110_GATE_GOP_DP: + case CP110_GATE_PPV2: + parent = ppv2_name; + break; + case CP110_GATE_SDIO: + parent = sdio_name; + break; + case CP110_GATE_MAIN: + case CP110_GATE_PCIE_XOR: + case CP110_GATE_PCIE_X4: + case CP110_GATE_EIP150: + case CP110_GATE_EIP197: + parent = x2core_name; + break; + default: + parent = core_name; + break; + } + hw = cp110_register_gate(gate_name[i], parent, regmap, i); + + if (IS_ERR(hw)) { + ret = PTR_ERR(hw); + goto fail_gate; + } + + cp110_clks[CP110_MAX_CORE_CLOCKS + i] = hw; + } + + ret = of_clk_add_hw_provider(np, cp110_of_clk_get, cp110_clk_data); + if (ret) + goto fail_clk_add; + + platform_set_drvdata(pdev, cp110_clks); + + return 0; + +fail_clk_add: +fail_gate: + for (i = 0; i < CP110_MAX_GATABLE_CLOCKS; i++) { + hw = cp110_clks[CP110_MAX_CORE_CLOCKS + i]; + + if (hw) + cp110_unregister_gate(hw); + } + + clk_hw_unregister_fixed_factor(cp110_clks[CP110_CORE_SDIO]); +fail_sdio: + clk_hw_unregister_fixed_factor(cp110_clks[CP110_CORE_NAND]); +fail_nand: + clk_hw_unregister_fixed_factor(cp110_clks[CP110_CORE_CORE]); +fail_core: + clk_hw_unregister_fixed_factor(cp110_clks[CP110_CORE_X2CORE]); +fail_eip: + clk_hw_unregister_fixed_factor(cp110_clks[CP110_CORE_PPV2]); +fail_ppv2: + clk_hw_unregister_fixed_rate(cp110_clks[CP110_CORE_PLL0]); +fail_pll0: + return ret; +} + +static int cp110_syscon_legacy_clk_probe(struct platform_device *pdev) +{ + dev_warn(&pdev->dev, FW_WARN "Using legacy device tree binding\n"); + dev_warn(&pdev->dev, FW_WARN "Update your device tree:\n"); + dev_warn(&pdev->dev, FW_WARN + "This binding won't be supported in future kernels\n"); + + return cp110_syscon_common_probe(pdev, pdev->dev.of_node); +} + +static int cp110_clk_probe(struct platform_device *pdev) +{ + return cp110_syscon_common_probe(pdev, pdev->dev.of_node->parent); +} + +static const struct of_device_id cp110_syscon_legacy_of_match[] = { + { .compatible = "marvell,cp110-system-controller0", }, + { } +}; + +static struct platform_driver cp110_syscon_legacy_driver = { + .probe = cp110_syscon_legacy_clk_probe, + .driver = { + .name = "marvell-cp110-system-controller0", + .of_match_table = cp110_syscon_legacy_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(cp110_syscon_legacy_driver); + +static const struct of_device_id cp110_clock_of_match[] = { + { .compatible = "marvell,cp110-clock", }, + { } +}; + +static struct platform_driver cp110_clock_driver = { + .probe = cp110_clk_probe, + .driver = { + .name = "marvell-cp110-clock", + .of_match_table = cp110_clock_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(cp110_clock_driver); diff --git a/drivers/clk/mvebu/dove-divider.c b/drivers/clk/mvebu/dove-divider.c new file mode 100644 index 000000000..47cc49e4c --- /dev/null +++ b/drivers/clk/mvebu/dove-divider.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Dove PMU Core PLL divider driver + * + * Cleaned up by substantially rewriting, and converted to DT by + * Russell King. Origin is not known. + */ +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "dove-divider.h" + +struct dove_clk { + const char *name; + struct clk_hw hw; + void __iomem *base; + spinlock_t *lock; + u8 div_bit_start; + u8 div_bit_end; + u8 div_bit_load; + u8 div_bit_size; + u32 *divider_table; +}; + +enum { + DIV_CTRL0 = 0, + DIV_CTRL1 = 4, + DIV_CTRL1_N_RESET_MASK = BIT(10), +}; + +#define to_dove_clk(hw) container_of(hw, struct dove_clk, hw) + +static void dove_load_divider(void __iomem *base, u32 val, u32 mask, u32 load) +{ + u32 v; + + v = readl_relaxed(base + DIV_CTRL1) | DIV_CTRL1_N_RESET_MASK; + writel_relaxed(v, base + DIV_CTRL1); + + v = (readl_relaxed(base + DIV_CTRL0) & ~(mask | load)) | val; + writel_relaxed(v, base + DIV_CTRL0); + writel_relaxed(v | load, base + DIV_CTRL0); + ndelay(250); + writel_relaxed(v, base + DIV_CTRL0); +} + +static unsigned int dove_get_divider(struct dove_clk *dc) +{ + unsigned int divider; + u32 val; + + val = readl_relaxed(dc->base + DIV_CTRL0); + val >>= dc->div_bit_start; + + divider = val & ~(~0 << dc->div_bit_size); + + if (dc->divider_table) + divider = dc->divider_table[divider]; + + return divider; +} + +static int dove_calc_divider(const struct dove_clk *dc, unsigned long rate, + unsigned long parent_rate, bool set) +{ + unsigned int divider, max; + + divider = DIV_ROUND_CLOSEST(parent_rate, rate); + + if (dc->divider_table) { + unsigned int i; + + for (i = 0; dc->divider_table[i]; i++) + if (divider == dc->divider_table[i]) { + divider = i; + break; + } + + if (!dc->divider_table[i]) + return -EINVAL; + } else { + max = 1 << dc->div_bit_size; + + if (set && (divider == 0 || divider >= max)) + return -EINVAL; + if (divider >= max) + divider = max - 1; + else if (divider == 0) + divider = 1; + } + + return divider; +} + +static unsigned long dove_recalc_rate(struct clk_hw *hw, unsigned long parent) +{ + struct dove_clk *dc = to_dove_clk(hw); + unsigned int divider = dove_get_divider(dc); + unsigned long rate = DIV_ROUND_CLOSEST(parent, divider); + + pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n", + __func__, dc->name, divider, parent, rate); + + return rate; +} + +static int dove_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct dove_clk *dc = to_dove_clk(hw); + unsigned long parent_rate = req->best_parent_rate; + int divider; + + divider = dove_calc_divider(dc, req->rate, parent_rate, false); + if (divider < 0) + return divider; + + req->rate = DIV_ROUND_CLOSEST(parent_rate, divider); + + pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n", + __func__, dc->name, divider, parent_rate, req->rate); + + return 0; +} + +static int dove_set_clock(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dove_clk *dc = to_dove_clk(hw); + u32 mask, load, div; + int divider; + + divider = dove_calc_divider(dc, rate, parent_rate, true); + if (divider < 0) + return divider; + + pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n", + __func__, dc->name, divider, parent_rate, rate); + + div = (u32)divider << dc->div_bit_start; + mask = ~(~0 << dc->div_bit_size) << dc->div_bit_start; + load = BIT(dc->div_bit_load); + + spin_lock(dc->lock); + dove_load_divider(dc->base, div, mask, load); + spin_unlock(dc->lock); + + return 0; +} + +static const struct clk_ops dove_divider_ops = { + .set_rate = dove_set_clock, + .determine_rate = dove_determine_rate, + .recalc_rate = dove_recalc_rate, +}; + +static struct clk *clk_register_dove_divider(struct device *dev, + struct dove_clk *dc, const char **parent_names, size_t num_parents, + void __iomem *base) +{ + char name[32]; + struct clk_init_data init = { + .name = name, + .ops = &dove_divider_ops, + .parent_names = parent_names, + .num_parents = num_parents, + }; + + strscpy(name, dc->name, sizeof(name)); + + dc->hw.init = &init; + dc->base = base; + dc->div_bit_size = dc->div_bit_end - dc->div_bit_start + 1; + + return clk_register(dev, &dc->hw); +} + +static DEFINE_SPINLOCK(dove_divider_lock); + +static u32 axi_divider[] = {-1, 2, 1, 3, 4, 6, 5, 7, 8, 10, 9, 0}; + +static struct dove_clk dove_hw_clocks[4] = { + { + .name = "axi", + .lock = &dove_divider_lock, + .div_bit_start = 1, + .div_bit_end = 6, + .div_bit_load = 7, + .divider_table = axi_divider, + }, { + .name = "gpu", + .lock = &dove_divider_lock, + .div_bit_start = 8, + .div_bit_end = 13, + .div_bit_load = 14, + }, { + .name = "vmeta", + .lock = &dove_divider_lock, + .div_bit_start = 15, + .div_bit_end = 20, + .div_bit_load = 21, + }, { + .name = "lcd", + .lock = &dove_divider_lock, + .div_bit_start = 22, + .div_bit_end = 27, + .div_bit_load = 28, + }, +}; + +static const char *core_pll[] = { + "core-pll", +}; + +static int dove_divider_init(struct device *dev, void __iomem *base, + struct clk **clks) +{ + struct clk *clk; + int i; + + /* + * Create the core PLL clock. We treat this as a fixed rate + * clock as we don't know any better, and documentation is sparse. + */ + clk = clk_register_fixed_rate(dev, core_pll[0], NULL, 0, 2000000000UL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + for (i = 0; i < ARRAY_SIZE(dove_hw_clocks); i++) + clks[i] = clk_register_dove_divider(dev, &dove_hw_clocks[i], + core_pll, + ARRAY_SIZE(core_pll), base); + + return 0; +} + +static struct clk *dove_divider_clocks[4]; + +static struct clk_onecell_data dove_divider_data = { + .clks = dove_divider_clocks, + .clk_num = ARRAY_SIZE(dove_divider_clocks), +}; + +void __init dove_divider_clk_init(struct device_node *np) +{ + void __iomem *base; + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + return; + + if (WARN_ON(dove_divider_init(NULL, base, dove_divider_clocks))) { + iounmap(base); + return; + } + + of_clk_add_provider(np, of_clk_src_onecell_get, &dove_divider_data); +} diff --git a/drivers/clk/mvebu/dove-divider.h b/drivers/clk/mvebu/dove-divider.h new file mode 100644 index 000000000..38ea37308 --- /dev/null +++ b/drivers/clk/mvebu/dove-divider.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef DOVE_DIVIDER_H +#define DOVE_DIVIDER_H + +void __init dove_divider_clk_init(struct device_node *np); + +#endif diff --git a/drivers/clk/mvebu/dove.c b/drivers/clk/mvebu/dove.c new file mode 100644 index 000000000..0bd09d33f --- /dev/null +++ b/drivers/clk/mvebu/dove.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Dove SoC clocks + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" +#include "dove-divider.h" + +/* + * Core Clocks + * + * Dove PLL sample-at-reset configuration + * + * SAR0[8:5] : CPU frequency + * 5 = 1000 MHz + * 6 = 933 MHz + * 7 = 933 MHz + * 8 = 800 MHz + * 9 = 800 MHz + * 10 = 800 MHz + * 11 = 1067 MHz + * 12 = 667 MHz + * 13 = 533 MHz + * 14 = 400 MHz + * 15 = 333 MHz + * others reserved. + * + * SAR0[11:9] : CPU to L2 Clock divider ratio + * 0 = (1/1) * CPU + * 2 = (1/2) * CPU + * 4 = (1/3) * CPU + * 6 = (1/4) * CPU + * others reserved. + * + * SAR0[15:12] : CPU to DDR DRAM Clock divider ratio + * 0 = (1/1) * CPU + * 2 = (1/2) * CPU + * 3 = (2/5) * CPU + * 4 = (1/3) * CPU + * 6 = (1/4) * CPU + * 8 = (1/5) * CPU + * 10 = (1/6) * CPU + * 12 = (1/7) * CPU + * 14 = (1/8) * CPU + * 15 = (1/10) * CPU + * others reserved. + * + * SAR0[24:23] : TCLK frequency + * 0 = 166 MHz + * 1 = 125 MHz + * others reserved. + */ + +#define SAR_DOVE_CPU_FREQ 5 +#define SAR_DOVE_CPU_FREQ_MASK 0xf +#define SAR_DOVE_L2_RATIO 9 +#define SAR_DOVE_L2_RATIO_MASK 0x7 +#define SAR_DOVE_DDR_RATIO 12 +#define SAR_DOVE_DDR_RATIO_MASK 0xf +#define SAR_DOVE_TCLK_FREQ 23 +#define SAR_DOVE_TCLK_FREQ_MASK 0x3 + +enum { DOVE_CPU_TO_L2, DOVE_CPU_TO_DDR }; + +static const struct coreclk_ratio dove_coreclk_ratios[] __initconst = { + { .id = DOVE_CPU_TO_L2, .name = "l2clk", }, + { .id = DOVE_CPU_TO_DDR, .name = "ddrclk", } +}; + +static const u32 dove_tclk_freqs[] __initconst = { + 166666667, + 125000000, + 0, 0 +}; + +static u32 __init dove_get_tclk_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_DOVE_TCLK_FREQ) & + SAR_DOVE_TCLK_FREQ_MASK; + return dove_tclk_freqs[opt]; +} + +static const u32 dove_cpu_freqs[] __initconst = { + 0, 0, 0, 0, 0, + 1000000000, + 933333333, 933333333, + 800000000, 800000000, 800000000, + 1066666667, + 666666667, + 533333333, + 400000000, + 333333333 +}; + +static u32 __init dove_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_DOVE_CPU_FREQ) & + SAR_DOVE_CPU_FREQ_MASK; + return dove_cpu_freqs[opt]; +} + +static const int dove_cpu_l2_ratios[8][2] __initconst = { + { 1, 1 }, { 0, 1 }, { 1, 2 }, { 0, 1 }, + { 1, 3 }, { 0, 1 }, { 1, 4 }, { 0, 1 } +}; + +static const int dove_cpu_ddr_ratios[16][2] __initconst = { + { 1, 1 }, { 0, 1 }, { 1, 2 }, { 2, 5 }, + { 1, 3 }, { 0, 1 }, { 1, 4 }, { 0, 1 }, + { 1, 5 }, { 0, 1 }, { 1, 6 }, { 0, 1 }, + { 1, 7 }, { 0, 1 }, { 1, 8 }, { 1, 10 } +}; + +static void __init dove_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + switch (id) { + case DOVE_CPU_TO_L2: + { + u32 opt = (readl(sar) >> SAR_DOVE_L2_RATIO) & + SAR_DOVE_L2_RATIO_MASK; + *mult = dove_cpu_l2_ratios[opt][0]; + *div = dove_cpu_l2_ratios[opt][1]; + break; + } + case DOVE_CPU_TO_DDR: + { + u32 opt = (readl(sar) >> SAR_DOVE_DDR_RATIO) & + SAR_DOVE_DDR_RATIO_MASK; + *mult = dove_cpu_ddr_ratios[opt][0]; + *div = dove_cpu_ddr_ratios[opt][1]; + break; + } + } +} + +static const struct coreclk_soc_desc dove_coreclks = { + .get_tclk_freq = dove_get_tclk_freq, + .get_cpu_freq = dove_get_cpu_freq, + .get_clk_ratio = dove_get_clk_ratio, + .ratios = dove_coreclk_ratios, + .num_ratios = ARRAY_SIZE(dove_coreclk_ratios), +}; + +/* + * Clock Gating Control + */ + +static const struct clk_gating_soc_desc dove_gating_desc[] __initconst = { + { "usb0", NULL, 0, 0 }, + { "usb1", NULL, 1, 0 }, + { "ge", "gephy", 2, 0 }, + { "sata", NULL, 3, 0 }, + { "pex0", NULL, 4, 0 }, + { "pex1", NULL, 5, 0 }, + { "sdio0", NULL, 8, 0 }, + { "sdio1", NULL, 9, 0 }, + { "nand", NULL, 10, 0 }, + { "camera", NULL, 11, 0 }, + { "i2s0", NULL, 12, 0 }, + { "i2s1", NULL, 13, 0 }, + { "crypto", NULL, 15, 0 }, + { "ac97", NULL, 21, 0 }, + { "pdma", NULL, 22, 0 }, + { "xor0", NULL, 23, 0 }, + { "xor1", NULL, 24, 0 }, + { "gephy", NULL, 30, 0 }, + { } +}; + +static void __init dove_clk_init(struct device_node *np) +{ + struct device_node *cgnp = + of_find_compatible_node(NULL, NULL, "marvell,dove-gating-clock"); + struct device_node *ddnp = + of_find_compatible_node(NULL, NULL, "marvell,dove-divider-clock"); + + mvebu_coreclk_setup(np, &dove_coreclks); + + if (ddnp) { + dove_divider_clk_init(ddnp); + of_node_put(ddnp); + } + + if (cgnp) { + mvebu_clk_gating_setup(cgnp, dove_gating_desc); + of_node_put(cgnp); + } +} +CLK_OF_DECLARE(dove_clk, "marvell,dove-core-clock", dove_clk_init); diff --git a/drivers/clk/mvebu/kirkwood.c b/drivers/clk/mvebu/kirkwood.c new file mode 100644 index 000000000..f4f62b241 --- /dev/null +++ b/drivers/clk/mvebu/kirkwood.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Kirkwood SoC clocks + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include "common.h" + +/* + * Core Clocks + * + * Kirkwood PLL sample-at-reset configuration + * (6180 has different SAR layout than other Kirkwood SoCs) + * + * SAR0[4:3,22,1] : CPU frequency (6281,6292,6282) + * 4 = 600 MHz + * 6 = 800 MHz + * 7 = 1000 MHz + * 9 = 1200 MHz + * 12 = 1500 MHz + * 13 = 1600 MHz + * 14 = 1800 MHz + * 15 = 2000 MHz + * others reserved. + * + * SAR0[19,10:9] : CPU to L2 Clock divider ratio (6281,6292,6282) + * 1 = (1/2) * CPU + * 3 = (1/3) * CPU + * 5 = (1/4) * CPU + * others reserved. + * + * SAR0[8:5] : CPU to DDR DRAM Clock divider ratio (6281,6292,6282) + * 2 = (1/2) * CPU + * 4 = (1/3) * CPU + * 6 = (1/4) * CPU + * 7 = (2/9) * CPU + * 8 = (1/5) * CPU + * 9 = (1/6) * CPU + * others reserved. + * + * SAR0[4:2] : Kirkwood 6180 cpu/l2/ddr clock configuration (6180 only) + * 5 = [CPU = 600 MHz, L2 = (1/2) * CPU, DDR = 200 MHz = (1/3) * CPU] + * 6 = [CPU = 800 MHz, L2 = (1/2) * CPU, DDR = 200 MHz = (1/4) * CPU] + * 7 = [CPU = 1000 MHz, L2 = (1/2) * CPU, DDR = 200 MHz = (1/5) * CPU] + * others reserved. + * + * SAR0[21] : TCLK frequency + * 0 = 200 MHz + * 1 = 166 MHz + * others reserved. + */ + +#define SAR_KIRKWOOD_CPU_FREQ(x) \ + (((x & (1 << 1)) >> 1) | \ + ((x & (1 << 22)) >> 21) | \ + ((x & (3 << 3)) >> 1)) +#define SAR_KIRKWOOD_L2_RATIO(x) \ + (((x & (3 << 9)) >> 9) | \ + (((x & (1 << 19)) >> 17))) +#define SAR_KIRKWOOD_DDR_RATIO 5 +#define SAR_KIRKWOOD_DDR_RATIO_MASK 0xf +#define SAR_MV88F6180_CLK 2 +#define SAR_MV88F6180_CLK_MASK 0x7 +#define SAR_KIRKWOOD_TCLK_FREQ 21 +#define SAR_KIRKWOOD_TCLK_FREQ_MASK 0x1 + +enum { KIRKWOOD_CPU_TO_L2, KIRKWOOD_CPU_TO_DDR }; + +static const struct coreclk_ratio kirkwood_coreclk_ratios[] __initconst = { + { .id = KIRKWOOD_CPU_TO_L2, .name = "l2clk", }, + { .id = KIRKWOOD_CPU_TO_DDR, .name = "ddrclk", } +}; + +static u32 __init kirkwood_get_tclk_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_KIRKWOOD_TCLK_FREQ) & + SAR_KIRKWOOD_TCLK_FREQ_MASK; + return (opt) ? 166666667 : 200000000; +} + +static const u32 kirkwood_cpu_freqs[] __initconst = { + 0, 0, 0, 0, + 600000000, + 0, + 800000000, + 1000000000, + 0, + 1200000000, + 0, 0, + 1500000000, + 1600000000, + 1800000000, + 2000000000 +}; + +static u32 __init kirkwood_get_cpu_freq(void __iomem *sar) +{ + u32 opt = SAR_KIRKWOOD_CPU_FREQ(readl(sar)); + return kirkwood_cpu_freqs[opt]; +} + +static const int kirkwood_cpu_l2_ratios[8][2] __initconst = { + { 0, 1 }, { 1, 2 }, { 0, 1 }, { 1, 3 }, + { 0, 1 }, { 1, 4 }, { 0, 1 }, { 0, 1 } +}; + +static const int kirkwood_cpu_ddr_ratios[16][2] __initconst = { + { 0, 1 }, { 0, 1 }, { 1, 2 }, { 0, 1 }, + { 1, 3 }, { 0, 1 }, { 1, 4 }, { 2, 9 }, + { 1, 5 }, { 1, 6 }, { 0, 1 }, { 0, 1 }, + { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 1 } +}; + +static void __init kirkwood_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + switch (id) { + case KIRKWOOD_CPU_TO_L2: + { + u32 opt = SAR_KIRKWOOD_L2_RATIO(readl(sar)); + *mult = kirkwood_cpu_l2_ratios[opt][0]; + *div = kirkwood_cpu_l2_ratios[opt][1]; + break; + } + case KIRKWOOD_CPU_TO_DDR: + { + u32 opt = (readl(sar) >> SAR_KIRKWOOD_DDR_RATIO) & + SAR_KIRKWOOD_DDR_RATIO_MASK; + *mult = kirkwood_cpu_ddr_ratios[opt][0]; + *div = kirkwood_cpu_ddr_ratios[opt][1]; + break; + } + } +} + +static const u32 mv88f6180_cpu_freqs[] __initconst = { + 0, 0, 0, 0, 0, + 600000000, + 800000000, + 1000000000 +}; + +static u32 __init mv88f6180_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F6180_CLK) & SAR_MV88F6180_CLK_MASK; + return mv88f6180_cpu_freqs[opt]; +} + +static const int mv88f6180_cpu_ddr_ratios[8][2] __initconst = { + { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 1 }, + { 0, 1 }, { 1, 3 }, { 1, 4 }, { 1, 5 } +}; + +static void __init mv88f6180_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + switch (id) { + case KIRKWOOD_CPU_TO_L2: + { + /* mv88f6180 has a fixed 1:2 CPU-to-L2 ratio */ + *mult = 1; + *div = 2; + break; + } + case KIRKWOOD_CPU_TO_DDR: + { + u32 opt = (readl(sar) >> SAR_MV88F6180_CLK) & + SAR_MV88F6180_CLK_MASK; + *mult = mv88f6180_cpu_ddr_ratios[opt][0]; + *div = mv88f6180_cpu_ddr_ratios[opt][1]; + break; + } + } +} + +static u32 __init mv98dx1135_get_tclk_freq(void __iomem *sar) +{ + return 166666667; +} + +static const struct coreclk_soc_desc kirkwood_coreclks = { + .get_tclk_freq = kirkwood_get_tclk_freq, + .get_cpu_freq = kirkwood_get_cpu_freq, + .get_clk_ratio = kirkwood_get_clk_ratio, + .ratios = kirkwood_coreclk_ratios, + .num_ratios = ARRAY_SIZE(kirkwood_coreclk_ratios), +}; + +static const struct coreclk_soc_desc mv88f6180_coreclks = { + .get_tclk_freq = kirkwood_get_tclk_freq, + .get_cpu_freq = mv88f6180_get_cpu_freq, + .get_clk_ratio = mv88f6180_get_clk_ratio, + .ratios = kirkwood_coreclk_ratios, + .num_ratios = ARRAY_SIZE(kirkwood_coreclk_ratios), +}; + +static const struct coreclk_soc_desc mv98dx1135_coreclks = { + .get_tclk_freq = mv98dx1135_get_tclk_freq, + .get_cpu_freq = kirkwood_get_cpu_freq, + .get_clk_ratio = kirkwood_get_clk_ratio, + .ratios = kirkwood_coreclk_ratios, + .num_ratios = ARRAY_SIZE(kirkwood_coreclk_ratios), +}; + +/* + * Clock Gating Control + */ + +static const struct clk_gating_soc_desc kirkwood_gating_desc[] __initconst = { + { "ge0", NULL, 0, 0 }, + { "pex0", NULL, 2, 0 }, + { "usb0", NULL, 3, 0 }, + { "sdio", NULL, 4, 0 }, + { "tsu", NULL, 5, 0 }, + { "runit", NULL, 7, 0 }, + { "xor0", NULL, 8, 0 }, + { "audio", NULL, 9, 0 }, + { "sata0", NULL, 14, 0 }, + { "sata1", NULL, 15, 0 }, + { "xor1", NULL, 16, 0 }, + { "crypto", NULL, 17, 0 }, + { "pex1", NULL, 18, 0 }, + { "ge1", NULL, 19, 0 }, + { "tdm", NULL, 20, 0 }, + { } +}; + + +/* + * Clock Muxing Control + */ + +struct clk_muxing_soc_desc { + const char *name; + const char **parents; + int num_parents; + int shift; + int width; + unsigned long flags; +}; + +struct clk_muxing_ctrl { + spinlock_t *lock; + int num_muxes; + struct clk *muxes[] __counted_by(num_muxes); +}; + +static const char *powersave_parents[] = { + "cpuclk", + "ddrclk", +}; + +static const struct clk_muxing_soc_desc kirkwood_mux_desc[] __initconst = { + { "powersave", powersave_parents, ARRAY_SIZE(powersave_parents), + 11, 1, 0 }, + { } +}; + +static struct clk *clk_muxing_get_src( + struct of_phandle_args *clkspec, void *data) +{ + struct clk_muxing_ctrl *ctrl = (struct clk_muxing_ctrl *)data; + int n; + + if (clkspec->args_count < 1) + return ERR_PTR(-EINVAL); + + for (n = 0; n < ctrl->num_muxes; n++) { + struct clk_mux *mux = + to_clk_mux(__clk_get_hw(ctrl->muxes[n])); + if (clkspec->args[0] == mux->shift) + return ctrl->muxes[n]; + } + return ERR_PTR(-ENODEV); +} + +static void __init kirkwood_clk_muxing_setup(struct device_node *np, + const struct clk_muxing_soc_desc *desc) +{ + struct clk_muxing_ctrl *ctrl; + void __iomem *base; + int n; + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + return; + + /* Count, allocate, and register clock muxes */ + for (n = 0; desc[n].name;) + n++; + + ctrl = kzalloc_flex(*ctrl, muxes, n); + if (WARN_ON(!ctrl)) + goto ctrl_out; + + ctrl->num_muxes = n; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; + + for (n = 0; n < ctrl->num_muxes; n++) { + ctrl->muxes[n] = clk_register_mux(NULL, desc[n].name, + desc[n].parents, desc[n].num_parents, + desc[n].flags, base, desc[n].shift, + desc[n].width, desc[n].flags, ctrl->lock); + WARN_ON(IS_ERR(ctrl->muxes[n])); + } + + of_clk_add_provider(np, clk_muxing_get_src, ctrl); + + return; +ctrl_out: + iounmap(base); +} + +static void __init kirkwood_clk_init(struct device_node *np) +{ + struct device_node *cgnp = + of_find_compatible_node(NULL, NULL, "marvell,kirkwood-gating-clock"); + + + if (of_device_is_compatible(np, "marvell,mv88f6180-core-clock")) + mvebu_coreclk_setup(np, &mv88f6180_coreclks); + else if (of_device_is_compatible(np, "marvell,mv98dx1135-core-clock")) + mvebu_coreclk_setup(np, &mv98dx1135_coreclks); + else + mvebu_coreclk_setup(np, &kirkwood_coreclks); + + if (cgnp) { + mvebu_clk_gating_setup(cgnp, kirkwood_gating_desc); + kirkwood_clk_muxing_setup(cgnp, kirkwood_mux_desc); + + of_node_put(cgnp); + } +} +CLK_OF_DECLARE(kirkwood_clk, "marvell,kirkwood-core-clock", + kirkwood_clk_init); +CLK_OF_DECLARE(mv88f6180_clk, "marvell,mv88f6180-core-clock", + kirkwood_clk_init); +CLK_OF_DECLARE(98dx1135_clk, "marvell,mv98dx1135-core-clock", + kirkwood_clk_init); diff --git a/drivers/clk/mvebu/mv98dx3236.c b/drivers/clk/mvebu/mv98dx3236.c new file mode 100644 index 000000000..1c8ab4f83 --- /dev/null +++ b/drivers/clk/mvebu/mv98dx3236.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell MV98DX3236 SoC clocks + * + * Copyright (C) 2012 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + + +/* + * For 98DX4251 Sample At Reset the CPU, DDR and Main PLL clocks are all + * defined at the same time + * + * SAR1[20:18] : CPU frequency DDR frequency MPLL frequency + * 0 = 400 MHz 400 MHz 800 MHz + * 2 = 667 MHz 667 MHz 2000 MHz + * 3 = 800 MHz 800 MHz 1600 MHz + * others reserved. + * + * For 98DX3236 Sample At Reset the CPU, DDR and Main PLL clocks are all + * defined at the same time + * + * SAR1[20:18] : CPU frequency DDR frequency MPLL frequency + * 1 = 667 MHz 667 MHz 2000 MHz + * 2 = 400 MHz 400 MHz 400 MHz + * 3 = 800 MHz 800 MHz 800 MHz + * 5 = 800 MHz 400 MHz 800 MHz + * others reserved. + */ + +#define SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT 18 +#define SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT_MASK 0x7 + +static u32 __init mv98dx3236_get_tclk_freq(void __iomem *sar) +{ + /* Tclk = 200MHz, no SaR dependency */ + return 200000000; +} + +static const u32 mv98dx3236_cpu_frequencies[] __initconst = { + 0, + 667000000, + 400000000, + 800000000, + 0, + 800000000, + 0, 0, +}; + +static const u32 mv98dx4251_cpu_frequencies[] __initconst = { + 400000000, + 0, + 667000000, + 800000000, + 0, 0, 0, 0, +}; + +static u32 __init mv98dx3236_get_cpu_freq(void __iomem *sar) +{ + u32 cpu_freq = 0; + u8 cpu_freq_select = 0; + + cpu_freq_select = ((readl(sar) >> SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT) & + SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT_MASK); + + if (of_machine_is_compatible("marvell,armadaxp-98dx4251")) + cpu_freq = mv98dx4251_cpu_frequencies[cpu_freq_select]; + else if (of_machine_is_compatible("marvell,armadaxp-98dx3236")) + cpu_freq = mv98dx3236_cpu_frequencies[cpu_freq_select]; + + if (!cpu_freq) + pr_err("CPU freq select unsupported %d\n", cpu_freq_select); + + return cpu_freq; +} + +enum { + MV98DX3236_CPU_TO_DDR, + MV98DX3236_CPU_TO_MPLL +}; + +static const struct coreclk_ratio mv98dx3236_core_ratios[] __initconst = { + { .id = MV98DX3236_CPU_TO_DDR, .name = "ddrclk" }, + { .id = MV98DX3236_CPU_TO_MPLL, .name = "mpll" }, +}; + +static const int __initconst mv98dx3236_cpu_mpll_ratios[8][2] = { + {0, 1}, {3, 1}, {1, 1}, {1, 1}, + {0, 1}, {1, 1}, {0, 1}, {0, 1}, +}; + +static const int __initconst mv98dx3236_cpu_ddr_ratios[8][2] = { + {0, 1}, {1, 1}, {1, 1}, {1, 1}, + {0, 1}, {1, 2}, {0, 1}, {0, 1}, +}; + +static const int __initconst mv98dx4251_cpu_mpll_ratios[8][2] = { + {2, 1}, {0, 1}, {3, 1}, {2, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static const int __initconst mv98dx4251_cpu_ddr_ratios[8][2] = { + {1, 1}, {0, 1}, {1, 1}, {1, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, +}; + +static void __init mv98dx3236_get_clk_ratio( + void __iomem *sar, int id, int *mult, int *div) +{ + u32 opt = ((readl(sar) >> SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT) & + SAR1_MV98DX3236_CPU_DDR_MPLL_FREQ_OPT_MASK); + + switch (id) { + case MV98DX3236_CPU_TO_DDR: + if (of_machine_is_compatible("marvell,armadaxp-98dx4251")) { + *mult = mv98dx4251_cpu_ddr_ratios[opt][0]; + *div = mv98dx4251_cpu_ddr_ratios[opt][1]; + } else if (of_machine_is_compatible("marvell,armadaxp-98dx3236")) { + *mult = mv98dx3236_cpu_ddr_ratios[opt][0]; + *div = mv98dx3236_cpu_ddr_ratios[opt][1]; + } + break; + case MV98DX3236_CPU_TO_MPLL: + if (of_machine_is_compatible("marvell,armadaxp-98dx4251")) { + *mult = mv98dx4251_cpu_mpll_ratios[opt][0]; + *div = mv98dx4251_cpu_mpll_ratios[opt][1]; + } else if (of_machine_is_compatible("marvell,armadaxp-98dx3236")) { + *mult = mv98dx3236_cpu_mpll_ratios[opt][0]; + *div = mv98dx3236_cpu_mpll_ratios[opt][1]; + } + break; + } +} + +static const struct coreclk_soc_desc mv98dx3236_core_clocks = { + .get_tclk_freq = mv98dx3236_get_tclk_freq, + .get_cpu_freq = mv98dx3236_get_cpu_freq, + .get_clk_ratio = mv98dx3236_get_clk_ratio, + .ratios = mv98dx3236_core_ratios, + .num_ratios = ARRAY_SIZE(mv98dx3236_core_ratios), +}; + + +/* + * Clock Gating Control + */ + +static const struct clk_gating_soc_desc mv98dx3236_gating_desc[] __initconst = { + { "ge1", NULL, 3, 0 }, + { "ge0", NULL, 4, 0 }, + { "pex00", NULL, 5, 0 }, + { "sdio", NULL, 17, 0 }, + { "usb0", NULL, 18, 0 }, + { "xor0", NULL, 22, 0 }, + { } +}; + +static void __init mv98dx3236_clk_init(struct device_node *np) +{ + struct device_node *cgnp = + of_find_compatible_node(NULL, NULL, "marvell,mv98dx3236-gating-clock"); + + mvebu_coreclk_setup(np, &mv98dx3236_core_clocks); + + if (cgnp) { + mvebu_clk_gating_setup(cgnp, mv98dx3236_gating_desc); + of_node_put(cgnp); + } +} +CLK_OF_DECLARE(mv98dx3236_clk, "marvell,mv98dx3236-core-clock", mv98dx3236_clk_init); diff --git a/drivers/clk/mvebu/orion.c b/drivers/clk/mvebu/orion.c new file mode 100644 index 000000000..f681a65be --- /dev/null +++ b/drivers/clk/mvebu/orion.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Orion SoC clocks + * + * Copyright (C) 2014 Thomas Petazzoni + * + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + */ + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include "common.h" + +static const struct coreclk_ratio orion_coreclk_ratios[] __initconst = { + { .id = 0, .name = "ddrclk", } +}; + +/* + * Orion 5181 + */ + +#define SAR_MV88F5181_TCLK_FREQ 8 +#define SAR_MV88F5181_TCLK_FREQ_MASK 0x3 + +static u32 __init mv88f5181_get_tclk_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F5181_TCLK_FREQ) & + SAR_MV88F5181_TCLK_FREQ_MASK; + if (opt == 0) + return 133333333; + else if (opt == 1) + return 150000000; + else if (opt == 2) + return 166666667; + else + return 0; +} + +#define SAR_MV88F5181_CPU_FREQ 4 +#define SAR_MV88F5181_CPU_FREQ_MASK 0xf + +static u32 __init mv88f5181_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F5181_CPU_FREQ) & + SAR_MV88F5181_CPU_FREQ_MASK; + if (opt == 0) + return 333333333; + else if (opt == 1 || opt == 2) + return 400000000; + else if (opt == 3) + return 500000000; + else + return 0; +} + +static void __init mv88f5181_get_clk_ratio(void __iomem *sar, int id, + int *mult, int *div) +{ + u32 opt = (readl(sar) >> SAR_MV88F5181_CPU_FREQ) & + SAR_MV88F5181_CPU_FREQ_MASK; + if (opt == 0 || opt == 1) { + *mult = 1; + *div = 2; + } else if (opt == 2 || opt == 3) { + *mult = 1; + *div = 3; + } else { + *mult = 0; + *div = 1; + } +} + +static const struct coreclk_soc_desc mv88f5181_coreclks = { + .get_tclk_freq = mv88f5181_get_tclk_freq, + .get_cpu_freq = mv88f5181_get_cpu_freq, + .get_clk_ratio = mv88f5181_get_clk_ratio, + .ratios = orion_coreclk_ratios, + .num_ratios = ARRAY_SIZE(orion_coreclk_ratios), +}; + +static void __init mv88f5181_clk_init(struct device_node *np) +{ + return mvebu_coreclk_setup(np, &mv88f5181_coreclks); +} + +CLK_OF_DECLARE(mv88f5181_clk, "marvell,mv88f5181-core-clock", mv88f5181_clk_init); + +/* + * Orion 5182 + */ + +#define SAR_MV88F5182_TCLK_FREQ 8 +#define SAR_MV88F5182_TCLK_FREQ_MASK 0x3 + +static u32 __init mv88f5182_get_tclk_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F5182_TCLK_FREQ) & + SAR_MV88F5182_TCLK_FREQ_MASK; + if (opt == 1) + return 150000000; + else if (opt == 2) + return 166666667; + else + return 0; +} + +#define SAR_MV88F5182_CPU_FREQ 4 +#define SAR_MV88F5182_CPU_FREQ_MASK 0xf + +static u32 __init mv88f5182_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F5182_CPU_FREQ) & + SAR_MV88F5182_CPU_FREQ_MASK; + if (opt == 0) + return 333333333; + else if (opt == 1 || opt == 2) + return 400000000; + else if (opt == 3) + return 500000000; + else + return 0; +} + +static void __init mv88f5182_get_clk_ratio(void __iomem *sar, int id, + int *mult, int *div) +{ + u32 opt = (readl(sar) >> SAR_MV88F5182_CPU_FREQ) & + SAR_MV88F5182_CPU_FREQ_MASK; + if (opt == 0 || opt == 1) { + *mult = 1; + *div = 2; + } else if (opt == 2 || opt == 3) { + *mult = 1; + *div = 3; + } else { + *mult = 0; + *div = 1; + } +} + +static const struct coreclk_soc_desc mv88f5182_coreclks = { + .get_tclk_freq = mv88f5182_get_tclk_freq, + .get_cpu_freq = mv88f5182_get_cpu_freq, + .get_clk_ratio = mv88f5182_get_clk_ratio, + .ratios = orion_coreclk_ratios, + .num_ratios = ARRAY_SIZE(orion_coreclk_ratios), +}; + +static void __init mv88f5182_clk_init(struct device_node *np) +{ + return mvebu_coreclk_setup(np, &mv88f5182_coreclks); +} + +CLK_OF_DECLARE(mv88f5182_clk, "marvell,mv88f5182-core-clock", mv88f5182_clk_init); + +/* + * Orion 5281 + */ + +static u32 __init mv88f5281_get_tclk_freq(void __iomem *sar) +{ + /* On 5281, tclk is always 166 Mhz */ + return 166666667; +} + +#define SAR_MV88F5281_CPU_FREQ 4 +#define SAR_MV88F5281_CPU_FREQ_MASK 0xf + +static u32 __init mv88f5281_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F5281_CPU_FREQ) & + SAR_MV88F5281_CPU_FREQ_MASK; + if (opt == 1 || opt == 2) + return 400000000; + else if (opt == 3) + return 500000000; + else + return 0; +} + +static void __init mv88f5281_get_clk_ratio(void __iomem *sar, int id, + int *mult, int *div) +{ + u32 opt = (readl(sar) >> SAR_MV88F5281_CPU_FREQ) & + SAR_MV88F5281_CPU_FREQ_MASK; + if (opt == 1) { + *mult = 1; + *div = 2; + } else if (opt == 2 || opt == 3) { + *mult = 1; + *div = 3; + } else { + *mult = 0; + *div = 1; + } +} + +static const struct coreclk_soc_desc mv88f5281_coreclks = { + .get_tclk_freq = mv88f5281_get_tclk_freq, + .get_cpu_freq = mv88f5281_get_cpu_freq, + .get_clk_ratio = mv88f5281_get_clk_ratio, + .ratios = orion_coreclk_ratios, + .num_ratios = ARRAY_SIZE(orion_coreclk_ratios), +}; + +static void __init mv88f5281_clk_init(struct device_node *np) +{ + return mvebu_coreclk_setup(np, &mv88f5281_coreclks); +} + +CLK_OF_DECLARE(mv88f5281_clk, "marvell,mv88f5281-core-clock", mv88f5281_clk_init); + +/* + * Orion 6183 + */ + +#define SAR_MV88F6183_TCLK_FREQ 9 +#define SAR_MV88F6183_TCLK_FREQ_MASK 0x1 + +static u32 __init mv88f6183_get_tclk_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F6183_TCLK_FREQ) & + SAR_MV88F6183_TCLK_FREQ_MASK; + if (opt == 0) + return 133333333; + else if (opt == 1) + return 166666667; + else + return 0; +} + +#define SAR_MV88F6183_CPU_FREQ 1 +#define SAR_MV88F6183_CPU_FREQ_MASK 0x3f + +static u32 __init mv88f6183_get_cpu_freq(void __iomem *sar) +{ + u32 opt = (readl(sar) >> SAR_MV88F6183_CPU_FREQ) & + SAR_MV88F6183_CPU_FREQ_MASK; + if (opt == 9) + return 333333333; + else if (opt == 17) + return 400000000; + else + return 0; +} + +static void __init mv88f6183_get_clk_ratio(void __iomem *sar, int id, + int *mult, int *div) +{ + u32 opt = (readl(sar) >> SAR_MV88F6183_CPU_FREQ) & + SAR_MV88F6183_CPU_FREQ_MASK; + if (opt == 9 || opt == 17) { + *mult = 1; + *div = 2; + } else { + *mult = 0; + *div = 1; + } +} + +static const struct coreclk_soc_desc mv88f6183_coreclks = { + .get_tclk_freq = mv88f6183_get_tclk_freq, + .get_cpu_freq = mv88f6183_get_cpu_freq, + .get_clk_ratio = mv88f6183_get_clk_ratio, + .ratios = orion_coreclk_ratios, + .num_ratios = ARRAY_SIZE(orion_coreclk_ratios), +}; + + +static void __init mv88f6183_clk_init(struct device_node *np) +{ + return mvebu_coreclk_setup(np, &mv88f6183_coreclks); +} + +CLK_OF_DECLARE(mv88f6183_clk, "marvell,mv88f6183-core-clock", mv88f6183_clk_init); |
