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