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