1 /*
2 * Copyright (c) 2018 SiFive Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT sifive_pwm0
8
9 #include <zephyr/arch/cpu.h>
10 #include <zephyr/logging/log.h>
11 #include <zephyr/sys/sys_io.h>
12 #include <zephyr/device.h>
13 #include <zephyr/drivers/pinctrl.h>
14 #include <zephyr/drivers/pwm.h>
15 #include <soc.h>
16
17 LOG_MODULE_REGISTER(pwm_sifive, CONFIG_PWM_LOG_LEVEL);
18
19 /* Macros */
20
21 #define PWM_REG(z_config, _offset) ((mem_addr_t) ((z_config)->base + _offset))
22
23 /* Register Offsets */
24 #define REG_PWMCFG 0x00
25 #define REG_PWMCOUNT 0x08
26 #define REG_PWMS 0x10
27 #define REG_PWMCMP0 0x20
28 #define REG_PWMCMP(_channel) (REG_PWMCMP0 + ((_channel) * 0x4))
29
30 /* Number of PWM Channels */
31 #define SF_NUMCHANNELS 4
32
33 /* pwmcfg Bit Offsets */
34 #define SF_PWMSTICKY 8
35 #define SF_PWMZEROCMP 9
36 #define SF_PWMDEGLITCH 10
37 #define SF_PWMENALWAYS 12
38 #define SF_PWMENONESHOT 13
39 #define SF_PWMCMPCENTER(_channel) (16 + (_channel))
40 #define SF_PWMCMPGANG(_channel) (24 + (_channel))
41 #define SF_PWMCMPIP(_channel) (28 + (_channel))
42
43 /* pwmcount scale factor */
44 #define SF_PWMSCALEMASK 0xF
45 #define SF_PWMSCALE(_val) (SF_PWMSCALEMASK & (_val))
46
47 #define SF_PWMCOUNT_MIN_WIDTH 15
48
49 /* Structure Declarations */
50
51 struct pwm_sifive_data {};
52
53 struct pwm_sifive_cfg {
54 uint32_t base;
55 uint32_t f_sys;
56 uint32_t cmpwidth;
57 const struct pinctrl_dev_config *pcfg;
58 };
59
60 /* Helper Functions */
61
sys_set_mask(mem_addr_t addr,uint32_t mask,uint32_t value)62 static inline void sys_set_mask(mem_addr_t addr, uint32_t mask, uint32_t value)
63 {
64 uint32_t temp = sys_read32(addr);
65
66 temp &= ~(mask);
67 temp |= value;
68
69 sys_write32(temp, addr);
70 }
71
72 /* API Functions */
73
pwm_sifive_init(const struct device * dev)74 static int pwm_sifive_init(const struct device *dev)
75 {
76 const struct pwm_sifive_cfg *config = dev->config;
77 #ifdef CONFIG_PINCTRL
78 int ret;
79
80 ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
81 if (ret < 0) {
82 return ret;
83 }
84 #endif
85
86 /* When pwms == pwmcmp0, reset the counter */
87 sys_set_bit(PWM_REG(config, REG_PWMCFG), SF_PWMZEROCMP);
88
89 /* Enable continuous operation */
90 sys_set_bit(PWM_REG(config, REG_PWMCFG), SF_PWMENALWAYS);
91
92 /* Clear IP config bits */
93 sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMSTICKY);
94 sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMDEGLITCH);
95
96 /* Clear all channels */
97 for (int i = 0; i < SF_NUMCHANNELS; i++) {
98 /* Clear the channel comparator */
99 sys_write32(0, PWM_REG(config, REG_PWMCMP(i)));
100
101 /* Clear the compare center and compare gang bits */
102 sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMCMPCENTER(i));
103 sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMCMPGANG(i));
104 }
105
106 return 0;
107 }
108
pwm_sifive_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)109 static int pwm_sifive_set_cycles(const struct device *dev, uint32_t channel,
110 uint32_t period_cycles, uint32_t pulse_cycles,
111 pwm_flags_t flags)
112 {
113 const struct pwm_sifive_cfg *config = dev->config;
114 uint32_t count_max = 0U;
115 uint32_t max_cmp_val = 0U;
116 uint32_t pwmscale = 0U;
117
118 if (flags) {
119 /* PWM polarity not supported (yet?) */
120 return -ENOTSUP;
121 }
122
123 if (channel >= SF_NUMCHANNELS) {
124 LOG_ERR("The requested PWM channel %d is invalid\n", channel);
125 return -EINVAL;
126 }
127
128 /* Channel 0 sets the period, we can't output PWM with it */
129 if (channel == 0U) {
130 LOG_ERR("PWM channel 0 cannot be configured\n");
131 return -ENOTSUP;
132 }
133
134 /* We can't support periods greater than we can store in pwmcount */
135 count_max = (1 << (config->cmpwidth + SF_PWMCOUNT_MIN_WIDTH)) - 1;
136
137 if (period_cycles > count_max) {
138 LOG_ERR("Requested period is %d but maximum is %d\n",
139 period_cycles, count_max);
140 return -EIO;
141 }
142
143 /* Calculate the maximum value that pwmcmpX can be set to */
144 max_cmp_val = ((1 << config->cmpwidth) - 1);
145
146 /*
147 * Find the minimum value of pwmscale that will allow us to set the
148 * requested period
149 */
150 while ((period_cycles >> pwmscale) > max_cmp_val) {
151 pwmscale++;
152 }
153
154 /* Make sure that we can scale that much */
155 if (pwmscale > SF_PWMSCALEMASK) {
156 LOG_ERR("Requested period is %d but maximum is %d\n",
157 period_cycles, max_cmp_val << pwmscale);
158 return -EIO;
159 }
160
161 /* Set the pwmscale field */
162 sys_set_mask(PWM_REG(config, REG_PWMCFG),
163 SF_PWMSCALEMASK,
164 SF_PWMSCALE(pwmscale));
165
166 /* Set the period by setting pwmcmp0 */
167 sys_write32((period_cycles >> pwmscale), PWM_REG(config, REG_PWMCMP0));
168
169 /* Set the duty cycle by setting pwmcmpX */
170 sys_write32((pulse_cycles >> pwmscale),
171 PWM_REG(config, REG_PWMCMP(channel)));
172
173 LOG_DBG("channel: %d, pwmscale: %d, pwmcmp0: %d, pwmcmp%d: %d",
174 channel,
175 pwmscale,
176 (period_cycles >> pwmscale),
177 channel,
178 (pulse_cycles >> pwmscale));
179
180 return 0;
181 }
182
pwm_sifive_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)183 static int pwm_sifive_get_cycles_per_sec(const struct device *dev,
184 uint32_t channel, uint64_t *cycles)
185 {
186 const struct pwm_sifive_cfg *config;
187
188 if (dev == NULL) {
189 LOG_ERR("The device instance pointer was NULL\n");
190 return -EFAULT;
191 }
192
193 config = dev->config;
194 if (config == NULL) {
195 LOG_ERR("The device configuration is NULL\n");
196 return -EFAULT;
197 }
198
199 /* Fail if we don't have that channel */
200 if (channel >= SF_NUMCHANNELS) {
201 return -EINVAL;
202 }
203
204 *cycles = config->f_sys;
205
206 return 0;
207 }
208
209 /* Device Instantiation */
210
211 static const struct pwm_driver_api pwm_sifive_api = {
212 .set_cycles = pwm_sifive_set_cycles,
213 .get_cycles_per_sec = pwm_sifive_get_cycles_per_sec,
214 };
215
216 #define PWM_SIFIVE_INIT(n) \
217 PINCTRL_DT_INST_DEFINE(n); \
218 static struct pwm_sifive_data pwm_sifive_data_##n; \
219 static const struct pwm_sifive_cfg pwm_sifive_cfg_##n = { \
220 .base = DT_INST_REG_ADDR(n), \
221 .f_sys = SIFIVE_PERIPHERAL_CLOCK_FREQUENCY, \
222 .cmpwidth = DT_INST_PROP(n, sifive_compare_width), \
223 .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
224 }; \
225 DEVICE_DT_INST_DEFINE(n, \
226 pwm_sifive_init, \
227 NULL, \
228 &pwm_sifive_data_##n, \
229 &pwm_sifive_cfg_##n, \
230 POST_KERNEL, \
231 CONFIG_PWM_INIT_PRIORITY, \
232 &pwm_sifive_api);
233
234 DT_INST_FOREACH_STATUS_OKAY(PWM_SIFIVE_INIT)
235