diff options
author | Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com> | 2016-07-05 02:00:25 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2016-07-11 06:49:25 -0400 |
commit | daa5abc41c80e32ebaf069bd482b7561e0ada71d (patch) | |
tree | 0d900c822657feec4f5cbb07deaf5a4ce8b5a083 | |
parent | f734f6ddbc12364021603d4bc8ba031b782508a5 (diff) |
pwm: Add support for Broadcom iProc PWM controller
Add support for the PWM controller present in Broadcom's iProc family of
SoCs. It has been tested on the Northstar+ bcm958625HR board.
Signed-off-by: Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>
[thierry.reding@gmail.com: bunch of coding style fixes, cleanups]
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | drivers/pwm/Kconfig | 10 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-bcm-iproc.c | 277 |
3 files changed, 288 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c182efc62c7b..d298ebdd872d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -74,6 +74,16 @@ config PWM_ATMEL_TCB | |||
74 | To compile this driver as a module, choose M here: the module | 74 | To compile this driver as a module, choose M here: the module |
75 | will be called pwm-atmel-tcb. | 75 | will be called pwm-atmel-tcb. |
76 | 76 | ||
77 | config PWM_BCM_IPROC | ||
78 | tristate "iProc PWM support" | ||
79 | depends on ARCH_BCM_IPROC | ||
80 | help | ||
81 | Generic PWM framework driver for Broadcom iProc PWM block. This | ||
82 | block is used in Broadcom iProc SoC's. | ||
83 | |||
84 | To compile this driver as a module, choose M here: the module | ||
85 | will be called pwm-bcm-iproc. | ||
86 | |||
77 | config PWM_BCM_KONA | 87 | config PWM_BCM_KONA |
78 | tristate "Kona PWM support" | 88 | tristate "Kona PWM support" |
79 | depends on ARCH_BCM_MOBILE | 89 | depends on ARCH_BCM_MOBILE |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index dd35bc121a18..a196d79fbd3f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o | |||
4 | obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o | 4 | obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o |
5 | obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o | 5 | obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o |
6 | obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o | 6 | obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o |
7 | obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o | ||
7 | obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o | 8 | obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o |
8 | obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o | 9 | obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o |
9 | obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o | 10 | obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o |
diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c new file mode 100644 index 000000000000..d961a8207b1c --- /dev/null +++ b/drivers/pwm/pwm-bcm-iproc.c | |||
@@ -0,0 +1,277 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Broadcom | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation version 2. | ||
7 | * | ||
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
9 | * kind, whether express or implied; without even the implied warranty | ||
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | #include <linux/clk.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/math64.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/of.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/pwm.h> | ||
23 | |||
24 | #define IPROC_PWM_CTRL_OFFSET 0x00 | ||
25 | #define IPROC_PWM_CTRL_TYPE_SHIFT(ch) (15 + (ch)) | ||
26 | #define IPROC_PWM_CTRL_POLARITY_SHIFT(ch) (8 + (ch)) | ||
27 | #define IPROC_PWM_CTRL_EN_SHIFT(ch) (ch) | ||
28 | |||
29 | #define IPROC_PWM_PERIOD_OFFSET(ch) (0x04 + ((ch) << 3)) | ||
30 | #define IPROC_PWM_PERIOD_MIN 0x02 | ||
31 | #define IPROC_PWM_PERIOD_MAX 0xffff | ||
32 | |||
33 | #define IPROC_PWM_DUTY_CYCLE_OFFSET(ch) (0x08 + ((ch) << 3)) | ||
34 | #define IPROC_PWM_DUTY_CYCLE_MIN 0x00 | ||
35 | #define IPROC_PWM_DUTY_CYCLE_MAX 0xffff | ||
36 | |||
37 | #define IPROC_PWM_PRESCALE_OFFSET 0x24 | ||
38 | #define IPROC_PWM_PRESCALE_BITS 0x06 | ||
39 | #define IPROC_PWM_PRESCALE_SHIFT(ch) ((3 - (ch)) * \ | ||
40 | IPROC_PWM_PRESCALE_BITS) | ||
41 | #define IPROC_PWM_PRESCALE_MASK(ch) (IPROC_PWM_PRESCALE_MAX << \ | ||
42 | IPROC_PWM_PRESCALE_SHIFT(ch)) | ||
43 | #define IPROC_PWM_PRESCALE_MIN 0x00 | ||
44 | #define IPROC_PWM_PRESCALE_MAX 0x3f | ||
45 | |||
46 | struct iproc_pwmc { | ||
47 | struct pwm_chip chip; | ||
48 | void __iomem *base; | ||
49 | struct clk *clk; | ||
50 | }; | ||
51 | |||
52 | static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip) | ||
53 | { | ||
54 | return container_of(chip, struct iproc_pwmc, chip); | ||
55 | } | ||
56 | |||
57 | static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel) | ||
58 | { | ||
59 | u32 value; | ||
60 | |||
61 | value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); | ||
62 | value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel); | ||
63 | writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); | ||
64 | |||
65 | /* must be a 400 ns delay between clearing and setting enable bit */ | ||
66 | ndelay(400); | ||
67 | } | ||
68 | |||
69 | static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel) | ||
70 | { | ||
71 | u32 value; | ||
72 | |||
73 | value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); | ||
74 | value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel)); | ||
75 | writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); | ||
76 | |||
77 | /* must be a 400 ns delay between clearing and setting enable bit */ | ||
78 | ndelay(400); | ||
79 | } | ||
80 | |||
81 | static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm, | ||
82 | struct pwm_state *state) | ||
83 | { | ||
84 | struct iproc_pwmc *ip = to_iproc_pwmc(chip); | ||
85 | u64 tmp, multi, rate; | ||
86 | u32 value, prescale; | ||
87 | |||
88 | rate = clk_get_rate(ip->clk); | ||
89 | |||
90 | value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); | ||
91 | |||
92 | if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm))) | ||
93 | state->enabled = true; | ||
94 | else | ||
95 | state->enabled = false; | ||
96 | |||
97 | if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm))) | ||
98 | state->polarity = PWM_POLARITY_NORMAL; | ||
99 | else | ||
100 | state->polarity = PWM_POLARITY_INVERSED; | ||
101 | |||
102 | value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); | ||
103 | prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); | ||
104 | prescale &= IPROC_PWM_PRESCALE_MAX; | ||
105 | |||
106 | multi = NSEC_PER_SEC * (prescale + 1); | ||
107 | |||
108 | value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); | ||
109 | tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; | ||
110 | state->period = div64_u64(tmp, rate); | ||
111 | |||
112 | value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); | ||
113 | tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; | ||
114 | state->duty_cycle = div64_u64(tmp, rate); | ||
115 | } | ||
116 | |||
117 | static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm, | ||
118 | struct pwm_state *state) | ||
119 | { | ||
120 | unsigned long prescale = IPROC_PWM_PRESCALE_MIN; | ||
121 | struct iproc_pwmc *ip = to_iproc_pwmc(chip); | ||
122 | u32 value, period, duty; | ||
123 | u64 rate; | ||
124 | |||
125 | rate = clk_get_rate(ip->clk); | ||
126 | |||
127 | /* | ||
128 | * Find period count, duty count and prescale to suit duty_cycle and | ||
129 | * period. This is done according to formulas described below: | ||
130 | * | ||
131 | * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE | ||
132 | * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE | ||
133 | * | ||
134 | * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) | ||
135 | * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) | ||
136 | */ | ||
137 | while (1) { | ||
138 | u64 value, div; | ||
139 | |||
140 | div = NSEC_PER_SEC * (prescale + 1); | ||
141 | value = rate * state->period; | ||
142 | period = div64_u64(value, div); | ||
143 | value = rate * state->duty_cycle; | ||
144 | duty = div64_u64(value, div); | ||
145 | |||
146 | if (period < IPROC_PWM_PERIOD_MIN || | ||
147 | duty < IPROC_PWM_DUTY_CYCLE_MIN) | ||
148 | return -EINVAL; | ||
149 | |||
150 | if (period <= IPROC_PWM_PERIOD_MAX && | ||
151 | duty <= IPROC_PWM_DUTY_CYCLE_MAX) | ||
152 | break; | ||
153 | |||
154 | /* Otherwise, increase prescale and recalculate counts */ | ||
155 | if (++prescale > IPROC_PWM_PRESCALE_MAX) | ||
156 | return -EINVAL; | ||
157 | } | ||
158 | |||
159 | iproc_pwmc_disable(ip, pwm->hwpwm); | ||
160 | |||
161 | /* Set prescale */ | ||
162 | value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); | ||
163 | value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm); | ||
164 | value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); | ||
165 | writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET); | ||
166 | |||
167 | /* set period and duty cycle */ | ||
168 | writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); | ||
169 | writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); | ||
170 | |||
171 | /* set polarity */ | ||
172 | value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); | ||
173 | |||
174 | if (state->polarity == PWM_POLARITY_NORMAL) | ||
175 | value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm); | ||
176 | else | ||
177 | value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)); | ||
178 | |||
179 | writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); | ||
180 | |||
181 | if (state->enabled) | ||
182 | iproc_pwmc_enable(ip, pwm->hwpwm); | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static const struct pwm_ops iproc_pwm_ops = { | ||
188 | .apply = iproc_pwmc_apply, | ||
189 | .get_state = iproc_pwmc_get_state, | ||
190 | }; | ||
191 | |||
192 | static int iproc_pwmc_probe(struct platform_device *pdev) | ||
193 | { | ||
194 | struct iproc_pwmc *ip; | ||
195 | struct resource *res; | ||
196 | unsigned int i; | ||
197 | u32 value; | ||
198 | int ret; | ||
199 | |||
200 | ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL); | ||
201 | if (!ip) | ||
202 | return -ENOMEM; | ||
203 | |||
204 | platform_set_drvdata(pdev, ip); | ||
205 | |||
206 | ip->chip.dev = &pdev->dev; | ||
207 | ip->chip.ops = &iproc_pwm_ops; | ||
208 | ip->chip.base = -1; | ||
209 | ip->chip.npwm = 4; | ||
210 | ip->chip.of_xlate = of_pwm_xlate_with_flags; | ||
211 | ip->chip.of_pwm_n_cells = 3; | ||
212 | |||
213 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
214 | ip->base = devm_ioremap_resource(&pdev->dev, res); | ||
215 | if (IS_ERR(ip->base)) | ||
216 | return PTR_ERR(ip->base); | ||
217 | |||
218 | ip->clk = devm_clk_get(&pdev->dev, NULL); | ||
219 | if (IS_ERR(ip->clk)) { | ||
220 | dev_err(&pdev->dev, "failed to get clock: %ld\n", | ||
221 | PTR_ERR(ip->clk)); | ||
222 | return PTR_ERR(ip->clk); | ||
223 | } | ||
224 | |||
225 | ret = clk_prepare_enable(ip->clk); | ||
226 | if (ret < 0) { | ||
227 | dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); | ||
228 | return ret; | ||
229 | } | ||
230 | |||
231 | /* Set full drive and normal polarity for all channels */ | ||
232 | value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); | ||
233 | |||
234 | for (i = 0; i < ip->chip.npwm; i++) { | ||
235 | value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i)); | ||
236 | value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i); | ||
237 | } | ||
238 | |||
239 | writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); | ||
240 | |||
241 | ret = pwmchip_add(&ip->chip); | ||
242 | if (ret < 0) { | ||
243 | dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); | ||
244 | clk_disable_unprepare(ip->clk); | ||
245 | } | ||
246 | |||
247 | return ret; | ||
248 | } | ||
249 | |||
250 | static int iproc_pwmc_remove(struct platform_device *pdev) | ||
251 | { | ||
252 | struct iproc_pwmc *ip = platform_get_drvdata(pdev); | ||
253 | |||
254 | clk_disable_unprepare(ip->clk); | ||
255 | |||
256 | return pwmchip_remove(&ip->chip); | ||
257 | } | ||
258 | |||
259 | static const struct of_device_id bcm_iproc_pwmc_dt[] = { | ||
260 | { .compatible = "brcm,iproc-pwm" }, | ||
261 | { }, | ||
262 | }; | ||
263 | MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt); | ||
264 | |||
265 | static struct platform_driver iproc_pwmc_driver = { | ||
266 | .driver = { | ||
267 | .name = "bcm-iproc-pwm", | ||
268 | .of_match_table = bcm_iproc_pwmc_dt, | ||
269 | }, | ||
270 | .probe = iproc_pwmc_probe, | ||
271 | .remove = iproc_pwmc_remove, | ||
272 | }; | ||
273 | module_platform_driver(iproc_pwmc_driver); | ||
274 | |||
275 | MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>"); | ||
276 | MODULE_DESCRIPTION("Broadcom iProc PWM driver"); | ||
277 | MODULE_LICENSE("GPL v2"); | ||