Skip to content
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

drivers/periph/i2c: add periph_i2c_reconfigure feature & implementation for sam0 #13421

Merged
merged 3 commits into from
May 5, 2020

Conversation

benpicco
Copy link
Contributor

@benpicco benpicco commented Feb 20, 2020

Contribution description

It is often desirable to re-configure the pins of a bus back to GPIO mode,
either to save power when an external peripheral is turned off and current
would leak, or because a device may need a non-standard in-band signal to
be generated on the bus lines.

To serve those use cases, this patch introduces four new functions to the
I2C API:

  • i2c_deinit() to switch the configuration of the I2C pins back to GPIO mode and block access to the bus.

  • i2c_reinit() to return the bus pins back to I2C mode and release the bus again.

  • i2c_pin_sda() to get the data pin for a given bus

  • i2c_pin_scl() to get the clock pin for a given bus

Since it's unreasonable to expect having implementations for all platforms
already, those functions are only available when the periph_i2c_reconfigure
feature is available.

Applications should use FEATURES_REQUIRED += periph_i2c_reconfigure or
FEATURES_OPTIONAL += periph_i2c_reconfigure if they want to make use of those functions.

Testing procedure

Run the tests/periph_i2c test application.
On a supported platform, the i2c_gpio command should be available.
Verify that I2C communication is possible before and after issuing the i2c_gpio command.

You can observe a change of power draw when the pins are being reconfigured.

Issues/PRs references

alternative to #13413
allows for a generic solution of #13412

If this approach is acceptable, I will provide implementations for SPI and UART as well.

@smlng
Copy link
Member

smlng commented Feb 20, 2020

On first sight I see a benefit in i2c_deinit while i2c_reinit should be covered by i2c_deinit followed by i2c_init, right? Also I'm not sure for what one would need i2c_pin_*.

@benpicco
Copy link
Contributor Author

benpicco commented Feb 20, 2020

i2c_reinit should be covered by i2c_deinit followed by i2c_init, right?

Not quite. i2c_deinit() will acquire the bus mutex, so all other calls to i2c_acquire() will block until i2c_deinit() is called.
i2c_init() would re-initialize the mutex, I'm not sure if that would unblock the waiters, but I'm also not confident that all implementations of i2c_init() are safe to call multiple times. (But those implementations could be ensured when implementing this feature)

Also I'm not sure for what one would need i2c_pin_*.

There is no grantee all i2c_conf_t will have a gpio_t sda_pin, etc. Granted, now that I'm looking through it all implementations have some member like that, although with a different names (and lpc23xx will be the odd one out)

I would have preferred a static inline, but that's tricky to get right for an API definition.

cpu/sam0_common/periph/i2c.c Outdated Show resolved Hide resolved
@smlng smlng added the Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR label Feb 20, 2020
smlng
smlng previously requested changes Feb 20, 2020
Copy link
Member

@smlng smlng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me the current proposal goes against the general concept of simple, minimal, and lean low-level APIs.

I would like to get other opinions (and suggestions) on this: e.g. @haukepetersen, @MrKevinWeiss and @MichelRottleuthner please have a look.

@MrKevinWeiss
Copy link
Contributor

