xref: /netbsd-src/sys/arch/arm/amlogic/meson_pwm.c (revision 90313c06e62e910bf0d1bb24faa9d17dcefd0ab6)
1 /* $NetBSD: meson_pwm.c,v 1.6 2024/02/07 04:20:26 msaitoh Exp $ */
2 
3 /*
4  * Copyright (c) 2021 Ryo Shimizu
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.6 2024/02/07 04:20:26 msaitoh 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 
68 static 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 	DEVICE_COMPAT_EOL
73 };
74 
75 #define MESON_PWM_NCHAN	2
76 struct meson_pwm_channel {
77 	struct meson_pwm_softc *mpc_sc;
78 	struct pwm_controller mpc_pwm;
79 	struct pwm_config mpc_conf;
80 	u_int mpc_index;
81 };
82 
83 struct meson_pwm_softc {
84 	device_t sc_dev;
85 	bus_space_tag_t sc_bst;
86 	bus_space_handle_t sc_bsh;
87 	kmutex_t sc_reglock;	/* for PWM_A vs. PWM_B */
88 	int sc_phandle;
89 	u_int sc_clkfreq;
90 	u_int sc_clksource;
91 	struct meson_pwm_channel sc_pwmchan[MESON_PWM_NCHAN];
92 };
93 
94 static int
meson_pwm_enable(pwm_tag_t pwm,bool enable)95 meson_pwm_enable(pwm_tag_t pwm, bool enable)
96 {
97 	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
98 	struct meson_pwm_softc * const sc = device_private(pwm->pwm_dev);
99 	uint32_t val;
100 
101 	mutex_enter(&sc->sc_reglock);
102 	switch (pwmchan->mpc_index) {
103 	case 0: /* A */
104 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
105 		val &= ~MESON_PWM_MISC_AB_DS_A_EN;
106 		val |= MESON_PWM_MISC_AB_PWM_A_EN;
107 		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
108 		break;
109 	case 1: /* B */
110 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
111 		val &= ~MESON_PWM_MISC_AB_DS_B_EN;
112 		val |= MESON_PWM_MISC_AB_PWM_B_EN;
113 		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
114 		break;
115 	}
116 	mutex_exit(&sc->sc_reglock);
117 
118 	return 0;
119 }
120 
121 static void
meson_pwm_get_current(struct meson_pwm_softc * sc,int chan,struct pwm_config * conf)122 meson_pwm_get_current(struct meson_pwm_softc *sc, int chan,
123     struct pwm_config *conf)
124 {
125 	uint64_t period_hz, duty_hz;
126 	uint32_t val;
127 	u_int period, duty, clk_div, hi, lo;
128 
129 	memset(conf, 0, sizeof(*conf));
130 
131 	mutex_enter(&sc->sc_reglock);
132 	switch (chan) {
133 	case 0: /* A */
134 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
135 		clk_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_A_CLK_DIV);
136 		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_A_REG);
137 		hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
138 		lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
139 		break;
140 	case 1: /* B */
141 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
142 		clk_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_B_CLK_DIV);
143 		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_B_REG);
144 		hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
145 		lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
146 		break;
147 	default:
148 		mutex_exit(&sc->sc_reglock);
149 		return;
150 	}
151 	mutex_exit(&sc->sc_reglock);
152 
153 	clk_div += 1;
154 	duty_hz = (uint64_t)hi * clk_div;
155 	period_hz = (uint64_t)(hi + lo) * clk_div;
156 
157 	period = period_hz * 1000000000ULL / sc->sc_clkfreq;
158 	duty = duty_hz * 1000000000ULL / sc->sc_clkfreq;
159 
160 	conf->polarity = PWM_ACTIVE_HIGH;
161 	conf->period = period;
162 	conf->duty_cycle = duty;
163 }
164 
165 static int
meson_pwm_get_config(pwm_tag_t pwm,struct pwm_config * conf)166 meson_pwm_get_config(pwm_tag_t pwm, struct pwm_config *conf)
167 {
168 	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
169 
170 	*conf = pwmchan->mpc_conf;
171 	return 0;
172 }
173 
174 static int
meson_pwm_set_config(pwm_tag_t pwm,const struct pwm_config * conf)175 meson_pwm_set_config(pwm_tag_t pwm, const struct pwm_config *conf)
176 {
177 	struct meson_pwm_channel * const pwmchan = pwm->pwm_priv;
178 	struct meson_pwm_softc * const sc = device_private(pwm->pwm_dev);
179 	uint64_t period_hz, duty_hz;
180 	uint32_t val;
181 	u_int period, duty, clk_div, hi, lo;
182 #ifdef MESON_PWM_DEBUG
183 	u_int old_div = 0, old_hi = 0, old_lo = 0;
184 #endif
185 
186 	period = conf->period;
187 	duty = conf->duty_cycle;
188 	if (period == 0)
189 		return EINVAL;
190 	KASSERT(period >= duty);
191 	if (conf->polarity == PWM_ACTIVE_LOW)
192 		duty = period - duty;
193 
194 	/* calculate the period to be within the maximum value (0xffff) */
195 #define MESON_PWMTIME_MAX	0xffff
196 #define MESON_CLKDIV_MAX	127
197 	clk_div = 1;
198 	period_hz = ((uint64_t)sc->sc_clkfreq * period + 500000000ULL) /
199 	    1000000000ULL;
200 	duty_hz = ((uint64_t)sc->sc_clkfreq * duty + 500000000ULL) /
201 	    1000000000ULL;
202 	if (period_hz > MESON_PWMTIME_MAX) {
203 		clk_div = (period_hz + 0x7fff) / 0xffff;
204 		period_hz /= clk_div;
205 		duty_hz /= clk_div;
206 	}
207 
208 	clk_div -= 1;	/* the divider is N+1 */
209 	if (clk_div > MESON_CLKDIV_MAX)
210 		return EINVAL;
211 
212 	hi = duty_hz;
213 	lo = period_hz - duty_hz;
214 
215 	mutex_enter(&sc->sc_reglock);
216 	switch (pwmchan->mpc_index) {
217 	case 0: /* A */
218 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
219 		val &= ~MESON_PWM_MISC_AB_A_CLK_DIV;
220 #ifdef MESON_PWM_DEBUG
221 		old_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_A_CLK_DIV);
222 #endif
223 		val |= __SHIFTIN(clk_div, MESON_PWM_MISC_AB_A_CLK_DIV);
224 		val &= ~MESON_PWM_MISC_AB_A_CLK_SEL;
225 		val |= __SHIFTIN(sc->sc_clksource, MESON_PWM_MISC_AB_A_CLK_SEL);
226 		val |= MESON_PWM_MISC_AB_A_CLK_EN;
227 		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
228 		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_A_REG);
229 #ifdef MESON_PWM_DEBUG
230 		old_hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
231 		old_lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
232 #endif
233 		PWM_WRITE_REG(sc, MESON_PWM_DUTYCYCLE_A_REG,
234 		    __SHIFTIN(hi, MESON_PWM_DUTYCYCLE_HIGH) |
235 		    __SHIFTIN(lo, MESON_PWM_DUTYCYCLE_LOW));
236 		break;
237 	case 1: /* B */
238 		val = PWM_READ_REG(sc, MESON_PWM_MISC_AB_REG);
239 		val &= ~MESON_PWM_MISC_AB_B_CLK_DIV;
240 #ifdef MESON_PWM_DEBUG
241 		old_div = __SHIFTOUT(val, MESON_PWM_MISC_AB_B_CLK_DIV);
242 #endif
243 		val |= __SHIFTIN(clk_div, MESON_PWM_MISC_AB_B_CLK_DIV);
244 		val &= ~MESON_PWM_MISC_AB_B_CLK_SEL;
245 		val |= __SHIFTIN(sc->sc_clksource, MESON_PWM_MISC_AB_B_CLK_SEL);
246 		val |= MESON_PWM_MISC_AB_B_CLK_EN;
247 		PWM_WRITE_REG(sc, MESON_PWM_MISC_AB_REG, val);
248 		val = PWM_READ_REG(sc, MESON_PWM_DUTYCYCLE_B_REG);
249 #ifdef MESON_PWM_DEBUG
250 		old_hi = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_HIGH);
251 		old_lo = __SHIFTOUT(val, MESON_PWM_DUTYCYCLE_LOW);
252 #endif
253 		PWM_WRITE_REG(sc, MESON_PWM_DUTYCYCLE_B_REG,
254 		    __SHIFTIN(hi, MESON_PWM_DUTYCYCLE_HIGH) |
255 		    __SHIFTIN(lo, MESON_PWM_DUTYCYCLE_LOW));
256 		break;
257 	}
258 	mutex_exit(&sc->sc_reglock);
259 
260 	pwmchan->mpc_conf = *conf;
261 
262 #ifdef MESON_PWM_DEBUG
263 	device_printf(sc->sc_dev,
264 	    "%s: %s: polarity=%s, DutuCycle/Period=%uns/%uns(%u%%) : "
265 	    "%uHz, HIGH:LOW=%u:%u(%u%%) -> "
266 	    "%uHz, HIGH:LOW=%u:%u(%u%%)\n", __func__,
267 	    pwmchan->mpc_index ? "A" : "B",
268 	    (conf->polarity == PWM_ACTIVE_LOW) ? "LOW" : "HIGH",
269 	    conf->duty_cycle, conf->period,
270 	    conf->duty_cycle * 100 / conf->period,
271 	    sc->sc_clkfreq / (old_div + 1), old_hi, old_lo,
272 	    old_hi * 100 / (old_hi + old_lo),
273 	    sc->sc_clkfreq / (clk_div + 1), hi, lo, hi * 100 / (hi + lo));
274 #endif
275 	return 0;
276 }
277 
278 static pwm_tag_t
meson_pwm_get_tag(device_t dev,const void * data,size_t len)279 meson_pwm_get_tag(device_t dev, const void *data, size_t len)
280 {
281 	struct meson_pwm_softc * const sc = device_private(dev);
282 	struct meson_pwm_channel *pwmchan;
283 	const u_int *pwm = data;
284 
285 	if (len != 16)
286 		return NULL;
287 
288 	const u_int index = be32toh(pwm[1]);
289 	if (index >= MESON_PWM_NCHAN)
290 		return NULL;
291 	const u_int period = be32toh(pwm[2]);
292 	const u_int polarity = (pwm[3] == 0) ? PWM_ACTIVE_HIGH : PWM_ACTIVE_LOW;
293 
294 	pwmchan = &sc->sc_pwmchan[index];
295 
296 	/*
297 	 * if polarity or period in pwm-tag is different from the copy of
298 	 * config it holds, the content returned by pwm_get_conf() should
299 	 * also be according to the tag.
300 	 * this is because the caller may only set_conf() if necessary.
301 	 */
302 	if (pwmchan->mpc_conf.polarity != polarity) {
303 		pwmchan->mpc_conf.duty_cycle =
304 		    pwmchan->mpc_conf.period - pwmchan->mpc_conf.duty_cycle;
305 		pwmchan->mpc_conf.polarity = polarity;
306 	}
307 	if (pwmchan->mpc_conf.period != period) {
308 		if (pwmchan->mpc_conf.period == 0) {
309 			pwmchan->mpc_conf.duty_cycle = 0;
310 		} else {
311 			pwmchan->mpc_conf.duty_cycle =
312 			    (uint64_t)pwmchan->mpc_conf.duty_cycle *
313 			    period / pwmchan->mpc_conf.period;
314 		}
315 		pwmchan->mpc_conf.period = period;
316 	}
317 
318 	return &pwmchan->mpc_pwm;
319 }
320 
321 static struct fdtbus_pwm_controller_func meson_pwm_funcs = {
322 	.get_tag = meson_pwm_get_tag
323 };
324 
325 static int
meson_pwm_match(device_t parent,cfdata_t cf,void * aux)326 meson_pwm_match(device_t parent, cfdata_t cf, void *aux)
327 {
328 	struct fdt_attach_args * const faa = aux;
329 
330 	return of_compatible_match(faa->faa_phandle, compat_data);
331 }
332 
333 static void
meson_pwm_attach(device_t parent,device_t self,void * aux)334 meson_pwm_attach(device_t parent, device_t self, void *aux)
335 {
336 	struct meson_pwm_softc * const sc = device_private(self);
337 	struct fdt_attach_args * const faa = aux;
338 	bus_addr_t addr;
339 	bus_size_t size;
340 	struct clk *clk;
341 	int phandle, i;
342 
343 	sc->sc_dev = self;
344 	sc->sc_bst = faa->faa_bst;
345 	sc->sc_phandle = phandle = faa->faa_phandle;
346 
347 	clk = fdtbus_clock_get_index(phandle, 0);
348 	if (clk == NULL) {
349 		aprint_error(": couldn't get clock\n");
350 		return;
351 	}
352 	sc->sc_clkfreq = clk_get_rate(clk);
353 
354 	if (clk == fdtbus_clock_byname("vid_pll"))
355 		sc->sc_clksource = 1;
356 	else if (clk == fdtbus_clock_byname("fclk_div4"))
357 		sc->sc_clksource = 2;
358 	else if (clk == fdtbus_clock_byname("fclk_div3"))
359 		sc->sc_clksource = 3;
360 	else
361 		sc->sc_clksource = 0;	/* default: "xtal" */
362 
363 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
364 		aprint_error(": couldn't get registers\n");
365 		return;
366 	}
367 	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
368 		aprint_error(": couldn't map registers\n");
369 		return;
370 	}
371 
372 	aprint_naive("\n");
373 	aprint_normal(": Pulse-Width Modulation\n");
374 
375 	mutex_init(&sc->sc_reglock, MUTEX_DEFAULT, IPL_NONE);
376 	for (i = 0; i < __arraycount(sc->sc_pwmchan); i++) {
377 		sc->sc_pwmchan[i].mpc_sc = sc;
378 		sc->sc_pwmchan[i].mpc_index = i;
379 		sc->sc_pwmchan[i].mpc_pwm.pwm_enable = meson_pwm_enable;
380 		sc->sc_pwmchan[i].mpc_pwm.pwm_get_config = meson_pwm_get_config;
381 		sc->sc_pwmchan[i].mpc_pwm.pwm_set_config = meson_pwm_set_config;
382 		sc->sc_pwmchan[i].mpc_pwm.pwm_dev = self;
383 		sc->sc_pwmchan[i].mpc_pwm.pwm_priv = &sc->sc_pwmchan[i];
384 		meson_pwm_get_current(sc, i, &sc->sc_pwmchan[i].mpc_conf);
385 	}
386 
387 	fdtbus_register_pwm_controller(self, phandle, &meson_pwm_funcs);
388 }
389 
390 CFATTACH_DECL_NEW(meson_pwm, sizeof(struct meson_pwm_softc),
391     meson_pwm_match, meson_pwm_attach, NULL, NULL);
392