1 /*
2  * Copyright (c) 2021 Sun Amar.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT silabs_gecko_pwm
8 
9 #include <zephyr/drivers/pwm.h>
10 #include <zephyr/dt-bindings/pwm/pwm.h>
11 #include <em_cmu.h>
12 #include <em_timer.h>
13 
14 /** PWM configuration. */
15 struct pwm_gecko_config {
16 	TIMER_TypeDef *timer;
17 	CMU_Clock_TypeDef clock;
18 	uint16_t prescaler;
19 	TIMER_Prescale_TypeDef prescale_enum;
20 	uint8_t channel;
21 	uint8_t location;
22 	uint8_t port;
23 	uint8_t pin;
24 };
25 
pwm_gecko_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)26 static int pwm_gecko_set_cycles(const struct device *dev, uint32_t channel,
27 				uint32_t period_cycles, uint32_t pulse_cycles,
28 				pwm_flags_t flags)
29 {
30 	TIMER_InitCC_TypeDef compare_config = TIMER_INITCC_DEFAULT;
31 	const struct pwm_gecko_config *cfg = dev->config;
32 
33 	if (BUS_RegMaskedRead(&cfg->timer->CC[channel].CTRL,
34 		_TIMER_CC_CTRL_MODE_MASK) != timerCCModePWM) {
35 
36 #ifdef _TIMER_ROUTE_MASK
37 		BUS_RegMaskedWrite(&cfg->timer->ROUTE,
38 			_TIMER_ROUTE_LOCATION_MASK,
39 			cfg->location << _TIMER_ROUTE_LOCATION_SHIFT);
40 		BUS_RegMaskedSet(&cfg->timer->ROUTE, 1 << channel);
41 #elif defined(_TIMER_ROUTELOC0_MASK)
42 		BUS_RegMaskedWrite(&cfg->timer->ROUTELOC0,
43 			_TIMER_ROUTELOC0_CC0LOC_MASK <<
44 			(channel * _TIMER_ROUTELOC0_CC1LOC_SHIFT),
45 			cfg->location << (channel * _TIMER_ROUTELOC0_CC1LOC_SHIFT));
46 		BUS_RegMaskedSet(&cfg->timer->ROUTEPEN, 1 << channel);
47 #else
48 #error Unsupported device
49 #endif
50 
51 		compare_config.mode = timerCCModePWM;
52 		TIMER_InitCC(cfg->timer, channel, &compare_config);
53 	}
54 
55 	cfg->timer->CC[channel].CTRL |= (flags & PWM_POLARITY_INVERTED) ?
56 		TIMER_CC_CTRL_OUTINV : 0;
57 
58 	TIMER_TopSet(cfg->timer, period_cycles);
59 
60 	TIMER_CompareBufSet(cfg->timer, channel, pulse_cycles);
61 
62 	return 0;
63 }
64 
pwm_gecko_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)65 static int pwm_gecko_get_cycles_per_sec(const struct device *dev,
66 					uint32_t channel, uint64_t *cycles)
67 {
68 	const struct pwm_gecko_config *cfg = dev->config;
69 
70 	*cycles = CMU_ClockFreqGet(cfg->clock) / cfg->prescaler;
71 
72 	return 0;
73 }
74 
75 static const struct pwm_driver_api pwm_gecko_driver_api = {
76 	.set_cycles = pwm_gecko_set_cycles,
77 	.get_cycles_per_sec = pwm_gecko_get_cycles_per_sec,
78 };
79 
pwm_gecko_init(const struct device * dev)80 static int pwm_gecko_init(const struct device *dev)
81 {
82 	TIMER_Init_TypeDef timer = TIMER_INIT_DEFAULT;
83 	const struct pwm_gecko_config *cfg = dev->config;
84 
85 	CMU_ClockEnable(cfg->clock, true);
86 
87 	CMU_ClockEnable(cmuClock_GPIO, true);
88 	GPIO_PinModeSet(cfg->port, cfg->pin, gpioModePushPull, 0);
89 
90 	timer.prescale = cfg->prescale_enum;
91 	TIMER_Init(cfg->timer, &timer);
92 
93 	return 0;
94 }
95 
96 #define CLOCK_TIMER(id) _CONCAT(cmuClock_TIMER, id)
97 #define PRESCALING_FACTOR(factor) \
98 	((_CONCAT(timerPrescale, factor)))
99 
100 #define PWM_GECKO_INIT(index)							\
101 	static const struct pwm_gecko_config pwm_gecko_config_##index = {	\
102 		.timer = (TIMER_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(index)),	\
103 		.clock = CLOCK_TIMER(index),					\
104 		.prescaler = DT_INST_PROP(index, prescaler),			\
105 		.prescale_enum = (TIMER_Prescale_TypeDef)			\
106 			PRESCALING_FACTOR(DT_INST_PROP(index, prescaler)),	\
107 		.location = DT_INST_PROP_BY_IDX(index, pin_location, 0),	\
108 		.port = (GPIO_Port_TypeDef)					\
109 			DT_INST_PROP_BY_IDX(index, pin_location, 1),		\
110 		.pin = DT_INST_PROP_BY_IDX(index, pin_location, 2),		\
111 	};									\
112 										\
113 	DEVICE_DT_INST_DEFINE(index, &pwm_gecko_init, NULL, NULL,		\
114 				&pwm_gecko_config_##index, POST_KERNEL,		\
115 				CONFIG_PWM_INIT_PRIORITY,			\
116 				&pwm_gecko_driver_api);
117 
118 DT_INST_FOREACH_STATUS_OKAY(PWM_GECKO_INIT)
119