]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/misc/ep93xx_pwm.c
Merge remote-tracking branch 'tegra/for-next'
[karo-tx-linux.git] / drivers / misc / ep93xx_pwm.c
1 /*
2  *  Simple PWM driver for EP93XX
3  *
4  *      (c) Copyright 2009  Matthieu Crapet <mcrapet@gmail.com>
5  *      (c) Copyright 2009  H Hartley Sweeten <hsweeten@visionengravers.com>
6  *
7  *      This program is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU General Public License
9  *      as published by the Free Software Foundation; either version
10  *      2 of the License, or (at your option) any later version.
11  *
12  *  EP9307 has only one channel:
13  *    - PWMOUT
14  *
15  *  EP9301/02/12/15 have two channels:
16  *    - PWMOUT
17  *    - PWMOUT1 (alternate function for EGPIO14)
18  */
19
20 #include <linux/module.h>
21 #include <linux/platform_device.h>
22 #include <linux/slab.h>
23 #include <linux/clk.h>
24 #include <linux/err.h>
25 #include <linux/io.h>
26
27 #include <mach/platform.h>
28
29 #define EP93XX_PWMx_TERM_COUNT  0x00
30 #define EP93XX_PWMx_DUTY_CYCLE  0x04
31 #define EP93XX_PWMx_ENABLE      0x08
32 #define EP93XX_PWMx_INVERT      0x0C
33
34 #define EP93XX_PWM_MAX_COUNT    0xFFFF
35
36 struct ep93xx_pwm {
37         void __iomem    *mmio_base;
38         struct clk      *clk;
39         u32             duty_percent;
40 };
41
42 /*
43  * /sys/devices/platform/ep93xx-pwm.N
44  *   /min_freq      read-only   minimum pwm output frequency
45  *   /max_req       read-only   maximum pwm output frequency
46  *   /freq          read-write  pwm output frequency (0 = disable output)
47  *   /duty_percent  read-write  pwm duty cycle percent (1..99)
48  *   /invert        read-write  invert pwm output
49  */
50
51 static ssize_t ep93xx_pwm_get_min_freq(struct device *dev,
52                 struct device_attribute *attr, char *buf)
53 {
54         struct platform_device *pdev = to_platform_device(dev);
55         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
56         unsigned long rate = clk_get_rate(pwm->clk);
57
58         return sprintf(buf, "%ld\n", rate / (EP93XX_PWM_MAX_COUNT + 1));
59 }
60
61 static ssize_t ep93xx_pwm_get_max_freq(struct device *dev,
62                 struct device_attribute *attr, char *buf)
63 {
64         struct platform_device *pdev = to_platform_device(dev);
65         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
66         unsigned long rate = clk_get_rate(pwm->clk);
67
68         return sprintf(buf, "%ld\n", rate / 2);
69 }
70
71 static ssize_t ep93xx_pwm_get_freq(struct device *dev,
72                 struct device_attribute *attr, char *buf)
73 {
74         struct platform_device *pdev = to_platform_device(dev);
75         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
76
77         if (readl(pwm->mmio_base + EP93XX_PWMx_ENABLE) & 0x1) {
78                 unsigned long rate = clk_get_rate(pwm->clk);
79                 u16 term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
80
81                 return sprintf(buf, "%ld\n", rate / (term + 1));
82         } else {
83                 return sprintf(buf, "disabled\n");
84         }
85 }
86
87 static ssize_t ep93xx_pwm_set_freq(struct device *dev,
88                 struct device_attribute *attr, const char *buf, size_t count)
89 {
90         struct platform_device *pdev = to_platform_device(dev);
91         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
92         long val;
93         int err;
94
95         err = kstrtol(buf, 10, &val);
96         if (err)
97                 return -EINVAL;
98
99         if (val == 0) {
100                 writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
101         } else if (val <= (clk_get_rate(pwm->clk) / 2)) {
102                 u32 term, duty;
103
104                 val = (clk_get_rate(pwm->clk) / val) - 1;
105                 if (val > EP93XX_PWM_MAX_COUNT)
106                         val = EP93XX_PWM_MAX_COUNT;
107                 if (val < 1)
108                         val = 1;
109
110                 term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
111                 duty = ((val + 1) * pwm->duty_percent / 100) - 1;
112
113                 /* If pwm is running, order is important */
114                 if (val > term) {
115                         writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
116                         writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
117                 } else {
118                         writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
119                         writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
120                 }
121
122                 if (!readl(pwm->mmio_base + EP93XX_PWMx_ENABLE) & 0x1)
123                         writel(0x1, pwm->mmio_base + EP93XX_PWMx_ENABLE);
124         } else {
125                 return -EINVAL;
126         }
127
128         return count;
129 }
130
131 static ssize_t ep93xx_pwm_get_duty_percent(struct device *dev,
132                 struct device_attribute *attr, char *buf)
133 {
134         struct platform_device *pdev = to_platform_device(dev);
135         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
136
137         return sprintf(buf, "%d\n", pwm->duty_percent);
138 }
139
140 static ssize_t ep93xx_pwm_set_duty_percent(struct device *dev,
141                 struct device_attribute *attr, const char *buf, size_t count)
142 {
143         struct platform_device *pdev = to_platform_device(dev);
144         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
145         long val;
146         int err;
147
148         err = kstrtol(buf, 10, &val);
149         if (err)
150                 return -EINVAL;
151
152         if (val > 0 && val < 100) {
153                 u32 term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
154                 u32 duty = ((term + 1) * val / 100) - 1;
155
156                 writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
157                 pwm->duty_percent = val;
158                 return count;
159         }
160
161         return -EINVAL;
162 }
163
164 static ssize_t ep93xx_pwm_get_invert(struct device *dev,
165                 struct device_attribute *attr, char *buf)
166 {
167         struct platform_device *pdev = to_platform_device(dev);
168         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
169         int inverted = readl(pwm->mmio_base + EP93XX_PWMx_INVERT) & 0x1;
170
171         return sprintf(buf, "%d\n", inverted);
172 }
173
174 static ssize_t ep93xx_pwm_set_invert(struct device *dev,
175                 struct device_attribute *attr, const char *buf, size_t count)
176 {
177         struct platform_device *pdev = to_platform_device(dev);
178         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
179         long val;
180         int err;
181
182         err = kstrtol(buf, 10, &val);
183         if (err)
184                 return -EINVAL;
185
186         if (val == 0)
187                 writel(0x0, pwm->mmio_base + EP93XX_PWMx_INVERT);
188         else if (val == 1)
189                 writel(0x1, pwm->mmio_base + EP93XX_PWMx_INVERT);
190         else
191                 return -EINVAL;
192
193         return count;
194 }
195
196 static DEVICE_ATTR(min_freq, S_IRUGO, ep93xx_pwm_get_min_freq, NULL);
197 static DEVICE_ATTR(max_freq, S_IRUGO, ep93xx_pwm_get_max_freq, NULL);
198 static DEVICE_ATTR(freq, S_IWUSR | S_IRUGO,
199                    ep93xx_pwm_get_freq, ep93xx_pwm_set_freq);
200 static DEVICE_ATTR(duty_percent, S_IWUSR | S_IRUGO,
201                    ep93xx_pwm_get_duty_percent, ep93xx_pwm_set_duty_percent);
202 static DEVICE_ATTR(invert, S_IWUSR | S_IRUGO,
203                    ep93xx_pwm_get_invert, ep93xx_pwm_set_invert);
204
205 static struct attribute *ep93xx_pwm_attrs[] = {
206         &dev_attr_min_freq.attr,
207         &dev_attr_max_freq.attr,
208         &dev_attr_freq.attr,
209         &dev_attr_duty_percent.attr,
210         &dev_attr_invert.attr,
211         NULL
212 };
213
214 static const struct attribute_group ep93xx_pwm_sysfs_files = {
215         .attrs  = ep93xx_pwm_attrs,
216 };
217
218 static int ep93xx_pwm_probe(struct platform_device *pdev)
219 {
220         struct ep93xx_pwm *pwm;
221         struct resource *res;
222         int ret;
223
224         pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
225         if (!pwm)
226                 return -ENOMEM;
227
228         pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk");
229         if (IS_ERR(pwm->clk))
230                 return PTR_ERR(pwm->clk);
231
232         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
233         pwm->mmio_base = devm_ioremap_resource(&pdev->dev, res);
234         if (IS_ERR(pwm->mmio_base))
235                 return PTR_ERR(pwm->mmio_base);
236
237         ret = ep93xx_pwm_acquire_gpio(pdev);
238         if (ret)
239                 return ret;
240
241         ret = sysfs_create_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
242         if (ret) {
243                 ep93xx_pwm_release_gpio(pdev);
244                 return ret;
245         }
246
247         pwm->duty_percent = 50;
248
249         /* disable pwm at startup. Avoids zero value. */
250         writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
251         writel(EP93XX_PWM_MAX_COUNT, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
252         writel(EP93XX_PWM_MAX_COUNT/2, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
253
254         clk_enable(pwm->clk);
255
256         platform_set_drvdata(pdev, pwm);
257         return 0;
258 }
259
260 static int ep93xx_pwm_remove(struct platform_device *pdev)
261 {
262         struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
263
264         writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
265         clk_disable(pwm->clk);
266         sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
267         ep93xx_pwm_release_gpio(pdev);
268
269         return 0;
270 }
271
272 static struct platform_driver ep93xx_pwm_driver = {
273         .driver         = {
274                 .name   = "ep93xx-pwm",
275                 .owner  = THIS_MODULE,
276         },
277         .probe          = ep93xx_pwm_probe,
278         .remove         = ep93xx_pwm_remove,
279 };
280 module_platform_driver(ep93xx_pwm_driver);
281
282 MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>, "
283               "H Hartley Sweeten <hsweeten@visionengravers.com>");
284 MODULE_DESCRIPTION("EP93xx PWM driver");
285 MODULE_LICENSE("GPL");
286 MODULE_ALIAS("platform:ep93xx-pwm");