diff options
Diffstat (limited to 'drivers/clk/sunxi-ng/ccu-sun50i-h6.c')
| -rw-r--r-- | drivers/clk/sunxi-ng/ccu-sun50i-h6.c | 183 |
1 files changed, 128 insertions, 55 deletions
diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-h6.c b/drivers/clk/sunxi-ng/ccu-sun50i-h6.c index 139e8389615c..7fccda96d444 100644 --- a/drivers/clk/sunxi-ng/ccu-sun50i-h6.c +++ b/drivers/clk/sunxi-ng/ccu-sun50i-h6.c @@ -4,7 +4,8 @@ */ #include <linux/clk-provider.h> -#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/module.h> #include <linux/platform_device.h> #include "ccu_common.h" @@ -94,13 +95,13 @@ static struct ccu_nkmp pll_periph1_clk = { }, }; +/* For GPU PLL, using an output divider for DFS causes system to fail */ #define SUN50I_H6_PLL_GPU_REG 0x030 static struct ccu_nkmp pll_gpu_clk = { .enable = BIT(31), .lock = BIT(28), .n = _SUNXI_CCU_MULT_MIN(8, 8, 12), .m = _SUNXI_CCU_DIV(1, 1), /* input divider */ - .p = _SUNXI_CCU_DIV(0, 1), /* output divider */ .common = { .reg = 0x030, .hw.init = CLK_HW_INIT("pll-gpu", "osc24M", @@ -202,12 +203,21 @@ static struct ccu_nkmp pll_hsic_clk = { * hardcode it to match with the clock names. */ #define SUN50I_H6_PLL_AUDIO_REG 0x078 + +static struct ccu_sdm_setting pll_audio_sdm_table[] = { + { .rate = 541900800, .pattern = 0xc001288d, .m = 1, .n = 22 }, + { .rate = 589824000, .pattern = 0xc00126e9, .m = 1, .n = 24 }, +}; + static struct ccu_nm pll_audio_base_clk = { .enable = BIT(31), .lock = BIT(28), .n = _SUNXI_CCU_MULT_MIN(8, 8, 12), .m = _SUNXI_CCU_DIV(1, 1), /* input divider */ + .sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table, + BIT(24), 0x178, BIT(31)), .common = { + .features = CCU_FEATURE_SIGMA_DELTA_MOD, .reg = 0x078, .hw.init = CLK_HW_INIT("pll-audio-base", "osc24M", &ccu_nm_ops, @@ -227,7 +237,7 @@ static const char * const psi_ahb1_ahb2_parents[] = { "osc24M", "osc32k", static SUNXI_CCU_MP_WITH_MUX(psi_ahb1_ahb2_clk, "psi-ahb1-ahb2", psi_ahb1_ahb2_parents, 0x510, - 0, 5, /* M */ + 0, 2, /* M */ 8, 2, /* P */ 24, 2, /* mux */ 0); @@ -236,19 +246,19 @@ static const char * const ahb3_apb1_apb2_parents[] = { "osc24M", "osc32k", "psi-ahb1-ahb2", "pll-periph0" }; static SUNXI_CCU_MP_WITH_MUX(ahb3_clk, "ahb3", ahb3_apb1_apb2_parents, 0x51c, - 0, 5, /* M */ + 0, 2, /* M */ 8, 2, /* P */ 24, 2, /* mux */ 0); static SUNXI_CCU_MP_WITH_MUX(apb1_clk, "apb1", ahb3_apb1_apb2_parents, 0x520, - 0, 5, /* M */ + 0, 2, /* M */ 8, 2, /* P */ 24, 2, /* mux */ 0); static SUNXI_CCU_MP_WITH_MUX(apb2_clk, "apb2", ahb3_apb1_apb2_parents, 0x524, - 0, 5, /* M */ + 0, 2, /* M */ 8, 2, /* P */ 24, 2, /* mux */ 0); @@ -266,7 +276,7 @@ static SUNXI_CCU_M_WITH_MUX_GATE(de_clk, "de", de_parents, 0x600, 0, 4, /* M */ 24, 1, /* mux */ BIT(31), /* gate */ - 0); + CLK_SET_RATE_PARENT); static SUNXI_CCU_GATE(bus_de_clk, "bus-de", "psi-ahb1-ahb2", 0x60c, BIT(0), 0); @@ -284,12 +294,12 @@ static SUNXI_CCU_M_WITH_MUX_GATE(deinterlace_clk, "deinterlace", static SUNXI_CCU_GATE(bus_deinterlace_clk, "bus-deinterlace", "psi-ahb1-ahb2", 0x62c, BIT(0), 0); +/* Keep GPU_CLK divider const to avoid DFS instability. */ static const char * const gpu_parents[] = { "pll-gpu" }; -static SUNXI_CCU_M_WITH_MUX_GATE(gpu_clk, "gpu", gpu_parents, 0x670, - 0, 3, /* M */ +static SUNXI_CCU_MUX_WITH_GATE(gpu_clk, "gpu", gpu_parents, 0x670, 24, 1, /* mux */ BIT(31), /* gate */ - 0); + CLK_SET_RATE_PARENT); static SUNXI_CCU_GATE(bus_gpu_clk, "bus-gpu", "psi-ahb1-ahb2", 0x67c, BIT(0), 0); @@ -311,7 +321,7 @@ static SUNXI_CCU_M_WITH_MUX_GATE(ve_clk, "ve", ve_parents, 0x690, 0, 3, /* M */ 24, 1, /* mux */ BIT(31), /* gate */ - 0); + CLK_SET_RATE_PARENT); static SUNXI_CCU_GATE(bus_ve_clk, "bus-ve", "psi-ahb1-ahb2", 0x69c, BIT(0), 0); @@ -504,7 +514,7 @@ static struct ccu_div i2s3_clk = { .hw.init = CLK_HW_INIT_PARENTS("i2s3", audio_parents, &ccu_div_ops, - 0), + CLK_SET_RATE_PARENT), }, }; @@ -517,7 +527,7 @@ static struct ccu_div i2s0_clk = { .hw.init = CLK_HW_INIT_PARENTS("i2s0", audio_parents, &ccu_div_ops, - 0), + CLK_SET_RATE_PARENT), }, }; @@ -530,7 +540,7 @@ static struct ccu_div i2s1_clk = { .hw.init = CLK_HW_INIT_PARENTS("i2s1", audio_parents, &ccu_div_ops, - 0), + CLK_SET_RATE_PARENT), }, }; @@ -543,7 +553,7 @@ static struct ccu_div i2s2_clk = { .hw.init = CLK_HW_INIT_PARENTS("i2s2", audio_parents, &ccu_div_ops, - 0), + CLK_SET_RATE_PARENT), }, }; @@ -621,8 +631,9 @@ static SUNXI_CCU_GATE(bus_xhci_clk, "bus-xhci", "ahb3", 0xa8c, BIT(5), 0); static SUNXI_CCU_GATE(bus_ehci3_clk, "bus-ehci3", "ahb3", 0xa8c, BIT(7), 0); static SUNXI_CCU_GATE(bus_otg_clk, "bus-otg", "ahb3", 0xa8c, BIT(8), 0); -static CLK_FIXED_FACTOR(pcie_ref_100m_clk, "pcie-ref-100M", - "pll-periph0-4x", 24, 1, 0); +static struct clk_fixed_factor pll_periph0_4x_clk; +static CLK_FIXED_FACTOR_HW(pcie_ref_100m_clk, "pcie-ref-100M", + &pll_periph0_4x_clk.hw, 24, 1, 0); static SUNXI_CCU_GATE(pcie_ref_clk, "pcie-ref", "pcie-ref-100M", 0xab0, BIT(31), 0); static SUNXI_CCU_GATE(pcie_ref_out_clk, "pcie-ref-out", "pcie-ref", @@ -656,6 +667,8 @@ static const char * const hdmi_cec_parents[] = { "osc32k", "pll-periph0-2x" }; static const struct ccu_mux_fixed_prediv hdmi_cec_predivs[] = { { .index = 1, .div = 36621 }, }; + +#define SUN50I_H6_HDMI_CEC_CLK_REG 0xb10 static struct ccu_mux hdmi_cec_clk = { .enable = BIT(31), @@ -669,7 +682,7 @@ static struct ccu_mux hdmi_cec_clk = { .common = { .reg = 0xb10, - .features = CCU_FEATURE_VARIABLE_PREDIV, + .features = CCU_FEATURE_FIXED_PREDIV, .hw.init = CLK_HW_INIT_PARENTS("hdmi-cec", hdmi_cec_parents, &ccu_mux_ops, @@ -689,7 +702,7 @@ static SUNXI_CCU_MUX_WITH_GATE(tcon_lcd0_clk, "tcon-lcd0", tcon_lcd0_parents, 0xb60, 24, 3, /* mux */ BIT(31), /* gate */ - 0); + CLK_SET_RATE_PARENT); static SUNXI_CCU_GATE(bus_tcon_lcd0_clk, "bus-tcon-lcd0", "ahb3", 0xb7c, BIT(0), 0); @@ -704,7 +717,7 @@ static SUNXI_CCU_MP_WITH_MUX_GATE(tcon_tv0_clk, "tcon-tv0", 8, 2, /* P */ 24, 3, /* mux */ BIT(31), /* gate */ - 0); + CLK_SET_RATE_PARENT); static SUNXI_CCU_GATE(bus_tcon_tv0_clk, "bus-tcon-tv0", "ahb3", 0xb9c, BIT(0), 0); @@ -742,34 +755,52 @@ static SUNXI_CCU_M_WITH_MUX_GATE(hdcp_clk, "hdcp", hdcp_parents, 0xc40, static SUNXI_CCU_GATE(bus_hdcp_clk, "bus-hdcp", "ahb3", 0xc4c, BIT(0), 0); /* Fixed factor clocks */ -static CLK_FIXED_FACTOR(osc12M_clk, "osc12M", "osc24M", 2, 1, 0); +static CLK_FIXED_FACTOR_FW_NAME(osc12M_clk, "osc12M", "hosc", 2, 1, 0); + +static const struct clk_hw *clk_parent_pll_audio[] = { + &pll_audio_base_clk.common.hw +}; /* - * The divider of pll-audio is fixed to 8 now, as pll-audio-4x has a - * fixed post-divider 2. + * The divider of pll-audio is fixed to 24 for now, so 24576000 and 22579200 + * rates can be set exactly in conjunction with sigma-delta modulation. */ -static CLK_FIXED_FACTOR(pll_audio_clk, "pll-audio", - "pll-audio-base", 8, 1, CLK_SET_RATE_PARENT); -static CLK_FIXED_FACTOR(pll_audio_2x_clk, "pll-audio-2x", - "pll-audio-base", 4, 1, CLK_SET_RATE_PARENT); -static CLK_FIXED_FACTOR(pll_audio_4x_clk, "pll-audio-4x", - "pll-audio-base", 2, 1, CLK_SET_RATE_PARENT); - -static CLK_FIXED_FACTOR(pll_periph0_4x_clk, "pll-periph0-4x", - "pll-periph0", 1, 4, 0); -static CLK_FIXED_FACTOR(pll_periph0_2x_clk, "pll-periph0-2x", - "pll-periph0", 1, 2, 0); - -static CLK_FIXED_FACTOR(pll_periph1_4x_clk, "pll-periph1-4x", - "pll-periph1", 1, 4, 0); -static CLK_FIXED_FACTOR(pll_periph1_2x_clk, "pll-periph1-2x", - "pll-periph1", 1, 2, 0); - -static CLK_FIXED_FACTOR(pll_video0_4x_clk, "pll-video0-4x", - "pll-video0", 1, 4, CLK_SET_RATE_PARENT); - -static CLK_FIXED_FACTOR(pll_video1_4x_clk, "pll-video1-4x", - "pll-video1", 1, 4, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR_HWS(pll_audio_clk, "pll-audio", + clk_parent_pll_audio, + 24, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x", + clk_parent_pll_audio, + 4, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR_HWS(pll_audio_4x_clk, "pll-audio-4x", + clk_parent_pll_audio, + 2, 1, CLK_SET_RATE_PARENT); + +static const struct clk_hw *pll_periph0_parents[] = { + &pll_periph0_clk.common.hw +}; +static CLK_FIXED_FACTOR_HWS(pll_periph0_4x_clk, "pll-periph0-4x", + pll_periph0_parents, + 1, 4, 0); +static CLK_FIXED_FACTOR_HWS(pll_periph0_2x_clk, "pll-periph0-2x", + pll_periph0_parents, + 1, 2, 0); + +static const struct clk_hw *pll_periph1_parents[] = { + &pll_periph1_clk.common.hw +}; +static CLK_FIXED_FACTOR_HWS(pll_periph1_4x_clk, "pll-periph1-4x", + pll_periph1_parents, + 1, 4, 0); +static CLK_FIXED_FACTOR_HWS(pll_periph1_2x_clk, "pll-periph1-2x", + pll_periph1_parents, + 1, 2, 0); + +static CLK_FIXED_FACTOR_HW(pll_video0_4x_clk, "pll-video0-4x", + &pll_video0_clk.common.hw, + 1, 4, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR_HW(pll_video1_4x_clk, "pll-video1-4x", + &pll_video1_clk.common.hw, + 1, 4, CLK_SET_RATE_PARENT); static struct ccu_common *sun50i_h6_ccu_clks[] = { &pll_cpux_clk.common, @@ -1045,7 +1076,7 @@ static struct clk_hw_onecell_data sun50i_h6_hw_clks = { .num = CLK_NUMBER, }; -static struct ccu_reset_map sun50i_h6_ccu_resets[] = { +static const struct ccu_reset_map sun50i_h6_ccu_resets[] = { [RST_MBUS] = { 0x540, BIT(30) }, [RST_BUS_DE] = { 0x60c, BIT(16) }, @@ -1150,18 +1181,37 @@ static const u32 usb2_clk_regs[] = { SUN50I_H6_USB3_CLK_REG, }; +static struct ccu_mux_nb sun50i_h6_cpu_nb = { + .common = &cpux_clk.common, + .cm = &cpux_clk.mux, + .delay_us = 1, + .bypass_index = 0, /* index of 24 MHz oscillator */ +}; + static int sun50i_h6_ccu_probe(struct platform_device *pdev) { - struct resource *res; void __iomem *reg; + int i, ret; u32 val; - int i; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - reg = devm_ioremap_resource(&pdev->dev, res); + reg = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(reg)) return PTR_ERR(reg); + /* + * Force PLL_GPU output divider bits to 0 and adjust + * multiplier to sensible default value of 432 MHz. + */ + val = readl(reg + SUN50I_H6_PLL_GPU_REG); + val &= ~(GENMASK(15, 8) | BIT(0)); + val |= 17 << 8; + writel(val, reg + SUN50I_H6_PLL_GPU_REG); + + /* Force GPU_CLK divider bits to 0 */ + val = readl(reg + gpu_clk.common.reg); + val &= ~GENMASK(3, 0); + writel(val, reg + gpu_clk.common.reg); + /* Enable the lock bits on all PLLs */ for (i = 0; i < ARRAY_SIZE(pll_regs); i++) { val = readl(reg + pll_regs[i]); @@ -1193,26 +1243,49 @@ static int sun50i_h6_ccu_probe(struct platform_device *pdev) } /* - * Force the post-divider of pll-audio to 8 and the output divider - * of it to 1, to make the clock name represents the real frequency. + * Force the post-divider of pll-audio to 12 and the output divider + * of it to 2, so 24576000 and 22579200 rates can be set exactly. */ val = readl(reg + SUN50I_H6_PLL_AUDIO_REG); val &= ~(GENMASK(21, 16) | BIT(0)); - writel(val | (7 << 16), reg + SUN50I_H6_PLL_AUDIO_REG); + writel(val | (11 << 16) | BIT(0), reg + SUN50I_H6_PLL_AUDIO_REG); - return sunxi_ccu_probe(pdev->dev.of_node, reg, &sun50i_h6_ccu_desc); + /* + * First clock parent (osc32K) is unusable for CEC. But since there + * is no good way to force parent switch (both run with same frequency), + * just set second clock parent here. + */ + val = readl(reg + SUN50I_H6_HDMI_CEC_CLK_REG); + val |= BIT(24); + writel(val, reg + SUN50I_H6_HDMI_CEC_CLK_REG); + + ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_h6_ccu_desc); + if (ret) + return ret; + + /* Reparent CPU during PLL CPUX rate changes */ + ccu_mux_notifier_register(pll_cpux_clk.common.hw.clk, + &sun50i_h6_cpu_nb); + + return 0; } static const struct of_device_id sun50i_h6_ccu_ids[] = { { .compatible = "allwinner,sun50i-h6-ccu" }, { } }; +MODULE_DEVICE_TABLE(of, sun50i_h6_ccu_ids); static struct platform_driver sun50i_h6_ccu_driver = { .probe = sun50i_h6_ccu_probe, .driver = { .name = "sun50i-h6-ccu", + .suppress_bind_attrs = true, .of_match_table = sun50i_h6_ccu_ids, }, }; -builtin_platform_driver(sun50i_h6_ccu_driver); +module_platform_driver(sun50i_h6_ccu_driver); + +MODULE_IMPORT_NS("SUNXI_CCU"); +MODULE_DESCRIPTION("Support for the Allwinner H6 CCU"); +MODULE_LICENSE("GPL"); |
