ambarella H2 platform fpga capture card driver case

The company recently developed a product that uses a platform Soc ambarella H2, H2 series of very well-known ambarella of Soc coding capability, up to 4kp60, the catch is not recommended by the boards of several ambarella capture card, their own fpga to do with a capture card, capture card so the driver needs to write their own.

Capture card driver actually nothing is simply i2c communication, H2 Soc i2c and communicate via capture card, can be standard audio channels, and input source information through the check register, thereby performing the setting and by the number of audio channels / proc file system tells the application layer to the corresponding capture and encoding standard.

As standard terms of switching, to capture card side will send to a cpu interrupt gpio (200ms high level), the capture card driver captured after the interruption, once again check register values, and update the configuration registers auido relevant standard in / proc file, the application layer constantly poll the standard file format change that by stopping the original capture, open a new standard for capturing and encoding. This method has the disadvantage that the response will be slower, but can be optimized, such as to inform the driver layer switching by the method of the application layer format of the asynchronous notification, then the layer should go check / proc file under standard.

The i2c capture card driver and ambarella H2 SDK to capture relevant driver in the integrated, here ambarela SDK in a drive to capture ignored.

Write drive

First, modify the device file tree

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";
        };
...

Second, write drivers

Capture card i2c read and write addresses are 2bytes, each read and write data 4bytes. 0x0004 test register read and write.

The following code is only achieved registration, i2c i2c read and write function-driven, interrupt request. Trigger an interrupt when plugging a video source, the interrupt handler to 0x0004 register read and write tests. Other code for the demo ambarella platform video capture part, are hardcode, you can call the interrupt handler in dummyfpga_get_format and dummyfpga_get_format in check capture card register, thereby setting the appropriate timing to capture, do not make too much tired above.

#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");

After the driver is loaded, switch the video source format, interrupt is triggered, there will see in dmesg interrupt handler to read and write the results in the register of 0x0004.

It is first introduced to it here, ambarella H2 platform pit is really a lot, I hope you will not be used later in the development process things of this platform, multi-purpose use Hass platform to support domestic, O (∩_∩) O haha ​​~

Guess you like

Origin www.cnblogs.com/wanglouxiaozi/p/11759961.html