diff options
author | Uwe Kleine-König <u.kleine-koenig@pengutronix.de> | 2024-03-17 11:40:39 +0100 |
---|---|---|
committer | Uwe Kleine-König <u.kleine-koenig@pengutronix.de> | 2024-04-22 12:42:25 +0200 |
commit | 24626c4e685b45614118144c65511aacde3b1599 (patch) | |
tree | 629e0a06ecbfb1a6bb7343f8a39e00dad1433eb1 | |
parent | 56c0b4a2183e4385b35dc064ec652b40df3bfe8f (diff) | |
download | renesas-drivers-24626c4e685b45614118144c65511aacde3b1599.tar.gz |
pwm: Add support for pwmchip devices for faster and easier userspace access
Notice: this object is not reachable from any branch.
With this change each pwmchip 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.
Link: https://lore.kernel.org/r/8d3acfc431ecd431d6cced032dcb58ad2579474c.1710670958.git.u.kleine-koenig@pengutronix.de
[ukleinek: Fix copy_(from|to)_user return code handling]
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Notice: this object is not reachable from any branch.
-rw-r--r-- | drivers/pwm/core.c | 314 | ||||
-rw-r--r-- | include/linux/pwm.h | 2 | ||||
-rw-r--r-- | include/uapi/linux/pwm.h | 23 |
3 files changed, 327 insertions, 12 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index d2e6154b10661e..1fd40fbafc047b 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -21,6 +21,8 @@ #include <dt-bindings/pwm/pwm.h> +#include <uapi/linux/pwm.h> + #define CREATE_TRACE_POINTS #include <trace/events/pwm.h> @@ -249,6 +251,25 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) } EXPORT_SYMBOL_GPL(pwm_apply_atomic); +static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + int ret = -EOPNOTSUPP; + + if (ops->get_state) { + pwmchip_lock(chip); + + ret = ops->get_state(chip, pwm, state); + + pwmchip_unlock(chip); + + trace_pwm_get(pwm, state, ret); + } + + return ret; +} + /** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device @@ -411,14 +432,7 @@ err_get_device: */ struct pwm_state state = { 0, }; - pwmchip_lock(chip); - - err = ops->get_state(chip, pwm, &state); - - pwmchip_unlock(chip); - - trace_pwm_get(pwm, &state, err); - + err = pwm_get_state_hw(pwm, &state); if (!err) pwm->state = state; @@ -1120,6 +1134,256 @@ static bool pwm_ops_check(const struct pwm_chip *chip) return true; } +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; + int ret; + + mutex_lock(&pwm_lock); + + if (!chip->operational) { + ret = -ENXIO; + goto out_unlock; + } + + cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL); + if (!cdata) { + ret = -ENOMEM; + goto out_unlock; + } + + cdata->chip = chip; + + file->private_data = cdata; + + ret = nonseekable_open(inode, file); + +out_unlock: + mutex_unlock(&pwm_lock); + + return ret; +} + +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 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; + + mutex_lock(&pwm_lock); + + if (!chip->operational) { + ret = -ENODEV; + goto out_unlock; + } + + switch (cmd) { + case PWM_IOCTL_GET_NUM_PWMS: + ret = chip->npwm; + break; + + case PWM_IOCTL_REQUEST: + { + unsigned int hwpwm; + + ret = get_user(hwpwm, (unsigned int __user *)arg); + if (ret) + goto out_unlock; + + ret = pwm_cdev_request(cdata, hwpwm); + } + break; + + case PWM_IOCTL_FREE: + { + unsigned int hwpwm; + + ret = get_user(hwpwm, (unsigned int __user *)arg); + if (ret) + goto out_unlock; + + ret = pwm_cdev_free(cdata, hwpwm); + } + break; + + case PWM_IOCTL_GET: + { + struct pwmchip_state cstate; + struct pwm_state state; + struct pwm_device *pwm; + + ret = copy_from_user(&cstate, (struct pwmchip_state __user *)arg, sizeof(cstate)); + if (ret) { + ret = -EFAULT; + goto out_unlock; + } + + ret = pwm_cdev_request(cdata, cstate.hwpwm); + if (ret) + goto out_unlock; + + pwm = cdata->pwm[cstate.hwpwm]; + + ret = pwm_get_state_hw(pwm, &state); + if (ret) + goto out_unlock; + + if (state.enabled) { + cstate.period = state.period; + if (state.polarity == PWM_POLARITY_NORMAL) { + cstate.duty_offset = 0; + cstate.duty_cycle = state.duty_cycle; + } else { + cstate.duty_offset = state.duty_cycle; + cstate.duty_cycle = state.period - state.duty_cycle; + } + } else { + cstate.period = 0; + cstate.duty_cycle = 0; + cstate.duty_offset = 0; + } + ret = copy_to_user((struct pwmchip_state __user *)arg, &cstate, sizeof(cstate)); + if (ret) + ret = -EFAULT; + } + break; + + case PWM_IOCTL_APPLY: + { + struct pwmchip_state cstate; + struct pwm_state state = { }; + struct pwm_device *pwm; + + ret = copy_from_user(&cstate, (struct pwmchip_state __user *)arg, sizeof(cstate)); + if (ret) { + ret = -EFAULT; + goto out_unlock; + } + + if (cstate.period > 0 && + (cstate.duty_cycle > cstate.period || + cstate.duty_offset >= cstate.period)) { + ret = -EINVAL; + goto out_unlock; + } + + /* + * While the API provides a duty_offset member + * to describe (among others) also inversed + * polarity wave forms, the translation into the + * traditional representation with a (binary) polarity + * isn't trivial because the lowlevel drivers round + * duty_cycle down when applying a setting and so in the + * representation with duty_offset the rounding is + * inconsistent. I have no idea what's the best way to + * fix that, so to not commit to a solution yet, just + * refuse requests with .duty_offset that would yield + * inversed polarity for now. + */ + if (cstate.duty_cycle < cstate.period && + cstate.duty_offset + cstate.duty_cycle >= cstate.period) { + ret = -EINVAL; + goto out_unlock; + } + + ret = pwm_cdev_request(cdata, cstate.hwpwm); + if (ret) + goto out_unlock; + + pwm = cdata->pwm[cstate.hwpwm]; + + if (cstate.period > 0) { + state.enabled = true; + state.period = cstate.period; + state.polarity = PWM_POLARITY_NORMAL; + state.duty_cycle = cstate.duty_cycle; + } else { + state.enabled = false; + } + + ret = pwm_apply_might_sleep(pwm, &state); + } + break; + + default: + ret = -ENOTTY; + break; + } +out_unlock: + mutex_unlock(&pwm_lock); + + return ret; +} + +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 @@ -1173,7 +1437,13 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) chip->operational = true; pwmchip_unlock(chip); - ret = device_add(&chip->dev); + if (chip->id < 256) + 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; @@ -1232,7 +1502,7 @@ void pwmchip_remove(struct pwm_chip *chip) mutex_unlock(&pwm_lock); - device_del(&chip->dev); + cdev_device_del(&chip->cdev, &chip->dev); } EXPORT_SYMBOL_GPL(pwmchip_remove); @@ -1785,9 +2055,29 @@ DEFINE_SEQ_ATTRIBUTE(pwm_debugfs); static int __init pwm_init(void) { + struct dentry *pwm_debugfs = NULL; + int ret; + if (IS_ENABLED(CONFIG_DEBUG_FS)) - debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops); + pwm_debugfs = debugfs_create_file("pwm", 0444, NULL, NULL, + &pwm_debugfs_fops); + + ret = alloc_chrdev_region(&pwm_devt, 0, 256, "pwm"); + if (ret) { + pr_warn("Failed to initialize chrdev region for PWM usage\n"); + goto err_alloc_chrdev; + } + + ret = class_register(&pwm_class); + if (ret) { + pr_warn("Failed to initialize PWM class\n"); - return class_register(&pwm_class); + unregister_chrdev_region(pwm_devt, 256); +err_alloc_chrdev: + + debugfs_remove(pwm_debugfs); + } + + return ret; } subsys_initcall(pwm_init); diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 9c84e0ba81a4f4..d5fed23b48f0f4 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -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/mutex.h> @@ -281,6 +282,7 @@ struct pwm_ops { */ struct pwm_chip { struct device dev; + struct cdev cdev; const struct pwm_ops *ops; struct module *owner; unsigned int id; diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h new file mode 100644 index 00000000000000..ca765bfaa68dd8 --- /dev/null +++ b/include/uapi/linux/pwm.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ + +#ifndef _UAPI_PWM_H_ +#define _UAPI_PWM_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +struct pwmchip_state { + unsigned int hwpwm; + __u64 period; + __u64 duty_cycle; + __u64 duty_offset; +}; + +#define PWM_IOCTL_GET_NUM_PWMS _IO(0x75, 0) +#define PWM_IOCTL_REQUEST _IOW(0x75, 1, unsigned int) +#define PWM_IOCTL_FREE _IOW(0x75, 2, unsigned int) +/* reserve nr = 3 for rounding */ +#define PWM_IOCTL_GET _IOWR(0x75, 4, struct pwmchip_state) +#define PWM_IOCTL_APPLY _IOW(0x75, 5, struct pwmchip_state) + +#endif /* _UAPI_PWM_H_ */ |