1/* $OpenBSD: sxipwm.c,v 1.2 2021/10/24 17:52:27 mpi Exp $ */ 2/* 3 * Copyright (c) 2019 Krystian Lewandowski 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/device.h> 21#include <sys/malloc.h> 22 23#include <machine/fdt.h> 24#include <machine/bus.h> 25 26#include <dev/ofw/openfirm.h> 27#include <dev/ofw/ofw_clock.h> 28#include <dev/ofw/ofw_misc.h> 29#include <dev/ofw/ofw_pinctrl.h> 30#include <dev/ofw/fdt.h> 31 32#define PWM_CTRL_REG 0x0 33#define PWM0_RDY (1 << 28) 34#define SCLK_CH0_GATING (1 << 6) 35#define PWM_CH0_ACT_STA (1 << 5) 36#define PWM_CH0_EN (1 << 4) 37#define PWM_CH0_PRESCAL 0xf 38#define PWM_CH0_PERIOD 0x4 39#define PWM_CH0_CYCLES_SHIFT 16 40#define PWM_CH0_ACT_CYCLES_SHIFT 0 41#define PWM_CH0_CYCLES_MAX 0xffff 42 43#define NS_PER_S 1000000000 44 45#define HREAD4(sc, reg) \ 46 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) 47#define HWRITE4(sc, reg, val) \ 48 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 49 50struct sxipwm_prescaler { 51 uint32_t divider; 52 uint8_t value; 53}; 54 55const struct sxipwm_prescaler sxipwm_prescalers[] = { 56 { 1, 0xf }, 57 { 120, 0x0 }, 58 { 180, 0x1 }, 59 { 240, 0x2 }, 60 { 360, 0x3 }, 61 { 480, 0x4 }, 62 { 12000, 0x8 }, 63 { 24000, 0x9 }, 64 { 36000, 0xa }, 65 { 48000, 0xb }, 66 { 72000, 0xc }, 67 { 0 } 68}; 69 70struct sxipwm_softc { 71 struct device sc_dev; 72 bus_space_tag_t sc_iot; 73 bus_space_handle_t sc_ioh; 74 75 uint32_t sc_clkin; 76 struct pwm_device sc_pd; 77}; 78 79int sxipwm_match(struct device *, void *, void *); 80void sxipwm_attach(struct device *, struct device *, void *); 81 82const struct cfattach sxipwm_ca = { 83 sizeof(struct sxipwm_softc), sxipwm_match, sxipwm_attach 84}; 85 86struct cfdriver sxipwm_cd = { 87 NULL, "sxipwm", DV_DULL 88}; 89 90int sxipwm_get_state(void *, uint32_t *, struct pwm_state *); 91int sxipwm_set_state(void *, uint32_t *, struct pwm_state *); 92 93int 94sxipwm_match(struct device *parent, void *match, void *aux) 95{ 96 struct fdt_attach_args *faa = aux; 97 98 return OF_is_compatible(faa->fa_node, "allwinner,sun5i-a13-pwm"); 99} 100 101void 102sxipwm_attach(struct device *parent, struct device *self, void *aux) 103{ 104 struct sxipwm_softc *sc = (struct sxipwm_softc *)self; 105 struct fdt_attach_args *faa = aux; 106 107 if (faa->fa_nreg < 1) { 108 printf(": no registers\n"); 109 return; 110 } 111 112 sc->sc_clkin = clock_get_frequency_idx(faa->fa_node, 0); 113 if (sc->sc_clkin == 0) { 114 printf(": no clock\n"); 115 return; 116 } 117 118 sc->sc_iot = faa->fa_iot; 119 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 120 faa->fa_reg[0].size, 0, &sc->sc_ioh)) { 121 printf(": can't map registers\n"); 122 return; 123 } 124 125 printf("\n"); 126 127 pinctrl_byname(faa->fa_node, "default"); 128 129 clock_enable_all(faa->fa_node); 130 reset_deassert_all(faa->fa_node); 131 132 sc->sc_pd.pd_node = faa->fa_node; 133 sc->sc_pd.pd_cookie = sc; 134 sc->sc_pd.pd_get_state = sxipwm_get_state; 135 sc->sc_pd.pd_set_state = sxipwm_set_state; 136 137 pwm_register(&sc->sc_pd); 138} 139 140int 141sxipwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 142{ 143 struct sxipwm_softc *sc = cookie; 144 uint32_t idx = cells[0]; 145 uint32_t ctrl, ch_period; 146 uint64_t rate, cycles, act_cycles; 147 int i, prescaler; 148 149 if (idx != 0) 150 return EINVAL; 151 152 ctrl = HREAD4(sc, PWM_CTRL_REG); 153 ch_period = HREAD4(sc, PWM_CH0_PERIOD); 154 155 prescaler = -1; 156 for (i = 0; sxipwm_prescalers[i].divider; i++) { 157 if ((ctrl & PWM_CH0_PRESCAL) == sxipwm_prescalers[i].value) { 158 prescaler = i; 159 break; 160 } 161 } 162 if (prescaler < 0) 163 return EINVAL; 164 165 rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider; 166 cycles = ((ch_period >> PWM_CH0_CYCLES_SHIFT) & 167 PWM_CH0_CYCLES_MAX) + 1; 168 act_cycles = (ch_period >> PWM_CH0_ACT_CYCLES_SHIFT) & 169 PWM_CH0_CYCLES_MAX; 170 171 memset(ps, 0, sizeof(struct pwm_state)); 172 ps->ps_period = (NS_PER_S * cycles) / rate; 173 ps->ps_pulse_width = (NS_PER_S * act_cycles) / rate; 174 if ((ctrl & PWM_CH0_EN) && (ctrl & SCLK_CH0_GATING)) 175 ps->ps_enabled = 1; 176 177 return 0; 178} 179 180int 181sxipwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 182{ 183 struct sxipwm_softc *sc = cookie; 184 uint32_t idx = cells[0]; 185 uint64_t rate, cycles, act_cycles; 186 uint32_t reg; 187 int i, prescaler; 188 189 if (idx != 0) 190 return EINVAL; 191 192 prescaler = -1; 193 for (i = 0; sxipwm_prescalers[i].divider; i++) { 194 rate = sc->sc_clkin / sxipwm_prescalers[i].divider; 195 cycles = (rate * ps->ps_period) / NS_PER_S; 196 if ((cycles - 1) < PWM_CH0_CYCLES_MAX) { 197 prescaler = i; 198 break; 199 } 200 } 201 if (prescaler < 0) 202 return EINVAL; 203 204 rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider; 205 cycles = (rate * ps->ps_period) / NS_PER_S; 206 act_cycles = (rate * ps->ps_pulse_width) / NS_PER_S; 207 if (cycles < 1 || act_cycles > cycles) 208 return EINVAL; 209 210 KASSERT(cycles - 1 <= PWM_CH0_CYCLES_MAX); 211 KASSERT(act_cycles <= PWM_CH0_CYCLES_MAX); 212 213 reg = HREAD4(sc, PWM_CTRL_REG); 214 if (reg & PWM0_RDY) 215 return EBUSY; 216 if (ps->ps_enabled) 217 reg |= (PWM_CH0_EN | SCLK_CH0_GATING); 218 else 219 reg &= ~(PWM_CH0_EN | SCLK_CH0_GATING); 220 if (ps->ps_flags & PWM_POLARITY_INVERTED) 221 reg &= ~PWM_CH0_ACT_STA; 222 else 223 reg |= PWM_CH0_ACT_STA; 224 reg &= ~PWM_CH0_PRESCAL; 225 reg |= sxipwm_prescalers[prescaler].value; 226 HWRITE4(sc, PWM_CTRL_REG, reg); 227 228 reg = ((cycles - 1) << PWM_CH0_CYCLES_SHIFT) | 229 (act_cycles << PWM_CH0_ACT_CYCLES_SHIFT); 230 HWRITE4(sc, PWM_CH0_PERIOD, reg); 231 232 return 0; 233} 234