2022-06-03 19:33:49 +05:30
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright ( C ) 2021 RenewOutReach
* Copyright ( C ) 2021 Amarula Solutions ( India )
*
* Author :
* Jagan Teki < jagan @ amarulasolutions . com >
* Christopher Vollo < chris @ renewoutreach . org >
*/
# include <drm/drm_atomic_helper.h>
# include <drm/drm_of.h>
# include <drm/drm_print.h>
# include <drm/drm_mipi_dsi.h>
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/i2c.h>
2022-06-30 22:51:13 +03:00
# include <linux/media-bus-format.h>
2022-06-03 19:33:49 +05:30
# include <linux/module.h>
# include <linux/regmap.h>
# include <linux/regulator/consumer.h>
enum cmd_registers {
WR_INPUT_SOURCE = 0x05 , /* Write Input Source Select */
WR_EXT_SOURCE_FMT = 0x07 , /* Write External Video Source Format */
WR_IMAGE_CROP = 0x10 , /* Write Image Crop */
WR_DISPLAY_SIZE = 0x12 , /* Write Display Size */
WR_IMAGE_FREEZE = 0x1A , /* Write Image Freeze */
WR_INPUT_IMAGE_SIZE = 0x2E , /* Write External Input Image Size */
WR_RGB_LED_EN = 0x52 , /* Write RGB LED Enable */
WR_RGB_LED_CURRENT = 0x54 , /* Write RGB LED Current */
WR_RGB_LED_MAX_CURRENT = 0x5C , /* Write RGB LED Max Current */
WR_DSI_HS_CLK = 0xBD , /* Write DSI HS Clock */
RD_DEVICE_ID = 0xD4 , /* Read Controller Device ID */
WR_DSI_PORT_EN = 0xD7 , /* Write DSI Port Enable */
} ;
enum input_source {
INPUT_EXTERNAL_VIDEO = 0 ,
INPUT_TEST_PATTERN ,
INPUT_SPLASH_SCREEN ,
} ;
# define DEV_ID_MASK GENMASK(3, 0)
# define IMAGE_FREESE_EN BIT(0)
# define DSI_PORT_EN 0
# define EXT_SOURCE_FMT_DSI 0
# define RED_LED_EN BIT(0)
# define GREEN_LED_EN BIT(1)
# define BLUE_LED_EN BIT(2)
# define LED_MASK GENMASK(2, 0)
# define MAX_BYTE_SIZE 8
struct dlpc {
struct device * dev ;
struct drm_bridge bridge ;
struct drm_bridge * next_bridge ;
struct device_node * host_node ;
struct mipi_dsi_device * dsi ;
struct drm_display_mode mode ;
struct gpio_desc * enable_gpio ;
struct regulator * vcc_intf ;
struct regulator * vcc_flsh ;
struct regmap * regmap ;
unsigned int dsi_lanes ;
} ;
static inline struct dlpc * bridge_to_dlpc ( struct drm_bridge * bridge )
{
return container_of ( bridge , struct dlpc , bridge ) ;
}
static bool dlpc_writeable_noinc_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case WR_IMAGE_CROP :
case WR_DISPLAY_SIZE :
case WR_INPUT_IMAGE_SIZE :
case WR_DSI_HS_CLK :
return true ;
default :
return false ;
}
}
static const struct regmap_range dlpc_volatile_ranges [ ] = {
{ . range_min = 0x10 , . range_max = 0xBF } ,
} ;
static const struct regmap_access_table dlpc_volatile_table = {
. yes_ranges = dlpc_volatile_ranges ,
. n_yes_ranges = ARRAY_SIZE ( dlpc_volatile_ranges ) ,
} ;
static struct regmap_config dlpc_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = WR_DSI_PORT_EN ,
. writeable_noinc_reg = dlpc_writeable_noinc_reg ,
. volatile_table = & dlpc_volatile_table ,
. cache_type = REGCACHE_RBTREE ,
. name = " dlpc3433 " ,
} ;
static void dlpc_atomic_enable ( struct drm_bridge * bridge ,
struct drm_bridge_state * old_bridge_state )
{
struct dlpc * dlpc = bridge_to_dlpc ( bridge ) ;
struct device * dev = dlpc - > dev ;
struct drm_display_mode * mode = & dlpc - > mode ;
struct regmap * regmap = dlpc - > regmap ;
char buf [ MAX_BYTE_SIZE ] ;
unsigned int devid ;
regmap_read ( regmap , RD_DEVICE_ID , & devid ) ;
devid & = DEV_ID_MASK ;
DRM_DEV_DEBUG ( dev , " DLPC3433 device id: 0x%02x \n " , devid ) ;
if ( devid ! = 0x01 ) {
DRM_DEV_ERROR ( dev , " Unsupported DLPC device id: 0x%02x \n " , devid ) ;
return ;
}
/* disable image freeze */
regmap_write ( regmap , WR_IMAGE_FREEZE , IMAGE_FREESE_EN ) ;
/* enable DSI port */
regmap_write ( regmap , WR_DSI_PORT_EN , DSI_PORT_EN ) ;
memset ( buf , 0 , MAX_BYTE_SIZE ) ;
/* set image crop */
buf [ 4 ] = mode - > hdisplay & 0xff ;
buf [ 5 ] = ( mode - > hdisplay & 0xff00 ) > > 8 ;
buf [ 6 ] = mode - > vdisplay & 0xff ;
buf [ 7 ] = ( mode - > vdisplay & 0xff00 ) > > 8 ;
regmap_noinc_write ( regmap , WR_IMAGE_CROP , buf , MAX_BYTE_SIZE ) ;
/* set display size */
buf [ 4 ] = mode - > hdisplay & 0xff ;
buf [ 5 ] = ( mode - > hdisplay & 0xff00 ) > > 8 ;
buf [ 6 ] = mode - > vdisplay & 0xff ;
buf [ 7 ] = ( mode - > vdisplay & 0xff00 ) > > 8 ;
regmap_noinc_write ( regmap , WR_DISPLAY_SIZE , buf , MAX_BYTE_SIZE ) ;
/* set input image size */
buf [ 0 ] = mode - > hdisplay & 0xff ;
buf [ 1 ] = ( mode - > hdisplay & 0xff00 ) > > 8 ;
buf [ 2 ] = mode - > vdisplay & 0xff ;
buf [ 3 ] = ( mode - > vdisplay & 0xff00 ) > > 8 ;
regmap_noinc_write ( regmap , WR_INPUT_IMAGE_SIZE , buf , 4 ) ;
/* set external video port */
regmap_write ( regmap , WR_INPUT_SOURCE , INPUT_EXTERNAL_VIDEO ) ;
/* set external video format select as DSI */
regmap_write ( regmap , WR_EXT_SOURCE_FMT , EXT_SOURCE_FMT_DSI ) ;
/* disable image freeze */
regmap_write ( regmap , WR_IMAGE_FREEZE , 0x00 ) ;
/* enable RGB led */
regmap_update_bits ( regmap , WR_RGB_LED_EN , LED_MASK ,
RED_LED_EN | GREEN_LED_EN | BLUE_LED_EN ) ;
msleep ( 10 ) ;
}
static void dlpc_atomic_pre_enable ( struct drm_bridge * bridge ,
struct drm_bridge_state * old_bridge_state )
{
struct dlpc * dlpc = bridge_to_dlpc ( bridge ) ;
int ret ;
gpiod_set_value ( dlpc - > enable_gpio , 1 ) ;
msleep ( 500 ) ;
ret = regulator_enable ( dlpc - > vcc_intf ) ;
if ( ret )
DRM_DEV_ERROR ( dlpc - > dev ,
" failed to enable VCC_INTF regulator: %d \n " , ret ) ;
ret = regulator_enable ( dlpc - > vcc_flsh ) ;
if ( ret )
DRM_DEV_ERROR ( dlpc - > dev ,
" failed to enable VCC_FLSH regulator: %d \n " , ret ) ;
msleep ( 10 ) ;
}
static void dlpc_atomic_post_disable ( struct drm_bridge * bridge ,
struct drm_bridge_state * old_bridge_state )
{
struct dlpc * dlpc = bridge_to_dlpc ( bridge ) ;
regulator_disable ( dlpc - > vcc_flsh ) ;
regulator_disable ( dlpc - > vcc_intf ) ;
msleep ( 10 ) ;
gpiod_set_value ( dlpc - > enable_gpio , 0 ) ;
msleep ( 500 ) ;
}
# define MAX_INPUT_SEL_FORMATS 1
static u32 *
dlpc_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 )
{
u32 * input_fmts ;
* num_input_fmts = 0 ;
input_fmts = kcalloc ( MAX_INPUT_SEL_FORMATS , sizeof ( * input_fmts ) ,
GFP_KERNEL ) ;
if ( ! input_fmts )
return NULL ;
/* This is the DSI-end bus format */
input_fmts [ 0 ] = MEDIA_BUS_FMT_RGB888_1X24 ;
* num_input_fmts = 1 ;
return input_fmts ;
}
static void dlpc_mode_set ( struct drm_bridge * bridge ,
const struct drm_display_mode * mode ,
const struct drm_display_mode * adjusted_mode )
{
struct dlpc * dlpc = bridge_to_dlpc ( bridge ) ;
drm_mode_copy ( & dlpc - > mode , adjusted_mode ) ;
}
static int dlpc_attach ( struct drm_bridge * bridge ,
enum drm_bridge_attach_flags flags )
{
struct dlpc * dlpc = bridge_to_dlpc ( bridge ) ;
return drm_bridge_attach ( bridge - > encoder , dlpc - > next_bridge , bridge , flags ) ;
}
static const struct drm_bridge_funcs dlpc_bridge_funcs = {
. atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_bridge_destroy_state ,
. atomic_get_input_bus_fmts = dlpc_atomic_get_input_bus_fmts ,
. atomic_reset = drm_atomic_helper_bridge_reset ,
. atomic_pre_enable = dlpc_atomic_pre_enable ,
. atomic_enable = dlpc_atomic_enable ,
. atomic_post_disable = dlpc_atomic_post_disable ,
. mode_set = dlpc_mode_set ,
. attach = dlpc_attach ,
} ;
static int dlpc3433_parse_dt ( struct dlpc * dlpc )
{
struct device * dev = dlpc - > dev ;
struct device_node * endpoint ;
int ret ;
dlpc - > enable_gpio = devm_gpiod_get ( dev , " enable " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( dlpc - > enable_gpio ) )
return PTR_ERR ( dlpc - > enable_gpio ) ;
dlpc - > vcc_intf = devm_regulator_get ( dlpc - > dev , " vcc_intf " ) ;
if ( IS_ERR ( dlpc - > vcc_intf ) )
return dev_err_probe ( dev , PTR_ERR ( dlpc - > vcc_intf ) ,
" failed to get VCC_INTF supply \n " ) ;
dlpc - > vcc_flsh = devm_regulator_get ( dlpc - > dev , " vcc_flsh " ) ;
if ( IS_ERR ( dlpc - > vcc_flsh ) )
return dev_err_probe ( dev , PTR_ERR ( dlpc - > vcc_flsh ) ,
" failed to get VCC_FLSH supply \n " ) ;
dlpc - > next_bridge = devm_drm_of_get_bridge ( dev , dev - > of_node , 1 , 0 ) ;
if ( IS_ERR ( dlpc - > next_bridge ) )
return PTR_ERR ( dlpc - > next_bridge ) ;
endpoint = of_graph_get_endpoint_by_regs ( dev - > of_node , 0 , 0 ) ;
dlpc - > dsi_lanes = of_property_count_u32_elems ( endpoint , " data-lanes " ) ;
if ( dlpc - > dsi_lanes < 0 | | dlpc - > dsi_lanes > 4 ) {
ret = - EINVAL ;
goto err_put_endpoint ;
}
dlpc - > host_node = of_graph_get_remote_port_parent ( endpoint ) ;
if ( ! dlpc - > host_node ) {
ret = - ENODEV ;
goto err_put_host ;
}
of_node_put ( endpoint ) ;
return 0 ;
err_put_host :
of_node_put ( dlpc - > host_node ) ;
err_put_endpoint :
of_node_put ( endpoint ) ;
return ret ;
}
static int dlpc_host_attach ( struct dlpc * dlpc )
{
struct device * dev = dlpc - > dev ;
struct mipi_dsi_host * host ;
struct mipi_dsi_device_info info = {
. type = " dlpc3433 " ,
. channel = 0 ,
. node = NULL ,
} ;
host = of_find_mipi_dsi_host_by_node ( dlpc - > host_node ) ;
if ( ! host ) {
DRM_DEV_ERROR ( dev , " failed to find dsi host \n " ) ;
return - EPROBE_DEFER ;
}
dlpc - > dsi = mipi_dsi_device_register_full ( host , & info ) ;
if ( IS_ERR ( dlpc - > dsi ) ) {
DRM_DEV_ERROR ( dev , " failed to create dsi device \n " ) ;
return PTR_ERR ( dlpc - > dsi ) ;
}
dlpc - > dsi - > mode_flags = MIPI_DSI_MODE_VIDEO_BURST ;
dlpc - > dsi - > format = MIPI_DSI_FMT_RGB565 ;
dlpc - > dsi - > lanes = dlpc - > dsi_lanes ;
return devm_mipi_dsi_attach ( dev , dlpc - > dsi ) ;
}
static int dlpc3433_probe ( struct i2c_client * client )
{
struct device * dev = & client - > dev ;
struct dlpc * dlpc ;
int ret ;
dlpc = devm_kzalloc ( dev , sizeof ( * dlpc ) , GFP_KERNEL ) ;
if ( ! dlpc )
return - ENOMEM ;
dlpc - > dev = dev ;
dlpc - > regmap = devm_regmap_init_i2c ( client , & dlpc_regmap_config ) ;
if ( IS_ERR ( dlpc - > regmap ) )
return PTR_ERR ( dlpc - > regmap ) ;
ret = dlpc3433_parse_dt ( dlpc ) ;
if ( ret )
return ret ;
dev_set_drvdata ( dev , dlpc ) ;
i2c_set_clientdata ( client , dlpc ) ;
dlpc - > bridge . funcs = & dlpc_bridge_funcs ;
dlpc - > bridge . of_node = dev - > of_node ;
drm_bridge_add ( & dlpc - > bridge ) ;
ret = dlpc_host_attach ( dlpc ) ;
if ( ret ) {
DRM_DEV_ERROR ( dev , " failed to attach dsi host \n " ) ;
goto err_remove_bridge ;
}
return 0 ;
err_remove_bridge :
drm_bridge_remove ( & dlpc - > bridge ) ;
return ret ;
}
2022-08-15 10:02:30 +02:00
static void dlpc3433_remove ( struct i2c_client * client )
2022-06-03 19:33:49 +05:30
{
struct dlpc * dlpc = i2c_get_clientdata ( client ) ;
drm_bridge_remove ( & dlpc - > bridge ) ;
of_node_put ( dlpc - > host_node ) ;
}
static const struct i2c_device_id dlpc3433_id [ ] = {
{ " ti,dlpc3433 " , 0 } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( i2c , dlpc3433_id ) ;
static const struct of_device_id dlpc3433_match_table [ ] = {
{ . compatible = " ti,dlpc3433 " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , dlpc3433_match_table ) ;
static struct i2c_driver dlpc3433_driver = {
. probe_new = dlpc3433_probe ,
. remove = dlpc3433_remove ,
. id_table = dlpc3433_id ,
. driver = {
. name = " ti-dlpc3433 " ,
. of_match_table = dlpc3433_match_table ,
} ,
} ;
module_i2c_driver ( dlpc3433_driver ) ;
MODULE_AUTHOR ( " Jagan Teki <jagan@amarulasolutions.com> " ) ;
MODULE_AUTHOR ( " Christopher Vollo <chris@renewoutreach.org> " ) ;
MODULE_DESCRIPTION ( " TI DLPC3433 MIPI DSI Display Controller Bridge " ) ;
MODULE_LICENSE ( " GPL " ) ;