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 (at) 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
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
73 { 0 }
74 };
75
76 #define MESON_PWM_NCHAN 2
77 struct 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
84 struct 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
95 static int
96 meson_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
122 static void
123 meson_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
166 static int
167 meson_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
175 static int
176 meson_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
279 static pwm_tag_t
280 meson_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
322 static struct fdtbus_pwm_controller_func meson_pwm_funcs = {
323 .get_tag = meson_pwm_get_tag
324 };
325
326 static int
327 meson_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
334 static void
335 meson_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
391 CFATTACH_DECL_NEW(meson_pwm, sizeof(struct meson_pwm_softc),
392 meson_pwm_match, meson_pwm_attach, NULL, NULL);
393