Skip to content

Commit 8c5cad9

Browse files
committed
drivers: i2c_sam0.c: add i2c slave
The existing sam0 i2c driver does not support slave mode. This commit enables slave support for sam0 devices. The Slave support is based on Linux I2C Slave support by Wolfram Sang and documented at https://www.kernel.org/doc/Documentation/i2c/slave-interface The code for this commit is largely based on code taken from PR #5224 and the stm mcu implementation. This was modified to work with the sam0 SERCOM perhiperal and integrated into the sam0 driver. A slave must wait for data ready when there is an address match to ensure the data is valid. Disable SCLSM (SCL stetch mode) when operating in slave mode to ensure that i2c ACK's are handled properly. Fix k_timeout_t. Signed-off-by: Steven Slupsky <[email protected]>
1 parent 669b756 commit 8c5cad9

File tree

1 file changed

+256
-3
lines changed

1 file changed

+256
-3
lines changed

drivers/i2c/i2c_sam0.c

Lines changed: 256 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,17 @@ struct i2c_sam0_dev_data {
6464
struct device *dma;
6565
#endif
6666

67+
#ifdef CONFIG_I2C_SLAVE
68+
bool master_active;
69+
struct i2c_slave_config *slave_cfg;
70+
bool slave_attached;
71+
#endif /* CONFIG_I2C_SLAVE */
6772
};
6873

74+
#ifdef CONFIG_I2C_SLAVE
75+
static void i2c_sam0_slave_event(struct device *dev);
76+
#endif /* CONFIG_I2C_SLAVE */
77+
6978
#define DEV_NAME(dev) ((dev)->name)
7079
#define DEV_CFG(dev) \
7180
((const struct i2c_sam0_dev_config *const)(dev)->config_info)
@@ -162,6 +171,13 @@ static void i2c_sam0_isr(void *arg)
162171
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
163172
SercomI2cm *i2c = cfg->regs;
164173

174+
#ifdef CONFIG_I2C_SLAVE
175+
if (data->slave_attached && !data->master_active) {
176+
i2c_sam0_slave_event(dev);
177+
return;
178+
}
179+
#endif /* CONFIG_I2C_SLAVE */
180+
165181
int key = irq_lock();
166182

167183
/* Get present interrupts */
@@ -449,6 +465,13 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs,
449465
return 0;
450466
}
451467

468+
#ifdef CONFIG_I2C_SLAVE
469+
if (data->slave_attached) {
470+
LOG_ERR("Driver is registered as slave");
471+
return -EBUSY;
472+
}
473+
#endif /* CONFIG_I2C_SLAVE */
474+
452475
i = 0;
453476
wait_synchronization(i2c);
454477
while (i2c->STATUS.bit.BUSSTATE != 1) {
@@ -460,11 +483,15 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs,
460483
i++;
461484
}
462485

