1/*	$OpenBSD: sfclock.c,v 1.2 2022/04/06 18:59:27 naddy Exp $	*/
2/*
3 * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
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/intr.h>
23#include <machine/bus.h>
24#include <machine/fdt.h>
25
26#include <dev/ofw/openfirm.h>
27#include <dev/ofw/ofw_clock.h>
28#include <dev/ofw/ofw_misc.h>
29#include <dev/ofw/fdt.h>
30
31/* Clock IDs */
32#define FU740_CLK_COREPLL	0
33#define FU740_CLK_DDRPLL	1
34#define FU740_CLK_GEMGXLPLL	2
35#define FU740_CLK_DVFSCOREPLL	3
36#define FU740_CLK_HFPCLKPLL	4
37#define FU740_CLK_CLTXPLL	5
38#define FU740_CLK_TLCLK		6
39#define FU740_CLK_PCLK		7
40#define FU740_CLK_PCIE_AUX	8
41
42/* Registers */
43#define CORE_PLLCFG		0x04
44#define GEMGXL_PLLCFG		0x1c
45#define HFPCLK_PLLCFG		0x50
46#define HFPCLK_PLLOUTDIV	0x54
47#define HFPCLKPLLSEL		0x58
48#define  HFPCLKPLLSEL_HFCLK	(1 << 0)
49#define HFPCLK_DIV		0x5c
50
51#define PLLCFG_PLLR(x)		(((x) >> 0) & 0x3f)
52#define PLLCFG_PLLF(x)		(((x) >> 6) & 0x1ff)
53#define PLLCFG_PLLQ(x)		(((x) >> 15) & 0x7)
54
55#define HREAD4(sc, reg)							\
56	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
57#define HWRITE4(sc, reg, val)						\
58	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
59
60struct sfclock_softc {
61	struct device		sc_dev;
62	bus_space_tag_t		sc_iot;
63	bus_space_handle_t	sc_ioh;
64	int			sc_node;
65
66	struct clock_device	sc_cd;
67};
68
69int	sfclock_match(struct device *, void *, void *);
70void	sfclock_attach(struct device *, struct device *, void *);
71
72const struct cfattach sfclock_ca = {
73	sizeof (struct sfclock_softc), sfclock_match, sfclock_attach
74};
75
76struct cfdriver sfclock_cd = {
77	NULL, "sfclock", DV_DULL
78};
79
80uint32_t sfclock_get_frequency(void *, uint32_t *);
81int	sfclock_set_frequency(void *, uint32_t *, uint32_t);
82void	sfclock_enable(void *, uint32_t *, int);
83
84int
85sfclock_match(struct device *parent, void *match, void *aux)
86{
87	struct fdt_attach_args *faa = aux;
88
89	return OF_is_compatible(faa->fa_node, "sifive,fu740-c000-prci");
90}
91
92void
93sfclock_attach(struct device *parent, struct device *self, void *aux)
94{
95	struct sfclock_softc *sc = (struct sfclock_softc *)self;
96	struct fdt_attach_args *faa = aux;
97
98	if (faa->fa_nreg < 1) {
99		printf(": no registers\n");
100		return;
101	}
102
103	sc->sc_iot = faa->fa_iot;
104	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
105	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
106		printf(": can't map registers\n");
107		return;
108	}
109
110	sc->sc_node = faa->fa_node;
111
112	printf("\n");
113
114	sc->sc_cd.cd_node = faa->fa_node;
115	sc->sc_cd.cd_cookie = sc;
116	sc->sc_cd.cd_get_frequency = sfclock_get_frequency;
117	sc->sc_cd.cd_set_frequency = sfclock_set_frequency;
118	sc->sc_cd.cd_enable = sfclock_enable;
119	clock_register(&sc->sc_cd);
120}
121
122uint32_t
123sfclock_getpll_frequency(struct sfclock_softc *sc, bus_size_t off)
124{
125	uint64_t parent_freq = clock_get_frequency_idx(sc->sc_node, 0);
126	uint32_t pllr, pllf, pllq;
127	uint32_t reg;
128
129	reg = HREAD4(sc, off);
130	pllr = PLLCFG_PLLR(reg);
131	pllf = PLLCFG_PLLF(reg);
132	pllq = PLLCFG_PLLQ(reg);
133	return ((parent_freq * 2 * (pllf + 1)) / (pllr + 1)) >> pllq;
134}
135
136uint32_t
137sfclock_get_frequency(void *cookie, uint32_t *cells)
138{
139	struct sfclock_softc *sc = cookie;
140	uint32_t idx = cells[0];
141	uint32_t reg, div;
142
143	switch (idx) {
144	case FU740_CLK_COREPLL:
145		return sfclock_getpll_frequency(sc, CORE_PLLCFG);
146	case FU740_CLK_GEMGXLPLL:
147		return sfclock_getpll_frequency(sc, GEMGXL_PLLCFG);
148	case FU740_CLK_HFPCLKPLL:
149		reg = HREAD4(sc, HFPCLKPLLSEL);
150		if (reg & HFPCLKPLLSEL_HFCLK)
151			return clock_get_frequency_idx(sc->sc_node, 0);
152		return sfclock_getpll_frequency(sc, HFPCLK_PLLCFG);
153	case FU740_CLK_PCLK:
154		div = HREAD4(sc, HFPCLK_DIV) + 2;
155		idx = FU740_CLK_HFPCLKPLL;
156		return sfclock_get_frequency(sc, &idx) / div;
157	}
158
159	printf("%s: 0x%08x\n", __func__, idx);
160	return 0;
161}
162
163int
164sfclock_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
165{
166	uint32_t idx = cells[0];
167
168	printf("%s: 0x%08x\n", __func__, idx);
169	return -1;
170}
171
172void
173sfclock_enable(void *cookie, uint32_t *cells, int on)
174{
175	uint32_t idx = cells[0];
176
177	switch (idx) {
178	case FU740_CLK_PCLK:
179		return;
180	}
181
182	printf("%s: 0x%08x\n", __func__, idx);
183}
184