From 4902c2025b8ade9c230d4bca25ec5f691e91cb1f Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Wed, 26 Jul 2017 16:47:27 +0300 Subject: clk: ti: add support for register read-modify-write low-level operation Useful for changing few bits on a register, this makes sure for example that the operation is done atomically in case of syscon. Signed-off-by: Tero Kristo --- include/linux/clk/ti.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/linux') diff --git a/include/linux/clk/ti.h b/include/linux/clk/ti.h index d18da839b810..9e8611470187 100644 --- a/include/linux/clk/ti.h +++ b/include/linux/clk/ti.h @@ -211,6 +211,7 @@ enum { * struct ti_clk_ll_ops - low-level ops for clocks * @clk_readl: pointer to register read function * @clk_writel: pointer to register write function + * @clk_rmw: pointer to register read-modify-write function * @clkdm_clk_enable: pointer to clockdomain enable function * @clkdm_clk_disable: pointer to clockdomain disable function * @clkdm_lookup: pointer to clockdomain lookup function @@ -226,6 +227,7 @@ enum { struct ti_clk_ll_ops { u32 (*clk_readl)(const struct clk_omap_reg *reg); void (*clk_writel)(u32 val, const struct clk_omap_reg *reg); + void (*clk_rmw)(u32 val, u32 mask, const struct clk_omap_reg *reg); int (*clkdm_clk_enable)(struct clockdomain *clkdm, struct clk *clk); int (*clkdm_clk_disable)(struct clockdomain *clkdm, struct clk *clk); -- cgit v1.2.3 From e403d00573431e1e3de1710a91c6090c60ec16af Mon Sep 17 00:00:00 2001 From: Peter De Schrijver Date: Thu, 25 Jan 2018 16:00:12 +0200 Subject: clk: tegra: MBIST work around for Tegra210 Tegra210 has a hw bug which can cause IP blocks to lock up when ungating a domain. The reason is that the logic responsible for resetting the memory built-in self test mode can come up in an undefined state because its clock is gated by a second level clock gate (SLCG). Work around this by making sure the logic will get some clock edges by ensuring the relevant clock is enabled and temporarily override the relevant SLCGs. Unfortunately for some IP blocks, the control bits for overriding the SLCGs are not in CAR, but in the IP block itself. This means we need to map a few extra register banks in the clock code. Signed-off-by: Peter De Schrijver Reviewed-by: Jon Hunter Tested-by: Jon Hunter Tested-by: Hector Martin Tested-by: Andre Heider Tested-by: Mikko Perttunen Signed-off-by: Thierry Reding fixup mbist --- drivers/clk/tegra/clk-tegra210.c | 344 ++++++++++++++++++++++++++++++++++++++- include/linux/clk/tegra.h | 1 + 2 files changed, 343 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index f790c2dc5b5d..946d708add1e 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -22,10 +22,12 @@ #include #include #include +#include #include #include #include #include +#include #include "clk.h" #include "clk-id.h" @@ -232,6 +234,30 @@ #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8 #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac +#define LVL2_CLK_GATE_OVRA 0xf8 +#define LVL2_CLK_GATE_OVRC 0x3a0 +#define LVL2_CLK_GATE_OVRD 0x3a4 +#define LVL2_CLK_GATE_OVRE 0x554 + +/* I2S registers to handle during APE MBIST WAR */ +#define TEGRA210_I2S_BASE 0x1000 +#define TEGRA210_I2S_SIZE 0x100 +#define TEGRA210_I2S_CTRLS 5 +#define TEGRA210_I2S_CG 0x88 +#define TEGRA210_I2S_CTRL 0xa0 + +/* DISPA registers to handle during MBIST WAR */ +#define DC_CMD_DISPLAY_COMMAND 0xc8 +#define DC_COM_DSC_TOP_CTL 0xcf8 + +/* VIC register to handle during MBIST WAR */ +#define NV_PVIC_THI_SLCG_OVERRIDE_LOW 0x8c + +/* APE, DISPA and VIC base addesses needed for MBIST WAR */ +#define TEGRA210_AHUB_BASE 0x702d0000 +#define TEGRA210_DISPA_BASE 0x54200000 +#define TEGRA210_VIC_BASE 0x54340000 + /* * SDM fractional divisor is 16-bit 2's complement signed number within * (-2^12 ... 2^12-1) range. Represented in PLL data structure as unsigned @@ -256,8 +282,22 @@ static struct cpu_clk_suspend_context { } tegra210_cpu_clk_sctx; #endif +struct tegra210_domain_mbist_war { + void (*handle_lvl2_ovr)(struct tegra210_domain_mbist_war *mbist); + const u32 lvl2_offset; + const u32 lvl2_mask; + const unsigned int num_clks; + const unsigned int *clk_init_data; + struct clk_bulk_data *clks; +}; + +static struct clk **clks; + static void __iomem *clk_base; static void __iomem *pmc_base; +static void __iomem *ahub_base; +static void __iomem *dispa_base; +static void __iomem *vic_base; static unsigned long osc_freq; static unsigned long pll_ref_freq; @@ -268,6 +308,7 @@ static DEFINE_SPINLOCK(pll_re_lock); static DEFINE_SPINLOCK(pll_u_lock); static DEFINE_SPINLOCK(sor1_lock); static DEFINE_SPINLOCK(emc_lock); +static DEFINE_MUTEX(lvl2_ovr_lock); /* possible OSC frequencies in Hz */ static unsigned long tegra210_input_freq[] = { @@ -311,6 +352,8 @@ static const char *mux_pllmcp_clkm[] = { #define PLLA_MISC2_WRITE_MASK 0x06ffffff /* PLLD */ +#define PLLD_BASE_CSI_CLKSOURCE (1 << 23) + #define PLLD_MISC0_EN_SDM (1 << 16) #define PLLD_MISC0_LOCK_OVERRIDE (1 << 17) #define PLLD_MISC0_LOCK_ENABLE (1 << 18) @@ -514,6 +557,115 @@ void tegra210_set_sata_pll_seq_sw(bool state) } EXPORT_SYMBOL_GPL(tegra210_set_sata_pll_seq_sw); +static void tegra210_generic_mbist_war(struct tegra210_domain_mbist_war *mbist) +{ + u32 val; + + val = readl_relaxed(clk_base + mbist->lvl2_offset); + writel_relaxed(val | mbist->lvl2_mask, clk_base + mbist->lvl2_offset); + fence_udelay(1, clk_base); + writel_relaxed(val, clk_base + mbist->lvl2_offset); + fence_udelay(1, clk_base); +} + +static void tegra210_venc_mbist_war(struct tegra210_domain_mbist_war *mbist) +{ + u32 csi_src, ovra, ovre; + unsigned long flags = 0; + + spin_lock_irqsave(&pll_d_lock, flags); + + csi_src = readl_relaxed(clk_base + PLLD_BASE); + writel_relaxed(csi_src | PLLD_BASE_CSI_CLKSOURCE, clk_base + PLLD_BASE); + fence_udelay(1, clk_base); + + ovra = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRA); + writel_relaxed(ovra | BIT(15), clk_base + LVL2_CLK_GATE_OVRA); + ovre = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRE); + writel_relaxed(ovre | BIT(3), clk_base + LVL2_CLK_GATE_OVRE); + fence_udelay(1, clk_base); + + writel_relaxed(ovra, clk_base + LVL2_CLK_GATE_OVRA); + writel_relaxed(ovre, clk_base + LVL2_CLK_GATE_OVRE); + writel_relaxed(csi_src, clk_base + PLLD_BASE); + fence_udelay(1, clk_base); + + spin_unlock_irqrestore(&pll_d_lock, flags); +} + +static void tegra210_disp_mbist_war(struct tegra210_domain_mbist_war *mbist) +{ + u32 ovra, dsc_top_ctrl; + + ovra = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRA); + writel_relaxed(ovra | BIT(1), clk_base + LVL2_CLK_GATE_OVRA); + fence_udelay(1, clk_base); + + dsc_top_ctrl = readl_relaxed(dispa_base + DC_COM_DSC_TOP_CTL); + writel_relaxed(dsc_top_ctrl | BIT(2), dispa_base + DC_COM_DSC_TOP_CTL); + readl_relaxed(dispa_base + DC_CMD_DISPLAY_COMMAND); + writel_relaxed(dsc_top_ctrl, dispa_base + DC_COM_DSC_TOP_CTL); + readl_relaxed(dispa_base + DC_CMD_DISPLAY_COMMAND); + + writel_relaxed(ovra, clk_base + LVL2_CLK_GATE_OVRA); + fence_udelay(1, clk_base); +} + +static void tegra210_vic_mbist_war(struct tegra210_domain_mbist_war *mbist) +{ + u32 ovre, val; + + ovre = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRE); + writel_relaxed(ovre | BIT(5), clk_base + LVL2_CLK_GATE_OVRE); + fence_udelay(1, clk_base); + + val = readl_relaxed(vic_base + NV_PVIC_THI_SLCG_OVERRIDE_LOW); + writel_relaxed(val | BIT(0) | GENMASK(7, 2) | BIT(24), + vic_base + NV_PVIC_THI_SLCG_OVERRIDE_LOW); + fence_udelay(1, vic_base + NV_PVIC_THI_SLCG_OVERRIDE_LOW); + + writel_relaxed(val, vic_base + NV_PVIC_THI_SLCG_OVERRIDE_LOW); + readl(vic_base + NV_PVIC_THI_SLCG_OVERRIDE_LOW); + + writel_relaxed(ovre, clk_base + LVL2_CLK_GATE_OVRE); + fence_udelay(1, clk_base); +} + +static void tegra210_ape_mbist_war(struct tegra210_domain_mbist_war *mbist) +{ + void __iomem *i2s_base; + unsigned int i; + u32 ovrc, ovre; + + ovrc = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRC); + ovre = readl_relaxed(clk_base + LVL2_CLK_GATE_OVRE); + writel_relaxed(ovrc | BIT(1), clk_base + LVL2_CLK_GATE_OVRC); + writel_relaxed(ovre | BIT(10) | BIT(11), + clk_base + LVL2_CLK_GATE_OVRE); + fence_udelay(1, clk_base); + + i2s_base = ahub_base + TEGRA210_I2S_BASE; + + for (i = 0; i < TEGRA210_I2S_CTRLS; i++) { + u32 i2s_ctrl; + + i2s_ctrl = readl_relaxed(i2s_base + TEGRA210_I2S_CTRL); + writel_relaxed(i2s_ctrl | BIT(10), + i2s_base + TEGRA210_I2S_CTRL); + writel_relaxed(0, i2s_base + TEGRA210_I2S_CG); + readl(i2s_base + TEGRA210_I2S_CG); + writel_relaxed(1, i2s_base + TEGRA210_I2S_CG); + writel_relaxed(i2s_ctrl, i2s_base + TEGRA210_I2S_CTRL); + readl(i2s_base + TEGRA210_I2S_CTRL); + + i2s_base += TEGRA210_I2S_SIZE; + } + + writel_relaxed(ovrc, clk_base + LVL2_CLK_GATE_OVRC); + writel_relaxed(ovre, clk_base + LVL2_CLK_GATE_OVRE); + fence_udelay(1, clk_base); +} + static inline void _pll_misc_chk_default(void __iomem *base, struct tegra_clk_pll_params *params, u8 misc_num, u32 default_val, u32 mask) @@ -2412,13 +2564,150 @@ static struct tegra_audio_clk_info tegra210_audio_plls[] = { { "pll_a1", &pll_a1_params, tegra_clk_pll_a1, "pll_ref" }, }; -static struct clk **clks; - static const char * const aclk_parents[] = { "pll_a1", "pll_c", "pll_p", "pll_a_out0", "pll_c2", "pll_c3", "clk_m" }; +static const unsigned int nvjpg_slcg_clkids[] = { TEGRA210_CLK_NVDEC }; +static const unsigned int nvdec_slcg_clkids[] = { TEGRA210_CLK_NVJPG }; +static const unsigned int sor_slcg_clkids[] = { TEGRA210_CLK_HDA2CODEC_2X, + TEGRA210_CLK_HDA2HDMI, TEGRA210_CLK_DISP1, TEGRA210_CLK_DISP2 }; +static const unsigned int disp_slcg_clkids[] = { TEGRA210_CLK_LA, + TEGRA210_CLK_HOST1X}; +static const unsigned int xusba_slcg_clkids[] = { TEGRA210_CLK_XUSB_HOST, + TEGRA210_CLK_XUSB_DEV }; +static const unsigned int xusbb_slcg_clkids[] = { TEGRA210_CLK_XUSB_HOST, + TEGRA210_CLK_XUSB_SS }; +static const unsigned int xusbc_slcg_clkids[] = { TEGRA210_CLK_XUSB_DEV, + TEGRA210_CLK_XUSB_SS }; +static const unsigned int venc_slcg_clkids[] = { TEGRA210_CLK_HOST1X, + TEGRA210_CLK_PLL_D }; +static const unsigned int ape_slcg_clkids[] = { TEGRA210_CLK_ACLK, + TEGRA210_CLK_I2S0, TEGRA210_CLK_I2S1, TEGRA210_CLK_I2S2, + TEGRA210_CLK_I2S3, TEGRA210_CLK_I2S4, TEGRA210_CLK_SPDIF_OUT, + TEGRA210_CLK_D_AUDIO }; +static const unsigned int vic_slcg_clkids[] = { TEGRA210_CLK_HOST1X }; + +static struct tegra210_domain_mbist_war tegra210_pg_mbist_war[] = { + [TEGRA_POWERGATE_VENC] = { + .handle_lvl2_ovr = tegra210_venc_mbist_war, + .num_clks = ARRAY_SIZE(venc_slcg_clkids), + .clk_init_data = venc_slcg_clkids, + }, + [TEGRA_POWERGATE_SATA] = { + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(0) | BIT(17) | BIT(19), + }, + [TEGRA_POWERGATE_MPE] = { + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRE, + .lvl2_mask = BIT(2), + }, + [TEGRA_POWERGATE_SOR] = { + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .num_clks = ARRAY_SIZE(sor_slcg_clkids), + .clk_init_data = sor_slcg_clkids, + .lvl2_offset = LVL2_CLK_GATE_OVRA, + .lvl2_mask = BIT(1) | BIT(2), + }, + [TEGRA_POWERGATE_DIS] = { + .handle_lvl2_ovr = tegra210_disp_mbist_war, + .num_clks = ARRAY_SIZE(disp_slcg_clkids), + .clk_init_data = disp_slcg_clkids, + }, + [TEGRA_POWERGATE_DISB] = { + .num_clks = ARRAY_SIZE(disp_slcg_clkids), + .clk_init_data = disp_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRA, + .lvl2_mask = BIT(2), + }, + [TEGRA_POWERGATE_XUSBA] = { + .num_clks = ARRAY_SIZE(xusba_slcg_clkids), + .clk_init_data = xusba_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(30) | BIT(31), + }, + [TEGRA_POWERGATE_XUSBB] = { + .num_clks = ARRAY_SIZE(xusbb_slcg_clkids), + .clk_init_data = xusbb_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(30) | BIT(31), + }, + [TEGRA_POWERGATE_XUSBC] = { + .num_clks = ARRAY_SIZE(xusbc_slcg_clkids), + .clk_init_data = xusbc_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(30) | BIT(31), + }, + [TEGRA_POWERGATE_VIC] = { + .num_clks = ARRAY_SIZE(vic_slcg_clkids), + .clk_init_data = vic_slcg_clkids, + .handle_lvl2_ovr = tegra210_vic_mbist_war, + }, + [TEGRA_POWERGATE_NVDEC] = { + .num_clks = ARRAY_SIZE(nvdec_slcg_clkids), + .clk_init_data = nvdec_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(9) | BIT(31), + }, + [TEGRA_POWERGATE_NVJPG] = { + .num_clks = ARRAY_SIZE(nvjpg_slcg_clkids), + .clk_init_data = nvjpg_slcg_clkids, + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRC, + .lvl2_mask = BIT(9) | BIT(31), + }, + [TEGRA_POWERGATE_AUD] = { + .num_clks = ARRAY_SIZE(ape_slcg_clkids), + .clk_init_data = ape_slcg_clkids, + .handle_lvl2_ovr = tegra210_ape_mbist_war, + }, + [TEGRA_POWERGATE_VE2] = { + .handle_lvl2_ovr = tegra210_generic_mbist_war, + .lvl2_offset = LVL2_CLK_GATE_OVRD, + .lvl2_mask = BIT(22), + }, +}; + +int tegra210_clk_handle_mbist_war(unsigned int id) +{ + int err; + struct tegra210_domain_mbist_war *mbist_war; + + if (id >= ARRAY_SIZE(tegra210_pg_mbist_war)) { + WARN(1, "unknown domain id in MBIST WAR handler\n"); + return -EINVAL; + } + + mbist_war = &tegra210_pg_mbist_war[id]; + if (!mbist_war->handle_lvl2_ovr) + return 0; + + if (mbist_war->num_clks && !mbist_war->clks) + return -ENODEV; + + err = clk_bulk_prepare_enable(mbist_war->num_clks, mbist_war->clks); + if (err < 0) + return err; + + mutex_lock(&lvl2_ovr_lock); + + mbist_war->handle_lvl2_ovr(mbist_war); + + mutex_unlock(&lvl2_ovr_lock); + + clk_bulk_disable_unprepare(mbist_war->num_clks, mbist_war->clks); + + return 0; +} + void tegra210_put_utmipll_in_iddq(void) { u32 reg; @@ -3163,6 +3452,37 @@ static int tegra210_reset_deassert(unsigned long id) return 0; } +static void tegra210_mbist_clk_init(void) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(tegra210_pg_mbist_war); i++) { + unsigned int num_clks = tegra210_pg_mbist_war[i].num_clks; + struct clk_bulk_data *clk_data; + + if (!num_clks) + continue; + + clk_data = kmalloc_array(num_clks, sizeof(*clk_data), + GFP_KERNEL); + if (WARN_ON(!clk_data)) + return; + + tegra210_pg_mbist_war[i].clks = clk_data; + for (j = 0; j < num_clks; j++) { + int clk_id = tegra210_pg_mbist_war[i].clk_init_data[j]; + struct clk *clk = clks[clk_id]; + + if (WARN(IS_ERR(clk), "clk_id: %d\n", clk_id)) { + kfree(clk_data); + tegra210_pg_mbist_war[i].clks = NULL; + break; + } + clk_data[j].clk = clk; + } + } +} + /** * tegra210_clock_init - Tegra210-specific clock initialization * @np: struct device_node * of the DT node for the SoC CAR IP block @@ -3197,6 +3517,24 @@ static void __init tegra210_clock_init(struct device_node *np) return; } + ahub_base = ioremap(TEGRA210_AHUB_BASE, SZ_64K); + if (!ahub_base) { + pr_err("ioremap tegra210 APE failed\n"); + return; + } + + dispa_base = ioremap(TEGRA210_DISPA_BASE, SZ_256K); + if (!dispa_base) { + pr_err("ioremap tegra210 DISPA failed\n"); + return; + } + + vic_base = ioremap(TEGRA210_VIC_BASE, SZ_256K); + if (!vic_base) { + pr_err("ioremap tegra210 VIC failed\n"); + return; + } + clks = tegra_clk_init(clk_base, TEGRA210_CLK_CLK_MAX, TEGRA210_CAR_BANK_COUNT); if (!clks) @@ -3233,6 +3571,8 @@ static void __init tegra210_clock_init(struct device_node *np) tegra_add_of_provider(np); tegra_register_devclks(devclks, ARRAY_SIZE(devclks)); + tegra210_mbist_clk_init(); + tegra_cpu_car_ops = &tegra210_cpu_car_ops; } CLK_OF_DECLARE(tegra210, "nvidia,tegra210-car", tegra210_clock_init); diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h index d23c9cf26993..afb9edfa5d58 100644 --- a/include/linux/clk/tegra.h +++ b/include/linux/clk/tegra.h @@ -128,5 +128,6 @@ extern void tegra210_sata_pll_hw_sequence_start(void); extern void tegra210_set_sata_pll_seq_sw(bool state); extern void tegra210_put_utmipll_in_iddq(void); extern void tegra210_put_utmipll_out_iddq(void); +extern int tegra210_clk_handle_mbist_war(unsigned int id); #endif /* __LINUX_CLK_TEGRA_H_ */ -- cgit v1.2.3 From e6d3cc7b1fac3d7f1313faf8ac9b23830113e3ec Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Feb 2018 14:43:33 +0100 Subject: clk: divider: export clk_div_mask() helper Export clk_div_mask() in clk-provider header so every clock providers derived from the generic clock divider may share the definition instead of redefining it. Signed-off-by: Jerome Brunet Signed-off-by: Michael Turquette Signed-off-by: Stephen Boyd --- drivers/clk/clk-divider.c | 24 +++++++++++------------- include/linux/clk-provider.h | 1 + 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index b49942b9fe50..3c98d2650fa3 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -28,12 +28,10 @@ * parent - fixed parent. No clk_set_parent support */ -#define div_mask(width) ((1 << (width)) - 1) - static unsigned int _get_table_maxdiv(const struct clk_div_table *table, u8 width) { - unsigned int maxdiv = 0, mask = div_mask(width); + unsigned int maxdiv = 0, mask = clk_div_mask(width); const struct clk_div_table *clkt; for (clkt = table; clkt->div; clkt++) @@ -57,12 +55,12 @@ static unsigned int _get_maxdiv(const struct clk_div_table *table, u8 width, unsigned long flags) { if (flags & CLK_DIVIDER_ONE_BASED) - return div_mask(width); + return clk_div_mask(width); if (flags & CLK_DIVIDER_POWER_OF_TWO) - return 1 << div_mask(width); + return 1 << clk_div_mask(width); if (table) return _get_table_maxdiv(table, width); - return div_mask(width) + 1; + return clk_div_mask(width) + 1; } static unsigned int _get_table_div(const struct clk_div_table *table, @@ -84,7 +82,7 @@ static unsigned int _get_div(const struct clk_div_table *table, if (flags & CLK_DIVIDER_POWER_OF_TWO) return 1 << val; if (flags & CLK_DIVIDER_MAX_AT_ZERO) - return val ? val : div_mask(width) + 1; + return val ? val : clk_div_mask(width) + 1; if (table) return _get_table_div(table, val); return val + 1; @@ -109,7 +107,7 @@ static unsigned int _get_val(const struct clk_div_table *table, if (flags & CLK_DIVIDER_POWER_OF_TWO) return __ffs(div); if (flags & CLK_DIVIDER_MAX_AT_ZERO) - return (div == div_mask(width) + 1) ? 0 : div; + return (div == clk_div_mask(width) + 1) ? 0 : div; if (table) return _get_table_val(table, div); return div - 1; @@ -141,7 +139,7 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw, unsigned int val; val = clk_readl(divider->reg) >> divider->shift; - val &= div_mask(divider->width); + val &= clk_div_mask(divider->width); return divider_recalc_rate(hw, parent_rate, val, divider->table, divider->flags, divider->width); @@ -353,7 +351,7 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, /* if read only, just return current value */ if (divider->flags & CLK_DIVIDER_READ_ONLY) { bestdiv = clk_readl(divider->reg) >> divider->shift; - bestdiv &= div_mask(divider->width); + bestdiv &= clk_div_mask(divider->width); bestdiv = _get_div(divider->table, bestdiv, divider->flags, divider->width); return DIV_ROUND_UP_ULL((u64)*prate, bestdiv); @@ -376,7 +374,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, value = _get_val(table, div, flags, width); - return min_t(unsigned int, value, div_mask(width)); + return min_t(unsigned int, value, clk_div_mask(width)); } EXPORT_SYMBOL_GPL(divider_get_val); @@ -399,10 +397,10 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate, __acquire(divider->lock); if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { - val = div_mask(divider->width) << (divider->shift + 16); + val = clk_div_mask(divider->width) << (divider->shift + 16); } else { val = clk_readl(divider->reg); - val &= ~(div_mask(divider->width) << divider->shift); + val &= ~(clk_div_mask(divider->width) << divider->shift); } val |= (u32)value << divider->shift; clk_writel(val, divider->reg); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index f711be6e8c44..d8ba26d03332 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -399,6 +399,7 @@ struct clk_divider { spinlock_t *lock; }; +#define clk_div_mask(width) ((1 << (width)) - 1) #define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) #define CLK_DIVIDER_ONE_BASED BIT(0) -- cgit v1.2.3 From 77deb66d262f8512130ff75ec5ea8e31070b41ed Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Feb 2018 14:43:34 +0100 Subject: clk: mux: add helper function for index/value translation Add helper functions for the translation between parent index and register value in the generic multiplexer function. The purpose of this change is avoid duplicating the code in other clock providers, using the same generic logic. Signed-off-by: Jerome Brunet Signed-off-by: Michael Turquette Signed-off-by: Stephen Boyd --- drivers/clk/clk-mux.c | 75 +++++++++++++++++++++++++------------------- include/linux/clk-provider.h | 4 +++ 2 files changed, 47 insertions(+), 32 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index 39cabe157163..ac4a042f8658 100644 --- a/drivers/clk/clk-mux.c +++ b/drivers/clk/clk-mux.c @@ -26,35 +26,24 @@ * parent - parent is adjustable through clk_set_parent */ -static u8 clk_mux_get_parent(struct clk_hw *hw) +int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags, + unsigned int val) { - struct clk_mux *mux = to_clk_mux(hw); int num_parents = clk_hw_get_num_parents(hw); - u32 val; - /* - * FIXME need a mux-specific flag to determine if val is bitwise or numeric - * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 - * to 0x7 (index starts at one) - * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so - * val = 0x4 really means "bit 2, index starts at bit 0" - */ - val = clk_readl(mux->reg) >> mux->shift; - val &= mux->mask; - - if (mux->table) { + if (table) { int i; for (i = 0; i < num_parents; i++) - if (mux->table[i] == val) + if (table[i] == val) return i; return -EINVAL; } - if (val && (mux->flags & CLK_MUX_INDEX_BIT)) + if (val && (flags & CLK_MUX_INDEX_BIT)) val = ffs(val) - 1; - if (val && (mux->flags & CLK_MUX_INDEX_ONE)) + if (val && (flags & CLK_MUX_INDEX_ONE)) val--; if (val >= num_parents) @@ -62,36 +51,58 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) return val; } +EXPORT_SYMBOL_GPL(clk_mux_val_to_index); -static int clk_mux_set_parent(struct clk_hw *hw, u8 index) +unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index) { - struct clk_mux *mux = to_clk_mux(hw); - u32 val; - unsigned long flags = 0; + unsigned int val = index; - if (mux->table) { - index = mux->table[index]; + if (table) { + val = table[index]; } else { - if (mux->flags & CLK_MUX_INDEX_BIT) - index = 1 << index; + if (flags & CLK_MUX_INDEX_BIT) + val = 1 << index; - if (mux->flags & CLK_MUX_INDEX_ONE) - index++; + if (flags & CLK_MUX_INDEX_ONE) + val++; } + return val; +} +EXPORT_SYMBOL_GPL(clk_mux_index_to_val); + +static u8 clk_mux_get_parent(struct clk_hw *hw) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val; + + val = clk_readl(mux->reg) >> mux->shift; + val &= mux->mask; + + return clk_mux_val_to_index(hw, mux->table, mux->flags, val); +} + +static int clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val = clk_mux_index_to_val(mux->table, mux->flags, index); + unsigned long flags = 0; + u32 reg; + if (mux->lock) spin_lock_irqsave(mux->lock, flags); else __acquire(mux->lock); if (mux->flags & CLK_MUX_HIWORD_MASK) { - val = mux->mask << (mux->shift + 16); + reg = mux->mask << (mux->shift + 16); } else { - val = clk_readl(mux->reg); - val &= ~(mux->mask << mux->shift); + reg = clk_readl(mux->reg); + reg &= ~(mux->mask << mux->shift); } - val |= index << mux->shift; - clk_writel(val, mux->reg); + val = val << mux->shift; + reg |= val; + clk_writel(reg, mux->reg); if (mux->lock) spin_unlock_irqrestore(mux->lock, flags); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index d8ba26d03332..fe720d679c31 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -511,6 +511,10 @@ struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name, void __iomem *reg, u8 shift, u32 mask, u8 clk_mux_flags, u32 *table, spinlock_t *lock); +int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags, + unsigned int val); +unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index); + void clk_unregister_mux(struct clk *clk); void clk_hw_unregister_mux(struct clk_hw *hw); -- cgit v1.2.3 From fe3f338f0cb2ed4d4f06da054c21ae2f8a36ef2d Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Feb 2018 14:43:38 +0100 Subject: clk: fix mux clock documentation The mux documentation mentions the non-existing parameter width instead of mask, so just sed this. The table field is missing in the documentation of clk_mux. Add a small blurb explaining what it is Fixes: 9d9f78ed9af0 ("clk: basic clock hardware types") Signed-off-by: Jerome Brunet Signed-off-by: Michael Turquette Signed-off-by: Stephen Boyd --- include/linux/clk-provider.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index fe720d679c31..cb18526d69cb 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -450,8 +450,9 @@ void clk_hw_unregister_divider(struct clk_hw *hw); * * @hw: handle between common and hardware-specific interfaces * @reg: register controlling multiplexer + * @table: array of register values corresponding to the parent index * @shift: shift to multiplexer bit field - * @width: width of mutliplexer bit field + * @mask: mask of mutliplexer bit field * @flags: hardware-specific flags * @lock: register lock * -- cgit v1.2.3 From b15ee490e16324c35b51f04bad54ae45a2cefd29 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Feb 2018 14:43:39 +0100 Subject: clk: divider: read-only divider can propagate rate change When a divider clock has CLK_DIVIDER_READ_ONLY set, it means that the register shall be left un-touched, but it does not mean the clock should stop rate propagation if CLK_SET_RATE_PARENT is set This is properly handled in qcom clk-regmap-divider but it was not in the generic divider To fix this situation, introduce a new helper function divider_ro_round_rate, on the same model as divider_round_rate. Fixes: e6d5e7d90be9 ("clk-divider: Fix READ_ONLY when divider > 1") Signed-off-by: Jerome Brunet Tested-By: David Lechner Signed-off-by: Michael Turquette Signed-off-by: Stephen Boyd --- drivers/clk/clk-divider.c | 36 ++++++++++++++++++++++++++++++------ include/linux/clk-provider.h | 15 +++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index 3c98d2650fa3..b6234a5da12d 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -342,19 +342,43 @@ long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, } EXPORT_SYMBOL_GPL(divider_round_rate_parent); +long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, + unsigned long rate, unsigned long *prate, + const struct clk_div_table *table, u8 width, + unsigned long flags, unsigned int val) +{ + int div; + + div = _get_div(table, val, flags, width); + + /* Even a read-only clock can propagate a rate change */ + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + if (!parent) + return -EINVAL; + + *prate = clk_hw_round_rate(parent, rate * div); + } + + return DIV_ROUND_UP_ULL((u64)*prate, div); +} +EXPORT_SYMBOL_GPL(divider_ro_round_rate_parent); + + static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { struct clk_divider *divider = to_clk_divider(hw); - int bestdiv; /* if read only, just return current value */ if (divider->flags & CLK_DIVIDER_READ_ONLY) { - bestdiv = clk_readl(divider->reg) >> divider->shift; - bestdiv &= clk_div_mask(divider->width); - bestdiv = _get_div(divider->table, bestdiv, divider->flags, - divider->width); - return DIV_ROUND_UP_ULL((u64)*prate, bestdiv); + u32 val; + + val = clk_readl(divider->reg) >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_ro_round_rate(hw, rate, prate, divider->table, + divider->width, divider->flags, + val); } return divider_round_rate(hw, rate, prate, divider->table, diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index cb18526d69cb..210a890008f9 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -420,6 +420,10 @@ long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, unsigned long rate, unsigned long *prate, const struct clk_div_table *table, u8 width, unsigned long flags); +long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, + unsigned long rate, unsigned long *prate, + const struct clk_div_table *table, u8 width, + unsigned long flags, unsigned int val); int divider_get_val(unsigned long rate, unsigned long parent_rate, const struct clk_div_table *table, u8 width, unsigned long flags); @@ -780,6 +784,17 @@ static inline long divider_round_rate(struct clk_hw *hw, unsigned long rate, rate, prate, table, width, flags); } +static inline long divider_ro_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate, + const struct clk_div_table *table, + u8 width, unsigned long flags, + unsigned int val) +{ + return divider_ro_round_rate_parent(hw, clk_hw_get_parent(hw), + rate, prate, table, width, flags, + val); +} + /* * FIXME clock api without lock protection */ -- cgit v1.2.3 From 6e0d4ff4580c1272f4e4860bf22841ef31fd31ba Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 23 Jan 2018 20:24:45 +0800 Subject: clk: add more __must_check for bulk APIs we need it even when !CONFIG_HAVE_CLK because it allows us to catch missing checking return values in the non-clk compile configurations too. More test coverage. Cc: Stephen Boyd Suggested-by: Stephen Boyd Signed-off-by: Dong Aisheng Signed-off-by: Stephen Boyd --- include/linux/clk.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/include/linux/clk.h b/include/linux/clk.h index 4c4ef9f34db3..0dbd0885b2c2 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -209,7 +209,7 @@ static inline int clk_prepare(struct clk *clk) return 0; } -static inline int clk_bulk_prepare(int num_clks, struct clk_bulk_data *clks) +static inline int __must_check clk_bulk_prepare(int num_clks, struct clk_bulk_data *clks) { might_sleep(); return 0; @@ -603,8 +603,8 @@ static inline struct clk *clk_get(struct device *dev, const char *id) return NULL; } -static inline int clk_bulk_get(struct device *dev, int num_clks, - struct clk_bulk_data *clks) +static inline int __must_check clk_bulk_get(struct device *dev, int num_clks, + struct clk_bulk_data *clks) { return 0; } @@ -614,8 +614,8 @@ static inline struct clk *devm_clk_get(struct device *dev, const char *id) return NULL; } -static inline int devm_clk_bulk_get(struct device *dev, int num_clks, - struct clk_bulk_data *clks) +static inline int __must_check devm_clk_bulk_get(struct device *dev, int num_clks, + struct clk_bulk_data *clks) { return 0; } @@ -645,7 +645,7 @@ static inline int clk_enable(struct clk *clk) return 0; } -static inline int clk_bulk_enable(int num_clks, struct clk_bulk_data *clks) +static inline int __must_check clk_bulk_enable(int num_clks, struct clk_bulk_data *clks) { return 0; } @@ -719,8 +719,8 @@ static inline void clk_disable_unprepare(struct clk *clk) clk_unprepare(clk); } -static inline int clk_bulk_prepare_enable(int num_clks, - struct clk_bulk_data *clks) +static inline int __must_check clk_bulk_prepare_enable(int num_clks, + struct clk_bulk_data *clks) { int ret; -- cgit v1.2.3 From 08fdc8a0138afaf324296a342f32ad26ec465e43 Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Tue, 3 Oct 2017 18:17:41 +0200 Subject: buffer.c: call thaw_super during emergency thaw There are 2 distinct freezing mechanisms - one operates on block devices and another one directly on super blocks. Both end up with the same result, but thaw of only one of these does not thaw the other. In particular fsfreeze --freeze uses the ioctl variant going to the super block. Since prior to this patch emergency thaw was not doing a relevant thaw, filesystems frozen with this method remained unaffected. The patch is a hack which adds blind unfreezing. In order to keep the super block write-locked the whole time the code is shuffled around and the newly introduced __iterate_supers is employed. Signed-off-by: Mateusz Guzik Signed-off-by: Al Viro --- fs/buffer.c | 25 +------------------------ fs/super.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- include/linux/fs.h | 6 ++++++ 3 files changed, 49 insertions(+), 26 deletions(-) (limited to 'include/linux') diff --git a/fs/buffer.c b/fs/buffer.c index 170df856bdb9..37ea00b265d0 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -523,35 +523,12 @@ repeat: return err; } -static void do_thaw_one(struct super_block *sb, void *unused) +void emergency_thaw_bdev(struct super_block *sb) { while (sb->s_bdev && !thaw_bdev(sb->s_bdev, sb)) printk(KERN_WARNING "Emergency Thaw on %pg\n", sb->s_bdev); } -static void do_thaw_all(struct work_struct *work) -{ - iterate_supers(do_thaw_one, NULL); - kfree(work); - printk(KERN_WARNING "Emergency Thaw complete\n"); -} - -/** - * emergency_thaw_all -- forcibly thaw every frozen filesystem - * - * Used for emergency unfreeze of all filesystems via SysRq - */ -void emergency_thaw_all(void) -{ - struct work_struct *work; - - work = kmalloc(sizeof(*work), GFP_ATOMIC); - if (work) { - INIT_WORK(work, do_thaw_all); - schedule_work(work); - } -} - /** * sync_mapping_buffers - write out & wait upon a mapping's "associated" buffers * @mapping: the mapping which wants those buffers written diff --git a/fs/super.c b/fs/super.c index fd9c02f543eb..83c5c8a60f5f 100644 --- a/fs/super.c +++ b/fs/super.c @@ -36,6 +36,7 @@ #include #include "internal.h" +static int thaw_super_locked(struct super_block *sb); static LIST_HEAD(super_blocks); static DEFINE_SPINLOCK(sb_lock); @@ -934,6 +935,40 @@ void emergency_remount(void) } } +static void do_thaw_all_callback(struct super_block *sb) +{ + down_write(&sb->s_umount); + if (sb->s_root && sb->s_flags & MS_BORN) { + emergency_thaw_bdev(sb); + thaw_super_locked(sb); + } else { + up_write(&sb->s_umount); + } +} + +static void do_thaw_all(struct work_struct *work) +{ + __iterate_supers(do_thaw_all_callback); + kfree(work); + printk(KERN_WARNING "Emergency Thaw complete\n"); +} + +/** + * emergency_thaw_all -- forcibly thaw every frozen filesystem + * + * Used for emergency unfreeze of all filesystems via SysRq + */ +void emergency_thaw_all(void) +{ + struct work_struct *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK(work, do_thaw_all); + schedule_work(work); + } +} + /* * Unnamed block devices are dummy devices used by virtual * filesystems which don't use real block-devices. -- jrs @@ -1503,11 +1538,10 @@ EXPORT_SYMBOL(freeze_super); * * Unlocks the filesystem and marks it writeable again after freeze_super(). */ -int thaw_super(struct super_block *sb) +static int thaw_super_locked(struct super_block *sb) { int error; - down_write(&sb->s_umount); if (sb->s_writers.frozen != SB_FREEZE_COMPLETE) { up_write(&sb->s_umount); return -EINVAL; @@ -1538,4 +1572,10 @@ out: deactivate_locked_super(sb); return 0; } + +int thaw_super(struct super_block *sb) +{ + down_write(&sb->s_umount); + return thaw_super_locked(sb); +} EXPORT_SYMBOL(thaw_super); diff --git a/include/linux/fs.h b/include/linux/fs.h index 339e73742e73..b864fcb3b5aa 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2428,6 +2428,7 @@ extern int sync_blockdev(struct block_device *bdev); extern void kill_bdev(struct block_device *); extern struct super_block *freeze_bdev(struct block_device *); extern void emergency_thaw_all(void); +extern void emergency_thaw_bdev(struct super_block *sb); extern int thaw_bdev(struct block_device *bdev, struct super_block *sb); extern int fsync_bdev(struct block_device *); @@ -2453,6 +2454,11 @@ static inline int thaw_bdev(struct block_device *bdev, struct super_block *sb) return 0; } +static inline int emergency_thaw_bdev(struct super_block *sb) +{ + return 0; +} + static inline void iterate_bdevs(void (*f)(struct block_device *, void *), void *arg) { } -- cgit v1.2.3 From 2d172691515961cad2abb4bf1b15d187bf2106cf Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 15 Mar 2018 21:52:18 -0500 Subject: clk: davinci: New driver for davinci PLL clocks This adds a new driver for mach-davinci PLL clocks. This is porting the code from arch/arm/mach-davinci/clock.c to the common clock framework. Additionally, it adds device tree support for these clocks. The ifeq ($(CONFIG_COMMON_CLK), y) in the Makefile is needed to prevent compile errors until the clock code in arch/arm/mach-davinci is removed. Note: although there are similar clocks for TI Keystone we are not able to share the code for a few reasons. The keystone clocks are device tree only and use legacy one-node-per-clock bindings. Also the register layouts are a bit different, which would add even more if/else mess to the keystone clocks. And the keystone PLL driver doesn't support setting clock rates. Signed-off-by: David Lechner Signed-off-by: Stephen Boyd --- MAINTAINERS | 7 + drivers/clk/Makefile | 1 + drivers/clk/davinci/Makefile | 5 + drivers/clk/davinci/pll.c | 888 ++++++++++++++++++++++++++ drivers/clk/davinci/pll.h | 120 ++++ include/linux/platform_data/clk-davinci-pll.h | 21 + 6 files changed, 1042 insertions(+) create mode 100644 drivers/clk/davinci/Makefile create mode 100644 drivers/clk/davinci/pll.c create mode 100644 drivers/clk/davinci/pll.h create mode 100644 include/linux/platform_data/clk-davinci-pll.h (limited to 'include/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 3bdc260e36b7..e6b9f169a243 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13792,6 +13792,13 @@ F: arch/arm/mach-davinci/ F: drivers/i2c/busses/i2c-davinci.c F: arch/arm/boot/dts/da850* +TI DAVINCI SERIES CLOCK DRIVER +M: David Lechner +R: Sekhar Nori +S: Maintained +F: Documentation/devicetree/bindings/clock/ti/davinci/ +F: drivers/clk/davinci/ + TI DAVINCI SERIES GPIO DRIVER M: Keerthy L: linux-gpio@vger.kernel.org diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 71ec41e6364f..07ac0fdb71a9 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/ obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ +obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_H8300) += h8300/ obj-$(CONFIG_ARCH_HISI) += hisilicon/ obj-y += imgtec/ diff --git a/drivers/clk/davinci/Makefile b/drivers/clk/davinci/Makefile new file mode 100644 index 000000000000..d9673bd321e0 --- /dev/null +++ b/drivers/clk/davinci/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifeq ($(CONFIG_COMMON_CLK), y) +obj-y += pll.o +endif diff --git a/drivers/clk/davinci/pll.c b/drivers/clk/davinci/pll.c new file mode 100644 index 000000000000..bfa5b7e52d3d --- /dev/null +++ b/drivers/clk/davinci/pll.c @@ -0,0 +1,888 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PLL clock driver for TI Davinci SoCs + * + * Copyright (C) 2018 David Lechner + * + * Based on arch/arm/mach-davinci/clock.c + * Copyright (C) 2006-2007 Texas Instruments. + * Copyright (C) 2008-2009 Deep Root Systems, LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pll.h" + +#define MAX_NAME_SIZE 20 +#define OSCIN_CLK_NAME "oscin" + +#define REVID 0x000 +#define PLLCTL 0x100 +#define OCSEL 0x104 +#define PLLSECCTL 0x108 +#define PLLM 0x110 +#define PREDIV 0x114 +#define PLLDIV1 0x118 +#define PLLDIV2 0x11c +#define PLLDIV3 0x120 +#define OSCDIV 0x124 +#define POSTDIV 0x128 +#define BPDIV 0x12c +#define PLLCMD 0x138 +#define PLLSTAT 0x13c +#define ALNCTL 0x140 +#define DCHANGE 0x144 +#define CKEN 0x148 +#define CKSTAT 0x14c +#define SYSTAT 0x150 +#define PLLDIV4 0x160 +#define PLLDIV5 0x164 +#define PLLDIV6 0x168 +#define PLLDIV7 0x16c +#define PLLDIV8 0x170 +#define PLLDIV9 0x174 + +#define PLLCTL_PLLEN BIT(0) +#define PLLCTL_PLLPWRDN BIT(1) +#define PLLCTL_PLLRST BIT(3) +#define PLLCTL_PLLDIS BIT(4) +#define PLLCTL_PLLENSRC BIT(5) +#define PLLCTL_CLKMODE BIT(8) + +/* shared by most *DIV registers */ +#define DIV_RATIO_SHIFT 0 +#define DIV_RATIO_WIDTH 5 +#define DIV_ENABLE_SHIFT 15 + +#define PLLCMD_GOSET BIT(0) +#define PLLSTAT_GOSTAT BIT(0) + +#define CKEN_OBSCLK_SHIFT 1 +#define CKEN_AUXEN_SHIFT 0 + +/* + * OMAP-L138 system reference guide recommends a wait for 4 OSCIN/CLKIN + * cycles to ensure that the PLLC has switched to bypass mode. Delay of 1us + * ensures we are good for all > 4MHz OSCIN/CLKIN inputs. Typically the input + * is ~25MHz. Units are micro seconds. + */ +#define PLL_BYPASS_TIME 1 + +/* From OMAP-L138 datasheet table 6-4. Units are micro seconds */ +#define PLL_RESET_TIME 1 + +/* + * From OMAP-L138 datasheet table 6-4; assuming prediv = 1, sqrt(pllm) = 4 + * Units are micro seconds. + */ +#define PLL_LOCK_TIME 20 + +/** + * struct davinci_pll_clk - Main PLL clock (aka PLLOUT) + * @hw: clk_hw for the pll + * @base: Base memory address + * @pllm_min: The minimum allowable PLLM[PLLM] value + * @pllm_max: The maxiumum allowable PLLM[PLLM] value + * @pllm_mask: Bitmask for PLLM[PLLM] value + */ +struct davinci_pll_clk { + struct clk_hw hw; + void __iomem *base; + u32 pllm_min; + u32 pllm_max; + u32 pllm_mask; +}; + +#define to_davinci_pll_clk(_hw) \ + container_of((_hw), struct davinci_pll_clk, hw) + +static unsigned long davinci_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + unsigned long rate = parent_rate; + u32 mult; + + mult = readl(pll->base + PLLM) & pll->pllm_mask; + rate *= mult + 1; + + return rate; +} + +static int davinci_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + struct clk_hw *parent = req->best_parent_hw; + unsigned long parent_rate = req->best_parent_rate; + unsigned long rate = req->rate; + unsigned long best_rate, r; + u32 mult; + + /* there is a limited range of valid outputs (see datasheet) */ + if (rate < req->min_rate) + return -EINVAL; + + rate = min(rate, req->max_rate); + mult = rate / parent_rate; + best_rate = parent_rate * mult; + + /* easy case when there is no PREDIV */ + if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) { + if (best_rate < req->min_rate) + return -EINVAL; + + if (mult < pll->pllm_min || mult > pll->pllm_max) + return -EINVAL; + + req->rate = best_rate; + + return 0; + } + + /* see if the PREDIV clock can help us */ + best_rate = 0; + + for (mult = pll->pllm_min; mult <= pll->pllm_max; mult++) { + parent_rate = clk_hw_round_rate(parent, rate / mult); + r = parent_rate * mult; + if (r < req->min_rate) + continue; + if (r > rate || r > req->max_rate) + break; + if (r > best_rate) { + best_rate = r; + req->rate = best_rate; + req->best_parent_rate = parent_rate; + if (best_rate == rate) + break; + } + } + + return 0; +} + +static int davinci_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + u32 mult; + + mult = rate / parent_rate; + writel(mult - 1, pll->base + PLLM); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int davinci_pll_debug_init(struct clk_hw *hw, struct dentry *dentry); +#else +#define davinci_pll_debug_init NULL +#endif + +static const struct clk_ops davinci_pll_ops = { + .recalc_rate = davinci_pll_recalc_rate, + .determine_rate = davinci_pll_determine_rate, + .set_rate = davinci_pll_set_rate, + .debug_init = davinci_pll_debug_init, +}; + +/* PLLM works differently on DM365 */ +static unsigned long dm365_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + unsigned long rate = parent_rate; + u32 mult; + + mult = readl(pll->base + PLLM) & pll->pllm_mask; + rate *= mult * 2; + + return rate; +} + +static const struct clk_ops dm365_pll_ops = { + .recalc_rate = dm365_pll_recalc_rate, + .debug_init = davinci_pll_debug_init, +}; + +/** + * davinci_pll_div_register - common *DIV clock implementation + * @name: the clock name + * @parent_name: the parent clock name + * @reg: the *DIV register + * @fixed: if true, the divider is a fixed value + * @flags: bitmap of CLK_* flags from clock-provider.h + */ +static struct clk *davinci_pll_div_register(struct device *dev, + const char *name, + const char *parent_name, + void __iomem *reg, + bool fixed, u32 flags) +{ + const char * const *parent_names = parent_name ? &parent_name : NULL; + int num_parents = parent_name ? 1 : 0; + const struct clk_ops *divider_ops = &clk_divider_ops; + struct clk_gate *gate; + struct clk_divider *divider; + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->reg = reg; + gate->bit_idx = DIV_ENABLE_SHIFT; + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + divider->reg = reg; + divider->shift = DIV_RATIO_SHIFT; + divider->width = DIV_RATIO_WIDTH; + + if (fixed) { + divider->flags |= CLK_DIVIDER_READ_ONLY; + divider_ops = &clk_divider_ro_ops; + } + + return clk_register_composite(dev, name, parent_names, num_parents, + NULL, NULL, ÷r->hw, divider_ops, + &gate->hw, &clk_gate_ops, flags); +} + +struct davinci_pllen_clk { + struct clk_hw hw; + void __iomem *base; +}; + +#define to_davinci_pllen_clk(_hw) \ + container_of((_hw), struct davinci_pllen_clk, hw) + +static const struct clk_ops davinci_pllen_ops = { + /* this clocks just uses the clock notification feature */ +}; + +/* + * The PLL has to be switched into bypass mode while we are chaning the rate, + * so we do that on the PLLEN clock since it is the end of the line. This will + * switch to bypass before any of the parent clocks (PREDIV, PLL, POSTDIV) are + * changed and will switch back to the PLL after the changes have been made. + */ +static int davinci_pllen_rate_change(struct notifier_block *nb, + unsigned long flags, void *data) +{ + struct clk_notifier_data *cnd = data; + struct clk_hw *hw = __clk_get_hw(cnd->clk); + struct davinci_pllen_clk *pll = to_davinci_pllen_clk(hw); + u32 ctrl; + + ctrl = readl(pll->base + PLLCTL); + + if (flags == PRE_RATE_CHANGE) { + /* Switch the PLL to bypass mode */ + ctrl &= ~(PLLCTL_PLLENSRC | PLLCTL_PLLEN); + writel(ctrl, pll->base + PLLCTL); + + udelay(PLL_BYPASS_TIME); + + /* Reset and enable PLL */ + ctrl &= ~(PLLCTL_PLLRST | PLLCTL_PLLDIS); + writel(ctrl, pll->base + PLLCTL); + } else { + udelay(PLL_RESET_TIME); + + /* Bring PLL out of reset */ + ctrl |= PLLCTL_PLLRST; + writel(ctrl, pll->base + PLLCTL); + + udelay(PLL_LOCK_TIME); + + /* Remove PLL from bypass mode */ + ctrl |= PLLCTL_PLLEN; + writel(ctrl, pll->base + PLLCTL); + } + + return NOTIFY_OK; +} + +static struct davinci_pll_platform_data *davinci_pll_get_pdata(struct device *dev) +{ + struct davinci_pll_platform_data *pdata = dev_get_platdata(dev); + + /* + * Platform data is optional, so allocate a new struct if one was not + * provided. For device tree, this will always be the case. + */ + if (!pdata) + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + /* for device tree, we need to fill in the struct */ + if (dev->of_node) + pdata->cfgchip = + syscon_regmap_lookup_by_compatible("ti,da830-cfgchip"); + + return pdata; +} + +static struct notifier_block davinci_pllen_notifier = { + .notifier_call = davinci_pllen_rate_change, +}; + +/** + * davinci_pll_clk_register - Register a PLL clock + * @info: The device-specific clock info + * @parent_name: The parent clock name + * @base: The PLL's memory region + * + * This creates a series of clocks that represent the PLL. + * + * OSCIN > [PREDIV >] PLL > [POSTDIV >] PLLEN + * + * - OSCIN is the parent clock (on secondary PLL, may come from primary PLL) + * - PREDIV and POSTDIV are optional (depends on the PLL controller) + * - PLL is the PLL output (aka PLLOUT) + * - PLLEN is the bypass multiplexer + * + * Returns: The PLLOUT clock or a negative error code. + */ +struct clk *davinci_pll_clk_register(struct device *dev, + const struct davinci_pll_clk_info *info, + const char *parent_name, + void __iomem *base) +{ + struct davinci_pll_platform_data *pdata; + char prediv_name[MAX_NAME_SIZE]; + char pllout_name[MAX_NAME_SIZE]; + char postdiv_name[MAX_NAME_SIZE]; + char pllen_name[MAX_NAME_SIZE]; + struct clk_init_data init; + struct davinci_pll_clk *pllout; + struct davinci_pllen_clk *pllen; + struct clk *pllout_clk, *clk; + + pdata = davinci_pll_get_pdata(dev); + if (!pdata) + return ERR_PTR(-ENOMEM); + + if (info->flags & PLL_HAS_CLKMODE) { + /* + * If a PLL has PLLCTL[CLKMODE], then it is the primary PLL. + * We register a clock named "oscin" that serves as the internal + * "input clock" domain shared by both PLLs (if there are 2) + * and will be the parent clock to the AUXCLK, SYSCLKBP and + * OBSCLK domains. NB: The various TRMs use "OSCIN" to mean + * a number of different things. In this driver we use it to + * mean the signal after the PLLCTL[CLKMODE] switch. + */ + clk = clk_register_fixed_factor(dev, OSCIN_CLK_NAME, + parent_name, 0, 1, 1); + if (IS_ERR(clk)) + return clk; + + parent_name = OSCIN_CLK_NAME; + } + + if (info->flags & PLL_HAS_PREDIV) { + bool fixed = info->flags & PLL_PREDIV_FIXED_DIV; + u32 flags = 0; + + snprintf(prediv_name, MAX_NAME_SIZE, "%s_prediv", info->name); + + if (info->flags & PLL_PREDIV_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + /* Some? DM355 chips don't correctly report the PREDIV value */ + if (info->flags & PLL_PREDIV_FIXED8) + clk = clk_register_fixed_factor(dev, prediv_name, + parent_name, flags, 1, 8); + else + clk = davinci_pll_div_register(dev, prediv_name, + parent_name, base + PREDIV, fixed, flags); + if (IS_ERR(clk)) + return clk; + + parent_name = prediv_name; + } + + /* Unlock writing to PLL registers */ + if (info->unlock_reg) { + if (IS_ERR_OR_NULL(pdata->cfgchip)) + dev_warn(dev, "Failed to get CFGCHIP (%ld)\n", + PTR_ERR(pdata->cfgchip)); + else + regmap_write_bits(pdata->cfgchip, info->unlock_reg, + info->unlock_mask, 0); + } + + pllout = devm_kzalloc(dev, sizeof(*pllout), GFP_KERNEL); + if (!pllout) + return ERR_PTR(-ENOMEM); + + snprintf(pllout_name, MAX_NAME_SIZE, "%s_pllout", info->name); + + init.name = pllout_name; + if (info->flags & PLL_PLLM_2X) + init.ops = &dm365_pll_ops; + else + init.ops = &davinci_pll_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = 0; + + if (info->flags & PLL_HAS_PREDIV) + init.flags |= CLK_SET_RATE_PARENT; + + pllout->hw.init = &init; + pllout->base = base; + pllout->pllm_mask = info->pllm_mask; + pllout->pllm_min = info->pllm_min; + pllout->pllm_max = info->pllm_max; + + pllout_clk = devm_clk_register(dev, &pllout->hw); + if (IS_ERR(pllout_clk)) + return pllout_clk; + + clk_hw_set_rate_range(&pllout->hw, info->pllout_min_rate, + info->pllout_max_rate); + + parent_name = pllout_name; + + if (info->flags & PLL_HAS_POSTDIV) { + bool fixed = info->flags & PLL_POSTDIV_FIXED_DIV; + u32 flags = CLK_SET_RATE_PARENT; + + snprintf(postdiv_name, MAX_NAME_SIZE, "%s_postdiv", info->name); + + if (info->flags & PLL_POSTDIV_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + clk = davinci_pll_div_register(dev, postdiv_name, parent_name, + base + POSTDIV, fixed, flags); + if (IS_ERR(clk)) + return clk; + + parent_name = postdiv_name; + } + + pllen = devm_kzalloc(dev, sizeof(*pllout), GFP_KERNEL); + if (!pllen) + return ERR_PTR(-ENOMEM); + + snprintf(pllen_name, MAX_NAME_SIZE, "%s_pllen", info->name); + + init.name = pllen_name; + init.ops = &davinci_pllen_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + pllen->hw.init = &init; + pllen->base = base; + + clk = devm_clk_register(dev, &pllen->hw); + if (IS_ERR(clk)) + return clk; + + clk_notifier_register(clk, &davinci_pllen_notifier); + + return pllout_clk; +} + +/** + * davinci_pll_auxclk_register - Register bypass clock (AUXCLK) + * @name: The clock name + * @base: The PLL memory region + */ +struct clk *davinci_pll_auxclk_register(struct device *dev, + const char *name, + void __iomem *base) +{ + return clk_register_gate(dev, name, OSCIN_CLK_NAME, 0, base + CKEN, + CKEN_AUXEN_SHIFT, 0, NULL); +} + +/** + * davinci_pll_sysclkbp_clk_register - Register bypass divider clock (SYSCLKBP) + * @name: The clock name + * @base: The PLL memory region + */ +struct clk *davinci_pll_sysclkbp_clk_register(struct device *dev, + const char *name, + void __iomem *base) +{ + return clk_register_divider(dev, name, OSCIN_CLK_NAME, 0, base + BPDIV, + DIV_RATIO_SHIFT, DIV_RATIO_WIDTH, + CLK_DIVIDER_READ_ONLY, NULL); +} + +/** + * davinci_pll_obsclk_register - Register oscillator divider clock (OBSCLK) + * @info: The clock info + * @base: The PLL memory region + */ +struct clk * +davinci_pll_obsclk_register(struct device *dev, + const struct davinci_pll_obsclk_info *info, + void __iomem *base) +{ + struct clk_mux *mux; + struct clk_gate *gate; + struct clk_divider *divider; + u32 oscdiv; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->reg = base + OCSEL; + mux->table = info->table; + mux->mask = info->ocsrc_mask; + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->reg = base + CKEN; + gate->bit_idx = CKEN_OBSCLK_SHIFT; + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + divider->reg = base + OSCDIV; + divider->shift = DIV_RATIO_SHIFT; + divider->width = DIV_RATIO_WIDTH; + + /* make sure divider is enabled just in case bootloader disabled it */ + oscdiv = readl(base + OSCDIV); + oscdiv |= BIT(DIV_ENABLE_SHIFT); + writel(oscdiv, base + OSCDIV); + + return clk_register_composite(dev, info->name, info->parent_names, + info->num_parents, + &mux->hw, &clk_mux_ops, + ÷r->hw, &clk_divider_ops, + &gate->hw, &clk_gate_ops, 0); +} + +/* The PLL SYSCLKn clocks have a mechanism for synchronizing rate changes. */ +static int davinci_pll_sysclk_rate_change(struct notifier_block *nb, + unsigned long flags, void *data) +{ + struct clk_notifier_data *cnd = data; + struct clk_hw *hw = __clk_get_hw(clk_get_parent(cnd->clk)); + struct davinci_pllen_clk *pll = to_davinci_pllen_clk(hw); + u32 pllcmd, pllstat; + + switch (flags) { + case POST_RATE_CHANGE: + /* apply the changes */ + pllcmd = readl(pll->base + PLLCMD); + pllcmd |= PLLCMD_GOSET; + writel(pllcmd, pll->base + PLLCMD); + /* fallthrough */ + case PRE_RATE_CHANGE: + /* Wait until for outstanding changes to take effect */ + do { + pllstat = readl(pll->base + PLLSTAT); + } while (pllstat & PLLSTAT_GOSTAT); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block davinci_pll_sysclk_notifier = { + .notifier_call = davinci_pll_sysclk_rate_change, +}; + +/** + * davinci_pll_sysclk_register - Register divider clocks (SYSCLKn) + * @info: The clock info + * @base: The PLL memory region + */ +struct clk * +davinci_pll_sysclk_register(struct device *dev, + const struct davinci_pll_sysclk_info *info, + void __iomem *base) +{ + const struct clk_ops *divider_ops = &clk_divider_ops; + struct clk_gate *gate; + struct clk_divider *divider; + struct clk *clk; + u32 reg; + u32 flags = 0; + + /* PLLDIVn registers are not entirely consecutive */ + if (info->id < 4) + reg = PLLDIV1 + 4 * (info->id - 1); + else + reg = PLLDIV4 + 4 * (info->id - 4); + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->reg = base + reg; + gate->bit_idx = DIV_ENABLE_SHIFT; + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + divider->reg = base + reg; + divider->shift = DIV_RATIO_SHIFT; + divider->width = info->ratio_width; + divider->flags = 0; + + if (info->flags & SYSCLK_FIXED_DIV) { + divider->flags |= CLK_DIVIDER_READ_ONLY; + divider_ops = &clk_divider_ro_ops; + } + + /* Only the ARM clock can change the parent PLL rate */ + if (info->flags & SYSCLK_ARM_RATE) + flags |= CLK_SET_RATE_PARENT; + + if (info->flags & SYSCLK_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + clk = clk_register_composite(dev, info->name, &info->parent_name, 1, + NULL, NULL, ÷r->hw, divider_ops, + &gate->hw, &clk_gate_ops, flags); + if (IS_ERR(clk)) + return clk; + + clk_notifier_register(clk, &davinci_pll_sysclk_notifier); + + return clk; +} + +int of_davinci_pll_init(struct device *dev, + const struct davinci_pll_clk_info *info, + const struct davinci_pll_obsclk_info *obsclk_info, + const struct davinci_pll_sysclk_info **div_info, + u8 max_sysclk_id, + void __iomem *base) +{ + struct device_node *node = dev->of_node; + struct device_node *child; + const char *parent_name; + struct clk *clk; + + if (info->flags & PLL_HAS_CLKMODE) + parent_name = of_clk_get_parent_name(node, 0); + else + parent_name = OSCIN_CLK_NAME; + + clk = davinci_pll_clk_register(dev, info, parent_name, base); + if (IS_ERR(clk)) { + dev_err(dev, "failed to register %s\n", info->name); + return PTR_ERR(clk); + } + + child = of_get_child_by_name(node, "pllout"); + if (of_device_is_available(child)) + of_clk_add_provider(child, of_clk_src_simple_get, clk); + of_node_put(child); + + child = of_get_child_by_name(node, "sysclk"); + if (of_device_is_available(child)) { + struct clk_onecell_data *clk_data; + struct clk **clks; + int n_clks = max_sysclk_id + 1; + int i; + + clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clks = devm_kmalloc_array(dev, n_clks, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + + clk_data->clks = clks; + clk_data->clk_num = n_clks; + + for (i = 0; i < n_clks; i++) + clks[i] = ERR_PTR(-ENOENT); + + for (; *div_info; div_info++) { + clk = davinci_pll_sysclk_register(dev, *div_info, base); + if (IS_ERR(clk)) + dev_warn(dev, "failed to register %s (%ld)\n", + (*div_info)->name, PTR_ERR(clk)); + else + clks[(*div_info)->id] = clk; + } + of_clk_add_provider(child, of_clk_src_onecell_get, clk_data); + } + of_node_put(child); + + child = of_get_child_by_name(node, "auxclk"); + if (of_device_is_available(child)) { + char child_name[MAX_NAME_SIZE]; + + snprintf(child_name, MAX_NAME_SIZE, "%s_auxclk", info->name); + + clk = davinci_pll_auxclk_register(dev, child_name, base); + if (IS_ERR(clk)) + dev_warn(dev, "failed to register %s (%ld)\n", + child_name, PTR_ERR(clk)); + else + of_clk_add_provider(child, of_clk_src_simple_get, clk); + } + of_node_put(child); + + child = of_get_child_by_name(node, "obsclk"); + if (of_device_is_available(child)) { + if (obsclk_info) + clk = davinci_pll_obsclk_register(dev, obsclk_info, base); + else + clk = ERR_PTR(-EINVAL); + + if (IS_ERR(clk)) + dev_warn(dev, "failed to register obsclk (%ld)\n", + PTR_ERR(clk)); + else + of_clk_add_provider(child, of_clk_src_simple_get, clk); + } + of_node_put(child); + + return 0; +} + +static const struct of_device_id davinci_pll_of_match[] = { + { } +}; + +static const struct platform_device_id davinci_pll_id_table[] = { + { } +}; + +typedef int (*davinci_pll_init)(struct device *dev, void __iomem *base); + +static int davinci_pll_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + davinci_pll_init pll_init = NULL; + struct resource *res; + void __iomem *base; + + of_id = of_match_device(davinci_pll_of_match, dev); + if (of_id) + pll_init = of_id->data; + else if (pdev->id_entry) + pll_init = (void *)pdev->id_entry->driver_data; + + if (!pll_init) { + dev_err(dev, "unable to find driver data\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(dev, "ioremap failed\n"); + return PTR_ERR(base); + } + + return pll_init(dev, base); +} + +static struct platform_driver davinci_pll_driver = { + .probe = davinci_pll_probe, + .driver = { + .name = "davinci-pll-clk", + .of_match_table = davinci_pll_of_match, + }, + .id_table = davinci_pll_id_table, +}; + +static int __init davinci_pll_driver_init(void) +{ + return platform_driver_register(&davinci_pll_driver); +} + +/* has to be postcore_initcall because PSC devices depend on PLL parent clocks */ +postcore_initcall(davinci_pll_driver_init); + +#ifdef CONFIG_DEBUG_FS +#include + +#define DEBUG_REG(n) \ +{ \ + .name = #n, \ + .offset = n, \ +} + +static const struct debugfs_reg32 davinci_pll_regs[] = { + DEBUG_REG(REVID), + DEBUG_REG(PLLCTL), + DEBUG_REG(OCSEL), + DEBUG_REG(PLLSECCTL), + DEBUG_REG(PLLM), + DEBUG_REG(PREDIV), + DEBUG_REG(PLLDIV1), + DEBUG_REG(PLLDIV2), + DEBUG_REG(PLLDIV3), + DEBUG_REG(OSCDIV), + DEBUG_REG(POSTDIV), + DEBUG_REG(BPDIV), + DEBUG_REG(PLLCMD), + DEBUG_REG(PLLSTAT), + DEBUG_REG(ALNCTL), + DEBUG_REG(DCHANGE), + DEBUG_REG(CKEN), + DEBUG_REG(CKSTAT), + DEBUG_REG(SYSTAT), + DEBUG_REG(PLLDIV4), + DEBUG_REG(PLLDIV5), + DEBUG_REG(PLLDIV6), + DEBUG_REG(PLLDIV7), + DEBUG_REG(PLLDIV8), + DEBUG_REG(PLLDIV9), +}; + +static int davinci_pll_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + struct debugfs_regset32 *regset; + struct dentry *d; + + regset = kzalloc(sizeof(*regset), GFP_KERNEL); + if (!regset) + return -ENOMEM; + + regset->regs = davinci_pll_regs; + regset->nregs = ARRAY_SIZE(davinci_pll_regs); + regset->base = pll->base; + + d = debugfs_create_regset32("registers", 0400, dentry, regset); + if (IS_ERR(d)) { + kfree(regset); + return PTR_ERR(d); + } + + return 0; +} +#endif diff --git a/drivers/clk/davinci/pll.h b/drivers/clk/davinci/pll.h new file mode 100644 index 000000000000..52103aeeceec --- /dev/null +++ b/drivers/clk/davinci/pll.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock driver for TI Davinci PSC controllers + * + * Copyright (C) 2018 David Lechner + */ + +#ifndef __CLK_DAVINCI_PLL_H___ +#define __CLK_DAVINCI_PLL_H___ + +#include +#include +#include +#include + +#define PLL_HAS_CLKMODE BIT(0) /* PLL has PLLCTL[CLKMODE] */ +#define PLL_HAS_PREDIV BIT(1) /* has prediv before PLL */ +#define PLL_PREDIV_ALWAYS_ENABLED BIT(2) /* don't clear DEN bit */ +#define PLL_PREDIV_FIXED_DIV BIT(3) /* fixed divider value */ +#define PLL_HAS_POSTDIV BIT(4) /* has postdiv after PLL */ +#define PLL_POSTDIV_ALWAYS_ENABLED BIT(5) /* don't clear DEN bit */ +#define PLL_POSTDIV_FIXED_DIV BIT(6) /* fixed divider value */ +#define PLL_HAS_EXTCLKSRC BIT(7) /* has selectable bypass */ +#define PLL_PLLM_2X BIT(8) /* PLLM value is 2x (DM365) */ +#define PLL_PREDIV_FIXED8 BIT(9) /* DM355 quirk */ + +/** davinci_pll_clk_info - controller-specific PLL info + * @name: The name of the PLL + * @unlock_reg: Option CFGCHIP register for unlocking PLL + * @unlock_mask: Bitmask used with @unlock_reg + * @pllm_mask: Bitmask for PLLM[PLLM] value + * @pllm_min: Minimum allowable value for PLLM[PLLM] + * @pllm_max: Maximum allowable value for PLLM[PLLM] + * @pllout_min_rate: Minimum allowable rate for PLLOUT + * @pllout_max_rate: Maximum allowable rate for PLLOUT + * @flags: Bitmap of PLL_* flags. + */ +struct davinci_pll_clk_info { + const char *name; + u32 unlock_reg; + u32 unlock_mask; + u32 pllm_mask; + u32 pllm_min; + u32 pllm_max; + unsigned long pllout_min_rate; + unsigned long pllout_max_rate; + u32 flags; +}; + +#define SYSCLK_ARM_RATE BIT(0) /* Controls ARM rate */ +#define SYSCLK_ALWAYS_ENABLED BIT(1) /* Or bad things happen */ +#define SYSCLK_FIXED_DIV BIT(2) /* Fixed divider */ + +/** davinci_pll_sysclk_info - SYSCLKn-specific info + * @name: The name of the clock + * @parent_name: The name of the parent clock + * @id: "n" in "SYSCLKn" + * @ratio_width: Width (in bits) of RATIO in PLLDIVn register + * @flags: Bitmap of SYSCLK_* flags. + */ +struct davinci_pll_sysclk_info { + const char *name; + const char *parent_name; + u32 id; + u32 ratio_width; + u32 flags; +}; + +#define SYSCLK(i, n, p, w, f) \ +static const struct davinci_pll_sysclk_info n = { \ + .name = #n, \ + .parent_name = #p, \ + .id = (i), \ + .ratio_width = (w), \ + .flags = (f), \ +} + +/** davinci_pll_obsclk_info - OBSCLK-specific info + * @name: The name of the clock + * @parent_names: Array of names of the parent clocks + * @num_parents: Length of @parent_names + * @table: Array of values to write to OCSEL[OCSRC] cooresponding to + * @parent_names + * @ocsrc_mask: Bitmask for OCSEL[OCSRC] + */ +struct davinci_pll_obsclk_info { + const char *name; + const char * const *parent_names; + u8 num_parents; + u32 *table; + u32 ocsrc_mask; +}; + +struct clk *davinci_pll_clk_register(struct device *dev, + const struct davinci_pll_clk_info *info, + const char *parent_name, + void __iomem *base); +struct clk *davinci_pll_auxclk_register(struct device *dev, + const char *name, + void __iomem *base); +struct clk *davinci_pll_sysclkbp_clk_register(struct device *dev, + const char *name, + void __iomem *base); +struct clk * +davinci_pll_obsclk_register(struct device *dev, + const struct davinci_pll_obsclk_info *info, + void __iomem *base); +struct clk * +davinci_pll_sysclk_register(struct device *dev, + const struct davinci_pll_sysclk_info *info, + void __iomem *base); + +int of_davinci_pll_init(struct device *dev, + const struct davinci_pll_clk_info *info, + const struct davinci_pll_obsclk_info *obsclk_info, + const struct davinci_pll_sysclk_info **div_info, + u8 max_sysclk_id, + void __iomem *base); + +#endif /* __CLK_DAVINCI_PLL_H___ */ diff --git a/include/linux/platform_data/clk-davinci-pll.h b/include/linux/platform_data/clk-davinci-pll.h new file mode 100644 index 000000000000..e55dab1d578b --- /dev/null +++ b/include/linux/platform_data/clk-davinci-pll.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PLL clock driver for TI Davinci SoCs + * + * Copyright (C) 2018 David Lechner + */ + +#ifndef __LINUX_PLATFORM_DATA_CLK_DAVINCI_PLL_H__ +#define __LINUX_PLATFORM_DATA_CLK_DAVINCI_PLL_H__ + +#include + +/** + * davinci_pll_platform_data + * @cfgchip: CFGCHIP syscon regmap + */ +struct davinci_pll_platform_data { + struct regmap *cfgchip; +}; + +#endif /* __LINUX_PLATFORM_DATA_CLK_DAVINCI_PLL_H__ */ -- cgit v1.2.3 From 1e88a8d64f221208801bb279ee7452df0b6d609f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 15 Mar 2018 21:52:34 -0500 Subject: clk: davinci: New driver for TI DA8XX CFGCHIP clocks This adds a new driver for the gate and multiplexer clocks in the CFGCHIPn syscon registers on TI DA8XX-type SoCs. Signed-off-by: David Lechner Signed-off-by: Stephen Boyd --- drivers/clk/davinci/Makefile | 2 + drivers/clk/davinci/da8xx-cfgchip.c | 439 ++++++++++++++++++++++++ include/linux/platform_data/clk-da8xx-cfgchip.h | 21 ++ 3 files changed, 462 insertions(+) create mode 100644 drivers/clk/davinci/da8xx-cfgchip.c create mode 100644 include/linux/platform_data/clk-da8xx-cfgchip.h (limited to 'include/linux') diff --git a/drivers/clk/davinci/Makefile b/drivers/clk/davinci/Makefile index 6c388d44162d..11178b79b483 100644 --- a/drivers/clk/davinci/Makefile +++ b/drivers/clk/davinci/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 ifeq ($(CONFIG_COMMON_CLK), y) +obj-$(CONFIG_ARCH_DAVINCI_DA8XX) += da8xx-cfgchip.o + obj-y += pll.o obj-$(CONFIG_ARCH_DAVINCI_DA830) += pll-da830.o obj-$(CONFIG_ARCH_DAVINCI_DA850) += pll-da850.o diff --git a/drivers/clk/davinci/da8xx-cfgchip.c b/drivers/clk/davinci/da8xx-cfgchip.c new file mode 100644 index 000000000000..880a77ace273 --- /dev/null +++ b/drivers/clk/davinci/da8xx-cfgchip.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock driver for DA8xx/AM17xx/AM18xx/OMAP-L13x CFGCHIP + * + * Copyright (C) 2018 David Lechner + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --- Gate clocks --- */ + +#define DA8XX_GATE_CLOCK_IS_DIV4P5 BIT(1) + +struct da8xx_cfgchip_gate_clk_info { + const char *name; + u32 cfgchip; + u32 bit; + u32 flags; +}; + +struct da8xx_cfgchip_gate_clk { + struct clk_hw hw; + struct regmap *regmap; + u32 reg; + u32 mask; +}; + +#define to_da8xx_cfgchip_gate_clk(_hw) \ + container_of((_hw), struct da8xx_cfgchip_gate_clk, hw) + +static int da8xx_cfgchip_gate_clk_enable(struct clk_hw *hw) +{ + struct da8xx_cfgchip_gate_clk *clk = to_da8xx_cfgchip_gate_clk(hw); + + return regmap_write_bits(clk->regmap, clk->reg, clk->mask, clk->mask); +} + +static void da8xx_cfgchip_gate_clk_disable(struct clk_hw *hw) +{ + struct da8xx_cfgchip_gate_clk *clk = to_da8xx_cfgchip_gate_clk(hw); + + regmap_write_bits(clk->regmap, clk->reg, clk->mask, 0); +} + +static int da8xx_cfgchip_gate_clk_is_enabled(struct clk_hw *hw) +{ + struct da8xx_cfgchip_gate_clk *clk = to_da8xx_cfgchip_gate_clk(hw); + unsigned int val; + + regmap_read(clk->regmap, clk->reg, &val); + + return !!(val & clk->mask); +} + +static unsigned long da8xx_cfgchip_div4p5_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + /* this clock divides by 4.5 */ + return parent_rate * 2 / 9; +} + +static const struct clk_ops da8xx_cfgchip_gate_clk_ops = { + .enable = da8xx_cfgchip_gate_clk_enable, + .disable = da8xx_cfgchip_gate_clk_disable, + .is_enabled = da8xx_cfgchip_gate_clk_is_enabled, +}; + +static const struct clk_ops da8xx_cfgchip_div4p5_clk_ops = { + .enable = da8xx_cfgchip_gate_clk_enable, + .disable = da8xx_cfgchip_gate_clk_disable, + .is_enabled = da8xx_cfgchip_gate_clk_is_enabled, + .recalc_rate = da8xx_cfgchip_div4p5_recalc_rate, +}; + +static struct da8xx_cfgchip_gate_clk * __init +da8xx_cfgchip_gate_clk_register(struct device *dev, + const struct da8xx_cfgchip_gate_clk_info *info, + struct regmap *regmap) +{ + struct clk *parent; + const char *parent_name; + struct da8xx_cfgchip_gate_clk *gate; + struct clk_init_data init; + int ret; + + parent = devm_clk_get(dev, NULL); + if (IS_ERR(parent)) + return ERR_CAST(parent); + + parent_name = __clk_get_name(parent); + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + init.name = info->name; + if (info->flags & DA8XX_GATE_CLOCK_IS_DIV4P5) + init.ops = &da8xx_cfgchip_div4p5_clk_ops; + else + init.ops = &da8xx_cfgchip_gate_clk_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = 0; + + gate->hw.init = &init; + gate->regmap = regmap; + gate->reg = info->cfgchip; + gate->mask = info->bit; + + ret = devm_clk_hw_register(dev, &gate->hw); + if (ret < 0) + return ERR_PTR(ret); + + return gate; +} + +static const struct da8xx_cfgchip_gate_clk_info da8xx_tbclksync_info __initconst = { + .name = "ehrpwm_tbclk", + .cfgchip = CFGCHIP(1), + .bit = CFGCHIP1_TBCLKSYNC, +}; + +static int __init da8xx_cfgchip_register_tbclk(struct device *dev, + struct regmap *regmap) +{ + struct da8xx_cfgchip_gate_clk *gate; + + gate = da8xx_cfgchip_gate_clk_register(dev, &da8xx_tbclksync_info, + regmap); + if (IS_ERR(gate)) + return PTR_ERR(gate); + + clk_hw_register_clkdev(&gate->hw, "tbclk", "ehrpwm.0"); + clk_hw_register_clkdev(&gate->hw, "tbclk", "ehrpwm.1"); + + return 0; +} + +static const struct da8xx_cfgchip_gate_clk_info da8xx_div4p5ena_info __initconst = { + .name = "div4.5", + .cfgchip = CFGCHIP(3), + .bit = CFGCHIP3_DIV45PENA, + .flags = DA8XX_GATE_CLOCK_IS_DIV4P5, +}; + +static int __init da8xx_cfgchip_register_div4p5(struct device *dev, + struct regmap *regmap) +{ + struct da8xx_cfgchip_gate_clk *gate; + + gate = da8xx_cfgchip_gate_clk_register(dev, &da8xx_div4p5ena_info, regmap); + if (IS_ERR(gate)) + return PTR_ERR(gate); + + return 0; +} + +static int __init +of_da8xx_cfgchip_gate_clk_init(struct device *dev, + const struct da8xx_cfgchip_gate_clk_info *info, + struct regmap *regmap) +{ + struct da8xx_cfgchip_gate_clk *gate; + + gate = da8xx_cfgchip_gate_clk_register(dev, info, regmap); + if (IS_ERR(gate)) + return PTR_ERR(gate); + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, gate); +} + +static int __init of_da8xx_tbclksync_init(struct device *dev, + struct regmap *regmap) +{ + return of_da8xx_cfgchip_gate_clk_init(dev, &da8xx_tbclksync_info, regmap); +} + +static int __init of_da8xx_div4p5ena_init(struct device *dev, + struct regmap *regmap) +{ + return of_da8xx_cfgchip_gate_clk_init(dev, &da8xx_div4p5ena_info, regmap); +} + +/* --- MUX clocks --- */ + +struct da8xx_cfgchip_mux_clk_info { + const char *name; + const char *parent0; + const char *parent1; + u32 cfgchip; + u32 bit; +}; + +struct da8xx_cfgchip_mux_clk { + struct clk_hw hw; + struct regmap *regmap; + u32 reg; + u32 mask; +}; + +#define to_da8xx_cfgchip_mux_clk(_hw) \ + container_of((_hw), struct da8xx_cfgchip_mux_clk, hw) + +static int da8xx_cfgchip_mux_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct da8xx_cfgchip_mux_clk *clk = to_da8xx_cfgchip_mux_clk(hw); + unsigned int val = index ? clk->mask : 0; + + return regmap_write_bits(clk->regmap, clk->reg, clk->mask, val); +} + +static u8 da8xx_cfgchip_mux_clk_get_parent(struct clk_hw *hw) +{ + struct da8xx_cfgchip_mux_clk *clk = to_da8xx_cfgchip_mux_clk(hw); + unsigned int val; + + regmap_read(clk->regmap, clk->reg, &val); + + return (val & clk->mask) ? 1 : 0; +} + +static const struct clk_ops da8xx_cfgchip_mux_clk_ops = { + .set_parent = da8xx_cfgchip_mux_clk_set_parent, + .get_parent = da8xx_cfgchip_mux_clk_get_parent, +}; + +static struct da8xx_cfgchip_mux_clk * __init +da8xx_cfgchip_mux_clk_register(struct device *dev, + const struct da8xx_cfgchip_mux_clk_info *info, + struct regmap *regmap) +{ + const char * const parent_names[] = { info->parent0, info->parent1 }; + struct da8xx_cfgchip_mux_clk *mux; + struct clk_init_data init; + int ret; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + init.name = info->name; + init.ops = &da8xx_cfgchip_mux_clk_ops; + init.parent_names = parent_names; + init.num_parents = 2; + init.flags = 0; + + mux->hw.init = &init; + mux->regmap = regmap; + mux->reg = info->cfgchip; + mux->mask = info->bit; + + ret = devm_clk_hw_register(dev, &mux->hw); + if (ret < 0) + return ERR_PTR(ret); + + return mux; +} + +static const struct da8xx_cfgchip_mux_clk_info da850_async1_info __initconst = { + .name = "async1", + .parent0 = "pll0_sysclk3", + .parent1 = "div4.5", + .cfgchip = CFGCHIP(3), + .bit = CFGCHIP3_EMA_CLKSRC, +}; + +static int __init da8xx_cfgchip_register_async1(struct device *dev, + struct regmap *regmap) +{ + struct da8xx_cfgchip_mux_clk *mux; + + mux = da8xx_cfgchip_mux_clk_register(dev, &da850_async1_info, regmap); + if (IS_ERR(mux)) + return PTR_ERR(mux); + + clk_hw_register_clkdev(&mux->hw, "async1", "da850-psc0"); + + return 0; +} + +static const struct da8xx_cfgchip_mux_clk_info da850_async3_info __initconst = { + .name = "async3", + .parent0 = "pll0_sysclk2", + .parent1 = "pll1_sysclk2", + .cfgchip = CFGCHIP(3), + .bit = CFGCHIP3_ASYNC3_CLKSRC, +}; + +static int __init da850_cfgchip_register_async3(struct device *dev, + struct regmap *regmap) +{ + struct da8xx_cfgchip_mux_clk *mux; + struct clk_hw *parent; + + mux = da8xx_cfgchip_mux_clk_register(dev, &da850_async3_info, regmap); + if (IS_ERR(mux)) + return PTR_ERR(mux); + + clk_hw_register_clkdev(&mux->hw, "async3", "da850-psc1"); + + /* pll1_sysclk2 is not affected by CPU scaling, so use it for async3 */ + parent = clk_hw_get_parent_by_index(&mux->hw, 1); + if (parent) + clk_set_parent(mux->hw.clk, parent->clk); + else + dev_warn(dev, "Failed to find async3 parent clock\n"); + + return 0; +} + +static int __init +of_da8xx_cfgchip_init_mux_clock(struct device *dev, + const struct da8xx_cfgchip_mux_clk_info *info, + struct regmap *regmap) +{ + struct da8xx_cfgchip_mux_clk *mux; + + mux = da8xx_cfgchip_mux_clk_register(dev, info, regmap); + if (IS_ERR(mux)) + return PTR_ERR(mux); + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &mux->hw); +} + +static int __init of_da850_async1_init(struct device *dev, struct regmap *regmap) +{ + return of_da8xx_cfgchip_init_mux_clock(dev, &da850_async1_info, regmap); +} + +static int __init of_da850_async3_init(struct device *dev, struct regmap *regmap) +{ + return of_da8xx_cfgchip_init_mux_clock(dev, &da850_async3_info, regmap); +} + +/* --- platform device --- */ + +static const struct of_device_id da8xx_cfgchip_of_match[] = { + { + .compatible = "ti,da830-tbclksync", + .data = of_da8xx_tbclksync_init, + }, + { + .compatible = "ti,da830-div4p5ena", + .data = of_da8xx_div4p5ena_init, + }, + { + .compatible = "ti,da850-async1-clksrc", + .data = of_da850_async1_init, + }, + { + .compatible = "ti,da850-async3-clksrc", + .data = of_da850_async3_init, + }, + { } +}; + +static const struct platform_device_id da8xx_cfgchip_id_table[] = { + { + .name = "da830-tbclksync", + .driver_data = (kernel_ulong_t)da8xx_cfgchip_register_tbclk, + }, + { + .name = "da830-div4p5ena", + .driver_data = (kernel_ulong_t)da8xx_cfgchip_register_div4p5, + }, + { + .name = "da850-async1-clksrc", + .driver_data = (kernel_ulong_t)da8xx_cfgchip_register_async1, + }, + { + .name = "da850-async3-clksrc", + .driver_data = (kernel_ulong_t)da850_cfgchip_register_async3, + }, + { } +}; + +typedef int (*da8xx_cfgchip_init)(struct device *dev, struct regmap *regmap); + +static int da8xx_cfgchip_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da8xx_cfgchip_clk_platform_data *pdata = dev->platform_data; + const struct of_device_id *of_id; + da8xx_cfgchip_init clk_init = NULL; + struct regmap *regmap = NULL; + + of_id = of_match_device(da8xx_cfgchip_of_match, dev); + if (of_id) { + struct device_node *parent; + + clk_init = of_id->data; + parent = of_get_parent(dev->of_node); + regmap = syscon_node_to_regmap(parent); + of_node_put(parent); + } else if (pdev->id_entry && pdata) { + clk_init = (void *)pdev->id_entry->driver_data; + regmap = pdata->cfgchip; + } + + if (!clk_init) { + dev_err(dev, "unable to find driver data\n"); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(regmap)) { + dev_err(dev, "no regmap for CFGCHIP syscon\n"); + return regmap ? PTR_ERR(regmap) : -ENOENT; + } + + return clk_init(dev, regmap); +} + +static struct platform_driver da8xx_cfgchip_driver = { + .probe = da8xx_cfgchip_probe, + .driver = { + .name = "da8xx-cfgchip-clk", + .of_match_table = da8xx_cfgchip_of_match, + }, + .id_table = da8xx_cfgchip_id_table, +}; + +static int __init da8xx_cfgchip_driver_init(void) +{ + return platform_driver_register(&da8xx_cfgchip_driver); +} + +/* has to be postcore_initcall because PSC devices depend on the async3 clock */ +postcore_initcall(da8xx_cfgchip_driver_init); diff --git a/include/linux/platform_data/clk-da8xx-cfgchip.h b/include/linux/platform_data/clk-da8xx-cfgchip.h new file mode 100644 index 000000000000..de0f77d38669 --- /dev/null +++ b/include/linux/platform_data/clk-da8xx-cfgchip.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * clk-da8xx-cfgchip - TI DaVinci DA8xx CFGCHIP clock driver + * + * Copyright (C) 2018 David Lechner + */ + +#ifndef __LINUX_PLATFORM_DATA_CLK_DA8XX_CFGCHIP_H__ +#define __LINUX_PLATFORM_DATA_CLK_DA8XX_CFGCHIP_H__ + +#include + +/** + * da8xx_cfgchip_clk_platform_data + * @cfgchip: CFGCHIP syscon regmap + */ +struct da8xx_cfgchip_clk_platform_data { + struct regmap *cfgchip; +}; + +#endif /* __LINUX_PLATFORM_DATA_CLK_DA8XX_CFGCHIP_H__ */ -- cgit v1.2.3 From 8ea229511e06f9635ecc338dcbe0db41a73623f0 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Apr 2018 16:26:25 +0530 Subject: thermal: Add cooling device's statistics in sysfs This extends the sysfs interface for thermal cooling devices and exposes some pretty useful statistics. These statistics have proven to be quite useful specially while doing benchmarks related to the task scheduler, where we want to make sure that nothing has disrupted the test, specially the cooling device which may have put constraints on the CPUs. The information exposed here tells us to what extent the CPUs were constrained by the thermal framework. The write-only "reset" file is used to reset the statistics. The read-only "time_in_state_ms" file shows the time (in msec) spent by the device in the respective cooling states, and it prints one line per cooling state. The read-only "total_trans" file shows single positive integer value showing the total number of cooling state transitions the device has gone through since the time the cooling device is registered or the time when statistics were reset last. The read-only "trans_table" file shows a two dimensional matrix, where an entry (row i, column j) represents the number of transitions from State_i to State_j. This is how the directory structure looks like for a single cooling device: $ ls -R /sys/class/thermal/cooling_device0/ /sys/class/thermal/cooling_device0/: cur_state max_state power stats subsystem type uevent /sys/class/thermal/cooling_device0/power: autosuspend_delay_ms runtime_active_time runtime_suspended_time control runtime_status /sys/class/thermal/cooling_device0/stats: reset time_in_state_ms total_trans trans_table This is tested on ARM 64-bit Hisilicon hikey620 board running Ubuntu and ARM 64-bit Hisilicon hikey960 board running Android. Signed-off-by: Viresh Kumar Signed-off-by: Zhang Rui --- Documentation/thermal/sysfs-api.txt | 31 +++++ drivers/thermal/Kconfig | 7 ++ drivers/thermal/thermal_core.c | 3 +- drivers/thermal/thermal_core.h | 10 ++ drivers/thermal/thermal_helpers.c | 5 +- drivers/thermal/thermal_sysfs.c | 225 ++++++++++++++++++++++++++++++++++++ include/linux/thermal.h | 1 + 7 files changed, 280 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index bb9a0a53e76b..911399730c1c 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -255,6 +255,7 @@ temperature) and throttle appropriate devices. 2. sysfs attributes structure RO read only value +WO write only value RW read/write value Thermal sysfs attributes will be represented under /sys/class/thermal. @@ -286,6 +287,11 @@ Thermal cooling device sys I/F, created once it's registered: |---type: Type of the cooling device(processor/fan/...) |---max_state: Maximum cooling state of the cooling device |---cur_state: Current cooling state of the cooling device + |---stats: Directory containing cooling device's statistics + |---stats/reset: Writing any value resets the statistics + |---stats/time_in_state_ms: Time (msec) spent in various cooling states + |---stats/total_trans: Total number of times cooling state is changed + |---stats/trans_table: Cooing state transition table Then next two dynamic attributes are created/removed in pairs. They represent @@ -490,6 +496,31 @@ cur_state - cur_state == max_state means the maximum cooling. RW, Required +stats/reset + Writing any value resets the cooling device's statistics. + WO, Required + +stats/time_in_state_ms: + The amount of time spent by the cooling device in various cooling + states. The output will have "