18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Raydium RM67191 MIPI-DSI panel driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2019 NXP
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/backlight.h>
98c2ecf20Sopenharmony_ci#include <linux/delay.h>
108c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/of.h>
138c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <video/mipi_display.h>
168c2ecf20Sopenharmony_ci#include <video/of_videomode.h>
178c2ecf20Sopenharmony_ci#include <video/videomode.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h>
208c2ecf20Sopenharmony_ci#include <drm/drm_mipi_dsi.h>
218c2ecf20Sopenharmony_ci#include <drm/drm_panel.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/* Panel specific color-format bits */
248c2ecf20Sopenharmony_ci#define COL_FMT_16BPP 0x55
258c2ecf20Sopenharmony_ci#define COL_FMT_18BPP 0x66
268c2ecf20Sopenharmony_ci#define COL_FMT_24BPP 0x77
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci/* Write Manufacture Command Set Control */
298c2ecf20Sopenharmony_ci#define WRMAUCCTR 0xFE
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* Manufacturer Command Set pages (CMD2) */
328c2ecf20Sopenharmony_cistruct cmd_set_entry {
338c2ecf20Sopenharmony_ci	u8 cmd;
348c2ecf20Sopenharmony_ci	u8 param;
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/*
388c2ecf20Sopenharmony_ci * There is no description in the Reference Manual about these commands.
398c2ecf20Sopenharmony_ci * We received them from vendor, so just use them as is.
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_cistatic const struct cmd_set_entry manufacturer_cmd_set[] = {
428c2ecf20Sopenharmony_ci	{0xFE, 0x0B},
438c2ecf20Sopenharmony_ci	{0x28, 0x40},
448c2ecf20Sopenharmony_ci	{0x29, 0x4F},
458c2ecf20Sopenharmony_ci	{0xFE, 0x0E},
468c2ecf20Sopenharmony_ci	{0x4B, 0x00},
478c2ecf20Sopenharmony_ci	{0x4C, 0x0F},
488c2ecf20Sopenharmony_ci	{0x4D, 0x20},
498c2ecf20Sopenharmony_ci	{0x4E, 0x40},
508c2ecf20Sopenharmony_ci	{0x4F, 0x60},
518c2ecf20Sopenharmony_ci	{0x50, 0xA0},
528c2ecf20Sopenharmony_ci	{0x51, 0xC0},
538c2ecf20Sopenharmony_ci	{0x52, 0xE0},
548c2ecf20Sopenharmony_ci	{0x53, 0xFF},
558c2ecf20Sopenharmony_ci	{0xFE, 0x0D},
568c2ecf20Sopenharmony_ci	{0x18, 0x08},
578c2ecf20Sopenharmony_ci	{0x42, 0x00},
588c2ecf20Sopenharmony_ci	{0x08, 0x41},
598c2ecf20Sopenharmony_ci	{0x46, 0x02},
608c2ecf20Sopenharmony_ci	{0x72, 0x09},
618c2ecf20Sopenharmony_ci	{0xFE, 0x0A},
628c2ecf20Sopenharmony_ci	{0x24, 0x17},
638c2ecf20Sopenharmony_ci	{0x04, 0x07},
648c2ecf20Sopenharmony_ci	{0x1A, 0x0C},
658c2ecf20Sopenharmony_ci	{0x0F, 0x44},
668c2ecf20Sopenharmony_ci	{0xFE, 0x04},
678c2ecf20Sopenharmony_ci	{0x00, 0x0C},
688c2ecf20Sopenharmony_ci	{0x05, 0x08},
698c2ecf20Sopenharmony_ci	{0x06, 0x08},
708c2ecf20Sopenharmony_ci	{0x08, 0x08},
718c2ecf20Sopenharmony_ci	{0x09, 0x08},
728c2ecf20Sopenharmony_ci	{0x0A, 0xE6},
738c2ecf20Sopenharmony_ci	{0x0B, 0x8C},
748c2ecf20Sopenharmony_ci	{0x1A, 0x12},
758c2ecf20Sopenharmony_ci	{0x1E, 0xE0},
768c2ecf20Sopenharmony_ci	{0x29, 0x93},
778c2ecf20Sopenharmony_ci	{0x2A, 0x93},
788c2ecf20Sopenharmony_ci	{0x2F, 0x02},
798c2ecf20Sopenharmony_ci	{0x31, 0x02},
808c2ecf20Sopenharmony_ci	{0x33, 0x05},
818c2ecf20Sopenharmony_ci	{0x37, 0x2D},
828c2ecf20Sopenharmony_ci	{0x38, 0x2D},
838c2ecf20Sopenharmony_ci	{0x3A, 0x1E},
848c2ecf20Sopenharmony_ci	{0x3B, 0x1E},
858c2ecf20Sopenharmony_ci	{0x3D, 0x27},
868c2ecf20Sopenharmony_ci	{0x3F, 0x80},
878c2ecf20Sopenharmony_ci	{0x40, 0x40},
888c2ecf20Sopenharmony_ci	{0x41, 0xE0},
898c2ecf20Sopenharmony_ci	{0x4F, 0x2F},
908c2ecf20Sopenharmony_ci	{0x50, 0x1E},
918c2ecf20Sopenharmony_ci	{0xFE, 0x06},
928c2ecf20Sopenharmony_ci	{0x00, 0xCC},
938c2ecf20Sopenharmony_ci	{0x05, 0x05},
948c2ecf20Sopenharmony_ci	{0x07, 0xA2},
958c2ecf20Sopenharmony_ci	{0x08, 0xCC},
968c2ecf20Sopenharmony_ci	{0x0D, 0x03},
978c2ecf20Sopenharmony_ci	{0x0F, 0xA2},
988c2ecf20Sopenharmony_ci	{0x32, 0xCC},
998c2ecf20Sopenharmony_ci	{0x37, 0x05},
1008c2ecf20Sopenharmony_ci	{0x39, 0x83},
1018c2ecf20Sopenharmony_ci	{0x3A, 0xCC},
1028c2ecf20Sopenharmony_ci	{0x41, 0x04},
1038c2ecf20Sopenharmony_ci	{0x43, 0x83},
1048c2ecf20Sopenharmony_ci	{0x44, 0xCC},
1058c2ecf20Sopenharmony_ci	{0x49, 0x05},
1068c2ecf20Sopenharmony_ci	{0x4B, 0xA2},
1078c2ecf20Sopenharmony_ci	{0x4C, 0xCC},
1088c2ecf20Sopenharmony_ci	{0x51, 0x03},
1098c2ecf20Sopenharmony_ci	{0x53, 0xA2},
1108c2ecf20Sopenharmony_ci	{0x75, 0xCC},
1118c2ecf20Sopenharmony_ci	{0x7A, 0x03},
1128c2ecf20Sopenharmony_ci	{0x7C, 0x83},
1138c2ecf20Sopenharmony_ci	{0x7D, 0xCC},
1148c2ecf20Sopenharmony_ci	{0x82, 0x02},
1158c2ecf20Sopenharmony_ci	{0x84, 0x83},
1168c2ecf20Sopenharmony_ci	{0x85, 0xEC},
1178c2ecf20Sopenharmony_ci	{0x86, 0x0F},
1188c2ecf20Sopenharmony_ci	{0x87, 0xFF},
1198c2ecf20Sopenharmony_ci	{0x88, 0x00},
1208c2ecf20Sopenharmony_ci	{0x8A, 0x02},
1218c2ecf20Sopenharmony_ci	{0x8C, 0xA2},
1228c2ecf20Sopenharmony_ci	{0x8D, 0xEA},
1238c2ecf20Sopenharmony_ci	{0x8E, 0x01},
1248c2ecf20Sopenharmony_ci	{0x8F, 0xE8},
1258c2ecf20Sopenharmony_ci	{0xFE, 0x06},
1268c2ecf20Sopenharmony_ci	{0x90, 0x0A},
1278c2ecf20Sopenharmony_ci	{0x92, 0x06},
1288c2ecf20Sopenharmony_ci	{0x93, 0xA0},
1298c2ecf20Sopenharmony_ci	{0x94, 0xA8},
1308c2ecf20Sopenharmony_ci	{0x95, 0xEC},
1318c2ecf20Sopenharmony_ci	{0x96, 0x0F},
1328c2ecf20Sopenharmony_ci	{0x97, 0xFF},
1338c2ecf20Sopenharmony_ci	{0x98, 0x00},
1348c2ecf20Sopenharmony_ci	{0x9A, 0x02},
1358c2ecf20Sopenharmony_ci	{0x9C, 0xA2},
1368c2ecf20Sopenharmony_ci	{0xAC, 0x04},
1378c2ecf20Sopenharmony_ci	{0xFE, 0x06},
1388c2ecf20Sopenharmony_ci	{0xB1, 0x12},
1398c2ecf20Sopenharmony_ci	{0xB2, 0x17},
1408c2ecf20Sopenharmony_ci	{0xB3, 0x17},
1418c2ecf20Sopenharmony_ci	{0xB4, 0x17},
1428c2ecf20Sopenharmony_ci	{0xB5, 0x17},
1438c2ecf20Sopenharmony_ci	{0xB6, 0x11},
1448c2ecf20Sopenharmony_ci	{0xB7, 0x08},
1458c2ecf20Sopenharmony_ci	{0xB8, 0x09},
1468c2ecf20Sopenharmony_ci	{0xB9, 0x06},
1478c2ecf20Sopenharmony_ci	{0xBA, 0x07},
1488c2ecf20Sopenharmony_ci	{0xBB, 0x17},
1498c2ecf20Sopenharmony_ci	{0xBC, 0x17},
1508c2ecf20Sopenharmony_ci	{0xBD, 0x17},
1518c2ecf20Sopenharmony_ci	{0xBE, 0x17},
1528c2ecf20Sopenharmony_ci	{0xBF, 0x17},
1538c2ecf20Sopenharmony_ci	{0xC0, 0x17},
1548c2ecf20Sopenharmony_ci	{0xC1, 0x17},
1558c2ecf20Sopenharmony_ci	{0xC2, 0x17},
1568c2ecf20Sopenharmony_ci	{0xC3, 0x17},
1578c2ecf20Sopenharmony_ci	{0xC4, 0x0F},
1588c2ecf20Sopenharmony_ci	{0xC5, 0x0E},
1598c2ecf20Sopenharmony_ci	{0xC6, 0x00},
1608c2ecf20Sopenharmony_ci	{0xC7, 0x01},
1618c2ecf20Sopenharmony_ci	{0xC8, 0x10},
1628c2ecf20Sopenharmony_ci	{0xFE, 0x06},
1638c2ecf20Sopenharmony_ci	{0x95, 0xEC},
1648c2ecf20Sopenharmony_ci	{0x8D, 0xEE},
1658c2ecf20Sopenharmony_ci	{0x44, 0xEC},
1668c2ecf20Sopenharmony_ci	{0x4C, 0xEC},
1678c2ecf20Sopenharmony_ci	{0x32, 0xEC},
1688c2ecf20Sopenharmony_ci	{0x3A, 0xEC},
1698c2ecf20Sopenharmony_ci	{0x7D, 0xEC},
1708c2ecf20Sopenharmony_ci	{0x75, 0xEC},
1718c2ecf20Sopenharmony_ci	{0x00, 0xEC},
1728c2ecf20Sopenharmony_ci	{0x08, 0xEC},
1738c2ecf20Sopenharmony_ci	{0x85, 0xEC},
1748c2ecf20Sopenharmony_ci	{0xA6, 0x21},
1758c2ecf20Sopenharmony_ci	{0xA7, 0x05},
1768c2ecf20Sopenharmony_ci	{0xA9, 0x06},
1778c2ecf20Sopenharmony_ci	{0x82, 0x06},
1788c2ecf20Sopenharmony_ci	{0x41, 0x06},
1798c2ecf20Sopenharmony_ci	{0x7A, 0x07},
1808c2ecf20Sopenharmony_ci	{0x37, 0x07},
1818c2ecf20Sopenharmony_ci	{0x05, 0x06},
1828c2ecf20Sopenharmony_ci	{0x49, 0x06},
1838c2ecf20Sopenharmony_ci	{0x0D, 0x04},
1848c2ecf20Sopenharmony_ci	{0x51, 0x04},
1858c2ecf20Sopenharmony_ci};
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic const u32 rad_bus_formats[] = {
1888c2ecf20Sopenharmony_ci	MEDIA_BUS_FMT_RGB888_1X24,
1898c2ecf20Sopenharmony_ci	MEDIA_BUS_FMT_RGB666_1X18,
1908c2ecf20Sopenharmony_ci	MEDIA_BUS_FMT_RGB565_1X16,
1918c2ecf20Sopenharmony_ci};
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic const u32 rad_bus_flags = DRM_BUS_FLAG_DE_LOW |
1948c2ecf20Sopenharmony_ci				 DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistruct rad_panel {
1978c2ecf20Sopenharmony_ci	struct drm_panel panel;
1988c2ecf20Sopenharmony_ci	struct mipi_dsi_device *dsi;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	struct gpio_desc *reset;
2018c2ecf20Sopenharmony_ci	struct backlight_device *backlight;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	struct regulator_bulk_data *supplies;
2048c2ecf20Sopenharmony_ci	unsigned int num_supplies;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	bool prepared;
2078c2ecf20Sopenharmony_ci	bool enabled;
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic const struct drm_display_mode default_mode = {
2118c2ecf20Sopenharmony_ci	.clock = 132000,
2128c2ecf20Sopenharmony_ci	.hdisplay = 1080,
2138c2ecf20Sopenharmony_ci	.hsync_start = 1080 + 20,
2148c2ecf20Sopenharmony_ci	.hsync_end = 1080 + 20 + 2,
2158c2ecf20Sopenharmony_ci	.htotal = 1080 + 20 + 2 + 34,
2168c2ecf20Sopenharmony_ci	.vdisplay = 1920,
2178c2ecf20Sopenharmony_ci	.vsync_start = 1920 + 10,
2188c2ecf20Sopenharmony_ci	.vsync_end = 1920 + 10 + 2,
2198c2ecf20Sopenharmony_ci	.vtotal = 1920 + 10 + 2 + 4,
2208c2ecf20Sopenharmony_ci	.width_mm = 68,
2218c2ecf20Sopenharmony_ci	.height_mm = 121,
2228c2ecf20Sopenharmony_ci	.flags = DRM_MODE_FLAG_NHSYNC |
2238c2ecf20Sopenharmony_ci		 DRM_MODE_FLAG_NVSYNC,
2248c2ecf20Sopenharmony_ci};
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_cistatic inline struct rad_panel *to_rad_panel(struct drm_panel *panel)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	return container_of(panel, struct rad_panel, panel);
2298c2ecf20Sopenharmony_ci}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_cistatic int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi)
2328c2ecf20Sopenharmony_ci{
2338c2ecf20Sopenharmony_ci	size_t i;
2348c2ecf20Sopenharmony_ci	size_t count = ARRAY_SIZE(manufacturer_cmd_set);
2358c2ecf20Sopenharmony_ci	int ret = 0;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	for (i = 0; i < count; i++) {
2388c2ecf20Sopenharmony_ci		const struct cmd_set_entry *entry = &manufacturer_cmd_set[i];
2398c2ecf20Sopenharmony_ci		u8 buffer[2] = { entry->cmd, entry->param };
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer));
2428c2ecf20Sopenharmony_ci		if (ret < 0)
2438c2ecf20Sopenharmony_ci			return ret;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	return ret;
2478c2ecf20Sopenharmony_ci};
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cistatic int color_format_from_dsi_format(enum mipi_dsi_pixel_format format)
2508c2ecf20Sopenharmony_ci{
2518c2ecf20Sopenharmony_ci	switch (format) {
2528c2ecf20Sopenharmony_ci	case MIPI_DSI_FMT_RGB565:
2538c2ecf20Sopenharmony_ci		return COL_FMT_16BPP;
2548c2ecf20Sopenharmony_ci	case MIPI_DSI_FMT_RGB666:
2558c2ecf20Sopenharmony_ci	case MIPI_DSI_FMT_RGB666_PACKED:
2568c2ecf20Sopenharmony_ci		return COL_FMT_18BPP;
2578c2ecf20Sopenharmony_ci	case MIPI_DSI_FMT_RGB888:
2588c2ecf20Sopenharmony_ci		return COL_FMT_24BPP;
2598c2ecf20Sopenharmony_ci	default:
2608c2ecf20Sopenharmony_ci		return COL_FMT_24BPP; /* for backward compatibility */
2618c2ecf20Sopenharmony_ci	}
2628c2ecf20Sopenharmony_ci};
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_cistatic int rad_panel_prepare(struct drm_panel *panel)
2658c2ecf20Sopenharmony_ci{
2668c2ecf20Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
2678c2ecf20Sopenharmony_ci	int ret;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	if (rad->prepared)
2708c2ecf20Sopenharmony_ci		return 0;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	ret = regulator_bulk_enable(rad->num_supplies, rad->supplies);
2738c2ecf20Sopenharmony_ci	if (ret)
2748c2ecf20Sopenharmony_ci		return ret;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	if (rad->reset) {
2778c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 1);
2788c2ecf20Sopenharmony_ci		usleep_range(3000, 5000);
2798c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 0);
2808c2ecf20Sopenharmony_ci		usleep_range(18000, 20000);
2818c2ecf20Sopenharmony_ci	}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	rad->prepared = true;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return 0;
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistatic int rad_panel_unprepare(struct drm_panel *panel)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
2918c2ecf20Sopenharmony_ci	int ret;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	if (!rad->prepared)
2948c2ecf20Sopenharmony_ci		return 0;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	/*
2978c2ecf20Sopenharmony_ci	 * Right after asserting the reset, we need to release it, so that the
2988c2ecf20Sopenharmony_ci	 * touch driver can have an active connection with the touch controller
2998c2ecf20Sopenharmony_ci	 * even after the display is turned off.
3008c2ecf20Sopenharmony_ci	 */
3018c2ecf20Sopenharmony_ci	if (rad->reset) {
3028c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 1);
3038c2ecf20Sopenharmony_ci		usleep_range(15000, 17000);
3048c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(rad->reset, 0);
3058c2ecf20Sopenharmony_ci	}
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci	ret = regulator_bulk_disable(rad->num_supplies, rad->supplies);
3088c2ecf20Sopenharmony_ci	if (ret)
3098c2ecf20Sopenharmony_ci		return ret;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	rad->prepared = false;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	return 0;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_cistatic int rad_panel_enable(struct drm_panel *panel)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
3198c2ecf20Sopenharmony_ci	struct mipi_dsi_device *dsi = rad->dsi;
3208c2ecf20Sopenharmony_ci	struct device *dev = &dsi->dev;
3218c2ecf20Sopenharmony_ci	int color_format = color_format_from_dsi_format(dsi->format);
3228c2ecf20Sopenharmony_ci	int ret;
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	if (rad->enabled)
3258c2ecf20Sopenharmony_ci		return 0;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	ret = rad_panel_push_cmd_list(dsi);
3308c2ecf20Sopenharmony_ci	if (ret < 0) {
3318c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to send MCS (%d)\n", ret);
3328c2ecf20Sopenharmony_ci		goto fail;
3338c2ecf20Sopenharmony_ci	}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	/* Select User Command Set table (CMD1) */
3368c2ecf20Sopenharmony_ci	ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2);
3378c2ecf20Sopenharmony_ci	if (ret < 0)
3388c2ecf20Sopenharmony_ci		goto fail;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	/* Software reset */
3418c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_soft_reset(dsi);
3428c2ecf20Sopenharmony_ci	if (ret < 0) {
3438c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to do Software Reset (%d)\n", ret);
3448c2ecf20Sopenharmony_ci		goto fail;
3458c2ecf20Sopenharmony_ci	}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	usleep_range(15000, 17000);
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	/* Set DSI mode */
3508c2ecf20Sopenharmony_ci	ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2);
3518c2ecf20Sopenharmony_ci	if (ret < 0) {
3528c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
3538c2ecf20Sopenharmony_ci		goto fail;
3548c2ecf20Sopenharmony_ci	}
3558c2ecf20Sopenharmony_ci	/* Set tear ON */
3568c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
3578c2ecf20Sopenharmony_ci	if (ret < 0) {
3588c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set tear ON (%d)\n", ret);
3598c2ecf20Sopenharmony_ci		goto fail;
3608c2ecf20Sopenharmony_ci	}
3618c2ecf20Sopenharmony_ci	/* Set tear scanline */
3628c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x380);
3638c2ecf20Sopenharmony_ci	if (ret < 0) {
3648c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set tear scanline (%d)\n", ret);
3658c2ecf20Sopenharmony_ci		goto fail;
3668c2ecf20Sopenharmony_ci	}
3678c2ecf20Sopenharmony_ci	/* Set pixel format */
3688c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format);
3698c2ecf20Sopenharmony_ci	dev_dbg(dev, "Interface color format set to 0x%x\n", color_format);
3708c2ecf20Sopenharmony_ci	if (ret < 0) {
3718c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set pixel format (%d)\n", ret);
3728c2ecf20Sopenharmony_ci		goto fail;
3738c2ecf20Sopenharmony_ci	}
3748c2ecf20Sopenharmony_ci	/* Exit sleep mode */
3758c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
3768c2ecf20Sopenharmony_ci	if (ret < 0) {
3778c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to exit sleep mode (%d)\n", ret);
3788c2ecf20Sopenharmony_ci		goto fail;
3798c2ecf20Sopenharmony_ci	}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	usleep_range(5000, 7000);
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_on(dsi);
3848c2ecf20Sopenharmony_ci	if (ret < 0) {
3858c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set display ON (%d)\n", ret);
3868c2ecf20Sopenharmony_ci		goto fail;
3878c2ecf20Sopenharmony_ci	}
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	backlight_enable(rad->backlight);
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	rad->enabled = true;
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	return 0;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_cifail:
3968c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(rad->reset, 1);
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	return ret;
3998c2ecf20Sopenharmony_ci}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_cistatic int rad_panel_disable(struct drm_panel *panel)
4028c2ecf20Sopenharmony_ci{
4038c2ecf20Sopenharmony_ci	struct rad_panel *rad = to_rad_panel(panel);
4048c2ecf20Sopenharmony_ci	struct mipi_dsi_device *dsi = rad->dsi;
4058c2ecf20Sopenharmony_ci	struct device *dev = &dsi->dev;
4068c2ecf20Sopenharmony_ci	int ret;
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	if (!rad->enabled)
4098c2ecf20Sopenharmony_ci		return 0;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	backlight_disable(rad->backlight);
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	usleep_range(10000, 12000);
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_off(dsi);
4188c2ecf20Sopenharmony_ci	if (ret < 0) {
4198c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to set display OFF (%d)\n", ret);
4208c2ecf20Sopenharmony_ci		return ret;
4218c2ecf20Sopenharmony_ci	}
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	usleep_range(5000, 10000);
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
4268c2ecf20Sopenharmony_ci	if (ret < 0) {
4278c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to enter sleep mode (%d)\n", ret);
4288c2ecf20Sopenharmony_ci		return ret;
4298c2ecf20Sopenharmony_ci	}
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	rad->enabled = false;
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	return 0;
4348c2ecf20Sopenharmony_ci}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_cistatic int rad_panel_get_modes(struct drm_panel *panel,
4378c2ecf20Sopenharmony_ci			       struct drm_connector *connector)
4388c2ecf20Sopenharmony_ci{
4398c2ecf20Sopenharmony_ci	struct drm_display_mode *mode;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &default_mode);
4428c2ecf20Sopenharmony_ci	if (!mode) {
4438c2ecf20Sopenharmony_ci		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
4448c2ecf20Sopenharmony_ci			default_mode.hdisplay, default_mode.vdisplay,
4458c2ecf20Sopenharmony_ci			drm_mode_vrefresh(&default_mode));
4468c2ecf20Sopenharmony_ci		return -ENOMEM;
4478c2ecf20Sopenharmony_ci	}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	drm_mode_set_name(mode);
4508c2ecf20Sopenharmony_ci	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
4518c2ecf20Sopenharmony_ci	drm_mode_probed_add(connector, mode);
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	connector->display_info.width_mm = mode->width_mm;
4548c2ecf20Sopenharmony_ci	connector->display_info.height_mm = mode->height_mm;
4558c2ecf20Sopenharmony_ci	connector->display_info.bus_flags = rad_bus_flags;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	drm_display_info_set_bus_formats(&connector->display_info,
4588c2ecf20Sopenharmony_ci					 rad_bus_formats,
4598c2ecf20Sopenharmony_ci					 ARRAY_SIZE(rad_bus_formats));
4608c2ecf20Sopenharmony_ci	return 1;
4618c2ecf20Sopenharmony_ci}
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cistatic int rad_bl_get_brightness(struct backlight_device *bl)
4648c2ecf20Sopenharmony_ci{
4658c2ecf20Sopenharmony_ci	struct mipi_dsi_device *dsi = bl_get_data(bl);
4668c2ecf20Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
4678c2ecf20Sopenharmony_ci	u16 brightness;
4688c2ecf20Sopenharmony_ci	int ret;
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_ci	if (!rad->prepared)
4718c2ecf20Sopenharmony_ci		return 0;
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);
4768c2ecf20Sopenharmony_ci	if (ret < 0)
4778c2ecf20Sopenharmony_ci		return ret;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	bl->props.brightness = brightness;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	return brightness & 0xff;
4828c2ecf20Sopenharmony_ci}
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_cistatic int rad_bl_update_status(struct backlight_device *bl)
4858c2ecf20Sopenharmony_ci{
4868c2ecf20Sopenharmony_ci	struct mipi_dsi_device *dsi = bl_get_data(bl);
4878c2ecf20Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
4888c2ecf20Sopenharmony_ci	int ret = 0;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	if (!rad->prepared)
4918c2ecf20Sopenharmony_ci		return 0;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci	dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness);
4968c2ecf20Sopenharmony_ci	if (ret < 0)
4978c2ecf20Sopenharmony_ci		return ret;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	return 0;
5008c2ecf20Sopenharmony_ci}
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_cistatic const struct backlight_ops rad_bl_ops = {
5038c2ecf20Sopenharmony_ci	.update_status = rad_bl_update_status,
5048c2ecf20Sopenharmony_ci	.get_brightness = rad_bl_get_brightness,
5058c2ecf20Sopenharmony_ci};
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_cistatic const struct drm_panel_funcs rad_panel_funcs = {
5088c2ecf20Sopenharmony_ci	.prepare = rad_panel_prepare,
5098c2ecf20Sopenharmony_ci	.unprepare = rad_panel_unprepare,
5108c2ecf20Sopenharmony_ci	.enable = rad_panel_enable,
5118c2ecf20Sopenharmony_ci	.disable = rad_panel_disable,
5128c2ecf20Sopenharmony_ci	.get_modes = rad_panel_get_modes,
5138c2ecf20Sopenharmony_ci};
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_cistatic const char * const rad_supply_names[] = {
5168c2ecf20Sopenharmony_ci	"v3p3",
5178c2ecf20Sopenharmony_ci	"v1p8",
5188c2ecf20Sopenharmony_ci};
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_cistatic int rad_init_regulators(struct rad_panel *rad)
5218c2ecf20Sopenharmony_ci{
5228c2ecf20Sopenharmony_ci	struct device *dev = &rad->dsi->dev;
5238c2ecf20Sopenharmony_ci	int i;
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_ci	rad->num_supplies = ARRAY_SIZE(rad_supply_names);
5268c2ecf20Sopenharmony_ci	rad->supplies = devm_kcalloc(dev, rad->num_supplies,
5278c2ecf20Sopenharmony_ci				     sizeof(*rad->supplies), GFP_KERNEL);
5288c2ecf20Sopenharmony_ci	if (!rad->supplies)
5298c2ecf20Sopenharmony_ci		return -ENOMEM;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	for (i = 0; i < rad->num_supplies; i++)
5328c2ecf20Sopenharmony_ci		rad->supplies[i].supply = rad_supply_names[i];
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies);
5358c2ecf20Sopenharmony_ci};
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_cistatic int rad_panel_probe(struct mipi_dsi_device *dsi)
5388c2ecf20Sopenharmony_ci{
5398c2ecf20Sopenharmony_ci	struct device *dev = &dsi->dev;
5408c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
5418c2ecf20Sopenharmony_ci	struct rad_panel *panel;
5428c2ecf20Sopenharmony_ci	struct backlight_properties bl_props;
5438c2ecf20Sopenharmony_ci	int ret;
5448c2ecf20Sopenharmony_ci	u32 video_mode;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);
5478c2ecf20Sopenharmony_ci	if (!panel)
5488c2ecf20Sopenharmony_ci		return -ENOMEM;
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	mipi_dsi_set_drvdata(dsi, panel);
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	panel->dsi = dsi;
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	dsi->format = MIPI_DSI_FMT_RGB888;
5558c2ecf20Sopenharmony_ci	dsi->mode_flags =  MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	ret = of_property_read_u32(np, "video-mode", &video_mode);
5588c2ecf20Sopenharmony_ci	if (!ret) {
5598c2ecf20Sopenharmony_ci		switch (video_mode) {
5608c2ecf20Sopenharmony_ci		case 0:
5618c2ecf20Sopenharmony_ci			/* burst mode */
5628c2ecf20Sopenharmony_ci			dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST;
5638c2ecf20Sopenharmony_ci			break;
5648c2ecf20Sopenharmony_ci		case 1:
5658c2ecf20Sopenharmony_ci			/* non-burst mode with sync event */
5668c2ecf20Sopenharmony_ci			break;
5678c2ecf20Sopenharmony_ci		case 2:
5688c2ecf20Sopenharmony_ci			/* non-burst mode with sync pulse */
5698c2ecf20Sopenharmony_ci			dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
5708c2ecf20Sopenharmony_ci			break;
5718c2ecf20Sopenharmony_ci		default:
5728c2ecf20Sopenharmony_ci			dev_warn(dev, "invalid video mode %d\n", video_mode);
5738c2ecf20Sopenharmony_ci			break;
5748c2ecf20Sopenharmony_ci		}
5758c2ecf20Sopenharmony_ci	}
5768c2ecf20Sopenharmony_ci
5778c2ecf20Sopenharmony_ci	ret = of_property_read_u32(np, "dsi-lanes", &dsi->lanes);
5788c2ecf20Sopenharmony_ci	if (ret) {
5798c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get dsi-lanes property (%d)\n", ret);
5808c2ecf20Sopenharmony_ci		return ret;
5818c2ecf20Sopenharmony_ci	}
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci	panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
5848c2ecf20Sopenharmony_ci	if (IS_ERR(panel->reset))
5858c2ecf20Sopenharmony_ci		return PTR_ERR(panel->reset);
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci	memset(&bl_props, 0, sizeof(bl_props));
5888c2ecf20Sopenharmony_ci	bl_props.type = BACKLIGHT_RAW;
5898c2ecf20Sopenharmony_ci	bl_props.brightness = 255;
5908c2ecf20Sopenharmony_ci	bl_props.max_brightness = 255;
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	panel->backlight = devm_backlight_device_register(dev, dev_name(dev),
5938c2ecf20Sopenharmony_ci							  dev, dsi, &rad_bl_ops,
5948c2ecf20Sopenharmony_ci							  &bl_props);
5958c2ecf20Sopenharmony_ci	if (IS_ERR(panel->backlight)) {
5968c2ecf20Sopenharmony_ci		ret = PTR_ERR(panel->backlight);
5978c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to register backlight (%d)\n", ret);
5988c2ecf20Sopenharmony_ci		return ret;
5998c2ecf20Sopenharmony_ci	}
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	ret = rad_init_regulators(panel);
6028c2ecf20Sopenharmony_ci	if (ret)
6038c2ecf20Sopenharmony_ci		return ret;
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci	drm_panel_init(&panel->panel, dev, &rad_panel_funcs,
6068c2ecf20Sopenharmony_ci		       DRM_MODE_CONNECTOR_DSI);
6078c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, panel);
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	drm_panel_add(&panel->panel);
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci	ret = mipi_dsi_attach(dsi);
6128c2ecf20Sopenharmony_ci	if (ret)
6138c2ecf20Sopenharmony_ci		drm_panel_remove(&panel->panel);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	return ret;
6168c2ecf20Sopenharmony_ci}
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_cistatic int rad_panel_remove(struct mipi_dsi_device *dsi)
6198c2ecf20Sopenharmony_ci{
6208c2ecf20Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
6218c2ecf20Sopenharmony_ci	struct device *dev = &dsi->dev;
6228c2ecf20Sopenharmony_ci	int ret;
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci	ret = mipi_dsi_detach(dsi);
6258c2ecf20Sopenharmony_ci	if (ret)
6268c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to detach from host (%d)\n", ret);
6278c2ecf20Sopenharmony_ci
6288c2ecf20Sopenharmony_ci	drm_panel_remove(&rad->panel);
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	return 0;
6318c2ecf20Sopenharmony_ci}
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_cistatic void rad_panel_shutdown(struct mipi_dsi_device *dsi)
6348c2ecf20Sopenharmony_ci{
6358c2ecf20Sopenharmony_ci	struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci	rad_panel_disable(&rad->panel);
6388c2ecf20Sopenharmony_ci	rad_panel_unprepare(&rad->panel);
6398c2ecf20Sopenharmony_ci}
6408c2ecf20Sopenharmony_ci
6418c2ecf20Sopenharmony_cistatic const struct of_device_id rad_of_match[] = {
6428c2ecf20Sopenharmony_ci	{ .compatible = "raydium,rm67191", },
6438c2ecf20Sopenharmony_ci	{ /* sentinel */ }
6448c2ecf20Sopenharmony_ci};
6458c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rad_of_match);
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_cistatic struct mipi_dsi_driver rad_panel_driver = {
6488c2ecf20Sopenharmony_ci	.driver = {
6498c2ecf20Sopenharmony_ci		.name = "panel-raydium-rm67191",
6508c2ecf20Sopenharmony_ci		.of_match_table = rad_of_match,
6518c2ecf20Sopenharmony_ci	},
6528c2ecf20Sopenharmony_ci	.probe = rad_panel_probe,
6538c2ecf20Sopenharmony_ci	.remove = rad_panel_remove,
6548c2ecf20Sopenharmony_ci	.shutdown = rad_panel_shutdown,
6558c2ecf20Sopenharmony_ci};
6568c2ecf20Sopenharmony_cimodule_mipi_dsi_driver(rad_panel_driver);
6578c2ecf20Sopenharmony_ci
6588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>");
6598c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel");
6608c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
661