ambarella H2平台fpga捕捉卡驱动案例
公司最近开发的一款产品用到了ambarella H2平台的一款Soc,众所周知ambarella的H2系列的Soc编码能力很强,最高可达4kp60,捕捉上没有用ambarella开发板推荐的几个捕捉卡,是自己用fpga做的一款捕捉卡, 所以捕捉卡驱动需要自己来写。
捕捉卡驱动其实没有什么东西,就是简单地i2c通信, H2 Soc通过i2c和捕捉卡进行通信, 可以通过check寄存器得到输入源的制式以及audio通道数相关信息,从而进行audio通道数设定并且通过/proc文件系统告诉应用层,从而进行相应制式的捕捉与编码。
至于制式切换方面,捕捉卡这边会向cpu发来一个gpio中断(200ms高电平),捕捉卡驱动捕获到该中断后,重新check一次寄存器值,配置auido相关寄存器并更新/proc下的制式文件,应用层通过不断地poll这个制式文件得知制式改变时,停止原先的捕捉,开启新的制式的捕捉及编码。这种方法有个缺点就是响应速度会慢一些,但是可以优化,比如驱动层通过异步通知的方法告知应用层制式切换了,然后应从层再去check /proc下制式文件。
这个i2c捕捉卡驱动要和ambarella H2 SDK里的捕捉相关驱动集成在一起,这里ambarela SDK里捕捉驱动先略过。
驱动编写
一、修改设备树文件
ambarella/boards/h2_xxx/bsp/h2_xxx.dts
apb@e8000000 {
i2c0: i2c@e8003000 {
single_vin: ambvin0@ {
compatible = "ambarella,ambvin";
reg = <0x3B>; /* slave address */
interrupt-parent = <&gpio>;
interrupts = < 0x2>; /* gpio26, 下降沿触发*/
};
status = "ok";
};
...
二、编写驱动
捕捉卡i2c的读写地址都是2bytes, 读写数据每次4bytes。0x0004为读写测试寄存器。
下面代码只实现了i2c驱动的注册、i2c读写函数、中断申请。插拔视频源时会触发中断,中断处理函数中对0x0004寄存器进行了读写测试。其他代码为ambarella平台视频捕捉部分的demo,都是hardcode的,可以在中断处理函数里调用dummyfpga_get_format, 并在dummyfpga_get_format里check捕捉卡寄存器,从而设定相应的timing去捕捉,这里不做过多的累述。
#include <linux/module.h>
#include <linux/ambpriv_device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/bitrev.h>
#include <linux/stat.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/param.h>
#include <plat/spi.h>
#include <iav_utils.h>
#include <vin_api.h>
#include <plat/clk.h>
#include <plat/gpio.h> #include "dummyfpga_table.c" unsigned int g_audio_mode=;
unsigned int g_cap_cap_w=;
unsigned int g_cap_cap_h=; static int w = ;
MODULE_PARM_DESC(w, "video input width");
module_param(w, int, S_IRUGO); static int h = ;
MODULE_PARM_DESC(h, "video input height");
module_param(h, int, S_IRUGO); static int p = ;
MODULE_PARM_DESC(p, "video input format");
module_param(p, int, S_IRUGO); static int bit = ;
MODULE_PARM_DESC(bit, "video input format bits");
module_param(bit, int, S_IRUGO); struct xilinx_dev {
struct cdev cdev;
struct i2c_client *client;
struct class *cls;
struct mutex rw_mutex;
}; //struct xilinx_dev *xilinx_devp;
static int xilinx_cdev_major = ; static int dummyfpga_set_audio_mode_width_height(struct vin_device *vdev, u32 width, u32 height)
{
g_audio_mode = ;
g_cap_cap_w = width;
g_cap_cap_h = height; return ;
} static int dummyfpga_set_vin_mode(struct vin_device *vdev, struct vin_video_format *format)
{
struct vin_device_config dummyfpga_config;
static int yuv_order = SENSOR_CB_Y0_CR_Y1; memset(&dummyfpga_config, , sizeof (dummyfpga_config)); dummyfpga_config.sensor_id = GENERIC_SENSOR;
dummyfpga_config.interface_type = SENSOR_PARALLEL_LVDS;
if (bit == ) {
dummyfpga_config.bit_resolution = AMBA_VIDEO_BITS_10;
} else {
dummyfpga_config.bit_resolution = AMBA_VIDEO_BITS_8;
}
dummyfpga_config.input_mode = SENSOR_YUV_2PIX;
//dummyfpga_config.input_mode = SENSOR_YUV_1PIX;
dummyfpga_config.plvds_cfg.data_edge = SENSOR_DATA_FALLING_EDGE;
//if (w == 720) {
// dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_ACROSS_BOTH;
// } else {
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
// } dummyfpga_config.plvds_cfg.data_rate = SENSOR_PARALLEL_DATA_RATE_SDR;
dummyfpga_config.plvds_cfg.a8_mode = SENSOR_PARALLEL_NONE_A8_MODE;
dummyfpga_config.yuv_pixel_order = yuv_order;
dummyfpga_config.plvds_cfg.hw_specific = SENSOR_PARALLEL_HW_BUB;
//dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
//dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_INTLC; if (p == ) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_PROG;
} else {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_INTERLACE;
dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_INTLC;
} /*
if(format->video_mode == AMBA_VIDEO_MODE_1080P || format->video_mode == AMBA_VIDEO_MODE_1080P50)
{
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_1080I || format->video_mode == AMBA_VIDEO_MODE_1080I50) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_720P || format->video_mode == AMBA_VIDEO_MODE_720P50) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_576I) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_480I) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else {
printk("Unsupport mode %d \n", format->video_mode);
return -1;
}
*/
dummyfpga_config.cap_win.x = ;
dummyfpga_config.cap_win.y = ;
dummyfpga_config.cap_win.width = w;
dummyfpga_config.cap_win.height = h; printk("cap_win_height %d video_format %d \n", dummyfpga_config.cap_win.height, format->format);
dummyfpga_config.video_format = format->format ;
return ambarella_set_vin_config(vdev, &dummyfpga_config);
} static int dummyfpga_set_format(struct vin_device *vdev, struct vin_video_format *format)
{
int rval; rval = dummyfpga_set_vin_mode(vdev, format);
if (rval < )
return rval; printk("############### Debug: dummyfpga_set_format ##############\n");
return ;
} static int dummyfpga_get_format(struct vin_device *vdev)
{
//int rval; vdev->formats->video_mode = AMBA_VIDEO_MODE_AUTO;
vdev->formats->def_start_x = ;//pinfo->cap_start_x;
vdev->formats->def_start_y = ;//pinfo->cap_start_y;
vdev->formats->def_width = g_audio_mode ? g_cap_cap_w:w;//pinfo->cap_cap_w;
vdev->formats->def_height = g_audio_mode ? g_cap_cap_h:h;//pinfo->cap_cap_h;
vdev->formats->default_fps = AMBA_VIDEO_FPS_60;//pinfo->frame_rate;
vdev->formats->max_fps = AMBA_VIDEO_FPS_60;//pinfo->frame_rate;
vdev->formats->ratio = AMBA_VIDEO_RATIO_16_9;//pinfo->aspect_ratio;
if (p == ) {
vdev->formats->format = AMBA_VIDEO_FORMAT_PROGRESSIVE;//pinfo->video_format;
} else {
vdev->formats->format = AMBA_VIDEO_FORMAT_INTERLACE;//pinfo->video_format;
}
vdev->formats->type = AMBA_VIDEO_TYPE_YUV_656;//pinfo->input_type;
if (bit == ) {
vdev->formats->bits = ;
} else {
vdev->formats->bits = ;//pinfo->bit_resolution;
}
//format->sync_start = pinfo->sync_start;
printk("############### Debug: dummyfpga_get_format ##############\n");
return ;
} static int dummyfpga_init_device(struct vin_device *vdev)
{
vin_info("DUMMYFPGA Init\n");
return ;
} static struct vin_ops dummyfpga_ops = {
.init_device = dummyfpga_init_device,
.set_format = dummyfpga_set_format,
.get_format = dummyfpga_get_format,
.set_audio_mode_width_height = dummyfpga_set_audio_mode_width_height
}; /* ========================================================================== */
static int dummyfpga_drv_probe(struct ambpriv_device *ambdev)
{
struct vin_device *vdev;
int rval = ;
vdev = ambarella_vin_create_device(ambdev->name,
DECODER_GS2970, );
if (!vdev)
return -ENOMEM; vdev->intf_id = ;
vdev->dev_type = VINDEV_TYPE_DECODER;
vdev->sub_type = VINDEV_SUBTYPE_SDI;
vdev->default_mode = AMBA_VIDEO_MODE_AUTO;
vdev->default_hdr_mode = AMBA_VIDEO_LINEAR_MODE;
vdev->frame_rate = AMBA_VIDEO_FPS_AUTO; rval = ambarella_vin_register_device(vdev, &dummyfpga_ops,
dummyfpga_formats, ARRAY_SIZE(dummyfpga_formats),
dummyfpga_plls, ARRAY_SIZE(dummyfpga_plls));
if (rval < ) {
ambarella_vin_free_device(vdev);
return rval;
} ambpriv_set_drvdata(ambdev, vdev); vin_info("Dummyfpga Init, with LVDS I/F\n");
return ;
} static int dummyfpga_drv_remove(struct ambpriv_device *ambdev)
{
struct vin_device *vdev = ambpriv_get_drvdata(ambdev); ambarella_vin_unregister_device(vdev);
ambarella_vin_free_device(vdev); return ;
} static struct ambpriv_driver dummyfpga_driver = {
.probe = dummyfpga_drv_probe,
.remove = dummyfpga_drv_remove,
.driver = {
.name = "dummyfpga",
.owner = THIS_MODULE,
}
}; static int xilinx_i2c_write_4bytes(struct xilinx_dev *dev, u8 subaddr[], u8 data[])
{
int rval;
u8 pbuf[];
struct i2c_client *client = dev->client; pbuf[] = subaddr[];
pbuf[] = subaddr[];
pbuf[] = data[];
pbuf[] = data[];
pbuf[] = data[];
pbuf[] = data[]; rval = i2c_master_send(client, pbuf, );
if (rval < ) {
vin_error("addr w failed(%d): [0x%x%x]\n", rval, subaddr[], subaddr[]);
return rval;
} return ;
} static int xilinx_i2c_read_4bytes(struct xilinx_dev *dev, u8 subaddr[], u8 data[])
{
int rval;
struct i2c_msg msgs[];
struct i2c_client *client = dev->client; msgs[].len = ;
msgs[].addr = client->addr;
msgs[].flags = client->flags;
//msgs[0].buf = &subaddr[0];
msgs[].buf = subaddr; msgs[].len = ;
msgs[].addr = client->addr;
msgs[].flags = client->flags | I2C_M_RD;
//msgs[1].buf = &data[0];
msgs[].buf = data; rval = i2c_transfer(client->adapter, msgs, );
if (rval < ) {
vin_error("addr r failed(%d): [0x%x%x]\n", rval, subaddr[], subaddr[]);
return rval;
} return ;
} static int i2c_test_write(struct xilinx_dev *dev)
{
int rval;
u8 subaddr[];
u8 data[]; subaddr[] = 0x00;
subaddr[] = 0x04;
data[] = 0x12;
data[] = 0x34;
data[] = 0x56;
data[] = 0x78;
rval = xilinx_i2c_write_4bytes(dev, subaddr, data);
if (rval < )
return rval; return ;
} static int i2c_test_read(struct xilinx_dev *dev)
{
int rval;
u8 subaddr[];
u8 data[]; subaddr[] = 0x00;
subaddr[] = 0x04;
rval = xilinx_i2c_read_4bytes(dev, subaddr, data);
if (rval < )
return rval;
printk("0x0004=%02x%02x%02x%02x\n", data[], data[], data[], data[]); subaddr[] = 0x00;
subaddr[] = 0x00;
rval = xilinx_i2c_read_4bytes(dev, subaddr, data);
if (rval < )
return rval;
printk("0x0000=%02x%02x%02x%02x\n", data[], data[], data[], data[]); return ;
} static irqreturn_t xilinx_i2c_interrupt(int irq, void *dev_id)
{
struct xilinx_dev *xilinx_devp = (struct xilinx_dev *)dev_id;
i2c_test_write(xilinx_devp);
i2c_test_read(xilinx_devp); return IRQ_HANDLED;
} static const struct file_operations xilinx_fops = {
.owner = THIS_MODULE,
// .read =
// .write =
// .unlocked_ioctl =
// .open =
// .release =
}; static void xilinx_setup_cdev(struct xilinx_dev *dev, int index)
{
int err, devno = MKDEV(xilinx_cdev_major, index); cdev_init(&dev->cdev, &xilinx_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, );
if (err)
printk(KERN_NOTICE "Error %d adding xilinx_cdev%d", err, index);
} static int xilinx_i2c_proble(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
struct xilinx_dev *xilinx_devp;
struct device *dev = &client->dev;
dev_t devno = MKDEV(xilinx_cdev_major, ); printk("xilinx_i2c_probe\n");
if (xilinx_cdev_major)
ret = register_chrdev_region(devno, , "xilinx_cdev");
else {
ret = alloc_chrdev_region(&devno, , , "xilinx_cdev");
xilinx_cdev_major = MAJOR(devno);
}
if (ret < )
return ret; xilinx_devp = kzalloc(sizeof(struct xilinx_dev), GFP_KERNEL);
if (!xilinx_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
xilinx_devp->client = client;
i2c_set_clientdata(client, xilinx_devp); xilinx_setup_cdev(xilinx_devp, );
xilinx_devp->cls = class_create(THIS_MODULE, "xilinx_class");
if (IS_ERR(xilinx_devp->cls)) {
printk("Err: failed in creating xilinx cdev class\n");
return -;
}
device_create(xilinx_devp->cls, NULL, MKDEV(xilinx_cdev_major, ), NULL, "xilinx_fpga");
ret = devm_request_threaded_irq(dev, client->irq, NULL,
xilinx_i2c_interrupt, IRQF_ONESHOT,
client->name, xilinx_devp);
if (ret) {
dev_err(dev, "request irq %d failed: %d\n", client->irq, ret);
return ret;
} else
printk("request irq:%d\n", client->irq); mutex_init(&xilinx_devp->rw_mutex); //i2c_test_write(xilinx_devp);
//i2c_test_read(xilinx_devp); fail_malloc:
unregister_chrdev_region(devno, );
return ret;
} static int xilinx_i2c_remove(struct i2c_client *client)
{
struct xilinx_dev *xilinx_devp;
dev_t devno = MKDEV(xilinx_cdev_major, );
xilinx_devp = (struct xilinx_dev *)i2c_get_clientdata(client);
cdev_del(&xilinx_devp->cdev);
device_destroy(xilinx_devp->cls, devno);
class_destroy(xilinx_devp->cls);
kfree(xilinx_devp);
unregister_chrdev_region(devno, ); return ;
} static const struct i2c_device_id xilinx_idtable[] = {
{"xilinx", },
{},
};
MODULE_DEVICE_TABLE(i2c, xilinx_idtable); static const struct of_device_id xilinx_dt_ids[] = {
{.compatible = "ambarella,ambvin",},
{},
};
MODULE_DEVICE_TABLE(of, xilinx_dt_ids); static struct i2c_driver i2c_driver_xilinx = {
.driver = {
.name = "xilinx",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(xilinx_dt_ids),
//.of_match_table = xilinx_dt_ids,
},
.id_table = xilinx_idtable,
.probe = xilinx_i2c_proble,
.remove = xilinx_i2c_remove, }; static struct ambpriv_device *dummyfpga_device;
static int __init dummyfpga_init(void)
{
int rval = ; rval = i2c_add_driver(&i2c_driver_xilinx);
if (rval < ) {
printk("add xilinx i2c driver failed\n");
return rval;
} dummyfpga_device = ambpriv_create_bundle(&dummyfpga_driver, NULL, -, NULL, -);
if (IS_ERR(dummyfpga_device))
rval = PTR_ERR(dummyfpga_device);
return ;
} static void __exit dummyfpga_exit(void)
{
i2c_del_driver(&i2c_driver_xilinx);
ambpriv_device_unregister(dummyfpga_device);
ambpriv_driver_unregister(&dummyfpga_driver);
} module_init(dummyfpga_init);
module_exit(dummyfpga_exit); MODULE_DESCRIPTION("dummyfpga decoder");
MODULE_LICENSE("GPL");
驱动加载后,切换视频源制式,中断会触发,在dmesg里会看到中断处理函数里对0x0004寄存器的读写结果。
就先介绍到这里吧,ambarella H2平台坑真的很多,希望你们在以后的开发过程中不会用到这个平台的东西,多用用海思平台,支持国产,O(∩_∩)O哈哈~
最新文章
- word转化jpg
- 第二篇 基于.net搭建热插拔式web框架(沙箱的构建)
- 艺萌TCP文件传输及自动更新系统介绍(TCP文件传输)(四)
- 昨天晚上画了个带apple的图:ide插件与php和xdebug通信原理图,周末写1个调试器。
- .NET微信公众号开发-1.0初始微信公众号
- 【HDU 2203】亲和串
- CRT团队组员博客地址统计
- Vue.js中Directive知识
- C# dynamic类型
- mysql批量上传数据
- 实战DeviceIoControl 之三:制作磁盘镜像文件
- 20165230 2017-2018-2 《Java程序设计》第2周学习总结
- HBASE基础知识总结
- Python selenium ActionChains 基本操作
- JS模态框 简单案例
- 2017 Russian Code Cup (RCC 17), Final Round
- hide server info
- Docker Basic
- Win10 开启便签快捷键
- tortoise svn 忽略bin、obj等文件夹