2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-01-22 15:23:48 +01:00
/*
* DRM driver for Multi - Inno MI0283QT panels
*
* Copyright 2016 Noralf Trønnes
*/
2018-01-24 16:36:09 +00:00
# include <linux/backlight.h>
2017-01-22 15:23:48 +01:00
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/module.h>
# include <linux/property.h>
# include <linux/regulator/consumer.h>
# include <linux/spi/spi.h>
2018-01-10 19:59:34 +01:00
2019-02-10 14:10:33 +01:00
# include <drm/drm_atomic_helper.h>
2019-01-08 20:29:38 +01:00
# include <drm/drm_drv.h>
2019-02-25 15:42:30 +01:00
# include <drm/drm_fb_helper.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_gem_cma_helper.h>
2018-04-05 17:44:42 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2020-03-23 15:49:04 +01:00
# include <drm/drm_managed.h>
2019-07-22 12:43:11 +02:00
# include <drm/drm_mipi_dbi.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_modeset_helper.h>
2017-01-22 15:23:48 +01:00
# include <video/mipi_display.h>
2018-01-10 19:59:35 +01:00
# define ILI9341_FRMCTR1 0xb1
# define ILI9341_DISCTRL 0xb6
# define ILI9341_ETMOD 0xb7
# define ILI9341_PWCTRL1 0xc0
# define ILI9341_PWCTRL2 0xc1
# define ILI9341_VMCTRL1 0xc5
# define ILI9341_VMCTRL2 0xc7
# define ILI9341_PWCTRLA 0xcb
# define ILI9341_PWCTRLB 0xcf
# define ILI9341_PGAMCTRL 0xe0
# define ILI9341_NGAMCTRL 0xe1
# define ILI9341_DTCTRLA 0xe8
# define ILI9341_DTCTRLB 0xea
# define ILI9341_PWRSEQ 0xed
# define ILI9341_EN3GAM 0xf2
# define ILI9341_PUMPCTRL 0xf7
# define ILI9341_MADCTL_BGR BIT(3)
# define ILI9341_MADCTL_MV BIT(5)
# define ILI9341_MADCTL_MX BIT(6)
# define ILI9341_MADCTL_MY BIT(7)
2018-01-10 19:59:38 +01:00
static void mi0283qt_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-01-22 15:23:48 +01:00
{
2019-07-22 12:43:07 +02:00
struct mipi_dbi_dev * dbidev = drm_to_mipi_dbi_dev ( pipe - > crtc . dev ) ;
struct mipi_dbi * dbi = & dbidev - > dbi ;
2017-01-22 15:23:48 +01:00
u8 addr_mode ;
2019-02-25 15:42:32 +01:00
int ret , idx ;
if ( ! drm_dev_enter ( pipe - > crtc . dev , & idx ) )
return ;
2017-01-22 15:23:48 +01:00
DRM_DEBUG_KMS ( " \n " ) ;
2019-07-22 12:43:06 +02:00
ret = mipi_dbi_poweron_conditional_reset ( dbidev ) ;
2018-01-10 19:59:37 +01:00
if ( ret < 0 )
2019-02-25 15:42:32 +01:00
goto out_exit ;
2018-01-10 19:59:37 +01:00
if ( ret = = 1 )
2018-01-10 19:59:38 +01:00
goto out_enable ;
2017-01-22 15:23:48 +01:00
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , MIPI_DCS_SET_DISPLAY_OFF ) ;
2017-01-22 15:23:48 +01:00
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_PWCTRLB , 0x00 , 0x83 , 0x30 ) ;
mipi_dbi_command ( dbi , ILI9341_PWRSEQ , 0x64 , 0x03 , 0x12 , 0x81 ) ;
mipi_dbi_command ( dbi , ILI9341_DTCTRLA , 0x85 , 0x01 , 0x79 ) ;
mipi_dbi_command ( dbi , ILI9341_PWCTRLA , 0x39 , 0x2c , 0x00 , 0x34 , 0x02 ) ;
mipi_dbi_command ( dbi , ILI9341_PUMPCTRL , 0x20 ) ;
mipi_dbi_command ( dbi , ILI9341_DTCTRLB , 0x00 , 0x00 ) ;
2017-01-22 15:23:48 +01:00
/* Power Control */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_PWCTRL1 , 0x26 ) ;
mipi_dbi_command ( dbi , ILI9341_PWCTRL2 , 0x11 ) ;
2017-01-22 15:23:48 +01:00
/* VCOM */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_VMCTRL1 , 0x35 , 0x3e ) ;
mipi_dbi_command ( dbi , ILI9341_VMCTRL2 , 0xbe ) ;
2017-01-22 15:23:48 +01:00
/* Memory Access Control */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , MIPI_DCS_SET_PIXEL_FORMAT , MIPI_DCS_PIXEL_FMT_16BIT ) ;
2017-01-22 15:23:48 +01:00
/* Frame Rate */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_FRMCTR1 , 0x00 , 0x1b ) ;
2017-01-22 15:23:48 +01:00
/* Gamma */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_EN3GAM , 0x08 ) ;
mipi_dbi_command ( dbi , MIPI_DCS_SET_GAMMA_CURVE , 0x01 ) ;
mipi_dbi_command ( dbi , ILI9341_PGAMCTRL ,
2017-01-22 15:23:48 +01:00
0x1f , 0x1a , 0x18 , 0x0a , 0x0f , 0x06 , 0x45 , 0x87 ,
0x32 , 0x0a , 0x07 , 0x02 , 0x07 , 0x05 , 0x00 ) ;
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_NGAMCTRL ,
2017-01-22 15:23:48 +01:00
0x00 , 0x25 , 0x27 , 0x05 , 0x10 , 0x09 , 0x3a , 0x78 ,
0x4d , 0x05 , 0x18 , 0x0d , 0x38 , 0x3a , 0x1f ) ;
/* DDRAM */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_ETMOD , 0x07 ) ;
2017-01-22 15:23:48 +01:00
/* Display */
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , ILI9341_DISCTRL , 0x0a , 0x82 , 0x27 , 0x00 ) ;
mipi_dbi_command ( dbi , MIPI_DCS_EXIT_SLEEP_MODE ) ;
2017-01-22 15:23:48 +01:00
msleep ( 100 ) ;
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , MIPI_DCS_SET_DISPLAY_ON ) ;
2017-01-22 15:23:48 +01:00
msleep ( 100 ) ;
2018-01-10 19:59:38 +01:00
out_enable :
2018-04-23 12:16:39 -04:00
/* The PiTFT (ili9340) has a hardware reset circuit that
* resets only on power - on and not on each reboot through
* a gpio like the rpi - display does .
* As a result , we need to always apply the rotation value
* regardless of the display " on/off " state .
*/
2019-07-22 12:43:06 +02:00
switch ( dbidev - > rotation ) {
2018-04-23 12:16:39 -04:00
default :
addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
ILI9341_MADCTL_MX ;
break ;
case 90 :
addr_mode = ILI9341_MADCTL_MY ;
break ;
case 180 :
addr_mode = ILI9341_MADCTL_MV ;
break ;
case 270 :
addr_mode = ILI9341_MADCTL_MX ;
break ;
}
addr_mode | = ILI9341_MADCTL_BGR ;
2019-07-22 12:43:05 +02:00
mipi_dbi_command ( dbi , MIPI_DCS_SET_ADDRESS_MODE , addr_mode ) ;
2019-07-22 12:43:06 +02:00
mipi_dbi_enable_flush ( dbidev , crtc_state , plane_state ) ;
2019-02-25 15:42:32 +01:00
out_exit :
drm_dev_exit ( idx ) ;
2017-01-22 15:23:48 +01:00
}
static const struct drm_simple_display_pipe_funcs mi0283qt_pipe_funcs = {
2018-01-10 19:59:38 +01:00
. enable = mi0283qt_enable ,
2017-01-22 15:23:48 +01:00
. disable = mipi_dbi_pipe_disable ,
2019-01-15 05:36:42 +01:00
. update = mipi_dbi_pipe_update ,
2018-04-05 17:44:42 +02:00
. prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb ,
2017-01-22 15:23:48 +01:00
} ;
static const struct drm_display_mode mi0283qt_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 320 , 240 , 58 , 43 ) ,
2017-01-22 15:23:48 +01:00
} ;
2017-03-26 16:25:29 +02:00
DEFINE_DRM_GEM_CMA_FOPS ( mi0283qt_fops ) ;
2017-01-22 15:23:48 +01:00
static struct drm_driver mi0283qt_driver = {
2019-06-17 17:39:24 +02:00
. driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC ,
2017-03-26 16:25:29 +02:00
. fops = & mi0283qt_fops ,
2020-06-05 09:32:06 +02:00
DRM_GEM_CMA_DRIVER_OPS_VMAP ,
2017-01-22 15:23:48 +01:00
. debugfs_init = mipi_dbi_debugfs_init ,
. name = " mi0283qt " ,
. desc = " Multi-Inno MI0283QT " ,
. date = " 20160614 " ,
. major = 1 ,
. minor = 0 ,
} ;
static const struct of_device_id mi0283qt_of_match [ ] = {
{ . compatible = " multi-inno,mi0283qt " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , mi0283qt_of_match ) ;
static const struct spi_device_id mi0283qt_id [ ] = {
{ " mi0283qt " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( spi , mi0283qt_id ) ;
static int mi0283qt_probe ( struct spi_device * spi )
{
struct device * dev = & spi - > dev ;
2019-07-22 12:43:07 +02:00
struct mipi_dbi_dev * dbidev ;
2019-02-25 15:42:30 +01:00
struct drm_device * drm ;
2019-07-22 12:43:05 +02:00
struct mipi_dbi * dbi ;
2017-01-22 15:23:48 +01:00
struct gpio_desc * dc ;
u32 rotation = 0 ;
int ret ;
2020-04-15 09:39:51 +02:00
dbidev = devm_drm_dev_alloc ( dev , & mi0283qt_driver ,
struct mipi_dbi_dev , drm ) ;
if ( IS_ERR ( dbidev ) )
return PTR_ERR ( dbidev ) ;
2017-01-22 15:23:48 +01:00
2019-07-22 12:43:07 +02:00
dbi = & dbidev - > dbi ;
2019-07-22 12:43:06 +02:00
drm = & dbidev - > drm ;
2019-02-25 15:42:30 +01:00
2019-07-22 12:43:05 +02:00
dbi - > reset = devm_gpiod_get_optional ( dev , " reset " , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( dbi - > reset ) ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'reset' \n " ) ;
2019-07-22 12:43:05 +02:00
return PTR_ERR ( dbi - > reset ) ;
2017-01-22 15:23:48 +01:00
}
dc = devm_gpiod_get_optional ( dev , " dc " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( dc ) ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'dc' \n " ) ;
2017-01-22 15:23:48 +01:00
return PTR_ERR ( dc ) ;
}
2019-07-22 12:43:06 +02:00
dbidev - > regulator = devm_regulator_get ( dev , " power " ) ;
if ( IS_ERR ( dbidev - > regulator ) )
return PTR_ERR ( dbidev - > regulator ) ;
2017-01-22 15:23:48 +01:00
2019-07-22 12:43:06 +02:00
dbidev - > backlight = devm_of_find_backlight ( dev ) ;
if ( IS_ERR ( dbidev - > backlight ) )
return PTR_ERR ( dbidev - > backlight ) ;
2017-01-22 15:23:48 +01:00
device_property_read_u32 ( dev , " rotation " , & rotation ) ;
2019-07-22 12:43:05 +02:00
ret = mipi_dbi_spi_init ( spi , dbi , dc ) ;
2017-08-03 17:33:45 -05:00
if ( ret )
return ret ;
2019-07-22 12:43:07 +02:00
ret = mipi_dbi_dev_init ( dbidev , & mi0283qt_pipe_funcs , & mi0283qt_mode , rotation ) ;
2019-02-25 15:42:30 +01:00
if ( ret )
return ret ;
drm_mode_config_reset ( drm ) ;
ret = drm_dev_register ( drm , 0 ) ;
2017-01-22 15:23:48 +01:00
if ( ret )
return ret ;
2019-02-25 15:42:30 +01:00
spi_set_drvdata ( spi , drm ) ;
2017-01-22 15:23:48 +01:00
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 mi0283qt_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-01-22 15:23:48 +01:00
}
static void mi0283qt_shutdown ( struct spi_device * spi )
{
2019-02-10 14:10:33 +01:00
drm_atomic_helper_shutdown ( spi_get_drvdata ( spi ) ) ;
2017-01-22 15:23:48 +01:00
}
static int __maybe_unused mi0283qt_pm_suspend ( struct device * dev )
{
2019-02-10 14:10:33 +01:00
return drm_mode_config_helper_suspend ( dev_get_drvdata ( dev ) ) ;
2017-01-22 15:23:48 +01:00
}
static int __maybe_unused mi0283qt_pm_resume ( struct device * dev )
{
2019-02-10 14:10:33 +01:00
drm_mode_config_helper_resume ( dev_get_drvdata ( dev ) ) ;
2017-11-06 20:18:11 +01:00
return 0 ;
2017-01-22 15:23:48 +01:00
}
static const struct dev_pm_ops mi0283qt_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( mi0283qt_pm_suspend , mi0283qt_pm_resume )
} ;
static struct spi_driver mi0283qt_spi_driver = {
. driver = {
. name = " mi0283qt " ,
. owner = THIS_MODULE ,
. of_match_table = mi0283qt_of_match ,
. pm = & mi0283qt_pm_ops ,
} ,
. id_table = mi0283qt_id ,
. probe = mi0283qt_probe ,
2019-02-25 15:42:30 +01:00
. remove = mi0283qt_remove ,
2017-01-22 15:23:48 +01:00
. shutdown = mi0283qt_shutdown ,
} ;
module_spi_driver ( mi0283qt_spi_driver ) ;
MODULE_DESCRIPTION ( " Multi-Inno MI0283QT DRM driver " ) ;
MODULE_AUTHOR ( " Noralf Trønnes " ) ;
MODULE_LICENSE ( " GPL " ) ;