diff --git a/sys/arm/ti/am335x/am335x_ecap.c b/sys/arm/ti/am335x/am335x_ecap.c --- a/sys/arm/ti/am335x/am335x_ecap.c +++ b/sys/arm/ti/am335x/am335x_ecap.c @@ -42,7 +42,14 @@ #include #include -#include "am335x_pwm.h" +#include +#include +#include + +#include "pwmbus_if.h" + +#define NS_PER_SEC 1000000000 +#define NUM_CHANNELS 1 #define ECAP_TSCTR 0x00 #define ECAP_CAP1 0x08 @@ -50,6 +57,7 @@ #define ECAP_CAP3 0x10 #define ECAP_CAP4 0x14 #define ECAP_ECCTL2 0x2A +#define ECCTL2_APWMPOL (1 << 10) #define ECCTL2_MODE_APWM (1 << 9) #define ECCTL2_SYNCO_SEL (3 << 6) #define ECCTL2_TSCTRSTOP_FREERUN (1 << 4) @@ -63,86 +71,191 @@ #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) #define PWM_LOCK_INIT(_sc) mtx_init(&(_sc)->sc_mtx, \ device_get_nameunit(_sc->sc_dev), "am335x_ecap softc", MTX_DEF) #define PWM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) -static device_probe_t am335x_ecap_probe; -static device_attach_t am335x_ecap_attach; -static device_detach_t am335x_ecap_detach; - struct am335x_ecap_softc { device_t sc_dev; + device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_mem_res; int sc_mem_rid; + + clk_t sc_clkfck; + + /* Things used for configuration via pwm(9) api. */ + uint64_t sc_clkfreq; /* frequency in Hz */ + u_int sc_period; /* duration in ns */ + u_int sc_duty; /* on duration, in ns */ + bool sc_enabled; /* channel enabled? */ + bool sc_inverted; /* signal inverted? */ }; static struct ofw_compat_data compat_data[] = { {"ti,am3352-ecap", true}, {"ti,am33xx-ecap", true}, + {"ti,da850-ecap", true}, + {"ti,am4372-ecap", true}, + {"ti,dra746-ecap", true}, + {"ti,k2g-ecap", true}, + {"ti,am654-ecap", true}, + {"ti,am64-ecap", true}, {NULL, false}, }; SIMPLEBUS_PNP_INFO(compat_data); -static device_method_t am335x_ecap_methods[] = { - DEVMETHOD(device_probe, am335x_ecap_probe), - DEVMETHOD(device_attach, am335x_ecap_attach), - DEVMETHOD(device_detach, am335x_ecap_detach), +static void +am335x_ecap_cfg_enable(struct am335x_ecap_softc *sc, u_int chan, bool enable) +{ + uint16_t regval; - DEVMETHOD_END -}; + sc->sc_enabled = enable; -static driver_t am335x_ecap_driver = { - "am335x_ecap", - am335x_ecap_methods, - sizeof(struct am335x_ecap_softc), -}; + PWM_LOCK_ASSERT(sc); + regval = ECAP_READ2(sc, ECAP_ECCTL2); + if (enable == true) + regval |= (ECCTL2_MODE_APWM | ECCTL2_TSCTRSTOP_FREERUN); + else + regval &= ~(ECCTL2_MODE_APWM | ECCTL2_TSCTRSTOP_FREERUN); + ECAP_WRITE2(sc, ECAP_ECCTL2, regval); +} -/* - * API function to set period/duty cycles for ECAPx - */ -int -am335x_pwm_config_ecap(int unit, int period, int duty) +static int +am335x_ecap_channel_count(device_t dev, u_int *nchannel) +{ + *nchannel = NUM_CHANNELS; + + return (0); +} + +static int +am335x_ecap_channel_config(device_t dev, u_int channel, u_int period_ns, + u_int duty_ns) { - device_t dev; struct am335x_ecap_softc *sc; - uint16_t reg; + uint32_t period, duty; - dev = devclass_get_device(devclass_find(am335x_ecap_driver.name), unit); - if (dev == NULL) - return (ENXIO); + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + PWM_LOCK(sc); - if (duty > period) + /* convert period_ns/duty_ns to counter values */ + period = period_ns * sc->sc_clkfreq / NS_PER_SEC; + duty = duty_ns * sc->sc_clkfreq / NS_PER_SEC; + + sc->sc_period = period_ns; + ECAP_WRITE2(sc, ECAP_CAP3, period - 1); + + sc->sc_duty = duty_ns; + ECAP_WRITE2(sc, ECAP_CAP4, duty); + + PWM_UNLOCK(sc); + + return (0); +} + +static int +am335x_ecap_channel_get_config(device_t dev, u_int channel, + u_int *period_ns, u_int *duty_ns) +{ + struct am335x_ecap_softc *sc; + + if (channel >= NUM_CHANNELS) return (EINVAL); - if (period == 0) + sc = device_get_softc(dev); + *period_ns = sc->sc_period; + *duty_ns = sc->sc_duty; + + return (0); +} + +static int +am335x_ecap_channel_set_flags(device_t dev, u_int channel, + uint32_t flags) +{ + uint16_t regval; + struct am335x_ecap_softc *sc; + + if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); + PWM_LOCK(sc); - reg = ECAP_READ2(sc, ECAP_ECCTL2); - reg |= ECCTL2_MODE_APWM | ECCTL2_TSCTRSTOP_FREERUN | ECCTL2_SYNCO_SEL; - ECAP_WRITE2(sc, ECAP_ECCTL2, reg); + sc->sc_inverted = flags & PWM_POLARITY_INVERTED; - /* CAP3 in APWM mode is APRD shadow register */ - ECAP_WRITE4(sc, ECAP_CAP3, period - 1); + regval = ECAP_READ2(sc, ECAP_ECCTL2); + if (sc->sc_inverted == true) + regval |= (ECCTL2_APWMPOL); + else + regval &= ~(ECCTL2_APWMPOL); + ECAP_WRITE2(sc, ECAP_ECCTL2, regval); + + PWM_UNLOCK(sc); - /* CAP4 in APWM mode is ACMP shadow register */ - ECAP_WRITE4(sc, ECAP_CAP4, duty); - /* Restart counter */ - ECAP_WRITE4(sc, ECAP_TSCTR, 0); + return (0); +} + +static int +am335x_ecap_channel_get_flags(device_t dev, u_int channel, + uint32_t *flags) +{ + struct am335x_ecap_softc *sc; + if (channel >= NUM_CHANNELS) + return (EINVAL); + sc = device_get_softc(dev); + + if (sc->sc_inverted == true) + *flags = PWM_POLARITY_INVERTED; + else + *flags = 0; + + return (0); +} + +static int +am335x_ecap_channel_enable(device_t dev, u_int channel, bool enable) +{ + struct am335x_ecap_softc *sc; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + PWM_LOCK(sc); + am335x_ecap_cfg_enable(sc, channel, enable); PWM_UNLOCK(sc); return (0); } static int -am335x_ecap_probe(device_t dev) +am335x_ecap_channel_is_enabled(device_t dev, u_int channel, bool *enabled) { + struct am335x_ecap_softc *sc; + + if (channel >= NUM_CHANNELS) + return (EINVAL); + + sc = device_get_softc(dev); + + *enabled = sc->sc_enabled; + return (0); +} + +static int +am335x_ecap_probe(device_t dev) +{ if (!ofw_bus_status_okay(dev)) return (ENXIO); @@ -158,6 +271,8 @@ am335x_ecap_attach(device_t dev) { struct am335x_ecap_softc *sc; + int err; + phandle_t node; sc = device_get_softc(dev); sc->sc_dev = dev; @@ -171,10 +286,39 @@ goto fail; } - return (0); + err = clk_get_by_ofw_index(dev, 0, 0, &sc->sc_clkfck); + if (err != 0) { + device_printf(dev, "Cant find clock index 0. err: %d\n", + err); + goto fail; + } + /* Get the base clock frequency. */ + err = clk_get_freq(sc->sc_clkfck, &sc->sc_clkfreq); + if (err != 0) { + device_printf(dev, "Cant get sysclk frequency, err %d\n", + err); + goto fail; + } + + /* + * Note that we don't check for failure to attach pwmbus -- even without + * it we can still service clients who connect via fdt xref data. + */ + node = ofw_bus_get_node(dev); + OF_device_register_xref(OF_xref_from_node(node), dev); + + if ((sc->sc_busdev = device_add_child(dev, "pwmbus", -1)) == NULL && + bootverbose) + device_printf(dev, "Cannot add child pwmbus\n"); + + bus_generic_probe(dev); + return (bus_generic_attach(dev)); fail: PWM_LOCK_DESTROY(sc); + if (sc->sc_mem_res) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_mem_rid, sc->sc_mem_res); return (ENXIO); } @@ -182,13 +326,22 @@ am335x_ecap_detach(device_t dev) { struct am335x_ecap_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) + return (error); + 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); @@ -196,6 +349,43 @@ return (0); } +static phandle_t +am335x_ecap_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_ecap_methods[] = { + DEVMETHOD(device_probe, am335x_ecap_probe), + DEVMETHOD(device_attach, am335x_ecap_attach), + DEVMETHOD(device_detach, am335x_ecap_detach), + + /* ofw_bus_if */ + DEVMETHOD(ofw_bus_get_node, am335x_ecap_get_node), + + /* pwm interface */ + DEVMETHOD(pwmbus_channel_count, am335x_ecap_channel_count), + DEVMETHOD(pwmbus_channel_config, am335x_ecap_channel_config), + DEVMETHOD(pwmbus_channel_get_config, am335x_ecap_channel_get_config), + DEVMETHOD(pwmbus_channel_set_flags, am335x_ecap_channel_set_flags), + DEVMETHOD(pwmbus_channel_get_flags, am335x_ecap_channel_get_flags), + DEVMETHOD(pwmbus_channel_enable, am335x_ecap_channel_enable), + DEVMETHOD(pwmbus_channel_is_enabled, am335x_ecap_channel_is_enabled), + + DEVMETHOD_END +}; + +static driver_t am335x_ecap_driver = { + "pwm", + am335x_ecap_methods, + sizeof(struct am335x_ecap_softc), +}; + DRIVER_MODULE(am335x_ecap, am335x_pwmss, am335x_ecap_driver, 0, 0); MODULE_VERSION(am335x_ecap, 1); MODULE_DEPEND(am335x_ecap, am335x_pwmss, 1, 1, 1); +MODULE_DEPEND(am335x_ecap, pwmbus, 1, 1, 1);