aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShiraz Hashim <shiraz.hashim@st.com>2012-10-25 00:09:13 -0400
committerThierry Reding <thierry.reding@avionic-design.de>2012-11-22 16:47:03 -0500
commitce20364bf75b0e91156698eea49f1e8586b212c1 (patch)
treee6a2506dbb883d704ec1f1f3d3fc1ff62479c75b
parent63e1ed2364050073770c085021377d7764969b85 (diff)
pwm: Add SPEAr PWM chip driver support
Add support for PWM chips present on SPEAr platforms. These PWM chips support 4 channel output with programmable duty cycle and frequency. More details on these PWM chips can be obtained from relevant chapter of reference manual, present at following[1] location. 1. http://www.st.com/internet/mcu/product/251211.jsp Cc: Thierry Reding <thierry.reding@avionic-design.de> Signed-off-by: Shiraz Hashim <shiraz.hashim@st.com> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Reviewed-by: Vipin Kumar <vipin.kumar@st.com> Acked-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
-rw-r--r--Documentation/devicetree/bindings/pwm/spear-pwm.txt18
-rw-r--r--drivers/pwm/Kconfig11
-rw-r--r--drivers/pwm/Makefile1
-rw-r--r--drivers/pwm/pwm-spear.c276
4 files changed, 306 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/pwm/spear-pwm.txt b/Documentation/devicetree/bindings/pwm/spear-pwm.txt
new file mode 100644
index 000000000000..3ac779d83386
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/spear-pwm.txt
@@ -0,0 +1,18 @@
1== ST SPEAr SoC PWM controller ==
2
3Required properties:
4- compatible: should be one of:
5 - "st,spear320-pwm"
6 - "st,spear1340-pwm"
7- reg: physical base address and length of the controller's registers
8- #pwm-cells: number of cells used to specify PWM which is fixed to 2 on
9 SPEAr. The first cell specifies the per-chip index of the PWM to use and
10 the second cell is the period in nanoseconds.
11
12Example:
13
14 pwm: pwm@a8000000 {
15 compatible ="st,spear320-pwm";
16 reg = <0xa8000000 0x1000>;
17 #pwm-cells = <2>;
18 };
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index ed81720e7b2b..6e556c7da81e 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -112,6 +112,17 @@ config PWM_SAMSUNG
112 To compile this driver as a module, choose M here: the module 112 To compile this driver as a module, choose M here: the module
113 will be called pwm-samsung. 113 will be called pwm-samsung.
114 114
115config PWM_SPEAR
116 tristate "STMicroelectronics SPEAr PWM support"
117 depends on PLAT_SPEAR
118 depends on OF
119 help
120 Generic PWM framework driver for the PWM controller on ST
121 SPEAr SoCs.
122
123 To compile this driver as a module, choose M here: the module
124 will be called pwm-spear.
125
115config PWM_TEGRA 126config PWM_TEGRA
116 tristate "NVIDIA Tegra PWM support" 127 tristate "NVIDIA Tegra PWM support"
117 depends on ARCH_TEGRA 128 depends on ARCH_TEGRA
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index acfe4821c58b..3b3f4c9aa4ef 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
8obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o 8obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
9obj-$(CONFIG_PWM_PXA) += pwm-pxa.o 9obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
10obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o 10obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
11obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
11obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o 12obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
12obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o 13obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
13obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o 14obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c
new file mode 100644
index 000000000000..6a8fd9b5dc42
--- /dev/null
+++ b/drivers/pwm/pwm-spear.c
@@ -0,0 +1,276 @@
1/*
2 * ST Microelectronics SPEAr Pulse Width Modulator driver
3 *
4 * Copyright (C) 2012 ST Microelectronics
5 * Shiraz Hashim <shiraz.hashim@st.com>
6 *
7 * This file is licensed under the terms of the GNU General Public
8 * License version 2. This program is licensed "as is" without any
9 * warranty of any kind, whether express or implied.
10 */
11
12#include <linux/clk.h>
13#include <linux/err.h>
14#include <linux/io.h>
15#include <linux/ioport.h>
16#include <linux/kernel.h>
17#include <linux/math64.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/platform_device.h>
21#include <linux/pwm.h>
22#include <linux/slab.h>
23#include <linux/types.h>
24
25#define NUM_PWM 4
26
27/* PWM registers and bits definitions */
28#define PWMCR 0x00 /* Control Register */
29#define PWMCR_PWM_ENABLE 0x1
30#define PWMCR_PRESCALE_SHIFT 2
31#define PWMCR_MIN_PRESCALE 0x00
32#define PWMCR_MAX_PRESCALE 0x3FFF
33
34#define PWMDCR 0x04 /* Duty Cycle Register */
35#define PWMDCR_MIN_DUTY 0x0001
36#define PWMDCR_MAX_DUTY 0xFFFF
37
38#define PWMPCR 0x08 /* Period Register */
39#define PWMPCR_MIN_PERIOD 0x0001
40#define PWMPCR_MAX_PERIOD 0xFFFF
41
42/* Following only available on 13xx SoCs */
43#define PWMMCR 0x3C /* Master Control Register */
44#define PWMMCR_PWM_ENABLE 0x1
45
46/**
47 * struct spear_pwm_chip - struct representing pwm chip
48 *
49 * @mmio_base: base address of pwm chip
50 * @clk: pointer to clk structure of pwm chip
51 * @chip: linux pwm chip representation
52 * @dev: pointer to device structure of pwm chip
53 */
54struct spear_pwm_chip {
55 void __iomem *mmio_base;
56 struct clk *clk;
57 struct pwm_chip chip;
58 struct device *dev;
59};
60
61static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
62{
63 return container_of(chip, struct spear_pwm_chip, chip);
64}
65
66static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
67 unsigned long offset)
68{
69 return readl_relaxed(chip->mmio_base + (num << 4) + offset);
70}
71
72static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
73 unsigned int num, unsigned long offset,
74 unsigned long val)
75{
76 writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
77}
78
79int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns,
80 int period_ns)
81{
82 struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
83 u64 val, div, clk_rate;
84 unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
85 int ret;
86
87 /*
88 * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
89 * according to formulas described below:
90 *
91 * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
92 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
93 *
94 * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
95 * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
96 */
97 clk_rate = clk_get_rate(pc->clk);
98 while (1) {
99 div = 1000000000;
100 div *= 1 + prescale;
101 val = clk_rate * period_ns;
102 pv = div64_u64(val, div);
103 val = clk_rate * duty_ns;
104 dc = div64_u64(val, div);
105
106 /* if duty_ns and period_ns are not achievable then return */
107 if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
108 return -EINVAL;
109
110 /*
111 * if pv and dc have crossed their upper limit, then increase
112 * prescale and recalculate pv and dc.
113 */
114 if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
115 if (++prescale > PWMCR_MAX_PRESCALE)
116 return -EINVAL;
117 continue;
118 }
119 break;
120 }
121
122 /*
123 * NOTE: the clock to PWM has to be enabled first before writing to the
124 * registers.
125 */
126 ret = clk_enable(pc->clk);
127 if (ret)
128 return ret;
129
130 spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
131 prescale << PWMCR_PRESCALE_SHIFT);
132 spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
133 spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
134 clk_disable(pc->clk);
135
136 return 0;
137}
138
139static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
140{
141 struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
142 int rc = 0;
143 u32 val;
144
145 rc = clk_enable(pc->clk);
146 if (!rc)
147 return rc;
148
149 val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
150 val |= PWMCR_PWM_ENABLE;
151 spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
152
153 return 0;
154}
155
156static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
157{
158 struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
159 u32 val;
160
161 val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
162 val &= ~PWMCR_PWM_ENABLE;
163 spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
164
165 clk_disable(pc->clk);
166}
167
168static const struct pwm_ops spear_pwm_ops = {
169 .config = spear_pwm_config,
170 .enable = spear_pwm_enable,
171 .disable = spear_pwm_disable,
172 .owner = THIS_MODULE,
173};
174
175static int spear_pwm_probe(struct platform_device *pdev)
176{
177 struct device_node *np = pdev->dev.of_node;
178 struct spear_pwm_chip *pc;
179 struct resource *r;
180 int ret;
181 u32 val;
182
183 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
184 if (!r) {
185 dev_err(&pdev->dev, "no memory resources defined\n");
186 return -ENODEV;
187 }
188
189 pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
190 if (!pc) {
191 dev_err(&pdev->dev, "failed to allocate memory\n");
192 return -ENOMEM;
193 }
194
195 pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
196 if (!pc->mmio_base)
197 return -EADDRNOTAVAIL;
198
199 pc->clk = devm_clk_get(&pdev->dev, NULL);
200 if (IS_ERR(pc->clk))
201 return PTR_ERR(pc->clk);
202
203 pc->dev = &pdev->dev;
204 platform_set_drvdata(pdev, pc);
205
206 pc->chip.dev = &pdev->dev;
207 pc->chip.ops = &spear_pwm_ops;
208 pc->chip.base = -1;
209 pc->chip.npwm = NUM_PWM;
210
211 ret = clk_prepare(pc->clk);
212 if (!ret)
213 return ret;
214
215 if (of_device_is_compatible(np, "st,spear1340-pwm")) {
216 ret = clk_enable(pc->clk);
217 if (!ret) {
218 clk_unprepare(pc->clk);
219 return ret;
220 }
221 /*
222 * Following enables PWM chip, channels would still be
223 * enabled individually through their control register
224 */
225 val = readl_relaxed(pc->mmio_base + PWMMCR);
226 val |= PWMMCR_PWM_ENABLE;
227 writel_relaxed(val, pc->mmio_base + PWMMCR);
228
229 clk_disable(pc->clk);
230 }
231
232 ret = pwmchip_add(&pc->chip);
233 if (!ret) {
234 clk_unprepare(pc->clk);
235 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
236 }
237
238 return ret;
239}
240
241static int spear_pwm_remove(struct platform_device *pdev)
242{
243 struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
244 int i;
245
246 for (i = 0; i < NUM_PWM; i++)
247 pwm_disable(&pc->chip.pwms[i]);
248
249 /* clk was prepared in probe, hence unprepare it here */
250 clk_unprepare(pc->clk);
251 return pwmchip_remove(&pc->chip);
252}
253
254static struct of_device_id spear_pwm_of_match[] = {
255 { .compatible = "st,spear320-pwm" },
256 { .compatible = "st,spear1340-pwm" },
257 { }
258};
259
260MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
261
262static struct platform_driver spear_pwm_driver = {
263 .driver = {
264 .name = "spear-pwm",
265 .of_match_table = spear_pwm_of_match,
266 },
267 .probe = spear_pwm_probe,
268 .remove = spear_pwm_remove,
269};
270
271module_platform_driver(spear_pwm_driver);
272
273MODULE_LICENSE("GPL");
274MODULE_AUTHOR("Shiraz Hashim <shiraz.hashim@st.com>");
275MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
276MODULE_ALIAS("platform:spear-pwm");