-
Notifications
You must be signed in to change notification settings - Fork 2.1k
cpu/sam0: update I2C driver to new API #9198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only minor doxygen things and style. Not tested
@@ -41,6 +42,17 @@ extern "C" { | |||
#define PERIPH_SPI_NEEDS_TRANSFER_REGS | |||
/** @} */ | |||
|
|||
/** | |||
* @brief Use shared I2C functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be @name
#define PERIPH_I2C_NEED_READ_REGS | ||
#define PERIPH_I2C_NEED_WRITE_REG | ||
#define PERIPH_I2C_NEED_WRITE_REGS | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blank line could be removed
|
||
/** | ||
* @brief Override SPI clock speed values | ||
* @{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed
I2C_SPEED_FAST_PLUS = 1000000U, /**< fast plus mode: ~1Mbit/s */ | ||
I2C_SPEED_HIGH = 3400000U, /**< high speed mode: ~3.4Mbit/s */ | ||
} i2c_speed_t; | ||
/** @} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed
uint8_t gclk_src; /**< GCLK source which supplys SERCOM */ | ||
uint8_t flags; /**< allow SERCOM to run in standby mode */ | ||
} i2c_conf_t; | ||
/** @} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed
cpu/sam0_common/periph/i2c.c
Outdated
/* start transmission and send slave address */ | ||
if (_start(i2c, address, I2C_FLAG_READ) < 0) { | ||
return 0; | ||
if(!(flags & I2C_NOSTART)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a space after if
could be added
cpu/sam0_common/periph/i2c.c
Outdated
} | ||
/* read data to register */ | ||
if (_read(i2c, data, length) < 0) { | ||
if (_read(bus(dev), data, len) < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a space after if
could be added
cpu/sam0_common/periph/i2c.c
Outdated
/* start transmission and send slave address */ | ||
if (_start(i2c, address, I2C_FLAG_WRITE) < 0) { | ||
return 0; | ||
if(!(flags & I2C_NOSTOP)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a space after if
could be added
cpu/sam0_common/periph/i2c.c
Outdated
/* send register address and wait for complete transfer to be finished */ | ||
if (_write(i2c, ®, 1) < 0) { | ||
return 0; | ||
if(!(flags & I2C_NOSTART)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a space after if
could be added
cpu/sam0_common/periph/i2c.c
Outdated
{ | ||
if (sercom == NULL) { | ||
return; | ||
if(!(flags & I2C_NOSTOP)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a space after if
could be added
@aabadie I just pushed the required changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another set of comments but it looks good in general.
.gclk_src = GCLK_CLKCTRL_GEN_GCLK0, | ||
.flags = I2C_FLAG_NONE, | ||
}, | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: this opening curly brace is not aligned
gpio_t sda_pin; /**< used MOSI pin */ | ||
gpio_mux_t mux; /**< alternate function (mux) */ | ||
uint8_t gclk_src; /**< GCLK source which supplys SERCOM */ | ||
uint8_t flags; /**< allow SERCOM to run in standby mode */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IRQ management has been removed by the refactoring. Is this intentional ? Do you think it could be set in an attribute of this struct ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it was useless for SAM0 but now you bring this to the stage... I don't know if it could be useful for others arch.
I guess we could still add something later if it's needed right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with keeping it like this for now.
cpu/sam0_common/periph/i2c.c
Outdated
|
||
/* HACK: fixes cppcheck issue until #7588 is merged. */ | ||
if (I2CSercom == NULL) { | ||
if (dev >= I2C_NUMOF) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use an assert instead.
assert(dev < I2C_NUMOF);
cpu/sam0_common/periph/i2c.c
Outdated
#endif | ||
default: | ||
return -1; | ||
if (dev >= I2C_NUMOF) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about the assert, there might be others
cpu/sam0_common/periph/i2c.c
Outdated
/* start transmission and send slave address */ | ||
if (_start(i2c, address, I2C_FLAG_READ) < 0) { | ||
return 0; | ||
if (!(flags & I2C_NOSTART)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing opening {
, the if block should be surrounded with {}
even if it's a one liner. It improves readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops ...
Nice catch !
@aabadie I've addressed your comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found one minor remaining thing. You can squash directly when fixed.
cpu/sam0_common/periph/i2c.c
Outdated
static inline int _write(SercomI2cm *dev, const uint8_t *data, int length); | ||
static inline int _read(SercomI2cm *dev, uint8_t *data, int length); | ||
static inline void _stop(SercomI2cm *dev); | ||
static inline int _wait_for_response(SercomI2cm *dev, uint32_t max_timeout_counter); | ||
static void i2c_poweron(i2c_t dev); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, just notice this now: internal static functions should prefixed with an _
Squashed |
Travis is reporting trailing white spaces issues, can you have a look ? |
Here are the issues I found while testing this PR with the new periph_i2c test on a samr21. I found that if you try to acquire twice it waits forever instead of timeout or return with busy message. It also appears that all read and write calls return I2C_ACK regardless of success. A read/write reg will continue with the second action (after the initial write to reg) regardless of success. I haven't been able to get a true successful read from my device with this yet (but that may be my problem, I will confirm later). It seems that the device doesn't need to be acquired to be used, I am not sure if it is intentional or not. |
This is because the if (xxxx) return -1; has been replace by an assertion. Maybe it was not a good idea ?
I guess the old driver behaves the same and I still haven't find enough time to handle the error on the bus correctly. Maybe it will be done after the refactoring if I can't fix it quickly.
I think we will deal with it right now, because we must implement (in a near future ?) the acquire and release function with clock gating + pm_management. So it will be impossible to use the bus if acquire is not called. |
if (dev >= I2C_NUMOF) { | ||
return -1; | ||
} | ||
assert(dev < I2C_NUMOF); | ||
mutex_lock(&locks[dev]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the assert, I assume once everything is ported we can either remove the int return (because all failures would be asserted) or at least update the comments in the i2c.h driver.
Should the mutex_lock be used or the mutex_trylock or should there be an option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aabadie what's your opinion about assert vs return err_code ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for the assert. Briefly comparing with spi API, the spi_init returns void, I think it should be the same for i2c (sorry for noticing this only now).
So it appears that the _start fxn both sends the start, addr and automatically gets the first byte from that slave (feature or bug). Disabling the smart mode doesn't seem to effect it and on the register level I can't see why it would do that. Either way it should be fixed since read adds an extra byte (even though it isn't shown on the higher levels, the last byte seems to just remain in the dev->DATA.reg). For i2c.c in the fxn: it seems like the last byte should only be read after the stop command to prevent it from reading another:
maybe protect it with some I also don't know how this effects the flags (ie if no start then maybe len should be +1 and if no stop then reading the byte will still trigger another (unwanted) read). I am a bit unclear on the no start functionality, should it send the addr without a start bit or no addr at all? Also it would be nice to make the return codes match the API (maybe a separate PR). |
If NO_START is NOT selected, then we must send start bit AND the device address. |
I'm currently working on a solution for the extra byte but I'm running out of hardware. I'll push some more commits soon ! |
Let me know if I can help. Also I noticed that the i2c_write_regs is not well defined. if the 16 bit flag is set it goes This appears to be two different write commands and shouldn't work for any sensors, the first byte of the data usually would be the register byte. I think it should either go: or Can anyone give me a sensor or slave device that follows either the current riot implementation or the no stop version? |
Well according to the SAMR21 datasheet page 513 or 514 it appears impossible to support the no stop flag without having an extra unwanted read. I think the closest you can come is that it sends a NACK instead of a ACK after the last byte is read if no stop bit is requested. I would suggest for the read_bytes we just don't support that call with the stop flag. |
@MrKevinWeiss I just push a fix for the extra read byte without loosing support of NOSTART, NOSTOP Now I'll cleanup some parts and work on the errno return code. |
cpu/sam0_common/periph/i2c.c
Outdated
/* Save data to buffer. */ | ||
data[count] = dev->DATA.reg; | ||
|
||
/* Wait for response on bus. */ | ||
if (_wait_for_response(dev, SAMD21_I2C_TIMEOUT) < 0) | ||
return -1; | ||
return I2C_ERR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to always fail here, maybe put a condition that it only checks if it is not the last byte.
cpu/sam0_common/periph/i2c.c
Outdated
} | ||
_stop(I2CSercom); | ||
return length; | ||
return len; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be ret not len?
cpu/sam0_common/periph/i2c.c
Outdated
int i2c_read_reg(i2c_t dev, uint8_t address, uint8_t reg, void *data) | ||
{ | ||
return i2c_read_regs(dev, address, reg, data, 1); | ||
return len; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be ret not len?
|
||
void i2c_init(i2c_t dev) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why no feedback if the init was successful, I imagine it would alter the program if something failed or is there an GET_INIT_STATE somewhere?
@dylad I like moving the functionality to _read, much cleaner, just a few specific things that I have commented on. This fixes the extra read problem if you have a stop bit but the problem is still there if you do not have a stop bit (I2C_NOSTOP). This is an issue with the state machine of the hardware. If you try to read data without the next state being a stop or nack or repeated start it will clk the slave for another byte. The closest I have come is by disabling smart mode and withholding the ACK but it still doesn't seem correct to me. This is why I suggest we just return error for a read with NOSTOP. The only reason to support a read NOSTOP is if we want to read a certain amount wait (with the scl line held down) then do a NOSTART read later. I don't see this functionality being useful (this also means that we wouldn't need to support a NOSTART since we would never get into the state where it could be used). |
I had the same situation with the Kinetis hardware. I chose to let the hardware do another read when NOSTOP is set and avoid performing a dummy read to clock the first byte when NOSTART is set. I don't know if it applies to the Sam hardware though Edit, see https://github.com/gebart/RIOT/blob/pr/kinetis-i2c/cpu/kinetis/periph/i2c.c#L293 |
@MrKevinWeiss I understand what you mean but I would like to keep this level of freedom for the driver. |
@gebart Yup seems to be the same situation. If everyone agrees that we can assume anyone calling a read bytes with NOSTART will have previously called read bytes with NOSTOP or NOSTOP | NOSTART then it should be doable. We could even add some static memory or certain reg check to know that it is in an inbetween state, I noticed that without a stop byte it appears that the scl stays low. We could just say if the scl is low there is a valid previous byte in the register that we want. @dylad Maybe I am getting too obsessy over this. I also don't think saying the sam0_common cpu doesn't support it means that the driver doesn't support it (other cpus could have it). I tend think if something doesn't work perfectly then it should be clearly not supported rather than hiding corner case errors (which end up costing the people using RIOT a lot of time). With information above I will gear my tests to your decision. |
@dylad I need a little bit more time to test it... |
cpu/sam0_common/periph/i2c.c
Outdated
DEBUG("STATUS_ERR_PACKET_COLLISION\n"); | ||
return -EAGAIN; | ||
} | ||
|
||
/* Wait for hardware module to sync */ | ||
while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {} | ||
|
||
DEBUG("Written byte #%i to data reg, now waiting for DR to be empty again\n", buffer_counter); | ||
DEBUG("Written byte #%i to data reg, now waiting for DR \ | ||
to be empty again\n", buffer_counter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not good. The newline is escaped alright, but then there are 14 spaces instead of one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW. The way to solve it is like this
DEBUG("Written byte #%i to data reg, now waiting for DR"
" to be empty again\n", buffer_counter);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
cpu/sam0_common/periph/i2c.c
Outdated
@@ -281,7 +283,7 @@ static int _start(SercomI2cm *dev, uint16_t addr) | |||
|
|||
/* Wait for response on bus. */ | |||
if (addr & I2C_READ) { | |||
/* Some devices (e.g. SHT2x) can hold the bus while preparing the reply */ | |||
/* Some devices can hold the bus while preparing the reply */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example of SHT2x was added by me. If we leave it out there could be a moment in the future when somebody wants to know why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change that.
Signed-off-by: dylad <dylan.laduranty@mesotic.com>
@keestux I'm really really sorry I squashed the branch before seeing your comments... |
no worry @keestux, if we merge this branch, it will be in the new_i2c_if feature branch. You'll still be able to test this feature branch and provide fixes if you find problems. When all is adapted (cpu + drivers) and tested, we'll open a PR of the feature branch to master. We hope this will be done in the next 2 weeks. |
cpu/sam0_common/periph/i2c.c
Outdated
} | ||
|
||
assert(dev < I2C_NUMOF); | ||
/* Initiliaze mutex */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo on 'Initiliaze', should be 'Initialize'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops
@dylad I'm not happy yet. My BME280 driver does not work (gives -EAGAIN). And when I enable debug the error goes away. This smells like a timing problem. And this is just a simple sensor. What other drivers were tested? Oh, and don't worry about the resquashing |
I tested a hts221 (temperature/humidity) and lsm6dsl (accelero/gyro) from ST mems extension directly plugged on a genuino-zero. |
@keestux I've got one idea. I'll push a potential fix in a few minutes. |
Found a critical bug when preparing a fix. Sorry but we need to re-test this driver... |
@dylad Hmm, this is making it worse. Last debug lines are:
Anyway, for me, time is up for today. |
@keestux the lastest introduced bug is solved by the last commit. |
I tested the latest version of this branch on arduino-zero with bmp280 (using a fixed version of #9206) and it works. |
@dylad Great. It's working for me (bme280). |
cpu/sam0_common/periph/i2c.c
Outdated
while ((bus(dev)->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE_Msk) | ||
!= BUSSTATE_IDLE) {} | ||
/* while ((bus(dev)->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE_Msk) | ||
!= BUSSTATE_IDLE) {}*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be cleaned up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh a wild comment !
Will fix
cpu/sam0_common/periph/i2c.c
Outdated
@@ -363,6 +355,10 @@ static inline int _write(SercomI2cm *dev, const uint8_t *data, int length, | |||
return -EIO; | |||
} | |||
} | |||
if (stop) { | |||
/* Prepare stop command before write last byte */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the comment correct? The last byte was already written.
Also, I get the feeling we should do something similar in _read. I mean, do the _stop after the while loop (if it is requested).
The comment probably belongs to the ACK/NACK, which you "prepare" it before doing the last read byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix the comment, but we should not touch the current call orders.
Calling the _stop after the while loop in the _read will lead to an extra byte read.
I can add comment within the code to explain the situation if you want
@keestux I've updated the comments |
Let's go for it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you gave your green light @keestux, let's formally ACK this one.
Please squash!
Squashed ! |
So let's go then! |
cpu/sam0: update I2C driver to new API
cpu/sam0: update I2C driver to new API
cpu/sam0: update I2C driver to new API
Contribution description
This PR updates the I2C driver from sam0 families CPU and also update their periph_conf.h file to fit the new driver.
tested with #9193 #9195 and ADXL345
Issues/PRs references
#6577