2018-09-28 16:13:25 -03:00
// SPDX-License-Identifier: GPL-2.0+
2012-09-21 10:07:48 +02:00
/*
* i . MX drm driver - parallel display implementation
*
* Copyright ( C ) 2012 Sascha Hauer , Pengutronix
*/
2013-11-03 11:23:34 +00:00
# include <linux/component.h>
2012-09-21 10:07:48 +02:00
# include <linux/module.h>
2019-07-16 08:42:18 +02:00
# include <linux/platform_device.h>
# include <linux/videodev2.h>
# include <video/of_display_timing.h>
2016-07-08 17:40:56 +08:00
# include <drm/drm_atomic_helper.h>
2019-08-26 17:26:29 +02:00
# include <drm/drm_bridge.h>
2012-09-21 10:07:48 +02:00
# include <drm/drm_fb_helper.h>
2020-12-10 16:38:43 +01:00
# include <drm/drm_managed.h>
2017-03-29 13:55:46 -05:00
# include <drm/drm_of.h>
2014-02-25 11:55:04 +01:00
# include <drm/drm_panel.h>
2019-01-17 22:03:34 +01:00
# include <drm/drm_probe_helper.h>
2020-03-05 16:59:36 +01:00
# include <drm/drm_simple_kms_helper.h>
2012-09-21 10:07:48 +02:00
# include "imx-drm.h"
2020-12-10 16:38:43 +01:00
struct imx_parallel_display_encoder {
2012-09-21 10:07:48 +02:00
struct drm_connector connector ;
2016-07-06 14:49:24 +02:00
struct drm_encoder encoder ;
2020-01-28 14:55:09 +01:00
struct drm_bridge bridge ;
2020-12-10 16:38:43 +01:00
struct imx_parallel_display * pd ;
} ;
struct imx_parallel_display {
2012-09-21 10:07:48 +02:00
struct device * dev ;
void * edid ;
2016-07-06 14:49:24 +02:00
u32 bus_format ;
2016-07-12 15:30:03 +02:00
u32 bus_flags ;
2012-09-21 10:07:48 +02:00
struct drm_display_mode mode ;
2014-02-25 11:55:04 +01:00
struct drm_panel * panel ;
2020-01-28 14:55:09 +01:00
struct drm_bridge * next_bridge ;
2012-09-21 10:07:48 +02:00
} ;
2016-07-06 15:47:11 +02:00
static inline struct imx_parallel_display * con_to_imxpd ( struct drm_connector * c )
{
2020-12-10 16:38:43 +01:00
return container_of ( c , struct imx_parallel_display_encoder , connector ) - > pd ;
2016-07-06 15:47:11 +02:00
}
2020-01-28 14:55:09 +01:00
static inline struct imx_parallel_display * bridge_to_imxpd ( struct drm_bridge * b )
{
2020-12-10 16:38:43 +01:00
return container_of ( b , struct imx_parallel_display_encoder , bridge ) - > pd ;
2020-01-28 14:55:09 +01:00
}
2012-09-21 10:07:48 +02:00
static int imx_pd_connector_get_modes ( struct drm_connector * connector )
{
struct imx_parallel_display * imxpd = con_to_imxpd ( connector ) ;
2013-04-03 12:36:28 -03:00
struct device_node * np = imxpd - > dev - > of_node ;
2019-08-04 22:16:25 +02:00
int num_modes ;
2019-12-07 15:03:34 +01:00
num_modes = drm_panel_get_modes ( imxpd - > panel , connector ) ;
2019-08-04 22:16:25 +02:00
if ( num_modes > 0 )
return num_modes ;
2014-02-25 11:55:04 +01:00
2012-09-21 10:07:48 +02:00
if ( imxpd - > edid ) {
2018-07-09 10:40:06 +02:00
drm_connector_update_edid_property ( connector , imxpd - > edid ) ;
2012-09-21 10:07:48 +02:00
num_modes = drm_add_edid_modes ( connector , imxpd - > edid ) ;
}
2013-04-03 12:36:28 -03:00
if ( np ) {
struct drm_display_mode * mode = drm_mode_create ( connector - > dev ) ;
2016-07-12 15:30:01 +02:00
int ret ;
2014-08-04 21:07:07 +02:00
2014-02-26 20:53:42 -03:00
if ( ! mode )
return - EINVAL ;
2016-07-12 15:30:01 +02:00
ret = of_get_drm_display_mode ( np , & imxpd - > mode ,
2016-07-12 15:30:03 +02:00
& imxpd - > bus_flags ,
2016-07-12 15:30:01 +02:00
OF_USE_NATIVE_MODE ) ;
if ( ret )
return ret ;
2013-04-03 12:36:28 -03:00
drm_mode_copy ( mode , & imxpd - > mode ) ;
2020-12-11 16:58:52 +08:00
mode - > type | = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ;
2012-09-21 10:07:48 +02:00
drm_mode_probed_add ( connector , mode ) ;
num_modes + + ;
}
return num_modes ;
}
2020-01-28 14:55:09 +01:00
static void imx_pd_bridge_enable ( struct drm_bridge * bridge )
2012-09-21 10:07:48 +02:00
{
2020-01-28 14:55:09 +01:00
struct imx_parallel_display * imxpd = bridge_to_imxpd ( bridge ) ;
2014-11-03 15:54:47 +01:00
drm_panel_prepare ( imxpd - > panel ) ;
drm_panel_enable ( imxpd - > panel ) ;
2012-09-21 10:07:48 +02:00
}
2020-01-28 14:55:09 +01:00
static void imx_pd_bridge_disable ( struct drm_bridge * bridge )
2012-09-21 10:07:48 +02:00
{
2020-01-28 14:55:09 +01:00
struct imx_parallel_display * imxpd = bridge_to_imxpd ( bridge ) ;
2014-11-03 15:54:47 +01:00
drm_panel_disable ( imxpd - > panel ) ;
drm_panel_unprepare ( imxpd - > panel ) ;
2012-09-21 10:07:48 +02:00
}
2020-01-28 14:55:09 +01:00
static const u32 imx_pd_bus_fmts [ ] = {
MEDIA_BUS_FMT_RGB888_1X24 ,
MEDIA_BUS_FMT_BGR888_1X24 ,
MEDIA_BUS_FMT_GBR888_1X24 ,
MEDIA_BUS_FMT_RGB666_1X18 ,
MEDIA_BUS_FMT_RGB666_1X24_CPADHI ,
MEDIA_BUS_FMT_RGB565_1X16 ,
} ;
static u32 *
imx_pd_bridge_atomic_get_output_bus_fmts ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state ,
struct drm_crtc_state * crtc_state ,
struct drm_connector_state * conn_state ,
unsigned int * num_output_fmts )
2016-07-06 14:49:24 +02:00
{
struct drm_display_info * di = & conn_state - > connector - > display_info ;
2020-01-28 14:55:09 +01:00
struct imx_parallel_display * imxpd = bridge_to_imxpd ( bridge ) ;
u32 * output_fmts ;
2016-07-06 14:49:24 +02:00
2020-01-28 14:55:09 +01:00
if ( ! imxpd - > bus_format & & ! di - > num_bus_formats ) {
* num_output_fmts = ARRAY_SIZE ( imx_pd_bus_fmts ) ;
return kmemdup ( imx_pd_bus_fmts , sizeof ( imx_pd_bus_fmts ) ,
GFP_KERNEL ) ;
}
* num_output_fmts = 1 ;
output_fmts = kmalloc ( sizeof ( * output_fmts ) , GFP_KERNEL ) ;
if ( ! output_fmts )
return NULL ;
if ( ! imxpd - > bus_format & & di - > num_bus_formats )
output_fmts [ 0 ] = di - > bus_formats [ 0 ] ;
else
output_fmts [ 0 ] = imxpd - > bus_format ;
return output_fmts ;
}
static bool imx_pd_format_supported ( u32 output_fmt )
{
unsigned int i ;
for ( i = 0 ; i < ARRAY_SIZE ( imx_pd_bus_fmts ) ; i + + ) {
if ( imx_pd_bus_fmts [ i ] = = output_fmt )
return true ;
}
return false ;
}
static u32 *
imx_pd_bridge_atomic_get_input_bus_fmts ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state ,
struct drm_crtc_state * crtc_state ,
struct drm_connector_state * conn_state ,
u32 output_fmt ,
unsigned int * num_input_fmts )
{
struct imx_parallel_display * imxpd = bridge_to_imxpd ( bridge ) ;
u32 * input_fmts ;
/*
* If the next bridge does not support bus format negotiation , let ' s
* use the static bus format definition ( imxpd - > bus_format ) if it ' s
* specified , RGB888 when it ' s not .
*/
if ( output_fmt = = MEDIA_BUS_FMT_FIXED )
output_fmt = imxpd - > bus_format ? : MEDIA_BUS_FMT_RGB888_1X24 ;
/* Now make sure the requested output format is supported. */
if ( ( imxpd - > bus_format & & imxpd - > bus_format ! = output_fmt ) | |
! imx_pd_format_supported ( output_fmt ) ) {
* num_input_fmts = 0 ;
return NULL ;
}
* num_input_fmts = 1 ;
input_fmts = kmalloc ( sizeof ( * input_fmts ) , GFP_KERNEL ) ;
if ( ! input_fmts )
return NULL ;
input_fmts [ 0 ] = output_fmt ;
return input_fmts ;
}
static int imx_pd_bridge_atomic_check ( struct drm_bridge * bridge ,
struct drm_bridge_state * bridge_state ,
struct drm_crtc_state * crtc_state ,
struct drm_connector_state * conn_state )
{
struct imx_crtc_state * imx_crtc_state = to_imx_crtc_state ( crtc_state ) ;
struct drm_display_info * di = & conn_state - > connector - > display_info ;
struct imx_parallel_display * imxpd = bridge_to_imxpd ( bridge ) ;
struct drm_bridge_state * next_bridge_state = NULL ;
struct drm_bridge * next_bridge ;
u32 bus_flags , bus_fmt ;
next_bridge = drm_bridge_get_next_bridge ( bridge ) ;
if ( next_bridge )
next_bridge_state = drm_atomic_get_new_bridge_state ( crtc_state - > state ,
next_bridge ) ;
if ( next_bridge_state )
bus_flags = next_bridge_state - > input_bus_cfg . flags ;
2020-03-09 21:18:33 +01:00
else if ( di - > num_bus_formats )
2020-01-28 14:55:09 +01:00
bus_flags = di - > bus_flags ;
else
bus_flags = imxpd - > bus_flags ;
bus_fmt = bridge_state - > input_bus_cfg . format ;
if ( ! imx_pd_format_supported ( bus_fmt ) )
return - EINVAL ;
if ( bus_flags &
~ ( DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_DE_HIGH |
DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE |
DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE ) ) {
dev_warn ( imxpd - > dev , " invalid bus_flags (%x) \n " , bus_flags ) ;
return - EINVAL ;
2016-07-12 15:30:03 +02:00
}
2020-01-28 14:55:09 +01:00
bridge_state - > output_bus_cfg . flags = bus_flags ;
bridge_state - > input_bus_cfg . flags = bus_flags ;
imx_crtc_state - > bus_flags = bus_flags ;
imx_crtc_state - > bus_format = bridge_state - > input_bus_cfg . format ;
2016-07-06 14:49:24 +02:00
imx_crtc_state - > di_hsync_pin = 2 ;
imx_crtc_state - > di_vsync_pin = 3 ;
return 0 ;
}
2015-12-15 12:21:09 +01:00
static const struct drm_connector_funcs imx_pd_connector_funcs = {
2012-09-21 10:07:48 +02:00
. fill_modes = drm_helper_probe_single_connector_modes ,
2013-11-03 13:30:48 +00:00
. destroy = imx_drm_connector_destroy ,
2016-07-08 17:40:56 +08:00
. reset = drm_atomic_helper_connector_reset ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
2012-09-21 10:07:48 +02:00
} ;
2015-12-15 12:21:09 +01:00
static const struct drm_connector_helper_funcs imx_pd_connector_helper_funcs = {
2012-09-21 10:07:48 +02:00
. get_modes = imx_pd_connector_get_modes ,
} ;
2020-01-28 14:55:09 +01:00
static const struct drm_bridge_funcs imx_pd_bridge_funcs = {
. enable = imx_pd_bridge_enable ,
. disable = imx_pd_bridge_disable ,
. atomic_reset = drm_atomic_helper_bridge_reset ,
. atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_bridge_destroy_state ,
. atomic_check = imx_pd_bridge_atomic_check ,
. atomic_get_input_bus_fmts = imx_pd_bridge_atomic_get_input_bus_fmts ,
. atomic_get_output_bus_fmts = imx_pd_bridge_atomic_get_output_bus_fmts ,
2012-09-21 10:07:48 +02:00
} ;
2020-12-10 16:38:43 +01:00
static int imx_pd_bind ( struct device * dev , struct device * master , void * data )
2012-09-21 10:07:48 +02:00
{
2020-12-10 16:38:43 +01:00
struct drm_device * drm = data ;
struct imx_parallel_display * imxpd = dev_get_drvdata ( dev ) ;
struct imx_parallel_display_encoder * imxpd_encoder ;
struct drm_connector * connector ;
struct drm_encoder * encoder ;
struct drm_bridge * bridge ;
2012-09-21 10:07:48 +02:00
int ret ;
2020-12-10 16:38:43 +01:00
imxpd_encoder = drmm_simple_encoder_alloc ( drm , struct imx_parallel_display_encoder ,
encoder , DRM_MODE_ENCODER_NONE ) ;
if ( IS_ERR ( imxpd_encoder ) )
return PTR_ERR ( imxpd_encoder ) ;
imxpd_encoder - > pd = imxpd ;
connector = & imxpd_encoder - > connector ;
encoder = & imxpd_encoder - > encoder ;
bridge = & imxpd_encoder - > bridge ;
2020-12-10 16:38:39 +01:00
2016-07-06 14:49:24 +02:00
ret = imx_drm_encoder_parse_of ( drm , encoder , imxpd - > dev - > of_node ) ;
2013-11-03 13:30:48 +00:00
if ( ret )
return ret ;
2012-09-21 10:07:48 +02:00
2014-06-17 15:34:51 +02:00
/* set the connector's dpms to OFF so that
* drm_helper_connector_dpms ( ) won ' t return
* immediately since the current state is ON
* at this point .
*/
2020-12-10 16:38:38 +01:00
connector - > dpms = DRM_MODE_DPMS_OFF ;
2014-06-17 15:34:51 +02:00
2020-12-10 16:38:38 +01:00
bridge - > funcs = & imx_pd_bridge_funcs ;
drm_bridge_attach ( encoder , bridge , NULL , 0 ) ;
2020-01-28 14:55:09 +01:00
if ( imxpd - > next_bridge ) {
2020-12-10 16:38:38 +01:00
ret = drm_bridge_attach ( encoder , imxpd - > next_bridge , bridge , 0 ) ;
2021-03-23 23:50:08 +02:00
if ( ret < 0 )
2016-06-27 15:26:31 +02:00
return ret ;
} else {
2020-12-10 16:38:43 +01:00
drm_connector_helper_add ( connector ,
& imx_pd_connector_helper_funcs ) ;
drm_connector_init ( drm , connector , & imx_pd_connector_funcs ,
DRM_MODE_CONNECTOR_DPI ) ;
2020-12-10 16:38:38 +01:00
drm_connector_attach_encoder ( connector , encoder ) ;
2016-06-27 15:26:31 +02:00
}
2012-09-21 10:07:48 +02:00
return 0 ;
}
2020-12-10 16:38:39 +01:00
static const struct component_ops imx_pd_ops = {
. bind = imx_pd_bind ,
} ;
static int imx_pd_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2013-11-03 11:23:34 +00:00
struct device_node * np = dev - > of_node ;
2012-09-21 10:07:48 +02:00
const u8 * edidp ;
struct imx_parallel_display * imxpd ;
2020-09-11 14:38:55 +02:00
int edid_len ;
2012-09-21 10:07:48 +02:00
int ret ;
2016-07-08 17:40:58 +08:00
u32 bus_format = 0 ;
2012-09-21 10:07:48 +02:00
const char * fmt ;
2020-12-10 16:38:39 +01:00
imxpd = devm_kzalloc ( dev , sizeof ( * imxpd ) , GFP_KERNEL ) ;
if ( ! imxpd )
return - ENOMEM ;
2012-09-21 10:07:48 +02:00
2019-11-20 17:54:59 +01:00
/* port@1 is the output port */
ret = drm_of_find_panel_or_bridge ( np , 1 , 0 , & imxpd - > panel ,
& imxpd - > next_bridge ) ;
if ( ret & & ret ! = - ENODEV )
return ret ;
2012-09-21 10:07:48 +02:00
2020-09-11 14:38:55 +02:00
edidp = of_get_property ( np , " edid " , & edid_len ) ;
2012-09-21 10:07:48 +02:00
if ( edidp )
2020-09-11 14:38:55 +02:00
imxpd - > edid = devm_kmemdup ( dev , edidp , edid_len , GFP_KERNEL ) ;
2012-09-21 10:07:48 +02:00
ret = of_property_read_string ( np , " interface-pix-fmt " , & fmt ) ;
if ( ! ret ) {
if ( ! strcmp ( fmt , " rgb24 " ) )
2016-07-08 17:40:58 +08:00
bus_format = MEDIA_BUS_FMT_RGB888_1X24 ;
2012-09-21 10:07:48 +02:00
else if ( ! strcmp ( fmt , " rgb565 " ) )
2016-07-08 17:40:58 +08:00
bus_format = MEDIA_BUS_FMT_RGB565_1X16 ;
2013-04-22 23:19:31 +02:00
else if ( ! strcmp ( fmt , " bgr666 " ) )
2016-07-08 17:40:58 +08:00
bus_format = MEDIA_BUS_FMT_RGB666_1X18 ;
2014-03-29 13:57:44 +01:00
else if ( ! strcmp ( fmt , " lvds666 " ) )
2016-07-08 17:40:58 +08:00
bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI ;
2012-09-21 10:07:48 +02:00
}
2016-07-06 14:49:24 +02:00
imxpd - > bus_format = bus_format ;
2012-09-21 10:07:48 +02:00
2013-11-03 11:23:34 +00:00
imxpd - > dev = dev ;
2012-09-21 10:07:48 +02:00
2020-06-11 14:43:31 +02:00
platform_set_drvdata ( pdev , imxpd ) ;
2020-12-10 16:38:39 +01:00
return component_add ( dev , & imx_pd_ops ) ;
2013-11-03 11:23:34 +00:00
}
static int imx_pd_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & imx_pd_ops ) ;
2013-12-08 22:03:58 +01:00
2012-09-21 10:07:48 +02:00
return 0 ;
}
static const struct of_device_id imx_pd_dt_ids [ ] = {
{ . compatible = " fsl,imx-parallel-display " , } ,
{ /* sentinel */ }
} ;
2013-06-07 18:36:46 +09:00
MODULE_DEVICE_TABLE ( of , imx_pd_dt_ids ) ;
2012-09-21 10:07:48 +02:00
static struct platform_driver imx_pd_driver = {
. probe = imx_pd_probe ,
2012-11-19 13:20:51 -05:00
. remove = imx_pd_remove ,
2012-09-21 10:07:48 +02:00
. driver = {
. of_match_table = imx_pd_dt_ids ,
. name = " imx-parallel-display " ,
} ,
} ;
module_platform_driver ( imx_pd_driver ) ;
MODULE_DESCRIPTION ( " i.MX parallel display driver " ) ;
MODULE_AUTHOR ( " Sascha Hauer, Pengutronix " ) ;
MODULE_LICENSE ( " GPL " ) ;
2013-08-18 21:40:05 -03:00
MODULE_ALIAS ( " platform:imx-parallel-display " ) ;