1 /*
2  * Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT xlnx_xps_timer_1_00_a_pwm
8 
9 #include <zephyr/arch/cpu.h>
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/pwm.h>
12 #include <zephyr/sys/sys_io.h>
13 #include <zephyr/logging/log.h>
14 
15 LOG_MODULE_REGISTER(xlnx_axi_timer_pwm, CONFIG_PWM_LOG_LEVEL);
16 
17 /* AXI Timer v2.0 registers offsets (See Xilinx PG079 for details) */
18 #define TCSR0_OFFSET 0x00
19 #define TLR0_OFFSET  0x04
20 #define TCR0_OFFSET  0x08
21 #define TCSR1_OFFSET 0x10
22 #define TLR1_OFFSET  0x14
23 #define TCR1_OFFSET  0x18
24 
25 /* TCSRx bit definitions */
26 #define TCSR_MDT   BIT(0)
27 #define TCSR_UDT   BIT(1)
28 #define TCSR_GENT  BIT(2)
29 #define TCSR_CAPT  BIT(3)
30 #define TCSR_ARHT  BIT(4)
31 #define TCSR_LOAD  BIT(5)
32 #define TCSR_ENIT  BIT(6)
33 #define TCSR_ENT   BIT(7)
34 #define TCSR_TINT  BIT(8)
35 #define TCSR_PWMA  BIT(9)
36 #define TCSR_ENALL BIT(10)
37 #define TCSR_CASC  BIT(11)
38 
39 /* Generate PWM mode, count-down, auto-reload */
40 #define TCSR_PWM (TCSR_UDT | TCSR_GENT | TCSR_ARHT | TCSR_PWMA)
41 
42 struct xlnx_axi_timer_config {
43 	mm_reg_t base;
44 	uint32_t cycles_max;
45 	uint32_t freq;
46 };
47 
xlnx_axi_timer_read32(const struct device * dev,mm_reg_t offset)48 static inline uint32_t xlnx_axi_timer_read32(const struct device *dev,
49 					     mm_reg_t offset)
50 {
51 	const struct xlnx_axi_timer_config *config = dev->config;
52 
53 	return sys_read32(config->base + offset);
54 }
55 
xlnx_axi_timer_write32(const struct device * dev,uint32_t value,mm_reg_t offset)56 static inline void xlnx_axi_timer_write32(const struct device *dev,
57 					  uint32_t value,
58 					  mm_reg_t offset)
59 {
60 	const struct xlnx_axi_timer_config *config = dev->config;
61 
62 	sys_write32(value, config->base + offset);
63 }
64 
xlnx_axi_timer_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)65 static int xlnx_axi_timer_set_cycles(const struct device *dev, uint32_t channel,
66 				     uint32_t period_cycles,
67 				     uint32_t pulse_cycles, pwm_flags_t flags)
68 {
69 	const struct xlnx_axi_timer_config *config = dev->config;
70 	uint32_t tcsr0 = TCSR_PWM;
71 	uint32_t tcsr1 = TCSR_PWM;
72 	uint32_t tlr0;
73 	uint32_t tlr1;
74 
75 	if (channel != 0) {
76 		return -ENOTSUP;
77 	}
78 
79 	LOG_DBG("period = 0x%08x, pulse = 0x%08x", period_cycles, pulse_cycles);
80 
81 	if (pulse_cycles == 0) {
82 		LOG_DBG("setting constant inactive level");
83 
84 		if (flags & PWM_POLARITY_INVERTED) {
85 			tcsr0 |= TCSR_ENT;
86 		} else {
87 			tcsr1 |= TCSR_ENT;
88 		}
89 	} else if (pulse_cycles == period_cycles) {
90 		LOG_DBG("setting constant active level");
91 
92 		if (flags & PWM_POLARITY_INVERTED) {
93 			tcsr1 |= TCSR_ENT;
94 		} else {
95 			tcsr0 |= TCSR_ENT;
96 		}
97 	} else {
98 		LOG_DBG("setting normal pwm");
99 
100 		if (period_cycles < 2) {
101 			LOG_ERR("period cycles too narrow");
102 			return -ENOTSUP;
103 		}
104 
105 		/* PWM_PERIOD = (TLR0 + 2) * AXI_CLOCK_PERIOD */
106 		tlr0 = period_cycles - 2;
107 
108 		if (tlr0 > config->cycles_max) {
109 			LOG_ERR("tlr0 out of range (0x%08x > 0x%08x)", tlr0,
110 				config->cycles_max);
111 			return -ENOTSUP;
112 		}
113 
114 		if (flags & PWM_POLARITY_INVERTED) {
115 			/*
116 			 * Since this is a single-channel PWM controller (with
117 			 * no other channels to phase align with) inverse
118 			 * polarity can be achieved simply by inverting the
119 			 * pulse.
120 			 */
121 
122 			if ((period_cycles - pulse_cycles) < 2) {
123 				LOG_ERR("pulse cycles too narrow");
124 				return -ENOTSUP;
125 			}
126 
127 			/* PWM_HIGH_TIME = (TLR1 + 2) * AXI_CLOCK_PERIOD */
128 			tlr1 = period_cycles - pulse_cycles - 2;
129 		} else {
130 			if (pulse_cycles < 2) {
131 				LOG_ERR("pulse cycles too narrow");
132 				return -ENOTSUP;
133 			}
134 
135 			/* PWM_HIGH_TIME = (TLR1 + 2) * AXI_CLOCK_PERIOD */
136 			tlr1 = pulse_cycles - 2;
137 		}
138 
139 		LOG_DBG("tlr0 = 0x%08x, tlr1 = 0x%08x", tlr0, tlr1);
140 
141 		/* Stop both timers */
142 		xlnx_axi_timer_write32(dev, TCSR_PWM, TCSR0_OFFSET);
143 		xlnx_axi_timer_write32(dev, TCSR_PWM, TCSR1_OFFSET);
144 
145 		/* Load period cycles */
146 		xlnx_axi_timer_write32(dev, tlr0, TLR0_OFFSET);
147 		xlnx_axi_timer_write32(dev, TCSR_PWM | TCSR_LOAD, TCSR0_OFFSET);
148 
149 		/* Load pulse cycles */
150 		xlnx_axi_timer_write32(dev, tlr1, TLR1_OFFSET);
151 		xlnx_axi_timer_write32(dev, TCSR_PWM | TCSR_LOAD, TCSR1_OFFSET);
152 
153 		/* Start both timers */
154 		tcsr1 |= TCSR_ENALL;
155 	}
156 
157 	xlnx_axi_timer_write32(dev, tcsr0, TCSR0_OFFSET);
158 	xlnx_axi_timer_write32(dev, tcsr1, TCSR1_OFFSET);
159 
160 	return 0;
161 }
162 
xlnx_axi_timer_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)163 static int xlnx_axi_timer_get_cycles_per_sec(const struct device *dev,
164 					     uint32_t channel, uint64_t *cycles)
165 {
166 	const struct xlnx_axi_timer_config *config = dev->config;
167 
168 	ARG_UNUSED(channel);
169 
170 	*cycles = config->freq;
171 
172 	return 0;
173 }
174 
175 static const struct pwm_driver_api xlnx_axi_timer_driver_api = {
176 	.set_cycles = xlnx_axi_timer_set_cycles,
177 	.get_cycles_per_sec = xlnx_axi_timer_get_cycles_per_sec,
178 };
179 
180 #define XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, prop, val, str)	\
181 	BUILD_ASSERT(DT_INST_PROP(n, prop) == val, str)
182 
183 #define XLNX_AXI_TIMER_INIT(n)						\
184 	XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_gen0_assert, 1,		\
185 				   "xlnx,gen0-assert must be 1 for pwm"); \
186 	XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_gen1_assert, 1,		\
187 				   "xlnx,gen1-assert must be 1 for pwm"); \
188 	XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_one_timer_only, 0,	\
189 				   "xlnx,one-timer-only must be 0 for pwm"); \
190 									\
191 	static struct xlnx_axi_timer_config xlnx_axi_timer_config_##n = { \
192 		.base = DT_INST_REG_ADDR(n),				\
193 		.freq = DT_INST_PROP(n, clock_frequency),		\
194 		.cycles_max =						\
195 			GENMASK(DT_INST_PROP(n, xlnx_count_width) - 1, 0), \
196 	};								\
197 									\
198 	DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL,			\
199 			    &xlnx_axi_timer_config_##n,			\
200 			    POST_KERNEL,				\
201 			    CONFIG_PWM_INIT_PRIORITY,			\
202 			    &xlnx_axi_timer_driver_api)
203 
204 DT_INST_FOREACH_STATUS_OKAY(XLNX_AXI_TIMER_INIT);
205