untested but finished I2C-Tiny-USB impl

This commit is contained in:
Triss 2021-06-14 03:37:32 +02:00
parent b5e29c1dd7
commit 191c9025e8
10 changed files with 372 additions and 142 deletions

View File

@ -56,10 +56,11 @@ target_sources(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/CMSIS_5/CMSIS/DAP/Firmware/Source/SWO.c
${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}/i2c_tinyusb.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/spi_serprog.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/unique.c
${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_serprog.c
${CMAKE_CURRENT_SOURCE_DIR}/src/i2ctinyusb.c
${CMAKE_CURRENT_SOURCE_DIR}/src/vnd_i2ctinyusb.c
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
${CMAKE_CURRENT_SOURCE_DIR}/src/rtconf.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
@ -80,7 +81,7 @@ target_include_directories(${PROJECT} PUBLIC
if(FAMILY STREQUAL "rp2040")
target_link_libraries(${PROJECT} pico_stdlib pico_unique_id hardware_spi
pico_fix_rp2040_usb_device_enumeration
pico_fix_rp2040_usb_device_enumeration hardware_i2c
tinyusb_device tinyusb_board tinyusb_additions)
if(USE_USBCDC_FOR_STDIO)

45
bsp/rp2040/i2c_tinyusb.c Normal file
View File

@ -0,0 +1,45 @@
#include <pico/stdlib.h>
#include <pico/binary_info.h>
#include <hardware/i2c.h>
#include "protocfg.h"
#include "pinout.h"
#include "i2ctinyusb.h"
__attribute__((__const__))
enum ki2c_funcs i2ctu_get_func(void) {
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
void i2ctu_init(void) {
// default to 100 kHz (SDK example default so should be ok)
i2c_init(PINOUT_I2C_DEV, 100*1000);
gpio_set_function(PINOUT_I2C_SCL, GPIO_FUNC_I2C);
gpio_set_function(PINOUT_I2C_SDA, GPIO_FUNC_I2C);
gpio_pull_up(PINOUT_I2C_SCL);
gpio_pull_up(PINOUT_I2C_SDA);
bi_decl(bi_2pins_with_func(PINOUT_I2C_SCL, PINOUT_I2C_SDA, GPIO_FUNC_I2C));
}
uint32_t i2ctu_set_freq(uint32_t freq) {
return i2c_set_baudrate(PINOUT_I2C_DEV, freq);
}
enum itu_status i2ctu_write(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, const uint8_t* buf, size_t len) {
int rv = i2c_write_timeout_us(PINOUT_I2C_DEV, addr, buf, len,
!(startstopflags & ITU_CMD_I2C_IO_END), 1000*1000);
if (rv < 0) return ITU_STATUS_ADDR_NAK;
return ITU_STATUS_ADDR_ACK;
}
enum itu_status i2ctu_read(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, uint8_t* buf, size_t len) {
int rv = i2c_read_timeout_us(PINOUT_I2C_DEV, addr, buf, len,
!(startstopflags & ITU_CMD_I2C_IO_END), 1000*1000);
if (rv < 0) return ITU_STATUS_ADDR_NAK;
return ITU_STATUS_ADDR_ACK;
}

View File

@ -25,6 +25,11 @@
#define PINOUT_SPI_MISO 12
#define PINOUT_SPI_nCS 13
// I2C config
#define PINOUT_I2C_DEV i2c0
#define PINOUT_I2C_SCL 21
#define PINOUT_I2C_SDA 20
// LED config
// you can change these two as you like

View File

@ -0,0 +1,7 @@
#include "protos.h"
bool stdio_usb_init(void) {
return true;
}

View File

@ -1,4 +1,6 @@
#include "protos.h"
void cdc_uart_init(void) {
}

View File

@ -0,0 +1,25 @@
#include "protocfg.h"
#include "i2ctinyusb.h"
__attribute__((__const__))
enum ki2c_funcs i2ctu_get_func(void) {
return 0;
}
void i2ctu_init(void) {
}
uint32_t i2ctu_set_freq(uint32_t freq) {
return 0;
}
enum itu_status i2ctu_write(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, const uint8_t* buf, size_t len) {
return ITU_STATUS_IDLE;
}
enum itu_status i2ctu_read(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, uint8_t* buf, size_t len) {
return ITU_STATUS_IDLE;
}

View File

@ -11,7 +11,7 @@ module:
kernel:
request reqtype(fix) value index dmalen
i2c_msg:
cmd? type? flags index len
cmd? type? flags addr len
CMD_SET_DELAY:
delayv 0 0
CMD_GET_STATUS: data=[status retval]
@ -108,7 +108,7 @@ USB vendor setup:
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
sets clock period, in flags, unit is microseconds
GET_STATUS command:
return the status register (1 byte)

View File

@ -1,138 +0,0 @@
#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"
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", ' ', ' ', ' ', ' ', ' ');
// ????
uint8_t buf[req->wLength];
bool rv = tud_control_xfer(rhport, req, buf, req->wLength);
printf("rv %c len %04x buf %02x %02x %02x ...\n",
(rv?'t':'f'), req->wLength, buf[0], buf[1], buf[2]);
return rv;
}
}
}
}
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 */

106
src/i2ctinyusb.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef I2CTINYUSB_H_
#define I2CTINYUSB_H_
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "protocfg.h"
enum itu_command {
ITU_CMD_ECHO = 0,
ITU_CMD_GET_FUNC = 1,
ITU_CMD_SET_DELAY = 2,
ITU_CMD_GET_STATUS = 3,
ITU_CMD_I2C_IO_BEGIN_F = (1<<0),
ITU_CMD_I2C_IO_END_F = (1<<1),
ITU_CMD_I2C_IO_DIR_MASK = ITU_CMD_I2C_IO_BEGIN_F | ITU_CMD_I2C_IO_END_F,
ITU_CMD_I2C_IO = 4,
ITU_CMD_I2C_IO_BEGIN = 4 | ITU_CMD_I2C_IO_BEGIN_F,
ITU_CMD_I2C_IO_END = 4 | ITU_CMD_I2C_IO_END_F ,
ITU_CMD_I2C_IO_BEGINEND = 4 | ITU_CMD_I2C_IO_BEGIN_F | ITU_CMD_I2C_IO_END_F,
};
enum itu_status {
ITU_STATUS_IDLE = 0,
ITU_STATUS_ADDR_ACK = 1,
ITU_STATUS_ADDR_NAK = 2
};
// these two are lifted straight from the linux kernel, lmao
enum ki2c_flags {
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 */
};
enum ki2c_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),
};
struct itu_cmd {
uint8_t cmd;
uint16_t flags;
uint16_t addr;
uint16_t len;
};
#ifdef DBOARD_HAS_I2C
__attribute__((__const__))
enum ki2c_funcs i2ctu_get_func(void);
void i2ctu_init(void);
uint32_t i2ctu_set_freq(uint32_t freq); // returns selected frequency, or 0 on error
enum itu_status i2ctu_write(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, const uint8_t* buf, size_t len);
enum itu_status i2ctu_read(enum ki2c_flags flags, enum itu_command startstopflags,
uint16_t addr, uint8_t* buf, size_t len);
#endif
#endif

177
src/vnd_i2ctinyusb.c Normal file
View File

@ -0,0 +1,177 @@
#include "protos.h"
#ifdef DBOARD_HAS_I2C
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "tusb.h"
#include "device/usbd_pvt.h"
#include "i2ctinyusb.h"
static uint8_t itf_num;
static enum itu_status state;
static struct itu_cmd curcmd;
static void iub_init(void) {
state = ITU_STATUS_IDLE;
memset(&curcmd, 0, sizeof curcmd);
i2ctu_init();
}
static void iub_reset(uint8_t rhport) {
state = ITU_STATUS_IDLE;
memset(&curcmd, 0, sizeof curcmd);
i2ctu_init();
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;
}
static bool iub_ctl_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const* req) {
if (stage == CONTROL_STAGE_DATA) {
// TODO: see also the TODO below
return true;
}
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 ITU_CMD_ECHO: { // flags to be echoed back, addr unused, len=2
if (req->wLength != 2) return false; // bad length -> let's stall
uint8_t rv[2];
rv[0] = req->wValue&0xff;
rv[1] = (req->wValue>>8)&0xff;
return tud_control_xfer(rhport, req, rv, sizeof rv);
}
break;
case ITU_CMD_GET_FUNC: { // flags unused, addr unused, len=4
if (req->wLength != 0) return false;
const uint32_t func = i2ctu_get_func();
uint8_t rv[4];
rv[0]=func&0xff;
rv[1]=(func>>8)&0xff;
rv[2]=(func>>16)&0xff;
rv[3]=(func>>24)&0xff;
return tud_control_xfer(rhport, req, rv, sizeof rv);
}
break;
case ITU_CMD_SET_DELAY: { // flags=delay, addr unused, len=0
if (req->wLength != 0) return false;
uint32_t us = req->wValue ? req->wValue : 1;
uint32_t freq = 1000*1000 / us;
if (i2ctu_set_freq(freq) != 0) // returned an ok frequency
return tud_control_status(rhport, req);
else return false;
}
break;
case ITU_CMD_GET_STATUS: { // flags unused, addr unused, len=1
if (req->wLength != 1) return false;
uint8_t rv = state;
return tud_control_xfer(rhport, req, &rv, 1);
}
break;
case ITU_CMD_I2C_IO: // flags: ki2c_flags
case ITU_CMD_I2C_IO_BEGIN: // addr: I2C address
case ITU_CMD_I2C_IO_END: // len: transfer size
case ITU_CMD_I2C_IO_BEGINEND: { // (transfer dir is in flags)
struct itu_cmd cmd;
cmd.cmd = req->bRequest;
cmd.flags = req->wValue;
cmd.addr = req->wIndex;
cmd.len = req->wLength;
curcmd = cmd;
// TODO: what's the max value of wLength? does this need
// to be handled separately in the data stage as well?
// will the entire thing be read into one big chunk, or
// does it also get split up into buffers of eg. 64 bytes?
uint8_t buf[cmd.len];
if (cmd.flags & I2C_M_RD) { // read from I2C device
state = i2ctu_read(cmd.flags, cmd.cmd & ITU_CMD_I2C_IO_DIR_MASK,
cmd.addr, buf, sizeof buf);
return tud_control_xfer(rhport, req, buf, cmd.len);
} else { // write
bool rv = tud_control_xfer(rhport, req, buf, cmd.len);
if (rv) {
state = i2ctu_write(cmd.flags, cmd.cmd & ITU_CMD_I2C_IO_DIR_MASK,
cmd.addr, buf, sizeof buf);
}
return rv;
}
}
break;
default:
printf("I2C-Tiny-USB: unknown command %02x\n", req->bRequest);
return false; // unknown!
}
} else {
printf("I2C-Tiny-USB: bad request type %02x\n", req->bmRequestType);
return false; // not a vendor command? no clue what to do with it!
}
}
// never actually called fsr
static bool iub_xfer(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t 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 */