/* * Copyright (c) 2019 Derek Hageman * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam0_tc32 #include #include #include #include LOG_MODULE_REGISTER(counter_sam0_tc32, CONFIG_COUNTER_LOG_LEVEL); 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; #ifdef MCLK volatile uint32_t *mclk; uint32_t mclk_mask; uint16_t gclk_id; #else uint32_t pm_apbcmask; uint16_t gclk_clkctrl_id; #endif uint16_t prescaler; void (*irq_config_func)(struct device *dev); }; #define DEV_CFG(dev) ((const struct counter_sam0_tc32_config *const) \ (dev)->config_info) #define DEV_DATA(dev) ((struct counter_sam0_tc32_data *const) \ (dev)->driver_data) 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(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); 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(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); 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(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; read_synchronize_count(tc); return tc->COUNT.reg; } static int counter_sam0_tc32_get_value(struct device *dev, uint32_t *ticks) { *ticks = counter_sam0_tc32_read(dev); return 0; } static void counter_sam0_tc32_relative_alarm(struct device *dev, uint32_t ticks) { struct counter_sam0_tc32_data *data = DEV_DATA(dev); const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); 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(struct device *dev, uint8_t chan_id, const struct counter_alarm_cfg *alarm_cfg) { struct counter_sam0_tc32_data *data = DEV_DATA(dev); const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; ARG_UNUSED(chan_id); if (alarm_cfg->ticks > tc->CC[0].reg) { return -EINVAL; } 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(struct device *dev, uint8_t chan_id) { struct counter_sam0_tc32_data *data = DEV_DATA(dev); const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; 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(struct device *dev, const struct counter_top_cfg *top_cfg) { struct counter_sam0_tc32_data *data = DEV_DATA(dev); const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; int err = 0; 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(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; return tc->INTFLAG.reg & (TC_INTFLAG_MC0 | TC_INTFLAG_MC1); } static uint32_t counter_sam0_tc32_get_top_value(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); 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 uint32_t counter_sam0_tc32_get_max_relative_alarm(struct device *dev) { return counter_sam0_tc32_get_top_value(dev) - 1; } static void counter_sam0_tc32_isr(void *arg) { struct device *dev = (struct device *)arg; struct counter_sam0_tc32_data *data = DEV_DATA(dev); const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); 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(struct device *dev) { const struct counter_sam0_tc32_config *const cfg = DEV_CFG(dev); TcCount32 *tc = cfg->regs; #ifdef MCLK /* Enable the GCLK */ GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN; /* Enable TC clock in MCLK */ *cfg->mclk |= cfg->mclk_mask; #else /* Enable the GCLK */ GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_CLKEN; /* Enable clock in PM */ PM->APBCMASK.reg |= cfg->pm_apbcmask; #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; /* 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 const struct counter_driver_api 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, .get_max_relative_alarm = counter_sam0_tc32_get_max_relative_alarm, }; #ifdef MCLK #define COUNTER_SAM0_TC32_CLOCK_CONTROL(n) \ .mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(n), \ .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, bit)), \ .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, periph_ch), #else #define COUNTER_SAM0_TC32_CLOCK_CONTROL(n) \ .pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(n, pm, bit)), \ .gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, clkctrl_id), #endif #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) \ static void counter_sam0_tc32_config_##n(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), \ COUNTER_SAM0_TC32_CLOCK_CONTROL(n) \ .prescaler = UTIL_CAT(TC_CTRLA_PRESCALER_DIV, \ SAM0_TC32_PRESCALER(n)), \ .irq_config_func = &counter_sam0_tc32_config_##n, \ }; \ \ static struct counter_sam0_tc32_data counter_sam0_tc32_dev_data_##n;\ \ DEVICE_AND_API_INIT(counter_sam0_tc32_##n, \ DT_INST_LABEL(n), \ &counter_sam0_tc32_initialize, \ &counter_sam0_tc32_dev_data_##n, \ &counter_sam0_tc32_dev_config_##n, \ PRE_KERNEL_1, \ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &counter_sam0_tc32_driver_api); \ \ static void counter_sam0_tc32_config_##n(struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), \ DT_INST_IRQ(n, priority), \ counter_sam0_tc32_isr, \ DEVICE_GET(counter_sam0_tc32_##n), 0); \ irq_enable(DT_INST_IRQN(n)); \ } DT_INST_FOREACH_STATUS_OKAY(COUNTER_SAM0_TC32_DEVICE)