396 lines
11 KiB
Diff
396 lines
11 KiB
Diff
|
From 9f502a241f066a5b1047bc252bd3b559a5223dea Mon Sep 17 00:00:00 2001
|
||
|
From: Ondrej Jirman <megi@xff.cz>
|
||
|
Date: Sun, 7 Aug 2022 15:17:09 +0200
|
||
|
Subject: [PATCH 046/464] media: ov5640: Implement autofocus
|
||
|
|
||
|
The autofocus functionality needs a firmware blob loaded into the
|
||
|
internal microcontroller.
|
||
|
|
||
|
V4L2 doesn't have an api to control all autofocus functionality, but
|
||
|
this at least makes it possible to focus on the center of the sensor.
|
||
|
|
||
|
Signed-off-by: Martijn Braam <martijn@brixit.nl>
|
||
|
---
|
||
|
drivers/media/i2c/ov5640.c | 279 +++++++++++++++++++++++++++++++++++++
|
||
|
1 file changed, 279 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c
|
||
|
index fc885e350e9a..a2edc9c509c4 100644
|
||
|
--- a/drivers/media/i2c/ov5640.c
|
||
|
+++ b/drivers/media/i2c/ov5640.c
|
||
|
@@ -9,6 +9,7 @@
|
||
|
#include <linux/clkdev.h>
|
||
|
#include <linux/ctype.h>
|
||
|
#include <linux/delay.h>
|
||
|
+#include <linux/firmware.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/gpio/consumer.h>
|
||
|
#include <linux/i2c.h>
|
||
|
@@ -119,6 +120,38 @@
|
||
|
#define OV5640_REG_SDE_CTRL5 0x5585
|
||
|
#define OV5640_REG_AVG_READOUT 0x56a1
|
||
|
|
||
|
+/* autofocus registers */
|
||
|
+
|
||
|
+#define OV5640_REG_SYS_RESET00 0x3000
|
||
|
+#define OV5640_REG_SYS_RESET01 0x3001
|
||
|
+#define OV5640_REG_SYS_CLOCK_ENABLE00 0x3004
|
||
|
+#define OV5640_REG_SYS_CLOCK_ENABLE01 0x3005
|
||
|
+
|
||
|
+#define OV5640_REG_FW_CMD_MAIN 0x3022
|
||
|
+#define OV5640_REG_FW_CMD_ACK 0x3023
|
||
|
+#define OV5640_REG_FW_CMD_PARA0 0x3024
|
||
|
+#define OV5640_REG_FW_CMD_PARA1 0x3025
|
||
|
+#define OV5640_REG_FW_CMD_PARA2 0x3026
|
||
|
+#define OV5640_REG_FW_CMD_PARA3 0x3027
|
||
|
+#define OV5640_REG_FW_CMD_PARA4 0x3028
|
||
|
+#define OV5640_REG_FW_STATUS 0x3029
|
||
|
+
|
||
|
+#define OV5640_REG_VCM_CONTROL4 0x3606
|
||
|
+#define OV5640_REG_FIRMWARE_BASE 0x8000
|
||
|
+
|
||
|
+#define OV5640_FW_STATUS_S_FIRMWARE 0x7f
|
||
|
+#define OV5640_FW_STATUS_S_STARTUP 0x7e
|
||
|
+#define OV5640_FW_STATUS_S_IDLE 0x70
|
||
|
+#define OV5640_FW_STATUS_S_FOCUSING 0x00
|
||
|
+#define OV5640_FW_STATUS_S_FOCUSED 0x10
|
||
|
+
|
||
|
+#define OV5640_FW_CMD_TRIGGER_FOCUS 0x03
|
||
|
+#define OV5640_FW_CMD_CONTINUOUS_FOCUS 0x04
|
||
|
+#define OV5640_FW_CMD_GET_FOCUS_RESULT 0x07
|
||
|
+#define OV5640_FW_CMD_RELEASE_FOCUS 0x08
|
||
|
+#define OV5640_FW_CMD_ZONE_CONFIG 0x12
|
||
|
+#define OV5640_FW_CMD_DEFAULT_ZONES 0x80
|
||
|
+
|
||
|
enum ov5640_mode_id {
|
||
|
OV5640_MODE_QQVGA_160_120 = 0,
|
||
|
OV5640_MODE_QCIF_176_144,
|
||
|
@@ -423,6 +456,12 @@ struct ov5640_ctrls {
|
||
|
struct v4l2_ctrl *auto_gain;
|
||
|
struct v4l2_ctrl *gain;
|
||
|
};
|
||
|
+ struct {
|
||
|
+ struct v4l2_ctrl *focus_auto;
|
||
|
+ struct v4l2_ctrl *af_start;
|
||
|
+ struct v4l2_ctrl *af_stop;
|
||
|
+ struct v4l2_ctrl *af_status;
|
||
|
+ };
|
||
|
struct v4l2_ctrl *brightness;
|
||
|
struct v4l2_ctrl *light_freq;
|
||
|
struct v4l2_ctrl *saturation;
|
||
|
@@ -465,6 +504,8 @@ struct ov5640_dev {
|
||
|
|
||
|
bool pending_mode_change;
|
||
|
bool streaming;
|
||
|
+
|
||
|
+ bool af_initialized;
|
||
|
};
|
||
|
|
||
|
static inline struct ov5640_dev *to_ov5640_dev(struct v4l2_subdev *sd)
|
||
|
@@ -2497,6 +2538,118 @@ static void ov5640_powerup_sequence(struct ov5640_dev *sensor)
|
||
|
OV5640_REG_SYS_CTRL0_SW_PWDN);
|
||
|
}
|
||
|
|
||
|
+static int ov5640_copy_fw_to_device(struct ov5640_dev *sensor,
|
||
|
+ const struct firmware *fw)
|
||
|
+{
|
||
|
+ struct i2c_client *client = sensor->i2c_client;
|
||
|
+ const u8 *data = (const u8 *)fw->data;
|
||
|
+ u8 fw_status;
|
||
|
+ int i;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ // Putting MCU in reset state
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x20);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ // Write firmware
|
||
|
+ for (i = 0; i < fw->size / sizeof(u8); i++)
|
||
|
+ ov5640_write_reg(sensor,
|
||
|
+ OV5640_REG_FIRMWARE_BASE + i,
|
||
|
+ data[i]);
|
||
|
+
|
||
|
+ // Reset MCU state
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_MAIN, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_ACK, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA0, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA1, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA2, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA3, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_CMD_PARA4, 0x00);
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_FW_STATUS, 0x7f);
|
||
|
+
|
||
|
+ // Start AF MCU
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x00);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ dev_info(&client->dev, "firmware upload success\n");
|
||
|
+
|
||
|
+ // Wait for firmware to be ready
|
||
|
+ for (i = 0; i < 5; i++) {
|
||
|
+ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status);
|
||
|
+ if (fw_status == OV5640_FW_STATUS_S_IDLE) {
|
||
|
+ dev_info(&client->dev, "fw started after %d ms\n", i * 50);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+ msleep(50);
|
||
|
+ }
|
||
|
+ dev_err(&client->dev, "uploaded firmware didn't start, got to 0x%x, retrying...\n", fw_status);
|
||
|
+
|
||
|
+ // Putting MCU in reset state
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x20);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ // Start AF MCU
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_SYS_RESET00, 0x00);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ // Wait for firmware to be ready
|
||
|
+ for (i = 0; i < 5; i++) {
|
||
|
+ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status);
|
||
|
+ if (fw_status == OV5640_FW_STATUS_S_IDLE) {
|
||
|
+ dev_info(&client->dev, "fw started after %d ms\n", i * 50);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+ msleep(50);
|
||
|
+ }
|
||
|
+ dev_err(&client->dev, "uploaded firmware didn't start, got to 0x%x\n", fw_status);
|
||
|
+ return -ETIMEDOUT;
|
||
|
+}
|
||
|
+
|
||
|
+static int ov5640_af_init(struct ov5640_dev *sensor)
|
||
|
+{
|
||
|
+ struct i2c_client *client = sensor->i2c_client;
|
||
|
+ const char* fwname = "ov5640_af.bin";
|
||
|
+ const struct firmware *fw;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (sensor->af_initialized) {
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (firmware_request_nowarn(&fw, fwname, &client->dev) == 0) {
|
||
|
+ ret = ov5640_copy_fw_to_device(sensor, fw);
|
||
|
+ if (ret == 0)
|
||
|
+ sensor->af_initialized = 1;
|
||
|
+ } else {
|
||
|
+ dev_warn(&client->dev, "%s: no autofocus firmware available (%s)\n",
|
||
|
+ __func__, fwname);
|
||
|
+ ret = -1;
|
||
|
+ }
|
||
|
+ release_firmware(fw);
|
||
|
+
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ // Enable AF systems
|
||
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE00,
|
||
|
+ (BIT(6) | BIT(5)), (BIT(6) | BIT(5)));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE01,
|
||
|
+ BIT(6), BIT(6));
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ // Set lens focus driver on
|
||
|
+ ov5640_write_reg(sensor, OV5640_REG_VCM_CONTROL4, 0x3f);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
static int ov5640_set_power_on(struct ov5640_dev *sensor)
|
||
|
{
|
||
|
struct i2c_client *client = sensor->i2c_client;
|
||
|
@@ -2518,8 +2671,11 @@ static int ov5640_set_power_on(struct ov5640_dev *sensor)
|
||
|
goto xclk_off;
|
||
|
}
|
||
|
|
||
|
+ sensor->af_initialized = 0;
|
||
|
+
|
||
|
ov5640_powerup_sequence(sensor);
|
||
|
|
||
|
+
|
||
|
ret = ov5640_init_slave_id(sensor);
|
||
|
if (ret)
|
||
|
goto power_off;
|
||
|
@@ -3105,6 +3261,35 @@ static int ov5640_set_framefmt(struct ov5640_dev *sensor,
|
||
|
is_jpeg ? (BIT(5) | BIT(3)) : 0);
|
||
|
}
|
||
|
|
||
|
+static int ov5640_fw_command(struct ov5640_dev *sensor, int command)
|
||
|
+{
|
||
|
+ u8 fw_ack;
|
||
|
+ int i;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_FW_CMD_ACK, 0x01);
|
||
|
+ if(ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_FW_CMD_MAIN, command);
|
||
|
+ if(ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ for (i = 0; i < 100; i++) {
|
||
|
+ ret = ov5640_read_reg(sensor, OV5640_REG_FW_CMD_ACK, &fw_ack);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (fw_ack == 0){
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ msleep(50);
|
||
|
+ }
|
||
|
+ return -ETIMEDOUT;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
/*
|
||
|
* Sensor Controls.
|
||
|
*/
|
||
|
@@ -3221,6 +3406,41 @@ static int ov5640_set_ctrl_exposure(struct ov5640_dev *sensor,
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
+static int ov5640_set_ctrl_focus(struct ov5640_dev *sensor, int command)
|
||
|
+{
|
||
|
+ struct i2c_client *client = sensor->i2c_client;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = ov5640_af_init(sensor);
|
||
|
+ if (ret) {
|
||
|
+ dev_err(&client->dev, "%s: no autofocus firmware loaded\n",
|
||
|
+ __func__);
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (command == OV5640_FW_CMD_RELEASE_FOCUS) {
|
||
|
+ dev_dbg(&client->dev, "%s: Releasing autofocus\n",
|
||
|
+ __func__);
|
||
|
+ return ov5640_fw_command(sensor, OV5640_FW_CMD_RELEASE_FOCUS);
|
||
|
+ }
|
||
|
+
|
||
|
+ // Restart zone config
|
||
|
+ ret = ov5640_fw_command(sensor, OV5640_FW_CMD_ZONE_CONFIG);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ // Set default focus zones
|
||
|
+ ret = ov5640_fw_command(sensor, OV5640_FW_CMD_DEFAULT_ZONES);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ dev_dbg(&client->dev, "%s: Triggering autofocus\n",
|
||
|
+ __func__);
|
||
|
+
|
||
|
+ // Start focussing
|
||
|
+ return ov5640_fw_command(sensor, command);
|
||
|
+}
|
||
|
+
|
||
|
static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, bool auto_gain)
|
||
|
{
|
||
|
struct ov5640_ctrls *ctrls = &sensor->ctrls;
|
||
|
@@ -3336,6 +3556,32 @@ static int ov5640_set_ctrl_vblank(struct ov5640_dev *sensor, int value)
|
||
|
mode->height + value);
|
||
|
}
|
||
|
|
||
|
+static int ov5640_get_af_status(struct ov5640_dev *sensor)
|
||
|
+{
|
||
|
+ u8 fw_status;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = ov5640_read_reg(sensor, OV5640_REG_FW_STATUS, &fw_status);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ switch (fw_status) {
|
||
|
+ case OV5640_FW_STATUS_S_FIRMWARE:
|
||
|
+ case OV5640_FW_STATUS_S_STARTUP:
|
||
|
+ return V4L2_AUTO_FOCUS_STATUS_FAILED;
|
||
|
+ break;
|
||
|
+ case OV5640_FW_STATUS_S_IDLE:
|
||
|
+ return V4L2_AUTO_FOCUS_STATUS_IDLE;
|
||
|
+ break;
|
||
|
+ case OV5640_FW_STATUS_S_FOCUSED:
|
||
|
+ return V4L2_AUTO_FOCUS_STATUS_REACHED;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return V4L2_AUTO_FOCUS_STATUS_BUSY;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
{
|
||
|
struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
|
||
|
@@ -3360,6 +3606,12 @@ static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
return val;
|
||
|
sensor->ctrls.exposure->val = val;
|
||
|
break;
|
||
|
+ case V4L2_CID_FOCUS_AUTO:
|
||
|
+ val = ov5640_get_af_status(sensor);
|
||
|
+ if (val < 0)
|
||
|
+ return val;
|
||
|
+ sensor->ctrls.af_status->val = val;
|
||
|
+ break;
|
||
|
}
|
||
|
|
||
|
pm_runtime_mark_last_busy(&sensor->i2c_client->dev);
|
||
|
@@ -3409,6 +3661,18 @@ static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
case V4L2_CID_AUTO_WHITE_BALANCE:
|
||
|
ret = ov5640_set_ctrl_white_balance(sensor, ctrl->val);
|
||
|
break;
|
||
|
+ case V4L2_CID_FOCUS_AUTO:
|
||
|
+ if (ctrl->val)
|
||
|
+ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_CONTINUOUS_FOCUS);
|
||
|
+ else
|
||
|
+ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_RELEASE_FOCUS);
|
||
|
+ break;
|
||
|
+ case V4L2_CID_AUTO_FOCUS_START:
|
||
|
+ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_TRIGGER_FOCUS);
|
||
|
+ break;
|
||
|
+ case V4L2_CID_AUTO_FOCUS_STOP:
|
||
|
+ ret = ov5640_set_ctrl_focus(sensor, OV5640_FW_CMD_RELEASE_FOCUS);
|
||
|
+ break;
|
||
|
case V4L2_CID_HUE:
|
||
|
ret = ov5640_set_ctrl_hue(sensor, ctrl->val);
|
||
|
break;
|
||
|
@@ -3509,6 +3773,20 @@ static int ov5640_init_controls(struct ov5640_dev *sensor)
|
||
|
ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN,
|
||
|
0, 1023, 1, 0);
|
||
|
|
||
|
+ /* Autofocus */
|
||
|
+ ctrls->focus_auto = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_AUTO,
|
||
|
+ 0, 1, 1, 0);
|
||
|
+ 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->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION,
|
||
|
0, 255, 1, 64);
|
||
|
ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE,
|
||
|
@@ -3555,6 +3833,7 @@ static int ov5640_init_controls(struct ov5640_dev *sensor)
|
||
|
v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false);
|
||
|
v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true);
|
||
|
v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true);
|
||
|
+ v4l2_ctrl_cluster(4, &ctrls->focus_auto);
|
||
|
|
||
|
sensor->sd.ctrl_handler = hdl;
|
||
|
return 0;
|
||
|
--
|
||
|
2.34.1
|
||
|
|