i2c: cadence: Added slave support
authorRadu Pirea <radu_nicolae.pirea@upb.ro>
Mon, 6 Jan 2020 10:43:36 +0000 (12:43 +0200)
committerWolfram Sang <wsa@the-dreams.de>
Wed, 15 Apr 2020 16:25:39 +0000 (18:25 +0200)
Added support for I2C slave functionality

Signed-off-by: Chirag Parekh <chirag.parekh@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
Signed-off-by: Radu Pirea <radu_nicolae.pirea@upb.ro>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
drivers/i2c/busses/i2c-cadence.c

index 803c802611eb5354aa33da6dc03e4dd8bcb52ac9..4b72398af505df85d5d318b2aa82e69fe0dfb3e3 100644 (file)
@@ -23,6 +23,7 @@
 #define CDNS_I2C_ISR_OFFSET            0x10 /* IRQ Status Register, RW */
 #define CDNS_I2C_XFER_SIZE_OFFSET      0x14 /* Transfer Size Register, RW */
 #define CDNS_I2C_TIME_OUT_OFFSET       0x1C /* Time Out Register, RW */
+#define CDNS_I2C_IMR_OFFSET            0x20 /* IRQ Mask Register, RO */
 #define CDNS_I2C_IER_OFFSET            0x24 /* IRQ Enable Register, WO */
 #define CDNS_I2C_IDR_OFFSET            0x28 /* IRQ Disable Register, WO */
 
 #define CDNS_I2C_CR_DIVB_SHIFT         8
 #define CDNS_I2C_CR_DIVB_MASK          (0x3f << CDNS_I2C_CR_DIVB_SHIFT)
 
+#define CDNS_I2C_CR_MASTER_EN_MASK     (CDNS_I2C_CR_NEA | \
+                                        CDNS_I2C_CR_ACK_EN | \
+                                        CDNS_I2C_CR_MS)
+
+#define CDNS_I2C_CR_SLAVE_EN_MASK      ~CDNS_I2C_CR_MASTER_EN_MASK
+
 /* Status Register Bit mask definitions */
 #define CDNS_I2C_SR_BA         BIT(8)
+#define CDNS_I2C_SR_TXDV       BIT(6)
 #define CDNS_I2C_SR_RXDV       BIT(5)
