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
meson8_ddr_clkc_probe(struct platform_device * pdev)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);
150