[PATCH 2/3] can: mcp251xfd: suspend and resume handlers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Added suspend and resume handlers, placing the device into sleep on entry to suspend and waking it on resume.

Have tested both suspend to idle (freeze) and suspend to ram (mem).

Freeze was tested on an Allwinner A64 (pine 64 dev board I had lying around which didn't support suspend to ram), running 5.15.13.

Both freeze and mem were tested on a Coretex A7 (Quectel QuecOpen AG5xx), running my horrible mashup of 4.9.217 with a lot of backporting.

Each device has entered and exited suspend, triggered by another device, serial console in my case multiple times without issue (~50 times at least). With candump and cangen running, each time reception and transmission have resumed as I would have expected.

Logic analyser confirmed interrupt main / rx interrupt lines remain high despite traffic on the bus, transceiver enable line changes from low to high when entering sleep and reverse when resuming.

Signed-off-by: Phil Greenland <phil@xxxxxxxxxxxxxxx>
---
 .../net/can/spi/mcp251xfd/mcp251xfd-core.c    | 110 ++++++++++++++++--
 .../can/spi/mcp251xfd/mcp251xfd-timestamp.c   |   6 +-
 drivers/net/can/spi/mcp251xfd/mcp251xfd.h     |   1 +
 3 files changed, 109 insertions(+), 8 deletions(-)

diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
index 9ee2c69c5..98eb597d8 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
@@ -425,6 +425,30 @@ static void mcp251xfd_ring_init(struct mcp251xfd_priv *priv)
 	}
 }
 
+static void mcp251xfd_ring_reset(struct mcp251xfd_priv *priv)
+{
+	struct mcp251xfd_tef_ring *tef_ring;
+	struct mcp251xfd_tx_ring *tx_ring;
+	struct mcp251xfd_rx_ring *rx_ring = NULL;
+	int i;
+
+	/* TEF */
+	tef_ring = priv->tef;
+	tef_ring->head = 0;
+	tef_ring->tail = 0;
+
+	/* TX */
+	tx_ring = priv->tx;
+	tx_ring->head = 0;
+	tx_ring->tail = 0;
+
+	/* RX */
+	mcp251xfd_for_each_rx_ring(priv, rx_ring, i) {
+		rx_ring->head = 0;
+		rx_ring->tail = 0;
+	}
+}
+
 static void mcp251xfd_ring_free(struct mcp251xfd_priv *priv)
 {
 	int i;
@@ -703,13 +727,8 @@ static int mcp251xfd_chip_clock_init(const struct mcp251xfd_priv *priv)
 	u32 osc;
 	int err;
 
-	/* Activate Low Power Mode on Oscillator Disable. This only
-	 * works on the MCP2518FD. The MCP2517FD will go into normal
-	 * Sleep Mode instead.
-	 */
-	osc = MCP251XFD_REG_OSC_LPMEN |
-		FIELD_PREP(MCP251XFD_REG_OSC_CLKODIV_MASK,
-			   MCP251XFD_REG_OSC_CLKODIV_10);
+	/* Divide clock by 10 before presenting on clk output */
+	osc = FIELD_PREP(MCP251XFD_REG_OSC_CLKODIV_MASK, MCP251XFD_REG_OSC_CLKODIV_10);
 	err = regmap_write(priv->map_reg, MCP251XFD_REG_OSC, osc);
 	if (err)
 		return err;
@@ -723,6 +742,16 @@ static int mcp251xfd_chip_clock_init(const struct mcp251xfd_priv *priv)
 			    MCP251XFD_REG_TSCON_TBCEN);
 }
 
