]> Pileus Git - ~andy/linux/blobdiff - drivers/i2c/busses/i2c-mv64xxx.c
i2c: mv64xxx: Fix timing issue on Armada XP (errata FE-8471889)
[~andy/linux] / drivers / i2c / busses / i2c-mv64xxx.c
index b1f42bf409638f166f592eba381ac9848dccb46a..bc60f9ac7c04c01ad27f9b4ed13f7ee6c2c86480 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/of_i2c.h>
 #include <linux/clk.h>
 #include <linux/err.h>
+#include <linux/delay.h>
 
 #define MV64XXX_I2C_ADDR_ADDR(val)                     ((val & 0x7f) << 1)
 #define MV64XXX_I2C_BAUD_DIV_N(val)                    (val & 0x7)
 #define        MV64XXX_I2C_STATUS_MAST_RD_ADDR_2_NO_ACK        0xe8
 #define        MV64XXX_I2C_STATUS_NO_STATUS                    0xf8
 
+/* Register defines (I2C bridge) */
+#define        MV64XXX_I2C_REG_TX_DATA_LO                      0xc0
+#define        MV64XXX_I2C_REG_TX_DATA_HI                      0xc4
+#define        MV64XXX_I2C_REG_RX_DATA_LO                      0xc8
+#define        MV64XXX_I2C_REG_RX_DATA_HI                      0xcc
+#define        MV64XXX_I2C_REG_BRIDGE_CONTROL                  0xd0
+#define        MV64XXX_I2C_REG_BRIDGE_STATUS                   0xd4
+#define        MV64XXX_I2C_REG_BRIDGE_INTR_CAUSE               0xd8
+#define        MV64XXX_I2C_REG_BRIDGE_INTR_MASK                0xdC
+#define        MV64XXX_I2C_REG_BRIDGE_TIMING                   0xe0
+
+/* Bridge Control values */
+#define        MV64XXX_I2C_BRIDGE_CONTROL_WR                   0x00000001
+#define        MV64XXX_I2C_BRIDGE_CONTROL_RD                   0x00000002
+#define        MV64XXX_I2C_BRIDGE_CONTROL_ADDR_SHIFT           2
+#define        MV64XXX_I2C_BRIDGE_CONTROL_ADDR_EXT             0x00001000
+#define        MV64XXX_I2C_BRIDGE_CONTROL_TX_SIZE_SHIFT        13
+#define        MV64XXX_I2C_BRIDGE_CONTROL_RX_SIZE_SHIFT        16
+#define        MV64XXX_I2C_BRIDGE_CONTROL_ENABLE               0x00080000
+
+/* Bridge Status values */
+#define        MV64XXX_I2C_BRIDGE_STATUS_ERROR                 0x00000001
+#define        MV64XXX_I2C_STATUS_OFFLOAD_ERROR                0xf0000001
+#define        MV64XXX_I2C_STATUS_OFFLOAD_OK                   0xf0000000
+
+
 /* Driver states */
 enum {
        MV64XXX_I2C_STATE_INVALID,
@@ -71,14 +98,17 @@ enum {
 enum {
        MV64XXX_I2C_ACTION_INVALID,
        MV64XXX_I2C_ACTION_CONTINUE,
+       MV64XXX_I2C_ACTION_OFFLOAD_SEND_START,
        MV64XXX_I2C_ACTION_SEND_START,
        MV64XXX_I2C_ACTION_SEND_RESTART,
+       MV64XXX_I2C_ACTION_OFFLOAD_RESTART,
        MV64XXX_I2C_ACTION_SEND_ADDR_1,
        MV64XXX_I2C_ACTION_SEND_ADDR_2,
        MV64XXX_I2C_ACTION_SEND_DATA,
        MV64XXX_I2C_ACTION_RCV_DATA,
        MV64XXX_I2C_ACTION_RCV_DATA_STOP,
        MV64XXX_I2C_ACTION_SEND_STOP,
+       MV64XXX_I2C_ACTION_OFFLOAD_SEND_STOP,
 };
 
 struct mv64xxx_i2c_regs {
@@ -117,6 +147,9 @@ struct mv64xxx_i2c_data {
        spinlock_t              lock;
        struct i2c_msg          *msg;
        struct i2c_adapter      adapter;
+       bool                    offload_enabled;
+/* 5us delay in order to avoid repeated start timing violation */
+       bool                    errata_delay;
 };
 
 static struct mv64xxx_i2c_regs mv64xxx_i2c_regs_mv64xxx = {
@@ -165,6 +198,77 @@ mv64xxx_i2c_prepare_for_io(struct mv64xxx_i2c_data *drv_data,
        }
 }
 
+static int mv64xxx_i2c_offload_msg(struct mv64xxx_i2c_data *drv_data)
+{
+       unsigned long data_reg_hi = 0;
+       unsigned long data_reg_lo = 0;
+       unsigned long ctrl_reg;
+       struct i2c_msg *msg = drv_data->msgs;
+
+       drv_data->msg = msg;
+       drv_data->byte_posn = 0;
+       drv_data->bytes_left = msg->len;
+       drv_data->aborting = 0;
+       drv_data->rc = 0;
+       /* Only regular transactions can be offloaded */
+       if ((msg->flags & ~(I2C_M_TEN | I2C_M_RD)) != 0)
+               return -EINVAL;
+
+       /* Only 1-8 byte transfers can be offloaded */
+       if (msg->len < 1 || msg->len > 8)
+               return -EINVAL;
+
+       /* Build transaction */
+       ctrl_reg = MV64XXX_I2C_BRIDGE_CONTROL_ENABLE |
+                  (msg->addr << MV64XXX_I2C_BRIDGE_CONTROL_ADDR_SHIFT);
+
+       if ((msg->flags & I2C_M_TEN) != 0)
+               ctrl_reg |=  MV64XXX_I2C_BRIDGE_CONTROL_ADDR_EXT;
+
+       if ((msg->flags & I2C_M_RD) == 0) {
+               u8 local_buf[8] = { 0 };
+
+               memcpy(local_buf, msg->buf, msg->len);
+               data_reg_lo = cpu_to_le32(*((u32 *)local_buf));
+               data_reg_hi = cpu_to_le32(*((u32 *)(local_buf+4)));
+
+               ctrl_reg |= MV64XXX_I2C_BRIDGE_CONTROL_WR |
+                   (msg->len - 1) << MV64XXX_I2C_BRIDGE_CONTROL_TX_SIZE_SHIFT;
+
+               writel_relaxed(data_reg_lo,
+                       drv_data->reg_base + MV64XXX_I2C_REG_TX_DATA_LO);
+               writel_relaxed(data_reg_hi,
+                       drv_data->reg_base + MV64XXX_I2C_REG_TX_DATA_HI);
+
+       } else {
+               ctrl_reg |= MV64XXX_I2C_BRIDGE_CONTROL_RD |
+                   (msg->len - 1) << MV64XXX_I2C_BRIDGE_CONTROL_RX_SIZE_SHIFT;
+       }
+
+       /* Execute transaction */
+       writel(ctrl_reg, drv_data->reg_base + MV64XXX_I2C_REG_BRIDGE_CONTROL);
+
+       return 0;
+}
+
+static void
+mv64xxx_i2c_update_offload_data(struct mv64xxx_i2c_data *drv_data)
+{
+       struct i2c_msg *msg = drv_data->msg;
+
+       if (msg->flags & I2C_M_RD) {
+               u32 data_reg_lo = readl(drv_data->reg_base +
+                               MV64XXX_I2C_REG_RX_DATA_LO);
+               u32 data_reg_hi = readl(drv_data->reg_base +
+                               MV64XXX_I2C_REG_RX_DATA_HI);
+               u8 local_buf[8] = { 0 };
+
+               *((u32 *)local_buf) = le32_to_cpu(data_reg_lo);
+               *((u32 *)(local_buf+4)) = le32_to_cpu(data_reg_hi);
+               memcpy(msg->buf, local_buf, msg->len);
+       }
+
+}
 /*
  *****************************************************************************
  *
@@ -177,6 +281,15 @@ mv64xxx_i2c_prepare_for_io(struct mv64xxx_i2c_data *drv_data,
 static void
 mv64xxx_i2c_hw_init(struct mv64xxx_i2c_data *drv_data)
 {
+       if (drv_data->offload_enabled) {
+               writel(0, drv_data->reg_base + MV64XXX_I2C_REG_BRIDGE_CONTROL);
+               writel(0, drv_data->reg_base + MV64XXX_I2C_REG_BRIDGE_TIMING);
+               writel(0, drv_data->reg_base +
+                       MV64XXX_I2C_REG_BRIDGE_INTR_CAUSE);
+               writel(0, drv_data->reg_base +
+                       MV64XXX_I2C_REG_BRIDGE_INTR_MASK);
+       }
+
        writel(0, drv_data->reg_base + drv_data->reg_offsets.soft_reset);
        writel(MV64XXX_I2C_BAUD_DIV_M(drv_data->freq_m) | MV64XXX_I2C_BAUD_DIV_N(drv_data->freq_n),
                drv_data->reg_base + drv_data->reg_offsets.clock);
@@ -283,6 +396,16 @@ mv64xxx_i2c_fsm(struct mv64xxx_i2c_data *drv_data, u32 status)
                drv_data->rc = -ENXIO;
                break;
 
+       case MV64XXX_I2C_STATUS_OFFLOAD_OK:
+               if (drv_data->send_stop || drv_data->aborting) {
+                       drv_data->action = MV64XXX_I2C_ACTION_OFFLOAD_SEND_STOP;
+                       drv_data->state = MV64XXX_I2C_STATE_IDLE;
+               } else {
+                       drv_data->action = MV64XXX_I2C_ACTION_OFFLOAD_RESTART;
+                       drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_RESTART;
+               }
+               break;
+
        default:
                dev_err(&drv_data->adapter.dev,
                        "mv64xxx_i2c_fsm: Ctlr Error -- state: 0x%x, "
@@ -299,19 +422,29 @@ static void
 mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
 {
        switch(drv_data->action) {
+       case MV64XXX_I2C_ACTION_OFFLOAD_RESTART:
+               mv64xxx_i2c_update_offload_data(drv_data);
+               writel(0, drv_data->reg_base +  MV64XXX_I2C_REG_BRIDGE_CONTROL);
+               writel(0, drv_data->reg_base +
+                       MV64XXX_I2C_REG_BRIDGE_INTR_CAUSE);
+               /* FALLTHRU */
        case MV64XXX_I2C_ACTION_SEND_RESTART:
                /* We should only get here if we have further messages */
                BUG_ON(drv_data->num_msgs == 0);
 
-               drv_data->cntl_bits |= MV64XXX_I2C_REG_CONTROL_START;
-               writel(drv_data->cntl_bits,
-                       drv_data->reg_base + drv_data->reg_offsets.control);
-
                drv_data->msgs++;
                drv_data->num_msgs--;
+               if (!(drv_data->offload_enabled &&
+                               mv64xxx_i2c_offload_msg(drv_data))) {
+                       drv_data->cntl_bits |= MV64XXX_I2C_REG_CONTROL_START;
+                       writel(drv_data->cntl_bits,
+                       drv_data->reg_base + drv_data->reg_offsets.control);
 
-               /* Setup for the next message */
-               mv64xxx_i2c_prepare_for_io(drv_data, drv_data->msgs);
+                       /* Setup for the next message */
+                       mv64xxx_i2c_prepare_for_io(drv_data, drv_data->msgs);
+               }
+               if (drv_data->errata_delay)
+                       udelay(5);
 
                /*
                 * We're never at the start of the message here, and by this
@@ -326,6 +459,12 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
                        drv_data->reg_base + drv_data->reg_offsets.control);
                break;
 
+       case MV64XXX_I2C_ACTION_OFFLOAD_SEND_START:
+               if (!mv64xxx_i2c_offload_msg(drv_data))
+                       break;
+               else
+                       drv_data->action = MV64XXX_I2C_ACTION_SEND_START;
+               /* FALLTHRU */
        case MV64XXX_I2C_ACTION_SEND_START:
                writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_START,
                        drv_data->reg_base + drv_data->reg_offsets.control);
@@ -366,6 +505,9 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
                writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP,
                        drv_data->reg_base + drv_data->reg_offsets.control);
                drv_data->block = 0;
+               if (drv_data->errata_delay)
+                       udelay(5);
+
                wake_up(&drv_data->waitq);
                break;
 
@@ -375,6 +517,7 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
                        "mv64xxx_i2c_do_action: Invalid action: %d\n",
                        drv_data->action);
                drv_data->rc = -EIO;
