Files
linux_media/drivers/media/pci/tbscapture/tbs_pcie.c

1720 lines
45 KiB
C

/*
TurboSight TBS PCIE HDMI/SDI capture driver
Copyright (C) 2017 www.tbsdtv.com
*/
#include <linux/pci.h>
#include "tbs_pcie.h"
#include "tbs_pcie-reg.h"
static void tbs_adapters_init(struct tbs_pcie_dev *dev);
static void tbs_hdmi_video_param(struct tbs_pcie_dev *dev,int index);
static void tbs_sdi_video_param(struct tbs_pcie_dev *dev,int index);
struct workqueue_struct *wq;
u8 fw7611[]=
{
// 0x98, 0xFF, 0x80 , //I2C reset
0x98, 0xF4, 0x80 , //CEC
0x98, 0xF5, 0x7C , //INFOFRAME
0x98, 0xF8, 0x4C , //DPLL
0x98, 0xF9, 0x64 ,// KSV
0x98, 0xFA, 0x6C , //EDID
0x98, 0xFB, 0x68, // HDMI
0x98, 0xFD, 0x44 ,// CP
0x98, 0x01, 0x06 , //Prim_Mode =110b HDMI-GR
0x98, 0x02, 0xF5 , //Auto CSC, YCrCb out, Set op_656 bit
0x98, 0x03, 0x80 , //24 bit SDR 444 Mode 0
0x98, 0x05, 0x28 , //AV Codes Off 28
//0x98, 0x06, 0xA4 ,// A4 Invert VS,HS pins
0x98, 0x06, 0x26, //eric mark, field;
0x98, 0x0B, 0x44 , //Power up part
0x98, 0x0C, 0x42 , //Power up part
0x98, 0x14, 0x7F , //Max Drive Strength
0x98, 0x15, 0x80 , //Disable Tristate of Pins
0x98, 0x19, 0x83 , //LLC DLL phase
0x98, 0x33, 0x40 , //LLC DLL enable
// 0x68, 0x48, 0x40 ,
0x44, 0xBA, 0x01 ,// Set HDMI FreeRun
0x64, 0x40, 0x81 , //Disable HDCP 1.1 features
0x68, 0x9B, 0x03 , //ADI recommended setting
0x68, 0xC1, 0x01 , //ADI recommended setting
0x68, 0xC2, 0x01 ,// ADI recommended setting
0x68, 0xC3, 0x01 , //ADI recommended setting
0x68, 0xC4, 0x01 , //ADI recommended setting
0x68, 0xC5, 0x01 , //ADI recommended setting
0x68, 0xC6, 0x01 , //ADI recommended setting
0x68, 0xC7, 0x01 , //ADI recommended setting
0x68, 0xC8, 0x01 , //ADI recommended setting
0x68, 0xC9, 0x01 ,// ADI recommended setting
0x68, 0xCA, 0x01 , //ADI recommended setting
0x68, 0xCB, 0x01 , //ADI recommended setting
0x68, 0xCC, 0x01 ,// ADI recommended setting
0x68, 0x00, 0x00 , //Set HDMI Input Port A
0x68, 0x83, 0xFE , //Enable clock terminator for port A
0x68, 0x6F, 0x0C ,// ADI recommended setting
0x68, 0x85, 0x1F , //ADI recommended setting
0x68, 0x87, 0x70 , //ADI recommended setting
0x68, 0x8D, 0x04 , //LFG
0x68, 0x8E, 0x1E , //HFG
0x68, 0x1A, 0x8A , //unmute audio
0x68, 0x57, 0xDA , //ADI recommended setting
0x68, 0x58, 0x01 , //ADI recommended setting
0x68, 0x03, 0x98 , // DIS_I2C_ZERO_COMPR
0x68, 0x75, 0x10 , //DDC drive strength
0xff};
static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, u8 reg, u8 *val, int len)
{
struct i2c_msg msgs[2] = {{.addr = adr, .flags =0,
.buf = &reg, .len =1},
{.addr =adr, .flags = I2C_M_RD,
.buf = val, .len = len}};
return (i2c_transfer(adapter, msgs,2) == 2) ? 0 : -1;
}
static int i2c_write_reg(struct i2c_adapter *adapter, u8 adr, u8 *val, int len)
{
struct i2c_msg msg[1] = {{.addr = adr, .flags =0,
.buf = val, .len =len}};
return (i2c_transfer(adapter, msg,1) == 1) ? 0 : -1;
}
void i2c_write_tab_new(struct i2c_adapter *adapter, u8 *script)
{
u8 temp[2];
do{
temp[0] = *(script+1);
temp[1] = *(script+2);
i2c_write_reg(adapter,*(script),temp,2 );
script += 3;
}while(*script != 0xff);
}
static int tbs_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
{
struct tbs_video *videodev = video_drvdata(file);
strcpy(cap->driver, KBUILD_MODNAME);
sprintf(cap->card, "pcie video %d",(videodev->index-1)?1:0);
strcpy(cap->bus_info, "pcie");
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE |V4L2_CAP_STREAMING;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int tbs_vidioc_enum_fmt_vid_cap(struct file *file, void *priv_fh,struct v4l2_fmtdesc *f)
{
switch (f->index) {
case 0:
strscpy(f->description, "YUV 4:2:2", sizeof(f->description));
f->pixelformat = V4L2_PIX_FMT_UYVY;
break;
default:
return -EINVAL;
}
return 0;
}
static int tbs_vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct tbs_video *videodev = video_drvdata(file);
struct v4l2_pix_format *pix = &fmt->fmt.pix;
if(videodev->width==0 || videodev->height==0 )
return -1;
pix->width = videodev->width;
pix->height = videodev->height;
pix->pixelformat = V4L2_PIX_FMT_UYVY;
pix->field = V4L2_FIELD_ALTERNATE;
pix->bytesperline = videodev->width*2;
pix->sizeimage = 2 * videodev->width * videodev->height;
/* Just a guess */
pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
return 0;
}
static int tbs_vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct tbs_video *videodev = video_drvdata(file);
struct v4l2_pix_format *pix = &fmt->fmt.pix;
if(videodev->width==0 || videodev->height==0 )
return -1;
pix->height = videodev->height;
pix->width = videodev->width ;
pix->field = V4L2_FIELD_ALTERNATE;
pix->pixelformat = V4L2_PIX_FMT_UYVY;
pix->bytesperline = videodev->width*2;
pix->sizeimage = 2 * videodev->width * videodev->height;
videodev->dmabuf[1].size = videodev->dmabuf[0].size = pix->sizeimage;
/* Just a guess */
pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
return 0;
}
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
struct tbs_video *videodev = video_drvdata(file);
int err;
err = tbs_vidioc_try_fmt_vid_cap(file, priv, f);
if (0 != err)
return err;
videodev->pixfmt = f->fmt.pix.pixelformat;
videodev->width = f->fmt.pix.width;
videodev->height = f->fmt.pix.height;
return 0;
}
static int tbs_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms)
{
struct tbs_video *videodev = video_drvdata(file);
*tvnorms = videodev->std;
return 0;
}
static int tbs_vidioc_s_std(struct file *file, void *priv,v4l2_std_id tvnorms)
{
struct tbs_video *videodev = video_drvdata(file);
videodev->std = tvnorms;
return 0;
}
int tbs_vidioc_g_parm(struct file *file,void *fh, struct v4l2_streamparm *setfps)
{
struct tbs_video *videodev = video_drvdata(file);
setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps->parm.capture.timeperframe.numerator=1;
setfps->parm.capture.timeperframe.denominator=videodev->fps;
return 0;
}
static int tbs_vidioc_enum_input(struct file *file, void *priv,struct v4l2_input *i)
{
if(i->index)
return -EINVAL;
i->type = V4L2_INPUT_TYPE_CAMERA;
strcpy(i->name, KBUILD_MODNAME);
i->std = V4L2_STD_NTSC_M;
return 0;
}
static int tbs_vidioc_g_input(struct file *file, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int tbs_vidioc_s_input(struct file *file, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_log_status(struct file *file, void *priv)
{
return 0;
}
static ssize_t tbs_read(struct file *file,char *buf,size_t count, loff_t *ppos)
{
return 0;
}
static int tbs_open(struct file *file)
{
struct tbs_video *videodev = video_drvdata(file);
struct pci_dev *pci = videodev->dev->pdev;
u32 temp1,temp2;
u32 h_para,v_para;
u32 frame_ps,pori;
struct tbs_pcie_dev *dev = videodev->dev;
if(pci->subsystem_vendor == 0x6324) //tbs6324 sdi
{
temp1= TBS_PCIE_READ(0x0,40);
temp2 =TBS_PCIE_READ(0x0,44);
h_para = ((temp1 & 0xff)<<8) + ((temp1 & 0xff00)>>8);
v_para = ((temp1 & 0xff0000)>>8) + ((temp1 & 0xff000000)>>24);
frame_ps = temp2 &0xff;
pori = (temp2>>8) & 0xff;
printk("fpga get para: %x, %x == v:%d, h:%d, frame:%d, P:%d\n", temp1,temp2,v_para,h_para,frame_ps,pori);
tbs_sdi_video_param(videodev->dev, videodev->index>>1);
}
else //hdmi
tbs_hdmi_video_param(videodev->dev, videodev->index>>1);
if(videodev->width==0 || videodev->height==0 )
return -1;
return 0;
}
static int tbs_vidioc_get_ctrl(struct file *file, void *fh,
struct v4l2_tbs_data * data)
{
struct tbs_video *videodev = video_drvdata(file);
struct tbs_pcie_dev *dev = videodev->dev;
data->value = TBS_PCIE_READ(data->baseaddr, data->reg);
printk("read :baseaddr=0x%x, reg=0x%x, value=0x%x\n", data->baseaddr, data->reg, data->value);
return 0;
}
static int tbs_vidioc_set_ctrl(struct file *file, void *fh,
struct v4l2_tbs_data * data)
{
struct tbs_video *videodev = video_drvdata(file);
struct tbs_pcie_dev *dev = videodev->dev;
printk("write: baseaddr=0x%x, reg=0x%x, value=0x%x\n", data->baseaddr, data->reg, data->value);
TBS_PCIE_WRITE(data->baseaddr,data->reg,data->value);
return 0;
}
static const struct v4l2_file_operations tbs_fops = {
.owner = THIS_MODULE,
.open = tbs_open,//v4l2_fh_open,
.release = vb2_fop_release,
.read = tbs_read,//vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
static const struct v4l2_ioctl_ops tbs_ioctl_fops = {
.vidioc_querycap = tbs_vidioc_querycap,
.vidioc_enum_fmt_vid_cap = tbs_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = tbs_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = tbs_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_g_std = tbs_vidioc_g_std,
.vidioc_s_std = tbs_vidioc_s_std,
.vidioc_enum_input = tbs_vidioc_enum_input,
.vidioc_g_input = tbs_vidioc_g_input,
.vidioc_s_input = tbs_vidioc_s_input,
.vidioc_log_status = vidioc_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_g_parm = tbs_vidioc_g_parm,
.vidioc_tbs_g_ctrls = tbs_vidioc_get_ctrl,
.vidioc_tbs_s_ctrls = tbs_vidioc_set_ctrl,
};
//long (*vidioc_default)(struct file *file, void *fh,
// bool valid_prio, unsigned int cmd, void *arg);
/* calc max # of buffers from size (must not exceed the 4MB virtual
* address space per DMA channel) */
static int tbs_buffer_count(unsigned int size, unsigned int count)
{
unsigned int maxcount;
maxcount = (8 * 1024 * 1024) / roundup(size, PAGE_SIZE);
if (count > maxcount)
count = maxcount;
return count;
}
static int tbs_queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct tbs_video *videodev = q->drv_priv;
unsigned tot_bufs = q->num_buffers + *num_buffers;
unsigned size = 2* videodev->width * videodev->height; // 16bit
videodev->dmabuf[1].size = videodev->dmabuf[0].size = size;
if (tot_bufs < 2)
tot_bufs = 2;
tot_bufs = tbs_buffer_count(size, tot_bufs);
*num_buffers = tot_bufs - q->num_buffers;
if (*num_planes)
return sizes[0] < size ? -EINVAL : 0;
*num_planes = 1;
sizes[0] = size;
return 0;
}
static int tbs_buffer_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct tbsvideo_buffer *buf =
container_of(vbuf, struct tbsvideo_buffer, vb);
struct tbs_video *videodev = vb->vb2_queue->drv_priv;
u32 size;
size = 2* videodev->width * videodev->height; // 16bit
if (vb2_plane_size(vb, 0) < size)
return -EINVAL;
vb2_set_plane_payload(vb, 0, size);
buf->mem = vb2_plane_vaddr(vb,0);
return 0;
}
static void tbs_buffer_finish(struct vb2_buffer *vb)
{
return;
}
static void tbs_buffer_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct tbs_video *videodev = vb->vb2_queue->drv_priv;
struct tbsvideo_buffer *buf =
container_of(vbuf, struct tbsvideo_buffer, vb);
unsigned long flags;
spin_lock_irqsave(&videodev->slock, flags);
list_add_tail(&buf->queue, &videodev->queue);
spin_unlock_irqrestore(&videodev->slock, flags);
}
static void start_video_dma_transfer(struct tbs_video *videodev)
{
struct tbs_pcie_dev *dev =videodev->dev;
//start dma
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_DMA_MASK(videodev->index), 0x00000001);
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_START, 0x00000001);
TBS_PCIE_READ(TBS_DMA_BASE(videodev->index), TBS_DMA_STATUS);
// write picture size
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_CELL_SIZE, DMA_VIDEO_CELL);
//write dma size
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_SIZE,DMA_VIDEO_CELL );
//set dma address:
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_HIGH, 0);
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_LOW, videodev->dmabuf[0].dma);
//dma Rd done
TBS_PCIE_READ(TBS_DMA_BASE(videodev->index), TBS_DMA_SIZE);
//set dma address:
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_HIGH, 0);
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_LOW, videodev->dmabuf[0].dma);
}
static void next_video_dma_transfer(struct tbs_video *videodev,unsigned char flag)
{
struct tbs_pcie_dev *dev =videodev->dev;
//write dma size
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_SIZE,DMA_VIDEO_CELL );
//set dma address:
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_HIGH, 0);
if(videodev->Interlaced){
if(flag)
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_LOW, videodev->dmabuf[(videodev->seqnr+1)&1].dma+DMA_VIDEO_CELL);
else
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_LOW, videodev->dmabuf[(videodev->seqnr+1)&1].dma);
}else{
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_ADDR_LOW, videodev->dmabuf[(videodev->seqnr+1)&1].dma);
}
}
static void stop_video_dma_transfer(struct tbs_video *videodev)
{
struct tbs_pcie_dev *dev =videodev->dev;
TBS_PCIE_READ(TBS_DMA_BASE(videodev->index), TBS_DMA_STATUS);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_DMA_MASK(videodev->index), 0x00000000);
TBS_PCIE_WRITE(TBS_DMA_BASE(videodev->index), TBS_DMA_START, 0x00000000);
}
static int tbs_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct tbs_video *videodev = q->drv_priv;
start_video_dma_transfer(videodev);
videodev->seqnr = 0;
return 0;
}
static void tbs_stop_streaming(struct vb2_queue *q)
{
struct tbs_video *videodev = q->drv_priv;
unsigned long flags;
int offset,regval;
int index = videodev->index>>1;
struct tbs_pcie_dev *dev = videodev->dev;
if(index)
offset = 32;//video1 read address
else
offset = 8;//video0 read and write address
//disable PtoI: bit24 set to default value 0
regval =0x0;
TBS_PCIE_WRITE(0x0, offset, regval);
if(videodev->seqnr){
//vb2_wait_for_all_buffers(q);
mdelay(100);
//printk( "%s() vb2_wait_for_all_buffers\n", __func__);
}
stop_video_dma_transfer(videodev);
spin_lock_irqsave(&videodev->slock, flags);
while (!list_empty(&videodev->queue)) {
struct tbsvideo_buffer *buf = list_entry(videodev->queue.next,
struct tbsvideo_buffer, queue);
list_del(&buf->queue);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&videodev->slock, flags);
}
static const struct vb2_ops tbspcie_video_qops = {
.queue_setup = tbs_queue_setup,
.buf_prepare = tbs_buffer_prepare,
.buf_finish = tbs_buffer_finish,
.buf_queue = tbs_buffer_queue,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.start_streaming = tbs_start_streaming,
.stop_streaming = tbs_stop_streaming,
};
static int tbs_i2c_xfer(struct i2c_adapter *adapter,struct i2c_msg msg[], int num)
{
struct tbs_i2c *i2c = i2c_get_adapdata(adapter);
struct tbs_pcie_dev *dev = i2c->dev;
u8 tmpbuf[8];
u32 data0 = 0;
int timeout;
int i =0;
if (num == 2 &&
msg[1].flags & I2C_M_RD && !(msg[0].flags & I2C_M_RD)) {
//test
tmpbuf[0] =0x81;
tmpbuf[1] = msg[0].addr;
tmpbuf[1] &=0xfe;
tmpbuf[2] = msg[0].buf[0];
if (TBS_PCIE_READ(i2c->base, TBS_I2C_CTRL) == 1);
i2c->ready = 0;
TBS_PCIE_WRITE(i2c->base, TBS_I2C_CTRL, *(u32 *)&tmpbuf[0]);
timeout = wait_event_timeout(i2c->wq, i2c->ready == 1, HZ);
if (timeout <= 0) {
printk(KERN_ERR "TBS PCIE I2C%d timeout\n", i2c->i2c_dev);
return -EIO;
}
tmpbuf[0] =0x80;
tmpbuf[1] = msg[0].addr;
tmpbuf[1] &=0xfe;
tmpbuf[1] +=1; // read operation;
if (msg[1].len <= 4) {
tmpbuf[0] |= 0x40;
} else {
printk(KERN_ERR "TBS PCIE I2C%d read limit is 4 bytes\n",
i2c->i2c_dev);
return -EIO;
}
tmpbuf[0] += msg[1].len;
if (TBS_PCIE_READ(i2c->base, TBS_I2C_CTRL) == 1);
i2c->ready = 0;
TBS_PCIE_WRITE(i2c->base, TBS_I2C_CTRL, *(u32 *)&tmpbuf[0]);
// timeout of 1 sec
timeout = wait_event_timeout(i2c->wq, i2c->ready == 1, HZ);
if (timeout <= 0) {
printk(KERN_ERR "TBS PCIE I2C%d timeout\n", i2c->i2c_dev);
return -EIO;}
data0 = TBS_PCIE_READ(i2c->base, TBS_I2C_DATA);
memcpy(msg[1].buf, &data0, msg[1].len);
return num;
}
if (num == 1 && !(msg[0].flags & I2C_M_RD)) {
tmpbuf[0] =0x80 + msg[0].len;
tmpbuf[1] = msg[0].addr;
tmpbuf[1] &=0xfe;
tmpbuf[0] |= 0x40; // add stop
for(i =0;i<msg[0].len;i++)
tmpbuf[2+i] = msg[0].buf[i];
if (TBS_PCIE_READ(i2c->base, TBS_I2C_CTRL) == 1);
i2c->ready = 0;
TBS_PCIE_WRITE(i2c->base, TBS_I2C_CTRL, *(u32 *)&tmpbuf[0]);
// timeout of 1 sec
timeout = wait_event_timeout(i2c->wq, i2c->ready == 1, HZ);
if (timeout <= 0) {
printk(KERN_ERR "TBS PCIE I2C%d timeout\n", i2c->i2c_dev);
return -EIO;
}
return num;
}
if (num == 1 && (msg[0].flags & I2C_M_RD)) {
printk(KERN_INFO "TBS PCIE I2C%d not implemented\n", i2c->i2c_dev);
return num;
}
return -EIO;
}
static u32 tbs_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL;
}
struct i2c_algorithm tbs_i2c_algo = {
.master_xfer = tbs_i2c_xfer,
.functionality = tbs_i2c_func,
};
static int tbs_i2c_init(struct tbs_pcie_dev *dev)
{
struct tbs_i2c *i2c;
struct i2c_adapter *adap;
int i, j, err = 0;
dev->i2c_bus[0].base = TBS_I2C_BASE_0;
dev->i2c_bus[1].base = TBS_I2C_BASE_1;
dev->i2c_bus[2].base = TBS_I2C_BASE_2;
dev->i2c_bus[3].base = TBS_I2C_BASE_3;
/* enable i2c interrupts */
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_INT_ENABLE, 0x00000001);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_I2C_MASK_0, 0x00000001);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_I2C_MASK_1, 0x00000001);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_I2C_MASK_2, 0x00000001);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_I2C_MASK_3, 0x00000001);
for (i = 0; i < 2; i++) {
i2c = &dev->i2c_bus[i];
i2c->dev = dev;
i2c->i2c_dev = i;
i2c->base = dev->i2c_bus[i].base;
init_waitqueue_head(&i2c->wq);
adap = &i2c->i2c_adap;
i2c_set_adapdata(adap, i2c);
/* TODO: replace X by I2C adapter number */
strscpy(adap->name, "TBS PCIE I2C Adapter X", sizeof(adap->name));
adap->algo = &tbs_i2c_algo;
adap->algo_data = dev;
adap->dev.parent = &dev->pdev->dev;
err = i2c_add_adapter(adap);
if (err)
goto fail;
}
return 0;
fail:
for (j = 0; j < i; j++) {
i2c = &dev->i2c_bus[j];
adap = &i2c->i2c_adap;
i2c_del_adapter(adap);
}
return err;
}
static void tbs_i2c_exit(struct tbs_pcie_dev *dev)
{
struct tbs_i2c *i2c;
struct i2c_adapter *adap;
int i;
for (i = 0; i < 4; i++) {
i2c = &dev->i2c_bus[i];
adap = &i2c->i2c_adap;
i2c_del_adapter(adap);
}
}
void video_data_process(struct work_struct *p_work)
{
struct tbs_video *videodev = container_of(p_work, struct tbs_video, videowork);
struct tbsvideo_buffer *buf;
unsigned long flags;
struct tbs_pcie_dev *dev = videodev->dev;
int ret = TBS_PCIE_READ(TBS_DMA_BASE(videodev->index), 0);
spin_lock_irqsave(&videodev->slock, flags);
if(list_empty(&videodev->queue)){
spin_unlock_irqrestore(&videodev->slock, flags);
return;
}
buf = list_entry(videodev->queue.next, struct tbsvideo_buffer, queue);
list_del(&buf->queue);
buf->vb.vb2_buf.timestamp = ktime_get_ns();
buf->vb.field = videodev->pixfmt;
if(videodev->Interlaced){
int i;
for(i=0;i<videodev->height;i+=2){
memcpy(buf->mem+(i+1)*videodev->width*2,
(u8*)videodev->dmabuf[videodev->seqnr&1].cpu+DMA_VIDEO_CELL+(i>>1)*videodev->width*2,videodev->width*2);
memcpy(buf->mem+(i)*videodev->width*2,
(u8*)videodev->dmabuf[videodev->seqnr&1].cpu+(i>>1)*videodev->width*2,videodev->width*2);
}
buf->vb.sequence = videodev->seqnr++;
}else{
buf->vb.sequence = videodev->seqnr++;
memcpy(buf->mem,(u8*)videodev->dmabuf[videodev->seqnr&1].cpu,videodev->dmabuf[videodev->seqnr&1].size);
}
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
spin_unlock_irqrestore(&videodev->slock, flags);
return;
}
/*
void tbs_irq_video_done(struct tbs_pcie_dev *dev, int index)
{
struct tbsvideo_buffer *buf;
unsigned long flags;
spin_lock_irqsave(&dev->video[index].slock, flags);
if(list_empty(&dev->video[index].queue)){
spin_unlock_irqrestore(&dev->video[index].slock, flags);
return;
}
buf = list_entry(dev->video[index].queue.next, struct tbsvideo_buffer, queue);
list_del(&buf->queue);
buf->vb.vb2_buf.timestamp = ktime_get_ns();
buf->vb.field = dev->video[index].pixfmt;
buf->vb.sequence = dev->video[index].seqnr++;
memcpy(vb2_plane_vaddr(&buf->vb.vb2_buf,0),(u8*)dev->video[index].cpu,dev->video[index].size);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
spin_unlock_irqrestore(&dev->video[index].slock, flags);
next_video_dma_transfer(&dev->video[index]);
return;
}
*/
static irqreturn_t tbs_pcie_irq(int irq, void *dev_id)
{
struct tbs_pcie_dev *dev = (struct tbs_pcie_dev *) dev_id;
struct tbs_i2c *i2c;
u32 stat;
u32 ret;
unsigned char index;
stat = TBS_PCIE_READ(TBS_INT_BASE, TBS_INT_STATUS);
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_INT_STATUS, stat);
if (!(stat & 0x000000ff))
{
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_INT_ENABLE, 0x00000001);
return IRQ_HANDLED;
}
if (stat & 0x00000010){ //audio 0
ret = TBS_PCIE_READ(TBS_DMA_BASE_0, 0);
index = (ret)&3;
dev->audio[0].pos = index *TBS_AUDIO_CELL_SIZE;
snd_pcm_period_elapsed(dev->audio[0].substream);
}
if (stat & 0x00000020){//video 0
ret = TBS_PCIE_READ(TBS_DMA_BASE_1, 0);
//printk("video 0 ret: %8x\n",ret);
if(dev->video[0].Interlaced){
// if(ret == 0x1010100){
if((ret & 0x1000000)==0x1000000){
next_video_dma_transfer(&dev->video[0],0);
queue_work(wq,&dev->video[0].videowork);
}else{
next_video_dma_transfer(&dev->video[0],1);
}
}else{
next_video_dma_transfer(&dev->video[0],1);
queue_work(wq,&dev->video[0].videowork);
}
}
if (stat & 0x00000040){ //audio 1
ret = TBS_PCIE_READ(TBS_DMA_BASE_2, 0);
index = (ret)&3;
dev->audio[1].pos = index *TBS_AUDIO_CELL_SIZE;
snd_pcm_period_elapsed(dev->audio[1].substream);
}
if (stat & 0x00000080){//video 1
ret = TBS_PCIE_READ(TBS_DMA_BASE_3, 0);
//printk("video 1 ret: %8x\n",ret);
if(dev->video[1].Interlaced){
// if(ret == 0x1010100){
if((ret & 0x1000000)==0x1000000){
next_video_dma_transfer(&dev->video[1],0);
queue_work(wq,&dev->video[1].videowork);
}else{
next_video_dma_transfer(&dev->video[1],1);
}
}else{
next_video_dma_transfer(&dev->video[1],1);
queue_work(wq,&dev->video[1].videowork);
}
}
if (stat & 0x00000001) {
i2c = &dev->i2c_bus[0];
i2c->ready = 1;
wake_up(&i2c->wq);
}
if (stat & 0x00000002) {
i2c = &dev->i2c_bus[1];
i2c->ready = 1;
wake_up(&i2c->wq);
}
if (stat & 0x00000004) {
i2c = &dev->i2c_bus[2];
i2c->ready = 1;
wake_up(&i2c->wq);
}
if (stat & 0x00000008) {
i2c = &dev->i2c_bus[3];
i2c->ready = 1;
wake_up(&i2c->wq);
}
/* enable interrupt */
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_INT_ENABLE, 0x00000001);
return IRQ_HANDLED;
}
static void signaltable(u32 val, u32 *wid, u32 *high,u32 *freq, u32 *interlaced )
{
switch(val)
{
case 0x16:
case 0x17:
case 0x1B:
case 0x19:
// 720(1440)*480i @ 59.94/60hz
*wid = 720;
*high = 480;
*freq = 60;
*interlaced = 1;
break;
case 0x18:
case 0x1A:
// 720(1440)*576i @ 50hz
*wid = 720;
*high = 576;///2;
*freq = 50;
*interlaced = 1;
break;
case 0x20:
case 0x00:
// 1280*720p @ 59.94/60hz
*wid = 1280;
*high = 720;
*freq = 60;
*interlaced = 0;
break;
case 0x24:
case 0x04:
// 1280*720p @ 50hz
*wid = 1280;
*high = 720;
*freq = 50;
*interlaced = 0;
break;
case 0x2a:
case 0x0a:
// 1920*1080i @ 59.94/60hz
*wid = 1920;
*high = 1080;
*freq = 60;
*interlaced = 1;
break;
case 0x2c:
case 0x0c:
// 1920*1080i @ 50hz
*wid = 1920;
*high = 1080;
*freq = 50;
*interlaced = 1;
break;
case 0x0b:
// 1920*1080p @ 29.97/30hz
*wid = 1920;
*high = 1080;
*freq = 30;
*interlaced = 0;
break;
case 0x0d:
// 1920*1080p @ 25hz
*wid = 1920;
*high = 1080;
*freq = 25;
*interlaced = 0;
break;
case 0x30:
case 0x10:
// 1920*1080p @ 23.98/24hz
*wid = 1920;
*high = 1080;
*freq = 24;
*interlaced = 0;
break;
case 0x2b:
// 1920*1080p @ 59.94/60hz
*wid = 1920;
*high = 1080;
*freq = 60;
*interlaced = 0;
break;
case 0x2d:
// 1280*720p @ 50hz
*wid = 1920;
*high = 1080;
*freq = 50;
*interlaced = 0;
break;
/////// add /////////////////
case 0x02:
// 1280*720p @ 30
*wid = 1280;
*high = 720;
*freq = 30;
*interlaced = 0;
break;
case 0x03:
// 1280*720p @ 30 -EM
*wid = 1280;
*high = 720;
*freq = 30;
*interlaced = 0;
break;
case 0x05:
// 1280*720p @ 50 -EM
*wid = 1280;
*high = 720;
*freq = 50;
*interlaced = 0;
break;
case 0x06:
// 1280*720p @ 25
*wid = 1280;
*high = 720;
*freq = 25;
*interlaced = 0;
break;
case 0x07:
// 1280*720p @ 25 -EM
*wid = 1280;
*high = 720;
*freq = 25;
*interlaced = 0;
break;
case 0x08:
// 1280*720p @ 24
*wid = 1280;
*high = 720;
*freq = 24;
*interlaced = 0;
break;
case 0x09:
// 1280*720p @ 24 -EM
*wid = 1280;
*high = 720;
*freq = 24;
*interlaced = 0;
break;
case 0x12:
// 1920*1080p @ 24 -
*wid = 1920;
*high = 1080;
*freq = 24;
*interlaced = 0;
break;
case 0x14:
// 1920*1080i @ 50
*wid = 1920;
*high = 1080;
*freq = 50;
*interlaced = 1;
break;
case 0x15:
// 1920*1035i @ 60
*wid = 1920;
*high = 1035;
*freq = 60;
*interlaced = 1;
break;
case 0x26:
// 1280*720p @ 25
*wid = 1280;
*high = 720;
*freq = 25;
*interlaced = 0;
break;
case 0x28:
// 1280*720p @ 24
*wid = 1280;
*high = 720;
*freq = 24;
*interlaced = 0;
break;
default:
// 1920*1080p @ 25hz
*wid = 1920;
*high = 1080;
*freq = 25;
*interlaced = 0;
break;
}
return;
}
static void tbs_sdi_video_param(struct tbs_pcie_dev *dev,int index)
{
u32 v_regdata;
u32 v_width,v_height, v_interlaced,v_refq=0;
int regval;
int ptoi = 0;
int itoi = 0;
v_regdata = sdi_read16bit(dev,ASI0_BASEADDRESS,0x06);
v_regdata = (v_regdata & 0x3f00)>>8;
//printk("SDI signal reg value : %x\n", v_regdata);
if(v_regdata==0x1d)
{
printk("SDI cable is not connect! \n");
dev->video[index].width = 1280;
dev->video[index].height = 720;
return;
}
signaltable(v_regdata,&v_width,&v_height,&v_refq,&v_interlaced);
dev->video[index].width = v_width;
dev->video[index].height = v_height;
dev->video[index].fps = v_refq;
if(!v_interlaced && v_refq>30 && dev->video[index].width==1920 && dev->video[index].height==1080)
{
printk("SDI 1080 50/60 Progressive change to Interlaced Input \n");
dev->video[index].Interlaced = 1;
dev->video[index].fps>>=1;
//enable PtoI: bit24 set to 1 by gpio offset address 8
regval =0x1;
TBS_PCIE_WRITE(0x0, 8, regval);
}
else{
//disable PtoI: bit24 set to default value 0
regval =0x0;
TBS_PCIE_WRITE(0x0, 8, regval);
dev->video[index].Interlaced = v_interlaced;
if(v_interlaced)
{
dev->video[index].fps>>=1;
printk("SDI Interlaced Input \n");
}
else
printk("SDI Progressive Input \n");
}
printk("pix:%d line:%d frameRate:%d IorP:%d regvalue:%x\n",dev->video[index].width,dev->video[index].height,v_refq,dev->video[index].Interlaced,v_regdata );
}
static void tbs_hdmi_video_param(struct tbs_pcie_dev *dev,int index)
{
struct tbs_adapter *tbs_adap;
struct pci_dev *pci = dev->pdev;
u8 tmp[2];
u32 tmp_B,v_refq=0;
int regval;
int offset;
int ptoi = 0;
int itoi = 0;
tbs_adap = &dev->tbs_pcie_adap[index];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x98, 0x6f,tmp,1);
if((tmp[0]&0x01)==0)
{
printk("HDMI cable is not connect! \n");
dev->video[index].width = 1280;
dev->video[index].height = 720;
return;
}
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x68, 0x07,tmp, 2);
dev->video[index].width = (tmp[0]&0x1f)*256+tmp[1];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x68, 0x09,tmp, 2);
dev->video[index].height =(tmp[0]&0x1f)*256+tmp[1];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x44,0xb8,tmp,2);
tmp_B=((tmp[0]&0x1f)<<8)+tmp[1];
if(tmp_B)
v_refq = (unsigned char) ((103663 + (tmp_B -1)) / tmp_B);
dev->video[index].fps=v_refq;
if(index)
offset = 20;//video1 read address
else
offset = 8;//video0 read and write address
ptoi = TBS_PCIE_READ(0x0, offset);
if(index)
offset = 32;//video1 write address
else
offset = 8;
if(v_refq>30 && dev->video[index].width==1920 && dev->video[index].height==1080)
{
printk("HDMI 1080 50/60 Progressive change to Interlaced Input \n");
dev->video[index].Interlaced = 1;
dev->video[index].fps>>=1;
//enable PtoI: bit24 set to 1 by gpio offset address 8
regval =0x1;
TBS_PCIE_WRITE(0x0, offset, regval);
}
else if((v_refq>50) && (dev->video[index].width==1280) && (dev->video[index].height==720) &&(pci->subsystem_vendor ==0x6312))
{
printk("HDMI 720 60 Progressive change to Interlaced Input \n");
dev->video[index].Interlaced = 1;
dev->video[index].fps>>=1;
//enable PtoI: bit24 set to 1 by gpio offset address 8
regval =0x1;
TBS_PCIE_WRITE(0x0, offset, regval);
}
else{
//disable PtoI: bit24 set to default value 0
//regval =0x0;
//TBS_PCIE_WRITE(0x0, offset, regval);
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x68,0x0b,tmp,1);
if (tmp[0]&0x20){
printk("HDMI Interlaced Input \n");
dev->video[index].Interlaced = 1;
dev->video[index].height<<=1;
dev->video[index].fps>>=1;
//disable PtoI: bit24 set to default value 0
regval =0x0;
TBS_PCIE_WRITE(0x0, offset, regval);
}
else if(ptoi ==1) // deal to ptoi
{
printk("HDMI Progressive change to Interlaced Input \n");
dev->video[index].Interlaced = 1;
dev->video[index].fps>>=1;
}
else{
printk("HDMI Progressive Input \n");
dev->video[index].Interlaced = 0;
}
}
printk("pix:%d line:%d frameRate:%d\n",dev->video[index].width,dev->video[index].height,v_refq);
}
static void tbs6314_mac(struct tbs_adapter *tbs_adap)
{
struct tbs_pcie_dev *dev = tbs_adap->dev;
u8 tmp[8];
//read mac address:
tbs_adap = &dev->tbs_pcie_adap[0];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xa0,tmp, 4);
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xa4,tmp+4, 2);
//printk("mac address : %x, %x, %x, %x, %x, %x\n", tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]);
}
static void tbs6312_mac(struct tbs_adapter *tbs_adap)
{
struct tbs_pcie_dev *dev = tbs_adap->dev;
u8 tmp[8];
//read mac address:
tbs_adap = &dev->tbs_pcie_adap[1];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xa0,tmp, 4);
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xa4,tmp+4, 2);
printk("mac address : %x, %x, %x, %x, %x, %x\n", tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]);
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xb0,tmp, 4);
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0xa0, 0xb4,tmp+4, 2);
printk("mac address : %x, %x, %x, %x, %x, %x\n", tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]);
}
static void tbs_adapters_init(struct tbs_pcie_dev *dev)
{
struct tbs_adapter *tbs_adap;
struct pci_dev *pci = dev->pdev;
int i;
u8 tmp[8];
/* disable all interrupts */
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_INT_ENABLE, 0x00000001);
/* disable dma */
TBS_PCIE_WRITE(TBS_DMA_BASE_0, TBS_DMA_START, 0x00000000);
TBS_PCIE_WRITE(TBS_DMA_BASE_1, TBS_DMA_START, 0x00000000);
TBS_PCIE_WRITE(TBS_DMA_BASE_2, TBS_DMA_START, 0x00000000);
TBS_PCIE_WRITE(TBS_DMA_BASE_3, TBS_DMA_START, 0x00000000);
switch(pci->subsystem_vendor){
case 0x6312:
printk("tbs6312 card\n");
dev->nr = 2;
break;
case 0x6314:
printk("tbs6314 card\n");
dev->nr = 1;
break;
case 0x6324:
printk("tbs6324 card\n");
dev->nr = 1;
break;
default:
printk("unknonw card\n");
}
for (i = 0; i <dev->nr; i++) {
tbs_adap = &dev->tbs_pcie_adap[i];
tbs_adap->dev = dev;
tbs_adap->count = i;
tbs_adap->i2c = &dev->i2c_bus[i];
if(pci->subsystem_vendor == 0x6324) //tbs6324 sdi
{
// init asi
int regdata;
u8 mpbuf[4];
mpbuf[0] = 0; //0--3 select value
TBS_PCIE_WRITE( TBS_GPIO_BASE, 0x34 , *(u32 *)&mpbuf[0]); // select chip : 13*8 =104=0x68 select address
sdi_chip_reset(dev,ASI0_BASEADDRESS); //asi chip reset;
mpbuf[0] = 1; //active spi bus from "z"
TBS_PCIE_WRITE( ASI0_BASEADDRESS, ASI_SPI_ENABLE, *(u32 *)&mpbuf[0]);
regdata = sdi_read16bit(dev,ASI0_BASEADDRESS,0x24);
printk("GS2971 chip id : %x\n",regdata);
tbs_sdi_video_param(dev,i);
}
else
{
//read 7611 id and init chip here
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x98, 0xea,tmp, 2);
printk("7611 chip id : %x, %x\n", tmp[0],tmp[1]);
//reset
tmp[0] = 0xff;
tmp[1] = 0x80;
i2c_write_reg(&tbs_adap->i2c->i2c_adap,0x98, tmp,2);
mdelay(200);//sleep
i2c_write_tab_new(&tbs_adap->i2c->i2c_adap, fw7611);
mdelay(200);
tbs_hdmi_video_param(dev,i);
}
}
switch(pci->subsystem_vendor){
case 0x6312:
tbs6312_mac(tbs_adap);
break;
case 0x6314:
tbs6314_mac(tbs_adap);
break;
}
}
int tbs_video_register(struct tbs_pcie_dev *dev)
{
struct video_device *vdev ;
struct vb2_queue *q ;
int i;
int err=-1;
for(i=0;i<dev->nr;i++){
err = v4l2_device_register(&dev->pdev->dev, &dev->video[i].v4l2_dev);
if(err<0){
printk(KERN_ERR " v4l2_device_register %d error! \n",i);
return -1;
}
}
for(i=0;i<dev->nr;i++){
vdev = &(dev->video[i].vdev);
q = &(dev->video[i].vq);
if (NULL == vdev){
printk(KERN_ERR " video_device_alloc failed ! \n");
goto fail;
}
dev->video[i].index = i*2+1;
dev->video[i].dev = dev;
dev->video[i].std = V4L2_STD_NTSC_M;
dev->video[i].pixfmt = V4L2_PIX_FMT_UYVY;
vdev->v4l2_dev = &(dev->video[i].v4l2_dev);
vdev->lock = &(dev->video[i].video_lock);
vdev->fops = &tbs_fops;
strcpy(vdev->name,KBUILD_MODNAME);
vdev->release = video_device_release_empty;
video_set_drvdata(vdev, &(dev->video[i]));
vdev->ioctl_ops = &tbs_ioctl_fops;
mutex_init(&(dev->video[i].video_lock));
mutex_init(&(dev->video[i].queue_lock));
spin_lock_init(&dev->video[i].slock);
INIT_LIST_HEAD(&dev->video[i].queue);
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
q->gfp_flags = GFP_DMA32;
q->min_buffers_needed = 2;
q->drv_priv = &(dev->video[i]);
q->buf_struct_size = sizeof(struct tbsvideo_buffer);
q->ops = &tbspcie_video_qops;
q->mem_ops = &vb2_dma_sg_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &(dev->video[i].queue_lock);
q->dev = &(dev->pdev->dev);
vdev->queue = q;
err = vb2_queue_init(q);
if(err != 0){
printk(KERN_ERR " vb2_queue_init failed! \n");
goto fail;
}
INIT_WORK(&dev->video[i].videowork,video_data_process);
dev->video[i].dmabuf[0].cpu = dma_alloc_coherent(&dev->pdev->dev, 4095*1024, &dev->video[i].dmabuf[0].dma, GFP_KERNEL);
if (!dev->video[i].dmabuf[0].cpu) {
printk(" allocate memory failed\n");
goto fail;
}
dev->video[i].dmabuf[1].cpu = dma_alloc_coherent(&dev->pdev->dev, 4095*1024, &dev->video[i].dmabuf[1].dma, GFP_KERNEL);
if (!dev->video[i].dmabuf[1].cpu) {
printk(" allocate memory failed\n");
goto fail;
}
err = video_register_device(vdev, VFL_TYPE_VIDEO,-1);
if(err!=0){
printk(KERN_ERR " v4l2_device_register failed!\n");
goto fail;
}else{
printk(" TBS video Capture %d register OK! \n",i);
}
}
return 0;
fail:
for(i=0;i<dev->nr;i++){
if(!dev->video[i].dmabuf[0].cpu){
dma_free_coherent(&dev->pdev->dev, 4095*1024, dev->video[i].dmabuf[0].cpu, dev->video[i].dmabuf[0].dma);
dev->video[i].dmabuf[0].cpu =NULL;
}
if(!dev->video[i].dmabuf[1].cpu){
dma_free_coherent(&dev->pdev->dev, 4095*1024, dev->video[i].dmabuf[1].cpu, dev->video[i].dmabuf[1].dma);
dev->video[i].dmabuf[1].cpu =NULL;
}
vdev = &dev->video[i].vdev;
video_unregister_device(vdev);
v4l2_device_unregister(&dev->video[i].v4l2_dev);
}
return err;
}
/* HDMI 0x39[3:0] - CS_DATA[27:24] 0 for reserved values*/
static const int cs_data_fs[] = {
44100,
0,
48000,
32000,
0,
0,
0,
0,
88200,
768000,
96000,
0,
176000,
0,
192000,
0,
};
static struct snd_pcm_hardware mycard_capture_stero ={
.info = (SNDRV_PCM_INFO_INTERLEAVED |SNDRV_PCM_INFO_BLOCK_TRANSFER ),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000,
.rate_min = 8000,
.rate_max = 192000,
.channels_min = 2,
.channels_max =2,
.period_bytes_min = TBS_AUDIO_CELL_SIZE,
.period_bytes_max = TBS_AUDIO_CELL_SIZE,
.periods_min = 4,
.periods_max = 4,
.buffer_bytes_max = TBS_AUDIO_CELL_SIZE*4,
};
int tbs_pcie_audio_open(struct snd_pcm_substream *substream)
{
struct tbs_audio *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct tbs_adapter *tbs_adap;
unsigned int rate,setrate=44100;
struct pci_dev *pci = chip->dev->pdev;
u8 tmp[2];
chip->substream = substream;
runtime->hw = mycard_capture_stero;
if(pci->subsystem_vendor == 0x6324) //tbs6324 sdi
{
setrate=48000;//sdi
//printk(KERN_INFO " tbs6324 sdi audio set to 48000\n");
}
else
{
tbs_adap = &chip->dev->tbs_pcie_adap[chip->index>>1];
i2c_read_reg(&tbs_adap->i2c->i2c_adap,0x68, 0x39,tmp, 1);
rate = cs_data_fs[tmp[0]&15];
if(rate)
setrate = rate;
}
//printk(KERN_INFO "%s() index:%x rate:%d setrate:%d tmp[0]:%d\n",__func__, chip->index,rate,setrate,tmp[0]&15);
snd_pcm_hw_constraint_minmax(runtime,SNDRV_PCM_HW_PARAM_RATE,setrate,setrate);
return 0;
}
int tbs_pcie_audio__close(struct snd_pcm_substream *substream)
{
// struct tbs_audio *chip = snd_pcm_substream_chip(substream);
return 0;
}
int tbs_pcie_audio_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
int tbs_pcie_audio_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
int tbs_pcie_audio_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct tbs_audio *chip = snd_pcm_substream_chip(substream);
struct tbs_pcie_dev *dev= chip->dev;
int i;
for(i=0;i<2;i++){
//set dma address:
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_ADDR_HIGH, 0);
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_ADDR_LOW, runtime->dma_addr);
//write dma size
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_SIZE,TBS_AUDIO_CELL_SIZE*4 );
// write picture size
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_CELL_SIZE, TBS_AUDIO_CELL_SIZE);
msleep(10);
}
chip->pos=0;
return 0;
}
int tbs_pcie_audio_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tbs_audio *chip = snd_pcm_substream_chip(substream);
struct tbs_pcie_dev *dev= chip->dev;
switch(cmd){
case SNDRV_PCM_TRIGGER_START:
TBS_PCIE_READ(TBS_DMA_BASE(chip->index), TBS_DMA_STATUS);
//start dma
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_DMA_MASK(chip->index), 0x00000001);
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_START, 0x00000001);
break;
case SNDRV_PCM_TRIGGER_STOP:
//stop dma
TBS_PCIE_WRITE(TBS_INT_BASE, TBS_DMA_MASK(chip->index), 0x000000000);
TBS_PCIE_WRITE(TBS_DMA_BASE(chip->index), TBS_DMA_START, 0x00000000);
break;
default:
return -EINVAL;
break;
}
return 0;
}
static snd_pcm_uframes_t tbs_pcie_audio_pointer(struct snd_pcm_substream *substream)
{
struct tbs_audio *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
return bytes_to_frames(runtime,chip->pos);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
static int tbs_pcie_audio_copy_user(struct snd_pcm_substream *substream,
int channel,
unsigned long pos,
void __user *dst,
unsigned long count)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
ret = copy_to_user(dst,runtime->dma_area+pos,count);
return 0;
}
#else
static int tbs_pcie_audio_copy(struct snd_pcm_substream *substream, int channel,
unsigned long pos, struct iov_iter *dst,
unsigned long count)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
if (copy_to_iter(runtime->dma_area+frames_to_bytes(runtime,pos), frames_to_bytes(runtime,count), dst) !=
frames_to_bytes(runtime,count))
return -EFAULT;
return 0;
}
#endif
struct snd_pcm_ops tbs_pcie_pcm_ops ={
.open = tbs_pcie_audio_open,
.close = tbs_pcie_audio__close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tbs_pcie_audio_hw_params,
.hw_free = tbs_pcie_audio_hw_free,
.prepare = tbs_pcie_audio_prepare,
.trigger = tbs_pcie_audio_trigger,
.pointer = tbs_pcie_audio_pointer,
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
.copy_user = tbs_pcie_audio_copy_user,
#else
.copy = tbs_pcie_audio_copy,
#endif
};
int tbs_audio_register(struct tbs_pcie_dev *dev)
{
struct snd_pcm *pcm;
struct snd_card *card;
int ret;
int i;
char audioname[100];
for(i=0;i<dev->nr;i++){
sprintf(audioname,"tbs_pcie audio %d",i);
ret = snd_card_new(&dev->pdev->dev, -1, audioname, THIS_MODULE, sizeof(struct tbs_audio), &card);
if (ret < 0){
printk(KERN_ERR "%s() ERROR: snd_card_new failed <%d>\n",__func__, ret);
goto fail0;
}
strcpy(card->driver, KBUILD_MODNAME);
strcpy(card->shortname, "tbs_pcie audio");
sprintf(card->longname, "%s %x",card->shortname,i);
ret = snd_pcm_new(card,audioname,0,0,1,&pcm);
if (ret < 0){
printk(KERN_ERR "%s() ERROR: snd_pcm_new failed <%d>\n",__func__, ret);
goto fail1;
}
dev->audio[i].index=i*2;
dev->audio[i].dev=dev;
pcm->private_data = &dev->audio[i];
snd_pcm_set_ops(pcm,SNDRV_PCM_STREAM_CAPTURE,&tbs_pcie_pcm_ops);
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,&dev->pdev->dev, TBS_AUDIO_CELL_SIZE*4, TBS_AUDIO_CELL_SIZE*4);
ret = snd_card_register(card);
if ( ret < 0) {
printk(KERN_ERR "%s() ERROR: snd_card_register failed\n",__func__);
goto fail1;
}
dev->audio[i].card =card;
}
return 0;
fail1:
for(i=0;i<dev->nr;i++){
if(dev->audio[i].card)
snd_card_free(dev->audio[i].card);
dev->audio[i].card=NULL;
}
fail0:
return -1;
}
static void tbs_remove(struct pci_dev *pdev)
{
struct tbs_pcie_dev *dev =
(struct tbs_pcie_dev*) pci_get_drvdata(pdev);
struct video_device *vdev;
int i;
for(i=0;i<2;i++){
if(dev->audio[i].card)
snd_card_free(dev->audio[i].card);
dev->audio[i].card=NULL;
}
for(i=0;i<2;i++){
if(!dev->video[i].dmabuf[0].cpu){
dma_free_coherent(&dev->pdev->dev, 4095*1024,dev->video[i].dmabuf[0].cpu, dev->video[i].dmabuf[0].dma);
dev->video[i].dmabuf[0].cpu =NULL;
}
if(!dev->video[i].dmabuf[1].cpu){
dma_free_coherent(&dev->pdev->dev, 4095*1024,dev->video[i].dmabuf[1].cpu, dev->video[i].dmabuf[1].dma);
dev->video[i].dmabuf[1].cpu =NULL;
}
vdev = &dev->video[i].vdev;
video_unregister_device(vdev);
v4l2_device_unregister(&dev->video[i].v4l2_dev);
}
tbs_i2c_exit(dev);
/* disable interrupts */
free_irq(dev->pdev->irq, dev);
if (dev->mmio)
iounmap(dev->mmio);
kfree(dev);
pci_disable_device(pdev);
pci_set_drvdata(pdev, NULL);
}
static int tbs_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
{
struct tbs_pcie_dev *dev;
int err = 0, ret = -ENODEV;
dev = kzalloc(sizeof (struct tbs_pcie_dev), GFP_KERNEL);
if (dev == NULL) {
printk(KERN_ERR "pcie_tbs_probe ERROR: out of memory\n");
ret = -ENOMEM;
goto fail0;
}
dev->pdev = pdev;
err = pci_enable_device(pdev);
if (err != 0) {
ret = -ENODEV;
printk(KERN_ERR "pcie_tbs_probe ERROR: PCI enable failed (%i)\n", err);
goto fail1;
}
dev->mmio = ioremap(pci_resource_start(dev->pdev, 0),
pci_resource_len(dev->pdev, 0));
if (!dev->mmio) {
printk(KERN_ERR "pcie_tbs_probe ERROR: Mem 0 remap failed\n");
ret = -ENODEV; /* -ENOMEM better?! */
goto fail2;
}
ret = request_irq(dev->pdev->irq,tbs_pcie_irq,IRQF_SHARED,KBUILD_MODNAME,(void *) dev);
if (ret < 0) {
printk(KERN_ERR "pcie_tbs_probe ERROR: IRQ registration failed <%d>\n", ret);
ret = -ENODEV;
goto fail2;
}
pci_set_drvdata(pdev, dev);
if (tbs_i2c_init(dev) < 0)
goto fail3;
tbs_adapters_init(dev);
if( tbs_video_register(dev) )
goto fail3;
if(tbs_audio_register(dev))
goto fail3;
return 0;
fail3:
free_irq(dev->pdev->irq, dev);
if (dev->mmio)
iounmap(dev->mmio);
fail2:
pci_disable_device(pdev);
fail1:
pci_set_drvdata(pdev, NULL);
kfree(dev);
fail0:
return ret;
}
#define MAKE_ENTRY( __vend, __chip, __subven, __subdev, __configptr) { \
.vendor = (__vend), \
.device = (__chip), \
.subvendor = (__subven), \
.subdevice = (__subdev), \
.driver_data = (unsigned long) (__configptr) \
}
static const struct pci_device_id tbs_pci_table[] = {
MAKE_ENTRY(0x544d, 0x6178, 0x6312, 0x0002, NULL),
MAKE_ENTRY(0x544d, 0x6178, 0x6312, 0x2000, NULL),
MAKE_ENTRY(0x544d, 0x6178, 0x6314, 0x1000, NULL),
MAKE_ENTRY(0x544d, 0x6178, 0x6324, 0x8000, NULL), // sdi
{ }
};
MODULE_DEVICE_TABLE(pci, tbs_pci_table);
static struct pci_driver tbs_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = tbs_pci_table,
.probe = tbs_probe,
.remove = tbs_remove,
};
static __init int pcie_tbs_init(void)
{
wq = create_singlethread_workqueue("tbs");
return pci_register_driver(&tbs_pci_driver);
}
static __exit void pcie_tbs_exit(void)
{
if(wq)
destroy_workqueue(wq);
wq=NULL;
pci_unregister_driver(&tbs_pci_driver);
}
module_init(pcie_tbs_init);
module_exit(pcie_tbs_exit);
MODULE_DESCRIPTION("TBS PCIE HDMI/SDI capture driver");
MODULE_AUTHOR("kernelcoding <wugang@kernelcoding.com>");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");