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