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