RK3568 Camera 使用
RK3568 Sensor驱动开发移植(1)
RK3568 Sensor驱动开发移植(2)
RK3568 Sensor驱动开发移植(3)
v4l2_subdev_ops
v4l2_subdev_ops 回调函数是 Sensor 驱动中逻辑控制的核心。回调函数包括丰富的接口,具体可以查看kernel 代码 include/media/v4l2-subdev.h 。建议 Sensor 驱动至少包括如下回调函数:
static const struct v4l2_subdev_ops gc8034_subdev_ops = {
.core = &gc8034_core_ops,//Define core ops callbacks for subdevs
.video = &gc8034_video_ops, //Callbacks used when v4l device was opened in video mode.
.pad = &gc8034_pad_ops,//v4l2-subdev pad level operations
};
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
//V4L2 subdev internal ops
static const struct v4l2_subdev_internal_ops gc8034_internal_ops = {
.open = gc8034_open,
};
#endif
static const struct v4l2_subdev_core_ops gc8034_core_ops = {
.s_power = gc8034_s_power,
.ioctl = gc8034_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = gc8034_compat_ioctl32,
#endif
};
static const struct v4l2_subdev_video_ops gc8034_video_ops = {
.s_stream = gc8034_s_stream,
.g_frame_interval = gc8034_g_frame_interval,
.g_mbus_config = gc8034_g_mbus_config,
};
static const struct v4l2_subdev_pad_ops gc8034_pad_ops = {
.enum_mbus_code = gc8034_enum_mbus_code,
.enum_frame_size = gc8034_enum_frame_sizes,
.enum_frame_interval = gc8034_enum_frame_interval,
.get_fmt = gc8034_get_fmt,
.set_fmt = gc8034_set_fmt,
};
.s_power(),包括power on和power off(上电或者下电)。
//puts subdevice in power saving mode (on == 0) or normal operation mode (on == 1).
static int gc8034_s_power(struct v4l2_subdev *sd, int on)
{
struct gc8034 *gc8034 = to_gc8034(sd);
struct i2c_client *client = gc8034->client;
int ret = 0;
dev_info(&client->dev, "%s(%d) on(%d)\n", __func__, __LINE__, on);
mutex_lock(&gc8034->mutex);
/* If the power state is not modified - no work to do. */
if (gc8034->power_on == !!on)
goto unlock_and_return;
if (on) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
ret = gc8034_write_array(gc8034->client, gc8034_global_regs);
if (ret) {
v4l2_err(sd, "could not set init registers\n");
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
gc8034->power_on = true;
} else {
pm_runtime_put(&client->dev);
gc8034->power_on = false;
}
unlock_and_return:
mutex_unlock(&gc8034->mutex);
return ret;
}
ioctl
//called at the end of ioctl() syscall handler at the V4L2 core. used to provide support for private ioctls used on the driver.
static long gc8034_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct gc8034 *gc8034 = to_gc8034(sd);
long ret = 0;
u32 stream = 0;
switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
gc8034_get_module_inf(gc8034, (struct rkmodule_inf *)arg);
break;
case RKMODULE_AWB_CFG:
gc8034_set_module_inf(gc8034, (struct rkmodule_awb_cfg *)arg);
break;
case RKMODULE_SET_QUICK_STREAM:
stream = *((u32 *)arg);
if (stream) {
ret = gc8034_write_reg(gc8034->client,
GC8034_REG_SET_PAGE,
GC8034_SET_PAGE_ZERO);
if (2 == gc8034->lane_num) {
ret |= gc8034_write_reg(gc8034->client,
GC8034_REG_CTRL_MODE,
0x91);
} else {
ret |= gc8034_write_reg(gc8034->client,
GC8034_REG_CTRL_MODE,
GC8034_MODE_STREAMING);
}
} else {
ret = gc8034_write_reg(gc8034->client,
GC8034_REG_SET_PAGE,
GC8034_SET_PAGE_ZERO);
ret |= gc8034_write_reg(gc8034->client,
GC8034_REG_CTRL_MODE,
GC8034_MODE_SW_STANDBY);
}
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
目前使用了如下的私有 ioctl 实现模组信息的查询和 OTP 信息的查询设置。
.s_stream(),即 set stream。包括 stream on 和 stream off。一般在这里配置寄存器,使其输出图像
//used to notify the driver that a video stream will start or has stopped.
static int gc8034_s_stream(struct v4l2_subdev *sd, int on)
{
struct gc8034 *gc8034 = to_gc8034(sd);
struct i2c_client *client = gc8034->client;
int ret = 0;
dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on,
gc8034->cur_mode->width,
gc8034->cur_mode->height,
DIV_ROUND_CLOSEST(gc8034->cur_mode->max_fps.denominator,
gc8034->cur_mode->max_fps.numerator));
mutex_lock(&gc8034->mutex);
on = !!on;
if (on == gc8034->streaming)
goto unlock_and_return;
if (on) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
ret = __gc8034_start_stream(gc8034);
if (ret) {
v4l2_err(sd, "start stream failed while write regs\n");
pm_runtime_put(&client->dev);
goto unlock_and_return;
}
} else {
__gc8034_stop_stream(gc8034);
pm_runtime_put(&client->dev);
}
gc8034->streaming = on;
unlock_and_return:
mutex_unlock(&gc8034->mutex);
return ret;
}
.g_frame_interval , 获取 sensor 输出 fps
//callback for VIDIOC_SUBDEV_G_FRAME_INTERVAL() ioctl handler code.
static int gc8034_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct gc8034 *gc8034 = to_gc8034(sd);
const struct gc8034_mode *mode = gc8034->cur_mode;
mutex_lock(&gc8034->mutex);
fi->interval = mode->max_fps;
mutex_unlock(&gc8034->mutex);
return 0;
}
.g_mbus_config ,获取支持的媒体总线配置
//get supported mediabus configurations
static int gc8034_g_mbus_config(struct v4l2_subdev *sd,
struct v4l2_mbus_config *config)
{
struct gc8034 *sensor = to_gc8034(sd);
struct device *dev = &sensor->client->dev;
dev_info(dev, "%s(%d) enter!\n", __func__, __LINE__);
if (2 == sensor->lane_num) {
config->type = V4L2_MBUS_CSI2;
config->flags = V4L2_MBUS_CSI2_2_LANE |
V4L2_MBUS_CSI2_CHANNEL_0 |
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
} else if (4 == sensor->lane_num) {
config->type = V4L2_MBUS_CSI2;
config->flags = V4L2_MBUS_CSI2_4_LANE |
V4L2_MBUS_CSI2_CHANNEL_0 |
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
} else {
dev_err(&sensor->client->dev,
"unsupported lane_num(%d)\n", sensor->lane_num);
}
return 0;
}
.enum_mbus_code(),枚举 sensor 输出 bus format。
//callback for VIDIOC_SUBDEV_ENUM_MBUS_CODE() ioctl handler code.
static int gc8034_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index != 0)
return -EINVAL;
code->code = GC8034_MEDIA_BUS_FMT;
return 0;
}
.enum_frame_size(),枚举 sensor 支持输出的分辨率大小。
//callback for VIDIOC_SUBDEV_ENUM_FRAME_SIZE() ioctl handler code.
static int gc8034_enum_frame_sizes(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_size_enum *fse)
{
struct gc8034 *gc8034 = to_gc8034(sd);
if (fse->index >= gc8034->cfg_num)
return -EINVAL;
if (fse->code != GC8034_MEDIA_BUS_FMT)
return -EINVAL;
fse->min_width = supported_modes[fse->index].width;
fse->max_width = supported_modes[fse->index].width;
fse->max_height = supported_modes[fse->index].height;
fse->min_height = supported_modes[fse->index].height;
return 0;
}
.enum_frame_interval, 枚举帧间隔
//allback for VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL() ioctl handler code.
static int gc8034_enum_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_interval_enum *fie)
{
struct gc8034 *gc8034 = to_gc8034(sd);
if (fie->index >= gc8034->cfg_num)
return -EINVAL;
if (fie->code != GC8034_MEDIA_BUS_FMT)
return -EINVAL;
fie->width = supported_modes[fie->index].width;
fie->height = supported_modes[fie->index].height;
fie->interval = supported_modes[fie->index].max_fps;
return 0;
}
.get_fmt(),获取 sensor 输出格式。如果.get_fmt() 缺失,media-ctl 工具无法查看 sensor entity 当前配置的format
//callback for VIDIOC_SUBDEV_G_FMT() ioctl handler code.
static int gc8034_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct gc8034 *gc8034 = to_gc8034(sd);
const struct gc8034_mode *mode = gc8034->cur_mode;
mutex_lock(&gc8034->mutex);
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
#else
mutex_unlock(&gc8034->mutex);
return -ENOTTY;
#endif
} else {
fmt->format.width = mode->width;
fmt->format.height = mode->height;
fmt->format.code = GC8034_MEDIA_BUS_FMT;
fmt->format.field = V4L2_FIELD_NONE;
}
mutex_unlock(&gc8034->mutex);
return 0;
}
.set_fmt(),设置 sensor 输出格式
//callback for VIDIOC_SUBDEV_S_FMT() ioctl handler code.
static int gc8034_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct gc8034 *gc8034 = to_gc8034(sd);
const struct gc8034_mode *mode;
s64 h_blank, vblank_def;
mutex_lock(&gc8034->mutex);
mode = gc8034_find_best_fit(gc8034, fmt);
fmt->format.code = GC8034_MEDIA_BUS_FMT;
fmt->format.width = mode->width;
fmt->format.height = mode->height;
fmt->format.field = V4L2_FIELD_NONE;
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
#else
mutex_unlock(&gc8034->mutex);
return -ENOTTY;
#endif
} else {
gc8034->cur_mode = mode;
h_blank = mode->hts_def - mode->width;
__v4l2_ctrl_modify_range(gc8034->hblank, h_blank,
h_blank, 1, h_blank);
vblank_def = mode->vts_def - mode->height;
__v4l2_ctrl_modify_range(gc8034->vblank, vblank_def,
GC8034_VTS_MAX - mode->height,
1, vblank_def);
__v4l2_ctrl_s_ctrl(gc8034->link_freq,
link_freq_menu_items[0]);
}
mutex_unlock(&gc8034->mutex);
return 0;
}
.open(),Userspace通过在打开/dev/v4l-subdev节点时,会调用到该.open()函数。在上层需要单独对 sensor 设置 control 时.open()是必须实现的
//called when the subdev device node is opened by an application.
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static int gc8034_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct gc8034 *gc8034 = to_gc8034(sd);
struct v4l2_mbus_framefmt *try_fmt =
v4l2_subdev_get_try_format(sd, fh->pad, 0);
const struct gc8034_mode *def_mode = &supported_modes[0];
mutex_lock(&gc8034->mutex);
/* Initialize try_fmt */
try_fmt->width = def_mode->width;
try_fmt->height = def_mode->height;
try_fmt->code = GC8034_MEDIA_BUS_FMT;
try_fmt->field = V4L2_FIELD_NONE;
mutex_unlock(&gc8034->mutex);
/* No crop or compose */
return 0;
}
#endif