Skip to content

Commit

Permalink
pwm: Add support for pwmchip devices for faster and easier userspace …
Browse files Browse the repository at this point in the history
…access

With this change each pwmchip defining the new-style waveform callbacks
can be accessed from userspace via a character device. Compared to the
sysfs-API this is faster (on a stm32mp157 applying a new configuration
takes approx 25% only) and allows to pass the whole configuration in a
single ioctl allowing atomic application.

Signed-off-by: Uwe Kleine-König <[email protected]>
Signed-off-by: Trevor Gamblin <[email protected]>
  • Loading branch information
Uwe Kleine-König authored and threexc committed Oct 30, 2024
1 parent 09de43b commit 49dec64
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 15 deletions.
291 changes: 276 additions & 15 deletions drivers/pwm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

#include <dt-bindings/pwm/pwm.h>

#include <uapi/linux/pwm.h>

#define CREATE_TRACE_POINTS
#include <trace/events/pwm.h>

Expand Down Expand Up @@ -1915,20 +1917,9 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
}
EXPORT_SYMBOL_GPL(pwm_get);

/**
* pwm_put() - release a PWM device
* @pwm: PWM device
*/
void pwm_put(struct pwm_device *pwm)
static void __pwm_put(struct pwm_device *pwm)
{
struct pwm_chip *chip;

if (!pwm)
return;

chip = pwm->chip;

guard(mutex)(&pwm_lock);
struct pwm_chip *chip = pwm->chip;

/*
* Trigger a warning if a consumer called pwm_put() twice.
Expand All @@ -1949,6 +1940,20 @@ void pwm_put(struct pwm_device *pwm)

module_put(chip->owner);
}

/**
* pwm_put() - release a PWM device
* @pwm: PWM device
*/
void pwm_put(struct pwm_device *pwm)
{
if (!pwm)
return;

guard(mutex)(&pwm_lock);

__pwm_put(pwm);
}
EXPORT_SYMBOL_GPL(pwm_put);

static void devm_pwm_release(void *pwm)
Expand Down Expand Up @@ -2018,6 +2023,249 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);

struct pwm_cdev_data {
struct pwm_chip *chip;
struct pwm_device *pwm[];
};

static int pwm_cdev_open(struct inode *inode, struct file *file)
{
struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev);
struct pwm_cdev_data *cdata;

guard(mutex)(&pwm_lock);

if (!chip->operational)
return -ENXIO;

cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL);
if (!cdata)
return -ENOMEM;

cdata->chip = chip;

file->private_data = cdata;

return nonseekable_open(inode, file);
}

static int pwm_cdev_release(struct inode *inode, struct file *file)
{
struct pwm_cdev_data *cdata = file->private_data;
unsigned int i;

for (i = 0; i < cdata->chip->npwm; ++i) {
if (cdata->pwm[i])
pwm_put(cdata->pwm[i]);
}
kfree(cdata);

return 0;
}

static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm)
{
struct pwm_chip *chip = cdata->chip;

if (hwpwm >= chip->npwm)
return -EINVAL;

if (!cdata->pwm[hwpwm]) {
struct pwm_device *pwm = &chip->pwms[hwpwm];
int ret;

ret = pwm_device_request(pwm, "pwm-cdev");
if (ret < 0)
return ret;

cdata->pwm[hwpwm] = pwm;
}

return 0;
}

static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm)
{
struct pwm_chip *chip = cdata->chip;

if (hwpwm >= chip->npwm)
return -EINVAL;

if (cdata->pwm[hwpwm]) {
struct pwm_device *pwm = cdata->pwm[hwpwm];

__pwm_put(pwm);

cdata->pwm[hwpwm] = NULL;
}

return 0;
}

static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata,
u32 hwpwm)
{
struct pwm_chip *chip = cdata->chip;

if (hwpwm >= chip->npwm)
return ERR_PTR(-EINVAL);

if (cdata->pwm[hwpwm])
return cdata->pwm[hwpwm];

return ERR_PTR(-EINVAL);
}

static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct pwm_cdev_data *cdata = file->private_data;
struct pwm_chip *chip = cdata->chip;

guard(mutex)(&pwm_lock);

if (!chip->operational)
return -ENODEV;

