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
qcpwm_match(struct device * parent,void * match,void * aux)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
qcpwm_attach(struct device * parent,struct device * self,void * aux)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
qcpwm_get_state(void * cookie,uint32_t * cells,struct pwm_state * ps)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
qcpwm_set_state(void * cookie,uint32_t * cells,struct pwm_state * ps)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
qcpwm_read(struct qcpwm_softc * sc,uint16_t reg)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
qcpwm_write(struct qcpwm_softc * sc,uint16_t reg,uint8_t val)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