SAMA5: Fix bugs in timer/counter interrupts and one-shot timer

This commit is contained in:
Gregory Nutt 2014-08-10 10:47:38 -06:00
parent 6324df44e8
commit 320707fdfa
9 changed files with 120 additions and 55 deletions

View File

@ -69,6 +69,8 @@
#ifdef CONFIG_SAMA5_TC_DEBUG #ifdef CONFIG_SAMA5_TC_DEBUG
# define tcdbg dbg # define tcdbg dbg
# define tcvdbg vdbg # define tcvdbg vdbg
# define tcdbg lldbg
# define tcvdbg llvdbg
# define tclldbg lldbg # define tclldbg lldbg
# define tcllvdbg llvdbg # define tcllvdbg llvdbg
#else #else

View File

@ -102,32 +102,44 @@
* level up. * level up.
* *
* Input Parameters: * Input Parameters:
* handle - The handle that represents the timer state * tch - The handle that represents the timer state
* arg - An opaque argument provided when the interrupt was registered * arg - An opaque argument provided when the interrupt was registered
* sr - The value of the timer interrupt status register at the time * sr - The value of the timer interrupt status register at the time
* that the interrupt occurred. * that the interrupt occurred.
* *
* Returned Value: * Returned Value:
* None * None
* *
****************************************************************************/ ****************************************************************************/
static void sam_oneshot_handler(TC_HANDLE handle, void *arg, uint32_t sr) static void sam_oneshot_handler(TC_HANDLE tch, void *arg, uint32_t sr)
{ {
struct sam_oneshot_s *oneshot = (struct sam_oneshot_s *)arg; struct sam_oneshot_s *oneshot = (struct sam_oneshot_s *)arg;
oneshot_handler_t oneshot_handler;
void *oneshot_arg;
tcllvdbg("Expired...\n");
DEBUGASSERT(oneshot && oneshot->handler); DEBUGASSERT(oneshot && oneshot->handler);
/* The clock was stopped, but not disabled when the RC match occurred. /* The clock was stopped, but not disabled when the RC match occurred.
* Disable the TC now and disable any further interrupts. * Disable the TC now and disable any further interrupts.
*/ */
sam_tc_attach(oneshot->handler, NULL, NULL, 0); sam_tc_attach(oneshot->tch, NULL, NULL, 0);
sam_tc_stop(oneshot->handle); sam_tc_stop(oneshot->tch);
/* Forward the event */ /* The timer is no longer running */
oneshot->running = false; oneshot->running = false;
oneshot->handler(oneshot->arg);
/* Forward the event, clearing out any vestiges */
oneshot_handler = oneshot->handler;
oneshot->handler = NULL;
oneshot_arg = oneshot->arg;
oneshot->arg = NULL;
oneshot_handler(oneshot_arg);
} }
/**************************************************************************** /****************************************************************************
@ -210,8 +222,8 @@ int sam_oneshot_initialize(struct sam_oneshot_s *oneshot, int chan,
TC_CMR_ASWTRG_NONE | TC_CMR_BCPB_NONE | TC_CMR_BCPC_NONE | TC_CMR_ASWTRG_NONE | TC_CMR_BCPB_NONE | TC_CMR_BCPC_NONE |
TC_CMR_BEEVT_NONE | TC_CMR_BSWTRG_NONE); TC_CMR_BEEVT_NONE | TC_CMR_BSWTRG_NONE);
oneshot->handle = sam_tc_allocate(chan, cmr); oneshot->tch = sam_tc_allocate(chan, cmr);
if (!oneshot->handle) if (!oneshot->tch)
{ {
tcdbg("ERROR: Failed to allocate timer channel %d\n", chan); tcdbg("ERROR: Failed to allocate timer channel %d\n", chan);
return -EBUSY; return -EBUSY;
@ -270,6 +282,11 @@ int sam_oneshot_start(struct sam_oneshot_s *oneshot, oneshot_handler_t handler,
(void)sam_oneshot_cancel(oneshot, NULL); (void)sam_oneshot_cancel(oneshot, NULL);
} }
/* Save the new handler and its argument */
oneshot->handler = handler;
oneshot->arg = arg;
/* We configured the counter to run with an LSB of the specified /* We configured the counter to run with an LSB of the specified
* resolution. We now must need need to set RC to the number * resolution. We now must need need to set RC to the number
* of resolution units corresponding to the requested delay. * of resolution units corresponding to the requested delay.
@ -278,23 +295,24 @@ int sam_oneshot_start(struct sam_oneshot_s *oneshot, oneshot_handler_t handler,
usec = (uint64_t)ts->tv_sec * 1000000 + (uint64_t)(ts->tv_nsec / 1000); usec = (uint64_t)ts->tv_sec * 1000000 + (uint64_t)(ts->tv_nsec / 1000);
regval = usec / oneshot->resolution; regval = usec / oneshot->resolution;
tcdbg("usec=%llu regval=%08llx\n", usec, regval); tcdbg("usec=%lu regval=%08lx\n",
(unsigned long)usec, (unsigned long)regval);
DEBUGASSERT(regval <= UINT32_MAX); DEBUGASSERT(regval <= UINT32_MAX);
/* Set up to receive the callback when the interrupt occurs */ /* Set up to receive the callback when the interrupt occurs */
(void)sam_tc_attach(oneshot->handle, sam_oneshot_handler, oneshot, (void)sam_tc_attach(oneshot->tch, sam_oneshot_handler, oneshot,
TC_INT_CPCS); TC_INT_CPCS);
/* Set RC so that an event will be triggered when TC_CV register counts /* Set RC so that an event will be triggered when TC_CV register counts
* up to RC. * up to RC.
*/ */
sam_tc_setregister(oneshot->handle, TC_REGC, regval); sam_tc_setregister(oneshot->tch, TC_REGC, (uint32_t)regval);
/* Start the counter */ /* Start the counter */
sam_tc_start(oneshot->handle); sam_tc_start(oneshot->tch);
/* Enable interrupts. We should get the callback when the interrupt /* Enable interrupts. We should get the callback when the interrupt
* occurs. * occurs.
@ -311,16 +329,21 @@ int sam_oneshot_start(struct sam_oneshot_s *oneshot, oneshot_handler_t handler,
* Description: * Description:
* Cancel the oneshot timer and return the time remaining on the timer. * Cancel the oneshot timer and return the time remaining on the timer.
* *
* NOTE: This function may execute at a high rate with no timer running (as
* when pre-emption is enabled and disabled).
*
* Input Parameters: * Input Parameters:
* oneshot Caller allocated instance of the oneshot state structure. This * oneshot Caller allocated instance of the oneshot state structure. This
* structure must have been previously initialized via a call to * structure must have been previously initialized via a call to
* sam_oneshot_initialize(); * sam_oneshot_initialize();
* ts The location in which to return the time remaining on the * ts The location in which to return the time remaining on the
* oneshot timer. * oneshot timer. A time of zero is returned if the timer is
* not running.
* *
* Returned Value: * Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned * Zero (OK) is returned on success. A call to up_timer_cancel() when
* on failure. * the timer is not active should also return success; a negated errno
* value is returned on any failure.
* *
****************************************************************************/ ****************************************************************************/
@ -332,16 +355,35 @@ int sam_oneshot_cancel(struct sam_oneshot_s *oneshot, struct timespec *ts)
uint32_t usec; uint32_t usec;
uint32_t sec; uint32_t sec;
/* Get the timer counter and rc registers and stop the counter. If the /* Was the timer running? */
* counter expires while we are doing this, the counter clock will be
* stopped, but the clock will not be disabled.
*
* REVISIT: Will the counter value be reset to zero?
*/
flags = irqsave(); flags = irqsave();
count = sam_tc_getcounter(oneshot->handle); if (!oneshot->running)
rc = sam_tc_getregister(oneshot->handle, TC_REGC); {
/* No.. Just return zero timer remaining and successful cancellation.
* This function may execute at a high rate with no timer running
* (as when pre-emption is enabled and disabled).
*/
ts->tv_sec = 0;
ts->tv_nsec = 0;
irqrestore(flags);
return OK;
}
/* Yes.. Get the timer counter and rc registers and stop the counter. If
* the counter expires while we are doing this, the counter clock will be
* stopped, but the clock will not be disabled.
*
* NOTE: This is not documented, but I have observed in this case that the
* counter register freezes at a value equal to the RC register. The
* following logic depends on this fact.
*/
tcvdbg("Canceling...\n");
count = sam_tc_getcounter(oneshot->tch);
rc = sam_tc_getregister(oneshot->tch, TC_REGC);
/* Now we can disable the interrupt and stop the timer. /* Now we can disable the interrupt and stop the timer.
* *
@ -350,11 +392,13 @@ int sam_oneshot_cancel(struct sam_oneshot_s *oneshot, struct timespec *ts)
* clock will be disabled, so I am hoping not. * clock will be disabled, so I am hoping not.
*/ */
DEBUGASSERT(count > 0 || (sam_tc_getpending(oneshot->handle) & TC_INT_CPCS) == 0); DEBUGASSERT(count > 0 || (sam_tc_getpending(oneshot->tch) & TC_INT_CPCS) == 0);
sam_tc_attach(oneshot->handle, NULL, NULL, 0); sam_tc_attach(oneshot->tch, NULL, NULL, 0);
sam_tc_stop(oneshot->handle); sam_tc_stop(oneshot->tch);
oneshot->running = false; oneshot->running = false;
oneshot->handler = NULL;
oneshot->arg = NULL;
irqrestore(flags); irqrestore(flags);
/* The total time remaining is the difference */ /* The total time remaining is the difference */

View File

@ -73,13 +73,15 @@ typedef void (*oneshot_handler_t)(void *arg);
struct sam_oneshot_s struct sam_oneshot_s
{ {
uint8_t chan; /* The timer/counter in use */ uint8_t chan; /* The timer/counter in use */
bool running; /* True: the timer is running */ volatile bool running; /* True: the timer is running */
uint16_t resolution; /* Timer resolution in microseconds */ uint16_t resolution; /* Timer resolution in microseconds */
uint32_t divisor; /* TC divisor derived from resolution */ uint32_t divisor; /* TC divisor derived from resolution */
TC_HANDLE handle; /* Handle returned by sam_tc_initialize() */ TC_HANDLE tch; /* Handle returned by
oneshot_handler_t handler; /* Oneshot expiration callback */ * sam_tc_initialize() */
void *arg; /* The argument that will accompany the callback */ volatile oneshot_handler_t handler; /* Oneshot expiration callback */
volatile void *arg; /* The argument that will accompany
* the callback */
}; };
/**************************************************************************** /****************************************************************************
@ -151,16 +153,21 @@ int sam_oneshot_start(struct sam_oneshot_s *oneshot, oneshot_handler_t handler,
* Description: * Description:
* Cancel the oneshot timer and return the time remaining on the timer. * Cancel the oneshot timer and return the time remaining on the timer.
* *
* NOTE: This function may execute at a high rate with no timer running (as
* when pre-emption is enabled and disabled).
*
* Input Parameters: * Input Parameters:
* oneshot Caller allocated instance of the oneshot state structure. This * oneshot Caller allocated instance of the oneshot state structure. This
* structure must have been previously initialized via a call to * structure must have been previously initialized via a call to
* sam_oneshot_initialize(); * sam_oneshot_initialize();
* ts The location in which to return the time remaining on the * ts The location in which to return the time remaining on the
* oneshot timer. * oneshot timer. A time of zero is returned if the timer is
* not running.
* *
* Returned Value: * Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned * Zero (OK) is returned on success. A call to up_timer_cancel() when
* on failure. * the timer is not active should also return success; a negated errno
* value is returned on any failure.
* *
****************************************************************************/ ****************************************************************************/

