1*57f6190aSLinus Walleij // SPDX-License-Identifier: GPL-2.0 2*57f6190aSLinus Walleij /* 3*57f6190aSLinus Walleij * Panel driver for the WideChips WS2401 480x800 DPI RGB panel, used in 4*57f6190aSLinus Walleij * the Samsung Mobile Display (SMD) LMS380KF01. 5*57f6190aSLinus Walleij * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone. 6*57f6190aSLinus Walleij * Linus Walleij <linus.walleij@linaro.org> 7*57f6190aSLinus Walleij * Inspired by code and know-how in the vendor driver by Gareth Phillips. 8*57f6190aSLinus Walleij */ 9*57f6190aSLinus Walleij #include <drm/drm_mipi_dbi.h> 10*57f6190aSLinus Walleij #include <drm/drm_modes.h> 11*57f6190aSLinus Walleij #include <drm/drm_panel.h> 12*57f6190aSLinus Walleij 13*57f6190aSLinus Walleij #include <linux/backlight.h> 14*57f6190aSLinus Walleij #include <linux/delay.h> 15*57f6190aSLinus Walleij #include <linux/gpio/consumer.h> 16*57f6190aSLinus Walleij #include <linux/init.h> 17*57f6190aSLinus Walleij #include <linux/kernel.h> 18*57f6190aSLinus Walleij #include <linux/media-bus-format.h> 19*57f6190aSLinus Walleij #include <linux/module.h> 20*57f6190aSLinus Walleij #include <linux/regulator/consumer.h> 21*57f6190aSLinus Walleij #include <linux/spi/spi.h> 22*57f6190aSLinus Walleij 23*57f6190aSLinus Walleij #include <video/mipi_display.h> 24*57f6190aSLinus Walleij 25*57f6190aSLinus Walleij #define WS2401_RESCTL 0xb8 /* Resolution select control */ 26*57f6190aSLinus Walleij #define WS2401_PSMPS 0xbd /* SMPS positive control */ 27*57f6190aSLinus Walleij #define WS2401_NSMPS 0xbe /* SMPS negative control */ 28*57f6190aSLinus Walleij #define WS2401_SMPS 0xbf 29*57f6190aSLinus Walleij #define WS2401_BCMODE 0xc1 /* Backlight control mode */ 30*57f6190aSLinus Walleij #define WS2401_WRBLCTL 0xc3 /* Backlight control */ 31*57f6190aSLinus Walleij #define WS2401_WRDISBV 0xc4 /* Write manual brightness */ 32*57f6190aSLinus Walleij #define WS2401_WRCTRLD 0xc6 /* Write BL control */ 33*57f6190aSLinus Walleij #define WS2401_WRMIE 0xc7 /* Write MIE mode */ 34*57f6190aSLinus Walleij #define WS2401_READ_ID1 0xda /* Read panel ID 1 */ 35*57f6190aSLinus Walleij #define WS2401_READ_ID2 0xdb /* Read panel ID 2 */ 36*57f6190aSLinus Walleij #define WS2401_READ_ID3 0xdc /* Read panel ID 3 */ 37*57f6190aSLinus Walleij #define WS2401_GAMMA_R1 0xe7 /* Gamma red 1 */ 38*57f6190aSLinus Walleij #define WS2401_GAMMA_G1 0xe8 /* Gamma green 1 */ 39*57f6190aSLinus Walleij #define WS2401_GAMMA_B1 0xe9 /* Gamma blue 1 */ 40*57f6190aSLinus Walleij #define WS2401_GAMMA_R2 0xea /* Gamma red 2 */ 41*57f6190aSLinus Walleij #define WS2401_GAMMA_G2 0xeb /* Gamma green 2 */ 42*57f6190aSLinus Walleij #define WS2401_GAMMA_B2 0xec /* Gamma blue 2 */ 43*57f6190aSLinus Walleij #define WS2401_PASSWD1 0xf0 /* Password command for level 2 */ 44*57f6190aSLinus Walleij #define WS2401_DISCTL 0xf2 /* Display control */ 45*57f6190aSLinus Walleij #define WS2401_PWRCTL 0xf3 /* Power control */ 46*57f6190aSLinus Walleij #define WS2401_VCOMCTL 0xf4 /* VCOM control */ 47*57f6190aSLinus Walleij #define WS2401_SRCCTL 0xf5 /* Source control */ 48*57f6190aSLinus Walleij #define WS2401_PANELCTL 0xf6 /* Panel control */ 49*57f6190aSLinus Walleij 50*57f6190aSLinus Walleij static const u8 ws2401_dbi_read_commands[] = { 51*57f6190aSLinus Walleij WS2401_READ_ID1, 52*57f6190aSLinus Walleij WS2401_READ_ID2, 53*57f6190aSLinus Walleij WS2401_READ_ID3, 54*57f6190aSLinus Walleij 0, /* sentinel */ 55*57f6190aSLinus Walleij }; 56*57f6190aSLinus Walleij 57*57f6190aSLinus Walleij /** 58*57f6190aSLinus Walleij * struct ws2401 - state container for a panel controlled by the WS2401 59*57f6190aSLinus Walleij * controller 60*57f6190aSLinus Walleij */ 61*57f6190aSLinus Walleij struct ws2401 { 62*57f6190aSLinus Walleij /** @dev: the container device */ 63*57f6190aSLinus Walleij struct device *dev; 64*57f6190aSLinus Walleij /** @dbi: the DBI bus abstraction handle */ 65*57f6190aSLinus Walleij struct mipi_dbi dbi; 66*57f6190aSLinus Walleij /** @panel: the DRM panel instance for this device */ 67*57f6190aSLinus Walleij struct drm_panel panel; 68*57f6190aSLinus Walleij /** @width: the width of this panel in mm */ 69*57f6190aSLinus Walleij u32 width; 70*57f6190aSLinus Walleij /** @height: the height of this panel in mm */ 71*57f6190aSLinus Walleij u32 height; 72*57f6190aSLinus Walleij /** @reset: reset GPIO line */ 73*57f6190aSLinus Walleij struct gpio_desc *reset; 74*57f6190aSLinus Walleij /** @regulators: VCCIO and VIO supply regulators */ 75*57f6190aSLinus Walleij struct regulator_bulk_data regulators[2]; 76*57f6190aSLinus Walleij /** @internal_bl: If using internal backlight */ 77*57f6190aSLinus Walleij bool internal_bl; 78*57f6190aSLinus Walleij }; 79*57f6190aSLinus Walleij 80*57f6190aSLinus Walleij static const struct drm_display_mode lms380kf01_480_800_mode = { 81*57f6190aSLinus Walleij /* 82*57f6190aSLinus Walleij * The vendor driver states that the "SMD panel" has a clock 83*57f6190aSLinus Walleij * frequency of 49920000 Hz / 2 = 24960000 Hz. 84*57f6190aSLinus Walleij */ 85*57f6190aSLinus Walleij .clock = 24960, 86*57f6190aSLinus Walleij .hdisplay = 480, 87*57f6190aSLinus Walleij .hsync_start = 480 + 8, 88*57f6190aSLinus Walleij .hsync_end = 480 + 8 + 10, 89*57f6190aSLinus Walleij .htotal = 480 + 8 + 10 + 8, 90*57f6190aSLinus Walleij .vdisplay = 800, 91*57f6190aSLinus Walleij .vsync_start = 800 + 8, 92*57f6190aSLinus Walleij .vsync_end = 800 + 8 + 2, 93*57f6190aSLinus Walleij .vtotal = 800 + 8 + 2 + 18, 94*57f6190aSLinus Walleij .width_mm = 50, 95*57f6190aSLinus Walleij .height_mm = 84, 96*57f6190aSLinus Walleij .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, 97*57f6190aSLinus Walleij }; 98*57f6190aSLinus Walleij 99*57f6190aSLinus Walleij static inline struct ws2401 *to_ws2401(struct drm_panel *panel) 100*57f6190aSLinus Walleij { 101*57f6190aSLinus Walleij return container_of(panel, struct ws2401, panel); 102*57f6190aSLinus Walleij } 103*57f6190aSLinus Walleij 104*57f6190aSLinus Walleij static void ws2401_read_mtp_id(struct ws2401 *ws) 105*57f6190aSLinus Walleij { 106*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 107*57f6190aSLinus Walleij u8 id1, id2, id3; 108*57f6190aSLinus Walleij int ret; 109*57f6190aSLinus Walleij 110*57f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID1, &id1); 111*57f6190aSLinus Walleij if (ret) { 112*57f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 1\n"); 113*57f6190aSLinus Walleij return; 114*57f6190aSLinus Walleij } 115*57f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID2, &id2); 116*57f6190aSLinus Walleij if (ret) { 117*57f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 2\n"); 118*57f6190aSLinus Walleij return; 119*57f6190aSLinus Walleij } 120*57f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID3, &id3); 121*57f6190aSLinus Walleij if (ret) { 122*57f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 3\n"); 123*57f6190aSLinus Walleij return; 124*57f6190aSLinus Walleij } 125*57f6190aSLinus Walleij dev_info(ws->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3); 126*57f6190aSLinus Walleij } 127*57f6190aSLinus Walleij 128*57f6190aSLinus Walleij static int ws2401_power_on(struct ws2401 *ws) 129*57f6190aSLinus Walleij { 130*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 131*57f6190aSLinus Walleij int ret; 132*57f6190aSLinus Walleij 133*57f6190aSLinus Walleij /* Power up */ 134*57f6190aSLinus Walleij ret = regulator_bulk_enable(ARRAY_SIZE(ws->regulators), 135*57f6190aSLinus Walleij ws->regulators); 136*57f6190aSLinus Walleij if (ret) { 137*57f6190aSLinus Walleij dev_err(ws->dev, "failed to enable regulators: %d\n", ret); 138*57f6190aSLinus Walleij return ret; 139*57f6190aSLinus Walleij } 140*57f6190aSLinus Walleij msleep(10); 141*57f6190aSLinus Walleij 142*57f6190aSLinus Walleij /* Assert reset >=1 ms */ 143*57f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 1); 144*57f6190aSLinus Walleij usleep_range(1000, 5000); 145*57f6190aSLinus Walleij /* De-assert reset */ 146*57f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 0); 147*57f6190aSLinus Walleij /* Wait >= 10 ms */ 148*57f6190aSLinus Walleij msleep(10); 149*57f6190aSLinus Walleij dev_dbg(ws->dev, "de-asserted RESET\n"); 150*57f6190aSLinus Walleij 151*57f6190aSLinus Walleij /* 152*57f6190aSLinus Walleij * Exit sleep mode and initialize display - some hammering is 153*57f6190aSLinus Walleij * necessary. 154*57f6190aSLinus Walleij */ 155*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 156*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 157*57f6190aSLinus Walleij msleep(50); 158*57f6190aSLinus Walleij 159*57f6190aSLinus Walleij /* Magic to unlock level 2 control of the display */ 160*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PASSWD1, 0x5a, 0x5a); 161*57f6190aSLinus Walleij /* Configure resolution to 480RGBx800 */ 162*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_RESCTL, 0x12); 163*57f6190aSLinus Walleij /* Set addressing mode Flip V(d0), Flip H(d1) RGB/BGR(d3) */ 164*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x01); 165*57f6190aSLinus Walleij /* Set pixel format: 24 bpp */ 166*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x70); 167*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SMPS, 0x00, 0x0f); 168*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PSMPS, 0x06, 0x03, /* DDVDH: 4.6v */ 169*57f6190aSLinus Walleij 0x7e, 0x03, 0x12, 0x37); 170*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_NSMPS, 0x06, 0x03, /* DDVDH: -4.6v */ 171*57f6190aSLinus Walleij 0x7e, 0x02, 0x15, 0x37); 172*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SMPS, 0x02, 0x0f); 173*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PWRCTL, 0x10, 0xA9, 0x00, 0x01, 0x44, 174*57f6190aSLinus Walleij 0xb4, /* VGH:16.1v, VGL:-13.8v */ 175*57f6190aSLinus Walleij 0x50, /* GREFP:4.2v (default) */ 176*57f6190aSLinus Walleij 0x50, /* GREFN:-4.2v (default) */ 177*57f6190aSLinus Walleij 0x00, 178*57f6190aSLinus Walleij 0x44); /* VOUTL:-10v (default) */ 179*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_DISCTL, 0x01, 0x00, 0x00, 0x00, 0x14, 180*57f6190aSLinus Walleij 0x16); 181*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_VCOMCTL, 0x30, 0x53, 0x53); 182*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SRCCTL, 0x03, 0x0C, 0x00, 0x00, 0x00, 183*57f6190aSLinus Walleij 0x01, /* 2 dot inversion */ 184*57f6190aSLinus Walleij 0x01, 0x06, 0x03); 185*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PANELCTL, 0x14, 0x00, 0x80, 0x00); 186*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRMIE, 0x01); 187*57f6190aSLinus Walleij 188*57f6190aSLinus Walleij /* Set up gamma, probably these are P-gamma and N-gamma for each color */ 189*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_R1, 0x00, 190*57f6190aSLinus Walleij 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e, 191*57f6190aSLinus Walleij 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00); 192*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_R2, 0x00, 193*57f6190aSLinus Walleij 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e, 194*57f6190aSLinus Walleij 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00); 195*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_G1, 0x00, 196*57f6190aSLinus Walleij 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f, 197*57f6190aSLinus Walleij 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00); 198*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_G2, 0x00, 199*57f6190aSLinus Walleij 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f, 200*57f6190aSLinus Walleij 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00); 201*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_B1, 0x00, 202*57f6190aSLinus Walleij 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27, 203*57f6190aSLinus Walleij 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00); 204*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_B2, 0x00, 205*57f6190aSLinus Walleij 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27, 206*57f6190aSLinus Walleij 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00); 207*57f6190aSLinus Walleij 208*57f6190aSLinus Walleij if (ws->internal_bl) { 209*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c); 210*57f6190aSLinus Walleij } else { 211*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); 212*57f6190aSLinus Walleij /* 213*57f6190aSLinus Walleij * When not using internal backlight we do not need any further 214*57f6190aSLinus Walleij * L2 accesses to the panel so we close the door on our way out. 215*57f6190aSLinus Walleij * Otherwise we need to leave the L2 door open. 216*57f6190aSLinus Walleij */ 217*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PASSWD1, 0xa5, 0xa5); 218*57f6190aSLinus Walleij } 219*57f6190aSLinus Walleij 220*57f6190aSLinus Walleij return 0; 221*57f6190aSLinus Walleij } 222*57f6190aSLinus Walleij 223*57f6190aSLinus Walleij static int ws2401_power_off(struct ws2401 *ws) 224*57f6190aSLinus Walleij { 225*57f6190aSLinus Walleij /* Go into RESET and disable regulators */ 226*57f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 1); 227*57f6190aSLinus Walleij return regulator_bulk_disable(ARRAY_SIZE(ws->regulators), 228*57f6190aSLinus Walleij ws->regulators); 229*57f6190aSLinus Walleij } 230*57f6190aSLinus Walleij 231*57f6190aSLinus Walleij static int ws2401_unprepare(struct drm_panel *panel) 232*57f6190aSLinus Walleij { 233*57f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel); 234*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 235*57f6190aSLinus Walleij 236*57f6190aSLinus Walleij /* Make sure we disable backlight, if any */ 237*57f6190aSLinus Walleij if (ws->internal_bl) 238*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); 239*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); 240*57f6190aSLinus Walleij msleep(120); 241*57f6190aSLinus Walleij return ws2401_power_off(to_ws2401(panel)); 242*57f6190aSLinus Walleij } 243*57f6190aSLinus Walleij 244*57f6190aSLinus Walleij static int ws2401_disable(struct drm_panel *panel) 245*57f6190aSLinus Walleij { 246*57f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel); 247*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 248*57f6190aSLinus Walleij 249*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); 250*57f6190aSLinus Walleij msleep(25); 251*57f6190aSLinus Walleij 252*57f6190aSLinus Walleij return 0; 253*57f6190aSLinus Walleij } 254*57f6190aSLinus Walleij 255*57f6190aSLinus Walleij static int ws2401_prepare(struct drm_panel *panel) 256*57f6190aSLinus Walleij { 257*57f6190aSLinus Walleij return ws2401_power_on(to_ws2401(panel)); 258*57f6190aSLinus Walleij } 259*57f6190aSLinus Walleij 260*57f6190aSLinus Walleij static int ws2401_enable(struct drm_panel *panel) 261*57f6190aSLinus Walleij { 262*57f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel); 263*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 264*57f6190aSLinus Walleij 265*57f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); 266*57f6190aSLinus Walleij 267*57f6190aSLinus Walleij return 0; 268*57f6190aSLinus Walleij } 269*57f6190aSLinus Walleij 270*57f6190aSLinus Walleij /** 271*57f6190aSLinus Walleij * ws2401_get_modes() - return the mode 272*57f6190aSLinus Walleij * @panel: the panel to get the mode for 273*57f6190aSLinus Walleij * @connector: reference to the central DRM connector control structure 274*57f6190aSLinus Walleij */ 275*57f6190aSLinus Walleij static int ws2401_get_modes(struct drm_panel *panel, 276*57f6190aSLinus Walleij struct drm_connector *connector) 277*57f6190aSLinus Walleij { 278*57f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel); 279*57f6190aSLinus Walleij struct drm_display_mode *mode; 280*57f6190aSLinus Walleij static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; 281*57f6190aSLinus Walleij 282*57f6190aSLinus Walleij /* 283*57f6190aSLinus Walleij * We just support the LMS380KF01 so far, if we implement more panels 284*57f6190aSLinus Walleij * this mode, the following connector display_info settings and 285*57f6190aSLinus Walleij * probably the custom DCS sequences needs to selected based on what 286*57f6190aSLinus Walleij * the target panel needs. 287*57f6190aSLinus Walleij */ 288*57f6190aSLinus Walleij mode = drm_mode_duplicate(connector->dev, &lms380kf01_480_800_mode); 289*57f6190aSLinus Walleij if (!mode) { 290*57f6190aSLinus Walleij dev_err(ws->dev, "failed to add mode\n"); 291*57f6190aSLinus Walleij return -ENOMEM; 292*57f6190aSLinus Walleij } 293*57f6190aSLinus Walleij 294*57f6190aSLinus Walleij connector->display_info.bpc = 8; 295*57f6190aSLinus Walleij connector->display_info.width_mm = mode->width_mm; 296*57f6190aSLinus Walleij connector->display_info.height_mm = mode->height_mm; 297*57f6190aSLinus Walleij connector->display_info.bus_flags = 298*57f6190aSLinus Walleij DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; 299*57f6190aSLinus Walleij drm_display_info_set_bus_formats(&connector->display_info, 300*57f6190aSLinus Walleij &bus_format, 1); 301*57f6190aSLinus Walleij 302*57f6190aSLinus Walleij drm_mode_set_name(mode); 303*57f6190aSLinus Walleij mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 304*57f6190aSLinus Walleij 305*57f6190aSLinus Walleij drm_mode_probed_add(connector, mode); 306*57f6190aSLinus Walleij 307*57f6190aSLinus Walleij return 1; 308*57f6190aSLinus Walleij } 309*57f6190aSLinus Walleij 310*57f6190aSLinus Walleij static const struct drm_panel_funcs ws2401_drm_funcs = { 311*57f6190aSLinus Walleij .disable = ws2401_disable, 312*57f6190aSLinus Walleij .unprepare = ws2401_unprepare, 313*57f6190aSLinus Walleij .prepare = ws2401_prepare, 314*57f6190aSLinus Walleij .enable = ws2401_enable, 315*57f6190aSLinus Walleij .get_modes = ws2401_get_modes, 316*57f6190aSLinus Walleij }; 317*57f6190aSLinus Walleij 318*57f6190aSLinus Walleij static int ws2401_set_brightness(struct backlight_device *bl) 319*57f6190aSLinus Walleij { 320*57f6190aSLinus Walleij struct ws2401 *ws = bl_get_data(bl); 321*57f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi; 322*57f6190aSLinus Walleij u8 brightness = backlight_get_brightness(bl); 323*57f6190aSLinus Walleij 324*57f6190aSLinus Walleij if (backlight_is_blank(bl)) { 325*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); 326*57f6190aSLinus Walleij } else { 327*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c); 328*57f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRDISBV, brightness); 329*57f6190aSLinus Walleij } 330*57f6190aSLinus Walleij 331*57f6190aSLinus Walleij return 0; 332*57f6190aSLinus Walleij } 333*57f6190aSLinus Walleij 334*57f6190aSLinus Walleij static const struct backlight_ops ws2401_bl_ops = { 335*57f6190aSLinus Walleij .update_status = ws2401_set_brightness, 336*57f6190aSLinus Walleij }; 337*57f6190aSLinus Walleij 338*57f6190aSLinus Walleij static const struct backlight_properties ws2401_bl_props = { 339*57f6190aSLinus Walleij .type = BACKLIGHT_PLATFORM, 340*57f6190aSLinus Walleij .brightness = 120, 341*57f6190aSLinus Walleij .max_brightness = U8_MAX, 342*57f6190aSLinus Walleij }; 343*57f6190aSLinus Walleij 344*57f6190aSLinus Walleij static int ws2401_probe(struct spi_device *spi) 345*57f6190aSLinus Walleij { 346*57f6190aSLinus Walleij struct device *dev = &spi->dev; 347*57f6190aSLinus Walleij struct ws2401 *ws; 348*57f6190aSLinus Walleij int ret; 349*57f6190aSLinus Walleij 350*57f6190aSLinus Walleij ws = devm_kzalloc(dev, sizeof(*ws), GFP_KERNEL); 351*57f6190aSLinus Walleij if (!ws) 352*57f6190aSLinus Walleij return -ENOMEM; 353*57f6190aSLinus Walleij ws->dev = dev; 354*57f6190aSLinus Walleij 355*57f6190aSLinus Walleij /* 356*57f6190aSLinus Walleij * VCI is the analog voltage supply 357*57f6190aSLinus Walleij * VCCIO is the digital I/O voltage supply 358*57f6190aSLinus Walleij */ 359*57f6190aSLinus Walleij ws->regulators[0].supply = "vci"; 360*57f6190aSLinus Walleij ws->regulators[1].supply = "vccio"; 361*57f6190aSLinus Walleij ret = devm_regulator_bulk_get(dev, 362*57f6190aSLinus Walleij ARRAY_SIZE(ws->regulators), 363*57f6190aSLinus Walleij ws->regulators); 364*57f6190aSLinus Walleij if (ret) 365*57f6190aSLinus Walleij return dev_err_probe(dev, ret, "failed to get regulators\n"); 366*57f6190aSLinus Walleij 367*57f6190aSLinus Walleij ws->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 368*57f6190aSLinus Walleij if (IS_ERR(ws->reset)) { 369*57f6190aSLinus Walleij ret = PTR_ERR(ws->reset); 370*57f6190aSLinus Walleij return dev_err_probe(dev, ret, "no RESET GPIO\n"); 371*57f6190aSLinus Walleij } 372*57f6190aSLinus Walleij 373*57f6190aSLinus Walleij ret = mipi_dbi_spi_init(spi, &ws->dbi, NULL); 374*57f6190aSLinus Walleij if (ret) 375*57f6190aSLinus Walleij return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); 376*57f6190aSLinus Walleij ws->dbi.read_commands = ws2401_dbi_read_commands; 377*57f6190aSLinus Walleij 378*57f6190aSLinus Walleij ws2401_power_on(ws); 379*57f6190aSLinus Walleij ws2401_read_mtp_id(ws); 380*57f6190aSLinus Walleij ws2401_power_off(ws); 381*57f6190aSLinus Walleij 382*57f6190aSLinus Walleij drm_panel_init(&ws->panel, dev, &ws2401_drm_funcs, 383*57f6190aSLinus Walleij DRM_MODE_CONNECTOR_DPI); 384*57f6190aSLinus Walleij 385*57f6190aSLinus Walleij ret = drm_panel_of_backlight(&ws->panel); 386*57f6190aSLinus Walleij if (ret) 387*57f6190aSLinus Walleij return dev_err_probe(dev, ret, 388*57f6190aSLinus Walleij "failed to get external backlight device\n"); 389*57f6190aSLinus Walleij 390*57f6190aSLinus Walleij if (!ws->panel.backlight) { 391*57f6190aSLinus Walleij dev_dbg(dev, "no external backlight, using internal backlight\n"); 392*57f6190aSLinus Walleij ws->panel.backlight = 393*57f6190aSLinus Walleij devm_backlight_device_register(dev, "ws2401", dev, ws, 394*57f6190aSLinus Walleij &ws2401_bl_ops, &ws2401_bl_props); 395*57f6190aSLinus Walleij if (IS_ERR(ws->panel.backlight)) 396*57f6190aSLinus Walleij return dev_err_probe(dev, PTR_ERR(ws->panel.backlight), 397*57f6190aSLinus Walleij "failed to register backlight device\n"); 398*57f6190aSLinus Walleij } else { 399*57f6190aSLinus Walleij dev_dbg(dev, "using external backlight\n"); 400*57f6190aSLinus Walleij } 401*57f6190aSLinus Walleij 402*57f6190aSLinus Walleij spi_set_drvdata(spi, ws); 403*57f6190aSLinus Walleij 404*57f6190aSLinus Walleij drm_panel_add(&ws->panel); 405*57f6190aSLinus Walleij dev_dbg(dev, "added panel\n"); 406*57f6190aSLinus Walleij 407*57f6190aSLinus Walleij return 0; 408*57f6190aSLinus Walleij } 409*57f6190aSLinus Walleij 410*57f6190aSLinus Walleij static int ws2401_remove(struct spi_device *spi) 411*57f6190aSLinus Walleij { 412*57f6190aSLinus Walleij struct ws2401 *ws = spi_get_drvdata(spi); 413*57f6190aSLinus Walleij 414*57f6190aSLinus Walleij drm_panel_remove(&ws->panel); 415*57f6190aSLinus Walleij return 0; 416*57f6190aSLinus Walleij } 417*57f6190aSLinus Walleij 418*57f6190aSLinus Walleij /* 419*57f6190aSLinus Walleij * Samsung LMS380KF01 is the one instance of this display controller that we 420*57f6190aSLinus Walleij * know about, but if more are found, the controller can be parameterized 421*57f6190aSLinus Walleij * here and used for other configurations. 422*57f6190aSLinus Walleij */ 423*57f6190aSLinus Walleij static const struct of_device_id ws2401_match[] = { 424*57f6190aSLinus Walleij { .compatible = "samsung,lms380kf01", }, 425*57f6190aSLinus Walleij {}, 426*57f6190aSLinus Walleij }; 427*57f6190aSLinus Walleij MODULE_DEVICE_TABLE(of, ws2401_match); 428*57f6190aSLinus Walleij 429*57f6190aSLinus Walleij static struct spi_driver ws2401_driver = { 430*57f6190aSLinus Walleij .probe = ws2401_probe, 431*57f6190aSLinus Walleij .remove = ws2401_remove, 432*57f6190aSLinus Walleij .driver = { 433*57f6190aSLinus Walleij .name = "ws2401-panel", 434*57f6190aSLinus Walleij .of_match_table = ws2401_match, 435*57f6190aSLinus Walleij }, 436*57f6190aSLinus Walleij }; 437*57f6190aSLinus Walleij module_spi_driver(ws2401_driver); 438*57f6190aSLinus Walleij 439*57f6190aSLinus Walleij MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); 440*57f6190aSLinus Walleij MODULE_DESCRIPTION("Samsung WS2401 panel driver"); 441*57f6190aSLinus Walleij MODULE_LICENSE("GPL v2"); 442