[PATCH] pwm-backlight: add support for device tree gpio control

From: Mike Dunn
Date: Tue Sep 03 2013 - 16:29:13 EST


This patch adds support for controlling an arbitrary number of gpios to the
pwm-backlight driver. This was left as a TODO when initial device tree support
was added by Thierry a while back. This functionality replaces the callbacks
that are passed in the platform data for non-DT cases. Users can avail
themselves of this feature by adding a 'gpios' property to the 'backlight' node.
When the update_status() callback in backlight_ops runs, the gpios listed in the
property are asserted/deasserted if the specified brightness is non-zero/zero.

Tested on a pxa270-based Palm Treo 680.

Signed-off-by: Mike Dunn <mikedunn@xxxxxxxxxxx>
---

Thanks for looking!

.../bindings/video/backlight/pwm-backlight.txt | 4 +
drivers/video/backlight/pwm_bl.c | 128 ++++++++++++++++++---
2 files changed, 113 insertions(+), 19 deletions(-)

diff --git a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
index 1e4fc72..4583e68 100644
--- a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
+++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt
@@ -14,6 +14,9 @@ Required properties:
Optional properties:
- pwm-names: a list of names for the PWM devices specified in the
"pwms" property (see PWM binding[0])
+ - gpios: An arbitrary number of gpios that must be asserted when the
+ backlight is on, and de-asserted when off. They will be asserted
+ in the order they appear, and de-asserted in reverse order.

[0]: Documentation/devicetree/bindings/pwm/pwm.txt

@@ -25,4 +28,5 @@ Example:

brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
+ gpios = <&gpio 77 0>, <&gpio 25 1>; /* gpio 25 is active low */
};
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 1fea627..1e2ab52 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -20,6 +20,12 @@
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/slab.h>
+#include <linux/of_gpio.h>
+
+struct pwm_bl_gpio {
+ unsigned int gpio;
+ enum of_gpio_flags flags;
+};

