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