1/*-
2 * Copyright 2013-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 I2C driver.
29 *
30 * Currently this implementation doesn't take full advantage of the hardware.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <sys/bus.h>
39#include <sys/conf.h>
40#include <sys/kernel.h>
41#include <sys/lock.h>
42#include <sys/mbuf.h>
43#include <sys/malloc.h>
44#include <sys/module.h>
45#include <sys/mutex.h>
46#include <sys/rman.h>
47
48#include <machine/bus.h>
49
50#include <dev/ofw/ofw_bus.h>
51#include <dev/ofw/ofw_bus_subr.h>
52
53#include <dev/iicbus/iiconf.h>
54#include <dev/iicbus/iicbus.h>
55
56#include "iicbb_if.h"
57
58
59struct aml8726_iic_softc {
60	device_t	dev;
61	struct resource	*res[1];
62	struct mtx	mtx;
63	device_t	iicbb;
64};
65
66static struct resource_spec aml8726_iic_spec[] = {
67	{ SYS_RES_MEMORY, 0, RF_ACTIVE },
68	{ -1, 0 }
69};
70
71#define	AML_I2C_LOCK(sc)		mtx_lock(&(sc)->mtx)
72#define	AML_I2C_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
73#define	AML_I2C_LOCK_INIT(sc)		\
74    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
75    "i2c", MTX_DEF)
76#define	AML_I2C_LOCK_DESTROY(sc)	mtx_destroy(&(sc)->mtx);
77
78#define	AML_I2C_CTRL_REG		0
79#define	AML_I2C_MANUAL_SDA_I		(1 << 26)
80#define	AML_I2C_MANUAL_SCL_I		(1 << 25)
81#define	AML_I2C_MANUAL_SDA_O		(1 << 24)
82#define	AML_I2C_MANUAL_SCL_O		(1 << 23)
83#define	AML_I2C_MANUAL_EN		(1 << 22)
84
85#define	CSR_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
86#define	CSR_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
87
88static int
89aml8726_iic_probe(device_t dev)
90{
91
92	if (!ofw_bus_status_okay(dev))
93		return (ENXIO);
94
95	if (!ofw_bus_is_compatible(dev, "amlogic,meson6-i2c"))
96		return (ENXIO);
97
98	device_set_desc(dev, "Amlogic aml8726 I2C");
99
100	return (BUS_PROBE_DEFAULT);
101}
102
103static int
104aml8726_iic_attach(device_t dev)
105{
106	struct aml8726_iic_softc *sc = device_get_softc(dev);
107	int error;
108
109	sc->dev = dev;
110
111	if (bus_alloc_resources(dev, aml8726_iic_spec, sc->res)) {
112		device_printf(dev, "can not allocate resources for device\n");
113		return (ENXIO);
114	}
115
116	AML_I2C_LOCK_INIT(sc);
117
118	sc->iicbb = device_add_child(dev, "iicbb", -1);
119
120	if (sc->iicbb == NULL) {
121		device_printf(dev, "could not add iicbb\n");
122		error = ENXIO;
123		goto fail;
124	}
125
126	error = device_probe_and_attach(sc->iicbb);
127
128	if (error) {
129		device_printf(dev, "could not attach iicbb\n");
130		goto fail;
131	}
132
133	return (0);
134
135fail:
136	AML_I2C_LOCK_DESTROY(sc);
137	bus_release_resources(dev, aml8726_iic_spec, sc->res);
138
139	return (error);
140}
141
142static int
143aml8726_iic_detach(device_t dev)
144{
145	struct aml8726_iic_softc *sc = device_get_softc(dev);
146	device_t child;
147
148	/*
149	 * Detach the children before recursively deleting
150	 * in case a child has a pointer to a grandchild
151	 * which is used by the child's detach routine.
152	 *
153	 * Remember the child before detaching so we can
154	 * delete it (bus_generic_detach indirectly zeroes
155	 * sc->child_dev).
156	 */
157	child = sc->iicbb;
158	bus_generic_detach(dev);
159	if (child)
160		device_delete_child(dev, child);
161
162	AML_I2C_LOCK_DESTROY(sc);
163
164	bus_release_resources(dev, aml8726_iic_spec, sc->res);
165
166	return (0);
167}
168
169static void
170aml8726_iic_child_detached(device_t dev, device_t child)
171{
172	struct aml8726_iic_softc *sc = device_get_softc(dev);
173
174	if (child == sc->iicbb)
175		sc->iicbb = NULL;
176}
177
178static int
179aml8726_iic_callback(device_t dev, int index, caddr_t data)
180{
181
182	return (0);
183}
184
185static int
186aml8726_iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
187{
188	struct aml8726_iic_softc *sc = device_get_softc(dev);
189
190	AML_I2C_LOCK(sc);
191
192	CSR_WRITE_4(sc, AML_I2C_CTRL_REG,
193	    (CSR_READ_4(sc, AML_I2C_CTRL_REG) | AML_I2C_MANUAL_SDA_O |
194	    AML_I2C_MANUAL_SCL_O | AML_I2C_MANUAL_EN));
195
196	AML_I2C_UNLOCK(sc);
197
198	/* Wait for 10 usec */
199	DELAY(10);
200
201	return (IIC_ENOADDR);
202}
203
204static int
205aml8726_iic_getscl(device_t dev)
206{
207	struct aml8726_iic_softc *sc = device_get_softc(dev);
208
209	return (CSR_READ_4(sc, AML_I2C_CTRL_REG) & AML_I2C_MANUAL_SCL_I);
210}
211
212static int
213aml8726_iic_getsda(device_t dev)
214{
215	struct aml8726_iic_softc *sc = device_get_softc(dev);
216
217	return (CSR_READ_4(sc, AML_I2C_CTRL_REG) & AML_I2C_MANUAL_SDA_I);
218}
219
220static void
221aml8726_iic_setscl(device_t dev, int val)
222{
223	struct aml8726_iic_softc *sc = device_get_softc(dev);
224
225	AML_I2C_LOCK(sc);
226
227	CSR_WRITE_4(sc, AML_I2C_CTRL_REG, ((CSR_READ_4(sc, AML_I2C_CTRL_REG) &
228	    ~AML_I2C_MANUAL_SCL_O) | (val ? AML_I2C_MANUAL_SCL_O : 0) |
229	    AML_I2C_MANUAL_EN));
230
231	AML_I2C_UNLOCK(sc);
232}
233
234static void
235aml8726_iic_setsda(device_t dev, int val)
236{
237	struct aml8726_iic_softc *sc = device_get_softc(dev);
238
239	AML_I2C_LOCK(sc);
240
241	CSR_WRITE_4(sc, AML_I2C_CTRL_REG, ((CSR_READ_4(sc, AML_I2C_CTRL_REG) &
242	    ~AML_I2C_MANUAL_SDA_O) | (val ? AML_I2C_MANUAL_SDA_O : 0) |
243	    AML_I2C_MANUAL_EN));
244
245	AML_I2C_UNLOCK(sc);
246}
247
248static device_method_t aml8726_iic_methods[] = {
249	/* Device interface */
250	DEVMETHOD(device_probe,		aml8726_iic_probe),
251	DEVMETHOD(device_attach,	aml8726_iic_attach),
252	DEVMETHOD(device_detach,	aml8726_iic_detach),
253
254	/* bus interface */
255	DEVMETHOD(bus_child_detached,	aml8726_iic_child_detached),
256	DEVMETHOD(bus_print_child,	bus_generic_print_child),
257	DEVMETHOD(bus_driver_added,	bus_generic_driver_added),
258
259	/* IICBB interface */
260	DEVMETHOD(iicbb_callback,	aml8726_iic_callback),
261	DEVMETHOD(iicbb_reset,		aml8726_iic_reset),
262
263	DEVMETHOD(iicbb_getscl,		aml8726_iic_getscl),
264	DEVMETHOD(iicbb_getsda,		aml8726_iic_getsda),
265	DEVMETHOD(iicbb_setscl,		aml8726_iic_setscl),
266	DEVMETHOD(iicbb_setsda,		aml8726_iic_setsda),
267
268	DEVMETHOD_END
269};
270
271static driver_t aml8726_iic_driver = {
272	"aml8726_iic",
273	aml8726_iic_methods,
274	sizeof(struct aml8726_iic_softc),
275};
276
277static devclass_t aml8726_iic_devclass;
278
279DRIVER_MODULE(aml8726_iic, simplebus, aml8726_iic_driver,
280    aml8726_iic_devclass, 0, 0);
281DRIVER_MODULE(iicbb, aml8726_iic, iicbb_driver, iicbb_devclass, 0, 0);
282MODULE_DEPEND(aml8726_iic, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER);
283MODULE_VERSION(aml8726_iic, 1);
284