View File

@ -1264,8 +1264,8 @@ tc_handler_t sam_tc_attach(TC_HANDLE handle, tc_handler_t handler,
/* Remember the old interrupt handler and set the new handler */ /* Remember the old interrupt handler and set the new handler */
flags = irqsave(); flags = irqsave();
oldhandler = handler; oldhandler = chan->handler;
chan->handler = handler; chan->handler = handler;
/* Don't enable interrupt if we are detaching no matter what the caller /* Don't enable interrupt if we are detaching no matter what the caller
@ -1278,6 +1278,8 @@ tc_handler_t sam_tc_attach(TC_HANDLE handle, tc_handler_t handler,
mask = 0; mask = 0;
} }
chan->arg = arg;
/* Now enable interrupt as requested */ /* Now enable interrupt as requested */
sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL & ~mask); sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL & ~mask);

View File

@ -81,13 +81,13 @@ typedef void *TC_HANDLE;
/* Timer interrupt callback. When a timer interrupt expires, the client will /* Timer interrupt callback. When a timer interrupt expires, the client will
* receive: * receive:
* *
* handle - The handle that represents the timer state * tch - The handle that represents the timer state
* arg - An opaque argument provided when the interrupt was registered * arg - An opaque argument provided when the interrupt was registered
* sr - The value of the timer interrupt status register at the time * sr - The value of the timer interrupt status register at the time
* that the interrupt occurred. * that the interrupt occurred.
*/ */
typedef void (*tc_handler_t)(TC_HANDLE handle, void *arg, uint32_t sr); typedef void (*tc_handler_t)(TC_HANDLE tch, void *arg, uint32_t sr);
/**************************************************************************** /****************************************************************************
* Public Data * Public Data

View File

@ -151,6 +151,8 @@
#ifdef CONFIG_SAMA5_TC_DEBUG #ifdef CONFIG_SAMA5_TC_DEBUG
# define tcdbg dbg # define tcdbg dbg
# define tcvdbg vdbg # define tcvdbg vdbg
# define tcdbg lldbg
# define tcvdbg llvdbg
# define tclldbg lldbg # define tclldbg lldbg
# define tcllvdbg llvdbg # define tcllvdbg llvdbg
#else #else
@ -204,6 +206,7 @@ struct sam_tickless_s g_tickless;
static void sam_oneshot_handler(void *arg) static void sam_oneshot_handler(void *arg)
{ {
tcllvdbg("Expired...\n");
sched_timer_expiration(); sched_timer_expiration();
} }
@ -315,6 +318,9 @@ int up_timer_gettime(FAR struct timespec *ts)
* that up_timer_start() and the remaining time of zero should be * that up_timer_start() and the remaining time of zero should be
* returned. * returned.
* *
* NOTE: This function may execute at a high rate with no timer running (as
* when pre-emption is enabled and disabled).
*
* Provided by platform-specific code and called from the RTOS base code. * Provided by platform-specific code and called from the RTOS base code.
* *
* Input Parameters: * Input Parameters:
@ -322,8 +328,9 @@ int up_timer_gettime(FAR struct timespec *ts)
* if the timer is not active. * if the timer is not active.
* *
* Returned Value: * Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on * Zero (OK) is returned on success. A call to up_timer_cancel() when
* any failure. * the timer is not active should also return success; a negated errno
* value is returned on any failure.
* *
* Assumptions: * Assumptions:
* May be called from interrupt level handling or from the normal tasking * May be called from interrupt level handling or from the normal tasking

View File

@ -435,8 +435,8 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer,
* *
* OXTABS - primarily a full-screen terminal optimisation * OXTABS - primarily a full-screen terminal optimisation
* ONOEOT - Unix interoperability hack * ONOEOT - Unix interoperability hack
* OLCUC - Not specified by Posix * OLCUC - Not specified by POSIX
* ONOCR - low-speed interactive optimisation * ONOCR - low-speed interactive optimisation
*/ */
} }
@ -447,7 +447,6 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer,
{ {
ret = uart_putxmitchar(dev, '\r', oktoblock); ret = uart_putxmitchar(dev, '\r', oktoblock);
} }
#endif #endif
/* Put the character into the transmit buffer */ /* Put the character into the transmit buffer */

