/* $OpenBSD: qcpwm.c,v 1.2 2022/11/30 09:52:13 patrick Exp $ */ /* * Copyright (c) 2022 Patrick Wildt * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define PWM_CHAN_OFF(x) (0x100 * (x)) #define PWM_SUBTYPE 0x05 #define PWM_SUBTYPE_LPG 0x2 #define PWM_SUBTYPE_PWM 0xb #define PWM_SUBTYPE_LPG_LITE 0x11 #define PWM_SIZE_CLK 0x41 #define PWM_SIZE_CLK_SELECT_SHIFT 0 #define PWM_SIZE_CLK_SELECT_MASK 0x3 #define PWM_SIZE_CLK_LPG_9BIT (3 << 4) #define PWM_SIZE_CLK_PWM_9BIT (1 << 2) #define PWM_SIZE_CLK_LPG_LITE_9BIT (1 << 4) #define PWM_PREDIV_CLK 0x42 #define PWM_PREDIV_CLK_EXP_SHIFT 0 #define PWM_PREDIV_CLK_EXP_MASK 0x7 #define PWM_PREDIV_CLK_PREDIV_SHIFT 5 #define PWM_PREDIV_CLK_PREDIV_MASK 0x3 #define PWM_TYPE_CONFIG 0x43 #define PWM_TYPE_CONFIG_GLITCH_REMOVAL (1 << 5) #define PWM_VALUE 0x44 #define PWM_ENABLE_CONTROL 0x46 #define PWM_ENABLE_CONTROL_OUTPUT (1U << 7) #define PWM_ENABLE_CONTROL_BUFFER_TRISTATE (1 << 5) #define PWM_ENABLE_CONTROL_SRC_PWM (1 << 2) #define PWM_ENABLE_CONTROL_RAMP_GEN (1 << 1) #define PWM_SYNC 0x47 #define PWM_SYNC_PWM (1 << 0) #define PWM_RESOLUTION 512 #define NS_PER_S 1000000000LLU uint64_t qcpwm_clk_rates[] = { 0, 1024, 32768, 19200000 }; uint64_t qcpwm_pre_divs[] = { 1, 3, 5, 6 }; struct qcpwm_softc { struct device sc_dev; int sc_node; spmi_tag_t sc_tag; int8_t sc_sid; uint16_t sc_addr; int sc_nchannel; struct pwm_device sc_pd; }; int qcpwm_match(struct device *, void *, void *); void qcpwm_attach(struct device *, struct device *, void *); const struct cfattach qcpwm_ca = { sizeof(struct qcpwm_softc), qcpwm_match, qcpwm_attach }; struct cfdriver qcpwm_cd = { NULL, "qcpwm", DV_DULL }; uint8_t qcpwm_read(struct qcpwm_softc *, uint16_t); void qcpwm_write(struct qcpwm_softc *, uint16_t, uint8_t); int qcpwm_get_state(void *, uint32_t *, struct pwm_state *); int qcpwm_set_state(void *, uint32_t *, struct pwm_state *); int qcpwm_match(struct device *parent, void *match, void *aux) { struct spmi_attach_args *saa = aux; return OF_is_compatible(saa->sa_node, "qcom,pm8350c-pwm"); } void qcpwm_attach(struct device *parent, struct device *self, void *aux) { struct qcpwm_softc *sc = (struct qcpwm_softc *)self; struct spmi_attach_args *saa = aux; sc->sc_addr = OF_getpropint(saa->sa_node, "reg", 0xe800); sc->sc_node = saa->sa_node; sc->sc_tag = saa->sa_tag; sc->sc_sid = saa->sa_sid; sc->sc_nchannel = 4; printf("\n"); pinctrl_byname(saa->sa_node, "default"); clock_enable_all(saa->sa_node); reset_deassert_all(saa->sa_node); sc->sc_pd.pd_node = saa->sa_node; sc->sc_pd.pd_cookie = sc; sc->sc_pd.pd_get_state = qcpwm_get_state; sc->sc_pd.pd_set_state = qcpwm_set_state; pwm_register(&sc->sc_pd); } int qcpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) { struct qcpwm_softc *sc = cookie; int chan = cells[0]; uint64_t refclk, prediv, exp; uint64_t pcycles, dcycles; uint16_t pwmval; uint8_t reg; if (chan >= sc->sc_nchannel) return ENXIO; memset(ps, 0, sizeof(struct pwm_state)); ps->ps_enabled = !!(qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_ENABLE_CONTROL) & PWM_ENABLE_CONTROL_OUTPUT); reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_SIZE_CLK); refclk = (reg >> PWM_SIZE_CLK_SELECT_SHIFT) & PWM_SIZE_CLK_SELECT_MASK; refclk = qcpwm_clk_rates[refclk]; if (refclk == 0) return 0; reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_PREDIV_CLK); exp = (reg >> PWM_PREDIV_CLK_EXP_SHIFT) & PWM_PREDIV_CLK_EXP_MASK; prediv = (reg >> PWM_PREDIV_CLK_PREDIV_SHIFT) & PWM_PREDIV_CLK_PREDIV_MASK; prediv = qcpwm_pre_divs[prediv]; spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, &pwmval, sizeof(pwmval)); pcycles = (NS_PER_S * PWM_RESOLUTION * prediv * (1 << exp)); pcycles = (pcycles + refclk - 1) / refclk; dcycles = (NS_PER_S * pwmval * prediv * (1 << exp)); dcycles = (dcycles + refclk - 1) / refclk; ps->ps_period = pcycles; ps->ps_pulse_width = dcycles; return 0; } int qcpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) { struct qcpwm_softc *sc = cookie; uint64_t dcycles, pcycles, acycles, diff = UINT64_MAX; int chan = cells[0]; int clksel, divsel, exp; uint16_t pwmval; uint8_t reg; int i, j, m; if (chan >= sc->sc_nchannel) return ENXIO; pcycles = ps->ps_period; dcycles = ps->ps_pulse_width; for (i = 1; i < nitems(qcpwm_clk_rates); i++) { for (j = 0; j < nitems(qcpwm_pre_divs); j++) { if (pcycles * qcpwm_clk_rates[i] < NS_PER_S * qcpwm_pre_divs[j] * PWM_RESOLUTION) continue; m = flsl((pcycles * qcpwm_clk_rates[i]) / (NS_PER_S * qcpwm_pre_divs[j] * PWM_RESOLUTION)) - 1; if (m > PWM_PREDIV_CLK_EXP_MASK) m = PWM_PREDIV_CLK_EXP_MASK; acycles = (NS_PER_S * qcpwm_pre_divs[j] * PWM_RESOLUTION * (1 << m)) / qcpwm_clk_rates[i]; if (pcycles - acycles < diff) { diff = pcycles - acycles; ps->ps_period = acycles; clksel = i; divsel = j; exp = m; } } } ps->ps_pulse_width = (dcycles * ps->ps_period) / pcycles; if (ps->ps_pulse_width > ps->ps_period) ps->ps_pulse_width = ps->ps_period; pcycles = ps->ps_period; dcycles = ps->ps_pulse_width; pwmval = ((uint64_t)ps->ps_pulse_width * qcpwm_clk_rates[clksel]) / (NS_PER_S * qcpwm_pre_divs[divsel] * (1 << exp)); reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG); reg |= PWM_TYPE_CONFIG_GLITCH_REMOVAL; qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG, reg); reg = clksel << PWM_SIZE_CLK_SELECT_SHIFT; switch (qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_SUBTYPE)) { case PWM_SUBTYPE_LPG: reg |= PWM_SIZE_CLK_LPG_9BIT; break; case PWM_SUBTYPE_PWM: reg |= PWM_SIZE_CLK_PWM_9BIT; break; case PWM_SUBTYPE_LPG_LITE: default: reg |= PWM_SIZE_CLK_LPG_LITE_9BIT; break; } qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_SIZE_CLK, reg); qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_PREDIV_CLK, divsel << PWM_PREDIV_CLK_PREDIV_SHIFT | exp << PWM_PREDIV_CLK_EXP_SHIFT); if (ps->ps_enabled) { spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, &pwmval, sizeof(pwmval)); } reg = PWM_ENABLE_CONTROL_BUFFER_TRISTATE; if (ps->ps_enabled) reg |= PWM_ENABLE_CONTROL_OUTPUT; reg |= PWM_ENABLE_CONTROL_SRC_PWM; qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_ENABLE_CONTROL, reg); /* HW quirk: rewrite after enable */ if (ps->ps_enabled) { spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, sc->sc_addr + PWM_CHAN_OFF(chan) + PWM_VALUE, &pwmval, sizeof(pwmval)); } qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_SYNC, PWM_SYNC_PWM); reg = qcpwm_read(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG); reg &= ~PWM_TYPE_CONFIG_GLITCH_REMOVAL; qcpwm_write(sc, PWM_CHAN_OFF(chan) + PWM_TYPE_CONFIG, reg); return 0; } uint8_t qcpwm_read(struct qcpwm_softc *sc, uint16_t reg) { uint8_t val = 0; int error; error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, sc->sc_addr + reg, &val, sizeof(val)); if (error) printf("%s: error reading\n", sc->sc_dev.dv_xname); return val; } void qcpwm_write(struct qcpwm_softc *sc, uint16_t reg, uint8_t val) { int error; error = spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, sc->sc_addr + reg, &val, sizeof(val)); if (error) printf("%s: error writing\n", sc->sc_dev.dv_xname); }