Skip to content

Commit 363d0e5

Browse files
seanyoungthierryreding
authored andcommitted
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 <[email protected]> Signed-off-by: Thierry Reding <[email protected]>
1 parent fcc7607 commit 363d0e5

File tree

1 file changed

+78
-5
lines changed

1 file changed

+78
-5
lines changed

drivers/media/rc/pwm-ir-tx.c

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@
1010
#include <linux/slab.h>
1111
#include <linux/of.h>
1212
#include <linux/platform_device.h>
13+
#include <linux/hrtimer.h>
14+
#include <linux/completion.h>
1315
#include <media/rc-core.h>
1416

1517
#define DRIVER_NAME "pwm-ir-tx"
1618
#define DEVICE_NAME "PWM IR Transmitter"
1719

1820
struct pwm_ir {
1921
struct pwm_device *pwm;
20-
unsigned int carrier;
21-
unsigned int duty_cycle;
22+
struct hrtimer timer;
23+
struct completion tx_done;
24+
struct pwm_state *state;
25+
u32 carrier;
26+
u32 duty_cycle;
27+
const unsigned int *txbuf;
28+
unsigned int txbuf_len;
29+
unsigned int txbuf_index;
2230
};
2331

2432
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)
4957
return 0;
5058
}
5159

52-
static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
53-
unsigned int count)
60+
static int pwm_ir_tx_sleep(struct rc_dev *dev, unsigned int *txbuf,
61+
unsigned int count)
5462
{
5563
struct pwm_ir *pwm_ir = dev->priv;
5664
struct pwm_device *pwm = pwm_ir->pwm;
@@ -82,6 +90,62 @@ static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf,
8290
return count;
8391
}
8492

93+
static int pwm_ir_tx_atomic(struct rc_dev *dev, unsigned int *txbuf,
94+
unsigned int count)
95+
{
96+
struct pwm_ir *pwm_ir = dev->priv;
97+
struct pwm_device *pwm = pwm_ir->pwm;
98+
struct pwm_state state;
99+
100+
pwm_init_state(pwm, &state);
101+
102+
state.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, pwm_ir->carrier);
103+
pwm_set_relative_duty_cycle(&state, pwm_ir->duty_cycle, 100);
104+
105+
pwm_ir->txbuf = txbuf;
106+
pwm_ir->txbuf_len = count;
107+
pwm_ir->txbuf_index = 0;
108+
pwm_ir->state = &state;
109+
110+
hrtimer_start(&pwm_ir->timer, 0, HRTIMER_MODE_REL);
111+
112+
wait_for_completion(&pwm_ir->tx_done);
113+
114+
return count;
115+
}
116+
117+
static enum hrtimer_restart pwm_ir_timer(struct hrtimer *timer)
118+
{
119+
struct pwm_ir *pwm_ir = container_of(timer, struct pwm_ir, timer);
120+
ktime_t now;
121+
122+
/*
123+
* If we happen to hit an odd latency spike, loop through the
124+
* pulses until we catch up.
125+
*/
126+
do {
127+
u64 ns;
128+
129+
pwm_ir->state->enabled = !(pwm_ir->txbuf_index % 2);
130+
pwm_apply_atomic(pwm_ir->pwm, pwm_ir->state);
131+
132+
if (pwm_ir->txbuf_index >= pwm_ir->txbuf_len) {
133+
complete(&pwm_ir->tx_done);
134+
135+
return HRTIMER_NORESTART;
136+
}
137+
138+
ns = US_TO_NS(pwm_ir->txbuf[pwm_ir->txbuf_index]);
139+
hrtimer_add_expires_ns(timer, ns);
140+
141+
pwm_ir->txbuf_index++;
142+
143+
now = timer->base->get_time();
144+
} while (hrtimer_get_expires_tv64(timer) < now);
145+
146+
return HRTIMER_RESTART;
147+
}
148+
85149
static int pwm_ir_probe(struct platform_device *pdev)
86150
{
87151
struct pwm_ir *pwm_ir;
@@ -103,10 +167,19 @@ static int pwm_ir_probe(struct platform_device *pdev)
103167
if (!rcdev)
104168
return -ENOMEM;
105169

170+
if (pwm_might_sleep(pwm_ir->pwm)) {
171+
dev_info(&pdev->dev, "TX will not be accurate as PWM device might sleep\n");
172+
rcdev->tx_ir = pwm_ir_tx_sleep;
173+
} else {
174+
init_completion(&pwm_ir->tx_done);
175+
hrtimer_init(&pwm_ir->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
176+
pwm_ir->timer.function = pwm_ir_timer;
177+
rcdev->tx_ir = pwm_ir_tx_atomic;
178+
}
179+
106180
rcdev->priv = pwm_ir;
107181
rcdev->driver_name = DRIVER_NAME;
108182
rcdev->device_name = DEVICE_NAME;
109-
rcdev->tx_ir = pwm_ir_tx;
110183
rcdev->s_tx_duty_cycle = pwm_ir_set_duty_cycle;
111184
rcdev->s_tx_carrier = pwm_ir_set_carrier;
112185

0 commit comments

Comments
 (0)