blob: b45cf4d340721b355cac869ee732c4797ed93f93 [file] [log] [blame]
/*
* Copyright (c) 2017, 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) "[drm-dp] %s: " fmt, __func__
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/component.h>
#include <linux/of_irq.h>
#include "msm_drv.h"
#include "dp_usbpd.h"
#include "dp_parser.h"
#include "dp_power.h"
#include "dp_catalog.h"
#include "dp_aux.h"
#include "dp_link.h"
#include "dp_panel.h"
#include "dp_ctrl.h"
#include "dp_display.h"
static struct dp_display *g_dp_display;
struct dp_display_private {
char *name;
int irq;
/* state variables */
bool core_initialized;
bool power_on;
bool hpd_irq_on;
struct platform_device *pdev;
struct dentry *root;
struct completion notification_comp;
struct dp_usbpd *usbpd;
struct dp_parser *parser;
struct dp_power *power;
struct dp_catalog *catalog;
struct dp_aux *aux;
struct dp_link *link;
struct dp_panel *panel;
struct dp_ctrl *ctrl;
struct dp_usbpd_cb usbpd_cb;
struct dp_display_mode mode;
struct dp_display dp_display;
};
static const struct of_device_id dp_dt_match[] = {
{.compatible = "qcom,dp-display"},
{}
};
static irqreturn_t dp_display_irq(int irq, void *dev_id)
{
struct dp_display_private *dp = dev_id;
if (!dp) {
pr_err("invalid data\n");
return IRQ_NONE;
}
/* DP controller isr */
dp->ctrl->isr(dp->ctrl);
/* DP aux isr */
dp->aux->isr(dp->aux);
return IRQ_HANDLED;
}
static ssize_t debugfs_dp_info_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
struct dp_display_private *dp = file->private_data;
char *buf;
u32 len = 0;
if (!dp)
return -ENODEV;
if (*ppos)
return 0;
buf = kzalloc(SZ_4K, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += snprintf(buf + len, (SZ_4K - len), "name = %s\n", dp->name);
len += snprintf(buf + len, (SZ_4K - len),
"\tResolution = %dx%d\n",
dp->panel->pinfo.h_active,
dp->panel->pinfo.v_active);
if (copy_to_user(buff, buf, len)) {
kfree(buf);
return -EFAULT;
}
*ppos += len;
kfree(buf);
return len;
}
static const struct file_operations dp_debug_fops = {
.open = simple_open,
.read = debugfs_dp_info_read,
};
static int dp_display_debugfs_init(struct dp_display_private *dp)
{
int rc = 0;
struct dentry *dir, *file;
dir = debugfs_create_dir(dp->name, NULL);
if (IS_ERR_OR_NULL(dir)) {
rc = PTR_ERR(dir);
pr_err("[%s] debugfs create dir failed, rc = %d\n",
dp->name, rc);
goto error;
}
file = debugfs_create_file("dp_debug", 0444, dir, dp, &dp_debug_fops);
if (IS_ERR_OR_NULL(file)) {
rc = PTR_ERR(file);
pr_err("[%s] debugfs create file failed, rc=%d\n",
dp->name, rc);
goto error_remove_dir;
}
dp->root = dir;
return rc;
error_remove_dir:
debugfs_remove(dir);
error:
return rc;
}
static int dp_display_debugfs_deinit(struct dp_display_private *dp)
{
debugfs_remove(dp->root);
return 0;
}
static int dp_display_bind(struct device *dev, struct device *master,
void *data)
{
int rc = 0;
struct dp_display_private *dp;
struct drm_device *drm;
struct msm_drm_private *priv;
struct platform_device *pdev = to_platform_device(dev);
if (!dev || !pdev || !master) {
pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n",
dev, pdev, master);
rc = -EINVAL;
goto end;
}
drm = dev_get_drvdata(master);
dp = platform_get_drvdata(pdev);
if (!drm || !dp) {
pr_err("invalid param(s), drm %pK, dp %pK\n",
drm, dp);
rc = -EINVAL;
goto end;
}
dp->dp_display.drm_dev = drm;
priv = drm->dev_private;
rc = dp_display_debugfs_init(dp);
if (rc) {
pr_err("[%s]Debugfs init failed, rc=%d\n", dp->name, rc);
goto end;
}
rc = dp->parser->parse(dp->parser);
if (rc) {
pr_err("device tree parsing failed\n");
goto end;
}
rc = dp->aux->drm_aux_register(dp->aux);
if (rc) {
pr_err("DRM DP AUX register failed\n");
goto end;
}
rc = dp->panel->sde_edid_register(dp->panel);
if (rc) {
pr_err("DRM DP EDID register failed\n");
goto end;
}
rc = dp->power->power_client_init(dp->power, &priv->phandle);
if (rc) {
pr_err("Power client create failed\n");
goto end;
}
end:
return rc;
}
static void dp_display_unbind(struct device *dev, struct device *master,
void *data)
{
struct dp_display_private *dp;
struct platform_device *pdev = to_platform_device(dev);
if (!dev || !pdev) {
pr_err("invalid param(s)\n");
return;
}
dp = platform_get_drvdata(pdev);
if (!dp) {
pr_err("Invalid params\n");
return;
}
(void)dp->power->power_client_deinit(dp->power);
(void)dp->panel->sde_edid_deregister(dp->panel);
(void)dp->aux->drm_aux_deregister(dp->aux);
(void)dp_display_debugfs_deinit(dp);
}
static const struct component_ops dp_display_comp_ops = {
.bind = dp_display_bind,
.unbind = dp_display_unbind,
};
static int dp_display_process_hpd_high(struct dp_display_private *dp)
{
int rc = 0;
u32 max_pclk_from_edid = 0;
rc = dp->panel->read_dpcd(dp->panel);
if (rc)
return rc;
sde_get_edid(dp->dp_display.connector, &dp->aux->drm_aux->ddc,
(void **)&dp->panel->edid_ctrl);
max_pclk_from_edid = dp->panel->get_max_pclk(dp->panel);
dp->dp_display.max_pclk_khz = min(max_pclk_from_edid,
dp->parser->max_pclk_khz);
dp->dp_display.is_connected = true;
drm_helper_hpd_irq_event(dp->dp_display.connector->dev);
reinit_completion(&dp->notification_comp);
if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2))
pr_warn("timeout\n");
return rc;
}
static void dp_display_host_init(struct dp_display_private *dp)
{
bool flip = false;
if (dp->core_initialized) {
pr_debug("DP core already initialized\n");
return;
}
if (dp->usbpd->orientation == ORIENTATION_CC2)
flip = true;
dp->power->init(dp->power, flip);
dp->ctrl->init(dp->ctrl, flip);
dp->aux->init(dp->aux, dp->parser->aux_cfg);
enable_irq(dp->irq);
dp->core_initialized = true;
}
static void dp_display_host_deinit(struct dp_display_private *dp)
{
if (!dp->core_initialized) {
pr_debug("DP core already off\n");
return;
}
dp->aux->deinit(dp->aux);
dp->ctrl->deinit(dp->ctrl);
dp->power->deinit(dp->power);
disable_irq(dp->irq);
dp->core_initialized = false;
}
static void dp_display_process_hpd_low(struct dp_display_private *dp)
{
/* cancel any pending request */
dp->ctrl->abort(dp->ctrl);
dp->dp_display.is_connected = false;
drm_helper_hpd_irq_event(dp->dp_display.connector->dev);
reinit_completion(&dp->notification_comp);
if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2))
pr_warn("timeout\n");
}
static int dp_display_usbpd_configure_cb(struct device *dev)
{
int rc = 0;
struct dp_display_private *dp;
if (!dev) {
pr_err("invalid dev\n");
rc = -EINVAL;
goto end;
}
dp = dev_get_drvdata(dev);
if (!dp) {
pr_err("no driver data found\n");
rc = -ENODEV;
goto end;
}
dp_display_host_init(dp);
if (dp->usbpd->hpd_high)
dp_display_process_hpd_high(dp);
end:
return rc;
}
static int dp_display_usbpd_disconnect_cb(struct device *dev)
{
int rc = 0;
struct dp_display_private *dp;
if (!dev) {
pr_err("invalid dev\n");
rc = -EINVAL;
goto end;
}
dp = dev_get_drvdata(dev);
if (!dp) {
pr_err("no driver data found\n");
rc = -ENODEV;
goto end;
}
/* cancel any pending request */
dp->ctrl->abort(dp->ctrl);
dp->dp_display.is_connected = false;
drm_helper_hpd_irq_event(dp->dp_display.connector->dev);
reinit_completion(&dp->notification_comp);
if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2))
pr_warn("timeout\n");
/*
* If a cable/dongle is connected to the TX device but
* no sink device is connected, we call host
* initialization where orientation settings are
* configured. When the cable/dongle is disconnect,
* call host de-initialization to make sure
* we re-configure the orientation settings during
* the next connect event.
*/
if (!dp->power_on && dp->core_initialized)
dp_display_host_deinit(dp);
end:
return rc;
}
static int dp_display_usbpd_attention_cb(struct device *dev)
{
int rc = 0;
struct dp_display_private *dp;
if (!dev) {
pr_err("invalid dev\n");
return -EINVAL;
}
dp = dev_get_drvdata(dev);
if (!dp) {
pr_err("no driver data found\n");
return -ENODEV;
}
if (dp->usbpd->hpd_irq) {
dp->hpd_irq_on = true;
rc = dp->link->process_request(dp->link);
dp->hpd_irq_on = false;
if (!rc)
goto end;
}
if (!dp->usbpd->hpd_high) {
dp_display_process_hpd_low(dp);
goto end;
}
if (dp->usbpd->alt_mode_cfg_done) {
dp_display_host_init(dp);
dp_display_process_hpd_high(dp);
}
end:
return rc;
}
static int dp_init_sub_modules(struct dp_display_private *dp)
{
int rc = 0;
struct device *dev = &dp->pdev->dev;
struct dp_usbpd_cb *cb = &dp->usbpd_cb;
struct dp_ctrl_in ctrl_in = {
.dev = dev,
};
cb->configure = dp_display_usbpd_configure_cb;
cb->disconnect = dp_display_usbpd_disconnect_cb;
cb->attention = dp_display_usbpd_attention_cb;
dp->usbpd = dp_usbpd_get(dev, cb);
if (IS_ERR(dp->usbpd)) {
rc = PTR_ERR(dp->usbpd);
pr_err("failed to initialize usbpd, rc = %d\n", rc);
goto err;
}
dp->parser = dp_parser_get(dp->pdev);
if (IS_ERR(dp->parser)) {
rc = PTR_ERR(dp->parser);
pr_err("failed to initialize parser, rc = %d\n", rc);
goto err;
}
dp->catalog = dp_catalog_get(dev, &dp->parser->io);
if (IS_ERR(dp->catalog)) {
rc = PTR_ERR(dp->catalog);
pr_err("failed to initialize catalog, rc = %d\n", rc);
goto err;
}
dp->power = dp_power_get(dp->parser);
if (IS_ERR(dp->power)) {
rc = PTR_ERR(dp->power);
pr_err("failed to initialize power, rc = %d\n", rc);
goto err;
}
dp->aux = dp_aux_get(dev, &dp->catalog->aux);
if (IS_ERR(dp->aux)) {
rc = PTR_ERR(dp->aux);
pr_err("failed to initialize aux, rc = %d\n", rc);
goto err;
}
dp->panel = dp_panel_get(dev, dp->aux, &dp->catalog->panel);
if (IS_ERR(dp->panel)) {
rc = PTR_ERR(dp->panel);
pr_err("failed to initialize panel, rc = %d\n", rc);
goto err;
}
dp->link = dp_link_get(dev, dp->aux);
if (IS_ERR(dp->link)) {
rc = PTR_ERR(dp->link);
pr_err("failed to initialize link, rc = %d\n", rc);
goto err;
}
ctrl_in.link = dp->link;
ctrl_in.panel = dp->panel;
ctrl_in.aux = dp->aux;
ctrl_in.power = dp->power;
ctrl_in.catalog = &dp->catalog->ctrl;
ctrl_in.parser = dp->parser;
dp->ctrl = dp_ctrl_get(&ctrl_in);
if (IS_ERR(dp->ctrl)) {
rc = PTR_ERR(dp->ctrl);
pr_err("failed to initialize ctrl, rc = %d\n", rc);
goto err;
}
err:
return rc;
}
static int dp_display_set_mode(struct dp_display *dp_display,
struct dp_display_mode *mode)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
rc = -EINVAL;
goto error;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
dp->panel->pinfo = mode->timing;
dp->panel->init_info(dp->panel);
error:
return rc;
}
static int dp_display_prepare(struct dp_display *dp)
{
return 0;
}
static int dp_display_enable(struct dp_display *dp_display)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
rc = -EINVAL;
goto error;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
rc = dp->ctrl->on(dp->ctrl, dp->hpd_irq_on);
if (!rc)
dp->power_on = true;
error:
return rc;
}
static int dp_display_post_enable(struct dp_display *dp_display)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
complete_all(&dp->notification_comp);
end:
return rc;
}
static int dp_display_pre_disable(struct dp_display *dp_display)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
rc = -EINVAL;
goto error;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
dp->ctrl->push_idle(dp->ctrl);
error:
return rc;
}
static int dp_display_disable(struct dp_display *dp_display)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
rc = -EINVAL;
goto error;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
dp->ctrl->off(dp->ctrl, dp->hpd_irq_on);
dp_display_host_deinit(dp);
dp->power_on = false;
complete_all(&dp->notification_comp);
error:
return rc;
}
static int dp_request_irq(struct dp_display *dp_display)
{
int rc = 0;
struct dp_display_private *dp;
if (!dp_display) {
pr_err("invalid input\n");
return -EINVAL;
}
dp = container_of(dp_display, struct dp_display_private, dp_display);
dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0);
if (dp->irq < 0) {
rc = dp->irq;
pr_err("failed to get irq: %d\n", rc);
return rc;
}
rc = devm_request_irq(&dp->pdev->dev, dp->irq, dp_display_irq,
IRQF_TRIGGER_HIGH, "dp_display_isr", dp);
if (rc < 0) {
pr_err("failed to request IRQ%u: %d\n",
dp->irq, rc);
return rc;
}
disable_irq(dp->irq);
return 0;
}
static int dp_display_unprepare(struct dp_display *dp)
{
return 0;
}
static int dp_display_validate_mode(struct dp_display *dp,
struct dp_display_mode *mode)
{
return 0;
}
static int dp_display_get_modes(struct dp_display *dp)
{
int ret = 0;
struct dp_display_private *dp_display;
dp_display = container_of(dp, struct dp_display_private, dp_display);
ret = _sde_edid_update_modes(dp->connector,
dp_display->panel->edid_ctrl);
return ret;
}
static int dp_display_probe(struct platform_device *pdev)
{
int rc = 0;
struct dp_display_private *dp;
if (!pdev || !pdev->dev.of_node) {
pr_err("pdev not found\n");
return -ENODEV;
}
dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
init_completion(&dp->notification_comp);
dp->pdev = pdev;
dp->name = "drm_dp";
rc = dp_init_sub_modules(dp);
if (rc) {
devm_kfree(&pdev->dev, dp);
return -EPROBE_DEFER;
}
platform_set_drvdata(pdev, dp);
g_dp_display = &dp->dp_display;
g_dp_display->enable = dp_display_enable;
g_dp_display->post_enable = dp_display_post_enable;
g_dp_display->pre_disable = dp_display_pre_disable;
g_dp_display->disable = dp_display_disable;
g_dp_display->set_mode = dp_display_set_mode;
g_dp_display->validate_mode = dp_display_validate_mode;
g_dp_display->get_modes = dp_display_get_modes;
g_dp_display->prepare = dp_display_prepare;
g_dp_display->unprepare = dp_display_unprepare;
g_dp_display->request_irq = dp_request_irq;
rc = component_add(&pdev->dev, &dp_display_comp_ops);
if (rc)
pr_err("component add failed, rc=%d\n", rc);
return rc;
}
int dp_display_get_displays(void **displays, int count)
{
if (!displays) {
pr_err("invalid data\n");
return -EINVAL;
}
if (count != 1) {
pr_err("invalid number of displays\n");
return -EINVAL;
}
displays[0] = g_dp_display;
return count;
}
int dp_display_get_num_of_displays(void)
{
return 1;
}
static void dp_display_deinit_sub_modules(struct dp_display_private *dp)
{
dp_ctrl_put(dp->ctrl);
dp_link_put(dp->link);
dp_panel_put(dp->panel);
dp_aux_put(dp->aux);
dp_power_put(dp->power);
dp_catalog_put(dp->catalog);
dp_parser_put(dp->parser);
dp_usbpd_put(dp->usbpd);
}
static int dp_display_remove(struct platform_device *pdev)
{
struct dp_display_private *dp;
if (!pdev)
return -EINVAL;
dp = platform_get_drvdata(pdev);
dp_display_deinit_sub_modules(dp);
platform_set_drvdata(pdev, NULL);
devm_kfree(&pdev->dev, dp);
return 0;
}
static struct platform_driver dp_display_driver = {
.probe = dp_display_probe,
.remove = dp_display_remove,
.driver = {
.name = "msm-dp-display",
.of_match_table = dp_dt_match,
},
};
static int __init dp_display_init(void)
{
int ret;
ret = platform_driver_register(&dp_display_driver);
if (ret) {
pr_err("driver register failed");
return ret;
}
return ret;
}
module_init(dp_display_init);
static void __exit dp_display_cleanup(void)
{
platform_driver_unregister(&dp_display_driver);
}
module_exit(dp_display_cleanup);