cpu/sam0_common: I2C: add support for SAMD20

This commit is contained in:
Benjamin Valentin 2020-06-17 18:06:25 +02:00
parent 81e06f46a4
commit 93d27bb9a0

View File

@ -73,6 +73,27 @@ static inline SercomI2cm *bus(i2c_t dev)
return i2c_config[dev].dev;
}
static void _syncbusy(SercomI2cm *dev)
{
#ifdef SERCOM_I2CM_STATUS_SYNCBUSY
while (dev->STATUS.bit.SYNCBUSY) {}
#else
while (dev->SYNCBUSY.reg) {}
#endif
}
static void _reset(SercomI2cm *dev)
{
dev->CTRLA.reg |= SERCOM_SPI_CTRLA_SWRST;
while (dev->CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) {}
#ifdef SERCOM_I2CM_STATUS_SYNCBUSY
while (dev->STATUS.bit.SYNCBUSY) {}
#else
while (dev->SYNCBUSY.bit.SWRST) {}
#endif
}
void i2c_init(i2c_t dev)
{
uint32_t timeout_counter = 0;
@ -85,12 +106,12 @@ void i2c_init(i2c_t dev)
/* Initialize mutex */
mutex_init(&locks[dev]);
/* DISABLE I2C MASTER */
_i2c_poweroff(dev);
/* Reset I2C */
bus(dev)->CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST;
while (bus(dev)->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_reset(bus(dev));
/* Turn on power manager for sercom */
sercom_clk_en(bus(dev));
@ -114,25 +135,32 @@ void i2c_init(i2c_t dev)
gpio_init_mux(i2c_config[dev].scl_pin, i2c_config[dev].mux);
/* I2C CONFIGURATION */
while (bus(dev)->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_syncbusy(bus(dev));
/* Set sercom module to operate in I2C master mode and run in Standby
if user requests it */
bus(dev)->CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER |
((i2c_config[dev].flags & I2C_FLAG_RUN_STANDBY) ?
if user requests it */
bus(dev)->CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER
| ((i2c_config[dev].flags & I2C_FLAG_RUN_STANDBY) ?
SERCOM_I2CM_CTRLA_RUNSTDBY : 0);
/* Enable Smart Mode (ACK is sent when DATA.DATA is read) */
bus(dev)->CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
/* Set SPEED */
#ifdef SERCOM_I2CM_CTRLA_SPEED
/* > 1000 kHz */
if (fSCL > I2C_SPEED_FAST_PLUS) {
bus(dev)->CTRLA.reg |= SERCOM_I2CM_CTRLA_SPEED(2);
/* > 400 kHz */
} else if (fSCL > I2C_SPEED_FAST) {
bus(dev)->CTRLA.reg |= SERCOM_I2CM_CTRLA_SPEED(1);
/* ≤ 400 kHz */
} else {
bus(dev)->CTRLA.reg |= SERCOM_I2CM_CTRLA_SPEED(0);
}
#else
assert(fSCL < I2C_SPEED_FAST_PLUS);
#endif
/* Get the baudrate */
/* fSCL = fGCLK / (10 + 2 * BAUD) -> BAUD = fGCLK / (2 * fSCL) - 5 */
@ -144,9 +172,12 @@ void i2c_init(i2c_t dev)
/* Ensure baudrate is within limits */
assert(tmp_baud < 255 && tmp_baud > 0);
#ifdef SERCOM_I2CM_BAUD_HSBAUD
if (fSCL > I2C_SPEED_FAST_PLUS) {
bus(dev)->BAUD.reg = SERCOM_I2CM_BAUD_HSBAUD(tmp_baud);
} else {
} else
#endif
{
bus(dev)->BAUD.reg = SERCOM_I2CM_BAUD_BAUD(tmp_baud);
}
@ -276,7 +307,7 @@ void _i2c_poweron(i2c_t dev)
return;
}
bus(dev)->CTRLA.bit.ENABLE = 1;
while (bus(dev)->SYNCBUSY.bit.ENABLE) {}
_syncbusy(bus(dev));
}
void _i2c_poweroff(i2c_t dev)
@ -287,21 +318,21 @@ void _i2c_poweroff(i2c_t dev)
return;
}
bus(dev)->CTRLA.bit.ENABLE = 0;
while (bus(dev)->SYNCBUSY.bit.ENABLE) {}
_syncbusy(bus(dev));
}
static int _start(SercomI2cm *dev, uint16_t addr)
{
/* Wait for hardware module to sync */
DEBUG("Wait for device to be ready\n");
while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_syncbusy(dev);
/* Set action to ACK. */
dev->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
/* Send Start | Address | Write/Read */
DEBUG("Generate start condition by sending address\n");
dev->ADDR.reg = (addr) | (0 << SERCOM_I2CM_ADDR_HS_Pos);
dev->ADDR.reg = addr;
/* Wait for response on bus. */
if (addr & I2C_READ) {
@ -353,7 +384,7 @@ static inline int _write(SercomI2cm *dev, const uint8_t *data, int length,
}
/* Wait for hardware module to sync */
while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_syncbusy(dev);
DEBUG("Written byte #%i to data reg, now waiting for DR"
" to be empty again\n", count);
@ -395,7 +426,8 @@ static inline int _read(SercomI2cm *dev, uint8_t *data, int length,
}
/* Wait for hardware module to sync */
while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_syncbusy(dev);
/* Check if this is the last byte to read */
if (length == 0 && stop) {
/* Send NACK before STOP */
@ -420,7 +452,7 @@ static inline int _read(SercomI2cm *dev, uint8_t *data, int length,
static inline void _stop(SercomI2cm *dev)
{
/* Wait for hardware module to sync */
while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {}
_syncbusy(dev);
/* Stop command */
dev->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
DEBUG("Stop sent\n");