/*
 * Copyright 2024 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ovti_ov5640

#include <zephyr/kernel.h>
#include <math.h>
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>

LOG_MODULE_REGISTER(video_ov5640, CONFIG_VIDEO_LOG_LEVEL);

#define CHIP_ID_REG 0x300a
#define CHIP_ID_VAL 0x5640

#define SYS_CTRL0_REG     0x3008
#define SYS_CTRL0_SW_PWDN 0x42
#define SYS_CTRL0_SW_PWUP 0x02
#define SYS_CTRL0_SW_RST  0x82

#define SYS_RESET00_REG      0x3000
#define SYS_RESET02_REG      0x3002
#define SYS_CLK_ENABLE00_REG 0x3004
#define SYS_CLK_ENABLE02_REG 0x3006
#define IO_MIPI_CTRL00_REG   0x300e
#define SYSTEM_CONTROL1_REG  0x302e
#define SCCB_SYS_CTRL1_REG   0x3103
#define TIMING_TC_REG20_REG  0x3820
#define TIMING_TC_REG21_REG  0x3821
#define HZ5060_CTRL00_REG    0x3c00
#define HZ5060_CTRL01_REG    0x3c01
#define ISP_CTRL01_REG       0x5001
#define PRE_ISP_TEST_SET1    0x503d

#define SC_PLL_CTRL0_REG 0x3034
#define SC_PLL_CTRL1_REG 0x3035
#define SC_PLL_CTRL2_REG 0x3036
#define SC_PLL_CTRL3_REG 0x3037
#define SYS_ROOT_DIV_REG 0x3108
#define PCLK_PERIOD_REG  0x4837

#define AEC_PK_REAL_GAIN 0x350a
#define AEC_PK_MANUAL    0x3503
#define AEC_CTRL00_REG   0x3a00
#define AEC_CTRL0F_REG   0x3a0f
#define AEC_CTRL10_REG   0x3a10
#define AEC_CTRL11_REG   0x3a11
#define AEC_CTRL1B_REG   0x3a1b
#define AEC_CTRL1E_REG   0x3a1e
#define AEC_CTRL1F_REG   0x3a1f

#define BLC_CTRL01_REG 0x4001
#define BLC_CTRL04_REG 0x4004
#define BLC_CTRL05_REG 0x4005

#define AWB_CTRL00_REG 0x5180
#define AWB_CTRL01_REG 0x5181
#define AWB_CTRL02_REG 0x5182
#define AWB_CTRL03_REG 0x5183
#define AWB_CTRL04_REG 0x5184
#define AWB_CTRL05_REG 0x5185
#define AWB_CTRL17_REG 0x5191
#define AWB_CTRL18_REG 0x5192
#define AWB_CTRL19_REG 0x5193
#define AWB_CTRL20_REG 0x5194
#define AWB_CTRL21_REG 0x5195
#define AWB_CTRL22_REG 0x5196
#define AWB_CTRL23_REG 0x5197
#define AWB_CTRL30_REG 0x519e

#define SDE_CTRL0_REG  0x5580
#define SDE_CTRL1_REG  0x5581
#define SDE_CTRL2_REG  0x5582
#define SDE_CTRL3_REG  0x5583
#define SDE_CTRL4_REG  0x5584
#define SDE_CTRL5_REG  0x5585
#define SDE_CTRL6_REG  0x5586
#define SDE_CTRL7_REG  0x5587
#define SDE_CTRL8_REG  0x5588
#define SDE_CTRL9_REG  0x5589
#define SDE_CTRL10_REG 0x558a
#define SDE_CTRL11_REG 0x558b

#define DEFAULT_MIPI_CHANNEL 0

#define PI 3.141592654

#define ABS(a, b) (a > b ? a - b : b - a)

#define PCLK_ROOT_DIV 1
#define SCLK2X_DIV    1
#define SCLK_DIV      2
#define PLL_ROOT_DIV  2
#define PLL_PRE_DIV   3
#define MIPI_BIT_MODE 0x08

/* Must be kept in ascending order */
enum ov5640_frame_rate {
	OV5640_15_FPS = 15,
	OV5640_30_FPS = 30,
	OV5640_60_FPS = 60,
};

struct ov5640_config {
	struct i2c_dt_spec i2c;
	struct gpio_dt_spec reset_gpio;
	struct gpio_dt_spec powerdown_gpio;
};

struct ov5640_reg {
	uint16_t addr;
	uint8_t val;
};

struct ov5640_mipi_frmrate_config {
	uint8_t frmrate;
	uint8_t pllCtrl1;
	uint8_t pllCtrl2;
	uint32_t pixelrate;
};

struct ov5640_mode_config {
	uint16_t width;
	uint16_t height;
	const struct ov5640_reg *res_params;
	const struct ov5640_mipi_frmrate_config *mipi_frmrate_config;
	uint16_t max_frmrate;
	uint16_t def_frmrate;
};

