Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 24f96f1d authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "mdss: msm: hdmi: fix CEC broadcast loopback issue"

parents d672d967 fd06f358
Loading
Loading
Loading
Loading
+39 −1
Original line number Diff line number Diff line
/* 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,
+2 −1
Original line number Diff line number Diff line
/* 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);
+140 −56
Original line number Diff line number Diff line
/* 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,65 +186,106 @@ 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);

	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 &&
@@ -247,6 +310,7 @@ static void hdmi_cec_msg_recv(struct work_struct *work)
		if (cbs && cbs->msg_recv_notify)
			cbs->msg_recv_notify(cbs->data, &msg);
	}
}

/**
 * hdmi_cec_isr() - interrupt handler for cec hw module
@@ -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;