(仅作记录,rk3588平台)
RGB888这种数据格式可以被csi或dsi接收,驱动代码里默认是被dsi接收,但我们经常会遇到hdmi输出rgb888到mipi csi,所以需要在驱动中增加对csi接口的支持。
首先了解下回调函数 v4l2_subdev_call,参数介绍以及定义如下:
/**
* v4l2_subdev_call - call an operation of a v4l2_subdev.
*
* @sd: pointer to the &struct v4l2_subdev
* @o: name of the element at &struct v4l2_subdev_ops that contains @f.
* Each element there groups a set of callbacks functions.
* @f: callback function to be called.
* The callback functions are defined in groups, according to
* each element at &struct v4l2_subdev_ops.
* @args: arguments for @f.
*
* Example: err = v4l2_subdev_call(sd, video, s_std, norm);
*/
#define v4l2_subdev_call(sd, o, f, args...) \
({ \
struct v4l2_subdev *__sd = (sd); \
int __result; \
if (!__sd) \
__result = -ENODEV; \
else if (!(__sd->ops->o && __sd->ops->o->f)) \
__result = -ENOIOCTLCMD; \
else if (v4l2_subdev_call_wrappers.o && \
v4l2_subdev_call_wrappers.o->f) \
__result = v4l2_subdev_call_wrappers.o->f( \
__sd, ##args); \
else \
__result = __sd->ops->o->f(__sd, ##args); \
__result; \
})
对于rgb888是要使用csi接口还是dsi接口呢? 这就是根据实际来确认,我们调试hdmi需要输出rgb到csi,那么在hdmi驱动中需要在ioctl里添加case 用来判断dsi与csi;假设这个case命名为RKMODULE_RGB888_GET_CSI_DSI_INFO,则在 kernel-5.10/include/uapi/linux/rk-camera-module.h 参考其它模块进行添加;
#define RKMODULE_GET_CSI_DPHY_PARAM \
_IOWR('V', BASE_VIDIOC_PRIVATE + 32, struct rkmodule_csi_dphy_param)
/* 定义一个新的case */
#define RKMODULE_RGB888_GET_CSI_DSI_INFO \
_IOWR('V', BASE_VIDIOC_PRIVATE + 33, __u32)
那么在hdmi驱动中添加如下:
static long lt6911uxc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct lt6911uxc *lt6911uxc = to_state(sd);
long ret = 0;
switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
lt6911uxc_get_module_inf(lt6911uxc, (struct rkmodule_inf *)arg);
break;
// 添加一个case
case RKMODULE_RGB888_GET_CSI_DSI_INFO:
*(int *)arg = RKMODULE_CSI_INPUT; // 自己定义的
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return ret;
}
static long lt6911uxc_compat_ioctl32(struct v4l2_subdev *sd,
unsigned int cmd, unsigned long arg)
{
int *seq;
...........
switch (cmd) {
...........
case RKMODULE_RGB888_GET_CSI_DSI_INFO:
seq = kzalloc(sizeof(*seq), GFP_KERNEL);
if (!seq) {
ret = -ENOMEM;
return ret;
}
ret = lt6911uxc_ioctl(sd, cmd, seq);
if (!ret) {
ret = copy_to_user(up, seq, sizeof(*seq));
if (ret)
ret = -EFAULT;
}
kfree(seq);
break;
..........
return ret;
}
上面ioctl中的RKMODULE_CSI_INPUT也是自己在rk-camera-module.h文件中定义的,如下所示;
/*
* CSI/DSI input select IOCTL
*/
enum rkmodule_csi_dsi_seq {
RKMODULE_CSI_INPUT = 0,
RKMODULE_DSI_INPUT,
};
那么在cif驱动中如何调用新添加的这个case呢?首先要知道数据先经过mipi csi2然后再传入cif控制器的;因此在mipi csi2驱动里也需要增加对csi与dsi的判断,那首先就要定义个函数用来获得我们接入的sensor,如下
kernel-5.10\drivers\media\platform\rockchip\cif\mipi-csi2.c
static void get_remote_terminal_sensor(struct v4l2_subdev *sd,
struct v4l2_subdev **sensor_sd)
{
struct media_graph graph;
struct media_entity *entity = &sd->entity;
struct media_device *mdev = entity->graph_obj.mdev;
int ret;
/* Walk the graph to locate sensor nodes. */
mutex_lock(&mdev->graph_mutex);
ret = media_graph_walk_init(&graph, mdev);
if (ret) {
mutex_unlock(&mdev->graph_mutex);
*sensor_sd = NULL;
return;
}
media_graph_walk_start(&graph, entity);
while ((entity = media_graph_walk_next(&graph))) {
if (entity->function == MEDIA_ENT_F_CAM_SENSOR)
break;
}
mutex_unlock(&mdev->graph_mutex);
media_graph_walk_cleanup(&graph);
if (entity)
*sensor_sd = media_entity_to_v4l2_subdev(entity);
else
*sensor_sd = NULL;
}
看下v4l2_subdev结构体的定义,路径kernel/include/media/v4l2-subdev.h,上面那个函数主要是解析media拓扑结构,来获取sensor的entity。
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
struct list_head list;
struct module *owner;
bool owner_v4l2_dev;
u32 flags;
struct v4l2_device *v4l2_dev;
const struct v4l2_subdev_ops *ops;
const struct v4l2_subdev_internal_ops *internal_ops;
struct v4l2_ctrl_handler *ctrl_handler;
char name[V4L2_SUBDEV_NAME_SIZE];
u32 grp_id;
void *dev_priv;
void *host_priv;
struct video_device *devnode;
struct device *dev;
struct fwnode_handle *fwnode;
struct list_head async_list;
struct v4l2_async_subdev *asd;
struct v4l2_async_notifier *notifier;
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
};
获取终端的sensor之后,那就要在csi2_update_sensor函数中获取sensor的最新状态,也就是获取rgb888数据是接入csi还是dsi;
kernel-5.10\drivers\media\platform\rockchip\cif\mipi-csi2.c
static void csi2_update_sensor_info(struct csi2_dev *csi2)
{
.............
struct v4l2_subdev *terminal_sensor_sd = NULL;
.............
get_remote_terminal_sensor(&csi2->sd, &terminal_sensor_sd);
ret = v4l2_subdev_call(terminal_sensor_sd, core, ioctl,
RKMODULE_GET_CSI_DSI_INFO, &csi2->dsi_input_en);
if (ret) {
v4l2_dbg(1, csi2_debug, &csi2->sd, "get CSI/DSI sel failed, default csi!\n");
csi2->dsi_input_en = 0;
}
............
}
v4l2_subdev_call中的参数是怎么选择的,像core,ioctl;其实上面刚才有介绍,可以看下hdmi驱动就知道了
//在 core 这个ops中,调用ioctl
static const struct v4l2_subdev_core_ops lt6911uxc_core_ops = {
.interrupt_service_routine = lt6911uxc_isr,
.subscribe_event = lt6911uxc_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
.ioctl = lt6911uxc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = lt6911uxc_compat_ioctl32,
#endif
};
执行完回调函数后,就知道了rgb888是csi接收的,但在这还增加了判断;
kernel-5.10\drivers\media\platform\rockchip\cif\mipi-csi2.c
static int csi2_start(struct csi2_dev *csi2)
{
............
csi2_update_sensor_info(csi2);
if (csi2->dsi_input_en == RKMODULE_DSI_INPUT)
host_type = RK_DSI_RXHOST;
else
host_type = RK_CSI_RXHOST;
.............
}
kernel-5.10\drivers\media\platform\rockchip\cif\mipi-csi2.h
struct csi2_dev {
struct device *dev;
............
const char *dev_name;
int dsi_input_en; //增加这个变量
};
mipi csi修改完后,接下来就是cif驱动了,和mipi csi的修改一样,加入对csi和dsi的判断
kernel-5.10\drivers\media\platform\rockchip\cif\capture.c
//回调函数,更新sensor的状态,确定接口
int rkcif_update_sensor_info(struct rkcif_stream *stream)
{
..........
terminal_sensor = &stream->cifdev->terminal_sensor;
get_remote_terminal_sensor(stream, &terminal_sensor->sd);
..........
if (v4l2_subdev_call(terminal_sensor->sd, core, ioctl, RKMODULE_GET_CSI_DSI_INFO,
&terminal_sensor->dsi_input_en)) {
v4l2_dbg(1, rkcif_debug, &stream->cifdev->v4l2_dev,
"%s: get terminal %s CSI/DSI sel failed, default csi input!\n",
__func__, terminal_sensor->sd->name);
terminal_sensor->dsi_input_en = 0;
}
.........
}
static int rkcif_csi_channel_init(struct rkcif_stream *stream,
struct csi_channel_info *channel)
{
struct rkcif_device *dev = stream->cifdev;
..........
channel->cmd_mode_en = 0; /* default use DSI Video Mode */
channel->dsi_input = dev->terminal_sensor.dsi_input_en;
..........
channel->data_type = get_data_type(stream->cif_fmt_in->mbus_code,
channel->cmd_mode_en,
channel->dsi_input);
..........
}
static unsigned char get_data_type(u32 pixelformat, u8 cmd_mode_en, u8 dsi_input)
{
.............
case MEDIA_BUS_FMT_RGB888_1X24:
if (dsi_input) { //dsi_input 自己定义添加
if (cmd_mode_en) /* dsi command mode*/
return 0x39;
else /* dsi video mode */
return 0x3e;
} else {
return 0x24;
}
............
}
kernel-5.10\drivers\media\platform\rockchip\cif\dev.h
struct rkcif_sensor_info {
struct v4l2_subdev *sd;
struct v4l2_mbus_config mbus;
struct v4l2_subdev_frame_interval fi;
int lanes;
struct v4l2_rect raw_rect;
struct v4l2_subdev_selection selection;
int dsi_input_en; //自己定义的
};