diff --git a/drivers/i2c/i2c_sam0.c b/drivers/i2c/i2c_sam0.c index f52c78104602..70f17d1e241a 100644 --- a/drivers/i2c/i2c_sam0.c +++ b/drivers/i2c/i2c_sam0.c @@ -12,6 +12,7 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(i2c_sam0, CONFIG_I2C_LOG_LEVEL); @@ -22,6 +23,9 @@ LOG_MODULE_REGISTER(i2c_sam0, CONFIG_I2C_LOG_LEVEL); #define SERCOM_I2CM_CTRLA_MODE_I2C_MASTER SERCOM_I2CM_CTRLA_MODE(5) #endif +/* max i2c idle bus wait timeout is approx. 20us */ +#define I2C_IDLE_TIMEOUT 20 + struct i2c_sam0_dev_config { SercomI2cm *regs; uint32_t bitrate; @@ -50,14 +54,27 @@ struct i2c_sam0_msg { }; struct i2c_sam0_dev_data { - struct k_sem sem; + struct k_sem completion_sync; + struct k_sem transfer_sync; struct i2c_sam0_msg msg; + struct i2c_msg *msgs; + u8_t num_msgs; #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN struct device *dma; #endif + +#ifdef CONFIG_I2C_SLAVE + bool master_active; + struct i2c_slave_config *slave_cfg; + bool slave_attached; +#endif /* CONFIG_I2C_SLAVE */ }; +#ifdef CONFIG_I2C_SLAVE +static void i2c_sam0_slave_event(struct device *dev); +#endif /* CONFIG_I2C_SLAVE */ + #define DEV_NAME(dev) ((dev)->name) #define DEV_CFG(dev) \ ((const struct i2c_sam0_dev_config *const)(dev)->config_info) @@ -109,6 +126,9 @@ static bool i2c_sam0_terminate_on_error(struct device *dev) data->msg.status = i2c->STATUS.reg; + if (i2c->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { + i2c->CTRLB.bit.CMD = 3; + } /* * Clear all the flags that require an explicit clear * (as opposed to being cleared by ADDR writes, etc) @@ -119,13 +139,31 @@ static bool i2c_sam0_terminate_on_error(struct device *dev) #endif SERCOM_I2CM_STATUS_LOWTOUT | SERCOM_I2CM_STATUS_BUSERR; - wait_synchronization(i2c); + i2c->INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR | SERCOM_I2CM_INTFLAG_MB + | SERCOM_I2CM_INTFLAG_SB; i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; - k_sem_give(&data->sem); + k_sem_give(&data->completion_sync); return true; } +/* + * The i2c CMD bits can only be set when either the Slave on Bus + * interrupt flag (INTFLAG.SB) or Master on Bus interrupt flag + * (INTFLAG.MB) is '1'. See 28.10.2 + * + * Writing or reading the data register when SBEN is enabled clears + * the SB and MB interrupt status bits. + * + * So, the STOP command must be issued before the MB and SB interrupt + * flag bits are cleared; otherwise: + * 1. the CMD bits are ignored, + * 2. a STOP does not occur, + * 3. the bus remains in the OWNER state, + * 4. the clock (SCL) is held low. + * See 28.10.6 + */ + static void i2c_sam0_isr(void *arg) { struct device *dev = (struct device *)arg; @@ -133,50 +171,92 @@ static void i2c_sam0_isr(void *arg) const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev); SercomI2cm *i2c = cfg->regs; - /* Get present interrupts and clear them */ - uint32_t status = i2c->INTFLAG.reg; +#ifdef CONFIG_I2C_SLAVE + if (data->slave_attached && !data->master_active) { + i2c_sam0_slave_event(dev); + return; + } +#endif /* CONFIG_I2C_SLAVE */ + + int key = irq_lock(); - i2c->INTFLAG.reg = status; + /* Get present interrupts */ + uint32_t status = i2c->INTFLAG.reg; if (i2c_sam0_terminate_on_error(dev)) { + irq_unlock(key); return; } if (status & SERCOM_I2CM_INTFLAG_MB) { - if (!data->msg.size) { + if (data->msg.size == 0) { + if (data->msgs->flags & I2C_MSG_STOP) { + i2c->CTRLB.bit.CMD = 3; + } else if (data->num_msgs > 1) { + if ((data->msgs+1)->flags & I2C_MSG_RESTART) { + /* + * if next message is flagged + * to restart, clear MB flag and + * do not send STOP. + */ + i2c->INTFLAG.reg &= + ~SERCOM_I2CM_INTFLAG_MB; + } + } else { + /* default: send STOP */ + i2c->CTRLB.bit.CMD = 3; + } i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; - k_sem_give(&data->sem); + k_sem_give(&data->completion_sync); + irq_unlock(key); return; } i2c->DATA.reg = *data->msg.buffer; data->msg.buffer++; data->msg.size--; - - return; } if (status & SERCOM_I2CM_INTFLAG_SB) { if (data->msg.size == 1) { /* - * If this is the last byte, then prepare for an auto - * NACK before doing the actual read. This does not - * require write synchronization. - */ + * If this is the last byte, then prepare for an auto + * NACK before doing the actual read. This does not + * require write synchronization. + */ i2c->CTRLB.bit.ACKACT = 1; + if (data->msgs->flags & I2C_MSG_STOP) { + i2c->CTRLB.bit.CMD = 3; + } else if (data->num_msgs > 1) { + if ((data->msgs+1)->flags & I2C_MSG_RESTART) { + /* + * if next message is flagged + * to restart, clear SB flag and + * do not send STOP. + */ + i2c->INTFLAG.reg &= + ~SERCOM_I2CM_INTFLAG_SB; + } + } else { + /* default: send STOP */ + i2c->CTRLB.bit.CMD = 3; + } } *data->msg.buffer = i2c->DATA.reg; data->msg.buffer++; data->msg.size--; - if (!data->msg.size) { + if (data->msg.size == 0) { i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; - k_sem_give(&data->sem); + k_sem_give(&data->completion_sync); + irq_unlock(key); return; } - return; } + + irq_unlock(key); + return; } #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN @@ -204,7 +284,7 @@ static void i2c_sam0_dma_write_done(void *arg, uint32_t id, int error_code) data->msg.status = error_code; - k_sem_give(&data->sem); + k_sem_give(&data->completion_sync); return; } @@ -297,7 +377,7 @@ static void i2c_sam0_dma_read_done(void *arg, uint32_t id, int error_code) data->msg.status = error_code; - k_sem_give(&data->sem); + k_sem_give(&data->completion_sync); return; } @@ -378,15 +458,45 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs, const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev); SercomI2cm *i2c = cfg->regs; uint32_t addr_reg; + int ret = 0; + uint32_t i; if (!num_msgs) { return 0; } +#ifdef CONFIG_I2C_SLAVE + if (data->slave_attached) { + LOG_ERR("Driver is registered as slave"); + return -EBUSY; + } +#endif /* CONFIG_I2C_SLAVE */ + + i = 0; + wait_synchronization(i2c); + while (i2c->STATUS.bit.BUSSTATE != 1) { + if (i > I2C_IDLE_TIMEOUT) { + LOG_WRN("Bus is not idle"); + return -EBUSY; + } + k_busy_wait(1); + i++; + } + + if (k_sem_take(&data->transfer_sync, K_MSEC(1000)) == -EAGAIN) { + LOG_WRN("Failed to acquire sam0 i2c device lock"); + return -EAGAIN; + } + +#ifdef CONFIG_I2C_SLAVE + data->master_active = true; +#endif /* CONFIG_I2C_SLAVE */ + for (; num_msgs > 0;) { if (!msgs->len) { if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { - return -EINVAL; + ret = -EINVAL; + break; } } @@ -404,6 +514,8 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs, data->msg.buffer = msgs->buf; data->msg.size = msgs->len; data->msg.status = 0; + data->msgs = msgs; + data->num_msgs = num_msgs; addr_reg = addr << 1U; if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { @@ -418,7 +530,8 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs, #ifdef SERCOM_I2CM_ADDR_TENBITEN addr_reg |= SERCOM_I2CM_ADDR_TENBITEN; #else - return -ENOTSUP; + ret = -ENOTSUP; + break; #endif } @@ -466,41 +579,47 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs, irq_unlock(key); - /* Now wait for the ISR to handle everything */ - k_sem_take(&data->sem, K_FOREVER); + /* + * Wait for the ISR to handle bus transaction + * + * The following wait is a busy wait that prevents the kernel + * from idling and therefore prevents system power management + * until the bus transaction is complete. + */ + while (k_sem_take(&data->completion_sync, K_NO_WAIT) + == -EBUSY) { + /* busy wait for semaphore */ + } if (data->msg.status) { if (data->msg.status & SERCOM_I2CM_STATUS_ARBLOST) { LOG_DBG("Arbitration lost on %s", DEV_NAME(dev)); - return -EAGAIN; + ret = -EAGAIN; + break; + } else if (data->msg.status + & SERCOM_I2CM_STATUS_RXNACK) { + LOG_DBG("RXNACK on %s", DEV_NAME(dev)); + ret = -EAGAIN; + break; } LOG_ERR("Transaction error on %s: %08X", DEV_NAME(dev), data->msg.status); - return -EIO; - } - - if (msgs->flags & I2C_MSG_STOP) { - i2c->CTRLB.bit.CMD = 3; - } else if ((msgs->flags & I2C_MSG_RESTART) && num_msgs > 1) { - /* - * No action, since we do this automatically if we - * don't send an explicit stop - */ - } else { - /* - * Neither present, so assume we want to release - * the bus (by sending a stop) - */ - i2c->CTRLB.bit.CMD = 3; + ret = -EIO; + break; } num_msgs--; msgs++; } - return 0; +#ifdef CONFIG_I2C_SLAVE + data->master_active = false; +#endif /* CONFIG_I2C_SLAVE */ + + k_sem_give(&data->transfer_sync); + return ret; } static int i2c_sam0_set_apply_bitrate(struct device *dev, uint32_t config) @@ -644,7 +763,54 @@ static int i2c_sam0_configure(struct device *dev, uint32_t config) int retval; if (!(config & I2C_MODE_MASTER)) { - return -EINVAL; + /* configure for slave mode */ + i2c->CTRLA.bit.ENABLE = 0; + wait_synchronization(i2c); + + /* Disable all I2C interrupts */ + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_MASK; + + /* I2C mode, enable timeouts */ + i2c->CTRLA.reg = SERCOM_I2CS_CTRLA_MODE_I2C_SLAVE +#ifdef SERCOM_I2CS_CTRLA_LOWTOUTEN + | SERCOM_I2CS_CTRLA_LOWTOUTEN +#endif + | SERCOM_I2CS_CTRLA_RUNSTDBY; + wait_synchronization(i2c); + + /* + * Enable smart mode (auto ACK data received) + * and auto ack an address match + */ + i2c->CTRLB.reg = SERCOM_I2CS_CTRLB_SMEN; + wait_synchronization(i2c); + + i2c->CTRLA.bit.ENABLE = 1; + wait_synchronization(i2c); + } + + if (config & I2C_MODE_MASTER) { + /* configure for master mode */ + i2c->CTRLA.bit.ENABLE = 0; + wait_synchronization(i2c); + + /* Disable all I2C interrupts */ + i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; + + /* I2C mode, enable timeouts */ + i2c->CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER +#ifdef SERCOM_I2CM_CTRLA_LOWTOUTEN + | SERCOM_I2CM_CTRLA_LOWTOUTEN +#endif + | SERCOM_I2CM_CTRLA_INACTOUT(0x3); + wait_synchronization(i2c); + + /* Enable smart mode (auto ACK) */ + i2c->CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; + wait_synchronization(i2c); + + i2c->CTRLA.bit.ENABLE = 1; + wait_synchronization(i2c); } if (config & I2C_SPEED_MASK) { @@ -682,6 +848,15 @@ static int i2c_sam0_initialize(struct device *dev) GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_CLKEN; + /* + * The GCLK_SERCOM_SLOW clock is used to time the time-outs + * and must be configured to use a 32KHz oscillator. See 28.6.3.1 + * GCLK4 is configured to 32768 Hz + */ + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_SERCOMX_SLOW | + GCLK_CLKCTRL_GEN_GCLK4 | + GCLK_CLKCTRL_CLKEN; + /* Enable SERCOM clock in PM */ PM->APBCMASK.reg |= cfg->pm_apbcmask; #endif @@ -701,12 +876,13 @@ static int i2c_sam0_initialize(struct device *dev) wait_synchronization(i2c); retval = i2c_sam0_set_apply_bitrate(dev, - i2c_map_dt_bitrate(cfg->bitrate)); + i2c_map_dt_bitrate(cfg->bitrate)); if (retval != 0) { return retval; } - k_sem_init(&data->sem, 0, 1); + k_sem_init(&data->completion_sync, 0, 1); + k_sem_init(&data->transfer_sync, 1, 1); cfg->irq_config_func(dev); @@ -716,6 +892,8 @@ static int i2c_sam0_initialize(struct device *dev) #endif + i2c->DBGCTRL.bit.DBGSTOP = 1; + i2c->CTRLA.bit.ENABLE = 1; wait_synchronization(i2c); @@ -723,13 +901,211 @@ static int i2c_sam0_initialize(struct device *dev) i2c->STATUS.bit.BUSSTATE = 1; wait_synchronization(i2c); +#ifdef CONFIG_I2C_SLAVE + data->master_active = false; + data->slave_attached = false; +#endif /* CONFIG_I2C_SLAVE */ + return 0; } +#ifdef CONFIG_I2C_SLAVE +static void i2c_sam0_slave_event(struct device *dev) +{ + const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev); + struct i2c_sam0_dev_data *data = DEV_DATA(dev); + SercomI2cs *i2c = (SercomI2cs *)cfg->regs; + const struct i2c_slave_callbacks *slave_cb = + data->slave_cfg->callbacks; + + /* Get present interrupts */ + u32_t status = i2c->INTFLAG.reg; + + if (status & SERCOM_I2CS_INTFLAG_ERROR) { + /* error */ + if (i2c->STATUS.bit.LOWTOUT) { + LOG_ERR("slave SCL low timeout"); + } + + if (i2c->STATUS.bit.SEXTTOUT) { + LOG_ERR("slave SCL low extend timeout"); + } + + if (i2c->STATUS.bit.COLL || i2c->STATUS.bit.BUSERR) { + /* Bus error */ + i2c->STATUS.bit.COLL = 1; + i2c->STATUS.bit.BUSERR = 1; + LOG_ERR("slave bus error"); + } + i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_ERROR; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_ERROR; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_DRDY; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_PREC; + slave_cb->stop(data->slave_cfg); + return; + } + + if (status & SERCOM_I2CS_INTFLAG_PREC) { + /* stop */ + i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_ERROR; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_DRDY; + i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_PREC; + slave_cb->stop(data->slave_cfg); + } + + if (status & SERCOM_I2CS_INTFLAG_AMATCH) { + /* address match */ + i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH; + + if (!i2c->STATUS.bit.DIR) { + /* Master requested write */ + slave_cb->write_requested(data->slave_cfg); + } else { + /* Master requested read */ + u8_t val; + + slave_cb->read_requested(data->slave_cfg, &val); + while (!(i2c->INTFLAG.reg & SERCOM_I2CS_INTFLAG_DRDY)) { + /* wait for data ready */ + } + i2c->data.reg = val; + } + + i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_ERROR; + i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_DRDY; + i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_PREC; + i2c->CTRLB.bit.ACKACT = 0; + } + + if (status & SERCOM_I2CS_INTFLAG_DRDY) { + /* data ready */ + if (!i2c->STATUS.bit.DIR) { + /* Master requested write */ + u8_t val = i2c->data.reg; + + if (slave_cb->write_received(data->slave_cfg, val)) { + /* + * If write_received() returns !=0, then + * handle exception and send NACK to master + */ + } + } else { + /* Master requested read */ + u8_t val; + + if (!i2c->STATUS.bit.RXNACK) { + slave_cb->read_processed(data->slave_cfg, &val); + i2c->data.reg = val; + } else { + /* + * Master terminated the bus transaction. + * Clear DRDY flag + */ + i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_DRDY; + } + } + return; + } + +} + +/* Attach and start I2C as slave */ +int i2c_sam0_slave_register(struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev); + struct i2c_sam0_dev_data *data = DEV_DATA(dev); + SercomI2cs *i2c = (SercomI2cs *)cfg->regs; + u32_t bitrate_cfg; + int ret; + + if (!config) { + return -EINVAL; + } + + if (data->slave_attached) { + return -EBUSY; + } + + if (data->master_active) { + return -EBUSY; + } + + bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate) & ~I2C_MODE_MASTER; + + ret = i2c_sam0_configure(dev, bitrate_cfg); + if (ret < 0) { + LOG_ERR("i2c: failure initializing"); + return ret; + } + + i2c->CTRLA.bit.ENABLE = 0; + wait_synchronization((SercomI2cm *)i2c); + i2c->ADDR.reg = (config->address << 1); + i2c->CTRLA.bit.ENABLE = 1; + wait_synchronization((SercomI2cm *)i2c); + data->slave_cfg = config; + data->slave_attached = true; + + /* Enable Address Match interrupt */ + i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_AMATCH; + + LOG_DBG("i2c: slave registered"); + + return 0; +} + +int i2c_sam0_slave_unregister(struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev); + struct i2c_sam0_dev_data *data = DEV_DATA(dev); + SercomI2cs *i2c = (SercomI2cs *)cfg->regs; + int ret; + + if (!data->slave_attached) { + return -EINVAL; + } + + if (data->master_active) { + return -EBUSY; + } + + i2c->CTRLA.bit.ENABLE = 0; + wait_synchronization((SercomI2cm *)i2c); + + /* Disable all I2C interrupts */ + i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; + + /* Clear all pending I2C interrupts */ + i2c->INTFLAG.reg = i2c->INTFLAG.reg; + + data->slave_attached = false; + + u32_t bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate) | I2C_MODE_MASTER; + + ret = i2c_sam0_configure(dev, bitrate_cfg); + if (ret < 0) { + LOG_ERR("i2c: failure initializing"); + return ret; + } + + LOG_DBG("i2c: slave unregistered"); + + return 0; +} + +#endif /* CONFIG_I2C_SLAVE */ + static const struct i2c_driver_api i2c_sam0_driver_api = { .configure = i2c_sam0_configure, .transfer = i2c_sam0_transfer, +#ifdef CONFIG_I2C_SLAVE + .slave_register = i2c_sam0_slave_register, + .slave_unregister = i2c_sam0_slave_unregister, +#endif /* CONFIG_I2C_SLAVE */ }; #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN diff --git a/soc/arm/atmel_sam0/common/soc_samd2x.c b/soc/arm/atmel_sam0/common/soc_samd2x.c index 89dca11380ca..8cae21ade2ca 100644 --- a/soc/arm/atmel_sam0/common/soc_samd2x.c +++ b/soc/arm/atmel_sam0/common/soc_samd2x.c @@ -152,6 +152,11 @@ static void gclks_init(void) GCLK_GENCTRL_ID(3) | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_GENEN; wait_gclk_synchronization(); + /* XOSC32K/1 -> GCLK4 */ + GCLK->GENCTRL.reg = + GCLK_GENCTRL_ID(4) | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_GENEN; + wait_gclk_synchronization(); + /* OSCULP32K/32 -> GCLK2 */ GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(32 - 1); wait_gclk_synchronization(); diff --git a/soc/arm/atmel_sam0/samd21/soc.h b/soc/arm/atmel_sam0/samd21/soc.h index a886570321e5..4bf856435610 100644 --- a/soc/arm/atmel_sam0/samd21/soc.h +++ b/soc/arm/atmel_sam0/samd21/soc.h @@ -70,6 +70,10 @@ #endif #define SOC_ATMEL_SAM0_GCLK3_FREQ_HZ SOC_ATMEL_SAM0_OSC8M_FREQ_HZ + +/** GCLK4 Frequency */ +#define SOC_ATMEL_SAM0_GCLK4_FREQ_HZ SOC_ATMEL_SAM0_XOSC32K_FREQ_HZ + #define SOC_ATMEL_SAM0_APBA_FREQ_HZ SOC_ATMEL_SAM0_MCK_FREQ_HZ #define SOC_ATMEL_SAM0_APBB_FREQ_HZ SOC_ATMEL_SAM0_MCK_FREQ_HZ #define SOC_ATMEL_SAM0_APBC_FREQ_HZ SOC_ATMEL_SAM0_MCK_FREQ_HZ