Skip to content

Commit

Permalink
i2c: at91: added slave mode support
Browse files Browse the repository at this point in the history
Slave mode driver is based on the concept of i2c-designware driver.

Signed-off-by: Juergen Fitschen <me@jue.yt>
[ludovic.desroches@microchip.com: rework Kconfig and replace IS_ENABLED
by defined]
Signed-off-by: Ludovic Desroches <ludovic.desroches@microchip.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
  • Loading branch information
Juergen Fitschen authored and Wolfram Sang committed Mar 24, 2019
1 parent ad7d142 commit 9d3ca54
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 4 deletions.
13 changes: 13 additions & 0 deletions drivers/i2c/busses/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions drivers/i2c/busses/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions drivers/i2c/busses/i2c-at91-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,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 = {
Expand Down Expand Up @@ -239,7 +241,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;

Expand Down
143 changes: 143 additions & 0 deletions drivers/i2c/busses/i2c-at91-slave.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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);
}
}
30 changes: 29 additions & 1 deletion drivers/i2c/busses/i2c-at91.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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 */
Expand Down Expand Up @@ -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);
Expand All @@ -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

0 comments on commit 9d3ca54

Please sign in to comment.