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