aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/pwm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/pwm.c')
-rw-r--r--arch/arm/mach-tegra/pwm.c296
1 files changed, 296 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/pwm.c b/arch/arm/mach-tegra/pwm.c
new file mode 100644
index 00000000000..a268c391cb2
--- /dev/null
+++ b/arch/arm/mach-tegra/pwm.c
@@ -0,0 +1,296 @@
1/*
2 * arch/arm/mach-tegra/pwm.c
3 *
4 * Tegra pulse-width-modulation controller driver
5 *
6 * Copyright (c) 2010, NVIDIA Corporation.
7 * Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24#include <linux/clk.h>
25#include <linux/err.h>
26#include <linux/io.h>
27#include <linux/module.h>
28#include <linux/kernel.h>
29#include <linux/platform_device.h>
30#include <linux/pwm.h>
31#include <linux/slab.h>
32
33#define PWM_ENABLE (1 << 31)
34#define PWM_DUTY_WIDTH 8
35#define PWM_DUTY_SHIFT 16
36#define PWM_SCALE_WIDTH 13
37#define PWM_SCALE_SHIFT 0
38
39struct pwm_device {
40 struct list_head node;
41 struct platform_device *pdev;
42
43 const char *label;
44 struct clk *clk;
45
46 int clk_enb;
47 void __iomem *mmio_base;
48
49 unsigned int in_use;
50 unsigned int id;
51};
52
53static DEFINE_MUTEX(pwm_lock);
54static LIST_HEAD(pwm_list);
55
56static inline int pwm_writel(struct pwm_device *pwm, unsigned long val)
57{
58 int rc;
59
60 rc = clk_enable(pwm->clk);
61 if (WARN_ON(rc))
62 return rc;
63 writel(val, pwm->mmio_base);
64 clk_disable(pwm->clk);
65 return 0;
66}
67
68int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
69{
70 unsigned long long c;
71 unsigned long rate, hz;
72 u32 val = 0;
73
74 /* convert from duty_ns / period_ns to a fixed number of duty
75 * ticks per (1 << PWM_DUTY_WIDTH) cycles. */
76 c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1);
77 do_div(c, period_ns);
78
79 val = (u32)c << PWM_DUTY_SHIFT;
80
81 /* compute the prescaler value for which (1 << PWM_DUTY_WIDTH)
82 * cycles at the PWM clock rate will take period_ns nanoseconds. */
83 rate = clk_get_rate(pwm->clk) >> PWM_DUTY_WIDTH;
84 hz = 1000000000ul / period_ns;
85
86 rate = (rate + (hz / 2)) / hz;
87
88 if (rate >> PWM_SCALE_WIDTH)
89 return -EINVAL;
90 /* Due to the PWM divider is zero-based, we need to minus 1 to get desired frequency*/
91 if (rate>0)
92 rate--;
93
94 val |= (rate << PWM_SCALE_SHIFT);
95
96 /* the struct clk may be shared across multiple PWM devices, so
97 * only enable the PWM if this device has been enabled */
98 if (pwm->clk_enb)
99 val |= PWM_ENABLE;
100
101 return pwm_writel(pwm, val);
102}
103EXPORT_SYMBOL(pwm_config);
104
105int pwm_enable(struct pwm_device *pwm)
106{
107 int rc = 0;
108
109 mutex_lock(&pwm_lock);
110 if (!pwm->clk_enb) {
111 rc = clk_enable(pwm->clk);
112 if (!rc) {
113 u32 val = readl(pwm->mmio_base);
114 writel(val | PWM_ENABLE, pwm->mmio_base);
115 pwm->clk_enb = 1;
116 }
117 }
118 mutex_unlock(&pwm_lock);
119
120 return rc;
121}
122EXPORT_SYMBOL(pwm_enable);
123
124void pwm_disable(struct pwm_device *pwm)
125{
126 mutex_lock(&pwm_lock);
127 if (pwm->clk_enb) {
128 u32 val = readl(pwm->mmio_base);
129 writel(val & ~PWM_ENABLE, pwm->mmio_base);
130 clk_disable(pwm->clk);
131 pwm->clk_enb = 0;
132 } else
133 dev_warn(&pwm->pdev->dev, "%s called on disabled PWM\n",
134 __func__);
135 mutex_unlock(&pwm_lock);
136}
137EXPORT_SYMBOL(pwm_disable);
138
139struct pwm_device *pwm_request(int pwm_id, const char *label)
140{
141 struct pwm_device *pwm;
142 int found = 0;
143
144 mutex_lock(&pwm_lock);
145
146 list_for_each_entry(pwm, &pwm_list, node) {
147 if (pwm->id == pwm_id) {
148 found = 1;
149 break;
150 }
151 }
152
153 if (found) {
154 if (!pwm->in_use) {
155 pwm->in_use = 1;
156 pwm->label = label;
157 } else
158 pwm = ERR_PTR(-EBUSY);
159 } else
160 pwm = ERR_PTR(-ENOENT);
161
162 mutex_unlock(&pwm_lock);
163
164 return pwm;
165}
166EXPORT_SYMBOL(pwm_request);
167
168void pwm_free(struct pwm_device *pwm)
169{
170 mutex_lock(&pwm_lock);
171 if (pwm->in_use) {
172 pwm->in_use = 0;
173 pwm->label = NULL;
174 } else
175 dev_warn(&pwm->pdev->dev, "PWM device already freed\n");
176
177 mutex_unlock(&pwm_lock);
178}
179EXPORT_SYMBOL(pwm_free);
180
181static int tegra_pwm_probe(struct platform_device *pdev)
182{
183 struct pwm_device *pwm;
184 struct resource *r;
185 int ret;
186
187 pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
188 if (!pwm) {
189 dev_err(&pdev->dev, "failed to allocate memory\n");
190 return -ENOMEM;
191 }
192 pwm->clk = clk_get(&pdev->dev, NULL);
193
194 if (IS_ERR(pwm->clk)) {
195 ret = PTR_ERR(pwm->clk);
196 goto err_free;
197 }
198
199 pwm->clk_enb = 0;
200 pwm->in_use = 0;
201 pwm->id = pdev->id;
202 pwm->pdev = pdev;
203
204 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
205 if (!r) {
206 dev_err(&pdev->dev, "no memory resources defined\n");
207 ret = -ENODEV;
208 goto err_put_clk;
209 }
210
211 r = request_mem_region(r->start, resource_size(r), pdev->name);
212 if (!r) {
213 dev_err(&pdev->dev, "failed to request memory\n");
214 ret = -EBUSY;
215 goto err_put_clk;
216 }
217
218 pwm->mmio_base = ioremap(r->start, resource_size(r));
219 if (!pwm->mmio_base) {
220 dev_err(&pdev->dev, "failed to ioremap() region\n");
221 ret = -ENODEV;
222 goto err_free_mem;
223 }
224
225 platform_set_drvdata(pdev, pwm);
226
227 mutex_lock(&pwm_lock);
228 list_add_tail(&pwm->node, &pwm_list);
229 mutex_unlock(&pwm_lock);
230
231 return 0;
232
233err_free_mem:
234 release_mem_region(r->start, resource_size(r));
235err_put_clk:
236 clk_put(pwm->clk);
237err_free:
238 kfree(pwm);
239 return ret;
240}
241
242static int __devexit tegra_pwm_remove(struct platform_device *pdev)
243{
244 struct pwm_device *pwm = platform_get_drvdata(pdev);
245 struct resource *r;
246 int rc;
247
248 if (WARN_ON(!pwm))
249 return -ENODEV;
250
251 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
252
253 mutex_lock(&pwm_lock);
254 if (pwm->in_use) {
255 mutex_unlock(&pwm_lock);
256 return -EBUSY;
257 }
258 list_del(&pwm->node);
259 mutex_unlock(&pwm_lock);
260
261 rc = pwm_writel(pwm, 0);
262
263 iounmap(pwm->mmio_base);
264 release_mem_region(r->start, resource_size(r));
265
266 if (pwm->clk_enb)
267 clk_disable(pwm->clk);
268
269 clk_put(pwm->clk);
270
271 kfree(pwm);
272 return rc;
273}
274
275static struct platform_driver tegra_pwm_driver = {
276 .driver = {
277 .name = "tegra_pwm",
278 },
279 .probe = tegra_pwm_probe,
280 .remove = __devexit_p(tegra_pwm_remove),
281};
282
283static int __init tegra_pwm_init(void)
284{
285 return platform_driver_register(&tegra_pwm_driver);
286}
287subsys_initcall(tegra_pwm_init);
288
289static void __exit tegra_pwm_exit(void)
290{
291 platform_driver_unregister(&tegra_pwm_driver);
292}
293module_exit(tegra_pwm_exit);
294
295MODULE_LICENSE("GPL v2");
296MODULE_AUTHOR("NVIDIA Corporation");