Re: [PATCH v3 4/7] PWM: add pwm driver for stm32 plaftorm

From: Thierry Reding
Date: Mon Dec 05 2016 - 02:25:06 EST


On Fri, Dec 02, 2016 at 11:17:19AM +0100, Benjamin Gaignard wrote:
> This driver add support for pwm driver on stm32 platform.

"adds". Also please use PWM in prose because it's an abbreviation.

> The SoC have multiple instances of the hardware IP and each
> of them could have small differences: number of channels,
> complementary output, counter register size...
> Use DT parameters to handle those differentes configuration

"different configurations"

>
> version 2:
> - only keep one comptatible
> - use DT paramaters to discover hardware block configuration

"parameters"

>
> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@xxxxxx>
> ---
> drivers/pwm/Kconfig | 8 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-stm32.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 294 insertions(+)
> create mode 100644 drivers/pwm/pwm-stm32.c
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index bf01288..a89fdba 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -388,6 +388,14 @@ config PWM_STI
> To compile this driver as a module, choose M here: the module
> will be called pwm-sti.
>
> +config PWM_STM32
> + bool "STMicroelectronics STM32 PWM"
> + depends on ARCH_STM32
> + depends on OF
> + select MFD_STM32_GP_TIMER

Should that be a "depends on"?

> + help
> + Generic PWM framework driver for STM32 SoCs.
> +
> config PWM_STMPE
> bool "STMPE expander PWM export"
> depends on MFD_STMPE
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 1194c54..5aa9308 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -37,6 +37,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
> obj-$(CONFIG_PWM_STI) += pwm-sti.o
> +obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
> obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
> obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
> obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
> diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c
> new file mode 100644
> index 0000000..a362f63
> --- /dev/null
> +++ b/drivers/pwm/pwm-stm32.c
> @@ -0,0 +1,285 @@
> +/*
> + * Copyright (C) STMicroelectronics 2016
> + * Author: Gerald Baeza <gerald.baeza@xxxxxx>

Could use a blank line between the above. Also, please use a single
space after : for consistency.

> + * License terms: GNU General Public License (GPL), version 2

Here too.

> + *
> + * Inspired by timer-stm32.c from Maxime Coquelin
> + * pwm-atmel.c from Bo Shen
> + */
> +
> +#include <linux/of.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>

Please sort these alphabetically.

> +
> +#include <linux/mfd/stm32-gptimer.h>
> +
> +#define DRIVER_NAME "stm32-pwm"
> +
> +#define CAP_COMPLEMENTARY BIT(0)
> +#define CAP_32BITS_COUNTER BIT(1)
> +#define CAP_BREAKINPUT BIT(2)
> +#define CAP_BREAKINPUT_POLARITY BIT(3)

Just make these boolean. Makes the conditionals a lot simpler to read.

> +
> +struct stm32_pwm_dev {

No need for the _dev suffix.

> + struct device *dev;
> + struct clk *clk;
> + struct regmap *regmap;
> + struct pwm_chip chip;

It's slightly more efficient to put this as first field because then
to_stm32_pwm() becomes a no-op.

> + int caps;
> + int npwm;

unsigned int, please.

> + u32 polarity;

Maybe use a prefix here to stress that it is the polarity of the
complementary output. Otherwise one might take it for the PWM signal's
polarity that's already part of the PWM state.

> +};
> +
> +#define to_stm32_pwm_dev(x) container_of(chip, struct stm32_pwm_dev, chip)

Please turn this into a static inline.

> +
> +static u32 __active_channels(struct stm32_pwm_dev *pwm_dev)

No need for a __ prefix.

> +{
> + u32 ccer;
> +
> + regmap_read(pwm_dev->regmap, TIM_CCER, &ccer);
> +
> + return ccer & TIM_CCER_CCXE;
> +}
> +
> +static int write_ccrx(struct stm32_pwm_dev *dev, struct pwm_device *pwm,
> + u32 ccr)

u32 value, perhaps? I first mistook this to be a register offset.

> +{
> + switch (pwm->hwpwm) {
> + case 0:
> + return regmap_write(dev->regmap, TIM_CCR1, ccr);
> + case 1:
> + return regmap_write(dev->regmap, TIM_CCR2, ccr);
> + case 2:
> + return regmap_write(dev->regmap, TIM_CCR3, ccr);
> + case 3:
> + return regmap_write(dev->regmap, TIM_CCR4, ccr);
> + }
> + return -EINVAL;
> +}
> +
> +static int stm32_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> + int duty_ns, int period_ns)

Please implement this as an atomic PWM driver, I don't want new drivers
to use the legacy callbacks.

> +{
> + struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);

