]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/qcom_tlmm/qcom_tlmm_pinmux.c
sound: Remove hw.snd.version and SND_DRV_VERSION
[FreeBSD/FreeBSD.git] / sys / dev / qcom_tlmm / qcom_tlmm_pinmux.c
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