Commit 9e32e16e authored by Yakir Yang's avatar Yakir Yang

drm: rockchip: dp: add rockchip platform dp driver

Rockchip have three clocks for dp controller, we leave pclk_edp
to analogix_dp driver control, and keep the sclk_edp_24m and
sclk_edp in platform driver.
Acked-by: default avatarMark Yao <mark.yao@rock-chips.com>
Tested-by: default avatarCaesar Wang <wxt@rock-chips.com>
Tested-by: default avatarDouglas Anderson <dianders@chromium.org>
Tested-by: default avatarHeiko Stuebner <heiko@sntech.de>
Signed-off-by: default avatarYakir Yang <ykk@rock-chips.com>
Signed-off-by: default avatarHeiko Stuebner <heiko@sntech.de>
parent 12315576
...@@ -16,6 +16,15 @@ config DRM_ROCKCHIP ...@@ -16,6 +16,15 @@ config DRM_ROCKCHIP
2D or 3D acceleration; acceleration is performed by other 2D or 3D acceleration; acceleration is performed by other
IP found on the SoC. IP found on the SoC.
config ROCKCHIP_ANALOGIX_DP
tristate "Rockchip specific extensions for Analogix DP driver"
depends on DRM_ROCKCHIP
select DRM_ANALOGIX_DP
help
This selects support for Rockchip SoC specific extensions
for the Analogix Core DP driver. If you want to enable DP
on RK3288 based SoC, you should selet this option.
config ROCKCHIP_DW_HDMI config ROCKCHIP_DW_HDMI
tristate "Rockchip specific extensions for Synopsys DW HDMI" tristate "Rockchip specific extensions for Synopsys DW HDMI"
depends on DRM_ROCKCHIP depends on DRM_ROCKCHIP
......
...@@ -6,6 +6,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ ...@@ -6,6 +6,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o rockchip_drm_gem.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
......
/*
* Rockchip SoC DP (Display Port) interface driver.
*
* Copyright (C) Fuzhou Rockchip Electronics Co., Ltd.
* Author: Andy Yan <andy.yan@rock-chips.com>
* Yakir Yang <ykk@rock-chips.com>
* Jeff Chen <jeff.chen@rock-chips.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/of_graph.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/clk.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include <drm/bridge/analogix_dp.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_vop.h"
#define to_dp(nm) container_of(nm, struct rockchip_dp_device, nm)
/* dp grf register offset */
#define GRF_SOC_CON6 0x025c
#define GRF_EDP_LCD_SEL_MASK BIT(5)
#define GRF_EDP_SEL_VOP_LIT BIT(5)
#define GRF_EDP_SEL_VOP_BIG 0
struct rockchip_dp_device {
struct drm_device *drm_dev;
struct device *dev;
struct drm_encoder encoder;
struct drm_display_mode mode;
struct clk *pclk;
struct regmap *grf;
struct reset_control *rst;
struct analogix_dp_plat_data plat_data;
};
static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
{
reset_control_assert(dp->rst);
usleep_range(10, 20);
reset_control_deassert(dp->rst);
return 0;
}
static int rockchip_dp_poweron(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = to_dp(plat_data);
int ret;
ret = clk_prepare_enable(dp->pclk);
if (ret < 0) {
dev_err(dp->dev, "failed to enable pclk %d\n", ret);
return ret;
}
ret = rockchip_dp_pre_init(dp);
if (ret < 0) {
dev_err(dp->dev, "failed to dp pre init %d\n", ret);
return ret;
}
return 0;
}
static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = to_dp(plat_data);
clk_disable_unprepare(dp->pclk);
return 0;
}
static bool
rockchip_dp_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* do nothing */
return true;
}
static void rockchip_dp_drm_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
/* do nothing */
}
static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_dp_device *dp = to_dp(encoder);
int ret;
u32 val;
/*
* FIXME(Yakir): driver should configure the CRTC output video
* mode with the display information which indicated the monitor
* support colorimetry.
*
* But don't know why the CRTC driver seems could only output the
* RGBaaa rightly. For example, if connect the "innolux,n116bge"
* eDP screen, EDID would indicated that screen only accepted the
* 6bpc mode. But if I configure CRTC to RGB666 output, then eDP
* screen would show a blue picture (RGB888 show a green picture).
* But if I configure CTRC to RGBaaa, and eDP driver still keep
* RGB666 input video mode, then screen would works prefect.
*/
ret = rockchip_drm_crtc_mode_config(encoder->crtc,
DRM_MODE_CONNECTOR_eDP,
ROCKCHIP_OUT_MODE_AAAA);
if (ret < 0) {
dev_err(dp->dev, "Could not set crtc mode config (%d)\n", ret);
return;
}
ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
if (ret < 0)
return;
if (ret)
val = GRF_EDP_SEL_VOP_LIT | (GRF_EDP_LCD_SEL_MASK << 16);
else
val = GRF_EDP_SEL_VOP_BIG | (GRF_EDP_LCD_SEL_MASK << 16);
dev_dbg(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG");
ret = regmap_write(dp->grf, GRF_SOC_CON6, val);
if (ret != 0) {
dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
return;
}
}
static void rockchip_dp_drm_encoder_nop(struct drm_encoder *encoder)
{
/* do nothing */
}
static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
.mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
.mode_set = rockchip_dp_drm_encoder_mode_set,
.enable = rockchip_dp_drm_encoder_enable,
.disable = rockchip_dp_drm_encoder_nop,
};
static void rockchip_dp_drm_encoder_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
}
static struct drm_encoder_funcs rockchip_dp_encoder_funcs = {
.destroy = rockchip_dp_drm_encoder_destroy,
};
static int rockchip_dp_init(struct rockchip_dp_device *dp)
{
struct device *dev = dp->dev;
struct device_node *np = dev->of_node;
int ret;
dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(dp->grf)) {
dev_err(dev, "failed to get rockchip,grf property\n");
return PTR_ERR(dp->grf);
}
dp->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dp->pclk)) {
dev_err(dev, "failed to get pclk property\n");
return PTR_ERR(dp->pclk);
}
dp->rst = devm_reset_control_get(dev, "dp");
if (IS_ERR(dp->rst)) {
dev_err(dev, "failed to get dp reset control\n");
return PTR_ERR(dp->rst);
}
ret = clk_prepare_enable(dp->pclk);
if (ret < 0) {
dev_err(dp->dev, "failed to enable pclk %d\n", ret);
return ret;
}
ret = rockchip_dp_pre_init(dp);
if (ret < 0) {
dev_err(dp->dev, "failed to pre init %d\n", ret);
return ret;
}
return 0;
}
static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp)
{
struct drm_encoder *encoder = &dp->encoder;
struct drm_device *drm_dev = dp->drm_dev;
struct device *dev = dp->dev;
int ret;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
ret = drm_encoder_init(drm_dev, encoder, &rockchip_dp_encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
if (ret) {
DRM_ERROR("failed to initialize encoder with drm\n");
return ret;
}
drm_encoder_helper_add(encoder, &rockchip_dp_encoder_helper_funcs);
return 0;
}
static int rockchip_dp_bind(struct device *dev, struct device *master,
void *data)
{
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
int ret;
/*
* Just like the probe function said, we don't need the
* device drvrate anymore, we should leave the charge to
* analogix dp driver, set the device drvdata to NULL.
*/
dev_set_drvdata(dev, NULL);
ret = rockchip_dp_init(dp);
if (ret < 0)
return ret;
dp->drm_dev = drm_dev;
ret = rockchip_dp_drm_create_encoder(dp);
if (ret) {
DRM_ERROR("failed to create drm encoder\n");
return ret;
}
dp->plat_data.encoder = &dp->encoder;
dp->plat_data.dev_type = RK3288_DP;
dp->plat_data.power_on = rockchip_dp_poweron;
dp->plat_data.power_off = rockchip_dp_powerdown;
return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
}
static void rockchip_dp_unbind(struct device *dev, struct device *master,
void *data)
{
return analogix_dp_unbind(dev, master, data);
}
static const struct component_ops rockchip_dp_component_ops = {
.bind = rockchip_dp_bind,
.unbind = rockchip_dp_unbind,
};
static int rockchip_dp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *panel_node, *port, *endpoint;
struct rockchip_dp_device *dp;
struct drm_panel *panel;
port = of_graph_get_port_by_id(dev->of_node, 1);
if (!port) {
dev_err(dev, "can't find output port\n");
return -EINVAL;
}
endpoint = of_get_child_by_name(port, "endpoint");
of_node_put(port);
if (!endpoint) {
dev_err(dev, "no output endpoint found\n");
return -EINVAL;
}
panel_node = of_graph_get_remote_port_parent(endpoint);
of_node_put(endpoint);
if (!panel_node) {
dev_err(dev, "no output node found\n");
return -EINVAL;
}
panel = of_drm_find_panel(panel_node);
if (!panel) {
DRM_ERROR("failed to find panel\n");
of_node_put(panel_node);
return -EPROBE_DEFER;
}
of_node_put(panel_node);
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
dp->dev = dev;
dp->plat_data.panel = panel;
/*
* We just use the drvdata until driver run into component
* add function, and then we would set drvdata to null, so
* that analogix dp driver could take charge of the drvdata.
*/
platform_set_drvdata(pdev, dp);
return component_add(dev, &rockchip_dp_component_ops);
}
static int rockchip_dp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &rockchip_dp_component_ops);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int rockchip_dp_suspend(struct device *dev)
{
return analogix_dp_suspend(dev);
}
static int rockchip_dp_resume(struct device *dev)
{
return analogix_dp_resume(dev);
}
#endif
static const struct dev_pm_ops rockchip_dp_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rockchip_dp_suspend, rockchip_dp_resume)
};
static const struct of_device_id rockchip_dp_dt_ids[] = {
{.compatible = "rockchip,rk3288-dp",},
{}
};
MODULE_DEVICE_TABLE(of, rockchip_dp_dt_ids);
static struct platform_driver rockchip_dp_driver = {
.probe = rockchip_dp_probe,
.remove = rockchip_dp_remove,
.driver = {
.name = "rockchip-dp",
.owner = THIS_MODULE,
.pm = &rockchip_dp_pm_ops,
.of_match_table = of_match_ptr(rockchip_dp_dt_ids),
},
};
module_platform_driver(rockchip_dp_driver);
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
MODULE_AUTHOR("Jeff chen <jeff.chen@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Specific Analogix-DP Driver Extension");
MODULE_LICENSE("GPL v2");
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
enum analogix_dp_devtype { enum analogix_dp_devtype {
EXYNOS_DP, EXYNOS_DP,
RK3288_DP,
}; };
struct analogix_dp_plat_data { struct analogix_dp_plat_data {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment