meson_pwm.c revision 1.1
1/* $NetBSD: meson_pwm.c,v 1.1 2021/01/01 07:21:58 ryo 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.1 2021/01/01 07:21:58 ryo 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 of_compat_data compat_data[] = { 69 { "amlogic,meson-g12a-ao-pwm-ab", 0 }, 70 { "amlogic,meson-g12a-ao-pwm-cd", 0 }, 71 { "amlogic,meson-g12a-ee-pwm", 0 }, 72 { NULL } 73}; 74 75#define MESON_PWM_NCHAN 2 76struct 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 83struct 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 94static int 95meson_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 121static void 122meson_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 165static int 166meson_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 174static int 175meson_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 278static pwm_tag_t 279meson_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 321static struct fdtbus_pwm_controller_func meson_pwm_funcs = { 322 .get_tag = meson_pwm_get_tag 323}; 324 325static int 326meson_pwm_match(device_t parent, cfdata_t cf, void *aux) 327{ 328 struct fdt_attach_args * const faa = aux; 329 330 return of_match_compat_data(faa->faa_phandle, compat_data); 331} 332 333static void 334meson_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 390CFATTACH_DECL_NEW(meson_pwm, sizeof(struct meson_pwm_softc), 391 meson_pwm_match, meson_pwm_attach, NULL, NULL); 392