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);
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to