1 /* $OpenBSD: qcpwm.c,v 1.2 2022/11/30 09:52:13 patrick Exp $ */ 2 /* 3 * Copyright (c) 2022 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/fdt/spmivar.h> 28 29 #include <dev/ofw/openfirm.h> 30 #include <dev/ofw/fdt.h> 31 #include <dev/ofw/ofw_clock.h> 32 #include <dev/ofw/ofw_pinctrl.h> 33 #include <dev/ofw/ofw_misc.h> 34 35 #define PWM_CHAN_OFF(x) (0x100 * (x)) 36 #define PWM_SUBTYPE 0x05 37 #define PWM_SUBTYPE_LPG 0x2 38 #define PWM_SUBTYPE_PWM 0xb 39 #define PWM_SUBTYPE_LPG_LITE 0x11 40 #define PWM_SIZE_CLK 0x41 41 #define PWM_SIZE_CLK_SELECT_SHIFT 0 42 #define PWM_SIZE_CLK_SELECT_MASK 0x3 43 #define PWM_SIZE_CLK_LPG_9BIT (3 << 4) 44 #define PWM_SIZE_CLK_PWM_9BIT (1 << 2) 45 #define PWM_SIZE_CLK_LPG_LITE_9BIT (1 << 4) 46 #define PWM_PREDIV_CLK 0x42 47 #define PWM_PREDIV_CLK_EXP_SHIFT 0 48 #define PWM_PREDIV_CLK_EXP_MASK 0x7 49 #define PWM_PREDIV_CLK_PREDIV_SHIFT 5 50 #define PWM_PREDIV_CLK_PREDIV_MASK 0x3 51 #define PWM_TYPE_CONFIG 0x43 52 #define PWM_TYPE_CONFIG_GLITCH_REMOVAL (1 << 5) 53 #define PWM_VALUE 0x44 54 #define PWM_ENABLE_CONTROL 0x46 55 #define PWM_ENABLE_CONTROL_OUTPUT (1U << 7) 56 #define PWM_ENABLE_CONTROL_BUFFER_TRISTATE (1 << 5) 57 #define PWM_ENABLE_CONTROL_SRC_PWM (1 << 2) 58 #define PWM_ENABLE_CONTROL_RAMP_GEN (1 << 1) 59 #define PWM_SYNC 0x47 60 #define PWM_SYNC_PWM (1 << 0) 61 62 #define PWM_RESOLUTION 512 63 64 #define NS_PER_S 1000000000LLU 65 66 uint64_t qcpwm_clk_rates[] = { 0, 1024, 32768, 19200000 }; 67 uint64_t qcpwm_pre_divs[] = { 1, 3, 5, 6 }; 68 69 struct qcpwm_softc { 70 struct device sc_dev; 71 int sc_node; 72 73 spmi_tag_t sc_tag; 74 int8_t sc_sid; 75 uint16_t sc_addr; 76 77 int sc_nchannel; 78 79 struct pwm_device sc_pd; 80 }; 81 82 int qcpwm_match(struct device *, void *, void *); 83 void qcpwm_attach(struct device *, struct device *, void *); 84 85 const struct cfattach qcpwm_ca = { 86 sizeof(struct qcpwm_softc), qcpwm_match, qcpwm_attach 87 }; 88 89 struct cfdriver qcpwm_cd = { 90 NULL, "qcpwm", DV_DULL 91 }; 92 93 uint8_t qcpwm_read(struct qcpwm_softc *, uint16_t); 94 void qcpwm_write(struct qcpwm_softc *, uint16_t, uint8_t); 95 96 int qcpwm_get_state(void *, uint32_t *, struct pwm_state *); 97 int qcpwm_set_state(void *, uint32_t *, struct pwm_state *); 98 99 int 100 qcpwm_match(struct device *parent, void *match, void *aux) 101 { 102 struct spmi_attach_args *saa = aux; 103 104 return OF_is_compatible(saa->sa_node, "qcom,pm8350c-pwm"); 105 } 106 107 void 108 qcpwm_attach(struct device *parent, struct device *self, void *aux) 109 { 110 struct qcpwm_softc *sc = (struct qcpwm_softc *)self; 111 struct spmi_attach_args *saa = aux; 112 113 sc->sc_addr = OF_getpropint(saa->sa_node, "reg", 0xe800); 114 sc->sc_node = saa->sa_node; 115 sc->sc_tag = saa->sa_tag; 116 sc->sc_sid = saa->sa_sid; 117 118 sc->sc_nchannel = 4; 119 120 printf("\n"); 121 122 pinctrl_byname(saa->sa_node, "default"); 123 124 clock_enable_all(saa->sa_node); 125 reset_deassert_all(saa->sa_node); 126 127 sc->sc_pd.pd_node = saa->sa_node; 128 sc->sc_pd.pd_cookie = sc; 129 sc->sc_pd.pd_get_state = qcpwm_get_state; 130 sc->sc_pd.pd_set_state = qcpwm_set_state; 131 132 pwm_register(&sc->sc_pd); 133 } 134 135 int 136 qcpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 137 { 138 struct qcpwm_softc *sc = cookie; 139 int chan = cells[0]; 140 uint64_t refclk, prediv, exp; 141 uint64_t pcycles, dcycles; 142 uint16_t pwmval; 143 uint8_t reg; 144 145 if (chan >= sc->sc_nchannel) 146 return ENXIO; 147 148 memset(ps, 0, sizeof(struct pwm_state)); 149 150 ps->ps_enabled = !!(qcpwm_read(sc, PWM_CHAN_OFF(chan) + 151 PWM_ENABLE_CONTROL) & PWM_ENABLE_CONTROL_OUTPUT); 152 153 reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_SIZE_CLK); 154 refclk = (reg >> PWM_SIZE_CLK_SELECT_SHIFT) & 155 PWM_SIZE_CLK_SELECT_MASK; 156 refclk = qcpwm_clk_rates[refclk]; 157 if (refclk == 0) 158 return 0; 159 160 reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_PREDIV_CLK); 161 exp = (reg >> PWM_PREDIV_CLK_EXP_SHIFT) & 162 PWM_PREDIV_CLK_EXP_MASK; 163 prediv = (reg >> PWM_PREDIV_CLK_PREDIV_SHIFT) & 164 PWM_PREDIV_CLK_PREDIV_MASK; 165 prediv = qcpwm_pre_divs[prediv]; 166 167 spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 168 sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, 169 &pwmval, sizeof(pwmval)); 170 171 pcycles = (NS_PER_S * PWM_RESOLUTION * prediv * (1 << exp)); 172 pcycles = (pcycles + refclk - 1) / refclk; 173 dcycles = (NS_PER_S * pwmval * prediv * (1 << exp)); 174 dcycles = (dcycles + refclk - 1) / refclk; 175 176 ps->ps_period = pcycles; 177 ps->ps_pulse_width = dcycles; 178 return 0; 179 } 180 181 int 182 qcpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) 183 { 184 struct qcpwm_softc *sc = cookie; 185 uint64_t dcycles, pcycles, acycles, diff = UINT64_MAX; 186 int chan = cells[0]; 187 int clksel, divsel, exp; 188 uint16_t pwmval; 189 uint8_t reg; 190 int i, j, m; 191 192 if (chan >= sc->sc_nchannel) 193 return ENXIO; 194 195 pcycles = ps->ps_period; 196 dcycles = ps->ps_pulse_width; 197 198 for (i = 1; i < nitems(qcpwm_clk_rates); i++) { 199 for (j = 0; j < nitems(qcpwm_pre_divs); j++) { 200 if (pcycles * qcpwm_clk_rates[i] < 201 NS_PER_S * qcpwm_pre_divs[j] * PWM_RESOLUTION) 202 continue; 203 204 m = flsl((pcycles * qcpwm_clk_rates[i]) / 205 (NS_PER_S * qcpwm_pre_divs[j] * PWM_RESOLUTION)) 206 - 1; 207 if (m > PWM_PREDIV_CLK_EXP_MASK) 208 m = PWM_PREDIV_CLK_EXP_MASK; 209 210 acycles = (NS_PER_S * qcpwm_pre_divs[j] * 211 PWM_RESOLUTION * (1 << m)) / 212 qcpwm_clk_rates[i]; 213 if (pcycles - acycles < diff) { 214 diff = pcycles - acycles; 215 ps->ps_period = acycles; 216 clksel = i; 217 divsel = j; 218 exp = m; 219 } 220 } 221 } 222 223 ps->ps_pulse_width = (dcycles * ps->ps_period) / pcycles; 224 if (ps->ps_pulse_width > ps->ps_period) 225 ps->ps_pulse_width = ps->ps_period; 226 227 pcycles = ps->ps_period; 228 dcycles = ps->ps_pulse_width; 229 pwmval = ((uint64_t)ps->ps_pulse_width * qcpwm_clk_rates[clksel]) / 230 (NS_PER_S * qcpwm_pre_divs[divsel] * (1 << exp)); 231 232 reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG); 233 reg |= PWM_TYPE_CONFIG_GLITCH_REMOVAL; 234 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG, reg); 235 236 reg = clksel << PWM_SIZE_CLK_SELECT_SHIFT; 237 switch (qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_SUBTYPE)) { 238 case PWM_SUBTYPE_LPG: 239 reg |= PWM_SIZE_CLK_LPG_9BIT; 240 break; 241 case PWM_SUBTYPE_PWM: 242 reg |= PWM_SIZE_CLK_PWM_9BIT; 243 break; 244 case PWM_SUBTYPE_LPG_LITE: 245 default: 246 reg |= PWM_SIZE_CLK_LPG_LITE_9BIT; 247 break; 248 } 249 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_SIZE_CLK, reg); 250 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_PREDIV_CLK, 251 divsel << PWM_PREDIV_CLK_PREDIV_SHIFT | 252 exp << PWM_PREDIV_CLK_EXP_SHIFT); 253 254 if (ps->ps_enabled) { 255 spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 256 sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, 257 &pwmval, sizeof(pwmval)); 258 } 259 260 reg = PWM_ENABLE_CONTROL_BUFFER_TRISTATE; 261 if (ps->ps_enabled) 262 reg |= PWM_ENABLE_CONTROL_OUTPUT; 263 reg |= PWM_ENABLE_CONTROL_SRC_PWM; 264 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_ENABLE_CONTROL, reg); 265 266 /* HW quirk: rewrite after enable */ 267 if (ps->ps_enabled) { 268 spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 269 sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, 270 &pwmval, sizeof(pwmval)); 271 } 272 273 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_SYNC, PWM_SYNC_PWM); 274 275 reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG); 276 reg &= ~PWM_TYPE_CONFIG_GLITCH_REMOVAL; 277 qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG, reg); 278 279 return 0; 280 } 281 282 uint8_t 283 qcpwm_read(struct qcpwm_softc *sc, uint16_t reg) 284 { 285 uint8_t val = 0; 286 int error; 287 288 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 289 sc->sc_addr + reg, &val, sizeof(val)); 290 if (error) 291 printf("%s: error reading\n", sc->sc_dev.dv_xname); 292 293 return val; 294 } 295 296 void 297 qcpwm_write(struct qcpwm_softc *sc, uint16_t reg, uint8_t val) 298 { 299 int error; 300 301 error = spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 302 sc->sc_addr + reg, &val, sizeof(val)); 303 if (error) 304 printf("%s: error writing\n", sc->sc_dev.dv_xname); 305 } 306