1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * S6E63M0 AMOLED LCD drm_panel driver.
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (C) 2019 Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
6*4882a593Smuzhiyun * Derived from drivers/gpu/drm/panel-samsung-ld9040.c
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun * Andrzej Hajda <a.hajda@samsung.com>
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <drm/drm_modes.h>
12*4882a593Smuzhiyun #include <drm/drm_panel.h>
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun #include <linux/backlight.h>
15*4882a593Smuzhiyun #include <linux/delay.h>
16*4882a593Smuzhiyun #include <linux/gpio/consumer.h>
17*4882a593Smuzhiyun #include <linux/module.h>
18*4882a593Smuzhiyun #include <linux/regulator/consumer.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #include <video/mipi_display.h>
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun #include "panel-samsung-s6e63m0.h"
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun /* Manufacturer Command Set */
25*4882a593Smuzhiyun #define MCS_ELVSS_ON 0xb1
26*4882a593Smuzhiyun #define MCS_MIECTL1 0xc0
27*4882a593Smuzhiyun #define MCS_BCMODE 0xc1
28*4882a593Smuzhiyun #define MCS_ERROR_CHECK 0xd5
29*4882a593Smuzhiyun #define MCS_READ_ID1 0xda
30*4882a593Smuzhiyun #define MCS_READ_ID2 0xdb
31*4882a593Smuzhiyun #define MCS_READ_ID3 0xdc
32*4882a593Smuzhiyun #define MCS_LEVEL_2_KEY 0xf0
33*4882a593Smuzhiyun #define MCS_MTP_KEY 0xf1
34*4882a593Smuzhiyun #define MCS_DISCTL 0xf2
35*4882a593Smuzhiyun #define MCS_SRCCTL 0xf6
36*4882a593Smuzhiyun #define MCS_IFCTL 0xf7
37*4882a593Smuzhiyun #define MCS_PANELCTL 0xF8
38*4882a593Smuzhiyun #define MCS_PGAMMACTL 0xfa
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun #define S6E63M0_LCD_ID_VALUE_M2 0xA4
41*4882a593Smuzhiyun #define S6E63M0_LCD_ID_VALUE_SM2 0xB4
42*4882a593Smuzhiyun #define S6E63M0_LCD_ID_VALUE_SM2_1 0xB6
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun #define NUM_GAMMA_LEVELS 11
45*4882a593Smuzhiyun #define GAMMA_TABLE_COUNT 23
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun #define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun /* array of gamma tables for gamma value 2.2 */
50*4882a593Smuzhiyun static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
51*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
52*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
53*4882a593Smuzhiyun 0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
54*4882a593Smuzhiyun 0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
55*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
56*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
57*4882a593Smuzhiyun 0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
58*4882a593Smuzhiyun 0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
59*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
60*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
61*4882a593Smuzhiyun 0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
62*4882a593Smuzhiyun 0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
63*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
64*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
65*4882a593Smuzhiyun 0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
66*4882a593Smuzhiyun 0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
67*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
68*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
69*4882a593Smuzhiyun 0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
70*4882a593Smuzhiyun 0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
71*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
72*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
73*4882a593Smuzhiyun 0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
74*4882a593Smuzhiyun 0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
75*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
76*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
77*4882a593Smuzhiyun 0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
78*4882a593Smuzhiyun 0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
79*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
80*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
81*4882a593Smuzhiyun 0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
82*4882a593Smuzhiyun 0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
83*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
84*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
85*4882a593Smuzhiyun 0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
86*4882a593Smuzhiyun 0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
87*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
88*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
89*4882a593Smuzhiyun 0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
90*4882a593Smuzhiyun 0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
91*4882a593Smuzhiyun { MCS_PGAMMACTL, 0x00,
92*4882a593Smuzhiyun 0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
93*4882a593Smuzhiyun 0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
94*4882a593Smuzhiyun 0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
95*4882a593Smuzhiyun };
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun struct s6e63m0 {
98*4882a593Smuzhiyun struct device *dev;
99*4882a593Smuzhiyun int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val);
100*4882a593Smuzhiyun int (*dcs_write)(struct device *dev, const u8 *data, size_t len);
101*4882a593Smuzhiyun struct drm_panel panel;
102*4882a593Smuzhiyun struct backlight_device *bl_dev;
103*4882a593Smuzhiyun u8 lcd_type;
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun struct regulator_bulk_data supplies[2];
106*4882a593Smuzhiyun struct gpio_desc *reset_gpio;
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun bool prepared;
109*4882a593Smuzhiyun bool enabled;
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun /*
112*4882a593Smuzhiyun * This field is tested by functions directly accessing bus before
113*4882a593Smuzhiyun * transfer, transfer is skipped if it is set. In case of transfer
114*4882a593Smuzhiyun * failure or unexpected response the field is set to error value.
115*4882a593Smuzhiyun * Such construct allows to eliminate many checks in higher level
116*4882a593Smuzhiyun * functions.
117*4882a593Smuzhiyun */
118*4882a593Smuzhiyun int error;
119*4882a593Smuzhiyun };
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun static const struct drm_display_mode default_mode = {
122*4882a593Smuzhiyun .clock = 25628,
123*4882a593Smuzhiyun .hdisplay = 480,
124*4882a593Smuzhiyun .hsync_start = 480 + 16,
125*4882a593Smuzhiyun .hsync_end = 480 + 16 + 2,
126*4882a593Smuzhiyun .htotal = 480 + 16 + 2 + 16,
127*4882a593Smuzhiyun .vdisplay = 800,
128*4882a593Smuzhiyun .vsync_start = 800 + 28,
129*4882a593Smuzhiyun .vsync_end = 800 + 28 + 2,
130*4882a593Smuzhiyun .vtotal = 800 + 28 + 2 + 1,
131*4882a593Smuzhiyun .width_mm = 53,
132*4882a593Smuzhiyun .height_mm = 89,
133*4882a593Smuzhiyun .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
134*4882a593Smuzhiyun };
135*4882a593Smuzhiyun
panel_to_s6e63m0(struct drm_panel * panel)136*4882a593Smuzhiyun static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun return container_of(panel, struct s6e63m0, panel);
139*4882a593Smuzhiyun }
140*4882a593Smuzhiyun
s6e63m0_clear_error(struct s6e63m0 * ctx)141*4882a593Smuzhiyun static int s6e63m0_clear_error(struct s6e63m0 *ctx)
142*4882a593Smuzhiyun {
143*4882a593Smuzhiyun int ret = ctx->error;
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun ctx->error = 0;
146*4882a593Smuzhiyun return ret;
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun
s6e63m0_dcs_read(struct s6e63m0 * ctx,const u8 cmd,u8 * data)149*4882a593Smuzhiyun static void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data)
150*4882a593Smuzhiyun {
151*4882a593Smuzhiyun if (ctx->error < 0)
152*4882a593Smuzhiyun return;
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun ctx->error = ctx->dcs_read(ctx->dev, cmd, data);
155*4882a593Smuzhiyun }
156*4882a593Smuzhiyun
s6e63m0_dcs_write(struct s6e63m0 * ctx,const u8 * data,size_t len)157*4882a593Smuzhiyun static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
158*4882a593Smuzhiyun {
159*4882a593Smuzhiyun if (ctx->error < 0 || len == 0)
160*4882a593Smuzhiyun return;
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun ctx->error = ctx->dcs_write(ctx->dev, data, len);
163*4882a593Smuzhiyun }
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun #define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
166*4882a593Smuzhiyun ({ \
167*4882a593Smuzhiyun static const u8 d[] = { seq }; \
168*4882a593Smuzhiyun s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
169*4882a593Smuzhiyun })
170*4882a593Smuzhiyun
s6e63m0_check_lcd_type(struct s6e63m0 * ctx)171*4882a593Smuzhiyun static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx)
172*4882a593Smuzhiyun {
173*4882a593Smuzhiyun u8 id1, id2, id3;
174*4882a593Smuzhiyun int ret;
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun s6e63m0_dcs_read(ctx, MCS_READ_ID1, &id1);
177*4882a593Smuzhiyun s6e63m0_dcs_read(ctx, MCS_READ_ID2, &id2);
178*4882a593Smuzhiyun s6e63m0_dcs_read(ctx, MCS_READ_ID3, &id3);
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun ret = s6e63m0_clear_error(ctx);
181*4882a593Smuzhiyun if (ret) {
182*4882a593Smuzhiyun dev_err(ctx->dev, "error checking LCD type (%d)\n", ret);
183*4882a593Smuzhiyun ctx->lcd_type = 0x00;
184*4882a593Smuzhiyun return ret;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun /* We attempt to detect what panel is mounted on the controller */
190*4882a593Smuzhiyun switch (id2) {
191*4882a593Smuzhiyun case S6E63M0_LCD_ID_VALUE_M2:
192*4882a593Smuzhiyun dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n");
193*4882a593Smuzhiyun break;
194*4882a593Smuzhiyun case S6E63M0_LCD_ID_VALUE_SM2:
195*4882a593Smuzhiyun case S6E63M0_LCD_ID_VALUE_SM2_1:
196*4882a593Smuzhiyun dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n");
197*4882a593Smuzhiyun break;
198*4882a593Smuzhiyun default:
199*4882a593Smuzhiyun dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2);
200*4882a593Smuzhiyun break;
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun ctx->lcd_type = id2;
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun return 0;
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun
s6e63m0_init(struct s6e63m0 * ctx)208*4882a593Smuzhiyun static void s6e63m0_init(struct s6e63m0 *ctx)
209*4882a593Smuzhiyun {
210*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
211*4882a593Smuzhiyun 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
212*4882a593Smuzhiyun 0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
215*4882a593Smuzhiyun 0x02, 0x03, 0x1c, 0x10, 0x10);
216*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
217*4882a593Smuzhiyun 0x03, 0x00, 0x00);
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
220*4882a593Smuzhiyun 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
221*4882a593Smuzhiyun 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
222*4882a593Smuzhiyun 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
223*4882a593Smuzhiyun 0xd6);
224*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
225*4882a593Smuzhiyun 0x01);
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
228*4882a593Smuzhiyun 0x00, 0x8c, 0x07);
229*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb3,
230*4882a593Smuzhiyun 0xc);
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb5,
233*4882a593Smuzhiyun 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
234*4882a593Smuzhiyun 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
235*4882a593Smuzhiyun 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
236*4882a593Smuzhiyun 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
237*4882a593Smuzhiyun 0x21, 0x20, 0x1e, 0x1e);
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb6,
240*4882a593Smuzhiyun 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
241*4882a593Smuzhiyun 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
242*4882a593Smuzhiyun 0x66, 0x66);
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb7,
245*4882a593Smuzhiyun 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
246*4882a593Smuzhiyun 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
247*4882a593Smuzhiyun 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
248*4882a593Smuzhiyun 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
249*4882a593Smuzhiyun 0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
250*4882a593Smuzhiyun 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
251*4882a593Smuzhiyun 0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb9,
254*4882a593Smuzhiyun 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
255*4882a593Smuzhiyun 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
256*4882a593Smuzhiyun 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
257*4882a593Smuzhiyun 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
258*4882a593Smuzhiyun 0x21, 0x20, 0x1e, 0x1e);
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xba,
261*4882a593Smuzhiyun 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
262*4882a593Smuzhiyun 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
263*4882a593Smuzhiyun 0x66, 0x66);
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
266*4882a593Smuzhiyun 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
267*4882a593Smuzhiyun 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
268*4882a593Smuzhiyun 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
269*4882a593Smuzhiyun 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, 0xb2,
272*4882a593Smuzhiyun 0x10, 0x10, 0x0b, 0x05);
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
275*4882a593Smuzhiyun 0x01);
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
278*4882a593Smuzhiyun 0x0b);
279*4882a593Smuzhiyun }
280*4882a593Smuzhiyun
s6e63m0_power_on(struct s6e63m0 * ctx)281*4882a593Smuzhiyun static int s6e63m0_power_on(struct s6e63m0 *ctx)
282*4882a593Smuzhiyun {
283*4882a593Smuzhiyun int ret;
284*4882a593Smuzhiyun
285*4882a593Smuzhiyun ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
286*4882a593Smuzhiyun if (ret < 0)
287*4882a593Smuzhiyun return ret;
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun msleep(25);
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun /* Be sure to send a reset pulse */
292*4882a593Smuzhiyun gpiod_set_value(ctx->reset_gpio, 1);
293*4882a593Smuzhiyun msleep(5);
294*4882a593Smuzhiyun gpiod_set_value(ctx->reset_gpio, 0);
295*4882a593Smuzhiyun msleep(120);
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun return 0;
298*4882a593Smuzhiyun }
299*4882a593Smuzhiyun
s6e63m0_power_off(struct s6e63m0 * ctx)300*4882a593Smuzhiyun static int s6e63m0_power_off(struct s6e63m0 *ctx)
301*4882a593Smuzhiyun {
302*4882a593Smuzhiyun int ret;
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun gpiod_set_value(ctx->reset_gpio, 1);
305*4882a593Smuzhiyun msleep(120);
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
308*4882a593Smuzhiyun if (ret < 0)
309*4882a593Smuzhiyun return ret;
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun return 0;
312*4882a593Smuzhiyun }
313*4882a593Smuzhiyun
s6e63m0_disable(struct drm_panel * panel)314*4882a593Smuzhiyun static int s6e63m0_disable(struct drm_panel *panel)
315*4882a593Smuzhiyun {
316*4882a593Smuzhiyun struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun if (!ctx->enabled)
319*4882a593Smuzhiyun return 0;
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun backlight_disable(ctx->bl_dev);
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
324*4882a593Smuzhiyun msleep(10);
325*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
326*4882a593Smuzhiyun msleep(120);
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun ctx->enabled = false;
329*4882a593Smuzhiyun
330*4882a593Smuzhiyun return 0;
331*4882a593Smuzhiyun }
332*4882a593Smuzhiyun
s6e63m0_unprepare(struct drm_panel * panel)333*4882a593Smuzhiyun static int s6e63m0_unprepare(struct drm_panel *panel)
334*4882a593Smuzhiyun {
335*4882a593Smuzhiyun struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
336*4882a593Smuzhiyun int ret;
337*4882a593Smuzhiyun
338*4882a593Smuzhiyun if (!ctx->prepared)
339*4882a593Smuzhiyun return 0;
340*4882a593Smuzhiyun
341*4882a593Smuzhiyun s6e63m0_clear_error(ctx);
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun ret = s6e63m0_power_off(ctx);
344*4882a593Smuzhiyun if (ret < 0)
345*4882a593Smuzhiyun return ret;
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun ctx->prepared = false;
348*4882a593Smuzhiyun
349*4882a593Smuzhiyun return 0;
350*4882a593Smuzhiyun }
351*4882a593Smuzhiyun
s6e63m0_prepare(struct drm_panel * panel)352*4882a593Smuzhiyun static int s6e63m0_prepare(struct drm_panel *panel)
353*4882a593Smuzhiyun {
354*4882a593Smuzhiyun struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
355*4882a593Smuzhiyun int ret;
356*4882a593Smuzhiyun
357*4882a593Smuzhiyun if (ctx->prepared)
358*4882a593Smuzhiyun return 0;
359*4882a593Smuzhiyun
360*4882a593Smuzhiyun ret = s6e63m0_power_on(ctx);
361*4882a593Smuzhiyun if (ret < 0)
362*4882a593Smuzhiyun return ret;
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun /* Magic to unlock level 2 control of the display */
365*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a);
366*4882a593Smuzhiyun /* Magic to unlock MTP reading */
367*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
368*4882a593Smuzhiyun
369*4882a593Smuzhiyun ret = s6e63m0_check_lcd_type(ctx);
370*4882a593Smuzhiyun if (ret < 0)
371*4882a593Smuzhiyun return ret;
372*4882a593Smuzhiyun
373*4882a593Smuzhiyun s6e63m0_init(ctx);
374*4882a593Smuzhiyun
375*4882a593Smuzhiyun ret = s6e63m0_clear_error(ctx);
376*4882a593Smuzhiyun
377*4882a593Smuzhiyun if (ret < 0)
378*4882a593Smuzhiyun s6e63m0_unprepare(panel);
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun ctx->prepared = true;
381*4882a593Smuzhiyun
382*4882a593Smuzhiyun return ret;
383*4882a593Smuzhiyun }
384*4882a593Smuzhiyun
s6e63m0_enable(struct drm_panel * panel)385*4882a593Smuzhiyun static int s6e63m0_enable(struct drm_panel *panel)
386*4882a593Smuzhiyun {
387*4882a593Smuzhiyun struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun if (ctx->enabled)
390*4882a593Smuzhiyun return 0;
391*4882a593Smuzhiyun
392*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
393*4882a593Smuzhiyun msleep(120);
394*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
395*4882a593Smuzhiyun msleep(10);
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK,
398*4882a593Smuzhiyun 0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3,
399*4882a593Smuzhiyun 0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20,
400*4882a593Smuzhiyun 0x0F, 0x00);
401*4882a593Smuzhiyun
402*4882a593Smuzhiyun backlight_enable(ctx->bl_dev);
403*4882a593Smuzhiyun
404*4882a593Smuzhiyun ctx->enabled = true;
405*4882a593Smuzhiyun
406*4882a593Smuzhiyun return 0;
407*4882a593Smuzhiyun }
408*4882a593Smuzhiyun
s6e63m0_get_modes(struct drm_panel * panel,struct drm_connector * connector)409*4882a593Smuzhiyun static int s6e63m0_get_modes(struct drm_panel *panel,
410*4882a593Smuzhiyun struct drm_connector *connector)
411*4882a593Smuzhiyun {
412*4882a593Smuzhiyun struct drm_display_mode *mode;
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun mode = drm_mode_duplicate(connector->dev, &default_mode);
415*4882a593Smuzhiyun if (!mode) {
416*4882a593Smuzhiyun dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
417*4882a593Smuzhiyun default_mode.hdisplay, default_mode.vdisplay,
418*4882a593Smuzhiyun drm_mode_vrefresh(&default_mode));
419*4882a593Smuzhiyun return -ENOMEM;
420*4882a593Smuzhiyun }
421*4882a593Smuzhiyun
422*4882a593Smuzhiyun drm_mode_set_name(mode);
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
425*4882a593Smuzhiyun drm_mode_probed_add(connector, mode);
426*4882a593Smuzhiyun
427*4882a593Smuzhiyun return 1;
428*4882a593Smuzhiyun }
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun static const struct drm_panel_funcs s6e63m0_drm_funcs = {
431*4882a593Smuzhiyun .disable = s6e63m0_disable,
432*4882a593Smuzhiyun .unprepare = s6e63m0_unprepare,
433*4882a593Smuzhiyun .prepare = s6e63m0_prepare,
434*4882a593Smuzhiyun .enable = s6e63m0_enable,
435*4882a593Smuzhiyun .get_modes = s6e63m0_get_modes,
436*4882a593Smuzhiyun };
437*4882a593Smuzhiyun
s6e63m0_set_brightness(struct backlight_device * bd)438*4882a593Smuzhiyun static int s6e63m0_set_brightness(struct backlight_device *bd)
439*4882a593Smuzhiyun {
440*4882a593Smuzhiyun struct s6e63m0 *ctx = bl_get_data(bd);
441*4882a593Smuzhiyun
442*4882a593Smuzhiyun int brightness = bd->props.brightness;
443*4882a593Smuzhiyun
444*4882a593Smuzhiyun /* disable and set new gamma */
445*4882a593Smuzhiyun s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
446*4882a593Smuzhiyun ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun /* update gamma table. */
449*4882a593Smuzhiyun s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun return s6e63m0_clear_error(ctx);
452*4882a593Smuzhiyun }
453*4882a593Smuzhiyun
454*4882a593Smuzhiyun static const struct backlight_ops s6e63m0_backlight_ops = {
455*4882a593Smuzhiyun .update_status = s6e63m0_set_brightness,
456*4882a593Smuzhiyun };
457*4882a593Smuzhiyun
s6e63m0_backlight_register(struct s6e63m0 * ctx)458*4882a593Smuzhiyun static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
459*4882a593Smuzhiyun {
460*4882a593Smuzhiyun struct backlight_properties props = {
461*4882a593Smuzhiyun .type = BACKLIGHT_RAW,
462*4882a593Smuzhiyun .brightness = MAX_BRIGHTNESS,
463*4882a593Smuzhiyun .max_brightness = MAX_BRIGHTNESS
464*4882a593Smuzhiyun };
465*4882a593Smuzhiyun struct device *dev = ctx->dev;
466*4882a593Smuzhiyun int ret = 0;
467*4882a593Smuzhiyun
468*4882a593Smuzhiyun ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
469*4882a593Smuzhiyun &s6e63m0_backlight_ops,
470*4882a593Smuzhiyun &props);
471*4882a593Smuzhiyun if (IS_ERR(ctx->bl_dev)) {
472*4882a593Smuzhiyun ret = PTR_ERR(ctx->bl_dev);
473*4882a593Smuzhiyun dev_err(dev, "error registering backlight device (%d)\n", ret);
474*4882a593Smuzhiyun }
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun return ret;
477*4882a593Smuzhiyun }
478*4882a593Smuzhiyun
s6e63m0_probe(struct device * dev,int (* dcs_read)(struct device * dev,const u8 cmd,u8 * val),int (* dcs_write)(struct device * dev,const u8 * data,size_t len),bool dsi_mode)479*4882a593Smuzhiyun int s6e63m0_probe(struct device *dev,
480*4882a593Smuzhiyun int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val),
481*4882a593Smuzhiyun int (*dcs_write)(struct device *dev, const u8 *data, size_t len),
482*4882a593Smuzhiyun bool dsi_mode)
483*4882a593Smuzhiyun {
484*4882a593Smuzhiyun struct s6e63m0 *ctx;
485*4882a593Smuzhiyun int ret;
486*4882a593Smuzhiyun
487*4882a593Smuzhiyun ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
488*4882a593Smuzhiyun if (!ctx)
489*4882a593Smuzhiyun return -ENOMEM;
490*4882a593Smuzhiyun
491*4882a593Smuzhiyun ctx->dcs_read = dcs_read;
492*4882a593Smuzhiyun ctx->dcs_write = dcs_write;
493*4882a593Smuzhiyun dev_set_drvdata(dev, ctx);
494*4882a593Smuzhiyun
495*4882a593Smuzhiyun ctx->dev = dev;
496*4882a593Smuzhiyun ctx->enabled = false;
497*4882a593Smuzhiyun ctx->prepared = false;
498*4882a593Smuzhiyun
499*4882a593Smuzhiyun ctx->supplies[0].supply = "vdd3";
500*4882a593Smuzhiyun ctx->supplies[1].supply = "vci";
501*4882a593Smuzhiyun ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
502*4882a593Smuzhiyun ctx->supplies);
503*4882a593Smuzhiyun if (ret < 0) {
504*4882a593Smuzhiyun dev_err(dev, "failed to get regulators: %d\n", ret);
505*4882a593Smuzhiyun return ret;
506*4882a593Smuzhiyun }
507*4882a593Smuzhiyun
508*4882a593Smuzhiyun ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
509*4882a593Smuzhiyun if (IS_ERR(ctx->reset_gpio)) {
510*4882a593Smuzhiyun dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
511*4882a593Smuzhiyun return PTR_ERR(ctx->reset_gpio);
512*4882a593Smuzhiyun }
513*4882a593Smuzhiyun
514*4882a593Smuzhiyun drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
515*4882a593Smuzhiyun dsi_mode ? DRM_MODE_CONNECTOR_DSI :
516*4882a593Smuzhiyun DRM_MODE_CONNECTOR_DPI);
517*4882a593Smuzhiyun
518*4882a593Smuzhiyun ret = s6e63m0_backlight_register(ctx);
519*4882a593Smuzhiyun if (ret < 0)
520*4882a593Smuzhiyun return ret;
521*4882a593Smuzhiyun
522*4882a593Smuzhiyun drm_panel_add(&ctx->panel);
523*4882a593Smuzhiyun
524*4882a593Smuzhiyun return 0;
525*4882a593Smuzhiyun }
526*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(s6e63m0_probe);
527*4882a593Smuzhiyun
s6e63m0_remove(struct device * dev)528*4882a593Smuzhiyun int s6e63m0_remove(struct device *dev)
529*4882a593Smuzhiyun {
530*4882a593Smuzhiyun struct s6e63m0 *ctx = dev_get_drvdata(dev);
531*4882a593Smuzhiyun
532*4882a593Smuzhiyun drm_panel_remove(&ctx->panel);
533*4882a593Smuzhiyun
534*4882a593Smuzhiyun return 0;
535*4882a593Smuzhiyun }
536*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(s6e63m0_remove);
537*4882a593Smuzhiyun
538*4882a593Smuzhiyun MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
539*4882a593Smuzhiyun MODULE_DESCRIPTION("s6e63m0 LCD Driver");
540*4882a593Smuzhiyun MODULE_LICENSE("GPL v2");
541