summaryrefslogtreecommitdiff
path: root/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/sunxi-ng/ccu-sun50i-h616.c')
-rw-r--r--drivers/clk/sunxi-ng/ccu-sun50i-h616.c144
1 files changed, 124 insertions, 20 deletions
diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
index 21e918582aa5..955c614830fa 100644
--- a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
+++ b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
@@ -216,19 +216,29 @@ static struct ccu_nkmp pll_de_clk = {
};
/*
- * TODO: Determine SDM settings for the audio PLL. The manual suggests
- * PLL_FACTOR_N=16, PLL_POST_DIV_P=2, OUTPUT_DIV=2, pattern=0xe000c49b
- * for 24.576 MHz, and PLL_FACTOR_N=22, PLL_POST_DIV_P=3, OUTPUT_DIV=2,
- * pattern=0xe001288c for 22.5792 MHz.
- * This clashes with our fixed PLL_POST_DIV_P.
+ * Sigma-delta modulation settings table obtained from the vendor SDK driver.
+ * There are additional M0 and M1 divider bits not modelled here, so forced to
+ * fixed values in the probe routine. Sigma-delta modulation allows providing a
+ * fractional-N divider in the PLL, to help reaching those specific
+ * frequencies with less error.
*/
+static struct ccu_sdm_setting pll_audio_sdm_table[] = {
+ { .rate = 90316800, .pattern = 0xc001288d, .m = 3, .n = 22 },
+ { .rate = 98304000, .pattern = 0xc001eb85, .m = 5, .n = 40 },
+};
+
#define SUN50I_H616_PLL_AUDIO_REG 0x078
static struct ccu_nm pll_audio_hs_clk = {
.enable = BIT(31),
.lock = BIT(28),
.n = _SUNXI_CCU_MULT_MIN(8, 8, 12),
- .m = _SUNXI_CCU_DIV(1, 1), /* input divider */
+ .m = _SUNXI_CCU_DIV(16, 6),
+ .sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table,
+ BIT(24), 0x178, BIT(31)),
+ .fixed_post_div = 2,
.common = {
+ .features = CCU_FEATURE_FIXED_POSTDIV |
+ CCU_FEATURE_SIGMA_DELTA_MOD,
.reg = 0x078,
.hw.init = CLK_HW_INIT("pll-audio-hs", "osc24M",
&ccu_nm_ops,
@@ -318,10 +328,16 @@ static SUNXI_CCU_M_WITH_MUX_GATE(gpu0_clk, "gpu0", gpu0_parents, 0x670,
24, 1, /* mux */
BIT(31), /* gate */
CLK_SET_RATE_PARENT);
+
+/*
+ * This clk is needed as a temporary fall back during GPU PLL freq changes.
+ * Set CLK_IS_CRITICAL flag to prevent from being disabled.
+ */
+#define SUN50I_H616_GPU_CLK1_REG 0x674
static SUNXI_CCU_M_WITH_GATE(gpu1_clk, "gpu1", "pll-periph0-2x", 0x674,
0, 2, /* M */
BIT(31),/* gate */
- 0);
+ CLK_IS_CRITICAL);
static SUNXI_CCU_GATE(bus_gpu_clk, "bus-gpu", "psi-ahb1-ahb2",
0x67c, BIT(0), 0);
@@ -489,6 +505,8 @@ static SUNXI_CCU_MP_WITH_MUX_GATE(ts_clk, "ts", ts_parents, 0x9b0,
static SUNXI_CCU_GATE(bus_ts_clk, "bus-ts", "ahb3", 0x9bc, BIT(0), 0);
+static SUNXI_CCU_GATE(bus_gpadc_clk, "bus-gpadc", "apb1", 0x9ec, BIT(0), 0);
+
static SUNXI_CCU_GATE(bus_ths_clk, "bus-ths", "apb1", 0x9fc, BIT(0), 0);
static const char * const audio_parents[] = { "pll-audio-1x", "pll-audio-2x",
@@ -633,6 +651,20 @@ static const char * const tcon_tv_parents[] = { "pll-video0",
"pll-video0-4x",
"pll-video1",
"pll-video1-4x" };
+static SUNXI_CCU_MUX_WITH_GATE(tcon_lcd0_clk, "tcon-lcd0",
+ tcon_tv_parents, 0xb60,
+ 24, 3, /* mux */
+ BIT(31), /* gate */
+ CLK_SET_RATE_PARENT);
+static SUNXI_CCU_MUX_WITH_GATE(tcon_lcd1_clk, "tcon-lcd1",
+ tcon_tv_parents, 0xb64,
+ 24, 3, /* mux */
+ BIT(31), /* gate */
+ CLK_SET_RATE_PARENT);
+static SUNXI_CCU_GATE(bus_tcon_lcd0_clk, "bus-tcon-lcd0", "ahb3",
+ 0xb7c, BIT(0), 0);
+static SUNXI_CCU_GATE(bus_tcon_lcd1_clk, "bus-tcon-lcd1", "ahb3",
+ 0xb7c, BIT(1), 0);
static SUNXI_CCU_MP_WITH_MUX_GATE(tcon_tv0_clk, "tcon-tv0",
tcon_tv_parents, 0xb80,
0, 4, /* M */
@@ -683,18 +715,20 @@ static const struct clk_hw *clk_parent_pll_audio[] = {
};
/*
- * 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.
+ * The PLL_AUDIO_4X clock defaults to 24.5714 MHz according to the manual, with
+ * a final divider of 1. The 2X and 1X clocks use 2 and 4 respectively. The 1x
+ * clock is set to either 24576000 or 22579200 for 48Khz and 44.1Khz (and
+ * multiples).
*/
static CLK_FIXED_FACTOR_HWS(pll_audio_1x_clk, "pll-audio-1x",
clk_parent_pll_audio,
- 96, 1, CLK_SET_RATE_PARENT);
+ 4, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x",
clk_parent_pll_audio,
- 48, 1, CLK_SET_RATE_PARENT);
+ 2, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR_HWS(pll_audio_4x_clk, "pll-audio-4x",
clk_parent_pll_audio,
- 24, 1, CLK_SET_RATE_PARENT);
+ 1, 1, CLK_SET_RATE_PARENT);
static const struct clk_hw *pll_periph0_parents[] = {
&pll_periph0_clk.common.hw
@@ -807,6 +841,7 @@ static struct ccu_common *sun50i_h616_ccu_clks[] = {
&bus_emac1_clk.common,
&ts_clk.common,
&bus_ts_clk.common,
+ &bus_gpadc_clk.common,
&bus_ths_clk.common,
&spdif_clk.common,
&bus_spdif_clk.common,
@@ -840,8 +875,12 @@ static struct ccu_common *sun50i_h616_ccu_clks[] = {
&hdmi_cec_clk.common,
&bus_hdmi_clk.common,
&bus_tcon_top_clk.common,
+ &tcon_lcd0_clk.common,
+ &tcon_lcd1_clk.common,
&tcon_tv0_clk.common,
&tcon_tv1_clk.common,
+ &bus_tcon_lcd0_clk.common,
+ &bus_tcon_lcd1_clk.common,
&bus_tcon_tv0_clk.common,
&bus_tcon_tv1_clk.common,
&tve0_clk.common,
@@ -940,6 +979,7 @@ static struct clk_hw_onecell_data sun50i_h616_hw_clks = {
[CLK_BUS_EMAC1] = &bus_emac1_clk.common.hw,
[CLK_TS] = &ts_clk.common.hw,
[CLK_BUS_TS] = &bus_ts_clk.common.hw,
+ [CLK_BUS_GPADC] = &bus_gpadc_clk.common.hw,
[CLK_BUS_THS] = &bus_ths_clk.common.hw,
[CLK_SPDIF] = &spdif_clk.common.hw,
[CLK_BUS_SPDIF] = &bus_spdif_clk.common.hw,
@@ -973,8 +1013,12 @@ static struct clk_hw_onecell_data sun50i_h616_hw_clks = {
[CLK_HDMI_CEC] = &hdmi_cec_clk.common.hw,
[CLK_BUS_HDMI] = &bus_hdmi_clk.common.hw,
[CLK_BUS_TCON_TOP] = &bus_tcon_top_clk.common.hw,
+ [CLK_TCON_LCD0] = &tcon_lcd0_clk.common.hw,
+ [CLK_TCON_LCD1] = &tcon_lcd1_clk.common.hw,
[CLK_TCON_TV0] = &tcon_tv0_clk.common.hw,
[CLK_TCON_TV1] = &tcon_tv1_clk.common.hw,
+ [CLK_BUS_TCON_LCD0] = &bus_tcon_lcd0_clk.common.hw,
+ [CLK_BUS_TCON_LCD1] = &bus_tcon_lcd1_clk.common.hw,
[CLK_BUS_TCON_TV0] = &bus_tcon_tv0_clk.common.hw,
[CLK_BUS_TCON_TV1] = &bus_tcon_tv1_clk.common.hw,
[CLK_TVE0] = &tve0_clk.common.hw,
@@ -986,7 +1030,7 @@ static struct clk_hw_onecell_data sun50i_h616_hw_clks = {
.num = CLK_NUMBER,
};
-static struct ccu_reset_map sun50i_h616_ccu_resets[] = {
+static const struct ccu_reset_map sun50i_h616_ccu_resets[] = {
[RST_MBUS] = { 0x540, BIT(30) },
[RST_BUS_DE] = { 0x60c, BIT(16) },
@@ -1021,6 +1065,7 @@ static struct ccu_reset_map sun50i_h616_ccu_resets[] = {
[RST_BUS_EMAC0] = { 0x97c, BIT(16) },
[RST_BUS_EMAC1] = { 0x97c, BIT(17) },
[RST_BUS_TS] = { 0x9bc, BIT(16) },
+ [RST_BUS_GPADC] = { 0x9ec, BIT(16) },
[RST_BUS_THS] = { 0x9fc, BIT(16) },
[RST_BUS_SPDIF] = { 0xa2c, BIT(16) },
[RST_BUS_DMIC] = { 0xa4c, BIT(16) },
@@ -1045,8 +1090,11 @@ static struct ccu_reset_map sun50i_h616_ccu_resets[] = {
[RST_BUS_HDMI] = { 0xb1c, BIT(16) },
[RST_BUS_HDMI_SUB] = { 0xb1c, BIT(17) },
[RST_BUS_TCON_TOP] = { 0xb5c, BIT(16) },
+ [RST_BUS_TCON_LCD0] = { 0xb7c, BIT(16) },
+ [RST_BUS_TCON_LCD1] = { 0xb7c, BIT(17) },
[RST_BUS_TCON_TV0] = { 0xb9c, BIT(16) },
[RST_BUS_TCON_TV1] = { 0xb9c, BIT(17) },
+ [RST_BUS_LVDS] = { 0xbac, BIT(16) },
[RST_BUS_TVE_TOP] = { 0xbbc, BIT(16) },
[RST_BUS_TVE0] = { 0xbbc, BIT(17) },
[RST_BUS_HDCP] = { 0xc4c, BIT(16) },
@@ -1090,11 +1138,37 @@ static const u32 usb2_clk_regs[] = {
SUN50I_H616_USB3_CLK_REG,
};
+static struct ccu_mux_nb sun50i_h616_cpu_nb = {
+ .common = &cpux_clk.common,
+ .cm = &cpux_clk.mux,
+ .delay_us = 1, /* manual doesn't really say */
+ .bypass_index = 4, /* PLL_PERI0@600MHz, as recommended by manual */
+};
+
+static struct ccu_pll_nb sun50i_h616_pll_cpu_nb = {
+ .common = &pll_cpux_clk.common,
+ .enable = BIT(29), /* LOCK_ENABLE */
+ .lock = BIT(28),
+};
+
+static struct ccu_mux_nb sun50i_h616_gpu_nb = {
+ .common = &gpu0_clk.common,
+ .cm = &gpu0_clk.mux,
+ .delay_us = 1, /* manual doesn't really say */
+ .bypass_index = 1, /* GPU_CLK1@400MHz */
+};
+
+static struct ccu_pll_nb sun50i_h616_pll_gpu_nb = {
+ .common = &pll_gpu_clk.common,
+ .enable = BIT(29), /* LOCK_ENABLE */
+ .lock = BIT(28),
+};
+
static int sun50i_h616_ccu_probe(struct platform_device *pdev)
{
void __iomem *reg;
u32 val;
- int i;
+ int ret, i;
reg = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(reg))
@@ -1131,12 +1205,22 @@ static int sun50i_h616_ccu_probe(struct platform_device *pdev)
}
/*
- * 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.
+ * Set the output-divider for the pll-audio clocks (M0) to 2 and the
+ * input divider (M1) to 1 as recommended by the manual when using
+ * SDM.
*/
val = readl(reg + SUN50I_H616_PLL_AUDIO_REG);
- val &= ~(GENMASK(21, 16) | BIT(0));
- writel(val | (11 << 16) | BIT(0), reg + SUN50I_H616_PLL_AUDIO_REG);
+ val &= ~BIT(1);
+ val |= BIT(0);
+ writel(val, reg + SUN50I_H616_PLL_AUDIO_REG);
+
+ /*
+ * Set the input-divider for the gpu1 clock to 3, to reach a safe 400 MHz.
+ */
+ val = readl(reg + SUN50I_H616_GPU_CLK1_REG);
+ val &= ~GENMASK(1, 0);
+ val |= 2;
+ writel(val, reg + SUN50I_H616_GPU_CLK1_REG);
/*
* First clock parent (osc32K) is unusable for CEC. But since there
@@ -1147,13 +1231,32 @@ static int sun50i_h616_ccu_probe(struct platform_device *pdev)
val |= BIT(24);
writel(val, reg + SUN50I_H616_HDMI_CEC_CLK_REG);
- return devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_h616_ccu_desc);
+ ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_h616_ccu_desc);
+ if (ret)
+ return ret;
+
+ /* Reparent CPU during CPU PLL rate changes */
+ ccu_mux_notifier_register(pll_cpux_clk.common.hw.clk,
+ &sun50i_h616_cpu_nb);
+
+ /* Re-lock the CPU PLL after any rate changes */
+ ccu_pll_notifier_register(&sun50i_h616_pll_cpu_nb);
+
+ /* Reparent GPU during GPU PLL rate changes */
+ ccu_mux_notifier_register(pll_gpu_clk.common.hw.clk,
+ &sun50i_h616_gpu_nb);
+
+ /* Re-lock the GPU PLL after any rate changes */
+ ccu_pll_notifier_register(&sun50i_h616_pll_gpu_nb);
+
+ return 0;
}
static const struct of_device_id sun50i_h616_ccu_ids[] = {
{ .compatible = "allwinner,sun50i-h616-ccu" },
{ }
};
+MODULE_DEVICE_TABLE(of, sun50i_h616_ccu_ids);
static struct platform_driver sun50i_h616_ccu_driver = {
.probe = sun50i_h616_ccu_probe,
@@ -1165,5 +1268,6 @@ static struct platform_driver sun50i_h616_ccu_driver = {
};
module_platform_driver(sun50i_h616_ccu_driver);
-MODULE_IMPORT_NS(SUNXI_CCU);
+MODULE_IMPORT_NS("SUNXI_CCU");
+MODULE_DESCRIPTION("Support for the Allwinner H616 CCU");
MODULE_LICENSE("GPL");