msm: mdss: cec: hdmi cec refactoring

Define CEC on/enable functions in DBA (Display Bridge Abstract) so that
clients can enable disable CEC based on other dependent CEC modules.

Provide CEC supported flag in panel information data so that different
display modules can identify if a particular interface supports CEC
or not.

Separate out CEC abstract data with CEC driver data and initialize and
release corresponding modules properly.

Change-Id: I84f53d99547dcd4ce0b8275401b03ed8e96e14d5
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
This commit is contained in:
Ajay Singh Parmar 2015-06-26 19:29:25 -07:00
parent 6f19ed69c2
commit c997f23eb0
10 changed files with 415 additions and 222 deletions

View File

@ -10,6 +10,8 @@
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/io.h>
#include <linux/list.h>
#include <linux/types.h>
@ -36,40 +38,59 @@ struct cec_ctl {
spinlock_t lock;
struct list_head msg_head;
struct kobject *sysfs_kobj;
struct cec_ops *ops;
struct cec_cbs *cbs;
};
struct cec_abstract_init_data init_data;
static struct cec_ctl *mdss_cec_clients[MAX_CEC_CLIENTS];
};
static struct cec_ctl *cec_get_ctl(struct device *dev)
{
struct cec_ctl *cec_ctl = NULL;
struct fb_info *fbi = dev_get_drvdata(dev);
struct fb_info *fbi;
struct msm_fb_data_type *mfd;
struct mdss_panel_info *pinfo;
if (!fbi)
goto end;
if (!dev) {
pr_err("invlid input\n");
goto error;
}
if (fbi->node >= 0 && fbi->node < MAX_CEC_CLIENTS)
cec_ctl = mdss_cec_clients[fbi->node];
fbi = dev_get_drvdata(dev);
if (!fbi) {
pr_err("invlid fbi\n");
goto error;
}
end:
return cec_ctl;
mfd = (struct msm_fb_data_type *)fbi->par;
if (!mfd) {
pr_err("invlid mfd\n");
goto error;
}
pinfo = mfd->panel_info;
if (!pinfo) {
pr_err("invlid pinfo\n");
goto error;
}
return pinfo->cec_data;
error:
return NULL;
}
static int cec_msg_send(struct cec_ctl *ctl, struct cec_msg *msg)
{
int ret = -EINVAL;
struct cec_ops *ops;
if (!ctl || !msg) {
pr_err("%s: invalid input\n", __func__);
pr_err("invalid input\n");
goto end;
}
if (ctl->ops->send_msg)
ret = ctl->ops->send_msg(ctl->ops->data, msg);
ops = ctl->init_data.ops;
if (ops && ops->send_msg)
ret = ops->send_msg(ops->data, msg);
end:
return ret;
}
@ -80,7 +101,7 @@ static void cec_dump_msg(struct cec_ctl *ctl, struct cec_msg *msg)
unsigned long flags;
if (!ctl || !msg) {
pr_err("%s: invalid input\n", __func__);
pr_err("invalid input\n");
return;
}
@ -111,9 +132,10 @@ static int cec_disable(struct cec_ctl *ctl)
unsigned long flags;
int ret = -EINVAL;
struct cec_msg_node *msg_node, *tmp;
struct cec_ops *ops;
if (!ctl) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
goto end;
}
@ -124,8 +146,10 @@ static int cec_disable(struct cec_ctl *ctl)
}
spin_unlock_irqrestore(&ctl->lock, flags);
if (ctl->ops->disable)
ret = ctl->ops->disable(ctl->ops->data);
ops = ctl->init_data.ops;
if (ops && ops->enable)
ret = ops->enable(ops->data, false);
if (!ret)
ctl->enabled = false;
@ -137,16 +161,19 @@ end:
static int cec_enable(struct cec_ctl *ctl)
{
int ret = -EINVAL;
struct cec_ops *ops;
if (!ctl) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
goto end;
}
INIT_LIST_HEAD(&ctl->msg_head);
if (ctl->ops->enable)
ret = ctl->ops->enable(ctl->ops->data);
ops = ctl->init_data.ops;
if (ops && ops->enable)
ret = ops->enable(ops->data, true);
if (!ret)
ctl->enabled = true;
@ -162,7 +189,7 @@ static int cec_send_abort_opcode(struct cec_ctl *ctl,
struct cec_msg out_msg;
if (!ctl || !in_msg) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
return -EINVAL;
}
@ -182,21 +209,21 @@ static int cec_msg_parser(struct cec_ctl *ctl, struct cec_msg *in_msg)
struct cec_msg out_msg;
if (!ctl || !in_msg) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
rc = -EINVAL;
goto end;
}
pr_debug("%s: in_msg->opcode = 0x%x\n", __func__, in_msg->opcode);
pr_debug("in_msg->opcode = 0x%x\n", in_msg->opcode);
switch (in_msg->opcode) {
case 0x64:
/* Set OSD String */
pr_debug("%s: Recvd OSD Str=[0x%x]\n", __func__,
pr_debug("Recvd OSD Str=[0x%x]\n",
in_msg->operand[3]);
break;
case 0x83:
/* Give Phy Addr */
pr_debug("%s: Recvd a Give Phy Addr cmd\n", __func__);
pr_debug("Recvd a Give Phy Addr cmd\n");
out_msg.sender_id = 0x4;
/* Broadcast */
@ -211,14 +238,14 @@ static int cec_msg_parser(struct cec_ctl *ctl, struct cec_msg *in_msg)
break;
case 0xFF:
/* Abort */
pr_debug("%s: Recvd an abort cmd.\n", __func__);
pr_debug("Recvd an abort cmd.\n");
/* reason = "Refused" */
rc = cec_send_abort_opcode(ctl, in_msg, 0x04);
break;
case 0x46:
/* Give OSD name */
pr_debug("%s: Recvd 'Give OSD name' cmd.\n", __func__);
pr_debug("Recvd 'Give OSD name' cmd.\n");
out_msg.sender_id = 0x4;
out_msg.recvr_id = in_msg->sender_id;
@ -242,7 +269,7 @@ static int cec_msg_parser(struct cec_ctl *ctl, struct cec_msg *in_msg)
break;
case 0x8F:
/* Give Device Power status */
pr_debug("%s: Recvd a Power status message\n", __func__);
pr_debug("Recvd a Power status message\n");
out_msg.sender_id = 0x4;
out_msg.recvr_id = in_msg->sender_id;
@ -266,8 +293,7 @@ static int cec_msg_parser(struct cec_ctl *ctl, struct cec_msg *in_msg)
/* Routing Change cmd */
case 0x86:
/* Set Stream Path */
pr_debug("%s: Recvd Set Stream or Routing Change cmd\n",
__func__);
pr_debug("Recvd Set Stream or Routing Change cmd\n");
out_msg.sender_id = 0x4;
out_msg.recvr_id = 0xF; /* broadcast this message */
@ -292,14 +318,14 @@ static int cec_msg_parser(struct cec_ctl *ctl, struct cec_msg *in_msg)
break;
case 0x44:
/* User Control Pressed */
pr_debug("%s: User Control Pressed\n", __func__);
pr_debug("User Control Pressed\n");
break;
case 0x45:
/* User Control Released */
pr_debug("%s: User Control Released\n", __func__);
pr_debug("User Control Released\n");
break;
default:
pr_debug("%s: Recvd an unknown cmd = [%u]\n", __func__,
pr_debug("Recvd an unknown cmd = [%u]\n",
in_msg->opcode);
/* reason = "Unrecognized opcode" */
@ -318,21 +344,21 @@ static int cec_msg_recv(void *data, struct cec_msg *msg)
int ret = 0;
if (!ctl || !ctl->enabled) {
pr_err("%s: invalid input\n", __func__);
pr_err("invalid input\n");
ret = -EINVAL;
goto end;
}
msg_node = kzalloc(sizeof(*msg_node), GFP_KERNEL);
if (!msg_node) {
pr_err("%s: FAILED: out of memory\n", __func__);
pr_err("FAILED: out of memory\n");
ret = -ENOMEM;
goto end;
}
msg_node->msg = *msg;
pr_debug("%s: CEC read frame done\n", __func__);
pr_debug("CEC read frame done\n");
cec_dump_msg(ctl, &msg_node->msg);
spin_lock_irqsave(&ctl->lock, flags);
@ -341,7 +367,7 @@ static int cec_msg_recv(void *data, struct cec_msg *msg)
ret = cec_msg_parser(ctl, &msg_node->msg);
if (ret)
pr_err("%s: msg parsing failed\n", __func__);
pr_err("msg parsing failed\n");
kfree(msg_node);
} else {
@ -349,7 +375,7 @@ static int cec_msg_recv(void *data, struct cec_msg *msg)
spin_unlock_irqrestore(&ctl->lock, flags);
/* wake-up sysfs read_msg context */
sysfs_notify(ctl->sysfs_kobj, "cec", "rd_msg");
sysfs_notify(ctl->init_data.kobj, "cec", "rd_msg");
}
end:
return ret;
@ -363,17 +389,17 @@ static ssize_t cec_rda_enable(struct device *dev,
struct cec_ctl *ctl = cec_get_ctl(dev);
if (!ctl) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
ret = -EINVAL;
goto end;
}
spin_lock_irqsave(&ctl->lock, flags);
if (ctl->enabled) {
pr_debug("%s: cec is enabled\n", __func__);
pr_debug("cec is enabled\n");
ret = snprintf(buf, PAGE_SIZE, "%d\n", 1);
} else {
pr_debug("%s: cec is disabled\n", __func__);
pr_err("cec is disabled\n");
ret = snprintf(buf, PAGE_SIZE, "%d\n", 0);
}
spin_unlock_irqrestore(&ctl->lock, flags);
@ -388,16 +414,19 @@ static ssize_t cec_wta_enable(struct device *dev,
bool cec_en;
ssize_t ret;
struct cec_ctl *ctl = cec_get_ctl(dev);
struct cec_ops *ops;
if (!ctl) {
pr_err("%s: Invalid input\n", __func__);
pr_err("Invalid input\n");
ret = -EINVAL;
goto end;
}
ops = ctl->init_data.ops;
ret = kstrtoint(buf, 10, &val);
if (ret) {
pr_err("%s: kstrtoint failed.\n", __func__);
pr_err("kstrtoint failed.\n");
goto end;
}
@ -406,14 +435,14 @@ static ssize_t cec_wta_enable(struct device *dev,
/* bit 1 is used for wakeup feature */
ctl->cec_wakeup_en = ((val & 0x3) == 0x3) ? true : false;
if (ctl->ops->wakeup_en)
ret = ctl->ops->wakeup_en(ctl->ops->data, ctl->cec_wakeup_en);
if (ops && ops->wakeup_en)
ret = ops->wakeup_en(ops->data, ctl->cec_wakeup_en);
if (ret)
goto end;
if (ctl->enabled == cec_en) {
pr_info("%s: cec is already %s\n", __func__,
pr_debug("cec is already %s\n",
cec_en ? "enabled" : "disabled");
goto bail;
}
@ -440,7 +469,7 @@ static ssize_t cec_rda_enable_compliance(struct device *dev,
struct cec_ctl *ctl = cec_get_ctl(dev);
if (!ctl) {
pr_err("%s: Invalid ctl\n", __func__);
pr_err("Invalid ctl\n");
return -EINVAL;
}
@ -459,16 +488,19 @@ static ssize_t cec_wta_enable_compliance(struct device *dev,
int val;
ssize_t ret;
struct cec_ctl *ctl = cec_get_ctl(dev);
struct cec_ops *ops;
if (!ctl) {
pr_err("%s: Invalid ctl\n", __func__);
pr_err("Invalid ctl\n");
ret = -EINVAL;
goto end;
}
ops = ctl->init_data.ops;
ret = kstrtoint(buf, 10, &val);
if (ret) {
pr_err("%s: kstrtoint failed.\n", __func__);
pr_err("kstrtoint failed.\n");
goto end;
}
@ -481,9 +513,8 @@ static ssize_t cec_wta_enable_compliance(struct device *dev,
ctl->logical_addr = 0x4;
if (ctl->ops->wt_logical_addr)
ctl->ops->wt_logical_addr(ctl->ops->data,
ctl->logical_addr);
if (ops && ops->wt_logical_addr)
ops->wt_logical_addr(ops->data, ctl->logical_addr);
} else {
ctl->logical_addr = 0;
@ -506,7 +537,7 @@ static ssize_t cec_rda_logical_addr(struct device *dev,
struct cec_ctl *ctl = cec_get_ctl(dev);
if (!ctl) {
pr_err("%s: Invalid ctl\n", __func__);
pr_err("Invalid ctl\n");
return -EINVAL;
}
@ -524,21 +555,24 @@ static ssize_t cec_wta_logical_addr(struct device *dev,
unsigned long flags;
ssize_t ret = strnlen(buf, PAGE_SIZE);
struct cec_ctl *ctl = cec_get_ctl(dev);
struct cec_ops *ops;
if (!ctl) {
pr_err("%s: Invalid ctl\n", __func__);
pr_err("Invalid ctl\n");
ret = -EINVAL;
goto end;
}
ops = ctl->init_data.ops;
ret = kstrtoint(buf, 10, &logical_addr);
if (ret) {
pr_err("%s: kstrtoint failed\n", __func__);
pr_err("kstrtoint failed\n");
goto end;
}
if (logical_addr < 0 || logical_addr > 15) {
pr_err("%s: Invalid logical address\n", __func__);
pr_err("Invalid logical address\n");
ret = -EINVAL;
goto end;
}
@ -546,9 +580,8 @@ static ssize_t cec_wta_logical_addr(struct device *dev,
spin_lock_irqsave(&ctl->lock, flags);
ctl->logical_addr = (u8)logical_addr;
if (ctl->enabled) {
if (ctl->ops->wt_logical_addr)
ctl->ops->wt_logical_addr(ctl->ops->data,
ctl->logical_addr);
if (ops && ops->wt_logical_addr)
ops->wt_logical_addr(ops->data, ctl->logical_addr);
}
spin_unlock_irqrestore(&ctl->lock, flags);
end:
@ -564,8 +597,14 @@ static ssize_t cec_rda_msg(struct device *dev,
struct cec_ctl *ctl = cec_get_ctl(dev);
ssize_t ret;
if (!ctl || !ctl->enabled) {
pr_err("%s: Invalid ctl\n", __func__);
if (!ctl) {
pr_err("Invalid ctl\n");
ret = -EINVAL;
goto end;
}
if (!ctl->enabled) {
pr_err("cec not enabled\n");
ret = -EINVAL;
goto end;
}
@ -574,22 +613,21 @@ static ssize_t cec_rda_msg(struct device *dev,
if (ctl->compliance_enabled) {
spin_unlock_irqrestore(&ctl->lock, flags);
pr_err("%s: Read no allowed in compliance mode\n",
__func__);
pr_err("Read no allowed in compliance mode\n");
ret = -EPERM;
goto end;
}
if (list_empty_careful(&ctl->msg_head)) {
spin_unlock_irqrestore(&ctl->lock, flags);
pr_err("%s: CEC message queue is empty\n", __func__);
pr_err("CEC message queue is empty\n");
ret = -EINVAL;
goto end;
}
list_for_each_entry_safe(msg_node, tmp, &ctl->msg_head, list) {
if ((i + 1) * sizeof(struct cec_msg) > PAGE_SIZE) {
pr_debug("%s: Overflowing PAGE_SIZE.\n", __func__);
pr_err("Overflowing PAGE_SIZE.\n");
break;
}
@ -616,7 +654,7 @@ static ssize_t cec_wta_msg(struct device *dev,
struct cec_ctl *ctl = cec_get_ctl(dev);
if (!ctl) {
pr_err("%s: Invalid ctl\n", __func__);
pr_err("Invalid ctl\n");
ret = -EINVAL;
goto end;
}
@ -624,29 +662,27 @@ static ssize_t cec_wta_msg(struct device *dev,
spin_lock_irqsave(&ctl->lock, flags);
if (ctl->compliance_enabled) {
spin_unlock_irqrestore(&ctl->lock, flags);
pr_err("%s: Write not allowed in compliance mode\n",
__func__);
pr_err("Write not allowed in compliance mode\n");
ret = -EPERM;
goto end;
}
if (!ctl->enabled) {
spin_unlock_irqrestore(&ctl->lock, flags);
pr_err("%s: CEC is not configed.\n",
__func__);
pr_err("CEC is not configed.\n");
ret = -EPERM;
goto end;
}
spin_unlock_irqrestore(&ctl->lock, flags);
if (msg->frame_size > MAX_OPERAND_SIZE) {
pr_err("%s: msg frame too big!\n", __func__);
pr_err("msg frame too big!\n");
ret = -EINVAL;
goto end;
}
ret = cec_msg_send(ctl, msg);
if (ret) {
pr_err("%s: cec_msg_send failed\n", __func__);
pr_err("cec_msg_send failed\n");
goto end;
}
@ -678,6 +714,15 @@ static struct attribute_group cec_fs_attr_group = {
.attrs = cec_fs_attrs,
};
/**
* cec_abstract_deinit() - Release CEC abstract module
* @input: CEC abstract data
*
* This API release all the resources allocated for this
* module.
*
* Return: 0 on success otherwise error code.
*/
int cec_abstract_deinit(void *input)
{
struct cec_ctl *ctl = (struct cec_ctl *)input;
@ -685,62 +730,65 @@ int cec_abstract_deinit(void *input)
if (!ctl)
return -EINVAL;
sysfs_remove_group(ctl->sysfs_kobj,
&cec_fs_attr_group);
sysfs_remove_group(ctl->init_data.kobj, &cec_fs_attr_group);
kfree(ctl);
return 0;
}
int cec_abstract_init(struct cec_data *init_data)
/**
* cec_abstract_init() - Initialize CEC abstract module
* @init_data: data needed to initialize the CEC abstraction module
*
* This API will initialize the CEC abstract module which connects
* CEC client with CEC hardware. It creats sysfs nodes for client
* to read and write CEC messages. It interacts with hardware with
* provided operation function pointers. Also provides callback
* function pointers to let the hardware inform about incoming
* CEC message.
*
* Return: pinter to cec abstract data which needs to be passed
* as parameter with callback functions.
*/
void *cec_abstract_init(struct cec_abstract_init_data *init_data)
{
struct cec_ctl *ctl;
struct cec_cbs *cbs;
int id;
struct cec_ctl *ctl = NULL;
int ret = 0;
if (!init_data || !init_data->ops || !init_data->cbs ||
!init_data->sysfs_kobj) {
pr_err("%s: invalid input\n", __func__);
if (!init_data) {
pr_err("invalid input\n");
ret = -EINVAL;
goto end;
}
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (!ctl) {
pr_err("%s: FAILED: out of memory\n", __func__);
pr_err("FAILED: out of memory\n");
ret = -ENOMEM;
goto end;
}
id = init_data->id;
/* keep a copy of init data */
ctl->init_data = *init_data;
if (id >= 0 && id < MAX_CEC_CLIENTS) {
mdss_cec_clients[id] = ctl;
} else {
ret = -EINVAL;
pr_err("%s: invalid id %d\n", __func__, id);
goto end;
}
ctl->sysfs_kobj = init_data->sysfs_kobj;
ret = sysfs_create_group(ctl->sysfs_kobj, &cec_fs_attr_group);
ret = sysfs_create_group(ctl->init_data.kobj, &cec_fs_attr_group);
if (ret) {
pr_err("%s: cec sysfs group creation failed\n", __func__);
pr_err("cec sysfs group creation failed\n");
goto end;
}
spin_lock_init(&ctl->lock);
ctl->ops = init_data->ops;
cbs = init_data->cbs;
cbs->msg_recv_notify = cec_msg_recv;
cbs->data = (void *)ctl;
/* provide callback function pointers */
if (init_data->cbs) {
init_data->cbs->msg_recv_notify = cec_msg_recv;
init_data->cbs->data = ctl;
}
return ctl;
end:
return ret;
kfree(ctl);
return ERR_PTR(ret);
}

View File

@ -18,6 +18,17 @@
/* total size: HEADER block (1) + opcode block (1) + operands (14) */
#define MAX_CEC_FRAME_SIZE (MAX_OPERAND_SIZE + 2)
/**
* struct cec_msg - CEC message related data
* @sender_id: CEC message initiator's id
* @recvr_id: CEC message destination's id
* @opcode: CEC message opcode
* @operand: CEC mesage operands corresponding to opcode
* @frame_size: total CEC frame size
* @retransmit: number of re-tries to transmit message
*
* Basic CEC message structure used by both client and driver.
*/
struct cec_msg {
u8 sender_id;
u8 recvr_id;
@ -27,9 +38,19 @@ struct cec_msg {
u8 retransmit;
};
/**
* struct cec_ops - CEC operations function pointers
* @enable: function pointer to enable CEC
* @send_msg: function pointer to send CEC message
* @wt_logical_addr: function pointer to write logical address
* @wakeup_en: function pointer to enable wakup feature
* @data: pointer to the data needed to send with operation functions
*
* Defines all the operations that abstract module can call
* to programe the CEC driver.
*/
struct cec_ops {
int (*enable)(void *data);
int (*disable)(void *data);
int (*enable)(void *data, bool enable);
int (*send_msg)(void *data,
struct cec_msg *msg);
void (*wt_logical_addr)(void *data, u8 addr);
@ -37,18 +58,33 @@ struct cec_ops {
void *data;
};
/**
* struct cec_cbs - CEC callback function pointers
* @msg_recv_notify: function pointer called CEC driver to notify incoming msg
* @data: pointer to data needed to be send with the callback function
*
* Defines callback functions which CEC driver can callback to notify any
* change in the hardware.
*/
struct cec_cbs {
int (*msg_recv_notify)(void *data, struct cec_msg *msg);
void *data;
};
struct cec_data {
u32 id;
/**
* struct cec_abstract_init_data - initalization data for abstract module
* @ops: pointer to struct containing all operation function pointers
* @cbs: pointer to struct containing all callack function pointers
* @kobj: pointer to kobject instance associated with CEC driver.
*
* Defines initialization data needed by init API to initalize the module.
*/
struct cec_abstract_init_data {
struct cec_ops *ops;
struct cec_cbs *cbs;
struct kobject *sysfs_kobj;
struct kobject *kobj;
};
int cec_abstract_init(struct cec_data *init_data);
void *cec_abstract_init(struct cec_abstract_init_data *init_data);
int cec_abstract_deinit(void *input);
#endif /* __MDSS_CEC_ABSTRACT_H_*/

View File

@ -35,6 +35,7 @@ struct mdss_dba_utils_data {
struct mdss_panel_info *pinfo;
void *dba_data;
void *edid_data;
void *cec_abst_data;
u8 *edid_buf;
u32 edid_buf_size;
u8 cec_buf[CEC_BUF_SIZE];
@ -285,6 +286,22 @@ static void mdss_dba_utils_dba_cb(void *data, enum msm_dba_callback_event event)
}
}
static int mdss_dba_utils_cec_enable(void *data, bool enable)
{
int ret = -EINVAL;
struct mdss_dba_utils_data *udata = data;
if (!udata) {
pr_err("%s: Invalid data\n", __func__);
return -EINVAL;
}
if (udata->ops.hdmi_cec_on)
ret = udata->ops.hdmi_cec_on(udata->dba_data, enable, 0);
return ret;
}
static int mdss_dba_utils_send_cec_msg(void *data, struct cec_msg *msg)
{
int ret = -EINVAL, i;
@ -400,7 +417,8 @@ void *mdss_dba_utils_init(struct mdss_dba_utils_init_data *uid)
struct hdmi_edid_init_data edid_init_data;
struct mdss_dba_utils_data *udata = NULL;
struct msm_dba_reg_info info;
struct cec_data cec_abstract_data;
struct cec_abstract_init_data cec_abst_init_data;
void *cec_abst_data;
int ret = 0;
if (!uid) {
@ -485,21 +503,33 @@ void *mdss_dba_utils_init(struct mdss_dba_utils_init_data *uid)
if (uid->pinfo)
uid->pinfo->edid_data = udata->edid_data;
/* Initialize cec abstract layer and get callbacks */
udata->cops.send_msg = mdss_dba_utils_send_cec_msg;
udata->cops.data = udata;
cec_abstract_data.id = 0;
cec_abstract_data.sysfs_kobj = uid->kobj;
cec_abstract_data.ops = &udata->cops;
cec_abstract_data.cbs = &udata->ccbs;
cec_abstract_init(&cec_abstract_data);
/* get edid buffer from edid parser */
udata->edid_buf = edid_init_data.buf;
udata->edid_buf_size = edid_init_data.buf_size;
/* Initialize cec abstract layer and get callbacks */
udata->cops.send_msg = mdss_dba_utils_send_cec_msg;
udata->cops.enable = mdss_dba_utils_cec_enable;
udata->cops.data = udata;
/* initialize cec abstraction module */
cec_abst_init_data.kobj = uid->kobj;
cec_abst_init_data.ops = &udata->cops;
cec_abst_init_data.cbs = &udata->ccbs;
udata->cec_abst_data = cec_abstract_init(&cec_abst_init_data);
if (IS_ERR_OR_NULL(udata->cec_abst_data)) {
pr_err("error initializing cec abstract module\n");
ret = PTR_ERR(cec_abst_data);
goto error;
}
/* update cec data to retrieve it back in cec abstract module */
if (uid->pinfo) {
uid->pinfo->is_cec_supported = true;
uid->pinfo->cec_data = udata->cec_abst_data;
}
/* power on downstream device */
if (udata->ops.power_on) {
ret = udata->ops.power_on(udata->dba_data, true, 0);
@ -530,11 +560,16 @@ void mdss_dba_utils_deinit(void *data)
return;
}
if (!IS_ERR_OR_NULL(udata->cec_abst_data))
cec_abstract_deinit(udata->cec_abst_data);
if (udata->edid_data)
hdmi_edid_deinit(udata->edid_data);
if (udata->pinfo)
if (udata->pinfo) {
udata->pinfo->edid_data = NULL;
udata->pinfo->is_cec_supported = false;
}
if (udata->audio_switch_registered)
switch_dev_unregister(&udata->sdev_audio);

View File

@ -428,14 +428,15 @@ static ssize_t mdss_fb_get_panel_info(struct device *dev,
"pu_en=%d\nxstart=%d\nwalign=%d\nystart=%d\nhalign=%d\n"
"min_w=%d\nmin_h=%d\nroi_merge=%d\ndyn_fps_en=%d\n"
"min_fps=%d\nmax_fps=%d\npanel_name=%s\n"
"primary_panel=%d\nis_pluggable=%d\n",
"primary_panel=%d\nis_pluggable=%d\n"
"is_cec_supported=%d\n",
pinfo->partial_update_enabled, pinfo->xstart_pix_align,
pinfo->width_pix_align, pinfo->ystart_pix_align,
pinfo->height_pix_align, pinfo->min_width,
pinfo->min_height, pinfo->partial_update_roi_merge,
pinfo->dynamic_fps, pinfo->min_fps, pinfo->max_fps,
pinfo->panel_name, pinfo->is_prim_panel,
pinfo->is_pluggable);
pinfo->is_pluggable, pinfo->is_cec_supported);
return ret;
}

View File

@ -18,11 +18,14 @@
#include <linux/device.h>
#include "mdss_hdmi_cec.h"
#include "mdss_panel.h"
#define CEC_STATUS_WR_ERROR BIT(0)
#define CEC_STATUS_WR_DONE BIT(1)
#define CEC_INTR (BIT(1) | BIT(3) | BIT(7))
#define CEC_SUPPORTED_HW_VERSION 0x30000001
/* Reference: HDMI 1.4a Specification section 7.1 */
struct hdmi_cec_ctrl {
@ -33,7 +36,6 @@ struct hdmi_cec_ctrl {
struct work_struct cec_read_work;
struct completion cec_msg_wr_done;
struct hdmi_cec_init_data init_data;
struct cec_cbs *cbs;
};
static int hdmi_cec_msg_send(void *data, struct cec_msg *msg)
@ -121,14 +123,21 @@ static void hdmi_cec_msg_recv(struct work_struct *work)
struct hdmi_cec_ctrl *cec_ctrl = NULL;
struct dss_io_data *io = NULL;
struct cec_msg msg;
struct cec_cbs *cbs;
cec_ctrl = container_of(work, struct hdmi_cec_ctrl, cec_read_work);
if (!cec_ctrl || !cec_ctrl->cec_enabled || !cec_ctrl->init_data.io) {
if (!cec_ctrl || !cec_ctrl->init_data.io) {
DEV_ERR("%s: invalid input\n", __func__);
return;
}
if (!cec_ctrl->cec_enabled) {
DEV_ERR("%s: cec not enabled\n", __func__);
return;
}
io = cec_ctrl->init_data.io;
cbs = cec_ctrl->init_data.cbs;
data = DSS_REG_R(io, HDMI_CEC_RD_DATA);
@ -162,8 +171,8 @@ static void hdmi_cec_msg_recv(struct work_struct *work)
for (; i < 14; i++)
msg.operand[i] = 0;
if (cec_ctrl->cbs->msg_recv_notify)
cec_ctrl->cbs->msg_recv_notify(cec_ctrl->cbs->data, &msg);
if (cbs && cbs->msg_recv_notify)
cbs->msg_recv_notify(cbs->data, &msg);
}
int hdmi_cec_isr(void *input)
@ -237,12 +246,13 @@ static void hdmi_cec_write_logical_addr(void *input, u8 addr)
DSS_REG_W(cec_ctrl->init_data.io, HDMI_CEC_ADDR, addr & 0xF);
}
static int hdmi_cec_enable(void *input)
static int hdmi_cec_enable(void *input, bool enable)
{
int ret = 0;
u32 hdmi_hw_version;
u32 hdmi_hw_version, reg_val;
struct dss_io_data *io = NULL;
struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
struct mdss_panel_info *pinfo;
if (!cec_ctrl || !cec_ctrl->init_data.io) {
DEV_ERR("%s: Invalid input\n", __func__);
@ -251,98 +261,97 @@ static int hdmi_cec_enable(void *input)
}
io = cec_ctrl->init_data.io;
pinfo = cec_ctrl->init_data.pinfo;
/* 19.2Mhz * 0.00005 us = 950 = 0x3B6 */
DSS_REG_W(io, HDMI_CEC_REFTIMER, (0x3B6 & 0xFFF) | BIT(16));
hdmi_hw_version = DSS_REG_R(io, HDMI_VERSION);
if (hdmi_hw_version >= 0x30000001) {
DSS_REG_W(io, HDMI_CEC_RD_RANGE, 0x30AB9888);
DSS_REG_W(io, HDMI_CEC_WR_RANGE, 0x888AA888);
DSS_REG_W(io, HDMI_CEC_RD_START_RANGE, 0x88888888);
DSS_REG_W(io, HDMI_CEC_RD_TOTAL_RANGE, 0x99);
DSS_REG_W(io, HDMI_CEC_COMPL_CTL, 0xF);
DSS_REG_W(io, HDMI_CEC_WR_CHECK_CONFIG, 0x4);
} else {
DEV_INFO("%s: CEC is not supported on %d HDMI HW version.\n",
__func__, hdmi_hw_version);
ret = -EPERM;
if (!pinfo) {
DEV_ERR("%s: invalid pinfo\n", __func__);
goto end;
}
DSS_REG_W(io, HDMI_CEC_RD_FILTER, BIT(0) | (0x7FF << 4));
DSS_REG_W(io, HDMI_CEC_TIME, BIT(0) | ((7 * 0x30) << 7));
if (enable) {
/* 19.2Mhz * 0.00005 us = 950 = 0x3B6 */
DSS_REG_W(io, HDMI_CEC_REFTIMER, (0x3B6 & 0xFFF) | BIT(16));
/* Enable CEC interrupts */
DSS_REG_W(io, HDMI_CEC_INT, CEC_INTR);
hdmi_hw_version = DSS_REG_R(io, HDMI_VERSION);
if (hdmi_hw_version >= CEC_SUPPORTED_HW_VERSION) {
DSS_REG_W(io, HDMI_CEC_RD_RANGE, 0x30AB9888);
DSS_REG_W(io, HDMI_CEC_WR_RANGE, 0x888AA888);
/* Enable Engine */
DSS_REG_W(io, HDMI_CEC_CTRL, BIT(0));
DSS_REG_W(io, HDMI_CEC_RD_START_RANGE, 0x88888888);
DSS_REG_W(io, HDMI_CEC_RD_TOTAL_RANGE, 0x99);
DSS_REG_W(io, HDMI_CEC_COMPL_CTL, 0xF);
DSS_REG_W(io, HDMI_CEC_WR_CHECK_CONFIG, 0x4);
} else {
DEV_DBG("%s: CEC version %d is not supported.\n",
__func__, hdmi_hw_version);
ret = -EPERM;
goto end;
}
cec_ctrl->cec_enabled = true;
DSS_REG_W(io, HDMI_CEC_RD_FILTER, BIT(0) | (0x7FF << 4));
DSS_REG_W(io, HDMI_CEC_TIME, BIT(0) | ((7 * 0x30) << 7));
/* Enable CEC interrupts */
DSS_REG_W(io, HDMI_CEC_INT, CEC_INTR);
/* Enable Engine */
DSS_REG_W(io, HDMI_CEC_CTRL, BIT(0));
} else {
/* Disable Engine */
DSS_REG_W(io, HDMI_CEC_CTRL, 0);
/* Disable CEC interrupts */
reg_val = DSS_REG_R(io, HDMI_CEC_INT);
DSS_REG_W(io, HDMI_CEC_INT, reg_val & ~CEC_INTR);
}
cec_ctrl->cec_enabled = enable;
end:
return ret;
}
static int hdmi_cec_disable(void *input)
{
u32 reg_val;
struct dss_io_data *io = NULL;
struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
if (!cec_ctrl || !cec_ctrl->init_data.io)
return -EINVAL;
io = cec_ctrl->init_data.io;
/* Disable Engine */
DSS_REG_W(io, HDMI_CEC_CTRL, 0);
/* Disable CEC interrupts */
reg_val = DSS_REG_R(io, HDMI_CEC_INT);
DSS_REG_W(io, HDMI_CEC_INT, reg_val & ~CEC_INTR);
cec_ctrl->cec_enabled = false;
return 0;
}
int hdmi_cec_init(struct hdmi_cec_init_data *init_data,
struct cec_ops *ops)
void *hdmi_cec_init(struct hdmi_cec_init_data *init_data)
{
struct hdmi_cec_ctrl *cec_ctrl;
struct cec_ops *ops;
int ret = 0;
if (!init_data || !ops) {
if (!init_data) {
DEV_ERR("%s: Invalid input\n", __func__);
ret = -EINVAL;
goto end;
goto error;
}
ops = init_data->ops;
if (ops) {
DEV_ERR("%s: no ops provided\n", __func__);
ret = -EINVAL;
goto error;
}
cec_ctrl = kzalloc(sizeof(*cec_ctrl), GFP_KERNEL);
if (!cec_ctrl) {
DEV_ERR("%s: FAILED: out of memory\n", __func__);
ret = -EINVAL;
goto end;
goto error;
}
/* keep a copy of init data */
cec_ctrl->init_data = *init_data;
spin_lock_init(&cec_ctrl->lock);
INIT_WORK(&cec_ctrl->cec_read_work, hdmi_cec_msg_recv);
init_completion(&cec_ctrl->cec_msg_wr_done);
/* populate hardware specific operations to client */
ops->send_msg = hdmi_cec_msg_send;
ops->wt_logical_addr = hdmi_cec_write_logical_addr;
ops->enable = hdmi_cec_enable;
ops->disable = hdmi_cec_disable;
ops->data = (void *)cec_ctrl;
ops->data = cec_ctrl;
end:
return ret;
return cec_ctrl;
error:
return ERR_PTR(ret);
}
void hdmi_cec_deinit(void *data)
@ -351,10 +360,3 @@ void hdmi_cec_deinit(void *data)
kfree(cec_ctrl);
}
void hdmi_cec_register_cb(void *data, struct cec_cbs *cbs)
{
struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)data;
cec_ctrl->cbs = cbs;
}

View File

@ -18,14 +18,52 @@
#define RETRANSMIT_MAX_NUM 5
/**
* struct hdmi_cec_init_data - data needed for initializing cec hw module
* @workq: pointer to workqueue
* @io: pointer to register access related data
* @pinfo: pointer to panel information data
* @cbs: pointer to cec abstract callback functions.
* @ops: pointer to cec hw operation functions.
*
* Defines the data needed to be provided while initializing cec hw module
*/
struct hdmi_cec_init_data {
struct workqueue_struct *workq;
struct dss_io_data *io;
struct mdss_panel_info *pinfo;
struct cec_cbs *cbs;
struct cec_ops *ops;
};
/**
* hdmi_cec_isr() - interrupt handler for cec hw module
* @cec_ctrl: pointer to cec hw module's data
*
* Return: irq error code
*
* The API can be called by HDMI Tx driver on receiving hw interrupts
* to let the CEC related interrupts handled by this module.
*/
int hdmi_cec_isr(void *cec_ctrl);
int hdmi_cec_init(struct hdmi_cec_init_data *init_data,
struct cec_ops *ops);
/**
* hdmi_cec_init() - Initialize the CEC hw module
* @init_data: data needed to initalize the cec hw module
*
* Return: pointer to cec hw modules data that needs to be passed when
* calling cec hw modules API or error code.
*
* The API registers CEC HW modules with the client and provides HW
* specific operations.
*/
void *hdmi_cec_init(struct hdmi_cec_init_data *init_data);
/**
* hdmi_cec_deinit() - de-initialize CEC HW module
* @data: CEC HW module data
*
* This API release all resources allocated.
*/
void hdmi_cec_deinit(void *data);
void hdmi_cec_register_cb(void *data, struct cec_cbs *cbs);
#endif /* __MDSS_HDMI_CEC_H__ */

View File

@ -1467,12 +1467,14 @@ static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
struct hdmi_edid_init_data edid_init_data;
struct hdmi_hdcp_init_data hdcp_init_data;
struct hdmi_cec_init_data cec_init_data;
struct cec_data cec_abstract_data;
struct cec_abstract_init_data cec_abst_init_data;
int ret = 0;
void *cec_hw_data, *cec_abst_data;
if (!hdmi_ctrl || !fbi) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
ret = -EINVAL;
goto end;
}
/* Initialize EDID feature */
@ -1508,7 +1510,7 @@ static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
DEV_ERR("%s: Error getting HDMI tx core resource\n",
__func__);
ret = -ENODEV;
goto end;
goto err_hdcp;
}
hdcp_init_data.phy_addr = res->start;
hdcp_init_data.core_io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
@ -1529,38 +1531,53 @@ static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
HDMI_TX_FEAT_EDID]);
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = NULL;
ret = -EPERM;
goto end;
goto err_hdcp;
}
DEV_DBG("%s: HDCP feature initialized\n", __func__);
}
/* initialize cec feature and get ops */
/* initialize cec hw feature and get ops */
cec_init_data.io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
cec_init_data.workq = hdmi_ctrl->workq;
cec_init_data.pinfo = &hdmi_ctrl->panel_data.panel_info;
cec_init_data.ops = &hdmi_ctrl->hdmi_cec_ops;
cec_init_data.cbs = &hdmi_ctrl->hdmi_cec_cbs;
ret = hdmi_cec_init(&cec_init_data, &hdmi_ctrl->hdmi_cec_ops);
if (ret) {
cec_hw_data = hdmi_cec_init(&cec_init_data);
if (IS_ERR_OR_NULL(cec_hw_data)) {
DEV_ERR("%s: error cec init\n", __func__);
goto end;
goto err_cec_hw;
}
hdmi_ctrl->panel_data.panel_info.is_cec_supported = true;
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW] = cec_hw_data;
/* initialize cec abstract layer and get callbacks */
cec_abstract_data.id = fbi->node;
cec_abstract_data.sysfs_kobj = hdmi_ctrl->kobj;
cec_abstract_data.ops = &hdmi_ctrl->hdmi_cec_ops;
cec_abstract_data.cbs = &hdmi_ctrl->hdmi_cec_cbs;
cec_abst_init_data.kobj = hdmi_ctrl->kobj;
cec_abst_init_data.ops = &hdmi_ctrl->hdmi_cec_ops;
cec_abst_init_data.cbs = &hdmi_ctrl->hdmi_cec_cbs;
cec_abstract_init(&cec_abstract_data);
cec_abst_data = cec_abstract_init(&cec_abst_init_data);
if (IS_ERR_OR_NULL(cec_abst_data)) {
DEV_ERR("%s: error cec init\n", __func__);
goto err_cec_abst;
}
/* register callbacks with hdmi cec */
hdmi_cec_register_cb(hdmi_ctrl->hdmi_cec_ops.data,
&hdmi_ctrl->hdmi_cec_cbs);
/* keep cec abstract data */
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST] = cec_abst_data;
hdmi_ctrl->panel_data.panel_info.cec_data = cec_abst_data;
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC] =
hdmi_ctrl->hdmi_cec_ops.data;
return 0;
err_hdcp:
hdmi_edid_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
err_cec_hw:
hdmi_hdcp_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]);
err_cec_abst:
hdmi_cec_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]);
hdmi_ctrl->panel_data.panel_info.is_cec_supported = false;
end:
return ret;
} /* hdmi_tx_init_features */
@ -3468,8 +3485,8 @@ static irqreturn_t hdmi_tx_isr(int irq, void *data)
if (hdmi_ddc_isr(&hdmi_ctrl->ddc_ctrl))
DEV_ERR("%s: hdmi_ddc_isr failed\n", __func__);
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC])
if (hdmi_cec_isr(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC]))
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW])
if (hdmi_cec_isr(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]))
DEV_ERR("%s: hdmi_cec_isr failed\n", __func__);
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP])
@ -3486,9 +3503,16 @@ static void hdmi_tx_dev_deinit(struct hdmi_tx_ctrl *hdmi_ctrl)
return;
}
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC]) {
hdmi_cec_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC]);
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC] = NULL;
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST]) {
cec_abstract_deinit(
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST]);
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST] = NULL;
}
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]) {
hdmi_cec_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]);
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW] = NULL;
hdmi_ctrl->panel_data.panel_info.is_cec_supported = false;
}
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) {

View File

@ -242,7 +242,8 @@
enum hdmi_tx_feature_type {
HDMI_TX_FEAT_EDID,
HDMI_TX_FEAT_HDCP,
HDMI_TX_FEAT_CEC,
HDMI_TX_FEAT_CEC_HW,
HDMI_TX_FEAT_CEC_ABST,
HDMI_TX_FEAT_MAX,
};

View File

@ -450,9 +450,11 @@ struct mdss_panel_info {
bool is_prim_panel;
bool is_pluggable;
bool is_cec_supported;
void *edid_data;
void *dba_data;
void *cec_data;
char panel_name[MDSS_MAX_PANEL_LEN];
struct mdss_mdp_pp_tear_check te;

View File

@ -418,6 +418,8 @@ struct msm_dba_video_cfg {
* hdcp_get_ksv_list_size first and then allocate 40*size
* bytes to hold all the KSVs.
* DEFER and ASYNC flags are not supported.
* @hdmi_cec_on: enable or disable cec module. Clients need to enable CEC
* feature before they do read or write CEC messages.
* @hdmi_cec_write: perform a CEC write. For bridges with HDMI as output
* interface, this function allows clients to send a CEC
* message. Client should pack the data according to the CEC
@ -521,6 +523,10 @@ struct msm_dba_ops {
char *buf,
u32 flags);
int (*hdmi_cec_on)(void *client,
bool enable,
u32 flags);
int (*hdmi_cec_write)(void *client,
u32 size,
char *buf,