mirror of
https://github.com/torvalds/linux.git
synced 2024-12-25 12:21:37 +00:00
1a1934fab0
The allowed and enabled protocol masks need to be expanded to be per filter type in order to support wakeup filter protocol selection. To ease that process abstract access to the rc_dev::allowed_protos and rc_dev::enabled_protocols members with inline functions. Signed-off-by: James Hogan <james.hogan@imgtec.com> Reviewed-by: Antti Seppälä <a.seppala@gmail.com> Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
502 lines
12 KiB
C
502 lines
12 KiB
C
/*
|
|
* tm6000-input.c - driver for TM5600/TM6000/TM6010 USB video capture devices
|
|
*
|
|
* Copyright (C) 2010 Stefan Ringel <stefan.ringel@arcor.de>
|
|
*
|
|
* 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 version 2
|
|
*
|
|
* 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 <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/input.h>
|
|
#include <linux/usb.h>
|
|
|
|
#include <media/rc-core.h>
|
|
|
|
#include "tm6000.h"
|
|
#include "tm6000-regs.h"
|
|
|
|
static unsigned int ir_debug;
|
|
module_param(ir_debug, int, 0644);
|
|
MODULE_PARM_DESC(ir_debug, "debug message level");
|
|
|
|
static unsigned int enable_ir = 1;
|
|
module_param(enable_ir, int, 0644);
|
|
MODULE_PARM_DESC(enable_ir, "enable ir (default is enable)");
|
|
|
|
static unsigned int ir_clock_mhz = 12;
|
|
module_param(ir_clock_mhz, int, 0644);
|
|
MODULE_PARM_DESC(enable_ir, "ir clock, in MHz");
|
|
|
|
#define URB_SUBMIT_DELAY 100 /* ms - Delay to submit an URB request on retrial and init */
|
|
#define URB_INT_LED_DELAY 100 /* ms - Delay to turn led on again on int mode */
|
|
|
|
#undef dprintk
|
|
|
|
#define dprintk(level, fmt, arg...) do {\
|
|
if (ir_debug >= level) \
|
|
printk(KERN_DEBUG "%s/ir: " fmt, ir->name , ## arg); \
|
|
} while (0)
|
|
|
|
struct tm6000_ir_poll_result {
|
|
u16 rc_data;
|
|
};
|
|
|
|
struct tm6000_IR {
|
|
struct tm6000_core *dev;
|
|
struct rc_dev *rc;
|
|
char name[32];
|
|
char phys[32];
|
|
|
|
/* poll expernal decoder */
|
|
int polling;
|
|
struct delayed_work work;
|
|
u8 wait:1;
|
|
u8 pwled:2;
|
|
u8 submit_urb:1;
|
|
u16 key_addr;
|
|
struct urb *int_urb;
|
|
|
|
/* IR device properties */
|
|
u64 rc_type;
|
|
};
|
|
|
|
void tm6000_ir_wait(struct tm6000_core *dev, u8 state)
|
|
{
|
|
struct tm6000_IR *ir = dev->ir;
|
|
|
|
if (!dev->ir)
|
|
return;
|
|
|
|
dprintk(2, "%s: %i\n",__func__, ir->wait);
|
|
|
|
if (state)
|
|
ir->wait = 1;
|
|
else
|
|
ir->wait = 0;
|
|
}
|
|
|
|
static int tm6000_ir_config(struct tm6000_IR *ir)
|
|
{
|
|
struct tm6000_core *dev = ir->dev;
|
|
u32 pulse = 0, leader = 0;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
/*
|
|
* The IR decoder supports RC-5 or NEC, with a configurable timing.
|
|
* The timing configuration there is not that accurate, as it uses
|
|
* approximate values. The NEC spec mentions a 562.5 unit period,
|
|
* and RC-5 uses a 888.8 period.
|
|
* Currently, driver assumes a clock provided by a 12 MHz XTAL, but
|
|
* a modprobe parameter can adjust it.
|
|
* Adjustments are required for other timings.
|
|
* It seems that the 900ms timing for NEC is used to detect a RC-5
|
|
* IR, in order to discard such decoding
|
|
*/
|
|
|
|
switch (ir->rc_type) {
|
|
case RC_BIT_NEC:
|
|
leader = 900; /* ms */
|
|
pulse = 700; /* ms - the actual value would be 562 */
|
|
break;
|
|
default:
|
|
case RC_BIT_RC5:
|
|
leader = 900; /* ms - from the NEC decoding */
|
|
pulse = 1780; /* ms - The actual value would be 1776 */
|
|
break;
|
|
}
|
|
|
|
pulse = ir_clock_mhz * pulse;
|
|
leader = ir_clock_mhz * leader;
|
|
if (ir->rc_type == RC_BIT_NEC)
|
|
leader = leader | 0x8000;
|
|
|
|
dprintk(2, "%s: %s, %d MHz, leader = 0x%04x, pulse = 0x%06x \n",
|
|
__func__,
|
|
(ir->rc_type == RC_BIT_NEC) ? "NEC" : "RC-5",
|
|
ir_clock_mhz, leader, pulse);
|
|
|
|
/* Remote WAKEUP = enable, normal mode, from IR decoder output */
|
|
tm6000_set_reg(dev, TM6010_REQ07_RE5_REMOTE_WAKEUP, 0xfe);
|
|
|
|
/* Enable IR reception on non-busrt mode */
|
|
tm6000_set_reg(dev, TM6010_REQ07_RD8_IR, 0x2f);
|
|
|
|
/* IR_WKUP_SEL = Low byte in decoded IR data */
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDA_IR_WAKEUP_SEL, 0xff);
|
|
/* IR_WKU_ADD code */
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDB_IR_WAKEUP_ADD, 0xff);
|
|
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDC_IR_LEADER1, leader >> 8);
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDD_IR_LEADER0, leader);
|
|
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDE_IR_PULSE_CNT1, pulse >> 8);
|
|
tm6000_set_reg(dev, TM6010_REQ07_RDF_IR_PULSE_CNT0, pulse);
|
|
|
|
if (!ir->polling)
|
|
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
|
|
else
|
|
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 1);
|
|
msleep(10);
|
|
|
|
/* Shows that IR is working via the LED */
|
|
tm6000_flash_led(dev, 0);
|
|
msleep(100);
|
|
tm6000_flash_led(dev, 1);
|
|
ir->pwled = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tm6000_ir_urb_received(struct urb *urb)
|
|
{
|
|
struct tm6000_core *dev = urb->context;
|
|
struct tm6000_IR *ir = dev->ir;
|
|
struct tm6000_ir_poll_result poll_result;
|
|
char *buf;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
if (urb->status < 0 || urb->actual_length <= 0) {
|
|
printk(KERN_INFO "tm6000: IR URB failure: status: %i, length %i\n",
|
|
urb->status, urb->actual_length);
|
|
ir->submit_urb = 1;
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
|
|
return;
|
|
}
|
|
buf = urb->transfer_buffer;
|
|
|
|
if (ir_debug)
|
|
print_hex_dump(KERN_DEBUG, "tm6000: IR data: ",
|
|
DUMP_PREFIX_OFFSET,16, 1,
|
|
buf, urb->actual_length, false);
|
|
|
|
poll_result.rc_data = buf[0];
|
|
if (urb->actual_length > 1)
|
|
poll_result.rc_data |= buf[1] << 8;
|
|
|
|
dprintk(1, "%s, scancode: 0x%04x\n",__func__, poll_result.rc_data);
|
|
rc_keydown(ir->rc, poll_result.rc_data, 0);
|
|
|
|
usb_submit_urb(urb, GFP_ATOMIC);
|
|
/*
|
|
* Flash the led. We can't do it here, as it is running on IRQ context.
|
|
* So, use the scheduler to do it, in a few ms.
|
|
*/
|
|
ir->pwled = 2;
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(10));
|
|
}
|
|
|
|
static void tm6000_ir_handle_key(struct work_struct *work)
|
|
{
|
|
struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
|
|
struct tm6000_core *dev = ir->dev;
|
|
struct tm6000_ir_poll_result poll_result;
|
|
int rc;
|
|
u8 buf[2];
|
|
|
|
if (ir->wait)
|
|
return;
|
|
|
|
dprintk(3, "%s\n",__func__);
|
|
|
|
rc = tm6000_read_write_usb(dev, USB_DIR_IN |
|
|
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
|
REQ_02_GET_IR_CODE, 0, 0, buf, 2);
|
|
if (rc < 0)
|
|
return;
|
|
|
|
if (rc > 1)
|
|
poll_result.rc_data = buf[0] | buf[1] << 8;
|
|
else
|
|
poll_result.rc_data = buf[0];
|
|
|
|
/* Check if something was read */
|
|
if ((poll_result.rc_data & 0xff) == 0xff) {
|
|
if (!ir->pwled) {
|
|
tm6000_flash_led(dev, 1);
|
|
ir->pwled = 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
dprintk(1, "%s, scancode: 0x%04x\n",__func__, poll_result.rc_data);
|
|
rc_keydown(ir->rc, poll_result.rc_data, 0);
|
|
tm6000_flash_led(dev, 0);
|
|
ir->pwled = 0;
|
|
|
|
/* Re-schedule polling */
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
|
|
}
|
|
|
|
static void tm6000_ir_int_work(struct work_struct *work)
|
|
{
|
|
struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
|
|
struct tm6000_core *dev = ir->dev;
|
|
int rc;
|
|
|
|
dprintk(3, "%s, submit_urb = %d, pwled = %d\n",__func__, ir->submit_urb,
|
|
ir->pwled);
|
|
|
|
if (ir->submit_urb) {
|
|
dprintk(3, "Resubmit urb\n");
|
|
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
|
|
|
|
rc = usb_submit_urb(ir->int_urb, GFP_ATOMIC);
|
|
if (rc < 0) {
|
|
printk(KERN_ERR "tm6000: Can't submit an IR interrupt. Error %i\n",
|
|
rc);
|
|
/* Retry in 100 ms */
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
|
|
return;
|
|
}
|
|
ir->submit_urb = 0;
|
|
}
|
|
|
|
/* Led is enabled only if USB submit doesn't fail */
|
|
if (ir->pwled == 2) {
|
|
tm6000_flash_led(dev, 0);
|
|
ir->pwled = 0;
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_INT_LED_DELAY));
|
|
} else if (!ir->pwled) {
|
|
tm6000_flash_led(dev, 1);
|
|
ir->pwled = 1;
|
|
}
|
|
}
|
|
|
|
static int tm6000_ir_start(struct rc_dev *rc)
|
|
{
|
|
struct tm6000_IR *ir = rc->priv;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
schedule_delayed_work(&ir->work, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tm6000_ir_stop(struct rc_dev *rc)
|
|
{
|
|
struct tm6000_IR *ir = rc->priv;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
cancel_delayed_work_sync(&ir->work);
|
|
}
|
|
|
|
static int tm6000_ir_change_protocol(struct rc_dev *rc, u64 *rc_type)
|
|
{
|
|
struct tm6000_IR *ir = rc->priv;
|
|
|
|
if (!ir)
|
|
return 0;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
if ((rc->rc_map.scan) && (*rc_type == RC_BIT_NEC))
|
|
ir->key_addr = ((rc->rc_map.scan[0].scancode >> 8) & 0xffff);
|
|
|
|
ir->rc_type = *rc_type;
|
|
|
|
tm6000_ir_config(ir);
|
|
/* TODO */
|
|
return 0;
|
|
}
|
|
|
|
static int __tm6000_ir_int_start(struct rc_dev *rc)
|
|
{
|
|
struct tm6000_IR *ir = rc->priv;
|
|
struct tm6000_core *dev;
|
|
int pipe, size;
|
|
int err = -ENOMEM;
|
|
|
|
if (!ir)
|
|
return -ENODEV;
|
|
dev = ir->dev;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
ir->int_urb = usb_alloc_urb(0, GFP_ATOMIC);
|
|
if (!ir->int_urb)
|
|
return -ENOMEM;
|
|
|
|
pipe = usb_rcvintpipe(dev->udev,
|
|
dev->int_in.endp->desc.bEndpointAddress
|
|
& USB_ENDPOINT_NUMBER_MASK);
|
|
|
|
size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe));
|
|
dprintk(1, "IR max size: %d\n", size);
|
|
|
|
ir->int_urb->transfer_buffer = kzalloc(size, GFP_ATOMIC);
|
|
if (ir->int_urb->transfer_buffer == NULL) {
|
|
usb_free_urb(ir->int_urb);
|
|
return err;
|
|
}
|
|
dprintk(1, "int interval: %d\n", dev->int_in.endp->desc.bInterval);
|
|
|
|
usb_fill_int_urb(ir->int_urb, dev->udev, pipe,
|
|
ir->int_urb->transfer_buffer, size,
|
|
tm6000_ir_urb_received, dev,
|
|
dev->int_in.endp->desc.bInterval);
|
|
|
|
ir->submit_urb = 1;
|
|
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __tm6000_ir_int_stop(struct rc_dev *rc)
|
|
{
|
|
struct tm6000_IR *ir = rc->priv;
|
|
|
|
if (!ir || !ir->int_urb)
|
|
return;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
usb_kill_urb(ir->int_urb);
|
|
kfree(ir->int_urb->transfer_buffer);
|
|
usb_free_urb(ir->int_urb);
|
|
ir->int_urb = NULL;
|
|
}
|
|
|
|
int tm6000_ir_int_start(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_IR *ir = dev->ir;
|
|
|
|
if (!ir)
|
|
return 0;
|
|
|
|
return __tm6000_ir_int_start(ir->rc);
|
|
}
|
|
|
|
void tm6000_ir_int_stop(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_IR *ir = dev->ir;
|
|
|
|
if (!ir || !ir->rc)
|
|
return;
|
|
|
|
__tm6000_ir_int_stop(ir->rc);
|
|
}
|
|
|
|
int tm6000_ir_init(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_IR *ir;
|
|
struct rc_dev *rc;
|
|
int err = -ENOMEM;
|
|
u64 rc_type;
|
|
|
|
if (!enable_ir)
|
|
return -ENODEV;
|
|
|
|
if (!dev->caps.has_remote)
|
|
return 0;
|
|
|
|
if (!dev->ir_codes)
|
|
return 0;
|
|
|
|
ir = kzalloc(sizeof(*ir), GFP_ATOMIC);
|
|
rc = rc_allocate_device();
|
|
if (!ir || !rc)
|
|
goto out;
|
|
|
|
dprintk(2, "%s\n", __func__);
|
|
|
|
/* record handles to ourself */
|
|
ir->dev = dev;
|
|
dev->ir = ir;
|
|
ir->rc = rc;
|
|
|
|
/* input setup */
|
|
rc_set_allowed_protocols(rc, RC_BIT_RC5 | RC_BIT_NEC);
|
|
/* Neded, in order to support NEC remotes with 24 or 32 bits */
|
|
rc->scanmask = 0xffff;
|
|
rc->priv = ir;
|
|
rc->change_protocol = tm6000_ir_change_protocol;
|
|
if (dev->int_in.endp) {
|
|
rc->open = __tm6000_ir_int_start;
|
|
rc->close = __tm6000_ir_int_stop;
|
|
INIT_DELAYED_WORK(&ir->work, tm6000_ir_int_work);
|
|
} else {
|
|
rc->open = tm6000_ir_start;
|
|
rc->close = tm6000_ir_stop;
|
|
ir->polling = 50;
|
|
INIT_DELAYED_WORK(&ir->work, tm6000_ir_handle_key);
|
|
}
|
|
rc->driver_type = RC_DRIVER_SCANCODE;
|
|
|
|
snprintf(ir->name, sizeof(ir->name), "tm5600/60x0 IR (%s)",
|
|
dev->name);
|
|
|
|
usb_make_path(dev->udev, ir->phys, sizeof(ir->phys));
|
|
strlcat(ir->phys, "/input0", sizeof(ir->phys));
|
|
|
|
rc_type = RC_BIT_UNKNOWN;
|
|
tm6000_ir_change_protocol(rc, &rc_type);
|
|
|
|
rc->input_name = ir->name;
|
|
rc->input_phys = ir->phys;
|
|
rc->input_id.bustype = BUS_USB;
|
|
rc->input_id.version = 1;
|
|
rc->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
|
|
rc->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct);
|
|
rc->map_name = dev->ir_codes;
|
|
rc->driver_name = "tm6000";
|
|
rc->dev.parent = &dev->udev->dev;
|
|
|
|
/* ir register */
|
|
err = rc_register_device(rc);
|
|
if (err)
|
|
goto out;
|
|
|
|
return 0;
|
|
|
|
out:
|
|
dev->ir = NULL;
|
|
rc_free_device(rc);
|
|
kfree(ir);
|
|
return err;
|
|
}
|
|
|
|
int tm6000_ir_fini(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_IR *ir = dev->ir;
|
|
|
|
/* skip detach on non attached board */
|
|
|
|
if (!ir)
|
|
return 0;
|
|
|
|
dprintk(2, "%s\n",__func__);
|
|
|
|
if (!ir->polling)
|
|
__tm6000_ir_int_stop(ir->rc);
|
|
|
|
tm6000_ir_stop(ir->rc);
|
|
|
|
/* Turn off the led */
|
|
tm6000_flash_led(dev, 0);
|
|
ir->pwled = 0;
|
|
|
|
rc_unregister_device(ir->rc);
|
|
|
|
kfree(ir);
|
|
dev->ir = NULL;
|
|
|
|
return 0;
|
|
}
|