diff options
author | Florian Fainelli <f.fainelli@gmail.com> | 2015-09-14 19:47:06 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2015-10-06 10:07:27 -0400 |
commit | 3a9f5957020f87503afd5d4c1d5705253f0b3569 (patch) | |
tree | a21b140943b20fc0c6cd751e1f0ad92e32d57152 | |
parent | 9495525e51e6f54fda4fbb7ef19eed4d131f18b2 (diff) |
pwm: Add Broadcom BCM7038 PWM controller support
Add support for the BCM7038-style PWM controller found in all BCM7xxx STB SoCs.
This controller has a hardcoded 2 channels per controller, and cascades a
variable frequency generator on top of a fixed frequency generator which offers
a range of a 148ns period all the way to ~622ms periods.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
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-brcmstb.c | 343 |
3 files changed, 354 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index de18bfe146b4..b2843060d373 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -110,6 +110,16 @@ config PWM_BFIN | |||
110 | To compile this driver as a module, choose M here: the module | 110 | To compile this driver as a module, choose M here: the module |
111 | will be called pwm-bfin. | 111 | will be called pwm-bfin. |
112 | 112 | ||
113 | config PWM_BRCMSTB | ||
114 | tristate "Broadcom STB PWM support" | ||
115 | depends on ARCH_BRCMSTB || BMIPS_GENERIC | ||
116 | help | ||
117 | Generic PWM framework driver for the Broadcom Set-top-Box | ||
118 | SoCs (BCM7xxx). | ||
119 | |||
120 | To compile this driver as a module, choose M Here: the module | ||
121 | will be called pwm-brcmstb.c. | ||
122 | |||
113 | config PWM_CLPS711X | 123 | config PWM_CLPS711X |
114 | tristate "CLPS711X PWM support" | 124 | tristate "CLPS711X PWM support" |
115 | depends on ARCH_CLPS711X || COMPILE_TEST | 125 | depends on ARCH_CLPS711X || COMPILE_TEST |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index fc61acad9787..963e6c5b4d9f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -8,6 +8,7 @@ obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o | |||
8 | obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o | 8 | obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o |
9 | obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o | 9 | obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o |
10 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o | 10 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o |
11 | obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o | ||
11 | obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o | 12 | obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o |
12 | obj-$(CONFIG_PWM_CRC) += pwm-crc.o | 13 | obj-$(CONFIG_PWM_CRC) += pwm-crc.o |
13 | obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o | 14 | obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o |
diff --git a/drivers/pwm/pwm-brcmstb.c b/drivers/pwm/pwm-brcmstb.c new file mode 100644 index 000000000000..423ce087cd9c --- /dev/null +++ b/drivers/pwm/pwm-brcmstb.c | |||
@@ -0,0 +1,343 @@ | |||
1 | /* | ||
2 | * Broadcom BCM7038 PWM driver | ||
3 | * Author: Florian Fainelli | ||
4 | * | ||
5 | * Copyright (C) 2015 Broadcom Corporation | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
19 | |||
20 | #include <linux/clk.h> | ||
21 | #include <linux/export.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/of.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/pwm.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | |||
31 | #define PWM_CTRL 0x00 | ||
32 | #define CTRL_START BIT(0) | ||
33 | #define CTRL_OEB BIT(1) | ||
34 | #define CTRL_FORCE_HIGH BIT(2) | ||
35 | #define CTRL_OPENDRAIN BIT(3) | ||
36 | #define CTRL_CHAN_OFFS 4 | ||
37 | |||
38 | #define PWM_CTRL2 0x04 | ||
39 | #define CTRL2_OUT_SELECT BIT(0) | ||
40 | |||
41 | #define PWM_CH_SIZE 0x8 | ||
42 | |||
43 | #define PWM_CWORD_MSB(ch) (0x08 + ((ch) * PWM_CH_SIZE)) | ||
44 | #define PWM_CWORD_LSB(ch) (0x0c + ((ch) * PWM_CH_SIZE)) | ||
45 | |||
46 | /* Number of bits for the CWORD value */ | ||
47 | #define CWORD_BIT_SIZE 16 | ||
48 | |||
49 | /* | ||
50 | * Maximum control word value allowed when variable-frequency PWM is used as a | ||
51 | * clock for the constant-frequency PMW. | ||
52 | */ | ||
53 | #define CONST_VAR_F_MAX 32768 | ||
54 | #define CONST_VAR_F_MIN 1 | ||
55 | |||
56 | #define PWM_ON(ch) (0x18 + ((ch) * PWM_CH_SIZE)) | ||
57 | #define PWM_ON_MIN 1 | ||
58 | #define PWM_PERIOD(ch) (0x1c + ((ch) * PWM_CH_SIZE)) | ||
59 | #define PWM_PERIOD_MIN 0 | ||
60 | |||
61 | #define PWM_ON_PERIOD_MAX 0xff | ||
62 | |||
63 | struct brcmstb_pwm { | ||
64 | void __iomem *base; | ||
65 | spinlock_t lock; | ||
66 | struct clk *clk; | ||
67 | struct pwm_chip chip; | ||
68 | }; | ||
69 | |||
70 | static inline u32 brcmstb_pwm_readl(struct brcmstb_pwm *p, | ||
71 | unsigned int offset) | ||
72 | { | ||
73 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) | ||
74 | return __raw_readl(p->base + offset); | ||
75 | else | ||
76 | return readl_relaxed(p->base + offset); | ||
77 | } | ||
78 | |||
79 | static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value, | ||
80 | unsigned int offset) | ||
81 | { | ||
82 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) | ||
83 | __raw_writel(value, p->base + offset); | ||
84 | else | ||
85 | writel_relaxed(value, p->base + offset); | ||
86 | } | ||
87 | |||
88 | static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip) | ||
89 | { | ||
90 | return container_of(chip, struct brcmstb_pwm, chip); | ||
91 | } | ||
92 | |||
93 | /* | ||
94 | * Fv is derived from the variable frequency output. The variable frequency | ||
95 | * output is configured using this formula: | ||
96 | * | ||
97 | * W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword | ||
98 | * | ||
99 | * Fv = W x 2 ^ -16 x 27Mhz (reference clock) | ||
100 | * | ||
101 | * The period is: (period + 1) / Fv and "on" time is on / (period + 1) | ||
102 | * | ||
103 | * The PWM core framework specifies that the "duty_ns" parameter is in fact the | ||
104 | * "on" time, so this translates directly into our HW programming here. | ||
105 | */ | ||
106 | static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
107 | int duty_ns, int period_ns) | ||
108 | { | ||
109 | struct brcmstb_pwm *p = to_brcmstb_pwm(chip); | ||
110 | unsigned long pc, dc, cword = CONST_VAR_F_MAX; | ||
111 | unsigned int channel = pwm->hwpwm; | ||
112 | u32 value; | ||
113 | |||
114 | /* | ||
115 | * If asking for a duty_ns equal to period_ns, we need to substract | ||
116 | * the period value by 1 to make it shorter than the "on" time and | ||
117 | * produce a flat 100% duty cycle signal, and max out the "on" time | ||
118 | */ | ||
119 | if (duty_ns == period_ns) { | ||
120 | dc = PWM_ON_PERIOD_MAX; | ||
121 | pc = PWM_ON_PERIOD_MAX - 1; | ||
122 | goto done; | ||
123 | } | ||
124 | |||
125 | while (1) { | ||
126 | u64 rate, tmp; | ||
127 | |||
128 | /* | ||
129 | * Calculate the base rate from base frequency and current | ||
130 | * cword | ||
131 | */ | ||
132 | rate = (u64)clk_get_rate(p->clk) * (u64)cword; | ||
133 | do_div(rate, 1 << CWORD_BIT_SIZE); | ||
134 | |||
135 | tmp = period_ns * rate; | ||
136 | do_div(tmp, NSEC_PER_SEC); | ||
137 | pc = tmp; | ||
138 | |||
139 | tmp = (duty_ns + 1) * rate; | ||
140 | do_div(tmp, NSEC_PER_SEC); | ||
141 | dc = tmp; | ||
142 | |||
143 | /* | ||
144 | * We can be called with separate duty and period updates, | ||
145 | * so do not reject dc == 0 right away | ||
146 | */ | ||
147 | if (pc == PWM_PERIOD_MIN || (dc < PWM_ON_MIN && duty_ns)) | ||
148 | return -EINVAL; | ||
149 | |||
150 | /* We converged on a calculation */ | ||
151 | if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX) | ||
152 | break; | ||
153 | |||
154 | /* | ||
155 | * The cword needs to be a power of 2 for the variable | ||
156 | * frequency generator to output a 50% duty cycle variable | ||
157 | * frequency which is used as input clock to the fixed | ||
158 | * frequency generator. | ||
159 | */ | ||
160 | cword >>= 1; | ||
161 | |||
162 | /* | ||
163 | * Desired periods are too large, we do not have a divider | ||
164 | * for them | ||
165 | */ | ||
166 | if (cword < CONST_VAR_F_MIN) | ||
167 | return -EINVAL; | ||
168 | } | ||
169 | |||
170 | done: | ||
171 | /* | ||
172 | * Configure the defined "cword" value to have the variable frequency | ||
173 | * generator output a base frequency for the constant frequency | ||
174 | * generator to derive from. | ||
175 | */ | ||
176 | spin_lock(&p->lock); | ||
177 | brcmstb_pwm_writel(p, cword >> 8, PWM_CWORD_MSB(channel)); | ||
178 | brcmstb_pwm_writel(p, cword & 0xff, PWM_CWORD_LSB(channel)); | ||
179 | |||
180 | /* Select constant frequency signal output */ | ||
181 | value = brcmstb_pwm_readl(p, PWM_CTRL2); | ||
182 | value |= CTRL2_OUT_SELECT << (channel * CTRL_CHAN_OFFS); | ||
183 | brcmstb_pwm_writel(p, value, PWM_CTRL2); | ||
184 | |||
185 | /* Configure on and period value */ | ||
186 | brcmstb_pwm_writel(p, pc, PWM_PERIOD(channel)); | ||
187 | brcmstb_pwm_writel(p, dc, PWM_ON(channel)); | ||
188 | spin_unlock(&p->lock); | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p, | ||
194 | unsigned int channel, bool enable) | ||
195 | { | ||
196 | unsigned int shift = channel * CTRL_CHAN_OFFS; | ||
197 | u32 value; | ||
198 | |||
199 | spin_lock(&p->lock); | ||
200 | value = brcmstb_pwm_readl(p, PWM_CTRL); | ||
201 | |||
202 | if (enable) { | ||
203 | value &= ~(CTRL_OEB << shift); | ||
204 | value |= (CTRL_START | CTRL_OPENDRAIN) << shift; | ||
205 | } else { | ||
206 | value &= ~((CTRL_START | CTRL_OPENDRAIN) << shift); | ||
207 | value |= CTRL_OEB << shift; | ||
208 | } | ||
209 | |||
210 | brcmstb_pwm_writel(p, value, PWM_CTRL); | ||
211 | spin_unlock(&p->lock); | ||
212 | } | ||
213 | |||
214 | static int brcmstb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
215 | { | ||
216 | struct brcmstb_pwm *p = to_brcmstb_pwm(chip); | ||
217 | |||
218 | brcmstb_pwm_enable_set(p, pwm->hwpwm, true); | ||
219 | |||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | static void brcmstb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
224 | { | ||
225 | struct brcmstb_pwm *p = to_brcmstb_pwm(chip); | ||
226 | |||
227 | brcmstb_pwm_enable_set(p, pwm->hwpwm, false); | ||
228 | } | ||
229 | |||
230 | static const struct pwm_ops brcmstb_pwm_ops = { | ||
231 | .config = brcmstb_pwm_config, | ||
232 | .enable = brcmstb_pwm_enable, | ||
233 | .disable = brcmstb_pwm_disable, | ||
234 | .owner = THIS_MODULE, | ||
235 | }; | ||
236 | |||
237 | static const struct of_device_id brcmstb_pwm_of_match[] = { | ||
238 | { .compatible = "brcm,bcm7038-pwm", }, | ||
239 | { /* sentinel */ } | ||
240 | }; | ||
241 | MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match); | ||
242 | |||
243 | static int brcmstb_pwm_probe(struct platform_device *pdev) | ||
244 | { | ||
245 | struct brcmstb_pwm *p; | ||
246 | struct resource *res; | ||
247 | int ret; | ||
248 | |||
249 | p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); | ||
250 | if (!p) | ||
251 | return -ENOMEM; | ||
252 | |||
253 | spin_lock_init(&p->lock); | ||
254 | |||
255 | p->clk = devm_clk_get(&pdev->dev, NULL); | ||
256 | if (IS_ERR(p->clk)) { | ||
257 | dev_err(&pdev->dev, "failed to obtain clock\n"); | ||
258 | return PTR_ERR(p->clk); | ||
259 | } | ||
260 | |||
261 | ret = clk_prepare_enable(p->clk); | ||
262 | if (ret < 0) { | ||
263 | dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); | ||
264 | return ret; | ||
265 | } | ||
266 | |||
267 | platform_set_drvdata(pdev, p); | ||
268 | |||
269 | p->chip.dev = &pdev->dev; | ||
270 | p->chip.ops = &brcmstb_pwm_ops; | ||
271 | p->chip.base = -1; | ||
272 | p->chip.npwm = 2; | ||
273 | p->chip.can_sleep = true; | ||
274 | |||
275 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
276 | p->base = devm_ioremap_resource(&pdev->dev, res); | ||
277 | if (!p->base) { | ||
278 | ret = -ENOMEM; | ||
279 | goto out_clk; | ||
280 | } | ||
281 | |||
282 | ret = pwmchip_add(&p->chip); | ||
283 | if (ret) { | ||
284 | dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); | ||
285 | goto out_clk; | ||
286 | } | ||
287 | |||
288 | return 0; | ||
289 | |||
290 | out_clk: | ||
291 | clk_disable_unprepare(p->clk); | ||
292 | return ret; | ||
293 | } | ||
294 | |||
295 | static int brcmstb_pwm_remove(struct platform_device *pdev) | ||
296 | { | ||
297 | struct brcmstb_pwm *p = platform_get_drvdata(pdev); | ||
298 | int ret; | ||
299 | |||
300 | ret = pwmchip_remove(&p->chip); | ||
301 | clk_disable_unprepare(p->clk); | ||
302 | |||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | #ifdef CONFIG_PM_SLEEP | ||
307 | static int brcmstb_pwm_suspend(struct device *dev) | ||
308 | { | ||
309 | struct brcmstb_pwm *p = dev_get_drvdata(dev); | ||
310 | |||
311 | clk_disable(p->clk); | ||
312 | |||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | static int brcmstb_pwm_resume(struct device *dev) | ||
317 | { | ||
318 | struct brcmstb_pwm *p = dev_get_drvdata(dev); | ||
319 | |||
320 | clk_enable(p->clk); | ||
321 | |||
322 | return 0; | ||
323 | } | ||
324 | #endif | ||
325 | |||
326 | static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend, | ||
327 | brcmstb_pwm_resume); | ||
328 | |||
329 | static struct platform_driver brcmstb_pwm_driver = { | ||
330 | .probe = brcmstb_pwm_probe, | ||
331 | .remove = brcmstb_pwm_remove, | ||
332 | .driver = { | ||
333 | .name = "pwm-brcmstb", | ||
334 | .of_match_table = brcmstb_pwm_of_match, | ||
335 | .pm = &brcmstb_pwm_pm_ops, | ||
336 | }, | ||
337 | }; | ||
338 | module_platform_driver(brcmstb_pwm_driver); | ||
339 | |||
340 | MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>"); | ||
341 | MODULE_DESCRIPTION("Broadcom STB PWM driver"); | ||
342 | MODULE_ALIAS("platform:pwm-brcmstb"); | ||
343 | MODULE_LICENSE("GPL"); | ||