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 *
qcom_tlmm_pin_lookup(struct qcom_tlmm_softc * sc,int 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
qcom_tlmm_pin_configure(struct qcom_tlmm_softc * sc,struct gpio_pin * pin,unsigned int flags)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
qcom_tlmm_get_bus(device_t dev)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
qcom_tlmm_pin_max(device_t dev,int * maxpin)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
qcom_tlmm_pin_getcaps(device_t dev,uint32_t pin,uint32_t * caps)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
qcom_tlmm_pin_getflags(device_t dev,uint32_t pin,uint32_t * flags)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
qcom_tlmm_pin_getname(device_t dev,uint32_t pin,char * name)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
qcom_tlmm_pin_setflags(device_t dev,uint32_t pin,uint32_t flags)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
qcom_tlmm_pin_set(device_t dev,uint32_t pin,unsigned int value)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
qcom_tlmm_pin_get(device_t dev,uint32_t pin,unsigned int * val)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
qcom_tlmm_pin_toggle(device_t dev,uint32_t pin)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
qcom_tlmm_filter(void * arg)293 qcom_tlmm_filter(void *arg)
294 {
295
296 /* TODO: something useful */
297 return (FILTER_STRAY);
298 }
299
300 void
qcom_tlmm_intr(void * arg)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
qcom_tlmm_pin_get_node(device_t dev,device_t bus)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