This guide assumes that the developer has knowledge of the V4L2 API and has worked or is familiar with sensor drivers and their operation within the Linux kernel. This guide does not focus on the details of the sensor driver development that you want to port. It is assumed that you already have an existing driver for your sensor, before making the port.
The version of the ISP's was 6.6.36 Linux BSP. If a different version is used, it is the developer's responsibility to review the API documentation for the corresponding version, since there may be changes that affect what is indicated in this guide.
To port the camera sensor, the following steps must be taken as described in the following sections:
Define Sensor Attributes and Create Instances
The following three steps are already implemented in CamDevice and are included for reference only.
ISS Driver and ISP Media Server
/*****************************************************************************
* Each sensor driver needs to declare this struct for ISI load
*****************************************************************************/
IsiCamDrvConfig_t IsiCamDrvConfig = {
.CameraDriverID = 0x0000,
.pIsiHalQuerySensor = <SENSOR>_IsiHalQuerySensorIss,
.pfIsiGetSensorIss = <SENSOR>_IsiGetSensorIss,
};
Important Note: Modify the CameraDriverID according to the chip ID of your sensor. Apply this change to any Chip ID occurrence within the code. |
#define <SENSOR>_MIN_GAIN_STEP
(1.0f/16.0f)
Sensor Calibration Files
It is a requirement for using the ISP, to have a calibration file in XML format, specific to the sensor you are using and according to the resolution and working mode. To obtain the calibration files in XML format, there are 3 options:
VVCAM Driver Creation
The changes indicated below are based on the assumption that there is a functional sensor driver in its base form, and that it is compatible with the V4L2 API. From now on we focus on applying the changes suggested in the NXP documentation, specifically to establish the communication of the VVCAM Driver (kernel side) and the ISI Layer.
#include "vvsensor.h"
.
.
.
static struct vvcam_mode_info_s <sensor>_mode_info[] = {
{
.index = 0,
.width = ... ,
.height = ... ,
.hdr_mode = ... ,
.bit_width = ... ,
.data_compress.enable = ... ,
.bayer_pattern = ... ,
.ae_info = {
.
.
.
},
.mipi_info = {
.mipi_lane = ... ,
},
},
{
.index = 1,
.
.
.
},
};
#define client_to_<sensor>(client)\
container_of(i2c_get_clientdata(client), struct <sensor>, subdev)
long <sensor>_priv_ioctl(struct v4l2_subdev *subdev, unsigned int cmd, void *arg)
{
struct i2c_client *client = v4l2_get_subdevdata(subdev);
struct <sensor> *sensor = client_to_<sensor>(client);
struct vvcam_sccb_data_s reg;
uint32_t value = 0;
long ret = 0;
if(!sensor){
return -EINVAL;
}
switch (cmd) {
case VVSENSORIOC_G_CLK: {
ret = custom_implementation();
break;
}
case VIDIOC_QUERYCAP: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_QUERY: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_G_CHIP_ID: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_G_RESERVE_ID: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_G_SENSOR_MODE:{
ret = custom_implementation();
break;
}
case VVSENSORIOC_S_SENSOR_MODE: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_S_STREAM: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_WRITE_REG: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_READ_REG: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_S_EXP: {
ret = custom_implementation();
break;
}
case VVSENSORIOC_S_POWER:
case VVSENSORIOC_S_CLK:
case VVSENSORIOC_RESET:
case VVSENSORIOC_S_FPS:
case VVSENSORIOC_G_FPS:
case VVSENSORIOC_S_LONG_GAIN:
case VVSENSORIOC_S_GAIN:
case VVSENSORIOC_S_VSGAIN:
case VVSENSORIOC_S_LONG_EXP:
case VVSENSORIOC_S_VSEXP:
case VVSENSORIOC_S_WB:
case VVSENSORIOC_S_BLC:
case VVSENSORIOC_G_EXPAND_CURVE:
break;
default:
break;
}
return ret;
}
As you can see in the example, some cases are implemented but others are not. Developers are free to implement the features they consider necessary, as long as a minimum base of operation of the driver is guaranteed (query commands, read and write registers, among others). It is the developer's responsibility to implement each custom function, for each case or scenario that may arise when interacting with the sensor. In addition to what was shown previously, a link must be created to make the ioctl connection with the driver in question. Link your priv_ioctl function on the v4l2_subdev_core_ops struct, as in the example below:
static const struct v4l2_subdev_core_ops <sensor>_core_ops = {
.s_power = v4l2_s_power,
.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
// IOCTL link
.ioctl = <sensor>_priv_ioctl,
};
...
obj-m += <sensor>/
...
mxc-mipi-csi2.<X>: is_entity_link_setup, No remote pad found!
The link setup callback is required by the Media Controller when performing the linking process of the media entities involved in the capture process of the camera. Normally, this callback is triggered by the imx8-media-dev driver included as part of the Kernel sources. To make sure that the problem is not related to your sensor driver, verify the link setup callback is already created in the code, and if is not, you can add the following template:
/* Function needed by i.MX8MP */
static int <sensor>_camera_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
/* Return always zero */
return 0;
}
/* Add the link setup callback to the media entity operations struct */
static const struct media_entity_operations <sensor>_camera_subdev_media_ops = {
.link_setup = <sensor>_camera_link_setup,
};
/* Verify the initialization process of the media entity ops in the sensor driver's probe function*/
static int <sensor>_probe(struct i2c_client *client, ...)
{
/* Initialize subdev */
sd = &<sensor>->subdev;
sd->dev = &client->dev;
<sensor>->subdev.internal_ops = ...
<sensor>->subdev.flags |= ...
<sensor->subdev.entity.function = ...
/* Entity ops initialization */
<sensor->subdev.entity.ops = &<sensor>_camera_subdev_media_ops;
}
In most cases, adding the link setup function will solve the media controller issue, or at least it discards problems on the driver side.
Device Tree Modifications
On the Device Tree side, it is necessary to enable the ISP channels that will be used. Likewise, it is necessary to disable the ISI channels, which are normally the ones that connect to the MIPI_CSI2 ports to extract raw data from the sensor (in case the ISP is not used). A MIPI_CSI2 port can be mapped to either an ISI channel or an ISP channel, but not both simultaneously. In this guide, we focus on using the ISP, so any other custom configuration that you want to implement may vary from what is shown. In the code below, ISP channel 0 is enabled, and the connection is made to the port where the sensor is connected (mipi_csi_0).
&mipi_csi_0 {
status = "okay";
port@0 {
// Example endpoint to <sensor>_ep
mipi0_sensor_ep: endpoint@1 {
remote-endpoint = <&<sensor>_ep>;
};
};
};
&cameradev {
status = "okay";
};
&isi_0 {
status = "disabled";
};
&isi_1 {
status = "disabled";
};
&isp_0 {
status = "okay";
};
&isp_1 {
status = "disabled";
};
&dewarp {
status = "okay";
};
What is shown above does not represent a complete device tree file, is only a general skeleton of the points you should pay attention to when working with ISP channels. For simplicity, we omitted all the attributes that are normally defined when working with camera sensor drivers and their respective configurations in the i2c port of the hardware.
Note: Due to hardware restrictions when using ISP channels, it is recommended to use the isp_0 channel, when working with only one sensor. In case you need to use two sensors, you can enable both channels, taking into account the limitations regarding the output resolutions and the clock frequency when both channels are working simultaneously. What is not recommended is to use the isp_1 channel when working with a single sensor.
References