pwm: Provide a gpio device for waveform drivers

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>
This commit is contained in:
Uwe Kleine-König 2025-07-17 17:11:16 +02:00 committed by Uwe Kleine-König
parent b871d093f1
commit e7c9b66b10
3 changed files with 84 additions and 0 deletions

View file

@ -38,6 +38,15 @@ config PWM_DEBUG
It is expected to introduce some runtime overhead and diagnostic It is expected to introduce some runtime overhead and diagnostic
output to the kernel log, so only enable while working on a driver. 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 config PWM_AB8500
tristate "AB8500 PWM support" tristate "AB8500 PWM support"
depends on AB8500_CORE && ARCH_U8500 depends on AB8500_CORE && ARCH_U8500

View file

@ -2391,6 +2391,51 @@ static const struct file_operations pwm_cdev_fileops = {
static dev_t pwm_devt; 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 * __pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add * @chip: the PWM chip to add
@ -2457,9 +2502,33 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
if (ret) if (ret)
goto err_device_add; 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; return 0;
err_gpiochip_add:
cdev_device_del(&chip->cdev, &chip->dev);
err_device_add: err_device_add:
scoped_guard(pwmchip, chip) scoped_guard(pwmchip, chip)
chip->operational = false; chip->operational = false;
@ -2480,6 +2549,9 @@ EXPORT_SYMBOL_GPL(__pwmchip_add);
*/ */
void pwmchip_remove(struct pwm_chip *chip) 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); pwmchip_sysfs_unexport(chip);
scoped_guard(mutex, &pwm_lock) { scoped_guard(mutex, &pwm_lock) {

View file

@ -5,6 +5,7 @@
#include <linux/cdev.h> #include <linux/cdev.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/gpio/driver.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/of.h> #include <linux/of.h>
@ -321,6 +322,7 @@ struct pwm_ops {
* @npwm: number of PWMs controlled by this chip * @npwm: number of PWMs controlled by this chip
* @of_xlate: request a PWM device given a device tree PWM specifier * @of_xlate: request a PWM device given a device tree PWM specifier
* @atomic: can the driver's ->apply() be called in atomic context * @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 * @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) * @operational: signals if the chip can be used (or is already deregistered)
* @nonatomic_lock: mutex for nonatomic chips * @nonatomic_lock: mutex for nonatomic chips
@ -340,6 +342,7 @@ struct pwm_chip {
bool atomic; bool atomic;
/* only used internally by the PWM framework */ /* only used internally by the PWM framework */
struct gpio_chip gpio;
bool uses_pwmchip_alloc; bool uses_pwmchip_alloc;
bool operational; bool operational;
union { union {