xref: /freebsd/sys/dev/qcom_tlmm/qcom_tlmm_pinmux.c (revision 1719886f6d08408b834d270c59ffcfd821c8f63a)
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 *
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
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 *
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
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
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
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
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
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
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