pwm: Add PWM framework support
[linux-2.6-block.git] / drivers / pwm / core.c
1 /*
2  * Generic pwmlib implementation
3  *
4  * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; see the file COPYING.  If not, write to
18  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #include <linux/module.h>
22 #include <linux/pwm.h>
23 #include <linux/list.h>
24 #include <linux/mutex.h>
25 #include <linux/err.h>
26 #include <linux/slab.h>
27 #include <linux/device.h>
28
29 struct pwm_device {
30         struct                  pwm_chip *chip;
31         const char              *label;
32         unsigned long           flags;
33 #define FLAG_REQUESTED  0
34 #define FLAG_ENABLED    1
35         struct list_head        node;
36 };
37
38 static LIST_HEAD(pwm_list);
39
40 static DEFINE_MUTEX(pwm_lock);
41
42 static struct pwm_device *_find_pwm(int pwm_id)
43 {
44         struct pwm_device *pwm;
45
46         list_for_each_entry(pwm, &pwm_list, node) {
47                 if (pwm->chip->pwm_id == pwm_id)
48                         return pwm;
49         }
50
51         return NULL;
52 }
53
54 /**
55  * pwmchip_add() - register a new PWM chip
56  * @chip: the PWM chip to add
57  */
58 int pwmchip_add(struct pwm_chip *chip)
59 {
60         struct pwm_device *pwm;
61         int ret = 0;
62
63         pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
64         if (!pwm)
65                 return -ENOMEM;
66
67         pwm->chip = chip;
68
69         mutex_lock(&pwm_lock);
70
71         if (chip->pwm_id >= 0 && _find_pwm(chip->pwm_id)) {
72                 ret = -EBUSY;
73                 goto out;
74         }
75
76         list_add_tail(&pwm->node, &pwm_list);
77 out:
78         mutex_unlock(&pwm_lock);
79
80         if (ret)
81                 kfree(pwm);
82
83         return ret;
84 }
85 EXPORT_SYMBOL_GPL(pwmchip_add);
86
87 /**
88  * pwmchip_remove() - remove a PWM chip
89  * @chip: the PWM chip to remove
90  *
91  * Removes a PWM chip. This function may return busy if the PWM chip provides
92  * a PWM device that is still requested.
93  */
94 int pwmchip_remove(struct pwm_chip *chip)
95 {
96         struct pwm_device *pwm;
97         int ret = 0;
98
99         mutex_lock(&pwm_lock);
100
101         pwm = _find_pwm(chip->pwm_id);
102         if (!pwm) {
103                 ret = -ENOENT;
104                 goto out;
105         }
106
107         if (test_bit(FLAG_REQUESTED, &pwm->flags)) {
108                 ret = -EBUSY;
109                 goto out;
110         }
111
112         list_del(&pwm->node);
113
114         kfree(pwm);
115 out:
116         mutex_unlock(&pwm_lock);
117
118         return ret;
119 }
120 EXPORT_SYMBOL_GPL(pwmchip_remove);
121
122 /**
123  * pwm_request() - request a PWM device
124  * @pwm_id: global PWM device index
125  * @label: PWM device label
126  */
127 struct pwm_device *pwm_request(int pwm_id, const char *label)
128 {
129         struct pwm_device *pwm;
130         int ret;
131
132         mutex_lock(&pwm_lock);
133
134         pwm = _find_pwm(pwm_id);
135         if (!pwm) {
136                 pwm = ERR_PTR(-ENOENT);
137                 goto out;
138         }
139
140         if (test_bit(FLAG_REQUESTED, &pwm->flags)) {
141                 pwm = ERR_PTR(-EBUSY);
142                 goto out;
143         }
144
145         if (!try_module_get(pwm->chip->ops->owner)) {
146                 pwm = ERR_PTR(-ENODEV);
147                 goto out;
148         }
149
150         if (pwm->chip->ops->request) {
151                 ret = pwm->chip->ops->request(pwm->chip);
152                 if (ret) {
153                         pwm = ERR_PTR(ret);
154                         goto out_put;
155                 }
156         }
157
158         pwm->label = label;
159         set_bit(FLAG_REQUESTED, &pwm->flags);
160
161         goto out;
162
163 out_put:
164         module_put(pwm->chip->ops->owner);
165 out:
166         mutex_unlock(&pwm_lock);
167
168         return pwm;
169 }
170 EXPORT_SYMBOL_GPL(pwm_request);
171
172 /**
173  * pwm_free() - free a PWM device
174  * @pwm: PWM device
175  */
176 void pwm_free(struct pwm_device *pwm)
177 {
178         mutex_lock(&pwm_lock);
179
180         if (!test_and_clear_bit(FLAG_REQUESTED, &pwm->flags)) {
181                 pr_warning("PWM device already freed\n");
182                 goto out;
183         }
184
185         pwm->label = NULL;
186
187         module_put(pwm->chip->ops->owner);
188 out:
189         mutex_unlock(&pwm_lock);
190 }
191 EXPORT_SYMBOL_GPL(pwm_free);
192
193 /**
194  * pwm_config() - change a PWM device configuration
195  * @pwm: PWM device
196  * @duty_ns: "on" time (in nanoseconds)
197  * @period_ns: duration (in nanoseconds) of one cycle
198  */
199 int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
200 {
201         return pwm->chip->ops->config(pwm->chip, duty_ns, period_ns);
202 }
203 EXPORT_SYMBOL_GPL(pwm_config);
204
205 /**
206  * pwm_enable() - start a PWM output toggling
207  * @pwm: PWM device
208  */
209 int pwm_enable(struct pwm_device *pwm)
210 {
211         if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags))
212                 return pwm->chip->ops->enable(pwm->chip);
213
214         return 0;
215 }
216 EXPORT_SYMBOL_GPL(pwm_enable);
217
218 /**
219  * pwm_disable() - stop a PWM output toggling
220  * @pwm: PWM device
221  */
222 void pwm_disable(struct pwm_device *pwm)
223 {
224         if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags))
225                 pwm->chip->ops->disable(pwm->chip);
226 }
227 EXPORT_SYMBOL_GPL(pwm_disable);