struct ov5640_data {
	struct video_format fmt;
	uint64_t cur_pixrate;
	uint16_t cur_frmrate;
	const struct ov5640_mode_config *cur_mode;
};

static const struct ov5640_reg init_params[] = {
	/* Power down */
	{SYS_CTRL0_REG, SYS_CTRL0_SW_PWDN},

	/* System setting. */
	{SCCB_SYS_CTRL1_REG, 0x13},
	{SCCB_SYS_CTRL1_REG, 0x03},
	{SYS_RESET00_REG, 0x00},
	{SYS_CLK_ENABLE00_REG, 0xff},
	{SYS_RESET02_REG, 0x1c},
	{SYS_CLK_ENABLE02_REG, 0xc3},
	{SYSTEM_CONTROL1_REG, 0x08},
	{0x3618, 0x00},
	{0x3612, 0x29},
	{0x3708, 0x64},
	{0x3709, 0x52},
	{0x370c, 0x03},
	{TIMING_TC_REG20_REG, 0x41},
	{TIMING_TC_REG21_REG, 0x07},
	{0x3630, 0x36},
	{0x3631, 0x0e},
	{0x3632, 0xe2},
	{0x3633, 0x12},
	{0x3621, 0xe0},
	{0x3704, 0xa0},
	{0x3703, 0x5a},
	{0x3715, 0x78},
	{0x3717, 0x01},
	{0x370b, 0x60},
	{0x3705, 0x1a},
	{0x3905, 0x02},
	{0x3906, 0x10},
	{0x3901, 0x0a},
	{0x3731, 0x12},
	{0x3600, 0x08},
	{0x3601, 0x33},
	{0x302d, 0x60},
	{0x3620, 0x52},
	{0x371b, 0x20},
	{0x471c, 0x50},
	{0x3a13, 0x43},
	{0x3a18, 0x00},
	{0x3a19, 0x7c},
	{0x3635, 0x13},
	{0x3636, 0x03},
	{0x3634, 0x40},
	{0x3622, 0x01},
	{HZ5060_CTRL01_REG, 0x00},
	{AEC_CTRL00_REG, 0x58},
	{BLC_CTRL01_REG, 0x02},
	{BLC_CTRL04_REG, 0x02},
	{BLC_CTRL05_REG, 0x1a},
	{ISP_CTRL01_REG, 0xa3},

	/* AEC */
	{AEC_CTRL0F_REG, 0x30},
	{AEC_CTRL10_REG, 0x28},
	{AEC_CTRL1B_REG, 0x30},
	{AEC_CTRL1E_REG, 0x26},
	{AEC_CTRL11_REG, 0x60},
	{AEC_CTRL1F_REG, 0x14},

	/* AWB */
	{AWB_CTRL00_REG, 0xff},
	{AWB_CTRL01_REG, 0xf2},
	{AWB_CTRL02_REG, 0x00},
	{AWB_CTRL03_REG, 0x14},
	{AWB_CTRL04_REG, 0x25},
	{AWB_CTRL05_REG, 0x24},
	{0x5186, 0x09},
	{0x5187, 0x09},
	{0x5188, 0x09},
	{0x5189, 0x88},
	{0x518a, 0x54},
	{0x518b, 0xee},
	{0x518c, 0xb2},
	{0x518d, 0x50},
	{0x518e, 0x34},
	{0x518f, 0x6b},
	{0x5190, 0x46},
	{AWB_CTRL17_REG, 0xf8},
	{AWB_CTRL18_REG, 0x04},
	{AWB_CTRL19_REG, 0x70},
	{AWB_CTRL20_REG, 0xf0},
	{AWB_CTRL21_REG, 0xf0},
	{AWB_CTRL22_REG, 0x03},
	{AWB_CTRL23_REG, 0x01},
	{0x5198, 0x04},
	{0x5199, 0x6c},
	{0x519a, 0x04},
	{0x519b, 0x00},
	{0x519c, 0x09},
	{0x519d, 0x2b},
	{AWB_CTRL30_REG, 0x38},

	/* Color Matrix */
	{0x5381, 0x1e},
	{0x5382, 0x5b},
	{0x5383, 0x08},
	{0x5384, 0x0a},
	{0x5385, 0x7e},
	{0x5386, 0x88},
	{0x5387, 0x7c},
	{0x5388, 0x6c},
	{0x5389, 0x10},
	{0x538a, 0x01},
	{0x538b, 0x98},

	/* Sharp */
	{0x5300, 0x08},
	{0x5301, 0x30},
	{0x5302, 0x10},
	{0x5303, 0x00},
	{0x5304, 0x08},
	{0x5305, 0x30},
	{0x5306, 0x08},
	{0x5307, 0x16},
	{0x5309, 0x08},
	{0x530a, 0x30},
	{0x530b, 0x04},
	{0x530c, 0x06},

	/* Gamma */
	{0x5480, 0x01},
	{0x5481, 0x08},
	{0x5482, 0x14},
	{0x5483, 0x28},
	{0x5484, 0x51},
	{0x5485, 0x65},
	{0x5486, 0x71},
	{0x5487, 0x7d},
	{0x5488, 0x87},
	{0x5489, 0x91},
	{0x548a, 0x9a},
	{0x548b, 0xaa},
	{0x548c, 0xb8},
	{0x548d, 0xcd},
	{0x548e, 0xdd},
	{0x548f, 0xea},
	{0x5490, 0x1d},

	/* UV adjust. */
	{SDE_CTRL0_REG, 0x02},
	{SDE_CTRL3_REG, 0x40},
	{SDE_CTRL4_REG, 0x10},
	{SDE_CTRL9_REG, 0x10},
	{SDE_CTRL10_REG, 0x00},
	{SDE_CTRL11_REG, 0xf8},

	/* Lens correction. */
	{0x5800, 0x23},
	{0x5801, 0x14},
	{0x5802, 0x0f},
	{0x5803, 0x0f},
	{0x5804, 0x12},
	{0x5805, 0x26},
	{0x5806, 0x0c},
	{0x5807, 0x08},
	{0x5808, 0x05},
	{0x5809, 0x05},
	{0x580a, 0x08},
	{0x580b, 0x0d},
	{0x580c, 0x08},
	{0x580d, 0x03},
	{0x580e, 0x00},
	{0x580f, 0x00},
	{0x5810, 0x03},
	{0x5811, 0x09},
	{0x5812, 0x07},
	{0x5813, 0x03},
	{0x5814, 0x00},
	{0x5815, 0x01},
	{0x5816, 0x03},
	{0x5817, 0x08},
	{0x5818, 0x0d},
	{0x5819, 0x08},
	{0x581a, 0x05},
	{0x581b, 0x06},
	{0x581c, 0x08},
	{0x581d, 0x0e},
	{0x581e, 0x29},
	{0x581f, 0x17},
	{0x5820, 0x11},
	{0x5821, 0x11},
	{0x5822, 0x15},
	{0x5823, 0x28},
	{0x5824, 0x46},
	{0x5825, 0x26},
	{0x5826, 0x08},
	{0x5827, 0x26},
	{0x5828, 0x64},
	{0x5829, 0x26},
	{0x582a, 0x24},
	{0x582b, 0x22},
	{0x582c, 0x24},
	{0x582d, 0x24},
	{0x582e, 0x06},
	{0x582f, 0x22},
	{0x5830, 0x40},
	{0x5831, 0x42},
	{0x5832, 0x24},
	{0x5833, 0x26},
	{0x5834, 0x24},
	{0x5835, 0x22},
	{0x5836, 0x22},
	{0x5837, 0x26},
	{0x5838, 0x44},
	{0x5839, 0x24},
	{0x583a, 0x26},
	{0x583b, 0x28},
	{0x583c, 0x42},
	{0x583d, 0xce},
	{0x5000, 0xa7},
};

