mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-25 16:31:13 +00:00
141 lines
3.8 KiB
Diff
141 lines
3.8 KiB
Diff
|
From 1db50b3b0222f6f9d89f30bfa8fd64457bd01bef Mon Sep 17 00:00:00 2001
|
||
|
From: Sean Young <sean@mess.org>
|
||
|
Date: Tue, 19 Dec 2023 16:30:29 +0000
|
||
|
Subject: [PATCH 0942/1085] media: pwm-ir-tx: Trigger edges from hrtimer
|
||
|
interrupt context
|
||
|
|
||
|
commit 363d0e56285e80cda997d41d94c22313b673557d upstream.
|
||
|
|
||
|
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>
|
||
|
---
|
||
|
drivers/media/rc/pwm-ir-tx.c | 83 +++++++++++++++++++++++++++++++++---
|
||
|
1 file changed, 78 insertions(+), 5 deletions(-)
|
||
|
|
||
|
--- a/drivers/media/rc/pwm-ir-tx.c
|
||
|
+++ b/drivers/media/rc/pwm-ir-tx.c
|
||
|
@@ -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[] = {
|
||
|
@@ -48,8 +56,8 @@ static int pwm_ir_set_carrier(struct rc_
|
||
|
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;
|
||
|
@@ -81,6 +89,62 @@ static int pwm_ir_tx(struct rc_dev *dev,
|
||
|
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;
|
||
|
@@ -102,10 +166,19 @@ static int pwm_ir_probe(struct platform_
|
||
|
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;
|
||
|
|