My first thoughts are that we should really consider things when it comes to API changes (though this appears not to be a change but just an addition which isn't as critical).

That being said I would like to first understand what everyone else is doing (zephyr, mbed, each vendors HAL) if we want to talk about another set of changes to the i2c api.

With regards to the reinit maybe it would be better to make the init capable of being called multiple times? If the mutex is the problem check to see if it is already initialized.
However, I think an argument could be made for the reinit, maybe easier to implement, less cycles to run, or maybe a special sequence is needed to free the bus.

As for the pins, I really think we should just specify the struct and have it a standard way of describing it rather than having to add a workaround function to convert to a standard.

@benpicco
Copy link
Contributor Author

Ok, I will then

  • drop i2c_pin_scl() and i2c_pin_sda()
  • drop i2c_reinit() and instead use mutex_trylock() in i2c_init() to check if the mutex has been locked and thus init is called after i2c_deinit().

@benpicco benpicco force-pushed the cpu/sam0_common/i2c-deinit branch from 23bb807 to 469bae4 Compare February 26, 2020 13:42
@benpicco
Copy link
Contributor Author

I changed the implementation accordingly & rebased to resolve a merge conflict.

@MrKevinWeiss
Copy link
Contributor

So as far as I see it looks good. I still want to do some testing (maybe I will get to it tomorrow).

I think @smlng comments should be addressed and I think he is on vacation so if we don't hear from him and testing/review is complete I think we can dismiss.

In terms of the API comparison, we would only talk about the deinit call

Vendor Call Breakdown
ST HAL_StatusTypeDef HAL_I2C_DeInit (I2C_HandleTypeDef * hi2c) has return but only used for null checks, only arg is the device
Zephyr ? I don't know if they have a generic runtime disable unless it is a configure parameter
Mbed ? Doesn't look like they have one
Atmel ASF int32_t i2c_m_async_deinit(struct i2c_m_async_desc *const i2c) return code and a device...
NXP HAL static void I2C_HAL_Disable (I2C_Type ∗base) has an init but also enable and disable... Though it looks private
TI RTOS void I2C_close (I2C_Handle handle) Also an init and an open and close.

From what I can tell the vendors seem to support an i2c_disable API call with a device handle and sometimes return code (though I think we don't need one and can just assert for invalid conditions).

@@ -79,6 +79,20 @@ void i2c_init(i2c_t dev)
int32_t tmp_baud;

assert(dev < I2C_NUMOF);

/* i2c_deinit() has been called - restore config */
if (mutex_trylock(&locks[dev]) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to call mutex_trylock() on uninitialized mutexes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as MUTEX_INIT remains defined to {{ NULL }} and locks is a static buffer…

I agree it's not nice to rely on an implementation detail here, should probably make that more explicit (and drop the call to mutex_init() in i2c_init().

Copy link
Contributor

@MrKevinWeiss MrKevinWeiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to wrap all the reinit functionality with #ifdef MODULE_PERIPH_I2C_RECONFIGURE so it doesn't add code size to the init function and doesn't compile the other functions slowing down the CI (even though it doesn't link)?

* @brief Change the pins of the given I2C bus back to plain GPIO functionality
*
* The pin mux of the SDA and SCL pins of the bus will be changed back to
* GPIO mode and the I2C bus is powered off.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what GPIO mode (if it saves the previous state, has some default state, input, with or without pullups, etc.)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends no the implementation, gpio_init() should be called if you want a defined state - I'll add a line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends no the implementation, gpio_init() should be called if you want a defined state - I'll add a line.

cpu/sam0_common/periph/i2c.c Outdated Show resolved Hide resolved
cpu/sam0_common/periph/i2c.c Show resolved Hide resolved
@@ -92,6 +92,14 @@ void gpio_init_mux(gpio_t pin, gpio_mux_t mux)
port->PMUX[pin_pos >> 1].reg |= (mux << (4 * (pin_pos & 0x1)));
}

void gpio_disable_mux(gpio_t pin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be wrapped with #ifdef MODULE_PERIPH_I2C_RECONFIGURE

Copy link
Contributor Author

@benpicco benpicco Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this can be useful on it's own (and it's internal to sam0)
It will also be needed for spi_deinit() and uart_deinit().

@jue89
Copy link
Contributor

jue89 commented Feb 28, 2020

I just pulled you PR for test purposes into my PR #13412 and it is working well :) Thank you!

For a clean PR, I need the standardized I2C struct @MrKevinWeiss was talking about. Currently, I am assuming that i2c_config[cfg->atcai2c.bus].sda_pin is giving me the gpti_t of the SDA line.

@MichelRottleuthner
Copy link
Contributor

Thanks for working on this! I want to add to the discussion that this functionality will also be helpful (required?) for implementing generic functionality to recover slaves from blocking or failed states.
After discussing F2F with @MrKevinWeiss we agreed that this would be important for reliable
operation e.g. when there are unexpected transient signals on the bus or simply when the RIOT node is reset at the wrong moment.

The below code can be used as an example to trigger such a situation. If the node is reset while looping over saul_reg_read it is very likely to leave the slave in a blocked state and I2C device initialization of all subsequent boots will fail. On a pba-d-01-kw2x I could reliably reproduce the problem, albeit not always on first try. The slave will be blocked with SDA==0 and SCL==1, presumably because it still wants to clock out data. This will make the master always fail with arbitration error.

#include <stdio.h>

#include "xtimer.h"
#include "phydat.h"
#include "saul_reg.h"

int main(void)
{
    phydat_t res;
    xtimer_sleep(5); /* wait a bit to see the output */

    saul_reg_t *dev = saul_reg;
    int cnt = 0;
    while (dev != NULL) {
        cnt++;
        printf("%s\n", saul_class_to_str(dev->driver->type));
        dev = dev->next;
    }

    printf("found %d SAUL devices\n", cnt);

    dev = saul_reg_find_type(SAUL_SENSE_ACCEL);

    if (dev == NULL) {
        printf("no such device found!\n");
        return 1;
    }

    while (1) {
        saul_reg_read(dev, &res);
    }

    return 0;
}

@benpicco
Copy link
Contributor Author

benpicco commented Mar 3, 2020

For a clean PR, I need the standardized I2C struct @MrKevinWeiss was talking about. Currently, I am assuming that i2c_config[cfg->atcai2c.bus].sda_pin is giving me the gpti_t of the SDA line.

Well the other platforms will need to be touched anyway when they implement i2c_deinit(), so might as well change the member names then. sam0 would in this case set the new standard 😉

@jue89
Copy link
Contributor

jue89 commented Mar 4, 2020

Well the other platforms will need to be touched anyway when they implement i2c_deinit(), so might as well change the member names then. sam0 would in this case set the new standard

Are you thinking about some kind of generic i2c_conf_t struct that is inherited into an platform-specific i2c_conf_t struct?

@benpicco benpicco force-pushed the cpu/sam0_common/i2c-deinit branch from 1c95731 to 4ea4094 Compare March 12, 2020 15:12
@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Mar 13, 2020
@tcschmidt
Copy link
Member

@MrKevinWeiss can you add advise on readjusting the layers here?

@MrKevinWeiss
Copy link
Contributor

It seems like there will be another abstraction layer is what is selected to move forward. The unification of the implementation specific structs should be done on a seperate PR. So like I said I won't block.

I am also thinking @smlng can dismiss his review?

@benpicco
Copy link
Contributor Author

Btw.: SPI already has spi_init_pins() so it would be also consistent do add a i2c_init_pins() / i2c_deinit_pins() combination.

That would spare us additional complexity in i2c_init() and it would be more obvious what the functions are supposed to to.

@fjmolinas
Copy link
Contributor

Btw.: SPI already has spi_init_pins() so it would be also consistent do add a i2c_init_pins() / i2c_deinit_pins() combination.

That would spare us additional complexity in i2c_init() and it would be more obvious what the functions are supposed to to.

+1 for this approach, what do you think @MrKevinWeiss?

@MrKevinWeiss
Copy link
Contributor

Btw.: SPI already has spi_init_pins() so it would be also consistent do add a i2c_init_pins() / i2c_deinit_pins() combination.

Make sense +1

@fjmolinas
Copy link
Contributor

@MrKevinWeiss do you have any i2c driver to test this? I'm sorry to say I don't anything to test ATM :/.

@benpicco
Copy link
Contributor Author

If you happen to have a samr21-xpro you can read the EUI-64 from the embedded debugger using I2C.

i2c_read_regs  0 0x28 0xe1 2 0 # first you have to read a token
i2c_read_regs  0 0x28 0xd2 8 0 # now you can read the EUI-64

it should match the MAC address printed on the bottom of the board.

@fjmolinas
Copy link
Contributor

Maybe @aabadie has a samr21-xpro and can test?

@MrKevinWeiss MrKevinWeiss added the Reviewed: 3-testing The PR was tested according to the maintainer guidelines label May 5, 2020
@MrKevinWeiss
Copy link
Contributor

I tested on the samr21-xpro using the periph_i2c test.

I was able to acquire, read registers, release, reinit pins, acquire, read registers.

It works as expected!

Copy link
Contributor

@fjmolinas fjmolinas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrKevinWeiss tested. @smlng requested changes but deffering opion to @MrKevinWeiss and @MichelRottleuthner. The later was fine with the changes #13421 (comment). @MrKevinWeiss are you fine with the changes as well?

I think we can dismiss @smlng after 1 month.

On my side this is an ACK!

@benpicco benpicco force-pushed the cpu/sam0_common/i2c-deinit branch from 4e5e405 to 0a6c640 Compare May 5, 2020 13:09
@MrKevinWeiss MrKevinWeiss dismissed smlng’s stale review May 5, 2020 14:07

Comments have been addressed

@MrKevinWeiss
Copy link
Contributor

Just the CI need to pass now I guess!

benpicco added 3 commits May 5, 2020 16:12
It is often desireable to re-configure the pins of a bus back to GPIO mode,
either to save power when an external peripheral is turned off and current
would leak, or because a device may need a non-standard in-band signal to
be generated on the bus lines.

To serve those use cases, this patch introduces four new functions to the
I2C API:

 - `i2c_init_pins()`    equivalent to spi_init_pins(), restores I2C pin configuration

 - `i2c_deinit_pins()`  to switch the configuration of the I2C pins back to GPIO
                        mode and block access to the bus.

 - `i2c_pin_sda()`      to get the data pin for a given bus

 - `i2c_pin_scl()`      to get the clock pin for a given bus

Since it's unreasonable to expect having implementations for all platforms
already, those functions are only availiable when the periph_i2c_reconfigure
feature is availiable.

Applications should use FEATURES_REQUIRED += periph_i2c_reconfigure or
FEATURES_OPTIONAL if they want to make use of those functions.
This adds sam0 implementations for

 - i2c_init_pins()
 - i2c_deinit_pins()
 - i2c_pin_sda()
 - i2c_pin_scl()
Add a test to re-configure the I2C pins to GPIO functionality and
use them as output.

A small delay is added to allow for observing the change in power
draw.
@benpicco benpicco force-pushed the cpu/sam0_common/i2c-deinit branch from 0a6c640 to 896f9db Compare May 5, 2020 14:12
@fjmolinas
Copy link
Contributor

go!

@fjmolinas fjmolinas merged commit cea0d1c into RIOT-OS:master May 5, 2020
@benpicco benpicco deleted the cpu/sam0_common/i2c-deinit branch May 5, 2020 18:02
@miri64 miri64 added this to the Release 2020.07 milestone Jun 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR Reviewed: 3-testing The PR was tested according to the maintainer guidelines Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants