summaryrefslogtreecommitdiffstats
path: root/drivers/net/can/c_can/c_can.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/c_can/c_can.c')
-rw-r--r--drivers/net/can/c_can/c_can.c130
1 files changed, 125 insertions, 5 deletions
diff --git a/drivers/net/can/c_can/c_can.c b/drivers/net/can/c_can/c_can.c
index 4c538e3..e5180dfd 100644
--- a/drivers/net/can/c_can/c_can.c
+++ b/drivers/net/can/c_can/c_can.c
@@ -34,6 +34,7 @@
#include <linux/if_ether.h>
#include <linux/list.h>
#include <linux/io.h>
+#include <linux/pm_runtime.h>
#include <linux/can.h>
#include <linux/can/dev.h>
@@ -45,6 +46,9 @@
#define IF_ENUM_REG_LEN 11
#define C_CAN_IFACE(reg, iface) (C_CAN_IF1_##reg + (iface) * IF_ENUM_REG_LEN)
+/* control extension register D_CAN specific */
+#define CONTROL_EX_PDR BIT(8)
+
/* control register */
#define CONTROL_TEST BIT(7)
#define CONTROL_CCE BIT(6)
@@ -64,6 +68,7 @@
#define TEST_BASIC BIT(2)
/* status register */
+#define STATUS_PDA BIT(10)
#define STATUS_BOFF BIT(7)
#define STATUS_EWARN BIT(6)
#define STATUS_EPASS BIT(5)
@@ -163,6 +168,9 @@
/* minimum timeout for checking BUSY status */
#define MIN_TIMEOUT_VALUE 6
+/* Wait for ~1 sec for INIT bit */
+#define INIT_WAIT_MS 1000
+
/* napi related */
#define C_CAN_NAPI_WEIGHT C_CAN_MSG_OBJ_RX_NUM
@@ -201,6 +209,30 @@ static const struct can_bittiming_const c_can_bittiming_const = {
.brp_inc = 1,
};
+static inline void c_can_pm_runtime_enable(const struct c_can_priv *priv)
+{
+ if (priv->device)
+ pm_runtime_enable(priv->device);
+}
+
+static inline void c_can_pm_runtime_disable(const struct c_can_priv *priv)
+{
+ if (priv->device)
+ pm_runtime_disable(priv->device);
+}
+
+static inline void c_can_pm_runtime_get_sync(const struct c_can_priv *priv)
+{
+ if (priv->device)
+ pm_runtime_get_sync(priv->device);
+}
+
+static inline void c_can_pm_runtime_put_sync(const struct c_can_priv *priv)
+{
+ if (priv->device)
+ pm_runtime_put_sync(priv->device);
+}
+
static inline int get_tx_next_msg_obj(const struct c_can_priv *priv)
{
return (priv->tx_next & C_CAN_NEXT_MSG_OBJ_MASK) +
@@ -673,11 +705,15 @@ static int c_can_get_berr_counter(const struct net_device *dev,
unsigned int reg_err_counter;
struct c_can_priv *priv = netdev_priv(dev);
+ c_can_pm_runtime_get_sync(priv);
+
reg_err_counter = priv->read_reg(priv, C_CAN_ERR_CNT_REG);
bec->rxerr = (reg_err_counter & ERR_CNT_REC_MASK) >>
ERR_CNT_REC_SHIFT;
bec->txerr = reg_err_counter & ERR_CNT_TEC_MASK;
+ c_can_pm_runtime_put_sync(priv);
+
return 0;
}
@@ -1053,11 +1089,13 @@ static int c_can_open(struct net_device *dev)
int err;
struct c_can_priv *priv = netdev_priv(dev);
+ c_can_pm_runtime_get_sync(priv);
+
/* open the can device */
err = open_candev(dev);
if (err) {
netdev_err(dev, "failed to open can device\n");
- return err;
+ goto exit_open_fail;
}
/* register interrupt handler */
@@ -1079,6 +1117,8 @@ static int c_can_open(struct net_device *dev)
exit_irq_fail:
close_candev(dev);
+exit_open_fail:
+ c_can_pm_runtime_put_sync(priv);
return err;
}
@@ -1091,6 +1131,7 @@ static int c_can_close(struct net_device *dev)
c_can_stop(dev);
free_irq(dev->irq, dev);
close_candev(dev);
+ c_can_pm_runtime_put_sync(priv);
return 0;
}
@@ -1119,6 +1160,77 @@ struct net_device *alloc_c_can_dev(void)
}
EXPORT_SYMBOL_GPL(alloc_c_can_dev);
+#ifdef CONFIG_PM
+int c_can_power_down(struct net_device *dev)
+{
+ u32 val;
+ unsigned long time_out;
+ struct c_can_priv *priv = netdev_priv(dev);
+
+ if (!(dev->flags & IFF_UP))
+ return 0;
+
+ WARN_ON(priv->type != BOSCH_D_CAN);
+
+ /* set PDR value so the device goes to power down mode */
+ val = priv->read_reg(priv, C_CAN_CTRL_EX_REG);
+ val |= CONTROL_EX_PDR;
+ priv->write_reg(priv, C_CAN_CTRL_EX_REG, val);
+
+ /* Wait for the PDA bit to get set */
+ time_out = jiffies + msecs_to_jiffies(INIT_WAIT_MS);
+ while (!(priv->read_reg(priv, C_CAN_STS_REG) & STATUS_PDA) &&
+ time_after(time_out, jiffies))
+ cpu_relax();
+
+ if (time_after(jiffies, time_out))
+ return -ETIMEDOUT;
+
+ c_can_stop(dev);
+
+ c_can_pm_runtime_put_sync(priv);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(c_can_power_down);
+
+int c_can_power_up(struct net_device *dev)
+{
+ u32 val;
+ unsigned long time_out;
+ struct c_can_priv *priv = netdev_priv(dev);
+
+ if (!(dev->flags & IFF_UP))
+ return 0;
+
+ WARN_ON(priv->type != BOSCH_D_CAN);
+
+ c_can_pm_runtime_get_sync(priv);
+
+ /* Clear PDR and INIT bits */
+ val = priv->read_reg(priv, C_CAN_CTRL_EX_REG);
+ val &= ~CONTROL_EX_PDR;
+ priv->write_reg(priv, C_CAN_CTRL_EX_REG, val);
+ val = priv->read_reg(priv, C_CAN_CTRL_REG);
+ val &= ~CONTROL_INIT;
+ priv->write_reg(priv, C_CAN_CTRL_REG, val);
+
+ /* Wait for the PDA bit to get clear */
+ time_out = jiffies + msecs_to_jiffies(INIT_WAIT_MS);
+ while ((priv->read_reg(priv, C_CAN_STS_REG) & STATUS_PDA) &&
+ time_after(time_out, jiffies))
+ cpu_relax();
+
+ if (time_after(jiffies, time_out))
+ return -ETIMEDOUT;
+
+ c_can_start(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(c_can_power_up);
+#endif
+
void free_c_can_dev(struct net_device *dev)
{
free_candev(dev);
@@ -1133,10 +1245,19 @@ static const struct net_device_ops c_can_netdev_ops = {
int register_c_can_dev(struct net_device *dev)
{
+ struct c_can_priv *priv = netdev_priv(dev);
+ int err;
+
+ c_can_pm_runtime_enable(priv);
+
dev->flags |= IFF_ECHO; /* we support local echo */
dev->netdev_ops = &c_can_netdev_ops;
- return register_candev(dev);
+ err = register_candev(dev);
+ if (err)
+ c_can_pm_runtime_disable(priv);
+
+ return err;
}
EXPORT_SYMBOL_GPL(register_c_can_dev);
@@ -1144,10 +1265,9 @@ void unregister_c_can_dev(struct net_device *dev)
{
struct c_can_priv *priv = netdev_priv(dev);
- /* disable all interrupts */
- c_can_enable_all_interrupts(priv, DISABLE_ALL_INTERRUPTS);
-
unregister_candev(dev);
+
+ c_can_pm_runtime_disable(priv);
}
EXPORT_SYMBOL_GPL(unregister_c_can_dev);
OpenPOWER on IntegriCloud