157f6190aSLinus Walleij // SPDX-License-Identifier: GPL-2.0
257f6190aSLinus Walleij /*
357f6190aSLinus Walleij * Panel driver for the WideChips WS2401 480x800 DPI RGB panel, used in
457f6190aSLinus Walleij * the Samsung Mobile Display (SMD) LMS380KF01.
557f6190aSLinus Walleij * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone.
657f6190aSLinus Walleij * Linus Walleij <linus.walleij@linaro.org>
757f6190aSLinus Walleij * Inspired by code and know-how in the vendor driver by Gareth Phillips.
857f6190aSLinus Walleij */
957f6190aSLinus Walleij #include <drm/drm_mipi_dbi.h>
1057f6190aSLinus Walleij #include <drm/drm_modes.h>
1157f6190aSLinus Walleij #include <drm/drm_panel.h>
1257f6190aSLinus Walleij
1357f6190aSLinus Walleij #include <linux/backlight.h>
1457f6190aSLinus Walleij #include <linux/delay.h>
1557f6190aSLinus Walleij #include <linux/gpio/consumer.h>
1657f6190aSLinus Walleij #include <linux/init.h>
1757f6190aSLinus Walleij #include <linux/kernel.h>
1857f6190aSLinus Walleij #include <linux/media-bus-format.h>
1957f6190aSLinus Walleij #include <linux/module.h>
2057f6190aSLinus Walleij #include <linux/regulator/consumer.h>
2157f6190aSLinus Walleij #include <linux/spi/spi.h>
2257f6190aSLinus Walleij
2357f6190aSLinus Walleij #include <video/mipi_display.h>
2457f6190aSLinus Walleij
2557f6190aSLinus Walleij #define WS2401_RESCTL 0xb8 /* Resolution select control */
2657f6190aSLinus Walleij #define WS2401_PSMPS 0xbd /* SMPS positive control */
2757f6190aSLinus Walleij #define WS2401_NSMPS 0xbe /* SMPS negative control */
2857f6190aSLinus Walleij #define WS2401_SMPS 0xbf
2957f6190aSLinus Walleij #define WS2401_BCMODE 0xc1 /* Backlight control mode */
3057f6190aSLinus Walleij #define WS2401_WRBLCTL 0xc3 /* Backlight control */
3157f6190aSLinus Walleij #define WS2401_WRDISBV 0xc4 /* Write manual brightness */
3257f6190aSLinus Walleij #define WS2401_WRCTRLD 0xc6 /* Write BL control */
3357f6190aSLinus Walleij #define WS2401_WRMIE 0xc7 /* Write MIE mode */
3457f6190aSLinus Walleij #define WS2401_READ_ID1 0xda /* Read panel ID 1 */
3557f6190aSLinus Walleij #define WS2401_READ_ID2 0xdb /* Read panel ID 2 */
3657f6190aSLinus Walleij #define WS2401_READ_ID3 0xdc /* Read panel ID 3 */
3757f6190aSLinus Walleij #define WS2401_GAMMA_R1 0xe7 /* Gamma red 1 */
3857f6190aSLinus Walleij #define WS2401_GAMMA_G1 0xe8 /* Gamma green 1 */
3957f6190aSLinus Walleij #define WS2401_GAMMA_B1 0xe9 /* Gamma blue 1 */
4057f6190aSLinus Walleij #define WS2401_GAMMA_R2 0xea /* Gamma red 2 */
4157f6190aSLinus Walleij #define WS2401_GAMMA_G2 0xeb /* Gamma green 2 */
4257f6190aSLinus Walleij #define WS2401_GAMMA_B2 0xec /* Gamma blue 2 */
4357f6190aSLinus Walleij #define WS2401_PASSWD1 0xf0 /* Password command for level 2 */
4457f6190aSLinus Walleij #define WS2401_DISCTL 0xf2 /* Display control */
4557f6190aSLinus Walleij #define WS2401_PWRCTL 0xf3 /* Power control */
4657f6190aSLinus Walleij #define WS2401_VCOMCTL 0xf4 /* VCOM control */
4757f6190aSLinus Walleij #define WS2401_SRCCTL 0xf5 /* Source control */
4857f6190aSLinus Walleij #define WS2401_PANELCTL 0xf6 /* Panel control */
4957f6190aSLinus Walleij
5057f6190aSLinus Walleij static const u8 ws2401_dbi_read_commands[] = {
5157f6190aSLinus Walleij WS2401_READ_ID1,
5257f6190aSLinus Walleij WS2401_READ_ID2,
5357f6190aSLinus Walleij WS2401_READ_ID3,
5457f6190aSLinus Walleij 0, /* sentinel */
5557f6190aSLinus Walleij };
5657f6190aSLinus Walleij
5757f6190aSLinus Walleij /**
5857f6190aSLinus Walleij * struct ws2401 - state container for a panel controlled by the WS2401
5957f6190aSLinus Walleij * controller
6057f6190aSLinus Walleij */
6157f6190aSLinus Walleij struct ws2401 {
6257f6190aSLinus Walleij /** @dev: the container device */
6357f6190aSLinus Walleij struct device *dev;
6457f6190aSLinus Walleij /** @dbi: the DBI bus abstraction handle */
6557f6190aSLinus Walleij struct mipi_dbi dbi;
6657f6190aSLinus Walleij /** @panel: the DRM panel instance for this device */
6757f6190aSLinus Walleij struct drm_panel panel;
6857f6190aSLinus Walleij /** @width: the width of this panel in mm */
6957f6190aSLinus Walleij u32 width;
7057f6190aSLinus Walleij /** @height: the height of this panel in mm */
7157f6190aSLinus Walleij u32 height;
7257f6190aSLinus Walleij /** @reset: reset GPIO line */
7357f6190aSLinus Walleij struct gpio_desc *reset;
7457f6190aSLinus Walleij /** @regulators: VCCIO and VIO supply regulators */
7557f6190aSLinus Walleij struct regulator_bulk_data regulators[2];
7657f6190aSLinus Walleij /** @internal_bl: If using internal backlight */
7757f6190aSLinus Walleij bool internal_bl;
7857f6190aSLinus Walleij };
7957f6190aSLinus Walleij
8057f6190aSLinus Walleij static const struct drm_display_mode lms380kf01_480_800_mode = {
8157f6190aSLinus Walleij /*
8257f6190aSLinus Walleij * The vendor driver states that the "SMD panel" has a clock
8357f6190aSLinus Walleij * frequency of 49920000 Hz / 2 = 24960000 Hz.
8457f6190aSLinus Walleij */
8557f6190aSLinus Walleij .clock = 24960,
8657f6190aSLinus Walleij .hdisplay = 480,
8757f6190aSLinus Walleij .hsync_start = 480 + 8,
8857f6190aSLinus Walleij .hsync_end = 480 + 8 + 10,
8957f6190aSLinus Walleij .htotal = 480 + 8 + 10 + 8,
9057f6190aSLinus Walleij .vdisplay = 800,
9157f6190aSLinus Walleij .vsync_start = 800 + 8,
9257f6190aSLinus Walleij .vsync_end = 800 + 8 + 2,
9357f6190aSLinus Walleij .vtotal = 800 + 8 + 2 + 18,
9457f6190aSLinus Walleij .width_mm = 50,
9557f6190aSLinus Walleij .height_mm = 84,
9657f6190aSLinus Walleij .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
9757f6190aSLinus Walleij };
9857f6190aSLinus Walleij
to_ws2401(struct drm_panel * panel)9957f6190aSLinus Walleij static inline struct ws2401 *to_ws2401(struct drm_panel *panel)
10057f6190aSLinus Walleij {
10157f6190aSLinus Walleij return container_of(panel, struct ws2401, panel);
10257f6190aSLinus Walleij }
10357f6190aSLinus Walleij
ws2401_read_mtp_id(struct ws2401 * ws)10457f6190aSLinus Walleij static void ws2401_read_mtp_id(struct ws2401 *ws)
10557f6190aSLinus Walleij {
10657f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
10757f6190aSLinus Walleij u8 id1, id2, id3;
10857f6190aSLinus Walleij int ret;
10957f6190aSLinus Walleij
11057f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID1, &id1);
11157f6190aSLinus Walleij if (ret) {
11257f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 1\n");
11357f6190aSLinus Walleij return;
11457f6190aSLinus Walleij }
11557f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID2, &id2);
11657f6190aSLinus Walleij if (ret) {
11757f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 2\n");
11857f6190aSLinus Walleij return;
11957f6190aSLinus Walleij }
12057f6190aSLinus Walleij ret = mipi_dbi_command_read(dbi, WS2401_READ_ID3, &id3);
12157f6190aSLinus Walleij if (ret) {
12257f6190aSLinus Walleij dev_err(ws->dev, "unable to read MTP ID 3\n");
12357f6190aSLinus Walleij return;
12457f6190aSLinus Walleij }
12557f6190aSLinus Walleij dev_info(ws->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
12657f6190aSLinus Walleij }
12757f6190aSLinus Walleij
ws2401_power_on(struct ws2401 * ws)12857f6190aSLinus Walleij static int ws2401_power_on(struct ws2401 *ws)
12957f6190aSLinus Walleij {
13057f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
13157f6190aSLinus Walleij int ret;
13257f6190aSLinus Walleij
13357f6190aSLinus Walleij /* Power up */
13457f6190aSLinus Walleij ret = regulator_bulk_enable(ARRAY_SIZE(ws->regulators),
13557f6190aSLinus Walleij ws->regulators);
13657f6190aSLinus Walleij if (ret) {
13757f6190aSLinus Walleij dev_err(ws->dev, "failed to enable regulators: %d\n", ret);
13857f6190aSLinus Walleij return ret;
13957f6190aSLinus Walleij }
14057f6190aSLinus Walleij msleep(10);
14157f6190aSLinus Walleij
14257f6190aSLinus Walleij /* Assert reset >=1 ms */
14357f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 1);
14457f6190aSLinus Walleij usleep_range(1000, 5000);
14557f6190aSLinus Walleij /* De-assert reset */
14657f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 0);
14757f6190aSLinus Walleij /* Wait >= 10 ms */
14857f6190aSLinus Walleij msleep(10);
14957f6190aSLinus Walleij dev_dbg(ws->dev, "de-asserted RESET\n");
15057f6190aSLinus Walleij
15157f6190aSLinus Walleij /*
15257f6190aSLinus Walleij * Exit sleep mode and initialize display - some hammering is
15357f6190aSLinus Walleij * necessary.
15457f6190aSLinus Walleij */
15557f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
15657f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
15757f6190aSLinus Walleij msleep(50);
15857f6190aSLinus Walleij
15957f6190aSLinus Walleij /* Magic to unlock level 2 control of the display */
16057f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PASSWD1, 0x5a, 0x5a);
16157f6190aSLinus Walleij /* Configure resolution to 480RGBx800 */
16257f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_RESCTL, 0x12);
16357f6190aSLinus Walleij /* Set addressing mode Flip V(d0), Flip H(d1) RGB/BGR(d3) */
16457f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x01);
16557f6190aSLinus Walleij /* Set pixel format: 24 bpp */
16657f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x70);
16757f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SMPS, 0x00, 0x0f);
16857f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PSMPS, 0x06, 0x03, /* DDVDH: 4.6v */
16957f6190aSLinus Walleij 0x7e, 0x03, 0x12, 0x37);
17057f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_NSMPS, 0x06, 0x03, /* DDVDH: -4.6v */
17157f6190aSLinus Walleij 0x7e, 0x02, 0x15, 0x37);
17257f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SMPS, 0x02, 0x0f);
17357f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PWRCTL, 0x10, 0xA9, 0x00, 0x01, 0x44,
17457f6190aSLinus Walleij 0xb4, /* VGH:16.1v, VGL:-13.8v */
17557f6190aSLinus Walleij 0x50, /* GREFP:4.2v (default) */
17657f6190aSLinus Walleij 0x50, /* GREFN:-4.2v (default) */
17757f6190aSLinus Walleij 0x00,
17857f6190aSLinus Walleij 0x44); /* VOUTL:-10v (default) */
17957f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_DISCTL, 0x01, 0x00, 0x00, 0x00, 0x14,
18057f6190aSLinus Walleij 0x16);
18157f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_VCOMCTL, 0x30, 0x53, 0x53);
18257f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_SRCCTL, 0x03, 0x0C, 0x00, 0x00, 0x00,
18357f6190aSLinus Walleij 0x01, /* 2 dot inversion */
18457f6190aSLinus Walleij 0x01, 0x06, 0x03);
18557f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PANELCTL, 0x14, 0x00, 0x80, 0x00);
18657f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRMIE, 0x01);
18757f6190aSLinus Walleij
18857f6190aSLinus Walleij /* Set up gamma, probably these are P-gamma and N-gamma for each color */
18957f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_R1, 0x00,
19057f6190aSLinus Walleij 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e,
19157f6190aSLinus Walleij 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00);
19257f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_R2, 0x00,
19357f6190aSLinus Walleij 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e,
19457f6190aSLinus Walleij 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00);
19557f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_G1, 0x00,
19657f6190aSLinus Walleij 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f,
19757f6190aSLinus Walleij 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00);
19857f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_G2, 0x00,
19957f6190aSLinus Walleij 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f,
20057f6190aSLinus Walleij 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00);
20157f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_B1, 0x00,
20257f6190aSLinus Walleij 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27,
20357f6190aSLinus Walleij 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00);
20457f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_GAMMA_B2, 0x00,
20557f6190aSLinus Walleij 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27,
20657f6190aSLinus Walleij 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00);
20757f6190aSLinus Walleij
20857f6190aSLinus Walleij if (ws->internal_bl) {
20957f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c);
21057f6190aSLinus Walleij } else {
21157f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
21257f6190aSLinus Walleij /*
21357f6190aSLinus Walleij * When not using internal backlight we do not need any further
21457f6190aSLinus Walleij * L2 accesses to the panel so we close the door on our way out.
21557f6190aSLinus Walleij * Otherwise we need to leave the L2 door open.
21657f6190aSLinus Walleij */
21757f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_PASSWD1, 0xa5, 0xa5);
21857f6190aSLinus Walleij }
21957f6190aSLinus Walleij
22057f6190aSLinus Walleij return 0;
22157f6190aSLinus Walleij }
22257f6190aSLinus Walleij
ws2401_power_off(struct ws2401 * ws)22357f6190aSLinus Walleij static int ws2401_power_off(struct ws2401 *ws)
22457f6190aSLinus Walleij {
22557f6190aSLinus Walleij /* Go into RESET and disable regulators */
22657f6190aSLinus Walleij gpiod_set_value_cansleep(ws->reset, 1);
22757f6190aSLinus Walleij return regulator_bulk_disable(ARRAY_SIZE(ws->regulators),
22857f6190aSLinus Walleij ws->regulators);
22957f6190aSLinus Walleij }
23057f6190aSLinus Walleij
ws2401_unprepare(struct drm_panel * panel)23157f6190aSLinus Walleij static int ws2401_unprepare(struct drm_panel *panel)
23257f6190aSLinus Walleij {
23357f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel);
23457f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
23557f6190aSLinus Walleij
23657f6190aSLinus Walleij /* Make sure we disable backlight, if any */
23757f6190aSLinus Walleij if (ws->internal_bl)
23857f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
23957f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE);
24057f6190aSLinus Walleij msleep(120);
24157f6190aSLinus Walleij return ws2401_power_off(to_ws2401(panel));
24257f6190aSLinus Walleij }
24357f6190aSLinus Walleij
ws2401_disable(struct drm_panel * panel)24457f6190aSLinus Walleij static int ws2401_disable(struct drm_panel *panel)
24557f6190aSLinus Walleij {
24657f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel);
24757f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
24857f6190aSLinus Walleij
24957f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
25057f6190aSLinus Walleij msleep(25);
25157f6190aSLinus Walleij
25257f6190aSLinus Walleij return 0;
25357f6190aSLinus Walleij }
25457f6190aSLinus Walleij
ws2401_prepare(struct drm_panel * panel)25557f6190aSLinus Walleij static int ws2401_prepare(struct drm_panel *panel)
25657f6190aSLinus Walleij {
25757f6190aSLinus Walleij return ws2401_power_on(to_ws2401(panel));
25857f6190aSLinus Walleij }
25957f6190aSLinus Walleij
ws2401_enable(struct drm_panel * panel)26057f6190aSLinus Walleij static int ws2401_enable(struct drm_panel *panel)
26157f6190aSLinus Walleij {
26257f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel);
26357f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
26457f6190aSLinus Walleij
26557f6190aSLinus Walleij mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
26657f6190aSLinus Walleij
26757f6190aSLinus Walleij return 0;
26857f6190aSLinus Walleij }
26957f6190aSLinus Walleij
27057f6190aSLinus Walleij /**
27157f6190aSLinus Walleij * ws2401_get_modes() - return the mode
27257f6190aSLinus Walleij * @panel: the panel to get the mode for
27357f6190aSLinus Walleij * @connector: reference to the central DRM connector control structure
27457f6190aSLinus Walleij */
ws2401_get_modes(struct drm_panel * panel,struct drm_connector * connector)27557f6190aSLinus Walleij static int ws2401_get_modes(struct drm_panel *panel,
27657f6190aSLinus Walleij struct drm_connector *connector)
27757f6190aSLinus Walleij {
27857f6190aSLinus Walleij struct ws2401 *ws = to_ws2401(panel);
27957f6190aSLinus Walleij struct drm_display_mode *mode;
28057f6190aSLinus Walleij static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
28157f6190aSLinus Walleij
28257f6190aSLinus Walleij /*
28357f6190aSLinus Walleij * We just support the LMS380KF01 so far, if we implement more panels
28457f6190aSLinus Walleij * this mode, the following connector display_info settings and
28557f6190aSLinus Walleij * probably the custom DCS sequences needs to selected based on what
28657f6190aSLinus Walleij * the target panel needs.
28757f6190aSLinus Walleij */
28857f6190aSLinus Walleij mode = drm_mode_duplicate(connector->dev, &lms380kf01_480_800_mode);
28957f6190aSLinus Walleij if (!mode) {
29057f6190aSLinus Walleij dev_err(ws->dev, "failed to add mode\n");
29157f6190aSLinus Walleij return -ENOMEM;
29257f6190aSLinus Walleij }
29357f6190aSLinus Walleij
29457f6190aSLinus Walleij connector->display_info.bpc = 8;
29557f6190aSLinus Walleij connector->display_info.width_mm = mode->width_mm;
29657f6190aSLinus Walleij connector->display_info.height_mm = mode->height_mm;
29757f6190aSLinus Walleij connector->display_info.bus_flags =
29857f6190aSLinus Walleij DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
29957f6190aSLinus Walleij drm_display_info_set_bus_formats(&connector->display_info,
30057f6190aSLinus Walleij &bus_format, 1);
30157f6190aSLinus Walleij
30257f6190aSLinus Walleij drm_mode_set_name(mode);
30357f6190aSLinus Walleij mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
30457f6190aSLinus Walleij
30557f6190aSLinus Walleij drm_mode_probed_add(connector, mode);
30657f6190aSLinus Walleij
30757f6190aSLinus Walleij return 1;
30857f6190aSLinus Walleij }
30957f6190aSLinus Walleij
31057f6190aSLinus Walleij static const struct drm_panel_funcs ws2401_drm_funcs = {
31157f6190aSLinus Walleij .disable = ws2401_disable,
31257f6190aSLinus Walleij .unprepare = ws2401_unprepare,
31357f6190aSLinus Walleij .prepare = ws2401_prepare,
31457f6190aSLinus Walleij .enable = ws2401_enable,
31557f6190aSLinus Walleij .get_modes = ws2401_get_modes,
31657f6190aSLinus Walleij };
31757f6190aSLinus Walleij
ws2401_set_brightness(struct backlight_device * bl)31857f6190aSLinus Walleij static int ws2401_set_brightness(struct backlight_device *bl)
31957f6190aSLinus Walleij {
32057f6190aSLinus Walleij struct ws2401 *ws = bl_get_data(bl);
32157f6190aSLinus Walleij struct mipi_dbi *dbi = &ws->dbi;
32257f6190aSLinus Walleij u8 brightness = backlight_get_brightness(bl);
32357f6190aSLinus Walleij
32457f6190aSLinus Walleij if (backlight_is_blank(bl)) {
32557f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
32657f6190aSLinus Walleij } else {
32757f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c);
32857f6190aSLinus Walleij mipi_dbi_command(dbi, WS2401_WRDISBV, brightness);
32957f6190aSLinus Walleij }
33057f6190aSLinus Walleij
33157f6190aSLinus Walleij return 0;
33257f6190aSLinus Walleij }
33357f6190aSLinus Walleij
33457f6190aSLinus Walleij static const struct backlight_ops ws2401_bl_ops = {
33557f6190aSLinus Walleij .update_status = ws2401_set_brightness,
33657f6190aSLinus Walleij };
33757f6190aSLinus Walleij
33857f6190aSLinus Walleij static const struct backlight_properties ws2401_bl_props = {
33957f6190aSLinus Walleij .type = BACKLIGHT_PLATFORM,
34057f6190aSLinus Walleij .brightness = 120,
34157f6190aSLinus Walleij .max_brightness = U8_MAX,
34257f6190aSLinus Walleij };
34357f6190aSLinus Walleij
ws2401_probe(struct spi_device * spi)34457f6190aSLinus Walleij static int ws2401_probe(struct spi_device *spi)
34557f6190aSLinus Walleij {
34657f6190aSLinus Walleij struct device *dev = &spi->dev;
34757f6190aSLinus Walleij struct ws2401 *ws;
34857f6190aSLinus Walleij int ret;
34957f6190aSLinus Walleij
35057f6190aSLinus Walleij ws = devm_kzalloc(dev, sizeof(*ws), GFP_KERNEL);
35157f6190aSLinus Walleij if (!ws)
35257f6190aSLinus Walleij return -ENOMEM;
35357f6190aSLinus Walleij ws->dev = dev;
35457f6190aSLinus Walleij
35557f6190aSLinus Walleij /*
35657f6190aSLinus Walleij * VCI is the analog voltage supply
35757f6190aSLinus Walleij * VCCIO is the digital I/O voltage supply
35857f6190aSLinus Walleij */
35957f6190aSLinus Walleij ws->regulators[0].supply = "vci";
36057f6190aSLinus Walleij ws->regulators[1].supply = "vccio";
36157f6190aSLinus Walleij ret = devm_regulator_bulk_get(dev,
36257f6190aSLinus Walleij ARRAY_SIZE(ws->regulators),
36357f6190aSLinus Walleij ws->regulators);
36457f6190aSLinus Walleij if (ret)
36557f6190aSLinus Walleij return dev_err_probe(dev, ret, "failed to get regulators\n");
36657f6190aSLinus Walleij
36757f6190aSLinus Walleij ws->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
36857f6190aSLinus Walleij if (IS_ERR(ws->reset)) {
36957f6190aSLinus Walleij ret = PTR_ERR(ws->reset);
37057f6190aSLinus Walleij return dev_err_probe(dev, ret, "no RESET GPIO\n");
37157f6190aSLinus Walleij }
37257f6190aSLinus Walleij
37357f6190aSLinus Walleij ret = mipi_dbi_spi_init(spi, &ws->dbi, NULL);
37457f6190aSLinus Walleij if (ret)
37557f6190aSLinus Walleij return dev_err_probe(dev, ret, "MIPI DBI init failed\n");
37657f6190aSLinus Walleij ws->dbi.read_commands = ws2401_dbi_read_commands;
37757f6190aSLinus Walleij
37857f6190aSLinus Walleij ws2401_power_on(ws);
37957f6190aSLinus Walleij ws2401_read_mtp_id(ws);
38057f6190aSLinus Walleij ws2401_power_off(ws);
38157f6190aSLinus Walleij
38257f6190aSLinus Walleij drm_panel_init(&ws->panel, dev, &ws2401_drm_funcs,
38357f6190aSLinus Walleij DRM_MODE_CONNECTOR_DPI);
38457f6190aSLinus Walleij
38557f6190aSLinus Walleij ret = drm_panel_of_backlight(&ws->panel);
38657f6190aSLinus Walleij if (ret)
38757f6190aSLinus Walleij return dev_err_probe(dev, ret,
38857f6190aSLinus Walleij "failed to get external backlight device\n");
38957f6190aSLinus Walleij
39057f6190aSLinus Walleij if (!ws->panel.backlight) {
39157f6190aSLinus Walleij dev_dbg(dev, "no external backlight, using internal backlight\n");
39257f6190aSLinus Walleij ws->panel.backlight =
39357f6190aSLinus Walleij devm_backlight_device_register(dev, "ws2401", dev, ws,
39457f6190aSLinus Walleij &ws2401_bl_ops, &ws2401_bl_props);
39557f6190aSLinus Walleij if (IS_ERR(ws->panel.backlight))
39657f6190aSLinus Walleij return dev_err_probe(dev, PTR_ERR(ws->panel.backlight),
39757f6190aSLinus Walleij "failed to register backlight device\n");
39857f6190aSLinus Walleij } else {
39957f6190aSLinus Walleij dev_dbg(dev, "using external backlight\n");
40057f6190aSLinus Walleij }
40157f6190aSLinus Walleij
40257f6190aSLinus Walleij spi_set_drvdata(spi, ws);
40357f6190aSLinus Walleij
40457f6190aSLinus Walleij drm_panel_add(&ws->panel);
40557f6190aSLinus Walleij dev_dbg(dev, "added panel\n");
40657f6190aSLinus Walleij
40757f6190aSLinus Walleij return 0;
40857f6190aSLinus Walleij }
40957f6190aSLinus Walleij
ws2401_remove(struct spi_device * spi)410a0386bbaSUwe Kleine-König static void ws2401_remove(struct spi_device *spi)
41157f6190aSLinus Walleij {
41257f6190aSLinus Walleij struct ws2401 *ws = spi_get_drvdata(spi);
41357f6190aSLinus Walleij
41457f6190aSLinus Walleij drm_panel_remove(&ws->panel);
41557f6190aSLinus Walleij }
41657f6190aSLinus Walleij
41757f6190aSLinus Walleij /*
41857f6190aSLinus Walleij * Samsung LMS380KF01 is the one instance of this display controller that we
41957f6190aSLinus Walleij * know about, but if more are found, the controller can be parameterized
42057f6190aSLinus Walleij * here and used for other configurations.
42157f6190aSLinus Walleij */
42257f6190aSLinus Walleij static const struct of_device_id ws2401_match[] = {
42357f6190aSLinus Walleij { .compatible = "samsung,lms380kf01", },
42457f6190aSLinus Walleij {},
42557f6190aSLinus Walleij };
42657f6190aSLinus Walleij MODULE_DEVICE_TABLE(of, ws2401_match);
42757f6190aSLinus Walleij
428*353b6bf2SWei Yongjun static const struct spi_device_id ws2401_ids[] = {
429*353b6bf2SWei Yongjun { "lms380kf01" },
430*353b6bf2SWei Yongjun { },
431*353b6bf2SWei Yongjun };
432*353b6bf2SWei Yongjun MODULE_DEVICE_TABLE(spi, ws2401_ids);
433*353b6bf2SWei Yongjun
43457f6190aSLinus Walleij static struct spi_driver ws2401_driver = {
43557f6190aSLinus Walleij .probe = ws2401_probe,
43657f6190aSLinus Walleij .remove = ws2401_remove,
437*353b6bf2SWei Yongjun .id_table = ws2401_ids,
43857f6190aSLinus Walleij .driver = {
43957f6190aSLinus Walleij .name = "ws2401-panel",
44057f6190aSLinus Walleij .of_match_table = ws2401_match,
44157f6190aSLinus Walleij },
44257f6190aSLinus Walleij };
44357f6190aSLinus Walleij module_spi_driver(ws2401_driver);
44457f6190aSLinus Walleij
44557f6190aSLinus Walleij MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
44657f6190aSLinus Walleij MODULE_DESCRIPTION("Samsung WS2401 panel driver");
44757f6190aSLinus Walleij MODULE_LICENSE("GPL v2");
448