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> |
0a41b0c5 | 18 | #include <linux/of.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 | 27 | struct jz4740_pwm_chip { |
c2693514 | 28 | struct regmap *map; |
89c8de78 | 29 | struct clk *clk[]; |
f6b8a570 TR |
30 | }; |
31 | ||
32 | static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) | |
33 | { | |
5d0237a7 | 34 | return pwmchip_get_drvdata(chip); |
f6b8a570 TR |
35 | } |
36 | ||
cebf107d | 37 | static bool jz4740_pwm_can_use_chn(struct pwm_chip *chip, unsigned int channel) |
a2005fc7 PC |
38 | { |
39 | /* Enable all TCU channels for PWM use by default except channels 0/1 */ | |
cebf107d | 40 | u32 pwm_channels_mask = GENMASK(chip->npwm - 1, 2); |
a2005fc7 | 41 | |
4eeb3322 | 42 | device_property_read_u32(pwmchip_parent(chip)->parent, |
a2005fc7 PC |
43 | "ingenic,pwm-channels-mask", |
44 | &pwm_channels_mask); | |
45 | ||
46 | return !!(pwm_channels_mask & BIT(channel)); | |
47 | } | |
48 | ||
f6b8a570 TR |
49 | static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) |
50 | { | |
ce1f9cec PC |
51 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
52 | struct clk *clk; | |
53 | char name[16]; | |
54 | int err; | |
55 | ||
cebf107d | 56 | if (!jz4740_pwm_can_use_chn(chip, pwm->hwpwm)) |
f6b8a570 TR |
57 | return -EBUSY; |
58 | ||
ce1f9cec PC |
59 | snprintf(name, sizeof(name), "timer%u", pwm->hwpwm); |
60 | ||
4eeb3322 | 61 | clk = clk_get(pwmchip_parent(chip), name); |
9320fc50 | 62 | if (IS_ERR(clk)) { |
4eeb3322 UKK |
63 | dev_err(pwmchip_parent(chip), |
64 | "error %pe: Failed to get clock\n", clk); | |
9320fc50 UKK |
65 | return PTR_ERR(clk); |
66 | } | |
ce1f9cec PC |
67 | |
68 | err = clk_prepare_enable(clk); | |
69 | if (err < 0) { | |
70 | clk_put(clk); | |
71 | return err; | |
72 | } | |
73 | ||
89c8de78 | 74 | jz->clk[pwm->hwpwm] = clk; |
f6b8a570 TR |
75 | |
76 | return 0; | |
77 | } | |
78 | ||
79 | static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | |
80 | { | |
89c8de78 UKK |
81 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
82 | struct clk *clk = jz->clk[pwm->hwpwm]; | |
f6b8a570 | 83 | |
ce1f9cec PC |
84 | clk_disable_unprepare(clk); |
85 | clk_put(clk); | |
f6b8a570 TR |
86 | } |
87 | ||
88 | static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
89 | { | |
c2693514 PC |
90 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
91 | ||
92 | /* Enable PWM output */ | |
7d919999 | 93 | regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN); |
f6b8a570 | 94 | |
c2693514 PC |
95 | /* Start counter */ |
96 | regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm)); | |
f6b8a570 TR |
97 | |
98 | return 0; | |
99 | } | |
100 | ||
101 | static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
102 | { | |
c2693514 | 103 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
f6b8a570 | 104 | |
6580fd17 PC |
105 | /* |
106 | * Set duty > period. This trick allows the TCU channels in TCU2 mode to | |
107 | * properly return to their init level. | |
108 | */ | |
c2693514 PC |
109 | regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), 0xffff); |
110 | regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), 0x0); | |
6580fd17 PC |
111 | |
112 | /* | |
113 | * Disable PWM output. | |
df56b171 MH |
114 | * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the |
115 | * counter is stopped, while in TCU1 mode the order does not matter. | |
116 | */ | |
7d919999 | 117 | regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN); |
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 | 125 | { |
80943bbd | 126 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
485b56f0 | 127 | unsigned long long tmp = 0xffffull * NSEC_PER_SEC; |
89c8de78 | 128 | struct clk *clk = jz->clk[pwm->hwpwm]; |
485b56f0 | 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) { | |
4eeb3322 | 152 | dev_err(pwmchip_parent(chip), "Unable to round rate: %ld\n", rate); |
485b56f0 | 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) { | |
4eeb3322 | 173 | dev_err(pwmchip_parent(chip), "Unable to set rate: %d\n", err); |
ce1f9cec PC |
174 | return err; |
175 | } | |
176 | ||
c2693514 | 177 | /* Reset counter to 0 */ |
7a3663c2 | 178 | regmap_write(jz->map, TCU_REG_TCNTc(pwm->hwpwm), 0); |
c2693514 PC |
179 | |
180 | /* Set duty */ | |
7a3663c2 | 181 | regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), duty); |
f6b8a570 | 182 | |
c2693514 | 183 | /* Set period */ |
7a3663c2 | 184 | regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), period); |
f6b8a570 | 185 | |
c2693514 | 186 | /* Set abrupt shutdown */ |
7a3663c2 | 187 | regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), |
7d919999 | 188 | TCU_TCSR_PWM_SD); |
c2693514 | 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) | |
32b4f1a4 UKK |
204 | regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), |
205 | TCU_TCSR_PWM_INITL_HIGH); | |
a020f22a | 206 | else |
32b4f1a4 UKK |
207 | regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), |
208 | TCU_TCSR_PWM_INITL_HIGH); | |
174dcc8e | 209 | |
1ac99c58 PC |
210 | if (state->enabled) |
211 | jz4740_pwm_enable(chip, pwm); | |
212 | ||
174dcc8e PC |
213 | return 0; |
214 | } | |
215 | ||
f6b8a570 TR |
216 | static const struct pwm_ops jz4740_pwm_ops = { |
217 | .request = jz4740_pwm_request, | |
218 | .free = jz4740_pwm_free, | |
1ac99c58 | 219 | .apply = jz4740_pwm_apply, |
f6b8a570 TR |
220 | }; |
221 | ||
3e9fe83d | 222 | static int jz4740_pwm_probe(struct platform_device *pdev) |
f6b8a570 | 223 | { |
c2693514 | 224 | struct device *dev = &pdev->dev; |
5d0237a7 | 225 | struct pwm_chip *chip; |
7a3663c2 | 226 | struct jz4740_pwm_chip *jz; |
74db728c PC |
227 | const struct soc_info *info; |
228 | ||
229 | info = device_get_match_data(dev); | |
230 | if (!info) | |
231 | return -EINVAL; | |
f6b8a570 | 232 | |
5d0237a7 UKK |
233 | chip = devm_pwmchip_alloc(dev, info->num_pwms, struct_size(jz, clk, info->num_pwms)); |
234 | if (IS_ERR(chip)) | |
235 | return PTR_ERR(chip); | |
236 | jz = to_jz4740(chip); | |
f6b8a570 | 237 | |
7a3663c2 UKK |
238 | jz->map = device_node_to_regmap(dev->parent->of_node); |
239 | if (IS_ERR(jz->map)) { | |
240 | dev_err(dev, "regmap not found: %ld\n", PTR_ERR(jz->map)); | |
241 | return PTR_ERR(jz->map); | |
c2693514 PC |
242 | } |
243 | ||
5d0237a7 | 244 | chip->ops = &jz4740_pwm_ops; |
f6b8a570 | 245 | |
5d0237a7 | 246 | return devm_pwmchip_add(dev, chip); |
f6b8a570 TR |
247 | } |
248 | ||
731c4793 | 249 | static const struct soc_info jz4740_soc_info = { |
74db728c PC |
250 | .num_pwms = 8, |
251 | }; | |
252 | ||
731c4793 | 253 | static const struct soc_info jz4725b_soc_info = { |
74db728c PC |
254 | .num_pwms = 6, |
255 | }; | |
256 | ||
731c4793 | 257 | static const struct soc_info x1000_soc_info = { |
5a471520 AM |
258 | .num_pwms = 5, |
259 | }; | |
260 | ||
cc201733 | 261 | static const struct of_device_id jz4740_pwm_dt_ids[] = { |
74db728c PC |
262 | { .compatible = "ingenic,jz4740-pwm", .data = &jz4740_soc_info }, |
263 | { .compatible = "ingenic,jz4725b-pwm", .data = &jz4725b_soc_info }, | |
5a471520 | 264 | { .compatible = "ingenic,x1000-pwm", .data = &x1000_soc_info }, |
cc201733 PC |
265 | {}, |
266 | }; | |
267 | MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); | |
cc201733 | 268 | |
f6b8a570 TR |
269 | static struct platform_driver jz4740_pwm_driver = { |
270 | .driver = { | |
271 | .name = "jz4740-pwm", | |
731c4793 | 272 | .of_match_table = jz4740_pwm_dt_ids, |
f6b8a570 TR |
273 | }, |
274 | .probe = jz4740_pwm_probe, | |
f6b8a570 TR |
275 | }; |
276 | module_platform_driver(jz4740_pwm_driver); | |
277 | ||
278 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
279 | MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); | |
280 | MODULE_ALIAS("platform:jz4740-pwm"); | |
281 | MODULE_LICENSE("GPL"); |