+#define CDNS_I2C_SR_RXRW       BIT(3)
 
 /*
  * I2C Address Register Bit mask definitions
                                         CDNS_I2C_IXR_DATA | \
                                         CDNS_I2C_IXR_COMP)
 
+#define CDNS_I2C_IXR_SLAVE_INTR_MASK   (CDNS_I2C_IXR_RX_UNF | \
+                                        CDNS_I2C_IXR_TX_OVF | \
+                                        CDNS_I2C_IXR_RX_OVF | \
+                                        CDNS_I2C_IXR_TO | \
+                                        CDNS_I2C_IXR_NACK | \
+                                        CDNS_I2C_IXR_DATA | \
+                                        CDNS_I2C_IXR_COMP)
+
 #define CDNS_I2C_TIMEOUT               msecs_to_jiffies(1000)
 /* timeout for pm runtime autosuspend */
 #define CNDS_I2C_PM_TIMEOUT            1000    /* ms */
 #define cdns_i2c_readreg(offset)       readl_relaxed(id->membase + offset)
 #define cdns_i2c_writereg(val, offset) writel_relaxed(val, id->membase + offset)
 
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+/**
+ * enum cdns_i2c_mode - I2C Controller current operating mode
+ *
+ * @CDNS_I2C_MODE_SLAVE:       I2C controller operating in slave mode
+ * @CDNS_I2C_MODE_MASTER:      I2C Controller operating in master mode
+ */
+enum cdns_i2c_mode {
+       CDNS_I2C_MODE_SLAVE,
+       CDNS_I2C_MODE_MASTER,
+};
+
+/**
+ * enum cdns_i2c_slave_mode - Slave state when I2C is operating in slave mode
+ *
+ * @CDNS_I2C_SLAVE_STATE_IDLE: I2C slave idle
+ * @CDNS_I2C_SLAVE_STATE_SEND: I2C slave sending data to master
+ * @CDNS_I2C_SLAVE_STATE_RECV: I2C slave receiving data from master
+ */
+enum cdns_i2c_slave_state {
+       CDNS_I2C_SLAVE_STATE_IDLE,
+       CDNS_I2C_SLAVE_STATE_SEND,
+       CDNS_I2C_SLAVE_STATE_RECV,
+};
+#endif
+
 /**
  * struct cdns_i2c - I2C device private data structure
  *
  * @clk:               Pointer to struct clk
  * @clk_rate_change_nb:        Notifier block for clock rate changes
  * @quirks:            flag for broken hold bit usage in r1p10
+ * @ctrl_reg_diva_divb: value of fields DIV_A and DIV_B from CR register
+ * @slave:             Registered slave instance.
+ * @dev_mode:          I2C operating role(master/slave).
+ * @slave_state:       I2C Slave state(idle/read/write).
  */
 struct cdns_i2c {
        struct device           *dev;
@@ -155,6 +202,12 @@ struct cdns_i2c {
        struct clk *clk;
        struct notifier_block clk_rate_change_nb;
        u32 quirks;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       u16 ctrl_reg_diva_divb;
+       struct i2c_client *slave;
+       enum cdns_i2c_mode dev_mode;
+       enum cdns_i2c_slave_state slave_state;
+#endif
 };
 
 struct cdns_platform_data {
@@ -183,17 +236,155 @@ static inline bool cdns_is_holdquirk(struct cdns_i2c *id, bool hold_wrkaround)
                (id->curr_recv_count == CDNS_I2C_FIFO_DEPTH + 1));
 }
 
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static void cdns_i2c_set_mode(enum cdns_i2c_mode mode, struct cdns_i2c *id)
+{
+       /* Disable all interrupts */
+       cdns_i2c_writereg(CDNS_I2C_IXR_ALL_INTR_MASK, CDNS_I2C_IDR_OFFSET);
+
+       /* Clear FIFO and transfer size */
+       cdns_i2c_writereg(CDNS_I2C_CR_CLR_FIFO, CDNS_I2C_CR_OFFSET);
+
+       /* Update device mode and state */
+       id->dev_mode = mode;
+       id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
+
+       switch (mode) {
+       case CDNS_I2C_MODE_MASTER:
+               /* Enable i2c master */
+               cdns_i2c_writereg(id->ctrl_reg_diva_divb |
+                                 CDNS_I2C_CR_MASTER_EN_MASK,
+                                 CDNS_I2C_CR_OFFSET);
+               /*
+                * This delay is needed to give the IP some time to switch to
+                * the master mode. With lower values(like 110 us) i2cdetect
+                * will not detect any slave and without this delay, the IP will
+                * trigger a timeout interrupt.
+                */
+               usleep_range(115, 125);
+               break;
+       case CDNS_I2C_MODE_SLAVE:
+               /* Enable i2c slave */
+               cdns_i2c_writereg(id->ctrl_reg_diva_divb &
+                                 CDNS_I2C_CR_SLAVE_EN_MASK,
+                                 CDNS_I2C_CR_OFFSET);
+
+               /* Setting slave address */
+               cdns_i2c_writereg(id->slave->addr & CDNS_I2C_ADDR_MASK,
+                                 CDNS_I2C_ADDR_OFFSET);
+
+               /* Enable slave send/receive interrupts */
+               cdns_i2c_writereg(CDNS_I2C_IXR_SLAVE_INTR_MASK,
+                                 CDNS_I2C_IER_OFFSET);
+               break;
+       }
+}
+
+static void cdns_i2c_slave_rcv_data(struct cdns_i2c *id)
+{
+       u8 bytes;
+       unsigned char data;
+
+       /* Prepare backend for data reception */
+       if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {
+               id->slave_state = CDNS_I2C_SLAVE_STATE_RECV;
+               i2c_slave_event(id->slave, I2C_SLAVE_WRITE_REQUESTED, NULL);
+       }
+
+       /* Fetch number of bytes to receive */
+       bytes = cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET);
+
+       /* Read data and send to backend */
+       while (bytes--) {
+               data = cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
+               i2c_slave_event(id->slave, I2C_SLAVE_WRITE_RECEIVED, &data);
+       }
+}
+
+static void cdns_i2c_slave_send_data(struct cdns_i2c *id)
+{
+       u8 data;
+
+       /* Prepare backend for data transmission */
+       if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {
+               id->slave_state = CDNS_I2C_SLAVE_STATE_SEND;
+               i2c_slave_event(id->slave, I2C_SLAVE_READ_REQUESTED, &data);
+       } else {
+               i2c_slave_event(id->slave, I2C_SLAVE_READ_PROCESSED, &data);
+       }
+
+       /* Send data over bus */
+       cdns_i2c_writereg(data, CDNS_I2C_DATA_OFFSET);
+}
+
 /**
- * cdns_i2c_isr - Interrupt handler for the I2C device
- * @irq:       irq number for the I2C device
- * @ptr:       void pointer to cdns_i2c structure
+ * cdns_i2c_slave_isr - Interrupt handler for the I2C device in slave role
+ * @ptr:       Pointer to I2C device private data
+ *
+ * This function handles the data interrupt and transfer complete interrupt of
+ * the I2C device in slave role.
+ *
+ * Return: IRQ_HANDLED always
+ */
+static irqreturn_t cdns_i2c_slave_isr(void *ptr)
+{
+       struct cdns_i2c *id = ptr;
+       unsigned int isr_status, i2c_status;
+
+       /* Fetch the interrupt status */
+       isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
+       cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
+
+       /* Ignore masked interrupts */
+       isr_status &= ~cdns_i2c_readreg(CDNS_I2C_IMR_OFFSET);
+
+       /* Fetch transfer mode (send/receive) */
+       i2c_status = cdns_i2c_readreg(CDNS_I2C_SR_OFFSET);
+
+       /* Handle data send/receive */
+       if (i2c_status & CDNS_I2C_SR_RXRW) {
+               /* Send data to master */
+               if (isr_status & CDNS_I2C_IXR_DATA)
+                       cdns_i2c_slave_send_data(id);
+
+               if (isr_status & CDNS_I2C_IXR_COMP) {
+                       id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
+                       i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
+               }
+       } else {
+               /* Receive data from master */
+               if (isr_status & CDNS_I2C_IXR_DATA)
+                       cdns_i2c_slave_rcv_data(id);
+
+               if (isr_status & CDNS_I2C_IXR_COMP) {
+                       cdns_i2c_slave_rcv_data(id);
+                       id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
+                       i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
+               }
+       }
+
+       /* Master indicated xfer stop or fifo underflow/overflow */
+       if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_RX_OVF |
+                         CDNS_I2C_IXR_RX_UNF | CDNS_I2C_IXR_TX_OVF)) {
+               id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
+               i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
+               cdns_i2c_writereg(CDNS_I2C_CR_CLR_FIFO, CDNS_I2C_CR_OFFSET);
+       }
+
+       return IRQ_HANDLED;
+}
+#endif
+
+/**
+ * cdns_i2c_master_isr - Interrupt handler for the I2C device in master role
+ * @ptr:       Pointer to I2C device private data
  *
  * This function handles the data interrupt, transfer complete interrupt and
- * the error interrupts of the I2C device.
+ * the error interrupts of the I2C device in master role.
  *
  * Return: IRQ_HANDLED always
  */
