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); | |
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 */ | |
7d919999 | 91 | regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN); |
f6b8a570 | 92 | |
c2693514 PC |
93 | /* Start counter */ |
94 | regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm)); | |
f6b8a570 TR |
95 | |
96 | return 0; | |
97 | } | |
98 | ||
99 | static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
100 | { | |
c2693514 | 101 | struct jz4740_pwm_chip *jz = to_jz4740(chip); |
f6b8a570 | 102 | |
6580fd17 PC |
103 | /* |
104 | * Set duty > period. This trick allows the TCU channels in TCU2 mode to | |
105 | * properly return to their init level. | |
106 | */ | |
c2693514 PC |
107 | regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), 0xffff); |
108 | regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), 0x0); | |
6580fd17 PC |
109 | |
110 | /* | |
111 | * Disable PWM output. | |
df56b171 MH |
112 | * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the |
113 | * counter is stopped, while in TCU1 mode the order does not matter. | |
114 | */ | |
7d919999 | 115 | regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN); |
df56b171 MH |
116 | |
117 | /* Stop counter */ | |
c2693514 | 118 | regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm)); |
f6b8a570 TR |
119 | } |
120 | ||
1ac99c58 | 121 | static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
71523d18 | 122 | const struct pwm_state *state) |
f6b8a570 TR |
123 | { |
124 | struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); | |
485b56f0 PC |
125 | unsigned long long tmp = 0xffffull * NSEC_PER_SEC; |
126 | struct clk *clk = pwm_get_chip_data(pwm); | |
127 | unsigned long period, duty; | |
485b56f0 | 128 | long rate; |
ce1f9cec | 129 | int err; |
f6b8a570 | 130 | |
485b56f0 PC |
131 | /* |
132 | * Limit the clock to a maximum rate that still gives us a period value | |
133 | * which fits in 16 bits. | |
134 | */ | |
135 | do_div(tmp, state->period); | |
f6b8a570 | 136 | |
485b56f0 PC |
137 | /* |
138 | * /!\ IMPORTANT NOTE: | |
139 | * ------------------- | |
140 | * This code relies on the fact that clk_round_rate() will always round | |
141 | * down, which is not a valid assumption given by the clk API, but only | |
142 | * happens to be true with the clk drivers used for Ingenic SoCs. | |
143 | * | |
144 | * Right now, there is no alternative as the clk API does not have a | |
145 | * round-down function (and won't have one for a while), but if it ever | |
146 | * comes to light, a round-down function should be used instead. | |
147 | */ | |
148 | rate = clk_round_rate(clk, tmp); | |
149 | if (rate < 0) { | |
150 | dev_err(chip->dev, "Unable to round rate: %ld", rate); | |
151 | return rate; | |
f6b8a570 TR |
152 | } |
153 | ||
485b56f0 PC |
154 | /* Calculate period value */ |
155 | tmp = (unsigned long long)rate * state->period; | |
156 | do_div(tmp, NSEC_PER_SEC); | |
9017dc4f | 157 | period = tmp; |
f6b8a570 | 158 | |
485b56f0 | 159 | /* Calculate duty value */ |
9017dc4f PC |
160 | tmp = (unsigned long long)rate * state->duty_cycle; |
161 | do_div(tmp, NSEC_PER_SEC); | |
a020f22a | 162 | duty = tmp; |
f6b8a570 TR |
163 | |
164 | if (duty >= period) | |
165 | duty = period - 1; | |
166 | ||
1ac99c58 | 167 | jz4740_pwm_disable(chip, pwm); |
f6b8a570 | 168 | |
ce1f9cec PC |
169 | err = clk_set_rate(clk, rate); |
170 | if (err) { | |
171 | dev_err(chip->dev, "Unable to set rate: %d", err); | |
172 | return err; | |
173 | } | |
174 | ||
c2693514 PC |
175 | /* Reset counter to 0 */ |
176 | regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); | |
177 | ||
178 | /* Set duty */ | |
179 | regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); | |
f6b8a570 | 180 | |
c2693514 PC |
181 | /* Set period */ |
182 | regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); | |
f6b8a570 | 183 | |
c2693514 | 184 | /* Set abrupt shutdown */ |
7d919999 PC |
185 | regmap_set_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), |
186 | TCU_TCSR_PWM_SD); | |
c2693514 | 187 | |
a020f22a PC |
188 | /* |
189 | * Set polarity. | |
190 | * | |
191 | * The PWM starts in inactive state until the internal timer reaches the | |
192 | * duty value, then becomes active until the timer reaches the period | |
193 | * value. In theory, we should then use (period - duty) as the real duty | |
194 | * value, as a high duty value would otherwise result in the PWM pin | |
195 | * being inactive most of the time. | |
196 | * | |
197 | * Here, we don't do that, and instead invert the polarity of the PWM | |
198 | * when it is active. This trick makes the PWM start with its active | |
199 | * state instead of its inactive state. | |
200 | */ | |
201 | if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) | |
c2693514 PC |
202 | regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), |
203 | TCU_TCSR_PWM_INITL_HIGH, 0); | |
a020f22a | 204 | else |
c2693514 PC |
205 | regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), |
206 | TCU_TCSR_PWM_INITL_HIGH, | |
207 | TCU_TCSR_PWM_INITL_HIGH); | |
174dcc8e | 208 | |
1ac99c58 PC |
209 | if (state->enabled) |
210 | jz4740_pwm_enable(chip, pwm); | |
211 | ||
174dcc8e PC |
212 | return 0; |
213 | } | |
214 | ||
f6b8a570 TR |
215 | static const struct pwm_ops jz4740_pwm_ops = { |
216 | .request = jz4740_pwm_request, | |
217 | .free = jz4740_pwm_free, | |
1ac99c58 | 218 | .apply = jz4740_pwm_apply, |
f6b8a570 TR |
219 | .owner = THIS_MODULE, |
220 | }; | |
221 | ||
3e9fe83d | 222 | static int jz4740_pwm_probe(struct platform_device *pdev) |
f6b8a570 | 223 | { |
c2693514 | 224 | struct device *dev = &pdev->dev; |
f6b8a570 | 225 | struct jz4740_pwm_chip *jz4740; |
74db728c PC |
226 | const struct soc_info *info; |
227 | ||
228 | info = device_get_match_data(dev); | |
229 | if (!info) | |
230 | return -EINVAL; | |
f6b8a570 | 231 | |
c2693514 | 232 | jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL); |
f6b8a570 TR |
233 | if (!jz4740) |
234 | return -ENOMEM; | |
235 | ||
c2693514 PC |
236 | jz4740->map = device_node_to_regmap(dev->parent->of_node); |
237 | if (IS_ERR(jz4740->map)) { | |
238 | dev_err(dev, "regmap not found: %ld\n", PTR_ERR(jz4740->map)); | |
239 | return PTR_ERR(jz4740->map); | |
240 | } | |
241 | ||
242 | jz4740->chip.dev = dev; | |
f6b8a570 | 243 | jz4740->chip.ops = &jz4740_pwm_ops; |
74db728c | 244 | jz4740->chip.npwm = info->num_pwms; |
f6b8a570 | 245 | |
f0d6d7f2 | 246 | return devm_pwmchip_add(dev, &jz4740->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"); |