drivers: can: mcp2515: abort transfers before entering configuration mode

Abort any pending transmissions before entering configuration mode and
notify the senders.

Poll for mode change completion instead of assuming mode changes take
immediate effect. This is needed for successfully entering configuration
mode with recently aborted transmissions.

Fixes: #50545

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
diff --git a/drivers/can/can_mcp2515.c b/drivers/can/can_mcp2515.c
index 59df01c..c88820b 100644
--- a/drivers/can/can_mcp2515.c
+++ b/drivers/can/can_mcp2515.c
@@ -35,6 +35,12 @@
 #error You must either set a sampling-point or timings (phase-seg* and prop-seg)
 #endif
 
+/* Timeout for changing mode */
+#define MCP2515_MODE_CHANGE_TIMEOUT_USEC 1000
+#define MCP2515_MODE_CHANGE_RETRIES      100
+#define MCP2515_MODE_CHANGE_DELAY	    \
+	K_USEC(MCP2515_MODE_CHANGE_TIMEOUT_USEC / MCP2515_MODE_CHANGE_RETRIES)
+
 static int mcp2515_cmd_soft_reset(const struct device *dev)
 {
 	const struct mcp2515_config *dev_cfg = dev->config;
@@ -271,6 +277,7 @@
 
 const int mcp2515_set_mode_int(const struct device *dev, uint8_t mcp2515_mode)
 {
+	int retries = MCP2515_MODE_CHANGE_RETRIES;
 	uint8_t canstat;
 
 	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_CANCTRL,
@@ -278,15 +285,36 @@
 			       mcp2515_mode << MCP2515_CANCTRL_MODE_POS);
 	mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANSTAT, &canstat, 1);
 
-	if (((canstat & MCP2515_CANSTAT_MODE_MASK) >> MCP2515_CANSTAT_MODE_POS)
-	    != mcp2515_mode) {
-		LOG_ERR("Failed to set MCP2515 operation mode");
-		return -EIO;
+	while (((canstat & MCP2515_CANSTAT_MODE_MASK) >> MCP2515_CANSTAT_MODE_POS)
+		!= mcp2515_mode) {
+		if (--retries < 0) {
+			LOG_ERR("Timeout trying to set MCP2515 operation mode");
+			return -EIO;
+		}
+
+		k_sleep(MCP2515_MODE_CHANGE_DELAY);
+		mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANSTAT, &canstat, 1);
 	}
 
 	return 0;
 }
 
+static void mcp2515_tx_done(const struct device *dev, uint8_t tx_idx, int status)
+{
+	struct mcp2515_data *dev_data = dev->data;
+	can_tx_callback_t callback = dev_data->tx_cb[tx_idx].cb;
+
+	if (callback != NULL) {
+		callback(dev, status, dev_data->tx_cb[tx_idx].cb_arg);
+		dev_data->tx_cb[tx_idx].cb = NULL;
+
+		k_mutex_lock(&dev_data->mutex, K_FOREVER);
+		dev_data->tx_busy_map &= ~BIT(tx_idx);
+		k_mutex_unlock(&dev_data->mutex);
+		k_sem_give(&dev_data->tx_sem);
+	}
+}
+
 static int mcp2515_get_core_clock(const struct device *dev, uint32_t *rate)
 {
 	const struct mcp2515_config *dev_cfg = dev->config;
@@ -457,6 +485,7 @@
 	const struct mcp2515_config *dev_cfg = dev->config;
 	struct mcp2515_data *dev_data = dev->data;
 	int ret;
+	int i;
 
 	if (!dev_data->started) {
 		return -EALREADY;
@@ -464,6 +493,18 @@
 
 	k_mutex_lock(&dev_data->mutex, K_FOREVER);
 
+	/* Abort any pending transmissions before entering configuration mode */
+	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB0CTRL,
+			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
+#if MCP2515_TX_CNT == 2
+	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB1CTRL,
+			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
+#endif /*  MCP2515_TX_CNT == 2 */
+#if MCP2515_TX_CNT == 3
+	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB2CTRL,
+			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
+#endif /* MCP2515_TX_CNT == 3 */
+
 	ret = mcp2515_set_mode_int(dev, MCP2515_MODE_CONFIGURATION);
 	if (ret < 0) {
 		LOG_ERR("Failed to enter configuration mode [%d]", ret);
@@ -475,6 +516,10 @@
 
 	k_mutex_unlock(&dev_data->mutex);
 
+	for (i = 0; i < MCP2515_TX_CNT; i++) {
+		mcp2515_tx_done(dev, i, -ENETDOWN);
+	}
+
 	if (dev_cfg->phy != NULL) {
 		ret = can_transceiver_disable(dev_cfg->phy);
 		if (ret != 0) {
@@ -678,19 +723,6 @@
 	mcp2515_rx_filter(dev, &frame);
 }
 
-static void mcp2515_tx_done(const struct device *dev, uint8_t tx_idx)
-{
-	struct mcp2515_data *dev_data = dev->data;
-
-	dev_data->tx_cb[tx_idx].cb(dev, 0, dev_data->tx_cb[tx_idx].cb_arg);
-	dev_data->tx_cb[tx_idx].cb = NULL;
-
-	k_mutex_lock(&dev_data->mutex, K_FOREVER);
-	dev_data->tx_busy_map &= ~BIT(tx_idx);
-	k_mutex_unlock(&dev_data->mutex);
-	k_sem_give(&dev_data->tx_sem);
-}
-
 static int mcp2515_get_state(const struct device *dev, enum can_state *state,
 			     struct can_bus_err_cnt *err_cnt)
 {
@@ -805,15 +837,15 @@
 		}
 
 		if (canintf & MCP2515_CANINTF_TX0IF) {
-			mcp2515_tx_done(dev, 0);
+			mcp2515_tx_done(dev, 0, 0);
 		}
 
 		if (canintf & MCP2515_CANINTF_TX1IF) {
-			mcp2515_tx_done(dev, 1);
+			mcp2515_tx_done(dev, 1, 0);
 		}
 
 		if (canintf & MCP2515_CANINTF_TX2IF) {
-			mcp2515_tx_done(dev, 2);
+			mcp2515_tx_done(dev, 2, 0);
 		}
 
 		if (canintf & MCP2515_CANINTF_ERRIF) {
diff --git a/drivers/can/can_mcp2515.h b/drivers/can/can_mcp2515.h
index c25fed6..8d9277b 100644
--- a/drivers/can/can_mcp2515.h
+++ b/drivers/can/can_mcp2515.h
@@ -156,5 +156,7 @@
 #define MCP2515_CANSTAT_MODE_MASK		(0x07 << MCP2515_CANSTAT_MODE_POS)
 #define MCP2515_CANCTRL_MODE_POS		5
 #define MCP2515_CANCTRL_MODE_MASK		(0x07 << MCP2515_CANCTRL_MODE_POS)
+#define MCP2515_TXBNCTRL_TXREQ_POS		3
+#define MCP2515_TXBNCTRL_TXREQ_MASK		(0x01 << MCP2515_TXBNCTRL_TXREQ_POS)
 
 #endif /*_MCP2515_H_*/