1/*	$OpenBSD: sxitimer.c,v 1.1 2024/01/27 12:05:40 kettenis Exp $	*/
2/*
3 * Copyright (c) 2024 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/clockintr.h>
21#include <sys/device.h>
22#include <sys/sensors.h>
23
24#include <machine/intr.h>
25#include <machine/bus.h>
26#include <machine/fdt.h>
27
28#include <dev/ofw/openfirm.h>
29#include <dev/ofw/ofw_clock.h>
30#include <dev/ofw/fdt.h>
31
32/* Registers */
33#define TMR_IRQ_EN		0x0000
34#define  TMR1_IRQ_EN		(1 << 1)
35#define  TMR0_IRQ_EN		(1 << 0)
36#define TMR_IRQ_STA		0x0004
37#define  TMR1_IRQ_PEND		(1 << 1)
38#define  TMR0_IRQ_PEND		(1 << 0)
39#define TMR0_CTRL		0x0010
40#define  TMR0_MODE_SINGLE	(1 << 7)
41#define  TMR0_CLK_PRES_1	(0 << 4)
42#define  TMR0_CLK_SRC_OSC24M	(1 << 2)
43#define  TMR0_RELOAD		(1 << 1)
44#define  TMR0_EN		(1 << 0)
45#define TMR0_INTV_VALUE		0x0014
46
47#define HREAD4(sc, reg)							\
48	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
49#define HWRITE4(sc, reg, val)						\
50	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
51
52struct sxitimer_softc {
53	struct device		sc_dev;
54	bus_space_tag_t		sc_iot;
55	bus_space_handle_t	sc_ioh;
56
57	uint32_t		sc_ticks_per_second;
58	uint64_t		sc_nsec_cycle_ratio;
59	uint64_t		sc_nsec_max;
60	void			*sc_ih;
61};
62
63int	sxitimer_match(struct device *, void *, void *);
64void	sxitimer_attach(struct device *, struct device *, void *);
65
66const struct cfattach sxitimer_ca = {
67	sizeof (struct sxitimer_softc), sxitimer_match, sxitimer_attach
68};
69
70struct cfdriver sxitimer_cd = {
71	NULL, "sxitimer", DV_DULL
72};
73
74void	sxitimer_startclock(void);
75int	sxitimer_intr(void *);
76void	sxitimer_rearm(void *, uint64_t);
77void	sxitimer_trigger(void *);
78
79struct intrclock sxitimer_intrclock = {
80	.ic_rearm = sxitimer_rearm,
81	.ic_trigger = sxitimer_trigger
82};
83
84int
85sxitimer_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, "allwinner,sun20i-d1-timer");
90}
91
92void
93sxitimer_attach(struct device *parent, struct device *self, void *aux)
94{
95	struct sxitimer_softc *sc = (struct sxitimer_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	HWRITE4(sc, TMR_IRQ_EN, 0);
111
112	sc->sc_ticks_per_second = clock_get_frequency(faa->fa_node, NULL);
113	sc->sc_nsec_cycle_ratio =
114	    sc->sc_ticks_per_second * (1ULL << 32) / 1000000000;
115	sc->sc_nsec_max = UINT64_MAX / sc->sc_nsec_cycle_ratio;
116
117	sxitimer_intrclock.ic_cookie = sc;
118	cpu_startclock_fcn = sxitimer_startclock;
119
120	sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, 0, IPL_CLOCK,
121	    sxitimer_intr, NULL, sc->sc_dev.dv_xname);
122	if (sc->sc_ih == NULL) {
123		printf("can't establish interrupt\n");
124		return;
125	}
126
127	HWRITE4(sc, TMR0_INTV_VALUE, 0);
128	HWRITE4(sc, TMR0_CTRL, TMR0_MODE_SINGLE | TMR0_CLK_PRES_1 |
129	    TMR0_CLK_SRC_OSC24M | TMR0_RELOAD | TMR0_EN);
130	HWRITE4(sc, TMR_IRQ_STA, TMR0_IRQ_PEND);
131	HWRITE4(sc, TMR_IRQ_EN, TMR0_IRQ_EN);
132
133	printf(": %u kHz\n", sc->sc_ticks_per_second / 1000);
134}
135
136void
137sxitimer_startclock(void)
138{
139	clockintr_cpu_init(&sxitimer_intrclock);
140	clockintr_trigger();
141}
142
143int
144sxitimer_intr(void *frame)
145{
146	struct sxitimer_softc *sc = sxitimer_intrclock.ic_cookie;
147
148	HWRITE4(sc, TMR_IRQ_STA, TMR0_IRQ_PEND);
149	return clockintr_dispatch(frame);
150}
151
152void
153sxitimer_rearm(void *cookie, uint64_t nsecs)
154{
155	struct sxitimer_softc *sc = cookie;
156	uint32_t cycles;
157
158	if (nsecs > sc->sc_nsec_max)
159		nsecs = sc->sc_nsec_max;
160	cycles = (nsecs * sc->sc_nsec_cycle_ratio) >> 32;
161	if (cycles > UINT32_MAX)
162		cycles = UINT32_MAX;
163	if (cycles < 1)
164		cycles = 1;
165	HWRITE4(sc, TMR0_INTV_VALUE, cycles);
166	HWRITE4(sc, TMR0_CTRL, TMR0_MODE_SINGLE | TMR0_CLK_PRES_1 |
167	    TMR0_CLK_SRC_OSC24M | TMR0_RELOAD | TMR0_EN);
168}
169
170void
171sxitimer_trigger(void *cookie)
172{
173	struct sxitimer_softc *sc = cookie;
174
175	HWRITE4(sc, TMR0_INTV_VALUE, 1);
176	HWRITE4(sc, TMR0_CTRL, TMR0_MODE_SINGLE | TMR0_CLK_PRES_1 |
177	    TMR0_CLK_SRC_OSC24M | TMR0_RELOAD | TMR0_EN);
178}
179