diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py index 28bfcdf0d15d..6d889a9502b0 100644 --- a/klippy/extras/bus.py +++ b/klippy/extras/bus.py @@ -179,7 +179,7 @@ def build_config(self): self.i2c_read_cmd = self.mcu.lookup_query_command( "i2c_read oid=%c reg=%*s read_len=%u", "i2c_read_response oid=%c response=%*s", oid=self.oid, - cq=self.cmd_queue) + cq=self.cmd_queue, is_async=True) self.i2c_modify_bits_cmd = self.mcu.lookup_command( "i2c_modify_bits oid=%c reg=%*s clear_set_bits=%*s", cq=self.cmd_queue) diff --git a/src/atsam/gpio.h b/src/atsam/gpio.h index e9fc4c3ed040..972ad381b955 100644 --- a/src/atsam/gpio.h +++ b/src/atsam/gpio.h @@ -2,6 +2,7 @@ #define __ATSAM_GPIO_H #include // uint32_t +#include "sched.h" // timer struct gpio_out { void *regs; @@ -53,5 +54,6 @@ struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/atsam/i2c.c b/src/atsam/i2c.c index 0661727c274e..629bc056c50c 100644 --- a/src/atsam/i2c.c +++ b/src/atsam/i2c.c @@ -10,6 +10,7 @@ #include "gpio.h" // i2c_setup #include "internal.h" // gpio_peripheral #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s #if CONFIG_MACH_SAME70 #include "same70_i2c.h" // Fixes for upstream header changes @@ -193,3 +194,37 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg ; (void)p_twi->TWI_SR; } + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c->i2c_config; + if (i2c->data_len[1] & I2C_R) { + uint8_t reg_len = i2c->data_len[0]; + uint8_t *reg = i2c->buf; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t *resp = &i2c->buf[reg_len]; + i2c_read(config, reg_len, reg, read_len, resp); + for (int i = 0; i < reg_len; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + i2c->cur = i2c->tail + read_len; + timer->func = i2c->callback; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } else if (i2c->data_len[0]) { + uint8_t to_write = i2c->data_len[0]; + uint8_t buf[to_write]; + for (int i = 0; i < to_write; i++) { + buf[i] = i2c_buf_read_b(i2c); + } + i2c_write(config, to_write, buf); + i2c_cmd_done(i2c); + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/atsamd/gpio.h b/src/atsamd/gpio.h index b8cb3e861d19..a6fcbfc6e868 100644 --- a/src/atsamd/gpio.h +++ b/src/atsamd/gpio.h @@ -2,6 +2,7 @@ #define __ATSAMD_GPIO_H #include +#include "sched.h" // timer struct gpio_out { void *regs; @@ -54,5 +55,6 @@ struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/atsamd/i2c.c b/src/atsamd/i2c.c index 3c316142ce5b..b82bb93540dd 100644 --- a/src/atsamd/i2c.c +++ b/src/atsamd/i2c.c @@ -9,6 +9,7 @@ #include "command.h" // shutdown #include "gpio.h" // i2c_setup #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s #define TIME_RISE 125ULL // 125 nanoseconds #define I2C_FREQ 100000 @@ -144,3 +145,37 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg *read++ = si->DATA.reg; } } + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c->i2c_config; + if (i2c->data_len[1] & I2C_R) { + uint8_t reg_len = i2c->data_len[0]; + uint8_t *reg = i2c->buf; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t *resp = &i2c->buf[reg_len]; + i2c_read(config, reg_len, reg, read_len, resp); + for (int i = 0; i < reg_len; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + i2c->cur = i2c->tail + read_len; + timer->func = i2c->callback; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } else if (i2c->data_len[0]) { + uint8_t to_write = i2c->data_len[0]; + uint8_t buf[to_write]; + for (int i = 0; i < to_write; i++) { + buf[i] = i2c_buf_read_b(i2c); + } + i2c_write(config, to_write, buf); + i2c_cmd_done(i2c); + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/avr/gpio.h b/src/avr/gpio.h index 9d98ee709218..43646e0757a3 100644 --- a/src/avr/gpio.h +++ b/src/avr/gpio.h @@ -2,6 +2,7 @@ #define __AVR_GPIO_H #include +#include "sched.h" // timer struct gpio_out { struct gpio_digital_regs *regs; @@ -47,11 +48,13 @@ void spi_transfer(struct spi_config config, uint8_t receive_data struct i2c_config { uint8_t addr; + uint32_t pause; }; struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/avr/i2c.c b/src/avr/i2c.c index 658a30a12187..5a969438553e 100644 --- a/src/avr/i2c.c +++ b/src/avr/i2c.c @@ -11,6 +11,7 @@ #include "gpio.h" // i2c_setup #include "internal.h" // GPIO #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s DECL_ENUMERATION("i2c_bus", "twi", 0); @@ -30,6 +31,7 @@ DECL_CONSTANT_STR("BUS_PINS_twi", "PD0,PD1"); struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) { + uint8_t us = 23; if (bus) shutdown("Unsupported i2c bus"); @@ -40,15 +42,16 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) // Set frequency avoiding pulling in integer divide TWSR = 0; - if (rate >= 400000) + if (rate >= 400000) { TWBR = ((CONFIG_CLOCK_FREQ / 400000) - 16) / 2; - else + } else { TWBR = ((CONFIG_CLOCK_FREQ / 100000) - 16) / 2; - + us = 90; + } // Enable interface TWCR = (1<i2c_config; + + if (TWCR & (1 << TWINT)) { + i2c_slv->buf[i2c_slv->cur] = TWDR; + i2c_slv->cur++; + if (i2c_slv->data_len[0] == 0) { + TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWSTO); + timer->func = i2c_slv->callback; + } + return i2c_async_read(timer); + } + + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_read(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + + i2c_slv->data_len[0]--; + TWCR = (1<data_len[0] ? 1 : 0)<func = i2c_async_read_wait; + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_read_start_wait(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + if (TWCR & (1 << TWINT)) { + timer->func = i2c_async_read; + return i2c_async_read(timer); + } + + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_start(struct timer *timer); + +static uint_fast8_t i2c_async_write_end(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + if (TWCR & (1 << TWINT)) { + if (i2c_slv->data_len[0] & I2C_R) { + return i2c_async_start(timer); + } + TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWSTO); + timer->func = i2c_async; + } + + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_write(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + if (TWCR & (1 << TWINT)) { + TWDR = i2c_buf_read_b(i2c_slv); + TWCR = (1 << TWEN) | (1 << TWINT); + i2c_slv->data_len[0]--; + if (i2c_slv->data_len[0] == 0) { + i2c_cmd_done(i2c_slv); + timer->func = i2c_async_write_end; + } + } + + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_start_wait(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + if (TWCR & (1 << TWINT)) { + uint32_t status = TWSR; + if (status != 0x10 && status != 0x08) + shutdown("Failed to send i2c start"); + + if (i2c_slv->data_len[0] & I2C_R) { + TWDR = config->addr | 0x1; + i2c_slv->data_len[0] &= ~(I2C_R); + i2c_slv->cur = i2c_slv->tail; + timer->func = i2c_async_read_start_wait; + } else { + TWDR = config->addr; + timer->func = i2c_async_write; + } + TWCR = (1 << TWEN) | (1 << TWINT); + } + + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_start(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWSTA); + timer->func = i2c_async_start_wait; + timer->waketime = timer_read_time() + config->pause; + return SF_RESCHEDULE; +} + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + + // write register + read + if (i2c_slv->data_len[0] || i2c_slv->data_len[1] & I2C_R) { + if (i2c_slv->data_len[0] == 0) { + // cleanup empty write, start read + i2c_cmd_done(i2c_slv); + } + + return i2c_async_start(timer); + } + + i2c_slv->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/i2c_software.c b/src/i2c_software.c index ed0efe536556..fbd662ae28dd 100644 --- a/src/i2c_software.c +++ b/src/i2c_software.c @@ -19,163 +19,333 @@ struct i2c_software { struct gpio_in scl_in, sda_in; uint8_t addr; unsigned int ticks; + unsigned int ticks_half; + + // callback to return to + uint_fast8_t (*func)(struct timer *timer); + + // bit-banging + uint8_t byte; + uint8_t nack; + int i; }; +static unsigned int +nsecs_to_ticks(uint32_t ns) +{ + return timer_from_us(ns * 1000) / 1000000; +} + void command_i2c_set_software_bus(uint32_t *args) { struct i2cdev_s *i2c = i2cdev_oid_lookup(args[0]); struct i2c_software *is = alloc_chunk(sizeof(*is)); - is->ticks = 1000000 / 100 / 2; // 100KHz + int rate = args[3] / 1000; + int ns = 1000000 / rate / 2; + is->ticks = nsecs_to_ticks(ns); + is->ticks_half = is->ticks / 2; is->addr = (args[4] & 0x7f) << 1; // address format shifted is->scl_in = gpio_in_setup(args[1], 1); is->scl_out = gpio_out_setup(args[1], 1); is->sda_in = gpio_in_setup(args[2], 1); is->sda_out = gpio_out_setup(args[2], 1); i2cdev_set_software_bus(i2c, is); + + // time correction + uint32_t start = timer_read_time(); + gpio_out_reset(is->scl_out, 0); + gpio_in_reset(is->scl_in, 1); + uint32_t end = timer_read_time(); + uint32_t t_diff = (end - start) / 2; + if (t_diff < is->ticks) { + is->ticks = is->ticks - t_diff; + is->ticks_half = is->ticks / 2; + } } DECL_COMMAND(command_i2c_set_software_bus, "i2c_set_software_bus oid=%c scl_pin=%u sda_pin=%u" " rate=%u address=%u"); -// The AVR micro-controllers require specialized timing -#if CONFIG_MACH_AVR - -#define i2c_delay(ticks) (void)(ticks) +// Example implementation of Async RW of I2C +// Function chaining used for simplify flag checks & overral logic -#else +uint_fast8_t i2c_software_async(struct timer *timer); -static unsigned int -nsecs_to_ticks(uint32_t ns) +static uint_fast8_t +i2c_async_end(struct timer *timer) { - return timer_from_us(ns * 1000) / 1000000; -} + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; -static void -i2c_delay(unsigned int ticks) { - unsigned int t = timer_read_time() + nsecs_to_ticks(ticks); - while (t > timer_read_time()); -} - -#endif - -static void -i2c_software_send_ack(struct i2c_software *is, const uint8_t ack) -{ - if (ack) { - gpio_in_reset(is->sda_in, 1); - } else { + is->i++; + switch (is->i) { + case 1: gpio_out_reset(is->sda_out, 0); + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; + case 2: + gpio_in_reset(is->scl_in, 1); + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } - i2c_delay(is->ticks); - gpio_in_reset(is->scl_in, 1); - i2c_delay(is->ticks); - gpio_out_reset(is->scl_out, 0); + gpio_in_reset(is->sda_in, 1); + + if (is->nack) + shutdown("soft_i2c NACK"); + + timer->func = i2c_software_async; + if (i2c->flags & IF_RW) + return i2c->callback(timer); + timer->waketime = timer_read_time() + is->ticks; + return SF_RESCHEDULE; } -static uint8_t -i2c_software_read_ack(struct i2c_software *is) +static uint_fast8_t +i2c_async_read_byte(struct timer *timer); + +static uint_fast8_t sda_read(struct timer *timer) { - uint8_t nack = 0; - gpio_in_reset(is->sda_in, 1); - i2c_delay(is->ticks); - gpio_in_reset(is->scl_in, 1); - nack = gpio_in_read(is->sda_in); - i2c_delay(is->ticks); + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + i2c->buf[i2c->cur] <<= 1; + i2c->buf[i2c->cur] |= gpio_in_read(is->sda_in); gpio_out_reset(is->scl_out, 0); - gpio_in_reset(is->sda_in, 1); - return nack; + is->i++; + timer->func = i2c_async_read_byte; + if (is->i == 8) + timer->waketime = timer_read_time() + is->ticks_half; + else + timer->waketime = timer_read_time() + is->ticks; + return SF_RESCHEDULE; } -static void -i2c_software_send_byte(struct i2c_software *is, uint8_t b) +static uint_fast8_t +i2c_async_read_byte(struct timer *timer) { - for (uint_fast8_t i = 0; i < 8; i++) { - if (b & 0x80) { + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + + if (is->i < 8) { + gpio_in_reset(is->scl_in, 1); + timer->func = sda_read; + timer->waketime = timer_read_time() + is->ticks; + return SF_RESCHEDULE; + } + + if (is->i == 8) { + is->i++; + if (i2c->data_len[0] == 0) { gpio_in_reset(is->sda_in, 1); } else { gpio_out_reset(is->sda_out, 0); } - b <<= 1; - i2c_delay(is->ticks); + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; + } + + if (is->i == 9) { gpio_in_reset(is->scl_in, 1); - i2c_delay(is->ticks); - gpio_out_reset(is->scl_out, 0); + is->i++; + timer->waketime = timer_read_time() + is->ticks; + return SF_RESCHEDULE; } - if (i2c_software_read_ack(is)) { - shutdown("soft_i2c NACK"); + gpio_out_reset(is->scl_out, 0); + i2c->cur++; + is->i = 0; + if (i2c->data_len[0] == 0) { + timer->func = i2c_async_end; + return i2c_async_end(timer); } + + timer->func = is->func; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } -static uint8_t -i2c_software_read_byte(struct i2c_software *is, uint8_t remaining) +static uint_fast8_t +i2c_async_read(struct timer *timer) { - uint8_t b = 0; - gpio_in_reset(is->sda_in, 1); - for (uint_fast8_t i = 0; i < 8; i++) { - i2c_delay(is->ticks); - gpio_in_reset(is->scl_in, 1); - i2c_delay(is->ticks); - b <<= 1; - b |= gpio_in_read(is->sda_in); - gpio_out_reset(is->scl_out, 0); - } + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + + i2c->data_len[0]--; gpio_in_reset(is->sda_in, 1); - i2c_software_send_ack(is, remaining == 0); - return b; + is->i = 0; + is->func = i2c_async_read; + i2c->buf[i2c->cur] = 0; + timer->func = i2c_async_read_byte; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } -static void -i2c_software_start(struct i2c_software *is, uint8_t addr) +static uint_fast8_t +i2c_async_send_byte(struct timer *timer); +static uint_fast8_t send_bit(struct timer *timer); +static uint_fast8_t scl_high(struct timer *timer); + +static uint_fast8_t scl_low(struct timer *timer) { - i2c_delay(is->ticks); - gpio_in_reset(is->sda_in, 1); - gpio_in_reset(is->scl_in, 1); - i2c_delay(is->ticks); - gpio_out_reset(is->sda_out, 0); - i2c_delay(is->ticks); + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; gpio_out_reset(is->scl_out, 0); + timer->func = send_bit; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; +} - i2c_software_send_byte(is, addr); +static uint_fast8_t send_bit(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + if (is->byte & 0x80) { + gpio_in_reset(is->sda_in, 1); + } else { + gpio_out_reset(is->sda_out, 0); + } + is->byte <<= 1; + is->i++; + timer->func = scl_high; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } -static void -i2c_software_stop(struct i2c_software *is) +static uint_fast8_t scl_high(struct timer *timer) { - gpio_out_reset(is->sda_out, 0); - i2c_delay(is->ticks); + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; gpio_in_reset(is->scl_in, 1); - i2c_delay(is->ticks); - gpio_in_reset(is->sda_in, 1); + timer->func = i2c_async_send_byte; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } -void -i2c_software_write(struct i2c_software *is, uint8_t write_len, uint8_t *write) +static uint_fast8_t fin_scl_low(struct timer *timer) { - i2c_software_start(is, is->addr); - while (write_len--) - i2c_software_send_byte(is, *write++); - i2c_software_stop(is); + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + gpio_out_reset(is->scl_out, 0); + timer->func = i2c_async_send_byte; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } -void -i2c_software_read(struct i2c_software *is, uint8_t reg_len, uint8_t *reg - , uint8_t read_len, uint8_t *read) +static uint_fast8_t +i2c_async_send_byte(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + + if (is->i < 8) { + timer->func = scl_low; + goto sched; + } + + // prepare ACK + if (is->i == 8) { + is->byte = 1 << 7; + timer->func = scl_low; + goto sched; + } + + if (is->i == 9) { + is->nack = gpio_in_read(is->sda_in); + is->i++; + timer->func = fin_scl_low; + goto sched; + } + + gpio_in_reset(is->sda_in, 1); + // HW controllers ignore last NACK + if (is->nack && i2c->data_len[0] != 0) { + // Stop as HW controller + timer->func = i2c_async_end; + return i2c_async_end(timer); + } + is->i = 0; + timer->func = is->func; + return is->func(timer); +sched: + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; +} + +static uint_fast8_t +i2c_async_start(struct timer *timer); + +static uint_fast8_t +i2c_async_write(struct timer *timer) { - uint8_t addr = is->addr | 0x01; + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + is->i = 0; + is->byte = i2c_buf_read_b(i2c); + i2c->data_len[0]--; - if (reg_len) { - // write the register - i2c_software_start(is, is->addr); - while(reg_len--) - i2c_software_send_byte(is, *reg++); + timer->func = i2c_async_send_byte; + if (i2c->data_len[0]) { + is->func = i2c_async_write; + return i2c_async_send_byte(timer); } + i2c_cmd_done(i2c); + is->i = 0; + if (i2c->flags & IF_RW) + is->func = i2c_async_start; + else + is->func = i2c_async_end; + return i2c_async_send_byte(timer); +} + +static uint_fast8_t +i2c_async_start(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_software *is = i2c->i2c_software; + uint8_t rw_bit; + // start/re-start and read data - i2c_software_start(is, addr); - while(read_len--) { - *read = i2c_software_read_byte(is, read_len); - read++; + is->i++; + switch (is->i) + { + case 1: + gpio_in_reset(is->sda_in, 1); + timer->func = i2c_async_start; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; + case 2: + gpio_in_reset(is->scl_in, 1); + timer->func = i2c_async_start; + timer->waketime = timer_read_time() + is->ticks_half; + return SF_RESCHEDULE; } - i2c_software_stop(is); + gpio_out_reset(is->sda_out, 0); + + rw_bit = ((i2c->data_len[0] & IF_RW) >> 7); + is->i = 0; + is->byte = is->addr | rw_bit; + i2c->cur = i2c->tail; + i2c->data_len[0] &= ~(I2C_R); + if (rw_bit) + is->func = i2c_async_read; + else + is->func = i2c_async_write; + timer->func = i2c_async_send_byte; + return i2c_async_send_byte(timer); +} + +uint_fast8_t i2c_software_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + // write register + read + if (i2c->data_len[0] || i2c->data_len[1] & IF_RW) { + if (i2c->data_len[0] == 0) + // cleanup empty write + i2c_cmd_done(i2c); + i2c->i2c_software->i = 0; + return i2c_async_start(timer); + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; } diff --git a/src/i2c_software.h b/src/i2c_software.h index 9bd54f29af90..7ef7f9474859 100644 --- a/src/i2c_software.h +++ b/src/i2c_software.h @@ -2,12 +2,9 @@ #define __I2C_SOFTWARE_H #include // uint8_t +#include "sched.h" // struct timer struct i2c_software *i2c_software_oid_lookup(uint8_t oid); -void i2c_software_write(struct i2c_software *sw_i2c - , uint8_t write_len, uint8_t *write); -void i2c_software_read(struct i2c_software *sw_i2c - , uint8_t reg_len, uint8_t *reg - , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_software_async(struct timer *timer); #endif // i2c_software.h diff --git a/src/i2ccmds.c b/src/i2ccmds.c index 377a9537deaa..967ba49d776f 100644 --- a/src/i2ccmds.c +++ b/src/i2ccmds.c @@ -10,13 +10,11 @@ #include "command.h" //sendf #include "sched.h" //DECL_COMMAND #include "board/gpio.h" //i2c_write/read/setup +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time #include "i2c_software.h" // i2c_software_setup #include "i2ccmds.h" -enum { - IF_SOFTWARE = 1, IF_HARDWARE = 2 -}; - void command_config_i2c(uint32_t *args) { @@ -37,7 +35,7 @@ command_i2c_set_bus(uint32_t *args) uint8_t addr = args[3] & 0x7f; struct i2cdev_s *i2c = i2cdev_oid_lookup(args[0]); i2c->i2c_config = i2c_setup(args[1], args[2], addr); - i2c->flags |= IF_HARDWARE; + i2c->timer.func = i2c_async; } DECL_COMMAND(command_i2c_set_bus, "i2c_set_bus oid=%c i2c_bus=%u rate=%u address=%u"); @@ -47,23 +45,93 @@ i2cdev_set_software_bus(struct i2cdev_s *i2c, struct i2c_software *is) { i2c->i2c_software = is; i2c->flags |= IF_SOFTWARE; + i2c->timer.func = i2c_software_async; } -void -command_i2c_write(uint32_t *args) +void i2c_buf_write_b(struct i2cdev_s *i2c, uint8_t byte) +{ + i2c->buf[i2c->head] = byte; + i2c->head = (i2c->head + 1) % sizeof(i2c->buf); + i2c->size++; +} + +uint8_t i2c_buf_read_b(struct i2cdev_s *i2c) +{ + uint8_t b = i2c->buf[i2c->tail]; + i2c->tail = (i2c->tail + 1) % sizeof(i2c->buf); + i2c->size--; + return b; +} + +// shift cmd buffer cmd[N] = cmd[N+1] +void i2c_cmd_done(struct i2cdev_s *i2c) +{ + memmove(i2c->data_len, &i2c->data_len[1], sizeof(i2c->data_len) - 1); + i2c->data_len[sizeof(i2c->data_len) - 1] = 0; +} + +void command_i2c_write(uint32_t *args) { uint8_t oid = args[0]; struct i2cdev_s *i2c = oid_lookup(oid, command_config_i2c); uint8_t data_len = args[1]; uint8_t *data = command_decode_ptr(args[2]); - uint_fast8_t flags = i2c->flags; - if (CONFIG_WANT_SOFTWARE_I2C && flags & IF_SOFTWARE) - i2c_software_write(i2c->i2c_software, data_len, data); - else - i2c_write(i2c->i2c_config, data_len, data); + uint8_t start = 0; + + if (i2c->data_len[sizeof(i2c->data_len)-1]) { + shutdown("i2c_write: cmd buffer overload"); + } + + // write register -> read, write can be empty + if (i2c->flags & IF_RW) { + start = 1; + } + + while (i2c->data_len[start]) { + start++; + } + + if (data_len > sizeof(i2c->buf) - i2c->size) + shutdown("i2c_write: buffer overload - data too large"); + + i2c->data_len[start] = data_len; + for (uint8_t i = 0; i < data_len; i++) { + i2c_buf_write_b(i2c, data[i]); + } + + if (i2c->flags & IF_ACTIVE) + return; + + i2c->flags |= IF_ACTIVE; + irq_disable(); + i2c->timer.waketime = timer_read_time() + timer_from_us(200); + sched_add_timer(&i2c->timer); + irq_enable(); } DECL_COMMAND(command_i2c_write, "i2c_write oid=%c data=%*s"); +// Expects that i2c->read len is 0 and read_len = i2c->cur - i2c->tail; +static uint_fast8_t +i2c_read_response(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + uint8_t read_len = i2c->cur - i2c->tail; + sendf("i2c_read_response oid=%c response=%*s", i2c->oid, read_len + , &i2c->buf[i2c->tail]); + for (int i = 0; i < read_len; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + if (CONFIG_WANT_SOFTWARE_I2C && i2c->flags & IF_SOFTWARE) + i2c->timer.func = i2c_software_async; + else + i2c->timer.func = i2c_async; + i2c->flags &= ~(IF_RW); + i2c->timer.waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + void command_i2c_read(uint32_t * args) { @@ -72,16 +140,76 @@ command_i2c_read(uint32_t * args) uint8_t reg_len = args[1]; uint8_t *reg = command_decode_ptr(args[2]); uint8_t data_len = args[3]; - uint8_t data[data_len]; - uint_fast8_t flags = i2c->flags; - if (CONFIG_WANT_SOFTWARE_I2C && flags & IF_SOFTWARE) - i2c_software_read(i2c->i2c_software, reg_len, reg, data_len, data); - else - i2c_read(i2c->i2c_config, reg_len, reg, data_len, data); - sendf("i2c_read_response oid=%c response=%*s", oid, data_len, data); + if (i2c->flags & IF_ACTIVE) + // i2C is busy - silently drop this request (host should retransmit) + return; + + // buffer is always empty here + if (reg_len + data_len > sizeof(i2c->buf)) + shutdown("i2c_read: buffer overload - data too large"); + + i2c->oid = oid; + i2c->tail = 0; + i2c->head = 0; + + i2c->data_len[0] = reg_len; + i2c->data_len[1] = data_len | I2C_R; + for (int i = 0; i < reg_len; i++) { + i2c_buf_write_b(i2c, reg[i]); + } + // reserve space + for (int i = 0; i < data_len; i++) { + i2c_buf_write_b(i2c, 0); + } + + i2c->flags |= IF_ACTIVE | IF_RW; + i2c->callback = i2c_read_response; + irq_disable(); + i2c->timer.waketime = timer_read_time() + timer_from_us(200); + sched_add_timer(&i2c->timer); + irq_enable(); } DECL_COMMAND(command_i2c_read, "i2c_read oid=%c reg=%*s read_len=%u"); +static uint_fast8_t +i2c_modify_bits(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + uint8_t read_len = i2c->cur - i2c->tail; + uint8_t buf[read_len]; + + for (int i = 0; i < read_len; i++) { + buf[i] = i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + for (int i = 0; i < read_len; i++) { + buf[i] &= i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + for (int i = 0; i < read_len; i++) { + buf[i] |= i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + // Because of FIFO next write is ours + uint8_t reg_len = i2c->data_len[0] - read_len; + i2c->cur = i2c->tail + reg_len; + for (int i = 0; i < read_len; i++) { + i2c->buf[i2c->cur] = buf[i]; + i2c->cur++; + } + + i2c->flags &= ~(IF_RW); + if (CONFIG_WANT_SOFTWARE_I2C && i2c->flags & IF_SOFTWARE) + timer->func = i2c_software_async; + else + timer->func = i2c_async; + timer->waketime = timer_read_time() + timer_from_us(200); + return SF_RESCHEDULE; +} + void command_i2c_modify_bits(uint32_t *args) { @@ -94,23 +222,57 @@ command_i2c_modify_bits(uint32_t *args) shutdown("i2c_modify_bits: Odd number of bits!"); uint8_t data_len = clear_set_len/2; uint8_t *clear_set = command_decode_ptr(args[4]); - uint8_t receive_data[reg_len + data_len]; - uint_fast8_t flags = i2c->flags; - memcpy(receive_data, reg, reg_len); - if (CONFIG_WANT_SOFTWARE_I2C && flags & IF_SOFTWARE) - i2c_software_read( - i2c->i2c_software, reg_len, reg, data_len, receive_data + reg_len); - else - i2c_read( - i2c->i2c_config, reg_len, reg, data_len, receive_data + reg_len); + + if (i2c->flags & IF_ACTIVE) { + shutdown("i2c_modify_bits: busy in process"); + // i2c is busy - drop request for now + // I think there should be some sort of msg queue encoding, + // implemented again + // But every new supported RMW will complicate code. + return; + } + + if (reg_len * 2 + data_len * 4 > sizeof(i2c->buf)) + shutdown("i2c_modify_bits: buffer overload - data too large"); + + i2c->tail = 0; + i2c->head = 0; + + i2c->data_len[0] = reg_len; + for (int i = 0; i < reg_len; i++) { + i2c_buf_write_b(i2c, reg[i]); + } + + i2c->data_len[1] = data_len | I2C_R; + // reserve space for read for (int i = 0; i < data_len; i++) { - receive_data[reg_len + i] &= ~clear_set[i]; - receive_data[reg_len + i] |= clear_set[data_len + i]; + i2c_buf_write_b(i2c, 0); } - if (CONFIG_WANT_SOFTWARE_I2C && flags & IF_SOFTWARE) - i2c_software_write(i2c->i2c_software, reg_len + data_len, receive_data); - else - i2c_write(i2c->i2c_config, reg_len + data_len, receive_data); + + // save clear bits + i2c->data_len[2] = data_len | I2C_R; + for (int i = 0; i < data_len; i++) { + i2c_buf_write_b(i2c, ~clear_set[i]); + } + + // save set bits + i2c->data_len[3] = data_len | I2C_R; + for (int i = 0; i < data_len; i++) { + i2c_buf_write_b(i2c, clear_set[data_len + i]); + } + + // Reserve space for later write + i2c->data_len[4] = reg_len + data_len; + for (int i = 0; i < reg_len + data_len; i++) { + i2c_buf_write_b(i2c, reg[i]); + } + + i2c->flags |= IF_ACTIVE | IF_RW; + i2c->callback = i2c_modify_bits; + irq_disable(); + i2c->timer.waketime = timer_read_time() + timer_from_us(200); + sched_add_timer(&i2c->timer); + irq_enable(); } DECL_COMMAND(command_i2c_modify_bits, "i2c_modify_bits oid=%c reg=%*s clear_set_bits=%*s"); diff --git a/src/i2ccmds.h b/src/i2ccmds.h index 9ce54aa060b2..0562cce98519 100644 --- a/src/i2ccmds.h +++ b/src/i2ccmds.h @@ -2,15 +2,38 @@ #define __I2CCMDS_H #include +#include "sched.h" // timer #include "board/gpio.h" // i2c_config +enum { + IF_SOFTWARE = 1, + IF_ACTIVE = 1 << 2, + IF_RW = 1 << 7, + I2C_R = 1 << 7 +}; + struct i2cdev_s { + struct timer timer; struct i2c_config i2c_config; struct i2c_software *i2c_software; + uint8_t oid; uint8_t flags; + // MSB is W/R ops W is 0, R is 1 + uint8_t data_len[8]; + uint8_t buf[32]; + uint8_t head; + uint8_t tail; + uint8_t size; + uint8_t cur; + + // callback to return to + uint_fast8_t (*callback)(struct timer *timer); }; struct i2cdev_s *i2cdev_oid_lookup(uint8_t oid); void i2cdev_set_software_bus(struct i2cdev_s *i2c, struct i2c_software *is); +void i2c_buf_write_b(struct i2cdev_s *i2c, uint8_t byte); +uint8_t i2c_buf_read_b(struct i2cdev_s *i2c); +void i2c_cmd_done(struct i2cdev_s *i2c); #endif diff --git a/src/linux/gpio.h b/src/linux/gpio.h index df9de752566e..8023d4a6bfa2 100644 --- a/src/linux/gpio.h +++ b/src/linux/gpio.h @@ -2,6 +2,7 @@ #define __LINUX_GPIO_H #include // uint8_t +#include "sched.h" // timer struct gpio_out { struct gpio_line* line; @@ -46,11 +47,13 @@ void gpio_pwm_write(struct gpio_pwm g, uint16_t val); struct i2c_config { int fd; uint8_t addr; + int ret; }; struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); -void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); +void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *data); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/linux/i2c.c b/src/linux/i2c.c index b328dc56eaa6..77dcbb63c19b 100644 --- a/src/linux/i2c.c +++ b/src/linux/i2c.c @@ -9,10 +9,13 @@ #include // snprintf #include // ioctl #include // write +#include #include "gpio.h" // i2c_setup #include "command.h" // shutdown #include "internal.h" // report_errno #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s +#include "board/misc.h" // timer_read_time DECL_ENUMERATION_RANGE("i2c_bus", "i2c.0", 0, 15); @@ -125,3 +128,128 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg try_shutdown("Unable to read i2c device"); } } + +static pthread_t i2c_thread; + +uint_fast8_t i2c_async_write_end(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c->i2c_config; + uint8_t to_write = i2c->data_len[0]; + + pthread_join(i2c_thread, NULL); + // drain buffer + for (int i = 0; i < to_write; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + if (config->ret != to_write) { + if (config->ret < 0) + report_errno("write value i2c", config->ret); + try_shutdown("Unable write i2c device"); + } + + timer->waketime = timer_read_time() + timer_from_us(50); + timer->func = i2c_async; + return SF_RESCHEDULE; +} + +void *i2c_async_write(void *arg) +{ + struct i2cdev_s *i2c = arg; + struct i2c_config *config = &i2c->i2c_config; + + uint8_t to_write = i2c->data_len[0]; + uint8_t buf[to_write]; + for (int i = 0; i < to_write; i++) { + uint8_t c = (i2c->tail + i) % sizeof(i2c->buf); + buf[i] = i2c->buf[c]; + } + + config->ret = write(config->fd, buf, to_write); + return NULL; +} + +uint_fast8_t i2c_async_read_end(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c->i2c_config; + uint8_t to_write = i2c->data_len[0]; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + + pthread_join(i2c_thread, NULL); + // drain write buffer + for (int i = 0; i < to_write; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + + if (config->ret < 0) + try_shutdown("Unable to read i2c device"); + + i2c->cur = i2c->tail + read_len; + timer->waketime = timer_read_time() + timer_from_us(50); + timer->func = i2c->callback; + return SF_RESCHEDULE; +} + +void *i2c_async_rw(void *arg) +{ + struct i2cdev_s *i2c = arg; + struct i2c_config *config = &i2c->i2c_config; + struct i2c_rdwr_ioctl_data i2c_data; + struct i2c_msg msgs[2]; + uint8_t reg_len = i2c->data_len[0]; + uint8_t *reg = i2c->buf; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t *resp = &i2c->buf[reg_len]; + + if (reg_len != 0) { + msgs[0].addr = config->addr; + msgs[0].flags = 0x0; + msgs[0].len = reg_len; + msgs[0].buf = reg; + i2c_data.nmsgs = 2; + i2c_data.msgs = &msgs[0]; + } else { + i2c_data.nmsgs = 1; + i2c_data.msgs = &msgs[1]; + } + + msgs[1].addr = config->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = read_len; + msgs[1].buf = resp; + + config->ret = ioctl(config->fd, I2C_RDWR, &i2c_data); + return NULL; +} + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + // write register + read + // For now data expected to be linear because read is always first in queue + if (i2c->data_len[1] & I2C_R) { + uint8_t reg_len = i2c->data_len[0]; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t tx_len = reg_len + read_len; + pthread_create(&i2c_thread, NULL, i2c_async_rw, i2c); + + timer->waketime = timer_read_time() + timer_from_us(22) * tx_len; + timer->func = i2c_async_read_end; + + return SF_RESCHEDULE; + } else if (i2c->data_len[0]) { + uint8_t to_write = i2c->data_len[0]; + pthread_create(&i2c_thread, NULL, i2c_async_write, i2c); + + timer->waketime = timer_read_time() + timer_from_us(22) * (to_write); + timer->func = i2c_async_write_end; + return SF_RESCHEDULE; + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/lpc176x/gpio.h b/src/lpc176x/gpio.h index e03afbb44910..9d34eea50ee1 100644 --- a/src/lpc176x/gpio.h +++ b/src/lpc176x/gpio.h @@ -2,6 +2,7 @@ #define __LPC176X_GPIO_H #include +#include "sched.h" // timer struct gpio_out { void *regs; @@ -54,5 +55,6 @@ struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/lpc176x/i2c.c b/src/lpc176x/i2c.c index 7ed631af1f15..1d4e2625cc77 100644 --- a/src/lpc176x/i2c.c +++ b/src/lpc176x/i2c.c @@ -9,6 +9,7 @@ #include "gpio.h" // i2c_setup #include "internal.h" // gpio_peripheral #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s struct i2c_info { LPC_I2C_TypeDef *i2c; @@ -160,3 +161,37 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg } i2c_stop(i2c, timeout); } + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c->i2c_config; + if (i2c->data_len[1] & I2C_R) { + uint8_t reg_len = i2c->data_len[0]; + uint8_t *reg = i2c->buf; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t *resp = &i2c->buf[reg_len]; + i2c_read(config, reg_len, reg, read_len, resp); + for (int i = 0; i < reg_len; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + i2c->cur = i2c->tail + read_len; + timer->func = i2c->callback; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } else if (i2c->data_len[0]) { + uint8_t to_write = i2c->data_len[0]; + uint8_t buf[to_write]; + for (int i = 0; i < to_write; i++) { + buf[i] = i2c_buf_read_b(i2c); + } + i2c_write(config, to_write, buf); + i2c_cmd_done(i2c); + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/rp2040/gpio.h b/src/rp2040/gpio.h index ae6083780ce9..7fd3236b9125 100644 --- a/src/rp2040/gpio.h +++ b/src/rp2040/gpio.h @@ -2,6 +2,7 @@ #define __RP2040_GPIO_H #include // uint32_t +#include "sched.h" struct gpio_out { uint32_t bit; @@ -47,11 +48,14 @@ void spi_transfer(struct spi_config config, uint8_t receive_data struct i2c_config { void *i2c; uint8_t addr; + int i; + int to_read; }; struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/rp2040/i2c.c b/src/rp2040/i2c.c index 8a32417badd4..40cfc5ec088a 100644 --- a/src/rp2040/i2c.c +++ b/src/rp2040/i2c.c @@ -9,6 +9,7 @@ #include "command.h" // shutdown #include "sched.h" // sched_shutdown #include "internal.h" // pclock, gpio_peripheral +#include "i2ccmds.h" // struct i2cdev_s #include "hardware/regs/resets.h" // RESETS_RESET_I2C*_BITS #include "hardware/structs/i2c.h" @@ -213,3 +214,128 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg i2c_do_read(i2c, config.addr, read_len, read, timeout); i2c_stop(i2c); } + +static uint_fast8_t +i2c_async_read(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + i2c_hw_t *i2c = (i2c_hw_t*)config->i2c; + + if (config->to_read > 0 && i2c->txflr < 16) { + int first = config->i == 0; + int last = config->to_read == 1; + i2c->data_cmd = first << I2C_IC_DATA_CMD_RESTART_LSB + | last << I2C_IC_DATA_CMD_STOP_LSB + | I2C_IC_DATA_CMD_CMD_BITS; + config->i++; + config->to_read--; + } + + if (i2c_slv->data_len[0] > 0 && i2c->rxflr > 0) { + i2c_slv->data_len[0]--; + i2c_slv->buf[i2c_slv->cur] = i2c->data_cmd & 0xFF; + i2c_slv->cur++; + } + + if (i2c_slv->data_len[0] == 0) { + timer->func = i2c_slv->callback; + } + + timer->waketime = timer_read_time() + timer_from_us(10); + return SF_RESCHEDULE; +} + +static uint_fast8_t +i2c_async_read_start(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + + config->i = 0; + i2c_slv->data_len[0] &= ~(I2C_R); + config->to_read = i2c_slv->data_len[0]; + i2c_slv->cur = i2c_slv->tail; + timer->func = i2c_async_read; + return i2c_async_read(timer); +} + +static uint_fast8_t +i2c_async_write_end(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c_slv->i2c_config; + i2c_hw_t *i2c = (i2c_hw_t*)config.i2c; + if (i2c_slv->data_len[0] & I2C_R) { + return i2c_async_read_start(timer); + } + + if (i2c->txflr != 0) { + timer->waketime = timer_read_time() + timer_from_us(10); + return SF_RESCHEDULE; + } + + timer->func = i2c_async; + timer->waketime = timer_read_time() + timer_from_us(10); + return SF_RESCHEDULE; +} + +static uint_fast8_t +i2c_async_write(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config *config = &i2c_slv->i2c_config; + i2c_hw_t *i2c = (i2c_hw_t*)config->i2c; + uint8_t byte; + int first = config->i == 0; + int last = 0; + + // Wait until there's a spot in the TX FIFO + if (i2c->txflr == 16) { + goto out; + } + + byte = i2c_buf_read_b(i2c_slv); + i2c_slv->data_len[0]--; + config->i++; + + if (i2c_slv->data_len[0] == 0) { + last = 1; + i2c_cmd_done(i2c_slv); + timer->func = i2c_async_write_end; + } + + i2c->data_cmd = first << I2C_IC_DATA_CMD_RESTART_LSB | + last << I2C_IC_DATA_CMD_STOP_LSB | byte; + +out: + timer->waketime = timer_read_time() + timer_from_us(10); + return SF_RESCHEDULE; +} + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c_slv->i2c_config; + i2c_hw_t *i2c = (i2c_hw_t*)config.i2c; + + // write register + read + if (i2c_slv->data_len[0] || i2c_slv->data_len[1] & I2C_R) { + i2c->enable = 0; + i2c->tar = i2c_slv->i2c_config.addr; + i2c->enable = 1; + if (i2c_slv->data_len[0]) { + i2c_slv->i2c_config.i = 0; + timer->func = i2c_async_write; + return i2c_async_write(timer); + } + + // cleanup empty write, start read + i2c_cmd_done(i2c_slv); + return i2c_async_read_start(timer); + } + + i2c->enable = 0; + i2c_slv->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/stm32/gpio.h b/src/stm32/gpio.h index 281e3a8db44c..1cb83ec3d360 100644 --- a/src/stm32/gpio.h +++ b/src/stm32/gpio.h @@ -2,6 +2,7 @@ #define __STM32_GPIO_H #include // uint32_t +#include "sched.h" // struct timer struct gpio_out { void *regs; @@ -54,5 +55,6 @@ struct i2c_config i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr); void i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write); void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *read); +uint_fast8_t i2c_async(struct timer *timer); #endif // gpio.h diff --git a/src/stm32/i2c.c b/src/stm32/i2c.c index f5bbd01dd04a..4ea61c813974 100644 --- a/src/stm32/i2c.c +++ b/src/stm32/i2c.c @@ -10,6 +10,7 @@ #include "gpio.h" // i2c_setup #include "internal.h" // GPIO #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // struct i2cdev_s #include "board/irq.h" //irq_disable struct i2c_info { @@ -181,3 +182,37 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg } i2c_wait(i2c, 0, I2C_SR1_RXNE, timeout); } + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c->i2c_config; + if (i2c->data_len[1] & I2C_R) { + uint8_t reg_len = i2c->data_len[0]; + uint8_t *reg = i2c->buf; + uint8_t read_len = i2c->data_len[1] & ~(I2C_R); + uint8_t *resp = &i2c->buf[reg_len]; + i2c_read(config, reg_len, reg, read_len, resp); + for (int i = 0; i < reg_len; i++) { + i2c_buf_read_b(i2c); + } + i2c_cmd_done(i2c); + i2c->cur = i2c->tail + read_len; + timer->func = i2c->callback; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } else if (i2c->data_len[0]) { + uint8_t to_write = i2c->data_len[0]; + uint8_t buf[to_write]; + for (int i = 0; i < to_write; i++) { + buf[i] = i2c_buf_read_b(i2c); + } + i2c_write(config, to_write, buf); + i2c_cmd_done(i2c); + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + i2c->flags &= ~IF_ACTIVE; + return SF_DONE; +} diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index 597b48460eb7..338af0aa146d 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -9,6 +9,7 @@ #include "gpio.h" // i2c_setup #include "internal.h" // GPIO #include "sched.h" // sched_shutdown +#include "i2ccmds.h" // IF_ACTIVE struct i2c_info { I2C_TypeDef *i2c; @@ -212,3 +213,117 @@ i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg } i2c_wait(i2c, I2C_ISR_STOPF, timeout); } + +static uint_fast8_t i2c_async_read_end(struct timer *timer){ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + I2C_TypeDef *i2c_reg = i2c_slv->i2c_config.i2c; + uint32_t timeout = timer_read_time() + timer_from_us(50); + i2c_wait(i2c_reg, I2C_ISR_STOPF, timeout); + + timer->func = i2c_slv->callback; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_read(struct timer *timer){ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + I2C_TypeDef *i2c_reg = i2c_slv->i2c_config.i2c; + uint32_t timeout = timer_read_time() + timer_from_us(50); + + i2c_wait(i2c_reg, I2C_ISR_RXNE, timeout); + i2c_slv->data_len[0]--; + i2c_slv->buf[i2c_slv->cur] = i2c_reg->RXDR; + i2c_slv->cur++; + + if (i2c_slv->data_len[0] == 0) { + i2c_slv->timer.func = i2c_async_read_end; + } + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_read_start(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c_slv->i2c_config; + I2C_TypeDef *i2c_reg = config.i2c; + uint8_t read_len; + i2c_slv->data_len[0] &= ~(I2C_R); + read_len = i2c_slv->data_len[0]; + + i2c_reg->CR2 = (I2C_CR2_START | I2C_CR2_RD_WRN | config.addr | + (read_len << I2C_CR2_NBYTES_Pos) | I2C_CR2_AUTOEND); + + i2c_slv->cur = i2c_slv->tail; + timer->func = i2c_async_read; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_write_end(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + I2C_TypeDef *i2c_reg = i2c_slv->i2c_config.i2c; + uint32_t timeout = timer_read_time() + timer_from_us(50); + + if (i2c_slv->flags & IF_RW) { + i2c_wait(i2c_reg, I2C_ISR_TC, timeout); + return i2c_async_read_start(timer); + } + + i2c_wait(i2c_reg, I2C_ISR_TXE, timeout); + timer->func = i2c_async; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + +static uint_fast8_t i2c_async_write(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + I2C_TypeDef *i2c_reg = i2c_slv->i2c_config.i2c; + uint32_t timeout = timer_read_time() + timer_from_us(50); + + i2c_wait(i2c_reg, I2C_ISR_TXIS, timeout); + i2c_reg->TXDR = i2c_buf_read_b(i2c_slv); + i2c_slv->data_len[0]--; + if (i2c_slv->data_len[0]) { + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + i2c_cmd_done(i2c_slv); + timer->func = i2c_async_write_end; + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; +} + +uint_fast8_t i2c_async(struct timer *timer) +{ + struct i2cdev_s *i2c_slv = container_of(timer, struct i2cdev_s, timer); + struct i2c_config config = i2c_slv->i2c_config; + I2C_TypeDef *i2c_reg = config.i2c; + + // write register + read + if (i2c_slv->data_len[0] || i2c_slv->data_len[1] & I2C_R) { + if (i2c_slv->data_len[0]) { + uint8_t to_write = i2c_slv->data_len[0]; + uint32_t autoend = I2C_CR2_AUTOEND; + if (i2c_slv->data_len[1] & I2C_R) + autoend = 0; + i2c_reg->CR2 = (I2C_CR2_START | config.addr | + (to_write << I2C_CR2_NBYTES_Pos) | autoend); + timer->func = i2c_async_write; + // 100kHz 5 microsend per pulse, 18 pulses for byte + ack + // But by some strange circumstances in vitro there is 50 + timer->waketime = timer_read_time() + timer_from_us(50); + return SF_RESCHEDULE; + } + + // cleanup empty write, start read + i2c_cmd_done(i2c_slv); + return i2c_async_read_start(timer); + } + + i2c_slv->flags &= ~IF_ACTIVE; + return SF_DONE; +}