* [RFC][PATCH V2 1/3] power sequences interpreter for device tree
2012-07-09 6:08 [RFC][PATCHv2 0/3] Power sequences interpreter for pwm_backlight Alexandre Courbot
@ 2012-07-09 6:08 ` Alexandre Courbot
2012-07-09 6:08 ` [RFC][PATCH V2 2/3] pwm_backlight: use power sequences Alexandre Courbot
2012-07-09 6:08 ` [RFC][PATCH V2 3/3] tegra: add pwm backlight device tree nodes Alexandre Courbot
2 siblings, 0 replies; 11+ messages in thread
From: Alexandre Courbot @ 2012-07-09 6:08 UTC (permalink / raw)
To: Thierry Reding
Cc: linux-tegra, linux-kernel, linux-fbdev, devicetree-discuss,
Alexandre Courbot
Some device drivers (panel backlights especially) need to follow precise
sequences for powering on and off, involving gpios, regulators, PWMs
with a precise powering order and delays to respect between each steps.
These sequences are board-specific, and do not belong to a particular
driver - therefore they have been performed by board-specific hook
functions to far.
With the advent of the device tree, we cannot rely of board-specific
hooks anymore, but still need a way to implement these sequences in a
portable manner. This patch introduces a simple interpreter that can
execute such power sequences encoded either as platform data or within
the device tree.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/video/backlight/Makefile | 2 +-
drivers/video/backlight/power_seq.c | 298 ++++++++++++++++++++++++++++++++++++
drivers/video/backlight/pwm_bl.c | 3 +-
include/linux/power_seq.h | 96 ++++++++++++
4 files changed, 397 insertions(+), 2 deletions(-)
create mode 100644 drivers/video/backlight/power_seq.c
create mode 100644 include/linux/power_seq.h
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index a2ac9cf..6bff124 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -28,7 +28,7 @@ obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o
obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o
obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o
obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
-obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
+obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o power_seq.o
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o
obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o
diff --git a/drivers/video/backlight/power_seq.c b/drivers/video/backlight/power_seq.c
new file mode 100644
index 0000000..f54cb7d
--- /dev/null
+++ b/drivers/video/backlight/power_seq.c
@@ -0,0 +1,298 @@
+#include <linux/err.h>
+#include <linux/of_gpio.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/power_seq.h>
+#include <linux/delay.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+
+#define PWM_SEQ_TYPE(type) [POWER_SEQ_ ## type] = #type
+static const char *pwm_seq_types[] = {
+ PWM_SEQ_TYPE(STOP),
+ PWM_SEQ_TYPE(DELAY),
+ PWM_SEQ_TYPE(REGULATOR),
+ PWM_SEQ_TYPE(PWM),
+ PWM_SEQ_TYPE(GPIO),
+};
+#undef PWM_SEQ_TYPE
+
+static bool power_seq_step_run(struct power_seq_step *step)
+{
+ switch (step->type) {
+ case POWER_SEQ_DELAY:
+ msleep(step->parameter);
+ break;
+ case POWER_SEQ_REGULATOR:
+ if (step->parameter)
+ regulator_enable(step->resource->regulator);
+ else
+ regulator_disable(step->resource->regulator);
+ break;
+ case POWER_SEQ_PWM:
+ if (step->parameter)
+ pwm_enable(step->resource->pwm);
+ else
+ pwm_disable(step->resource->pwm);
+ break;
+ case POWER_SEQ_GPIO:
+ gpio_set_value_cansleep(step->resource->gpio, step->parameter);
+ break;
+ /* should never happen since we verify the data when building it */
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int power_seq_run(power_seq *seq)
+{
+ int err;
+
+ if (!seq) return 0;
+
+ while (seq->type != POWER_SEQ_STOP) {
+ if ((err = power_seq_step_run(seq++))) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int of_parse_power_seq_step(struct device *dev, struct property *prop,
+ struct platform_power_seq_step *seq,
+ int max_steps)
+{
+ void *value = prop->value;
+ void *end = prop->value + prop->length;
+ int slen, smax, cpt = 0, i, ret;
+ char tmp_buf[32];
+
+ while (value < end && cpt < max_steps) {
+ smax = value - end;
+ slen = strnlen(value, end - value);
+
+ /* Unterminated string / not a string? */
+ if (slen >= end - value)
+ goto invalid_seq;
+
+ /* Find a matching sequence step type */
+ for (i = 0; i < POWER_SEQ_MAX; i++)
+ if (!strcmp(value, pwm_seq_types[i]))
+ break;
+
+ if (i >= POWER_SEQ_MAX)
+ goto unknown_step;
+
+ value += slen + 1;
+
+ seq[cpt].type = i;
+ switch (seq[cpt].type) {
+ case POWER_SEQ_DELAY:
+ /* integer parameter */
+ seq[cpt].parameter = be32_to_cpup(value);
+ value += sizeof(__be32);
+ break;
+ case POWER_SEQ_REGULATOR:
+ case POWER_SEQ_PWM:
+ case POWER_SEQ_GPIO:
+ /* consumer string */
+ slen = strnlen(value, end - value);
+ if (slen >= end - value)
+ goto invalid_seq;
+ seq[cpt].id = value;
+ value += slen + 1;
+
+ /* parameter */
+ seq[cpt].parameter = be32_to_cpup(value);
+ be32_to_cpup(value);
+ value += sizeof(__be32);
+
+ /* For GPIO we still need to resolve the phandle */
+ if (seq[cpt].type != POWER_SEQ_GPIO)
+ break;
+
+ strncpy(tmp_buf, seq[cpt].id, sizeof(tmp_buf) - 6);
+ tmp_buf[sizeof(tmp_buf) - 6] = 0;
+ strcat(tmp_buf, "-gpios");
+ ret = of_get_named_gpio(dev->of_node, tmp_buf, 0);
+ if (ret >= 0)
+ seq[cpt].value = ret;
+ else {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "cannot get gpio \"%s\"\n",
+ seq[cpt].id);
+ return ret;
+ }
+ default:
+ break;
+ }
+
+ cpt++;
+ }
+
+ if (cpt >= max_steps)
+ return -EOVERFLOW;
+
+ return 0;
+
+invalid_seq:
+ dev_err(dev, "invalid sequence \"%s\"\n", prop->name);
+ return -EINVAL;
+unknown_step:
+ dev_err(dev, "unknown step type \"%s\" in sequence \"%s\"\n",
+ (char *)value, prop->name);
+ return -EINVAL;
+}
+
+#define PWM_SEQ_MAX_LENGTH 16
+platform_power_seq *of_parse_power_seq(struct device *dev,
+ struct device_node *node, char *propname)
+{
+ platform_power_seq *seq = NULL;
+ struct property *prop;
+ int length;
+ int ret;
+
+ prop = of_find_property(node, propname, &length);
+ if (prop && length > 0) {
+ seq = devm_kzalloc(dev, sizeof(*seq) * PWM_SEQ_MAX_LENGTH,
+ GFP_KERNEL);
+ if (!seq)
+ return ERR_PTR(-ENOMEM);
+ /* keep one empty entry for the STOP step */
+ ret = of_parse_power_seq_step(dev, prop, seq,
+ PWM_SEQ_MAX_LENGTH - 1);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ }
+
+ return seq;
+}
+
+static
+struct power_seq_resource * power_seq_find_resource(power_seq_resources *ress,
+ struct platform_power_seq_step *res)
+{
+ struct power_seq_resource *step;
+
+ list_for_each_entry(step, ress, list) {
+ if (step->plat.type != res->type) continue;
+ switch (res->type) {
+ case POWER_SEQ_DELAY:
+ case POWER_SEQ_GPIO:
+ if (step->plat.value = res->value)
+ return step;
+ break;
+ default:
+ if (!strcmp(step->plat.id, res->id))
+ return step;
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+power_seq *power_seq_build(struct device *dev, power_seq_resources *ress,
+ platform_power_seq *pseq)
+{
+ struct power_seq_step *seq = NULL, *ret;
+ struct power_seq_resource *res;
+ int cpt;
+
+ /* first pass to count the number of elements */
+ for (cpt = 0; pseq[cpt].type != POWER_SEQ_STOP; cpt++);
+
+ if (!cpt)
+ return seq;
+
+ /* 1 more for the STOP step */
+ ret = seq = devm_kzalloc(dev, sizeof(*seq) * (cpt + 1), GFP_KERNEL);
+ if (!seq)
+ return ERR_PTR(-ENOMEM);
+
+ for (; pseq->type != POWER_SEQ_STOP; pseq++, seq++) {
+ seq->type = pseq->type;
+
+ switch (pseq->type) {
+ case POWER_SEQ_REGULATOR:
+ case POWER_SEQ_GPIO:
+ case POWER_SEQ_PWM:
+ if (!(res = power_seq_find_resource(ress, pseq))) {
+ /* create resource node */
+ res = devm_kzalloc(dev, sizeof(*res),
+ GFP_KERNEL);
+ if (!res)
+ return ERR_PTR(-ENOMEM);
+ memcpy(&res->plat, pseq, sizeof(*pseq));
+
+ list_add(&res->list, ress);
+ }
+ seq->resource = res;
+ case POWER_SEQ_DELAY:
+ seq->parameter = pseq->parameter;
+ break;
+ default:
+ dev_err(dev, "invalid sequence step type!\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ return ret;
+}
+
+int power_seq_allocate_resources(struct device *dev, power_seq_resources *ress)
+{
+ struct power_seq_resource *res;
+ int err;
+
+ list_for_each_entry(res, ress, list) {
+ switch (res->plat.type) {
+ case POWER_SEQ_REGULATOR:
+ res->regulator = devm_regulator_get(dev, res->plat.id);
+ if (IS_ERR(res->regulator)) {
+ dev_err(dev, "cannot get regulator \"%s\"\n",
+ res->plat.id);
+ return PTR_ERR(res->regulator);
+ }
+ dev_dbg(dev, "got regulator %s\n", res->plat.id);
+ break;
+ case POWER_SEQ_PWM:
+ res->pwm = pwm_get(dev, res->plat.id);
+ if (IS_ERR(res->pwm)) {
+ dev_err(dev, "cannot get pwm \"%s\"\n",
+ res->plat.id);
+ return PTR_ERR(res->pwm);
+ }
+ dev_dbg(dev, "got PWM %s\n", res->plat.id);
+ break;
+ case POWER_SEQ_GPIO:
+ err = devm_gpio_request_one(dev, res->plat.value,
+ GPIOF_OUT_INIT_HIGH, "backlight_gpio");
+ if (err) {
+ dev_err(dev, "cannot get gpio %d\n",
+ res->plat.value);
+ return err;
+ }
+ res->gpio = res->plat.value;
+ dev_dbg(dev, "got GPIO %d\n", res->plat.value);
+ break;
+ default:
+ break;
+ };
+ }
+
+ return 0;
+}
+
+void power_seq_free_resources(power_seq_resources *ress) {
+ struct power_seq_resource *res;
+
+ list_for_each_entry(res, ress, list) {
+ if (res->plat.type = POWER_SEQ_PWM)
+ pwm_put(res->pwm);
+ }
+}
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index be48517..1a38953 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -203,8 +203,9 @@ static int pwm_backlight_probe(struct platform_device *pdev)
if (data->levels) {
max = data->levels[data->max_brightness];
pb->levels = data->levels;
- } else
+ } else {
max = data->max_brightness;
+ }
pb->notify = data->notify;
pb->notify_after = data->notify_after;
diff --git a/include/linux/power_seq.h b/include/linux/power_seq.h
new file mode 100644
index 0000000..7f76270
--- /dev/null
+++ b/include/linux/power_seq.h
@@ -0,0 +1,96 @@
+/*
+ * Simple interpreter for defining power sequences as platform data or device
+ * tree properties. Mainly for use with backlight drivers.
+ */
+
+#ifndef __LINUX_POWER_SEQ_H
+#define __LINUX_POWER_SEQ_H
+
+#include <linux/of.h>
+#include <linux/types.h>
+
+/**
+ * The different kinds of resources that can be controlled during the sequences.
+ */
+typedef enum {
+ POWER_SEQ_STOP = 0,
+ POWER_SEQ_DELAY,
+ POWER_SEQ_REGULATOR,
+ POWER_SEQ_PWM,
+ POWER_SEQ_GPIO,
+ POWER_SEQ_MAX,
+} seq_type;
+
+/**
+ * Describe something to do during the power-up/down sequence.
+ */
+struct platform_power_seq_step {
+ seq_type type;
+ /**
+ * Identify the resource. Steps of type DELAY use value, others name the
+ * consumer to use in id.
+ */
+ union {
+ const char *id;
+ int value;
+ };
+ /**
+ * A value of 0 disables the resource, while a non-zero enables it.
+ * For DELAY steps this contains the delay to wait in milliseconds.
+ */
+ int parameter;
+};
+typedef struct platform_power_seq_step platform_power_seq;
+
+struct power_seq_resource {
+ /* copied from platform data */
+ struct platform_power_seq_step plat;
+ /* resolved resource */
+ union {
+ struct regulator *regulator;
+ struct pwm_device *pwm;
+ int gpio;
+ };
+ /* used to maintain a list of resources used by the driver */
+ struct list_head list;
+};
+typedef struct list_head power_seq_resources;
+
+struct power_seq_step {
+ seq_type type;
+ int parameter;
+ struct power_seq_resource *resource;
+};
+typedef struct power_seq_step power_seq;
+
+/**
+ * Build a platform data sequence from a device tree node. Memory for the
+ * sequence is allocated using devm_kzalloc on dev.
+ */
+platform_power_seq *of_parse_power_seq(struct device *dev,
+ struct device_node *node,
+ char *propname);
+/**
+ * Build a runnable power sequence from platform data, and add the resources
+ * it uses into ress. Memory for the sequence is allocated using devm_kzalloc
+ * on dev.
+ */
+power_seq *power_seq_build(struct device *dev, power_seq_resources *ress,
+ platform_power_seq *pseq);
+/**
+ * Allocate all resources (regulators, PWMs, GPIOs) found by calls to
+ * power_seq_build() for use by dev. Return 0 in case of success, error code
+ * otherwise.
+ */
+int power_seq_allocate_resources(struct device *dev, power_seq_resources *ress);
+/**
+ * Free all the resources previously allocated by power_seq_allocate_resources.
+ */
+void power_seq_free_resources(power_seq_resources *ress);
+/**
+ * Run the given power sequence. Returns 0 on success, error code in case of
+ * failure.
+ */
+int power_seq_run(power_seq *seq);
+
+#endif
--
1.7.11.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC][PATCH V2 2/3] pwm_backlight: use power sequences
2012-07-09 6:08 [RFC][PATCHv2 0/3] Power sequences interpreter for pwm_backlight Alexandre Courbot
2012-07-09 6:08 ` [RFC][PATCH V2 1/3] power sequences interpreter for device tree Alexandre Courbot
@ 2012-07-09 6:08 ` Alexandre Courbot
[not found] ` <1341814105-20690-3-git-send-email-acourbot-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2012-07-09 6:08 ` [RFC][PATCH V2 3/3] tegra: add pwm backlight device tree nodes Alexandre Courbot
2 siblings, 1 reply; 11+ messages in thread
From: Alexandre Courbot @ 2012-07-09 6:08 UTC (permalink / raw)
To: Thierry Reding
Cc: linux-tegra, linux-kernel, linux-fbdev, devicetree-discuss,
Alexandre Courbot
Make use of the power sequences specified in the device tree or platform
data, if any.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
.../bindings/video/backlight/pwm-backlight.txt | 28 ++-
drivers/video/backlight/power_seq.c | 44 ++---
drivers/video/backlight/pwm_bl.c | 210 +++++++++++++++------
include/linux/pwm_backlight.h | 37 +++-
4 files changed, 239 insertions(+), 80 deletions(-)
diff --git a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
index 1e4fc72..86c9253 100644
--- a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
+++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
@@ -2,7 +2,10 @@ pwm-backlight bindings
Required properties:
- compatible: "pwm-backlight"
- - pwms: OF device-tree PWM specification (see PWM binding[0])
+ - pwms: OF device-tree PWM specification (see PWM binding[0]). Exactly one PWM
+ must be specified
+ - pwm-names: a list of names for the PWM devices specified in the
+ "pwms" property (see PWM binding[0])
- brightness-levels: Array of distinct brightness levels. Typically these
are in the range from 0 to 255, but any range starting at 0 will do.
The actual brightness level (PWM duty cycle) will be interpolated
@@ -10,10 +13,18 @@ Required properties:
last value in the array represents a 100% duty cycle (brightest).
- default-brightness-level: the default brightness level (index into the
array defined by the "brightness-levels" property)
+ - power-on-sequence: Power sequence that will bring the backlight on. This
+ sequence must reference the PWM specified in the pwms property by its
+ name. It can also reference extra GPIOs or regulators, and introduce
+ delays between sequence steps
+ - power-off-sequence: Power sequence that will bring the backlight off. This
+ sequence must reference the PWM specified in the pwms property by its
+ name. It can also reference extra GPIOs or regulators, and introduce
+ delays between sequence steps
Optional properties:
- - pwm-names: a list of names for the PWM devices specified in the
- "pwms" property (see PWM binding[0])
+ - *-supply: a reference to a regulator used within a power sequence
+ - *-gpios: a reference to a GPIO used within a power sequence.
[0]: Documentation/devicetree/bindings/pwm/pwm.txt
@@ -22,7 +33,18 @@ Example:
backlight {
compatible = "pwm-backlight";
pwms = <&pwm 0 5000000>;
+ pwm-names = "backlight";
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
+ power-supply = <&backlight_reg>;
+ enable-gpios = <&gpio 6 0>;
+ power-on-sequence = "REGULATOR", "power", <1>,
+ "DELAY", <10>,
+ "PWM", "backlight", <1>,
+ "GPIO", "enable", <1>;
+ power-off-sequence = "GPIO", "enable", <0>,
+ "PWM", "backlight", <0>,
+ "DELAY", <10>,
+ "REGULATOR", "power", <0>;
};
diff --git a/drivers/video/backlight/power_seq.c b/drivers/video/backlight/power_seq.c
index f54cb7d..f8737db 100644
--- a/drivers/video/backlight/power_seq.c
+++ b/drivers/video/backlight/power_seq.c
@@ -118,9 +118,9 @@ static int of_parse_power_seq_step(struct device *dev, struct property *prop,
tmp_buf[sizeof(tmp_buf) - 6] = 0;
strcat(tmp_buf, "-gpios");
ret = of_get_named_gpio(dev->of_node, tmp_buf, 0);
- if (ret >= 0)
+ if (ret >= 0) {
seq[cpt].value = ret;
- else {
+ } else {
if (ret != -EPROBE_DEFER)
dev_err(dev, "cannot get gpio \"%s\"\n",
seq[cpt].id);
@@ -218,26 +218,26 @@ power_seq *power_seq_build(struct device *dev, power_seq_resources *ress,
seq->type = pseq->type;
switch (pseq->type) {
- case POWER_SEQ_REGULATOR:
- case POWER_SEQ_GPIO:
- case POWER_SEQ_PWM:
- if (!(res = power_seq_find_resource(ress, pseq))) {
- /* create resource node */
- res = devm_kzalloc(dev, sizeof(*res),
- GFP_KERNEL);
- if (!res)
- return ERR_PTR(-ENOMEM);
- memcpy(&res->plat, pseq, sizeof(*pseq));
-
- list_add(&res->list, ress);
- }
- seq->resource = res;
- case POWER_SEQ_DELAY:
- seq->parameter = pseq->parameter;
- break;
- default:
- dev_err(dev, "invalid sequence step type!\n");
- return ERR_PTR(-EINVAL);
+ case POWER_SEQ_REGULATOR:
+ case POWER_SEQ_GPIO:
+ case POWER_SEQ_PWM:
+ if (!(res = power_seq_find_resource(ress, pseq))) {
+ /* create resource node */
+ res = devm_kzalloc(dev, sizeof(*res),
+ GFP_KERNEL);
+ if (!res)
+ return ERR_PTR(-ENOMEM);
+ memcpy(&res->plat, pseq, sizeof(*pseq));
+
+ list_add(&res->list, ress);
+ }
+ seq->resource = res;
+ case POWER_SEQ_DELAY:
+ seq->parameter = pseq->parameter;
+ break;
+ default:
+ dev_err(dev, "invalid sequence step type!\n");
+ return ERR_PTR(-EINVAL);
}
}
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 1a38953..2936819 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -27,6 +27,12 @@ struct pwm_bl_data {
unsigned int period;
unsigned int lth_brightness;
unsigned int *levels;
+ bool enabled;
+ power_seq_resources resources;
+ power_seq *power_on_seq;
+ power_seq *power_off_seq;
+
+ /* Legacy callbacks */
int (*notify)(struct device *,
int brightness);
void (*notify_after)(struct device *,
@@ -35,6 +41,34 @@ struct pwm_bl_data {
void (*exit)(struct device *);
};
+static void pwm_backlight_on(struct backlight_device *bl)
+{
+ struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
+ int ret;
+
+ if (pb->enabled)
+ return;
+
+ if ((ret = power_seq_run(pb->power_on_seq)) < 0)
+ dev_err(&bl->dev, "cannot run power on sequence\n");
+
+ pb->enabled = true;
+}
+
+static void pwm_backlight_off(struct backlight_device *bl)
+{
+ struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
+ int ret;
+
+ if (!pb->enabled)
+ return;
+
+ if ((ret = power_seq_run(pb->power_off_seq)) < 0)
+ dev_err(&bl->dev, "cannot run power off sequence\n");
+
+ pb->enabled = false;
+}
+
static int pwm_backlight_update_status(struct backlight_device *bl)
{
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
@@ -51,8 +85,7 @@ static int pwm_backlight_update_status(struct backlight_device *bl)
brightness = pb->notify(pb->dev, brightness);
if (brightness = 0) {
- pwm_config(pb->pwm, 0, pb->period);
- pwm_disable(pb->pwm);
+ pwm_backlight_off(bl);
} else {
int duty_cycle;
if (pb->levels) {
@@ -144,12 +177,15 @@ static int pwm_backlight_parse_dt(struct device *dev,
data->max_brightness--;
}
- /*
- * TODO: Most users of this driver use a number of GPIOs to control
- * backlight power. Support for specifying these needs to be
- * added.
- */
+ data->power_on_seq = of_parse_power_seq(dev, node, "power-on-sequence");
+ if (IS_ERR(data->power_on_seq))
+ return PTR_ERR(data->power_on_seq);
+ data->power_off_seq = of_parse_power_seq(dev, node,
+ "power-off-sequence");
+ if (IS_ERR(data->power_off_seq))
+ return PTR_ERR(data->power_off_seq);
+ data->use_power_sequences = true;
return 0;
}
@@ -167,37 +203,134 @@ static int pwm_backlight_parse_dt(struct device *dev,
}
#endif
+/**
+ * Construct the power sequences corresponding to the legacy platform data.
+ */
+static int pwm_backlight_legacy_probe(struct platform_device *pdev,
+ struct pwm_bl_data *pb)
+{
+ struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct power_seq_resource *res;
+ struct power_seq_step *step;
+
+ pb->pwm = pwm_get(dev, NULL);
+ if (IS_ERR(pb->pwm)) {
+ dev_warn(dev, "unable to request PWM, trying legacy API\n");
+
+ pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
+ if (IS_ERR(pb->pwm)) {
+ dev_err(dev, "unable to request legacy PWM\n");
+ return PTR_ERR(pb->pwm);
+ }
+ pwm_set_period(pb->pwm, data->pwm_period_ns);
+ }
+
+ pb->notify = data->notify;
+ pb->notify_after = data->notify_after;
+ pb->check_fb = data->check_fb;
+ pb->exit = data->exit;
+ pb->dev = dev;
+
+ /* Now build the resources and sequences corresponding to this PWM */
+ res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
+ if (!res) return -ENOMEM;
+ res->plat.type = POWER_SEQ_PWM;
+ res->plat.id = "pwm-backlight";
+ res->pwm = pb->pwm;
+ list_add(&res->list, &pb->resources);
+
+ /* allocate both power on and off sequences at the same time */
+ step = devm_kzalloc(dev, sizeof(*step) * 4, GFP_KERNEL);
+ if (!step) return -ENOMEM;
+ step->type = POWER_SEQ_PWM;
+ step->resource = res;
+ memcpy(&step[2], &step[0], sizeof(*step));
+ step[0].parameter = 1;
+ pb->power_on_seq = &step[0];
+ pb->power_off_seq = &step[2];
+
+ return 0;
+}
+
static int pwm_backlight_probe(struct platform_device *pdev)
{
struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
struct platform_pwm_backlight_data defdata;
+ struct power_seq_resource *res;
struct backlight_properties props;
struct backlight_device *bl;
struct pwm_bl_data *pb;
unsigned int max;
int ret;
+ pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
+ if (!pb) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&pb->resources);
+
+ /* using new interface or device tree */
if (!data) {
+ /* build platform data from device tree */
ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
- if (ret < 0) {
+ if (ret = -EPROBE_DEFER) {
+ return ret;
+ } else if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
-
data = &defdata;
}
- if (data->init) {
- ret = data->init(&pdev->dev);
+ if (!data->use_power_sequences) {
+ /* using legacy interface */
+ ret = pwm_backlight_legacy_probe(pdev, pb);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* build sequences and allocate resources from platform data */
+ if (data->power_on_seq) {
+ pb->power_on_seq = power_seq_build(&pdev->dev, &pb->resources,
+ data->power_on_seq);
+ if (IS_ERR(pb->power_on_seq))
+ return PTR_ERR(pb->power_on_seq);
+ }
+ if (data->power_off_seq) {
+ pb->power_off_seq = power_seq_build(&pdev->dev, &pb->resources,
+ data->power_off_seq);
+ if (IS_ERR(pb->power_off_seq))
+ return PTR_ERR(pb->power_off_seq);
+ }
+ ret = power_seq_allocate_resources(&pdev->dev, &pb->resources);
if (ret < 0)
return ret;
+
+ /* we must have exactly one PWM for this driver */
+ list_for_each_entry(res, &pb->resources, list) {
+ if (res->plat.type != POWER_SEQ_PWM)
+ continue;
+ if (pb->pwm) {
+ dev_err(&pdev->dev, "cannot use more than one PWM\n");
+ return -EINVAL;
+ }
+ /* keep the pwm at hand */
+ pb->pwm = res->pwm;
+ }
}
- pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
- if (!pb) {
- dev_err(&pdev->dev, "no memory for state\n");
- ret = -ENOMEM;
- goto err_alloc;
+ /* from here we should have a PWM */
+ if (!pb->pwm) {
+ dev_err(&pdev->dev, "no PWM defined!\n");
+ return -EINVAL;
+ }
+
+ if (data->init) {
+ ret = data->init(&pdev->dev);
+ if (ret < 0)
+ goto err;
}
if (data->levels) {
@@ -207,34 +340,6 @@ static int pwm_backlight_probe(struct platform_device *pdev)
max = data->max_brightness;
}
- pb->notify = data->notify;
- pb->notify_after = data->notify_after;
- pb->check_fb = data->check_fb;
- pb->exit = data->exit;
- pb->dev = &pdev->dev;
-
- pb->pwm = pwm_get(&pdev->dev, NULL);
- if (IS_ERR(pb->pwm)) {
- dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
-
- pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
- if (IS_ERR(pb->pwm)) {
- dev_err(&pdev->dev, "unable to request legacy PWM\n");
- ret = PTR_ERR(pb->pwm);
- goto err_alloc;
- }
- }
-
- dev_dbg(&pdev->dev, "got pwm for backlight\n");
-
- /*
- * The DT case will set the pwm_period_ns field to 0 and store the
- * period, parsed from the DT, in the PWM device. For the non-DT case,
- * set the period from platform data.
- */
- if (data->pwm_period_ns > 0)
- pwm_set_period(pb->pwm, data->pwm_period_ns);
-
pb->period = pwm_get_period(pb->pwm);
pb->lth_brightness = data->lth_brightness * (pb->period / max);
@@ -246,20 +351,20 @@ static int pwm_backlight_probe(struct platform_device *pdev)
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
ret = PTR_ERR(bl);
- goto err_bl;
+ goto err;
}
bl->props.brightness = data->dft_brightness;
backlight_update_status(bl);
platform_set_drvdata(pdev, bl);
+
return 0;
-err_bl:
- pwm_put(pb->pwm);
-err_alloc:
+err:
if (data->exit)
data->exit(&pdev->dev);
+ power_seq_free_resources(&pb->resources);
return ret;
}
@@ -269,9 +374,9 @@ static int pwm_backlight_remove(struct platform_device *pdev)
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
backlight_device_unregister(bl);
- pwm_config(pb->pwm, 0, pb->period);
- pwm_disable(pb->pwm);
- pwm_put(pb->pwm);
+ pwm_backlight_off(bl);
+ power_seq_free_resources(&pb->resources);
+
if (pb->exit)
pb->exit(&pdev->dev);
return 0;
@@ -285,8 +390,7 @@ static int pwm_backlight_suspend(struct device *dev)
if (pb->notify)
pb->notify(pb->dev, 0);
- pwm_config(pb->pwm, 0, pb->period);
- pwm_disable(pb->pwm);
+ pwm_backlight_off(bl);
if (pb->notify_after)
pb->notify_after(pb->dev, 0);
return 0;
diff --git a/include/linux/pwm_backlight.h b/include/linux/pwm_backlight.h
index 56f4a86..dda267e 100644
--- a/include/linux/pwm_backlight.h
+++ b/include/linux/pwm_backlight.h
@@ -5,14 +5,47 @@
#define __LINUX_PWM_BACKLIGHT_H
#include <linux/backlight.h>
+#include <linux/power_seq.h>
+/**
+ * Two ways of passing data to the driver can be used:
+ * 1) If not using device tree and use_power_sequences is not set, the legacy
+ * interface is used. power_on_sequence and power_off_sequences are ignored,
+ * and pwm_id and pwm_period_ns can be used to assign a PWM and period to
+ * the backlight. The callback functions will also be called by the driver
+ * at appropriate times.
+ * 2) If use_power_sequences is set, the power sequences should either be NULL
+ * of contain an array of platform_pwm_backlight_seq_step instances
+ * terminated by a full-zero'd one. The described sequences will then be used
+ * for powering the backlight on and off, and the callbacks will not be
+ * called. Instances of resources will be obtained using the get_* functions,
+ * giving id as a consumer name.
+ *
+ * If the device tree is used, the power sequences properties are parsed and
+ * converted to the corresponding power sequences of this structure, which is
+ * passed to the driver with use_power_sequences set to true. See the
+ * pwm-backlight bindings documentation file for more details.
+ */
struct platform_pwm_backlight_data {
- int pwm_id;
unsigned int max_brightness;
unsigned int dft_brightness;
unsigned int lth_brightness;
- unsigned int pwm_period_ns;
unsigned int *levels;
+ /* Set this to true otherwise the legacy interface will be used */
+ bool use_power_sequences;
+ /*
+ * New interface - arrays of steps terminated by a STOP instance, or
+ * NULL if unused.
+ */
+ struct platform_power_seq_step *power_on_seq;
+ struct platform_power_seq_step *power_off_seq;
+ /*
+ * Legacy interface - single PWM and callback methods to control
+ * the power sequence. pwm_id and pwm_period_ns need only be specified
+ * if get_pwm(dev, NULL) will return NULL.
+ */
+ int pwm_id;
+ unsigned int pwm_period_ns;
int (*init)(struct device *dev);
int (*notify)(struct device *dev, int brightness);
void (*notify_after)(struct device *dev, int brightness);
--
1.7.11.1
^ permalink raw reply related [flat|nested] 11+ messages in thread