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 */
72static 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
98static const struct qcom_tlmm_spec_pin *
99qcom_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
114static int
115qcom_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
125static const struct qcom_tlmm_gpio_mux *
126qcom_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
141static int
142qcom_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
156static int
157qcom_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
203static int
204qcom_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	}
439done:
440	GPIO_UNLOCK(sc);
441	return (0);
442}
443
444
445static int
446qcom_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
470static int
471qcom_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
510int
511qcom_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