2019-08-13 23:10:57 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* NEC NL8048HL11 Panel Driver
*
* Copyright ( C ) 2019 Texas Instruments Incorporated
*
* Based on the omapdrm - specific panel - nec - nl8048hl11 driver
*
* Copyright ( C ) 2010 Texas Instruments Incorporated
* Author : Erik Gilling < konkers @ android . com >
*/
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/module.h>
# include <linux/pm.h>
# include <linux/spi/spi.h>
# include <drm/drm_connector.h>
# include <drm/drm_modes.h>
# include <drm/drm_panel.h>
struct nl8048_panel {
struct drm_panel panel ;
struct spi_device * spi ;
struct gpio_desc * reset_gpio ;
} ;
# define to_nl8048_device(p) container_of(p, struct nl8048_panel, panel)
static int nl8048_write ( struct nl8048_panel * lcd , unsigned char addr ,
unsigned char value )
{
u8 data [ 4 ] = { value , 0x01 , addr , 0x00 } ;
int ret ;
ret = spi_write ( lcd - > spi , data , sizeof ( data ) ) ;
if ( ret )
dev_err ( & lcd - > spi - > dev , " SPI write to %u failed: %d \n " ,
addr , ret ) ;
return ret ;
}
static int nl8048_init ( struct nl8048_panel * lcd )
{
static const struct {
unsigned char addr ;
unsigned char data ;
} nl8048_init_seq [ ] = {
{ 3 , 0x01 } , { 0 , 0x00 } , { 1 , 0x01 } , { 4 , 0x00 } ,
{ 5 , 0x14 } , { 6 , 0x24 } , { 16 , 0xd7 } , { 17 , 0x00 } ,
{ 18 , 0x00 } , { 19 , 0x55 } , { 20 , 0x01 } , { 21 , 0x70 } ,
{ 22 , 0x1e } , { 23 , 0x25 } , { 24 , 0x25 } , { 25 , 0x02 } ,
{ 26 , 0x02 } , { 27 , 0xa0 } , { 32 , 0x2f } , { 33 , 0x0f } ,
{ 34 , 0x0f } , { 35 , 0x0f } , { 36 , 0x0f } , { 37 , 0x0f } ,
{ 38 , 0x0f } , { 39 , 0x00 } , { 40 , 0x02 } , { 41 , 0x02 } ,
{ 42 , 0x02 } , { 43 , 0x0f } , { 44 , 0x0f } , { 45 , 0x0f } ,
{ 46 , 0x0f } , { 47 , 0x0f } , { 48 , 0x0f } , { 49 , 0x0f } ,
{ 50 , 0x00 } , { 51 , 0x02 } , { 52 , 0x02 } , { 53 , 0x02 } ,
{ 80 , 0x0c } , { 83 , 0x42 } , { 84 , 0x42 } , { 85 , 0x41 } ,
{ 86 , 0x14 } , { 89 , 0x88 } , { 90 , 0x01 } , { 91 , 0x00 } ,
{ 92 , 0x02 } , { 93 , 0x0c } , { 94 , 0x1c } , { 95 , 0x27 } ,
{ 98 , 0x49 } , { 99 , 0x27 } , { 102 , 0x76 } , { 103 , 0x27 } ,
{ 112 , 0x01 } , { 113 , 0x0e } , { 114 , 0x02 } , { 115 , 0x0c } ,
{ 118 , 0x0c } , { 121 , 0x30 } , { 130 , 0x00 } , { 131 , 0x00 } ,
{ 132 , 0xfc } , { 134 , 0x00 } , { 136 , 0x00 } , { 138 , 0x00 } ,
{ 139 , 0x00 } , { 140 , 0x00 } , { 141 , 0xfc } , { 143 , 0x00 } ,
{ 145 , 0x00 } , { 147 , 0x00 } , { 148 , 0x00 } , { 149 , 0x00 } ,
{ 150 , 0xfc } , { 152 , 0x00 } , { 154 , 0x00 } , { 156 , 0x00 } ,
{ 157 , 0x00 } ,
} ;
unsigned int i ;
int ret ;
for ( i = 0 ; i < ARRAY_SIZE ( nl8048_init_seq ) ; + + i ) {
ret = nl8048_write ( lcd , nl8048_init_seq [ i ] . addr ,
nl8048_init_seq [ i ] . data ) ;
if ( ret < 0 )
return ret ;
}
udelay ( 20 ) ;
return nl8048_write ( lcd , 2 , 0x00 ) ;
}
static int nl8048_disable ( struct drm_panel * panel )
{
struct nl8048_panel * lcd = to_nl8048_device ( panel ) ;
gpiod_set_value_cansleep ( lcd - > reset_gpio , 0 ) ;
return 0 ;
}
static int nl8048_enable ( struct drm_panel * panel )
{
struct nl8048_panel * lcd = to_nl8048_device ( panel ) ;
gpiod_set_value_cansleep ( lcd - > reset_gpio , 1 ) ;
return 0 ;
}
static const struct drm_display_mode nl8048_mode = {
/* NEC PIX Clock Ratings MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz */
. clock = 23800 ,
. hdisplay = 800 ,
. hsync_start = 800 + 6 ,
. hsync_end = 800 + 6 + 1 ,
. htotal = 800 + 6 + 1 + 4 ,
. vdisplay = 480 ,
. vsync_start = 480 + 3 ,
. vsync_end = 480 + 3 + 1 ,
. vtotal = 480 + 3 + 1 + 4 ,
. vrefresh = 60 ,
. type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ,
. flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC ,
. width_mm = 89 ,
. height_mm = 53 ,
} ;
2019-12-07 15:03:33 +01:00
static int nl8048_get_modes ( struct drm_panel * panel ,
struct drm_connector * connector )
2019-08-13 23:10:57 +03:00
{
struct drm_display_mode * mode ;
2019-12-07 15:03:35 +01:00
mode = drm_mode_duplicate ( connector - > dev , & nl8048_mode ) ;
2019-08-13 23:10:57 +03:00
if ( ! mode )
return - ENOMEM ;
drm_mode_set_name ( mode ) ;
drm_mode_probed_add ( connector , mode ) ;
connector - > display_info . width_mm = nl8048_mode . width_mm ;
connector - > display_info . height_mm = nl8048_mode . height_mm ;
connector - > display_info . bus_flags = DRM_BUS_FLAG_DE_HIGH
| DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
| DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE ;
return 1 ;
}
static const struct drm_panel_funcs nl8048_funcs = {
. disable = nl8048_disable ,
. enable = nl8048_enable ,
. get_modes = nl8048_get_modes ,
} ;
static int __maybe_unused nl8048_suspend ( struct device * dev )
{
struct nl8048_panel * lcd = dev_get_drvdata ( dev ) ;
nl8048_write ( lcd , 2 , 0x01 ) ;
msleep ( 40 ) ;
return 0 ;
}
static int __maybe_unused nl8048_resume ( struct device * dev )
{
struct nl8048_panel * lcd = dev_get_drvdata ( dev ) ;
/* Reinitialize the panel. */
spi_setup ( lcd - > spi ) ;
nl8048_write ( lcd , 2 , 0x00 ) ;
nl8048_init ( lcd ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( nl8048_pm_ops , nl8048_suspend , nl8048_resume ) ;
static int nl8048_probe ( struct spi_device * spi )
{
struct nl8048_panel * lcd ;
int ret ;
lcd = devm_kzalloc ( & spi - > dev , sizeof ( * lcd ) , GFP_KERNEL ) ;
2019-08-13 23:10:58 +03:00
if ( ! lcd )
2019-08-13 23:10:57 +03:00
return - ENOMEM ;
spi_set_drvdata ( spi , lcd ) ;
lcd - > spi = spi ;
lcd - > reset_gpio = devm_gpiod_get ( & spi - > dev , " reset " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( lcd - > reset_gpio ) ) {
dev_err ( & spi - > dev , " failed to parse reset gpio \n " ) ;
return PTR_ERR ( lcd - > reset_gpio ) ;
}
spi - > mode = SPI_MODE_0 ;
spi - > bits_per_word = 32 ;
ret = spi_setup ( spi ) ;
if ( ret < 0 ) {
dev_err ( & spi - > dev , " failed to setup SPI: %d \n " , ret ) ;
return ret ;
}
ret = nl8048_init ( lcd ) ;
if ( ret < 0 )
return ret ;
2019-09-04 16:28:03 +03:00
drm_panel_init ( & lcd - > panel , & lcd - > spi - > dev , & nl8048_funcs ,
DRM_MODE_CONNECTOR_DPI ) ;
2019-08-13 23:10:57 +03:00
return drm_panel_add ( & lcd - > panel ) ;
}
static int nl8048_remove ( struct spi_device * spi )
{
struct nl8048_panel * lcd = spi_get_drvdata ( spi ) ;
drm_panel_remove ( & lcd - > panel ) ;
drm_panel_disable ( & lcd - > panel ) ;
drm_panel_unprepare ( & lcd - > panel ) ;
return 0 ;
}
static const struct of_device_id nl8048_of_match [ ] = {
{ . compatible = " nec,nl8048hl11 " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , nl8048_of_match ) ;
2019-10-07 20:07:58 +03:00
static const struct spi_device_id nl8048_ids [ ] = {
{ " nl8048hl11 " , 0 } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( spi , nl8048_ids ) ;
2019-08-13 23:10:57 +03:00
static struct spi_driver nl8048_driver = {
. probe = nl8048_probe ,
. remove = nl8048_remove ,
2019-10-07 20:07:58 +03:00
. id_table = nl8048_ids ,
2019-08-13 23:10:57 +03:00
. driver = {
. name = " panel-nec-nl8048hl11 " ,
. pm = & nl8048_pm_ops ,
. of_match_table = nl8048_of_match ,
} ,
} ;
module_spi_driver ( nl8048_driver ) ;
MODULE_AUTHOR ( " Erik Gilling <konkers@android.com> " ) ;
MODULE_DESCRIPTION ( " NEC-NL8048HL11 Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;