-static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
+static irqreturn_t cdns_i2c_master_isr(void *ptr)
 {
        unsigned int isr_status, avail_bytes, updatetx;
        unsigned int bytes_to_send;
@@ -357,6 +548,27 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
        return status;
 }
 
+/**
+ * cdns_i2c_isr - Interrupt handler for the I2C device
+ * @irq:       irq number for the I2C device
+ * @ptr:       void pointer to cdns_i2c structure
+ *
+ * This function passes the control to slave/master based on current role of
+ * i2c controller.
+ *
+ * Return: IRQ_HANDLED always
+ */
+static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
+{
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       struct cdns_i2c *id = ptr;
+
+       if (id->dev_mode == CDNS_I2C_MODE_SLAVE)
+               return cdns_i2c_slave_isr(ptr);
+#endif
+       return cdns_i2c_master_isr(ptr);
+}
+
 /**
  * cdns_i2c_mrecv - Prepare and start a master receive operation
  * @id:                pointer to the i2c device structure
@@ -577,10 +789,28 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
        u32 reg;
        struct cdns_i2c *id = adap->algo_data;
        bool hold_quirk;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       bool change_role = false;
+#endif
 
        ret = pm_runtime_get_sync(id->dev);
        if (ret < 0)
                return ret;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       /* Check i2c operating mode and switch if possible */
