2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-08-07 12:39:39 -05:00
/*
* DRM driver for Sitronix ST7586 panels
*
* Copyright 2017 David Lechner < david @ lechnology . com >
*/
# include <linux/delay.h>
# include <linux/dma-buf.h>
# include <linux/gpio/consumer.h>
# include <linux/module.h>
# include <linux/property.h>
# include <linux/spi/spi.h>
# include <video/mipi_display.h>
2019-02-10 14:10:33 +01:00
# include <drm/drm_atomic_helper.h>
2019-01-15 05:36:42 +01:00
# include <drm/drm_damage_helper.h>
2019-01-08 20:29:38 +01:00
# include <drm/drm_drv.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_fb_cma_helper.h>
2019-02-25 15:42:30 +01:00
# include <drm/drm_fb_helper.h>
2019-04-05 11:52:15 +02:00
# include <drm/drm_format_helper.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_gem_cma_helper.h>
2017-09-24 14:26:16 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2019-01-15 05:36:41 +01:00
# include <drm/drm_rect.h>
2019-01-15 05:36:42 +01:00
# include <drm/drm_vblank.h>
2017-08-07 12:39:39 -05:00
# include <drm/tinydrm/mipi-dbi.h>
/* controller-specific commands */
# define ST7586_DISP_MODE_GRAY 0x38
# define ST7586_DISP_MODE_MONO 0x39
# define ST7586_ENABLE_DDRAM 0x3a
# define ST7586_SET_DISP_DUTY 0xb0
# define ST7586_SET_PART_DISP 0xb4
# define ST7586_SET_NLINE_INV 0xb5
# define ST7586_SET_VOP 0xc0
# define ST7586_SET_BIAS_SYSTEM 0xc3
# define ST7586_SET_BOOST_LEVEL 0xc4
# define ST7586_SET_VOP_OFFSET 0xc7
# define ST7586_ENABLE_ANALOG 0xd0
# define ST7586_AUTO_READ_CTRL 0xd7
# define ST7586_OTP_RW_CTRL 0xe0
# define ST7586_OTP_CTRL_OUT 0xe1
# define ST7586_OTP_READ 0xe3
# define ST7586_DISP_CTRL_MX BIT(6)
# define ST7586_DISP_CTRL_MY BIT(7)
/*
* The ST7586 controller has an unusual pixel format where 2 bpp grayscale is
* packed 3 pixels per byte with the first two pixels using 3 bits and the 3 rd
* pixel using only 2 bits .
*
* | D7 | D6 | D5 | | | | | 2 bpp |
* | ( D4 ) | ( D3 ) | ( D2 ) | | D1 | D0 | | GRAY |
* + - - - - - - + - - - - - - + - - - - - - + + - - - - - - + - - - - - - + + - - - - - - +
* | 1 | 1 | 1 | | 1 | 1 | | 0 0 | black
* | 1 | 0 | 0 | | 1 | 0 | | 0 1 | dark gray
* | 0 | 1 | 0 | | 0 | 1 | | 1 0 | light gray
* | 0 | 0 | 0 | | 0 | 0 | | 1 1 | white
*/
static const u8 st7586_lookup [ ] = { 0x7 , 0x4 , 0x2 , 0x0 } ;
static void st7586_xrgb8888_to_gray332 ( u8 * dst , void * vaddr ,
struct drm_framebuffer * fb ,
2019-01-15 05:36:41 +01:00
struct drm_rect * clip )
2017-08-07 12:39:39 -05:00
{
size_t len = ( clip - > x2 - clip - > x1 ) * ( clip - > y2 - clip - > y1 ) ;
unsigned int x , y ;
u8 * src , * buf , val ;
buf = kmalloc ( len , GFP_KERNEL ) ;
if ( ! buf )
return ;
2019-04-05 11:52:15 +02:00
drm_fb_xrgb8888_to_gray8 ( buf , vaddr , fb , clip ) ;
2017-08-07 12:39:39 -05:00
src = buf ;
for ( y = clip - > y1 ; y < clip - > y2 ; y + + ) {
for ( x = clip - > x1 ; x < clip - > x2 ; x + = 3 ) {
val = st7586_lookup [ * src + + > > 6 ] < < 5 ;
val | = st7586_lookup [ * src + + > > 6 ] < < 2 ;
val | = st7586_lookup [ * src + + > > 6 ] > > 1 ;
* dst + + = val ;
}
}
kfree ( buf ) ;
}
static int st7586_buf_copy ( void * dst , struct drm_framebuffer * fb ,
2019-01-15 05:36:41 +01:00
struct drm_rect * clip )
2017-08-07 12:39:39 -05:00
{
struct drm_gem_cma_object * cma_obj = drm_fb_cma_get_gem_obj ( fb , 0 ) ;
struct dma_buf_attachment * import_attach = cma_obj - > base . import_attach ;
void * src = cma_obj - > vaddr ;
int ret = 0 ;
if ( import_attach ) {
ret = dma_buf_begin_cpu_access ( import_attach - > dmabuf ,
DMA_FROM_DEVICE ) ;
if ( ret )
return ret ;
}
st7586_xrgb8888_to_gray332 ( dst , src , fb , clip ) ;
if ( import_attach )
ret = dma_buf_end_cpu_access ( import_attach - > dmabuf ,
DMA_FROM_DEVICE ) ;
return ret ;
}
2019-01-15 05:36:42 +01:00
static void st7586_fb_dirty ( struct drm_framebuffer * fb , struct drm_rect * rect )
2017-08-07 12:39:39 -05:00
{
2019-02-10 14:10:34 +01:00
struct mipi_dbi * mipi = drm_to_mipi_dbi ( fb - > dev ) ;
2019-02-25 15:42:32 +01:00
int start , end , idx , ret = 0 ;
2017-08-07 12:39:39 -05:00
if ( ! mipi - > enabled )
2019-01-15 05:36:42 +01:00
return ;
2017-08-07 12:39:39 -05:00
2019-02-25 15:42:32 +01:00
if ( ! drm_dev_enter ( fb - > dev , & idx ) )
return ;
2017-08-07 12:39:39 -05:00
/* 3 pixels per byte, so grow clip to nearest multiple of 3 */
2019-01-15 05:36:41 +01:00
rect - > x1 = rounddown ( rect - > x1 , 3 ) ;
rect - > x2 = roundup ( rect - > x2 , 3 ) ;
2017-08-07 12:39:39 -05:00
2019-01-15 05:36:41 +01:00
DRM_DEBUG_KMS ( " Flushing [FB:%d] " DRM_RECT_FMT " \n " , fb - > base . id , DRM_RECT_ARG ( rect ) ) ;
2017-08-07 12:39:39 -05:00
2019-01-15 05:36:41 +01:00
ret = st7586_buf_copy ( mipi - > tx_buf , fb , rect ) ;
2017-08-07 12:39:39 -05:00
if ( ret )
2019-01-15 05:36:42 +01:00
goto err_msg ;
2017-08-07 12:39:39 -05:00
/* Pixels are packed 3 per byte */
2019-01-15 05:36:41 +01:00
start = rect - > x1 / 3 ;
end = rect - > x2 / 3 ;
2017-08-07 12:39:39 -05:00
mipi_dbi_command ( mipi , MIPI_DCS_SET_COLUMN_ADDRESS ,
( start > > 8 ) & 0xFF , start & 0xFF ,
( end > > 8 ) & 0xFF , ( end - 1 ) & 0xFF ) ;
mipi_dbi_command ( mipi , MIPI_DCS_SET_PAGE_ADDRESS ,
2019-01-15 05:36:41 +01:00
( rect - > y1 > > 8 ) & 0xFF , rect - > y1 & 0xFF ,
( rect - > y2 > > 8 ) & 0xFF , ( rect - > y2 - 1 ) & 0xFF ) ;
2017-08-07 12:39:39 -05:00
ret = mipi_dbi_command_buf ( mipi , MIPI_DCS_WRITE_MEMORY_START ,
( u8 * ) mipi - > tx_buf ,
2019-01-15 05:36:41 +01:00
( end - start ) * ( rect - > y2 - rect - > y1 ) ) ;
2019-01-15 05:36:42 +01:00
err_msg :
if ( ret )
dev_err_once ( fb - > dev - > dev , " Failed to update display %d \n " , ret ) ;
2019-02-25 15:42:32 +01:00
drm_dev_exit ( idx ) ;
2017-08-07 12:39:39 -05:00
}
2019-01-15 05:36:42 +01:00
static void st7586_pipe_update ( struct drm_simple_display_pipe * pipe ,
struct drm_plane_state * old_state )
{
struct drm_plane_state * state = pipe - > plane . state ;
struct drm_crtc * crtc = & pipe - > crtc ;
struct drm_rect rect ;
if ( drm_atomic_helper_damage_merged ( old_state , state , & rect ) )
st7586_fb_dirty ( state - > fb , & rect ) ;
if ( crtc - > state - > event ) {
spin_lock_irq ( & crtc - > dev - > event_lock ) ;
drm_crtc_send_vblank_event ( crtc , crtc - > state - > event ) ;
spin_unlock_irq ( & crtc - > dev - > event_lock ) ;
crtc - > state - > event = NULL ;
}
}
2017-08-07 12:39:39 -05:00
2017-08-16 10:23:06 +01:00
static void st7586_pipe_enable ( struct drm_simple_display_pipe * pipe ,
2018-03-22 22:27:37 +02:00
struct drm_crtc_state * crtc_state ,
struct drm_plane_state * plane_state )
2017-08-07 12:39:39 -05:00
{
2019-02-10 14:10:34 +01:00
struct mipi_dbi * mipi = drm_to_mipi_dbi ( pipe - > crtc . dev ) ;
2019-01-15 05:36:42 +01:00
struct drm_framebuffer * fb = plane_state - > fb ;
struct drm_rect rect = {
. x1 = 0 ,
. x2 = fb - > width ,
. y1 = 0 ,
. y2 = fb - > height ,
} ;
2019-02-25 15:42:32 +01:00
int idx , ret ;
2017-08-07 12:39:39 -05:00
u8 addr_mode ;
2019-02-25 15:42:32 +01:00
if ( ! drm_dev_enter ( pipe - > crtc . dev , & idx ) )
return ;
2017-08-07 12:39:39 -05:00
DRM_DEBUG_KMS ( " \n " ) ;
2018-01-10 19:59:37 +01:00
ret = mipi_dbi_poweron_reset ( mipi ) ;
if ( ret )
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-08-07 12:39:39 -05:00
2018-01-10 19:59:37 +01:00
mipi_dbi_command ( mipi , ST7586_AUTO_READ_CTRL , 0x9f ) ;
2017-08-07 12:39:39 -05:00
mipi_dbi_command ( mipi , ST7586_OTP_RW_CTRL , 0x00 ) ;
msleep ( 10 ) ;
mipi_dbi_command ( mipi , ST7586_OTP_READ ) ;
msleep ( 20 ) ;
mipi_dbi_command ( mipi , ST7586_OTP_CTRL_OUT ) ;
mipi_dbi_command ( mipi , MIPI_DCS_EXIT_SLEEP_MODE ) ;
mipi_dbi_command ( mipi , MIPI_DCS_SET_DISPLAY_OFF ) ;
msleep ( 50 ) ;
mipi_dbi_command ( mipi , ST7586_SET_VOP_OFFSET , 0x00 ) ;
mipi_dbi_command ( mipi , ST7586_SET_VOP , 0xe3 , 0x00 ) ;
mipi_dbi_command ( mipi , ST7586_SET_BIAS_SYSTEM , 0x02 ) ;
mipi_dbi_command ( mipi , ST7586_SET_BOOST_LEVEL , 0x04 ) ;
mipi_dbi_command ( mipi , ST7586_ENABLE_ANALOG , 0x1d ) ;
mipi_dbi_command ( mipi , ST7586_SET_NLINE_INV , 0x00 ) ;
mipi_dbi_command ( mipi , ST7586_DISP_MODE_GRAY ) ;
mipi_dbi_command ( mipi , ST7586_ENABLE_DDRAM , 0x02 ) ;
switch ( mipi - > rotation ) {
default :
addr_mode = 0x00 ;
break ;
case 90 :
addr_mode = ST7586_DISP_CTRL_MY ;
break ;
case 180 :
addr_mode = ST7586_DISP_CTRL_MX | ST7586_DISP_CTRL_MY ;
break ;
case 270 :
addr_mode = ST7586_DISP_CTRL_MX ;
break ;
}
mipi_dbi_command ( mipi , MIPI_DCS_SET_ADDRESS_MODE , addr_mode ) ;
mipi_dbi_command ( mipi , ST7586_SET_DISP_DUTY , 0x7f ) ;
mipi_dbi_command ( mipi , ST7586_SET_PART_DISP , 0xa0 ) ;
mipi_dbi_command ( mipi , MIPI_DCS_SET_PARTIAL_AREA , 0x00 , 0x00 , 0x00 , 0x77 ) ;
mipi_dbi_command ( mipi , MIPI_DCS_EXIT_INVERT_MODE ) ;
msleep ( 100 ) ;
2019-01-15 05:36:42 +01:00
mipi - > enabled = true ;
st7586_fb_dirty ( fb , & rect ) ;
2017-08-07 12:39:39 -05:00
2019-01-15 05:36:42 +01:00
mipi_dbi_command ( mipi , MIPI_DCS_SET_DISPLAY_ON ) ;
2019-02-25 15:42:32 +01:00
out_exit :
drm_dev_exit ( idx ) ;
2017-08-07 12:39:39 -05:00
}
static void st7586_pipe_disable ( struct drm_simple_display_pipe * pipe )
{
2019-02-10 14:10:34 +01:00
struct mipi_dbi * mipi = drm_to_mipi_dbi ( pipe - > crtc . dev ) ;
2017-08-07 12:39:39 -05:00
2019-02-25 15:42:32 +01:00
/*
* This callback is not protected by drm_dev_enter / exit since we want to
* turn off the display on regular driver unload . It ' s highly unlikely
* that the underlying SPI controller is gone should this be called after
* unplug .
*/
2017-08-07 12:39:39 -05:00
DRM_DEBUG_KMS ( " \n " ) ;
if ( ! mipi - > enabled )
return ;
mipi_dbi_command ( mipi , MIPI_DCS_SET_DISPLAY_OFF ) ;
mipi - > enabled = false ;
}
static const u32 st7586_formats [ ] = {
DRM_FORMAT_XRGB8888 ,
} ;
static const struct drm_simple_display_pipe_funcs st7586_pipe_funcs = {
. enable = st7586_pipe_enable ,
. disable = st7586_pipe_disable ,
2019-01-15 05:36:42 +01:00
. update = st7586_pipe_update ,
2018-04-05 17:44:42 +02:00
. prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb ,
2017-08-07 12:39:39 -05:00
} ;
static const struct drm_display_mode st7586_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 178 , 128 , 37 , 27 ) ,
2017-08-07 12:39:39 -05:00
} ;
DEFINE_DRM_GEM_CMA_FOPS ( st7586_fops ) ;
static struct drm_driver st7586_driver = {
2019-06-17 17:39:24 +02:00
. driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC ,
2017-08-07 12:39:39 -05:00
. fops = & st7586_fops ,
2019-02-25 15:42:30 +01:00
. release = mipi_dbi_release ,
2018-11-10 15:56:47 +01:00
DRM_GEM_CMA_VMAP_DRIVER_OPS ,
2017-08-07 12:39:39 -05:00
. debugfs_init = mipi_dbi_debugfs_init ,
. name = " st7586 " ,
. desc = " Sitronix ST7586 " ,
. date = " 20170801 " ,
. major = 1 ,
. minor = 0 ,
} ;
static const struct of_device_id st7586_of_match [ ] = {
{ . compatible = " lego,ev3-lcd " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , st7586_of_match ) ;
static const struct spi_device_id st7586_id [ ] = {
{ " ev3-lcd " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( spi , st7586_id ) ;
static int st7586_probe ( struct spi_device * spi )
{
struct device * dev = & spi - > dev ;
2019-02-25 15:42:30 +01:00
struct drm_device * drm ;
2017-08-07 12:39:39 -05:00
struct mipi_dbi * mipi ;
struct gpio_desc * a0 ;
u32 rotation = 0 ;
2019-02-25 15:42:30 +01:00
size_t bufsize ;
2017-08-07 12:39:39 -05:00
int ret ;
2019-02-25 15:42:30 +01:00
mipi = kzalloc ( sizeof ( * mipi ) , GFP_KERNEL ) ;
2017-08-07 12:39:39 -05:00
if ( ! mipi )
return - ENOMEM ;
2019-02-25 15:42:30 +01:00
drm = & mipi - > drm ;
ret = devm_drm_dev_init ( dev , drm , & st7586_driver ) ;
if ( ret ) {
kfree ( mipi ) ;
return ret ;
}
drm_mode_config_init ( drm ) ;
bufsize = ( st7586_mode . vdisplay + 2 ) / 3 * st7586_mode . hdisplay ;
2017-08-07 12:39:39 -05:00
mipi - > reset = devm_gpiod_get ( dev , " reset " , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( mipi - > reset ) ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'reset' \n " ) ;
2017-08-07 12:39:39 -05:00
return PTR_ERR ( mipi - > reset ) ;
}
a0 = devm_gpiod_get ( dev , " a0 " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( a0 ) ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'a0' \n " ) ;
2017-08-07 12:39:39 -05:00
return PTR_ERR ( a0 ) ;
}
device_property_read_u32 ( dev , " rotation " , & rotation ) ;
ret = mipi_dbi_spi_init ( spi , mipi , a0 ) ;
if ( ret )
return ret ;
/* Cannot read from this controller via SPI */
mipi - > read_commands = NULL ;
2019-07-19 17:59:15 +02:00
ret = mipi_dbi_init_with_formats ( mipi , & st7586_pipe_funcs ,
st7586_formats , ARRAY_SIZE ( st7586_formats ) ,
& st7586_mode , rotation , bufsize ) ;
if ( ret )
return ret ;
2017-08-07 12:39:39 -05:00
/*
* we are using 8 - bit data , so we are not actually swapping anything ,
* but setting mipi - > swap_bytes makes mipi_dbi_typec3_command ( ) do the
* right thing and not use 16 - bit transfers ( which results in swapped
* bytes on little - endian systems and causes out of order data to be
* sent to the display ) .
*/
mipi - > swap_bytes = true ;
2019-02-25 15:42:30 +01:00
drm_mode_config_reset ( drm ) ;
2017-08-07 12:39:39 -05:00
2019-02-25 15:42:30 +01:00
ret = drm_dev_register ( drm , 0 ) ;
if ( ret )
return ret ;
spi_set_drvdata ( spi , drm ) ;
2019-04-10 14:43:45 +02:00
drm_fbdev_generic_setup ( drm , 0 ) ;
2019-02-25 15:42:30 +01:00
return 0 ;
}
static int st7586_remove ( struct spi_device * spi )
{
struct drm_device * drm = spi_get_drvdata ( spi ) ;
drm_dev_unplug ( drm ) ;
drm_atomic_helper_shutdown ( drm ) ;
return 0 ;
2017-08-07 12:39:39 -05:00
}
static void st7586_shutdown ( struct spi_device * spi )
{
2019-02-10 14:10:33 +01:00
drm_atomic_helper_shutdown ( spi_get_drvdata ( spi ) ) ;
2017-08-07 12:39:39 -05:00
}
static struct spi_driver st7586_spi_driver = {
. driver = {
. name = " st7586 " ,
. owner = THIS_MODULE ,
. of_match_table = st7586_of_match ,
} ,
. id_table = st7586_id ,
. probe = st7586_probe ,
2019-02-25 15:42:30 +01:00
. remove = st7586_remove ,
2017-08-07 12:39:39 -05:00
. shutdown = st7586_shutdown ,
} ;
module_spi_driver ( st7586_spi_driver ) ;
MODULE_DESCRIPTION ( " Sitronix ST7586 DRM driver " ) ;
MODULE_AUTHOR ( " David Lechner <david@lechnology.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;