On Fri, Oct 27, 2017 at 05:12:00PM +0200, Juergen Fitschen wrote:
> Slave mode driver is based on the concept of i2c-designware driver.
> 
> Signed-off-by: Juergen Fitschen <[email protected]>
> ---
>  drivers/i2c/busses/Makefile         |   3 +
>  drivers/i2c/busses/i2c-at91-core.c  |  13 +++-
>  drivers/i2c/busses/i2c-at91-slave.c | 147 
> ++++++++++++++++++++++++++++++++++++
>  drivers/i2c/busses/i2c-at91.h       |  30 +++++++-
>  4 files changed, 189 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/i2c/busses/i2c-at91-slave.c
> 

Adding an example in Documentation/devicetree/bindings/i2c/i2c-at91.txt
could be useful.

> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 2a79c3d..b38fb8e9 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_SLAVE),y)
> +     i2c-at91-objs           += i2c-at91-slave.o
> +endif

As Designware driver, I would add an entry in 'I2C Hardware Bus
Support'.

If a user wants to use the I2C GPIO driver as the I2C slave (once it has
the slave support), it's not useful to embed i2c-at91-slave code.

>  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..3d7287c 100644
> --- a/drivers/i2c/busses/i2c-at91-core.c
> +++ b/drivers/i2c/busses/i2c-at91-core.c
> @@ -60,8 +60,10 @@ 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 = {
> @@ -243,7 +245,12 @@ 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);
> +     dev->slave_detected = i2c_detect_slave_mode(&pdev->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

I need to have a look to the slave framework and to test this patch before
giving you more feedback.

Some minor comments below.

> new file mode 100644
> index 0000000..4b4808e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-at91-slave.c
> @@ -0,0 +1,147 @@
> +/*
> + *  i2c slave support for Atmel's AT91 Two-Wire Interface (TWI)
> + *
> + *  Copyright (C) 2017 Juergen Fitschen <[email protected]>
> + *
> + *  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;
> +
> +     /* slave address has been detected on I2C bus */
> +     if (irqstatus & AT91_TWI_SVACC) {
> +             if (status & AT91_TWI_SVREAD) {
> +                     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);
> +             } else {
> +                     i2c_slave_event(dev->slave,
> +                                     I2C_SLAVE_WRITE_REQUESTED, &value);
> +                     at91_twi_write(dev, AT91_TWI_IER,
> +                                    AT91_TWI_RXRDY | AT91_TWI_EOSACC);
> +             }
> +             at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_SVACC);
> +     }
> +
> +     /* byte transmitted to remote master */
> +     if (irqstatus & AT91_TWI_TXRDY) {
> +             i2c_slave_event(dev->slave, I2C_SLAVE_READ_PROCESSED, &value);
> +             writeb_relaxed(value, dev->base + AT91_TWI_THR);
> +     }
> +
> +     /* byte received from remote master */
> +     if (irqstatus & AT91_TWI_RXRDY) {
> +             value = readb_relaxed(dev->base + AT91_TWI_RHR);
> +             i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_RECEIVED, &value);
> +     }
> +
> +     /* master sent stop */
> +     if (irqstatus & 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_IER, AT91_TWI_SVACC);
> +             i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &value);
> +     }
> +
> +     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);
> +     at91_twi_write(dev, AT91_TWI_IER, AT91_TWI_SVACC);
> +
> +     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->slave_detected && dev->smr) {

slave_detected has been checked in the caller. smr has been set just
before calling at91_init_twi_bus().

Regards

Ludovic

> +             at91_twi_write(dev, AT91_TWI_SMR, dev->smr);
> +             at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SVEN);
> +     }
> +}
> diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h
> index 555b167..bb502c1 100644
> --- a/drivers/i2c/busses/i2c-at91.h
> +++ b/drivers/i2c/busses/i2c-at91.h
> @@ -53,6 +53,10 @@
>  #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_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 +67,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 */
> @@ -137,6 +145,11 @@ struct at91_twi_dev {
>       bool recv_len_abort;
>       u32 fifo_size;
>       struct at91_twi_dma dma;
> +     bool slave_detected;
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +     unsigned smr;
> +     struct i2c_client *slave;
> +#endif
>  };
>  
>  unsigned at91_twi_read(struct at91_twi_dev *dev, unsigned reg);
> @@ -149,3 +162,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_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
> 

Reply via email to