SAMV7: Add support for PCK6 as a timer/counter clock source

This commit is contained in:
Gregory Nutt
2015-12-04 10:49:20 -06:00
parent 9d769f6056
commit 52601d9da8
8 changed files with 258 additions and 131 deletions
+9 -9
View File
@@ -163,13 +163,13 @@ uint32_t sam_pck_configure(enum pckid_e pckid, enum pckid_clksrc_e clksrc,
{
pres = 1;
}
else if (pres > (PMC_PCK_PRES_MASK + 1))
else if (pres > 256)
{
pres = PMC_PCK_PRES_MASK + 1;
pres = 256;
}
regval |= PMC_PCK_PRES(pres - 1);
actual = frequency / pres;
actual = clkin / pres;
#else
/* The the larger smallest divisor that does not exceed the requested
@@ -226,29 +226,29 @@ uint32_t sam_pck_configure(enum pckid_e pckid, enum pckid_clksrc_e clksrc,
switch (pckid)
{
#ifdef PIO_PMC_PCK0
case PCK0:
putreg32(PMC_PCK0, SAM_PMC_SCDR);
#ifdef PIO_PMC_PCK0
(void)sam_configpio(PIO_PMC_PCK0);
#endif
putreg32(regval, SAM_PMC_PCK0);
break;
#endif
#ifdef PIO_PMC_PCK1
case PCK1:
putreg32(PMC_PCK1, SAM_PMC_SCDR);
#ifdef PIO_PMC_PCK1
(void)sam_configpio(PIO_PMC_PCK1);
#endif
putreg32(regval, SAM_PMC_PCK1);
break;
#endif
#ifdef PIO_PMC_PCK2
case PCK2:
putreg32(PMC_PCK2, SAM_PMC_SCDR);
#ifdef PIO_PMC_PCK2
(void)sam_configpio(PIO_PMC_PCK2);
#endif
putreg32(regval, SAM_PMC_PCK2);
break;
#endif
default:
return -EINVAL;
+1 -1
View File
@@ -303,7 +303,7 @@
# define PMC_PCK_CSS_MCK (4 << PMC_PCK_CSS_SHIFT) /* Master Clock */
#define PMC_PCK_PRES_SHIFT (4) /* Bits 4-11: Programmable Clock Prescaler */
#define PMC_PCK_PRES_MASK (0xff << PMC_PCK_PRES_SHIFT)
# define PMC_PCK_PRES(n) ((uint32_t)((n)-1) << PMC_PCK_PRES_SHIFT) /* n=1..256 */
# define PMC_PCK_PRES(n) ((uint32_t)(n) << PMC_PCK_PRES_SHIFT) /* n=0..255 */
/* PMC Interrupt Enable Register, PMC Interrupt Disable Register, PMC Status Register,
* and PMC Interrupt Mask Register common bit-field definitions
+6 -6
View File
@@ -120,7 +120,7 @@ int sam_freerun_initialize(struct sam_freerun_s *freerun, int chan,
uint16_t resolution)
{
uint32_t frequency;
uint32_t divisor;
uint32_t actual;
uint32_t cmr;
int ret;
@@ -133,20 +133,20 @@ int sam_freerun_initialize(struct sam_freerun_s *freerun, int chan,
/* The pre-calculate values to use when we start the timer */
ret = sam_tc_divisor(frequency, &divisor, &cmr);
ret = sam_tc_clockselect(frequency, &cmr, &actual);
if (ret < 0)
{
tcdbg("ERROR: sam_tc_divisor failed: %d\n", ret);
tcdbg("ERROR: sam_tc_clockselect failed: %d\n", ret);
return ret;
}
tcvdbg("frequency=%lu, divisor=%u, cmr=%08lx\n",
(unsigned long)frequency, (unsigned long)divisor,
tcvdbg("frequency=%lu, actual=%lu, cmr=%08lx\n",
(unsigned long)frequency, (unsigned long)actual,
(unsigned long)cmr);
/* Allocate the timer/counter and select its mode of operation
*
* TC_CMR_TCCLKS - Returned by sam_tc_divisor
* TC_CMR_TCCLKS - Returned by sam_tc_clockselect
* TC_CMR_CLKI=0 - Not inverted
* TC_CMR_BURST_NONE - Not gated by an external signal
* TC_CMR_CPCSTOP=0 - Don't stop the clock on an RC compare event
+1 -1
View File
@@ -3720,7 +3720,7 @@ FAR struct can_dev_s *sam_mcan_initialize(int port)
* use PCK5 to derive bit rate.
*/
regval = PMC_PCK_PRES(CONFIG_SAMV7_MCAN_CLKSRC_PRESCALER) | SAMV7_MCAN_CLKSRC;
regval = PMC_PCK_PRES(CONFIG_SAMV7_MCAN_CLKSRC_PRESCALER - 1) | SAMV7_MCAN_CLKSRC;
putreg32(regval, SAM_PMC_PCK5);
/* Enable PCK5 */
+6 -6
View File
@@ -160,7 +160,7 @@ int sam_oneshot_initialize(struct sam_oneshot_s *oneshot, int chan,
uint16_t resolution)
{
uint32_t frequency;
uint32_t divisor;
uint32_t actual;
uint32_t cmr;
int ret;
@@ -173,20 +173,20 @@ int sam_oneshot_initialize(struct sam_oneshot_s *oneshot, int chan,
/* The pre-calculate values to use when we start the timer */
ret = sam_tc_divisor(frequency, &divisor, &cmr);
ret = sam_tc_clockselect(frequency, &cmr, &actual);
if (ret < 0)
{
tcdbg("ERROR: sam_tc_divisor failed: %d\n", ret);
tcdbg("ERROR: sam_tc_clockselect failed: %d\n", ret);
return ret;
}
tcvdbg("frequency=%lu, divisor=%lu, cmr=%08lx\n",
(unsigned long)frequency, (unsigned long)divisor,
tcvdbg("frequency=%lu, actual=%lu, cmr=%08lx\n",
(unsigned long)frequency, (unsigned long)actual,
(unsigned long)cmr);
/* Allocate the timer/counter and select its mode of operation
*
* TC_CMR_TCCLKS - Returned by sam_tc_divisor
* TC_CMR_TCCLKS - Returned by sam_tc_clockselect
* TC_CMR_CLKI=0 - Not inverted
* TC_CMR_BURST_NONE - Not gated by an external signal
* TC_CMR_CPCSTOP=1 - Stop the clock on an RC compare event
+17 -17
View File
@@ -139,13 +139,13 @@ uint32_t sam_pck_configure(enum pckid_e pckid, enum pckid_clksrc_e clksrc,
{
pres = 1;
}
else if (pres > (PMC_PCK_PRES_MASK + 1))
else if (pres > 256)
{
pres = PMC_PCK_PRES_MASK + 1;
pres = 256;
}
regval |= PMC_PCK_PRES(pres - 1);
actual = frequency / pres;
actual = clkin / pres;
/* Disable the programmable clock, configure the PCK output pin, then set
* the selected configuration.
@@ -153,61 +153,61 @@ uint32_t sam_pck_configure(enum pckid_e pckid, enum pckid_clksrc_e clksrc,
switch (pckid)
{
#ifdef GPIO_PMC_PCK0
case PCK0:
putreg32(PMC_PCK0, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK0
(void)cam_configgpio(GPIO_PMC_PCK0);
#endif
putreg32(regval, SAM_PMC_PCK0);
break;
#endif
#ifdef GPIO_PMC_PCK1
case PCK1:
putreg32(PMC_PCK1, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK1
(void)cam_configgpio(GPIO_PMC_PCK1);
#endif
putreg32(regval, SAM_PMC_PCK1);
break;
#endif
#ifdef GPIO_PMC_PCK2
case PCK2:
putreg32(PMC_PCK2, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK2
(void)cam_configgpio(GPIO_PMC_PCK2);
#endif
putreg32(regval, SAM_PMC_PCK2);
break;
#endif
#ifdef GPIO_PMC_PCK3
case PCK3:
putreg32(PMC_PCK3, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK3
(void)cam_configgpio(GPIO_PMC_PCK3);
#endif
putreg32(regval, SAM_PMC_PCK3);
break;
#endif
#ifdef GPIO_PMC_PCK4
case PCK4:
putreg32(PMC_PCK4, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK4
(void)cam_configgpio(GPIO_PMC_PCK4);
#endif
putreg32(regval, SAM_PMC_PCK4);
break;
#endif
#ifdef GPIO_PMC_PCK5
case PCK5:
putreg32(PMC_PCK5, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK5
(void)cam_configgpio(GPIO_PMC_PCK5);
#endif
putreg32(regval, SAM_PMC_PCK5);
break;
#endif
#ifdef GPIO_PMC_PCK6
case PCK6:
putreg32(PMC_PCK6, SAM_PMC_SCDR);
#ifdef GPIO_PMC_PCK6
(void)cam_configgpio(GPIO_PMC_PCK6);
#endif
putreg32(regval, SAM_PMC_PCK6);
break;
#endif
default:
return -EINVAL;
+214 -88
View File
@@ -66,6 +66,7 @@
#include "chip/sam_pinmap.h"
#include "chip/sam_pmc.h"
#include "sam_gpio.h"
#include "sam_pck.h"
#include "sam_tc.h"
#if defined(CONFIG_SAMV7_TC0) || defined(CONFIG_SAMV7_TC1) || \
@@ -139,6 +140,14 @@ struct sam_tc_s
#endif
};
/* Type of the MCK divider lookup table */
struct mck_divsrc_s
{
uint8_t log2; /* Log2 of the divider */
uint8_t tcclks; /* CMR TCCLCKS setting */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@@ -197,8 +206,10 @@ static int sam_tc11_interrupt(int irq, void *context);
/* Initialization ***********************************************************/
static int sam_tc_freqdiv_lookup(uint32_t ftcin, int ndx);
static uint32_t sam_tc_divfreq_lookup(uint32_t ftcin, int ndx);
static uint32_t sam_tc_mckfreq_lookup(uint32_t ftcin, int ndx);
static inline uint32_t sam_tc_tcclks_lookup(int ndx);
static int sam_tc_mcksrc(uint32_t frequency, uint32_t *tcclks,
uint32_t *actual);
static inline struct sam_chan_s *sam_tc_initialize(int channel);
/****************************************************************************
@@ -546,17 +557,18 @@ static struct sam_tc_s g_tc901;
/* TC frequency data. This table provides the frequency for each selection of TCCLK */
#define TC_NDIVIDERS 4
#define TC_NDIVOPTIONS 5
#define TC_NDIVIDERS 3
#define TC_NDIVOPTIONS 4
/* This is the list of divider values: divider = (1 << value) */
static const uint8_t g_log2divider[TC_NDIVIDERS] =
static struct mck_divsrc_s g_log2divider[TC_NDIVOPTIONS] =
{
1, /* TIMER_CLOCK1 -> PCK6 REVISIT! Was MCK/2 */
3, /* TIMER_CLOCK2 -> MCK/8 */
5, /* TIMER_CLOCK3 -> MCK/32 */
7 /* TIMER_CLOCK4 -> MCK/128 */
/* TIMER_CLOCK1(0) -> PCK6 */
{3, 1}, /* TIMER_CLOCK2(1) -> MCK/8 */
{5, 2}, /* TIMER_CLOCK3(2) -> MCK/32 */
{7, 3}, /* TIMER_CLOCK4(3) -> MCK/128 */
{0, 4}, /* TIMER_CLOCK5(4) -> SLCK (No MCK divider) */
};
/* TC register lookup used by sam_tc_setregister */
@@ -948,46 +960,12 @@ static int sam_tc11_interrupt(int irq, void *context)
****************************************************************************/
/****************************************************************************
* 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
* Name: sam_tc_mckfreq_lookup
*
* Description:
* Given the TC input frequency (Ftcin) and a divider index, return the
* value of the divided frequency
* value of the divided frequency. The slow clock source is treated as
* though it were a divided down MCK frequency.
*
* Input Parameters:
* ftcin - TC input frequency
@@ -998,7 +976,7 @@ static int sam_tc_freqdiv_lookup(uint32_t ftcin, int ndx)
*
****************************************************************************/
static uint32_t sam_tc_divfreq_lookup(uint32_t ftcin, int ndx)
static uint32_t sam_tc_mckfreq_lookup(uint32_t ftcin, int ndx)
{
/* The final option is to use the SLOW clock */
@@ -1008,10 +986,113 @@ static uint32_t sam_tc_divfreq_lookup(uint32_t ftcin, int ndx)
}
else
{
return ftcin >> g_log2divider[ndx];
return ftcin >> g_log2divider[ndx].log2;
}
}
/****************************************************************************
* Name: sam_tc_tcclks_lookup
*
* Description:
* Given the TC input frequency (Ftcin) and a divider index, return the
* value of the divided frequency. The slow clock source is treated as
* though it were a divided down MCK frequency.
*
* Input Parameters:
* ftcin - TC input frequency
* ndx - Divider index
*
* Returned Value:
* The divided frequency value
*
****************************************************************************/
static inline uint32_t sam_tc_tcclks_lookup(int ndx)
{
unsigned int index = g_log2divider[ndx].tcclks;
return TC_CMR_TCCLKS(index);
}
/****************************************************************************
* Name: sam_tc_mcksrc
*
* 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 / div)
*
* 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.
* tcclks TCCLKS field value for divisor.
* actual The actual freqency of the MCK
*
* Returned Value:
* Zero (OK) if a proper divisor has been found, otherwise a negated errno
* value indicating the nature of the failure.
*
****************************************************************************/
static int sam_tc_mcksrc(uint32_t frequency, uint32_t *tcclks,
uint32_t *actual)
{
uint32_t fselect;
uint32_t fnext;
int ndx = 0;
tcvdbg("frequency=%d\n", frequency);
/* Satisfy lower bound. That is, the value of the divider such that:
*
* frequency >= (tc_input_frequency * 65536) / divider.
*/
for (; ndx < TC_NDIVIDERS; ndx++)
{
fselect = sam_tc_mckfreq_lookup(BOARD_MCK_FREQUENCY, ndx);
if (frequency >= (fselect >> 16))
{
break;
}
}
if (ndx >= TC_NDIVIDERS)
{
/* If no divisor can be found, return -ERANGE */
tcdbg("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_NDIVIDERS; ndx++)
{
fnext = sam_tc_mckfreq_lookup(BOARD_MCK_FREQUENCY, ndx + 1);
if (frequency > fnext)
{
break;
}
fselect = fnext;
}
/* Return the actual frequency and the TCCLKS selection */
*actual = fselect;
*tcclks = sam_tc_tcclks_lookup(ndx);
return OK;
}
/****************************************************************************
* Name: sam_tc_initialize
*
@@ -1538,11 +1619,24 @@ uint32_t sam_tc_divfreq(TC_HANDLE handle)
/* And use the TCCLKS index to calculate the timer counter frequency */
return sam_tc_divfreq_lookup(BOARD_MCK_FREQUENCY, tcclks);
if (tcclks == 0)
{
/* The tcclks value of 0 corresponds to PCK6 */
return sam_pck_frequency(PCK6);
}
else
{
/* Values of tcclks in the range {1,5} correspond to the divided
* down MCK or to the slow clock.
*/
return sam_tc_mckfreq_lookup(BOARD_MCK_FREQUENCY, tcclks - 1);
}
}
/****************************************************************************
* Name: sam_tc_divisor
* Name: sam_tc_clockselect
*
* Description:
* Finds the best MCK divisor given the timer frequency and MCK. The
@@ -1557,8 +1651,8 @@ uint32_t sam_tc_divfreq(TC_HANDLE handle)
*
* Input Parameters:
* frequency Desired timer frequency.
* div Divisor value.
* tcclks TCCLKS field value for divisor.
* actual The actual freqency of the MCK
*
* Returned Value:
* Zero (OK) if a proper divisor has been found, otherwise a negated errno
@@ -1566,67 +1660,99 @@ uint32_t sam_tc_divfreq(TC_HANDLE handle)
*
****************************************************************************/
int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks)
int sam_tc_clockselect(uint32_t frequency, uint32_t *tcclks,
uint32_t *actual)
{
int ndx = 0;
uint32_t mck_actual;
uint32_t mck_tcclks;
uint32_t mck_error;
int ret;
tcvdbg("frequency=%d\n", frequency);
/* Try to satisfy the requested frequency with the MCK or slow clock */
/* On other chips, TCCLCKS==0 corresponded to MCK/2. But for the SAMV7,
* this is PCK6. That will need to be handled differently.
*/
#warning REVISIT: PCK6 clock source not yet supported
ndx++;
/* Satisfy lower bound. That is, the value of the divider such that:
*
* frequency >= (tc_input_frequency * 65536) / divider.
*/
while (frequency < (sam_tc_divfreq_lookup(BOARD_MCK_FREQUENCY, ndx) >> 16))
ret = sam_tc_mcksrc(frequency, &mck_tcclks, &mck_actual);
if (ret < 0)
{
if (++ndx > TC_NDIVOPTIONS)
{
/* If no divisor can be found, return -ERANGE */
mck_error = UINT32_MAX;
}
else
{
/* Get the absolute value of the frequency error */
tcdbg("Lower bound search failed\n");
return -ERANGE;
if (mck_actual > frequency)
{
mck_error = mck_actual - frequency;
}
else
{
mck_error = frequency - mck_actual;
}
}
/* Try to maximize DIV while still satisfying upper bound. That the
* value of the divider such that:
*
* frequency < tc_input_frequency / divider.
*/
/* See if we do better with PCK6 */
for (; ndx < (TC_NDIVOPTIONS-1); ndx++)
if (sam_pck_isenabled(PCK6))
{
if (frequency > sam_tc_divfreq_lookup(BOARD_MCK_FREQUENCY, ndx + 1))
uint32_t pck6_actual;
uint32_t pck6_error;
/* Get the absolute value of the frequency error */
pck6_actual = sam_pck_frequency(PCK6);
if (pck6_actual > frequency)
{
break;
pck6_error = pck6_actual - frequency;
}
else
{
pck6_error = frequency - pck6_actual;
}
/* Return the PCK6 selection if the error is smaller */
if (pck6_error < mck_error)
{
/* Return the PCK selection */
if (actual)
{
tcvdbg("return actual=%lu\n", (unsigned long)fselect);
*actual = pck6_actual;
}
/* Return the TCCLKS selection */
if (tcclks)
{
tcvdbg("return tcclks=%08lx\n", (unsigned long)TC_CMR_TCCLKS_PCK6);
*tcclks = TC_CMR_TCCLKS_PCK6;
}
/* Return success */
return OK;
}
}
/* Return the divider value */
/* Return the MCK/slow clock selection */
if (div)
if (actual)
{
uint32_t value = sam_tc_freqdiv_lookup(BOARD_MCK_FREQUENCY, ndx);
tcvdbg("return div=%lu\n", (unsigned long)value);
*div = value;
tcvdbg("return actual=%lu\n", (unsigned long)mck_actual);
*actual = mck_actual;
}
/* Return the TCCLKS selection */
if (tcclks)
{
tcvdbg("return tcclks=%08lx\n", (unsigned long)TC_CMR_TCCLKS(ndx));
*tcclks = TC_CMR_TCCLKS(ndx);
tcvdbg("return tcclks=%08lx\n", (unsigned long)mck_tcclks);
*tcclks = mck_tcclks;
}
return OK;
/* Return success */
return ret;
}
#endif /* CONFIG_SAMV7_TC0 || CONFIG_SAMV7_TC1 || CONFIG_SAMV7_TC2 || CONFIG_SAMV7_TC3 */
+4 -3
View File
@@ -331,7 +331,7 @@ uint32_t sam_tc_infreq(void);
uint32_t sam_tc_divfreq(TC_HANDLE handle);
/****************************************************************************
* Name: sam_tc_divisor
* Name: sam_tc_clockselect
*
* Description:
* Finds the best MCK divisor given the timer frequency and MCK. The
@@ -346,8 +346,8 @@ uint32_t sam_tc_divfreq(TC_HANDLE handle);
*
* Input Parameters:
* frequency Desired timer frequency.
* div Divisor value.
* tcclks TCCLKS field value for divisor.
* actual The actual freqency of the MCK
*
* Returned Value:
* Zero (OK) if a proper divisor has been found, otherwise a negated errno
@@ -355,7 +355,8 @@ uint32_t sam_tc_divfreq(TC_HANDLE handle);
*
****************************************************************************/
int sam_tc_divisor(uint32_t frequency, uint32_t *div, uint32_t *tcclks);
int sam_tc_clockselect(uint32_t frequency, uint32_t *tcclks,
uint32_t *actual);
#undef EXTERN
#ifdef __cplusplus