diff options
| -rw-r--r-- | drivers/clk/renesas/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/clk/renesas/r9a09g077-cpg.c | 371 |
2 files changed, 373 insertions, 0 deletions
diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig index 7659550b8566..5c0238e878b7 100644 --- a/drivers/clk/renesas/Kconfig +++ b/drivers/clk/renesas/Kconfig @@ -218,10 +218,12 @@ config CLK_R9A09G057 config CLK_R9A09G077 bool "RZ/T2H clock support" if COMPILE_TEST select CLK_RENESAS_CPG_MSSR + select CLK_RZV2H_CPG_LIB config CLK_R9A09G087 bool "RZ/N2H clock support" if COMPILE_TEST select CLK_RENESAS_CPG_MSSR + select CLK_RZV2H_CPG_LIB config CLK_SH73A0 bool "SH-Mobile AG5 clock support" if COMPILE_TEST diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c index e8f760776c99..5640c2035e5a 100644 --- a/drivers/clk/renesas/r9a09g077-cpg.c +++ b/drivers/clk/renesas/r9a09g077-cpg.c @@ -8,16 +8,23 @@ #include <linux/bitfield.h> #include <linux/clk-provider.h> +#include <linux/clk/renesas.h> #include <linux/device.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/math.h> +#include <linux/module.h> #include <linux/types.h> +#include <linux/units.h> #include <dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h> #include <dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h> #include "renesas-cpg-mssr.h" +MODULE_IMPORT_NS("RZV2H_CPG"); + #define RZT2H_REG_BLOCK_SHIFT 11 #define RZT2H_REG_OFFSET_MASK GENMASK(10, 0) #define RZT2H_REG_CONF(block, offset) (((block) << RZT2H_REG_BLOCK_SHIFT) | \ @@ -66,11 +73,26 @@ #define DIVSCI2ASYNC CONF_PACK(SCKCR3, 10, 2) #define DIVSCI3ASYNC CONF_PACK(SCKCR3, 12, 2) #define DIVSCI4ASYNC CONF_PACK(SCKCR3, 14, 2) +#define LCDCDIVSEL CONF_PACK(SCKCR3, 20, 4) + +#define PLL3EN FIELD_PREP_CONST(OFFSET_MASK, (0xc0)) + +#define CPG_PLL_EN_EN BIT(0) +#define CPG_PLL3_VCO_CTR0(x) ((x) + 0x4) +#define CPG_PLL3_VCO_CTR0_PDIV GENMASK(21, 16) +#define CPG_PLL3_VCO_CTR0_MDIV GENMASK(9, 0) +#define CPG_PLL3_VCO_CTR1(x) ((x) + 0x8) +#define CPG_PLL3_VCO_CTR1_KDIV GENMASK(31, 16) +#define CPG_PLL3_VCO_CTR1_SDIV GENMASK(2, 0) +#define CPG_PLL_MON(x) ((x) - 0x10) +#define CPG_PLL_MON_LOCK BIT(0) enum rzt2h_clk_types { CLK_TYPE_RZT2H_DIV = CLK_TYPE_CUSTOM, /* Clock with divider */ CLK_TYPE_RZT2H_MUX, /* Clock with clock source selector */ CLK_TYPE_RZT2H_FSELXSPI, /* Clock with FSELXSPIn source selector */ + CLK_TYPE_RZT2H_PLL3, /* PLL3 Clock */ + CLK_TYPE_RZT2H_LCDCDIV, /* LCDC divider clock */ }; #define DEF_DIV(_name, _id, _parent, _conf, _dtable) \ @@ -83,6 +105,47 @@ enum rzt2h_clk_types { #define DEF_DIV_FSELXSPI(_name, _id, _parent, _conf, _dtable) \ DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_FSELXSPI, .conf = _conf, \ .parent = _parent, .dtable = _dtable, .flag = 0) +#define DEF_PLL3(_name, _id, _parent, _conf) \ + DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_PLL3, .conf = _conf, \ + .parent = _parent) +#define DEF_DIV_LCDC(_name, _id, _parent, _conf, _dtable) \ + DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_LCDCDIV, .conf = _conf, \ + .parent = _parent, .dtable = _dtable, .flag = CLK_SET_RATE_PARENT) + +struct pll_clk { + void __iomem *reg; + const struct rzv2h_pll_limits *limits; + struct device *dev; + struct rzv2h_pll_pars pll_parameters; + struct clk_hw hw; + unsigned long cur_rate; +}; + +#define to_pll(_hw) container_of(_hw, struct pll_clk, hw) + +struct r9a09g077_lcdc_div_clk { + const struct clk_div_table *dtable; + void __iomem *reg; + struct device *dev; + struct clk_hw hw; + u32 conf; + u8 divider; +}; + +#define to_lcdc_div_clk(_hw) \ + container_of(_hw, struct r9a09g077_lcdc_div_clk, hw) + +#define RZT2H_MAX_LCDC_DIV_TABLES 16 + +static const struct rzv2h_pll_limits r9a09g077_cpg_pll3_limits = { + .input_fref = 48 * MEGA, + .fout = { .min = 25 * MEGA, .max = 430 * MEGA }, + .fvco = { .min = 1600 * MEGA, .max = 3200 * MEGA }, + .m = { .min = 0x40, .max = 0x3ff }, + .p = { .min = 0x2, .max = 0x8 }, + .s = { .min = 0x0, .max = 0x6 }, + .k = { .min = -32768, .max = 32767 }, +}; enum clk_ids { /* Core Clock Outputs exported to DT */ @@ -96,10 +159,12 @@ enum clk_ids { CLK_PLL0, CLK_PLL1, CLK_PLL2, + CLK_PLL3, CLK_PLL4, CLK_SEL_CLK_PLL0, CLK_SEL_CLK_PLL1, CLK_SEL_CLK_PLL2, + CLK_SEL_CLK_PLL3, CLK_SEL_CLK_PLL4, CLK_PLL4D1, CLK_PLL4D1_DIV3, @@ -107,6 +172,7 @@ enum clk_ids { CLK_PLL4D3, CLK_PLL4D3_DIV10, CLK_PLL4D3_DIV20, + CLK_PLL4D50, CLK_SCI0ASYNC, CLK_SCI1ASYNC, CLK_SCI2ASYNC, @@ -119,6 +185,7 @@ enum clk_ids { CLK_SPI3ASYNC, CLK_DIVSELXSPI0_SCKCR, CLK_DIVSELXSPI1_SCKCR, + CLK_LCDDIVSEL, /* Module Clocks */ MOD_CLK_BASE, @@ -130,6 +197,26 @@ static const struct clk_div_table dtable_1_2[] = { {0, 0}, }; +static const struct clk_div_table dtable_2_32[] = { + {0, 2}, + {1, 4}, + {2, 6}, + {3, 8}, + {4, 10}, + {5, 12}, + {6, 14}, + {7, 16}, + {8, 18}, + {9, 20}, + {10, 22}, + {11, 24}, + {12, 26}, + {13, 28}, + {14, 30}, + {15, 32}, + {0, 0}, +}; + static const struct clk_div_table dtable_6_8_16_32_64[] = { {6, 64}, {5, 32}, @@ -152,6 +239,7 @@ static const struct clk_div_table dtable_24_25_30_32[] = { static const char * const sel_clk_pll0[] = { ".loco", ".pll0" }; static const char * const sel_clk_pll1[] = { ".loco", ".pll1" }; static const char * const sel_clk_pll2[] = { ".loco", ".pll2" }; +static const char * const sel_clk_pll3[] = { ".loco", ".pll3" }; static const char * const sel_clk_pll4[] = { ".loco", ".pll4" }; static const char * const sel_clk_pll4d1_div3_div4[] = { ".pll4d1_div3", ".pll4d1_div4" }; static const char * const sel_clk_pll4d3_div10_div20[] = { ".pll4d3_div10", ".pll4d3_div20" }; @@ -173,10 +261,14 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { sel_clk_pll1, ARRAY_SIZE(sel_clk_pll1), CLK_MUX_READ_ONLY), DEF_MUX(".sel_clk_pll2", CLK_SEL_CLK_PLL2, SEL_PLL, sel_clk_pll2, ARRAY_SIZE(sel_clk_pll2), CLK_MUX_READ_ONLY), + DEF_MUX(".sel_clk_pll3", CLK_SEL_CLK_PLL3, SEL_PLL, + sel_clk_pll3, ARRAY_SIZE(sel_clk_pll3), CLK_MUX_READ_ONLY), DEF_MUX(".sel_clk_pll4", CLK_SEL_CLK_PLL4, SEL_PLL, sel_clk_pll4, ARRAY_SIZE(sel_clk_pll4), CLK_MUX_READ_ONLY), DEF_FIXED(".pll4d1", CLK_PLL4D1, CLK_SEL_CLK_PLL4, 1, 1), + DEF_FIXED(".pll4d50", CLK_PLL4D50, CLK_SEL_CLK_PLL4, 50, 1), + DEF_PLL3(".pll3", CLK_PLL3, CLK_PLL4D50, PLL3EN), DEF_FIXED(".pll4d1_div3", CLK_PLL4D1_DIV3, CLK_PLL4D1, 3, 1), DEF_FIXED(".pll4d1_div4", CLK_PLL4D1_DIV4, CLK_PLL4D1, 4, 1), DEF_FIXED(".pll4d3", CLK_PLL4D3, CLK_SEL_CLK_PLL4, 3, 1), @@ -229,6 +321,7 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { DEF_FIXED("PCLKL", R9A09G077_CLK_PCLKL, CLK_SEL_CLK_PLL1, 16, 1), DEF_FIXED("PCLKAH", R9A09G077_CLK_PCLKAH, CLK_PLL4D1, 6, 1), DEF_FIXED("PCLKAM", R9A09G077_CLK_PCLKAM, CLK_PLL4D1, 12, 1), + DEF_FIXED("PCLKAL", R9A09G077_CLK_PCLKAL, CLK_PLL4D1, 24, 1), DEF_FIXED("SDHI_CLKHS", R9A09G077_SDHI_CLKHS, CLK_SEL_CLK_PLL2, 1, 1), DEF_FIXED("USB_CLK", R9A09G077_USB_CLK, CLK_PLL4D1, 48, 1), DEF_FIXED("ETCLKA", R9A09G077_ETCLKA, CLK_SEL_CLK_PLL1, 5, 1), @@ -242,6 +335,8 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { FSELXSPI1, dtable_6_8_16_32_64), DEF_MUX("PCLKCAN", R9A09G077_PCLKCAN, FSELCANFD, sel_clk_pll4d3_div10_div20, ARRAY_SIZE(sel_clk_pll4d3_div10_div20), 0), + DEF_DIV_LCDC("LCDC_CLKD", R9A09G077_LCDC_CLKD, CLK_SEL_CLK_PLL3, LCDCDIVSEL, + dtable_2_32), DEF_FIXED("PCLKRTC", R9A09G077_PCLKRTC, CLK_EXTAL, 128, 1), }; @@ -274,6 +369,7 @@ static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = { DEF_MOD("iic2", 601, R9A09G077_CLK_PCLKL), DEF_MOD("spi3", 602, CLK_SPI3ASYNC), DEF_MOD("rtc", 605, R9A09G077_CLK_PCLKL), + DEF_MOD("lcdc", 1204, R9A09G077_CLK_PCLKAL), DEF_MOD("sdhi0", 1212, R9A09G077_CLK_PCLKAM), DEF_MOD("sdhi1", 1213, R9A09G077_CLK_PCLKAM), }; @@ -483,6 +579,276 @@ r9a09g077_cpg_fselxspi_div_clk_register(struct device *dev, return hw->clk; } +static unsigned long r9a09g077_cpg_pll3_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk = to_pll(hw); + u32 ctr0, ctr1; + u8 pdiv, sdiv; + u64 rate; + u16 mdiv; + s16 kdiv; + + ctr0 = readl(CPG_PLL3_VCO_CTR0(pll_clk->reg)); + ctr1 = readl(CPG_PLL3_VCO_CTR1(pll_clk->reg)); + + pdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_PDIV, ctr0); + mdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_MDIV, ctr0); + kdiv = (s16)FIELD_GET(CPG_PLL3_VCO_CTR1_KDIV, ctr1); + sdiv = FIELD_GET(CPG_PLL3_VCO_CTR1_SDIV, ctr1); + + rate = mul_u64_u32_shr(parent_rate, (mdiv << 16) + kdiv, 16 + sdiv); + + return DIV_ROUND_CLOSEST_ULL(rate, pdiv); +} + +static int r9a09g077_cpg_pll3_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct pll_clk *pll_clk = to_pll(hw); + u64 rate_millihz; + + if (req->rate == pll_clk->cur_rate) + return 0; + + rate_millihz = mul_u32_u32(req->rate, MILLI); + if (!rzv2h_cpg_get_pll_pars(pll_clk->limits, &pll_clk->pll_parameters, + rate_millihz)) { + dev_dbg(pll_clk->dev, + "failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + req->rate = DIV_ROUND_CLOSEST_ULL(pll_clk->pll_parameters.freq_millihz, MILLI); + pll_clk->cur_rate = req->rate; + + return 0; +} + +static int r9a09g077_cpg_pll3_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk = to_pll(hw); + struct rzv2h_pll_pars *params = &pll_clk->pll_parameters; + void __iomem *offset = pll_clk->reg; + u32 val; + int ret; + + /* Put PLL into standby mode */ + writel(0, offset); + ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset), + val, !(val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + dev_err(pll_clk->dev, "Failed to put PLL into standby mode"); + return ret; + } + + /* Output clock setting 1 */ + val = readl(CPG_PLL3_VCO_CTR0(offset)); + FIELD_MODIFY(CPG_PLL3_VCO_CTR0_MDIV, &val, params->m); + FIELD_MODIFY(CPG_PLL3_VCO_CTR0_PDIV, &val, params->p); + writel(val, CPG_PLL3_VCO_CTR0(offset)); + + /* Output clock setting 2 */ + val = readl(CPG_PLL3_VCO_CTR1(offset)); + FIELD_MODIFY(CPG_PLL3_VCO_CTR1_KDIV, &val, params->k); + FIELD_MODIFY(CPG_PLL3_VCO_CTR1_SDIV, &val, params->s); + writel(val, CPG_PLL3_VCO_CTR1(offset)); + + writel(CPG_PLL_EN_EN, offset); + + /* PLL normal mode transition, output clock stability check */ + ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset), + val, (val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + writel(0, offset); + dev_err(pll_clk->dev, "Failed to put PLL into normal mode"); + return ret; + } + + return 0; +} + +static const struct clk_ops r9a09g077_cpg_pll3_ops = { + .recalc_rate = r9a09g077_cpg_pll3_clk_recalc_rate, + .determine_rate = r9a09g077_cpg_pll3_determine_rate, + .set_rate = r9a09g077_cpg_pll3_set_rate, +}; + +static struct clk * __init +r9a09g077_cpg_pll3_clk_register(struct device *dev, + const struct cpg_core_clk *core, + void __iomem *addr, + struct cpg_mssr_pub *pub, + const struct rzv2h_pll_limits *limits) +{ + struct clk_init_data init = {}; + const struct clk *parent; + const char *parent_name; + struct pll_clk *pll_clk; + int ret; + + parent = pub->clks[core->parent]; + if (IS_ERR(parent)) + return ERR_CAST(parent); + + pll_clk = devm_kzalloc(dev, sizeof(*pll_clk), GFP_KERNEL); + if (!pll_clk) + return ERR_PTR(-ENOMEM); + + parent_name = __clk_get_name(parent); + init.name = core->name; + init.ops = &r9a09g077_cpg_pll3_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + + pll_clk->dev = dev; + pll_clk->hw.init = &init; + pll_clk->reg = addr; + pll_clk->limits = limits; + + ret = devm_clk_hw_register(dev, &pll_clk->hw); + if (ret) + return ERR_PTR(ret); + + return pll_clk->hw.clk; +} + +static int r9a09g077_cpg_lcdc_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + struct clk_hw *mux_hw = clk_hw_get_parent(hw); + u8 table[RZT2H_MAX_LCDC_DIV_TABLES] = { 0 }; + struct rzv2h_pll_div_pars dsi_params; + const struct clk_div_table *div; + struct pll_clk *pll_clk; + unsigned int i = 0; + u64 freq_millihz; + + /* index 1 is always .pll3 in sel_clk_pll3[] */ + pll_clk = to_pll(clk_hw_get_parent_by_index(mux_hw, 1)); + + for (div = dsi_div->dtable; div->div; div++) { + if (i >= RZT2H_MAX_LCDC_DIV_TABLES) + return -EINVAL; + table[i++] = div->div; + } + + freq_millihz = mul_u32_u32(req->rate, MILLI); + + if (!rzv2h_cpg_get_pll_divs_pars(pll_clk->limits, &dsi_params, table, + i, freq_millihz)) { + dev_err(dsi_div->dev, + "LCDC divider failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + + req->rate = DIV_ROUND_CLOSEST_ULL(dsi_params.div.freq_millihz, MILLI); + req->best_parent_rate = req->rate * dsi_params.div.divider_value; + dsi_div->divider = dsi_params.div.divider_value; + pll_clk->cur_rate = req->best_parent_rate; + pll_clk->pll_parameters = dsi_params.pll; + + return 0; +} + +static int r9a09g077_cpg_lcdc_div_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + const struct clk_div_table *clkt; + bool divider_found = false; + u32 val, shift; + + for (clkt = dsi_div->dtable; clkt->div; clkt++) { + if (clkt->div == dsi_div->divider) { + divider_found = true; + break; + } + } + + if (!divider_found) + return -EINVAL; + + shift = GET_SHIFT(dsi_div->conf); + val = readl(dsi_div->reg); + val &= ~(clk_div_mask(GET_WIDTH(dsi_div->conf)) << shift); + val |= clkt->val << shift; + writel(val, dsi_div->reg); + + return 0; +} + +static unsigned long +r9a09g077_cpg_lcdc_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + u32 div; + + div = readl(dsi_div->reg); + div >>= GET_SHIFT(dsi_div->conf); + div &= clk_div_mask(GET_WIDTH(dsi_div->conf)); + div = dsi_div->dtable[div].div; + + return DIV_ROUND_CLOSEST_ULL(parent_rate, div); +} + +static const struct clk_ops r9a09g077_cpg_lcdc_div_ops = { + .recalc_rate = r9a09g077_cpg_lcdc_div_recalc_rate, + .determine_rate = r9a09g077_cpg_lcdc_div_determine_rate, + .set_rate = r9a09g077_cpg_lcdc_div_set_rate, +}; + +static struct clk * __init +r9a09g077_cpg_lcdc_div_clk_register(struct device *dev, + const struct cpg_core_clk *core, + void __iomem *addr, + struct cpg_mssr_pub *pub) +{ + struct r9a09g077_lcdc_div_clk *clk_hw_data; + struct clk_init_data init = {}; + struct clk **clks = pub->clks; + const struct clk *parent; + const char *parent_name; + struct clk_hw *hw; + int ret; + + parent = clks[core->parent]; + if (IS_ERR(parent)) + return ERR_CAST(parent); + + clk_hw_data = devm_kzalloc(dev, sizeof(*clk_hw_data), GFP_KERNEL); + if (!clk_hw_data) + return ERR_PTR(-ENOMEM); + + clk_hw_data->dtable = core->dtable; + clk_hw_data->reg = addr; + clk_hw_data->conf = core->conf; + clk_hw_data->dev = dev; + clk_hw_data->divider = 32; /* Initialize divider for LCDC */ + + parent_name = __clk_get_name(parent); + init.name = core->name; + init.ops = &r9a09g077_cpg_lcdc_div_ops; + init.flags = core->flag; + init.parent_names = &parent_name; + init.num_parents = 1; + + hw = &clk_hw_data->hw; + hw->init = &init; + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ERR_PTR(ret); + + return hw->clk; +} + static struct clk * __init r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core, const struct cpg_mssr_info *info, @@ -499,6 +865,11 @@ r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core, return r9a09g077_cpg_mux_clk_register(dev, core, addr, pub); case CLK_TYPE_RZT2H_FSELXSPI: return r9a09g077_cpg_fselxspi_div_clk_register(dev, core, addr, pub); + case CLK_TYPE_RZT2H_PLL3: + return r9a09g077_cpg_pll3_clk_register(dev, core, pub->base1 + offset, + pub, &r9a09g077_cpg_pll3_limits); + case CLK_TYPE_RZT2H_LCDCDIV: + return r9a09g077_cpg_lcdc_div_clk_register(dev, core, addr, pub); default: return ERR_PTR(-EINVAL); } |
