diff options
author | Baolin Wang <baolin.wang@linaro.org> | 2019-08-14 08:46:11 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2019-09-20 19:15:48 -0400 |
commit | 8aae4b02e8a6dd138afd2b54d9984d17685b0364 (patch) | |
tree | 26a9af239aa7a3d014f3111edc89a72d8aacef8d | |
parent | bdaadd5948179f07ca8b508a62a8f8b00ece51ed (diff) |
pwm: sprd: Add Spreadtrum PWM support
This patch adds the Spreadtrum PWM support, which provides maximum 4
channels.
Signed-off-by: Neo Hou <neo.hou@unisoc.com>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | drivers/pwm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-sprd.c | 309 |
3 files changed, 321 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a7e57516959e..31dfc88ef362 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -423,6 +423,17 @@ config PWM_SPEAR | |||
423 | To compile this driver as a module, choose M here: the module | 423 | To compile this driver as a module, choose M here: the module |
424 | will be called pwm-spear. | 424 | will be called pwm-spear. |
425 | 425 | ||
426 | config PWM_SPRD | ||
427 | tristate "Spreadtrum PWM support" | ||
428 | depends on ARCH_SPRD || COMPILE_TEST | ||
429 | depends on HAS_IOMEM | ||
430 | help | ||
431 | Generic PWM framework driver for the PWM controller on | ||
432 | Spreadtrum SoCs. | ||
433 | |||
434 | To compile this driver as a module, choose M here: the module | ||
435 | will be called pwm-sprd. | ||
436 | |||
426 | config PWM_STI | 437 | config PWM_STI |
427 | tristate "STiH4xx PWM support" | 438 | tristate "STiH4xx PWM support" |
428 | depends on ARCH_STI | 439 | depends on ARCH_STI |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 76b555b51887..26326adf71d7 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -41,6 +41,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o | |||
41 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | 41 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
42 | obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o | 42 | obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o |
43 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o | 43 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o |
44 | obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o | ||
44 | obj-$(CONFIG_PWM_STI) += pwm-sti.o | 45 | obj-$(CONFIG_PWM_STI) += pwm-sti.o |
45 | obj-$(CONFIG_PWM_STM32) += pwm-stm32.o | 46 | obj-$(CONFIG_PWM_STM32) += pwm-stm32.o |
46 | obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o | 47 | obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o |
diff --git a/drivers/pwm/pwm-sprd.c b/drivers/pwm/pwm-sprd.c new file mode 100644 index 000000000000..68c2d9f0411b --- /dev/null +++ b/drivers/pwm/pwm-sprd.c | |||
@@ -0,0 +1,309 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (C) 2019 Spreadtrum Communications Inc. | ||
4 | */ | ||
5 | |||
6 | #include <linux/clk.h> | ||
7 | #include <linux/err.h> | ||
8 | #include <linux/io.h> | ||
9 | #include <linux/math64.h> | ||
10 | #include <linux/module.h> | ||
11 | #include <linux/platform_device.h> | ||
12 | #include <linux/pwm.h> | ||
13 | |||
14 | #define SPRD_PWM_PRESCALE 0x0 | ||
15 | #define SPRD_PWM_MOD 0x4 | ||
16 | #define SPRD_PWM_DUTY 0x8 | ||
17 | #define SPRD_PWM_ENABLE 0x18 | ||
18 | |||
19 | #define SPRD_PWM_MOD_MAX GENMASK(7, 0) | ||
20 | #define SPRD_PWM_DUTY_MSK GENMASK(15, 0) | ||
21 | #define SPRD_PWM_PRESCALE_MSK GENMASK(7, 0) | ||
22 | #define SPRD_PWM_ENABLE_BIT BIT(0) | ||
23 | |||
24 | #define SPRD_PWM_CHN_NUM 4 | ||
25 | #define SPRD_PWM_REGS_SHIFT 5 | ||
26 | #define SPRD_PWM_CHN_CLKS_NUM 2 | ||
27 | #define SPRD_PWM_CHN_OUTPUT_CLK 1 | ||
28 | |||
29 | struct sprd_pwm_chn { | ||
30 | struct clk_bulk_data clks[SPRD_PWM_CHN_CLKS_NUM]; | ||
31 | u32 clk_rate; | ||
32 | }; | ||
33 | |||
34 | struct sprd_pwm_chip { | ||
35 | void __iomem *base; | ||
36 | struct device *dev; | ||
37 | struct pwm_chip chip; | ||
38 | int num_pwms; | ||
39 | struct sprd_pwm_chn chn[SPRD_PWM_CHN_NUM]; | ||
40 | }; | ||
41 | |||
42 | /* | ||
43 | * The list of clocks required by PWM channels, and each channel has 2 clocks: | ||
44 | * enable clock and pwm clock. | ||
45 | */ | ||
46 | static const char * const sprd_pwm_clks[] = { | ||
47 | "enable0", "pwm0", | ||
48 | "enable1", "pwm1", | ||
49 | "enable2", "pwm2", | ||
50 | "enable3", "pwm3", | ||
51 | }; | ||
52 | |||
53 | static u32 sprd_pwm_read(struct sprd_pwm_chip *spc, u32 hwid, u32 reg) | ||
54 | { | ||
55 | u32 offset = reg + (hwid << SPRD_PWM_REGS_SHIFT); | ||
56 | |||
57 | return readl_relaxed(spc->base + offset); | ||
58 | } | ||
59 | |||
60 | static void sprd_pwm_write(struct sprd_pwm_chip *spc, u32 hwid, | ||
61 | u32 reg, u32 val) | ||
62 | { | ||
63 | u32 offset = reg + (hwid << SPRD_PWM_REGS_SHIFT); | ||
64 | |||
65 | writel_relaxed(val, spc->base + offset); | ||
66 | } | ||
67 | |||
68 | static void sprd_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, | ||
69 | struct pwm_state *state) | ||
70 | { | ||
71 | struct sprd_pwm_chip *spc = | ||
72 | container_of(chip, struct sprd_pwm_chip, chip); | ||
73 | struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; | ||
74 | u32 val, duty, prescale; | ||
75 | u64 tmp; | ||
76 | int ret; | ||
77 | |||
78 | /* | ||
79 | * The clocks to PWM channel has to be enabled first before | ||
80 | * reading to the registers. | ||
81 | */ | ||
82 | ret = clk_bulk_prepare_enable(SPRD_PWM_CHN_CLKS_NUM, chn->clks); | ||
83 | if (ret) { | ||
84 | dev_err(spc->dev, "failed to enable pwm%u clocks\n", | ||
85 | pwm->hwpwm); | ||
86 | return; | ||
87 | } | ||
88 | |||
89 | val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_ENABLE); | ||
90 | if (val & SPRD_PWM_ENABLE_BIT) | ||
91 | state->enabled = true; | ||
92 | else | ||
93 | state->enabled = false; | ||
94 | |||
95 | /* | ||
96 | * The hardware provides a counter that is feed by the source clock. | ||
97 | * The period length is (PRESCALE + 1) * MOD counter steps. | ||
98 | * The duty cycle length is (PRESCALE + 1) * DUTY counter steps. | ||
99 | * Thus the period_ns and duty_ns calculation formula should be: | ||
100 | * period_ns = NSEC_PER_SEC * (prescale + 1) * mod / clk_rate | ||
101 | * duty_ns = NSEC_PER_SEC * (prescale + 1) * duty / clk_rate | ||
102 | */ | ||
103 | val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_PRESCALE); | ||
104 | prescale = val & SPRD_PWM_PRESCALE_MSK; | ||
105 | tmp = (prescale + 1) * NSEC_PER_SEC * SPRD_PWM_MOD_MAX; | ||
106 | state->period = DIV_ROUND_CLOSEST_ULL(tmp, chn->clk_rate); | ||
107 | |||
108 | val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_DUTY); | ||
109 | duty = val & SPRD_PWM_DUTY_MSK; | ||
110 | tmp = (prescale + 1) * NSEC_PER_SEC * duty; | ||
111 | state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, chn->clk_rate); | ||
112 | |||
113 | /* Disable PWM clocks if the PWM channel is not in enable state. */ | ||
114 | if (!state->enabled) | ||
115 | clk_bulk_disable_unprepare(SPRD_PWM_CHN_CLKS_NUM, chn->clks); | ||
116 | } | ||
117 | |||
118 | static int sprd_pwm_config(struct sprd_pwm_chip *spc, struct pwm_device *pwm, | ||
119 | int duty_ns, int period_ns) | ||
120 | { | ||
121 | struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; | ||
122 | u32 prescale, duty; | ||
123 | u64 tmp; | ||
124 | |||
125 | /* | ||
126 | * The hardware provides a counter that is feed by the source clock. | ||
127 | * The period length is (PRESCALE + 1) * MOD counter steps. | ||
128 | * The duty cycle length is (PRESCALE + 1) * DUTY counter steps. | ||
129 | * | ||
130 | * To keep the maths simple we're always using MOD = SPRD_PWM_MOD_MAX. | ||
131 | * The value for PRESCALE is selected such that the resulting period | ||
132 | * gets the maximal length not bigger than the requested one with the | ||
133 | * given settings (MOD = SPRD_PWM_MOD_MAX and input clock). | ||
134 | */ | ||
135 | duty = duty_ns * SPRD_PWM_MOD_MAX / period_ns; | ||
136 | |||
137 | tmp = (u64)chn->clk_rate * period_ns; | ||
138 | do_div(tmp, NSEC_PER_SEC); | ||
139 | prescale = DIV_ROUND_CLOSEST_ULL(tmp, SPRD_PWM_MOD_MAX) - 1; | ||
140 | if (prescale > SPRD_PWM_PRESCALE_MSK) | ||
141 | prescale = SPRD_PWM_PRESCALE_MSK; | ||
142 | |||
143 | /* | ||
144 | * Note: Writing DUTY triggers the hardware to actually apply the | ||
145 | * values written to MOD and DUTY to the output, so must keep writing | ||
146 | * DUTY last. | ||
147 | * | ||
148 | * The hardware can ensures that current running period is completed | ||
149 | * before changing a new configuration to avoid mixed settings. | ||
150 | */ | ||
151 | sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_PRESCALE, prescale); | ||
152 | sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_MOD, SPRD_PWM_MOD_MAX); | ||
153 | sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_DUTY, duty); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static int sprd_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, | ||
159 | struct pwm_state *state) | ||
160 | { | ||
161 | struct sprd_pwm_chip *spc = | ||
162 | container_of(chip, struct sprd_pwm_chip, chip); | ||
163 | struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; | ||
164 | struct pwm_state *cstate = &pwm->state; | ||
165 | int ret; | ||
166 | |||
167 | if (state->enabled) { | ||
168 | if (!cstate->enabled) { | ||
169 | /* | ||
170 | * The clocks to PWM channel has to be enabled first | ||
171 | * before writing to the registers. | ||
172 | */ | ||
173 | ret = clk_bulk_prepare_enable(SPRD_PWM_CHN_CLKS_NUM, | ||
174 | chn->clks); | ||
175 | if (ret) { | ||
176 | dev_err(spc->dev, | ||
177 | "failed to enable pwm%u clocks\n", | ||
178 | pwm->hwpwm); | ||
179 | return ret; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | if (state->period != cstate->period || | ||
184 | state->duty_cycle != cstate->duty_cycle) { | ||
185 | ret = sprd_pwm_config(spc, pwm, state->duty_cycle, | ||
186 | state->period); | ||
187 | if (ret) | ||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_ENABLE, 1); | ||
192 | } else if (cstate->enabled) { | ||
193 | /* | ||
194 | * Note: After setting SPRD_PWM_ENABLE to zero, the controller | ||
195 | * will not wait for current period to be completed, instead it | ||
196 | * will stop the PWM channel immediately. | ||
197 | */ | ||
198 | sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_ENABLE, 0); | ||
199 | |||
200 | clk_bulk_disable_unprepare(SPRD_PWM_CHN_CLKS_NUM, chn->clks); | ||
201 | } | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | static const struct pwm_ops sprd_pwm_ops = { | ||
207 | .apply = sprd_pwm_apply, | ||
208 | .get_state = sprd_pwm_get_state, | ||
209 | .owner = THIS_MODULE, | ||
210 | }; | ||
211 | |||
212 | static int sprd_pwm_clk_init(struct sprd_pwm_chip *spc) | ||
213 | { | ||
214 | struct clk *clk_pwm; | ||
215 | int ret, i; | ||
216 | |||
217 | for (i = 0; i < SPRD_PWM_CHN_NUM; i++) { | ||
218 | struct sprd_pwm_chn *chn = &spc->chn[i]; | ||
219 | int j; | ||
220 | |||
221 | for (j = 0; j < SPRD_PWM_CHN_CLKS_NUM; ++j) | ||
222 | chn->clks[j].id = | ||
223 | sprd_pwm_clks[i * SPRD_PWM_CHN_CLKS_NUM + j]; | ||
224 | |||
225 | ret = devm_clk_bulk_get(spc->dev, SPRD_PWM_CHN_CLKS_NUM, | ||
226 | chn->clks); | ||
227 | if (ret) { | ||
228 | if (ret == -ENOENT) | ||
229 | break; | ||
230 | |||
231 | if (ret != -EPROBE_DEFER) | ||
232 | dev_err(spc->dev, | ||
233 | "failed to get channel clocks\n"); | ||
234 | |||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | clk_pwm = chn->clks[SPRD_PWM_CHN_OUTPUT_CLK].clk; | ||
239 | chn->clk_rate = clk_get_rate(clk_pwm); | ||
240 | } | ||
241 | |||
242 | if (!i) { | ||
243 | dev_err(spc->dev, "no available PWM channels\n"); | ||
244 | return -ENODEV; | ||
245 | } | ||
246 | |||
247 | spc->num_pwms = i; | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int sprd_pwm_probe(struct platform_device *pdev) | ||
253 | { | ||
254 | struct sprd_pwm_chip *spc; | ||
255 | int ret; | ||
256 | |||
257 | spc = devm_kzalloc(&pdev->dev, sizeof(*spc), GFP_KERNEL); | ||
258 | if (!spc) | ||
259 | return -ENOMEM; | ||
260 | |||
261 | spc->base = devm_platform_ioremap_resource(pdev, 0); | ||
262 | if (IS_ERR(spc->base)) | ||
263 | return PTR_ERR(spc->base); | ||
264 | |||
265 | spc->dev = &pdev->dev; | ||
266 | platform_set_drvdata(pdev, spc); | ||
267 | |||
268 | ret = sprd_pwm_clk_init(spc); | ||
269 | if (ret) | ||
270 | return ret; | ||
271 | |||
272 | spc->chip.dev = &pdev->dev; | ||
273 | spc->chip.ops = &sprd_pwm_ops; | ||
274 | spc->chip.base = -1; | ||
275 | spc->chip.npwm = spc->num_pwms; | ||
276 | |||
277 | ret = pwmchip_add(&spc->chip); | ||
278 | if (ret) | ||
279 | dev_err(&pdev->dev, "failed to add PWM chip\n"); | ||
280 | |||
281 | return ret; | ||
282 | } | ||
283 | |||
284 | static int sprd_pwm_remove(struct platform_device *pdev) | ||
285 | { | ||
286 | struct sprd_pwm_chip *spc = platform_get_drvdata(pdev); | ||
287 | |||
288 | return pwmchip_remove(&spc->chip); | ||
289 | } | ||
290 | |||
291 | static const struct of_device_id sprd_pwm_of_match[] = { | ||
292 | { .compatible = "sprd,ums512-pwm", }, | ||
293 | { }, | ||
294 | }; | ||
295 | MODULE_DEVICE_TABLE(of, sprd_pwm_of_match); | ||
296 | |||
297 | static struct platform_driver sprd_pwm_driver = { | ||
298 | .driver = { | ||
299 | .name = "sprd-pwm", | ||
300 | .of_match_table = sprd_pwm_of_match, | ||
301 | }, | ||
302 | .probe = sprd_pwm_probe, | ||
303 | .remove = sprd_pwm_remove, | ||
304 | }; | ||
305 | |||
306 | module_platform_driver(sprd_pwm_driver); | ||
307 | |||
308 | MODULE_DESCRIPTION("Spreadtrum PWM Driver"); | ||
309 | MODULE_LICENSE("GPL v2"); | ||