Commit 34b20e6df6970e36b93f445669ba5ef7a05fe01a
Exists in
ti-lsk-linux-4.1.y
and in
10 other branches
Merge tag 'pwm/for-3.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/th…
…ierry.reding/linux-pwm Pull pwm changes from Thierry Reding: "The set of changes for this merge window contains two new drivers: one for Rockchip SoCs and another for STMicroelectronics STiH4xx SoCs. The remainder of the changes are the usual small cleanups such as removing redundant OOM messages, signalling that a PWM chip's operations can sleep and removing an unneeded dependency" * tag 'pwm/for-3.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: pwm: rockchip: Added to support for RK3288 SoC pwm: rockchip: document RK3288 SoC compatible pwm: sti: Remove PWM period table pwm: sti: Sync between enable/disable calls pwm: sti: Ensure same period values for all channels pwm: sti: Fix PWM prescaler handling pwm: sti: Supply Device Tree binding documentation for ST's PWM IP pwm: sti: Add new driver for ST's PWM IP pwm: imx: set can_sleep flag for imx_pwm pwm: lpss: remove dependency on clk framework pwm: pwm-tipwmss: remove unnecessary OOM messages pwm: rockchip: document device tree bindings pwm: add Rockchip SoC PWM support
Showing 9 changed files Side-by-side Diff
Documentation/devicetree/bindings/pwm/pwm-rockchip.txt
1 | +Rockchip PWM controller | |
2 | + | |
3 | +Required properties: | |
4 | + - compatible: should be "rockchip,<name>-pwm" | |
5 | + "rockchip,rk2928-pwm": found on RK29XX,RK3066 and RK3188 SoCs | |
6 | + "rockchip,rk3288-pwm": found on RK3288 SoC | |
7 | + "rockchip,vop-pwm": found integrated in VOP on RK3288 SoC | |
8 | + - reg: physical base address and length of the controller's registers | |
9 | + - clocks: phandle and clock specifier of the PWM reference clock | |
10 | + - #pwm-cells: should be 2. See pwm.txt in this directory for a | |
11 | + description of the cell format. | |
12 | + | |
13 | +Example: | |
14 | + | |
15 | + pwm0: pwm@20030000 { | |
16 | + compatible = "rockchip,rk2928-pwm"; | |
17 | + reg = <0x20030000 0x10>; | |
18 | + clocks = <&cru PCLK_PWM01>; | |
19 | + #pwm-cells = <2>; | |
20 | + }; |
Documentation/devicetree/bindings/pwm/pwm-st.txt
1 | +STMicroelectronics PWM driver bindings | |
2 | +-------------------------------------- | |
3 | + | |
4 | +Required parameters: | |
5 | +- compatible : "st,pwm" | |
6 | +- #pwm-cells : Number of cells used to specify a PWM. First cell | |
7 | + specifies the per-chip index of the PWM to use and the | |
8 | + second cell is the period in nanoseconds - fixed to 2 | |
9 | + for STiH41x. | |
10 | +- reg : Physical base address and length of the controller's | |
11 | + registers. | |
12 | +- pinctrl-names: Set to "default". | |
13 | +- pinctrl-0: List of phandles pointing to pin configuration nodes | |
14 | + for PWM module. | |
15 | + For Pinctrl properties, please refer to [1]. | |
16 | +- clock-names: Set to "pwm". | |
17 | +- clocks: phandle of the clock used by the PWM module. | |
18 | + For Clk properties, please refer to [2]. | |
19 | + | |
20 | +Optional properties: | |
21 | +- st,pwm-num-chan: Number of available channels. If not passed, the driver | |
22 | + will consider single channel by default. | |
23 | + | |
24 | +[1] Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt | |
25 | +[2] Documentation/devicetree/bindings/clock/clock-bindings.txt | |
26 | + | |
27 | +Example: | |
28 | + | |
29 | +pwm1: pwm@fe510000 { | |
30 | + compatible = "st,pwm"; | |
31 | + reg = <0xfe510000 0x68>; | |
32 | + #pwm-cells = <2>; | |
33 | + pinctrl-names = "default"; | |
34 | + pinctrl-0 = <&pinctrl_pwm1_chan0_default | |
35 | + &pinctrl_pwm1_chan1_default | |
36 | + &pinctrl_pwm1_chan2_default | |
37 | + &pinctrl_pwm1_chan3_default>; | |
38 | + clocks = <&clk_sysin>; | |
39 | + clock-names = "pwm"; | |
40 | + st,pwm-num-chan = <4>; | |
41 | +}; |
drivers/pwm/Kconfig
... | ... | @@ -206,6 +206,13 @@ |
206 | 206 | To compile this driver as a module, choose M here: the module |
207 | 207 | will be called pwm-renesas-tpu. |
208 | 208 | |
209 | +config PWM_ROCKCHIP | |
210 | + tristate "Rockchip PWM support" | |
211 | + depends on ARCH_ROCKCHIP | |
212 | + help | |
213 | + Generic PWM framework driver for the PWM controller found on | |
214 | + Rockchip SoCs. | |
215 | + | |
209 | 216 | config PWM_SAMSUNG |
210 | 217 | tristate "Samsung PWM support" |
211 | 218 | depends on PLAT_SAMSUNG |
... | ... | @@ -225,6 +232,16 @@ |
225 | 232 | |
226 | 233 | To compile this driver as a module, choose M here: the module |
227 | 234 | will be called pwm-spear. |
235 | + | |
236 | +config PWM_STI | |
237 | + tristate "STiH4xx PWM support" | |
238 | + depends on ARCH_STI | |
239 | + depends on OF | |
240 | + help | |
241 | + Generic PWM framework driver for STiH4xx SoCs. | |
242 | + | |
243 | + To compile this driver as a module, choose M here: the module | |
244 | + will be called pwm-sti. | |
228 | 245 | |
229 | 246 | config PWM_TEGRA |
230 | 247 | tristate "NVIDIA Tegra PWM support" |
drivers/pwm/Makefile
... | ... | @@ -18,8 +18,10 @@ |
18 | 18 | obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o |
19 | 19 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o |
20 | 20 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o |
21 | +obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o | |
21 | 22 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
22 | 23 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o |
24 | +obj-$(CONFIG_PWM_STI) += pwm-sti.o | |
23 | 25 | obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o |
24 | 26 | obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o |
25 | 27 | obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o |
drivers/pwm/pwm-imx.c
drivers/pwm/pwm-lpss.c
... | ... | @@ -14,7 +14,6 @@ |
14 | 14 | */ |
15 | 15 | |
16 | 16 | #include <linux/acpi.h> |
17 | -#include <linux/clk.h> | |
18 | 17 | #include <linux/device.h> |
19 | 18 | #include <linux/kernel.h> |
20 | 19 | #include <linux/module.h> |
... | ... | @@ -37,7 +36,6 @@ |
37 | 36 | struct pwm_lpss_chip { |
38 | 37 | struct pwm_chip chip; |
39 | 38 | void __iomem *regs; |
40 | - struct clk *clk; | |
41 | 39 | unsigned long clk_rate; |
42 | 40 | }; |
43 | 41 | |
44 | 42 | |
... | ... | @@ -97,12 +95,7 @@ |
97 | 95 | { |
98 | 96 | struct pwm_lpss_chip *lpwm = to_lpwm(chip); |
99 | 97 | u32 ctrl; |
100 | - int ret; | |
101 | 98 | |
102 | - ret = clk_prepare_enable(lpwm->clk); | |
103 | - if (ret) | |
104 | - return ret; | |
105 | - | |
106 | 99 | ctrl = readl(lpwm->regs + PWM); |
107 | 100 | writel(ctrl | PWM_ENABLE, lpwm->regs + PWM); |
108 | 101 | |
... | ... | @@ -116,8 +109,6 @@ |
116 | 109 | |
117 | 110 | ctrl = readl(lpwm->regs + PWM); |
118 | 111 | writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); |
119 | - | |
120 | - clk_disable_unprepare(lpwm->clk); | |
121 | 112 | } |
122 | 113 | |
123 | 114 | static const struct pwm_ops pwm_lpss_ops = { |
... | ... | @@ -142,17 +133,7 @@ |
142 | 133 | if (IS_ERR(lpwm->regs)) |
143 | 134 | return ERR_CAST(lpwm->regs); |
144 | 135 | |
145 | - if (info) { | |
146 | - lpwm->clk_rate = info->clk_rate; | |
147 | - } else { | |
148 | - lpwm->clk = devm_clk_get(dev, NULL); | |
149 | - if (IS_ERR(lpwm->clk)) { | |
150 | - dev_err(dev, "failed to get PWM clock\n"); | |
151 | - return ERR_CAST(lpwm->clk); | |
152 | - } | |
153 | - lpwm->clk_rate = clk_get_rate(lpwm->clk); | |
154 | - } | |
155 | - | |
136 | + lpwm->clk_rate = info->clk_rate; | |
156 | 137 | lpwm->chip.dev = dev; |
157 | 138 | lpwm->chip.ops = &pwm_lpss_ops; |
158 | 139 | lpwm->chip.base = -1; |
159 | 140 | |
160 | 141 | |
... | ... | @@ -221,12 +202,19 @@ |
221 | 202 | |
222 | 203 | static int pwm_lpss_probe_platform(struct platform_device *pdev) |
223 | 204 | { |
205 | + const struct pwm_lpss_boardinfo *info; | |
206 | + const struct acpi_device_id *id; | |
224 | 207 | struct pwm_lpss_chip *lpwm; |
225 | 208 | struct resource *r; |
226 | 209 | |
210 | + id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); | |
211 | + if (!id) | |
212 | + return -ENODEV; | |
213 | + | |
227 | 214 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
228 | 215 | |
229 | - lpwm = pwm_lpss_probe(&pdev->dev, r, NULL); | |
216 | + info = (struct pwm_lpss_boardinfo *)id->driver_data; | |
217 | + lpwm = pwm_lpss_probe(&pdev->dev, r, info); | |
230 | 218 | if (IS_ERR(lpwm)) |
231 | 219 | return PTR_ERR(lpwm); |
232 | 220 | |
... | ... | @@ -242,7 +230,7 @@ |
242 | 230 | } |
243 | 231 | |
244 | 232 | static const struct acpi_device_id pwm_lpss_acpi_match[] = { |
245 | - { "80860F09", 0 }, | |
233 | + { "80860F09", (unsigned long)&byt_info }, | |
246 | 234 | { }, |
247 | 235 | }; |
248 | 236 | MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); |
drivers/pwm/pwm-rockchip.c
1 | +/* | |
2 | + * PWM driver for Rockchip SoCs | |
3 | + * | |
4 | + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> | |
5 | + * Copyright (C) 2014 ROCKCHIP, Inc. | |
6 | + * | |
7 | + * This program is free software; you can redistribute it and/or | |
8 | + * modify it under the terms of the GNU General Public License | |
9 | + * version 2 as published by the Free Software Foundation. | |
10 | + */ | |
11 | + | |
12 | +#include <linux/clk.h> | |
13 | +#include <linux/io.h> | |
14 | +#include <linux/module.h> | |
15 | +#include <linux/of.h> | |
16 | +#include <linux/of_device.h> | |
17 | +#include <linux/platform_device.h> | |
18 | +#include <linux/pwm.h> | |
19 | +#include <linux/time.h> | |
20 | + | |
21 | +#define PWM_CTRL_TIMER_EN (1 << 0) | |
22 | +#define PWM_CTRL_OUTPUT_EN (1 << 3) | |
23 | + | |
24 | +#define PWM_ENABLE (1 << 0) | |
25 | +#define PWM_CONTINUOUS (1 << 1) | |
26 | +#define PWM_DUTY_POSITIVE (1 << 3) | |
27 | +#define PWM_INACTIVE_NEGATIVE (0 << 4) | |
28 | +#define PWM_OUTPUT_LEFT (0 << 5) | |
29 | +#define PWM_LP_DISABLE (0 << 8) | |
30 | + | |
31 | +struct rockchip_pwm_chip { | |
32 | + struct pwm_chip chip; | |
33 | + struct clk *clk; | |
34 | + const struct rockchip_pwm_data *data; | |
35 | + void __iomem *base; | |
36 | +}; | |
37 | + | |
38 | +struct rockchip_pwm_regs { | |
39 | + unsigned long duty; | |
40 | + unsigned long period; | |
41 | + unsigned long cntr; | |
42 | + unsigned long ctrl; | |
43 | +}; | |
44 | + | |
45 | +struct rockchip_pwm_data { | |
46 | + struct rockchip_pwm_regs regs; | |
47 | + unsigned int prescaler; | |
48 | + | |
49 | + void (*set_enable)(struct pwm_chip *chip, bool enable); | |
50 | +}; | |
51 | + | |
52 | +static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) | |
53 | +{ | |
54 | + return container_of(c, struct rockchip_pwm_chip, chip); | |
55 | +} | |
56 | + | |
57 | +static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) | |
58 | +{ | |
59 | + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); | |
60 | + u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; | |
61 | + u32 val; | |
62 | + | |
63 | + val = readl_relaxed(pc->base + pc->data->regs.ctrl); | |
64 | + | |
65 | + if (enable) | |
66 | + val |= enable_conf; | |
67 | + else | |
68 | + val &= ~enable_conf; | |
69 | + | |
70 | + writel_relaxed(val, pc->base + pc->data->regs.ctrl); | |
71 | +} | |
72 | + | |
73 | +static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) | |
74 | +{ | |
75 | + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); | |
76 | + u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | | |
77 | + PWM_CONTINUOUS | PWM_DUTY_POSITIVE | | |
78 | + PWM_INACTIVE_NEGATIVE; | |
79 | + u32 val; | |
80 | + | |
81 | + val = readl_relaxed(pc->base + pc->data->regs.ctrl); | |
82 | + | |
83 | + if (enable) | |
84 | + val |= enable_conf; | |
85 | + else | |
86 | + val &= ~enable_conf; | |
87 | + | |
88 | + writel_relaxed(val, pc->base + pc->data->regs.ctrl); | |
89 | +} | |
90 | + | |
91 | +static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | |
92 | + int duty_ns, int period_ns) | |
93 | +{ | |
94 | + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); | |
95 | + unsigned long period, duty; | |
96 | + u64 clk_rate, div; | |
97 | + int ret; | |
98 | + | |
99 | + clk_rate = clk_get_rate(pc->clk); | |
100 | + | |
101 | + /* | |
102 | + * Since period and duty cycle registers have a width of 32 | |
103 | + * bits, every possible input period can be obtained using the | |
104 | + * default prescaler value for all practical clock rate values. | |
105 | + */ | |
106 | + div = clk_rate * period_ns; | |
107 | + do_div(div, pc->data->prescaler * NSEC_PER_SEC); | |
108 | + period = div; | |
109 | + | |
110 | + div = clk_rate * duty_ns; | |
111 | + do_div(div, pc->data->prescaler * NSEC_PER_SEC); | |
112 | + duty = div; | |
113 | + | |
114 | + ret = clk_enable(pc->clk); | |
115 | + if (ret) | |
116 | + return ret; | |
117 | + | |
118 | + writel(period, pc->base + pc->data->regs.period); | |
119 | + writel(duty, pc->base + pc->data->regs.duty); | |
120 | + writel(0, pc->base + pc->data->regs.cntr); | |
121 | + | |
122 | + clk_disable(pc->clk); | |
123 | + | |
124 | + return 0; | |
125 | +} | |
126 | + | |
127 | +static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
128 | +{ | |
129 | + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); | |
130 | + int ret; | |
131 | + | |
132 | + ret = clk_enable(pc->clk); | |
133 | + if (ret) | |
134 | + return ret; | |
135 | + | |
136 | + pc->data->set_enable(chip, true); | |
137 | + | |
138 | + return 0; | |
139 | +} | |
140 | + | |
141 | +static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
142 | +{ | |
143 | + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); | |
144 | + | |
145 | + pc->data->set_enable(chip, false); | |
146 | + | |
147 | + clk_disable(pc->clk); | |
148 | +} | |
149 | + | |
150 | +static const struct pwm_ops rockchip_pwm_ops = { | |
151 | + .config = rockchip_pwm_config, | |
152 | + .enable = rockchip_pwm_enable, | |
153 | + .disable = rockchip_pwm_disable, | |
154 | + .owner = THIS_MODULE, | |
155 | +}; | |
156 | + | |
157 | +static const struct rockchip_pwm_data pwm_data_v1 = { | |
158 | + .regs = { | |
159 | + .duty = 0x04, | |
160 | + .period = 0x08, | |
161 | + .cntr = 0x00, | |
162 | + .ctrl = 0x0c, | |
163 | + }, | |
164 | + .prescaler = 2, | |
165 | + .set_enable = rockchip_pwm_set_enable_v1, | |
166 | +}; | |
167 | + | |
168 | +static const struct rockchip_pwm_data pwm_data_v2 = { | |
169 | + .regs = { | |
170 | + .duty = 0x08, | |
171 | + .period = 0x04, | |
172 | + .cntr = 0x00, | |
173 | + .ctrl = 0x0c, | |
174 | + }, | |
175 | + .prescaler = 1, | |
176 | + .set_enable = rockchip_pwm_set_enable_v2, | |
177 | +}; | |
178 | + | |
179 | +static const struct rockchip_pwm_data pwm_data_vop = { | |
180 | + .regs = { | |
181 | + .duty = 0x08, | |
182 | + .period = 0x04, | |
183 | + .cntr = 0x0c, | |
184 | + .ctrl = 0x00, | |
185 | + }, | |
186 | + .prescaler = 1, | |
187 | + .set_enable = rockchip_pwm_set_enable_v2, | |
188 | +}; | |
189 | + | |
190 | +static const struct of_device_id rockchip_pwm_dt_ids[] = { | |
191 | + { .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1}, | |
192 | + { .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2}, | |
193 | + { .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop}, | |
194 | + { /* sentinel */ } | |
195 | +}; | |
196 | +MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids); | |
197 | + | |
198 | +static int rockchip_pwm_probe(struct platform_device *pdev) | |
199 | +{ | |
200 | + const struct of_device_id *id; | |
201 | + struct rockchip_pwm_chip *pc; | |
202 | + struct resource *r; | |
203 | + int ret; | |
204 | + | |
205 | + id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); | |
206 | + if (!id) | |
207 | + return -EINVAL; | |
208 | + | |
209 | + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); | |
210 | + if (!pc) | |
211 | + return -ENOMEM; | |
212 | + | |
213 | + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
214 | + pc->base = devm_ioremap_resource(&pdev->dev, r); | |
215 | + if (IS_ERR(pc->base)) | |
216 | + return PTR_ERR(pc->base); | |
217 | + | |
218 | + pc->clk = devm_clk_get(&pdev->dev, NULL); | |
219 | + if (IS_ERR(pc->clk)) | |
220 | + return PTR_ERR(pc->clk); | |
221 | + | |
222 | + ret = clk_prepare(pc->clk); | |
223 | + if (ret) | |
224 | + return ret; | |
225 | + | |
226 | + platform_set_drvdata(pdev, pc); | |
227 | + | |
228 | + pc->data = id->data; | |
229 | + pc->chip.dev = &pdev->dev; | |
230 | + pc->chip.ops = &rockchip_pwm_ops; | |
231 | + pc->chip.base = -1; | |
232 | + pc->chip.npwm = 1; | |
233 | + | |
234 | + ret = pwmchip_add(&pc->chip); | |
235 | + if (ret < 0) { | |
236 | + clk_unprepare(pc->clk); | |
237 | + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); | |
238 | + } | |
239 | + | |
240 | + return ret; | |
241 | +} | |
242 | + | |
243 | +static int rockchip_pwm_remove(struct platform_device *pdev) | |
244 | +{ | |
245 | + struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); | |
246 | + | |
247 | + clk_unprepare(pc->clk); | |
248 | + | |
249 | + return pwmchip_remove(&pc->chip); | |
250 | +} | |
251 | + | |
252 | +static struct platform_driver rockchip_pwm_driver = { | |
253 | + .driver = { | |
254 | + .name = "rockchip-pwm", | |
255 | + .of_match_table = rockchip_pwm_dt_ids, | |
256 | + }, | |
257 | + .probe = rockchip_pwm_probe, | |
258 | + .remove = rockchip_pwm_remove, | |
259 | +}; | |
260 | +module_platform_driver(rockchip_pwm_driver); | |
261 | + | |
262 | +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); | |
263 | +MODULE_DESCRIPTION("Rockchip SoC PWM driver"); | |
264 | +MODULE_LICENSE("GPL v2"); |
drivers/pwm/pwm-sti.c
1 | +/* | |
2 | + * PWM device driver for ST SoCs. | |
3 | + * Author: Ajit Pal Singh <ajitpal.singh@st.com> | |
4 | + * | |
5 | + * Copyright (C) 2013-2014 STMicroelectronics (R&D) Limited | |
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 | + | |
13 | +#include <linux/clk.h> | |
14 | +#include <linux/math64.h> | |
15 | +#include <linux/mfd/syscon.h> | |
16 | +#include <linux/module.h> | |
17 | +#include <linux/of.h> | |
18 | +#include <linux/platform_device.h> | |
19 | +#include <linux/pwm.h> | |
20 | +#include <linux/regmap.h> | |
21 | +#include <linux/slab.h> | |
22 | +#include <linux/time.h> | |
23 | + | |
24 | +#define STI_DS_REG(ch) (4 * (ch)) /* Channel's Duty Cycle register */ | |
25 | +#define STI_PWMCR 0x50 /* Control/Config register */ | |
26 | +#define STI_INTEN 0x54 /* Interrupt Enable/Disable register */ | |
27 | +#define PWM_PRESCALE_LOW_MASK 0x0f | |
28 | +#define PWM_PRESCALE_HIGH_MASK 0xf0 | |
29 | + | |
30 | +/* Regfield IDs */ | |
31 | +enum { | |
32 | + PWMCLK_PRESCALE_LOW, | |
33 | + PWMCLK_PRESCALE_HIGH, | |
34 | + PWM_EN, | |
35 | + PWM_INT_EN, | |
36 | + | |
37 | + /* Keep last */ | |
38 | + MAX_REGFIELDS | |
39 | +}; | |
40 | + | |
41 | +struct sti_pwm_compat_data { | |
42 | + const struct reg_field *reg_fields; | |
43 | + unsigned int num_chan; | |
44 | + unsigned int max_pwm_cnt; | |
45 | + unsigned int max_prescale; | |
46 | +}; | |
47 | + | |
48 | +struct sti_pwm_chip { | |
49 | + struct device *dev; | |
50 | + struct clk *clk; | |
51 | + unsigned long clk_rate; | |
52 | + struct regmap *regmap; | |
53 | + struct sti_pwm_compat_data *cdata; | |
54 | + struct regmap_field *prescale_low; | |
55 | + struct regmap_field *prescale_high; | |
56 | + struct regmap_field *pwm_en; | |
57 | + struct regmap_field *pwm_int_en; | |
58 | + struct pwm_chip chip; | |
59 | + struct pwm_device *cur; | |
60 | + unsigned int en_count; | |
61 | + struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ | |
62 | + void __iomem *mmio; | |
63 | +}; | |
64 | + | |
65 | +static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = { | |
66 | + [PWMCLK_PRESCALE_LOW] = REG_FIELD(STI_PWMCR, 0, 3), | |
67 | + [PWMCLK_PRESCALE_HIGH] = REG_FIELD(STI_PWMCR, 11, 14), | |
68 | + [PWM_EN] = REG_FIELD(STI_PWMCR, 9, 9), | |
69 | + [PWM_INT_EN] = REG_FIELD(STI_INTEN, 0, 0), | |
70 | +}; | |
71 | + | |
72 | +static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip) | |
73 | +{ | |
74 | + return container_of(chip, struct sti_pwm_chip, chip); | |
75 | +} | |
76 | + | |
77 | +/* | |
78 | + * Calculate the prescaler value corresponding to the period. | |
79 | + */ | |
80 | +static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, | |
81 | + unsigned int *prescale) | |
82 | +{ | |
83 | + struct sti_pwm_compat_data *cdata = pc->cdata; | |
84 | + unsigned long val; | |
85 | + unsigned int ps; | |
86 | + | |
87 | + /* | |
88 | + * prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_count + 1)) - 1 | |
89 | + */ | |
90 | + val = NSEC_PER_SEC / pc->clk_rate; | |
91 | + val *= cdata->max_pwm_cnt + 1; | |
92 | + | |
93 | + if (period % val) { | |
94 | + return -EINVAL; | |
95 | + } else { | |
96 | + ps = period / val - 1; | |
97 | + if (ps > cdata->max_prescale) | |
98 | + return -EINVAL; | |
99 | + } | |
100 | + *prescale = ps; | |
101 | + | |
102 | + return 0; | |
103 | +} | |
104 | + | |
105 | +/* Calculate the number of PWM devices configured with a period. */ | |
106 | +static unsigned int sti_pwm_count_configured(struct pwm_chip *chip) | |
107 | +{ | |
108 | + struct pwm_device *pwm; | |
109 | + unsigned int ncfg = 0; | |
110 | + unsigned int i; | |
111 | + | |
112 | + for (i = 0; i < chip->npwm; i++) { | |
113 | + pwm = &chip->pwms[i]; | |
114 | + if (test_bit(PWMF_REQUESTED, &pwm->flags)) { | |
115 | + if (pwm_get_period(pwm)) | |
116 | + ncfg++; | |
117 | + } | |
118 | + } | |
119 | + | |
120 | + return ncfg; | |
121 | +} | |
122 | + | |
123 | +/* | |
124 | + * For STiH4xx PWM IP, the PWM period is fixed to 256 local clock cycles. | |
125 | + * The only way to change the period (apart from changing the PWM input clock) | |
126 | + * is to change the PWM clock prescaler. | |
127 | + * The prescaler is of 8 bits, so 256 prescaler values and hence | |
128 | + * 256 possible period values are supported (for a particular clock rate). | |
129 | + * The requested period will be applied only if it matches one of these | |
130 | + * 256 values. | |
131 | + */ | |
132 | +static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | |
133 | + int duty_ns, int period_ns) | |
134 | +{ | |
135 | + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); | |
136 | + struct sti_pwm_compat_data *cdata = pc->cdata; | |
137 | + struct pwm_device *cur = pc->cur; | |
138 | + struct device *dev = pc->dev; | |
139 | + unsigned int prescale = 0, pwmvalx; | |
140 | + int ret; | |
141 | + unsigned int ncfg; | |
142 | + bool period_same = false; | |
143 | + | |
144 | + ncfg = sti_pwm_count_configured(chip); | |
145 | + if (ncfg) | |
146 | + period_same = (period_ns == pwm_get_period(cur)); | |
147 | + | |
148 | + /* Allow configuration changes if one of the | |
149 | + * following conditions satisfy. | |
150 | + * 1. No channels have been configured. | |
151 | + * 2. Only one channel has been configured and the new request | |
152 | + * is for the same channel. | |
153 | + * 3. Only one channel has been configured and the new request is | |
154 | + * for a new channel and period of the new channel is same as | |
155 | + * the current configured period. | |
156 | + * 4. More than one channels are configured and period of the new | |
157 | + * requestis the same as the current period. | |
158 | + */ | |
159 | + if (!ncfg || | |
160 | + ((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) || | |
161 | + ((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) || | |
162 | + ((ncfg > 1) && period_same)) { | |
163 | + /* Enable clock before writing to PWM registers. */ | |
164 | + ret = clk_enable(pc->clk); | |
165 | + if (ret) | |
166 | + return ret; | |
167 | + | |
168 | + if (!period_same) { | |
169 | + ret = sti_pwm_get_prescale(pc, period_ns, &prescale); | |
170 | + if (ret) | |
171 | + goto clk_dis; | |
172 | + | |
173 | + ret = | |
174 | + regmap_field_write(pc->prescale_low, | |
175 | + prescale & PWM_PRESCALE_LOW_MASK); | |
176 | + if (ret) | |
177 | + goto clk_dis; | |
178 | + | |
179 | + ret = | |
180 | + regmap_field_write(pc->prescale_high, | |
181 | + (prescale & PWM_PRESCALE_HIGH_MASK) >> 4); | |
182 | + if (ret) | |
183 | + goto clk_dis; | |
184 | + } | |
185 | + | |
186 | + /* | |
187 | + * When PWMVal == 0, PWM pulse = 1 local clock cycle. | |
188 | + * When PWMVal == max_pwm_count, | |
189 | + * PWM pulse = (max_pwm_count + 1) local cycles, | |
190 | + * that is continuous pulse: signal never goes low. | |
191 | + */ | |
192 | + pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns; | |
193 | + | |
194 | + ret = regmap_write(pc->regmap, STI_DS_REG(pwm->hwpwm), pwmvalx); | |
195 | + if (ret) | |
196 | + goto clk_dis; | |
197 | + | |
198 | + ret = regmap_field_write(pc->pwm_int_en, 0); | |
199 | + | |
200 | + pc->cur = pwm; | |
201 | + | |
202 | + dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n", | |
203 | + prescale, period_ns, duty_ns, pwmvalx); | |
204 | + } else { | |
205 | + return -EINVAL; | |
206 | + } | |
207 | + | |
208 | +clk_dis: | |
209 | + clk_disable(pc->clk); | |
210 | + return ret; | |
211 | +} | |
212 | + | |
213 | +static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |
214 | +{ | |
215 | + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); | |
216 | + struct device *dev = pc->dev; | |
217 | + int ret = 0; | |
218 | + | |
219 | + /* | |
220 | + * Since we have a common enable for all PWM channels, | |
221 | + * do not enable if already enabled. | |
222 | + */ | |
223 | + mutex_lock(&pc->sti_pwm_lock); | |
224 | + if (!pc->en_count) { | |
225 | + ret = clk_enable(pc->clk); | |
226 | + if (ret) | |
227 | + goto out; | |
228 | + | |
229 | + ret = regmap_field_write(pc->pwm_en, 1); | |
230 | + if (ret) { | |
231 | + dev_err(dev, "failed to enable PWM device:%d\n", | |
232 | + pwm->hwpwm); | |
233 | + goto out; | |
234 | + } | |
235 | + } | |
236 | + pc->en_count++; | |
237 | +out: | |
238 | + mutex_unlock(&pc->sti_pwm_lock); | |
239 | + return ret; | |
240 | +} | |
241 | + | |
242 | +static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |
243 | +{ | |
244 | + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); | |
245 | + | |
246 | + mutex_lock(&pc->sti_pwm_lock); | |
247 | + if (--pc->en_count) { | |
248 | + mutex_unlock(&pc->sti_pwm_lock); | |
249 | + return; | |
250 | + } | |
251 | + regmap_field_write(pc->pwm_en, 0); | |
252 | + | |
253 | + clk_disable(pc->clk); | |
254 | + mutex_unlock(&pc->sti_pwm_lock); | |
255 | +} | |
256 | + | |
257 | +static const struct pwm_ops sti_pwm_ops = { | |
258 | + .config = sti_pwm_config, | |
259 | + .enable = sti_pwm_enable, | |
260 | + .disable = sti_pwm_disable, | |
261 | + .owner = THIS_MODULE, | |
262 | +}; | |
263 | + | |
264 | +static int sti_pwm_probe_dt(struct sti_pwm_chip *pc) | |
265 | +{ | |
266 | + struct device *dev = pc->dev; | |
267 | + const struct reg_field *reg_fields; | |
268 | + struct device_node *np = dev->of_node; | |
269 | + struct sti_pwm_compat_data *cdata = pc->cdata; | |
270 | + u32 num_chan; | |
271 | + | |
272 | + of_property_read_u32(np, "st,pwm-num-chan", &num_chan); | |
273 | + if (num_chan) | |
274 | + cdata->num_chan = num_chan; | |
275 | + | |
276 | + reg_fields = cdata->reg_fields; | |
277 | + | |
278 | + pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap, | |
279 | + reg_fields[PWMCLK_PRESCALE_LOW]); | |
280 | + if (IS_ERR(pc->prescale_low)) | |
281 | + return PTR_ERR(pc->prescale_low); | |
282 | + | |
283 | + pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap, | |
284 | + reg_fields[PWMCLK_PRESCALE_HIGH]); | |
285 | + if (IS_ERR(pc->prescale_high)) | |
286 | + return PTR_ERR(pc->prescale_high); | |
287 | + | |
288 | + pc->pwm_en = devm_regmap_field_alloc(dev, pc->regmap, | |
289 | + reg_fields[PWM_EN]); | |
290 | + if (IS_ERR(pc->pwm_en)) | |
291 | + return PTR_ERR(pc->pwm_en); | |
292 | + | |
293 | + pc->pwm_int_en = devm_regmap_field_alloc(dev, pc->regmap, | |
294 | + reg_fields[PWM_INT_EN]); | |
295 | + if (IS_ERR(pc->pwm_int_en)) | |
296 | + return PTR_ERR(pc->pwm_int_en); | |
297 | + | |
298 | + return 0; | |
299 | +} | |
300 | + | |
301 | +static const struct regmap_config sti_pwm_regmap_config = { | |
302 | + .reg_bits = 32, | |
303 | + .val_bits = 32, | |
304 | + .reg_stride = 4, | |
305 | +}; | |
306 | + | |
307 | +static int sti_pwm_probe(struct platform_device *pdev) | |
308 | +{ | |
309 | + struct device *dev = &pdev->dev; | |
310 | + struct sti_pwm_compat_data *cdata; | |
311 | + struct sti_pwm_chip *pc; | |
312 | + struct resource *res; | |
313 | + int ret; | |
314 | + | |
315 | + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); | |
316 | + if (!pc) | |
317 | + return -ENOMEM; | |
318 | + | |
319 | + cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL); | |
320 | + if (!cdata) | |
321 | + return -ENOMEM; | |
322 | + | |
323 | + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
324 | + | |
325 | + pc->mmio = devm_ioremap_resource(dev, res); | |
326 | + if (IS_ERR(pc->mmio)) | |
327 | + return PTR_ERR(pc->mmio); | |
328 | + | |
329 | + pc->regmap = devm_regmap_init_mmio(dev, pc->mmio, | |
330 | + &sti_pwm_regmap_config); | |
331 | + if (IS_ERR(pc->regmap)) | |
332 | + return PTR_ERR(pc->regmap); | |
333 | + | |
334 | + /* | |
335 | + * Setup PWM data with default values: some values could be replaced | |
336 | + * with specific ones provided from Device Tree. | |
337 | + */ | |
338 | + cdata->reg_fields = &sti_pwm_regfields[0]; | |
339 | + cdata->max_prescale = 0xff; | |
340 | + cdata->max_pwm_cnt = 255; | |
341 | + cdata->num_chan = 1; | |
342 | + | |
343 | + pc->cdata = cdata; | |
344 | + pc->dev = dev; | |
345 | + pc->en_count = 0; | |
346 | + mutex_init(&pc->sti_pwm_lock); | |
347 | + | |
348 | + ret = sti_pwm_probe_dt(pc); | |
349 | + if (ret) | |
350 | + return ret; | |
351 | + | |
352 | + pc->clk = of_clk_get_by_name(dev->of_node, "pwm"); | |
353 | + if (IS_ERR(pc->clk)) { | |
354 | + dev_err(dev, "failed to get PWM clock\n"); | |
355 | + return PTR_ERR(pc->clk); | |
356 | + } | |
357 | + | |
358 | + pc->clk_rate = clk_get_rate(pc->clk); | |
359 | + if (!pc->clk_rate) { | |
360 | + dev_err(dev, "failed to get clock rate\n"); | |
361 | + return -EINVAL; | |
362 | + } | |
363 | + | |
364 | + ret = clk_prepare(pc->clk); | |
365 | + if (ret) { | |
366 | + dev_err(dev, "failed to prepare clock\n"); | |
367 | + return ret; | |
368 | + } | |
369 | + | |
370 | + pc->chip.dev = dev; | |
371 | + pc->chip.ops = &sti_pwm_ops; | |
372 | + pc->chip.base = -1; | |
373 | + pc->chip.npwm = pc->cdata->num_chan; | |
374 | + pc->chip.can_sleep = true; | |
375 | + | |
376 | + ret = pwmchip_add(&pc->chip); | |
377 | + if (ret < 0) { | |
378 | + clk_unprepare(pc->clk); | |
379 | + return ret; | |
380 | + } | |
381 | + | |
382 | + platform_set_drvdata(pdev, pc); | |
383 | + | |
384 | + return 0; | |
385 | +} | |
386 | + | |
387 | +static int sti_pwm_remove(struct platform_device *pdev) | |
388 | +{ | |
389 | + struct sti_pwm_chip *pc = platform_get_drvdata(pdev); | |
390 | + unsigned int i; | |
391 | + | |
392 | + for (i = 0; i < pc->cdata->num_chan; i++) | |
393 | + pwm_disable(&pc->chip.pwms[i]); | |
394 | + | |
395 | + clk_unprepare(pc->clk); | |
396 | + | |
397 | + return pwmchip_remove(&pc->chip); | |
398 | +} | |
399 | + | |
400 | +static const struct of_device_id sti_pwm_of_match[] = { | |
401 | + { .compatible = "st,sti-pwm", }, | |
402 | + { /* sentinel */ } | |
403 | +}; | |
404 | +MODULE_DEVICE_TABLE(of, sti_pwm_of_match); | |
405 | + | |
406 | +static struct platform_driver sti_pwm_driver = { | |
407 | + .driver = { | |
408 | + .name = "sti-pwm", | |
409 | + .of_match_table = sti_pwm_of_match, | |
410 | + }, | |
411 | + .probe = sti_pwm_probe, | |
412 | + .remove = sti_pwm_remove, | |
413 | +}; | |
414 | +module_platform_driver(sti_pwm_driver); | |
415 | + | |
416 | +MODULE_AUTHOR("Ajit Pal Singh <ajitpal.singh@st.com>"); | |
417 | +MODULE_DESCRIPTION("STMicroelectronics ST PWM driver"); | |
418 | +MODULE_LICENSE("GPL"); |
drivers/pwm/pwm-tipwmss.c
... | ... | @@ -62,10 +62,8 @@ |
62 | 62 | struct device_node *node = pdev->dev.of_node; |
63 | 63 | |
64 | 64 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); |
65 | - if (!info) { | |
66 | - dev_err(&pdev->dev, "failed to allocate memory\n"); | |
65 | + if (!info) | |
67 | 66 | return -ENOMEM; |
68 | - } | |
69 | 67 | |
70 | 68 | mutex_init(&info->pwmss_lock); |
71 | 69 |