static const struct ov5640_reg low_res_params[] = {
	{0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, {0x3803, 0x04}, {0x3804, 0x0a},
	{0x3805, 0x3f}, {0x3806, 0x07}, {0x3807, 0x9b}, {0x3808, 0x02}, {0x3809, 0x80},
	{0x380a, 0x01}, {0x380b, 0xe0}, {0x380c, 0x07}, {0x380d, 0x68}, {0x380e, 0x03},
	{0x380f, 0xd8}, {0x3810, 0x00}, {0x3811, 0x10}, {0x3812, 0x00}, {0x3813, 0x06},
	{0x3814, 0x31}, {0x3815, 0x31}, {0x3824, 0x02}, {0x460c, 0x22}};

static const struct ov5640_reg hd_res_params[] = {
	{0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, {0x3803, 0xfa}, {0x3804, 0x0a},
	{0x3805, 0x3f}, {0x3806, 0x06}, {0x3807, 0xa9}, {0x3808, 0x05}, {0x3809, 0x00},
	{0x380a, 0x02}, {0x380b, 0xd0}, {0x380c, 0x07}, {0x380d, 0x64}, {0x380e, 0x02},
	{0x380f, 0xe4}, {0x3810, 0x00}, {0x3811, 0x10}, {0x3812, 0x00}, {0x3813, 0x04},
	{0x3814, 0x31}, {0x3815, 0x31}, {0x3824, 0x04}, {0x460c, 0x20}};

static const struct ov5640_mipi_frmrate_config mipi_hd_frmrate_params[] = {
	{15, 0x21, 0x2A, 24000000}, {30, 0x21, 0x54, 48000000}, {60, 0x11, 0x54, 96000000}};

static const struct ov5640_mipi_frmrate_config mipi_vga_frmrate_params[] = {
	{15, 0x22, 0x38, 24000000}, {30, 0x14, 0x38, 24000000}, {60, 0x14, 0x70, 48000000}};

static const struct ov5640_mode_config modes[] = {
	{
		.width = 640,
		.height = 480,
		.res_params = low_res_params,
		.mipi_frmrate_config = mipi_vga_frmrate_params,
		.max_frmrate = OV5640_60_FPS,
		.def_frmrate = OV5640_30_FPS,
	},
	{
		.width = 1280,
		.height = 720,
		.res_params = hd_res_params,
		.mipi_frmrate_config = mipi_hd_frmrate_params,
		.max_frmrate = OV5640_60_FPS,
		.def_frmrate = OV5640_30_FPS,
	}};

static const int ov5640_frame_rates[] = {OV5640_15_FPS, OV5640_30_FPS, OV5640_60_FPS};

#define OV5640_VIDEO_FORMAT_CAP(width, height, format)                                             \
	{                                                                                          \
		.pixelformat = (format), .width_min = (width), .width_max = (width),               \
		.height_min = (height), .height_max = (height), .width_step = 0, .height_step = 0  \
	}

static const struct video_format_cap fmts[] = {
	OV5640_VIDEO_FORMAT_CAP(1280, 720, VIDEO_PIX_FMT_RGB565),
	OV5640_VIDEO_FORMAT_CAP(1280, 720, VIDEO_PIX_FMT_YUYV),
	OV5640_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565),
	OV5640_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_YUYV),
	{0}};

