/* * Copyright 2017-2018, 2020 NXP * All rights reserved. * * * SPDX-License-Identifier: BSD-3-Clause */ #include "fsl_video_common.h" #include "fsl_camera.h" #include "fsl_camera_device.h" #include "fsl_ov7725.h" /******************************************************************************* * Definitions ******************************************************************************/ #define OV7725_SCCB_ADDR 0x21U #define OV7725_REVISION 0x7721U #define OV7725_WriteReg(handle, reg, val) \ SCCB_WriteReg(OV7725_SCCB_ADDR, kSCCB_RegAddr8Bit, (reg), (val), \ ((ov7725_resource_t *)((handle)->resource))->i2cSendFunc) #define OV7725_ReadReg(handle, reg, val) \ SCCB_ReadReg(OV7725_SCCB_ADDR, kSCCB_RegAddr8Bit, (reg), (val), \ ((ov7725_resource_t *)((handle)->resource))->i2cReceiveFunc) #define OV7725_ModifyReg(handle, reg, clrMask, val) \ SCCB_ModifyReg(OV7725_SCCB_ADDR, kSCCB_RegAddr8Bit, (reg), (clrMask), (val), \ ((ov7725_resource_t *)((handle)->resource))->i2cReceiveFunc, \ ((ov7725_resource_t *)((handle)->resource))->i2cSendFunc) #define OV7725_CHECK_RET(x) \ do \ { \ status = (x); \ if (kStatus_Success != status) \ { \ return status; \ } \ } while (false) typedef struct _ov7725_clock_config { uint32_t frameRate_Hz; uint32_t inputClk_Hz; uint8_t clkrc; /*!< Register CLKRC. */ uint8_t com4; /*!< Register COM4. */ uint8_t dm_lnl; /*!< Register DM_LNL. */ } ov7725_clock_config_t; typedef struct _ov7725_light_mode { uint8_t lightMode; uint8_t com8; uint8_t blue; uint8_t red; uint8_t com5; } ov7725_light_mode_config_t; typedef struct _ov7725_special_effect_config { uint8_t effect; uint8_t sde; uint8_t ufix; uint8_t vfix; } ov7725_special_effect_config_t; typedef struct _ov7725_night_mode { uint8_t nightMode; uint8_t com5; } ov7725_night_mode_t; typedef struct _ov7725_pixel_format_config { video_pixel_format_t fmt; uint8_t com7; } ov7725_pixel_format_config_t; typedef status_t (*ov7725_cmd_func_t)(camera_device_handle_t *handle, int32_t arg); typedef struct _ov7725_cmd_func_map { camera_device_cmd_t cmd; ov7725_cmd_func_t func; } ov7725_cmd_func_map_t; typedef struct _ov7725_reg { uint8_t reg; uint8_t val; } ov7725_reg_t; /******************************************************************************* * Prototypes ******************************************************************************/ status_t OV7725_Init(camera_device_handle_t *handle, const camera_config_t *config); status_t OV7725_InitExt(camera_device_handle_t *handle, const camera_config_t *config, const void *specialConfig); status_t OV7725_Deinit(camera_device_handle_t *handle); status_t OV7725_Control(camera_device_handle_t *handle, camera_device_cmd_t cmd, int32_t arg); status_t OV7725_Start(camera_device_handle_t *handle); status_t OV7725_Stop(camera_device_handle_t *handle); status_t OV7725_SetSpecialEffect(camera_device_handle_t *handle, int32_t effect); status_t OV7725_SetLightMode(camera_device_handle_t *handle, int32_t lightMode); status_t OV7725_SetNightMode(camera_device_handle_t *handle, int32_t nightMode); status_t OV7725_SetSaturation(camera_device_handle_t *handle, int32_t saturation); status_t OV7725_SetContrast(camera_device_handle_t *handle, int32_t contrast); status_t OV7725_SetBrightness(camera_device_handle_t *handle, int32_t brightness); /******************************************************************************* * Variables ******************************************************************************/ const camera_device_operations_t ov7725_ops = { .init = OV7725_Init, .deinit = OV7725_Deinit, .start = OV7725_Start, .stop = OV7725_Stop, .control = OV7725_Control, .init_ext = OV7725_InitExt, }; static const ov7725_clock_config_t ov7725ClockConfigs[] = { /* FrameRate, inputClk, clkrc, com4, dm_lnl. */ {.frameRate_Hz = 30, .inputClk_Hz = 24000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x00}, {.frameRate_Hz = 15, .inputClk_Hz = 24000000, .clkrc = 0x03, .com4 = 0x41, .dm_lnl = 0x00}, {.frameRate_Hz = 25, .inputClk_Hz = 24000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x66}, {.frameRate_Hz = 14, .inputClk_Hz = 24000000, .clkrc = 0x03, .com4 = 0x41, .dm_lnl = 0x1a}, {.frameRate_Hz = 30, .inputClk_Hz = 26000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x2b}, {.frameRate_Hz = 15, .inputClk_Hz = 26000000, .clkrc = 0x03, .com4 = 0x41, .dm_lnl = 0x2b}, {.frameRate_Hz = 25, .inputClk_Hz = 26000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x99}, {.frameRate_Hz = 14, .inputClk_Hz = 26000000, .clkrc = 0x03, .com4 = 0x41, .dm_lnl = 0x46}, {.frameRate_Hz = 30, .inputClk_Hz = 13000000, .clkrc = 0x00, .com4 = 0x41, .dm_lnl = 0x2b}, {.frameRate_Hz = 15, .inputClk_Hz = 13000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x2b}, {.frameRate_Hz = 25, .inputClk_Hz = 13000000, .clkrc = 0x00, .com4 = 0x41, .dm_lnl = 0x99}, {.frameRate_Hz = 14, .inputClk_Hz = 13000000, .clkrc = 0x01, .com4 = 0x41, .dm_lnl = 0x46}, }; static const ov7725_special_effect_config_t ov7725SpecialEffectConfigs[] = { /* Normal. */ {.effect = CAMERA_SPECIAL_EFFECT_NORMAL, .sde = 0x06, .ufix = 0x80, .vfix = 0x80}, /* B & W */ {.effect = CAMERA_SPECIAL_EFFECT_BW, .sde = 0x26, .ufix = 0x80, .vfix = 0x80}, /* Sepia. */ {.effect = CAMERA_SPECIAL_EFFECT_SEPIA, .sde = 0x1e, .ufix = 0x40, .vfix = 0xa0}, /* Bluish. */ {.effect = CAMERA_SPECIAL_EFFECT_BLUISH, .sde = 0x1e, .ufix = 0xa0, .vfix = 0x40}, /* Redish. */ {.effect = CAMERA_SPECIAL_EFFECT_REDISH, .sde = 0x1e, .ufix = 0x80, .vfix = 0x40}, /* Greenish. */ {.effect = CAMERA_SPECIAL_EFFECT_GREENISH, .sde = 0x1e, .ufix = 0x60, .vfix = 0x60}, /* Negtive. */ {.effect = CAMERA_SPECIAL_EFFECT_NEGTIVE, .sde = 0x46, .ufix = 0x00, .vfix = 0x00}, }; static const ov7725_light_mode_config_t ov7725LightModeConfigs[] = { /* Auto. */ {.lightMode = CAMERA_LIGHT_MODE_AUTO, .com8 = 0xff, .blue = 0x80, .red = 0x80, .com5 = 0x65}, /* Sunny. */ {.lightMode = CAMERA_LIGHT_MODE_SUNNY, .com8 = 0xfd, .blue = 0x5a, .red = 0x5c, .com5 = 0x65}, /* Cloudy. */ {.lightMode = CAMERA_LIGHT_MODE_CLOUDY, .com8 = 0xfd, .blue = 0x58, .red = 0x60, .com5 = 0x65}, /* Office. */ {.lightMode = CAMERA_LIGHT_MODE_OFFICE, .com8 = 0xfd, .blue = 0x84, .red = 0x4c, .com5 = 0x65}, /* Home. */ {.lightMode = CAMERA_LIGHT_MODE_HOME, .com8 = 0xfd, .blue = 0x96, .red = 0x40, .com5 = 0x65}, /* Night. */ {.lightMode = CAMERA_LIGHT_MODE_NIGHT, .com8 = 0xff, .blue = 0x80, .red = 0x80, .com5 = 0xe5}}; static const ov7725_pixel_format_config_t ov7725PixelFormatConfigs[] = { {.fmt = kVIDEO_PixelFormatYUYV, .com7 = (0)}, {.fmt = kVIDEO_PixelFormatRGB565, .com7 = (1 << 2) | (2)}, {.fmt = kVIDEO_PixelFormatXRGB1555, .com7 = (2 << 2) | (2)}, {.fmt = kVIDEO_PixelFormatXRGB4444, .com7 = (3 << 2) | (2)}}; static const ov7725_night_mode_t ov7725NightModeConfigs[] = { {.nightMode = CAMERA_NIGHT_MODE_DISABLED, .com5 = 0}, {.nightMode = CAMERA_NIGHT_MODE_AUTO_FR_DIVBY2, .com5 = 8 | (1 << 4)}, {.nightMode = CAMERA_NIGHT_MODE_AUTO_FR_DIVBY4, .com5 = 8 | (2 << 4)}, {.nightMode = CAMERA_NIGHT_MODE_AUTO_FR_DIVBY8, .com5 = 8 | (3 << 4)}}; static const ov7725_cmd_func_map_t ov7725CmdFuncMap[] = { { kCAMERA_DeviceLightMode, OV7725_SetLightMode, }, { kCAMERA_DeviceSaturation, OV7725_SetSaturation, }, { kCAMERA_DeviceBrightness, OV7725_SetBrightness, }, { kCAMERA_DeviceContrast, OV7725_SetContrast, }, { kCAMERA_DeviceSpecialEffect, OV7725_SetSpecialEffect, }, { kCAMERA_DeviceNightMode, OV7725_SetNightMode, }, }; static const ov7725_reg_t ov7725InitRegs[] = { {0x3d, 0x03}, {0x42, 0x7f}, {0x4d, 0x09}, /* DSP */ {0x64, 0xff}, {0x65, 0x20}, {0x66, 0x00}, {0x67, 0x48}, {0x0f, 0xc5}, {0x13, 0xff}, /* AEC/AGC/AWB */ {0x63, 0xe0}, {0x14, 0x11}, {0x22, 0x3f}, {0x23, 0x07}, {0x24, 0x40}, {0x25, 0x30}, {0x26, 0xa1}, {0x2b, 0x00}, {0x6b, 0xaa}, {0x0d, 0x41}, /* Sharpness. */ {0x90, 0x05}, {0x91, 0x01}, {0x92, 0x03}, {0x93, 0x00}, /* Matrix. */ {0x94, 0x90}, {0x95, 0x8a}, {0x96, 0x06}, {0x97, 0x0b}, {0x98, 0x95}, {0x99, 0xa0}, {0x9a, 0x1e}, /* Brightness. */ {0x9b, 0x08}, /* Contrast. */ {0x9c, 0x20}, /* UV */ {0x9e, 0x81}, /* DSE */ {0xa6, 0x04}, /* Gamma. */ {0x7e, 0x0c}, {0x7f, 0x16}, {0x80, 0x2a}, {0x81, 0x4e}, {0x82, 0x61}, {0x83, 0x6f}, {0x84, 0x7b}, {0x85, 0x86}, {0x86, 0x8e}, {0x87, 0x97}, {0x88, 0xa4}, {0x89, 0xaf}, {0x8a, 0xc5}, {0x8b, 0xd7}, {0x8c, 0xe8}, }; /******************************************************************************* * Code ******************************************************************************/ static void OV7725_DelayMs(uint32_t ms) { VIDEO_DelayMs(ms); } static status_t OV7725_WriteRegs(camera_device_handle_t *handle, const ov7725_reg_t regs[], uint32_t num) { status_t status = kStatus_Success; for (uint32_t i = 0; i < num; i++) { status = OV7725_WriteReg(handle, regs[i].reg, regs[i].val); if (kStatus_Success != status) { break; } } return status; } static status_t OV7725_SoftwareReset(camera_device_handle_t *handle) { return OV7725_WriteReg(handle, OV7725_COM7_REG, 0x80); } static status_t OV7725_SetClockConfig(camera_device_handle_t *handle, uint32_t frameRate_Hz, uint32_t inputClk_Hz) { status_t status; for (uint32_t i = 0; i < ARRAY_SIZE(ov7725ClockConfigs); i++) { if ((ov7725ClockConfigs[i].frameRate_Hz == frameRate_Hz) && (ov7725ClockConfigs[i].inputClk_Hz == inputClk_Hz)) { OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_CLKRC_REG, ov7725ClockConfigs[i].clkrc)); OV7725_CHECK_RET(OV7725_ModifyReg(handle, OV7725_COM4_REG, 0xC0, ov7725ClockConfigs[i].com4)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_EXHCL_REG, 0x00)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_DM_LNL_REG, ov7725ClockConfigs[i].dm_lnl)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_DM_LNH_REG, 0x00)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_ADVFL_REG, 0x00)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_ADVFH_REG, 0x00)); return OV7725_WriteReg(handle, OV7725_COM5_REG, 0x65); } } return kStatus_InvalidArgument; } static status_t OV7725_SetPixelFormat(camera_device_handle_t *handle, video_pixel_format_t fmt) { for (uint8_t i = 0; i < ARRAY_SIZE(ov7725PixelFormatConfigs); i++) { if (ov7725PixelFormatConfigs[i].fmt == fmt) { return OV7725_ModifyReg(handle, OV7725_COM7_REG, 0x1FU, ov7725PixelFormatConfigs[i].com7); } } return kStatus_InvalidArgument; } status_t OV7725_Init(camera_device_handle_t *handle, const camera_config_t *config) { status_t status; uint8_t pid = 0U, ver = 0U; uint8_t com10 = 0; uint8_t tmpReg; uint16_t width, height; uint16_t hstart, vstart, hsize; ov7725_resource_t *resource = (ov7725_resource_t *)(handle->resource); if ((kCAMERA_InterfaceNonGatedClock != config->interface) && (kCAMERA_InterfaceGatedClock != config->interface) && (kCAMERA_InterfaceCCIR656 != config->interface)) { return kStatus_InvalidArgument; } width = FSL_VIDEO_EXTRACT_WIDTH(config->resolution); height = FSL_VIDEO_EXTRACT_HEIGHT(config->resolution); if ((width > 640U) || (height > 480U)) { return kStatus_InvalidArgument; } resource->pullPowerDownPin(true); /* Delay 1ms. */ OV7725_DelayMs(1); resource->pullPowerDownPin(false); /* Delay 1ms. */ OV7725_DelayMs(1); resource->pullResetPin(false); /* Delay 1ms. */ OV7725_DelayMs(1); resource->pullResetPin(true); /* Delay 1ms. */ OV7725_DelayMs(1); /* Identify the device. */ status = OV7725_ReadReg(handle, OV7725_PID_REG, &pid); if (kStatus_Success != status) { return status; } status = OV7725_ReadReg(handle, OV7725_VER_REG, &ver); if (kStatus_Success != status) { return status; } if (OV7725_REVISION != (((uint32_t)pid << 8U) | (uint32_t)ver)) { return kStatus_Fail; } /* Device identify OK, perform software reset. */ OV7725_CHECK_RET(OV7725_SoftwareReset(handle)); /* Delay 2ms. */ OV7725_DelayMs(2); /* Start configuration */ status = OV7725_WriteRegs(handle, ov7725InitRegs, ARRAY_SIZE(ov7725InitRegs)); if (kStatus_Success != status) { return status; } /* Clock setting. */ status = OV7725_SetClockConfig(handle, config->framePerSec, resource->inputClockFreq_Hz); if (kStatus_Success != status) { return status; } /* Pixel format setting. */ status = OV7725_SetPixelFormat(handle, config->pixelFormat); if (kStatus_Success != status) { return status; } if (kCAMERA_InterfaceCCIR656 == config->interface) { status = OV7725_ModifyReg(handle, OV7725_COM7_REG, (1U << 5), (1U << 5)); width += 2U; } else { status = OV7725_ModifyReg(handle, OV7725_COM7_REG, (1U << 5), (0U << 5)); } if (kStatus_Success != status) { return status; } if ((uint32_t)kCAMERA_HrefActiveHigh != (config->controlFlags & (uint32_t)kCAMERA_HrefActiveHigh)) { com10 |= OV7725_COM10_HREF_REVERSE_MASK; } if ((uint32_t)kCAMERA_VsyncActiveHigh != (config->controlFlags & (uint32_t)kCAMERA_VsyncActiveHigh)) { com10 |= OV7725_COM10_VSYNC_NEG_MASK; } if ((uint32_t)kCAMERA_DataLatchOnRisingEdge != (config->controlFlags & (uint32_t)kCAMERA_DataLatchOnRisingEdge)) { com10 |= OV7725_COM10_PCLK_REVERSE_MASK; } if (kCAMERA_InterfaceNonGatedClock == config->interface) { com10 |= OV7725_COM10_PCLK_OUT_MASK; } OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_COM10_REG, com10)); /* Don't swap output MSB/LSB. */ OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_COM3_REG, 0x00)); /* * Output drive capability * 0: 1X * 1: 2X * 2: 3X * 3: 4X */ OV7725_CHECK_RET(OV7725_ModifyReg(handle, OV7725_COM2_REG, 0x03, 0x03)); /* Resolution and timing. */ hstart = 0x22U << 2U; vstart = 0x07U << 1U; hsize = width + 16U; /* Set the window size. */ OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_HSTART_REG, (uint8_t)(hstart >> 2U))); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_HSIZE_REG, (uint8_t)(hsize >> 2U))); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_VSTART_REG, (uint8_t)(vstart >> 1U))); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_VSIZE_REG, (uint8_t)(height >> 1U))); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_HOUTSIZE_REG, (uint8_t)(width >> 2U))); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_VOUTSIZE_REG, (uint8_t)(height >> 1U))); tmpReg = (((uint8_t)vstart & 1U) << 6U) | (((uint8_t)hstart & 3U) << 4U) | (((uint8_t)height & 1U) << 2U) | (((uint8_t)hsize & 3U) << 0U); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_HREF_REG, tmpReg)); return OV7725_WriteReg(handle, OV7725_EXHCH_REG, (((uint8_t)height & 1U) << 2U) | (((uint8_t)width & 3U) << 0U)); } status_t OV7725_SetSpecialEffect(camera_device_handle_t *handle, int32_t effect) { uint8_t i; status_t status; for (i = 0; i < ARRAY_SIZE(ov7725SpecialEffectConfigs); i++) { if (effect == (int32_t)ov7725SpecialEffectConfigs[i].effect) { OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_SDE_REG, ov7725SpecialEffectConfigs[i].sde)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_UFIX_REG, ov7725SpecialEffectConfigs[i].ufix)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_VFIX_REG, ov7725SpecialEffectConfigs[i].vfix)); return OV7725_ModifyReg(handle, OV7725_DSP_CTRL1_REG, 1U << 5, 1U << 5); } } /* No configuration found. */ return kStatus_InvalidArgument; } status_t OV7725_SetContrast(camera_device_handle_t *handle, int32_t contrast) { if ((contrast < -4) || (contrast > 4)) { return kStatus_InvalidArgument; } contrast *= 4; contrast += 0x20; return OV7725_WriteReg(handle, OV7725_CNST_REG, (uint8_t)contrast); } status_t OV7725_SetBrightness(camera_device_handle_t *handle, int32_t brightness) { status_t status; if ((brightness < -4) || (brightness > 4)) { return kStatus_InvalidArgument; } if (brightness >= 0) { OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_BRIGHT_REG, 0x08U + (0x10U * (uint8_t)brightness))); return OV7725_WriteReg(handle, OV7725_SIGN_REG, 0x06); } else { brightness = -brightness - 1; OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_BRIGHT_REG, 0x08U + (0x10U * (uint8_t)brightness))); return OV7725_WriteReg(handle, OV7725_SIGN_REG, 0x0e); } } status_t OV7725_SetSaturation(camera_device_handle_t *handle, int32_t saturation) { status_t status; if ((saturation < -4) || (saturation > 4)) { return kStatus_InvalidArgument; } saturation += 4; uint8_t val = (uint8_t)saturation * 0x10U; OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_USAT_REG, val)); return OV7725_WriteReg(handle, OV7725_VSAT_REG, val); } status_t OV7725_SetLightMode(camera_device_handle_t *handle, int32_t lightMode) { uint8_t i; status_t status; for (i = 0; i < ARRAY_SIZE(ov7725LightModeConfigs); i++) { if (lightMode == (int32_t)ov7725LightModeConfigs[i].lightMode) { OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_COM8_REG, ov7725LightModeConfigs[i].com8)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_BLUE_REG, ov7725LightModeConfigs[i].blue)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_RED_REG, ov7725LightModeConfigs[i].red)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_COM5_REG, ov7725LightModeConfigs[i].com5)); OV7725_CHECK_RET(OV7725_WriteReg(handle, OV7725_ADVFL_REG, 0)); return OV7725_WriteReg(handle, OV7725_ADVFH_REG, 0); } } /* No configuration found. */ return kStatus_InvalidArgument; } status_t OV7725_SetNightMode(camera_device_handle_t *handle, int32_t nightMode) { uint8_t i; for (i = 0; i < ARRAY_SIZE(ov7725NightModeConfigs); i++) { if (nightMode == (int32_t)ov7725NightModeConfigs[i].nightMode) { return OV7725_ModifyReg(handle, OV7725_COM5_REG, 0xF0, ov7725NightModeConfigs[i].com5); } } /* No configuration found. */ return kStatus_InvalidArgument; } status_t OV7725_Deinit(camera_device_handle_t *handle) { ((ov7725_resource_t *)(handle->resource))->pullPowerDownPin(true); return kStatus_Success; } status_t OV7725_Control(camera_device_handle_t *handle, camera_device_cmd_t cmd, int32_t arg) { for (uint8_t i = 0; i < ARRAY_SIZE(ov7725CmdFuncMap); i++) { if (ov7725CmdFuncMap[i].cmd == cmd) { return ov7725CmdFuncMap[i].func(handle, arg); } } return kStatus_InvalidArgument; } status_t OV7725_Start(camera_device_handle_t *handle) { return kStatus_Success; } status_t OV7725_Stop(camera_device_handle_t *handle) { return kStatus_Success; } status_t OV7725_InitExt(camera_device_handle_t *handle, const camera_config_t *config, const void *specialConfig) { return OV7725_Init(handle, config); }