+static int mcp251xfd_sleep_in_low_power_mode(const struct mcp251xfd_priv *priv)
+{
+	/* Activate Low Power Mode for sleep. This only
+	 * works on the MCP2518FD. The MCP2517FD will go into normal
+	 * Sleep Mode instead.
+	 */
+	return regmap_update_bits(priv->map_reg, MCP251XFD_REG_OSC,
+				  MCP251XFD_REG_OSC_LPMEN, MCP251XFD_REG_OSC_LPMEN);
+}
+
 static int mcp251xfd_set_bittiming(const struct mcp251xfd_priv *priv)
 {
 	const struct can_bittiming *bt = &priv->can.bittiming;
@@ -1068,6 +1097,7 @@ static int mcp251xfd_chip_stop(struct mcp251xfd_priv *priv,
 
 	mcp251xfd_chip_interrupts_disable(priv);
 	mcp251xfd_chip_configure_gpio(priv, false);
+	mcp251xfd_sleep_in_low_power_mode(priv);
 	return mcp251xfd_chip_set_mode(priv, MCP251XFD_REG_CON_MODE_SLEEP);
 }
 
@@ -2525,6 +2555,7 @@ static int mcp251xfd_open(struct net_device *ndev)
 		goto out_transceiver_disable;
 
 	mcp251xfd_timestamp_init(priv);
+	mcp251xfd_timestamp_start(priv);
 	can_rx_offload_enable(&priv->offload);
 
 	err = request_threaded_irq(spi->irq, NULL, mcp251xfd_irq,
@@ -3030,6 +3061,70 @@ static int mcp251xfd_remove(struct spi_device *spi)
 	return 0;
 }
 
+static int __maybe_unused mcp251xfd_suspend(struct device *device)
+{
+	struct spi_device *spi = to_spi_device(device);
+	struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
+	int err;
+
+	if (!netif_running(priv->ndev))
+		return 0;
+
+	disable_irq(priv->ndev->irq);
+
+	mcp251xfd_timestamp_stop(priv);
+
+	err = mcp251xfd_chip_interrupts_disable(priv);
+	if (err)
+		return err;
+
+	err = mcp251xfd_chip_set_mode(priv, MCP251XFD_REG_CON_MODE_SLEEP);
+	if (err)
+		return err;
+
+	netif_stop_queue(priv->ndev);
+	netif_device_detach(priv->ndev);
+
+	return 0;
+}
+
+static int __maybe_unused mcp251xfd_resume(struct device *device)
+{
+	struct spi_device *spi = to_spi_device(device);
+	struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
+	int err;
+
+	if (!netif_running(priv->ndev))
+		return 0;
+
+	netif_device_attach(priv->ndev);
+	netif_start_queue(priv->ndev);
+
+	/* restart oscillator (disabled automatically when entering sleep) */
+	err = mcp251xfd_chip_clock_enable(priv);
+	if (err)
+		return err;
+
+	/* reset tx and rx fifos (controller resets them all when entering configuration
+	 * mode as it does when exiting sleep)
+	 */
+	mcp251xfd_ring_reset(priv);
+
+	err = mcp251xfd_chip_set_normal_mode(priv);
+	if (err)
+		return err;
+
+	err = mcp251xfd_chip_interrupts_enable(priv);
+	if (err)
+		return err;
+
+	mcp251xfd_timestamp_start(priv);
+
+	enable_irq(priv->ndev->irq);
+
+	return 0;
+}
+
 static int __maybe_unused mcp251xfd_runtime_suspend(struct device *device)
 {
 	const struct mcp251xfd_priv *priv = dev_get_drvdata(device);
@@ -3045,6 +3140,7 @@ static int __maybe_unused mcp251xfd_runtime_resume(struct device *device)
 }
 
 static const struct dev_pm_ops mcp251xfd_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(mcp251xfd_suspend, mcp251xfd_resume)
 	SET_RUNTIME_PM_OPS(mcp251xfd_runtime_suspend,
 			   mcp251xfd_runtime_resume, NULL)
 };
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-timestamp.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-timestamp.c
index 712e09186..d889c19b0 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-timestamp.c
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-timestamp.c
@@ -61,8 +61,12 @@ void mcp251xfd_timestamp_init(struct mcp251xfd_priv *priv)
 	timecounter_init(&priv->tc, &priv->cc, ktime_get_real_ns());
 
 	INIT_DELAYED_WORK(&priv->timestamp, mcp251xfd_timestamp_work);
+}
+
+void mcp251xfd_timestamp_start(struct mcp251xfd_priv *priv)
+{
 	schedule_delayed_work(&priv->timestamp,
-			      MCP251XFD_TIMESTAMP_WORK_DELAY_SEC * HZ);
+						  MCP251XFD_TIMESTAMP_WORK_DELAY_SEC * HZ);
 }
 
 void mcp251xfd_timestamp_stop(struct mcp251xfd_priv *priv)
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd.h b/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
index ad658faa2..111f88dc2 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
@@ -858,6 +858,7 @@ u16 mcp251xfd_crc16_compute(const void *data, size_t data_size);
 void mcp251xfd_skb_set_timestamp(const struct mcp251xfd_priv *priv,
 				 struct sk_buff *skb, u32 timestamp);
 void mcp251xfd_timestamp_init(struct mcp251xfd_priv *priv);
+void mcp251xfd_timestamp_start(struct mcp251xfd_priv *priv);
 void mcp251xfd_timestamp_stop(struct mcp251xfd_priv *priv);
 
 #if IS_ENABLED(CONFIG_DEV_COREDUMP)
-- 
2.25.1




[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux