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 <acour...@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

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to