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 531