Commit 76abbdde authored by H Hartley Sweeten's avatar H Hartley Sweeten Committed by Thierry Reding

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: default avatarH 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: default avatarThierry Reding <thierry.reding@gmail.com>
parent 3dd0a909
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
...@@ -45,6 +45,43 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); ...@@ -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(). 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 Implementing a PWM driver
------------------------- -------------------------
......
...@@ -28,6 +28,10 @@ menuconfig PWM ...@@ -28,6 +28,10 @@ menuconfig PWM
if PWM if PWM
config PWM_SYSFS
bool
default y if SYSFS
config PWM_AB8500 config PWM_AB8500
tristate "AB8500 PWM support" tristate "AB8500 PWM support"
depends on AB8500_CORE && ARCH_U8500 depends on AB8500_CORE && ARCH_U8500
......
obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM) += core.o
obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
......
...@@ -274,6 +274,8 @@ int pwmchip_add(struct pwm_chip *chip) ...@@ -274,6 +274,8 @@ int pwmchip_add(struct pwm_chip *chip)
if (IS_ENABLED(CONFIG_OF)) if (IS_ENABLED(CONFIG_OF))
of_pwmchip_add(chip); of_pwmchip_add(chip);
pwmchip_sysfs_export(chip);
out: out:
mutex_unlock(&pwm_lock); mutex_unlock(&pwm_lock);
return ret; return ret;
...@@ -310,6 +312,8 @@ int pwmchip_remove(struct pwm_chip *chip) ...@@ -310,6 +312,8 @@ int pwmchip_remove(struct pwm_chip *chip)
free_pwms(chip); free_pwms(chip);
pwmchip_sysfs_unexport(chip);
out: out:
mutex_unlock(&pwm_lock); mutex_unlock(&pwm_lock);
return ret; return ret;
...@@ -402,10 +406,19 @@ EXPORT_SYMBOL_GPL(pwm_free); ...@@ -402,10 +406,19 @@ EXPORT_SYMBOL_GPL(pwm_free);
*/ */
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 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) if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
return -EINVAL; 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); EXPORT_SYMBOL_GPL(pwm_config);
...@@ -418,6 +431,8 @@ EXPORT_SYMBOL_GPL(pwm_config); ...@@ -418,6 +431,8 @@ EXPORT_SYMBOL_GPL(pwm_config);
*/ */
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
{ {
int err;
if (!pwm || !pwm->chip->ops) if (!pwm || !pwm->chip->ops)
return -EINVAL; return -EINVAL;
...@@ -427,7 +442,13 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) ...@@ -427,7 +442,13 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
if (test_bit(PWMF_ENABLED, &pwm->flags)) if (test_bit(PWMF_ENABLED, &pwm->flags))
return -EBUSY; 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); EXPORT_SYMBOL_GPL(pwm_set_polarity);
......
/*
* 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);
...@@ -76,6 +76,7 @@ enum pwm_polarity { ...@@ -76,6 +76,7 @@ enum pwm_polarity {
enum { enum {
PWMF_REQUESTED = 1 << 0, PWMF_REQUESTED = 1 << 0,
PWMF_ENABLED = 1 << 1, PWMF_ENABLED = 1 << 1,
PWMF_EXPORTED = 1 << 2,
}; };
struct pwm_device { struct pwm_device {
...@@ -86,7 +87,9 @@ struct pwm_device { ...@@ -86,7 +87,9 @@ struct pwm_device {
struct pwm_chip *chip; struct pwm_chip *chip;
void *chip_data; 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) 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) ...@@ -100,6 +103,17 @@ static inline unsigned int pwm_get_period(struct pwm_device *pwm)
return pwm ? pwm->period : 0; 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 * pwm_set_polarity - configure the polarity of a PWM signal
*/ */
...@@ -278,4 +292,17 @@ static inline void pwm_add_table(struct pwm_lookup *table, size_t num) ...@@ -278,4 +292,17 @@ static inline void pwm_add_table(struct pwm_lookup *table, size_t num)
} }
#endif #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 */ #endif /* __LINUX_PWM_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment