aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/core.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-07-09 08:57:45 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2019-07-09 08:57:45 -0700
commit6e2bbb688aa6d05073dd1dd0b836d9becec195c1 (patch)
treeb267f5f593c397aa4c5eababc8d78ebc69ea3e7b /drivers/pwm/core.c
parentMerge branch 'siginfo-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace (diff)
parentpwm: fsl-ftm: Make sure to unlock mutex on failure (diff)
downloadlinux-dev-6e2bbb688aa6d05073dd1dd0b836d9becec195c1.tar.xz
linux-dev-6e2bbb688aa6d05073dd1dd0b836d9becec195c1.zip
Merge tag 'pwm/for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm
Pull pwm updates from Thierry Reding: "This set of changes contains a new driver for SiFive SoCs as well as enhancements to the core (device links are used to track dependencies between PWM providers and consumers, support for PWM controllers via ACPI, sysfs will now suspend/resume PWMs that it has claimed) and various existing drivers" * tag 'pwm/for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (37 commits) pwm: fsl-ftm: Make sure to unlock mutex on failure pwm: fsl-ftm: Use write protection for prescaler & polarity pwm: fsl-ftm: More relaxed permissions for updating period pwm: atmel-hlcdc: Add compatible for SAM9X60 HLCDC's PWM pwm: bcm2835: Improve precision of PWM leds: pwm: Support ACPI via firmware-node framework pwm: Add support referencing PWMs from ACPI pwm: rcar: Remove suspend/resume support pwm: sysfs: Add suspend/resume support pwm: Add power management descriptions pwm: meson: Add documentation to the driver pwm: meson: Add support PWM_POLARITY_INVERSED when disabling pwm: meson: Don't cache struct pwm_state internally pwm: meson: Read the full hardware state in meson_pwm_get_state() pwm: meson: Simplify the calculation of the pre-divider and count pwm: meson: Move pwm_set_chip_data() to meson_pwm_request() pwm: meson: Add the per-channel register offsets and bits in a struct pwm: meson: Add the meson_pwm_channel data to struct meson_pwm pwm: meson: Pass struct pwm_device to meson_pwm_calc() pwm: meson: Don't duplicate the polarity internally ...
Diffstat (limited to 'drivers/pwm/core.c')
-rw-r--r--drivers/pwm/core.c172
1 files changed, 169 insertions, 3 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 275b5f399a1a..c3ab07ab31a9 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -6,6 +6,7 @@
* Copyright (C) 2011-2012 Avionic Design GmbH
*/
+#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/radix-tree.h>
@@ -626,8 +627,35 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
return ERR_PTR(-EPROBE_DEFER);
}
+static struct device_link *pwm_device_link_add(struct device *dev,
+ struct pwm_device *pwm)
+{
+ struct device_link *dl;
+
+ if (!dev) {
+ /*
+ * No device for the PWM consumer has been provided. It may
+ * impact the PM sequence ordering: the PWM supplier may get
+ * suspended before the consumer.
+ */
+ dev_warn(pwm->chip->dev,
+ "No consumer device specified to create a link to\n");
+ return NULL;
+ }
+
+ dl = device_link_add(dev, pwm->chip->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!dl) {
+ dev_err(dev, "failed to create device link to %s\n",
+ dev_name(pwm->chip->dev));
+ return ERR_PTR(-EINVAL);
+ }
+
+ return dl;
+}
+
/**
* of_pwm_get() - request a PWM via the PWM framework
+ * @dev: device for PWM consumer
* @np: device node to get the PWM from
* @con_id: consumer name
*
@@ -645,10 +673,12 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
* Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded
* error code on failure.
*/
-struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
+struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np,
+ const char *con_id)
{
struct pwm_device *pwm = NULL;
struct of_phandle_args args;
+ struct device_link *dl;
struct pwm_chip *pc;
int index = 0;
int err;
@@ -679,6 +709,14 @@ struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
if (IS_ERR(pwm))
goto put;
+ dl = pwm_device_link_add(dev, pwm);
+ if (IS_ERR(dl)) {
+ /* of_xlate ended up calling pwm_request_from_chip() */
+ pwm_free(pwm);
+ pwm = ERR_CAST(dl);
+ goto put;
+ }
+
/*
* If a consumer name was not given, try to look it up from the
* "pwm-names" property if it exists. Otherwise use the name of
@@ -700,6 +738,85 @@ put:
}
EXPORT_SYMBOL_GPL(of_pwm_get);
+#if IS_ENABLED(CONFIG_ACPI)
+static struct pwm_chip *device_to_pwmchip(struct device *dev)
+{
+ struct pwm_chip *chip;
+
+ mutex_lock(&pwm_lock);
+
+ list_for_each_entry(chip, &pwm_chips, list) {
+ struct acpi_device *adev = ACPI_COMPANION(chip->dev);
+
+ if ((chip->dev == dev) || (adev && &adev->dev == dev)) {
+ mutex_unlock(&pwm_lock);
+ return chip;
+ }
+ }
+
+ mutex_unlock(&pwm_lock);
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+#endif
+
+/**
+ * acpi_pwm_get() - request a PWM via parsing "pwms" property in ACPI
+ * @fwnode: firmware node to get the "pwm" property from
+ *
+ * Returns the PWM device parsed from the fwnode and index specified in the
+ * "pwms" property or a negative error-code on failure.
+ * Values parsed from the device tree are stored in the returned PWM device
+ * object.
+ *
+ * This is analogous to of_pwm_get() except con_id is not yet supported.
+ * ACPI entries must look like
+ * Package () {"pwms", Package ()
+ * { <PWM device reference>, <PWM index>, <PWM period> [, <PWM flags>]}}
+ *
+ * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded
+ * error code on failure.
+ */
+static struct pwm_device *acpi_pwm_get(struct fwnode_handle *fwnode)
+{
+ struct pwm_device *pwm = ERR_PTR(-ENODEV);
+#if IS_ENABLED(CONFIG_ACPI)
+ struct fwnode_reference_args args;
+ struct acpi_device *acpi;
+ struct pwm_chip *chip;
+ int ret;
+
+ memset(&args, 0, sizeof(args));
+
+ ret = __acpi_node_get_property_reference(fwnode, "pwms", 0, 3, &args);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ acpi = to_acpi_device_node(args.fwnode);
+ if (!acpi)
+ return ERR_PTR(-EINVAL);
+
+ if (args.nargs < 2)
+ return ERR_PTR(-EPROTO);
+
+ chip = device_to_pwmchip(&acpi->dev);
+ if (IS_ERR(chip))
+ return ERR_CAST(chip);
+
+ pwm = pwm_request_from_chip(chip, args.args[0], NULL);
+ if (IS_ERR(pwm))
+ return pwm;
+
+ pwm->args.period = args.args[1];
+ pwm->args.polarity = PWM_POLARITY_NORMAL;
+
+ if (args.nargs > 2 && args.args[2] & PWM_POLARITY_INVERTED)
+ pwm->args.polarity = PWM_POLARITY_INVERSED;
+#endif
+
+ return pwm;
+}
+
/**
* pwm_add_table() - register PWM device consumers
* @table: array of consumers to register
@@ -754,6 +871,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
const char *dev_id = dev ? dev_name(dev) : NULL;
struct pwm_device *pwm;
struct pwm_chip *chip;
+ struct device_link *dl;
unsigned int best = 0;
struct pwm_lookup *p, *chosen = NULL;
unsigned int match;
@@ -761,7 +879,11 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
/* look up via DT first */
if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node)
- return of_pwm_get(dev->of_node, con_id);
+ return of_pwm_get(dev, dev->of_node, con_id);
+
+ /* then lookup via ACPI */
+ if (dev && is_acpi_node(dev->fwnode))
+ return acpi_pwm_get(dev->fwnode);
/*
* We look up the provider in the static table typically provided by
@@ -838,6 +960,12 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
if (IS_ERR(pwm))
return pwm;
+ dl = pwm_device_link_add(dev, pwm);
+ if (IS_ERR(dl)) {
+ pwm_free(pwm);
+ return ERR_CAST(dl);
+ }
+
pwm->args.period = chosen->period;
pwm->args.polarity = chosen->polarity;
@@ -930,7 +1058,7 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
if (!ptr)
return ERR_PTR(-ENOMEM);
- pwm = of_pwm_get(np, con_id);
+ pwm = of_pwm_get(dev, np, con_id);
if (!IS_ERR(pwm)) {
*ptr = pwm;
devres_add(dev, ptr);
@@ -942,6 +1070,44 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
}
EXPORT_SYMBOL_GPL(devm_of_pwm_get);
+/**
+ * devm_fwnode_pwm_get() - request a resource managed PWM from firmware node
+ * @dev: device for PWM consumer
+ * @fwnode: firmware node to get the PWM from
+ * @con_id: consumer name
+ *
+ * Returns the PWM device parsed from the firmware node. See of_pwm_get() and
+ * acpi_pwm_get() for a detailed description.
+ *
+ * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded
+ * error code on failure.
+ */
+struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
+ struct fwnode_handle *fwnode,
+ const char *con_id)
+{
+ struct pwm_device **ptr, *pwm = ERR_PTR(-ENODEV);
+
+ ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ if (is_of_node(fwnode))
+ pwm = of_pwm_get(dev, to_of_node(fwnode), con_id);
+ else if (is_acpi_node(fwnode))
+ pwm = acpi_pwm_get(fwnode);
+
+ if (!IS_ERR(pwm)) {
+ *ptr = pwm;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return pwm;
+}
+EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
+
static int devm_pwm_match(struct device *dev, void *res, void *data)
{
struct pwm_device **p = res;