Date: Fri, 21 Jun 2019 14:24:33 +0000 (UTC) From: Ian Lepore <ian@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r349270 - head/sys/arm/ti/am335x Message-ID: <201906211424.x5LEOX4p088049@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: ian Date: Fri Jun 21 14:24:33 2019 New Revision: 349270 URL: https://svnweb.freebsd.org/changeset/base/349270 Log: Add support for the PWM(9) API. This allows configuring the pwm output using pwm(9), but also maintains the historical sysctl config interface for compatiblity with existing apps. The two config systems are not compatible with each other; if you use both interfaces to change configurations you're likely to end up with incorrect output or none at all. Modified: head/sys/arm/ti/am335x/am335x_ehrpwm.c Modified: head/sys/arm/ti/am335x/am335x_ehrpwm.c ============================================================================== --- head/sys/arm/ti/am335x/am335x_ehrpwm.c Fri Jun 21 14:01:02 2019 (r349269) +++ head/sys/arm/ti/am335x/am335x_ehrpwm.c Fri Jun 21 14:24:33 2019 (r349270) @@ -45,12 +45,33 @@ __FBSDID("$FreeBSD$"); #include <dev/ofw/ofw_bus.h> #include <dev/ofw/ofw_bus_subr.h> +#include "pwmbus_if.h" + #include "am335x_pwm.h" +/******************************************************************************* + * Enhanced resolution PWM driver. Many of the advanced featues of the hardware + * are not supported by this driver. What is implemented here is simple + * variable-duty-cycle PWM output. + * + * Note that this driver was historically configured using a set of sysctl + * variables/procs, and later gained support for the PWM(9) API. The sysctl + * code is still present to support existing apps, but that interface is + * considered deprecated. + * + * An important caveat is that the original sysctl interface and the new PWM API + * cannot both be used at once. If both interfaces are used to change + * configuration, it's quite likely you won't get the expected results. Also, + * reading the sysctl values after configuring via PWM will not return the right + * results. + ******************************************************************************/ + /* In ticks */ #define DEFAULT_PWM_PERIOD 1000 #define PWM_CLOCK 100000000UL +#define NS_PER_SEC 1000000000 + #define PWM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define PWM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define PWM_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) @@ -120,6 +141,11 @@ __FBSDID("$FreeBSD$"); #define AQCTL_ZRO_TOGGLE (3 << 0) #define EPWM_AQSFRC 0x1a #define EPWM_AQCSFRC 0x1c +#define AQCSFRC_OFF 0 +#define AQCSFRC_LO 1 +#define AQCSFRC_HI 2 +#define AQCSFRC_MASK 3 +#define AQCSFRC(chan, hilo) ((hilo) << (2 * chan)) /* Trip-Zone module */ #define EPWM_TZCTL 0x28 @@ -136,8 +162,16 @@ static device_detach_t am335x_ehrpwm_detach; static int am335x_ehrpwm_clkdiv[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; +struct ehrpwm_channel { + u_int duty; /* on duration, in ns */ + bool enabled; /* channel enabled? */ + bool inverted; /* signal inverted? */ +}; +#define NUM_CHANNELS 2 + struct am335x_ehrpwm_softc { device_t sc_dev; + device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_mem_res; int sc_mem_rid; @@ -153,6 +187,12 @@ struct am335x_ehrpwm_softc { uint32_t sc_pwm_period; uint32_t sc_pwm_dutyA; uint32_t sc_pwm_dutyB; + + /* Things used for configuration via pwm(9) api. */ + u_int sc_clkfreq; /* frequency in Hz */ + u_int sc_clktick; /* duration in ns */ + u_int sc_period; /* duration in ns */ + struct ehrpwm_channel sc_channels[NUM_CHANNELS]; }; static struct ofw_compat_data compat_data[] = { @@ -161,8 +201,118 @@ static struct ofw_compat_data compat_data[] = { }; SIMPLEBUS_PNP_INFO(compat_data); +static void +am335x_ehrpwm_cfg_duty(struct am335x_ehrpwm_softc *sc, u_int chan, u_int duty) +{ + u_int tbcmp; + if (duty == 0) + tbcmp = 0; + else + tbcmp = max(1, duty / sc->sc_clktick); + + sc->sc_channels[chan].duty = tbcmp * sc->sc_clktick; + + PWM_LOCK_ASSERT(sc); + EPWM_WRITE2(sc, (chan == 0) ? EPWM_CMPA : EPWM_CMPB, tbcmp); +} + static void +am335x_ehrpwm_cfg_enable(struct am335x_ehrpwm_softc *sc, u_int chan, bool enable) +{ + uint16_t regval; + + sc->sc_channels[chan].enabled = enable; + + /* + * Turn off any existing software-force of the channel, then force + * it in the right direction (high or low) if it's not being enabled. + */ + PWM_LOCK_ASSERT(sc); + regval = EPWM_READ2(sc, EPWM_AQCSFRC); + regval &= ~AQCSFRC(chan, AQCSFRC_MASK); + if (!sc->sc_channels[chan].enabled) { + if (sc->sc_channels[chan].inverted) + regval |= AQCSFRC(chan, AQCSFRC_HI); + else + regval |= AQCSFRC(chan, AQCSFRC_LO); + } + EPWM_WRITE2(sc, EPWM_AQCSFRC, regval); +} + +static bool +am335x_ehrpwm_cfg_period(struct am335x_ehrpwm_softc *sc, u_int period) +{ + uint16_t regval; + u_int clkdiv, hspclkdiv, pwmclk, pwmtick, tbprd; + + /* Can't do a period shorter than 2 clock ticks. */ + if (period < 2 * NS_PER_SEC / PWM_CLOCK) { + sc->sc_clkfreq = 0; + sc->sc_clktick = 0; + sc->sc_period = 0; + return (false); + } + + /* + * Figure out how much we have to divide down the base 100MHz clock so + * that we can express the requested period as a 16-bit tick count. + */ + tbprd = 0; + for (clkdiv = 0; clkdiv < 8; ++clkdiv) { + const u_int cd = 1 << clkdiv; + for (hspclkdiv = 0; hspclkdiv < 8; ++hspclkdiv) { + const u_int cdhs = max(1, hspclkdiv * 2); + pwmclk = PWM_CLOCK / (cd * cdhs); + pwmtick = NS_PER_SEC / pwmclk; + if (period / pwmtick < 65536) { + tbprd = period / pwmtick; + break; + } + } + if (tbprd != 0) + break; + } + + /* Handle requested period too long for available clock divisors. */ + if (tbprd == 0) + return (false); + + /* + * If anything has changed from the current settings, reprogram the + * clock divisors and period register. + */ + if (sc->sc_clkfreq != pwmclk || sc->sc_clktick != pwmtick || + sc->sc_period != tbprd * pwmtick) { + + sc->sc_clkfreq = pwmclk; + sc->sc_clktick = pwmtick; + sc->sc_period = tbprd * pwmtick; + + PWM_LOCK_ASSERT(sc); + regval = EPWM_READ2(sc, EPWM_TBCTL); + regval &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK); + regval |= TBCTL_CLKDIV(clkdiv) | TBCTL_HSPCLKDIV(hspclkdiv); + EPWM_WRITE2(sc, EPWM_TBCTL, regval); + EPWM_WRITE2(sc, EPWM_TBPRD, tbprd - 1); +#if 0 + device_printf(sc->sc_dev, "clkdiv %u hspclkdiv %u tbprd %u " + "clkfreq %u Hz clktick %u ns period got %u requested %u\n", + clkdiv, hspclkdiv, tbprd - 1, + sc->sc_clkfreq, sc->sc_clktick, sc->sc_period, period); +#endif + /* + * If the period changed, that invalidates the current CMP + * registers (duty values), just zero them out. + */ + am335x_ehrpwm_cfg_duty(sc, 0, 0); + am335x_ehrpwm_cfg_duty(sc, 1, 0); + } + + return (true); +} + +static void am335x_ehrpwm_freq(struct am335x_ehrpwm_softc *sc) { int clkdiv; @@ -324,6 +474,82 @@ am335x_ehrpwm_sysctl_period(SYSCTL_HANDLER_ARGS) } static int +am335x_ehrpwm_channel_count(device_t dev, u_int *nchannel) +{ + + *nchannel = NUM_CHANNELS; + + return (0); +} + +static int +am335x_ehrpwm_channel_config(device_t dev, u_int channel, u_int period, u_int duty) +{ + struct am335x_ehrpwm_softc *sc; + bool status; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + PWM_LOCK(sc); + status = am335x_ehrpwm_cfg_period(sc, period); + if (status) + am335x_ehrpwm_cfg_duty(sc, channel, duty); + PWM_UNLOCK(sc); + + return (status ? 0 : EINVAL); +} + +static int +am335x_ehrpwm_channel_get_config(device_t dev, u_int channel, + u_int *period, u_int *duty) +{ + struct am335x_ehrpwm_softc *sc; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + *period = sc->sc_period; + *duty = sc->sc_channels[channel].duty; + return (0); +} + +static int +am335x_ehrpwm_channel_enable(device_t dev, u_int channel, bool enable) +{ + struct am335x_ehrpwm_softc *sc; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + PWM_LOCK(sc); + am335x_ehrpwm_cfg_enable(sc, channel, enable); + PWM_UNLOCK(sc); + + return (0); +} + +static int +am335x_ehrpwm_channel_is_enabled(device_t dev, u_int channel, bool *enabled) +{ + struct am335x_ehrpwm_softc *sc; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + *enabled = sc->sc_channels[channel].enabled; + + return (0); +} + +static int am335x_ehrpwm_probe(device_t dev) { @@ -407,7 +633,13 @@ am335x_ehrpwm_attach(device_t dev) EPWM_WRITE2(sc, EPWM_TZCTL, 0xf); reg = EPWM_READ2(sc, EPWM_TZFLG); - return (0); + if ((sc->sc_busdev = device_add_child(dev, "pwmbus", -1)) == NULL) { + device_printf(dev, "Cannot add child pwmbus\n"); + // This driver can still do things even without the bus child. + } + + bus_generic_probe(dev); + return (bus_generic_attach(dev)); fail: PWM_LOCK_DESTROY(sc); if (sc->sc_mem_res) @@ -421,13 +653,22 @@ static int am335x_ehrpwm_detach(device_t dev) { struct am335x_ehrpwm_softc *sc; + int error; sc = device_get_softc(dev); + if ((error = bus_generic_detach(sc->sc_dev)) != 0) + return (error); + PWM_LOCK(sc); + + if (sc->sc_busdev != NULL) + device_delete_child(dev, sc->sc_busdev); + if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); + PWM_UNLOCK(sc); PWM_LOCK_DESTROY(sc); @@ -435,11 +676,32 @@ am335x_ehrpwm_detach(device_t dev) return (0); } +static phandle_t +am335x_ehrpwm_get_node(device_t bus, device_t dev) +{ + + /* + * Share our controller node with our pwmbus child; it instantiates + * devices by walking the children contained within our node. + */ + return ofw_bus_get_node(bus); +} + static device_method_t am335x_ehrpwm_methods[] = { DEVMETHOD(device_probe, am335x_ehrpwm_probe), DEVMETHOD(device_attach, am335x_ehrpwm_attach), DEVMETHOD(device_detach, am335x_ehrpwm_detach), + /* ofw_bus_if */ + DEVMETHOD(ofw_bus_get_node, am335x_ehrpwm_get_node), + + /* pwm interface */ + DEVMETHOD(pwmbus_channel_count, am335x_ehrpwm_channel_count), + DEVMETHOD(pwmbus_channel_config, am335x_ehrpwm_channel_config), + DEVMETHOD(pwmbus_channel_get_config, am335x_ehrpwm_channel_get_config), + DEVMETHOD(pwmbus_channel_enable, am335x_ehrpwm_channel_enable), + DEVMETHOD(pwmbus_channel_is_enabled, am335x_ehrpwm_channel_is_enabled), + DEVMETHOD_END }; @@ -454,3 +716,4 @@ static devclass_t am335x_ehrpwm_devclass; DRIVER_MODULE(am335x_ehrpwm, am335x_pwmss, am335x_ehrpwm_driver, am335x_ehrpwm_devclass, 0, 0); MODULE_VERSION(am335x_ehrpwm, 1); MODULE_DEPEND(am335x_ehrpwm, am335x_pwmss, 1, 1, 1); +MODULE_DEPEND(am335x_ehrpwm, pwmbus, 1, 1, 1);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201906211424.x5LEOX4p088049>