forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
844 lines
20 KiB
844 lines
20 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* serial_ir.c |
|
* |
|
* serial_ir - Device driver that records pulse- and pause-lengths |
|
* (space-lengths) between DDCD event on a serial port. |
|
* |
|
* Copyright (C) 1996,97 Ralph Metzler <[email protected]> |
|
* Copyright (C) 1998 Trent Piepho <[email protected]> |
|
* Copyright (C) 1998 Ben Pfaff <[email protected]> |
|
* Copyright (C) 1999 Christoph Bartelmus <[email protected]> |
|
* Copyright (C) 2007 Andrei Tanas <[email protected]> (suspend/resume support) |
|
* Copyright (C) 2016 Sean Young <[email protected]> (port to rc-core) |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/module.h> |
|
#include <linux/errno.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/kernel.h> |
|
#include <linux/serial_reg.h> |
|
#include <linux/types.h> |
|
#include <linux/delay.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/spinlock.h> |
|
#include <media/rc-core.h> |
|
|
|
struct serial_ir_hw { |
|
int signal_pin; |
|
int signal_pin_change; |
|
u8 on; |
|
u8 off; |
|
unsigned set_send_carrier:1; |
|
unsigned set_duty_cycle:1; |
|
void (*send_pulse)(unsigned int length, ktime_t edge); |
|
void (*send_space)(void); |
|
spinlock_t lock; |
|
}; |
|
|
|
#define IR_HOMEBREW 0 |
|
#define IR_IRDEO 1 |
|
#define IR_IRDEO_REMOTE 2 |
|
#define IR_ANIMAX 3 |
|
#define IR_IGOR 4 |
|
|
|
/* module parameters */ |
|
static int type; |
|
static int io; |
|
static int irq; |
|
static ulong iommap; |
|
static int ioshift; |
|
static bool softcarrier = true; |
|
static bool share_irq; |
|
static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */ |
|
static bool txsense; /* 0 = active high, 1 = active low */ |
|
|
|
/* forward declarations */ |
|
static void send_pulse_irdeo(unsigned int length, ktime_t edge); |
|
static void send_space_irdeo(void); |
|
#ifdef CONFIG_IR_SERIAL_TRANSMITTER |
|
static void send_pulse_homebrew(unsigned int length, ktime_t edge); |
|
static void send_space_homebrew(void); |
|
#endif |
|
|
|
static struct serial_ir_hw hardware[] = { |
|
[IR_HOMEBREW] = { |
|
.lock = __SPIN_LOCK_UNLOCKED(hardware[IR_HOMEBREW].lock), |
|
.signal_pin = UART_MSR_DCD, |
|
.signal_pin_change = UART_MSR_DDCD, |
|
.on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), |
|
.off = (UART_MCR_RTS | UART_MCR_OUT2), |
|
#ifdef CONFIG_IR_SERIAL_TRANSMITTER |
|
.send_pulse = send_pulse_homebrew, |
|
.send_space = send_space_homebrew, |
|
.set_send_carrier = true, |
|
.set_duty_cycle = true, |
|
#endif |
|
}, |
|
|
|
[IR_IRDEO] = { |
|
.lock = __SPIN_LOCK_UNLOCKED(hardware[IR_IRDEO].lock), |
|
.signal_pin = UART_MSR_DSR, |
|
.signal_pin_change = UART_MSR_DDSR, |
|
.on = UART_MCR_OUT2, |
|
.off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), |
|
.send_pulse = send_pulse_irdeo, |
|
.send_space = send_space_irdeo, |
|
.set_duty_cycle = true, |
|
}, |
|
|
|
[IR_IRDEO_REMOTE] = { |
|
.lock = __SPIN_LOCK_UNLOCKED(hardware[IR_IRDEO_REMOTE].lock), |
|
.signal_pin = UART_MSR_DSR, |
|
.signal_pin_change = UART_MSR_DDSR, |
|
.on = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), |
|
.off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), |
|
.send_pulse = send_pulse_irdeo, |
|
.send_space = send_space_irdeo, |
|
.set_duty_cycle = true, |
|
}, |
|
|
|
[IR_ANIMAX] = { |
|
.lock = __SPIN_LOCK_UNLOCKED(hardware[IR_ANIMAX].lock), |
|
.signal_pin = UART_MSR_DCD, |
|
.signal_pin_change = UART_MSR_DDCD, |
|
.on = 0, |
|
.off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), |
|
}, |
|
|
|
[IR_IGOR] = { |
|
.lock = __SPIN_LOCK_UNLOCKED(hardware[IR_IGOR].lock), |
|
.signal_pin = UART_MSR_DSR, |
|
.signal_pin_change = UART_MSR_DDSR, |
|
.on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), |
|
.off = (UART_MCR_RTS | UART_MCR_OUT2), |
|
#ifdef CONFIG_IR_SERIAL_TRANSMITTER |
|
.send_pulse = send_pulse_homebrew, |
|
.send_space = send_space_homebrew, |
|
.set_send_carrier = true, |
|
.set_duty_cycle = true, |
|
#endif |
|
}, |
|
}; |
|
|
|
#define RS_ISR_PASS_LIMIT 256 |
|
|
|
struct serial_ir { |
|
ktime_t lastkt; |
|
struct rc_dev *rcdev; |
|
struct platform_device *pdev; |
|
struct timer_list timeout_timer; |
|
|
|
unsigned int carrier; |
|
unsigned int duty_cycle; |
|
}; |
|
|
|
static struct serial_ir serial_ir; |
|
|
|
/* fetch serial input packet (1 byte) from register offset */ |
|
static u8 sinp(int offset) |
|
{ |
|
if (iommap) |
|
/* the register is memory-mapped */ |
|
offset <<= ioshift; |
|
|
|
return inb(io + offset); |
|
} |
|
|
|
/* write serial output packet (1 byte) of value to register offset */ |
|
static void soutp(int offset, u8 value) |
|
{ |
|
if (iommap) |
|
/* the register is memory-mapped */ |
|
offset <<= ioshift; |
|
|
|
outb(value, io + offset); |
|
} |
|
|
|
static void on(void) |
|
{ |
|
if (txsense) |
|
soutp(UART_MCR, hardware[type].off); |
|
else |
|
soutp(UART_MCR, hardware[type].on); |
|
} |
|
|
|
static void off(void) |
|
{ |
|
if (txsense) |
|
soutp(UART_MCR, hardware[type].on); |
|
else |
|
soutp(UART_MCR, hardware[type].off); |
|
} |
|
|
|
static void send_pulse_irdeo(unsigned int length, ktime_t target) |
|
{ |
|
long rawbits; |
|
int i; |
|
unsigned char output; |
|
unsigned char chunk, shifted; |
|
|
|
/* how many bits have to be sent ? */ |
|
rawbits = length * 1152 / 10000; |
|
if (serial_ir.duty_cycle > 50) |
|
chunk = 3; |
|
else |
|
chunk = 1; |
|
for (i = 0, output = 0x7f; rawbits > 0; rawbits -= 3) { |
|
shifted = chunk << (i * 3); |
|
shifted >>= 1; |
|
output &= (~shifted); |
|
i++; |
|
if (i == 3) { |
|
soutp(UART_TX, output); |
|
while (!(sinp(UART_LSR) & UART_LSR_THRE)) |
|
; |
|
output = 0x7f; |
|
i = 0; |
|
} |
|
} |
|
if (i != 0) { |
|
soutp(UART_TX, output); |
|
while (!(sinp(UART_LSR) & UART_LSR_TEMT)) |
|
; |
|
} |
|
} |
|
|
|
static void send_space_irdeo(void) |
|
{ |
|
} |
|
|
|
#ifdef CONFIG_IR_SERIAL_TRANSMITTER |
|
static void send_pulse_homebrew_softcarrier(unsigned int length, ktime_t edge) |
|
{ |
|
ktime_t now, target = ktime_add_us(edge, length); |
|
/* |
|
* delta should never exceed 4 seconds and on m68k |
|
* ndelay(s64) does not compile; so use s32 rather than s64. |
|
*/ |
|
s32 delta; |
|
unsigned int pulse, space; |
|
|
|
/* Ensure the dividend fits into 32 bit */ |
|
pulse = DIV_ROUND_CLOSEST(serial_ir.duty_cycle * (NSEC_PER_SEC / 100), |
|
serial_ir.carrier); |
|
space = DIV_ROUND_CLOSEST((100 - serial_ir.duty_cycle) * |
|
(NSEC_PER_SEC / 100), serial_ir.carrier); |
|
|
|
for (;;) { |
|
now = ktime_get(); |
|
if (ktime_compare(now, target) >= 0) |
|
break; |
|
on(); |
|
edge = ktime_add_ns(edge, pulse); |
|
delta = ktime_to_ns(ktime_sub(edge, now)); |
|
if (delta > 0) |
|
ndelay(delta); |
|
now = ktime_get(); |
|
off(); |
|
if (ktime_compare(now, target) >= 0) |
|
break; |
|
edge = ktime_add_ns(edge, space); |
|
delta = ktime_to_ns(ktime_sub(edge, now)); |
|
if (delta > 0) |
|
ndelay(delta); |
|
} |
|
} |
|
|
|
static void send_pulse_homebrew(unsigned int length, ktime_t edge) |
|
{ |
|
if (softcarrier) |
|
send_pulse_homebrew_softcarrier(length, edge); |
|
else |
|
on(); |
|
} |
|
|
|
static void send_space_homebrew(void) |
|
{ |
|
off(); |
|
} |
|
#endif |
|
|
|
static void frbwrite(unsigned int l, bool is_pulse) |
|
{ |
|
/* simple noise filter */ |
|
static unsigned int ptr, pulse, space; |
|
struct ir_raw_event ev = {}; |
|
|
|
if (ptr > 0 && is_pulse) { |
|
pulse += l; |
|
if (pulse > 250) { |
|
ev.duration = space; |
|
ev.pulse = false; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
ev.duration = pulse; |
|
ev.pulse = true; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
ptr = 0; |
|
pulse = 0; |
|
} |
|
return; |
|
} |
|
if (!is_pulse) { |
|
if (ptr == 0) { |
|
if (l > 20000) { |
|
space = l; |
|
ptr++; |
|
return; |
|
} |
|
} else { |
|
if (l > 20000) { |
|
space += pulse; |
|
if (space > IR_MAX_DURATION) |
|
space = IR_MAX_DURATION; |
|
space += l; |
|
if (space > IR_MAX_DURATION) |
|
space = IR_MAX_DURATION; |
|
pulse = 0; |
|
return; |
|
} |
|
|
|
ev.duration = space; |
|
ev.pulse = false; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
ev.duration = pulse; |
|
ev.pulse = true; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
ptr = 0; |
|
pulse = 0; |
|
} |
|
} |
|
|
|
ev.duration = l; |
|
ev.pulse = is_pulse; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
} |
|
|
|
static irqreturn_t serial_ir_irq_handler(int i, void *blah) |
|
{ |
|
ktime_t kt; |
|
int counter, dcd; |
|
u8 status; |
|
ktime_t delkt; |
|
unsigned int data; |
|
static int last_dcd = -1; |
|
|
|
if ((sinp(UART_IIR) & UART_IIR_NO_INT)) { |
|
/* not our interrupt */ |
|
return IRQ_NONE; |
|
} |
|
|
|
counter = 0; |
|
do { |
|
counter++; |
|
status = sinp(UART_MSR); |
|
if (counter > RS_ISR_PASS_LIMIT) { |
|
dev_err(&serial_ir.pdev->dev, "Trapped in interrupt"); |
|
break; |
|
} |
|
if ((status & hardware[type].signal_pin_change) && |
|
sense != -1) { |
|
/* get current time */ |
|
kt = ktime_get(); |
|
|
|
/* |
|
* The driver needs to know if your receiver is |
|
* active high or active low, or the space/pulse |
|
* sense could be inverted. |
|
*/ |
|
|
|
/* calc time since last interrupt in nanoseconds */ |
|
dcd = (status & hardware[type].signal_pin) ? 1 : 0; |
|
|
|
if (dcd == last_dcd) { |
|
dev_dbg(&serial_ir.pdev->dev, |
|
"ignoring spike: %d %d %lldns %lldns\n", |
|
dcd, sense, ktime_to_ns(kt), |
|
ktime_to_ns(serial_ir.lastkt)); |
|
continue; |
|
} |
|
|
|
delkt = ktime_sub(kt, serial_ir.lastkt); |
|
if (ktime_compare(delkt, ktime_set(15, 0)) > 0) { |
|
data = IR_MAX_DURATION; /* really long time */ |
|
if (!(dcd ^ sense)) { |
|
/* sanity check */ |
|
dev_err(&serial_ir.pdev->dev, |
|
"dcd unexpected: %d %d %lldns %lldns\n", |
|
dcd, sense, ktime_to_ns(kt), |
|
ktime_to_ns(serial_ir.lastkt)); |
|
/* |
|
* detecting pulse while this |
|
* MUST be a space! |
|
*/ |
|
sense = sense ? 0 : 1; |
|
} |
|
} else { |
|
data = ktime_to_us(delkt); |
|
} |
|
frbwrite(data, !(dcd ^ sense)); |
|
serial_ir.lastkt = kt; |
|
last_dcd = dcd; |
|
} |
|
} while (!(sinp(UART_IIR) & UART_IIR_NO_INT)); /* still pending ? */ |
|
|
|
mod_timer(&serial_ir.timeout_timer, |
|
jiffies + usecs_to_jiffies(serial_ir.rcdev->timeout)); |
|
|
|
ir_raw_event_handle(serial_ir.rcdev); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static int hardware_init_port(void) |
|
{ |
|
u8 scratch, scratch2, scratch3; |
|
|
|
/* |
|
* This is a simple port existence test, borrowed from the autoconfig |
|
* function in drivers/tty/serial/8250/8250_port.c |
|
*/ |
|
scratch = sinp(UART_IER); |
|
soutp(UART_IER, 0); |
|
#ifdef __i386__ |
|
outb(0xff, 0x080); |
|
#endif |
|
scratch2 = sinp(UART_IER) & 0x0f; |
|
soutp(UART_IER, 0x0f); |
|
#ifdef __i386__ |
|
outb(0x00, 0x080); |
|
#endif |
|
scratch3 = sinp(UART_IER) & 0x0f; |
|
soutp(UART_IER, scratch); |
|
if (scratch2 != 0 || scratch3 != 0x0f) { |
|
/* we fail, there's nothing here */ |
|
pr_err("port existence test failed, cannot continue\n"); |
|
return -ENODEV; |
|
} |
|
|
|
/* Set DLAB 0. */ |
|
soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); |
|
|
|
/* First of all, disable all interrupts */ |
|
soutp(UART_IER, sinp(UART_IER) & |
|
(~(UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI | UART_IER_RDI))); |
|
|
|
/* Clear registers. */ |
|
sinp(UART_LSR); |
|
sinp(UART_RX); |
|
sinp(UART_IIR); |
|
sinp(UART_MSR); |
|
|
|
/* Set line for power source */ |
|
off(); |
|
|
|
/* Clear registers again to be sure. */ |
|
sinp(UART_LSR); |
|
sinp(UART_RX); |
|
sinp(UART_IIR); |
|
sinp(UART_MSR); |
|
|
|
switch (type) { |
|
case IR_IRDEO: |
|
case IR_IRDEO_REMOTE: |
|
/* setup port to 7N1 @ 115200 Baud */ |
|
/* 7N1+start = 9 bits at 115200 ~ 3 bits at 38kHz */ |
|
|
|
/* Set DLAB 1. */ |
|
soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); |
|
/* Set divisor to 1 => 115200 Baud */ |
|
soutp(UART_DLM, 0); |
|
soutp(UART_DLL, 1); |
|
/* Set DLAB 0 + 7N1 */ |
|
soutp(UART_LCR, UART_LCR_WLEN7); |
|
/* THR interrupt already disabled at this point */ |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void serial_ir_timeout(struct timer_list *unused) |
|
{ |
|
struct ir_raw_event ev = { |
|
.timeout = true, |
|
.duration = serial_ir.rcdev->timeout |
|
}; |
|
ir_raw_event_store_with_filter(serial_ir.rcdev, &ev); |
|
ir_raw_event_handle(serial_ir.rcdev); |
|
} |
|
|
|
/* Needed by serial_ir_probe() */ |
|
static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf, |
|
unsigned int count); |
|
static int serial_ir_tx_duty_cycle(struct rc_dev *dev, u32 cycle); |
|
static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier); |
|
static int serial_ir_open(struct rc_dev *rcdev); |
|
static void serial_ir_close(struct rc_dev *rcdev); |
|
|
|
static int serial_ir_probe(struct platform_device *dev) |
|
{ |
|
struct rc_dev *rcdev; |
|
int i, nlow, nhigh, result; |
|
|
|
rcdev = devm_rc_allocate_device(&dev->dev, RC_DRIVER_IR_RAW); |
|
if (!rcdev) |
|
return -ENOMEM; |
|
|
|
if (hardware[type].send_pulse && hardware[type].send_space) |
|
rcdev->tx_ir = serial_ir_tx; |
|
if (hardware[type].set_send_carrier) |
|
rcdev->s_tx_carrier = serial_ir_tx_carrier; |
|
if (hardware[type].set_duty_cycle) |
|
rcdev->s_tx_duty_cycle = serial_ir_tx_duty_cycle; |
|
|
|
switch (type) { |
|
case IR_HOMEBREW: |
|
rcdev->device_name = "Serial IR type home-brew"; |
|
break; |
|
case IR_IRDEO: |
|
rcdev->device_name = "Serial IR type IRdeo"; |
|
break; |
|
case IR_IRDEO_REMOTE: |
|
rcdev->device_name = "Serial IR type IRdeo remote"; |
|
break; |
|
case IR_ANIMAX: |
|
rcdev->device_name = "Serial IR type AnimaX"; |
|
break; |
|
case IR_IGOR: |
|
rcdev->device_name = "Serial IR type IgorPlug"; |
|
break; |
|
} |
|
|
|
rcdev->input_phys = KBUILD_MODNAME "/input0"; |
|
rcdev->input_id.bustype = BUS_HOST; |
|
rcdev->input_id.vendor = 0x0001; |
|
rcdev->input_id.product = 0x0001; |
|
rcdev->input_id.version = 0x0100; |
|
rcdev->open = serial_ir_open; |
|
rcdev->close = serial_ir_close; |
|
rcdev->dev.parent = &serial_ir.pdev->dev; |
|
rcdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; |
|
rcdev->driver_name = KBUILD_MODNAME; |
|
rcdev->map_name = RC_MAP_RC6_MCE; |
|
rcdev->min_timeout = 1; |
|
rcdev->timeout = IR_DEFAULT_TIMEOUT; |
|
rcdev->max_timeout = 10 * IR_DEFAULT_TIMEOUT; |
|
rcdev->rx_resolution = 250; |
|
|
|
serial_ir.rcdev = rcdev; |
|
|
|
timer_setup(&serial_ir.timeout_timer, serial_ir_timeout, 0); |
|
|
|
result = devm_request_irq(&dev->dev, irq, serial_ir_irq_handler, |
|
share_irq ? IRQF_SHARED : 0, |
|
KBUILD_MODNAME, &hardware); |
|
if (result < 0) { |
|
if (result == -EBUSY) |
|
dev_err(&dev->dev, "IRQ %d busy\n", irq); |
|
else if (result == -EINVAL) |
|
dev_err(&dev->dev, "Bad irq number or handler\n"); |
|
return result; |
|
} |
|
|
|
/* Reserve io region. */ |
|
if ((iommap && |
|
(devm_request_mem_region(&dev->dev, iommap, 8UL << ioshift, |
|
KBUILD_MODNAME) == NULL)) || |
|
(!iommap && (devm_request_region(&dev->dev, io, 8, |
|
KBUILD_MODNAME) == NULL))) { |
|
dev_err(&dev->dev, "port %04x already in use\n", io); |
|
dev_warn(&dev->dev, "use 'setserial /dev/ttySX uart none'\n"); |
|
dev_warn(&dev->dev, |
|
"or compile the serial port driver as module and\n"); |
|
dev_warn(&dev->dev, "make sure this module is loaded first\n"); |
|
return -EBUSY; |
|
} |
|
|
|
result = hardware_init_port(); |
|
if (result < 0) |
|
return result; |
|
|
|
/* Initialize pulse/space widths */ |
|
serial_ir.duty_cycle = 50; |
|
serial_ir.carrier = 38000; |
|
|
|
/* If pin is high, then this must be an active low receiver. */ |
|
if (sense == -1) { |
|
/* wait 1/2 sec for the power supply */ |
|
msleep(500); |
|
|
|
/* |
|
* probe 9 times every 0.04s, collect "votes" for |
|
* active high/low |
|
*/ |
|
nlow = 0; |
|
nhigh = 0; |
|
for (i = 0; i < 9; i++) { |
|
if (sinp(UART_MSR) & hardware[type].signal_pin) |
|
nlow++; |
|
else |
|
nhigh++; |
|
msleep(40); |
|
} |
|
sense = nlow >= nhigh ? 1 : 0; |
|
dev_info(&dev->dev, "auto-detected active %s receiver\n", |
|
sense ? "low" : "high"); |
|
} else |
|
dev_info(&dev->dev, "Manually using active %s receiver\n", |
|
sense ? "low" : "high"); |
|
|
|
dev_dbg(&dev->dev, "Interrupt %d, port %04x obtained\n", irq, io); |
|
|
|
return devm_rc_register_device(&dev->dev, rcdev); |
|
} |
|
|
|
static int serial_ir_open(struct rc_dev *rcdev) |
|
{ |
|
unsigned long flags; |
|
|
|
/* initialize timestamp */ |
|
serial_ir.lastkt = ktime_get(); |
|
|
|
spin_lock_irqsave(&hardware[type].lock, flags); |
|
|
|
/* Set DLAB 0. */ |
|
soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); |
|
|
|
soutp(UART_IER, sinp(UART_IER) | UART_IER_MSI); |
|
|
|
spin_unlock_irqrestore(&hardware[type].lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void serial_ir_close(struct rc_dev *rcdev) |
|
{ |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(&hardware[type].lock, flags); |
|
|
|
/* Set DLAB 0. */ |
|
soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); |
|
|
|
/* First of all, disable all interrupts */ |
|
soutp(UART_IER, sinp(UART_IER) & |
|
(~(UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI | UART_IER_RDI))); |
|
spin_unlock_irqrestore(&hardware[type].lock, flags); |
|
} |
|
|
|
static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf, |
|
unsigned int count) |
|
{ |
|
unsigned long flags; |
|
ktime_t edge; |
|
s64 delta; |
|
int i; |
|
|
|
spin_lock_irqsave(&hardware[type].lock, flags); |
|
if (type == IR_IRDEO) { |
|
/* DTR, RTS down */ |
|
on(); |
|
} |
|
|
|
edge = ktime_get(); |
|
for (i = 0; i < count; i++) { |
|
if (i % 2) |
|
hardware[type].send_space(); |
|
else |
|
hardware[type].send_pulse(txbuf[i], edge); |
|
|
|
edge = ktime_add_us(edge, txbuf[i]); |
|
delta = ktime_us_delta(edge, ktime_get()); |
|
if (delta > 25) { |
|
spin_unlock_irqrestore(&hardware[type].lock, flags); |
|
usleep_range(delta - 25, delta + 25); |
|
spin_lock_irqsave(&hardware[type].lock, flags); |
|
} else if (delta > 0) { |
|
udelay(delta); |
|
} |
|
} |
|
off(); |
|
spin_unlock_irqrestore(&hardware[type].lock, flags); |
|
return count; |
|
} |
|
|
|
static int serial_ir_tx_duty_cycle(struct rc_dev *dev, u32 cycle) |
|
{ |
|
serial_ir.duty_cycle = cycle; |
|
return 0; |
|
} |
|
|
|
static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier) |
|
{ |
|
if (carrier > 500000 || carrier < 20000) |
|
return -EINVAL; |
|
|
|
serial_ir.carrier = carrier; |
|
return 0; |
|
} |
|
|
|
static int serial_ir_suspend(struct platform_device *dev, |
|
pm_message_t state) |
|
{ |
|
/* Set DLAB 0. */ |
|
soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); |
|
|
|
/* Disable all interrupts */ |
|
soutp(UART_IER, sinp(UART_IER) & |
|
(~(UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI | UART_IER_RDI))); |
|
|
|
/* Clear registers. */ |
|
sinp(UART_LSR); |
|
sinp(UART_RX); |
|
sinp(UART_IIR); |
|
sinp(UART_MSR); |
|
|
|
return 0; |
|
} |
|
|
|
static int serial_ir_resume(struct platform_device *dev) |
|
{ |
|
unsigned long flags; |
|
int result; |
|
|
|
result = hardware_init_port(); |
|
if (result < 0) |
|
return result; |
|
|
|
spin_lock_irqsave(&hardware[type].lock, flags); |
|
/* Enable Interrupt */ |
|
serial_ir.lastkt = ktime_get(); |
|
soutp(UART_IER, sinp(UART_IER) | UART_IER_MSI); |
|
off(); |
|
|
|
spin_unlock_irqrestore(&hardware[type].lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static struct platform_driver serial_ir_driver = { |
|
.probe = serial_ir_probe, |
|
.suspend = serial_ir_suspend, |
|
.resume = serial_ir_resume, |
|
.driver = { |
|
.name = "serial_ir", |
|
}, |
|
}; |
|
|
|
static int __init serial_ir_init(void) |
|
{ |
|
int result; |
|
|
|
result = platform_driver_register(&serial_ir_driver); |
|
if (result) |
|
return result; |
|
|
|
serial_ir.pdev = platform_device_alloc("serial_ir", 0); |
|
if (!serial_ir.pdev) { |
|
result = -ENOMEM; |
|
goto exit_driver_unregister; |
|
} |
|
|
|
result = platform_device_add(serial_ir.pdev); |
|
if (result) |
|
goto exit_device_put; |
|
|
|
return 0; |
|
|
|
exit_device_put: |
|
platform_device_put(serial_ir.pdev); |
|
exit_driver_unregister: |
|
platform_driver_unregister(&serial_ir_driver); |
|
return result; |
|
} |
|
|
|
static void serial_ir_exit(void) |
|
{ |
|
platform_device_unregister(serial_ir.pdev); |
|
platform_driver_unregister(&serial_ir_driver); |
|
} |
|
|
|
static int __init serial_ir_init_module(void) |
|
{ |
|
switch (type) { |
|
case IR_HOMEBREW: |
|
case IR_IRDEO: |
|
case IR_IRDEO_REMOTE: |
|
case IR_ANIMAX: |
|
case IR_IGOR: |
|
/* if nothing specified, use ttyS0/com1 and irq 4 */ |
|
io = io ? io : 0x3f8; |
|
irq = irq ? irq : 4; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
if (!softcarrier) { |
|
switch (type) { |
|
case IR_HOMEBREW: |
|
case IR_IGOR: |
|
hardware[type].set_send_carrier = false; |
|
hardware[type].set_duty_cycle = false; |
|
break; |
|
} |
|
} |
|
|
|
/* make sure sense is either -1, 0, or 1 */ |
|
if (sense != -1) |
|
sense = !!sense; |
|
|
|
return serial_ir_init(); |
|
} |
|
|
|
static void __exit serial_ir_exit_module(void) |
|
{ |
|
del_timer_sync(&serial_ir.timeout_timer); |
|
serial_ir_exit(); |
|
} |
|
|
|
module_init(serial_ir_init_module); |
|
module_exit(serial_ir_exit_module); |
|
|
|
MODULE_DESCRIPTION("Infra-red receiver driver for serial ports."); |
|
MODULE_AUTHOR("Ralph Metzler, Trent Piepho, Ben Pfaff, Christoph Bartelmus, Andrei Tanas"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
module_param(type, int, 0444); |
|
MODULE_PARM_DESC(type, "Hardware type (0 = home-brew, 1 = IRdeo, 2 = IRdeo Remote, 3 = AnimaX, 4 = IgorPlug"); |
|
|
|
module_param_hw(io, int, ioport, 0444); |
|
MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); |
|
|
|
/* some architectures (e.g. intel xscale) have memory mapped registers */ |
|
module_param_hw(iommap, ulong, other, 0444); |
|
MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O (0 = no memory mapped io)"); |
|
|
|
/* |
|
* some architectures (e.g. intel xscale) align the 8bit serial registers |
|
* on 32bit word boundaries. |
|
* See linux-kernel/drivers/tty/serial/8250/8250.c serial_in()/out() |
|
*/ |
|
module_param_hw(ioshift, int, other, 0444); |
|
MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)"); |
|
|
|
module_param_hw(irq, int, irq, 0444); |
|
MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); |
|
|
|
module_param_hw(share_irq, bool, other, 0444); |
|
MODULE_PARM_DESC(share_irq, "Share interrupts (0 = off, 1 = on)"); |
|
|
|
module_param(sense, int, 0444); |
|
MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit (0 = active high, 1 = active low )"); |
|
|
|
#ifdef CONFIG_IR_SERIAL_TRANSMITTER |
|
module_param(txsense, bool, 0444); |
|
MODULE_PARM_DESC(txsense, "Sense of transmitter circuit (0 = active high, 1 = active low )"); |
|
#endif |
|
|
|
module_param(softcarrier, bool, 0444); |
|
MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)");
|
|
|