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