summaryrefslogtreecommitdiffstats
path: root/drivers/media/video/cx23885
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/cx23885')
-rw-r--r--drivers/media/video/cx23885/Kconfig2
-rw-r--r--drivers/media/video/cx23885/Makefile3
-rw-r--r--drivers/media/video/cx23885/cx23885-417.c10
-rw-r--r--drivers/media/video/cx23885/cx23885-cards.c155
-rw-r--r--drivers/media/video/cx23885/cx23885-core.c115
-rw-r--r--drivers/media/video/cx23885/cx23885-dvb.c158
-rw-r--r--drivers/media/video/cx23885/cx23885-f300.c177
-rw-r--r--drivers/media/video/cx23885/cx23885-f300.h2
-rw-r--r--drivers/media/video/cx23885/cx23885-input.c427
-rw-r--r--drivers/media/video/cx23885/cx23885-input.h30
-rw-r--r--drivers/media/video/cx23885/cx23885-ioctl.c208
-rw-r--r--drivers/media/video/cx23885/cx23885-ioctl.h39
-rw-r--r--drivers/media/video/cx23885/cx23885-ir.c101
-rw-r--r--drivers/media/video/cx23885/cx23885-ir.h31
-rw-r--r--drivers/media/video/cx23885/cx23885-reg.h5
-rw-r--r--drivers/media/video/cx23885/cx23885-video.c44
-rw-r--r--drivers/media/video/cx23885/cx23885.h31
-rw-r--r--drivers/media/video/cx23885/cx23888-ir.c1239
-rw-r--r--drivers/media/video/cx23885/cx23888-ir.h28
19 files changed, 2705 insertions, 100 deletions
diff --git a/drivers/media/video/cx23885/Kconfig b/drivers/media/video/cx23885/Kconfig
index fd3fc3e3198a..bcdda9a9aa96 100644
--- a/drivers/media/video/cx23885/Kconfig
+++ b/drivers/media/video/cx23885/Kconfig
@@ -18,7 +18,9 @@ config VIDEO_CX23885
select DVB_TDA10048 if !DVB_FE_CUSTOMISE
select DVB_LNBP21 if !DVB_FE_CUSTOMISE
select DVB_STV6110 if !DVB_FE_CUSTOMISE
+ select DVB_CX24116 if !DVB_FE_CUSTOMISE
select DVB_STV0900 if !DVB_FE_CUSTOMISE
+ select DVB_DS3000 if !DVB_FE_CUSTOMISE
select MEDIA_TUNER_MT2131 if !MEDIA_TUNER_CUSTOMISE
select MEDIA_TUNER_XC2028 if !MEDIA_TUNER_CUSTOMISE
select MEDIA_TUNER_TDA8290 if !MEDIA_TUNER_CUSTOMISE
diff --git a/drivers/media/video/cx23885/Makefile b/drivers/media/video/cx23885/Makefile
index ab8ea35c9bfb..5787ae243631 100644
--- a/drivers/media/video/cx23885/Makefile
+++ b/drivers/media/video/cx23885/Makefile
@@ -1,6 +1,7 @@
cx23885-objs := cx23885-cards.o cx23885-video.o cx23885-vbi.o \
cx23885-core.o cx23885-i2c.o cx23885-dvb.o cx23885-417.o \
- netup-init.o cimax2.o netup-eeprom.o
+ cx23885-ioctl.o cx23885-ir.o cx23885-input.o cx23888-ir.o \
+ netup-init.o cimax2.o netup-eeprom.o cx23885-f300.o
obj-$(CONFIG_VIDEO_CX23885) += cx23885.o
diff --git a/drivers/media/video/cx23885/cx23885-417.c b/drivers/media/video/cx23885/cx23885-417.c
index 6c3b51ce3372..0eed852c61e9 100644
--- a/drivers/media/video/cx23885/cx23885-417.c
+++ b/drivers/media/video/cx23885/cx23885-417.c
@@ -37,6 +37,7 @@
#include <media/cx2341x.h>
#include "cx23885.h"
+#include "cx23885-ioctl.h"
#define CX23885_FIRM_IMAGE_SIZE 376836
#define CX23885_FIRM_IMAGE_NAME "v4l-cx23885-enc.fw"
@@ -318,7 +319,7 @@ static int mc417_wait_ready(struct cx23885_dev *dev)
}
}
-static int mc417_register_write(struct cx23885_dev *dev, u16 address, u32 value)
+int mc417_register_write(struct cx23885_dev *dev, u16 address, u32 value)
{
u32 regval;
@@ -382,7 +383,7 @@ static int mc417_register_write(struct cx23885_dev *dev, u16 address, u32 value)
return mc417_wait_ready(dev);
}
-static int mc417_register_read(struct cx23885_dev *dev, u16 address, u32 *value)
+int mc417_register_read(struct cx23885_dev *dev, u16 address, u32 *value)
{
int retval;
u32 regval;
@@ -1724,6 +1725,11 @@ static const struct v4l2_ioctl_ops mpeg_ioctl_ops = {
.vidioc_log_status = vidioc_log_status,
.vidioc_querymenu = vidioc_querymenu,
.vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_chip_ident = cx23885_g_chip_ident,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = cx23885_g_register,
+ .vidioc_s_register = cx23885_s_register,
+#endif
};
static struct video_device cx23885_mpeg_template = {
diff --git a/drivers/media/video/cx23885/cx23885-cards.c b/drivers/media/video/cx23885/cx23885-cards.c
index bfdf79f1033c..1ec48169277d 100644
--- a/drivers/media/video/cx23885/cx23885-cards.c
+++ b/drivers/media/video/cx23885/cx23885-cards.c
@@ -28,6 +28,7 @@
#include "cx23885.h"
#include "tuner-xc2028.h"
#include "netup-init.h"
+#include "cx23888-ir.h"
/* ------------------------------------------------------------------ */
/* board config info */
@@ -199,11 +200,61 @@ struct cx23885_board cx23885_boards[] = {
},
[CX23885_BOARD_MYGICA_X8506] = {
.name = "Mygica X8506 DMB-TH",
+ .tuner_type = TUNER_XC5000,
+ .tuner_addr = 0x61,
+ .porta = CX23885_ANALOG_VIDEO,
.portb = CX23885_MPEG_DVB,
+ .input = {
+ {
+ .type = CX23885_VMUX_TELEVISION,
+ .vmux = CX25840_COMPOSITE2,
+ },
+ {
+ .type = CX23885_VMUX_COMPOSITE1,
+ .vmux = CX25840_COMPOSITE8,
+ },
+ {
+ .type = CX23885_VMUX_SVIDEO,
+ .vmux = CX25840_SVIDEO_LUMA3 |
+ CX25840_SVIDEO_CHROMA4,
+ },
+ {
+ .type = CX23885_VMUX_COMPONENT,
+ .vmux = CX25840_COMPONENT_ON |
+ CX25840_VIN1_CH1 |
+ CX25840_VIN6_CH2 |
+ CX25840_VIN7_CH3,
+ },
+ },
},
[CX23885_BOARD_MAGICPRO_PROHDTVE2] = {
.name = "Magic-Pro ProHDTV Extreme 2",
+ .tuner_type = TUNER_XC5000,
+ .tuner_addr = 0x61,
+ .porta = CX23885_ANALOG_VIDEO,
.portb = CX23885_MPEG_DVB,
+ .input = {
+ {
+ .type = CX23885_VMUX_TELEVISION,
+ .vmux = CX25840_COMPOSITE2,
+ },
+ {
+ .type = CX23885_VMUX_COMPOSITE1,
+ .vmux = CX25840_COMPOSITE8,
+ },
+ {
+ .type = CX23885_VMUX_SVIDEO,
+ .vmux = CX25840_SVIDEO_LUMA3 |
+ CX25840_SVIDEO_CHROMA4,
+ },
+ {
+ .type = CX23885_VMUX_COMPONENT,
+ .vmux = CX25840_COMPONENT_ON |
+ CX25840_VIN1_CH1 |
+ CX25840_VIN6_CH2 |
+ CX25840_VIN7_CH3,
+ },
+ },
},
[CX23885_BOARD_HAUPPAUGE_HVR1850] = {
.name = "Hauppauge WinTV-HVR1850",
@@ -214,6 +265,15 @@ struct cx23885_board cx23885_boards[] = {
.name = "Compro VideoMate E800",
.portc = CX23885_MPEG_DVB,
},
+ [CX23885_BOARD_HAUPPAUGE_HVR1290] = {
+ .name = "Hauppauge WinTV-HVR1290",
+ .portc = CX23885_MPEG_DVB,
+ },
+ [CX23885_BOARD_MYGICA_X8558PRO] = {
+ .name = "Mygica X8558 PRO DMB-TH",
+ .portb = CX23885_MPEG_DVB,
+ .portc = CX23885_MPEG_DVB,
+ },
};
const unsigned int cx23885_bcount = ARRAY_SIZE(cx23885_boards);
@@ -349,6 +409,14 @@ struct cx23885_subid cx23885_subids[] = {
.subvendor = 0x1858,
.subdevice = 0xe800,
.card = CX23885_BOARD_COMPRO_VIDEOMATE_E800,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x8551,
+ .card = CX23885_BOARD_HAUPPAUGE_HVR1290,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0x8578,
+ .card = CX23885_BOARD_MYGICA_X8558PRO,
},
};
const unsigned int cx23885_idcount = ARRAY_SIZE(cx23885_subids);
@@ -509,9 +577,13 @@ static void hauppauge_eeprom(struct cx23885_dev *dev, u8 *eeprom_data)
* DVB-T and MPEG2 HW Encoder */
break;
case 85021:
- /* WinTV-HVR1850 (PCIe, OEM, RCA in, IR, FM,
+ /* WinTV-HVR1850 (PCIe, Retail, 3.5mm in, IR, FM,
Dual channel ATSC and MPEG2 HW Encoder */
break;
+ case 85721:
+ /* WinTV-HVR1290 (PCIe, OEM, RCA in, IR,
+ Dual channel ATSC and Basic analog */
+ break;
default:
printk(KERN_WARNING "%s: warning: "
"unknown hauppauge model #%d\n",
@@ -710,10 +782,14 @@ void cx23885_gpio_setup(struct cx23885_dev *dev)
cx_set(GP0_IO, 0x00040004);
break;
case CX23885_BOARD_TBS_6920:
- case CX23885_BOARD_TEVII_S470:
cx_write(MC417_CTL, 0x00000036);
cx_write(MC417_OEN, 0x00001000);
- cx_write(MC417_RWD, 0x00001800);
+ cx_set(MC417_RWD, 0x00000002);
+ mdelay(200);
+ cx_clear(MC417_RWD, 0x00000800);
+ mdelay(200);
+ cx_set(MC417_RWD, 0x00000800);
+ mdelay(200);
break;
case CX23885_BOARD_NETUP_DUAL_DVBS2_CI:
/* GPIO-0 INTA from CiMax1
@@ -758,15 +834,26 @@ void cx23885_gpio_setup(struct cx23885_dev *dev)
break;
case CX23885_BOARD_MYGICA_X8506:
case CX23885_BOARD_MAGICPRO_PROHDTVE2:
+ /* GPIO-0 (0)Analog / (1)Digital TV */
/* GPIO-1 reset XC5000 */
/* GPIO-2 reset LGS8GL5 / LGS8G75 */
- cx_set(GP0_IO, 0x00060000);
- cx_clear(GP0_IO, 0x00000006);
+ cx23885_gpio_enable(dev, GPIO_0 | GPIO_1 | GPIO_2, 1);
+ cx23885_gpio_clear(dev, GPIO_1 | GPIO_2);
mdelay(100);
- cx_set(GP0_IO, 0x00060006);
+ cx23885_gpio_set(dev, GPIO_0 | GPIO_1 | GPIO_2);
+ mdelay(100);
+ break;
+ case CX23885_BOARD_MYGICA_X8558PRO:
+ /* GPIO-0 reset first ATBM8830 */
+ /* GPIO-1 reset second ATBM8830 */
+ cx23885_gpio_enable(dev, GPIO_0 | GPIO_1, 1);
+ cx23885_gpio_clear(dev, GPIO_0 | GPIO_1);
+ mdelay(100);
+ cx23885_gpio_set(dev, GPIO_0 | GPIO_1);
mdelay(100);
break;
case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
/* GPIO-0 656_CLK */
/* GPIO-1 656_D0 */
/* GPIO-2 Wake# */
@@ -801,6 +888,7 @@ void cx23885_gpio_setup(struct cx23885_dev *dev)
int cx23885_ir_init(struct cx23885_dev *dev)
{
+ int ret = 0;
switch (dev->board) {
case CX23885_BOARD_HAUPPAUGE_HVR1250:
case CX23885_BOARD_HAUPPAUGE_HVR1500:
@@ -812,15 +900,46 @@ int cx23885_ir_init(struct cx23885_dev *dev)
case CX23885_BOARD_HAUPPAUGE_HVR1275:
case CX23885_BOARD_HAUPPAUGE_HVR1255:
case CX23885_BOARD_HAUPPAUGE_HVR1210:
- case CX23885_BOARD_HAUPPAUGE_HVR1850:
/* FIXME: Implement me */
break;
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ ret = cx23888_ir_probe(dev);
+ if (ret)
+ break;
+ dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_888_IR);
+ dev->pci_irqmask |= PCI_MSK_IR;
+ break;
case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP:
request_module("ir-kbd-i2c");
break;
}
- return 0;
+ return ret;
+}
+
+void cx23885_ir_fini(struct cx23885_dev *dev)
+{
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ dev->pci_irqmask &= ~PCI_MSK_IR;
+ cx_clear(PCI_INT_MSK, PCI_MSK_IR);
+ cx23888_ir_remove(dev);
+ dev->sd_ir = NULL;
+ break;
+ }
+}
+
+void cx23885_ir_pci_int_enable(struct cx23885_dev *dev)
+{
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ if (dev->sd_ir && (dev->pci_irqmask & PCI_MSK_IR))
+ cx_set(PCI_INT_MSK, PCI_MSK_IR);
+ break;
+ }
}
void cx23885_card_setup(struct cx23885_dev *dev)
@@ -853,6 +972,7 @@ void cx23885_card_setup(struct cx23885_dev *dev)
case CX23885_BOARD_HAUPPAUGE_HVR1255:
case CX23885_BOARD_HAUPPAUGE_HVR1210:
case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
if (dev->i2c_bus[0].i2c_rc == 0)
hauppauge_eeprom(dev, eeprom+0xc0);
break;
@@ -886,8 +1006,12 @@ void cx23885_card_setup(struct cx23885_dev *dev)
ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
break;
- case CX23885_BOARD_TEVII_S470:
case CX23885_BOARD_TBS_6920:
+ ts1->gen_ctrl_val = 0x4; /* Parallel */
+ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
+ break;
+ case CX23885_BOARD_TEVII_S470:
case CX23885_BOARD_DVBWORLD_2005:
ts1->gen_ctrl_val = 0x5; /* Parallel */
ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
@@ -907,6 +1031,14 @@ void cx23885_card_setup(struct cx23885_dev *dev)
ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
break;
+ case CX23885_BOARD_MYGICA_X8558PRO:
+ ts1->gen_ctrl_val = 0x5; /* Parallel */
+ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
+ ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */
+ ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+ ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
+ break;
case CX23885_BOARD_HAUPPAUGE_HVR1250:
case CX23885_BOARD_HAUPPAUGE_HVR1500:
case CX23885_BOARD_HAUPPAUGE_HVR1500Q:
@@ -922,6 +1054,7 @@ void cx23885_card_setup(struct cx23885_dev *dev)
case CX23885_BOARD_HAUPPAUGE_HVR1210:
case CX23885_BOARD_HAUPPAUGE_HVR1850:
case CX23885_BOARD_COMPRO_VIDEOMATE_E800:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
default:
ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */
ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
@@ -939,6 +1072,10 @@ void cx23885_card_setup(struct cx23885_dev *dev)
case CX23885_BOARD_COMPRO_VIDEOMATE_E650F:
case CX23885_BOARD_NETUP_DUAL_DVBS2_CI:
case CX23885_BOARD_COMPRO_VIDEOMATE_E800:
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_MYGICA_X8506:
+ case CX23885_BOARD_MAGICPRO_PROHDTVE2:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev,
&dev->i2c_bus[2].i2c_adap,
"cx25840", "cx25840", 0x88 >> 1, NULL);
diff --git a/drivers/media/video/cx23885/cx23885-core.c b/drivers/media/video/cx23885/cx23885-core.c
index fa2d350e20fd..04b12d27bc13 100644
--- a/drivers/media/video/cx23885/cx23885-core.c
+++ b/drivers/media/video/cx23885/cx23885-core.c
@@ -32,6 +32,9 @@
#include "cx23885.h"
#include "cimax2.h"
+#include "cx23888-ir.h"
+#include "cx23885-ir.h"
+#include "cx23885-input.h"
MODULE_DESCRIPTION("Driver for cx23885 based TV cards");
MODULE_AUTHOR("Steven Toth <stoth@linuxtv.org>");
@@ -753,6 +756,23 @@ static void cx23885_dev_checkrevision(struct cx23885_dev *dev)
__func__, dev->hwrevision);
}
+/* Find the first v4l2_subdev member of the group id in hw */
+struct v4l2_subdev *cx23885_find_hw(struct cx23885_dev *dev, u32 hw)
+{
+ struct v4l2_subdev *result = NULL;
+ struct v4l2_subdev *sd;
+
+ spin_lock(&dev->v4l2_dev.lock);
+ v4l2_device_for_each_subdev(sd, &dev->v4l2_dev) {
+ if (sd->grp_id == hw) {
+ result = sd;
+ break;
+ }
+ }
+ spin_unlock(&dev->v4l2_dev.lock);
+ return result;
+}
+
static int cx23885_dev_setup(struct cx23885_dev *dev)
{
int i;
@@ -899,7 +919,7 @@ static int cx23885_dev_setup(struct cx23885_dev *dev)
cx23885_i2c_register(&dev->i2c_bus[1]);
cx23885_i2c_register(&dev->i2c_bus[2]);
cx23885_card_setup(dev);
- call_all(dev, tuner, s_standby);
+ call_all(dev, core, s_power, 0);
cx23885_ir_init(dev);
if (cx23885_boards[dev->board].porta == CX23885_ANALOG_VIDEO) {
@@ -1637,6 +1657,7 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id)
u32 ts1_status, ts1_mask;
u32 ts2_status, ts2_mask;
int vida_count = 0, ts1_count = 0, ts2_count = 0, handled = 0;
+ bool ir_handled = false;
pci_status = cx_read(PCI_INT_STAT);
pci_mask = cx_read(PCI_INT_MSK);
@@ -1662,18 +1683,12 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id)
dprintk(7, "ts2_status: 0x%08x ts2_mask: 0x%08x count: 0x%x\n",
ts2_status, ts2_mask, ts2_count);
- if ((pci_status & PCI_MSK_RISC_RD) ||
- (pci_status & PCI_MSK_RISC_WR) ||
- (pci_status & PCI_MSK_AL_RD) ||
- (pci_status & PCI_MSK_AL_WR) ||
- (pci_status & PCI_MSK_APB_DMA) ||
- (pci_status & PCI_MSK_VID_C) ||
- (pci_status & PCI_MSK_VID_B) ||
- (pci_status & PCI_MSK_VID_A) ||
- (pci_status & PCI_MSK_AUD_INT) ||
- (pci_status & PCI_MSK_AUD_EXT) ||
- (pci_status & PCI_MSK_GPIO0) ||
- (pci_status & PCI_MSK_GPIO1)) {
+ if (pci_status & (PCI_MSK_RISC_RD | PCI_MSK_RISC_WR |
+ PCI_MSK_AL_RD | PCI_MSK_AL_WR | PCI_MSK_APB_DMA |
+ PCI_MSK_VID_C | PCI_MSK_VID_B | PCI_MSK_VID_A |
+ PCI_MSK_AUD_INT | PCI_MSK_AUD_EXT |
+ PCI_MSK_GPIO0 | PCI_MSK_GPIO1 |
+ PCI_MSK_IR)) {
if (pci_status & PCI_MSK_RISC_RD)
dprintk(7, " (PCI_MSK_RISC_RD 0x%08x)\n",
@@ -1722,6 +1737,10 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id)
if (pci_status & PCI_MSK_GPIO1)
dprintk(7, " (PCI_MSK_GPIO1 0x%08x)\n",
PCI_MSK_GPIO1);
+
+ if (pci_status & PCI_MSK_IR)
+ dprintk(7, " (PCI_MSK_IR 0x%08x)\n",
+ PCI_MSK_IR);
}
if (cx23885_boards[dev->board].cimax > 0 &&
@@ -1752,12 +1771,48 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id)
if (vida_status)
handled += cx23885_video_irq(dev, vida_status);
+ if (pci_status & PCI_MSK_IR) {
+ v4l2_subdev_call(dev->sd_ir, ir, interrupt_service_routine,
+ pci_status, &ir_handled);
+ if (ir_handled)
+ handled++;
+ }
+
if (handled)
cx_write(PCI_INT_STAT, pci_status);
out:
return IRQ_RETVAL(handled);
}
+static void cx23885_v4l2_dev_notify(struct v4l2_subdev *sd,
+ unsigned int notification, void *arg)
+{
+ struct cx23885_dev *dev;
+
+ if (sd == NULL)
+ return;
+
+ dev = to_cx23885(sd->v4l2_dev);
+
+ switch (notification) {
+ case V4L2_SUBDEV_IR_RX_NOTIFY: /* Called in an IRQ context */
+ if (sd == dev->sd_ir)
+ cx23885_ir_rx_v4l2_dev_notify(sd, *(u32 *)arg);
+ break;
+ case V4L2_SUBDEV_IR_TX_NOTIFY: /* Called in an IRQ context */
+ if (sd == dev->sd_ir)
+ cx23885_ir_tx_v4l2_dev_notify(sd, *(u32 *)arg);
+ break;
+ }
+}
+
+static void cx23885_v4l2_dev_notify_init(struct cx23885_dev *dev)
+{
+ INIT_WORK(&dev->ir_rx_work, cx23885_ir_rx_work_handler);
+ INIT_WORK(&dev->ir_tx_work, cx23885_ir_tx_work_handler);
+ dev->v4l2_dev.notify = cx23885_v4l2_dev_notify;
+}
+
static inline int encoder_on_portb(struct cx23885_dev *dev)
{
return cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER;
@@ -1816,6 +1871,26 @@ void cx23885_gpio_clear(struct cx23885_dev *dev, u32 mask)
printk(KERN_INFO "%s: Unsupported\n", dev->name);
}
+u32 cx23885_gpio_get(struct cx23885_dev *dev, u32 mask)
+{
+ if (mask & 0x00000007)
+ return (cx_read(GP0_IO) >> 8) & mask & 0x7;
+
+ if (mask & 0x0007fff8) {
+ if (encoder_on_portb(dev) || encoder_on_portc(dev))
+ printk(KERN_ERR
+ "%s: Reading GPIO moving on encoder ports\n",
+ dev->name);
+ return (cx_read(MC417_RWD) & ((mask & 0x7fff8) >> 3)) << 3;
+ }
+
+ /* TODO: 23-19 */
+ if (mask & 0x00f80000)
+ printk(KERN_INFO "%s: Unsupported\n", dev->name);
+
+ return 0;
+}
+
void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput)
{
if ((mask & 0x00000007) && asoutput)
@@ -1854,6 +1929,9 @@ static int __devinit cx23885_initdev(struct pci_dev *pci_dev,
if (err < 0)
goto fail_free;
+ /* Prepare to handle notifications from subdevices */
+ cx23885_v4l2_dev_notify_init(dev);
+
/* pci init */
dev->pci = pci_dev;
if (pci_enable_device(pci_dev)) {
@@ -1896,6 +1974,14 @@ static int __devinit cx23885_initdev(struct pci_dev *pci_dev,
break;
}
+ /*
+ * The CX2388[58] IR controller can start firing interrupts when
+ * enabled, so these have to take place after the cx23885_irq() handler
+ * is hooked up by the call to request_irq() above.
+ */
+ cx23885_ir_pci_int_enable(dev);
+ cx23885_input_init(dev);
+
return 0;
fail_irq:
@@ -1912,6 +1998,9 @@ static void __devexit cx23885_finidev(struct pci_dev *pci_dev)
struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
struct cx23885_dev *dev = to_cx23885(v4l2_dev);
+ cx23885_input_fini(dev);
+ cx23885_ir_fini(dev);
+
cx23885_shutdown(dev);
pci_disable_device(pci_dev);
diff --git a/drivers/media/video/cx23885/cx23885-dvb.c b/drivers/media/video/cx23885/cx23885-dvb.c
index 16c6a921f40b..e45d2df08138 100644
--- a/drivers/media/video/cx23885/cx23885-dvb.c
+++ b/drivers/media/video/cx23885/cx23885-dvb.c
@@ -38,6 +38,7 @@
#include "tda18271.h"
#include "lgdt330x.h"
#include "xc5000.h"
+#include "max2165.h"
#include "tda10048.h"
#include "tuner-xc2028.h"
#include "tuner-simple.h"
@@ -54,6 +55,9 @@
#include "netup-eeprom.h"
#include "netup-init.h"
#include "lgdt3305.h"
+#include "atbm8830.h"
+#include "ds3000.h"
+#include "cx23885-f300.h"
static unsigned int debug;
@@ -400,6 +404,7 @@ static struct stv0900_reg stv0900_ts_regs[] = {
static struct stv0900_config netup_stv0900_config = {
.demod_address = 0x68,
+ .demod_mode = 1, /* dual */
.xtal = 8000000,
.clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */
.diseqc_mode = 2,/* 2/3 PWM */
@@ -414,34 +419,22 @@ static struct stv6110_config netup_stv6110_tunerconfig_a = {
.i2c_address = 0x60,
.mclk = 16000000,
.clk_div = 1,
+ .gain = 8, /* +16 dB - maximum gain */
};
static struct stv6110_config netup_stv6110_tunerconfig_b = {
.i2c_address = 0x63,
.mclk = 16000000,
.clk_div = 1,
+ .gain = 8, /* +16 dB - maximum gain */
};
-static int tbs_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
-{
- struct cx23885_tsport *port = fe->dvb->priv;
- struct cx23885_dev *dev = port->dev;
-
- if (voltage == SEC_VOLTAGE_18)
- cx_write(MC417_RWD, 0x00001e00);/* GPIO-13 high */
- else if (voltage == SEC_VOLTAGE_13)
- cx_write(MC417_RWD, 0x00001a00);/* GPIO-13 low */
- else
- cx_write(MC417_RWD, 0x00001800);/* GPIO-12 low */
- return 0;
-}
-
static struct cx24116_config tbs_cx24116_config = {
- .demod_address = 0x05,
+ .demod_address = 0x55,
};
-static struct cx24116_config tevii_cx24116_config = {
- .demod_address = 0x55,
+static struct ds3000_config tevii_ds3000_config = {
+ .demod_address = 0x68,
};
static struct cx24116_config dvbworld_cx24116_config = {
@@ -486,11 +479,40 @@ static int cx23885_dvb_set_frontend(struct dvb_frontend *fe,
break;
}
break;
+ case CX23885_BOARD_MYGICA_X8506:
+ case CX23885_BOARD_MAGICPRO_PROHDTVE2:
+ /* Select Digital TV */
+ cx23885_gpio_set(dev, GPIO_0);
+ break;
}
- return (port->set_frontend_save) ?
- port->set_frontend_save(fe, param) : -ENODEV;
+ return 0;
}
+static int cx23885_dvb_fe_ioctl_override(struct dvb_frontend *fe,
+ unsigned int cmd, void *parg,
+ unsigned int stage)
+{
+ int err = 0;
+
+ switch (stage) {
+ case DVB_FE_IOCTL_PRE:
+
+ switch (cmd) {
+ case FE_SET_FRONTEND:
+ err = cx23885_dvb_set_frontend(fe,
+ (struct dvb_frontend_parameters *) parg);
+ break;
+ }
+ break;
+
+ case DVB_FE_IOCTL_POST:
+ /* no post-ioctl handling required */
+ break;
+ }
+ return err;
+};
+
+
static struct lgs8gxx_config magicpro_prohdtve2_lgs8g75_config = {
.prod = LGS8GXX_PROD_LGS8G75,
.demod_address = 0x19,
@@ -511,6 +533,38 @@ static struct xc5000_config magicpro_prohdtve2_xc5000_config = {
.if_khz = 6500,
};
+static struct atbm8830_config mygica_x8558pro_atbm8830_cfg1 = {
+ .prod = ATBM8830_PROD_8830,
+ .demod_address = 0x44,
+ .serial_ts = 0,
+ .ts_sampling_edge = 1,
+ .ts_clk_gated = 0,
+ .osc_clk_freq = 30400, /* in kHz */
+ .if_freq = 0, /* zero IF */
+ .zif_swap_iq = 1,
+};
+
+static struct max2165_config mygic_x8558pro_max2165_cfg1 = {
+ .i2c_address = 0x60,
+ .osc_clk = 20
+};
+
+static struct atbm8830_config mygica_x8558pro_atbm8830_cfg2 = {
+ .prod = ATBM8830_PROD_8830,
+ .demod_address = 0x44,
+ .serial_ts = 1,
+ .ts_sampling_edge = 1,
+ .ts_clk_gated = 0,
+ .osc_clk_freq = 30400, /* in kHz */
+ .if_freq = 0, /* zero IF */
+ .zif_swap_iq = 1,
+};
+
+static struct max2165_config mygic_x8558pro_max2165_cfg2 = {
+ .i2c_address = 0x60,
+ .osc_clk = 20
+};
+
static int dvb_register(struct cx23885_tsport *port)
{
struct cx23885_dev *dev = port->dev;
@@ -550,12 +604,6 @@ static int dvb_register(struct cx23885_tsport *port)
0x60, &dev->i2c_bus[1].i2c_adap,
&hauppauge_hvr127x_config);
}
-
- /* FIXME: temporary hack */
- /* define bridge override to set_frontend */
- port->set_frontend_save = fe0->dvb.frontend->ops.set_frontend;
- fe0->dvb.frontend->ops.set_frontend = cx23885_dvb_set_frontend;
-
break;
case CX23885_BOARD_HAUPPAUGE_HVR1255:
i2c_bus = &dev->i2c_bus[0];
@@ -772,23 +820,23 @@ static int dvb_register(struct cx23885_tsport *port)
}
break;
case CX23885_BOARD_TBS_6920:
- i2c_bus = &dev->i2c_bus[0];
+ i2c_bus = &dev->i2c_bus[1];
fe0->dvb.frontend = dvb_attach(cx24116_attach,
- &tbs_cx24116_config,
- &i2c_bus->i2c_adap);
+ &tbs_cx24116_config,
+ &i2c_bus->i2c_adap);
if (fe0->dvb.frontend != NULL)
- fe0->dvb.frontend->ops.set_voltage = tbs_set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = f300_set_voltage;
break;
case CX23885_BOARD_TEVII_S470:
i2c_bus = &dev->i2c_bus[1];
- fe0->dvb.frontend = dvb_attach(cx24116_attach,
- &tevii_cx24116_config,
- &i2c_bus->i2c_adap);
+ fe0->dvb.frontend = dvb_attach(ds3000_attach,
+ &tevii_ds3000_config,
+ &i2c_bus->i2c_adap);
if (fe0->dvb.frontend != NULL)
- fe0->dvb.frontend->ops.set_voltage = tbs_set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = f300_set_voltage;
break;
case CX23885_BOARD_DVBWORLD_2005:
@@ -814,8 +862,8 @@ static int dvb_register(struct cx23885_tsport *port)
if (!dvb_attach(lnbh24_attach,
fe0->dvb.frontend,
&i2c_bus->i2c_adap,
- LNBH24_PCL,
- LNBH24_TTX, 0x09))
+ LNBH24_PCL | LNBH24_TTX,
+ LNBH24_TEN, 0x09))
printk(KERN_ERR
"No LNBH24 found!\n");
@@ -835,8 +883,8 @@ static int dvb_register(struct cx23885_tsport *port)
if (!dvb_attach(lnbh24_attach,
fe0->dvb.frontend,
&i2c_bus->i2c_adap,
- LNBH24_PCL,
- LNBH24_TTX, 0x0a))
+ LNBH24_PCL | LNBH24_TTX,
+ LNBH24_TEN, 0x0a))
printk(KERN_ERR
"No LNBH24 found!\n");
@@ -872,6 +920,7 @@ static int dvb_register(struct cx23885_tsport *port)
}
break;
case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
i2c_bus = &dev->i2c_bus[0];
fe0->dvb.frontend = dvb_attach(s5h1411_attach,
&hcw_s5h1411_config,
@@ -881,6 +930,36 @@ static int dvb_register(struct cx23885_tsport *port)
0x60, &dev->i2c_bus[0].i2c_adap,
&hauppauge_tda18271_config);
break;
+ case CX23885_BOARD_MYGICA_X8558PRO:
+ switch (port->nr) {
+ /* port B */
+ case 1:
+ i2c_bus = &dev->i2c_bus[0];
+ fe0->dvb.frontend = dvb_attach(atbm8830_attach,
+ &mygica_x8558pro_atbm8830_cfg1,
+ &i2c_bus->i2c_adap);
+ if (fe0->dvb.frontend != NULL) {
+ dvb_attach(max2165_attach,
+ fe0->dvb.frontend,
+ &i2c_bus->i2c_adap,
+ &mygic_x8558pro_max2165_cfg1);
+ }
+ break;
+ /* port C */
+ case 2:
+ i2c_bus = &dev->i2c_bus[1];
+ fe0->dvb.frontend = dvb_attach(atbm8830_attach,
+ &mygica_x8558pro_atbm8830_cfg2,
+ &i2c_bus->i2c_adap);
+ if (fe0->dvb.frontend != NULL) {
+ dvb_attach(max2165_attach,
+ fe0->dvb.frontend,
+ &i2c_bus->i2c_adap,
+ &mygic_x8558pro_max2165_cfg2);
+ }
+ break;
+ }
+ break;
default:
printk(KERN_INFO "%s: The frontend of your DVB/ATSC card "
@@ -897,14 +976,15 @@ static int dvb_register(struct cx23885_tsport *port)
fe0->dvb.frontend->callback = cx23885_tuner_callback;
/* Put the analog decoder in standby to keep it quiet */
- call_all(dev, tuner, s_standby);
+ call_all(dev, core, s_power, 0);
if (fe0->dvb.frontend->ops.analog_ops.standby)
fe0->dvb.frontend->ops.analog_ops.standby(fe0->dvb.frontend);
/* register everything */
ret = videobuf_dvb_register_bus(&port->frontends, THIS_MODULE, port,
- &dev->pci->dev, adapter_nr, 0);
+ &dev->pci->dev, adapter_nr, 0,
+ cx23885_dvb_fe_ioctl_override);
/* init CI & MAC */
switch (dev->board) {
diff --git a/drivers/media/video/cx23885/cx23885-f300.c b/drivers/media/video/cx23885/cx23885-f300.c
new file mode 100644
index 000000000000..93998f220986
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-f300.c
@@ -0,0 +1,177 @@
+/*
+ * Driver for Silicon Labs C8051F300 microcontroller.
+ *
+ * It is used for LNB power control in TeVii S470,
+ * TBS 6920 PCIe DVB-S2 cards.
+ *
+ * Microcontroller connected to cx23885 GPIO pins:
+ * GPIO0 - data - P0.3 F300
+ * GPIO1 - reset - P0.2 F300
+ * GPIO2 - clk - P0.1 F300
+ * GPIO3 - busy - P0.0 F300
+ *
+ * Copyright (C) 2009 Igor M. Liplianin <liplianin@me.by>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "cx23885.h"
+
+#define F300_DATA GPIO_0
+#define F300_RESET GPIO_1
+#define F300_CLK GPIO_2
+#define F300_BUSY GPIO_3
+
+static void f300_set_line(struct cx23885_dev *dev, u32 line, u8 lvl)
+{
+ cx23885_gpio_enable(dev, line, 1);
+ if (lvl == 1)
+ cx23885_gpio_set(dev, line);
+ else
+ cx23885_gpio_clear(dev, line);
+}
+
+static u8 f300_get_line(struct cx23885_dev *dev, u32 line)
+{
+ cx23885_gpio_enable(dev, line, 0);
+
+ return cx23885_gpio_get(dev, line);
+}
+
+static void f300_send_byte(struct cx23885_dev *dev, u8 dta)
+{
+ u8 i;
+
+ for (i = 0; i < 8; i++) {
+ f300_set_line(dev, F300_CLK, 0);
+ udelay(30);
+ f300_set_line(dev, F300_DATA, (dta & 0x80) >> 7);/* msb first */
+ udelay(30);
+ dta <<= 1;
+ f300_set_line(dev, F300_CLK, 1);
+ udelay(30);
+ }
+}
+
+static u8 f300_get_byte(struct cx23885_dev *dev)
+{
+ u8 i, dta = 0;
+
+ for (i = 0; i < 8; i++) {
+ f300_set_line(dev, F300_CLK, 0);
+ udelay(30);
+ dta <<= 1;
+ f300_set_line(dev, F300_CLK, 1);
+ udelay(30);
+ dta |= f300_get_line(dev, F300_DATA);/* msb first */
+
+ }
+
+ return dta;
+}
+
+static u8 f300_xfer(struct dvb_frontend *fe, u8 *buf)
+{
+ struct cx23885_tsport *port = fe->dvb->priv;
+ struct cx23885_dev *dev = port->dev;
+ u8 i, temp, ret = 0;
+
+ temp = buf[0];
+ for (i = 0; i < buf[0]; i++)
+ temp += buf[i + 1];
+ temp = (~temp + 1);/* get check sum */
+ buf[1 + buf[0]] = temp;
+
+ f300_set_line(dev, F300_RESET, 1);
+ f300_set_line(dev, F300_CLK, 1);
+ udelay(30);
+ f300_set_line(dev, F300_DATA, 1);
+ msleep(1);
+
+ /* question: */
+ f300_set_line(dev, F300_RESET, 0);/* begin to send data */
+ msleep(1);
+
+ f300_send_byte(dev, 0xe0);/* the slave address is 0xe0, write */
+ msleep(1);
+
+ temp = buf[0];
+ temp += 2;
+ for (i = 0; i < temp; i++)
+ f300_send_byte(dev, buf[i]);
+
+ f300_set_line(dev, F300_RESET, 1);/* sent data over */
+ f300_set_line(dev, F300_DATA, 1);
+
+ /* answer: */
+ temp = 0;
+ for (i = 0; ((i < 8) & (temp == 0)); i++) {
+ msleep(1);
+ if (f300_get_line(dev, F300_BUSY) == 0)
+ temp = 1;
+ }
+
+ if (i > 7) {
+ printk(KERN_ERR "%s: timeout, the slave no response\n",
+ __func__);
+ ret = 1; /* timeout, the slave no response */
+ } else { /* the slave not busy, prepare for getting data */
+ f300_set_line(dev, F300_RESET, 0);/*ready...*/
+ msleep(1);
+ f300_send_byte(dev, 0xe1);/* 0xe1 is Read */
+ msleep(1);
+ temp = f300_get_byte(dev);/*get the data length */
+ if (temp > 14)
+ temp = 14;
+
+ for (i = 0; i < (temp + 1); i++)
+ f300_get_byte(dev);/* get data to empty buffer */
+
+ f300_set_line(dev, F300_RESET, 1);/* received data over */
+ f300_set_line(dev, F300_DATA, 1);
+ }
+
+ return ret;
+}
+
+int f300_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+ u8 buf[16];
+
+ buf[0] = 0x05;
+ buf[1] = 0x38;/* write port */
+ buf[2] = 0x01;/* A port, lnb power */
+
+ switch (voltage) {
+ case SEC_VOLTAGE_13:
+ buf[3] = 0x01;/* power on */
+ buf[4] = 0x02;/* B port, H/V */
+ buf[5] = 0x00;/*13V v*/
+ break;
+ case SEC_VOLTAGE_18:
+ buf[3] = 0x01;
+ buf[4] = 0x02;
+ buf[5] = 0x01;/* 18V h*/
+ break;
+ case SEC_VOLTAGE_OFF:
+ buf[3] = 0x00;/* power off */
+ buf[4] = 0x00;
+ buf[5] = 0x00;
+ break;
+ }
+
+ return f300_xfer(fe, buf);
+}
diff --git a/drivers/media/video/cx23885/cx23885-f300.h b/drivers/media/video/cx23885/cx23885-f300.h
new file mode 100644
index 000000000000..e73344c94963
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-f300.h
@@ -0,0 +1,2 @@
+extern int f300_set_voltage(struct dvb_frontend *fe,
+ fe_sec_voltage_t voltage);
diff --git a/drivers/media/video/cx23885/cx23885-input.c b/drivers/media/video/cx23885/cx23885-input.c
new file mode 100644
index 000000000000..469e083dd5f8
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-input.c
@@ -0,0 +1,427 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Infrared remote control input device
+ *
+ * Most of this file is
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * However, the cx23885_input_{init,fini} functions contained herein are
+ * derived from Linux kernel files linux/media/video/.../...-input.c marked as:
+ *
+ * Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+ * Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+ * Markus Rechberger <mrechberger@gmail.com>
+ * Mauro Carvalho Chehab <mchehab@infradead.org>
+ * Sascha Sommer <saschasommer@freenet.de>
+ * Copyright (C) 2004, 2005 Chris Pascoe
+ * Copyright (C) 2003, 2004 Gerd Knorr
+ * Copyright (C) 2003 Pavel Machek
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/input.h>
+#include <media/ir-common.h>
+#include <media/v4l2-subdev.h>
+
+#include "cx23885.h"
+
+#define RC5_BITS 14
+#define RC5_HALF_BITS (2*RC5_BITS)
+#define RC5_HALF_BITS_MASK ((1 << RC5_HALF_BITS) - 1)
+
+#define RC5_START_BITS_NORMAL 0x3 /* Command range 0 - 63 */
+#define RC5_START_BITS_EXTENDED 0x2 /* Command range 64 - 127 */
+
+#define RC5_EXTENDED_COMMAND_OFFSET 64
+
+static inline unsigned int rc5_command(u32 rc5_baseband)
+{
+ return RC5_INSTR(rc5_baseband) +
+ ((RC5_START(rc5_baseband) == RC5_START_BITS_EXTENDED)
+ ? RC5_EXTENDED_COMMAND_OFFSET : 0);
+}
+
+static void cx23885_input_process_raw_rc5(struct cx23885_dev *dev)
+{
+ struct card_ir *ir_input = dev->ir_input;
+ unsigned int code, command;
+ u32 rc5;
+
+ /* Ignore codes that are too short to be valid RC-5 */
+ if (ir_input->last_bit < (RC5_HALF_BITS - 1))
+ return;
+
+ /* The library has the manchester coding backwards; XOR to adapt. */
+ code = (ir_input->code & RC5_HALF_BITS_MASK) ^ RC5_HALF_BITS_MASK;
+ rc5 = ir_rc5_decode(code);
+
+ switch (RC5_START(rc5)) {
+ case RC5_START_BITS_NORMAL:
+ break;
+ case RC5_START_BITS_EXTENDED:
+ /* Don't allow if the remote only emits standard commands */
+ if (ir_input->start == RC5_START_BITS_NORMAL)
+ return;
+ break;
+ default:
+ return;
+ }
+
+ if (ir_input->addr != RC5_ADDR(rc5))
+ return;
+
+ /* Don't generate a keypress for RC-5 auto-repeated keypresses */
+ command = rc5_command(rc5);
+ if (RC5_TOGGLE(rc5) != RC5_TOGGLE(ir_input->last_rc5) ||
+ command != rc5_command(ir_input->last_rc5) ||
+ /* Catch T == 0, CMD == 0 (e.g. '0') as first keypress after init */
+ RC5_START(ir_input->last_rc5) == 0) {
+ /* This keypress is differnet: not an auto repeat */
+ ir_input_nokey(ir_input->dev, &ir_input->ir);
+ ir_input_keydown(ir_input->dev, &ir_input->ir, command);
+ }
+ ir_input->last_rc5 = rc5;
+
+ /* Schedule when we should do the key up event: ir_input_nokey() */
+ mod_timer(&ir_input->timer_keyup,
+ jiffies + msecs_to_jiffies(ir_input->rc5_key_timeout));
+}
+
+static void cx23885_input_next_pulse_width_rc5(struct cx23885_dev *dev,
+ u32 ns_pulse)
+{
+ const int rc5_quarterbit_ns = 444444; /* 32 cycles/36 kHz/2 = 444 us */
+ struct card_ir *ir_input = dev->ir_input;
+ int i, level, quarterbits, halfbits;
+
+ if (!ir_input->active) {
+ ir_input->active = 1;
+ /* assume an initial space that we may not detect or measure */
+ ir_input->code = 0;
+ ir_input->last_bit = 0;
+ }
+
+ if (ns_pulse == V4L2_SUBDEV_IR_PULSE_RX_SEQ_END) {
+ ir_input->last_bit++; /* Account for the final space */
+ ir_input->active = 0;
+ cx23885_input_process_raw_rc5(dev);
+ return;
+ }
+
+ level = (ns_pulse & V4L2_SUBDEV_IR_PULSE_LEVEL_MASK) ? 1 : 0;
+
+ /* Skip any leading space to sync to the start bit */
+ if (ir_input->last_bit == 0 && level == 0)
+ return;
+
+ /*
+ * With valid RC-5 we can get up to two consecutive half-bits in a
+ * single pulse measurment. Experiments have shown that the duration
+ * of a half-bit can vary. Make sure we always end up with an even
+ * number of quarter bits at the same level (mark or space).
+ */
+ ns_pulse &= V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS;
+ quarterbits = ns_pulse / rc5_quarterbit_ns;
+ if (quarterbits & 1)
+ quarterbits++;
+ halfbits = quarterbits / 2;
+
+ for (i = 0; i < halfbits; i++) {
+ ir_input->last_bit++;
+ ir_input->code |= (level << ir_input->last_bit);
+
+ if (ir_input->last_bit >= RC5_HALF_BITS-1) {
+ ir_input->active = 0;
+ cx23885_input_process_raw_rc5(dev);
+ /*
+ * If level is 1, a leading mark is invalid for RC5.
+ * If level is 0, we scan past extra intial space.
+ * Either way we don't want to reactivate collecting
+ * marks or spaces here with any left over half-bits.
+ */
+ break;
+ }
+ }
+}
+
+static void cx23885_input_process_pulse_widths_rc5(struct cx23885_dev *dev,
+ bool add_eom)
+{
+ struct card_ir *ir_input = dev->ir_input;
+ struct ir_input_state *ir_input_state = &ir_input->ir;
+
+ u32 ns_pulse[RC5_HALF_BITS+1];
+ ssize_t num = 0;
+ int count, i;
+
+ do {
+ v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ns_pulse,
+ sizeof(ns_pulse), &num);
+
+ count = num / sizeof(u32);
+
+ /* Append an end of Rx seq, if the caller requested */
+ if (add_eom && count < ARRAY_SIZE(ns_pulse)) {
+ ns_pulse[count] = V4L2_SUBDEV_IR_PULSE_RX_SEQ_END;
+ count++;
+ }
+
+ /* Just drain the Rx FIFO, if we're called, but not RC-5 */
+ if (ir_input_state->ir_type != IR_TYPE_RC5)
+ continue;
+
+ for (i = 0; i < count; i++)
+ cx23885_input_next_pulse_width_rc5(dev, ns_pulse[i]);
+ } while (num != 0);
+}
+
+void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events)
+{
+ struct v4l2_subdev_ir_parameters params;
+ int overrun, data_available;
+
+ if (dev->sd_ir == NULL || events == 0)
+ return;
+
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ /*
+ * The only board we handle right now. However other boards
+ * using the CX2388x integrated IR controller should be similar
+ */
+ break;
+ default:
+ return;
+ }
+
+ overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN |
+ V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN);
+
+ data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED |
+ V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ);
+
+ if (overrun) {
+ /* If there was a FIFO overrun, stop the device */
+ v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+ params.enable = false;
+ /* Mitigate race with cx23885_input_ir_stop() */
+ params.shutdown = atomic_read(&dev->ir_input_stopping);
+ v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+ }
+
+ if (data_available)
+ cx23885_input_process_pulse_widths_rc5(dev, overrun);
+
+ if (overrun) {
+ /* If there was a FIFO overrun, clear & restart the device */
+ params.enable = true;
+ /* Mitigate race with cx23885_input_ir_stop() */
+ params.shutdown = atomic_read(&dev->ir_input_stopping);
+ v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+ }
+}
+
+static void cx23885_input_ir_start(struct cx23885_dev *dev)
+{
+ struct card_ir *ir_input = dev->ir_input;
+ struct ir_input_state *ir_input_state = &ir_input->ir;
+ struct v4l2_subdev_ir_parameters params;
+
+ if (dev->sd_ir == NULL)
+ return;
+
+ atomic_set(&dev->ir_input_stopping, 0);
+
+ /* keyup timer set up, if needed */
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ setup_timer(&ir_input->timer_keyup,
+ ir_rc5_timer_keyup, /* Not actually RC-5 specific */
+ (unsigned long) ir_input);
+ if (ir_input_state->ir_type == IR_TYPE_RC5) {
+ /*
+ * RC-5 repeats a held key every
+ * 64 bits * (2 * 32/36000) sec/bit = 113.778 ms
+ */
+ ir_input->rc5_key_timeout = 115;
+ }
+ break;
+ }
+
+ v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ /*
+ * The IR controller on this board only returns pulse widths.
+ * Any other mode setting will fail to set up the device.
+ */
+ params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
+ params.enable = true;
+ params.interrupt_enable = true;
+ params.shutdown = false;
+
+ /* Setup for baseband compatible with both RC-5 and RC-6A */
+ params.modulation = false;
+ /* RC-5: 2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/
+ /* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/
+ params.max_pulse_width = 3333333; /* ns */
+ /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */
+ /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */
+ params.noise_filter_min_width = 333333; /* ns */
+ /*
+ * This board has inverted receive sense:
+ * mark is received as low logic level;
+ * falling edges are detected as rising edges; etc.
+ */
+ params.invert = true;
+ break;
+ }
+ v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+}
+
+static void cx23885_input_ir_stop(struct cx23885_dev *dev)
+{
+ struct card_ir *ir_input = dev->ir_input;
+ struct v4l2_subdev_ir_parameters params;
+
+ if (dev->sd_ir == NULL)
+ return;
+
+ /*
+ * Stop the sd_ir subdevice from generating notifications and
+ * scheduling work.
+ * It is shutdown this way in order to mitigate a race with
+ * cx23885_input_rx_work_handler() in the overrun case, which could
+ * re-enable the subdevice.
+ */
+ atomic_set(&dev->ir_input_stopping, 1);
+ v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+ while (params.shutdown == false) {
+ params.enable = false;
+ params.interrupt_enable = false;
+ params.shutdown = true;
+ v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+ v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+ }
+
+ flush_scheduled_work();
+
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ del_timer_sync(&ir_input->timer_keyup);
+ break;
+ }
+}
+
+int cx23885_input_init(struct cx23885_dev *dev)
+{
+ struct card_ir *ir;
+ struct input_dev *input_dev;
+ struct ir_scancode_table *ir_codes = NULL;
+ int ir_type, ir_addr, ir_start;
+ int ret;
+
+ /*
+ * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't
+ * encapsulated in a v4l2_subdev, then I'm not going to deal with it.
+ */
+ if (dev->sd_ir == NULL)
+ return -ENODEV;
+
+ switch (dev->board) {
+ case CX23885_BOARD_HAUPPAUGE_HVR1850:
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ /* Parameters for the grey Hauppauge remote for the HVR-1850 */
+ ir_codes = &ir_codes_hauppauge_new_table;
+ ir_type = IR_TYPE_RC5;
+ ir_addr = 0x1e; /* RC-5 system bits emitted by the remote */
+ ir_start = RC5_START_BITS_NORMAL; /* A basic RC-5 remote */
+ break;
+ }
+ if (ir_codes == NULL)
+ return -ENODEV;
+
+ ir = kzalloc(sizeof(*ir), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!ir || !input_dev) {
+ ret = -ENOMEM;
+ goto err_out_free;
+ }
+
+ ir->dev = input_dev;
+ ir->addr = ir_addr;
+ ir->start = ir_start;
+
+ /* init input device */
+ snprintf(ir->name, sizeof(ir->name), "cx23885 IR (%s)",
+ cx23885_boards[dev->board].name);
+ snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", pci_name(dev->pci));
+
+ ret = ir_input_init(input_dev, &ir->ir, ir_type, ir_codes);
+ if (ret < 0)
+ goto err_out_free;
+
+ input_dev->name = ir->name;
+ input_dev->phys = ir->phys;
+ input_dev->id.bustype = BUS_PCI;
+ input_dev->id.version = 1;
+ if (dev->pci->subsystem_vendor) {
+ input_dev->id.vendor = dev->pci->subsystem_vendor;
+ input_dev->id.product = dev->pci->subsystem_device;
+ } else {
+ input_dev->id.vendor = dev->pci->vendor;
+ input_dev->id.product = dev->pci->device;
+ }
+ input_dev->dev.parent = &dev->pci->dev;
+
+ dev->ir_input = ir;
+ cx23885_input_ir_start(dev);
+
+ ret = input_register_device(ir->dev);
+ if (ret)
+ goto err_out_stop;
+
+ return 0;
+
+err_out_stop:
+ cx23885_input_ir_stop(dev);
+ dev->ir_input = NULL;
+err_out_free:
+ ir_input_free(input_dev);
+ input_free_device(input_dev);
+ kfree(ir);
+ return ret;
+}
+
+void cx23885_input_fini(struct cx23885_dev *dev)
+{
+ /* Always stop the IR hardware from generating interrupts */
+ cx23885_input_ir_stop(dev);
+
+ if (dev->ir_input == NULL)
+ return;
+ ir_input_free(dev->ir_input->dev);
+ input_unregister_device(dev->ir_input->dev);
+ kfree(dev->ir_input);
+ dev->ir_input = NULL;
+}
diff --git a/drivers/media/video/cx23885/cx23885-input.h b/drivers/media/video/cx23885/cx23885-input.h
new file mode 100644
index 000000000000..3572cb1ecfc2
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-input.h
@@ -0,0 +1,30 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Infrared remote control input device
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef _CX23885_INPUT_H_
+#define _CX23885_INPUT_H_
+int cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events);
+
+int cx23885_input_init(struct cx23885_dev *dev);
+void cx23885_input_fini(struct cx23885_dev *dev);
+#endif
diff --git a/drivers/media/video/cx23885/cx23885-ioctl.c b/drivers/media/video/cx23885/cx23885-ioctl.c
new file mode 100644
index 000000000000..dfb4627fb340
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-ioctl.c
@@ -0,0 +1,208 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Various common ioctl() support functions
+ *
+ * Copyright (c) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "cx23885.h"
+#include <media/v4l2-chip-ident.h>
+
+int cx23885_g_chip_ident(struct file *file, void *fh,
+ struct v4l2_dbg_chip_ident *chip)
+{
+ struct cx23885_dev *dev = ((struct cx23885_fh *)fh)->dev;
+ int err = 0;
+ u8 rev;
+
+ chip->ident = V4L2_IDENT_NONE;
+ chip->revision = 0;
+ switch (chip->match.type) {
+ case V4L2_CHIP_MATCH_HOST:
+ switch (chip->match.addr) {
+ case 0:
+ rev = cx_read(RDR_CFG2) & 0xff;
+ switch (dev->pci->device) {
+ case 0x8852:
+ /* rev 0x04 could be '885 or '888. Pick '888. */
+ if (rev == 0x04)
+ chip->ident = V4L2_IDENT_CX23888;
+ else
+ chip->ident = V4L2_IDENT_CX23885;
+ break;
+ case 0x8880:
+ if (rev == 0x0e || rev == 0x0f)
+ chip->ident = V4L2_IDENT_CX23887;
+ else
+ chip->ident = V4L2_IDENT_CX23888;
+ break;
+ default:
+ chip->ident = V4L2_IDENT_UNKNOWN;
+ break;
+ }
+ chip->revision = (dev->pci->device << 16) | (rev << 8) |
+ (dev->hwrevision & 0xff);
+ break;
+ case 1:
+ if (dev->v4l_device != NULL) {
+ chip->ident = V4L2_IDENT_CX23417;
+ chip->revision = 0;
+ }
+ break;
+ case 2:
+ /*
+ * The integrated IR controller on the CX23888 is
+ * host chip 2. It may not be used/initialized or sd_ir
+ * may be pointing at the cx25840 subdevice for the
+ * IR controller on the CX23885. Thus we find it
+ * without using the dev->sd_ir pointer.
+ */
+ call_hw(dev, CX23885_HW_888_IR, core, g_chip_ident,
+ chip);
+ break;
+ default:
+ err = -EINVAL; /* per V4L2 spec */
+ break;
+ }
+ break;
+ case V4L2_CHIP_MATCH_I2C_DRIVER:
+ /* If needed, returns V4L2_IDENT_AMBIGUOUS without extra work */
+ call_all(dev, core, g_chip_ident, chip);
+ break;
+ case V4L2_CHIP_MATCH_I2C_ADDR:
+ /*
+ * We could return V4L2_IDENT_UNKNOWN, but we don't do the work
+ * to look if a chip is at the address with no driver. That's a
+ * dangerous thing to do with EEPROMs anyway.
+ */
+ call_all(dev, core, g_chip_ident, chip);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ return err;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int cx23885_g_host_register(struct cx23885_dev *dev,
+ struct v4l2_dbg_register *reg)
+{
+ if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0))
+ return -EINVAL;
+
+ reg->size = 4;
+ reg->val = cx_read(reg->reg);
+ return 0;
+}
+
+static int cx23417_g_register(struct cx23885_dev *dev,
+ struct v4l2_dbg_register *reg)
+{
+ u32 value;
+
+ if (dev->v4l_device == NULL)
+ return -EINVAL;
+
+ if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000)
+ return -EINVAL;
+
+ if (mc417_register_read(dev, (u16) reg->reg, &value))
+ return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */
+
+ reg->size = 4;
+ reg->val = value;
+ return 0;
+}
+
+int cx23885_g_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+ struct cx23885_dev *dev = ((struct cx23885_fh *)fh)->dev;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (reg->match.type == V4L2_CHIP_MATCH_HOST) {
+ switch (reg->match.addr) {
+ case 0:
+ return cx23885_g_host_register(dev, reg);
+ case 1:
+ return cx23417_g_register(dev, reg);
+ default:
+ break;
+ }
+ }
+
+ /* FIXME - any error returns should not be ignored */
+ call_all(dev, core, g_register, reg);
+ return 0;
+}
+
+static int cx23885_s_host_register(struct cx23885_dev *dev,
+ struct v4l2_dbg_register *reg)
+{
+ if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0))
+ return -EINVAL;
+
+ reg->size = 4;
+ cx_write(reg->reg, reg->val);
+ return 0;
+}
+
+static int cx23417_s_register(struct cx23885_dev *dev,
+ struct v4l2_dbg_register *reg)
+{
+ if (dev->v4l_device == NULL)
+ return -EINVAL;
+
+ if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000)
+ return -EINVAL;
+
+ if (mc417_register_write(dev, (u16) reg->reg, (u32) reg->val))
+ return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */
+
+ reg->size = 4;
+ return 0;
+}
+
+int cx23885_s_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+ struct cx23885_dev *dev = ((struct cx23885_fh *)fh)->dev;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (reg->match.type == V4L2_CHIP_MATCH_HOST) {
+ switch (reg->match.addr) {
+ case 0:
+ return cx23885_s_host_register(dev, reg);
+ case 1:
+ return cx23417_s_register(dev, reg);
+ default:
+ break;
+ }
+ }
+
+ /* FIXME - any error returns should not be ignored */
+ call_all(dev, core, s_register, reg);
+ return 0;
+}
+#endif
diff --git a/drivers/media/video/cx23885/cx23885-ioctl.h b/drivers/media/video/cx23885/cx23885-ioctl.h
new file mode 100644
index 000000000000..80b0f4923c6a
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-ioctl.h
@@ -0,0 +1,39 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Various common ioctl() support functions
+ *
+ * Copyright (c) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _CX23885_IOCTL_H_
+#define _CX23885_IOCTL_H_
+
+int cx23885_g_chip_ident(struct file *file, void *fh,
+ struct v4l2_dbg_chip_ident *chip);
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+int cx23885_g_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg);
+
+
+int cx23885_s_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg);
+
+#endif
+#endif
diff --git a/drivers/media/video/cx23885/cx23885-ir.c b/drivers/media/video/cx23885/cx23885-ir.c
new file mode 100644
index 000000000000..6ae982cc9856
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-ir.c
@@ -0,0 +1,101 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Infrared device support routines - non-input, non-vl42_subdev routines
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <media/v4l2-device.h>
+
+#include "cx23885.h"
+#include "cx23885-input.h"
+
+#define CX23885_IR_RX_FIFO_SERVICE_REQ 0
+#define CX23885_IR_RX_END_OF_RX_DETECTED 1
+#define CX23885_IR_RX_HW_FIFO_OVERRUN 2
+#define CX23885_IR_RX_SW_FIFO_OVERRUN 3
+
+#define CX23885_IR_TX_FIFO_SERVICE_REQ 0
+
+
+void cx23885_ir_rx_work_handler(struct work_struct *work)
+{
+ struct cx23885_dev *dev =
+ container_of(work, struct cx23885_dev, ir_rx_work);
+ u32 events = 0;
+ unsigned long *notifications = &dev->ir_rx_notifications;
+
+ if (test_and_clear_bit(CX23885_IR_RX_SW_FIFO_OVERRUN, notifications))
+ events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN;
+ if (test_and_clear_bit(CX23885_IR_RX_HW_FIFO_OVERRUN, notifications))
+ events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN;
+ if (test_and_clear_bit(CX23885_IR_RX_END_OF_RX_DETECTED, notifications))
+ events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED;
+ if (test_and_clear_bit(CX23885_IR_RX_FIFO_SERVICE_REQ, notifications))
+ events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ;
+
+ if (events == 0)
+ return;
+
+ if (dev->ir_input)
+ cx23885_input_rx_work_handler(dev, events);
+}
+
+void cx23885_ir_tx_work_handler(struct work_struct *work)
+{
+ struct cx23885_dev *dev =
+ container_of(work, struct cx23885_dev, ir_tx_work);
+ u32 events = 0;
+ unsigned long *notifications = &dev->ir_tx_notifications;
+
+ if (test_and_clear_bit(CX23885_IR_TX_FIFO_SERVICE_REQ, notifications))
+ events |= V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ;
+
+ if (events == 0)
+ return;
+
+}
+
+/* Called in an IRQ context */
+void cx23885_ir_rx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events)
+{
+ struct cx23885_dev *dev = to_cx23885(sd->v4l2_dev);
+ unsigned long *notifications = &dev->ir_rx_notifications;
+
+ if (events & V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ)
+ set_bit(CX23885_IR_RX_FIFO_SERVICE_REQ, notifications);
+ if (events & V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED)
+ set_bit(CX23885_IR_RX_END_OF_RX_DETECTED, notifications);
+ if (events & V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN)
+ set_bit(CX23885_IR_RX_HW_FIFO_OVERRUN, notifications);
+ if (events & V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN)
+ set_bit(CX23885_IR_RX_SW_FIFO_OVERRUN, notifications);
+ schedule_work(&dev->ir_rx_work);
+}
+
+/* Called in an IRQ context */
+void cx23885_ir_tx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events)
+{
+ struct cx23885_dev *dev = to_cx23885(sd->v4l2_dev);
+ unsigned long *notifications = &dev->ir_tx_notifications;
+
+ if (events & V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ)
+ set_bit(CX23885_IR_TX_FIFO_SERVICE_REQ, notifications);
+ schedule_work(&dev->ir_tx_work);
+}
diff --git a/drivers/media/video/cx23885/cx23885-ir.h b/drivers/media/video/cx23885/cx23885-ir.h
new file mode 100644
index 000000000000..9b8a6d5d1ef6
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23885-ir.h
@@ -0,0 +1,31 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * Infrared device support routines - non-input, non-vl42_subdev routines
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef _CX23885_IR_H_
+#define _CX23885_IR_H_
+void cx23885_ir_rx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events);
+void cx23885_ir_tx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events);
+
+void cx23885_ir_rx_work_handler(struct work_struct *work);
+void cx23885_ir_tx_work_handler(struct work_struct *work);
+#endif
diff --git a/drivers/media/video/cx23885/cx23885-reg.h b/drivers/media/video/cx23885/cx23885-reg.h
index eafbe5226bae..c0bc9a068954 100644
--- a/drivers/media/video/cx23885/cx23885-reg.h
+++ b/drivers/media/video/cx23885/cx23885-reg.h
@@ -212,8 +212,9 @@ Channel manager Data Structure entry = 20 DWORD
#define DEV_CNTRL2 0x00040000
-#define PCI_MSK_GPIO1 (1 << 24)
-#define PCI_MSK_GPIO0 (1 << 23)
+#define PCI_MSK_IR (1 << 28)
+#define PCI_MSK_GPIO1 (1 << 24)
+#define PCI_MSK_GPIO0 (1 << 23)
#define PCI_MSK_APB_DMA (1 << 12)
#define PCI_MSK_AL_WR (1 << 11)
#define PCI_MSK_AL_RD (1 << 10)
diff --git a/drivers/media/video/cx23885/cx23885-video.c b/drivers/media/video/cx23885/cx23885-video.c
index 654cc253cd50..8b372b4f0de2 100644
--- a/drivers/media/video/cx23885/cx23885-video.c
+++ b/drivers/media/video/cx23885/cx23885-video.c
@@ -35,6 +35,7 @@
#include "cx23885.h"
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
+#include "cx23885-ioctl.h"
MODULE_DESCRIPTION("v4l2 driver module for cx23885 based TV cards");
MODULE_AUTHOR("Steven Toth <stoth@linuxtv.org>");
@@ -401,6 +402,13 @@ static int cx23885_video_mux(struct cx23885_dev *dev, unsigned int input)
INPUT(input)->gpio2, INPUT(input)->gpio3);
dev->input = input;
+ if (dev->board == CX23885_BOARD_MYGICA_X8506 ||
+ dev->board == CX23885_BOARD_MAGICPRO_PROHDTVE2) {
+ /* Select Analog TV */
+ if (INPUT(input)->type == CX23885_VMUX_TELEVISION)
+ cx23885_gpio_clear(dev, GPIO_0);
+ }
+
/* Tell the internal A/V decoder */
v4l2_subdev_call(dev->sd_cx25840, video, s_routing,
INPUT(input)->vmux, 0, 0);
@@ -1144,6 +1152,7 @@ static int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i)
[CX23885_VMUX_COMPOSITE3] = "Composite3",
[CX23885_VMUX_COMPOSITE4] = "Composite4",
[CX23885_VMUX_SVIDEO] = "S-Video",
+ [CX23885_VMUX_COMPONENT] = "Component",
[CX23885_VMUX_TELEVISION] = "Television",
[CX23885_VMUX_CABLE] = "Cable TV",
[CX23885_VMUX_DVB] = "DVB",
@@ -1312,34 +1321,6 @@ static int vidioc_s_frequency(struct file *file, void *priv,
cx23885_set_freq(dev, f);
}
-#ifdef CONFIG_VIDEO_ADV_DEBUG
-static int vidioc_g_register(struct file *file, void *fh,
- struct v4l2_dbg_register *reg)
-{
- struct cx23885_dev *dev = ((struct cx23885_fh *)fh)->dev;
-
- if (!v4l2_chip_match_host(&reg->match))
- return -EINVAL;
-
- call_all(dev, core, g_register, reg);
-
- return 0;
-}
-
-static int vidioc_s_register(struct file *file, void *fh,
- struct v4l2_dbg_register *reg)
-{
- struct cx23885_dev *dev = ((struct cx23885_fh *)fh)->dev;
-
- if (!v4l2_chip_match_host(&reg->match))
- return -EINVAL;
-
- call_all(dev, core, s_register, reg);
-
- return 0;
-}
-#endif
-
/* ----------------------------------------------------------- */
static void cx23885_vid_timeout(unsigned long data)
@@ -1449,9 +1430,10 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_g_chip_ident = cx23885_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
- .vidioc_g_register = vidioc_g_register,
- .vidioc_s_register = vidioc_s_register,
+ .vidioc_g_register = cx23885_g_register,
+ .vidioc_s_register = cx23885_s_register,
#endif
};
@@ -1529,9 +1511,11 @@ int cx23885_video_register(struct cx23885_dev *dev)
if (sd) {
struct tuner_setup tun_setup;
+ memset(&tun_setup, 0, sizeof(tun_setup));
tun_setup.mode_mask = T_ANALOG_TV;
tun_setup.type = dev->tuner_type;
tun_setup.addr = v4l2_i2c_subdev_addr(sd);
+ tun_setup.tuner_callback = cx23885_tuner_callback;
v4l2_subdev_call(sd, tuner, s_type_addr, &tun_setup);
}
diff --git a/drivers/media/video/cx23885/cx23885.h b/drivers/media/video/cx23885/cx23885.h
index cc7a165561ff..fa744764dc8b 100644
--- a/drivers/media/video/cx23885/cx23885.h
+++ b/drivers/media/video/cx23885/cx23885.h
@@ -79,6 +79,8 @@
#define CX23885_BOARD_MAGICPRO_PROHDTVE2 23
#define CX23885_BOARD_HAUPPAUGE_HVR1850 24
#define CX23885_BOARD_COMPRO_VIDEOMATE_E800 25
+#define CX23885_BOARD_HAUPPAUGE_HVR1290 26
+#define CX23885_BOARD_MYGICA_X8558PRO 27
#define GPIO_0 0x00000001
#define GPIO_1 0x00000002
@@ -157,6 +159,7 @@ enum cx23885_itype {
CX23885_VMUX_COMPOSITE3,
CX23885_VMUX_COMPOSITE4,
CX23885_VMUX_SVIDEO,
+ CX23885_VMUX_COMPONENT,
CX23885_VMUX_TELEVISION,
CX23885_VMUX_CABLE,
CX23885_VMUX_DVB,
@@ -297,10 +300,6 @@ struct cx23885_tsport {
/* Allow a single tsport to have multiple frontends */
u32 num_frontends;
void *port_priv;
-
- /* FIXME: temporary hack */
- int (*set_frontend_save) (struct dvb_frontend *,
- struct dvb_frontend_parameters *);
};
struct cx23885_dev {
@@ -356,6 +355,16 @@ struct cx23885_dev {
unsigned int has_radio;
struct v4l2_subdev *sd_cx25840;
+ /* Infrared */
+ struct v4l2_subdev *sd_ir;
+ struct work_struct ir_rx_work;
+ unsigned long ir_rx_notifications;
+ struct work_struct ir_tx_work;
+ unsigned long ir_tx_notifications;
+
+ struct card_ir *ir_input;
+ atomic_t ir_input_stopping;
+
/* V4l */
u32 freq;
struct video_device *video_dev;
@@ -383,6 +392,13 @@ static inline struct cx23885_dev *to_cx23885(struct v4l2_device *v4l2_dev)
#define call_all(dev, o, f, args...) \
v4l2_device_call_all(&dev->v4l2_dev, 0, o, f, ##args)
+#define CX23885_HW_888_IR (1 << 0)
+
+#define call_hw(dev, grpid, o, f, args...) \
+ v4l2_device_call_all(&dev->v4l2_dev, grpid, o, f, ##args)
+
+extern struct v4l2_subdev *cx23885_find_hw(struct cx23885_dev *dev, u32 hw);
+
extern struct list_head cx23885_devlist;
#define SRAM_CH01 0 /* Video A */
@@ -455,6 +471,7 @@ extern void cx23885_wakeup(struct cx23885_tsport *port,
extern void cx23885_gpio_set(struct cx23885_dev *dev, u32 mask);
extern void cx23885_gpio_clear(struct cx23885_dev *dev, u32 mask);
+extern u32 cx23885_gpio_get(struct cx23885_dev *dev, u32 mask);
extern void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask,
int asoutput);
@@ -471,6 +488,8 @@ extern int cx23885_tuner_callback(void *priv, int component,
int command, int arg);
extern void cx23885_card_list(struct cx23885_dev *dev);
extern int cx23885_ir_init(struct cx23885_dev *dev);
+extern void cx23885_ir_pci_int_enable(struct cx23885_dev *dev);
+extern void cx23885_ir_fini(struct cx23885_dev *dev);
extern void cx23885_gpio_setup(struct cx23885_dev *dev);
extern void cx23885_card_setup(struct cx23885_dev *dev);
extern void cx23885_card_setup_pre_i2c(struct cx23885_dev *dev);
@@ -515,6 +534,10 @@ extern void cx23885_417_check_encoder(struct cx23885_dev *dev);
extern void cx23885_mc417_init(struct cx23885_dev *dev);
extern int mc417_memory_read(struct cx23885_dev *dev, u32 address, u32 *value);
extern int mc417_memory_write(struct cx23885_dev *dev, u32 address, u32 value);
+extern int mc417_register_read(struct cx23885_dev *dev,
+ u16 address, u32 *value);
+extern int mc417_register_write(struct cx23885_dev *dev,
+ u16 address, u32 value);
extern void mc417_gpio_set(struct cx23885_dev *dev, u32 mask);
extern void mc417_gpio_clear(struct cx23885_dev *dev, u32 mask);
extern void mc417_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput);
diff --git a/drivers/media/video/cx23885/cx23888-ir.c b/drivers/media/video/cx23885/cx23888-ir.c
new file mode 100644
index 000000000000..3ccc8afeccf3
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23888-ir.c
@@ -0,0 +1,1239 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * CX23888 Integrated Consumer Infrared Controller
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/kfifo.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-chip-ident.h>
+
+#include "cx23885.h"
+
+static unsigned int ir_888_debug;
+module_param(ir_888_debug, int, 0644);
+MODULE_PARM_DESC(ir_888_debug, "enable debug messages [CX23888 IR controller]");
+
+#define CX23888_IR_REG_BASE 0x170000
+/*
+ * These CX23888 register offsets have a straightforward one to one mapping
+ * to the CX23885 register offsets of 0x200 through 0x218
+ */
+#define CX23888_IR_CNTRL_REG 0x170000
+#define CNTRL_WIN_3_3 0x00000000
+#define CNTRL_WIN_4_3 0x00000001
+#define CNTRL_WIN_3_4 0x00000002
+#define CNTRL_WIN_4_4 0x00000003
+#define CNTRL_WIN 0x00000003
+#define CNTRL_EDG_NONE 0x00000000
+#define CNTRL_EDG_FALL 0x00000004
+#define CNTRL_EDG_RISE 0x00000008
+#define CNTRL_EDG_BOTH 0x0000000C
+#define CNTRL_EDG 0x0000000C
+#define CNTRL_DMD 0x00000010
+#define CNTRL_MOD 0x00000020
+#define CNTRL_RFE 0x00000040
+#define CNTRL_TFE 0x00000080
+#define CNTRL_RXE 0x00000100
+#define CNTRL_TXE 0x00000200
+#define CNTRL_RIC 0x00000400
+#define CNTRL_TIC 0x00000800
+#define CNTRL_CPL 0x00001000
+#define CNTRL_LBM 0x00002000
+#define CNTRL_R 0x00004000
+
+#define CX23888_IR_TXCLK_REG 0x170004
+#define TXCLK_TCD 0x0000FFFF
+
+#define CX23888_IR_RXCLK_REG 0x170008
+#define RXCLK_RCD 0x0000FFFF
+
+#define CX23888_IR_CDUTY_REG 0x17000C
+#define CDUTY_CDC 0x0000000F
+
+#define CX23888_IR_STATS_REG 0x170010
+#define STATS_RTO 0x00000001
+#define STATS_ROR 0x00000002
+#define STATS_RBY 0x00000004
+#define STATS_TBY 0x00000008
+#define STATS_RSR 0x00000010
+#define STATS_TSR 0x00000020
+
+#define CX23888_IR_IRQEN_REG 0x170014
+#define IRQEN_RTE 0x00000001
+#define IRQEN_ROE 0x00000002
+#define IRQEN_RSE 0x00000010
+#define IRQEN_TSE 0x00000020
+
+#define CX23888_IR_FILTR_REG 0x170018
+#define FILTR_LPF 0x0000FFFF
+
+/* This register doesn't follow the pattern; it's 0x23C on a CX23885 */
+#define CX23888_IR_FIFO_REG 0x170040
+#define FIFO_RXTX 0x0000FFFF
+#define FIFO_RXTX_LVL 0x00010000
+#define FIFO_RXTX_RTO 0x0001FFFF
+#define FIFO_RX_NDV 0x00020000
+#define FIFO_RX_DEPTH 8
+#define FIFO_TX_DEPTH 8
+
+/* CX23888 unique registers */
+#define CX23888_IR_SEEDP_REG 0x17001C
+#define CX23888_IR_TIMOL_REG 0x170020
+#define CX23888_IR_WAKE0_REG 0x170024
+#define CX23888_IR_WAKE1_REG 0x170028
+#define CX23888_IR_WAKE2_REG 0x17002C
+#define CX23888_IR_MASK0_REG 0x170030
+#define CX23888_IR_MASK1_REG 0x170034
+#define CX23888_IR_MAKS2_REG 0x170038
+#define CX23888_IR_DPIPG_REG 0x17003C
+#define CX23888_IR_LEARN_REG 0x170044
+
+#define CX23888_VIDCLK_FREQ 108000000 /* 108 MHz, BT.656 */
+#define CX23888_IR_REFCLK_FREQ (CX23888_VIDCLK_FREQ / 2)
+
+#define CX23888_IR_RX_KFIFO_SIZE (512 * sizeof(u32))
+#define CX23888_IR_TX_KFIFO_SIZE (512 * sizeof(u32))
+
+struct cx23888_ir_state {
+ struct v4l2_subdev sd;
+ struct cx23885_dev *dev;
+ u32 id;
+ u32 rev;
+
+ struct v4l2_subdev_ir_parameters rx_params;
+ struct mutex rx_params_lock;
+ atomic_t rxclk_divider;
+ atomic_t rx_invert;
+
+ struct kfifo *rx_kfifo;
+ spinlock_t rx_kfifo_lock;
+
+ struct v4l2_subdev_ir_parameters tx_params;
+ struct mutex tx_params_lock;
+ atomic_t txclk_divider;
+
+ struct kfifo *tx_kfifo;
+ spinlock_t tx_kfifo_lock;
+};
+
+static inline struct cx23888_ir_state *to_state(struct v4l2_subdev *sd)
+{
+ return v4l2_get_subdevdata(sd);
+}
+
+/*
+ * IR register block read and write functions
+ */
+static
+inline int cx23888_ir_write4(struct cx23885_dev *dev, u32 addr, u32 value)
+{
+ cx_write(addr, value);
+ return 0;
+}
+
+static inline u32 cx23888_ir_read4(struct cx23885_dev *dev, u32 addr)
+{
+ return cx_read(addr);
+}
+
+static inline int cx23888_ir_and_or4(struct cx23885_dev *dev, u32 addr,
+ u32 and_mask, u32 or_value)
+{
+ cx_andor(addr, ~and_mask, or_value);
+ return 0;
+}
+
+/*
+ * Rx and Tx Clock Divider register computations
+ *
+ * Note the largest clock divider value of 0xffff corresponds to:
+ * (0xffff + 1) * 1000 / 108/2 MHz = 1,213,629.629... ns
+ * which fits in 21 bits, so we'll use unsigned int for time arguments.
+ */
+static inline u16 count_to_clock_divider(unsigned int d)
+{
+ if (d > RXCLK_RCD + 1)
+ d = RXCLK_RCD;
+ else if (d < 2)
+ d = 1;
+ else
+ d--;
+ return (u16) d;
+}
+
+static inline u16 ns_to_clock_divider(unsigned int ns)
+{
+ return count_to_clock_divider(
+ DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ / 1000000 * ns, 1000));
+}
+
+static inline unsigned int clock_divider_to_ns(unsigned int divider)
+{
+ /* Period of the Rx or Tx clock in ns */
+ return DIV_ROUND_CLOSEST((divider + 1) * 1000,
+ CX23888_IR_REFCLK_FREQ / 1000000);
+}
+
+static inline u16 carrier_freq_to_clock_divider(unsigned int freq)
+{
+ return count_to_clock_divider(
+ DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, freq * 16));
+}
+
+static inline unsigned int clock_divider_to_carrier_freq(unsigned int divider)
+{
+ return DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, (divider + 1) * 16);
+}
+
+static inline u16 freq_to_clock_divider(unsigned int freq,
+ unsigned int rollovers)
+{
+ return count_to_clock_divider(
+ DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, freq * rollovers));
+}
+
+static inline unsigned int clock_divider_to_freq(unsigned int divider,
+ unsigned int rollovers)
+{
+ return DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ,
+ (divider + 1) * rollovers);
+}
+
+/*
+ * Low Pass Filter register calculations
+ *
+ * Note the largest count value of 0xffff corresponds to:
+ * 0xffff * 1000 / 108/2 MHz = 1,213,611.11... ns
+ * which fits in 21 bits, so we'll use unsigned int for time arguments.
+ */
+static inline u16 count_to_lpf_count(unsigned int d)
+{
+ if (d > FILTR_LPF)
+ d = FILTR_LPF;
+ else if (d < 4)
+ d = 0;
+ return (u16) d;
+}
+
+static inline u16 ns_to_lpf_count(unsigned int ns)
+{
+ return count_to_lpf_count(
+ DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ / 1000000 * ns, 1000));
+}
+
+static inline unsigned int lpf_count_to_ns(unsigned int count)
+{
+ /* Duration of the Low Pass Filter rejection window in ns */
+ return DIV_ROUND_CLOSEST(count * 1000,
+ CX23888_IR_REFCLK_FREQ / 1000000);
+}
+
+static inline unsigned int lpf_count_to_us(unsigned int count)
+{
+ /* Duration of the Low Pass Filter rejection window in us */
+ return DIV_ROUND_CLOSEST(count, CX23888_IR_REFCLK_FREQ / 1000000);
+}
+
+/*
+ * FIFO register pulse width count compuations
+ */
+static u32 clock_divider_to_resolution(u16 divider)
+{
+ /*
+ * Resolution is the duration of 1 tick of the readable portion of
+ * of the pulse width counter as read from the FIFO. The two lsb's are
+ * not readable, hence the << 2. This function returns ns.
+ */
+ return DIV_ROUND_CLOSEST((1 << 2) * ((u32) divider + 1) * 1000,
+ CX23888_IR_REFCLK_FREQ / 1000000);
+}
+
+static u64 pulse_width_count_to_ns(u16 count, u16 divider)
+{
+ u64 n;
+ u32 rem;
+
+ /*
+ * The 2 lsb's of the pulse width timer count are not readable, hence
+ * the (count << 2) | 0x3
+ */
+ n = (((u64) count << 2) | 0x3) * (divider + 1) * 1000; /* millicycles */
+ rem = do_div(n, CX23888_IR_REFCLK_FREQ / 1000000); /* / MHz => ns */
+ if (rem >= CX23888_IR_REFCLK_FREQ / 1000000 / 2)
+ n++;
+ return n;
+}
+
+static unsigned int pulse_width_count_to_us(u16 count, u16 divider)
+{
+ u64 n;
+ u32 rem;
+
+ /*
+ * The 2 lsb's of the pulse width timer count are not readable, hence
+ * the (count << 2) | 0x3
+ */
+ n = (((u64) count << 2) | 0x3) * (divider + 1); /* cycles */
+ rem = do_div(n, CX23888_IR_REFCLK_FREQ / 1000000); /* / MHz => us */
+ if (rem >= CX23888_IR_REFCLK_FREQ / 1000000 / 2)
+ n++;
+ return (unsigned int) n;
+}
+
+/*
+ * Pulse Clocks computations: Combined Pulse Width Count & Rx Clock Counts
+ *
+ * The total pulse clock count is an 18 bit pulse width timer count as the most
+ * significant part and (up to) 16 bit clock divider count as a modulus.
+ * When the Rx clock divider ticks down to 0, it increments the 18 bit pulse
+ * width timer count's least significant bit.
+ */
+static u64 ns_to_pulse_clocks(u32 ns)
+{
+ u64 clocks;
+ u32 rem;
+ clocks = CX23888_IR_REFCLK_FREQ / 1000000 * (u64) ns; /* millicycles */
+ rem = do_div(clocks, 1000); /* /1000 = cycles */
+ if (rem >= 1000 / 2)
+ clocks++;
+ return clocks;
+}
+
+static u16 pulse_clocks_to_clock_divider(u64 count)
+{
+ u32 rem;
+
+ rem = do_div(count, (FIFO_RXTX << 2) | 0x3);
+
+ /* net result needs to be rounded down and decremented by 1 */
+ if (count > RXCLK_RCD + 1)
+ count = RXCLK_RCD;
+ else if (count < 2)
+ count = 1;
+ else
+ count--;
+ return (u16) count;
+}
+
+/*
+ * IR Control Register helpers
+ */
+enum tx_fifo_watermark {
+ TX_FIFO_HALF_EMPTY = 0,
+ TX_FIFO_EMPTY = CNTRL_TIC,
+};
+
+enum rx_fifo_watermark {
+ RX_FIFO_HALF_FULL = 0,
+ RX_FIFO_NOT_EMPTY = CNTRL_RIC,
+};
+
+static inline void control_tx_irq_watermark(struct cx23885_dev *dev,
+ enum tx_fifo_watermark level)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_TIC, level);
+}
+
+static inline void control_rx_irq_watermark(struct cx23885_dev *dev,
+ enum rx_fifo_watermark level)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_RIC, level);
+}
+
+static inline void control_tx_enable(struct cx23885_dev *dev, bool enable)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~(CNTRL_TXE | CNTRL_TFE),
+ enable ? (CNTRL_TXE | CNTRL_TFE) : 0);
+}
+
+static inline void control_rx_enable(struct cx23885_dev *dev, bool enable)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~(CNTRL_RXE | CNTRL_RFE),
+ enable ? (CNTRL_RXE | CNTRL_RFE) : 0);
+}
+
+static inline void control_tx_modulation_enable(struct cx23885_dev *dev,
+ bool enable)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_MOD,
+ enable ? CNTRL_MOD : 0);
+}
+
+static inline void control_rx_demodulation_enable(struct cx23885_dev *dev,
+ bool enable)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_DMD,
+ enable ? CNTRL_DMD : 0);
+}
+
+static inline void control_rx_s_edge_detection(struct cx23885_dev *dev,
+ u32 edge_types)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_EDG_BOTH,
+ edge_types & CNTRL_EDG_BOTH);
+}
+
+static void control_rx_s_carrier_window(struct cx23885_dev *dev,
+ unsigned int carrier,
+ unsigned int *carrier_range_low,
+ unsigned int *carrier_range_high)
+{
+ u32 v;
+ unsigned int c16 = carrier * 16;
+
+ if (*carrier_range_low < DIV_ROUND_CLOSEST(c16, 16 + 3)) {
+ v = CNTRL_WIN_3_4;
+ *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 4);
+ } else {
+ v = CNTRL_WIN_3_3;
+ *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 3);
+ }
+
+ if (*carrier_range_high > DIV_ROUND_CLOSEST(c16, 16 - 3)) {
+ v |= CNTRL_WIN_4_3;
+ *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 4);
+ } else {
+ v |= CNTRL_WIN_3_3;
+ *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 3);
+ }
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_WIN, v);
+}
+
+static inline void control_tx_polarity_invert(struct cx23885_dev *dev,
+ bool invert)
+{
+ cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_CPL,
+ invert ? CNTRL_CPL : 0);
+}
+
+/*
+ * IR Rx & Tx Clock Register helpers
+ */
+static unsigned int txclk_tx_s_carrier(struct cx23885_dev *dev,
+ unsigned int freq,
+ u16 *divider)
+{
+ *divider = carrier_freq_to_clock_divider(freq);
+ cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, *divider);
+ return clock_divider_to_carrier_freq(*divider);
+}
+
+static unsigned int rxclk_rx_s_carrier(struct cx23885_dev *dev,
+ unsigned int freq,
+ u16 *divider)
+{
+ *divider = carrier_freq_to_clock_divider(freq);
+ cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, *divider);
+ return clock_divider_to_carrier_freq(*divider);
+}
+
+static u32 txclk_tx_s_max_pulse_width(struct cx23885_dev *dev, u32 ns,
+ u16 *divider)
+{
+ u64 pulse_clocks;
+
+ if (ns > V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS)
+ ns = V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS;
+ pulse_clocks = ns_to_pulse_clocks(ns);
+ *divider = pulse_clocks_to_clock_divider(pulse_clocks);
+ cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, *divider);
+ return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider);
+}
+
+static u32 rxclk_rx_s_max_pulse_width(struct cx23885_dev *dev, u32 ns,
+ u16 *divider)
+{
+ u64 pulse_clocks;
+
+ if (ns > V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS)
+ ns = V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS;
+ pulse_clocks = ns_to_pulse_clocks(ns);
+ *divider = pulse_clocks_to_clock_divider(pulse_clocks);
+ cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, *divider);
+ return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider);
+}
+
+/*
+ * IR Tx Carrier Duty Cycle register helpers
+ */
+static unsigned int cduty_tx_s_duty_cycle(struct cx23885_dev *dev,
+ unsigned int duty_cycle)
+{
+ u32 n;
+ n = DIV_ROUND_CLOSEST(duty_cycle * 100, 625); /* 16ths of 100% */
+ if (n != 0)
+ n--;
+ if (n > 15)
+ n = 15;
+ cx23888_ir_write4(dev, CX23888_IR_CDUTY_REG, n);
+ return DIV_ROUND_CLOSEST((n + 1) * 100, 16);
+}
+
+/*
+ * IR Filter Register helpers
+ */
+static u32 filter_rx_s_min_width(struct cx23885_dev *dev, u32 min_width_ns)
+{
+ u32 count = ns_to_lpf_count(min_width_ns);
+ cx23888_ir_write4(dev, CX23888_IR_FILTR_REG, count);
+ return lpf_count_to_ns(count);
+}
+
+/*
+ * IR IRQ Enable Register helpers
+ */
+static inline void irqenable_rx(struct cx23885_dev *dev, u32 mask)
+{
+ mask &= (IRQEN_RTE | IRQEN_ROE | IRQEN_RSE);
+ cx23888_ir_and_or4(dev, CX23888_IR_IRQEN_REG,
+ ~(IRQEN_RTE | IRQEN_ROE | IRQEN_RSE), mask);
+}
+
+static inline void irqenable_tx(struct cx23885_dev *dev, u32 mask)
+{
+ mask &= IRQEN_TSE;
+ cx23888_ir_and_or4(dev, CX23888_IR_IRQEN_REG, ~IRQEN_TSE, mask);
+}
+
+/*
+ * V4L2 Subdevice IR Ops
+ */
+static int cx23888_ir_irq_handler(struct v4l2_subdev *sd, u32 status,
+ bool *handled)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+
+ u32 cntrl = cx23888_ir_read4(dev, CX23888_IR_CNTRL_REG);
+ u32 irqen = cx23888_ir_read4(dev, CX23888_IR_IRQEN_REG);
+ u32 stats = cx23888_ir_read4(dev, CX23888_IR_STATS_REG);
+
+ u32 rx_data[FIFO_RX_DEPTH];
+ int i, j, k;
+ u32 events, v;
+ int tsr, rsr, rto, ror, tse, rse, rte, roe, kror;
+
+ tsr = stats & STATS_TSR; /* Tx FIFO Service Request */
+ rsr = stats & STATS_RSR; /* Rx FIFO Service Request */
+ rto = stats & STATS_RTO; /* Rx Pulse Width Timer Time Out */
+ ror = stats & STATS_ROR; /* Rx FIFO Over Run */
+
+ tse = irqen & IRQEN_TSE; /* Tx FIFO Service Request IRQ Enable */
+ rse = irqen & IRQEN_RSE; /* Rx FIFO Service Reuqest IRQ Enable */
+ rte = irqen & IRQEN_RTE; /* Rx Pulse Width Timer Time Out IRQ Enable */
+ roe = irqen & IRQEN_ROE; /* Rx FIFO Over Run IRQ Enable */
+
+ *handled = false;
+ v4l2_dbg(2, ir_888_debug, sd, "IRQ Status: %s %s %s %s %s %s\n",
+ tsr ? "tsr" : " ", rsr ? "rsr" : " ",
+ rto ? "rto" : " ", ror ? "ror" : " ",
+ stats & STATS_TBY ? "tby" : " ",
+ stats & STATS_RBY ? "rby" : " ");
+
+ v4l2_dbg(2, ir_888_debug, sd, "IRQ Enables: %s %s %s %s\n",
+ tse ? "tse" : " ", rse ? "rse" : " ",
+ rte ? "rte" : " ", roe ? "roe" : " ");
+
+ /*
+ * Transmitter interrupt service
+ */
+ if (tse && tsr) {
+ /*
+ * TODO:
+ * Check the watermark threshold setting
+ * Pull FIFO_TX_DEPTH or FIFO_TX_DEPTH/2 entries from tx_kfifo
+ * Push the data to the hardware FIFO.
+ * If there was nothing more to send in the tx_kfifo, disable
+ * the TSR IRQ and notify the v4l2_device.
+ * If there was something in the tx_kfifo, check the tx_kfifo
+ * level and notify the v4l2_device, if it is low.
+ */
+ /* For now, inhibit TSR interrupt until Tx is implemented */
+ irqenable_tx(dev, 0);
+ events = V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ;
+ v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_TX_NOTIFY, &events);
+ *handled = true;
+ }
+
+ /*
+ * Receiver interrupt service
+ */
+ kror = 0;
+ if ((rse && rsr) || (rte && rto)) {
+ /*
+ * Receive data on RSR to clear the STATS_RSR.
+ * Receive data on RTO, since we may not have yet hit the RSR
+ * watermark when we receive the RTO.
+ */
+ for (i = 0, v = FIFO_RX_NDV;
+ (v & FIFO_RX_NDV) && !kror; i = 0) {
+ for (j = 0;
+ (v & FIFO_RX_NDV) && j < FIFO_RX_DEPTH; j++) {
+ v = cx23888_ir_read4(dev, CX23888_IR_FIFO_REG);
+ rx_data[i++] = v & ~FIFO_RX_NDV;
+ }
+ if (i == 0)
+ break;
+ j = i * sizeof(u32);
+ k = kfifo_put(state->rx_kfifo,
+ (unsigned char *) rx_data, j);
+ if (k != j)
+ kror++; /* rx_kfifo over run */
+ }
+ *handled = true;
+ }
+
+ events = 0;
+ v = 0;
+ if (kror) {
+ events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN;
+ v4l2_err(sd, "IR receiver software FIFO overrun\n");
+ }
+ if (roe && ror) {
+ /*
+ * The RX FIFO Enable (CNTRL_RFE) must be toggled to clear
+ * the Rx FIFO Over Run status (STATS_ROR)
+ */
+ v |= CNTRL_RFE;
+ events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN;
+ v4l2_err(sd, "IR receiver hardware FIFO overrun\n");
+ }
+ if (rte && rto) {
+ /*
+ * The IR Receiver Enable (CNTRL_RXE) must be toggled to clear
+ * the Rx Pulse Width Timer Time Out (STATS_RTO)
+ */
+ v |= CNTRL_RXE;
+ events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED;
+ }
+ if (v) {
+ /* Clear STATS_ROR & STATS_RTO as needed by reseting hardware */
+ cx23888_ir_write4(dev, CX23888_IR_CNTRL_REG, cntrl & ~v);
+ cx23888_ir_write4(dev, CX23888_IR_CNTRL_REG, cntrl);
+ *handled = true;
+ }
+ if (kfifo_len(state->rx_kfifo) >= CX23888_IR_RX_KFIFO_SIZE / 2)
+ events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ;
+
+ if (events)
+ v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_RX_NOTIFY, &events);
+ return 0;
+}
+
+/* Receiver */
+static int cx23888_ir_rx_read(struct v4l2_subdev *sd, u8 *buf, size_t count,
+ ssize_t *num)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ bool invert = (bool) atomic_read(&state->rx_invert);
+ u16 divider = (u16) atomic_read(&state->rxclk_divider);
+
+ unsigned int i, n;
+ u32 *p;
+ u32 u, v;
+
+ n = count / sizeof(u32) * sizeof(u32);
+ if (n == 0) {
+ *num = 0;
+ return 0;
+ }
+
+ n = kfifo_get(state->rx_kfifo, buf, n);
+
+ n /= sizeof(u32);
+ *num = n * sizeof(u32);
+
+ for (p = (u32 *) buf, i = 0; i < n; p++, i++) {
+ if ((*p & FIFO_RXTX_RTO) == FIFO_RXTX_RTO) {
+ *p = V4L2_SUBDEV_IR_PULSE_RX_SEQ_END;
+ v4l2_dbg(2, ir_888_debug, sd, "rx read: end of rx\n");
+ continue;
+ }
+
+ u = (*p & FIFO_RXTX_LVL) ? V4L2_SUBDEV_IR_PULSE_LEVEL_MASK : 0;
+ if (invert)
+ u = u ? 0 : V4L2_SUBDEV_IR_PULSE_LEVEL_MASK;
+
+ v = (u32) pulse_width_count_to_ns((u16) (*p & FIFO_RXTX),
+ divider);
+ if (v >= V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS)
+ v = V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS - 1;
+
+ *p = u | v;
+
+ v4l2_dbg(2, ir_888_debug, sd, "rx read: %10u ns %s\n",
+ v, u ? "mark" : "space");
+ }
+ return 0;
+}
+
+static int cx23888_ir_rx_g_parameters(struct v4l2_subdev *sd,
+ struct v4l2_subdev_ir_parameters *p)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ mutex_lock(&state->rx_params_lock);
+ memcpy(p, &state->rx_params, sizeof(struct v4l2_subdev_ir_parameters));
+ mutex_unlock(&state->rx_params_lock);
+ return 0;
+}
+
+static int cx23888_ir_rx_shutdown(struct v4l2_subdev *sd)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+
+ mutex_lock(&state->rx_params_lock);
+
+ /* Disable or slow down all IR Rx circuits and counters */
+ irqenable_rx(dev, 0);
+ control_rx_enable(dev, false);
+ control_rx_demodulation_enable(dev, false);
+ control_rx_s_edge_detection(dev, CNTRL_EDG_NONE);
+ filter_rx_s_min_width(dev, 0);
+ cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, RXCLK_RCD);
+
+ state->rx_params.shutdown = true;
+
+ mutex_unlock(&state->rx_params_lock);
+ return 0;
+}
+
+static int cx23888_ir_rx_s_parameters(struct v4l2_subdev *sd,
+ struct v4l2_subdev_ir_parameters *p)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+ struct v4l2_subdev_ir_parameters *o = &state->rx_params;
+ u16 rxclk_divider;
+
+ if (p->shutdown)
+ return cx23888_ir_rx_shutdown(sd);
+
+ if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH)
+ return -ENOSYS;
+
+ mutex_lock(&state->rx_params_lock);
+
+ o->shutdown = p->shutdown;
+
+ o->mode = p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
+
+ o->bytes_per_data_element = p->bytes_per_data_element = sizeof(u32);
+
+ /* Before we tweak the hardware, we have to disable the receiver */
+ irqenable_rx(dev, 0);
+ control_rx_enable(dev, false);
+
+ control_rx_demodulation_enable(dev, p->modulation);
+ o->modulation = p->modulation;
+
+ if (p->modulation) {
+ p->carrier_freq = rxclk_rx_s_carrier(dev, p->carrier_freq,
+ &rxclk_divider);
+
+ o->carrier_freq = p->carrier_freq;
+
+ o->duty_cycle = p->duty_cycle = 50;
+
+ control_rx_s_carrier_window(dev, p->carrier_freq,
+ &p->carrier_range_lower,
+ &p->carrier_range_upper);
+ o->carrier_range_lower = p->carrier_range_lower;
+ o->carrier_range_upper = p->carrier_range_upper;
+ } else {
+ p->max_pulse_width =
+ rxclk_rx_s_max_pulse_width(dev, p->max_pulse_width,
+ &rxclk_divider);
+ o->max_pulse_width = p->max_pulse_width;
+ }
+ atomic_set(&state->rxclk_divider, rxclk_divider);
+
+ p->noise_filter_min_width =
+ filter_rx_s_min_width(dev, p->noise_filter_min_width);
+ o->noise_filter_min_width = p->noise_filter_min_width;
+
+ p->resolution = clock_divider_to_resolution(rxclk_divider);
+ o->resolution = p->resolution;
+
+ /* FIXME - make this dependent on resolution for better performance */
+ control_rx_irq_watermark(dev, RX_FIFO_HALF_FULL);
+
+ control_rx_s_edge_detection(dev, CNTRL_EDG_BOTH);
+
+ o->invert = p->invert;
+ atomic_set(&state->rx_invert, p->invert);
+
+ o->interrupt_enable = p->interrupt_enable;
+ o->enable = p->enable;
+ if (p->enable) {
+ kfifo_reset(state->rx_kfifo);
+ if (p->interrupt_enable)
+ irqenable_rx(dev, IRQEN_RSE | IRQEN_RTE | IRQEN_ROE);
+ control_rx_enable(dev, p->enable);
+ }
+
+ mutex_unlock(&state->rx_params_lock);
+ return 0;
+}
+
+/* Transmitter */
+static int cx23888_ir_tx_write(struct v4l2_subdev *sd, u8 *buf, size_t count,
+ ssize_t *num)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+ /* For now enable the Tx FIFO Service interrupt & pretend we did work */
+ irqenable_tx(dev, IRQEN_TSE);
+ *num = count;
+ return 0;
+}
+
+static int cx23888_ir_tx_g_parameters(struct v4l2_subdev *sd,
+ struct v4l2_subdev_ir_parameters *p)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ mutex_lock(&state->tx_params_lock);
+ memcpy(p, &state->tx_params, sizeof(struct v4l2_subdev_ir_parameters));
+ mutex_unlock(&state->tx_params_lock);
+ return 0;
+}
+
+static int cx23888_ir_tx_shutdown(struct v4l2_subdev *sd)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+
+ mutex_lock(&state->tx_params_lock);
+
+ /* Disable or slow down all IR Tx circuits and counters */
+ irqenable_tx(dev, 0);
+ control_tx_enable(dev, false);
+ control_tx_modulation_enable(dev, false);
+ cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, TXCLK_TCD);
+
+ state->tx_params.shutdown = true;
+
+ mutex_unlock(&state->tx_params_lock);
+ return 0;
+}
+
+static int cx23888_ir_tx_s_parameters(struct v4l2_subdev *sd,
+ struct v4l2_subdev_ir_parameters *p)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+ struct v4l2_subdev_ir_parameters *o = &state->tx_params;
+ u16 txclk_divider;
+
+ if (p->shutdown)
+ return cx23888_ir_tx_shutdown(sd);
+
+ if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH)
+ return -ENOSYS;
+
+ mutex_lock(&state->tx_params_lock);
+
+ o->shutdown = p->shutdown;
+
+ o->mode = p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
+
+ o->bytes_per_data_element = p->bytes_per_data_element = sizeof(u32);
+
+ /* Before we tweak the hardware, we have to disable the transmitter */
+ irqenable_tx(dev, 0);
+ control_tx_enable(dev, false);
+
+ control_tx_modulation_enable(dev, p->modulation);
+ o->modulation = p->modulation;
+
+ if (p->modulation) {
+ p->carrier_freq = txclk_tx_s_carrier(dev, p->carrier_freq,
+ &txclk_divider);
+ o->carrier_freq = p->carrier_freq;
+
+ p->duty_cycle = cduty_tx_s_duty_cycle(dev, p->duty_cycle);
+ o->duty_cycle = p->duty_cycle;
+ } else {
+ p->max_pulse_width =
+ txclk_tx_s_max_pulse_width(dev, p->max_pulse_width,
+ &txclk_divider);
+ o->max_pulse_width = p->max_pulse_width;
+ }
+ atomic_set(&state->txclk_divider, txclk_divider);
+
+ p->resolution = clock_divider_to_resolution(txclk_divider);
+ o->resolution = p->resolution;
+
+ /* FIXME - make this dependent on resolution for better performance */
+ control_tx_irq_watermark(dev, TX_FIFO_HALF_EMPTY);
+
+ control_tx_polarity_invert(dev, p->invert);
+ o->invert = p->invert;
+
+ o->interrupt_enable = p->interrupt_enable;
+ o->enable = p->enable;
+ if (p->enable) {
+ kfifo_reset(state->tx_kfifo);
+ if (p->interrupt_enable)
+ irqenable_tx(dev, IRQEN_TSE);
+ control_tx_enable(dev, p->enable);
+ }
+
+ mutex_unlock(&state->tx_params_lock);
+ return 0;
+}
+
+
+/*
+ * V4L2 Subdevice Core Ops
+ */
+static int cx23888_ir_log_status(struct v4l2_subdev *sd)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ struct cx23885_dev *dev = state->dev;
+ char *s;
+ int i, j;
+
+ u32 cntrl = cx23888_ir_read4(dev, CX23888_IR_CNTRL_REG);
+ u32 txclk = cx23888_ir_read4(dev, CX23888_IR_TXCLK_REG) & TXCLK_TCD;
+ u32 rxclk = cx23888_ir_read4(dev, CX23888_IR_RXCLK_REG) & RXCLK_RCD;
+ u32 cduty = cx23888_ir_read4(dev, CX23888_IR_CDUTY_REG) & CDUTY_CDC;
+ u32 stats = cx23888_ir_read4(dev, CX23888_IR_STATS_REG);
+ u32 irqen = cx23888_ir_read4(dev, CX23888_IR_IRQEN_REG);
+ u32 filtr = cx23888_ir_read4(dev, CX23888_IR_FILTR_REG) & FILTR_LPF;
+
+ v4l2_info(sd, "IR Receiver:\n");
+ v4l2_info(sd, "\tEnabled: %s\n",
+ cntrl & CNTRL_RXE ? "yes" : "no");
+ v4l2_info(sd, "\tDemodulation from a carrier: %s\n",
+ cntrl & CNTRL_DMD ? "enabled" : "disabled");
+ v4l2_info(sd, "\tFIFO: %s\n",
+ cntrl & CNTRL_RFE ? "enabled" : "disabled");
+ switch (cntrl & CNTRL_EDG) {
+ case CNTRL_EDG_NONE:
+ s = "disabled";
+ break;
+ case CNTRL_EDG_FALL:
+ s = "falling edge";
+ break;
+ case CNTRL_EDG_RISE:
+ s = "rising edge";
+ break;
+ case CNTRL_EDG_BOTH:
+ s = "rising & falling edges";
+ break;
+ default:
+ s = "??? edge";
+ break;
+ }
+ v4l2_info(sd, "\tPulse timers' start/stop trigger: %s\n", s);
+ v4l2_info(sd, "\tFIFO data on pulse timer overflow: %s\n",
+ cntrl & CNTRL_R ? "not loaded" : "overflow marker");
+ v4l2_info(sd, "\tFIFO interrupt watermark: %s\n",
+ cntrl & CNTRL_RIC ? "not empty" : "half full or greater");
+ v4l2_info(sd, "\tLoopback mode: %s\n",
+ cntrl & CNTRL_LBM ? "loopback active" : "normal receive");
+ if (cntrl & CNTRL_DMD) {
+ v4l2_info(sd, "\tExpected carrier (16 clocks): %u Hz\n",
+ clock_divider_to_carrier_freq(rxclk));
+ switch (cntrl & CNTRL_WIN) {
+ case CNTRL_WIN_3_3:
+ i = 3;
+ j = 3;
+ break;
+ case CNTRL_WIN_4_3:
+ i = 4;
+ j = 3;
+ break;
+ case CNTRL_WIN_3_4:
+ i = 3;
+ j = 4;
+ break;
+ case CNTRL_WIN_4_4:
+ i = 4;
+ j = 4;
+ break;
+ default:
+ i = 0;
+ j = 0;
+ break;
+ }
+ v4l2_info(sd, "\tNext carrier edge window: 16 clocks "
+ "-%1d/+%1d, %u to %u Hz\n", i, j,
+ clock_divider_to_freq(rxclk, 16 + j),
+ clock_divider_to_freq(rxclk, 16 - i));
+ } else {
+ v4l2_info(sd, "\tMax measurable pulse width: %u us, "
+ "%llu ns\n",
+ pulse_width_count_to_us(FIFO_RXTX, rxclk),
+ pulse_width_count_to_ns(FIFO_RXTX, rxclk));
+ }
+ v4l2_info(sd, "\tLow pass filter: %s\n",
+ filtr ? "enabled" : "disabled");
+ if (filtr)
+ v4l2_info(sd, "\tMin acceptable pulse width (LPF): %u us, "
+ "%u ns\n",
+ lpf_count_to_us(filtr),
+ lpf_count_to_ns(filtr));
+ v4l2_info(sd, "\tPulse width timer timed-out: %s\n",
+ stats & STATS_RTO ? "yes" : "no");
+ v4l2_info(sd, "\tPulse width timer time-out intr: %s\n",
+ irqen & IRQEN_RTE ? "enabled" : "disabled");
+ v4l2_info(sd, "\tFIFO overrun: %s\n",
+ stats & STATS_ROR ? "yes" : "no");
+ v4l2_info(sd, "\tFIFO overrun interrupt: %s\n",
+ irqen & IRQEN_ROE ? "enabled" : "disabled");
+ v4l2_info(sd, "\tBusy: %s\n",
+ stats & STATS_RBY ? "yes" : "no");
+ v4l2_info(sd, "\tFIFO service requested: %s\n",
+ stats & STATS_RSR ? "yes" : "no");
+ v4l2_info(sd, "\tFIFO service request interrupt: %s\n",
+ irqen & IRQEN_RSE ? "enabled" : "disabled");
+
+ v4l2_info(sd, "IR Transmitter:\n");
+ v4l2_info(sd, "\tEnabled: %s\n",
+ cntrl & CNTRL_TXE ? "yes" : "no");
+ v4l2_info(sd, "\tModulation onto a carrier: %s\n",
+ cntrl & CNTRL_MOD ? "enabled" : "disabled");
+ v4l2_info(sd, "\tFIFO: %s\n",
+ cntrl & CNTRL_TFE ? "enabled" : "disabled");
+ v4l2_info(sd, "\tFIFO interrupt watermark: %s\n",
+ cntrl & CNTRL_TIC ? "not empty" : "half full or less");
+ v4l2_info(sd, "\tSignal polarity: %s\n",
+ cntrl & CNTRL_CPL ? "0:mark 1:space" : "0:space 1:mark");
+ if (cntrl & CNTRL_MOD) {
+ v4l2_info(sd, "\tCarrier (16 clocks): %u Hz\n",
+ clock_divider_to_carrier_freq(txclk));
+ v4l2_info(sd, "\tCarrier duty cycle: %2u/16\n",
+ cduty + 1);
+ } else {
+ v4l2_info(sd, "\tMax pulse width: %u us, "
+ "%llu ns\n",
+ pulse_width_count_to_us(FIFO_RXTX, txclk),
+ pulse_width_count_to_ns(FIFO_RXTX, txclk));
+ }
+ v4l2_info(sd, "\tBusy: %s\n",
+ stats & STATS_TBY ? "yes" : "no");
+ v4l2_info(sd, "\tFIFO service requested: %s\n",
+ stats & STATS_TSR ? "yes" : "no");
+ v4l2_info(sd, "\tFIFO service request interrupt: %s\n",
+ irqen & IRQEN_TSE ? "enabled" : "disabled");
+
+ return 0;
+}
+
+static inline int cx23888_ir_dbg_match(const struct v4l2_dbg_match *match)
+{
+ return match->type == V4L2_CHIP_MATCH_HOST && match->addr == 2;
+}
+
+static int cx23888_ir_g_chip_ident(struct v4l2_subdev *sd,
+ struct v4l2_dbg_chip_ident *chip)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+
+ if (cx23888_ir_dbg_match(&chip->match)) {
+ chip->ident = state->id;
+ chip->revision = state->rev;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int cx23888_ir_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ u32 addr = CX23888_IR_REG_BASE + (u32) reg->reg;
+
+ if (!cx23888_ir_dbg_match(&reg->match))
+ return -EINVAL;
+ if ((addr & 0x3) != 0)
+ return -EINVAL;
+ if (addr < CX23888_IR_CNTRL_REG || addr > CX23888_IR_LEARN_REG)
+ return -EINVAL;
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ reg->size = 4;
+ reg->val = cx23888_ir_read4(state->dev, addr);
+ return 0;
+}
+
+static int cx23888_ir_s_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct cx23888_ir_state *state = to_state(sd);
+ u32 addr = CX23888_IR_REG_BASE + (u32) reg->reg;
+
+ if (!cx23888_ir_dbg_match(&reg->match))
+ return -EINVAL;
+ if ((addr & 0x3) != 0)
+ return -EINVAL;
+ if (addr < CX23888_IR_CNTRL_REG || addr > CX23888_IR_LEARN_REG)
+ return -EINVAL;
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ cx23888_ir_write4(state->dev, addr, reg->val);
+ return 0;
+}
+#endif
+
+static const struct v4l2_subdev_core_ops cx23888_ir_core_ops = {
+ .g_chip_ident = cx23888_ir_g_chip_ident,
+ .log_status = cx23888_ir_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = cx23888_ir_g_register,
+ .s_register = cx23888_ir_s_register,
+#endif
+};
+
+static const struct v4l2_subdev_ir_ops cx23888_ir_ir_ops = {
+ .interrupt_service_routine = cx23888_ir_irq_handler,
+
+ .rx_read = cx23888_ir_rx_read,
+ .rx_g_parameters = cx23888_ir_rx_g_parameters,
+ .rx_s_parameters = cx23888_ir_rx_s_parameters,
+
+ .tx_write = cx23888_ir_tx_write,
+ .tx_g_parameters = cx23888_ir_tx_g_parameters,
+ .tx_s_parameters = cx23888_ir_tx_s_parameters,
+};
+
+static const struct v4l2_subdev_ops cx23888_ir_controller_ops = {
+ .core = &cx23888_ir_core_ops,
+ .ir = &cx23888_ir_ir_ops,
+};
+
+static const struct v4l2_subdev_ir_parameters default_rx_params = {
+ .bytes_per_data_element = sizeof(u32),
+ .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH,
+
+ .enable = false,
+ .interrupt_enable = false,
+ .shutdown = true,
+
+ .modulation = true,
+ .carrier_freq = 36000, /* 36 kHz - RC-5, RC-6, and RC-6A carrier */
+
+ /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */
+ /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */
+ .noise_filter_min_width = 333333, /* ns */
+ .carrier_range_lower = 35000,
+ .carrier_range_upper = 37000,
+ .invert = false,
+};
+
+static const struct v4l2_subdev_ir_parameters default_tx_params = {
+ .bytes_per_data_element = sizeof(u32),
+ .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH,
+
+ .enable = false,
+ .interrupt_enable = false,
+ .shutdown = true,
+
+ .modulation = true,
+ .carrier_freq = 36000, /* 36 kHz - RC-5 carrier */
+ .duty_cycle = 25, /* 25 % - RC-5 carrier */
+ .invert = false,
+};
+
+int cx23888_ir_probe(struct cx23885_dev *dev)
+{
+ struct cx23888_ir_state *state;
+ struct v4l2_subdev *sd;
+ struct v4l2_subdev_ir_parameters default_params;
+ int ret;
+
+ state = kzalloc(sizeof(struct cx23888_ir_state), GFP_KERNEL);
+ if (state == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&state->rx_kfifo_lock);
+ state->rx_kfifo = kfifo_alloc(CX23888_IR_RX_KFIFO_SIZE, GFP_KERNEL,
+ &state->rx_kfifo_lock);
+ if (state->rx_kfifo == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&state->tx_kfifo_lock);
+ state->tx_kfifo = kfifo_alloc(CX23888_IR_TX_KFIFO_SIZE, GFP_KERNEL,
+ &state->tx_kfifo_lock);
+ if (state->tx_kfifo == NULL) {
+ kfifo_free(state->rx_kfifo);
+ return -ENOMEM;
+ }
+
+ state->dev = dev;
+ state->id = V4L2_IDENT_CX23888_IR;
+ state->rev = 0;
+ sd = &state->sd;
+
+ v4l2_subdev_init(sd, &cx23888_ir_controller_ops);
+ v4l2_set_subdevdata(sd, state);
+ /* FIXME - fix the formatting of dev->v4l2_dev.name and use it */
+ snprintf(sd->name, sizeof(sd->name), "%s/888-ir", dev->name);
+ sd->grp_id = CX23885_HW_888_IR;
+
+ ret = v4l2_device_register_subdev(&dev->v4l2_dev, sd);
+ if (ret == 0) {
+ /*
+ * Ensure no interrupts arrive from '888 specific conditions,
+ * since we ignore them in this driver to have commonality with
+ * similar IR controller cores.
+ */
+ cx23888_ir_write4(dev, CX23888_IR_IRQEN_REG, 0);
+
+ mutex_init(&state->rx_params_lock);
+ memcpy(&default_params, &default_rx_params,
+ sizeof(struct v4l2_subdev_ir_parameters));
+ v4l2_subdev_call(sd, ir, rx_s_parameters, &default_params);
+
+ mutex_init(&state->tx_params_lock);
+ memcpy(&default_params, &default_tx_params,
+ sizeof(struct v4l2_subdev_ir_parameters));
+ v4l2_subdev_call(sd, ir, tx_s_parameters, &default_params);
+ } else {
+ kfifo_free(state->rx_kfifo);
+ kfifo_free(state->tx_kfifo);
+ }
+ return ret;
+}
+
+int cx23888_ir_remove(struct cx23885_dev *dev)
+{
+ struct v4l2_subdev *sd;
+ struct cx23888_ir_state *state;
+
+ sd = cx23885_find_hw(dev, CX23885_HW_888_IR);
+ if (sd == NULL)
+ return -ENODEV;
+
+ cx23888_ir_rx_shutdown(sd);
+ cx23888_ir_tx_shutdown(sd);
+
+ state = to_state(sd);
+ v4l2_device_unregister_subdev(sd);
+ kfifo_free(state->rx_kfifo);
+ kfifo_free(state->tx_kfifo);
+ kfree(state);
+ /* Nothing more to free() as state held the actual v4l2_subdev object */
+ return 0;
+}
diff --git a/drivers/media/video/cx23885/cx23888-ir.h b/drivers/media/video/cx23885/cx23888-ir.h
new file mode 100644
index 000000000000..3d446f9eb94b
--- /dev/null
+++ b/drivers/media/video/cx23885/cx23888-ir.h
@@ -0,0 +1,28 @@
+/*
+ * Driver for the Conexant CX23885/7/8 PCIe bridge
+ *
+ * CX23888 Integrated Consumer Infrared Controller
+ *
+ * Copyright (C) 2009 Andy Walls <awalls@radix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef _CX23888_IR_H_
+#define _CX23888_IR_H_
+int cx23888_ir_probe(struct cx23885_dev *dev);
+int cx23888_ir_remove(struct cx23885_dev *dev);
+#endif