blob: 8510667adb985b0696bad773cd8bf44e80b1d2cb [file] [log] [blame]
/*
* 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/crc16.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.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/drm_encoder_slave.h>
#include <drm/bridge/dw_hdmi.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_vop.h"
#include "../../../../arch/arm/mach-rockchip/efuse.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 CLK_SLOP(clk) ((clk) / 1000)
#define CLK_PLUS_SLOP(clk) ((clk) + CLK_SLOP(clk))
#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
static const int dw_hdmi_rates[] = {
25176471, /* for 25.175 MHz, 0.006% off */
25200000,
27000000,
28320000,
30240000,
31500000,
32000000,
33750000,
36000000,
40000000,
49500000,
50000000,
54000000,
57290323, /* for 57.284 MHz, .011 % off */
65000000,
68250000,
71000000,
72000000,
73250000,
74250000,
74437500, /* for 74.44 MHz, .003% off */
75000000,
78750000,
78800000,
79500000,
83500000,
85500000,
88750000,
97750000,
101000000,
106500000,
108000000,
115500000,
118666667, /* for 118.68 MHz, .011% off */
119000000,
121714286, /* for 121.75 MHz, .029% off */
135000000,
136800000, /* for 136.75 MHz, .037% off */
146250000,
148500000,
154000000,
162000000,
};
/*
* There are some rates that would be ranged for better clock jitter at
* Chrome OS tree, like 25.175Mhz would range to 25.170732Mhz. But due
* to the clock is aglined to KHz in struct drm_display_mode, this would
* bring some inaccurate error if we still run the compute_n math, so
* let's just code an const table for it until we can actually get the
* right clock rate.
*/
static const struct dw_hdmi_audio_tmds_n rockchip_werid_tmds_n_table[] = {
/* 25176471 for 25.175 MHz = 428000000 / 17. */
{ .tmds = 25177000, .n_32k = 4352, .n_44k1 = 14994, .n_48k = 6528, },
/* 57290323 for 57.284 MHz */
{ .tmds = 57291000, .n_32k = 3968, .n_44k1 = 4557, .n_48k = 5952, },
/* 74437500 for 74.44 MHz = 297750000 / 4 */
{ .tmds = 74438000, .n_32k = 8192, .n_44k1 = 18816, .n_48k = 4096, },
/* 118666667 for 118.68 MHz */
{ .tmds = 118667000, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 6336, },
/* 121714286 for 121.75 MHz */
{ .tmds = 121715000, .n_32k = 4480, .n_44k1 = 6174, .n_48k = 6272, },
/* 136800000 for 136.75 MHz */
{ .tmds = 136800000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
/* End of table */
{ .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, },
};
static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
{
30666000, {
{ 0x00b3, 0x0000 },
{ 0x2153, 0x0000 },
{ 0x40f3, 0x0000 },
},
}, {
36800000, {
{ 0x00b3, 0x0000 },
{ 0x2153, 0x0000 },
{ 0x40a2, 0x0001 },
},
}, {
46000000, {
{ 0x00b3, 0x0000 },
{ 0x2142, 0x0001 },
{ 0x40a2, 0x0001 },
},
}, {
61333000, {
{ 0x0072, 0x0001 },
{ 0x2142, 0x0001 },
{ 0x40a2, 0x0001 },
},
}, {
73600000, {
{ 0x0072, 0x0001 },
{ 0x2142, 0x0001 },
{ 0x4061, 0x0002 },
},
}, {
92000000, {
{ 0x0072, 0x0001 },
{ 0x2145, 0x0002 },
{ 0x4061, 0x0002 },
},
}, {
122666000, {
{ 0x0051, 0x0002 },
{ 0x2145, 0x0002 },
{ 0x4061, 0x0002 },
},
}, {
147200000, {
{ 0x0051, 0x0002 },
{ 0x2145, 0x0002 },
{ 0x4064, 0x0003 },
},
}, {
184000000, {
{ 0x0051, 0x0002 },
{ 0x214c, 0x0003 },
{ 0x4064, 0x0003 },
},
}, {
226666000, {
{ 0x0040, 0x0003 },
{ 0x214c, 0x0003 },
{ 0x4064, 0x0003 },
},
}, {
272000000, {
{ 0x0040, 0x0003 },
{ 0x214c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
340000000, {
{ 0x0040, 0x0003 },
{ 0x3b4c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
600000000, {
{ 0x1a40, 0x0003 },
{ 0x3b4c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
~0UL, {
{ 0x0000, 0x0000 },
{ 0x0000, 0x0000 },
{ 0x0000, 0x0000 },
},
}
};
static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
/* pixelclk bpp8 bpp10 bpp12 */
{
600000000, { 0x0000, 0x0000, 0x0000 },
}, {
~0UL, { 0x0000, 0x0000, 0x0000 },
},
};
static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
/*pixelclk symbol term vlev*/
{ CLK_PLUS_SLOP(74250000), 0x8009, 0x0004, 0x0272},
{ CLK_PLUS_SLOP(165000000), 0x802b, 0x0004, 0x0209},
{ CLK_PLUS_SLOP(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)
{
int pclk = mode->clock * 1000;
int num_rates = ARRAY_SIZE(dw_hdmi_rates);
int i;
/*
* Pixel clocks we support are always < 2GHz and so fit in an
* int. We should make sure source rate does too so we don't get
* overflow when we multiply by 1000.
*/
if (mode->clock > INT_MAX / 1000)
return MODE_BAD;
/* HACK: Modes > 3840x2160 pixels can't work on the VOP; filter them. */
if (mode->hdisplay > 3840 || mode->vdisplay > 2160)
return MODE_BAD;
for (i = 0; i < num_rates; i++) {
int slop = CLK_SLOP(pclk);
if ((pclk >= dw_hdmi_rates[i] - slop) &&
(pclk <= dw_hdmi_rates[i] + slop))
return MODE_OK;
}
return MODE_BAD;
}
static 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)
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
int pclk = adj_mode->clock * 1000;
int best_diff = INT_MAX;
int best_clock = 0;
int slop;
int i;
/* Pick the best clock */
for (i = 0; i < ARRAY_SIZE(dw_hdmi_rates); i++) {
int diff = dw_hdmi_rates[i] - pclk;
if (diff < 0)
diff = -diff;
if (diff < best_diff) {
best_diff = diff;
best_clock = dw_hdmi_rates[i];
/* Bail early if we're exact */
if (best_diff == 0)
return true;
}
}
/* Double check that it's OK */
slop = CLK_SLOP(pclk);
if ((pclk >= best_clock - slop) && (pclk <= best_clock + slop)) {
adj_mode->clock = DIV_ROUND_UP(best_clock, 1000);
return true;
}
/* Shoudn't be here; we should have said rate wasn't valid */
dev_warn(hdmi->dev, "tried to set invalid rate %d\n", adj_mode->clock);
return false;
}
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_commit(struct drm_encoder *encoder)
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
u32 val;
int mux;
mux = rockchip_drm_encoder_get_mux_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 void dw_hdmi_rockchip_encoder_prepare(struct drm_encoder *encoder)
{
rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA,
ROCKCHIP_OUT_MODE_AAAA);
}
static 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,
.prepare = dw_hdmi_rockchip_encoder_prepare,
.commit = dw_hdmi_rockchip_encoder_commit,
.disable = dw_hdmi_rockchip_encoder_disable,
};
static ssize_t hdcp_key_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct dw_hdmi_hdcp_key_1x *key;
struct device_node *efuse_np;
struct platform_device *efuse;
char uid[EFUSE_CHIP_UID_LEN];
u16 encrypt_seed;
int retval;
key = kzalloc(sizeof(*key), GFP_KERNEL);
if (IS_ERR(key))
return -ENOMEM;
/*
* The HDCP Key format should look like this: "12345678...",
* every two charactor stand for a byte, so the total key size
* would be (308 * 2) byte.
*/
if (count < (DW_HDMI_HDCP_KEY_LEN * 2))
goto err_key_format;
efuse_np = of_parse_phandle(dev->of_node, "rockchip,efuse", 0);
if (!efuse_np) {
dev_err(dev, "missing rockchip,efuse property\n");
goto err_key_format;
}
efuse = of_find_device_by_node(efuse_np);
of_node_put(efuse_np);
if (!efuse) {
dev_err(dev, "couldn't find efuse device\n");
goto err_key_format;
}
retval = rockchip_efuse_get_uid(efuse, uid);
platform_device_put(efuse);
if (retval) {
dev_err(dev, "failed to read efuse cpu uid\n");
goto err_key_format;
}
/*
* The format of input HDCP Key should be "12345678...".
* there is no standard format for HDCP keys, so it is
* just made up for this driver.
*
* The "ksv & device_key & sha" should parsed from input data
* buffer, and the "seed" would take the crc16 of cpu uid.
*/
retval = hex2bin((u8 *)key, buf, DW_HDMI_HDCP_KEY_LEN);
if (retval) {
dev_err(dev, "Failed to decode the input HDCP key format\n");
goto err_key_format;
}
encrypt_seed = crc16(0xFFFF, uid, EFUSE_CHIP_UID_LEN);
key->seed[0] = encrypt_seed & 0xFF;
key->seed[1] = (encrypt_seed >> 8) & 0xFF;
retval = dw_hdmi_config_hdcp_key(dev, key);
if (retval)
goto err_key_format;
kfree(key);
return DW_HDMI_HDCP_KEY_LEN * 2;
err_key_format:
kfree(key);
return -EINVAL;
}
static DEVICE_ATTR(hdcp_key, S_IWUSR, NULL, hdcp_key_store);
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,
.tmds_n_table = rockchip_werid_tmds_n_table,
};
static const struct of_device_id dw_hdmi_rockchip_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;
struct resource *iores;
int irq;
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_ids, pdev->dev.of_node);
plat_data = match->data;
hdmi->dev = &pdev->dev;
encoder = &hdmi->encoder;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENXIO;
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;
}
device_create_file(dev, &dev_attr_hdcp_key);
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);
ret = dw_hdmi_bind(dev, master, data, encoder, iores, irq, 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, master, data);
}
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 int dw_hdmi_rockchip_suspend(struct device *dev)
{
return dw_hdmi_suspend(dev);
}
static int dw_hdmi_rockchip_resume(struct device *dev)
{
return dw_hdmi_resume(dev);
}
static const struct dev_pm_ops dw_hdmi_rockchip_pm = {
.resume_early = dw_hdmi_rockchip_resume,
.suspend_late = dw_hdmi_rockchip_suspend,
};
static struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
.probe = dw_hdmi_rockchip_probe,
.remove = dw_hdmi_rockchip_remove,
.driver = {
.name = "dwhdmi-rockchip",
.pm = &dw_hdmi_rockchip_pm,
.of_match_table = dw_hdmi_rockchip_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");