Skip to content

Commit e70a540

Browse files
Fabrice GasnierLee Jones
authored andcommitted
pwm: Add STM32 LPTimer PWM driver
Add support for single PWM channel on Low-Power Timer, that can be found on some STM32 platforms. Signed-off-by: Fabrice Gasnier <[email protected]> Acked-by: Thierry Reding <[email protected]> Signed-off-by: Lee Jones <[email protected]>
1 parent e892400 commit e70a540

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

drivers/pwm/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,16 @@ config PWM_STM32
417417
To compile this driver as a module, choose M here: the module
418418
will be called pwm-stm32.
419419

420+
config PWM_STM32_LP
421+
tristate "STMicroelectronics STM32 PWM LP"
422+
depends on MFD_STM32_LPTIMER || COMPILE_TEST
423+
help
424+
Generic PWM framework driver for STMicroelectronics STM32 SoCs
425+
with Low-Power Timer (LPTIM).
426+
427+
To compile this driver as a module, choose M here: the module
428+
will be called pwm-stm32-lp.
429+
420430
config PWM_STMPE
421431
bool "STMPE expander PWM export"
422432
depends on MFD_STMPE

drivers/pwm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
4040
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
4141
obj-$(CONFIG_PWM_STI) += pwm-sti.o
4242
obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
43+
obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o
4344
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
4445
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
4546
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o

drivers/pwm/pwm-stm32-lp.c

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
* STM32 Low-Power Timer PWM driver
3+
*
4+
* Copyright (C) STMicroelectronics 2017
5+
*
6+
* Author: Gerald Baeza <[email protected]>
7+
*
8+
* License terms: GNU General Public License (GPL), version 2
9+
*
10+
* Inspired by Gerald Baeza's pwm-stm32 driver
11+
*/
12+
13+
#include <linux/bitfield.h>
14+
#include <linux/mfd/stm32-lptimer.h>
15+
#include <linux/module.h>
16+
#include <linux/of.h>
17+
#include <linux/platform_device.h>
18+
#include <linux/pwm.h>
19+
20+
struct stm32_pwm_lp {
21+
struct pwm_chip chip;
22+
struct clk *clk;
23+
struct regmap *regmap;
24+
};
25+
26+
static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip)
27+
{
28+
return container_of(chip, struct stm32_pwm_lp, chip);
29+
}
30+
31+
/* STM32 Low-Power Timer is preceded by a configurable power-of-2 prescaler */
32+
#define STM32_LPTIM_MAX_PRESCALER 128
33+
34+
static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
35+
struct pwm_state *state)
36+
{
37+
struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip);
38+
unsigned long long prd, div, dty;
39+
struct pwm_state cstate;
40+
u32 val, mask, cfgr, presc = 0;
41+
bool reenable;
42+
int ret;
43+
44+
pwm_get_state(pwm, &cstate);
45+
reenable = !cstate.enabled;
46+
47+
if (!state->enabled) {
48+
if (cstate.enabled) {
49+
/* Disable LP timer */
50+
ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
51+
if (ret)
52+
return ret;
53+
/* disable clock to PWM counter */
54+
clk_disable(priv->clk);
55+
}
56+
return 0;
57+
}
58+
59+
/* Calculate the period and prescaler value */
60+
div = (unsigned long long)clk_get_rate(priv->clk) * state->period;
61+
do_div(div, NSEC_PER_SEC);
62+
prd = div;
63+
while (div > STM32_LPTIM_MAX_ARR) {
64+
presc++;
65+
if ((1 << presc) > STM32_LPTIM_MAX_PRESCALER) {
66+
dev_err(priv->chip.dev, "max prescaler exceeded\n");
67+
return -EINVAL;
68+
}
69+
div = prd >> presc;
70+
}
71+
prd = div;
72+
73+
/* Calculate the duty cycle */
74+
dty = prd * state->duty_cycle;
75+
do_div(dty, state->period);
76+
77+
if (!cstate.enabled) {
78+
/* enable clock to drive PWM counter */
79+
ret = clk_enable(priv->clk);
80+
if (ret)
81+
return ret;
82+
}
83+
84+
ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr);
85+
if (ret)
86+
goto err;
87+
88+
if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) != presc) ||
89+
(FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) != state->polarity)) {
90+
val = FIELD_PREP(STM32_LPTIM_PRESC, presc);
91+
val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity);
92+
mask = STM32_LPTIM_PRESC | STM32_LPTIM_WAVPOL;
93+
94+
/* Must disable LP timer to modify CFGR */
95+
reenable = true;
96+
ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
97+
if (ret)
98+
goto err;
99+
100+
ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CFGR, mask,
101+
val);
102+
if (ret)
103+
goto err;
104+
}
105+
106+
if (reenable) {
107+
/* Must (re)enable LP timer to modify CMP & ARR */
108+
ret = regmap_write(priv->regmap, STM32_LPTIM_CR,
109+
STM32_LPTIM_ENABLE);
110+
if (ret)
111+
goto err;
112+
}
113+
114+
ret = regmap_write(priv->regmap, STM32_LPTIM_ARR, prd - 1);
115+
if (ret)
116+
goto err;
117+
118+
ret = regmap_write(priv->regmap, STM32_LPTIM_CMP, prd - (1 + dty));
119+
if (ret)
120+
goto err;
121+
122+
/* ensure CMP & ARR registers are properly written */
123+
ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val,
124+
(val & STM32_LPTIM_CMPOK_ARROK),
125+
100, 1000);
126+
if (ret) {
127+
dev_err(priv->chip.dev, "ARR/CMP registers write issue\n");
128+
goto err;
129+
}
130+
ret = regmap_write(priv->regmap, STM32_LPTIM_ICR,
131+
STM32_LPTIM_CMPOKCF_ARROKCF);
132+
if (ret)
133+
goto err;
134+
135+
if (reenable) {
136+
/* Start LP timer in continuous mode */
137+
ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CR,
138+
STM32_LPTIM_CNTSTRT,
139+
STM32_LPTIM_CNTSTRT);
140+
if (ret) {
141+
regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
142+
goto err;
143+
}
144+
}
145+
146+
return 0;
147+
err:
148+
if (!cstate.enabled)
149+
clk_disable(priv->clk);
150+
151+
return ret;
152+
}
153+
154+
static void stm32_pwm_lp_get_state(struct pwm_chip *chip,
155+
struct pwm_device *pwm,
156+
struct pwm_state *state)
157+
{
158+
struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip);
159+
unsigned long rate = clk_get_rate(priv->clk);
160+
u32 val, presc, prd;
161+
u64 tmp;
162+
163+
regmap_read(priv->regmap, STM32_LPTIM_CR, &val);
164+
state->enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val);
165+
/* Keep PWM counter clock refcount in sync with PWM initial state */
166+
if (state->enabled)
167+
clk_enable(priv->clk);
168+
169+
regmap_read(priv->regmap, STM32_LPTIM_CFGR, &val);
170+
presc = FIELD_GET(STM32_LPTIM_PRESC, val);
171+
state->polarity = FIELD_GET(STM32_LPTIM_WAVPOL, val);
172+
173+
regmap_read(priv->regmap, STM32_LPTIM_ARR, &prd);
174+
tmp = prd + 1;
175+
tmp = (tmp << presc) * NSEC_PER_SEC;
176+
state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
177+
178+
regmap_read(priv->regmap, STM32_LPTIM_CMP, &val);
179+
tmp = prd - val;
180+
tmp = (tmp << presc) * NSEC_PER_SEC;
181+
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
182+
}
183+
184+
static const struct pwm_ops stm32_pwm_lp_ops = {
185+
.owner = THIS_MODULE,
186+
.apply = stm32_pwm_lp_apply,
187+
.get_state = stm32_pwm_lp_get_state,
188+
};
189+
190+
static int stm32_pwm_lp_probe(struct platform_device *pdev)
191+
{
192+
struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent);
193+
struct stm32_pwm_lp *priv;
194+
int ret;
195+
196+
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
197+
if (!priv)
198+
return -ENOMEM;
199+
200+
priv->regmap = ddata->regmap;
201+
priv->clk = ddata->clk;
202+
priv->chip.base = -1;
203+
priv->chip.dev = &pdev->dev;
204+
priv->chip.ops = &stm32_pwm_lp_ops;
205+
priv->chip.npwm = 1;
206+
207+
ret = pwmchip_add(&priv->chip);
208+
if (ret < 0)
209+
return ret;
210+
211+
platform_set_drvdata(pdev, priv);
212+
213+
return 0;
214+
}
215+
216+
static int stm32_pwm_lp_remove(struct platform_device *pdev)
217+
{
218+
struct stm32_pwm_lp *priv = platform_get_drvdata(pdev);
219+
unsigned int i;
220+
221+
for (i = 0; i < priv->chip.npwm; i++)
222+
if (pwm_is_enabled(&priv->chip.pwms[i]))
223+
pwm_disable(&priv->chip.pwms[i]);
224+
225+
return pwmchip_remove(&priv->chip);
226+
}
227+
228+
static const struct of_device_id stm32_pwm_lp_of_match[] = {
229+
{ .compatible = "st,stm32-pwm-lp", },
230+
{},
231+
};
232+
MODULE_DEVICE_TABLE(of, stm32_pwm_lp_of_match);
233+
234+
static struct platform_driver stm32_pwm_lp_driver = {
235+
.probe = stm32_pwm_lp_probe,
236+
.remove = stm32_pwm_lp_remove,
237+
.driver = {
238+
.name = "stm32-pwm-lp",
239+
.of_match_table = of_match_ptr(stm32_pwm_lp_of_match),
240+
},
241+
};
242+
module_platform_driver(stm32_pwm_lp_driver);
243+
244+
MODULE_ALIAS("platform:stm32-pwm-lp");
245+
MODULE_DESCRIPTION("STMicroelectronics STM32 PWM LP driver");
246+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)