1 /*
2  * Copyright (c) 2023 Zephyr Project
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_cc13xx_cc26xx_timer_pwm
8 
9 #include <zephyr/drivers/i2c.h>
10 #include <zephyr/drivers/pinctrl.h>
11 #include <zephyr/drivers/pwm.h>
12 
13 #include <driverlib/gpio.h>
14 #include <driverlib/prcm.h>
15 #include <driverlib/timer.h>
16 #include <inc/hw_memmap.h>
17 #include <inc/hw_types.h>
18 #include <ti/drivers/Power.h>
19 #include <ti/drivers/power/PowerCC26XX.h>
20 
21 #include <zephyr/logging/log.h>
22 #define LOG_MODULE_NAME pwm_cc13xx_cc26xx_timer
23 LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_PWM_LOG_LEVEL);
24 
25 /* TODO: Clock frequency can be settable via KConfig, see TOP:PRCM:GPTCLKDIV */
26 #define CPU_FREQ ((uint32_t)DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency))
27 
28 /* GPT peripherals in 16 bit mode have maximum 24 counter bits incl. the
29  * prescaler. Count is set to (2^24 - 2) to allow for a glitch free 100% duty
30  * cycle at max. period count.
31  */
32 #define PWM_COUNT_MAX      0xFFFFFE
33 #define PWM_INITIAL_PERIOD PWM_COUNT_MAX
34 #define PWM_INITIAL_DUTY   0U /* initially off */
35 
36 struct pwm_cc13xx_cc26xx_data {
37 	bool standby_disabled;
38 };
39 
40 struct pwm_cc13xx_cc26xx_config {
41 	const uint32_t gpt_base; /* GPT register base address */
42 	const struct pinctrl_dev_config *pcfg;
43 
44 	LOG_INSTANCE_PTR_DECLARE(log);
45 };
46 
write_value(const struct pwm_cc13xx_cc26xx_config * config,uint32_t value,uint32_t prescale_register,uint32_t value_register)47 static void write_value(const struct pwm_cc13xx_cc26xx_config *config, uint32_t value,
48 			uint32_t prescale_register, uint32_t value_register)
49 {
50 	/* Upper byte represents the prescaler value. */
51 	uint8_t prescaleValue = 0xff & (value >> 16);
52 
53 	HWREG(config->gpt_base + prescale_register) = prescaleValue;
54 
55 	/* The remaining bytes represent the load / match value. */
56 	HWREG(config->gpt_base + value_register) = value & 0xffff;
57 }
58 
set_period_and_pulse(const struct pwm_cc13xx_cc26xx_config * config,uint32_t period,uint32_t pulse,struct pwm_cc13xx_cc26xx_data * data)59 static int set_period_and_pulse(const struct pwm_cc13xx_cc26xx_config *config, uint32_t period,
60 				uint32_t pulse, struct pwm_cc13xx_cc26xx_data *data)
61 {
62 	uint32_t match_value = pulse;
63 
64 	if (pulse == 0U) {
65 		TimerDisable(config->gpt_base, TIMER_B);
66 #ifdef CONFIG_PM
67 		if (data->standby_disabled) {
68 			Power_releaseConstraint(PowerCC26XX_DISALLOW_STANDBY);
69 			data->standby_disabled = false;
70 		}
71 #endif
72 		match_value = period + 1;
73 	}
74 
75 	/* Fail if period is out of range */
76 	if ((period > PWM_COUNT_MAX) || (period == 0)) {
77 		LOG_ERR("Period (%d) is out of range.", period);
78 		return -EINVAL;
79 	}
80 
81 	/* Compare to new period and fail if invalid */
82 	if (period < (match_value - 1) || (match_value < 0)) {
83 		LOG_ERR("Period (%d) is shorter than pulse (%d).", period, pulse);
84 		return -EINVAL;
85 	}
86 
87 	/* Store new period and update timer */
88 	write_value(config, period, GPT_O_TBPR, GPT_O_TBILR);
89 	write_value(config, match_value, GPT_O_TBPMR, GPT_O_TBMATCHR);
90 
91 	if (pulse > 0U) {
92 #ifdef CONFIG_PM
93 		if (!data->standby_disabled) {
94 			Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY);
95 			data->standby_disabled = true;
96 		}
97 #endif
98 		TimerEnable(config->gpt_base, TIMER_B);
99 	}
100 
101 	LOG_DBG("Period and pulse successfully set.");
102 	return 0;
103 }
104 
set_cycles(const struct device * dev,uint32_t channel,uint32_t period,uint32_t pulse,pwm_flags_t flags)105 static int set_cycles(const struct device *dev, uint32_t channel, uint32_t period, uint32_t pulse,
106 		      pwm_flags_t flags)
107 {
108 	const struct pwm_cc13xx_cc26xx_config *config = dev->config;
109 
110 	if (channel != 0) {
111 		return -EIO;
112 	}
113 
114 	if (flags & PWM_POLARITY_INVERTED) {
115 		HWREG(config->gpt_base + GPT_O_CTL) |= GPT_CTL_TBPWML_INVERTED;
116 	} else {
117 		HWREG(config->gpt_base + GPT_O_CTL) |= GPT_CTL_TBPWML_NORMAL;
118 	}
119 
120 	set_period_and_pulse(config, period, pulse, dev->data);
121 
122 	return 0;
123 }
124 
get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)125 static int get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles)
126 {
127 	if (channel > 0) {
128 		return -EIO;
129 	}
130 
131 	if (cycles) {
132 		*cycles = CPU_FREQ;
133 	}
134 
135 	return 0;
136 }
137 
138 static DEVICE_API(pwm, pwm_driver_api) = {
139 	.set_cycles = set_cycles,
140 	.get_cycles_per_sec = get_cycles_per_sec,
141 };
142 
143 #ifdef CONFIG_PM
get_timer_inst_number(const struct pwm_cc13xx_cc26xx_config * config)144 static int get_timer_inst_number(const struct pwm_cc13xx_cc26xx_config *config)
145 {
146 	switch (config->gpt_base) {
147 	case GPT0_BASE:
148 		return 0;
149 	case GPT1_BASE:
150 		return 1;
151 	case GPT2_BASE:
152 		return 2;
153 	case GPT3_BASE:
154 		return 3;
155 	default:
156 		CODE_UNREACHABLE;
157 	}
158 }
159 #else
get_timer_peripheral(const struct pwm_cc13xx_cc26xx_config * config)160 static int get_timer_peripheral(const struct pwm_cc13xx_cc26xx_config *config)
161 {
162 	switch (config->gpt_base) {
163 	case GPT0_BASE:
164 		return PRCM_PERIPH_TIMER0;
165 	case GPT1_BASE:
166 		return PRCM_PERIPH_TIMER1;
167 	case GPT2_BASE:
168 		return PRCM_PERIPH_TIMER2;
169 	case GPT3_BASE:
170 		return PRCM_PERIPH_TIMER3;
171 	default:
172 		CODE_UNREACHABLE;
173 	}
174 }
175 #endif /* CONFIG_PM */
176 
init_pwm(const struct device * dev)177 static int init_pwm(const struct device *dev)
178 {
179 	const struct pwm_cc13xx_cc26xx_config *config = dev->config;
180 	pinctrl_soc_pin_t pin = config->pcfg->states[0].pins[0];
181 	int ret;
182 
183 #ifdef CONFIG_PM
184 	/* Set dependency on gpio resource to turn on power domains */
185 	Power_setDependency(get_timer_inst_number(config));
186 #else
187 	/* Enable peripheral power domain. */
188 	PRCMPowerDomainOn(PRCM_DOMAIN_PERIPH);
189 
190 	/* Enable GPIO peripheral. */
191 	PRCMPeripheralRunEnable(get_timer_peripheral(config));
192 	PRCMPeripheralSleepEnable(get_timer_peripheral(config));
193 	PRCMPeripheralDeepSleepEnable(get_timer_peripheral(config));
194 
195 	/* Load PRCM settings. */
196 	PRCMLoadSet();
197 	while (!PRCMLoadGet()) {
198 		continue;
199 	}
200 #endif /* CONFIG_PM */
201 
202 	ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
203 	if (ret < 0) {
204 		LOG_ERR("failed to setup PWM pinctrl");
205 		return ret;
206 	}
207 
208 	/* Configures the PWM idle output level.
209 	 *
210 	 * TODO: Make PWM idle high/low configurable via custom DT PWM flag.
211 	 */
212 	GPIO_writeDio(pin.pin, 0);
213 
214 	GPIO_setOutputEnableDio(pin.pin, GPIO_OUTPUT_ENABLE);
215 
216 	/* Peripheral should not be accessed until power domain is on. */
217 	while (PRCMPowerDomainsAllOn(PRCM_DOMAIN_PERIPH) != PRCM_DOMAIN_POWER_ON) {
218 		continue;
219 	}
220 
221 	TimerDisable(config->gpt_base, TIMER_B);
222 
223 	HWREG(config->gpt_base + GPT_O_CFG) = GPT_CFG_CFG_16BIT_TIMER;
224 	/* Stall timer when debugging.
225 	 *
226 	 * TODO: Make debug stall configurable via custom DT prop.
227 	 */
228 	HWREG(config->gpt_base + GPT_O_CTL) |= GPT_CTL_TBSTALL;
229 
230 	HWREG(config->gpt_base + GPT_O_TBMR) = GPT_TBMR_TBAMS_PWM | GPT_TBMR_TBMRSU_TOUPDATE |
231 					       GPT_TBMR_TBPWMIE_EN | GPT_TBMR_TBMR_PERIODIC;
232 
233 	set_period_and_pulse(config, PWM_INITIAL_PERIOD, PWM_INITIAL_DUTY, dev->data);
234 
235 	return 0;
236 }
237 
238 #define DT_TIMER(idx)           DT_INST_PARENT(idx)
239 #define DT_TIMER_BASE_ADDR(idx) (DT_REG_ADDR(DT_TIMER(idx)))
240 
241 #define PWM_DEVICE_INIT(idx)                                                                       \
242 	PINCTRL_DT_INST_DEFINE(idx);                                                               \
243 	LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, idx, CONFIG_PWM_LOG_LEVEL);                         \
244 	static const struct pwm_cc13xx_cc26xx_config pwm_cc13xx_cc26xx_##idx##_config = {          \
245 		.gpt_base = DT_TIMER_BASE_ADDR(idx),                                               \
246 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx),                                       \
247 		LOG_INSTANCE_PTR_INIT(log, LOG_MODULE_NAME, idx)};                                 \
248                                                                                                    \
249 	static struct pwm_cc13xx_cc26xx_data pwm_cc13xx_cc26xx_##idx##_data;                       \
250                                                                                                    \
251 	DEVICE_DT_INST_DEFINE(idx, init_pwm, NULL, &pwm_cc13xx_cc26xx_##idx##_data,                \
252 			      &pwm_cc13xx_cc26xx_##idx##_config, POST_KERNEL,                      \
253 			      CONFIG_PWM_INIT_PRIORITY, &pwm_driver_api)
254 
255 DT_INST_FOREACH_STATUS_OKAY(PWM_DEVICE_INIT);
256