From 5be93bdda64e85450598c6e97f79fb8f6acf30e0 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Wed, 19 Feb 2014 12:00:51 +0100 Subject: can: flexcan: fix shutdown: first disable chip, then all interrupts When shutting down the CAN interface (ifconfig canX down) during high CAN bus loads, the CAN core might hang and freeze the whole CPU. This patch fixes the shutdown sequence by first disabling the CAN core then disabling all interrupts. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 320bef2dba42..dcd69c9374ae 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -827,14 +827,16 @@ static void flexcan_chip_stop(struct net_device *dev) struct flexcan_regs __iomem *regs = priv->base; u32 reg; - /* Disable all interrupts */ - flexcan_write(0, ®s->imask1); - /* Disable + halt module */ reg = flexcan_read(®s->mcr); reg |= FLEXCAN_MCR_MDIS | FLEXCAN_MCR_HALT; flexcan_write(reg, ®s->mcr); + /* Disable all interrupts */ + flexcan_write(0, ®s->imask1); + flexcan_write(priv->reg_ctrl_default & ~FLEXCAN_CTRL_ERR_ALL, + ®s->ctrl); + if (priv->reg_xceiver) regulator_disable(priv->reg_xceiver); priv->can.state = CAN_STATE_STOPPED; -- cgit v1.2.3-59-g8ed1b From 7e9e148af01ef388efb6e2490805970be4622792 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 14:52:01 +0100 Subject: can: flexcan: flexcan_open(): fix error path if flexcan_chip_start() fails If flexcan_chip_start() in flexcan_open() fails, the interrupt is not freed, this patch adds the missing cleanup. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index dcd69c9374ae..30af702a07ad 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -868,7 +868,7 @@ static int flexcan_open(struct net_device *dev) /* start chip and queuing */ err = flexcan_chip_start(dev); if (err) - goto out_close; + goto out_free_irq; can_led_event(dev, CAN_LED_EVENT_OPEN); @@ -877,6 +877,8 @@ static int flexcan_open(struct net_device *dev) return 0; + out_free_irq: + free_irq(dev->irq, dev); out_close: close_candev(dev); out_disable_per: -- cgit v1.2.3-59-g8ed1b From 9b00b300e7bce032c467c36ca47fe2a776887fc2 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 15:30:18 +0100 Subject: can: flexcan: fix transition from and to low power mode in chip_{en,dis}able In flexcan_chip_enable() and flexcan_chip_disable() fixed delays are used. Experiments have shown that the transition from and to low power mode may take several microseconds. This patch adds a while loop which polls the Low Power Mode ACK bit (LPM_ACK) that indicates a successfull mode change. If the function runs into a timeout a error value is returned. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 50 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 30af702a07ad..5af60ab23e6f 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -144,6 +144,8 @@ #define FLEXCAN_MB_CODE_MASK (0xf0ffffff) +#define FLEXCAN_TIMEOUT_US (50) + /* * FLEXCAN hardware feature flags * @@ -269,26 +271,42 @@ static inline int flexcan_has_and_handle_berr(const struct flexcan_priv *priv, (reg_esr & FLEXCAN_ESR_ERR_BUS); } -static inline void flexcan_chip_enable(struct flexcan_priv *priv) +static int flexcan_chip_enable(struct flexcan_priv *priv) { struct flexcan_regs __iomem *regs = priv->base; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; u32 reg; reg = flexcan_read(®s->mcr); reg &= ~FLEXCAN_MCR_MDIS; flexcan_write(reg, ®s->mcr); - udelay(10); + while (timeout-- && (flexcan_read(®s->mcr) & FLEXCAN_MCR_LPM_ACK)) + usleep_range(10, 20); + + if (flexcan_read(®s->mcr) & FLEXCAN_MCR_LPM_ACK) + return -ETIMEDOUT; + + return 0; } -static inline void flexcan_chip_disable(struct flexcan_priv *priv) +static int flexcan_chip_disable(struct flexcan_priv *priv) { struct flexcan_regs __iomem *regs = priv->base; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; u32 reg; reg = flexcan_read(®s->mcr); reg |= FLEXCAN_MCR_MDIS; flexcan_write(reg, ®s->mcr); + + while (timeout-- && !(flexcan_read(®s->mcr) & FLEXCAN_MCR_LPM_ACK)) + usleep_range(10, 20); + + if (!(flexcan_read(®s->mcr) & FLEXCAN_MCR_LPM_ACK)) + return -ETIMEDOUT; + + return 0; } static int flexcan_get_berr_counter(const struct net_device *dev, @@ -709,7 +727,9 @@ static int flexcan_chip_start(struct net_device *dev) u32 reg_mcr, reg_ctrl; /* enable module */ - flexcan_chip_enable(priv); + err = flexcan_chip_enable(priv); + if (err) + return err; /* soft reset */ flexcan_write(FLEXCAN_MCR_SOFTRST, ®s->mcr); @@ -949,12 +969,16 @@ static int register_flexcandev(struct net_device *dev) goto out_disable_ipg; /* select "bus clock", chip must be disabled */ - flexcan_chip_disable(priv); + err = flexcan_chip_disable(priv); + if (err) + goto out_disable_per; reg = flexcan_read(®s->ctrl); reg |= FLEXCAN_CTRL_CLK_SRC; flexcan_write(reg, ®s->ctrl); - flexcan_chip_enable(priv); + err = flexcan_chip_enable(priv); + if (err) + goto out_chip_disable; /* set freeze, halt and activate FIFO, restrict register access */ reg = flexcan_read(®s->mcr); @@ -971,14 +995,15 @@ static int register_flexcandev(struct net_device *dev) if (!(reg & FLEXCAN_MCR_FEN)) { netdev_err(dev, "Could not enable RX FIFO, unsupported core\n"); err = -ENODEV; - goto out_disable_per; + goto out_chip_disable; } err = register_candev(dev); - out_disable_per: /* disable core and turn off clocks */ + out_chip_disable: flexcan_chip_disable(priv); + out_disable_per: clk_disable_unprepare(priv->clk_per); out_disable_ipg: clk_disable_unprepare(priv->clk_ipg); @@ -1121,8 +1146,11 @@ static int flexcan_suspend(struct device *device) { struct net_device *dev = dev_get_drvdata(device); struct flexcan_priv *priv = netdev_priv(dev); + int err; - flexcan_chip_disable(priv); + err = flexcan_chip_disable(priv); + if (err) + return err; if (netif_running(dev)) { netif_stop_queue(dev); @@ -1143,9 +1171,7 @@ static int flexcan_resume(struct device *device) netif_device_attach(dev); netif_start_queue(dev); } - flexcan_chip_enable(priv); - - return 0; + return flexcan_chip_enable(priv); } #endif /* CONFIG_PM_SLEEP */ -- cgit v1.2.3-59-g8ed1b From f003698e23f6f56a791774f14d0ac35d04872490 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 17:18:27 +0100 Subject: can: flexcan: factor out transceiver {en,dis}able into seperate functions This patch moves the transceiver enable and disable into seperate functions, where the NULL pointer check is hidden. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 5af60ab23e6f..6b0fecd0a6ad 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -264,6 +264,22 @@ static inline void flexcan_write(u32 val, void __iomem *addr) } #endif +static inline int flexcan_transceiver_enable(const struct flexcan_priv *priv) +{ + if (!priv->reg_xceiver) + return 0; + + return regulator_enable(priv->reg_xceiver); +} + +static inline int flexcan_transceiver_disable(const struct flexcan_priv *priv) +{ + if (!priv->reg_xceiver) + return 0; + + return regulator_disable(priv->reg_xceiver); +} + static inline int flexcan_has_and_handle_berr(const struct flexcan_priv *priv, u32 reg_esr) { @@ -808,11 +824,9 @@ static int flexcan_chip_start(struct net_device *dev) if (priv->devtype_data->features & FLEXCAN_HAS_V10_FEATURES) flexcan_write(0x0, ®s->rxfgmask); - if (priv->reg_xceiver) { - err = regulator_enable(priv->reg_xceiver); - if (err) - goto out; - } + err = flexcan_transceiver_enable(priv); + if (err) + goto out; /* synchronize with the can bus */ reg_mcr = flexcan_read(®s->mcr); @@ -857,8 +871,7 @@ static void flexcan_chip_stop(struct net_device *dev) flexcan_write(priv->reg_ctrl_default & ~FLEXCAN_CTRL_ERR_ALL, ®s->ctrl); - if (priv->reg_xceiver) - regulator_disable(priv->reg_xceiver); + flexcan_transceiver_disable(priv); priv->can.state = CAN_STATE_STOPPED; return; -- cgit v1.2.3-59-g8ed1b From b1aa1c7a2165b44ecce66286a3095cc6c7667d1c Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 17:08:21 +0100 Subject: can: flexcan: fix transition from and to freeze mode in chip_{,un}freeze This patch factors out freeze and unfreeze of the CAN core into seperate functions. Experiments have shown that the transition from and to freeze mode may take several microseconds, especially the time entering the freeze mode depends on the current bitrate. This patch adds a while loop which polls the Freeze Mode ACK bit (FRZ_ACK) that indicates a successfull mode change. If the function runs into a timeout a error value is returned. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 60 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 11 deletions(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 6b0fecd0a6ad..330b5b957a39 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -325,6 +325,44 @@ static int flexcan_chip_disable(struct flexcan_priv *priv) return 0; } +static int flexcan_chip_freeze(struct flexcan_priv *priv) +{ + struct flexcan_regs __iomem *regs = priv->base; + unsigned int timeout = 1000 * 1000 * 10 / priv->can.bittiming.bitrate; + u32 reg; + + reg = flexcan_read(®s->mcr); + reg |= FLEXCAN_MCR_HALT; + flexcan_write(reg, ®s->mcr); + + while (timeout-- && !(flexcan_read(®s->mcr) & FLEXCAN_MCR_FRZ_ACK)) + usleep_range(100, 200); + + if (!(flexcan_read(®s->mcr) & FLEXCAN_MCR_FRZ_ACK)) + return -ETIMEDOUT; + + return 0; +} + +static int flexcan_chip_unfreeze(struct flexcan_priv *priv) +{ + struct flexcan_regs __iomem *regs = priv->base; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; + u32 reg; + + reg = flexcan_read(®s->mcr); + reg &= ~FLEXCAN_MCR_HALT; + flexcan_write(reg, ®s->mcr); + + while (timeout-- && (flexcan_read(®s->mcr) & FLEXCAN_MCR_FRZ_ACK)) + usleep_range(10, 20); + + if (flexcan_read(®s->mcr) & FLEXCAN_MCR_FRZ_ACK) + return -ETIMEDOUT; + + return 0; +} + static int flexcan_get_berr_counter(const struct net_device *dev, struct can_berr_counter *bec) { @@ -756,7 +794,7 @@ static int flexcan_chip_start(struct net_device *dev) netdev_err(dev, "Failed to softreset can module (mcr=0x%08x)\n", reg_mcr); err = -ENODEV; - goto out; + goto out_chip_disable; } flexcan_set_bittiming(dev); @@ -826,12 +864,12 @@ static int flexcan_chip_start(struct net_device *dev) err = flexcan_transceiver_enable(priv); if (err) - goto out; + goto out_chip_disable; /* synchronize with the can bus */ - reg_mcr = flexcan_read(®s->mcr); - reg_mcr &= ~FLEXCAN_MCR_HALT; - flexcan_write(reg_mcr, ®s->mcr); + err = flexcan_chip_unfreeze(priv); + if (err) + goto out_transceiver_disable; priv->can.state = CAN_STATE_ERROR_ACTIVE; @@ -844,7 +882,9 @@ static int flexcan_chip_start(struct net_device *dev) return 0; - out: + out_transceiver_disable: + flexcan_transceiver_disable(priv); + out_chip_disable: flexcan_chip_disable(priv); return err; } @@ -859,12 +899,10 @@ static void flexcan_chip_stop(struct net_device *dev) { struct flexcan_priv *priv = netdev_priv(dev); struct flexcan_regs __iomem *regs = priv->base; - u32 reg; - /* Disable + halt module */ - reg = flexcan_read(®s->mcr); - reg |= FLEXCAN_MCR_MDIS | FLEXCAN_MCR_HALT; - flexcan_write(reg, ®s->mcr); + /* freeze + disable module */ + flexcan_chip_freeze(priv); + flexcan_chip_disable(priv); /* Disable all interrupts */ flexcan_write(0, ®s->imask1); -- cgit v1.2.3-59-g8ed1b From d96e43e8fce28cf97df576a07af9d65657a41a6f Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 20:48:36 +0100 Subject: can: flexcan: flexcan_remove(): add missing netif_napi_del() This patch adds the missing netif_napi_del() to the flexcan_remove() function. Cc: linux-stable Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 330b5b957a39..dff4fa472c3a 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -1184,9 +1184,10 @@ static int flexcan_probe(struct platform_device *pdev) static int flexcan_remove(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); + struct flexcan_priv *priv = netdev_priv(dev); unregister_flexcandev(dev); - + netif_napi_del(&priv->napi); free_candev(dev); return 0; -- cgit v1.2.3-59-g8ed1b From 4b5b82274a17f0ebbf02378df2ba7f36a3f5af19 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 28 Feb 2014 15:16:59 +0100 Subject: can: flexcan: factor out soft reset into seperate funtion This patch moves the soft reset into a seperate function. Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers/net/can') diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index dff4fa472c3a..61376abdab39 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -363,6 +363,21 @@ static int flexcan_chip_unfreeze(struct flexcan_priv *priv) return 0; } +static int flexcan_chip_softreset(struct flexcan_priv *priv) +{ + struct flexcan_regs __iomem *regs = priv->base; + unsigned int timeout = FLEXCAN_TIMEOUT_US / 10; + + flexcan_write(FLEXCAN_MCR_SOFTRST, ®s->mcr); + while (timeout-- && (flexcan_read(®s->mcr) & FLEXCAN_MCR_SOFTRST)) + usleep_range(10, 20); + + if (flexcan_read(®s->mcr) & FLEXCAN_MCR_SOFTRST) + return -ETIMEDOUT; + + return 0; +} + static int flexcan_get_berr_counter(const struct net_device *dev, struct can_berr_counter *bec) { @@ -786,16 +801,9 @@ static int flexcan_chip_start(struct net_device *dev) return err; /* soft reset */ - flexcan_write(FLEXCAN_MCR_SOFTRST, ®s->mcr); - udelay(10); - - reg_mcr = flexcan_read(®s->mcr); - if (reg_mcr & FLEXCAN_MCR_SOFTRST) { - netdev_err(dev, "Failed to softreset can module (mcr=0x%08x)\n", - reg_mcr); - err = -ENODEV; + err = flexcan_chip_softreset(priv); + if (err) goto out_chip_disable; - } flexcan_set_bittiming(dev); -- cgit v1.2.3-59-g8ed1b