Commit 0a847634 authored by Sean Young's avatar Sean Young Committed by Mauro Carvalho Chehab

[media] lirc_serial: use precision ktime rather than guessing

This makes transmission more reliable and the code much cleaner.
Signed-off-by: default avatarSean Young <sean@mess.org>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent b66db53f
...@@ -21,29 +21,6 @@ ...@@ -21,29 +21,6 @@
* GNU General Public License for more details. * GNU General Public License for more details.
*/ */
/*
* Steve's changes to improve transmission fidelity:
* - for systems with the rdtsc instruction and the clock counter, a
* send_pule that times the pulses directly using the counter.
* This means that the IR_SERIAL_TRANSMITTER_LATENCY fudge is
* not needed. Measurement shows very stable waveform, even where
* PCI activity slows the access to the UART, which trips up other
* versions.
* - For other system, non-integer-microsecond pulse/space lengths,
* done using fixed point binary. So, much more accurate carrier
* frequency.
* - fine tuned transmitter latency, taking advantage of fractional
* microseconds in previous change
* - Fixed bug in the way transmitter latency was accounted for by
* tuning the pulse lengths down - the send_pulse routine ignored
* this overhead as it timed the overall pulse length - so the
* pulse frequency was right but overall pulse length was too
* long. Fixed by accounting for latency on each pulse/space
* iteration.
*
* Steve Davies <steve@daviesfam.org> July 2001
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h> #include <linux/module.h>
...@@ -64,8 +41,8 @@ struct serial_ir_hw { ...@@ -64,8 +41,8 @@ struct serial_ir_hw {
u8 off; u8 off;
unsigned set_send_carrier:1; unsigned set_send_carrier:1;
unsigned set_duty_cycle:1; unsigned set_duty_cycle:1;
long (*send_pulse)(unsigned long length); void (*send_pulse)(unsigned int length, ktime_t edge);
void (*send_space)(long length); void (*send_space)(void);
spinlock_t lock; spinlock_t lock;
}; };
...@@ -87,11 +64,11 @@ static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */ ...@@ -87,11 +64,11 @@ static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */
static bool txsense; /* 0 = active high, 1 = active low */ static bool txsense; /* 0 = active high, 1 = active low */
/* forward declarations */ /* forward declarations */
static long send_pulse_irdeo(unsigned long length); static void send_pulse_irdeo(unsigned int length, ktime_t edge);
static void send_space_irdeo(long length); static void send_space_irdeo(void);
#ifdef CONFIG_IR_SERIAL_TRANSMITTER #ifdef CONFIG_IR_SERIAL_TRANSMITTER
static long send_pulse_homebrew(unsigned long length); static void send_pulse_homebrew(unsigned int length, ktime_t edge);
static void send_space_homebrew(long length); static void send_space_homebrew(void);
#endif #endif
static struct serial_ir_hw hardware[] = { static struct serial_ir_hw hardware[] = {
...@@ -137,8 +114,6 @@ static struct serial_ir_hw hardware[] = { ...@@ -137,8 +114,6 @@ static struct serial_ir_hw hardware[] = {
.signal_pin_change = UART_MSR_DDCD, .signal_pin_change = UART_MSR_DDCD,
.on = 0, .on = 0,
.off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
.send_pulse = NULL,
.send_space = NULL,
}, },
[IR_IGOR] = { [IR_IGOR] = {
...@@ -166,51 +141,11 @@ struct serial_ir { ...@@ -166,51 +141,11 @@ struct serial_ir {
unsigned int freq; unsigned int freq;
unsigned int duty_cycle; unsigned int duty_cycle;
unsigned long period; unsigned int pulse_width, space_width;
unsigned long pulse_width, space_width;
}; };
static struct serial_ir serial_ir; static struct serial_ir serial_ir;
#if defined(__i386__)
/*
* From:
* Linux I/O port programming mini-HOWTO
* Author: Riku Saikkonen <Riku.Saikkonen@hut.fi>
* v, 28 December 1997
*
* [...]
* Actually, a port I/O instruction on most ports in the 0-0x3ff range
* takes almost exactly 1 microsecond, so if you're, for example, using
* the parallel port directly, just do additional inb()s from that port
* to delay.
* [...]
*/
/* transmitter latency 1.5625us 0x1.90 - this figure arrived at from
* comment above plus trimming to match actual measured frequency.
* This will be sensitive to cpu speed, though hopefully most of the 1.5us
* is spent in the uart access. Still - for reference test machine was a
* 1.13GHz Athlon system - Steve
*/
/*
* changed from 400 to 450 as this works better on slower machines;
* faster machines will use the rdtsc code anyway
*/
#define IR_SERIAL_TRANSMITTER_LATENCY 450
#else
/* does anybody have information on other platforms ? */
/* 256 = 1<<8 */
#define IR_SERIAL_TRANSMITTER_LATENCY 256
#endif /* __i386__ */
/*
* FIXME: should we be using hrtimers instead of this
* IR_SERIAL_TRANSMITTER_LATENCY nonsense?
*/
/* fetch serial input packet (1 byte) from register offset */ /* fetch serial input packet (1 byte) from register offset */
static u8 sinp(int offset) static u8 sinp(int offset)
{ {
...@@ -247,96 +182,21 @@ static void off(void) ...@@ -247,96 +182,21 @@ static void off(void)
soutp(UART_MCR, hardware[type].off); soutp(UART_MCR, hardware[type].off);
} }
#ifndef MAX_UDELAY_MS static void init_timing_params(unsigned int new_duty_cycle,
#define MAX_UDELAY_US 5000 unsigned int new_freq)
#else
#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
#endif
static void safe_udelay(unsigned long usecs)
{ {
while (usecs > MAX_UDELAY_US) {
udelay(MAX_UDELAY_US);
usecs -= MAX_UDELAY_US;
}
udelay(usecs);
}
#ifdef USE_RDTSC
/*
* This is an overflow/precision juggle, complicated in that we can't
* do long long divide in the kernel
*/
/*
* When we use the rdtsc instruction to measure clocks, we keep the
* pulse and space widths as clock cycles. As this is CPU speed
* dependent, the widths must be calculated in init_port and ioctl
* time
*/
static int init_timing_params(unsigned int new_duty_cycle,
unsigned int new_freq)
{
__u64 loops_per_sec, work;
serial_ir.duty_cycle = new_duty_cycle; serial_ir.duty_cycle = new_duty_cycle;
serial_ir.freq = new_freq; serial_ir.freq = new_freq;
loops_per_sec = __this_cpu_read(cpu.info.loops_per_jiffy); serial_ir.pulse_width = DIV_ROUND_CLOSEST(
loops_per_sec *= HZ; new_duty_cycle * NSEC_PER_SEC, new_freq * 100l);
serial_ir.space_width = DIV_ROUND_CLOSEST(
/* How many clocks in a microsecond?, avoiding long long divide */ (100l - new_duty_cycle) * NSEC_PER_SEC, new_freq * 100l);
work = loops_per_sec;
work *= 4295; /* 4295 = 2^32 / 1e6 */
/*
* Carrier period in clocks, approach good up to 32GHz clock,
* gets carrier frequency within 8Hz
*/
serial_ir.period = loops_per_sec >> 3;
serial_ir.pperiod /= (freq >> 3);
/* Derive pulse and space from the period */
serial_ir.ppulse_width = serial_ir.period * serial.ir.duty_cycle / 100;
serial_ir.pspace_width = serial_ir.period - serial_ir.pulse_width;
pr_debug("in init_timing_params, freq=%d, duty_cycle=%d, clk/jiffy=%ld, pulse=%ld, space=%ld, conv_us_to_clocks=%ld\n",
freq, duty_cycle, __this_cpu_read(cpu_info.loops_per_jiffy),
pulse_width, space_width, conv_us_to_clocks);
return 0;
}
#else /* ! USE_RDTSC */
static int init_timing_params(unsigned int new_duty_cycle,
unsigned int new_freq)
{
/*
* period, pulse/space width are kept with 8 binary places -
* IE multiplied by 256.
*/
if (256 * 1000000L / new_freq * new_duty_cycle / 100 <=
IR_SERIAL_TRANSMITTER_LATENCY)
return -EINVAL;
if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <=
IR_SERIAL_TRANSMITTER_LATENCY)
return -EINVAL;
serial_ir.duty_cycle = new_duty_cycle;
serial_ir.freq = new_freq;
serial_ir.period = 256 * 1000000L / serial_ir.freq;
serial_ir.pulse_width = serial_ir.period * serial_ir.duty_cycle / 100;
serial_ir.space_width = serial_ir.period - serial_ir.pulse_width;
pr_debug("in init_timing_params, freq=%d pulse=%ld, space=%ld\n",
serial_ir.freq, serial_ir.pulse_width,
serial_ir.space_width);
return 0;
} }
#endif /* USE_RDTSC */
static void send_pulse_irdeo(unsigned int length, ktime_t target)
/* return value: space length delta */
static long send_pulse_irdeo(unsigned long length)
{ {
long rawbits, ret; long rawbits;
int i; int i;
unsigned char output; unsigned char output;
unsigned char chunk, shifted; unsigned char chunk, shifted;
...@@ -365,84 +225,53 @@ static long send_pulse_irdeo(unsigned long length) ...@@ -365,84 +225,53 @@ static long send_pulse_irdeo(unsigned long length)
while (!(sinp(UART_LSR) & UART_LSR_TEMT)) while (!(sinp(UART_LSR) & UART_LSR_TEMT))
; ;
} }
if (i == 0)
ret = (-rawbits) * 10000 / 1152;
else
ret = (3 - i) * 3 * 10000 / 1152 + (-rawbits) * 10000 / 1152;
return ret;
} }
/* Version using udelay() */ static void send_space_irdeo(void)
/*
* here we use fixed point arithmetic, with 8
* fractional bits. that gets us within 0.1% or so of the right average
* frequency, albeit with some jitter in pulse length - Steve
*
* This should use ndelay instead.
*/
/* To match 8 fractional bits used for pulse/space length */
static void send_space_irdeo(long length)
{ {
if (length <= 0)
return;
safe_udelay(length);
} }
#ifdef CONFIG_IR_SERIAL_TRANSMITTER #ifdef CONFIG_IR_SERIAL_TRANSMITTER
static long send_pulse_homebrew_softcarrier(unsigned long length) static void send_pulse_homebrew_softcarrier(unsigned int length, ktime_t edge)
{ {
int flag; ktime_t now, target = ktime_add_us(edge, length);
unsigned long actual, target, d; /*
* delta should never exceed 4 seconds and on m68k
length <<= 8; * ndelay(s64) does not compile; so use s32 rather than s64.
*/
s32 delta;
actual = 0; target = 0; flag = 0; for (;;) {
while (actual < length) { now = ktime_get();
if (flag) { if (ktime_compare(now, target) >= 0)
off(); break;
target += serial_ir.space_width; on();
} else { edge = ktime_add_ns(edge, serial_ir.pulse_width);
on(); delta = ktime_to_ns(ktime_sub(edge, now));
target += serial_ir.pulse_width; if (delta > 0)
} ndelay(delta);
d = (target - actual - now = ktime_get();
IR_SERIAL_TRANSMITTER_LATENCY + 128) >> 8; off();
/* if (ktime_compare(now, target) >= 0)
* Note - we've checked in ioctl that the pulse/space break;
* widths are big enough so that d is > 0 edge = ktime_add_ns(edge, serial_ir.space_width);
*/ delta = ktime_to_ns(ktime_sub(edge, now));
udelay(d); if (delta > 0)
actual += (d << 8) + IR_SERIAL_TRANSMITTER_LATENCY; ndelay(delta);
flag = !flag;
} }
return (actual-length) >> 8;
} }
static long send_pulse_homebrew(unsigned long length) static void send_pulse_homebrew(unsigned int length, ktime_t edge)
{ {
if (length <= 0)
return 0;
if (softcarrier) if (softcarrier)
return send_pulse_homebrew_softcarrier(length); send_pulse_homebrew_softcarrier(length, edge);
else
on(); on();
safe_udelay(length);
return 0;
} }
static void send_space_homebrew(long length) static void send_space_homebrew(void)
{ {
off(); off();
if (length <= 0)
return;
safe_udelay(length);
} }
#endif #endif
...@@ -746,7 +575,8 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf, ...@@ -746,7 +575,8 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
unsigned int count) unsigned int count)
{ {
unsigned long flags; unsigned long flags;
long delta = 0; ktime_t edge;
s64 delta;
int i; int i;
spin_lock_irqsave(&hardware[type].lock, flags); spin_lock_irqsave(&hardware[type].lock, flags);
...@@ -754,11 +584,23 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf, ...@@ -754,11 +584,23 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
/* DTR, RTS down */ /* DTR, RTS down */
on(); on();
} }
edge = ktime_get();
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (i%2) if (i%2)
hardware[type].send_space(txbuf[i] - delta); hardware[type].send_space();
else else
delta = hardware[type].send_pulse(txbuf[i]); 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(); off();
spin_unlock_irqrestore(&hardware[type].lock, flags); spin_unlock_irqrestore(&hardware[type].lock, flags);
...@@ -767,7 +609,8 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf, ...@@ -767,7 +609,8 @@ static int serial_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
static int serial_ir_tx_duty_cycle(struct rc_dev *dev, u32 cycle) static int serial_ir_tx_duty_cycle(struct rc_dev *dev, u32 cycle)
{ {
return init_timing_params(cycle, serial_ir.freq); init_timing_params(cycle, serial_ir.freq);
return 0;
} }
static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier) static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier)
...@@ -775,7 +618,8 @@ static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier) ...@@ -775,7 +618,8 @@ static int serial_ir_tx_carrier(struct rc_dev *dev, u32 carrier)
if (carrier > 500000 || carrier < 20000) if (carrier > 500000 || carrier < 20000)
return -EINVAL; return -EINVAL;
return init_timing_params(serial_ir.duty_cycle, carrier); init_timing_params(serial_ir.duty_cycle, carrier);
return 0;
} }
static int serial_ir_suspend(struct platform_device *dev, static int serial_ir_suspend(struct platform_device *dev,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment