| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- /*
- * 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 <avr/pgmspace.h>
- #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();
- }
- }
|