Re: [PATCH v2 3/3] i2c: at91: added slave mode support

From: Ludovic Desroches
Date: Sun Dec 03 2017 - 13:02:55 EST


Hi Juergen,

On Thu, Nov 09, 2017 at 06:22:29PM +0100, Juergen Fitschen wrote:
> Slave mode driver is based on the concept of i2c-designware driver.
>

Sorry to be so long to answer you. I would like to test it before acking
it. Unfortunately, I didn't have the time to perform all the tests I
have in mind but I don't way to delay the inclusion of the slave mode
support. If there are bugs or restrictions, we could fix it later.

I tested it quickly on a sama5d2 xplained board: I used an i2c-gpio
master and the eeprom driver. It works pretty well. I tried to increase
the size of the eeprom by adding:
+ { "slave-24c64", 65536 / 8 },
in i2c-slave-eeprom.c. Unfortunately, it no longer works: I get
different checksums. Looking quickly at i2c-slave-eeprom.c, I don't see
any reason why it may not work if I increase the size but I may have
missed something.

I have seen you described how you tested it thanks. Maybe you have also
tried in the same way as me? Did you experience the same behavior?

Regards

Ludovic

> Signed-off-by: Juergen Fitschen <me@xxxxxx>
> ---
> Changes in v2:
> - Squashed commit "take slave mode capabilities of hardware into
> account" into this commit
> - Removed unused feature bit AT91_TWI_SM_HAS_FIFO
> - Added NACK support
> - Reworked interrupt handler:
> - The interrupt handler takes into account that several events can
> occur between two handler calls and thus must be handled in one
> handler run. It caanot be assumed that every events triggers the
> handler individually.
> - Instead of using the states register of the I2c interface, a state
> machine is held in the at91_twi_dev struct. This ensures that no
> state transitions are missed due ti interrupt latency.
> - Added Kconfig option for selection whether slave mode suppurt should
> be included in the driver or not
> - Added example in Documentation/devicetree/bindings/i2c/i2c-at91.txt
> ---
> Documentation/devicetree/bindings/i2c/i2c-at91.txt | 14 ++
> drivers/i2c/busses/Kconfig | 10 +
> drivers/i2c/busses/Makefile | 3 +
> drivers/i2c/busses/i2c-at91-core.c | 23 ++-
> drivers/i2c/busses/i2c-at91-slave.c | 216 +++++++++++++++++++++
> drivers/i2c/busses/i2c-at91.h | 42 +++-
> 6 files changed, 304 insertions(+), 4 deletions(-)
> create mode 100644 drivers/i2c/busses/i2c-at91-slave.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-at91.txt b/Documentation/devicetree/bindings/i2c/i2c-at91.txt
> index ef973a0..95c79a8 100644
> --- a/Documentation/devicetree/bindings/i2c/i2c-at91.txt
> +++ b/Documentation/devicetree/bindings/i2c/i2c-at91.txt
> @@ -61,3 +61,17 @@ i2c0: i2c@f8034600 {
> reg = <0x1a>;
> };
> };
> +
> +i2c0: i2c@f8028000 {
> + compatible = "atmel,sama5d2-i2c";
> + reg = <0xf8028000 0x100>;
> + interrupts = <29 4 7>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + clocks = <&twi0_clk>;
> +
> + eeprom@64 {
> + compatible = "linux,slave-24c02";
> + reg = <0x40000064>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 009345d..41338e8 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -380,6 +380,16 @@ config I2C_AT91
> the latency to fill the transmission register is too long. If you
> are facing this situation, use the i2c-gpio driver.
>
> +config I2C_AT91_SLAVE
> + bool "Atmel AT91 I2C Two-Wire interface (TWI) Slave"
> + depends on I2C_AT91 && I2C_SLAVE
> + help
> + This adds slave mode support to the I2C interface on Atmel AT91
> + processors.
> +
> + This is not a standalone module and must be compiled together with the
> + master mode driver.
> +
> config I2C_AU1550
> tristate "Au1550/Au1200/Au1300 SMBus interface"
> depends on MIPS_ALCHEMY
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 2a79c3d..daa2ea4 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -34,6 +34,9 @@ obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o
> obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o
> obj-$(CONFIG_I2C_AT91) += i2c-at91.o
> i2c-at91-objs := i2c-at91-core.o i2c-at91-master.o
> +ifeq ($(CONFIG_I2C_AT91_SLAVE),y)
> + i2c-at91-objs += i2c-at91-slave.o
> +endif
> obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
> obj-$(CONFIG_I2C_AXXIA) += i2c-axxia.o
> obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
> diff --git a/drivers/i2c/busses/i2c-at91-core.c b/drivers/i2c/busses/i2c-at91-core.c
> index 4fed72d..cba447e 100644
> --- a/drivers/i2c/busses/i2c-at91-core.c
> +++ b/drivers/i2c/busses/i2c-at91-core.c
> @@ -60,13 +60,16 @@ void at91_init_twi_bus(struct at91_twi_dev *dev)
> {
> at91_disable_twi_interrupts(dev);
> at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SWRST);
> -
> - at91_init_twi_bus_master(dev);
> + if (dev->slave_detected)
> + at91_init_twi_bus_slave(dev);
> + else
> + at91_init_twi_bus_master(dev);
> }
>
> static struct at91_twi_pdata at91rm9200_config = {
> .clk_max_div = 5,
> .clk_offset = 3,
> + .slave_mode_features = 0,
> .has_unre_flag = true,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -75,6 +78,7 @@ static struct at91_twi_pdata at91rm9200_config = {
> static struct at91_twi_pdata at91sam9261_config = {
> .clk_max_div = 5,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -83,6 +87,7 @@ static struct at91_twi_pdata at91sam9261_config = {
> static struct at91_twi_pdata at91sam9260_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -91,6 +96,7 @@ static struct at91_twi_pdata at91sam9260_config = {
> static struct at91_twi_pdata at91sam9g20_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -99,6 +105,7 @@ static struct at91_twi_pdata at91sam9g20_config = {
> static struct at91_twi_pdata at91sam9g10_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -129,6 +136,7 @@ static const struct platform_device_id at91_twi_devtypes[] = {
> static struct at91_twi_pdata at91sam9x5_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = false,
> @@ -137,6 +145,7 @@ static struct at91_twi_pdata at91sam9x5_config = {
> static struct at91_twi_pdata sama5d4_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE,
> .has_unre_flag = false,
> .has_alt_cmd = false,
> .has_hold_field = true,
> @@ -145,6 +154,7 @@ static struct at91_twi_pdata sama5d4_config = {
> static struct at91_twi_pdata sama5d2_config = {
> .clk_max_div = 7,
> .clk_offset = 4,
> + .slave_mode_features = AT91_TWI_SM_AVAILABLE | AT91_TWI_SM_CAN_NACK,
> .has_unre_flag = true,
> .has_alt_cmd = true,
> .has_hold_field = true,
> @@ -217,6 +227,10 @@ static int at91_twi_probe(struct platform_device *pdev)
> if (!dev->pdata)
> return -ENODEV;
>
> + dev->slave_detected = i2c_detect_slave_mode(&pdev->dev);
> + if (dev->slave_detected && dev->pdata->slave_mode_features == 0)
> + return -EPFNOSUPPORT;
> +
> dev->base = devm_ioremap_resource(&pdev->dev, mem);
> if (IS_ERR(dev->base))
> return PTR_ERR(dev->base);
> @@ -243,7 +257,10 @@ static int at91_twi_probe(struct platform_device *pdev)
> dev->adapter.timeout = AT91_I2C_TIMEOUT;
> dev->adapter.dev.of_node = pdev->dev.of_node;
>
> - rc = at91_twi_probe_master(pdev, phy_addr, dev);
> + if (dev->slave_detected)
> + rc = at91_twi_probe_slave(pdev, phy_addr, dev);
> + else
> + rc = at91_twi_probe_master(pdev, phy_addr, dev);
> if (rc)
> return rc;
>
> diff --git a/drivers/i2c/busses/i2c-at91-slave.c b/drivers/i2c/busses/i2c-at91-slave.c
> new file mode 100644
> index 0000000..a5881e9
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-at91-slave.c
> @@ -0,0 +1,216 @@
> +/*
> + * i2c slave support for Atmel's AT91 Two-Wire Interface (TWI)
> + *
> + * Copyright (C) 2017 Juergen Fitschen <me@xxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "i2c-at91.h"
> +
> +static irqreturn_t atmel_twi_interrupt_slave(int irq, void *dev_id)
> +{
> + struct at91_twi_dev *dev = dev_id;
> + const unsigned status = at91_twi_read(dev, AT91_TWI_SR);
> + const unsigned irqstatus = status & at91_twi_read(dev, AT91_TWI_IMR);
> + u8 value;
> +
> + if (!irqstatus)
> + return IRQ_NONE;
> +
> + /*
> + * The order of processing AT91_TWI_TXRDY [a], AT91_TWI_SVACC [b] and
> + * AT91_TWI_RXRDY [c] status bit is important:
> + * + Remote master wants to read data:
> + * - AT91_TWI_SVACC IRQ with AT91_TWI_SVREAD unset is raised
> + * - I2C_SLAVE_READ_REQUESTED slave event is fired and first byte
> + * received from the I2C slave backend
> + * - Byte is written to AT91_TWI_THR
> + * - AT91_TWI_TXRDY is still set since AT91_TWI_SR isn't reread but
> + * AT91_TWI_THR must not be written a second time!
> + * --> Check AT91_TWI_TXRDY before AT91_TWI_SVACC
> + * + Remote master wants to write data:
> + * - AT91_TWI_SVACC IRQ with AT91_TWI_SVREAD set is raised
> + * - If the first byte already has been received due to interrupt
> + * latency, AT91_TWI_RXRDY is set and AT91_TWI_RHR has to be read
> + * in the same IRQ handler run!
> + * --> Check AT91_TWI_RXRDY after AT91_TWI_SVACC
> + */
> +
> + /*
> + * [a] Next byte can be stored into transmit holding register
> + */
> + if ((dev->state == AT91_TWI_STATE_TX) && (status & AT91_TWI_TXRDY)) {
> + i2c_slave_event(dev->slave, I2C_SLAVE_READ_PROCESSED, &value);
> + writeb_relaxed(value, dev->base + AT91_TWI_THR);
> +
> + dev_dbg(dev->dev, "DATA %02x", value);
> + }
> +
> + /*
> + * [b] An I2C transmission has been started and the interface detected
> + * its slave address.
> + */
> + if ((dev->state == AT91_TWI_STATE_STOP) && (status & AT91_TWI_SVACC)) {
> + /*
> + * AT91_TWI_SVREAD indicates whether data should be read from or
> + * written to the slave. This works flawlessly until the
> + * transmission has been stopped (i.e. AT91_TWI_EOSACC is set).
> + * If the interrupt latency is high, a master can start a write
> + * transmission, write one byte and stop the transmission before
> + * the IRQ handler is called. In that case AT91_TWI_SVACC,
> + * AT91_TWI_RXRDY and AT91_TWI_EOSACC are set, but we cannot
> + * rely on AT91_TWI_SVREAD. That's the reason why the following
> + * condition looks like it does.
> + */
> + if (!(status & AT91_TWI_SVREAD) ||
> + ((status & AT91_TWI_EOSACC) && (status & AT91_TWI_RXRDY))) {
> + i2c_slave_event(dev->slave,
> + I2C_SLAVE_WRITE_REQUESTED, &value);
> +
> + at91_twi_write(dev, AT91_TWI_IER,
> + AT91_TWI_RXRDY | AT91_TWI_EOSACC);
> +
> + dev->state = AT91_TWI_STATE_RX;
> +
> + dev_dbg(dev->dev, "START LOCAL <- REMOTE");
> + } else {
> + i2c_slave_event(dev->slave,
> + I2C_SLAVE_READ_REQUESTED, &value);
> + writeb_relaxed(value, dev->base + AT91_TWI_THR);
> +
> + at91_twi_write(dev, AT91_TWI_IER,
> + AT91_TWI_TXRDY | AT91_TWI_EOSACC);
> +
> + dev->state = AT91_TWI_STATE_TX;
> +
> + dev_dbg(dev->dev, "START LOCAL -> REMOTE");
> + dev_dbg(dev->dev, "DATA %02x", value);
> + }
> + at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_SVACC);
> + }
> +
> + /*
> + * [c] Byte can be read from receive holding register
> + */
> + if ((dev->state == AT91_TWI_STATE_RX) && (status & AT91_TWI_RXRDY)) {
> + int rc;
> +
> + value = readb_relaxed(dev->base + AT91_TWI_RHR);
> + rc = i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_RECEIVED, &value);
> + if ((rc < 0) && (dev->pdata->slave_mode_features & AT91_TWI_SM_CAN_NACK))
> + at91_twi_write(dev, AT91_TWI_SMR, dev->smr | AT91_TWI_SMR_NACKEN);
> + else
> + at91_twi_write(dev, AT91_TWI_SMR, dev->smr);
> +
> + dev_dbg(dev->dev, "DATA %02x", value);
> + }
> +
> + /*
> + * Master sent stop
> + */
> + if ((dev->state != AT91_TWI_STATE_STOP) && (status & AT91_TWI_EOSACC)) {
> + at91_twi_write(dev, AT91_TWI_IDR,
> + AT91_TWI_TXRDY | AT91_TWI_RXRDY | AT91_TWI_EOSACC);
> + at91_twi_write(dev, AT91_TWI_CR,
> + AT91_TWI_THRCLR | AT91_TWI_RHRCLR);
> + at91_twi_write(dev, AT91_TWI_IER,
> + AT91_TWI_SVACC);
> +
> + i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &value);
> +
> + dev->state = AT91_TWI_STATE_STOP;
> + dev_dbg(dev->dev, "STOP");
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int at91_reg_slave(struct i2c_client *slave)
> +{
> + struct at91_twi_dev *dev = i2c_get_adapdata(slave->adapter);
> +
> + if (dev->slave)
> + return -EBUSY;
> +
> + if (slave->flags & I2C_CLIENT_TEN)
> + return -EAFNOSUPPORT;
> +
> + /* Make sure twi_clk doesn't get turned off! */
> + pm_runtime_get_sync(dev->dev);
> +
> + dev->slave = slave;
> + dev->smr = AT91_TWI_SMR_SADR(slave->addr);
> +
> + at91_init_twi_bus(dev);
> +
> + dev_info(dev->dev, "entered slave mode (ADR=%d)\n", slave->addr);
> +
> + return 0;
> +}
> +
> +static int at91_unreg_slave(struct i2c_client *slave)
> +{
> + struct at91_twi_dev *dev = i2c_get_adapdata(slave->adapter);
> +
> + WARN_ON(!dev->slave);
> +
> + dev_info(dev->dev, "leaving slave mode\n");
> +
> + dev->slave = NULL;
> + dev->smr = 0;
> +
> + at91_init_twi_bus(dev);
> +
> + pm_runtime_put(dev->dev);
> +
> + return 0;
> +}
> +
> +static u32 at91_twi_func(struct i2c_adapter *adapter)
> +{
> + return I2C_FUNC_SLAVE | I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
> + | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
> +}
> +
> +static const struct i2c_algorithm at91_twi_algorithm_slave = {
> + .reg_slave = at91_reg_slave,
> + .unreg_slave = at91_unreg_slave,
> + .functionality = at91_twi_func,
> +};
> +
> +int at91_twi_probe_slave(struct platform_device *pdev,
> + u32 phy_addr, struct at91_twi_dev *dev)
> +{
> + int rc;
> +
> + rc = devm_request_irq(&pdev->dev, dev->irq, atmel_twi_interrupt_slave,
> + 0, dev_name(dev->dev), dev);
> + if (rc) {
> + dev_err(dev->dev, "Cannot get irq %d: %d\n", dev->irq, rc);
> + return rc;
> + }
> +
> + dev->adapter.algo = &at91_twi_algorithm_slave;
> +
> + return 0;
> +}
> +
> +void at91_init_twi_bus_slave(struct at91_twi_dev *dev)
> +{
> + at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_MSDIS);
> + if (dev->smr) {
> + dev->state = AT91_TWI_STATE_STOP;
> + at91_twi_write(dev, AT91_TWI_SMR, dev->smr);
> + at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SVEN);
> + at91_twi_write(dev, AT91_TWI_IER, AT91_TWI_SVACC);
> + }
> +}
> diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h
> index 555b167..e15a99e 100644
> --- a/drivers/i2c/busses/i2c-at91.h
> +++ b/drivers/i2c/busses/i2c-at91.h
> @@ -53,6 +53,11 @@
> #define AT91_TWI_IADRSZ_1 0x0100 /* Internal Device Address Size */
> #define AT91_TWI_MREAD BIT(12) /* Master Read Direction */
>
> +#define AT91_TWI_SMR 0x0008 /* Slave Mode Register */
> +#define AT91_TWI_SMR_NACKEN BIT(0) /* NACK value in next ACK cycle */
> +#define AT91_TWI_SMR_SADR_MAX 0x007f
> +#define AT91_TWI_SMR_SADR(x) (((x) & AT91_TWI_SMR_SADR_MAX) << 16)
> +
> #define AT91_TWI_IADR 0x000c /* Internal Address Register */
>
> #define AT91_TWI_CWGR 0x0010 /* Clock Waveform Generator Reg */
> @@ -63,13 +68,17 @@
> #define AT91_TWI_TXCOMP BIT(0) /* Transmission Complete */
> #define AT91_TWI_RXRDY BIT(1) /* Receive Holding Register Ready */
> #define AT91_TWI_TXRDY BIT(2) /* Transmit Holding Register Ready */
> +#define AT91_TWI_SVREAD BIT(3) /* Slave Read */
> +#define AT91_TWI_SVACC BIT(4) /* Slave Access */
> #define AT91_TWI_OVRE BIT(6) /* Overrun Error */
> #define AT91_TWI_UNRE BIT(7) /* Underrun Error */
> #define AT91_TWI_NACK BIT(8) /* Not Acknowledged */
> +#define AT91_TWI_EOSACC BIT(11) /* End Of Slave Access */
> #define AT91_TWI_LOCK BIT(23) /* TWI Lock due to Frame Errors */
>
> #define AT91_TWI_INT_MASK \
> - (AT91_TWI_TXCOMP | AT91_TWI_RXRDY | AT91_TWI_TXRDY | AT91_TWI_NACK)
> + (AT91_TWI_TXCOMP | AT91_TWI_RXRDY | AT91_TWI_TXRDY | AT91_TWI_NACK \
> + | AT91_TWI_SVACC | AT91_TWI_EOSACC)
>
> #define AT91_TWI_IER 0x0024 /* Interrupt Enable Register */
> #define AT91_TWI_IDR 0x0028 /* Interrupt Disable Register */
> @@ -99,9 +108,19 @@
>
> #define AT91_TWI_VER 0x00fc /* Version Register */
>
> +#define AT91_TWI_SM_AVAILABLE BIT(0) /* Slave mode supported */
> +#define AT91_TWI_SM_CAN_NACK BIT(1) /* Can send NACKs in slave mode */
> +
> +enum at91_twi_state {
> + AT91_TWI_STATE_STOP,
> + AT91_TWI_STATE_TX,
> + AT91_TWI_STATE_RX
> +};
> +
> struct at91_twi_pdata {
> unsigned clk_max_div;
> unsigned clk_offset;
> + unsigned slave_mode_features;
> bool has_unre_flag;
> bool has_alt_cmd;
> bool has_hold_field;
> @@ -137,6 +156,12 @@ struct at91_twi_dev {
> bool recv_len_abort;
> u32 fifo_size;
> struct at91_twi_dma dma;
> + bool slave_detected;
> +#if IS_ENABLED(CONFIG_I2C_AT91_SLAVE)
> + unsigned smr;
> + enum at91_twi_state state;
> + struct i2c_client *slave;
> +#endif
> };
>
> unsigned at91_twi_read(struct at91_twi_dev *dev, unsigned reg);
> @@ -149,3 +174,18 @@ void at91_init_twi_bus(struct at91_twi_dev *dev);
> void at91_init_twi_bus_master(struct at91_twi_dev *dev);
> int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr,
> struct at91_twi_dev *dev);
> +
> +#if IS_ENABLED(CONFIG_I2C_AT91_SLAVE)
> +void at91_init_twi_bus_slave(struct at91_twi_dev *dev);
> +int at91_twi_probe_slave(struct platform_device *pdev, u32 phy_addr,
> + struct at91_twi_dev *dev);
> +
> +#else
> +static inline void at91_init_twi_bus_slave(struct at91_twi_dev *dev) {}
> +static inline int at91_twi_probe_slave(struct platform_device *pdev,
> + u32 phy_addr, struct at91_twi_dev *dev)
> +{
> + return -EINVAL;
> +}
> +
> +#endif
> --
> 2.7.4