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