| /* | 
 |  * Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk> | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #define DT_DRV_COMPAT xlnx_xps_timer_1_00_a | 
 |  | 
 | #include <zephyr/device.h> | 
 | #include <zephyr/drivers/counter.h> | 
 | #include <zephyr/sys/sys_io.h> | 
 | #include <zephyr/logging/log.h> | 
 | LOG_MODULE_REGISTER(xlnx_axi_timer, CONFIG_COUNTER_LOG_LEVEL); | 
 |  | 
 | /* AXI Timer v2.0 registers offsets (See Xilinx PG079 for details) */ | 
 | #define TCSR0_OFFSET 0x00 | 
 | #define TLR0_OFFSET  0x04 | 
 | #define TCR0_OFFSET  0x08 | 
 | #define TCSR1_OFFSET 0x10 | 
 | #define TLR1_OFFSET  0x14 | 
 | #define TCR1_OFFSET  0x18 | 
 |  | 
 | /* TCSRx bit definitions */ | 
 | #define TCSR_MDT   BIT(0) | 
 | #define TCSR_UDT   BIT(1) | 
 | #define TCSR_GENT  BIT(2) | 
 | #define TCSR_CAPT  BIT(3) | 
 | #define TCSR_ARHT  BIT(4) | 
 | #define TCSR_LOAD  BIT(5) | 
 | #define TCSR_ENIT  BIT(6) | 
 | #define TCSR_ENT   BIT(7) | 
 | #define TCSR_TINT  BIT(8) | 
 | #define TCSR_PWMA  BIT(9) | 
 | #define TCSR_ENALL BIT(10) | 
 | #define TCSR_CASC  BIT(11) | 
 |  | 
 | /* 1st timer used as main timer in auto-reload, count-down. generate mode */ | 
 | #define TCSR0_DEFAULT (TCSR_ENIT | TCSR_ARHT | TCSR_GENT | TCSR_UDT) | 
 |  | 
 | /* 2nd timer (if available) used as alarm timer in count-down, generate mode */ | 
 | #define TCSR1_DEFAULT (TCSR_ENIT | TCSR_GENT | TCSR_UDT) | 
 |  | 
 | struct xlnx_axi_timer_config { | 
 | 	struct counter_config_info info; | 
 | 	mm_reg_t base; | 
 | 	void (*irq_config_func)(const struct device *dev); | 
 | }; | 
 |  | 
 | struct xlnx_axi_timer_data { | 
 | 	counter_top_callback_t top_callback; | 
 | 	void *top_user_data; | 
 | 	counter_alarm_callback_t alarm_callback; | 
 | 	void *alarm_user_data; | 
 | }; | 
 |  | 
 | static inline uint32_t xlnx_axi_timer_read32(const struct device *dev, | 
 | 					     mm_reg_t offset) | 
 | { | 
 | 	const struct xlnx_axi_timer_config *config = dev->config; | 
 |  | 
 | 	return sys_read32(config->base + offset); | 
 | } | 
 |  | 
 | static inline void xlnx_axi_timer_write32(const struct device *dev, | 
 | 					  uint32_t value, | 
 | 					  mm_reg_t offset) | 
 | { | 
 | 	const struct xlnx_axi_timer_config *config = dev->config; | 
 |  | 
 | 	sys_write32(value, config->base + offset); | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_start(const struct device *dev) | 
 | { | 
 | 	const struct xlnx_axi_timer_data *data = dev->data; | 
 | 	uint32_t tcsr = TCSR0_DEFAULT | TCSR_ENT; | 
 |  | 
 | 	LOG_DBG("starting timer"); | 
 |  | 
 | 	if (data->alarm_callback) { | 
 | 		/* Start both timers synchronously */ | 
 | 		tcsr |= TCSR_ENALL; | 
 | 	} | 
 |  | 
 | 	xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_stop(const struct device *dev) | 
 | { | 
 | 	const struct xlnx_axi_timer_config *config = dev->config; | 
 | 	unsigned int key; | 
 |  | 
 | 	LOG_DBG("stopping timer"); | 
 |  | 
 | 	key = irq_lock(); | 
 |  | 
 | 	/* The timers cannot be stopped synchronously */ | 
 | 	if (config->info.channels > 0) { | 
 | 		xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET); | 
 | 	} | 
 | 	xlnx_axi_timer_write32(dev, TCSR0_DEFAULT, TCSR0_OFFSET); | 
 |  | 
 | 	irq_unlock(key); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_get_value(const struct device *dev, uint32_t *ticks) | 
 | { | 
 |  | 
 | 	*ticks = xlnx_axi_timer_read32(dev, TCR0_OFFSET); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_set_alarm(const struct device *dev, uint8_t chan_id, | 
 | 				    const struct counter_alarm_cfg *cfg) | 
 | { | 
 | 	struct xlnx_axi_timer_data *data = dev->data; | 
 | 	unsigned int key; | 
 | 	uint32_t tcsr; | 
 |  | 
 | 	ARG_UNUSED(chan_id); | 
 |  | 
 | 	if (cfg->callback == NULL) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (data->alarm_callback != NULL) { | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	if (cfg->ticks > xlnx_axi_timer_read32(dev, TLR0_OFFSET)) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) { | 
 | 		/* | 
 | 		 * Since two different timers (with the same clock signal) are | 
 | 		 * used for main timer and alarm timer we cannot support | 
 | 		 * absolute alarms in a reliable way. | 
 | 		 */ | 
 | 		return -ENOTSUP; | 
 | 	} | 
 |  | 
 | 	LOG_DBG("triggering alarm in 0x%08x ticks", cfg->ticks); | 
 |  | 
 | 	/* Load alarm timer */ | 
 | 	xlnx_axi_timer_write32(dev, cfg->ticks, TLR1_OFFSET); | 
 | 	xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | TCSR_LOAD, TCSR1_OFFSET); | 
 |  | 
 | 	key = irq_lock(); | 
 |  | 
 | 	data->alarm_callback = cfg->callback; | 
 | 	data->alarm_user_data = cfg->user_data; | 
 |  | 
 | 	/* Enable alarm timer only if main timer already enabled */ | 
 | 	tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET); | 
 | 	tcsr &= TCSR_ENT; | 
 | 	xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | tcsr, TCSR1_OFFSET); | 
 |  | 
 | 	irq_unlock(key); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_cancel_alarm(const struct device *dev, | 
 | 				       uint8_t chan_id) | 
 | { | 
 | 	struct xlnx_axi_timer_data *data = dev->data; | 
 |  | 
 | 	ARG_UNUSED(chan_id); | 
 |  | 
 | 	LOG_DBG("cancelling alarm"); | 
 |  | 
 | 	xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET); | 
 | 	data->alarm_callback = NULL; | 
 | 	data->alarm_user_data = NULL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_set_top_value(const struct device *dev, | 
 | 					const struct counter_top_cfg *cfg) | 
 | { | 
 | 	struct xlnx_axi_timer_data *data = dev->data; | 
 | 	bool reload = true; | 
 | 	uint32_t tcsr; | 
 | 	uint32_t now; | 
 |  | 
 | 	if (cfg->ticks == 0) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (data->alarm_callback) { | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	LOG_DBG("setting top value to 0x%08x", cfg->ticks); | 
 |  | 
 | 	data->top_callback = cfg->callback; | 
 | 	data->top_user_data = cfg->user_data; | 
 |  | 
 | 	if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { | 
 | 		reload = false; | 
 |  | 
 | 		if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { | 
 | 			now = xlnx_axi_timer_read32(dev, TCR0_OFFSET); | 
 | 			reload = cfg->ticks < now; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET); | 
 | 	if ((tcsr & TCSR_ENT) == 0U) { | 
 | 		/* Timer not enabled, force reload of new top value */ | 
 | 		reload = true; | 
 | 	} | 
 |  | 
 | 	xlnx_axi_timer_write32(dev, cfg->ticks, TLR0_OFFSET); | 
 |  | 
 | 	if (reload) { | 
 | 		xlnx_axi_timer_write32(dev, tcsr | TCSR_LOAD, TCSR0_OFFSET); | 
 | 		xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static uint32_t xlnx_axi_timer_get_pending_int(const struct device *dev) | 
 | { | 
 | 	const struct xlnx_axi_timer_config *config = dev->config; | 
 | 	uint32_t pending = 0; | 
 | 	uint32_t tcsr; | 
 |  | 
 | 	tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET); | 
 | 	if (tcsr & TCSR_TINT) { | 
 | 		pending = 1; | 
 | 	} | 
 |  | 
 | 	if (config->info.channels > 0) { | 
 | 		tcsr = xlnx_axi_timer_read32(dev, TCSR1_OFFSET); | 
 | 		if (tcsr & TCSR_TINT) { | 
 | 			pending = 1; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	LOG_DBG("%sinterrupt pending", pending ? "" : "no "); | 
 |  | 
 | 	return pending; | 
 | } | 
 |  | 
 | static uint32_t xlnx_axi_timer_get_top_value(const struct device *dev) | 
 | { | 
 | 	return xlnx_axi_timer_read32(dev, TLR0_OFFSET); | 
 | } | 
 |  | 
 | static void xlnx_axi_timer_isr(const struct device *dev) | 
 | { | 
 | 	struct xlnx_axi_timer_data *data = dev->data; | 
 | 	counter_alarm_callback_t alarm_cb; | 
 | 	uint32_t tcsr; | 
 | 	uint32_t now; | 
 |  | 
 | 	tcsr = xlnx_axi_timer_read32(dev, TCSR1_OFFSET); | 
 | 	if (tcsr & TCSR_TINT) { | 
 | 		xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | TCSR_TINT, | 
 | 				       TCSR1_OFFSET); | 
 |  | 
 | 		if (data->alarm_callback) { | 
 | 			now = xlnx_axi_timer_read32(dev, TCR0_OFFSET); | 
 | 			alarm_cb = data->alarm_callback; | 
 | 			data->alarm_callback = NULL; | 
 |  | 
 | 			alarm_cb(dev, 0, now, data->alarm_user_data); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET); | 
 | 	if (tcsr & TCSR_TINT) { | 
 | 		xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET); | 
 |  | 
 | 		if (data->top_callback) { | 
 | 			data->top_callback(dev, data->top_user_data); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static int xlnx_axi_timer_init(const struct device *dev) | 
 | { | 
 | 	const struct xlnx_axi_timer_config *config = dev->config; | 
 |  | 
 | 	LOG_DBG("max top value = 0x%08x", config->info.max_top_value); | 
 | 	LOG_DBG("frequency = %d", config->info.freq); | 
 | 	LOG_DBG("channels = %d", config->info.channels); | 
 |  | 
 | 	xlnx_axi_timer_write32(dev, config->info.max_top_value, TLR0_OFFSET); | 
 | 	xlnx_axi_timer_write32(dev, TCSR0_DEFAULT | TCSR_LOAD, TCSR0_OFFSET); | 
 |  | 
 | 	if (config->info.channels > 0) { | 
 | 		xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET); | 
 | 	} | 
 |  | 
 | 	config->irq_config_func(dev); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct counter_driver_api xlnx_axi_timer_driver_api = { | 
 | 	.start = xlnx_axi_timer_start, | 
 | 	.stop = xlnx_axi_timer_stop, | 
 | 	.get_value = xlnx_axi_timer_get_value, | 
 | 	.set_alarm = xlnx_axi_timer_set_alarm, | 
 | 	.cancel_alarm = xlnx_axi_timer_cancel_alarm, | 
 | 	.set_top_value = xlnx_axi_timer_set_top_value, | 
 | 	.get_pending_int = xlnx_axi_timer_get_pending_int, | 
 | 	.get_top_value = xlnx_axi_timer_get_top_value, | 
 | }; | 
 |  | 
 | #define XLNX_AXI_TIMER_INIT(n)						\ | 
 | 	static void xlnx_axi_timer_config_func_##n(const struct device *dev); \ | 
 | 									\ | 
 | 	static struct xlnx_axi_timer_config xlnx_axi_timer_config_##n = { \ | 
 | 		.info = {						\ | 
 | 			.max_top_value =				\ | 
 | 			GENMASK(DT_INST_PROP(n, xlnx_count_width) - 1, 0), \ | 
 | 			.freq = DT_INST_PROP(n, clock_frequency),	\ | 
 | 			.flags = 0,					\ | 
 | 			.channels =					\ | 
 | 			COND_CODE_1(DT_INST_PROP(n, xlnx_one_timer_only), \ | 
 | 				    (0), (1)),				\ | 
 | 		},							\ | 
 | 		.base = DT_INST_REG_ADDR(n),				\ | 
 | 		.irq_config_func = xlnx_axi_timer_config_func_##n,	\ | 
 | 	};								\ | 
 | 									\ | 
 | 	static struct xlnx_axi_timer_data xlnx_axi_timer_data_##n;	\ | 
 | 									\ | 
 | 	DEVICE_DT_INST_DEFINE(n, &xlnx_axi_timer_init,			\ | 
 | 			NULL,						\ | 
 | 			&xlnx_axi_timer_data_##n,			\ | 
 | 			&xlnx_axi_timer_config_##n,			\ | 
 | 			POST_KERNEL,					\ | 
 | 			CONFIG_COUNTER_INIT_PRIORITY,			\ | 
 | 			&xlnx_axi_timer_driver_api);			\ | 
 | 									\ | 
 | 	static void xlnx_axi_timer_config_func_##n(const struct device *dev) \ | 
 | 	{								\ | 
 | 		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority),	\ | 
 | 			    xlnx_axi_timer_isr,				\ | 
 | 			    DEVICE_DT_INST_GET(n), 0);			\ | 
 | 		irq_enable(DT_INST_IRQN(n));				\ | 
 | 	} | 
 |  | 
 | DT_INST_FOREACH_STATUS_OKAY(XLNX_AXI_TIMER_INIT) |