2019-08-13 23:10:56 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* LG . Philips LB035Q02 LCD Panel Driver
*
* Copyright ( C ) 2019 Texas Instruments Incorporated
*
* Based on the omapdrm - specific panel - lgphilips - lb035q02 driver
*
* Copyright ( C ) 2013 Texas Instruments Incorporated
* Author : Tomi Valkeinen < tomi . valkeinen @ ti . com >
*
* Based on a driver by : Steve Sakoman < steve @ sakoman . com >
*/
# include <linux/gpio/consumer.h>
# include <linux/module.h>
# include <linux/spi/spi.h>
# include <drm/drm_connector.h>
# include <drm/drm_modes.h>
# include <drm/drm_panel.h>
struct lb035q02_device {
struct drm_panel panel ;
struct spi_device * spi ;
struct gpio_desc * enable_gpio ;
} ;
# define to_lb035q02_device(p) container_of(p, struct lb035q02_device, panel)
static int lb035q02_write ( struct lb035q02_device * lcd , u16 reg , u16 val )
{
struct spi_message msg ;
struct spi_transfer index_xfer = {
. len = 3 ,
. cs_change = 1 ,
} ;
struct spi_transfer value_xfer = {
. len = 3 ,
} ;
u8 buffer [ 16 ] ;
spi_message_init ( & msg ) ;
/* register index */
buffer [ 0 ] = 0x70 ;
buffer [ 1 ] = 0x00 ;
buffer [ 2 ] = reg & 0x7f ;
index_xfer . tx_buf = buffer ;
spi_message_add_tail ( & index_xfer , & msg ) ;
/* register value */
buffer [ 4 ] = 0x72 ;
buffer [ 5 ] = val > > 8 ;
buffer [ 6 ] = val ;
value_xfer . tx_buf = buffer + 4 ;
spi_message_add_tail ( & value_xfer , & msg ) ;
return spi_sync ( lcd - > spi , & msg ) ;
}
static int lb035q02_init ( struct lb035q02_device * lcd )
{
/* Init sequence from page 28 of the lb035q02 spec. */
static const struct {
u16 index ;
u16 value ;
} init_data [ ] = {
{ 0x01 , 0x6300 } ,
{ 0x02 , 0x0200 } ,
{ 0x03 , 0x0177 } ,
{ 0x04 , 0x04c7 } ,
{ 0x05 , 0xffc0 } ,
{ 0x06 , 0xe806 } ,
{ 0x0a , 0x4008 } ,
{ 0x0b , 0x0000 } ,
{ 0x0d , 0x0030 } ,
{ 0x0e , 0x2800 } ,
{ 0x0f , 0x0000 } ,
{ 0x16 , 0x9f80 } ,
{ 0x17 , 0x0a0f } ,
{ 0x1e , 0x00c1 } ,
{ 0x30 , 0x0300 } ,
{ 0x31 , 0x0007 } ,
{ 0x32 , 0x0000 } ,
{ 0x33 , 0x0000 } ,
{ 0x34 , 0x0707 } ,
{ 0x35 , 0x0004 } ,
{ 0x36 , 0x0302 } ,
{ 0x37 , 0x0202 } ,
{ 0x3a , 0x0a0d } ,
{ 0x3b , 0x0806 } ,
} ;
unsigned int i ;
int ret ;
for ( i = 0 ; i < ARRAY_SIZE ( init_data ) ; + + i ) {
ret = lb035q02_write ( lcd , init_data [ i ] . index ,
init_data [ i ] . value ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
static int lb035q02_disable ( struct drm_panel * panel )
{
struct lb035q02_device * lcd = to_lb035q02_device ( panel ) ;
gpiod_set_value_cansleep ( lcd - > enable_gpio , 0 ) ;
return 0 ;
}
static int lb035q02_enable ( struct drm_panel * panel )
{
struct lb035q02_device * lcd = to_lb035q02_device ( panel ) ;
gpiod_set_value_cansleep ( lcd - > enable_gpio , 1 ) ;
return 0 ;
}
static const struct drm_display_mode lb035q02_mode = {
. clock = 6500 ,
. hdisplay = 320 ,
. hsync_start = 320 + 20 ,
. hsync_end = 320 + 20 + 2 ,
. htotal = 320 + 20 + 2 + 68 ,
. vdisplay = 240 ,
. vsync_start = 240 + 4 ,
. vsync_end = 240 + 4 + 2 ,
. vtotal = 240 + 4 + 2 + 18 ,
. type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ,
. flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC ,
. width_mm = 70 ,
. height_mm = 53 ,
} ;
2019-12-07 15:03:33 +01:00
static int lb035q02_get_modes ( struct drm_panel * panel ,
struct drm_connector * connector )
2019-08-13 23:10:56 +03:00
{
struct drm_display_mode * mode ;
2019-12-07 15:03:35 +01:00
mode = drm_mode_duplicate ( connector - > dev , & lb035q02_mode ) ;
2019-08-13 23:10:56 +03:00
if ( ! mode )
return - ENOMEM ;
drm_mode_set_name ( mode ) ;
drm_mode_probed_add ( connector , mode ) ;
connector - > display_info . width_mm = lb035q02_mode . width_mm ;
connector - > display_info . height_mm = lb035q02_mode . height_mm ;
/*
* FIXME : According to the datasheet pixel data is sampled on the
* rising edge of the clock , but the code running on the Gumstix Overo
* Palo35 indicates sampling on the negative edge . This should be
* tested on a real device .
*/
connector - > display_info . bus_flags = DRM_BUS_FLAG_DE_HIGH
| DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
| DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE ;
return 1 ;
}
static const struct drm_panel_funcs lb035q02_funcs = {
. disable = lb035q02_disable ,
. enable = lb035q02_enable ,
. get_modes = lb035q02_get_modes ,
} ;
static int lb035q02_probe ( struct spi_device * spi )
{
struct lb035q02_device * lcd ;
int ret ;
lcd = devm_kzalloc ( & spi - > dev , sizeof ( * lcd ) , GFP_KERNEL ) ;
2019-08-13 23:10:57 +03:00
if ( ! lcd )
2019-08-13 23:10:56 +03:00
return - ENOMEM ;
spi_set_drvdata ( spi , lcd ) ;
lcd - > spi = spi ;
lcd - > enable_gpio = devm_gpiod_get ( & spi - > dev , " enable " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( lcd - > enable_gpio ) ) {
dev_err ( & spi - > dev , " failed to parse enable gpio \n " ) ;
return PTR_ERR ( lcd - > enable_gpio ) ;
}
ret = lb035q02_init ( lcd ) ;
if ( ret < 0 )
return ret ;
2019-09-04 16:28:03 +03:00
drm_panel_init ( & lcd - > panel , & lcd - > spi - > dev , & lb035q02_funcs ,
DRM_MODE_CONNECTOR_DPI ) ;
2019-08-13 23:10:56 +03:00
2020-08-01 20:02:13 +08:00
drm_panel_add ( & lcd - > panel ) ;
return 0 ;
2019-08-13 23:10:56 +03:00
}
2022-01-23 18:52:01 +01:00
static void lb035q02_remove ( struct spi_device * spi )
2019-08-13 23:10:56 +03:00
{
struct lb035q02_device * lcd = spi_get_drvdata ( spi ) ;
drm_panel_remove ( & lcd - > panel ) ;
drm_panel_disable ( & lcd - > panel ) ;
}
static const struct of_device_id lb035q02_of_match [ ] = {
{ . compatible = " lgphilips,lb035q02 " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , lb035q02_of_match ) ;
2019-10-07 20:07:57 +03:00
static const struct spi_device_id lb035q02_ids [ ] = {
{ " lb035q02 " , 0 } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( spi , lb035q02_ids ) ;
2019-08-13 23:10:56 +03:00
static struct spi_driver lb035q02_driver = {
. probe = lb035q02_probe ,
. remove = lb035q02_remove ,
2019-10-07 20:07:57 +03:00
. id_table = lb035q02_ids ,
2019-08-13 23:10:56 +03:00
. driver = {
. name = " panel-lg-lb035q02 " ,
. of_match_table = lb035q02_of_match ,
} ,
} ;
module_spi_driver ( lb035q02_driver ) ;
MODULE_AUTHOR ( " Tomi Valkeinen <tomi.valkeinen@ti.com> " ) ;
MODULE_DESCRIPTION ( " LG.Philips LB035Q02 LCD Panel driver " ) ;
MODULE_LICENSE ( " GPL " ) ;