*/
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/rockchip/cpu.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
+#include <linux/phy/phy.h>
#include <drm/drm_of.h>
#include <drm/drmP.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_vop.h"
+#define RK3228_GRF_SOC_CON2 0x0408
+#define RK3228_DDC_MASK_EN ((3 << 13) | (3 << (13 + 16)))
+#define RK3228_GRF_SOC_CON6 0x0418
+#define RK3228_IO_3V_DOMAIN ((7 << 4) | (7 << (4 + 16)))
+
#define RK3288_GRF_SOC_CON6 0x025C
#define RK3288_HDMI_LCDC_SEL BIT(4)
+#define RK3366_GRF_SOC_CON0 0x0400
+#define RK3366_HDMI_LCDC_SEL BIT(1)
#define RK3399_GRF_SOC_CON20 0x6250
#define RK3399_HDMI_LCDC_SEL BIT(6)
+#define RK3328_GRF_SOC_CON2 0x0408
+#define RK3328_DDC_MASK_EN ((3 << 10) | (3 << (10 + 16)))
+#define RK3328_GRF_SOC_CON3 0x040c
+#define RK3328_IO_CTRL_BY_HDMI (0xf0000000 | BIT(13) | BIT(12))
+#define RK3328_GRF_SOC_CON4 0x0410
+#define RK3328_IO_3V_DOMAIN (7 << (9 + 16))
+#define RK3328_IO_5V_DOMAIN ((7 << 9) | (3 << (9 + 16)))
+#define RK3328_HPD_3V (BIT(8 + 16) | BIT(13 + 16))
+
#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
+ void __iomem *hdmiphy;
struct drm_encoder encoder;
enum dw_hdmi_devtype dev_type;
struct clk *vpll_clk;
struct clk *grf_clk;
+ struct clk *hclk_vio;
+ struct clk *dclk;
+
+ struct phy *phy;
};
#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
+static int
+inno_dw_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data,
+ struct drm_display_mode *mode)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ return phy_power_on(hdmi->phy);
+}
+
+static void inno_dw_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ phy_power_off(hdmi->phy);
+}
+
+static enum drm_connector_status
+inno_dw_hdmi_phy_read_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+ enum drm_connector_status status;
+
+ status = dw_hdmi_phy_read_hpd(dw_hdmi, data);
+
+ if (hdmi->dev_type == RK3228_HDMI)
+ return status;
+
+ if (status == connector_status_connected)
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ RK3328_IO_5V_DOMAIN);
+ else
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ RK3328_IO_3V_DOMAIN);
+ return status;
+}
+
+static int inno_dw_hdmi_init(struct rockchip_hdmi *hdmi)
+{
+ int ret;
+
+ ret = clk_prepare_enable(hdmi->grf_clk);
+ if (ret < 0) {
+ dev_err(hdmi->dev, "failed to enable grfclk %d\n", ret);
+ return -EPROBE_DEFER;
+ }
+ if (hdmi->dev_type == RK3328_HDMI) {
+ /* Map HPD pin to 3V io */
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ RK3328_IO_3V_DOMAIN |
+ RK3328_HPD_3V);
+ /* Map ddc pin to 5V io */
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON3,
+ RK3328_IO_CTRL_BY_HDMI);
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON2,
+ RK3328_DDC_MASK_EN |
+ BIT(18));
+ } else {
+ regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON2,
+ RK3228_DDC_MASK_EN);
+ regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON6,
+ RK3228_IO_3V_DOMAIN);
+ }
+ clk_disable_unprepare(hdmi->grf_clk);
+ return 0;
+}
+
/*
* 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
rockchip_phy_config[i].mpixelclock = (u64)config[i * 4];
else
rockchip_phy_config[i].mpixelclock = ~0UL;
- rockchip_phy_config[i].term = (u16)config[i * 4 + 1];
- rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 2];
+ rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 1];
+ rockchip_phy_config[i].term = (u16)config[i * 4 + 2];
rockchip_phy_config[i].vlev_ctr = (u16)config[i * 4 + 3];
}
return PTR_ERR(hdmi->grf_clk);
}
+ hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio");
+ if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) {
+ hdmi->hclk_vio = NULL;
+ } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(hdmi->hclk_vio)) {
+ dev_err(hdmi->dev, "failed to get hclk_vio clock\n");
+ return PTR_ERR(hdmi->hclk_vio);
+ }
+
+ hdmi->dclk = devm_clk_get(hdmi->dev, "dclk");
+ if (PTR_ERR(hdmi->dclk) == -ENOENT) {
+ hdmi->dclk = NULL;
+ } else if (PTR_ERR(hdmi->dclk) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(hdmi->dclk)) {
+ dev_err(hdmi->dev, "failed to get dclk\n");
+ return PTR_ERR(hdmi->dclk);
+ }
+
ret = clk_prepare_enable(hdmi->vpll_clk);
if (ret) {
dev_err(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret);
return ret;
}
+ ret = clk_prepare_enable(hdmi->hclk_vio);
+ if (ret) {
+ dev_err(hdmi->dev, "Failed to eanble HDMI hclk_vio: %d\n",
+ ret);
+ return ret;
+ }
+
if (of_get_property(np, "rockchip,phy-table", &val)) {
phy_config = kmalloc(val, GFP_KERNEL);
if (!phy_config) {
return -ENOMEM;
}
phy_table_size = val / 16;
- of_property_read_u32_array(np, "rockchip,phy_table",
+ of_property_read_u32_array(np, "rockchip,phy-table",
phy_config, val / sizeof(u32));
ret = rockchip_hdmi_update_phy_table(hdmi, phy_config,
phy_table_size);
*/
if (mode->clock > INT_MAX / 1000)
return MODE_BAD;
+ /*
+ * If sink max TMDS clock < 340MHz, we should check the mode pixel
+ * clock > 340MHz is YCbCr420 or not.
+ */
+ if (mode->clock > 340000 &&
+ connector->display_info.max_tmds_clock < 340000 &&
+ !(mode->flags & DRM_MODE_FLAG_420_MASK))
+ return MODE_BAD;
if (!encoder) {
const struct drm_connector_helper_funcs *funcs;
static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
{
+ struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+
+ clk_disable_unprepare(hdmi->dclk);
}
static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
clk_set_rate(hdmi->vpll_clk,
crtc->state->adjusted_mode.crtc_clock * 1000);
+ clk_set_rate(hdmi->dclk, crtc->state->adjusted_mode.crtc_clock * 1000);
+ clk_prepare_enable(hdmi->dclk);
+
switch (hdmi->dev_type) {
case RK3288_HDMI:
lcdsel_grf_reg = RK3288_GRF_SOC_CON6;
lcdsel_mask = RK3288_HDMI_LCDC_SEL;
break;
+ case RK3366_HDMI:
+ lcdsel_grf_reg = RK3366_GRF_SOC_CON0;
+ lcdsel_mask = RK3366_HDMI_LCDC_SEL;
+ break;
case RK3399_HDMI:
lcdsel_grf_reg = RK3399_GRF_SOC_CON20;
lcdsel_mask = RK3399_HDMI_LCDC_SEL;
break;
default:
return;
- };
+ }
mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
if (mux)
struct drm_connector_state *conn_state)
{
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+ struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+
+ if (hdmi->phy) {
+ if (drm_match_cea_mode(&crtc_state->mode) > 94 &&
+ crtc_state->mode.crtc_clock > 340000 &&
+ !(crtc_state->mode.flags & DRM_MODE_FLAG_420_MASK)) {
+ crtc_state->mode.flags |= DRM_MODE_FLAG_420;
+ phy_set_bus_width(hdmi->phy, 4);
+ } else {
+ phy_set_bus_width(hdmi->phy, 8);
+ }
+ }
if (crtc_state->mode.flags & DRM_MODE_FLAG_420_MASK) {
s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
- s->bus_format = MEDIA_BUS_FMT_YUV8_1X24;
+ s->bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24;
} else {
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
.atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
};
+static const struct dw_hdmi_phy_ops inno_dw_hdmi_phy_ops = {
+ .init = inno_dw_hdmi_phy_init,
+ .disable = inno_dw_hdmi_phy_disable,
+ .read_hpd = inno_dw_hdmi_phy_read_hpd,
+};
+
+static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .phy_ops = &inno_dw_hdmi_phy_ops,
+ .phy_name = "inno_dw_hdmi_phy",
+ .dev_type = RK3228_HDMI,
+};
+
static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
.mpll_cfg = rockchip_mpll_cfg,
.tmds_n_table = rockchip_werid_tmds_n_table,
};
+static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .phy_ops = &inno_dw_hdmi_phy_ops,
+ .phy_name = "inno_dw_hdmi_phy2",
+ .dev_type = RK3328_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3366_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 = RK3366_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3368_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 = RK3368_HDMI,
+};
+
static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
.mpll_cfg = rockchip_mpll_cfg,
};
static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
+ { .compatible = "rockchip,rk3228-dw-hdmi",
+ .data = &rk3228_hdmi_drv_data
+ },
{ .compatible = "rockchip,rk3288-dw-hdmi",
.data = &rk3288_hdmi_drv_data
},
+ {
+ .compatible = "rockchip,rk3328-dw-hdmi",
+ .data = &rk3328_hdmi_drv_data
+ },
+ {
+ .compatible = "rockchip,rk3366-dw-hdmi",
+ .data = &rk3366_hdmi_drv_data
+ },
+ {
+ .compatible = "rockchip,rk3368-dw-hdmi",
+ .data = &rk3368_hdmi_drv_data
+ },
{ .compatible = "rockchip,rk3399-dw-hdmi",
.data = &rk3399_hdmi_drv_data
},
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
- const struct dw_hdmi_plat_data *plat_data;
+ struct dw_hdmi_plat_data *plat_data;
const struct of_device_id *match;
struct drm_device *drm = data;
struct drm_encoder *encoder;
return -ENOMEM;
match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
- plat_data = match->data;
+ plat_data = (struct dw_hdmi_plat_data *)match->data;
hdmi->dev = &pdev->dev;
hdmi->dev_type = plat_data->dev_type;
encoder = &hdmi->encoder;
return ret;
}
+ if (hdmi->dev_type == RK3328_HDMI || hdmi->dev_type == RK3228_HDMI) {
+ hdmi->phy = devm_phy_get(dev, "hdmi_phy");
+ if (IS_ERR(hdmi->phy)) {
+ ret = PTR_ERR(hdmi->phy);
+ dev_err(dev, "failed to get phy: %d\n", ret);
+ return ret;
+ }
+ plat_data->phy_data = hdmi;
+ ret = inno_dw_hdmi_init(hdmi);
+ if (ret < 0)
+ 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);