mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 13:22:23 +00:00
i2c: at91: added slave mode support
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>
This commit is contained in:
parent
ad7d142f89
commit
9d3ca54b55
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
@ -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;
|
||||
|
||||
|
143
drivers/i2c/busses/i2c-at91-slave.c
Normal file
143
drivers/i2c/busses/i2c-at91-slave.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user