I think something like "stm", or "priv" would be more appropriate here.
If you ever need access to a struct device, you'll be hard-pressed to
find a good name for it.

> + unsigned long long prd, div, dty;
> + int prescaler = 0;

If this can never be negative, please make it unsigned int.

> + u32 max_arr = 0xFFFF, ccmr, mask, shift, bdtr;
> +
> + if (dev->caps & CAP_32BITS_COUNTER)
> + max_arr = 0xFFFFFFFF;

I prefer lower-case hexadecimal digits.

> +
> + /* Period and prescaler values depends of clock rate */

"depend on"

> + div = (unsigned long long)clk_get_rate(dev->clk) * period_ns;
> +
> + do_div(div, NSEC_PER_SEC);
> + prd = div;
> +
> + while (div > max_arr) {
> + prescaler++;
> + div = prd;
> + do_div(div, (prescaler + 1));
> + }
> + prd = div;

Blank line after blocks, please.

> +
> + if (prescaler > MAX_TIM_PSC) {
> + dev_err(chip->dev, "prescaler exceeds the maximum value\n");
> + return -EINVAL;
> + }
> +
> + /* All channels share the same prescaler and counter so
> + * when two channels are active at the same we can't change them
> + */

This isn't proper block comment style. Also, please use all of the
columns at your disposal.

> + if (__active_channels(dev) & ~(1 << pwm->hwpwm * 4)) {
> + u32 psc, arr;
> +
> + regmap_read(dev->regmap, TIM_PSC, &psc);
> + regmap_read(dev->regmap, TIM_ARR, &arr);
> +
> + if ((psc != prescaler) || (arr != prd - 1))
> + return -EINVAL;

Maybe -EBUSY to differentiate from other error cases?

> + }
> +
> + regmap_write(dev->regmap, TIM_PSC, prescaler);
> + regmap_write(dev->regmap, TIM_ARR, prd - 1);
> + regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
> +
> + /* Calculate the duty cycles */
> + dty = prd * duty_ns;
> + do_div(dty, period_ns);
> +
> + write_ccrx(dev, pwm, dty);
> +
> + /* Configure output mode */
> + shift = (pwm->hwpwm & 0x1) * 8;
> + ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
> + mask = 0xFF << shift;
> +
> + if (pwm->hwpwm & 0x2)

This looks as though TIM_CCMR1 is used for channels 0 and 1, while
TIM_CCMR2 is used for channels 2 and 3. Wouldn't it be more natural to
make the conditional above:

if (pwm->hwpwm >= 2)

? Or perhaps better yet:

if (pwm->hwpwm < 2)
/* update TIM_CCMR1 */
else
/* update TIM_CCMR2 */

The other alternative, which might make the code slightly more readable,
would be to get rid of all these conditionals by parameterizing the
offsets per PWM channel. The PWM subsystem has a means of storing per-
channel chip-specific data (see pwm_{set,get}_chip_data()). It might be
a little overkill for this particular driver, given that the number of
conditionals is fairly small.

> + regmap_update_bits(dev->regmap, TIM_CCMR2, mask, ccmr);
> + else
> + regmap_update_bits(dev->regmap, TIM_CCMR1, mask, ccmr);
> +
> + if (!(dev->caps & CAP_BREAKINPUT))
> + return 0;

If you make these capabilities boolean, it becomes much more readable,
especially for negations:

if (!dev->has_breakinput)

> +
> + bdtr = TIM_BDTR_MOE | TIM_BDTR_AOE;
> +
> + if (dev->caps & CAP_BREAKINPUT_POLARITY)
> + bdtr |= TIM_BDTR_BKE;
> +
> + if (dev->polarity)
> + bdtr |= TIM_BDTR_BKP;
> +
> + regmap_update_bits(dev->regmap, TIM_BDTR,
> + TIM_BDTR_MOE | TIM_BDTR_AOE |
> + TIM_BDTR_BKP | TIM_BDTR_BKE,
> + bdtr);
> +
> + return 0;
> +}
> +
> +static int stm32_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
> + enum pwm_polarity polarity)
> +{
> + u32 mask;
> + struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
> +
> + mask = TIM_CCER_CC1P << (pwm->hwpwm * 4);
> + if (dev->caps & CAP_COMPLEMENTARY)
> + mask |= TIM_CCER_CC1NP << (pwm->hwpwm * 4);
> +
> + regmap_update_bits(dev->regmap, TIM_CCER, mask,
> + polarity == PWM_POLARITY_NORMAL ? 0 : mask);
> +
> + return 0;
> +}
> +
> +static int stm32_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + u32 mask;
> + struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
> +
> + clk_enable(dev->clk);
> +
> + /* Enable channel */
> + mask = TIM_CCER_CC1E << (pwm->hwpwm * 4);
> + if (dev->caps & CAP_COMPLEMENTARY)
> + mask |= TIM_CCER_CC1NE << (pwm->hwpwm * 4);
> +
> + regmap_update_bits(dev->regmap, TIM_CCER, mask, mask);
> +
> + /* Make sure that registers are updated */
> + regmap_update_bits(dev->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
> +
> + /* Enable controller */
> + regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_CEN, TIM_CR1_CEN);
> +
> + return 0;
> +}
> +
> +static void stm32_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + u32 mask;
> + struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
> +
> + /* Disable channel */
> + mask = TIM_CCER_CC1E << (pwm->hwpwm * 4);
> + if (dev->caps & CAP_COMPLEMENTARY)
> + mask |= TIM_CCER_CC1NE << (pwm->hwpwm * 4);
> +
> + regmap_update_bits(dev->regmap, TIM_CCER, mask, 0);
> +
> + /* When all channels are disabled, we can disable the controller */
> + if (!__active_channels(dev))
> + regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_CEN, 0);
> +
> + clk_disable(dev->clk);
> +}

