| From 0fc6ef4f18ff9d06db71534ddd972bb4e2886db0 Mon Sep 17 00:00:00 2001 |
| From: Gregory CLEMENT <gregory.clement@free-electrons.com> |
| Date: Thu, 22 Aug 2013 16:19:05 +0200 |
| Subject: i2c: mv64xxx: Add I2C Transaction Generator support |
| |
| The I2C Transaction Generator offloads CPU from managing I2C |
| transfer step by step. |
| |
| This feature is currently only available on Armada XP, so usage of |
| this mechanism is activated through device tree. |
| |
| Based on the work of Piotr Ziecik and rewrote to use the new way of |
| handling multiples i2c messages. |
| |
| Signed-off-by: Piotr Ziecik <kosmo@semihalf.com> |
| Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> |
| Signed-off-by: Wolfram Sang <wsa@the-dreams.de> |
| (cherry picked from commit 930ab3d403ae43f19d7e6d972139e02c9b8a5ec6) |
| Signed-off-by: Darren Hart <dvhart@linux.intel.com> |
| --- |
| .../devicetree/bindings/i2c/i2c-mv64xxx.txt | 10 ++ |
| drivers/i2c/busses/i2c-mv64xxx.c | 193 +++++++++++++++++++-- |
| 2 files changed, 192 insertions(+), 11 deletions(-) |
| |
| diff --git a/Documentation/devicetree/bindings/i2c/i2c-mv64xxx.txt b/Documentation/devicetree/bindings/i2c/i2c-mv64xxx.txt |
| index 6113f9275f42..73cdc03cc2bf 100644 |
| --- a/Documentation/devicetree/bindings/i2c/i2c-mv64xxx.txt |
| +++ b/Documentation/devicetree/bindings/i2c/i2c-mv64xxx.txt |
| @@ -5,6 +5,7 @@ Required properties : |
| |
| - reg : Offset and length of the register set for the device |
| - compatible : Should be "marvell,mv64xxx-i2c" or "allwinner,sun4i-i2c" |
| + or "marvell,mv7230-i2c" |
| - interrupts : The interrupt number |
| |
| Optional properties : |
| @@ -20,3 +21,12 @@ Examples: |
| interrupts = <29>; |
| clock-frequency = <100000>; |
| }; |
| + |
| +For the Armada XP: |
| + |
| + i2c@11000 { |
| + compatible = "marvell,mv78230-i2c", "marvell,mv64xxx-i2c"; |
| + reg = <0x11000 0x100>; |
| + interrupts = <29>; |
| + clock-frequency = <100000>; |
| + }; |
| diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c |
| index 9cc361d19941..2404c4e0f35c 100644 |
| --- a/drivers/i2c/busses/i2c-mv64xxx.c |
| +++ b/drivers/i2c/busses/i2c-mv64xxx.c |
| @@ -55,6 +55,32 @@ |
| #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 +97,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 +146,7 @@ struct mv64xxx_i2c_data { |
| spinlock_t lock; |
| struct i2c_msg *msg; |
| struct i2c_adapter adapter; |
| + bool offload_enabled; |
| }; |
| |
| static struct mv64xxx_i2c_regs mv64xxx_i2c_regs_mv64xxx = { |
| @@ -165,6 +195,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 +278,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 +393,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,20 +419,27 @@ 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); |
| + } |
| /* |
| * We're never at the start of the message here, and by this |
| * time it's already too late to do any protocol mangling. |
| @@ -326,6 +453,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); |
| @@ -375,6 +508,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 +517,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 +538,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 +617,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 +683,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 +764,13 @@ 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. |
| + */ |
| + if (of_device_is_compatible(np, "marvell,mv78230-i2c")) |
| + drv_data->offload_enabled = true; |
| + |
| out: |
| return rc; |
| #endif |
| @@ -654,6 +824,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); |
| -- |
| 1.8.5.rc3 |
| |