2268 lines
63 KiB
Diff
2268 lines
63 KiB
Diff
|
From 12395a0e12f4d6707728fd13bab2f3694a876d53 Mon Sep 17 00:00:00 2001
|
||
|
From: =?UTF-8?q?Ond=C5=99ej=20Jirman?= <megi@xff.cz>
|
||
|
Date: Sat, 30 Sep 2017 02:39:48 +0200
|
||
|
Subject: [PATCH 034/469] media: hm5065: Add subdev driver for Himax HM5065
|
||
|
camera sensor
|
||
|
|
||
|
HM5065 is 5MP CMOS sensor. This driver implements support for
|
||
|
V4L2_MBUS_PARALLEL bus type only. The driver Other features:
|
||
|
|
||
|
- External clock rates support from 6-27MHz (discrete values)
|
||
|
- Resolution support from 2592x1944 to 88x72
|
||
|
- Frame rates are available depending on the PCLK frequency
|
||
|
(from VGA@120 to 2592x1944@8)
|
||
|
- Support for several YUV and RGB media bus formats
|
||
|
|
||
|
Signed-off-by: Ondrej Jirman <megi@xff.cz>
|
||
|
---
|
||
|
drivers/media/i2c/Kconfig | 10 +
|
||
|
drivers/media/i2c/Makefile | 1 +
|
||
|
drivers/media/i2c/hm5065.c | 2205 ++++++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 2216 insertions(+)
|
||
|
create mode 100644 drivers/media/i2c/hm5065.c
|
||
|
|
||
|
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
|
||
|
index 256d55bb2b1d..4e79b9d60e08 100644
|
||
|
--- a/drivers/media/i2c/Kconfig
|
||
|
+++ b/drivers/media/i2c/Kconfig
|
||
|
@@ -797,6 +797,16 @@ config VIDEO_ST_VGXY61
|
||
|
source "drivers/media/i2c/ccs/Kconfig"
|
||
|
source "drivers/media/i2c/et8ek8/Kconfig"
|
||
|
|
||
|
+config VIDEO_HM5065
|
||
|
+ tristate "Himax HM5065 sensor support"
|
||
|
+ depends on I2C
|
||
|
+ select V4L2_FWNODE
|
||
|
+ select VIDEO_V4L2_SUBDEV_API
|
||
|
+ select MEDIA_CONTROLLER
|
||
|
+ help
|
||
|
+ This is a V4L2 sensor-level driver for Himax HM5065
|
||
|
+ 5 Mpixel camera.
|
||
|
+
|
||
|
endmenu
|
||
|
|
||
|
menu "Lens drivers"
|
||
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
||
|
index b44dacf935f4..2ea647270f40 100644
|
||
|
--- a/drivers/media/i2c/Makefile
|
||
|
+++ b/drivers/media/i2c/Makefile
|
||
|
@@ -140,3 +140,4 @@ obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o
|
||
|
obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
|
||
|
obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
|
||
|
obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
|
||
|
+obj-$(CONFIG_VIDEO_HM5065) += hm5065.o
|
||
|
diff --git a/drivers/media/i2c/hm5065.c b/drivers/media/i2c/hm5065.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..a2ed05b12411
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/media/i2c/hm5065.c
|
||
|
@@ -0,0 +1,2205 @@
|
||
|
+/*
|
||
|
+ * Himax HM5065 driver.
|
||
|
+ * Copyright (C) 2017-2019 Ondřej Jirman <megi@xff.cz>.
|
||
|
+ *
|
||
|
+ * This program is free software; you can redistribute it and/or modify
|
||
|
+ * it under the terms of the GNU General Public License as published by
|
||
|
+ * the Free Software Foundation; either version 2 of the License, or
|
||
|
+ * (at your option) any later version.
|
||
|
+ */
|
||
|
+
|
||
|
+#include <asm/div64.h>
|
||
|
+#include <linux/clk.h>
|
||
|
+#include <linux/clk-provider.h>
|
||
|
+#include <linux/clkdev.h>
|
||
|
+#include <linux/ctype.h>
|
||
|
+#include <linux/delay.h>
|
||
|
+#include <linux/device.h>
|
||
|
+#include <linux/firmware.h>
|
||
|
+#include <linux/i2c.h>
|
||
|
+#include <linux/init.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/of_device.h>
|
||
|
+#include <linux/slab.h>
|
||
|
+#include <linux/types.h>
|
||
|
+#include <linux/gpio/consumer.h>
|
||
|
+#include <linux/regulator/consumer.h>
|
||
|
+#include <media/v4l2-async.h>
|
||
|
+#include <media/v4l2-ctrls.h>
|
||
|
+#include <media/v4l2-device.h>
|
||
|
+#include <media/v4l2-fwnode.h>
|
||
|
+#include <media/v4l2-subdev.h>
|
||
|
+
|
||
|
+#define HM5065_AF_FIRMWARE "hm5065-af.bin"
|
||
|
+#define HM5065_FIRMWARE_PARAMETERS "hm5065-init.bin"
|
||
|
+
|
||
|
+#define HM5065_SENSOR_WIDTH 2592u
|
||
|
+#define HM5065_SENSOR_HEIGHT 1944u
|
||
|
+#define HM5065_CAPTURE_WIDTH_MIN 88u
|
||
|
+#define HM5065_CAPTURE_HEIGHT_MIN 72u
|
||
|
+
|
||
|
+/* {{{ Register definitions */
|
||
|
+
|
||
|
+/* registers are assumed to be u8 unless otherwise specified */
|
||
|
+
|
||
|
+/* device parameters */
|
||
|
+#define HM5065_REG_DEVICE_ID 0x0000 /* u16 */
|
||
|
+#define HM5065_REG_DEVICE_ID_VALUE 0x039e
|
||
|
+#define HM5065_REG_FIRMWARE_VSN 0x0002
|
||
|
+#define HM5065_REG_PATCH_VSN 0x0003
|
||
|
+#define HM5065_REG_EXCLOCKLUT 0x0009 /* standby */
|
||
|
+
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG 0x000a
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG_OP_MODE BIT(0)
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG_CAM_MODE BIT(1)
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG_JPEG_STATUS BIT(2)
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG_NUM_FRAMES BIT(3)
|
||
|
+#define HM5065_REG_INT_EVENT_FLAG_AF_LOCKED BIT(4)
|
||
|
+
|
||
|
+/* mode manager */
|
||
|
+#define HM5065_REG_USER_COMMAND 0x0010
|
||
|
+#define HM5065_REG_USER_COMMAND_STOP 0x00
|
||
|
+#define HM5065_REG_USER_COMMAND_RUN 0x01
|
||
|
+#define HM5065_REG_USER_COMMAND_POWEROFF 0x02
|
||
|
+
|
||
|
+#define HM5065_REG_STATE 0x0011
|
||
|
+#define HM5065_REG_STATE_RAW 0x10
|
||
|
+#define HM5065_REG_STATE_IDLE 0x20
|
||
|
+#define HM5065_REG_STATE_RUNNING 0x30
|
||
|
+
|
||
|
+#define HM5065_REG_ACTIVE_PIPE_SETUP_BANK 0x0012
|
||
|
+#define HM5065_REG_ACTIVE_PIPE_SETUP_BANK_0 0x00
|
||
|
+#define HM5065_REG_ACTIVE_PIPE_SETUP_BANK_1 0x01
|
||
|
+
|
||
|
+#define HM5065_REG_NUMBER_OF_FRAMES_STREAMED 0x0014 /* ro */
|
||
|
+#define HM5065_REG_REQUIRED_STREAM_LENGTH 0x0015
|
||
|
+
|
||
|
+#define HM5065_REG_CSI_ENABLE 0x0016 /* standby */
|
||
|
+#define HM5065_REG_CSI_ENABLE_DISABLE 0x00
|
||
|
+#define HM5065_REG_CSI_ENABLE_CSI2_1LANE 0x01
|
||
|
+#define HM5065_REG_CSI_ENABLE_CSI2_2LANE 0x02
|
||
|
+
|
||
|
+/* pipe setup bank 0 */
|
||
|
+#define HM5065_REG_P0_SENSOR_MODE 0x0040
|
||
|
+#define HM5065_REG_SENSOR_MODE_FULLSIZE 0x00
|
||
|
+#define HM5065_REG_SENSOR_MODE_BINNING_2X2 0x01
|
||
|
+#define HM5065_REG_SENSOR_MODE_BINNING_4X4 0x02
|
||
|
+#define HM5065_REG_SENSOR_MODE_SUBSAMPLING_2X2 0x03
|
||
|
+#define HM5065_REG_SENSOR_MODE_SUBSAMPLING_4X4 0x04
|
||
|
+
|
||
|
+#define HM5065_REG_P0_IMAGE_SIZE 0x0041
|
||
|
+#define HM5065_REG_IMAGE_SIZE_5MP 0x00
|
||
|
+#define HM5065_REG_IMAGE_SIZE_UXGA 0x01
|
||
|
+#define HM5065_REG_IMAGE_SIZE_SXGA 0x02
|
||
|
+#define HM5065_REG_IMAGE_SIZE_SVGA 0x03
|
||
|
+#define HM5065_REG_IMAGE_SIZE_VGA 0x04
|
||
|
+#define HM5065_REG_IMAGE_SIZE_CIF 0x05
|
||
|
+#define HM5065_REG_IMAGE_SIZE_QVGA 0x06
|
||
|
+#define HM5065_REG_IMAGE_SIZE_QCIF 0x07
|
||
|
+#define HM5065_REG_IMAGE_SIZE_QQVGA 0x08
|
||
|
+#define HM5065_REG_IMAGE_SIZE_QQCIF 0x09
|
||
|
+#define HM5065_REG_IMAGE_SIZE_MANUAL 0x0a
|
||
|
+
|
||
|
+#define HM5065_REG_P0_MANUAL_HSIZE 0x0042 /* u16 */
|
||
|
+#define HM5065_REG_P0_MANUAL_VSIZE 0x0044 /* u16 */
|
||
|
+
|
||
|
+#define HM5065_REG_P0_DATA_FORMAT 0x0046
|
||
|
+#define HM5065_REG_DATA_FORMAT_YCBCR_JFIF 0x00
|
||
|
+#define HM5065_REG_DATA_FORMAT_YCBCR_REC601 0x01
|
||
|
+#define HM5065_REG_DATA_FORMAT_YCBCR_CUSTOM 0x02
|
||
|
+#define HM5065_REG_DATA_FORMAT_RGB_565 0x03
|
||
|
+#define HM5065_REG_DATA_FORMAT_RGB_565_CUSTOM 0x04
|
||
|
+#define HM5065_REG_DATA_FORMAT_RGB_444 0x05
|
||
|
+#define HM5065_REG_DATA_FORMAT_RGB_555 0x06
|
||
|
+#define HM5065_REG_DATA_FORMAT_RAW10ITU10 0x07
|
||
|
+#define HM5065_REG_DATA_FORMAT_RAW10ITU8 0x08
|
||
|
+#define HM5065_REG_DATA_FORMAT_JPEG 0x09
|
||
|
+
|
||
|
+#define HM5065_REG_P0_GAMMA_GAIN 0x0049 /* 0-31 */
|
||
|
+#define HM5065_REG_P0_GAMMA_INTERPOLATION 0x004a /* 0-16 */
|
||
|
+#define HM5065_REG_P0_PEAKING_GAIN 0x004c /* 0-63 */
|
||
|
+
|
||
|
+#define HM5065_REG_P0_JPEG_SQUEEZE_MODE 0x004d
|
||
|
+#define HM5065_REG_JPEG_SQUEEZE_MODE_USER 0x00
|
||
|
+#define HM5065_REG_JPEG_SQUEEZE_MODE_AUTO 0x01
|
||
|
+
|
||
|
+#define HM5065_REG_P0_JPEG_TARGET_FILE_SIZE 0x004e /* u16, kB */
|
||
|
+#define HM5065_REG_P0_JPEG_IMAGE_QUALITY 0x0050
|
||
|
+#define HM5065_REG_JPEG_IMAGE_QUALITY_HIGH 0x00
|
||
|
+#define HM5065_REG_JPEG_IMAGE_QUALITY_MEDIUM 0x01
|
||
|
+#define HM5065_REG_JPEG_IMAGE_QUALITY_LOW 0x02
|
||
|
+
|
||
|
+/* pipe setup bank 1 (only register indexes) */
|
||
|
+#define HM5065_REG_P1_SENSOR_MODE 0x0060
|
||
|
+#define HM5065_REG_P1_IMAGE_SIZE 0x0061
|
||
|
+#define HM5065_REG_P1_MANUAL_HSIZE 0x0062 /* u16 */
|
||
|
+#define HM5065_REG_P1_MANUAL_VSIZE 0x0064 /* u16 */
|
||
|
+#define HM5065_REG_P1_DATA_FORMAT 0x0066
|
||
|
+#define HM5065_REG_P1_GAMMA_GAIN 0x0069 /* 0-31 */
|
||
|
+#define HM5065_REG_P1_GAMMA_INTERPOLATION 0x006a /* 0-16 */
|
||
|
+#define HM5065_REG_P1_PEAKING_GAIN 0x006c /* 0-63 */
|
||
|
+#define HM5065_REG_P1_JPEG_SQUEEZE_MODE 0x006d
|
||
|
+#define HM5065_REG_P1_JPEG_TARGET_FILE_SIZE 0x006e /* u16, kB */
|
||
|
+#define HM5065_REG_P1_JPEG_IMAGE_QUALITY 0x0070
|
||
|
+
|
||
|
+/* pipe setup - common registers */
|
||
|
+#define HM5065_REG_CONTRAST 0x0080 /* 0-200 */
|
||
|
+#define HM5065_REG_COLOR_SATURATION 0x0081 /* 0-200 */
|
||
|
+#define HM5065_REG_BRIGHTNESS 0x0082 /* 0-200 */
|
||
|
+#define HM5065_REG_HORIZONTAL_MIRROR 0x0083 /* 0,1 */
|
||
|
+#define HM5065_REG_VERTICAL_FLIP 0x0084 /* 0,1 */
|
||
|
+
|
||
|
+#define HM5065_REG_YCRCB_ORDER 0x0085
|
||
|
+#define HM5065_REG_YCRCB_ORDER_CB_Y_CR_Y 0x00
|
||
|
+#define HM5065_REG_YCRCB_ORDER_CR_Y_CB_Y 0x01
|
||
|
+#define HM5065_REG_YCRCB_ORDER_Y_CB_Y_CR 0x02
|
||
|
+#define HM5065_REG_YCRCB_ORDER_Y_CR_Y_CB 0x03
|
||
|
+
|
||
|
+/* clock chain parameter inputs (floating point) */
|
||
|
+#define HM5065_REG_EXTERNAL_CLOCK_FREQ_MHZ 0x00b0 /* fp16, 6-27, standby */
|
||
|
+#define HM5065_REG_TARGET_PLL_OUTPUT 0x00b2 /* fp16, 450-1000, standby */
|
||
|
+
|
||
|
+/* static frame rate control */
|
||
|
+#define HM5065_REG_DESIRED_FRAME_RATE_NUM 0x00c8 /* u16 */
|
||
|
+#define HM5065_REG_DESIRED_FRAME_RATE_DEN 0x00ca
|
||
|
+
|
||
|
+/* static frame rate status */
|
||
|
+#define HM5065_REG_REQUESTED_FRAME_RATE_HZ 0x00d8 /* fp16 */
|
||
|
+#define HM5065_REG_MAX_FRAME_RATE_HZ 0x00da /* fp16 */
|
||
|
+#define HM5065_REG_MIN_FRAME_RATE_HZ 0x00dc /* fp16 */
|
||
|
+
|
||
|
+/* exposure controls */
|
||
|
+#define HM5065_REG_EXPOSURE_MODE 0x0128
|
||
|
+#define HM5065_REG_EXPOSURE_MODE_AUTO 0x00
|
||
|
+#define HM5065_REG_EXPOSURE_MODE_COMPILED_MANUAL 0x01
|
||
|
+#define HM5065_REG_EXPOSURE_MODE_DIRECT_MANUAL 0x02
|
||
|
+
|
||
|
+#define HM5065_REG_EXPOSURE_METERING 0x0129
|
||
|
+#define HM5065_REG_EXPOSURE_METERING_FLAT 0x00
|
||
|
+#define HM5065_REG_EXPOSURE_METERING_BACKLIT 0x01
|
||
|
+#define HM5065_REG_EXPOSURE_METERING_CENTERED 0x02
|
||
|
+
|
||
|
+#define HM5065_REG_MANUAL_EXPOSURE_TIME_NUM 0x012a
|
||
|
+#define HM5065_REG_MANUAL_EXPOSURE_TIME_DEN 0x012b
|
||
|
+#define HM5065_REG_MANUAL_EXPOSURE_TIME_US 0x012c /* fp16 */
|
||
|
+#define HM5065_REG_COLD_START_DESIRED_TIME_US 0x012e /* fp16, standby */
|
||
|
+#define HM5065_REG_EXPOSURE_COMPENSATION 0x0130 /* s8, -7 - +7 */
|
||
|
+
|
||
|
+#define HM5065_REG_DIRECT_MODE_COARSE_INTEGRATION_LINES 0x0132 /* u16 */
|
||
|
+#define HM5065_REG_DIRECT_MODE_FINE_INTEGRATION_PIXELS 0x0134 /* u16 */
|
||
|
+#define HM5065_REG_DIRECT_MODE_CODED_ANALOG_GAIN 0x0136 /* u16 */
|
||
|
+#define HM5065_REG_DIRECT_MODE_DIGITAL_GAIN 0x0138 /* fp16 */
|
||
|
+#define HM5065_REG_FREEZE_AUTO_EXPOSURE 0x0142 /* 0,1 */
|
||
|
+#define HM5065_REG_USER_MAXIMUM_INTEGRATION_TIME_US 0x0143 /* fp16 */
|
||
|
+#define HM5065_REG_ANTI_FLICKER_MODE 0x0148 /* 0,1 */
|
||
|
+
|
||
|
+/* exposure algorithm controls */
|
||
|
+#define HM5065_REG_DIGITAL_GAIN_FLOOR 0x015c /* fp16 */
|
||
|
+#define HM5065_REG_DIGITAL_GAIN_CEILING 0x015e /* fp16 */
|
||
|
+#define HM5065_REG_ANALOG_GAIN_FLOOR 0x02c0 /* u16 */
|
||
|
+#define HM5065_REG_ANALOG_GAIN_CEILING 0x02c2 /* u16 */
|
||
|
+
|
||
|
+/* exposure status */
|
||
|
+#define HM5065_REG_COARSE_INTEGRATION 0x017c /* u16 */
|
||
|
+#define HM5065_REG_FINE_INTEGRATION_PENDING_PIXELS 0x017e /* u16 */
|
||
|
+#define HM5065_REG_ANALOG_GAIN_PENDING 0x0180 /* fp16 */
|
||
|
+#define HM5065_REG_DIGITAL_GAIN_PENDING 0x0182 /* fp16 */
|
||
|
+#define HM5065_REG_DESIRED_EXPOSURE_TIME_US 0x0184 /* fp16 */
|
||
|
+#define HM5065_REG_COMPILED_EXPOSURE_TIME_US 0x0186 /* fp16 */
|
||
|
+#define HM5065_REG_USER_MAXIMUM_INTEGRATION_LINES 0x0189 /* u16 */
|
||
|
+#define HM5065_REG_TOTAL_INTEGRATION_TIME_PENDING_US 0x018b /* fp16 */
|
||
|
+#define HM5065_REG_CODED_ANALOG_GAIN_PENDING 0x018d /* u16 */
|
||
|
+
|
||
|
+/* flicker detect */
|
||
|
+#define HM5065_REG_FD_ENABLE_DETECT 0x0190 /* 0,1 */
|
||
|
+#define HM5065_REG_FD_DETECTION_START 0x0191 /* 0,1 */
|
||
|
+#define HM5065_REG_FD_MAX_NUMBER_ATTEMP 0x0192 /* 0-255, 0 = continuous */
|
||
|
+#define HM5065_REG_FD_FLICKER_IDENTIFICATION_THRESHOLD 0x0193 /* u16 */
|
||
|
+#define HM5065_REG_FD_WIN_TIMES 0x0195
|
||
|
+#define HM5065_REG_FD_FRAME_RATE_SHIFT_NUMBER 0x0196
|
||
|
+#define HM5065_REG_FD_MANUAL_FREF_ENABLE 0x0197 /* 0,1 */
|
||
|
+#define HM5065_REG_FD_MANU_FREF_100 0x0198 /* u16 */
|
||
|
+#define HM5065_REG_FD_MANU_FREF_120 0x019a /* u16 */
|
||
|
+#define HM5065_REG_FD_FLICKER_FREQUENCY 0x019c /* fp16 */
|
||
|
+
|
||
|
+/* white balance control */
|
||
|
+#define HM5065_REG_WB_MODE 0x01a0
|
||
|
+#define HM5065_REG_WB_MODE_OFF 0x00
|
||
|
+#define HM5065_REG_WB_MODE_AUTOMATIC 0x01
|
||
|
+#define HM5065_REG_WB_MODE_AUTO_INSTANT 0x02
|
||
|
+#define HM5065_REG_WB_MODE_MANUAL_RGB 0x03
|
||
|
+#define HM5065_REG_WB_MODE_CLOUDY_PRESET 0x04
|
||
|
+#define HM5065_REG_WB_MODE_SUNNY_PRESET 0x05
|
||
|
+#define HM5065_REG_WB_MODE_LED_PRESET 0x06
|
||
|
+#define HM5065_REG_WB_MODE_FLUORESCENT_PRESET 0x07
|
||
|
+#define HM5065_REG_WB_MODE_TUNGSTEN_PRESET 0x08
|
||
|
+#define HM5065_REG_WB_MODE_HORIZON_PRESET 0x09
|
||
|
+
|
||
|
+#define HM5065_REG_WB_MANUAL_RED_GAIN 0x01a1
|
||
|
+#define HM5065_REG_WB_MANUAL_GREEN_GAIN 0x01a2
|
||
|
+#define HM5065_REG_WB_MANUAL_BLUE_GAIN 0x01a3
|
||
|
+
|
||
|
+#define HM5065_REG_WB_MISC_SETTINGS 0x01a4
|
||
|
+#define HM5065_REG_WB_MISC_SETTINGS_FREEZE_ALGO BIT(2)
|
||
|
+
|
||
|
+#define HM5065_REG_WB_HUE_R_BIAS 0x01a5 /* fp16 */
|
||
|
+#define HM5065_REG_WB_HUE_B_BIAS 0x01a7 /* fp16 */
|
||
|
+
|
||
|
+#define HM5065_REG_WB_STATUS 0x01c0
|
||
|
+#define HM5065_REG_WB_STATUS_STABLE BIT(0)
|
||
|
+
|
||
|
+#define HM5065_REG_WB_NORM_RED_GAIN 0x01c8 /* fp16 */
|
||
|
+#define HM5065_REG_WB_PART_RED_GAIN 0x01e0 /* fp16 */
|
||
|
+#define HM5065_REG_WB_PART_GREEN_GAIN 0x01e2 /* fp16 */
|
||
|
+#define HM5065_REG_WB_PART_BLUE_GAIN 0x01e4 /* fp16 */
|
||
|
+
|
||
|
+/* image stability status */
|
||
|
+#define HM5065_REG_WHITE_BALANCE_STABLE 0x0291 /* 0,1 */
|
||
|
+#define HM5065_REG_EXPOSURE_STABLE 0x0292 /* 0,1 */
|
||
|
+#define HM5065_REG_STABLE 0x0294 /* 0,1 */
|
||
|
+
|
||
|
+/* special effects */
|
||
|
+#define HM5065_REG_EFFECTS_NEGATIVE 0x0380 /* 0,1 */
|
||
|
+#define HM5065_REG_EFFECTS_SOLARISING 0x0381 /* 0,1 */
|
||
|
+#define HM5065_REG_EFFECTS_SKECTH 0x0382 /* 0,1 */
|
||
|
+
|
||
|
+#define HM5065_REG_EFFECTS_COLOR 0x0384
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_NORMAL 0x00
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_RED_ONLY 0x01
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_YELLOW_ONLY 0x02
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_GREEN_ONLY 0x03
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_BLUE_ONLY 0x04
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_BLACK_WHITE 0x05
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_SEPIA 0x06
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_ANTIQUE 0x07
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_AQUA 0x08
|
||
|
+#define HM5065_REG_EFFECTS_COLOR_MANUAL_MATRIX 0x09
|
||
|
+
|
||
|
+/* anti-vignete, otp flash (skipped), page 79-89 */
|
||
|
+
|
||
|
+/* flash control */
|
||
|
+#define HM5065_REG_FLASH_MODE 0x02d0 /* 0,1 */
|
||
|
+#define HM5065_REG_FLASH_RECOMMENDED 0x02d1 /* 0,1 */
|
||
|
+
|
||
|
+/* test pattern */
|
||
|
+#define HM5065_REG_ENABLE_TEST_PATTERN 0x05d8 /* 0,1 */
|
||
|
+
|
||
|
+#define HM5065_REG_TEST_PATTERN 0x05d9
|
||
|
+#define HM5065_REG_TEST_PATTERN_NONE 0x00
|
||
|
+#define HM5065_REG_TEST_PATTERN_HORIZONTAL_GREY_SCALE 0x01
|
||
|
+#define HM5065_REG_TEST_PATTERN_VERTICAL_GREY_SCALE 0x02
|
||
|
+#define HM5065_REG_TEST_PATTERN_DIAGONAL_GREY_SCALE 0x03
|
||
|
+#define HM5065_REG_TEST_PATTERN_PN28 0x04
|
||
|
+#define HM5065_REG_TEST_PATTERN_PN9 0x05
|
||
|
+#define HM5065_REG_TEST_PATTERN_SOLID_COLOR 0x06
|
||
|
+#define HM5065_REG_TEST_PATTERN_COLOR_BARS 0x07
|
||
|
+#define HM5065_REG_TEST_PATTERN_GRADUATED_COLOR_BARS 0x08
|
||
|
+
|
||
|
+#define HM5065_REG_TESTDATA_RED 0x4304 /* u16, 0-1023 */
|
||
|
+#define HM5065_REG_TESTDATA_GREEN_R 0x4308 /* u16, 0-1023 */
|
||
|
+#define HM5065_REG_TESTDATA_BLUE 0x430c /* u16, 0-1023 */
|
||
|
+#define HM5065_REG_TESTDATA_GREEN_B 0x4310 /* u16, 0-1023 */
|
||
|
+
|
||
|
+/* contrast stretch */
|
||
|
+#define HM5065_REG_CS_ENABLE 0x05e8 /* 0,1 */
|
||
|
+#define HM5065_REG_CS_GAIN_CEILING 0x05e9 /* fp16 */
|
||
|
+#define HM5065_REG_CS_BLACK_OFFSET_CEILING 0x05eb
|
||
|
+#define HM5065_REG_CS_WHITE_PIX_TARGET 0x05ec /* fp16 */
|
||
|
+#define HM5065_REG_CS_BLACK_PIX_TARGET 0x05ee /* fp16 */
|
||
|
+#define HM5065_REG_CS_ENABLED 0x05f8 /* 0,1 */
|
||
|
+#define HM5065_REG_CS_TOTAL_PIXEL 0x05f9 /* fp16 */
|
||
|
+#define HM5065_REG_CS_W_TARGET 0x05fb /* u32 */
|
||
|
+#define HM5065_REG_CS_B_TARGET 0x05ff /* u32 */
|
||
|
+#define HM5065_REG_CS_GAIN 0x0603 /* fp16 */
|
||
|
+#define HM5065_REG_CS_BLACK_OFFSET 0x0605
|
||
|
+#define HM5065_REG_CS_WHITE_LIMIT 0x0606
|
||
|
+
|
||
|
+/* preset controls */
|
||
|
+#define HM5065_REG_PRESET_LOADER_ENABLE 0x0638 /* 0,1, standby */
|
||
|
+
|
||
|
+#define HM5065_REG_INDIVIDUAL_PRESET 0x0639 /* standby */
|
||
|
+#define HM5065_REG_INDIVIDUAL_PRESET_ANTIVIGNETTE BIT(0)
|
||
|
+#define HM5065_REG_INDIVIDUAL_PRESET_WHITE_BALANCE BIT(1)
|
||
|
+#define HM5065_REG_INDIVIDUAL_PRESET_VCM BIT(4)
|
||
|
+
|
||
|
+/* jpeg control parameters*/
|
||
|
+#define HM5065_REG_JPEG_STATUS 0x0649
|
||
|
+#define HM5065_REG_JPEG_RESTART 0x064a
|
||
|
+#define HM5065_REG_JPEG_HI_SQUEEZE_VALUE 0x064b /* 5-255 (5 = max q.) */
|
||
|
+#define HM5065_REG_JPEG_MED_SQUEEZE_VALUE 0x064c /* 5-255 */
|
||
|
+#define HM5065_REG_JPEG_LOW_SQUEEZE_VALUE 0x064d /* 5-255 */
|
||
|
+#define HM5065_REG_JPEG_LINE_LENGTH 0x064e /* u16, standby */
|
||
|
+#define HM5065_REG_JPEG_CLOCK_RATIO 0x0650 /* 1-8, standby */
|
||
|
+#define HM5065_REG_JPEG_THRES 0x0651 /* u16, standby */
|
||
|
+#define HM5065_REG_JPEG_BYTE_SENT 0x0653 /* u32 */
|
||
|
+
|
||
|
+/* autofocus */
|
||
|
+
|
||
|
+#define HM5065_REG_AF_WINDOWS_SYSTEM 0x065a
|
||
|
+#define HM5065_REG_AF_WINDOWS_SYSTEM_7_ZONES 0x00
|
||
|
+#define HM5065_REG_AF_WINDOWS_SYSTEM_1_ZONE 0x01
|
||
|
+
|
||
|
+#define HM5065_REG_AF_H_RATIO_NUM 0x065b
|
||
|
+#define HM5065_REG_AF_H_RATIO_DEN 0x065c
|
||
|
+#define HM5065_REG_AF_V_RATIO_NUM 0x065d
|
||
|
+#define HM5065_REG_AF_V_RATIO_DEN 0x065e
|
||
|
+
|
||
|
+#define HM5065_REG_AF_RANGE 0x0709
|
||
|
+#define HM5065_REG_AF_RANGE_FULL 0x00
|
||
|
+#define HM5065_REG_AF_RANGE_LANDSCAPE 0x01
|
||
|
+#define HM5065_REG_AF_RANGE_MACRO 0x02
|
||
|
+
|
||
|
+#define HM5065_REG_AF_MODE 0x070a
|
||
|
+#define HM5065_REG_AF_MODE_MANUAL 0x00
|
||
|
+#define HM5065_REG_AF_MODE_CONTINUOUS 0x01
|
||
|
+#define HM5065_REG_AF_MODE_SINGLE 0x03
|
||
|
+
|
||
|
+#define HM5065_REG_AF_MODE_STATUS 0x0720
|
||
|
+
|
||
|
+#define HM5065_REG_AF_COMMAND 0x070b
|
||
|
+#define HM5065_REG_AF_COMMAND_NULL 0x00
|
||
|
+#define HM5065_REG_AF_COMMAND_RELEASED_BUTTON 0x01
|
||
|
+#define HM5065_REG_AF_COMMAND_HALF_BUTTON 0x02
|
||
|
+#define HM5065_REG_AF_COMMAND_TAKE_SNAPSHOT 0x03
|
||
|
+#define HM5065_REG_AF_COMMAND_REFOCUS 0x04
|
||
|
+
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND 0x070c
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_NULL 0x00
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_MOVE_STEP_TO_INFINITY 0x01
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_MOVE_STEP_TO_MACRO 0x02
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_GOTO_INFINITY 0x03
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_GOTO_MACRO 0x04
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_GOTO_RECOVERY 0x05
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_GOTO_TARGET_POSITION 0x07
|
||
|
+#define HM5065_REG_AF_LENS_COMMAND_GOTO_HYPERFOCAL 0x0C
|
||
|
+
|
||
|
+#define HM5065_REG_AF_MANUAL_STEP_SIZE 0x070d
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_CTRL_ENABLE 0x0714
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_CTRL_ENABLE_AF BIT(0)
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_CTRL_ENABLE_AE BIT(1)
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_CTRL_ENABLE_AWB BIT(2)
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_X_START 0x0715 /* u16 */
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_X_SIZE 0x0717 /* u16 */
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_Y_START 0x0719 /* u16 */
|
||
|
+#define HM5065_REG_AF_FACE_LOCATION_Y_SIZE 0x071b /* u16 */
|
||
|
+
|
||
|
+#define HM5065_REG_AF_IN_FOCUS 0x07ae /* ro 0,1 */
|
||
|
+#define HM5065_REG_AF_IS_STABLE 0x0725 /* ro 0,1 */
|
||
|
+
|
||
|
+/* reverse engineered registers */
|
||
|
+#define HM5065_REG_BUS_DATA_FORMAT 0x7000
|
||
|
+#define HM5065_REG_COLORSPACE 0x5200
|
||
|
+#define HM5065_REG_BUS_CONFIG 0x7101
|
||
|
+#define HM5065_REG_BUS_CONFIG_BT656 0x24
|
||
|
+#define HM5065_REG_BUS_CONFIG_PARALLEL_HH_VL 0x44
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+
|
||
|
+struct reg_value {
|
||
|
+ u16 addr;
|
||
|
+ u8 value;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+/*
|
||
|
+ * Sensor has various pre-defined PLL configurations for a set of
|
||
|
+ * external clock frequencies.
|
||
|
+ */
|
||
|
+struct hm5065_clk_lut {
|
||
|
+ unsigned long clk_freq;
|
||
|
+ u8 lut_id;
|
||
|
+};
|
||
|
+
|
||
|
+static const struct hm5065_clk_lut hm5065_clk_luts[] = {
|
||
|
+ { .clk_freq = 12000000, .lut_id = 0x10 },
|
||
|
+ { .clk_freq = 13000000, .lut_id = 0x11 },
|
||
|
+ { .clk_freq = 13500000, .lut_id = 0x12 },
|
||
|
+ { .clk_freq = 14400000, .lut_id = 0x13 },
|
||
|
+ { .clk_freq = 18000000, .lut_id = 0x14 },
|
||
|
+ { .clk_freq = 19200000, .lut_id = 0x15 },
|
||
|
+ { .clk_freq = 24000000, .lut_id = 0x16 },
|
||
|
+ { .clk_freq = 26000000, .lut_id = 0x17 },
|
||
|
+ { .clk_freq = 27000000, .lut_id = 0x18 },
|
||
|
+};
|
||
|
+
|
||
|
+static const struct hm5065_clk_lut *hm5065_find_clk_lut(unsigned long freq)
|
||
|
+{
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < ARRAY_SIZE(hm5065_clk_luts); i++)
|
||
|
+ if (hm5065_clk_luts[i].clk_freq == freq)
|
||
|
+ return &hm5065_clk_luts[i];
|
||
|
+
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+struct hm5065_pixfmt {
|
||
|
+ u32 code;
|
||
|
+ u32 colorspace;
|
||
|
+ u8 data_fmt;
|
||
|
+ u8 ycbcr_order;
|
||
|
+ u8 fmt_setup;
|
||
|
+};
|
||
|
+
|
||
|
+//XXX: identify colrorspace correctly, see datasheet page 40
|
||
|
+static const struct hm5065_pixfmt hm5065_formats[] = {
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_UYVY8_2X8,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_YCBCR_CUSTOM,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_CB_Y_CR_Y,
|
||
|
+ .fmt_setup = 0x08
|
||
|
+ },
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_VYUY8_2X8,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_YCBCR_CUSTOM,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_CR_Y_CB_Y,
|
||
|
+ .fmt_setup = 0x08
|
||
|
+ },
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_YUYV8_2X8,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_YCBCR_CUSTOM,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_Y_CB_Y_CR,
|
||
|
+ .fmt_setup = 0x08
|
||
|
+ },
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_YVYU8_2X8,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_YCBCR_CUSTOM,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_Y_CR_Y_CB,
|
||
|
+ .fmt_setup = 0x08
|
||
|
+ },
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_RGB_565,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_Y_CR_Y_CB,
|
||
|
+ .fmt_setup = 0x02
|
||
|
+ },
|
||
|
+ {
|
||
|
+ .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
|
||
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
||
|
+ .data_fmt = HM5065_REG_DATA_FORMAT_RGB_555,
|
||
|
+ .ycbcr_order = HM5065_REG_YCRCB_ORDER_Y_CR_Y_CB,
|
||
|
+ .fmt_setup = 0x02
|
||
|
+ },
|
||
|
+};
|
||
|
+
|
||
|
+#define HM5065_NUM_FORMATS ARRAY_SIZE(hm5065_formats)
|
||
|
+
|
||
|
+static const struct hm5065_pixfmt *hm5065_find_format(u32 code)
|
||
|
+{
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < HM5065_NUM_FORMATS; i++)
|
||
|
+ if (hm5065_formats[i].code == code)
|
||
|
+ return &hm5065_formats[i];
|
||
|
+
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+/* regulator supplies */
|
||
|
+static const char * const hm5065_supply_name[] = {
|
||
|
+ "IOVDD", /* Digital I/O (2.8V) suppply */
|
||
|
+ "AFVDD", /* Autofocus (2.8V) supply */
|
||
|
+ "DVDD", /* Digital Core (1.8V) supply */
|
||
|
+ "AVDD", /* Analog (2.8V) supply */
|
||
|
+};
|
||
|
+
|
||
|
+#define HM5065_NUM_SUPPLIES ARRAY_SIZE(hm5065_supply_name)
|
||
|
+
|
||
|
+struct hm5065_ctrls {
|
||
|
+ struct v4l2_ctrl_handler handler;
|
||
|
+ struct {
|
||
|
+ struct v4l2_ctrl *auto_exposure;
|
||
|
+ struct v4l2_ctrl *exposure;
|
||
|
+ struct v4l2_ctrl *d_gain;
|
||
|
+ struct v4l2_ctrl *a_gain;
|
||
|
+ };
|
||
|
+ struct v4l2_ctrl *metering;
|
||
|
+ struct v4l2_ctrl *exposure_bias;
|
||
|
+ struct {
|
||
|
+ struct v4l2_ctrl *wb;
|
||
|
+ struct v4l2_ctrl *blue_balance;
|
||
|
+ struct v4l2_ctrl *red_balance;
|
||
|
+ };
|
||
|
+ struct {
|
||
|
+ struct v4l2_ctrl *focus_auto;
|
||
|
+ struct v4l2_ctrl *af_start;
|
||
|
+ struct v4l2_ctrl *af_stop;
|
||
|
+ struct v4l2_ctrl *af_status;
|
||
|
+ struct v4l2_ctrl *af_distance;
|
||
|
+ struct v4l2_ctrl *focus_relative;
|
||
|
+ };
|
||
|
+ struct v4l2_ctrl *aaa_lock;
|
||
|
+ struct v4l2_ctrl *hflip;
|
||
|
+ struct v4l2_ctrl *vflip;
|
||
|
+ struct v4l2_ctrl *pl_freq;
|
||
|
+ struct v4l2_ctrl *colorfx;
|
||
|
+ struct v4l2_ctrl *brightness;
|
||
|
+ struct v4l2_ctrl *saturation;
|
||
|
+ struct v4l2_ctrl *contrast;
|
||
|
+ struct v4l2_ctrl *gamma;
|
||
|
+ struct v4l2_ctrl *test_pattern;
|
||
|
+ struct v4l2_ctrl *test_data[4];
|
||
|
+};
|
||
|
+
|
||
|
+struct hm5065_dev {
|
||
|
+ struct i2c_client *i2c_client;
|
||
|
+ struct v4l2_subdev sd;
|
||
|
+ struct media_pad pad;
|
||
|
+ struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
|
||
|
+ struct clk *xclk; /* external clock for HM5065 */
|
||
|
+
|
||
|
+ struct regulator_bulk_data supplies[HM5065_NUM_SUPPLIES];
|
||
|
+ struct gpio_desc *reset_gpio; // nrst pin
|
||
|
+ struct gpio_desc *enable_gpio; // ce pin
|
||
|
+
|
||
|
+ /* lock to protect all members below */
|
||
|
+ struct mutex lock;
|
||
|
+
|
||
|
+ struct v4l2_mbus_framefmt fmt;
|
||
|
+ struct v4l2_fract frame_interval;
|
||
|
+ struct hm5065_ctrls ctrls;
|
||
|
+
|
||
|
+ bool pending_mode_change;
|
||
|
+ bool powered;
|
||
|
+ bool streaming;
|
||
|
+};
|
||
|
+
|
||
|
+static inline struct hm5065_dev *to_hm5065_dev(struct v4l2_subdev *sd)
|
||
|
+{
|
||
|
+ return container_of(sd, struct hm5065_dev, sd);
|
||
|
+}
|
||
|
+
|
||
|
+static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
|
||
|
+{
|
||
|
+ return &container_of(ctrl->handler, struct hm5065_dev,
|
||
|
+ ctrls.handler)->sd;
|
||
|
+}
|
||
|
+
|
||
|
+/* {{{ Register access helpers */
|
||
|
+
|
||
|
+static int hm5065_write_regs(struct hm5065_dev *sensor, u16 start_index,
|
||
|
+ u8 *data, int data_size)
|
||
|
+{
|
||
|
+ struct i2c_client *client = sensor->i2c_client;
|
||
|
+ struct i2c_msg msg;
|
||
|
+ u8 buf[130];
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (data_size > sizeof(buf) - 2) {
|
||
|
+ v4l2_err(&sensor->sd, "%s: oversized transfer (size=%d)\n",
|
||
|
+ __func__, data_size);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ buf[0] = start_index >> 8;
|
||
|
+ buf[1] = start_index & 0xff;
|
||
|
+ memcpy(buf + 2, data, data_size);
|
||
|
+
|
||
|
+ msg.addr = client->addr;
|
||
|
+ msg.flags = client->flags;
|
||
|
+ msg.buf = buf;
|
||
|
+ msg.len = data_size + 2;
|
||
|
+
|
||
|
+ dev_dbg(&sensor->i2c_client->dev, "wr: %04x <= %*ph\n",
|
||
|
+ (u32)start_index, data_size, data);
|
||
|
+
|
||
|
+ ret = i2c_transfer(client->adapter, &msg, 1);
|
||
|
+ if (ret < 0) {
|
||
|
+ v4l2_err(&sensor->sd,
|
||
|
+ "%s: error %d: start_index=%x, data=%*ph\n",
|
||
|
+ __func__, ret, (u32)start_index, data_size, data);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_read_regs(struct hm5065_dev *sensor, u16 start_index,
|
||
|
+ u8 *data, int data_size)
|
||
|
+{
|
||
|
+ struct i2c_client *client = sensor->i2c_client;
|
||
|
+ struct i2c_msg msg[2];
|
||
|
+ u8 buf[2];
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ buf[0] = start_index >> 8;
|
||
|
+ buf[1] = start_index & 0xff;
|
||
|
+
|
||
|
+ msg[0].addr = client->addr;
|
||
|
+ msg[0].flags = client->flags;
|
||
|
+ msg[0].buf = buf;
|
||
|
+ msg[0].len = sizeof(buf);
|
||
|
+
|
||
|
+ msg[1].addr = client->addr;
|
||
|
+ msg[1].flags = client->flags | I2C_M_RD;
|
||
|
+ msg[1].buf = data;
|
||
|
+ msg[1].len = data_size;
|
||
|
+
|
||
|
+ ret = i2c_transfer(client->adapter, msg, 2);
|
||
|
+ if (ret < 0) {
|
||
|
+ v4l2_err(&sensor->sd,
|
||
|
+ "%s: error %d: start_index=%x, data_size=%d\n",
|
||
|
+ __func__, ret, (u32)start_index, data_size);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(&sensor->i2c_client->dev, "rd: %04x => %*ph\n",
|
||
|
+ (u32)start_index, data_size, data);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_read(struct hm5065_dev *sensor, u16 reg, u8 *val)
|
||
|
+{
|
||
|
+ return hm5065_read_regs(sensor, reg, val, 1);
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_write(struct hm5065_dev *sensor, u16 reg, u8 val)
|
||
|
+{
|
||
|
+ return hm5065_write_regs(sensor, reg, &val, 1);
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_read16(struct hm5065_dev *sensor, u16 reg, u16 *val)
|
||
|
+{
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = hm5065_read_regs(sensor, reg, (u8 *)val, sizeof(*val));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ *val = be16_to_cpu(*val);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_write16(struct hm5065_dev *sensor, u16 reg, u16 val)
|
||
|
+{
|
||
|
+ u16 tmp = cpu_to_be16(val);
|
||
|
+
|
||
|
+ return hm5065_write_regs(sensor, reg, (u8 *)&tmp, sizeof(tmp));
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * The firmware format:
|
||
|
+ * <record 0>, ..., <record N - 1>
|
||
|
+ * "record" is a 2-byte register address (big endian) followed by 1-byte data
|
||
|
+ */
|
||
|
+static int hm5065_load_firmware(struct hm5065_dev *sensor, const char *name)
|
||
|
+{
|
||
|
+ int ret = 0, i = 0, list_size;
|
||
|
+ const struct firmware *fw;
|
||
|
+ struct reg_value *list;
|
||
|
+ u16 start, len;
|
||
|
+ u8 buf[128];
|
||
|
+
|
||
|
+ ret = request_firmware(&fw, name, sensor->sd.v4l2_dev->dev);
|
||
|
+ if (ret) {
|
||
|
+ v4l2_warn(&sensor->sd,
|
||
|
+ "Failed to read firmware %s, continuing anyway...\n",
|
||
|
+ name);
|
||
|
+ return 1;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (fw->size == 0) {
|
||
|
+ ret = 1;
|
||
|
+ goto err_release;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (fw->size % 3 != 0) {
|
||
|
+ v4l2_err(&sensor->sd, "Firmware image %s has invalid size\n",
|
||
|
+ name);
|
||
|
+ ret = -EINVAL;
|
||
|
+ goto err_release;
|
||
|
+ }
|
||
|
+
|
||
|
+ list_size = fw->size / 3;
|
||
|
+ list = (struct reg_value *)fw->data;
|
||
|
+
|
||
|
+ /* we speed up I2C communication via auto-increment functionality */
|
||
|
+ while (i < list_size) {
|
||
|
+ start = be16_to_cpu(list[i].addr);
|
||
|
+ len = 0;
|
||
|
+
|
||
|
+ while (i < list_size &&
|
||
|
+ be16_to_cpu(list[i].addr) == (start + len) &&
|
||
|
+ len < sizeof(buf))
|
||
|
+ buf[len++] = list[i++].value;
|
||
|
+
|
||
|
+ ret = hm5065_write_regs(sensor, start, buf, len);
|
||
|
+ if (ret)
|
||
|
+ goto err_release;
|
||
|
+ }
|
||
|
+
|
||
|
+err_release:
|
||
|
+ release_firmware(fw);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Sensor uses ST Float900 format to represent floating point numbers.
|
||
|
+ * Binary floating point number: * (s ? -1 : 0) * 1.mmmmmmmmm * 2^eeeeee
|
||
|
+ *
|
||
|
+ * Following functions convert long value to and from the floating point format.
|
||
|
+ *
|
||
|
+ * Example:
|
||
|
+ * mili variant: val = 123456 => fp_val = 123.456
|
||
|
+ * micro variant: val = -12345678 => fp_val = -12.345678
|
||
|
+ */
|
||
|
+static s64 hm5065_mili_from_fp16(u16 fp_val)
|
||
|
+{
|
||
|
+ s64 val;
|
||
|
+ s64 mantisa = fp_val & 0x1ff;
|
||
|
+ int exp = (int)((fp_val >> 9) & 0x3f) - 31;
|
||
|
+
|
||
|
+ val = (1000 * (mantisa | 0x200));
|
||
|
+ if (exp > 0)
|
||
|
+ val <<= exp;
|
||
|
+ else if (exp < 0)
|
||
|
+ val >>= -exp;
|
||
|
+ val >>= 9;
|
||
|
+
|
||
|
+ if (fp_val & 0x8000)
|
||
|
+ val = -val;
|
||
|
+
|
||
|
+ return val;
|
||
|
+}
|
||
|
+
|
||
|
+static u16 hm5065_mili_to_fp16(s32 val)
|
||
|
+{
|
||
|
+ int fls;
|
||
|
+ u16 e, m, s = 0;
|
||
|
+ u64 v, rem;
|
||
|
+
|
||
|
+ if (val == 0)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ if (val < 0) {
|
||
|
+ val = -val;
|
||
|
+ s = 0x8000;
|
||
|
+ }
|
||
|
+
|
||
|
+ v = (u64)val * 1024;
|
||
|
+ rem = do_div(v, 1000);
|
||
|
+ if (rem >= 500)
|
||
|
+ v++;
|
||
|
+
|
||
|
+ fls = fls64(v) - 1;
|
||
|
+ e = 31 + fls - 10;
|
||
|
+ m = fls > 9 ? v >> (fls - 9) : v << (9 - fls);
|
||
|
+
|
||
|
+ return s | (m & 0x1ff) | (e << 9);
|
||
|
+}
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+/* {{{ Controls */
|
||
|
+
|
||
|
+static int hm5065_get_af_status(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ u8 is_stable, mode;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = hm5065_read(sensor, HM5065_REG_AF_MODE_STATUS, &mode);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (mode == HM5065_REG_AF_MODE_MANUAL) {
|
||
|
+ ctrls->af_status->val = V4L2_AUTO_FOCUS_STATUS_IDLE;
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_read(sensor, HM5065_REG_AF_IS_STABLE, &is_stable);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (is_stable)
|
||
|
+ ctrls->af_status->val = V4L2_AUTO_FOCUS_STATUS_REACHED;
|
||
|
+ else if (!is_stable && mode == HM5065_REG_AF_MODE_CONTINUOUS)
|
||
|
+ ctrls->af_status->val = V4L2_AUTO_FOCUS_STATUS_BUSY;
|
||
|
+ else
|
||
|
+ ctrls->af_status->val = V4L2_AUTO_FOCUS_STATUS_IDLE;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_get_exposure(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ u16 again, dgain, exp;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = hm5065_read16(sensor, HM5065_REG_CODED_ANALOG_GAIN_PENDING,
|
||
|
+ &again);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_read16(sensor, HM5065_REG_DIGITAL_GAIN_PENDING, &dgain);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_read16(sensor, HM5065_REG_COARSE_INTEGRATION, &exp);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ctrls->exposure->val = exp;
|
||
|
+ ctrls->d_gain->val = clamp(hm5065_mili_from_fp16(dgain), 1000ll,
|
||
|
+ 4000ll);
|
||
|
+ ctrls->a_gain->val = again;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
+{
|
||
|
+ struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ /* v4l2_ctrl_lock() locks our own mutex */
|
||
|
+
|
||
|
+ if (!sensor->powered)
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ switch (ctrl->id) {
|
||
|
+ case V4L2_CID_FOCUS_AUTO:
|
||
|
+ ret = hm5065_get_af_status(sensor);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ break;
|
||
|
+ case V4L2_CID_EXPOSURE_AUTO:
|
||
|
+ ret = hm5065_get_exposure(sensor);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const u8 hm5065_wb_opts[][2] = {
|
||
|
+ { V4L2_WHITE_BALANCE_MANUAL, HM5065_REG_WB_MODE_OFF },
|
||
|
+ { V4L2_WHITE_BALANCE_INCANDESCENT, HM5065_REG_WB_MODE_TUNGSTEN_PRESET },
|
||
|
+ { V4L2_WHITE_BALANCE_FLUORESCENT,
|
||
|
+ HM5065_REG_WB_MODE_FLUORESCENT_PRESET },
|
||
|
+ { V4L2_WHITE_BALANCE_HORIZON, HM5065_REG_WB_MODE_HORIZON_PRESET },
|
||
|
+ { V4L2_WHITE_BALANCE_CLOUDY, HM5065_REG_WB_MODE_CLOUDY_PRESET },
|
||
|
+ { V4L2_WHITE_BALANCE_DAYLIGHT, HM5065_REG_WB_MODE_SUNNY_PRESET },
|
||
|
+ { V4L2_WHITE_BALANCE_AUTO, HM5065_REG_WB_MODE_AUTOMATIC },
|
||
|
+};
|
||
|
+
|
||
|
+static int hm5065_set_power_line_frequency(struct hm5065_dev *sensor, s32 val)
|
||
|
+{
|
||
|
+ u16 freq;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ switch (val) {
|
||
|
+ case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED:
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_ANTI_FLICKER_MODE, 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return hm5065_write(sensor, HM5065_REG_FD_ENABLE_DETECT, 0);
|
||
|
+ case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
|
||
|
+ case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_ANTI_FLICKER_MODE, 1);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_FD_ENABLE_DETECT, 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ freq = (val == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) ?
|
||
|
+ 0x4b20 : 0x4bc0;
|
||
|
+
|
||
|
+ return hm5065_write16(sensor, HM5065_REG_FD_FLICKER_FREQUENCY,
|
||
|
+ freq);
|
||
|
+ case V4L2_CID_POWER_LINE_FREQUENCY_AUTO:
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_FD_ENABLE_DETECT, 1);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_ANTI_FLICKER_MODE, 1);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_FD_MAX_NUMBER_ATTEMP,
|
||
|
+ 100);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_FD_FLICKER_FREQUENCY,
|
||
|
+ 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return hm5065_write(sensor, HM5065_REG_FD_DETECTION_START, 1);
|
||
|
+ default:
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_colorfx(struct hm5065_dev *sensor, s32 val)
|
||
|
+{
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EFFECTS_COLOR,
|
||
|
+ HM5065_REG_EFFECTS_COLOR_NORMAL);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EFFECTS_NEGATIVE, 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EFFECTS_SOLARISING, 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EFFECTS_SKECTH, 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ switch (val) {
|
||
|
+ case V4L2_COLORFX_NONE:
|
||
|
+ return 0;
|
||
|
+ case V4L2_COLORFX_NEGATIVE:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_NEGATIVE, 1);
|
||
|
+ case V4L2_COLORFX_SOLARIZATION:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_SOLARISING, 1);
|
||
|
+ case V4L2_COLORFX_SKETCH:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_SKECTH, 1);
|
||
|
+ case V4L2_COLORFX_ANTIQUE:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_COLOR,
|
||
|
+ HM5065_REG_EFFECTS_COLOR_ANTIQUE);
|
||
|
+ case V4L2_COLORFX_SEPIA:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_COLOR,
|
||
|
+ HM5065_REG_EFFECTS_COLOR_SEPIA);
|
||
|
+ case V4L2_COLORFX_AQUA:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_COLOR,
|
||
|
+ HM5065_REG_EFFECTS_COLOR_AQUA);
|
||
|
+ case V4L2_COLORFX_BW:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EFFECTS_COLOR,
|
||
|
+ HM5065_REG_EFFECTS_COLOR_BLACK_WHITE);
|
||
|
+ default:
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+#define AE_BIAS_MENU_DEFAULT_VALUE_INDEX 7
|
||
|
+static const s64 ae_bias_menu_values[] = {
|
||
|
+ -2100, -1800, -1500, -1200, -900, -600, -300,
|
||
|
+ 0, 300, 600, 900, 1200, 1500, 1800, 2100
|
||
|
+};
|
||
|
+
|
||
|
+static const s8 ae_bias_menu_reg_values[] = {
|
||
|
+ -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7
|
||
|
+};
|
||
|
+
|
||
|
+static int hm5065_set_exposure(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ bool is_auto = (ctrls->auto_exposure->val != V4L2_EXPOSURE_MANUAL);
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ if (ctrls->auto_exposure->is_new) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EXPOSURE_MODE,
|
||
|
+ is_auto ?
|
||
|
+ HM5065_REG_EXPOSURE_MODE_AUTO :
|
||
|
+ HM5065_REG_EXPOSURE_MODE_DIRECT_MANUAL);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (ctrls->auto_exposure->cur.val != ctrls->auto_exposure->val &&
|
||
|
+ !is_auto) {
|
||
|
+ /*
|
||
|
+ * Hack: At this point, there are current volatile
|
||
|
+ * values in val, but control framework will not
|
||
|
+ * update the cur values for our autocluster, as it
|
||
|
+ * should. I couldn't find the reason. This fixes
|
||
|
+ * it for our driver. Remove this after the kernel
|
||
|
+ * is fixed.
|
||
|
+ */
|
||
|
+ ctrls->exposure->cur.val = ctrls->exposure->val;
|
||
|
+ ctrls->d_gain->cur.val = ctrls->d_gain->val;
|
||
|
+ ctrls->a_gain->cur.val = ctrls->a_gain->val;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!is_auto && ctrls->exposure->is_new) {
|
||
|
+ ret = hm5065_write16(sensor,
|
||
|
+ HM5065_REG_DIRECT_MODE_COARSE_INTEGRATION_LINES,
|
||
|
+ ctrls->exposure->val);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!is_auto && ctrls->d_gain->is_new) {
|
||
|
+ ret = hm5065_write16(sensor,
|
||
|
+ HM5065_REG_DIRECT_MODE_DIGITAL_GAIN,
|
||
|
+ hm5065_mili_to_fp16(ctrls->d_gain->val));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!is_auto && ctrls->a_gain->is_new)
|
||
|
+ ret = hm5065_write16(sensor,
|
||
|
+ HM5065_REG_DIRECT_MODE_CODED_ANALOG_GAIN,
|
||
|
+ ctrls->a_gain->val);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_3a_lock(struct hm5065_dev *sensor, struct v4l2_ctrl *ctrl)
|
||
|
+{
|
||
|
+ bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE;
|
||
|
+ bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE;
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_EXPOSURE
|
||
|
+ && sensor->ctrls.auto_exposure->val == V4L2_EXPOSURE_AUTO) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_FREEZE_AUTO_EXPOSURE,
|
||
|
+ ae_lock);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_WHITE_BALANCE)
|
||
|
+ && sensor->ctrls.wb->val == V4L2_WHITE_BALANCE_AUTO) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_WB_MISC_SETTINGS,
|
||
|
+ awb_lock ?
|
||
|
+ HM5065_REG_WB_MISC_SETTINGS_FREEZE_ALGO : 0);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_auto_focus(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ bool auto_focus = ctrls->focus_auto->val;
|
||
|
+ int ret = 0;
|
||
|
+ u8 range;
|
||
|
+
|
||
|
+ if (auto_focus && ctrls->af_distance->is_new) {
|
||
|
+ switch (ctrls->af_distance->val) {
|
||
|
+ case V4L2_AUTO_FOCUS_RANGE_MACRO:
|
||
|
+ range = HM5065_REG_AF_RANGE_MACRO;
|
||
|
+ break;
|
||
|
+ case V4L2_AUTO_FOCUS_RANGE_AUTO:
|
||
|
+ range = HM5065_REG_AF_RANGE_FULL;
|
||
|
+ break;
|
||
|
+ case V4L2_AUTO_FOCUS_RANGE_INFINITY:
|
||
|
+ range = HM5065_REG_AF_RANGE_LANDSCAPE;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_RANGE, range);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (ctrls->focus_auto->is_new) {
|
||
|
+ v4l2_ctrl_activate(ctrls->af_start, !auto_focus);
|
||
|
+ v4l2_ctrl_activate(ctrls->af_stop, !auto_focus);
|
||
|
+ v4l2_ctrl_activate(ctrls->focus_relative, !auto_focus);
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_MODE,
|
||
|
+ auto_focus ?
|
||
|
+ HM5065_REG_AF_MODE_CONTINUOUS :
|
||
|
+ HM5065_REG_AF_MODE_SINGLE);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (!auto_focus) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_COMMAND,
|
||
|
+ HM5065_REG_AF_COMMAND_RELEASED_BUTTON);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!auto_focus && ctrls->af_start->is_new) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_MODE,
|
||
|
+ HM5065_REG_AF_MODE_SINGLE);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_COMMAND,
|
||
|
+ HM5065_REG_AF_COMMAND_RELEASED_BUTTON);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ usleep_range(190000, 200000);
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_COMMAND,
|
||
|
+ HM5065_REG_AF_COMMAND_HALF_BUTTON);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!auto_focus && ctrls->af_stop->is_new) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_COMMAND,
|
||
|
+ HM5065_REG_AF_COMMAND_RELEASED_BUTTON);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_MODE,
|
||
|
+ HM5065_REG_AF_MODE_MANUAL);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!auto_focus && ctrls->focus_relative->is_new &&
|
||
|
+ ctrls->focus_relative->val) {
|
||
|
+ u8 cmd = 0xff;
|
||
|
+ s32 step = ctrls->focus_relative->val;
|
||
|
+
|
||
|
+ ctrls->focus_relative->val = 0;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_MODE,
|
||
|
+ HM5065_REG_AF_MODE_MANUAL);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_MANUAL_STEP_SIZE,
|
||
|
+ abs(step));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (step < 0)
|
||
|
+ cmd = HM5065_REG_AF_LENS_COMMAND_MOVE_STEP_TO_INFINITY;
|
||
|
+ else if (step > 0)
|
||
|
+ cmd = HM5065_REG_AF_LENS_COMMAND_MOVE_STEP_TO_MACRO;
|
||
|
+
|
||
|
+ if (cmd != 0xff)
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_AF_LENS_COMMAND,
|
||
|
+ cmd);
|
||
|
+
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_white_balance(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ bool manual_wb = ctrls->wb->val == V4L2_WHITE_BALANCE_MANUAL;
|
||
|
+ int ret = 0, i;
|
||
|
+ s32 val;
|
||
|
+
|
||
|
+ if (ctrls->wb->is_new) {
|
||
|
+ for (i = 0; i < ARRAY_SIZE(hm5065_wb_opts); i++) {
|
||
|
+ if (hm5065_wb_opts[i][0] != ctrls->wb->val)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_WB_MODE,
|
||
|
+ hm5065_wb_opts[i][1]);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ goto next;
|
||
|
+ }
|
||
|
+
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+next:
|
||
|
+ if (ctrls->wb->is_new || ctrls->blue_balance->is_new) {
|
||
|
+ val = manual_wb ? ctrls->blue_balance->val : 1000;
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_WB_HUE_B_BIAS,
|
||
|
+ hm5065_mili_to_fp16(val));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (ctrls->wb->is_new || ctrls->red_balance->is_new) {
|
||
|
+ val = manual_wb ? ctrls->red_balance->val : 1000;
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_WB_HUE_R_BIAS,
|
||
|
+ hm5065_mili_to_fp16(val));
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_s_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
+{
|
||
|
+ struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ s32 val = ctrl->val;
|
||
|
+ unsigned int i;
|
||
|
+ int ret;
|
||
|
+ u8 reg;
|
||
|
+
|
||
|
+ /* v4l2_ctrl_lock() locks our own mutex */
|
||
|
+
|
||
|
+ /*
|
||
|
+ * If the device is not powered up by the host driver do
|
||
|
+ * not apply any controls to H/W at this time. Instead
|
||
|
+ * the controls will be restored right after power-up.
|
||
|
+ */
|
||
|
+ if (!sensor->powered)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ switch (ctrl->id) {
|
||
|
+ case V4L2_CID_EXPOSURE_AUTO:
|
||
|
+ return hm5065_set_exposure(sensor);
|
||
|
+
|
||
|
+ case V4L2_CID_EXPOSURE_METERING:
|
||
|
+ if (val == V4L2_EXPOSURE_METERING_AVERAGE)
|
||
|
+ reg = HM5065_REG_EXPOSURE_METERING_FLAT;
|
||
|
+ else if (val == V4L2_EXPOSURE_METERING_CENTER_WEIGHTED)
|
||
|
+ reg = HM5065_REG_EXPOSURE_METERING_CENTERED;
|
||
|
+ else
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EXPOSURE_METERING, reg);
|
||
|
+
|
||
|
+ case V4L2_CID_AUTO_EXPOSURE_BIAS:
|
||
|
+ if (val < 0 || val >= ARRAY_SIZE(ae_bias_menu_reg_values))
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ return hm5065_write(sensor, HM5065_REG_EXPOSURE_COMPENSATION,
|
||
|
+ (u8)ae_bias_menu_reg_values[val]);
|
||
|
+
|
||
|
+ case V4L2_CID_FOCUS_AUTO:
|
||
|
+ return hm5065_set_auto_focus(sensor);
|
||
|
+
|
||
|
+ case V4L2_CID_CONTRAST:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_CONTRAST, val);
|
||
|
+
|
||
|
+ case V4L2_CID_SATURATION:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_COLOR_SATURATION, val);
|
||
|
+
|
||
|
+ case V4L2_CID_BRIGHTNESS:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_BRIGHTNESS, val);
|
||
|
+
|
||
|
+ case V4L2_CID_POWER_LINE_FREQUENCY:
|
||
|
+ return hm5065_set_power_line_frequency(sensor, val);
|
||
|
+
|
||
|
+ case V4L2_CID_GAMMA:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_P0_GAMMA_GAIN, val);
|
||
|
+
|
||
|
+ case V4L2_CID_VFLIP:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_VERTICAL_FLIP,
|
||
|
+ val ? 1 : 0);
|
||
|
+
|
||
|
+ case V4L2_CID_HFLIP:
|
||
|
+ return hm5065_write(sensor, HM5065_REG_HORIZONTAL_MIRROR,
|
||
|
+ val ? 1 : 0);
|
||
|
+
|
||
|
+ case V4L2_CID_COLORFX:
|
||
|
+ return hm5065_set_colorfx(sensor, val);
|
||
|
+
|
||
|
+ case V4L2_CID_3A_LOCK:
|
||
|
+ return hm5065_3a_lock(sensor, ctrl);
|
||
|
+
|
||
|
+ case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
|
||
|
+ return hm5065_set_white_balance(sensor);
|
||
|
+
|
||
|
+ case V4L2_CID_TEST_PATTERN_RED:
|
||
|
+ return hm5065_write16(sensor, HM5065_REG_TESTDATA_RED, val);
|
||
|
+
|
||
|
+ case V4L2_CID_TEST_PATTERN_GREENR:
|
||
|
+ return hm5065_write16(sensor, HM5065_REG_TESTDATA_GREEN_R, val);
|
||
|
+
|
||
|
+ case V4L2_CID_TEST_PATTERN_BLUE:
|
||
|
+ return hm5065_write16(sensor, HM5065_REG_TESTDATA_BLUE, val);
|
||
|
+
|
||
|
+ case V4L2_CID_TEST_PATTERN_GREENB:
|
||
|
+ return hm5065_write16(sensor, HM5065_REG_TESTDATA_GREEN_B, val);
|
||
|
+
|
||
|
+ case V4L2_CID_TEST_PATTERN:
|
||
|
+ for (i = 0; i < ARRAY_SIZE(ctrls->test_data); i++)
|
||
|
+ v4l2_ctrl_activate(ctrls->test_data[i],
|
||
|
+ val == 6); /* solid color */
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_ENABLE_TEST_PATTERN,
|
||
|
+ val == 0 ? 0 : 1);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return hm5065_write(sensor, HM5065_REG_TEST_PATTERN, val);
|
||
|
+
|
||
|
+ default:
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static const struct v4l2_ctrl_ops hm5065_ctrl_ops = {
|
||
|
+ .g_volatile_ctrl = hm5065_g_volatile_ctrl,
|
||
|
+ .s_ctrl = hm5065_s_ctrl,
|
||
|
+};
|
||
|
+
|
||
|
+static const char * const test_pattern_menu[] = {
|
||
|
+ "Disabled",
|
||
|
+ "Horizontal gray scale",
|
||
|
+ "Vertical gray scale",
|
||
|
+ "Diagonal gray scale",
|
||
|
+ "PN28",
|
||
|
+ "PN9 (bus test)",
|
||
|
+ "Solid color",
|
||
|
+ "Color bars",
|
||
|
+ "Graduated color bars",
|
||
|
+};
|
||
|
+
|
||
|
+static int hm5065_init_controls(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ const struct v4l2_ctrl_ops *ops = &hm5065_ctrl_ops;
|
||
|
+ struct hm5065_ctrls *ctrls = &sensor->ctrls;
|
||
|
+ struct v4l2_ctrl_handler *hdl = &ctrls->handler;
|
||
|
+ u8 wb_max = 0;
|
||
|
+ u64 wb_mask = 0;
|
||
|
+ unsigned int i;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ v4l2_ctrl_handler_init(hdl, 32);
|
||
|
+
|
||
|
+ /* we can use our own mutex for the ctrl lock */
|
||
|
+ hdl->lock = &sensor->lock;
|
||
|
+
|
||
|
+ ctrls->auto_exposure = v4l2_ctrl_new_std_menu(hdl, ops,
|
||
|
+ V4L2_CID_EXPOSURE_AUTO,
|
||
|
+ V4L2_EXPOSURE_MANUAL, 0,
|
||
|
+ V4L2_EXPOSURE_AUTO);
|
||
|
+ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_EXPOSURE,
|
||
|
+ 1, HM5065_SENSOR_HEIGHT, 1, 30);
|
||
|
+ ctrls->d_gain = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_DIGITAL_GAIN,
|
||
|
+ 1000, 4000, 1, 1000);
|
||
|
+
|
||
|
+ ctrls->a_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN,
|
||
|
+ 0, 0xf4, 1, 0);
|
||
|
+
|
||
|
+ ctrls->metering =
|
||
|
+ v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_EXPOSURE_METERING,
|
||
|
+ V4L2_EXPOSURE_METERING_CENTER_WEIGHTED,
|
||
|
+ 0, V4L2_EXPOSURE_METERING_AVERAGE);
|
||
|
+ ctrls->exposure_bias =
|
||
|
+ v4l2_ctrl_new_int_menu(hdl, ops,
|
||
|
+ V4L2_CID_AUTO_EXPOSURE_BIAS,
|
||
|
+ ARRAY_SIZE(ae_bias_menu_values) - 1,
|
||
|
+ AE_BIAS_MENU_DEFAULT_VALUE_INDEX,
|
||
|
+ ae_bias_menu_values);
|
||
|
+
|
||
|
+ for (i = 0; i < ARRAY_SIZE(hm5065_wb_opts); i++) {
|
||
|
+ if (wb_max < hm5065_wb_opts[i][0])
|
||
|
+ wb_max = hm5065_wb_opts[i][0];
|
||
|
+ wb_mask |= BIT(hm5065_wb_opts[i][0]);
|
||
|
+ }
|
||
|
+
|
||
|
+ ctrls->wb = v4l2_ctrl_new_std_menu(hdl, ops,
|
||
|
+ V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
|
||
|
+ wb_max, ~wb_mask, V4L2_WHITE_BALANCE_AUTO);
|
||
|
+
|
||
|
+ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE,
|
||
|
+ 0, 4000, 1, 1000);
|
||
|
+ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE,
|
||
|
+ 0, 4000, 1, 1000);
|
||
|
+
|
||
|
+ ctrls->gamma = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA,
|
||
|
+ 0, 31, 1, 20);
|
||
|
+
|
||
|
+ ctrls->colorfx =
|
||
|
+ v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, 15,
|
||
|
+ ~(BIT(V4L2_COLORFX_NONE) |
|
||
|
+ BIT(V4L2_COLORFX_NEGATIVE) |
|
||
|
+ BIT(V4L2_COLORFX_SOLARIZATION) |
|
||
|
+ BIT(V4L2_COLORFX_SKETCH) |
|
||
|
+ BIT(V4L2_COLORFX_SEPIA) |
|
||
|
+ BIT(V4L2_COLORFX_ANTIQUE) |
|
||
|
+ BIT(V4L2_COLORFX_AQUA) |
|
||
|
+ BIT(V4L2_COLORFX_BW)),
|
||
|
+ V4L2_COLORFX_NONE);
|
||
|
+
|
||
|
+ ctrls->pl_freq =
|
||
|
+ v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY,
|
||
|
+ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0,
|
||
|
+ V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
|
||
|
+
|
||
|
+ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
|
||
|
+ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||
|
+
|
||
|
+ ctrls->focus_auto = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_AUTO,
|
||
|
+ 0, 1, 1, 1);
|
||
|
+
|
||
|
+ ctrls->af_start = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_FOCUS_START,
|
||
|
+ 0, 1, 1, 0);
|
||
|
+
|
||
|
+ ctrls->af_stop = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_FOCUS_STOP,
|
||
|
+ 0, 1, 1, 0);
|
||
|
+
|
||
|
+ ctrls->af_status = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_AUTO_FOCUS_STATUS, 0,
|
||
|
+ (V4L2_AUTO_FOCUS_STATUS_BUSY |
|
||
|
+ V4L2_AUTO_FOCUS_STATUS_REACHED |
|
||
|
+ V4L2_AUTO_FOCUS_STATUS_FAILED),
|
||
|
+ 0, V4L2_AUTO_FOCUS_STATUS_IDLE);
|
||
|
+
|
||
|
+ ctrls->af_distance =
|
||
|
+ v4l2_ctrl_new_std_menu(hdl, ops,
|
||
|
+ V4L2_CID_AUTO_FOCUS_RANGE,
|
||
|
+ V4L2_AUTO_FOCUS_RANGE_MACRO,
|
||
|
+ ~(BIT(V4L2_AUTO_FOCUS_RANGE_AUTO) |
|
||
|
+ BIT(V4L2_AUTO_FOCUS_RANGE_INFINITY) |
|
||
|
+ BIT(V4L2_AUTO_FOCUS_RANGE_MACRO)),
|
||
|
+ V4L2_AUTO_FOCUS_RANGE_AUTO);
|
||
|
+
|
||
|
+ ctrls->focus_relative = v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_FOCUS_RELATIVE,
|
||
|
+ -100, 100, 1, 0);
|
||
|
+
|
||
|
+ ctrls->brightness = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS,
|
||
|
+ 0, 200, 1, 90);
|
||
|
+ ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION,
|
||
|
+ 0, 200, 1, 110);
|
||
|
+ ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST,
|
||
|
+ 0, 200, 1, 108);
|
||
|
+
|
||
|
+ ctrls->aaa_lock = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_3A_LOCK,
|
||
|
+ 0, 0x7, 0, 0);
|
||
|
+
|
||
|
+ ctrls->test_pattern =
|
||
|
+ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
|
||
|
+ ARRAY_SIZE(test_pattern_menu) - 1,
|
||
|
+ 0, 0, test_pattern_menu);
|
||
|
+ for (i = 0; i < ARRAY_SIZE(ctrls->test_data); i++)
|
||
|
+ ctrls->test_data[i] =
|
||
|
+ v4l2_ctrl_new_std(hdl, ops,
|
||
|
+ V4L2_CID_TEST_PATTERN_RED + i,
|
||
|
+ 0, 1023, 1, 0);
|
||
|
+
|
||
|
+ if (hdl->error) {
|
||
|
+ ret = hdl->error;
|
||
|
+ goto free_ctrls;
|
||
|
+ }
|
||
|
+
|
||
|
+ ctrls->af_status->flags |= V4L2_CTRL_FLAG_VOLATILE |
|
||
|
+ V4L2_CTRL_FLAG_READ_ONLY;
|
||
|
+
|
||
|
+ v4l2_ctrl_auto_cluster(3, &ctrls->wb, V4L2_WHITE_BALANCE_MANUAL, false);
|
||
|
+ v4l2_ctrl_auto_cluster(4, &ctrls->auto_exposure, V4L2_EXPOSURE_MANUAL,
|
||
|
+ true);
|
||
|
+ v4l2_ctrl_cluster(6, &ctrls->focus_auto);
|
||
|
+
|
||
|
+ sensor->sd.ctrl_handler = hdl;
|
||
|
+ return 0;
|
||
|
+
|
||
|
+free_ctrls:
|
||
|
+ v4l2_ctrl_handler_free(hdl);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+/* {{{ Video ops */
|
||
|
+
|
||
|
+static int hm5065_g_frame_interval(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_frame_interval *fi)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+
|
||
|
+ if (fi->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+ fi->interval = sensor->frame_interval;
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_get_max_binning(int width, int height)
|
||
|
+{
|
||
|
+ if (width < HM5065_SENSOR_WIDTH / 4 &&
|
||
|
+ height < HM5065_SENSOR_HEIGHT / 4)
|
||
|
+ return 4;
|
||
|
+ else if (width < HM5065_SENSOR_WIDTH / 2 &&
|
||
|
+ height < HM5065_SENSOR_HEIGHT / 2)
|
||
|
+ return 2;
|
||
|
+
|
||
|
+ return 1;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_get_max_fps(int width, int height)
|
||
|
+{
|
||
|
+ int max_fps, bin_factor;
|
||
|
+
|
||
|
+ // more bining allows for faster readouts
|
||
|
+ bin_factor = hm5065_get_max_binning(width, height);
|
||
|
+ max_fps = 25000000 / (width * height * 2) * bin_factor;
|
||
|
+
|
||
|
+ return clamp(max_fps, 1, 60);
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_s_frame_interval(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_frame_interval *fi)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ int ret = 0, fps, max_fps;
|
||
|
+
|
||
|
+ if (fi->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+
|
||
|
+ max_fps = hm5065_get_max_fps(sensor->fmt.width, sensor->fmt.height);
|
||
|
+
|
||
|
+ /* user requested infinite frame rate */
|
||
|
+ if (fi->interval.numerator == 0)
|
||
|
+ fps = max_fps;
|
||
|
+ else
|
||
|
+ fps = DIV_ROUND_CLOSEST(fi->interval.denominator,
|
||
|
+ fi->interval.numerator);
|
||
|
+
|
||
|
+ fps = clamp(fps, 1, max_fps);
|
||
|
+
|
||
|
+ sensor->frame_interval.numerator = 1;
|
||
|
+ sensor->frame_interval.denominator = fps;
|
||
|
+ fi->interval = sensor->frame_interval;
|
||
|
+
|
||
|
+ if (sensor->streaming) {
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_DESIRED_FRAME_RATE_NUM,
|
||
|
+ fps);
|
||
|
+ if (ret)
|
||
|
+ goto err_unlock;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_DESIRED_FRAME_RATE_DEN,
|
||
|
+ 1);
|
||
|
+ }
|
||
|
+
|
||
|
+err_unlock:
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_setup_mode(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ const struct hm5065_pixfmt *pix_fmt;
|
||
|
+ u8 sensor_mode;
|
||
|
+ int ret, fps;
|
||
|
+
|
||
|
+ pix_fmt = hm5065_find_format(sensor->fmt.code);
|
||
|
+ if (!pix_fmt) {
|
||
|
+ dev_err(&sensor->i2c_client->dev,
|
||
|
+ "pixel format not supported %u\n", sensor->fmt.code);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_USER_COMMAND,
|
||
|
+ HM5065_REG_USER_COMMAND_POWEROFF);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ switch (hm5065_get_max_binning(sensor->fmt.width, sensor->fmt.height)) {
|
||
|
+ case 4:
|
||
|
+ sensor_mode = HM5065_REG_SENSOR_MODE_BINNING_4X4;
|
||
|
+ break;
|
||
|
+ case 2:
|
||
|
+ sensor_mode = HM5065_REG_SENSOR_MODE_BINNING_2X2;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ sensor_mode = HM5065_REG_SENSOR_MODE_FULLSIZE;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_P0_SENSOR_MODE, sensor_mode);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_P0_MANUAL_HSIZE,
|
||
|
+ sensor->fmt.width);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_P0_MANUAL_VSIZE,
|
||
|
+ sensor->fmt.height);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_P0_IMAGE_SIZE,
|
||
|
+ HM5065_REG_IMAGE_SIZE_MANUAL);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_P0_DATA_FORMAT,
|
||
|
+ pix_fmt->data_fmt);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_YCRCB_ORDER,
|
||
|
+ pix_fmt->ycbcr_order);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ /* without this, brightness, contrast and saturation will not work */
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_COLORSPACE, 9);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_BUS_DATA_FORMAT,
|
||
|
+ pix_fmt->fmt_setup);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ fps = hm5065_get_max_fps(sensor->fmt.width, sensor->fmt.height);
|
||
|
+ fps = clamp(fps, 1, (int)sensor->frame_interval.denominator);
|
||
|
+
|
||
|
+ ret = hm5065_write16(sensor, HM5065_REG_DESIRED_FRAME_RATE_NUM, fps);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_DESIRED_FRAME_RATE_DEN, 1);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_stream(struct hm5065_dev *sensor, int enable)
|
||
|
+{
|
||
|
+ return hm5065_write(sensor, HM5065_REG_USER_COMMAND, enable ?
|
||
|
+ HM5065_REG_USER_COMMAND_RUN :
|
||
|
+ HM5065_REG_USER_COMMAND_STOP);
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_s_stream(struct v4l2_subdev *sd, int enable)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+
|
||
|
+ if (sensor->streaming == !enable) {
|
||
|
+ if (enable && sensor->pending_mode_change) {
|
||
|
+ ret = hm5065_setup_mode(sensor);
|
||
|
+ if (ret)
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_set_stream(sensor, enable);
|
||
|
+ if (ret)
|
||
|
+ goto out;
|
||
|
+
|
||
|
+ if (enable && sensor->ctrls.focus_auto->cur.val) {
|
||
|
+ msleep(100);
|
||
|
+
|
||
|
+ /* checking error here is not super important */
|
||
|
+ hm5065_write(sensor, HM5065_REG_AF_MODE,
|
||
|
+ HM5065_REG_AF_MODE_CONTINUOUS);
|
||
|
+ }
|
||
|
+
|
||
|
+ sensor->streaming = !!enable;
|
||
|
+ }
|
||
|
+
|
||
|
+out:
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+/* {{{ Pad ops */
|
||
|
+
|
||
|
+static int hm5065_enum_mbus_code(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_state *sd_state,
|
||
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
||
|
+{
|
||
|
+ if (code->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+ if (code->index >= HM5065_NUM_FORMATS)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ code->code = hm5065_formats[code->index].code;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_enum_frame_size(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_state *sd_state,
|
||
|
+ struct v4l2_subdev_frame_size_enum *fse)
|
||
|
+{
|
||
|
+ if (fse->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+ if (fse->index != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ fse->min_width = HM5065_CAPTURE_WIDTH_MIN;
|
||
|
+ fse->min_height = HM5065_CAPTURE_HEIGHT_MIN;
|
||
|
+
|
||
|
+ fse->max_width = HM5065_SENSOR_WIDTH;
|
||
|
+ fse->max_height = HM5065_SENSOR_HEIGHT;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_enum_frame_interval(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_state *sd_state,
|
||
|
+ struct v4l2_subdev_frame_interval_enum
|
||
|
+ *fie)
|
||
|
+{
|
||
|
+ struct v4l2_fract tpf;
|
||
|
+ u32 max_fps, width, height;
|
||
|
+
|
||
|
+ if (fie->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ width = clamp(fie->width, HM5065_CAPTURE_WIDTH_MIN,
|
||
|
+ HM5065_SENSOR_WIDTH);
|
||
|
+ height = clamp(fie->height, HM5065_CAPTURE_HEIGHT_MIN,
|
||
|
+ HM5065_SENSOR_HEIGHT);
|
||
|
+
|
||
|
+ max_fps = hm5065_get_max_fps(width, height);
|
||
|
+
|
||
|
+ if (fie->index + 1 > max_fps)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ tpf.numerator = 1;
|
||
|
+ tpf.denominator = fie->index + 1;
|
||
|
+ fie->interval = tpf;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_get_fmt(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_state *sd_state,
|
||
|
+ struct v4l2_subdev_format *format)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ struct v4l2_mbus_framefmt *mf;
|
||
|
+
|
||
|
+ if (format->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
|
||
|
+ mf = v4l2_subdev_get_try_format(sd, sd_state, format->pad);
|
||
|
+ format->format = *mf;
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+ format->format = sensor->fmt;
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_fmt(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_subdev_state *sd_state,
|
||
|
+ struct v4l2_subdev_format *format)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ struct v4l2_mbus_framefmt *mf = &format->format;
|
||
|
+ const struct hm5065_pixfmt *pixfmt;
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ if (format->pad != 0)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ /* check if we support requested mbus fmt */
|
||
|
+ pixfmt = hm5065_find_format(mf->code);
|
||
|
+ if (!pixfmt)
|
||
|
+ pixfmt = &hm5065_formats[0];
|
||
|
+
|
||
|
+ mf->code = pixfmt->code;
|
||
|
+ mf->colorspace = pixfmt->colorspace;
|
||
|
+ mf->xfer_func = V4L2_XFER_FUNC_DEFAULT;
|
||
|
+ mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
|
||
|
+ mf->quantization = V4L2_QUANTIZATION_DEFAULT;
|
||
|
+ mf->field = V4L2_FIELD_NONE;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+
|
||
|
+ mf->width = clamp(mf->width, HM5065_CAPTURE_WIDTH_MIN,
|
||
|
+ HM5065_SENSOR_WIDTH);
|
||
|
+ mf->height = clamp(mf->height, HM5065_CAPTURE_HEIGHT_MIN,
|
||
|
+ HM5065_SENSOR_HEIGHT);
|
||
|
+
|
||
|
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
|
||
|
+ struct v4l2_mbus_framefmt *try_mf;
|
||
|
+
|
||
|
+ try_mf = v4l2_subdev_get_try_format(sd, sd_state, format->pad);
|
||
|
+ *try_mf = *mf;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (sensor->streaming) {
|
||
|
+ ret = -EBUSY;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ sensor->fmt = *mf;
|
||
|
+ sensor->pending_mode_change = true;
|
||
|
+out:
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+/* {{{ Core Ops */
|
||
|
+
|
||
|
+static void hm5065_chip_enable(struct hm5065_dev *sensor, bool enable)
|
||
|
+{
|
||
|
+ gpiod_set_value(sensor->enable_gpio, enable ? 1 : 0);
|
||
|
+ gpiod_set_value(sensor->reset_gpio, enable ? 0 : 1);
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_configure(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ int ret;
|
||
|
+ u16 device_id;
|
||
|
+ const struct hm5065_clk_lut *lut;
|
||
|
+ unsigned long xclk_freq;
|
||
|
+
|
||
|
+ ret = hm5065_read16(sensor, HM5065_REG_DEVICE_ID, &device_id);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (device_id != HM5065_REG_DEVICE_ID_VALUE) {
|
||
|
+ dev_err(&sensor->i2c_client->dev,
|
||
|
+ "unsupported device id: 0x%04x\n",
|
||
|
+ (unsigned int)device_id);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ xclk_freq = clk_get_rate(sensor->xclk);
|
||
|
+ lut = hm5065_find_clk_lut(xclk_freq);
|
||
|
+ if (!lut) {
|
||
|
+ dev_err(&sensor->i2c_client->dev,
|
||
|
+ "xclk frequency out of range: %lu Hz\n", xclk_freq);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_EXCLOCKLUT, lut->lut_id);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_load_firmware(sensor, HM5065_AF_FIRMWARE);
|
||
|
+ if (ret < 0)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (ret == 0) /* ret == 1 means firmware file missing */
|
||
|
+ mdelay(200);
|
||
|
+
|
||
|
+ ret = hm5065_load_firmware(sensor, HM5065_FIRMWARE_PARAMETERS);
|
||
|
+ if (ret < 0)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (sensor->ep.bus_type == V4L2_MBUS_BT656) {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_BUS_CONFIG,
|
||
|
+ HM5065_REG_BUS_CONFIG_BT656);
|
||
|
+ } else {
|
||
|
+ ret = hm5065_write(sensor, HM5065_REG_BUS_CONFIG,
|
||
|
+ HM5065_REG_BUS_CONFIG_PARALLEL_HH_VL);
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_set_power(struct hm5065_dev *sensor, bool on)
|
||
|
+{
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ if (on) {
|
||
|
+ ret = regulator_bulk_enable(HM5065_NUM_SUPPLIES,
|
||
|
+ sensor->supplies);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = clk_prepare_enable(sensor->xclk);
|
||
|
+ if (ret)
|
||
|
+ goto power_off;
|
||
|
+
|
||
|
+ ret = clk_set_rate(sensor->xclk, 24000000);
|
||
|
+ if (ret)
|
||
|
+ goto xclk_off;
|
||
|
+
|
||
|
+ usleep_range(1000, 2000);
|
||
|
+ hm5065_chip_enable(sensor, false);
|
||
|
+ usleep_range(1000, 2000);
|
||
|
+ hm5065_chip_enable(sensor, true);
|
||
|
+ usleep_range(50000, 70000);
|
||
|
+
|
||
|
+ ret = hm5065_configure(sensor);
|
||
|
+ if (ret)
|
||
|
+ goto xclk_off;
|
||
|
+
|
||
|
+ ret = hm5065_setup_mode(sensor);
|
||
|
+ if (ret)
|
||
|
+ goto xclk_off;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+xclk_off:
|
||
|
+ clk_disable_unprepare(sensor->xclk);
|
||
|
+power_off:
|
||
|
+ hm5065_chip_enable(sensor, false);
|
||
|
+ regulator_bulk_disable(HM5065_NUM_SUPPLIES, sensor->supplies);
|
||
|
+ msleep(100);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_s_power(struct v4l2_subdev *sd, int on)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ bool power_up, power_down;
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+
|
||
|
+ power_up = on && !sensor->powered;
|
||
|
+ power_down = !on && sensor->powered;
|
||
|
+
|
||
|
+ if (power_up || power_down) {
|
||
|
+ ret = hm5065_set_power(sensor, power_up);
|
||
|
+ if (!ret)
|
||
|
+ sensor->powered = on;
|
||
|
+ }
|
||
|
+
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+
|
||
|
+ if (!ret && power_up) {
|
||
|
+ /* restore controls */
|
||
|
+ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
|
||
|
+ if (ret)
|
||
|
+ hm5065_s_power(sd, 0);
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||
|
+static int hm5065_g_register(struct v4l2_subdev *sd,
|
||
|
+ struct v4l2_dbg_register *reg)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ int ret;
|
||
|
+ u8 val = 0;
|
||
|
+
|
||
|
+ if (reg->reg > 0xffff)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ reg->size = 1;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+ ret = hm5065_read(sensor, reg->reg, &val);
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+ if (ret)
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ reg->val = val;
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int hm5065_s_register(struct v4l2_subdev *sd,
|
||
|
+ const struct v4l2_dbg_register *reg)
|
||
|
+{
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (reg->reg > 0xffff || reg->val > 0xff)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ mutex_lock(&sensor->lock);
|
||
|
+ ret = hm5065_write(sensor, reg->reg, reg->val);
|
||
|
+ mutex_unlock(&sensor->lock);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+#endif
|
||
|
+
|
||
|
+/* }}} */
|
||
|
+
|
||
|
+static const struct v4l2_subdev_core_ops hm5065_core_ops = {
|
||
|
+ .s_power = hm5065_s_power,
|
||
|
+#ifdef CONFIG_VIDEO_ADV_DEBUG
|
||
|
+ .g_register = hm5065_g_register,
|
||
|
+ .s_register = hm5065_s_register,
|
||
|
+#endif
|
||
|
+};
|
||
|
+
|
||
|
+static const struct v4l2_subdev_pad_ops hm5065_pad_ops = {
|
||
|
+ .enum_mbus_code = hm5065_enum_mbus_code,
|
||
|
+ .enum_frame_size = hm5065_enum_frame_size,
|
||
|
+ .enum_frame_interval = hm5065_enum_frame_interval,
|
||
|
+ .get_fmt = hm5065_get_fmt,
|
||
|
+ .set_fmt = hm5065_set_fmt,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct v4l2_subdev_video_ops hm5065_video_ops = {
|
||
|
+ .g_frame_interval = hm5065_g_frame_interval,
|
||
|
+ .s_frame_interval = hm5065_s_frame_interval,
|
||
|
+ .s_stream = hm5065_s_stream,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct v4l2_subdev_ops hm5065_subdev_ops = {
|
||
|
+ .core = &hm5065_core_ops,
|
||
|
+ .pad = &hm5065_pad_ops,
|
||
|
+ .video = &hm5065_video_ops,
|
||
|
+};
|
||
|
+
|
||
|
+static int hm5065_get_regulators(struct hm5065_dev *sensor)
|
||
|
+{
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < HM5065_NUM_SUPPLIES; i++)
|
||
|
+ sensor->supplies[i].supply = hm5065_supply_name[i];
|
||
|
+
|
||
|
+ return devm_regulator_bulk_get(&sensor->i2c_client->dev,
|
||
|
+ HM5065_NUM_SUPPLIES,
|
||
|
+ sensor->supplies);
|
||
|
+}
|
||
|
+
|
||
|
+#define HM5065_PARALLEL_SUPPORT_FLAGS \
|
||
|
+ (V4L2_MBUS_HSYNC_ACTIVE_LOW | V4L2_MBUS_VSYNC_ACTIVE_HIGH | \
|
||
|
+ V4L2_MBUS_PCLK_SAMPLE_FALLING | V4L2_MBUS_DATA_ACTIVE_HIGH)
|
||
|
+
|
||
|
+static int hm5065_probe(struct i2c_client *client)
|
||
|
+{
|
||
|
+ struct device *dev = &client->dev;
|
||
|
+ struct fwnode_handle *endpoint;
|
||
|
+ struct hm5065_dev *sensor;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
|
||
|
+ if (!sensor)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ sensor->i2c_client = client;
|
||
|
+
|
||
|
+ sensor->fmt.code = hm5065_formats[0].code;
|
||
|
+ sensor->fmt.width = 1280;
|
||
|
+ sensor->fmt.height = 720;
|
||
|
+ sensor->fmt.field = V4L2_FIELD_NONE;
|
||
|
+ sensor->frame_interval.numerator = 1;
|
||
|
+ sensor->frame_interval.denominator = 15;
|
||
|
+ sensor->pending_mode_change = true;
|
||
|
+
|
||
|
+ endpoint = fwnode_graph_get_next_endpoint(
|
||
|
+ of_fwnode_handle(client->dev.of_node), NULL);
|
||
|
+ if (!endpoint) {
|
||
|
+ dev_err(dev, "endpoint node not found\n");
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
|
||
|
+ fwnode_handle_put(endpoint);
|
||
|
+ if (ret) {
|
||
|
+ dev_err(dev, "could not parse endpoint\n");
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * We don't know how to configure the camera for any other parallel
|
||
|
+ * mode, yet.
|
||
|
+ */
|
||
|
+ if (sensor->ep.bus_type != V4L2_MBUS_BT656 &&
|
||
|
+ !(sensor->ep.bus_type == V4L2_MBUS_PARALLEL &&
|
||
|
+ (sensor->ep.bus.parallel.flags & HM5065_PARALLEL_SUPPORT_FLAGS) ==
|
||
|
+ HM5065_PARALLEL_SUPPORT_FLAGS)) {
|
||
|
+ dev_err(dev, "unsupported bus configuration %d/%08x\n",
|
||
|
+ sensor->ep.bus_type, sensor->ep.bus.parallel.flags);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* get system clock (xclk) */
|
||
|
+ sensor->xclk = devm_clk_get(dev, "xclk");
|
||
|
+ if (IS_ERR(sensor->xclk)) {
|
||
|
+ dev_err(dev, "failed to get xclk\n");
|
||
|
+ return PTR_ERR(sensor->xclk);
|
||
|
+ }
|
||
|
+
|
||
|
+ sensor->enable_gpio = devm_gpiod_get_optional(dev, "enable",
|
||
|
+ GPIOD_OUT_LOW);
|
||
|
+ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
||
|
+ GPIOD_OUT_HIGH);
|
||
|
+
|
||
|
+ if (!sensor->enable_gpio && !sensor->reset_gpio) {
|
||
|
+ dev_err(dev,
|
||
|
+ "either chip enable or reset pin must be configured\n");
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ v4l2_i2c_subdev_init(&sensor->sd, client, &hm5065_subdev_ops);
|
||
|
+
|
||
|
+ sensor->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
|
||
|
+ sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
|
||
|
+ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
||
|
+ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = hm5065_get_regulators(sensor);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ mutex_init(&sensor->lock);
|
||
|
+
|
||
|
+ ret = hm5065_init_controls(sensor);
|
||
|
+ if (ret)
|
||
|
+ goto entity_cleanup;
|
||
|
+
|
||
|
+ ret = v4l2_async_register_subdev(&sensor->sd);
|
||
|
+ if (ret)
|
||
|
+ goto free_ctrls;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+
|
||
|
+free_ctrls:
|
||
|
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
|
||
|
+entity_cleanup:
|
||
|
+ mutex_destroy(&sensor->lock);
|
||
|
+ media_entity_cleanup(&sensor->sd.entity);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static void hm5065_remove(struct i2c_client *client)
|
||
|
+{
|
||
|
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||
|
+ struct hm5065_dev *sensor = to_hm5065_dev(sd);
|
||
|
+
|
||
|
+ v4l2_async_unregister_subdev(&sensor->sd);
|
||
|
+ mutex_destroy(&sensor->lock);
|
||
|
+ media_entity_cleanup(&sensor->sd.entity);
|
||
|
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
|
||
|
+}
|
||
|
+
|
||
|
+static const struct i2c_device_id hm5065_id[] = {
|
||
|
+ {"hm5065", 0},
|
||
|
+ { /* sentinel */ }
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(i2c, hm5065_id);
|
||
|
+
|
||
|
+static const struct of_device_id hm5065_dt_ids[] = {
|
||
|
+ { .compatible = "himax,hm5065" },
|
||
|
+ { /* sentinel */ }
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(of, hm5065_dt_ids);
|
||
|
+
|
||
|
+static struct i2c_driver hm5065_i2c_driver = {
|
||
|
+ .driver = {
|
||
|
+ .name = "hm5065",
|
||
|
+ .of_match_table = hm5065_dt_ids,
|
||
|
+ },
|
||
|
+ .id_table = hm5065_id,
|
||
|
+ .probe = hm5065_probe,
|
||
|
+ .remove = hm5065_remove,
|
||
|
+};
|
||
|
+
|
||
|
+module_i2c_driver(hm5065_i2c_driver);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Ondrej Jirman <megi@xff.cz>");
|
||
|
+MODULE_DESCRIPTION("HM5065 Camera Subdev Driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
--
|
||
|
2.34.1
|
||
|
|