Path: blob/main/sys/arm/ti/am335x/am335x_ehrpwm.c
110378 views
/*-1* Copyright (c) 2013 Oleksandr Tymoshenko <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND14* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE15* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE17* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF23* SUCH DAMAGE.24*/2526#include <sys/param.h>27#include <sys/systm.h>28#include <sys/bus.h>29#include <sys/kernel.h>30#include <sys/limits.h>31#include <sys/lock.h>32#include <sys/module.h>33#include <sys/mutex.h>34#include <sys/resource.h>35#include <sys/rman.h>3637#include <machine/bus.h>3839#include <dev/ofw/openfirm.h>40#include <dev/ofw/ofw_bus.h>41#include <dev/ofw/ofw_bus_subr.h>4243#include <dev/pwm/pwmc.h>4445#include "pwmbus_if.h"4647#include "am335x_pwm.h"4849/*******************************************************************************50* Enhanced resolution PWM driver. Many of the advanced featues of the hardware51* are not supported by this driver. What is implemented here is simple52* variable-duty-cycle PWM output.53******************************************************************************/5455/* In ticks */56#define DEFAULT_PWM_PERIOD 100057#define PWM_CLOCK 100000000UL5859#define NS_PER_SEC 10000000006061#define PWM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)62#define PWM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)63#define PWM_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED)64#define PWM_LOCK_INIT(_sc) mtx_init(&(_sc)->sc_mtx, \65device_get_nameunit(_sc->sc_dev), "am335x_ehrpwm softc", MTX_DEF)66#define PWM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx)6768#define EPWM_READ2(_sc, reg) bus_read_2((_sc)->sc_mem_res, reg)69#define EPWM_WRITE2(_sc, reg, value) \70bus_write_2((_sc)->sc_mem_res, reg, value)7172#define EPWM_TBCTL 0x0073/* see 15.2.2.11 for the first two, used in debug situations */74#define TBCTL_FREERUN_STOP_NEXT_TBC_INCREMENT (0 << 14)75#define TBCTL_FREERUN_STOP_COMPLETE_CYCLE (1 << 14)76/* ignore suspend control signal */77#define TBCTL_FREERUN (2 << 14)7879#define TBCTL_PHDIR_UP (1 << 13)80#define TBCTL_PHDIR_DOWN (0 << 13)81#define TBCTL_CLKDIV(x) ((x) << 10)82#define TBCTL_CLKDIV_MASK (7 << 10)83#define TBCTL_HSPCLKDIV(x) ((x) << 7)84#define TBCTL_HSPCLKDIV_MASK (7 << 7)85#define TBCTL_SYNCOSEL_DISABLED (3 << 4)86#define TBCTL_PRDLD_SHADOW (0 << 3)87#define TBCTL_PRDLD_IMMEDIATE (1 << 3)88#define TBCTL_PHSEN_DISABLED (0 << 2)89#define TBCTL_PHSEN_ENABLED (1 << 2)90#define TBCTL_CTRMODE_MASK (3)91#define TBCTL_CTRMODE_UP (0 << 0)92#define TBCTL_CTRMODE_DOWN (1 << 0)93#define TBCTL_CTRMODE_UPDOWN (2 << 0)94#define TBCTL_CTRMODE_FREEZE (3 << 0)9596#define EPWM_TBSTS 0x0297#define EPWM_TBPHSHR 0x0498#define EPWM_TBPHS 0x0699#define EPWM_TBCNT 0x08100#define EPWM_TBPRD 0x0a101/* Counter-compare */102#define EPWM_CMPCTL 0x0e103#define CMPCTL_SHDWBMODE_SHADOW (1 << 6)104#define CMPCTL_SHDWBMODE_IMMEDIATE (0 << 6)105#define CMPCTL_SHDWAMODE_SHADOW (1 << 4)106#define CMPCTL_SHDWAMODE_IMMEDIATE (0 << 4)107#define CMPCTL_LOADBMODE_ZERO (0 << 2)108#define CMPCTL_LOADBMODE_PRD (1 << 2)109#define CMPCTL_LOADBMODE_EITHER (2 << 2)110#define CMPCTL_LOADBMODE_FREEZE (3 << 2)111#define CMPCTL_LOADAMODE_ZERO (0 << 0)112#define CMPCTL_LOADAMODE_PRD (1 << 0)113#define CMPCTL_LOADAMODE_EITHER (2 << 0)114#define CMPCTL_LOADAMODE_FREEZE (3 << 0)115#define EPWM_CMPAHR 0x10116#define EPWM_CMPA 0x12117#define EPWM_CMPB 0x14118/* CMPCTL_LOADAMODE_ZERO */119#define EPWM_AQCTLA 0x16120#define EPWM_AQCTLB 0x18121#define AQCTL_CBU_NONE (0 << 8)122#define AQCTL_CBU_CLEAR (1 << 8)123#define AQCTL_CBU_SET (2 << 8)124#define AQCTL_CBU_TOGGLE (3 << 8)125#define AQCTL_CAU_NONE (0 << 4)126#define AQCTL_CAU_CLEAR (1 << 4)127#define AQCTL_CAU_SET (2 << 4)128#define AQCTL_CAU_TOGGLE (3 << 4)129#define AQCTL_ZRO_NONE (0 << 0)130#define AQCTL_ZRO_CLEAR (1 << 0)131#define AQCTL_ZRO_SET (2 << 0)132#define AQCTL_ZRO_TOGGLE (3 << 0)133#define EPWM_AQSFRC 0x1a134#define EPWM_AQCSFRC 0x1c135#define AQCSFRC_OFF 0136#define AQCSFRC_LO 1137#define AQCSFRC_HI 2138#define AQCSFRC_MASK 3139#define AQCSFRC(chan, hilo) ((hilo) << (2 * chan))140141/* Trip-Zone module */142#define EPWM_TZSEL 0x24143#define EPWM_TZCTL 0x28144#define EPWM_TZFLG 0x2C145146/* Dead band */147#define EPWM_DBCTL 0x1E148#define DBCTL_MASK (3 << 0)149#define DBCTL_BYPASS 0150#define DBCTL_RISING_EDGE 1151#define DBCTL_FALLING_EDGE 2152#define DBCTL_BOTH_EDGE 3153154/* PWM-chopper */155#define EPWM_PCCTL 0x3C156#define PCCTL_CHPEN_MASK (1 << 0)157#define PCCTL_CHPEN_DISABLE 0158#define PCCTL_CHPEN_ENABLE 1159160/* High-Resolution PWM */161#define EPWM_HRCTL 0x40162#define HRCTL_DELMODE_BOTH 3163#define HRCTL_DELMODE_FALL 2164#define HRCTL_DELMODE_RISE 1165166static device_probe_t am335x_ehrpwm_probe;167static device_attach_t am335x_ehrpwm_attach;168static device_detach_t am335x_ehrpwm_detach;169170struct ehrpwm_channel {171u_int duty; /* on duration, in ns */172bool enabled; /* channel enabled? */173bool inverted; /* signal inverted? */174};175#define NUM_CHANNELS 2176177struct am335x_ehrpwm_softc {178device_t sc_dev;179device_t sc_busdev;180struct mtx sc_mtx;181struct resource *sc_mem_res;182int sc_mem_rid;183184/* Things used for configuration via pwm(9) api. */185u_int sc_clkfreq; /* frequency in Hz */186u_int sc_clktick; /* duration in ns */187u_int sc_period; /* duration in ns */188struct ehrpwm_channel sc_channels[NUM_CHANNELS];189};190191static struct ofw_compat_data compat_data[] = {192{"ti,am3352-ehrpwm", true},193{"ti,am33xx-ehrpwm", true},194{NULL, false},195};196SIMPLEBUS_PNP_INFO(compat_data);197198static void199am335x_ehrpwm_cfg_duty(struct am335x_ehrpwm_softc *sc, u_int chan, u_int duty)200{201u_int tbcmp;202203if (duty == 0)204tbcmp = 0;205else206tbcmp = max(1, duty / sc->sc_clktick);207208sc->sc_channels[chan].duty = tbcmp * sc->sc_clktick;209210PWM_LOCK_ASSERT(sc);211EPWM_WRITE2(sc, (chan == 0) ? EPWM_CMPA : EPWM_CMPB, tbcmp);212}213214static void215am335x_ehrpwm_cfg_enable(struct am335x_ehrpwm_softc *sc, u_int chan, bool enable)216{217uint16_t regval;218219sc->sc_channels[chan].enabled = enable;220221/*222* Turn off any existing software-force of the channel, then force223* it in the right direction (high or low) if it's not being enabled.224*/225PWM_LOCK_ASSERT(sc);226regval = EPWM_READ2(sc, EPWM_AQCSFRC);227regval &= ~AQCSFRC(chan, AQCSFRC_MASK);228if (!sc->sc_channels[chan].enabled) {229if (sc->sc_channels[chan].inverted)230regval |= AQCSFRC(chan, AQCSFRC_HI);231else232regval |= AQCSFRC(chan, AQCSFRC_LO);233}234EPWM_WRITE2(sc, EPWM_AQCSFRC, regval);235}236237static bool238am335x_ehrpwm_cfg_period(struct am335x_ehrpwm_softc *sc, u_int period)239{240uint16_t regval;241u_int clkdiv, hspclkdiv, pwmclk, pwmtick, tbprd;242243/* Can't do a period shorter than 2 clock ticks. */244if (period < 2 * NS_PER_SEC / PWM_CLOCK) {245sc->sc_clkfreq = 0;246sc->sc_clktick = 0;247sc->sc_period = 0;248return (false);249}250251/*252* Figure out how much we have to divide down the base 100MHz clock so253* that we can express the requested period as a 16-bit tick count.254*/255tbprd = 0;256for (clkdiv = 0; clkdiv < 8; ++clkdiv) {257const u_int cd = 1 << clkdiv;258for (hspclkdiv = 0; hspclkdiv < 8; ++hspclkdiv) {259const u_int cdhs = max(1, hspclkdiv * 2);260pwmclk = PWM_CLOCK / (cd * cdhs);261pwmtick = NS_PER_SEC / pwmclk;262if (period / pwmtick < 65536) {263tbprd = period / pwmtick;264break;265}266}267if (tbprd != 0)268break;269}270271/* Handle requested period too long for available clock divisors. */272if (tbprd == 0)273return (false);274275/*276* If anything has changed from the current settings, reprogram the277* clock divisors and period register.278*/279if (sc->sc_clkfreq != pwmclk || sc->sc_clktick != pwmtick ||280sc->sc_period != tbprd * pwmtick) {281sc->sc_clkfreq = pwmclk;282sc->sc_clktick = pwmtick;283sc->sc_period = tbprd * pwmtick;284285PWM_LOCK_ASSERT(sc);286regval = EPWM_READ2(sc, EPWM_TBCTL);287regval &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK);288regval |= TBCTL_CLKDIV(clkdiv) | TBCTL_HSPCLKDIV(hspclkdiv);289EPWM_WRITE2(sc, EPWM_TBCTL, regval);290EPWM_WRITE2(sc, EPWM_TBPRD, tbprd - 1);291#if 0292device_printf(sc->sc_dev, "clkdiv %u hspclkdiv %u tbprd %u "293"clkfreq %u Hz clktick %u ns period got %u requested %u\n",294clkdiv, hspclkdiv, tbprd - 1,295sc->sc_clkfreq, sc->sc_clktick, sc->sc_period, period);296#endif297/*298* If the period changed, that invalidates the current CMP299* registers (duty values), just zero them out.300*/301am335x_ehrpwm_cfg_duty(sc, 0, 0);302am335x_ehrpwm_cfg_duty(sc, 1, 0);303}304305return (true);306}307308static int309am335x_ehrpwm_channel_count(device_t dev, u_int *nchannel)310{311312*nchannel = NUM_CHANNELS;313314return (0);315}316317static int318am335x_ehrpwm_channel_config(device_t dev, u_int channel, u_int period, u_int duty)319{320struct am335x_ehrpwm_softc *sc;321bool status;322323if (channel >= NUM_CHANNELS)324return (EINVAL);325326sc = device_get_softc(dev);327328PWM_LOCK(sc);329status = am335x_ehrpwm_cfg_period(sc, period);330if (status)331am335x_ehrpwm_cfg_duty(sc, channel, duty);332PWM_UNLOCK(sc);333334return (status ? 0 : EINVAL);335}336337static int338am335x_ehrpwm_channel_get_config(device_t dev, u_int channel,339u_int *period, u_int *duty)340{341struct am335x_ehrpwm_softc *sc;342343if (channel >= NUM_CHANNELS)344return (EINVAL);345346sc = device_get_softc(dev);347*period = sc->sc_period;348*duty = sc->sc_channels[channel].duty;349return (0);350}351352static int353am335x_ehrpwm_channel_set_flags(device_t dev, u_int channel,354uint32_t flags)355{356struct am335x_ehrpwm_softc *sc;357358if (channel >= NUM_CHANNELS)359return (EINVAL);360361sc = device_get_softc(dev);362363PWM_LOCK(sc);364if (flags & PWM_POLARITY_INVERTED) {365sc->sc_channels[channel].inverted = true;366/* Action-Qualifier 15.2.2.5 */367if (channel == 0)368EPWM_WRITE2(sc, EPWM_AQCTLA,369(AQCTL_ZRO_CLEAR | AQCTL_CAU_SET));370else371EPWM_WRITE2(sc, EPWM_AQCTLB,372(AQCTL_ZRO_CLEAR | AQCTL_CBU_SET));373} else {374sc->sc_channels[channel].inverted = false;375if (channel == 0)376EPWM_WRITE2(sc, EPWM_AQCTLA,377(AQCTL_ZRO_SET | AQCTL_CAU_CLEAR));378else379EPWM_WRITE2(sc, EPWM_AQCTLB,380(AQCTL_ZRO_SET | AQCTL_CBU_CLEAR));381}382PWM_UNLOCK(sc);383384return (0);385}386387static int388am335x_ehrpwm_channel_get_flags(device_t dev, u_int channel,389uint32_t *flags)390{391struct am335x_ehrpwm_softc *sc;392if (channel >= NUM_CHANNELS)393return (EINVAL);394395sc = device_get_softc(dev);396397if (sc->sc_channels[channel].inverted == true)398*flags = PWM_POLARITY_INVERTED;399else400*flags = 0;401402return (0);403}404405406static int407am335x_ehrpwm_channel_enable(device_t dev, u_int channel, bool enable)408{409struct am335x_ehrpwm_softc *sc;410411if (channel >= NUM_CHANNELS)412return (EINVAL);413414sc = device_get_softc(dev);415416PWM_LOCK(sc);417am335x_ehrpwm_cfg_enable(sc, channel, enable);418PWM_UNLOCK(sc);419420return (0);421}422423static int424am335x_ehrpwm_channel_is_enabled(device_t dev, u_int channel, bool *enabled)425{426struct am335x_ehrpwm_softc *sc;427428if (channel >= NUM_CHANNELS)429return (EINVAL);430431sc = device_get_softc(dev);432433*enabled = sc->sc_channels[channel].enabled;434435return (0);436}437438static int439am335x_ehrpwm_probe(device_t dev)440{441442if (!ofw_bus_status_okay(dev))443return (ENXIO);444445if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)446return (ENXIO);447448device_set_desc(dev, "AM335x EHRPWM");449450return (BUS_PROBE_DEFAULT);451}452453static int454am335x_ehrpwm_attach(device_t dev)455{456struct am335x_ehrpwm_softc *sc;457uint16_t reg;458459sc = device_get_softc(dev);460sc->sc_dev = dev;461462PWM_LOCK_INIT(sc);463464sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,465&sc->sc_mem_rid, RF_ACTIVE);466if (sc->sc_mem_res == NULL) {467device_printf(dev, "cannot allocate memory resources\n");468goto fail;469}470471/* CONFIGURE EPWM */472reg = EPWM_READ2(sc, EPWM_TBCTL);473reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK);474EPWM_WRITE2(sc, EPWM_TBCTL, reg);475476EPWM_WRITE2(sc, EPWM_TBPRD, DEFAULT_PWM_PERIOD - 1);477EPWM_WRITE2(sc, EPWM_CMPA, 0);478EPWM_WRITE2(sc, EPWM_CMPB, 0);479480/* Action-Qualifier 15.2.2.5 */481EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR));482EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR));483484/* Dead band 15.2.2.6 */485reg = EPWM_READ2(sc, EPWM_DBCTL);486reg &= ~DBCTL_MASK;487reg |= DBCTL_BYPASS;488EPWM_WRITE2(sc, EPWM_DBCTL, reg);489490/* PWM-chopper described in 15.2.2.7 */491/* Acc. TRM used in pulse transformerbased gate drivers492* to control the power switching-elements493*/494reg = EPWM_READ2(sc, EPWM_PCCTL);495reg &= ~PCCTL_CHPEN_MASK;496reg |= PCCTL_CHPEN_DISABLE;497EPWM_WRITE2(sc, EPWM_PCCTL, PCCTL_CHPEN_DISABLE);498499/* Trip zone are described in 15.2.2.8.500* Essential its used to detect faults and can be configured501* to react on such faults..502*/503/* disable TZn as one-shot / CVC trip source 15.2.4.18 */504EPWM_WRITE2(sc, EPWM_TZSEL, 0x0);505/* reg described in 15.2.4.19 */506EPWM_WRITE2(sc, EPWM_TZCTL, 0xf);507reg = EPWM_READ2(sc, EPWM_TZFLG);508509/* START EPWM */510reg &= ~TBCTL_CTRMODE_MASK;511reg |= TBCTL_CTRMODE_UP | TBCTL_FREERUN;512EPWM_WRITE2(sc, EPWM_TBCTL, reg);513514if ((sc->sc_busdev = device_add_child(dev, "pwmbus",515DEVICE_UNIT_ANY)) == NULL) {516device_printf(dev, "Cannot add child pwmbus\n");517// This driver can still do things even without the bus child.518}519520bus_identify_children(dev);521bus_attach_children(dev);522return (0);523fail:524PWM_LOCK_DESTROY(sc);525if (sc->sc_mem_res)526bus_release_resource(dev, SYS_RES_MEMORY,527sc->sc_mem_rid, sc->sc_mem_res);528529return(ENXIO);530}531532static int533am335x_ehrpwm_detach(device_t dev)534{535struct am335x_ehrpwm_softc *sc;536int error;537538sc = device_get_softc(dev);539540if ((error = bus_generic_detach(sc->sc_dev)) != 0)541return (error);542543PWM_LOCK(sc);544545if (sc->sc_mem_res)546bus_release_resource(dev, SYS_RES_MEMORY,547sc->sc_mem_rid, sc->sc_mem_res);548549PWM_UNLOCK(sc);550551PWM_LOCK_DESTROY(sc);552553return (0);554}555556static phandle_t557am335x_ehrpwm_get_node(device_t bus, device_t dev)558{559560/*561* Share our controller node with our pwmbus child; it instantiates562* devices by walking the children contained within our node.563*/564return ofw_bus_get_node(bus);565}566567static device_method_t am335x_ehrpwm_methods[] = {568DEVMETHOD(device_probe, am335x_ehrpwm_probe),569DEVMETHOD(device_attach, am335x_ehrpwm_attach),570DEVMETHOD(device_detach, am335x_ehrpwm_detach),571572/* ofw_bus_if */573DEVMETHOD(ofw_bus_get_node, am335x_ehrpwm_get_node),574575/* pwm interface */576DEVMETHOD(pwmbus_channel_count, am335x_ehrpwm_channel_count),577DEVMETHOD(pwmbus_channel_config, am335x_ehrpwm_channel_config),578DEVMETHOD(pwmbus_channel_get_config, am335x_ehrpwm_channel_get_config),579DEVMETHOD(pwmbus_channel_set_flags, am335x_ehrpwm_channel_set_flags),580DEVMETHOD(pwmbus_channel_get_flags, am335x_ehrpwm_channel_get_flags),581DEVMETHOD(pwmbus_channel_enable, am335x_ehrpwm_channel_enable),582DEVMETHOD(pwmbus_channel_is_enabled, am335x_ehrpwm_channel_is_enabled),583584DEVMETHOD_END585};586587static driver_t am335x_ehrpwm_driver = {588"pwm",589am335x_ehrpwm_methods,590sizeof(struct am335x_ehrpwm_softc),591};592593DRIVER_MODULE(am335x_ehrpwm, am335x_pwmss, am335x_ehrpwm_driver, 0, 0);594MODULE_VERSION(am335x_ehrpwm, 1);595MODULE_DEPEND(am335x_ehrpwm, am335x_pwmss, 1, 1, 1);596MODULE_DEPEND(am335x_ehrpwm, pwmbus, 1, 1, 1);597598599