static int ov5640_read_reg(const struct i2c_dt_spec *spec, const uint16_t addr, void *val,
			   const uint8_t val_size)
{
	int ret;
	struct i2c_msg msg[2];
	uint8_t addr_buf[2];

	if (val_size > 4) {
		return -ENOTSUP;
	}

	addr_buf[1] = addr & 0xFF;
	addr_buf[0] = addr >> 8;
	msg[0].buf = addr_buf;
	msg[0].len = 2U;
	msg[0].flags = I2C_MSG_WRITE;

	msg[1].buf = (uint8_t *)val;
	msg[1].len = val_size;
	msg[1].flags = I2C_MSG_READ | I2C_MSG_STOP | I2C_MSG_RESTART;

	ret = i2c_transfer_dt(spec, msg, 2);
	if (ret) {
		return ret;
	}

	switch (val_size) {
	case 4:
		*(uint32_t *)val = sys_be32_to_cpu(*(uint32_t *)val);
		break;
	case 2:
		*(uint16_t *)val = sys_be16_to_cpu(*(uint16_t *)val);
		break;
	case 1:
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static int ov5640_write_reg(const struct i2c_dt_spec *spec, const uint16_t addr, const uint8_t val)
{
	uint8_t addr_buf[2];
	struct i2c_msg msg[2];

	addr_buf[1] = addr & 0xFF;
	addr_buf[0] = addr >> 8;
	msg[0].buf = addr_buf;
	msg[0].len = 2U;
	msg[0].flags = I2C_MSG_WRITE;

	msg[1].buf = (uint8_t *)&val;
	msg[1].len = 1;
	msg[1].flags = I2C_MSG_WRITE | I2C_MSG_STOP;

	return i2c_transfer_dt(spec, msg, 2);
}

static int ov5640_modify_reg(const struct i2c_dt_spec *spec, const uint16_t addr,
			     const uint8_t mask, const uint8_t val)
{
	uint8_t regVal = 0;
	int ret = ov5640_read_reg(spec, addr, &regVal, sizeof(regVal));

	if (ret) {
		return ret;
	}

	return ov5640_write_reg(spec, addr, (regVal & ~mask) | (val & mask));
}

static int ov5640_write_multi_regs(const struct i2c_dt_spec *spec, const struct ov5640_reg *regs,
				   const uint32_t num_regs)
{
	int ret;

	for (int i = 0; i < num_regs; i++) {
		ret = ov5640_write_reg(spec, regs[i].addr, regs[i].val);
		if (ret) {
			return ret;
		}
	}

	return 0;
}

static int ov5640_set_frmival(const struct device *dev, enum video_endpoint_id ep,
			      struct video_frmival *frmival)
{
	const struct ov5640_config *cfg = dev->config;
	struct ov5640_data *drv_data = dev->data;
	int ret;
	uint8_t i, ind = 0;
	uint32_t desired_frmrate, best_match = ov5640_frame_rates[ind];

	desired_frmrate = DIV_ROUND_CLOSEST(frmival->denominator, frmival->numerator);

	/* Find the supported frame rate closest to the desired one */
	for (i = 0; i < ARRAY_SIZE(ov5640_frame_rates); i++) {
		if (ov5640_frame_rates[i] <= drv_data->cur_mode->max_frmrate &&
		    ABS(desired_frmrate, ov5640_frame_rates[i]) <
			    ABS(desired_frmrate, best_match)) {
			best_match = ov5640_frame_rates[i];
			ind = i;
		}
	}

	struct ov5640_reg frmrate_params[] = {
		{SC_PLL_CTRL1_REG, drv_data->cur_mode->mipi_frmrate_config[ind].pllCtrl1},
		{SC_PLL_CTRL2_REG, drv_data->cur_mode->mipi_frmrate_config[ind].pllCtrl2},
		{PCLK_PERIOD_REG, 0x0a}};

	ret = ov5640_write_multi_regs(&cfg->i2c, frmrate_params, ARRAY_SIZE(frmrate_params));
	ret |= ov5640_modify_reg(&cfg->i2c, SC_PLL_CTRL0_REG, 0x0f, MIPI_BIT_MODE);
	ret |= ov5640_modify_reg(&cfg->i2c, SC_PLL_CTRL3_REG, 0x1f,
				 (LOG2CEIL(PLL_ROOT_DIV) << 4) | (PLL_PRE_DIV & 0x07));
	ret |= ov5640_modify_reg(&cfg->i2c, SYS_ROOT_DIV_REG, 0x3f,
				 (LOG2CEIL(PCLK_ROOT_DIV) & 0x03 << 4) |
					 (LOG2CEIL(SCLK2X_DIV) & 0x03 << 2) |
					 (LOG2CEIL(SCLK_DIV) & 0x03));

	if (ret) {
		LOG_ERR("Unable to set frame interval");
		return ret;
	}

	drv_data->cur_frmrate = best_match;
	drv_data->cur_pixrate = drv_data->cur_mode->mipi_frmrate_config[ind].pixelrate;

	frmival->numerator = 1;
	frmival->denominator = best_match;

	return 0;
}

static int ov5640_set_fmt(const struct device *dev, enum video_endpoint_id ep,
			  struct video_format *fmt)
{
	struct ov5640_data *drv_data = dev->data;
	const struct ov5640_config *cfg = dev->config;
	int ret, i;
	struct video_frmival def_frmival;

	for (i = 0; i < ARRAY_SIZE(fmts); ++i) {
		if (fmt->pixelformat == fmts[i].pixelformat && fmt->width >= fmts[i].width_min &&
		    fmt->width <= fmts[i].width_max && fmt->height >= fmts[i].height_min &&
		    fmt->height <= fmts[i].height_max) {
			break;
		}
	}

	if (i == ARRAY_SIZE(fmts)) {
		LOG_ERR("Unsupported pixel format or resolution");
		return -ENOTSUP;
	}

	if (!memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt))) {
		return 0;
	}

	drv_data->fmt = *fmt;

	/* Set resolution */
	for (i = 0; i < ARRAY_SIZE(modes); i++) {
		if (fmt->width == modes[i].width && fmt->height == modes[i].height) {
			ret = ov5640_write_multi_regs(&cfg->i2c, modes[i].res_params,
						      modes[i].res_params == hd_res_params
							      ? ARRAY_SIZE(hd_res_params)
							      : ARRAY_SIZE(low_res_params));
			if (ret) {
				LOG_ERR("Unable to set resolution parameters");
				return ret;
			}

			drv_data->cur_mode = &modes[i];
			break;
		}
	}

	/* Set pixel format */
	struct ov5640_reg fmt_params[] = {
		{0x4300, 0x6f},
		{0x501f, 0x01},
	};

	if (fmt->pixelformat == VIDEO_PIX_FMT_YUYV) {
		fmt_params[0].val = 0x3f;
		fmt_params[1].val = 0x00;
	}

	ret = ov5640_write_multi_regs(&cfg->i2c, fmt_params, ARRAY_SIZE(fmt_params));
	if (ret) {
		LOG_ERR("Unable to set pixel format");
		return ret;
	}

	/* Set frame rate */
	def_frmival.denominator = drv_data->cur_mode->def_frmrate;
	def_frmival.numerator = 1;

	return ov5640_set_frmival(dev, ep, &def_frmival);
}

static int ov5640_get_fmt(const struct device *dev, enum video_endpoint_id ep,
			  struct video_format *fmt)
{
	struct ov5640_data *drv_data = dev->data;

	*fmt = drv_data->fmt;

	return 0;
}

static int ov5640_get_caps(const struct device *dev, enum video_endpoint_id ep,
			   struct video_caps *caps)
{
	caps->format_caps = fmts;
	return 0;
}

static int ov5640_stream_start(const struct device *dev)
{
	const struct ov5640_config *cfg = dev->config;
	/* Power up MIPI PHY HS Tx & LP Rx in 2 data lanes mode */
	int ret = ov5640_write_reg(&cfg->i2c, IO_MIPI_CTRL00_REG, 0x45);

	if (ret) {
		LOG_ERR("Unable to power up MIPI PHY");
		return ret;
	}
	return ov5640_write_reg(&cfg->i2c, SYS_CTRL0_REG, SYS_CTRL0_SW_PWUP);
}

static int ov5640_stream_stop(const struct device *dev)
{
	const struct ov5640_config *cfg = dev->config;
	/* Power down MIPI PHY HS Tx & LP Rx */
	int ret = ov5640_write_reg(&cfg->i2c, IO_MIPI_CTRL00_REG, 0x40);

	if (ret) {
		LOG_ERR("Unable to power down MIPI PHY");
		return ret;
	}
	return ov5640_write_reg(&cfg->i2c, SYS_CTRL0_REG, SYS_CTRL0_SW_PWDN);
}

#define TEST_PATTERN_ENABLE  BIT(7)
#define TEST_PATTERN_ROLLING BIT(6)
#define TEST_PATTERN_BAR     (0 << 0)
#define TEST_PATTERN_SQUARE  (2 << 0)

static const uint8_t test_pattern_val[] = {
	0,
	TEST_PATTERN_ENABLE | TEST_PATTERN_BAR | (1 << 2),
	TEST_PATTERN_ENABLE | TEST_PATTERN_BAR | (1 << 2) | TEST_PATTERN_ROLLING,
	TEST_PATTERN_ENABLE | TEST_PATTERN_SQUARE,
	TEST_PATTERN_ENABLE | TEST_PATTERN_SQUARE | TEST_PATTERN_ROLLING,
};

static int ov5640_set_ctrl_test_pattern(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	if (!IN_RANGE(value, 0, ARRAY_SIZE(test_pattern_val) - 1)) {
		return -EINVAL;
	}

	return ov5640_write_reg(&cfg->i2c, PRE_ISP_TEST_SET1, test_pattern_val[value]);
}

static int ov5640_set_ctrl_hue(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;
	int cos_coef, sin_coef, sign = 0;

	if (!IN_RANGE(value, 0, 360)) {
		return -EINVAL;
	}

	double rad_val = value;
	int ret = ov5640_modify_reg(&cfg->i2c, SDE_CTRL0_REG, BIT(0), BIT(0));

	if (ret) {
		return ret;
	}

	rad_val = value * PI / 180.0;
	cos_coef = round(cos(rad_val) * 128);
	sin_coef = round(sin(rad_val) * 128);

	if (0 <= value && value < 90) {
		sign = 0x01;
	} else if (90 <= value && value < 180) {
		sign = 0x31;
	} else if (180 <= value && value < 270) {
		sign = 0x32;
	} else if (270 <= value && value < 360) {
		sign = 0x02;
	}

	struct ov5640_reg hue_params[] = {{SDE_CTRL8_REG, sign},
					  {SDE_CTRL1_REG, abs(cos_coef)},
					  {SDE_CTRL2_REG, abs(sin_coef)}};

	return ov5640_write_multi_regs(&cfg->i2c, hue_params, ARRAY_SIZE(hue_params));
}

static int ov5640_set_ctrl_saturation(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	if (!IN_RANGE(value, 0, UINT8_MAX)) {
		return -EINVAL;
	}

	struct ov5640_reg saturation_params[] = {{SDE_CTRL3_REG, value}, {SDE_CTRL4_REG, value}};
	int ret = ov5640_modify_reg(&cfg->i2c, SDE_CTRL8_REG, BIT(6) | BIT(0), BIT(6) | BIT(0));

	if (ret) {
		return ret;
	}

	return ov5640_write_multi_regs(&cfg->i2c, saturation_params, ARRAY_SIZE(saturation_params));
}

static int ov5640_set_ctrl_brightness(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	if (!IN_RANGE(value, -UINT8_MAX, UINT8_MAX)) {
		return -EINVAL;
	}

	struct ov5640_reg brightness_params[] = {{SDE_CTRL8_REG, value >= 0 ? 0x01 : 0x09},
						 {SDE_CTRL7_REG, abs(value) & 0xff}};
	int ret = ov5640_modify_reg(&cfg->i2c, SDE_CTRL0_REG, BIT(2), BIT(2));

	if (ret) {
		return ret;
	}

	return ov5640_write_multi_regs(&cfg->i2c, brightness_params, ARRAY_SIZE(brightness_params));
}

static int ov5640_set_ctrl_contrast(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	if (!IN_RANGE(value, 0, UINT8_MAX)) {
		return -EINVAL;
	}

	int ret = ov5640_modify_reg(&cfg->i2c, SDE_CTRL0_REG, BIT(2), BIT(2));

	if (ret) {
		return ret;
	}

	return ov5640_write_reg(&cfg->i2c, SDE_CTRL6_REG, value & 0xff);
}

static int ov5640_set_ctrl_gain(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	if (!IN_RANGE(value, 0, UINT16_MAX)) {
		return -EINVAL;
	}

	if (value) {
		int ret = ov5640_modify_reg(&cfg->i2c, AEC_PK_MANUAL, BIT(1), BIT(0));

		if (ret) {
			return ret;
		}

		struct ov5640_reg gain_params[] = {{AEC_PK_REAL_GAIN, value >> 8},
						   {AEC_PK_REAL_GAIN + 1, value & 0xff}};

		return ov5640_write_multi_regs(&cfg->i2c, gain_params, ARRAY_SIZE(gain_params));
	} else {
		return ov5640_write_reg(&cfg->i2c, AEC_PK_MANUAL, 0);
	}
}

static int ov5640_set_ctrl_hflip(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	return ov5640_modify_reg(&cfg->i2c, TIMING_TC_REG21_REG, BIT(2) | BIT(1),
				 value ? 0 : BIT(2) | BIT(1));
}

static int ov5640_set_ctrl_vflip(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;

	return ov5640_modify_reg(&cfg->i2c, TIMING_TC_REG20_REG, BIT(2) | BIT(1),
				 value ? BIT(2) | BIT(1) : 0);
}

static int ov5640_set_ctrl_power_line_freq(const struct device *dev, int value)
{
	const struct ov5640_config *cfg = dev->config;
	int ret;

	switch (value) {
	case VIDEO_CID_POWER_LINE_FREQUENCY_AUTO:
		ret = ov5640_modify_reg(&cfg->i2c, HZ5060_CTRL01_REG, BIT(7), 0);
		return ret;
	case VIDEO_CID_POWER_LINE_FREQUENCY_50HZ:
		ret = ov5640_modify_reg(&cfg->i2c, HZ5060_CTRL00_REG, BIT(2), BIT(2));
		break;
	case VIDEO_CID_POWER_LINE_FREQUENCY_60HZ:
		ret = ov5640_modify_reg(&cfg->i2c, HZ5060_CTRL00_REG, BIT(2), 0);
		break;
	default:
		return -EINVAL;
	}

	if (ret) {
		return ret;
	}

	return ov5640_modify_reg(&cfg->i2c, HZ5060_CTRL01_REG, BIT(7), BIT(7));
}

static int ov5640_set_ctrl(const struct device *dev, unsigned int cid, void *value)
{
	switch (cid) {
	case VIDEO_CID_TEST_PATTERN:
		return ov5640_set_ctrl_test_pattern(dev, (int)value);
	case VIDEO_CID_HUE:
		return ov5640_set_ctrl_hue(dev, (int)value);
	case VIDEO_CID_SATURATION:
		return ov5640_set_ctrl_saturation(dev, (int)(value));
	case VIDEO_CID_BRIGHTNESS:
		return ov5640_set_ctrl_brightness(dev, (int)(value));
	case VIDEO_CID_CONTRAST:
		return ov5640_set_ctrl_contrast(dev, (int)value);
	case VIDEO_CID_GAIN:
		return ov5640_set_ctrl_gain(dev, (int)(value));
	case VIDEO_CID_HFLIP:
		return ov5640_set_ctrl_hflip(dev, (int)(value));
	case VIDEO_CID_VFLIP:
		return ov5640_set_ctrl_vflip(dev, (int)(value));
	case VIDEO_CID_POWER_LINE_FREQUENCY:
		return ov5640_set_ctrl_power_line_freq(dev, (int)(value));
	default:
		return -ENOTSUP;
	}
}

static inline int ov5640_get_ctrl(const struct device *dev, unsigned int cid, void *value)
{
	struct ov5640_data *drv_data = dev->data;

	switch (cid) {
	case VIDEO_CID_PIXEL_RATE:
		*((uint64_t *)value) = drv_data->cur_pixrate;

		return 0;
	default:
		return -ENOTSUP;
	}
}

static int ov5640_get_frmival(const struct device *dev, enum video_endpoint_id ep,
			      struct video_frmival *frmival)
{
	struct ov5640_data *drv_data = dev->data;

	frmival->numerator = 1;
	frmival->denominator = drv_data->cur_frmrate;

	return 0;
}

static int ov5640_enum_frmival(const struct device *dev, enum video_endpoint_id ep,
			       struct video_frmival_enum *fie)
{
	uint8_t i = 0;

	for (i = 0; i < ARRAY_SIZE(modes); i++) {
		if (fie->format->width == modes[i].width &&
		    fie->format->height == modes[i].height) {
			break;
		}
	}
	if (i == ARRAY_SIZE(modes) || fie->index >= ARRAY_SIZE(ov5640_frame_rates) ||
	    ov5640_frame_rates[fie->index] > modes[i].max_frmrate) {
		return -EINVAL;
	}

	fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE;
	fie->discrete.numerator = 1;
	fie->discrete.denominator = ov5640_frame_rates[fie->index];

	return 0;
}

static DEVICE_API(video, ov5640_driver_api) = {
	.set_format = ov5640_set_fmt,
	.get_format = ov5640_get_fmt,
	.get_caps = ov5640_get_caps,
	.stream_start = ov5640_stream_start,
	.stream_stop = ov5640_stream_stop,
	.set_ctrl = ov5640_set_ctrl,
	.get_ctrl = ov5640_get_ctrl,
	.set_frmival = ov5640_set_frmival,
	.get_frmival = ov5640_get_frmival,
	.enum_frmival = ov5640_enum_frmival,
};

static int ov5640_init(const struct device *dev)
{
	const struct ov5640_config *cfg = dev->config;
	struct video_format fmt;
	uint16_t chip_id;
	int ret;

	if (!device_is_ready(cfg->i2c.bus)) {
		LOG_ERR("Bus device is not ready");
		return -ENODEV;
	}

	if (!gpio_is_ready_dt(&cfg->reset_gpio)) {
		LOG_ERR("%s: device %s is not ready", dev->name, cfg->reset_gpio.port->name);
		return -ENODEV;
	}

	if (!gpio_is_ready_dt(&cfg->powerdown_gpio)) {
		LOG_ERR("%s: device %s is not ready", dev->name, cfg->powerdown_gpio.port->name);
		return -ENODEV;
	}

	/* Power up sequence */
	if (cfg->powerdown_gpio.port != NULL) {
		ret = gpio_pin_configure_dt(&cfg->powerdown_gpio, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	if (cfg->reset_gpio.port != NULL) {
		ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	k_sleep(K_MSEC(5));

	if (cfg->powerdown_gpio.port != NULL) {
		gpio_pin_set_dt(&cfg->powerdown_gpio, 0);
	}

	k_sleep(K_MSEC(1));

	if (cfg->reset_gpio.port != NULL) {
		gpio_pin_set_dt(&cfg->reset_gpio, 0);
	}

	k_sleep(K_MSEC(20));

	/* Software reset */
	ret = ov5640_write_reg(&cfg->i2c, SYS_CTRL0_REG, SYS_CTRL0_SW_RST);
	if (ret) {
		LOG_ERR("Unable to perform software reset");
		return -EIO;
	}

	k_sleep(K_MSEC(5));

	/* Initialize register values */
	ret = ov5640_write_multi_regs(&cfg->i2c, init_params, ARRAY_SIZE(init_params));
	if (ret) {
		LOG_ERR("Unable to initialize the sensor");
		return -EIO;
	}

	/* Set virtual channel */
	ret = ov5640_modify_reg(&cfg->i2c, 0x4814, 3U << 6, (uint8_t)(DEFAULT_MIPI_CHANNEL) << 6);
	if (ret) {
		LOG_ERR("Unable to set virtual channel");
		return -EIO;
	}

	/* Check sensor chip id */
	ret = ov5640_read_reg(&cfg->i2c, CHIP_ID_REG, &chip_id, sizeof(chip_id));
	if (ret) {
		LOG_ERR("Unable to read sensor chip ID, ret = %d", ret);
		return -ENODEV;
	}

	if (chip_id != CHIP_ID_VAL) {
		LOG_ERR("Wrong chip ID: %04x (expected %04x)", chip_id, CHIP_ID_VAL);
		return -ENODEV;
	}

	/* Set default format */
	fmt.pixelformat = VIDEO_PIX_FMT_RGB565;
	fmt.width = 1280;
	fmt.height = 720;
	fmt.pitch = fmt.width * 2;
	ret = ov5640_set_fmt(dev, VIDEO_EP_OUT, &fmt);
	if (ret) {
		LOG_ERR("Unable to configure default format");
		return -EIO;
	}

	return 0;
}

#define OV5640_INIT(n)                                                                             \
	static struct ov5640_data ov5640_data_##n;                                                 \
                                                                                                   \
	static const struct ov5640_config ov5640_cfg_##n = {                                       \
		.i2c = I2C_DT_SPEC_INST_GET(n),                                                    \
		.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}),                       \
		.powerdown_gpio = GPIO_DT_SPEC_INST_GET_OR(n, powerdown_gpios, {0}),               \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(n, &ov5640_init, NULL, &ov5640_data_##n, &ov5640_cfg_##n,            \
			      POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &ov5640_driver_api);

DT_INST_FOREACH_STATUS_OKAY(OV5640_INIT)
