diff options
Diffstat (limited to 'recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch')
-rw-r--r-- | recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch b/recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch new file mode 100644 index 0000000..fb4ee30 --- /dev/null +++ b/recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch @@ -0,0 +1,684 @@ +From xxxx Mon Sep 17 00:00:00 2001 +From: H Hartley Sweeten <hartleys@visionengravers.com> +Date: Tue, 11 Jun 2013 10:38:59 -0700 +Subject: [PATCH 04/21] pwm: Add sysfs interface + +Add a simple sysfs interface to the generic PWM framework. + + /sys/class/pwm/ + `-- pwmchipN/ for each PWM chip + |-- export (w/o) ask the kernel to export a PWM channel + |-- npwm (r/o) number of PWM channels in this PWM chip + |-- pwmX/ for each exported PWM channel + | |-- duty_cycle (r/w) duty cycle (in nanoseconds) + | |-- enable (r/w) enable/disable PWM + | |-- period (r/w) period (in nanoseconds) + | `-- polarity (r/w) polarity of PWM (normal/inversed) + `-- unexport (w/o) return a PWM channel to the kernel + +Based on work by Lars Poeschel. + +Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> +Cc: Thierry Reding <thierry.reding@gmail.com> +Cc: Lars Poeschel <poeschel@lemonage.de> +Cc: Ryan Mallon <rmallon@gmail.com> +Cc: Rob Landley <rob@landley.net> +Signed-off-by: Thierry Reding <thierry.reding@gmail.com> +(cherry picked from commit 76abbdde2d95a3807d0dc6bf9f84d03d0dbd4f3d) +--- + Documentation/ABI/testing/sysfs-class-pwm | 79 +++++++ + Documentation/pwm.txt | 37 +++ + drivers/pwm/Kconfig | 4 + + drivers/pwm/Makefile | 1 + + drivers/pwm/core.c | 25 ++- + drivers/pwm/sysfs.c | 352 +++++++++++++++++++++++++++++ + include/linux/pwm.h | 29 +++- + 7 files changed, 524 insertions(+), 3 deletions(-) + create mode 100644 Documentation/ABI/testing/sysfs-class-pwm + create mode 100644 drivers/pwm/sysfs.c + +diff --git a/Documentation/ABI/testing/sysfs-class-pwm b/Documentation/ABI/testing/sysfs-class-pwm +new file mode 100644 +index 0000000..c479d77 +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-class-pwm +@@ -0,0 +1,79 @@ ++What: /sys/class/pwm/ ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ The pwm/ class sub-directory belongs to the Generic PWM ++ Framework and provides a sysfs interface for using PWM ++ channels. ++ ++What: /sys/class/pwm/pwmchipN/ ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ A /sys/class/pwm/pwmchipN directory is created for each ++ probed PWM controller/chip where N is the base of the ++ PWM chip. ++ ++What: /sys/class/pwm/pwmchipN/npwm ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ The number of PWM channels supported by the PWM chip. ++ ++What: /sys/class/pwm/pwmchipN/export ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Exports a PWM channel from the PWM chip for sysfs control. ++ Value is between 0 and /sys/class/pwm/pwmchipN/npwm - 1. ++ ++What: /sys/class/pwm/pwmchipN/unexport ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Unexports a PWM channel. ++ ++What: /sys/class/pwm/pwmchipN/pwmX ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ A /sys/class/pwm/pwmchipN/pwmX directory is created for ++ each exported PWM channel where X is the exported PWM ++ channel number. ++ ++What: /sys/class/pwm/pwmchipN/pwmX/period ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Sets the PWM signal period in nanoseconds. ++ ++What: /sys/class/pwm/pwmchipN/pwmX/duty_cycle ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Sets the PWM signal duty cycle in nanoseconds. ++ ++What: /sys/class/pwm/pwmchipN/pwmX/polarity ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Sets the output polarity of the PWM signal to "normal" or ++ "inversed". ++ ++What: /sys/class/pwm/pwmchipN/pwmX/enable ++Date: May 2013 ++KernelVersion: 3.11 ++Contact: H Hartley Sweeten <hsweeten@visionengravers.com> ++Description: ++ Enable/disable the PWM signal. ++ 0 is disabled ++ 1 is enabled +diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt +index 7d2b4c9..1039b68 100644 +--- a/Documentation/pwm.txt ++++ b/Documentation/pwm.txt +@@ -45,6 +45,43 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); + + To start/stop toggling the PWM output use pwm_enable()/pwm_disable(). + ++Using PWMs with the sysfs interface ++----------------------------------- ++ ++If CONFIG_SYSFS is enabled in your kernel configuration a simple sysfs ++interface is provided to use the PWMs from userspace. It is exposed at ++/sys/class/pwm/. Each probed PWM controller/chip will be exported as ++pwmchipN, where N is the base of the PWM chip. Inside the directory you ++will find: ++ ++npwm - The number of PWM channels this chip supports (read-only). ++ ++export - Exports a PWM channel for use with sysfs (write-only). ++ ++unexport - Unexports a PWM channel from sysfs (write-only). ++ ++The PWM channels are numbered using a per-chip index from 0 to npwm-1. ++ ++When a PWM channel is exported a pwmX directory will be created in the ++pwmchipN directory it is associated with, where X is the number of the ++channel that was exported. The following properties will then be available: ++ ++period - The total period of the PWM signal (read/write). ++ Value is in nanoseconds and is the sum of the active and inactive ++ time of the PWM. ++ ++duty_cycle - The active time of the PWM signal (read/write). ++ Value is in nanoseconds and must be less than the period. ++ ++polarity - Changes the polarity of the PWM signal (read/write). ++ Writes to this property only work if the PWM chip supports changing ++ the polarity. The polarity can only be changed if the PWM is not ++ enabled. Value is the string "normal" or "inversed". ++ ++enable - Enable/disable the PWM signal (read/write). ++ 0 - disabled ++ 1 - enabled ++ + Implementing a PWM driver + ------------------------- + +diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig +index e513cd9..03120a8 100644 +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -28,6 +28,10 @@ menuconfig PWM + + if PWM + ++config PWM_SYSFS ++ bool ++ default y if SYSFS ++ + config PWM_AB8500 + tristate "AB8500 PWM support" + depends on AB8500_CORE && ARCH_U8500 +diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile +index 62a2963..f98371b 100644 +--- a/drivers/pwm/Makefile ++++ b/drivers/pwm/Makefile +@@ -1,4 +1,5 @@ + obj-$(CONFIG_PWM) += core.o ++obj-$(CONFIG_PWM_SYSFS) += sysfs.o + obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o + obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o + obj-$(CONFIG_PWM_IMX) += pwm-imx.o +diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c +index 903138b..bfc8c05 100644 +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -272,6 +272,8 @@ int pwmchip_add(struct pwm_chip *chip) + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_add(chip); + ++ pwmchip_sysfs_export(chip); ++ + out: + mutex_unlock(&pwm_lock); + return ret; +@@ -308,6 +310,8 @@ int pwmchip_remove(struct pwm_chip *chip) + + free_pwms(chip); + ++ pwmchip_sysfs_unexport(chip); ++ + out: + mutex_unlock(&pwm_lock); + return ret; +@@ -400,10 +404,19 @@ EXPORT_SYMBOL_GPL(pwm_free); + */ + int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) + { ++ int err; ++ + if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) + return -EINVAL; + +- return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); ++ err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); ++ if (err) ++ return err; ++ ++ pwm->duty_cycle = duty_ns; ++ pwm->period = period_ns; ++ ++ return 0; + } + EXPORT_SYMBOL_GPL(pwm_config); + +@@ -416,6 +429,8 @@ EXPORT_SYMBOL_GPL(pwm_config); + */ + int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) + { ++ int err; ++ + if (!pwm || !pwm->chip->ops) + return -EINVAL; + +@@ -425,7 +440,13 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) + if (test_bit(PWMF_ENABLED, &pwm->flags)) + return -EBUSY; + +- return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); ++ err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); ++ if (err) ++ return err; ++ ++ pwm->polarity = polarity; ++ ++ return 0; + } + EXPORT_SYMBOL_GPL(pwm_set_polarity); + +diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c +new file mode 100644 +index 0000000..8ca5de3 +--- /dev/null ++++ b/drivers/pwm/sysfs.c +@@ -0,0 +1,352 @@ ++/* ++ * A simple sysfs interface for the generic PWM framework ++ * ++ * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com> ++ * ++ * Based on previous work by Lars Poeschel <poeschel@lemonage.de> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/device.h> ++#include <linux/mutex.h> ++#include <linux/err.h> ++#include <linux/slab.h> ++#include <linux/kdev_t.h> ++#include <linux/pwm.h> ++ ++struct pwm_export { ++ struct device child; ++ struct pwm_device *pwm; ++}; ++ ++static struct pwm_export *child_to_pwm_export(struct device *child) ++{ ++ return container_of(child, struct pwm_export, child); ++} ++ ++static struct pwm_device *child_to_pwm_device(struct device *child) ++{ ++ struct pwm_export *export = child_to_pwm_export(child); ++ ++ return export->pwm; ++} ++ ++static ssize_t pwm_period_show(struct device *child, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ const struct pwm_device *pwm = child_to_pwm_device(child); ++ ++ return sprintf(buf, "%u\n", pwm->period); ++} ++ ++static ssize_t pwm_period_store(struct device *child, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct pwm_device *pwm = child_to_pwm_device(child); ++ unsigned int val; ++ int ret; ++ ++ ret = kstrtouint(buf, 0, &val); ++ if (ret) ++ return ret; ++ ++ ret = pwm_config(pwm, pwm->duty_cycle, val); ++ ++ return ret ? : size; ++} ++ ++static ssize_t pwm_duty_cycle_show(struct device *child, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ const struct pwm_device *pwm = child_to_pwm_device(child); ++ ++ return sprintf(buf, "%u\n", pwm->duty_cycle); ++} ++ ++static ssize_t pwm_duty_cycle_store(struct device *child, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct pwm_device *pwm = child_to_pwm_device(child); ++ unsigned int val; ++ int ret; ++ ++ ret = kstrtouint(buf, 0, &val); ++ if (ret) ++ return ret; ++ ++ ret = pwm_config(pwm, val, pwm->period); ++ ++ return ret ? : size; ++} ++ ++static ssize_t pwm_enable_show(struct device *child, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ const struct pwm_device *pwm = child_to_pwm_device(child); ++ int enabled = test_bit(PWMF_ENABLED, &pwm->flags); ++ ++ return sprintf(buf, "%d\n", enabled); ++} ++ ++static ssize_t pwm_enable_store(struct device *child, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct pwm_device *pwm = child_to_pwm_device(child); ++ int val, ret; ++ ++ ret = kstrtoint(buf, 0, &val); ++ if (ret) ++ return ret; ++ ++ switch (val) { ++ case 0: ++ pwm_disable(pwm); ++ break; ++ case 1: ++ ret = pwm_enable(pwm); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret ? : size; ++} ++ ++static ssize_t pwm_polarity_show(struct device *child, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ const struct pwm_device *pwm = child_to_pwm_device(child); ++ ++ return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal"); ++} ++ ++static ssize_t pwm_polarity_store(struct device *child, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct pwm_device *pwm = child_to_pwm_device(child); ++ enum pwm_polarity polarity; ++ int ret; ++ ++ if (sysfs_streq(buf, "normal")) ++ polarity = PWM_POLARITY_NORMAL; ++ else if (sysfs_streq(buf, "inversed")) ++ polarity = PWM_POLARITY_INVERSED; ++ else ++ return -EINVAL; ++ ++ ret = pwm_set_polarity(pwm, polarity); ++ ++ return ret ? : size; ++} ++ ++static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store); ++static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store); ++static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store); ++static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); ++ ++static struct attribute *pwm_attrs[] = { ++ &dev_attr_period.attr, ++ &dev_attr_duty_cycle.attr, ++ &dev_attr_enable.attr, ++ &dev_attr_polarity.attr, ++ NULL ++}; ++ ++static const struct attribute_group pwm_attr_group = { ++ .attrs = pwm_attrs, ++}; ++ ++static const struct attribute_group *pwm_attr_groups[] = { ++ &pwm_attr_group, ++ NULL, ++}; ++ ++static void pwm_export_release(struct device *child) ++{ ++ struct pwm_export *export = child_to_pwm_export(child); ++ ++ kfree(export); ++} ++ ++static int pwm_export_child(struct device *parent, struct pwm_device *pwm) ++{ ++ struct pwm_export *export; ++ 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; ++ ++ export->child.release = pwm_export_release; ++ export->child.parent = parent; ++ export->child.devt = MKDEV(0, 0); ++ export->child.groups = pwm_attr_groups; ++ dev_set_name(&export->child, "pwm%u", pwm->hwpwm); ++ ++ ret = device_register(&export->child); ++ if (ret) { ++ clear_bit(PWMF_EXPORTED, &pwm->flags); ++ kfree(export); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int pwm_unexport_match(struct device *child, void *data) ++{ ++ return child_to_pwm_device(child) == data; ++} ++ ++static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm) ++{ ++ struct device *child; ++ ++ if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) ++ return -ENODEV; ++ ++ child = device_find_child(parent, pwm, pwm_unexport_match); ++ if (!child) ++ return -ENODEV; ++ ++ /* for device_find_child() */ ++ put_device(child); ++ device_unregister(child); ++ pwm_put(pwm); ++ ++ return 0; ++} ++ ++static ssize_t pwm_export_store(struct device *parent, ++ struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ struct pwm_chip *chip = dev_get_drvdata(parent); ++ 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(parent, pwm); ++ if (ret < 0) ++ pwm_put(pwm); ++ ++ return ret ? : len; ++} ++ ++static ssize_t pwm_unexport_store(struct device *parent, ++ struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ struct pwm_chip *chip = dev_get_drvdata(parent); ++ 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(parent, &chip->pwms[hwpwm]); ++ ++ return ret ? : len; ++} ++ ++static ssize_t pwm_npwm_show(struct device *parent, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ const struct pwm_chip *chip = dev_get_drvdata(parent); ++ ++ return sprintf(buf, "%u\n", chip->npwm); ++} ++ ++static struct device_attribute pwm_chip_attrs[] = { ++ __ATTR(export, 0200, NULL, pwm_export_store), ++ __ATTR(unexport, 0200, NULL, pwm_unexport_store), ++ __ATTR(npwm, 0444, pwm_npwm_show, NULL), ++ __ATTR_NULL, ++}; ++ ++static struct class pwm_class = { ++ .name = "pwm", ++ .owner = THIS_MODULE, ++ .dev_attrs = pwm_chip_attrs, ++}; ++ ++static int pwmchip_sysfs_match(struct device *parent, const void *data) ++{ ++ return dev_get_drvdata(parent) == data; ++} ++ ++void pwmchip_sysfs_export(struct pwm_chip *chip) ++{ ++ struct device *parent; ++ ++ /* ++ * If device_create() fails the pwm_chip is still usable by ++ * the kernel its just not exported. ++ */ ++ parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, ++ "pwmchip%d", chip->base); ++ if (IS_ERR(parent)) { ++ dev_warn(chip->dev, ++ "device_create failed for pwm_chip sysfs export\n"); ++ } ++} ++ ++void pwmchip_sysfs_unexport(struct pwm_chip *chip) ++{ ++ struct device *parent; ++ ++ parent = class_find_device(&pwm_class, NULL, chip, ++ pwmchip_sysfs_match); ++ if (parent) { ++ /* for class_find_device() */ ++ put_device(parent); ++ device_unregister(parent); ++ } ++} ++ ++static int __init pwm_sysfs_init(void) ++{ ++ return class_register(&pwm_class); ++} ++subsys_initcall(pwm_sysfs_init); +diff --git a/include/linux/pwm.h b/include/linux/pwm.h +index 6d661f3..6842d11 100644 +--- a/include/linux/pwm.h ++++ b/include/linux/pwm.h +@@ -76,6 +76,7 @@ enum pwm_polarity { + enum { + PWMF_REQUESTED = 1 << 0, + PWMF_ENABLED = 1 << 1, ++ PWMF_EXPORTED = 1 << 2, + }; + + struct pwm_device { +@@ -86,7 +87,9 @@ struct pwm_device { + struct pwm_chip *chip; + void *chip_data; + +- unsigned int period; /* in nanoseconds */ ++ unsigned int period; /* in nanoseconds */ ++ unsigned int duty_cycle; /* in nanoseconds */ ++ enum pwm_polarity polarity; + }; + + static inline void pwm_set_period(struct pwm_device *pwm, unsigned int period) +@@ -100,6 +103,17 @@ static inline unsigned int pwm_get_period(struct pwm_device *pwm) + return pwm ? pwm->period : 0; + } + ++static inline void pwm_set_duty_cycle(struct pwm_device *pwm, unsigned int duty) ++{ ++ if (pwm) ++ pwm->duty_cycle = duty; ++} ++ ++static inline unsigned int pwm_get_duty_cycle(struct pwm_device *pwm) ++{ ++ return pwm ? pwm->duty_cycle : 0; ++} ++ + /* + * pwm_set_polarity - configure the polarity of a PWM signal + */ +@@ -252,4 +266,17 @@ static inline void pwm_add_table(struct pwm_lookup *table, size_t num) + } + #endif + ++#ifdef CONFIG_PWM_SYSFS ++void pwmchip_sysfs_export(struct pwm_chip *chip); ++void pwmchip_sysfs_unexport(struct pwm_chip *chip); ++#else ++static inline void pwmchip_sysfs_export(struct pwm_chip *chip) ++{ ++} ++ ++static inline void pwmchip_sysfs_unexport(struct pwm_chip *chip) ++{ ++} ++#endif /* CONFIG_PWM_SYSFS */ ++ + #endif /* __LINUX_PWM_H */ +-- +1.7.4.1 + |