View File

@ -1042,6 +1042,9 @@ int up_timer_gettime(FAR struct timespec *ts);
* that up_timer_start() and the remaining time of zero should be * that up_timer_start() and the remaining time of zero should be
* returned. * returned.
* *
* NOTE: This function may execute at a high rate with no timer running (as
* when pre-emption is enabled and disabled).
*
* Provided by platform-specific code and called from the RTOS base code. * Provided by platform-specific code and called from the RTOS base code.
* *
* Input Parameters: * Input Parameters:
@ -1049,8 +1052,9 @@ int up_timer_gettime(FAR struct timespec *ts);
* if the timer is not active. * if the timer is not active.
* *
* Returned Value: * Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on * Zero (OK) is returned on success. A call to up_timer_cancel() when
* any failure. * the timer is not active should also return success; a negated errno
* value is returned on any failure.
* *
* Assumptions: * Assumptions:
* May be called from interrupt level handling or from the normal tasking * May be called from interrupt level handling or from the normal tasking

View File

@ -73,8 +73,8 @@ static void rawoutstream_putc(FAR struct lib_outstream_s *this, int ch)
} }
/* The only expected error is EINTR, meaning that the write operation /* The only expected error is EINTR, meaning that the write operation
* was awakened by a signal. Zero would not be a valid return value * was awakened by a signal. Zero or values > 1 would not be valid
* from write(). * return values from write().
*/ */
DEBUGASSERT(nwritten < 0); DEBUGASSERT(nwritten < 0);