forked from Minki/linux
c608119dfd
There's no need to duplicate identical code in multiple drivers (two at the moment, one more to come soon). Move it to the dw-hdmi core where it can be shared. If resource allocation ever becomes device-specific later we'll always have the option of splitting it out again. While it at pass the platform device to the bind function to avoid having to cast struct device to struct platform_device. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Reviewed-by: Jose Abreu <joabreu@synopsys.com> Signed-off-by: Archit Taneja <architt@codeaurora.org> Link: http://patchwork.freedesktop.org/patch/msgid/20170117082910.27023-8-laurent.pinchart+renesas@ideasonboard.com
345 lines
8.1 KiB
C
345 lines
8.1 KiB
C
/*
|
|
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/regmap.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/bridge/dw_hdmi.h>
|
|
|
|
#include "rockchip_drm_drv.h"
|
|
#include "rockchip_drm_vop.h"
|
|
|
|
#define GRF_SOC_CON6 0x025c
|
|
#define HDMI_SEL_VOP_LIT (1 << 4)
|
|
|
|
struct rockchip_hdmi {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct drm_encoder encoder;
|
|
};
|
|
|
|
#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
|
|
|
|
static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
|
|
{
|
|
27000000, {
|
|
{ 0x00b3, 0x0000},
|
|
{ 0x2153, 0x0000},
|
|
{ 0x40f3, 0x0000}
|
|
},
|
|
}, {
|
|
36000000, {
|
|
{ 0x00b3, 0x0000},
|
|
{ 0x2153, 0x0000},
|
|
{ 0x40f3, 0x0000}
|
|
},
|
|
}, {
|
|
40000000, {
|
|
{ 0x00b3, 0x0000},
|
|
{ 0x2153, 0x0000},
|
|
{ 0x40f3, 0x0000}
|
|
},
|
|
}, {
|
|
54000000, {
|
|
{ 0x0072, 0x0001},
|
|
{ 0x2142, 0x0001},
|
|
{ 0x40a2, 0x0001},
|
|
},
|
|
}, {
|
|
65000000, {
|
|
{ 0x0072, 0x0001},
|
|
{ 0x2142, 0x0001},
|
|
{ 0x40a2, 0x0001},
|
|
},
|
|
}, {
|
|
66000000, {
|
|
{ 0x013e, 0x0003},
|
|
{ 0x217e, 0x0002},
|
|
{ 0x4061, 0x0002}
|
|
},
|
|
}, {
|
|
74250000, {
|
|
{ 0x0072, 0x0001},
|
|
{ 0x2145, 0x0002},
|
|
{ 0x4061, 0x0002}
|
|
},
|
|
}, {
|
|
83500000, {
|
|
{ 0x0072, 0x0001},
|
|
},
|
|
}, {
|
|
108000000, {
|
|
{ 0x0051, 0x0002},
|
|
{ 0x2145, 0x0002},
|
|
{ 0x4061, 0x0002}
|
|
},
|
|
}, {
|
|
106500000, {
|
|
{ 0x0051, 0x0002},
|
|
{ 0x2145, 0x0002},
|
|
{ 0x4061, 0x0002}
|
|
},
|
|
}, {
|
|
146250000, {
|
|
{ 0x0051, 0x0002},
|
|
{ 0x2145, 0x0002},
|
|
{ 0x4061, 0x0002}
|
|
},
|
|
}, {
|
|
148500000, {
|
|
{ 0x0051, 0x0003},
|
|
{ 0x214c, 0x0003},
|
|
{ 0x4064, 0x0003}
|
|
},
|
|
}, {
|
|
~0UL, {
|
|
{ 0x00a0, 0x000a },
|
|
{ 0x2001, 0x000f },
|
|
{ 0x4002, 0x000f },
|
|
},
|
|
}
|
|
};
|
|
|
|
static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
|
|
/* pixelclk bpp8 bpp10 bpp12 */
|
|
{
|
|
40000000, { 0x0018, 0x0018, 0x0018 },
|
|
}, {
|
|
65000000, { 0x0028, 0x0028, 0x0028 },
|
|
}, {
|
|
66000000, { 0x0038, 0x0038, 0x0038 },
|
|
}, {
|
|
74250000, { 0x0028, 0x0038, 0x0038 },
|
|
}, {
|
|
83500000, { 0x0028, 0x0038, 0x0038 },
|
|
}, {
|
|
146250000, { 0x0038, 0x0038, 0x0038 },
|
|
}, {
|
|
148500000, { 0x0000, 0x0038, 0x0038 },
|
|
}, {
|
|
~0UL, { 0x0000, 0x0000, 0x0000},
|
|
}
|
|
};
|
|
|
|
static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
|
|
/*pixelclk symbol term vlev*/
|
|
{ 74250000, 0x8009, 0x0004, 0x0272},
|
|
{ 148500000, 0x802b, 0x0004, 0x028d},
|
|
{ 297000000, 0x8039, 0x0005, 0x028d},
|
|
{ ~0UL, 0x0000, 0x0000, 0x0000}
|
|
};
|
|
|
|
static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
|
|
{
|
|
struct device_node *np = hdmi->dev->of_node;
|
|
|
|
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
|
if (IS_ERR(hdmi->regmap)) {
|
|
dev_err(hdmi->dev, "Unable to get rockchip,grf\n");
|
|
return PTR_ERR(hdmi->regmap);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
|
|
int pclk = mode->clock * 1000;
|
|
bool valid = false;
|
|
int i;
|
|
|
|
for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
|
|
if (pclk == mpll_cfg[i].mpixelclock) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (valid) ? MODE_OK : MODE_BAD;
|
|
}
|
|
|
|
static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
|
|
.destroy = drm_encoder_cleanup,
|
|
};
|
|
|
|
static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
|
|
{
|
|
}
|
|
|
|
static bool
|
|
dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
|
|
const struct drm_display_mode *mode,
|
|
struct drm_display_mode *adj_mode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
|
|
struct drm_display_mode *mode,
|
|
struct drm_display_mode *adj_mode)
|
|
{
|
|
}
|
|
|
|
static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
|
|
{
|
|
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
|
|
u32 val;
|
|
int mux;
|
|
|
|
mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
|
|
if (mux)
|
|
val = HDMI_SEL_VOP_LIT | (HDMI_SEL_VOP_LIT << 16);
|
|
else
|
|
val = HDMI_SEL_VOP_LIT << 16;
|
|
|
|
regmap_write(hdmi->regmap, GRF_SOC_CON6, val);
|
|
dev_dbg(hdmi->dev, "vop %s output to hdmi\n",
|
|
(mux) ? "LIT" : "BIG");
|
|
}
|
|
|
|
static int
|
|
dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_connector_state *conn_state)
|
|
{
|
|
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
|
|
|
|
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
|
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
|
|
.mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
|
|
.mode_set = dw_hdmi_rockchip_encoder_mode_set,
|
|
.enable = dw_hdmi_rockchip_encoder_enable,
|
|
.disable = dw_hdmi_rockchip_encoder_disable,
|
|
.atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
|
|
};
|
|
|
|
static const struct dw_hdmi_plat_data rockchip_hdmi_drv_data = {
|
|
.mode_valid = dw_hdmi_rockchip_mode_valid,
|
|
.mpll_cfg = rockchip_mpll_cfg,
|
|
.cur_ctr = rockchip_cur_ctr,
|
|
.phy_config = rockchip_phy_config,
|
|
.dev_type = RK3288_HDMI,
|
|
};
|
|
|
|
static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
|
|
{ .compatible = "rockchip,rk3288-dw-hdmi",
|
|
.data = &rockchip_hdmi_drv_data
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids);
|
|
|
|
static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
const struct dw_hdmi_plat_data *plat_data;
|
|
const struct of_device_id *match;
|
|
struct drm_device *drm = data;
|
|
struct drm_encoder *encoder;
|
|
struct rockchip_hdmi *hdmi;
|
|
int ret;
|
|
|
|
if (!pdev->dev.of_node)
|
|
return -ENODEV;
|
|
|
|
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
|
|
if (!hdmi)
|
|
return -ENOMEM;
|
|
|
|
match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
|
|
plat_data = match->data;
|
|
hdmi->dev = &pdev->dev;
|
|
encoder = &hdmi->encoder;
|
|
|
|
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
|
|
/*
|
|
* If we failed to find the CRTC(s) which this encoder is
|
|
* supposed to be connected to, it's because the CRTC has
|
|
* not been registered yet. Defer probing, and hope that
|
|
* the required CRTC is added later.
|
|
*/
|
|
if (encoder->possible_crtcs == 0)
|
|
return -EPROBE_DEFER;
|
|
|
|
ret = rockchip_hdmi_parse_dt(hdmi);
|
|
if (ret) {
|
|
dev_err(hdmi->dev, "Unable to parse OF data\n");
|
|
return ret;
|
|
}
|
|
|
|
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
|
|
drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs,
|
|
DRM_MODE_ENCODER_TMDS, NULL);
|
|
|
|
ret = dw_hdmi_bind(pdev, encoder, plat_data);
|
|
|
|
/*
|
|
* If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
|
|
* which would have called the encoder cleanup. Do it manually.
|
|
*/
|
|
if (ret)
|
|
drm_encoder_cleanup(encoder);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
return dw_hdmi_unbind(dev);
|
|
}
|
|
|
|
static const struct component_ops dw_hdmi_rockchip_ops = {
|
|
.bind = dw_hdmi_rockchip_bind,
|
|
.unbind = dw_hdmi_rockchip_unbind,
|
|
};
|
|
|
|
static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
|
|
{
|
|
return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
|
|
}
|
|
|
|
static int dw_hdmi_rockchip_remove(struct platform_device *pdev)
|
|
{
|
|
component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
|
|
.probe = dw_hdmi_rockchip_probe,
|
|
.remove = dw_hdmi_rockchip_remove,
|
|
.driver = {
|
|
.name = "dwhdmi-rockchip",
|
|
.of_match_table = dw_hdmi_rockchip_dt_ids,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(dw_hdmi_rockchip_pltfm_driver);
|
|
|
|
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
|
|
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
|
|
MODULE_DESCRIPTION("Rockchip Specific DW-HDMI Driver Extension");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:dwhdmi-rockchip");
|