diff options
author | Neil Armstrong <narmstrong@baylibre.com> | 2015-11-02 06:14:21 -0500 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2015-12-16 11:25:37 -0500 |
commit | 6604c6556db9e41c85f2839f66bd9d617bcf9f87 (patch) | |
tree | df8c02d08ed1d824c158ead4c10999b295b652ae /drivers/pwm/pwm-omap-dmtimer.c | |
parent | 72c16a9f98afad073b4a9c947c1c89bfb886ffcb (diff) |
pwm: Add PWM driver for OMAP using dual-mode timers
Adds support for using a OMAP dual-mode timer with PWM capability
as a Linux PWM device. The driver controls the timer by using the
dmtimer API.
Add a platform_data structure for each pwm-omap-dmtimer nodes containing
the dmtimers functions in order to get driver not rely on platform
specific functions.
Cc: Grant Erickson <marathon96@gmail.com>
Cc: NeilBrown <neilb@suse.de>
Cc: Joachim Eastwood <manabian@gmail.com>
Suggested-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Tony Lindgren <tony@atomide.com>
[thierry.reding@gmail.com: coding style bikeshed, fix timer leak]
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm/pwm-omap-dmtimer.c')
-rw-r--r-- | drivers/pwm/pwm-omap-dmtimer.c | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c new file mode 100644 index 000000000000..c453b3360605 --- /dev/null +++ b/drivers/pwm/pwm-omap-dmtimer.c | |||
@@ -0,0 +1,327 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015 Neil Armstrong <narmstrong@baylibre.com> | ||
3 | * Copyright (c) 2014 Joachim Eastwood <manabian@gmail.com> | ||
4 | * Copyright (c) 2012 NeilBrown <neilb@suse.de> | ||
5 | * Heavily based on earlier code which is: | ||
6 | * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com> | ||
7 | * | ||
8 | * Also based on pwm-samsung.c | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License | ||
12 | * version 2 as published by the Free Software Foundation. | ||
13 | * | ||
14 | * Description: | ||
15 | * This file is the core OMAP support for the generic, Linux | ||
16 | * PWM driver / controller, using the OMAP's dual-mode timers. | ||
17 | */ | ||
18 | |||
19 | #include <linux/clk.h> | ||
20 | #include <linux/err.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/mutex.h> | ||
24 | #include <linux/of.h> | ||
25 | #include <linux/of_platform.h> | ||
26 | #include <linux/platform_data/pwm_omap_dmtimer.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/pm_runtime.h> | ||
29 | #include <linux/pwm.h> | ||
30 | #include <linux/slab.h> | ||
31 | #include <linux/time.h> | ||
32 | |||
33 | #define DM_TIMER_LOAD_MIN 0xfffffffe | ||
34 | |||
35 | struct pwm_omap_dmtimer_chip { | ||
36 | struct pwm_chip chip; | ||
37 | struct mutex mutex; | ||
38 | pwm_omap_dmtimer *dm_timer; | ||
39 | struct pwm_omap_dmtimer_pdata *pdata; | ||
40 | struct platform_device *dm_timer_pdev; | ||
41 | }; | ||
42 | |||
43 | static inline struct pwm_omap_dmtimer_chip * | ||
44 | to_pwm_omap_dmtimer_chip(struct pwm_chip *chip) | ||
45 | { | ||
46 | return container_of(chip, struct pwm_omap_dmtimer_chip, chip); | ||
47 | } | ||
48 | |||
49 | static int pwm_omap_dmtimer_calc_value(unsigned long clk_rate, int ns) | ||
50 | { | ||
51 | u64 c = (u64)clk_rate * ns; | ||
52 | |||
53 | do_div(c, NSEC_PER_SEC); | ||
54 | |||
55 | return DM_TIMER_LOAD_MIN - c; | ||
56 | } | ||
57 | |||
58 | static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap) | ||
59 | { | ||
60 | /* | ||
61 | * According to OMAP 4 TRM section 22.2.4.10 the counter should be | ||
62 | * started at 0xFFFFFFFE when overflow and match is used to ensure | ||
63 | * that the PWM line is toggled on the first event. | ||
64 | * | ||
65 | * Note that omap_dm_timer_enable/disable is for register access and | ||
66 | * not the timer counter itself. | ||
67 | */ | ||
68 | omap->pdata->enable(omap->dm_timer); | ||
69 | omap->pdata->write_counter(omap->dm_timer, DM_TIMER_LOAD_MIN); | ||
70 | omap->pdata->disable(omap->dm_timer); | ||
71 | |||
72 | omap->pdata->start(omap->dm_timer); | ||
73 | } | ||
74 | |||
75 | static int pwm_omap_dmtimer_enable(struct pwm_chip *chip, | ||
76 | struct pwm_device *pwm) | ||
77 | { | ||
78 | struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); | ||
79 | |||
80 | mutex_lock(&omap->mutex); | ||
81 | pwm_omap_dmtimer_start(omap); | ||
82 | mutex_unlock(&omap->mutex); | ||
83 | |||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | static void pwm_omap_dmtimer_disable(struct pwm_chip *chip, | ||
88 | struct pwm_device *pwm) | ||
89 | { | ||
90 | struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); | ||
91 | |||
92 | mutex_lock(&omap->mutex); | ||
93 | omap->pdata->stop(omap->dm_timer); | ||
94 | mutex_unlock(&omap->mutex); | ||
95 | } | ||
96 | |||
97 | static int pwm_omap_dmtimer_config(struct pwm_chip *chip, | ||
98 | struct pwm_device *pwm, | ||
99 | int duty_ns, int period_ns) | ||
100 | { | ||
101 | struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); | ||
102 | int load_value, match_value; | ||
103 | struct clk *fclk; | ||
104 | unsigned long clk_rate; | ||
105 | bool timer_active; | ||
106 | |||
107 | dev_dbg(chip->dev, "duty cycle: %d, period %d\n", duty_ns, period_ns); | ||
108 | |||
109 | mutex_lock(&omap->mutex); | ||
110 | if (duty_ns == pwm_get_duty_cycle(pwm) && | ||
111 | period_ns == pwm_get_period(pwm)) { | ||
112 | /* No change - don't cause any transients. */ | ||
113 | mutex_unlock(&omap->mutex); | ||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | fclk = omap->pdata->get_fclk(omap->dm_timer); | ||
118 | if (!fclk) { | ||
119 | dev_err(chip->dev, "invalid pmtimer fclk\n"); | ||
120 | mutex_unlock(&omap->mutex); | ||
121 | return -EINVAL; | ||
122 | } | ||
123 | |||
124 | clk_rate = clk_get_rate(fclk); | ||
125 | if (!clk_rate) { | ||
126 | dev_err(chip->dev, "invalid pmtimer fclk rate\n"); | ||
127 | mutex_unlock(&omap->mutex); | ||
128 | return -EINVAL; | ||
129 | } | ||
130 | |||
131 | dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate); | ||
132 | |||
133 | /* | ||
134 | * Calculate the appropriate load and match values based on the | ||
135 | * specified period and duty cycle. The load value determines the | ||
136 | * cycle time and the match value determines the duty cycle. | ||
137 | */ | ||
138 | load_value = pwm_omap_dmtimer_calc_value(clk_rate, period_ns); | ||
139 | match_value = pwm_omap_dmtimer_calc_value(clk_rate, | ||
140 | period_ns - duty_ns); | ||
141 | |||
142 | /* | ||
143 | * We MUST stop the associated dual-mode timer before attempting to | ||
144 | * write its registers, but calls to omap_dm_timer_start/stop must | ||
145 | * be balanced so check if timer is active before calling timer_stop. | ||
146 | */ | ||
147 | timer_active = pm_runtime_active(&omap->dm_timer_pdev->dev); | ||
148 | if (timer_active) | ||
149 | omap->pdata->stop(omap->dm_timer); | ||
150 | |||
151 | omap->pdata->set_load(omap->dm_timer, true, load_value); | ||
152 | omap->pdata->set_match(omap->dm_timer, true, match_value); | ||
153 | |||
154 | dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n", | ||
155 | load_value, load_value, match_value, match_value); | ||
156 | |||
157 | omap->pdata->set_pwm(omap->dm_timer, | ||
158 | pwm->polarity == PWM_POLARITY_INVERSED, | ||
159 | true, | ||
160 | PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); | ||
161 | |||
162 | /* If config was called while timer was running it must be reenabled. */ | ||
163 | if (timer_active) | ||
164 | pwm_omap_dmtimer_start(omap); | ||
165 | |||
166 | mutex_unlock(&omap->mutex); | ||
167 | |||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | static int pwm_omap_dmtimer_set_polarity(struct pwm_chip *chip, | ||
172 | struct pwm_device *pwm, | ||
173 | enum pwm_polarity polarity) | ||
174 | { | ||
175 | struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); | ||
176 | |||
177 | /* | ||
178 | * PWM core will not call set_polarity while PWM is enabled so it's | ||
179 | * safe to reconfigure the timer here without stopping it first. | ||
180 | */ | ||
181 | mutex_lock(&omap->mutex); | ||
182 | omap->pdata->set_pwm(omap->dm_timer, | ||
183 | polarity == PWM_POLARITY_INVERSED, | ||
184 | true, | ||
185 | PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); | ||
186 | mutex_unlock(&omap->mutex); | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static const struct pwm_ops pwm_omap_dmtimer_ops = { | ||
192 | .enable = pwm_omap_dmtimer_enable, | ||
193 | .disable = pwm_omap_dmtimer_disable, | ||
194 | .config = pwm_omap_dmtimer_config, | ||
195 | .set_polarity = pwm_omap_dmtimer_set_polarity, | ||
196 | .owner = THIS_MODULE, | ||
197 | }; | ||
198 | |||
199 | static int pwm_omap_dmtimer_probe(struct platform_device *pdev) | ||
200 | { | ||
201 | struct device_node *np = pdev->dev.of_node; | ||
202 | struct device_node *timer; | ||
203 | struct pwm_omap_dmtimer_chip *omap; | ||
204 | struct pwm_omap_dmtimer_pdata *pdata; | ||
205 | pwm_omap_dmtimer *dm_timer; | ||
206 | u32 prescaler; | ||
207 | int status; | ||
208 | |||
209 | pdata = dev_get_platdata(&pdev->dev); | ||
210 | if (!pdata) { | ||
211 | dev_err(&pdev->dev, "Missing dmtimer platform data\n"); | ||
212 | return -EINVAL; | ||
213 | } | ||
214 | |||
215 | if (!pdata->request_by_node || | ||
216 | !pdata->free || | ||
217 | !pdata->enable || | ||
218 | !pdata->disable || | ||
219 | !pdata->get_fclk || | ||
220 | !pdata->start || | ||
221 | !pdata->stop || | ||
222 | !pdata->set_load || | ||
223 | !pdata->set_match || | ||
224 | !pdata->set_pwm || | ||
225 | !pdata->set_prescaler || | ||
226 | !pdata->write_counter) { | ||
227 | dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n"); | ||
228 | return -EINVAL; | ||
229 | } | ||
230 | |||
231 | timer = of_parse_phandle(np, "ti,timers", 0); | ||
232 | if (!timer) | ||
233 | return -ENODEV; | ||
234 | |||
235 | if (!of_get_property(timer, "ti,timer-pwm", NULL)) { | ||
236 | dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); | ||
237 | return -ENODEV; | ||
238 | } | ||
239 | |||
240 | dm_timer = pdata->request_by_node(timer); | ||
241 | if (!dm_timer) | ||
242 | return -EPROBE_DEFER; | ||
243 | |||
244 | omap = devm_kzalloc(&pdev->dev, sizeof(*omap), GFP_KERNEL); | ||
245 | if (!omap) { | ||
246 | omap->pdata->free(dm_timer); | ||
247 | return -ENOMEM; | ||
248 | } | ||
249 | |||
250 | omap->pdata = pdata; | ||
251 | omap->dm_timer = dm_timer; | ||
252 | |||
253 | omap->dm_timer_pdev = of_find_device_by_node(timer); | ||
254 | if (!omap->dm_timer_pdev) { | ||
255 | dev_err(&pdev->dev, "Unable to find timer pdev\n"); | ||
256 | omap->pdata->free(dm_timer); | ||
257 | return -EINVAL; | ||
258 | } | ||
259 | |||
260 | /* | ||
261 | * Ensure that the timer is stopped before we allow PWM core to call | ||
262 | * pwm_enable. | ||
263 | */ | ||
264 | if (pm_runtime_active(&omap->dm_timer_pdev->dev)) | ||
265 | omap->pdata->stop(omap->dm_timer); | ||
266 | |||
267 | /* setup dmtimer prescaler */ | ||
268 | if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", | ||
269 | &prescaler)) | ||
270 | omap->pdata->set_prescaler(omap->dm_timer, prescaler); | ||
271 | |||
272 | omap->chip.dev = &pdev->dev; | ||
273 | omap->chip.ops = &pwm_omap_dmtimer_ops; | ||
274 | omap->chip.base = -1; | ||
275 | omap->chip.npwm = 1; | ||
276 | omap->chip.of_xlate = of_pwm_xlate_with_flags; | ||
277 | omap->chip.of_pwm_n_cells = 3; | ||
278 | |||
279 | mutex_init(&omap->mutex); | ||
280 | |||
281 | status = pwmchip_add(&omap->chip); | ||
282 | if (status < 0) { | ||
283 | dev_err(&pdev->dev, "failed to register PWM\n"); | ||
284 | omap->pdata->free(omap->dm_timer); | ||
285 | return status; | ||
286 | } | ||
287 | |||
288 | platform_set_drvdata(pdev, omap); | ||
289 | |||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | static int pwm_omap_dmtimer_remove(struct platform_device *pdev) | ||
294 | { | ||
295 | struct pwm_omap_dmtimer_chip *omap = platform_get_drvdata(pdev); | ||
296 | |||
297 | if (pm_runtime_active(&omap->dm_timer_pdev->dev)) | ||
298 | omap->pdata->stop(omap->dm_timer); | ||
299 | |||
300 | omap->pdata->free(omap->dm_timer); | ||
301 | |||
302 | mutex_destroy(&omap->mutex); | ||
303 | |||
304 | return pwmchip_remove(&omap->chip); | ||
305 | } | ||
306 | |||
307 | static const struct of_device_id pwm_omap_dmtimer_of_match[] = { | ||
308 | {.compatible = "ti,omap-dmtimer-pwm"}, | ||
309 | {} | ||
310 | }; | ||
311 | MODULE_DEVICE_TABLE(of, pwm_omap_dmtimer_of_match); | ||
312 | |||
313 | static struct platform_driver pwm_omap_dmtimer_driver = { | ||
314 | .driver = { | ||
315 | .name = "omap-dmtimer-pwm", | ||
316 | .of_match_table = of_match_ptr(pwm_omap_dmtimer_of_match), | ||
317 | }, | ||
318 | .probe = pwm_omap_dmtimer_probe, | ||
319 | .remove = pwm_omap_dmtimer_remove, | ||
320 | }; | ||
321 | module_platform_driver(pwm_omap_dmtimer_driver); | ||
322 | |||
323 | MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>"); | ||
324 | MODULE_AUTHOR("NeilBrown <neilb@suse.de>"); | ||
325 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); | ||
326 | MODULE_LICENSE("GPL v2"); | ||
327 | MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers"); | ||