2019-05-29 17:17:56 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-02-24 14:31:24 +04:00
/*
* NXP PTN3460 DP / LVDS bridge driver
*
* Copyright ( C ) 2013 Google , Inc .
*/
2015-01-20 19:38:42 +03:00
# include <linux/delay.h>
# include <linux/gpio.h>
2015-05-05 19:32:17 +03:00
# include <linux/gpio/consumer.h>
2015-01-20 19:38:42 +03:00
# include <linux/i2c.h>
2014-02-24 14:31:24 +04:00
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_gpio.h>
2019-05-19 21:36:36 +03:00
2017-04-24 07:50:24 +03:00
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc.h>
# include <drm/drm_edid.h>
2017-03-29 21:55:46 +03:00
# include <drm/drm_of.h>
2015-01-20 19:38:47 +03:00
# include <drm/drm_panel.h>
2019-05-19 21:36:36 +03:00
# include <drm/drm_print.h>
2019-01-18 00:03:34 +03:00
# include <drm/drm_probe_helper.h>
2014-02-24 14:31:24 +04:00
# define PTN3460_EDID_ADDR 0x0
# define PTN3460_EDID_EMULATION_ADDR 0x84
# define PTN3460_EDID_ENABLE_EMULATION 0
# define PTN3460_EDID_EMULATION_SELECTION 1
# define PTN3460_EDID_SRAM_LOAD_ADDR 0x85
struct ptn3460_bridge {
struct drm_connector connector ;
struct i2c_client * client ;
2015-01-20 19:38:42 +03:00
struct drm_bridge bridge ;
2014-02-24 14:31:24 +04:00
struct edid * edid ;
2015-01-20 19:38:47 +03:00
struct drm_panel * panel ;
2015-01-20 19:38:49 +03:00
struct gpio_desc * gpio_pd_n ;
struct gpio_desc * gpio_rst_n ;
2014-02-24 14:31:24 +04:00
u32 edid_emulation ;
bool enabled ;
} ;
2015-01-20 19:38:42 +03:00
static inline struct ptn3460_bridge *
bridge_to_ptn3460 ( struct drm_bridge * bridge )
{
return container_of ( bridge , struct ptn3460_bridge , bridge ) ;
}
static inline struct ptn3460_bridge *
connector_to_ptn3460 ( struct drm_connector * connector )
{
return container_of ( connector , struct ptn3460_bridge , connector ) ;
}
2014-02-24 14:31:24 +04:00
static int ptn3460_read_bytes ( struct ptn3460_bridge * ptn_bridge , char addr ,
u8 * buf , int len )
{
int ret ;
ret = i2c_master_send ( ptn_bridge - > client , & addr , 1 ) ;
if ( ret < = 0 ) {
DRM_ERROR ( " Failed to send i2c command, ret=%d \n " , ret ) ;
return ret ;
}
ret = i2c_master_recv ( ptn_bridge - > client , buf , len ) ;
if ( ret < = 0 ) {
DRM_ERROR ( " Failed to recv i2c data, ret=%d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static int ptn3460_write_byte ( struct ptn3460_bridge * ptn_bridge , char addr ,
char val )
{
int ret ;
char buf [ 2 ] ;
buf [ 0 ] = addr ;
buf [ 1 ] = val ;
ret = i2c_master_send ( ptn_bridge - > client , buf , ARRAY_SIZE ( buf ) ) ;
if ( ret < = 0 ) {
DRM_ERROR ( " Failed to send i2c command, ret=%d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static int ptn3460_select_edid ( struct ptn3460_bridge * ptn_bridge )
{
int ret ;
char val ;
/* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */
ret = ptn3460_write_byte ( ptn_bridge , PTN3460_EDID_SRAM_LOAD_ADDR ,
ptn_bridge - > edid_emulation ) ;
if ( ret ) {
2015-01-20 19:38:42 +03:00
DRM_ERROR ( " Failed to transfer EDID to sram, ret=%d \n " , ret ) ;
2014-02-24 14:31:24 +04:00
return ret ;
}
/* Enable EDID emulation and select the desired EDID */
val = 1 < < PTN3460_EDID_ENABLE_EMULATION |
ptn_bridge - > edid_emulation < < PTN3460_EDID_EMULATION_SELECTION ;
ret = ptn3460_write_byte ( ptn_bridge , PTN3460_EDID_EMULATION_ADDR , val ) ;
if ( ret ) {
2015-01-20 19:38:42 +03:00
DRM_ERROR ( " Failed to write EDID value, ret=%d \n " , ret ) ;
2014-02-24 14:31:24 +04:00
return ret ;
}
return 0 ;
}
static void ptn3460_pre_enable ( struct drm_bridge * bridge )
{
2015-01-20 19:38:42 +03:00
struct ptn3460_bridge * ptn_bridge = bridge_to_ptn3460 ( bridge ) ;
2014-02-24 14:31:24 +04:00
int ret ;
if ( ptn_bridge - > enabled )
return ;
2015-01-20 19:38:49 +03:00
gpiod_set_value ( ptn_bridge - > gpio_pd_n , 1 ) ;
2014-02-24 14:31:24 +04:00
2015-01-20 19:38:49 +03:00
gpiod_set_value ( ptn_bridge - > gpio_rst_n , 0 ) ;
usleep_range ( 10 , 20 ) ;
gpiod_set_value ( ptn_bridge - > gpio_rst_n , 1 ) ;
2014-02-24 14:31:24 +04:00
2015-01-20 19:38:47 +03:00
if ( drm_panel_prepare ( ptn_bridge - > panel ) ) {
DRM_ERROR ( " failed to prepare panel \n " ) ;
return ;
}
2014-02-24 14:31:24 +04:00
/*
* There ' s a bug in the PTN chip where it falsely asserts hotplug before
* it is fully functional . We ' re forced to wait for the maximum start up
* time specified in the chip ' s datasheet to make sure we ' re really up .
*/
msleep ( 90 ) ;
ret = ptn3460_select_edid ( ptn_bridge ) ;
if ( ret )
2015-01-20 19:38:42 +03:00
DRM_ERROR ( " Select EDID failed ret=%d \n " , ret ) ;
2014-02-24 14:31:24 +04:00
ptn_bridge - > enabled = true ;
}
static void ptn3460_enable ( struct drm_bridge * bridge )
{
2015-01-20 19:38:47 +03:00
struct ptn3460_bridge * ptn_bridge = bridge_to_ptn3460 ( bridge ) ;
if ( drm_panel_enable ( ptn_bridge - > panel ) ) {
DRM_ERROR ( " failed to enable panel \n " ) ;
return ;
}
2014-02-24 14:31:24 +04:00
}
static void ptn3460_disable ( struct drm_bridge * bridge )
{
2015-01-20 19:38:42 +03:00
struct ptn3460_bridge * ptn_bridge = bridge_to_ptn3460 ( bridge ) ;
2014-02-24 14:31:24 +04:00
if ( ! ptn_bridge - > enabled )
return ;
ptn_bridge - > enabled = false ;
2015-01-20 19:38:47 +03:00
if ( drm_panel_disable ( ptn_bridge - > panel ) ) {
DRM_ERROR ( " failed to disable panel \n " ) ;
return ;
}
2015-01-20 19:38:49 +03:00
gpiod_set_value ( ptn_bridge - > gpio_rst_n , 1 ) ;
gpiod_set_value ( ptn_bridge - > gpio_pd_n , 0 ) ;
2014-02-24 14:31:24 +04:00
}
static void ptn3460_post_disable ( struct drm_bridge * bridge )
{
2015-01-20 19:38:47 +03:00
struct ptn3460_bridge * ptn_bridge = bridge_to_ptn3460 ( bridge ) ;
if ( drm_panel_unprepare ( ptn_bridge - > panel ) ) {
DRM_ERROR ( " failed to unprepare panel \n " ) ;
return ;
}
2014-02-24 14:31:24 +04:00
}
2015-01-20 19:38:42 +03:00
static int ptn3460_get_modes ( struct drm_connector * connector )
2014-02-24 14:31:24 +04:00
{
struct ptn3460_bridge * ptn_bridge ;
u8 * edid ;
2015-01-20 19:38:42 +03:00
int ret , num_modes = 0 ;
2014-02-24 14:31:24 +04:00
bool power_off ;
2015-01-20 19:38:42 +03:00
ptn_bridge = connector_to_ptn3460 ( connector ) ;
2014-02-24 14:31:24 +04:00
if ( ptn_bridge - > edid )
return drm_add_edid_modes ( connector , ptn_bridge - > edid ) ;
power_off = ! ptn_bridge - > enabled ;
2015-01-20 19:38:42 +03:00
ptn3460_pre_enable ( & ptn_bridge - > bridge ) ;
2014-02-24 14:31:24 +04:00
edid = kmalloc ( EDID_LENGTH , GFP_KERNEL ) ;
if ( ! edid ) {
2015-01-20 19:38:42 +03:00
DRM_ERROR ( " Failed to allocate EDID \n " ) ;
2014-02-24 14:31:24 +04:00
return 0 ;
}
ret = ptn3460_read_bytes ( ptn_bridge , PTN3460_EDID_ADDR , edid ,
EDID_LENGTH ) ;
if ( ret ) {
kfree ( edid ) ;
goto out ;
}
ptn_bridge - > edid = ( struct edid * ) edid ;
2018-07-09 11:40:06 +03:00
drm_connector_update_edid_property ( connector , ptn_bridge - > edid ) ;
2014-02-24 14:31:24 +04:00
num_modes = drm_add_edid_modes ( connector , ptn_bridge - > edid ) ;
out :
if ( power_off )
2015-01-20 19:38:42 +03:00
ptn3460_disable ( & ptn_bridge - > bridge ) ;
2014-02-24 14:31:24 +04:00
return num_modes ;
}
2015-12-15 14:21:03 +03:00
static const struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = {
2014-02-24 14:31:24 +04:00
. get_modes = ptn3460_get_modes ,
} ;
2015-12-15 14:21:03 +03:00
static const struct drm_connector_funcs ptn3460_connector_funcs = {
2014-02-24 14:31:24 +04:00
. fill_modes = drm_helper_probe_single_connector_modes ,
2016-10-05 01:23:31 +03:00
. destroy = drm_connector_cleanup ,
2015-06-01 18:04:44 +03: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 ,
2014-02-24 14:31:24 +04:00
} ;
2015-01-30 14:33:58 +03:00
static int ptn3460_bridge_attach ( struct drm_bridge * bridge )
2014-02-24 14:31:24 +04:00
{
2015-01-20 19:38:45 +03:00
struct ptn3460_bridge * ptn_bridge = bridge_to_ptn3460 ( bridge ) ;
2014-02-24 14:31:24 +04:00
int ret ;
2015-01-20 19:38:45 +03:00
if ( ! bridge - > encoder ) {
DRM_ERROR ( " Parent encoder object not found " ) ;
return - ENODEV ;
}
2015-01-20 19:38:48 +03:00
ptn_bridge - > connector . polled = DRM_CONNECTOR_POLL_HPD ;
2015-01-20 19:38:45 +03:00
ret = drm_connector_init ( bridge - > dev , & ptn_bridge - > connector ,
& ptn3460_connector_funcs , DRM_MODE_CONNECTOR_LVDS ) ;
if ( ret ) {
DRM_ERROR ( " Failed to initialize connector with drm \n " ) ;
return ret ;
}
drm_connector_helper_add ( & ptn_bridge - > connector ,
& ptn3460_connector_helper_funcs ) ;
drm_connector_register ( & ptn_bridge - > connector ) ;
2018-07-09 11:40:07 +03:00
drm_connector_attach_encoder ( & ptn_bridge - > connector ,
2015-01-20 19:38:45 +03:00
bridge - > encoder ) ;
2015-01-20 19:38:47 +03:00
if ( ptn_bridge - > panel )
drm_panel_attach ( ptn_bridge - > panel , & ptn_bridge - > connector ) ;
2015-01-20 19:38:48 +03:00
drm_helper_hpd_irq_event ( ptn_bridge - > connector . dev ) ;
2015-01-20 19:38:45 +03:00
return ret ;
}
2015-12-15 14:21:03 +03:00
static const struct drm_bridge_funcs ptn3460_bridge_funcs = {
2015-01-20 19:38:45 +03:00
. pre_enable = ptn3460_pre_enable ,
. enable = ptn3460_enable ,
. disable = ptn3460_disable ,
. post_disable = ptn3460_post_disable ,
. attach = ptn3460_bridge_attach ,
} ;
static int ptn3460_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct device * dev = & client - > dev ;
2014-02-24 14:31:24 +04:00
struct ptn3460_bridge * ptn_bridge ;
2015-01-20 19:38:45 +03:00
int ret ;
2014-02-24 14:31:24 +04:00
2015-01-20 19:38:45 +03:00
ptn_bridge = devm_kzalloc ( dev , sizeof ( * ptn_bridge ) , GFP_KERNEL ) ;
2014-02-24 14:31:24 +04:00
if ( ! ptn_bridge ) {
return - ENOMEM ;
}
2017-03-29 21:55:46 +03:00
ret = drm_of_find_panel_or_bridge ( dev - > of_node , 0 , 0 , & ptn_bridge - > panel , NULL ) ;
if ( ret )
return ret ;
2015-01-20 19:38:47 +03:00
2014-02-24 14:31:24 +04:00
ptn_bridge - > client = client ;
2015-01-20 19:38:49 +03:00
2015-05-19 10:15:56 +03:00
ptn_bridge - > gpio_pd_n = devm_gpiod_get ( & client - > dev , " powerdown " ,
GPIOD_OUT_HIGH ) ;
2015-01-20 19:38:49 +03:00
if ( IS_ERR ( ptn_bridge - > gpio_pd_n ) ) {
ret = PTR_ERR ( ptn_bridge - > gpio_pd_n ) ;
dev_err ( dev , " cannot get gpio_pd_n %d \n " , ret ) ;
return ret ;
2014-02-24 14:31:24 +04:00
}
2015-01-20 19:38:49 +03:00
/*
* Request the reset pin low to avoid the bridge being
* initialized prematurely
*/
2015-05-19 10:15:56 +03:00
ptn_bridge - > gpio_rst_n = devm_gpiod_get ( & client - > dev , " reset " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ptn_bridge - > gpio_rst_n ) ) {
ret = PTR_ERR ( ptn_bridge - > gpio_rst_n ) ;
DRM_ERROR ( " cannot get gpio_rst_n %d \n " , ret ) ;
2015-01-20 19:38:49 +03:00
return ret ;
2014-02-24 14:31:24 +04:00
}
2015-01-20 19:38:45 +03:00
ret = of_property_read_u32 ( dev - > of_node , " edid-emulation " ,
2014-02-24 14:31:24 +04:00
& ptn_bridge - > edid_emulation ) ;
if ( ret ) {
2015-01-20 19:38:45 +03:00
dev_err ( dev , " Can't read EDID emulation value \n " ) ;
2015-01-20 19:38:49 +03:00
return ret ;
2014-02-24 14:31:24 +04:00
}
2015-01-20 19:38:43 +03:00
ptn_bridge - > bridge . funcs = & ptn3460_bridge_funcs ;
2015-01-20 19:38:47 +03:00
ptn_bridge - > bridge . of_node = dev - > of_node ;
2017-07-03 11:42:21 +03:00
drm_bridge_add ( & ptn_bridge - > bridge ) ;
2014-02-24 14:31:24 +04:00
2015-01-20 19:38:45 +03:00
i2c_set_clientdata ( client , ptn_bridge ) ;
2014-02-24 14:31:24 +04:00
return 0 ;
}
2015-01-20 19:38:44 +03:00
2015-01-20 19:38:45 +03:00
static int ptn3460_remove ( struct i2c_client * client )
2015-01-20 19:38:44 +03:00
{
2015-01-20 19:38:45 +03:00
struct ptn3460_bridge * ptn_bridge = i2c_get_clientdata ( client ) ;
drm_bridge_remove ( & ptn_bridge - > bridge ) ;
2015-01-20 19:38:44 +03:00
2015-01-20 19:38:45 +03:00
return 0 ;
2015-01-20 19:38:44 +03:00
}
2015-01-20 19:38:45 +03:00
static const struct i2c_device_id ptn3460_i2c_table [ ] = {
2015-05-14 17:31:29 +03:00
{ " ptn3460 " , 0 } ,
2015-01-20 19:38:45 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , ptn3460_i2c_table ) ;
static const struct of_device_id ptn3460_match [ ] = {
{ . compatible = " nxp,ptn3460 " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ptn3460_match ) ;
static struct i2c_driver ptn3460_driver = {
. id_table = ptn3460_i2c_table ,
. probe = ptn3460_probe ,
. remove = ptn3460_remove ,
. driver = {
. name = " nxp,ptn3460 " ,
. of_match_table = ptn3460_match ,
} ,
} ;
module_i2c_driver ( ptn3460_driver ) ;
MODULE_AUTHOR ( " Sean Paul <seanpaul@chromium.org> " ) ;
MODULE_DESCRIPTION ( " NXP ptn3460 eDP-LVDS converter driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;