diff options
Diffstat (limited to 'avr/i2c.c')
-rw-r--r-- | avr/i2c.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/avr/i2c.c b/avr/i2c.c new file mode 100644 index 0000000..ae2f8da --- /dev/null +++ b/avr/i2c.c @@ -0,0 +1,378 @@ +/* + * (C) Copyright 2014 Leo C. <erbl259-lmu@yahoo.de> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/* + * I2C (TWI) master interface. + */ + +#include "common.h" +#include <avr/interrupt.h> +#include <string.h> + +#include "config.h" +#include "timer.h" +#include "debug.h" +#include "i2c.h" + +#define DEBUG_I2C 0 + +#define debug_i2c(fmt, args...) \ + debug_cond(DEBUG_I2C, fmt, ##args) + + +/* General TWI Master status codes */ +#define TWI_START 0x08 /* START has been transmitted */ +#define TWI_REP_START 0x10 /* Repeated START has been transmitted */ +#define TWI_ARB_LOST 0x38 /* Arbitration lost */ + +/* TWI Master Transmitter status codes */ +#define TWI_MTX_ADR_ACK 0x18 /* SLA+W has been transmitted and ACK received */ +#define TWI_MTX_ADR_NACK 0x20 /* SLA+W has been transmitted and NACK received */ +#define TWI_MTX_DATA_ACK 0x28 /* Data byte has been transmitted and ACK received */ +#define TWI_MTX_DATA_NACK 0x30 /* Data byte has been transmitted and NACK received */ + +/* TWI Master Receiver status codes */ +#define TWI_MRX_ADR_ACK 0x40 /* SLA+R has been transmitted and ACK received */ +#define TWI_MRX_ADR_NACK 0x48 /* SLA+R has been transmitted and NACK received */ +#define TWI_MRX_DATA_ACK 0x50 /* Data byte has been received and ACK transmitted */ +#define TWI_MRX_DATA_NACK 0x58 /* Data byte has been received and NACK transmitted */ + +/* TWI Miscellaneous status codes */ +#define TWI_NO_STATE 0xF8 /* No relevant state information available */ +#define TWI_BUS_ERROR 0x00 /* Bus error due to an illegal START or STOP condition */ + + +/* + * TWINT: TWI Interrupt Flag + * TWEA: TWI Enable Acknowledge Bit + * TWSTA: TWI START Condition Bit + * TWSTO: TWI STOP Condition Bit + * TWEN: TWI Enable Bit + * TWIE: TWI Interrupt Enable + * + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT) + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)| (1<<TWEA) + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT) + * + * default: + * (1<<TWEN)| (1<<TWINT)| (1<<TWSTO) + * + * Init: + * (1<<TWEN) + * + * start read/write: + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA) + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA) + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA) + * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA) + * + * wait ready: + * (1<<TWIE)|(1<<TWSTO) + * + * + * + *i2c_result + * + * 0b10000000 Busy (Transmission in progress) + * 0b01000000 Timeout + * 0b00001000 Start transmitted + * 0b00000100 Slave acknowledged address + * 0b00000010 Data byte(s) transmitted/received + * 0b00000001 Transmission completed + * + * + *---------------------------------------------------------------------- + */ + +#define TWI_C_DISABLE 0x00 +#define TWI_C_ENABLE (1<<TWEN) + + + + typedef struct i2c_msg_s { + uint8_t stat; + #define XMIT_DONE (1<<0) + #define DATA_ACK (1<<1) + #define ADDR_ACK (1<<2) + #define START (1<<3) + #define TIMEOUT (1<<6) + #define BUSY (1<<7) + uint8_t idx; + uint8_t len; + uint8_t buf[CONFIG_SYS_I2C_BUFSIZE]; +} i2c_msg_t; + +static volatile i2c_msg_t xmit; + +ISR(TWI_vect) +{ + uint8_t tmp_stat; + uint8_t tmp_idx; + uint8_t next_twcr; + uint8_t n; + + tmp_idx = xmit.idx; + tmp_stat = xmit.stat; + + uint8_t twsr = TWSR; + + switch (twsr & 0xf8) { + + case TWI_START: + case TWI_REP_START: + tmp_stat = BUSY | START; + tmp_idx = 0; /* reset xmit_buf index */ + + if (tmp_idx < xmit.len) { /* all bytes transmited? */ + TWDR = xmit.buf[tmp_idx]; + ++tmp_idx; + next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT); + } else { + tmp_stat |= XMIT_DONE; + tmp_stat &= ~BUSY; + next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO); + } + break; + + case TWI_MTX_ADR_ACK: + case TWI_MTX_DATA_ACK: + if ((twsr&0xf8) == TWI_MTX_ADR_ACK) + tmp_stat |= ADDR_ACK; + else + tmp_stat |= DATA_ACK; + + if (tmp_idx < xmit.len) { /* all bytes transmited? */ + TWDR = xmit.buf[tmp_idx]; + ++tmp_idx; + next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT); + } else { + tmp_stat |= XMIT_DONE; + tmp_stat &= ~BUSY; + next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO); + } + break; + + case TWI_MTX_DATA_NACK: + tmp_stat |= XMIT_DONE; + tmp_stat &= ~BUSY; + next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO); + break; + + case TWI_MRX_DATA_ACK: + xmit.buf[tmp_idx] = TWDR; + ++tmp_idx; + /* fall thru */ + case TWI_MRX_ADR_ACK: + if ((twsr&0xf8) == TWI_MRX_ADR_ACK) + tmp_stat |= ADDR_ACK; + else + tmp_stat |= DATA_ACK; + + n = xmit.len-1; + if (tmp_idx < n) { + next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA); + } else { + next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT); + } + break; + + case TWI_MRX_DATA_NACK: + tmp_stat |= ADDR_ACK | DATA_ACK; + + xmit.buf[tmp_idx] = TWDR; + ++tmp_idx; + /* fall thru */ + default: + tmp_stat &= ~BUSY; + next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO); + break; + } + + xmit.stat = tmp_stat; + xmit.idx = tmp_idx; + + debug_i2c("|%02x", twsr); + TWCR = next_twcr; +} + + +/*------------------------------------------------------------------*/ + +static uint8_t twps; +static uint8_t twbr; + + +static void _init(void) +{ + xmit.stat = 0; + + /* Disable TWI, disable TWI interrupt. */ + /* (Reset TWI hardware state machine.) */ + TWCR = TWI_C_DISABLE; + _delay_us(5); +#if DEBUG_I2C + memset((void *) xmit.buf, 0xdf, sizeof(xmit.buf)); +#endif + + TWDR = 0xff; + TWBR = twbr; + TWSR = twps & 0x03; + TWCR = TWI_C_ENABLE; +} + +void i2c_init(uint32_t speed) +{ + twps = 0; + uint32_t tmp_twbr = F_CPU /2 / speed - 8; + + while (tmp_twbr > 255) { + tmp_twbr >>= 4; + twps += 1; + } + debug_cond((twps > 3), "*** TWCLK too low: %lu Hz\n", speed); + + twbr = (uint8_t) tmp_twbr; + + PRR0 &= ~_BV(PRTWI); + _init(); +} + + +int_fast8_t i2c_waitready(void) +{ + uint32_t timer = get_timer(0); + uint8_t timeout = 0; + + do { + if (get_timer(timer) >= 30) { + timeout = TIMEOUT; + _init(); + } + } while ((TWCR & ((1<<TWIE)|(1<<TWSTO))) != 0 && !timeout); + + xmit.stat |= timeout; + +#if DEBUG_I2C + dump_ram((uint8_t *) &xmit, 4, "=== i2c_wait ready: (done)"); + _delay_ms(30); +#endif + return xmit.stat; +} + +static +int i2c_send(uint8_t chip, uint16_t addr, uint8_t alen, uint8_t *buffer, int8_t len) +{ + uint8_t i, n; + uint8_t rc; + + rc = i2c_waitready(); + if ((rc & (BUSY | TIMEOUT)) != 0) + return rc; + + xmit.stat = BUSY; + xmit.buf[0] = chip<<1; + for (i = 1; i < alen+1; i++) { + xmit.buf[i] = (uint8_t) addr; + addr >>= 8; + } + for (n = len + i; i < n; i++) + xmit.buf[i] = *buffer++; + xmit.len = i; + +#if DEBUG_I2C + dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_send"); + _delay_ms(30); +#endif + /* Enable TWI, TWI int and initiate start condition */ + TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA); + + rc = xmit.stat; + + return rc; +} + +static +int i2c_recv(uint8_t chip, uint8_t *buffer, int8_t len) +{ + uint8_t rc; + + rc = i2c_waitready(); + if ((rc & (BUSY | TIMEOUT)) != 0) + return rc; + + xmit.stat = BUSY; + xmit.len = len + 1; + xmit.buf[0] = (chip<<1) | 1; + +#if DEBUG_I2C + dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: before start"); + _delay_ms(30); +#endif + /* Enable TWI, TWI int and initiate start condition */ + TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA); + rc = i2c_waitready(); + +#if DEBUG_I2C + dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: after completion"); + _delay_ms(30); +#endif + if (rc & DATA_ACK) { + /* at least 1 byte received */ + for (uint8_t i=1, n=xmit.idx; i < n; i++) + *buffer++ = xmit.buf[i]; + } + + return rc; +} + +/* + * Read/Write interface: + * chip: I2C chip address, range 0..127 + * addr: Memory (register) address within the chip + * alen: Number of bytes to use for addr (typically 1, 2 for larger + * memories, 0 for register type devices with only one + * register) + * buffer: Where to read/write the data + * len: How many bytes to read/write + * + * Returns: 0 on success, not 0 on failure + */ + +int i2c_write(uint8_t chip, unsigned int addr, uint_fast8_t alen, + uint8_t *buffer, uint_fast8_t len) +{ + int rc; + + if ((alen > 2) || (1 + alen + len > CONFIG_SYS_I2C_BUFSIZE)) { + debug("** i2c_write: buffer overflow, alen: %u, len: %u\n", + alen, len); + return -1; + } + + i2c_send(chip, addr, alen, buffer, len); + rc = i2c_waitready(); + + return (rc & XMIT_DONE) != 0; +} + +int i2c_read(uint8_t chip, unsigned int addr, uint_fast8_t alen, + uint8_t *buffer, uint_fast8_t len) +{ + int rc; + + if ((alen > 2) || (1 + len > CONFIG_SYS_I2C_BUFSIZE)) { + debug("** i2c_read: parameter error: alen: %u, len: %u\n", + alen, len); + return -1; + } + + if (alen != 0) { + i2c_send(chip, addr, alen, NULL, 0); + } + rc = i2c_recv(chip, buffer, len); + + return !((rc & (XMIT_DONE|DATA_ACK)) == (XMIT_DONE|DATA_ACK)); +} |