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

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
...@@ -387,6 +387,19 @@ config I2C_AT91 ...@@ -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
......
...@@ -36,6 +36,9 @@ obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o ...@@ -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
......
...@@ -56,8 +56,10 @@ void at91_init_twi_bus(struct at91_twi_dev *dev) ...@@ -56,8 +56,10 @@ 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_master(dev); at91_init_twi_bus_slave(dev);
else
at91_init_twi_bus_master(dev);
} }
static struct at91_twi_pdata at91rm9200_config = { static struct at91_twi_pdata at91rm9200_config = {
...@@ -239,7 +241,12 @@ static int at91_twi_probe(struct platform_device *pdev) ...@@ -239,7 +241,12 @@ 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;
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) if (rc)
return rc; return rc;
......
// 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);
}
}
...@@ -49,6 +49,10 @@ ...@@ -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 @@ ...@@ -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 { ...@@ -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); ...@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment