1 /*
2  * Copyright (c) 2018, Diego Sueiro <diego.sueiro@gmail.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <zephyr/drivers/pwm.h>
9 #include <zephyr/kernel.h>
10 #include <soc.h>
11 #include <device_imx.h>
12 #include <zephyr/drivers/pinctrl.h>
13 
14 #include <zephyr/logging/log.h>
15 
16 LOG_MODULE_REGISTER(pwm_imx, CONFIG_PWM_LOG_LEVEL);
17 
18 #define PWM_PWMSR_FIFOAV_4WORDS	0x4
19 
20 #define PWM_PWMCR_SWR(x) (((uint32_t)(((uint32_t)(x)) \
21 				<<PWM_PWMCR_SWR_SHIFT))&PWM_PWMCR_SWR_MASK)
22 
23 struct imx_pwm_config {
24 	PWM_Type *base;
25 	uint16_t prescaler;
26 	const struct pinctrl_dev_config *pincfg;
27 };
28 
29 struct imx_pwm_data {
30 	uint32_t period_cycles;
31 };
32 
33 
imx_pwm_is_enabled(PWM_Type * base)34 static bool imx_pwm_is_enabled(PWM_Type *base)
35 {
36 	return PWM_PWMCR_REG(base) & PWM_PWMCR_EN_MASK;
37 }
38 
imx_pwm_get_cycles_per_sec(const struct device * dev,uint32_t pwm,uint64_t * cycles)39 static int imx_pwm_get_cycles_per_sec(const struct device *dev, uint32_t pwm,
40 				      uint64_t *cycles)
41 {
42 	const struct imx_pwm_config *config = dev->config;
43 
44 	*cycles = get_pwm_clock_freq(config->base) >> config->prescaler;
45 
46 	return 0;
47 }
48 
imx_pwm_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)49 static int imx_pwm_set_cycles(const struct device *dev, uint32_t channel,
50 			      uint32_t period_cycles, uint32_t pulse_cycles,
51 			      pwm_flags_t flags)
52 {
53 	const struct imx_pwm_config *config = dev->config;
54 	struct imx_pwm_data *data = dev->data;
55 	unsigned int period_ms;
56 	bool enabled = imx_pwm_is_enabled(config->base);
57 	int wait_count = 0, fifoav;
58 	uint32_t cr, sr;
59 
60 
61 	if (period_cycles == 0U) {
62 		LOG_ERR("Channel can not be set to inactive level");
63 		return -ENOTSUP;
64 	}
65 
66 	if (flags) {
67 		/* PWM polarity not supported (yet?) */
68 		return -ENOTSUP;
69 	}
70 
71 	LOG_DBG("enabled=%d, pulse_cycles=%d, period_cycles=%d,"
72 		    " duty_cycle=%d\n", enabled, pulse_cycles, period_cycles,
73 		    (pulse_cycles * 100U / period_cycles));
74 
75 	/*
76 	 * i.MX PWMv2 has a 4-word sample FIFO.
77 	 * In order to avoid FIFO overflow issue, we do software reset
78 	 * to clear all sample FIFO if the controller is disabled or
79 	 * wait for a full PWM cycle to get a relinquished FIFO slot
80 	 * when the controller is enabled and the FIFO is fully loaded.
81 	 */
82 	if (enabled) {
83 		sr = PWM_PWMSR_REG(config->base);
84 		fifoav = PWM_PWMSR_FIFOAV(sr);
85 		if (fifoav == PWM_PWMSR_FIFOAV_4WORDS) {
86 			period_ms = (get_pwm_clock_freq(config->base) >>
87 					config->prescaler) * MSEC_PER_SEC;
88 			k_sleep(K_MSEC(period_ms));
89 
90 			sr = PWM_PWMSR_REG(config->base);
91 			if (fifoav == PWM_PWMSR_FIFOAV(sr)) {
92 				LOG_WRN("there is no free FIFO slot\n");
93 			}
94 		}
95 	} else {
96 		PWM_PWMCR_REG(config->base) = PWM_PWMCR_SWR(1);
97 		do {
98 			k_sleep(K_MSEC(1));
99 			cr = PWM_PWMCR_REG(config->base);
100 		} while ((PWM_PWMCR_SWR(cr)) &&
101 			 (++wait_count < CONFIG_PWM_PWMSWR_LOOP));
102 
103 		if (PWM_PWMCR_SWR(cr)) {
104 			LOG_WRN("software reset timeout\n");
105 		}
106 
107 	}
108 
109 	/*
110 	 * according to imx pwm RM, the real period value should be
111 	 * PERIOD value in PWMPR plus 2.
112 	 */
113 	if (period_cycles > 2) {
114 		period_cycles -= 2U;
115 	} else {
116 		return -EINVAL;
117 	}
118 
119 	PWM_PWMSAR_REG(config->base) = pulse_cycles;
120 
121 	if (data->period_cycles != period_cycles) {
122 		LOG_WRN("Changing period cycles from %d to %d in %s",
123 			    data->period_cycles, period_cycles,
124 			    dev->name);
125 
126 		data->period_cycles = period_cycles;
127 		PWM_PWMPR_REG(config->base) = period_cycles;
128 	}
129 
130 	cr = PWM_PWMCR_EN_MASK | PWM_PWMCR_PRESCALER(config->prescaler) |
131 		PWM_PWMCR_DOZEN_MASK | PWM_PWMCR_WAITEN_MASK |
132 		PWM_PWMCR_DBGEN_MASK | PWM_PWMCR_CLKSRC(2);
133 
134 	PWM_PWMCR_REG(config->base) = cr;
135 
136 	return 0;
137 }
138 
imx_pwm_init(const struct device * dev)139 static int imx_pwm_init(const struct device *dev)
140 {
141 	const struct imx_pwm_config *config = dev->config;
142 	struct imx_pwm_data *data = dev->data;
143 	int err;
144 
145 	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
146 	if (err) {
147 		return err;
148 	}
149 
150 	PWM_PWMPR_REG(config->base) = data->period_cycles;
151 
152 	return 0;
153 }
154 
155 static const struct pwm_driver_api imx_pwm_driver_api = {
156 	.set_cycles = imx_pwm_set_cycles,
157 	.get_cycles_per_sec = imx_pwm_get_cycles_per_sec,
158 };
159 
160 #define PWM_IMX_INIT(n)							\
161 	PINCTRL_DT_INST_DEFINE(n);					\
162 	static const struct imx_pwm_config imx_pwm_config_##n = {	\
163 		.base = (PWM_Type *)DT_INST_REG_ADDR(n),		\
164 		.prescaler = DT_INST_PROP(n, prescaler),		\
165 		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),		\
166 	};								\
167 									\
168 	static struct imx_pwm_data imx_pwm_data_##n;			\
169 									\
170 	DEVICE_DT_INST_DEFINE(n, &imx_pwm_init, NULL,			\
171 			    &imx_pwm_data_##n,				\
172 			    &imx_pwm_config_##n, POST_KERNEL,		\
173 			    CONFIG_PWM_INIT_PRIORITY,			\
174 			    &imx_pwm_driver_api);
175 
176 #if DT_HAS_COMPAT_STATUS_OKAY(fsl_imx27_pwm)
177 #define DT_DRV_COMPAT fsl_imx27_pwm
178 DT_INST_FOREACH_STATUS_OKAY(PWM_IMX_INIT)
179 #endif
180