|  | 
|  | 1 | +/* | 
|  | 2 | + * Copyright (c) 2021, Toby Firth. | 
|  | 3 | + * | 
|  | 4 | + * SPDX-License-Identifier: Apache-2.0 | 
|  | 5 | + */ | 
|  | 6 | +#define DT_DRV_COMPAT nxp_lpc_ctimer | 
|  | 7 | + | 
|  | 8 | +#include <drivers/counter.h> | 
|  | 9 | +#include <fsl_ctimer.h> | 
|  | 10 | +#include <logging/log.h> | 
|  | 11 | +#include <drivers/clock_control.h> | 
|  | 12 | +#include <dt-bindings/clock/mcux_lpc_syscon_clock.h> | 
|  | 13 | +LOG_MODULE_REGISTER(mcux_ctimer, CONFIG_COUNTER_LOG_LEVEL); | 
|  | 14 | + | 
|  | 15 | +#define NUM_CHANNELS 4 | 
|  | 16 | + | 
|  | 17 | +struct mcux_lpc_ctimer_channel_data { | 
|  | 18 | +	counter_alarm_callback_t alarm_callback; | 
|  | 19 | +	void *alarm_user_data; | 
|  | 20 | +}; | 
|  | 21 | + | 
|  | 22 | +struct mcux_lpc_ctimer_data { | 
|  | 23 | +	struct mcux_lpc_ctimer_channel_data channels[NUM_CHANNELS]; | 
|  | 24 | +}; | 
|  | 25 | + | 
|  | 26 | +struct mcux_lpc_ctimer_config { | 
|  | 27 | +	struct counter_config_info info; | 
|  | 28 | +	CTIMER_Type *base; | 
|  | 29 | +	const struct device *clock_dev; | 
|  | 30 | +	clock_control_subsys_t clock_subsys; | 
|  | 31 | +	ctimer_timer_mode_t mode; | 
|  | 32 | +	ctimer_capture_channel_t input; | 
|  | 33 | +	uint32_t prescale; | 
|  | 34 | +	void (*irq_config_func)(const struct device *dev); | 
|  | 35 | +}; | 
|  | 36 | + | 
|  | 37 | +static int mcux_lpc_ctimer_start(const struct device *dev) | 
|  | 38 | +{ | 
|  | 39 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 40 | + | 
|  | 41 | +	CTIMER_StartTimer(config->base); | 
|  | 42 | + | 
|  | 43 | +	return 0; | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +static int mcux_lpc_ctimer_stop(const struct device *dev) | 
|  | 47 | +{ | 
|  | 48 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 49 | + | 
|  | 50 | +	CTIMER_StopTimer(config->base); | 
|  | 51 | + | 
|  | 52 | +	return 0; | 
|  | 53 | +} | 
|  | 54 | + | 
|  | 55 | +static uint32_t mcux_lpc_ctimer_read(CTIMER_Type *base) | 
|  | 56 | +{ | 
|  | 57 | +	return CTIMER_GetTimerCountValue(base); | 
|  | 58 | +} | 
|  | 59 | + | 
|  | 60 | +static int mcux_lpc_ctimer_get_value(const struct device *dev, uint32_t *ticks) | 
|  | 61 | +{ | 
|  | 62 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 63 | +	*ticks = mcux_lpc_ctimer_read(config->base); | 
|  | 64 | +	return 0; | 
|  | 65 | +} | 
|  | 66 | + | 
|  | 67 | +static int mcux_lpc_ctimer_set_alarm(const struct device *dev, uint8_t chan_id, | 
|  | 68 | +				     const struct counter_alarm_cfg *alarm_cfg) | 
|  | 69 | +{ | 
|  | 70 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 71 | +	struct mcux_lpc_ctimer_data *data = dev->data; | 
|  | 72 | + | 
|  | 73 | +	uint32_t ticks = alarm_cfg->ticks; | 
|  | 74 | +	uint32_t current = mcux_lpc_ctimer_read(config->base); | 
|  | 75 | + | 
|  | 76 | +	if (data->channels[chan_id].alarm_callback != NULL) { | 
|  | 77 | +		LOG_ERR("channel already in use"); | 
|  | 78 | +		return -EBUSY; | 
|  | 79 | +	} | 
|  | 80 | + | 
|  | 81 | +	if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) { | 
|  | 82 | +		ticks += current; | 
|  | 83 | +	} | 
|  | 84 | + | 
|  | 85 | +	data->channels[chan_id].alarm_callback = alarm_cfg->callback; | 
|  | 86 | +	data->channels[chan_id].alarm_user_data = alarm_cfg->user_data; | 
|  | 87 | + | 
|  | 88 | +	ctimer_match_config_t match_config = { .matchValue = ticks, | 
|  | 89 | +					       .enableCounterReset = false, | 
|  | 90 | +					       .enableCounterStop = false, | 
|  | 91 | +					       .outControl = kCTIMER_Output_NoAction, | 
|  | 92 | +					       .outPinInitState = false, | 
|  | 93 | +					       .enableInterrupt = true }; | 
|  | 94 | + | 
|  | 95 | +	CTIMER_SetupMatch(config->base, chan_id, &match_config); | 
|  | 96 | + | 
|  | 97 | +	return 0; | 
|  | 98 | +} | 
|  | 99 | + | 
|  | 100 | +static int mcux_lpc_ctimer_cancel_alarm(const struct device *dev, uint8_t chan_id) | 
|  | 101 | +{ | 
|  | 102 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 103 | +	struct mcux_lpc_ctimer_data *data = dev->data; | 
|  | 104 | + | 
|  | 105 | +	CTIMER_DisableInterrupts(config->base, (1 << chan_id)); | 
|  | 106 | + | 
|  | 107 | +	data->channels[chan_id].alarm_callback = NULL; | 
|  | 108 | +	data->channels[chan_id].alarm_user_data = NULL; | 
|  | 109 | + | 
|  | 110 | +	return 0; | 
|  | 111 | +} | 
|  | 112 | + | 
|  | 113 | +static int mcux_lpc_ctimer_set_top_value(const struct device *dev, | 
|  | 114 | +					 const struct counter_top_cfg *cfg) | 
|  | 115 | +{ | 
|  | 116 | +	ARG_UNUSED(dev); | 
|  | 117 | +	ARG_UNUSED(cfg); | 
|  | 118 | +	return -ENOTSUP; | 
|  | 119 | +} | 
|  | 120 | + | 
|  | 121 | +static uint32_t mcux_lpc_ctimer_get_pending_int(const struct device *dev) | 
|  | 122 | +{ | 
|  | 123 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 124 | + | 
|  | 125 | +	return (CTIMER_GetStatusFlags(config->base) & 0xF) != 0; | 
|  | 126 | +} | 
|  | 127 | + | 
|  | 128 | +static uint32_t mcux_lpc_ctimer_get_top_value(const struct device *dev) | 
|  | 129 | +{ | 
|  | 130 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 131 | + | 
|  | 132 | +	return config->info.max_top_value; | 
|  | 133 | +} | 
|  | 134 | + | 
|  | 135 | +static void mcux_lpc_ctimer_isr(const struct device *dev) | 
|  | 136 | +{ | 
|  | 137 | +	const struct mcux_lpc_ctimer_config *config = dev->config; | 
|  | 138 | +	struct mcux_lpc_ctimer_data *data = dev->data; | 
|  | 139 | + | 
|  | 140 | +	uint32_t interrupt_stat = CTIMER_GetStatusFlags(config->base); | 
|  | 141 | + | 
|  | 142 | +	CTIMER_ClearStatusFlags(config->base, interrupt_stat); | 
|  | 143 | + | 
|  | 144 | +	uint32_t ticks = mcux_lpc_ctimer_read(config->base); | 
|  | 145 | + | 
|  | 146 | +	for (uint8_t chan = 0; chan < NUM_CHANNELS; chan++) { | 
|  | 147 | +		uint8_t channel_mask = 0x01 << chan; | 
|  | 148 | + | 
|  | 149 | +		if (((interrupt_stat & channel_mask) != 0) && | 
|  | 150 | +		    (data->channels[chan].alarm_callback != NULL)) { | 
|  | 151 | +			counter_alarm_callback_t alarm_callback = | 
|  | 152 | +				data->channels[chan].alarm_callback; | 
|  | 153 | +			void *alarm_user_data = data->channels[chan].alarm_user_data; | 
|  | 154 | + | 
|  | 155 | +			data->channels[chan].alarm_callback = NULL; | 
|  | 156 | +			data->channels[chan].alarm_user_data = NULL; | 
|  | 157 | +			alarm_callback(dev, chan, ticks, alarm_user_data); | 
|  | 158 | +		} | 
|  | 159 | +	} | 
|  | 160 | +} | 
|  | 161 | + | 
|  | 162 | +static int mcux_lpc_ctimer_init(const struct device *dev) | 
|  | 163 | +{ | 
|  | 164 | +	/* | 
|  | 165 | +	 * The frequency of the timer is not known at compile time so we need to | 
|  | 166 | +	 * modify the timer's config in the init function at runtime when the | 
|  | 167 | +	 * frequency is known. | 
|  | 168 | +	 */ | 
|  | 169 | +	struct mcux_lpc_ctimer_config *config = (struct mcux_lpc_ctimer_config *)dev->config; | 
|  | 170 | +	struct mcux_lpc_ctimer_data *data = dev->data; | 
|  | 171 | + | 
|  | 172 | +	ctimer_config_t ctimer_config; | 
|  | 173 | + | 
|  | 174 | +	uint32_t clk_freq = 0; | 
|  | 175 | + | 
|  | 176 | +	if (clock_control_get_rate(config->clock_dev, config->clock_subsys, | 
|  | 177 | +					&clk_freq)) { | 
|  | 178 | +		LOG_ERR("unable to get clock frequency"); | 
|  | 179 | +		return -EINVAL; | 
|  | 180 | +	} | 
|  | 181 | + | 
|  | 182 | +	/* prescale increments when the prescale counter is 0 so if prescale is 1 | 
|  | 183 | +	 * the counter is incremented every 2 cycles of the clock so will actually | 
|  | 184 | +	 * divide by 2 hence the addition of 1 to the value here. | 
|  | 185 | +	 */ | 
|  | 186 | +	uint32_t freq = clk_freq / (config->prescale + 1); | 
|  | 187 | + | 
|  | 188 | +	config->info.freq = freq; | 
|  | 189 | + | 
|  | 190 | +	for (uint8_t chan = 0; chan < NUM_CHANNELS; chan++) { | 
|  | 191 | +		data->channels[chan].alarm_callback = NULL; | 
|  | 192 | +		data->channels[chan].alarm_user_data = NULL; | 
|  | 193 | +	} | 
|  | 194 | + | 
|  | 195 | +	CTIMER_GetDefaultConfig(&ctimer_config); | 
|  | 196 | + | 
|  | 197 | +	ctimer_config.mode = config->mode; | 
|  | 198 | +	ctimer_config.input = config->input; | 
|  | 199 | +	ctimer_config.prescale = config->prescale; | 
|  | 200 | + | 
|  | 201 | +	CTIMER_Init(config->base, &ctimer_config); | 
|  | 202 | + | 
|  | 203 | +	config->irq_config_func(dev); | 
|  | 204 | + | 
|  | 205 | +	return 0; | 
|  | 206 | +} | 
|  | 207 | + | 
|  | 208 | +static const struct counter_driver_api mcux_ctimer_driver_api = { | 
|  | 209 | +	.start = mcux_lpc_ctimer_start, | 
|  | 210 | +	.stop = mcux_lpc_ctimer_stop, | 
|  | 211 | +	.get_value = mcux_lpc_ctimer_get_value, | 
|  | 212 | +	.set_alarm = mcux_lpc_ctimer_set_alarm, | 
|  | 213 | +	.cancel_alarm = mcux_lpc_ctimer_cancel_alarm, | 
|  | 214 | +	.set_top_value = mcux_lpc_ctimer_set_top_value, | 
|  | 215 | +	.get_pending_int = mcux_lpc_ctimer_get_pending_int, | 
|  | 216 | +	.get_top_value = mcux_lpc_ctimer_get_top_value, | 
|  | 217 | +}; | 
|  | 218 | + | 
|  | 219 | +#define CTIMER_CLOCK_SOURCE(id) TO_CTIMER_CLOCK_SOURCE(id, DT_INST_PROP(id, clk_source)) | 
|  | 220 | +#define TO_CTIMER_CLOCK_SOURCE(id, val) MUX_A(CM_CTIMERCLKSEL##id, val) | 
|  | 221 | + | 
|  | 222 | +#define COUNTER_LPC_CTIMER_DEVICE(id)                                                              \ | 
|  | 223 | +	static void mcux_lpc_ctimer_irq_config_##id(const struct device *dev);                     \ | 
|  | 224 | +	static struct mcux_lpc_ctimer_config mcux_lpc_ctimer_config_##id = { \ | 
|  | 225 | +		.info = {						\ | 
|  | 226 | +			.max_top_value = UINT32_MAX,			\ | 
|  | 227 | +			.freq = 1,					\ | 
|  | 228 | +			.flags = COUNTER_CONFIG_INFO_COUNT_UP,		\ | 
|  | 229 | +			.channels = NUM_CHANNELS,					\ | 
|  | 230 | +		},\ | 
|  | 231 | +		.base = (CTIMER_Type *)DT_INST_REG_ADDR(id),		\ | 
|  | 232 | +		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(id)),	\ | 
|  | 233 | +		.clock_subsys =				\ | 
|  | 234 | +		(clock_control_subsys_t)(DT_INST_CLOCKS_CELL(id, name) + MCUX_CTIMER_CLK_OFFSET),\ | 
|  | 235 | +		.mode = DT_INST_PROP(id, mode),						\ | 
|  | 236 | +		.input = DT_INST_PROP(id, input),					\ | 
|  | 237 | +		.prescale = DT_INST_PROP(id, prescale),				\ | 
|  | 238 | +		.irq_config_func = mcux_lpc_ctimer_irq_config_##id,	\ | 
|  | 239 | +	};                     \ | 
|  | 240 | +	static struct mcux_lpc_ctimer_data mcux_lpc_ctimer_data_##id;                              \ | 
|  | 241 | +	DEVICE_DT_INST_DEFINE(id, &mcux_lpc_ctimer_init, NULL, &mcux_lpc_ctimer_data_##id,         \ | 
|  | 242 | +			      &mcux_lpc_ctimer_config_##id, POST_KERNEL,                           \ | 
|  | 243 | +			      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &mcux_ctimer_driver_api);        \ | 
|  | 244 | +	static void mcux_lpc_ctimer_irq_config_##id(const struct device *dev)                      \ | 
|  | 245 | +	{                                                                                          \ | 
|  | 246 | +		IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), mcux_lpc_ctimer_isr,      \ | 
|  | 247 | +			    DEVICE_DT_INST_GET(id), 0);                                            \ | 
|  | 248 | +		irq_enable(DT_INST_IRQN(id));                                                      \ | 
|  | 249 | +	} | 
|  | 250 | + | 
|  | 251 | +DT_INST_FOREACH_STATUS_OKAY(COUNTER_LPC_CTIMER_DEVICE) | 
0 commit comments