summaryrefslogtreecommitdiffstats
path: root/drivers/i2c/busses/i2c-at91-slave.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i2c/busses/i2c-at91-slave.c')
-rw-r--r--drivers/i2c/busses/i2c-at91-slave.c143
1 files changed, 143 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-at91-slave.c b/drivers/i2c/busses/i2c-at91-slave.c
new file mode 100644
index 000000000..d6eeea516
--- /dev/null
+++ b/drivers/i2c/busses/i2c-at91-slave.c
@@ -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);
+ }
+}