WIP i2c-tiny-usb stuff

This commit is contained in:
Triss 2021-06-13 18:47:46 +02:00
parent 4de8c00613
commit c6fdc53fdd
16 changed files with 776 additions and 36 deletions

View File

@ -46,8 +46,6 @@ if(FAMILY STREQUAL "rp2040")
pico_enable_stdio_usb(${PROJECT} 0)
target_sources(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/main.c
${CMAKE_CURRENT_SOURCE_DIR}/usb_descriptors.c
${CMAKE_CURRENT_SOURCE_DIR}/libco/libco.S
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/DAP/Firmware/Source/DAP.c
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/DAP/Firmware/Source/JTAG_DP.c
@ -56,11 +54,14 @@ if(FAMILY STREQUAL "rp2040")
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/DAP/Firmware/Source/SW_DP.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/cdc_uart.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/spi_serprog.c
${CMAKE_CURRENT_SOURCE_DIR}/cdc_serprog.c
${CMAKE_CURRENT_SOURCE_DIR}/rtconf.c
${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_serprog.c
${CMAKE_CURRENT_SOURCE_DIR}/src/i2ctinyusb.c
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
${CMAKE_CURRENT_SOURCE_DIR}/src/rtconf.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
)
target_include_directories(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/
${CMAKE_CURRENT_SOURCE_DIR}/src/
${CMAKE_CURRENT_SOURCE_DIR}/libco/
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/DAP/Firmware/Include/
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/Core/Include/

View File

@ -157,6 +157,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC)
separate mode that temporarily disables all other IO protocols
- [x] UART with CTS/RTS flow control
- [x] Needs configurable stuff as well, as some UART interfaces won't use this.
- [ ] Second UART port for when stdio UART is disabled?
- [x] Debug interface to send printf stuff directly to USB, instead of having
- to use the UART interface as a loopback thing.
- [ ] I2C support by emulating the I2C Tiny USB
@ -175,16 +176,16 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC)
- https://github.com/derekmulcahy/xvcpi
- OpenOCD as XVC client??
- [ ] Maybe use the ADCs for something?
- [ ] SD/MMC/SDIO (will be a pain)
- [ ] SUMP logic analyzer?????
- see also [this](https://github.com/perexg/picoprobe-sump)
- [ ] AVR programming (USBavr emulation?)
- AVR ISP is hardly used anymore
- TPI/UPDI requires 5V levels, Pico doesn't do that :/
- debugWIRE????
- Renesas E7-{0,1,2} programming thing????
- Renesas tell us how this works pls
- Maybe steal other features from the Bus Pirate or Glasgow or so
- 1-wire? Never seen this one in the wild
- MIDI? Feels mostly gimmicky...
- PS/2? Hmmmm idk
- HD44780 LCD? See MIDI
- CAN? If I'd first be able to find a CAN device to test it with, sure
- Maybe steal other features from the Bus Pirate, [HydraBus](https://github.com/hydrabus/hydrafw) or Glasgow or so
- 1-wire and 3-wire? Never seen this one in the wild
- CAN? LIN? If I'd first be able to find a CAN device to test it with, sure

View File

@ -5,11 +5,12 @@
#define DBOARD_HAS_UART
#define DBOARD_HAS_CMSISDAP
#define DBOARD_HAS_SERPROG
/*#define DBOARD_HAS_TINYI2C*/
#define DBOARD_HAS_I2C
#define HID_N_CMSISDAP 0
#define CDC_N_UART 0
#define CDC_N_SERPROG 1
#define VND_N_I2CTINYUSB 0
#ifdef USE_USBCDC_FOR_STDIO
#define CDC_N_STDIO 2

View File

@ -0,0 +1,8 @@
obj-m := i2c-tiny-usb.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules

View File

@ -0,0 +1,156 @@
all multibyte words are little-endian
--
USB interface is a vendor interface, with an in and an out endpoint
--
module:
cmd value index len
kernel:
request reqtype(fix) value index dmalen
i2c_msg:
cmd? type? flags index len
CMD_SET_DELAY:
delayv 0 0
CMD_GET_STATUS: data=[status retval]
0 0 1
i2c_msg:
type: byte
cmd: byte
flags: ushort
addr: ushort
len: ushort
data: byte[len]
commands:
CMD_ECHO = 0
CMD_GET_FUNC = 1
CMD_SET_DELAY = 2
CMD_GET_STATUS = 3
CMD_I2C_IO = 4
CMD_I2C_IO_W_BEGIN = 5 = CMD_I2C_IO | CMD_I2C_IO_BEGIN
CMD_I2C_IO_W_END = 6 = CMD_I2C_IO | CMD_I2C_IO_END
CMD_I2C_IO_W_BEGINEND = 7 = CMD_I2C_IO | CMD_I2C_IO_BEGIN | CMD_I2C_IO_END
CMD_I2C_IO_BEGIN = 1<<0 // the beginning of the I2C transaction: do a start cond
CMD_I2C_IO_END = 1<<1 // the end of the I2C transaction: do an end cond
// if none of the above two: repeated start condition anyway?
statuses:
STATUS_IDLE = 0
STATUS_ADDRESS_ACK = 1
STATUS_DDRESS_NAK = 2
flags: (literally just from the kernel)
I2C_M_RD 0x0001 /* guaranteed to be 0x0001! */
I2C_M_TEN 0x0010 /* use only if I2C_FUNC_10BIT_ADDR */
I2C_M_DMA_SAFE 0x0200 /* use only in kernel space */
I2C_M_RECV_LEN 0x0400 /* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */
I2C_M_NO_RD_ACK 0x0800 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
I2C_M_IGNORE_NAK 0x1000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
I2C_M_REV_DIR_ADDR 0x2000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
I2C_M_NOSTART 0x4000 /* use only if I2C_FUNC_NOSTART */
I2C_M_STOP 0x8000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
funcs:
* I2C_FUNC_I2C 0x00000001
I2C_FUNC_10BIT_ADDR 0x00000002 /* required for I2C_M_TEN */
I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* required for I2C_M_IGNORE_NAK etc. */
I2C_FUNC_SMBUS_PEC 0x00000008
I2C_FUNC_NOSTART 0x00000010 /* required for I2C_M_NOSTART */
I2C_FUNC_SLAVE 0x00000020
I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 or later */
* I2C_FUNC_SMBUS_QUICK 0x00010000
* I2C_FUNC_SMBUS_READ_BYTE 0x00020000
* I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000
* I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000
* I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000
* I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000
* I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000
* I2C_FUNC_SMBUS_PROC_CALL 0x00800000
I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 /* required for I2C_M_RECV_LEN */
* I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
* I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */
* I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */
* I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 or later */
* I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 or later */
* I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE)
* I2C_FUNC_SMBUS_BYTE_DATA (I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)
* I2C_FUNC_SMBUS_WORD_DATA (I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_WRITE_WORD_DATA)
I2C_FUNC_SMBUS_BLOCK_DATA (I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)
* I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)
* I2C_FUNC_SMBUS_EMUL (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | \
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | \
I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \
I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_PEC)
/* if I2C_M_RECV_LEN is also supported */
I2C_FUNC_SMBUS_EMUL_ALL (I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
I2C_FUNC_SMBUS_BLOCK_PROC_CALL)
USB vendor setup:
state machine: 3 8-bit registers: status, cmd, len
status is STATUS_ADDRESS_IDLE at reset
cmd, len are reset to 0
ECHO command:
send back flags bytes
GET_FUNC command:
send back supported functions: 4-byte little-endian of the above flags
SET_DELAY command:
sets clock period, in flags, unit is half a microsecond
GET_STATUS command:
return the status register (1 byte)
I2C_IO command: I2C xfers:
actual CMD_I2C_IO stuff:
do start cond
send (device addr << 1) | (I2C_M_RD?1:0) (aka set device address and read/write mode)
if ack bit received from previous step
status = STATUS_ADDRESS_ACK
save cmd and len in state regs
if CMD_I2C_IO_END set in cmd AND len is 0:
do end cond
else
status = STATUS_ADDRESS_NAK
do end cond
USB vendor read:
if status == STATUS_ADDRESS_ACK:
if number of bytes in this block larger than bytes expected:
clamp this length, only use the number of expected bytes in the block
read bytes, decrease len reg by number of bytes read
(if last byte read, set NAK in I2C xfer, else ACK)
if CMD_I2C_IO_END set in cmd reg AND len reg is 0:
do end cond
return bytes read over USB
else:
do nothing, return zeros
USB vendor write:
if status == STATUS_ADDRESS_ACK:
if number of bytes in this block larger than bytes expected:
clamp this length, only use the number of expected bytes in the block
write bytes, decrease len reg by number of bytes written
(if no ack bit returned, maybe set an error state??)
if CMD_I2C_IO_END set in cmd reg AND len reg is 0:
do end cond
echo back bytes written
else:
do nothing, return zeros

