Path: blob/master/drivers/media/video/cx88/cx88-video.c
17728 views
/*1*2* device driver for Conexant 2388x based TV cards3* video4linux video interface4*5* (c) 2003-04 Gerd Knorr <[email protected]> [SuSE Labs]6*7* (c) 2005-2006 Mauro Carvalho Chehab <[email protected]>8* - Multituner support9* - video_ioctl2 conversion10* - PAL/M fixes11*12* This program is free software; you can redistribute it and/or modify13* it under the terms of the GNU General Public License as published by14* the Free Software Foundation; either version 2 of the License, or15* (at your option) any later version.16*17* This program is distributed in the hope that it will be useful,18* but WITHOUT ANY WARRANTY; without even the implied warranty of19* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the20* GNU General Public License for more details.21*22* You should have received a copy of the GNU General Public License23* along with this program; if not, write to the Free Software24* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.25*/2627#include <linux/init.h>28#include <linux/list.h>29#include <linux/module.h>30#include <linux/kmod.h>31#include <linux/kernel.h>32#include <linux/slab.h>33#include <linux/interrupt.h>34#include <linux/dma-mapping.h>35#include <linux/delay.h>36#include <linux/kthread.h>37#include <asm/div64.h>3839#include "cx88.h"40#include <media/v4l2-common.h>41#include <media/v4l2-ioctl.h>42#include <media/wm8775.h>4344MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards");45MODULE_AUTHOR("Gerd Knorr <[email protected]> [SuSE Labs]");46MODULE_LICENSE("GPL");4748/* ------------------------------------------------------------------ */4950static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };51static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };52static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };5354module_param_array(video_nr, int, NULL, 0444);55module_param_array(vbi_nr, int, NULL, 0444);56module_param_array(radio_nr, int, NULL, 0444);5758MODULE_PARM_DESC(video_nr,"video device numbers");59MODULE_PARM_DESC(vbi_nr,"vbi device numbers");60MODULE_PARM_DESC(radio_nr,"radio device numbers");6162static unsigned int video_debug;63module_param(video_debug,int,0644);64MODULE_PARM_DESC(video_debug,"enable debug messages [video]");6566static unsigned int irq_debug;67module_param(irq_debug,int,0644);68MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]");6970static unsigned int vid_limit = 16;71module_param(vid_limit,int,0644);72MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes");7374#define dprintk(level,fmt, arg...) if (video_debug >= level) \75printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg)7677/* ------------------------------------------------------------------- */78/* static data */7980static const struct cx8800_fmt formats[] = {81{82.name = "8 bpp, gray",83.fourcc = V4L2_PIX_FMT_GREY,84.cxformat = ColorFormatY8,85.depth = 8,86.flags = FORMAT_FLAGS_PACKED,87},{88.name = "15 bpp RGB, le",89.fourcc = V4L2_PIX_FMT_RGB555,90.cxformat = ColorFormatRGB15,91.depth = 16,92.flags = FORMAT_FLAGS_PACKED,93},{94.name = "15 bpp RGB, be",95.fourcc = V4L2_PIX_FMT_RGB555X,96.cxformat = ColorFormatRGB15 | ColorFormatBSWAP,97.depth = 16,98.flags = FORMAT_FLAGS_PACKED,99},{100.name = "16 bpp RGB, le",101.fourcc = V4L2_PIX_FMT_RGB565,102.cxformat = ColorFormatRGB16,103.depth = 16,104.flags = FORMAT_FLAGS_PACKED,105},{106.name = "16 bpp RGB, be",107.fourcc = V4L2_PIX_FMT_RGB565X,108.cxformat = ColorFormatRGB16 | ColorFormatBSWAP,109.depth = 16,110.flags = FORMAT_FLAGS_PACKED,111},{112.name = "24 bpp RGB, le",113.fourcc = V4L2_PIX_FMT_BGR24,114.cxformat = ColorFormatRGB24,115.depth = 24,116.flags = FORMAT_FLAGS_PACKED,117},{118.name = "32 bpp RGB, le",119.fourcc = V4L2_PIX_FMT_BGR32,120.cxformat = ColorFormatRGB32,121.depth = 32,122.flags = FORMAT_FLAGS_PACKED,123},{124.name = "32 bpp RGB, be",125.fourcc = V4L2_PIX_FMT_RGB32,126.cxformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP,127.depth = 32,128.flags = FORMAT_FLAGS_PACKED,129},{130.name = "4:2:2, packed, YUYV",131.fourcc = V4L2_PIX_FMT_YUYV,132.cxformat = ColorFormatYUY2,133.depth = 16,134.flags = FORMAT_FLAGS_PACKED,135},{136.name = "4:2:2, packed, UYVY",137.fourcc = V4L2_PIX_FMT_UYVY,138.cxformat = ColorFormatYUY2 | ColorFormatBSWAP,139.depth = 16,140.flags = FORMAT_FLAGS_PACKED,141},142};143144static const struct cx8800_fmt* format_by_fourcc(unsigned int fourcc)145{146unsigned int i;147148for (i = 0; i < ARRAY_SIZE(formats); i++)149if (formats[i].fourcc == fourcc)150return formats+i;151return NULL;152}153154/* ------------------------------------------------------------------- */155156static const struct v4l2_queryctrl no_ctl = {157.name = "42",158.flags = V4L2_CTRL_FLAG_DISABLED,159};160161static const struct cx88_ctrl cx8800_ctls[] = {162/* --- video --- */163{164.v = {165.id = V4L2_CID_BRIGHTNESS,166.name = "Brightness",167.minimum = 0x00,168.maximum = 0xff,169.step = 1,170.default_value = 0x7f,171.type = V4L2_CTRL_TYPE_INTEGER,172},173.off = 128,174.reg = MO_CONTR_BRIGHT,175.mask = 0x00ff,176.shift = 0,177},{178.v = {179.id = V4L2_CID_CONTRAST,180.name = "Contrast",181.minimum = 0,182.maximum = 0xff,183.step = 1,184.default_value = 0x3f,185.type = V4L2_CTRL_TYPE_INTEGER,186},187.off = 0,188.reg = MO_CONTR_BRIGHT,189.mask = 0xff00,190.shift = 8,191},{192.v = {193.id = V4L2_CID_HUE,194.name = "Hue",195.minimum = 0,196.maximum = 0xff,197.step = 1,198.default_value = 0x7f,199.type = V4L2_CTRL_TYPE_INTEGER,200},201.off = 128,202.reg = MO_HUE,203.mask = 0x00ff,204.shift = 0,205},{206/* strictly, this only describes only U saturation.207* V saturation is handled specially through code.208*/209.v = {210.id = V4L2_CID_SATURATION,211.name = "Saturation",212.minimum = 0,213.maximum = 0xff,214.step = 1,215.default_value = 0x7f,216.type = V4L2_CTRL_TYPE_INTEGER,217},218.off = 0,219.reg = MO_UV_SATURATION,220.mask = 0x00ff,221.shift = 0,222},{223.v = {224.id = V4L2_CID_CHROMA_AGC,225.name = "Chroma AGC",226.minimum = 0,227.maximum = 1,228.default_value = 0x1,229.type = V4L2_CTRL_TYPE_BOOLEAN,230},231.reg = MO_INPUT_FORMAT,232.mask = 1 << 10,233.shift = 10,234}, {235.v = {236.id = V4L2_CID_COLOR_KILLER,237.name = "Color killer",238.minimum = 0,239.maximum = 1,240.default_value = 0x1,241.type = V4L2_CTRL_TYPE_BOOLEAN,242},243.reg = MO_INPUT_FORMAT,244.mask = 1 << 9,245.shift = 9,246}, {247/* --- audio --- */248.v = {249.id = V4L2_CID_AUDIO_MUTE,250.name = "Mute",251.minimum = 0,252.maximum = 1,253.default_value = 1,254.type = V4L2_CTRL_TYPE_BOOLEAN,255},256.reg = AUD_VOL_CTL,257.sreg = SHADOW_AUD_VOL_CTL,258.mask = (1 << 6),259.shift = 6,260},{261.v = {262.id = V4L2_CID_AUDIO_VOLUME,263.name = "Volume",264.minimum = 0,265.maximum = 0x3f,266.step = 1,267.default_value = 0x3f,268.type = V4L2_CTRL_TYPE_INTEGER,269},270.reg = AUD_VOL_CTL,271.sreg = SHADOW_AUD_VOL_CTL,272.mask = 0x3f,273.shift = 0,274},{275.v = {276.id = V4L2_CID_AUDIO_BALANCE,277.name = "Balance",278.minimum = 0,279.maximum = 0x7f,280.step = 1,281.default_value = 0x40,282.type = V4L2_CTRL_TYPE_INTEGER,283},284.reg = AUD_BAL_CTL,285.sreg = SHADOW_AUD_BAL_CTL,286.mask = 0x7f,287.shift = 0,288}289};290enum { CX8800_CTLS = ARRAY_SIZE(cx8800_ctls) };291292/* Must be sorted from low to high control ID! */293const u32 cx88_user_ctrls[] = {294V4L2_CID_USER_CLASS,295V4L2_CID_BRIGHTNESS,296V4L2_CID_CONTRAST,297V4L2_CID_SATURATION,298V4L2_CID_HUE,299V4L2_CID_AUDIO_VOLUME,300V4L2_CID_AUDIO_BALANCE,301V4L2_CID_AUDIO_MUTE,302V4L2_CID_CHROMA_AGC,303V4L2_CID_COLOR_KILLER,3040305};306EXPORT_SYMBOL(cx88_user_ctrls);307308static const u32 * const ctrl_classes[] = {309cx88_user_ctrls,310NULL311};312313int cx8800_ctrl_query(struct cx88_core *core, struct v4l2_queryctrl *qctrl)314{315int i;316317if (qctrl->id < V4L2_CID_BASE ||318qctrl->id >= V4L2_CID_LASTP1)319return -EINVAL;320for (i = 0; i < CX8800_CTLS; i++)321if (cx8800_ctls[i].v.id == qctrl->id)322break;323if (i == CX8800_CTLS) {324*qctrl = no_ctl;325return 0;326}327*qctrl = cx8800_ctls[i].v;328/* Report chroma AGC as inactive when SECAM is selected */329if (cx8800_ctls[i].v.id == V4L2_CID_CHROMA_AGC &&330core->tvnorm & V4L2_STD_SECAM)331qctrl->flags |= V4L2_CTRL_FLAG_INACTIVE;332333return 0;334}335EXPORT_SYMBOL(cx8800_ctrl_query);336337/* ------------------------------------------------------------------- */338/* resource management */339340static int res_get(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bit)341{342struct cx88_core *core = dev->core;343if (fh->resources & bit)344/* have it already allocated */345return 1;346347/* is it free? */348mutex_lock(&core->lock);349if (dev->resources & bit) {350/* no, someone else uses it */351mutex_unlock(&core->lock);352return 0;353}354/* it's free, grab it */355fh->resources |= bit;356dev->resources |= bit;357dprintk(1,"res: get %d\n",bit);358mutex_unlock(&core->lock);359return 1;360}361362static363int res_check(struct cx8800_fh *fh, unsigned int bit)364{365return (fh->resources & bit);366}367368static369int res_locked(struct cx8800_dev *dev, unsigned int bit)370{371return (dev->resources & bit);372}373374static375void res_free(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bits)376{377struct cx88_core *core = dev->core;378BUG_ON((fh->resources & bits) != bits);379380mutex_lock(&core->lock);381fh->resources &= ~bits;382dev->resources &= ~bits;383dprintk(1,"res: put %d\n",bits);384mutex_unlock(&core->lock);385}386387/* ------------------------------------------------------------------ */388389int cx88_video_mux(struct cx88_core *core, unsigned int input)390{391/* struct cx88_core *core = dev->core; */392393dprintk(1,"video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n",394input, INPUT(input).vmux,395INPUT(input).gpio0,INPUT(input).gpio1,396INPUT(input).gpio2,INPUT(input).gpio3);397core->input = input;398cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input).vmux << 14);399cx_write(MO_GP3_IO, INPUT(input).gpio3);400cx_write(MO_GP0_IO, INPUT(input).gpio0);401cx_write(MO_GP1_IO, INPUT(input).gpio1);402cx_write(MO_GP2_IO, INPUT(input).gpio2);403404switch (INPUT(input).type) {405case CX88_VMUX_SVIDEO:406cx_set(MO_AFECFG_IO, 0x00000001);407cx_set(MO_INPUT_FORMAT, 0x00010010);408cx_set(MO_FILTER_EVEN, 0x00002020);409cx_set(MO_FILTER_ODD, 0x00002020);410break;411default:412cx_clear(MO_AFECFG_IO, 0x00000001);413cx_clear(MO_INPUT_FORMAT, 0x00010010);414cx_clear(MO_FILTER_EVEN, 0x00002020);415cx_clear(MO_FILTER_ODD, 0x00002020);416break;417}418419/* if there are audioroutes defined, we have an external420ADC to deal with audio */421if (INPUT(input).audioroute) {422/* The wm8775 module has the "2" route hardwired into423the initialization. Some boards may use different424routes for different inputs. HVR-1300 surely does */425if (core->board.audio_chip &&426core->board.audio_chip == V4L2_IDENT_WM8775) {427call_all(core, audio, s_routing,428INPUT(input).audioroute, 0, 0);429}430/* cx2388's C-ADC is connected to the tuner only.431When used with S-Video, that ADC is busy dealing with432chroma, so an external must be used for baseband audio */433if (INPUT(input).type != CX88_VMUX_TELEVISION &&434INPUT(input).type != CX88_VMUX_CABLE) {435/* "I2S ADC mode" */436core->tvaudio = WW_I2SADC;437cx88_set_tvaudio(core);438} else {439/* Normal mode */440cx_write(AUD_I2SCNTL, 0x0);441cx_clear(AUD_CTL, EN_I2SIN_ENABLE);442}443}444445return 0;446}447EXPORT_SYMBOL(cx88_video_mux);448449/* ------------------------------------------------------------------ */450451static int start_video_dma(struct cx8800_dev *dev,452struct cx88_dmaqueue *q,453struct cx88_buffer *buf)454{455struct cx88_core *core = dev->core;456457/* setup fifo + format */458cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21],459buf->bpl, buf->risc.dma);460cx88_set_scale(core, buf->vb.width, buf->vb.height, buf->vb.field);461cx_write(MO_COLOR_CTRL, buf->fmt->cxformat | ColorFormatGamma);462463/* reset counter */464cx_write(MO_VIDY_GPCNTRL,GP_COUNT_CONTROL_RESET);465q->count = 1;466467/* enable irqs */468cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT);469470/* Enables corresponding bits at PCI_INT_STAT:471bits 0 to 4: video, audio, transport stream, VIP, Host472bit 7: timer473bits 8 and 9: DMA complete for: SRC, DST474bits 10 and 11: BERR signal asserted for RISC: RD, WR475bits 12 to 15: BERR signal asserted for: BRDG, SRC, DST, IPB476*/477cx_set(MO_VID_INTMSK, 0x0f0011);478479/* enable capture */480cx_set(VID_CAPTURE_CONTROL,0x06);481482/* start dma */483cx_set(MO_DEV_CNTRL2, (1<<5));484cx_set(MO_VID_DMACNTRL, 0x11); /* Planar Y and packed FIFO and RISC enable */485486return 0;487}488489#ifdef CONFIG_PM490static int stop_video_dma(struct cx8800_dev *dev)491{492struct cx88_core *core = dev->core;493494/* stop dma */495cx_clear(MO_VID_DMACNTRL, 0x11);496497/* disable capture */498cx_clear(VID_CAPTURE_CONTROL,0x06);499500/* disable irqs */501cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT);502cx_clear(MO_VID_INTMSK, 0x0f0011);503return 0;504}505#endif506507static int restart_video_queue(struct cx8800_dev *dev,508struct cx88_dmaqueue *q)509{510struct cx88_core *core = dev->core;511struct cx88_buffer *buf, *prev;512513if (!list_empty(&q->active)) {514buf = list_entry(q->active.next, struct cx88_buffer, vb.queue);515dprintk(2,"restart_queue [%p/%d]: restart dma\n",516buf, buf->vb.i);517start_video_dma(dev, q, buf);518list_for_each_entry(buf, &q->active, vb.queue)519buf->count = q->count++;520mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);521return 0;522}523524prev = NULL;525for (;;) {526if (list_empty(&q->queued))527return 0;528buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue);529if (NULL == prev) {530list_move_tail(&buf->vb.queue, &q->active);531start_video_dma(dev, q, buf);532buf->vb.state = VIDEOBUF_ACTIVE;533buf->count = q->count++;534mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);535dprintk(2,"[%p/%d] restart_queue - first active\n",536buf,buf->vb.i);537538} else if (prev->vb.width == buf->vb.width &&539prev->vb.height == buf->vb.height &&540prev->fmt == buf->fmt) {541list_move_tail(&buf->vb.queue, &q->active);542buf->vb.state = VIDEOBUF_ACTIVE;543buf->count = q->count++;544prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);545dprintk(2,"[%p/%d] restart_queue - move to active\n",546buf,buf->vb.i);547} else {548return 0;549}550prev = buf;551}552}553554/* ------------------------------------------------------------------ */555556static int557buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)558{559struct cx8800_fh *fh = q->priv_data;560561*size = fh->fmt->depth*fh->width*fh->height >> 3;562if (0 == *count)563*count = 32;564if (*size * *count > vid_limit * 1024 * 1024)565*count = (vid_limit * 1024 * 1024) / *size;566return 0;567}568569static int570buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,571enum v4l2_field field)572{573struct cx8800_fh *fh = q->priv_data;574struct cx8800_dev *dev = fh->dev;575struct cx88_core *core = dev->core;576struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb);577struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);578int rc, init_buffer = 0;579580BUG_ON(NULL == fh->fmt);581if (fh->width < 48 || fh->width > norm_maxw(core->tvnorm) ||582fh->height < 32 || fh->height > norm_maxh(core->tvnorm))583return -EINVAL;584buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3;585if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)586return -EINVAL;587588if (buf->fmt != fh->fmt ||589buf->vb.width != fh->width ||590buf->vb.height != fh->height ||591buf->vb.field != field) {592buf->fmt = fh->fmt;593buf->vb.width = fh->width;594buf->vb.height = fh->height;595buf->vb.field = field;596init_buffer = 1;597}598599if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {600init_buffer = 1;601if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL)))602goto fail;603}604605if (init_buffer) {606buf->bpl = buf->vb.width * buf->fmt->depth >> 3;607switch (buf->vb.field) {608case V4L2_FIELD_TOP:609cx88_risc_buffer(dev->pci, &buf->risc,610dma->sglist, 0, UNSET,611buf->bpl, 0, buf->vb.height);612break;613case V4L2_FIELD_BOTTOM:614cx88_risc_buffer(dev->pci, &buf->risc,615dma->sglist, UNSET, 0,616buf->bpl, 0, buf->vb.height);617break;618case V4L2_FIELD_INTERLACED:619cx88_risc_buffer(dev->pci, &buf->risc,620dma->sglist, 0, buf->bpl,621buf->bpl, buf->bpl,622buf->vb.height >> 1);623break;624case V4L2_FIELD_SEQ_TB:625cx88_risc_buffer(dev->pci, &buf->risc,626dma->sglist,6270, buf->bpl * (buf->vb.height >> 1),628buf->bpl, 0,629buf->vb.height >> 1);630break;631case V4L2_FIELD_SEQ_BT:632cx88_risc_buffer(dev->pci, &buf->risc,633dma->sglist,634buf->bpl * (buf->vb.height >> 1), 0,635buf->bpl, 0,636buf->vb.height >> 1);637break;638default:639BUG();640}641}642dprintk(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n",643buf, buf->vb.i,644fh->width, fh->height, fh->fmt->depth, fh->fmt->name,645(unsigned long)buf->risc.dma);646647buf->vb.state = VIDEOBUF_PREPARED;648return 0;649650fail:651cx88_free_buffer(q,buf);652return rc;653}654655static void656buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)657{658struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb);659struct cx88_buffer *prev;660struct cx8800_fh *fh = vq->priv_data;661struct cx8800_dev *dev = fh->dev;662struct cx88_core *core = dev->core;663struct cx88_dmaqueue *q = &dev->vidq;664665/* add jump to stopper */666buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC);667buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma);668669if (!list_empty(&q->queued)) {670list_add_tail(&buf->vb.queue,&q->queued);671buf->vb.state = VIDEOBUF_QUEUED;672dprintk(2,"[%p/%d] buffer_queue - append to queued\n",673buf, buf->vb.i);674675} else if (list_empty(&q->active)) {676list_add_tail(&buf->vb.queue,&q->active);677start_video_dma(dev, q, buf);678buf->vb.state = VIDEOBUF_ACTIVE;679buf->count = q->count++;680mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);681dprintk(2,"[%p/%d] buffer_queue - first active\n",682buf, buf->vb.i);683684} else {685prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue);686if (prev->vb.width == buf->vb.width &&687prev->vb.height == buf->vb.height &&688prev->fmt == buf->fmt) {689list_add_tail(&buf->vb.queue,&q->active);690buf->vb.state = VIDEOBUF_ACTIVE;691buf->count = q->count++;692prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);693dprintk(2,"[%p/%d] buffer_queue - append to active\n",694buf, buf->vb.i);695696} else {697list_add_tail(&buf->vb.queue,&q->queued);698buf->vb.state = VIDEOBUF_QUEUED;699dprintk(2,"[%p/%d] buffer_queue - first queued\n",700buf, buf->vb.i);701}702}703}704705static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb)706{707struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb);708709cx88_free_buffer(q,buf);710}711712static const struct videobuf_queue_ops cx8800_video_qops = {713.buf_setup = buffer_setup,714.buf_prepare = buffer_prepare,715.buf_queue = buffer_queue,716.buf_release = buffer_release,717};718719/* ------------------------------------------------------------------ */720721722/* ------------------------------------------------------------------ */723724static struct videobuf_queue* get_queue(struct cx8800_fh *fh)725{726switch (fh->type) {727case V4L2_BUF_TYPE_VIDEO_CAPTURE:728return &fh->vidq;729case V4L2_BUF_TYPE_VBI_CAPTURE:730return &fh->vbiq;731default:732BUG();733return NULL;734}735}736737static int get_ressource(struct cx8800_fh *fh)738{739switch (fh->type) {740case V4L2_BUF_TYPE_VIDEO_CAPTURE:741return RESOURCE_VIDEO;742case V4L2_BUF_TYPE_VBI_CAPTURE:743return RESOURCE_VBI;744default:745BUG();746return 0;747}748}749750static int video_open(struct file *file)751{752struct video_device *vdev = video_devdata(file);753struct cx8800_dev *dev = video_drvdata(file);754struct cx88_core *core = dev->core;755struct cx8800_fh *fh;756enum v4l2_buf_type type = 0;757int radio = 0;758759switch (vdev->vfl_type) {760case VFL_TYPE_GRABBER:761type = V4L2_BUF_TYPE_VIDEO_CAPTURE;762break;763case VFL_TYPE_VBI:764type = V4L2_BUF_TYPE_VBI_CAPTURE;765break;766case VFL_TYPE_RADIO:767radio = 1;768break;769}770771dprintk(1, "open dev=%s radio=%d type=%s\n",772video_device_node_name(vdev), radio, v4l2_type_names[type]);773774/* allocate + initialize per filehandle data */775fh = kzalloc(sizeof(*fh),GFP_KERNEL);776if (unlikely(!fh))777return -ENOMEM;778779file->private_data = fh;780fh->dev = dev;781fh->radio = radio;782fh->type = type;783fh->width = 320;784fh->height = 240;785fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);786787mutex_lock(&core->lock);788789videobuf_queue_sg_init(&fh->vidq, &cx8800_video_qops,790&dev->pci->dev, &dev->slock,791V4L2_BUF_TYPE_VIDEO_CAPTURE,792V4L2_FIELD_INTERLACED,793sizeof(struct cx88_buffer),794fh, NULL);795videobuf_queue_sg_init(&fh->vbiq, &cx8800_vbi_qops,796&dev->pci->dev, &dev->slock,797V4L2_BUF_TYPE_VBI_CAPTURE,798V4L2_FIELD_SEQ_TB,799sizeof(struct cx88_buffer),800fh, NULL);801802if (fh->radio) {803dprintk(1,"video_open: setting radio device\n");804cx_write(MO_GP3_IO, core->board.radio.gpio3);805cx_write(MO_GP0_IO, core->board.radio.gpio0);806cx_write(MO_GP1_IO, core->board.radio.gpio1);807cx_write(MO_GP2_IO, core->board.radio.gpio2);808if (core->board.radio.audioroute) {809if(core->board.audio_chip &&810core->board.audio_chip == V4L2_IDENT_WM8775) {811call_all(core, audio, s_routing,812core->board.radio.audioroute, 0, 0);813}814/* "I2S ADC mode" */815core->tvaudio = WW_I2SADC;816cx88_set_tvaudio(core);817} else {818/* FM Mode */819core->tvaudio = WW_FM;820cx88_set_tvaudio(core);821cx88_set_stereo(core,V4L2_TUNER_MODE_STEREO,1);822}823call_all(core, tuner, s_radio);824}825826core->users++;827mutex_unlock(&core->lock);828829return 0;830}831832static ssize_t833video_read(struct file *file, char __user *data, size_t count, loff_t *ppos)834{835struct cx8800_fh *fh = file->private_data;836837switch (fh->type) {838case V4L2_BUF_TYPE_VIDEO_CAPTURE:839if (res_locked(fh->dev,RESOURCE_VIDEO))840return -EBUSY;841return videobuf_read_one(&fh->vidq, data, count, ppos,842file->f_flags & O_NONBLOCK);843case V4L2_BUF_TYPE_VBI_CAPTURE:844if (!res_get(fh->dev,fh,RESOURCE_VBI))845return -EBUSY;846return videobuf_read_stream(&fh->vbiq, data, count, ppos, 1,847file->f_flags & O_NONBLOCK);848default:849BUG();850return 0;851}852}853854static unsigned int855video_poll(struct file *file, struct poll_table_struct *wait)856{857struct cx8800_fh *fh = file->private_data;858struct cx88_buffer *buf;859unsigned int rc = POLLERR;860861if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) {862if (!res_get(fh->dev,fh,RESOURCE_VBI))863return POLLERR;864return videobuf_poll_stream(file, &fh->vbiq, wait);865}866867mutex_lock(&fh->vidq.vb_lock);868if (res_check(fh,RESOURCE_VIDEO)) {869/* streaming capture */870if (list_empty(&fh->vidq.stream))871goto done;872buf = list_entry(fh->vidq.stream.next,struct cx88_buffer,vb.stream);873} else {874/* read() capture */875buf = (struct cx88_buffer*)fh->vidq.read_buf;876if (NULL == buf)877goto done;878}879poll_wait(file, &buf->vb.done, wait);880if (buf->vb.state == VIDEOBUF_DONE ||881buf->vb.state == VIDEOBUF_ERROR)882rc = POLLIN|POLLRDNORM;883else884rc = 0;885done:886mutex_unlock(&fh->vidq.vb_lock);887return rc;888}889890static int video_release(struct file *file)891{892struct cx8800_fh *fh = file->private_data;893struct cx8800_dev *dev = fh->dev;894895/* turn off overlay */896if (res_check(fh, RESOURCE_OVERLAY)) {897/* FIXME */898res_free(dev,fh,RESOURCE_OVERLAY);899}900901/* stop video capture */902if (res_check(fh, RESOURCE_VIDEO)) {903videobuf_queue_cancel(&fh->vidq);904res_free(dev,fh,RESOURCE_VIDEO);905}906if (fh->vidq.read_buf) {907buffer_release(&fh->vidq,fh->vidq.read_buf);908kfree(fh->vidq.read_buf);909}910911/* stop vbi capture */912if (res_check(fh, RESOURCE_VBI)) {913videobuf_stop(&fh->vbiq);914res_free(dev,fh,RESOURCE_VBI);915}916917videobuf_mmap_free(&fh->vidq);918videobuf_mmap_free(&fh->vbiq);919920mutex_lock(&dev->core->lock);921file->private_data = NULL;922kfree(fh);923924dev->core->users--;925if (!dev->core->users)926call_all(dev->core, core, s_power, 0);927mutex_unlock(&dev->core->lock);928929return 0;930}931932static int933video_mmap(struct file *file, struct vm_area_struct * vma)934{935struct cx8800_fh *fh = file->private_data;936937return videobuf_mmap_mapper(get_queue(fh), vma);938}939940/* ------------------------------------------------------------------ */941/* VIDEO CTRL IOCTLS */942943int cx88_get_control (struct cx88_core *core, struct v4l2_control *ctl)944{945const struct cx88_ctrl *c = NULL;946u32 value;947int i;948949for (i = 0; i < CX8800_CTLS; i++)950if (cx8800_ctls[i].v.id == ctl->id)951c = &cx8800_ctls[i];952if (unlikely(NULL == c))953return -EINVAL;954955value = c->sreg ? cx_sread(c->sreg) : cx_read(c->reg);956switch (ctl->id) {957case V4L2_CID_AUDIO_BALANCE:958ctl->value = ((value & 0x7f) < 0x40) ? ((value & 0x7f) + 0x40)959: (0x7f - (value & 0x7f));960break;961case V4L2_CID_AUDIO_VOLUME:962ctl->value = 0x3f - (value & 0x3f);963break;964default:965ctl->value = ((value + (c->off << c->shift)) & c->mask) >> c->shift;966break;967}968dprintk(1,"get_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",969ctl->id, c->v.name, ctl->value, c->reg,970value,c->mask, c->sreg ? " [shadowed]" : "");971return 0;972}973EXPORT_SYMBOL(cx88_get_control);974975int cx88_set_control(struct cx88_core *core, struct v4l2_control *ctl)976{977const struct cx88_ctrl *c = NULL;978u32 value,mask;979int i;980981for (i = 0; i < CX8800_CTLS; i++) {982if (cx8800_ctls[i].v.id == ctl->id) {983c = &cx8800_ctls[i];984}985}986if (unlikely(NULL == c))987return -EINVAL;988989if (ctl->value < c->v.minimum)990ctl->value = c->v.minimum;991if (ctl->value > c->v.maximum)992ctl->value = c->v.maximum;993994/* Pass changes onto any WM8775 */995if (core->board.audio_chip == V4L2_IDENT_WM8775) {996struct v4l2_control client_ctl;997memset(&client_ctl, 0, sizeof(client_ctl));998client_ctl.id = ctl->id;9991000switch (ctl->id) {1001case V4L2_CID_AUDIO_MUTE:1002client_ctl.value = ctl->value;1003break;1004case V4L2_CID_AUDIO_VOLUME:1005client_ctl.value = (ctl->value) ?1006(0x90 + ctl->value) << 8 : 0;1007break;1008case V4L2_CID_AUDIO_BALANCE:1009client_ctl.value = ctl->value << 9;1010break;1011default:1012client_ctl.id = 0;1013break;1014}1015if (client_ctl.id)1016call_hw(core, WM8775_GID, core, s_ctrl, &client_ctl);1017}10181019mask=c->mask;1020switch (ctl->id) {1021case V4L2_CID_AUDIO_BALANCE:1022value = (ctl->value < 0x40) ? (0x7f - ctl->value) : (ctl->value - 0x40);1023break;1024case V4L2_CID_AUDIO_VOLUME:1025value = 0x3f - (ctl->value & 0x3f);1026break;1027case V4L2_CID_SATURATION:1028/* special v_sat handling */10291030value = ((ctl->value - c->off) << c->shift) & c->mask;10311032if (core->tvnorm & V4L2_STD_SECAM) {1033/* For SECAM, both U and V sat should be equal */1034value=value<<8|value;1035} else {1036/* Keeps U Saturation proportional to V Sat */1037value=(value*0x5a)/0x7f<<8|value;1038}1039mask=0xffff;1040break;1041case V4L2_CID_CHROMA_AGC:1042/* Do not allow chroma AGC to be enabled for SECAM */1043value = ((ctl->value - c->off) << c->shift) & c->mask;1044if (core->tvnorm & V4L2_STD_SECAM && value)1045return -EINVAL;1046break;1047default:1048value = ((ctl->value - c->off) << c->shift) & c->mask;1049break;1050}1051dprintk(1,"set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",1052ctl->id, c->v.name, ctl->value, c->reg, value,1053mask, c->sreg ? " [shadowed]" : "");1054if (c->sreg) {1055cx_sandor(c->sreg, c->reg, mask, value);1056} else {1057cx_andor(c->reg, mask, value);1058}1059return 0;1060}1061EXPORT_SYMBOL(cx88_set_control);10621063static void init_controls(struct cx88_core *core)1064{1065struct v4l2_control ctrl;1066int i;10671068for (i = 0; i < CX8800_CTLS; i++) {1069ctrl.id=cx8800_ctls[i].v.id;1070ctrl.value=cx8800_ctls[i].v.default_value;10711072cx88_set_control(core, &ctrl);1073}1074}10751076/* ------------------------------------------------------------------ */1077/* VIDEO IOCTLS */10781079static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,1080struct v4l2_format *f)1081{1082struct cx8800_fh *fh = priv;10831084f->fmt.pix.width = fh->width;1085f->fmt.pix.height = fh->height;1086f->fmt.pix.field = fh->vidq.field;1087f->fmt.pix.pixelformat = fh->fmt->fourcc;1088f->fmt.pix.bytesperline =1089(f->fmt.pix.width * fh->fmt->depth) >> 3;1090f->fmt.pix.sizeimage =1091f->fmt.pix.height * f->fmt.pix.bytesperline;1092return 0;1093}10941095static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,1096struct v4l2_format *f)1097{1098struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;1099const struct cx8800_fmt *fmt;1100enum v4l2_field field;1101unsigned int maxw, maxh;11021103fmt = format_by_fourcc(f->fmt.pix.pixelformat);1104if (NULL == fmt)1105return -EINVAL;11061107field = f->fmt.pix.field;1108maxw = norm_maxw(core->tvnorm);1109maxh = norm_maxh(core->tvnorm);11101111if (V4L2_FIELD_ANY == field) {1112field = (f->fmt.pix.height > maxh/2)1113? V4L2_FIELD_INTERLACED1114: V4L2_FIELD_BOTTOM;1115}11161117switch (field) {1118case V4L2_FIELD_TOP:1119case V4L2_FIELD_BOTTOM:1120maxh = maxh / 2;1121break;1122case V4L2_FIELD_INTERLACED:1123break;1124default:1125return -EINVAL;1126}11271128f->fmt.pix.field = field;1129v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,1130&f->fmt.pix.height, 32, maxh, 0, 0);1131f->fmt.pix.bytesperline =1132(f->fmt.pix.width * fmt->depth) >> 3;1133f->fmt.pix.sizeimage =1134f->fmt.pix.height * f->fmt.pix.bytesperline;11351136return 0;1137}11381139static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,1140struct v4l2_format *f)1141{1142struct cx8800_fh *fh = priv;1143int err = vidioc_try_fmt_vid_cap (file,priv,f);11441145if (0 != err)1146return err;1147fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat);1148fh->width = f->fmt.pix.width;1149fh->height = f->fmt.pix.height;1150fh->vidq.field = f->fmt.pix.field;1151return 0;1152}11531154static int vidioc_querycap (struct file *file, void *priv,1155struct v4l2_capability *cap)1156{1157struct cx8800_dev *dev = ((struct cx8800_fh *)priv)->dev;1158struct cx88_core *core = dev->core;11591160strcpy(cap->driver, "cx8800");1161strlcpy(cap->card, core->board.name, sizeof(cap->card));1162sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci));1163cap->version = CX88_VERSION_CODE;1164cap->capabilities =1165V4L2_CAP_VIDEO_CAPTURE |1166V4L2_CAP_READWRITE |1167V4L2_CAP_STREAMING |1168V4L2_CAP_VBI_CAPTURE;1169if (UNSET != core->board.tuner_type)1170cap->capabilities |= V4L2_CAP_TUNER;1171return 0;1172}11731174static int vidioc_enum_fmt_vid_cap (struct file *file, void *priv,1175struct v4l2_fmtdesc *f)1176{1177if (unlikely(f->index >= ARRAY_SIZE(formats)))1178return -EINVAL;11791180strlcpy(f->description,formats[f->index].name,sizeof(f->description));1181f->pixelformat = formats[f->index].fourcc;11821183return 0;1184}11851186static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p)1187{1188struct cx8800_fh *fh = priv;1189return (videobuf_reqbufs(get_queue(fh), p));1190}11911192static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p)1193{1194struct cx8800_fh *fh = priv;1195return (videobuf_querybuf(get_queue(fh), p));1196}11971198static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p)1199{1200struct cx8800_fh *fh = priv;1201return (videobuf_qbuf(get_queue(fh), p));1202}12031204static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p)1205{1206struct cx8800_fh *fh = priv;1207return (videobuf_dqbuf(get_queue(fh), p,1208file->f_flags & O_NONBLOCK));1209}12101211static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)1212{1213struct cx8800_fh *fh = priv;1214struct cx8800_dev *dev = fh->dev;12151216/* We should remember that this driver also supports teletext, */1217/* so we have to test if the v4l2_buf_type is VBI capture data. */1218if (unlikely((fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) &&1219(fh->type != V4L2_BUF_TYPE_VBI_CAPTURE)))1220return -EINVAL;12211222if (unlikely(i != fh->type))1223return -EINVAL;12241225if (unlikely(!res_get(dev,fh,get_ressource(fh))))1226return -EBUSY;1227return videobuf_streamon(get_queue(fh));1228}12291230static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)1231{1232struct cx8800_fh *fh = priv;1233struct cx8800_dev *dev = fh->dev;1234int err, res;12351236if ((fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) &&1237(fh->type != V4L2_BUF_TYPE_VBI_CAPTURE))1238return -EINVAL;12391240if (i != fh->type)1241return -EINVAL;12421243res = get_ressource(fh);1244err = videobuf_streamoff(get_queue(fh));1245if (err < 0)1246return err;1247res_free(dev,fh,res);1248return 0;1249}12501251static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *tvnorms)1252{1253struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;12541255mutex_lock(&core->lock);1256cx88_set_tvnorm(core,*tvnorms);1257mutex_unlock(&core->lock);12581259return 0;1260}12611262/* only one input in this sample driver */1263int cx88_enum_input (struct cx88_core *core,struct v4l2_input *i)1264{1265static const char * const iname[] = {1266[ CX88_VMUX_COMPOSITE1 ] = "Composite1",1267[ CX88_VMUX_COMPOSITE2 ] = "Composite2",1268[ CX88_VMUX_COMPOSITE3 ] = "Composite3",1269[ CX88_VMUX_COMPOSITE4 ] = "Composite4",1270[ CX88_VMUX_SVIDEO ] = "S-Video",1271[ CX88_VMUX_TELEVISION ] = "Television",1272[ CX88_VMUX_CABLE ] = "Cable TV",1273[ CX88_VMUX_DVB ] = "DVB",1274[ CX88_VMUX_DEBUG ] = "for debug only",1275};1276unsigned int n = i->index;12771278if (n >= 4)1279return -EINVAL;1280if (0 == INPUT(n).type)1281return -EINVAL;1282i->type = V4L2_INPUT_TYPE_CAMERA;1283strcpy(i->name,iname[INPUT(n).type]);1284if ((CX88_VMUX_TELEVISION == INPUT(n).type) ||1285(CX88_VMUX_CABLE == INPUT(n).type)) {1286i->type = V4L2_INPUT_TYPE_TUNER;1287i->std = CX88_NORMS;1288}1289return 0;1290}1291EXPORT_SYMBOL(cx88_enum_input);12921293static int vidioc_enum_input (struct file *file, void *priv,1294struct v4l2_input *i)1295{1296struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;1297return cx88_enum_input (core,i);1298}12991300static int vidioc_g_input (struct file *file, void *priv, unsigned int *i)1301{1302struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;13031304*i = core->input;1305return 0;1306}13071308static int vidioc_s_input (struct file *file, void *priv, unsigned int i)1309{1310struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;13111312if (i >= 4)1313return -EINVAL;13141315mutex_lock(&core->lock);1316cx88_newstation(core);1317cx88_video_mux(core,i);1318mutex_unlock(&core->lock);1319return 0;1320}1321132213231324static int vidioc_queryctrl (struct file *file, void *priv,1325struct v4l2_queryctrl *qctrl)1326{1327struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;13281329qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id);1330if (unlikely(qctrl->id == 0))1331return -EINVAL;1332return cx8800_ctrl_query(core, qctrl);1333}13341335static int vidioc_g_ctrl (struct file *file, void *priv,1336struct v4l2_control *ctl)1337{1338struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;1339return1340cx88_get_control(core,ctl);1341}13421343static int vidioc_s_ctrl (struct file *file, void *priv,1344struct v4l2_control *ctl)1345{1346struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;1347return1348cx88_set_control(core,ctl);1349}13501351static int vidioc_g_tuner (struct file *file, void *priv,1352struct v4l2_tuner *t)1353{1354struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;1355u32 reg;13561357if (unlikely(UNSET == core->board.tuner_type))1358return -EINVAL;1359if (0 != t->index)1360return -EINVAL;13611362strcpy(t->name, "Television");1363t->type = V4L2_TUNER_ANALOG_TV;1364t->capability = V4L2_TUNER_CAP_NORM;1365t->rangehigh = 0xffffffffUL;13661367cx88_get_stereo(core ,t);1368reg = cx_read(MO_DEVICE_STATUS);1369t->signal = (reg & (1<<5)) ? 0xffff : 0x0000;1370return 0;1371}13721373static int vidioc_s_tuner (struct file *file, void *priv,1374struct v4l2_tuner *t)1375{1376struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;13771378if (UNSET == core->board.tuner_type)1379return -EINVAL;1380if (0 != t->index)1381return -EINVAL;13821383cx88_set_stereo(core, t->audmode, 1);1384return 0;1385}13861387static int vidioc_g_frequency (struct file *file, void *priv,1388struct v4l2_frequency *f)1389{1390struct cx8800_fh *fh = priv;1391struct cx88_core *core = fh->dev->core;13921393if (unlikely(UNSET == core->board.tuner_type))1394return -EINVAL;13951396/* f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; */1397f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;1398f->frequency = core->freq;13991400call_all(core, tuner, g_frequency, f);14011402return 0;1403}14041405int cx88_set_freq (struct cx88_core *core,1406struct v4l2_frequency *f)1407{1408if (unlikely(UNSET == core->board.tuner_type))1409return -EINVAL;1410if (unlikely(f->tuner != 0))1411return -EINVAL;14121413mutex_lock(&core->lock);1414core->freq = f->frequency;1415cx88_newstation(core);1416call_all(core, tuner, s_frequency, f);14171418/* When changing channels it is required to reset TVAUDIO */1419msleep (10);1420cx88_set_tvaudio(core);14211422mutex_unlock(&core->lock);14231424return 0;1425}1426EXPORT_SYMBOL(cx88_set_freq);14271428static int vidioc_s_frequency (struct file *file, void *priv,1429struct v4l2_frequency *f)1430{1431struct cx8800_fh *fh = priv;1432struct cx88_core *core = fh->dev->core;14331434if (unlikely(0 == fh->radio && f->type != V4L2_TUNER_ANALOG_TV))1435return -EINVAL;1436if (unlikely(1 == fh->radio && f->type != V4L2_TUNER_RADIO))1437return -EINVAL;14381439return1440cx88_set_freq (core,f);1441}14421443#ifdef CONFIG_VIDEO_ADV_DEBUG1444static int vidioc_g_register (struct file *file, void *fh,1445struct v4l2_dbg_register *reg)1446{1447struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core;14481449if (!v4l2_chip_match_host(®->match))1450return -EINVAL;1451/* cx2388x has a 24-bit register space */1452reg->val = cx_read(reg->reg & 0xffffff);1453reg->size = 4;1454return 0;1455}14561457static int vidioc_s_register (struct file *file, void *fh,1458struct v4l2_dbg_register *reg)1459{1460struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core;14611462if (!v4l2_chip_match_host(®->match))1463return -EINVAL;1464cx_write(reg->reg & 0xffffff, reg->val);1465return 0;1466}1467#endif14681469/* ----------------------------------------------------------- */1470/* RADIO ESPECIFIC IOCTLS */1471/* ----------------------------------------------------------- */14721473static int radio_querycap (struct file *file, void *priv,1474struct v4l2_capability *cap)1475{1476struct cx8800_dev *dev = ((struct cx8800_fh *)priv)->dev;1477struct cx88_core *core = dev->core;14781479strcpy(cap->driver, "cx8800");1480strlcpy(cap->card, core->board.name, sizeof(cap->card));1481sprintf(cap->bus_info,"PCI:%s", pci_name(dev->pci));1482cap->version = CX88_VERSION_CODE;1483cap->capabilities = V4L2_CAP_TUNER;1484return 0;1485}14861487static int radio_g_tuner (struct file *file, void *priv,1488struct v4l2_tuner *t)1489{1490struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;14911492if (unlikely(t->index > 0))1493return -EINVAL;14941495strcpy(t->name, "Radio");1496t->type = V4L2_TUNER_RADIO;14971498call_all(core, tuner, g_tuner, t);1499return 0;1500}15011502static int radio_enum_input (struct file *file, void *priv,1503struct v4l2_input *i)1504{1505if (i->index != 0)1506return -EINVAL;1507strcpy(i->name,"Radio");1508i->type = V4L2_INPUT_TYPE_TUNER;15091510return 0;1511}15121513static int radio_g_audio (struct file *file, void *priv, struct v4l2_audio *a)1514{1515if (unlikely(a->index))1516return -EINVAL;15171518strcpy(a->name,"Radio");1519return 0;1520}15211522/* FIXME: Should add a standard for radio */15231524static int radio_s_tuner (struct file *file, void *priv,1525struct v4l2_tuner *t)1526{1527struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core;15281529if (0 != t->index)1530return -EINVAL;15311532call_all(core, tuner, s_tuner, t);15331534return 0;1535}15361537static int radio_s_audio (struct file *file, void *fh,1538struct v4l2_audio *a)1539{1540return 0;1541}15421543static int radio_s_input (struct file *file, void *fh, unsigned int i)1544{1545return 0;1546}15471548static int radio_queryctrl (struct file *file, void *priv,1549struct v4l2_queryctrl *c)1550{1551int i;15521553if (c->id < V4L2_CID_BASE ||1554c->id >= V4L2_CID_LASTP1)1555return -EINVAL;1556if (c->id == V4L2_CID_AUDIO_MUTE ||1557c->id == V4L2_CID_AUDIO_VOLUME ||1558c->id == V4L2_CID_AUDIO_BALANCE) {1559for (i = 0; i < CX8800_CTLS; i++) {1560if (cx8800_ctls[i].v.id == c->id)1561break;1562}1563if (i == CX8800_CTLS)1564return -EINVAL;1565*c = cx8800_ctls[i].v;1566} else1567*c = no_ctl;1568return 0;1569}15701571/* ----------------------------------------------------------- */15721573static void cx8800_vid_timeout(unsigned long data)1574{1575struct cx8800_dev *dev = (struct cx8800_dev*)data;1576struct cx88_core *core = dev->core;1577struct cx88_dmaqueue *q = &dev->vidq;1578struct cx88_buffer *buf;1579unsigned long flags;15801581cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]);15821583cx_clear(MO_VID_DMACNTRL, 0x11);1584cx_clear(VID_CAPTURE_CONTROL, 0x06);15851586spin_lock_irqsave(&dev->slock,flags);1587while (!list_empty(&q->active)) {1588buf = list_entry(q->active.next, struct cx88_buffer, vb.queue);1589list_del(&buf->vb.queue);1590buf->vb.state = VIDEOBUF_ERROR;1591wake_up(&buf->vb.done);1592printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", core->name,1593buf, buf->vb.i, (unsigned long)buf->risc.dma);1594}1595restart_video_queue(dev,q);1596spin_unlock_irqrestore(&dev->slock,flags);1597}15981599static const char *cx88_vid_irqs[32] = {1600"y_risci1", "u_risci1", "v_risci1", "vbi_risc1",1601"y_risci2", "u_risci2", "v_risci2", "vbi_risc2",1602"y_oflow", "u_oflow", "v_oflow", "vbi_oflow",1603"y_sync", "u_sync", "v_sync", "vbi_sync",1604"opc_err", "par_err", "rip_err", "pci_abort",1605};16061607static void cx8800_vid_irq(struct cx8800_dev *dev)1608{1609struct cx88_core *core = dev->core;1610u32 status, mask, count;16111612status = cx_read(MO_VID_INTSTAT);1613mask = cx_read(MO_VID_INTMSK);1614if (0 == (status & mask))1615return;1616cx_write(MO_VID_INTSTAT, status);1617if (irq_debug || (status & mask & ~0xff))1618cx88_print_irqbits(core->name, "irq vid",1619cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs),1620status, mask);16211622/* risc op code error */1623if (status & (1 << 16)) {1624printk(KERN_WARNING "%s/0: video risc op code error\n",core->name);1625cx_clear(MO_VID_DMACNTRL, 0x11);1626cx_clear(VID_CAPTURE_CONTROL, 0x06);1627cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]);1628}16291630/* risc1 y */1631if (status & 0x01) {1632spin_lock(&dev->slock);1633count = cx_read(MO_VIDY_GPCNT);1634cx88_wakeup(core, &dev->vidq, count);1635spin_unlock(&dev->slock);1636}16371638/* risc1 vbi */1639if (status & 0x08) {1640spin_lock(&dev->slock);1641count = cx_read(MO_VBI_GPCNT);1642cx88_wakeup(core, &dev->vbiq, count);1643spin_unlock(&dev->slock);1644}16451646/* risc2 y */1647if (status & 0x10) {1648dprintk(2,"stopper video\n");1649spin_lock(&dev->slock);1650restart_video_queue(dev,&dev->vidq);1651spin_unlock(&dev->slock);1652}16531654/* risc2 vbi */1655if (status & 0x80) {1656dprintk(2,"stopper vbi\n");1657spin_lock(&dev->slock);1658cx8800_restart_vbi_queue(dev,&dev->vbiq);1659spin_unlock(&dev->slock);1660}1661}16621663static irqreturn_t cx8800_irq(int irq, void *dev_id)1664{1665struct cx8800_dev *dev = dev_id;1666struct cx88_core *core = dev->core;1667u32 status;1668int loop, handled = 0;16691670for (loop = 0; loop < 10; loop++) {1671status = cx_read(MO_PCI_INTSTAT) &1672(core->pci_irqmask | PCI_INT_VIDINT);1673if (0 == status)1674goto out;1675cx_write(MO_PCI_INTSTAT, status);1676handled = 1;16771678if (status & core->pci_irqmask)1679cx88_core_irq(core,status);1680if (status & PCI_INT_VIDINT)1681cx8800_vid_irq(dev);1682};1683if (10 == loop) {1684printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n",1685core->name);1686cx_write(MO_PCI_INTMSK,0);1687}16881689out:1690return IRQ_RETVAL(handled);1691}16921693/* ----------------------------------------------------------- */1694/* exported stuff */16951696static const struct v4l2_file_operations video_fops =1697{1698.owner = THIS_MODULE,1699.open = video_open,1700.release = video_release,1701.read = video_read,1702.poll = video_poll,1703.mmap = video_mmap,1704.unlocked_ioctl = video_ioctl2,1705};17061707static const struct v4l2_ioctl_ops video_ioctl_ops = {1708.vidioc_querycap = vidioc_querycap,1709.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,1710.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,1711.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,1712.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,1713.vidioc_g_fmt_vbi_cap = cx8800_vbi_fmt,1714.vidioc_try_fmt_vbi_cap = cx8800_vbi_fmt,1715.vidioc_s_fmt_vbi_cap = cx8800_vbi_fmt,1716.vidioc_reqbufs = vidioc_reqbufs,1717.vidioc_querybuf = vidioc_querybuf,1718.vidioc_qbuf = vidioc_qbuf,1719.vidioc_dqbuf = vidioc_dqbuf,1720.vidioc_s_std = vidioc_s_std,1721.vidioc_enum_input = vidioc_enum_input,1722.vidioc_g_input = vidioc_g_input,1723.vidioc_s_input = vidioc_s_input,1724.vidioc_queryctrl = vidioc_queryctrl,1725.vidioc_g_ctrl = vidioc_g_ctrl,1726.vidioc_s_ctrl = vidioc_s_ctrl,1727.vidioc_streamon = vidioc_streamon,1728.vidioc_streamoff = vidioc_streamoff,1729.vidioc_g_tuner = vidioc_g_tuner,1730.vidioc_s_tuner = vidioc_s_tuner,1731.vidioc_g_frequency = vidioc_g_frequency,1732.vidioc_s_frequency = vidioc_s_frequency,1733#ifdef CONFIG_VIDEO_ADV_DEBUG1734.vidioc_g_register = vidioc_g_register,1735.vidioc_s_register = vidioc_s_register,1736#endif1737};17381739static struct video_device cx8800_vbi_template;17401741static const struct video_device cx8800_video_template = {1742.name = "cx8800-video",1743.fops = &video_fops,1744.ioctl_ops = &video_ioctl_ops,1745.tvnorms = CX88_NORMS,1746.current_norm = V4L2_STD_NTSC_M,1747};17481749static const struct v4l2_file_operations radio_fops =1750{1751.owner = THIS_MODULE,1752.open = video_open,1753.release = video_release,1754.unlocked_ioctl = video_ioctl2,1755};17561757static const struct v4l2_ioctl_ops radio_ioctl_ops = {1758.vidioc_querycap = radio_querycap,1759.vidioc_g_tuner = radio_g_tuner,1760.vidioc_enum_input = radio_enum_input,1761.vidioc_g_audio = radio_g_audio,1762.vidioc_s_tuner = radio_s_tuner,1763.vidioc_s_audio = radio_s_audio,1764.vidioc_s_input = radio_s_input,1765.vidioc_queryctrl = radio_queryctrl,1766.vidioc_g_ctrl = vidioc_g_ctrl,1767.vidioc_s_ctrl = vidioc_s_ctrl,1768.vidioc_g_frequency = vidioc_g_frequency,1769.vidioc_s_frequency = vidioc_s_frequency,1770#ifdef CONFIG_VIDEO_ADV_DEBUG1771.vidioc_g_register = vidioc_g_register,1772.vidioc_s_register = vidioc_s_register,1773#endif1774};17751776static const struct video_device cx8800_radio_template = {1777.name = "cx8800-radio",1778.fops = &radio_fops,1779.ioctl_ops = &radio_ioctl_ops,1780};17811782/* ----------------------------------------------------------- */17831784static void cx8800_unregister_video(struct cx8800_dev *dev)1785{1786if (dev->radio_dev) {1787if (video_is_registered(dev->radio_dev))1788video_unregister_device(dev->radio_dev);1789else1790video_device_release(dev->radio_dev);1791dev->radio_dev = NULL;1792}1793if (dev->vbi_dev) {1794if (video_is_registered(dev->vbi_dev))1795video_unregister_device(dev->vbi_dev);1796else1797video_device_release(dev->vbi_dev);1798dev->vbi_dev = NULL;1799}1800if (dev->video_dev) {1801if (video_is_registered(dev->video_dev))1802video_unregister_device(dev->video_dev);1803else1804video_device_release(dev->video_dev);1805dev->video_dev = NULL;1806}1807}18081809static int __devinit cx8800_initdev(struct pci_dev *pci_dev,1810const struct pci_device_id *pci_id)1811{1812struct cx8800_dev *dev;1813struct cx88_core *core;18141815int err;18161817dev = kzalloc(sizeof(*dev),GFP_KERNEL);1818if (NULL == dev)1819return -ENOMEM;18201821/* pci init */1822dev->pci = pci_dev;1823if (pci_enable_device(pci_dev)) {1824err = -EIO;1825goto fail_free;1826}1827core = cx88_core_get(dev->pci);1828if (NULL == core) {1829err = -EINVAL;1830goto fail_free;1831}1832dev->core = core;18331834/* print pci info */1835dev->pci_rev = pci_dev->revision;1836pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat);1837printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, "1838"latency: %d, mmio: 0x%llx\n", core->name,1839pci_name(pci_dev), dev->pci_rev, pci_dev->irq,1840dev->pci_lat,(unsigned long long)pci_resource_start(pci_dev,0));18411842pci_set_master(pci_dev);1843if (!pci_dma_supported(pci_dev,DMA_BIT_MASK(32))) {1844printk("%s/0: Oops: no 32bit PCI DMA ???\n",core->name);1845err = -EIO;1846goto fail_core;1847}18481849/* Initialize VBI template */1850memcpy( &cx8800_vbi_template, &cx8800_video_template,1851sizeof(cx8800_vbi_template) );1852strcpy(cx8800_vbi_template.name,"cx8800-vbi");18531854/* initialize driver struct */1855spin_lock_init(&dev->slock);1856core->tvnorm = cx8800_video_template.current_norm;18571858/* init video dma queues */1859INIT_LIST_HEAD(&dev->vidq.active);1860INIT_LIST_HEAD(&dev->vidq.queued);1861dev->vidq.timeout.function = cx8800_vid_timeout;1862dev->vidq.timeout.data = (unsigned long)dev;1863init_timer(&dev->vidq.timeout);1864cx88_risc_stopper(dev->pci,&dev->vidq.stopper,1865MO_VID_DMACNTRL,0x11,0x00);18661867/* init vbi dma queues */1868INIT_LIST_HEAD(&dev->vbiq.active);1869INIT_LIST_HEAD(&dev->vbiq.queued);1870dev->vbiq.timeout.function = cx8800_vbi_timeout;1871dev->vbiq.timeout.data = (unsigned long)dev;1872init_timer(&dev->vbiq.timeout);1873cx88_risc_stopper(dev->pci,&dev->vbiq.stopper,1874MO_VID_DMACNTRL,0x88,0x00);18751876/* get irq */1877err = request_irq(pci_dev->irq, cx8800_irq,1878IRQF_SHARED | IRQF_DISABLED, core->name, dev);1879if (err < 0) {1880printk(KERN_ERR "%s/0: can't get IRQ %d\n",1881core->name,pci_dev->irq);1882goto fail_core;1883}1884cx_set(MO_PCI_INTMSK, core->pci_irqmask);18851886/* load and configure helper modules */18871888if (core->board.audio_chip == V4L2_IDENT_WM8775) {1889struct i2c_board_info wm8775_info = {1890.type = "wm8775",1891.addr = 0x36 >> 1,1892.platform_data = &core->wm8775_data,1893};1894struct v4l2_subdev *sd;18951896if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1)1897core->wm8775_data.is_nova_s = true;1898else1899core->wm8775_data.is_nova_s = false;19001901sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap,1902&wm8775_info, NULL);1903if (sd != NULL)1904sd->grp_id = WM8775_GID;1905}19061907if (core->board.audio_chip == V4L2_IDENT_TVAUDIO) {1908/* This probes for a tda9874 as is used on some1909Pixelview Ultra boards. */1910v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap,1911"tvaudio", 0, I2C_ADDRS(0xb0 >> 1));1912}19131914switch (core->boardnr) {1915case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:1916case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: {1917static const struct i2c_board_info rtc_info = {1918I2C_BOARD_INFO("isl1208", 0x6f)1919};19201921request_module("rtc-isl1208");1922core->i2c_rtc = i2c_new_device(&core->i2c_adap, &rtc_info);1923}1924/* break intentionally omitted */1925case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:1926request_module("ir-kbd-i2c");1927}19281929/* Sets device info at pci_dev */1930pci_set_drvdata(pci_dev, dev);19311932/* initial device configuration */1933mutex_lock(&core->lock);1934cx88_set_tvnorm(core, core->tvnorm);1935init_controls(core);1936cx88_video_mux(core, 0);19371938/* register v4l devices */1939dev->video_dev = cx88_vdev_init(core,dev->pci,1940&cx8800_video_template,"video");1941video_set_drvdata(dev->video_dev, dev);1942err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER,1943video_nr[core->nr]);1944if (err < 0) {1945printk(KERN_ERR "%s/0: can't register video device\n",1946core->name);1947goto fail_unreg;1948}1949printk(KERN_INFO "%s/0: registered device %s [v4l2]\n",1950core->name, video_device_node_name(dev->video_dev));19511952dev->vbi_dev = cx88_vdev_init(core,dev->pci,&cx8800_vbi_template,"vbi");1953video_set_drvdata(dev->vbi_dev, dev);1954err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI,1955vbi_nr[core->nr]);1956if (err < 0) {1957printk(KERN_ERR "%s/0: can't register vbi device\n",1958core->name);1959goto fail_unreg;1960}1961printk(KERN_INFO "%s/0: registered device %s\n",1962core->name, video_device_node_name(dev->vbi_dev));19631964if (core->board.radio.type == CX88_RADIO) {1965dev->radio_dev = cx88_vdev_init(core,dev->pci,1966&cx8800_radio_template,"radio");1967video_set_drvdata(dev->radio_dev, dev);1968err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO,1969radio_nr[core->nr]);1970if (err < 0) {1971printk(KERN_ERR "%s/0: can't register radio device\n",1972core->name);1973goto fail_unreg;1974}1975printk(KERN_INFO "%s/0: registered device %s\n",1976core->name, video_device_node_name(dev->radio_dev));1977}19781979/* start tvaudio thread */1980if (core->board.tuner_type != TUNER_ABSENT) {1981core->kthread = kthread_run(cx88_audio_thread, core, "cx88 tvaudio");1982if (IS_ERR(core->kthread)) {1983err = PTR_ERR(core->kthread);1984printk(KERN_ERR "%s/0: failed to create cx88 audio thread, err=%d\n",1985core->name, err);1986}1987}1988mutex_unlock(&core->lock);19891990return 0;19911992fail_unreg:1993cx8800_unregister_video(dev);1994free_irq(pci_dev->irq, dev);1995mutex_unlock(&core->lock);1996fail_core:1997cx88_core_put(core,dev->pci);1998fail_free:1999kfree(dev);2000return err;2001}20022003static void __devexit cx8800_finidev(struct pci_dev *pci_dev)2004{2005struct cx8800_dev *dev = pci_get_drvdata(pci_dev);2006struct cx88_core *core = dev->core;20072008/* stop thread */2009if (core->kthread) {2010kthread_stop(core->kthread);2011core->kthread = NULL;2012}20132014if (core->ir)2015cx88_ir_stop(core);20162017cx88_shutdown(core); /* FIXME */2018pci_disable_device(pci_dev);20192020/* unregister stuff */20212022free_irq(pci_dev->irq, dev);2023cx8800_unregister_video(dev);2024pci_set_drvdata(pci_dev, NULL);20252026/* free memory */2027btcx_riscmem_free(dev->pci,&dev->vidq.stopper);2028cx88_core_put(core,dev->pci);2029kfree(dev);2030}20312032#ifdef CONFIG_PM2033static int cx8800_suspend(struct pci_dev *pci_dev, pm_message_t state)2034{2035struct cx8800_dev *dev = pci_get_drvdata(pci_dev);2036struct cx88_core *core = dev->core;20372038/* stop video+vbi capture */2039spin_lock(&dev->slock);2040if (!list_empty(&dev->vidq.active)) {2041printk("%s/0: suspend video\n", core->name);2042stop_video_dma(dev);2043del_timer(&dev->vidq.timeout);2044}2045if (!list_empty(&dev->vbiq.active)) {2046printk("%s/0: suspend vbi\n", core->name);2047cx8800_stop_vbi_dma(dev);2048del_timer(&dev->vbiq.timeout);2049}2050spin_unlock(&dev->slock);20512052if (core->ir)2053cx88_ir_stop(core);2054/* FIXME -- shutdown device */2055cx88_shutdown(core);20562057pci_save_state(pci_dev);2058if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) {2059pci_disable_device(pci_dev);2060dev->state.disabled = 1;2061}2062return 0;2063}20642065static int cx8800_resume(struct pci_dev *pci_dev)2066{2067struct cx8800_dev *dev = pci_get_drvdata(pci_dev);2068struct cx88_core *core = dev->core;2069int err;20702071if (dev->state.disabled) {2072err=pci_enable_device(pci_dev);2073if (err) {2074printk(KERN_ERR "%s/0: can't enable device\n",2075core->name);2076return err;2077}20782079dev->state.disabled = 0;2080}2081err= pci_set_power_state(pci_dev, PCI_D0);2082if (err) {2083printk(KERN_ERR "%s/0: can't set power state\n", core->name);2084pci_disable_device(pci_dev);2085dev->state.disabled = 1;20862087return err;2088}2089pci_restore_state(pci_dev);20902091/* FIXME: re-initialize hardware */2092cx88_reset(core);2093if (core->ir)2094cx88_ir_start(core);20952096cx_set(MO_PCI_INTMSK, core->pci_irqmask);20972098/* restart video+vbi capture */2099spin_lock(&dev->slock);2100if (!list_empty(&dev->vidq.active)) {2101printk("%s/0: resume video\n", core->name);2102restart_video_queue(dev,&dev->vidq);2103}2104if (!list_empty(&dev->vbiq.active)) {2105printk("%s/0: resume vbi\n", core->name);2106cx8800_restart_vbi_queue(dev,&dev->vbiq);2107}2108spin_unlock(&dev->slock);21092110return 0;2111}2112#endif21132114/* ----------------------------------------------------------- */21152116static const struct pci_device_id cx8800_pci_tbl[] = {2117{2118.vendor = 0x14f1,2119.device = 0x8800,2120.subvendor = PCI_ANY_ID,2121.subdevice = PCI_ANY_ID,2122},{2123/* --- end of list --- */2124}2125};2126MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl);21272128static struct pci_driver cx8800_pci_driver = {2129.name = "cx8800",2130.id_table = cx8800_pci_tbl,2131.probe = cx8800_initdev,2132.remove = __devexit_p(cx8800_finidev),2133#ifdef CONFIG_PM2134.suspend = cx8800_suspend,2135.resume = cx8800_resume,2136#endif2137};21382139static int __init cx8800_init(void)2140{2141printk(KERN_INFO "cx88/0: cx2388x v4l2 driver version %d.%d.%d loaded\n",2142(CX88_VERSION_CODE >> 16) & 0xff,2143(CX88_VERSION_CODE >> 8) & 0xff,2144CX88_VERSION_CODE & 0xff);2145#ifdef SNAPSHOT2146printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n",2147SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100);2148#endif2149return pci_register_driver(&cx8800_pci_driver);2150}21512152static void __exit cx8800_fini(void)2153{2154pci_unregister_driver(&cx8800_pci_driver);2155}21562157module_init(cx8800_init);2158module_exit(cx8800_fini);21592160/* ----------------------------------------------------------- */2161/*2162* Local variables:2163* c-basic-offset: 82164* End:2165* kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off2166*/216721682169