forked from Minki/linux
c003ab1bed
The RC_TYPE_* defines are currently used both where a single protocol is expected and where a bitmap of protocols is expected. Functions like rc_keydown() and functions which add/remove entries to the keytable want a single protocol. Future userspace APIs would also benefit from numeric protocols (rather than bitmap ones). Keytables are smaller if they can use a small(ish) integer rather than a bitmap. Other functions or struct members (e.g. allowed_protos, enabled_protocols, etc) accept multiple protocols and need a bitmap. Using different types reduces the risk of programmer error. Using a protocol enum whereever possible also makes for a more future-proof user-space API as we don't need to worry about a sufficient number of bits being available (e.g. in structs used for ioctl() calls). The use of both a number and a corresponding bit is dalso one in e.g. the input subsystem as well (see all the references to set/clear bit when changing keytables for example). This patch separate the different usages in preparation for upcoming patches. Where a single protocol is expected, enum rc_type is used; where one or more protocol(s) are expected, something like u64 is used. The patch has been rewritten so that the format of the sysfs "protocols" file is no longer altered (at the loss of some detail). The file itself should probably be deprecated in the future though. Signed-off-by: David Härdeman <david@hardeman.nu> Cc: Andy Walls <awalls@md.metrocast.net> Cc: Maxim Levitsky <maximlevitsky@gmail.com> Cc: Antti Palosaari <crope@iki.fi> Cc: Mike Isely <isely@pobox.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.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->allowed_protos = 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;
|
|
}
|