diff options
author | Alexandre Pereira da Silva <aletes.xgr@gmail.com> | 2012-07-10 10:38:10 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@avionic-design.de> | 2012-07-23 07:24:05 -0400 |
commit | 2132fa8d95bc13b8b0e307553b04ee3517762ebe (patch) | |
tree | 7f7f843273eeb8b2ce6c9f05a725c02fb4abde4f /drivers | |
parent | 9fb978b12f9cc2b4df428764273d96877d0a262d (diff) |
pwm: add lpc32xx PWM support
Add lpc32xx SOC PWM driver.
Signed-off-by: Alexandre Pereira da Silva <aletes.xgr@gmail.com>
Acked-by: Roland Stigge <stigge@antcom.de>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/pwm/Kconfig | 10 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpc32xx.c | 148 |
3 files changed, 159 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0b2800fc1ca7..94e176e26f4c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -28,6 +28,16 @@ config PWM_IMX | |||
28 | To compile this driver as a module, choose M here: the module | 28 | To compile this driver as a module, choose M here: the module |
29 | will be called pwm-imx. | 29 | will be called pwm-imx. |
30 | 30 | ||
31 | config PWM_LPC32XX | ||
32 | tristate "LPC32XX PWM support" | ||
33 | depends on ARCH_LPC32XX | ||
34 | help | ||
35 | Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two | ||
36 | PWM controllers. | ||
37 | |||
38 | To compile this driver as a module, choose M here: the module | ||
39 | will be called pwm-lpc32xx. | ||
40 | |||
31 | config PWM_MXS | 41 | config PWM_MXS |
32 | tristate "Freescale MXS PWM support" | 42 | tristate "Freescale MXS PWM support" |
33 | depends on ARCH_MXS && OF | 43 | depends on ARCH_MXS && OF |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index cec250091cff..54597020350f 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_LPC32XX) += pwm-lpc32xx.o | ||
4 | obj-$(CONFIG_PWM_MXS) += pwm-mxs.o | 5 | obj-$(CONFIG_PWM_MXS) += pwm-mxs.o |
5 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o | 6 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o |
6 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | 7 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c new file mode 100644 index 000000000000..adb87f0c1633 --- /dev/null +++ b/drivers/pwm/pwm-lpc32xx.c | |||
@@ -0,0 +1,148 @@ | |||
1 | /* | ||
2 | * Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.com> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; version 2. | ||
7 | * | ||
8 | */ | ||
9 | |||
10 | #include <linux/clk.h> | ||
11 | #include <linux/err.h> | ||
12 | #include <linux/io.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/pwm.h> | ||
19 | #include <linux/slab.h> | ||
20 | |||
21 | struct lpc32xx_pwm_chip { | ||
22 | struct pwm_chip chip; | ||
23 | struct clk *clk; | ||
24 | void __iomem *base; | ||
25 | }; | ||
26 | |||
27 | #define PWM_ENABLE (1 << 31) | ||
28 | #define PWM_RELOADV(x) (((x) & 0xFF) << 8) | ||
29 | #define PWM_DUTY(x) ((x) & 0xFF) | ||
30 | |||
31 | #define to_lpc32xx_pwm_chip(_chip) \ | ||
32 | container_of(_chip, struct lpc32xx_pwm_chip, chip) | ||
33 | |||
34 | static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
35 | int duty_ns, int period_ns) | ||
36 | { | ||
37 | struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); | ||
38 | unsigned long long c; | ||
39 | int period_cycles, duty_cycles; | ||
40 | |||
41 | c = clk_get_rate(lpc32xx->clk) / 256; | ||
42 | c = c * period_ns; | ||
43 | do_div(c, NSEC_PER_SEC); | ||
44 | |||
45 | /* Handle high and low extremes */ | ||
46 | if (c == 0) | ||
47 | c = 1; | ||
48 | if (c > 255) | ||
49 | c = 0; /* 0 set division by 256 */ | ||
50 | period_cycles = c; | ||
51 | |||
52 | c = 256 * duty_ns; | ||
53 | do_div(c, period_ns); | ||
54 | duty_cycles = c; | ||
55 | |||
56 | writel(PWM_ENABLE | PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles), | ||
57 | lpc32xx->base + (pwm->hwpwm << 2)); | ||
58 | |||
59 | return 0; | ||
60 | } | ||
61 | |||
62 | static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
63 | { | ||
64 | struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); | ||
65 | |||
66 | return clk_enable(lpc32xx->clk); | ||
67 | } | ||
68 | |||
69 | static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
70 | { | ||
71 | struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); | ||
72 | |||
73 | writel(0, lpc32xx->base + (pwm->hwpwm << 2)); | ||
74 | clk_disable(lpc32xx->clk); | ||
75 | } | ||
76 | |||
77 | static const struct pwm_ops lpc32xx_pwm_ops = { | ||
78 | .config = lpc32xx_pwm_config, | ||
79 | .enable = lpc32xx_pwm_enable, | ||
80 | .disable = lpc32xx_pwm_disable, | ||
81 | .owner = THIS_MODULE, | ||
82 | }; | ||
83 | |||
84 | static int lpc32xx_pwm_probe(struct platform_device *pdev) | ||
85 | { | ||
86 | struct lpc32xx_pwm_chip *lpc32xx; | ||
87 | struct resource *res; | ||
88 | int ret; | ||
89 | |||
90 | lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); | ||
91 | if (!lpc32xx) | ||
92 | return -ENOMEM; | ||
93 | |||
94 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
95 | if (!res) | ||
96 | return -EINVAL; | ||
97 | |||
98 | lpc32xx->base = devm_request_and_ioremap(&pdev->dev, res); | ||
99 | if (!lpc32xx->base) | ||
100 | return -EADDRNOTAVAIL; | ||
101 | |||
102 | lpc32xx->clk = devm_clk_get(&pdev->dev, NULL); | ||
103 | if (IS_ERR(lpc32xx->clk)) | ||
104 | return PTR_ERR(lpc32xx->clk); | ||
105 | |||
106 | lpc32xx->chip.dev = &pdev->dev; | ||
107 | lpc32xx->chip.ops = &lpc32xx_pwm_ops; | ||
108 | lpc32xx->chip.npwm = 2; | ||
109 | |||
110 | ret = pwmchip_add(&lpc32xx->chip); | ||
111 | if (ret < 0) { | ||
112 | dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret); | ||
113 | return ret; | ||
114 | } | ||
115 | |||
116 | platform_set_drvdata(pdev, lpc32xx); | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | static int __devexit lpc32xx_pwm_remove(struct platform_device *pdev) | ||
122 | { | ||
123 | struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); | ||
124 | |||
125 | clk_disable(lpc32xx->clk); | ||
126 | return pwmchip_remove(&lpc32xx->chip); | ||
127 | } | ||
128 | |||
129 | static struct of_device_id lpc32xx_pwm_dt_ids[] = { | ||
130 | { .compatible = "nxp,lpc3220-pwm", }, | ||
131 | { /* sentinel */ } | ||
132 | }; | ||
133 | MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); | ||
134 | |||
135 | static struct platform_driver lpc32xx_pwm_driver = { | ||
136 | .driver = { | ||
137 | .name = "lpc32xx-pwm", | ||
138 | .of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids), | ||
139 | }, | ||
140 | .probe = lpc32xx_pwm_probe, | ||
141 | .remove = __devexit_p(lpc32xx_pwm_remove), | ||
142 | }; | ||
143 | module_platform_driver(lpc32xx_pwm_driver); | ||
144 | |||
145 | MODULE_ALIAS("platform:lpc32xx-pwm"); | ||
146 | MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>"); | ||
147 | MODULE_DESCRIPTION("LPC32XX PWM Driver"); | ||
148 | MODULE_LICENSE("GPL v2"); | ||