1/* $OpenBSD: exclock.c,v 1.10 2021/10/24 17:52:27 mpi Exp $ */
2/*
3 * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21
22#include <machine/bus.h>
23#include <machine/fdt.h>
24
25#include <dev/ofw/openfirm.h>
26#include <dev/ofw/ofw_clock.h>
27#include <dev/ofw/fdt.h>
28
29/* registers */
30#define CLOCK_APLL_CON0				0x0100
31#define CLOCK_APLL_CON1				0x0104
32#define CLOCK_BPLL_CON0				0x0110
33#define CLOCK_BPLL_CON1				0x0114
34#define CLOCK_EPLL_CON0				0x0130
35#define CLOCK_EPLL_CON1				0x0134
36#define CLOCK_EPLL_CON2				0x0138
37#define CLOCK_VPLL_CON0				0x0140
38#define CLOCK_VPLL_CON1				0x0144
39#define CLOCK_VPLL_CON2				0x0148
40#define CLOCK_CLK_DIV_CPU0			0x0500
41#define CLOCK_CLK_DIV_CPU1			0x0504
42#define CLOCK_CLK_DIV_TOP0			0x0510
43#define CLOCK_CLK_DIV_TOP1			0x0514
44#define CLOCK_PLL_DIV2_SEL			0x0A24
45#define CLOCK_MPLL_CON0				0x4100
46#define CLOCK_MPLL_CON1				0x4104
47
48/* bits and bytes */
49#define MPLL_FOUT_SEL_SHIFT			0x4
50#define MPLL_FOUT_SEL_MASK			0x1
51#define BPLL_FOUT_SEL_SHIFT			0x0
52#define BPLL_FOUT_SEL_MASK			0x1
53
54#define HCLK_FREQ				24000000
55
56#define HREAD4(sc, reg)							\
57	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
58#define HWRITE4(sc, reg, val)						\
59	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
60#define HSET4(sc, reg, bits)						\
61	HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
62#define HCLR4(sc, reg, bits)						\
63	HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
64
65struct exclock_softc {
66	struct device		sc_dev;
67	bus_space_tag_t		sc_iot;
68	bus_space_handle_t	sc_ioh;
69
70	struct clock_device	sc_cd;
71};
72
73enum clocks {
74	/* OSC */
75	OSC,		/* 24 MHz OSC */
76
77	/* PLLs */
78	APLL,		/* ARM core clock */
79	MPLL,		/* System bus clock for memory controller */
80	BPLL,		/* Graphic 3D processor clock and 1066 MHz clock for memory controller if necessary */
81	CPLL,		/* Multi Format Video Hardware Codec clock */
82	GPLL,		/* Graphic 3D processor clock or other clocks for DVFS flexibility */
83	EPLL,		/* Audio interface clocks and clocks for other external device interfaces */
84	VPLL,		/* dithered PLL, helps to reduce the EMI of display and camera */
85	KPLL,
86};
87
88struct exclock_softc *exclock_sc;
89
90int	exclock_match(struct device *, void *, void *);
91void	exclock_attach(struct device *, struct device *, void *);
92uint32_t exclock_decode_pll_clk(enum clocks, unsigned int, unsigned int);
93uint32_t exclock_get_pll_clk(struct exclock_softc *, enum clocks);
94uint32_t exclock_get_armclk(struct exclock_softc *);
95uint32_t exclock_get_kfcclk(struct exclock_softc *);
96unsigned int exclock_get_i2cclk(void);
97
98const struct cfattach	exclock_ca = {
99	sizeof (struct exclock_softc), exclock_match, exclock_attach
100};
101
102struct cfdriver exclock_cd = {
103	NULL, "exclock", DV_DULL
104};
105
106uint32_t exynos5250_get_frequency(void *, uint32_t *);
107int	exynos5250_set_frequency(void *, uint32_t *, uint32_t);
108void	exynos5250_enable(void *, uint32_t *, int);
109uint32_t exynos5420_get_frequency(void *, uint32_t *);
110int	exynos5420_set_frequency(void *, uint32_t *, uint32_t);
111void	exynos5420_enable(void *, uint32_t *, int);
112
113int
114exclock_match(struct device *parent, void *match, void *aux)
115{
116	struct fdt_attach_args *faa = aux;
117
118	if (OF_is_compatible(faa->fa_node, "samsung,exynos5250-clock") ||
119	    OF_is_compatible(faa->fa_node, "samsung,exynos5800-clock"))
120		return 10;	/* Must beat syscon(4). */
121
122	return 0;
123}
124
125void
126exclock_attach(struct device *parent, struct device *self, void *aux)
127{
128	struct exclock_softc *sc = (struct exclock_softc *)self;
129	struct fdt_attach_args *faa = aux;
130
131	sc->sc_iot = faa->fa_iot;
132	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
133	    faa->fa_reg[0].size, 0, &sc->sc_ioh))
134		panic("%s: bus_space_map failed!", __func__);
135
136	exclock_sc = sc;
137
138	printf("\n");
139
140	if (OF_is_compatible(faa->fa_node, "samsung,exynos5250-clock")) {
141		/* Exynos 5250 */
142		sc->sc_cd.cd_enable = exynos5250_enable;
143		sc->sc_cd.cd_get_frequency = exynos5250_get_frequency;
144		sc->sc_cd.cd_set_frequency = exynos5250_set_frequency;
145	} else {
146		/* Exynos 5420/5800 */
147		sc->sc_cd.cd_enable = exynos5420_enable;
148		sc->sc_cd.cd_get_frequency = exynos5420_get_frequency;
149		sc->sc_cd.cd_set_frequency = exynos5420_set_frequency;
150	}
151
152	sc->sc_cd.cd_node = faa->fa_node;
153	sc->sc_cd.cd_cookie = sc;
154	clock_register(&sc->sc_cd);
155}
156
157/*
158 * Exynos 5250
159 */
160
161/* Clocks */
162#define EXYNOS5250_CLK_ARM_CLK		9
163
164uint32_t
165exynos5250_get_frequency(void *cookie, uint32_t *cells)
166{
167	struct exclock_softc *sc = cookie;
168	uint32_t idx = cells[0];
169
170	switch (idx) {
171	case EXYNOS5250_CLK_ARM_CLK:
172		return exclock_get_armclk(sc);
173	}
174
175	printf("%s: 0x%08x\n", __func__, idx);
176	return 0;
177}
178
179int
180exynos5250_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
181{
182	uint32_t idx = cells[0];
183
184	switch (idx) {
185	case EXYNOS5250_CLK_ARM_CLK:
186		return -1;
187	}
188
189	printf("%s: 0x%08x\n", __func__, idx);
190	return -1;
191}
192
193void
194exynos5250_enable(void *cookie, uint32_t *cells, int on)
195{
196	uint32_t idx = cells[0];
197
198	printf("%s: 0x%08x\n", __func__, idx);
199}
200
201/*
202 * Exynos 5420/5800
203 */
204
205/* Clocks */
206#define EXYNOS5420_CLK_FIN_PLL		1
207#define EXYNOS5420_CLK_FOUT_RPLL	6
208#define EXYNOS5420_CLK_FOUT_SPLL	8
209#define EXYNOS5420_CLK_ARM_CLK		13
210#define EXYNOS5420_CLK_KFC_CLK		14
211#define EXYNOS5420_CLK_SCLK_MMC2	134
212#define EXYNOS5420_CLK_MMC2		353
213#define EXYNOS5420_CLK_USBH20		365
214#define EXYNOS5420_CLK_USBD300		366
215#define EXYNOS5420_CLK_USBD301		367
216#define EXYNOS5420_CLK_SCLK_SPLL	-1
217
218/* Registers */
219#define EXYNOS5420_RPLL_CON0		0x10140
220#define EXYNOS5420_RPLL_CON1		0x10144
221#define EXYNOS5420_SPLL_CON0		0x10160
222#define EXYNOS5420_SRC_TOP6		0x10218
223#define EXYNOS5420_DIV_FSYS1		0x1054c
224#define EXYNOS5420_SRC_FSYS		0x10244
225#define EXYNOS5420_GATE_TOP_SCLK_FSYS	0x10840
226#define EXYNOS5420_GATE_IP_FSYS		0x10944
227#define EXYNOS5420_KPLL_CON0		0x28100
228#define EXYNOS5420_SRC_KFC		0x28200
229#define EXYNOS5420_DIV_KFC0		0x28500
230
231uint32_t
232exynos5420_get_frequency(void *cookie, uint32_t *cells)
233{
234	struct exclock_softc *sc = cookie;
235	uint32_t idx = cells[0];
236	uint32_t reg, div, mux;
237	uint32_t kdiv, mdiv, pdiv, sdiv;
238	uint64_t freq;
239
240	switch (idx) {
241	case EXYNOS5420_CLK_FIN_PLL:
242		return 24000000;
243	case EXYNOS5420_CLK_ARM_CLK:
244		return exclock_get_armclk(sc);
245	case EXYNOS5420_CLK_KFC_CLK:
246		return exclock_get_kfcclk(sc);
247	case EXYNOS5420_CLK_SCLK_MMC2:
248		reg = HREAD4(sc, EXYNOS5420_DIV_FSYS1);
249		div = ((reg >> 20) & ((1 << 10) - 1)) + 1;
250		reg = HREAD4(sc, EXYNOS5420_SRC_FSYS);
251		mux = ((reg >> 16) & ((1 << 3) - 1));
252		switch (mux) {
253		case 0:
254			idx = EXYNOS5420_CLK_FIN_PLL;
255			break;
256		case 4:
257			idx = EXYNOS5420_CLK_SCLK_SPLL;
258			break;
259		default:
260			idx = 0;
261			break;
262		}
263		return exynos5420_get_frequency(sc, &idx) / div;
264	case EXYNOS5420_CLK_SCLK_SPLL:
265		reg = HREAD4(sc, EXYNOS5420_SRC_TOP6);
266		mux = ((reg >> 8) & ((1 << 1) - 1));
267		switch (mux) {
268		case 0:
269			idx = EXYNOS5420_CLK_FIN_PLL;
270			break;
271		case 1:
272			idx = EXYNOS5420_CLK_FOUT_SPLL;
273			break;
274		}
275		return exynos5420_get_frequency(sc, &idx);
276	case EXYNOS5420_CLK_FOUT_RPLL:
277		reg = HREAD4(sc, EXYNOS5420_RPLL_CON0);
278		mdiv = (reg >> 16) & 0x1ff;
279		pdiv = (reg >> 8) & 0x3f;
280		sdiv = (reg >> 0) & 0x7;
281		reg = HREAD4(sc, EXYNOS5420_RPLL_CON1);
282		kdiv = (reg >> 0) & 0xffff;
283		idx = EXYNOS5420_CLK_FIN_PLL;
284		freq = exynos5420_get_frequency(sc, &idx);
285		freq = ((mdiv << 16) + kdiv) * freq / (pdiv * (1 << sdiv));
286		return (freq >> 16);
287	case EXYNOS5420_CLK_FOUT_SPLL:
288		reg = HREAD4(sc, EXYNOS5420_SPLL_CON0);
289		mdiv = (reg >> 16) & 0x3ff;
290		pdiv = (reg >> 8) & 0x3f;
291		sdiv = (reg >> 0) & 0x7;
292		idx = EXYNOS5420_CLK_FIN_PLL;
293		freq = exynos5420_get_frequency(sc, &idx);
294		return mdiv * freq / (pdiv * (1 << sdiv));
295	}
296
297	printf("%s: 0x%08x\n", __func__, idx);
298	return 0;
299}
300
301int
302exynos5420_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
303{
304	uint32_t idx = cells[0];
305
306	switch (idx) {
307	case EXYNOS5420_CLK_ARM_CLK:
308	case EXYNOS5420_CLK_KFC_CLK:
309		return -1;
310	}
311
312	printf("%s: 0x%08x\n", __func__, idx);
313	return -1;
314}
315
316void
317exynos5420_enable(void *cookie, uint32_t *cells, int on)
318{
319	uint32_t idx = cells[0];
320
321	switch (idx) {
322	case EXYNOS5420_CLK_SCLK_MMC2:	/* CLK_GATE_TOP_SCLK_FSYS */
323	case EXYNOS5420_CLK_MMC2:	/* CLK_GATE_IP_FSYS */
324	case EXYNOS5420_CLK_USBH20:	/* CLK_GATE_IP_FSYS */
325	case EXYNOS5420_CLK_USBD300:	/* CLK_GATE_IP_FSYS */
326	case EXYNOS5420_CLK_USBD301:	/* CLK_GATE_IP_FSYS */
327		/* Enabled by default. */
328		return;
329	}
330
331	printf("%s: 0x%08x\n", __func__, idx);
332}
333
334uint32_t
335exclock_decode_pll_clk(enum clocks pll, unsigned int r, unsigned int k)
336{
337	uint64_t freq;
338	uint32_t m, p, s = 0, mask, fout;
339	/*
340	 * APLL_CON: MIDV [25:16]
341	 * MPLL_CON: MIDV [25:16]
342	 * EPLL_CON: MIDV [24:16]
343	 * VPLL_CON: MIDV [24:16]
344	 * BPLL_CON: MIDV [25:16]: Exynos5
345	 */
346
347	switch (pll)
348	{
349	case APLL:
350	case MPLL:
351	case BPLL:
352	case KPLL:
353		mask = 0x3ff;
354		break;
355	default:
356		mask = 0x1ff;
357	}
358
359	m = (r >> 16) & mask;
360
361	/* PDIV [13:8] */
362	p = (r >> 8) & 0x3f;
363	/* SDIV [2:0] */
364	s = r & 0x7;
365
366	freq = HCLK_FREQ;
367
368	if (pll == EPLL) {
369		k = k & 0xffff;
370		/* FOUT = (MDIV + K / 65536) * FIN / (PDIV * 2^SDIV) */
371		fout = (m + k / 65536) * (freq / (p * (1 << s)));
372	} else if (pll == VPLL) {
373		k = k & 0xfff;
374		/* FOUT = (MDIV + K / 1024) * FIN / (PDIV * 2^SDIV) */
375		fout = (m + k / 1024) * (freq / (p * (1 << s)));
376	} else {
377		/* FOUT = MDIV * FIN / (PDIV * 2^(SDIV - 1)) */
378		fout = m * (freq / (p * (1 << s)));
379	}
380
381	return fout;
382}
383
384uint32_t
385exclock_get_pll_clk(struct exclock_softc *sc, enum clocks pll)
386{
387	uint32_t freq;
388
389	switch (pll) {
390	case APLL:
391		freq = exclock_decode_pll_clk(pll,
392		    HREAD4(sc, CLOCK_APLL_CON0), 0);
393		break;
394	case MPLL:
395		freq = exclock_decode_pll_clk(pll,
396		    HREAD4(sc, CLOCK_MPLL_CON0), 0);
397		break;
398	case BPLL:
399		freq = exclock_decode_pll_clk(pll,
400		    HREAD4(sc, CLOCK_BPLL_CON0), 0);
401		break;
402	case EPLL:
403		freq = exclock_decode_pll_clk(pll,
404		    HREAD4(sc, CLOCK_EPLL_CON0),
405		    HREAD4(sc, CLOCK_EPLL_CON1));
406		break;
407	case VPLL:
408		freq = exclock_decode_pll_clk(pll,
409		    HREAD4(sc, CLOCK_VPLL_CON0),
410		    HREAD4(sc, CLOCK_VPLL_CON1));
411		break;
412	case KPLL:
413		freq = exclock_decode_pll_clk(pll,
414		    HREAD4(sc, EXYNOS5420_KPLL_CON0), 0);
415		break;
416	default:
417		return 0;
418	}
419
420	/*
421	 * According to the user manual, in EVT1 MPLL and BPLL always gives
422	 * 1.6GHz clock, so divide by 2 to get 800MHz MPLL clock.
423	 */
424	if (pll == MPLL || pll == BPLL) {
425		uint32_t freq_sel;
426		uint32_t pll_div2_sel = HREAD4(sc, CLOCK_PLL_DIV2_SEL);
427
428		switch (pll) {
429		case MPLL:
430			freq_sel = (pll_div2_sel >> MPLL_FOUT_SEL_SHIFT)
431					& MPLL_FOUT_SEL_MASK;
432			break;
433		case BPLL:
434			freq_sel = (pll_div2_sel >> BPLL_FOUT_SEL_SHIFT)
435					& BPLL_FOUT_SEL_MASK;
436			break;
437		default:
438			freq_sel = -1;
439			break;
440		}
441
442		if (freq_sel == 0)
443			freq /= 2;
444	}
445
446	return freq;
447}
448
449uint32_t
450exclock_get_armclk(struct exclock_softc *sc)
451{
452	uint32_t div, armclk, arm_ratio, arm2_ratio;
453
454	div = HREAD4(sc, CLOCK_CLK_DIV_CPU0);
455
456	/* ARM_RATIO: [2:0], ARM2_RATIO: [30:28] */
457	arm_ratio = (div >> 0) & 0x7;
458	arm2_ratio = (div >> 28) & 0x7;
459
460	armclk = exclock_get_pll_clk(sc, APLL) / (arm_ratio + 1);
461	armclk /= (arm2_ratio + 1);
462
463	return armclk;
464}
465
466uint32_t
467exclock_get_kfcclk(struct exclock_softc *sc)
468{
469	uint32_t div, kfc_ratio;
470
471	div = HREAD4(sc, EXYNOS5420_DIV_KFC0);
472
473	/* KFC_RATIO: [2:0] */
474	kfc_ratio = (div >> 0) & 0x7;
475
476	return exclock_get_pll_clk(sc, KPLL) / (kfc_ratio + 1);
477}
478
479unsigned int
480exclock_get_i2cclk(void)
481{
482	struct exclock_softc *sc = exclock_sc;
483	uint32_t aclk_66, aclk_66_pre, div, ratio;
484
485	div = HREAD4(sc, CLOCK_CLK_DIV_TOP1);
486	ratio = (div >> 24) & 0x7;
487	aclk_66_pre = exclock_get_pll_clk(sc, MPLL) / (ratio + 1);
488	div = HREAD4(sc, CLOCK_CLK_DIV_TOP0);
489	ratio = (div >> 0) & 0x7;
490	aclk_66 = aclk_66_pre / (ratio + 1);
491
492	return aclk_66 / 1000;
493}
494