Commit 006669ec21d99e161015150ffedeeaeaad513c3b
1 parent
e1b5596533
Exists in
master
and in
6 other branches
Blackfin: pwm: implement linux/pwm.h API
For now, this only supports gptimers. Support for dedicated PWM devices as found on newer parts to come. Signed-off-by: Michael Hennerich <michael.hennerich@analog.com> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Showing 3 changed files with 111 additions and 0 deletions Side-by-side Diff
arch/blackfin/Kconfig
... | ... | @@ -953,6 +953,16 @@ |
953 | 953 | To compile this driver as a module, choose M here: the module |
954 | 954 | will be called gptimers. |
955 | 955 | |
956 | +config HAVE_PWM | |
957 | + tristate "Enable PWM API support" | |
958 | + depends on BFIN_GPTIMERS | |
959 | + help | |
960 | + Enable support for the Pulse Width Modulation framework (as | |
961 | + found in linux/pwm.h). | |
962 | + | |
963 | + To compile this driver as a module, choose M here: the module | |
964 | + will be called pwm. | |
965 | + | |
956 | 966 | choice |
957 | 967 | prompt "Uncached DMA region" |
958 | 968 | default DMA_UNCACHED_1M |
arch/blackfin/kernel/Makefile
arch/blackfin/kernel/pwm.c
1 | +/* | |
2 | + * Blackfin Pulse Width Modulation (PWM) core | |
3 | + * | |
4 | + * Copyright (c) 2011 Analog Devices Inc. | |
5 | + * | |
6 | + * Licensed under the GPL-2 or later. | |
7 | + */ | |
8 | + | |
9 | +#include <linux/module.h> | |
10 | +#include <linux/pwm.h> | |
11 | +#include <linux/slab.h> | |
12 | + | |
13 | +#include <asm/gptimers.h> | |
14 | +#include <asm/portmux.h> | |
15 | + | |
16 | +struct pwm_device { | |
17 | + unsigned id; | |
18 | + unsigned short pin; | |
19 | +}; | |
20 | + | |
21 | +static const unsigned short pwm_to_gptimer_per[] = { | |
22 | + P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, | |
23 | + P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, | |
24 | +}; | |
25 | + | |
26 | +struct pwm_device *pwm_request(int pwm_id, const char *label) | |
27 | +{ | |
28 | + struct pwm_device *pwm; | |
29 | + int ret; | |
30 | + | |
31 | + /* XXX: pwm_id really should be unsigned */ | |
32 | + if (pwm_id < 0) | |
33 | + return NULL; | |
34 | + | |
35 | + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); | |
36 | + if (!pwm) | |
37 | + return pwm; | |
38 | + | |
39 | + pwm->id = pwm_id; | |
40 | + if (pwm->id >= ARRAY_SIZE(pwm_to_gptimer_per)) | |
41 | + goto err; | |
42 | + | |
43 | + pwm->pin = pwm_to_gptimer_per[pwm->id]; | |
44 | + ret = peripheral_request(pwm->pin, label); | |
45 | + if (ret) | |
46 | + goto err; | |
47 | + | |
48 | + return pwm; | |
49 | + err: | |
50 | + kfree(pwm); | |
51 | + return NULL; | |
52 | +} | |
53 | +EXPORT_SYMBOL(pwm_request); | |
54 | + | |
55 | +void pwm_free(struct pwm_device *pwm) | |
56 | +{ | |
57 | + peripheral_free(pwm->pin); | |
58 | + kfree(pwm); | |
59 | +} | |
60 | +EXPORT_SYMBOL(pwm_free); | |
61 | + | |
62 | +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) | |
63 | +{ | |
64 | + unsigned long period, duty; | |
65 | + unsigned long long val; | |
66 | + | |
67 | + if (duty_ns < 0 || duty_ns > period_ns) | |
68 | + return -EINVAL; | |
69 | + | |
70 | + val = (unsigned long long)get_sclk() * period_ns; | |
71 | + do_div(val, NSEC_PER_SEC); | |
72 | + period = val; | |
73 | + | |
74 | + val = (unsigned long long)period * duty_ns; | |
75 | + do_div(val, period_ns); | |
76 | + duty = period - val; | |
77 | + | |
78 | + if (duty >= period) | |
79 | + duty = period - 1; | |
80 | + | |
81 | + set_gptimer_config(pwm->id, TIMER_MODE_PWM | TIMER_PERIOD_CNT); | |
82 | + set_gptimer_pwidth(pwm->id, duty); | |
83 | + set_gptimer_period(pwm->id, period); | |
84 | + | |
85 | + return 0; | |
86 | +} | |
87 | +EXPORT_SYMBOL(pwm_config); | |
88 | + | |
89 | +int pwm_enable(struct pwm_device *pwm) | |
90 | +{ | |
91 | + enable_gptimer(pwm->id); | |
92 | + return 0; | |
93 | +} | |
94 | +EXPORT_SYMBOL(pwm_enable); | |
95 | + | |
96 | +void pwm_disable(struct pwm_device *pwm) | |
97 | +{ | |
98 | + disable_gptimer(pwm->id); | |
99 | +} | |
100 | +EXPORT_SYMBOL(pwm_disable); |