All of the above can be folded into the ->apply() hook for atomic PWM
support.

Also, in the above you use clk_enable() in the ->enable() callback and
clk_disable() in ->disable(). If you need the clock to access registers
you'll have to enabled it in the others as well because there are no
guarantees that configuration will only happen between ->enable() and
->disable(). Atomic PWM simplifies this a bit, but you still need to be
careful about when to enable the clock in your ->apply() hook.

> +
> +static const struct pwm_ops stm32pwm_ops = {
> + .config = stm32_pwm_config,
> + .set_polarity = stm32_pwm_set_polarity,
> + .enable = stm32_pwm_enable,
> + .disable = stm32_pwm_disable,
> +};

You need to set the .owner field as well.

> +
> +static int stm32_pwm_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct stm32_gptimer_dev *mfd = dev_get_drvdata(pdev->dev.parent);
> + struct stm32_pwm_dev *pwm;
> + int ret;
> +
> + pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> + if (!pwm)
> + return -ENOMEM;
> +
> + pwm->regmap = mfd->regmap;
> + pwm->clk = mfd->clk;
> +
> + if (!pwm->regmap || !pwm->clk)
> + return -EINVAL;
> +
> + if (of_property_read_bool(np, "st,complementary"))
> + pwm->caps |= CAP_COMPLEMENTARY;
> +
> + if (of_property_read_bool(np, "st,32bits-counter"))
> + pwm->caps |= CAP_32BITS_COUNTER;
> +
> + if (of_property_read_bool(np, "st,breakinput"))
> + pwm->caps |= CAP_BREAKINPUT;
> +
> + if (!of_property_read_u32(np, "st,breakinput-polarity", &pwm->polarity))
> + pwm->caps |= CAP_BREAKINPUT_POLARITY;
> +
> + of_property_read_u32(np, "st,pwm-num-chan", &pwm->npwm);
> +
> + pwm->chip.base = -1;
> + pwm->chip.dev = dev;
> + pwm->chip.ops = &stm32pwm_ops;
> + pwm->chip.npwm = pwm->npwm;
> +
> + ret = pwmchip_add(&pwm->chip);
> + if (ret < 0)
> + return ret;
> +
> + platform_set_drvdata(pdev, pwm);
> +
> + return 0;
> +}
> +
> +static int stm32_pwm_remove(struct platform_device *pdev)
> +{
> + struct stm32_pwm_dev *pwm = platform_get_drvdata(pdev);
> + int i;

unsigned int, please.

> +
> + for (i = 0; i < pwm->npwm; i++)
> + pwm_disable(&pwm->chip.pwms[i]);
> +
> + pwmchip_remove(&pwm->chip);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id stm32_pwm_of_match[] = {
> + {
> + .compatible = "st,stm32-pwm",
> + },

The above can be collapsed into a single line.

> +};
> +MODULE_DEVICE_TABLE(of, stm32_pwm_of_match);
> +
> +static struct platform_driver stm32_pwm_driver = {
> + .probe = stm32_pwm_probe,
> + .remove = stm32_pwm_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .of_match_table = stm32_pwm_of_match,
> + },
> +};

Please don't use tabs for padding within the structure definition since
it doesn't align properly anyway.

> +module_platform_driver(stm32_pwm_driver);
> +
> +MODULE_ALIAS("platform:" DRIVER_NAME);
> +MODULE_DESCRIPTION("STMicroelectronics STM32 PWM driver");
> +MODULE_LICENSE("GPL");

According to the header comment this should be "GPL v2".

Thanks,
Thierry

Attachment: signature.asc
Description: PGP signature