| /**************************************************************************** |
| * arch/risc-v/src/esp32c3-legacy/esp32c3_wdt_lowerhalf.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/clock.h> |
| #include <nuttx/timers/watchdog.h> |
| |
| #include "riscv_internal.h" |
| #include "hardware/esp32c3_soc.h" |
| #include "esp32c3_wdt.h" |
| #include "esp32c3_wdt_lowerhalf.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* MWDT clock period in microseconds */ |
| |
| #define MWDT_CLK_PERIOD_US (500) |
| |
| /* Number of MWDT cycles per microseconds */ |
| |
| #define MWDT_CYCLES_PER_MS (USEC_PER_MSEC / MWDT_CLK_PERIOD_US) |
| |
| /* Convert MWDT timeout cycles to milliseconds */ |
| |
| #define MWDT_TIMEOUT_MS(t) ((t) * MWDT_CYCLES_PER_MS) |
| |
| /* Maximum number of MWDT cycles supported for timeout */ |
| |
| #define MWDT_MAX_TIMEOUT_MS (UINT32_MAX / MWDT_CYCLES_PER_MS) |
| |
| /* MWDT clock prescaler value */ |
| |
| #define MWDT_CLK_PRESCALER_VALUE (MWDT_CLK_PERIOD_US * NSEC_PER_USEC / 12.5) |
| |
| /* Maximum number of cycles supported for a RWDT stage timeout */ |
| |
| #define RWDT_FULL_STAGE (UINT32_MAX) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| typedef enum |
| { |
| RTC, |
| TIMER, |
| } wdt_peripherals_t; |
| |
| /* This structure provides the private representation of the "lower-half" |
| * driver state structure. This structure must be cast-compatible with the |
| * well-known watchdog_lowerhalf_s structure. |
| */ |
| |
| struct esp32c3_wdt_lowerhalf_s |
| { |
| const struct watchdog_ops_s *ops; /* Lower half operations */ |
| struct esp32c3_wdt_dev_s *wdt; /* ESP32-C3 watchdog driver */ |
| uint32_t timeout; /* The current timeout */ |
| wdt_peripherals_t peripheral; /* Indicates if it is from RTC or Timer Module */ |
| uint32_t lastreset; /* The last reset time */ |
| bool started; /* True: Timer has been started */ |
| xcpt_t handler; /* User Handler */ |
| void *upper; /* Pointer to watchdog_upperhalf_s */ |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Interrupt handling *******************************************************/ |
| |
| static int esp32c3_wdt_handler(int irq, void *context, void *arg); |
| |
| /* "Lower half" driver methods **********************************************/ |
| |
| static int esp32c3_wdt_start(struct watchdog_lowerhalf_s *lower); |
| static int esp32c3_wdt_stop(struct watchdog_lowerhalf_s *lower); |
| static int esp32c3_wdt_keepalive(struct watchdog_lowerhalf_s *lower); |
| static int esp32c3_wdt_getstatus(struct watchdog_lowerhalf_s *lower, |
| struct watchdog_status_s *status); |
| static int esp32c3_wdt_settimeout(struct watchdog_lowerhalf_s *lower, |
| uint32_t timeout); |
| static xcpt_t esp32c3_wdt_capture(struct watchdog_lowerhalf_s *lower, |
| xcpt_t handler); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* "Lower half" driver methods */ |
| |
| static const struct watchdog_ops_s g_esp32c3_wdg_ops = |
| { |
| .start = esp32c3_wdt_start, |
| .stop = esp32c3_wdt_stop, |
| .keepalive = esp32c3_wdt_keepalive, |
| .getstatus = esp32c3_wdt_getstatus, |
| .settimeout = esp32c3_wdt_settimeout, |
| .capture = esp32c3_wdt_capture, |
| .ioctl = NULL, |
| }; |
| |
| #ifdef CONFIG_ESP32C3_MWDT0 |
| /* MWDT0 lower-half */ |
| |
| static struct esp32c3_wdt_lowerhalf_s g_esp32c3_mwdt0_lowerhalf = |
| { |
| .ops = &g_esp32c3_wdg_ops, |
| }; |
| #endif |
| |
| #ifdef CONFIG_ESP32C3_MWDT1 |
| /* MWDT1 lower-half */ |
| |
| static struct esp32c3_wdt_lowerhalf_s g_esp32c3_mwdt1_lowerhalf = |
| { |
| .ops = &g_esp32c3_wdg_ops, |
| }; |
| #endif |
| |
| #ifdef CONFIG_ESP32C3_RWDT |
| /* RWDT lower-half */ |
| |
| static struct esp32c3_wdt_lowerhalf_s g_esp32c3_rwdt_lowerhalf = |
| { |
| .ops = &g_esp32c3_wdg_ops, |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_start |
| * |
| * Description: |
| * Start the watchdog timer, register a callback if there is one and |
| * enables interrupt, otherwise, configure it to reset system on |
| * expiration. |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of the |
| * "lower-half" driver state structure. |
| * |
| * Returned Values: |
| * Zero on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int esp32c3_wdt_start(struct watchdog_lowerhalf_s *lower) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| int ret = OK; |
| irqstate_t flags; |
| |
| wdinfo("Entry: started\n"); |
| DEBUGASSERT(priv); |
| |
| if (priv->started == true) |
| { |
| /* Return EBUSY to indicate that the timer was already running */ |
| |
| return -EBUSY; |
| } |
| |
| /* If WDT was not started yet */ |
| |
| else |
| { |
| priv->started = true; |
| |
| /* Unlock WDT */ |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| /* No User Handler */ |
| |
| if (priv->handler == NULL) |
| { |
| /* Then configure it to reset on wdt expiration */ |
| |
| if (priv->peripheral == TIMER) |
| { |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_RESET_SYSTEM); |
| ESP32C3_MWDT_UPD_CONF(priv->wdt); |
| } |
| else |
| { |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_RESET_RTC); |
| } |
| } |
| |
| /* User handler was already provided */ |
| |
| else |
| { |
| /* Then configure it to call the user handler on wdt expiration */ |
| |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_INT); |
| |
| if (priv->peripheral == TIMER) |
| { |
| ESP32C3_MWDT_UPD_CONF(priv->wdt); |
| } |
| |
| /* Set the lower half handler and enable interrupt */ |
| |
| flags = enter_critical_section(); |
| ESP32C3_WDT_SETISR(priv->wdt, esp32c3_wdt_handler, priv); |
| leave_critical_section(flags); |
| ESP32C3_WDT_ENABLEINT(priv->wdt); |
| } |
| flags = enter_critical_section(); |
| priv->lastreset = clock_systime_ticks(); |
| ESP32C3_WDT_START(priv->wdt); |
| leave_critical_section(flags); |
| |
| /* Lock it again */ |
| |
| ESP32C3_WDT_LOCK(priv->wdt); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_stop |
| * |
| * Description: |
| * Stop the watchdog timer. In case a callback was previously configured, |
| * unregister and deallocate it. |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of the |
| * "lower-half" driver state structure. |
| * |
| ****************************************************************************/ |
| |
| static int esp32c3_wdt_stop(struct watchdog_lowerhalf_s *lower) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| irqstate_t flags; |
| |
| /* Unlock WDT */ |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| /* Disable the WDT */ |
| |
| ESP32C3_WDT_STOP(priv->wdt); |
| |
| /* In case there is some callback registered, disable and deallocate */ |
| |
| if (priv->handler != NULL) |
| { |
| ESP32C3_WDT_DISABLEINT(priv->wdt); |
| flags = enter_critical_section(); |
| ESP32C3_WDT_SETISR(priv->wdt, NULL, NULL); |
| leave_critical_section(flags); |
| } |
| |
| /* Lock it again */ |
| |
| ESP32C3_WDT_LOCK(priv->wdt); |
| |
| priv->started = false; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_keepalive |
| * |
| * Description: |
| * Reset the watchdog timer, prevent any |
| * imminent watchdog timeouts. This is sometimes referred as "pinging" |
| * the watchdog timer or "petting the dog". |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of the |
| * "lower-half" driver state structure. |
| * |
| * |
| ****************************************************************************/ |
| |
| static int esp32c3_wdt_keepalive(struct watchdog_lowerhalf_s *lower) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| irqstate_t flags; |
| |
| wdinfo("Entry\n"); |
| |
| /* Unlock */ |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| /* Feed the dog and updates the lastreset variable */ |
| |
| flags = enter_critical_section(); |
| priv->lastreset = clock_systime_ticks(); |
| ESP32C3_WDT_FEED(priv->wdt); |
| leave_critical_section(flags); |
| |
| /* Lock */ |
| |
| ESP32C3_WDT_LOCK(priv->wdt); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_getstatus |
| * |
| * Description: |
| * Get the current watchdog timer status |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of |
| * the "lower-half" driver state structure. |
| * status - The location to return the watchdog status information. |
| * |
| ****************************************************************************/ |
| |
| static int esp32c3_wdt_getstatus(struct watchdog_lowerhalf_s *lower, |
| struct watchdog_status_s *status) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| uint32_t ticks; |
| uint32_t elapsed; |
| |
| DEBUGASSERT(priv); |
| |
| /* Flags */ |
| |
| status->flags = 0; |
| |
| /* If no handler was settled, then RESET on expiration. |
| * Otherwise, call the user handler. |
| */ |
| |
| if (priv->handler == NULL) |
| { |
| status->flags |= WDFLAGS_RESET; |
| } |
| else |
| { |
| status->flags |= WDFLAGS_CAPTURE; |
| } |
| if (priv->started) |
| { |
| status->flags |= WDFLAGS_ACTIVE; |
| } |
| |
| /* Return the current timeout in milliseconds */ |
| |
| status->timeout = priv->timeout; |
| |
| /* Get the elapsed time since the last ping */ |
| |
| ticks = clock_systime_ticks() - priv->lastreset; |
| elapsed = (uint32_t)TICK2MSEC(ticks); |
| |
| if (elapsed < priv->timeout) |
| { |
| /* Return the approximate time until the watchdog timer expiration */ |
| |
| status->timeleft = priv->timeout - elapsed; |
| } |
| else |
| { |
| status->timeleft = 0; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_settimeout |
| * |
| * Description: |
| * Set a new timeout value (and reset the watchdog timer) |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of |
| * the "lower-half" driver state structure. |
| * timeout - The new timeout value in milliseconds. |
| * |
| * Returned Values: |
| * Zero on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int esp32c3_wdt_settimeout(struct watchdog_lowerhalf_s *lower, |
| uint32_t timeout) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| uint16_t rtc_cycles = 0; |
| uint32_t rtc_ms_max = 0; |
| |
| wdinfo("Entry: timeout=%"PRIu32"\n", timeout); |
| DEBUGASSERT(priv); |
| |
| /* Unlock WDT */ |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| /* Write the timeout value */ |
| |
| priv->timeout = timeout; |
| |
| /* Watchdog from Timer Module */ |
| |
| if (priv->peripheral == TIMER) |
| { |
| /* Is this timeout a valid value for Timer's WDT? */ |
| |
| if (timeout == 0 || timeout > MWDT_MAX_TIMEOUT_MS) |
| { |
| wderr("ERROR: Cannot represent timeout=%"PRIu32" > %"PRIu32"\n", |
| timeout, MWDT_MAX_TIMEOUT_MS); |
| return -ERANGE; |
| } |
| else |
| { |
| ESP32C3_WDT_STO(priv->wdt, MWDT_TIMEOUT_MS(timeout), |
| ESP32C3_WDT_STAGE0); |
| ESP32C3_MWDT_UPD_CONF(priv->wdt); |
| } |
| } |
| |
| /* Watchdog from RTC Module */ |
| |
| else |
| { |
| rtc_cycles = ESP32C3_RWDT_CLK(priv->wdt); |
| rtc_ms_max = (RWDT_FULL_STAGE / (uint32_t)rtc_cycles); |
| |
| /* Is this timeout a valid value for RTC WDT? */ |
| |
| if (timeout == 0 || timeout > rtc_ms_max) |
| { |
| wderr("ERROR: Cannot represent timeout=%"PRIu32" > %"PRIu32"\n", |
| timeout, rtc_ms_max); |
| return -ERANGE; |
| } |
| else |
| { |
| timeout = timeout * rtc_cycles; |
| ESP32C3_WDT_STO(priv->wdt, timeout, ESP32C3_WDT_STAGE0); |
| } |
| } |
| |
| /* Reset the wdt */ |
| |
| ESP32C3_WDT_FEED(priv->wdt); |
| |
| /* Lock it again */ |
| |
| ESP32C3_WDT_LOCK(priv->wdt); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_capture |
| * |
| * Description: |
| * Don't reset on watchdog timer timeout; instead, call this user provider |
| * timeout handler. NOTE: Providing handler==NULL will restore the reset |
| * behavior. |
| * |
| * Input Parameters: |
| * lower - A pointer the publicly visible representation of the |
| * "lower-half" driver state structure. |
| * newhandler - The new watchdog expiration function pointer. If this |
| * function pointer is NULL, then the reset-on-expiration |
| * behavior is restored, |
| * |
| * Returned Value: |
| * The previous watchdog expiration function pointer or NULL if there was |
| * no previous function pointer, i.e., if the previous behavior was |
| * reset-on-expiration (NULL is also returned if an error occurs). |
| * |
| ****************************************************************************/ |
| |
| static xcpt_t esp32c3_wdt_capture(struct watchdog_lowerhalf_s *lower, |
| xcpt_t handler) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = |
| (struct esp32c3_wdt_lowerhalf_s *)lower; |
| irqstate_t flags; |
| xcpt_t oldhandler; |
| |
| DEBUGASSERT(priv); |
| |
| wdinfo("Entry: handler=%p\n", handler); |
| |
| /* Get the old handler to return it */ |
| |
| oldhandler = priv->handler; |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| flags = enter_critical_section(); |
| |
| /* Save the new user handler */ |
| |
| priv->handler = handler; |
| |
| /* There is a user callback and the timer has already been started. |
| * The user wants to set a callback after starting the wdt or wants to |
| * change the callback function once a callback has already been settled. |
| */ |
| |
| if (priv->handler != NULL && priv->started == true) |
| { |
| /* Deallocate the previous allocated interrupt |
| * If there is a previous allocated interrupt. |
| */ |
| |
| if (oldhandler != NULL) |
| { |
| ESP32C3_WDT_SETISR(priv->wdt, NULL, NULL); |
| } |
| else |
| { |
| /* If it was previous configured to reset on timeout |
| * then change to interrupt. |
| */ |
| |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_INT); |
| |
| if (priv->peripheral == TIMER) |
| { |
| ESP32C3_MWDT_UPD_CONF(priv->wdt); |
| } |
| } |
| |
| /* Set the lower half handler and enable interrupt */ |
| |
| ESP32C3_WDT_SETISR(priv->wdt, esp32c3_wdt_handler, priv); |
| ESP32C3_WDT_ENABLEINT(priv->wdt); |
| } |
| |
| /* In case the user wants to disable the callback */ |
| |
| else |
| { |
| ESP32C3_WDT_DISABLEINT(priv->wdt); |
| ESP32C3_WDT_SETISR(priv->wdt, NULL, NULL); |
| |
| /* Then configure it to reset on WDT expiration */ |
| |
| if (priv->peripheral == TIMER) |
| { |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_RESET_SYSTEM); |
| ESP32C3_MWDT_UPD_CONF(priv->wdt); |
| } |
| else |
| { |
| ESP32C3_WDT_STG_CONF(priv->wdt, ESP32C3_WDT_STAGE0, |
| ESP32C3_WDT_STAGE_ACTION_RESET_RTC); |
| } |
| } |
| |
| leave_critical_section(flags); |
| ESP32C3_WDT_LOCK(priv->wdt); |
| return oldhandler; |
| } |
| |
| /* Interrupt handling *******************************************************/ |
| |
| static int esp32c3_wdt_handler(int irq, void *context, void *arg) |
| { |
| struct esp32c3_wdt_lowerhalf_s *priv = arg; |
| |
| /* Run the user callback */ |
| |
| priv->handler(irq, context, priv->upper); |
| |
| /* Clear the Interrupt */ |
| |
| ESP32C3_WDT_UNLOCK(priv->wdt); |
| |
| ESP32C3_WDT_ACKINT(priv->wdt); |
| |
| ESP32C3_WDT_LOCK(priv->wdt); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: esp32c3_wdt_initialize |
| * |
| * Description: |
| * Initialize the watchdog timer. The watchdog timer is initialized |
| * and registered as 'devpath'. |
| * |
| * Input Parameters: |
| * devpath - The full path to the watchdog. This should |
| * be of the form /dev/watchdogX |
| * wdt - WDT instance to be initialized. |
| * |
| * Returned Values: |
| * Zero (OK) is returned on success; a negated errno value is returned on |
| * any failure. |
| * |
| ****************************************************************************/ |
| |
| int esp32c3_wdt_initialize(const char *devpath, enum esp32c3_wdt_inst_e wdt) |
| { |
| struct esp32c3_wdt_lowerhalf_s *lower = NULL; |
| |
| DEBUGASSERT(devpath); |
| |
| switch (wdt) |
| { |
| #ifdef CONFIG_ESP32C3_MWDT0 |
| case ESP32C3_WDT_MWDT0: |
| { |
| lower = &g_esp32c3_mwdt0_lowerhalf; |
| lower->peripheral = TIMER; |
| break; |
| } |
| #endif |
| |
| #ifdef CONFIG_ESP32C3_MWDT1 |
| case ESP32C3_WDT_MWDT1: |
| { |
| lower = &g_esp32c3_mwdt1_lowerhalf; |
| lower->peripheral = TIMER; |
| break; |
| } |
| #endif |
| |
| #ifdef CONFIG_ESP32C3_RWDT |
| case ESP32C3_WDT_RWDT: |
| { |
| lower = &g_esp32c3_rwdt_lowerhalf; |
| lower->peripheral = RTC; |
| break; |
| } |
| #endif |
| |
| default: |
| { |
| return -ENODEV; |
| } |
| } |
| |
| /* Initialize the elements of lower half state structure */ |
| |
| lower->handler = NULL; |
| lower->timeout = 0; |
| lower->wdt = esp32c3_wdt_init(wdt); |
| |
| if (lower->wdt == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| lower->started = esp32c3_wdt_is_running(lower->wdt); |
| |
| ESP32C3_WDT_UNLOCK(lower->wdt); |
| |
| /* If it is a Main System Watchdog Timer configure the Prescale to |
| * have a 500us period. |
| */ |
| |
| if (lower->peripheral == TIMER) |
| { |
| ESP32C3_MWDT_PRE(lower->wdt, MWDT_CLK_PRESCALER_VALUE); |
| ESP32C3_MWDT_UPD_CONF(lower->wdt); |
| } |
| |
| ESP32C3_WDT_LOCK(lower->wdt); |
| |
| /* Register the watchdog driver as /dev/watchdogX. If the registration goes |
| * right the returned value from watchdog_register is a pointer to |
| * watchdog_upperhalf_s that can be either used with watchdog_unregister() |
| * or with the handler's arg. |
| */ |
| |
| lower->upper = watchdog_register(devpath, |
| (struct watchdog_lowerhalf_s *)lower); |
| if (lower->upper == NULL) |
| { |
| /* The actual cause of the failure may have been a failure to allocate |
| * perhaps a failure to register the watchdog driver (such as if the |
| * 'devpath' were not unique). We know here but we return EEXIST to |
| * indicate the failure (implying the non-unique devpath). |
| */ |
| |
| return -EEXIST; |
| } |
| |
| return OK; |
| } |