To enter stop mode, the CPU should manually assert a global Stop Mode request and check the acknowledgment asserted by FlexCAN. The CPU must only consider the FlexCAN in stop mode when both request and acknowledgment conditions are satisfied. Fixes: de3578c198c6 ("can: flexcan: add self wakeup support") Reported-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> Signed-off-by: Joakim Zhang <qiangqing.zhang@xxxxxxx> --- drivers/net/can/flexcan.c | 47 ++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index e35083ff31ee..282dac1d8f5c 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -404,9 +404,11 @@ static void flexcan_enable_wakeup_irq(struct flexcan_priv *priv, bool enable) priv->write(reg_mcr, ®s->mcr); } -static inline void flexcan_enter_stop_mode(struct flexcan_priv *priv) +static inline int flexcan_enter_stop_mode(struct flexcan_priv *priv) { struct flexcan_regs __iomem *regs = priv->regs; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; + unsigned int ackval; u32 reg_mcr; reg_mcr = priv->read(®s->mcr); @@ -416,20 +418,48 @@ static inline void flexcan_enter_stop_mode(struct flexcan_priv *priv) /* enable stop request */ regmap_update_bits(priv->stm.gpr, priv->stm.req_gpr, 1 << priv->stm.req_bit, 1 << priv->stm.req_bit); + + /* get stop acknowledgment */ + regmap_read(priv->stm.gpr, priv->stm.ack_gpr, &ackval); + + while (timeout-- && !(ackval & (1 << priv->stm.ack_bit))) { + udelay(10); + regmap_read(priv->stm.gpr, priv->stm.ack_gpr, &ackval); + } + + if (!(ackval & (1 << priv->stm.ack_bit))) + return -ETIMEDOUT; + + return 0; } -static inline void flexcan_exit_stop_mode(struct flexcan_priv *priv) +static inline int flexcan_exit_stop_mode(struct flexcan_priv *priv) { struct flexcan_regs __iomem *regs = priv->regs; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; + unsigned int ackval; u32 reg_mcr; + reg_mcr = priv->read(®s->mcr); + reg_mcr &= ~FLEXCAN_MCR_SLF_WAK; + priv->write(reg_mcr, ®s->mcr); + /* remove stop request */ regmap_update_bits(priv->stm.gpr, priv->stm.req_gpr, 1 << priv->stm.req_bit, 0); - reg_mcr = priv->read(®s->mcr); - reg_mcr &= ~FLEXCAN_MCR_SLF_WAK; - priv->write(reg_mcr, ®s->mcr); + /* get stop acknowledgment */ + regmap_read(priv->stm.gpr, priv->stm.ack_gpr, &ackval); + + while (timeout-- && (ackval & (1 << priv->stm.ack_bit))) { + udelay(10); + regmap_read(priv->stm.gpr, priv->stm.ack_gpr, &ackval); + } + + if (ackval & (1 << priv->stm.ack_bit)) + return -ETIMEDOUT; + + return 0; } static inline void flexcan_error_irq_enable(const struct flexcan_priv *priv) @@ -1652,7 +1682,7 @@ static int __maybe_unused flexcan_suspend(struct device *device) */ if (device_may_wakeup(device)) { enable_irq_wake(dev->irq); - flexcan_enter_stop_mode(priv); + err = flexcan_enter_stop_mode(priv); } else { err = flexcan_chip_disable(priv); if (err) @@ -1725,13 +1755,14 @@ static int __maybe_unused flexcan_noirq_resume(struct device *device) { struct net_device *dev = dev_get_drvdata(device); struct flexcan_priv *priv = netdev_priv(dev); + int err = 0; if (netif_running(dev) && device_may_wakeup(device)) { flexcan_enable_wakeup_irq(priv, false); - flexcan_exit_stop_mode(priv); + err = flexcan_exit_stop_mode(priv); } - return 0; + return err; } static const struct dev_pm_ops flexcan_pm_ops = { -- 2.17.1