meson_pwm.c revision 1.2
1/* $NetBSD: meson_pwm.c,v 1.2 2021/01/18 02:35:48 thorpej Exp $ */
2
3/*
4 * Copyright (c) 2021 Ryo Shimizu <ryo@nerv.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
17 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
20 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
25 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: meson_pwm.c,v 1.2 2021/01/18 02:35:48 thorpej Exp $");
31
32#include <sys/param.h>
33#include <sys/types.h>
34#include <sys/bus.h>
35#include <sys/device.h>
36
37#include <dev/fdt/fdtvar.h>
38#include <dev/pwm/pwmvar.h>
39
40/*#define MESON_PWM_DEBUG*/
41
42#define MESON_PWM_DUTYCYCLE_A_REG	0x00
43#define MESON_PWM_DUTYCYCLE_B_REG	0x01
44#define  MESON_PWM_DUTYCYCLE_HIGH	__BITS(31,16)
45#define  MESON_PWM_DUTYCYCLE_LOW	__BITS(15,0)
46#define MESON_PWM_MISC_AB_REG		0x02
47#define  MESON_PWM_MISC_AB_B_CLK_EN	__BIT(23)
48#define  MESON_PWM_MISC_AB_B_CLK_DIV	__BITS(22,16)
49#define  MESON_PWM_MISC_AB_A_CLK_EN	__BIT(15)
50#define  MESON_PWM_MISC_AB_A_CLK_DIV	__BITS(14,8)
51#define  MESON_PWM_MISC_AB_B_CLK_SEL	__BITS(7,6)
52#define  MESON_PWM_MISC_AB_A_CLK_SEL	__BITS(5,4)
53#define  MESON_PWM_MISC_AB_DS_B_EN	__BIT(3)
54#define  MESON_PWM_MISC_AB_DS_A_EN	__BIT(2)
55#define  MESON_PWM_MISC_AB_PWM_B_EN	__BIT(1)
56#define  MESON_PWM_MISC_AB_PWM_A_EN	__BIT(0)
57#define MESON_PWM_DELTASIGMA_A_B_REG	0x03
58#define MESON_PWM_TIME_AB_REG		0x04
59#define MESON_PWM_A2_REG		0x05
60#define MESON_PWM_B2_REG		0x06
61#define MESON_PWM_BLINK_AB_REG		0x07
62
63#define PWM_READ_REG(sc, reg) \
64	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg) * 4)
65#define PWM_WRITE_REG(sc, reg, val) \
66	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg) * 4, (val))
67
68static const struct device_compatible_entry compat_data[] = {
69	{ .compat = "amlogic,meson-g12a-ao-pwm-ab" },
70	{ .compat = "amlogic,meson-g12a-ao-pwm-cd" },
71	{ .compat = "amlogic,meson-g12a-ee-pwm" },
72
73	{ 0 }
74};
75
76#define MESON_PWM_NCHAN	2
77struct meson_pwm_channel {
78	struct meson_pwm_softc *mpc_sc;
79	struct pwm_controller mpc_pwm;
80	struct pwm_config mpc_conf;
81	u_int mpc_index;
82};
83
84struct meson_pwm_softc {
85	device_t sc_dev;
86	bus_space_tag_t sc_bst;
87	bus_space_handle_t sc_bsh;
88	kmutex_t sc_reglock;	/* for PWM_A vs. PWM_B */
89	int sc_phandle;
90	u_int sc_clkfreq;
91	u_int sc_clksource;
92	struct meson_pwm_channel sc_pwmchan[MESON_PWM_NCHAN];
93};
94
95static int
96meson_pwm_enable(pwm_tag_t pwm, bool enable)
97{
98	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
99	struct meson_pwm_softc * const sc = device_private(pwm->pwm_dev);
100	uint32_t val;
101
102	mutex_enter(&sc->sc_reglock);
103	switch (pwmchan->mpc_index) {
104	case 0: /* A */
105		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
106		val &= ~MESON_PWM_MISC_AB_DS_A_EN;
107		val |= MESON_PWM_MISC_AB_PWM_A_EN;
108		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
109		break;
110	case 1: /* B */
111		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
112		val &= ~MESON_PWM_MISC_AB_DS_B_EN;
113		val |= MESON_PWM_MISC_AB_PWM_B_EN;
114		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
115		break;
116	}
117	mutex_exit(&sc->sc_reglock);
118
119	return 0;
120}
121
122static void
123meson_pwm_get_current(struct meson_pwm_softc *sc, int chan,
124    struct pwm_config *conf)
125{
126	uint64_t period_hz, duty_hz;
127	uint32_t val;
128	u_int period, duty, clk_div, hi, lo;
129
130	memset(conf, 0, sizeof(*conf));
131
132	mutex_enter(&sc->sc_reglock);
133	switch (chan) {
134	case 0: /* A */
135		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
136		clk_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_A_CLK_DIV);
137		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_A_REG);
138		hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
139		lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
140		break;
141	case 1: /* B */
142		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
143		clk_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_B_CLK_DIV);
144		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_B_REG);
145		hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
146		lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
147		break;
148	default:
149		mutex_exit(&sc->sc_reglock);
150		return;
151	}
152	mutex_exit(&sc->sc_reglock);
153
154	clk_div += 1;
155	duty_hz = (uint64_t)hi * clk_div;
156	period_hz = (uint64_t)(hi + lo) * clk_div;
157
158	period = period_hz * 1000000000ULL / sc->sc_clkfreq;
159	duty = duty_hz * 1000000000ULL / sc->sc_clkfreq;
160
161	conf->polarity = PWM_ACTIVE_HIGH;
162	conf->period = period;
163	conf->duty_cycle = duty;
164}
165
166static int
167meson_pwm_get_config(pwm_tag_t pwm, struct pwm_config *conf)
168{
169	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
170
171	*conf = pwmchan->mpc_conf;
172	return 0;
173}
174
175static int
176meson_pwm_set_config(pwm_tag_t pwm, const struct pwm_config *conf)
177{
178	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
179	struct meson_pwm_softc * const sc = device_private(pwm->pwm_dev);
180	uint64_t period_hz, duty_hz;
181	uint32_t val;
182	u_int period, duty, clk_div, hi, lo;
183#ifdef MESON_PWM_DEBUG
184	u_int old_div = 0, old_hi = 0, old_lo = 0;
185#endif
186
187	period = conf->period;
188	duty = conf->duty_cycle;
189	if (period == 0)
190		return EINVAL;
191	KASSERT(period >= duty);
192	if (conf->polarity == PWM_ACTIVE_LOW)
193		duty = period - duty;
194
195	/* calculate the period to be within the maximum value (0xffff) */
196#define MESON_PWMTIME_MAX	0xffff
197#define MESON_CLKDIV_MAX	127
198	clk_div = 1;
199	period_hz = ((uint64_t)sc->sc_clkfreq * period + 500000000ULL) /
200	    1000000000ULL;
201	duty_hz = ((uint64_t)sc->sc_clkfreq * duty + 500000000ULL) /
202	    1000000000ULL;
203	if (period_hz > MESON_PWMTIME_MAX) {
204		clk_div = (period_hz + 0x7fff) / 0xffff;
205		period_hz /= clk_div;
206		duty_hz /= clk_div;
207	}
208
209	clk_div -= 1;	/* the divider is N+1 */
210	if (clk_div > MESON_CLKDIV_MAX)
211		return EINVAL;
212
213	hi = duty_hz;
214	lo = period_hz - duty_hz;
215
216	mutex_enter(&sc->sc_reglock);
217	switch (pwmchan->mpc_index) {
218	case 0: /* A */
219		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
220		val &= ~MESON_PWM_MISC_AB_A_CLK_DIV;
221#ifdef MESON_PWM_DEBUG
222		old_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_A_CLK_DIV);
223#endif
224		val |= __SHIFTIN(clk_div, MESON_PWM_MISC_AB_A_CLK_DIV);
225		val &= ~MESON_PWM_MISC_AB_A_CLK_SEL;
226		val |= __SHIFTIN(sc->sc_clksource, MESON_PWM_MISC_AB_A_CLK_SEL);
227		val |= MESON_PWM_MISC_AB_A_CLK_EN;
228		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
229		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_A_REG);
230#ifdef MESON_PWM_DEBUG
231		old_hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
232		old_lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
233#endif
234		PWM_WRITE_REG(sc, MESON_PWM_DUTYCYCLE_A_REG,
235		    __SHIFTIN(hi, MESON_PWM_DUTYCYCLE_HIGH) |
236		    __SHIFTIN(lo, MESON_PWM_DUTYCYCLE_LOW));
237		break;
238	case 1: /* B */
239		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
240		val &= ~MESON_PWM_MISC_AB_B_CLK_DIV;
241#ifdef MESON_PWM_DEBUG
242		old_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_B_CLK_DIV);
243#endif
244		val |= __SHIFTIN(clk_div, MESON_PWM_MISC_AB_B_CLK_DIV);
245		val &= ~MESON_PWM_MISC_AB_B_CLK_SEL;
246		val |= __SHIFTIN(sc->sc_clksource, MESON_PWM_MISC_AB_B_CLK_SEL);
247		val |= MESON_PWM_MISC_AB_B_CLK_EN;
248		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
249		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_B_REG);
250#ifdef MESON_PWM_DEBUG
251		old_hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
252		old_lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
253#endif
254		PWM_WRITE_REG(sc, MESON_PWM_DUTYCYCLE_B_REG,
255		    __SHIFTIN(hi, MESON_PWM_DUTYCYCLE_HIGH) |
256		    __SHIFTIN(lo, MESON_PWM_DUTYCYCLE_LOW));
257		break;
258	}
259	mutex_exit(&sc->sc_reglock);
260
261	pwmchan->mpc_conf = *conf;
262
263#ifdef MESON_PWM_DEBUG
264	device_printf(sc->sc_dev,
265	    "%s: %s: polarity=%s, DutuCycle/Period=%uns/%uns(%u%%) : "
266	    "%uHz, HIGH:LOW=%u:%u(%u%%) -> "
267	    "%uHz, HIGH:LOW=%u:%u(%u%%)\n", __func__,
268	    pwmchan->mpc_index ? "A" : "B",
269	    (conf->polarity == PWM_ACTIVE_LOW) ? "LOW" : "HIGH",
270	    conf->duty_cycle, conf->period,
271	    conf->duty_cycle * 100 / conf->period,
272	    sc->sc_clkfreq / (old_div + 1), old_hi, old_lo,
273	    old_hi * 100 / (old_hi + old_lo),
274	    sc->sc_clkfreq / (clk_div + 1), hi, lo, hi * 100 / (hi + lo));
275#endif
276	return 0;
277}
278
279static pwm_tag_t
280meson_pwm_get_tag(device_t dev, const void *data, size_t len)
281{
282	struct meson_pwm_softc * const sc = device_private(dev);
283	struct meson_pwm_channel *pwmchan;
284	const u_int *pwm = data;
285
286	if (len != 16)
287		return NULL;
288
289	const u_int index = be32toh(pwm[1]);
290	if (index >= MESON_PWM_NCHAN)
291		return NULL;
292	const u_int period = be32toh(pwm[2]);
293	const u_int polarity = (pwm[3] == 0) ? PWM_ACTIVE_HIGH : PWM_ACTIVE_LOW;
294
295	pwmchan = &sc->sc_pwmchan[index];
296
297	/*
298	 * if polarity or period in pwm-tag is different from the copy of
299	 * config it holds, the content returned by pwm_get_conf() should
300	 * also be according to the tag.
301	 * this is because the caller may only set_conf() if necessary.
302	 */
303	if (pwmchan->mpc_conf.polarity != polarity) {
304		pwmchan->mpc_conf.duty_cycle =
305		    pwmchan->mpc_conf.period - pwmchan->mpc_conf.duty_cycle;
306		pwmchan->mpc_conf.polarity = polarity;
307	}
308	if (pwmchan->mpc_conf.period != period) {
309		if (pwmchan->mpc_conf.period == 0) {
310			pwmchan->mpc_conf.duty_cycle = 0;
311		} else {
312			pwmchan->mpc_conf.duty_cycle =
313			    (uint64_t)pwmchan->mpc_conf.duty_cycle *
314			    period / pwmchan->mpc_conf.period;
315		}
316		pwmchan->mpc_conf.period = period;
317	}
318
319	return &pwmchan->mpc_pwm;
320}
321
322static struct fdtbus_pwm_controller_func meson_pwm_funcs = {
323	.get_tag = meson_pwm_get_tag
324};
325
326static int
327meson_pwm_match(device_t parent, cfdata_t cf, void *aux)
328{
329	struct fdt_attach_args * const faa = aux;
330
331	return of_match_compat_data(faa->faa_phandle, compat_data);
332}
333
334static void
335meson_pwm_attach(device_t parent, device_t self, void *aux)
336{
337	struct meson_pwm_softc * const sc = device_private(self);
338	struct fdt_attach_args * const faa = aux;
339	bus_addr_t addr;
340	bus_size_t size;
341	struct clk *clk;
342	int phandle, i;
343
344	sc->sc_dev = self;
345	sc->sc_bst = faa->faa_bst;
346	sc->sc_phandle = phandle = faa->faa_phandle;
347
348	clk = fdtbus_clock_get_index(phandle, 0);
349	if (clk == NULL) {
350		aprint_error(": couldn't get clock\n");
351		return;
352	}
353	sc->sc_clkfreq = clk_get_rate(clk);
354
355	if (clk == fdtbus_clock_byname("vid_pll"))
356		sc->sc_clksource = 1;
357	else if (clk == fdtbus_clock_byname("fclk_div4"))
358		sc->sc_clksource = 2;
359	else if (clk == fdtbus_clock_byname("fclk_div3"))
360		sc->sc_clksource = 3;
361	else
362		sc->sc_clksource = 0;	/* default: "xtal" */
363
364	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
365		aprint_error(": couldn't get registers\n");
366		return;
367	}
368	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
369		aprint_error(": couldn't map registers\n");
370		return;
371	}
372
373	aprint_naive("\n");
374	aprint_normal(": Pulse-Width Modulation\n");
375
376	mutex_init(&sc->sc_reglock, MUTEX_DEFAULT, IPL_NONE);
377	for (i = 0; i < __arraycount(sc->sc_pwmchan); i++) {
378		sc->sc_pwmchan[i].mpc_sc = sc;
379		sc->sc_pwmchan[i].mpc_index = i;
380		sc->sc_pwmchan[i].mpc_pwm.pwm_enable = meson_pwm_enable;
381		sc->sc_pwmchan[i].mpc_pwm.pwm_get_config = meson_pwm_get_config;
382		sc->sc_pwmchan[i].mpc_pwm.pwm_set_config = meson_pwm_set_config;
383		sc->sc_pwmchan[i].mpc_pwm.pwm_dev = self;
384		sc->sc_pwmchan[i].mpc_pwm.pwm_priv = &sc->sc_pwmchan[i];
385		meson_pwm_get_current(sc, i, &sc->sc_pwmchan[i].mpc_conf);
386	}
387
388	fdtbus_register_pwm_controller(self, phandle, &meson_pwm_funcs);
389}
390
391CFATTACH_DECL_NEW(meson_pwm, sizeof(struct meson_pwm_softc),
392    meson_pwm_match, meson_pwm_attach, NULL, NULL);
393