summaryrefslogtreecommitdiff
path: root/drivers/media/cec/core/cec-pin.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/cec/core/cec-pin.c')
-rw-r--r--drivers/media/cec/core/cec-pin.c170
1 files changed, 111 insertions, 59 deletions
diff --git a/drivers/media/cec/core/cec-pin.c b/drivers/media/cec/core/cec-pin.c
index f006bd8eec63..4d7155281daa 100644
--- a/drivers/media/cec/core/cec-pin.c
+++ b/drivers/media/cec/core/cec-pin.c
@@ -4,8 +4,9 @@
*/
#include <linux/delay.h>
-#include <linux/slab.h>
#include <linux/sched/types.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
#include <media/cec-pin.h>
#include "cec-pin-priv.h"
@@ -135,21 +136,48 @@ static void cec_pin_update(struct cec_pin *pin, bool v, bool force)
static bool cec_pin_read(struct cec_pin *pin)
{
- bool v = pin->ops->read(pin->adap);
+ bool v = call_pin_op(pin, read);
cec_pin_update(pin, v, false);
return v;
}
+static void cec_pin_insert_glitch(struct cec_pin *pin, bool rising_edge)
+{
+ /*
+ * Insert a short glitch after the falling or rising edge to
+ * simulate reflections on the CEC line. This can be used to
+ * test deglitch filters, which should be present in CEC devices
+ * to deal with noise on the line.
+ */
+ if (!pin->tx_glitch_high_usecs || !pin->tx_glitch_low_usecs)
+ return;
+ if (rising_edge) {
+ udelay(pin->tx_glitch_high_usecs);
+ call_void_pin_op(pin, low);
+ udelay(pin->tx_glitch_low_usecs);
+ call_void_pin_op(pin, high);
+ } else {
+ udelay(pin->tx_glitch_low_usecs);
+ call_void_pin_op(pin, high);
+ udelay(pin->tx_glitch_high_usecs);
+ call_void_pin_op(pin, low);
+ }
+}
+
static void cec_pin_low(struct cec_pin *pin)
{
- pin->ops->low(pin->adap);
+ call_void_pin_op(pin, low);
+ if (pin->tx_glitch_falling_edge && pin->adap->cec_pin_is_high)
+ cec_pin_insert_glitch(pin, false);
cec_pin_update(pin, false, false);
}
static bool cec_pin_high(struct cec_pin *pin)
{
- pin->ops->high(pin->adap);
+ call_void_pin_op(pin, high);
+ if (pin->tx_glitch_rising_edge && !pin->adap->cec_pin_is_high)
+ cec_pin_insert_glitch(pin, true);
return cec_pin_read(pin);
}
@@ -769,7 +797,7 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts)
* Go to low drive state when the total bit time is
* too short.
*/
- if (delta < CEC_TIM_DATA_BIT_TOTAL_MIN) {
+ if (delta < CEC_TIM_DATA_BIT_TOTAL_MIN && !pin->rx_no_low_drive) {
if (!pin->rx_data_bit_too_short_cnt++) {
pin->rx_data_bit_too_short_ts = ktime_to_ns(pin->ts);
pin->rx_data_bit_too_short_delta = delta;
@@ -854,9 +882,9 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
if (delta > 100 && pin->state != CEC_ST_IDLE) {
/* Keep track of timer overruns */
pin->timer_sum_overrun += delta;
- pin->timer_100ms_overruns++;
+ pin->timer_100us_overruns++;
if (delta > 300)
- pin->timer_300ms_overruns++;
+ pin->timer_300us_overruns++;
if (delta > pin->timer_max_overrun)
pin->timer_max_overrun = delta;
}
@@ -872,19 +900,19 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
if (pin->wait_usecs > 150) {
pin->wait_usecs -= 100;
pin->timer_ts = ktime_add_us(ts, 100);
- hrtimer_forward_now(timer, ns_to_ktime(100000));
+ hrtimer_forward_now(timer, us_to_ktime(100));
return HRTIMER_RESTART;
}
if (pin->wait_usecs > 100) {
pin->wait_usecs /= 2;
pin->timer_ts = ktime_add_us(ts, pin->wait_usecs);
hrtimer_forward_now(timer,
- ns_to_ktime(pin->wait_usecs * 1000));
+ us_to_ktime(pin->wait_usecs));
return HRTIMER_RESTART;
}
pin->timer_ts = ktime_add_us(ts, pin->wait_usecs);
hrtimer_forward_now(timer,
- ns_to_ktime(pin->wait_usecs * 1000));
+ us_to_ktime(pin->wait_usecs));
pin->wait_usecs = 0;
return HRTIMER_RESTART;
}
@@ -957,7 +985,7 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
* so we can kick off the pending transmit.
*/
delta = ktime_us_delta(ts, pin->ts);
- if (delta / CEC_TIM_DATA_BIT_TOTAL >
+ if (delta / CEC_TIM_DATA_BIT_TOTAL >=
pin->tx_signal_free_time) {
pin->tx_nacked = false;
if (tx_custom_start(pin))
@@ -968,7 +996,7 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
cec_pin_low(pin);
break;
}
- if (delta / CEC_TIM_DATA_BIT_TOTAL >
+ if (delta / CEC_TIM_DATA_BIT_TOTAL >=
pin->tx_signal_free_time - 1)
pin->state = CEC_ST_TX_WAIT;
break;
@@ -982,7 +1010,7 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
}
if (pin->state != CEC_ST_IDLE || pin->ops->enable_irq == NULL ||
pin->enable_irq_failed || adap->is_configuring ||
- adap->is_configured || adap->monitor_all_cnt)
+ adap->is_configured || adap->monitor_all_cnt || !adap->monitor_pin_cnt)
break;
/* Switch to interrupt mode */
atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_ENABLE);
@@ -1019,13 +1047,12 @@ static enum hrtimer_restart cec_pin_timer(struct hrtimer *timer)
if (!adap->monitor_pin_cnt || usecs <= 150) {
pin->wait_usecs = 0;
pin->timer_ts = ktime_add_us(ts, usecs);
- hrtimer_forward_now(timer,
- ns_to_ktime(usecs * 1000));
+ hrtimer_forward_now(timer, us_to_ktime(usecs));
return HRTIMER_RESTART;
}
pin->wait_usecs = usecs - 100;
pin->timer_ts = ktime_add_us(ts, 100);
- hrtimer_forward_now(timer, ns_to_ktime(100000));
+ hrtimer_forward_now(timer, us_to_ktime(100));
return HRTIMER_RESTART;
}
@@ -1034,13 +1061,18 @@ static int cec_pin_thread_func(void *_adap)
struct cec_adapter *adap = _adap;
struct cec_pin *pin = adap->pin;
+ pin->enabled_irq = false;
+ pin->enable_irq_failed = false;
for (;;) {
wait_event_interruptible(pin->kthread_waitq,
- kthread_should_stop() ||
- pin->work_rx_msg.len ||
- pin->work_tx_status ||
- atomic_read(&pin->work_irq_change) ||
- atomic_read(&pin->work_pin_num_events));
+ kthread_should_stop() ||
+ pin->work_rx_msg.len ||
+ pin->work_tx_status ||
+ atomic_read(&pin->work_irq_change) ||
+ atomic_read(&pin->work_pin_num_events));
+
+ if (kthread_should_stop())
+ break;
if (pin->work_rx_msg.len) {
struct cec_msg *msg = &pin->work_rx_msg;
@@ -1060,6 +1092,7 @@ static int cec_pin_thread_func(void *_adap)
ns_to_ktime(pin->work_rx_msg.rx_ts));
msg->len = 0;
}
+
if (pin->work_tx_status) {
unsigned int tx_status = pin->work_tx_status;
@@ -1083,26 +1116,41 @@ static int cec_pin_thread_func(void *_adap)
switch (atomic_xchg(&pin->work_irq_change,
CEC_PIN_IRQ_UNCHANGED)) {
case CEC_PIN_IRQ_DISABLE:
- pin->ops->disable_irq(adap);
+ if (pin->enabled_irq) {
+ pin->ops->disable_irq(adap);
+ pin->enabled_irq = false;
+ pin->enable_irq_failed = false;
+ }
cec_pin_high(pin);
+ if (pin->state == CEC_ST_OFF)
+ break;
cec_pin_to_idle(pin);
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
break;
case CEC_PIN_IRQ_ENABLE:
+ if (pin->enabled_irq || !pin->ops->enable_irq ||
+ pin->adap->devnode.unregistered)
+ break;
pin->enable_irq_failed = !pin->ops->enable_irq(adap);
if (pin->enable_irq_failed) {
cec_pin_to_idle(pin);
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
+ } else {
+ pin->enabled_irq = true;
}
break;
default:
break;
}
+ }
- if (kthread_should_stop())
- break;
+ if (pin->enabled_irq) {
+ pin->ops->disable_irq(pin->adap);
+ pin->enabled_irq = false;
+ pin->enable_irq_failed = false;
+ cec_pin_high(pin);
}
return 0;
}
@@ -1111,32 +1159,33 @@ static int cec_pin_adap_enable(struct cec_adapter *adap, bool enable)
{
struct cec_pin *pin = adap->pin;
- pin->enabled = enable;
if (enable) {
- atomic_set(&pin->work_pin_num_events, 0);
- pin->work_pin_events_rd = pin->work_pin_events_wr = 0;
- pin->work_pin_events_dropped = false;
cec_pin_read(pin);
cec_pin_to_idle(pin);
pin->tx_msg.len = 0;
pin->timer_ts = ns_to_ktime(0);
atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_UNCHANGED);
- pin->kthread = kthread_run(cec_pin_thread_func, adap,
- "cec-pin");
- if (IS_ERR(pin->kthread)) {
- pr_err("cec-pin: kernel_thread() failed\n");
- return PTR_ERR(pin->kthread);
+ if (!pin->kthread) {
+ pin->kthread = kthread_run(cec_pin_thread_func, adap,
+ "cec-pin");
+ if (IS_ERR(pin->kthread)) {
+ int err = PTR_ERR(pin->kthread);
+
+ pr_err("cec-pin: kernel_thread() failed\n");
+ pin->kthread = NULL;
+ return err;
+ }
}
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
- } else {
- if (pin->ops->disable_irq)
- pin->ops->disable_irq(adap);
+ } else if (pin->kthread) {
hrtimer_cancel(&pin->timer);
- kthread_stop(pin->kthread);
- cec_pin_read(pin);
+ cec_pin_high(pin);
cec_pin_to_idle(pin);
pin->state = CEC_ST_OFF;
+ pin->work_tx_status = 0;
+ atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_DISABLE);
+ wake_up_interruptible(&pin->kthread_waitq);
}
return 0;
}
@@ -1157,11 +1206,8 @@ void cec_pin_start_timer(struct cec_pin *pin)
if (pin->state != CEC_ST_RX_IRQ)
return;
- atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_UNCHANGED);
- pin->ops->disable_irq(pin->adap);
- cec_pin_high(pin);
- cec_pin_to_idle(pin);
- hrtimer_start(&pin->timer, ns_to_ktime(0), HRTIMER_MODE_REL);
+ atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_DISABLE);
+ wake_up_interruptible(&pin->kthread_waitq);
}
static int cec_pin_adap_transmit(struct cec_adapter *adap, u8 attempts,
@@ -1203,19 +1249,21 @@ static void cec_pin_adap_status(struct cec_adapter *adap,
seq_printf(file, "state: %s\n", states[pin->state].name);
seq_printf(file, "tx_bit: %d\n", pin->tx_bit);
seq_printf(file, "rx_bit: %d\n", pin->rx_bit);
- seq_printf(file, "cec pin: %d\n", pin->ops->read(adap));
+ seq_printf(file, "cec pin: %d\n", call_pin_op(pin, read));
seq_printf(file, "cec pin events dropped: %u\n",
pin->work_pin_events_dropped_cnt);
- seq_printf(file, "irq failed: %d\n", pin->enable_irq_failed);
- if (pin->timer_100ms_overruns) {
- seq_printf(file, "timer overruns > 100ms: %u of %u\n",
- pin->timer_100ms_overruns, pin->timer_cnt);
- seq_printf(file, "timer overruns > 300ms: %u of %u\n",
- pin->timer_300ms_overruns, pin->timer_cnt);
+ if (pin->ops->enable_irq)
+ seq_printf(file, "irq %s\n", pin->enabled_irq ? "enabled" :
+ (pin->enable_irq_failed ? "failed" : "disabled"));
+ if (pin->timer_100us_overruns) {
+ seq_printf(file, "timer overruns > 100us: %u of %u\n",
+ pin->timer_100us_overruns, pin->timer_cnt);
+ seq_printf(file, "timer overruns > 300us: %u of %u\n",
+ pin->timer_300us_overruns, pin->timer_cnt);
seq_printf(file, "max timer overrun: %u usecs\n",
pin->timer_max_overrun);
seq_printf(file, "avg timer overrun: %u usecs\n",
- pin->timer_sum_overrun / pin->timer_100ms_overruns);
+ pin->timer_sum_overrun / pin->timer_100us_overruns);
}
if (pin->rx_start_bit_low_too_short_cnt)
seq_printf(file,
@@ -1245,8 +1293,8 @@ static void cec_pin_adap_status(struct cec_adapter *adap,
seq_printf(file, "tx detected low drive: %u\n", pin->tx_low_drive_cnt);
pin->work_pin_events_dropped_cnt = 0;
pin->timer_cnt = 0;
- pin->timer_100ms_overruns = 0;
- pin->timer_300ms_overruns = 0;
+ pin->timer_100us_overruns = 0;
+ pin->timer_300us_overruns = 0;
pin->timer_max_overrun = 0;
pin->timer_sum_overrun = 0;
pin->rx_start_bit_low_too_short_cnt = 0;
@@ -1256,8 +1304,7 @@ static void cec_pin_adap_status(struct cec_adapter *adap,
pin->rx_data_bit_too_long_cnt = 0;
pin->rx_low_drive_cnt = 0;
pin->tx_low_drive_cnt = 0;
- if (pin->ops->status)
- pin->ops->status(adap, file);
+ call_void_pin_op(pin, status, file);
}
static int cec_pin_adap_monitor_all_enable(struct cec_adapter *adap,
@@ -1273,6 +1320,9 @@ static void cec_pin_adap_free(struct cec_adapter *adap)
{
struct cec_pin *pin = adap->pin;
+ if (pin->kthread)
+ kthread_stop(pin->kthread);
+ pin->kthread = NULL;
if (pin->ops->free)
pin->ops->free(adap);
adap->pin = NULL;
@@ -1283,7 +1333,7 @@ static int cec_pin_received(struct cec_adapter *adap, struct cec_msg *msg)
{
struct cec_pin *pin = adap->pin;
- if (pin->ops->received)
+ if (pin->ops->received && !adap->devnode.unregistered)
return pin->ops->received(adap, msg);
return -ENOMSG;
}
@@ -1294,7 +1344,7 @@ void cec_pin_changed(struct cec_adapter *adap, bool value)
cec_pin_update(pin, value, false);
if (!value && (adap->is_configuring || adap->is_configured ||
- adap->monitor_all_cnt))
+ adap->monitor_all_cnt || !adap->monitor_pin_cnt))
atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_DISABLE);
}
EXPORT_SYMBOL_GPL(cec_pin_changed);
@@ -1322,11 +1372,13 @@ struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops,
if (pin == NULL)
return ERR_PTR(-ENOMEM);
pin->ops = pin_ops;
- hrtimer_init(&pin->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
- pin->timer.function = cec_pin_timer;
+ atomic_set(&pin->work_pin_num_events, 0);
+ hrtimer_setup(&pin->timer, cec_pin_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
init_waitqueue_head(&pin->kthread_waitq);
pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT;
pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT;
+ pin->tx_glitch_low_usecs = CEC_TIM_GLITCH_DEFAULT;
+ pin->tx_glitch_high_usecs = CEC_TIM_GLITCH_DEFAULT;
adap = cec_allocate_adapter(&cec_pin_adap_ops, priv, name,
caps | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN,