aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm/core.c')
-rw-r--r--drivers/pwm/core.c994
1 files changed, 954 insertions, 40 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 403525cc17833c..2745941a008b40 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>
@@ -29,6 +31,22 @@ static DEFINE_MUTEX(pwm_lock);
static DEFINE_IDR(pwm_chips);
+static void pwmchip_lock(struct pwm_chip *chip)
+{
+ if (chip->atomic)
+ spin_lock(&chip->atomic_lock);
+ else
+ mutex_lock(&chip->nonatomic_lock);
+}
+
+static void pwmchip_unlock(struct pwm_chip *chip)
+{
+ if (chip->atomic)
+ spin_unlock(&chip->atomic_lock);
+ else
+ mutex_unlock(&chip->nonatomic_lock);
+}
+
static void pwm_apply_debug(struct pwm_device *pwm,
const struct pwm_state *state)
{
@@ -183,6 +201,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
{
int err;
+ struct pwm_chip *chip = pwm->chip;
/*
* Some lowlevel driver's implementations of .apply() make use of
@@ -193,7 +212,13 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
*/
might_sleep();
- if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) {
+ pwmchip_lock(chip);
+ if (!chip->operational) {
+ pwmchip_unlock(chip);
+ return -ENODEV;
+ }
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
/*
* Catch any drivers that have been marked as atomic but
* that will sleep anyway.
@@ -205,6 +230,8 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
err = __pwm_apply(pwm, state);
}
+ pwmchip_unlock(chip);
+
return err;
}
EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
@@ -224,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
@@ -291,16 +337,26 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config);
int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
unsigned long timeout)
{
+ struct pwm_chip *chip;
int err;
- if (!pwm || !pwm->chip->ops)
+ if (!pwm || !(chip = pwm->chip)->ops)
return -EINVAL;
- if (!pwm->chip->ops->capture)
+ if (!chip->ops->capture)
return -ENOSYS;
mutex_lock(&pwm_lock);
- err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout);
+
+ pwmchip_lock(chip);
+
+ if (chip->operational)
+ err = chip->ops->capture(pwm->chip, pwm, result, timeout);
+ else
+ err = -ENODEV;
+
+ pwmchip_unlock(chip);
+
mutex_unlock(&pwm_lock);
return err;
@@ -340,12 +396,27 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
if (test_bit(PWMF_REQUESTED, &pwm->flags))
return -EBUSY;
+ /*
+ * This function is called while holding pwm_lock. As .operational only
+ * changes while holding this lock, checking it here without holding the
+ * chip lock is fine.
+ */
+ if (!chip->operational)
+ return -ENODEV;
+
if (!try_module_get(chip->owner))
return -ENODEV;
+ if (!get_device(&chip->dev)) {
+ err = -ENODEV;
+ goto err_get_device;
+ }
+
if (ops->request) {
err = ops->request(chip, pwm);
if (err) {
+ put_device(&chip->dev);
+err_get_device:
module_put(chip->owner);
return err;
}
@@ -361,9 +432,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
*/
struct pwm_state state = { 0, };
- err = ops->get_state(chip, pwm, &state);
- trace_pwm_get(pwm, &state, err);
-
+ err = pwm_get_state_hw(pwm, &state);
if (!err)
pwm->state = state;
@@ -454,36 +523,558 @@ of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
}
EXPORT_SYMBOL_GPL(of_pwm_single_xlate);
+struct pwm_export {
+ struct device pwm_dev;
+ struct pwm_device *pwm;
+ struct mutex lock;
+ struct pwm_state suspend;
+};
+
+static inline struct pwm_chip *pwmchip_from_dev(struct device *pwmchip_dev)
+{
+ return container_of(pwmchip_dev, struct pwm_chip, dev);
+}
+
+static inline struct pwm_export *pwmexport_from_dev(struct device *pwm_dev)
+{
+ return container_of(pwm_dev, struct pwm_export, pwm_dev);
+}
+
+static inline struct pwm_device *pwm_from_dev(struct device *pwm_dev)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+
+ return export->pwm;
+}
+
+static ssize_t period_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%llu\n", state.period);
+}
+
+static ssize_t period_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ u64 val;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.period = val;
+ ret = pwm_apply_might_sleep(pwm, &state);
+ mutex_unlock(&export->lock);
+
+ return ret ? : size;
+}
+
+static ssize_t duty_cycle_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%llu\n", state.duty_cycle);
+}
+
+static ssize_t duty_cycle_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ u64 val;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.duty_cycle = val;
+ ret = pwm_apply_might_sleep(pwm, &state);
+ mutex_unlock(&export->lock);
+
+ return ret ? : size;
+}
+
+static ssize_t enable_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%d\n", state.enabled);
+}
+
+static ssize_t enable_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ int val, ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ mutex_lock(&export->lock);
+
+ pwm_get_state(pwm, &state);
+
+ switch (val) {
+ case 0:
+ state.enabled = false;
+ break;
+ case 1:
+ state.enabled = true;
+ break;
+ default:
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = pwm_apply_might_sleep(pwm, &state);
+
+unlock:
+ mutex_unlock(&export->lock);
+ return ret ? : size;
+}
+
+static ssize_t polarity_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ const char *polarity = "unknown";
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ switch (state.polarity) {
+ case PWM_POLARITY_NORMAL:
+ polarity = "normal";
+ break;
+
+ case PWM_POLARITY_INVERSED:
+ polarity = "inversed";
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", polarity);
+}
+
+static ssize_t polarity_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ enum pwm_polarity polarity;
+ struct pwm_state state;
+ int ret;
+
+ if (sysfs_streq(buf, "normal"))
+ polarity = PWM_POLARITY_NORMAL;
+ else if (sysfs_streq(buf, "inversed"))
+ polarity = PWM_POLARITY_INVERSED;
+ else
+ return -EINVAL;
+
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.polarity = polarity;
+ ret = pwm_apply_might_sleep(pwm, &state);
+ mutex_unlock(&export->lock);
+
+ return ret ? : size;
+}
+
+static ssize_t capture_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_capture result;
+ int ret;
+
+ ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ));
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u %u\n", result.period, result.duty_cycle);
+}
+
+static DEVICE_ATTR_RW(period);
+static DEVICE_ATTR_RW(duty_cycle);
+static DEVICE_ATTR_RW(enable);
+static DEVICE_ATTR_RW(polarity);
+static DEVICE_ATTR_RO(capture);
+
+static struct attribute *pwm_attrs[] = {
+ &dev_attr_period.attr,
+ &dev_attr_duty_cycle.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_polarity.attr,
+ &dev_attr_capture.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pwm);
+
+static void pwm_export_release(struct device *pwm_dev)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+
+ kfree(export);
+}
+
+static int pwm_export_child(struct device *pwmchip_dev, struct pwm_device *pwm)
+{
+ struct pwm_export *export;
+ char *pwm_prop[2];
+ int ret;
+
+ if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
+ return -EBUSY;
+
+ export = kzalloc(sizeof(*export), GFP_KERNEL);
+ if (!export) {
+ clear_bit(PWMF_EXPORTED, &pwm->flags);
+ return -ENOMEM;
+ }
+
+ export->pwm = pwm;
+ mutex_init(&export->lock);
+
+ export->pwm_dev.release = pwm_export_release;
+ export->pwm_dev.parent = pwmchip_dev;
+ export->pwm_dev.devt = MKDEV(0, 0);
+ export->pwm_dev.groups = pwm_groups;
+ dev_set_name(&export->pwm_dev, "pwm%u", pwm->hwpwm);
+
+ ret = device_register(&export->pwm_dev);
+ if (ret) {
+ clear_bit(PWMF_EXPORTED, &pwm->flags);
+ put_device(&export->pwm_dev);
+ export = NULL;
+ return ret;
+ }
+ pwm_prop[0] = kasprintf(GFP_KERNEL, "EXPORT=pwm%u", pwm->hwpwm);
+ pwm_prop[1] = NULL;
+ kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop);
+ kfree(pwm_prop[0]);
+
+ return 0;
+}
+
+static int pwm_unexport_match(struct device *pwm_dev, void *data)
+{
+ return pwm_from_dev(pwm_dev) == data;
+}
+
+static int pwm_unexport_child(struct device *pwmchip_dev, struct pwm_device *pwm)
+{
+ struct device *pwm_dev;
+ char *pwm_prop[2];
+
+ if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags))
+ return -ENODEV;
+
+ pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match);
+ if (!pwm_dev)
+ return -ENODEV;
+
+ pwm_prop[0] = kasprintf(GFP_KERNEL, "UNEXPORT=pwm%u", pwm->hwpwm);
+ pwm_prop[1] = NULL;
+ kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop);
+ kfree(pwm_prop[0]);
+
+ /* for device_find_child() */
+ put_device(pwm_dev);
+ device_unregister(pwm_dev);
+ pwm_put(pwm);
+
+ return 0;
+}
+
+static ssize_t export_store(struct device *pwmchip_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ struct pwm_device *pwm;
+ unsigned int hwpwm;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &hwpwm);
+ if (ret < 0)
+ return ret;
+
+ if (hwpwm >= chip->npwm)
+ return -ENODEV;
+
+ pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_export_child(pwmchip_dev, pwm);
+ if (ret < 0)
+ pwm_put(pwm);
+
+ return ret ? : len;
+}
+static DEVICE_ATTR_WO(export);
+
+static ssize_t unexport_store(struct device *pwmchip_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int hwpwm;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &hwpwm);
+ if (ret < 0)
+ return ret;
+
+ if (hwpwm >= chip->npwm)
+ return -ENODEV;
+
+ ret = pwm_unexport_child(pwmchip_dev, &chip->pwms[hwpwm]);
+
+ return ret ? : len;
+}
+static DEVICE_ATTR_WO(unexport);
+
+static ssize_t npwm_show(struct device *pwmchip_dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ return sysfs_emit(buf, "%u\n", chip->npwm);
+}
+static DEVICE_ATTR_RO(npwm);
+
+static struct attribute *pwm_chip_attrs[] = {
+ &dev_attr_export.attr,
+ &dev_attr_unexport.attr,
+ &dev_attr_npwm.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(pwm_chip);
+
+/* takes export->lock on success */
+static struct pwm_export *pwm_class_get_state(struct device *pwmchip_dev,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct device *pwm_dev;
+ struct pwm_export *export;
+
+ if (!test_bit(PWMF_EXPORTED, &pwm->flags))
+ return NULL;
+
+ pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match);
+ if (!pwm_dev)
+ return NULL;
+
+ export = pwmexport_from_dev(pwm_dev);
+ put_device(pwm_dev); /* for device_find_child() */
+
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, state);
+
+ return export;
+}
+
+static int pwm_class_apply_state(struct pwm_export *export,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ int ret = pwm_apply_might_sleep(pwm, state);
+
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+
+ return ret;
+}
+
+static int pwm_class_resume_npwm(struct device *pwmchip_dev, unsigned int npwm)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ struct pwm_state state;
+ struct pwm_export *export;
+
+ export = pwm_class_get_state(pwmchip_dev, pwm, &state);
+ if (!export)
+ continue;
+
+ /* If pwmchip was not enabled before suspend, do nothing. */
+ if (!export->suspend.enabled) {
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+ continue;
+ }
+
+ state.enabled = export->suspend.enabled;
+ ret = pwm_class_apply_state(export, pwm, &state);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int pwm_class_suspend(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ struct pwm_state state;
+ struct pwm_export *export;
+
+ export = pwm_class_get_state(pwmchip_dev, pwm, &state);
+ if (!export)
+ continue;
+
+ /*
+ * If pwmchip was not enabled before suspend, save
+ * state for resume time and do nothing else.
+ */
+ export->suspend = state;
+ if (!state.enabled) {
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+ continue;
+ }
+
+ state.enabled = false;
+ ret = pwm_class_apply_state(export, pwm, &state);
+ if (ret < 0) {
+ /*
+ * roll back the PWM devices that were disabled by
+ * this suspend function.
+ */
+ pwm_class_resume_npwm(pwmchip_dev, i);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int pwm_class_resume(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ return pwm_class_resume_npwm(pwmchip_dev, chip->npwm);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume);
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .dev_groups = pwm_chip_groups,
+ .pm = pm_sleep_ptr(&pwm_class_pm_ops),
+};
+
+static void pwmchip_sysfs_unexport(struct pwm_chip *chip)
+{
+ unsigned int i;
+
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+
+ if (test_bit(PWMF_EXPORTED, &pwm->flags))
+ pwm_unexport_child(&chip->dev, pwm);
+ }
+}
+
#define PWMCHIP_ALIGN ARCH_DMA_MINALIGN
static void *pwmchip_priv(struct pwm_chip *chip)
{
- return (void *)chip + ALIGN(sizeof(*chip), PWMCHIP_ALIGN);
+ return (void *)chip + ALIGN(struct_size(chip, pwms, chip->npwm), PWMCHIP_ALIGN);
}
/* This is the counterpart to pwmchip_alloc() */
void pwmchip_put(struct pwm_chip *chip)
{
- kfree(chip);
+ put_device(&chip->dev);
}
EXPORT_SYMBOL_GPL(pwmchip_put);
+static void pwmchip_release(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ kfree(chip);
+}
+
struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
{
struct pwm_chip *chip;
+ struct device *pwmchip_dev;
size_t alloc_size;
+ unsigned int i;
- alloc_size = size_add(ALIGN(sizeof(*chip), PWMCHIP_ALIGN), sizeof_priv);
+ alloc_size = size_add(ALIGN(struct_size(chip, pwms, npwm), PWMCHIP_ALIGN),
+ sizeof_priv);
chip = kzalloc(alloc_size, GFP_KERNEL);
if (!chip)
return ERR_PTR(-ENOMEM);
- chip->dev = parent;
chip->npwm = npwm;
+ chip->uses_pwmchip_alloc = true;
+ chip->operational = false;
+
+ pwmchip_dev = &chip->dev;
+ device_initialize(pwmchip_dev);
+ pwmchip_dev->class = &pwm_class;
+ pwmchip_dev->parent = parent;
+ pwmchip_dev->release = pwmchip_release;
pwmchip_set_drvdata(chip, pwmchip_priv(chip));
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ pwm->chip = chip;
+ pwm->hwpwm = i;
+ }
+
return chip;
}
EXPORT_SYMBOL_GPL(pwmchip_alloc);
@@ -543,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
@@ -555,47 +1396,75 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
*/
int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
{
- unsigned int i;
int ret;
if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm)
return -EINVAL;
+ /*
+ * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc,
+ * otherwise the embedded struct device might disappear too early
+ * resulting in memory corruption.
+ * Catch drivers that were not converted appropriately.
+ */
+ if (!chip->uses_pwmchip_alloc)
+ return -EINVAL;
+
if (!pwm_ops_check(chip))
return -EINVAL;
chip->owner = owner;
- chip->pwms = kcalloc(chip->npwm, sizeof(*chip->pwms), GFP_KERNEL);
- if (!chip->pwms)
- return -ENOMEM;
+ if (chip->atomic)
+ spin_lock_init(&chip->atomic_lock);
+ else
+ mutex_init(&chip->nonatomic_lock);
mutex_lock(&pwm_lock);
ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
- if (ret < 0) {
- mutex_unlock(&pwm_lock);
- kfree(chip->pwms);
- return ret;
- }
+ if (ret < 0)
+ goto err_idr_alloc;
chip->id = ret;
- for (i = 0; i < chip->npwm; i++) {
- struct pwm_device *pwm = &chip->pwms[i];
+ dev_set_name(&chip->dev, "pwmchip%u", chip->id);
- pwm->chip = chip;
- pwm->hwpwm = i;
- }
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_add(chip);
+
+ pwmchip_lock(chip);
+ chip->operational = true;
+ pwmchip_unlock(chip);
+
+ 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;
mutex_unlock(&pwm_lock);
+ return 0;
+
+err_device_add:
+ pwmchip_lock(chip);
+ chip->operational = false;
+ pwmchip_unlock(chip);
+
if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_add(chip);
+ of_pwmchip_remove(chip);
- pwmchip_sysfs_export(chip);
+ idr_remove(&pwm_chips, chip->id);
+err_idr_alloc:
- return 0;
+ mutex_unlock(&pwm_lock);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(__pwmchip_add);
@@ -608,17 +1477,32 @@ EXPORT_SYMBOL_GPL(__pwmchip_add);
void pwmchip_remove(struct pwm_chip *chip)
{
pwmchip_sysfs_unexport(chip);
+ unsigned int i;
+
+ mutex_lock(&pwm_lock);
+
+ pwmchip_lock(chip);
+ chip->operational = false;
+ pwmchip_unlock(chip);
+
+ for (i = 0; i < chip->npwm; ++i) {
+ struct pwm_device *pwm = &chip->pwms[i];
+
+ if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ dev_alert(&chip->dev, "Freeing requested PWM #%u\n", i);
+ if (pwm->chip->ops->free)
+ pwm->chip->ops->free(pwm->chip, pwm);
+ }
+ }
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_remove(chip);
- mutex_lock(&pwm_lock);
-
idr_remove(&pwm_chips, chip->id);
mutex_unlock(&pwm_lock);
- kfree(chip->pwms);
+ cdev_device_del(&chip->cdev, &chip->dev);
}
EXPORT_SYMBOL_GPL(pwmchip_remove);
@@ -988,22 +1872,33 @@ EXPORT_SYMBOL_GPL(pwm_get);
*/
void pwm_put(struct pwm_device *pwm)
{
+ struct pwm_chip *chip;
+
if (!pwm)
return;
+ chip = pwm->chip;
+
mutex_lock(&pwm_lock);
- if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ /*
+ * If the chip isn't operational, PWMF_REQUESTED was already cleared. So
+ * don't warn in this case. This can only happen if a consumer called
+ * pwm_put() twice.
+ */
+ if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warn("PWM device already freed\n");
goto out;
}
- if (pwm->chip->ops->free)
+ if (chip->operational && chip->ops->free)
pwm->chip->ops->free(pwm->chip, pwm);
pwm->label = NULL;
- module_put(pwm->chip->owner);
+ put_device(&chip->dev);
+
+ module_put(chip->owner);
out:
mutex_unlock(&pwm_lock);
}
@@ -1076,7 +1971,6 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
-#ifdef CONFIG_DEBUG_FS
static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
{
unsigned int i;
@@ -1161,11 +2055,31 @@ static const struct seq_operations pwm_debugfs_sops = {
DEFINE_SEQ_ATTRIBUTE(pwm_debugfs);
-static int __init pwm_debugfs_init(void)
+static int __init pwm_init(void)
{
- debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops);
+ struct dentry *pwm_debugfs = NULL;
+ int ret;
- return 0;
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ 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");
+
+ unregister_chrdev_region(pwm_devt, 256);
+err_alloc_chrdev:
+
+ debugfs_remove(pwm_debugfs);
+ }
+
+ return ret;
}
-subsys_initcall(pwm_debugfs_init);
-#endif /* CONFIG_DEBUG_FS */
+subsys_initcall(pwm_init);