mirror of
https://github.com/edk2-porting/linux-next.git
synced 2025-01-27 08:05:27 +08:00
a0c7056fda
Based on 1 normalized pattern(s): 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 to obtain the license point your browser to http www gnu org copyleft gpl html extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 26 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190523091650.572604764@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
384 lines
9.8 KiB
C
384 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Driver for the remote control of SAA7146 based AV7110 cards
|
|
*
|
|
* Copyright (C) 1999-2003 Holger Waechtler <holger@convergence.de>
|
|
* Copyright (C) 2003-2007 Oliver Endriss <o.endriss@gmx.de>
|
|
*/
|
|
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include "av7110.h"
|
|
#include "av7110_hw.h"
|
|
|
|
|
|
#define AV_CNT 4
|
|
|
|
#define IR_RC5 0
|
|
#define IR_RCMM 1
|
|
#define IR_RC5_EXT 2 /* internal only */
|
|
|
|
#define IR_ALL 0xffffffff
|
|
|
|
#define UP_TIMEOUT (HZ*7/25)
|
|
|
|
|
|
/* Note: enable ir debugging by or'ing debug with 16 */
|
|
|
|
static int ir_protocol[AV_CNT] = { IR_RCMM, IR_RCMM, IR_RCMM, IR_RCMM};
|
|
module_param_array(ir_protocol, int, NULL, 0644);
|
|
MODULE_PARM_DESC(ir_protocol, "Infrared protocol: 0 RC5, 1 RCMM (default)");
|
|
|
|
static int ir_inversion[AV_CNT];
|
|
module_param_array(ir_inversion, int, NULL, 0644);
|
|
MODULE_PARM_DESC(ir_inversion, "Inversion of infrared signal: 0 not inverted (default), 1 inverted");
|
|
|
|
static uint ir_device_mask[AV_CNT] = { IR_ALL, IR_ALL, IR_ALL, IR_ALL };
|
|
module_param_array(ir_device_mask, uint, NULL, 0644);
|
|
MODULE_PARM_DESC(ir_device_mask, "Bitmask of infrared devices: bit 0..31 = device 0..31 (default: all)");
|
|
|
|
|
|
static int av_cnt;
|
|
static struct av7110 *av_list[AV_CNT];
|
|
|
|
static u16 default_key_map [256] = {
|
|
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7,
|
|
KEY_8, KEY_9, KEY_BACK, 0, KEY_POWER, KEY_MUTE, 0, KEY_INFO,
|
|
KEY_VOLUMEUP, KEY_VOLUMEDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
KEY_CHANNELUP, KEY_CHANNELDOWN, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, KEY_TEXT, 0, 0, KEY_TV, 0, 0, 0, 0, 0, KEY_SETUP, 0, 0,
|
|
0, 0, 0, KEY_SUBTITLE, 0, 0, KEY_LANGUAGE, 0,
|
|
KEY_RADIO, 0, 0, 0, 0, KEY_EXIT, 0, 0,
|
|
KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_OK, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RED, KEY_GREEN, KEY_YELLOW,
|
|
KEY_BLUE, 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_LIST, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, KEY_UP, KEY_UP, KEY_DOWN, KEY_DOWN,
|
|
0, 0, 0, 0, KEY_EPG, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_VCR
|
|
};
|
|
|
|
|
|
/* key-up timer */
|
|
static void av7110_emit_keyup(struct timer_list *t)
|
|
{
|
|
struct infrared *ir = from_timer(ir, t, keyup_timer);
|
|
|
|
if (!ir || !ir->keypressed)
|
|
return;
|
|
|
|
input_report_key(ir->input_dev, ir->last_key, 0);
|
|
input_sync(ir->input_dev);
|
|
ir->keypressed = false;
|
|
}
|
|
|
|
|
|
/* tasklet */
|
|
static void av7110_emit_key(unsigned long parm)
|
|
{
|
|
struct infrared *ir = (struct infrared *) parm;
|
|
u32 ircom = ir->ir_command;
|
|
u8 data;
|
|
u8 addr;
|
|
u16 toggle;
|
|
u16 keycode;
|
|
|
|
/* extract device address and data */
|
|
switch (ir->protocol) {
|
|
case IR_RC5: /* RC5: 5 bits device address, 6 bits data */
|
|
data = ircom & 0x3f;
|
|
addr = (ircom >> 6) & 0x1f;
|
|
toggle = ircom & 0x0800;
|
|
break;
|
|
|
|
case IR_RCMM: /* RCMM: ? bits device address, ? bits data */
|
|
data = ircom & 0xff;
|
|
addr = (ircom >> 8) & 0x1f;
|
|
toggle = ircom & 0x8000;
|
|
break;
|
|
|
|
case IR_RC5_EXT: /* extended RC5: 5 bits device address, 7 bits data */
|
|
data = ircom & 0x3f;
|
|
addr = (ircom >> 6) & 0x1f;
|
|
/* invert 7th data bit for backward compatibility with RC5 keymaps */
|
|
if (!(ircom & 0x1000))
|
|
data |= 0x40;
|
|
toggle = ircom & 0x0800;
|
|
break;
|
|
|
|
default:
|
|
printk("%s invalid protocol %x\n", __func__, ir->protocol);
|
|
return;
|
|
}
|
|
|
|
input_event(ir->input_dev, EV_MSC, MSC_RAW, (addr << 16) | data);
|
|
input_event(ir->input_dev, EV_MSC, MSC_SCAN, data);
|
|
|
|
keycode = ir->key_map[data];
|
|
|
|
dprintk(16, "%s: code %08x -> addr %i data 0x%02x -> keycode %i\n",
|
|
__func__, ircom, addr, data, keycode);
|
|
|
|
/* check device address */
|
|
if (!(ir->device_mask & (1 << addr)))
|
|
return;
|
|
|
|
if (!keycode) {
|
|
printk ("%s: code %08x -> addr %i data 0x%02x -> unknown key!\n",
|
|
__func__, ircom, addr, data);
|
|
return;
|
|
}
|
|
|
|
if (ir->keypressed &&
|
|
(ir->last_key != keycode || toggle != ir->last_toggle))
|
|
input_event(ir->input_dev, EV_KEY, ir->last_key, 0);
|
|
|
|
input_event(ir->input_dev, EV_KEY, keycode, 1);
|
|
input_sync(ir->input_dev);
|
|
|
|
ir->keypressed = true;
|
|
ir->last_key = keycode;
|
|
ir->last_toggle = toggle;
|
|
|
|
mod_timer(&ir->keyup_timer, jiffies + UP_TIMEOUT);
|
|
}
|
|
|
|
|
|
/* register with input layer */
|
|
static void input_register_keys(struct infrared *ir)
|
|
{
|
|
int i;
|
|
|
|
set_bit(EV_KEY, ir->input_dev->evbit);
|
|
set_bit(EV_REP, ir->input_dev->evbit);
|
|
set_bit(EV_MSC, ir->input_dev->evbit);
|
|
|
|
set_bit(MSC_RAW, ir->input_dev->mscbit);
|
|
set_bit(MSC_SCAN, ir->input_dev->mscbit);
|
|
|
|
memset(ir->input_dev->keybit, 0, sizeof(ir->input_dev->keybit));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ir->key_map); i++) {
|
|
if (ir->key_map[i] > KEY_MAX)
|
|
ir->key_map[i] = 0;
|
|
else if (ir->key_map[i] > KEY_RESERVED)
|
|
set_bit(ir->key_map[i], ir->input_dev->keybit);
|
|
}
|
|
|
|
ir->input_dev->keycode = ir->key_map;
|
|
ir->input_dev->keycodesize = sizeof(ir->key_map[0]);
|
|
ir->input_dev->keycodemax = ARRAY_SIZE(ir->key_map);
|
|
}
|
|
|
|
/* check for configuration changes */
|
|
int av7110_check_ir_config(struct av7110 *av7110, int force)
|
|
{
|
|
int i;
|
|
int modified = force;
|
|
int ret = -ENODEV;
|
|
|
|
for (i = 0; i < av_cnt; i++)
|
|
if (av7110 == av_list[i])
|
|
break;
|
|
|
|
if (i < av_cnt && av7110) {
|
|
if ((av7110->ir.protocol & 1) != ir_protocol[i] ||
|
|
av7110->ir.inversion != ir_inversion[i])
|
|
modified = true;
|
|
|
|
if (modified) {
|
|
/* protocol */
|
|
if (ir_protocol[i]) {
|
|
ir_protocol[i] = 1;
|
|
av7110->ir.protocol = IR_RCMM;
|
|
av7110->ir.ir_config = 0x0001;
|
|
} else if (FW_VERSION(av7110->arm_app) >= 0x2620) {
|
|
av7110->ir.protocol = IR_RC5_EXT;
|
|
av7110->ir.ir_config = 0x0002;
|
|
} else {
|
|
av7110->ir.protocol = IR_RC5;
|
|
av7110->ir.ir_config = 0x0000;
|
|
}
|
|
/* inversion */
|
|
if (ir_inversion[i]) {
|
|
ir_inversion[i] = 1;
|
|
av7110->ir.ir_config |= 0x8000;
|
|
}
|
|
av7110->ir.inversion = ir_inversion[i];
|
|
/* update ARM */
|
|
ret = av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1,
|
|
av7110->ir.ir_config);
|
|
} else
|
|
ret = 0;
|
|
|
|
/* address */
|
|
if (av7110->ir.device_mask != ir_device_mask[i])
|
|
av7110->ir.device_mask = ir_device_mask[i];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* /proc/av7110_ir interface */
|
|
static ssize_t av7110_ir_proc_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
char *page;
|
|
u32 ir_config;
|
|
int size = sizeof ir_config + sizeof av_list[0]->ir.key_map;
|
|
int i;
|
|
|
|
if (count < size)
|
|
return -EINVAL;
|
|
|
|
page = vmalloc(size);
|
|
if (!page)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(page, buffer, size)) {
|
|
vfree(page);
|
|
return -EFAULT;
|
|
}
|
|
|
|
memcpy(&ir_config, page, sizeof ir_config);
|
|
|
|
for (i = 0; i < av_cnt; i++) {
|
|
/* keymap */
|
|
memcpy(av_list[i]->ir.key_map, page + sizeof ir_config,
|
|
sizeof(av_list[i]->ir.key_map));
|
|
/* protocol, inversion, address */
|
|
ir_protocol[i] = ir_config & 0x0001;
|
|
ir_inversion[i] = ir_config & 0x8000 ? 1 : 0;
|
|
if (ir_config & 0x4000)
|
|
ir_device_mask[i] = 1 << ((ir_config >> 16) & 0x1f);
|
|
else
|
|
ir_device_mask[i] = IR_ALL;
|
|
/* update configuration */
|
|
av7110_check_ir_config(av_list[i], false);
|
|
input_register_keys(&av_list[i]->ir);
|
|
}
|
|
vfree(page);
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations av7110_ir_proc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.write = av7110_ir_proc_write,
|
|
.llseek = noop_llseek,
|
|
};
|
|
|
|
/* interrupt handler */
|
|
static void ir_handler(struct av7110 *av7110, u32 ircom)
|
|
{
|
|
dprintk(4, "ir command = %08x\n", ircom);
|
|
av7110->ir.ir_command = ircom;
|
|
tasklet_schedule(&av7110->ir.ir_tasklet);
|
|
}
|
|
|
|
|
|
int av7110_ir_init(struct av7110 *av7110)
|
|
{
|
|
struct input_dev *input_dev;
|
|
static struct proc_dir_entry *e;
|
|
int err;
|
|
|
|
if (av_cnt >= ARRAY_SIZE(av_list))
|
|
return -ENOSPC;
|
|
|
|
av_list[av_cnt++] = av7110;
|
|
av7110_check_ir_config(av7110, true);
|
|
|
|
timer_setup(&av7110->ir.keyup_timer, av7110_emit_keyup, 0);
|
|
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev)
|
|
return -ENOMEM;
|
|
|
|
av7110->ir.input_dev = input_dev;
|
|
snprintf(av7110->ir.input_phys, sizeof(av7110->ir.input_phys),
|
|
"pci-%s/ir0", pci_name(av7110->dev->pci));
|
|
|
|
input_dev->name = "DVB on-card IR receiver";
|
|
|
|
input_dev->phys = av7110->ir.input_phys;
|
|
input_dev->id.bustype = BUS_PCI;
|
|
input_dev->id.version = 2;
|
|
if (av7110->dev->pci->subsystem_vendor) {
|
|
input_dev->id.vendor = av7110->dev->pci->subsystem_vendor;
|
|
input_dev->id.product = av7110->dev->pci->subsystem_device;
|
|
} else {
|
|
input_dev->id.vendor = av7110->dev->pci->vendor;
|
|
input_dev->id.product = av7110->dev->pci->device;
|
|
}
|
|
input_dev->dev.parent = &av7110->dev->pci->dev;
|
|
/* initial keymap */
|
|
memcpy(av7110->ir.key_map, default_key_map, sizeof av7110->ir.key_map);
|
|
input_register_keys(&av7110->ir);
|
|
err = input_register_device(input_dev);
|
|
if (err) {
|
|
input_free_device(input_dev);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Input core's default autorepeat is 33 cps with 250 msec
|
|
* delay, let's adjust to numbers more suitable for remote
|
|
* control.
|
|
*/
|
|
input_enable_softrepeat(input_dev, 250, 125);
|
|
|
|
if (av_cnt == 1) {
|
|
e = proc_create("av7110_ir", S_IWUSR, NULL, &av7110_ir_proc_fops);
|
|
if (e)
|
|
proc_set_size(e, 4 + 256 * sizeof(u16));
|
|
}
|
|
|
|
tasklet_init(&av7110->ir.ir_tasklet, av7110_emit_key, (unsigned long) &av7110->ir);
|
|
av7110->ir.ir_handler = ir_handler;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void av7110_ir_exit(struct av7110 *av7110)
|
|
{
|
|
int i;
|
|
|
|
if (av_cnt == 0)
|
|
return;
|
|
|
|
del_timer_sync(&av7110->ir.keyup_timer);
|
|
av7110->ir.ir_handler = NULL;
|
|
tasklet_kill(&av7110->ir.ir_tasklet);
|
|
|
|
for (i = 0; i < av_cnt; i++)
|
|
if (av_list[i] == av7110) {
|
|
av_list[i] = av_list[av_cnt-1];
|
|
av_list[av_cnt-1] = NULL;
|
|
break;
|
|
}
|
|
|
|
if (av_cnt == 1)
|
|
remove_proc_entry("av7110_ir", NULL);
|
|
|
|
input_unregister_device(av7110->ir.input_dev);
|
|
|
|
av_cnt--;
|
|
}
|
|
|
|
//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
|
|
//MODULE_LICENSE("GPL");
|