持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天。
记录自己对驱动的框架的大致分析 参考文档:
https://blog.csdn.net/yanbixing123/article/details/52299519
https://blog.csdn.net/yapingmcu/article/details/37817727
一、驱动大致分析
我们可以从头文件看出是否支持v4l2的框架
/*如果包含这些头文件,那么一般都是可以支持v4l2的框架的,在对应的代码中也会有相关的调用*/
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>
为了代码中的方便,驱动会将对应的寄存器通过宏定义
/*这些寄存器在对应的数据手册上有说明,什么功能,对应的值代表什么*/
#define OV5640_REG_SYS_RESET02 0x3002
#define OV5640_REG_SYS_CLOCK_ENABLE02 0x3006
#define OV5640_REG_SYS_CTRL0 0x3008
#define OV5640_REG_SYS_CTRL0_SW_PWDN 0x42
#define OV5640_REG_SYS_CTRL0_SW_PWUP 0x02
#define OV5640_REG_CHIP_ID 0x300a
#define OV5640_REG_IO_MIPI_CTRL00 0x300e
#define OV5640_REG_PAD_OUTPUT_ENABLE01 0x3017
#define OV5640_REG_PAD_OUTPUT_ENABLE02 0x3018
#define OV5640_REG_PAD_OUTPUT00 0x3019
#define OV5640_REG_SYSTEM_CONTROL1 0x302e
#define OV5640_REG_SC_PLL_CTRL0 0x3034
#define OV5640_REG_SC_PLL_CTRL1 0x3035
#define OV5640_REG_SC_PLL_CTRL2 0x3036
#define OV5640_REG_SC_PLL_CTRL3 0x3037
..........
因为同一款相机可能会有不同的像素值,为了兼容,所以就可以通过枚举一个结构体去把这款相机的所有模式都列举出来。
enum ov5640_mode_id {
OV5640_MODE_QQVGA_160_120 = 0,
OV5640_MODE_QCIF_176_144,
OV5640_MODE_QVGA_320_240,
OV5640_MODE_VGA_640_480,
OV5640_MODE_NTSC_720_480,
OV5640_MODE_PAL_720_576,
OV5640_MODE_XGA_1024_768,
OV5640_MODE_720P_1280_720,
OV5640_MODE_1080P_1920_1080,
OV5640_MODE_QSXGA_2592_1944,
OV5640_NUM_MODES,
};
不同的驱动可能写法不一,笔记ov8856的驱动中并没有枚举对应的模式,而是通过许多结构体数组将不同的模式的都列举出来,而且8865是通过配置寄存器来设置的:
static const struct ov8856_reg mipi_data_rate_720mbps[] = {
{0x0103, 0x01},
{0x0100, 0x00},
................
};
static const struct ov8856_reg mipi_data_rate_360mbps[] = {
{0x0103, 0x01},
{0x0100, 0x00},
{0x0302, 0x4b},
................
};
static const struct ov8856_reg mode_3280x2464_regs[] = {
{0x3000, 0x20},
{0x3003, 0x08},
{0x300e, 0x20},
.................
};
但是目都一样,都是为了方便后面的调用,在ov5640中还通过同样的方法定义了帧数和图像格式
enum ov5640_frame_rate {
OV5640_08_FPS = 0,
OV5640_15_FPS,
OV5640_30_FPS,
OV5640_60_FPS,
OV5640_NUM_FRAMERATES,
};
enum ov5640_format_mux {
OV5640_FMT_MUX_YUV422 = 0,
OV5640_FMT_MUX_RGB,
OV5640_FMT_MUX_DITHER,
OV5640_FMT_MUX_RAW_DPC,
OV5640_FMT_MUX_SNR_RAW,
OV5640_FMT_MUX_RAW_CIP,
};
通过结构体定义ov5640这个设备的属性,方便后期调用
struct ov5640_dev {
/*相关标准接口定义属性*/
struct i2c_client *i2c_client;
struct v4l2_subdev sd;
struct media_pad pad;
struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */
struct clk *xclk; /* system clock to OV5640 */
u32 xclk_freq;
/*供电相关的gpio*/
struct regulator_bulk_data supplies[OV5640_NUM_SUPPLIES];
struct gpio_desc *reset_gpio;
struct gpio_desc *pwdn_gpio;
bool upside_down;
/* lock to protect all members below */
struct mutex lock;
int power_count;
struct v4l2_mbus_framefmt fmt;
bool pending_fmt_change;
const struct ov5640_mode_info *current_mode;
const struct ov5640_mode_info *last_mode;
enum ov5640_frame_rate current_fr;
struct v4l2_fract frame_interval;
struct ov5640_ctrls ctrls;
u32 prev_sysclk, prev_hts;
u32 ae_low, ae_high, ae_target;
bool pending_mode_change;
bool streaming;
};
这里用来设定模块的初始化的配置
static const struct ov5640_mode_info ov5640_mode_init_data = {
0, SUBSAMPLING, 640, 1896, 480, 984,
ov5640_init_setting_30fps_VGA,
ARRAY_SIZE(ov5640_init_setting_30fps_VGA),
OV5640_30_FPS,
};
/*这里用数组来添加一些其余的接口,后面可以通过数组的方式在指定使用那些配置*/
static const struct ov5640_mode_info
ov5640_mode_data[OV5640_NUM_MODES] = {
{OV5640_MODE_QCIF_176_144, SUBSAMPLING,
176, 1896, 144, 984,
ov5640_setting_QCIF_176_144,
ARRAY_SIZE(ov5640_setting_QCIF_176_144),
OV5640_30_FPS},
.....
{OV5640_MODE_QSXGA_2592_1944, SCALING,
2592, 2844, 1944, 1968,
ov5640_setting_QSXGA_2592_1944,
ARRAY_SIZE(ov5640_setting_QSXGA_2592_1944),
OV5640_15_FPS},
};
i2c地址的保存
static int ov5640_init_slave_id(struct ov5640_dev *sensor)
{
/*i2c实例化, 将对应的sensor地址赋值给i2c_client的指针*/
struct i2c_client *client = sensor->i2c_client;
struct i2c_msg msg;
u8 buf[3];
int ret;
/*判断地址是否和定义的地址一样*/
/*#define OV5640_DEFAULT_SLAVE_ID 0x3c*/
if (client->addr == OV5640_DEFAULT_SLAVE_ID)
return 0;
/*如果是则直接返回,如果不是则将新的地址重新写到client的地址里面去*/
buf[0] = OV5640_REG_SLAVE_ID >> 8;
buf[1] = OV5640_REG_SLAVE_ID & 0xff;
buf[2] = client->addr << 1;
msg.addr = OV5640_DEFAULT_SLAVE_ID;
msg.flags = 0;
msg.buf = buf;
msg.len = sizeof(buf);
/*读取该地址上的值,判断是否存在*/
ret = i2c_transfer(client->adapter, &msg, 1); //0写 1读
if (ret < 0) {
dev_err(&client->dev, "%s: failed with %d\n", __func__, ret);
return ret;
}
return 0;
}
后面就是一些标准的读写接口,这些都是标准接口,通过后期进行一些逻辑判断
static int ov5640_write_reg(struct ov5640_dev *sensor, u16 reg, u8 val)
{
};
...........
static int ov5640_read_reg(struct ov5640_dev *sensor, u16 reg, u8 *val)
{
};
.............
static int ov5640_read_reg16(struct ov5640_dev *sensor, u16 reg, u16 *val)
{
};
.............
static int ov5640_write_reg16(struct ov5640_dev *sensor, u16 reg, u16 val)
{
};
设置ov5640的模式
static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor,
const struct ov5640_mode_info *mode)
{
int ret;
ret = ov5640_mod_reg(sensor, OV5640_REG_JPG_MODE_SELECT, 0x7, 0x3);
if (ret < 0)
return ret;
/*0x4602 VFIFO HSIZE 0x04 JPEG Output Width High Byte */
ret = ov5640_write_reg16(sensor, OV5640_REG_VFIFO_HSIZE, mode->hact);
if (ret < 0)
return ret;
/*0x4604 VFIFO VSIZE 0x03 JPEG Output Height High Byte*/
return ov5640_write_reg16(sensor, OV5640_REG_VFIFO_VSIZE, mode->vact);
}
/* download ov5640 settings to sensor through i2c */
static int ov5640_set_timings(struct ov5640_dev *sensor,
const struct ov5640_mode_info *mode)
{
int ret;
if (sensor->fmt.code == MEDIA_BUS_FMT_JPEG_1X8) {
ret = ov5640_set_jpeg_timings(sensor, mode);
if (ret < 0)
return ret;
}
ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact);
if (ret < 0)
return ret;
ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPVO, mode->vact);
if (ret < 0)
return ret;
ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_HTS, mode->htot);
if (ret < 0)
return ret;
return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, mode->vtot);
}
ov5640的增益设置
/*
*#define OV5640_REG_AEC_PK_MANUAL 0x3503
*#define OV5640_REG_AEC_PK_REAL_GAIN 0x350a
*To manually change gain, first set register bit 0x3503[1] to enable manual control, then change the values in
0x350A/0x350B for the manual gain. The OV5640 has a maximum of 64x gain
*这里说明如何去手动增益或则配置自动增益
*/
static int ov5640_get_gain(struct ov5640_dev *sensor)
{
u16 gain;
int ret;
ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, &gain);
if (ret)
return ret;
return gain & 0x3ff;
}
static int ov5640_set_gain(struct ov5640_dev *sensor, int gain)
{
return ov5640_write_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN,
(u16)gain & 0x3ff);
}
static int ov5640_set_autogain(struct ov5640_dev *sensor, bool on)
{
return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL,
BIT(1), on ? 0 : BIT(1));
}
这个函数的目的是获得ov5640的频闪 频闪是什么: 日常使用的普通光源如白炽灯、日光灯、石英灯等都是直接用220/50Hz交流电工作,每秒钟内正负半周各变化50次,因而导致灯光在1秒钟内发生100(50×2)次的闪烁,再加上市电电压的不稳定,灯光忽明忽暗,这样就产生了所谓的“频闪”。 考虑到频闪的周期性,在一个周期内,光源亮度的累积值,应该是大体一致的,所以,如果控制曝光的时间是频闪周期的整倍数,那么每一帧图像的亮度就大体是一致的了,这样就可以有效地抑制频闪对图像亮度的影响。 所以,在自动曝光的模式下,sensor会根据频闪的频率,调整曝光时间为其周期的整倍数。 因为各地的交流电的频率不同,所以有50Hz/60Hz之分。
static int ov5640_get_light_freq(struct ov5640_dev *sensor)
{
/* get banding filter value */
int ret, light_freq = 0;
u8 temp, temp1;
/*
*#define OV5640_REG_HZ5060_CTRL01 0x3c01
*读取寄存器值,判断是自动模式还是手动模式 如果都不是则返回一个错误的值
*/
ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL01, &temp);
if (ret)
return ret;
if (temp & 0x80) {
/* manual */
/* define OV5640_REG_HZ5060_CTRL00 0x3c00
* Bit[2]: Band50 default value
* 读取第二位,判断是第50HZ还是60HZ
*/
ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL00,
&temp1);
if (ret)
return ret;
if (temp1 & 0x04) {
/* 50Hz */
light_freq = 50;
} else {
/* 60Hz */
light_freq = 60;
}
} else {
/* auto */
/*#define OV5640_REG_SIGMADELTA_CTRL0C 0x3c0c
*Bit[0]: Band50/60
*读取第0位判断是50HZ还是60HZ
*/
ret = ov5640_read_reg(sensor, OV5640_REG_SIGMADELTA_CTRL0C,
&temp1);
if (ret)
return ret;
if (temp1 & 0x01) {
/* 50Hz */
light_freq = 50;
} else {
/* 60Hz */
}
}
return light_freq;
}
//TODO:添加注释查看值
下面是AEC(自动曝光控制)和 AGC(自动增益控制)
这个函数里面会读取曝光的最大时间(0x3A0F)和最小时间(0X3A10),0x3a1b存储的是图像从稳定状态切换到不稳定状态时曝光时间的最大值,0x3a1e存储的是图像从稳定状态切换到不稳定状态时曝光时间的最小值,
这时候需要理解另外一个寄存器:0x56a1,这个寄存器中保存的是目标图像的亮度平均值,这个寄存器是一个只读寄存器。
当0x56a1寄存器的值不在{0x3a1e,0x3a1b}这个区间之内时,AEC就调整它们,并且使他们位于{0x3a10,0x3a0f}这个区间。所以这个{0x3a1e,0x3a1b}这个区间称为稳定状态区间。 当0x56a1寄存器的值位于{0x3a1e,0x3a1b}这个区间的时候,就是指图像处于稳定状态。反之,则称为不稳定状态。
static int ov5640_set_ae_target(struct ov5640_dev *sensor, int target)
{
/* stable in high */
u32 fast_high, fast_low;
int ret;
sensor->ae_low = target * 23 / 25; /* 0.92 */
sensor->ae_high = target * 27 / 25; /* 1.08 */
fast_high = sensor->ae_high << 1;
if (fast_high > 255)
fast_high = 255;
fast_low = sensor->ae_low >> 1;
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0F, sensor->ae_high);
if (ret)
return ret;
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL10, sensor->ae_low);
if (ret)
return ret;
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1B, sensor->ae_high);
if (ret)
return ret;
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1E, sensor->ae_low);
if (ret)
return ret;
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL11, fast_high);
if (ret)
return ret;
return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1F, fast_low);
}
这个函数是设置摄像头的工频干扰(设置带状滤波器)一个50HZ的光源,电压曲线为正弦曲线,那能量曲线定性分析可以认为是取了绝对值的电压曲线。那就是能量做1/100秒的周期变化。那就要求曝光的时间必须是1/100秒的整数倍。如果没有把曝光时间调整到1/100秒的整数倍,就有可能会有每行的曝光值不一样,造成同一个image上有水波纹现象。CCD是整帧同时曝光,所以,工频干扰表现的就是图像有轻微的闪烁。产生的原理与CMOS sensor的原理相似。 首先通过OV5640_get_sysclk函数来获取系统的时钟,然后OV5640_get_HTS和OV5640_get_VTS函数分别获取ov5640的Horizontaltotalsize 和verticaltotal size,对于50Hz和60Hz,都有不同的计算方式.
static int ov5640_set_bandingfilter(struct ov5640_dev *sensor)
{
u32 band_step60, max_band60, band_step50, max_band50, prev_vts;
int ret;
/* read preview PCLK */
ret = ov5640_get_sysclk(sensor);
if (ret < 0)
return ret;
if (ret == 0)
return -EINVAL;
sensor->prev_sysclk = ret;
/* read preview HTS */
ret = ov5640_get_hts(sensor);
if (ret < 0)
return ret;
if (ret == 0)
return -EINVAL;
sensor->prev_hts = ret;
/* read preview VTS */
ret = ov5640_get_vts(sensor);
if (ret < 0)
return ret;
prev_vts = ret;
/* calculate banding filter */
/* 60Hz */
band_step60 = sensor->prev_sysclk * 100 / sensor->prev_hts * 100 / 120;
ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B60_STEP, band_step60);
if (ret)
return ret;
if (!band_step60)
return -EINVAL;
max_band60 = (int)((prev_vts - 4) / band_step60);
ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0D, max_band60);
if (ret)
return ret;
/* 50Hz */
band_step50 = sensor->prev_sysclk * 100 / sensor->prev_hts;
ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B50_STEP, band_step50);
if (ret)
return ret;
if (!band_step50)
return -EINVAL;
max_band50 = (int)((prev_vts - 4) / band_step50);
return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0E, max_band50);
}
通过ov5640_get_gain()函数读取寄存器值 OV5640_REG_AEC_PK_REAL_GAIN 通过ov5640_set_gain() 函数设置寄存器值 OV5640_REG_AEC_PK_REAL_GAIN
static int ov5640_get_gain(struct ov5640_dev *sensor)
{
u16 gain;
int ret;
ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, &gain);
if (ret)
return ret;
return gain & 0x3ff;
}
static int ov5640_set_gain(struct ov5640_dev *sensor, int gain)
{
return ov5640_write_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN,
(u16)gain & 0x3ff);
}
static int ov5640_set_autogain(struct ov5640_dev *sensor, bool on)
{
return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL,
BIT(1), on ? 0 : BIT(1));
}
5640获取时钟的函数
static int ov5640_get_sysclk(struct ov5640_dev *sensor)
{
/* calculate sysclk */
u32 xvclk = sensor->xclk_freq / 10000;
u32 multiplier, prediv, VCO, sysdiv, pll_rdiv;
u32 sclk_rdiv_map[] = {1, 2, 4, 8};
u32 bit_div2x = 1, sclk_rdiv, sysclk;
u8 temp1, temp2;
int ret;
ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL0, &temp1);
if (ret)
return ret;
temp2 = temp1 & 0x0f;
if (temp2 == 8 || temp2 == 10)
bit_div2x = temp2 / 2;
ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL1, &temp1);
if (ret)
return ret;
sysdiv = temp1 >> 4;
if (sysdiv == 0)
sysdiv = 16;
ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL2, &temp1);
if (ret)
return ret;
multiplier = temp1;
ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL3, &temp1);
if (ret)
return ret;
prediv = temp1 & 0x0f;
pll_rdiv = ((temp1 >> 4) & 0x01) + 1;
/*设置时钟分频*/
ret = ov5640_read_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, &temp1);
if (ret)
return ret;
temp2 = temp1 & 0x03;
sclk_rdiv = sclk_rdiv_map[temp2];
if (!prediv || !sysdiv || !pll_rdiv || !bit_div2x)
return -EINVAL;
VCO = xvclk * multiplier / prediv;
sysclk = VCO / sysdiv / pll_rdiv * 2 / bit_div2x / sclk_rdiv;
return sysclk;
}
如何计算时钟:
后面还有一些函数是关于上电时序的,就是读取设备数的gpio,拉高拉底的,没啥太大的问题。 以及注册函数,卸载函数和读取dts配置的相关东西。