1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Samsung Exynos7420 clock driver.
4 * Copyright (C) 2016 Samsung Electronics
5 * Thomas Abraham <thomas.ab@samsung.com>
6 */
7
8#include <common.h>
9#include <dm.h>
10#include <errno.h>
11#include <clk-uclass.h>
12#include <asm/io.h>
13#include <div64.h>
14#include <dt-bindings/clock/exynos7420-clk.h>
15
16#define PLL145X_MDIV_SHIFT	16
17#define PLL145X_MDIV_MASK	0x3ff
18#define PLL145X_PDIV_SHIFT	8
19#define PLL145X_PDIV_MASK	0x3f
20#define PLL145X_SDIV_SHIFT	0
21#define PLL145X_SDIV_MASK	0x7
22
23#define DIVIDER(reg, shift, mask)	\
24	(((readl(reg) >> shift) & mask) + 1)
25
26/* CMU TOPC block device structure */
27struct exynos7420_clk_cmu_topc {
28	unsigned int	rsvd1[68];
29	unsigned int	bus0_pll_con[2];
30	unsigned int	rsvd2[2];
31	unsigned int	bus1_pll_con[2];
32	unsigned int	rsvd3[54];
33	unsigned int	mux_sel[6];
34	unsigned int	rsvd4[250];
35	unsigned int	div[4];
36};
37
38/* CMU TOP0 block device structure */
39struct exynos7420_clk_cmu_top0 {
40	unsigned int	rsvd0[128];
41	unsigned int	mux_sel[7];
42	unsigned int	rsvd1[261];
43	unsigned int	div_peric[5];
44};
45
46/**
47 * struct exynos7420_clk_topc_priv - private data for CMU topc clock driver.
48 *
49 * @topc: base address of the memory mapped CMU TOPC controller.
50 * @fin_freq: frequency of the Oscillator clock.
51 * @sclk_bus0_pll_a: frequency of sclk_bus0_pll_a clock.
52 * @sclk_bus1_pll_a: frequency of sclk_bus1_pll_a clock.
53 */
54struct exynos7420_clk_topc_priv {
55	struct exynos7420_clk_cmu_topc *topc;
56	unsigned long fin_freq;
57	unsigned long sclk_bus0_pll_a;
58	unsigned long sclk_bus1_pll_a;
59};
60
61/**
62 * struct exynos7420_clk_top0_priv - private data for CMU top0 clock driver.
63 *
64 * @top0: base address of the memory mapped CMU TOP0 controller.
65 * @mout_top0_bus0_pll_half: frequency of mout_top0_bus0_pll_half clock
66 * @sclk_uart2: frequency of sclk_uart2 clock.
67 */
68struct exynos7420_clk_top0_priv {
69	struct exynos7420_clk_cmu_top0 *top0;
70	unsigned long mout_top0_bus0_pll_half;
71	unsigned long sclk_uart2;
72};
73
74static unsigned long pll145x_get_rate(unsigned int *con1,
75				      unsigned long fin_freq)
76{
77	unsigned long pll_con1 = readl(con1);
78	unsigned long mdiv, sdiv, pdiv;
79	u64 fvco = fin_freq;
80
81	mdiv = (pll_con1 >> PLL145X_MDIV_SHIFT) & PLL145X_MDIV_MASK;
82	pdiv = (pll_con1 >> PLL145X_PDIV_SHIFT) & PLL145X_PDIV_MASK;
83	sdiv = (pll_con1 >> PLL145X_SDIV_SHIFT) & PLL145X_SDIV_MASK;
84
85	fvco *= mdiv;
86	do_div(fvco, (pdiv << sdiv));
87	return (unsigned long)fvco;
88}
89
90static ulong exynos7420_topc_get_rate(struct clk *clk)
91{
92	struct exynos7420_clk_topc_priv *priv = dev_get_priv(clk->dev);
93
94	switch (clk->id) {
95	case DOUT_SCLK_BUS0_PLL:
96	case SCLK_BUS0_PLL_A:
97	case SCLK_BUS0_PLL_B:
98		return priv->sclk_bus0_pll_a;
99	case DOUT_SCLK_BUS1_PLL:
100	case SCLK_BUS1_PLL_A:
101	case SCLK_BUS1_PLL_B:
102		return priv->sclk_bus1_pll_a;
103	default:
104		return 0;
105	}
106}
107
108static struct clk_ops exynos7420_clk_topc_ops = {
109	.get_rate	= exynos7420_topc_get_rate,
110};
111
112static int exynos7420_clk_topc_probe(struct udevice *dev)
113{
114	struct exynos7420_clk_topc_priv *priv = dev_get_priv(dev);
115	struct exynos7420_clk_cmu_topc *topc;
116	struct clk in_clk;
117	unsigned long rate;
118	fdt_addr_t base;
119	int ret;
120
121	base = dev_read_addr(dev);
122	if (base == FDT_ADDR_T_NONE)
123		return -EINVAL;
124
125	topc = (struct exynos7420_clk_cmu_topc *)base;
126	priv->topc = topc;
127
128	ret = clk_get_by_index(dev, 0, &in_clk);
129	if (ret >= 0)
130		priv->fin_freq = clk_get_rate(&in_clk);
131
132	rate = pll145x_get_rate(&topc->bus0_pll_con[0], priv->fin_freq);
133	if (readl(&topc->mux_sel[1]) & (1 << 16))
134		rate >>= 1;
135	rate /= DIVIDER(&topc->div[3], 0, 0xf);
136	priv->sclk_bus0_pll_a = rate;
137
138	rate = pll145x_get_rate(&topc->bus1_pll_con[0], priv->fin_freq) /
139			DIVIDER(&topc->div[3], 8, 0xf);
140	priv->sclk_bus1_pll_a = rate;
141
142	return 0;
143}
144
145static ulong exynos7420_top0_get_rate(struct clk *clk)
146{
147	struct exynos7420_clk_top0_priv *priv = dev_get_priv(clk->dev);
148	struct exynos7420_clk_cmu_top0 *top0 = priv->top0;
149
150	switch (clk->id) {
151	case CLK_SCLK_UART2:
152		return priv->mout_top0_bus0_pll_half /
153			DIVIDER(&top0->div_peric[3], 8, 0xf);
154	default:
155		return 0;
156	}
157}
158
159static struct clk_ops exynos7420_clk_top0_ops = {
160	.get_rate	= exynos7420_top0_get_rate,
161};
162
163static int exynos7420_clk_top0_probe(struct udevice *dev)
164{
165	struct exynos7420_clk_top0_priv *priv;
166	struct exynos7420_clk_cmu_top0 *top0;
167	struct clk in_clk;
168	fdt_addr_t base;
169	int ret;
170
171	priv = dev_get_priv(dev);
172	if (!priv)
173		return -EINVAL;
174
175	base = dev_read_addr(dev);
176	if (base == FDT_ADDR_T_NONE)
177		return -EINVAL;
178
179	top0 = (struct exynos7420_clk_cmu_top0 *)base;
180	priv->top0 = top0;
181
182	ret = clk_get_by_index(dev, 1, &in_clk);
183	if (ret >= 0) {
184		priv->mout_top0_bus0_pll_half =
185			clk_get_rate(&in_clk);
186		if (readl(&top0->mux_sel[1]) & (1 << 16))
187			priv->mout_top0_bus0_pll_half >>= 1;
188	}
189
190	return 0;
191}
192
193static ulong exynos7420_peric1_get_rate(struct clk *clk)
194{
195	struct clk in_clk;
196	unsigned int ret;
197	unsigned long freq = 0;
198
199	switch (clk->id) {
200	case SCLK_UART2:
201		ret = clk_get_by_index(clk->dev, 3, &in_clk);
202		if (ret < 0)
203			return ret;
204		freq = clk_get_rate(&in_clk);
205		break;
206	}
207
208	return freq;
209}
210
211static struct clk_ops exynos7420_clk_peric1_ops = {
212	.get_rate	= exynos7420_peric1_get_rate,
213};
214
215static const struct udevice_id exynos7420_clk_topc_compat[] = {
216	{ .compatible = "samsung,exynos7-clock-topc" },
217	{ }
218};
219
220U_BOOT_DRIVER(exynos7420_clk_topc) = {
221	.name = "exynos7420-clock-topc",
222	.id = UCLASS_CLK,
223	.of_match = exynos7420_clk_topc_compat,
224	.probe = exynos7420_clk_topc_probe,
225	.priv_auto	= sizeof(struct exynos7420_clk_topc_priv),
226	.ops = &exynos7420_clk_topc_ops,
227};
228
229static const struct udevice_id exynos7420_clk_top0_compat[] = {
230	{ .compatible = "samsung,exynos7-clock-top0" },
231	{ }
232};
233
234U_BOOT_DRIVER(exynos7420_clk_top0) = {
235	.name = "exynos7420-clock-top0",
236	.id = UCLASS_CLK,
237	.of_match = exynos7420_clk_top0_compat,
238	.probe = exynos7420_clk_top0_probe,
239	.priv_auto	= sizeof(struct exynos7420_clk_top0_priv),
240	.ops = &exynos7420_clk_top0_ops,
241};
242
243static const struct udevice_id exynos7420_clk_peric1_compat[] = {
244	{ .compatible = "samsung,exynos7-clock-peric1" },
245	{ }
246};
247
248U_BOOT_DRIVER(exynos7420_clk_peric1) = {
249	.name = "exynos7420-clock-peric1",
250	.id = UCLASS_CLK,
251	.of_match = exynos7420_clk_peric1_compat,
252	.ops = &exynos7420_clk_peric1_ops,
253};
254