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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxx>
> ---
>  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@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;
> +
> +	/* 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
> 



[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux