mirror of
https://github.com/torvalds/linux.git
synced 2025-11-02 09:40:27 +02:00
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:
parent
b871d093f1
commit
e7c9b66b10
3 changed files with 84 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue