diff options
| author | Linux Build Service Account <lnxbuild@localhost> | 2020-03-15 10:05:20 -0700 |
|---|---|---|
| committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2020-03-15 10:05:20 -0700 |
| commit | 24f96f1dc9ce12ded962e1724f1f2223ac840975 (patch) | |
| tree | d8be0aab6953e76bbd53ef875238a72167c59e9a | |
| parent | d672d9676bfcac9bd0127e964c460edef1ab62fa (diff) | |
| parent | fd06f35834d58041efd3b804ba33767dd808f188 (diff) | |
Merge "mdss: msm: hdmi: fix CEC broadcast loopback issue"
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_cec_core.c | 40 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_cec_core.h | 3 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_hdmi_cec.c | 196 |
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; |
