關於 RN6752V1 這個晶片這裡就不做介紹了,看到這篇筆記的小夥伴應該都明白,雖然說 RN6752V1 晶片是 AHD 訊號的解碼晶片,但是也可以把晶片當做是一個 YUV 訊號的 MIPI 攝像頭,所以驅動的編寫和 MIPI 攝像頭無太大的區別。這裡主要是介紹具體的函數,關於 MIPI 驅動的框架程式看我之前的筆記:Linux MIPI 攝像頭驅動框架編寫(RN6752解碼晶片)
RN6752 支援 DVP 和 MIPI 訊號,這裡我主要是對 MIPI 訊號的使用,當然 DVP 通訊的操作也可以做參考。
寄存地設定
通過代理商提供的標頭檔案中可以獲取到相關暫存器的設定,如下所示:
static const struct sensor_register rn6752_fhd_1080P25_video[] = {
{ 0x19, 0x0A }, // 視訊格式檢測滯後控制
{ 0x81, 0x01 }, // 開啟視訊解碼器
{ 0xDF, 0xFE }, // 啟用HD格式
{ 0xF0, 0xC0 }, // 使能 FIFO 和 144 MHz 解碼器輸出
{ 0xA3, 0x04 }, // 啟用 HD 輸出
{ 0x88, 0x40 }, // 禁用 SCLK1 輸出
{ 0xF6, 0x40 }, // 禁用 SCLK3A 輸出
/* 切換到ch0(預設;可選) */
{ 0xFF, 0x00 }, // 暫存器集選擇
{ 0x33, 0x10 }, // 檢測中的視訊
{ 0x4A, 0xA8 }, // 檢測中的視訊
{ 0x00, 0x20 }, // internal use*
{ 0x06, 0x08 }, // internal use*
{ 0x07, 0x63 }, // 高清格式
{ 0x2A, 0x01 }, // 濾波器控制
{ 0x3A, 0x24 }, // 在SAV/EAV程式碼中插入通道ID
{ 0x3F, 0x10 }, // 通道ID
{ 0x4C, 0x37 }, // 均衡器
{ 0x4F, 0x03 }, // 同步控制
{ 0x50, 0x03 }, // 1080p解析度
{ 0x56, 0x02 }, // 144M 和 BT656模式
{ 0x5F, 0x44 }, // 消隱電平
{ 0x63, 0xF8 }, // 濾波器控制
{ 0x59, 0x00 }, // 擴充套件暫存器存取
{ 0x5A, 0x48 }, // 擴充套件暫存器的資料
{ 0x58, 0x01 }, // 啟用擴充套件暫存器寫入
{ 0x59, 0x33 }, // 擴充套件暫存器存取
{ 0x5A, 0x23 }, // 擴充套件暫存器的資料
{ 0x58, 0x01 }, // 啟用擴充套件暫存器寫入
{ 0x51, 0xF4 }, // 比例因子1
{ 0x52, 0x29 }, // 比例因子2
{ 0x53, 0x15 }, // 比例因子3
{ 0x5B, 0x01 }, // H-標度控制
{ 0x5E, 0x08 }, // 啟用H縮放控制
{ 0x6A, 0x87 }, // H-標度控制
{ 0x28, 0x92 }, // 剪裁
{ 0x03, 0x80 }, // 飽和
{ 0x04, 0x80 }, // 顏色
{ 0x05, 0x04 }, // 尖銳
{ 0x57, 0x23 }, // 黑色/白色拉伸
{ 0x68, 0x00 }, // coring
{ 0x37, 0x33 }, //
{ 0x61, 0x6C }, //
#ifdef USE_BLUE_SCREEN
{ 0x3A, 0x24 }, // AHD 斷開連結時,螢幕為藍色
#else
{ 0x3A, 0x2C }, // AHD 斷開連結時,螢幕為黑色
{ 0x3B, 0x00 }, //
{ 0x3C, 0x80 }, //
{ 0x3D, 0x80 }, //
#endif
{ 0x2E, 0x30 }, // 強制不播放視訊
{ 0x2E, 0x00 }, // 迴歸平常
/* mipi 連線 */
{ 0xFF, 0x09 }, // 切換到 mipi tx1
{ 0x00, 0x03 }, // enable bias
{ 0xFF, 0x08 }, // 切換到 mipi csi1
{ 0x04, 0x03 }, // csi1 和 tx1 重置
{ 0x6C, 0x11 }, // 禁用 ch 輸出,開啟 ch0
#ifdef USE_MIPI_4LANES
{ 0x06, 0x7C }, // mipi 4 線
#else
{ 0x06, 0x4C }, // mipi 2 線
#endif
{ 0x21, 0x01 }, // 啟用 hs 時鐘
{ 0x34, 0x06 }, //
{ 0x35, 0x0B }, //
{ 0x78, 0xC0 }, // ch0 的 Y/C 計數
{ 0x79, 0x03 }, // ch0 的 Y/C 計數
{ 0x6C, 0x01 }, // 啟用 ch 輸出
{ 0x04, 0x00 }, // csi1 和 tx1 重置完成
{ 0x20, 0xAA }, //
#ifdef USE_MIPI_NON_CONTINUOUS_CLOCK
{ 0x07, 0x05 }, // 啟用非連續時鐘
#else
{ 0x07, 0x04 }, // 啟用連續時鐘
#endif
{ 0xFF, 0x0A }, // 切換到 mipi csi3
{ 0x6C, 0x10 }, // 禁用 ch 輸出;關閉 ch0~3
{REG_NULL, 0x00},
};
注意: 其他格式的暫存器我這裡就不附上了,可以參考代理商提供的標頭檔案
將設定資訊存入幀列表中
static const struct rn6752_framesize rn6752_mipi_framesizes[] = {
{
.width = 1280,
.height = 720,
.max_fps = {
.numerator = 10000,
.denominator = 250000,
},
.regs = rn6752_fhd_720P25_video,
},
{
.width = 1280,
.height = 720,
.max_fps = {
.numerator = 10000,
.denominator = 300000,
},
.regs = rn6752_fhd_720P30_video,
},
{
.width = 1920,
.height = 1080,
.max_fps = {
.numerator = 10000,
.denominator = 250000,
},
.regs = rn6752_fhd_1080P25_video,
},
{
.width = 1920,
.height = 1080,
.max_fps = {
.numerator = 10000,
.denominator = 300000,
},
.regs = rn6752_fhd_1080P30_video,
},
{
.width = 1280,
.height = 960,
.max_fps = {
.numerator = 10000,
.denominator = 250000,
},
.regs = rn6752_fhd_960P25_video,
},
{
.width = 1280,
.height = 960,
.max_fps = {
.numerator = 10000,
.denominator = 300000,
},
.regs = rn6752_fhd_960P30_video,
}
};
設定預設幀
在 rn6752_probe 函數中存入預設支援的幀列表,如下所示
static void rn6752_get_default_format(struct rn6752 *rn6752,
struct v4l2_mbus_framefmt *format)
{
format->width = rn6752->framesize_cfg[2].width; /* 設定預設寬度 */
format->height = rn6752->framesize_cfg[2].height; /* 設定預設高度 */
format->colorspace = V4L2_COLORSPACE_SRGB; /* 設定預設色彩空間為標準的 sRGB 色彩空間 */
format->code = rn6752_formats[0].code; /* 設定預設編碼格式 */
format->field = V4L2_FIELD_NONE; /* 設定預設場模式 */
}
/* rn6752_mipi_framesizes 是 rn6752 mipi 通訊支援的所有幀格式 */
rn6752->framesize_cfg = rn6752_mipi_framesizes;
rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
/* 獲取攝像頭感測器支援的影象幀格式 */
rn6752_get_default_format(rn6752, &rn6752->format);
rn6752->frame_size = &rn6752->framesize_cfg[2]; /* 設定幀大小 */
rn6752->format.width = rn6752->framesize_cfg[2].width; /* 設定寬度 */
rn6752->format.height = rn6752->framesize_cfg[2].height; /* 設定高度 */
rn6752->fps = DIV_ROUND_CLOSEST(
rn6752->framesize_cfg[2].max_fps.denominator,
rn6752->framesize_cfg[2].max_fps.numerator); /* 設定最大幀速率 */
注意:
之前在 Media 子系統中提到過模組之間的關係檢視命令media-ctl -p -d /dev/mediaX
,通過命令可以得到驅動中的一些資訊,如下圖所示
Media 幀大小
Media 幀大小是在驅動初始化時,通過 rn6752_get_fmt 函數獲取的,程式如下
static int rn6752_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct i2c_client *client = v4l2_get_subdevdata(sd); /* 獲取i2c_client指標 */
struct rn6752 *rn6752 = to_rn6752(sd);
/* 使用dev_dbg列印紀錄檔,顯示當前函數進入 */
// dev_info(&client->dev, "%s enter\n", __func__);
/* 條件成立時,表示要獲取正在嘗試的格式 */
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
struct v4l2_mbus_framefmt *mf;
/* 獲取正在嘗試的格式 */
mf = v4l2_subdev_get_try_format(sd, cfg, 0);
mutex_lock(&rn6752->lock);
fmt->format = *mf;
mutex_unlock(&rn6752->lock);
return 0;
#else
return -ENOTTY;
#endif
}
/* 條件不成立時,表示要獲取當前的格式 */
mutex_lock(&rn6752->lock);
fmt->format = rn6752->format;
mutex_unlock(&rn6752->lock);
/* 使用dev_dbg列印紀錄檔,顯示當前格式的程式碼值、寬度和高度 */
dev_dbg(&client->dev, "%s: %x %dx%d\n", __func__, rn6752->format.code,
rn6752->format.width, rn6752->format.height);
return 0;
}
幀格式判斷
Media 裝置是通過 rn6752_enum_frame_sizes 和 rn6752_enum_frame_interval 函數列舉了幀大小和影格率,這兩個函數主要起到判斷的作用,確實當前影格率是否是驅動支援的,程式如下
static int rn6752_enum_frame_sizes(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_size_enum *fse)
{
struct rn6752 *rn6752 = to_rn6752(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
int i = ARRAY_SIZE(rn6752_formats);
printk(KERN_INFO
"rn6752_enum_frame_sizes................................................\n");
dev_dbg(&client->dev, "%s:\n", __func__);
if (fse->index >= rn6752->cfg_num)
return -EINVAL;
while (--i)
if (fse->code == rn6752_formats[i].code)
break;
fse->code = rn6752_formats[i].code;
fse->min_width = rn6752->framesize_cfg[fse->index].width;
fse->max_width = fse->min_width;
fse->max_height = rn6752->framesize_cfg[fse->index].height;
fse->min_height = fse->max_height;
return 0;
}
static int rn6752_enum_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_interval_enum *fie)
{
struct rn6752 *rn6752 = to_rn6752(sd);
printk(KERN_INFO
"rn6752_enum_frame_interval index: %d....................\n", fie->index );
/* 檢查傳入的 fie 結構體中的 index 欄位是否超出了 rn6752 所支援的幀間隔設定數量(cfg_num) */
if (fie->index >= rn6752->cfg_num)
return -EINVAL;
/* 檢查傳入的 fie 結構體中的 code 欄位是否與期望的媒體匯流排格式(MEDIA_BUS_FMT_UYVY8_2X8)匹配 */
if (fie->code != MEDIA_BUS_FMT_UYVY8_2X8)
return -EINVAL;
fie->width = rn6752->framesize_cfg[fie->index].width; /* 寬 */
fie->height = rn6752->framesize_cfg[fie->index].height; /* 高 */
fie->interval = rn6752->framesize_cfg[fie->index].max_fps; /* 最大影格率 */
return 0;
}
幀大小設定
可以通過 rn6752_set_fmt 函數設定幀的大小,程式如下
tatic int rn6752_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
int index = ARRAY_SIZE(rn6752_formats);
struct v4l2_mbus_framefmt *mf = &fmt->format;
const struct rn6752_framesize *size = NULL;
struct rn6752 *rn6752 = to_rn6752(sd);
printk(KERN_INFO
"rn6752_set_fmt................................................\n");
dev_info(&client->dev, "%s enter\n", __func__);
/* 根據傳入的引數調整幀大小和幀速率,並返回適合的幀大小和幀速率 */
__rn6752_try_frame_size_fps(rn6752, mf, &size, rn6752->fps);
/* 遍歷rn6752_formats陣列 */
while (--index >= 0)
if (rn6752_formats[index].code == mf->code)
break;
if (index < 0)
return -EINVAL;
/* 色彩空間為sRGB,場為無 */
mf->colorspace = V4L2_COLORSPACE_SRGB;
mf->code = rn6752_formats[index].code;
mf->field = V4L2_FIELD_NONE;
mutex_lock(&rn6752->lock);
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
mf = v4l2_subdev_get_try_format(
sd, cfg,
fmt->pad); /* 使用v4l2_subdev_get_try_format函數獲取正在嘗試的格式 */
*mf = fmt->format;
#else
return -ENOTTY;
#endif
} else {
if (rn6752->streaming) {
mutex_unlock(&rn6752->lock);
return -EBUSY;
}
/* 分別設定為獲取到的幀大小和傳入的格式 */
rn6752->frame_size = size;
rn6752->format = fmt->format;
}
mutex_unlock(&rn6752->lock);
return 0;
}
幀間隔獲取
static int rn6752_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct rn6752 *rn6752 = to_rn6752(sd);
printk(KERN_INFO
"rn6752_g_frame_interval................................................\n");
mutex_lock(&rn6752->lock);
fi->interval = rn6752->frame_size->max_fps;
mutex_unlock(&rn6752->lock);
return 0;
}
之前有提到過,RN6752 支援 DVP 和 MIPI 匯流排格式,所以可以在一個驅動中實現兩個功能,這裡我就是寫了 MIPI 的通訊方式,我目前對 DVP 也不瞭解,以後在補上。
剛好驅動中提供了兩個函數可以獲取驅動匯流排的格式,如下所示
獲取當前媒體匯流排設定的函數
static int rn6752_g_mbus_config(struct v4l2_subdev *sd,
struct v4l2_mbus_config *config)
{
printk(KERN_INFO
"rn6752_g_mbus_config................................................\n");
/* 匯流排型別是CSI-2 */
config->type = V4L2_MBUS_CSI2;
config->flags = V4L2_MBUS_CSI2_4_LANE | V4L2_MBUS_CSI2_CHANNEL_0 |
V4L2_MBUS_CSI2_CHANNEL_1 |
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
return 0;
}
列舉所有支援的媒體匯流排編碼和格式
static int rn6752_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *code)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
printk(KERN_INFO
"rn6752_enum_mbus_code................................................\n");
dev_dbg(&client->dev, "%s:\n", __func__);
if (code->index >= ARRAY_SIZE(rn6752_formats))
return -EINVAL;
code->code = rn6752_formats[code->index].code;
return 0;
}
攝像頭每次開啟和關閉時,都需要通過電源管理函數設定攝像頭電源
static int rn6752_power(struct v4l2_subdev *sd, int on)
{
struct rn6752 *rn6752 = to_rn6752(sd);
struct i2c_client *client = rn6752->client;
int ret = 0;
/* 使用dev_info列印紀錄檔,顯示當前函數和行號,並列印on引數的值 */
dev_dbg(&client->dev, "%s(%d) on(%d)\n", __func__, __LINE__, on);
mutex_lock(&rn6752->lock);
if (rn6752->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;
}
rn6752->power_on = true;
} else {
pm_runtime_put(&client->dev);
rn6752->power_on = false;
}
unlock_and_return:
mutex_unlock(&rn6752->lock);
return ret;
}
由於這裡我沒有實現太多的控制功能,所以只實現了必要的兩個控制,最主要的是復位時執行的 RKMODULE_SET_QUICK_STREAM 功能
static long rn6752_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct rn6752 *rn6752 = to_rn6752(sd);
// struct rkmodule_hdr_cfg *hdr;
long ret = 0;
u32 stream = 0;
// dev_dbg(KERN_INFO "rn6752_ioctl 0x%x..........\n", cmd);
switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
rn6752_get_module_info(rn6752, (struct rkmodule_inf *)arg);
break;
case RKMODULE_SET_QUICK_STREAM:
stream = *((u32 *)arg);
rn6752_set_streaming(rn6752, !!stream);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long rn6752_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd,
unsigned long arg)
{
void __user *up = compat_ptr(arg);
struct rkmodule_inf *inf;
struct rkmodule_awb_cfg *cfg;
long ret;
u32 stream = 0;
// dev_dbg(KERN_INFO "rn6752_compat_ioctl32..........\n");
switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
inf = kzalloc(sizeof(*inf), GFP_KERNEL);
if (!inf) {
ret = -ENOMEM;
return ret;
}
ret = rn6752_ioctl(sd, cmd, inf);
if (!ret)
ret = copy_to_user(up, inf, sizeof(*inf));
kfree(inf);
break;
case RKMODULE_AWB_CFG:
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg) {
ret = -ENOMEM;
return ret;
}
ret = copy_from_user(cfg, up, sizeof(*cfg));
if (!ret)
ret = rn6752_ioctl(sd, cmd, cfg);
kfree(cfg);
break;
case RKMODULE_SET_QUICK_STREAM:
ret = copy_from_user(&stream, up, sizeof(u32));
if (!ret)
ret = rn6752_ioctl(sd, cmd, &stream);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return 0;
}
#endif
整個驅動最重要的便是流控制函數,通過此函數完成了攝像頭的啟動和停止
static int rn6752_set_streaming(struct rn6752 *rn6752, int on)
{
struct i2c_client *client = rn6752->client;
int ret = 0;
dev_info(&client->dev, "%s: on: %d\n", __func__, on);
if (on)
{
ret = rn6752_write(client, 0x80, 0x31);
usleep_range(200, 500);
ret |= rn6752_write(client, 0x80, 0x30);
if (ret)
{
dev_err(&client->dev, "rn6752 soft reset failed\n");
return ret;
}
ret = rn6752_write_array(client, rn6752->frame_size->regs);
if (ret)
dev_err(&client->dev, "rn6752 start initialization failed\n");
}
else
{
ret = rn6752_write(client, 0x80, 0x00);
if (ret)
dev_err(&client->dev, "rn6752 soft standby failed\n");
}
return ret;
}
static int rn6752_s_stream(struct v4l2_subdev *sd, int on)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct rn6752 *rn6752 = to_rn6752(sd);
int ret = 0;
unsigned int fps;
/* 計算影格率和延遲時間 */
fps = DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator,
rn6752->frame_size->max_fps.numerator);
dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on,
rn6752->frame_size->width, rn6752->frame_size->height,
DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator,
rn6752->frame_size->max_fps.numerator));
mutex_lock(&rn6752->lock);
on = !!on;
if (rn6752->streaming == on)
goto unlock;
if (on) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0)
{
pm_runtime_put_noidle(&client->dev);
goto unlock;
}
}
rn6752->streaming = on;
ret = rn6752_set_streaming(rn6752, on);
if (ret)
rn6752->streaming = !on;
pm_runtime_put(&client->dev);
unlock:
mutex_unlock(&rn6752->lock);
return ret;
}
注意: 攝像頭驅動中並沒有影象接收之類的關係,而資料流操作函數主要的作用是對晶片進行初始化,使攝像頭進入工作模式。從上面的驅動程式可以看出,整個驅動並沒有其他特別的功能,就是一個 I2C 控制功能,所以攝像頭的驅動其實就是一個 I2C 驅動程式。
由於筆記內容有點多,這裡我就不附上完成的驅動程式了,其次是驅動程式也比較簡單,看完的小夥伴應該都能明白。主要的難度都在偵錯攝像頭驅動上面,我也折騰了很久,有需要的小夥變可以看我後面的筆記
本文來自部落格園,作者:澆築菜鳥,轉載請註明原文連結:https://www.cnblogs.com/jzcn/p/17825502.html
如本部落格的內容侵犯了你的權益,請與以下地址聯絡,本人獲知後,馬上刪除。同時本人深表歉意,並致以崇高的謝意! [email protected]