// SPDX-License-Identifier: GPL-2.0
/*

This driver is written based on panel-ilitek-ili9881c.c and 
panel-ilitek-ili9805.c

 */

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>

#include <linux/gpio/consumer.h>
//#include <linux/regulator/consumer.h>

#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>

#include <video/mipi_display.h>

#define ILI9805_INSTR(_delay, ...) { \
		.delay = (_delay), \
		.len = sizeof((u8[]) {__VA_ARGS__}), \
		.data = (u8[]){__VA_ARGS__} \
	}

struct ili9805_instr {
	size_t len;
	const u8 *data;
	u32 delay;
};

struct ili9805_desc {
	const struct ili9805_instr *init;
	const size_t init_length;
	const struct drm_display_mode *mode;
	const unsigned long mode_flags;
};

struct ili9805 {
	struct drm_panel	panel;
	struct mipi_dsi_device	*dsi;
	const struct ili9805_desc	*desc;

	//struct regulator	*power;
	struct gpio_desc	*reset;

	enum drm_panel_orientation	orientation;
};



static const struct ili9805_instr msi_ili9805_init[] = {
	//the delay shall be handled after the data write
	ILI9805_INSTR(0, 0xff, 0xff, 0x98, 0x05),
	ILI9805_INSTR(0, 0xFD, 0x0F, 0x10, 0x66, 0x00),
	ILI9805_INSTR(0, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x00,
		      0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00),
	ILI9805_INSTR(0, 0xB8, 0x62),
	ILI9805_INSTR(0, 0xF1, 0x00),
	ILI9805_INSTR(0, 0xF2, 0x00, 0x58, 0x40),
	ILI9805_INSTR(0, 0xF3, 0x60, 0x83, 0x04),
	ILI9805_INSTR(0, 0xFC, 0x04, 0x0F, 0x01),
	ILI9805_INSTR(0, 0xEB, 0x08, 0x0F),  //0x08 = video mode, 0x00 = command mode
	ILI9805_INSTR(0, 0xe0, 0x00, 0x06, 0x06, 0x0d, 0x0c, 0x18, 0x0c, 0x0c, 0x00,
		      0x03, 0x02, 0x09, 0x06, 0x2c, 0x26, 0x0a),
	ILI9805_INSTR(0, 0xe1, 0x00, 0x06, 0x06, 0x0d, 0x0c, 0x18, 0x0c, 0x0c, 0x00,
		      0x03, 0x02, 0x09, 0x06, 0x2c, 0x26, 0x0a, 0x0a, 0x36),
	ILI9805_INSTR(0, 0xc1, 0x13, 0x0A, 0x0A, 0x36),
	ILI9805_INSTR(0, 0xc2, 0x12),
	ILI9805_INSTR(0, 0xc3, 0x12),
	ILI9805_INSTR(0, 0xc4, 0x12),
	ILI9805_INSTR(0, 0xB1, 0x00, 0x12, 0x14),
	ILI9805_INSTR(0, 0xB4, 0x02),
	ILI9805_INSTR(0, MIPI_DCS_SET_ADDRESS_MODE, 0x4A),  //MIPI_DCS_SET_ADDRESS_MODE = 0x36
	ILI9805_INSTR(0, MIPI_DCS_SET_PIXEL_FORMAT, 0x77),  //MIPI_DCS_SET_PIXEL_FORMAT = 0x3a
	ILI9805_INSTR(0, 0xB0, 0x01),
	ILI9805_INSTR(0, 0xDF, 0x23),
	ILI9805_INSTR(0, 0xB9, 0x02, 0x02),
	ILI9805_INSTR(0, 0x2a, 0x00, 0x00, 0x01, 0xdf),
	ILI9805_INSTR(0, 0x2b, 0x00, 0x00, 0x03, 0x1f),
	ILI9805_INSTR(0, 0x35, 0x00),
	ILI9805_INSTR(120, 0x11),
	ILI9805_INSTR(20, 0x29),
	ILI9805_INSTR(0, 0x2C),
// must have a 800ms delay before can turn on backlight
};

