diff --git a/doc/releases/migration-guide-4.4.rst b/doc/releases/migration-guide-4.4.rst index 93eb5a0606796..656d4abb86c64 100644 --- a/doc/releases/migration-guide-4.4.rst +++ b/doc/releases/migration-guide-4.4.rst @@ -113,6 +113,11 @@ Networking code cannot use POSIX APIs, then the relevant network API prefix needs to be added to the code calling a network API. +Video +***** + +* ``CONFIG_VIDEO_HIMAX_HM01B0`` has been renamed into :kconfig:option:`CONFIG_VIDEO_HM01B0`. + Other subsystems **************** diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 5fb9cbdc5d1a4..e22295b93514a 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -11,7 +11,7 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_IMAGER video_emul_imager.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_RX video_emul_rx.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_ESP32 video_esp32_dvp.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_GC2145 gc2145.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_HIMAX_HM01B0 hm01b0.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_HM01B0 hm01b0.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_HM0360 hm0360.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX335 imx335.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) diff --git a/drivers/video/Kconfig.hm01b0 b/drivers/video/Kconfig.hm01b0 index b8b97d9974f15..a84dfed8f8649 100644 --- a/drivers/video/Kconfig.hm01b0 +++ b/drivers/video/Kconfig.hm01b0 @@ -1,6 +1,6 @@ # Copyright The Zephyr Project Contributors # SPDX-License-Identifier: Apache-2.0 -config VIDEO_HIMAX_HM01B0 +config VIDEO_HM01B0 bool "Real-time monochrome camera Himax HM01B0 sensor" depends on DT_HAS_HIMAX_HM01B0_ENABLED select I2C diff --git a/drivers/video/hm01b0.c b/drivers/video/hm01b0.c index 74672494455ca..ec062bdbb192c 100644 --- a/drivers/video/hm01b0.c +++ b/drivers/video/hm01b0.c @@ -6,6 +6,7 @@ #define DT_DRV_COMPAT himax_hm01b0 +#include #include #include #include @@ -14,49 +15,111 @@ #include #include -#include "video_device.h" #include "video_common.h" +#include "video_ctrls.h" +#include "video_device.h" LOG_MODULE_REGISTER(hm01b0, CONFIG_VIDEO_LOG_LEVEL); #define MAX_FRAME_RATE 10 #define MIN_FRAME_RATE 1 #define HM01B0_ID 0x01B0 +#define CHECK_CONNECTION_DELAY_MS 1 #define HM01B0_LINE_LEN_PCLK_IDX 5 #define HM01B0_REG8(addr) ((addr) | VIDEO_REG_ADDR16_DATA8) #define HM01B0_REG16(addr) ((addr) | VIDEO_REG_ADDR16_DATA16_BE) #define HM01B0_CCI_ID HM01B0_REG16(0x0000) #define HM01B0_CCI_STS HM01B0_REG8(0x0100) +#define HM01B0_CCI_IMG_ORIENTATION HM01B0_REG8(0x0101) #define HM01B0_CCI_RESET HM01B0_REG8(0x0103) #define HM01B0_CCI_GRP_PARAM_HOLD HM01B0_REG8(0x0104) #define HM01B0_CCI_INTEGRATION_H HM01B0_REG16(0x0202) +#define HM01B0_CCI_ANALOG_GAIN HM01B0_REG8(0x0205) +#define HM01B0_CCI_DIGITAL_GAIN_H HM01B0_REG16(0x020E) #define HM01B0_CCI_FRAME_LENGTH_LINES HM01B0_REG16(0x0340) #define HM01B0_CCI_LINE_LENGTH_PCLK HM01B0_REG16(0x0342) #define HM01B0_CCI_WIDTH HM01B0_REG8(0x0383) #define HM01B0_CCI_HEIGHT HM01B0_REG8(0x0387) #define HM01B0_CCI_BINNING_MODE HM01B0_REG8(0x0390) +#define HM01B0_CCI_BLC_CFG HM01B0_REG8(0x1000) +#define HM01B0_CCI_BLC_TGT HM01B0_REG8(0x1003) +#define HM01B0_CCI_BLI_EN HM01B0_REG8(0x1006) +#define HM01B0_CCI_BLC2_TGT HM01B0_REG8(0x1007) +#define HM01B0_CCI_DPC_CTRL HM01B0_REG8(0x1008) +#define HM01B0_CCI_SINGLE_THR_HOT HM01B0_REG8(0x100B) +#define HM01B0_CCI_SINGLE_THR_COLD HM01B0_REG8(0x100C) +#define HM01B0_CCI_STATISTIC_CTRL HM01B0_REG8(0x2000) +#define HM01B0_CCI_AE_CTRL HM01B0_REG8(0x2100) +#define HM01B0_CCI_AE_TARGET_MEAN HM01B0_REG8(0x2101) +#define HM01B0_CCI_AE_MIN_MEAN HM01B0_REG8(0x2102) +#define HM01B0_CCI_CONVERGE_IN_TH HM01B0_REG8(0x2103) +#define HM01B0_CCI_CONVERGE_OUT_TH HM01B0_REG8(0x2104) +#define HM01B0_CCI_MAX_INTG HM01B0_REG16(0x2105) +#define HM01B0_CCI_MIN_INTG HM01B0_REG8(0x2107) +#define HM01B0_CCI_MAX_AGAIN_FULL HM01B0_REG8(0x2108) +#define HM01B0_CCI_MAX_AGAIN_BIN2 HM01B0_REG8(0x2109) +#define HM01B0_CCI_MIN_AGAIN HM01B0_REG8(0x210A) +#define HM01B0_CCI_MAX_DGAIN HM01B0_REG8(0x210B) +#define HM01B0_CCI_MIN_DGAIN HM01B0_REG8(0x210C) +#define HM01B0_CCI_DAMPING_FACTOR HM01B0_REG8(0x210D) +#define HM01B0_CCI_FS_CTRL HM01B0_REG8(0x210E) +#define HM01B0_CCI_FS_60HZ_H HM01B0_REG16(0x210F) +#define HM01B0_CCI_FS_50HZ_H HM01B0_REG16(0x2111) +#define HM01B0_CCI_FS_HYST_TH HM01B0_REG8(0x2113) +#define HM01B0_CCI_MD_CTRL HM01B0_REG8(0x2150) #define HM01B0_CCI_QVGA_WIN_EN HM01B0_REG8(0x3010) #define HM01B0_CCI_BIT_CONTROL HM01B0_REG8(0x3059) #define HM01B0_CCI_OSC_CLOCK_DIV HM01B0_REG8(0x3060) +#define HM01B0_CCI_ANA_REGISTER_17 HM01B0_REG8(0x3067) + +#define HM01B0_ORIENTATION_HMIRROR BIT(0) +#define HM01B0_ORIENTATION_VMIRROR BIT(1) +#define HM01B0_CCI_OSC_CLOCK_DIV_VT_REG_DIV_1 0x08 /* vt_reg_div[3:2]=10 */ #define HM01B0_CTRL_VAL(data_bits) \ ((data_bits) == 8 ? 0x02 : \ (data_bits) == 4 ? 0x42 : \ (data_bits) == 1 ? 0x22 : 0x00) +/* Note: Bayer versions do not support 160x120 type settings */ enum hm01b0_resolution { RESOLUTION_160x120, RESOLUTION_320x240, RESOLUTION_320x320, + RESOLUTION_162x122, + RESOLUTION_324x244, + RESOLUTION_324x324, + RESOLUTION_162x122_Y4, + RESOLUTION_324x244_Y4, + RESOLUTION_324x324_Y4, + RESOLUTION_324x244_BAYER, + RESOLUTION_324x324_BAYER, }; +enum { + HM01B0_5_OR_8_FPS, + HM01B0_11_OR_15_FPS, + HM01B0_22_OR_30_FPS, + HM01B0_45_OR_60_FPS, +}; + +#define HIMAX_LINE_LEN_PCK_FULL 0x178 +#define HIMAX_FRAME_LENGTH_FULL 0x109 + +#define HIMAX_LINE_LEN_PCK_QVGA 0x178 +#define HIMAX_FRAME_LENGTH_QVGA 0x104 + +#define HIMAX_LINE_LEN_PCK_QQVGA 0x178 +#define HIMAX_FRAME_LENGTH_QQVGA 0x084 + struct video_reg hm01b0_160x120_regs[] = { {HM01B0_CCI_WIDTH, 0x3}, {HM01B0_CCI_HEIGHT, 0x3}, {HM01B0_CCI_BINNING_MODE, 0x3}, {HM01B0_CCI_QVGA_WIN_EN, 0x1}, - {HM01B0_CCI_FRAME_LENGTH_LINES, 0x80}, - {HM01B0_CCI_LINE_LENGTH_PCLK, 0xD7}, + {HM01B0_CCI_MAX_INTG, HIMAX_FRAME_LENGTH_QQVGA - 2}, + {HM01B0_CCI_FRAME_LENGTH_LINES, HIMAX_FRAME_LENGTH_QQVGA}, + {HM01B0_CCI_LINE_LENGTH_PCLK, HIMAX_LINE_LEN_PCK_QQVGA}, }; struct video_reg hm01b0_320x240_regs[] = { @@ -64,8 +127,9 @@ struct video_reg hm01b0_320x240_regs[] = { {HM01B0_CCI_HEIGHT, 0x1}, {HM01B0_CCI_BINNING_MODE, 0x0}, {HM01B0_CCI_QVGA_WIN_EN, 0x1}, - {HM01B0_CCI_FRAME_LENGTH_LINES, 0x104}, - {HM01B0_CCI_LINE_LENGTH_PCLK, 0x178}, + {HM01B0_CCI_MAX_INTG, HIMAX_FRAME_LENGTH_QVGA - 2}, + {HM01B0_CCI_FRAME_LENGTH_LINES, HIMAX_FRAME_LENGTH_QVGA}, + {HM01B0_CCI_LINE_LENGTH_PCLK, HIMAX_LINE_LEN_PCK_QVGA}, }; struct video_reg hm01b0_320x320_regs[] = { @@ -73,24 +137,126 @@ struct video_reg hm01b0_320x320_regs[] = { {HM01B0_CCI_HEIGHT, 0x1}, {HM01B0_CCI_BINNING_MODE, 0x0}, {HM01B0_CCI_QVGA_WIN_EN, 0x0}, - {HM01B0_CCI_FRAME_LENGTH_LINES, 0x158}, - {HM01B0_CCI_LINE_LENGTH_PCLK, 0x178}, + {HM01B0_CCI_MAX_INTG, HIMAX_FRAME_LENGTH_QVGA - 2}, + {HM01B0_CCI_FRAME_LENGTH_LINES, HIMAX_FRAME_LENGTH_FULL}, + {HM01B0_CCI_LINE_LENGTH_PCLK, HIMAX_LINE_LEN_PCK_FULL}, }; struct video_reg *hm01b0_init_regs[] = { [RESOLUTION_160x120] = hm01b0_160x120_regs, [RESOLUTION_320x240] = hm01b0_320x240_regs, [RESOLUTION_320x320] = hm01b0_320x320_regs, + [RESOLUTION_162x122] = hm01b0_160x120_regs, + [RESOLUTION_324x244] = hm01b0_320x240_regs, + [RESOLUTION_324x324] = hm01b0_320x320_regs, + [RESOLUTION_162x122_Y4] = hm01b0_160x120_regs, + [RESOLUTION_324x244_Y4] = hm01b0_320x240_regs, + [RESOLUTION_324x324_Y4] = hm01b0_320x320_regs, + [RESOLUTION_324x244_BAYER] = hm01b0_320x240_regs, + [RESOLUTION_324x324_BAYER] = hm01b0_320x320_regs, +}; + +struct video_reg hm01b0_default_regs[] = { + {HM01B0_CCI_BLC_TGT, 0x08}, /* BLC target :8 at 8 bit mode */ + {HM01B0_CCI_BLC2_TGT, 0x08}, /* BLI target :8 at 8 bit mode */ + {HM01B0_REG8(0x3044), 0x0A}, /* Increase CDS time for settling */ + {HM01B0_REG8(0x3045), 0x00}, /* Make symmetric for cds_tg and rst_tg */ + {HM01B0_REG8(0x3047), 0x0A}, /* Increase CDS time for settling */ + {HM01B0_REG8(0x3050), 0xC0}, /* Make negative offset up to 4x */ + {HM01B0_REG8(0x3051), 0x42}, + {HM01B0_REG8(0x3052), 0x50}, + {HM01B0_REG8(0x3053), 0x00}, + {HM01B0_REG8(0x3054), 0x03}, /* tuning sf sig clamping as lowest */ + {HM01B0_REG8(0x3055), 0xF7}, /* tuning dsun */ + {HM01B0_REG8(0x3056), 0xF8}, /* increase adc nonoverlap clk */ + {HM01B0_REG8(0x3057), 0x29}, /* increase adc pwr for missing code */ + {HM01B0_REG8(0x3058), 0x1F}, /* turn on dsun */ + {HM01B0_REG8(0x3059), 0x1E}, + {HM01B0_REG8(0x3064), 0x00}, + {HM01B0_REG8(0x3065), 0x04}, /* pad pull 0 */ + {HM01B0_CCI_ANA_REGISTER_17, 0x00}, /* Disable internal oscillator */ + + {HM01B0_CCI_BLC_CFG, 0x43}, /* BLC_on, IIR */ + + {HM01B0_REG8(0x1001), 0x43}, /* BLC dithering en */ + {HM01B0_REG8(0x1002), 0x43}, /* blc_darkpixel_thd */ + {HM01B0_REG8(0x0350), 0x7F}, /* Dgain Control */ + {HM01B0_CCI_BLI_EN, 0x01}, /* BLI enable */ + {HM01B0_REG8(0x1003), 0x00}, /* BLI Target [Def: 0x20] */ + + {HM01B0_CCI_DPC_CTRL, 0x01}, /* DPC option 0:DPC off 1:mono3:bayer1 5:bayer2 */ + {HM01B0_REG8(0x1009), 0xA0}, /* cluster hot pixel th */ + {HM01B0_REG8(0x100A), 0x60}, /* cluster cold pixel th */ + {HM01B0_CCI_SINGLE_THR_HOT, 0x90}, /* single hot pixel th */ + {HM01B0_CCI_SINGLE_THR_COLD, 0x40}, /* single cold pixel th */ + {HM01B0_REG8(0x1012), 0x00}, /* Sync. shift disable */ + {HM01B0_CCI_STATISTIC_CTRL, 0x07}, /* AE stat en | MD LROI stat en | magic */ + {HM01B0_REG8(0x2003), 0x00}, + {HM01B0_REG8(0x2004), 0x1C}, + {HM01B0_REG8(0x2007), 0x00}, + {HM01B0_REG8(0x2008), 0x58}, + {HM01B0_REG8(0x200B), 0x00}, + {HM01B0_REG8(0x200C), 0x7A}, + {HM01B0_REG8(0x200F), 0x00}, + {HM01B0_REG8(0x2010), 0xB8}, + {HM01B0_REG8(0x2013), 0x00}, + {HM01B0_REG8(0x2014), 0x58}, + {HM01B0_REG8(0x2017), 0x00}, + {HM01B0_REG8(0x2018), 0x9B}, + + {HM01B0_CCI_AE_CTRL, 0x01}, /* Automatic Exposure */ + {HM01B0_CCI_AE_TARGET_MEAN, 0x64}, /* AE target mean [Def: 0x3C] */ + {HM01B0_CCI_AE_MIN_MEAN, 0x0A}, /* AE min target mean [Def: 0x0A] */ + {HM01B0_CCI_CONVERGE_IN_TH, 0x03}, /* Converge in threshold [Def: 0x03] */ + {HM01B0_CCI_CONVERGE_OUT_TH, 0x05}, /* Converge out threshold [Def: 0x05] */ + {HM01B0_CCI_MAX_INTG, (HIMAX_FRAME_LENGTH_QVGA - 2)}, /* Max INTG High Byte */ + {HM01B0_CCI_MAX_AGAIN_FULL, 0x04}, /* Max Analog gain full frame mode [Def: 0x03] */ + {HM01B0_CCI_MAX_AGAIN_BIN2, 0x04}, /* Max Analog gain bin2 mode [Def: 0x04] */ + {HM01B0_CCI_MAX_DGAIN, 0xC0}, + + {HM01B0_CCI_INTEGRATION_H, 0x0108}, /* Integration H [Def: 0x01] */ + {HM01B0_CCI_ANALOG_GAIN, 0x00}, /* Analog Global Gain [Def: 0x00] */ + {HM01B0_CCI_DAMPING_FACTOR, 0x20}, /* Damping Factor [Def: 0x20] */ + {HM01B0_CCI_DIGITAL_GAIN_H, 0x0100}, /* Digital Gain High [Def: 0x01] */ + + {HM01B0_CCI_FS_CTRL, 0x00}, /* Flicker Control */ + + {HM01B0_CCI_FS_60HZ_H, 0x003C}, + {HM01B0_CCI_FS_50HZ_H, 0x0032}, + + {HM01B0_CCI_MD_CTRL, 0x00}, + {HM01B0_CCI_FRAME_LENGTH_LINES, HIMAX_FRAME_LENGTH_QVGA}, + {HM01B0_CCI_LINE_LENGTH_PCLK, HIMAX_LINE_LEN_PCK_QVGA}, + {HM01B0_CCI_QVGA_WIN_EN, 0x01}, /* Enable QVGA window readout */ + {HM01B0_REG8(0x0383), 0x01}, + {HM01B0_REG8(0x0387), 0x01}, + {HM01B0_REG8(0x0390), 0x00}, + {HM01B0_REG8(0x3011), 0x70}, + {HM01B0_REG8(0x3059), 0x02}, + {HM01B0_CCI_OSC_CLOCK_DIV, 0x0A}, /* vt_sys_div / 2 */ + {HM01B0_CCI_IMG_ORIENTATION, 0x00}, /* change the orientation */ + {HM01B0_REG8(0x0104), 0x01}, +}; + +struct hm01b0_ctrls { + struct video_ctrl hflip; + struct video_ctrl vflip; }; struct hm01b0_data { + struct hm01b0_ctrls ctrls; struct video_format fmt; }; struct hm01b0_config { const struct i2c_dt_spec i2c; - const uint8_t data_bits; const uint8_t ctrl_val; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + struct gpio_dt_spec reset; +#endif +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) + struct gpio_dt_spec pwdn; +#endif }; #define HM01B0_VIDEO_FORMAT_CAP(width, height, format) \ @@ -105,9 +271,17 @@ struct hm01b0_config { } static const struct video_format_cap hm01b0_fmts[] = { - HM01B0_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_GREY), - HM01B0_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_GREY), - HM01B0_VIDEO_FORMAT_CAP(320, 320, VIDEO_PIX_FMT_GREY), + [RESOLUTION_160x120] = HM01B0_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_GREY), + [RESOLUTION_320x240] = HM01B0_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_GREY), + [RESOLUTION_320x320] = HM01B0_VIDEO_FORMAT_CAP(320, 320, VIDEO_PIX_FMT_GREY), + [RESOLUTION_162x122] = HM01B0_VIDEO_FORMAT_CAP(162, 122, VIDEO_PIX_FMT_GREY), + [RESOLUTION_324x244] = HM01B0_VIDEO_FORMAT_CAP(324, 244, VIDEO_PIX_FMT_GREY), + [RESOLUTION_324x324] = HM01B0_VIDEO_FORMAT_CAP(324, 324, VIDEO_PIX_FMT_GREY), + [RESOLUTION_162x122_Y4] = HM01B0_VIDEO_FORMAT_CAP(162, 122, VIDEO_PIX_FMT_Y4), + [RESOLUTION_324x244_Y4] = HM01B0_VIDEO_FORMAT_CAP(324, 244, VIDEO_PIX_FMT_Y4), + [RESOLUTION_324x324_Y4] = HM01B0_VIDEO_FORMAT_CAP(324, 324, VIDEO_PIX_FMT_Y4), + [RESOLUTION_324x244_BAYER] = HM01B0_VIDEO_FORMAT_CAP(324, 244, VIDEO_PIX_FMT_SBGGR8), + [RESOLUTION_324x324_BAYER] = HM01B0_VIDEO_FORMAT_CAP(324, 324, VIDEO_PIX_FMT_SBGGR8), {0}, }; @@ -131,13 +305,6 @@ static int hm01b0_apply_configuration(const struct device *dev, enum hm01b0_reso return ret; } - /* OSC_CLK_DIV */ - ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_OSC_CLOCK_DIV, 0x08); - if (ret < 0) { - LOG_ERR("Failed to write OSC_CLK_DIV reg (%d)", ret); - return ret; - } - /* INTEGRATION_H */ ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_INTEGRATION_H, hm01b0_init_regs[resolution][HM01B0_LINE_LEN_PCLK_IDX].data / 2); @@ -239,11 +406,167 @@ static int hm01b0_soft_reset(const struct device *dev) return ret; } +static int hm01b0_set_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct hm01b0_config *config = dev->config; + struct hm01b0_data *drv_data = dev->data; + int ret; + uint32_t osc_div = 0; + struct video_frmival_enum fie; + + fie.format = &drv_data->fmt; + fie.discrete = *frmival; + fie.type = VIDEO_FRMIVAL_TYPE_DISCRETE; + video_closest_frmival(dev, &fie); + + /* + * Note: in highres mode max FPS is 45 else 60 + * OSC clock divider: 00=/8, 01=/4, 10=/2, 11=/1 + * The return index will be the same value that + * we use for the osc_div field of OSC_CLOCK_DIV + */ + osc_div = fie.index; + LOG_DBG("%u %u %u", fie.index, fie.discrete.numerator, fie.discrete.denominator); + + /* We also set the VT_REG_DIV bits[3:2] to 10 (0x2) or /1 */ + osc_div |= HM01B0_CCI_OSC_CLOCK_DIV_VT_REG_DIV_1; + + ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_OSC_CLOCK_DIV, osc_div); + if (ret < 0) { + LOG_ERR("Failed to write OSC_CLK_DIV = %x reg (%d)", osc_div, ret); + return ret; + } + + /* GRP_PARAM_HOLD */ + ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_GRP_PARAM_HOLD, 0x01); + if (ret < 0) { + LOG_ERR("Failed to write GRP_PARAM_HOLD reg (%d)", ret); + return ret; + } + + LOG_DBG("FrameRate selected: %d %d = %d", fie.discrete.numerator, fie.discrete.denominator, + fie.discrete.denominator / fie.discrete.numerator); + LOG_DBG("OSC DIV: %d", osc_div); + + return 0; +} + +static int hm01b0_get_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct hm01b0_config *config = dev->config; + struct hm01b0_data *drv_data = dev->data; + uint32_t reg; + int ret; + + /* lets compute the frmival from the HM01B0_CCI_OSC_CLOCK_DIV registervalue */ + ret = video_read_cci_reg(&config->i2c, HM01B0_CCI_OSC_CLOCK_DIV, ®); + if (ret < 0) { + LOG_ERR("Failed to write OSC_CLK_DIV reg (%d)", ret); + return ret; + } + + frmival->denominator = ((drv_data->fmt.width >= 320) + && (drv_data->fmt.height >= 320)) ? 45 : 60; + + /* 0=>8 1->4 2->2 3->1 */ + frmival->numerator = 8 >> (reg & 0x3); + + return 0; +} + +int hm01b0_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) +{ + struct hm01b0_data *drv_data = dev->data; + + fie->discrete.denominator = ((drv_data->fmt.width >= 320) + && (drv_data->fmt.height >= 320)) ? 45 : 60; + + switch (fie->index) { + case HM01B0_5_OR_8_FPS: + fie->discrete.numerator = 8; + break; + case HM01B0_11_OR_15_FPS: + fie->discrete.numerator = 4; + break; + case HM01B0_22_OR_30_FPS: + fie->discrete.numerator = 2; + break; + case HM01B0_45_OR_60_FPS: + /* if we are doing 4 bit mode don't allow highest speed */ + if (fie->format && (fie->format->pixelformat == VIDEO_PIX_FMT_Y4)) { + return -EINVAL; + } + fie->discrete.numerator = 1; + break; + default: + return -EINVAL; + } + + fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; + LOG_DBG("i:%u %u %u", fie->index, fie->discrete.numerator, fie->discrete.denominator); + + return 0; +} + +static int hm01b0_init_controls(const struct device *dev) +{ + int ret; + struct hm01b0_data *drv_data = dev->data; + struct hm01b0_ctrls *ctrls = &drv_data->ctrls; + + ret = video_init_ctrl(&ctrls->hflip, dev, VIDEO_CID_HFLIP, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 0}); + if (ret < 0) { + return ret; + } + + return video_init_ctrl(&ctrls->vflip, dev, VIDEO_CID_VFLIP, + (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 0}); +} + +static int hm01b0_set_ctrl(const struct device *dev, uint32_t id) +{ + const struct hm01b0_config *config = dev->config; + struct hm01b0_data *drv_data = dev->data; + struct hm01b0_ctrls *ctrls = &drv_data->ctrls; + int ret; + + switch (id) { + case VIDEO_CID_HFLIP: + ret = video_modify_cci_reg(&config->i2c, HM01B0_CCI_IMG_ORIENTATION, + HM01B0_ORIENTATION_HMIRROR, + FIELD_PREP(HM01B0_ORIENTATION_HMIRROR, + ctrls->hflip.val)); + if (ret < 0) { + return ret; + } + break; + case VIDEO_CID_VFLIP: + ret = video_modify_cci_reg(&config->i2c, HM01B0_CCI_IMG_ORIENTATION, + HM01B0_ORIENTATION_VMIRROR, + FIELD_PREP(HM01B0_ORIENTATION_VMIRROR, + ctrls->vflip.val)); + if (ret < 0) { + return ret; + } + break; + default: + CODE_UNREACHABLE; + } + + /* if we get here it implies that register modified so tell system to use it */ + return video_write_cci_reg(&config->i2c, HM01B0_CCI_GRP_PARAM_HOLD, 0x01); +} + static DEVICE_API(video, hm01b0_driver_api) = { .set_format = hm01b0_set_fmt, .get_format = hm01b0_get_fmt, + .set_ctrl = hm01b0_set_ctrl, .set_stream = hm01b0_set_stream, .get_caps = hm01b0_get_caps, + .set_frmival = hm01b0_set_frmival, + .get_frmival = hm01b0_get_frmival, + .enum_frmival = hm01b0_enum_frmival, }; static bool hm01b0_check_connection(const struct device *dev) @@ -263,8 +586,40 @@ static bool hm01b0_check_connection(const struct device *dev) static int hm01b0_init(const struct device *dev) { + const struct hm01b0_config __maybe_unused *config = dev->config; int ret; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) || DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) + /* Power up camera module */ + if (config->pwdn.port != NULL) { + if (!gpio_is_ready_dt(&config->pwdn)) { + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->pwdn, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + LOG_ERR("Could not clear power down pin: %d", ret); + return ret; + } + } +#endif +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + /* Reset camera module */ + if (config->reset.port != NULL) { + if (!gpio_is_ready_dt(&config->reset)) { + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + LOG_ERR("Could not set reset pin: %d", ret); + return ret; + } + } +#endif + /* give time for the camera to startup before checking it */ + k_msleep(CHECK_CONNECTION_DELAY_MS); +#endif + if (!hm01b0_check_connection(dev)) { LOG_ERR("%s is not ready", dev->name); return -ENODEV; @@ -276,6 +631,14 @@ static int hm01b0_init(const struct device *dev) return ret; } + /* Try to reset the registers to the same as Arduino did at reset */ + ret = video_write_cci_multiregs(&config->i2c, hm01b0_default_regs, + ARRAY_SIZE(hm01b0_default_regs)); + if (ret < 0) { + LOG_ERR("Failed to write config list registers (%d)", ret); + return ret; + } + struct video_format fmt = { .pixelformat = VIDEO_PIX_FMT_GREY, .width = 160, @@ -289,19 +652,33 @@ static int hm01b0_init(const struct device *dev) return ret; } - return 0; + /* Initialize controls */ + return hm01b0_init_controls(dev); } -#define HM01B0_INIT(inst) \ - const struct hm01b0_config hm01b0_config_##inst = { \ - .i2c = I2C_DT_SPEC_INST_GET(inst), \ - .data_bits = DT_INST_PROP(inst, data_bits), \ - .ctrl_val = HM01B0_CTRL_VAL(DT_INST_PROP(inst, data_bits)), \ - }; \ - struct hm01b0_data hm01b0_data_##inst; \ - DEVICE_DT_INST_DEFINE(inst, &hm01b0_init, NULL, &hm01b0_data_##inst, \ - &hm01b0_config_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ - &hm01b0_driver_api); \ +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) +#define HM01B0_RESET_GPIO(inst) .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}), +#else +#define HM01B0_RESET_GPIO(inst) +#endif + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) +#define HM01B0_PWDN_GPIO(inst) .pwdn = GPIO_DT_SPEC_INST_GET_OR(inst, pwdn_gpios, {}), +#else +#define HM01B0_PWDN_GPIO(inst) +#endif + +#define HM01B0_INIT(inst) \ + const struct hm01b0_config hm01b0_config_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .ctrl_val = HM01B0_CTRL_VAL(DT_INST_PROP(inst, data_bits)), \ + HM01B0_RESET_GPIO(inst) \ + HM01B0_PWDN_GPIO(inst) \ + }; \ + struct hm01b0_data hm01b0_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, &hm01b0_init, NULL, &hm01b0_data_##inst, \ + &hm01b0_config_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ + &hm01b0_driver_api); \ VIDEO_DEVICE_DEFINE(hm01b0_##inst, DEVICE_DT_INST_GET(inst), NULL); DT_INST_FOREACH_STATUS_OKAY(HM01B0_INIT) diff --git a/dts/bindings/video/himax,hm01b0.yaml b/dts/bindings/video/himax,hm01b0.yaml index bf0a7d4fb1a43..c19161c071796 100644 --- a/dts/bindings/video/himax,hm01b0.yaml +++ b/dts/bindings/video/himax,hm01b0.yaml @@ -33,6 +33,16 @@ properties: Camera output data width. 1/4/8 bits. The default output data width value is 1, pixel value is serialised. enum: [1, 4, 8] + reset-gpios: + type: phandle-array + description: | + The RESETn pin is asserted to disable the sensor causing a hard + reset. The sensor receives this as an active-low signal. + pwdn-gpios: + type: phandle-array + description: | + The PWDN pin is asserted to power down the sensor. The sensor + receives this as an active high signal include: i2c-device.yaml diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index cf66a674c15a1..e3c9f5e742193 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -1377,6 +1377,15 @@ int video_transfer_buffer(const struct device *src, const struct device *sink, */ #define VIDEO_PIX_FMT_GREY VIDEO_FOURCC('G', 'R', 'E', 'Y') + +/** + * @code{.unparsed} + * 0 1 2 3 + * | 0000yyyy 0000Yyyy | 0000yyyy 0000Yyyy | 0000yyyy 0000Yyyy | 0000yyyy 0000Yyyy | ... + * @endcode + */ +#define VIDEO_PIX_FMT_Y4 VIDEO_FOURCC('Y', '0', '4', ' ') + /** * @code{.unparsed} * 0 1 2 3 3 2 1 0 @@ -1892,6 +1901,7 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) case VIDEO_PIX_FMT_Y16: case VIDEO_PIX_FMT_NV16: case VIDEO_PIX_FMT_NV61: + case VIDEO_PIX_FMT_Y4: return 16; case VIDEO_PIX_FMT_BGR24: case VIDEO_PIX_FMT_RGB24: