xref: /freebsd/sys/dev/usb/misc/i2ctinyusb.c (revision 18250ec6c089c0c50cbd9fd87d78e03ff89916df)
154e231b3SDenis Bodor /*-
254e231b3SDenis Bodor  * Copyright (c) 2024 Denis Bodor <dbodor@rollmops.ninja>
354e231b3SDenis Bodor  * All rights reserved.
454e231b3SDenis Bodor  *
554e231b3SDenis Bodor  * Redistribution and use in source and binary forms, with or without
654e231b3SDenis Bodor  * modification, are permitted provided that the following conditions
754e231b3SDenis Bodor  * are met:
854e231b3SDenis Bodor  * 1. Redistributions of source code must retain the above copyright
954e231b3SDenis Bodor  *    notice, this list of conditions and the following disclaimer.
1054e231b3SDenis Bodor  * 2. Redistributions in binary form must reproduce the above copyright
1154e231b3SDenis Bodor  *    notice, this list of conditions and the following disclaimer in the
1254e231b3SDenis Bodor  *    documentation and/or other materials provided with the distribution.
1354e231b3SDenis Bodor  *
1454e231b3SDenis Bodor  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1554e231b3SDenis Bodor  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1654e231b3SDenis Bodor  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1754e231b3SDenis Bodor  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1854e231b3SDenis Bodor  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1954e231b3SDenis Bodor  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2054e231b3SDenis Bodor  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
2154e231b3SDenis Bodor  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2254e231b3SDenis Bodor  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2354e231b3SDenis Bodor  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2454e231b3SDenis Bodor  * SUCH DAMAGE.
2554e231b3SDenis Bodor  */
2654e231b3SDenis Bodor 
2754e231b3SDenis Bodor /*
2854e231b3SDenis Bodor  * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from
2954e231b3SDenis Bodor  * Till Harbaum & Nicolai Electronics
3054e231b3SDenis Bodor  * See :
3154e231b3SDenis Bodor  *   https://github.com/harbaum/I2C-Tiny-USB
3254e231b3SDenis Bodor  * and
3354e231b3SDenis Bodor  *   https://github.com/Nicolai-Electronics/rp2040-i2c-interface
3454e231b3SDenis Bodor  */
3554e231b3SDenis Bodor 
3654e231b3SDenis Bodor #include <sys/systm.h>
3754e231b3SDenis Bodor #include <sys/kernel.h>
3854e231b3SDenis Bodor #include <sys/bus.h>
3954e231b3SDenis Bodor #include <sys/module.h>
4054e231b3SDenis Bodor #include <sys/mutex.h>
4154e231b3SDenis Bodor #include <sys/condvar.h>
4254e231b3SDenis Bodor #include <sys/unistd.h>
4354e231b3SDenis Bodor #include <dev/usb/usb.h>
4454e231b3SDenis Bodor #include <dev/usb/usbdi.h>
4554e231b3SDenis Bodor #include <dev/usb/usbhid.h>
4654e231b3SDenis Bodor #include <dev/usb/usb_device.h>
4754e231b3SDenis Bodor 
4854e231b3SDenis Bodor #include <dev/iicbus/iiconf.h>
4954e231b3SDenis Bodor #include <dev/iicbus/iicbus.h>
5054e231b3SDenis Bodor #include "iicbus_if.h"
5154e231b3SDenis Bodor 
5254e231b3SDenis Bodor // commands via USB, must match command ids in the firmware
5354e231b3SDenis Bodor #define CMD_ECHO		0
5454e231b3SDenis Bodor #define CMD_GET_FUNC		1
5554e231b3SDenis Bodor #define CMD_SET_DELAY		2
5654e231b3SDenis Bodor #define CMD_GET_STATUS		3
5754e231b3SDenis Bodor #define CMD_I2C_IO		4
5854e231b3SDenis Bodor #define CMD_SET_LED		8
5954e231b3SDenis Bodor #define CMD_I2C_IO_BEGIN	(1 << 0)
6054e231b3SDenis Bodor #define CMD_I2C_IO_END		(1 << 1)
6154e231b3SDenis Bodor #define STATUS_IDLE		0
6254e231b3SDenis Bodor #define STATUS_ADDRESS_ACK	1
6354e231b3SDenis Bodor #define STATUS_ADDRESS_NAK	2
6454e231b3SDenis Bodor 
6554e231b3SDenis Bodor struct i2ctinyusb_softc {
6654e231b3SDenis Bodor 	struct usb_device	*sc_udev;
6754e231b3SDenis Bodor 	device_t		sc_iic_dev;
6854e231b3SDenis Bodor 	device_t		iicbus_dev;
6954e231b3SDenis Bodor 	struct mtx		sc_mtx;
7054e231b3SDenis Bodor };
7154e231b3SDenis Bodor 
7254e231b3SDenis Bodor #define USB_VENDOR_EZPROTOTYPES	0x1c40
7354e231b3SDenis Bodor #define USB_VENDOR_FTDI		0x0403
7454e231b3SDenis Bodor 
7554e231b3SDenis Bodor static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = {
7654e231b3SDenis Bodor 	{ USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) },
7754e231b3SDenis Bodor 	{ USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) },
7854e231b3SDenis Bodor };
7954e231b3SDenis Bodor 
8054e231b3SDenis Bodor /* Prototypes. */
8154e231b3SDenis Bodor static int i2ctinyusb_probe(device_t dev);
8254e231b3SDenis Bodor static int i2ctinyusb_attach(device_t dev);
8354e231b3SDenis Bodor static int i2ctinyusb_detach(device_t dev);
8454e231b3SDenis Bodor static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs,
8554e231b3SDenis Bodor 		uint32_t nmsgs);
8654e231b3SDenis Bodor static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr,
8754e231b3SDenis Bodor 		u_char *oldaddr);
8854e231b3SDenis Bodor 
8954e231b3SDenis Bodor static int
usb_read(struct i2ctinyusb_softc * sc,int cmd,int value,int index,void * data,int len)9054e231b3SDenis Bodor usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
9154e231b3SDenis Bodor 		void *data, int len)
9254e231b3SDenis Bodor {
9354e231b3SDenis Bodor 	int error;
9454e231b3SDenis Bodor 	struct usb_device_request req;
9554e231b3SDenis Bodor 	uint16_t actlen;
9654e231b3SDenis Bodor 
9754e231b3SDenis Bodor 	req.bmRequestType = UT_READ_VENDOR_INTERFACE;
9854e231b3SDenis Bodor 	req.bRequest = cmd;
9954e231b3SDenis Bodor 	USETW(req.wValue, value);
10054e231b3SDenis Bodor 	USETW(req.wIndex, (index >> 1));
10154e231b3SDenis Bodor 	USETW(req.wLength, len);
10254e231b3SDenis Bodor 
10354e231b3SDenis Bodor 	error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
10454e231b3SDenis Bodor 			&actlen, 2000);
10554e231b3SDenis Bodor 
10654e231b3SDenis Bodor 	if (error)
10754e231b3SDenis Bodor 		actlen = -1;
10854e231b3SDenis Bodor 
10954e231b3SDenis Bodor 	return (actlen);
11054e231b3SDenis Bodor }
11154e231b3SDenis Bodor 
11254e231b3SDenis Bodor static int
usb_write(struct i2ctinyusb_softc * sc,int cmd,int value,int index,void * data,int len)11354e231b3SDenis Bodor usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
11454e231b3SDenis Bodor 		void *data, int len)
11554e231b3SDenis Bodor {
11654e231b3SDenis Bodor 	int error;
11754e231b3SDenis Bodor 	struct usb_device_request req;
11854e231b3SDenis Bodor 	uint16_t actlen;
11954e231b3SDenis Bodor 
12054e231b3SDenis Bodor 	req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
12154e231b3SDenis Bodor 	req.bRequest = cmd;
12254e231b3SDenis Bodor 	USETW(req.wValue, value);
12354e231b3SDenis Bodor 	USETW(req.wIndex, (index >> 1));
12454e231b3SDenis Bodor 	USETW(req.wLength, len);
12554e231b3SDenis Bodor 
12654e231b3SDenis Bodor 	error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
12754e231b3SDenis Bodor 			&actlen, 2000);
12854e231b3SDenis Bodor 
12954e231b3SDenis Bodor 	if (error) {
13054e231b3SDenis Bodor 		actlen = -1;
13154e231b3SDenis Bodor 	}
13254e231b3SDenis Bodor 
13354e231b3SDenis Bodor 	return (actlen);
13454e231b3SDenis Bodor }
13554e231b3SDenis Bodor 
13654e231b3SDenis Bodor static int
i2ctinyusb_probe(device_t dev)13754e231b3SDenis Bodor i2ctinyusb_probe(device_t dev)
13854e231b3SDenis Bodor {
13954e231b3SDenis Bodor 	struct usb_attach_arg *uaa;
14054e231b3SDenis Bodor 
14154e231b3SDenis Bodor 	uaa = device_get_ivars(dev);
14254e231b3SDenis Bodor 
14354e231b3SDenis Bodor 	if (uaa->usb_mode != USB_MODE_HOST)
14454e231b3SDenis Bodor 		return (ENXIO);
14554e231b3SDenis Bodor 
14654e231b3SDenis Bodor 	if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs),
14754e231b3SDenis Bodor 				uaa) == 0) {
14854e231b3SDenis Bodor 		device_set_desc(dev, "I2C-Tiny-USB I2C interface");
14954e231b3SDenis Bodor 		return (BUS_PROBE_DEFAULT);
15054e231b3SDenis Bodor 	}
15154e231b3SDenis Bodor 
15254e231b3SDenis Bodor 	return (ENXIO);
15354e231b3SDenis Bodor }
15454e231b3SDenis Bodor 
15554e231b3SDenis Bodor static int
i2ctinyusb_attach(device_t dev)15654e231b3SDenis Bodor i2ctinyusb_attach(device_t dev)
15754e231b3SDenis Bodor {
15854e231b3SDenis Bodor 	struct i2ctinyusb_softc *sc;
15954e231b3SDenis Bodor 	struct usb_attach_arg *uaa;
16054e231b3SDenis Bodor 	int err;
16154e231b3SDenis Bodor 
16254e231b3SDenis Bodor 	sc = device_get_softc(dev);
16354e231b3SDenis Bodor 
16454e231b3SDenis Bodor 	uaa = device_get_ivars(dev);
16554e231b3SDenis Bodor 	device_set_usb_desc(dev);
16654e231b3SDenis Bodor 
16754e231b3SDenis Bodor 	sc->sc_udev = uaa->device;
16854e231b3SDenis Bodor 	mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE);
16954e231b3SDenis Bodor 
17054e231b3SDenis Bodor 	sc->iicbus_dev = device_add_child(dev, "iicbus", -1);
17154e231b3SDenis Bodor 	if (sc->iicbus_dev == NULL) {
17254e231b3SDenis Bodor 		device_printf(dev, "iicbus creation failed\n");
17354e231b3SDenis Bodor 		err = ENXIO;
17454e231b3SDenis Bodor 		goto detach;
17554e231b3SDenis Bodor 	}
176*18250ec6SJohn Baldwin 	bus_attach_children(dev);
17754e231b3SDenis Bodor 
17854e231b3SDenis Bodor 	return (0);
17954e231b3SDenis Bodor 
18054e231b3SDenis Bodor detach:
18154e231b3SDenis Bodor 	i2ctinyusb_detach(dev);
18254e231b3SDenis Bodor 	return (err);
18354e231b3SDenis Bodor }
18454e231b3SDenis Bodor 
18554e231b3SDenis Bodor static int
i2ctinyusb_detach(device_t dev)18654e231b3SDenis Bodor i2ctinyusb_detach(device_t dev)
18754e231b3SDenis Bodor {
18854e231b3SDenis Bodor 	struct i2ctinyusb_softc *sc;
18954e231b3SDenis Bodor 	int err;
19054e231b3SDenis Bodor 
19154e231b3SDenis Bodor 	sc = device_get_softc(dev);
19254e231b3SDenis Bodor 
19354e231b3SDenis Bodor 	err = bus_generic_detach(dev);
19454e231b3SDenis Bodor 	if (err != 0)
19554e231b3SDenis Bodor 		return (err);
19654e231b3SDenis Bodor 	device_delete_children(dev);
19754e231b3SDenis Bodor 
19854e231b3SDenis Bodor 	mtx_destroy(&sc->sc_mtx);
19954e231b3SDenis Bodor 
20054e231b3SDenis Bodor 	return (0);
20154e231b3SDenis Bodor }
20254e231b3SDenis Bodor 
20354e231b3SDenis Bodor static int
i2ctinyusb_transfer(device_t dev,struct iic_msg * msgs,uint32_t nmsgs)20454e231b3SDenis Bodor i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
20554e231b3SDenis Bodor {
20654e231b3SDenis Bodor 	struct i2ctinyusb_softc *sc;
20754e231b3SDenis Bodor 	uint32_t i;
20854e231b3SDenis Bodor 	int ret = 0;
20954e231b3SDenis Bodor 	int cmd = CMD_I2C_IO;
21054e231b3SDenis Bodor 	struct iic_msg *pmsg;
21154e231b3SDenis Bodor 	unsigned char pstatus;
21254e231b3SDenis Bodor 
21354e231b3SDenis Bodor 	sc = device_get_softc(dev);
21454e231b3SDenis Bodor 
21554e231b3SDenis Bodor 	mtx_lock(&sc->sc_mtx);
21654e231b3SDenis Bodor 
21754e231b3SDenis Bodor 	for (i = 0; i < nmsgs; i++) {
21854e231b3SDenis Bodor 		pmsg = &msgs[i];
21954e231b3SDenis Bodor 		if (i == 0)
22054e231b3SDenis Bodor 			cmd |= CMD_I2C_IO_BEGIN;
22154e231b3SDenis Bodor 		if (i == nmsgs - 1)
22254e231b3SDenis Bodor 			cmd |= CMD_I2C_IO_END;
22354e231b3SDenis Bodor 
22454e231b3SDenis Bodor 		if ((msgs[i].flags & IIC_M_RD) != 0) {
22554e231b3SDenis Bodor 			if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
22654e231b3SDenis Bodor 							pmsg->len)) != pmsg->len) {
22754e231b3SDenis Bodor 				printf("Read error: got %u\n", ret);
22854e231b3SDenis Bodor 				ret = EIO;
22954e231b3SDenis Bodor 				goto out;
23054e231b3SDenis Bodor 			}
23154e231b3SDenis Bodor 		} else {
23254e231b3SDenis Bodor 			if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
23354e231b3SDenis Bodor 							pmsg->len)) != pmsg->len) {
23454e231b3SDenis Bodor 				printf("Write error: got %u\n", ret);
23554e231b3SDenis Bodor 				ret = EIO;
23654e231b3SDenis Bodor 				goto out;
23754e231b3SDenis Bodor 			}
23854e231b3SDenis Bodor 
23954e231b3SDenis Bodor 		}
24054e231b3SDenis Bodor 		// check status
24154e231b3SDenis Bodor 		if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) {
24254e231b3SDenis Bodor 			ret = EIO;
24354e231b3SDenis Bodor 			goto out;
24454e231b3SDenis Bodor 		}
24554e231b3SDenis Bodor 
24654e231b3SDenis Bodor 		if (pstatus == STATUS_ADDRESS_NAK) {
24754e231b3SDenis Bodor 			ret = EIO;
24854e231b3SDenis Bodor 			goto out;
24954e231b3SDenis Bodor 		}
25054e231b3SDenis Bodor 	}
25154e231b3SDenis Bodor 
25254e231b3SDenis Bodor 	ret = 0;
25354e231b3SDenis Bodor 
25454e231b3SDenis Bodor out:
25554e231b3SDenis Bodor 	mtx_unlock(&sc->sc_mtx);
25654e231b3SDenis Bodor 	return (ret);
25754e231b3SDenis Bodor }
25854e231b3SDenis Bodor 
25954e231b3SDenis Bodor static int
i2ctinyusb_reset(device_t dev,u_char speed,u_char addr,u_char * oldaddr)26054e231b3SDenis Bodor i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
26154e231b3SDenis Bodor {
26254e231b3SDenis Bodor 	struct i2ctinyusb_softc *sc;
26354e231b3SDenis Bodor 	int ret;
26454e231b3SDenis Bodor 
26554e231b3SDenis Bodor 	sc = device_get_softc(dev);
26654e231b3SDenis Bodor 
26754e231b3SDenis Bodor 	mtx_lock(&sc->sc_mtx);
26854e231b3SDenis Bodor 	ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0);
26954e231b3SDenis Bodor 	mtx_unlock(&sc->sc_mtx);
27054e231b3SDenis Bodor 
27154e231b3SDenis Bodor 	if (ret < 0)
27254e231b3SDenis Bodor 		printf("i2ctinyusb_reset error!\n");
27354e231b3SDenis Bodor 
27454e231b3SDenis Bodor 	return (0);
27554e231b3SDenis Bodor }
27654e231b3SDenis Bodor 
27754e231b3SDenis Bodor static device_method_t i2ctinyusb_methods[] = {
27854e231b3SDenis Bodor 	/* Device interface */
27954e231b3SDenis Bodor 	DEVMETHOD(device_probe, i2ctinyusb_probe),
28054e231b3SDenis Bodor 	DEVMETHOD(device_attach, i2ctinyusb_attach),
28154e231b3SDenis Bodor 	DEVMETHOD(device_detach, i2ctinyusb_detach),
28254e231b3SDenis Bodor 
28354e231b3SDenis Bodor 	/* I2C methods */
28454e231b3SDenis Bodor 	DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer),
28554e231b3SDenis Bodor 	DEVMETHOD(iicbus_reset, i2ctinyusb_reset),
28654e231b3SDenis Bodor 	DEVMETHOD(iicbus_callback, iicbus_null_callback),
28754e231b3SDenis Bodor 
28854e231b3SDenis Bodor 	DEVMETHOD_END
28954e231b3SDenis Bodor };
29054e231b3SDenis Bodor 
29154e231b3SDenis Bodor static driver_t i2ctinyusb_driver = {
29254e231b3SDenis Bodor 	.name = "iichb",
29354e231b3SDenis Bodor 	.methods = i2ctinyusb_methods,
29454e231b3SDenis Bodor 	.size = sizeof(struct i2ctinyusb_softc),
29554e231b3SDenis Bodor };
29654e231b3SDenis Bodor 
29754e231b3SDenis Bodor DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL);
29854e231b3SDenis Bodor MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1);
29954e231b3SDenis Bodor MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
30054e231b3SDenis Bodor MODULE_VERSION(i2ctinyusb, 1);
30154e231b3SDenis Bodor 
30254e231b3SDenis Bodor /* vi: set ts=8 sw=8: */
303