// SPDX-License-Identifier: GPL-2.0-only /* * mt9m114.c onsemi MT9M114 sensor driver * * Copyright (c) 2020-2023 Laurent Pinchart * Copyright (c) 2012 Analog Devices Inc. * * Almost complete rewrite of work by Scott Jiang * itself based on work from Andrew Chew . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Sysctl registers */ #define MT9M114_CHIP_ID CCI_REG16(0x0000) #define MT9M114_COMMAND_REGISTER CCI_REG16(0x0080) #define MT9M114_COMMAND_REGISTER_APPLY_PATCH BIT(0) #define MT9M114_COMMAND_REGISTER_SET_STATE BIT(1) #define MT9M114_COMMAND_REGISTER_REFRESH BIT(2) #define MT9M114_COMMAND_REGISTER_WAIT_FOR_EVENT BIT(3) #define MT9M114_COMMAND_REGISTER_OK BIT(15) #define MT9M114_RESET_AND_MISC_CONTROL CCI_REG16(0x001a) #define MT9M114_RESET_SOC BIT(0) #define MT9M114_PAD_SLEW CCI_REG16(0x001e) #define MT9M114_PAD_CONTROL CCI_REG16(0x0032) /* XDMA registers */ #define MT9M114_ACCESS_CTL_STAT CCI_REG16(0x0982) #define MT9M114_PHYSICAL_ADDRESS_ACCESS CCI_REG16(0x098a) #define MT9M114_LOGICAL_ADDRESS_ACCESS CCI_REG16(0x098e) /* Sensor Core registers */ #define MT9M114_COARSE_INTEGRATION_TIME CCI_REG16(0x3012) #define MT9M114_FINE_INTEGRATION_TIME CCI_REG16(0x3014) #define MT9M114_RESET_REGISTER CCI_REG16(0x301a) #define MT9M114_RESET_REGISTER_LOCK_REG BIT(3) #define MT9M114_RESET_REGISTER_MASK_BAD BIT(9) #define MT9M114_FLASH CCI_REG16(0x3046) #define MT9M114_GREEN1_GAIN CCI_REG16(0x3056) #define MT9M114_BLUE_GAIN CCI_REG16(0x3058) #define MT9M114_RED_GAIN CCI_REG16(0x305a) #define MT9M114_GREEN2_GAIN CCI_REG16(0x305c) #define MT9M114_GLOBAL_GAIN CCI_REG16(0x305e) #define MT9M114_GAIN_DIGITAL_GAIN(n) ((n) << 12) #define MT9M114_GAIN_DIGITAL_GAIN_MASK (0xf << 12) #define MT9M114_GAIN_ANALOG_GAIN(n) ((n) << 0) #define MT9M114_GAIN_ANALOG_GAIN_MASK (0xff << 0) #define MT9M114_CUSTOMER_REV CCI_REG16(0x31fe) /* Monitor registers */ #define MT9M114_MON_MAJOR_VERSION CCI_REG16(0x8000) #define MT9M114_MON_MINOR_VERSION CCI_REG16(0x8002) #define MT9M114_MON_RELEASE_VERSION CCI_REG16(0x8004) /* Auto-Exposure Track registers */ #define MT9M114_AE_TRACK_ALGO CCI_REG16(0xa804) #define MT9M114_AE_TRACK_EXEC_AUTOMATIC_EXPOSURE BIT(0) #define MT9M114_AE_TRACK_AE_TRACKING_DAMPENING_SPEED CCI_REG8(0xa80a) /* Color Correction Matrix registers */ #define MT9M114_CCM_ALGO CCI_REG16(0xb404) #define MT9M114_CCM_EXEC_CALC_CCM_MATRIX BIT(4) #define MT9M114_CCM_DELTA_GAIN CCI_REG8(0xb42a) /* Camera Control registers */ #define MT9M114_CAM_SENSOR_CFG_Y_ADDR_START CCI_REG16(0xc800) #define MT9M114_CAM_SENSOR_CFG_X_ADDR_START CCI_REG16(0xc802) #define MT9M114_CAM_SENSOR_CFG_Y_ADDR_END CCI_REG16(0xc804) #define MT9M114_CAM_SENSOR_CFG_X_ADDR_END CCI_REG16(0xc806) #define MT9M114_CAM_SENSOR_CFG_PIXCLK CCI_REG32(0xc808) #define MT9M114_CAM_SENSOR_CFG_ROW_SPEED CCI_REG16(0xc80c) #define MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MIN CCI_REG16(0xc80e) #define MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MAX CCI_REG16(0xc810) #define MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES CCI_REG16(0xc812) #define MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES_MAX 65535 #define MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK CCI_REG16(0xc814) #define MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK_MAX 8191 #define MT9M114_CAM_SENSOR_CFG_FINE_CORRECTION CCI_REG16(0xc816) #define MT9M114_CAM_SENSOR_CFG_CPIPE_LAST_ROW CCI_REG16(0xc818) #define MT9M114_CAM_SENSOR_CFG_REG_0_DATA CCI_REG16(0xc826) #define MT9M114_CAM_SENSOR_CONTROL_READ_MODE CCI_REG16(0xc834) #define MT9M114_CAM_SENSOR_CONTROL_HORZ_MIRROR_EN BIT(0) #define MT9M114_CAM_SENSOR_CONTROL_VERT_FLIP_EN BIT(1) #define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_NORMAL (0 << 4) #define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SKIPPING (1 << 4) #define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_AVERAGE (2 << 4) #define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SUMMING (3 << 4) #define MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_MASK (3 << 4) #define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_NORMAL (0 << 8) #define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SKIPPING (1 << 8) #define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SUMMING (3 << 8) #define MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_MASK (3 << 8) #define MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN CCI_REG16(0xc836) #define MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME CCI_REG16(0xc83c) #define MT9M114_CAM_SENSOR_CONTROL_FINE_INTEGRATION_TIME CCI_REG16(0xc83e) #define MT9M114_CAM_MODE_SELECT CCI_REG8(0xc84c) #define MT9M114_CAM_MODE_SELECT_NORMAL (0 << 0) #define MT9M114_CAM_MODE_SELECT_LENS_CALIBRATION (1 << 0) #define MT9M114_CAM_MODE_SELECT_TEST_PATTERN (2 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT CCI_REG8(0xc84d) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID (1 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID_BARS (4 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_RANDOM (5 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_FADING_BARS (8 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_10B (10 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_8B (11 << 0) #define MT9M114_CAM_MODE_TEST_PATTERN_RED CCI_REG16(0xc84e) #define MT9M114_CAM_MODE_TEST_PATTERN_GREEN CCI_REG16(0xc850) #define MT9M114_CAM_MODE_TEST_PATTERN_BLUE CCI_REG16(0xc852) #define MT9M114_CAM_CROP_WINDOW_XOFFSET CCI_REG16(0xc854) #define MT9M114_CAM_CROP_WINDOW_YOFFSET CCI_REG16(0xc856) #define MT9M114_CAM_CROP_WINDOW_WIDTH CCI_REG16(0xc858) #define MT9M114_CAM_CROP_WINDOW_HEIGHT CCI_REG16(0xc85a) #define MT9M114_CAM_CROP_CROPMODE CCI_REG8(0xc85c) #define MT9M114_CAM_CROP_MODE_AE_AUTO_CROP_EN BIT(0) #define MT9M114_CAM_CROP_MODE_AWB_AUTO_CROP_EN BIT(1) #define MT9M114_CAM_OUTPUT_WIDTH CCI_REG16(0xc868) #define MT9M114_CAM_OUTPUT_HEIGHT CCI_REG16(0xc86a) #define MT9M114_CAM_OUTPUT_FORMAT CCI_REG16(0xc86c) #define MT9M114_CAM_OUTPUT_FORMAT_SWAP_RED_BLUE BIT(0) #define MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES BIT(1) #define MT9M114_CAM_OUTPUT_FORMAT_MONO_ENABLE BIT(2) #define MT9M114_CAM_OUTPUT_FORMAT_BT656_ENABLE BIT(3) #define MT9M114_CAM_OUTPUT_FORMAT_BT656_CROP_SCALE_DISABLE BIT(4) #define MT9M114_CAM_OUTPUT_FORMAT_FVLV_DISABLE BIT(5) #define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV (0 << 8) #define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB (1 << 8) #define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER (2 << 8) #define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_NONE (3 << 8) #define MT9M114_CAM_OUTPUT_FORMAT_FORMAT_MASK (3 << 8) #define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_RAWR10 (0 << 10) #define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PRELSC_8_2 (1 << 10) #define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_POSTLSC_8_2 (2 << 10) #define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PROCESSED8 (3 << 10) #define MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_MASK (3 << 10) #define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB (0 << 12) #define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_555RGB (1 << 12) #define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_444xRGB (2 << 12) #define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_444RGBx (3 << 12) #define MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_MASK (3 << 12) #define MT9M114_CAM_OUTPUT_FORMAT_YUV CCI_REG16(0xc86e) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_CLIP BIT(5) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_AUV_OFFSET BIT(4) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_SELECT_601 BIT(3) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_NORMALISE BIT(2) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_EVEN_UV (0 << 0) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_ODD_UV (1 << 0) #define MT9M114_CAM_OUTPUT_FORMAT_YUV_SAMPLING_EVENU_ODDV (2 << 0) #define MT9M114_CAM_OUTPUT_Y_OFFSET CCI_REG8(0xc870) #define MT9M114_CAM_AET_AEMODE CCI_REG8(0xc878) #define MT9M114_CAM_AET_EXEC_SET_INDOOR BIT(0) #define MT9M114_CAM_AET_DISCRETE_FRAMERATE BIT(1) #define MT9M114_CAM_AET_ADAPTATIVE_TARGET_LUMA BIT(2) #define MT9M114_CAM_AET_ADAPTATIVE_SKIP_FRAMES BIT(3) #define MT9M114_CAM_AET_SKIP_FRAMES CCI_REG8(0xc879) #define MT9M114_CAM_AET_TARGET_AVERAGE_LUMA CCI_REG8(0xc87a) #define MT9M114_CAM_AET_TARGET_AVERAGE_LUMA_DARK CCI_REG8(0xc87b) #define MT9M114_CAM_AET_BLACK_CLIPPING_TARGET CCI_REG16(0xc87c) #define MT9M114_CAM_AET_AE_MIN_VIRT_INT_TIME_PCLK CCI_REG16(0xc87e) #define MT9M114_CAM_AET_AE_MIN_VIRT_DGAIN CCI_REG16(0xc880) #define MT9M114_CAM_AET_AE_MAX_VIRT_DGAIN CCI_REG16(0xc882) #define MT9M114_CAM_AET_AE_MIN_VIRT_AGAIN CCI_REG16(0xc884) #define MT9M114_CAM_AET_AE_MAX_VIRT_AGAIN CCI_REG16(0xc886) #define MT9M114_CAM_AET_AE_VIRT_GAIN_TH_EG CCI_REG16(0xc888) #define MT9M114_CAM_AET_AE_EG_GATE_PERCENTAGE CCI_REG8(0xc88a) #define MT9M114_CAM_AET_FLICKER_FREQ_HZ CCI_REG8(0xc88b) #define MT9M114_CAM_AET_MAX_FRAME_RATE CCI_REG16(0xc88c) #define MT9M114_CAM_AET_MIN_FRAME_RATE CCI_REG16(0xc88e) #define MT9M114_CAM_AET_TARGET_GAIN CCI_REG16(0xc890) #define MT9M114_CAM_AWB_CCM_L(n) CCI_REG16(0xc892 + (n) * 2) #define MT9M114_CAM_AWB_CCM_M(n) CCI_REG16(0xc8a4 + (n) * 2) #define MT9M114_CAM_AWB_CCM_R(n) CCI_REG16(0xc8b6 + (n) * 2) #define MT9M114_CAM_AWB_CCM_L_RG_GAIN CCI_REG16(0xc8c8) #define MT9M114_CAM_AWB_CCM_L_BG_GAIN CCI_REG16(0xc8ca) #define MT9M114_CAM_AWB_CCM_M_RG_GAIN CCI_REG16(0xc8cc) #define MT9M114_CAM_AWB_CCM_M_BG_GAIN CCI_REG16(0xc8ce) #define MT9M114_CAM_AWB_CCM_R_RG_GAIN CCI_REG16(0xc8d0) #define MT9M114_CAM_AWB_CCM_R_BG_GAIN CCI_REG16(0xc8d2) #define MT9M114_CAM_AWB_CCM_L_CTEMP CCI_REG16(0xc8d4) #define MT9M114_CAM_AWB_CCM_M_CTEMP CCI_REG16(0xc8d6) #define MT9M114_CAM_AWB_CCM_R_CTEMP CCI_REG16(0xc8d8) #define MT9M114_CAM_AWB_AWB_XSCALE CCI_REG8(0xc8f2) #define MT9M114_CAM_AWB_AWB_YSCALE CCI_REG8(0xc8f3) #define MT9M114_CAM_AWB_AWB_WEIGHTS(n) CCI_REG16(0xc8f4 + (n) * 2) #define MT9M114_CAM_AWB_AWB_XSHIFT_PRE_ADJ CCI_REG16(0xc904) #define MT9M114_CAM_AWB_AWB_YSHIFT_PRE_ADJ CCI_REG16(0xc906) #define MT9M114_CAM_AWB_AWBMODE CCI_REG8(0xc909) #define MT9M114_CAM_AWB_MODE_AUTO BIT(1) #define MT9M114_CAM_AWB_MODE_EXCLUSIVE_AE BIT(0) #define MT9M114_CAM_AWB_K_R_L CCI_REG8(0xc90c) #define MT9M114_CAM_AWB_K_G_L CCI_REG8(0xc90d) #define MT9M114_CAM_AWB_K_B_L CCI_REG8(0xc90e) #define MT9M114_CAM_AWB_K_R_R CCI_REG8(0xc90f) #define MT9M114_CAM_AWB_K_G_R CCI_REG8(0xc910) #define MT9M114_CAM_AWB_K_B_R CCI_REG8(0xc911) #define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XSTART CCI_REG16(0xc914) #define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YSTART CCI_REG16(0xc916) #define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XEND CCI_REG16(0xc918) #define MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YEND CCI_REG16(0xc91a) #define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XSTART CCI_REG16(0xc91c) #define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YSTART CCI_REG16(0xc91e) #define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XEND CCI_REG16(0xc920) #define MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YEND CCI_REG16(0xc922) #define MT9M114_CAM_LL_LLMODE CCI_REG16(0xc924) #define MT9M114_CAM_LL_START_BRIGHTNESS CCI_REG16(0xc926) #define MT9M114_CAM_LL_STOP_BRIGHTNESS CCI_REG16(0xc928) #define MT9M114_CAM_LL_START_SATURATION CCI_REG8(0xc92a) #define MT9M114_CAM_LL_END_SATURATION CCI_REG8(0xc92b) #define MT9M114_CAM_LL_START_DESATURATION CCI_REG8(0xc92c) #define MT9M114_CAM_LL_END_DESATURATION CCI_REG8(0xc92d) #define MT9M114_CAM_LL_START_DEMOSAICING CCI_REG8(0xc92e) #define MT9M114_CAM_LL_START_AP_GAIN CCI_REG8(0xc92f) #define MT9M114_CAM_LL_START_AP_THRESH CCI_REG8(0xc930) #define MT9M114_CAM_LL_STOP_DEMOSAICING CCI_REG8(0xc931) #define MT9M114_CAM_LL_STOP_AP_GAIN CCI_REG8(0xc932) #define MT9M114_CAM_LL_STOP_AP_THRESH CCI_REG8(0xc933) #define MT9M114_CAM_LL_START_NR_RED CCI_REG8(0xc934) #define MT9M114_CAM_LL_START_NR_GREEN CCI_REG8(0xc935) #define MT9M114_CAM_LL_START_NR_BLUE CCI_REG8(0xc936) #define MT9M114_CAM_LL_START_NR_THRESH CCI_REG8(0xc937) #define MT9M114_CAM_LL_STOP_NR_RED CCI_REG8(0xc938) #define MT9M114_CAM_LL_STOP_NR_GREEN CCI_REG8(0xc939) #define MT9M114_CAM_LL_STOP_NR_BLUE CCI_REG8(0xc93a) #define MT9M114_CAM_LL_STOP_NR_THRESH CCI_REG8(0xc93b) #define MT9M114_CAM_LL_START_CONTRAST_BM CCI_REG16(0xc93c) #define MT9M114_CAM_LL_STOP_CONTRAST_BM CCI_REG16(0xc93e) #define MT9M114_CAM_LL_GAMMA CCI_REG16(0xc940) #define MT9M114_CAM_LL_START_CONTRAST_GRADIENT CCI_REG8(0xc942) #define MT9M114_CAM_LL_STOP_CONTRAST_GRADIENT CCI_REG8(0xc943) #define MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE CCI_REG8(0xc944) #define MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE CCI_REG8(0xc945) #define MT9M114_CAM_LL_START_GAIN_METRIC CCI_REG16(0xc946) #define MT9M114_CAM_LL_STOP_GAIN_METRIC CCI_REG16(0xc948) #define MT9M114_CAM_LL_START_FADE_TO_BLACK_LUMA CCI_REG16(0xc94a) #define MT9M114_CAM_LL_STOP_FADE_TO_BLACK_LUMA CCI_REG16(0xc94c) #define MT9M114_CAM_LL_CLUSTER_DC_TH_BM CCI_REG16(0xc94e) #define MT9M114_CAM_LL_CLUSTER_DC_GATE_PERCENTAGE CCI_REG8(0xc950) #define MT9M114_CAM_LL_SUMMING_SENSITIVITY_FACTOR CCI_REG8(0xc951) #define MT9M114_CAM_LL_START_TARGET_LUMA_BM CCI_REG16(0xc952) #define MT9M114_CAM_LL_STOP_TARGET_LUMA_BM CCI_REG16(0xc954) #define MT9M114_CAM_PGA_PGA_CONTROL CCI_REG16(0xc95e) #define MT9M114_CAM_SYSCTL_PLL_ENABLE CCI_REG8(0xc97e) #define MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE BIT(0) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N CCI_REG16(0xc980) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(m, n) (((n) << 8) | (m)) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P CCI_REG16(0xc982) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(p) ((p) << 8) #define MT9M114_CAM_PORT_OUTPUT_CONTROL CCI_REG16(0xc984) #define MT9M114_CAM_PORT_PORT_SELECT_PARALLEL (0 << 0) #define MT9M114_CAM_PORT_PORT_SELECT_MIPI (1 << 0) #define MT9M114_CAM_PORT_CLOCK_SLOWDOWN BIT(3) #define MT9M114_CAM_PORT_TRUNCATE_RAW_BAYER BIT(4) #define MT9M114_CAM_PORT_PIXCLK_GATE BIT(5) #define MT9M114_CAM_PORT_CONT_MIPI_CLK BIT(6) #define MT9M114_CAM_PORT_CHAN_NUM(vc) ((vc) << 8) #define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_ZERO CCI_REG16(0xc988) #define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_ZERO_VALUE(n) ((n) << 8) #define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_EXIT_TRAIL CCI_REG16(0xc98a) #define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_EXIT_VALUE(n) ((n) << 8) #define MT9M114_CAM_PORT_MIPI_TIMING_T_HS_TRAIL_VALUE(n) ((n) << 0) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_POST_PRE CCI_REG16(0xc98c) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_POST_VALUE(n) ((n) << 8) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_PRE_VALUE(n) ((n) << 0) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_TRAIL_ZERO CCI_REG16(0xc98e) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_TRAIL_VALUE(n) ((n) << 8) #define MT9M114_CAM_PORT_MIPI_TIMING_T_CLK_ZERO_VALUE(n) ((n) << 0) /* System Manager registers */ #define MT9M114_SYSMGR_NEXT_STATE CCI_REG8(0xdc00) #define MT9M114_SYSMGR_CURRENT_STATE CCI_REG8(0xdc01) #define MT9M114_SYSMGR_CMD_STATUS CCI_REG8(0xdc02) /* Patch Loader registers */ #define MT9M114_PATCHLDR_LOADER_ADDRESS CCI_REG16(0xe000) #define MT9M114_PATCHLDR_PATCH_ID CCI_REG16(0xe002) #define MT9M114_PATCHLDR_FIRMWARE_ID CCI_REG32(0xe004) #define MT9M114_PATCHLDR_APPLY_STATUS CCI_REG8(0xe008) #define MT9M114_PATCHLDR_NUM_PATCHES CCI_REG8(0xe009) #define MT9M114_PATCHLDR_PATCH_ID_0 CCI_REG16(0xe00a) #define MT9M114_PATCHLDR_PATCH_ID_1 CCI_REG16(0xe00c) #define MT9M114_PATCHLDR_PATCH_ID_2 CCI_REG16(0xe00e) #define MT9M114_PATCHLDR_PATCH_ID_3 CCI_REG16(0xe010) #define MT9M114_PATCHLDR_PATCH_ID_4 CCI_REG16(0xe012) #define MT9M114_PATCHLDR_PATCH_ID_5 CCI_REG16(0xe014) #define MT9M114_PATCHLDR_PATCH_ID_6 CCI_REG16(0xe016) #define MT9M114_PATCHLDR_PATCH_ID_7 CCI_REG16(0xe018) /* SYS_STATE values (for SYSMGR_NEXT_STATE and SYSMGR_CURRENT_STATE) */ #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 /* Result status of last SET_STATE comamnd */ #define MT9M114_SET_STATE_RESULT_ENOERR 0x00 #define MT9M114_SET_STATE_RESULT_EINVAL 0x0c #define MT9M114_SET_STATE_RESULT_ENOSPC 0x0d /* * The minimum amount of horizontal and vertical blanking is undocumented. The * minimum values that have been seen in register lists are 303 and 38, use * them. * * Set the default to achieve 1280x960 at 30fps. */ #define MT9M114_MIN_HBLANK 303 #define MT9M114_MIN_VBLANK 38 #define MT9M114_DEF_HBLANK 323 #define MT9M114_DEF_VBLANK 39 #define MT9M114_DEF_FRAME_RATE 30 #define MT9M114_MAX_FRAME_RATE 120 #define MT9M114_PIXEL_ARRAY_WIDTH 1296U #define MT9M114_PIXEL_ARRAY_HEIGHT 976U /* * These values are not well documented and are semi-arbitrary. The pixel array * minimum output size is 8 pixels larger than the minimum scaler cropped input * width to account for the demosaicing. */ #define MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH (32U + 8U) #define MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT (32U + 8U) #define MT9M114_SCALER_CROPPED_INPUT_WIDTH 32U #define MT9M114_SCALER_CROPPED_INPUT_HEIGHT 32U /* Indices into the mt9m114.ifp.tpg array. */ #define MT9M114_TPG_PATTERN 0 #define MT9M114_TPG_RED 1 #define MT9M114_TPG_GREEN 2 #define MT9M114_TPG_BLUE 3 /* ----------------------------------------------------------------------------- * Data Structures */ enum mt9m114_format_flag { MT9M114_FMT_FLAG_PARALLEL = BIT(0), MT9M114_FMT_FLAG_CSI2 = BIT(1), }; struct mt9m114_format_info { u32 code; u32 output_format; u32 flags; }; struct mt9m114 { struct i2c_client *client; struct regmap *regmap; struct clk *clk; struct gpio_desc *reset; struct regulator_bulk_data supplies[3]; struct v4l2_fwnode_endpoint bus_cfg; struct { unsigned int m; unsigned int n; unsigned int p; } pll; unsigned int pixrate; bool streaming; /* Pixel Array */ struct { struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler hdl; struct v4l2_ctrl *exposure; struct v4l2_ctrl *gain; struct v4l2_ctrl *hblank; struct v4l2_ctrl *vblank; } pa; /* Image Flow Processor */ struct { struct v4l2_subdev sd; struct media_pad pads[2]; struct v4l2_ctrl_handler hdl; unsigned int frame_rate; struct v4l2_ctrl *tpg[4]; } ifp; }; /* ----------------------------------------------------------------------------- * Formats */ static const struct mt9m114_format_info mt9m114_format_infos[] = { { /* * The first two entries are used as defaults, for parallel and * CSI-2 buses respectively. Keep them in that order. */ .code = MEDIA_BUS_FMT_UYVY8_2X8, .flags = MT9M114_FMT_FLAG_PARALLEL, .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV, }, { .code = MEDIA_BUS_FMT_UYVY8_1X16, .flags = MT9M114_FMT_FLAG_CSI2, .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV, }, { .code = MEDIA_BUS_FMT_YUYV8_2X8, .flags = MT9M114_FMT_FLAG_PARALLEL, .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, }, { .code = MEDIA_BUS_FMT_YUYV8_1X16, .flags = MT9M114_FMT_FLAG_CSI2, .output_format = MT9M114_CAM_OUTPUT_FORMAT_FORMAT_YUV | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, }, { .code = MEDIA_BUS_FMT_RGB565_2X8_LE, .flags = MT9M114_FMT_FLAG_PARALLEL, .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES, }, { .code = MEDIA_BUS_FMT_RGB565_2X8_BE, .flags = MT9M114_FMT_FLAG_PARALLEL, .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB, }, { .code = MEDIA_BUS_FMT_RGB565_1X16, .flags = MT9M114_FMT_FLAG_CSI2, .output_format = MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_565RGB | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_RGB, }, { .code = MEDIA_BUS_FMT_SGRBG8_1X8, .output_format = MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_PROCESSED8 | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER, .flags = MT9M114_FMT_FLAG_PARALLEL | MT9M114_FMT_FLAG_CSI2, }, { /* Keep the format compatible with the IFP sink pad last. */ .code = MEDIA_BUS_FMT_SGRBG10_1X10, .output_format = MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_RAWR10 | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_BAYER, .flags = MT9M114_FMT_FLAG_PARALLEL | MT9M114_FMT_FLAG_CSI2, } }; static const struct mt9m114_format_info * mt9m114_default_format_info(struct mt9m114 *sensor) { if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) return &mt9m114_format_infos[1]; else return &mt9m114_format_infos[0]; } static const struct mt9m114_format_info * mt9m114_format_info(struct mt9m114 *sensor, unsigned int pad, u32 code) { const unsigned int num_formats = ARRAY_SIZE(mt9m114_format_infos); unsigned int flag; unsigned int i; switch (pad) { case 0: return &mt9m114_format_infos[num_formats - 1]; case 1: if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) flag = MT9M114_FMT_FLAG_CSI2; else flag = MT9M114_FMT_FLAG_PARALLEL; for (i = 0; i < num_formats; ++i) { const struct mt9m114_format_info *info = &mt9m114_format_infos[i]; if (info->code == code && info->flags & flag) return info; } return mt9m114_default_format_info(sensor); default: return NULL; } } /* ----------------------------------------------------------------------------- * Initialization */ static const struct cci_reg_sequence mt9m114_init[] = { { MT9M114_RESET_REGISTER, MT9M114_RESET_REGISTER_MASK_BAD | MT9M114_RESET_REGISTER_LOCK_REG | 0x0010 }, /* Sensor optimization */ { CCI_REG16(0x316a), 0x8270 }, { CCI_REG16(0x316c), 0x8270 }, { CCI_REG16(0x3ed0), 0x2305 }, { CCI_REG16(0x3ed2), 0x77cf }, { CCI_REG16(0x316e), 0x8202 }, { CCI_REG16(0x3180), 0x87ff }, { CCI_REG16(0x30d4), 0x6080 }, { CCI_REG16(0xa802), 0x0008 }, { CCI_REG16(0x3e14), 0xff39 }, /* APGA */ { MT9M114_CAM_PGA_PGA_CONTROL, 0x0000 }, /* Automatic White balance */ { MT9M114_CAM_AWB_CCM_L(0), 0x0267 }, { MT9M114_CAM_AWB_CCM_L(1), 0xff1a }, { MT9M114_CAM_AWB_CCM_L(2), 0xffb3 }, { MT9M114_CAM_AWB_CCM_L(3), 0xff80 }, { MT9M114_CAM_AWB_CCM_L(4), 0x0166 }, { MT9M114_CAM_AWB_CCM_L(5), 0x0003 }, { MT9M114_CAM_AWB_CCM_L(6), 0xff9a }, { MT9M114_CAM_AWB_CCM_L(7), 0xfeb4 }, { MT9M114_CAM_AWB_CCM_L(8), 0x024d }, { MT9M114_CAM_AWB_CCM_M(0), 0x01bf }, { MT9M114_CAM_AWB_CCM_M(1), 0xff01 }, { MT9M114_CAM_AWB_CCM_M(2), 0xfff3 }, { MT9M114_CAM_AWB_CCM_M(3), 0xff75 }, { MT9M114_CAM_AWB_CCM_M(4), 0x0198 }, { MT9M114_CAM_AWB_CCM_M(5), 0xfffd }, { MT9M114_CAM_AWB_CCM_M(6), 0xff9a }, { MT9M114_CAM_AWB_CCM_M(7), 0xfee7 }, { MT9M114_CAM_AWB_CCM_M(8), 0x02a8 }, { MT9M114_CAM_AWB_CCM_R(0), 0x01d9 }, { MT9M114_CAM_AWB_CCM_R(1), 0xff26 }, { MT9M114_CAM_AWB_CCM_R(2), 0xfff3 }, { MT9M114_CAM_AWB_CCM_R(3), 0xffb3 }, { MT9M114_CAM_AWB_CCM_R(4), 0x0132 }, { MT9M114_CAM_AWB_CCM_R(5), 0xffe8 }, { MT9M114_CAM_AWB_CCM_R(6), 0xffda }, { MT9M114_CAM_AWB_CCM_R(7), 0xfecd }, { MT9M114_CAM_AWB_CCM_R(8), 0x02c2 }, { MT9M114_CAM_AWB_CCM_L_RG_GAIN, 0x0075 }, { MT9M114_CAM_AWB_CCM_L_BG_GAIN, 0x011c }, { MT9M114_CAM_AWB_CCM_M_RG_GAIN, 0x009a }, { MT9M114_CAM_AWB_CCM_M_BG_GAIN, 0x0105 }, { MT9M114_CAM_AWB_CCM_R_RG_GAIN, 0x00a4 }, { MT9M114_CAM_AWB_CCM_R_BG_GAIN, 0x00ac }, { MT9M114_CAM_AWB_CCM_L_CTEMP, 0x0a8c }, { MT9M114_CAM_AWB_CCM_M_CTEMP, 0x0f0a }, { MT9M114_CAM_AWB_CCM_R_CTEMP, 0x1964 }, { MT9M114_CAM_AWB_AWB_XSHIFT_PRE_ADJ, 51 }, { MT9M114_CAM_AWB_AWB_YSHIFT_PRE_ADJ, 60 }, { MT9M114_CAM_AWB_AWB_XSCALE, 3 }, { MT9M114_CAM_AWB_AWB_YSCALE, 2 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(0), 0x0000 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(1), 0x0000 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(2), 0x0000 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(3), 0xe724 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(4), 0x1583 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(5), 0x2045 }, { MT9M114_CAM_AWB_AWB_WEIGHTS(6), 0x03ff }, { MT9M114_CAM_AWB_AWB_WEIGHTS(7), 0x007c }, { MT9M114_CAM_AWB_K_R_L, 0x80 }, { MT9M114_CAM_AWB_K_G_L, 0x80 }, { MT9M114_CAM_AWB_K_B_L, 0x80 }, { MT9M114_CAM_AWB_K_R_R, 0x88 }, { MT9M114_CAM_AWB_K_G_R, 0x80 }, { MT9M114_CAM_AWB_K_B_R, 0x80 }, /* Low-Light Image Enhancements */ { MT9M114_CAM_LL_START_BRIGHTNESS, 0x0020 }, { MT9M114_CAM_LL_STOP_BRIGHTNESS, 0x009a }, { MT9M114_CAM_LL_START_GAIN_METRIC, 0x0070 }, { MT9M114_CAM_LL_STOP_GAIN_METRIC, 0x00f3 }, { MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE, 0x20 }, { MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE, 0x9a }, { MT9M114_CAM_LL_START_SATURATION, 0x80 }, { MT9M114_CAM_LL_END_SATURATION, 0x4b }, { MT9M114_CAM_LL_START_DESATURATION, 0x00 }, { MT9M114_CAM_LL_END_DESATURATION, 0xff }, { MT9M114_CAM_LL_START_DEMOSAICING, 0x3c }, { MT9M114_CAM_LL_START_AP_GAIN, 0x02 }, { MT9M114_CAM_LL_START_AP_THRESH, 0x06 }, { MT9M114_CAM_LL_STOP_DEMOSAICING, 0x64 }, { MT9M114_CAM_LL_STOP_AP_GAIN, 0x01 }, { MT9M114_CAM_LL_STOP_AP_THRESH, 0x0c }, { MT9M114_CAM_LL_START_NR_RED, 0x3c }, { MT9M114_CAM_LL_START_NR_GREEN, 0x3c }, { MT9M114_CAM_LL_START_NR_BLUE, 0x3c }, { MT9M114_CAM_LL_START_NR_THRESH, 0x0f }, { MT9M114_CAM_LL_STOP_NR_RED, 0x64 }, { MT9M114_CAM_LL_STOP_NR_GREEN, 0x64 }, { MT9M114_CAM_LL_STOP_NR_BLUE, 0x64 }, { MT9M114_CAM_LL_STOP_NR_THRESH, 0x32 }, { MT9M114_CAM_LL_START_CONTRAST_BM, 0x0020 }, { MT9M114_CAM_LL_STOP_CONTRAST_BM, 0x009a }, { MT9M114_CAM_LL_GAMMA, 0x00dc }, { MT9M114_CAM_LL_START_CONTRAST_GRADIENT, 0x38 }, { MT9M114_CAM_LL_STOP_CONTRAST_GRADIENT, 0x30 }, { MT9M114_CAM_LL_START_CONTRAST_LUMA_PERCENTAGE, 0x50 }, { MT9M114_CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE, 0x19 }, { MT9M114_CAM_LL_START_FADE_TO_BLACK_LUMA, 0x0230 }, { MT9M114_CAM_LL_STOP_FADE_TO_BLACK_LUMA, 0x0010 }, { MT9M114_CAM_LL_CLUSTER_DC_TH_BM, 0x01cd }, { MT9M114_CAM_LL_CLUSTER_DC_GATE_PERCENTAGE, 0x05 }, { MT9M114_CAM_LL_SUMMING_SENSITIVITY_FACTOR, 0x40 }, /* Auto-Exposure */ { MT9M114_CAM_AET_TARGET_AVERAGE_LUMA_DARK, 0x1b }, { MT9M114_CAM_AET_AEMODE, 0x00 }, { MT9M114_CAM_AET_TARGET_GAIN, 0x0080 }, { MT9M114_CAM_AET_AE_MAX_VIRT_AGAIN, 0x0100 }, { MT9M114_CAM_AET_BLACK_CLIPPING_TARGET, 0x005a }, { MT9M114_CCM_DELTA_GAIN, 0x05 }, { MT9M114_AE_TRACK_AE_TRACKING_DAMPENING_SPEED, 0x20 }, /* Pixel array timings and integration time */ { MT9M114_CAM_SENSOR_CFG_ROW_SPEED, 1 }, { MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MIN, 219 }, { MT9M114_CAM_SENSOR_CFG_FINE_INTEG_TIME_MAX, 1459 }, { MT9M114_CAM_SENSOR_CFG_FINE_CORRECTION, 96 }, { MT9M114_CAM_SENSOR_CFG_REG_0_DATA, 32 }, /* Miscellaneous settings */ { MT9M114_PAD_SLEW, 0x0777 }, }; /* ----------------------------------------------------------------------------- * Hardware Configuration */ /* Wait for a command to complete. */ static int mt9m114_poll_command(struct mt9m114 *sensor, u32 command) { unsigned int i; u64 value; int ret; for (i = 0; i < 100; ++i) { ret = cci_read(sensor->regmap, MT9M114_COMMAND_REGISTER, &value, NULL); if (ret < 0) return ret; if (!(value & command)) break; usleep_range(5000, 6000); } if (value & command) { dev_err(&sensor->client->dev, "Command %u completion timeout\n", command); return -ETIMEDOUT; } if (!(value & MT9M114_COMMAND_REGISTER_OK)) { dev_err(&sensor->client->dev, "Command %u failed\n", command); return -EIO; } return 0; } /* Wait for a state to be entered. */ static int mt9m114_poll_state(struct mt9m114 *sensor, u32 state) { unsigned int i; u64 value; int ret; for (i = 0; i < 100; ++i) { ret = cci_read(sensor->regmap, MT9M114_SYSMGR_CURRENT_STATE, &value, NULL); if (ret < 0) return ret; if (value == state) return 0; usleep_range(1000, 1500); } dev_err(&sensor->client->dev, "Timeout waiting for state 0x%02x\n", state); return -ETIMEDOUT; } static int mt9m114_set_state(struct mt9m114 *sensor, u8 next_state) { int ret = 0; /* Set the next desired state and start the state transition. */ cci_write(sensor->regmap, MT9M114_SYSMGR_NEXT_STATE, next_state, &ret); cci_write(sensor->regmap, MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_OK | MT9M114_COMMAND_REGISTER_SET_STATE, &ret); if (ret < 0) return ret; /* Wait for the state transition to complete. */ ret = mt9m114_poll_command(sensor, MT9M114_COMMAND_REGISTER_SET_STATE); if (ret < 0) return ret; return 0; } static int mt9m114_initialize(struct mt9m114 *sensor) { u32 value; int ret; ret = cci_multi_reg_write(sensor->regmap, mt9m114_init, ARRAY_SIZE(mt9m114_init), NULL); if (ret < 0) { dev_err(&sensor->client->dev, "Failed to initialize the sensor\n"); return ret; } /* Configure the PLL. */ cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_ENABLE, MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE, &ret); cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N, MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(sensor->pll.m, sensor->pll.n), &ret); cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p), &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_PIXCLK, sensor->pixrate, &ret); /* Configure the output mode. */ if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) { value = MT9M114_CAM_PORT_PORT_SELECT_MIPI | MT9M114_CAM_PORT_CHAN_NUM(0) | 0x8000; if (!(sensor->bus_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) value |= MT9M114_CAM_PORT_CONT_MIPI_CLK; } else { value = MT9M114_CAM_PORT_PORT_SELECT_PARALLEL | 0x8000; } cci_write(sensor->regmap, MT9M114_CAM_PORT_OUTPUT_CONTROL, value, &ret); if (ret < 0) return ret; ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); if (ret < 0) return ret; ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_SUSPEND); if (ret < 0) return ret; return 0; } static int mt9m114_configure(struct mt9m114 *sensor, struct v4l2_subdev_state *pa_state, struct v4l2_subdev_state *ifp_state) { const struct v4l2_mbus_framefmt *pa_format; const struct v4l2_rect *pa_crop; const struct mt9m114_format_info *ifp_info; const struct v4l2_mbus_framefmt *ifp_format; const struct v4l2_rect *ifp_crop; const struct v4l2_rect *ifp_compose; unsigned int hratio, vratio; u64 output_format; u64 read_mode; int ret = 0; pa_format = v4l2_subdev_state_get_format(pa_state, 0); pa_crop = v4l2_subdev_state_get_crop(pa_state, 0); ifp_format = v4l2_subdev_state_get_format(ifp_state, 1); ifp_info = mt9m114_format_info(sensor, 1, ifp_format->code); ifp_crop = v4l2_subdev_state_get_crop(ifp_state, 0); ifp_compose = v4l2_subdev_state_get_compose(ifp_state, 0); ret = cci_read(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, &read_mode, NULL); if (ret < 0) return ret; ret = cci_read(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, &output_format, NULL); if (ret < 0) return ret; hratio = pa_crop->width / pa_format->width; vratio = pa_crop->height / pa_format->height; /* * Pixel array crop and binning. The CAM_SENSOR_CFG_CPIPE_LAST_ROW * register isn't clearly documented, but is always set to the number * of active rows minus 4 divided by the vertical binning factor in all * example sensor modes. */ cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_START, pa_crop->left, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_START, pa_crop->top, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_END, pa_crop->width + pa_crop->left - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_END, pa_crop->height + pa_crop->top - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_CPIPE_LAST_ROW, (pa_crop->height - 4) / vratio - 1, &ret); read_mode &= ~(MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_MASK | MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_MASK); if (hratio > 1) read_mode |= MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_SUMMING; if (vratio > 1) read_mode |= MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_SUMMING; cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, read_mode, &ret); /* * Color pipeline (IFP) cropping and scaling. Subtract 4 from the left * and top coordinates to compensate for the lines and columns removed * by demosaicing that are taken into account in the crop rectangle but * not in the hardware. */ cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_XOFFSET, ifp_crop->left - 4, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_YOFFSET, ifp_crop->top - 4, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_WIDTH, ifp_crop->width, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_HEIGHT, ifp_crop->height, &ret); cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_WIDTH, ifp_compose->width, &ret); cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_HEIGHT, ifp_compose->height, &ret); /* AWB and AE windows, use the full frame. */ cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XEND, ifp_compose->width - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YEND, ifp_compose->height - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XEND, ifp_compose->width / 5 - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YEND, ifp_compose->height / 5 - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_CROPMODE, MT9M114_CAM_CROP_MODE_AWB_AUTO_CROP_EN | MT9M114_CAM_CROP_MODE_AE_AUTO_CROP_EN, &ret); /* Set the media bus code. */ output_format &= ~(MT9M114_CAM_OUTPUT_FORMAT_RGB_FORMAT_MASK | MT9M114_CAM_OUTPUT_FORMAT_BAYER_FORMAT_MASK | MT9M114_CAM_OUTPUT_FORMAT_FORMAT_MASK | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES | MT9M114_CAM_OUTPUT_FORMAT_SWAP_RED_BLUE); output_format |= ifp_info->output_format; cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, output_format, &ret); return ret; } static int mt9m114_set_frame_rate(struct mt9m114 *sensor) { u16 frame_rate = sensor->ifp.frame_rate << 8; int ret = 0; cci_write(sensor->regmap, MT9M114_CAM_AET_MIN_FRAME_RATE, frame_rate, &ret); cci_write(sensor->regmap, MT9M114_CAM_AET_MAX_FRAME_RATE, frame_rate, &ret); return ret; } static int mt9m114_start_streaming(struct mt9m114 *sensor, struct v4l2_subdev_state *pa_state, struct v4l2_subdev_state *ifp_state) { int ret; ret = pm_runtime_resume_and_get(&sensor->client->dev); if (ret) return ret; ret = mt9m114_configure(sensor, pa_state, ifp_state); if (ret) goto error; ret = mt9m114_set_frame_rate(sensor); if (ret) goto error; ret = __v4l2_ctrl_handler_setup(&sensor->pa.hdl); if (ret) goto error; ret = __v4l2_ctrl_handler_setup(&sensor->ifp.hdl); if (ret) goto error; /* * The Change-Config state is transient and moves to the streaming * state automatically. */ ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); if (ret) goto error; sensor->streaming = true; return 0; error: pm_runtime_mark_last_busy(&sensor->client->dev); pm_runtime_put_autosuspend(&sensor->client->dev); return ret; } static int mt9m114_stop_streaming(struct mt9m114 *sensor) { int ret; sensor->streaming = false; ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_SUSPEND); pm_runtime_mark_last_busy(&sensor->client->dev); pm_runtime_put_autosuspend(&sensor->client->dev); return ret; } /* ----------------------------------------------------------------------------- * Common Subdev Operations */ static const struct media_entity_operations mt9m114_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; /* ----------------------------------------------------------------------------- * Pixel Array Control Operations */ static inline struct mt9m114 *pa_ctrl_to_mt9m114(struct v4l2_ctrl *ctrl) { return container_of(ctrl->handler, struct mt9m114, pa.hdl); } static int mt9m114_pa_g_ctrl(struct v4l2_ctrl *ctrl) { struct mt9m114 *sensor = pa_ctrl_to_mt9m114(ctrl); u64 value; int ret; if (!pm_runtime_get_if_in_use(&sensor->client->dev)) return 0; switch (ctrl->id) { case V4L2_CID_EXPOSURE: ret = cci_read(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME, &value, NULL); if (ret) break; ctrl->val = value; break; case V4L2_CID_ANALOGUE_GAIN: ret = cci_read(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN, &value, NULL); if (ret) break; ctrl->val = value; break; default: ret = -EINVAL; break; } pm_runtime_mark_last_busy(&sensor->client->dev); pm_runtime_put_autosuspend(&sensor->client->dev); return ret; } static int mt9m114_pa_s_ctrl(struct v4l2_ctrl *ctrl) { struct mt9m114 *sensor = pa_ctrl_to_mt9m114(ctrl); const struct v4l2_mbus_framefmt *format; struct v4l2_subdev_state *state; int ret = 0; u64 mask; /* V4L2 controls values are applied only when power is up. */ if (!pm_runtime_get_if_in_use(&sensor->client->dev)) return 0; state = v4l2_subdev_get_locked_active_state(&sensor->pa.sd); format = v4l2_subdev_state_get_format(state, 0); switch (ctrl->id) { case V4L2_CID_HBLANK: cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK, ctrl->val + format->width, &ret); break; case V4L2_CID_VBLANK: cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES, ctrl->val + format->height, &ret); break; case V4L2_CID_EXPOSURE: cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_COARSE_INTEGRATION_TIME, ctrl->val, &ret); break; case V4L2_CID_ANALOGUE_GAIN: /* * The CAM_SENSOR_CONTROL_ANALOG_GAIN contains linear analog * gain values that are mapped to the GLOBAL_GAIN register * values by the sensor firmware. */ cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_ANALOG_GAIN, ctrl->val, &ret); break; case V4L2_CID_HFLIP: mask = MT9M114_CAM_SENSOR_CONTROL_HORZ_MIRROR_EN; ret = cci_update_bits(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, mask, ctrl->val ? mask : 0, NULL); break; case V4L2_CID_VFLIP: mask = MT9M114_CAM_SENSOR_CONTROL_VERT_FLIP_EN; ret = cci_update_bits(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, mask, ctrl->val ? mask : 0, NULL); break; default: ret = -EINVAL; break; } pm_runtime_mark_last_busy(&sensor->client->dev); pm_runtime_put_autosuspend(&sensor->client->dev); return ret; } static const struct v4l2_ctrl_ops mt9m114_pa_ctrl_ops = { .g_volatile_ctrl = mt9m114_pa_g_ctrl, .s_ctrl = mt9m114_pa_s_ctrl, }; static void mt9m114_pa_ctrl_update_exposure(struct mt9m114 *sensor, bool manual) { /* * Update the volatile flag on the manual exposure and gain controls. * If the controls have switched to manual, read their current value * from the hardware to ensure that control read and write operations * will behave correctly */ if (manual) { mt9m114_pa_g_ctrl(sensor->pa.exposure); sensor->pa.exposure->cur.val = sensor->pa.exposure->val; sensor->pa.exposure->flags &= ~V4L2_CTRL_FLAG_VOLATILE; mt9m114_pa_g_ctrl(sensor->pa.gain); sensor->pa.gain->cur.val = sensor->pa.gain->val; sensor->pa.gain->flags &= ~V4L2_CTRL_FLAG_VOLATILE; } else { sensor->pa.exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; sensor->pa.gain->flags |= V4L2_CTRL_FLAG_VOLATILE; } } static void mt9m114_pa_ctrl_update_blanking(struct mt9m114 *sensor, const struct v4l2_mbus_framefmt *format) { unsigned int max_blank; /* Update the blanking controls ranges based on the output size. */ max_blank = MT9M114_CAM_SENSOR_CFG_LINE_LENGTH_PCK_MAX - format->width; __v4l2_ctrl_modify_range(sensor->pa.hblank, MT9M114_MIN_HBLANK, max_blank, 1, MT9M114_DEF_HBLANK); max_blank = MT9M114_CAM_SENSOR_CFG_FRAME_LENGTH_LINES_MAX - format->height; __v4l2_ctrl_modify_range(sensor->pa.vblank, MT9M114_MIN_VBLANK, max_blank, 1, MT9M114_DEF_VBLANK); } /* ----------------------------------------------------------------------------- * Pixel Array Subdev Operations */ static inline struct mt9m114 *pa_to_mt9m114(struct v4l2_subdev *sd) { return container_of(sd, struct mt9m114, pa.sd); } static int mt9m114_pa_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; crop = v4l2_subdev_state_get_crop(state, 0); crop->left = 0; crop->top = 0; crop->width = MT9M114_PIXEL_ARRAY_WIDTH; crop->height = MT9M114_PIXEL_ARRAY_HEIGHT; format = v4l2_subdev_state_get_format(state, 0); format->width = MT9M114_PIXEL_ARRAY_WIDTH; format->height = MT9M114_PIXEL_ARRAY_HEIGHT; format->code = MEDIA_BUS_FMT_SGRBG10_1X10; format->field = V4L2_FIELD_NONE; format->colorspace = V4L2_COLORSPACE_RAW; format->ycbcr_enc = V4L2_YCBCR_ENC_601; format->quantization = V4L2_QUANTIZATION_FULL_RANGE; format->xfer_func = V4L2_XFER_FUNC_NONE; return 0; } static int mt9m114_pa_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) { if (code->index > 0) return -EINVAL; code->code = MEDIA_BUS_FMT_SGRBG10_1X10; return 0; } static int mt9m114_pa_enum_framesizes(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_size_enum *fse) { if (fse->index > 1) return -EINVAL; if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) return -EINVAL; /* Report binning capability through frame size enumeration. */ fse->min_width = MT9M114_PIXEL_ARRAY_WIDTH / (fse->index + 1); fse->max_width = MT9M114_PIXEL_ARRAY_WIDTH / (fse->index + 1); fse->min_height = MT9M114_PIXEL_ARRAY_HEIGHT / (fse->index + 1); fse->max_height = MT9M114_PIXEL_ARRAY_HEIGHT / (fse->index + 1); return 0; } static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { struct mt9m114 *sensor = pa_to_mt9m114(sd); struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; unsigned int hscale; unsigned int vscale; crop = v4l2_subdev_state_get_crop(state, fmt->pad); format = v4l2_subdev_state_get_format(state, fmt->pad); /* The sensor can bin horizontally and vertically. */ hscale = DIV_ROUND_CLOSEST(crop->width, fmt->format.width ? : 1); vscale = DIV_ROUND_CLOSEST(crop->height, fmt->format.height ? : 1); format->width = crop->width / clamp(hscale, 1U, 2U); format->height = crop->height / clamp(vscale, 1U, 2U); fmt->format = *format; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) mt9m114_pa_ctrl_update_blanking(sensor, format); return 0; } static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { switch (sel->target) { case V4L2_SEL_TGT_CROP: sel->r = *v4l2_subdev_state_get_crop(state, sel->pad); return 0; case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_NATIVE_SIZE: sel->r.left = 0; sel->r.top = 0; sel->r.width = MT9M114_PIXEL_ARRAY_WIDTH; sel->r.height = MT9M114_PIXEL_ARRAY_HEIGHT; return 0; default: return -EINVAL; } } static int mt9m114_pa_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { struct mt9m114 *sensor = pa_to_mt9m114(sd); struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; if (sel->target != V4L2_SEL_TGT_CROP) return -EINVAL; crop = v4l2_subdev_state_get_crop(state, sel->pad); format = v4l2_subdev_state_get_format(state, sel->pad); /* * Clamp the crop rectangle. The vertical coordinates must be even, and * the horizontal coordinates must be a multiple of 4. * * FIXME: The horizontal coordinates must be a multiple of 8 when * binning, but binning is configured after setting the selection, so * we can't know tell here if it will be used. */ crop->left = ALIGN(sel->r.left, 4); crop->top = ALIGN(sel->r.top, 2); crop->width = clamp_t(unsigned int, ALIGN(sel->r.width, 4), MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH, MT9M114_PIXEL_ARRAY_WIDTH - crop->left); crop->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT, MT9M114_PIXEL_ARRAY_HEIGHT - crop->top); sel->r = *crop; /* Reset the format. */ format->width = crop->width; format->height = crop->height; if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) mt9m114_pa_ctrl_update_blanking(sensor, format); return 0; } static const struct v4l2_subdev_pad_ops mt9m114_pa_pad_ops = { .enum_mbus_code = mt9m114_pa_enum_mbus_code, .enum_frame_size = mt9m114_pa_enum_framesizes, .get_fmt = v4l2_subdev_get_fmt, .set_fmt = mt9m114_pa_set_fmt, .get_selection = mt9m114_pa_get_selection, .set_selection = mt9m114_pa_set_selection, }; static const struct v4l2_subdev_ops mt9m114_pa_ops = { .pad = &mt9m114_pa_pad_ops, }; static const struct v4l2_subdev_internal_ops mt9m114_pa_internal_ops = { .init_state = mt9m114_pa_init_state, }; static int mt9m114_pa_init(struct mt9m114 *sensor) { struct v4l2_ctrl_handler *hdl = &sensor->pa.hdl; struct v4l2_subdev *sd = &sensor->pa.sd; struct media_pad *pads = &sensor->pa.pad; const struct v4l2_mbus_framefmt *format; struct v4l2_subdev_state *state; unsigned int max_exposure; int ret; /* Initialize the subdev. */ v4l2_subdev_init(sd, &mt9m114_pa_ops); sd->internal_ops = &mt9m114_pa_internal_ops; v4l2_i2c_subdev_set_name(sd, sensor->client, NULL, " pixel array"); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; sd->owner = THIS_MODULE; sd->dev = &sensor->client->dev; v4l2_set_subdevdata(sd, sensor->client); /* Initialize the media entity. */ sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; sd->entity.ops = &mt9m114_entity_ops; pads[0].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&sd->entity, 1, pads); if (ret < 0) return ret; /* Initialize the control handler. */ v4l2_ctrl_handler_init(hdl, 7); /* The range of the HBLANK and VBLANK controls will be updated below. */ sensor->pa.hblank = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_HBLANK, MT9M114_DEF_HBLANK, MT9M114_DEF_HBLANK, 1, MT9M114_DEF_HBLANK); sensor->pa.vblank = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_VBLANK, MT9M114_DEF_VBLANK, MT9M114_DEF_VBLANK, 1, MT9M114_DEF_VBLANK); /* * The maximum coarse integration time is the frame length in lines * minus two. The default is taken directly from the datasheet, but * makes little sense as auto-exposure is enabled by default. */ max_exposure = MT9M114_PIXEL_ARRAY_HEIGHT + MT9M114_MIN_VBLANK - 2; sensor->pa.exposure = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_EXPOSURE, 1, max_exposure, 1, 16); if (sensor->pa.exposure) sensor->pa.exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; sensor->pa.gain = v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, 1, 511, 1, 32); if (sensor->pa.gain) sensor->pa.gain->flags |= V4L2_CTRL_FLAG_VOLATILE; v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_PIXEL_RATE, sensor->pixrate, sensor->pixrate, 1, sensor->pixrate); v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std(hdl, &mt9m114_pa_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); if (hdl->error) { ret = hdl->error; goto error; } sd->state_lock = hdl->lock; ret = v4l2_subdev_init_finalize(sd); if (ret) goto error; /* Update the range of the blanking controls based on the format. */ state = v4l2_subdev_lock_and_get_active_state(sd); format = v4l2_subdev_state_get_format(state, 0); mt9m114_pa_ctrl_update_blanking(sensor, format); v4l2_subdev_unlock_state(state); sd->ctrl_handler = hdl; return 0; error: v4l2_ctrl_handler_free(&sensor->pa.hdl); media_entity_cleanup(&sensor->pa.sd.entity); return ret; } static void mt9m114_pa_cleanup(struct mt9m114 *sensor) { v4l2_ctrl_handler_free(&sensor->pa.hdl); media_entity_cleanup(&sensor->pa.sd.entity); } /* ----------------------------------------------------------------------------- * Image Flow Processor Control Operations */ static const char * const mt9m114_test_pattern_menu[] = { "Disabled", "Solid Color", "100% Color Bars", "Pseudo-Random", "Fade-to-Gray Color Bars", "Walking Ones 10-bit", "Walking Ones 8-bit", }; /* Keep in sync with mt9m114_test_pattern_menu */ static const unsigned int mt9m114_test_pattern_value[] = { MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID, MT9M114_CAM_MODE_TEST_PATTERN_SELECT_SOLID_BARS, MT9M114_CAM_MODE_TEST_PATTERN_SELECT_RANDOM, MT9M114_CAM_MODE_TEST_PATTERN_SELECT_FADING_BARS, MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_10B, MT9M114_CAM_MODE_TEST_PATTERN_SELECT_WALKING_1S_8B, }; static inline struct mt9m114 *ifp_ctrl_to_mt9m114(struct v4l2_ctrl *ctrl) { return container_of(ctrl->handler, struct mt9m114, ifp.hdl); } static int mt9m114_ifp_s_ctrl(struct v4l2_ctrl *ctrl) { struct mt9m114 *sensor = ifp_ctrl_to_mt9m114(ctrl); u32 value; int ret = 0; if (ctrl->id == V4L2_CID_EXPOSURE_AUTO) mt9m114_pa_ctrl_update_exposure(sensor, ctrl->val != V4L2_EXPOSURE_AUTO); /* V4L2 controls values are applied only when power is up. */ if (!pm_runtime_get_if_in_use(&sensor->client->dev)) return 0; switch (ctrl->id) { case V4L2_CID_AUTO_WHITE_BALANCE: /* Control both the AWB mode and the CCM algorithm. */ if (ctrl->val) value = MT9M114_CAM_AWB_MODE_AUTO | MT9M114_CAM_AWB_MODE_EXCLUSIVE_AE; else value = 0; cci_write(sensor->regmap, MT9M114_CAM_AWB_AWBMODE, value, &ret); if (ctrl->val) value = MT9M114_CCM_EXEC_CALC_CCM_MATRIX | 0x22; else value = 0; cci_write(sensor->regmap, MT9M114_CCM_ALGO, value, &ret); break; case V4L2_CID_EXPOSURE_AUTO: if (ctrl->val == V4L2_EXPOSURE_AUTO) value = MT9M114_AE_TRACK_EXEC_AUTOMATIC_EXPOSURE | 0x00fe; else value = 0; cci_write(sensor->regmap, MT9M114_AE_TRACK_ALGO, value, &ret); if (ret) break; break; case V4L2_CID_TEST_PATTERN: case V4L2_CID_TEST_PATTERN_RED: case V4L2_CID_TEST_PATTERN_GREENR: case V4L2_CID_TEST_PATTERN_BLUE: { unsigned int pattern = sensor->ifp.tpg[MT9M114_TPG_PATTERN]->val; if (pattern) { cci_write(sensor->regmap, MT9M114_CAM_MODE_SELECT, MT9M114_CAM_MODE_SELECT_TEST_PATTERN, &ret); cci_write(sensor->regmap, MT9M114_CAM_MODE_TEST_PATTERN_SELECT, mt9m114_test_pattern_value[pattern - 1], &ret); cci_write(sensor->regmap, MT9M114_CAM_MODE_TEST_PATTERN_RED, sensor->ifp.tpg[MT9M114_TPG_RED]->val, &ret); cci_write(sensor->regmap, MT9M114_CAM_MODE_TEST_PATTERN_GREEN, sensor->ifp.tpg[MT9M114_TPG_GREEN]->val, &ret); cci_write(sensor->regmap, MT9M114_CAM_MODE_TEST_PATTERN_BLUE, sensor->ifp.tpg[MT9M114_TPG_BLUE]->val, &ret); } else { cci_write(sensor->regmap, MT9M114_CAM_MODE_SELECT, MT9M114_CAM_MODE_SELECT_NORMAL, &ret); } /* * A Config-Change needs to be issued for the change to take * effect. If we're not streaming ignore this, the change will * be applied when the stream is started. */ if (ret || !sensor->streaming) break; ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); break; } default: ret = -EINVAL; break; } pm_runtime_mark_last_busy(&sensor->client->dev); pm_runtime_put_autosuspend(&sensor->client->dev); return ret; } static const struct v4l2_ctrl_ops mt9m114_ifp_ctrl_ops = { .s_ctrl = mt9m114_ifp_s_ctrl, }; /* ----------------------------------------------------------------------------- * Image Flow Processor Subdev Operations */ static inline struct mt9m114 *ifp_to_mt9m114(struct v4l2_subdev *sd) { return container_of(sd, struct mt9m114, ifp.sd); } static int mt9m114_ifp_s_stream(struct v4l2_subdev *sd, int enable) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); struct v4l2_subdev_state *pa_state; struct v4l2_subdev_state *ifp_state; int ret; if (!enable) return mt9m114_stop_streaming(sensor); ifp_state = v4l2_subdev_lock_and_get_active_state(&sensor->ifp.sd); pa_state = v4l2_subdev_lock_and_get_active_state(&sensor->pa.sd); ret = mt9m114_start_streaming(sensor, pa_state, ifp_state); v4l2_subdev_unlock_state(pa_state); v4l2_subdev_unlock_state(ifp_state); return ret; } static int mt9m114_ifp_get_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_interval *interval) { struct v4l2_fract *ival = &interval->interval; struct mt9m114 *sensor = ifp_to_mt9m114(sd); /* * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 * subdev active state API. */ if (interval->which != V4L2_SUBDEV_FORMAT_ACTIVE) return -EINVAL; mutex_lock(sensor->ifp.hdl.lock); ival->numerator = 1; ival->denominator = sensor->ifp.frame_rate; mutex_unlock(sensor->ifp.hdl.lock); return 0; } static int mt9m114_ifp_set_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_interval *interval) { struct v4l2_fract *ival = &interval->interval; struct mt9m114 *sensor = ifp_to_mt9m114(sd); int ret = 0; /* * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 * subdev active state API. */ if (interval->which != V4L2_SUBDEV_FORMAT_ACTIVE) return -EINVAL; mutex_lock(sensor->ifp.hdl.lock); if (ival->numerator != 0 && ival->denominator != 0) sensor->ifp.frame_rate = min_t(unsigned int, ival->denominator / ival->numerator, MT9M114_MAX_FRAME_RATE); else sensor->ifp.frame_rate = MT9M114_MAX_FRAME_RATE; ival->numerator = 1; ival->denominator = sensor->ifp.frame_rate; if (sensor->streaming) ret = mt9m114_set_frame_rate(sensor); mutex_unlock(sensor->ifp.hdl.lock); return ret; } static int mt9m114_ifp_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; struct v4l2_rect *compose; format = v4l2_subdev_state_get_format(state, 0); format->width = MT9M114_PIXEL_ARRAY_WIDTH; format->height = MT9M114_PIXEL_ARRAY_HEIGHT; format->code = MEDIA_BUS_FMT_SGRBG10_1X10; format->field = V4L2_FIELD_NONE; format->colorspace = V4L2_COLORSPACE_RAW; format->ycbcr_enc = V4L2_YCBCR_ENC_601; format->quantization = V4L2_QUANTIZATION_FULL_RANGE; format->xfer_func = V4L2_XFER_FUNC_NONE; crop = v4l2_subdev_state_get_crop(state, 0); crop->left = 4; crop->top = 4; crop->width = format->width - 8; crop->height = format->height - 8; compose = v4l2_subdev_state_get_compose(state, 0); compose->left = 0; compose->top = 0; compose->width = crop->width; compose->height = crop->height; format = v4l2_subdev_state_get_format(state, 1); format->width = compose->width; format->height = compose->height; format->code = mt9m114_default_format_info(sensor)->code; format->field = V4L2_FIELD_NONE; format->colorspace = V4L2_COLORSPACE_SRGB; format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; format->quantization = V4L2_QUANTIZATION_DEFAULT; format->xfer_func = V4L2_XFER_FUNC_DEFAULT; return 0; } static int mt9m114_ifp_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) { const unsigned int num_formats = ARRAY_SIZE(mt9m114_format_infos); struct mt9m114 *sensor = ifp_to_mt9m114(sd); unsigned int index = 0; unsigned int flag; unsigned int i; switch (code->pad) { case 0: if (code->index != 0) return -EINVAL; code->code = mt9m114_format_infos[num_formats - 1].code; return 0; case 1: if (sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY) flag = MT9M114_FMT_FLAG_CSI2; else flag = MT9M114_FMT_FLAG_PARALLEL; for (i = 0; i < num_formats; ++i) { const struct mt9m114_format_info *info = &mt9m114_format_infos[i]; if (info->flags & flag) { if (index == code->index) { code->code = info->code; return 0; } index++; } } return -EINVAL; default: return -EINVAL; } } static int mt9m114_ifp_enum_framesizes(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_size_enum *fse) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); const struct mt9m114_format_info *info; if (fse->index > 0) return -EINVAL; info = mt9m114_format_info(sensor, fse->pad, fse->code); if (!info || info->code != fse->code) return -EINVAL; if (fse->pad == 0) { fse->min_width = MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH; fse->max_width = MT9M114_PIXEL_ARRAY_WIDTH; fse->min_height = MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT; fse->max_height = MT9M114_PIXEL_ARRAY_HEIGHT; } else { const struct v4l2_rect *crop; crop = v4l2_subdev_state_get_crop(state, 0); fse->max_width = crop->width; fse->max_height = crop->height; fse->min_width = fse->max_width / 4; fse->min_height = fse->max_height / 4; } return 0; } static int mt9m114_ifp_enum_frameintervals(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval_enum *fie) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); const struct mt9m114_format_info *info; if (fie->index > 0) return -EINVAL; info = mt9m114_format_info(sensor, fie->pad, fie->code); if (!info || info->code != fie->code) return -EINVAL; fie->interval.numerator = 1; fie->interval.denominator = MT9M114_MAX_FRAME_RATE; return 0; } static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); struct v4l2_mbus_framefmt *format; format = v4l2_subdev_state_get_format(state, fmt->pad); if (fmt->pad == 0) { /* Only the size can be changed on the sink pad. */ format->width = clamp(ALIGN(fmt->format.width, 8), MT9M114_PIXEL_ARRAY_MIN_OUTPUT_WIDTH, MT9M114_PIXEL_ARRAY_WIDTH); format->height = clamp(ALIGN(fmt->format.height, 8), MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT, MT9M114_PIXEL_ARRAY_HEIGHT); } else { const struct mt9m114_format_info *info; /* Only the media bus code can be changed on the source pad. */ info = mt9m114_format_info(sensor, 1, fmt->format.code); format->code = info->code; /* If the output format is RAW10, bypass the scaler. */ if (format->code == MEDIA_BUS_FMT_SGRBG10_1X10) *format = *v4l2_subdev_state_get_format(state, 0); } fmt->format = *format; return 0; } static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { const struct v4l2_mbus_framefmt *format; const struct v4l2_rect *crop; int ret = 0; /* Crop and compose are only supported on the sink pad. */ if (sel->pad != 0) return -EINVAL; switch (sel->target) { case V4L2_SEL_TGT_CROP: sel->r = *v4l2_subdev_state_get_crop(state, 0); break; case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP_BOUNDS: /* * The crop default and bounds are equal to the sink * format size minus 4 pixels on each side for demosaicing. */ format = v4l2_subdev_state_get_format(state, 0); sel->r.left = 4; sel->r.top = 4; sel->r.width = format->width - 8; sel->r.height = format->height - 8; break; case V4L2_SEL_TGT_COMPOSE: sel->r = *v4l2_subdev_state_get_compose(state, 0); break; case V4L2_SEL_TGT_COMPOSE_DEFAULT: case V4L2_SEL_TGT_COMPOSE_BOUNDS: /* * The compose default and bounds sizes are equal to the sink * crop rectangle size. */ crop = v4l2_subdev_state_get_crop(state, 0); sel->r.left = 0; sel->r.top = 0; sel->r.width = crop->width; sel->r.height = crop->height; break; default: ret = -EINVAL; break; } return ret; } static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; struct v4l2_rect *compose; if (sel->target != V4L2_SEL_TGT_CROP && sel->target != V4L2_SEL_TGT_COMPOSE) return -EINVAL; /* Crop and compose are only supported on the sink pad. */ if (sel->pad != 0) return -EINVAL; format = v4l2_subdev_state_get_format(state, 0); crop = v4l2_subdev_state_get_crop(state, 0); compose = v4l2_subdev_state_get_compose(state, 0); if (sel->target == V4L2_SEL_TGT_CROP) { /* * Clamp the crop rectangle. Demosaicing removes 4 pixels on * each side of the image. */ crop->left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), 4, format->width - 4 - MT9M114_SCALER_CROPPED_INPUT_WIDTH); crop->top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), 4, format->height - 4 - MT9M114_SCALER_CROPPED_INPUT_HEIGHT); crop->width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), MT9M114_SCALER_CROPPED_INPUT_WIDTH, format->width - 4 - crop->left); crop->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), MT9M114_SCALER_CROPPED_INPUT_HEIGHT, format->height - 4 - crop->top); sel->r = *crop; /* Propagate to the compose rectangle. */ compose->width = crop->width; compose->height = crop->height; } else { /* * Clamp the compose rectangle. The scaler can only downscale. */ compose->left = 0; compose->top = 0; compose->width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), MT9M114_SCALER_CROPPED_INPUT_WIDTH, crop->width); compose->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), MT9M114_SCALER_CROPPED_INPUT_HEIGHT, crop->height); sel->r = *compose; } /* Propagate the compose rectangle to the source format. */ format = v4l2_subdev_state_get_format(state, 1); format->width = compose->width; format->height = compose->height; return 0; } static void mt9m114_ifp_unregistered(struct v4l2_subdev *sd) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); v4l2_device_unregister_subdev(&sensor->pa.sd); } static int mt9m114_ifp_registered(struct v4l2_subdev *sd) { struct mt9m114 *sensor = ifp_to_mt9m114(sd); int ret; ret = v4l2_device_register_subdev(sd->v4l2_dev, &sensor->pa.sd); if (ret < 0) { dev_err(&sensor->client->dev, "Failed to register pixel array subdev\n"); return ret; } ret = media_create_pad_link(&sensor->pa.sd.entity, 0, &sensor->ifp.sd.entity, 0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); if (ret < 0) { dev_err(&sensor->client->dev, "Failed to link pixel array to ifp\n"); v4l2_device_unregister_subdev(&sensor->pa.sd); return ret; } return 0; } static const struct v4l2_subdev_video_ops mt9m114_ifp_video_ops = { .s_stream = mt9m114_ifp_s_stream, }; static const struct v4l2_subdev_pad_ops mt9m114_ifp_pad_ops = { .enum_mbus_code = mt9m114_ifp_enum_mbus_code, .enum_frame_size = mt9m114_ifp_enum_framesizes, .enum_frame_interval = mt9m114_ifp_enum_frameintervals, .get_fmt = v4l2_subdev_get_fmt, .set_fmt = mt9m114_ifp_set_fmt, .get_selection = mt9m114_ifp_get_selection, .set_selection = mt9m114_ifp_set_selection, .get_frame_interval = mt9m114_ifp_get_frame_interval, .set_frame_interval = mt9m114_ifp_set_frame_interval, }; static const struct v4l2_subdev_ops mt9m114_ifp_ops = { .video = &mt9m114_ifp_video_ops, .pad = &mt9m114_ifp_pad_ops, }; static const struct v4l2_subdev_internal_ops mt9m114_ifp_internal_ops = { .init_state = mt9m114_ifp_init_state, .registered = mt9m114_ifp_registered, .unregistered = mt9m114_ifp_unregistered, }; static int mt9m114_ifp_init(struct mt9m114 *sensor) { struct v4l2_subdev *sd = &sensor->ifp.sd; struct media_pad *pads = sensor->ifp.pads; struct v4l2_ctrl_handler *hdl = &sensor->ifp.hdl; struct v4l2_ctrl *link_freq; int ret; /* Initialize the subdev. */ v4l2_i2c_subdev_init(sd, sensor->client, &mt9m114_ifp_ops); v4l2_i2c_subdev_set_name(sd, sensor->client, NULL, " ifp"); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; sd->internal_ops = &mt9m114_ifp_internal_ops; /* Initialize the media entity. */ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP; sd->entity.ops = &mt9m114_entity_ops; pads[0].flags = MEDIA_PAD_FL_SINK; pads[1].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&sd->entity, 2, pads); if (ret < 0) return ret; sensor->ifp.frame_rate = MT9M114_DEF_FRAME_RATE; /* Initialize the control handler. */ v4l2_ctrl_handler_init(hdl, 8); v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); v4l2_ctrl_new_std_menu(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); link_freq = v4l2_ctrl_new_int_menu(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_LINK_FREQ, sensor->bus_cfg.nr_of_link_frequencies - 1, 0, sensor->bus_cfg.link_frequencies); if (link_freq) link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_PIXEL_RATE, sensor->pixrate, sensor->pixrate, 1, sensor->pixrate); sensor->ifp.tpg[MT9M114_TPG_PATTERN] = v4l2_ctrl_new_std_menu_items(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(mt9m114_test_pattern_menu) - 1, 0, 0, mt9m114_test_pattern_menu); sensor->ifp.tpg[MT9M114_TPG_RED] = v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_TEST_PATTERN_RED, 0, 1023, 1, 1023); sensor->ifp.tpg[MT9M114_TPG_GREEN] = v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_TEST_PATTERN_GREENR, 0, 1023, 1, 1023); sensor->ifp.tpg[MT9M114_TPG_BLUE] = v4l2_ctrl_new_std(hdl, &mt9m114_ifp_ctrl_ops, V4L2_CID_TEST_PATTERN_BLUE, 0, 1023, 1, 1023); v4l2_ctrl_cluster(ARRAY_SIZE(sensor->ifp.tpg), sensor->ifp.tpg); if (hdl->error) { ret = hdl->error; goto error; } sd->ctrl_handler = hdl; sd->state_lock = hdl->lock; ret = v4l2_subdev_init_finalize(sd); if (ret) goto error; return 0; error: v4l2_ctrl_handler_free(&sensor->ifp.hdl); media_entity_cleanup(&sensor->ifp.sd.entity); return ret; } static void mt9m114_ifp_cleanup(struct mt9m114 *sensor) { v4l2_ctrl_handler_free(&sensor->ifp.hdl); media_entity_cleanup(&sensor->ifp.sd.entity); } /* ----------------------------------------------------------------------------- * Power Management */ static int mt9m114_power_on(struct mt9m114 *sensor) { int ret; /* Enable power and clocks. */ ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), sensor->supplies); if (ret < 0) return ret; ret = clk_prepare_enable(sensor->clk); if (ret < 0) goto error_regulator; /* Perform a hard reset if available, or a soft reset otherwise. */ if (sensor->reset) { long freq = clk_get_rate(sensor->clk); unsigned int duration; /* * The minimum duration is 50 clock cycles, thus typically * around 2µs. Double it to be safe. */ duration = DIV_ROUND_UP(2 * 50 * 1000000, freq); gpiod_set_value(sensor->reset, 1); fsleep(duration); gpiod_set_value(sensor->reset, 0); } else { /* * The power may have just been turned on, we need to wait for * the sensor to be ready to accept I2C commands. */ usleep_range(44500, 50000); cci_write(sensor->regmap, MT9M114_RESET_AND_MISC_CONTROL, MT9M114_RESET_SOC, &ret); cci_write(sensor->regmap, MT9M114_RESET_AND_MISC_CONTROL, 0, &ret); if (ret < 0) { dev_err(&sensor->client->dev, "Soft reset failed\n"); goto error_clock; } } /* * Wait for the sensor to be ready to accept I2C commands by polling the * command register to wait for initialization to complete. */ usleep_range(44500, 50000); ret = mt9m114_poll_command(sensor, MT9M114_COMMAND_REGISTER_SET_STATE); if (ret < 0) goto error_clock; if (sensor->bus_cfg.bus_type == V4L2_MBUS_PARALLEL) { /* * In parallel mode (OE set to low), the sensor will enter the * streaming state after initialization. Enter the standby * manually to stop streaming. */ ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_STANDBY); if (ret < 0) goto error_clock; } /* * Before issuing any Set-State command, we must ensure that the sensor * reaches the standby mode (either initiated manually above in * parallel mode, or automatically after reset in MIPI mode). */ ret = mt9m114_poll_state(sensor, MT9M114_SYS_STATE_STANDBY); if (ret < 0) goto error_clock; return 0; error_clock: clk_disable_unprepare(sensor->clk); error_regulator: regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); return ret; } static void mt9m114_power_off(struct mt9m114 *sensor) { clk_disable_unprepare(sensor->clk); regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); } static int __maybe_unused mt9m114_runtime_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct mt9m114 *sensor = ifp_to_mt9m114(sd); int ret; ret = mt9m114_power_on(sensor); if (ret) return ret; ret = mt9m114_initialize(sensor); if (ret) { mt9m114_power_off(sensor); return ret; } return 0; } static int __maybe_unused mt9m114_runtime_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct mt9m114 *sensor = ifp_to_mt9m114(sd); mt9m114_power_off(sensor); return 0; } static const struct dev_pm_ops mt9m114_pm_ops = { SET_RUNTIME_PM_OPS(mt9m114_runtime_suspend, mt9m114_runtime_resume, NULL) }; /* ----------------------------------------------------------------------------- * Probe & Remove */ static int mt9m114_clk_init(struct mt9m114 *sensor) { unsigned int link_freq; /* Hardcode the PLL multiplier and dividers to default settings. */ sensor->pll.m = 32; sensor->pll.n = 1; sensor->pll.p = 7; /* * Calculate the pixel rate and link frequency. The CSI-2 bus is clocked * for 16-bit per pixel, transmitted in DDR over a single lane. For * parallel mode, the sensor ouputs one pixel in two PIXCLK cycles. */ sensor->pixrate = clk_get_rate(sensor->clk) * sensor->pll.m / ((sensor->pll.n + 1) * (sensor->pll.p + 1)); link_freq = sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY ? sensor->pixrate * 8 : sensor->pixrate * 2; if (sensor->bus_cfg.nr_of_link_frequencies != 1 || sensor->bus_cfg.link_frequencies[0] != link_freq) { dev_err(&sensor->client->dev, "Unsupported DT link-frequencies\n"); return -EINVAL; } return 0; } static int mt9m114_identify(struct mt9m114 *sensor) { u64 major, minor, release, customer; u64 value; int ret; ret = cci_read(sensor->regmap, MT9M114_CHIP_ID, &value, NULL); if (ret) { dev_err(&sensor->client->dev, "Failed to read chip ID\n"); return -ENXIO; } if (value != 0x2481) { dev_err(&sensor->client->dev, "Invalid chip ID 0x%04llx\n", value); return -ENXIO; } cci_read(sensor->regmap, MT9M114_MON_MAJOR_VERSION, &major, &ret); cci_read(sensor->regmap, MT9M114_MON_MINOR_VERSION, &minor, &ret); cci_read(sensor->regmap, MT9M114_MON_RELEASE_VERSION, &release, &ret); cci_read(sensor->regmap, MT9M114_CUSTOMER_REV, &customer, &ret); if (ret) { dev_err(&sensor->client->dev, "Failed to read version\n"); return -ENXIO; } dev_dbg(&sensor->client->dev, "monitor v%llu.%llu.%04llx customer rev 0x%04llx\n", major, minor, release, customer); return 0; } static int mt9m114_parse_dt(struct mt9m114 *sensor) { struct fwnode_handle *fwnode = dev_fwnode(&sensor->client->dev); struct fwnode_handle *ep; int ret; ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) { dev_err(&sensor->client->dev, "No endpoint found\n"); return -EINVAL; } sensor->bus_cfg.bus_type = V4L2_MBUS_UNKNOWN; ret = v4l2_fwnode_endpoint_alloc_parse(ep, &sensor->bus_cfg); fwnode_handle_put(ep); if (ret < 0) { dev_err(&sensor->client->dev, "Failed to parse endpoint\n"); goto error; } switch (sensor->bus_cfg.bus_type) { case V4L2_MBUS_CSI2_DPHY: case V4L2_MBUS_PARALLEL: break; default: dev_err(&sensor->client->dev, "unsupported bus type %u\n", sensor->bus_cfg.bus_type); ret = -EINVAL; goto error; } return 0; error: v4l2_fwnode_endpoint_free(&sensor->bus_cfg); return ret; } static int mt9m114_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct mt9m114 *sensor; int ret; sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) return -ENOMEM; sensor->client = client; sensor->regmap = devm_cci_regmap_init_i2c(client, 16); if (IS_ERR(sensor->regmap)) { dev_err(dev, "Unable to initialize I2C\n"); return -ENODEV; } ret = mt9m114_parse_dt(sensor); if (ret < 0) return ret; /* Acquire clocks, GPIOs and regulators. */ sensor->clk = devm_clk_get(dev, NULL); if (IS_ERR(sensor->clk)) { ret = PTR_ERR(sensor->clk); dev_err_probe(dev, ret, "Failed to get clock\n"); goto error_ep_free; } sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(sensor->reset)) { ret = PTR_ERR(sensor->reset); dev_err_probe(dev, ret, "Failed to get reset GPIO\n"); goto error_ep_free; } sensor->supplies[0].supply = "vddio"; sensor->supplies[1].supply = "vdd"; sensor->supplies[2].supply = "vaa"; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sensor->supplies), sensor->supplies); if (ret < 0) { dev_err_probe(dev, ret, "Failed to get regulators\n"); goto error_ep_free; } ret = mt9m114_clk_init(sensor); if (ret) goto error_ep_free; /* * Identify the sensor. The driver supports runtime PM, but needs to * work when runtime PM is disabled in the kernel. To that end, power * the sensor on manually here, and initialize it after identification * to reach the same state as if resumed through runtime PM. */ ret = mt9m114_power_on(sensor); if (ret < 0) { dev_err_probe(dev, ret, "Could not power on the device\n"); goto error_ep_free; } ret = mt9m114_identify(sensor); if (ret < 0) goto error_power_off; ret = mt9m114_initialize(sensor); if (ret < 0) goto error_power_off; /* * Enable runtime PM with autosuspend. As the device has been powered * manually, mark it as active, and increase the usage count without * resuming the device. */ pm_runtime_set_active(dev); pm_runtime_get_noresume(dev); pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, 1000); pm_runtime_use_autosuspend(dev); /* Initialize the subdevices. */ ret = mt9m114_pa_init(sensor); if (ret < 0) goto error_pm_cleanup; ret = mt9m114_ifp_init(sensor); if (ret < 0) goto error_pa_cleanup; ret = v4l2_async_register_subdev(&sensor->ifp.sd); if (ret < 0) goto error_ifp_cleanup; /* * Decrease the PM usage count. The device will get suspended after the * autosuspend delay, turning the power off. */ pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; error_ifp_cleanup: mt9m114_ifp_cleanup(sensor); error_pa_cleanup: mt9m114_pa_cleanup(sensor); error_pm_cleanup: pm_runtime_disable(dev); pm_runtime_put_noidle(dev); error_power_off: mt9m114_power_off(sensor); error_ep_free: v4l2_fwnode_endpoint_free(&sensor->bus_cfg); return ret; } static void mt9m114_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct mt9m114 *sensor = ifp_to_mt9m114(sd); struct device *dev = &client->dev; v4l2_async_unregister_subdev(&sensor->ifp.sd); mt9m114_ifp_cleanup(sensor); mt9m114_pa_cleanup(sensor); v4l2_fwnode_endpoint_free(&sensor->bus_cfg); /* * Disable runtime PM. In case runtime PM is disabled in the kernel, * make sure to turn power off manually. */ pm_runtime_disable(dev); if (!pm_runtime_status_suspended(dev)) mt9m114_power_off(sensor); pm_runtime_set_suspended(dev); } static const struct of_device_id mt9m114_of_ids[] = { { .compatible = "onnn,mt9m114" }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mt9m114_of_ids); static struct i2c_driver mt9m114_driver = { .driver = { .name = "mt9m114", .pm = &mt9m114_pm_ops, .of_match_table = mt9m114_of_ids, }, .probe = mt9m114_probe, .remove = mt9m114_remove, }; module_i2c_driver(mt9m114_driver); MODULE_DESCRIPTION("onsemi MT9M114 Sensor Driver"); MODULE_AUTHOR("Laurent Pinchart "); MODULE_LICENSE("GPL");