Exposes the RC register so that the initial value can be set in the device tree. This is useful in the case where the timer generates an event but an interrupt is not required. e.g generate event to sample adc on RC register match. Tested on Atmel SMART SAM E70 Xplained Ultra board Signed-off-by: Marius Scholtz <mariuss@ricelectronics.com>
416 lines
11 KiB
C
416 lines
11 KiB
C
/*
|
|
* Copyright (c) 2021, Piotr Mienkowski
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT atmel_sam_tc
|
|
|
|
/** @file
|
|
* @brief Atmel SAM MCU family counter (TC) driver.
|
|
*
|
|
* This version of the driver uses a single channel to provide a basic 16-bit
|
|
* counter (on SAM4E series the counter is 32-bit). Remaining TC channels could
|
|
* be used in the future to provide additional functionality, e.g. input clock
|
|
* divider configured via DT properties.
|
|
*
|
|
* Remarks:
|
|
* - The driver is not thread safe.
|
|
* - The driver does not implement guard periods.
|
|
* - The driver does not guarantee that short relative alarm will trigger the
|
|
* interrupt immediately and not after the full cycle / counter overflow.
|
|
*
|
|
* Use at your own risk or submit a patch.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <sys/__assert.h>
|
|
#include <sys/util.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
#include <soc.h>
|
|
#include <drivers/counter.h>
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(counter_sam_tc, CONFIG_COUNTER_LOG_LEVEL);
|
|
|
|
#define MAX_ALARMS_PER_TC_CHANNEL 2
|
|
#if defined(CONFIG_SOC_SERIES_SAM4E) || defined(CONFIG_SOC_SERIES_SAM3X)
|
|
#define COUNTER_SAM_TOP_VALUE_MAX UINT32_MAX
|
|
#else
|
|
#define COUNTER_SAM_TOP_VALUE_MAX UINT16_MAX
|
|
#define COUNTER_SAM_16_BIT
|
|
#endif
|
|
|
|
/* Device constant configuration parameters */
|
|
struct counter_sam_dev_cfg {
|
|
struct counter_config_info info;
|
|
Tc *regs;
|
|
uint32_t reg_cmr;
|
|
uint32_t reg_rc;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
const struct soc_gpio_pin *pin_list;
|
|
uint8_t pin_list_size;
|
|
uint8_t clk_sel;
|
|
bool nodivclk;
|
|
uint8_t tc_chan_num;
|
|
uint8_t periph_id[TCCHANNEL_NUMBER];
|
|
};
|
|
|
|
struct counter_sam_alarm_data {
|
|
counter_alarm_callback_t callback;
|
|
void *user_data;
|
|
};
|
|
|
|
/* Device run time data */
|
|
struct counter_sam_dev_data {
|
|
counter_top_callback_t top_cb;
|
|
void *top_user_data;
|
|
|
|
struct counter_sam_alarm_data alarm[MAX_ALARMS_PER_TC_CHANNEL];
|
|
};
|
|
|
|
#define DEV_NAME(dev) ((dev)->name)
|
|
|
|
static const uint32_t sam_tc_input_freq_table[] = {
|
|
#if defined(CONFIG_SOC_SERIES_SAME70) || defined(CONFIG_SOC_SERIES_SAMV71)
|
|
USEC_PER_SEC,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 8,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 32,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 128,
|
|
32768,
|
|
#elif defined(CONFIG_SOC_SERIES_SAM4L)
|
|
USEC_PER_SEC,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 2,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 8,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 32,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 128,
|
|
#else
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 2,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 8,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 32,
|
|
SOC_ATMEL_SAM_MCK_FREQ_HZ / 128,
|
|
32768,
|
|
#endif
|
|
USEC_PER_SEC, USEC_PER_SEC, USEC_PER_SEC,
|
|
};
|
|
|
|
static int counter_sam_tc_start(const struct device *dev)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
tc_ch->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int counter_sam_tc_stop(const struct device *dev)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
tc_ch->TC_CCR = TC_CCR_CLKDIS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int counter_sam_tc_get_value(const struct device *dev, uint32_t *ticks)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
*ticks = tc_ch->TC_CV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int counter_sam_tc_set_alarm(const struct device *dev, uint8_t chan_id,
|
|
const struct counter_alarm_cfg *alarm_cfg)
|
|
{
|
|
struct counter_sam_dev_data *data = dev->data;
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
uint32_t top_value;
|
|
uint32_t alarm_value;
|
|
|
|
__ASSERT_NO_MSG(alarm_cfg->callback != NULL);
|
|
|
|
top_value = tc_ch->TC_RC;
|
|
|
|
if ((top_value != 0) && (alarm_cfg->ticks > top_value)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef COUNTER_SAM_16_BIT
|
|
if ((top_value == 0) && (alarm_cfg->ticks > UINT16_MAX)) {
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
if (data->alarm[chan_id].callback != NULL) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (chan_id == 0) {
|
|
tc_ch->TC_IDR = TC_IDR_CPAS;
|
|
} else {
|
|
tc_ch->TC_IDR = TC_IDR_CPBS;
|
|
}
|
|
|
|
data->alarm[chan_id].callback = alarm_cfg->callback;
|
|
data->alarm[chan_id].user_data = alarm_cfg->user_data;
|
|
|
|
if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) != 0) {
|
|
alarm_value = alarm_cfg->ticks;
|
|
} else {
|
|
alarm_value = tc_ch->TC_CV + alarm_cfg->ticks;
|
|
if (top_value != 0) {
|
|
alarm_value %= top_value;
|
|
}
|
|
}
|
|
|
|
if (chan_id == 0) {
|
|
tc_ch->TC_RA = alarm_value;
|
|
/* Clear interrupt status register */
|
|
(void)tc_ch->TC_SR;
|
|
tc_ch->TC_IER = TC_IER_CPAS;
|
|
} else {
|
|
tc_ch->TC_RB = alarm_value;
|
|
/* Clear interrupt status register */
|
|
(void)tc_ch->TC_SR;
|
|
tc_ch->TC_IER = TC_IER_CPBS;
|
|
}
|
|
|
|
LOG_DBG("set alarm: channel %u, count %u", chan_id, alarm_value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int counter_sam_tc_cancel_alarm(const struct device *dev, uint8_t chan_id)
|
|
{
|
|
struct counter_sam_dev_data *data = dev->data;
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
if (chan_id == 0) {
|
|
tc_ch->TC_IDR = TC_IDR_CPAS;
|
|
tc_ch->TC_RA = 0;
|
|
} else {
|
|
tc_ch->TC_IDR = TC_IDR_CPBS;
|
|
tc_ch->TC_RB = 0;
|
|
}
|
|
|
|
data->alarm[chan_id].callback = NULL;
|
|
data->alarm[chan_id].user_data = NULL;
|
|
|
|
LOG_DBG("cancel alarm: channel %u", chan_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int counter_sam_tc_set_top_value(const struct device *dev,
|
|
const struct counter_top_cfg *top_cfg)
|
|
{
|
|
struct counter_sam_dev_data *data = dev->data;
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
int ret = 0;
|
|
|
|
for (int i = 0; i < MAX_ALARMS_PER_TC_CHANNEL; i++) {
|
|
if (data->alarm[i].callback) {
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
/* Disable the compare interrupt */
|
|
tc_ch->TC_IDR = TC_IDR_CPCS;
|
|
|
|
data->top_cb = top_cfg->callback;
|
|
data->top_user_data = top_cfg->user_data;
|
|
|
|
tc_ch->TC_RC = top_cfg->ticks;
|
|
|
|
if ((top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) != 0) {
|
|
if (tc_ch->TC_CV >= top_cfg->ticks) {
|
|
ret = -ETIME;
|
|
if ((top_cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) != 0) {
|
|
tc_ch->TC_CCR = TC_CCR_SWTRG;
|
|
}
|
|
}
|
|
} else {
|
|
tc_ch->TC_CCR = TC_CCR_SWTRG;
|
|
}
|
|
|
|
/* Enable the compare interrupt */
|
|
tc_ch->TC_IER = TC_IER_CPCS;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t counter_sam_tc_get_top_value(const struct device *dev)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
return tc_ch->TC_RC;
|
|
}
|
|
|
|
static uint32_t counter_sam_tc_get_pending_int(const struct device *dev)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
return tc_ch->TC_SR & tc_ch->TC_IMR;
|
|
}
|
|
|
|
static void counter_sam_tc_isr(const struct device *dev)
|
|
{
|
|
struct counter_sam_dev_data *data = dev->data;
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
uint32_t status;
|
|
|
|
status = tc_ch->TC_SR;
|
|
|
|
if ((status & TC_SR_CPAS) != 0) {
|
|
tc_ch->TC_IDR = TC_IDR_CPAS;
|
|
if (data->alarm[0].callback) {
|
|
counter_alarm_callback_t cb = data->alarm[0].callback;
|
|
|
|
data->alarm[0].callback = NULL;
|
|
cb(dev, 0, tc_ch->TC_RA, data->alarm[0].user_data);
|
|
}
|
|
}
|
|
|
|
if ((status & TC_SR_CPBS) != 0) {
|
|
tc_ch->TC_IDR = TC_IDR_CPBS;
|
|
if (data->alarm[1].callback) {
|
|
counter_alarm_callback_t cb = data->alarm[1].callback;
|
|
|
|
data->alarm[1].callback = NULL;
|
|
cb(dev, 1, tc_ch->TC_RB, data->alarm[1].user_data);
|
|
}
|
|
}
|
|
|
|
if ((status & TC_SR_CPCS) != 0) {
|
|
if (data->top_cb) {
|
|
data->top_cb(dev, data->top_user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int counter_sam_initialize(const struct device *dev)
|
|
{
|
|
const struct counter_sam_dev_cfg *const dev_cfg = dev->config;
|
|
Tc *const tc = dev_cfg->regs;
|
|
TcChannel *tc_ch = &tc->TcChannel[dev_cfg->tc_chan_num];
|
|
|
|
/* Connect pins to the peripheral */
|
|
soc_gpio_list_configure(dev_cfg->pin_list, dev_cfg->pin_list_size);
|
|
|
|
/* Enable channel's clock */
|
|
soc_pmc_peripheral_enable(dev_cfg->periph_id[dev_cfg->tc_chan_num]);
|
|
|
|
/* Clock and Mode Selection */
|
|
tc_ch->TC_CMR = dev_cfg->reg_cmr;
|
|
tc_ch->TC_RC = dev_cfg->reg_rc;
|
|
|
|
#ifdef TC_EMR_NODIVCLK
|
|
if (dev_cfg->nodivclk) {
|
|
tc_ch->TC_EMR = TC_EMR_NODIVCLK;
|
|
}
|
|
#endif
|
|
dev_cfg->irq_config_func(dev);
|
|
|
|
LOG_INF("Device %s initialized", DEV_NAME(dev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct counter_driver_api counter_sam_driver_api = {
|
|
.start = counter_sam_tc_start,
|
|
.stop = counter_sam_tc_stop,
|
|
.get_value = counter_sam_tc_get_value,
|
|
.set_alarm = counter_sam_tc_set_alarm,
|
|
.cancel_alarm = counter_sam_tc_cancel_alarm,
|
|
.set_top_value = counter_sam_tc_set_top_value,
|
|
.get_top_value = counter_sam_tc_get_top_value,
|
|
.get_pending_int = counter_sam_tc_get_pending_int,
|
|
};
|
|
|
|
#define COUNTER_SAM_TC_CMR(n) \
|
|
(TC_CMR_TCCLKS(DT_INST_PROP_OR(n, clk, 0)) \
|
|
| TC_CMR_WAVEFORM_WAVSEL_UP_RC \
|
|
| TC_CMR_WAVE)
|
|
|
|
#define COUNTER_SAM_TC_REG_CMR(n) \
|
|
DT_INST_PROP_OR(n, reg_cmr, COUNTER_SAM_TC_CMR(n))
|
|
|
|
#define COUNTER_SAM_TC_INPUT_FREQUENCY(n) \
|
|
COND_CODE_1(DT_INST_PROP(n, nodivclk), \
|
|
(SOC_ATMEL_SAM_MCK_FREQ_HZ), \
|
|
(sam_tc_input_freq_table[COUNTER_SAM_TC_REG_CMR(n) \
|
|
& TC_CMR_TCCLKS_Msk]))
|
|
|
|
#define COUNTER_SAM_TC_INIT(n) \
|
|
static const struct soc_gpio_pin pins_tc##n[] = ATMEL_SAM_DT_INST_PINS(n); \
|
|
\
|
|
static void counter_##n##_sam_config_func(const struct device *dev); \
|
|
\
|
|
static const struct counter_sam_dev_cfg counter_##n##_sam_config = { \
|
|
.info = { \
|
|
.max_top_value = COUNTER_SAM_TOP_VALUE_MAX, \
|
|
.freq = COUNTER_SAM_TC_INPUT_FREQUENCY(n), \
|
|
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
|
|
.channels = MAX_ALARMS_PER_TC_CHANNEL \
|
|
}, \
|
|
.regs = (Tc *)DT_INST_REG_ADDR(n), \
|
|
.reg_cmr = COUNTER_SAM_TC_REG_CMR(n), \
|
|
.reg_rc = DT_INST_PROP_OR(n, reg_rc, 0), \
|
|
.irq_config_func = &counter_##n##_sam_config_func, \
|
|
.pin_list = pins_tc##n, \
|
|
.pin_list_size = ARRAY_SIZE(pins_tc##n), \
|
|
.nodivclk = DT_INST_PROP(n, nodivclk), \
|
|
.tc_chan_num = DT_INST_PROP_OR(n, channel, 0), \
|
|
.periph_id = DT_INST_PROP(n, peripheral_id), \
|
|
}; \
|
|
\
|
|
static struct counter_sam_dev_data counter_##n##_sam_data; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, counter_sam_initialize, NULL, \
|
|
&counter_##n##_sam_data, &counter_##n##_sam_config, \
|
|
PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY, \
|
|
&counter_sam_driver_api); \
|
|
\
|
|
static void counter_##n##_sam_config_func(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 0, irq), \
|
|
DT_INST_IRQ_BY_IDX(n, 0, priority), \
|
|
counter_sam_tc_isr, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQ_BY_IDX(n, 0, irq)); \
|
|
\
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 1, irq), \
|
|
DT_INST_IRQ_BY_IDX(n, 1, priority), \
|
|
counter_sam_tc_isr, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQ_BY_IDX(n, 1, irq)); \
|
|
\
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 2, irq), \
|
|
DT_INST_IRQ_BY_IDX(n, 2, priority), \
|
|
counter_sam_tc_isr, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQ_BY_IDX(n, 2, irq)); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(COUNTER_SAM_TC_INIT)
|