diff options
author | Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> | 2015-09-30 04:47:53 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2015-10-06 03:40:46 -0400 |
commit | ed6c1476bf7f16d5f203ce2893ed04284c3aebd3 (patch) | |
tree | b971a1ef4ff54a53401faa06f5809a3385dcde9d /drivers/pwm | |
parent | 7260d25145a1485767efc5d4341405bcb5703eab (diff) |
pwm: Add support for R-Car PWM Timer
This patch adds support for R-Car SoCs PWM Timer. The PWM timer of
R-Car H2 has 7 channels. So, we can use the channels if we describe
device tree nodes.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Reviewed-by: Simon Horman <horms+renesas@verge.net.au>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-rcar.c | 274 |
3 files changed, 286 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index afe6573b0253..de18bfe146b4 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -277,6 +277,17 @@ config PWM_PXA | |||
277 | To compile this driver as a module, choose M here: the module | 277 | To compile this driver as a module, choose M here: the module |
278 | will be called pwm-pxa. | 278 | will be called pwm-pxa. |
279 | 279 | ||
280 | config PWM_RCAR | ||
281 | tristate "Renesas R-Car PWM support" | ||
282 | depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST | ||
283 | depends on HAS_IOMEM | ||
284 | help | ||
285 | This driver exposes the PWM Timer controller found in Renesas | ||
286 | R-Car chips through the PWM API. | ||
287 | |||
288 | To compile this driver as a module, choose M here: the module | ||
289 | will be called pwm-rcar. | ||
290 | |||
280 | config PWM_RENESAS_TPU | 291 | config PWM_RENESAS_TPU |
281 | tristate "Renesas TPU PWM support" | 292 | tristate "Renesas TPU PWM support" |
282 | depends on ARCH_SHMOBILE || COMPILE_TEST | 293 | depends on ARCH_SHMOBILE || COMPILE_TEST |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 601833d82da5..fc61acad9787 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -25,6 +25,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o | |||
25 | obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o | 25 | obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o |
26 | obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o | 26 | obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o |
27 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o | 27 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o |
28 | obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o | ||
28 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o | 29 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o |
29 | obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o | 30 | obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o |
30 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | 31 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c new file mode 100644 index 000000000000..6e99a63ffa29 --- /dev/null +++ b/drivers/pwm/pwm-rcar.c | |||
@@ -0,0 +1,274 @@ | |||
1 | /* | ||
2 | * R-Car PWM Timer driver | ||
3 | * | ||
4 | * Copyright (C) 2015 Renesas Electronics Corporation | ||
5 | * | ||
6 | * This is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of version 2 of the GNU General Public License as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/clk.h> | ||
12 | #include <linux/err.h> | ||
13 | #include <linux/io.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/pm_runtime.h> | ||
18 | #include <linux/pwm.h> | ||
19 | #include <linux/slab.h> | ||
20 | |||
21 | #define RCAR_PWM_MAX_DIVISION 24 | ||
22 | #define RCAR_PWM_MAX_CYCLE 1023 | ||
23 | |||
24 | #define RCAR_PWMCR 0x00 | ||
25 | #define RCAR_PWMCR_CC0_MASK 0x000f0000 | ||
26 | #define RCAR_PWMCR_CC0_SHIFT 16 | ||
27 | #define RCAR_PWMCR_CCMD BIT(15) | ||
28 | #define RCAR_PWMCR_SYNC BIT(11) | ||
29 | #define RCAR_PWMCR_SS0 BIT(4) | ||
30 | #define RCAR_PWMCR_EN0 BIT(0) | ||
31 | |||
32 | #define RCAR_PWMCNT 0x04 | ||
33 | #define RCAR_PWMCNT_CYC0_MASK 0x03ff0000 | ||
34 | #define RCAR_PWMCNT_CYC0_SHIFT 16 | ||
35 | #define RCAR_PWMCNT_PH0_MASK 0x000003ff | ||
36 | #define RCAR_PWMCNT_PH0_SHIFT 0 | ||
37 | |||
38 | struct rcar_pwm_chip { | ||
39 | struct pwm_chip chip; | ||
40 | void __iomem *base; | ||
41 | struct clk *clk; | ||
42 | }; | ||
43 | |||
44 | static inline struct rcar_pwm_chip *to_rcar_pwm_chip(struct pwm_chip *chip) | ||
45 | { | ||
46 | return container_of(chip, struct rcar_pwm_chip, chip); | ||
47 | } | ||
48 | |||
49 | static void rcar_pwm_write(struct rcar_pwm_chip *rp, u32 data, | ||
50 | unsigned int offset) | ||
51 | { | ||
52 | writel(data, rp->base + offset); | ||
53 | } | ||
54 | |||
55 | static u32 rcar_pwm_read(struct rcar_pwm_chip *rp, unsigned int offset) | ||
56 | { | ||
57 | return readl(rp->base + offset); | ||
58 | } | ||
59 | |||
60 | static void rcar_pwm_update(struct rcar_pwm_chip *rp, u32 mask, u32 data, | ||
61 | unsigned int offset) | ||
62 | { | ||
63 | u32 value; | ||
64 | |||
65 | value = rcar_pwm_read(rp, offset); | ||
66 | value &= ~mask; | ||
67 | value |= data & mask; | ||
68 | rcar_pwm_write(rp, value, offset); | ||
69 | } | ||
70 | |||
71 | static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns) | ||
72 | { | ||
73 | unsigned long clk_rate = clk_get_rate(rp->clk); | ||
74 | unsigned long long max; /* max cycle / nanoseconds */ | ||
75 | unsigned int div; | ||
76 | |||
77 | if (clk_rate == 0) | ||
78 | return -EINVAL; | ||
79 | |||
80 | for (div = 0; div <= RCAR_PWM_MAX_DIVISION; div++) { | ||
81 | max = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE * | ||
82 | (1 << div); | ||
83 | do_div(max, clk_rate); | ||
84 | if (period_ns < max) | ||
85 | break; | ||
86 | } | ||
87 | |||
88 | return (div <= RCAR_PWM_MAX_DIVISION) ? div : -ERANGE; | ||
89 | } | ||
90 | |||
91 | static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp, | ||
92 | unsigned int div) | ||
93 | { | ||
94 | u32 value; | ||
95 | |||
96 | value = rcar_pwm_read(rp, RCAR_PWMCR); | ||
97 | value &= ~(RCAR_PWMCR_CCMD | RCAR_PWMCR_CC0_MASK); | ||
98 | |||
99 | if (div & 1) | ||
100 | value |= RCAR_PWMCR_CCMD; | ||
101 | |||
102 | div >>= 1; | ||
103 | |||
104 | value |= div << RCAR_PWMCR_CC0_SHIFT; | ||
105 | rcar_pwm_write(rp, value, RCAR_PWMCR); | ||
106 | } | ||
107 | |||
108 | static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns, | ||
109 | int period_ns) | ||
110 | { | ||
111 | unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */ | ||
112 | unsigned long clk_rate = clk_get_rate(rp->clk); | ||
113 | u32 cyc, ph; | ||
114 | |||
115 | one_cycle = (unsigned long long)NSEC_PER_SEC * 100ULL * (1 << div); | ||
116 | do_div(one_cycle, clk_rate); | ||
117 | |||
118 | tmp = period_ns * 100ULL; | ||
119 | do_div(tmp, one_cycle); | ||
120 | cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK; | ||
121 | |||
122 | tmp = duty_ns * 100ULL; | ||
123 | do_div(tmp, one_cycle); | ||
124 | ph = tmp & RCAR_PWMCNT_PH0_MASK; | ||
125 | |||
126 | /* Avoid prohibited setting */ | ||
127 | if (cyc == 0 || ph == 0) | ||
128 | return -EINVAL; | ||
129 | |||
130 | rcar_pwm_write(rp, cyc | ph, RCAR_PWMCNT); | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
136 | { | ||
137 | struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); | ||
138 | |||
139 | return clk_prepare_enable(rp->clk); | ||
140 | } | ||
141 | |||
142 | static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
143 | { | ||
144 | struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); | ||
145 | |||
146 | clk_disable_unprepare(rp->clk); | ||
147 | } | ||
148 | |||
149 | static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
150 | int duty_ns, int period_ns) | ||
151 | { | ||
152 | struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); | ||
153 | int div, ret; | ||
154 | |||
155 | div = rcar_pwm_get_clock_division(rp, period_ns); | ||
156 | if (div < 0) | ||
157 | return div; | ||
158 | |||
159 | /* Let the core driver set pwm->period if disabled and duty_ns == 0 */ | ||
160 | if (!test_bit(PWMF_ENABLED, &pwm->flags) && !duty_ns) | ||
161 | return 0; | ||
162 | |||
163 | rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR); | ||
164 | |||
165 | ret = rcar_pwm_set_counter(rp, div, duty_ns, period_ns); | ||
166 | if (!ret) | ||
167 | rcar_pwm_set_clock_control(rp, div); | ||
168 | |||
169 | /* The SYNC should be set to 0 even if rcar_pwm_set_counter failed */ | ||
170 | rcar_pwm_update(rp, RCAR_PWMCR_SYNC, 0, RCAR_PWMCR); | ||
171 | |||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | static int rcar_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
176 | { | ||
177 | struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); | ||
178 | u32 value; | ||
179 | |||
180 | /* Don't enable the PWM device if CYC0 or PH0 is 0 */ | ||
181 | value = rcar_pwm_read(rp, RCAR_PWMCNT); | ||
182 | if ((value & RCAR_PWMCNT_CYC0_MASK) == 0 || | ||
183 | (value & RCAR_PWMCNT_PH0_MASK) == 0) | ||
184 | return -EINVAL; | ||
185 | |||
186 | rcar_pwm_update(rp, RCAR_PWMCR_EN0, RCAR_PWMCR_EN0, RCAR_PWMCR); | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static void rcar_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
192 | { | ||
193 | struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); | ||
194 | |||
195 | rcar_pwm_update(rp, RCAR_PWMCR_EN0, 0, RCAR_PWMCR); | ||
196 | } | ||
197 | |||
198 | static const struct pwm_ops rcar_pwm_ops = { | ||
199 | .request = rcar_pwm_request, | ||
200 | .free = rcar_pwm_free, | ||
201 | .config = rcar_pwm_config, | ||
202 | .enable = rcar_pwm_enable, | ||
203 | .disable = rcar_pwm_disable, | ||
204 | .owner = THIS_MODULE, | ||
205 | }; | ||
206 | |||
207 | static int rcar_pwm_probe(struct platform_device *pdev) | ||
208 | { | ||
209 | struct rcar_pwm_chip *rcar_pwm; | ||
210 | struct resource *res; | ||
211 | int ret; | ||
212 | |||
213 | rcar_pwm = devm_kzalloc(&pdev->dev, sizeof(*rcar_pwm), GFP_KERNEL); | ||
214 | if (rcar_pwm == NULL) | ||
215 | return -ENOMEM; | ||
216 | |||
217 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
218 | rcar_pwm->base = devm_ioremap_resource(&pdev->dev, res); | ||
219 | if (IS_ERR(rcar_pwm->base)) | ||
220 | return PTR_ERR(rcar_pwm->base); | ||
221 | |||
222 | rcar_pwm->clk = devm_clk_get(&pdev->dev, NULL); | ||
223 | if (IS_ERR(rcar_pwm->clk)) { | ||
224 | dev_err(&pdev->dev, "cannot get clock\n"); | ||
225 | return PTR_ERR(rcar_pwm->clk); | ||
226 | } | ||
227 | |||
228 | platform_set_drvdata(pdev, rcar_pwm); | ||
229 | |||
230 | rcar_pwm->chip.dev = &pdev->dev; | ||
231 | rcar_pwm->chip.ops = &rcar_pwm_ops; | ||
232 | rcar_pwm->chip.base = -1; | ||
233 | rcar_pwm->chip.npwm = 1; | ||
234 | |||
235 | ret = pwmchip_add(&rcar_pwm->chip); | ||
236 | if (ret < 0) { | ||
237 | dev_err(&pdev->dev, "failed to register PWM chip: %d\n", ret); | ||
238 | return ret; | ||
239 | } | ||
240 | |||
241 | pm_runtime_enable(&pdev->dev); | ||
242 | |||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static int rcar_pwm_remove(struct platform_device *pdev) | ||
247 | { | ||
248 | struct rcar_pwm_chip *rcar_pwm = platform_get_drvdata(pdev); | ||
249 | |||
250 | pm_runtime_disable(&pdev->dev); | ||
251 | |||
252 | return pwmchip_remove(&rcar_pwm->chip); | ||
253 | } | ||
254 | |||
255 | static const struct of_device_id rcar_pwm_of_table[] = { | ||
256 | { .compatible = "renesas,pwm-rcar", }, | ||
257 | { }, | ||
258 | }; | ||
259 | MODULE_DEVICE_TABLE(of, rcar_pwm_of_table); | ||
260 | |||
261 | static struct platform_driver rcar_pwm_driver = { | ||
262 | .probe = rcar_pwm_probe, | ||
263 | .remove = rcar_pwm_remove, | ||
264 | .driver = { | ||
265 | .name = "pwm-rcar", | ||
266 | .of_match_table = of_match_ptr(rcar_pwm_of_table), | ||
267 | } | ||
268 | }; | ||
269 | module_platform_driver(rcar_pwm_driver); | ||
270 | |||
271 | MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); | ||
272 | MODULE_DESCRIPTION("Renesas PWM Timer Driver"); | ||
273 | MODULE_LICENSE("GPL v2"); | ||
274 | MODULE_ALIAS("platform:pwm-rcar"); | ||