/*
 * Copyright (c) 2023 bytes at work AG
 * Copyright (c) 2020 Teslabs Engineering S.L.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT orisetech_otm8009a

#include <zephyr/kernel.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/mipi_dsi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(otm8009a, CONFIG_DISPLAY_LOG_LEVEL);

#include "display_otm8009a.h"

struct otm8009a_config {
	const struct device *mipi_dsi;
	const struct gpio_dt_spec reset;
	const struct gpio_dt_spec backlight;
	uint8_t data_lanes;
	uint16_t width;
	uint16_t height;
	uint8_t channel;
	uint16_t rotation;
};

struct otm8009a_data {
	uint16_t xres;
	uint16_t yres;
	uint8_t dsi_pixel_format;
	enum display_pixel_format pixel_format;
	enum display_orientation orientation;
};

static inline int otm8009a_dcs_write(const struct device *dev, uint8_t cmd, const void *buf,
				     size_t len)
{
	const struct otm8009a_config *cfg = dev->config;
	int ret;

	ret = mipi_dsi_dcs_write(cfg->mipi_dsi, cfg->channel, cmd, buf, len);
	if (ret < 0) {
		LOG_ERR("DCS 0x%x write failed! (%d)", cmd, ret);
		return ret;
	}

	return 0;
}

static int otm8009a_mcs_write(const struct device *dev, uint16_t cmd, const void *buf, size_t len)
{
	const struct otm8009a_config *cfg = dev->config;
	uint8_t scmd;
	int ret;

	scmd = cmd & 0xFF;
	ret = mipi_dsi_dcs_write(cfg->mipi_dsi, cfg->channel, OTM8009A_MCS_ADRSFT, &scmd, 1);
	if (ret < 0) {
		return ret;
	}

	ret = mipi_dsi_dcs_write(cfg->mipi_dsi, cfg->channel, cmd >> 8, buf, len);
	if (ret < 0) {
		return ret;
	}

	return 0;
}

static int otm8009a_check_id(const struct device *dev)
{
	const struct otm8009a_config *cfg = dev->config;
	uint32_t id = 0;
	int ret;

	ret = mipi_dsi_dcs_read(cfg->mipi_dsi, cfg->channel, OTM8009A_CMD_ID1, &id, sizeof(id));
	if (ret != sizeof(id)) {
		LOG_ERR("Read panel ID failed! (%d)", ret);
		return -EIO;
	}

	if (id != OTM8009A_ID1) {
		LOG_ERR("ID 0x%x (should 0x%x)", id, OTM8009A_ID1);
		return -EINVAL;
	}

	return 0;
}

static int otm8009a_configure(const struct device *dev)
{
	struct otm8009a_data *data = dev->data;
	uint8_t buf[4];
	int ret;

	static const uint8_t pwr_ctrl2[] = {0x96, 0x34, 0x01, 0x33, 0x33, 0x34, 0x33};
	static const uint8_t sd_ctrl[] = {0x0D, 0x1B, 0x02, 0x01, 0x3C, 0x08};
	static const uint8_t goavst[] = {
		0x85, 0x01, 0x00, 0x84, 0x01, 0x00, 0x81, 0x01, 0x28, 0x82, 0x01, 0x28
	};
	static const uint8_t goaclka1[] = {0x18, 0x04, 0x03, 0x39, 0x00, 0x00, 0x00};
	static const uint8_t goaclka2[] = {0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00};
	static const uint8_t goaclka3[] = {0x18, 0x02, 0x03, 0x3B, 0x00, 0x00, 0x00};
	static const uint8_t goaclka4[] = {0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00};
	static const uint8_t goaeclk[] = {0x01, 0x01, 0x20, 0x20, 0x00, 0x00};
	static const uint8_t panctrlset1[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	static const uint8_t panctrlset2[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t panctrlset3[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t panctrlset4[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	static const uint8_t panctrlset5[] = {
		0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t panctrlset6[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t panctrlset7[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	static const uint8_t panctrlset8[] = {
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
	};
	static const uint8_t panu2d1[] = {
		0x00, 0x26, 0x09, 0x0B, 0x01, 0x25, 0x00, 0x00, 0x00, 0x00
	};
	static const uint8_t panu2d2[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C,
		0x02
	};
	static const uint8_t panu2d3[] = {
		0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t pand2u1[] = {
		0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26, 0x00, 0x00, 0x00, 0x00
	};
	static const uint8_t pand2u2[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09,
		0x01
	};
	static const uint8_t pand2u3[] = {
		0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00
	};
	static const uint8_t pgamma[] = {
		0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10,
		0x0A, 0x01
	};
	static const uint8_t ngamma[] =  {
		0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10,
		0x0A, 0x01
	};

	/* enter command 2 mode to access manufacturer registers (ref. 5.3) */
	buf[0] = 0x80;
	buf[1] = 0x09;
	buf[2] = 0x01;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_CMD2_ENA1, buf, 3);
	if (ret < 0) {
		return ret;
	}

	/* enter Orise command 2 mode */
	buf[0] = 0x80;
	buf[1] = 0x09;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_CMD2_ENA2, buf, 2);
	if (ret < 0) {
		return ret;
	}

	/* source driver precharge control */
	buf[0] = 0x30;
	buf[1] = 0x8A;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_SD_PCH_CTRL, buf, 2);
	if (ret < 0) {
		return ret;
	}

	/* not documented */
	buf[0] = 0x40;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_NO_DOC1, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* power control settings 4 for DC voltage settings */
	/* enable GVDD test mode */
	buf[0] = 0x04;
	buf[1] = 0xA9;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PWR_CTRL4, buf, 2);
	if (ret < 0) {
		return ret;
	}

	/* power control settings 2 for normal mode */
	/* set pump 4 vgh voltage from 15.0v down to 13.0v */
	/* set pump 5 vgh voltage from -12.0v downto -9.0v */
	/* set pump 4&5 x6 (ONLY VALID when PUMP4_EN_ASDM_HV = "0") */
	/* change pump4 clock ratio from 1 line to 1/2 line */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PWR_CTRL2, pwr_ctrl2, sizeof(pwr_ctrl2));
	if (ret < 0) {
		return ret;
	}

	/* panel driving mode */
	/* set column inversion */
	buf[0] = 0x50;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_P_DRV_M, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* VCOM voltage setting */
	/* VCOM Voltage settings from -1.0000v downto -1.2625v */
	buf[0] = 0x4E;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_VCOMDC, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* oscillator adjustment for idle/normal mode */
	/* set 65Hz */
	buf[0] = 0x66;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_OSC_ADJ, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* RGB video mode setting */
	buf[0] = 0x08;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_RGB_VID_SET, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* GVDD/NGVDD */
	buf[0] = 0x79;
	buf[1] = 0x79;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GVDDSET, buf, 2);
	if (ret < 0) {
		return ret;
	}

	/* source driver timing setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_SD_CTRL, sd_ctrl, sizeof(sd_ctrl));
	if (ret < 0) {
		return ret;
	}

	/* panel type setting */
	buf[0] = 0x00;
	buf[1] = 0x01;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANSET, buf, 2);
	if (ret < 0) {
		return ret;
	}

	/* GOA VST setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOAVST, goavst, sizeof(goavst));
	if (ret < 0) {
		return ret;
	}

	/* GOA CLKA1 setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOACLKA1, goaclka1, sizeof(goaclka1));
	if (ret < 0) {
		return ret;
	}

	/* GOA CLKA2 setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOACLKA2, goaclka2, sizeof(goaclka2));
	if (ret < 0) {
		return ret;
	}

	/* GOA CLKA3 setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOACLKA3, goaclka3, sizeof(goaclka3));
	if (ret < 0) {
		return ret;
	}

	/* GOA CLKA4 setting */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOACLKA4, goaclka4, sizeof(goaclka4));
	if (ret < 0) {
		return ret;
	}

	/* GOA ECLK */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOAECLK, goaeclk, sizeof(goaeclk));
	if (ret < 0) {
		return ret;
	}

	/** GOA Other Options 1 */
	buf[0] = 0x01;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOAPT1, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* GOA Signal Toggle Option Setting */
	buf[0] = 0x02;
	buf[1] = 0x00;
	buf[2] = 0x00;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GOATGOPT, buf, 3);
	if (ret < 0) {
		return ret;
	}

	/* not documented */
	buf[0] = 0x00;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_NO_DOC2, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* Panel Control Setting 1 */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET1, panctrlset1, sizeof(panctrlset1));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET2, panctrlset2, sizeof(panctrlset2));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET3, panctrlset3, sizeof(panctrlset3));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET4, panctrlset4, sizeof(panctrlset4));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET5, panctrlset5, sizeof(panctrlset5));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET6, panctrlset6, sizeof(panctrlset6));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET7, panctrlset7, sizeof(panctrlset7));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANCTRLSET8, panctrlset8, sizeof(panctrlset8));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANU2D1, panu2d1, sizeof(panu2d1));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANU2D2, panu2d2, sizeof(panu2d2));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PANU2D3, panu2d3, sizeof(panu2d3));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PAND2U1, pand2u1, sizeof(pand2u1));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PAND2U2, pand2u2, sizeof(pand2u2));
	if (ret < 0) {
		return ret;
	}

	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PAND2U3, pand2u3, sizeof(pand2u3));
	if (ret < 0) {
		return ret;
	}

	/* power control setting 1 */
	/* Pump 1 min and max DM */
	buf[0] = 0x08;
	buf[1] = 0x66;
	buf[2] = 0x83;
	buf[3] = 0x00;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PWR_CTRL1, buf, 4);
	if (ret < 0) {
		return ret;
	}

	/* not documented */
	buf[0] = 0x06;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_NO_DOC3, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* PWM parameter 3 */
	/* Freq: 19.5 KHz */
	buf[0] = 0x06;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_PWM_PARA3, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* gamma correction 2.2+ */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GMCT2_2P, pgamma, sizeof(pgamma));
	if (ret < 0) {
		return ret;
	}

	/* gamma correction 2.2- */
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_GMCT2_2N, ngamma, sizeof(ngamma));
	if (ret < 0) {
		return ret;
	}

	/* exit command 2 mode */
	buf[0] = 0xFF;
	buf[1] = 0xFF;
	buf[2] = 0xFF;
	ret = otm8009a_mcs_write(dev, OTM8009A_MCS_CMD2_ENA1, buf, 3);
	if (ret < 0) {
		return ret;
	}

	/* exit sleep mode */
	ret = otm8009a_dcs_write(dev, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	k_msleep(OTM8009A_EXIT_SLEEP_MODE_WAIT_TIME);

	/* set pixel color format */
	switch (data->dsi_pixel_format) {
	case MIPI_DSI_PIXFMT_RGB565:
		buf[0] = MIPI_DCS_PIXEL_FORMAT_16BIT;
		break;
	case MIPI_DSI_PIXFMT_RGB888:
		buf[0] = MIPI_DCS_PIXEL_FORMAT_24BIT;
		break;
	default:
		LOG_ERR("Unsupported pixel format 0x%x!", data->dsi_pixel_format);
		return -ENOTSUP;
	}

	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_PIXEL_FORMAT, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* configure address mode */
	if (data->orientation == DISPLAY_ORIENTATION_NORMAL) {
		buf[0] = 0x00;
	} else if (data->orientation == DISPLAY_ORIENTATION_ROTATED_90) {
		buf[0] = MIPI_DCS_ADDRESS_MODE_MIRROR_X | MIPI_DCS_ADDRESS_MODE_SWAP_XY;
	} else if (data->orientation == DISPLAY_ORIENTATION_ROTATED_180) {
		buf[0] = MIPI_DCS_ADDRESS_MODE_MIRROR_X | MIPI_DCS_ADDRESS_MODE_MIRROR_Y;
	} else if (data->orientation == DISPLAY_ORIENTATION_ROTATED_270) {
		buf[0] = MIPI_DCS_ADDRESS_MODE_MIRROR_Y | MIPI_DCS_ADDRESS_MODE_SWAP_XY;
	}

	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_ADDRESS_MODE, buf, 1);
	if (ret < 0) {
		return ret;
	}

	buf[0] = 0x00;
	buf[1] = 0x00;
	sys_put_be16(data->xres, (uint8_t *)&buf[2]);
	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_COLUMN_ADDRESS, buf, 4);
	if (ret < 0) {
		return ret;
	}

	buf[0] = 0x00;
	buf[1] = 0x00;
	sys_put_be16(data->yres, (uint8_t *)&buf[2]);
	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_PAGE_ADDRESS, buf, 4);
	if (ret < 0) {
		return ret;
	}

	/* backlight control */
	buf[0] = OTM8009A_WRCTRLD_BCTRL | OTM8009A_WRCTRLD_DD | OTM8009A_WRCTRLD_BL;
	ret = otm8009a_dcs_write(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* adaptive brightness control */
	buf[0] = OTM8009A_WRCABC_UI;
	ret = otm8009a_dcs_write(dev, MIPI_DCS_WRITE_POWER_SAVE, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* adaptive brightness control minimum brightness */
	buf[0] = 0xFF;
	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* brightness */
	buf[0] = 0xFF;
	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, buf, 1);
	if (ret < 0) {
		return ret;
	}

	/* Display On */
	ret = otm8009a_dcs_write(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	/* trigger display write (from data coming by DSI bus) */
	ret = otm8009a_dcs_write(dev, MIPI_DCS_WRITE_MEMORY_START, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	return 0;
}

static int otm8009a_blanking_on(const struct device *dev)
{
	const struct otm8009a_config *cfg = dev->config;
	int ret;

	if (cfg->backlight.port != NULL) {
		ret = gpio_pin_set_dt(&cfg->backlight, 0);
		if (ret) {
			LOG_ERR("Disable backlight failed! (%d)", ret);
			return ret;
		}
	}

	return otm8009a_dcs_write(dev, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
}

static int otm8009a_blanking_off(const struct device *dev)
{
	const struct otm8009a_config *cfg = dev->config;
	int ret;

	if (cfg->backlight.port != NULL) {
		ret = gpio_pin_set_dt(&cfg->backlight, 1);
		if (ret) {
			LOG_ERR("Enable backlight failed! (%d)", ret);
			return ret;
		}
	}

	return otm8009a_dcs_write(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
}

static int otm8009a_write(const struct device *dev, uint16_t x, uint16_t y,
			  const struct display_buffer_descriptor *desc, const void *buf)
{
	return -ENOTSUP;
}

static int otm8009a_set_brightness(const struct device *dev, uint8_t brightness)
{
	return otm8009a_dcs_write(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, &brightness, 1);
}

static void otm8009a_get_capabilities(const struct device *dev,
				      struct display_capabilities *capabilities)
{
	const struct otm8009a_config *cfg = dev->config;
	struct otm8009a_data *data = dev->data;

	memset(capabilities, 0, sizeof(struct display_capabilities));
	capabilities->x_resolution = cfg->width;
	capabilities->y_resolution = cfg->height;
	capabilities->supported_pixel_formats = data->pixel_format;
	capabilities->current_pixel_format = data->pixel_format;
	capabilities->current_orientation = data->orientation;
}

static DEVICE_API(display, otm8009a_api) = {
	.blanking_on = otm8009a_blanking_on,
	.blanking_off = otm8009a_blanking_off,
	.write = otm8009a_write,
	.set_brightness = otm8009a_set_brightness,
	.get_capabilities = otm8009a_get_capabilities,
};

static int otm8009a_init(const struct device *dev)
{
	const struct otm8009a_config *cfg = dev->config;
	struct otm8009a_data *data = dev->data;
	struct mipi_dsi_device mdev;
	int ret;

	if (cfg->reset.port) {
		if (!gpio_is_ready_dt(&cfg->reset)) {
			LOG_ERR("Reset GPIO device is not ready!");
			return -ENODEV;
		}
		ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_INACTIVE);
		if (ret < 0) {
			LOG_ERR("Reset display failed! (%d)", ret);
			return ret;
		}
		k_msleep(OTM8009A_RESET_TIME);
		ret = gpio_pin_set_dt(&cfg->reset, 1);
		if (ret < 0) {
			LOG_ERR("Enable display failed! (%d)", ret);
			return ret;
		}
		k_msleep(OTM8009A_WAKE_TIME);
	}

	/* store x/y resolution & rotation */
	if (cfg->rotation == 0) {
		data->xres = cfg->width;
		data->yres = cfg->height;
		data->orientation = DISPLAY_ORIENTATION_NORMAL;
	} else if (cfg->rotation == 90) {
		data->xres = cfg->height;
		data->yres = cfg->width;
		data->orientation = DISPLAY_ORIENTATION_ROTATED_90;
	} else if (cfg->rotation == 180) {
		data->xres = cfg->width;
		data->yres = cfg->height;
		data->orientation = DISPLAY_ORIENTATION_ROTATED_180;
	} else if (cfg->rotation == 270) {
		data->xres = cfg->height;
		data->yres = cfg->width;
		data->orientation = DISPLAY_ORIENTATION_ROTATED_270;
	}

	/* attach to MIPI-DSI host */
	mdev.data_lanes = cfg->data_lanes;
	mdev.pixfmt = data->dsi_pixel_format;
	mdev.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM;

	mdev.timings.hactive = data->xres;
	mdev.timings.hbp = OTM8009A_HBP;
	mdev.timings.hfp = OTM8009A_HFP;
	mdev.timings.hsync = OTM8009A_HSYNC;
	mdev.timings.vactive = data->yres;
	mdev.timings.vbp = OTM8009A_VBP;
	mdev.timings.vfp = OTM8009A_VFP;
	mdev.timings.vsync = OTM8009A_VSYNC;

	ret = mipi_dsi_attach(cfg->mipi_dsi, cfg->channel, &mdev);
	if (ret < 0) {
		LOG_ERR("MIPI-DSI attach failed! (%d)", ret);
		return ret;
	}

	ret = otm8009a_check_id(dev);
	if (ret) {
		LOG_ERR("Panel ID check failed! (%d)", ret);
		return ret;
	}

	ret = otm8009a_configure(dev);
	if (ret) {
		LOG_ERR("DSI init sequence failed! (%d)", ret);
		return ret;
	}

	ret = otm8009a_blanking_off(dev);
	if (ret) {
		LOG_ERR("Display blanking off failed! (%d)", ret);
		return ret;
	}

	return 0;
}

#define OTM8009A_DEVICE(inst)									\
	static const struct otm8009a_config otm8009a_config_##inst = {				\
		.mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(inst)),					\
		.reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}),			\
		.backlight = GPIO_DT_SPEC_INST_GET_OR(inst, bl_gpios, {0}),			\
		.data_lanes = DT_INST_PROP_BY_IDX(inst, data_lanes, 0),				\
		.width = DT_INST_PROP(inst, width),						\
		.height = DT_INST_PROP(inst, height),						\
		.channel = DT_INST_REG_ADDR(inst),						\
		.rotation = DT_INST_PROP(inst, rotation),					\
	};											\
	static struct otm8009a_data otm8009a_data_##inst = {					\
		.dsi_pixel_format = DT_INST_PROP(inst, pixel_format),				\
	};											\
	DEVICE_DT_INST_DEFINE(inst, &otm8009a_init, NULL, &otm8009a_data_##inst,		\
			      &otm8009a_config_##inst, POST_KERNEL,				\
			      CONFIG_DISPLAY_OTM8009A_INIT_PRIORITY, &otm8009a_api);		\

DT_INST_FOREACH_STATUS_OKAY(OTM8009A_DEVICE)
