+/*
+ * (C) Copyright 2014 Leo C. <erbl259-lmu@yahoo.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
-/*
- * I2C (TWI) master interface.
+/*
+ * I2C (TWI) master interface.
*/
#include "common.h"
#include <avr/interrupt.h>
-#include <util/delay.h>
-#include <string.h>
#include "config.h"
#include "timer.h"
#include "debug.h"
#include "i2c.h"
-#ifdef DEBUG
-//# define DEBUG_I2C
-#endif
+#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 */
* 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
+ *i2c_result
*
* 0b10000000 Busy (Transmission in progress)
* 0b01000000 Timeout
- * 0b00001000 Start transmitted
+ * 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)
uint8_t buf[CONFIG_SYS_I2C_BUFSIZE];
} i2c_msg_t;
-static i2c_msg_t xmit;
+static volatile i2c_msg_t xmit;
ISR(TWI_vect)
{
- uint8_t next_twcr;
+ 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:
- xmit.idx = 0; /* reset xmit_buf index */
- xmit.stat = BUSY | START;
+ tmp_stat = BUSY | START;
+ tmp_idx = 0; /* reset xmit_buf index */
- tmp_idx = xmit.idx;
if (tmp_idx < xmit.len) { /* all bytes transmited? */
TWDR = xmit.buf[tmp_idx];
- xmit.idx = ++tmp_idx;
+ ++tmp_idx;
next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
} else {
- xmit.stat |= XMIT_DONE;
- xmit.stat &= ~BUSY;
+ tmp_stat |= XMIT_DONE;
+ tmp_stat &= ~BUSY;
next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
}
break;
case TWI_MTX_ADR_ACK:
- xmit.stat |= ADDR_ACK;
-
- tmp_idx = xmit.idx;
- if (tmp_idx < xmit.len) { /* all bytes transmited? */
- TWDR = xmit.buf[tmp_idx];
- xmit.idx = ++tmp_idx;
- next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
- } else {
- xmit.stat |= XMIT_DONE;
- xmit.stat &= ~BUSY;
- next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
- }
- break;
-
case TWI_MTX_DATA_ACK:
- xmit.stat |= DATA_ACK;
+ if ((twsr&0xf8) == TWI_MTX_ADR_ACK)
+ tmp_stat |= ADDR_ACK;
+ else
+ tmp_stat |= DATA_ACK;
- tmp_idx = xmit.idx;
if (tmp_idx < xmit.len) { /* all bytes transmited? */
TWDR = xmit.buf[tmp_idx];
- xmit.idx = ++tmp_idx;
+ ++tmp_idx;
next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
} else {
- xmit.stat |= XMIT_DONE;
- xmit.stat &= ~BUSY;
+ tmp_stat |= XMIT_DONE;
+ tmp_stat &= ~BUSY;
next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
}
-
break;
case TWI_MTX_DATA_NACK:
- xmit.stat |= XMIT_DONE;
- xmit.stat &= ~BUSY;
+ tmp_stat |= XMIT_DONE;
+ tmp_stat &= ~BUSY;
next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
break;
- case TWI_MRX_ADR_ACK:
- xmit.stat |= ADDR_ACK;
- tmp_idx = xmit.idx;
- if (tmp_idx < xmit.len-1) {
- next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
- } else {
- next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
- }
- break;
-
case TWI_MRX_DATA_ACK:
- xmit.stat |= DATA_ACK;
- tmp_idx = xmit.idx;
xmit.buf[tmp_idx] = TWDR;
- xmit.idx = ++tmp_idx;
- if (tmp_idx < xmit.len-1) {
+ ++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:
- xmit.stat |= ADDR_ACK | DATA_ACK;
+ tmp_stat |= ADDR_ACK | DATA_ACK;
- tmp_idx = xmit.idx;
xmit.buf[tmp_idx] = TWDR;
- xmit.idx = ++tmp_idx;
+ ++tmp_idx;
/* fall thru */
-
default:
- xmit.stat &= ~BUSY;
+ tmp_stat &= ~BUSY;
next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
break;
}
-#ifdef DEBUG_I2C
- debug("|%02x", twsr);
-#endif
+ xmit.stat = tmp_stat;
+ xmit.idx = tmp_idx;
+
+ debug_i2c("|%02x", twsr);
TWCR = next_twcr;
}
static void _init(void)
{
xmit.stat = 0;
-#ifdef DEBUG_I2C
- memset((void *) xmit.buf, 0xdf, sizeof(xmit.buf));
-#endif
/* 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
- TWBR = twbr;
TWDR = 0xff;
- TWCR = TWI_C_ENABLE + twps;
+ TWBR = twbr;
+ TWSR = twps & 0x03;
+ TWCR = TWI_C_ENABLE;
}
void i2c_init(uint32_t speed)
{
twps = 0;
- uint32_t tmptwbr = F_CPU /2 / speed - 8;
-
- while (tmptwbr > 255) {
- tmptwbr >>= 4;
+ 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);
+ debug_cond((twps > 3), "*** TWCLK too low: %lu Hz\n", speed);
- twbr = (uint8_t) tmptwbr;
+ twbr = (uint8_t) tmp_twbr;
+
+ PRR0 &= ~_BV(PRTWI);
_init();
}
{
uint32_t timer = get_timer(0);
uint8_t timeout = 0;
-
+
do {
if (get_timer(timer) >= 30) {
timeout = TIMEOUT;
xmit.stat |= timeout;
-#ifdef DEBUG_I2C
+#if DEBUG_I2C
dump_ram((uint8_t *) &xmit, 4, "=== i2c_wait ready: (done)");
_delay_ms(30);
-#endif
+#endif
return xmit.stat;
}
-//static
+static
int i2c_send(uint8_t chip, uint16_t addr, uint8_t alen, uint8_t *buffer, int8_t len)
{
uint8_t i, n;
for (n = len + i; i < n; i++)
xmit.buf[i] = *buffer++;
xmit.len = i;
-
-#ifdef DEBUG_I2C
+
+#if DEBUG_I2C
dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_send");
_delay_ms(30);
-#endif
+#endif
/* Enable TWI, TWI int and initiate start condition */
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
return rc;
}
-//static
+static
int i2c_recv(uint8_t chip, uint8_t *buffer, int8_t len)
{
uint8_t rc;
xmit.len = len + 1;
xmit.buf[0] = (chip<<1) | 1;
-#ifdef DEBUG_I2C
+#if DEBUG_I2C
dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: before start");
_delay_ms(30);
#endif
TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
rc = i2c_waitready();
-#ifdef DEBUG_I2C
+#if DEBUG_I2C
dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: after completion");
_delay_ms(30);
#endif
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();
- debug("** i2c_write: result=0x%02x\n",rc);
return (rc & XMIT_DONE) != 0;
}
alen, len);
return -1;
}
-
+
if (alen != 0) {
i2c_send(chip, addr, alen, NULL, 0);
- }
+ }
rc = i2c_recv(chip, buffer, len);
- debug("** i2c_read: result=0x%02x\n",rc);
return !((rc & (XMIT_DONE|DATA_ACK)) == (XMIT_DONE|DATA_ACK));
}
-
-
-