463-
if (k_sem_take(&data->transfer_sync, 1000) == -EAGAIN) {
486+
if (k_sem_take(&data->transfer_sync, K_MSEC(1000)) == -EAGAIN) {
464487
LOG_WRN("Failed to acquire sam0 i2c device lock");
465488
return -EAGAIN;
466489
}
467490

491+
#ifdef CONFIG_I2C_SLAVE
492+
data->master_active = true;
493+
#endif /* CONFIG_I2C_SLAVE */
494+
468495
for (; num_msgs > 0;) {
469496
if (!msgs->len) {
470497
if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
@@ -587,6 +614,10 @@ static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs,
587614
msgs++;
588615
}
589616

617+
#ifdef CONFIG_I2C_SLAVE
618+
data->master_active = false;
619+
#endif /* CONFIG_I2C_SLAVE */
620+
590621
k_sem_give(&data->transfer_sync);
591622
return ret;
592623
}
@@ -727,16 +758,39 @@ static int i2c_sam0_set_apply_bitrate(struct device *dev, uint32_t config)
727758

728759
static int i2c_sam0_configure(struct device *dev, uint32_t config)
729760
{
730-
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
731761
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
732762
SercomI2cm *i2c = cfg->regs;
733763
int retval;
734764

735765
if (!(config & I2C_MODE_MASTER)) {
736-
return -EINVAL;
766+
/* configure for slave mode */
767+
i2c->CTRLA.bit.ENABLE = 0;
768+
wait_synchronization(i2c);
769+
770+
/* Disable all I2C interrupts */
771+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_MASK;
772+
773+
/* I2C mode, enable timeouts */
774+
i2c->CTRLA.reg = SERCOM_I2CS_CTRLA_MODE_I2C_SLAVE
775+
#ifdef SERCOM_I2CS_CTRLA_LOWTOUTEN
776+
| SERCOM_I2CS_CTRLA_LOWTOUTEN
777+
#endif
778+
| SERCOM_I2CS_CTRLA_RUNSTDBY;
779+
wait_synchronization(i2c);
780+
781+
/*
782+
* Enable smart mode (auto ACK data received)
783+
* and auto ack an address match
784+
*/
785+
i2c->CTRLB.reg = SERCOM_I2CS_CTRLB_SMEN;
786+
wait_synchronization(i2c);
787+
788+
i2c->CTRLA.bit.ENABLE = 1;
789+
wait_synchronization(i2c);
737790
}
738791

739792
if (config & I2C_MODE_MASTER) {
793+
/* configure for master mode */
740794
i2c->CTRLA.bit.ENABLE = 0;
741795
wait_synchronization(i2c);
742796

@@ -847,12 +901,211 @@ static int i2c_sam0_initialize(struct device *dev)
847901
i2c->STATUS.bit.BUSSTATE = 1;
848902
wait_synchronization(i2c);
849903

904+
#ifdef CONFIG_I2C_SLAVE
905+
data->master_active = false;
906+
data->slave_attached = false;
907+
#endif /* CONFIG_I2C_SLAVE */
908+
850909
return 0;
851910
}
852911

912+
#ifdef CONFIG_I2C_SLAVE
913+
static void i2c_sam0_slave_event(struct device *dev)
914+
{
915+
const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev);
916+
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
917+
SercomI2cs *i2c = (SercomI2cs *)cfg->regs;
918+
const struct i2c_slave_callbacks *slave_cb =
919+
data->slave_cfg->callbacks;
920+
921+
/* Get present interrupts */
922+
u32_t status = i2c->INTFLAG.reg;
923+
924+
if (status & SERCOM_I2CS_INTFLAG_ERROR) {
925+
/* error */
926+
if (i2c->STATUS.bit.LOWTOUT) {
927+
LOG_ERR("slave SCL low timeout");
928+
}
929+
930+
if (i2c->STATUS.bit.SEXTTOUT) {
931+
LOG_ERR("slave SCL low extend timeout");
932+
}
933+
934+
if (i2c->STATUS.bit.COLL || i2c->STATUS.bit.BUSERR) {
935+
/* Bus error */
936+
i2c->STATUS.bit.COLL = 1;
937+
i2c->STATUS.bit.BUSERR = 1;
938+
LOG_ERR("slave bus error");
939+
}
940+
i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_ERROR;
941+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_ERROR;
942+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_DRDY;
943+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_PREC;
944+
slave_cb->stop(data->slave_cfg);
945+
return;
946+
}
947+
948+
if (status & SERCOM_I2CS_INTFLAG_PREC) {
949+
/* stop */
950+
i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC;
951+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_ERROR;
952+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_DRDY;
953+
i2c->INTENCLR.reg = SERCOM_I2CS_INTENCLR_PREC;
954+
slave_cb->stop(data->slave_cfg);
955+
}
956+
957+
if (status & SERCOM_I2CS_INTFLAG_AMATCH) {
958+
/* address match */
959+
i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH;
960+
961+
if (!i2c->STATUS.bit.DIR) {
962+
/* Master requested write */
963+
slave_cb->write_requested(data->slave_cfg);
964+
} else {
965+
/* Master requested read */
966+
u8_t val;
967+
968+
slave_cb->read_requested(data->slave_cfg, &val);
969+
while (!(i2c->INTFLAG.reg & SERCOM_I2CS_INTFLAG_DRDY)) {
970+
/* wait for data ready */
971+
}
972+
i2c->data.reg = val;
973+
}
974+
975+
i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_ERROR;
976+
i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_DRDY;
977+
i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_PREC;
978+
i2c->CTRLB.bit.ACKACT = 0;
979+
}
980+
981+
if (status & SERCOM_I2CS_INTFLAG_DRDY) {
982+
/* data ready */
983+
if (!i2c->STATUS.bit.DIR) {
984+
/* Master requested write */
985+
u8_t val = i2c->data.reg;
986+
987+
if (slave_cb->write_received(data->slave_cfg, val)) {
988+
/*
989+
* If write_received() returns !=0, then
990+
* handle exception and send NACK to master
991+
*/
992+
}
993+
} else {
994+
/* Master requested read */
995+
u8_t val;
996+
997+
if (!i2c->STATUS.bit.RXNACK) {
998+
slave_cb->read_processed(data->slave_cfg, &val);
999+
i2c->data.reg = val;
1000+
} else {
1001+
/*
1002+
* Master terminated the bus transaction.
1003+
* Clear DRDY flag
1004+
*/
1005+
i2c->INTFLAG.reg = SERCOM_I2CS_INTFLAG_DRDY;
1006+
}
1007+
}
1008+
return;
1009+
}
1010+
1011+
}
1012+
1013+
/* Attach and start I2C as slave */
1014+
int i2c_sam0_slave_register(struct device *dev,
1015+
struct i2c_slave_config *config)
1016+
{
1017+
const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev);
1018+
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
1019+
SercomI2cs *i2c = (SercomI2cs *)cfg->regs;
1020+
u32_t bitrate_cfg;
1021+
int ret;
1022+
1023+
if (!config) {
1024+
return -EINVAL;
1025+
}
1026+
1027+
if (data->slave_attached) {
1028+
return -EBUSY;
1029+
}
1030+
1031+
if (data->master_active) {
1032+
return -EBUSY;
1033+
}
1034+
1035+
bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate) & ~I2C_MODE_MASTER;
1036+
1037+
ret = i2c_sam0_configure(dev, bitrate_cfg);
1038+
if (ret < 0) {
1039+
LOG_ERR("i2c: failure initializing");
1040+
return ret;
1041+
}
1042+
1043+
i2c->CTRLA.bit.ENABLE = 0;
1044+
wait_synchronization((SercomI2cm *)i2c);
1045+
i2c->ADDR.reg = (config->address << 1);
1046+
i2c->CTRLA.bit.ENABLE = 1;
1047+
wait_synchronization((SercomI2cm *)i2c);
1048+
data->slave_cfg = config;
1049+
data->slave_attached = true;
1050+
1051+
/* Enable Address Match interrupt */
1052+
i2c->INTENSET.reg = SERCOM_I2CS_INTENSET_AMATCH;
1053+
1054+
LOG_DBG("i2c: slave registered");
1055+
1056+
return 0;
1057+
}
1058+
1059+
int i2c_sam0_slave_unregister(struct device *dev,
1060+
struct i2c_slave_config *config)
1061+
{
1062+
const struct i2c_sam0_dev_config *cfg = DEV_CFG(dev);
1063+
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
1064+
SercomI2cs *i2c = (SercomI2cs *)cfg->regs;
1065+
int ret;
1066+
1067+
if (!data->slave_attached) {
1068+
return -EINVAL;
1069+
}
1070+
1071+
if (data->master_active) {
1072+
return -EBUSY;
1073+
}
1074+
1075+
i2c->CTRLA.bit.ENABLE = 0;
1076+
wait_synchronization((SercomI2cm *)i2c);
1077+
1078+
/* Disable all I2C interrupts */
1079+
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
1080+
1081+
/* Clear all pending I2C interrupts */
1082+
i2c->INTFLAG.reg = i2c->INTFLAG.reg;
1083+
1084+
data->slave_attached = false;
1085+
1086+
u32_t bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate) | I2C_MODE_MASTER;
1087+
1088+
ret = i2c_sam0_configure(dev, bitrate_cfg);
1089+
if (ret < 0) {
1090+
LOG_ERR("i2c: failure initializing");
1091+
return ret;
1092+
}
1093+
1094+
LOG_DBG("i2c: slave unregistered");
1095+
1096+
return 0;
1097+
}
1098+
1099+
#endif /* CONFIG_I2C_SLAVE */
1100+
1101+
8531102
static const struct i2c_driver_api i2c_sam0_driver_api = {
8541103
.configure = i2c_sam0_configure,
8551104
.transfer = i2c_sam0_transfer,
1105+
#ifdef CONFIG_I2C_SLAVE
1106+
.slave_register = i2c_sam0_slave_register,
1107+
.slave_unregister = i2c_sam0_slave_unregister,
1108+
#endif /* CONFIG_I2C_SLAVE */
8561109
};
8571110

8581111
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN

0 commit comments

Comments
 (0)