View File

@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* driver for the i2c-tiny-usb adapter - 1.0
* http://www.harbaum.org/till/i2c_tiny_usb
*
* Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org)
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/types.h>
/* include interfaces to usb layer */
#include <linux/usb.h>
/* include interface to i2c layer */
#include <linux/i2c.h>
/* commands via USB, must match command ids in the firmware */
#define CMD_ECHO 0
#define CMD_GET_FUNC 1
#define CMD_SET_DELAY 2
#define CMD_GET_STATUS 3
#define CMD_I2C_IO 4
#define CMD_I2C_IO_BEGIN (1<<0)
#define CMD_I2C_IO_END (1<<1)
/* i2c bit delay, default is 10us -> 100kHz max
(in practice, due to additional delays in the i2c bitbanging
code this results in a i2c clock of about 50kHz) */
static unsigned short delay = 10;
module_param(delay, ushort, 0);
MODULE_PARM_DESC(delay, "bit delay in microseconds "
"(default is 10us for 100kHz max)");
static int usb_read(struct i2c_adapter *adapter, int cmd,
int value, int index, void *data, int len);
static int usb_write(struct i2c_adapter *adapter, int cmd,
int value, int index, void *data, int len);
/* ----- begin of i2c layer ---------------------------------------------- */
#define STATUS_IDLE 0
#define STATUS_ADDRESS_ACK 1
#define STATUS_ADDRESS_NAK 2
static int usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
{
unsigned char *pstatus;
struct i2c_msg *pmsg;
int i, ret, r;
dev_dbg(&adapter->dev, "master xfer %d messages:\n", num);
pstatus = kmalloc(sizeof(*pstatus), GFP_KERNEL);
if (!pstatus)
return -ENOMEM;
for (i = 0 ; i < num ; i++) {
int cmd = CMD_I2C_IO;
if (i == 0)
cmd |= CMD_I2C_IO_BEGIN;
if (i == num-1)
cmd |= CMD_I2C_IO_END;
pmsg = &msgs[i];
dev_dbg(&adapter->dev,
" %d: %s (flags %d) %d bytes to 0x%02x\n",
i, pmsg->flags & I2C_M_RD ? "read" : "write",
pmsg->flags, pmsg->len, pmsg->addr);
/* and directly send the message */
if (pmsg->flags & I2C_M_RD) {
/* read data */
if ((r = usb_read(adapter, cmd,
pmsg->flags, pmsg->addr,
pmsg->buf, pmsg->len)) != pmsg->len) {
dev_err(&adapter->dev,
"failure reading data: %i\n", r);
ret = -EIO;
goto out;
}
} else {
/* write data */
if ((r = usb_write(adapter, cmd,
pmsg->flags, pmsg->addr,
pmsg->buf, pmsg->len)) != pmsg->len) {
dev_err(&adapter->dev,
"failure writing data: %i\n", r);
ret = -EIO;
goto out;
}
}
/* read status */
if ((r = usb_read(adapter, CMD_GET_STATUS, 0, 0, pstatus, 1)) != 1) {
dev_err(&adapter->dev, "failure reading status: %i\n", r);
ret = -EIO;
goto out;
}
dev_dbg(&adapter->dev, " status = %d\n", *pstatus);
if (*pstatus == STATUS_ADDRESS_NAK) {
ret = -ENXIO;
goto out;
}
}
ret = i;
out:
kfree(pstatus);
return ret;
}
static u32 usb_func(struct i2c_adapter *adapter)
{
__le32 *pfunc;
u32 ret;
int i=-1;
pfunc = kmalloc(sizeof(*pfunc), GFP_KERNEL);
/* get functionality from adapter */
if (!pfunc || (i=usb_read(adapter, CMD_GET_FUNC, 0, 0, pfunc,
sizeof(*pfunc))) != sizeof(*pfunc)) {
dev_err(&adapter->dev, "failure reading functionality: %i\n");
ret = 0;
goto out;
}
ret = le32_to_cpup(pfunc);
dev_warn(&adapter->dev, "itu func=%08x\n", ret);
out:
kfree(pfunc);
return ret;
}
/* This is the actual algorithm we define */
static const struct i2c_algorithm usb_algorithm = {
.master_xfer = usb_xfer,
.functionality = usb_func,
};
/* ----- end of i2c layer ------------------------------------------------ */
/* ----- begin of usb layer ---------------------------------------------- */
/*
* Initially the usb i2c interface uses a vid/pid pair donated by
* Future Technology Devices International Ltd., later a pair was
* bought from EZPrototypes
*/
static const struct usb_device_id i2c_tiny_usb_table[] = {
{ USB_DEVICE(0x0403, 0xc631) }, /* FTDI */
{ USB_DEVICE(0x1c40, 0x0534) }, /* EZPrototypes */
{ USB_DEVICE_INTERFACE_CLASS(0xcafe, 0x4017, 0/*255*/) }, /* TinyUSB DapperMime: we want the Vendor interface */
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, i2c_tiny_usb_table);
/* Structure to hold all of our device specific stuff */
struct i2c_tiny_usb {
struct usb_device *usb_dev; /* the usb device for this device */
struct usb_interface *interface; /* the interface for this device */
struct i2c_adapter adapter; /* i2c related things */
bool is_tusb_dmime;
};
static int usb_read(struct i2c_adapter *adapter, int cmd,
int value, int index, void *data, int len)
{
struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data;
uint8_t *dmadata;
int ret, actual_len = 0;
if (dev->is_tusb_dmime) {
// actual_len = len;
// if (actual_len < 7) actual_len = 7; /* cmd(1), value(2), index(2), len(2) */
//
// dmadata = (uint8_t*)kzalloc(actual_len, GFP_KERNEL);
// if (!dmadata)
// return -ENOMEM;
//
// dmadata[0] = cmd;
// dmadata[1] = value & 0xff;
// dmadata[2] = (value >> 8) & 0xff;
// dmadata[3] = index & 0xff;
// dmadata[4] = (index >> 8) & 0xff;
// dmadata[5] = len & 0xff;
// dmadata[6] = (len >> 8) & 0xff;
//
// /* send command */
// ret = usb_bulk_msg(dev->usb_dev, usb_sndbulkpipe(dev->usb_dev, 9),
// dmadata, 7, &actual_len, 2000);
//
// dev_warn(&dev->usb_dev->dev, "read bulk msg retval=%i\n", ret);
//
// /* command received correctly */
// if (ret == 7) {
// actual_len = 0;
// /* receive reply */
// ret = usb_bulk_msg(dev->usb_dev, usb_rcvbulkpipe(dev->usb_dev, 9),
// dmadata, len, &actual_len, 2000);
//
// memcpy(data, dmadata, len);
// }
dmadata = kmalloc(len, GFP_KERNEL);
if (!dmadata)
return -ENOMEM;
/* do control transfer */
ret = usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0),
cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN,
value, index, dmadata, len, 2000);
memcpy(data, dmadata, len);
} else {
dmadata = kmalloc(len, GFP_KERNEL);
if (!dmadata)
return -ENOMEM;
/* do control transfer */
ret = usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0),
cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN,
value, index, dmadata, len, 2000);
memcpy(data, dmadata, len);
}
kfree(dmadata);
return ret;
}
static int usb_write(struct i2c_adapter *adapter, int cmd,
int value, int index, void *data, int len)
{
struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data;
uint8_t *dmadata;
int ret, actual_len = 0;
if (dev->is_tusb_dmime) {
// dmadata = (uint8_t*)kzalloc(len + 7, GFP_KERNEL); /* cmd(1), value(2), index(2), len(2) */
// if (!dmadata)
// return -ENOMEM;
//
// dmadata[0] = cmd;
// dmadata[1] = value & 0xff;
// dmadata[2] = (value >> 8) & 0xff;
// dmadata[3] = index & 0xff;
// dmadata[4] = (index >> 8) & 0xff;
// dmadata[5] = len & 0xff;
// dmadata[6] = (len >> 8) & 0xff;
//
// if (data && len)
// memcpy(&dmadata[7], data, len);
//
// /* send command data */
// ret = usb_bulk_msg(dev->usb_dev, usb_sndbulkpipe(dev->usb_dev, 9),
// dmadata, len+7, &actual_len, 2000);
dmadata = (uint8_t*)kmemdup(data, len, GFP_KERNEL);
if (!dmadata)
return -ENOMEM;
/* do control transfer */
ret = usb_control_msg(dev->usb_dev, usb_sndctrlpipe(dev->usb_dev, 0),
cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
value, index, dmadata, len, 2000);
} else {
dmadata = (uint8_t*)kmemdup(data, len, GFP_KERNEL);
if (!dmadata)
return -ENOMEM;
/* do control transfer */
ret = usb_control_msg(dev->usb_dev, usb_sndctrlpipe(dev->usb_dev, 0),
cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
value, index, dmadata, len, 2000);
}
kfree(dmadata);
return ret;
}
static void i2c_tiny_usb_free(struct i2c_tiny_usb *dev)
{
usb_put_dev(dev->usb_dev);
kfree(dev);
}
static int i2c_tiny_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct i2c_tiny_usb *dev;
int retval = -ENOMEM;
u16 version;
dev_dbg(&interface->dev, "probing usb device\n");
/* allocate memory for our device state and initialize it */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
dev_err(&interface->dev, "Out of memory\n");
goto error;
}
dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
/* TinyUSB DapperMime needs different treatment because it has MANY endpoints */
dev->is_tusb_dmime = le16_to_cpu(dev->usb_dev->descriptor.idVendor ) == 0xcafe
&& le16_to_cpu(dev->usb_dev->descriptor.idProduct) == 0x4017;
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
version = le16_to_cpu(dev->usb_dev->descriptor.bcdDevice);
dev_info(&interface->dev,
"version %x.%02x found at bus %03d address %03d\n",
version >> 8, version & 0xff,
dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
/* setup i2c adapter description */
dev->adapter.owner = THIS_MODULE;
dev->adapter.class = I2C_CLASS_HWMON;
dev->adapter.algo = &usb_algorithm;
dev->adapter.algo_data = dev;
snprintf(dev->adapter.name, sizeof(dev->adapter.name),
"i2c-tiny-usb at bus %03d device %03d",
dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
if (usb_write(&dev->adapter, CMD_SET_DELAY, delay, 0, NULL, 0) != 0) {
dev_err(/*&dev->adapter.dev*/ &dev->usb_dev->dev, /* adapter.dev is null at this point */
"failure setting delay to %dus\n", delay);
retval = -EIO;
goto error;
}
dev->adapter.dev.parent = &dev->interface->dev;
/* and finally attach to i2c layer */
i2c_add_adapter(&dev->adapter);
/* inform user about successful attachment to i2c layer */
dev_info(&dev->adapter.dev, "connected i2c-tiny-usb device\n");
return 0;
error:
if (dev)
i2c_tiny_usb_free(dev);
return retval;
}
static void i2c_tiny_usb_disconnect(struct usb_interface *interface)
{
struct i2c_tiny_usb *dev = usb_get_intfdata(interface);
i2c_del_adapter(&dev->adapter);
usb_set_intfdata(interface, NULL);
i2c_tiny_usb_free(dev);
dev_dbg(&interface->dev, "disconnected\n");
}
static struct usb_driver i2c_tiny_usb_driver = {
.name = "i2c-tiny-usb",
.probe = i2c_tiny_usb_probe,
.disconnect = i2c_tiny_usb_disconnect,
.id_table = i2c_tiny_usb_table,
};
module_usb_driver(i2c_tiny_usb_driver);
/* ----- end of usb layer ------------------------------------------------ */
MODULE_AUTHOR("Till Harbaum <Till@Harbaum.org>");
MODULE_DESCRIPTION("i2c-tiny-usb driver v1.0");
MODULE_LICENSE("GPL");

135
src/i2ctinyusb.c Normal file
View File

@ -0,0 +1,135 @@
#include "protocfg.h"
#ifdef DBOARD_HAS_I2C
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "tusb.h"
#include "device/usbd_pvt.h"
#include "protos.h"
#include "thread.h"
static uint8_t itf_num;
static void iub_init(void) {
//printf("i2c init\n");
}
static void iub_reset(uint8_t rhport) {
//printf("i2c reset %02x\n", rhport);
itf_num = 0;
}
static uint16_t iub_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc,
uint16_t max_len) {
TU_VERIFY(itf_desc->bInterfaceClass == 0
&& itf_desc->bInterfaceSubClass == 0
&& itf_desc->bInterfaceProtocol == 0, 0);
const uint16_t drv_len = sizeof(tusb_desc_interface_t);
TU_VERIFY(max_len >= drv_len, 0);
itf_num = itf_desc->bInterfaceNumber;
return drv_len;
}
extern char msgbuf[256];
extern volatile bool msgflag;
char msgbuf[256] = {0};
volatile bool msgflag = false;
//, b, c, d, e ,f
#define printf(fmt, a, b, c, d, e) do { \
/*while (msgflag) ;*/\
snprintf(msgbuf, sizeof msgbuf, fmt, a, b, c, d, e);\
msgbuf[sizeof msgbuf - 1] = 0; \
msgflag = true;\
} while (0);\
static bool iub_ctl_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const* req) {
if (stage != CONTROL_STAGE_SETUP) return true;
/*printf("ctl req rhport=%02x, stage=%02x, wIndex=%04x, bReq=%02x, wValue=%04x\n",
rhport, stage,
req->wIndex, req->bRequest, req->wValue);*/
if (req->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR) {
switch (req->bRequest) {
case 1: // get func
{
const uint32_t flags = 0xceff0001;
uint8_t rv[4];
rv[0]=flags&0xff;
rv[1]=(flags>>8)&0xff;
rv[2]=(flags>>16)&0xff;
rv[3]=(flags>>24)&0xff;
return tud_control_xfer(rhport, req, rv, sizeof rv);
}
case 2: // set delay
return tud_control_status(rhport, req);
case 3: // get status
{
uint8_t rv = 0; // idle
return tud_control_xfer(rhport, req, &rv, 1);
}
case 4: case 5: case 6: case 7: // I2C_IO
{
if (req->wValue & 1) { // read: we need to return shit
printf("read!%c%c%c%c%c\n", ' ', ' ', ' ', ' ', ' ');
// so, we'll return some garbage
uint8_t buf[req->wLength];
return tud_control_xfer(rhport, req, buf, req->wLength);
} else {
//printf("write!%c%c%c%c%c\n", ' ', ' ', ' ', ' ', ' ');
// ????
return tud_control_status(rhport, req);
}
}
}
}
return false; // unk
}
// never actually called
static bool iub_xfer(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
printf("xfer cb! rh=%02x ep=%02x res=%02x len=%08x%c\n",
rhport, ep_addr, result, xferred_bytes, ' ');
return true;
}
// interfacing stuff for TinyUSB API, actually defines the driver
static usbd_class_driver_t const i2ctinyusb_driver = {
#if CFG_TUSB_DEBUG >= 2
.name = "i2c-tiny-usb",
#endif
.init = iub_init,
.reset = iub_reset,
.open = iub_open,
.control_xfer_cb = iub_ctl_req,
.xfer_cb = iub_xfer,
.sof = NULL
};
usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) {
*driver_count = 1;
return &i2ctinyusb_driver;
}
// we need to implement this one, because tinyusb uses hardcoded stuff for
// endpoint 0, which is what the i2c-tiny-usb kernel module uses
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, tusb_control_request_t const* req) {
return iub_ctl_req(rhport, ep_addr, req);
}
#endif /* DBOARD_HAS_I2C */

