/* * Copyright 2024 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ovti_ov5640 #include #include #include #include #include #include #include #include #include #include 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, ®Val, 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)