1 // SPDX-License-Identifier: GPL-2.0+
3 * Generic LVDS panel driver
5 * Copyright (C) 2016 Laurent Pinchart
6 * Copyright (C) 2016 Renesas Electronics Corporation
8 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
11 #include <linux/backlight.h>
12 #include <linux/gpio/consumer.h>
13 #include <linux/module.h>
14 #include <linux/of_platform.h>
15 #include <linux/platform_device.h>
16 #include <linux/regulator/consumer.h>
17 #include <linux/slab.h>
20 #include <drm/drm_crtc.h>
21 #include <drm/drm_panel.h>
23 #include <video/display_timing.h>
24 #include <video/of_display_timing.h>
25 #include <video/videomode.h>
28 struct drm_panel panel
;
34 struct videomode video_mode
;
35 unsigned int bus_format
;
38 struct backlight_device
*backlight
;
39 struct regulator
*supply
;
41 struct gpio_desc
*enable_gpio
;
42 struct gpio_desc
*reset_gpio
;
45 static inline struct panel_lvds
*to_panel_lvds(struct drm_panel
*panel
)
47 return container_of(panel
, struct panel_lvds
, panel
);
50 static int panel_lvds_disable(struct drm_panel
*panel
)
52 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
54 if (lvds
->backlight
) {
55 lvds
->backlight
->props
.power
= FB_BLANK_POWERDOWN
;
56 lvds
->backlight
->props
.state
|= BL_CORE_FBBLANK
;
57 backlight_update_status(lvds
->backlight
);
63 static int panel_lvds_unprepare(struct drm_panel
*panel
)
65 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
67 if (lvds
->enable_gpio
)
68 gpiod_set_value_cansleep(lvds
->enable_gpio
, 0);
71 regulator_disable(lvds
->supply
);
76 static int panel_lvds_prepare(struct drm_panel
*panel
)
78 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
83 err
= regulator_enable(lvds
->supply
);
85 dev_err(lvds
->dev
, "failed to enable supply: %d\n",
91 if (lvds
->enable_gpio
)
92 gpiod_set_value_cansleep(lvds
->enable_gpio
, 1);
97 static int panel_lvds_enable(struct drm_panel
*panel
)
99 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
101 if (lvds
->backlight
) {
102 lvds
->backlight
->props
.state
&= ~BL_CORE_FBBLANK
;
103 lvds
->backlight
->props
.power
= FB_BLANK_UNBLANK
;
104 backlight_update_status(lvds
->backlight
);
110 static int panel_lvds_get_modes(struct drm_panel
*panel
)
112 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
113 struct drm_connector
*connector
= lvds
->panel
.connector
;
114 struct drm_display_mode
*mode
;
116 mode
= drm_mode_create(lvds
->panel
.drm
);
120 drm_display_mode_from_videomode(&lvds
->video_mode
, mode
);
121 mode
->type
|= DRM_MODE_TYPE_DRIVER
| DRM_MODE_TYPE_PREFERRED
;
122 drm_mode_probed_add(connector
, mode
);
124 connector
->display_info
.width_mm
= lvds
->width
;
125 connector
->display_info
.height_mm
= lvds
->height
;
126 drm_display_info_set_bus_formats(&connector
->display_info
,
127 &lvds
->bus_format
, 1);
128 connector
->display_info
.bus_flags
= lvds
->data_mirror
129 ? DRM_BUS_FLAG_DATA_LSB_TO_MSB
130 : DRM_BUS_FLAG_DATA_MSB_TO_LSB
;
135 static const struct drm_panel_funcs panel_lvds_funcs
= {
136 .disable
= panel_lvds_disable
,
137 .unprepare
= panel_lvds_unprepare
,
138 .prepare
= panel_lvds_prepare
,
139 .enable
= panel_lvds_enable
,
140 .get_modes
= panel_lvds_get_modes
,
143 static int panel_lvds_parse_dt(struct panel_lvds
*lvds
)
145 struct device_node
*np
= lvds
->dev
->of_node
;
146 struct display_timing timing
;
150 ret
= of_get_display_timing(np
, "panel-timing", &timing
);
154 videomode_from_timing(&timing
, &lvds
->video_mode
);
156 ret
= of_property_read_u32(np
, "width-mm", &lvds
->width
);
158 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
162 ret
= of_property_read_u32(np
, "height-mm", &lvds
->height
);
164 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
169 of_property_read_string(np
, "label", &lvds
->label
);
171 ret
= of_property_read_string(np
, "data-mapping", &mapping
);
173 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
178 if (!strcmp(mapping
, "jeida-18")) {
179 lvds
->bus_format
= MEDIA_BUS_FMT_RGB666_1X7X3_SPWG
;
180 } else if (!strcmp(mapping
, "jeida-24")) {
181 lvds
->bus_format
= MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA
;
182 } else if (!strcmp(mapping
, "vesa-24")) {
183 lvds
->bus_format
= MEDIA_BUS_FMT_RGB888_1X7X4_SPWG
;
185 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
190 lvds
->data_mirror
= of_property_read_bool(np
, "data-mirror");
195 static int panel_lvds_probe(struct platform_device
*pdev
)
197 struct panel_lvds
*lvds
;
198 struct device_node
*np
;
201 lvds
= devm_kzalloc(&pdev
->dev
, sizeof(*lvds
), GFP_KERNEL
);
205 lvds
->dev
= &pdev
->dev
;
207 ret
= panel_lvds_parse_dt(lvds
);
211 lvds
->supply
= devm_regulator_get_optional(lvds
->dev
, "power");
212 if (IS_ERR(lvds
->supply
)) {
213 ret
= PTR_ERR(lvds
->supply
);
215 if (ret
!= -ENODEV
) {
216 if (ret
!= -EPROBE_DEFER
)
217 dev_err(lvds
->dev
, "failed to request regulator: %d\n",
225 /* Get GPIOs and backlight controller. */
226 lvds
->enable_gpio
= devm_gpiod_get_optional(lvds
->dev
, "enable",
228 if (IS_ERR(lvds
->enable_gpio
)) {
229 ret
= PTR_ERR(lvds
->enable_gpio
);
230 dev_err(lvds
->dev
, "failed to request %s GPIO: %d\n",
235 lvds
->reset_gpio
= devm_gpiod_get_optional(lvds
->dev
, "reset",
237 if (IS_ERR(lvds
->reset_gpio
)) {
238 ret
= PTR_ERR(lvds
->reset_gpio
);
239 dev_err(lvds
->dev
, "failed to request %s GPIO: %d\n",
244 np
= of_parse_phandle(lvds
->dev
->of_node
, "backlight", 0);
246 lvds
->backlight
= of_find_backlight_by_node(np
);
249 if (!lvds
->backlight
)
250 return -EPROBE_DEFER
;
254 * TODO: Handle all power supplies specified in the DT node in a generic
255 * way for panels that don't care about power supply ordering. LVDS
256 * panels that require a specific power sequence will need a dedicated
260 /* Register the panel. */
261 drm_panel_init(&lvds
->panel
);
262 lvds
->panel
.dev
= lvds
->dev
;
263 lvds
->panel
.funcs
= &panel_lvds_funcs
;
265 ret
= drm_panel_add(&lvds
->panel
);
269 dev_set_drvdata(lvds
->dev
, lvds
);
273 put_device(&lvds
->backlight
->dev
);
277 static int panel_lvds_remove(struct platform_device
*pdev
)
279 struct panel_lvds
*lvds
= dev_get_drvdata(&pdev
->dev
);
281 drm_panel_remove(&lvds
->panel
);
283 panel_lvds_disable(&lvds
->panel
);
286 put_device(&lvds
->backlight
->dev
);
291 static const struct of_device_id panel_lvds_of_table
[] = {
292 { .compatible
= "panel-lvds", },
296 MODULE_DEVICE_TABLE(of
, panel_lvds_of_table
);
298 static struct platform_driver panel_lvds_driver
= {
299 .probe
= panel_lvds_probe
,
300 .remove
= panel_lvds_remove
,
302 .name
= "panel-lvds",
303 .of_match_table
= panel_lvds_of_table
,
307 module_platform_driver(panel_lvds_driver
);
309 MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
310 MODULE_DESCRIPTION("LVDS Panel Driver");
311 MODULE_LICENSE("GPL");