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 Diff line number Diff line
@@ -387,6 +387,19 @@ 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_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
	tristate "Au1550/Au1200/Au1300 SMBus interface"
	depends on MIPS_ALCHEMY
+3 −0
Original line number 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_AT91)		+= i2c-at91.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_AXXIA)		+= i2c-axxia.o
obj-$(CONFIG_I2C_BCM2835)	+= i2c-bcm2835.o
+10 −3
Original line number 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_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);
}

@@ -239,6 +241,11 @@ static int at91_twi_probe(struct platform_device *pdev)
	dev->adapter.timeout = AT91_I2C_TIMEOUT;
	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);
	if (rc)
		return rc;
+143 −0
Original line number 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 Diff line number Diff line
@@ -49,6 +49,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 */
@@ -59,13 +63,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 */
@@ -133,6 +141,11 @@ struct at91_twi_dev {
	bool recv_len_abort;
	u32 fifo_size;
	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);
@@ -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);
int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr,
			  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