summaryrefslogtreecommitdiffstats
path: root/drivers/media/cec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/cec')
-rw-r--r--drivers/media/cec/cec-adap.c141
-rw-r--r--drivers/media/cec/cec-api.c8
-rw-r--r--drivers/media/cec/cec-core.c8
-rw-r--r--drivers/media/cec/cec-notifier.c112
-rw-r--r--drivers/media/cec/cec-priv.h5
5 files changed, 216 insertions, 58 deletions
diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c
index f1261cc2b6fa..451c61bde4d4 100644
--- a/drivers/media/cec/cec-adap.c
+++ b/drivers/media/cec/cec-adap.c
@@ -16,7 +16,10 @@
#include <linux/string.h>
#include <linux/types.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_device.h>
#include <drm/drm_edid.h>
+#include <drm/drm_file.h>
#include "cec-priv.h"
@@ -75,6 +78,16 @@ u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size,
}
EXPORT_SYMBOL_GPL(cec_get_edid_phys_addr);
+void cec_fill_conn_info_from_drm(struct cec_connector_info *conn_info,
+ const struct drm_connector *connector)
+{
+ memset(conn_info, 0, sizeof(*conn_info));
+ conn_info->type = CEC_CONNECTOR_TYPE_DRM;
+ conn_info->drm.card_no = connector->dev->primary->index;
+ conn_info->drm.connector_id = connector->base.id;
+}
+EXPORT_SYMBOL_GPL(cec_fill_conn_info_from_drm);
+
/*
* Queue a new event for this filehandle. If ts == 0, then set it
* to the current time.
@@ -720,6 +733,7 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
struct cec_fh *fh, bool block)
{
struct cec_data *data;
+ bool is_raw = msg_is_raw(msg);
msg->rx_ts = 0;
msg->tx_ts = 0;
@@ -735,15 +749,10 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
/* Make sure the timeout isn't 0. */
msg->timeout = 1000;
}
- if (msg->timeout)
- msg->flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS;
- else
- msg->flags = 0;
+ msg->flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS | CEC_MSG_FL_RAW;
- if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) {
- msg->msg[2] = adap->phys_addr >> 8;
- msg->msg[3] = adap->phys_addr & 0xff;
- }
+ if (!msg->timeout)
+ msg->flags &= ~CEC_MSG_FL_REPLY_TO_FOLLOWERS;
/* Sanity checks */
if (msg->len == 0 || msg->len > CEC_MAX_MSG_SIZE) {
@@ -765,44 +774,80 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
dprintk(1, "%s: can't reply to poll msg\n", __func__);
return -EINVAL;
}
- if (msg->len == 1) {
- if (cec_msg_destination(msg) == 0xf) {
- dprintk(1, "%s: invalid poll message\n", __func__);
+
+ if (is_raw) {
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ } else {
+ /* A CDC-Only device can only send CDC messages */
+ if ((adap->log_addrs.flags & CEC_LOG_ADDRS_FL_CDC_ONLY) &&
+ (msg->len == 1 || msg->msg[1] != CEC_MSG_CDC_MESSAGE)) {
+ dprintk(1, "%s: not a CDC message\n", __func__);
return -EINVAL;
}
- if (cec_has_log_addr(adap, cec_msg_destination(msg))) {
- /*
- * If the destination is a logical address our adapter
- * has already claimed, then just NACK this.
- * It depends on the hardware what it will do with a
- * POLL to itself (some OK this), so it is just as
- * easy to handle it here so the behavior will be
- * consistent.
- */
- msg->tx_ts = ktime_get_ns();
- msg->tx_status = CEC_TX_STATUS_NACK |
- CEC_TX_STATUS_MAX_RETRIES;
- msg->tx_nack_cnt = 1;
- msg->sequence = ++adap->sequence;
- if (!msg->sequence)
+
+ if (msg->len >= 4 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) {
+ msg->msg[2] = adap->phys_addr >> 8;
+ msg->msg[3] = adap->phys_addr & 0xff;
+ }
+
+ if (msg->len == 1) {
+ if (cec_msg_destination(msg) == 0xf) {
+ dprintk(1, "%s: invalid poll message\n",
+ __func__);
+ return -EINVAL;
+ }
+ if (cec_has_log_addr(adap, cec_msg_destination(msg))) {
+ /*
+ * If the destination is a logical address our
+ * adapter has already claimed, then just NACK
+ * this. It depends on the hardware what it will
+ * do with a POLL to itself (some OK this), so
+ * it is just as easy to handle it here so the
+ * behavior will be consistent.
+ */
+ msg->tx_ts = ktime_get_ns();
+ msg->tx_status = CEC_TX_STATUS_NACK |
+ CEC_TX_STATUS_MAX_RETRIES;
+ msg->tx_nack_cnt = 1;
msg->sequence = ++adap->sequence;
- return 0;
+ if (!msg->sequence)
+ msg->sequence = ++adap->sequence;
+ return 0;
+ }
+ }
+ if (msg->len > 1 && !cec_msg_is_broadcast(msg) &&
+ cec_has_log_addr(adap, cec_msg_destination(msg))) {
+ dprintk(1, "%s: destination is the adapter itself\n",
+ __func__);
+ return -EINVAL;
+ }
+ if (msg->len > 1 && adap->is_configured &&
+ !cec_has_log_addr(adap, cec_msg_initiator(msg))) {
+ dprintk(1, "%s: initiator has unknown logical address %d\n",
+ __func__, cec_msg_initiator(msg));
+ return -EINVAL;
+ }
+ /*
+ * Special case: allow Ping and IMAGE/TEXT_VIEW_ON to be
+ * transmitted to a TV, even if the adapter is unconfigured.
+ * This makes it possible to detect or wake up displays that
+ * pull down the HPD when in standby.
+ */
+ if (!adap->is_configured && !adap->is_configuring &&
+ (msg->len > 2 ||
+ cec_msg_destination(msg) != CEC_LOG_ADDR_TV ||
+ (msg->len == 2 && msg->msg[1] != CEC_MSG_IMAGE_VIEW_ON &&
+ msg->msg[1] != CEC_MSG_TEXT_VIEW_ON))) {
+ dprintk(1, "%s: adapter is unconfigured\n", __func__);
+ return -ENONET;
}
}
- if (msg->len > 1 && !cec_msg_is_broadcast(msg) &&
- cec_has_log_addr(adap, cec_msg_destination(msg))) {
- dprintk(1, "%s: destination is the adapter itself\n", __func__);
- return -EINVAL;
- }
- if (msg->len > 1 && adap->is_configured &&
- !cec_has_log_addr(adap, cec_msg_initiator(msg))) {
- dprintk(1, "%s: initiator has unknown logical address %d\n",
- __func__, cec_msg_initiator(msg));
- return -EINVAL;
- }
+
if (!adap->is_configured && !adap->is_configuring) {
- if (adap->needs_hpd || msg->msg[0] != 0xf0) {
- dprintk(1, "%s: adapter is unconfigured\n", __func__);
+ if (adap->needs_hpd) {
+ dprintk(1, "%s: adapter is unconfigured and needs HPD\n",
+ __func__);
return -ENONET;
}
if (msg->reply) {
@@ -1566,6 +1611,22 @@ void cec_s_phys_addr_from_edid(struct cec_adapter *adap,
}
EXPORT_SYMBOL_GPL(cec_s_phys_addr_from_edid);
+void cec_s_conn_info(struct cec_adapter *adap,
+ const struct cec_connector_info *conn_info)
+{
+ if (!(adap->capabilities & CEC_CAP_CONNECTOR_INFO))
+ return;
+
+ mutex_lock(&adap->lock);
+ if (conn_info)
+ adap->conn_info = *conn_info;
+ else
+ memset(&adap->conn_info, 0, sizeof(adap->conn_info));
+ cec_post_state_event(adap);
+ mutex_unlock(&adap->lock);
+}
+EXPORT_SYMBOL_GPL(cec_s_conn_info);
+
/*
* Called from either the ioctl or a driver to set the logical addresses.
*
diff --git a/drivers/media/cec/cec-api.c b/drivers/media/cec/cec-api.c
index 156a0d76ab2a..12d676484472 100644
--- a/drivers/media/cec/cec-api.c
+++ b/drivers/media/cec/cec-api.c
@@ -198,19 +198,11 @@ static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh,
if (copy_from_user(&msg, parg, sizeof(msg)))
return -EFAULT;
- /* A CDC-Only device can only send CDC messages */
- if ((adap->log_addrs.flags & CEC_LOG_ADDRS_FL_CDC_ONLY) &&
- (msg.len == 1 || msg.msg[1] != CEC_MSG_CDC_MESSAGE))
- return -EINVAL;
-
mutex_lock(&adap->lock);
if (adap->log_addrs.num_log_addrs == 0)
err = -EPERM;
else if (adap->is_configuring)
err = -ENONET;
- else if (!adap->is_configured &&
- (adap->needs_hpd || msg.msg[0] != 0xf0))
- err = -ENONET;
else if (cec_is_busy(adap, fh))
err = -EBUSY;
else
diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c
index f5d1578e256a..9c610e1e99b8 100644
--- a/drivers/media/cec/cec-core.c
+++ b/drivers/media/cec/cec-core.c
@@ -128,13 +128,14 @@ static int __must_check cec_devnode_register(struct cec_devnode *devnode,
devnode->cdev.owner = owner;
kobject_set_name(&devnode->cdev.kobj, "cec%d", devnode->minor);
+ devnode->registered = true;
ret = cdev_device_add(&devnode->cdev, &devnode->dev);
if (ret) {
+ devnode->registered = false;
pr_err("%s: cdev_device_add failed\n", __func__);
goto clr_bit;
}
- devnode->registered = true;
return 0;
clr_bit:
@@ -256,6 +257,11 @@ struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops,
struct cec_adapter *adap;
int res;
+ /*
+ * Disable this capability until the connector info public API
+ * is ready.
+ */
+ caps &= ~CEC_CAP_CONNECTOR_INFO;
#ifndef CONFIG_MEDIA_CEC_RC
caps &= ~CEC_CAP_RC;
#endif
diff --git a/drivers/media/cec/cec-notifier.c b/drivers/media/cec/cec-notifier.c
index 9598c7778871..52a867bde15f 100644
--- a/drivers/media/cec/cec-notifier.c
+++ b/drivers/media/cec/cec-notifier.c
@@ -21,8 +21,9 @@ struct cec_notifier {
struct mutex lock;
struct list_head head;
struct kref kref;
- struct device *dev;
- const char *conn;
+ struct device *hdmi_dev;
+ struct cec_connector_info conn_info;
+ const char *conn_name;
struct cec_adapter *cec_adap;
void (*callback)(struct cec_adapter *adap, u16 pa);
@@ -32,14 +33,16 @@ struct cec_notifier {
static LIST_HEAD(cec_notifiers);
static DEFINE_MUTEX(cec_notifiers_lock);
-struct cec_notifier *cec_notifier_get_conn(struct device *dev, const char *conn)
+struct cec_notifier *
+cec_notifier_get_conn(struct device *hdmi_dev, const char *conn_name)
{
struct cec_notifier *n;
mutex_lock(&cec_notifiers_lock);
list_for_each_entry(n, &cec_notifiers, head) {
- if (n->dev == dev &&
- (!conn || !strcmp(n->conn, conn))) {
+ if (n->hdmi_dev == hdmi_dev &&
+ (!conn_name ||
+ (n->conn_name && !strcmp(n->conn_name, conn_name)))) {
kref_get(&n->kref);
mutex_unlock(&cec_notifiers_lock);
return n;
@@ -48,10 +51,17 @@ struct cec_notifier *cec_notifier_get_conn(struct device *dev, const char *conn)
n = kzalloc(sizeof(*n), GFP_KERNEL);
if (!n)
goto unlock;
- n->dev = dev;
- if (conn)
- n->conn = kstrdup(conn, GFP_KERNEL);
+ n->hdmi_dev = hdmi_dev;
+ if (conn_name) {
+ n->conn_name = kstrdup(conn_name, GFP_KERNEL);
+ if (!n->conn_name) {
+ kfree(n);
+ n = NULL;
+ goto unlock;
+ }
+ }
n->phys_addr = CEC_PHYS_ADDR_INVALID;
+
mutex_init(&n->lock);
kref_init(&n->kref);
list_add_tail(&n->head, &cec_notifiers);
@@ -67,7 +77,7 @@ static void cec_notifier_release(struct kref *kref)
container_of(kref, struct cec_notifier, kref);
list_del(&n->head);
- kfree(n->conn);
+ kfree(n->conn_name);
kfree(n);
}
@@ -79,6 +89,84 @@ void cec_notifier_put(struct cec_notifier *n)
}
EXPORT_SYMBOL_GPL(cec_notifier_put);
+struct cec_notifier *
+cec_notifier_conn_register(struct device *hdmi_dev, const char *conn_name,
+ const struct cec_connector_info *conn_info)
+{
+ struct cec_notifier *n = cec_notifier_get_conn(hdmi_dev, conn_name);
+
+ if (!n)
+ return n;
+
+ mutex_lock(&n->lock);
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+ if (conn_info)
+ n->conn_info = *conn_info;
+ else
+ memset(&n->conn_info, 0, sizeof(n->conn_info));
+ if (n->cec_adap) {
+ cec_phys_addr_invalidate(n->cec_adap);
+ cec_s_conn_info(n->cec_adap, conn_info);
+ }
+ mutex_unlock(&n->lock);
+ return n;
+}
+EXPORT_SYMBOL_GPL(cec_notifier_conn_register);
+
+void cec_notifier_conn_unregister(struct cec_notifier *n)
+{
+ if (!n)
+ return;
+
+ mutex_lock(&n->lock);
+ memset(&n->conn_info, 0, sizeof(n->conn_info));
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+ if (n->cec_adap) {
+ cec_phys_addr_invalidate(n->cec_adap);
+ cec_s_conn_info(n->cec_adap, NULL);
+ }
+ mutex_unlock(&n->lock);
+ cec_notifier_put(n);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_conn_unregister);
+
+struct cec_notifier *
+cec_notifier_cec_adap_register(struct device *hdmi_dev, const char *conn_name,
+ struct cec_adapter *adap)
+{
+ struct cec_notifier *n;
+
+ if (WARN_ON(!adap))
+ return NULL;
+
+ n = cec_notifier_get_conn(hdmi_dev, conn_name);
+ if (!n)
+ return n;
+
+ mutex_lock(&n->lock);
+ n->cec_adap = adap;
+ adap->conn_info = n->conn_info;
+ adap->notifier = n;
+ cec_s_phys_addr(adap, n->phys_addr, false);
+ mutex_unlock(&n->lock);
+ return n;
+}
+EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_register);
+
+void cec_notifier_cec_adap_unregister(struct cec_notifier *n)
+{
+ if (!n)
+ return;
+
+ mutex_lock(&n->lock);
+ n->cec_adap->notifier = NULL;
+ n->cec_adap = NULL;
+ n->callback = NULL;
+ mutex_unlock(&n->lock);
+ cec_notifier_put(n);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_unregister);
+
void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)
{
if (n == NULL)
@@ -88,6 +176,8 @@ void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)
n->phys_addr = pa;
if (n->callback)
n->callback(n->cec_adap, n->phys_addr);
+ else if (n->cec_adap)
+ cec_s_phys_addr(n->cec_adap, n->phys_addr, false);
mutex_unlock(&n->lock);
}
EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr);
@@ -122,6 +212,10 @@ EXPORT_SYMBOL_GPL(cec_notifier_register);
void cec_notifier_unregister(struct cec_notifier *n)
{
+ /* Do nothing unless cec_notifier_register was called first */
+ if (!n->callback)
+ return;
+
mutex_lock(&n->lock);
n->callback = NULL;
mutex_unlock(&n->lock);
diff --git a/drivers/media/cec/cec-priv.h b/drivers/media/cec/cec-priv.h
index 804e38f849c7..7bdf855aaecd 100644
--- a/drivers/media/cec/cec-priv.h
+++ b/drivers/media/cec/cec-priv.h
@@ -20,6 +20,11 @@
/* devnode to cec_adapter */
#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode)
+static inline bool msg_is_raw(const struct cec_msg *msg)
+{
+ return msg->flags & CEC_MSG_FL_RAW;
+}
+
/* cec-core.c */
extern int cec_debug;
int cec_get_device(struct cec_devnode *devnode);