aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-tiecap.c
diff options
context:
space:
mode:
authorPhilip, Avinash <avinashphilip@ti.com>2012-07-25 07:28:18 -0400
committerThierry Reding <thierry.reding@avionic-design.de>2012-07-26 01:44:52 -0400
commit8e0cb05b3b758885aab09883adf189f8dd8402b2 (patch)
tree2ac613b6894bec3dbd75095a9f27c90dac76f4b5 /drivers/pwm/pwm-tiecap.c
parentd295b129762bf6b2b7541243f496e363580de4a2 (diff)
pwm: pwm-tiecap: PWM driver support for ECAP APWM
ECAP hardware on AM33XX SOC supports auxiliary PWM (APWM) feature. This commit adds PWM driver support for ECAP hardware on AM33XX SOC. In the ECAP hardware, each PWM pin can also be configured to be in capture mode. Current implementation only supports PWM mode of operation. Also, hardware supports sync between multiple PWM pins but the driver supports simple independent PWM functionality. Reviewed-by: Vaibhav Bedia <vaibhav.bedia@ti.com> Signed-off-by: Philip, Avinash <avinashphilip@ti.com> Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers/pwm/pwm-tiecap.c')
-rw-r--r--drivers/pwm/pwm-tiecap.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c
new file mode 100644
index 000000000000..3c2ad284ee3e
--- /dev/null
+++ b/drivers/pwm/pwm-tiecap.c
@@ -0,0 +1,232 @@
1/*
2 * ECAP PWM driver
3 *
4 * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
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 of the License, or
9 * (at your option) 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; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21#include <linux/module.h>
22#include <linux/platform_device.h>
23#include <linux/io.h>
24#include <linux/err.h>
25#include <linux/clk.h>
26#include <linux/pm_runtime.h>
27#include <linux/pwm.h>
28
29/* ECAP registers and bits definitions */
30#define CAP1 0x08
31#define CAP2 0x0C
32#define CAP3 0x10
33#define CAP4 0x14
34#define ECCTL2 0x2A
35#define ECCTL2_APWM_MODE BIT(9)
36#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))
37#define ECCTL2_TSCTR_FREERUN BIT(4)
38
39struct ecap_pwm_chip {
40 struct pwm_chip chip;
41 unsigned int clk_rate;
42 void __iomem *mmio_base;
43};
44
45static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
46{
47 return container_of(chip, struct ecap_pwm_chip, chip);
48}
49
50/*
51 * period_ns = 10^9 * period_cycles / PWM_CLK_RATE
52 * duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE
53 */
54static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
55 int duty_ns, int period_ns)
56{
57 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
58 unsigned long long c;
59 unsigned long period_cycles, duty_cycles;
60 unsigned int reg_val;
61
62 if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC)
63 return -ERANGE;
64
65 c = pc->clk_rate;
66 c = c * period_ns;
67 do_div(c, NSEC_PER_SEC);
68 period_cycles = (unsigned long)c;
69
70 if (period_cycles < 1) {
71 period_cycles = 1;
72 duty_cycles = 1;
73 } else {
74 c = pc->clk_rate;
75 c = c * duty_ns;
76 do_div(c, NSEC_PER_SEC);
77 duty_cycles = (unsigned long)c;
78 }
79
80 pm_runtime_get_sync(pc->chip.dev);
81
82 reg_val = readw(pc->mmio_base + ECCTL2);
83
84 /* Configure APWM mode & disable sync option */
85 reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
86
87 writew(reg_val, pc->mmio_base + ECCTL2);
88
89 if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
90 /* Update active registers if not running */
91 writel(duty_cycles, pc->mmio_base + CAP2);
92 writel(period_cycles, pc->mmio_base + CAP1);
93 } else {
94 /*
95 * Update shadow registers to configure period and
96 * compare values. This helps current PWM period to
97 * complete on reconfiguring
98 */
99 writel(duty_cycles, pc->mmio_base + CAP4);
100 writel(period_cycles, pc->mmio_base + CAP3);
101 }
102
103 pm_runtime_put_sync(pc->chip.dev);
104 return 0;
105}
106
107static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
108{
109 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
110 unsigned int reg_val;
111
112 /* Leave clock enabled on enabling PWM */
113 pm_runtime_get_sync(pc->chip.dev);
114
115 /*
116 * Enable 'Free run Time stamp counter mode' to start counter
117 * and 'APWM mode' to enable APWM output
118 */
119 reg_val = readw(pc->mmio_base + ECCTL2);
120 reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
121 writew(reg_val, pc->mmio_base + ECCTL2);
122 return 0;
123}
124
125static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
126{
127 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
128 unsigned int reg_val;
129
130 /*
131 * Disable 'Free run Time stamp counter mode' to stop counter
132 * and 'APWM mode' to put APWM output to low
133 */
134 reg_val = readw(pc->mmio_base + ECCTL2);
135 reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
136 writew(reg_val, pc->mmio_base + ECCTL2);
137
138 /* Disable clock on PWM disable */
139 pm_runtime_put_sync(pc->chip.dev);
140}
141
142static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
143{
144 if (test_bit(PWMF_ENABLED, &pwm->flags)) {
145 dev_warn(chip->dev, "Removing PWM device without disabling\n");
146 pm_runtime_put_sync(chip->dev);
147 }
148}
149
150static const struct pwm_ops ecap_pwm_ops = {
151 .free = ecap_pwm_free,
152 .config = ecap_pwm_config,
153 .enable = ecap_pwm_enable,
154 .disable = ecap_pwm_disable,
155 .owner = THIS_MODULE,
156};
157
158static int __devinit ecap_pwm_probe(struct platform_device *pdev)
159{
160 int ret;
161 struct resource *r;
162 struct clk *clk;
163 struct ecap_pwm_chip *pc;
164
165 pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
166 if (!pc) {
167 dev_err(&pdev->dev, "failed to allocate memory\n");
168 return -ENOMEM;
169 }
170
171 clk = devm_clk_get(&pdev->dev, "fck");
172 if (IS_ERR(clk)) {
173 dev_err(&pdev->dev, "failed to get clock\n");
174 return PTR_ERR(clk);
175 }
176
177 pc->clk_rate = clk_get_rate(clk);
178 if (!pc->clk_rate) {
179 dev_err(&pdev->dev, "failed to get clock rate\n");
180 return -EINVAL;
181 }
182
183 pc->chip.dev = &pdev->dev;
184 pc->chip.ops = &ecap_pwm_ops;
185 pc->chip.base = -1;
186 pc->chip.npwm = 1;
187
188 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
189 if (!r) {
190 dev_err(&pdev->dev, "no memory resource defined\n");
191 return -ENODEV;
192 }
193
194 pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
195 if (!pc->mmio_base) {
196 dev_err(&pdev->dev, "failed to ioremap() registers\n");
197 return -EADDRNOTAVAIL;
198 }
199
200 ret = pwmchip_add(&pc->chip);
201 if (ret < 0) {
202 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
203 return ret;
204 }
205
206 pm_runtime_enable(&pdev->dev);
207 platform_set_drvdata(pdev, pc);
208 return 0;
209}
210
211static int __devexit ecap_pwm_remove(struct platform_device *pdev)
212{
213 struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
214
215 pm_runtime_put_sync(&pdev->dev);
216 pm_runtime_disable(&pdev->dev);
217 return pwmchip_remove(&pc->chip);
218}
219
220static struct platform_driver ecap_pwm_driver = {
221 .driver = {
222 .name = "ecap",
223 },
224 .probe = ecap_pwm_probe,
225 .remove = __devexit_p(ecap_pwm_remove),
226};
227
228module_platform_driver(ecap_pwm_driver);
229
230MODULE_DESCRIPTION("ECAP PWM driver");
231MODULE_AUTHOR("Texas Instruments");
232MODULE_LICENSE("GPL");