1/* $OpenBSD: imxpwm.c,v 1.2 2022/04/06 18:59:28 naddy Exp $ */ 2/* 3 * Copyright (c) 2018-2020 Patrick Wildt <patrick@blueri.se> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/param.h> 19#include <sys/systm.h> 20#include <sys/malloc.h> 21#include <sys/device.h> 22#include <sys/sysctl.h> 23 24#include <machine/bus.h> 25#include <machine/fdt.h> 26 27#include <dev/ofw/openfirm.h> 28#include <dev/ofw/fdt.h> 29#include <dev/ofw/ofw_clock.h> 30#include <dev/ofw/ofw_pinctrl.h> 31#include <dev/ofw/ofw_misc.h> 32 33#define PWM_CR 0x00 34#define PWM_CR_EN (1 << 0) 35#define PWM_CR_SWR (1 << 3) 36#define PWM_CR_CLKSRC_IPG (1 << 16) 37#define PWM_CR_CLKSRC_IPG_HIGH (2 << 16) 38#define PWM_CR_DBGEN (1 << 22) 39#define PWM_CR_WAITEN (1 << 23) 40#define PWM_CR_DOZEEN (1 << 24) 41#define PWM_CR_PRESCALER(x) ((((x) - 1) & 0xfff) << 4) 42#define PWM_CR_PRESCALER_SHIFT 4 43#define PWM_CR_PRESCALER_MASK 0xfff 44#define PWM_SR 0x04 45#define PWM_SR_FIFOAV_4WORDS 0x4 46#define PWM_SR_FIFOAV_MASK 0x7 47#define PWM_SAR 0x0c 48#define PWM_PR 0x10 49#define PWM_PR_MAX 0xfffe 50 51#define NS_PER_S 1000000000 52 53#define HREAD4(sc, reg) \ 54 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) 55#define HWRITE4(sc, reg, val) \ 56 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 57#define HSET4(sc, reg, bits) \ 58 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) 59#define HCLR4(sc, reg, bits) \ 60 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) 61 62struct imxpwm_softc { 63 struct device sc_dev; 64 bus_space_tag_t sc_iot; 65 bus_space_handle_t sc_ioh; 66 67 uint32_t sc_dcycles; 68 uint32_t sc_clkin; 69 struct pwm_device sc_pd; 70}; 71 72int imxpwm_match(struct device *, void *, void *); 73void imxpwm_attach(struct device *, struct device *, void *); 74 75const struct cfattach imxpwm_ca = { 76 sizeof(struct imxpwm_softc), imxpwm_match, imxpwm_attach 77}; 78 79struct cfdriver imxpwm_cd = { 80 NULL, "imxpwm", DV_DULL 81}; 82 83int imxpwm_get_state(void *, uint32_t *, struct pwm_state *); 84int imxpwm_set_state(void *, uint32_t *, struct pwm_state *); 85 86int 87imxpwm_match(struct device *parent, void *match, void *aux) 88{ 89 struct fdt_attach_args *faa = aux; 90 91 return OF_is_compatible(faa->fa_node, "fsl,imx27-pwm"); 92} 93 94void 95imxpwm_attach(struct device *parent, struct device *self, void *aux) 96{ 97 struct imxpwm_softc *sc = (struct imxpwm_softc *)self; 98 struct fdt_attach_args *faa = aux; 99 100 if (faa->fa_nreg < 1) 101 return; 102 103 sc->sc_clkin = clock_get_frequency(faa->fa_node, "per"); 104 if (sc->sc_clkin == 0) { 105 printf(": no clock\n"); 106 return; 107 } 108 109 sc->sc_iot = faa->fa_iot; 110 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 111 faa->fa_reg[0].size, 0, &sc->sc_ioh)) { 112 printf(": can't map registers"); 113 return; 114 } 115 116 printf("\n"); 117 118 pinctrl_byname(faa->fa_node, "default"); 119 120 clock_enable_all(faa->fa_node); 121 reset_deassert_all(faa->fa_node); 122 123 sc->sc_pd.pd_node = faa->fa_node; 124 sc->sc_pd.pd_cookie = sc; 125 sc->sc_pd.pd_get_state = imxpwm_get_state; 126 sc->sc_pd.pd_set_state = imxpwm_set_state; 127 128 pwm_register(&sc->sc_pd); 129} 130 131int 132imxpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 133{ 134 struct imxpwm_softc *sc = cookie; 135 uint64_t dcycles, pcycles, prescale, pwmclk; 136 int enabled = 0; 137 138 prescale = ((HREAD4(sc, PWM_CR) >> PWM_CR_PRESCALER_SHIFT) & 139 PWM_CR_PRESCALER_MASK) + 1; 140 pwmclk = (sc->sc_clkin + (prescale / 2)) / prescale; 141 if (pwmclk == 0) 142 return EINVAL; 143 144 if (HREAD4(sc, PWM_CR) & PWM_CR_EN) 145 enabled = 1; 146 147 pcycles = HREAD4(sc, PWM_PR); 148 if (pcycles >= PWM_PR_MAX) 149 pcycles = PWM_PR_MAX; 150 pcycles = (pcycles + 2) * NS_PER_S; 151 pcycles = (pcycles + (pwmclk / 2)) / pwmclk; 152 153 dcycles = sc->sc_dcycles; 154 if (enabled) 155 dcycles = HREAD4(sc, PWM_SAR); 156 dcycles = dcycles * NS_PER_S; 157 dcycles = (dcycles + (pwmclk / 2)) / pwmclk; 158 159 memset(ps, 0, sizeof(struct pwm_state)); 160 ps->ps_period = pcycles; 161 ps->ps_pulse_width = dcycles; 162 ps->ps_enabled = enabled; 163 return 0; 164} 165 166int 167imxpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 168{ 169 struct imxpwm_softc *sc = cookie; 170 uint64_t dcycles, pcycles, prescale; 171 int i; 172 173 if (ps->ps_enabled) { 174 pcycles = sc->sc_clkin; 175 pcycles = (pcycles * ps->ps_period) / NS_PER_S; 176 prescale = pcycles / 0x10000 + 1; 177 178 if (ps->ps_period == 0 || prescale == 0) 179 return EINVAL; 180 181 pcycles = pcycles / prescale; 182 dcycles = (pcycles * ps->ps_pulse_width) / ps->ps_period; 183 184 if (pcycles > 2) 185 pcycles -= 2; 186 else 187 pcycles = 0; 188 } 189 190 /* disable and flush fifo */ 191 HCLR4(sc, PWM_CR, PWM_CR_EN); 192 HWRITE4(sc, PWM_CR, PWM_CR_SWR); 193 for (i = 0; i < 5; i++) { 194 delay(1000); 195 if ((HREAD4(sc, PWM_CR) & PWM_CR_SWR) == 0) 196 break; 197 } 198 if (i == 5) { 199 printf("%s: reset timeout\n", sc->sc_dev.dv_xname); 200 return ETIMEDOUT; 201 } 202 203 if (ps->ps_enabled) { 204 HWRITE4(sc, PWM_SAR, dcycles); 205 HWRITE4(sc, PWM_PR, pcycles); 206 207 sc->sc_dcycles = dcycles; 208 209 HWRITE4(sc, PWM_CR, PWM_CR_PRESCALER(prescale) | 210 PWM_CR_DOZEEN | PWM_CR_WAITEN | 211 PWM_CR_DBGEN | PWM_CR_CLKSRC_IPG_HIGH | 212 PWM_CR_EN); 213 } 214 215 return 0; 216} 217