diff mbox series

[RFC,3/4] i2c: at91: added slave mode support

Message ID b81e56548dbe4fa6c37fec4c80b38622f1f1fcdf.1509112910.git.me@jue.yt
State Superseded
Headers show
Series i2c: at91: slave mode support | expand

Commit Message

Juergen Fitschen Oct. 27, 2017, 3:12 p.m. UTC
Slave mode driver is based on the concept of i2c-designware driver.

Signed-off-by: Juergen Fitschen <me@jue.yt>
---
 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

Comments

Ludovic Desroches Oct. 31, 2017, 3:55 p.m. UTC | #1
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 <me@jue.yt>
> ---
>  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 <me@jue.yt>
> + *
> + *  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
>
Juergen Fitschen Nov. 1, 2017, 11:35 a.m. UTC | #2
Hello Ludovic,

On Tue, Oct 31, 2017 at 04:55:42PM +0100, Ludovic Desroches wrote:
> 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 <me@jue.yt>
> > ---
> >  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.

I will add this.

> > 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.

Good point.

> > +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().

Whoops! Better safe than sorry ;) I will remove that check.



Thank you for testing everything! I am curious about how the other MPUs will
perform with the patchset. I have a SAMA5D35 at hand and can test everything on
this MPU in the next days.


Best regards
  Juergen
Juergen Fitschen Nov. 1, 2017, 1:04 p.m. UTC | #3
Helle Ludovic,

while going through this patch a question related to the Atmel / Microchip HW
came into mind:

On Fri, Oct 27, 2017 at 05:12:00PM +0200, Juergen Fitschen wrote:
> diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h
> (...)
>  #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)

The AT91_TWI_INT_MASK is used to disable all interrputs in the
at91_disable_twi_interrupts function by writing the mask to the interrupt
disable register (IDR). I wonder what happens on MPUs that don't have
AT91_TWI_SVACC and AT91_TWI_EOSACC implemented, like the AT91RM9200? Do you
think we should revise this and write specific masks depending on the current
moude the I2C HW is in?

Something like this:

void at91_disable_twi_interrupts(struct at91_twi_dev *dev)
{
        if (dev->slave_detected)
                at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_INT_MASK_SLAVE);
        else
                at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_INT_MASK_MASTER);
}


Best regards
  Juergen
Ludovic Desroches Nov. 2, 2017, 2:53 p.m. UTC | #4
On Wed, Nov 01, 2017 at 02:04:18PM +0100, Juergen Fitschen wrote:
> Helle Ludovic,
> 
> while going through this patch a question related to the Atmel / Microchip HW
> came into mind:
> 
> On Fri, Oct 27, 2017 at 05:12:00PM +0200, Juergen Fitschen wrote:
> > diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h
> > (...)
> >  #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)
> 
> The AT91_TWI_INT_MASK is used to disable all interrputs in the
> at91_disable_twi_interrupts function by writing the mask to the interrupt
> disable register (IDR). I wonder what happens on MPUs that don't have
> AT91_TWI_SVACC and AT91_TWI_EOSACC implemented, like the AT91RM9200? Do you
> think we should revise this and write specific masks depending on the current
> moude the I2C HW is in?
> 
> Something like this:
> 
> void at91_disable_twi_interrupts(struct at91_twi_dev *dev)
> {
>         if (dev->slave_detected)
>                 at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_INT_MASK_SLAVE);
>         else
>                 at91_twi_write(dev, AT91_TWI_IDR, AT91_TWI_INT_MASK_MASTER);
> }
> 

I don't think it's necessary. Usually, writing a bit which is unused don't
cause weird behaviors.

Regards

Ludovic
diff mbox series

Patch

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
 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
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 <me@jue.yt>
+ *
+ *  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) {
+		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