2018-08-06 03:18:22 +00:00
// SPDX-License-Identifier: GPL-2.0+
2016-11-18 02:58:18 +02:00
/*
2018-01-22 18:01:42 +02:00
* Generic LVDS panel driver
2016-11-18 02:58:18 +02:00
*
* Copyright ( C ) 2016 Laurent Pinchart
* Copyright ( C ) 2016 Renesas Electronics Corporation
*
* Contact : Laurent Pinchart ( laurent . pinchart @ ideasonboard . com )
*/
# include <linux/gpio/consumer.h>
# include <linux/module.h>
2023-07-14 11:45:34 -06:00
# include <linux/of.h>
2016-11-18 02:58:18 +02:00
# include <linux/platform_device.h>
2017-12-21 12:02:28 +01:00
# include <linux/regulator/consumer.h>
2016-11-18 02:58:18 +02:00
# include <linux/slab.h>
# include <video/display_timing.h>
# include <video/of_display_timing.h>
# include <video/videomode.h>
2019-05-26 20:05:32 +02:00
# include <drm/drm_crtc.h>
2021-10-13 00:42:52 +02:00
# include <drm/drm_of.h>
2019-05-26 20:05:32 +02:00
# include <drm/drm_panel.h>
2016-11-18 02:58:18 +02:00
struct panel_lvds {
struct drm_panel panel ;
struct device * dev ;
const char * label ;
unsigned int width ;
unsigned int height ;
2022-04-01 18:21:53 +02:00
struct drm_display_mode dmode ;
u32 bus_flags ;
2016-11-18 02:58:18 +02:00
unsigned int bus_format ;
2017-12-21 12:02:28 +01:00
struct regulator * supply ;
2016-11-18 02:58:18 +02:00
struct gpio_desc * enable_gpio ;
struct gpio_desc * reset_gpio ;
2020-08-14 00:56:08 +03:00
enum drm_panel_orientation orientation ;
2016-11-18 02:58:18 +02:00
} ;
static inline struct panel_lvds * to_panel_lvds ( struct drm_panel * panel )
{
return container_of ( panel , struct panel_lvds , panel ) ;
}
static int panel_lvds_unprepare ( struct drm_panel * panel )
{
struct panel_lvds * lvds = to_panel_lvds ( panel ) ;
if ( lvds - > enable_gpio )
gpiod_set_value_cansleep ( lvds - > enable_gpio , 0 ) ;
2017-12-21 12:02:28 +01:00
if ( lvds - > supply )
regulator_disable ( lvds - > supply ) ;
2016-11-18 02:58:18 +02:00
return 0 ;
}
static int panel_lvds_prepare ( struct drm_panel * panel )
{
struct panel_lvds * lvds = to_panel_lvds ( panel ) ;
2017-12-21 12:02:28 +01:00
if ( lvds - > supply ) {
int err ;
err = regulator_enable ( lvds - > supply ) ;
if ( err < 0 ) {
dev_err ( lvds - > dev , " failed to enable supply: %d \n " ,
err ) ;
return err ;
}
}
2016-11-18 02:58:18 +02:00
if ( lvds - > enable_gpio )
gpiod_set_value_cansleep ( lvds - > enable_gpio , 1 ) ;
return 0 ;
}
2019-12-07 15:03:33 +01:00
static int panel_lvds_get_modes ( struct drm_panel * panel ,
struct drm_connector * connector )
2016-11-18 02:58:18 +02:00
{
struct panel_lvds * lvds = to_panel_lvds ( panel ) ;
struct drm_display_mode * mode ;
2022-04-01 18:21:53 +02:00
mode = drm_mode_duplicate ( connector - > dev , & lvds - > dmode ) ;
2016-11-18 02:58:18 +02:00
if ( ! mode )
return 0 ;
mode - > type | = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ;
drm_mode_probed_add ( connector , mode ) ;
2022-04-01 18:21:53 +02:00
connector - > display_info . width_mm = lvds - > dmode . width_mm ;
connector - > display_info . height_mm = lvds - > dmode . height_mm ;
2016-11-18 02:58:18 +02:00
drm_display_info_set_bus_formats ( & connector - > display_info ,
& lvds - > bus_format , 1 ) ;
2022-04-01 18:21:54 +02:00
connector - > display_info . bus_flags = lvds - > bus_flags ;
2022-06-09 15:27:18 +08:00
/*
* TODO : Remove once all drm drivers call
* drm_connector_set_orientation_from_panel ( )
*/
2020-08-14 00:56:08 +03:00
drm_connector_set_panel_orientation ( connector , lvds - > orientation ) ;
2016-11-18 02:58:18 +02:00
return 1 ;
}
2022-06-09 15:27:18 +08:00
static enum drm_panel_orientation panel_lvds_get_orientation ( struct drm_panel * panel )
{
struct panel_lvds * lvds = to_panel_lvds ( panel ) ;
return lvds - > orientation ;
}
2016-11-18 02:58:18 +02:00
static const struct drm_panel_funcs panel_lvds_funcs = {
. unprepare = panel_lvds_unprepare ,
. prepare = panel_lvds_prepare ,
. get_modes = panel_lvds_get_modes ,
2022-06-09 15:27:18 +08:00
. get_orientation = panel_lvds_get_orientation ,
2016-11-18 02:58:18 +02:00
} ;
static int panel_lvds_parse_dt ( struct panel_lvds * lvds )
{
struct device_node * np = lvds - > dev - > of_node ;
int ret ;
2020-08-14 00:56:08 +03:00
ret = of_drm_get_panel_orientation ( np , & lvds - > orientation ) ;
if ( ret < 0 ) {
dev_err ( lvds - > dev , " %pOF: failed to get orientation %d \n " , np , ret ) ;
return ret ;
}
2022-04-01 18:21:53 +02:00
ret = of_get_drm_panel_display_mode ( np , & lvds - > dmode , & lvds - > bus_flags ) ;
2019-07-22 11:24:38 -07:00
if ( ret < 0 ) {
dev_err ( lvds - > dev , " %pOF: problems parsing panel-timing (%d) \n " ,
np , ret ) ;
2016-11-18 02:58:18 +02:00
return ret ;
2019-07-22 11:24:38 -07:00
}
2016-11-18 02:58:18 +02:00
of_property_read_string ( np , " label " , & lvds - > label ) ;
2021-10-13 00:42:52 +02:00
ret = drm_of_lvds_get_data_mapping ( np ) ;
2016-11-18 02:58:18 +02:00
if ( ret < 0 ) {
2017-07-18 16:43:04 -05:00
dev_err ( lvds - > dev , " %pOF: invalid or missing %s DT property \n " ,
np , " data-mapping " ) ;
2021-10-13 00:42:52 +02:00
return ret ;
2016-11-18 02:58:18 +02:00
}
2021-10-13 00:42:52 +02:00
lvds - > bus_format = ret ;
2016-11-18 02:58:18 +02:00
2022-04-01 18:21:54 +02:00
lvds - > bus_flags | = of_property_read_bool ( np , " data-mirror " ) ?
DRM_BUS_FLAG_DATA_LSB_TO_MSB :
DRM_BUS_FLAG_DATA_MSB_TO_LSB ;
2016-11-18 02:58:18 +02:00
return 0 ;
}
static int panel_lvds_probe ( struct platform_device * pdev )
{
struct panel_lvds * lvds ;
int ret ;
lvds = devm_kzalloc ( & pdev - > dev , sizeof ( * lvds ) , GFP_KERNEL ) ;
if ( ! lvds )
return - ENOMEM ;
lvds - > dev = & pdev - > dev ;
ret = panel_lvds_parse_dt ( lvds ) ;
if ( ret < 0 )
return ret ;
2017-12-21 12:02:28 +01:00
lvds - > supply = devm_regulator_get_optional ( lvds - > dev , " power " ) ;
if ( IS_ERR ( lvds - > supply ) ) {
ret = PTR_ERR ( lvds - > supply ) ;
2018-01-10 16:59:41 +01:00
if ( ret ! = - ENODEV ) {
if ( ret ! = - EPROBE_DEFER )
dev_err ( lvds - > dev , " failed to request regulator: %d \n " ,
ret ) ;
return ret ;
}
lvds - > supply = NULL ;
2017-12-21 12:02:28 +01:00
}
2016-11-18 02:58:18 +02:00
/* Get GPIOs and backlight controller. */
lvds - > enable_gpio = devm_gpiod_get_optional ( lvds - > dev , " enable " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( lvds - > enable_gpio ) ) {
ret = PTR_ERR ( lvds - > enable_gpio ) ;
dev_err ( lvds - > dev , " failed to request %s GPIO: %d \n " ,
" enable " , ret ) ;
return ret ;
}
lvds - > reset_gpio = devm_gpiod_get_optional ( lvds - > dev , " reset " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( lvds - > reset_gpio ) ) {
ret = PTR_ERR ( lvds - > reset_gpio ) ;
dev_err ( lvds - > dev , " failed to request %s GPIO: %d \n " ,
" reset " , ret ) ;
return ret ;
}
/*
* TODO : Handle all power supplies specified in the DT node in a generic
* way for panels that don ' t care about power supply ordering . LVDS
* panels that require a specific power sequence will need a dedicated
* driver .
*/
/* Register the panel. */
2019-09-04 16:28:03 +03:00
drm_panel_init ( & lvds - > panel , lvds - > dev , & panel_lvds_funcs ,
DRM_MODE_CONNECTOR_LVDS ) ;
2016-11-18 02:58:18 +02:00
2019-12-07 15:03:40 +01:00
ret = drm_panel_of_backlight ( & lvds - > panel ) ;
if ( ret )
return ret ;
2020-08-01 20:02:13 +08:00
drm_panel_add ( & lvds - > panel ) ;
2016-11-18 02:58:18 +02:00
dev_set_drvdata ( lvds - > dev , lvds ) ;
return 0 ;
}
2023-05-07 18:25:58 +02:00
static void panel_lvds_remove ( struct platform_device * pdev )
2016-11-18 02:58:18 +02:00
{
2021-02-09 22:13:04 +01:00
struct panel_lvds * lvds = platform_get_drvdata ( pdev ) ;
2016-11-18 02:58:18 +02:00
drm_panel_remove ( & lvds - > panel ) ;
2019-12-07 15:03:40 +01:00
drm_panel_disable ( & lvds - > panel ) ;
2016-11-18 02:58:18 +02:00
}
static const struct of_device_id panel_lvds_of_table [ ] = {
{ . compatible = " panel-lvds " , } ,
{ /* Sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , panel_lvds_of_table ) ;
static struct platform_driver panel_lvds_driver = {
. probe = panel_lvds_probe ,
2023-05-07 18:25:58 +02:00
. remove_new = panel_lvds_remove ,
2016-11-18 02:58:18 +02:00
. driver = {
. name = " panel-lvds " ,
. of_match_table = panel_lvds_of_table ,
} ,
} ;
module_platform_driver ( panel_lvds_driver ) ;
MODULE_AUTHOR ( " Laurent Pinchart <laurent.pinchart@ideasonboard.com> " ) ;
MODULE_DESCRIPTION ( " LVDS Panel Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;