1/*
2 * gpio_backlight.c - Simple GPIO-controlled backlight
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/backlight.h>
10#include <linux/err.h>
11#include <linux/fb.h>
12#include <linux/gpio.h>
13#include <linux/init.h>
14#include <linux/kernel.h>
15#include <linux/module.h>
16#include <linux/of.h>
17#include <linux/of_gpio.h>
18#include <linux/platform_data/gpio_backlight.h>
19#include <linux/platform_device.h>
20#include <linux/slab.h>
21
22struct gpio_backlight {
23	struct device *dev;
24	struct device *fbdev;
25
26	int gpio;
27	int active;
28	int def_value;
29};
30
31static int gpio_backlight_update_status(struct backlight_device *bl)
32{
33	struct gpio_backlight *gbl = bl_get_data(bl);
34	int brightness = bl->props.brightness;
35
36	if (bl->props.power != FB_BLANK_UNBLANK ||
37	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
38	    bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
39		brightness = 0;
40
41	gpio_set_value_cansleep(gbl->gpio,
42				brightness ? gbl->active : !gbl->active);
43
44	return 0;
45}
46
47static int gpio_backlight_check_fb(struct backlight_device *bl,
48				   struct fb_info *info)
49{
50	struct gpio_backlight *gbl = bl_get_data(bl);
51
52	return gbl->fbdev == NULL || gbl->fbdev == info->dev;
53}
54
55static const struct backlight_ops gpio_backlight_ops = {
56	.options	= BL_CORE_SUSPENDRESUME,
57	.update_status	= gpio_backlight_update_status,
58	.check_fb	= gpio_backlight_check_fb,
59};
60
61static int gpio_backlight_probe_dt(struct platform_device *pdev,
62				   struct gpio_backlight *gbl)
63{
64	struct device_node *np = pdev->dev.of_node;
65	enum of_gpio_flags gpio_flags;
66
67	gbl->gpio = of_get_gpio_flags(np, 0, &gpio_flags);
68
69	if (!gpio_is_valid(gbl->gpio)) {
70		if (gbl->gpio != -EPROBE_DEFER) {
71			dev_err(&pdev->dev,
72				"Error: The gpios parameter is missing or invalid.\n");
73		}
74		return gbl->gpio;
75	}
76
77	gbl->active = (gpio_flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
78
79	gbl->def_value = of_property_read_bool(np, "default-on");
80
81	return 0;
82}
83
84static int gpio_backlight_probe(struct platform_device *pdev)
85{
86	struct gpio_backlight_platform_data *pdata =
87		dev_get_platdata(&pdev->dev);
88	struct backlight_properties props;
89	struct backlight_device *bl;
90	struct gpio_backlight *gbl;
91	struct device_node *np = pdev->dev.of_node;
92	int ret;
93
94	if (!pdata && !np) {
95		dev_err(&pdev->dev,
96			"failed to find platform data or device tree node.\n");
97		return -ENODEV;
98	}
99
100	gbl = devm_kzalloc(&pdev->dev, sizeof(*gbl), GFP_KERNEL);
101	if (gbl == NULL)
102		return -ENOMEM;
103
104	gbl->dev = &pdev->dev;
105
106	if (np) {
107		ret = gpio_backlight_probe_dt(pdev, gbl);
108		if (ret)
109			return ret;
110	} else {
111		gbl->fbdev = pdata->fbdev;
112		gbl->gpio = pdata->gpio;
113		gbl->active = pdata->active_low ? 0 : 1;
114		gbl->def_value = pdata->def_value;
115	}
116
117	ret = devm_gpio_request_one(gbl->dev, gbl->gpio, GPIOF_DIR_OUT |
118				    (gbl->active ? GPIOF_INIT_LOW
119						 : GPIOF_INIT_HIGH),
120				    pdata ? pdata->name : "backlight");
121	if (ret < 0) {
122		dev_err(&pdev->dev, "unable to request GPIO\n");
123		return ret;
124	}
125
126	memset(&props, 0, sizeof(props));
127	props.type = BACKLIGHT_RAW;
128	props.max_brightness = 1;
129	bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev),
130					&pdev->dev, gbl, &gpio_backlight_ops,
131					&props);
132	if (IS_ERR(bl)) {
133		dev_err(&pdev->dev, "failed to register backlight\n");
134		return PTR_ERR(bl);
135	}
136
137	bl->props.brightness = gbl->def_value;
138	backlight_update_status(bl);
139
140	platform_set_drvdata(pdev, bl);
141	return 0;
142}
143
144#ifdef CONFIG_OF
145static struct of_device_id gpio_backlight_of_match[] = {
146	{ .compatible = "gpio-backlight" },
147	{ /* sentinel */ }
148};
149#endif
150
151static struct platform_driver gpio_backlight_driver = {
152	.driver		= {
153		.name		= "gpio-backlight",
154		.of_match_table = of_match_ptr(gpio_backlight_of_match),
155	},
156	.probe		= gpio_backlight_probe,
157};
158
159module_platform_driver(gpio_backlight_driver);
160
161MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
162MODULE_DESCRIPTION("GPIO-based Backlight Driver");
163MODULE_LICENSE("GPL");
164MODULE_ALIAS("platform:gpio-backlight");
165