Commit ea5c8ef2 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab

media: cec-pin: add low-level pin hardware support

Add support for CEC hardware that relies on low-level pin polling or
GPIO interrupts.

One example is the Allwinner SoC. But any GPIO-based CEC implementation can
use this as well.

A GPIO implementation is very suitable as well for debugging: it can use
interrupts to detect state changes and report it. Userspace can then verify
if the bus traffic is correct. This also makes error injection possible.

The disadvantage is that it is hard to get the timings right since linux
isn't a hard realtime system.

In general on an idle system it works quite well, but under load the timer
will miss its mark every so often.

The debugfs file /sys/kernel/debug/cec/cecX/status gives some statistics
with respect to the timer overruns.

When the adapter is unconfigured and the low-level driver supports
interrupts, then the interrupt will be used to detect changes. This should
be quite accurate. But when the adapter is configured a hrtimer has to be
used.

The hrtimer implements a state machine where for each state the code will
read the bus or drive the bus and go on to the next state. It will re-arm
the timer with a delay based on the next state.
Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Reviewed-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent b8d62f50
...@@ -8,6 +8,9 @@ config CEC_CORE ...@@ -8,6 +8,9 @@ config CEC_CORE
config CEC_NOTIFIER config CEC_NOTIFIER
bool bool
config CEC_PIN
bool
menuconfig MEDIA_SUPPORT menuconfig MEDIA_SUPPORT
tristate "Multimedia support" tristate "Multimedia support"
depends on HAS_IOMEM depends on HAS_IOMEM
......
...@@ -4,4 +4,8 @@ ifeq ($(CONFIG_CEC_NOTIFIER),y) ...@@ -4,4 +4,8 @@ ifeq ($(CONFIG_CEC_NOTIFIER),y)
cec-objs += cec-notifier.o cec-objs += cec-notifier.o
endif endif
ifeq ($(CONFIG_CEC_PIN),y)
cec-objs += cec-pin.o
endif
obj-$(CONFIG_CEC_CORE) += cec.o obj-$(CONFIG_CEC_CORE) += cec.o
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/version.h> #include <linux/version.h>
#include <media/cec-pin.h>
#include "cec-priv.h" #include "cec-priv.h"
static inline struct cec_devnode *cec_devnode_data(struct file *filp) static inline struct cec_devnode *cec_devnode_data(struct file *filp)
...@@ -430,6 +431,15 @@ static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh, ...@@ -430,6 +431,15 @@ static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh,
if (mode_follower == CEC_MODE_FOLLOWER) if (mode_follower == CEC_MODE_FOLLOWER)
adap->follower_cnt++; adap->follower_cnt++;
if (mode_follower == CEC_MODE_MONITOR_PIN) { if (mode_follower == CEC_MODE_MONITOR_PIN) {
#ifdef CONFIG_CEC_PIN
struct cec_event ev = {
.flags = CEC_EVENT_FL_INITIAL_STATE,
};
ev.event = adap->pin->cur_value ? CEC_EVENT_PIN_HIGH :
CEC_EVENT_PIN_LOW;
cec_queue_event_fh(fh, &ev, 0);
#endif
adap->monitor_pin_cnt++; adap->monitor_pin_cnt++;
} }
if (mode_follower == CEC_MODE_EXCL_FOLLOWER || if (mode_follower == CEC_MODE_EXCL_FOLLOWER ||
......
This diff is collapsed.
/*
* cec-pin.h - low-level CEC pin control
*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef LINUX_CEC_PIN_H
#define LINUX_CEC_PIN_H
#include <linux/types.h>
#include <linux/atomic.h>
#include <media/cec.h>
enum cec_pin_state {
/* CEC is off */
CEC_ST_OFF,
/* CEC is idle, waiting for Rx or Tx */
CEC_ST_IDLE,
/* Tx states */
/* Pending Tx, waiting for Signal Free Time to expire */
CEC_ST_TX_WAIT,
/* Low-drive was detected, wait for bus to go high */
CEC_ST_TX_WAIT_FOR_HIGH,
/* Drive CEC low for the start bit */
CEC_ST_TX_START_BIT_LOW,
/* Drive CEC high for the start bit */
CEC_ST_TX_START_BIT_HIGH,
/* Drive CEC low for the 0 bit */
CEC_ST_TX_DATA_BIT_0_LOW,
/* Drive CEC high for the 0 bit */
CEC_ST_TX_DATA_BIT_0_HIGH,
/* Drive CEC low for the 1 bit */
CEC_ST_TX_DATA_BIT_1_LOW,
/* Drive CEC high for the 1 bit */
CEC_ST_TX_DATA_BIT_1_HIGH,
/*
* Wait for start of sample time to check for Ack bit or first
* four initiator bits to check for Arbitration Lost.
*/
CEC_ST_TX_DATA_BIT_1_HIGH_PRE_SAMPLE,
/* Wait for end of bit period after sampling */
CEC_ST_TX_DATA_BIT_1_HIGH_POST_SAMPLE,
/* Rx states */
/* Start bit low detected */
CEC_ST_RX_START_BIT_LOW,
/* Start bit high detected */
CEC_ST_RX_START_BIT_HIGH,
/* Wait for bit sample time */
CEC_ST_RX_DATA_SAMPLE,
/* Wait for earliest end of bit period after sampling */
CEC_ST_RX_DATA_POST_SAMPLE,
/* Wait for CEC to go high (i.e. end of bit period */
CEC_ST_RX_DATA_HIGH,
/* Drive CEC low to send 0 Ack bit */
CEC_ST_RX_ACK_LOW,
/* End of 0 Ack time, wait for earliest end of bit period */
CEC_ST_RX_ACK_LOW_POST,
/* Wait for CEC to go high (i.e. end of bit period */
CEC_ST_RX_ACK_HIGH_POST,
/* Wait for earliest end of bit period and end of message */
CEC_ST_RX_ACK_FINISH,
/* Start low drive */
CEC_ST_LOW_DRIVE,
/* Monitor pin using interrupts */
CEC_ST_RX_IRQ,
/* Total number of pin states */
CEC_PIN_STATES
};
/**
* struct cec_pin_ops - low-level CEC pin operations
* @read: read the CEC pin. Return true if high, false if low.
* @low: drive the CEC pin low.
* @high: stop driving the CEC pin. The pull-up will drive the pin
* high, unless someone else is driving the pin low.
* @enable_irq: optional, enable the interrupt to detect pin voltage changes.
* @disable_irq: optional, disable the interrupt.
* @free: optional. Free any allocated resources. Called when the
* adapter is deleted.
* @status: optional, log status information.
*
* These operations are used by the cec pin framework to manipulate
* the CEC pin.
*/
struct cec_pin_ops {
bool (*read)(struct cec_adapter *adap);
void (*low)(struct cec_adapter *adap);
void (*high)(struct cec_adapter *adap);
bool (*enable_irq)(struct cec_adapter *adap);
void (*disable_irq)(struct cec_adapter *adap);
void (*free)(struct cec_adapter *adap);
void (*status)(struct cec_adapter *adap, struct seq_file *file);
};
#define CEC_NUM_PIN_EVENTS 128
struct cec_pin {
struct cec_adapter *adap;
const struct cec_pin_ops *ops;
struct task_struct *kthread;
wait_queue_head_t kthread_waitq;
struct hrtimer timer;
ktime_t ts;
unsigned int wait_usecs;
u16 la_mask;
bool enabled;
bool monitor_all;
bool cur_value;
bool rx_eom;
bool enable_irq_failed;
enum cec_pin_state state;
struct cec_msg tx_msg;
u32 tx_bit;
bool tx_nacked;
u32 tx_signal_free_time;
struct cec_msg rx_msg;
u32 rx_bit;
struct cec_msg work_rx_msg;
u8 work_tx_status;
bool work_enable_irq;
ktime_t work_tx_ts;
atomic_t work_pin_events;
unsigned int work_pin_events_wr;
unsigned int work_pin_events_rd;
ktime_t work_pin_ts[CEC_NUM_PIN_EVENTS];
bool work_pin_is_high[CEC_NUM_PIN_EVENTS];
ktime_t timer_ts;
u32 timer_cnt;
u32 timer_100ms_overruns;
u32 timer_300ms_overruns;
u32 timer_max_overrun;
u32 timer_sum_overrun;
};
/**
* cec_pin_changed() - update pin state from interrupt
*
* @adap: pointer to the cec adapter
* @value: when true the pin is high, otherwise it is low
*
* If changes of the CEC voltage are detected via an interrupt, then
* cec_pin_changed is called from the interrupt with the new value.
*/
void cec_pin_changed(struct cec_adapter *adap, bool value);
/**
* cec_pin_allocate_adapter() - allocate a pin-based cec adapter
*
* @pin_ops: low-level pin operations
* @priv: will be stored in adap->priv and can be used by the adapter ops.
* Use cec_get_drvdata(adap) to get the priv pointer.
* @name: the name of the CEC adapter. Note: this name will be copied.
* @caps: capabilities of the CEC adapter. This will be ORed with
* CEC_CAP_MONITOR_ALL and CEC_CAP_MONITOR_PIN.
*
* Allocate a cec adapter using the cec pin framework.
*
* Return: a pointer to the cec adapter or an error pointer
*/
struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops,
void *priv, const char *name, u32 caps);
#endif
...@@ -61,6 +61,7 @@ struct cec_devnode { ...@@ -61,6 +61,7 @@ struct cec_devnode {
struct cec_adapter; struct cec_adapter;
struct cec_data; struct cec_data;
struct cec_pin;
struct cec_data { struct cec_data {
struct list_head list; struct list_head list;
...@@ -189,6 +190,9 @@ struct cec_adapter { ...@@ -189,6 +190,9 @@ struct cec_adapter {
#ifdef CONFIG_CEC_NOTIFIER #ifdef CONFIG_CEC_NOTIFIER
struct cec_notifier *notifier; struct cec_notifier *notifier;
#endif #endif
#ifdef CONFIG_CEC_PIN
struct cec_pin *pin;
#endif
struct dentry *cec_dir; struct dentry *cec_dir;
struct dentry *status_file; struct dentry *status_file;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment