]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: Provide a gpio device for waveform drivers
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Thu, 17 Jul 2025 15:11:16 +0000 (17:11 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Mon, 15 Sep 2025 09:39:44 +0000 (11:39 +0200)
A PWM is a more general concept than an output-only GPIO. When using
duty_length = period_length the PWM looks like an active GPIO, with
duty_length = 0 like an inactive GPIO. With the waveform abstraction
there is enough control over the configuration to ensure that PWMs that
cannot generate a constant signal at both levels error out.

The pwm-pca9685 driver already provides a gpio chip. When this driver is
converted to the waveform callbacks, the gpio part can just be dropped.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Link: https://lore.kernel.org/r/20250717151117.1828585-2-u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/Kconfig
drivers/pwm/core.c
include/linux/pwm.h

index f00ce973dddf651287168b44228574f4d5c28dc0..2aa24608a8dfca5c873d369da43fcc9916f575ed 100644 (file)
@@ -38,6 +38,15 @@ config PWM_DEBUG
          It is expected to introduce some runtime overhead and diagnostic
          output to the kernel log, so only enable while working on a driver.
 
+config PWM_PROVIDE_GPIO
+       bool "Provide a GPIO chip for each PWM chip"
+       depends on GPIOLIB
+       help
+         Most PWMs can emit both a constant active high and a constant active
+         low signal and so they can be used as GPIO. Say Y here to let each
+         PWM chip provide a GPIO chip and so be easily plugged into consumers
+         that know how to handle GPIOs but not PWMs.
+
 config PWM_AB8500
        tristate "AB8500 PWM support"
        depends on AB8500_CORE && ARCH_U8500
index 2570ad6a7f59d018107c6b937f10d08696d32e60..ea2ccf42e81441d00a349f6d8641c92143d94797 100644 (file)
@@ -2391,6 +2391,51 @@ static const struct file_operations pwm_cdev_fileops = {
 
 static dev_t pwm_devt;
 
+static int pwm_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+       struct pwm_chip *chip = gpiochip_get_data(gc);
+       struct pwm_device *pwm;
+
+       pwm = pwm_request_from_chip(chip, offset, "pwm-gpio");
+       if (IS_ERR(pwm))
+               return PTR_ERR(pwm);
+
+       return 0;
+}
+
+static void pwm_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+       struct pwm_chip *chip = gpiochip_get_data(gc);
+
+       pwm_put(&chip->pwms[offset]);
+}
+
+static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+       return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int pwm_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+       struct pwm_chip *chip = gpiochip_get_data(gc);
+       struct pwm_device *pwm = &chip->pwms[offset];
+       int ret;
+       struct pwm_waveform wf = {
+               .period_length_ns = 1,
+       };
+
+       ret = pwm_round_waveform_might_sleep(pwm, &wf);
+       if (ret < 0)
+               return ret;
+
+       if (value)
+               wf.duty_length_ns = wf.period_length_ns;
+       else
+               wf.duty_length_ns = 0;
+
+       return pwm_set_waveform_might_sleep(pwm, &wf, true);
+}
+
 /**
  * __pwmchip_add() - register a new PWM chip
  * @chip: the PWM chip to add
@@ -2457,9 +2502,33 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
        if (ret)
                goto err_device_add;
 
+       if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform) {
+               struct device *parent = pwmchip_parent(chip);
+
+               chip->gpio = (typeof(chip->gpio)){
+                       .label = dev_name(parent),
+                       .parent = parent,
+                       .request = pwm_gpio_request,
+                       .free = pwm_gpio_free,
+                       .get_direction = pwm_gpio_get_direction,
+                       .set = pwm_gpio_set,
+                       .base = -1,
+                       .ngpio = chip->npwm,
+                       .can_sleep = true,
+               };
+
+               ret = gpiochip_add_data(&chip->gpio, chip);
+               if (ret)
+                       goto err_gpiochip_add;
+       }
+
        return 0;
 
+err_gpiochip_add:
+
+       cdev_device_del(&chip->cdev, &chip->dev);
 err_device_add:
+
        scoped_guard(pwmchip, chip)
                chip->operational = false;
 
@@ -2480,6 +2549,9 @@ EXPORT_SYMBOL_GPL(__pwmchip_add);
  */
 void pwmchip_remove(struct pwm_chip *chip)
 {
+       if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform)
+               gpiochip_remove(&chip->gpio);
+
        pwmchip_sysfs_unexport(chip);
 
        scoped_guard(mutex, &pwm_lock) {
index 8cafc483db53addf95591d1ac74287532c0fa0ee..549ac4aaad59ba6d7ba58d818b46a58a8759e09c 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/cdev.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/gpio/driver.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/of.h>
@@ -321,6 +322,7 @@ struct pwm_ops {
  * @npwm: number of PWMs controlled by this chip
  * @of_xlate: request a PWM device given a device tree PWM specifier
  * @atomic: can the driver's ->apply() be called in atomic context
+ * @gpio: &struct gpio_chip to operate this PWM chip's lines as GPO
  * @uses_pwmchip_alloc: signals if pwmchip_allow was used to allocate this chip
  * @operational: signals if the chip can be used (or is already deregistered)
  * @nonatomic_lock: mutex for nonatomic chips
@@ -340,6 +342,7 @@ struct pwm_chip {
        bool atomic;
 
        /* only used internally by the PWM framework */
+       struct gpio_chip gpio;
        bool uses_pwmchip_alloc;
        bool operational;
        union {