Skip to content

Commit

Permalink
can: flexcan: implement can Runtime PM
Browse files Browse the repository at this point in the history
Flexcan will be disabled during suspend if no wakeup function required and
enabled after resume accordingly. During this period, we could explicitly
disable clocks.
Since PM is optional, the clock is enabled at probe to guarante the
clock is running when PM is not enabled in the kernel.

Implement Runtime PM which will:
1) Without CONFIG_PM, clock is running whether Flexcan up or down.
2) With CONFIG_PM, clock enabled while Flexcan up and disabled when
   Flexcan down.
3) Disable clock when do system suspend and enable clock while system
   resume.
4) Make Power Domain framework be able to shutdown the corresponding
   power domain of this device.

Signed-off-by: Aisheng Dong <aisheng.dong@nxp.com>
Signed-off-by: Joakim Zhang <qiangqing.zhang@nxp.com>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
  • Loading branch information
Aisheng Dong authored and Marc Kleine-Budde committed Jul 24, 2019
1 parent 26bca9f commit ca10989
Showing 1 changed file with 81 additions and 38 deletions.
119 changes: 81 additions & 38 deletions drivers/net/can/flexcan.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>

Expand Down Expand Up @@ -266,6 +267,7 @@ struct flexcan_stop_mode {
struct flexcan_priv {
struct can_priv can;
struct can_rx_offload offload;
struct device *dev;

struct flexcan_regs __iomem *regs;
struct flexcan_mb __iomem *tx_mb;
Expand Down Expand Up @@ -444,6 +446,27 @@ static inline void flexcan_error_irq_disable(const struct flexcan_priv *priv)
priv->write(reg_ctrl, &regs->ctrl);
}

static int flexcan_clks_enable(const struct flexcan_priv *priv)
{
int err;

err = clk_prepare_enable(priv->clk_ipg);
if (err)
return err;

err = clk_prepare_enable(priv->clk_per);
if (err)
clk_disable_unprepare(priv->clk_ipg);

return err;
}

static void flexcan_clks_disable(const struct flexcan_priv *priv)
{
clk_disable_unprepare(priv->clk_per);
clk_disable_unprepare(priv->clk_ipg);
}

static inline int flexcan_transceiver_enable(const struct flexcan_priv *priv)
{
if (!priv->reg_xceiver)
Expand Down Expand Up @@ -570,19 +593,13 @@ static int flexcan_get_berr_counter(const struct net_device *dev,
const struct flexcan_priv *priv = netdev_priv(dev);
int err;

err = clk_prepare_enable(priv->clk_ipg);
if (err)
err = pm_runtime_get_sync(priv->dev);
if (err < 0)
return err;

err = clk_prepare_enable(priv->clk_per);
if (err)
goto out_disable_ipg;

err = __flexcan_get_berr_counter(dev, bec);

clk_disable_unprepare(priv->clk_per);
out_disable_ipg:
clk_disable_unprepare(priv->clk_ipg);
pm_runtime_put(priv->dev);

return err;
}
Expand Down Expand Up @@ -1215,17 +1232,13 @@ static int flexcan_open(struct net_device *dev)
struct flexcan_priv *priv = netdev_priv(dev);
int err;

err = clk_prepare_enable(priv->clk_ipg);
if (err)
err = pm_runtime_get_sync(priv->dev);
if (err < 0)
return err;

err = clk_prepare_enable(priv->clk_per);
if (err)
goto out_disable_ipg;

err = open_candev(dev);
if (err)
goto out_disable_per;
goto out_runtime_put;

err = request_irq(dev->irq, flexcan_irq, IRQF_SHARED, dev->name, dev);
if (err)
Expand Down Expand Up @@ -1288,10 +1301,8 @@ static int flexcan_open(struct net_device *dev)
free_irq(dev->irq, dev);
out_close:
close_candev(dev);
out_disable_per:
clk_disable_unprepare(priv->clk_per);
out_disable_ipg:
clk_disable_unprepare(priv->clk_ipg);
out_runtime_put:
pm_runtime_put(priv->dev);

return err;
}
Expand All @@ -1306,10 +1317,9 @@ static int flexcan_close(struct net_device *dev)

can_rx_offload_del(&priv->offload);
free_irq(dev->irq, dev);
clk_disable_unprepare(priv->clk_per);
clk_disable_unprepare(priv->clk_ipg);

close_candev(dev);
pm_runtime_put(priv->dev);

can_led_event(dev, CAN_LED_EVENT_STOP);

Expand Down Expand Up @@ -1349,18 +1359,15 @@ static int register_flexcandev(struct net_device *dev)
struct flexcan_regs __iomem *regs = priv->regs;
u32 reg, err;

err = clk_prepare_enable(priv->clk_ipg);
err = flexcan_clks_enable(priv);
if (err)
return err;

err = clk_prepare_enable(priv->clk_per);
if (err)
goto out_disable_ipg;

/* select "bus clock", chip must be disabled */
err = flexcan_chip_disable(priv);
if (err)
goto out_disable_per;
goto out_clks_disable;

reg = priv->read(&regs->ctrl);
reg |= FLEXCAN_CTRL_CLK_SRC;
priv->write(reg, &regs->ctrl);
Expand Down Expand Up @@ -1388,15 +1395,21 @@ static int register_flexcandev(struct net_device *dev)
}

