Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d3ca54b authored by Juergen Fitschen's avatar Juergen Fitschen Committed by Wolfram Sang
Browse files

i2c: at91: added slave mode support



Slave mode driver is based on the concept of i2c-designware driver.

Signed-off-by: default avatarJuergen Fitschen <me@jue.yt>
[ludovic.desroches@microchip.com: rework Kconfig and replace IS_ENABLED
by defined]
Signed-off-by: default avatarLudovic Desroches <ludovic.desroches@microchip.com>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent ad7d142f
Loading
Loading
Loading
Loading
+13 −0
Original line number Original line Diff line number Diff line
@@ -387,6 +387,19 @@ config I2C_AT91
	  the latency to fill the transmission register is too long. If you
	  the latency to fill the transmission register is too long. If you
	  are facing this situation, use the i2c-gpio driver.
	  are facing this situation, use the i2c-gpio driver.


config I2C_AT91_SLAVE_EXPERIMENTAL
	tristate "Microchip AT91 I2C experimental slave mode"
	depends on I2C_AT91
	select I2C_SLAVE
	help
	  If you say yes to this option, support for the slave mode will be
	  added. Caution: do not use it for production. This feature has not
	  been tested in a heavy way, help wanted.
	  There are known bugs:
	    - It can hang, on a SAMA5D4, after several transfers.
	    - There are some mismtaches with a SAMA5D4 as slave and a SAMA5D2 as
	    master.

config I2C_AU1550
config I2C_AU1550
	tristate "Au1550/Au1200/Au1300 SMBus interface"
	tristate "Au1550/Au1200/Au1300 SMBus interface"
	depends on MIPS_ALCHEMY
	depends on MIPS_ALCHEMY
+3 −0
Original line number Original line Diff line number Diff line
@@ -36,6 +36,9 @@ obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o
obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
obj-$(CONFIG_I2C_AT91)		+= i2c-at91.o
obj-$(CONFIG_I2C_AT91)		+= i2c-at91.o
i2c-at91-objs			:= i2c-at91-core.o i2c-at91-master.o
i2c-at91-objs			:= i2c-at91-core.o i2c-at91-master.o
ifeq ($(CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL),y)
	i2c-at91-objs		+= i2c-at91-slave.o
endif
obj-$(CONFIG_I2C_AU1550)	+= i2c-au1550.o
obj-$(CONFIG_I2C_AU1550)	+= i2c-au1550.o
obj-$(CONFIG_I2C_AXXIA)		+= i2c-axxia.o
obj-$(CONFIG_I2C_AXXIA)		+= i2c-axxia.o
obj-$(CONFIG_I2C_BCM2835)	+= i2c-bcm2835.o
obj-$(CONFIG_I2C_BCM2835)	+= i2c-bcm2835.o
+10 −3
Original line number Original line Diff line number Diff line
@@ -56,7 +56,9 @@ void at91_init_twi_bus(struct at91_twi_dev *dev)
{
{
	at91_disable_twi_interrupts(dev);
	at91_disable_twi_interrupts(dev);
	at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SWRST);
	at91_twi_write(dev, AT91_TWI_CR, AT91_TWI_SWRST);

	if (dev->slave_detected)
		at91_init_twi_bus_slave(dev);
	else
		at91_init_twi_bus_master(dev);
		at91_init_twi_bus_master(dev);
}
}


@@ -239,6 +241,11 @@ static int at91_twi_probe(struct platform_device *pdev)
	dev->adapter.timeout = AT91_I2C_TIMEOUT;
	dev->adapter.timeout = AT91_I2C_TIMEOUT;
	dev->adapter.dev.of_node = pdev->dev.of_node;
	dev->adapter.dev.of_node = pdev->dev.of_node;


	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);
		rc = at91_twi_probe_master(pdev, phy_addr, dev);
	if (rc)
	if (rc)
		return rc;
		return rc;
+143 −0
Original line number Original line Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 *  i2c slave support for Atmel's AT91 Two-Wire Interface (TWI)
 *
 *  Copyright (C) 2017 Juergen Fitschen <me@jue.yt>
 */

#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);
	}
}
+29 −1
Original line number Original line Diff line number Diff line
@@ -49,6 +49,10 @@
#define	AT91_TWI_IADRSZ_1	0x0100	/* Internal Device Address Size */
#define	AT91_TWI_IADRSZ_1	0x0100	/* Internal Device Address Size */
#define	AT91_TWI_MREAD		BIT(12)	/* Master Read Direction */
#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_IADR		0x000c	/* Internal Address Register */


#define	AT91_TWI_CWGR		0x0010	/* Clock Waveform Generator Reg */
#define	AT91_TWI_CWGR		0x0010	/* Clock Waveform Generator Reg */
@@ -59,13 +63,17 @@
#define	AT91_TWI_TXCOMP		BIT(0)	/* Transmission Complete */
#define	AT91_TWI_TXCOMP		BIT(0)	/* Transmission Complete */
#define	AT91_TWI_RXRDY		BIT(1)	/* Receive Holding Register Ready */
#define	AT91_TWI_RXRDY		BIT(1)	/* Receive Holding Register Ready */
#define	AT91_TWI_TXRDY		BIT(2)	/* Transmit 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_OVRE		BIT(6)	/* Overrun Error */
#define	AT91_TWI_UNRE		BIT(7)	/* Underrun Error */
#define	AT91_TWI_UNRE		BIT(7)	/* Underrun Error */
#define	AT91_TWI_NACK		BIT(8)	/* Not Acknowledged */
#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_LOCK		BIT(23) /* TWI Lock due to Frame Errors */


#define	AT91_TWI_INT_MASK \
#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_IER		0x0024	/* Interrupt Enable Register */
#define	AT91_TWI_IDR		0x0028	/* Interrupt Disable Register */
#define	AT91_TWI_IDR		0x0028	/* Interrupt Disable Register */
@@ -133,6 +141,11 @@ struct at91_twi_dev {
	bool recv_len_abort;
	bool recv_len_abort;
	u32 fifo_size;
	u32 fifo_size;
	struct at91_twi_dma dma;
	struct at91_twi_dma dma;
	bool slave_detected;
#ifdef CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL
	unsigned smr;
	struct i2c_client *slave;
#endif
};
};


unsigned at91_twi_read(struct at91_twi_dev *dev, unsigned reg);
unsigned at91_twi_read(struct at91_twi_dev *dev, unsigned reg);
@@ -145,3 +158,18 @@ void at91_init_twi_bus(struct at91_twi_dev *dev);
void at91_init_twi_bus_master(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,
int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr,
			  struct at91_twi_dev *dev);
			  struct at91_twi_dev *dev);

#ifdef CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL
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