Implement basic support for atomic write - enough to get a simple
write to PMIC on shutdown. Only for chips having ALT_CMD register,
eg. SAMA5D2.
Signed-off-by: Michał Mirosław <[email protected]>
---
drivers/i2c/busses/i2c-at91-master.c | 73 +++++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 2 deletions(-)
diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c
index ba6fbb9c7390..67091616987f 100644
--- a/drivers/i2c/busses/i2c-at91-master.c
+++ b/drivers/i2c/busses/i2c-at91-master.c
@@ -15,6 +15,7 @@
#include <linux/clk.h>
#include <linux/completion.h>
+#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/err.h>
@@ -709,6 +710,73 @@ static int at91_twi_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num)
return ret;
}
+static int at91_twi_xfer_atomic(struct i2c_adapter *adap, struct i2c_msg *msg, int num)
+{
+ struct at91_twi_dev *dev = i2c_get_adapdata(adap);
+ unsigned long timeout;
+ __u32 stat;
+ int ret;
+
+ /* FIXME: only single write request supported to 7-bit addr */
+ if (num != 1)
+ return -EOPNOTSUPP;
+ if (msg->flags & I2C_M_RD)
+ return -EOPNOTSUPP;
+ if (msg->flags & I2C_M_TEN)
+ return -EOPNOTSUPP;
+ if (msg->len > dev->fifo_size && msg->len > 1)
+ return -EOPNOTSUPP;
+ if (!dev->pdata->has_alt_cmd)
+ return -EOPNOTSUPP;
+
+ ret = pm_runtime_get_sync(dev->dev);
+ if (ret < 0)
+ goto out;
+
+ /* Clear and disable pending interrupts, such as NACK. */
+ at91_twi_read(dev, AT91_TWI_SR);
+ at91_twi_write(dev, AT91_TWI_IDR, ~0);
+
+ at91_twi_write(dev, AT91_TWI_MMR, msg->addr << 16);
+
+ if (!msg->len) {
+ at91_twi_write(dev, AT91_TWI_CR,
+ AT91_TWI_ACMDIS | AT91_TWI_QUICK);
+ } else {
+ size_t n = msg->len;
+ __u8 *p;
+
+ at91_twi_write(dev, AT91_TWI_CR,
+ AT91_TWI_ACMEN |
+ AT91_TWI_THRCLR | AT91_TWI_RHRCLR);
+ at91_twi_write(dev, AT91_TWI_ACR, AT91_TWI_ACR_DATAL(n));
+ for (p = msg->buf; n--; ++p)
+ writeb_relaxed(*p, dev->base + AT91_TWI_THR);
+ }
+
+ timeout = jiffies + (2 + msg->len) * HZ/1000;
+ for (;;) {
+ stat = at91_twi_read(dev, AT91_TWI_SR);
+ if (stat & AT91_TWI_TXCOMP)
+ break;
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ udelay(100);
+ }
+
+ if (stat & AT91_TWI_NACK)
+ ret = -EREMOTEIO;
+ else
+ ret = num;
+out:
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+
+ return ret;
+}
+
/*
* The hardware can handle at most two messages concatenated by a
* repeated start via it's internal address feature.
@@ -725,8 +793,9 @@ static u32 at91_twi_func(struct i2c_adapter *adapter)
}
static const struct i2c_algorithm at91_twi_algorithm = {
- .master_xfer = at91_twi_xfer,
- .functionality = at91_twi_func,
+ .master_xfer = at91_twi_xfer,
+ .master_xfer_atomic = at91_twi_xfer_atomic,
+ .functionality = at91_twi_func,
};
static int at91_twi_configure_dma(struct at91_twi_dev *dev, u32 phy_addr)
--
2.20.1
15.03.2020 21:27, Michał Mirosław пишет:
> Implement basic support for atomic write - enough to get a simple
> write to PMIC on shutdown. Only for chips having ALT_CMD register,
> eg. SAMA5D2.
>
> Signed-off-by: Michał Mirosław <[email protected]>
> ---
Hello Michał,
...
> + ret = pm_runtime_get_sync(dev->dev);
> + if (ret < 0)
> + goto out;
Runtime PM can't be used while interrupts are disabled, unless
pm_runtime_irq_safe() is used and driver's RPM callback is IRQ-safe.
...
> + timeout = jiffies + (2 + msg->len) * HZ/1000;
> + for (;;) {
> + stat = at91_twi_read(dev, AT91_TWI_SR);
> + if (stat & AT91_TWI_TXCOMP)
> + break;
> + if (time_after(jiffies, timeout)) {
> + ret = -ETIMEDOUT;
> + goto out;
> + }
> + udelay(100);
> + }
Jiffies can't be used with the disabled interrupts because jiffies are
updated by timer's interrupt.
Either ktime() API or iterator-based loop should be used.
On Sun, Mar 15, 2020 at 11:46:33PM +0300, Dmitry Osipenko wrote:
> 15.03.2020 21:27, Michał Mirosław пишет:
> > Implement basic support for atomic write - enough to get a simple
> > write to PMIC on shutdown. Only for chips having ALT_CMD register,
> > eg. SAMA5D2.
> >
> > Signed-off-by: Michał Mirosław <[email protected]>
> > ---
>
> Hello Michał,
>
> ...
> > + ret = pm_runtime_get_sync(dev->dev);
> > + if (ret < 0)
> > + goto out;
>
> Runtime PM can't be used while interrupts are disabled, unless
> pm_runtime_irq_safe() is used and driver's RPM callback is IRQ-safe.
I didn't get any warnings from lockdep and friends, but I'll double
check if this is by luck.
> ...
> > + timeout = jiffies + (2 + msg->len) * HZ/1000;
> > + for (;;) {
> > + stat = at91_twi_read(dev, AT91_TWI_SR);
> > + if (stat & AT91_TWI_TXCOMP)
> > + break;
> > + if (time_after(jiffies, timeout)) {
> > + ret = -ETIMEDOUT;
> > + goto out;
> > + }
> > + udelay(100);
> > + }
>
> Jiffies can't be used with the disabled interrupts because jiffies are
> updated by timer's interrupt.
>
> Either ktime() API or iterator-based loop should be used.
Thanks for the pointers. In my use-case power is cut from the CPU at this
point so it didn't matter that the loop was infinite.
Best Regards
Michał Mirosław
Hi Michał,
On Mon, Mar 16, 2020 at 03:42:21PM +0100, Michał Mirosław wrote:
> On Sun, Mar 15, 2020 at 11:46:33PM +0300, Dmitry Osipenko wrote:
> > 15.03.2020 21:27, Michał Mirosław пишет:
> > > Implement basic support for atomic write - enough to get a simple
> > > write to PMIC on shutdown. Only for chips having ALT_CMD register,
> > > eg. SAMA5D2.
> > >
> > > Signed-off-by: Michał Mirosław <[email protected]>
> > > ---
> >
> > Hello Michał,
> >
> > ...
> > > + ret = pm_runtime_get_sync(dev->dev);
> > > + if (ret < 0)
> > > + goto out;
> >
> > Runtime PM can't be used while interrupts are disabled, unless
> > pm_runtime_irq_safe() is used and driver's RPM callback is IRQ-safe.
>
> I didn't get any warnings from lockdep and friends, but I'll double
> check if this is by luck.
You can have a look at the I2C atomic patch for the imx-driver. See
https://patchwork.ozlabs.org/patch/1225802/
In that patch Marco Felsch is using clk_enable() and clk_disable() calls.
>
> > ...
> > > + timeout = jiffies + (2 + msg->len) * HZ/1000;
> > > + for (;;) {
> > > + stat = at91_twi_read(dev, AT91_TWI_SR);
> > > + if (stat & AT91_TWI_TXCOMP)
> > > + break;
> > > + if (time_after(jiffies, timeout)) {
> > > + ret = -ETIMEDOUT;
> > > + goto out;
> > > + }
> > > + udelay(100);
> > > + }
> >
> > Jiffies can't be used with the disabled interrupts because jiffies are
> > updated by timer's interrupt.
> >
> > Either ktime() API or iterator-based loop should be used.
>
> Thanks for the pointers. In my use-case power is cut from the CPU at this
> point so it didn't matter that the loop was infinite.
Here again you can have a look at Marco Felsch's patch. He used the
function readb_poll_timeout_atomic(). So the loop can potentially
replaced by a single line.
Kind regards,
Stefan
On 20-03-16 18:20, Stefan Lengfeld wrote:
> Hi Michał,
>
> On Mon, Mar 16, 2020 at 03:42:21PM +0100, Michał Mirosław wrote:
> > On Sun, Mar 15, 2020 at 11:46:33PM +0300, Dmitry Osipenko wrote:
> > > 15.03.2020 21:27, Michał Mirosław пишет:
> > > > Implement basic support for atomic write - enough to get a simple
> > > > write to PMIC on shutdown. Only for chips having ALT_CMD register,
> > > > eg. SAMA5D2.
> > > >
> > > > Signed-off-by: Michał Mirosław <[email protected]>
> > > > ---
> > >
> > > Hello Michał,
> > >
> > > ...
> > > > + ret = pm_runtime_get_sync(dev->dev);
> > > > + if (ret < 0)
> > > > + goto out;
> > >
> > > Runtime PM can't be used while interrupts are disabled, unless
> > > pm_runtime_irq_safe() is used and driver's RPM callback is IRQ-safe.
> >
> > I didn't get any warnings from lockdep and friends, but I'll double
> > check if this is by luck.
>
> You can have a look at the I2C atomic patch for the imx-driver. See
>
> https://patchwork.ozlabs.org/patch/1225802/
>
> In that patch Marco Felsch is using clk_enable() and clk_disable() calls.
Yep because we need to handle the runtime_pm stuff by our-self. So for
the imx case we need to handle the clk en-/disable stuff. Runtime pm is
using a workqueue which can't be used in that late case.
Regards,
Marco
> > > ...
> > > > + timeout = jiffies + (2 + msg->len) * HZ/1000;
> > > > + for (;;) {
> > > > + stat = at91_twi_read(dev, AT91_TWI_SR);
> > > > + if (stat & AT91_TWI_TXCOMP)
> > > > + break;
> > > > + if (time_after(jiffies, timeout)) {
> > > > + ret = -ETIMEDOUT;
> > > > + goto out;
> > > > + }
> > > > + udelay(100);
> > > > + }
> > >
> > > Jiffies can't be used with the disabled interrupts because jiffies are
> > > updated by timer's interrupt.
> > >
> > > Either ktime() API or iterator-based loop should be used.
> >
> > Thanks for the pointers. In my use-case power is cut from the CPU at this
> > point so it didn't matter that the loop was infinite.
>
> Here again you can have a look at Marco Felsch's patch. He used the
> function readb_poll_timeout_atomic(). So the loop can potentially
> replaced by a single line.
>
> Kind regards,
> Stefan
>
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |