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