diff options
Diffstat (limited to 'drivers/i2c/busses/i2c-mv64xxx.c')
| -rw-r--r-- | drivers/i2c/busses/i2c-mv64xxx.c | 112 |
1 files changed, 72 insertions, 40 deletions
diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c index 5c8e94b6cdb5..1acba628e16c 100644 --- a/drivers/i2c/busses/i2c-mv64xxx.c +++ b/drivers/i2c/busses/i2c-mv64xxx.c @@ -19,16 +19,14 @@ #include <linux/platform_device.h> #include <linux/pinctrl/consumer.h> #include <linux/pm_runtime.h> +#include <linux/property.h> #include <linux/reset.h> #include <linux/io.h> #include <linux/of.h> -#include <linux/of_device.h> -#include <linux/of_irq.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_BAUD_DIV_M(val) ((val & 0xf) << 3) @@ -90,8 +88,8 @@ enum { MV64XXX_I2C_STATE_WAITING_FOR_RESTART, MV64XXX_I2C_STATE_WAITING_FOR_ADDR_1_ACK, MV64XXX_I2C_STATE_WAITING_FOR_ADDR_2_ACK, - MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_ACK, - MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_DATA, + MV64XXX_I2C_STATE_WAITING_FOR_TARGET_ACK, + MV64XXX_I2C_STATE_WAITING_FOR_TARGET_DATA, }; /* Driver actions */ @@ -150,6 +148,7 @@ struct mv64xxx_i2c_data { /* Clk div is 2 to the power n, not 2 to the power n + 1 */ bool clk_n_base_0; struct i2c_bus_recovery_info rinfo; + bool atomic; }; static struct mv64xxx_i2c_regs mv64xxx_i2c_regs_mv64xxx = { @@ -176,19 +175,17 @@ static void mv64xxx_i2c_prepare_for_io(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg) { - u32 dir = 0; - drv_data->cntl_bits = MV64XXX_I2C_REG_CONTROL_ACK | - MV64XXX_I2C_REG_CONTROL_INTEN | MV64XXX_I2C_REG_CONTROL_TWSIEN; + MV64XXX_I2C_REG_CONTROL_TWSIEN; - if (msg->flags & I2C_M_RD) - dir = 1; + if (!drv_data->atomic) + drv_data->cntl_bits |= MV64XXX_I2C_REG_CONTROL_INTEN; if (msg->flags & I2C_M_TEN) { - drv_data->addr1 = 0xf0 | (((u32)msg->addr & 0x300) >> 7) | dir; - drv_data->addr2 = (u32)msg->addr & 0xff; + drv_data->addr1 = i2c_10bit_addr_hi_from_msg(msg); + drv_data->addr2 = i2c_10bit_addr_lo_from_msg(msg); } else { - drv_data->addr1 = MV64XXX_I2C_ADDR_ADDR((u32)msg->addr) | dir; + drv_data->addr1 = i2c_8bit_addr_from_msg(msg); drv_data->addr2 = 0; } } @@ -276,7 +273,7 @@ mv64xxx_i2c_fsm(struct mv64xxx_i2c_data *drv_data, u32 status) } else { drv_data->action = MV64XXX_I2C_ACTION_SEND_DATA; drv_data->state = - MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_ACK; + MV64XXX_I2C_STATE_WAITING_FOR_TARGET_ACK; drv_data->bytes_left--; } break; @@ -304,7 +301,7 @@ mv64xxx_i2c_fsm(struct mv64xxx_i2c_data *drv_data, u32 status) drv_data->action = MV64XXX_I2C_ACTION_RCV_DATA; drv_data->bytes_left--; } - drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_SLAVE_DATA; + drv_data->state = MV64XXX_I2C_STATE_WAITING_FOR_TARGET_DATA; if ((drv_data->bytes_left == 1) || drv_data->aborting) drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_ACK; @@ -409,7 +406,8 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) case MV64XXX_I2C_ACTION_RCV_DATA_STOP: drv_data->msg->buf[drv_data->byte_posn++] = readl(drv_data->reg_base + drv_data->reg_offsets.data); - drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; + if (!drv_data->atomic) + drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP, drv_data->reg_base + drv_data->reg_offsets.control); drv_data->block = 0; @@ -427,7 +425,8 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) drv_data->rc = -EIO; fallthrough; case MV64XXX_I2C_ACTION_SEND_STOP: - drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; + if (!drv_data->atomic) + drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN; writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP, drv_data->reg_base + drv_data->reg_offsets.control); drv_data->block = 0; @@ -514,6 +513,17 @@ mv64xxx_i2c_intr(int irq, void *dev_id) while (readl(drv_data->reg_base + drv_data->reg_offsets.control) & MV64XXX_I2C_REG_CONTROL_IFLG) { + /* + * It seems that sometime the controller updates the status + * register only after it asserts IFLG in control register. + * This may result in weird bugs when in atomic mode. A delay + * of 100 ns before reading the status register solves this + * issue. This bug does not seem to appear when using + * interrupts. + */ + if (drv_data->atomic) + ndelay(100); + status = readl(drv_data->reg_base + drv_data->reg_offsets.status); mv64xxx_i2c_fsm(drv_data, status); mv64xxx_i2c_do_action(drv_data); @@ -575,6 +585,17 @@ mv64xxx_i2c_wait_for_completion(struct mv64xxx_i2c_data *drv_data) spin_unlock_irqrestore(&drv_data->lock, flags); } +static void mv64xxx_i2c_wait_polling(struct mv64xxx_i2c_data *drv_data) +{ + ktime_t timeout = ktime_add_ms(ktime_get(), drv_data->adapter.timeout); + + while (READ_ONCE(drv_data->block) && + ktime_compare(ktime_get(), timeout) < 0) { + udelay(5); + mv64xxx_i2c_intr(0, drv_data); + } +} + static int mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg, int is_last) @@ -590,7 +611,11 @@ mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg, mv64xxx_i2c_send_start(drv_data); spin_unlock_irqrestore(&drv_data->lock, flags); - mv64xxx_i2c_wait_for_completion(drv_data); + if (!drv_data->atomic) + mv64xxx_i2c_wait_for_completion(drv_data); + else + mv64xxx_i2c_wait_polling(drv_data); + return drv_data->rc; } @@ -717,7 +742,7 @@ mv64xxx_i2c_functionality(struct i2c_adapter *adap) } static int -mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +mv64xxx_i2c_xfer_core(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); int rc, ret = num; @@ -730,7 +755,7 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) drv_data->msgs = msgs; drv_data->num_msgs = num; - if (mv64xxx_i2c_can_offload(drv_data)) + if (mv64xxx_i2c_can_offload(drv_data) && !drv_data->atomic) rc = mv64xxx_i2c_offload_xfer(drv_data); else rc = mv64xxx_i2c_execute_msg(drv_data, &msgs[0], num == 1); @@ -741,14 +766,32 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) drv_data->num_msgs = 0; drv_data->msgs = NULL; - pm_runtime_mark_last_busy(&adap->dev); pm_runtime_put_autosuspend(&adap->dev); return ret; } +static int +mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); + + drv_data->atomic = 0; + return mv64xxx_i2c_xfer_core(adap, msgs, num); +} + +static int mv64xxx_i2c_xfer_atomic(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); + + drv_data->atomic = 1; + return mv64xxx_i2c_xfer_core(adap, msgs, num); +} + static const struct i2c_algorithm mv64xxx_i2c_algo = { - .master_xfer = mv64xxx_i2c_xfer, + .xfer = mv64xxx_i2c_xfer, + .xfer_atomic = mv64xxx_i2c_xfer_atomic, .functionality = mv64xxx_i2c_functionality, }; @@ -808,7 +851,7 @@ static int mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, struct device *dev) { - const struct of_device_id *device; + const struct mv64xxx_i2c_regs *data; struct device_node *np = dev->of_node; u32 bus_freq, tclk; int rc = 0; @@ -846,11 +889,11 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, */ drv_data->adapter.timeout = HZ; - device = of_match_device(mv64xxx_i2c_of_match_table, dev); - if (!device) + data = device_get_match_data(dev); + if (!data) return -ENODEV; - memcpy(&drv_data->reg_offsets, device->data, sizeof(drv_data->reg_offsets)); + memcpy(&drv_data->reg_offsets, data, sizeof(drv_data->reg_offsets)); /* * For controllers embedded in new SoCs activate the @@ -949,7 +992,7 @@ mv64xxx_i2c_probe(struct platform_device *pd) if (IS_ERR(drv_data->reg_base)) return PTR_ERR(drv_data->reg_base); - strlcpy(drv_data->adapter.name, MV64XXX_I2C_CTLR_NAME " adapter", + strscpy(drv_data->adapter.name, MV64XXX_I2C_CTLR_NAME " adapter", sizeof(drv_data->adapter.name)); init_waitqueue_head(&drv_data->waitq); @@ -1033,7 +1076,7 @@ exit_disable_pm: return rc; } -static int +static void mv64xxx_i2c_remove(struct platform_device *pd) { struct mv64xxx_i2c_data *drv_data = platform_get_drvdata(pd); @@ -1043,16 +1086,6 @@ mv64xxx_i2c_remove(struct platform_device *pd) pm_runtime_disable(&pd->dev); if (!pm_runtime_status_suspended(&pd->dev)) mv64xxx_i2c_runtime_suspend(&pd->dev); - - return 0; -} - -static void -mv64xxx_i2c_shutdown(struct platform_device *pd) -{ - pm_runtime_disable(&pd->dev); - if (!pm_runtime_status_suspended(&pd->dev)) - mv64xxx_i2c_runtime_suspend(&pd->dev); } static const struct dev_pm_ops mv64xxx_i2c_pm_ops = { @@ -1064,8 +1097,7 @@ static const struct dev_pm_ops mv64xxx_i2c_pm_ops = { static struct platform_driver mv64xxx_i2c_driver = { .probe = mv64xxx_i2c_probe, - .remove = mv64xxx_i2c_remove, - .shutdown = mv64xxx_i2c_shutdown, + .remove = mv64xxx_i2c_remove, .driver = { .name = MV64XXX_I2C_CTLR_NAME, .pm = &mv64xxx_i2c_pm_ops, |
