/* * i2c-tiny-usb protocol (Linux drivers/i2c/busses/i2c-tiny-usb.c) on EP0 vendor requests. * * bReq 0 ECHO IN 2 B: wValue echoed back * bReq 1 GET_FUNC IN 4 B: I2C functionality bits, little endian * bReq 2 SET_DELAY OUT 0 B: wValue = SCL half period in us (kernel default 10 = 100 kHz) * bReq 3 GET_STATUS IN 1 B: 0 idle, 1 address ACK, 2 address NAK * bReq 4 I2C_IO (+1 BEGIN: START, +2 END: STOP; neither: repeated START) * wValue = i2c_msg flags (bit0 = read), wIndex = 7-bit address, * data stage = message payload (OUT for write, IN for read), any length. * * The kernel sends one I2C_IO per i2c_msg followed by GET_STATUS. Address NAK is * reported via GET_STATUS (-> -ENXIO); data NAK or bus error STALLs the request (-> -EIO). * While a transaction is open, the CDC side is held off (see tiny_busy()). */ #include #include "usb.h" #include "i2c.h" #include "tiny.h" #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_IO_BEGIN 1 #define CMD_IO_END 2 #define STATUS_IDLE 0 #define STATUS_ACK 1 #define STATUS_NAK 2 #define I2C_M_RD 0x0001 #define I2C_M_TEN 0x0010 /* I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL */ static const uint8_t func[4] PROGMEM = { 0x09, 0x00, 0xFF, 0x0E }; static uint8_t status; static bool open; /* START sent, no STOP yet */ static uint8_t idle_ticks; static uint8_t k_delay = 50; /* own SCL setting; kernel default 10 us -> 50 */ bool tiny_busy(void) { return open; } static void close_bus(void) { if (open) i2c_stop(); open = false; } void usb_reset_hook(void) { close_bus(); status = STATUS_IDLE; } void tiny_tick(void) /* ~16 ms: drop a transaction the host abandoned */ { if (open && ++idle_ticks > 6) close_bus(); } /* * SCL half period in us -> i2c.c delay units, never faster than asked. The * period is 38 + 6 * units cycles (40 at 0), so 0 and 1 us both give the * 400 kHz maximum, 2 us 235 kHz and the driver's default 10 us 50 kHz. */ static uint8_t us_to_delay(uint16_t us) { if (us <= 1) return 0; if (us >= 49) return 255; return (uint8_t)((32 * us - 38 + 5) / 6); } static void i2c_io(uint8_t cmd, bool usb_in, uint16_t flags, uint8_t addr, uint16_t len) { bool rd = flags & I2C_M_RD; if (rd != usb_in || (flags & I2C_M_TEN)) { ep0_stall(); return; } bool repeated = open && !(cmd & CMD_IO_BEGIN); if (!repeated) i2c_set_delay(k_delay); /* the CDC side has its own speed */ open = true; uint8_t st = i2c_start((uint8_t)(addr << 1) | rd, repeated); if (st == I2C_BUS_ERR) goto fail; status = (st == I2C_OK) ? STATUS_ACK : STATUS_NAK; bool nak = st != I2C_OK; /* still complete the data stage, the driver checks status */ uint8_t buf[EP0_SIZE]; if (!rd) { while (len) { uint8_t n = ep0_rx(buf); if (n == 0xFF) goto abort; if (n == 0 || n > len) n = (uint8_t)len; /* ZLP / overrun: stop here */ len -= n; if (!nak) for (uint8_t i = 0; i < n; i++) if (i2c_write(buf[i]) != I2C_OK) goto fail; } } else { while (len) { uint8_t n = len > EP0_SIZE ? EP0_SIZE : (uint8_t)len; for (uint8_t i = 0; i < n; i++) { len--; buf[i] = nak ? 0xFF : i2c_read(len != 0); /* NACK the final byte */ } if (!nak && i2c_bus_error()) goto fail; if (!ep0_tx(buf, n)) goto abort; } } if (nak || (cmd & CMD_IO_END)) close_bus(); if (rd) ep0_status_out(); else ep0_status_in(); return; fail: ep0_stall(); abort: close_bus(); status = STATUS_IDLE; } void usb_vendor_request(uint8_t bmRT, uint8_t bReq, uint16_t wV, uint16_t wI, uint16_t wL) { idle_ticks = 0; switch (bReq) { case CMD_ECHO: ep0_send(&wV, 2, wL, false); break; case CMD_GET_FUNC: ep0_send(func, sizeof func, wL, true); break; case CMD_SET_DELAY: k_delay = us_to_delay(wV); ep0_status_in(); break; case CMD_GET_STATUS: ep0_send(&status, 1, wL, false); break; case CMD_I2C_IO: case CMD_I2C_IO | CMD_IO_BEGIN: case CMD_I2C_IO | CMD_IO_END: case CMD_I2C_IO | CMD_IO_BEGIN | CMD_IO_END: i2c_io(bReq, bmRT & 0x80, wV, wI & 0x7F, wL); break; default: ep0_stall(); } }