Skip to content

Commit

Permalink
media: pwm-ir-tx: Trigger edges from hrtimer interrupt context
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
seanyoung authored and thierryreding committed Dec 20, 2023
1 parent fcc7607 commit 363d0e5
Showing 1 changed file with 78 additions and 5 deletions.
83 changes: 78 additions & 5 deletions drivers/media/rc/pwm-ir-tx.c
Expand Up @@ -10,15 +10,23 @@
#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"
#define DEVICE_NAME "PWM IR Transmitter"

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[] = {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down

0 comments on commit 363d0e5

Please sign in to comment.