View File

@ -52,6 +52,9 @@ static cothread_t mainthread
#ifdef DBOARD_HAS_SERPROG
, serprogthread
#endif
#ifdef DBOARD_HAS_I2C
//, ituthread
#endif
;
void thread_yield(void) {
@ -80,18 +83,41 @@ static void serprog_thread_fn(void) {
}
#endif
#ifdef DBOARD_HAS_I2C
/*static void itu_thread_fn(void) {
itu_init();
thread_yield();
while (1) {
itu_task();
thread_yield();
}
}*/
#endif
#ifdef DBOARD_HAS_UART
static uint8_t uartstack[4096];
#endif
#ifdef DBOARD_HAS_UART
#ifdef DBOARD_HAS_SERPROG
static uint8_t serprogstack[4096];
#endif
#ifdef DBOARD_HAS_I2C
static uint8_t itustack[4096];
#endif
extern uint32_t co_active_buffer[64];
uint32_t co_active_buffer[64];
extern cothread_t co_active_handle;
cothread_t co_active_handle;
extern char msgbuf[256];
extern volatile bool msgflag;
static void domsg(void) {
if (msgflag) {
printf("%s", msgbuf);
//msgflag = false;
}
}
int main(void)
{
mainthread = co_active();
@ -104,15 +130,17 @@ int main(void)
board_init();
#ifdef DBOARD_HAS_UART
//cdc_uart_init();
uartthread = co_derive(uartstack, sizeof uartstack, uart_thread_fn);
co_switch(uartthread); // will call cdc_uart_init() on correct thread
#endif
#ifdef DBOARD_HAS_SERPROG
//cdc_serprog_init();
serprogthread = co_derive(serprogstack, sizeof serprogstack, serprog_thread_fn);
co_switch(serprogthread); // will call cdc_serprog_init() on correct thread
#endif
#ifdef DBOARD_HAS_I2C
//ituthread = co_derive(itustack, sizeof itustack, itu_thread_fn);
//co_switch(ituthread);
#endif
#ifdef DBOARD_HAS_CMSISDAP
DAP_Setup();
#endif
@ -127,19 +155,27 @@ int main(void)
{
//printf("hi\n");
domsg();
tud_task(); // tinyusb device task
#ifdef DBOARD_HAS_UART
//cdc_uart_task();
co_switch(uartthread);
#endif
domsg();
tud_task(); // tinyusb device task
#ifdef DBOARD_HAS_SERPROG
//cdc_serprog_task();
co_switch(serprogthread);
#endif
//printf("hi\n");
//domsg();
//tud_task();
#ifdef DBOARD_HAS_I2C
//co_switch(ituthread);
#endif
//printf("hi2\n");
}
return 0;

View File

@ -19,10 +19,15 @@ void cdc_serprog_task(void);
#endif
#ifdef USE_USBCDC_FOR_STDIO
//#ifndef PICO_BOARD
//#ifdef PICO_BOARD
bool stdio_usb_init(void);
//#endif
#endif
#ifdef DBOARD_HAS_I2C
void itu_init(void);
void itu_task(void);
#endif
#endif

View File

@ -115,6 +115,8 @@
// CDC FIFO size of TX and RX
#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#ifdef __cplusplus
}

View File

@ -28,6 +28,7 @@
#include "protocfg.h"
// TODO: actually do this properly
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
@ -103,31 +104,28 @@ uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance)
enum
{
#ifdef DBOARD_HAS_CMSISDAP
ITF_NUM_HID_CMSISDAP,
#endif
#ifdef DBOARD_HAS_UART
ITF_NUM_CDC_UART_COM,
ITF_NUM_CDC_UART_DATA,
#endif
#ifdef DBOARD_HAS_SERPROG
ITF_NUM_CDC_SERPROG_COM,
ITF_NUM_CDC_SERPROG_DATA,
#endif
#ifdef USE_USBCDC_FOR_STDIO
ITF_NUM_CDC_STDIO_COM,
ITF_NUM_CDC_STDIO_DATA,
#endif
ITF_NUM_VND_I2CTINYUSB,
ITF_NUM_TOTAL
};
/*#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_INOUT_DESC_LEN)*/
#define TUD_I2CTINYUSB_LEN (9)
#define TUD_I2CTINYUSB_DESCRIPTOR(_itfnum, _stridx) \
9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, 0, 0, 0, _stridx \
static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN
/*enum {*/ static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN
#ifdef DBOARD_HAS_I2C
+ TUD_I2CTINYUSB_LEN
//+ TUD_VENDOR_DESC_LEN
#endif
#ifdef DBOARD_HAS_UART
+ TUD_CDC_DESC_LEN
#endif
@ -140,7 +138,7 @@ static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN
#ifdef USE_USBCDC_FOR_STDIO
+ TUD_CDC_DESC_LEN
#endif
;
/*}*/;
#define EPNUM_CDC_UART_OUT 0x02 // 2
#define EPNUM_CDC_UART_IN 0x82 // 83
@ -152,23 +150,32 @@ static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN
#define EPNUM_CDC_STDIO_OUT 0x07
#define EPNUM_CDC_STDIO_IN 0x87
#define EPNUM_CDC_STDIO_NOTIF 0x88
#define EPNUM_VND_I2C_OUT 0x09
#define EPNUM_VND_I2C_IN 0x89
// NOTE: if you modify this table, don't forget to keep tusb_config.h up to date as well!
// TODO: maybe add some strings to all these interfaces
uint8_t const desc_configuration[] =
{
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
#ifdef DBOARD_HAS_UART
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_UART_COM, 0, EPNUM_CDC_UART_NOTIF, 64, EPNUM_CDC_UART_OUT, EPNUM_CDC_UART_IN, 64),
#endif
#ifdef DBOARD_HAS_CMSISDAP
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID_CMSISDAP, 0, 0/*HID_PROTOCOL_NONE*/, sizeof(desc_hid_report), EPNUM_HID_CMSISDAP, 0x80 | (EPNUM_HID_CMSISDAP+0), CFG_TUD_HID_EP_BUFSIZE, 1),
#endif
#ifdef DBOARD_HAS_I2C
// ???
TUD_I2CTINYUSB_DESCRIPTOR(ITF_NUM_VND_I2CTINYUSB, 0),
//TUD_VENDOR_DESCRIPTOR(ITF_NUM_VND_I2CTINYUSB, 0, EPNUM_VND_I2C_OUT, EPNUM_VND_I2C_IN, 64),
#endif
#ifdef DBOARD_HAS_UART
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_UART_COM, 0, EPNUM_CDC_UART_NOTIF, 64, EPNUM_CDC_UART_OUT, EPNUM_CDC_UART_IN, 64),
#endif
#ifdef DBOARD_HAS_SERPROG
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_SERPROG_COM, 0, EPNUM_CDC_SERPROG_NOTIF, 64, EPNUM_CDC_SERPROG_OUT, EPNUM_CDC_SERPROG_IN, 64),