Message ID | 20230926234801.4078042-4-chris.packham@alliedtelesis.co.nz |
---|---|
State | Superseded |
Headers | show |
Series | i2c: mv64xxx: Support for I2C unstuck | expand |
Hi Chris, Looks good, just a few questions. > +static int > +mv64xxx_i2c_recover_bus(struct i2c_adapter *adap) > +{ > + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); > + int ret; > + u32 val; > + > + dev_dbg(&adap->dev, "Trying i2c bus recovery\n"); > + writel(MV64XXX_I2C_UNSTUCK_TRIGGER, drv_data->unstuck_reg); > + ret = readl_poll_timeout_atomic(drv_data->unstuck_reg, val, > + !(val & MV64XXX_I2C_UNSTUCK_INPROGRESS), > + 1000, 5000); here you are busy looping for 1ms between reads which is a long time. Why not using read_poll_timeout() instead? > + if (ret) { > + dev_err(&adap->dev, "recovery timeout\n"); > + return ret; > + } > + > + if (val & MV64XXX_I2C_UNSTUCK_ERROR) { > + dev_err(&adap->dev, "recovery failed\n"); > + return -EBUSY; > + } > + > + dev_info(&adap->dev, "recovery complete after %d pulses\n", MV64XXX_I2C_UNSTUCK_COUNT(val)); dev_dbg? > + return 0; > +} > + [...] > - if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c")) { > + if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c") || > + of_device_is_compatible(np, "marvell,armada-8k-i2c")) { should this be part of a different patch? > drv_data->offload_enabled = false; > /* The delay is only needed in standard mode (100kHz) */ > if (bus_freq <= I2C_MAX_STANDARD_MODE_FREQ) > @@ -936,8 +973,21 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, > } > #endif /* CONFIG_OF */ > > -static int mv64xxx_i2c_init_recovery_info(struct mv64xxx_i2c_data *drv_data, > - struct device *dev) > +static int mv64xxx_i2c_init_fsm_recovery_info(struct mv64xxx_i2c_data *drv_data, > + struct device *dev) > +{ > + struct i2c_bus_recovery_info *rinfo = &drv_data->rinfo; > + > + dev_info(dev, "using FSM for recovery\n"); dev_dbg? > + rinfo->recover_bus = mv64xxx_i2c_recover_bus; > + drv_data->adapter.bus_recovery_info = rinfo; > + > + return 0; > + > +} > + [...] > + /* optional unstuck support */ > + res = platform_get_resource(pd, IORESOURCE_MEM, 1); > + if (res) { > + drv_data->unstuck_reg = devm_ioremap_resource(&pd->dev, res); > + if (IS_ERR(drv_data->unstuck_reg)) > + return PTR_ERR(drv_data->unstuck_reg); OK, we failed to ioremap... but instead of returning an error, wouldn't it be better to just set unstuck_reg to NULL and move forward without unstuck support? Maybe you will stil crash later because something might have happened, but failing on purpose on an optional feature looks a bit too drastic to me. What do you think? Thanks, Andi
On 6/10/23 10:58, Andi Shyti wrote: > Hi Chris, > > Looks good, just a few questions. > >> +static int >> +mv64xxx_i2c_recover_bus(struct i2c_adapter *adap) >> +{ >> + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); >> + int ret; >> + u32 val; >> + >> + dev_dbg(&adap->dev, "Trying i2c bus recovery\n"); >> + writel(MV64XXX_I2C_UNSTUCK_TRIGGER, drv_data->unstuck_reg); >> + ret = readl_poll_timeout_atomic(drv_data->unstuck_reg, val, >> + !(val & MV64XXX_I2C_UNSTUCK_INPROGRESS), >> + 1000, 5000); > here you are busy looping for 1ms between reads which is a long > time. Why not using read_poll_timeout() instead? I needed to use the atomic variant because this ends up getting called from an interrupt handler (mv64xxx_i2c_intr() -> mv64xxx_i2c_fsm()). I probably don't need to wait so long between reads those times were just pulled out of thin air. In my experimentation the faults that can be cleared do so within a couple of clocks, if it hasn't cleared within 8 clocks it's not going to. >> + if (ret) { >> + dev_err(&adap->dev, "recovery timeout\n"); >> + return ret; >> + } >> + >> + if (val & MV64XXX_I2C_UNSTUCK_ERROR) { >> + dev_err(&adap->dev, "recovery failed\n"); >> + return -EBUSY; >> + } >> + >> + dev_info(&adap->dev, "recovery complete after %d pulses\n", MV64XXX_I2C_UNSTUCK_COUNT(val)); > dev_dbg? ack. >> + return 0; >> +} >> + > [...] > >> - if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c")) { >> + if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c") || >> + of_device_is_compatible(np, "marvell,armada-8k-i2c")) { > should this be part of a different patch? Yes sorry. Originally I was going to use a new compatible to indicate the unstuck support but went with the 2nd reg cell so this is unnecessary. > >> drv_data->offload_enabled = false; >> /* The delay is only needed in standard mode (100kHz) */ >> if (bus_freq <= I2C_MAX_STANDARD_MODE_FREQ) >> @@ -936,8 +973,21 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, >> } >> #endif /* CONFIG_OF */ >> >> -static int mv64xxx_i2c_init_recovery_info(struct mv64xxx_i2c_data *drv_data, >> - struct device *dev) >> +static int mv64xxx_i2c_init_fsm_recovery_info(struct mv64xxx_i2c_data *drv_data, >> + struct device *dev) >> +{ >> + struct i2c_bus_recovery_info *rinfo = &drv_data->rinfo; >> + >> + dev_info(dev, "using FSM for recovery\n"); > dev_dbg? > >> + rinfo->recover_bus = mv64xxx_i2c_recover_bus; >> + drv_data->adapter.bus_recovery_info = rinfo; >> + >> + return 0; >> + >> +} >> + > [...] > >> + /* optional unstuck support */ >> + res = platform_get_resource(pd, IORESOURCE_MEM, 1); >> + if (res) { >> + drv_data->unstuck_reg = devm_ioremap_resource(&pd->dev, res); >> + if (IS_ERR(drv_data->unstuck_reg)) >> + return PTR_ERR(drv_data->unstuck_reg); > OK, we failed to ioremap... but instead of returning an error, > wouldn't it be better to just set unstuck_reg to NULL and move > forward without unstuck support? > > Maybe you will stil crash later because something might have > happened, but failing on purpose on an optional feature looks a > bit too drastic to me. What do you think? Personally I think if the reg property is supplied in the dts we'd better be able to use it. If the feature is not wanted then the way to indicate this is by supplying only one reg cell. I'd be happy with a dev_warn() and unstuck_reg = NULL if that helps get this landed. > > Thanks, > Andi
Hi Chris, > >> +static int > >> +mv64xxx_i2c_recover_bus(struct i2c_adapter *adap) > >> +{ > >> + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); > >> + int ret; > >> + u32 val; > >> + > >> + dev_dbg(&adap->dev, "Trying i2c bus recovery\n"); > >> + writel(MV64XXX_I2C_UNSTUCK_TRIGGER, drv_data->unstuck_reg); > >> + ret = readl_poll_timeout_atomic(drv_data->unstuck_reg, val, > >> + !(val & MV64XXX_I2C_UNSTUCK_INPROGRESS), > >> + 1000, 5000); > > here you are busy looping for 1ms between reads which is a long > > time. Why not using read_poll_timeout() instead? > > I needed to use the atomic variant because this ends up getting called > from an interrupt handler (mv64xxx_i2c_intr() -> mv64xxx_i2c_fsm()). I > probably don't need to wait so long between reads those times were just > pulled out of thin air. In my experimentation the faults that can be > cleared do so within a couple of clocks, if it hasn't cleared within 8 > clocks it's not going to. It's still a long time to wait in atomic context... readl_poll_timeout_atomic() waits in udelays, where the maximum accepted waiting time is 10us. Here you are waiting 100 times more. If we can't be within that value I would rather use a thread. Or, you could also consider using threaded_irq()... but this might have a bit of a higher impact. [...] > >> + /* optional unstuck support */ > >> + res = platform_get_resource(pd, IORESOURCE_MEM, 1); > >> + if (res) { > >> + drv_data->unstuck_reg = devm_ioremap_resource(&pd->dev, res); > >> + if (IS_ERR(drv_data->unstuck_reg)) > >> + return PTR_ERR(drv_data->unstuck_reg); > > OK, we failed to ioremap... but instead of returning an error, > > wouldn't it be better to just set unstuck_reg to NULL and move > > forward without unstuck support? > > > > Maybe you will stil crash later because something might have > > happened, but failing on purpose on an optional feature looks a > > bit too drastic to me. What do you think? > > Personally I think if the reg property is supplied in the dts we'd > better be able to use it. If the feature is not wanted then the way to > indicate this is by supplying only one reg cell. > > I'd be happy with a dev_warn() and unstuck_reg = NULL if that helps get > this landed. Don't ahve a strong opinion... as you like. Mine is just an opinion and your argument is valid :-) Andi
diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c index fd8403b07fa6..4345ab19b89c 100644 --- a/drivers/i2c/busses/i2c-mv64xxx.c +++ b/drivers/i2c/busses/i2c-mv64xxx.c @@ -21,6 +21,7 @@ #include <linux/pm_runtime.h> #include <linux/reset.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_irq.h> @@ -82,6 +83,13 @@ /* Bridge Status values */ #define MV64XXX_I2C_BRIDGE_STATUS_ERROR BIT(0) +/* Unstuck Register values */ +#define MV64XXX_I2C_UNSTUCK_TRIGGER BIT(0) +#define MV64XXX_I2C_UNSTUCK_ON_GOING BIT(1) +#define MV64XXX_I2C_UNSTUCK_ERROR BIT(2) +#define MV64XXX_I2C_UNSTUCK_COUNT(val) ((val & 0xf0) >> 4) +#define MV64XXX_I2C_UNSTUCK_INPROGRESS (MV64XXX_I2C_UNSTUCK_TRIGGER|MV64XXX_I2C_UNSTUCK_ON_GOING) + /* Driver states */ enum { MV64XXX_I2C_STATE_INVALID, @@ -126,6 +134,7 @@ struct mv64xxx_i2c_data { u32 aborting; u32 cntl_bits; void __iomem *reg_base; + void __iomem *unstuck_reg; struct mv64xxx_i2c_regs reg_offsets; u32 addr1; u32 addr2; @@ -735,6 +744,33 @@ mv64xxx_i2c_can_offload(struct mv64xxx_i2c_data *drv_data) return false; } +static int +mv64xxx_i2c_recover_bus(struct i2c_adapter *adap) +{ + struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); + int ret; + u32 val; + + dev_dbg(&adap->dev, "Trying i2c bus recovery\n"); + writel(MV64XXX_I2C_UNSTUCK_TRIGGER, drv_data->unstuck_reg); + ret = readl_poll_timeout_atomic(drv_data->unstuck_reg, val, + !(val & MV64XXX_I2C_UNSTUCK_INPROGRESS), + 1000, 5000); + if (ret) { + dev_err(&adap->dev, "recovery timeout\n"); + return ret; + } + + if (val & MV64XXX_I2C_UNSTUCK_ERROR) { + dev_err(&adap->dev, "recovery failed\n"); + return -EBUSY; + } + + dev_info(&adap->dev, "recovery complete after %d pulses\n", MV64XXX_I2C_UNSTUCK_COUNT(val)); + + return 0; +} + /* ***************************************************************************** * @@ -914,7 +950,8 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, drv_data->errata_delay = true; } - if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c")) { + if (of_device_is_compatible(np, "marvell,mv78230-a0-i2c") || + of_device_is_compatible(np, "marvell,armada-8k-i2c")) { drv_data->offload_enabled = false; /* The delay is only needed in standard mode (100kHz) */ if (bus_freq <= I2C_MAX_STANDARD_MODE_FREQ) @@ -936,8 +973,21 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, } #endif /* CONFIG_OF */ -static int mv64xxx_i2c_init_recovery_info(struct mv64xxx_i2c_data *drv_data, - struct device *dev) +static int mv64xxx_i2c_init_fsm_recovery_info(struct mv64xxx_i2c_data *drv_data, + struct device *dev) +{ + struct i2c_bus_recovery_info *rinfo = &drv_data->rinfo; + + dev_info(dev, "using FSM for recovery\n"); + rinfo->recover_bus = mv64xxx_i2c_recover_bus; + drv_data->adapter.bus_recovery_info = rinfo; + + return 0; + +} + +static int mv64xxx_i2c_init_gpio_recovery_info(struct mv64xxx_i2c_data *drv_data, + struct device *dev) { struct i2c_bus_recovery_info *rinfo = &drv_data->rinfo; @@ -986,6 +1036,7 @@ mv64xxx_i2c_probe(struct platform_device *pd) { struct mv64xxx_i2c_data *drv_data; struct mv64xxx_i2c_pdata *pdata = dev_get_platdata(&pd->dev); + struct resource *res; int rc; if ((!pdata && !pd->dev.of_node)) @@ -1000,6 +1051,14 @@ mv64xxx_i2c_probe(struct platform_device *pd) if (IS_ERR(drv_data->reg_base)) return PTR_ERR(drv_data->reg_base); + /* optional unstuck support */ + res = platform_get_resource(pd, IORESOURCE_MEM, 1); + if (res) { + drv_data->unstuck_reg = devm_ioremap_resource(&pd->dev, res); + if (IS_ERR(drv_data->unstuck_reg)) + return PTR_ERR(drv_data->unstuck_reg); + } + strscpy(drv_data->adapter.name, MV64XXX_I2C_CTLR_NAME " adapter", sizeof(drv_data->adapter.name)); @@ -1037,7 +1096,11 @@ mv64xxx_i2c_probe(struct platform_device *pd) return rc; } - rc = mv64xxx_i2c_init_recovery_info(drv_data, &pd->dev); + if (drv_data->unstuck_reg) + rc = mv64xxx_i2c_init_fsm_recovery_info(drv_data, &pd->dev); + else + rc = mv64xxx_i2c_init_gpio_recovery_info(drv_data, &pd->dev); + if (rc == -EPROBE_DEFER) return rc;
Some newer Marvell SoCs (AC5 and CN9130, possibly more) support a I2C unstuck function. This provides a recovery function as part of the FSM as an alternative to changing pinctrl modes and using the generic GPIO based recovery. Allow for using this by adding an optional resource to the platform data which contains the address of the I2C unstuck register for the I2C controller. Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz> --- drivers/i2c/busses/i2c-mv64xxx.c | 71 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-)