1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice unmodified, this list of conditions, and the following
11 * disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 /*
30 * This is the shared pinmux code that the qualcomm SoCs use for their
31 * specific way of configuring up pins.
32 *
33 * For now this does use the IPQ4018 TLMM related softc, but that
34 * may change as I extend the driver to support multiple kinds of
35 * qualcomm chipsets in the future.
36 */
37
38 #include <sys/param.h>
39 #include <sys/systm.h>
40 #include <sys/bus.h>
41
42 #include <sys/kernel.h>
43 #include <sys/module.h>
44 #include <sys/rman.h>
45 #include <sys/lock.h>
46 #include <sys/malloc.h>
47 #include <sys/mutex.h>
48 #include <sys/gpio.h>
49
50 #include <machine/bus.h>
51 #include <machine/resource.h>
52 #include <dev/gpio/gpiobusvar.h>
53
54 #include <dev/fdt/fdt_common.h>
55 #include <dev/ofw/ofw_bus.h>
56 #include <dev/ofw/ofw_bus_subr.h>
57
58 #include <dev/fdt/fdt_pinctrl.h>
59
60 #include "qcom_tlmm_var.h"
61 #include "qcom_tlmm_debug.h"
62
63 /*
64 * For now we're hard-coded to doing IPQ4018 stuff here, but
65 * it's not going to be very hard to flip it to being generic.
66 */
67 #include "qcom_tlmm_ipq4018_hw.h"
68
69 #include "gpio_if.h"
70
71 /* Parameters */
72 static const struct qcom_tlmm_prop_name prop_names[] = {
73 { "bias-disable", PIN_ID_BIAS_DISABLE, 0 },
74 { "bias-high-impedance", PIN_ID_BIAS_HIGH_IMPEDANCE, 0 },
75 { "bias-bus-hold", PIN_ID_BIAS_BUS_HOLD, 0 },
76 { "bias-pull-up", PIN_ID_BIAS_PULL_UP, 0 },
77 { "bias-pull-down", PIN_ID_BIAS_PULL_DOWN, 0 },
78 { "bias-pull-pin-default", PIN_ID_BIAS_PULL_PIN_DEFAULT, 0 },
79 { "drive-push-pull", PIN_ID_DRIVE_PUSH_PULL, 0 },
80 { "drive-open-drain", PIN_ID_DRIVE_OPEN_DRAIN, 0 },
81 { "drive-open-source", PIN_ID_DRIVE_OPEN_SOURCE, 0 },
82 { "drive-strength", PIN_ID_DRIVE_STRENGTH, 1 },
83 { "input-enable", PIN_ID_INPUT_ENABLE, 0 },
84 { "input-disable", PIN_ID_INPUT_DISABLE, 0 },
85 { "input-schmitt-enable", PIN_ID_INPUT_SCHMITT_ENABLE, 0 },
86 { "input-schmitt-disable", PIN_ID_INPUT_SCHMITT_DISABLE, 0 },
87 { "input-debounce", PIN_ID_INPUT_DEBOUNCE, 0 },
88 { "power-source", PIN_ID_POWER_SOURCE, 0 },
89 { "slew-rate", PIN_ID_SLEW_RATE, 0},
90 { "low-power-enable", PIN_ID_LOW_POWER_MODE_ENABLE, 0 },
91 { "low-power-disable", PIN_ID_LOW_POWER_MODE_DISABLE, 0 },
92 { "output-low", PIN_ID_OUTPUT_LOW, 0, },
93 { "output-high", PIN_ID_OUTPUT_HIGH, 0, },
94 { "vm-enable", PIN_ID_VM_ENABLE, 0, },
95 { "vm-disable", PIN_ID_VM_DISABLE, 0, },
96 };
97
98 static const struct qcom_tlmm_spec_pin *
qcom_tlmm_pinctrl_search_spin(struct qcom_tlmm_softc * sc,char * pin_name)99 qcom_tlmm_pinctrl_search_spin(struct qcom_tlmm_softc *sc, char *pin_name)
100 {
101 int i;
102
103 if (sc->spec_pins == NULL)
104 return (NULL);
105
106 for (i = 0; sc->spec_pins[i].name != NULL; i++) {
107 if (strcmp(pin_name, sc->spec_pins[i].name) == 0)
108 return (&sc->spec_pins[i]);
109 }
110
111 return (NULL);
112 }
113
114 static int
qcom_tlmm_pinctrl_config_spin(struct qcom_tlmm_softc * sc,char * pin_name,const struct qcom_tlmm_spec_pin * spin,struct qcom_tlmm_pinctrl_cfg * cfg)115 qcom_tlmm_pinctrl_config_spin(struct qcom_tlmm_softc *sc,
116 char *pin_name, const struct qcom_tlmm_spec_pin *spin,
117 struct qcom_tlmm_pinctrl_cfg *cfg)
118 {
119 /* XXX TODO */
120 device_printf(sc->dev, "%s: TODO: called; pin_name=%s\n",
121 __func__, pin_name);
122 return (0);
123 }
124
125 static const struct qcom_tlmm_gpio_mux *
qcom_tlmm_pinctrl_search_gmux(struct qcom_tlmm_softc * sc,char * pin_name)126 qcom_tlmm_pinctrl_search_gmux(struct qcom_tlmm_softc *sc, char *pin_name)
127 {
128 int i;
129
130 if (sc->gpio_muxes == NULL)
131 return (NULL);
132
133 for (i = 0; sc->gpio_muxes[i].id >= 0; i++) {
134 if (strcmp(pin_name, sc->gpio_muxes[i].name) == 0)
135 return (&sc->gpio_muxes[i]);
136 }
137
138 return (NULL);
139 }
140
141 static int
qcom_tlmm_pinctrl_gmux_function(const struct qcom_tlmm_gpio_mux * gmux,char * fnc_name)142 qcom_tlmm_pinctrl_gmux_function(const struct qcom_tlmm_gpio_mux *gmux,
143 char *fnc_name)
144 {
145 int i;
146
147 for (i = 0; i < 16; i++) { /* XXX size */
148 if ((gmux->functions[i] != NULL) &&
149 (strcmp(fnc_name, gmux->functions[i]) == 0))
150 return (i);
151 }
152
153 return (-1);
154 }
155
156 static int
qcom_tlmm_pinctrl_read_node(struct qcom_tlmm_softc * sc,phandle_t node,struct qcom_tlmm_pinctrl_cfg * cfg,char ** pins,int * lpins)157 qcom_tlmm_pinctrl_read_node(struct qcom_tlmm_softc *sc,
158 phandle_t node, struct qcom_tlmm_pinctrl_cfg *cfg, char **pins,
159 int *lpins)
160 {
161 int rv, i;
162
163 *lpins = OF_getprop_alloc(node, "pins", (void **)pins);
164 if (*lpins <= 0)
165 return (ENOENT);
166
167 /* Read function (mux) settings. */
168 rv = OF_getprop_alloc(node, "function", (void **)&cfg->function);
169 if (rv <= 0)
170 cfg->function = NULL;
171
172 /*
173 * Read the rest of the properties.
174 *
175 * Properties that are a flag are simply present with a value of 0.
176 * Properties that have arguments have have_value set to 1, and
177 * we will parse an argument out for it to use.
178 *
179 * Properties that were not found/parsed with have a value of -1
180 * and thus we won't program them into the hardware.
181 */
182 for (i = 0; i < PROP_ID_MAX_ID; i++) {
183 rv = OF_getencprop(node, prop_names[i].name, &cfg->params[i],
184 sizeof(cfg->params[i]));
185 if (prop_names[i].have_value) {
186 if (rv == 0) {
187 device_printf(sc->dev,
188 "WARNING: Missing value for propety"
189 " \"%s\"\n",
190 prop_names[i].name);
191 cfg->params[i] = 0;
192 }
193 } else {
194 /* No value, default to 0 */
195 cfg->params[i] = 0;
196 }
197 if (rv < 0)
198 cfg->params[i] = -1;
199 }
200 return (0);
201 }
202
203 static int
qcom_tlmm_pinctrl_config_gmux(struct qcom_tlmm_softc * sc,char * pin_name,const struct qcom_tlmm_gpio_mux * gmux,struct qcom_tlmm_pinctrl_cfg * cfg)204 qcom_tlmm_pinctrl_config_gmux(struct qcom_tlmm_softc *sc, char *pin_name,
205 const struct qcom_tlmm_gpio_mux *gmux, struct qcom_tlmm_pinctrl_cfg *cfg)
206 {
207 int err = 0, i;
208
209 QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
210 "%s: called; pin=%s, function %s\n",
211 __func__, pin_name, cfg->function);
212
213 GPIO_LOCK(sc);
214
215 /*
216 * Lookup the function in the configuration table. Configure it
217 * if required.
218 */
219 if (cfg->function != NULL) {
220 uint32_t tmp;
221
222 tmp = qcom_tlmm_pinctrl_gmux_function(gmux, cfg->function);
223 if (tmp == -1) {
224 device_printf(sc->dev,
225 "%s: pin=%s, function=%s, unknown!\n",
226 __func__,
227 pin_name,
228 cfg->function);
229 err = EINVAL;
230 goto done;
231 }
232
233 /*
234 * Program in the given function to the given pin.
235 */
236 QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
237 "%s: pin id=%u, new function=%u\n",
238 __func__,
239 gmux->id,
240 tmp);
241 err = qcom_tlmm_ipq4018_hw_pin_set_function(sc, gmux->id,
242 tmp);
243 if (err != 0) {
244 device_printf(sc->dev,
245 "%s: pin=%d: failed to set function (%d)\n",
246 __func__, gmux->id, err);
247 goto done;
248 }
249 }
250
251 /*
252 * Iterate the set of properties; call the relevant method
253 * if we need to change it.
254 */
255 for (i = 0; i < PROP_ID_MAX_ID; i++) {
256 if (cfg->params[i] == -1)
257 continue;
258 QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
259 "%s: pin_id=%u, param=%d, val=%d\n",
260 __func__,
261 gmux->id,
262 i,
263 cfg->params[i]);
264 switch (i) {
265 case PIN_ID_BIAS_DISABLE:
266 err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc,
267 gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE);
268 if (err != 0) {
269 device_printf(sc->dev,
270 "%s: pin=%d: failed to set pupd(DISABLE):"
271 " %d\n",
272 __func__, gmux->id, err);
273 goto done;
274 }
275 break;
276 case PIN_ID_BIAS_PULL_DOWN:
277 err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc,
278 gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN);
279 if (err != 0) {
280 device_printf(sc->dev,
281 "%s: pin=%d: failed to set pupd(PD):"
282 " %d\n",
283 __func__, gmux->id, err);
284 goto done;
285 }
286 break;
287 case PIN_ID_BIAS_BUS_HOLD:
288 err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc,
289 gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD);
290 if (err != 0) {
291 device_printf(sc->dev,
292 "%s: pin=%d: failed to set pupd(HOLD):"
293 " %d\n",
294 __func__, gmux->id, err);
295 goto done;
296 }
297 break;
298
299 case PIN_ID_BIAS_PULL_UP:
300 err = qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc,
301 gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP);
302 if (err != 0) {
303 device_printf(sc->dev,
304 "%s: pin=%d: failed to set pupd(PU):"
305 " %d\n",
306 __func__, gmux->id, err);
307 goto done;
308 }
309 break;
310 case PIN_ID_OUTPUT_LOW:
311 err = qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc,
312 gmux->id);
313 if (err != 0) {
314 device_printf(sc->dev,
315 "%s: pin=%d: failed to set OE:"
316 " %d\n",
317 __func__, gmux->id, err);
318 goto done;
319 }
320 err = qcom_tlmm_ipq4018_hw_pin_set_output_value(
321 sc, gmux->id, 0);
322 if (err != 0) {
323 device_printf(sc->dev,
324 "%s: pin=%d: failed to set output value:"
325 " %d\n",
326 __func__, gmux->id, err);
327 goto done;
328 }
329 break;
330 case PIN_ID_OUTPUT_HIGH:
331 err = qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc,
332 gmux->id);
333 if (err != 0) {
334 device_printf(sc->dev,
335 "%s: pin=%d: failed to set OE:"
336 " %d\n",
337 __func__, gmux->id, err);
338 goto done;
339 }
340 err = qcom_tlmm_ipq4018_hw_pin_set_output_value(
341 sc, gmux->id, 1);
342 if (err != 0) {
343 device_printf(sc->dev,
344 "%s: pin=%d: failed to set output value:"
345 " %d\n",
346 __func__, gmux->id, err);
347 goto done;
348 }
349 break;
350 case PIN_ID_DRIVE_STRENGTH:
351 err = qcom_tlmm_ipq4018_hw_pin_set_drive_strength(sc,
352 gmux->id, cfg->params[i]);
353 if (err != 0) {
354 device_printf(sc->dev,
355 "%s: pin=%d: failed to set drive"
356 " strength %d (%d)\n",
357 __func__, gmux->id,
358 cfg->params[i], err);
359 goto done;
360 }
361 break;
362 case PIN_ID_VM_ENABLE:
363 err = qcom_tlmm_ipq4018_hw_pin_set_vm(sc,
364 gmux->id, true);
365 if (err != 0) {
366 device_printf(sc->dev,
367 "%s: pin=%d: failed to set VM enable:"
368 " %d\n",
369 __func__, gmux->id, err);
370 goto done;
371 }
372 break;
373 case PIN_ID_VM_DISABLE:
374 err = qcom_tlmm_ipq4018_hw_pin_set_vm(sc,
375 gmux->id, false);
376 if (err != 0) {
377 device_printf(sc->dev,
378 "%s: pin=%d: failed to set VM disable:"
379 " %d\n",
380 __func__, gmux->id, err);
381 goto done;
382 }
383 break;
384 case PIN_ID_DRIVE_OPEN_DRAIN:
385 err = qcom_tlmm_ipq4018_hw_pin_set_open_drain(sc,
386 gmux->id, true);
387 if (err != 0) {
388 device_printf(sc->dev,
389 "%s: pin=%d: failed to set open drain"
390 " (%d)\n",
391 __func__, gmux->id, err);
392 goto done;
393 }
394 break;
395 case PIN_ID_INPUT_ENABLE:
396 /* Configure pin as an input */
397 err = qcom_tlmm_ipq4018_hw_pin_set_oe_input(sc,
398 gmux->id);
399 if (err != 0) {
400 device_printf(sc->dev,
401 "%s: pin=%d: failed to set pin as input"
402 " (%d)\n",
403 __func__, gmux->id, err);
404 goto done;
405 }
406 break;
407 case PIN_ID_INPUT_DISABLE:
408 /*
409 * the linux-msm GPIO driver treats this as an error;
410 * a pin should be configured as an output instead.
411 */
412 err = ENXIO;
413 goto done;
414 break;
415 case PIN_ID_BIAS_HIGH_IMPEDANCE:
416 case PIN_ID_INPUT_SCHMITT_ENABLE:
417 case PIN_ID_INPUT_SCHMITT_DISABLE:
418 case PIN_ID_INPUT_DEBOUNCE:
419 case PIN_ID_SLEW_RATE:
420 case PIN_ID_LOW_POWER_MODE_ENABLE:
421 case PIN_ID_LOW_POWER_MODE_DISABLE:
422 case PIN_ID_BIAS_PULL_PIN_DEFAULT:
423 case PIN_ID_DRIVE_PUSH_PULL:
424 case PIN_ID_DRIVE_OPEN_SOURCE:
425 case PIN_ID_POWER_SOURCE:
426 default:
427 device_printf(sc->dev,
428 "%s: ERROR: unknown/unsupported param: "
429 " pin_id=%u, param=%d, val=%d\n",
430 __func__,
431 gmux->id,
432 i,
433 cfg->params[i]);
434 err = ENXIO;
435 goto done;
436
437 }
438 }
439 done:
440 GPIO_UNLOCK(sc);
441 return (0);
442 }
443
444
445 static int
qcom_tlmm_pinctrl_config_node(struct qcom_tlmm_softc * sc,char * pin_name,struct qcom_tlmm_pinctrl_cfg * cfg)446 qcom_tlmm_pinctrl_config_node(struct qcom_tlmm_softc *sc,
447 char *pin_name, struct qcom_tlmm_pinctrl_cfg *cfg)
448 {
449 const struct qcom_tlmm_gpio_mux *gmux;
450 const struct qcom_tlmm_spec_pin *spin;
451 int rv;
452
453 /* Handle GPIO pins */
454 gmux = qcom_tlmm_pinctrl_search_gmux(sc, pin_name);
455
456 if (gmux != NULL) {
457 rv = qcom_tlmm_pinctrl_config_gmux(sc, pin_name, gmux, cfg);
458 return (rv);
459 }
460 /* Handle special pin groups */
461 spin = qcom_tlmm_pinctrl_search_spin(sc, pin_name);
462 if (spin != NULL) {
463 rv = qcom_tlmm_pinctrl_config_spin(sc, pin_name, spin, cfg);
464 return (rv);
465 }
466 device_printf(sc->dev, "Unknown pin: %s\n", pin_name);
467 return (ENXIO);
468 }
469
470 static int
qcom_tlmm_pinctrl_process_node(struct qcom_tlmm_softc * sc,phandle_t node)471 qcom_tlmm_pinctrl_process_node(struct qcom_tlmm_softc *sc,
472 phandle_t node)
473 {
474 struct qcom_tlmm_pinctrl_cfg cfg;
475 char *pins, *pname;
476 int i, len, lpins, rv;
477
478 /*
479 * Read the configuration and list of pins for the given node to
480 * configure.
481 */
482 rv = qcom_tlmm_pinctrl_read_node(sc, node, &cfg, &pins, &lpins);
483 if (rv != 0)
484 return (rv);
485
486 len = 0;
487 pname = pins;
488 do {
489 i = strlen(pname) + 1;
490 /*
491 * Configure the given node with the specific configuration.
492 */
493 rv = qcom_tlmm_pinctrl_config_node(sc, pname, &cfg);
494 if (rv != 0)
495 device_printf(sc->dev,
496 "Cannot configure pin: %s: %d\n", pname, rv);
497
498 len += i;
499 pname += i;
500 } while (len < lpins);
501
502 if (pins != NULL)
503 free(pins, M_OFWPROP);
504 if (cfg.function != NULL)
505 free(cfg.function, M_OFWPROP);
506
507 return (rv);
508 }
509
510 int
qcom_tlmm_pinctrl_configure(device_t dev,phandle_t cfgxref)511 qcom_tlmm_pinctrl_configure(device_t dev, phandle_t cfgxref)
512 {
513 struct qcom_tlmm_softc *sc;
514 phandle_t node, cfgnode;
515 int rv;
516
517 sc = device_get_softc(dev);
518 cfgnode = OF_node_from_xref(cfgxref);
519
520 for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) {
521 if (!ofw_bus_node_status_okay(node))
522 continue;
523 rv = qcom_tlmm_pinctrl_process_node(sc, node);
524 if (rv != 0)
525 device_printf(dev, "Pin config failed: %d\n", rv);
526 }
527
528 return (0);
529 }
530
531