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 59 static struct gpio_pin * 60 qcom_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 68 static void 69 qcom_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 120 device_t 121 qcom_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 130 int 131 qcom_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 139 int 140 qcom_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 156 int 157 qcom_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 205 done: 206 GPIO_UNLOCK(sc); 207 return (ret); 208 } 209 210 int 211 qcom_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 227 int 228 qcom_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 244 int 245 qcom_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 260 int 261 qcom_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 276 int 277 qcom_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 292 int 293 qcom_tlmm_filter(void *arg) 294 { 295 296 /* TODO: something useful */ 297 return (FILTER_STRAY); 298 } 299 300 void 301 qcom_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 */ 312 phandle_t 313 qcom_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