static inline struct ili9805 *panel_to_ili9805(struct drm_panel *panel)
{
	return container_of(panel, struct ili9805, panel);
}

static int ili9805_prepare(struct drm_panel *panel)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);
	unsigned int i;
	int ret;
	u8 l_data=0;
	u8 l_len=0;

	/* Panel power is permanently turn on for now, temporary disable */
	/* Power the panel 
	ret = regulator_enable(ctx->power);
	if (ret)
		return ret;
	msleep(5);
	*/
	
	/* And reset it */
	gpiod_set_value_cansleep(ctx->reset, 0);
	msleep(1);
	gpiod_set_value_cansleep(ctx->reset, 1);
	msleep(10);

	for (i = 0; i < ctx->desc->init_length; i++) {
		const struct ili9805_instr *instr = &ctx->desc->init[i];

		ret = mipi_dsi_dcs_write_buffer(ctx->dsi, instr->data, instr->len);
	//	ret = mipi_dsi_generic_write(ctx->dsi, instr->data, instr->len);
		/* debug code only */
		//l_data = *instr->data;
		//l_len= instr->len;
		//printk("\nKHL ili9805_activate data=%x, len=%x \n", l_data, l_len );
		if (ret < 0)
			return ret;

		if (instr->delay > 0)
			msleep(instr->delay);
	}
	/* already handle in the init sequence
	ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
	if (ret)
		return ret;
	*/
	printk("\nKHL::ili9805_prepare\n");
	return 0;
}

static int ili9805_enable(struct drm_panel *panel)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);

	msleep(120);

	mipi_dsi_dcs_set_display_on(ctx->dsi);
	printk("\nKHL::ili9805_enable\n");
	return 0;
}

static int ili9805_disable(struct drm_panel *panel)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);

	printk("\nKHL::ili9805_disable\n");
	return mipi_dsi_dcs_set_display_off(ctx->dsi);
}

static int ili9805_unprepare(struct drm_panel *panel)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);

	mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
//	regulator_disable(ctx->power);
	gpiod_set_value_cansleep(ctx->reset, 1);
	printk("\nKHL::ili9805_unprepare\n");
	return 0;
}


static const struct drm_display_mode msi_ili9805_default_mode = {
	.clock		= 26227,  //original from 9805.c

	.hdisplay = 480,
	.hsync_start = 480 + 10, //hact+hbp
	.hsync_end = 480 + 10 + 2, //hact+hbp+hsync
	.htotal = 480 + 10 + 2 + 36, //hact+hbp+hsync+hfp

	.vdisplay = 800,  //original 864
	.vsync_start = 800 + 2, //vact + vbp
	.vsync_end = 800 + 2 + 1, //vact+vbp+vsync
	.vtotal = 800 + 2 + 1 + 10, //vact+vb[+vsync+vfp
	/* physical measurement of the display area - KHL*/
	.width_mm	= 51,//94,
	.height_mm	= 78,//151,
};

static int ili9805_get_modes(struct drm_panel *panel,
			      struct drm_connector *connector)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);
	struct drm_display_mode *mode;

	
	mode = drm_mode_duplicate(connector->dev, ctx->desc->mode);
	if (!mode) {
		dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n",
			ctx->desc->mode->hdisplay,
			ctx->desc->mode->vdisplay,
			drm_mode_vrefresh(ctx->desc->mode));
		return -ENOMEM;
	}

	drm_mode_set_name(mode);

	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
	drm_mode_probed_add(connector, mode);  //move to last line

	connector->display_info.width_mm = mode->width_mm;
	connector->display_info.height_mm = mode->height_mm;

	drm_mode_probed_add(connector, mode);  //move to last line
	printk ("KHL::ili9805_get_modes, mode->hdisplay=%d\n", mode->hdisplay);
	/*
	 * TODO: Remove once all drm drivers call
	 * drm_connector_set_orientation_from_panel()
	 */
	drm_connector_set_panel_orientation(connector, ctx->orientation); 

	return 1;
}

static enum drm_panel_orientation ili9805_get_orientation(struct drm_panel *panel)
{
	struct ili9805 *ctx = panel_to_ili9805(panel);

