xref: /freebsd/sys/dev/usb/misc/i2ctinyusb.c (revision b985c9ca)
1 /*-
2  * Copyright (c) 2024 Denis Bodor <dbodor@rollmops.ninja>
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 ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * 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  * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from
29  * Till Harbaum & Nicolai Electronics
30  * See :
31  *   https://github.com/harbaum/I2C-Tiny-USB
32  * and
33  *   https://github.com/Nicolai-Electronics/rp2040-i2c-interface
34  */
35 
36 #include <sys/systm.h>
37 #include <sys/kernel.h>
38 #include <sys/bus.h>
39 #include <sys/module.h>
40 #include <sys/mutex.h>
41 #include <sys/condvar.h>
42 #include <sys/unistd.h>
43 #include <dev/usb/usb.h>
44 #include <dev/usb/usbdi.h>
45 #include <dev/usb/usbhid.h>
46 #include <dev/usb/usb_device.h>
47 
48 #include <dev/iicbus/iiconf.h>
49 #include <dev/iicbus/iicbus.h>
50 #include "iicbus_if.h"
51 
52 // commands via USB, must match command ids in the firmware
53 #define CMD_ECHO		0
54 #define CMD_GET_FUNC		1
55 #define CMD_SET_DELAY		2
56 #define CMD_GET_STATUS		3
57 #define CMD_I2C_IO		4
58 #define CMD_SET_LED		8
59 #define CMD_I2C_IO_BEGIN	(1 << 0)
60 #define CMD_I2C_IO_END		(1 << 1)
61 #define STATUS_IDLE		0
62 #define STATUS_ADDRESS_ACK	1
63 #define STATUS_ADDRESS_NAK	2
64 
65 struct i2ctinyusb_softc {
66 	struct usb_device	*sc_udev;
67 	device_t		sc_iic_dev;
68 	device_t		iicbus_dev;
69 	struct mtx		sc_mtx;
70 };
71 
72 #define USB_VENDOR_EZPROTOTYPES	0x1c40
73 #define USB_VENDOR_FTDI		0x0403
74 
75 static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = {
76 	{ USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) },
77 	{ USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) },
78 };
79 
80 /* Prototypes. */
81 static int i2ctinyusb_probe(device_t dev);
82 static int i2ctinyusb_attach(device_t dev);
83 static int i2ctinyusb_detach(device_t dev);
84 static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs,
85 		uint32_t nmsgs);
86 static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr,
87 		u_char *oldaddr);
88 
89 static int
90 usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
91 		void *data, int len)
92 {
93 	int error;
94 	struct usb_device_request req;
95 	uint16_t actlen;
96 
97 	req.bmRequestType = UT_READ_VENDOR_INTERFACE;
98 	req.bRequest = cmd;
99 	USETW(req.wValue, value);
100 	USETW(req.wIndex, (index >> 1));
101 	USETW(req.wLength, len);
102 
103 	error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
104 			&actlen, 2000);
105 
106 	if (error)
107 		actlen = -1;
108 
109 	return (actlen);
110 }
111 
112 static int
113 usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
114 		void *data, int len)
115 {
116 	int error;
117 	struct usb_device_request req;
118 	uint16_t actlen;
119 
120 	req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
121 	req.bRequest = cmd;
122 	USETW(req.wValue, value);
123 	USETW(req.wIndex, (index >> 1));
124 	USETW(req.wLength, len);
125 
126 	error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
127 			&actlen, 2000);
128 
129 	if (error) {
130 		actlen = -1;
131 	}
132 
133 	return (actlen);
134 }
135 
136 static int
137 i2ctinyusb_probe(device_t dev)
138 {
139 	struct usb_attach_arg *uaa;
140 
141 	uaa = device_get_ivars(dev);
142 
143 	if (uaa->usb_mode != USB_MODE_HOST)
144 		return (ENXIO);
145 
146 	if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs),
147 				uaa) == 0) {
148 		device_set_desc(dev, "I2C-Tiny-USB I2C interface");
149 		return (BUS_PROBE_DEFAULT);
150 	}
151 
152 	return (ENXIO);
153 }
154 
155 static int
156 i2ctinyusb_attach(device_t dev)
157 {
158 	struct i2ctinyusb_softc *sc;
159 	struct usb_attach_arg *uaa;
160 	int err;
161 
162 	sc = device_get_softc(dev);
163 
164 	uaa = device_get_ivars(dev);
165 	device_set_usb_desc(dev);
166 
167 	sc->sc_udev = uaa->device;
168 	mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE);
169 
170 	sc->iicbus_dev = device_add_child(dev, "iicbus", -1);
171 	if (sc->iicbus_dev == NULL) {
172 		device_printf(dev, "iicbus creation failed\n");
173 		err = ENXIO;
174 		goto detach;
175 	}
176 	err = bus_generic_attach(dev);
177 
178 	return (0);
179 
180 detach:
181 	i2ctinyusb_detach(dev);
182 	return (err);
183 }
184 
185 static int
186 i2ctinyusb_detach(device_t dev)
187 {
188 	struct i2ctinyusb_softc *sc;
189 	int err;
190 
191 	sc = device_get_softc(dev);
192 
193 	err = bus_generic_detach(dev);
194 	if (err != 0)
195 		return (err);
196 	device_delete_children(dev);
197 
198 	mtx_destroy(&sc->sc_mtx);
199 
200 	return (0);
201 }
202 
203 static int
204 i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
205 {
206 	struct i2ctinyusb_softc *sc;
207 	uint32_t i;
208 	int ret = 0;
209 	int cmd = CMD_I2C_IO;
210 	struct iic_msg *pmsg;
211 	unsigned char pstatus;
212 
213 	sc = device_get_softc(dev);
214 
215 	mtx_lock(&sc->sc_mtx);
216 
217 	for (i = 0; i < nmsgs; i++) {
218 		pmsg = &msgs[i];
219 		if (i == 0)
220 			cmd |= CMD_I2C_IO_BEGIN;
221 		if (i == nmsgs - 1)
222 			cmd |= CMD_I2C_IO_END;
223 
224 		if ((msgs[i].flags & IIC_M_RD) != 0) {
225 			if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
226 							pmsg->len)) != pmsg->len) {
227 				printf("Read error: got %u\n", ret);
228 				ret = EIO;
229 				goto out;
230 			}
231 		} else {
232 			if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
233 							pmsg->len)) != pmsg->len) {
234 				printf("Write error: got %u\n", ret);
235 				ret = EIO;
236 				goto out;
237 			}
238 
239 		}
240 		// check status
241 		if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) {
242 			ret = EIO;
243 			goto out;
244 		}
245 
246 		if (pstatus == STATUS_ADDRESS_NAK) {
247 			ret = EIO;
248 			goto out;
249 		}
250 	}
251 
252 	ret = 0;
253 
254 out:
255 	mtx_unlock(&sc->sc_mtx);
256 	return (ret);
257 }
258 
259 static int
260 i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
261 {
262 	struct i2ctinyusb_softc *sc;
263 	int ret;
264 
265 	sc = device_get_softc(dev);
266 
267 	mtx_lock(&sc->sc_mtx);
268 	ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0);
269 	mtx_unlock(&sc->sc_mtx);
270 
271 	if (ret < 0)
272 		printf("i2ctinyusb_reset error!\n");
273 
274 	return (0);
275 }
276 
277 static device_method_t i2ctinyusb_methods[] = {
278 	/* Device interface */
279 	DEVMETHOD(device_probe, i2ctinyusb_probe),
280 	DEVMETHOD(device_attach, i2ctinyusb_attach),
281 	DEVMETHOD(device_detach, i2ctinyusb_detach),
282 
283 	/* I2C methods */
284 	DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer),
285 	DEVMETHOD(iicbus_reset, i2ctinyusb_reset),
286 	DEVMETHOD(iicbus_callback, iicbus_null_callback),
287 
288 	DEVMETHOD_END
289 };
290 
291 static driver_t i2ctinyusb_driver = {
292 	.name = "iichb",
293 	.methods = i2ctinyusb_methods,
294 	.size = sizeof(struct i2ctinyusb_softc),
295 };
296 
297 DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL);
298 MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1);
299 MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
300 MODULE_VERSION(i2ctinyusb, 1);
301 
302 /* vi: set ts=8 sw=8: */
303