1/*	$OpenBSD: sxipwm.c,v 1.2 2021/10/24 17:52:27 mpi Exp $	*/
2/*
3 * Copyright (c) 2019 Krystian Lewandowski
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#include <sys/malloc.h>
22
23#include <machine/fdt.h>
24#include <machine/bus.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/ofw_pinctrl.h>
30#include <dev/ofw/fdt.h>
31
32#define PWM_CTRL_REG		0x0
33#define  PWM0_RDY			(1 << 28)
34#define  SCLK_CH0_GATING		(1 << 6)
35#define  PWM_CH0_ACT_STA		(1 << 5)
36#define  PWM_CH0_EN			(1 << 4)
37#define  PWM_CH0_PRESCAL		0xf
38#define PWM_CH0_PERIOD		0x4
39#define  PWM_CH0_CYCLES_SHIFT		16
40#define  PWM_CH0_ACT_CYCLES_SHIFT	0
41#define  PWM_CH0_CYCLES_MAX		0xffff
42
43#define NS_PER_S		1000000000
44
45#define HREAD4(sc, reg)							\
46	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
47#define HWRITE4(sc, reg, val)						\
48	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
49
50struct sxipwm_prescaler {
51	uint32_t	divider;
52	uint8_t		value;
53};
54
55const struct sxipwm_prescaler sxipwm_prescalers[] = {
56	{ 1, 0xf },
57	{ 120, 0x0 },
58	{ 180, 0x1 },
59	{ 240, 0x2 },
60	{ 360, 0x3 },
61	{ 480, 0x4 },
62	{ 12000, 0x8 },
63	{ 24000, 0x9 },
64	{ 36000, 0xa },
65	{ 48000, 0xb },
66	{ 72000, 0xc },
67	{ 0 }
68};
69
70struct sxipwm_softc {
71	struct device		sc_dev;
72	bus_space_tag_t		sc_iot;
73	bus_space_handle_t	sc_ioh;
74
75	uint32_t		sc_clkin;
76	struct pwm_device	sc_pd;
77};
78
79int	sxipwm_match(struct device *, void *, void *);
80void	sxipwm_attach(struct device *, struct device *, void *);
81
82const struct cfattach sxipwm_ca = {
83	sizeof(struct sxipwm_softc), sxipwm_match, sxipwm_attach
84};
85
86struct cfdriver sxipwm_cd = {
87	NULL, "sxipwm", DV_DULL
88};
89
90int	sxipwm_get_state(void *, uint32_t *, struct pwm_state *);
91int	sxipwm_set_state(void *, uint32_t *, struct pwm_state *);
92
93int
94sxipwm_match(struct device *parent, void *match, void *aux)
95{
96	struct fdt_attach_args *faa = aux;
97
98	return OF_is_compatible(faa->fa_node, "allwinner,sun5i-a13-pwm");
99}
100
101void
102sxipwm_attach(struct device *parent, struct device *self, void *aux)
103{
104	struct sxipwm_softc *sc = (struct sxipwm_softc *)self;
105	struct fdt_attach_args *faa = aux;
106
107	if (faa->fa_nreg < 1) {
108		printf(": no registers\n");
109		return;
110	}
111
112	sc->sc_clkin = clock_get_frequency_idx(faa->fa_node, 0);
113	if (sc->sc_clkin == 0) {
114		printf(": no clock\n");
115		return;
116	}
117
118	sc->sc_iot = faa->fa_iot;
119	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
120	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
121		printf(": can't map registers\n");
122		return;
123	}
124
125	printf("\n");
126
127	pinctrl_byname(faa->fa_node, "default");
128
129	clock_enable_all(faa->fa_node);
130	reset_deassert_all(faa->fa_node);
131
132	sc->sc_pd.pd_node = faa->fa_node;
133	sc->sc_pd.pd_cookie = sc;
134	sc->sc_pd.pd_get_state = sxipwm_get_state;
135	sc->sc_pd.pd_set_state = sxipwm_set_state;
136
137	pwm_register(&sc->sc_pd);
138}
139
140int
141sxipwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
142{
143	struct sxipwm_softc *sc = cookie;
144	uint32_t idx = cells[0];
145	uint32_t ctrl, ch_period;
146	uint64_t rate, cycles, act_cycles;
147	int i, prescaler;
148
149	if (idx != 0)
150		return EINVAL;
151
152	ctrl = HREAD4(sc, PWM_CTRL_REG);
153	ch_period = HREAD4(sc, PWM_CH0_PERIOD);
154
155	prescaler = -1;
156	for (i = 0; sxipwm_prescalers[i].divider; i++) {
157		if ((ctrl & PWM_CH0_PRESCAL) == sxipwm_prescalers[i].value) {
158			prescaler = i;
159			break;
160		}
161	}
162	if (prescaler < 0)
163		return EINVAL;
164
165	rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider;
166	cycles = ((ch_period >> PWM_CH0_CYCLES_SHIFT) &
167	    PWM_CH0_CYCLES_MAX) + 1;
168	act_cycles = (ch_period >> PWM_CH0_ACT_CYCLES_SHIFT) &
169	    PWM_CH0_CYCLES_MAX;
170
171	memset(ps, 0, sizeof(struct pwm_state));
172	ps->ps_period = (NS_PER_S * cycles) / rate;
173	ps->ps_pulse_width = (NS_PER_S * act_cycles) / rate;
174	if ((ctrl & PWM_CH0_EN)  && (ctrl & SCLK_CH0_GATING))
175		ps->ps_enabled = 1;
176
177	return 0;
178}
179
180int
181sxipwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
182{
183	struct sxipwm_softc *sc = cookie;
184	uint32_t idx = cells[0];
185	uint64_t rate, cycles, act_cycles;
186	uint32_t reg;
187	int i, prescaler;
188
189	if (idx != 0)
190		return EINVAL;
191
192	prescaler = -1;
193	for (i = 0; sxipwm_prescalers[i].divider; i++) {
194		rate = sc->sc_clkin / sxipwm_prescalers[i].divider;
195		cycles = (rate * ps->ps_period) / NS_PER_S;
196		if ((cycles - 1) < PWM_CH0_CYCLES_MAX) {
197			prescaler = i;
198			break;
199		}
200	}
201	if (prescaler < 0)
202		return EINVAL;
203
204	rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider;
205	cycles = (rate * ps->ps_period) / NS_PER_S;
206	act_cycles = (rate * ps->ps_pulse_width) / NS_PER_S;
207	if (cycles < 1 || act_cycles > cycles)
208		return EINVAL;
209
210	KASSERT(cycles - 1 <= PWM_CH0_CYCLES_MAX);
211	KASSERT(act_cycles <= PWM_CH0_CYCLES_MAX);
212
213	reg = HREAD4(sc, PWM_CTRL_REG);
214	if (reg & PWM0_RDY)
215		return EBUSY;
216	if (ps->ps_enabled)
217		reg |= (PWM_CH0_EN | SCLK_CH0_GATING);
218	else
219		reg &= ~(PWM_CH0_EN | SCLK_CH0_GATING);
220	if (ps->ps_flags & PWM_POLARITY_INVERTED)
221		reg &= ~PWM_CH0_ACT_STA;
222	else
223		reg |= PWM_CH0_ACT_STA;
224	reg &= ~PWM_CH0_PRESCAL;
225	reg |= sxipwm_prescalers[prescaler].value;
226	HWRITE4(sc, PWM_CTRL_REG, reg);
227
228	reg = ((cycles - 1) << PWM_CH0_CYCLES_SHIFT) |
229	    (act_cycles << PWM_CH0_ACT_CYCLES_SHIFT);
230	HWRITE4(sc, PWM_CH0_PERIOD, reg);
231
232	return 0;
233}
234