公司最近开发的一款产品用到了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@01 { compatible = "ambarella,ambvin"; reg = <0x3B>; /* slave address */ interrupt-parent = <&gpio>; interrupts = <26 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=0; unsigned int g_cap_cap_w=0; unsigned int g_cap_cap_h=0; static int w = 1920; MODULE_PARM_DESC(w, "video input width"); module_param(w, int, S_IRUGO); static int h = 1080; MODULE_PARM_DESC(h, "video input height"); module_param(h, int, S_IRUGO); static int p = 1; MODULE_PARM_DESC(p, "video input format"); module_param(p, int, S_IRUGO); static int bit = 10; 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 = 0; static int dummyfpga_set_audio_mode_width_height(struct vin_device *vdev, u32 width, u32 height) { g_audio_mode = 1; g_cap_cap_w = width; g_cap_cap_h = height; return 0; } 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, 0, sizeof (dummyfpga_config)); dummyfpga_config.sensor_id = GENERIC_SENSOR; dummyfpga_config.interface_type = SENSOR_PARALLEL_LVDS; if (bit == 10) { 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 == 1) { 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 ", format->video_mode); return -1; } */ dummyfpga_config.cap_win.x = 0; dummyfpga_config.cap_win.y = 0; dummyfpga_config.cap_win.width = w; dummyfpga_config.cap_win.height = h; printk("cap_win_height %d video_format %d ", 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 < 0) return rval; printk("############### Debug: dummyfpga_set_format ############## "); return 0; } static int dummyfpga_get_format(struct vin_device *vdev) { //int rval; vdev->formats->video_mode = AMBA_VIDEO_MODE_AUTO; vdev->formats->def_start_x = 0;//pinfo->cap_start_x; vdev->formats->def_start_y = 0;//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 == 1) { 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 == 10) { vdev->formats->bits = 10; } else { vdev->formats->bits = 8;//pinfo->bit_resolution; } //format->sync_start = pinfo->sync_start; printk("############### Debug: dummyfpga_get_format ############## "); return 0; } static int dummyfpga_init_device(struct vin_device *vdev) { vin_info("DUMMYFPGA Init "); return 0; } 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 = 0; vdev = ambarella_vin_create_device(ambdev->name, DECODER_GS2970, 0); if (!vdev) return -ENOMEM; vdev->intf_id = 0; 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 < 0) { ambarella_vin_free_device(vdev); return rval; } ambpriv_set_drvdata(ambdev, vdev); vin_info("Dummyfpga Init, with LVDS I/F "); return 0; } 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 0; } 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[6]; struct i2c_client *client = dev->client; pbuf[0] = subaddr[0]; pbuf[1] = subaddr[1]; pbuf[2] = data[0]; pbuf[3] = data[1]; pbuf[4] = data[2]; pbuf[5] = data[3]; rval = i2c_master_send(client, pbuf, 6); if (rval < 0) { vin_error("addr w failed(%d): [0x%x%x] ", rval, subaddr[0], subaddr[1]); return rval; } return 0; } static int xilinx_i2c_read_4bytes(struct xilinx_dev *dev, u8 subaddr[], u8 data[]) { int rval; struct i2c_msg msgs[2]; struct i2c_client *client = dev->client; msgs[0].len = 2; msgs[0].addr = client->addr; msgs[0].flags = client->flags; //msgs[0].buf = &subaddr[0]; msgs[0].buf = subaddr; msgs[1].len = 4; msgs[1].addr = client->addr; msgs[1].flags = client->flags | I2C_M_RD; //msgs[1].buf = &data[0]; msgs[1].buf = data; rval = i2c_transfer(client->adapter, msgs, 2); if (rval < 0) { vin_error("addr r failed(%d): [0x%x%x] ", rval, subaddr[0], subaddr[1]); return rval; } return 0; } static int i2c_test_write(struct xilinx_dev *dev) { int rval; u8 subaddr[2]; u8 data[4]; subaddr[0] = 0x00; subaddr[1] = 0x04; data[0] = 0x12; data[1] = 0x34; data[2] = 0x56; data[3] = 0x78; rval = xilinx_i2c_write_4bytes(dev, subaddr, data); if (rval < 0) return rval; return 0; } static int i2c_test_read(struct xilinx_dev *dev) { int rval; u8 subaddr[2]; u8 data[4]; subaddr[0] = 0x00; subaddr[1] = 0x04; rval = xilinx_i2c_read_4bytes(dev, subaddr, data); if (rval < 0) return rval; printk("0x0004=%02x%02x%02x%02x ", data[0], data[1], data[2], data[3]); subaddr[0] = 0x00; subaddr[1] = 0x00; rval = xilinx_i2c_read_4bytes(dev, subaddr, data); if (rval < 0) return rval; printk("0x0000=%02x%02x%02x%02x ", data[0], data[1], data[2], data[3]); return 0; } 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, 1); 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, 0); printk("xilinx_i2c_probe "); if (xilinx_cdev_major) ret = register_chrdev_region(devno, 1, "xilinx_cdev"); else { ret = alloc_chrdev_region(&devno, 0, 1, "xilinx_cdev"); xilinx_cdev_major = MAJOR(devno); } if (ret < 0) 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, 0); xilinx_devp->cls = class_create(THIS_MODULE, "xilinx_class"); if (IS_ERR(xilinx_devp->cls)) { printk("Err: failed in creating xilinx cdev class "); return -1; } device_create(xilinx_devp->cls, NULL, MKDEV(xilinx_cdev_major, 0), 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 ", client->irq, ret); return ret; } else printk("request irq:%d ", client->irq); mutex_init(&xilinx_devp->rw_mutex); //i2c_test_write(xilinx_devp); //i2c_test_read(xilinx_devp); fail_malloc: unregister_chrdev_region(devno, 1); return ret; } static int xilinx_i2c_remove(struct i2c_client *client) { struct xilinx_dev *xilinx_devp; dev_t devno = MKDEV(xilinx_cdev_major, 0); 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, 1); return 0; } static const struct i2c_device_id xilinx_idtable[] = { {"xilinx", 0}, {}, }; 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 = 0; rval = i2c_add_driver(&i2c_driver_xilinx); if (rval < 0) { printk("add xilinx i2c driver failed "); return rval; } dummyfpga_device = ambpriv_create_bundle(&dummyfpga_driver, NULL, -1, NULL, -1); if (IS_ERR(dummyfpga_device)) rval = PTR_ERR(dummyfpga_device); return 0; } 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哈哈~