774 lines
19 KiB
C
774 lines
19 KiB
C
/* Copyright (c) 2015, 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
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <video/msm_dba.h>
|
|
#include <linux/switch.h>
|
|
|
|
#include "mdss_dba_utils.h"
|
|
#include "mdss_hdmi_edid.h"
|
|
#include "mdss_cec_core.h"
|
|
#include "mdss_fb.h"
|
|
|
|
/* standard cec buf size + 1 byte specific to driver */
|
|
#define CEC_BUF_SIZE (MAX_CEC_FRAME_SIZE + 1)
|
|
#define MAX_SWITCH_NAME_SIZE 5
|
|
#define MSM_DBA_MAX_PCLK 148500
|
|
#define DEFAULT_VIDEO_RESOLUTION HDMI_VFRMT_640x480p60_4_3
|
|
|
|
struct mdss_dba_utils_data {
|
|
struct msm_dba_ops ops;
|
|
bool hpd_state;
|
|
bool audio_switch_registered;
|
|
bool display_switch_registered;
|
|
struct switch_dev sdev_display;
|
|
struct switch_dev sdev_audio;
|
|
struct kobject *kobj;
|
|
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];
|
|
struct cec_ops cops;
|
|
struct cec_cbs ccbs;
|
|
char disp_switch_name[MAX_SWITCH_NAME_SIZE];
|
|
u32 current_vic;
|
|
};
|
|
|
|
static struct mdss_dba_utils_data *mdss_dba_utils_get_data(
|
|
struct device *device)
|
|
{
|
|
struct msm_fb_data_type *mfd;
|
|
struct mdss_panel_info *pinfo;
|
|
struct fb_info *fbi;
|
|
struct mdss_dba_utils_data *udata = NULL;
|
|
|
|
if (!device) {
|
|
pr_err("Invalid device data\n");
|
|
goto end;
|
|
}
|
|
|
|
fbi = dev_get_drvdata(device);
|
|
if (!fbi) {
|
|
pr_err("Invalid fbi data\n");
|
|
goto end;
|
|
}
|
|
|
|
mfd = (struct msm_fb_data_type *)fbi->par;
|
|
if (!mfd) {
|
|
pr_err("Invalid mfd data\n");
|
|
goto end;
|
|
}
|
|
|
|
pinfo = mfd->panel_info;
|
|
if (!pinfo) {
|
|
pr_err("Invalid pinfo data\n");
|
|
goto end;
|
|
}
|
|
|
|
udata = pinfo->dba_data;
|
|
end:
|
|
return udata;
|
|
}
|
|
|
|
static void mdss_dba_utils_send_display_notification(
|
|
struct mdss_dba_utils_data *udata, int val)
|
|
{
|
|
int state = 0;
|
|
|
|
if (!udata) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
if (!udata->display_switch_registered) {
|
|
pr_err("display switch not registered\n");
|
|
return;
|
|
}
|
|
|
|
state = udata->sdev_display.state;
|
|
|
|
switch_set_state(&udata->sdev_display, val);
|
|
|
|
pr_debug("cable state %s %d\n",
|
|
udata->sdev_display.state == state ?
|
|
"is same" : "switched to",
|
|
udata->sdev_display.state);
|
|
}
|
|
|
|
static void mdss_dba_utils_send_audio_notification(
|
|
struct mdss_dba_utils_data *udata, int val)
|
|
{
|
|
int state = 0;
|
|
|
|
if (!udata) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
if (!udata->audio_switch_registered) {
|
|
pr_err("audio switch not registered\n");
|
|
return;
|
|
}
|
|
|
|
state = udata->sdev_audio.state;
|
|
|
|
switch_set_state(&udata->sdev_audio, val);
|
|
|
|
pr_debug("audio state %s %d\n",
|
|
udata->sdev_audio.state == state ?
|
|
"is same" : "switched to",
|
|
udata->sdev_audio.state);
|
|
}
|
|
|
|
static ssize_t mdss_dba_utils_sysfs_rda_connected(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t ret;
|
|
struct mdss_dba_utils_data *udata = NULL;
|
|
|
|
if (!dev) {
|
|
pr_err("invalid device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
udata = mdss_dba_utils_get_data(dev);
|
|
|
|
if (!udata) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", udata->hpd_state);
|
|
pr_debug("'%d'\n", udata->hpd_state);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t mdss_dba_utils_sysfs_rda_video_mode(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t ret;
|
|
struct mdss_dba_utils_data *udata = NULL;
|
|
|
|
if (!dev) {
|
|
pr_debug("invalid device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
udata = mdss_dba_utils_get_data(dev);
|
|
|
|
if (!udata) {
|
|
pr_debug("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", udata->current_vic);
|
|
pr_debug("'%d'\n", udata->current_vic);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t mdss_dba_utils_sysfs_wta_hpd(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct mdss_dba_utils_data *udata = NULL;
|
|
int rc, hpd;
|
|
|
|
udata = mdss_dba_utils_get_data(dev);
|
|
if (!udata) {
|
|
pr_debug("%s: invalid input\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = kstrtoint(buf, 10, &hpd);
|
|
if (rc) {
|
|
pr_debug("%s: kstrtoint failed\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_debug("%s: set value: %d hpd state: %d\n", __func__,
|
|
hpd, udata->hpd_state);
|
|
if (!hpd) {
|
|
if (udata->ops.power_on)
|
|
udata->ops.power_on(udata->dba_data, false, 0);
|
|
return count;
|
|
}
|
|
|
|
/* power on downstream device */
|
|
if (udata->ops.power_on)
|
|
udata->ops.power_on(udata->dba_data, true, 0);
|
|
|
|
/* check if cable is connected to bridge chip */
|
|
if (udata->ops.check_hpd)
|
|
udata->ops.check_hpd(udata->dba_data, 0);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(connected, S_IRUGO,
|
|
mdss_dba_utils_sysfs_rda_connected, NULL);
|
|
|
|
static DEVICE_ATTR(video_mode, S_IRUGO,
|
|
mdss_dba_utils_sysfs_rda_video_mode, NULL);
|
|
|
|
static DEVICE_ATTR(hpd, S_IRUGO | S_IWUSR, NULL,
|
|
mdss_dba_utils_sysfs_wta_hpd);
|
|
|
|
static struct attribute *mdss_dba_utils_fs_attrs[] = {
|
|
&dev_attr_connected.attr,
|
|
&dev_attr_video_mode.attr,
|
|
&dev_attr_hpd.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group mdss_dba_utils_fs_attrs_group = {
|
|
.attrs = mdss_dba_utils_fs_attrs,
|
|
};
|
|
|
|
static int mdss_dba_utils_sysfs_create(struct kobject *kobj)
|
|
{
|
|
int rc;
|
|
|
|
if (!kobj) {
|
|
pr_err("invalid input\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = sysfs_create_group(kobj, &mdss_dba_utils_fs_attrs_group);
|
|
if (rc) {
|
|
pr_err("failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mdss_dba_utils_sysfs_remove(struct kobject *kobj)
|
|
{
|
|
if (!kobj) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
sysfs_remove_group(kobj, &mdss_dba_utils_fs_attrs_group);
|
|
}
|
|
|
|
static void mdss_dba_utils_dba_cb(void *data, enum msm_dba_callback_event event)
|
|
{
|
|
int ret = -EINVAL;
|
|
struct mdss_dba_utils_data *udata = data;
|
|
struct cec_msg msg = {0};
|
|
bool pluggable = false;
|
|
bool operands_present = false;
|
|
u32 no_of_operands, size, i;
|
|
u32 operands_offset = MAX_CEC_FRAME_SIZE - MAX_OPERAND_SIZE;
|
|
|
|
if (!udata) {
|
|
pr_err("Invalid data\n");
|
|
return;
|
|
}
|
|
|
|
pr_debug("event: %d\n", event);
|
|
|
|
if (udata->pinfo)
|
|
pluggable = udata->pinfo->is_pluggable;
|
|
|
|
switch (event) {
|
|
case MSM_DBA_CB_HPD_CONNECT:
|
|
if (udata->hpd_state)
|
|
break;
|
|
if (udata->ops.get_raw_edid) {
|
|
ret = udata->ops.get_raw_edid(udata->dba_data,
|
|
udata->edid_buf_size, udata->edid_buf, 0);
|
|
|
|
if (!ret)
|
|
hdmi_edid_parser(udata->edid_data);
|
|
else
|
|
pr_err("failed to get edid%d\n", ret);
|
|
}
|
|
|
|
if (pluggable) {
|
|
mdss_dba_utils_send_display_notification(udata, 1);
|
|
mdss_dba_utils_send_audio_notification(udata, 1);
|
|
} else {
|
|
mdss_dba_utils_video_on(udata, udata->pinfo);
|
|
}
|
|
|
|
udata->hpd_state = true;
|
|
break;
|
|
|
|
case MSM_DBA_CB_HPD_DISCONNECT:
|
|
if (!udata->hpd_state)
|
|
break;
|
|
if (pluggable) {
|
|
mdss_dba_utils_send_audio_notification(udata, 0);
|
|
mdss_dba_utils_send_display_notification(udata, 0);
|
|
} else {
|
|
mdss_dba_utils_video_off(udata);
|
|
}
|
|
|
|
udata->hpd_state = false;
|
|
break;
|
|
|
|
case MSM_DBA_CB_CEC_READ_PENDING:
|
|
if (udata->ops.hdmi_cec_read) {
|
|
ret = udata->ops.hdmi_cec_read(
|
|
udata->dba_data,
|
|
&size,
|
|
udata->cec_buf, 0);
|
|
|
|
if (ret || !size || size > CEC_BUF_SIZE) {
|
|
pr_err("%s: cec read failed\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* prepare cec msg */
|
|
msg.recvr_id = udata->cec_buf[0] & 0x0F;
|
|
msg.sender_id = (udata->cec_buf[0] & 0xF0) >> 4;
|
|
msg.opcode = udata->cec_buf[1];
|
|
msg.frame_size = (udata->cec_buf[MAX_CEC_FRAME_SIZE] & 0x1F);
|
|
|
|
operands_present = (msg.frame_size > operands_offset) &&
|
|
(msg.frame_size <= MAX_CEC_FRAME_SIZE);
|
|
|
|
if (operands_present) {
|
|
no_of_operands = msg.frame_size - operands_offset;
|
|
|
|
for (i = 0; i < no_of_operands; i++)
|
|
msg.operand[i] =
|
|
udata->cec_buf[operands_offset + i];
|
|
}
|
|
|
|
ret = udata->ccbs.msg_recv_notify(udata->ccbs.data, &msg);
|
|
if (ret)
|
|
pr_err("%s: failed to notify cec msg\n", __func__);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
u32 operands_offset = MAX_CEC_FRAME_SIZE - MAX_OPERAND_SIZE;
|
|
struct mdss_dba_utils_data *udata = data;
|
|
|
|
u8 buf[MAX_CEC_FRAME_SIZE];
|
|
|
|
if (!udata || !msg) {
|
|
pr_err("%s: Invalid data\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf[0] = (msg->sender_id << 4) | msg->recvr_id;
|
|
buf[1] = msg->opcode;
|
|
|
|
for (i = 0; i < MAX_OPERAND_SIZE &&
|
|
i < msg->frame_size - operands_offset; i++)
|
|
buf[operands_offset + i] = msg->operand[i];
|
|
|
|
if (udata->ops.hdmi_cec_write)
|
|
ret = udata->ops.hdmi_cec_write(udata->dba_data,
|
|
msg->frame_size, (char *)buf, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_dba_utils_init_switch_dev(struct mdss_dba_utils_data *udata,
|
|
u32 fb_node)
|
|
{
|
|
int rc = -EINVAL, ret;
|
|
|
|
if (!udata) {
|
|
pr_err("invalid input\n");
|
|
goto end;
|
|
}
|
|
|
|
/* create switch device to update display modules */
|
|
udata->sdev_display.name = "hdmi";
|
|
rc = switch_dev_register(&udata->sdev_display);
|
|
if (rc) {
|
|
pr_err("display switch registration failed\n");
|
|
goto end;
|
|
}
|
|
|
|
udata->display_switch_registered = true;
|
|
|
|
/* create switch device to update audio modules */
|
|
udata->sdev_audio.name = "hdmi_audio";
|
|
ret = switch_dev_register(&udata->sdev_audio);
|
|
if (ret) {
|
|
pr_err("audio switch registration failed\n");
|
|
goto end;
|
|
}
|
|
|
|
udata->audio_switch_registered = true;
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_dba_get_vic_panel_info(struct mdss_dba_utils_data *udata,
|
|
struct mdss_panel_info *pinfo)
|
|
{
|
|
struct msm_hdmi_mode_timing_info timing;
|
|
struct hdmi_util_ds_data ds_data;
|
|
u32 h_total, v_total, vic = 0;
|
|
|
|
if (!udata || !pinfo) {
|
|
pr_err("%s: invalid input\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
timing.active_h = pinfo->xres;
|
|
timing.back_porch_h = pinfo->lcdc.h_back_porch;
|
|
timing.front_porch_h = pinfo->lcdc.h_front_porch;
|
|
timing.pulse_width_h = pinfo->lcdc.h_pulse_width;
|
|
h_total = (timing.active_h + timing.back_porch_h +
|
|
timing.front_porch_h + timing.pulse_width_h);
|
|
|
|
timing.active_v = pinfo->yres;
|
|
timing.back_porch_v = pinfo->lcdc.v_back_porch;
|
|
timing.front_porch_v = pinfo->lcdc.v_front_porch;
|
|
timing.pulse_width_v = pinfo->lcdc.v_pulse_width;
|
|
v_total = (timing.active_v + timing.back_porch_v +
|
|
timing.front_porch_v + timing.pulse_width_v);
|
|
|
|
timing.refresh_rate = pinfo->mipi.frame_rate * 1000;
|
|
timing.pixel_freq = (h_total * v_total *
|
|
pinfo->mipi.frame_rate) / 1000;
|
|
|
|
ds_data.ds_registered = true;
|
|
ds_data.ds_max_clk = MSM_DBA_MAX_PCLK;
|
|
|
|
vic = hdmi_get_video_id_code(&timing, &ds_data);
|
|
pr_debug("%s: current vic code is %d\n", __func__, vic);
|
|
|
|
return vic;
|
|
}
|
|
|
|
/**
|
|
* mdss_dba_utils_video_on() - Allow clients to switch on the video
|
|
* @data: DBA utils instance which was allocated during registration
|
|
* @pinfo: detailed panel information like x, y, porch values etc
|
|
*
|
|
* This API is used to power on the video on device registered
|
|
* with DBA.
|
|
*
|
|
* Return: returns the result of the video on call on device.
|
|
*/
|
|
int mdss_dba_utils_video_on(void *data, struct mdss_panel_info *pinfo)
|
|
{
|
|
struct mdss_dba_utils_data *ud = data;
|
|
struct msm_dba_video_cfg video_cfg;
|
|
int ret = -EINVAL;
|
|
|
|
if (!ud || !pinfo) {
|
|
pr_err("invalid input\n");
|
|
goto end;
|
|
}
|
|
|
|
memset(&video_cfg, 0, sizeof(video_cfg));
|
|
|
|
video_cfg.h_active = pinfo->xres;
|
|
video_cfg.v_active = pinfo->yres;
|
|
video_cfg.h_front_porch = pinfo->lcdc.h_front_porch;
|
|
video_cfg.v_front_porch = pinfo->lcdc.v_front_porch;
|
|
video_cfg.h_back_porch = pinfo->lcdc.h_back_porch;
|
|
video_cfg.v_back_porch = pinfo->lcdc.v_back_porch;
|
|
video_cfg.h_pulse_width = pinfo->lcdc.h_pulse_width;
|
|
video_cfg.v_pulse_width = pinfo->lcdc.v_pulse_width;
|
|
video_cfg.pclk_khz = (unsigned long)pinfo->clk_rate / 1000;
|
|
video_cfg.hdmi_mode = hdmi_edid_get_sink_mode(ud->edid_data);
|
|
|
|
/* Calculate number of DSI lanes configured */
|
|
video_cfg.num_of_input_lanes = 0;
|
|
if (pinfo->mipi.data_lane0)
|
|
video_cfg.num_of_input_lanes++;
|
|
if (pinfo->mipi.data_lane1)
|
|
video_cfg.num_of_input_lanes++;
|
|
if (pinfo->mipi.data_lane2)
|
|
video_cfg.num_of_input_lanes++;
|
|
if (pinfo->mipi.data_lane3)
|
|
video_cfg.num_of_input_lanes++;
|
|
|
|
/* Get scan information from EDID */
|
|
video_cfg.vic = mdss_dba_get_vic_panel_info(ud, pinfo);
|
|
ud->current_vic = video_cfg.vic;
|
|
video_cfg.scaninfo = hdmi_edid_get_sink_scaninfo(ud->edid_data,
|
|
video_cfg.vic);
|
|
if (ud->ops.video_on)
|
|
ret = ud->ops.video_on(ud->dba_data, true, &video_cfg, 0);
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* mdss_dba_utils_video_off() - Allow clients to switch off the video
|
|
* @data: DBA utils instance which was allocated during registration
|
|
*
|
|
* This API is used to power off the video on device registered
|
|
* with DBA.
|
|
*
|
|
* Return: returns the result of the video off call on device.
|
|
*/
|
|
int mdss_dba_utils_video_off(void *data)
|
|
{
|
|
struct mdss_dba_utils_data *ud = data;
|
|
int ret = -EINVAL;
|
|
|
|
if (!ud) {
|
|
pr_err("invalid input\n");
|
|
goto end;
|
|
}
|
|
|
|
if (ud->ops.video_on)
|
|
ret = ud->ops.video_on(ud->dba_data, false, NULL, 0);
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* mdss_dba_utils_hdcp_enable() - Allow clients to switch on HDCP.
|
|
* @data: DBA utils instance which was allocated during registration
|
|
* @enable: flag to enable or disable HDCP authentication
|
|
*
|
|
* This API is used to start the HDCP authentication process with the
|
|
* device registered with DBA.
|
|
*/
|
|
void mdss_dba_utils_hdcp_enable(void *data, bool enable)
|
|
{
|
|
struct mdss_dba_utils_data *ud = data;
|
|
|
|
if (!ud) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
if (ud->ops.hdcp_enable)
|
|
ud->ops.hdcp_enable(ud->dba_data, enable, enable, 0);
|
|
}
|
|
|
|
/**
|
|
* mdss_dba_utils_init() - Allow clients to register with DBA utils
|
|
* @uid: Initialization data for registration.
|
|
*
|
|
* This API lets the client to register with DBA Utils module.
|
|
* This allocate utils' instance and register with DBA (Display
|
|
* Bridge Abstract). Creates sysfs nodes and switch nodes to interact
|
|
* with other modules. Also registers with EDID parser to parse
|
|
* the EDID buffer.
|
|
*
|
|
* Return: Instance of DBA utils which needs to be sent as parameter
|
|
* when calling DBA utils APIs.
|
|
*/
|
|
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_abstract_init_data cec_abst_init_data;
|
|
void *cec_abst_data;
|
|
int ret = 0;
|
|
|
|
if (!uid) {
|
|
pr_err("invalid input\n");
|
|
ret = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
udata = kzalloc(sizeof(*udata), GFP_KERNEL);
|
|
if (!udata) {
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
memset(&edid_init_data, 0, sizeof(edid_init_data));
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
/* initialize DBA registration data */
|
|
strlcpy(info.client_name, uid->client_name, MSM_DBA_CLIENT_NAME_LEN);
|
|
strlcpy(info.chip_name, uid->chip_name, MSM_DBA_CHIP_NAME_MAX_LEN);
|
|
info.instance_id = uid->instance_id;
|
|
info.cb = mdss_dba_utils_dba_cb;
|
|
info.cb_data = udata;
|
|
|
|
/* register client with DBA and get device's ops*/
|
|
if (IS_ENABLED(CONFIG_MSM_DBA)) {
|
|
udata->dba_data = msm_dba_register_client(&info, &udata->ops);
|
|
if (IS_ERR_OR_NULL(udata->dba_data)) {
|
|
pr_err("ds not configured\n");
|
|
ret = PTR_ERR(udata->dba_data);
|
|
goto error;
|
|
}
|
|
} else {
|
|
pr_err("DBA not enabled\n");
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
/* create sysfs nodes for other modules to intract with utils */
|
|
ret = mdss_dba_utils_sysfs_create(uid->kobj);
|
|
if (ret) {
|
|
pr_err("sysfs creation failed\n");
|
|
goto error;
|
|
}
|
|
|
|
/* keep init data for future use */
|
|
udata->kobj = uid->kobj;
|
|
udata->pinfo = uid->pinfo;
|
|
|
|
/* register display and audio switch devices */
|
|
ret = mdss_dba_utils_init_switch_dev(udata, uid->fb_node);
|
|
if (ret) {
|
|
pr_err("switch dev registration failed\n");
|
|
goto error;
|
|
}
|
|
|
|
/* Initialize EDID feature */
|
|
edid_init_data.kobj = uid->kobj;
|
|
edid_init_data.ds_data.ds_registered = true;
|
|
edid_init_data.ds_data.ds_max_clk = MSM_DBA_MAX_PCLK;
|
|
edid_init_data.max_pclk_khz = MSM_DBA_MAX_PCLK;
|
|
|
|
/* register with edid module for parsing edid buffer */
|
|
udata->edid_data = hdmi_edid_init(&edid_init_data);
|
|
if (!udata->edid_data) {
|
|
pr_err("edid parser init failed\n");
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
/* update edid data to retrieve it back in edid parser */
|
|
if (uid->pinfo) {
|
|
uid->pinfo->edid_data = udata->edid_data;
|
|
/* Initialize to default resolution */
|
|
hdmi_edid_set_video_resolution(uid->pinfo->edid_data,
|
|
DEFAULT_VIDEO_RESOLUTION, true);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/*
|
|
* TODO: Currently there is no support from HAL to send
|
|
* HPD events to driver for usecase where bridge chip
|
|
* is used as primary panel. Once support is added remove
|
|
* this explicit calls to bridge chip driver.
|
|
*/
|
|
if (!uid->pinfo->is_pluggable) {
|
|
if (udata->ops.power_on)
|
|
udata->ops.power_on(udata->dba_data, true, 0);
|
|
if (udata->ops.check_hpd)
|
|
udata->ops.check_hpd(udata->dba_data, 0);
|
|
}
|
|
}
|
|
|
|
return udata;
|
|
|
|
error:
|
|
mdss_dba_utils_deinit(udata);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/**
|
|
* mdss_dba_utils_deinit() - Allow clients to de-register with DBA utils
|
|
* @data: DBA utils data that was allocated during registration.
|
|
*
|
|
* This API will release all the resources allocated during registration
|
|
* and delete the DBA utils instance.
|
|
*/
|
|
void mdss_dba_utils_deinit(void *data)
|
|
{
|
|
struct mdss_dba_utils_data *udata = data;
|
|
|
|
if (!udata) {
|
|
pr_err("invalid input\n");
|
|
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) {
|
|
udata->pinfo->edid_data = NULL;
|
|
udata->pinfo->is_cec_supported = false;
|
|
}
|
|
|
|
if (udata->audio_switch_registered)
|
|
switch_dev_unregister(&udata->sdev_audio);
|
|
|
|
if (udata->display_switch_registered)
|
|
switch_dev_unregister(&udata->sdev_display);
|
|
|
|
if (udata->kobj)
|
|
mdss_dba_utils_sysfs_remove(udata->kobj);
|
|
|
|
if (IS_ENABLED(CONFIG_MSM_DBA)) {
|
|
if (!IS_ERR_OR_NULL(udata->dba_data))
|
|
msm_dba_deregister_client(udata->dba_data);
|
|
}
|
|
|
|
kfree(udata);
|
|
}
|