@@ -21,6 +21,8 @@
#include <dt-bindings/pwm/pwm.h>
+#include <uapi/linux/pwm.h>
+
#define CREATE_TRACE_POINTS
#include <trace/events/pwm.h>
@@ -135,21 +137,13 @@ static void pwm_apply_debug(struct pwm_device *pwm,
}
}
-/**
- * __pwm_apply() - atomically apply a new state to a PWM device
- * @pwm: PWM device
- * @state: new state to apply
- */
-static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+static int __pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
- struct pwm_chip *chip;
int err;
- if (!pwm || !state || !state->period ||
- state->duty_cycle > state->period)
- return -EINVAL;
-
- chip = pwm->chip;
+ if (!chip->operational)
+ return -ENXIO;
if (state->period == pwm->state.period &&
state->duty_cycle == pwm->state.duty_cycle &&
@@ -174,6 +168,27 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
return 0;
}
+/**
+ * __pwm_apply() - atomically apply a new state to a PWM device
+ * @pwm: PWM device
+ * @state: new state to apply
+ */
+static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+{
+ struct pwm_chip *chip;
+ int err = 0;
+
+ if (!pwm || !state || !state->period ||
+ state->duty_cycle > state->period)
+ return -EINVAL;
+
+ chip = pwm->chip;
+
+ err = __pwm_apply_locked(chip, pwm, state);
+
+ return err;
+}
+
/**
* pwm_apply_might_sleep() - atomically apply a new state to a PWM device
* Cannot be used in atomic context.
@@ -409,7 +424,6 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
}
EXPORT_SYMBOL_GPL(pwm_request_from_chip);
-
struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args)
{
@@ -556,6 +570,187 @@ 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;
+
+ pr_info("%s:%d\n", __func__, __LINE__);
+ 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 = -ENXIO;
+ 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_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)
+ goto out_unlock;
+
+ ret = pwm_cdev_request(cdata, cstate.hwpwm);
+ if (ret)
+ goto out_unlock;
+
+ pwm = cdata->pwm[cstate.hwpwm];
+
+ state.period = cstate.period;
+ state.duty_cycle = cstate.duty_cycle;
+ if (cstate.duty_offset >= cstate.period - cstate.duty_cycle)
+ state.polarity = PWM_POLARITY_INVERSED;
+ else
+ state.polarity = PWM_POLARITY_NORMAL;
+ state.enabled = cstate.period > 0;
+
+ ret = __pwm_apply_locked(chip, 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,
+};
+
+extern dev_t pwm_devt;
+
/**
* __pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
@@ -602,7 +797,13 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_add(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;
@@ -646,7 +847,7 @@ void pwmchip_remove(struct pwm_chip *chip)
module_put(chip->owner);
- device_del(&chip->dev);
+ cdev_device_del(&chip->cdev, &chip->dev);
}
EXPORT_SYMBOL_GPL(pwmchip_remove);
@@ -8,6 +8,7 @@
*/
#include <linux/device.h>
+#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
@@ -509,8 +510,24 @@ void pwmchip_sysfs_unexport(struct pwm_chip *chip)
}
}
+dev_t pwm_devt;
+
static int __init pwm_sysfs_init(void)
{
- return class_register(&pwm_class);
+ 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_warn("Failed to register PWM class\n");
+ unregister_chrdev_region(pwm_devt, 256);
+ }
+
+ return ret;
}
subsys_initcall(pwm_sysfs_init);
@@ -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>
@@ -278,6 +279,7 @@ struct pwm_ops {
*/
struct pwm_chip {
struct device dev;
+ struct cdev cdev;
const struct pwm_ops *ops;
struct module *owner;
unsigned int id;
@@ -288,6 +290,7 @@ struct pwm_chip {
bool atomic;
/* only used internally by the PWM framework */
+ bool operational;
bool uses_pwmchip_alloc;
struct pwm_device pwms[] __counted_by(npwm);
};
new file mode 100644
@@ -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 {
+ /* Make alignment arch-independant */
+ 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)
+#define PWM_IOCTL_APPLY _IOW(0x75, 3, struct pwmchip_state)
+#define PWM_IOCTL_GET _IOWR(0x75, 4, struct pwmchip_state)
+
+#endif /* _UAPI_PWM_H_ */