2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2015-06-09 07:51:22 +02:00
/*
* Copyright ( C ) 2015 Heiko Schocher < hs @ denx . de >
*
* from :
* drivers / gpu / drm / panel / panel - ld9040 . c
* ld9040 AMOLED LCD drm_panel driver .
*
* Copyright ( c ) 2014 Samsung Electronics Co . , Ltd
* Derived from drivers / video / backlight / ld9040 . c
*
* Andrzej Hajda < a . hajda @ samsung . com >
*/
2019-05-26 20:05:32 +02:00
# include <linux/delay.h>
2015-06-09 07:51:22 +02:00
# include <linux/gpio/consumer.h>
2019-05-26 20:05:32 +02:00
# include <linux/module.h>
2015-06-09 07:51:22 +02:00
# include <linux/regulator/consumer.h>
# include <linux/spi/spi.h>
# include <video/mipi_display.h>
# include <video/of_videomode.h>
# include <video/videomode.h>
2019-05-26 20:05:32 +02:00
# include <drm/drm_device.h>
# include <drm/drm_modes.h>
# include <drm/drm_panel.h>
2015-06-09 07:51:22 +02:00
struct lg4573 {
struct drm_panel panel ;
struct spi_device * spi ;
struct videomode vm ;
} ;
static inline struct lg4573 * panel_to_lg4573 ( struct drm_panel * panel )
{
return container_of ( panel , struct lg4573 , panel ) ;
}
static int lg4573_spi_write_u16 ( struct lg4573 * ctx , u16 data )
{
struct spi_transfer xfer = {
. len = 2 ,
} ;
u16 temp = cpu_to_be16 ( data ) ;
struct spi_message msg ;
dev_dbg ( ctx - > panel . dev , " writing data: %x \n " , data ) ;
xfer . tx_buf = & temp ;
spi_message_init ( & msg ) ;
spi_message_add_tail ( & xfer , & msg ) ;
return spi_sync ( ctx - > spi , & msg ) ;
}
static int lg4573_spi_write_u16_array ( struct lg4573 * ctx , const u16 * buffer ,
unsigned int count )
{
unsigned int i ;
int ret ;
for ( i = 0 ; i < count ; i + + ) {
ret = lg4573_spi_write_u16 ( ctx , buffer [ i ] ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int lg4573_spi_write_dcs ( struct lg4573 * ctx , u8 dcs )
{
return lg4573_spi_write_u16 ( ctx , ( 0x70 < < 8 | dcs ) ) ;
}
static int lg4573_display_on ( struct lg4573 * ctx )
{
int ret ;
ret = lg4573_spi_write_dcs ( ctx , MIPI_DCS_EXIT_SLEEP_MODE ) ;
if ( ret )
return ret ;
msleep ( 5 ) ;
return lg4573_spi_write_dcs ( ctx , MIPI_DCS_SET_DISPLAY_ON ) ;
}
static int lg4573_display_off ( struct lg4573 * ctx )
{
int ret ;
ret = lg4573_spi_write_dcs ( ctx , MIPI_DCS_SET_DISPLAY_OFF ) ;
if ( ret )
return ret ;
msleep ( 120 ) ;
return lg4573_spi_write_dcs ( ctx , MIPI_DCS_ENTER_SLEEP_MODE ) ;
}
static int lg4573_display_mode_settings ( struct lg4573 * ctx )
{
static const u16 display_mode_settings [ ] = {
0x703A , 0x7270 , 0x70B1 , 0x7208 ,
0x723B , 0x720F , 0x70B2 , 0x7200 ,
0x72C8 , 0x70B3 , 0x7200 , 0x70B4 ,
0x7200 , 0x70B5 , 0x7242 , 0x7210 ,
0x7210 , 0x7200 , 0x7220 , 0x70B6 ,
0x720B , 0x720F , 0x723C , 0x7213 ,
0x7213 , 0x72E8 , 0x70B7 , 0x7246 ,
0x7206 , 0x720C , 0x7200 , 0x7200 ,
} ;
dev_dbg ( ctx - > panel . dev , " transfer display mode settings \n " ) ;
return lg4573_spi_write_u16_array ( ctx , display_mode_settings ,
ARRAY_SIZE ( display_mode_settings ) ) ;
}
static int lg4573_power_settings ( struct lg4573 * ctx )
{
static const u16 power_settings [ ] = {
0x70C0 , 0x7201 , 0x7211 , 0x70C3 ,
0x7207 , 0x7203 , 0x7204 , 0x7204 ,
0x7204 , 0x70C4 , 0x7212 , 0x7224 ,
0x7218 , 0x7218 , 0x7202 , 0x7249 ,
0x70C5 , 0x726F , 0x70C6 , 0x7241 ,
0x7263 ,
} ;
dev_dbg ( ctx - > panel . dev , " transfer power settings \n " ) ;
return lg4573_spi_write_u16_array ( ctx , power_settings ,
ARRAY_SIZE ( power_settings ) ) ;
}
static int lg4573_gamma_settings ( struct lg4573 * ctx )
{
static const u16 gamma_settings [ ] = {
0x70D0 , 0x7203 , 0x7207 , 0x7273 ,
0x7235 , 0x7200 , 0x7201 , 0x7220 ,
0x7200 , 0x7203 , 0x70D1 , 0x7203 ,
0x7207 , 0x7273 , 0x7235 , 0x7200 ,
0x7201 , 0x7220 , 0x7200 , 0x7203 ,
0x70D2 , 0x7203 , 0x7207 , 0x7273 ,
0x7235 , 0x7200 , 0x7201 , 0x7220 ,
0x7200 , 0x7203 , 0x70D3 , 0x7203 ,
0x7207 , 0x7273 , 0x7235 , 0x7200 ,
0x7201 , 0x7220 , 0x7200 , 0x7203 ,
0x70D4 , 0x7203 , 0x7207 , 0x7273 ,
0x7235 , 0x7200 , 0x7201 , 0x7220 ,
0x7200 , 0x7203 , 0x70D5 , 0x7203 ,
0x7207 , 0x7273 , 0x7235 , 0x7200 ,
0x7201 , 0x7220 , 0x7200 , 0x7203 ,
} ;
dev_dbg ( ctx - > panel . dev , " transfer gamma settings \n " ) ;
return lg4573_spi_write_u16_array ( ctx , gamma_settings ,
ARRAY_SIZE ( gamma_settings ) ) ;
}
static int lg4573_init ( struct lg4573 * ctx )
{
int ret ;
dev_dbg ( ctx - > panel . dev , " initializing LCD \n " ) ;
ret = lg4573_display_mode_settings ( ctx ) ;
if ( ret )
return ret ;
ret = lg4573_power_settings ( ctx ) ;
if ( ret )
return ret ;
return lg4573_gamma_settings ( ctx ) ;
}
static int lg4573_power_on ( struct lg4573 * ctx )
{
return lg4573_display_on ( ctx ) ;
}
static int lg4573_disable ( struct drm_panel * panel )
{
struct lg4573 * ctx = panel_to_lg4573 ( panel ) ;
return lg4573_display_off ( ctx ) ;
}
static int lg4573_enable ( struct drm_panel * panel )
{
struct lg4573 * ctx = panel_to_lg4573 ( panel ) ;
lg4573_init ( ctx ) ;
return lg4573_power_on ( ctx ) ;
}
static const struct drm_display_mode default_mode = {
. clock = 27000 ,
. hdisplay = 480 ,
. hsync_start = 480 + 10 ,
. hsync_end = 480 + 10 + 59 ,
. htotal = 480 + 10 + 59 + 10 ,
. vdisplay = 800 ,
. vsync_start = 800 + 15 ,
. vsync_end = 800 + 15 + 15 ,
. vtotal = 800 + 15 + 15 + 15 ,
. vrefresh = 60 ,
} ;
2019-12-07 15:03:33 +01:00
static int lg4573_get_modes ( struct drm_panel * panel ,
struct drm_connector * connector )
2015-06-09 07:51:22 +02:00
{
struct drm_display_mode * mode ;
2019-12-07 15:03:35 +01:00
mode = drm_mode_duplicate ( connector - > dev , & default_mode ) ;
2015-06-09 07:51:22 +02:00
if ( ! mode ) {
2019-12-07 15:03:35 +01:00
dev_err ( panel - > dev , " failed to add mode %ux%ux@%u \n " ,
2015-06-09 07:51:22 +02:00
default_mode . hdisplay , default_mode . vdisplay ,
default_mode . vrefresh ) ;
return - ENOMEM ;
}
drm_mode_set_name ( mode ) ;
mode - > type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ;
drm_mode_probed_add ( connector , mode ) ;
2019-12-07 15:03:33 +01:00
connector - > display_info . width_mm = 61 ;
connector - > display_info . height_mm = 103 ;
2015-06-09 07:51:22 +02:00
return 1 ;
}
static const struct drm_panel_funcs lg4573_drm_funcs = {
. disable = lg4573_disable ,
. enable = lg4573_enable ,
. get_modes = lg4573_get_modes ,
} ;
static int lg4573_probe ( struct spi_device * spi )
{
struct lg4573 * ctx ;
int ret ;
ctx = devm_kzalloc ( & spi - > dev , sizeof ( * ctx ) , GFP_KERNEL ) ;
if ( ! ctx )
return - ENOMEM ;
ctx - > spi = spi ;
spi_set_drvdata ( spi , ctx ) ;
spi - > bits_per_word = 8 ;
ret = spi_setup ( spi ) ;
if ( ret < 0 ) {
dev_err ( & spi - > dev , " SPI setup failed: %d \n " , ret ) ;
return ret ;
}
2019-09-04 16:28:03 +03:00
drm_panel_init ( & ctx - > panel , & spi - > dev , & lg4573_drm_funcs ,
DRM_MODE_CONNECTOR_DPI ) ;
2015-06-09 07:51:22 +02:00
return drm_panel_add ( & ctx - > panel ) ;
}
static int lg4573_remove ( struct spi_device * spi )
{
struct lg4573 * ctx = spi_get_drvdata ( spi ) ;
lg4573_display_off ( ctx ) ;
drm_panel_remove ( & ctx - > panel ) ;
return 0 ;
}
static const struct of_device_id lg4573_of_match [ ] = {
{ . compatible = " lg,lg4573 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , lg4573_of_match ) ;
static struct spi_driver lg4573_driver = {
. probe = lg4573_probe ,
. remove = lg4573_remove ,
. driver = {
. name = " lg4573 " ,
. of_match_table = lg4573_of_match ,
} ,
} ;
module_spi_driver ( lg4573_driver ) ;
MODULE_AUTHOR ( " Heiko Schocher <hs@denx.de> " ) ;
MODULE_DESCRIPTION ( " lg4573 LCD Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;