18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * S6E63M0 AMOLED LCD drm_panel driver.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
68c2ecf20Sopenharmony_ci * Derived from drivers/gpu/drm/panel-samsung-ld9040.c
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Andrzej Hajda <a.hajda@samsung.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <drm/drm_modes.h>
128c2ecf20Sopenharmony_ci#include <drm/drm_panel.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/backlight.h>
158c2ecf20Sopenharmony_ci#include <linux/delay.h>
168c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <video/mipi_display.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include "panel-samsung-s6e63m0.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* Manufacturer Command Set */
258c2ecf20Sopenharmony_ci#define MCS_ELVSS_ON                0xb1
268c2ecf20Sopenharmony_ci#define MCS_MIECTL1                0xc0
278c2ecf20Sopenharmony_ci#define MCS_BCMODE                              0xc1
288c2ecf20Sopenharmony_ci#define MCS_ERROR_CHECK		0xd5
298c2ecf20Sopenharmony_ci#define MCS_READ_ID1		0xda
308c2ecf20Sopenharmony_ci#define MCS_READ_ID2		0xdb
318c2ecf20Sopenharmony_ci#define MCS_READ_ID3		0xdc
328c2ecf20Sopenharmony_ci#define MCS_LEVEL_2_KEY		0xf0
338c2ecf20Sopenharmony_ci#define MCS_MTP_KEY		0xf1
348c2ecf20Sopenharmony_ci#define MCS_DISCTL   0xf2
358c2ecf20Sopenharmony_ci#define MCS_SRCCTL           0xf6
368c2ecf20Sopenharmony_ci#define MCS_IFCTL                       0xf7
378c2ecf20Sopenharmony_ci#define MCS_PANELCTL         0xF8
388c2ecf20Sopenharmony_ci#define MCS_PGAMMACTL                   0xfa
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define S6E63M0_LCD_ID_VALUE_M2		0xA4
418c2ecf20Sopenharmony_ci#define S6E63M0_LCD_ID_VALUE_SM2	0xB4
428c2ecf20Sopenharmony_ci#define S6E63M0_LCD_ID_VALUE_SM2_1	0xB6
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define NUM_GAMMA_LEVELS             11
458c2ecf20Sopenharmony_ci#define GAMMA_TABLE_COUNT           23
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define MAX_BRIGHTNESS              (NUM_GAMMA_LEVELS - 1)
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* array of gamma tables for gamma value 2.2 */
508c2ecf20Sopenharmony_cistatic u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
518c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
528c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
538c2ecf20Sopenharmony_ci	  0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
548c2ecf20Sopenharmony_ci	  0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
558c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
568c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
578c2ecf20Sopenharmony_ci	  0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
588c2ecf20Sopenharmony_ci	  0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
598c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
608c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
618c2ecf20Sopenharmony_ci	  0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
628c2ecf20Sopenharmony_ci	  0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
638c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
648c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
658c2ecf20Sopenharmony_ci	  0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
668c2ecf20Sopenharmony_ci	  0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
678c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
688c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
698c2ecf20Sopenharmony_ci	  0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
708c2ecf20Sopenharmony_ci	  0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
718c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
728c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
738c2ecf20Sopenharmony_ci	  0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
748c2ecf20Sopenharmony_ci	  0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
758c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
768c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
778c2ecf20Sopenharmony_ci	  0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
788c2ecf20Sopenharmony_ci	  0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
798c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
808c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
818c2ecf20Sopenharmony_ci	  0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
828c2ecf20Sopenharmony_ci	  0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
838c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
848c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
858c2ecf20Sopenharmony_ci	  0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
868c2ecf20Sopenharmony_ci	  0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
878c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
888c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
898c2ecf20Sopenharmony_ci	  0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
908c2ecf20Sopenharmony_ci	  0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
918c2ecf20Sopenharmony_ci	{ MCS_PGAMMACTL, 0x00,
928c2ecf20Sopenharmony_ci	  0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
938c2ecf20Sopenharmony_ci	  0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
948c2ecf20Sopenharmony_ci	  0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
958c2ecf20Sopenharmony_ci};
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistruct s6e63m0 {
988c2ecf20Sopenharmony_ci	struct device *dev;
998c2ecf20Sopenharmony_ci	int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val);
1008c2ecf20Sopenharmony_ci	int (*dcs_write)(struct device *dev, const u8 *data, size_t len);
1018c2ecf20Sopenharmony_ci	struct drm_panel panel;
1028c2ecf20Sopenharmony_ci	struct backlight_device *bl_dev;
1038c2ecf20Sopenharmony_ci	u8 lcd_type;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	struct regulator_bulk_data supplies[2];
1068c2ecf20Sopenharmony_ci	struct gpio_desc *reset_gpio;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	bool prepared;
1098c2ecf20Sopenharmony_ci	bool enabled;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	/*
1128c2ecf20Sopenharmony_ci	 * This field is tested by functions directly accessing bus before
1138c2ecf20Sopenharmony_ci	 * transfer, transfer is skipped if it is set. In case of transfer
1148c2ecf20Sopenharmony_ci	 * failure or unexpected response the field is set to error value.
1158c2ecf20Sopenharmony_ci	 * Such construct allows to eliminate many checks in higher level
1168c2ecf20Sopenharmony_ci	 * functions.
1178c2ecf20Sopenharmony_ci	 */
1188c2ecf20Sopenharmony_ci	int error;
1198c2ecf20Sopenharmony_ci};
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic const struct drm_display_mode default_mode = {
1228c2ecf20Sopenharmony_ci	.clock		= 25628,
1238c2ecf20Sopenharmony_ci	.hdisplay	= 480,
1248c2ecf20Sopenharmony_ci	.hsync_start	= 480 + 16,
1258c2ecf20Sopenharmony_ci	.hsync_end	= 480 + 16 + 2,
1268c2ecf20Sopenharmony_ci	.htotal		= 480 + 16 + 2 + 16,
1278c2ecf20Sopenharmony_ci	.vdisplay	= 800,
1288c2ecf20Sopenharmony_ci	.vsync_start	= 800 + 28,
1298c2ecf20Sopenharmony_ci	.vsync_end	= 800 + 28 + 2,
1308c2ecf20Sopenharmony_ci	.vtotal		= 800 + 28 + 2 + 1,
1318c2ecf20Sopenharmony_ci	.width_mm	= 53,
1328c2ecf20Sopenharmony_ci	.height_mm	= 89,
1338c2ecf20Sopenharmony_ci	.flags		= DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
1348c2ecf20Sopenharmony_ci};
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistatic inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	return container_of(panel, struct s6e63m0, panel);
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic int s6e63m0_clear_error(struct s6e63m0 *ctx)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	int ret = ctx->error;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	ctx->error = 0;
1468c2ecf20Sopenharmony_ci	return ret;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	if (ctx->error < 0)
1528c2ecf20Sopenharmony_ci		return;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	ctx->error = ctx->dcs_read(ctx->dev, cmd, data);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	if (ctx->error < 0 || len == 0)
1608c2ecf20Sopenharmony_ci		return;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	ctx->error = ctx->dcs_write(ctx->dev, data, len);
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
1668c2ecf20Sopenharmony_ci	({ \
1678c2ecf20Sopenharmony_ci		static const u8 d[] = { seq }; \
1688c2ecf20Sopenharmony_ci		s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
1698c2ecf20Sopenharmony_ci	})
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic int s6e63m0_check_lcd_type(struct s6e63m0 *ctx)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	u8 id1, id2, id3;
1748c2ecf20Sopenharmony_ci	int ret;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	s6e63m0_dcs_read(ctx, MCS_READ_ID1, &id1);
1778c2ecf20Sopenharmony_ci	s6e63m0_dcs_read(ctx, MCS_READ_ID2, &id2);
1788c2ecf20Sopenharmony_ci	s6e63m0_dcs_read(ctx, MCS_READ_ID3, &id3);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	ret = s6e63m0_clear_error(ctx);
1818c2ecf20Sopenharmony_ci	if (ret) {
1828c2ecf20Sopenharmony_ci		dev_err(ctx->dev, "error checking LCD type (%d)\n", ret);
1838c2ecf20Sopenharmony_ci		ctx->lcd_type = 0x00;
1848c2ecf20Sopenharmony_ci		return ret;
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	/* We attempt to detect what panel is mounted on the controller */
1908c2ecf20Sopenharmony_ci	switch (id2) {
1918c2ecf20Sopenharmony_ci	case S6E63M0_LCD_ID_VALUE_M2:
1928c2ecf20Sopenharmony_ci		dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n");
1938c2ecf20Sopenharmony_ci		break;
1948c2ecf20Sopenharmony_ci	case S6E63M0_LCD_ID_VALUE_SM2:
1958c2ecf20Sopenharmony_ci	case S6E63M0_LCD_ID_VALUE_SM2_1:
1968c2ecf20Sopenharmony_ci		dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n");
1978c2ecf20Sopenharmony_ci		break;
1988c2ecf20Sopenharmony_ci	default:
1998c2ecf20Sopenharmony_ci		dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2);
2008c2ecf20Sopenharmony_ci		break;
2018c2ecf20Sopenharmony_ci	}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	ctx->lcd_type = id2;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return 0;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic void s6e63m0_init(struct s6e63m0 *ctx)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
2118c2ecf20Sopenharmony_ci				     0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
2128c2ecf20Sopenharmony_ci				     0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
2158c2ecf20Sopenharmony_ci				     0x02, 0x03, 0x1c, 0x10, 0x10);
2168c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
2178c2ecf20Sopenharmony_ci				     0x03, 0x00, 0x00);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
2208c2ecf20Sopenharmony_ci				     0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
2218c2ecf20Sopenharmony_ci				     0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
2228c2ecf20Sopenharmony_ci				     0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
2238c2ecf20Sopenharmony_ci				     0xd6);
2248c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
2258c2ecf20Sopenharmony_ci				     0x01);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
2288c2ecf20Sopenharmony_ci				     0x00, 0x8c, 0x07);
2298c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb3,
2308c2ecf20Sopenharmony_ci				     0xc);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb5,
2338c2ecf20Sopenharmony_ci				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
2348c2ecf20Sopenharmony_ci				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
2358c2ecf20Sopenharmony_ci				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
2368c2ecf20Sopenharmony_ci				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
2378c2ecf20Sopenharmony_ci				     0x21, 0x20, 0x1e, 0x1e);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb6,
2408c2ecf20Sopenharmony_ci				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
2418c2ecf20Sopenharmony_ci				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
2428c2ecf20Sopenharmony_ci				     0x66, 0x66);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb7,
2458c2ecf20Sopenharmony_ci				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
2468c2ecf20Sopenharmony_ci				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
2478c2ecf20Sopenharmony_ci				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
2488c2ecf20Sopenharmony_ci				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
2498c2ecf20Sopenharmony_ci				     0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
2508c2ecf20Sopenharmony_ci				     0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
2518c2ecf20Sopenharmony_ci				     0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb9,
2548c2ecf20Sopenharmony_ci				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
2558c2ecf20Sopenharmony_ci				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
2568c2ecf20Sopenharmony_ci				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
2578c2ecf20Sopenharmony_ci				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
2588c2ecf20Sopenharmony_ci				     0x21, 0x20, 0x1e, 0x1e);
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xba,
2618c2ecf20Sopenharmony_ci				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
2628c2ecf20Sopenharmony_ci				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
2638c2ecf20Sopenharmony_ci				     0x66, 0x66);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
2668c2ecf20Sopenharmony_ci				     0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
2678c2ecf20Sopenharmony_ci				     0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
2688c2ecf20Sopenharmony_ci				     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
2698c2ecf20Sopenharmony_ci				     0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, 0xb2,
2728c2ecf20Sopenharmony_ci				     0x10, 0x10, 0x0b, 0x05);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
2758c2ecf20Sopenharmony_ci				     0x01);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
2788c2ecf20Sopenharmony_ci				     0x0b);
2798c2ecf20Sopenharmony_ci}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_cistatic int s6e63m0_power_on(struct s6e63m0 *ctx)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	int ret;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
2868c2ecf20Sopenharmony_ci	if (ret < 0)
2878c2ecf20Sopenharmony_ci		return ret;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	msleep(25);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	/* Be sure to send a reset pulse */
2928c2ecf20Sopenharmony_ci	gpiod_set_value(ctx->reset_gpio, 1);
2938c2ecf20Sopenharmony_ci	msleep(5);
2948c2ecf20Sopenharmony_ci	gpiod_set_value(ctx->reset_gpio, 0);
2958c2ecf20Sopenharmony_ci	msleep(120);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	return 0;
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistatic int s6e63m0_power_off(struct s6e63m0 *ctx)
3018c2ecf20Sopenharmony_ci{
3028c2ecf20Sopenharmony_ci	int ret;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	gpiod_set_value(ctx->reset_gpio, 1);
3058c2ecf20Sopenharmony_ci	msleep(120);
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
3088c2ecf20Sopenharmony_ci	if (ret < 0)
3098c2ecf20Sopenharmony_ci		return ret;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	return 0;
3128c2ecf20Sopenharmony_ci}
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_cistatic int s6e63m0_disable(struct drm_panel *panel)
3158c2ecf20Sopenharmony_ci{
3168c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	if (!ctx->enabled)
3198c2ecf20Sopenharmony_ci		return 0;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	backlight_disable(ctx->bl_dev);
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
3248c2ecf20Sopenharmony_ci	msleep(10);
3258c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
3268c2ecf20Sopenharmony_ci	msleep(120);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	ctx->enabled = false;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	return 0;
3318c2ecf20Sopenharmony_ci}
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic int s6e63m0_unprepare(struct drm_panel *panel)
3348c2ecf20Sopenharmony_ci{
3358c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
3368c2ecf20Sopenharmony_ci	int ret;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	if (!ctx->prepared)
3398c2ecf20Sopenharmony_ci		return 0;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	s6e63m0_clear_error(ctx);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	ret = s6e63m0_power_off(ctx);
3448c2ecf20Sopenharmony_ci	if (ret < 0)
3458c2ecf20Sopenharmony_ci		return ret;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	ctx->prepared = false;
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	return 0;
3508c2ecf20Sopenharmony_ci}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_cistatic int s6e63m0_prepare(struct drm_panel *panel)
3538c2ecf20Sopenharmony_ci{
3548c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
3558c2ecf20Sopenharmony_ci	int ret;
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	if (ctx->prepared)
3588c2ecf20Sopenharmony_ci		return 0;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	ret = s6e63m0_power_on(ctx);
3618c2ecf20Sopenharmony_ci	if (ret < 0)
3628c2ecf20Sopenharmony_ci		return ret;
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	/* Magic to unlock level 2 control of the display */
3658c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a);
3668c2ecf20Sopenharmony_ci	/* Magic to unlock MTP reading */
3678c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	ret = s6e63m0_check_lcd_type(ctx);
3708c2ecf20Sopenharmony_ci	if (ret < 0)
3718c2ecf20Sopenharmony_ci		return ret;
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	s6e63m0_init(ctx);
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	ret = s6e63m0_clear_error(ctx);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	if (ret < 0)
3788c2ecf20Sopenharmony_ci		s6e63m0_unprepare(panel);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	ctx->prepared = true;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	return ret;
3838c2ecf20Sopenharmony_ci}
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_cistatic int s6e63m0_enable(struct drm_panel *panel)
3868c2ecf20Sopenharmony_ci{
3878c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	if (ctx->enabled)
3908c2ecf20Sopenharmony_ci		return 0;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
3938c2ecf20Sopenharmony_ci	msleep(120);
3948c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
3958c2ecf20Sopenharmony_ci	msleep(10);
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK,
3988c2ecf20Sopenharmony_ci				     0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3,
3998c2ecf20Sopenharmony_ci				     0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20,
4008c2ecf20Sopenharmony_ci				     0x0F, 0x00);
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	backlight_enable(ctx->bl_dev);
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	ctx->enabled = true;
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	return 0;
4078c2ecf20Sopenharmony_ci}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_cistatic int s6e63m0_get_modes(struct drm_panel *panel,
4108c2ecf20Sopenharmony_ci			     struct drm_connector *connector)
4118c2ecf20Sopenharmony_ci{
4128c2ecf20Sopenharmony_ci	struct drm_display_mode *mode;
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &default_mode);
4158c2ecf20Sopenharmony_ci	if (!mode) {
4168c2ecf20Sopenharmony_ci		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
4178c2ecf20Sopenharmony_ci			default_mode.hdisplay, default_mode.vdisplay,
4188c2ecf20Sopenharmony_ci			drm_mode_vrefresh(&default_mode));
4198c2ecf20Sopenharmony_ci		return -ENOMEM;
4208c2ecf20Sopenharmony_ci	}
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	drm_mode_set_name(mode);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
4258c2ecf20Sopenharmony_ci	drm_mode_probed_add(connector, mode);
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	return 1;
4288c2ecf20Sopenharmony_ci}
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_cistatic const struct drm_panel_funcs s6e63m0_drm_funcs = {
4318c2ecf20Sopenharmony_ci	.disable	= s6e63m0_disable,
4328c2ecf20Sopenharmony_ci	.unprepare	= s6e63m0_unprepare,
4338c2ecf20Sopenharmony_ci	.prepare	= s6e63m0_prepare,
4348c2ecf20Sopenharmony_ci	.enable		= s6e63m0_enable,
4358c2ecf20Sopenharmony_ci	.get_modes	= s6e63m0_get_modes,
4368c2ecf20Sopenharmony_ci};
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_cistatic int s6e63m0_set_brightness(struct backlight_device *bd)
4398c2ecf20Sopenharmony_ci{
4408c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = bl_get_data(bd);
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	int brightness = bd->props.brightness;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	/* disable and set new gamma */
4458c2ecf20Sopenharmony_ci	s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
4468c2ecf20Sopenharmony_ci			  ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	/* update gamma table. */
4498c2ecf20Sopenharmony_ci	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	return s6e63m0_clear_error(ctx);
4528c2ecf20Sopenharmony_ci}
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_cistatic const struct backlight_ops s6e63m0_backlight_ops = {
4558c2ecf20Sopenharmony_ci	.update_status	= s6e63m0_set_brightness,
4568c2ecf20Sopenharmony_ci};
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_cistatic int s6e63m0_backlight_register(struct s6e63m0 *ctx)
4598c2ecf20Sopenharmony_ci{
4608c2ecf20Sopenharmony_ci	struct backlight_properties props = {
4618c2ecf20Sopenharmony_ci		.type		= BACKLIGHT_RAW,
4628c2ecf20Sopenharmony_ci		.brightness	= MAX_BRIGHTNESS,
4638c2ecf20Sopenharmony_ci		.max_brightness = MAX_BRIGHTNESS
4648c2ecf20Sopenharmony_ci	};
4658c2ecf20Sopenharmony_ci	struct device *dev = ctx->dev;
4668c2ecf20Sopenharmony_ci	int ret = 0;
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
4698c2ecf20Sopenharmony_ci						     &s6e63m0_backlight_ops,
4708c2ecf20Sopenharmony_ci						     &props);
4718c2ecf20Sopenharmony_ci	if (IS_ERR(ctx->bl_dev)) {
4728c2ecf20Sopenharmony_ci		ret = PTR_ERR(ctx->bl_dev);
4738c2ecf20Sopenharmony_ci		dev_err(dev, "error registering backlight device (%d)\n", ret);
4748c2ecf20Sopenharmony_ci	}
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	return ret;
4778c2ecf20Sopenharmony_ci}
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ciint s6e63m0_probe(struct device *dev,
4808c2ecf20Sopenharmony_ci		  int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val),
4818c2ecf20Sopenharmony_ci		  int (*dcs_write)(struct device *dev, const u8 *data, size_t len),
4828c2ecf20Sopenharmony_ci		  bool dsi_mode)
4838c2ecf20Sopenharmony_ci{
4848c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx;
4858c2ecf20Sopenharmony_ci	int ret;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
4888c2ecf20Sopenharmony_ci	if (!ctx)
4898c2ecf20Sopenharmony_ci		return -ENOMEM;
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci	ctx->dcs_read = dcs_read;
4928c2ecf20Sopenharmony_ci	ctx->dcs_write = dcs_write;
4938c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, ctx);
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	ctx->dev = dev;
4968c2ecf20Sopenharmony_ci	ctx->enabled = false;
4978c2ecf20Sopenharmony_ci	ctx->prepared = false;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	ctx->supplies[0].supply = "vdd3";
5008c2ecf20Sopenharmony_ci	ctx->supplies[1].supply = "vci";
5018c2ecf20Sopenharmony_ci	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
5028c2ecf20Sopenharmony_ci				      ctx->supplies);
5038c2ecf20Sopenharmony_ci	if (ret < 0) {
5048c2ecf20Sopenharmony_ci		dev_err(dev, "failed to get regulators: %d\n", ret);
5058c2ecf20Sopenharmony_ci		return ret;
5068c2ecf20Sopenharmony_ci	}
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
5098c2ecf20Sopenharmony_ci	if (IS_ERR(ctx->reset_gpio)) {
5108c2ecf20Sopenharmony_ci		dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
5118c2ecf20Sopenharmony_ci		return PTR_ERR(ctx->reset_gpio);
5128c2ecf20Sopenharmony_ci	}
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
5158c2ecf20Sopenharmony_ci		       dsi_mode ? DRM_MODE_CONNECTOR_DSI :
5168c2ecf20Sopenharmony_ci		       DRM_MODE_CONNECTOR_DPI);
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	ret = s6e63m0_backlight_register(ctx);
5198c2ecf20Sopenharmony_ci	if (ret < 0)
5208c2ecf20Sopenharmony_ci		return ret;
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	drm_panel_add(&ctx->panel);
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_ci	return 0;
5258c2ecf20Sopenharmony_ci}
5268c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s6e63m0_probe);
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ciint s6e63m0_remove(struct device *dev)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	struct s6e63m0 *ctx = dev_get_drvdata(dev);
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	drm_panel_remove(&ctx->panel);
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	return 0;
5358c2ecf20Sopenharmony_ci}
5368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s6e63m0_remove);
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
5398c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("s6e63m0 LCD Driver");
5408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
541