#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/pwm/pwmc.h>
#include "pwmbus_if.h"
#include "am335x_pwm.h"
#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)
#define PWM_LOCK_INIT(_sc) mtx_init(&(_sc)->sc_mtx, \
device_get_nameunit(_sc->sc_dev), "am335x_ehrpwm softc", MTX_DEF)
#define PWM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx)
#define EPWM_READ2(_sc, reg) bus_read_2((_sc)->sc_mem_res, reg)
#define EPWM_WRITE2(_sc, reg, value) \
bus_write_2((_sc)->sc_mem_res, reg, value)
#define EPWM_TBCTL 0x00
#define TBCTL_FREERUN_STOP_NEXT_TBC_INCREMENT (0 << 14)
#define TBCTL_FREERUN_STOP_COMPLETE_CYCLE (1 << 14)
#define TBCTL_FREERUN (2 << 14)
#define TBCTL_PHDIR_UP (1 << 13)
#define TBCTL_PHDIR_DOWN (0 << 13)
#define TBCTL_CLKDIV(x) ((x) << 10)
#define TBCTL_CLKDIV_MASK (7 << 10)
#define TBCTL_HSPCLKDIV(x) ((x) << 7)
#define TBCTL_HSPCLKDIV_MASK (7 << 7)
#define TBCTL_SYNCOSEL_DISABLED (3 << 4)
#define TBCTL_PRDLD_SHADOW (0 << 3)
#define TBCTL_PRDLD_IMMEDIATE (1 << 3)
#define TBCTL_PHSEN_DISABLED (0 << 2)
#define TBCTL_PHSEN_ENABLED (1 << 2)
#define TBCTL_CTRMODE_MASK (3)
#define TBCTL_CTRMODE_UP (0 << 0)
#define TBCTL_CTRMODE_DOWN (1 << 0)
#define TBCTL_CTRMODE_UPDOWN (2 << 0)
#define TBCTL_CTRMODE_FREEZE (3 << 0)
#define EPWM_TBSTS 0x02
#define EPWM_TBPHSHR 0x04
#define EPWM_TBPHS 0x06
#define EPWM_TBCNT 0x08
#define EPWM_TBPRD 0x0a
#define EPWM_CMPCTL 0x0e
#define CMPCTL_SHDWBMODE_SHADOW (1 << 6)
#define CMPCTL_SHDWBMODE_IMMEDIATE (0 << 6)
#define CMPCTL_SHDWAMODE_SHADOW (1 << 4)
#define CMPCTL_SHDWAMODE_IMMEDIATE (0 << 4)
#define CMPCTL_LOADBMODE_ZERO (0 << 2)
#define CMPCTL_LOADBMODE_PRD (1 << 2)
#define CMPCTL_LOADBMODE_EITHER (2 << 2)
#define CMPCTL_LOADBMODE_FREEZE (3 << 2)
#define CMPCTL_LOADAMODE_ZERO (0 << 0)
#define CMPCTL_LOADAMODE_PRD (1 << 0)
#define CMPCTL_LOADAMODE_EITHER (2 << 0)
#define CMPCTL_LOADAMODE_FREEZE (3 << 0)
#define EPWM_CMPAHR 0x10
#define EPWM_CMPA 0x12
#define EPWM_CMPB 0x14
#define EPWM_AQCTLA 0x16
#define EPWM_AQCTLB 0x18
#define AQCTL_CBU_NONE (0 << 8)
#define AQCTL_CBU_CLEAR (1 << 8)
#define AQCTL_CBU_SET (2 << 8)
#define AQCTL_CBU_TOGGLE (3 << 8)
#define AQCTL_CAU_NONE (0 << 4)
#define AQCTL_CAU_CLEAR (1 << 4)
#define AQCTL_CAU_SET (2 << 4)
#define AQCTL_CAU_TOGGLE (3 << 4)
#define AQCTL_ZRO_NONE (0 << 0)
#define AQCTL_ZRO_CLEAR (1 << 0)
#define AQCTL_ZRO_SET (2 << 0)
#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))
#define EPWM_TZSEL 0x24
#define EPWM_TZCTL 0x28
#define EPWM_TZFLG 0x2C
#define EPWM_DBCTL 0x1E
#define DBCTL_MASK (3 << 0)
#define DBCTL_BYPASS 0
#define DBCTL_RISING_EDGE 1
#define DBCTL_FALLING_EDGE 2
#define DBCTL_BOTH_EDGE 3
#define EPWM_PCCTL 0x3C
#define PCCTL_CHPEN_MASK (1 << 0)
#define PCCTL_CHPEN_DISABLE 0
#define PCCTL_CHPEN_ENABLE 1
#define EPWM_HRCTL 0x40
#define HRCTL_DELMODE_BOTH 3
#define HRCTL_DELMODE_FALL 2
#define HRCTL_DELMODE_RISE 1
static device_probe_t am335x_ehrpwm_probe;
static device_attach_t am335x_ehrpwm_attach;
static device_detach_t am335x_ehrpwm_detach;
struct ehrpwm_channel {
u_int duty;
bool enabled;
bool 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;
u_int sc_clkfreq;
u_int sc_clktick;
u_int sc_period;
struct ehrpwm_channel sc_channels[NUM_CHANNELS];
};
static struct ofw_compat_data compat_data[] = {
{"ti,am3352-ehrpwm", true},
{"ti,am33xx-ehrpwm", true},
{NULL, false},
};
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;
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;
if (period < 2 * NS_PER_SEC / PWM_CLOCK) {
sc->sc_clkfreq = 0;
sc->sc_clktick = 0;
sc->sc_period = 0;
return (false);
}
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;
}
if (tbprd == 0)
return (false);
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
am335x_ehrpwm_cfg_duty(sc, 0, 0);
am335x_ehrpwm_cfg_duty(sc, 1, 0);
}
return (true);
}
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_set_flags(device_t dev, u_int channel,
uint32_t flags)
{
struct am335x_ehrpwm_softc *sc;
if (channel >= NUM_CHANNELS)
return (EINVAL);
sc = device_get_softc(dev);
PWM_LOCK(sc);
if (flags & PWM_POLARITY_INVERTED) {
sc->sc_channels[channel].inverted = true;
if (channel == 0)
EPWM_WRITE2(sc, EPWM_AQCTLA,
(AQCTL_ZRO_CLEAR | AQCTL_CAU_SET));
else
EPWM_WRITE2(sc, EPWM_AQCTLB,
(AQCTL_ZRO_CLEAR | AQCTL_CBU_SET));
} else {
sc->sc_channels[channel].inverted = false;
if (channel == 0)
EPWM_WRITE2(sc, EPWM_AQCTLA,
(AQCTL_ZRO_SET | AQCTL_CAU_CLEAR));
else
EPWM_WRITE2(sc, EPWM_AQCTLB,
(AQCTL_ZRO_SET | AQCTL_CBU_CLEAR));
}
PWM_UNLOCK(sc);
return (0);
}
static int
am335x_ehrpwm_channel_get_flags(device_t dev, u_int channel,
uint32_t *flags)
{
struct am335x_ehrpwm_softc *sc;
if (channel >= NUM_CHANNELS)
return (EINVAL);
sc = device_get_softc(dev);
if (sc->sc_channels[channel].inverted == true)
*flags = PWM_POLARITY_INVERTED;
else
*flags = 0;
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)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
return (ENXIO);
device_set_desc(dev, "AM335x EHRPWM");
return (BUS_PROBE_DEFAULT);
}
static int
am335x_ehrpwm_attach(device_t dev)
{
struct am335x_ehrpwm_softc *sc;
uint16_t reg;
sc = device_get_softc(dev);
sc->sc_dev = dev;
PWM_LOCK_INIT(sc);
sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->sc_mem_rid, RF_ACTIVE);
if (sc->sc_mem_res == NULL) {
device_printf(dev, "cannot allocate memory resources\n");
goto fail;
}
reg = EPWM_READ2(sc, EPWM_TBCTL);
reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK);
EPWM_WRITE2(sc, EPWM_TBCTL, reg);
EPWM_WRITE2(sc, EPWM_TBPRD, DEFAULT_PWM_PERIOD - 1);
EPWM_WRITE2(sc, EPWM_CMPA, 0);
EPWM_WRITE2(sc, EPWM_CMPB, 0);
EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR));
EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR));
reg = EPWM_READ2(sc, EPWM_DBCTL);
reg &= ~DBCTL_MASK;
reg |= DBCTL_BYPASS;
EPWM_WRITE2(sc, EPWM_DBCTL, reg);
reg = EPWM_READ2(sc, EPWM_PCCTL);
reg &= ~PCCTL_CHPEN_MASK;
reg |= PCCTL_CHPEN_DISABLE;
EPWM_WRITE2(sc, EPWM_PCCTL, PCCTL_CHPEN_DISABLE);
EPWM_WRITE2(sc, EPWM_TZSEL, 0x0);
EPWM_WRITE2(sc, EPWM_TZCTL, 0xf);
reg = EPWM_READ2(sc, EPWM_TZFLG);
reg &= ~TBCTL_CTRMODE_MASK;
reg |= TBCTL_CTRMODE_UP | TBCTL_FREERUN;
EPWM_WRITE2(sc, EPWM_TBCTL, reg);
if ((sc->sc_busdev = device_add_child(dev, "pwmbus",
DEVICE_UNIT_ANY)) == NULL) {
device_printf(dev, "Cannot add child pwmbus\n");
}
bus_identify_children(dev);
bus_attach_children(dev);
return (0);
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);
}
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_mem_res)
bus_release_resource(dev, SYS_RES_MEMORY,
sc->sc_mem_rid, sc->sc_mem_res);
PWM_UNLOCK(sc);
PWM_LOCK_DESTROY(sc);
return (0);
}
static phandle_t
am335x_ehrpwm_get_node(device_t bus, device_t dev)
{
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),
DEVMETHOD(ofw_bus_get_node, am335x_ehrpwm_get_node),
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_set_flags, am335x_ehrpwm_channel_set_flags),
DEVMETHOD(pwmbus_channel_get_flags, am335x_ehrpwm_channel_get_flags),
DEVMETHOD(pwmbus_channel_enable, am335x_ehrpwm_channel_enable),
DEVMETHOD(pwmbus_channel_is_enabled, am335x_ehrpwm_channel_is_enabled),
DEVMETHOD_END
};
static driver_t am335x_ehrpwm_driver = {
"pwm",
am335x_ehrpwm_methods,
sizeof(struct am335x_ehrpwm_softc),
};
DRIVER_MODULE(am335x_ehrpwm, am335x_pwmss, am335x_ehrpwm_driver, 0, 0);
MODULE_VERSION(am335x_ehrpwm, 1);
MODULE_DEPEND(am335x_ehrpwm, am335x_pwmss, 1, 1, 1);
MODULE_DEPEND(am335x_ehrpwm, pwmbus, 1, 1, 1);