Signed-off-by: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/media/video/m5mols/m5mols.h | 19 ++- drivers/media/video/m5mols/m5mols_controls.c | 160 +++++++++++++++++++++++++- drivers/media/video/m5mols/m5mols_core.c | 67 ++++++++++- drivers/media/video/m5mols/m5mols_reg.h | 5 + 4 files changed, 242 insertions(+), 9 deletions(-) diff --git a/drivers/media/video/m5mols/m5mols.h b/drivers/media/video/m5mols/m5mols.h index 213b15b..cd70b71 100644 --- a/drivers/media/video/m5mols/m5mols.h +++ b/drivers/media/video/m5mols/m5mols.h @@ -150,6 +150,12 @@ struct m5mols_version { u8 af; }; +struct m5mols_focus { + u8 mode; + u16 x; + u16 y; +}; + /** * struct m5mols_info - M-5MOLS driver data structure * @pdata: platform data @@ -204,6 +210,16 @@ struct m5mols_info { struct v4l2_ctrl *auto_wb; struct v4l2_ctrl *wb_preset; }; + struct { + /* continuous auto focus/auto focus cluster */ + 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 *af_area; + }; + struct m5mols_focus focus; struct v4l2_ctrl *colorfx; struct v4l2_ctrl *saturation; @@ -226,7 +242,8 @@ struct m5mols_info { int (*set_power)(struct device *dev, int on); }; -#define is_available_af(__info) (__info->ver.af) +#define m5mols_has_auto_focus(__info) (__info->ver.af) + #define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code) #define is_manufacturer(__info, __manufacturer) \ (__info->ver.str[0] == __manufacturer[0] && \ diff --git a/drivers/media/video/m5mols/m5mols_controls.c b/drivers/media/video/m5mols/m5mols_controls.c index 2c7beef..9ee089f 100644 --- a/drivers/media/video/m5mols/m5mols_controls.c +++ b/drivers/media/video/m5mols/m5mols_controls.c @@ -161,9 +161,9 @@ int m5mols_do_scenemode(struct m5mols_info *info, u8 mode) ret = m5mols_write(sd, MON_EDGE_EN, scenemode.edge_en); if (!ret) ret = m5mols_write(sd, MON_EDGE_LVL, scenemode.edge_lvl); - if (!ret && is_available_af(info)) + if (!ret && m5mols_has_auto_focus(info)) ret = m5mols_write(sd, AF_MODE, scenemode.af_range); - if (!ret && is_available_af(info)) + if (!ret && m5mols_has_auto_focus(info)) ret = m5mols_write(sd, FD_CTL, scenemode.fd_mode); if (!ret) ret = m5mols_write(sd, MON_TONE_CTL, scenemode.tone); @@ -221,11 +221,12 @@ int m5mols_lock_3a(struct m5mols_info *info, bool lock) ret = m5mols_lock_ae(info, lock); if (!ret) ret = m5mols_lock_awb(info, lock); + /* Don't need to handle unlocking AF */ - if (!ret && is_available_af(info) && lock) - ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + if (!m5mols_has_auto_focus(info) || ret || !lock) + return ret; - return ret; + return m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); } /* Set exposure/auto exposure cluster */ @@ -291,6 +292,85 @@ static int m5mols_set_white_balance(struct m5mols_info *info, int awb) return ret; } +static int m5mols_set_auto_focus(struct m5mols_info *info, int caf) +{ + struct v4l2_subdev *sd = &info->sd; + u8 af_mode = REG_AF_NORMAL; + int unlock_3a = 0, ret = 0; + + if (info->af_distance->is_new) { + switch (info->af_distance->val) { + case V4L2_AUTO_FOCUS_DISTANCE_MACRO: + af_mode = REG_AF_MACRO; + unlock_3a = 1; + break; + case V4L2_AUTO_FOCUS_DISTANCE_INFINITY: + af_mode = REG_AF_INIFINITY; + break; + case V4L2_AUTO_FOCUS_DISTANCE_NORMAL: + af_mode = REG_AF_NORMAL; + unlock_3a = 1; + break; + } + } + + if (unlock_3a) + m5mols_lock_3a(info, 0); + + if (info->af_area->is_new) { + if (info->af_area->val == V4L2_AUTO_FOCUS_AREA_SPOT) { + v4l2_ctrl_activate(info->af_distance, 0); + af_mode = REG_AF_TOUCH; + } else { + /* + * Activate the auto focus distance control only if + * auto focus area is set to V4L2_AUTO_FOCUS_AREA_ALL + */ + v4l2_ctrl_activate(info->af_distance, 1); + } + } + + pr_info("af_mode= %#x\n", af_mode); + + if (info->focus.mode != af_mode) { + ret = m5mols_write(sd, AF_MODE, af_mode); + if (ret < 0) + return ret; + info->focus.mode = af_mode; + } + + if (info->af_area->val == V4L2_AUTO_FOCUS_AREA_SPOT) { + ret = m5mols_write(sd, AF_TOUCH_POSX, info->focus.x); + if (ret < 0) + return ret; + ret = m5mols_write(sd, AF_TOUCH_POSY, info->focus.y); + if (ret < 0) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, "Focus position: x: %u, y: %u\n", + info->focus.x, info->focus.y); + } + + if (info->af_stop->is_new) { + ret = m5mols_write(sd, AF_EXECUTE, REG_AF_STOP); + if (ret < 0) + return ret; + v4l2_dbg(1, m5mols_debug, sd, "Auto focus stopped\n"); + } + + if (info->af_start->is_new || info->focus_auto->is_new) { + /* Start continuous or one-shot auto focusing */ + u8 af = caf ? REG_AF_EXE_CAF : REG_AF_EXE_AUTO; + ret = m5mols_write(sd, AF_EXECUTE, af); + v4l2_dbg(1, m5mols_debug, sd, "%s auto focus started\n", + caf ? "Continuous" : "One-shot"); + } + + v4l2_dbg(1, m5mols_debug, sd, "af_mode: %#x (%d)\n", af_mode, ret); + + return ret; +} + static int m5mols_set_saturation(struct m5mols_info *info, int val) { int ret = m5mols_write(&info->sd, MON_CHROMA_LVL, val); @@ -372,9 +452,10 @@ static int m5mols_g_volatile_ctrl(struct v4l2_ctrl *ctrl) { struct v4l2_subdev *sd = to_sd(ctrl); struct m5mols_info *info = to_m5mols(sd); - int ret = 0; + int val, ret = 0; u8 status; + pr_info("ctrl: %s\n", ctrl->name); if (!info->isp_ready) return -EBUSY; @@ -385,6 +466,32 @@ static int m5mols_g_volatile_ctrl(struct v4l2_ctrl *ctrl) if (ret == 0) ctrl->val = !status; break; + + case V4L2_CID_FOCUS_AUTO: + /* TODO: Also consider M-5MOLS status (mode) here ? */ + ret = m5mols_read_u8(sd, AF_STATUS, &status); + if (ret) + return ret; + switch (status) { + case REG_AF_IDLE: + val = V4L2_AUTO_FOCUS_STATUS_IDLE; + break; + case REG_AF_BUSY: + val = V4L2_AUTO_FOCUS_STATUS_BUSY; + break; + case REG_AF_SUCCESS: + val = V4L2_AUTO_FOCUS_STATUS_SUCCESS; + break; + case REG_AF_FAIL: + val = V4L2_AUTO_FOCUS_STATUS_FAIL; + break; + default: + v4l2_err(sd, "Unknown AF state\n"); + return 0; + } + + info->af_status->val = val; + v4l2_dbg(1, m5mols_debug, sd, "AF status: %#x\n", val); } return ret; @@ -410,6 +517,9 @@ static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) v4l2_dbg(1, m5mols_debug, sd, "%s: %s, val: %d, priv: %#x\n", __func__, ctrl->name, ctrl->val, (int)ctrl->priv); + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return -EINVAL; + if (ctrl_mode && ctrl_mode != info->mode) { ret = m5mols_mode(info, ctrl_mode); if (ret < 0) @@ -433,6 +543,10 @@ static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) ret = m5mols_set_white_balance(info, ctrl->val); break; + case V4L2_CID_FOCUS_AUTO: + ret = m5mols_set_auto_focus(info, ctrl->val); + break; + case V4L2_CID_SATURATION: ret = m5mols_set_saturation(info, ctrl->val); break; @@ -522,6 +636,28 @@ int m5mols_init_controls(struct v4l2_subdev *sd) V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); + /* Auto focus control cluster */ + info->focus_auto = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_FOCUS_AUTO, 0, 1, 1, 0); + + info->af_start = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_AUTO_FOCUS_START, 0, 1, 1, 0); + + info->af_stop = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_AUTO_FOCUS_STOP, 0, 1, 1, 0); + + info->af_status = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_AUTO_FOCUS_STATUS, 0, 0x07, 0, 0); + + info->af_distance = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_FOCUS_DISTANCE, + 2, 0, V4L2_AUTO_FOCUS_DISTANCE_NORMAL); + + info->af_area = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_FOCUS_AREA, 1, + ~0x03, /* whole frame and spot */ + V4L2_AUTO_FOCUS_AREA_ALL); + info->saturation = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, V4L2_CID_SATURATION, 1, 5, 1, 3); @@ -551,6 +687,18 @@ int m5mols_init_controls(struct v4l2_subdev *sd) v4l2_ctrl_auto_cluster(2, &info->auto_iso, 0, false); v4l2_ctrl_auto_cluster(2, &info->auto_wb, 0, false); + + info->af_status->flags |= V4L2_CTRL_FLAG_VOLATILE; + v4l2_ctrl_cluster(6, &info->focus_auto); + + if (!m5mols_has_auto_focus(info)) { + info->af_start->flags |= V4L2_CTRL_FLAG_DISABLED; + info->af_stop->flags |= V4L2_CTRL_FLAG_DISABLED; + info->af_status->flags |= V4L2_CTRL_FLAG_DISABLED; + info->af_distance->flags |= V4L2_CTRL_FLAG_DISABLED; + info->af_area->flags |= V4L2_CTRL_FLAG_DISABLED; + } + sd->ctrl_handler = &info->handle; return 0; diff --git a/drivers/media/video/m5mols/m5mols_core.c b/drivers/media/video/m5mols/m5mols_core.c index 2afe12b..7daf9ae 100644 --- a/drivers/media/video/m5mols/m5mols_core.c +++ b/drivers/media/video/m5mols/m5mols_core.c @@ -322,7 +322,7 @@ int m5mols_busy_wait(struct v4l2_subdev *sd, u32 reg, u32 value, u32 mask, int m5mols_enable_interrupt(struct v4l2_subdev *sd, u8 reg) { struct m5mols_info *info = to_m5mols(sd); - u8 mask = is_available_af(info) ? REG_INT_AF : 0; + u8 mask = m5mols_has_auto_focus(info) ? REG_INT_AF : 0; u8 dummy; int ret; @@ -469,7 +469,7 @@ static int m5mols_get_version(struct v4l2_subdev *sd) v4l2_info(sd, "Customer/Project\t[0x%02x/0x%02x]\n", info->ver.customer, info->ver.project); - if (!is_available_af(info)) + if (!m5mols_has_auto_focus(info)) v4l2_info(sd, "No support Auto Focus on this firmware\n"); return ret; @@ -587,6 +587,10 @@ static int m5mols_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, *sfmt = *format; info->resolution = resolution; info->res_type = type; + + /* Initialize focus spot to center of the frame */ + info->focus.x = format->width / 2; + info->focus.y = format->height / 2; } return 0; @@ -604,10 +608,65 @@ static int m5mols_enum_mbus_code(struct v4l2_subdev *sd, return 0; } +static int m5mols_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *mf = &info->ffmt[M5MOLS_RESTYPE_MONITOR]; + struct v4l2_rect *r = &sel->r; + + v4l2_dbg(1, m5mols_debug, sd, "%s: (%d,%d) %dx%d, %#x\n", __func__, + r->left, r->top, r->width, r->height, sel->target); + + if (sel->target != V4L2_SUBDEV_SEL_TGT_AUTO_FOCUS_ACTUAL) { + v4l2_err(sd, "Unsupported selection target: %#x", sel->target); + return -EINVAL; + } + + r->left = clamp_t(s32, r->left, 0, mf->width); + r->top = clamp_t(s32, r->top, 0, mf->height); + r->width = 0; + r->height = 0; + + info->focus.x = r->left; + info->focus.y = r->top; + + return 0; +} + +static int m5mols_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *mf = &info->ffmt[M5MOLS_RESTYPE_MONITOR]; + + switch (sel->target) { + case V4L2_SUBDEV_SEL_TGT_AUTO_FOCUS_ACTUAL: + sel->r.left = info->focus.x; + sel->r.top = info->focus.y; + break; + case V4L2_SUBDEV_SEL_TGT_AUTO_FOCUS_BOUNDS: + sel->r.width = mf->width; + sel->r.height = mf->height; + sel->r.left = 0; + sel->r.top = 0; + break; + default: + v4l2_err(sd, "Unsupported selection target: %#x", sel->target); + return -EINVAL; + } + + return 0; +} + static struct v4l2_subdev_pad_ops m5mols_pad_ops = { .enum_mbus_code = m5mols_enum_mbus_code, .get_fmt = m5mols_get_fmt, .set_fmt = m5mols_set_fmt, + .set_selection = m5mols_set_selection, + .get_selection = m5mols_get_selection, }; /** @@ -706,6 +765,7 @@ static int m5mols_sensor_power(struct m5mols_info *info, bool enable) gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity); info->power = 1; + info->focus.mode = -1; return ret; } @@ -817,6 +877,9 @@ static int m5mols_log_status(struct v4l2_subdev *sd) v4l2_ctrl_handler_log_status(&info->handle, sd->name); + pr_info("Auto focus position: x: %u, y: %u\n", + info->focus.x, info->focus.y); + return 0; } diff --git a/drivers/media/video/m5mols/m5mols_reg.h b/drivers/media/video/m5mols/m5mols_reg.h index ae4aced..f058c62 100644 --- a/drivers/media/video/m5mols/m5mols_reg.h +++ b/drivers/media/video/m5mols/m5mols_reg.h @@ -285,6 +285,8 @@ #define AF_MODE I2C_REG(CAT_LENS, 0x01, 1) #define REG_AF_NORMAL 0x00 /* Normal AF, one time */ #define REG_AF_MACRO 0x01 /* Macro AF, one time */ +#define REG_AF_TOUCH 0x04 +#define REG_AF_INIFINITY 0x06 #define REG_AF_POWEROFF 0x07 #define AF_EXECUTE I2C_REG(CAT_LENS, 0x02, 1) @@ -300,6 +302,9 @@ #define AF_VERSION I2C_REG(CAT_LENS, 0x0a, 1) +#define AF_TOUCH_POSX I2C_REG(CAT_LENS, 0x30, 2) +#define AF_TOUCH_POSY I2C_REG(CAT_LENS, 0x32, 2) + /* * Category B - CAPTURE Parameter */ -- 1.7.9.2 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html