Home | History | Annotate | Line # | Download | only in amlogic
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