summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2020-03-15 10:05:20 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2020-03-15 10:05:20 -0700
commit24f96f1dc9ce12ded962e1724f1f2223ac840975 (patch)
treed8be0aab6953e76bbd53ef875238a72167c59e9a
parentd672d9676bfcac9bd0127e964c460edef1ab62fa (diff)
parentfd06f35834d58041efd3b804ba33767dd808f188 (diff)
Merge "mdss: msm: hdmi: fix CEC broadcast loopback issue"
-rw-r--r--drivers/video/fbdev/msm/mdss_cec_core.c40
-rw-r--r--drivers/video/fbdev/msm/mdss_cec_core.h3
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_cec.c196
3 files changed, 181 insertions, 58 deletions
diff --git a/drivers/video/fbdev/msm/mdss_cec_core.c b/drivers/video/fbdev/msm/mdss_cec_core.c
index 1d9950494d65..ab7a8056016a 100644
--- a/drivers/video/fbdev/msm/mdss_cec_core.c
+++ b/drivers/video/fbdev/msm/mdss_cec_core.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2017, 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -594,6 +594,41 @@ end:
return ret;
}
+static ssize_t cec_wta_clear_logical_addr(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int clear_flag;
+ unsigned long flags;
+ ssize_t ret;
+ struct cec_ctl *ctl = cec_get_ctl(dev);
+ struct cec_ops *ops;
+
+ if (!ctl) {
+ pr_err("Invalid ctl\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ops = ctl->init_data.ops;
+
+ ret = kstrtoint(buf, 10, &clear_flag);
+ if (ret) {
+ pr_err("kstrtoint failed\n");
+ goto end;
+ }
+
+ ret = count;
+
+ spin_lock_irqsave(&ctl->lock, flags);
+ if (ctl->enabled) {
+ if (ops && ops->clear_logical_addr)
+ ops->clear_logical_addr(ops->data, !!clear_flag);
+ }
+ spin_unlock_irqrestore(&ctl->lock, flags);
+end:
+ return ret;
+}
+
static ssize_t cec_rda_msg(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -703,6 +738,8 @@ static DEVICE_ATTR(enable_compliance, S_IRUGO | S_IWUSR,
cec_rda_enable_compliance, cec_wta_enable_compliance);
static DEVICE_ATTR(logical_addr, S_IRUSR | S_IWUSR,
cec_rda_logical_addr, cec_wta_logical_addr);
+static DEVICE_ATTR(clear_logical_addr, 0200,
+ NULL, cec_wta_clear_logical_addr);
static DEVICE_ATTR(rd_msg, S_IRUGO, cec_rda_msg, NULL);
static DEVICE_ATTR(wr_msg, S_IWUSR | S_IRUSR, NULL, cec_wta_msg);
@@ -710,6 +747,7 @@ static struct attribute *cec_fs_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_enable_compliance.attr,
&dev_attr_logical_addr.attr,
+ &dev_attr_clear_logical_addr.attr,
&dev_attr_rd_msg.attr,
&dev_attr_wr_msg.attr,
NULL,
diff --git a/drivers/video/fbdev/msm/mdss_cec_core.h b/drivers/video/fbdev/msm/mdss_cec_core.h
index 12b7677c5dee..481884a732d8 100644
--- a/drivers/video/fbdev/msm/mdss_cec_core.h
+++ b/drivers/video/fbdev/msm/mdss_cec_core.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2016, 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -67,6 +67,7 @@ struct cec_ops {
int (*send_msg)(void *data,
struct cec_msg *msg);
void (*wt_logical_addr)(void *data, u8 addr);
+ void (*clear_logical_addr)(void *data, bool flag);
void (*wakeup_en)(void *data, bool en);
bool (*is_wakeup_en)(void *data);
void (*device_suspend)(void *data, bool suspend);
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_cec.c b/drivers/video/fbdev/msm/mdss_hdmi_cec.c
index a4ed01210e04..3eda29edff4d 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_cec.c
+++ b/drivers/video/fbdev/msm/mdss_hdmi_cec.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2017, 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/input.h>
+#include <linux/circ_buf.h>
#include "mdss_hdmi_cec.h"
#include "mdss_panel.h"
@@ -33,12 +34,19 @@
#define CEC_OP_KEY_PRESS 0x44
#define CEC_OP_STANDBY 0x36
+#define CEC_RECV_Q_SIZE 4
+#define CEC_RECV_Q_MASK 3
+
struct hdmi_cec_ctrl {
bool cec_enabled;
bool cec_wakeup_en;
bool cec_device_suspend;
-
+ bool cec_clear_logical_addr;
+ u32 cec_logical_addr;
u32 cec_msg_wr_status;
+ struct cec_msg recv_msg[CEC_RECV_Q_SIZE];
+ u32 head;
+ u32 tail;
spinlock_t lock;
struct work_struct cec_read_work;
struct completion cec_msg_wr_done;
@@ -60,6 +68,20 @@ static int hdmi_cec_msg_send(void *data, struct cec_msg *msg)
return -EINVAL;
}
+ if (msg->sender_id != cec_ctrl->cec_logical_addr &&
+ msg->recvr_id == 0xF) {
+ /*
+ * If the current logical address is not the
+ * same as the sender_id and if the message is
+ * broadcasting, the message is looping back.
+ * Abort the message sending in that case
+ */
+ DEV_ERR("%s: abort potential MAL msg %d:%d logical %d\n",
+ __func__, msg->sender_id, msg->recvr_id,
+ cec_ctrl->cec_logical_addr);
+ return -EINVAL;
+ }
+
io = cec_ctrl->init_data.io;
reinit_completion(&cec_ctrl->cec_msg_wr_done);
@@ -164,88 +186,130 @@ static void hdmi_cec_deinit_input_event(struct hdmi_cec_ctrl *cec_ctrl)
cec_ctrl->input = NULL;
}
-static void hdmi_cec_msg_recv(struct work_struct *work)
+static int hdmi_cec_msg_read(struct hdmi_cec_ctrl *cec_ctrl)
{
- int i;
- u32 data;
- struct hdmi_cec_ctrl *cec_ctrl = NULL;
struct dss_io_data *io = NULL;
- struct cec_msg msg;
- struct cec_cbs *cbs;
+ struct cec_msg *msg;
+ u32 data;
+ int i;
+ u32 head;
+ u32 tail;
- cec_ctrl = container_of(work, struct hdmi_cec_ctrl, cec_read_work);
if (!cec_ctrl || !cec_ctrl->init_data.io) {
DEV_ERR("%s: invalid input\n", __func__);
- return;
+ return -EINVAL;
}
if (!cec_ctrl->cec_enabled) {
DEV_ERR("%s: cec not enabled\n", __func__);
- return;
+ return -ENODEV;
}
- io = cec_ctrl->init_data.io;
- cbs = cec_ctrl->init_data.cbs;
+ head = cec_ctrl->head;
+ tail = READ_ONCE(cec_ctrl->tail);
+ if (CIRC_SPACE(head, tail, CEC_RECV_Q_SIZE) < 1) {
+ DEV_ERR("%s: no more space to hold the buffer\n", __func__);
+ return 0; /* Let's just kick the thread */
+ }
- data = DSS_REG_R(io, HDMI_CEC_RD_DATA);
+ msg = &cec_ctrl->recv_msg[head];
- msg.recvr_id = (data & 0x000F);
- msg.sender_id = (data & 0x00F0) >> 4;
- msg.frame_size = (data & 0x1F00) >> 8;
- DEV_DBG("%s: Recvd init=[%u] dest=[%u] size=[%u]\n", __func__,
- msg.sender_id, msg.recvr_id,
- msg.frame_size);
+ io = cec_ctrl->init_data.io;
+ data = DSS_REG_R(io, HDMI_CEC_RD_DATA);
- if (msg.frame_size < 1 || msg.frame_size > MAX_CEC_FRAME_SIZE) {
+ msg->recvr_id = (data & 0x000F);
+ msg->sender_id = (data & 0x00F0) >> 4;
+ msg->frame_size = (data & 0x1F00) >> 8;
+ if (msg->frame_size < 1 || msg->frame_size > MAX_CEC_FRAME_SIZE) {
DEV_ERR("%s: invalid message (frame length = %d)\n",
- __func__, msg.frame_size);
- return;
- } else if (msg.frame_size == 1) {
+ __func__, msg->frame_size);
+ return -EINVAL;
+ } else if (msg->frame_size == 1) {
DEV_DBG("%s: polling message (dest[%x] <- init[%x])\n",
- __func__, msg.recvr_id, msg.sender_id);
- return;
+ __func__, msg->recvr_id, msg->sender_id);
+ return -EINVAL;
}
/* data block 0 : opcode */
data = DSS_REG_R_ND(io, HDMI_CEC_RD_DATA);
- msg.opcode = data & 0xFF;
+ msg->opcode = data & 0xFF;
/* data block 1-14 : operand 0-13 */
- for (i = 0; i < msg.frame_size - 2; i++) {
+ for (i = 0; i < msg->frame_size - 2; i++) {
data = DSS_REG_R_ND(io, HDMI_CEC_RD_DATA);
- msg.operand[i] = data & 0xFF;
+ msg->operand[i] = data & 0xFF;
}
for (; i < MAX_OPERAND_SIZE; i++)
- msg.operand[i] = 0;
+ msg->operand[i] = 0;
+
+ /*
+ * Clearing the logical address is used when the system doesn't
+ * need to process CEC command any more.
+ */
+ if (cec_ctrl->cec_clear_logical_addr)
+ return -EINVAL;
+
+ /* Update head */
+ smp_store_release(&cec_ctrl->head, (head + 1) & CEC_RECV_Q_MASK);
DEV_DBG("%s: opcode 0x%x, wakup_en %d, device_suspend %d\n", __func__,
- msg.opcode, cec_ctrl->cec_wakeup_en,
+ msg->opcode, cec_ctrl->cec_wakeup_en,
cec_ctrl->cec_device_suspend);
- if ((msg.opcode == CEC_OP_SET_STREAM_PATH ||
- msg.opcode == CEC_OP_KEY_PRESS) &&
- cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
- cec_ctrl->cec_device_suspend) {
- DEV_DBG("%s: Sending power on at wakeup\n", __func__);
- input_report_key(cec_ctrl->input, KEY_POWER, 1);
- input_sync(cec_ctrl->input);
- input_report_key(cec_ctrl->input, KEY_POWER, 0);
- input_sync(cec_ctrl->input);
- }
-
- if ((msg.opcode == CEC_OP_STANDBY) &&
- cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
- !cec_ctrl->cec_device_suspend) {
- DEV_DBG("%s: Sending power off on standby\n", __func__);
- input_report_key(cec_ctrl->input, KEY_POWER, 1);
- input_sync(cec_ctrl->input);
- input_report_key(cec_ctrl->input, KEY_POWER, 0);
- input_sync(cec_ctrl->input);
- }
-
- if (cbs && cbs->msg_recv_notify)
- cbs->msg_recv_notify(cbs->data, &msg);
+ return 0;
+}
+
+static void hdmi_cec_msg_recv(struct work_struct *work)
+{
+ struct hdmi_cec_ctrl *cec_ctrl = NULL;
+ struct cec_msg msg;
+ struct cec_cbs *cbs;
+ u32 head;
+ u32 tail;
+
+ cec_ctrl = container_of(work, struct hdmi_cec_ctrl, cec_read_work);
+ if (!cec_ctrl || !cec_ctrl->init_data.io) {
+ DEV_ERR("%s: invalid input\n", __func__);
+ return;
+ }
+
+ cbs = cec_ctrl->init_data.cbs;
+
+ /* Read head before reading contents */
+ head = smp_load_acquire(&cec_ctrl->head);
+ tail = cec_ctrl->tail;
+ while (CIRC_CNT(head, tail, CEC_RECV_Q_SIZE) >= 1) {
+ memcpy(&msg, &cec_ctrl->recv_msg[tail], sizeof(msg));
+ tail = (tail + 1) & CEC_RECV_Q_MASK;
+
+ /* Finishing reading before incrementing tail */
+ smp_store_release(&cec_ctrl->tail, tail);
+
+ if ((msg.opcode == CEC_OP_SET_STREAM_PATH ||
+ msg.opcode == CEC_OP_KEY_PRESS) &&
+ cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
+ cec_ctrl->cec_device_suspend) {
+ DEV_DBG("%s: Sending power on at wakeup\n", __func__);
+ input_report_key(cec_ctrl->input, KEY_POWER, 1);
+ input_sync(cec_ctrl->input);
+ input_report_key(cec_ctrl->input, KEY_POWER, 0);
+ input_sync(cec_ctrl->input);
+ }
+
+ if ((msg.opcode == CEC_OP_STANDBY) &&
+ cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
+ !cec_ctrl->cec_device_suspend) {
+ DEV_DBG("%s: Sending power off on standby\n", __func__);
+ input_report_key(cec_ctrl->input, KEY_POWER, 1);
+ input_sync(cec_ctrl->input);
+ input_report_key(cec_ctrl->input, KEY_POWER, 0);
+ input_sync(cec_ctrl->input);
+ }
+
+ if (cbs && cbs->msg_recv_notify)
+ cbs->msg_recv_notify(cbs->data, &msg);
+ }
}
/**
@@ -308,8 +372,12 @@ int hdmi_cec_isr(void *input)
if ((cec_intr & BIT(6)) && (cec_intr & BIT(7))) {
DEV_DBG("%s: CEC_IRQ_FRAME_RD_DONE\n", __func__);
+ rc = hdmi_cec_msg_read(cec_ctrl);
+ if (!rc)
+ queue_work(cec_ctrl->init_data.workq,
+ &cec_ctrl->cec_read_work);
+
DSS_REG_W(io, HDMI_CEC_INT, cec_intr | BIT(6));
- queue_work(cec_ctrl->init_data.workq, &cec_ctrl->cec_read_work);
}
return rc;
@@ -360,8 +428,23 @@ static void hdmi_cec_write_logical_addr(void *input, u8 addr)
return;
}
- if (cec_ctrl->cec_enabled)
+ if (cec_ctrl->cec_enabled) {
DSS_REG_W(cec_ctrl->init_data.io, HDMI_CEC_ADDR, addr & 0xF);
+ cec_ctrl->cec_logical_addr = addr & 0xF;
+ }
+}
+
+static void hdmi_cec_clear_logical_addr(void *input, bool clear_flag)
+{
+ struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
+
+ if (!cec_ctrl || !cec_ctrl->init_data.io) {
+ DEV_ERR("%s: Invalid input\n", __func__);
+ return;
+ }
+
+ if (cec_ctrl->cec_enabled)
+ cec_ctrl->cec_clear_logical_addr = clear_flag;
}
static int hdmi_cec_enable(void *input, bool enable)
@@ -474,6 +557,7 @@ void *hdmi_cec_init(struct hdmi_cec_init_data *init_data)
/* populate hardware specific operations to client */
ops->send_msg = hdmi_cec_msg_send;
ops->wt_logical_addr = hdmi_cec_write_logical_addr;
+ ops->clear_logical_addr = hdmi_cec_clear_logical_addr;
ops->enable = hdmi_cec_enable;
ops->data = cec_ctrl;
ops->wakeup_en = hdmi_cec_wakeup_en;