xref: /openbmc/linux/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
12dab3bf8SPhilippe CORNU // SPDX-License-Identifier: GPL-2.0
2f0a5bb98SPhilippe CORNU /*
3f0a5bb98SPhilippe CORNU  * Copyright (C) STMicroelectronics SA 2017
4f0a5bb98SPhilippe CORNU  *
5f0a5bb98SPhilippe CORNU  * Authors: Philippe Cornu <philippe.cornu@st.com>
6f0a5bb98SPhilippe CORNU  *          Yannick Fertre <yannick.fertre@st.com>
7f0a5bb98SPhilippe CORNU  */
82dab3bf8SPhilippe CORNU 
9f0a5bb98SPhilippe CORNU #include <linux/backlight.h>
10cb23eae3SSam Ravnborg #include <linux/delay.h>
11f0a5bb98SPhilippe CORNU #include <linux/gpio/consumer.h>
12cb23eae3SSam Ravnborg #include <linux/module.h>
13ded8d7feSPhilippe CORNU #include <linux/regulator/consumer.h>
14cb23eae3SSam Ravnborg 
15f0a5bb98SPhilippe CORNU #include <video/mipi_display.h>
16f0a5bb98SPhilippe CORNU 
17cb23eae3SSam Ravnborg #include <drm/drm_mipi_dsi.h>
18cb23eae3SSam Ravnborg #include <drm/drm_modes.h>
19cb23eae3SSam Ravnborg #include <drm/drm_panel.h>
20cb23eae3SSam Ravnborg 
21f0a5bb98SPhilippe CORNU #define OTM8009A_BACKLIGHT_DEFAULT	240
22f0a5bb98SPhilippe CORNU #define OTM8009A_BACKLIGHT_MAX		255
23f0a5bb98SPhilippe CORNU 
24f0a5bb98SPhilippe CORNU /* Manufacturer Command Set */
25f0a5bb98SPhilippe CORNU #define MCS_ADRSFT	0x0000	/* Address Shift Function */
26f0a5bb98SPhilippe CORNU #define MCS_PANSET	0xB3A6	/* Panel Type Setting */
27f0a5bb98SPhilippe CORNU #define MCS_SD_CTRL	0xC0A2	/* Source Driver Timing Setting */
28f0a5bb98SPhilippe CORNU #define MCS_P_DRV_M	0xC0B4	/* Panel Driving Mode */
29f0a5bb98SPhilippe CORNU #define MCS_OSC_ADJ	0xC181	/* Oscillator Adjustment for Idle/Normal mode */
30f0a5bb98SPhilippe CORNU #define MCS_RGB_VID_SET	0xC1A1	/* RGB Video Mode Setting */
31f0a5bb98SPhilippe CORNU #define MCS_SD_PCH_CTRL	0xC480	/* Source Driver Precharge Control */
32f0a5bb98SPhilippe CORNU #define MCS_NO_DOC1	0xC48A	/* Command not documented */
33f0a5bb98SPhilippe CORNU #define MCS_PWR_CTRL1	0xC580	/* Power Control Setting 1 */
34f0a5bb98SPhilippe CORNU #define MCS_PWR_CTRL2	0xC590	/* Power Control Setting 2 for Normal Mode */
35f0a5bb98SPhilippe CORNU #define MCS_PWR_CTRL4	0xC5B0	/* Power Control Setting 4 for DC Voltage */
36f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET1	0xCB80	/* Panel Control Setting 1 */
37f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET2	0xCB90	/* Panel Control Setting 2 */
38f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET3	0xCBA0	/* Panel Control Setting 3 */
39f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET4	0xCBB0	/* Panel Control Setting 4 */
40f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET5	0xCBC0	/* Panel Control Setting 5 */
41f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET6	0xCBD0	/* Panel Control Setting 6 */
42f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET7	0xCBE0	/* Panel Control Setting 7 */
43f0a5bb98SPhilippe CORNU #define MCS_PANCTRLSET8	0xCBF0	/* Panel Control Setting 8 */
44f0a5bb98SPhilippe CORNU #define MCS_PANU2D1	0xCC80	/* Panel U2D Setting 1 */
45f0a5bb98SPhilippe CORNU #define MCS_PANU2D2	0xCC90	/* Panel U2D Setting 2 */
46f0a5bb98SPhilippe CORNU #define MCS_PANU2D3	0xCCA0	/* Panel U2D Setting 3 */
47f0a5bb98SPhilippe CORNU #define MCS_PAND2U1	0xCCB0	/* Panel D2U Setting 1 */
48f0a5bb98SPhilippe CORNU #define MCS_PAND2U2	0xCCC0	/* Panel D2U Setting 2 */
49f0a5bb98SPhilippe CORNU #define MCS_PAND2U3	0xCCD0	/* Panel D2U Setting 3 */
50f0a5bb98SPhilippe CORNU #define MCS_GOAVST	0xCE80	/* GOA VST Setting */
51f0a5bb98SPhilippe CORNU #define MCS_GOACLKA1	0xCEA0	/* GOA CLKA1 Setting */
52f0a5bb98SPhilippe CORNU #define MCS_GOACLKA3	0xCEB0	/* GOA CLKA3 Setting */
53f0a5bb98SPhilippe CORNU #define MCS_GOAECLK	0xCFC0	/* GOA ECLK Setting */
54f0a5bb98SPhilippe CORNU #define MCS_NO_DOC2	0xCFD0	/* Command not documented */
55f0a5bb98SPhilippe CORNU #define MCS_GVDDSET	0xD800	/* GVDD/NGVDD */
56f0a5bb98SPhilippe CORNU #define MCS_VCOMDC	0xD900	/* VCOM Voltage Setting */
57f0a5bb98SPhilippe CORNU #define MCS_GMCT2_2P	0xE100	/* Gamma Correction 2.2+ Setting */
58f0a5bb98SPhilippe CORNU #define MCS_GMCT2_2N	0xE200	/* Gamma Correction 2.2- Setting */
59f0a5bb98SPhilippe CORNU #define MCS_NO_DOC3	0xF5B6	/* Command not documented */
60f0a5bb98SPhilippe CORNU #define MCS_CMD2_ENA1	0xFF00	/* Enable Access Command2 "CMD2" */
61f0a5bb98SPhilippe CORNU #define MCS_CMD2_ENA2	0xFF80	/* Enable Access Orise Command2 */
62f0a5bb98SPhilippe CORNU 
635bd785a8SRaphael GALLAIS-POU - foss #define OTM8009A_HDISPLAY	480
645bd785a8SRaphael GALLAIS-POU - foss #define OTM8009A_VDISPLAY	800
655bd785a8SRaphael GALLAIS-POU - foss 
66f0a5bb98SPhilippe CORNU struct otm8009a {
67f0a5bb98SPhilippe CORNU 	struct device *dev;
68f0a5bb98SPhilippe CORNU 	struct drm_panel panel;
69f0a5bb98SPhilippe CORNU 	struct backlight_device *bl_dev;
70f0a5bb98SPhilippe CORNU 	struct gpio_desc *reset_gpio;
71ded8d7feSPhilippe CORNU 	struct regulator *supply;
72f0a5bb98SPhilippe CORNU 	bool prepared;
73f0a5bb98SPhilippe CORNU 	bool enabled;
74f0a5bb98SPhilippe CORNU };
75f0a5bb98SPhilippe CORNU 
765bd785a8SRaphael GALLAIS-POU - foss static const struct drm_display_mode modes[] = {
775bd785a8SRaphael GALLAIS-POU - foss 	{ /* 50 Hz, preferred */
78fc13d710SYannick Fertré 		.clock = 29700,
79f0a5bb98SPhilippe CORNU 		.hdisplay = 480,
80fc13d710SYannick Fertré 		.hsync_start = 480 + 98,
81fc13d710SYannick Fertré 		.hsync_end = 480 + 98 + 32,
82fc13d710SYannick Fertré 		.htotal = 480 + 98 + 32 + 98,
83f0a5bb98SPhilippe CORNU 		.vdisplay = 800,
84fc13d710SYannick Fertré 		.vsync_start = 800 + 15,
85fc13d710SYannick Fertré 		.vsync_end = 800 + 15 + 10,
86fc13d710SYannick Fertré 		.vtotal = 800 + 15 + 10 + 14,
875bd785a8SRaphael GALLAIS-POU - foss 		.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
88f0a5bb98SPhilippe CORNU 		.width_mm = 52,
89f0a5bb98SPhilippe CORNU 		.height_mm = 86,
905bd785a8SRaphael GALLAIS-POU - foss 	},
915bd785a8SRaphael GALLAIS-POU - foss 	{ /* 60 Hz */
925bd785a8SRaphael GALLAIS-POU - foss 		.clock = 33000,
935bd785a8SRaphael GALLAIS-POU - foss 		.hdisplay = 480,
945bd785a8SRaphael GALLAIS-POU - foss 		.hsync_start = 480 + 70,
955bd785a8SRaphael GALLAIS-POU - foss 		.hsync_end = 480 + 70 + 32,
965bd785a8SRaphael GALLAIS-POU - foss 		.htotal = 480 + 70 + 32 + 72,
975bd785a8SRaphael GALLAIS-POU - foss 		.vdisplay = 800,
985bd785a8SRaphael GALLAIS-POU - foss 		.vsync_start = 800 + 15,
995bd785a8SRaphael GALLAIS-POU - foss 		.vsync_end = 800 + 15 + 10,
1005bd785a8SRaphael GALLAIS-POU - foss 		.vtotal = 800 + 15 + 10 + 16,
1015bd785a8SRaphael GALLAIS-POU - foss 		.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
1025bd785a8SRaphael GALLAIS-POU - foss 		.width_mm = 52,
1035bd785a8SRaphael GALLAIS-POU - foss 		.height_mm = 86,
1045bd785a8SRaphael GALLAIS-POU - foss 	},
105f0a5bb98SPhilippe CORNU };
106f0a5bb98SPhilippe CORNU 
panel_to_otm8009a(struct drm_panel * panel)107f0a5bb98SPhilippe CORNU static inline struct otm8009a *panel_to_otm8009a(struct drm_panel *panel)
108f0a5bb98SPhilippe CORNU {
109f0a5bb98SPhilippe CORNU 	return container_of(panel, struct otm8009a, panel);
110f0a5bb98SPhilippe CORNU }
111f0a5bb98SPhilippe CORNU 
otm8009a_dcs_write_buf(struct otm8009a * ctx,const void * data,size_t len)112f0a5bb98SPhilippe CORNU static void otm8009a_dcs_write_buf(struct otm8009a *ctx, const void *data,
113f0a5bb98SPhilippe CORNU 				   size_t len)
114f0a5bb98SPhilippe CORNU {
115f0a5bb98SPhilippe CORNU 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
116f0a5bb98SPhilippe CORNU 
117f0a5bb98SPhilippe CORNU 	if (mipi_dsi_dcs_write_buffer(dsi, data, len) < 0)
118a25b6b27SSam Ravnborg 		dev_warn(ctx->dev, "mipi dsi dcs write buffer failed\n");
119f0a5bb98SPhilippe CORNU }
120f0a5bb98SPhilippe CORNU 
121f0a5bb98SPhilippe CORNU #define dcs_write_seq(ctx, seq...)			\
122f0a5bb98SPhilippe CORNU ({							\
123f0a5bb98SPhilippe CORNU 	static const u8 d[] = { seq };			\
124f0a5bb98SPhilippe CORNU 	otm8009a_dcs_write_buf(ctx, d, ARRAY_SIZE(d));	\
125f0a5bb98SPhilippe CORNU })
126f0a5bb98SPhilippe CORNU 
127f0a5bb98SPhilippe CORNU #define dcs_write_cmd_at(ctx, cmd, seq...)		\
128f0a5bb98SPhilippe CORNU ({							\
129f0a5bb98SPhilippe CORNU 	dcs_write_seq(ctx, MCS_ADRSFT, (cmd) & 0xFF);	\
130f0a5bb98SPhilippe CORNU 	dcs_write_seq(ctx, (cmd) >> 8, seq);		\
131f0a5bb98SPhilippe CORNU })
132f0a5bb98SPhilippe CORNU 
otm8009a_init_sequence(struct otm8009a * ctx)133f0a5bb98SPhilippe CORNU static int otm8009a_init_sequence(struct otm8009a *ctx)
134f0a5bb98SPhilippe CORNU {
135f0a5bb98SPhilippe CORNU 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
136f0a5bb98SPhilippe CORNU 	int ret;
137f0a5bb98SPhilippe CORNU 
138f0a5bb98SPhilippe CORNU 	/* Enter CMD2 */
139f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0x80, 0x09, 0x01);
140f0a5bb98SPhilippe CORNU 
141f0a5bb98SPhilippe CORNU 	/* Enter Orise Command2 */
142f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_CMD2_ENA2, 0x80, 0x09);
143f0a5bb98SPhilippe CORNU 
144f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL, 0x30);
145f0a5bb98SPhilippe CORNU 	mdelay(10);
146f0a5bb98SPhilippe CORNU 
147f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_NO_DOC1, 0x40);
148f0a5bb98SPhilippe CORNU 	mdelay(10);
149f0a5bb98SPhilippe CORNU 
150f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL4 + 1, 0xA9);
151f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 1, 0x34);
152f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_P_DRV_M, 0x50);
153f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_VCOMDC, 0x4E);
154f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_OSC_ADJ, 0x66); /* 65Hz */
155f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 2, 0x01);
156f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 5, 0x34);
157f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 4, 0x33);
158f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GVDDSET, 0x79, 0x79);
159f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_SD_CTRL + 1, 0x1B);
160f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 2, 0x83);
161f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL + 1, 0x83);
162f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_RGB_VID_SET, 0x0E);
163f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANSET, 0x00, 0x01);
164f0a5bb98SPhilippe CORNU 
165f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00);
166f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00,
167f0a5bb98SPhilippe CORNU 			 0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00);
168f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00,
169f0a5bb98SPhilippe CORNU 			 0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00);
170f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00,
171f0a5bb98SPhilippe CORNU 			 0x01, 0x02, 0x00, 0x00);
172f0a5bb98SPhilippe CORNU 
173f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_NO_DOC2, 0x00);
174f0a5bb98SPhilippe CORNU 
175f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
176f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
177f0a5bb98SPhilippe CORNU 			 0, 0, 0, 0, 0);
178f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
179f0a5bb98SPhilippe CORNU 			 0, 0, 0, 0, 0);
180f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
181f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0,
182f0a5bb98SPhilippe CORNU 			 0, 0, 0, 0, 0);
183f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
184f0a5bb98SPhilippe CORNU 			 4, 0, 0, 0, 0);
185f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
186f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
187f0a5bb98SPhilippe CORNU 			 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
188f0a5bb98SPhilippe CORNU 
189f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25,
190f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00);
191f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
192f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02);
193f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
194f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
195f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26,
196f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00);
197f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
198f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01);
199f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
200f0a5bb98SPhilippe CORNU 			 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
201f0a5bb98SPhilippe CORNU 
202f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 1, 0x66);
203f0a5bb98SPhilippe CORNU 
204f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_NO_DOC3, 0x06);
205f0a5bb98SPhilippe CORNU 
206f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
207f0a5bb98SPhilippe CORNU 			 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
208f0a5bb98SPhilippe CORNU 			 0x01);
209f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
210f0a5bb98SPhilippe CORNU 			 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
211f0a5bb98SPhilippe CORNU 			 0x01);
212f0a5bb98SPhilippe CORNU 
213f0a5bb98SPhilippe CORNU 	/* Exit CMD2 */
214f0a5bb98SPhilippe CORNU 	dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF);
215f0a5bb98SPhilippe CORNU 
216f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_nop(dsi);
217f0a5bb98SPhilippe CORNU 	if (ret)
218f0a5bb98SPhilippe CORNU 		return ret;
219f0a5bb98SPhilippe CORNU 
220f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
221f0a5bb98SPhilippe CORNU 	if (ret)
222f0a5bb98SPhilippe CORNU 		return ret;
223f0a5bb98SPhilippe CORNU 
224f0a5bb98SPhilippe CORNU 	/* Wait for sleep out exit */
225f0a5bb98SPhilippe CORNU 	mdelay(120);
226f0a5bb98SPhilippe CORNU 
227f0a5bb98SPhilippe CORNU 	/* Default portrait 480x800 rgb24 */
228f0a5bb98SPhilippe CORNU 	dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
229f0a5bb98SPhilippe CORNU 
2305bd785a8SRaphael GALLAIS-POU - foss 	ret = mipi_dsi_dcs_set_column_address(dsi, 0, OTM8009A_HDISPLAY - 1);
231f0a5bb98SPhilippe CORNU 	if (ret)
232f0a5bb98SPhilippe CORNU 		return ret;
233f0a5bb98SPhilippe CORNU 
2345bd785a8SRaphael GALLAIS-POU - foss 	ret = mipi_dsi_dcs_set_page_address(dsi, 0, OTM8009A_VDISPLAY - 1);
235f0a5bb98SPhilippe CORNU 	if (ret)
236f0a5bb98SPhilippe CORNU 		return ret;
237f0a5bb98SPhilippe CORNU 
238f0a5bb98SPhilippe CORNU 	/* See otm8009a driver documentation for pixel format descriptions */
239f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT |
240f0a5bb98SPhilippe CORNU 					    MIPI_DCS_PIXEL_FMT_24BIT << 4);
241f0a5bb98SPhilippe CORNU 	if (ret)
242f0a5bb98SPhilippe CORNU 		return ret;
243f0a5bb98SPhilippe CORNU 
244f0a5bb98SPhilippe CORNU 	/* Disable CABC feature */
245f0a5bb98SPhilippe CORNU 	dcs_write_seq(ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
246f0a5bb98SPhilippe CORNU 
247f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_set_display_on(dsi);
248f0a5bb98SPhilippe CORNU 	if (ret)
249f0a5bb98SPhilippe CORNU 		return ret;
250f0a5bb98SPhilippe CORNU 
251f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_nop(dsi);
252f0a5bb98SPhilippe CORNU 	if (ret)
253f0a5bb98SPhilippe CORNU 		return ret;
254f0a5bb98SPhilippe CORNU 
255f0a5bb98SPhilippe CORNU 	/* Send Command GRAM memory write (no parameters) */
256f0a5bb98SPhilippe CORNU 	dcs_write_seq(ctx, MIPI_DCS_WRITE_MEMORY_START);
257f0a5bb98SPhilippe CORNU 
2580084c3c7SYannick Fertré 	/* Wait a short while to let the panel be ready before the 1st frame */
2590084c3c7SYannick Fertré 	mdelay(10);
2600084c3c7SYannick Fertré 
261f0a5bb98SPhilippe CORNU 	return 0;
262f0a5bb98SPhilippe CORNU }
263f0a5bb98SPhilippe CORNU 
otm8009a_disable(struct drm_panel * panel)264f0a5bb98SPhilippe CORNU static int otm8009a_disable(struct drm_panel *panel)
265f0a5bb98SPhilippe CORNU {
266f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = panel_to_otm8009a(panel);
267f0a5bb98SPhilippe CORNU 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
268f0a5bb98SPhilippe CORNU 	int ret;
269f0a5bb98SPhilippe CORNU 
270f0a5bb98SPhilippe CORNU 	if (!ctx->enabled)
271f0a5bb98SPhilippe CORNU 		return 0; /* This is not an issue so we return 0 here */
272f0a5bb98SPhilippe CORNU 
27312a6cbd4SPhilippe CORNU 	backlight_disable(ctx->bl_dev);
274f0a5bb98SPhilippe CORNU 
275f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_set_display_off(dsi);
276f0a5bb98SPhilippe CORNU 	if (ret)
277f0a5bb98SPhilippe CORNU 		return ret;
278f0a5bb98SPhilippe CORNU 
279f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
280f0a5bb98SPhilippe CORNU 	if (ret)
281f0a5bb98SPhilippe CORNU 		return ret;
282f0a5bb98SPhilippe CORNU 
283f0a5bb98SPhilippe CORNU 	msleep(120);
284f0a5bb98SPhilippe CORNU 
285f0a5bb98SPhilippe CORNU 	ctx->enabled = false;
286f0a5bb98SPhilippe CORNU 
287f0a5bb98SPhilippe CORNU 	return 0;
288f0a5bb98SPhilippe CORNU }
289f0a5bb98SPhilippe CORNU 
otm8009a_unprepare(struct drm_panel * panel)290f0a5bb98SPhilippe CORNU static int otm8009a_unprepare(struct drm_panel *panel)
291f0a5bb98SPhilippe CORNU {
292f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = panel_to_otm8009a(panel);
293f0a5bb98SPhilippe CORNU 
294f0a5bb98SPhilippe CORNU 	if (!ctx->prepared)
295f0a5bb98SPhilippe CORNU 		return 0;
296f0a5bb98SPhilippe CORNU 
297f0a5bb98SPhilippe CORNU 	if (ctx->reset_gpio) {
298f0a5bb98SPhilippe CORNU 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
299f0a5bb98SPhilippe CORNU 		msleep(20);
300f0a5bb98SPhilippe CORNU 	}
301f0a5bb98SPhilippe CORNU 
302ded8d7feSPhilippe CORNU 	regulator_disable(ctx->supply);
303ded8d7feSPhilippe CORNU 
304f0a5bb98SPhilippe CORNU 	ctx->prepared = false;
305f0a5bb98SPhilippe CORNU 
306f0a5bb98SPhilippe CORNU 	return 0;
307f0a5bb98SPhilippe CORNU }
308f0a5bb98SPhilippe CORNU 
otm8009a_prepare(struct drm_panel * panel)309f0a5bb98SPhilippe CORNU static int otm8009a_prepare(struct drm_panel *panel)
310f0a5bb98SPhilippe CORNU {
311f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = panel_to_otm8009a(panel);
312f0a5bb98SPhilippe CORNU 	int ret;
313f0a5bb98SPhilippe CORNU 
314f0a5bb98SPhilippe CORNU 	if (ctx->prepared)
315f0a5bb98SPhilippe CORNU 		return 0;
316f0a5bb98SPhilippe CORNU 
317ded8d7feSPhilippe CORNU 	ret = regulator_enable(ctx->supply);
318ded8d7feSPhilippe CORNU 	if (ret < 0) {
319a25b6b27SSam Ravnborg 		dev_err(panel->dev, "failed to enable supply: %d\n", ret);
320ded8d7feSPhilippe CORNU 		return ret;
321ded8d7feSPhilippe CORNU 	}
322ded8d7feSPhilippe CORNU 
323f0a5bb98SPhilippe CORNU 	if (ctx->reset_gpio) {
324f0a5bb98SPhilippe CORNU 		gpiod_set_value_cansleep(ctx->reset_gpio, 0);
325f0a5bb98SPhilippe CORNU 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
326f0a5bb98SPhilippe CORNU 		msleep(20);
327f0a5bb98SPhilippe CORNU 		gpiod_set_value_cansleep(ctx->reset_gpio, 0);
328f0a5bb98SPhilippe CORNU 		msleep(100);
329f0a5bb98SPhilippe CORNU 	}
330f0a5bb98SPhilippe CORNU 
331f0a5bb98SPhilippe CORNU 	ret = otm8009a_init_sequence(ctx);
332f0a5bb98SPhilippe CORNU 	if (ret)
333f0a5bb98SPhilippe CORNU 		return ret;
334f0a5bb98SPhilippe CORNU 
335f0a5bb98SPhilippe CORNU 	ctx->prepared = true;
336f0a5bb98SPhilippe CORNU 
337f0a5bb98SPhilippe CORNU 	return 0;
338f0a5bb98SPhilippe CORNU }
339f0a5bb98SPhilippe CORNU 
otm8009a_enable(struct drm_panel * panel)340f0a5bb98SPhilippe CORNU static int otm8009a_enable(struct drm_panel *panel)
341f0a5bb98SPhilippe CORNU {
342f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = panel_to_otm8009a(panel);
343f0a5bb98SPhilippe CORNU 
34436830ce4SPhilippe CORNU 	if (ctx->enabled)
34536830ce4SPhilippe CORNU 		return 0;
34636830ce4SPhilippe CORNU 
34712a6cbd4SPhilippe CORNU 	backlight_enable(ctx->bl_dev);
34836830ce4SPhilippe CORNU 
349f0a5bb98SPhilippe CORNU 	ctx->enabled = true;
350f0a5bb98SPhilippe CORNU 
351f0a5bb98SPhilippe CORNU 	return 0;
352f0a5bb98SPhilippe CORNU }
353f0a5bb98SPhilippe CORNU 
otm8009a_get_modes(struct drm_panel * panel,struct drm_connector * connector)3540ce8ddd8SSam Ravnborg static int otm8009a_get_modes(struct drm_panel *panel,
3550ce8ddd8SSam Ravnborg 			      struct drm_connector *connector)
356f0a5bb98SPhilippe CORNU {
357f0a5bb98SPhilippe CORNU 	struct drm_display_mode *mode;
3585bd785a8SRaphael GALLAIS-POU - foss 	unsigned int num_modes = ARRAY_SIZE(modes);
3595bd785a8SRaphael GALLAIS-POU - foss 	unsigned int i;
360f0a5bb98SPhilippe CORNU 
3615bd785a8SRaphael GALLAIS-POU - foss 	for (i = 0; i < num_modes; i++) {
3625bd785a8SRaphael GALLAIS-POU - foss 		mode = drm_mode_duplicate(connector->dev, &modes[i]);
363f0a5bb98SPhilippe CORNU 		if (!mode) {
364a25b6b27SSam Ravnborg 			dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
3655bd785a8SRaphael GALLAIS-POU - foss 				modes[i].hdisplay,
3665bd785a8SRaphael GALLAIS-POU - foss 				modes[i].vdisplay,
3675bd785a8SRaphael GALLAIS-POU - foss 				drm_mode_vrefresh(&modes[i]));
368f0a5bb98SPhilippe CORNU 			return -ENOMEM;
369f0a5bb98SPhilippe CORNU 		}
370f0a5bb98SPhilippe CORNU 
3715bd785a8SRaphael GALLAIS-POU - foss 		mode->type = DRM_MODE_TYPE_DRIVER;
372f0a5bb98SPhilippe CORNU 
3735bd785a8SRaphael GALLAIS-POU - foss 		/* Setting first mode as preferred */
3745bd785a8SRaphael GALLAIS-POU - foss 		if (!i)
3755bd785a8SRaphael GALLAIS-POU - foss 			mode->type |=  DRM_MODE_TYPE_PREFERRED;
3765bd785a8SRaphael GALLAIS-POU - foss 
3775bd785a8SRaphael GALLAIS-POU - foss 		drm_mode_set_name(mode);
3780ce8ddd8SSam Ravnborg 		drm_mode_probed_add(connector, mode);
3795bd785a8SRaphael GALLAIS-POU - foss 	}
380f0a5bb98SPhilippe CORNU 
3810ce8ddd8SSam Ravnborg 	connector->display_info.width_mm = mode->width_mm;
3820ce8ddd8SSam Ravnborg 	connector->display_info.height_mm = mode->height_mm;
383f0a5bb98SPhilippe CORNU 
3845bd785a8SRaphael GALLAIS-POU - foss 	return num_modes;
385f0a5bb98SPhilippe CORNU }
386f0a5bb98SPhilippe CORNU 
387f0a5bb98SPhilippe CORNU static const struct drm_panel_funcs otm8009a_drm_funcs = {
388f0a5bb98SPhilippe CORNU 	.disable   = otm8009a_disable,
389f0a5bb98SPhilippe CORNU 	.unprepare = otm8009a_unprepare,
390f0a5bb98SPhilippe CORNU 	.prepare   = otm8009a_prepare,
391f0a5bb98SPhilippe CORNU 	.enable    = otm8009a_enable,
392f0a5bb98SPhilippe CORNU 	.get_modes = otm8009a_get_modes,
393f0a5bb98SPhilippe CORNU };
394f0a5bb98SPhilippe CORNU 
395f0a5bb98SPhilippe CORNU /*
396f0a5bb98SPhilippe CORNU  * DSI-BASED BACKLIGHT
397f0a5bb98SPhilippe CORNU  */
398f0a5bb98SPhilippe CORNU 
otm8009a_backlight_update_status(struct backlight_device * bd)399f0a5bb98SPhilippe CORNU static int otm8009a_backlight_update_status(struct backlight_device *bd)
400f0a5bb98SPhilippe CORNU {
401f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = bl_get_data(bd);
402f0a5bb98SPhilippe CORNU 	u8 data[2];
403f0a5bb98SPhilippe CORNU 
404f0a5bb98SPhilippe CORNU 	if (!ctx->prepared) {
405a25b6b27SSam Ravnborg 		dev_dbg(&bd->dev, "lcd not ready yet for setting its backlight!\n");
406f0a5bb98SPhilippe CORNU 		return -ENXIO;
407f0a5bb98SPhilippe CORNU 	}
408f0a5bb98SPhilippe CORNU 
409f0a5bb98SPhilippe CORNU 	if (bd->props.power <= FB_BLANK_NORMAL) {
410f0a5bb98SPhilippe CORNU 		/* Power on the backlight with the requested brightness
411f0a5bb98SPhilippe CORNU 		 * Note We can not use mipi_dsi_dcs_set_display_brightness()
412f0a5bb98SPhilippe CORNU 		 * as otm8009a driver support only 8-bit brightness (1 param).
413f0a5bb98SPhilippe CORNU 		 */
414f0a5bb98SPhilippe CORNU 		data[0] = MIPI_DCS_SET_DISPLAY_BRIGHTNESS;
415f0a5bb98SPhilippe CORNU 		data[1] = bd->props.brightness;
416a34ebe7eSAntonio Borneo 		otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data));
417f0a5bb98SPhilippe CORNU 
418f0a5bb98SPhilippe CORNU 		/* set Brightness Control & Backlight on */
419f0a5bb98SPhilippe CORNU 		data[1] = 0x24;
420f0a5bb98SPhilippe CORNU 
421f0a5bb98SPhilippe CORNU 	} else {
422f0a5bb98SPhilippe CORNU 		/* Power off the backlight: set Brightness Control & Bl off */
423f0a5bb98SPhilippe CORNU 		data[1] = 0;
424f0a5bb98SPhilippe CORNU 	}
425f0a5bb98SPhilippe CORNU 
426f0a5bb98SPhilippe CORNU 	/* Update Brightness Control & Backlight */
427f0a5bb98SPhilippe CORNU 	data[0] = MIPI_DCS_WRITE_CONTROL_DISPLAY;
428a34ebe7eSAntonio Borneo 	otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data));
429f0a5bb98SPhilippe CORNU 
430f0a5bb98SPhilippe CORNU 	return 0;
431f0a5bb98SPhilippe CORNU }
432f0a5bb98SPhilippe CORNU 
433f0a5bb98SPhilippe CORNU static const struct backlight_ops otm8009a_backlight_ops = {
434f0a5bb98SPhilippe CORNU 	.update_status = otm8009a_backlight_update_status,
435f0a5bb98SPhilippe CORNU };
436f0a5bb98SPhilippe CORNU 
otm8009a_probe(struct mipi_dsi_device * dsi)437f0a5bb98SPhilippe CORNU static int otm8009a_probe(struct mipi_dsi_device *dsi)
438f0a5bb98SPhilippe CORNU {
439f0a5bb98SPhilippe CORNU 	struct device *dev = &dsi->dev;
440f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx;
441f0a5bb98SPhilippe CORNU 	int ret;
442f0a5bb98SPhilippe CORNU 
443f0a5bb98SPhilippe CORNU 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
444f0a5bb98SPhilippe CORNU 	if (!ctx)
445f0a5bb98SPhilippe CORNU 		return -ENOMEM;
446f0a5bb98SPhilippe CORNU 
447f0a5bb98SPhilippe CORNU 	ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
448f0a5bb98SPhilippe CORNU 	if (IS_ERR(ctx->reset_gpio)) {
449f0a5bb98SPhilippe CORNU 		dev_err(dev, "cannot get reset-gpio\n");
450f0a5bb98SPhilippe CORNU 		return PTR_ERR(ctx->reset_gpio);
451f0a5bb98SPhilippe CORNU 	}
452f0a5bb98SPhilippe CORNU 
453ded8d7feSPhilippe CORNU 	ctx->supply = devm_regulator_get(dev, "power");
454ded8d7feSPhilippe CORNU 	if (IS_ERR(ctx->supply)) {
455ded8d7feSPhilippe CORNU 		ret = PTR_ERR(ctx->supply);
456f390d43eSYannick Fertré 		if (ret != -EPROBE_DEFER)
457ded8d7feSPhilippe CORNU 			dev_err(dev, "failed to request regulator: %d\n", ret);
458ded8d7feSPhilippe CORNU 		return ret;
459ded8d7feSPhilippe CORNU 	}
460ded8d7feSPhilippe CORNU 
461f0a5bb98SPhilippe CORNU 	mipi_dsi_set_drvdata(dsi, ctx);
462f0a5bb98SPhilippe CORNU 
463f0a5bb98SPhilippe CORNU 	ctx->dev = dev;
464f0a5bb98SPhilippe CORNU 
465f0a5bb98SPhilippe CORNU 	dsi->lanes = 2;
466f0a5bb98SPhilippe CORNU 	dsi->format = MIPI_DSI_FMT_RGB888;
467f0a5bb98SPhilippe CORNU 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
468880ee3b7SAntonio Borneo 			  MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
469f0a5bb98SPhilippe CORNU 
4709a2654c0SLaurent Pinchart 	drm_panel_init(&ctx->panel, dev, &otm8009a_drm_funcs,
4719a2654c0SLaurent Pinchart 		       DRM_MODE_CONNECTOR_DSI);
472f0a5bb98SPhilippe CORNU 
47312a6cbd4SPhilippe CORNU 	ctx->bl_dev = devm_backlight_device_register(dev, dev_name(dev),
474*ab4f869fSJames Cowgill 						     dev, ctx,
47512a6cbd4SPhilippe CORNU 						     &otm8009a_backlight_ops,
47612a6cbd4SPhilippe CORNU 						     NULL);
477f0a5bb98SPhilippe CORNU 	if (IS_ERR(ctx->bl_dev)) {
47812a6cbd4SPhilippe CORNU 		ret = PTR_ERR(ctx->bl_dev);
47912a6cbd4SPhilippe CORNU 		dev_err(dev, "failed to register backlight: %d\n", ret);
48012a6cbd4SPhilippe CORNU 		return ret;
481f0a5bb98SPhilippe CORNU 	}
482f0a5bb98SPhilippe CORNU 
483f0a5bb98SPhilippe CORNU 	ctx->bl_dev->props.max_brightness = OTM8009A_BACKLIGHT_MAX;
484f0a5bb98SPhilippe CORNU 	ctx->bl_dev->props.brightness = OTM8009A_BACKLIGHT_DEFAULT;
485f0a5bb98SPhilippe CORNU 	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
486f0a5bb98SPhilippe CORNU 	ctx->bl_dev->props.type = BACKLIGHT_RAW;
487f0a5bb98SPhilippe CORNU 
488f0a5bb98SPhilippe CORNU 	drm_panel_add(&ctx->panel);
489f0a5bb98SPhilippe CORNU 
490f0a5bb98SPhilippe CORNU 	ret = mipi_dsi_attach(dsi);
491f0a5bb98SPhilippe CORNU 	if (ret < 0) {
492f0a5bb98SPhilippe CORNU 		dev_err(dev, "mipi_dsi_attach failed. Is host ready?\n");
493f0a5bb98SPhilippe CORNU 		drm_panel_remove(&ctx->panel);
494f0a5bb98SPhilippe CORNU 		return ret;
495f0a5bb98SPhilippe CORNU 	}
496f0a5bb98SPhilippe CORNU 
497f0a5bb98SPhilippe CORNU 	return 0;
498f0a5bb98SPhilippe CORNU }
499f0a5bb98SPhilippe CORNU 
otm8009a_remove(struct mipi_dsi_device * dsi)50079abca2bSUwe Kleine-König static void otm8009a_remove(struct mipi_dsi_device *dsi)
501f0a5bb98SPhilippe CORNU {
502f0a5bb98SPhilippe CORNU 	struct otm8009a *ctx = mipi_dsi_get_drvdata(dsi);
503f0a5bb98SPhilippe CORNU 
504f0a5bb98SPhilippe CORNU 	mipi_dsi_detach(dsi);
505f0a5bb98SPhilippe CORNU 	drm_panel_remove(&ctx->panel);
506f0a5bb98SPhilippe CORNU }
507f0a5bb98SPhilippe CORNU 
508f0a5bb98SPhilippe CORNU static const struct of_device_id orisetech_otm8009a_of_match[] = {
509f0a5bb98SPhilippe CORNU 	{ .compatible = "orisetech,otm8009a" },
510f0a5bb98SPhilippe CORNU 	{ }
511f0a5bb98SPhilippe CORNU };
512f0a5bb98SPhilippe CORNU MODULE_DEVICE_TABLE(of, orisetech_otm8009a_of_match);
513f0a5bb98SPhilippe CORNU 
514f0a5bb98SPhilippe CORNU static struct mipi_dsi_driver orisetech_otm8009a_driver = {
515f0a5bb98SPhilippe CORNU 	.probe  = otm8009a_probe,
516f0a5bb98SPhilippe CORNU 	.remove = otm8009a_remove,
517f0a5bb98SPhilippe CORNU 	.driver = {
5186982b943SPhilippe CORNU 		.name = "panel-orisetech-otm8009a",
519f0a5bb98SPhilippe CORNU 		.of_match_table = orisetech_otm8009a_of_match,
520f0a5bb98SPhilippe CORNU 	},
521f0a5bb98SPhilippe CORNU };
522f0a5bb98SPhilippe CORNU module_mipi_dsi_driver(orisetech_otm8009a_driver);
523f0a5bb98SPhilippe CORNU 
524f0a5bb98SPhilippe CORNU MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
525f0a5bb98SPhilippe CORNU MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
526f0a5bb98SPhilippe CORNU MODULE_DESCRIPTION("DRM driver for Orise Tech OTM8009A MIPI DSI panel");
527f0a5bb98SPhilippe CORNU MODULE_LICENSE("GPL v2");
528