Skip to content

Commit 9c273d7

Browse files
committed
drivers: i2c: i2c_mspm0g3xxx: add watchdog timer
When the system is crashing, the I2C lines (SDA/SCL) are not being reset and might stay LOW. To address this issue, we need to ensure that none of the user i2c driver callbacks (that are doing reads or writes) are taking longer than CONFIG_I2C_MSPM0G3XXX_WATCHDOG_TIMEOUT. So we fire a timer before each call. If the callback doesn't finish fast enough, the watchdog callback will reset the i2c lines, NACK the i2c message and panic with the CONFIG_MSPM0G3XXX_I2C_WATCHDOG_PANIC_CODE error code. The timeout is configured with CONFIG_I2C_MSPM0G3XXX_WATCHDOG_TIMEOUT The panic error code is configured with CONFIG_MSPM0G3XXX_I2C_WATCHDOG_PANIC_CODE. To make use of this reboot feature, the device tree needs to provide a reference to a hardware timer. Let's say we want to use timer timg7 as a watchdog: We need to configure that timer in the dts: ``` &timg7 { status = "okay"; mode = <ONE_SHOT_DOWN>; prescaler = <0>; divide-ratio = <RATE_1>; }; ``` and then reference in the i2c block: ``` &i2c0 { watchdog-timer = <&timg7>; extras ... ``` Since we are invoking a software panic on the mcu, make sure to disable the COREDUMP since the default backend is the "logging" one and it takes too much of the cpu resources, thus delaying the mcu reboot. This can be done with: ``` CONFIG_DEBUG_COREDUMP=n ``` Of course for now, the implementation is using a counter timer since we don't have a mspm0 watchdog. We need to also use I2C_MSPM0G3XXX_INIT_PRIORITY as the driver init priority. Signed-off-by: Dimitris Karnikis <dika@bang-olufsen.dk>
1 parent 10222e3 commit 9c273d7

File tree

1 file changed

+79
-1
lines changed

1 file changed

+79
-1
lines changed

drivers/i2c/i2c_mspm0g3xxx.c

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <zephyr/kernel.h>
77
#include <zephyr/drivers/i2c.h>
88
#include <zephyr/drivers/pinctrl.h>
9+
#include <zephyr/drivers/counter.h>
910
#include <soc.h>
1011

1112
/* Logging includes */
@@ -76,6 +77,7 @@ struct i2c_mspm0g3xxx_config {
7677
const struct pinctrl_dev_config *pinctrl;
7778
void (*interrupt_init_function)(const struct device *dev);
7879
uint32_t dt_bitrate;
80+
const struct device *watchdog_timer;
7981
};
8082

8183
struct i2c_mspm0g3xxx_data {
@@ -92,6 +94,7 @@ struct i2c_mspm0g3xxx_data {
9294
int target_rx_valid;
9395
struct k_sem i2c_busy_sem;
9496
struct k_sem transfer_timeout_sem;
97+
struct counter_alarm_cfg watchdog_timer_cfg;
9598
};
9699

97100
#ifdef CONFIG_I2C_MSPM0G3XXX_TARGET_SUPPORT
@@ -111,6 +114,58 @@ static K_KERNEL_STACK_DEFINE(i2c_mspm0g3xxx_target_stack,
111114
CONFIG_I2C_MSPM0G3XXX_TARGET_THREAD_STACK_SIZE);
112115
static struct k_thread i2c_mspm0g3xxx_target_thread;
113116

117+
static void i2c_mspm0g3xxx_target_stop_watchdog(struct i2c_mspm0g3xxx_data *data) {
118+
const struct i2c_mspm0g3xxx_config *config = data->cfg;
119+
if (!config->watchdog_timer) {
120+
return;
121+
}
122+
123+
int err = counter_stop(config-> watchdog_timer);
124+
if (err != 0) {
125+
LOG_ERR("Failed to stop the timer, err: %d", err);
126+
}
127+
128+
err = counter_cancel_channel_alarm(config->watchdog_timer, 0);
129+
if (err != 0) {
130+
LOG_ERR("Failed to cancel the timer alarm, err: %d", err);
131+
}
132+
}
133+
134+
static void i2c_mspm0g3xxx_panic(const struct device *dev, uint8_t channel, uint32_t ticks,
135+
void *user_data)
136+
{
137+
struct i2c_mspm0g3xxx_data *data = user_data;
138+
const struct i2c_mspm0g3xxx_config *config = data->cfg;
139+
140+
DL_I2C_setTargetACKOverrideValue((I2C_Regs *)config->base,
141+
DL_I2C_TARGET_RESPONSE_OVERRIDE_VALUE_NACK);
142+
DL_I2C_disableTargetClockStretching((I2C_Regs *)config->base);
143+
DL_I2C_disablePower((I2C_Regs *)config->base);
144+
z_except_reason(CONFIG_I2C_MSPM0G3XXX_WATCHDOG_PANIC_CODE);
145+
}
146+
147+
static void i2c_mspm0g3xxx_target_start_watchdog(struct i2c_mspm0g3xxx_data *data)
148+
{
149+
const struct i2c_mspm0g3xxx_config *config = data->cfg;
150+
struct counter_alarm_cfg *watchdog_timer_cfg = &data->watchdog_timer_cfg;
151+
152+
if (!config->watchdog_timer) {
153+
return;
154+
}
155+
156+
i2c_mspm0g3xxx_target_stop_watchdog(data);
157+
158+
int err = counter_set_channel_alarm(config->watchdog_timer, 0, watchdog_timer_cfg);
159+
if (err != 0) {
160+
LOG_ERR("Failed to cancel the timer alarm, err: %d", err);
161+
}
162+
163+
err = counter_start(config->watchdog_timer);
164+
if (err != 0) {
165+
LOG_ERR("Failed to start the timer, err: %d", err);
166+
}
167+
}
168+
114169
void i2c_mspm0g3xxx_target_thread_work(void)
115170
{
116171
struct i2c_mspm0g3xxx_target_msg target_msg;
@@ -151,6 +206,7 @@ void i2c_mspm0g3xxx_target_thread_work(void)
151206
uint8_t nextByte;
152207
while (DL_I2C_isTargetRXFIFOEmpty((I2C_Regs *)config->base) !=
153208
true) {
209+
i2c_mspm0g3xxx_target_start_watchdog(data);
154210
if (data->target_rx_valid == 0) {
155211
nextByte = DL_I2C_receiveTargetData(
156212
(I2C_Regs *)config->base);
@@ -174,6 +230,7 @@ void i2c_mspm0g3xxx_target_thread_work(void)
174230
(I2C_Regs *)config->base,
175231
DL_I2C_TARGET_RESPONSE_OVERRIDE_VALUE_NACK);
176232
}
233+
i2c_mspm0g3xxx_target_stop_watchdog(data);
177234
}
178235
}
179236

