Commit | Line | Data |
---|---|---|
ef12379f HS |
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> | |
5a0e3ad6 | 22 | #include <linux/slab.h> |
ef12379f HS |
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 | ||
ef12379f HS |
42 | static inline u16 ep93xx_pwm_read_tc(struct ep93xx_pwm *pwm) |
43 | { | |
a39ca274 | 44 | return readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT); |
ef12379f HS |
45 | } |
46 | ||
ef12379f HS |
47 | static inline int ep93xx_pwm_is_enabled(struct ep93xx_pwm *pwm) |
48 | { | |
a39ca274 | 49 | return readl(pwm->mmio_base + EP93XX_PWMx_ENABLE) & 0x1; |
ef12379f HS |
50 | } |
51 | ||
ef12379f HS |
52 | static inline int ep93xx_pwm_is_inverted(struct ep93xx_pwm *pwm) |
53 | { | |
a39ca274 | 54 | return readl(pwm->mmio_base + EP93XX_PWMx_INVERT) & 0x1; |
ef12379f HS |
55 | } |
56 | ||
57 | /* | |
58 | * /sys/devices/platform/ep93xx-pwm.N | |
59 | * /min_freq read-only minimum pwm output frequency | |
60 | * /max_req read-only maximum pwm output frequency | |
61 | * /freq read-write pwm output frequency (0 = disable output) | |
62 | * /duty_percent read-write pwm duty cycle percent (1..99) | |
63 | * /invert read-write invert pwm output | |
64 | */ | |
65 | ||
66 | static ssize_t ep93xx_pwm_get_min_freq(struct device *dev, | |
67 | struct device_attribute *attr, char *buf) | |
68 | { | |
69 | struct platform_device *pdev = to_platform_device(dev); | |
70 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
71 | unsigned long rate = clk_get_rate(pwm->clk); | |
72 | ||
73 | return sprintf(buf, "%ld\n", rate / (EP93XX_PWM_MAX_COUNT + 1)); | |
74 | } | |
75 | ||
76 | static ssize_t ep93xx_pwm_get_max_freq(struct device *dev, | |
77 | struct device_attribute *attr, char *buf) | |
78 | { | |
79 | struct platform_device *pdev = to_platform_device(dev); | |
80 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
81 | unsigned long rate = clk_get_rate(pwm->clk); | |
82 | ||
83 | return sprintf(buf, "%ld\n", rate / 2); | |
84 | } | |
85 | ||
86 | static ssize_t ep93xx_pwm_get_freq(struct device *dev, | |
87 | struct device_attribute *attr, char *buf) | |
88 | { | |
89 | struct platform_device *pdev = to_platform_device(dev); | |
90 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
91 | ||
92 | if (ep93xx_pwm_is_enabled(pwm)) { | |
93 | unsigned long rate = clk_get_rate(pwm->clk); | |
94 | u16 term = ep93xx_pwm_read_tc(pwm); | |
95 | ||
96 | return sprintf(buf, "%ld\n", rate / (term + 1)); | |
97 | } else { | |
98 | return sprintf(buf, "disabled\n"); | |
99 | } | |
100 | } | |
101 | ||
102 | static ssize_t ep93xx_pwm_set_freq(struct device *dev, | |
103 | struct device_attribute *attr, const char *buf, size_t count) | |
104 | { | |
105 | struct platform_device *pdev = to_platform_device(dev); | |
106 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
107 | long val; | |
108 | int err; | |
109 | ||
110 | err = strict_strtol(buf, 10, &val); | |
111 | if (err) | |
112 | return -EINVAL; | |
113 | ||
114 | if (val == 0) { | |
02846b97 | 115 | writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE); |
ef12379f HS |
116 | } else if (val <= (clk_get_rate(pwm->clk) / 2)) { |
117 | u32 term, duty; | |
118 | ||
119 | val = (clk_get_rate(pwm->clk) / val) - 1; | |
120 | if (val > EP93XX_PWM_MAX_COUNT) | |
121 | val = EP93XX_PWM_MAX_COUNT; | |
122 | if (val < 1) | |
123 | val = 1; | |
124 | ||
125 | term = ep93xx_pwm_read_tc(pwm); | |
126 | duty = ((val + 1) * pwm->duty_percent / 100) - 1; | |
127 | ||
128 | /* If pwm is running, order is important */ | |
129 | if (val > term) { | |
53e2e380 | 130 | writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT); |
aa919769 | 131 | writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE); |
ef12379f | 132 | } else { |
aa919769 | 133 | writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE); |
53e2e380 | 134 | writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT); |
ef12379f HS |
135 | } |
136 | ||
137 | if (!ep93xx_pwm_is_enabled(pwm)) | |
ac91b96f | 138 | writel(0x1, pwm->mmio_base + EP93XX_PWMx_ENABLE); |
ef12379f HS |
139 | } else { |
140 | return -EINVAL; | |
141 | } | |
142 | ||
143 | return count; | |
144 | } | |
145 | ||
146 | static ssize_t ep93xx_pwm_get_duty_percent(struct device *dev, | |
147 | struct device_attribute *attr, char *buf) | |
148 | { | |
149 | struct platform_device *pdev = to_platform_device(dev); | |
150 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
151 | ||
152 | return sprintf(buf, "%d\n", pwm->duty_percent); | |
153 | } | |
154 | ||
155 | static ssize_t ep93xx_pwm_set_duty_percent(struct device *dev, | |
156 | struct device_attribute *attr, const char *buf, size_t count) | |
157 | { | |
158 | struct platform_device *pdev = to_platform_device(dev); | |
159 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
160 | long val; | |
161 | int err; | |
162 | ||
163 | err = strict_strtol(buf, 10, &val); | |
164 | if (err) | |
165 | return -EINVAL; | |
166 | ||
167 | if (val > 0 && val < 100) { | |
168 | u32 term = ep93xx_pwm_read_tc(pwm); | |
aa919769 HS |
169 | u32 duty = ((term + 1) * val / 100) - 1; |
170 | ||
171 | writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE); | |
ef12379f HS |
172 | pwm->duty_percent = val; |
173 | return count; | |
174 | } | |
175 | ||
176 | return -EINVAL; | |
177 | } | |
178 | ||
179 | static ssize_t ep93xx_pwm_get_invert(struct device *dev, | |
180 | struct device_attribute *attr, char *buf) | |
181 | { | |
182 | struct platform_device *pdev = to_platform_device(dev); | |
183 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
184 | ||
185 | return sprintf(buf, "%d\n", ep93xx_pwm_is_inverted(pwm)); | |
186 | } | |
187 | ||
188 | static ssize_t ep93xx_pwm_set_invert(struct device *dev, | |
189 | struct device_attribute *attr, const char *buf, size_t count) | |
190 | { | |
191 | struct platform_device *pdev = to_platform_device(dev); | |
192 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
193 | long val; | |
194 | int err; | |
195 | ||
196 | err = strict_strtol(buf, 10, &val); | |
197 | if (err) | |
198 | return -EINVAL; | |
199 | ||
200 | if (val == 0) | |
d98d7903 | 201 | writel(0x0, pwm->mmio_base + EP93XX_PWMx_INVERT); |
ef12379f | 202 | else if (val == 1) |
47a36ee9 | 203 | writel(0x1, pwm->mmio_base + EP93XX_PWMx_INVERT); |
ef12379f HS |
204 | else |
205 | return -EINVAL; | |
206 | ||
207 | return count; | |
208 | } | |
209 | ||
210 | static DEVICE_ATTR(min_freq, S_IRUGO, ep93xx_pwm_get_min_freq, NULL); | |
211 | static DEVICE_ATTR(max_freq, S_IRUGO, ep93xx_pwm_get_max_freq, NULL); | |
deb187e7 | 212 | static DEVICE_ATTR(freq, S_IWUSR | S_IRUGO, |
ef12379f | 213 | ep93xx_pwm_get_freq, ep93xx_pwm_set_freq); |
deb187e7 | 214 | static DEVICE_ATTR(duty_percent, S_IWUSR | S_IRUGO, |
ef12379f | 215 | ep93xx_pwm_get_duty_percent, ep93xx_pwm_set_duty_percent); |
deb187e7 | 216 | static DEVICE_ATTR(invert, S_IWUSR | S_IRUGO, |
ef12379f HS |
217 | ep93xx_pwm_get_invert, ep93xx_pwm_set_invert); |
218 | ||
219 | static struct attribute *ep93xx_pwm_attrs[] = { | |
220 | &dev_attr_min_freq.attr, | |
221 | &dev_attr_max_freq.attr, | |
222 | &dev_attr_freq.attr, | |
223 | &dev_attr_duty_percent.attr, | |
224 | &dev_attr_invert.attr, | |
225 | NULL | |
226 | }; | |
227 | ||
228 | static const struct attribute_group ep93xx_pwm_sysfs_files = { | |
229 | .attrs = ep93xx_pwm_attrs, | |
230 | }; | |
231 | ||
232 | static int __init ep93xx_pwm_probe(struct platform_device *pdev) | |
233 | { | |
234 | struct ep93xx_pwm *pwm; | |
235 | struct resource *res; | |
6c7dd64a | 236 | int ret; |
ef12379f | 237 | |
6c7dd64a HS |
238 | pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); |
239 | if (!pwm) | |
240 | return -ENOMEM; | |
ef12379f | 241 | |
6c7dd64a HS |
242 | pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk"); |
243 | if (IS_ERR(pwm->clk)) | |
244 | return PTR_ERR(pwm->clk); | |
ef12379f HS |
245 | |
246 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
6c7dd64a HS |
247 | pwm->mmio_base = devm_ioremap_resource(&pdev->dev, res); |
248 | if (IS_ERR(pwm->mmio_base)) | |
249 | return PTR_ERR(pwm->mmio_base); | |
250 | ||
251 | ret = ep93xx_pwm_acquire_gpio(pdev); | |
252 | if (ret) | |
253 | return ret; | |
254 | ||
255 | ret = sysfs_create_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files); | |
256 | if (ret) { | |
257 | ep93xx_pwm_release_gpio(pdev); | |
258 | return ret; | |
ef12379f HS |
259 | } |
260 | ||
261 | pwm->duty_percent = 50; | |
262 | ||
ef12379f | 263 | /* disable pwm at startup. Avoids zero value. */ |
02846b97 | 264 | writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE); |
53e2e380 | 265 | writel(EP93XX_PWM_MAX_COUNT, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT); |
aa919769 | 266 | writel(EP93XX_PWM_MAX_COUNT/2, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE); |
ef12379f HS |
267 | |
268 | clk_enable(pwm->clk); | |
269 | ||
6c7dd64a | 270 | platform_set_drvdata(pdev, pwm); |
ef12379f | 271 | return 0; |
ef12379f HS |
272 | } |
273 | ||
274 | static int __exit ep93xx_pwm_remove(struct platform_device *pdev) | |
275 | { | |
276 | struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); | |
ef12379f | 277 | |
02846b97 | 278 | writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE); |
ef12379f | 279 | clk_disable(pwm->clk); |
ef12379f | 280 | sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files); |
ef12379f HS |
281 | ep93xx_pwm_release_gpio(pdev); |
282 | ||
283 | return 0; | |
284 | } | |
285 | ||
286 | static struct platform_driver ep93xx_pwm_driver = { | |
287 | .driver = { | |
288 | .name = "ep93xx-pwm", | |
289 | .owner = THIS_MODULE, | |
290 | }, | |
291 | .remove = __exit_p(ep93xx_pwm_remove), | |
292 | }; | |
293 | ||
ead050de | 294 | module_platform_driver_probe(ep93xx_pwm_driver, ep93xx_pwm_probe); |
ef12379f HS |
295 | |
296 | MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>, " | |
297 | "H Hartley Sweeten <hsweeten@visionengravers.com>"); | |
298 | MODULE_DESCRIPTION("EP93xx PWM driver"); | |
299 | MODULE_LICENSE("GPL"); | |
300 | MODULE_ALIAS("platform:ep93xx-pwm"); |