aboutsummaryrefslogtreecommitdiffstats
path: root/recipes-kernel/linux/files/0004-pwm-Add-sysfs-interface-quark.patch
diff options
context:
space:
mode:
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.patch684
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
+