162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Raydium RM67191 MIPI-DSI panel driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2019 NXP
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/backlight.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1162306a36Sopenharmony_ci#include <linux/media-bus-format.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/of.h>
1462306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <video/mipi_display.h>
1762306a36Sopenharmony_ci#include <video/of_videomode.h>
1862306a36Sopenharmony_ci#include <video/videomode.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <drm/drm_crtc.h>
2162306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h>
2262306a36Sopenharmony_ci#include <drm/drm_panel.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* Panel specific color-format bits */
2562306a36Sopenharmony_ci#define COL_FMT_16BPP 0x55
2662306a36Sopenharmony_ci#define COL_FMT_18BPP 0x66
2762306a36Sopenharmony_ci#define COL_FMT_24BPP 0x77
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* Write Manufacture Command Set Control */
3062306a36Sopenharmony_ci#define WRMAUCCTR 0xFE
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* Manufacturer Command Set pages (CMD2) */
3362306a36Sopenharmony_cistruct cmd_set_entry {
3462306a36Sopenharmony_ci	u8 cmd;
3562306a36Sopenharmony_ci	u8 param;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/*
3962306a36Sopenharmony_ci * There is no description in the Reference Manual about these commands.
4062306a36Sopenharmony_ci * We received them from vendor, so just use them as is.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_cistatic const struct cmd_set_entry manufacturer_cmd_set[] = {
4362306a36Sopenharmony_ci	{0xFE, 0x0B},
4462306a36Sopenharmony_ci	{0x28, 0x40},
4562306a36Sopenharmony_ci	{0x29, 0x4F},
4662306a36Sopenharmony_ci	{0xFE, 0x0E},
4762306a36Sopenharmony_ci	{0x4B, 0x00},
4862306a36Sopenharmony_ci	{0x4C, 0x0F},
4962306a36Sopenharmony_ci	{0x4D, 0x20},
5062306a36Sopenharmony_ci	{0x4E, 0x40},
5162306a36Sopenharmony_ci	{0x4F, 0x60},
5262306a36Sopenharmony_ci	{0x50, 0xA0},
5362306a36Sopenharmony_ci	{0x51, 0xC0},
5462306a36Sopenharmony_ci	{0x52, 0xE0},
5562306a36Sopenharmony_ci	{0x53, 0xFF},
5662306a36Sopenharmony_ci	{0xFE, 0x0D},
5762306a36Sopenharmony_ci	{0x18, 0x08},
5862306a36Sopenharmony_ci	{0x42, 0x00},
5962306a36Sopenharmony_ci	{0x08, 0x41},
6062306a36Sopenharmony_ci	{0x46, 0x02},
6162306a36Sopenharmony_ci	{0x72, 0x09},
6262306a36Sopenharmony_ci	{0xFE, 0x0A},
6362306a36Sopenharmony_ci	{0x24, 0x17},
6462306a36Sopenharmony_ci	{0x04, 0x07},
6562306a36Sopenharmony_ci	{0x1A, 0x0C},
6662306a36Sopenharmony_ci	{0x0F, 0x44},
6762306a36Sopenharmony_ci	{0xFE, 0x04},
6862306a36Sopenharmony_ci	{0x00, 0x0C},
6962306a36Sopenharmony_ci	{0x05, 0x08},
7062306a36Sopenharmony_ci	{0x06, 0x08},
7162306a36Sopenharmony_ci	{0x08, 0x08},
7262306a36Sopenharmony_ci	{0x09, 0x08},
7362306a36Sopenharmony_ci	{0x0A, 0xE6},
7462306a36Sopenharmony_ci	{0x0B, 0x8C},
7562306a36Sopenharmony_ci	{0x1A, 0x12},
7662306a36Sopenharmony_ci	{0x1E, 0xE0},
7762306a36Sopenharmony_ci	{0x29, 0x93},
7862306a36Sopenharmony_ci	{0x2A, 0x93},
7962306a36Sopenharmony_ci	{0x2F, 0x02},
8062306a36Sopenharmony_ci	{0x31, 0x02},
8162306a36Sopenharmony_ci	{0x33, 0x05},
8262306a36Sopenharmony_ci	{0x37, 0x2D},
8362306a36Sopenharmony_ci	{0x38, 0x2D},
8462306a36Sopenharmony_ci	{0x3A, 0x1E},
8562306a36Sopenharmony_ci	{0x3B, 0x1E},
8662306a36Sopenharmony_ci	{0x3D, 0x27},
8762306a36Sopenharmony_ci	{0x3F, 0x80},
8862306a36Sopenharmony_ci	{0x40, 0x40},
8962306a36Sopenharmony_ci	{0x41, 0xE0},
9062306a36Sopenharmony_ci	{0x4F, 0x2F},
9162306a36Sopenharmony_ci	{0x50, 0x1E},
9262306a36Sopenharmony_ci	{0xFE, 0x06},
9362306a36Sopenharmony_ci	{0x00, 0xCC},
9462306a36Sopenharmony_ci	{0x05, 0x05},
9562306a36Sopenharmony_ci	{0x07, 0xA2},
9662306a36Sopenharmony_ci	{0x08, 0xCC},
9762306a36Sopenharmony_ci	{0x0D, 0x03},
9862306a36Sopenharmony_ci	{0x0F, 0xA2},
9962306a36Sopenharmony_ci	{0x32, 0xCC},
10062306a36Sopenharmony_ci	{0x37, 0x05},
10162306a36Sopenharmony_ci	{0x39, 0x83},
10262306a36Sopenharmony_ci	{0x3A, 0xCC},
10362306a36Sopenharmony_ci	{0x41, 0x04},
10462306a36Sopenharmony_ci	{0x43, 0x83},
10562306a36Sopenharmony_ci	{0x44, 0xCC},
10662306a36Sopenharmony_ci	{0x49, 0x05},
10762306a36Sopenharmony_ci	{0x4B, 0xA2},
10862306a36Sopenharmony_ci	{0x4C, 0xCC},
10962306a36Sopenharmony_ci	{0x51, 0x03},
11062306a36Sopenharmony_ci	{0x53, 0xA2},
11162306a36Sopenharmony_ci	{0x75, 0xCC},
11262306a36Sopenharmony_ci	{0x7A, 0x03},
11362306a36Sopenharmony_ci	{0x7C, 0x83},
11462306a36Sopenharmony_ci	{0x7D, 0xCC},
11562306a36Sopenharmony_ci	{0x82, 0x02},
11662306a36Sopenharmony_ci	{0x84, 0x83},
11762306a36Sopenharmony_ci	{0x85, 0xEC},
11862306a36Sopenharmony_ci	{0x86, 0x0F},
11962306a36Sopenharmony_ci	{0x87, 0xFF},
12062306a36Sopenharmony_ci	{0x88, 0x00},
12162306a36Sopenharmony_ci	{0x8A, 0x02},
12262306a36Sopenharmony_ci	{0x8C, 0xA2},
12362306a36Sopenharmony_ci	{0x8D, 0xEA},
12462306a36Sopenharmony_ci	{0x8E, 0x01},
12562306a36Sopenharmony_ci	{0x8F, 0xE8},
12662306a36Sopenharmony_ci	{0xFE, 0x06},
12762306a36Sopenharmony_ci	{0x90, 0x0A},
12862306a36Sopenharmony_ci	{0x92, 0x06},
12962306a36Sopenharmony_ci	{0x93, 0xA0},
13062306a36Sopenharmony_ci	{0x94, 0xA8},
13162306a36Sopenharmony_ci	{0x95, 0xEC},
13262306a36Sopenharmony_ci	{0x96, 0x0F},
13362306a36Sopenharmony_ci	{0x97, 0xFF},
13462306a36Sopenharmony_ci	{0x98, 0x00},
13562306a36Sopenharmony_ci	{0x9A, 0x02},
13662306a36Sopenharmony_ci	{0x9C, 0xA2},
13762306a36Sopenharmony_ci	{0xAC, 0x04},
13862306a36Sopenharmony_ci	{0xFE, 0x06},
13962306a36Sopenharmony_ci	{0xB1, 0x12},
14062306a36Sopenharmony_ci	{0xB2, 0x17},
14162306a36Sopenharmony_ci	{0xB3, 0x17},
14262306a36Sopenharmony_ci	{0xB4, 0x17},
14362306a36Sopenharmony_ci	{0xB5, 0x17},
14462306a36Sopenharmony_ci	{0xB6, 0x11},
14562306a36Sopenharmony_ci	{0xB7, 0x08},
14662306a36Sopenharmony_ci	{0xB8, 0x09},
14762306a36Sopenharmony_ci	{0xB9, 0x06},
14862306a36Sopenharmony_ci	{0xBA, 0x07},
14962306a36Sopenharmony_ci	{0xBB, 0x17},
15062306a36Sopenharmony_ci	{0xBC, 0x17},
15162306a36Sopenharmony_ci	{0xBD, 0x17},
15262306a36Sopenharmony_ci	{0xBE, 0x17},
15362306a36Sopenharmony_ci	{0xBF, 0x17},
15462306a36Sopenharmony_ci	{0xC0, 0x17},
15562306a36Sopenharmony_ci	{0xC1, 0x17},
15662306a36Sopenharmony_ci	{0xC2, 0x17},
15762306a36Sopenharmony_ci	{0xC3, 0x17},
15862306a36Sopenharmony_ci	{0xC4, 0x0F},
15962306a36Sopenharmony_ci	{0xC5, 0x0E},
16062306a36Sopenharmony_ci	{0xC6, 0x00},
16162306a36Sopenharmony_ci	{0xC7, 0x01},
16262306a36Sopenharmony_ci	{0xC8, 0x10},
16362306a36Sopenharmony_ci	{0xFE, 0x06},
16462306a36Sopenharmony_ci	{0x95, 0xEC},
16562306a36Sopenharmony_ci	{0x8D, 0xEE},
16662306a36Sopenharmony_ci	{0x44, 0xEC},
16762306a36Sopenharmony_ci	{0x4C, 0xEC},
16862306a36Sopenharmony_ci	{0x32, 0xEC},
16962306a36Sopenharmony_ci	{0x3A, 0xEC},
17062306a36Sopenharmony_ci	{0x7D, 0xEC},
17162306a36Sopenharmony_ci	{0x75, 0xEC},
17262306a36Sopenharmony_ci	{0x00, 0xEC},
17362306a36Sopenharmony_ci	{0x08, 0xEC},
17462306a36Sopenharmony_ci	{0x85, 0xEC},
17562306a36Sopenharmony_ci	{0xA6, 0x21},
17662306a36Sopenharmony_ci	{0xA7, 0x05},
17762306a36Sopenharmony_ci	{0xA9, 0x06},
17862306a36Sopenharmony_ci	{0x82, 0x06},
17962306a36Sopenharmony_ci	{0x41, 0x06},
18062306a36Sopenharmony_ci	{0x7A, 0x07},
18162306a36Sopenharmony_ci	{0x37, 0x07},
18262306a36Sopenharmony_ci	{0x05, 0x06},
18362306a36Sopenharmony_ci	{0x49, 0x06},
18462306a36Sopenharmony_ci	{0x0D, 0x04},
18562306a36Sopenharmony_ci	{0x51, 0x04},
18662306a36Sopenharmony_ci};
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic const u32 rad_bus_formats[] = {
18962306a36Sopenharmony_ci	MEDIA_BUS_FMT_RGB888_1X24,
19062306a36Sopenharmony_ci	MEDIA_BUS_FMT_RGB666_1X18,
19162306a36Sopenharmony_ci	MEDIA_BUS_FMT_RGB565_1X16,
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic const u32 rad_bus_flags = DRM_BUS_FLAG_DE_LOW |
19562306a36Sopenharmony_ci				 DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistruct rad_panel {
19862306a36Sopenharmony_ci	struct drm_panel panel;
19962306a36Sopenharmony_ci	struct mipi_dsi_device *dsi;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	struct gpio_desc *reset;
20262306a36Sopenharmony_ci	struct backlight_device *backlight;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	struct regulator_bulk_data *supplies;
20562306a36Sopenharmony_ci	unsigned int num_supplies;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	bool prepared;
20862306a36Sopenharmony_ci	bool enabled;
20962306a36Sopenharmony_ci};
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic const struct drm_display_mode default_mode = {
21262306a36Sopenharmony_ci	.clock = 132000,
21362306a36Sopenharmony_ci	.hdisplay = 1080,
21462306a36Sopenharmony_ci	.hsync_start = 1080 + 20,
21562306a36Sopenharmony_ci	.hsync_end = 1080 + 20 + 2,
21662306a36Sopenharmony_ci	.htotal = 1080 + 20 + 2 + 34,
21762306a36Sopenharmony_ci	.vdisplay = 1920,
21862306a36Sopenharmony_ci	.vsync_start = 1920 + 10,
21962306a36Sopenharmony_ci	.vsync_end = 1920 + 10 + 2,
22062306a36Sopenharmony_ci	.vtotal = 1920 + 10 + 2 + 4,
22162306a36Sopenharmony_ci	.width_mm = 68,
22262306a36Sopenharmony_ci	.height_mm = 121,
22362306a36Sopenharmony_ci	.flags = DRM_MODE_FLAG_NHSYNC |
22462306a36Sopenharmony_ci		 DRM_MODE_FLAG_NVSYNC,
22562306a36Sopenharmony_ci};
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic inline struct rad_panel *to_rad_panel(struct drm_panel *panel)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	return container_of(panel, struct rad_panel, panel);
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	size_t i;
23562306a36Sopenharmony_ci	size_t count = ARRAY_SIZE(manufacturer_cmd_set);
23662306a36Sopenharmony_ci	int ret = 0;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
23962306a36Sopenharmony_ci		const struct cmd_set_entry *entry = &manufacturer_cmd_set[i];
24062306a36Sopenharmony_ci		u8 buffer[2] = { entry->cmd, entry->param };
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci		ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer));
24362306a36Sopenharmony_ci		if (ret < 0)
24462306a36Sopenharmony_ci			return ret;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return ret;
24862306a36Sopenharmony_ci};
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int color_format_from_dsi_format(enum mipi_dsi_pixel_format format)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	switch (format) {
25362306a36Sopenharmony_ci	case MIPI_DSI_FMT_RGB565:
25462306a36Sopenharmony_ci		return COL_FMT_16BPP;
25562306a36Sopenharmony_ci	case MIPI_DSI_FMT_RGB666:
25662306a36Sopenharmony_ci	case MIPI_DSI_FMT_RGB666_PACKED:
25762306a36Sopenharmony_ci		return COL_FMT_18BPP;
25862306a36Sopenharmony_ci	case MIPI_DSI_FMT_RGB888:
25962306a36Sopenharmony_ci		return COL_FMT_24BPP;
26062306a36Sopenharmony_ci	default:
26162306a36Sopenharmony_ci		return COL_FMT_24BPP; /* for backward compatibility */
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci};
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic int rad_panel_prepare(struct drm_panel *panel)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
26862306a36Sopenharmony_ci	int ret;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (rad->prepared)
27162306a36Sopenharmony_ci		return 0;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	ret = regulator_bulk_enable(rad->num_supplies, rad->supplies);
27462306a36Sopenharmony_ci	if (ret)
27562306a36Sopenharmony_ci		return ret;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (rad->reset) {
27862306a36Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 1);
27962306a36Sopenharmony_ci		usleep_range(3000, 5000);
28062306a36Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 0);
28162306a36Sopenharmony_ci		usleep_range(18000, 20000);
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	rad->prepared = true;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	return 0;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic int rad_panel_unprepare(struct drm_panel *panel)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
29262306a36Sopenharmony_ci	int ret;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	if (!rad->prepared)
29562306a36Sopenharmony_ci		return 0;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/*
29862306a36Sopenharmony_ci	 * Right after asserting the reset, we need to release it, so that the
29962306a36Sopenharmony_ci	 * touch driver can have an active connection with the touch controller
30062306a36Sopenharmony_ci	 * even after the display is turned off.
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci	if (rad->reset) {
30362306a36Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 1);
30462306a36Sopenharmony_ci		usleep_range(15000, 17000);
30562306a36Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 0);
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	ret = regulator_bulk_disable(rad->num_supplies, rad->supplies);
30962306a36Sopenharmony_ci	if (ret)
31062306a36Sopenharmony_ci		return ret;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	rad->prepared = false;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	return 0;
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic int rad_panel_enable(struct drm_panel *panel)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
32062306a36Sopenharmony_ci	struct mipi_dsi_device *dsi = rad->dsi;
32162306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
32262306a36Sopenharmony_ci	int color_format = color_format_from_dsi_format(dsi->format);
32362306a36Sopenharmony_ci	int ret;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	if (rad->enabled)
32662306a36Sopenharmony_ci		return 0;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	ret = rad_panel_push_cmd_list(dsi);
33162306a36Sopenharmony_ci	if (ret < 0) {
33262306a36Sopenharmony_ci		dev_err(dev, "Failed to send MCS (%d)\n", ret);
33362306a36Sopenharmony_ci		goto fail;
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	/* Select User Command Set table (CMD1) */
33762306a36Sopenharmony_ci	ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2);
33862306a36Sopenharmony_ci	if (ret < 0)
33962306a36Sopenharmony_ci		goto fail;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	/* Software reset */
34262306a36Sopenharmony_ci	ret = mipi_dsi_dcs_soft_reset(dsi);
34362306a36Sopenharmony_ci	if (ret < 0) {
34462306a36Sopenharmony_ci		dev_err(dev, "Failed to do Software Reset (%d)\n", ret);
34562306a36Sopenharmony_ci		goto fail;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	usleep_range(15000, 17000);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	/* Set DSI mode */
35162306a36Sopenharmony_ci	ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2);
35262306a36Sopenharmony_ci	if (ret < 0) {
35362306a36Sopenharmony_ci		dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
35462306a36Sopenharmony_ci		goto fail;
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci	/* Set tear ON */
35762306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
35862306a36Sopenharmony_ci	if (ret < 0) {
35962306a36Sopenharmony_ci		dev_err(dev, "Failed to set tear ON (%d)\n", ret);
36062306a36Sopenharmony_ci		goto fail;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci	/* Set tear scanline */
36362306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x380);
36462306a36Sopenharmony_ci	if (ret < 0) {
36562306a36Sopenharmony_ci		dev_err(dev, "Failed to set tear scanline (%d)\n", ret);
36662306a36Sopenharmony_ci		goto fail;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci	/* Set pixel format */
36962306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format);
37062306a36Sopenharmony_ci	dev_dbg(dev, "Interface color format set to 0x%x\n", color_format);
37162306a36Sopenharmony_ci	if (ret < 0) {
37262306a36Sopenharmony_ci		dev_err(dev, "Failed to set pixel format (%d)\n", ret);
37362306a36Sopenharmony_ci		goto fail;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci	/* Exit sleep mode */
37662306a36Sopenharmony_ci	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
37762306a36Sopenharmony_ci	if (ret < 0) {
37862306a36Sopenharmony_ci		dev_err(dev, "Failed to exit sleep mode (%d)\n", ret);
37962306a36Sopenharmony_ci		goto fail;
38062306a36Sopenharmony_ci	}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	usleep_range(5000, 7000);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_on(dsi);
38562306a36Sopenharmony_ci	if (ret < 0) {
38662306a36Sopenharmony_ci		dev_err(dev, "Failed to set display ON (%d)\n", ret);
38762306a36Sopenharmony_ci		goto fail;
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	backlight_enable(rad->backlight);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	rad->enabled = true;
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	return 0;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cifail:
39762306a36Sopenharmony_ci	gpiod_set_value_cansleep(rad->reset, 1);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	return ret;
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic int rad_panel_disable(struct drm_panel *panel)
40362306a36Sopenharmony_ci{
40462306a36Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
40562306a36Sopenharmony_ci	struct mipi_dsi_device *dsi = rad->dsi;
40662306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
40762306a36Sopenharmony_ci	int ret;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (!rad->enabled)
41062306a36Sopenharmony_ci		return 0;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	backlight_disable(rad->backlight);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	usleep_range(10000, 12000);
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_off(dsi);
41962306a36Sopenharmony_ci	if (ret < 0) {
42062306a36Sopenharmony_ci		dev_err(dev, "Failed to set display OFF (%d)\n", ret);
42162306a36Sopenharmony_ci		return ret;
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	usleep_range(5000, 10000);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
42762306a36Sopenharmony_ci	if (ret < 0) {
42862306a36Sopenharmony_ci		dev_err(dev, "Failed to enter sleep mode (%d)\n", ret);
42962306a36Sopenharmony_ci		return ret;
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	rad->enabled = false;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	return 0;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_cistatic int rad_panel_get_modes(struct drm_panel *panel,
43862306a36Sopenharmony_ci			       struct drm_connector *connector)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	struct drm_display_mode *mode;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &default_mode);
44362306a36Sopenharmony_ci	if (!mode) {
44462306a36Sopenharmony_ci		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
44562306a36Sopenharmony_ci			default_mode.hdisplay, default_mode.vdisplay,
44662306a36Sopenharmony_ci			drm_mode_vrefresh(&default_mode));
44762306a36Sopenharmony_ci		return -ENOMEM;
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	drm_mode_set_name(mode);
45162306a36Sopenharmony_ci	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
45262306a36Sopenharmony_ci	drm_mode_probed_add(connector, mode);
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	connector->display_info.width_mm = mode->width_mm;
45562306a36Sopenharmony_ci	connector->display_info.height_mm = mode->height_mm;
45662306a36Sopenharmony_ci	connector->display_info.bus_flags = rad_bus_flags;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	drm_display_info_set_bus_formats(&connector->display_info,
45962306a36Sopenharmony_ci					 rad_bus_formats,
46062306a36Sopenharmony_ci					 ARRAY_SIZE(rad_bus_formats));
46162306a36Sopenharmony_ci	return 1;
46262306a36Sopenharmony_ci}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_cistatic int rad_bl_get_brightness(struct backlight_device *bl)
46562306a36Sopenharmony_ci{
46662306a36Sopenharmony_ci	struct mipi_dsi_device *dsi = bl_get_data(bl);
46762306a36Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
46862306a36Sopenharmony_ci	u16 brightness;
46962306a36Sopenharmony_ci	int ret;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	if (!rad->prepared)
47262306a36Sopenharmony_ci		return 0;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);
47762306a36Sopenharmony_ci	if (ret < 0)
47862306a36Sopenharmony_ci		return ret;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	bl->props.brightness = brightness;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	return brightness & 0xff;
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_cistatic int rad_bl_update_status(struct backlight_device *bl)
48662306a36Sopenharmony_ci{
48762306a36Sopenharmony_ci	struct mipi_dsi_device *dsi = bl_get_data(bl);
48862306a36Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
48962306a36Sopenharmony_ci	int ret = 0;
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	if (!rad->prepared)
49262306a36Sopenharmony_ci		return 0;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness);
49762306a36Sopenharmony_ci	if (ret < 0)
49862306a36Sopenharmony_ci		return ret;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	return 0;
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic const struct backlight_ops rad_bl_ops = {
50462306a36Sopenharmony_ci	.update_status = rad_bl_update_status,
50562306a36Sopenharmony_ci	.get_brightness = rad_bl_get_brightness,
50662306a36Sopenharmony_ci};
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic const struct drm_panel_funcs rad_panel_funcs = {
50962306a36Sopenharmony_ci	.prepare = rad_panel_prepare,
51062306a36Sopenharmony_ci	.unprepare = rad_panel_unprepare,
51162306a36Sopenharmony_ci	.enable = rad_panel_enable,
51262306a36Sopenharmony_ci	.disable = rad_panel_disable,
51362306a36Sopenharmony_ci	.get_modes = rad_panel_get_modes,
51462306a36Sopenharmony_ci};
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic const char * const rad_supply_names[] = {
51762306a36Sopenharmony_ci	"v3p3",
51862306a36Sopenharmony_ci	"v1p8",
51962306a36Sopenharmony_ci};
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_cistatic int rad_init_regulators(struct rad_panel *rad)
52262306a36Sopenharmony_ci{
52362306a36Sopenharmony_ci	struct device *dev = &rad->dsi->dev;
52462306a36Sopenharmony_ci	int i;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	rad->num_supplies = ARRAY_SIZE(rad_supply_names);
52762306a36Sopenharmony_ci	rad->supplies = devm_kcalloc(dev, rad->num_supplies,
52862306a36Sopenharmony_ci				     sizeof(*rad->supplies), GFP_KERNEL);
52962306a36Sopenharmony_ci	if (!rad->supplies)
53062306a36Sopenharmony_ci		return -ENOMEM;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	for (i = 0; i < rad->num_supplies; i++)
53362306a36Sopenharmony_ci		rad->supplies[i].supply = rad_supply_names[i];
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies);
53662306a36Sopenharmony_ci};
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_cistatic int rad_panel_probe(struct mipi_dsi_device *dsi)
53962306a36Sopenharmony_ci{
54062306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
54162306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
54262306a36Sopenharmony_ci	struct rad_panel *panel;
54362306a36Sopenharmony_ci	struct backlight_properties bl_props;
54462306a36Sopenharmony_ci	int ret;
54562306a36Sopenharmony_ci	u32 video_mode;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);
54862306a36Sopenharmony_ci	if (!panel)
54962306a36Sopenharmony_ci		return -ENOMEM;
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	mipi_dsi_set_drvdata(dsi, panel);
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	panel->dsi = dsi;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	dsi->format = MIPI_DSI_FMT_RGB888;
55662306a36Sopenharmony_ci	dsi->mode_flags =  MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	ret = of_property_read_u32(np, "video-mode", &video_mode);
55962306a36Sopenharmony_ci	if (!ret) {
56062306a36Sopenharmony_ci		switch (video_mode) {
56162306a36Sopenharmony_ci		case 0:
56262306a36Sopenharmony_ci			/* burst mode */
56362306a36Sopenharmony_ci			dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST;
56462306a36Sopenharmony_ci			break;
56562306a36Sopenharmony_ci		case 1:
56662306a36Sopenharmony_ci			/* non-burst mode with sync event */
56762306a36Sopenharmony_ci			break;
56862306a36Sopenharmony_ci		case 2:
56962306a36Sopenharmony_ci			/* non-burst mode with sync pulse */
57062306a36Sopenharmony_ci			dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
57162306a36Sopenharmony_ci			break;
57262306a36Sopenharmony_ci		default:
57362306a36Sopenharmony_ci			dev_warn(dev, "invalid video mode %d\n", video_mode);
57462306a36Sopenharmony_ci			break;
57562306a36Sopenharmony_ci		}
57662306a36Sopenharmony_ci	}
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	ret = of_property_read_u32(np, "dsi-lanes", &dsi->lanes);
57962306a36Sopenharmony_ci	if (ret) {
58062306a36Sopenharmony_ci		dev_err(dev, "Failed to get dsi-lanes property (%d)\n", ret);
58162306a36Sopenharmony_ci		return ret;
58262306a36Sopenharmony_ci	}
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
58562306a36Sopenharmony_ci	if (IS_ERR(panel->reset))
58662306a36Sopenharmony_ci		return PTR_ERR(panel->reset);
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	memset(&bl_props, 0, sizeof(bl_props));
58962306a36Sopenharmony_ci	bl_props.type = BACKLIGHT_RAW;
59062306a36Sopenharmony_ci	bl_props.brightness = 255;
59162306a36Sopenharmony_ci	bl_props.max_brightness = 255;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	panel->backlight = devm_backlight_device_register(dev, dev_name(dev),
59462306a36Sopenharmony_ci							  dev, dsi, &rad_bl_ops,
59562306a36Sopenharmony_ci							  &bl_props);
59662306a36Sopenharmony_ci	if (IS_ERR(panel->backlight)) {
59762306a36Sopenharmony_ci		ret = PTR_ERR(panel->backlight);
59862306a36Sopenharmony_ci		dev_err(dev, "Failed to register backlight (%d)\n", ret);
59962306a36Sopenharmony_ci		return ret;
60062306a36Sopenharmony_ci	}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	ret = rad_init_regulators(panel);
60362306a36Sopenharmony_ci	if (ret)
60462306a36Sopenharmony_ci		return ret;
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	drm_panel_init(&panel->panel, dev, &rad_panel_funcs,
60762306a36Sopenharmony_ci		       DRM_MODE_CONNECTOR_DSI);
60862306a36Sopenharmony_ci	dev_set_drvdata(dev, panel);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	drm_panel_add(&panel->panel);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	ret = mipi_dsi_attach(dsi);
61362306a36Sopenharmony_ci	if (ret)
61462306a36Sopenharmony_ci		drm_panel_remove(&panel->panel);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	return ret;
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_cistatic void rad_panel_remove(struct mipi_dsi_device *dsi)
62062306a36Sopenharmony_ci{
62162306a36Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
62262306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
62362306a36Sopenharmony_ci	int ret;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	ret = mipi_dsi_detach(dsi);
62662306a36Sopenharmony_ci	if (ret)
62762306a36Sopenharmony_ci		dev_err(dev, "Failed to detach from host (%d)\n", ret);
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	drm_panel_remove(&rad->panel);
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic void rad_panel_shutdown(struct mipi_dsi_device *dsi)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	rad_panel_disable(&rad->panel);
63762306a36Sopenharmony_ci	rad_panel_unprepare(&rad->panel);
63862306a36Sopenharmony_ci}
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_cistatic const struct of_device_id rad_of_match[] = {
64162306a36Sopenharmony_ci	{ .compatible = "raydium,rm67191", },
64262306a36Sopenharmony_ci	{ /* sentinel */ }
64362306a36Sopenharmony_ci};
64462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rad_of_match);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_cistatic struct mipi_dsi_driver rad_panel_driver = {
64762306a36Sopenharmony_ci	.driver = {
64862306a36Sopenharmony_ci		.name = "panel-raydium-rm67191",
64962306a36Sopenharmony_ci		.of_match_table = rad_of_match,
65062306a36Sopenharmony_ci	},
65162306a36Sopenharmony_ci	.probe = rad_panel_probe,
65262306a36Sopenharmony_ci	.remove = rad_panel_remove,
65362306a36Sopenharmony_ci	.shutdown = rad_panel_shutdown,
65462306a36Sopenharmony_ci};
65562306a36Sopenharmony_cimodule_mipi_dsi_driver(rad_panel_driver);
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ciMODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>");
65862306a36Sopenharmony_ciMODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel");
65962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
660