You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
11 KiB
452 lines
11 KiB
/* |
|
* Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud> |
|
* Copyright (c) 2024 Gerson Fernando Budke <nandojve@gmail.com> |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT atmel_sam0_tc32 |
|
|
|
#include <zephyr/drivers/counter.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/irq.h> |
|
#include <soc.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(counter_sam0_tc32, CONFIG_COUNTER_LOG_LEVEL); |
|
|
|
/* clang-format off */ |
|
|
|
struct counter_sam0_tc32_ch_data { |
|
counter_alarm_callback_t callback; |
|
void *user_data; |
|
}; |
|
|
|
struct counter_sam0_tc32_data { |
|
counter_top_callback_t top_cb; |
|
void *top_user_data; |
|
|
|
struct counter_sam0_tc32_ch_data ch; |
|
}; |
|
|
|
struct counter_sam0_tc32_config { |
|
struct counter_config_info info; |
|
TcCount32 *regs; |
|
const struct pinctrl_dev_config *pcfg; |
|
volatile uint32_t *mclk; |
|
uint32_t mclk_mask; |
|
uint32_t gclk_gen; |
|
uint16_t gclk_id; |
|
uint16_t prescaler; |
|
void (*irq_config_func)(const struct device *dev); |
|
}; |
|
|
|
static void wait_synchronization(TcCount32 *regs) |
|
{ |
|
#if defined(TC_SYNCBUSY_MASK) |
|
/* SYNCBUSY is a register */ |
|
while ((regs->SYNCBUSY.reg & TC_SYNCBUSY_MASK) != 0) { |
|
} |
|
#elif defined(TC_STATUS_SYNCBUSY) |
|
/* SYNCBUSY is a bit */ |
|
while ((regs->STATUS.reg & TC_STATUS_SYNCBUSY) != 0) { |
|
} |
|
#else |
|
#error Unsupported device |
|
#endif |
|
} |
|
|
|
static void read_synchronize_count(TcCount32 *regs) |
|
{ |
|
#if defined(TC_READREQ_RREQ) |
|
regs->READREQ.reg = TC_READREQ_RREQ | |
|
TC_READREQ_ADDR(TC_COUNT32_COUNT_OFFSET); |
|
wait_synchronization(regs); |
|
#elif defined(TC_CTRLBSET_CMD_READSYNC) |
|
regs->CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC; |
|
wait_synchronization(regs); |
|
#else |
|
ARG_UNUSED(regs); |
|
#endif |
|
} |
|
|
|
static int counter_sam0_tc32_start(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
/* |
|
* This will also reset the current counter value if it's |
|
* already running. |
|
*/ |
|
tc->CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER; |
|
wait_synchronization(tc); |
|
return 0; |
|
} |
|
|
|
static int counter_sam0_tc32_stop(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
/* |
|
* The older (pre SAML1x) manuals claim the counter retains its |
|
* value on stop, but this doesn't actually seem to happen. |
|
* The SAML1x manual says it resets, which is what the SAMD21 |
|
* counter actually appears to do. |
|
*/ |
|
tc->CTRLBSET.reg = TC_CTRLBSET_CMD_STOP; |
|
wait_synchronization(tc); |
|
return 0; |
|
} |
|
|
|
static uint32_t counter_sam0_tc32_read(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
read_synchronize_count(tc); |
|
return tc->COUNT.reg; |
|
} |
|
|
|
static int counter_sam0_tc32_get_value(const struct device *dev, |
|
uint32_t *ticks) |
|
{ |
|
*ticks = counter_sam0_tc32_read(dev); |
|
return 0; |
|
} |
|
|
|
static void counter_sam0_tc32_relative_alarm(const struct device *dev, |
|
uint32_t ticks) |
|
{ |
|
struct counter_sam0_tc32_data *data = dev->data; |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
uint32_t before; |
|
uint32_t target; |
|
uint32_t after; |
|
uint32_t max; |
|
|
|
read_synchronize_count(tc); |
|
before = tc->COUNT.reg; |
|
|
|
target = before + ticks; |
|
max = tc->CC[0].reg; |
|
if (target > max) { |
|
target -= max; |
|
} |
|
|
|
tc->CC[1].reg = target; |
|
wait_synchronization(tc); |
|
tc->INTFLAG.reg = TC_INTFLAG_MC1; |
|
|
|
read_synchronize_count(tc); |
|
after = tc->COUNT.reg; |
|
|
|
/* Pending now, so no further checking required */ |
|
if (tc->INTFLAG.bit.MC1) { |
|
goto out_future; |
|
} |
|
|
|
/* |
|
* Check if we missed the interrupt and call the handler |
|
* immediately if we did. |
|
*/ |
|
if (after < target) { |
|
goto out_future; |
|
} |
|
|
|
/* Check wrapped */ |
|
if (target < before && after >= before) { |
|
goto out_future; |
|
} |
|
|
|
counter_alarm_callback_t cb = data->ch.callback; |
|
|
|
tc->INTENCLR.reg = TC_INTENCLR_MC1; |
|
tc->INTFLAG.reg = TC_INTFLAG_MC1; |
|
data->ch.callback = NULL; |
|
|
|
cb(dev, 0, target, data->ch.user_data); |
|
|
|
return; |
|
|
|
out_future: |
|
tc->INTENSET.reg = TC_INTFLAG_MC1; |
|
} |
|
|
|
static int counter_sam0_tc32_set_alarm(const struct device *dev, |
|
uint8_t chan_id, |
|
const struct counter_alarm_cfg *alarm_cfg) |
|
{ |
|
struct counter_sam0_tc32_data *data = dev->data; |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
ARG_UNUSED(chan_id); |
|
|
|
if (alarm_cfg->ticks > tc->CC[0].reg) { |
|
return -EINVAL; |
|
} |
|
|
|
unsigned int key = irq_lock(); |
|
|
|
if (data->ch.callback) { |
|
irq_unlock(key); |
|
return -EBUSY; |
|
} |
|
|
|
data->ch.callback = alarm_cfg->callback; |
|
data->ch.user_data = alarm_cfg->user_data; |
|
|
|
if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) != 0) { |
|
tc->CC[1].reg = alarm_cfg->ticks; |
|
wait_synchronization(tc); |
|
tc->INTFLAG.reg = TC_INTFLAG_MC1; |
|
tc->INTENSET.reg = TC_INTFLAG_MC1; |
|
} else { |
|
counter_sam0_tc32_relative_alarm(dev, alarm_cfg->ticks); |
|
} |
|
|
|
irq_unlock(key); |
|
|
|
return 0; |
|
} |
|
|
|
static int counter_sam0_tc32_cancel_alarm(const struct device *dev, |
|
uint8_t chan_id) |
|
{ |
|
struct counter_sam0_tc32_data *data = dev->data; |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
unsigned int key = irq_lock(); |
|
|
|
ARG_UNUSED(chan_id); |
|
|
|
data->ch.callback = NULL; |
|
tc->INTENCLR.reg = TC_INTENCLR_MC1; |
|
tc->INTFLAG.reg = TC_INTFLAG_MC1; |
|
|
|
irq_unlock(key); |
|
return 0; |
|
} |
|
|
|
static int counter_sam0_tc32_set_top_value(const struct device *dev, |
|
const struct counter_top_cfg *top_cfg) |
|
{ |
|
struct counter_sam0_tc32_data *data = dev->data; |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
int err = 0; |
|
unsigned int key = irq_lock(); |
|
|
|
if (data->ch.callback) { |
|
irq_unlock(key); |
|
return -EBUSY; |
|
} |
|
|
|
if (top_cfg->callback) { |
|
data->top_cb = top_cfg->callback; |
|
data->top_user_data = top_cfg->user_data; |
|
tc->INTENSET.reg = TC_INTFLAG_MC0; |
|
} else { |
|
tc->INTENCLR.reg = TC_INTFLAG_MC0; |
|
} |
|
|
|
tc->CC[0].reg = top_cfg->ticks; |
|
|
|
if (top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { |
|
/* |
|
* Top trigger is on equality of the rising edge only, so |
|
* manually reset it if the counter has missed the new top. |
|
*/ |
|
if (counter_sam0_tc32_read(dev) >= top_cfg->ticks) { |
|
err = -ETIME; |
|
if (top_cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { |
|
tc->CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER; |
|
} |
|
} |
|
} else { |
|
tc->CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER; |
|
} |
|
|
|
wait_synchronization(tc); |
|
|
|
tc->INTFLAG.reg = TC_INTFLAG_MC0; |
|
irq_unlock(key); |
|
return err; |
|
} |
|
|
|
static uint32_t counter_sam0_tc32_get_pending_int(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
return tc->INTFLAG.reg & (TC_INTFLAG_MC0 | TC_INTFLAG_MC1); |
|
} |
|
|
|
static uint32_t counter_sam0_tc32_get_top_value(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
|
|
/* |
|
* Unsync read is safe here because we're not using |
|
* capture mode, so things are only set from the CPU |
|
* end. |
|
*/ |
|
return tc->CC[0].reg; |
|
} |
|
|
|
static void counter_sam0_tc32_isr(const struct device *dev) |
|
{ |
|
struct counter_sam0_tc32_data *data = dev->data; |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
uint8_t status = tc->INTFLAG.reg; |
|
|
|
/* Acknowledge all interrupts */ |
|
tc->INTFLAG.reg = status; |
|
|
|
if (status & TC_INTFLAG_MC1) { |
|
if (data->ch.callback) { |
|
counter_alarm_callback_t cb = data->ch.callback; |
|
|
|
tc->INTENCLR.reg = TC_INTENCLR_MC1; |
|
data->ch.callback = NULL; |
|
|
|
cb(dev, 0, tc->CC[1].reg, data->ch.user_data); |
|
} |
|
} |
|
|
|
if (status & TC_INTFLAG_MC0) { |
|
if (data->top_cb) { |
|
data->top_cb(dev, data->top_user_data); |
|
} |
|
} |
|
} |
|
|
|
static int counter_sam0_tc32_initialize(const struct device *dev) |
|
{ |
|
const struct counter_sam0_tc32_config *const cfg = dev->config; |
|
TcCount32 *tc = cfg->regs; |
|
int retval; |
|
|
|
*cfg->mclk |= cfg->mclk_mask; |
|
|
|
#ifdef MCLK |
|
GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN |
|
| GCLK_PCHCTRL_GEN(cfg->gclk_gen); |
|
#else |
|
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |
|
| GCLK_CLKCTRL_GEN(cfg->gclk_gen) |
|
| GCLK_CLKCTRL_ID(cfg->gclk_id); |
|
#endif |
|
|
|
/* |
|
* In 32 bit mode, NFRQ mode always uses MAX as the counter top, so |
|
* use MFRQ mode which uses CC0 as the top at the expense of only |
|
* having CC1 available for alarms. |
|
*/ |
|
tc->CTRLA.reg = TC_CTRLA_MODE_COUNT32 | |
|
#ifdef TC_CTRLA_WAVEGEN_MFRQ |
|
TC_CTRLA_WAVEGEN_MFRQ | |
|
#endif |
|
cfg->prescaler; |
|
wait_synchronization(tc); |
|
|
|
#ifdef TC_WAVE_WAVEGEN_MFRQ |
|
tc->WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; |
|
#endif |
|
|
|
/* Disable all interrupts */ |
|
tc->INTENCLR.reg = TC_INTENCLR_MASK; |
|
|
|
retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
|
if (retval < 0) { |
|
return retval; |
|
} |
|
|
|
/* Set the initial top as the maximum */ |
|
tc->CC[0].reg = UINT32_MAX; |
|
|
|
cfg->irq_config_func(dev); |
|
|
|
tc->CTRLA.bit.ENABLE = 1; |
|
wait_synchronization(tc); |
|
|
|
/* Stop the counter initially */ |
|
tc->CTRLBSET.reg = TC_CTRLBSET_CMD_STOP; |
|
wait_synchronization(tc); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(counter, counter_sam0_tc32_driver_api) = { |
|
.start = counter_sam0_tc32_start, |
|
.stop = counter_sam0_tc32_stop, |
|
.get_value = counter_sam0_tc32_get_value, |
|
.set_alarm = counter_sam0_tc32_set_alarm, |
|
.cancel_alarm = counter_sam0_tc32_cancel_alarm, |
|
.set_top_value = counter_sam0_tc32_set_top_value, |
|
.get_pending_int = counter_sam0_tc32_get_pending_int, |
|
.get_top_value = counter_sam0_tc32_get_top_value, |
|
}; |
|
|
|
|
|
#define ASSIGNED_CLOCKS_CELL_BY_NAME \ |
|
ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME |
|
|
|
#define SAM0_TC32_PRESCALER(n) \ |
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, prescaler), \ |
|
(DT_INST_PROP(n, prescaler)), (1)) |
|
|
|
#define COUNTER_SAM0_TC32_DEVICE(n) \ |
|
PINCTRL_DT_INST_DEFINE(n); \ |
|
static void counter_sam0_tc32_config_##n(const struct device *dev); \ |
|
static const struct counter_sam0_tc32_config \ |
|
\ |
|
counter_sam0_tc32_dev_config_##n = { \ |
|
.info = { \ |
|
.max_top_value = UINT32_MAX, \ |
|
.freq = SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / \ |
|
SAM0_TC32_PRESCALER(n), \ |
|
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
|
.channels = 1 \ |
|
}, \ |
|
.regs = (TcCount32 *)DT_INST_REG_ADDR(n), \ |
|
.gclk_gen = ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen), \ |
|
.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id), \ |
|
.mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n), \ |
|
.mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit), \ |
|
.prescaler = UTIL_CAT(TC_CTRLA_PRESCALER_DIV, \ |
|
SAM0_TC32_PRESCALER(n)), \ |
|
.irq_config_func = &counter_sam0_tc32_config_##n, \ |
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
|
}; \ |
|
\ |
|
static struct counter_sam0_tc32_data counter_sam0_tc32_dev_data_##n; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, \ |
|
&counter_sam0_tc32_initialize, \ |
|
NULL, \ |
|
&counter_sam0_tc32_dev_data_##n, \ |
|
&counter_sam0_tc32_dev_config_##n, \ |
|
PRE_KERNEL_1, \ |
|
CONFIG_COUNTER_INIT_PRIORITY, \ |
|
&counter_sam0_tc32_driver_api); \ |
|
\ |
|
static void counter_sam0_tc32_config_##n(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), \ |
|
DT_INST_IRQ(n, priority), \ |
|
counter_sam0_tc32_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(COUNTER_SAM0_TC32_DEVICE) |
|
|
|
/* clang-format on */
|
|
|