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: releng/11.0/sys/arm/amlogic/aml8726/aml8726_pinctrl.c 300175 2016-05-18 23:41:58Z gonzo $");
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/fdt/fdt_common.h>
49#include <dev/fdt/fdt_pinctrl.h>
50#include <dev/ofw/ofw_bus.h>
51#include <dev/ofw/ofw_bus_subr.h>
52
53#include <arm/amlogic/aml8726/aml8726_soc.h>
54#include <arm/amlogic/aml8726/aml8726_pinctrl.h>
55
56struct aml8726_pinctrl_softc {
57	device_t				dev;
58	struct {
59		struct aml8726_pinctrl_function	*func;
60		struct aml8726_pinctrl_pkg_pin	*ppin;
61		boolean_t			pud_ctrl;
62	}					soc;
63	struct resource				*res[6];
64	struct mtx				mtx;
65};
66
67static struct resource_spec aml8726_pinctrl_spec[] = {
68	{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* mux */
69	{ SYS_RES_MEMORY, 1, RF_ACTIVE | RF_SHAREABLE }, /* pu/pd */
70	{ SYS_RES_MEMORY, 2, RF_ACTIVE | RF_SHAREABLE }, /* pull enable */
71	{ SYS_RES_MEMORY, 3, RF_ACTIVE }, /* ao mux */
72	{ SYS_RES_MEMORY, 4, RF_ACTIVE | RF_SHAREABLE }, /* ao pu/pd */
73	{ SYS_RES_MEMORY, 5, RF_ACTIVE | RF_SHAREABLE }, /* ao pull enable */
74	{ -1, 0 }
75};
76
77#define	AML_PINCTRL_LOCK(sc)		mtx_lock(&(sc)->mtx)
78#define	AML_PINCTRL_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
79#define	AML_PINCTRL_LOCK_INIT(sc)	\
80    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
81    "pinctrl", MTX_DEF)
82#define	AML_PINCTRL_LOCK_DESTROY(sc)	mtx_destroy(&(sc)->mtx);
83
84#define	MUX_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
85#define	MUX_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
86
87#define	PUD_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[1], reg, (val))
88#define	PUD_READ_4(sc, reg)		bus_read_4((sc)->res[1], reg)
89
90#define	PEN_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[2], reg, (val))
91#define	PEN_READ_4(sc, reg)		bus_read_4((sc)->res[2], reg)
92
93#define	AOMUX_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[3], reg, (val))
94#define	AOMUX_READ_4(sc, reg)		bus_read_4((sc)->res[3], reg)
95
96#define	AOPUD_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[4], reg, (val))
97#define	AOPUD_READ_4(sc, reg)		bus_read_4((sc)->res[4], reg)
98
99#define	AOPEN_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[5], reg, (val))
100#define	AOPEN_READ_4(sc, reg)		bus_read_4((sc)->res[5], reg)
101
102static int
103aml8726_pinctrl_probe(device_t dev)
104{
105
106	if (!ofw_bus_status_okay(dev))
107		return (ENXIO);
108
109	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-pinctrl"))
110		return (ENXIO);
111
112	device_set_desc(dev, "Amlogic aml8726 pinctrl");
113
114	return (BUS_PROBE_DEFAULT);
115}
116
117static int
118aml8726_pinctrl_attach(device_t dev)
119{
120	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
121
122	sc->dev = dev;
123
124	sc->soc.pud_ctrl = false;
125
126	switch (aml8726_soc_hw_rev) {
127	case AML_SOC_HW_REV_M3:
128		sc->soc.func = aml8726_m3_pinctrl;
129		sc->soc.ppin = aml8726_m3_pkg_pin;
130		break;
131	case AML_SOC_HW_REV_M6:
132		sc->soc.func = aml8726_m6_pinctrl;
133		sc->soc.ppin = aml8726_m6_pkg_pin;
134		break;
135	case AML_SOC_HW_REV_M8:
136		sc->soc.func = aml8726_m8_pinctrl;
137		sc->soc.ppin = aml8726_m8_pkg_pin;
138		sc->soc.pud_ctrl = true;
139		break;
140	case AML_SOC_HW_REV_M8B:
141		sc->soc.func = aml8726_m8b_pinctrl;
142		sc->soc.ppin = aml8726_m8b_pkg_pin;
143		sc->soc.pud_ctrl = true;
144		break;
145	default:
146		device_printf(dev, "unsupported SoC\n");
147		return (ENXIO);
148		/* NOTREACHED */
149	}
150
151	if (bus_alloc_resources(dev, aml8726_pinctrl_spec, sc->res)) {
152		device_printf(dev, "could not allocate resources for device\n");
153		return (ENXIO);
154	}
155
156	AML_PINCTRL_LOCK_INIT(sc);
157
158	fdt_pinctrl_register(dev, "amlogic,pins");
159	fdt_pinctrl_configure_tree(dev);
160
161	return (0);
162}
163
164static int
165aml8726_pinctrl_detach(device_t dev)
166{
167	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
168
169	AML_PINCTRL_LOCK_DESTROY(sc);
170
171	bus_release_resources(dev, aml8726_pinctrl_spec, sc->res);
172
173	return (0);
174}
175
176
177static int
178aml8726_pinctrl_configure_pins(device_t dev, phandle_t cfgxref)
179{
180	struct aml8726_pinctrl_softc *sc = device_get_softc(dev);
181	struct aml8726_pinctrl_function *cf;
182	struct aml8726_pinctrl_function *f;
183	struct aml8726_pinctrl_pkg_pin *pp;
184	struct aml8726_pinctrl_pin *cp;
185	struct aml8726_pinctrl_pin *p;
186	enum aml8726_pinctrl_pull_mode pm;
187	char *function_name;
188	char *pins;
189	char *pin_name;
190	char *pull;
191	phandle_t node;
192	ssize_t len;
193	uint32_t value;
194
195	node = OF_node_from_xref(cfgxref);
196
197	len = OF_getprop_alloc(node, "amlogic,function",
198	    sizeof(char), (void **)&function_name);
199
200	if (len < 0) {
201		device_printf(dev,
202		    "missing amlogic,function attribute in FDT\n");
203		return (ENXIO);
204	}
205
206	for (f = sc->soc.func; f->name != NULL; f++)
207		if (strncmp(f->name, function_name, len) == 0)
208			break;
209
210	if (f->name == NULL) {
211		device_printf(dev, "unknown function attribute %.*s in FDT\n",
212		    len, function_name);
213		OF_prop_free(function_name);
214		return (ENXIO);
215	}
216
217	OF_prop_free(function_name);
218
219	len = OF_getprop_alloc(node, "amlogic,pull",
220	    sizeof(char), (void **)&pull);
221
222	pm = aml8726_unknown_pm;
223
224	if (len > 0) {
225		if (strncmp(pull, "enable", len) == 0)
226			pm = aml8726_enable_pm;
227		else if (strncmp(pull, "disable", len) == 0)
228			pm = aml8726_disable_pm;
229		else if (strncmp(pull, "down", len) == 0)
230			pm = aml8726_enable_down_pm;
231		else if (strncmp(pull, "up", len) == 0)
232			pm = aml8726_enable_up_pm;
233		else {
234			device_printf(dev,
235			    "unknown pull attribute %.*s in FDT\n",
236			    len, pull);
237			OF_prop_free(pull);
238			return (ENXIO);
239		}
240	}
241
242	OF_prop_free(pull);
243
244	/*
245	 * Setting the pull direction isn't supported on all SoC.
246	 */
247	switch (pm) {
248	case aml8726_enable_down_pm:
249	case aml8726_enable_up_pm:
250		if (sc->soc.pud_ctrl == false) {
251			device_printf(dev,
252			    "SoC doesn't support setting pull direction.\n");
253			return (ENXIO);
254		}
255		break;
256	default:
257		break;
258	}
259
260	len = OF_getprop_alloc(node, "amlogic,pins",
261	    sizeof(char), (void **)&pins);
262
263	if (len < 0) {
264		device_printf(dev, "missing amlogic,pins attribute in FDT\n");
265		return (ENXIO);
266	}
267
268	pin_name = pins;
269
270	while (len) {
271		for (p = f->pins; p->name != NULL; p++)
272			if (strncmp(p->name, pin_name, len) == 0)
273				break;
274
275		if (p->name == NULL) {
276			/* display message prior to queuing up next string */
277			device_printf(dev, "unknown pin attribute %.*s in FDT\n",
278			    len, pin_name);
279		}
280
281		/* queue up next string */
282		while (*pin_name && len) {
283			pin_name++;
284			len--;
285		}
286		if (len) {
287			pin_name++;
288			len--;
289		}
290
291		if (p->name == NULL)
292			continue;
293
294		for (pp = sc->soc.ppin; pp->pkg_name != NULL; pp++)
295			if (strcmp(pp->pkg_name, p->pkg_name) == 0)
296				break;
297
298		if (pp->pkg_name == NULL) {
299			device_printf(dev,
300			    "missing entry for package pin %s\n",
301			    p->pkg_name);
302			continue;
303		}
304
305		if (pm != aml8726_unknown_pm && pp->pull_bits == 0x00000000) {
306			device_printf(dev,
307			    "missing pull info for package pin %s\n",
308			    p->pkg_name);
309			continue;
310		}
311
312		AML_PINCTRL_LOCK(sc);
313
314		/*
315		 * First clear all other mux bits associated with this
316		 * package pin.  This may briefly configure the pin as
317		 * GPIO ...  however this should be fine since after
318		 * reset the default GPIO mode is input.
319		 */
320
321		for (cf = sc->soc.func; cf->name != NULL; cf++)
322			for (cp = cf->pins; cp->name != NULL; cp++) {
323				if (cp == p)
324					continue;
325				if (strcmp(cp->pkg_name, p->pkg_name) != 0)
326					continue;
327				if (cp->mux_bits == 0)
328					continue;
329				if (pp->aobus == false) {
330					value = MUX_READ_4(sc, cp->mux_addr);
331					value &= ~cp->mux_bits;
332					MUX_WRITE_4(sc, cp->mux_addr, value);
333				} else {
334					value = AOMUX_READ_4(sc, cp->mux_addr);
335					value &= ~cp->mux_bits;
336					AOMUX_WRITE_4(sc, cp->mux_addr, value);
337				}
338			}
339
340		/*
341		 * Now set the desired mux bits.
342		 *
343		 * In the case of GPIO there's no bits to set.
344		 */
345
346		if (p->mux_bits != 0) {
347			if (pp->aobus == false) {
348				value = MUX_READ_4(sc, p->mux_addr);
349				value |= p->mux_bits;
350				MUX_WRITE_4(sc, p->mux_addr, value);
351			} else {
352				value = AOMUX_READ_4(sc, p->mux_addr);
353				value |= p->mux_bits;
354				AOMUX_WRITE_4(sc, p->mux_addr, value);
355			}
356		}
357
358		/*
359		 * Finally set the pull mode if it was specified.
360		 */
361
362		switch (pm) {
363		case aml8726_enable_down_pm:
364		case aml8726_enable_up_pm:
365			if (pp->aobus == false) {
366				value = PUD_READ_4(sc, pp->pull_addr);
367				if (pm == aml8726_enable_down_pm)
368					value &= ~pp->pull_bits;
369				else
370					value |= pp->pull_bits;
371				PUD_WRITE_4(sc, pp->pull_addr, value);
372			} else {
373				value = AOPUD_READ_4(sc, pp->pull_addr);
374				if (pm == aml8726_enable_down_pm)
375					value &= ~(pp->pull_bits << 16);
376				else
377					value |= (pp->pull_bits << 16);
378				AOPUD_WRITE_4(sc, pp->pull_addr, value);
379			}
380			/* FALLTHROUGH */
381		case aml8726_disable_pm:
382		case aml8726_enable_pm:
383			if (pp->aobus == false) {
384				value = PEN_READ_4(sc, pp->pull_addr);
385				if (pm == aml8726_disable_pm)
386					value &= ~pp->pull_bits;
387				else
388					value |= pp->pull_bits;
389				PEN_WRITE_4(sc, pp->pull_addr, value);
390			} else {
391				value = AOPEN_READ_4(sc, pp->pull_addr);
392				if (pm == aml8726_disable_pm)
393					value &= ~pp->pull_bits;
394				else
395					value |= pp->pull_bits;
396				AOPEN_WRITE_4(sc, pp->pull_addr, value);
397			}
398			break;
399		default:
400			break;
401		}
402
403		AML_PINCTRL_UNLOCK(sc);
404	}
405
406	OF_prop_free(pins);
407
408	return (0);
409}
410
411
412static device_method_t aml8726_pinctrl_methods[] = {
413	/* Device interface */
414	DEVMETHOD(device_probe,		aml8726_pinctrl_probe),
415	DEVMETHOD(device_attach,	aml8726_pinctrl_attach),
416	DEVMETHOD(device_detach,	aml8726_pinctrl_detach),
417
418	/* fdt_pinctrl interface */
419	DEVMETHOD(fdt_pinctrl_configure,aml8726_pinctrl_configure_pins),
420
421	DEVMETHOD_END
422};
423
424static driver_t aml8726_pinctrl_driver = {
425	"pinctrl",
426	aml8726_pinctrl_methods,
427	sizeof(struct aml8726_pinctrl_softc),
428};
429
430static devclass_t aml8726_pinctrl_devclass;
431
432EARLY_DRIVER_MODULE(pinctrl, simplebus, aml8726_pinctrl_driver,
433    aml8726_pinctrl_devclass, 0, 0,  BUS_PASS_CPU + BUS_PASS_ORDER_LATE);
434