summaryrefslogtreecommitdiff
path: root/drivers/pwm/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm/core.c')
-rw-r--r--drivers/pwm/core.c631
1 files changed, 540 insertions, 91 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 9c733877e98e..cd06229db394 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -6,7 +6,7 @@
* Copyright (C) 2011-2012 Avionic Design GmbH
*/
-#define DEFAULT_SYMBOL_NAMESPACE PWM
+#define DEFAULT_SYMBOL_NAMESPACE "PWM"
#include <linux/acpi.h>
#include <linux/module.h>
@@ -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);
@@ -206,8 +210,6 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c
return ret;
}
-#define WFHWSIZE 20
-
/**
* pwm_round_waveform_might_sleep - Query hardware capabilities
* Cannot be used in atomic context.
@@ -216,31 +218,41 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c
*
* Typically a given waveform cannot be implemented exactly by hardware, e.g.
* because hardware only supports coarse period resolution or no duty_offset.
- * This function returns the actually implemented waveform if you pass wf to
- * pwm_set_waveform_might_sleep now.
+ * This function returns the actually implemented waveform if you pass @wf to
+ * pwm_set_waveform_might_sleep() now.
*
* Note however that the world doesn't stop turning when you call it, so when
- * doing
+ * doing::
*
- * pwm_round_waveform_might_sleep(mypwm, &wf);
- * pwm_set_waveform_might_sleep(mypwm, &wf, true);
+ * pwm_round_waveform_might_sleep(mypwm, &wf);
+ * pwm_set_waveform_might_sleep(mypwm, &wf, true);
*
* the latter might fail, e.g. because an input clock changed its rate between
* these two calls and the waveform determined by
* pwm_round_waveform_might_sleep() cannot be implemented any more.
*
- * Returns 0 on success, 1 if there is no valid hardware configuration matching
- * the input waveform under the PWM rounding rules or a negative errno.
+ * 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. 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.
+ * Context: May sleep.
*/
int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
{
struct pwm_chip *chip = pwm->chip;
const struct pwm_ops *ops = chip->ops;
struct pwm_waveform wf_req = *wf;
- char wfhw[WFHWSIZE];
+ char wfhw[PWM_WFHWSIZE];
int ret_tohw, ret_fromhw;
- BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
+
+ if (!pwmchip_supports_waveform(chip))
+ return -EOPNOTSUPP;
if (!pwm_wf_valid(wf))
return -EINVAL;
@@ -264,13 +276,13 @@ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *
if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0)
dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n",
- wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_fromhw);
if (IS_ENABLED(CONFIG_PWM_DEBUG) &&
- ret_tohw == 0 && !pwm_check_rounding(&wf_req, wf))
- dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",
wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns,
- wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw);
return ret_tohw;
}
@@ -284,15 +296,21 @@ EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep);
*
* Stores the current configuration of the PWM in @wf. Note this is the
* equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform.
+ *
+ * Returns: 0 on success or a negative errno
+ * Context: May sleep.
*/
int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
{
struct pwm_chip *chip = pwm->chip;
const struct pwm_ops *ops = chip->ops;
- char wfhw[WFHWSIZE];
+ char wfhw[PWM_WFHWSIZE];
int err;
- BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
+
+ if (!pwmchip_supports_waveform(chip) || !ops->read_waveform)
+ return -EOPNOTSUPP;
guard(pwmchip)(chip);
@@ -314,28 +332,31 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
{
struct pwm_chip *chip = pwm->chip;
const struct pwm_ops *ops = chip->ops;
- char wfhw[WFHWSIZE];
+ char wfhw[PWM_WFHWSIZE];
struct pwm_waveform wf_rounded;
- int err;
+ int err, ret_tohw;
+
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
- BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+ if (!pwmchip_supports_waveform(chip))
+ return -EOPNOTSUPP;
if (!pwm_wf_valid(wf))
return -EINVAL;
- err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
- if (err)
- return err;
+ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
+ if (ret_tohw < 0)
+ return ret_tohw;
if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
if (err)
return err;
- if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
- dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",
wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
- wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw);
if (exact && pwmwfcmp(wf, &wf_rounded)) {
dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n",
@@ -373,7 +394,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
}
- return 0;
+
+ return ret_tohw;
}
/**
@@ -385,13 +407,37 @@ 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).
- * Note that even with exact = true, some rounding by less than 1 is
+ * 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 -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.
+ * @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.
*/
int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
const struct pwm_waveform *wf, bool exact)
@@ -418,6 +464,19 @@ 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 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;
}
EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep);
@@ -438,6 +497,13 @@ static void pwm_apply_debug(struct pwm_device *pwm,
return;
/*
+ * If a disabled PWM was requested the result is unspecified, so nothing
+ * to check.
+ */
+ if (!state->enabled)
+ return;
+
+ /*
* *state was just applied. Read out the hardware state and do some
* checks.
*/
@@ -449,25 +515,31 @@ static void pwm_apply_debug(struct pwm_device *pwm,
return;
/*
+ * If the PWM was disabled that's maybe strange but there is nothing
+ * that can be sensibly checked then. So return early.
+ */
+ if (!s1.enabled)
+ return;
+
+ /*
* The lowlevel driver either ignored .polarity (which is a bug) or as
* best effort inverted .polarity and fixed .duty_cycle respectively.
* Undo this inversion and fixup for further tests.
*/
- if (s1.enabled && s1.polarity != state->polarity) {
+ if (s1.polarity != state->polarity) {
s2.polarity = state->polarity;
s2.duty_cycle = s1.period - s1.duty_cycle;
s2.period = s1.period;
- s2.enabled = s1.enabled;
+ s2.enabled = true;
} else {
s2 = s1;
}
if (s2.polarity != state->polarity &&
- state->duty_cycle < state->period)
+ s2.duty_cycle < s2.period)
dev_warn(pwmchip_parent(chip), ".apply ignored .polarity\n");
- if (state->enabled && s2.enabled &&
- last->polarity == state->polarity &&
+ if (last->polarity == state->polarity &&
last->period > s2.period &&
last->period <= state->period)
dev_warn(pwmchip_parent(chip),
@@ -478,13 +550,12 @@ static void pwm_apply_debug(struct pwm_device *pwm,
* Rounding period up is fine only if duty_cycle is 0 then, because a
* flat line doesn't have a characteristic period.
*/
- if (state->enabled && s2.enabled && state->period < s2.period && s2.duty_cycle)
+ if (state->period < s2.period && s2.duty_cycle)
dev_warn(pwmchip_parent(chip),
".apply is supposed to round down period (requested: %llu, applied: %llu)\n",
state->period, s2.period);
- if (state->enabled &&
- last->polarity == state->polarity &&
+ if (last->polarity == state->polarity &&
last->period == s2.period &&
last->duty_cycle > s2.duty_cycle &&
last->duty_cycle <= state->duty_cycle)
@@ -494,16 +565,12 @@ static void pwm_apply_debug(struct pwm_device *pwm,
s2.duty_cycle, s2.period,
last->duty_cycle, last->period);
- if (state->enabled && s2.enabled && state->duty_cycle < s2.duty_cycle)
+ if (state->duty_cycle < s2.duty_cycle)
dev_warn(pwmchip_parent(chip),
".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n",
state->duty_cycle, state->period,
s2.duty_cycle, s2.period);
- if (!state->enabled && s2.enabled && s2.duty_cycle > 0)
- dev_warn(pwmchip_parent(chip),
- "requested disabled, but yielded enabled with duty > 0\n");
-
/* reapply the state that the driver reported being configured. */
err = chip->ops->apply(chip, pwm, &s1);
trace_pwm_apply(pwm, &s1, err);
@@ -539,7 +606,7 @@ static bool pwm_state_valid(const struct pwm_state *state)
* and supposed to be ignored. So also ignore any strange values and
* consider the state ok.
*/
- if (state->enabled)
+ if (!state->enabled)
return true;
if (!state->period)
@@ -551,11 +618,6 @@ static bool pwm_state_valid(const struct pwm_state *state)
return true;
}
-/**
- * __pwm_apply() - atomically apply a new state to a PWM device
- * @pwm: PWM device
- * @state: new state to apply
- */
static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
{
struct pwm_chip *chip;
@@ -592,11 +654,11 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
state->usage_power == pwm->state.usage_power)
return 0;
- if (ops->write_waveform) {
+ if (pwmchip_supports_waveform(chip)) {
struct pwm_waveform wf;
- char wfhw[WFHWSIZE];
+ char wfhw[PWM_WFHWSIZE];
- BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
pwm_state2wf(state, &wf);
@@ -664,6 +726,9 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
* Cannot be used in atomic context.
* @pwm: PWM device
* @state: new state to apply
+ *
+ * Returns: 0 on success, or a negative errno
+ * Context: May sleep.
*/
int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
{
@@ -705,6 +770,9 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
* Not all PWM devices support this function, check with pwm_might_sleep().
* @pwm: PWM device
* @state: new state to apply
+ *
+ * Returns: 0 on success, or a negative errno
+ * Context: Any
*/
int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
{
@@ -746,11 +814,11 @@ int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
if (!chip->operational)
return -ENODEV;
- if (ops->read_waveform) {
- char wfhw[WFHWSIZE];
+ if (pwmchip_supports_waveform(chip) && ops->read_waveform) {
+ char wfhw[PWM_WFHWSIZE];
struct pwm_waveform wf;
- BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
ret = __pwm_read_waveform(chip, pwm, &wfhw);
if (ret)
@@ -778,6 +846,9 @@ EXPORT_SYMBOL_GPL(pwm_get_state_hw);
* This function will adjust the PWM config to the PWM arguments provided
* by the DT or PWM lookup table. This is particularly useful to adapt
* the bootloader config to the Linux one.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ * Context: May sleep.
*/
int pwm_adjust_config(struct pwm_device *pwm)
{
@@ -991,11 +1062,27 @@ of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *arg
}
EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags);
+/*
+ * This callback is used for PXA PWM chips that only have a single PWM line.
+ * For such chips you could argue that passing the line number (i.e. the first
+ * parameter in the common case) is useless as it's always zero. So compared to
+ * the default xlate function of_pwm_xlate_with_flags() the first parameter is
+ * the default period and the second are flags.
+ *
+ * Note that if #pwm-cells = <3>, the semantic is the same as for
+ * of_pwm_xlate_with_flags() to allow converting the affected driver to
+ * #pwm-cells = <3> without breaking the legacy binding.
+ *
+ * Don't use for new drivers.
+ */
struct pwm_device *
of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
+ if (args->args_count >= 3)
+ return of_pwm_xlate_with_flags(chip, args);
+
pwm = pwm_request_from_chip(chip, 0, NULL);
if (IS_ERR(pwm))
return pwm;
@@ -1276,7 +1363,7 @@ static int pwm_export_child(struct device *pwmchip_dev, struct pwm_device *pwm)
return 0;
}
-static int pwm_unexport_match(struct device *pwm_dev, void *data)
+static int pwm_unexport_match(struct device *pwm_dev, const void *data)
{
return pwm_from_dev(pwm_dev) == data;
}
@@ -1521,12 +1608,13 @@ void pwmchip_put(struct pwm_chip *chip)
}
EXPORT_SYMBOL_GPL(pwmchip_put);
-static void pwmchip_release(struct device *pwmchip_dev)
+void pwmchip_release(struct device *pwmchip_dev)
{
struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
kfree(chip);
}
+EXPORT_SYMBOL_GPL(pwmchip_release);
struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
{
@@ -1615,8 +1703,8 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
!ops->write_waveform)
return false;
- if (WFHWSIZE < ops->sizeof_wfhw) {
- dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw);
+ if (PWM_WFHWSIZE < ops->sizeof_wfhw) {
+ dev_warn(pwmchip_parent(chip), "PWM_WFHWSIZE < %zu\n", ops->sizeof_wfhw);
return false;
}
} else {
@@ -1707,8 +1795,7 @@ static struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np,
return ERR_PTR(index);
}
- err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
- &args);
+ err = of_parse_phandle_with_args_map(np, "pwms", "pwm", index, &args);
if (err) {
pr_err("%s(): can't parse \"pwms\" property\n", __func__);
return ERR_PTR(err);
@@ -1931,20 +2018,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.
@@ -1965,6 +2041,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)
@@ -2034,6 +2124,319 @@ 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;
+
+static int pwm_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+ struct pwm_device *pwm;
+
+ pwm = pwm_request_from_chip(chip, offset, "pwm-gpio");
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ return 0;
+}
+
+static void pwm_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+
+ pwm_put(&chip->pwms[offset]);
+}
+
+static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int pwm_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+ struct pwm_device *pwm = &chip->pwms[offset];
+ int ret;
+ struct pwm_waveform wf = {
+ .period_length_ns = 1,
+ };
+
+ ret = pwm_round_waveform_might_sleep(pwm, &wf);
+ if (ret < 0)
+ return ret;
+
+ if (value)
+ wf.duty_length_ns = wf.period_length_ns;
+ else
+ wf.duty_length_ns = 0;
+
+ return pwm_set_waveform_might_sleep(pwm, &wf, true);
+}
+
/**
* __pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
@@ -2086,13 +2489,47 @@ 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;
+ if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform) {
+ struct device *parent = pwmchip_parent(chip);
+
+ chip->gpio = (typeof(chip->gpio)){
+ .label = dev_name(parent),
+ .parent = parent,
+ .request = pwm_gpio_request,
+ .free = pwm_gpio_free,
+ .get_direction = pwm_gpio_get_direction,
+ .set = pwm_gpio_set,
+ .base = -1,
+ .ngpio = chip->npwm,
+ .can_sleep = true,
+ };
+
+ ret = gpiochip_add_data(&chip->gpio, chip);
+ if (ret)
+ goto err_gpiochip_add;
+ }
+
return 0;
+err_gpiochip_add:
+
+ cdev_device_del(&chip->cdev, &chip->dev);
err_device_add:
+
scoped_guard(pwmchip, chip)
chip->operational = false;
@@ -2113,6 +2550,9 @@ EXPORT_SYMBOL_GPL(__pwmchip_add);
*/
void pwmchip_remove(struct pwm_chip *chip)
{
+ if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform)
+ gpiochip_remove(&chip->gpio);
+
pwmchip_sysfs_unexport(chip);
scoped_guard(mutex, &pwm_lock) {
@@ -2137,7 +2577,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);
@@ -2196,25 +2636,28 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
- struct pwm_state state;
+ struct pwm_state state, hwstate;
pwm_get_state(pwm, &state);
+ pwm_get_state_hw(pwm, &hwstate);
seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label);
if (test_bit(PWMF_REQUESTED, &pwm->flags))
seq_puts(s, " requested");
- if (state.enabled)
- seq_puts(s, " enabled");
+ seq_puts(s, "\n");
- seq_printf(s, " period: %llu ns", state.period);
- seq_printf(s, " duty: %llu ns", state.duty_cycle);
- seq_printf(s, " polarity: %s",
+ seq_printf(s, " requested configuration: %3sabled, %llu/%llu ns, %s polarity",
+ state.enabled ? "en" : "dis", state.duty_cycle, state.period,
state.polarity ? "inverse" : "normal");
-
if (state.usage_power)
- seq_puts(s, " usage_power");
+ seq_puts(s, ", usage_power");
+ seq_puts(s, "\n");
+
+ seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity",
+ hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period,
+ hwstate.polarity ? "inverse" : "normal");
seq_puts(s, "\n");
}
@@ -2254,11 +2697,10 @@ static int pwm_seq_show(struct seq_file *s, void *v)
{
struct pwm_chip *chip = v;
- seq_printf(s, "%s%d: %s/%s, %d PWM device%s\n",
+ seq_printf(s, "%s%u: %s/%s, npwm: %u\n",
(char *)s->private, chip->id,
pwmchip_parent(chip)->bus ? pwmchip_parent(chip)->bus->name : "no-bus",
- dev_name(pwmchip_parent(chip)), chip->npwm,
- (chip->npwm != 1) ? "s" : "");
+ dev_name(pwmchip_parent(chip)), chip->npwm);
pwm_dbg_show(chip, s);
@@ -2278,9 +2720,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;
}