	return ctx->orientation;
}

static const struct drm_panel_funcs ili9805_funcs = {
	.prepare	= ili9805_prepare,
	.unprepare	= ili9805_unprepare,
	.enable		= ili9805_enable,
	.disable	= ili9805_disable,
	.get_modes	= ili9805_get_modes,
	.get_orientation = ili9805_get_orientation,
};

static int ili9805_dsi_probe(struct mipi_dsi_device *dsi)
{
	struct ili9805 *ctx;
	int ret;

	ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;
	mipi_dsi_set_drvdata(dsi, ctx);
	ctx->dsi = dsi;
	ctx->desc = of_device_get_match_data(&dsi->dev);

	printk ("\nKHL::ili9805_dsi_probe in\n");
	drm_panel_init(&ctx->panel, &dsi->dev, &ili9805_funcs,
		       DRM_MODE_CONNECTOR_DSI);

	/* no power regulator, panel permanent turn on for now */
/*
	ctx->power = devm_regulator_get(&dsi->dev, "power");
	if (IS_ERR(ctx->power))
		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power),
				     "Couldn't get our power regulator\n");
*/
	ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->reset))
		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
				     "Couldn't get our reset GPIO\n");

	ret = of_drm_get_panel_orientation(dsi->dev.of_node, &ctx->orientation);
	if (ret) {
		dev_err(&dsi->dev, "%pOF: failed to get orientation: %d\n",
			dsi->dev.of_node, ret);
		return ret;
	}

	ctx->panel.prepare_prev_first = true;

	/*
	ret = drm_panel_of_backlight(&ctx->panel);
	if (ret)
		return ret;
	*/

	drm_panel_add(&ctx->panel);

	dsi->mode_flags = ctx->desc->mode_flags;
	dsi->format = MIPI_DSI_FMT_RGB565; //MIPI_DSI_FMT_RGB888;
	dsi->lanes = 2; //4;
	
	//return mipi_dsi_attach(dsi);
	ret = mipi_dsi_attach(dsi);
	if (ret < 0) {
		dev_err(&dsi->dev, "mipi_dsi_attach failed: %d\n", ret);
		drm_panel_remove(&ctx->panel);
		return ret;
	}
	printk ("\nKHL::ili9805_dsi_probe end\n");
	return ret;
}

static void ili9805_dsi_remove(struct mipi_dsi_device *dsi)
{
	struct ili9805 *ctx = mipi_dsi_get_drvdata(dsi);

	mipi_dsi_detach(dsi);
	drm_panel_remove(&ctx->panel);
}


static const struct ili9805_desc msi_ili9805_desc = {
	.init = msi_ili9805_init,
	.init_length = ARRAY_SIZE(msi_ili9805_init),
	.mode = &msi_ili9805_default_mode,
//	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE, 
/*	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
		      MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, */

	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
		MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM |
		MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_NO_EOT_PACKET,
};

static const struct of_device_id ili9805_of_match[] = {
	{ .compatible = "msi-9805", .data = &msi_ili9805_desc },
//	{ .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc },
//	{ .compatible = "startek,kd050hdfia020", .data = &kd050hdfia020_desc },
//	{ .compatible = "tdo,tl050hdv35", .data = &tl050hdv35_desc },
//	{ .compatible = "wanchanglong,w552946aba", .data = &w552946aba_desc },
//	{ .compatible = "ampire,am8001280g", .data = &am8001280g_desc },
	{ }
};
MODULE_DEVICE_TABLE(of, ili9805_of_match);

static struct mipi_dsi_driver ili9805_dsi_driver = {
	.probe		= ili9805_dsi_probe,
	.remove		= ili9805_dsi_remove,
	.driver = {
		.name		= "ili9805-dsi",
		.of_match_table	= ili9805_of_match,
	},
};
module_mipi_dsi_driver(ili9805_dsi_driver);

MODULE_AUTHOR("TBD");
MODULE_DESCRIPTION("Ilitek ili9805 Controller Driver");
MODULE_LICENSE("GPL v2");
