/* * Driver for the Conexant CX23885/7/8 PCIe bridge * * Infrared remote control input device * * Most of this file is * * Copyright (C) 2009 Andy Walls * * 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 * Copyright (C) 2005 Ludovico Cavedon * Markus Rechberger * Mauro Carvalho Chehab * Sascha Sommer * 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 #include #include #include #include "cx23885.h" #define MODULE_NAME "cx23885" static void convert_measurement(u32 x, struct ir_raw_event *y) { if (x == V4L2_SUBDEV_IR_PULSE_RX_SEQ_END) { y->pulse = false; y->duration = V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS; return; } y->pulse = (x & V4L2_SUBDEV_IR_PULSE_LEVEL_MASK) ? true : false; y->duration = x & V4L2_SUBDEV_IR_PULSE_MAX_WIDTH_NS; } static void cx23885_input_process_measurements(struct cx23885_dev *dev, bool overrun) { struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir; struct ir_raw_event kernel_ir_event; u32 sd_ir_data[64]; ssize_t num; int count, i; bool handle = false; do { num = 0; v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) sd_ir_data, sizeof(sd_ir_data), &num); count = num / sizeof(u32); for (i = 0; i < count; i++) { convert_measurement(sd_ir_data[i], &kernel_ir_event); ir_raw_event_store(kernel_ir->inp_dev, &kernel_ir_event); handle = true; } } while (num != 0); if (overrun) ir_raw_event_reset(kernel_ir->inp_dev); else if (handle) ir_raw_event_handle(kernel_ir->inp_dev); } 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, ¶ms); 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, ¶ms); } if (data_available) cx23885_input_process_measurements(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, ¶ms); } } static int cx23885_input_ir_start(struct cx23885_dev *dev) { struct v4l2_subdev_ir_parameters params; if (dev->sd_ir == NULL) return -ENODEV; atomic_set(&dev->ir_input_stopping, 0); v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); 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_level = true; break; } v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); return 0; } static int cx23885_input_ir_open(void *priv) { struct cx23885_kernel_ir *kernel_ir = priv; if (kernel_ir->cx == NULL) return -ENODEV; return cx23885_input_ir_start(kernel_ir->cx); } static void cx23885_input_ir_stop(struct cx23885_dev *dev) { 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, ¶ms); while (params.shutdown == false) { params.enable = false; params.interrupt_enable = false; params.shutdown = true; v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); } flush_scheduled_work(); } static void cx23885_input_ir_close(void *priv) { struct cx23885_kernel_ir *kernel_ir = priv; if (kernel_ir->cx != NULL) cx23885_input_ir_stop(kernel_ir->cx); } int cx23885_input_init(struct cx23885_dev *dev) { struct cx23885_kernel_ir *kernel_ir; struct input_dev *inp_dev; struct ir_dev_props *props; char *rc_map; enum rc_driver_type driver_type; unsigned long allowed_protos; 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: /* Integrated CX23888 IR controller */ driver_type = RC_DRIVER_IR_RAW; allowed_protos = IR_TYPE_ALL; /* The grey Hauppauge RC-5 remote */ rc_map = RC_MAP_RC5_HAUPPAUGE_NEW; break; default: return -ENODEV; } /* cx23885 board instance kernel IR state */ kernel_ir = kzalloc(sizeof(struct cx23885_kernel_ir), GFP_KERNEL); if (kernel_ir == NULL) return -ENOMEM; kernel_ir->cx = dev; kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)", cx23885_boards[dev->board].name); kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0", pci_name(dev->pci)); /* input device */ inp_dev = input_allocate_device(); if (inp_dev == NULL) { ret = -ENOMEM; goto err_out_free; } kernel_ir->inp_dev = inp_dev; inp_dev->name = kernel_ir->name; inp_dev->phys = kernel_ir->phys; inp_dev->id.bustype = BUS_PCI; inp_dev->id.version = 1; if (dev->pci->subsystem_vendor) { inp_dev->id.vendor = dev->pci->subsystem_vendor; inp_dev->id.product = dev->pci->subsystem_device; } else { inp_dev->id.vendor = dev->pci->vendor; inp_dev->id.product = dev->pci->device; } inp_dev->dev.parent = &dev->pci->dev; /* kernel ir device properties */ props = &kernel_ir->props; props->driver_type = driver_type; props->allowed_protos = allowed_protos; props->priv = kernel_ir; props->open = cx23885_input_ir_open; props->close = cx23885_input_ir_close; /* Go */ dev->kernel_ir = kernel_ir; ret = ir_input_register(inp_dev, rc_map, props, MODULE_NAME); if (ret) goto err_out_stop; return 0; err_out_stop: cx23885_input_ir_stop(dev); dev->kernel_ir = NULL; /* TODO: double check clean-up of kernel_ir->inp_dev */ err_out_free: kfree(kernel_ir->phys); kfree(kernel_ir->name); kfree(kernel_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->kernel_ir == NULL) return; ir_input_unregister(dev->kernel_ir->inp_dev); kfree(dev->kernel_ir->phys); kfree(dev->kernel_ir->name); kfree(dev->kernel_ir); dev->kernel_ir = NULL; }