1/*-
2 * Copyright 2015 John Wehle <john@feith.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * Amlogic aml8726 pinctrl driver.
29 *
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/conf.h>
38#include <sys/bus.h>
39#include <sys/kernel.h>
40#include <sys/module.h>
41#include <sys/lock.h>
42#include <sys/mutex.h>
43#include <sys/resource.h>
44#include <sys/rman.h>
45
46#include <machine/bus.h>
47
48#include <dev/ofw/ofw_bus.h>
49#include <dev/ofw/ofw_bus_subr.h>
50#include <dev/fdt/fdt_pinctrl.h>
51
52#include <arm/amlogic/aml8726/aml8726_soc.h>
53#include <arm/amlogic/aml8726/aml8726_pinctrl.h>
54
55struct aml8726_pinctrl_softc {
56	device_t				dev;
57	struct {
58		struct aml8726_pinctrl_function	*func;
59		struct aml8726_pinctrl_pkg_pin	*ppin;
60		boolean_t			pud_ctrl;
61	}					soc;
62	struct resource				*res[6];
63	struct mtx				mtx;
64};
65
66static struct resource_spec aml8726_pinctrl_spec[] = {
67	{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* mux */
68	{ SYS_RES_MEMORY, 1, RF_ACTIVE | RF_SHAREABLE }, /* pu/pd */
69	{ SYS_RES_MEMORY, 2, RF_ACTIVE | RF_SHAREABLE }, /* pull enable */
70	{ SYS_RES_MEMORY, 3, RF_ACTIVE }, /* ao mux */
71	{ SYS_RES_MEMORY, 4, RF_ACTIVE | RF_SHAREABLE }, /* ao pu/pd */
72	{ SYS_RES_MEMORY, 5, RF_ACTIVE | RF_SHAREABLE }, /* ao pull enable */
73	{ -1, 0 }
74};
75
76#define	AML_PINCTRL_LOCK(sc)		mtx_lock(&(sc)->mtx)
77#define	AML_PINCTRL_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
78#define	AML_PINCTRL_LOCK_INIT(sc)	\
79    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
80    "pinctrl", MTX_DEF)
81#define	AML_PINCTRL_LOCK_DESTROY(sc)	mtx_destroy(&(sc)->mtx);
82
83#define	MUX_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
84#define	MUX_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
85
86#define	PUD_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[1], reg, (val))
87#define	PUD_READ_4(sc, reg)		bus_read_4((sc)->res[1], reg)
88
89#define	PEN_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[2], reg, (val))
90#define	PEN_READ_4(sc, reg)		bus_read_4((sc)->res[2], reg)
91
92#define	AOMUX_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[3], reg, (val))
93#define	AOMUX_READ_4(sc, reg)		bus_read_4((sc)->res[3], reg)
94
95#define	AOPUD_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[4], reg, (val))
96#define	AOPUD_READ_4(sc, reg)		bus_read_4((sc)->res[4], reg)
97
98#define	AOPEN_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[5], reg, (val))
99#define	AOPEN_READ_4(sc, reg)		bus_read_4((sc)->res[5], reg)
100
101static int
102aml8726_pinctrl_probe(device_t dev)
103{
104
105	if (!ofw_bus_status_okay(dev))
106		return (ENXIO);
107
108	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-pinctrl"))
109		return (ENXIO);
110
111	device_set_desc(dev, "Amlogic aml8726 pinctrl");
112
113	return (BUS_PROBE_DEFAULT);
114}
115
116static int
117aml8726_pinctrl_attach(device_t dev)
118{
119	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
120
121	sc->dev = dev;
122
123	sc->soc.pud_ctrl = false;
124
125	switch (aml8726_soc_hw_rev) {
126	case AML_SOC_HW_REV_M3:
127		sc->soc.func = aml8726_m3_pinctrl;
128		sc->soc.ppin = aml8726_m3_pkg_pin;
129		break;
130	case AML_SOC_HW_REV_M6:
131		sc->soc.func = aml8726_m6_pinctrl;
132		sc->soc.ppin = aml8726_m6_pkg_pin;
133		break;
134	case AML_SOC_HW_REV_M8:
135		sc->soc.func = aml8726_m8_pinctrl;
136		sc->soc.ppin = aml8726_m8_pkg_pin;
137		sc->soc.pud_ctrl = true;
138		break;
139	case AML_SOC_HW_REV_M8B:
140		sc->soc.func = aml8726_m8b_pinctrl;
141		sc->soc.ppin = aml8726_m8b_pkg_pin;
142		sc->soc.pud_ctrl = true;
143		break;
144	default:
145		device_printf(dev, "unsupported SoC\n");
146		return (ENXIO);
147		/* NOTREACHED */
148	}
149
150	if (bus_alloc_resources(dev, aml8726_pinctrl_spec, sc->res)) {
151		device_printf(dev, "could not allocate resources for device\n");
152		return (ENXIO);
153	}
154
155	AML_PINCTRL_LOCK_INIT(sc);
156
157	fdt_pinctrl_register(dev, "amlogic,pins");
158	fdt_pinctrl_configure_tree(dev);
159
160	return (0);
161}
162
163static int
164aml8726_pinctrl_detach(device_t dev)
165{
166	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
167
168	AML_PINCTRL_LOCK_DESTROY(sc);
169
170	bus_release_resources(dev, aml8726_pinctrl_spec, sc->res);
171
172	return (0);
173}
174
175
176static int
177aml8726_pinctrl_configure_pins(device_t dev, phandle_t cfgxref)
178{
179	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
180	struct aml8726_pinctrl_function *cf;
181	struct aml8726_pinctrl_function *f;
182	struct aml8726_pinctrl_pkg_pin *pp;
183	struct aml8726_pinctrl_pin *cp;
184	struct aml8726_pinctrl_pin *p;
185	enum aml8726_pinctrl_pull_mode pm;
186	char *function_name;
187	char *pins;
188	char *pin_name;
189	char *pull;
190	phandle_t node;
191	ssize_t len;
192	uint32_t value;
193
194	node = OF_node_from_xref(cfgxref);
195
196	len = OF_getprop_alloc(node, "amlogic,function",
197	    (void **)&function_name);
198
199	if (len < 0) {
200		device_printf(dev,
201		    "missing amlogic,function attribute in FDT\n");
202		return (ENXIO);
203	}
204
205	for (f = sc->soc.func; f->name != NULL; f++)
206		if (strncmp(f->name, function_name, len) == 0)
207			break;
208
209	if (f->name == NULL) {
210		device_printf(dev, "unknown function attribute %.*s in FDT\n",
211		    len, function_name);
212		OF_prop_free(function_name);
213		return (ENXIO);
214	}
215
216	OF_prop_free(function_name);
217
218	len = OF_getprop_alloc(node, "amlogic,pull",
219	    (void **)&pull);
220
221	pm = aml8726_unknown_pm;
222
223	if (len > 0) {
224		if (strncmp(pull, "enable", len) == 0)
225			pm = aml8726_enable_pm;
226		else if (strncmp(pull, "disable", len) == 0)
227			pm = aml8726_disable_pm;
228		else if (strncmp(pull, "down", len) == 0)
229			pm = aml8726_enable_down_pm;
230		else if (strncmp(pull, "up", len) == 0)
231			pm = aml8726_enable_up_pm;
232		else {
233			device_printf(dev,
234			    "unknown pull attribute %.*s in FDT\n",
235			    len, pull);
236			OF_prop_free(pull);
237			return (ENXIO);
238		}
239	}
240
241	OF_prop_free(pull);
242
243	/*
244	 * Setting the pull direction isn't supported on all SoC.
245	 */
246	switch (pm) {
247	case aml8726_enable_down_pm:
248	case aml8726_enable_up_pm:
249		if (sc->soc.pud_ctrl == false) {
250			device_printf(dev,
251			    "SoC doesn't support setting pull direction.\n");
252			return (ENXIO);
253		}
254		break;
255	default:
256		break;
257	}
258
259	len = OF_getprop_alloc(node, "amlogic,pins",
260	    (void **)&pins);
261
262	if (len < 0) {
263		device_printf(dev, "missing amlogic,pins attribute in FDT\n");
264		return (ENXIO);
265	}
266
267	pin_name = pins;
268
269	while (len) {
270		for (p = f->pins; p->name != NULL; p++)
271			if (strncmp(p->name, pin_name, len) == 0)
272				break;
273
274		if (p->name == NULL) {
275			/* display message prior to queuing up next string */
276			device_printf(dev, "unknown pin attribute %.*s in FDT\n",
277			    len, pin_name);
278		}
279
280		/* queue up next string */
281		while (*pin_name && len) {
282			pin_name++;
283			len--;
284		}
285		if (len) {
286			pin_name++;
287			len--;
288		}
289
290		if (p->name == NULL)
291			continue;
292
293		for (pp = sc->soc.ppin; pp->pkg_name != NULL; pp++)
294			if (strcmp(pp->pkg_name, p->pkg_name) == 0)
295				break;
296
297		if (pp->pkg_name == NULL) {
298			device_printf(dev,
299			    "missing entry for package pin %s\n",
300			    p->pkg_name);
301			continue;
302		}
303
304		if (pm != aml8726_unknown_pm && pp->pull_bits == 0x00000000) {
305			device_printf(dev,
306			    "missing pull info for package pin %s\n",
307			    p->pkg_name);
308			continue;
309		}
310
311		AML_PINCTRL_LOCK(sc);
312
313		/*
314		 * First clear all other mux bits associated with this
315		 * package pin.  This may briefly configure the pin as
316		 * GPIO ...  however this should be fine since after
317		 * reset the default GPIO mode is input.
318		 */
319
320		for (cf = sc->soc.func; cf->name != NULL; cf++)
321			for (cp = cf->pins; cp->name != NULL; cp++) {
322				if (cp == p)
323					continue;
324				if (strcmp(cp->pkg_name, p->pkg_name) != 0)
325					continue;
326				if (cp->mux_bits == 0)
327					continue;
328				if (pp->aobus == false) {
329					value = MUX_READ_4(sc, cp->mux_addr);
330					value &= ~cp->mux_bits;
331					MUX_WRITE_4(sc, cp->mux_addr, value);
332				} else {
333					value = AOMUX_READ_4(sc, cp->mux_addr);
334					value &= ~cp->mux_bits;
335					AOMUX_WRITE_4(sc, cp->mux_addr, value);
336				}
337			}
338
339		/*
340		 * Now set the desired mux bits.
341		 *
342		 * In the case of GPIO there's no bits to set.
343		 */
344
345		if (p->mux_bits != 0) {
346			if (pp->aobus == false) {
347				value = MUX_READ_4(sc, p->mux_addr);
348				value |= p->mux_bits;
349				MUX_WRITE_4(sc, p->mux_addr, value);
350			} else {
351				value = AOMUX_READ_4(sc, p->mux_addr);
352				value |= p->mux_bits;
353				AOMUX_WRITE_4(sc, p->mux_addr, value);
354			}
355		}
356
357		/*
358		 * Finally set the pull mode if it was specified.
359		 */
360
361		switch (pm) {
362		case aml8726_enable_down_pm:
363		case aml8726_enable_up_pm:
364			if (pp->aobus == false) {
365				value = PUD_READ_4(sc, pp->pull_addr);
366				if (pm == aml8726_enable_down_pm)
367					value &= ~pp->pull_bits;
368				else
369					value |= pp->pull_bits;
370				PUD_WRITE_4(sc, pp->pull_addr, value);
371			} else {
372				value = AOPUD_READ_4(sc, pp->pull_addr);
373				if (pm == aml8726_enable_down_pm)
374					value &= ~(pp->pull_bits << 16);
375				else
376					value |= (pp->pull_bits << 16);
377				AOPUD_WRITE_4(sc, pp->pull_addr, value);
378			}
379			/* FALLTHROUGH */
380		case aml8726_disable_pm:
381		case aml8726_enable_pm:
382			if (pp->aobus == false) {
383				value = PEN_READ_4(sc, pp->pull_addr);
384				if (pm == aml8726_disable_pm)
385					value &= ~pp->pull_bits;
386				else
387					value |= pp->pull_bits;
388				PEN_WRITE_4(sc, pp->pull_addr, value);
389			} else {
390				value = AOPEN_READ_4(sc, pp->pull_addr);
391				if (pm == aml8726_disable_pm)
392					value &= ~pp->pull_bits;
393				else
394					value |= pp->pull_bits;
395				AOPEN_WRITE_4(sc, pp->pull_addr, value);
396			}
397			break;
398		default:
399			break;
400		}
401
402		AML_PINCTRL_UNLOCK(sc);
403	}
404
405	OF_prop_free(pins);
406
407	return (0);
408}
409
410
411static device_method_t aml8726_pinctrl_methods[] = {
412	/* Device interface */
413	DEVMETHOD(device_probe,		aml8726_pinctrl_probe),
414	DEVMETHOD(device_attach,	aml8726_pinctrl_attach),
415	DEVMETHOD(device_detach,	aml8726_pinctrl_detach),
416
417	/* fdt_pinctrl interface */
418	DEVMETHOD(fdt_pinctrl_configure,aml8726_pinctrl_configure_pins),
419
420	DEVMETHOD_END
421};
422
423static driver_t aml8726_pinctrl_driver = {
424	"pinctrl",
425	aml8726_pinctrl_methods,
426	sizeof(struct aml8726_pinctrl_softc),
427};
428
429static devclass_t aml8726_pinctrl_devclass;
430
431EARLY_DRIVER_MODULE(pinctrl, simplebus, aml8726_pinctrl_driver,
432    aml8726_pinctrl_devclass, 0, 0,  BUS_PASS_CPU + BUS_PASS_ORDER_LATE);
433