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