SAMA5 T/C: Can now handle non-constant BOARD_MCK_FREQUENCY. Also now supports methods to attach user interrupt handlers

This commit is contained in:
Gregory Nutt 2014-08-09 10:30:45 -06:00
parent a0d41e32e4
commit 5803fb78b8
4 changed files with 407 additions and 70 deletions

View File

@ -423,6 +423,7 @@
# define SAMA5_HAVE_PMC_PCR_DIV 1 /* Supports conditional compilation */
# define PMC_PCR_DIV_SHIFT (16) /* Bits 16-17: Divisor Value */
# define PMC_PCR_DIV_MASK (3 << PMC_PCR_DIV_SHIFT)
# define PMC_PCR_DIV(n) ((uint32_t)(n) << PMC_PCR_DIV_SHIFT)
# define PMC_PCR_DIV1 (0 << PMC_PCR_DIV_SHIFT) /* Peripheral clock is MCK */
# define PMC_PCR_DIV2 (1 << PMC_PCR_DIV_SHIFT) /* Peripheral clock is MCK/2 */
# define PMC_PCR_DIV4 (2 << PMC_PCR_DIV_SHIFT) /* Peripheral clock is MCK/4 */

View File

@ -58,6 +58,7 @@
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <arch/board/board.h>
#include "up_arch.h"
@ -74,24 +75,6 @@
* Pre-processor Definitions
****************************************************************************/
/* Clocking */
#if BOARD_MCK_FREQUENCY <= SAM_TC_MAXPERCLK
# define TC_FREQUENCY BOARD_MCK_FREQUENCY
# define TC_PCR_DIV PMC_PCR_DIV1
#elif (BOARD_MCK_FREQUENCY >> 1) <= SAM_TC_MAXPERCLK
# define TC_FREQUENCY (BOARD_MCK_FREQUENCY >> 1)
# define TC_PCR_DIV PMC_PCR_DIV2
#elif (BOARD_MCK_FREQUENCY >> 2) <= SAM_TC_MAXPERCLK
# define TC_FREQUENCY (BOARD_MCK_FREQUENCY >> 2)
# define TC_PCR_DIV PMC_PCR_DIV4
#elif (BOARD_MCK_FREQUENCY >> 3) <= SAM_TC_MAXPERCLK
# define TC_FREQUENCY (BOARD_MCK_FREQUENCY >> 3)
# define TC_PCR_DIV PMC_PCR_DIV8
#else
# error Cannot realize TC input frequency
#endif
/* Timer debug is enabled if any timer client is enabled */
#ifndef CONFIG_DEBUG
@ -146,17 +129,19 @@ struct sam_chan_s
{
struct sam_tc_s *tc; /* Parent timer/counter */
uintptr_t base; /* Channel register base address */
tc_handler_t handler; /* Attached interrupt handler */
void *arg; /* Interrupt handler argument */
uint8_t chan; /* Channel number (0, 1, or 2 OR 3, 4, or 5) */
bool inuse; /* True: channel is in use */
};
/* This structure describes on timer/counter */
/* This structure describes one timer/counter */
struct sam_tc_s
{
sem_t exclsem; /* Assures mutually exclusive access to TC */
uintptr_t base; /* Register base address */
uint8_t pid; /* Peripheral ID */
uint8_t pid; /* Peripheral ID/irq number */
uint8_t tc; /* Timer/channel number (0 or 1) */
bool initialized; /* True: Timer data has been initialized */
@ -202,8 +187,26 @@ static inline uint32_t sam_chan_getreg(struct sam_chan_s *chan,
static inline void sam_chan_putreg(struct sam_chan_s *chan,
unsigned int offset, uint32_t regval);
/* Interrupt Handling *******************************************************/
static int sam_tc_interrupt(struct sam_tc_s *tc);
#ifdef CONFIG_SAMA5_TC0
static int sam_tc012_interrupt(int irq, void *context);
#endif
#ifdef CONFIG_SAMA5_TC1
static int sam_tc345_interrupt(int irq, void *context);
#endif
#ifdef CONFIG_SAMA5_TC2
static int sam_tc678_interrupt(int irq, void *context);
#endif
/* Initialization ***********************************************************/
#ifdef SAMA5_HAVE_PMC_PCR_DIV
static int sam_tc_mckdivider(uint32_t mck);
#endif
static int sam_tc_freqdiv(uint32_t ftc, int ndx);
static uint32_t sam_tc_divfreq(uint32_t ftc, int ndx);
static inline struct sam_chan_s *sam_tc_initialize(int channel);
/****************************************************************************
@ -437,28 +440,17 @@ static struct sam_tc_s g_tc678;
/* TC frequency data. This table provides the frequency for each selection of TCCLK */
#define TC_NDIVIDERS 5
#define TC_NDIVIDERS 4
#define TC_NDIVOPTIONS 5
/* This is the list of divider values */
/* This is the list of divider values: divider = (1 << value) */
static const uint16_t g_divider[TC_NDIVIDERS] =
static const uint8_t g_log2divider[TC_NDIVIDERS] =
{
2, /* TIMER_CLOCK1 -> div2 */
8, /* TIMER_CLOCK2 -> div8 */
32, /* TIMER_CLOCK3 -> div32 */
128, /* TIMER_CLOCK4 -> div128 */
TC_FREQUENCY / 32768 /* TIMER_CLOCK5 -> slow clock (not really a divider) */
};
/* This is the list of divided down frequencies */
static const uint32_t g_divfreq[TC_NDIVIDERS] =
{
TC_FREQUENCY / 2, /* TIMER_CLOCK1 -> div2 */
TC_FREQUENCY / 8, /* TIMER_CLOCK2 -> div8 */
TC_FREQUENCY / 32, /* TIMER_CLOCK3 -> div32 */
TC_FREQUENCY / 128, /* TIMER_CLOCK4 -> div128 */
32768 /* TIMER_CLOCK5 -> slow clock */
1, /* TIMER_CLOCK1 -> div2 */
3, /* TIMER_CLOCK2 -> div8 */
5, /* TIMER_CLOCK3 -> div32 */
7 /* TIMER_CLOCK4 -> div128 */
};
/* TC register lookup used by sam_tc_setregister */
@ -699,9 +691,217 @@ static inline void sam_chan_putreg(struct sam_chan_s *chan, unsigned int offset,
putreg32(regval, regaddr);
}
/****************************************************************************
* Interrupt Handling
****************************************************************************/
/****************************************************************************
* Name: sam_tc_interrupt
*
* Description:
* Common timer channel interrupt handling.
*
* Input Parameters:
* tc Timer status instance
*
* Returned Value:
* A pointer to the initialized timer channel structure associated with tc
* and channel. NULL is returned on any failure.
*
* On successful return, the caller holds the tc exclusive access semaphore.
*
****************************************************************************/
static int sam_tc_interrupt(struct sam_tc_s *tc)
{
struct sam_chan_s *chan;
uint32_t sr;
uint32_t imr;
uint32_t pending;
int i;
/* Process interrupts on each channel */
for (i = 0; i < 3; i++)
{
/* Get the handy channel reference */
chan = &tc->channel[i];
/* Get the interrupt status for this channel */
sr = sam_chan_getreg(chan, SAM_TC_SR_OFFSET);
imr = sam_chan_getreg(chan, SAM_TC_IMR_OFFSET);
pending = sr & imr;
/* Are there any pending interrupts for this channel? */
if (pending)
{
/* Yes... if we have pending interrupts then interrupts must be
* enabled and we must have a handler attached.
*/
DEBUGASSERT(chan->handler);
if (chan->handler)
{
/* Execute the callback */
chan->handler(chan, chan->arg, sr);
}
else
{
/* Should never happen */
sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL);
}
}
}
return OK;
}
/****************************************************************************
* Name: sam_tcABC_interrupt
*
* Description:
* Timer block interrupt handlers
*
* Input Parameters:
* chan TC channel structure
* sr The status register value that generated the interrupt
*
* Returned Value:
* A pointer to the initialized timer channel structure associated with tc
* and channel. NULL is returned on any failure.
*
* On successful return, the caller holds the tc exclusive access semaphore.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_TC0
static int sam_tc012_interrupt(int irq, void *context)
{
return sam_tc_interrupt(&g_tc012);
}
#endif
#ifdef CONFIG_SAMA5_TC1
static int sam_tc345_interrupt(int irq, void *context)
{
return sam_tc_interrupt(&g_tc345);
}
#endif
#ifdef CONFIG_SAMA5_TC2
static int sam_tc678_interrupt(int irq, void *context)
{
return sam_tc_interrupt(&g_tc678);
}
#endif
/****************************************************************************
* Initialization
****************************************************************************/
/****************************************************************************
* Name: sam_tc_mckdivider
*
* Description:
* Return the TC clock input divider value. One of n=0..3 corresponding
* to divider values of {1, 2, 4, 8}.
*
* NOTE: The SAMA5D4 has no clock input divider
*
* Input Parameters:
* mck - The MCK frequency to be divider.
*
* Returned Value:
* Log2 of the TC clock divider.
*
****************************************************************************/
#ifdef SAMA5_HAVE_PMC_PCR_DIV
static int sam_tc_mckdivider(uint32_t mck)
{
if (mck <= SAM_TC_MAXPERCLK)
{
return 0;
}
else if ((mck >> 1) <= SAM_TC_MAXPERCLK)
{
return 1;
}
else if ((mck >> 2) <= SAM_TC_MAXPERCLK)
{
return 2;
}
else /* if ((mck >> 3) <= SAM_TC_MAXPERCLK) */
{
DEBUGASSERT((mck >> 3) <= SAM_TC_MAXPERCLK);
return 3;
}
}
#endif
/****************************************************************************
* Name: sam_tc_freqdiv
*
* Description:
* Given the TC input frequency (Ftc) and a divider index, return the value of
* the Ftc divider.
*
* Input Parameters:
* ftc - TC input frequency
* ndx - Divider index
*
* Returned Value:
* The ftc input divider value
*
****************************************************************************/
static int sam_tc_freqdiv(uint32_t ftc, int ndx)
{
/* The final option is to use the SLOW clock */
if (ndx >= TC_NDIVIDERS)
{
return ftc / BOARD_SLOWCLK_FREQUENCY;
}
else
{
return 1 << g_log2divider[ndx];
}
}
/****************************************************************************
* Name: sam_tc_divfreq
*
* Description:
* Given the TC input frequency (Ftc) and a divider index, return the value of
* the divided frequency
*
* Input Parameters:
* ftc - TC input frequency
* ndx - Divider index
*
* Returned Value:
* The divided frequency value
*
****************************************************************************/
static uint32_t sam_tc_divfreq(uint32_t ftc, int ndx)
{
/* The final option is to use the SLOW clock */
if (ndx >= TC_NDIVIDERS)
{
return BOARD_SLOWCLK_FREQUENCY;
}
else
{
return ftc >> g_log2divider[ndx];
}
}
/****************************************************************************
* Name: sam_tc_initialize
*
@ -724,11 +924,12 @@ static inline void sam_chan_putreg(struct sam_chan_s *chan, unsigned int offset,
static inline struct sam_chan_s *sam_tc_initialize(int channel)
{
FAR struct sam_tc_s *tc;
FAR const struct sam_tcconfig_s *tcconfig;
FAR struct sam_chan_s *chan;
FAR const struct sam_chconfig_s *chconfig;
struct sam_tc_s *tc;
const struct sam_tcconfig_s *tcconfig;
struct sam_chan_s *chan;
const struct sam_chconfig_s *chconfig;
irqstate_t flags;
xcpt_t handler;
uint32_t regval;
uint8_t ch;
int i;
@ -742,6 +943,7 @@ static inline struct sam_chan_s *sam_tc_initialize(int channel)
{
tc = &g_tc012;
tcconfig = &g_tc012config;
handler = sam_tc012_interrupt;
}
else
#endif
@ -750,6 +952,7 @@ static inline struct sam_chan_s *sam_tc_initialize(int channel)
{
tc = &g_tc345;
tcconfig = &g_tc345config;
handler = sam_tc345_interrupt;
}
else
#endif
@ -758,6 +961,7 @@ static inline struct sam_chan_s *sam_tc_initialize(int channel)
{
tc = &g_tc678;
tcconfig = &g_tc678config;
handler = sam_tc678_interrupt;
}
else
#endif
@ -820,17 +1024,34 @@ static inline struct sam_chan_s *sam_tc_initialize(int channel)
sam_configpio(chconfig->tiobset);
}
/* Disable and clear all channel interrupts */
sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL);
(void)sam_chan_getreg(chan, SAM_TC_SR_OFFSET);
}
/* Set the maximum TC peripheral clock frequency */
regval = PMC_PCR_PID(tcconfig->pid) | PMC_PCR_CMD | TC_PCR_DIV | PMC_PCR_EN;
regval = PMC_PCR_PID(tcconfig->pid) | PMC_PCR_CMD | PMC_PCR_EN;
#ifdef SAMA5_HAVE_PMC_PCR_DIV
/* Set the MCK divider (if any) */
regval |= PMC_PCR_DIV(sam_tc_mckdivider(BOARD_MCK_FREQUENCY));
#endif
putreg32(regval, SAM_PMC_PCR);
/* Enable clocking to the timer counter */
sam_enableperiph0(tcconfig->pid);
/* Attach the timer interrupt handler and enable the timer interrupts */
(void)irq_attach(tc->pid, handler);
up_enable_irq(tc->pid);
/* Now the channel is initialized */
tc->initialized = true;
@ -980,6 +1201,60 @@ void sam_tc_start(TC_HANDLE handle)
sam_regdump(chan, "Started");
}
/****************************************************************************
* Name: sam_tc_attach
*
* Description:
* Attach or detach an interrupt handler to the timer interrupt. The
* interrupt is detached if the handler argument is NULL.
*
* Input Parameters:
* handle The handle that represents the timer state
* handler The interrupt handler that will be invoked when the interrupt
* condition occurs
* arg An opaque argument that will be provided when the interrupt
* handler callback is executed.
* mask The value of the timer interrupt mask register that defines
* which interrupts should be disabled.
*
* Returned Value:
*
****************************************************************************/
tc_handler_t sam_tc_attach(TC_HANDLE handle, tc_handler_t handler,
void *arg, uint32_t mask)
{
struct sam_chan_s *chan = (struct sam_chan_s *)handle;
tc_handler_t oldhandler;
irqstate_t flags;
DEBUGASSERT(chan);
/* Remember the old interrupt handler and set the new handler */
flags = irqsave();
oldhandler = handler;
chan->handler = handler;
/* Don't enable interrupt if we are detaching no matter what the caller
* says.
*/
if (!handler)
{
arg = NULL;
mask = 0;
}
/* Now enable interrupt as requested */
sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL & ~mask);
sam_chan_putreg(chan, SAM_TC_IER_OFFSET, TC_INT_ALL & mask);
irqrestore(flags);
return oldhandler;
}
/****************************************************************************
* Name: sam_tc_stop
*
@ -1009,9 +1284,9 @@ void sam_tc_stop(TC_HANDLE handle)
*
* Description:
* Set TC_RA, TC_RB, or TC_RB using the provided divisor. The actual
* setting in the register will be the TC input frequency divided by
* setting in the register will be the TC input frequency (Ftc) divided by
* the provided divider (which should derive from the divider returned
* by sam_tc_divider).
* by sam_tc_divisor).
*
* Input Parameters:
* handle Channel handle previously allocated by sam_tc_allocate()
@ -1024,13 +1299,15 @@ void sam_tc_stop(TC_HANDLE handle)
void sam_tc_setregister(TC_HANDLE handle, int reg, unsigned int div)
{
struct sam_chan_s *chan = (struct sam_chan_s *)handle;
uint32_t frequency;
uint32_t regval;
DEBUGASSERT(reg < TC_NREGISTERS);
regval = TC_FREQUENCY / div;
frequency = sam_tc_frequency();
regval = frequency / div;
tcvdbg("Channel %d: Set register %d to %d / %d = %d\n",
chan->chan, reg, TC_FREQUENCY, div, (unsigned int)regval);
chan->chan, reg, frequency, div, (unsigned int)regval);
sam_chan_putreg(chan, g_regoffset[reg], regval);
sam_regdump(chan, "Set register");
@ -1040,8 +1317,9 @@ void sam_tc_setregister(TC_HANDLE handle, int reg, unsigned int div)
* Name: sam_tc_frequency
*
* Description:
* Return the timer input frequency, that is, the MCK frequency divided
* down so that the timer/counter is driven within its maximum frequency.
* Return the timer input frequency (Ftc), that is, the MCK frequency
* divided down so that the timer/counter is driven within its maximum
* frequency.
*
* Input Parameters:
* None
@ -1053,7 +1331,13 @@ void sam_tc_setregister(TC_HANDLE handle, int reg, unsigned int div)
uint32_t sam_tc_frequency(void)
{
return TC_FREQUENCY;
#ifdef SAMA5_HAVE_PMC_PCR_DIV
uint32_t mck = BOARD_MCK_FREQUENCY;
int shift = sam_tc_mckdivider(mck);
return mck >> shift;
#else
return BOARD_MCK_FREQUENCY;
#endif
}
/****************************************************************************
@ -1066,7 +1350,7 @@ uint32_t sam_tc_frequency(void)
* (Ftc / (div * 65536)) <= freq <= (Ftc / dev)
*
* where:
* freq - the desitred frequency
* freq - the desired frequency
* Ftc - The timer/counter input frequency
* div - With DIV being the highest possible value.
*
@ -1083,15 +1367,19 @@ uint32_t sam_tc_frequency(void)
int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks)
{
uint32_t ftc = sam_tc_frequency();
int ndx = 0;
tcvdbg("frequency=%d\n", frequency);
/* Satisfy lower bound */
/* Satisfy lower bound. That is, the value of the divider such that:
*
* frequency >= tc_input_frequency / divider.
*/
while (frequency < (g_divfreq[ndx] >> 16))
while (frequency < (sam_tc_divfreq(ftc, ndx) >> 16))
{
if (++ndx > TC_NDIVIDERS)
if (++ndx > TC_NDIVOPTIONS)
{
/* If no divisor can be found, return -ERANGE */
@ -1100,11 +1388,15 @@ int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks)
}
}
/* Try to maximize DIV while still satisfying upper bound */
/* Try to maximize DIV while still satisfying upper bound. That the
* value of the divider such that:
*
* frequency < tc_input_frequency / divider.
*/
for (; ndx < (TC_NDIVIDERS-1); ndx++)
for (; ndx < (TC_NDIVOPTIONS-1); ndx++)
{
if (frequency > g_divfreq[ndx + 1])
if (frequency > sam_tc_divfreq(ftc, ndx + 1))
{
break;
}
@ -1114,11 +1406,12 @@ int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks)
if (div)
{
tcvdbg("return div=%d\n", g_divider[ndx]);
*div = g_divider[ndx];
uint32_t value = sam_tc_freqdiv(ftc, ndx);
tcvdbg("return div=%lu\n", (unsigned long)value);
*div = value;
}
/* REturn the TCCLKS selection */
/* Return the TCCLKS selection */
if (tcclks)
{

View File

@ -55,12 +55,15 @@
/* The timer/counter and channel arguments to sam_tc_allocate() */
#define TC_CHAN0 0
#define TC_CHAN0 0 /* TC0 */
#define TC_CHAN1 1
#define TC_CHAN2 2
#define TC_CHAN3 3
#define TC_CHAN3 3 /* TC1 */
#define TC_CHAN4 4
#define TC_CHAN5 5
#define TC_CHAN6 6 /* TC2 */
#define TC_CHAN7 7
#define TC_CHAN8 8
/* Register identifier used with sam_tc_setregister */
@ -71,9 +74,21 @@
/****************************************************************************
* Public Types
****************************************************************************/
/* An opaque handle used to represent a timer channel */
typedef void *TC_HANDLE;
/* Timer interrupt callback. When a timer interrup expires, the client will
* receive:
*
* handle - The handle that represents the timer state
* arg - An opaque argument provided when the interrupt was registered
* sr - The value of the timer interrupt status register at the time
* that the interrupt occurred.
*/
typedef void (*tc_handler_t)(TC_HANDLE handle, void *arg, uint32_t sr);
/****************************************************************************
* Public Data
****************************************************************************/
@ -144,6 +159,33 @@ void sam_tc_free(TC_HANDLE handle);
void sam_tc_start(TC_HANDLE handle);
/****************************************************************************
* Name: sam_tc_attach/sam_tc_detach
*
* Description:
* Attach or detach an interrupt handler to the timer interrupt. The
* interrupt is detached if the handler argument is NULL.
*
* Input Parameters:
* handle The handle that represents the timer state
* handler The interrupt handler that will be invoked when the interrupt
* condition occurs
* arg An opaque argument that will be provided when the interrupt
* handler callback is executed. Ignored if handler is NULL.
* mask The value of the timer interrupt mask register that defines
* which interrupts should be disabled. Ignored if handler is
* NULL.
*
* Returned Value:
* The address of the previous handler, if any.
*
****************************************************************************/
tc_handler_t sam_tc_attach(TC_HANDLE handle, tc_handler_t handler,
void *arg, uint32_t mask);
#define sam_tc_detach(h) sam_tc_attach(h, NULL, NULL, 0)
/****************************************************************************
* Name: sam_tc_stop
*
@ -166,7 +208,7 @@ void sam_tc_stop(TC_HANDLE handle);
* Set TC_RA, TC_RB, or TC_RB using the provided divisor. The actual
* setting in the register will be the TC input frequency divided by
* the provided divider (which should derive from the divider returned
* by sam_tc_divider).
* by sam_tc_divisor).
*
*
* Input Parameters:
@ -206,7 +248,7 @@ uint32_t sam_tc_frequency(void);
* (Ftc / (div * 65536)) <= freq <= (Ftc / dev)
*
* where:
* freq - the desitred frequency
* freq - the desired frequency
* Ftc - The timer/counter input frequency
* div - With DIV being the highest possible value.
*

View File

@ -197,7 +197,8 @@ struct watchdog_lowerhalf_s
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C" {
extern "C"
{
#else
#define EXTERN extern
#endif
@ -235,8 +236,8 @@ extern "C" {
*
****************************************************************************/
EXTERN FAR void *watchdog_register(FAR const char *path,
FAR struct watchdog_lowerhalf_s *lower);
FAR void *watchdog_register(FAR const char *path,
FAR struct watchdog_lowerhalf_s *lower);
/****************************************************************************
* Name: watchdog_unregister
@ -253,7 +254,7 @@ EXTERN FAR void *watchdog_register(FAR const char *path,
*
****************************************************************************/
EXTERN void watchdog_unregister(FAR void *handle);
void watchdog_unregister(FAR void *handle);
/****************************************************************************
* Platform-Independent "Lower-Half" Watchdog Driver Interfaces
@ -282,7 +283,7 @@ EXTERN void watchdog_unregister(FAR void *handle);
*
****************************************************************************/
EXTERN int up_wdginitialize(void);
int up_wdginitialize(void);
#undef EXTERN
#ifdef __cplusplus