1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 1998, 2001 Nicolas Souchu, Marc Bouget
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following 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 */
30
31#include <sys/cdefs.h>
32/*
33 * I2C Bit-Banging over parallel port
34 *
35 * See the Official Philips interface description in lpbb(4)
36 */
37
38#include <sys/param.h>
39#include <sys/bus.h>
40#include <sys/lock.h>
41#include <sys/kernel.h>
42#include <sys/module.h>
43#include <sys/mutex.h>
44#include <sys/systm.h>
45#include <sys/uio.h>
46
47#include <dev/ppbus/ppbconf.h>
48#include "ppbus_if.h"
49#include <dev/ppbus/ppbio.h>
50
51#include <dev/iicbus/iiconf.h>
52#include <dev/iicbus/iicbus.h>
53
54#include "iicbb_if.h"
55
56static int lpbb_detect(device_t dev);
57
58static void
59lpbb_identify(driver_t *driver, device_t parent)
60{
61
62	device_t dev;
63
64	dev = device_find_child(parent, "lpbb", -1);
65	if (!dev)
66		BUS_ADD_CHILD(parent, 0, "lpbb", -1);
67}
68
69static int
70lpbb_probe(device_t dev)
71{
72
73	/* Perhaps call this during identify instead? */
74	if (!lpbb_detect(dev))
75		return (ENXIO);
76
77	device_set_desc(dev, "Parallel I2C bit-banging interface");
78
79	return (0);
80}
81
82static int
83lpbb_attach(device_t dev)
84{
85	device_t bitbang;
86
87	/* add generic bit-banging code */
88	bitbang = device_add_child(dev, "iicbb", -1);
89	device_probe_and_attach(bitbang);
90
91	return (0);
92}
93
94static int
95lpbb_callback(device_t dev, int index, caddr_t data)
96{
97	device_t ppbus = device_get_parent(dev);
98	int error = 0;
99	int how;
100
101	switch (index) {
102	case IIC_REQUEST_BUS:
103		/* request the ppbus */
104		how = *(int *)data;
105		ppb_lock(ppbus);
106		error = ppb_request_bus(ppbus, dev, how);
107		ppb_unlock(ppbus);
108		break;
109
110	case IIC_RELEASE_BUS:
111		/* release the ppbus */
112		ppb_lock(ppbus);
113		error = ppb_release_bus(ppbus, dev);
114		ppb_unlock(ppbus);
115		break;
116
117	default:
118		error = EINVAL;
119	}
120
121	return (error);
122}
123
124#define SDA_out 0x80
125#define SCL_out 0x08
126#define SDA_in  0x80
127#define SCL_in  0x08
128#define ALIM    0x20
129#define I2CKEY  0x50
130
131/* Reset bus by setting SDA first and then SCL. */
132static void
133lpbb_reset_bus(device_t dev)
134{
135	device_t ppbus = device_get_parent(dev);
136
137	ppb_assert_locked(ppbus);
138	ppb_wdtr(ppbus, (u_char)~SDA_out);
139	ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) | SCL_out));
140}
141
142static int
143lpbb_getscl(device_t dev)
144{
145	device_t ppbus = device_get_parent(dev);
146	int rval;
147
148	ppb_lock(ppbus);
149	rval = ((ppb_rstr(ppbus) & SCL_in) == SCL_in);
150	ppb_unlock(ppbus);
151	return (rval);
152}
153
154static int
155lpbb_getsda(device_t dev)
156{
157	device_t ppbus = device_get_parent(dev);
158	int rval;
159
160	ppb_lock(ppbus);
161	rval = ((ppb_rstr(ppbus) & SDA_in) == SDA_in);
162	ppb_unlock(ppbus);
163	return (rval);
164}
165
166static void
167lpbb_setsda(device_t dev, int val)
168{
169	device_t ppbus = device_get_parent(dev);
170
171	ppb_lock(ppbus);
172	if (val == 0)
173		ppb_wdtr(ppbus, (u_char)SDA_out);
174	else
175		ppb_wdtr(ppbus, (u_char)~SDA_out);
176	ppb_unlock(ppbus);
177}
178
179static void
180lpbb_setscl(device_t dev, int val)
181{
182	device_t ppbus = device_get_parent(dev);
183
184	ppb_lock(ppbus);
185	if (val == 0)
186		ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) & ~SCL_out));
187	else
188		ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) | SCL_out));
189	ppb_unlock(ppbus);
190}
191
192static int
193lpbb_detect(device_t dev)
194{
195	device_t ppbus = device_get_parent(dev);
196
197	ppb_lock(ppbus);
198	if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) {
199		ppb_unlock(ppbus);
200		device_printf(dev, "can't allocate ppbus\n");
201		return (0);
202	}
203
204	lpbb_reset_bus(dev);
205
206	if ((ppb_rstr(ppbus) & I2CKEY) ||
207		((ppb_rstr(ppbus) & ALIM) != ALIM)) {
208		ppb_release_bus(ppbus, dev);
209		ppb_unlock(ppbus);
210		return (0);
211	}
212
213	ppb_release_bus(ppbus, dev);
214	ppb_unlock(ppbus);
215
216	return (1);
217}
218
219static int
220lpbb_reset(device_t dev, u_char speed, u_char addr, u_char * oldaddr)
221{
222	device_t ppbus = device_get_parent(dev);
223
224	ppb_lock(ppbus);
225	if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) {
226		ppb_unlock(ppbus);
227		device_printf(dev, "can't allocate ppbus\n");
228		return (0);
229	}
230
231	lpbb_reset_bus(dev);
232
233	ppb_release_bus(ppbus, dev);
234	ppb_unlock(ppbus);
235
236	return (IIC_ENOADDR);
237}
238
239static device_method_t lpbb_methods[] = {
240	/* device interface */
241	DEVMETHOD(device_identify,	lpbb_identify),
242	DEVMETHOD(device_probe,		lpbb_probe),
243	DEVMETHOD(device_attach,	lpbb_attach),
244
245	/* iicbb interface */
246	DEVMETHOD(iicbb_callback,	lpbb_callback),
247	DEVMETHOD(iicbb_setsda,		lpbb_setsda),
248	DEVMETHOD(iicbb_setscl,		lpbb_setscl),
249	DEVMETHOD(iicbb_getsda,		lpbb_getsda),
250	DEVMETHOD(iicbb_getscl,		lpbb_getscl),
251	DEVMETHOD(iicbb_reset,		lpbb_reset),
252
253	DEVMETHOD_END
254};
255
256static driver_t lpbb_driver = {
257	"lpbb",
258	lpbb_methods,
259	1,
260};
261
262DRIVER_MODULE(lpbb, ppbus, lpbb_driver, 0, 0);
263DRIVER_MODULE(iicbb, lpbb, iicbb_driver, 0, 0);
264MODULE_DEPEND(lpbb, ppbus, 1, 1, 1);
265MODULE_DEPEND(lpbb, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER);
266MODULE_VERSION(lpbb, 1);
267