diff options
author | Shawn Guo <shawn.guo@linaro.org> | 2012-04-03 22:50:52 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@avionic-design.de> | 2012-07-02 16:06:34 -0400 |
commit | 4dce82c1e84007d38747533673c2289bfc6497d2 (patch) | |
tree | fc50fbb65e2ec13b816c010307fc5d725af5f110 /drivers/pwm | |
parent | a245ccebb4aad9fae60597d5cbad158c0de4483e (diff) |
pwm: add pwm-mxs support
Add generic PWM framework driver (DT only) for Freescale MXS.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-mxs.c | 207 |
3 files changed, 217 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a93feffbc36a..39bdebc0cb20 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -27,6 +27,15 @@ config PWM_IMX | |||
27 | To compile this driver as a module, choose M here: the module | 27 | To compile this driver as a module, choose M here: the module |
28 | will be called pwm-imx. | 28 | will be called pwm-imx. |
29 | 29 | ||
30 | config PWM_MXS | ||
31 | tristate "Freescale MXS PWM support" | ||
32 | depends on ARCH_MXS && OF | ||
33 | help | ||
34 | Generic PWM framework driver for Freescale MXS. | ||
35 | |||
36 | To compile this driver as a module, choose M here: the module | ||
37 | will be called pwm-mxs. | ||
38 | |||
30 | config PWM_PXA | 39 | config PWM_PXA |
31 | tristate "PXA PWM support" | 40 | tristate "PXA PWM support" |
32 | depends on ARCH_PXA | 41 | depends on ARCH_PXA |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index b7c0fcf4de16..cec250091cff 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | obj-$(CONFIG_PWM) += core.o | 1 | obj-$(CONFIG_PWM) += core.o |
2 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o | 2 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o |
3 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o | 3 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o |
4 | obj-$(CONFIG_PWM_MXS) += pwm-mxs.o | ||
4 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o | 5 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o |
5 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | 6 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
6 | obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o | 7 | obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o |
diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c new file mode 100644 index 000000000000..9602708372b8 --- /dev/null +++ b/drivers/pwm/pwm-mxs.c | |||
@@ -0,0 +1,207 @@ | |||
1 | /* | ||
2 | * Copyright 2012 Freescale Semiconductor, Inc. | ||
3 | * | ||
4 | * The code contained herein is licensed under the GNU General Public | ||
5 | * License. You may obtain a copy of the GNU General Public License | ||
6 | * Version 2 or later at the following locations: | ||
7 | * | ||
8 | * http://www.opensource.org/licenses/gpl-license.html | ||
9 | * http://www.gnu.org/copyleft/gpl.html | ||
10 | */ | ||
11 | |||
12 | #include <linux/clk.h> | ||
13 | #include <linux/err.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/of.h> | ||
18 | #include <linux/of_address.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/pwm.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <mach/common.h> | ||
23 | |||
24 | #define SET 0x4 | ||
25 | #define CLR 0x8 | ||
26 | #define TOG 0xc | ||
27 | |||
28 | #define PWM_CTRL 0x0 | ||
29 | #define PWM_ACTIVE0 0x10 | ||
30 | #define PWM_PERIOD0 0x20 | ||
31 | #define PERIOD_PERIOD(p) ((p) & 0xffff) | ||
32 | #define PERIOD_PERIOD_MAX 0x10000 | ||
33 | #define PERIOD_ACTIVE_HIGH (3 << 16) | ||
34 | #define PERIOD_INACTIVE_LOW (2 << 18) | ||
35 | #define PERIOD_CDIV(div) (((div) & 0x7) << 20) | ||
36 | #define PERIOD_CDIV_MAX 8 | ||
37 | |||
38 | struct mxs_pwm_chip { | ||
39 | struct pwm_chip chip; | ||
40 | struct device *dev; | ||
41 | struct clk *clk; | ||
42 | void __iomem *base; | ||
43 | }; | ||
44 | |||
45 | #define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip) | ||
46 | |||
47 | static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
48 | int duty_ns, int period_ns) | ||
49 | { | ||
50 | struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); | ||
51 | int ret, div = 0; | ||
52 | unsigned int period_cycles, duty_cycles; | ||
53 | unsigned long rate; | ||
54 | unsigned long long c; | ||
55 | |||
56 | rate = clk_get_rate(mxs->clk); | ||
57 | while (1) { | ||
58 | c = rate / (1 << div); | ||
59 | c = c * period_ns; | ||
60 | do_div(c, 1000000000); | ||
61 | if (c < PERIOD_PERIOD_MAX) | ||
62 | break; | ||
63 | div++; | ||
64 | if (div > PERIOD_CDIV_MAX) | ||
65 | return -EINVAL; | ||
66 | } | ||
67 | |||
68 | period_cycles = c; | ||
69 | c *= duty_ns; | ||
70 | do_div(c, period_ns); | ||
71 | duty_cycles = c; | ||
72 | |||
73 | /* | ||
74 | * If the PWM channel is disabled, make sure to turn on the clock | ||
75 | * before writing the register. Otherwise, keep it enabled. | ||
76 | */ | ||
77 | if (!test_bit(PWMF_ENABLED, &pwm->flags)) { | ||
78 | ret = clk_prepare_enable(mxs->clk); | ||
79 | if (ret) | ||
80 | return ret; | ||
81 | } | ||
82 | |||
83 | writel(duty_cycles << 16, | ||
84 | mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20); | ||
85 | writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH | | ||
86 | PERIOD_INACTIVE_LOW | PERIOD_CDIV(div), | ||
87 | mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20); | ||
88 | |||
89 | /* | ||
90 | * If the PWM is not enabled, turn the clock off again to save power. | ||
91 | */ | ||
92 | if (!test_bit(PWMF_ENABLED, &pwm->flags)) | ||
93 | clk_disable_unprepare(mxs->clk); | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
99 | { | ||
100 | struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); | ||
101 | int ret; | ||
102 | |||
103 | ret = clk_prepare_enable(mxs->clk); | ||
104 | if (ret) | ||
105 | return ret; | ||
106 | |||
107 | writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET); | ||
108 | |||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
113 | { | ||
114 | struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); | ||
115 | |||
116 | writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR); | ||
117 | |||
118 | clk_disable_unprepare(mxs->clk); | ||
119 | } | ||
120 | |||
121 | static const struct pwm_ops mxs_pwm_ops = { | ||
122 | .config = mxs_pwm_config, | ||
123 | .enable = mxs_pwm_enable, | ||
124 | .disable = mxs_pwm_disable, | ||
125 | .owner = THIS_MODULE, | ||
126 | }; | ||
127 | |||
128 | static int mxs_pwm_probe(struct platform_device *pdev) | ||
129 | { | ||
130 | struct device_node *np = pdev->dev.of_node; | ||
131 | struct mxs_pwm_chip *mxs; | ||
132 | int ret; | ||
133 | |||
134 | mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); | ||
135 | if (!mxs) | ||
136 | return -ENOMEM; | ||
137 | |||
138 | mxs->base = of_iomap(np, 0); | ||
139 | if (!mxs->base) | ||
140 | return -EADDRNOTAVAIL; | ||
141 | |||
142 | mxs->clk = clk_get(&pdev->dev, NULL); | ||
143 | if (IS_ERR(mxs->clk)) { | ||
144 | ret = PTR_ERR(mxs->clk); | ||
145 | goto iounmap; | ||
146 | } | ||
147 | |||
148 | mxs->chip.dev = &pdev->dev; | ||
149 | mxs->chip.ops = &mxs_pwm_ops; | ||
150 | mxs->chip.base = -1; | ||
151 | ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm); | ||
152 | if (ret < 0) { | ||
153 | dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret); | ||
154 | goto clk_put; | ||
155 | } | ||
156 | |||
157 | ret = pwmchip_add(&mxs->chip); | ||
158 | if (ret < 0) { | ||
159 | dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); | ||
160 | goto clk_put; | ||
161 | } | ||
162 | |||
163 | mxs->dev = &pdev->dev; | ||
164 | platform_set_drvdata(pdev, mxs); | ||
165 | |||
166 | mxs_reset_block(mxs->base); | ||
167 | |||
168 | return 0; | ||
169 | |||
170 | clk_put: | ||
171 | clk_put(mxs->clk); | ||
172 | iounmap: | ||
173 | iounmap(mxs->base); | ||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static int __devexit mxs_pwm_remove(struct platform_device *pdev) | ||
178 | { | ||
179 | struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); | ||
180 | |||
181 | pwmchip_remove(&mxs->chip); | ||
182 | clk_put(mxs->clk); | ||
183 | iounmap(mxs->base); | ||
184 | |||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static struct of_device_id mxs_pwm_dt_ids[] = { | ||
189 | { .compatible = "fsl,mxs-pwm", }, | ||
190 | { /* sentinel */ } | ||
191 | }; | ||
192 | MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); | ||
193 | |||
194 | static struct platform_driver mxs_pwm_driver = { | ||
195 | .driver = { | ||
196 | .name = "mxs-pwm", | ||
197 | .of_match_table = of_match_ptr(mxs_pwm_dt_ids), | ||
198 | }, | ||
199 | .probe = mxs_pwm_probe, | ||
200 | .remove = __devexit_p(mxs_pwm_remove), | ||
201 | }; | ||
202 | module_platform_driver(mxs_pwm_driver); | ||
203 | |||
204 | MODULE_ALIAS("platform:mxs-pwm"); | ||
205 | MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); | ||
206 | MODULE_DESCRIPTION("Freescale MXS PWM Driver"); | ||
207 | MODULE_LICENSE("GPL v2"); | ||