switch (cmd) {
case PWM_IOCTL_REQUEST:
{
unsigned int hwpwm = arg;

return pwm_cdev_request(cdata, hwpwm);
}
break;

case PWM_IOCTL_FREE:
{
unsigned int hwpwm = arg;

return pwm_cdev_free(cdata, hwpwm);
}
break;

case PWM_IOCTL_ROUNDWF:
{
struct pwmchip_waveform cwf;
struct pwm_waveform wf;
struct pwm_device *pwm;

ret = copy_from_user(&cwf,
(struct pwmchip_waveform __user *)arg,
sizeof(cwf));
if (ret)
return -EFAULT;

if (cwf.__pad != 0)
return -EINVAL;

pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
if (IS_ERR(pwm))
return PTR_ERR(pwm);

wf = (struct pwm_waveform) {
.period_length_ns = cwf.period_length_ns,
.duty_length_ns = cwf.duty_length_ns,
.duty_offset_ns = cwf.duty_offset_ns,
};

ret = pwm_round_waveform_might_sleep(pwm, &wf);
if (ret)
return ret;

cwf = (struct pwmchip_waveform) {
.hwpwm = cwf.hwpwm,
.period_length_ns = wf.period_length_ns,
.duty_length_ns = wf.duty_length_ns,
.duty_offset_ns = wf.duty_offset_ns,
};

return copy_to_user((struct pwmchip_waveform __user *)arg,
&cwf, sizeof(cwf));
}
break;

case PWM_IOCTL_GETWF:
{
struct pwmchip_waveform cwf;
struct pwm_waveform wf;
struct pwm_device *pwm;

ret = copy_from_user(&cwf,
(struct pwmchip_waveform __user *)arg,
sizeof(cwf));
if (ret)
return -EFAULT;

if (cwf.__pad != 0)
return -EINVAL;

pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
if (IS_ERR(pwm))
return PTR_ERR(pwm);

ret = pwm_get_waveform_might_sleep(pwm, &wf);
if (ret)
return ret;

cwf.period_length_ns = wf.period_length_ns;
cwf.duty_length_ns = wf.duty_length_ns;
cwf.duty_offset_ns = wf.duty_offset_ns;

return copy_to_user((struct pwmchip_waveform __user *)arg,
&cwf, sizeof(cwf));
}
break;

case PWM_IOCTL_SETROUNDEDWF:
case PWM_IOCTL_SETEXACTWF:
{
struct pwmchip_waveform cwf;
struct pwm_waveform wf;
struct pwm_device *pwm;

ret = copy_from_user(&cwf,
(struct pwmchip_waveform __user *)arg,
sizeof(cwf));
if (ret)
return -EFAULT;

if (cwf.__pad != 0)
return -EINVAL;

wf = (struct pwm_waveform){
.period_length_ns = cwf.period_length_ns,
.duty_length_ns = cwf.duty_length_ns,
.duty_offset_ns = cwf.duty_offset_ns,
};

if (!pwm_wf_valid(&wf))
return -EINVAL;

pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
if (IS_ERR(pwm))
return PTR_ERR(pwm);

return pwm_set_waveform_might_sleep(pwm, &wf,
cmd == PWM_IOCTL_SETEXACTWF);
}
break;

default:
return -ENOTTY;
}
}

static const struct file_operations pwm_cdev_fileops = {
.open = pwm_cdev_open,
.release = pwm_cdev_release,
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = pwm_cdev_ioctl,
};

static dev_t pwm_devt;

/**
* __pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
Expand Down Expand Up @@ -2070,7 +2318,13 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
scoped_guard(pwmchip, chip)
chip->operational = true;

ret = device_add(&chip->dev);
if (chip->id < 256 && chip->ops->write_waveform)
chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id);

cdev_init(&chip->cdev, &pwm_cdev_fileops);
chip->cdev.owner = owner;

ret = cdev_device_add(&chip->cdev, &chip->dev);
if (ret)
goto err_device_add;

Expand Down Expand Up @@ -2121,7 +2375,7 @@ void pwmchip_remove(struct pwm_chip *chip)
idr_remove(&pwm_chips, chip->id);
}

device_del(&chip->dev);
cdev_device_del(&chip->cdev, &chip->dev);
}
EXPORT_SYMBOL_GPL(pwmchip_remove);

Expand Down Expand Up @@ -2262,9 +2516,16 @@ static int __init pwm_init(void)
{
int ret;

ret = alloc_chrdev_region(&pwm_devt, 0, 256, "pwm");
if (ret) {
pr_warn("Failed to initialize chrdev region for PWM usage\n");
return ret;
}

ret = class_register(&pwm_class);
if (ret) {
pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret));
unregister_chrdev_region(pwm_devt, 256);
return ret;
}

Expand Down
3 changes: 3 additions & 0 deletions include/linux/pwm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#ifndef __LINUX_PWM_H
#define __LINUX_PWM_H

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/module.h>
Expand Down Expand Up @@ -309,6 +310,7 @@ struct pwm_ops {
/**
* struct pwm_chip - abstract a PWM controller
* @dev: device providing the PWMs
* @cdev: &struct cdev for this device
* @ops: callbacks for this PWM controller
* @owner: module providing this chip
* @id: unique number of this PWM chip
Expand All @@ -323,6 +325,7 @@ struct pwm_ops {
*/
struct pwm_chip {
struct device dev;
struct cdev cdev;
const struct pwm_ops *ops;
struct module *owner;
unsigned int id;
Expand Down
Loading

0 comments on commit 49dec64

Please sign in to comment.