Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: meson: add a driver for the Meson8/8b/8m2 DDR clock controller

The Meson8/Meson8b/Meson8m2 SoCs embed a DDR clock controller in the
MMCBUS registers. There is no public documentation, but the u-boot GPL
sources from the Amlogic BSP show that the DDR clock controller is
identical on all three SoCs:
#define CFG_DDR_CLK 792
#define CFG_PLL_M (((CFG_DDR_CLK/12)*12)/24)
#define CFG_PLL_N 1
#define CFG_PLL_OD 1

// from set_ddr_clock:
t_ddr_pll_cntl= (CFG_PLL_OD << 16)|(CFG_PLL_N<<9)|(CFG_PLL_M<<0)
writel(timing_reg->t_ddr_pll_cntl|(1<<29),AM_DDR_PLL_CNTL);
writel(readl(AM_DDR_PLL_CNTL) & (~(1<<29)),AM_DDR_PLL_CNTL);

// from hx_ddr_power_down_enter: shut down DDR PLL
writel(readl(AM_DDR_PLL_CNTL)|(1<<30),AM_DDR_PLL_CNTL);

do { ... } while((readl(AM_DDR_PLL_CNTL)&(1<<31))==0)

This translates to:
- AM_DDR_PLL_CNTL[29] is the reset bit
- AM_DDR_PLL_CNTL[30] is the enable bit
- AM_DDR_PLL_CNTL[31] is the lock bit
- AM_DDR_PLL_CNTL[8:0] is the m value (assuming the width is 9 bits
based on the start of the n value)
- AM_DDR_PLL_CNTL[13:9] is the n value (assuming the width is 5 bits
based on the start of the od)
- AM_DDR_PLL_CNTL[17:16] is the od (assuming the width is 2 bits based
on other PLLs on this SoC)

Add a driver for this PLL setup because it's used as one of the inputs
of the audio clocks. There may be more clocks inside that clock
controller - those can be added in subsequent patches.

Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>

authored by

Martin Blumenstingl and committed by
Jerome Brunet
64aa7008 25d31698

+150 -1
+1 -1
drivers/clk/meson/Makefile
··· 18 18 obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o 19 19 obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o 20 20 obj-$(CONFIG_COMMON_CLK_G12A) += g12a.o g12a-aoclk.o 21 - obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o 21 + obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o meson8-ddr.o
+149
drivers/clk/meson/meson8-ddr.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Amlogic Meson8 DDR clock controller 4 + * 5 + * Copyright (C) 2019 Martin Blumenstingl <martin.blumenstingl@googlemail.com> 6 + */ 7 + 8 + #include <dt-bindings/clock/meson8-ddr-clkc.h> 9 + 10 + #include <linux/clk-provider.h> 11 + #include <linux/platform_device.h> 12 + 13 + #include "clk-regmap.h" 14 + #include "clk-pll.h" 15 + 16 + #define AM_DDR_PLL_CNTL 0x00 17 + #define AM_DDR_PLL_CNTL1 0x04 18 + #define AM_DDR_PLL_CNTL2 0x08 19 + #define AM_DDR_PLL_CNTL3 0x0c 20 + #define AM_DDR_PLL_CNTL4 0x10 21 + #define AM_DDR_PLL_STS 0x14 22 + #define DDR_CLK_CNTL 0x18 23 + #define DDR_CLK_STS 0x1c 24 + 25 + static struct clk_regmap meson8_ddr_pll_dco = { 26 + .data = &(struct meson_clk_pll_data){ 27 + .en = { 28 + .reg_off = AM_DDR_PLL_CNTL, 29 + .shift = 30, 30 + .width = 1, 31 + }, 32 + .m = { 33 + .reg_off = AM_DDR_PLL_CNTL, 34 + .shift = 0, 35 + .width = 9, 36 + }, 37 + .n = { 38 + .reg_off = AM_DDR_PLL_CNTL, 39 + .shift = 9, 40 + .width = 5, 41 + }, 42 + .l = { 43 + .reg_off = AM_DDR_PLL_CNTL, 44 + .shift = 31, 45 + .width = 1, 46 + }, 47 + .rst = { 48 + .reg_off = AM_DDR_PLL_CNTL, 49 + .shift = 29, 50 + .width = 1, 51 + }, 52 + }, 53 + .hw.init = &(struct clk_init_data){ 54 + .name = "ddr_pll_dco", 55 + .ops = &meson_clk_pll_ro_ops, 56 + .parent_data = &(const struct clk_parent_data) { 57 + .fw_name = "xtal", 58 + }, 59 + .num_parents = 1, 60 + }, 61 + }; 62 + 63 + static struct clk_regmap meson8_ddr_pll = { 64 + .data = &(struct clk_regmap_div_data){ 65 + .offset = AM_DDR_PLL_CNTL, 66 + .shift = 16, 67 + .width = 2, 68 + .flags = CLK_DIVIDER_POWER_OF_TWO, 69 + }, 70 + .hw.init = &(struct clk_init_data){ 71 + .name = "ddr_pll", 72 + .ops = &clk_regmap_divider_ro_ops, 73 + .parent_hws = (const struct clk_hw *[]) { 74 + &meson8_ddr_pll_dco.hw 75 + }, 76 + .num_parents = 1, 77 + }, 78 + }; 79 + 80 + static struct clk_hw_onecell_data meson8_ddr_clk_hw_onecell_data = { 81 + .hws = { 82 + [DDR_CLKID_DDR_PLL_DCO] = &meson8_ddr_pll_dco.hw, 83 + [DDR_CLKID_DDR_PLL] = &meson8_ddr_pll.hw, 84 + }, 85 + .num = 2, 86 + }; 87 + 88 + static struct clk_regmap *const meson8_ddr_clk_regmaps[] = { 89 + &meson8_ddr_pll_dco, 90 + &meson8_ddr_pll, 91 + }; 92 + 93 + static const struct regmap_config meson8_ddr_clkc_regmap_config = { 94 + .reg_bits = 8, 95 + .val_bits = 32, 96 + .reg_stride = 4, 97 + .max_register = DDR_CLK_STS, 98 + }; 99 + 100 + static int meson8_ddr_clkc_probe(struct platform_device *pdev) 101 + { 102 + struct regmap *regmap; 103 + void __iomem *base; 104 + struct clk_hw *hw; 105 + int ret, i; 106 + 107 + base = devm_platform_ioremap_resource(pdev, 0); 108 + if (IS_ERR(base)) 109 + return PTR_ERR(base); 110 + 111 + regmap = devm_regmap_init_mmio(&pdev->dev, base, 112 + &meson8_ddr_clkc_regmap_config); 113 + if (IS_ERR(regmap)) 114 + return PTR_ERR(regmap); 115 + 116 + /* Populate regmap */ 117 + for (i = 0; i < ARRAY_SIZE(meson8_ddr_clk_regmaps); i++) 118 + meson8_ddr_clk_regmaps[i]->map = regmap; 119 + 120 + /* Register all clks */ 121 + for (i = 0; i < meson8_ddr_clk_hw_onecell_data.num; i++) { 122 + hw = meson8_ddr_clk_hw_onecell_data.hws[i]; 123 + 124 + ret = devm_clk_hw_register(&pdev->dev, hw); 125 + if (ret) { 126 + dev_err(&pdev->dev, "Clock registration failed\n"); 127 + return ret; 128 + } 129 + } 130 + 131 + return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get, 132 + &meson8_ddr_clk_hw_onecell_data); 133 + } 134 + 135 + static const struct of_device_id meson8_ddr_clkc_match_table[] = { 136 + { .compatible = "amlogic,meson8-ddr-clkc" }, 137 + { .compatible = "amlogic,meson8b-ddr-clkc" }, 138 + { /* sentinel */ } 139 + }; 140 + 141 + static struct platform_driver meson8_ddr_clkc_driver = { 142 + .probe = meson8_ddr_clkc_probe, 143 + .driver = { 144 + .name = "meson8-ddr-clkc", 145 + .of_match_table = meson8_ddr_clkc_match_table, 146 + }, 147 + }; 148 + 149 + builtin_platform_driver(meson8_ddr_clkc_driver);