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
usb_read(struct i2ctinyusb_softc * sc,int cmd,int value,int index,void * data,int len)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
usb_write(struct i2ctinyusb_softc * sc,int cmd,int value,int index,void * data,int len)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
i2ctinyusb_probe(device_t dev)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
i2ctinyusb_attach(device_t dev)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 bus_attach_children(dev);
177
178 return (0);
179
180 detach:
181 i2ctinyusb_detach(dev);
182 return (err);
183 }
184
185 static int
i2ctinyusb_detach(device_t dev)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
i2ctinyusb_transfer(device_t dev,struct iic_msg * msgs,uint32_t nmsgs)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
i2ctinyusb_reset(device_t dev,u_char speed,u_char addr,u_char * oldaddr)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