nrf: fix stop condition race in i2c

Этот коммит содержится в:
Yurii Soldak 2022-01-18 21:52:36 +01:00 коммит произвёл Ron Evans
родитель b7bcb256d7
коммит 31ee1637df

Просмотреть файл

@ -225,6 +225,9 @@ type I2CConfig struct {
// Configure is intended to setup the I2C interface. // Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error { func (i2c *I2C) Configure(config I2CConfig) error {
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Disabled)
// Default I2C bus speed is 100 kHz. // Default I2C bus speed is 100 kHz.
if config.Frequency == 0 { if config.Frequency == 0 {
config.Frequency = TWI_FREQ_100KHZ config.Frequency = TWI_FREQ_100KHZ
@ -256,9 +259,10 @@ func (i2c *I2C) Configure(config I2CConfig) error {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100) i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
} }
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
i2c.setPins(config.SCL, config.SDA) i2c.setPins(config.SCL, config.SDA)
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
return nil return nil
} }
@ -266,16 +270,23 @@ func (i2c *I2C) Configure(config I2CConfig) error {
// It clocks out the given address, writes the bytes in w, reads back len(r) // It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus. // bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// Tricky stop condition.
// After reads, the stop condition is generated implicitly with a shortcut.
// After writes not followed by reads and in the case of errors, stop must be generated explicitly.
i2c.Bus.ADDRESS.Set(uint32(addr)) i2c.Bus.ADDRESS.Set(uint32(addr))
if len(w) != 0 { if len(w) != 0 {
i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing
for _, b := range w { for _, b := range w {
if err = i2c.writeByte(b); err != nil { if err = i2c.writeByte(b); err != nil {
goto cleanUp i2c.signalStop()
return
} }
} }
} }
if len(r) != 0 { if len(r) != 0 {
// To trigger suspend task when a byte is received // To trigger suspend task when a byte is received
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND) i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND)
@ -289,22 +300,25 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading
} }
if r[i], err = i2c.readByte(); err != nil { if r[i], err = i2c.readByte(); err != nil {
// goto/break are practically equivalent here, i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
// but goto makes this more easily understandable for maintenance. i2c.signalStop()
goto cleanUp return
} }
} }
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
}
// Stop explicitly when no reads were executed, stoping unconditionally would be a mistake.
// It may execute after I2C peripheral has already been stopped by the shortcut in the read block,
// so stop task will trigger first thing in a subsequent transaction, hanging it.
if len(r) == 0 {
i2c.signalStop()
} }
cleanUp:
i2c.signalStop()
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
return return
} }
// signalStop sends a stop signal when writing or tells the I2C peripheral that // signalStop sends a stop signal to the I2C peripheral and waits for confirmation.
// it must generate a stop condition after the next character is retrieved when
// reading.
func (i2c *I2C) signalStop() { func (i2c *I2C) signalStop() {
i2c.Bus.TASKS_STOP.Set(1) i2c.Bus.TASKS_STOP.Set(1)
for i2c.Bus.EVENTS_STOPPED.Get() == 0 { for i2c.Bus.EVENTS_STOPPED.Get() == 0 {
@ -312,7 +326,7 @@ func (i2c *I2C) signalStop() {
i2c.Bus.EVENTS_STOPPED.Set(0) i2c.Bus.EVENTS_STOPPED.Set(0)
} }
// writeByte writes a single byte to the I2C bus. // writeByte writes a single byte to the I2C bus and waits for confirmation.
func (i2c *I2C) writeByte(data byte) error { func (i2c *I2C) writeByte(data byte) error {
i2c.Bus.TXD.Set(uint32(data)) i2c.Bus.TXD.Set(uint32(data))
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 { for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
@ -325,7 +339,7 @@ func (i2c *I2C) writeByte(data byte) error {
return nil return nil
} }
// readByte reads a single byte from the I2C bus. // readByte reads a single byte from the I2C bus when it is ready.
func (i2c *I2C) readByte() (byte, error) { func (i2c *I2C) readByte() (byte, error) {
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 { for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {