/*
 * Copyright (c) 2019, Linaro Limited
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT aptina_mt9m114
#include <zephyr/kernel.h>
#include <zephyr/device.h>

#include <zephyr/sys/byteorder.h>

#include <zephyr/drivers/video.h>
#include <zephyr/drivers/i2c.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mt9m114);

#define MT9M114_CHIP_ID_VAL				0x2481

/* Sysctl registers */
#define MT9M114_CHIP_ID					0x0000
#define MT9M114_COMMAND_REGISTER			0x0080
#define MT9M114_COMMAND_REGISTER_APPLY_PATCH		(1 << 0)
#define MT9M114_COMMAND_REGISTER_SET_STATE		(1 << 1)
#define MT9M114_COMMAND_REGISTER_REFRESH		(1 << 2)
#define MT9M114_COMMAND_REGISTER_WAIT_FOR_EVENT		(1 << 3)
#define MT9M114_COMMAND_REGISTER_OK			(1 << 15)
#define MT9M114_PAD_CONTROL				0x0032
#define MT9M114_RST_AND_MISC_CONTROL			0x001A

/* Camera Control registers */
#define MT9M114_CAM_OUTPUT_FORMAT			0xc86c

/* System Manager registers */
#define MT9M114_SYSMGR_NEXT_STATE			0xdc00
#define MT9M114_SYSMGR_CURRENT_STATE			0xdc01
#define MT9M114_SYSMGR_CMD_STATUS			0xdc02

/* System States */
#define MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE		0x28
#define MT9M114_SYS_STATE_STREAMING			0x31
#define MT9M114_SYS_STATE_START_STREAMING		0x34
#define MT9M114_SYS_STATE_ENTER_SUSPEND			0x40
#define MT9M114_SYS_STATE_SUSPENDED			0x41
#define MT9M114_SYS_STATE_ENTER_STANDBY			0x50
#define MT9M114_SYS_STATE_STANDBY			0x52
#define MT9M114_SYS_STATE_LEAVE_STANDBY			0x54

/* Camera output format */
#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV		(0 << 8)
#define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB		(1 << 8)

struct mt9m114_config {
	struct i2c_dt_spec i2c;
};

struct mt9m114_data {
	struct video_format fmt;
};

struct mt9m114_reg {
	uint16_t addr;
	uint16_t value_size;
	uint32_t value;
};

static struct mt9m114_reg mt9m114_vga_24mhz_pll[] = {
	{ 0x98E,  2, 0x1000	},
	{ 0xC97E, 2, 0x01	}, /* cam_sysctl_pll_enable = 1 */
	{ 0xC980, 2, 0x0120	}, /* cam_sysctl_pll_divider_m_n = 288 */
	{ 0xC982, 2, 0x0700	}, /* cam_sysctl_pll_divider_p = 1792 */
	{ 0xC984, 2, 0x8000	}, /* cam_port_output_control = 32776 */
	{ 0xC800, 2, 0x0000	}, /* cam_sensor_cfg_y_addr_start = 0 */
	{ 0xC802, 2, 0x0000	}, /* cam_sensor_cfg_x_addr_start = 0 */
	{ 0xC804, 2, 0x03CD	}, /* cam_sensor_cfg_y_addr_end = 973 */
	{ 0xC806, 2, 0x050D	}, /* cam_sensor_cfg_x_addr_end = 1293 */
	{ 0xC808, 4, 0x2DC6C00	}, /* cam_sensor_cfg_pixclk = 48000000 */
	{ 0xC80C, 2, 0x0001	}, /* cam_sensor_cfg_row_speed = 1 */
	{ 0xC80E, 2, 0x00DB	}, /* cam_sensor_cfg_fine_integ_min = 219 */
	{ 0xC810, 2, 0x07C2	}, /* cam_sensor_cfg_fine_integ_max = 1986 */
	{ 0xC812, 2, 0x02FE	}, /* cam_sensor_cfg_frame_length_lines = 766 */
	{ 0xC814, 2, 0x0845	}, /* cam_sensor_cfg_line_length_pck = 2117 */
	{ 0xC816, 2, 0x0060	}, /* cam_sensor_cfg_fine_correction = 96 */
	{ 0xC818, 2, 0x01E3	}, /* cam_sensor_cfg_cpipe_last_row = 483 */
	{ 0xC826, 2, 0x0020	}, /* cam_sensor_cfg_reg_0_data = 32 */
	{ 0xC834, 2, 0x0110	}, /* cam_sensor_control_read_mode = 272 */
	{ 0xC854, 2, 0x0000	}, /* cam_crop_window_xoffset = 0 */
	{ 0xC856, 2, 0x0000	}, /* cam_crop_window_yoffset = 0 */
	{ 0xC858, 2, 0x0280	}, /* cam_crop_window_width = 640 */
	{ 0xC85A, 2, 0x01E0	}, /* cam_crop_window_height = 480 */
	{ 0xC85C, 1, 0x03	}, /* cam_crop_cropmode = 3 */
	{ 0xC868, 2, 0x0280	}, /* cam_output_width = 640 */
	{ 0xC86A, 2, 0x01E0	}, /* cam_output_height = 480 */
	{ 0xC878, 1, 0x00	}, /* cam_aet_aemode = 0 */
	{ 0xC88C, 2, 0x1D9A	}, /* cam_aet_max_frame_rate = 7578 */
	{ 0xC914, 2, 0x0000	}, /* cam_stat_awb_clip_window_xstart = 0 */
	{ 0xC88E, 2, 0x1D9A	}, /* cam_aet_min_frame_rate = 7578 */
	{ 0xC916, 2, 0x0000	}, /* cam_stat_awb_clip_window_ystart = 0 */
	{ 0xC918, 2, 0x027F	}, /* cam_stat_awb_clip_window_xend = 639 */
	{ 0xC91A, 2, 0x01DF	}, /* cam_stat_awb_clip_window_yend = 479 */
	{ 0xC91C, 2, 0x0000	}, /* cam_stat_ae_initial_window_xstart = 0 */
	{ 0xC91E, 2, 0x0000	}, /* cam_stat_ae_initial_window_ystart = 0 */
	{ 0xC920, 2, 0x007F	}, /* cam_stat_ae_initial_window_xend = 127 */
	{ 0xC922, 2, 0x005F	}, /* cam_stat_ae_initial_window_yend = 95 */
	{ /* NULL terminated */ }
};

static inline int i2c_burst_read16_dt(const struct i2c_dt_spec *spec,
				   uint16_t start_addr, uint8_t *buf, uint32_t num_bytes)
{
	uint8_t addr_buffer[2];

	addr_buffer[1] = start_addr & 0xFF;
	addr_buffer[0] = start_addr >> 8;
	return i2c_write_read_dt(spec, addr_buffer, sizeof(addr_buffer), buf, num_bytes);
}

static inline int i2c_burst_write16_dt(const struct i2c_dt_spec *spec,
				    uint16_t start_addr, const uint8_t *buf,
				    uint32_t num_bytes)
{
	uint8_t addr_buffer[2];
	struct i2c_msg msg[2];

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

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

	return i2c_transfer_dt(spec, msg, 2);
}

static int mt9m114_write_reg(const struct device *dev, uint16_t reg_addr,
			     uint8_t reg_size,
			     void *value)
{
	const struct mt9m114_config *cfg = dev->config;

	switch (reg_size) {
	case 2:
		*(uint16_t *)value = sys_cpu_to_be16(*(uint16_t *)value);
		break;
	case 4:
		*(uint32_t *)value = sys_cpu_to_be32(*(uint32_t *)value);
		break;
	case 1:
		break;
	default:
		return -ENOTSUP;
	}

	return i2c_burst_write16_dt(&cfg->i2c, reg_addr, value, reg_size);
}

static int mt9m114_read_reg(const struct device *dev, uint16_t reg_addr,
			    uint8_t reg_size,
			    void *value)
{
	const struct mt9m114_config *cfg = dev->config;
	int err;

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

	err = i2c_burst_read16_dt(&cfg->i2c, reg_addr, value, reg_size);
	if (err) {
		return err;
	}

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

	return 0;
}

static int mt9m114_write_all(const struct device *dev,
			     struct mt9m114_reg *reg)
{
	int i = 0;

	while (reg[i].value_size) {
		int err;

		err = mt9m114_write_reg(dev, reg[i].addr, reg[i].value_size,
					&reg[i].value);
		if (err) {
			return err;
		}

		i++;
	}

	return 0;
}

static int mt9m114_set_state(const struct device *dev, uint8_t state)
{
	uint16_t val;
	int err;

	/* Set next state. */
	mt9m114_write_reg(dev, MT9M114_SYSMGR_NEXT_STATE, 1, &state);

	/* Check that the FW is ready to accept a new command. */
	while (1) {
		err = mt9m114_read_reg(dev, MT9M114_COMMAND_REGISTER, 2, &val);
		if (err) {
			return err;
		}

		if (!(val & MT9M114_COMMAND_REGISTER_SET_STATE)) {
			break;
		}

		k_sleep(K_MSEC(1));
	}

	/* Issue the Set State command. */
	val = MT9M114_COMMAND_REGISTER_SET_STATE | MT9M114_COMMAND_REGISTER_OK;
	mt9m114_write_reg(dev, MT9M114_COMMAND_REGISTER, 2, &val);

	/* Wait for the FW to complete the command. */
	while (1) {
		err = mt9m114_read_reg(dev, MT9M114_COMMAND_REGISTER, 2, &val);
		if (err) {
			return err;
		}

		if (!(val & MT9M114_COMMAND_REGISTER_SET_STATE)) {
			break;
		}

		k_sleep(K_MSEC(1));
	}

	/* Check the 'OK' bit to see if the command was successful. */
	err = mt9m114_read_reg(dev, MT9M114_COMMAND_REGISTER, 2, &val);
	if (err || !(val & MT9M114_COMMAND_REGISTER_OK)) {
		return -EIO;
	}

	return 0;
}

static int mt9m114_set_output_format(const struct device *dev,
				int pixel_format)
{
	int ret = 0;
	uint16_t output_format;

	if (pixel_format == VIDEO_PIX_FMT_YUYV) {
		output_format = (MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV | (1U << 1U));
	} else if (pixel_format == VIDEO_PIX_FMT_RGB565) {
		output_format = (MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB | (1U << 1U));
	} else {
		LOG_ERR("Image format not supported");
		return -ENOTSUP;
	}

	ret = mt9m114_write_reg(dev, MT9M114_CAM_OUTPUT_FORMAT,
				sizeof(output_format), &output_format);

	return ret;
}

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

	/* we support RGB565 and YUV output pixel formats for now */
	if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565 && fmt->pixelformat != VIDEO_PIX_FMT_YUYV) {
		LOG_ERR("Unsupported output pixel format");
		return -ENOTSUP;
	}

	/* we only support one format size for now (VGA) */
	if (fmt->height != 480 || fmt->width != 640) {
		LOG_ERR("Unsupported output size format");
		return -ENOTSUP;
	}

	if (!memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt))) {
		/* nothing to do */
		return 0;
	}

	drv_data->fmt = *fmt;

	/* Configure Sensor */
	ret = mt9m114_write_all(dev, mt9m114_vga_24mhz_pll);
	if (ret) {
		LOG_ERR("Unable to write mt9m114 config");
		return ret;
	}

	/* Set output format */
	ret = mt9m114_set_output_format(dev, fmt->pixelformat);
	if (ret) {
		LOG_ERR("Unable to set output format");
		return ret;
	}

	/* Apply Config */
	mt9m114_set_state(dev, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE);

	return 0;
}

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

	*fmt = drv_data->fmt;

	return 0;
}

static int mt9m114_stream_start(const struct device *dev)
{
	return mt9m114_set_state(dev, MT9M114_SYS_STATE_START_STREAMING);
}

static int mt9m114_stream_stop(const struct device *dev)
{
	return mt9m114_set_state(dev, MT9M114_SYS_STATE_ENTER_SUSPEND);
}

#define MT9M114_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[] = {
	MT9M114_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565),  	/* VGA  RGB565 	*/
	MT9M114_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_YUYV),   	/* VGA  YUYV 	*/
	{ 0 }
};

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

static const struct video_driver_api mt9m114_driver_api = {
	.set_format = mt9m114_set_fmt,
	.get_format = mt9m114_get_fmt,
	.get_caps = mt9m114_get_caps,
	.stream_start = mt9m114_stream_start,
	.stream_stop = mt9m114_stream_stop,
};

static int mt9m114_init(const struct device *dev)
{
	struct video_format fmt;
	uint16_t val;
	int ret;

	/* no power control, wait for camera ready */
	k_sleep(K_MSEC(100));

	ret = mt9m114_read_reg(dev, MT9M114_CHIP_ID, sizeof(val), &val);
	if (ret) {
		LOG_ERR("Unable to read chip ID");
		return -ENODEV;
	}

	if (val != MT9M114_CHIP_ID_VAL) {
		LOG_ERR("Wrong ID: %04x (exp %04x)", val, MT9M114_CHIP_ID_VAL);
		return -ENODEV;
	}

	/* set default/init format VGA RGB565 */
	fmt.pixelformat = VIDEO_PIX_FMT_RGB565;
	fmt.width = 640;
	fmt.height = 480;
	fmt.pitch = 640 * 2;

	ret = mt9m114_set_fmt(dev, VIDEO_EP_OUT, &fmt);
	if (ret) {
		LOG_ERR("Unable to configure default format");
		return -EIO;
	}

	/* Suspend any stream */
	mt9m114_set_state(dev, MT9M114_SYS_STATE_ENTER_SUSPEND);

	return 0;
}

#if 1 /* Unique Instance */

static const struct mt9m114_config mt9m114_cfg_0 = {
	.i2c = I2C_DT_SPEC_INST_GET(0),
};

static struct mt9m114_data mt9m114_data_0;

static int mt9m114_init_0(const struct device *dev)
{
	const struct mt9m114_config *cfg = dev->config;

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

	return mt9m114_init(dev);
}

DEVICE_DT_INST_DEFINE(0, &mt9m114_init_0, NULL,
		    &mt9m114_data_0, &mt9m114_cfg_0,
		    POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY,
		    &mt9m114_driver_api);
#endif
