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