err = register_candev(dev);
if (err)
goto out_chip_disable;

/* disable core and turn off clocks */
out_chip_disable:
/* Disable core and let pm_runtime_put() disable the clocks.
* If CONFIG_PM is not enabled, the clocks will stay powered.
*/
flexcan_chip_disable(priv);
out_disable_per:
clk_disable_unprepare(priv->clk_per);
out_disable_ipg:
clk_disable_unprepare(priv->clk_ipg);
pm_runtime_put(priv->dev);

return 0;

out_chip_disable:
flexcan_chip_disable(priv);
out_clks_disable:
flexcan_clks_disable(priv);
return err;
}

Expand Down Expand Up @@ -1556,6 +1569,7 @@ static int flexcan_probe(struct platform_device *pdev)
priv->write = flexcan_write_le;
}

priv->dev = &pdev->dev;
priv->can.clock.freq = clock_freq;
priv->can.bittiming_const = &flexcan_bittiming_const;
priv->can.do_set_mode = flexcan_set_mode;
Expand All @@ -1569,6 +1583,10 @@ static int flexcan_probe(struct platform_device *pdev)
priv->devtype_data = devtype_data;
priv->reg_xceiver = reg_xceiver;

pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);

err = register_flexcandev(dev);
if (err) {
dev_err(&pdev->dev, "registering netdev failed\n");
Expand All @@ -1595,6 +1613,7 @@ static int flexcan_remove(struct platform_device *pdev)
struct net_device *dev = platform_get_drvdata(pdev);

unregister_flexcandev(dev);
pm_runtime_disable(&pdev->dev);
free_candev(dev);

return 0;
Expand All @@ -1604,7 +1623,7 @@ static int __maybe_unused flexcan_suspend(struct device *device)
{
struct net_device *dev = dev_get_drvdata(device);
struct flexcan_priv *priv = netdev_priv(dev);
int err;
int err = 0;

if (netif_running(dev)) {
/* if wakeup is enabled, enter stop mode
Expand All @@ -1617,20 +1636,22 @@ static int __maybe_unused flexcan_suspend(struct device *device)
err = flexcan_chip_disable(priv);
if (err)
return err;

err = pm_runtime_force_suspend(device);
}
netif_stop_queue(dev);
netif_device_detach(dev);
}
priv->can.state = CAN_STATE_SLEEPING;

return 0;
return err;
}

static int __maybe_unused flexcan_resume(struct device *device)
{
struct net_device *dev = dev_get_drvdata(device);
struct flexcan_priv *priv = netdev_priv(dev);
int err;
int err = 0;

priv->can.state = CAN_STATE_ERROR_ACTIVE;
if (netif_running(dev)) {
Expand All @@ -1639,14 +1660,35 @@ static int __maybe_unused flexcan_resume(struct device *device)
if (device_may_wakeup(device)) {
disable_irq_wake(dev->irq);
} else {
err = flexcan_chip_enable(priv);
err = pm_runtime_force_resume(device);
if (err)
return err;

err = flexcan_chip_enable(priv);
}
}

return err;
}

static int __maybe_unused flexcan_runtime_suspend(struct device *device)
{
struct net_device *dev = dev_get_drvdata(device);
struct flexcan_priv *priv = netdev_priv(dev);

flexcan_clks_disable(priv);

return 0;
}

static int __maybe_unused flexcan_runtime_resume(struct device *device)
{
struct net_device *dev = dev_get_drvdata(device);
struct flexcan_priv *priv = netdev_priv(dev);

return flexcan_clks_enable(priv);
}

static int __maybe_unused flexcan_noirq_suspend(struct device *device)
{
struct net_device *dev = dev_get_drvdata(device);
Expand All @@ -1673,6 +1715,7 @@ static int __maybe_unused flexcan_noirq_resume(struct device *device)

static const struct dev_pm_ops flexcan_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(flexcan_suspend, flexcan_resume)
SET_RUNTIME_PM_OPS(flexcan_runtime_suspend, flexcan_runtime_resume, NULL)
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(flexcan_noirq_suspend, flexcan_noirq_resume)
};

Expand Down

0 comments on commit ca10989

Please sign in to comment.