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