xref: /freebsd/sys/dev/qcom_tlmm/qcom_tlmm_pin.c (revision 4543ef516683042d46f3bd3bb8a4f3f746e00499)
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