1*471aeecfSnaddy /* $OpenBSD: imxpwm.c,v 1.2 2022/04/06 18:59:28 naddy Exp $ */
28174da49Spatrick /*
38174da49Spatrick * Copyright (c) 2018-2020 Patrick Wildt <patrick@blueri.se>
48174da49Spatrick *
58174da49Spatrick * Permission to use, copy, modify, and distribute this software for any
68174da49Spatrick * purpose with or without fee is hereby granted, provided that the above
78174da49Spatrick * copyright notice and this permission notice appear in all copies.
88174da49Spatrick *
98174da49Spatrick * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
108174da49Spatrick * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
118174da49Spatrick * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
128174da49Spatrick * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
138174da49Spatrick * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
148174da49Spatrick * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
158174da49Spatrick * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
168174da49Spatrick */
178174da49Spatrick
188174da49Spatrick #include <sys/param.h>
198174da49Spatrick #include <sys/systm.h>
208174da49Spatrick #include <sys/malloc.h>
218174da49Spatrick #include <sys/device.h>
228174da49Spatrick #include <sys/sysctl.h>
238174da49Spatrick
248174da49Spatrick #include <machine/bus.h>
258174da49Spatrick #include <machine/fdt.h>
268174da49Spatrick
278174da49Spatrick #include <dev/ofw/openfirm.h>
288174da49Spatrick #include <dev/ofw/fdt.h>
298174da49Spatrick #include <dev/ofw/ofw_clock.h>
308174da49Spatrick #include <dev/ofw/ofw_pinctrl.h>
318174da49Spatrick #include <dev/ofw/ofw_misc.h>
328174da49Spatrick
338174da49Spatrick #define PWM_CR 0x00
348174da49Spatrick #define PWM_CR_EN (1 << 0)
358174da49Spatrick #define PWM_CR_SWR (1 << 3)
368174da49Spatrick #define PWM_CR_CLKSRC_IPG (1 << 16)
378174da49Spatrick #define PWM_CR_CLKSRC_IPG_HIGH (2 << 16)
388174da49Spatrick #define PWM_CR_DBGEN (1 << 22)
398174da49Spatrick #define PWM_CR_WAITEN (1 << 23)
408174da49Spatrick #define PWM_CR_DOZEEN (1 << 24)
418174da49Spatrick #define PWM_CR_PRESCALER(x) ((((x) - 1) & 0xfff) << 4)
428174da49Spatrick #define PWM_CR_PRESCALER_SHIFT 4
438174da49Spatrick #define PWM_CR_PRESCALER_MASK 0xfff
448174da49Spatrick #define PWM_SR 0x04
458174da49Spatrick #define PWM_SR_FIFOAV_4WORDS 0x4
468174da49Spatrick #define PWM_SR_FIFOAV_MASK 0x7
478174da49Spatrick #define PWM_SAR 0x0c
488174da49Spatrick #define PWM_PR 0x10
498174da49Spatrick #define PWM_PR_MAX 0xfffe
508174da49Spatrick
518174da49Spatrick #define NS_PER_S 1000000000
528174da49Spatrick
538174da49Spatrick #define HREAD4(sc, reg) \
548174da49Spatrick (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
558174da49Spatrick #define HWRITE4(sc, reg, val) \
568174da49Spatrick bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
578174da49Spatrick #define HSET4(sc, reg, bits) \
588174da49Spatrick HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
598174da49Spatrick #define HCLR4(sc, reg, bits) \
608174da49Spatrick HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
618174da49Spatrick
628174da49Spatrick struct imxpwm_softc {
638174da49Spatrick struct device sc_dev;
648174da49Spatrick bus_space_tag_t sc_iot;
658174da49Spatrick bus_space_handle_t sc_ioh;
668174da49Spatrick
678174da49Spatrick uint32_t sc_dcycles;
688174da49Spatrick uint32_t sc_clkin;
698174da49Spatrick struct pwm_device sc_pd;
708174da49Spatrick };
718174da49Spatrick
728174da49Spatrick int imxpwm_match(struct device *, void *, void *);
738174da49Spatrick void imxpwm_attach(struct device *, struct device *, void *);
748174da49Spatrick
75*471aeecfSnaddy const struct cfattach imxpwm_ca = {
768174da49Spatrick sizeof(struct imxpwm_softc), imxpwm_match, imxpwm_attach
778174da49Spatrick };
788174da49Spatrick
798174da49Spatrick struct cfdriver imxpwm_cd = {
808174da49Spatrick NULL, "imxpwm", DV_DULL
818174da49Spatrick };
828174da49Spatrick
838174da49Spatrick int imxpwm_get_state(void *, uint32_t *, struct pwm_state *);
848174da49Spatrick int imxpwm_set_state(void *, uint32_t *, struct pwm_state *);
858174da49Spatrick
868174da49Spatrick int
imxpwm_match(struct device * parent,void * match,void * aux)878174da49Spatrick imxpwm_match(struct device *parent, void *match, void *aux)
888174da49Spatrick {
898174da49Spatrick struct fdt_attach_args *faa = aux;
908174da49Spatrick
918174da49Spatrick return OF_is_compatible(faa->fa_node, "fsl,imx27-pwm");
928174da49Spatrick }
938174da49Spatrick
948174da49Spatrick void
imxpwm_attach(struct device * parent,struct device * self,void * aux)958174da49Spatrick imxpwm_attach(struct device *parent, struct device *self, void *aux)
968174da49Spatrick {
978174da49Spatrick struct imxpwm_softc *sc = (struct imxpwm_softc *)self;
988174da49Spatrick struct fdt_attach_args *faa = aux;
998174da49Spatrick
1008174da49Spatrick if (faa->fa_nreg < 1)
1018174da49Spatrick return;
1028174da49Spatrick
1038174da49Spatrick sc->sc_clkin = clock_get_frequency(faa->fa_node, "per");
1048174da49Spatrick if (sc->sc_clkin == 0) {
1058174da49Spatrick printf(": no clock\n");
1068174da49Spatrick return;
1078174da49Spatrick }
1088174da49Spatrick
1098174da49Spatrick sc->sc_iot = faa->fa_iot;
1108174da49Spatrick if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
1118174da49Spatrick faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
1128174da49Spatrick printf(": can't map registers");
1138174da49Spatrick return;
1148174da49Spatrick }
1158174da49Spatrick
1168174da49Spatrick printf("\n");
1178174da49Spatrick
1188174da49Spatrick pinctrl_byname(faa->fa_node, "default");
1198174da49Spatrick
1208174da49Spatrick clock_enable_all(faa->fa_node);
1218174da49Spatrick reset_deassert_all(faa->fa_node);
1228174da49Spatrick
1238174da49Spatrick sc->sc_pd.pd_node = faa->fa_node;
1248174da49Spatrick sc->sc_pd.pd_cookie = sc;
1258174da49Spatrick sc->sc_pd.pd_get_state = imxpwm_get_state;
1268174da49Spatrick sc->sc_pd.pd_set_state = imxpwm_set_state;
1278174da49Spatrick
1288174da49Spatrick pwm_register(&sc->sc_pd);
1298174da49Spatrick }
1308174da49Spatrick
1318174da49Spatrick int
imxpwm_get_state(void * cookie,uint32_t * cells,struct pwm_state * ps)1328174da49Spatrick imxpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
1338174da49Spatrick {
1348174da49Spatrick struct imxpwm_softc *sc = cookie;
1358174da49Spatrick uint64_t dcycles, pcycles, prescale, pwmclk;
1368174da49Spatrick int enabled = 0;
1378174da49Spatrick
1388174da49Spatrick prescale = ((HREAD4(sc, PWM_CR) >> PWM_CR_PRESCALER_SHIFT) &
1398174da49Spatrick PWM_CR_PRESCALER_MASK) + 1;
1408174da49Spatrick pwmclk = (sc->sc_clkin + (prescale / 2)) / prescale;
1418174da49Spatrick if (pwmclk == 0)
1428174da49Spatrick return EINVAL;
1438174da49Spatrick
1448174da49Spatrick if (HREAD4(sc, PWM_CR) & PWM_CR_EN)
1458174da49Spatrick enabled = 1;
1468174da49Spatrick
1478174da49Spatrick pcycles = HREAD4(sc, PWM_PR);
1488174da49Spatrick if (pcycles >= PWM_PR_MAX)
1498174da49Spatrick pcycles = PWM_PR_MAX;
1508174da49Spatrick pcycles = (pcycles + 2) * NS_PER_S;
1518174da49Spatrick pcycles = (pcycles + (pwmclk / 2)) / pwmclk;
1528174da49Spatrick
1538174da49Spatrick dcycles = sc->sc_dcycles;
1548174da49Spatrick if (enabled)
1558174da49Spatrick dcycles = HREAD4(sc, PWM_SAR);
1568174da49Spatrick dcycles = dcycles * NS_PER_S;
1578174da49Spatrick dcycles = (dcycles + (pwmclk / 2)) / pwmclk;
1588174da49Spatrick
1598174da49Spatrick memset(ps, 0, sizeof(struct pwm_state));
1608174da49Spatrick ps->ps_period = pcycles;
1618174da49Spatrick ps->ps_pulse_width = dcycles;
1628174da49Spatrick ps->ps_enabled = enabled;
1638174da49Spatrick return 0;
1648174da49Spatrick }
1658174da49Spatrick
1668174da49Spatrick int
imxpwm_set_state(void * cookie,uint32_t * cells,struct pwm_state * ps)1678174da49Spatrick imxpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
1688174da49Spatrick {
1698174da49Spatrick struct imxpwm_softc *sc = cookie;
1708174da49Spatrick uint64_t dcycles, pcycles, prescale;
1718174da49Spatrick int i;
1728174da49Spatrick
1738174da49Spatrick if (ps->ps_enabled) {
1748174da49Spatrick pcycles = sc->sc_clkin;
1758174da49Spatrick pcycles = (pcycles * ps->ps_period) / NS_PER_S;
1768174da49Spatrick prescale = pcycles / 0x10000 + 1;
1778174da49Spatrick
1788174da49Spatrick if (ps->ps_period == 0 || prescale == 0)
1798174da49Spatrick return EINVAL;
1808174da49Spatrick
1818174da49Spatrick pcycles = pcycles / prescale;
1828174da49Spatrick dcycles = (pcycles * ps->ps_pulse_width) / ps->ps_period;
1838174da49Spatrick
1848174da49Spatrick if (pcycles > 2)
1858174da49Spatrick pcycles -= 2;
1868174da49Spatrick else
1878174da49Spatrick pcycles = 0;
1888174da49Spatrick }
1898174da49Spatrick
1908174da49Spatrick /* disable and flush fifo */
1918174da49Spatrick HCLR4(sc, PWM_CR, PWM_CR_EN);
1928174da49Spatrick HWRITE4(sc, PWM_CR, PWM_CR_SWR);
1938174da49Spatrick for (i = 0; i < 5; i++) {
1948174da49Spatrick delay(1000);
1958174da49Spatrick if ((HREAD4(sc, PWM_CR) & PWM_CR_SWR) == 0)
1968174da49Spatrick break;
1978174da49Spatrick }
1988174da49Spatrick if (i == 5) {
1998174da49Spatrick printf("%s: reset timeout\n", sc->sc_dev.dv_xname);
2008174da49Spatrick return ETIMEDOUT;
2018174da49Spatrick }
2028174da49Spatrick
2038174da49Spatrick if (ps->ps_enabled) {
2048174da49Spatrick HWRITE4(sc, PWM_SAR, dcycles);
2058174da49Spatrick HWRITE4(sc, PWM_PR, pcycles);
2068174da49Spatrick
2078174da49Spatrick sc->sc_dcycles = dcycles;
2088174da49Spatrick
2098174da49Spatrick HWRITE4(sc, PWM_CR, PWM_CR_PRESCALER(prescale) |
2108174da49Spatrick PWM_CR_DOZEEN | PWM_CR_WAITEN |
2118174da49Spatrick PWM_CR_DBGEN | PWM_CR_CLKSRC_IPG_HIGH |
2128174da49Spatrick PWM_CR_EN);
2138174da49Spatrick }
2148174da49Spatrick
2158174da49Spatrick return 0;
2168174da49Spatrick }
217