M7350/kernel/drivers/video/msm/mdss/mdss_dba_utils.c

774 lines
19 KiB
C
Raw Permalink Normal View History

2024-09-09 08:57:42 +00:00
/* 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);
}