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#include <sys/param.h> 30#include <sys/systm.h> 31#include <sys/bus.h> 32 33#include <sys/kernel.h> 34#include <sys/module.h> 35#include <sys/rman.h> 36#include <sys/lock.h> 37#include <sys/malloc.h> 38#include <sys/mutex.h> 39#include <sys/gpio.h> 40 41#include <machine/bus.h> 42#include <machine/resource.h> 43#include <dev/gpio/gpiobusvar.h> 44 45#include <dev/fdt/fdt_common.h> 46#include <dev/ofw/ofw_bus.h> 47#include <dev/ofw/ofw_bus_subr.h> 48 49#include <dev/fdt/fdt_pinctrl.h> 50 51#include "qcom_tlmm_var.h" 52#include "qcom_tlmm_pin.h" 53 54#include "qcom_tlmm_ipq4018_reg.h" 55#include "qcom_tlmm_ipq4018_hw.h" 56 57#include "gpio_if.h" 58 59static struct gpio_pin * 60qcom_tlmm_pin_lookup(struct qcom_tlmm_softc *sc, int pin) 61{ 62 if (pin >= sc->gpio_npins) 63 return (NULL); 64 65 return &sc->gpio_pins[pin]; 66} 67 68static void 69qcom_tlmm_pin_configure(struct qcom_tlmm_softc *sc, 70 struct gpio_pin *pin, unsigned int flags) 71{ 72 73 GPIO_LOCK_ASSERT(sc); 74 75 /* 76 * Manage input/output 77 */ 78 if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { 79 pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT); 80 if (flags & GPIO_PIN_OUTPUT) { 81 /* 82 * XXX TODO: read GPIO_PIN_PRESET_LOW / 83 * GPIO_PIN_PRESET_HIGH and if we're a GPIO 84 * function pin here, set the output 85 * pin value before we flip on oe_output. 86 */ 87 pin->gp_flags |= GPIO_PIN_OUTPUT; 88 qcom_tlmm_ipq4018_hw_pin_set_oe_output(sc, 89 pin->gp_pin); 90 } else { 91 pin->gp_flags |= GPIO_PIN_INPUT; 92 qcom_tlmm_ipq4018_hw_pin_set_oe_input(sc, 93 pin->gp_pin); 94 } 95 } 96 97 /* 98 * Set pull-up / pull-down configuration 99 */ 100 if (flags & GPIO_PIN_PULLUP) { 101 pin->gp_flags |= GPIO_PIN_PULLUP; 102 qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, 103 QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP); 104 } else if (flags & GPIO_PIN_PULLDOWN) { 105 pin->gp_flags |= GPIO_PIN_PULLDOWN; 106 qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, 107 QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN); 108 } else if ((flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) == 109 (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) { 110 pin->gp_flags |= GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN; 111 qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, 112 QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD); 113 } else { 114 pin->gp_flags &= ~(GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); 115 qcom_tlmm_ipq4018_hw_pin_set_pupd_config(sc, pin->gp_pin, 116 QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE); 117 } 118} 119 120device_t 121qcom_tlmm_get_bus(device_t dev) 122{ 123 struct qcom_tlmm_softc *sc; 124 125 sc = device_get_softc(dev); 126 127 return (sc->busdev); 128} 129 130int 131qcom_tlmm_pin_max(device_t dev, int *maxpin) 132{ 133 struct qcom_tlmm_softc *sc = device_get_softc(dev); 134 135 *maxpin = sc->gpio_npins - 1; 136 return (0); 137} 138 139int 140qcom_tlmm_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) 141{ 142 struct qcom_tlmm_softc *sc = device_get_softc(dev); 143 struct gpio_pin *p; 144 145 p = qcom_tlmm_pin_lookup(sc, pin); 146 if (p == NULL) 147 return (EINVAL); 148 149 GPIO_LOCK(sc); 150 *caps = p->gp_caps; 151 GPIO_UNLOCK(sc); 152 153 return (0); 154} 155 156int 157qcom_tlmm_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) 158{ 159 struct qcom_tlmm_softc *sc = device_get_softc(dev); 160 uint32_t ret = 0, val; 161 bool is_output; 162 qcom_tlmm_pin_pupd_config_t pupd_config; 163 164 if (pin >= sc->gpio_npins) 165 return (EINVAL); 166 167 *flags = 0; 168 169 GPIO_LOCK(sc); 170 171 /* Lookup function - see what it is, whether we're a GPIO line */ 172 ret = qcom_tlmm_ipq4018_hw_pin_get_function(sc, pin, &val); 173 if (ret != 0) 174 goto done; 175 176 /* Lookup input/output state */ 177 ret = qcom_tlmm_ipq4018_hw_pin_get_oe_state(sc, pin, &is_output); 178 if (ret != 0) 179 goto done; 180 if (is_output) 181 *flags |= GPIO_PIN_OUTPUT; 182 else 183 *flags |= GPIO_PIN_INPUT; 184 185 /* Lookup pull-up / pull-down state */ 186 ret = qcom_tlmm_ipq4018_hw_pin_get_pupd_config(sc, pin, 187 &pupd_config); 188 if (ret != 0) 189 goto done; 190 191 switch (pupd_config) { 192 case QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE: 193 break; 194 case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN: 195 *flags |= GPIO_PIN_PULLDOWN; 196 break; 197 case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP: 198 *flags |= GPIO_PIN_PULLUP; 199 break; 200 case QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD: 201 *flags |= (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); 202 break; 203 } 204 205done: 206 GPIO_UNLOCK(sc); 207 return (ret); 208} 209 210int 211qcom_tlmm_pin_getname(device_t dev, uint32_t pin, char *name) 212{ 213 struct qcom_tlmm_softc *sc = device_get_softc(dev); 214 struct gpio_pin *p; 215 216 p = qcom_tlmm_pin_lookup(sc, pin); 217 if (p == NULL) 218 return (EINVAL); 219 220 GPIO_LOCK(sc); 221 memcpy(name, p->gp_name, GPIOMAXNAME); 222 GPIO_UNLOCK(sc); 223 224 return (0); 225} 226 227int 228qcom_tlmm_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) 229{ 230 struct qcom_tlmm_softc *sc = device_get_softc(dev); 231 struct gpio_pin *p; 232 233 p = qcom_tlmm_pin_lookup(sc, pin); 234 if (p == NULL) 235 return (EINVAL); 236 237 GPIO_LOCK(sc); 238 qcom_tlmm_pin_configure(sc, p, flags); 239 GPIO_UNLOCK(sc); 240 241 return (0); 242} 243 244int 245qcom_tlmm_pin_set(device_t dev, uint32_t pin, unsigned int value) 246{ 247 struct qcom_tlmm_softc *sc = device_get_softc(dev); 248 int ret; 249 250 if (pin >= sc->gpio_npins) 251 return (EINVAL); 252 253 GPIO_LOCK(sc); 254 ret = qcom_tlmm_ipq4018_hw_pin_set_output_value(sc, pin, value); 255 GPIO_UNLOCK(sc); 256 257 return (ret); 258} 259 260int 261qcom_tlmm_pin_get(device_t dev, uint32_t pin, unsigned int *val) 262{ 263 struct qcom_tlmm_softc *sc = device_get_softc(dev); 264 int ret; 265 266 if (pin >= sc->gpio_npins) 267 return (EINVAL); 268 269 GPIO_LOCK(sc); 270 ret = qcom_tlmm_ipq4018_hw_pin_get_input_value(sc, pin, val); 271 GPIO_UNLOCK(sc); 272 273 return (ret); 274} 275 276int 277qcom_tlmm_pin_toggle(device_t dev, uint32_t pin) 278{ 279 struct qcom_tlmm_softc *sc = device_get_softc(dev); 280 int ret; 281 282 if (pin >= sc->gpio_npins) 283 return (EINVAL); 284 285 GPIO_LOCK(sc); 286 ret = qcom_tlmm_ipq4018_hw_pin_toggle_output_value(sc, pin); 287 GPIO_UNLOCK(sc); 288 289 return (ret); 290} 291 292int 293qcom_tlmm_filter(void *arg) 294{ 295 296 /* TODO: something useful */ 297 return (FILTER_STRAY); 298} 299 300void 301qcom_tlmm_intr(void *arg) 302{ 303 struct qcom_tlmm_softc *sc = arg; 304 GPIO_LOCK(sc); 305 /* TODO: something useful */ 306 GPIO_UNLOCK(sc); 307} 308 309/* 310 * ofw bus interface 311 */ 312phandle_t 313qcom_tlmm_pin_get_node(device_t dev, device_t bus) 314{ 315 316 /* We only have one child, the GPIO bus, which needs our own node. */ 317 return (ofw_bus_get_node(dev)); 318} 319 320