/* * 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 #include #include #include #include #include #include #include #include #include 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 pinctrl_dev_config *pcfg; 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]; }; 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]; int retval; /* Connect pins to the peripheral */ retval = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); if (retval < 0) { return retval; } /* 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); 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) \ PINCTRL_DT_INST_DEFINE(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, \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(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)