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