struct pwm_bl_data {
struct pwm_device *pwm;
@@ -27,6 +33,8 @@ struct pwm_bl_data {
unsigned int period;
unsigned int lth_brightness;
unsigned int *levels;
+ unsigned int num_gpios;
+ struct pwm_bl_gpio *gpios;
int (*notify)(struct device *,
int brightness);
void (*notify_after)(struct device *,
@@ -94,14 +102,77 @@ static const struct backlight_ops pwm_backlight_ops = {
};

#ifdef CONFIG_OF
+static int pwm_backlight_dt_notify(struct device *dev, int brightness)
+{
+ struct backlight_device *bl = dev_get_drvdata(dev);
+ struct pwm_bl_data *pb = bl_get_data(bl);
+ int i;
+
+ if (brightness) {
+ for (i = 0; i < pb->num_gpios; i++) {
+ if (pb->gpios[i].flags == OF_GPIO_ACTIVE_LOW)
+ gpio_set_value(pb->gpios[i].gpio, 0);
+ else
+ gpio_set_value(pb->gpios[i].gpio, 1);
+ }
+ return 0;
+ }
+
+ /* de-assert gpios in reverse order, in case this is important */
+ for (i = pb->num_gpios - 1; i >= 0; i--) {
+ if (pb->gpios[i].flags == OF_GPIO_ACTIVE_LOW)
+ gpio_set_value(pb->gpios[i].gpio, 1);
+ else
+ gpio_set_value(pb->gpios[i].gpio, 0);
+ }
+ return 0;
+}
+
+static void pwm_backlight_dt_exit(struct pwm_bl_data *pb)
+{
+ int i;
+
+ for (i = 0; i < pb->num_gpios; i++)
+ gpio_free(pb->gpios[i].gpio);
+}
+
+static int pwm_backlight_dt_init(struct device *dev, struct pwm_bl_data *pb)
+{
+ int i, j, ret;
+
+ /* request gpios and drive in the inactive state */
+ for (i = 0; i < pb->num_gpios; i++) {
+ char gpio_name[32];
+ unsigned long flags;
+ if (pb->gpios[i].flags == OF_GPIO_ACTIVE_LOW)
+ flags = GPIOF_OUT_INIT_LOW;
+ else
+ flags = GPIOF_OUT_INIT_HIGH;
+ snprintf(gpio_name, 32, "%s.%d", dev_name(dev), i);
+ ret = gpio_request_one(pb->gpios[i].gpio, flags, gpio_name);
+ if (ret) {
+ dev_err(dev, "gpio #%d request failed\n", i);
+ goto gpio_err;
+ }
+ }
+ return 0;
+
+ gpio_err:
+ for (j = 0; j < i; j++)
+ gpio_free(pb->gpios[j].gpio);
+ return ret;
+}
+
static int pwm_backlight_parse_dt(struct device *dev,
- struct platform_pwm_backlight_data *data)
+ struct platform_pwm_backlight_data *data,
+ struct pwm_bl_data *pb)
{
struct device_node *node = dev->of_node;
struct property *prop;
int length;
u32 value;
- int ret;
+ int ret, i, num_gpios;
+ size_t gpiosize;

if (!node)
return -ENODEV;
@@ -138,13 +209,29 @@ 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.
- */
+ /* read gpios from DT property */
+ num_gpios = of_gpio_count(node);
+ if (num_gpios == -ENOENT)
+ return 0; /* no 'gpios' property present */
+ if (num_gpios < 0) {
+ dev_err(dev, "invalid DT node: gpios\n");
+ return -EINVAL;
+ }
+ gpiosize = sizeof(struct pwm_bl_gpio) * num_gpios;
+ pb->gpios = devm_kzalloc(dev, gpiosize, GFP_KERNEL);
+ if (!pb->gpios)
+ return -ENOMEM;
+ for (i = 0; i < num_gpios; i++) {
+ int gpio;
+ enum of_gpio_flags flags;
+ gpio = of_get_gpio_flags(node, i, &flags);
+ pb->gpios[i].gpio = (unsigned int)gpio;
+ pb->gpios[i].flags = flags;
+ }
+ pb->num_gpios = (unsigned int)num_gpios;
+ pb->notify = pwm_backlight_dt_notify;

- return 0;
+ return pwm_backlight_dt_init(dev, pb);
}

static struct of_device_id pwm_backlight_of_match[] = {
@@ -155,10 +242,12 @@ static struct of_device_id pwm_backlight_of_match[] = {
MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
#else
static int pwm_backlight_parse_dt(struct device *dev,
- struct platform_pwm_backlight_data *data)
+ struct platform_pwm_backlight_data *data,
+ struct pwm_bl_data *pb)
{
return -ENODEV;
}
+static void pwm_backlight_dt_exit(struct pwm_bl_data *pb) {}
#endif

static int pwm_backlight_probe(struct platform_device *pdev)
@@ -171,8 +260,14 @@ static int pwm_backlight_probe(struct platform_device *pdev)
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;
+ }
+
if (!data) {
- ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
+ ret = pwm_backlight_parse_dt(&pdev->dev, &defdata, pb);
if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
@@ -187,20 +282,14 @@ static int pwm_backlight_probe(struct platform_device *pdev)
return ret;
}

- 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;
- }
-
if (data->levels) {
max = data->levels[data->max_brightness];
pb->levels = data->levels;
} else
max = data->max_brightness;

- pb->notify = data->notify;
+ if (pb->notify == NULL) /* not using DT and its built-in notify() */
+ pb->notify = data->notify;
pb->notify_after = data->notify_after;
pb->check_fb = data->check_fb;
pb->exit = data->exit;
@@ -250,9 +339,9 @@ static int pwm_backlight_probe(struct platform_device *pdev)
}

bl->props.brightness = data->dft_brightness;
+ platform_set_drvdata(pdev, bl);
backlight_update_status(bl);

- platform_set_drvdata(pdev, bl);
return 0;

err_alloc:
@@ -271,6 +360,7 @@ static int pwm_backlight_remove(struct platform_device *pdev)
pwm_disable(pb->pwm);
if (pb->exit)
pb->exit(&pdev->dev);
+ pwm_backlight_dt_exit(pb);
return 0;
}

--
1.8.1.5

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