+
                /* FALLTHRU */
        case MV64XXX_I2C_ACTION_SEND_STOP:
                drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN;
@@ -383,6 +526,15 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
                drv_data->block = 0;
                wake_up(&drv_data->waitq);
                break;
+
+       case MV64XXX_I2C_ACTION_OFFLOAD_SEND_STOP:
+               mv64xxx_i2c_update_offload_data(drv_data);
+               writel(0, drv_data->reg_base +  MV64XXX_I2C_REG_BRIDGE_CONTROL);
+               writel(0, drv_data->reg_base +
+                       MV64XXX_I2C_REG_BRIDGE_INTR_CAUSE);
+               drv_data->block = 0;
+               wake_up(&drv_data->waitq);
+               break;
        }
 }
 
@@ -395,6 +547,21 @@ mv64xxx_i2c_intr(int irq, void *dev_id)
        irqreturn_t     rc = IRQ_NONE;
 
        spin_lock_irqsave(&drv_data->lock, flags);
+
+       if (drv_data->offload_enabled) {
+               while (readl(drv_data->reg_base +
+                               MV64XXX_I2C_REG_BRIDGE_INTR_CAUSE)) {
+                       int reg_status = readl(drv_data->reg_base +
+                                       MV64XXX_I2C_REG_BRIDGE_STATUS);
+                       if (reg_status & MV64XXX_I2C_BRIDGE_STATUS_ERROR)
+                               status = MV64XXX_I2C_STATUS_OFFLOAD_ERROR;
+                       else
+                               status = MV64XXX_I2C_STATUS_OFFLOAD_OK;
+                       mv64xxx_i2c_fsm(drv_data, status);
+                       mv64xxx_i2c_do_action(drv_data);
+                       rc = IRQ_HANDLED;
+               }
+       }
        while (readl(drv_data->reg_base + drv_data->reg_offsets.control) &
                                                MV64XXX_I2C_REG_CONTROL_IFLG) {
                status = readl(drv_data->reg_base + drv_data->reg_offsets.status);
@@ -459,11 +626,15 @@ mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg,
        unsigned long   flags;
 
        spin_lock_irqsave(&drv_data->lock, flags);
-       mv64xxx_i2c_prepare_for_io(drv_data, msg);
-
-       drv_data->action = MV64XXX_I2C_ACTION_SEND_START;
-       drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_START_COND;
+       if (drv_data->offload_enabled) {
+               drv_data->action = MV64XXX_I2C_ACTION_OFFLOAD_SEND_START;
+               drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_START_COND;
+       } else {
+               mv64xxx_i2c_prepare_for_io(drv_data, msg);
 
+               drv_data->action = MV64XXX_I2C_ACTION_SEND_START;
+               drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_START_COND;
+       }
        drv_data->send_stop = is_last;
        drv_data->block = 1;
        mv64xxx_i2c_do_action(drv_data);
@@ -521,6 +692,7 @@ static const struct i2c_algorithm mv64xxx_i2c_algo = {
 static const struct of_device_id mv64xxx_i2c_of_match_table[] = {
        { .compatible = "allwinner,sun4i-i2c", .data = &mv64xxx_i2c_regs_sun4i},
        { .compatible = "marvell,mv64xxx-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
+       { .compatible = "marvell,mv78230-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
        {}
 };
 MODULE_DEVICE_TABLE(of, mv64xxx_i2c_of_match_table);
@@ -601,6 +773,15 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data,
 
        memcpy(&drv_data->reg_offsets, device->data, sizeof(drv_data->reg_offsets));
 
+       /*
+        * For controllers embedded in new SoCs activate the
+        * Transaction Generator support and the errata fix.
+        */
+       if (of_device_is_compatible(np, "marvell,mv78230-i2c")) {
+               drv_data->offload_enabled = true;
+               drv_data->errata_delay = true;
+       }
+
 out:
        return rc;
 #endif
@@ -618,7 +799,7 @@ static int
 mv64xxx_i2c_probe(struct platform_device *pd)
 {
        struct mv64xxx_i2c_data         *drv_data;
-       struct mv64xxx_i2c_pdata        *pdata = pd->dev.platform_data;
+       struct mv64xxx_i2c_pdata        *pdata = dev_get_platdata(&pd->dev);
        struct resource *r;
        int     rc;
 
@@ -654,6 +835,7 @@ mv64xxx_i2c_probe(struct platform_device *pd)
                drv_data->freq_n = pdata->freq_n;
                drv_data->irq = platform_get_irq(pd, 0);
                drv_data->adapter.timeout = msecs_to_jiffies(pdata->timeout);
+               drv_data->offload_enabled = false;
                memcpy(&drv_data->reg_offsets, &mv64xxx_i2c_regs_mv64xxx, sizeof(drv_data->reg_offsets));
        } else if (pd->dev.of_node) {
                rc = mv64xxx_of_config(drv_data, &pd->dev);