diff options
Diffstat (limited to 'drivers/clk/mvebu')
25 files changed, 5390 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..25de4b6da --- /dev/null +++ b/drivers/clk/mvebu/ap-cpu-clk.c @@ -0,0 +1,364 @@ +// 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/of_platform.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 long ap_cpu_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + int divider = *parent_rate / rate; + + divider = min(divider, APN806_MAX_DIVIDER); + + return *parent_rate / divider; +} + +static const struct clk_ops ap_cpu_clk_ops = { + .recalc_rate = ap_cpu_clk_recalc_rate, + .round_rate = ap_cpu_clk_round_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) { + int cpu, err; + + err = of_property_read_u32(dn, "reg", &cpu); + if (WARN_ON(err)) { + of_node_put(dn); + return err; + } + + /* 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; + int cpu, err; + + err = of_property_read_u32(dn, "reg", &cpu); + if (WARN_ON(err)) { + of_node_put(dn); + return err; + } + + 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..32ac6b6b7 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-periph.c @@ -0,0 +1,807 @@ +// 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/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.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 PERIPH_GATE(_name, _bit) \ +struct clk_gate gate_##_name = { \ + .reg = (void *)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 = (void *)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 = (void *)_reg1, \ + .reg2 = (void *)_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 = (void *)_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 = (void *)TBG_SEL, \ + .mask_mux = 3, \ + .shift_mux = _shift1, \ + .reg_div = (void *)_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 long clk_pm_cpu_round_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, 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. + */ + return *parent_rate / div; + } + + /* 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 && 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, + .round_rate = clk_pm_cpu_round_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; + struct resource *res; + + 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; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + driver_data->reg = devm_ioremap_resource(dev, res); + 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 int 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]); + + return 0; +} + +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..585a02e0b --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-tbg.c @@ -0,0 +1,157 @@ +// 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 resource *res; + struct clk *parent; + void __iomem *reg; + int i, ret; + + 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); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg = devm_ioremap_resource(dev, res); + 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); + } + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, hw_tbg_data); + + return ret; +} + +static int 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]); + + return 0; +} + +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..41271351c --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-xtal.c @@ -0,0 +1,89 @@ +// 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 int armada_3700_xtal_clock_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + +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..45665655a --- /dev/null +++ b/drivers/clk/mvebu/armada-xp.c @@ -0,0 +1,208 @@ +// 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 bitfiled split in two + * register 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..6a930f697 --- /dev/null +++ b/drivers/clk/mvebu/armada_ap_cp_helper.c @@ -0,0 +1,30 @@ +// 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) +{ + const __be32 *reg; + u64 addr; + + /* Do not create a name if there is no clock */ + if (!name) + return NULL; + + reg = of_get_property(np, "reg", NULL); + addr = of_translate_address(np, reg); + return devm_kasprintf(dev, GFP_KERNEL, "%llx-%s", + (unsigned long long)addr, 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..818b17539 --- /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 long clk_corediv_round_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long *parent_rate) +{ + /* Valid ratio are 1:4, 1:5, 1:6 and 1:8 */ + u32 div; + + div = *parent_rate / rate; + if (div < 4) + div = 4; + else if (div > 6) + div = 8; + + return *parent_rate / div; +} + +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, + .round_rate = clk_corediv_round_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, + .round_rate = clk_corediv_round_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, + .round_rate = clk_corediv_round_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, + .round_rate = clk_corediv_round_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 = kcalloc(clk_data.clk_num, sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!clks)) + goto err_unmap; + /* corediv holds the clock specific array */ + corediv = kcalloc(clk_data.clk_num, sizeof(struct clk_corediv), + GFP_KERNEL); + 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..c2af3395c --- /dev/null +++ b/drivers/clk/mvebu/clk-cpu.c @@ -0,0 +1,253 @@ +// 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 long clk_cpu_round_rate(struct clk_hw *hwclk, unsigned long rate, + unsigned long *parent_rate) +{ + /* Valid ratio are 1:1, 1:2 and 1:3 */ + u32 div; + + div = *parent_rate / rate; + if (div == 0) + div = 1; + else if (div > 3) + div = 3; + + return *parent_rate / div; +} + +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_is_enabled(hwclk->clk)) + 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, + .round_rate = clk_cpu_round_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 = 0; + struct device_node *dn; + + 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__); + + for_each_of_cpu_node(dn) + ncpus++; + + cpuclk = kcalloc(ncpus, sizeof(*cpuclk), GFP_KERNEL); + if (WARN_ON(!cpuclk)) + goto cpuclk_out; + + clks = kcalloc(ncpus, sizeof(*clks), GFP_KERNEL); + if (WARN_ON(!clks)) + goto clks_out; + + for_each_of_cpu_node(dn) { + struct clk_init_data init; + struct clk *clk; + char *clk_name = kzalloc(5, GFP_KERNEL); + int cpu, err; + + if (WARN_ON(!clk_name)) + goto bail_out; + + err = of_property_read_u32(dn, "reg", &cpu); + if (WARN_ON(err)) + 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..785dbede4 --- /dev/null +++ b/drivers/clk/mvebu/common.c @@ -0,0 +1,294 @@ +// 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 = kcalloc(clk_data.clk_num, sizeof(*clk_data.clks), + GFP_KERNEL); + 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; + struct clk **gates; + int num_gates; + void __iomem *base; + u32 saved_reg; +}; + +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) +{ + ctrl->saved_reg = readl(ctrl->base); + return 0; +} + +static void mvebu_clk_gating_resume(void) +{ + writel(ctrl->saved_reg, ctrl->base); +} + +static struct syscore_ops clk_gate_syscore_ops = { + .suspend = mvebu_clk_gating_suspend, + .resume = mvebu_clk_gating_resume, +}; + +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); + } + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (WARN_ON(!ctrl)) + goto ctrl_out; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; + + ctrl->base = base; + + /* Count, allocate, and register clock gates */ + for (n = 0; desc[n].name;) + n++; + + ctrl->num_gates = n; + ctrl->gates = kcalloc(ctrl->num_gates, sizeof(*ctrl->gates), + GFP_KERNEL); + if (WARN_ON(!ctrl->gates)) + goto gates_out; + + 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_ops(&clk_gate_syscore_ops); + + return; +gates_out: + kfree(ctrl); +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..84c890054 --- /dev/null +++ b/drivers/clk/mvebu/cp110-system-controller.c @@ -0,0 +1,434 @@ +// 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" +}; + +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(sizeof(*gate), GFP_KERNEL); + 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; + + 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_clks = cp110_clk_data->hws; + cp110_clk_data->num = CP110_CLK_NUM; + + /* 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..7e35c891e --- /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 long dove_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent) +{ + struct dove_clk *dc = to_dove_clk(hw); + unsigned long parent_rate = *parent; + int divider; + + divider = dove_calc_divider(dc, rate, parent_rate, false); + if (divider < 0) + return divider; + + rate = DIV_ROUND_CLOSEST(parent_rate, divider); + + pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n", + __func__, dc->name, divider, parent_rate, rate); + + return rate; +} + +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, + .round_rate = dove_round_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, + }; + + strlcpy(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..8bc893df4 --- /dev/null +++ b/drivers/clk/mvebu/kirkwood.c @@ -0,0 +1,359 @@ +// 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; + struct clk **muxes; + int 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; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (WARN_ON(!ctrl)) + goto ctrl_out; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; + + /* Count, allocate, and register clock muxes */ + for (n = 0; desc[n].name;) + n++; + + ctrl->num_muxes = n; + ctrl->muxes = kcalloc(ctrl->num_muxes, sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!ctrl->muxes)) + goto muxes_out; + + 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; +muxes_out: + kfree(ctrl); +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); |