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/gpio/consumer.h>
12 #include <linux/module.h>
13 #include <linux/of_platform.h>
14 #include <linux/platform_device.h>
15 #include <linux/regulator/consumer.h>
16 #include <linux/slab.h>
18 #include <video/display_timing.h>
19 #include <video/of_display_timing.h>
20 #include <video/videomode.h>
22 #include <drm/drm_crtc.h>
23 #include <drm/drm_panel.h>
26 struct drm_panel panel
;
32 struct videomode video_mode
;
33 unsigned int bus_format
;
36 struct regulator
*supply
;
38 struct gpio_desc
*enable_gpio
;
39 struct gpio_desc
*reset_gpio
;
41 enum drm_panel_orientation orientation
;
44 static inline struct panel_lvds
*to_panel_lvds(struct drm_panel
*panel
)
46 return container_of(panel
, struct panel_lvds
, panel
);
49 static int panel_lvds_unprepare(struct drm_panel
*panel
)
51 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
53 if (lvds
->enable_gpio
)
54 gpiod_set_value_cansleep(lvds
->enable_gpio
, 0);
57 regulator_disable(lvds
->supply
);
62 static int panel_lvds_prepare(struct drm_panel
*panel
)
64 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
69 err
= regulator_enable(lvds
->supply
);
71 dev_err(lvds
->dev
, "failed to enable supply: %d\n",
77 if (lvds
->enable_gpio
)
78 gpiod_set_value_cansleep(lvds
->enable_gpio
, 1);
83 static int panel_lvds_get_modes(struct drm_panel
*panel
,
84 struct drm_connector
*connector
)
86 struct panel_lvds
*lvds
= to_panel_lvds(panel
);
87 struct drm_display_mode
*mode
;
89 mode
= drm_mode_create(connector
->dev
);
93 drm_display_mode_from_videomode(&lvds
->video_mode
, mode
);
94 mode
->type
|= DRM_MODE_TYPE_DRIVER
| DRM_MODE_TYPE_PREFERRED
;
95 drm_mode_probed_add(connector
, mode
);
97 connector
->display_info
.width_mm
= lvds
->width
;
98 connector
->display_info
.height_mm
= lvds
->height
;
99 drm_display_info_set_bus_formats(&connector
->display_info
,
100 &lvds
->bus_format
, 1);
101 connector
->display_info
.bus_flags
= lvds
->data_mirror
102 ? DRM_BUS_FLAG_DATA_LSB_TO_MSB
103 : DRM_BUS_FLAG_DATA_MSB_TO_LSB
;
104 drm_connector_set_panel_orientation(connector
, lvds
->orientation
);
109 static const struct drm_panel_funcs panel_lvds_funcs
= {
110 .unprepare
= panel_lvds_unprepare
,
111 .prepare
= panel_lvds_prepare
,
112 .get_modes
= panel_lvds_get_modes
,
115 static int panel_lvds_parse_dt(struct panel_lvds
*lvds
)
117 struct device_node
*np
= lvds
->dev
->of_node
;
118 struct display_timing timing
;
122 ret
= of_drm_get_panel_orientation(np
, &lvds
->orientation
);
124 dev_err(lvds
->dev
, "%pOF: failed to get orientation %d\n", np
, ret
);
128 ret
= of_get_display_timing(np
, "panel-timing", &timing
);
130 dev_err(lvds
->dev
, "%pOF: problems parsing panel-timing (%d)\n",
135 videomode_from_timing(&timing
, &lvds
->video_mode
);
137 ret
= of_property_read_u32(np
, "width-mm", &lvds
->width
);
139 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
143 ret
= of_property_read_u32(np
, "height-mm", &lvds
->height
);
145 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
150 of_property_read_string(np
, "label", &lvds
->label
);
152 ret
= of_property_read_string(np
, "data-mapping", &mapping
);
154 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
159 if (!strcmp(mapping
, "jeida-18")) {
160 lvds
->bus_format
= MEDIA_BUS_FMT_RGB666_1X7X3_SPWG
;
161 } else if (!strcmp(mapping
, "jeida-24")) {
162 lvds
->bus_format
= MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA
;
163 } else if (!strcmp(mapping
, "vesa-24")) {
164 lvds
->bus_format
= MEDIA_BUS_FMT_RGB888_1X7X4_SPWG
;
166 dev_err(lvds
->dev
, "%pOF: invalid or missing %s DT property\n",
171 lvds
->data_mirror
= of_property_read_bool(np
, "data-mirror");
176 static int panel_lvds_probe(struct platform_device
*pdev
)
178 struct panel_lvds
*lvds
;
181 lvds
= devm_kzalloc(&pdev
->dev
, sizeof(*lvds
), GFP_KERNEL
);
185 lvds
->dev
= &pdev
->dev
;
187 ret
= panel_lvds_parse_dt(lvds
);
191 lvds
->supply
= devm_regulator_get_optional(lvds
->dev
, "power");
192 if (IS_ERR(lvds
->supply
)) {
193 ret
= PTR_ERR(lvds
->supply
);
195 if (ret
!= -ENODEV
) {
196 if (ret
!= -EPROBE_DEFER
)
197 dev_err(lvds
->dev
, "failed to request regulator: %d\n",
205 /* Get GPIOs and backlight controller. */
206 lvds
->enable_gpio
= devm_gpiod_get_optional(lvds
->dev
, "enable",
208 if (IS_ERR(lvds
->enable_gpio
)) {
209 ret
= PTR_ERR(lvds
->enable_gpio
);
210 dev_err(lvds
->dev
, "failed to request %s GPIO: %d\n",
215 lvds
->reset_gpio
= devm_gpiod_get_optional(lvds
->dev
, "reset",
217 if (IS_ERR(lvds
->reset_gpio
)) {
218 ret
= PTR_ERR(lvds
->reset_gpio
);
219 dev_err(lvds
->dev
, "failed to request %s GPIO: %d\n",
225 * TODO: Handle all power supplies specified in the DT node in a generic
226 * way for panels that don't care about power supply ordering. LVDS
227 * panels that require a specific power sequence will need a dedicated
231 /* Register the panel. */
232 drm_panel_init(&lvds
->panel
, lvds
->dev
, &panel_lvds_funcs
,
233 DRM_MODE_CONNECTOR_LVDS
);
235 ret
= drm_panel_of_backlight(&lvds
->panel
);
239 drm_panel_add(&lvds
->panel
);
241 dev_set_drvdata(lvds
->dev
, lvds
);
245 static int panel_lvds_remove(struct platform_device
*pdev
)
247 struct panel_lvds
*lvds
= dev_get_drvdata(&pdev
->dev
);
249 drm_panel_remove(&lvds
->panel
);
251 drm_panel_disable(&lvds
->panel
);
256 static const struct of_device_id panel_lvds_of_table
[] = {
257 { .compatible
= "panel-lvds", },
261 MODULE_DEVICE_TABLE(of
, panel_lvds_of_table
);
263 static struct platform_driver panel_lvds_driver
= {
264 .probe
= panel_lvds_probe
,
265 .remove
= panel_lvds_remove
,
267 .name
= "panel-lvds",
268 .of_match_table
= panel_lvds_of_table
,
272 module_platform_driver(panel_lvds_driver
);
274 MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
275 MODULE_DESCRIPTION("LVDS Panel Driver");
276 MODULE_LICENSE("GPL");