blob: 1e16e9b39f5c6735f896a3dac1500b2c4b997470 [file] [edit]
/*
* Copyright The Zephyr Project Contributors
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT himax_hm0360
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/logging/log.h>
#include "video_common.h"
#include "video_ctrls.h"
#include "video_device.h"
LOG_MODULE_REGISTER(video_hm0360, CONFIG_VIDEO_LOG_LEVEL);
struct hm0360_config {
struct i2c_dt_spec bus;
#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
};
struct hm0360_ctrls {
struct video_ctrl hflip;
struct video_ctrl vflip;
};
struct hm0360_data {
struct hm0360_ctrls ctrls;
struct video_format fmt;
uint16_t cur_frmrate;
};
struct video_frmival default_frmival = {
.numerator = 1,
.denominator = 15
};
enum {
HM0360_60_FPS,
HM0360_30_FPS,
HM0360_15_FPS,
HM0360_10_FPS,
};
/* hm0360 registers */
#define HM0360_REG8(addr) ((addr) | VIDEO_REG_ADDR16_DATA8)
#define HM0360_REG16(addr) ((addr) | VIDEO_REG_ADDR16_DATA16_BE)
#define HM0360_CCI_ID HM0360_REG16(0x0000)
#define HM0360_CCI_RESET HM0360_REG8(0x0103)
#define HM0360_CCI_MODE HM0360_REG8(0x0100)
#define HM0360_CCI_ORIENTATION HM0360_REG8(0x0101)
#define HM0360_CCI_COMMAND_UPDATE HM0360_REG8(0x0104)
#define HM0360_CCI_PLL1_CONFIG HM0360_REG8(0x0300)
#define HM0360_CCI_H_SUBSAMPLE HM0360_REG8(0x0380)
#define HM0360_CCI_V_SUBSAMPLE HM0360_REG8(0x0381)
#define HM0360_CCI_BINNING_MODE HM0360_REG8(0x0382)
#define HM0360_CCI_WIN_MODE HM0360_REG8(0x3030)
#define HM0360_CCI_MAX_INTG HM0360_REG16(0x2029)
#define HM0360_CCI_FRAME_LEN_LINES HM0360_REG16(0x0340)
#define HM0360_CCI_LINE_LEN_PCK HM0360_REG16(0x0342)
#define HM0360_CCI_ROI_START_END_H HM0360_REG8(0x2082)
#define HM0360_CCI_ROI_START_END_V HM0360_REG8(0x2081)
#define HM0360_CCI_BIT_WIDTH HM0360_REG8(0x310F)
#define HM0360_CCI_PAD_REGISTER_07 HM0360_REG8(0x3112)
/* Sensor mode control */
#define HM0360_IMG_ORIENTATION 0x0101
#define HM0360_EMBEDDED_LINE_EN 0x0102
#define HM0360_COMMAND_UPDATE 0x0104
/* Sensor exposure gain control */
#define HM0360_INTEGRATION_H 0x0202
#define HM0360_INTEGRATION_L 0x0203
#define HM0360_ANALOG_GAIN 0x0205
#define HM0360_DIGITAL_GAIN_H 0x020E
#define HM0360_DIGITAL_GAIN_L 0x020F
/* Clock control */
#define HM0360_PLL1_CONFIG 0x0300
#define HM0360_PLL2_CONFIG 0x0301
#define HM0360_PLL3_CONFIG 0x0302
/* Frame timing control */
#define HM0360_FRAME_LEN_LINES_H 0x0340
#define HM0360_FRAME_LEN_LINES_L 0x0341
#define HM0360_LINE_LEN_PCK_H 0x0342
#define HM0360_LINE_LEN_PCK_L 0x0343
/* Monochrome programming */
#define HM0360_MONO_MODE 0x0370
#define HM0360_MONO_MODE_ISP 0x0371
#define HM0360_MONO_MODE_SEL 0x0372
/* Binning mode control */
#define HM0360_H_SUBSAMPLE 0x0380
#define HM0360_V_SUBSAMPLE 0x0381
#define HM0360_BINNING_MODE 0x0382
/* Test pattern control */
#define HM0360_TEST_PATTERN_MODE 0x0601
/* Black level control */
#define HM0360_BLC_TGT 0x1004
#define HM0360_BLC2_TGT 0x1009
#define HM0360_MONO_CTRL 0x100A
/* VSYNC / HSYNC / pixel shift registers */
#define HM0360_OPFM_CTRL 0x1014
/* Tone mapping registers */
#define HM0360_CMPRS_CTRL 0x102F
#define HM0360_CMPRS_01 0x1030
#define HM0360_CMPRS_02 0x1031
#define HM0360_CMPRS_03 0x1032
#define HM0360_CMPRS_04 0x1033
#define HM0360_CMPRS_05 0x1034
#define HM0360_CMPRS_06 0x1035
#define HM0360_CMPRS_07 0x1036
#define HM0360_CMPRS_08 0x1037
#define HM0360_CMPRS_09 0x1038
#define HM0360_CMPRS_10 0x1039
#define HM0360_CMPRS_11 0x103A
#define HM0360_CMPRS_12 0x103B
#define HM0360_CMPRS_13 0x103C
#define HM0360_CMPRS_14 0x103D
#define HM0360_CMPRS_15 0x103E
#define HM0360_CMPRS_16 0x103F
/* Automatic exposure control */
#define HM0360_AE_CTRL 0x2000
#define HM0360_AE_CTRL1 0x2001
#define HM0360_CNT_ORGH_H 0x2002
#define HM0360_CNT_ORGH_L 0x2003
#define HM0360_CNT_ORGV_H 0x2004
#define HM0360_CNT_ORGV_L 0x2005
#define HM0360_CNT_STH_H 0x2006
#define HM0360_CNT_STH_L 0x2007
#define HM0360_CNT_STV_H 0x2008
#define HM0360_CNT_STV_L 0x2009
#define HM0360_CTRL_PG_SKIPCNT 0x200A
#define HM0360_BV_WIN_WEIGHT_EN 0x200D
#define HM0360_MAX_INTG_H 0x2029
#define HM0360_MAX_INTG_L 0x202A
#define HM0360_MAX_AGAIN 0x202B
#define HM0360_MAX_DGAIN_H 0x202C
#define HM0360_MAX_DGAIN_L 0x202D
#define HM0360_MIN_INTG 0x202E
#define HM0360_MIN_AGAIN 0x202F
#define HM0360_MIN_DGAIN 0x2030
#define HM0360_T_DAMPING 0x2031
#define HM0360_N_DAMPING 0x2032
#define HM0360_ALC_TH 0x2033
#define HM0360_AE_TARGET_MEAN 0x2034
#define HM0360_AE_MIN_MEAN 0x2035
#define HM0360_AE_TARGET_ZONE 0x2036
#define HM0360_CONVERGE_IN_TH 0x2037
#define HM0360_CONVERGE_OUT_TH 0x2038
#define HM0360_FS_CTRL 0x203B
#define HM0360_FS_60HZ_H 0x203C
#define HM0360_FS_60HZ_L 0x203D
#define HM0360_FS_50HZ_H 0x203E
#define HM0360_FS_50HZ_L 0x203F
#define HM0360_FRAME_CNT_TH 0x205B
#define HM0360_AE_MEAN 0x205D
#define HM0360_AE_CONVERGE 0x2060
#define HM0360_AE_BLI_TGT 0x2070
/* Interrupt control */
#define HM0360_PULSE_MODE 0x2061
#define HM0360_PULSE_TH_H 0x2062
#define HM0360_PULSE_TH_L 0x2063
#define HM0360_INT_INDIC 0x2064
#define HM0360_INT_CLEAR 0x2065
/* Motion detection control */
#define HM0360_MD_CTRL 0x2080
#define HM0360_ROI_START_END_V 0x2081
#define HM0360_ROI_START_END_H 0x2082
#define HM0360_MD_TH_MIN 0x2083
#define HM0360_MD_TH_STR_L 0x2084
#define HM0360_MD_TH_STR_H 0x2085
#define HM0360_MD_LIGHT_COEF 0x2099
#define HM0360_MD_BLOCK_NUM_TH 0x209B
#define HM0360_MD_LATENCY 0x209C
#define HM0360_MD_LATENCY_TH 0x209D
#define HM0360_MD_CTRL1 0x209E
/* Context switch control registers */
#define HM0360_PMU_CFG_3 0x3024
#define HM0360_PMU_CFG_4 0x3025
/* Operation mode control */
#define HM0360_WIN_MODE 0x3030
/* IO and clock control */
#define HM0360_PAD_REGISTER_07 0x3112
/* Register bits/values */
#define HM0360_RESET 0x01
#define HM0360_MODE_STANDBY 0x00
#define HM0360_MODE_STREAMING 0x01 /* I2C triggered streaming enable */
#define HM0360_MODE_STREAMING_NFRAMES 0x03 /* Output N frames */
#define HM0360_MODE_STREAMING_TRIG 0x05 /* Hardware Trigger */
#define HM0360_ORIENTATION_HMIRROR BIT(0)
#define HM0360_ORIENTATION_VMIRROR BIT(1)
#define HM0360_PCLK_RISING_EDGE 0x00
#define HM0360_PCLK_FALLING_EDGE 0x01
#define HM0360_AE_CTRL_ENABLE 0x00
#define HM0360_AE_CTRL_DISABLE 0x01
/* hm0360 definitions */
#define HM0360_PROD_ID 0x0360
#define HM0360_LINE_LEN_PCK_VGA 0x0300
#define HM0360_FRAME_LENGTH_VGA 0x0214
#define HM0360_LINE_LEN_PCK_QVGA 0x0178
#define HM0360_FRAME_LENGTH_QVGA 0x0109
#define HM0360_LINE_LEN_PCK_QQVGA 0x0178
#define HM0360_FRAME_LENGTH_QQVGA 0x0084
/* Initialization register structure */
static const struct video_reg16 hm0360_default_regs[] = {
{HM0360_MONO_MODE, 0x00},
{HM0360_MONO_MODE_ISP, 0x01},
{HM0360_MONO_MODE_SEL, 0x01},
/* BLC control */
{0x1000, 0x01},
{0x1003, 0x04},
{HM0360_BLC_TGT, 0x04},
{0x1007, 0x01},
{0x1008, 0x04},
{HM0360_BLC2_TGT, 0x04},
{HM0360_MONO_CTRL, 0x01},
/* Output format control */
{HM0360_OPFM_CTRL, 0x0C},
/* Reserved regs */
{0x101D, 0x00},
{0x101E, 0x01},
{0x101F, 0x00},
{0x1020, 0x01},
{0x1021, 0x00},
{HM0360_CMPRS_CTRL, 0x00},
{HM0360_CMPRS_01, 0x09},
{HM0360_CMPRS_02, 0x12},
{HM0360_CMPRS_03, 0x23},
{HM0360_CMPRS_04, 0x31},
{HM0360_CMPRS_05, 0x3E},
{HM0360_CMPRS_06, 0x4B},
{HM0360_CMPRS_07, 0x56},
{HM0360_CMPRS_08, 0x5E},
{HM0360_CMPRS_09, 0x65},
{HM0360_CMPRS_10, 0x72},
{HM0360_CMPRS_11, 0x7F},
{HM0360_CMPRS_12, 0x8C},
{HM0360_CMPRS_13, 0x98},
{HM0360_CMPRS_14, 0xB2},
{HM0360_CMPRS_15, 0xCC},
{HM0360_CMPRS_16, 0xE6},
{HM0360_PLL1_CONFIG, 0x08}, /* Core = 24MHz PCLKO = 24MHz I2C = 12MHz */
{HM0360_PLL2_CONFIG, 0x0A}, /* MIPI pre-dev (default) */
{HM0360_PLL3_CONFIG, 0x77}, /* PMU/MIPI pre-dev (default) */
{HM0360_PMU_CFG_3, 0x08}, /* Disable context switching */
{HM0360_PAD_REGISTER_07, 0x00}, /* PCLKO_polarity falling */
{HM0360_AE_CTRL, 0x5F}, /* Automatic Exposure (NOTE: Auto framerate enabled) */
{HM0360_AE_CTRL1, 0x00},
{HM0360_T_DAMPING, 0x20}, /* AE T damping factor */
{HM0360_N_DAMPING, 0x00}, /* AE N damping factor */
{HM0360_AE_TARGET_MEAN, 0x64}, /* AE target */
{HM0360_AE_MIN_MEAN, 0x0A}, /* AE min target mean */
{HM0360_AE_TARGET_ZONE, 0x23}, /* AE target zone */
{HM0360_CONVERGE_IN_TH, 0x03}, /* AE converge in threshold */
{HM0360_CONVERGE_OUT_TH, 0x05}, /* AE converge out threshold */
{HM0360_MAX_INTG_H, (HM0360_FRAME_LENGTH_QVGA - 4) >> 8},
{HM0360_MAX_INTG_L, (HM0360_FRAME_LENGTH_QVGA - 4) & 0xFF},
{HM0360_MAX_AGAIN, 0x04}, /* Maximum analog gain */
{HM0360_MAX_DGAIN_H, 0x03},
{HM0360_MAX_DGAIN_L, 0x3F},
{HM0360_INTEGRATION_H, 0x01},
{HM0360_INTEGRATION_L, 0x08},
{HM0360_MD_CTRL, 0x6A},
{HM0360_MD_TH_MIN, 0x01},
{HM0360_MD_BLOCK_NUM_TH, 0x01},
{HM0360_MD_CTRL1, 0x06},
{HM0360_PULSE_MODE, 0x00}, /* Interrupt in level mode. */
{HM0360_ROI_START_END_V, 0xF0},
{HM0360_ROI_START_END_H, 0xF0},
{HM0360_FRAME_LEN_LINES_H, HM0360_FRAME_LENGTH_QVGA >> 8},
{HM0360_FRAME_LEN_LINES_L, HM0360_FRAME_LENGTH_QVGA & 0xFF},
{HM0360_LINE_LEN_PCK_H, HM0360_LINE_LEN_PCK_QVGA >> 8},
{HM0360_LINE_LEN_PCK_L, HM0360_LINE_LEN_PCK_QVGA & 0xFF},
{HM0360_H_SUBSAMPLE, 0x01},
{HM0360_V_SUBSAMPLE, 0x01},
{HM0360_BINNING_MODE, 0x00},
{HM0360_WIN_MODE, 0x00},
{HM0360_IMG_ORIENTATION, 0x00},
{HM0360_COMMAND_UPDATE, 0x01},
/* SYNC function config. */
{0x3010, 0x00},
{0x3013, 0x01},
{0x3019, 0x00},
{0x301A, 0x00},
{0x301B, 0x20},
{0x301C, 0xFF},
/* PREMETER config. */
{0x3026, 0x03},
{0x3027, 0x81},
{0x3028, 0x01},
{0x3029, 0x00},
{0x302A, 0x30},
{0x302E, 0x00},
{0x302F, 0x00},
/* Magic regs */
{0x302B, 0x2A},
{0x302C, 0x00},
{0x302D, 0x03},
{0x3031, 0x01},
{0x3051, 0x00},
{0x305C, 0x03},
{0x3060, 0x00},
{0x3061, 0xFA},
{0x3062, 0xFF},
{0x3063, 0xFF},
{0x3064, 0xFF},
{0x3065, 0xFF},
{0x3066, 0xFF},
{0x3067, 0xFF},
{0x3068, 0xFF},
{0x3069, 0xFF},
{0x306A, 0xFF},
{0x306B, 0xFF},
{0x306C, 0xFF},
{0x306D, 0xFF},
{0x306E, 0xFF},
{0x306F, 0xFF},
{0x3070, 0xFF},
{0x3071, 0xFF},
{0x3072, 0xFF},
{0x3073, 0xFF},
{0x3074, 0xFF},
{0x3075, 0xFF},
{0x3076, 0xFF},
{0x3077, 0xFF},
{0x3078, 0xFF},
{0x3079, 0xFF},
{0x307A, 0xFF},
{0x307B, 0xFF},
{0x307C, 0xFF},
{0x307D, 0xFF},
{0x307E, 0xFF},
{0x307F, 0xFF},
{0x3080, 0x01},
{0x3081, 0x01},
{0x3082, 0x03},
{0x3083, 0x20},
{0x3084, 0x00},
{0x3085, 0x20},
{0x3086, 0x00},
{0x3087, 0x20},
{0x3088, 0x00},
{0x3089, 0x04},
{0x3094, 0x02},
{0x3095, 0x02},
{0x3096, 0x00},
{0x3097, 0x02},
{0x3098, 0x00},
{0x3099, 0x02},
{0x309E, 0x05},
{0x309F, 0x02},
{0x30A0, 0x02},
{0x30A1, 0x00},
{0x30A2, 0x08},
{0x30A3, 0x00},
{0x30A4, 0x20},
{0x30A5, 0x04},
{0x30A6, 0x02},
{0x30A7, 0x02},
{0x30A8, 0x01},
{0x30A9, 0x00},
{0x30AA, 0x02},
{0x30AB, 0x34},
{0x30B0, 0x03},
{0x30C4, 0x10},
{0x30C5, 0x01},
{0x30C6, 0xBF},
{0x30C7, 0x00},
{0x30C8, 0x00},
{0x30CB, 0xFF},
{0x30CC, 0xFF},
{0x30CD, 0x7F},
{0x30CE, 0x7F},
{0x30D3, 0x01},
{0x30D4, 0xFF},
{0x30D5, 0x00},
{0x30D6, 0x40},
{0x30D7, 0x00},
{0x30D8, 0xA7},
{0x30D9, 0x05},
{0x30DA, 0x01},
{0x30DB, 0x40},
{0x30DC, 0x00},
{0x30DD, 0x27},
{0x30DE, 0x05},
{0x30DF, 0x07},
{0x30E0, 0x40},
{0x30E1, 0x00},
{0x30E2, 0x27},
{0x30E3, 0x05},
{0x30E4, 0x47},
{0x30E5, 0x30},
{0x30E6, 0x00},
{0x30E7, 0x27},
{0x30E8, 0x05},
{0x30E9, 0x87},
{0x30EA, 0x30},
{0x30EB, 0x00},
{0x30EC, 0x27},
{0x30ED, 0x05},
{0x30EE, 0x00},
{0x30EF, 0x40},
{0x30F0, 0x00},
{0x30F1, 0xA7},
{0x30F2, 0x05},
{0x30F3, 0x01},
{0x30F4, 0x40},
{0x30F5, 0x00},
{0x30F6, 0x27},
{0x30F7, 0x05},
{0x30F8, 0x07},
{0x30F9, 0x40},
{0x30FA, 0x00},
{0x30FB, 0x27},
{0x30FC, 0x05},
{0x30FD, 0x47},
{0x30FE, 0x30},
{0x30FF, 0x00},
{0x3100, 0x27},
{0x3101, 0x05},
{0x3102, 0x87},
{0x3103, 0x30},
{0x3104, 0x00},
{0x3105, 0x27},
{0x3106, 0x05},
{0x310B, 0x10},
{0x3113, 0xA0},
{0x3114, 0x67},
{0x3115, 0x42},
{0x3116, 0x10},
{0x3117, 0x0A},
{0x3118, 0x3F},
{0x311C, 0x10},
{0x311D, 0x06},
{0x311E, 0x0F},
{0x311F, 0x0E},
{0x3120, 0x0D},
{0x3121, 0x0F},
{0x3122, 0x00},
{0x3123, 0x1D},
{0x3126, 0x03},
{0x3128, 0x57},
{0x312A, 0x11},
{0x312B, 0x41},
{0x312E, 0x00},
{0x312F, 0x00},
{0x3130, 0x0C},
{0x3141, 0x2A},
{0x3142, 0x9F},
{0x3147, 0x18},
{0x3149, 0x18},
{0x314B, 0x01},
{0x3150, 0x50},
{0x3152, 0x00},
{0x3156, 0x2C},
{0x315A, 0x0A},
{0x315B, 0x2F},
{0x315C, 0xE0},
{0x315F, 0x02},
{0x3160, 0x1F},
{0x3163, 0x1F},
{0x3164, 0x7F},
{0x3165, 0x7F},
{0x317B, 0x94},
{0x317C, 0x00},
{0x317D, 0x02},
{0x318C, 0x00},
{0x310F, 0x00}, /* puts it in 8bit mode */
{0x3112, 0x04}, /* was 0x0c */
{HM0360_COMMAND_UPDATE, 0x01},
};
static const struct video_reg hm0360_qvga_regs[] = {
{HM0360_CCI_PLL1_CONFIG, 0x09}, /* Core=24MHz PCLKO=24MHz I2C= 12MHz */
{HM0360_CCI_H_SUBSAMPLE, 1},
{HM0360_CCI_V_SUBSAMPLE, 1},
{HM0360_CCI_BINNING_MODE, 0x00},
{HM0360_CCI_WIN_MODE, 0x01},
{HM0360_CCI_MAX_INTG, HM0360_FRAME_LENGTH_QVGA - 4},
{HM0360_CCI_FRAME_LEN_LINES, HM0360_FRAME_LENGTH_QVGA},
{HM0360_CCI_LINE_LEN_PCK, HM0360_LINE_LEN_PCK_QVGA},
{HM0360_CCI_ROI_START_END_H, 0xF0},
{HM0360_CCI_ROI_START_END_V, 0xE0},
{HM0360_CCI_BIT_WIDTH, 0x00}, /* added to get a good pic */
{HM0360_CCI_PAD_REGISTER_07, 0x04}, /* was 0x0c */
{HM0360_CCI_COMMAND_UPDATE, 0x01},
};
static const struct video_reg hm0360_vga_regs[] = {
{HM0360_CCI_PLL1_CONFIG, 0x08}, /* Core=24MHz PCLKO=24MHz I2C= 12MHz */
{HM0360_CCI_H_SUBSAMPLE, 0x00},
{HM0360_CCI_V_SUBSAMPLE, 0x00},
{HM0360_CCI_BINNING_MODE, 0x00},
{HM0360_CCI_WIN_MODE, 0x01},
{HM0360_CCI_MAX_INTG, HM0360_FRAME_LENGTH_VGA - 4},
{HM0360_CCI_FRAME_LEN_LINES, HM0360_FRAME_LENGTH_VGA},
{HM0360_CCI_LINE_LEN_PCK, HM0360_LINE_LEN_PCK_VGA},
{HM0360_CCI_ROI_START_END_H, 0xF0},
{HM0360_CCI_ROI_START_END_V, 0xE0},
{HM0360_CCI_BIT_WIDTH, 0x00}, /* added to get a good pic */
{HM0360_CCI_PAD_REGISTER_07, 0x04}, /* was 0x0c */
{HM0360_CCI_COMMAND_UPDATE, 0x01},
};
static const struct video_reg hm0360_qqvga_regs[] = {
{HM0360_CCI_PLL1_CONFIG, 0x09}, /* Core=24MHz PCLKO=24MHz I2C= 12MHz */
{HM0360_CCI_H_SUBSAMPLE, 0x02},
{HM0360_CCI_V_SUBSAMPLE, 0x02},
{HM0360_CCI_BINNING_MODE, 0x00},
{HM0360_CCI_WIN_MODE, 0x01},
{HM0360_CCI_MAX_INTG, HM0360_FRAME_LENGTH_QQVGA - 4},
{HM0360_CCI_FRAME_LEN_LINES, HM0360_FRAME_LENGTH_QQVGA},
{HM0360_CCI_LINE_LEN_PCK, HM0360_LINE_LEN_PCK_QQVGA},
{HM0360_CCI_ROI_START_END_H, 0xF0},
{HM0360_CCI_ROI_START_END_V, 0xD0},
{HM0360_CCI_COMMAND_UPDATE, 0x01},
};
#define HM0360_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[] = {
HM0360_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_GREY), /* QQVGA */
HM0360_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_GREY), /* QVGA */
HM0360_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_GREY), /* VGA */
{0}};
static int hm0360_get_caps(const struct device *dev, struct video_caps *caps)
{
caps->format_caps = fmts;
return 0;
}
static int hm0360_set_fmt(const struct device *dev, struct video_format *fmt)
{
const struct hm0360_config *config = dev->config;
struct hm0360_data *data = dev->data;
int ret;
size_t idx;
/* Note: hm0360 only supports grayscale */
LOG_DBG("HM01B0 set_fmt: %d x %d, fmt: %s", fmt->width, fmt->height,
VIDEO_FOURCC_TO_STR(fmt->pixelformat));
ret = video_format_caps_index(fmts, fmt, &idx);
if (ret < 0) {
return ret;
}
if (!memcmp(&data->fmt, fmt, sizeof(data->fmt))) {
/* nothing to do */
return 0;
}
memcpy(&data->fmt, fmt, sizeof(data->fmt));
switch (fmts[idx].width_min) {
case 160: /* QQVGA */
ret = video_write_cci_multiregs(&config->bus, hm0360_qqvga_regs,
ARRAY_SIZE(hm0360_qqvga_regs));
break;
case 320: /* QVGA */
ret = video_write_cci_multiregs(&config->bus, hm0360_qvga_regs,
ARRAY_SIZE(hm0360_qvga_regs));
break;
case 640: /* VGA */
ret = video_write_cci_multiregs(&config->bus, hm0360_vga_regs,
ARRAY_SIZE(hm0360_vga_regs));
break;
default:
CODE_UNREACHABLE;
}
return ret;
}
static int hm0360_get_fmt(const struct device *dev, struct video_format *fmt)
{
struct hm0360_data *data = dev->data;
*fmt = data->fmt;
return 0;
}
int hm0360_enum_frmival(const struct device *dev, struct video_frmival_enum *fie)
{
switch (fie->index) {
case HM0360_60_FPS:
fie->discrete.denominator = 60;
break;
case HM0360_30_FPS:
fie->discrete.denominator = 30;
break;
case HM0360_15_FPS:
fie->discrete.denominator = 15;
break;
case HM0360_10_FPS:
fie->discrete.denominator = 10;
break;
default:
return -EINVAL;
}
fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE;
fie->discrete.numerator = 1;
return 0;
}
static int hm0360_set_frmival(const struct device *dev, struct video_frmival *frmival)
{
const struct hm0360_config *config = dev->config;
struct hm0360_data *drv_data = dev->data;
struct video_frmival_enum fie = {.format = &drv_data->fmt, .discrete = *frmival};
int ret;
uint32_t osc_div = 0;
bool highres = false;
if (drv_data->fmt.width == 640 && drv_data->fmt.height == 480) {
highres = true;
}
video_closest_frmival(dev, &fie);
LOG_DBG("Applying frame interval number %u", fie.index);
switch (fie.index) {
case HM0360_60_FPS:
osc_div = highres ? 0x00 : 0x01;
break;
case HM0360_30_FPS:
osc_div = highres ? 0x01 : 0x02;
break;
case HM0360_15_FPS:
osc_div = highres ? 0x02 : 0x03;
break;
case HM0360_10_FPS:
osc_div = 0x03;
break;
default:
CODE_UNREACHABLE;
}
ret = video_modify_cci_reg(&config->bus, HM0360_CCI_PLL1_CONFIG, 0x03, osc_div);
if (ret < 0) {
LOG_ERR("Could not set PLL1 Config");
return ret;
}
LOG_DBG("FrameRate selected: %d", frmival->denominator);
frmival->denominator = fie.discrete.denominator;
drv_data->cur_frmrate = fie.discrete.denominator;
LOG_DBG("FrameRate rounded to: %d", fie.discrete.denominator);
LOG_DBG("HIRES Selected: %d", highres);
LOG_DBG("OSC DIV: 0x%x", osc_div);
return 0;
}
static int hm0360_get_frmival(const struct device *dev, struct video_frmival *frmival)
{
struct hm0360_data *drv_data = dev->data;
frmival->denominator = drv_data->cur_frmrate;
frmival->numerator = 1;
return 0;
}
static int hm0360_init_controls(const struct device *dev)
{
int ret;
struct hm0360_data *drv_data = dev->data;
struct hm0360_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) {
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 hm0360_init(const struct device *dev)
{
const struct hm0360_config *config = dev->config;
int ret;
struct video_format fmt = {
.pixelformat = fmts[0].pixelformat,
.width = fmts[0].width_min,
.height = fmts[0].height_min,
};
if (!i2c_is_ready_dt(&config->bus)) {
/* I2C device is not ready, return */
return -ENODEV;
}
#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;
}
/* Reset is active high, has 1ms settling time*/
ret = gpio_pin_set_dt(&config->reset, 0);
if (ret < 0) {
LOG_ERR("Error setting GPIO: %d\n", ret);
return ret;
}
k_msleep(1);
}
#endif
uint32_t pid = 0x0000;
/* Read Product ID and check if PID OK */
ret = video_read_cci_reg(&config->bus, HM0360_CCI_ID, &pid);
if (ret < 0) {
LOG_ERR("Could not read product ID: %d", ret);
return ret;
}
if (pid != HM0360_PROD_ID) {
LOG_ERR("Incorrect product ID: 0x%02X", pid);
return -ENODEV;
}
LOG_DBG("Product ID is: %x", pid);
/* Reset camera registers */
ret = video_write_cci_reg(&config->bus, HM0360_CCI_RESET, 0x01);
if (ret < 0) {
LOG_ERR("Could not write reset: %d", ret);
return ret;
}
/* Delay for 1ms. */
k_msleep(1);
/* Write Default registers */
ret = video_write_cci_multiregs16(&config->bus, hm0360_default_regs,
ARRAY_SIZE(hm0360_default_regs));
if (ret < 0) {
LOG_ERR("Failed to set default registers");
return ret;
}
ret = hm0360_set_fmt(dev, &fmt);
if (ret < 0) {
LOG_ERR("ERROR: Unable to set format");
return ret;
}
ret = hm0360_set_frmival(dev, &default_frmival);
if (ret < 0) {
LOG_ERR("ERROR: Unable to set frame rate");
return ret;
}
/* Initialize controls */
return hm0360_init_controls(dev);
}
static int hm0360_set_stream(const struct device *dev, bool enable, enum video_buf_type type)
{
int ret;
const struct hm0360_config *config = dev->config;
ret = video_write_cci_reg(&config->bus, HM0360_CCI_MODE, HM0360_MODE_STREAMING);
if (ret < 0) {
LOG_ERR("Could not set streaming");
return ret;
}
k_msleep(4);
return 0;
}
static int hm0360_set_ctrl(const struct device *dev, uint32_t id)
{
const struct hm0360_config *config = dev->config;
struct hm0360_data *drv_data = dev->data;
struct hm0360_ctrls *ctrls = &drv_data->ctrls;
int ret;
switch (id) {
case VIDEO_CID_HFLIP:
ret = video_modify_cci_reg(&config->bus, HM0360_CCI_ORIENTATION,
HM0360_ORIENTATION_HMIRROR,
FIELD_PREP(HM0360_ORIENTATION_HMIRROR, ctrls->hflip.val));
if (ret < 0) {
return ret;
}
break;
case VIDEO_CID_VFLIP:
ret = video_modify_cci_reg(&config->bus, HM0360_CCI_ORIENTATION,
HM0360_ORIENTATION_VMIRROR,
FIELD_PREP(HM0360_ORIENTATION_VMIRROR, ctrls->vflip.val));
if (ret < 0) {
return ret;
}
break;
default:
CODE_UNREACHABLE;
}
return video_write_cci_reg(&config->bus, HM0360_CCI_COMMAND_UPDATE, 0x01);
}
static DEVICE_API(video, hm0360_api) = {
.set_format = hm0360_set_fmt,
.get_format = hm0360_get_fmt,
.get_caps = hm0360_get_caps,
.set_stream = hm0360_set_stream,
.set_ctrl = hm0360_set_ctrl,
.set_frmival = hm0360_set_frmival,
.get_frmival = hm0360_get_frmival,
.enum_frmival = hm0360_enum_frmival,
};
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
#define HM0360_RESET_GPIO(inst) .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}),
#else
#define HM0360_RESET_GPIO(inst)
#endif
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios)
#define HM0360_PWDN_GPIO(inst) .pwdn = GPIO_DT_SPEC_INST_GET_OR(inst, pwdn_gpios, {}),
#else
#define HM0360_PWDN_GPIO(inst)
#endif
#define HM0360_INIT(inst) \
const struct hm0360_config hm0360_config_##inst = {.bus = I2C_DT_SPEC_INST_GET(inst), \
HM0360_RESET_GPIO(inst) \
HM0360_PWDN_GPIO(inst)}; \
struct hm0360_data hm0360_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, hm0360_init, NULL, &hm0360_data_##inst, &hm0360_config_##inst, \
POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &hm0360_api); \
\
VIDEO_DEVICE_DEFINE(hm0360_##inst, DEVICE_DT_INST_GET(inst), NULL);
DT_INST_FOREACH_STATUS_OKAY(HM0360_INIT)