公司最近开发的一款产品用到了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 \n", 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 \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 < 0)
        return rval;

    printk("############### Debug: dummyfpga_set_format ##############\n");
    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 ##############\n");
        return 0;
}

static int dummyfpga_init_device(struct vin_device *vdev)
{
    vin_info("DUMMYFPGA Init\n");
    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\n");
        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]\n", 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]\n", 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\n", 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\n", 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\n");
    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\n");
        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\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, 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\n");
        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哈哈~

01-05 17:10