diff options
author | David S. Miller <davem@davemloft.net> | 2014-03-03 16:05:04 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-03-03 16:05:04 -0500 |
commit | 82f1918351beb1b3d2ea7fa0bc842af04dc7e024 (patch) | |
tree | 0344d71ff90b168851a26c015a4a24cdb48c64fb /drivers | |
parent | 10ddceb22bab11dab10ba645c7df2e4a8e7a5db5 (diff) | |
parent | 821047c4055cca833c4674f172a9d73003563eb6 (diff) | |
download | linux-82f1918351beb1b3d2ea7fa0bc842af04dc7e024.tar.bz2 |
Merge tag 'linux-can-fixes-for-3.14-20140303' of git://gitorious.org/linux-can/linux-can
linux-can-fixes-for-3.14-20140303
Marc Kleine-Budde says:
====================
this is a pull request of 8 patches. Oliver Hartkopp contributes a patch which
removes the CAN FD compatibility for CAN 2.0 sockets, as it turns out that this
compatibility has some conceptual cornercases. The remaining 7 patches are by
me, they address a problem in the flexcan driver. When shutting down the
interface ("ifconfig can0 down") under heavy network load the whole system will
hang. This series reworks the actual sequence in close() and the transition
from and to the low power modes of the CAN controller.
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/can/flexcan.c | 172 |
1 files changed, 131 insertions, 41 deletions
diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 320bef2dba42..61376abdab39 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 * @@ -262,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) { @@ -269,26 +287,95 @@ 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_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_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, @@ -709,19 +796,14 @@ 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); - 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; - goto out; - } + err = flexcan_chip_softreset(priv); + if (err) + goto out_chip_disable; flexcan_set_bittiming(dev); @@ -788,16 +870,14 @@ 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_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; @@ -810,7 +890,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; } @@ -825,18 +907,17 @@ 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; + + /* freeze + disable module */ + flexcan_chip_freeze(priv); + flexcan_chip_disable(priv); /* Disable all interrupts */ flexcan_write(0, ®s->imask1); + flexcan_write(priv->reg_ctrl_default & ~FLEXCAN_CTRL_ERR_ALL, + ®s->ctrl); - /* Disable + halt module */ - reg = flexcan_read(®s->mcr); - reg |= FLEXCAN_MCR_MDIS | FLEXCAN_MCR_HALT; - flexcan_write(reg, ®s->mcr); - - if (priv->reg_xceiver) - regulator_disable(priv->reg_xceiver); + flexcan_transceiver_disable(priv); priv->can.state = CAN_STATE_STOPPED; return; @@ -866,7 +947,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); @@ -875,6 +956,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: @@ -945,12 +1028,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); @@ -967,14 +1054,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); @@ -1104,9 +1192,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; @@ -1117,8 +1206,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); @@ -1139,9 +1231,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 */ |