From: Sean Young <sean@mess.org>
To: Stefan Wahren <wahrenst@gmx.net>
Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>,
"Rob Herring" <robh+dt@kernel.org>,
"Krzysztof Kozlowski" <krzysztof.kozlowski+dt@linaro.org>,
"Conor Dooley" <conor+dt@kernel.org>,
andy.shevchenko@gmail.com,
"Angelo Compagnucci" <angelo.compagnucci@gmail.com>,
"Philip Howard" <phil@gadgetoid.com>,
"Linus Walleij" <linus.walleij@linaro.org>,
linux-pwm@vger.kernel.org, devicetree@vger.kernel.org,
linux-gpio@vger.kernel.org,
"Vincent Whitchurch" <vincent.whitchurch@axis.com>
Subject: Re: [PATCH V3 2/2] pwm: Add GPIO PWM driver
Date: Tue, 30 Jan 2024 09:08:53 +0000 [thread overview]
Message-ID: <Zbi8pbT7N0vKUgmx@gofer.mess.org> (raw)
In-Reply-To: <20240128163630.104725-3-wahrenst@gmx.net>
On Sun, Jan 28, 2024 at 05:36:30PM +0100, Stefan Wahren wrote:
> From: Vincent Whitchurch <vincent.whitchurch@axis.com>
>
> Add a software PWM which toggles a GPIO from a high-resolution timer.
>
> This will naturally not be as accurate or as efficient as a hardware
> PWM, but it is useful in some cases. I have for example used it for
> evaluating LED brightness handling (via leds-pwm) on a board where the
> LED was just hooked up to a GPIO, and for a simple verification of the
> timer frequency on another platform.
>
> Since high-resolution timers are used, sleeping gpio chips are not
> supported and are rejected in the probe function.
>
> Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
> Co-developed-by: Stefan Wahren <wahrenst@gmx.net>
> Signed-off-by: Stefan Wahren <wahrenst@gmx.net>
> ---
> drivers/pwm/Kconfig | 11 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-gpio.c | 221 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 233 insertions(+)
> create mode 100644 drivers/pwm/pwm-gpio.c
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 4b956d661755..7cfda2cde130 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -227,6 +227,17 @@ config PWM_FSL_FTM
> To compile this driver as a module, choose M here: the module
> will be called pwm-fsl-ftm.
>
> +config PWM_GPIO
> + tristate "GPIO PWM support"
> + depends on GPIOLIB
> + depends on HIGH_RES_TIMERS
> + help
> + Generic PWM framework driver for a software PWM toggling a GPIO pin
> + from kernel high-resolution timers.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-gpio.
> +
> config PWM_HIBVT
> tristate "HiSilicon BVT PWM support"
> depends on ARCH_HISI || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index c5ec9e168ee7..59d1a46bb1af 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
> obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
> obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
> obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
> +obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o
> obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
> obj-$(CONFIG_PWM_IMG) += pwm-img.o
> obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
> diff --git a/drivers/pwm/pwm-gpio.c b/drivers/pwm/pwm-gpio.c
> new file mode 100644
> index 000000000000..14637abe465b
> --- /dev/null
> +++ b/drivers/pwm/pwm-gpio.c
> @@ -0,0 +1,221 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Generic software PWM for modulating GPIOs
> + *
> + * Copyright (C) 2020 Axis Communications AB
> + * Copyright (C) 2020 Nicola Di Lieto
> + * Copyright (C) 2024 Stefan Wahren
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/hrtimer.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/spinlock.h>
> +
> +struct pwm_gpio {
> + struct pwm_chip chip;
> + struct hrtimer gpio_timer;
> + struct gpio_desc *gpio;
> + struct pwm_state state;
> + struct pwm_state next_state;
> +
> + /* Protect internal state between pwm_ops and hrtimer */
> + spinlock_t lock;
> +
> + bool changing;
> + bool running;
> + bool level;
> +};
> +
> +static unsigned long pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level)
> +{
> + const struct pwm_state *state = &gpwm->state;
> + bool invert = state->polarity == PWM_POLARITY_INVERSED;
> +
> + gpwm->level = level;
> + gpiod_set_value(gpwm->gpio, gpwm->level ^ invert);
> +
> + if (!state->duty_cycle || state->duty_cycle == state->period) {
> + gpwm->running = false;
> + return 0;
> + }
> +
> + gpwm->running = true;
> + return level ? state->duty_cycle : state->period - state->duty_cycle;
> +}
> +
> +static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
> +{
> + struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
> + gpio_timer);
> + unsigned long next_toggle;
> + unsigned long flags;
> + bool new_level;
> +
> + spin_lock_irqsave(&gpwm->lock, flags);
> +
> + /* Apply new state at end of current period */
> + if (!gpwm->level && gpwm->changing) {
> + gpwm->changing = false;
> + gpwm->state = gpwm->next_state;
> + new_level = !!gpwm->state.duty_cycle;
> + } else {
> + new_level = !gpwm->level;
> + }
> +
> + next_toggle = pwm_gpio_toggle(gpwm, new_level);
> + if (next_toggle) {
> + hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
> + ns_to_ktime(next_toggle));
> + }
> +
> + spin_unlock_irqrestore(&gpwm->lock, flags);
> +
> + return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
> +}
> +
> +static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> + const struct pwm_state *state)
> +{
> + struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
> + bool invert = state->polarity == PWM_POLARITY_INVERSED;
> + unsigned long flags;
Not sure this is necessary but how about:
if (state->duty_cycle < hrtimer_resolution ||
state->period - state->duty_cycle < hrtimer_resolution)
return -EINVAL;
> +
> + if (!state->enabled) {
> + hrtimer_cancel(&gpwm->gpio_timer);
> + } else if (!gpwm->running) {
> + /*
> + * This just enables the output, but pwm_gpio_toggle()
> + * really starts the duty cycle.
> + */
> + int ret = gpiod_direction_output(gpwm->gpio, invert);
> +
> + if (ret)
> + return ret;
> + }
> +
> + spin_lock_irqsave(&gpwm->lock, flags);
> +
> + if (!state->enabled) {
> + gpwm->state = *state;
> + gpwm->running = false;
> + gpwm->changing = false;
> +
> + gpiod_set_value(gpwm->gpio, invert);
> + } else if (gpwm->running) {
> + gpwm->next_state = *state;
> + gpwm->changing = true;
> + } else {
> + unsigned long next_toggle;
> +
> + gpwm->state = *state;
> + gpwm->changing = false;
> +
> + next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle);
> + if (next_toggle) {
> + hrtimer_start(&gpwm->gpio_timer, next_toggle,
> + HRTIMER_MODE_REL);
> + }
> + }
> +
> + spin_unlock_irqrestore(&gpwm->lock, flags);
> +
> + return 0;
> +}
> +
> +static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> + struct pwm_state *state)
> +{
> + struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gpwm->lock, flags);
> +
> + if (gpwm->changing)
> + *state = gpwm->next_state;
> + else
> + *state = gpwm->state;
> +
> + spin_unlock_irqrestore(&gpwm->lock, flags);
> +
> + return 0;
> +}
> +
> +static const struct pwm_ops pwm_gpio_ops = {
> + .apply = pwm_gpio_apply,
> + .get_state = pwm_gpio_get_state,
> +};
> +
> +static int pwm_gpio_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct pwm_gpio *gpwm;
> + int ret;
> +
> + gpwm = devm_kzalloc(dev, sizeof(*gpwm), GFP_KERNEL);
> + if (!gpwm)
> + return -ENOMEM;
> +
> + spin_lock_init(&gpwm->lock);
> +
> + gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
> + if (IS_ERR(gpwm->gpio)) {
> + return dev_err_probe(dev, PTR_ERR(gpwm->gpio),
> + "could not get gpio\n");
> + }
> +
> + if (gpiod_cansleep(gpwm->gpio)) {
> + return dev_err_probe(dev, -EINVAL,
> + "sleeping GPIO %d not supported\n",
> + desc_to_gpio(gpwm->gpio));
> + }
> +
> + gpwm->chip.dev = dev;
> + gpwm->chip.ops = &pwm_gpio_ops;
> + gpwm->chip.npwm = 1;
gpwm->chip.atomic = true; ?
> +
> + hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + gpwm->gpio_timer.function = pwm_gpio_timer;
> +
> + ret = pwmchip_add(&gpwm->chip);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "could not add pwmchip\n");
> +
> + platform_set_drvdata(pdev, gpwm);
> +
> + return 0;
> +}
> +
> +static void pwm_gpio_remove(struct platform_device *pdev)
> +{
> + struct pwm_gpio *gpwm = platform_get_drvdata(pdev);
> +
> + pwmchip_remove(&gpwm->chip);
> + hrtimer_cancel(&gpwm->gpio_timer);
> +}
> +
> +static const struct of_device_id pwm_gpio_dt_ids[] = {
> + { .compatible = "pwm-gpio" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids);
> +
> +static struct platform_driver pwm_gpio_driver = {
> + .driver = {
> + .name = "pwm-gpio",
> + .of_match_table = pwm_gpio_dt_ids,
> + },
> + .probe = pwm_gpio_probe,
> + .remove_new = pwm_gpio_remove,
> +};
> +module_platform_driver(pwm_gpio_driver);
> +
> +MODULE_DESCRIPTION("PWM GPIO driver");
> +MODULE_ALIAS("platform:pwm-gpio");
> +MODULE_AUTHOR("Vincent Whitchurch");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
>
next prev parent reply other threads:[~2024-01-30 9:08 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-01-28 16:36 [PATCH V3 0/2] pwm: Add GPIO PWM driver Stefan Wahren
2024-01-28 16:36 ` [PATCH V3 1/2] dt-bindings: pwm: Add pwm-gpio Stefan Wahren
2024-01-28 17:08 ` Conor Dooley
2024-01-28 16:36 ` [PATCH V3 2/2] pwm: Add GPIO PWM driver Stefan Wahren
2024-01-29 9:40 ` Krzysztof Kozlowski
2024-01-29 13:55 ` Linus Walleij
2024-01-30 8:36 ` Krzysztof Kozlowski
2024-01-30 9:08 ` Sean Young [this message]
2024-01-30 11:21 ` Stefan Wahren
2024-02-02 13:19 ` [PATCH V3 0/2] " Phil Howard
2024-02-02 20:13 ` Stefan Wahren
2024-02-04 19:21 ` Phil Howard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Zbi8pbT7N0vKUgmx@gofer.mess.org \
--to=sean@mess.org \
--cc=andy.shevchenko@gmail.com \
--cc=angelo.compagnucci@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzysztof.kozlowski+dt@linaro.org \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=phil@gadgetoid.com \
--cc=robh+dt@kernel.org \
--cc=u.kleine-koenig@pengutronix.de \
--cc=vincent.whitchurch@axis.com \
--cc=wahrenst@gmx.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox