* [PATCH v7 0/4] pwm: userspace support
@ 2025-04-30 11:55 Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 1/4] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests Uwe Kleine-König
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 11:55 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
Hello,
after the feedback I got from David on v7[1] and some internal
discussion here comes a new version of the patch.
Apart from rebasing to a newer base (current pwm/for-next) the only
change in the relevant patch #4 about return values from the
PWM_IOCTL_SETEXACTWF ioctl. Instead of returning 1 if the request failed
to apply exactly, return -EDOM.
The earlier patches in this series prepare that and implement a
similar change in pwm_set_waveform_might_sleep() to simplify also other
users of this function. Patch #3 is only a documentation update that is
not strictly related to the userspace chardev, but reflects the changes
in patches #1 and #2.
Feedback welcome.
Best regards
Uwe
[1] https://lore.kernel.org/linux-pwm/20250416094316.2494767-2-u.kleine-koenig@baylibre.com
Uwe Kleine-König (4):
pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible
requests
pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after
rounding up
pwm: Formally describe the procedure used to pick a hardware waveform
setting
pwm: Add support for pwmchip devices for faster and easier userspace
access
drivers/pwm/core.c | 369 ++++++++++++++++++++++++++++++++++++---
include/linux/pwm.h | 3 +
include/uapi/linux/pwm.h | 53 ++++++
3 files changed, 404 insertions(+), 21 deletions(-)
create mode 100644 include/uapi/linux/pwm.h
base-commit: e373991eb9ff0a9617634017c7f19fd36ec4f208
--
2.47.2
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v7 1/4] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
@ 2025-04-30 11:55 ` Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 2/4] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after rounding up Uwe Kleine-König
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 11:55 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
Up to now pwm_set_waveform_might_sleep() returned 1 for exact requests
that couldn't be served exactly. In contrast to
pwm_round_waveform_might_sleep() and pwm_set_waveform_might_sleep() with
exact = false this is an error condition. So simplify handling for
callers of pwm_set_waveform_might_sleep() by returning -EDOM instead of
1 in this case.
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
---
drivers/pwm/core.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index e0a90c4cd723..28cb6ab0f62d 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -404,15 +404,16 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
* Typically a requested waveform cannot be implemented exactly, e.g. because
* you requested .period_length_ns = 100 ns, but the hardware can only set
* periods that are a multiple of 8.5 ns. With that hardware passing @exact =
- * true results in pwm_set_waveform_might_sleep() failing and returning 1. If
- * @exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger
- * than the requested value).
+ * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM.
+ * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not
+ * bigger than the requested value).
* Note that even with @exact = true, some rounding by less than 1 ns is
* possible/needed. In the above example requesting .period_length_ns = 94 and
* @exact = true, you get the hardware configured with period = 93.5 ns.
*
- * Returns: 0 on success, 1 if was rounded up (if !@exact) or no perfect match was
- * possible (if @exact), or a negative errno
+ * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting
+ * failed due to the exact waveform not being possible (if @exact), or a
+ * different negative errno on failure.
* Context: May sleep.
*/
int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
@@ -440,6 +441,16 @@ int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
err = __pwm_set_waveform(pwm, wf, exact);
}
+ /*
+ * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is
+ * only returned in exactly that case. Note that __pwm_set_waveform()
+ * should never return -EDOM which justifies the unlikely().
+ */
+ if (unlikely(err == -EDOM))
+ err = -EINVAL;
+ else if (exact && err == 1)
+ err = -EDOM;
+
return err;
}
EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep);
--
2.47.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 2/4] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after rounding up
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 1/4] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests Uwe Kleine-König
@ 2025-04-30 11:55 ` Uwe Kleine-König
2025-04-30 11:56 ` [PATCH v7 3/4] pwm: Formally describe the procedure used to pick a hardware waveform setting Uwe Kleine-König
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 11:55 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
While telling the caller of pwm_set_waveform_might_sleep() if the
request was completed by rounding down only or (some) rounding up gives
additional information, it makes usage this function needlessly hard and
the additional information is not used. A prove for that is that
currently both users of this function just pass the returned value up to
their caller even though a positive value isn't intended there.
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
---
drivers/pwm/core.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 28cb6ab0f62d..5cf64b3a4cdf 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -411,9 +411,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
* possible/needed. In the above example requesting .period_length_ns = 94 and
* @exact = true, you get the hardware configured with period = 93.5 ns.
*
- * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting
- * failed due to the exact waveform not being possible (if @exact), or a
- * different negative errno on failure.
+ * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not
+ * being possible (if @exact), or a different negative errno on failure.
* Context: May sleep.
*/
int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
@@ -442,14 +441,17 @@ int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
}
/*
- * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is
- * only returned in exactly that case. Note that __pwm_set_waveform()
- * should never return -EDOM which justifies the unlikely().
+ * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also
+ * make sure that -EDOM is only returned in exactly that case. Note that
+ * __pwm_set_waveform() should never return -EDOM which justifies the
+ * unlikely().
*/
if (unlikely(err == -EDOM))
err = -EINVAL;
else if (exact && err == 1)
err = -EDOM;
+ else if (err == 1)
+ err = 0;
return err;
}
--
2.47.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 3/4] pwm: Formally describe the procedure used to pick a hardware waveform setting
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 1/4] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 2/4] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after rounding up Uwe Kleine-König
@ 2025-04-30 11:56 ` Uwe Kleine-König
2025-04-30 11:56 ` [PATCH v7 4/4] pwm: Add support for pwmchip devices for faster and easier userspace access Uwe Kleine-König
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 11:56 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
This serves as specification for both, PWM consumers and the respective
callback for lowlevel drivers.
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
---
drivers/pwm/core.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 5cf64b3a4cdf..4d842c692194 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -231,7 +231,9 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c
*
* Usually all values passed in @wf are rounded down to the nearest possible
* value (in the order period_length_ns, duty_length_ns and then
- * duty_offset_ns). Only if this isn't possible, a value might grow.
+ * duty_offset_ns). Only if this isn't possible, a value might grow. See the
+ * documentation for pwm_set_waveform_might_sleep() for a more formal
+ * description.
*
* Returns: 0 on success, 1 if at least one value had to be rounded up or a
* negative errno.
@@ -411,6 +413,26 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
* possible/needed. In the above example requesting .period_length_ns = 94 and
* @exact = true, you get the hardware configured with period = 93.5 ns.
*
+ * Let C be the set of possible hardware configurations for a given PWM device,
+ * consisting of tuples (p, d, o) where p is the period length, d is the duty
+ * length and o the duty offset.
+ *
+ * The following algorithm is implemented to pick the hardware setting
+ * (p, d, o) ∈ C for a given request (p', d', o') with @exact = false::
+ *
+ * p = max( { ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C ∧ ṗ ≤ p' } ∪ { min({ ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C }) })
+ * d = max( { ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C ∧ ḋ ≤ d' } ∪ { min({ ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C }) })
+ * o = max( { ȯ | (p, d, ȯ) ∈ C ∧ ȯ ≤ o' } ∪ { min({ ȯ | (p, d, ȯ) ∈ C }) })
+ *
+ * In words: The chosen period length is the maximal possible period length not
+ * bigger than the requested period length and if that doesn't exist, the
+ * minimal period length. The chosen duty length is the maximal possible duty
+ * length that is compatible with the chosen period length and isn't bigger than
+ * the requested duty length. Again if such a value doesn't exist, the minimal
+ * duty length compatible with the chosen period is picked. After that the duty
+ * offset compatible with the chosen period and duty length is chosen in the
+ * same way.
+ *
* Returns: 0 on success, -EDOM if setting failed due to the exact waveform not
* being possible (if @exact), or a different negative errno on failure.
* Context: May sleep.
--
2.47.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 4/4] pwm: Add support for pwmchip devices for faster and easier userspace access
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
` (2 preceding siblings ...)
2025-04-30 11:56 ` [PATCH v7 3/4] pwm: Formally describe the procedure used to pick a hardware waveform setting Uwe Kleine-König
@ 2025-04-30 11:56 ` Uwe Kleine-König
2025-04-30 12:04 ` [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
2025-05-07 10:04 ` Uwe Kleine-König
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 11:56 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
With this change each pwmchip defining the new-style waveform callbacks
can be accessed from userspace via a character device. Compared to the
sysfs-API this is faster and allows to pass the whole configuration in a
single ioctl allowing atomic application and thus reducing glitches.
On an STM32MP13 I see:
root@DistroKit:~ time pwmtestperf
real 0m 1.27s
user 0m 0.02s
sys 0m 1.21s
root@DistroKit:~ rm /dev/pwmchip0
root@DistroKit:~ time pwmtestperf
real 0m 3.61s
user 0m 0.27s
sys 0m 3.26s
pwmtestperf does essentially:
for i in 0 .. 50000:
pwm_set_waveform(duty_length_ns=i, period_length_ns=50000, duty_offset_ns=0)
and in the presence of /dev/pwmchip0 is uses the ioctls introduced here,
without that device it uses /sys/class/pwm/pwmchip0.
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
---
drivers/pwm/core.c | 322 +++++++++++++++++++++++++++++++++++++--
include/linux/pwm.h | 3 +
include/uapi/linux/pwm.h | 53 +++++++
3 files changed, 363 insertions(+), 15 deletions(-)
create mode 100644 include/uapi/linux/pwm.h
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 4d842c692194..b86f06ab2a32 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -23,9 +23,13 @@
#include <dt-bindings/pwm/pwm.h>
+#include <uapi/linux/pwm.h>
+
#define CREATE_TRACE_POINTS
#include <trace/events/pwm.h>
+#define PWM_MINOR_COUNT 256
+
/* protects access to pwm_chips */
static DEFINE_MUTEX(pwm_lock);
@@ -2007,20 +2011,9 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
}
EXPORT_SYMBOL_GPL(pwm_get);
-/**
- * pwm_put() - release a PWM device
- * @pwm: PWM device
- */
-void pwm_put(struct pwm_device *pwm)
+static void __pwm_put(struct pwm_device *pwm)
{
- struct pwm_chip *chip;
-
- if (!pwm)
- return;
-
- chip = pwm->chip;
-
- guard(mutex)(&pwm_lock);
+ struct pwm_chip *chip = pwm->chip;
/*
* Trigger a warning if a consumer called pwm_put() twice.
@@ -2041,6 +2034,20 @@ void pwm_put(struct pwm_device *pwm)
module_put(chip->owner);
}
+
+/**
+ * pwm_put() - release a PWM device
+ * @pwm: PWM device
+ */
+void pwm_put(struct pwm_device *pwm)
+{
+ if (!pwm)
+ return;
+
+ guard(mutex)(&pwm_lock);
+
+ __pwm_put(pwm);
+}
EXPORT_SYMBOL_GPL(pwm_put);
static void devm_pwm_release(void *pwm)
@@ -2110,6 +2117,274 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
+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;
+
+ guard(mutex)(&pwm_lock);
+
+ if (!chip->operational)
+ return -ENXIO;
+
+ cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL);
+ if (!cdata)
+ return -ENOMEM;
+
+ cdata->chip = chip;
+
+ file->private_data = cdata;
+
+ return nonseekable_open(inode, file);
+}
+
+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) {
+ struct pwm_device *pwm = cdata->pwm[i];
+
+ if (pwm) {
+ const char *label = pwm->label;
+
+ pwm_put(cdata->pwm[i]);
+ kfree(label);
+ }
+ }
+ 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];
+ const char *label;
+ int ret;
+
+ label = kasprintf(GFP_KERNEL, "pwm-cdev (pid=%d)", current->pid);
+ if (!label)
+ return -ENOMEM;
+
+ ret = pwm_device_request(pwm, label);
+ if (ret < 0) {
+ kfree(label);
+ 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];
+ const char *label = pwm->label;
+
+ __pwm_put(pwm);
+
+ kfree(label);
+
+ cdata->pwm[hwpwm] = NULL;
+ }
+
+ return 0;
+}
+
+static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata,
+ u32 hwpwm)
+{
+ struct pwm_chip *chip = cdata->chip;
+
+ if (hwpwm >= chip->npwm)
+ return ERR_PTR(-EINVAL);
+
+ if (cdata->pwm[hwpwm])
+ return cdata->pwm[hwpwm];
+
+ return ERR_PTR(-EINVAL);
+}
+
+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;
+
+ guard(mutex)(&pwm_lock);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ switch (cmd) {
+ case PWM_IOCTL_REQUEST:
+ {
+ unsigned int hwpwm = arg;
+
+ return pwm_cdev_request(cdata, hwpwm);
+ }
+
+ case PWM_IOCTL_FREE:
+ {
+ unsigned int hwpwm = arg;
+
+ return pwm_cdev_free(cdata, hwpwm);
+ }
+
+ case PWM_IOCTL_ROUNDWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ wf = (struct pwm_waveform) {
+ .period_length_ns = cwf.period_length_ns,
+ .duty_length_ns = cwf.duty_length_ns,
+ .duty_offset_ns = cwf.duty_offset_ns,
+ };
+
+ ret = pwm_round_waveform_might_sleep(pwm, &wf);
+ if (ret < 0)
+ return ret;
+
+ cwf = (struct pwmchip_waveform) {
+ .hwpwm = cwf.hwpwm,
+ .period_length_ns = wf.period_length_ns,
+ .duty_length_ns = wf.duty_length_ns,
+ .duty_offset_ns = wf.duty_offset_ns,
+ };
+
+ return copy_to_user((struct pwmchip_waveform __user *)arg,
+ &cwf, sizeof(cwf));
+ }
+
+ case PWM_IOCTL_GETWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_get_waveform_might_sleep(pwm, &wf);
+ if (ret)
+ return ret;
+
+ cwf = (struct pwmchip_waveform) {
+ .hwpwm = cwf.hwpwm,
+ .period_length_ns = wf.period_length_ns,
+ .duty_length_ns = wf.duty_length_ns,
+ .duty_offset_ns = wf.duty_offset_ns,
+ };
+
+ return copy_to_user((struct pwmchip_waveform __user *)arg,
+ &cwf, sizeof(cwf));
+ }
+
+ case PWM_IOCTL_SETROUNDEDWF:
+ case PWM_IOCTL_SETEXACTWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ wf = (struct pwm_waveform){
+ .period_length_ns = cwf.period_length_ns,
+ .duty_length_ns = cwf.duty_length_ns,
+ .duty_offset_ns = cwf.duty_offset_ns,
+ };
+
+ if (!pwm_wf_valid(&wf))
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_set_waveform_might_sleep(pwm, &wf,
+ cmd == PWM_IOCTL_SETEXACTWF);
+
+ /*
+ * If userspace cares about rounding deviations it has
+ * to check the values anyhow, so simplify handling for
+ * them and don't signal uprounding. This matches the
+ * behaviour of PWM_IOCTL_ROUNDWF which also returns 0
+ * in that case.
+ */
+ if (ret == 1)
+ ret = 0;
+
+ return ret;
+ }
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations pwm_cdev_fileops = {
+ .open = pwm_cdev_open,
+ .release = pwm_cdev_release,
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = pwm_cdev_ioctl,
+};
+
+static dev_t pwm_devt;
+
/**
* __pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
@@ -2162,7 +2437,17 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
scoped_guard(pwmchip, chip)
chip->operational = true;
- ret = device_add(&chip->dev);
+ if (chip->ops->write_waveform) {
+ if (chip->id < PWM_MINOR_COUNT)
+ chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id);
+ else
+ dev_warn(&chip->dev, "chip id too high to create a chardev\n");
+ }
+
+ 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;
@@ -2213,7 +2498,7 @@ void pwmchip_remove(struct pwm_chip *chip)
idr_remove(&pwm_chips, chip->id);
}
- device_del(&chip->dev);
+ cdev_device_del(&chip->cdev, &chip->dev);
}
EXPORT_SYMBOL_GPL(pwmchip_remove);
@@ -2357,9 +2642,16 @@ static int __init pwm_init(void)
{
int ret;
+ ret = alloc_chrdev_region(&pwm_devt, 0, PWM_MINOR_COUNT, "pwm");
+ if (ret) {
+ pr_err("Failed to initialize chrdev region for PWM usage\n");
+ return ret;
+ }
+
ret = class_register(&pwm_class);
if (ret) {
pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret));
+ unregister_chrdev_region(pwm_devt, 256);
return ret;
}
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 63a17d2b4ec8..2492c91452f9 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -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/module.h>
@@ -311,6 +312,7 @@ struct pwm_ops {
/**
* struct pwm_chip - abstract a PWM controller
* @dev: device providing the PWMs
+ * @cdev: &struct cdev for this device
* @ops: callbacks for this PWM controller
* @owner: module providing this chip
* @id: unique number of this PWM chip
@@ -325,6 +327,7 @@ struct pwm_ops {
*/
struct pwm_chip {
struct device dev;
+ struct cdev cdev;
const struct pwm_ops *ops;
struct module *owner;
unsigned int id;
diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h
new file mode 100644
index 000000000000..182d59cc07ee
--- /dev/null
+++ b/include/uapi/linux/pwm.h
@@ -0,0 +1,53 @@
+/* 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_waveform - Describe a PWM waveform for a pwm_chip's PWM channel
+ * @hwpwm: per-chip relative index of the PWM device
+ * @__pad: padding, must be zero
+ * @period_length_ns: duration of the repeating period.
+ * A value of 0 represents a disabled PWM.
+ * @duty_length_ns: duration of the active part in each period
+ * @duty_offset_ns: offset of the rising edge from a period's start
+ */
+struct pwmchip_waveform {
+ __u32 hwpwm;
+ __u32 __pad;
+ __u64 period_length_ns;
+ __u64 duty_length_ns;
+ __u64 duty_offset_ns;
+};
+
+/* Reserves the passed hwpwm for exclusive control. */
+#define PWM_IOCTL_REQUEST _IO(0x75, 1)
+
+/* counter part to PWM_IOCTL_REQUEST */
+#define PWM_IOCTL_FREE _IO(0x75, 2)
+
+/*
+ * Modifies the passed wf according to hardware constraints. All parameters are
+ * rounded down to the next possible value, unless there is no such value, then
+ * values are rounded up. Note that zero isn't considered for rounding down
+ * period_length_ns.
+ */
+#define PWM_IOCTL_ROUNDWF _IOWR(0x75, 3, struct pwmchip_waveform)
+
+/* Get the currently implemented waveform */
+#define PWM_IOCTL_GETWF _IOWR(0x75, 4, struct pwmchip_waveform)
+
+/* Like PWM_IOCTL_ROUNDWF + PWM_IOCTL_SETEXACTWF in one go. */
+#define PWM_IOCTL_SETROUNDEDWF _IOW(0x75, 5, struct pwmchip_waveform)
+
+/*
+ * Program the PWM to emit exactly the passed waveform, subject only to rounding
+ * down each value less than 1 ns. Returns 0 on success, -EDOM if the waveform
+ * cannot be implemented exactly, or other negative error codes.
+ */
+#define PWM_IOCTL_SETEXACTWF _IOW(0x75, 6, struct pwmchip_waveform)
+
+#endif /* _UAPI_PWM_H_ */
--
2.47.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v7 0/4] pwm: userspace support
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
` (3 preceding siblings ...)
2025-04-30 11:56 ` [PATCH v7 4/4] pwm: Add support for pwmchip devices for faster and easier userspace access Uwe Kleine-König
@ 2025-04-30 12:04 ` Uwe Kleine-König
2025-05-07 10:04 ` Uwe Kleine-König
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-04-30 12:04 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 395 bytes --]
On Wed, Apr 30, 2025 at 01:55:57PM +0200, Uwe Kleine-König wrote:
> after the feedback I got from David on v7[1] and some internal
> discussion here comes a new version of the patch.
So given that v7 was the previous revision of this patch set this should
have been v8. :-\
I hope all the annoyance that results from that fat-fingering is on my
side only. Sorry.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v7 0/4] pwm: userspace support
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
` (4 preceding siblings ...)
2025-04-30 12:04 ` [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
@ 2025-05-07 10:04 ` Uwe Kleine-König
5 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2025-05-07 10:04 UTC (permalink / raw)
To: linux-pwm; +Cc: David Lechner, Kent Gibson, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1162 bytes --]
Hello,
On Wed, Apr 30, 2025 at 01:55:57PM +0200, Uwe Kleine-König wrote:
> after the feedback I got from David on v7[1] and some internal
> discussion here comes a new version of the patch.
>
> Apart from rebasing to a newer base (current pwm/for-next) the only
> change in the relevant patch #4 about return values from the
> PWM_IOCTL_SETEXACTWF ioctl. Instead of returning 1 if the request failed
> to apply exactly, return -EDOM.
>
> The earlier patches in this series prepare that and implement a
> similar change in pwm_set_waveform_might_sleep() to simplify also other
> users of this function. Patch #3 is only a documentation update that is
> not strictly related to the userspace chardev, but reflects the changes
> in patches #1 and #2.
>
> Feedback welcome.
I hope that no feedback is good feedback and pushed that series. The
first three patches to
https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git pwm/for-next
as v6.16-rc1 material. The last patch only to pwm/for-nexxt which I will
push to next after v6.16-rc1 to give that a long time in next before
going into a release.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-05-07 10:04 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-30 11:55 [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 1/4] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests Uwe Kleine-König
2025-04-30 11:55 ` [PATCH v7 2/4] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after rounding up Uwe Kleine-König
2025-04-30 11:56 ` [PATCH v7 3/4] pwm: Formally describe the procedure used to pick a hardware waveform setting Uwe Kleine-König
2025-04-30 11:56 ` [PATCH v7 4/4] pwm: Add support for pwmchip devices for faster and easier userspace access Uwe Kleine-König
2025-04-30 12:04 ` [PATCH v7 0/4] pwm: userspace support Uwe Kleine-König
2025-05-07 10:04 ` Uwe Kleine-König
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.