|
| 1 | +/* |
| 2 | + Module to support i2c communication via TWI hardware peripheral |
| 3 | +
|
| 4 | + Author: Julie Zelenski |
| 5 | + Thu May 16 17:04:14 PDT 2024 |
| 6 | + */ |
| 7 | +#include "assert.h" |
| 8 | +#include "ccu.h" |
| 9 | +#include "gpio.h" |
| 10 | +#include "i2c.h" |
| 11 | +#include "malloc.h" |
| 12 | +#include <stddef.h> |
| 13 | +#include "strings.h" |
| 14 | +#include "timer.h" |
| 15 | + |
| 16 | +/* |
| 17 | + * Use D1-H TWI engine peripheral as hardware controller for I2C communication |
| 18 | + */ |
| 19 | + |
| 20 | +// p. 859 D1 user manual |
| 21 | +// structs defined to match layout of hardware registers |
| 22 | +typedef union { |
| 23 | + struct { |
| 24 | + uint32_t addr; |
| 25 | + uint32_t xaddr; |
| 26 | + uint32_t data; |
| 27 | + struct { |
| 28 | + uint32_t clk_mode : 1; |
| 29 | + uint32_t : 1; |
| 30 | + uint32_t ack : 1; |
| 31 | + uint32_t int_flag : 1; |
| 32 | + uint32_t m_stp : 1; |
| 33 | + uint32_t m_sta : 1; |
| 34 | + uint32_t bus_en : 1; |
| 35 | + uint32_t int_en : 1; |
| 36 | + uint32_t : 24; |
| 37 | + } cntr; |
| 38 | + uint32_t stat; |
| 39 | + struct { |
| 40 | + uint32_t clk_N : 3; |
| 41 | + uint32_t clk_M : 4; |
| 42 | + uint32_t clk_duty : 1; |
| 43 | + uint32_t : 24; |
| 44 | + } ccr; |
| 45 | + uint32_t srst; |
| 46 | + uint32_t efr; |
| 47 | + uint32_t lcr; |
| 48 | + } regs; |
| 49 | + uint32_t padding[0x100]; |
| 50 | +} twi_t; |
| 51 | + |
| 52 | +#define TWI_BASE ((twi_t *)0x02502000) |
| 53 | +_Static_assert(&(TWI_BASE[0].regs.lcr) == (uint32_t *)0x02502020, "TWI0 lcr reg must be at address 0x02502020"); |
| 54 | +_Static_assert(&(TWI_BASE[1].regs.efr) == (uint32_t *)0x0250241c, "TWI1 efr reg must be at address 0x0250241c"); |
| 55 | + |
| 56 | +static struct { |
| 57 | + volatile twi_t * const twi_base, *twi; |
| 58 | + const gpio_id_t sda, scl; |
| 59 | +} module = { |
| 60 | + .twi_base = &TWI_BASE[0], // twi0 |
| 61 | + .sda = GPIO_PG13, |
| 62 | + .scl = GPIO_PG12, |
| 63 | + .twi = NULL, |
| 64 | +}; |
| 65 | + |
| 66 | +#define SENTINEL 0x7e |
| 67 | + |
| 68 | +struct i2c_device { |
| 69 | + uint8_t addr; |
| 70 | +}; |
| 71 | + |
| 72 | +i2c_device_t * i2c_new(uint8_t addr) { |
| 73 | + i2c_device_t *dev = malloc(sizeof(*dev)); |
| 74 | + dev->addr = addr; |
| 75 | + if (!i2c_block_write(dev, 0, 0)) { |
| 76 | + free(dev); |
| 77 | + return NULL; |
| 78 | + } |
| 79 | + return dev; |
| 80 | +} |
| 81 | + |
| 82 | +bool i2c_write_reg(i2c_device_t *dev, uint8_t reg, uint8_t val) { |
| 83 | + assert(dev); |
| 84 | + uint8_t buf[2] = {reg, val}; |
| 85 | + return i2c_block_write(dev, buf, sizeof(buf)); |
| 86 | +} |
| 87 | + |
| 88 | +bool i2c_write_reg_n(i2c_device_t *dev, uint8_t reg, uint8_t *bytes, int n) { |
| 89 | + assert(dev); |
| 90 | + uint8_t buf[n+1]; |
| 91 | + buf[0] = reg; |
| 92 | + memcpy(buf+1, bytes, n); |
| 93 | + return i2c_block_write(dev, buf, sizeof(buf)); |
| 94 | +} |
| 95 | + |
| 96 | +uint8_t i2c_read_reg(i2c_device_t *dev, uint8_t reg) { |
| 97 | + assert(dev); |
| 98 | + uint8_t buf[1]; |
| 99 | + if (!i2c_block_write(dev, ®, 1) || !i2c_block_read(dev, buf, 1)) { |
| 100 | + return SENTINEL; // return distinctive pattern to help debug read failure |
| 101 | + } |
| 102 | + return buf[0]; |
| 103 | +} |
| 104 | + |
| 105 | +bool i2c_read_reg_n(i2c_device_t *dev, uint8_t reg, uint8_t *bytes, int n) { |
| 106 | + assert(dev); |
| 107 | + memset(bytes, SENTINEL, n); // init distinctive pattern to help debug read failure |
| 108 | + if (!i2c_block_write(dev, ®, 1)) return false; |
| 109 | + return i2c_block_read(dev, bytes, n); |
| 110 | +} |
| 111 | + |
| 112 | +enum { WRITE_BIT = 0, READ_BIT = 1}; |
| 113 | + |
| 114 | +typedef enum { |
| 115 | + BUS_ERROR = 0, |
| 116 | + START_TRANSMIT = 0x08, |
| 117 | + REPEATED_START_TRANSMIT= 0x10, |
| 118 | + ADDR_W_ACK = 0x18, |
| 119 | + ADDR_W_NAK = 0x20, |
| 120 | + DATA_TRANSMIT_ACK = 0x28, |
| 121 | + DATA_TRANSMIT_NAK = 0x30, |
| 122 | + LOST_ARBITRATION = 0x38, |
| 123 | + ADDR_R_ACK = 0x40, |
| 124 | + ADDR_R_NAK = 0x48, |
| 125 | + DATA_RECEIVE_ACK = 0x50, |
| 126 | + DATA_RECEIVE_NAK = 0x58, |
| 127 | + IDLE = 0xf8, |
| 128 | +} i2c_stat_t; |
| 129 | + |
| 130 | +void i2c_init(void) { |
| 131 | + // this driver supports only TWI0 |
| 132 | + module.twi = &module.twi_base[0]; |
| 133 | + // (gating bit 16) |
| 134 | + ccu_ungate_bus_clock(CCU_TWI_BGR_REG); |
| 135 | + gpio_set_function(module.sda, GPIO_FN_ALT3); // TWI0 |
| 136 | + gpio_set_function(module.scl, GPIO_FN_ALT3); |
| 137 | + // clock divisor values from p.876 of user manual |
| 138 | + // set for 100Khz |
| 139 | + module.twi->regs.ccr.clk_duty = 1; |
| 140 | + module.twi->regs.ccr.clk_M = 11; |
| 141 | + module.twi->regs.ccr.clk_N = 1; |
| 142 | + module.twi->regs.cntr.bus_en = 1; |
| 143 | + module.twi->regs.efr = 0; |
| 144 | + // efr disables special-case handling for unusual devices |
| 145 | + // see https://lore.kernel.org/linux-kernel/CAF8uH3u9L1cVyAZiY=981bDewYgVYM=27kcV0GwqHFURg21FgA@mail.gmail.com/T/ |
| 146 | +} |
| 147 | + |
| 148 | +static i2c_stat_t wait_completion(void) { |
| 149 | + // Note: int_flag is R/W1C Read/Write 1 to Clear. Write 0 has no effect! |
| 150 | + module.twi->regs.cntr.int_flag = 1; |
| 151 | + unsigned int wait_count = 1000*1000; |
| 152 | + while (module.twi->regs.cntr.int_flag == 0 && --wait_count); |
| 153 | + if (wait_count == 0) error("TIMEOUT wait_completion in i2c driver\n"); |
| 154 | + return module.twi->regs.stat; |
| 155 | +} |
| 156 | + |
| 157 | +static bool do_start(i2c_stat_t expected_status) { |
| 158 | + if (module.twi == NULL) error("i2c_init() has not been called!\n"); |
| 159 | + module.twi->regs.cntr.m_sta = 1; |
| 160 | + i2c_stat_t status = wait_completion(); |
| 161 | + return status == expected_status; |
| 162 | +} |
| 163 | + |
| 164 | +static void do_stop(void) { |
| 165 | + module.twi->regs.cntr.m_stp = 1; |
| 166 | + while (module.twi->regs.cntr.m_stp == 1) ; // no interrupt after stop, wait for stop bit to reset |
| 167 | + timer_delay_us(30); // add delay enough for min bus free time (required by adafruit seesaw for one) |
| 168 | +} |
| 169 | + |
| 170 | +static bool do_transmit(uint8_t byte, i2c_stat_t expected_status) { |
| 171 | + module.twi->regs.data = byte; |
| 172 | + i2c_stat_t status = wait_completion(); |
| 173 | + return status == expected_status; |
| 174 | +} |
| 175 | + |
| 176 | +static bool do_receive(uint8_t *p, bool is_last) { |
| 177 | + i2c_stat_t expected_status = is_last? DATA_RECEIVE_NAK : DATA_RECEIVE_ACK; |
| 178 | + int response = is_last? 0: 1; // respond NAK for last, ACK otherwise |
| 179 | + module.twi->regs.cntr.ack = response; |
| 180 | + i2c_stat_t status = wait_completion(); |
| 181 | + *p = module.twi->regs.data; |
| 182 | + return status == expected_status; |
| 183 | +} |
| 184 | + |
| 185 | +bool i2c_block_read(i2c_device_t *dev, uint8_t *bytes, int n) { |
| 186 | + assert(dev); |
| 187 | + memset(bytes, SENTINEL, n); // init distinctive pattern to help debug read failure |
| 188 | + bool success = do_start(START_TRANSMIT); |
| 189 | + success = success && do_transmit((dev->addr << 1) | READ_BIT, ADDR_R_ACK); |
| 190 | + for (int i = 0; success && i < n; i++) { |
| 191 | + bool is_last = (i == n - 1); |
| 192 | + success = do_receive(&bytes[i], is_last); |
| 193 | + } |
| 194 | + do_stop(); |
| 195 | + return success; |
| 196 | +} |
| 197 | + |
| 198 | +bool i2c_block_write(i2c_device_t *dev, uint8_t *bytes, int n) { |
| 199 | + assert(dev); |
| 200 | + bool success = do_start(START_TRANSMIT); |
| 201 | + success = success && do_transmit((dev->addr << 1) | WRITE_BIT, ADDR_W_ACK); |
| 202 | + for (int i = 0; success && i < n; i++) { |
| 203 | + success = do_transmit(bytes[i], DATA_TRANSMIT_ACK); |
| 204 | + } |
| 205 | + do_stop(); |
| 206 | + return success; |
| 207 | +} |
0 commit comments