+       if (id->dev_mode == CDNS_I2C_MODE_SLAVE) {
+               if (id->slave_state != CDNS_I2C_SLAVE_STATE_IDLE)
+                       return -EAGAIN;
+
+               /* Set mode to master */
+               cdns_i2c_set_mode(CDNS_I2C_MODE_MASTER, id);
+
+               /* Mark flag to change role once xfer is completed */
+               change_role = true;
+       }
+#endif
+
        /* Check if the bus is free */
        if (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_BA) {
                ret = -EAGAIN;
@@ -639,7 +869,15 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
        }
 
        ret = num;
+
 out:
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       /* Switch i2c mode to slave */
+       if (change_role)
+               cdns_i2c_set_mode(CDNS_I2C_MODE_SLAVE, id);
+#endif
+
        pm_runtime_mark_last_busy(id->dev);
        pm_runtime_put_autosuspend(id->dev);
        return ret;
@@ -653,14 +891,67 @@ out:
  */
 static u32 cdns_i2c_func(struct i2c_adapter *adap)
 {
-       return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
-               (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
-               I2C_FUNC_SMBUS_BLOCK_DATA;
+       u32 func = I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
+                       (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
+                       I2C_FUNC_SMBUS_BLOCK_DATA;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       func |= I2C_FUNC_SLAVE;
+#endif
+
+       return func;
 }
 
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int cdns_reg_slave(struct i2c_client *slave)
+{
+       int ret;
+       struct cdns_i2c *id = container_of(slave->adapter, struct cdns_i2c,
+                                                                       adap);
+
+       if (id->slave)
+               return -EBUSY;
+
+       if (slave->flags & I2C_CLIENT_TEN)
+               return -EAFNOSUPPORT;
+
+       ret = pm_runtime_get_sync(id->dev);
+       if (ret < 0)
+               return ret;
+
+       /* Store slave information */
+       id->slave = slave;
+
+       /* Enable I2C slave */
+       cdns_i2c_set_mode(CDNS_I2C_MODE_SLAVE, id);
+
+       return 0;
+}
+
+static int cdns_unreg_slave(struct i2c_client *slave)
+{
+       struct cdns_i2c *id = container_of(slave->adapter, struct cdns_i2c,
+                                                                       adap);
+
+       pm_runtime_put(id->dev);
+
+       /* Remove slave information */
+       id->slave = NULL;
+
+       /* Enable I2C master */
+       cdns_i2c_set_mode(CDNS_I2C_MODE_MASTER, id);
+
+       return 0;
+}
+#endif
+
 static const struct i2c_algorithm cdns_i2c_algo = {
        .master_xfer    = cdns_i2c_master_xfer,
        .functionality  = cdns_i2c_func,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       .reg_slave      = cdns_reg_slave,
+       .unreg_slave    = cdns_unreg_slave,
+#endif
 };
 
 /**
@@ -755,7 +1046,10 @@ static int cdns_i2c_setclk(unsigned long clk_in, struct cdns_i2c *id)
        ctrl_reg |= ((div_a << CDNS_I2C_CR_DIVA_SHIFT) |
                        (div_b << CDNS_I2C_CR_DIVB_SHIFT));
        cdns_i2c_writereg(ctrl_reg, CDNS_I2C_CR_OFFSET);
-
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       id->ctrl_reg_diva_divb = ctrl_reg & (CDNS_I2C_CR_DIVA_MASK |
+                                CDNS_I2C_CR_DIVB_MASK);
+#endif
        return 0;
 }
 
@@ -948,8 +1242,12 @@ static int cdns_i2c_probe(struct platform_device *pdev)
        if (ret || (id->i2c_clk > I2C_MAX_FAST_MODE_FREQ))
                id->i2c_clk = I2C_MAX_STANDARD_MODE_FREQ;
 
-       cdns_i2c_writereg(CDNS_I2C_CR_ACK_EN | CDNS_I2C_CR_NEA | CDNS_I2C_CR_MS,
-                         CDNS_I2C_CR_OFFSET);
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+       /* Set initial mode to master */
+       id->dev_mode = CDNS_I2C_MODE_MASTER;
+       id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
+#endif
+       cdns_i2c_writereg(CDNS_I2C_CR_MASTER_EN_MASK, CDNS_I2C_CR_OFFSET);
 
        ret = cdns_i2c_setclk(id->input_clk, id);
        if (ret) {