Commit | Line | Data |
---|---|---|
a912e80b | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
f6b8a570 TR |
2 | /* |
3 | * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> | |
4 | * JZ4740 platform PWM support | |
3b442c60 UKK |
5 | * |
6 | * Limitations: | |
7 | * - The .apply callback doesn't complete the currently running period before | |
8 | * reconfiguring the hardware. | |
f6b8a570 TR |
9 | */ |
10 | ||
11 | #include <linux/clk.h> | |
12 | #include <linux/err.h> | |
13 | #include <linux/gpio.h> | |
14 | #include <linux/kernel.h> | |
c2693514 PC |
15 | #include <linux/mfd/ingenic-tcu.h> |
16 | #include <linux/mfd/syscon.h> | |
f6b8a570 | 17 | #include <linux/module.h> |
cc201733 | 18 | #include <linux/of_device.h> |
f6b8a570 TR |
19 | #include <linux/platform_device.h> |
20 | #include <linux/pwm.h> | |
c2693514 | 21 | #include <linux/regmap.h> |
f6b8a570 | 22 | |
74db728c PC |
23 | struct soc_info { |
24 | unsigned int num_pwms; | |
25 | }; | |
f6b8a570 | 26 | |
f6b8a570 TR |
27 | struct jz4740_pwm_chip { |
28 | struct pwm_chip chip; | |
c2693514 | 29 | struct regmap *map; |
f6b8a570 TR |
30 | }; |
31 | ||
32 | static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) | |
33 | { | |
34 | return container_of(chip, struct jz4740_pwm_chip, chip); | |
35 | } | |
36 | ||
a2005fc7 PC |
37 | static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz, |
38 | unsigned int channel) | |
39 | { | |
40 | /* Enable all TCU channels for PWM use by default except channels 0/1 */ | |
74db728c | 41 | u32 pwm_channels_mask = GENMASK(jz->chip.npwm - 1, 2); |
a2005fc7 PC |
42 | |
43 | device_property_read_u32(jz->chip.dev->parent, | |
44 | "ingenic,pwm-channels-mask", | |
45 | &pwm_channels_mask); | |
46 | ||
47 | return !!(pwm_channels_mask & BIT(channel)); | |
48 | } | |
49 | ||
f6b8a570 TR |
50 | static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) |
51 | { | |
ce1f9cec PC |
52 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
53 | struct clk *clk; | |
54 | char name[16]; | |
55 | int err; | |
56 | ||
a2005fc7 | 57 | if (!jz4740_pwm_can_use_chn(jz, pwm->hwpwm)) |
f6b8a570 TR |
58 | return -EBUSY; |
59 | ||
ce1f9cec PC |
60 | snprintf(name, sizeof(name), "timer%u", pwm->hwpwm); |
61 | ||
62 | clk = clk_get(chip->dev, name); | |
63 | if (IS_ERR(clk)) { | |
64 | if (PTR_ERR(clk) != -EPROBE_DEFER) | |
65 | dev_err(chip->dev, "Failed to get clock: %pe", clk); | |
66 | ||
67 | return PTR_ERR(clk); | |
68 | } | |
69 | ||
70 | err = clk_prepare_enable(clk); | |
71 | if (err < 0) { | |
72 | clk_put(clk); | |
73 | return err; | |
74 | } | |
75 | ||
76 | pwm_set_chip_data(pwm, clk); | |
f6b8a570 TR |
77 | |
78 | return 0; | |
79 | } | |
80 | ||
81 | static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | |
82 | { | |
ce1f9cec | 83 | struct clk *clk = pwm_get_chip_data(pwm); |
f6b8a570 | 84 | |
ce1f9cec PC |
85 | clk_disable_unprepare(clk); |
86 | clk_put(clk); | |
f6b8a570 TR |
87 | } |
88 | ||
89 | static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
90 | { | |
c2693514 PC |
91 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
92 | ||
93 | /* Enable PWM output */ | |
94 | regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), | |
95 | TCU_TCSR_PWM_EN, TCU_TCSR_PWM_EN); | |
f6b8a570 | 96 | |
c2693514 PC |
97 | /* Start counter */ |
98 | regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm)); | |
f6b8a570 TR |
99 | |
100 | return 0; | |
101 | } | |
102 | ||
103 | static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
104 | { | |
c2693514 | 105 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
f6b8a570 | 106 | |
6580fd17 PC |
107 | /* |
108 | * Set duty > period. This trick allows the TCU channels in TCU2 mode to | |
109 | * properly return to their init level. | |
110 | */ | |
c2693514 PC |
111 | regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), 0xffff); |
112 | regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), 0x0); | |
6580fd17 PC |
113 | |
114 | /* | |
115 | * Disable PWM output. | |
df56b171 MH |
116 | * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the |
117 | * counter is stopped, while in TCU1 mode the order does not matter. | |
118 | */ | |
c2693514 PC |
119 | regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), |
120 | TCU_TCSR_PWM_EN, 0); | |
df56b171 MH |
121 | |
122 | /* Stop counter */ | |
c2693514 | 123 | regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm)); |
f6b8a570 TR |
124 | } |
125 | ||
1ac99c58 | 126 | static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
71523d18 | 127 | const struct pwm_state *state) |
f6b8a570 TR |
128 | { |
129 | struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); | |
485b56f0 PC |
130 | unsigned long long tmp = 0xffffull * NSEC_PER_SEC; |
131 | struct clk *clk = pwm_get_chip_data(pwm); | |
132 | unsigned long period, duty; | |
485b56f0 | 133 | long rate; |
ce1f9cec | 134 | int err; |
f6b8a570 | 135 | |
485b56f0 PC |
136 | /* |
137 | * Limit the clock to a maximum rate that still gives us a period value | |
138 | * which fits in 16 bits. | |
139 | */ | |
140 | do_div(tmp, state->period); | |
f6b8a570 | 141 | |
485b56f0 PC |
142 | /* |
143 | * /!\ IMPORTANT NOTE: | |
144 | * ------------------- | |
145 | * This code relies on the fact that clk_round_rate() will always round | |
146 | * down, which is not a valid assumption given by the clk API, but only | |
147 | * happens to be true with the clk drivers used for Ingenic SoCs. | |
148 | * | |
149 | * Right now, there is no alternative as the clk API does not have a | |
150 | * round-down function (and won't have one for a while), but if it ever | |
151 | * comes to light, a round-down function should be used instead. | |
152 | */ | |
153 | rate = clk_round_rate(clk, tmp); | |
154 | if (rate < 0) { | |
155 | dev_err(chip->dev, "Unable to round rate: %ld", rate); | |
156 | return rate; | |
f6b8a570 TR |
157 | } |
158 | ||
485b56f0 PC |
159 | /* Calculate period value */ |
160 | tmp = (unsigned long long)rate * state->period; | |
161 | do_div(tmp, NSEC_PER_SEC); | |
9017dc4f | 162 | period = tmp; |
f6b8a570 | 163 | |
485b56f0 | 164 | /* Calculate duty value */ |
9017dc4f PC |
165 | tmp = (unsigned long long)rate * state->duty_cycle; |
166 | do_div(tmp, NSEC_PER_SEC); | |
a020f22a | 167 | duty = tmp; |
f6b8a570 TR |
168 | |
169 | if (duty >= period) | |
170 | duty = period - 1; | |
171 | ||
1ac99c58 | 172 | jz4740_pwm_disable(chip, pwm); |
f6b8a570 | 173 | |
ce1f9cec PC |
174 | err = clk_set_rate(clk, rate); |
175 | if (err) { | |
176 | dev_err(chip->dev, "Unable to set rate: %d", err); | |
177 | return err; | |
178 | } | |
179 | ||
c2693514 PC |
180 | /* Reset counter to 0 */ |
181 | regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); | |
182 | ||
183 | /* Set duty */ | |
184 | regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); | |
f6b8a570 | 185 | |
c2693514 PC |
186 | /* Set period */ |
187 | regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); | |
f6b8a570 | 188 | |
c2693514 PC |
189 | /* Set abrupt shutdown */ |
190 | regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), | |
191 | TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); | |
192 | ||
a020f22a PC |
193 | /* |
194 | * Set polarity. | |
195 | * | |
196 | * The PWM starts in inactive state until the internal timer reaches the | |
197 | * duty value, then becomes active until the timer reaches the period | |
198 | * value. In theory, we should then use (period - duty) as the real duty | |
199 | * value, as a high duty value would otherwise result in the PWM pin | |
200 | * being inactive most of the time. | |
201 | * | |
202 | * Here, we don't do that, and instead invert the polarity of the PWM | |
203 | * when it is active. This trick makes the PWM start with its active | |
204 | * state instead of its inactive state. | |
205 | */ | |
206 | if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) | |
c2693514 PC |
207 | regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), |
208 | TCU_TCSR_PWM_INITL_HIGH, 0); | |
a020f22a | 209 | else |
c2693514 PC |
210 | regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), |
211 | TCU_TCSR_PWM_INITL_HIGH, | |
212 | TCU_TCSR_PWM_INITL_HIGH); | |
174dcc8e | 213 | |
1ac99c58 PC |
214 | if (state->enabled) |
215 | jz4740_pwm_enable(chip, pwm); | |
216 | ||
174dcc8e PC |
217 | return 0; |
218 | } | |
219 | ||
f6b8a570 TR |
220 | static const struct pwm_ops jz4740_pwm_ops = { |
221 | .request = jz4740_pwm_request, | |
222 | .free = jz4740_pwm_free, | |
1ac99c58 | 223 | .apply = jz4740_pwm_apply, |
f6b8a570 TR |
224 | .owner = THIS_MODULE, |
225 | }; | |
226 | ||
3e9fe83d | 227 | static int jz4740_pwm_probe(struct platform_device *pdev) |
f6b8a570 | 228 | { |
c2693514 | 229 | struct device *dev = &pdev->dev; |
f6b8a570 | 230 | struct jz4740_pwm_chip *jz4740; |
74db728c PC |
231 | const struct soc_info *info; |
232 | ||
233 | info = device_get_match_data(dev); | |
234 | if (!info) | |
235 | return -EINVAL; | |
f6b8a570 | 236 | |
c2693514 | 237 | jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL); |
f6b8a570 TR |
238 | if (!jz4740) |
239 | return -ENOMEM; | |
240 | ||
c2693514 PC |
241 | jz4740->map = device_node_to_regmap(dev->parent->of_node); |
242 | if (IS_ERR(jz4740->map)) { | |
243 | dev_err(dev, "regmap not found: %ld\n", PTR_ERR(jz4740->map)); | |
244 | return PTR_ERR(jz4740->map); | |
245 | } | |
246 | ||
247 | jz4740->chip.dev = dev; | |
f6b8a570 | 248 | jz4740->chip.ops = &jz4740_pwm_ops; |
74db728c | 249 | jz4740->chip.npwm = info->num_pwms; |
f6b8a570 | 250 | jz4740->chip.base = -1; |
cc201733 PC |
251 | jz4740->chip.of_xlate = of_pwm_xlate_with_flags; |
252 | jz4740->chip.of_pwm_n_cells = 3; | |
f6b8a570 | 253 | |
f6b8a570 TR |
254 | platform_set_drvdata(pdev, jz4740); |
255 | ||
0dc1135f | 256 | return pwmchip_add(&jz4740->chip); |
f6b8a570 TR |
257 | } |
258 | ||
77f37917 | 259 | static int jz4740_pwm_remove(struct platform_device *pdev) |
f6b8a570 TR |
260 | { |
261 | struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); | |
f6b8a570 | 262 | |
0dc1135f | 263 | return pwmchip_remove(&jz4740->chip); |
f6b8a570 TR |
264 | } |
265 | ||
74db728c PC |
266 | static const struct soc_info __maybe_unused jz4740_soc_info = { |
267 | .num_pwms = 8, | |
268 | }; | |
269 | ||
270 | static const struct soc_info __maybe_unused jz4725b_soc_info = { | |
271 | .num_pwms = 6, | |
272 | }; | |
273 | ||
cc201733 PC |
274 | #ifdef CONFIG_OF |
275 | static const struct of_device_id jz4740_pwm_dt_ids[] = { | |
74db728c PC |
276 | { .compatible = "ingenic,jz4740-pwm", .data = &jz4740_soc_info }, |
277 | { .compatible = "ingenic,jz4725b-pwm", .data = &jz4725b_soc_info }, | |
cc201733 PC |
278 | {}, |
279 | }; | |
280 | MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); | |
281 | #endif | |
282 | ||
f6b8a570 TR |
283 | static struct platform_driver jz4740_pwm_driver = { |
284 | .driver = { | |
285 | .name = "jz4740-pwm", | |
cc201733 | 286 | .of_match_table = of_match_ptr(jz4740_pwm_dt_ids), |
f6b8a570 TR |
287 | }, |
288 | .probe = jz4740_pwm_probe, | |
fd109112 | 289 | .remove = jz4740_pwm_remove, |
f6b8a570 TR |
290 | }; |
291 | module_platform_driver(jz4740_pwm_driver); | |
292 | ||
293 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
294 | MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); | |
295 | MODULE_ALIAS("platform:jz4740-pwm"); | |
296 | MODULE_LICENSE("GPL"); |