@@ -182,6 +239,7 @@ void i2c_mspm0g3xxx_target_thread_work(void)
182239
data->state = I2C_mspm0g3xxx_TARGET_TX_INPROGRESS;
183240
/* Fill TX FIFO if there are more bytes to send */
184241
if (tconfig->callbacks->read_requested != NULL) {
242+
i2c_mspm0g3xxx_target_start_watchdog(data);
185243
uint8_t nextByte;
186244
data->target_tx_valid =
187245
tconfig->callbacks->read_requested(tconfig, &nextByte);
@@ -193,15 +251,19 @@ void i2c_mspm0g3xxx_target_thread_work(void)
193251
* 0's are transmitted */
194252
DL_I2C_transmitTargetData((I2C_Regs *)config->base, 0x00);
195253
}
254+
255+
i2c_mspm0g3xxx_target_stop_watchdog(data);
196256
}
197257
break;
198258
case DL_I2C_IIDX_TARGET_TXFIFO_EMPTY:
199259
if (tconfig->callbacks->read_processed != NULL) {
260+
i2c_mspm0g3xxx_target_start_watchdog(data);
200261
/* still using the FIFO, we call read_processed in order to add
201262
* additional data rather than from a buffer. If the write-received
202263
* function chooses to return 0 (no more data present), then 0's
203264
* will be filled in */
204265
uint8_t nextByte;
266+
205267
if (data->target_tx_valid == 0) {
206268
data->target_tx_valid = tconfig->callbacks->read_processed(
207269
tconfig, &nextByte);
@@ -215,6 +277,8 @@ void i2c_mspm0g3xxx_target_thread_work(void)
215277
* 0's are transmitted */
216278
DL_I2C_transmitTargetData((I2C_Regs *)config->base, 0x00);
217279
}
280+
281+
i2c_mspm0g3xxx_target_stop_watchdog(data);
218282
}
219283
break;
220284
case DL_I2C_IIDX_TARGET_STOP:
@@ -768,12 +832,25 @@ static int i2c_mspm0g3xxx_init(const struct device *dev)
768832
config->clock_frequency);
769833
return -EINVAL;
770834
}
835+
771836
LOG_DBG("DT bitrate %uHz (%u)", config->clock_frequency,
772837
config->dt_bitrate);
773838

774839
k_sem_init(&data->i2c_busy_sem, 0, 1);
775840
k_sem_init(&data->transfer_timeout_sem, 1, 1);
776841

842+
if (config->watchdog_timer) {
843+
if (!device_is_ready(config->watchdog_timer)) {
844+
LOG_ERR("Watchdog timer is not ready");
845+
return -EINVAL;
846+
}
847+
848+
data->watchdog_timer_cfg.user_data = (void *)data;
849+
data->watchdog_timer_cfg.callback = i2c_mspm0g3xxx_panic;
850+
data->watchdog_timer_cfg.ticks = counter_us_to_ticks(
851+
config->watchdog_timer, CONFIG_I2C_MSPM0G3XXX_WATCHDOG_TIMEOUT);
852+
}
853+
777854
/* Init power */
778855
DL_I2C_reset((I2C_Regs *)config->base);
779856
DL_I2C_enablePower((I2C_Regs *)config->base);
@@ -895,6 +972,7 @@ static const struct i2c_driver_api i2c_mspm0g3xxx_driver_api = {
895972
.divideRatio = DL_I2C_CLOCK_DIVIDE_1}, \
896973
.dt_bitrate = CALC_DT_BITRATE(DT_INST_PROP(index, clock_frequency), \
897974
DT_INST_PROP_BY_PHANDLE(index, clocks, clock_frequency)), \
975+
.watchdog_timer = DEVICE_DT_GET_OR_NULL(DT_PHANDLE(DT_DRV_INST(index), watchdog_timer)),\
898976
}; \
899977
\
900978
static struct i2c_mspm0g3xxx_data i2c_mspm0g3xxx_data_##index = { \
@@ -903,7 +981,7 @@ static const struct i2c_driver_api i2c_mspm0g3xxx_driver_api = {
903981
\
904982
I2C_DEVICE_DT_INST_DEFINE(index, i2c_mspm0g3xxx_init, NULL, &i2c_mspm0g3xxx_data_##index, \
905983
&i2c_mspm0g3xxx_cfg_##index, POST_KERNEL, \
906-
CONFIG_I2C_INIT_PRIORITY, &i2c_mspm0g3xxx_driver_api); \
984+
CONFIG_I2C_MSPM0G3XXX_INIT_PRIORITY, &i2c_mspm0g3xxx_driver_api); \
907985
\
908986
INTERRUPT_INIT_FUNCTION(index)
909987

0 commit comments

Comments
 (0)