mirror of
https://github.com/nxp-imx/linux-imx.git
synced 2025-09-03 02:16:09 +02:00

Keep subdev name as v4l2 core set since libcamera camera module model identification heuristic does not work properly, it will identify camera modules by parsing the default name. Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com> Reviewed-by: Robby Cai <robby.cai@nxp.com>
2927 lines
75 KiB
C
2927 lines
75 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Driver for the AP130X external camera ISP from ON Semiconductor
|
|
*
|
|
* Copyright (C) 2021, Witekio, Inc.
|
|
* Copyright (C) 2021, Xilinx, Inc.
|
|
* Copyright (C) 2021, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/media.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <media/media-entity.h>
|
|
#include <media/mipi-csi2.h>
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-fwnode.h>
|
|
|
|
#define DRIVER_NAME "ap130x"
|
|
|
|
#define AP130X_FW_WINDOW_SIZE 0x2000
|
|
#define AP130X_FW_WINDOW_OFFSET 0x8000
|
|
|
|
#define AP130X_MIN_WIDTH 24U
|
|
#define AP130X_MIN_HEIGHT 16U
|
|
#define AP130X_MAX_WIDTH 4224U
|
|
#define AP130X_MAX_HEIGHT 4092U
|
|
|
|
#define AP130X_REG_16BIT(n) ((2 << 24) | (n))
|
|
#define AP130X_REG_32BIT(n) ((4 << 24) | (n))
|
|
#define AP130X_REG_SIZE(n) ((n) >> 24)
|
|
#define AP130X_REG_ADDR(n) ((n) & 0x0000ffff)
|
|
#define AP130X_REG_PAGE(n) ((n) & 0x00ff0000)
|
|
#define AP130X_REG_PAGE_MASK 0x00ff0000
|
|
|
|
/* Info Registers */
|
|
#define AP130X_CHIP_VERSION AP130X_REG_16BIT(0x0000)
|
|
#define AP130X_CHIP_ID 0x0265
|
|
#define AP130X_FRAME_CNT AP130X_REG_16BIT(0x0002)
|
|
#define AP130X_ERROR AP130X_REG_16BIT(0x0006)
|
|
#define AP130X_ERR_FILE AP130X_REG_32BIT(0x0008)
|
|
#define AP130X_ERR_LINE AP130X_REG_16BIT(0x000c)
|
|
#define AP130X_SIPM_ERR_0 AP130X_REG_16BIT(0x0014)
|
|
#define AP130X_SIPM_ERR_1 AP130X_REG_16BIT(0x0016)
|
|
#define AP130X_CHIP_REV AP130X_REG_16BIT(0x0050)
|
|
#define AP130X_CON_BUF(n) AP130X_REG_16BIT(0x0a2c + (n))
|
|
#define AP130X_CON_BUF_SIZE 512
|
|
|
|
/* Control Registers */
|
|
#define AP130X_DZ_TGT_FCT AP130X_REG_16BIT(0x1010)
|
|
#define AP130X_SFX_MODE AP130X_REG_16BIT(0x1016)
|
|
#define AP130X_SFX_MODE_SFX_NORMAL (0U << 0)
|
|
#define AP130X_SFX_MODE_SFX_ALIEN (1U << 0)
|
|
#define AP130X_SFX_MODE_SFX_ANTIQUE (2U << 0)
|
|
#define AP130X_SFX_MODE_SFX_BW (3U << 0)
|
|
#define AP130X_SFX_MODE_SFX_EMBOSS (4U << 0)
|
|
#define AP130X_SFX_MODE_SFX_EMBOSS_COLORED (5U << 0)
|
|
#define AP130X_SFX_MODE_SFX_GRAYSCALE (6U << 0)
|
|
#define AP130X_SFX_MODE_SFX_NEGATIVE (7U << 0)
|
|
#define AP130X_SFX_MODE_SFX_BLUISH (8U << 0)
|
|
#define AP130X_SFX_MODE_SFX_GREENISH (9U << 0)
|
|
#define AP130X_SFX_MODE_SFX_REDISH (10U << 0)
|
|
#define AP130X_SFX_MODE_SFX_POSTERIZE1 (11U << 0)
|
|
#define AP130X_SFX_MODE_SFX_POSTERIZE2 (12U << 0)
|
|
#define AP130X_SFX_MODE_SFX_SEPIA1 (13U << 0)
|
|
#define AP130X_SFX_MODE_SFX_SEPIA2 (14U << 0)
|
|
#define AP130X_SFX_MODE_SFX_SKETCH (15U << 0)
|
|
#define AP130X_SFX_MODE_SFX_SOLARIZE (16U << 0)
|
|
#define AP130X_SFX_MODE_SFX_FOGGY (17U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT AP130X_REG_16BIT(0x1164)
|
|
#define AP130X_BUBBLE_OUT_FMT_FT_YUV (3U << 4)
|
|
#define AP130X_BUBBLE_OUT_FMT_FT_RGB (4U << 4)
|
|
#define AP130X_BUBBLE_OUT_FMT_FT_YUV_JFIF (5U << 4)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_RGB_888 (0U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_RGB_565 (1U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_RGB_555M (2U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_RGB_555L (3U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_YUV_422 (0U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_YUV_420 (1U << 0)
|
|
#define AP130X_BUBBLE_OUT_FMT_FST_YUV_400 (2U << 0)
|
|
#define AP130X_ATOMIC AP130X_REG_16BIT(0x1184)
|
|
#define AP130X_ATOMIC_MODE BIT(2)
|
|
#define AP130X_ATOMIC_FINISH BIT(1)
|
|
#define AP130X_ATOMIC_RECORD BIT(0)
|
|
|
|
/*
|
|
* Preview Context Registers (preview_*). AP130X supports 3 "contexts"
|
|
* (Preview, Snapshot, Video). These can be programmed for different size,
|
|
* format, FPS, etc. There is no functional difference between the contexts,
|
|
* so the only potential benefit of using them is reduced number of register
|
|
* writes when switching output modes (if your concern is atomicity, see
|
|
* "atomic" register).
|
|
* So there's virtually no benefit in using contexts for this driver and it
|
|
* would significantly increase complexity. Let's use preview context only.
|
|
*/
|
|
#define AP130X_PREVIEW_WIDTH AP130X_REG_16BIT(0x2000)
|
|
#define AP130X_PREVIEW_HEIGHT AP130X_REG_16BIT(0x2002)
|
|
#define AP130X_PREVIEW_ROI_X0 AP130X_REG_16BIT(0x2004)
|
|
#define AP130X_PREVIEW_ROI_Y0 AP130X_REG_16BIT(0x2006)
|
|
#define AP130X_PREVIEW_ROI_X1 AP130X_REG_16BIT(0x2008)
|
|
#define AP130X_PREVIEW_ROI_Y1 AP130X_REG_16BIT(0x200a)
|
|
#define AP130X_PREVIEW_OUT_FMT AP130X_REG_16BIT(0x2012)
|
|
#define AP130X_PREVIEW_OUT_FMT_IPIPE_BYPASS BIT(13)
|
|
#define AP130X_PREVIEW_OUT_FMT_SS BIT(12)
|
|
#define AP130X_PREVIEW_OUT_FMT_FAKE_EN BIT(11)
|
|
#define AP130X_PREVIEW_OUT_FMT_ST_EN BIT(10)
|
|
#define AP130X_PREVIEW_OUT_FMT_IIS_NONE (0U << 8)
|
|
#define AP130X_PREVIEW_OUT_FMT_IIS_POST_VIEW (1U << 8)
|
|
#define AP130X_PREVIEW_OUT_FMT_IIS_VIDEO (2U << 8)
|
|
#define AP130X_PREVIEW_OUT_FMT_IIS_BUBBLE (3U << 8)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_JPEG_422 (0U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_JPEG_420 (1U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_YUV (3U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_RGB (4U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_YUV_JFIF (5U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_RAW8 (8U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_RAW10 (9U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_RAW12 (10U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_RAW16 (11U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_DNG8 (12U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_DNG10 (13U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_DNG12 (14U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FT_DNG16 (15U << 4)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_JPEG_ROTATE BIT(2)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_JPEG_SCAN (0U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_JPEG_JFIF (1U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_JPEG_EXIF (2U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RGB_888 (0U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RGB_565 (1U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RGB_555M (2U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RGB_555L (3U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_YUV_422 (0U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_YUV_420 (1U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_YUV_400 (2U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_SENSOR (0U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_CAPTURE (1U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_CP (2U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_BPC (3U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_IHDR (4U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_PP (5U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_DENSH (6U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_PM (7U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_GC (8U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_CURVE (9U << 0)
|
|
#define AP130X_PREVIEW_OUT_FMT_FST_RAW_CCONV (10U << 0)
|
|
#define AP130X_PREVIEW_S1_SENSOR_MODE AP130X_REG_16BIT(0x202e)
|
|
#define AP130X_PREVIEW_HINF_CTRL AP130X_REG_16BIT(0x2030)
|
|
#define AP130X_PREVIEW_HINF_CTRL_BT656_LE BIT(15)
|
|
#define AP130X_PREVIEW_HINF_CTRL_BT656_16BIT BIT(14)
|
|
#define AP130X_PREVIEW_HINF_CTRL_MUX_DELAY(n) ((n) << 8)
|
|
#define AP130X_PREVIEW_HINF_CTRL_LV_POL BIT(7)
|
|
#define AP130X_PREVIEW_HINF_CTRL_FV_POL BIT(6)
|
|
#define AP130X_PREVIEW_HINF_CTRL_MIPI_CONT_CLK BIT(5)
|
|
#define AP130X_PREVIEW_HINF_CTRL_SPOOF BIT(4)
|
|
#define AP130X_PREVIEW_HINF_CTRL_MIPI_MODE BIT(3)
|
|
#define AP130X_PREVIEW_HINF_CTRL_MIPI_LANES(n) ((n) << 0)
|
|
|
|
/* IQ Registers */
|
|
#define AP130X_AE_CTRL AP130X_REG_16BIT(0x5002)
|
|
#define AP130X_AE_CTRL_STATS_SEL BIT(11)
|
|
#define AP130X_AE_CTRL_IMM BIT(10)
|
|
#define AP130X_AE_CTRL_ROUND_ISO BIT(9)
|
|
#define AP130X_AE_CTRL_UROI_FACE BIT(7)
|
|
#define AP130X_AE_CTRL_UROI_LOCK BIT(6)
|
|
#define AP130X_AE_CTRL_UROI_BOUND BIT(5)
|
|
#define AP130X_AE_CTRL_IMM1 BIT(4)
|
|
#define AP130X_AE_CTRL_MANUAL_EXP_TIME_GAIN (0U << 0)
|
|
#define AP130X_AE_CTRL_MANUAL_BV_EXP_TIME (1U << 0)
|
|
#define AP130X_AE_CTRL_MANUAL_BV_GAIN (2U << 0)
|
|
#define AP130X_AE_CTRL_MANUAL_BV_ISO (3U << 0)
|
|
#define AP130X_AE_CTRL_AUTO_BV_EXP_TIME (9U << 0)
|
|
#define AP130X_AE_CTRL_AUTO_BV_GAIN (10U << 0)
|
|
#define AP130X_AE_CTRL_AUTO_BV_ISO (11U << 0)
|
|
#define AP130X_AE_CTRL_FULL_AUTO (12U << 0)
|
|
#define AP130X_AE_CTRL_MODE_MASK 0x000f
|
|
#define AP130X_AE_MANUAL_GAIN AP130X_REG_16BIT(0x5006)
|
|
#define AP130X_AE_BV_OFF AP130X_REG_16BIT(0x5014)
|
|
#define AP130X_AE_MET AP130X_REG_16BIT(0x503E)
|
|
#define AP130X_AWB_CTRL AP130X_REG_16BIT(0x5100)
|
|
#define AP130X_AWB_CTRL_RECALC BIT(13)
|
|
#define AP130X_AWB_CTRL_POSTGAIN BIT(12)
|
|
#define AP130X_AWB_CTRL_UNGAIN BIT(11)
|
|
#define AP130X_AWB_CTRL_CLIP BIT(10)
|
|
#define AP130X_AWB_CTRL_SKY BIT(9)
|
|
#define AP130X_AWB_CTRL_FLASH BIT(8)
|
|
#define AP130X_AWB_CTRL_FACE_OFF (0U << 6)
|
|
#define AP130X_AWB_CTRL_FACE_IGNORE (1U << 6)
|
|
#define AP130X_AWB_CTRL_FACE_CONSTRAINED (2U << 6)
|
|
#define AP130X_AWB_CTRL_FACE_ONLY (3U << 6)
|
|
#define AP130X_AWB_CTRL_IMM BIT(5)
|
|
#define AP130X_AWB_CTRL_IMM1 BIT(4)
|
|
#define AP130X_AWB_CTRL_MODE_OFF (0U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_HORIZON (1U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_A (2U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_CWF (3U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_D50 (4U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_D65 (5U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_D75 (6U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_MANUAL (7U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_MEASURE (8U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_AUTO (15U << 0)
|
|
#define AP130X_AWB_CTRL_MODE_MASK 0x000f
|
|
#define AP130X_FLICK_CTRL AP130X_REG_16BIT(0x5440)
|
|
#define AP130X_FLICK_CTRL_FREQ(n) ((n) << 8)
|
|
#define AP130X_FLICK_CTRL_ETC_IHDR_UP BIT(6)
|
|
#define AP130X_FLICK_CTRL_ETC_DIS BIT(5)
|
|
#define AP130X_FLICK_CTRL_FRC_OVERRIDE_MAX_ET BIT(4)
|
|
#define AP130X_FLICK_CTRL_FRC_OVERRIDE_UPPER_ET BIT(3)
|
|
#define AP130X_FLICK_CTRL_FRC_EN BIT(2)
|
|
#define AP130X_FLICK_CTRL_MODE_DISABLED (0U << 0)
|
|
#define AP130X_FLICK_CTRL_MODE_MANUAL (1U << 0)
|
|
#define AP130X_FLICK_CTRL_MODE_AUTO (2U << 0)
|
|
#define AP130X_SCENE_CTRL AP130X_REG_16BIT(0x5454)
|
|
#define AP130X_SCENE_CTRL_MODE_NORMAL (0U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_PORTRAIT (1U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_LANDSCAPE (2U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_SPORT (3U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_CLOSE_UP (4U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_NIGHT (5U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_TWILIGHT (6U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_BACKLIGHT (7U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_HIGH_SENSITIVE (8U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_NIGHT_PORTRAIT (9U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_BEACH (10U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_DOCUMENT (11U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_PARTY (12U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_FIREWORKS (13U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_SUNSET (14U << 0)
|
|
#define AP130X_SCENE_CTRL_MODE_AUTO (0xffU << 0)
|
|
|
|
/* System Registers */
|
|
#define AP130X_BOOTDATA_STAGE AP130X_REG_16BIT(0x6002)
|
|
#define AP130X_WARNING(n) AP130X_REG_16BIT(0x6004 + (n) * 2)
|
|
#define AP130X_SENSOR_SELECT AP130X_REG_16BIT(0x600c)
|
|
#define AP130X_SENSOR_SELECT_TP_MODE(n) ((n) << 8)
|
|
#define AP130X_SENSOR_SELECT_PATTERN_ON BIT(7)
|
|
#define AP130X_SENSOR_SELECT_MODE_3D_ON BIT(6)
|
|
#define AP130X_SENSOR_SELECT_CLOCK BIT(5)
|
|
#define AP130X_SENSOR_SELECT_SINF_MIPI BIT(4)
|
|
#define AP130X_SENSOR_SELECT_YUV BIT(2)
|
|
#define AP130X_SENSOR_SELECT_SENSOR_TP (0U << 0)
|
|
#define AP130X_SENSOR_SELECT_SENSOR(n) (((n) + 1) << 0)
|
|
#define AP130X_SYS_START AP130X_REG_16BIT(0x601a)
|
|
#define AP130X_SYS_START_PLL_LOCK BIT(15)
|
|
#define AP130X_SYS_START_LOAD_OTP BIT(12)
|
|
#define AP130X_SYS_START_RESTART_ERROR BIT(11)
|
|
#define AP130X_SYS_START_STALL_STATUS BIT(9)
|
|
#define AP130X_SYS_START_STALL_EN BIT(8)
|
|
#define AP130X_SYS_START_STALL_MODE_FRAME (0U << 6)
|
|
#define AP130X_SYS_START_STALL_MODE_DISABLED (1U << 6)
|
|
#define AP130X_SYS_START_STALL_MODE_POWER_DOWN (2U << 6)
|
|
#define AP130X_SYS_START_GO BIT(4)
|
|
#define AP130X_SYS_START_PATCH_FUN BIT(1)
|
|
#define AP130X_SYS_START_PLL_INIT BIT(0)
|
|
#define AP130X_DMA_SRC AP130X_REG_32BIT(0x60a0)
|
|
#define AP130X_DMA_DST AP130X_REG_32BIT(0x60a4)
|
|
#define AP130X_DMA_SIP_SIPM(n) ((n) << 26)
|
|
#define AP130X_DMA_SIP_DATA_16_BIT BIT(25)
|
|
#define AP130X_DMA_SIP_ADDR_16_BIT BIT(24)
|
|
#define AP130X_DMA_SIP_ID(n) ((n) << 17)
|
|
#define AP130X_DMA_SIP_REG(n) ((n) << 0)
|
|
#define AP130X_DMA_SIZE AP130X_REG_32BIT(0x60a8)
|
|
#define AP130X_DMA_CTRL AP130X_REG_16BIT(0x60ac)
|
|
#define AP130X_DMA_CTRL_SCH_NORMAL (0 << 12)
|
|
#define AP130X_DMA_CTRL_SCH_NEXT (1 << 12)
|
|
#define AP130X_DMA_CTRL_SCH_NOW (2 << 12)
|
|
#define AP130X_DMA_CTRL_DST_REG (0 << 8)
|
|
#define AP130X_DMA_CTRL_DST_SRAM (1 << 8)
|
|
#define AP130X_DMA_CTRL_DST_SPI (2 << 8)
|
|
#define AP130X_DMA_CTRL_DST_SIP (3 << 8)
|
|
#define AP130X_DMA_CTRL_SRC_REG (0 << 4)
|
|
#define AP130X_DMA_CTRL_SRC_SRAM (1 << 4)
|
|
#define AP130X_DMA_CTRL_SRC_SPI (2 << 4)
|
|
#define AP130X_DMA_CTRL_SRC_SIP (3 << 4)
|
|
#define AP130X_DMA_CTRL_MODE_32_BIT BIT(3)
|
|
#define AP130X_DMA_CTRL_MODE_MASK (7 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_IDLE (0 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_SET (1 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_COPY (2 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_MAP (3 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_UNPACK (4 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_OTP_READ (5 << 0)
|
|
#define AP130X_DMA_CTRL_MODE_SIP_PROBE (6 << 0)
|
|
|
|
#define AP130X_BRIGHTNESS AP130X_REG_16BIT(0x7000)
|
|
#define AP130X_CONTRAST AP130X_REG_16BIT(0x7002)
|
|
#define AP130X_SATURATION AP130X_REG_16BIT(0x7006)
|
|
#define AP130X_GAMMA AP130X_REG_16BIT(0x700A)
|
|
|
|
/* Misc Registers */
|
|
#define AP130X_REG_ADV_START 0xe000
|
|
#define AP130X_ADVANCED_BASE AP130X_REG_32BIT(0xf038)
|
|
#define AP130X_SIP_CRC AP130X_REG_16BIT(0xf052)
|
|
|
|
/* Advanced System Registers */
|
|
#define AP130X_ADV_IRQ_SYS_INTE AP130X_REG_32BIT(0x00230000)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_TEST_COUNT BIT(25)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_HINF_1 BIT(24)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_HINF_0 BIT(23)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SINF_B_MIPI_L (7U << 20)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SINF_B_MIPI BIT(19)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SINF_A_MIPI_L (15U << 14)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SINF_A_MIPI BIT(13)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SINF BIT(12)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_IPIPE_S BIT(11)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_IPIPE_B BIT(10)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_IPIPE_A BIT(9)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_IP BIT(8)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_TIMER BIT(7)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SIPM (3U << 6)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SIPS_ADR_RANGE BIT(5)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SIPS_DIRECT_WRITE BIT(4)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SIPS_FIFO_WRITE BIT(3)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_SPI BIT(2)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_GPIO_CNT BIT(1)
|
|
#define AP130X_ADV_IRQ_SYS_INTE_GPIO_PIN BIT(0)
|
|
|
|
/* Advanced Slave MIPI Registers */
|
|
#define AP130X_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(p, n) \
|
|
AP130X_REG_32BIT(0x00420008 + (p) * 0x50000 + (n) * 0x20)
|
|
#define AP130X_LANE_ERR_LP_VAL(n) (((n) >> 30) & 3)
|
|
#define AP130X_LANE_ERR_STATE(n) (((n) >> 24) & 0xf)
|
|
#define AP130X_LANE_ERR BIT(18)
|
|
#define AP130X_LANE_ABORT BIT(17)
|
|
#define AP130X_LANE_LP_VAL(n) (((n) >> 6) & 3)
|
|
#define AP130X_LANE_STATE(n) ((n) & 0xf)
|
|
#define AP130X_LANE_STATE_STOP_S 0x0
|
|
#define AP130X_LANE_STATE_HS_REQ_S 0x1
|
|
#define AP130X_LANE_STATE_LP_REQ_S 0x2
|
|
#define AP130X_LANE_STATE_HS_S 0x3
|
|
#define AP130X_LANE_STATE_LP_S 0x4
|
|
#define AP130X_LANE_STATE_ESC_REQ_S 0x5
|
|
#define AP130X_LANE_STATE_TURN_REQ_S 0x6
|
|
#define AP130X_LANE_STATE_ESC_S 0x7
|
|
#define AP130X_LANE_STATE_ESC_0 0x8
|
|
#define AP130X_LANE_STATE_ESC_1 0x9
|
|
#define AP130X_LANE_STATE_TURN_S 0xa
|
|
#define AP130X_LANE_STATE_TURN_MARK 0xb
|
|
#define AP130X_LANE_STATE_ERROR_S 0xc
|
|
|
|
#define AP130X_ADV_CAPTURE_A_FV_CNT AP130X_REG_32BIT(0x00490040)
|
|
|
|
struct ap130x_device;
|
|
|
|
enum {
|
|
AP130X_PAD_SINK_0,
|
|
AP130X_PAD_SINK_1,
|
|
AP130X_PAD_SOURCE,
|
|
AP130X_PAD_MAX,
|
|
};
|
|
|
|
struct ap130x_format_info {
|
|
unsigned int code;
|
|
u16 out_fmt;
|
|
u32 data_type;
|
|
};
|
|
|
|
struct ap130x_format {
|
|
struct v4l2_mbus_framefmt format;
|
|
const struct ap130x_format_info *info;
|
|
};
|
|
|
|
struct ap130x_size {
|
|
unsigned int width;
|
|
unsigned int height;
|
|
};
|
|
|
|
struct ap130x_sensor_supply {
|
|
const char *name;
|
|
unsigned int post_delay_us;
|
|
};
|
|
|
|
static const struct ap130x_sensor_supply ap130x_supplies[] = {
|
|
{ .name = "DVDD", .post_delay_us = 2000, },
|
|
{ .name = "VDDIO_HMISC", .post_delay_us = 2000, },
|
|
{ .name = "VDDIO_SMISC", .post_delay_us = 2000, },
|
|
};
|
|
|
|
#define AP130X_NUM_SUPPLIES ARRAY_SIZE(ap130x_supplies)
|
|
|
|
struct ap130x_sensor_info {
|
|
const char *model;
|
|
const char *name;
|
|
unsigned int i2c_addr;
|
|
struct ap130x_size resolution;
|
|
u32 format;
|
|
const struct ap130x_sensor_supply *supplies;
|
|
};
|
|
|
|
struct ap130x_sensor {
|
|
struct ap130x_device *ap130x;
|
|
unsigned int index;
|
|
|
|
struct device_node *of_node;
|
|
struct device *dev;
|
|
unsigned int num_supplies;
|
|
struct regulator_bulk_data *supplies;
|
|
|
|
struct v4l2_subdev sd;
|
|
struct media_pad pad;
|
|
};
|
|
|
|
static inline struct ap130x_sensor *to_ap130x_sensor(struct v4l2_subdev *sd)
|
|
{
|
|
return container_of(sd, struct ap130x_sensor, sd);
|
|
}
|
|
|
|
struct ap130x_device {
|
|
struct device *dev;
|
|
struct i2c_client *client;
|
|
|
|
struct gpio_desc *reset_gpio;
|
|
struct gpio_desc *standby_gpio;
|
|
struct gpio_desc *isp_en_gpio;
|
|
struct clk *clock;
|
|
struct regmap *regmap16;
|
|
struct regmap *regmap32;
|
|
u32 reg_page;
|
|
|
|
const struct firmware *fw;
|
|
|
|
struct v4l2_fwnode_endpoint bus_cfg;
|
|
|
|
struct mutex lock; /* Protects formats */
|
|
|
|
struct v4l2_subdev sd;
|
|
struct media_pad pads[AP130X_PAD_MAX];
|
|
struct ap130x_format formats[AP130X_PAD_MAX];
|
|
unsigned int width_factor;
|
|
bool streaming;
|
|
|
|
struct v4l2_ctrl_handler ctrls;
|
|
|
|
const struct ap130x_sensor_info *sensor_info;
|
|
struct ap130x_sensor sensors[2];
|
|
|
|
struct regulator_bulk_data supplies[AP130X_NUM_SUPPLIES];
|
|
|
|
struct {
|
|
struct dentry *dir;
|
|
struct mutex lock;
|
|
u32 sipm_addr;
|
|
} debugfs;
|
|
};
|
|
|
|
static inline struct ap130x_device *to_ap130x(struct v4l2_subdev *sd)
|
|
{
|
|
return container_of(sd, struct ap130x_device, sd);
|
|
}
|
|
|
|
struct ap130x_firmware_header {
|
|
u32 crc;
|
|
u32 checksum;
|
|
u32 pll_init_size;
|
|
u32 total_size;
|
|
} __packed;
|
|
|
|
static const struct ap130x_format_info supported_video_formats[] = {
|
|
{
|
|
.code = MEDIA_BUS_FMT_UYVY8_1X16,
|
|
.out_fmt = AP130X_PREVIEW_OUT_FMT_FT_YUV_JFIF
|
|
| AP130X_PREVIEW_OUT_FMT_FST_YUV_422,
|
|
.data_type = MIPI_CSI2_DT_YUV422_8B,
|
|
}, {
|
|
.code = MEDIA_BUS_FMT_UYYVYY8_0_5X24,
|
|
.out_fmt = AP130X_PREVIEW_OUT_FMT_FT_YUV_JFIF
|
|
| AP130X_PREVIEW_OUT_FMT_FST_YUV_420,
|
|
.data_type = MIPI_CSI2_DT_YUV420_8B,
|
|
},
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Sensor Info
|
|
*/
|
|
|
|
static const struct ap130x_sensor_info ap130x_sensor_info[] = {
|
|
{
|
|
.model = "onnn,ar0144",
|
|
.name = "ar0144",
|
|
.i2c_addr = 0x10,
|
|
.resolution = { 1280, 800 },
|
|
.format = MEDIA_BUS_FMT_SGRBG12_1X12,
|
|
.supplies = (const struct ap130x_sensor_supply[]) {
|
|
{ "vaa", 100 },
|
|
{ "vddio", 100 },
|
|
{ "vdd", 0 },
|
|
{ NULL, 0 },
|
|
},
|
|
}, {
|
|
.model = "onnn,ar0330",
|
|
.name = "ar0330",
|
|
.i2c_addr = 0x10,
|
|
.resolution = { 2304, 1536 },
|
|
.format = MEDIA_BUS_FMT_SGRBG12_1X12,
|
|
.supplies = (const struct ap130x_sensor_supply[]) {
|
|
{ "vddpll", 0 },
|
|
{ "vaa", 0 },
|
|
{ "vdd", 0 },
|
|
{ "vddio", 0 },
|
|
{ NULL, 0 },
|
|
},
|
|
}, {
|
|
.model = "onnn,ar1335",
|
|
.name = "ar1335",
|
|
.i2c_addr = 0x36,
|
|
.resolution = { 4208, 3120 },
|
|
.format = MEDIA_BUS_FMT_SGRBG10_1X10,
|
|
.supplies = (const struct ap130x_sensor_supply[]) {
|
|
{ "vaa", 0 },
|
|
{ "vddio", 0 },
|
|
{ "vdd", 0 },
|
|
{ NULL, 0 },
|
|
},
|
|
},
|
|
};
|
|
|
|
static const struct ap130x_sensor_info ap130x_sensor_info_tpg = {
|
|
.model = "",
|
|
.name = "tpg",
|
|
.resolution = { 1920, 1080 },
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Register Configuration
|
|
*/
|
|
|
|
static const struct regmap_config ap130x_reg16_config = {
|
|
.name = "val_16bits",
|
|
.reg_bits = 16,
|
|
.val_bits = 16,
|
|
.reg_stride = 2,
|
|
.reg_format_endian = REGMAP_ENDIAN_BIG,
|
|
.val_format_endian = REGMAP_ENDIAN_BIG,
|
|
.cache_type = REGCACHE_NONE,
|
|
};
|
|
|
|
static const struct regmap_config ap130x_reg32_config = {
|
|
.name = "val_32bits",
|
|
.reg_bits = 16,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.reg_format_endian = REGMAP_ENDIAN_BIG,
|
|
.val_format_endian = REGMAP_ENDIAN_BIG,
|
|
.cache_type = REGCACHE_NONE,
|
|
};
|
|
|
|
static int __ap130x_write(struct ap130x_device *ap130x, u32 reg, u32 val)
|
|
{
|
|
unsigned int size = AP130X_REG_SIZE(reg);
|
|
u16 addr = AP130X_REG_ADDR(reg);
|
|
int ret;
|
|
|
|
switch (size) {
|
|
case 2:
|
|
ret = regmap_write(ap130x->regmap16, addr, val);
|
|
break;
|
|
case 4:
|
|
ret = regmap_write(ap130x->regmap32, addr, val);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(ap130x->dev, "%s: register 0x%04x %s failed: %d\n",
|
|
__func__, addr, "write", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_write(struct ap130x_device *ap130x, u32 reg, u32 val,
|
|
int *err)
|
|
{
|
|
u32 page = AP130X_REG_PAGE(reg);
|
|
int ret;
|
|
|
|
if (err && *err)
|
|
return *err;
|
|
|
|
if (page) {
|
|
if (ap130x->reg_page != page) {
|
|
ret = __ap130x_write(ap130x, AP130X_ADVANCED_BASE,
|
|
page);
|
|
if (ret < 0)
|
|
goto done;
|
|
|
|
ap130x->reg_page = page;
|
|
}
|
|
|
|
reg &= ~AP130X_REG_PAGE_MASK;
|
|
reg += AP130X_REG_ADV_START;
|
|
}
|
|
|
|
ret = __ap130x_write(ap130x, reg, val);
|
|
|
|
done:
|
|
if (err && ret)
|
|
*err = ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __ap130x_read(struct ap130x_device *ap130x, u32 reg, u32 *val)
|
|
{
|
|
unsigned int size = AP130X_REG_SIZE(reg);
|
|
u16 addr = AP130X_REG_ADDR(reg);
|
|
int ret;
|
|
|
|
switch (size) {
|
|
case 2:
|
|
ret = regmap_read(ap130x->regmap16, addr, val);
|
|
break;
|
|
case 4:
|
|
ret = regmap_read(ap130x->regmap32, addr, val);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(ap130x->dev, "%s: register 0x%04x %s failed: %d\n",
|
|
__func__, addr, "read", ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(ap130x->dev, "%s: R0x%04x = 0x%0*x\n", __func__,
|
|
addr, size * 2, *val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_read(struct ap130x_device *ap130x, u32 reg, u32 *val)
|
|
{
|
|
u32 page = AP130X_REG_PAGE(reg);
|
|
int ret;
|
|
|
|
if (page) {
|
|
if (ap130x->reg_page != page) {
|
|
ret = __ap130x_write(ap130x, AP130X_ADVANCED_BASE,
|
|
page);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ap130x->reg_page = page;
|
|
}
|
|
|
|
reg &= ~AP130X_REG_PAGE_MASK;
|
|
reg += AP130X_REG_ADV_START;
|
|
}
|
|
|
|
return __ap130x_read(ap130x, reg, val);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Sensor Registers Access
|
|
*
|
|
* Read and write sensor registers through the AP130X DMA interface.
|
|
*/
|
|
|
|
static int ap130x_dma_wait_idle(struct ap130x_device *ap130x)
|
|
{
|
|
unsigned int i;
|
|
u32 ctrl;
|
|
int ret;
|
|
|
|
for (i = 50; i > 0; i--) {
|
|
ret = ap130x_read(ap130x, AP130X_DMA_CTRL, &ctrl);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((ctrl & AP130X_DMA_CTRL_MODE_MASK) ==
|
|
AP130X_DMA_CTRL_MODE_IDLE)
|
|
break;
|
|
|
|
usleep_range(1000, 1500);
|
|
}
|
|
|
|
if (!i) {
|
|
dev_err(ap130x->dev, "DMA timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sipm_read(struct ap130x_device *ap130x, unsigned int port,
|
|
u32 reg, u32 *val)
|
|
{
|
|
unsigned int size = AP130X_REG_SIZE(reg);
|
|
u32 src;
|
|
int ret;
|
|
|
|
if (size > 2)
|
|
return -EINVAL;
|
|
|
|
ret = ap130x_dma_wait_idle(ap130x);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ap130x_write(ap130x, AP130X_DMA_SIZE, size, &ret);
|
|
src = AP130X_DMA_SIP_SIPM(port)
|
|
| (size == 2 ? AP130X_DMA_SIP_DATA_16_BIT : 0)
|
|
| AP130X_DMA_SIP_ADDR_16_BIT
|
|
| AP130X_DMA_SIP_ID(ap130x->sensor_info->i2c_addr)
|
|
| AP130X_DMA_SIP_REG(AP130X_REG_ADDR(reg));
|
|
ap130x_write(ap130x, AP130X_DMA_SRC, src, &ret);
|
|
|
|
/*
|
|
* Use the AP130X_DMA_DST register as both the destination address, and
|
|
* the scratch pad to store the read value.
|
|
*/
|
|
ap130x_write(ap130x, AP130X_DMA_DST, AP130X_REG_ADDR(AP130X_DMA_DST),
|
|
&ret);
|
|
|
|
ap130x_write(ap130x, AP130X_DMA_CTRL,
|
|
AP130X_DMA_CTRL_SCH_NORMAL |
|
|
AP130X_DMA_CTRL_DST_REG |
|
|
AP130X_DMA_CTRL_SRC_SIP |
|
|
AP130X_DMA_CTRL_MODE_COPY, &ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_dma_wait_idle(ap130x);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_DMA_DST, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* The value is stored in big-endian at the DMA_DST address. The regmap
|
|
* uses big-endian, so 8-bit values are stored in bits 31:24 and 16-bit
|
|
* values in bits 23:16.
|
|
*/
|
|
*val >>= 32 - size * 8;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sipm_write(struct ap130x_device *ap130x, unsigned int port,
|
|
u32 reg, u32 val)
|
|
{
|
|
unsigned int size = AP130X_REG_SIZE(reg);
|
|
u32 dst;
|
|
int ret;
|
|
|
|
if (size > 2)
|
|
return -EINVAL;
|
|
|
|
ret = ap130x_dma_wait_idle(ap130x);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ap130x_write(ap130x, AP130X_DMA_SIZE, size, &ret);
|
|
|
|
/*
|
|
* Use the AP130X_DMA_SRC register as both the source address, and the
|
|
* scratch pad to store the write value.
|
|
*
|
|
* As the AP130X uses big endian, to store the value at address DMA_SRC
|
|
* it must be written in the high order bits of the registers. However,
|
|
* 8-bit values seem to be incorrectly handled by the AP130X, which
|
|
* expects them to be stored at DMA_SRC + 1 instead of DMA_SRC. The
|
|
* value is thus unconditionally shifted by 16 bits, unlike for DMA
|
|
* reads.
|
|
*/
|
|
ap130x_write(ap130x, AP130X_DMA_SRC,
|
|
(val << 16) | AP130X_REG_ADDR(AP130X_DMA_SRC), &ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dst = AP130X_DMA_SIP_SIPM(port)
|
|
| (size == 2 ? AP130X_DMA_SIP_DATA_16_BIT : 0)
|
|
| AP130X_DMA_SIP_ADDR_16_BIT
|
|
| AP130X_DMA_SIP_ID(ap130x->sensor_info->i2c_addr)
|
|
| AP130X_DMA_SIP_REG(AP130X_REG_ADDR(reg));
|
|
ap130x_write(ap130x, AP130X_DMA_DST, dst, &ret);
|
|
|
|
ap130x_write(ap130x, AP130X_DMA_CTRL,
|
|
AP130X_DMA_CTRL_SCH_NORMAL |
|
|
AP130X_DMA_CTRL_DST_SIP |
|
|
AP130X_DMA_CTRL_SRC_REG |
|
|
AP130X_DMA_CTRL_MODE_COPY, &ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_dma_wait_idle(ap130x);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Debugfs
|
|
*/
|
|
|
|
static int ap130x_sipm_addr_get(void *arg, u64 *val)
|
|
{
|
|
struct ap130x_device *ap130x = arg;
|
|
|
|
mutex_lock(&ap130x->debugfs.lock);
|
|
*val = ap130x->debugfs.sipm_addr;
|
|
mutex_unlock(&ap130x->debugfs.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sipm_addr_set(void *arg, u64 val)
|
|
{
|
|
struct ap130x_device *ap130x = arg;
|
|
|
|
if (val & ~0x8700ffff)
|
|
return -EINVAL;
|
|
|
|
switch ((val >> 24) & 7) {
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ap130x->debugfs.lock);
|
|
ap130x->debugfs.sipm_addr = val;
|
|
mutex_unlock(&ap130x->debugfs.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sipm_data_get(void *arg, u64 *val)
|
|
{
|
|
struct ap130x_device *ap130x = arg;
|
|
u32 value;
|
|
u32 addr;
|
|
int ret;
|
|
|
|
mutex_lock(&ap130x->debugfs.lock);
|
|
|
|
addr = ap130x->debugfs.sipm_addr;
|
|
if (!addr) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = ap130x_sipm_read(ap130x, addr >> 30, addr & ~BIT(31),
|
|
&value);
|
|
if (!ret)
|
|
*val = value;
|
|
|
|
unlock:
|
|
mutex_unlock(&ap130x->debugfs.lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ap130x_sipm_data_set(void *arg, u64 val)
|
|
{
|
|
struct ap130x_device *ap130x = arg;
|
|
u32 addr;
|
|
int ret;
|
|
|
|
mutex_lock(&ap130x->debugfs.lock);
|
|
|
|
addr = ap130x->debugfs.sipm_addr;
|
|
if (!addr) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = ap130x_sipm_write(ap130x, addr >> 30, addr & ~BIT(31),
|
|
val);
|
|
|
|
unlock:
|
|
mutex_unlock(&ap130x->debugfs.lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The sipm_addr and sipm_data attributes expose access to the sensor I2C bus.
|
|
*
|
|
* To read or write a register, sipm_addr has to first be written with the
|
|
* register address. The address is a 32-bit integer formatted as follows.
|
|
*
|
|
* I000 0SSS 0000 0000 RRRR RRRR RRRR RRRR
|
|
*
|
|
* I: SIPM index (0 or 1)
|
|
* S: Size (1: 8-bit, 2: 16-bit)
|
|
* R: Register address (16-bit)
|
|
*
|
|
* The sipm_data attribute can then be read to read the register value, or
|
|
* written to write it.
|
|
*/
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(ap130x_sipm_addr_fops, ap130x_sipm_addr_get,
|
|
ap130x_sipm_addr_set, "0x%08llx\n");
|
|
DEFINE_DEBUGFS_ATTRIBUTE(ap130x_sipm_data_fops, ap130x_sipm_data_get,
|
|
ap130x_sipm_data_set, "0x%08llx\n");
|
|
|
|
static void ap130x_debugfs_init(struct ap130x_device *ap130x)
|
|
{
|
|
struct dentry *dir;
|
|
char name[16];
|
|
|
|
mutex_init(&ap130x->debugfs.lock);
|
|
|
|
snprintf(name, sizeof(name), "ap130x.%s", dev_name(ap130x->dev));
|
|
|
|
dir = debugfs_create_dir(name, NULL);
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
ap130x->debugfs.dir = dir;
|
|
|
|
debugfs_create_file_unsafe("sipm_addr", 0600, ap130x->debugfs.dir,
|
|
ap130x, &ap130x_sipm_addr_fops);
|
|
debugfs_create_file_unsafe("sipm_data", 0600, ap130x->debugfs.dir,
|
|
ap130x, &ap130x_sipm_data_fops);
|
|
}
|
|
|
|
static void ap130x_debugfs_cleanup(struct ap130x_device *ap130x)
|
|
{
|
|
debugfs_remove_recursive(ap130x->debugfs.dir);
|
|
mutex_destroy(&ap130x->debugfs.lock);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Power Handling
|
|
*/
|
|
|
|
static int ap130x_power_on_sensors(struct ap130x_device *ap130x)
|
|
{
|
|
struct ap130x_sensor *sensor;
|
|
unsigned int i, j;
|
|
int ret;
|
|
|
|
if (!ap130x->sensor_info->supplies)
|
|
return 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
sensor = &ap130x->sensors[i];
|
|
ret = 0;
|
|
|
|
for (j = 0; j < sensor->num_supplies; ++j) {
|
|
unsigned int delay;
|
|
|
|
/*
|
|
* We can't use regulator_bulk_enable() as it would
|
|
* enable all supplies in parallel, breaking the sensor
|
|
* power sequencing constraints.
|
|
*/
|
|
ret = regulator_enable(sensor->supplies[j].consumer);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev,
|
|
"Failed to enable supply %u for sensor %u\n",
|
|
j, i);
|
|
goto error;
|
|
}
|
|
|
|
delay = ap130x->sensor_info->supplies[j].post_delay_us;
|
|
usleep_range(delay, delay + 100);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
for (; j > 0; --j)
|
|
regulator_disable(sensor->supplies[j - 1].consumer);
|
|
|
|
for (; i > 0; --i) {
|
|
sensor = &ap130x->sensors[i - 1];
|
|
regulator_bulk_disable(sensor->num_supplies, sensor->supplies);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_power_off_sensors(struct ap130x_device *ap130x)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (!ap130x->sensor_info->supplies)
|
|
return;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
struct ap130x_sensor *sensor = &ap130x->sensors[i];
|
|
|
|
regulator_bulk_disable(sensor->num_supplies, sensor->supplies);
|
|
}
|
|
}
|
|
|
|
static int ap130x_power_on(struct ap130x_device *ap130x)
|
|
{
|
|
int ret;
|
|
int i;
|
|
|
|
/* 0. RESET was asserted when getting the GPIO. */
|
|
|
|
/* 1. Assert STANDBY. */
|
|
if (ap130x->standby_gpio) {
|
|
gpiod_set_value_cansleep(ap130x->standby_gpio, 1);
|
|
usleep_range(200, 1000);
|
|
}
|
|
|
|
/* 2. Power up the regulators. To be implemented. */
|
|
for (i = 0; i < AP130X_NUM_SUPPLIES; ++i) {
|
|
unsigned int delay;
|
|
|
|
ret = regulator_enable(ap130x->supplies[i].consumer);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "enabel regulator fail\n");
|
|
return ret;
|
|
}
|
|
|
|
delay = ap130x_supplies[i].post_delay_us;
|
|
usleep_range(delay, delay + 100);
|
|
}
|
|
|
|
/* 3. De-assert STANDBY. */
|
|
if (ap130x->standby_gpio) {
|
|
gpiod_set_value_cansleep(ap130x->standby_gpio, 0);
|
|
usleep_range(200, 1000);
|
|
}
|
|
|
|
/* 4. Turn the clock on. */
|
|
ret = clk_prepare_enable(ap130x->clock);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "Failed to enable clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* 5. De-assert RESET. */
|
|
gpiod_set_value_cansleep(ap130x->reset_gpio, 0);
|
|
|
|
/*
|
|
* 6. Wait for the AP130X to initialize. The datasheet doesn't specify
|
|
* how long this takes.
|
|
*/
|
|
usleep_range(10000, 11000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ap130x_power_off(struct ap130x_device *ap130x)
|
|
{
|
|
/* 1. Assert RESET. */
|
|
gpiod_set_value_cansleep(ap130x->reset_gpio, 1);
|
|
|
|
/* 2. Turn the clock off. */
|
|
clk_disable_unprepare(ap130x->clock);
|
|
|
|
/* 3. Assert STANDBY. */
|
|
if (ap130x->standby_gpio) {
|
|
gpiod_set_value_cansleep(ap130x->standby_gpio, 1);
|
|
usleep_range(200, 1000);
|
|
}
|
|
|
|
/* 4. Power down the regulators. To be implemented. */
|
|
regulator_bulk_disable(AP130X_NUM_SUPPLIES, ap130x->supplies);
|
|
|
|
/* 5. De-assert STANDBY. */
|
|
if (ap130x->standby_gpio) {
|
|
usleep_range(200, 1000);
|
|
gpiod_set_value_cansleep(ap130x->standby_gpio, 0);
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Hardware Configuration
|
|
*/
|
|
|
|
static int ap130x_dump_console(struct ap130x_device *ap130x)
|
|
{
|
|
u8 *buffer;
|
|
u8 *endp;
|
|
u8 *p;
|
|
int ret;
|
|
|
|
buffer = kmalloc(AP130X_CON_BUF_SIZE + 1, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
ret = regmap_raw_read(ap130x->regmap16, AP130X_CON_BUF(0), buffer,
|
|
AP130X_CON_BUF_SIZE);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "Failed to read console buffer: %d\n",
|
|
ret);
|
|
goto done;
|
|
}
|
|
|
|
print_hex_dump(KERN_INFO, "console ", DUMP_PREFIX_OFFSET, 16, 1, buffer,
|
|
AP130X_CON_BUF_SIZE, true);
|
|
|
|
buffer[AP130X_CON_BUF_SIZE] = '\0';
|
|
|
|
for (p = buffer; p < buffer + AP130X_CON_BUF_SIZE && *p; p = endp + 1) {
|
|
endp = strchrnul(p, '\n');
|
|
*endp = '\0';
|
|
|
|
pr_info("console %s\n", p);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
done:
|
|
kfree(buffer);
|
|
return ret;
|
|
}
|
|
|
|
static int ap130x_configure(struct ap130x_device *ap130x)
|
|
{
|
|
const struct ap130x_format *format = &ap130x->formats[AP130X_PAD_SOURCE];
|
|
unsigned int data_lanes = ap130x->bus_cfg.bus.mipi_csi2.num_data_lanes;
|
|
int ret = 0;
|
|
|
|
ap130x_write(ap130x, AP130X_PREVIEW_HINF_CTRL,
|
|
AP130X_PREVIEW_HINF_CTRL_SPOOF |
|
|
AP130X_PREVIEW_HINF_CTRL_MIPI_LANES(data_lanes), &ret);
|
|
|
|
ap130x_write(ap130x, AP130X_PREVIEW_WIDTH,
|
|
format->format.width / ap130x->width_factor, &ret);
|
|
ap130x_write(ap130x, AP130X_PREVIEW_HEIGHT,
|
|
format->format.height, &ret);
|
|
ap130x_write(ap130x, AP130X_PREVIEW_OUT_FMT,
|
|
format->info->out_fmt, &ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
__v4l2_ctrl_handler_setup(&ap130x->ctrls);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_stall(struct ap130x_device *ap130x, bool stall)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (stall) {
|
|
ap130x_write(ap130x, AP130X_SYS_START,
|
|
AP130X_SYS_START_PLL_LOCK |
|
|
AP130X_SYS_START_STALL_MODE_DISABLED, &ret);
|
|
ap130x_write(ap130x, AP130X_SYS_START,
|
|
AP130X_SYS_START_PLL_LOCK |
|
|
AP130X_SYS_START_STALL_EN |
|
|
AP130X_SYS_START_STALL_MODE_DISABLED, &ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
msleep(200);
|
|
return ret;
|
|
} else {
|
|
return ap130x_write(ap130x, AP130X_SYS_START,
|
|
AP130X_SYS_START_PLL_LOCK |
|
|
AP130X_SYS_START_STALL_STATUS |
|
|
AP130X_SYS_START_STALL_EN |
|
|
AP130X_SYS_START_STALL_MODE_DISABLED, NULL);
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* V4L2 Controls
|
|
*/
|
|
|
|
static u16 ap130x_wb_values[] = {
|
|
AP130X_AWB_CTRL_MODE_OFF, /* V4L2_WHITE_BALANCE_MANUAL */
|
|
AP130X_AWB_CTRL_MODE_AUTO, /* V4L2_WHITE_BALANCE_AUTO */
|
|
AP130X_AWB_CTRL_MODE_A, /* V4L2_WHITE_BALANCE_INCANDESCENT */
|
|
AP130X_AWB_CTRL_MODE_D50, /* V4L2_WHITE_BALANCE_FLUORESCENT */
|
|
AP130X_AWB_CTRL_MODE_D65, /* V4L2_WHITE_BALANCE_FLUORESCENT_H */
|
|
AP130X_AWB_CTRL_MODE_HORIZON, /* V4L2_WHITE_BALANCE_HORIZON */
|
|
AP130X_AWB_CTRL_MODE_D65, /* V4L2_WHITE_BALANCE_DAYLIGHT */
|
|
AP130X_AWB_CTRL_MODE_AUTO, /* V4L2_WHITE_BALANCE_FLASH */
|
|
AP130X_AWB_CTRL_MODE_D75, /* V4L2_WHITE_BALANCE_CLOUDY */
|
|
AP130X_AWB_CTRL_MODE_D75, /* V4L2_WHITE_BALANCE_SHADE */
|
|
};
|
|
|
|
static inline struct ap130x_device *ctrl_to_sd(struct v4l2_ctrl *ctrl)
|
|
{
|
|
return container_of(ctrl->handler, struct ap130x_device, ctrls);
|
|
}
|
|
|
|
static int ap130x_set_wb_mode(struct ap130x_device *ap130x, s32 mode)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_AWB_CTRL, &val);
|
|
if (ret)
|
|
return ret;
|
|
val &= ~AP130X_AWB_CTRL_MODE_MASK;
|
|
val |= ap130x_wb_values[mode];
|
|
|
|
if (mode == V4L2_WHITE_BALANCE_FLASH)
|
|
val |= AP130X_AWB_CTRL_FLASH;
|
|
else
|
|
val &= ~AP130X_AWB_CTRL_FLASH;
|
|
|
|
return ap130x_write(ap130x, AP130X_AWB_CTRL, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_exposure(struct ap130x_device *ap130x, s32 mode)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_AE_CTRL, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val &= ~AP130X_AE_CTRL_MODE_MASK;
|
|
val |= mode;
|
|
|
|
return ap130x_write(ap130x, AP130X_AE_CTRL, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_exp_met(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_AE_MET, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_gain(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_AE_MANUAL_GAIN, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_contrast(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_CONTRAST, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_brightness(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_BRIGHTNESS, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_saturation(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_SATURATION, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_gamma(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_GAMMA, val, NULL);
|
|
}
|
|
|
|
static int ap130x_set_zoom(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_DZ_TGT_FCT, val, NULL);
|
|
}
|
|
|
|
static u16 ap130x_sfx_values[] = {
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_NONE */
|
|
AP130X_SFX_MODE_SFX_BW, /* V4L2_COLORFX_BW */
|
|
AP130X_SFX_MODE_SFX_SEPIA1, /* V4L2_COLORFX_SEPIA */
|
|
AP130X_SFX_MODE_SFX_NEGATIVE, /* V4L2_COLORFX_NEGATIVE */
|
|
AP130X_SFX_MODE_SFX_EMBOSS, /* V4L2_COLORFX_EMBOSS */
|
|
AP130X_SFX_MODE_SFX_SKETCH, /* V4L2_COLORFX_SKETCH */
|
|
AP130X_SFX_MODE_SFX_BLUISH, /* V4L2_COLORFX_SKY_BLUE */
|
|
AP130X_SFX_MODE_SFX_GREENISH, /* V4L2_COLORFX_GRASS_GREEN */
|
|
AP130X_SFX_MODE_SFX_REDISH, /* V4L2_COLORFX_SKIN_WHITEN */
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_VIVID */
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_AQUA */
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_ART_FREEZE */
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_SILHOUETTE */
|
|
AP130X_SFX_MODE_SFX_SOLARIZE, /* V4L2_COLORFX_SOLARIZATION */
|
|
AP130X_SFX_MODE_SFX_ANTIQUE, /* V4L2_COLORFX_ANTIQUE */
|
|
AP130X_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_SET_CBCR */
|
|
};
|
|
|
|
static int ap130x_set_special_effect(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_SFX_MODE, ap130x_sfx_values[val],
|
|
NULL);
|
|
}
|
|
|
|
static u16 ap130x_scene_mode_values[] = {
|
|
AP130X_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_NONE */
|
|
AP130X_SCENE_CTRL_MODE_BACKLIGHT, /* V4L2_SCENE_MODE_BACKLIGHT */
|
|
AP130X_SCENE_CTRL_MODE_BEACH, /* V4L2_SCENE_MODE_BEACH_SNOW */
|
|
AP130X_SCENE_CTRL_MODE_TWILIGHT, /* V4L2_SCENE_MODE_CANDLE_LIGHT */
|
|
AP130X_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_DAWN_DUSK */
|
|
AP130X_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_FALL_COLORS */
|
|
AP130X_SCENE_CTRL_MODE_FIREWORKS, /* V4L2_SCENE_MODE_FIREWORKS */
|
|
AP130X_SCENE_CTRL_MODE_LANDSCAPE, /* V4L2_SCENE_MODE_LANDSCAPE */
|
|
AP130X_SCENE_CTRL_MODE_NIGHT, /* V4L2_SCENE_MODE_NIGHT */
|
|
AP130X_SCENE_CTRL_MODE_PARTY, /* V4L2_SCENE_MODE_PARTY_INDOOR */
|
|
AP130X_SCENE_CTRL_MODE_PORTRAIT, /* V4L2_SCENE_MODE_PORTRAIT */
|
|
AP130X_SCENE_CTRL_MODE_SPORT, /* V4L2_SCENE_MODE_SPORTS */
|
|
AP130X_SCENE_CTRL_MODE_SUNSET, /* V4L2_SCENE_MODE_SUNSET */
|
|
AP130X_SCENE_CTRL_MODE_DOCUMENT, /* V4L2_SCENE_MODE_TEXT */
|
|
};
|
|
|
|
static int ap130x_set_scene_mode(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_SCENE_CTRL,
|
|
ap130x_scene_mode_values[val], NULL);
|
|
}
|
|
|
|
static const u16 ap130x_flicker_values[] = {
|
|
AP130X_FLICK_CTRL_MODE_DISABLED,
|
|
AP130X_FLICK_CTRL_FREQ(50) | AP130X_FLICK_CTRL_MODE_MANUAL,
|
|
AP130X_FLICK_CTRL_FREQ(60) | AP130X_FLICK_CTRL_MODE_MANUAL,
|
|
AP130X_FLICK_CTRL_MODE_AUTO,
|
|
};
|
|
|
|
static int ap130x_set_flicker_freq(struct ap130x_device *ap130x, s32 val)
|
|
{
|
|
return ap130x_write(ap130x, AP130X_FLICK_CTRL,
|
|
ap130x_flicker_values[val], NULL);
|
|
}
|
|
|
|
static int ap130x_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct ap130x_device *ap130x = ctrl_to_sd(ctrl);
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
|
|
return ap130x_set_wb_mode(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_EXPOSURE:
|
|
return ap130x_set_exposure(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_EXPOSURE_METERING:
|
|
return ap130x_set_exp_met(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_GAIN:
|
|
return ap130x_set_gain(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_GAMMA:
|
|
return ap130x_set_gamma(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_CONTRAST:
|
|
return ap130x_set_contrast(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_BRIGHTNESS:
|
|
return ap130x_set_brightness(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_SATURATION:
|
|
return ap130x_set_saturation(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_ZOOM_ABSOLUTE:
|
|
return ap130x_set_zoom(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_COLORFX:
|
|
return ap130x_set_special_effect(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_SCENE_MODE:
|
|
return ap130x_set_scene_mode(ap130x, ctrl->val);
|
|
|
|
case V4L2_CID_POWER_LINE_FREQUENCY:
|
|
return ap130x_set_flicker_freq(ap130x, ctrl->val);
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const s64 ap130x_link_freqs[] = {
|
|
445000000,
|
|
};
|
|
|
|
static int ap130x_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct ap130x_device *ap130x = ctrl_to_sd(ctrl);
|
|
int i;
|
|
u32 val;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_LINK_FREQ:
|
|
ap130x_read(ap130x, AP130X_REG_16BIT(0x0068), &val);
|
|
for (i = 0; i < ARRAY_SIZE(ap130x_link_freqs); i++) {
|
|
if (ap130x_link_freqs[i] == (val / 2) * 1000000)
|
|
break;
|
|
}
|
|
WARN_ON(i == ARRAY_SIZE(ap130x_link_freqs));
|
|
__v4l2_ctrl_s_ctrl(ctrl, i);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops ap130x_ctrl_ops = {
|
|
.s_ctrl = ap130x_s_ctrl,
|
|
.g_volatile_ctrl = ap130x_g_volatile_ctrl,
|
|
};
|
|
|
|
static const struct v4l2_ctrl_config ap130x_ctrls[] = {
|
|
{
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
|
|
.min = 0,
|
|
.max = 9,
|
|
.def = 1,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_GAMMA,
|
|
.name = "Gamma",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x0100,
|
|
.max = 0xFFFF,
|
|
.step = 0x100,
|
|
.def = 0x1000,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_CONTRAST,
|
|
.name = "Contrast",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x100,
|
|
.max = 0xFFFF,
|
|
.step = 0x100,
|
|
.def = 0x100,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_BRIGHTNESS,
|
|
.name = "Brightness",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x100,
|
|
.max = 0xFFFF,
|
|
.step = 0x100,
|
|
.def = 0x100,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_SATURATION,
|
|
.name = "Saturation",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x0100,
|
|
.max = 0xFFFF,
|
|
.step = 0x100,
|
|
.def = 0x1000,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_EXPOSURE,
|
|
.name = "Exposure",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x0,
|
|
.max = 0xC,
|
|
.step = 1,
|
|
.def = 0xC,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_EXPOSURE_METERING,
|
|
.name = "Exposure Metering",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x0,
|
|
.max = 0x3,
|
|
.step = 1,
|
|
.def = 0x1,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_GAIN,
|
|
.name = "Gain",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.min = 0x0100,
|
|
.max = 0xFFFF,
|
|
.step = 0x100,
|
|
.def = 0x100,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_ZOOM_ABSOLUTE,
|
|
.min = 0x0100,
|
|
.max = 0x1000,
|
|
.step = 1,
|
|
.def = 0x0100,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_COLORFX,
|
|
.min = 0,
|
|
.max = 15,
|
|
.def = 0,
|
|
.menu_skip_mask = BIT(15) | BIT(12) | BIT(11) | BIT(10) | BIT(9),
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_SCENE_MODE,
|
|
.min = 0,
|
|
.max = 13,
|
|
.def = 0,
|
|
.menu_skip_mask = BIT(5) | BIT(4),
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
|
.min = 0,
|
|
.max = 3,
|
|
.def = 3,
|
|
}, {
|
|
.ops = &ap130x_ctrl_ops,
|
|
.id = V4L2_CID_LINK_FREQ,
|
|
.min = 0,
|
|
.max = ARRAY_SIZE(ap130x_link_freqs) - 1,
|
|
.def = 0,
|
|
.qmenu_int = ap130x_link_freqs,
|
|
},
|
|
};
|
|
|
|
static int ap130x_ctrls_init(struct ap130x_device *ap130x)
|
|
{
|
|
struct v4l2_fwnode_device_properties props;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = v4l2_ctrl_handler_init(&ap130x->ctrls, ARRAY_SIZE(ap130x_ctrls));
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x_ctrls); i++)
|
|
v4l2_ctrl_new_custom(&ap130x->ctrls, &ap130x_ctrls[i], NULL);
|
|
|
|
if (ap130x->ctrls.error) {
|
|
ret = ap130x->ctrls.error;
|
|
goto free_ctrls;
|
|
}
|
|
|
|
ret = v4l2_fwnode_device_parse(ap130x->dev, &props);
|
|
if (ret)
|
|
goto free_ctrls;
|
|
|
|
ret = v4l2_ctrl_new_fwnode_properties(&ap130x->ctrls,
|
|
&ap130x_ctrl_ops, &props);
|
|
if (ret)
|
|
goto free_ctrls;
|
|
|
|
/* Use same lock for controls as for everything else. */
|
|
ap130x->ctrls.lock = &ap130x->lock;
|
|
ap130x->sd.ctrl_handler = &ap130x->ctrls;
|
|
|
|
return 0;
|
|
|
|
free_ctrls:
|
|
v4l2_ctrl_handler_free(&ap130x->ctrls);
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_ctrls_cleanup(struct ap130x_device *ap130x)
|
|
{
|
|
v4l2_ctrl_handler_free(&ap130x->ctrls);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* V4L2 Subdev Operations
|
|
*/
|
|
|
|
static struct v4l2_mbus_framefmt *
|
|
ap130x_get_pad_format(struct ap130x_device *ap130x,
|
|
struct v4l2_subdev_state *state,
|
|
unsigned int pad, u32 which)
|
|
{
|
|
switch (which) {
|
|
case V4L2_SUBDEV_FORMAT_TRY:
|
|
return v4l2_subdev_get_try_format(&ap130x->sd, state, pad);
|
|
case V4L2_SUBDEV_FORMAT_ACTIVE:
|
|
return &ap130x->formats[pad].format;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static int ap130x_init_cfg(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state)
|
|
{
|
|
u32 which = state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
const struct ap130x_sensor_info *info = ap130x->sensor_info;
|
|
unsigned int pad;
|
|
|
|
for (pad = 0; pad < ARRAY_SIZE(ap130x->formats); ++pad) {
|
|
struct v4l2_mbus_framefmt *format =
|
|
ap130x_get_pad_format(ap130x, state, pad, which);
|
|
|
|
format->width = info->resolution.width;
|
|
format->height = info->resolution.height;
|
|
|
|
/*
|
|
* The source pad combines images side by side in multi-sensor
|
|
* setup.
|
|
*/
|
|
if (pad == AP130X_PAD_SOURCE) {
|
|
format->width *= ap130x->width_factor;
|
|
format->code = ap130x->formats[pad].info->code;
|
|
} else {
|
|
format->code = info->format;
|
|
}
|
|
|
|
format->field = V4L2_FIELD_NONE;
|
|
format->colorspace = V4L2_COLORSPACE_SRGB;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
|
|
if (code->pad != AP130X_PAD_SOURCE) {
|
|
/*
|
|
* On the sink pads, only the format produced by the sensor is
|
|
* supported.
|
|
*/
|
|
if (code->index)
|
|
return -EINVAL;
|
|
|
|
code->code = ap130x->sensor_info->format;
|
|
} else {
|
|
/* On the source pad, multiple formats are supported. */
|
|
if (code->index >= ARRAY_SIZE(supported_video_formats))
|
|
return -EINVAL;
|
|
|
|
code->code = supported_video_formats[code->index].code;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_enum_frame_size(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_frame_size_enum *fse)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
unsigned int i;
|
|
|
|
if (fse->index)
|
|
return -EINVAL;
|
|
|
|
if (fse->pad != AP130X_PAD_SOURCE) {
|
|
/*
|
|
* On the sink pads, only the size produced by the sensor is
|
|
* supported.
|
|
*/
|
|
if (fse->code != ap130x->sensor_info->format)
|
|
return -EINVAL;
|
|
|
|
fse->min_width = ap130x->sensor_info->resolution.width;
|
|
fse->min_height = ap130x->sensor_info->resolution.height;
|
|
fse->max_width = ap130x->sensor_info->resolution.width;
|
|
fse->max_height = ap130x->sensor_info->resolution.height;
|
|
} else {
|
|
/*
|
|
* On the source pad, the AP130X can freely scale within the
|
|
* scaler's limits.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(supported_video_formats); i++) {
|
|
if (supported_video_formats[i].code == fse->code)
|
|
break;
|
|
}
|
|
|
|
if (i >= ARRAY_SIZE(supported_video_formats))
|
|
return -EINVAL;
|
|
|
|
fse->min_width = AP130X_MIN_WIDTH * ap130x->width_factor;
|
|
fse->min_height = AP130X_MIN_HEIGHT;
|
|
fse->max_width = AP130X_MAX_WIDTH;
|
|
fse->max_height = AP130X_MAX_HEIGHT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
const struct v4l2_mbus_framefmt *format;
|
|
|
|
format = ap130x_get_pad_format(ap130x, state, fmt->pad, fmt->which);
|
|
|
|
mutex_lock(&ap130x->lock);
|
|
fmt->format = *format;
|
|
mutex_unlock(&ap130x->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
const struct ap130x_format_info *info = NULL;
|
|
struct v4l2_mbus_framefmt *format;
|
|
unsigned int i;
|
|
|
|
/* Formats on the sink pads can't be changed. */
|
|
if (fmt->pad != AP130X_PAD_SOURCE)
|
|
return ap130x_get_fmt(sd, state, fmt);
|
|
|
|
format = ap130x_get_pad_format(ap130x, state, fmt->pad, fmt->which);
|
|
|
|
/* Validate the media bus code, default to the first supported value. */
|
|
for (i = 0; i < ARRAY_SIZE(supported_video_formats); i++) {
|
|
if (supported_video_formats[i].code == fmt->format.code) {
|
|
info = &supported_video_formats[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!info)
|
|
info = &supported_video_formats[0];
|
|
|
|
/*
|
|
* Clamp the size. The width must be a multiple of 4 (or 8 in the
|
|
* dual-sensor case) and the height a multiple of 2.
|
|
*/
|
|
fmt->format.width = clamp(ALIGN_DOWN(fmt->format.width,
|
|
4 * ap130x->width_factor),
|
|
AP130X_MIN_WIDTH * ap130x->width_factor,
|
|
AP130X_MAX_WIDTH);
|
|
fmt->format.height = clamp(ALIGN_DOWN(fmt->format.height, 2),
|
|
AP130X_MIN_HEIGHT, AP130X_MAX_HEIGHT);
|
|
|
|
mutex_lock(&ap130x->lock);
|
|
|
|
format->width = fmt->format.width;
|
|
format->height = fmt->format.height;
|
|
format->code = info->code;
|
|
|
|
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
ap130x->formats[fmt->pad].info = info;
|
|
|
|
mutex_unlock(&ap130x->lock);
|
|
|
|
fmt->format = *format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_get_selection(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_selection *sel)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
const struct ap130x_size *resolution = &ap130x->sensor_info->resolution;
|
|
|
|
switch (sel->target) {
|
|
case V4L2_SEL_TGT_NATIVE_SIZE:
|
|
case V4L2_SEL_TGT_CROP_BOUNDS:
|
|
case V4L2_SEL_TGT_CROP_DEFAULT:
|
|
case V4L2_SEL_TGT_CROP:
|
|
sel->r.left = 0;
|
|
sel->r.top = 0;
|
|
sel->r.width = resolution->width * ap130x->width_factor;
|
|
sel->r.height = resolution->height;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
|
|
struct v4l2_mbus_frame_desc *fd)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
const struct ap130x_format_info *info;
|
|
|
|
if (pad != AP130X_PAD_SOURCE || !fd)
|
|
return -EINVAL;
|
|
|
|
info = ap130x->formats[pad].info;
|
|
|
|
memset(fd, 0x0, sizeof(*fd));
|
|
|
|
mutex_lock(&ap130x->lock);
|
|
|
|
fd->entry[0].flags = 0;
|
|
fd->entry[0].pixelcode = info->code;
|
|
fd->entry[0].bus.csi2.vc = 0;
|
|
fd->entry[0].bus.csi2.dt = info->data_type;
|
|
|
|
mutex_unlock(&ap130x->lock);
|
|
|
|
fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
|
|
fd->num_entries = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_s_stream(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&ap130x->lock);
|
|
|
|
if (enable == ap130x->streaming)
|
|
goto done;
|
|
|
|
if (enable) {
|
|
ret = ap130x_configure(ap130x);
|
|
if (ret < 0)
|
|
goto done;
|
|
|
|
ret = ap130x_stall(ap130x, false);
|
|
if (!ret)
|
|
ap130x->streaming = true;
|
|
} else {
|
|
ret = ap130x_stall(ap130x, true);
|
|
if (!ret)
|
|
ap130x->streaming = false;
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&ap130x->lock);
|
|
|
|
if (ret < 0)
|
|
dev_err(ap130x->dev, "Failed to %s stream: %d\n",
|
|
enable ? "start" : "stop", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char * const ap130x_warnings[] = {
|
|
"HINF_BANDWIDTH",
|
|
"FLICKER_DETECTION",
|
|
"FACED_NE",
|
|
"SMILED_NE",
|
|
"HINF_OVERRUN",
|
|
NULL,
|
|
"FRAME_TOO_SMALL",
|
|
"MISSING_PHASES",
|
|
"SPOOF_UNDERRUN",
|
|
"JPEG_NOLAST",
|
|
"NO_IN_FREQ_SPEC",
|
|
"SINF0",
|
|
"SINF1",
|
|
"CAPTURE0",
|
|
"CAPTURE1",
|
|
"ISR_UNHANDLED",
|
|
"INTERLEAVE_SPOOF",
|
|
"INTERLEAVE_BUF",
|
|
"COORD_OUT_OF_RANGE",
|
|
"ICP_CLOCKING",
|
|
"SENSOR_CLOCKING",
|
|
"SENSOR_NO_IHDR",
|
|
"DIVIDE_BY_ZERO",
|
|
"INT0_UNDERRUN",
|
|
"INT1_UNDERRUN",
|
|
"SCRATCHPAD_TOO_BIG",
|
|
"OTP_RECORD_READ",
|
|
"NO_LSC_IN_OTP",
|
|
"GPIO_INT_LOST",
|
|
"NO_PDAF_DATA",
|
|
"FAR_PDAF_ACCESS_SKIP",
|
|
"PDAF_ERROR",
|
|
"ATM_TVI_BOUNDS",
|
|
"SIPM_0_RTY",
|
|
"SIPM_1_TRY",
|
|
"SIPM_0_NO_ACK",
|
|
"SIPM_1_NO_ACK",
|
|
"SMILE_DIS",
|
|
"DVS_DIS",
|
|
"TEST_DIS",
|
|
"SENSOR_LV2LV",
|
|
"SENSOR_FV2FV",
|
|
"FRAME_LOST",
|
|
};
|
|
|
|
static const char * const ap130x_lane_states[] = {
|
|
"stop_s",
|
|
"hs_req_s",
|
|
"lp_req_s",
|
|
"hs_s",
|
|
"lp_s",
|
|
"esc_req_s",
|
|
"turn_req_s",
|
|
"esc_s",
|
|
"esc_0",
|
|
"esc_1",
|
|
"turn_s",
|
|
"turn_mark",
|
|
"error_s",
|
|
};
|
|
|
|
#define NUM_LANES 4
|
|
static void ap130x_log_lane_state(struct ap130x_sensor *sensor,
|
|
unsigned int index)
|
|
{
|
|
static const char * const lp_states[] = {
|
|
"00", "10", "01", "11",
|
|
};
|
|
unsigned int counts[NUM_LANES][ARRAY_SIZE(ap130x_lane_states)];
|
|
unsigned int samples = 0;
|
|
unsigned int lane;
|
|
unsigned int i;
|
|
u32 first[NUM_LANES] = { 0, };
|
|
u32 last[NUM_LANES] = { 0, };
|
|
int ret;
|
|
|
|
memset(counts, 0, sizeof(counts));
|
|
|
|
for (i = 0; i < 1000; ++i) {
|
|
u32 values[NUM_LANES];
|
|
|
|
/*
|
|
* Read the state of all lanes and skip read errors and invalid
|
|
* values.
|
|
*/
|
|
for (lane = 0; lane < NUM_LANES; ++lane) {
|
|
ret = ap130x_read(sensor->ap130x,
|
|
AP130X_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(index, lane),
|
|
&values[lane]);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
if (AP130X_LANE_STATE(values[lane]) >=
|
|
ARRAY_SIZE(ap130x_lane_states)) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
continue;
|
|
|
|
/* Accumulate the samples and save the first and last states. */
|
|
for (lane = 0; lane < NUM_LANES; ++lane)
|
|
counts[lane][AP130X_LANE_STATE(values[lane])]++;
|
|
|
|
if (!samples)
|
|
memcpy(first, values, sizeof(first));
|
|
memcpy(last, values, sizeof(last));
|
|
|
|
samples++;
|
|
}
|
|
|
|
if (!samples)
|
|
return;
|
|
|
|
/*
|
|
* Print the LP state from the first sample, the error state from the
|
|
* last sample, and the states accumulators for each lane.
|
|
*/
|
|
for (lane = 0; lane < NUM_LANES; ++lane) {
|
|
u32 state = last[lane];
|
|
char error_msg[25] = "";
|
|
|
|
if (state & (AP130X_LANE_ERR | AP130X_LANE_ABORT)) {
|
|
unsigned int err = AP130X_LANE_ERR_STATE(state);
|
|
const char *err_state = NULL;
|
|
|
|
err_state = err < ARRAY_SIZE(ap130x_lane_states)
|
|
? ap130x_lane_states[err] : "INVALID";
|
|
|
|
snprintf(error_msg, sizeof(error_msg), "ERR (%s%s) %s LP%s",
|
|
state & AP130X_LANE_ERR ? "E" : "",
|
|
state & AP130X_LANE_ABORT ? "A" : "",
|
|
err_state,
|
|
lp_states[AP130X_LANE_ERR_LP_VAL(state)]);
|
|
}
|
|
|
|
dev_info(sensor->ap130x->dev, "SINF%u L%u state: LP%s %s",
|
|
index, lane, lp_states[AP130X_LANE_LP_VAL(first[lane])],
|
|
error_msg);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x_lane_states); ++i) {
|
|
if (counts[lane][i])
|
|
pr_cont(" %s:%u",
|
|
ap130x_lane_states[i],
|
|
counts[lane][i]);
|
|
}
|
|
pr_cont("\n");
|
|
}
|
|
|
|
/* Reset the error flags. */
|
|
for (lane = 0; lane < NUM_LANES; ++lane)
|
|
ap130x_write(sensor->ap130x,
|
|
AP130X_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(index, lane),
|
|
AP130X_LANE_ERR | AP130X_LANE_ABORT, NULL);
|
|
}
|
|
|
|
static int ap130x_log_status(struct v4l2_subdev *sd)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
u16 frame_count_icp;
|
|
u16 frame_count_brac;
|
|
u16 frame_count_hinf;
|
|
u32 warning[4];
|
|
u32 error[3];
|
|
unsigned int i;
|
|
u32 value;
|
|
int ret;
|
|
|
|
/* Dump the console buffer. */
|
|
ret = ap130x_dump_console(ap130x);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Print errors. */
|
|
ret = ap130x_read(ap130x, AP130X_ERROR, &error[0]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_ERR_FILE, &error[1]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_ERR_LINE, &error[2]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(ap130x->dev, "ERROR: 0x%04x (file 0x%08x:%u)\n",
|
|
error[0], error[1], error[2]);
|
|
|
|
ret = ap130x_read(ap130x, AP130X_SIPM_ERR_0, &error[0]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_SIPM_ERR_1, &error[1]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(ap130x->dev, "SIPM_ERR [0] 0x%04x [1] 0x%04x\n",
|
|
error[0], error[1]);
|
|
|
|
/* Print warnings. */
|
|
for (i = 0; i < ARRAY_SIZE(warning); ++i) {
|
|
ret = ap130x_read(ap130x, AP130X_WARNING(i), &warning[i]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
dev_info(ap130x->dev,
|
|
"WARNING [0] 0x%04x [1] 0x%04x [2] 0x%04x [3] 0x%04x\n",
|
|
warning[0], warning[1], warning[2], warning[3]);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x_warnings); ++i) {
|
|
if ((warning[i / 16] & BIT(i % 16)) &&
|
|
ap130x_warnings[i])
|
|
dev_info(ap130x->dev, "- WARN_%s\n",
|
|
ap130x_warnings[i]);
|
|
}
|
|
|
|
/* Print the frame counter. */
|
|
ret = ap130x_read(ap130x, AP130X_FRAME_CNT, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
frame_count_hinf = value >> 8;
|
|
frame_count_brac = value & 0xff;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_ADV_CAPTURE_A_FV_CNT, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
frame_count_icp = value & 0xffff;
|
|
|
|
dev_info(ap130x->dev, "Frame counters: ICP %u, HINF %u, BRAC %u\n",
|
|
frame_count_icp, frame_count_hinf, frame_count_brac);
|
|
|
|
/* Sample the lane state. */
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
struct ap130x_sensor *sensor = &ap130x->sensors[i];
|
|
|
|
if (!sensor->ap130x)
|
|
continue;
|
|
|
|
ap130x_log_lane_state(sensor, i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_subdev_registered(struct v4l2_subdev *sd)
|
|
{
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
struct ap130x_sensor *sensor = &ap130x->sensors[i];
|
|
|
|
if (!sensor->dev)
|
|
continue;
|
|
|
|
dev_dbg(ap130x->dev, "registering sensor %u\n", i);
|
|
|
|
ret = v4l2_device_register_subdev(sd->v4l2_dev, &sensor->sd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = media_create_pad_link(&sensor->sd.entity, 0,
|
|
&sd->entity, i,
|
|
MEDIA_LNK_FL_IMMUTABLE |
|
|
MEDIA_LNK_FL_ENABLED);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct media_entity_operations ap130x_media_ops = {
|
|
.link_validate = v4l2_subdev_link_validate
|
|
};
|
|
|
|
static const struct v4l2_subdev_pad_ops ap130x_pad_ops = {
|
|
.init_cfg = ap130x_init_cfg,
|
|
.enum_mbus_code = ap130x_enum_mbus_code,
|
|
.enum_frame_size = ap130x_enum_frame_size,
|
|
.get_fmt = ap130x_get_fmt,
|
|
.set_fmt = ap130x_set_fmt,
|
|
.get_selection = ap130x_get_selection,
|
|
.set_selection = ap130x_get_selection,
|
|
.get_frame_desc = ap130x_get_frame_desc,
|
|
};
|
|
|
|
static const struct v4l2_subdev_video_ops ap130x_video_ops = {
|
|
.s_stream = ap130x_s_stream,
|
|
};
|
|
|
|
static const struct v4l2_subdev_core_ops ap130x_core_ops = {
|
|
.log_status = ap130x_log_status,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops ap130x_subdev_ops = {
|
|
.core = &ap130x_core_ops,
|
|
.video = &ap130x_video_ops,
|
|
.pad = &ap130x_pad_ops,
|
|
};
|
|
|
|
static const struct v4l2_subdev_internal_ops ap130x_subdev_internal_ops = {
|
|
.registered = ap130x_subdev_registered,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Sensor
|
|
*/
|
|
|
|
static int ap130x_sensor_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
struct ap130x_sensor *sensor = to_ap130x_sensor(sd);
|
|
const struct ap130x_sensor_info *info = sensor->ap130x->sensor_info;
|
|
|
|
if (code->index)
|
|
return -EINVAL;
|
|
|
|
code->code = info->format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sensor_enum_frame_size(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_frame_size_enum *fse)
|
|
{
|
|
struct ap130x_sensor *sensor = to_ap130x_sensor(sd);
|
|
const struct ap130x_sensor_info *info = sensor->ap130x->sensor_info;
|
|
|
|
if (fse->index)
|
|
return -EINVAL;
|
|
|
|
if (fse->code != info->format)
|
|
return -EINVAL;
|
|
|
|
fse->min_width = info->resolution.width;
|
|
fse->min_height = info->resolution.height;
|
|
fse->max_width = info->resolution.width;
|
|
fse->max_height = info->resolution.height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_sensor_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *state,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct ap130x_sensor *sensor = to_ap130x_sensor(sd);
|
|
const struct ap130x_sensor_info *info = sensor->ap130x->sensor_info;
|
|
|
|
memset(&fmt->format, 0, sizeof(fmt->format));
|
|
|
|
fmt->format.width = info->resolution.width;
|
|
fmt->format.height = info->resolution.height;
|
|
fmt->format.field = V4L2_FIELD_NONE;
|
|
fmt->format.code = info->format;
|
|
fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_pad_ops ap130x_sensor_pad_ops = {
|
|
.enum_mbus_code = ap130x_sensor_enum_mbus_code,
|
|
.enum_frame_size = ap130x_sensor_enum_frame_size,
|
|
.get_fmt = ap130x_sensor_get_fmt,
|
|
.set_fmt = ap130x_sensor_get_fmt,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops ap130x_sensor_subdev_ops = {
|
|
.pad = &ap130x_sensor_pad_ops,
|
|
};
|
|
|
|
static int ap130x_sensor_parse_of(struct ap130x_device *ap130x,
|
|
struct device_node *node)
|
|
{
|
|
struct ap130x_sensor *sensor;
|
|
u32 reg;
|
|
int ret;
|
|
|
|
/* Retrieve the sensor index from the reg property. */
|
|
ret = of_property_read_u32(node, "reg", ®);
|
|
if (ret < 0) {
|
|
dev_warn(ap130x->dev,
|
|
"'reg' property missing in sensor node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (reg >= ARRAY_SIZE(ap130x->sensors)) {
|
|
dev_warn(ap130x->dev, "Out-of-bounds 'reg' value %u\n",
|
|
reg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sensor = &ap130x->sensors[reg];
|
|
if (sensor->ap130x) {
|
|
dev_warn(ap130x->dev, "Duplicate entry for sensor %u\n", reg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sensor->ap130x = ap130x;
|
|
sensor->of_node = of_node_get(node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ap130x_sensor_dev_release(struct device *dev)
|
|
{
|
|
of_node_put(dev->of_node);
|
|
kfree(dev);
|
|
}
|
|
|
|
static int ap130x_sensor_init(struct ap130x_sensor *sensor, unsigned int index)
|
|
{
|
|
struct ap130x_device *ap130x = sensor->ap130x;
|
|
struct v4l2_subdev *sd = &sensor->sd;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
sensor->index = index;
|
|
|
|
/*
|
|
* Register a device for the sensor, to support usage of the regulator
|
|
* API.
|
|
*/
|
|
sensor->dev = kzalloc(sizeof(*sensor->dev), GFP_KERNEL);
|
|
if (!sensor->dev)
|
|
return -ENOMEM;
|
|
|
|
sensor->dev->parent = ap130x->dev;
|
|
sensor->dev->of_node = of_node_get(sensor->of_node);
|
|
sensor->dev->release = &ap130x_sensor_dev_release;
|
|
dev_set_name(sensor->dev, "%s-%s.%u", dev_name(ap130x->dev),
|
|
ap130x->sensor_info->name, index);
|
|
|
|
ret = device_register(sensor->dev);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev,
|
|
"Failed to register device for sensor %u\n", index);
|
|
goto error;
|
|
}
|
|
|
|
/* Retrieve the power supplies for the sensor, if any. */
|
|
if (ap130x->sensor_info->supplies) {
|
|
const struct ap130x_sensor_supply *supplies =
|
|
ap130x->sensor_info->supplies;
|
|
unsigned int num_supplies;
|
|
|
|
for (num_supplies = 0; supplies[num_supplies].name;)
|
|
++num_supplies;
|
|
|
|
sensor->supplies = devm_kcalloc(ap130x->dev, num_supplies,
|
|
sizeof(*sensor->supplies),
|
|
GFP_KERNEL);
|
|
if (!sensor->supplies) {
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < num_supplies; ++i)
|
|
sensor->supplies[i].supply = supplies[i].name;
|
|
|
|
ret = regulator_bulk_get(sensor->dev, num_supplies,
|
|
sensor->supplies);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev,
|
|
"Failed to get supplies for sensor %u\n",
|
|
index);
|
|
goto error;
|
|
}
|
|
|
|
sensor->num_supplies = i;
|
|
}
|
|
|
|
sd->dev = sensor->dev;
|
|
v4l2_subdev_init(sd, &ap130x_sensor_subdev_ops);
|
|
|
|
snprintf(sd->name, sizeof(sd->name), "%s %u",
|
|
ap130x->sensor_info->name, index);
|
|
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
|
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
|
|
ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev,
|
|
"failed to initialize media entity for sensor %u: %d\n",
|
|
index, ret);
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
put_device(sensor->dev);
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_sensor_cleanup(struct ap130x_sensor *sensor)
|
|
{
|
|
media_entity_cleanup(&sensor->sd.entity);
|
|
|
|
if (sensor->num_supplies)
|
|
regulator_bulk_free(sensor->num_supplies, sensor->supplies);
|
|
|
|
put_device(sensor->dev);
|
|
of_node_put(sensor->of_node);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Boot & Firmware Handling
|
|
*/
|
|
|
|
static int ap130x_request_firmware(struct ap130x_device *ap130x)
|
|
{
|
|
static const char * const suffixes[] = {
|
|
"",
|
|
"_single",
|
|
"_dual",
|
|
};
|
|
|
|
const struct ap130x_firmware_header *fw_hdr;
|
|
unsigned int num_sensors;
|
|
unsigned int fw_size;
|
|
unsigned int i;
|
|
char name[64];
|
|
int ret;
|
|
|
|
for (i = 0, num_sensors = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
if (ap130x->sensors[i].dev)
|
|
num_sensors++;
|
|
}
|
|
|
|
ret = snprintf(name, sizeof(name), "ap130x_%s%s_fw.bin",
|
|
ap130x->sensor_info->name, suffixes[num_sensors]);
|
|
if (ret >= sizeof(name)) {
|
|
dev_err(ap130x->dev, "Firmware name too long\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(ap130x->dev, "Requesting firmware %s\n", name);
|
|
|
|
ret = request_firmware(&ap130x->fw, name, ap130x->dev);
|
|
if (ret) {
|
|
dev_err(ap130x->dev, "Failed to request firmware: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ap130x->fw->size < sizeof(*fw_hdr)) {
|
|
dev_err(ap130x->dev, "Invalid firmware: too small\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* The firmware binary contains a header defined by the
|
|
* ap130x_firmware_header structure. The firmware itself (also referred
|
|
* to as bootdata) follows the header. Perform sanity checks to ensure
|
|
* the firmware is valid.
|
|
*/
|
|
fw_hdr = (const struct ap130x_firmware_header *)ap130x->fw->data;
|
|
fw_size = ap130x->fw->size - sizeof(*fw_hdr);
|
|
|
|
if (fw_hdr->pll_init_size > fw_size) {
|
|
dev_err(ap130x->dev,
|
|
"Invalid firmware: PLL init size too large\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ap130x_write_fw_window() - Write a piece of firmware to the AP130X
|
|
* @win_pos: Firmware load window current position
|
|
* @buf: Firmware data buffer
|
|
* @len: Firmware data length
|
|
*
|
|
* The firmware is loaded through a window in the registers space. Writes are
|
|
* sequential starting at address 0x8000, and must wrap around when reaching
|
|
* 0x9fff. This function write the firmware data stored in @buf to the AP130X,
|
|
* keeping track of the window position in the @win_pos argument.
|
|
*/
|
|
static int ap130x_write_fw_window(struct ap130x_device *ap130x, const u8 *buf,
|
|
u32 len, unsigned int *win_pos)
|
|
{
|
|
while (len > 0) {
|
|
unsigned int write_addr;
|
|
unsigned int write_size;
|
|
int ret;
|
|
|
|
/*
|
|
* Write at most len bytes, from the current position to the
|
|
* end of the window.
|
|
*/
|
|
write_addr = *win_pos + AP130X_FW_WINDOW_OFFSET;
|
|
write_size = min(len, AP130X_FW_WINDOW_SIZE - *win_pos);
|
|
|
|
ret = regmap_raw_write(ap130x->regmap16, write_addr, buf,
|
|
write_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
buf += write_size;
|
|
len -= write_size;
|
|
|
|
*win_pos += write_size;
|
|
if (*win_pos >= AP130X_FW_WINDOW_SIZE)
|
|
*win_pos = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_load_firmware(struct ap130x_device *ap130x)
|
|
{
|
|
const struct ap130x_firmware_header *fw_hdr;
|
|
unsigned int fw_size;
|
|
const u8 *fw_data;
|
|
unsigned int win_pos = 0;
|
|
int ret;
|
|
|
|
fw_hdr = (const struct ap130x_firmware_header *)ap130x->fw->data;
|
|
fw_data = (u8 *)&fw_hdr[1];
|
|
fw_size = ap130x->fw->size - sizeof(*fw_hdr);
|
|
|
|
/* Clear the CRC register. */
|
|
ret = ap130x_write(ap130x, AP130X_SIP_CRC, 0xffff, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Load the PLL initialization settings, set the bootdata stage to 2 to
|
|
* apply the basic_init_hp settings, and wait 1ms for the PLL to lock.
|
|
*/
|
|
ret = ap130x_write_fw_window(ap130x, fw_data, fw_hdr->pll_init_size,
|
|
&win_pos);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ap130x_write(ap130x, AP130X_BOOTDATA_STAGE, 0x0002, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usleep_range(1000, 2000);
|
|
|
|
/* Load the rest of the bootdata content and verify the CRC. */
|
|
ret = ap130x_write_fw_window(ap130x, fw_data + fw_hdr->pll_init_size,
|
|
fw_size - fw_hdr->pll_init_size, &win_pos);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msleep(40);
|
|
|
|
#if 0 // disable CRC check for tempory
|
|
|
|
ret = ap130x_read(ap130x, AP130X_SIP_CRC, &crc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (crc != fw_hdr->crc) {
|
|
dev_warn(ap130x->dev,
|
|
"CRC mismatch: expected 0x%04x, got 0x%04x\n",
|
|
fw_hdr->crc, crc);
|
|
return -EAGAIN;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Write 0xffff to the bootdata_stage register to indicate to the
|
|
* AP130X that the whole bootdata content has been loaded.
|
|
*/
|
|
ret = ap130x_write(ap130x, AP130X_BOOTDATA_STAGE, 0xffff, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* The AP130X starts outputting frames right after boot, stop it. */
|
|
ret = ap130x_stall(ap130x, true);
|
|
if (!ret)
|
|
ap130x->streaming = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ap130x_detect_chip(struct ap130x_device *ap130x)
|
|
{
|
|
unsigned int version;
|
|
unsigned int revision;
|
|
int ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_CHIP_VERSION, &version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ap130x_read(ap130x, AP130X_CHIP_REV, &revision);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (version != AP130X_CHIP_ID) {
|
|
dev_err(ap130x->dev,
|
|
"Invalid chip version, expected 0x%04x, got 0x%04x\n",
|
|
AP130X_CHIP_ID, version);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_info(ap130x->dev, "AP130X revision %u.%u.%u detected\n",
|
|
(revision & 0xf000) >> 12, (revision & 0x0f00) >> 8,
|
|
revision & 0x00ff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ap130x_hw_init(struct ap130x_device *ap130x)
|
|
{
|
|
unsigned int retries;
|
|
int ret;
|
|
|
|
/* Request and validate the firmware. */
|
|
ret = ap130x_request_firmware(ap130x);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Power the sensors first, as the firmware will access them once it
|
|
* gets loaded.
|
|
*/
|
|
ret = ap130x_power_on_sensors(ap130x);
|
|
if (ret < 0)
|
|
goto error_firmware;
|
|
|
|
#define MAX_FW_LOAD_RETRIES 3
|
|
/*
|
|
* Load the firmware, retrying in case of CRC errors. The AP130X is
|
|
* reset with a full power cycle between each attempt.
|
|
*/
|
|
for (retries = 0; retries < MAX_FW_LOAD_RETRIES; ++retries) {
|
|
ret = ap130x_power_on(ap130x);
|
|
if (ret < 0)
|
|
goto error_power_sensors;
|
|
|
|
ret = ap130x_detect_chip(ap130x);
|
|
if (ret)
|
|
goto error_power;
|
|
|
|
ret = ap130x_load_firmware(ap130x);
|
|
if (!ret)
|
|
break;
|
|
|
|
if (ret != -EAGAIN)
|
|
goto error_power;
|
|
|
|
ap130x_power_off(ap130x);
|
|
}
|
|
|
|
if (retries == MAX_FW_LOAD_RETRIES) {
|
|
dev_err(ap130x->dev,
|
|
"Firmware load retries exceeded, aborting\n");
|
|
ret = -ETIMEDOUT;
|
|
goto error_power_sensors;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error_power:
|
|
ap130x_power_off(ap130x);
|
|
error_power_sensors:
|
|
ap130x_power_off_sensors(ap130x);
|
|
error_firmware:
|
|
release_firmware(ap130x->fw);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_hw_cleanup(struct ap130x_device *ap130x)
|
|
{
|
|
ap130x_power_off(ap130x);
|
|
ap130x_power_off_sensors(ap130x);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Probe & Remove
|
|
*/
|
|
|
|
static int ap130x_config_v4l2(struct ap130x_device *ap130x)
|
|
{
|
|
struct v4l2_subdev *sd;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
sd = &ap130x->sd;
|
|
sd->dev = ap130x->dev;
|
|
v4l2_i2c_subdev_init(sd, ap130x->client, &ap130x_subdev_ops);
|
|
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
|
|
sd->internal_ops = &ap130x_subdev_internal_ops;
|
|
sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
|
|
sd->entity.ops = &ap130x_media_ops;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->pads); ++i)
|
|
ap130x->pads[i].flags = i == AP130X_PAD_SOURCE
|
|
? MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK;
|
|
|
|
ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(ap130x->pads),
|
|
ap130x->pads);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "media_entity_init failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->formats); ++i)
|
|
ap130x->formats[i].info = &supported_video_formats[0];
|
|
|
|
ret = ap130x_init_cfg(sd, NULL);
|
|
if (ret < 0)
|
|
goto error_media;
|
|
|
|
ret = ap130x_ctrls_init(ap130x);
|
|
if (ret < 0)
|
|
goto error_media;
|
|
|
|
ret = v4l2_async_register_subdev(sd);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "v4l2_async_register_subdev failed %d\n", ret);
|
|
goto error_ctrls;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error_ctrls:
|
|
ap130x_ctrls_cleanup(ap130x);
|
|
error_media:
|
|
media_entity_cleanup(&sd->entity);
|
|
return ret;
|
|
}
|
|
|
|
static int ap130x_parse_of(struct ap130x_device *ap130x)
|
|
{
|
|
struct device_node *sensors;
|
|
struct device_node *node;
|
|
struct fwnode_handle *ep;
|
|
unsigned int num_sensors = 0;
|
|
const char *model;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
/* Clock */
|
|
ap130x->clock = devm_clk_get(ap130x->dev, NULL);
|
|
if (IS_ERR(ap130x->clock)) {
|
|
dev_err(ap130x->dev, "Failed to get clock: %ld\n",
|
|
PTR_ERR(ap130x->clock));
|
|
return PTR_ERR(ap130x->clock);
|
|
}
|
|
|
|
/* GPIOs */
|
|
ap130x->reset_gpio = devm_gpiod_get(ap130x->dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(ap130x->reset_gpio)) {
|
|
dev_err(ap130x->dev, "Can't get reset GPIO: %ld\n",
|
|
PTR_ERR(ap130x->reset_gpio));
|
|
return PTR_ERR(ap130x->reset_gpio);
|
|
}
|
|
|
|
ap130x->standby_gpio = devm_gpiod_get_optional(ap130x->dev, "standby",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(ap130x->standby_gpio)) {
|
|
dev_err(ap130x->dev, "Can't get standby GPIO: %ld\n",
|
|
PTR_ERR(ap130x->standby_gpio));
|
|
return PTR_ERR(ap130x->standby_gpio);
|
|
}
|
|
|
|
ap130x->isp_en_gpio = devm_gpiod_get_optional(ap130x->dev, "isp_en",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(ap130x->isp_en_gpio)) {
|
|
dev_err(ap130x->dev, "Can't get ISP enable GPIO: %ld\n",
|
|
PTR_ERR(ap130x->isp_en_gpio));
|
|
return PTR_ERR(ap130x->isp_en_gpio);
|
|
}
|
|
gpiod_set_value_cansleep(ap130x->isp_en_gpio, 1);
|
|
|
|
// has issue?
|
|
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(ap130x->dev),
|
|
AP130X_PAD_SOURCE, 0,
|
|
FWNODE_GRAPH_ENDPOINT_NEXT);
|
|
if (!ep) {
|
|
dev_err(ap130x->dev, "no sink port found");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ap130x->bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY;
|
|
|
|
ret = v4l2_fwnode_endpoint_alloc_parse(ep, &ap130x->bus_cfg);
|
|
if (ret < 0) {
|
|
dev_err(ap130x->dev, "Failed to parse bus configuration\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Sensors */
|
|
sensors = of_get_child_by_name(dev_of_node(ap130x->dev), "sensors");
|
|
if (!sensors) {
|
|
dev_err(ap130x->dev, "'sensors' child node not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_string(sensors, "onnn,model", &model);
|
|
if (ret < 0) {
|
|
/*
|
|
* If no sensor is connected, we can still support operation
|
|
* with the test pattern generator.
|
|
*/
|
|
ap130x->sensor_info = &ap130x_sensor_info_tpg;
|
|
ap130x->width_factor = 1;
|
|
ret = 0;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x_sensor_info); ++i) {
|
|
const struct ap130x_sensor_info *info =
|
|
&ap130x_sensor_info[i];
|
|
|
|
if (!strcmp(info->model, model)) {
|
|
ap130x->sensor_info = info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ap130x->sensor_info) {
|
|
dev_warn(ap130x->dev, "Unsupported sensor model %s\n", model);
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
for_each_child_of_node(sensors, node) {
|
|
if (of_node_name_eq(node, "sensor")) {
|
|
if (!ap130x_sensor_parse_of(ap130x, node))
|
|
num_sensors++;
|
|
}
|
|
}
|
|
|
|
if (!num_sensors) {
|
|
dev_err(ap130x->dev, "No sensor found\n");
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
ap130x->width_factor = num_sensors;
|
|
|
|
done:
|
|
of_node_put(sensors);
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_cleanup(struct ap130x_device *ap130x)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
struct ap130x_sensor *sensor = &ap130x->sensors[i];
|
|
|
|
if (!sensor->ap130x)
|
|
continue;
|
|
|
|
ap130x_sensor_cleanup(sensor);
|
|
}
|
|
|
|
v4l2_fwnode_endpoint_free(&ap130x->bus_cfg);
|
|
|
|
mutex_destroy(&ap130x->lock);
|
|
}
|
|
|
|
static int ap130x_probe(struct i2c_client *client)
|
|
{
|
|
struct ap130x_device *ap130x;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ap130x = devm_kzalloc(&client->dev, sizeof(*ap130x), GFP_KERNEL);
|
|
if (!ap130x)
|
|
return -ENOMEM;
|
|
|
|
ap130x->dev = &client->dev;
|
|
ap130x->client = client;
|
|
|
|
mutex_init(&ap130x->lock);
|
|
|
|
ap130x->regmap16 = devm_regmap_init_i2c(client, &ap130x_reg16_config);
|
|
if (IS_ERR(ap130x->regmap16)) {
|
|
dev_err(ap130x->dev, "regmap16 init failed: %ld\n",
|
|
PTR_ERR(ap130x->regmap16));
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
ap130x->regmap32 = devm_regmap_init_i2c(client, &ap130x_reg32_config);
|
|
if (IS_ERR(ap130x->regmap32)) {
|
|
dev_err(ap130x->dev, "regmap32 init failed: %ld\n",
|
|
PTR_ERR(ap130x->regmap32));
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
ret = ap130x_parse_of(ap130x);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ap130x->sensors); ++i) {
|
|
struct ap130x_sensor *sensor = &ap130x->sensors[i];
|
|
|
|
if (!sensor->ap130x)
|
|
continue;
|
|
|
|
ret = ap130x_sensor_init(sensor, i);
|
|
if (ret < 0)
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < AP130X_NUM_SUPPLIES; ++i)
|
|
ap130x->supplies[i].supply = ap130x_supplies[i].name;
|
|
|
|
ret = devm_regulator_bulk_get(&client->dev, AP130X_NUM_SUPPLIES,
|
|
ap130x->supplies);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ap130x_hw_init(ap130x);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ap130x_debugfs_init(ap130x);
|
|
|
|
ret = ap130x_config_v4l2(ap130x);
|
|
if (ret)
|
|
goto error_hw_cleanup;
|
|
|
|
dev_dbg(ap130x->dev, "%d: successfully\n", __LINE__);
|
|
return 0;
|
|
|
|
error_hw_cleanup:
|
|
ap130x_hw_cleanup(ap130x);
|
|
error:
|
|
ap130x_cleanup(ap130x);
|
|
return ret;
|
|
}
|
|
|
|
static void ap130x_remove(struct i2c_client *client)
|
|
{
|
|
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
|
struct ap130x_device *ap130x = to_ap130x(sd);
|
|
|
|
ap130x_debugfs_cleanup(ap130x);
|
|
|
|
ap130x_hw_cleanup(ap130x);
|
|
|
|
release_firmware(ap130x->fw);
|
|
|
|
v4l2_async_unregister_subdev(sd);
|
|
media_entity_cleanup(&sd->entity);
|
|
|
|
ap130x_ctrls_cleanup(ap130x);
|
|
|
|
ap130x_cleanup(ap130x);
|
|
}
|
|
|
|
static const struct of_device_id ap130x_of_id_table[] = {
|
|
{ .compatible = "onnn,ap130x" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ap130x_of_id_table);
|
|
|
|
static struct i2c_driver ap130x_i2c_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.of_match_table = ap130x_of_id_table,
|
|
},
|
|
.probe = ap130x_probe,
|
|
.remove = ap130x_remove,
|
|
};
|
|
|
|
module_i2c_driver(ap130x_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Florian Rebaudo <frebaudo@witekio.com>");
|
|
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
|
|
MODULE_AUTHOR("Anil Kumar M <anil.mamidala@xilinx.com>");
|
|
|
|
MODULE_DESCRIPTION("ON Semiconductor AP130X ISP driver");
|
|
MODULE_LICENSE("GPL");
|