| /**************************************************************************** |
| * arch/arm/src/sama5/sam_tc.c |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * SPDX-FileCopyrightText: 2016-2017 Gregory Nutt. All rights reserved. |
| * SPDX-FileCopyrightText: 2013-2014 Gregory Nutt. All rights reserved. |
| * SPDX-FileCopyrightText: 2011 Atmel Corporation |
| * SPDX-FileContributor: Gregory Nutt <gnutt@nuttx.orgr> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * 3. Neither the names NuttX nor Atmel nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /* References: |
| * |
| * SAMA5D3 Series Data Sheet |
| * Atmel NoOS sample code. |
| */ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/mutex.h> |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| #include "sam_periphclks.h" |
| #include "hardware/sam_pinmap.h" |
| #include "hardware/sam_pmc.h" |
| #include "sam_pio.h" |
| #include "sam_tc.h" |
| |
| #if defined(CONFIG_SAMA5_TC0) || defined(CONFIG_SAMA5_TC1) || \ |
| defined(CONFIG_SAMA5_TC2) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_DEBUG_TIMER_INFO |
| # undef CONFIG_SAMA5_TC_REGDEBUG |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure describes the static configuration of a TC channel */ |
| |
| struct sam_chconfig_s |
| { |
| uintptr_t base; /* Channel register base address */ |
| pio_pinset_t clkset; /* CLK input PIO configuration */ |
| pio_pinset_t tioaset; /* Output A PIO configuration */ |
| pio_pinset_t tiobset; /* Output B PIO configuration */ |
| }; |
| |
| /* This structure describes the static configuration of a TC */ |
| |
| struct sam_tcconfig_s |
| { |
| uint8_t chfirst; /* First channel number */ |
| |
| /* Channels */ |
| |
| struct sam_chconfig_s channel[3]; |
| }; |
| |
| /* This structure describes one timer counter channel */ |
| |
| struct sam_tc_s; |
| 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 one timer/counter */ |
| |
| struct sam_tc_s |
| { |
| mutex_t lock; /* Assures mutually exclusive access to TC */ |
| uintptr_t base; /* Register base address */ |
| uint8_t pid; /* Peripheral ID/irq number */ |
| uint8_t tc; /* Timer/channel number (0 or 1) */ |
| bool initialized; /* True: Timer data has been initialized */ |
| |
| /* Channels */ |
| |
| struct sam_chan_s channel[3]; |
| |
| /* Debug stuff */ |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| bool wr; /* True:Last was a write */ |
| uint32_t regaddr; /* Last address */ |
| uint32_t regval; /* Last value */ |
| int ntimes; /* Number of times */ |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Low-level helpers ********************************************************/ |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| static void sam_regdump(struct sam_chan_s *chan, const char *msg); |
| static bool sam_checkreg(struct sam_tc_s *tc, bool wr, uint32_t regaddr, |
| uint32_t regval); |
| #else |
| # define sam_regdump(chan,msg) |
| # define sam_checkreg(tc,wr,regaddr,regval) (false) |
| #endif |
| |
| static inline uint32_t sam_chan_getreg(struct sam_chan_s *chan, |
| unsigned int offset); |
| 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, void *arg); |
| #endif |
| #ifdef CONFIG_SAMA5_TC1 |
| static int sam_tc345_interrupt(int irq, void *context, void *arg); |
| #endif |
| #ifdef CONFIG_SAMA5_TC2 |
| static int sam_tc678_interrupt(int irq, void *context, void *arg); |
| #endif |
| |
| /* Initialization ***********************************************************/ |
| |
| #ifdef SAMA5_HAVE_PMC_PCR_DIV |
| static int sam_tc_mckdivider(uint32_t mck); |
| #endif |
| static int sam_tc_freqdiv_lookup(uint32_t ftcin, int ndx); |
| static uint32_t sam_tc_divfreq_lookup(uint32_t ftcin, int ndx); |
| static inline struct sam_chan_s *sam_tc_initialize(int channel); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Static timer configuration */ |
| |
| #ifdef CONFIG_SAMA5_TC0 |
| static const struct sam_tcconfig_s g_tc012config = |
| { |
| .chfirst = 0, |
| .channel = |
| { |
| [0] = |
| { |
| .base = SAM_TC012_CHAN_BASE(0), |
| #ifdef CONFIG_SAMA5_TC0_CLK0 |
| .clkset = PIO_TC0_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOA0 |
| .tioaset = PIO_TC0_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOB0 |
| .tiobset = PIO_TC0_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [1] = |
| { |
| .base = SAM_TC012_CHAN_BASE(1), |
| #ifdef CONFIG_SAMA5_TC0_CLK1 |
| .clkset = PIO_TC1_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOA1 |
| .tioaset = PIO_TC1_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOB1 |
| .tiobset = PIO_TC1_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [2] = |
| { |
| .base = SAM_TC012_CHAN_BASE(2), |
| #ifdef CONFIG_SAMA5_TC0_CLK2 |
| .clkset = PIO_TC2_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOA2 |
| .tioaset = PIO_TC2_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC0_TIOB2 |
| .tiobset = PIO_TC2_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| }, |
| }; |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC1 |
| static const struct sam_tcconfig_s g_tc345config = |
| { |
| .chfirst = 3, |
| .channel = |
| { |
| [0] = |
| { |
| .base = SAM_TC345_CHAN_BASE(3), |
| #ifdef CONFIG_SAMA5_TC1_CLK3 |
| .clkset = PIO_TC3_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOA3 |
| .tioaset = PIO_TC3_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOB3 |
| .tiobset = PIO_TC3_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [1] = |
| { |
| .base = SAM_TC345_CHAN_BASE(4), |
| #ifdef CONFIG_SAMA5_TC1_CLK4 |
| .clkset = PIO_TC4_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOA4 |
| .tioaset = PIO_TC4_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOB4 |
| .tiobset = PIO_TC4_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [2] = |
| { |
| .base = SAM_TC345_CHAN_BASE(5), |
| #ifdef CONFIG_SAMA5_TC1_CLK5 |
| .clkset = PIO_TC5_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOA5 |
| .tioaset = PIO_TC5_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC1_TIOB5 |
| .tiobset = PIO_TC5_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| }, |
| }; |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC2 |
| static const struct sam_tcconfig_s g_tc678config = |
| { |
| .chfirst = 6, |
| .channel = |
| { |
| [0] = |
| { |
| .base = SAM_TC678_CHAN_BASE(6), |
| #ifdef CONFIG_SAMA5_TC2_CLK6 |
| .clkset = PIO_TC6_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOA6 |
| .tioaset = PIO_TC6_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOB6 |
| .tiobset = PIO_TC6_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [1] = |
| { |
| .base = SAM_TC678_CHAN_BASE(7), |
| #ifdef CONFIG_SAMA5_TC2_CLK7 |
| .clkset = PIO_TC7_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOA7 |
| .tioaset = PIO_TC7_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOB7 |
| .tiobset = PIO_TC7_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| [2] = |
| { |
| .base = SAM_TC345_CHAN_BASE(8), |
| #ifdef CONFIG_SAMA5_TC2_CLK8 |
| .clkset = PIO_TC8_CLK, |
| #else |
| .clkset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOA8 |
| .tioaset = PIO_TC8_IOA, |
| #else |
| .tioaset = 0, |
| #endif |
| #ifdef CONFIG_SAMA5_TC2_TIOB8 |
| .tiobset = PIO_TC8_IOB, |
| #else |
| .tiobset = 0, |
| #endif |
| }, |
| }, |
| }; |
| #endif |
| |
| /* Timer/counter state */ |
| |
| #ifdef CONFIG_SAMA5_TC0 |
| static struct sam_tc_s g_tc012 = |
| { |
| .lock = NXMUTEX_INITIALIZER, |
| .base = SAM_TC012_VBASE, |
| .pid = SAM_PID_TC0, |
| .tc = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC1 |
| static struct sam_tc_s g_tc345 = |
| { |
| .lock = NXMUTEX_INITIALIZER, |
| .base = SAM_TC345_VBASE, |
| .pid = SAM_PID_TC1, |
| .tc = 1, |
| }; |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC2 |
| static struct sam_tc_s g_tc678 = |
| { |
| .lock = NXMUTEX_INITIALIZER, |
| .base = SAM_TC678_VBASE, |
| .pid = SAM_PID_TC2, |
| .tc = 2, |
| }; |
| #endif |
| |
| /* TC frequency data. |
| * This table provides the frequency for each selection of TCCLK |
| */ |
| |
| #define TC_NDIVIDERS 4 |
| #define TC_NDIVOPTIONS 5 |
| |
| /* This is the list of divider values: divider = (1 << value) */ |
| |
| static const uint8_t g_log2divider[TC_NDIVIDERS] = |
| { |
| 1, /* TIMER_CLOCK1 -> div2 */ |
| 3, /* TIMER_CLOCK2 -> div8 */ |
| 5, /* TIMER_CLOCK3 -> div32 */ |
| 7 /* TIMER_CLOCK4 -> div128 */ |
| }; |
| |
| /* TC register lookup used by sam_tc_setregister */ |
| |
| #define TC_NREGISTERS 3 |
| |
| static const uint8_t g_regoffset[TC_NREGISTERS] = |
| { |
| SAM_TC_RA_OFFSET, /* Register A */ |
| SAM_TC_RB_OFFSET, /* Register B */ |
| SAM_TC_RC_OFFSET /* Register C */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Low-level Helpers |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sam_regdump |
| * |
| * Description: |
| * Dump all timer/counter channel and global registers |
| * |
| * Input Parameters: |
| * chan - The timer/counter channel state |
| * msg - Message to print with the data |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| static void sam_regdump(struct sam_chan_s *chan, const char *msg) |
| { |
| struct sam_tc_s *tc = chan->tc; |
| uintptr_t base; |
| |
| base = tc->base; |
| tmrinfo("TC%d [%08x]: %s\n", tc->tc, (int)base, msg); |
| tmrinfo(" BMR: %08x QIMR: %08x QISR: %08x WPMR: %08x\n", |
| getreg32(base + SAM_TC_BMR_OFFSET), |
| getreg32(base + SAM_TC_QIMR_OFFSET), |
| getreg32(base + SAM_TC_QISR_OFFSET), |
| getreg32(base + SAM_TC_WPMR_OFFSET)); |
| |
| base = chan->base; |
| tmrinfo("TC%d Channel %d [%08x]: %s\n", tc->tc, |
| chan->chan, (int)base, msg); |
| tmrinfo(" CMR: %08x SSMR: %08x RAB: %08x CV: %08x\n", |
| getreg32(base + SAM_TC_CMR_OFFSET), |
| getreg32(base + SAM_TC_SMMR_OFFSET), |
| getreg32(base + SAM_TC_RAB_OFFSET), |
| getreg32(base + SAM_TC_CV_OFFSET)); |
| tmrinfo(" RA: %08x RB: %08x RC: %08x SR: %08x\n", |
| getreg32(base + SAM_TC_RA_OFFSET), |
| getreg32(base + SAM_TC_RB_OFFSET), |
| getreg32(base + SAM_TC_RC_OFFSET), |
| getreg32(base + SAM_TC_SR_OFFSET)); |
| tmrinfo(" IMR: %08x\n", |
| getreg32(base + SAM_TC_IMR_OFFSET)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sam_checkreg |
| * |
| * Description: |
| * Check if the current register access is a duplicate of the preceding. |
| * |
| * Input Parameters: |
| * tc - The timer/counter peripheral state |
| * wr - True:write access false:read access |
| * regval - The register value associated with the access |
| * regaddr - The address of the register being accessed |
| * |
| * Returned Value: |
| * true: This is the first register access of this type. |
| * false: This is the same as the preceding register access. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| static bool sam_checkreg(struct sam_tc_s *tc, bool wr, uint32_t regaddr, |
| uint32_t regval) |
| { |
| if (wr == tc->wr && /* Same kind of access? */ |
| regaddr == tc->regaddr && /* Same register address? */ |
| regval == tc->regval) /* Same register value? */ |
| { |
| /* Yes, then just keep a count of the number of times we did this. */ |
| |
| tc->ntimes++; |
| return false; |
| } |
| else |
| { |
| /* Did we do the previous operation more than once? */ |
| |
| if (tc->ntimes > 0) |
| { |
| /* Yes... show how many times we did it */ |
| |
| tmrinfo("...[Repeats %d times]...\n", tc->ntimes); |
| } |
| |
| /* Save information about the new access */ |
| |
| tc->wr = wr; |
| tc->regval = regval; |
| tc->regaddr = regaddr; |
| tc->ntimes = 0; |
| } |
| |
| /* Return true if this is the first time that we have done this operation */ |
| |
| return true; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sam_chan_getreg |
| * |
| * Description: |
| * Read an TC channel register |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t sam_chan_getreg(struct sam_chan_s *chan, |
| unsigned int offset) |
| { |
| uint32_t regaddr = chan->base + offset; |
| uint32_t regval = getreg32(regaddr); |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| if (sam_checkreg(chan->tc, false, regaddr, regval)) |
| { |
| tmrinfo("%08x->%08x\n", regaddr, regval); |
| } |
| #endif |
| |
| return regval; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_chan_putreg |
| * |
| * Description: |
| * Write a value to an TC channel register |
| * |
| ****************************************************************************/ |
| |
| static inline void sam_chan_putreg(struct sam_chan_s *chan, |
| unsigned int offset, uint32_t regval) |
| { |
| uint32_t regaddr = chan->base + offset; |
| |
| #ifdef CONFIG_SAMA5_TC_REGDEBUG |
| if (sam_checkreg(chan->tc, true, regaddr, regval)) |
| { |
| tmrinfo("%08" PRIx32 "<-%08" PRIx32 "\n", regaddr, regval); |
| } |
| #endif |
| |
| 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 |
| * mutex. |
| * |
| ****************************************************************************/ |
| |
| 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 |
| * mutex. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_TC0 |
| static int sam_tc012_interrupt(int irq, void *context, void *arg) |
| { |
| return sam_tc_interrupt(&g_tc012); |
| } |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC1 |
| static int sam_tc345_interrupt(int irq, void *context, void *arg) |
| { |
| return sam_tc_interrupt(&g_tc345); |
| } |
| #endif |
| |
| #ifdef CONFIG_SAMA5_TC2 |
| static int sam_tc678_interrupt(int irq, void *context, void *arg) |
| { |
| 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_lookup |
| * |
| * Description: |
| * Given the TC input frequency (Ftcin) and a divider index, return the |
| * value of the Ftcin divider. |
| * |
| * Input Parameters: |
| * ftcin - TC input frequency |
| * ndx - Divider index |
| * |
| * Returned Value: |
| * The Ftcin input divider value |
| * |
| ****************************************************************************/ |
| |
| static int sam_tc_freqdiv_lookup(uint32_t ftcin, int ndx) |
| { |
| /* The final option is to use the SLOW clock */ |
| |
| if (ndx >= TC_NDIVIDERS) |
| { |
| /* Not really a divider. In this case, the board is actually driven |
| * by the 32.768KHz slow clock. This returns a value that looks like |
| * correct divider if MCK were the input. |
| */ |
| |
| return ftcin / BOARD_SLOWCLK_FREQUENCY; |
| } |
| else |
| { |
| return 1 << g_log2divider[ndx]; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_divfreq_lookup |
| * |
| * Description: |
| * Given the TC input frequency (Ftcin) and a divider index, return the |
| * value of the divided frequency |
| * |
| * Input Parameters: |
| * ftcin - TC input frequency |
| * ndx - Divider index |
| * |
| * Returned Value: |
| * The divided frequency value |
| * |
| ****************************************************************************/ |
| |
| static uint32_t sam_tc_divfreq_lookup(uint32_t ftcin, int ndx) |
| { |
| /* The final option is to use the SLOW clock */ |
| |
| if (ndx >= TC_NDIVIDERS) |
| { |
| return BOARD_SLOWCLK_FREQUENCY; |
| } |
| else |
| { |
| return ftcin >> g_log2divider[ndx]; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_initialize |
| * |
| * Description: |
| * There is no global, one-time initialization of timer/counter data |
| * structures. Rather, this function is called each time that a channel |
| * is allocated and, if the channel has not been initialized, it will be |
| * initialized then. |
| * |
| * Input Parameters: |
| * channel TC channel number (see TC_CHANx definitions) |
| * |
| * 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 |
| * mutex. |
| * |
| ****************************************************************************/ |
| |
| static inline struct sam_chan_s *sam_tc_initialize(int channel) |
| { |
| 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; |
| int ret; |
| |
| /* Select the timer/counter and get the index associated with the |
| * channel. |
| */ |
| |
| #ifdef CONFIG_SAMA5_TC0 |
| if (channel >= 0 && channel < 3) |
| { |
| tc = &g_tc012; |
| tcconfig = &g_tc012config; |
| handler = sam_tc012_interrupt; |
| } |
| else |
| #endif |
| #ifdef CONFIG_SAMA5_TC1 |
| if (channel >= 3 && channel < 6) |
| { |
| tc = &g_tc345; |
| tcconfig = &g_tc345config; |
| handler = sam_tc345_interrupt; |
| } |
| else |
| #endif |
| #ifdef CONFIG_SAMA5_TC2 |
| if (channel >= 6 && channel < 9) |
| { |
| tc = &g_tc678; |
| tcconfig = &g_tc678config; |
| handler = sam_tc678_interrupt; |
| } |
| else |
| #endif |
| { |
| /* Timer/counter is not invalid or not enabled */ |
| |
| tmrerr("ERROR: Bad channel number: %d\n", channel); |
| return NULL; |
| } |
| |
| /* Has the timer counter been initialized. We have to be careful here |
| * because there is no mutex protection. |
| */ |
| |
| flags = enter_critical_section(); |
| if (!tc->initialized) |
| { |
| /* Initialize the channels */ |
| |
| for (i = 0, ch = tcconfig->chfirst; i < SAM_TC_NCHANNELS; i++) |
| { |
| tmrerr("ERROR: Initializing TC%d channel %d\n", tc->tc, ch); |
| |
| /* Initialize the channel data structure */ |
| |
| chan = &tc->channel[i]; |
| chconfig = &tcconfig->channel[i]; |
| |
| chan->tc = tc; |
| chan->base = chconfig->base; |
| chan->chan = ch++; |
| |
| /* Configure channel input/output pins */ |
| |
| if (chconfig->clkset) |
| { |
| /* Configure clock input pin */ |
| |
| sam_configpio(chconfig->clkset); |
| } |
| |
| if (chconfig->tioaset) |
| { |
| /* Configure output A pin */ |
| |
| sam_configpio(chconfig->tioaset); |
| } |
| |
| if (chconfig->tiobset) |
| { |
| /* Configure output B pin */ |
| |
| sam_configpio(chconfig->tiobset); |
| } |
| |
| /* Disable and clear all channel interrupts */ |
| |
| sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL); |
| sam_chan_getreg(chan, SAM_TC_SR_OFFSET); |
| } |
| |
| /* Set the maximum TC peripheral clock frequency */ |
| |
| regval = PMC_PCR_PID(tc->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(tc->pid); |
| |
| /* Attach the timer interrupt handler and enable the timer interrupts */ |
| |
| irq_attach(tc->pid, handler, NULL); |
| up_enable_irq(tc->pid); |
| |
| /* Now the channel is initialized */ |
| |
| tc->initialized = true; |
| } |
| |
| /* Get exclusive access to the timer/count data structure */ |
| |
| ret = nxmutex_lock(&tc->lock); |
| if (ret < 0) |
| { |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| leave_critical_section(flags); |
| |
| /* Get the requested channel structure */ |
| |
| chan = &tc->channel[channel - tcconfig->chfirst]; |
| |
| /* Is it available? */ |
| |
| if (chan->inuse) |
| { |
| /* No.. return a failure */ |
| |
| tmrerr("ERROR: Channel %d is in-use\n", channel); |
| nxmutex_unlock(&tc->lock); |
| return NULL; |
| } |
| |
| /* Mark the channel "inuse" */ |
| |
| chan->inuse = true; |
| |
| /* And return the channel with the mutex locked */ |
| |
| sam_regdump(chan, "Initialized"); |
| return chan; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sam_tc_allocate |
| * |
| * Description: |
| * Configures a Timer Counter to operate in the given mode. The timer is |
| * stopped after configuration and must be restarted with sam_tc_start(). |
| * All the interrupts of the timer are also disabled. |
| * |
| * Input Parameters: |
| * channel TC channel number (see TC_CHANx definitions) |
| * mode Operating mode (TC_CMR value). |
| * |
| * Returned Value: |
| * On success, a non-NULL handle value is returned. This handle may be |
| * used with subsequent timer/counter interfaces to manage the timer. A |
| * NULL handle value is returned on a failure. |
| * |
| ****************************************************************************/ |
| |
| TC_HANDLE sam_tc_allocate(int channel, int mode) |
| { |
| struct sam_chan_s *chan; |
| |
| /* Initialize the timer/counter data (if necessary) and get exclusive |
| * access to the requested channel. |
| */ |
| |
| tmrinfo("channel=%d mode=%08x\n", channel, mode); |
| |
| chan = sam_tc_initialize(channel); |
| if (chan) |
| { |
| /* Disable TC clock */ |
| |
| sam_chan_putreg(chan, SAM_TC_CCR_OFFSET, TC_CCR_CLKDIS); |
| |
| /* Disable channel interrupts */ |
| |
| sam_chan_putreg(chan, SAM_TC_IDR_OFFSET, TC_INT_ALL); |
| |
| /* Clear and pending status */ |
| |
| sam_chan_getreg(chan, SAM_TC_SR_OFFSET); |
| |
| /* And set the requested mode */ |
| |
| sam_chan_putreg(chan, SAM_TC_CMR_OFFSET, mode); |
| sam_regdump(chan, "Allocated"); |
| nxmutex_unlock(&chan->tc->lock); |
| } |
| |
| /* Return an opaque reference to the channel */ |
| |
| tmrinfo("Returning %p\n", chan); |
| return (TC_HANDLE)chan; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_free |
| * |
| * Description: |
| * Release the handle previously allocated by sam_tc_allocate(). |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void sam_tc_free(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| |
| tmrinfo("Freeing %p channel=%d inuse=%d\n", chan, chan->chan, chan->inuse); |
| DEBUGASSERT(chan && chan->inuse); |
| |
| /* Make sure that interrupts are detached and disabled and that the channel |
| * is stopped and disabled. |
| */ |
| |
| sam_tc_detach(handle); |
| sam_tc_stop(handle); |
| |
| /* Mark the channel as available */ |
| |
| chan->inuse = false; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_start |
| * |
| * Description: |
| * Reset and Start the TC Channel. Enables the timer clock and performs a |
| * software reset to start the counting. |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| void sam_tc_start(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| |
| tmrinfo("Starting channel %d inuse=%d\n", chan->chan, chan->inuse); |
| DEBUGASSERT(chan && chan->inuse); |
| |
| /* Read the SR to clear any pending interrupts on this channel */ |
| |
| sam_chan_getreg(chan, SAM_TC_SR_OFFSET); |
| |
| /* Then enable the timer (by setting the CLKEN bit). Setting SWTRIG |
| * will also reset the timer counter and starting the timer. |
| */ |
| |
| sam_chan_putreg(chan, SAM_TC_CCR_OFFSET, TC_CCR_CLKEN | TC_CCR_SWTRG); |
| sam_regdump(chan, "Started"); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_stop |
| * |
| * Description: |
| * Stop TC Channel. Disables the timer clock, stopping the counting. |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| void sam_tc_stop(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| |
| tmrinfo("Stopping channel %d inuse=%d\n", chan->chan, chan->inuse); |
| DEBUGASSERT(chan && chan->inuse); |
| |
| sam_chan_putreg(chan, SAM_TC_CCR_OFFSET, TC_CCR_CLKDIS); |
| sam_regdump(chan, "Stopped"); |
| } |
| |
| /**************************************************************************** |
| * 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 = enter_critical_section(); |
| oldhandler = chan->handler; |
| chan->handler = handler; |
| |
| /* Don't enable interrupt if we are detaching no matter what the caller |
| * says. |
| */ |
| |
| if (!handler) |
| { |
| arg = NULL; |
| mask = 0; |
| } |
| |
| chan->arg = arg; |
| |
| /* 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); |
| leave_critical_section(flags); |
| |
| return oldhandler; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_getpending |
| * |
| * Description: |
| * Return the current contents of the interrupt status register, clearing |
| * all pending interrupts. |
| * |
| * Input Parameters: |
| * handle The handle that represents the timer state |
| * |
| * Returned Value: |
| * The value of the channel interrupt status register. |
| * |
| ****************************************************************************/ |
| |
| uint32_t sam_tc_getpending(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| DEBUGASSERT(chan); |
| return sam_chan_getreg(chan, SAM_TC_SR_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_setregister |
| * |
| * Description: |
| * Set TC_REGA, TC_REGB, or TC_REGC register. |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * regid One of {TC_REGA, TC_REGB, or TC_REGC} |
| * regval Then value to set in the register |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void sam_tc_setregister(TC_HANDLE handle, int regid, uint32_t regval) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| |
| DEBUGASSERT(chan && regid < TC_NREGISTERS); |
| |
| tmrinfo("Channel %d: Set register RC%d to %08lx\n", |
| chan->chan, regid, (unsigned long)regval); |
| |
| sam_chan_putreg(chan, g_regoffset[regid], regval); |
| sam_regdump(chan, "set register"); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_getregister |
| * |
| * Description: |
| * Get the current value of the TC_REGA, TC_REGB, or TC_REGC register. |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * regid One of {TC_REGA, TC_REGB, or TC_REGC} |
| * |
| * Returned Value: |
| * The value of the specified register. |
| * |
| ****************************************************************************/ |
| |
| uint32_t sam_tc_getregister(TC_HANDLE handle, int regid) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| DEBUGASSERT(chan); |
| return sam_chan_getreg(chan, g_regoffset[regid]); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_getcounter |
| * |
| * Description: |
| * Return the current value of the timer counter register |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * |
| * Returned Value: |
| * The current value of the timer counter register for this channel. |
| * |
| ****************************************************************************/ |
| |
| uint32_t sam_tc_getcounter(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| DEBUGASSERT(chan); |
| return sam_chan_getreg(chan, SAM_TC_CV_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_infreq |
| * |
| * Description: |
| * Return the timer input frequency (Ftcin), that is, the MCK frequency |
| * divided down so that the timer/counter is driven within its maximum |
| * frequency. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * The timer input frequency. |
| * |
| ****************************************************************************/ |
| |
| uint32_t sam_tc_infreq(void) |
| { |
| #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 |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_divfreq |
| * |
| * Description: |
| * Return the divided timer input frequency that is currently driving the |
| * the timer counter. |
| * |
| * Input Parameters: |
| * handle Channel handle previously allocated by sam_tc_allocate() |
| * |
| * Returned Value: |
| * The timer counter frequency. |
| * |
| ****************************************************************************/ |
| |
| uint32_t sam_tc_divfreq(TC_HANDLE handle) |
| { |
| struct sam_chan_s *chan = (struct sam_chan_s *)handle; |
| uint32_t ftcin = sam_tc_infreq(); |
| uint32_t regval; |
| int tcclks; |
| |
| DEBUGASSERT(chan); |
| |
| /* Get the TC_CMR register contents for this channel and extract the |
| * TCCLKS index. |
| */ |
| |
| regval = sam_chan_getreg(chan, SAM_TC_CMR_OFFSET); |
| tcclks = (regval & TC_CMR_TCCLKS_MASK) >> TC_CMR_TCCLKS_SHIFT; |
| |
| /* And use the TCCLKS index to calculate the timer counter frequency */ |
| |
| return sam_tc_divfreq_lookup(ftcin, tcclks); |
| } |
| |
| /**************************************************************************** |
| * Name: sam_tc_divisor |
| * |
| * Description: |
| * Finds the best MCK divisor given the timer frequency and MCK. The |
| * result is guaranteed to satisfy the following equation: |
| * |
| * (Ftcin / (div * 65536)) <= freq <= (Ftcin / dev) |
| * |
| * where: |
| * freq - the desired frequency |
| * Ftcin - The timer/counter input frequency |
| * div - With DIV being the highest possible value. |
| * |
| * Input Parameters: |
| * frequency Desired timer frequency. |
| * div Divisor value. |
| * tcclks TCCLKS field value for divisor. |
| * |
| * Returned Value: |
| * Zero (OK) if a proper divisor has been found, otherwise a negated errno |
| * value indicating the nature of the failure. |
| * |
| ****************************************************************************/ |
| |
| int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks) |
| { |
| uint32_t ftcin = sam_tc_infreq(); |
| int ndx = 0; |
| |
| tmrinfo("frequency=%" PRIu32 "\n", frequency); |
| |
| /* Satisfy lower bound. That is, the value of the divider such that: |
| * |
| * frequency >= (tc_input_frequency * 65536) / divider. |
| */ |
| |
| while (frequency < (sam_tc_divfreq_lookup(ftcin, ndx) >> 16)) |
| { |
| if (++ndx > TC_NDIVOPTIONS) |
| { |
| /* If no divisor can be found, return -ERANGE */ |
| |
| tmrerr("ERROR: Lower bound search failed\n"); |
| return -ERANGE; |
| } |
| } |
| |
| /* 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_NDIVOPTIONS - 1); ndx++) |
| { |
| if (frequency > sam_tc_divfreq_lookup(ftcin, ndx + 1)) |
| { |
| break; |
| } |
| } |
| |
| /* Return the divider value */ |
| |
| if (div) |
| { |
| uint32_t value = sam_tc_freqdiv_lookup(ftcin, ndx); |
| tmrinfo("return div=%lu\n", (unsigned long)value); |
| *div = value; |
| } |
| |
| /* Return the TCCLKS selection */ |
| |
| if (tcclks) |
| { |
| tmrinfo("return tcclks=%08lx\n", (unsigned long)TC_CMR_TCCLKS(ndx)); |
| *tcclks = TC_CMR_TCCLKS(ndx); |
| } |
| |
| return OK; |
| } |
| |
| #endif /* CONFIG_SAMA5_TC0 || CONFIG_SAMA5_TC1 || CONFIG_SAMA5_TC2 */ |