media: pwm-ir-tx: Trigger edges from hrtimer interrupt context
This makes the generated IR much more precise. Before this change, the driver is unreliable and many users opted to use gpio-ir-tx instead. Signed-off-by: Sean Young <sean@mess.org> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
This commit is contained in:
parent
fcc7607293
commit
363d0e5628
@ -10,6 +10,8 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/completion.h>
|
||||
#include <media/rc-core.h>
|
||||
|
||||
#define DRIVER_NAME "pwm-ir-tx"
|
||||
@ -17,8 +19,14 @@
|
||||
|
||||
struct pwm_ir {
|
||||
struct pwm_device *pwm;
|
||||
unsigned int carrier;
|
||||
unsigned int duty_cycle;
|
||||
struct hrtimer timer;
|
||||
struct completion tx_done;
|
||||
struct pwm_state *state;
|
||||
u32 carrier;
|
||||
u32 duty_cycle;
|
||||
const unsigned int *txbuf;
|
||||
unsigned int txbuf_len;
|
||||
unsigned int txbuf_index;
|
||||
};
|
||||
|
||||
static const struct of_device_id pwm_ir_of_match[] = {
|
||||
@ -49,8 +57,8 @@ static int pwm_ir_set_carrier(struct rc_dev *dev, u32 carrier)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
|
||||
unsigned int count)
|
||||
static int pwm_ir_tx_sleep(struct rc_dev *dev, unsigned int *txbuf,
|
||||
unsigned int count)
|
||||
{
|
||||
struct pwm_ir *pwm_ir = dev->priv;
|
||||
struct pwm_device *pwm = pwm_ir->pwm;
|
||||
@ -82,6 +90,62 @@ static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
|
||||
return count;
|
||||
}
|
||||
|
||||
static int pwm_ir_tx_atomic(struct rc_dev *dev, unsigned int *txbuf,
|
||||
unsigned int count)
|
||||
{
|
||||
struct pwm_ir *pwm_ir = dev->priv;
|
||||
struct pwm_device *pwm = pwm_ir->pwm;
|
||||
struct pwm_state state;
|
||||
|
||||
pwm_init_state(pwm, &state);
|
||||
|
||||
state.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, pwm_ir->carrier);
|
||||
pwm_set_relative_duty_cycle(&state, pwm_ir->duty_cycle, 100);
|
||||
|
||||
pwm_ir->txbuf = txbuf;
|
||||
pwm_ir->txbuf_len = count;
|
||||
pwm_ir->txbuf_index = 0;
|
||||
pwm_ir->state = &state;
|
||||
|
||||
hrtimer_start(&pwm_ir->timer, 0, HRTIMER_MODE_REL);
|
||||
|
||||
wait_for_completion(&pwm_ir->tx_done);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static enum hrtimer_restart pwm_ir_timer(struct hrtimer *timer)
|
||||
{
|
||||
struct pwm_ir *pwm_ir = container_of(timer, struct pwm_ir, timer);
|
||||
ktime_t now;
|
||||
|
||||
/*
|
||||
* If we happen to hit an odd latency spike, loop through the
|
||||
* pulses until we catch up.
|
||||
*/
|
||||
do {
|
||||
u64 ns;
|
||||
|
||||
pwm_ir->state->enabled = !(pwm_ir->txbuf_index % 2);
|
||||
pwm_apply_atomic(pwm_ir->pwm, pwm_ir->state);
|
||||
|
||||
if (pwm_ir->txbuf_index >= pwm_ir->txbuf_len) {
|
||||
complete(&pwm_ir->tx_done);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
ns = US_TO_NS(pwm_ir->txbuf[pwm_ir->txbuf_index]);
|
||||
hrtimer_add_expires_ns(timer, ns);
|
||||
|
||||
pwm_ir->txbuf_index++;
|
||||
|
||||
now = timer->base->get_time();
|
||||
} while (hrtimer_get_expires_tv64(timer) < now);
|
||||
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static int pwm_ir_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pwm_ir *pwm_ir;
|
||||
@ -103,10 +167,19 @@ static int pwm_ir_probe(struct platform_device *pdev)
|
||||
if (!rcdev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (pwm_might_sleep(pwm_ir->pwm)) {
|
||||
dev_info(&pdev->dev, "TX will not be accurate as PWM device might sleep\n");
|
||||
rcdev->tx_ir = pwm_ir_tx_sleep;
|
||||
} else {
|
||||
init_completion(&pwm_ir->tx_done);
|
||||
hrtimer_init(&pwm_ir->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
pwm_ir->timer.function = pwm_ir_timer;
|
||||
rcdev->tx_ir = pwm_ir_tx_atomic;
|
||||
}
|
||||
|
||||
rcdev->priv = pwm_ir;
|
||||
rcdev->driver_name = DRIVER_NAME;
|
||||
rcdev->device_name = DEVICE_NAME;
|
||||
rcdev->tx_ir = pwm_ir_tx;
|
||||
rcdev->s_tx_duty_cycle = pwm_ir_set_duty_cycle;
|
||||
rcdev->s_tx_carrier = pwm_ir_set_carrier;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user