diff --git a/arch/arm/src/sama5/chip/sam_pmc.h b/arch/arm/src/sama5/chip/sam_pmc.h index 222149d7c9..19940593db 100644 --- a/arch/arm/src/sama5/chip/sam_pmc.h +++ b/arch/arm/src/sama5/chip/sam_pmc.h @@ -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 */ diff --git a/arch/arm/src/sama5/sam_tc.c b/arch/arm/src/sama5/sam_tc.c index 8f3865c864..c4a89aef7b 100644 --- a/arch/arm/src/sama5/sam_tc.c +++ b/arch/arm/src/sama5/sam_tc.c @@ -58,6 +58,7 @@ #include #include +#include #include #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) { diff --git a/arch/arm/src/sama5/sam_tc.h b/arch/arm/src/sama5/sam_tc.h index 50585f7a19..b767158ef2 100644 --- a/arch/arm/src/sama5/sam_tc.h +++ b/arch/arm/src/sama5/sam_tc.h @@ -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. *