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