Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,152 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_ROCKCHIP
tristate "DRM Support for Rockchip"
depends on DRM
depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on ROCKCHIP_IOMMU || !ROCKCHIP_IOMMU
depends on OF
select DRM_CLIENT_SELECTION
select DRM_GEM_DMA_HELPER
select DRM_KMS_HELPER
select DRM_PANEL
select VIDEOMODE_HELPERS
select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
select DRM_DISPLAY_DP_AUX_BUS if ROCKCHIP_ANALOGIX_DP
select DRM_DW_DP if ROCKCHIP_DW_DP
select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
select DRM_DW_MIPI_DSI2 if ROCKCHIP_DW_MIPI_DSI2
select DRM_INNO_HDMI if ROCKCHIP_INNO_HDMI
select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI
select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI
select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
help
Choose this option if you have a Rockchip soc chipset.
This driver provides kernel mode setting and buffer
management to userspace. This driver does not provide
2D or 3D acceleration; acceleration is performed by other
IP found on the SoC.
if DRM_ROCKCHIP
config ROCKCHIP_VOP
bool "Rockchip VOP driver"
default y
help
This selects support for the VOP driver. You should enable it
on older SoCs.
config ROCKCHIP_VOP2
bool "Rockchip VOP2 driver"
help
This selects support for the VOP2 driver. The VOP2 hardware is
first found on the RK3568.
config ROCKCHIP_ANALOGIX_DP
bool "Rockchip specific extensions for Analogix DP driver"
depends on ROCKCHIP_VOP
select DRM_BRIDGE_CONNECTOR
select DRM_DISPLAY_HELPER
select DRM_DISPLAY_DP_HELPER
help
This selects support for Rockchip SoC specific extensions
for the Analogix Core DP driver. If you want to enable DP
on RK3288 or RK3399 based SoC, you should select this option.
config ROCKCHIP_CDN_DP
bool "Rockchip cdn DP"
depends on EXTCON=y || (EXTCON=m && DRM_ROCKCHIP=m)
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_DISPLAY_DP_HELPER
help
This selects support for Rockchip SoC specific extensions
for the cdn DP driver. If you want to enable Dp on
RK3399 based SoC, you should select this
option.
config ROCKCHIP_DW_DP
bool "Rockchip specific extensions for Synopsys DW DP"
help
This selects support for Rockchip SoC specific extensions
to enable Synopsys DesignWare Cores based DisplayPort transmit
controller support on Rockchip SoC, If you want to enable DP on
rk3588 based SoC, you should select this option.
config ROCKCHIP_DW_HDMI
bool "Rockchip specific extensions for Synopsys DW HDMI"
help
This selects support for Rockchip SoC specific extensions
for the Synopsys DesignWare HDMI driver. If you want to
enable HDMI on RK3288 or RK3399 based SoC, you should select
this option.
config ROCKCHIP_DW_HDMI_QP
bool "Rockchip specific extensions for Synopsys DW HDMI QP"
select DRM_BRIDGE_CONNECTOR
help
This selects support for Rockchip SoC specific extensions
for the Synopsys DesignWare HDMI QP driver. If you want to
enable HDMI on RK3588 based SoC, you should select this option.
config ROCKCHIP_DW_MIPI_DSI
bool "Rockchip specific extensions for Synopsys DW MIPI DSI"
select GENERIC_PHY_MIPI_DPHY
help
This selects support for Rockchip SoC specific extensions
for the Synopsys DesignWare dsi driver. If you want to
enable MIPI DSI on RK3288 or RK3399 based SoC, you should
select this option.
config ROCKCHIP_DW_MIPI_DSI2
bool "Rockchip specific extensions for Synopsys DW MIPI DSI2"
select GENERIC_PHY_MIPI_DPHY
help
This selects support for Rockchip SoC specific extensions
for the Synopsys DesignWare DSI2 driver. If you want to
enable MIPI DSI on RK3576 or RK3588 based SoC, you should
select this option.
config ROCKCHIP_INNO_HDMI
bool "Rockchip specific extensions for Innosilicon HDMI"
select DRM_DISPLAY_HDMI_HELPER
select DRM_DISPLAY_HDMI_STATE_HELPER
select DRM_DISPLAY_HELPER
help
This selects support for Rockchip SoC specific extensions
for the Innosilicon HDMI driver. If you want to enable
HDMI on RK3036 based SoC, you should select this option.
config ROCKCHIP_LVDS
bool "Rockchip LVDS support"
depends on DRM_ROCKCHIP
depends on PINCTRL && OF
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
help
Choose this option to enable support for Rockchip LVDS controllers.
Rockchip rk3288 SoC has LVDS TX Controller can be used, and it
support LVDS, rgb, dual LVDS output mode. say Y to enable its
driver.
config ROCKCHIP_RGB
bool "Rockchip RGB support"
depends on DRM_ROCKCHIP
depends on PINCTRL
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
help
Choose this option to enable support for Rockchip RGB output.
Some Rockchip CRTCs, like rv1108, can directly output parallel
and serial RGB format to panel or connect to a conversion chip.
say Y to enable its driver.
config ROCKCHIP_RK3066_HDMI
bool "Rockchip specific extensions for RK3066 HDMI"
depends on DRM_ROCKCHIP
help
This selects support for Rockchip SoC specific extensions
for the RK3066 HDMI driver. If you want to enable
HDMI on RK3066 based SoC, you should select this option.
endif
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the drm device driver. This driver provides support for the
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o
rockchipdrm-$(CONFIG_ROCKCHIP_VOP2) += rockchip_drm_vop2.o rockchip_vop2_reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += rockchip_drm_vop.o rockchip_vop_reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o
obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Rockchip SoC DP (Display Port) interface driver.
*
* Copyright (C) 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>
*/
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/clk.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge_connector.h>
#include <drm/bridge/analogix_dp.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#define PSR_WAIT_LINE_FLAG_TIMEOUT_MS 100
#define GRF_REG_FIELD(_reg, _lsb, _msb) { \
.reg = _reg, \
.lsb = _lsb, \
.msb = _msb, \
.valid = true, \
}
struct rockchip_grf_reg_field {
u32 reg;
u32 lsb;
u32 msb;
bool valid;
};
/**
* struct rockchip_dp_chip_data - splite the grf setting of kind of chips
* @lcdc_sel: grf register field of lcdc_sel
* @edp_mode: grf register field of edp_mode
* @chip_type: specific chip type
* @reg: register base address
*/
struct rockchip_dp_chip_data {
const struct rockchip_grf_reg_field lcdc_sel;
const struct rockchip_grf_reg_field edp_mode;
u32 chip_type;
u32 reg;
};
struct rockchip_dp_device {
struct drm_device *drm_dev;
struct device *dev;
struct rockchip_encoder encoder;
struct drm_display_mode mode;
struct clk *pclk;
struct clk *grfclk;
struct regmap *grf;
struct reset_control *rst;
struct reset_control *apbrst;
const struct rockchip_dp_chip_data *data;
struct analogix_dp_device *adp;
struct analogix_dp_plat_data plat_data;
};
static struct rockchip_dp_device *encoder_to_dp(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct rockchip_dp_device, encoder);
}
static struct rockchip_dp_device *pdata_encoder_to_dp(struct analogix_dp_plat_data *plat_data)
{
return container_of(plat_data, struct rockchip_dp_device, plat_data);
}
static int rockchip_grf_write(struct regmap *grf, u32 reg, u32 mask, u32 val)
{
return regmap_write(grf, reg, (mask << 16) | (val & mask));
}
static int rockchip_grf_field_write(struct regmap *grf,
const struct rockchip_grf_reg_field *field,
u32 val)
{
u32 mask;
if (!field->valid)
return 0;
mask = GENMASK(field->msb, field->lsb);
val <<= field->lsb;
return rockchip_grf_write(grf, field->reg, mask, val);
}
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);
reset_control_assert(dp->apbrst);
usleep_range(10, 20);
reset_control_deassert(dp->apbrst);
return 0;
}
static int rockchip_dp_poweron(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
int ret;
ret = clk_prepare_enable(dp->pclk);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to enable pclk %d\n", ret);
return ret;
}
ret = rockchip_dp_pre_init(dp);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to dp pre init %d\n", ret);
clk_disable_unprepare(dp->pclk);
return ret;
}
ret = rockchip_grf_field_write(dp->grf, &dp->data->edp_mode, 1);
if (ret != 0)
DRM_DEV_ERROR(dp->dev, "failed to set edp mode %d\n", ret);
return ret;
}
static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
{
struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
int ret;
ret = rockchip_grf_field_write(dp->grf, &dp->data->edp_mode, 0);
if (ret != 0)
DRM_DEV_ERROR(dp->dev, "failed to set edp mode %d\n", ret);
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
struct drm_crtc *rockchip_dp_drm_get_new_crtc(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct drm_connector *connector;
struct drm_connector_state *conn_state;
connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
if (!connector)
return NULL;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (!conn_state)
return NULL;
return conn_state->crtc;
}
static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct rockchip_dp_device *dp = encoder_to_dp(encoder);
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
struct of_endpoint endpoint;
struct device_node *remote_port, *remote_port_parent;
char name[32];
u32 port_id;
int ret;
crtc = rockchip_dp_drm_get_new_crtc(encoder, state);
if (!crtc)
return;
old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
/* Coming back from self refresh, nothing to do */
if (old_crtc_state && old_crtc_state->self_refresh_active)
return;
ret = clk_prepare_enable(dp->grfclk);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to enable grfclk %d\n", ret);
return;
}
ret = drm_of_encoder_active_endpoint(dp->dev->of_node, encoder, &endpoint);
if (ret < 0)
return;
remote_port_parent = of_graph_get_remote_port_parent(endpoint.local_node);
if (remote_port_parent) {
if (of_get_child_by_name(remote_port_parent, "ports")) {
remote_port = of_graph_get_remote_port(endpoint.local_node);
of_property_read_u32(remote_port, "reg", &port_id);
of_node_put(remote_port);
sprintf(name, "%s vp%d", remote_port_parent->full_name, port_id);
} else {
sprintf(name, "%s %s",
remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb");
}
of_node_put(remote_port_parent);
DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG");
}
ret = rockchip_grf_field_write(dp->grf, &dp->data->lcdc_sel, endpoint.id);
if (ret != 0)
DRM_DEV_ERROR(dp->dev, "Could not write to GRF: %d\n", ret);
clk_disable_unprepare(dp->grfclk);
}
static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct rockchip_dp_device *dp = encoder_to_dp(encoder);
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state = NULL;
int ret;
crtc = rockchip_dp_drm_get_new_crtc(encoder, state);
/* No crtc means we're doing a full shutdown */
if (!crtc)
return;
new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
/* If we're not entering self-refresh, no need to wait for vact */
if (!new_crtc_state || !new_crtc_state->self_refresh_active)
return;
ret = rockchip_drm_wait_vact_end(crtc, PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
if (ret)
DRM_DEV_ERROR(dp->dev, "line flag irq timed out\n");
}
static int
rockchip_dp_drm_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);
struct drm_display_info *di = &conn_state->connector->display_info;
/*
* The hardware IC designed that VOP must output the RGB10 video
* format to eDP controller, and if eDP panel only support RGB8,
* then eDP controller should cut down the video data, not via VOP
* controller, that's why we need to hardcode the VOP output mode
* to RGA10 here.
*/
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
s->output_type = DRM_MODE_CONNECTOR_eDP;
s->output_bpc = di->bpc;
return 0;
}
static const 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,
.atomic_enable = rockchip_dp_drm_encoder_enable,
.atomic_disable = rockchip_dp_drm_encoder_disable,
.atomic_check = rockchip_dp_drm_encoder_atomic_check,
};
static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
{
struct device *dev = dp->dev;
struct device_node *np = dev->of_node;
dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(dp->grf))
return dev_err_probe(dev, PTR_ERR(dp->grf),
"failed to get rockchip,grf property\n");
dp->grfclk = devm_clk_get_optional(dev, "grf");
if (IS_ERR(dp->grfclk))
return dev_err_probe(dev, PTR_ERR(dp->grfclk),
"failed to get grf clock\n");
dp->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dp->pclk))
return dev_err_probe(dev, PTR_ERR(dp->pclk),
"failed to get pclk property\n");
dp->rst = devm_reset_control_get(dev, "dp");
if (IS_ERR(dp->rst))
return dev_err_probe(dev, PTR_ERR(dp->rst),
"failed to get dp reset control\n");
dp->apbrst = devm_reset_control_get_optional(dev, "apb");
if (IS_ERR(dp->apbrst))
return dev_err_probe(dev, PTR_ERR(dp->apbrst),
"failed to get apb reset control\n");
return 0;
}
static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp)
{
struct drm_encoder *encoder = &dp->encoder.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_simple_encoder_init(drm_dev, encoder,
DRM_MODE_ENCODER_TMDS);
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;
struct drm_connector *connector;
int 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;
}
rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder,
dev->of_node, 0, 0);
dp->plat_data.encoder = &dp->encoder.encoder;
ret = analogix_dp_bind(dp->adp, drm_dev);
if (ret)
goto err_cleanup_encoder;
connector = drm_bridge_connector_init(dp->drm_dev, dp->plat_data.encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
dev_err(dp->dev, "Failed to initialize bridge_connector\n");
goto err_cleanup_encoder;
}
return 0;
err_cleanup_encoder:
dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
return ret;
}
static void rockchip_dp_unbind(struct device *dev, struct device *master,
void *data)
{
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
analogix_dp_unbind(dp->adp);
dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
}
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;
const struct rockchip_dp_chip_data *dp_data;
struct rockchip_dp_device *dp;
struct resource *res;
int i;
int ret;
dp_data = of_device_get_match_data(dev);
if (!dp_data)
return -ENODEV;
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
i = 0;
while (dp_data[i].reg) {
if (dp_data[i].reg == res->start) {
dp->data = &dp_data[i];
break;
}
i++;
}
if (!dp->data)
return dev_err_probe(dev, -EINVAL, "no chip-data for %s node\n",
dev->of_node->name);
dp->dev = dev;
dp->adp = ERR_PTR(-ENODEV);
dp->plat_data.dev_type = dp->data->chip_type;
dp->plat_data.power_on = rockchip_dp_poweron;
dp->plat_data.power_off = rockchip_dp_powerdown;
dp->plat_data.ops = &rockchip_dp_component_ops;
ret = rockchip_dp_of_probe(dp);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, dp);
dp->adp = analogix_dp_probe(dev, &dp->plat_data);
if (IS_ERR(dp->adp))
return PTR_ERR(dp->adp);
return analogix_dp_finish_probe(dp->adp);
}
static void rockchip_dp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &rockchip_dp_component_ops);
}
static int rockchip_dp_suspend(struct device *dev)
{
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
if (IS_ERR(dp->adp))
return 0;
return analogix_dp_suspend(dp->adp);
}
static int rockchip_dp_resume(struct device *dev)
{
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
if (IS_ERR(dp->adp))
return 0;
return analogix_dp_resume(dp->adp);
}
static DEFINE_RUNTIME_DEV_PM_OPS(rockchip_dp_pm_ops, rockchip_dp_suspend,
rockchip_dp_resume, NULL);
static const struct rockchip_dp_chip_data rk3399_edp[] = {
{
.lcdc_sel = GRF_REG_FIELD(0x6250, 5, 5),
.chip_type = RK3399_EDP,
.reg = 0xff970000,
},
{ /* sentinel */ }
};
static const struct rockchip_dp_chip_data rk3288_dp[] = {
{
.lcdc_sel = GRF_REG_FIELD(0x025c, 5, 5),
.chip_type = RK3288_DP,
.reg = 0xff970000,
},
{ /* sentinel */ }
};
static const struct rockchip_dp_chip_data rk3588_edp[] = {
{
.edp_mode = GRF_REG_FIELD(0x0000, 0, 0),
.chip_type = RK3588_EDP,
.reg = 0xfdec0000,
},
{
.edp_mode = GRF_REG_FIELD(0x0004, 0, 0),
.chip_type = RK3588_EDP,
.reg = 0xfded0000,
},
{ /* sentinel */ }
};
static const struct of_device_id rockchip_dp_dt_ids[] = {
{.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp },
{.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp },
{.compatible = "rockchip,rk3588-edp", .data = &rk3588_edp },
{}
};
MODULE_DEVICE_TABLE(of, rockchip_dp_dt_ids);
struct platform_driver rockchip_dp_driver = {
.probe = rockchip_dp_probe,
.remove = rockchip_dp_remove,
.driver = {
.name = "rockchip-dp",
.pm = pm_ptr(&rockchip_dp_pm_ops),
.of_match_table = rockchip_dp_dt_ids,
},
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
* Copyright (C) Rockchip Electronics Co., Ltd.
*/
#ifndef _CDN_DP_CORE_H
#define _CDN_DP_CORE_H
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
#include <sound/hdmi-codec.h>
#include "rockchip_drm_drv.h"
#define MAX_PHY 2
enum audio_format {
AFMT_I2S = 0,
AFMT_SPDIF = 1,
AFMT_UNUSED,
};
struct audio_info {
enum audio_format format;
int sample_rate;
int channels;
int sample_width;
};
enum vic_pxl_encoding_format {
PXL_RGB = 0x1,
YCBCR_4_4_4 = 0x2,
YCBCR_4_2_2 = 0x4,
YCBCR_4_2_0 = 0x8,
Y_ONLY = 0x10,
};
struct video_info {
bool h_sync_polarity;
bool v_sync_polarity;
bool interlaced;
int color_depth;
enum vic_pxl_encoding_format color_fmt;
};
struct cdn_firmware_header {
u32 size_bytes; /* size of the entire header+image(s) in bytes */
u32 header_size; /* size of just the header in bytes */
u32 iram_size; /* size of iram */
u32 dram_size; /* size of dram */
};
struct cdn_dp_port {
struct cdn_dp_device *dp;
struct notifier_block event_nb;
struct extcon_dev *extcon;
struct phy *phy;
u8 lanes;
bool phy_enabled;
u8 id;
};
struct cdn_dp_device {
struct device *dev;
struct drm_device *drm_dev;
struct drm_bridge bridge;
struct rockchip_encoder encoder;
struct drm_display_mode mode;
struct platform_device *audio_pdev;
struct work_struct event_work;
struct mutex lock;
bool connected;
bool active;
bool suspended;
const struct firmware *fw; /* cdn dp firmware */
unsigned int fw_version; /* cdn fw version */
bool fw_loaded;
void __iomem *regs;
struct regmap *grf;
struct clk *core_clk;
struct clk *pclk;
struct clk *spdif_clk;
struct clk *grf_clk;
struct reset_control *spdif_rst;
struct reset_control *dptx_rst;
struct reset_control *apb_rst;
struct reset_control *core_rst;
struct audio_info audio_info;
struct video_info video_info;
struct cdn_dp_port *port[MAX_PHY];
u8 ports;
u8 max_lanes;
unsigned int max_rate;
u8 lanes;
int active_port;
u8 dpcd[DP_RECEIVER_CAP_SIZE];
};
#endif /* _CDN_DP_CORE_H */
@@ -0,0 +1,964 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Chris Zhong <zyw@rock-chips.com>
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/reset.h>
#include <drm/drm_print.h>
#include "cdn-dp-core.h"
#include "cdn-dp-reg.h"
#define CDN_DP_SPDIF_CLK 200000000
#define FW_ALIVE_TIMEOUT_US 1000000
#define MAILBOX_RETRY_US 1000
#define MAILBOX_TIMEOUT_US 5000000
#define LINK_TRAINING_RETRY_MS 20
#define LINK_TRAINING_TIMEOUT_MS 500
void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, unsigned long clk)
{
writel(clk / 1000000, dp->regs + SW_CLK_H);
}
void cdn_dp_clock_reset(struct cdn_dp_device *dp)
{
u32 val;
val = DPTX_FRMR_DATA_CLK_RSTN_EN |
DPTX_FRMR_DATA_CLK_EN |
DPTX_PHY_DATA_RSTN_EN |
DPTX_PHY_DATA_CLK_EN |
DPTX_PHY_CHAR_RSTN_EN |
DPTX_PHY_CHAR_CLK_EN |
SOURCE_AUX_SYS_CLK_RSTN_EN |
SOURCE_AUX_SYS_CLK_EN |
DPTX_SYS_CLK_RSTN_EN |
DPTX_SYS_CLK_EN |
CFG_DPTX_VIF_CLK_RSTN_EN |
CFG_DPTX_VIF_CLK_EN;
writel(val, dp->regs + SOURCE_DPTX_CAR);
val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
writel(val, dp->regs + SOURCE_PHY_CAR);
val = SOURCE_PKT_SYS_RSTN_EN |
SOURCE_PKT_SYS_CLK_EN |
SOURCE_PKT_DATA_RSTN_EN |
SOURCE_PKT_DATA_CLK_EN;
writel(val, dp->regs + SOURCE_PKT_CAR);
val = SPDIF_CDR_CLK_RSTN_EN |
SPDIF_CDR_CLK_EN |
SOURCE_AIF_SYS_RSTN_EN |
SOURCE_AIF_SYS_CLK_EN |
SOURCE_AIF_CLK_RSTN_EN |
SOURCE_AIF_CLK_EN;
writel(val, dp->regs + SOURCE_AIF_CAR);
val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
SOURCE_CIPHER_SYS_CLK_EN |
SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
SOURCE_CIPHER_CHAR_CLK_EN;
writel(val, dp->regs + SOURCE_CIPHER_CAR);
val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
SOURCE_CRYPTO_SYS_CLK_EN;
writel(val, dp->regs + SOURCE_CRYPTO_CAR);
/* enable Mailbox and PIF interrupt */
writel(0, dp->regs + APB_INT_MASK);
}
static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
{
int val, ret;
ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
val, !val, MAILBOX_RETRY_US,
MAILBOX_TIMEOUT_US);
if (ret < 0)
return ret;
return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
}
static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
{
int ret, full;
ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
full, !full, MAILBOX_RETRY_US,
MAILBOX_TIMEOUT_US);
if (ret < 0)
return ret;
writel(val, dp->regs + MAILBOX0_WR_DATA);
return 0;
}
static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
u8 module_id, u8 opcode,
u16 req_size)
{
u32 mbox_size, i;
u8 header[4];
int ret;
/* read the header of the message */
for (i = 0; i < 4; i++) {
ret = cdn_dp_mailbox_read(dp);
if (ret < 0)
return ret;
header[i] = ret;
}
mbox_size = (header[2] << 8) | header[3];
if (opcode != header[0] || module_id != header[1] ||
req_size != mbox_size) {
/*
* If the message in mailbox is not what we want, we need to
* clear the mailbox by reading its contents.
*/
for (i = 0; i < mbox_size; i++)
if (cdn_dp_mailbox_read(dp) < 0)
break;
return -EINVAL;
}
return 0;
}
static int cdn_dp_mailbox_read_receive(struct cdn_dp_device *dp,
u8 *buff, u16 buff_size)
{
u32 i;
int ret;
for (i = 0; i < buff_size; i++) {
ret = cdn_dp_mailbox_read(dp);
if (ret < 0)
return ret;
buff[i] = ret;
}
return 0;
}
static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
u8 opcode, u16 size, u8 *message)
{
u8 header[4];
int ret, i;
header[0] = opcode;
header[1] = module_id;
header[2] = (size >> 8) & 0xff;
header[3] = size & 0xff;
for (i = 0; i < 4; i++) {
ret = cdp_dp_mailbox_write(dp, header[i]);
if (ret)
return ret;
}
for (i = 0; i < size; i++) {
ret = cdp_dp_mailbox_write(dp, message[i]);
if (ret)
return ret;
}
return 0;
}
static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
{
u8 msg[6];
msg[0] = (addr >> 8) & 0xff;
msg[1] = addr & 0xff;
msg[2] = (val >> 24) & 0xff;
msg[3] = (val >> 16) & 0xff;
msg[4] = (val >> 8) & 0xff;
msg[5] = val & 0xff;
return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
sizeof(msg), msg);
}
static int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr,
u8 start_bit, u8 bits_no, u32 val)
{
u8 field[8];
field[0] = (addr >> 8) & 0xff;
field[1] = addr & 0xff;
field[2] = start_bit;
field[3] = bits_no;
field[4] = (val >> 24) & 0xff;
field[5] = (val >> 16) & 0xff;
field[6] = (val >> 8) & 0xff;
field[7] = val & 0xff;
return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
sizeof(field), field);
}
int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len)
{
u8 msg[5], reg[5];
int ret;
msg[0] = (len >> 8) & 0xff;
msg[1] = len & 0xff;
msg[2] = (addr >> 16) & 0xff;
msg[3] = (addr >> 8) & 0xff;
msg[4] = addr & 0xff;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD,
sizeof(msg), msg);
if (ret)
goto err_dpcd_read;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_READ_DPCD,
sizeof(reg) + len);
if (ret)
goto err_dpcd_read;
ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
if (ret)
goto err_dpcd_read;
ret = cdn_dp_mailbox_read_receive(dp, data, len);
err_dpcd_read:
return ret;
}
int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value)
{
u8 msg[6], reg[5];
int ret;
msg[0] = 0;
msg[1] = 1;
msg[2] = (addr >> 16) & 0xff;
msg[3] = (addr >> 8) & 0xff;
msg[4] = addr & 0xff;
msg[5] = value;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD,
sizeof(msg), msg);
if (ret)
goto err_dpcd_write;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_WRITE_DPCD, sizeof(reg));
if (ret)
goto err_dpcd_write;
ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
if (ret)
goto err_dpcd_write;
if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4]))
ret = -EINVAL;
err_dpcd_write:
if (ret)
DRM_DEV_ERROR(dp->dev, "dpcd write failed: %d\n", ret);
return ret;
}
int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
u32 i_size, const u32 *d_mem, u32 d_size)
{
u32 reg;
int i, ret;
/* reset ucpu before load firmware*/
writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
dp->regs + APB_CTRL);
for (i = 0; i < i_size; i += 4)
writel(*i_mem++, dp->regs + ADDR_IMEM + i);
for (i = 0; i < d_size; i += 4)
writel(*d_mem++, dp->regs + ADDR_DMEM + i);
/* un-reset ucpu */
writel(0, dp->regs + APB_CTRL);
/* check the keep alive register to make sure fw working */
ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
if (ret < 0) {
DRM_DEV_ERROR(dp->dev, "failed to loaded the FW reg = %x\n",
reg);
return -EINVAL;
}
reg = readl(dp->regs + VER_L) & 0xff;
dp->fw_version = reg;
reg = readl(dp->regs + VER_H) & 0xff;
dp->fw_version |= reg << 8;
reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
dp->fw_version |= reg << 16;
reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
dp->fw_version |= reg << 24;
DRM_DEV_DEBUG(dp->dev, "firmware version: %x\n", dp->fw_version);
return 0;
}
int cdn_dp_set_firmware_active(struct cdn_dp_device *dp, bool enable)
{
u8 msg[5];
int ret, i;
msg[0] = GENERAL_MAIN_CONTROL;
msg[1] = MB_MODULE_ID_GENERAL;
msg[2] = 0;
msg[3] = 1;
msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
for (i = 0; i < sizeof(msg); i++) {
ret = cdp_dp_mailbox_write(dp, msg[i]);
if (ret)
goto err_set_firmware_active;
}
/* read the firmware state */
for (i = 0; i < sizeof(msg); i++) {
ret = cdn_dp_mailbox_read(dp);
if (ret < 0)
goto err_set_firmware_active;
msg[i] = ret;
}
ret = 0;
err_set_firmware_active:
if (ret < 0)
DRM_DEV_ERROR(dp->dev, "set firmware active failed\n");
return ret;
}
int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes, bool flip)
{
u8 msg[8];
int ret;
msg[0] = CDN_DP_MAX_LINK_RATE;
msg[1] = lanes | SCRAMBLER_EN;
msg[2] = VOLTAGE_LEVEL_2;
msg[3] = PRE_EMPHASIS_LEVEL_3;
msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
msg[5] = FAST_LT_NOT_SUPPORT;
msg[6] = flip ? LANE_MAPPING_FLIPPED : LANE_MAPPING_NORMAL;
msg[7] = ENHANCED;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
DPTX_SET_HOST_CAPABILITIES,
sizeof(msg), msg);
if (ret)
goto err_set_host_cap;
ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
AUX_HOST_INVERT);
err_set_host_cap:
if (ret)
DRM_DEV_ERROR(dp->dev, "set host cap failed: %d\n", ret);
return ret;
}
int cdn_dp_event_config(struct cdn_dp_device *dp)
{
u8 msg[5];
int ret;
memset(msg, 0, sizeof(msg));
msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
sizeof(msg), msg);
if (ret)
DRM_DEV_ERROR(dp->dev, "set event config failed: %d\n", ret);
return ret;
}
u32 cdn_dp_get_event(struct cdn_dp_device *dp)
{
return readl(dp->regs + SW_EVENTS0);
}
int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
{
u8 status;
int ret;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
0, NULL);
if (ret)
goto err_get_hpd;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_HPD_STATE, sizeof(status));
if (ret)
goto err_get_hpd;
ret = cdn_dp_mailbox_read_receive(dp, &status, sizeof(status));
if (ret)
goto err_get_hpd;
return status;
err_get_hpd:
DRM_DEV_ERROR(dp->dev, "get hpd status failed: %d\n", ret);
return ret;
}
int cdn_dp_get_edid_block(void *data, u8 *edid,
unsigned int block, size_t length)
{
struct cdn_dp_device *dp = data;
u8 msg[2], reg[2], i;
int ret;
for (i = 0; i < 4; i++) {
msg[0] = block / 2;
msg[1] = block % 2;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
sizeof(msg), msg);
if (ret)
continue;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_GET_EDID,
sizeof(reg) + length);
if (ret)
continue;
ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
if (ret)
continue;
ret = cdn_dp_mailbox_read_receive(dp, edid, length);
if (ret)
continue;
if (reg[0] == length && reg[1] == block / 2)
break;
}
if (ret)
DRM_DEV_ERROR(dp->dev, "get block[%d] edid failed: %d\n", block,
ret);
return ret;
}
static int cdn_dp_training_start(struct cdn_dp_device *dp)
{
unsigned long timeout;
u8 msg, event[2];
int ret;
msg = LINK_TRAINING_RUN;
/* start training */
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
sizeof(msg), &msg);
if (ret)
goto err_training_start;
timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
while (time_before(jiffies, timeout)) {
msleep(LINK_TRAINING_RETRY_MS);
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
DPTX_READ_EVENT, 0, NULL);
if (ret)
goto err_training_start;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_READ_EVENT,
sizeof(event));
if (ret)
goto err_training_start;
ret = cdn_dp_mailbox_read_receive(dp, event, sizeof(event));
if (ret)
goto err_training_start;
if (event[1] & EQ_PHASE_FINISHED)
return 0;
}
ret = -ETIMEDOUT;
err_training_start:
DRM_DEV_ERROR(dp->dev, "training failed: %d\n", ret);
return ret;
}
static int cdn_dp_get_training_status(struct cdn_dp_device *dp)
{
u8 status[10];
int ret;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
0, NULL);
if (ret)
goto err_get_training_status;
ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
DPTX_READ_LINK_STAT,
sizeof(status));
if (ret)
goto err_get_training_status;
ret = cdn_dp_mailbox_read_receive(dp, status, sizeof(status));
if (ret)
goto err_get_training_status;
dp->max_rate = drm_dp_bw_code_to_link_rate(status[0]);
dp->max_lanes = status[1];
err_get_training_status:
if (ret)
DRM_DEV_ERROR(dp->dev, "get training status failed: %d\n", ret);
return ret;
}
int cdn_dp_train_link(struct cdn_dp_device *dp)
{
int ret;
ret = cdn_dp_training_start(dp);
if (ret) {
DRM_DEV_ERROR(dp->dev, "Failed to start training %d\n", ret);
return ret;
}
ret = cdn_dp_get_training_status(dp);
if (ret) {
DRM_DEV_ERROR(dp->dev, "Failed to get training stat %d\n", ret);
return ret;
}
DRM_DEV_DEBUG_KMS(dp->dev, "rate:0x%x, lanes:%d\n", dp->max_rate,
dp->max_lanes);
return ret;
}
int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
{
u8 msg;
int ret;
msg = !!active;
ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
sizeof(msg), &msg);
if (ret)
DRM_DEV_ERROR(dp->dev, "set video status failed: %d\n", ret);
return ret;
}
static int cdn_dp_get_msa_misc(struct video_info *video,
struct drm_display_mode *mode)
{
u32 msa_misc;
u8 val[2] = {0};
switch (video->color_fmt) {
case PXL_RGB:
case Y_ONLY:
val[0] = 0;
break;
/* set YUV default color space conversion to BT601 */
case YCBCR_4_4_4:
val[0] = 6 + BT_601 * 8;
break;
case YCBCR_4_2_2:
val[0] = 5 + BT_601 * 8;
break;
case YCBCR_4_2_0:
val[0] = 5;
break;
}
switch (video->color_depth) {
case 6:
val[1] = 0;
break;
case 8:
val[1] = 1;
break;
case 10:
val[1] = 2;
break;
case 12:
val[1] = 3;
break;
case 16:
val[1] = 4;
break;
}
msa_misc = 2 * val[0] + 32 * val[1] +
((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
return msa_misc;
}
int cdn_dp_config_video(struct cdn_dp_device *dp)
{
struct video_info *video = &dp->video_info;
struct drm_display_mode *mode = &dp->mode;
u64 symbol;
u32 val, link_rate, rem;
u8 bit_per_pix, tu_size_reg = TU_SIZE;
int ret;
bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
(video->color_depth * 2) : (video->color_depth * 3);
link_rate = dp->max_rate / 1000;
ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, VIF_BYPASS_INTERLACE);
if (ret)
goto err_config_video;
ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
if (ret)
goto err_config_video;
/*
* get a best tu_size and valid symbol:
* 1. chose Lclk freq(162Mhz, 270Mhz, 540Mhz), set TU to 32
* 2. calculate VS(valid symbol) = TU * Pclk * Bpp / (Lclk * Lanes)
* 3. if VS > *.85 or VS < *.1 or VS < 2 or TU < VS + 4, then set
* TU += 2 and repeat 2nd step.
*/
do {
tu_size_reg += 2;
symbol = (u64)tu_size_reg * mode->clock * bit_per_pix;
do_div(symbol, dp->max_lanes * link_rate * 8);
rem = do_div(symbol, 1000);
if (tu_size_reg > 64) {
ret = -EINVAL;
DRM_DEV_ERROR(dp->dev,
"tu error, clk:%d, lanes:%d, rate:%d\n",
mode->clock, dp->max_lanes, link_rate);
goto err_config_video;
}
} while ((symbol <= 1) || (tu_size_reg - symbol < 4) ||
(rem > 850) || (rem < 100));
val = symbol + (tu_size_reg << 8);
val |= TU_CNT_RST_EN;
ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
if (ret)
goto err_config_video;
/* set the FIFO Buffer size */
val = div_u64(mode->clock * (symbol + 1), 1000) + link_rate;
val /= (dp->max_lanes * link_rate);
val = div_u64(8 * (symbol + 1), bit_per_pix) - val;
val += 2;
ret = cdn_dp_reg_write(dp, DP_VC_TABLE(15), val);
if (ret)
goto err_config_video;
switch (video->color_depth) {
case 6:
val = BCS_6;
break;
case 8:
val = BCS_8;
break;
case 10:
val = BCS_10;
break;
case 12:
val = BCS_12;
break;
case 16:
val = BCS_16;
break;
}
val += video->color_fmt << 8;
ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
if (ret)
goto err_config_video;
val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
if (ret)
goto err_config_video;
val = (mode->hsync_start - mode->hdisplay) << 16;
val |= mode->htotal - mode->hsync_end;
ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
if (ret)
goto err_config_video;
val = mode->hdisplay * bit_per_pix / 8;
ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
if (ret)
goto err_config_video;
val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
if (ret)
goto err_config_video;
val = mode->hsync_end - mode->hsync_start;
val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
if (ret)
goto err_config_video;
val = mode->vtotal;
val |= (mode->vtotal - mode->vsync_start) << 16;
ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
if (ret)
goto err_config_video;
val = mode->vsync_end - mode->vsync_start;
val |= (mode->vdisplay << 16) | (video->v_sync_polarity << 15);
ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
if (ret)
goto err_config_video;
val = cdn_dp_get_msa_misc(video, mode);
ret = cdn_dp_reg_write(dp, MSA_MISC, val);
if (ret)
goto err_config_video;
ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
if (ret)
goto err_config_video;
val = mode->hsync_end - mode->hsync_start;
val |= mode->hdisplay << 16;
ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
if (ret)
goto err_config_video;
val = mode->vdisplay;
val |= (mode->vtotal - mode->vsync_start) << 16;
ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
if (ret)
goto err_config_video;
val = mode->vtotal;
ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
if (ret)
goto err_config_video;
ret = cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, 0);
err_config_video:
if (ret)
DRM_DEV_ERROR(dp->dev, "config video failed: %d\n", ret);
return ret;
}
int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
{
int ret;
ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, 0);
if (ret) {
DRM_DEV_ERROR(dp->dev, "audio stop failed: %d\n", ret);
return ret;
}
writel(0, dp->regs + SPDIF_CTRL_ADDR);
/* clearn the audio config and reset */
writel(0, dp->regs + AUDIO_SRC_CNTL);
writel(0, dp->regs + AUDIO_SRC_CNFG);
writel(AUDIO_SW_RST, dp->regs + AUDIO_SRC_CNTL);
writel(0, dp->regs + AUDIO_SRC_CNTL);
/* reset smpl2pckt component */
writel(0, dp->regs + SMPL2PKT_CNTL);
writel(AUDIO_SW_RST, dp->regs + SMPL2PKT_CNTL);
writel(0, dp->regs + SMPL2PKT_CNTL);
/* reset FIFO */
writel(AUDIO_SW_RST, dp->regs + FIFO_CNTL);
writel(0, dp->regs + FIFO_CNTL);
if (audio->format == AFMT_SPDIF)
clk_disable_unprepare(dp->spdif_clk);
return 0;
}
int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
{
int ret;
ret = cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
if (ret)
DRM_DEV_ERROR(dp->dev, "audio mute failed: %d\n", ret);
return ret;
}
static void cdn_dp_audio_config_i2s(struct cdn_dp_device *dp,
struct audio_info *audio)
{
int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
u32 val;
if (audio->channels == 2) {
if (dp->max_lanes == 1)
sub_pckt_num = 2;
else
sub_pckt_num = 4;
i2s_port_en_val = 1;
} else if (audio->channels == 4) {
i2s_port_en_val = 3;
}
writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
val = MAX_NUM_CH(audio->channels);
val |= NUM_OF_I2S_PORTS(audio->channels);
val |= AUDIO_TYPE_LPCM;
val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
writel(val, dp->regs + SMPL2PKT_CNFG);
if (audio->sample_width == 16)
val = 0;
else if (audio->sample_width == 24)
val = 1 << 9;
else
val = 2 << 9;
val |= AUDIO_CH_NUM(audio->channels);
val |= I2S_DEC_PORT_EN(i2s_port_en_val);
val |= TRANS_SMPL_WIDTH_32;
writel(val, dp->regs + AUDIO_SRC_CNFG);
for (i = 0; i < (audio->channels + 1) / 2; i++) {
if (audio->sample_width == 16)
val = (0x02 << 8) | (0x02 << 20);
else if (audio->sample_width == 24)
val = (0x0b << 8) | (0x0b << 20);
val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
writel(val, dp->regs + STTS_BIT_CH(i));
}
switch (audio->sample_rate) {
case 32000:
val = SAMPLING_FREQ(3) |
ORIGINAL_SAMP_FREQ(0xc);
break;
case 44100:
val = SAMPLING_FREQ(0) |
ORIGINAL_SAMP_FREQ(0xf);
break;
case 48000:
val = SAMPLING_FREQ(2) |
ORIGINAL_SAMP_FREQ(0xd);
break;
case 88200:
val = SAMPLING_FREQ(8) |
ORIGINAL_SAMP_FREQ(0x7);
break;
case 96000:
val = SAMPLING_FREQ(0xa) |
ORIGINAL_SAMP_FREQ(5);
break;
case 176400:
val = SAMPLING_FREQ(0xc) |
ORIGINAL_SAMP_FREQ(3);
break;
case 192000:
val = SAMPLING_FREQ(0xe) |
ORIGINAL_SAMP_FREQ(1);
break;
}
val |= 4;
writel(val, dp->regs + COM_CH_STTS_BITS);
writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
writel(I2S_DEC_START, dp->regs + AUDIO_SRC_CNTL);
}
static void cdn_dp_audio_config_spdif(struct cdn_dp_device *dp)
{
u32 val;
writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
writel(val, dp->regs + SMPL2PKT_CNFG);
writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
writel(val, dp->regs + SPDIF_CTRL_ADDR);
clk_prepare_enable(dp->spdif_clk);
clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
}
int cdn_dp_audio_config(struct cdn_dp_device *dp, struct audio_info *audio)
{
int ret;
/* reset the spdif clk before config */
if (audio->format == AFMT_SPDIF) {
reset_control_assert(dp->spdif_rst);
reset_control_deassert(dp->spdif_rst);
}
ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, LANE_REF_CYC);
if (ret)
goto err_audio_config;
ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
if (ret)
goto err_audio_config;
if (audio->format == AFMT_I2S)
cdn_dp_audio_config_i2s(dp, audio);
else if (audio->format == AFMT_SPDIF)
cdn_dp_audio_config_spdif(dp);
ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
err_audio_config:
if (ret)
DRM_DEV_ERROR(dp->dev, "audio config failed: %d\n", ret);
return ret;
}
@@ -0,0 +1,474 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Chris Zhong <zyw@rock-chips.com>
*/
#ifndef _CDN_DP_REG_H
#define _CDN_DP_REG_H
#include <linux/bitops.h>
#define ADDR_IMEM 0x10000
#define ADDR_DMEM 0x20000
/* APB CFG addr */
#define APB_CTRL 0
#define XT_INT_CTRL 0x04
#define MAILBOX_FULL_ADDR 0x08
#define MAILBOX_EMPTY_ADDR 0x0c
#define MAILBOX0_WR_DATA 0x10
#define MAILBOX0_RD_DATA 0x14
#define KEEP_ALIVE 0x18
#define VER_L 0x1c
#define VER_H 0x20
#define VER_LIB_L_ADDR 0x24
#define VER_LIB_H_ADDR 0x28
#define SW_DEBUG_L 0x2c
#define SW_DEBUG_H 0x30
#define MAILBOX_INT_MASK 0x34
#define MAILBOX_INT_STATUS 0x38
#define SW_CLK_L 0x3c
#define SW_CLK_H 0x40
#define SW_EVENTS0 0x44
#define SW_EVENTS1 0x48
#define SW_EVENTS2 0x4c
#define SW_EVENTS3 0x50
#define XT_OCD_CTRL 0x60
#define APB_INT_MASK 0x6c
#define APB_STATUS_MASK 0x70
/* audio decoder addr */
#define AUDIO_SRC_CNTL 0x30000
#define AUDIO_SRC_CNFG 0x30004
#define COM_CH_STTS_BITS 0x30008
#define STTS_BIT_CH(x) (0x3000c + ((x) << 2))
#define SPDIF_CTRL_ADDR 0x3004c
#define SPDIF_CH1_CS_3100_ADDR 0x30050
#define SPDIF_CH1_CS_6332_ADDR 0x30054
#define SPDIF_CH1_CS_9564_ADDR 0x30058
#define SPDIF_CH1_CS_12796_ADDR 0x3005c
#define SPDIF_CH1_CS_159128_ADDR 0x30060
#define SPDIF_CH1_CS_191160_ADDR 0x30064
#define SPDIF_CH2_CS_3100_ADDR 0x30068
#define SPDIF_CH2_CS_6332_ADDR 0x3006c
#define SPDIF_CH2_CS_9564_ADDR 0x30070
#define SPDIF_CH2_CS_12796_ADDR 0x30074
#define SPDIF_CH2_CS_159128_ADDR 0x30078
#define SPDIF_CH2_CS_191160_ADDR 0x3007c
#define SMPL2PKT_CNTL 0x30080
#define SMPL2PKT_CNFG 0x30084
#define FIFO_CNTL 0x30088
#define FIFO_STTS 0x3008c
/* source pif addr */
#define SOURCE_PIF_WR_ADDR 0x30800
#define SOURCE_PIF_WR_REQ 0x30804
#define SOURCE_PIF_RD_ADDR 0x30808
#define SOURCE_PIF_RD_REQ 0x3080c
#define SOURCE_PIF_DATA_WR 0x30810
#define SOURCE_PIF_DATA_RD 0x30814
#define SOURCE_PIF_FIFO1_FLUSH 0x30818
#define SOURCE_PIF_FIFO2_FLUSH 0x3081c
#define SOURCE_PIF_STATUS 0x30820
#define SOURCE_PIF_INTERRUPT_SOURCE 0x30824
#define SOURCE_PIF_INTERRUPT_MASK 0x30828
#define SOURCE_PIF_PKT_ALLOC_REG 0x3082c
#define SOURCE_PIF_PKT_ALLOC_WR_EN 0x30830
#define SOURCE_PIF_SW_RESET 0x30834
/* below registers need access by mailbox */
/* source car addr */
#define SOURCE_HDTX_CAR 0x0900
#define SOURCE_DPTX_CAR 0x0904
#define SOURCE_PHY_CAR 0x0908
#define SOURCE_CEC_CAR 0x090c
#define SOURCE_CBUS_CAR 0x0910
#define SOURCE_PKT_CAR 0x0918
#define SOURCE_AIF_CAR 0x091c
#define SOURCE_CIPHER_CAR 0x0920
#define SOURCE_CRYPTO_CAR 0x0924
/* clock meters addr */
#define CM_CTRL 0x0a00
#define CM_I2S_CTRL 0x0a04
#define CM_SPDIF_CTRL 0x0a08
#define CM_VID_CTRL 0x0a0c
#define CM_LANE_CTRL 0x0a10
#define I2S_NM_STABLE 0x0a14
#define I2S_NCTS_STABLE 0x0a18
#define SPDIF_NM_STABLE 0x0a1c
#define SPDIF_NCTS_STABLE 0x0a20
#define NMVID_MEAS_STABLE 0x0a24
#define I2S_MEAS 0x0a40
#define SPDIF_MEAS 0x0a80
#define NMVID_MEAS 0x0ac0
/* source vif addr */
#define BND_HSYNC2VSYNC 0x0b00
#define HSYNC2VSYNC_F1_L1 0x0b04
#define HSYNC2VSYNC_F2_L1 0x0b08
#define HSYNC2VSYNC_STATUS 0x0b0c
#define HSYNC2VSYNC_POL_CTRL 0x0b10
/* dptx phy addr */
#define DP_TX_PHY_CONFIG_REG 0x2000
#define DP_TX_PHY_SW_RESET 0x2004
#define DP_TX_PHY_SCRAMBLER_SEED 0x2008
#define DP_TX_PHY_TRAINING_01_04 0x200c
#define DP_TX_PHY_TRAINING_05_08 0x2010
#define DP_TX_PHY_TRAINING_09_10 0x2014
#define TEST_COR 0x23fc
/* dptx hpd addr */
#define HPD_IRQ_DET_MIN_TIMER 0x2100
#define HPD_IRQ_DET_MAX_TIMER 0x2104
#define HPD_UNPLGED_DET_MIN_TIMER 0x2108
#define HPD_STABLE_TIMER 0x210c
#define HPD_FILTER_TIMER 0x2110
#define HPD_EVENT_MASK 0x211c
#define HPD_EVENT_DET 0x2120
/* dpyx framer addr */
#define DP_FRAMER_GLOBAL_CONFIG 0x2200
#define DP_SW_RESET 0x2204
#define DP_FRAMER_TU 0x2208
#define DP_FRAMER_PXL_REPR 0x220c
#define DP_FRAMER_SP 0x2210
#define AUDIO_PACK_CONTROL 0x2214
#define DP_VC_TABLE(x) (0x2218 + ((x) << 2))
#define DP_VB_ID 0x2258
#define DP_MTPH_LVP_CONTROL 0x225c
#define DP_MTPH_SYMBOL_VALUES 0x2260
#define DP_MTPH_ECF_CONTROL 0x2264
#define DP_MTPH_ACT_CONTROL 0x2268
#define DP_MTPH_STATUS 0x226c
#define DP_INTERRUPT_SOURCE 0x2270
#define DP_INTERRUPT_MASK 0x2274
#define DP_FRONT_BACK_PORCH 0x2278
#define DP_BYTE_COUNT 0x227c
/* dptx stream addr */
#define MSA_HORIZONTAL_0 0x2280
#define MSA_HORIZONTAL_1 0x2284
#define MSA_VERTICAL_0 0x2288
#define MSA_VERTICAL_1 0x228c
#define MSA_MISC 0x2290
#define STREAM_CONFIG 0x2294
#define AUDIO_PACK_STATUS 0x2298
#define VIF_STATUS 0x229c
#define PCK_STUFF_STATUS_0 0x22a0
#define PCK_STUFF_STATUS_1 0x22a4
#define INFO_PACK_STATUS 0x22a8
#define RATE_GOVERNOR_STATUS 0x22ac
#define DP_HORIZONTAL 0x22b0
#define DP_VERTICAL_0 0x22b4
#define DP_VERTICAL_1 0x22b8
#define DP_BLOCK_SDP 0x22bc
/* dptx glbl addr */
#define DPTX_LANE_EN 0x2300
#define DPTX_ENHNCD 0x2304
#define DPTX_INT_MASK 0x2308
#define DPTX_INT_STATUS 0x230c
/* dp aux addr */
#define DP_AUX_HOST_CONTROL 0x2800
#define DP_AUX_INTERRUPT_SOURCE 0x2804
#define DP_AUX_INTERRUPT_MASK 0x2808
#define DP_AUX_SWAP_INVERSION_CONTROL 0x280c
#define DP_AUX_SEND_NACK_TRANSACTION 0x2810
#define DP_AUX_CLEAR_RX 0x2814
#define DP_AUX_CLEAR_TX 0x2818
#define DP_AUX_TIMER_STOP 0x281c
#define DP_AUX_TIMER_CLEAR 0x2820
#define DP_AUX_RESET_SW 0x2824
#define DP_AUX_DIVIDE_2M 0x2828
#define DP_AUX_TX_PREACHARGE_LENGTH 0x282c
#define DP_AUX_FREQUENCY_1M_MAX 0x2830
#define DP_AUX_FREQUENCY_1M_MIN 0x2834
#define DP_AUX_RX_PRE_MIN 0x2838
#define DP_AUX_RX_PRE_MAX 0x283c
#define DP_AUX_TIMER_PRESET 0x2840
#define DP_AUX_NACK_FORMAT 0x2844
#define DP_AUX_TX_DATA 0x2848
#define DP_AUX_RX_DATA 0x284c
#define DP_AUX_TX_STATUS 0x2850
#define DP_AUX_RX_STATUS 0x2854
#define DP_AUX_RX_CYCLE_COUNTER 0x2858
#define DP_AUX_MAIN_STATES 0x285c
#define DP_AUX_MAIN_TIMER 0x2860
#define DP_AUX_AFE_OUT 0x2864
/* crypto addr */
#define CRYPTO_HDCP_REVISION 0x5800
#define HDCP_CRYPTO_CONFIG 0x5804
#define CRYPTO_INTERRUPT_SOURCE 0x5808
#define CRYPTO_INTERRUPT_MASK 0x580c
#define CRYPTO22_CONFIG 0x5818
#define CRYPTO22_STATUS 0x581c
#define SHA_256_DATA_IN 0x583c
#define SHA_256_DATA_OUT_(x) (0x5850 + ((x) << 2))
#define AES_32_KEY_(x) (0x5870 + ((x) << 2))
#define AES_32_DATA_IN 0x5880
#define AES_32_DATA_OUT_(x) (0x5884 + ((x) << 2))
#define CRYPTO14_CONFIG 0x58a0
#define CRYPTO14_STATUS 0x58a4
#define CRYPTO14_PRNM_OUT 0x58a8
#define CRYPTO14_KM_0 0x58ac
#define CRYPTO14_KM_1 0x58b0
#define CRYPTO14_AN_0 0x58b4
#define CRYPTO14_AN_1 0x58b8
#define CRYPTO14_YOUR_KSV_0 0x58bc
#define CRYPTO14_YOUR_KSV_1 0x58c0
#define CRYPTO14_MI_0 0x58c4
#define CRYPTO14_MI_1 0x58c8
#define CRYPTO14_TI_0 0x58cc
#define CRYPTO14_KI_0 0x58d0
#define CRYPTO14_KI_1 0x58d4
#define CRYPTO14_BLOCKS_NUM 0x58d8
#define CRYPTO14_KEY_MEM_DATA_0 0x58dc
#define CRYPTO14_KEY_MEM_DATA_1 0x58e0
#define CRYPTO14_SHA1_MSG_DATA 0x58e4
#define CRYPTO14_SHA1_V_VALUE_(x) (0x58e8 + ((x) << 2))
#define TRNG_CTRL 0x58fc
#define TRNG_DATA_RDY 0x5900
#define TRNG_DATA 0x5904
/* cipher addr */
#define HDCP_REVISION 0x60000
#define INTERRUPT_SOURCE 0x60004
#define INTERRUPT_MASK 0x60008
#define HDCP_CIPHER_CONFIG 0x6000c
#define AES_128_KEY_0 0x60010
#define AES_128_KEY_1 0x60014
#define AES_128_KEY_2 0x60018
#define AES_128_KEY_3 0x6001c
#define AES_128_RANDOM_0 0x60020
#define AES_128_RANDOM_1 0x60024
#define CIPHER14_KM_0 0x60028
#define CIPHER14_KM_1 0x6002c
#define CIPHER14_STATUS 0x60030
#define CIPHER14_RI_PJ_STATUS 0x60034
#define CIPHER_MODE 0x60038
#define CIPHER14_AN_0 0x6003c
#define CIPHER14_AN_1 0x60040
#define CIPHER22_AUTH 0x60044
#define CIPHER14_R0_DP_STATUS 0x60048
#define CIPHER14_BOOTSTRAP 0x6004c
#define DPTX_FRMR_DATA_CLK_RSTN_EN BIT(11)
#define DPTX_FRMR_DATA_CLK_EN BIT(10)
#define DPTX_PHY_DATA_RSTN_EN BIT(9)
#define DPTX_PHY_DATA_CLK_EN BIT(8)
#define DPTX_PHY_CHAR_RSTN_EN BIT(7)
#define DPTX_PHY_CHAR_CLK_EN BIT(6)
#define SOURCE_AUX_SYS_CLK_RSTN_EN BIT(5)
#define SOURCE_AUX_SYS_CLK_EN BIT(4)
#define DPTX_SYS_CLK_RSTN_EN BIT(3)
#define DPTX_SYS_CLK_EN BIT(2)
#define CFG_DPTX_VIF_CLK_RSTN_EN BIT(1)
#define CFG_DPTX_VIF_CLK_EN BIT(0)
#define SOURCE_PHY_RSTN_EN BIT(1)
#define SOURCE_PHY_CLK_EN BIT(0)
#define SOURCE_PKT_SYS_RSTN_EN BIT(3)
#define SOURCE_PKT_SYS_CLK_EN BIT(2)
#define SOURCE_PKT_DATA_RSTN_EN BIT(1)
#define SOURCE_PKT_DATA_CLK_EN BIT(0)
#define SPDIF_CDR_CLK_RSTN_EN BIT(5)
#define SPDIF_CDR_CLK_EN BIT(4)
#define SOURCE_AIF_SYS_RSTN_EN BIT(3)
#define SOURCE_AIF_SYS_CLK_EN BIT(2)
#define SOURCE_AIF_CLK_RSTN_EN BIT(1)
#define SOURCE_AIF_CLK_EN BIT(0)
#define SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN BIT(3)
#define SOURCE_CIPHER_SYS_CLK_EN BIT(2)
#define SOURCE_CIPHER_CHAR_CLK_RSTN_EN BIT(1)
#define SOURCE_CIPHER_CHAR_CLK_EN BIT(0)
#define SOURCE_CRYPTO_SYS_CLK_RSTN_EN BIT(1)
#define SOURCE_CRYPTO_SYS_CLK_EN BIT(0)
#define APB_IRAM_PATH BIT(2)
#define APB_DRAM_PATH BIT(1)
#define APB_XT_RESET BIT(0)
#define MAILBOX_INT_MASK_BIT BIT(1)
#define PIF_INT_MASK_BIT BIT(0)
#define ALL_INT_MASK 3
/* mailbox */
#define MB_OPCODE_ID 0
#define MB_MODULE_ID 1
#define MB_SIZE_MSB_ID 2
#define MB_SIZE_LSB_ID 3
#define MB_DATA_ID 4
#define MB_MODULE_ID_DP_TX 0x01
#define MB_MODULE_ID_HDCP_TX 0x07
#define MB_MODULE_ID_HDCP_RX 0x08
#define MB_MODULE_ID_HDCP_GENERAL 0x09
#define MB_MODULE_ID_GENERAL 0x0a
/* general opcode */
#define GENERAL_MAIN_CONTROL 0x01
#define GENERAL_TEST_ECHO 0x02
#define GENERAL_BUS_SETTINGS 0x03
#define GENERAL_TEST_ACCESS 0x04
#define DPTX_SET_POWER_MNG 0x00
#define DPTX_SET_HOST_CAPABILITIES 0x01
#define DPTX_GET_EDID 0x02
#define DPTX_READ_DPCD 0x03
#define DPTX_WRITE_DPCD 0x04
#define DPTX_ENABLE_EVENT 0x05
#define DPTX_WRITE_REGISTER 0x06
#define DPTX_READ_REGISTER 0x07
#define DPTX_WRITE_FIELD 0x08
#define DPTX_TRAINING_CONTROL 0x09
#define DPTX_READ_EVENT 0x0a
#define DPTX_READ_LINK_STAT 0x0b
#define DPTX_SET_VIDEO 0x0c
#define DPTX_SET_AUDIO 0x0d
#define DPTX_GET_LAST_AUX_STAUS 0x0e
#define DPTX_SET_LINK_BREAK_POINT 0x0f
#define DPTX_FORCE_LANES 0x10
#define DPTX_HPD_STATE 0x11
#define FW_STANDBY 0
#define FW_ACTIVE 1
#define DPTX_EVENT_ENABLE_HPD BIT(0)
#define DPTX_EVENT_ENABLE_TRAINING BIT(1)
#define LINK_TRAINING_NOT_ACTIVE 0
#define LINK_TRAINING_RUN 1
#define LINK_TRAINING_RESTART 2
#define CONTROL_VIDEO_IDLE 0
#define CONTROL_VIDEO_VALID 1
#define TU_CNT_RST_EN BIT(15)
#define VIF_BYPASS_INTERLACE BIT(13)
#define INTERLACE_FMT_DET BIT(12)
#define INTERLACE_DTCT_WIN 0x20
#define DP_FRAMER_SP_INTERLACE_EN BIT(2)
#define DP_FRAMER_SP_HSP BIT(1)
#define DP_FRAMER_SP_VSP BIT(0)
/* capability */
#define AUX_HOST_INVERT 3
#define FAST_LT_SUPPORT 1
#define FAST_LT_NOT_SUPPORT 0
#define LANE_MAPPING_NORMAL 0x1b
#define LANE_MAPPING_FLIPPED 0xe4
#define ENHANCED 1
#define SCRAMBLER_EN BIT(4)
#define FULL_LT_STARTED BIT(0)
#define FASE_LT_STARTED BIT(1)
#define CLK_RECOVERY_FINISHED BIT(2)
#define EQ_PHASE_FINISHED BIT(3)
#define FASE_LT_START_FINISHED BIT(4)
#define CLK_RECOVERY_FAILED BIT(5)
#define EQ_PHASE_FAILED BIT(6)
#define FASE_LT_FAILED BIT(7)
#define DPTX_HPD_EVENT BIT(0)
#define DPTX_TRAINING_EVENT BIT(1)
#define HDCP_TX_STATUS_EVENT BIT(4)
#define HDCP2_TX_IS_KM_STORED_EVENT BIT(5)
#define HDCP2_TX_STORE_KM_EVENT BIT(6)
#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT BIT(7)
#define TU_SIZE 30
#define CDN_DP_MAX_LINK_RATE DP_LINK_BW_5_4
/* audio */
#define AUDIO_PACK_EN BIT(8)
#define SAMPLING_FREQ(x) (((x) & 0xf) << 16)
#define ORIGINAL_SAMP_FREQ(x) (((x) & 0xf) << 24)
#define SYNC_WR_TO_CH_ZERO BIT(1)
#define I2S_DEC_START BIT(1)
#define AUDIO_SW_RST BIT(0)
#define SMPL2PKT_EN BIT(1)
#define MAX_NUM_CH(x) (((x) & 0x1f) - 1)
#define NUM_OF_I2S_PORTS(x) ((((x) / 2 - 1) & 0x3) << 5)
#define AUDIO_TYPE_LPCM (2 << 7)
#define CFG_SUB_PCKT_NUM(x) ((((x) - 1) & 0x7) << 11)
#define AUDIO_CH_NUM(x) ((((x) - 1) & 0x1f) << 2)
#define TRANS_SMPL_WIDTH_16 0
#define TRANS_SMPL_WIDTH_24 BIT(11)
#define TRANS_SMPL_WIDTH_32 (2 << 11)
#define I2S_DEC_PORT_EN(x) (((x) & 0xf) << 17)
#define SPDIF_ENABLE BIT(21)
#define SPDIF_AVG_SEL BIT(20)
#define SPDIF_JITTER_BYPASS BIT(19)
#define SPDIF_FIFO_MID_RANGE(x) (((x) & 0xff) << 11)
#define SPDIF_JITTER_THRSH(x) (((x) & 0xff) << 3)
#define SPDIF_JITTER_AVG_WIN(x) ((x) & 0x7)
/* Reference cycles when using lane clock as reference */
#define LANE_REF_CYC 0x8000
enum voltage_swing_level {
VOLTAGE_LEVEL_0,
VOLTAGE_LEVEL_1,
VOLTAGE_LEVEL_2,
VOLTAGE_LEVEL_3,
};
enum pre_emphasis_level {
PRE_EMPHASIS_LEVEL_0,
PRE_EMPHASIS_LEVEL_1,
PRE_EMPHASIS_LEVEL_2,
PRE_EMPHASIS_LEVEL_3,
};
enum pattern_set {
PTS1 = BIT(0),
PTS2 = BIT(1),
PTS3 = BIT(2),
PTS4 = BIT(3),
DP_NONE = BIT(4)
};
enum vic_color_depth {
BCS_6 = 0x1,
BCS_8 = 0x2,
BCS_10 = 0x4,
BCS_12 = 0x8,
BCS_16 = 0x10,
};
enum vic_bt_type {
BT_601 = 0x0,
BT_709 = 0x1,
};
void cdn_dp_clock_reset(struct cdn_dp_device *dp);
void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, unsigned long clk);
int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
u32 i_size, const u32 *d_mem, u32 d_size);
int cdn_dp_set_firmware_active(struct cdn_dp_device *dp, bool enable);
int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes, bool flip);
int cdn_dp_event_config(struct cdn_dp_device *dp);
u32 cdn_dp_get_event(struct cdn_dp_device *dp);
int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len);
int cdn_dp_get_edid_block(void *dp, u8 *edid,
unsigned int block, size_t length);
int cdn_dp_train_link(struct cdn_dp_device *dp);
int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
int cdn_dp_config_video(struct cdn_dp_device *dp);
int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
int cdn_dp_audio_config(struct cdn_dp_device *dp, struct audio_info *audio);
#endif /* _CDN_DP_REG_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,507 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2024 Rockchip Electronics Co., Ltd.
* Author:
* Guochun Huang <hero.huang@rock-chips.com>
* Heiko Stuebner <heiko.stuebner@cherry.de>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/mfd/syscon.h>
#include <linux/phy/phy.h>
#include <drm/bridge/dw_mipi_dsi2.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_simple_kms_helper.h>
#include <uapi/linux/videodev2.h>
#include "rockchip_drm_drv.h"
#define PSEC_PER_SEC 1000000000000LL
struct dsigrf_reg {
u16 offset;
u16 lsb;
u16 msb;
};
enum grf_reg_fields {
TXREQCLKHS_EN,
GATING_EN,
IPI_SHUTDN,
IPI_COLORM,
IPI_COLOR_DEPTH,
IPI_FORMAT,
MAX_FIELDS,
};
#define IPI_DEPTH_5_6_5_BITS 0x02
#define IPI_DEPTH_6_BITS 0x03
#define IPI_DEPTH_8_BITS 0x05
#define IPI_DEPTH_10_BITS 0x06
struct rockchip_dw_dsi2_chip_data {
u32 reg;
const struct dsigrf_reg *grf_regs;
unsigned long long max_bit_rate_per_lane;
};
struct dw_mipi_dsi2_rockchip {
struct device *dev;
struct rockchip_encoder encoder;
struct regmap *regmap;
unsigned int lane_mbps; /* per lane */
u32 format;
struct regmap *grf_regmap;
struct phy *phy;
union phy_configure_opts phy_opts;
struct dw_mipi_dsi2 *dmd;
struct dw_mipi_dsi2_plat_data pdata;
const struct rockchip_dw_dsi2_chip_data *cdata;
};
static inline struct dw_mipi_dsi2_rockchip *to_dsi2(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct dw_mipi_dsi2_rockchip, encoder);
}
static void grf_field_write(struct dw_mipi_dsi2_rockchip *dsi2, enum grf_reg_fields index,
unsigned int val)
{
const struct dsigrf_reg *field = &dsi2->cdata->grf_regs[index];
if (!field)
return;
regmap_write(dsi2->grf_regmap, field->offset,
(val << field->lsb) | (GENMASK(field->msb, field->lsb) << 16));
}
static int dw_mipi_dsi2_phy_init(void *priv_data)
{
return 0;
}
static void dw_mipi_dsi2_phy_power_on(void *priv_data)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
int ret;
ret = phy_set_mode(dsi2->phy, PHY_MODE_MIPI_DPHY);
if (ret) {
dev_err(dsi2->dev, "Failed to set phy mode: %d\n", ret);
return;
}
phy_configure(dsi2->phy, &dsi2->phy_opts);
phy_power_on(dsi2->phy);
}
static void dw_mipi_dsi2_phy_power_off(void *priv_data)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
phy_power_off(dsi2->phy);
}
static int
dw_mipi_dsi2_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
unsigned long mode_flags, u32 lanes, u32 format,
unsigned int *lane_mbps)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
u64 max_lane_rate, target_phyclk;
unsigned int lane_rate_kbps;
int bpp;
max_lane_rate = dsi2->cdata->max_bit_rate_per_lane;
dsi2->format = format;
bpp = mipi_dsi_pixel_format_to_bpp(format);
if (bpp < 0) {
dev_err(dsi2->dev, "failed to get bpp for pixel format %d\n", format);
return bpp;
}
lane_rate_kbps = mode->clock * bpp / lanes;
/*
* Set BW a little larger only in video burst mode in
* consideration of the protocol overhead and HS mode
* switching to BLLP mode, take 1 / 0.9, since Mbps must
* big than bandwidth of RGB
*/
if (mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
lane_rate_kbps = (lane_rate_kbps * 10) / 9;
if (lane_rate_kbps > max_lane_rate) {
dev_err(dsi2->dev, "DPHY clock frequency is out of range\n");
return -ERANGE;
}
dsi2->lane_mbps = lane_rate_kbps / 1000;
*lane_mbps = dsi2->lane_mbps;
if (dsi2->phy) {
target_phyclk = DIV_ROUND_CLOSEST_ULL(lane_rate_kbps * lanes * 1000, bpp);
phy_mipi_dphy_get_default_config(target_phyclk, bpp, lanes,
&dsi2->phy_opts.mipi_dphy);
}
return 0;
}
static void dw_mipi_dsi2_phy_get_iface(void *priv_data, struct dw_mipi_dsi2_phy_iface *iface)
{
/* PPI width is fixed to 16 bits in DCPHY */
iface->ppi_width = 16;
iface->phy_type = DW_MIPI_DSI2_DPHY;
}
static int
dw_mipi_dsi2_phy_get_timing(void *priv_data, unsigned int lane_mbps,
struct dw_mipi_dsi2_phy_timing *timing)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
struct phy_configure_opts_mipi_dphy *cfg = &dsi2->phy_opts.mipi_dphy;
unsigned long long tmp, ui;
unsigned long long hstx_clk;
hstx_clk = DIV_ROUND_CLOSEST_ULL(dsi2->lane_mbps * USEC_PER_SEC, 16);
ui = ALIGN(PSEC_PER_SEC, hstx_clk);
do_div(ui, hstx_clk);
/* PHY_LP2HS_TIME = (TLPX + THS-PREPARE + THS-ZERO) / Tphy_hstx_clk */
tmp = cfg->lpx + cfg->hs_prepare + cfg->hs_zero;
tmp = DIV_ROUND_CLOSEST_ULL(tmp << 16, ui);
timing->data_lp2hs = tmp;
/* PHY_HS2LP_TIME = (THS-TRAIL + THS-EXIT) / Tphy_hstx_clk */
tmp = cfg->hs_trail + cfg->hs_exit;
tmp = DIV_ROUND_CLOSEST_ULL(tmp << 16, ui);
timing->data_hs2lp = tmp;
return 0;
}
static const struct dw_mipi_dsi2_phy_ops dw_mipi_dsi2_rockchip_phy_ops = {
.init = dw_mipi_dsi2_phy_init,
.power_on = dw_mipi_dsi2_phy_power_on,
.power_off = dw_mipi_dsi2_phy_power_off,
.get_interface = dw_mipi_dsi2_phy_get_iface,
.get_lane_mbps = dw_mipi_dsi2_get_lane_mbps,
.get_timing = dw_mipi_dsi2_phy_get_timing,
};
static void dw_mipi_dsi2_encoder_atomic_enable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct dw_mipi_dsi2_rockchip *dsi2 = to_dsi2(encoder);
u32 color_depth;
switch (dsi2->format) {
case MIPI_DSI_FMT_RGB666:
case MIPI_DSI_FMT_RGB666_PACKED:
color_depth = IPI_DEPTH_6_BITS;
break;
case MIPI_DSI_FMT_RGB565:
color_depth = IPI_DEPTH_5_6_5_BITS;
break;
case MIPI_DSI_FMT_RGB888:
color_depth = IPI_DEPTH_8_BITS;
break;
default:
/* Should've been caught by atomic_check */
WARN_ON(1);
return;
}
grf_field_write(dsi2, IPI_COLOR_DEPTH, color_depth);
}
static int
dw_mipi_dsi2_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);
struct dw_mipi_dsi2_rockchip *dsi2 = to_dsi2(encoder);
struct drm_connector *connector = conn_state->connector;
struct drm_display_info *info = &connector->display_info;
switch (dsi2->format) {
case MIPI_DSI_FMT_RGB666:
case MIPI_DSI_FMT_RGB666_PACKED:
s->output_mode = ROCKCHIP_OUT_MODE_P666;
break;
case MIPI_DSI_FMT_RGB565:
s->output_mode = ROCKCHIP_OUT_MODE_P565;
break;
case MIPI_DSI_FMT_RGB888:
s->output_mode = ROCKCHIP_OUT_MODE_P888;
break;
default:
WARN_ON(1);
return -EINVAL;
}
if (info->num_bus_formats)
s->bus_format = info->bus_formats[0];
else
s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
s->output_type = DRM_MODE_CONNECTOR_DSI;
s->bus_flags = info->bus_flags;
s->color_space = V4L2_COLORSPACE_DEFAULT;
return 0;
}
static const struct drm_encoder_helper_funcs
dw_mipi_dsi2_encoder_helper_funcs = {
.atomic_enable = dw_mipi_dsi2_encoder_atomic_enable,
.atomic_check = dw_mipi_dsi2_encoder_atomic_check,
};
static int rockchip_dsi2_drm_create_encoder(struct dw_mipi_dsi2_rockchip *dsi2,
struct drm_device *drm_dev)
{
struct drm_encoder *encoder = &dsi2->encoder.encoder;
int ret;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dsi2->dev->of_node);
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
if (ret) {
dev_err(dsi2->dev, "Failed to initialize encoder with drm\n");
return ret;
}
drm_encoder_helper_add(encoder, &dw_mipi_dsi2_encoder_helper_funcs);
return 0;
}
static int dw_mipi_dsi2_rockchip_bind(struct device *dev, struct device *master,
void *data)
{
struct dw_mipi_dsi2_rockchip *dsi2 = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
int ret;
ret = rockchip_dsi2_drm_create_encoder(dsi2, drm_dev);
if (ret)
return dev_err_probe(dev, ret, "Failed to create drm encoder\n");
rockchip_drm_encoder_set_crtc_endpoint_id(&dsi2->encoder,
dev->of_node, 0, 0);
ret = dw_mipi_dsi2_bind(dsi2->dmd, &dsi2->encoder.encoder);
if (ret)
return dev_err_probe(dev, ret, "Failed to bind\n");
return 0;
}
static void dw_mipi_dsi2_rockchip_unbind(struct device *dev, struct device *master,
void *data)
{
struct dw_mipi_dsi2_rockchip *dsi2 = dev_get_drvdata(dev);
dw_mipi_dsi2_unbind(dsi2->dmd);
}
static const struct component_ops dw_mipi_dsi2_rockchip_ops = {
.bind = dw_mipi_dsi2_rockchip_bind,
.unbind = dw_mipi_dsi2_rockchip_unbind,
};
static int dw_mipi_dsi2_rockchip_host_attach(void *priv_data,
struct mipi_dsi_device *device)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
int ret;
ret = component_add(dsi2->dev, &dw_mipi_dsi2_rockchip_ops);
if (ret)
return dev_err_probe(dsi2->dev, ret, "Failed to register component\n");
return 0;
}
static int dw_mipi_dsi2_rockchip_host_detach(void *priv_data,
struct mipi_dsi_device *device)
{
struct dw_mipi_dsi2_rockchip *dsi2 = priv_data;
component_del(dsi2->dev, &dw_mipi_dsi2_rockchip_ops);
return 0;
}
static const struct dw_mipi_dsi2_host_ops dw_mipi_dsi2_rockchip_host_ops = {
.attach = dw_mipi_dsi2_rockchip_host_attach,
.detach = dw_mipi_dsi2_rockchip_host_detach,
};
static const struct regmap_config dw_mipi_dsi2_rockchip_regmap_config = {
.name = "dsi2-host",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.fast_io = true,
};
static int dw_mipi_dsi2_rockchip_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
const struct rockchip_dw_dsi2_chip_data *cdata =
of_device_get_match_data(dev);
struct dw_mipi_dsi2_rockchip *dsi2;
struct resource *res;
void __iomem *base;
int i;
dsi2 = devm_kzalloc(dev, sizeof(*dsi2), GFP_KERNEL);
if (!dsi2)
return -ENOMEM;
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(base))
return dev_err_probe(dev, PTR_ERR(base), "Unable to get dsi registers\n");
dsi2->regmap = devm_regmap_init_mmio(dev, base, &dw_mipi_dsi2_rockchip_regmap_config);
if (IS_ERR(dsi2->regmap))
return dev_err_probe(dev, PTR_ERR(dsi2->regmap), "failed to init register map\n");
i = 0;
while (cdata[i].reg) {
if (cdata[i].reg == res->start) {
dsi2->cdata = &cdata[i];
break;
}
i++;
}
if (!dsi2->cdata)
return dev_err_probe(dev, -EINVAL, "No dsi-config for %s node\n", np->name);
dsi2->grf_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
if (IS_ERR(dsi2->grf_regmap))
return dev_err_probe(dsi2->dev, PTR_ERR(dsi2->grf_regmap), "Unable to get grf\n");
dsi2->phy = devm_phy_optional_get(dev, "dcphy");
if (IS_ERR(dsi2->phy))
return dev_err_probe(dev, PTR_ERR(dsi2->phy), "failed to get mipi phy\n");
dsi2->dev = dev;
dsi2->pdata.regmap = dsi2->regmap;
dsi2->pdata.max_data_lanes = 4;
dsi2->pdata.phy_ops = &dw_mipi_dsi2_rockchip_phy_ops;
dsi2->pdata.host_ops = &dw_mipi_dsi2_rockchip_host_ops;
dsi2->pdata.priv_data = dsi2;
platform_set_drvdata(pdev, dsi2);
dsi2->dmd = dw_mipi_dsi2_probe(pdev, &dsi2->pdata);
if (IS_ERR(dsi2->dmd))
return dev_err_probe(dev, PTR_ERR(dsi2->dmd), "Failed to probe dw_mipi_dsi2\n");
return 0;
}
static void dw_mipi_dsi2_rockchip_remove(struct platform_device *pdev)
{
struct dw_mipi_dsi2_rockchip *dsi2 = platform_get_drvdata(pdev);
dw_mipi_dsi2_remove(dsi2->dmd);
}
static const struct dsigrf_reg rk3576_dsi_grf_reg_fields[MAX_FIELDS] = {
[TXREQCLKHS_EN] = { 0x0028, 1, 1 },
[GATING_EN] = { 0x0028, 0, 0 },
[IPI_SHUTDN] = { 0x0028, 3, 3 },
[IPI_COLORM] = { 0x0028, 2, 2 },
[IPI_COLOR_DEPTH] = { 0x0028, 8, 11 },
[IPI_FORMAT] = { 0x0028, 4, 7 },
};
static const struct dsigrf_reg rk3588_dsi0_grf_reg_fields[MAX_FIELDS] = {
[TXREQCLKHS_EN] = { 0x0000, 11, 11 },
[GATING_EN] = { 0x0000, 10, 10 },
[IPI_SHUTDN] = { 0x0000, 9, 9 },
[IPI_COLORM] = { 0x0000, 8, 8 },
[IPI_COLOR_DEPTH] = { 0x0000, 4, 7 },
[IPI_FORMAT] = { 0x0000, 0, 3 },
};
static const struct dsigrf_reg rk3588_dsi1_grf_reg_fields[MAX_FIELDS] = {
[TXREQCLKHS_EN] = { 0x0004, 11, 11 },
[GATING_EN] = { 0x0004, 10, 10 },
[IPI_SHUTDN] = { 0x0004, 9, 9 },
[IPI_COLORM] = { 0x0004, 8, 8 },
[IPI_COLOR_DEPTH] = { 0x0004, 4, 7 },
[IPI_FORMAT] = { 0x0004, 0, 3 },
};
static const struct rockchip_dw_dsi2_chip_data rk3576_chip_data[] = {
{
.reg = 0x27d80000,
.grf_regs = rk3576_dsi_grf_reg_fields,
.max_bit_rate_per_lane = 2500000ULL,
},
{ /* sentinel */ }
};
static const struct rockchip_dw_dsi2_chip_data rk3588_chip_data[] = {
{
.reg = 0xfde20000,
.grf_regs = rk3588_dsi0_grf_reg_fields,
.max_bit_rate_per_lane = 4500000ULL,
},
{
.reg = 0xfde30000,
.grf_regs = rk3588_dsi1_grf_reg_fields,
.max_bit_rate_per_lane = 4500000ULL,
}
};
static const struct of_device_id dw_mipi_dsi2_rockchip_dt_ids[] = {
{
.compatible = "rockchip,rk3576-mipi-dsi2",
.data = &rk3576_chip_data,
}, {
.compatible = "rockchip,rk3588-mipi-dsi2",
.data = &rk3588_chip_data,
},
{}
};
MODULE_DEVICE_TABLE(of, dw_mipi_dsi2_rockchip_dt_ids);
struct platform_driver dw_mipi_dsi2_rockchip_driver = {
.probe = dw_mipi_dsi2_rockchip_probe,
.remove = dw_mipi_dsi2_rockchip_remove,
.driver = {
.of_match_table = dw_mipi_dsi2_rockchip_dt_ids,
.name = "dw-mipi-dsi2",
},
};
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 Rockchip Electronics Co., Ltd.
*
* Author: Zhang Yubing <yubing.zhang@rock-chips.com>
* Author: Andy Yan <andy.yan@rock-chips.com>
*/
#include <linux/component.h>
#include <linux/media-bus-format.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
#include <drm/bridge/dw_dp.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_managed.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
struct rockchip_dw_dp {
struct dw_dp *base;
struct device *dev;
struct rockchip_encoder encoder;
};
static int dw_dp_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);
struct drm_atomic_commit *state = conn_state->state;
struct drm_display_info *di = &conn_state->connector->display_info;
struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder);
struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
u32 bus_format = bridge_state->input_bus_cfg.format;
switch (bus_format) {
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
break;
case MEDIA_BUS_FMT_YUYV10_1X20:
case MEDIA_BUS_FMT_YUYV8_1X16:
s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY;
break;
case MEDIA_BUS_FMT_RGB101010_1X30:
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
case MEDIA_BUS_FMT_YUV10_1X30:
case MEDIA_BUS_FMT_YUV8_1X24:
default:
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
break;
}
s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
s->bus_format = bus_format;
s->bus_flags = di->bus_flags;
s->color_space = V4L2_COLORSPACE_DEFAULT;
return 0;
}
static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
.atomic_check = dw_dp_encoder_atomic_check,
};
static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
const struct dw_dp_plat_data *plat_data;
struct drm_device *drm_dev = data;
struct rockchip_dw_dp *dp;
struct drm_encoder *encoder;
struct drm_connector *connector;
int ret;
dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
dp->dev = dev;
platform_set_drvdata(pdev, dp);
plat_data = of_device_get_match_data(dev);
if (!plat_data)
return -ENODEV;
encoder = &dp->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
dp->base = dw_dp_bind(dev, encoder, plat_data);
if (IS_ERR(dp->base))
return PTR_ERR(dp->base);
connector = drm_bridge_connector_init(drm_dev, encoder);
if (IS_ERR(connector))
return dev_err_probe(dev, PTR_ERR(connector),
"Failed to init bridge connector");
return 0;
}
static const struct component_ops dw_dp_rockchip_component_ops = {
.bind = dw_dp_rockchip_bind,
};
static int dw_dp_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &dw_dp_rockchip_component_ops);
}
static void dw_dp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
}
static const struct dw_dp_plat_data rk3588_dp_plat_data = {
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_QUAD_PIXEL,
};
static const struct dw_dp_plat_data rk3576_dp_plat_data = {
.max_link_rate = 810000,
.pixel_mode = DW_DP_MP_DUAL_PIXEL,
};
static const struct of_device_id dw_dp_of_match[] = {
{
.compatible = "rockchip,rk3588-dp",
.data = &rk3588_dp_plat_data,
}, {
.compatible = "rockchip,rk3576-dp",
.data = &rk3576_dp_plat_data,
},
{}
};
MODULE_DEVICE_TABLE(of, dw_dp_of_match);
struct platform_driver dw_dp_driver = {
.probe = dw_dp_probe,
.remove = dw_dp_remove,
.driver = {
.name = "dw-dp",
.of_match_table = dw_dp_of_match,
},
};
@@ -0,0 +1,680 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2014, Rockchip Electronics Co., Ltd.
*/
#include <linux/clk.h>
#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#define RK3228_GRF_SOC_CON2 0x0408
#define RK3228_HDMI_SDAIN_MSK BIT(14)
#define RK3228_HDMI_SCLIN_MSK BIT(13)
#define RK3228_GRF_SOC_CON6 0x0418
#define RK3228_HDMI_HPD_VSEL BIT(6)
#define RK3228_HDMI_SDA_VSEL BIT(5)
#define RK3228_HDMI_SCL_VSEL BIT(4)
#define RK3288_GRF_SOC_CON6 0x025C
#define RK3288_HDMI_LCDC_SEL BIT(4)
#define RK3328_GRF_SOC_CON2 0x0408
#define RK3328_HDMI_SDAIN_MSK BIT(11)
#define RK3328_HDMI_SCLIN_MSK BIT(10)
#define RK3328_HDMI_HPD_IOE BIT(2)
#define RK3328_GRF_SOC_CON3 0x040c
/* need to be unset if hdmi or i2c should control voltage */
#define RK3328_HDMI_SDA5V_GRF BIT(15)
#define RK3328_HDMI_SCL5V_GRF BIT(14)
#define RK3328_HDMI_HPD5V_GRF BIT(13)
#define RK3328_HDMI_CEC5V_GRF BIT(12)
#define RK3328_GRF_SOC_CON4 0x0410
#define RK3328_HDMI_HPD_SARADC BIT(13)
#define RK3328_HDMI_CEC_5V BIT(11)
#define RK3328_HDMI_SDA_5V BIT(10)
#define RK3328_HDMI_SCL_5V BIT(9)
#define RK3328_HDMI_HPD_5V BIT(8)
#define RK3399_GRF_SOC_CON20 0x6250
#define RK3399_HDMI_LCDC_SEL BIT(6)
#define RK3568_GRF_VO_CON1 0x0364
#define RK3568_HDMI_SDAIN_MSK BIT(15)
#define RK3568_HDMI_SCLIN_MSK BIT(14)
/**
* struct rockchip_hdmi_chip_data - splite the grf setting of kind of chips
* @lcdsel_grf_reg: grf register offset of lcdc select
* @lcdsel_big: reg value of selecting vop big for HDMI
* @lcdsel_lit: reg value of selecting vop little for HDMI
* @max_tmds_clock: maximum TMDS clock rate supported
*/
struct rockchip_hdmi_chip_data {
int lcdsel_grf_reg;
u32 lcdsel_big;
u32 lcdsel_lit;
int max_tmds_clock;
};
struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
struct rockchip_encoder encoder;
const struct rockchip_hdmi_chip_data *chip_data;
const struct dw_hdmi_plat_data *plat_data;
struct clk *hdmiphy_clk;
struct clk *ref_clk;
struct clk *grf_clk;
struct dw_hdmi *hdmi;
struct phy *phy;
};
static struct rockchip_hdmi *to_rockchip_hdmi(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct rockchip_hdmi, encoder);
}
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*/
{ 74250000, 0x8009, 0x0004, 0x0272},
{ 165000000, 0x802b, 0x0004, 0x0209},
{ 297000000, 0x8039, 0x0005, 0x028d},
{ 594000000, 0x8039, 0x0000, 0x019d},
{ ~0UL, 0x0000, 0x0000, 0x0000}
};
static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
{
struct device_node *np = hdmi->dev->of_node;
int ret;
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);
}
hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "ref");
if (!hdmi->ref_clk)
hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "vpll");
if (IS_ERR(hdmi->ref_clk)) {
ret = PTR_ERR(hdmi->ref_clk);
return dev_err_probe(hdmi->dev, ret, "failed to get reference clock\n");
}
hdmi->grf_clk = devm_clk_get_optional(hdmi->dev, "grf");
if (IS_ERR(hdmi->grf_clk)) {
ret = PTR_ERR(hdmi->grf_clk);
return dev_err_probe(hdmi->dev, ret, "failed to get grf clock\n");
}
ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9");
if (ret)
return ret;
ret = devm_regulator_get_enable(hdmi->dev, "avdd-1v8");
return ret;
}
static enum drm_mode_status
dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
struct rockchip_hdmi *hdmi = data;
int pclk = mode->clock * 1000;
if (hdmi->chip_data->max_tmds_clock &&
mode->clock > hdmi->chip_data->max_tmds_clock)
return MODE_CLOCK_HIGH;
if (hdmi->ref_clk) {
int rpclk = clk_round_rate(hdmi->ref_clk, pclk);
if (rpclk < 0 || abs(rpclk - pclk) > pclk / 1000)
return MODE_NOCLOCK;
}
if (hdmi->hdmiphy_clk) {
int rpclk = clk_round_rate(hdmi->hdmiphy_clk, pclk);
if (rpclk < 0 || abs(rpclk - pclk) > pclk / 1000)
return MODE_NOCLOCK;
}
return MODE_OK;
}
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)
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000);
}
static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
u32 val;
int ret;
if (hdmi->chip_data->lcdsel_grf_reg < 0)
return;
ret = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
if (ret)
val = hdmi->chip_data->lcdsel_lit;
else
val = hdmi->chip_data->lcdsel_big;
ret = clk_prepare_enable(hdmi->grf_clk);
if (ret < 0) {
dev_err(hdmi->dev, "failed to enable grfclk %d\n", ret);
return;
}
ret = regmap_write(hdmi->regmap, hdmi->chip_data->lcdsel_grf_reg, val);
if (ret != 0)
dev_err(hdmi->dev, "Could not write to GRF: %d\n", ret);
clk_disable_unprepare(hdmi->grf_clk);
dev_dbg(hdmi->dev, "vop %s output to hdmi\n", ret ? "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 int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
dw_hdmi_set_high_tmds_clock_ratio(dw_hdmi, display);
return phy_power_on(hdmi->phy);
}
static void dw_hdmi_rockchip_genphy_disable(struct dw_hdmi *dw_hdmi, void *data)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
phy_power_off(hdmi->phy);
}
static void dw_hdmi_rk3228_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
dw_hdmi_phy_setup_hpd(dw_hdmi, data);
regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON6,
FIELD_PREP_WM16(RK3228_HDMI_HPD_VSEL, 1) |
FIELD_PREP_WM16(RK3228_HDMI_SDA_VSEL, 1) |
FIELD_PREP_WM16(RK3228_HDMI_SCL_VSEL, 1));
regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON2,
FIELD_PREP_WM16(RK3228_HDMI_SDAIN_MSK, 1) |
FIELD_PREP_WM16(RK3228_HDMI_SCLIN_MSK, 1));
}
static enum drm_connector_status
dw_hdmi_rk3328_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 (status == connector_status_connected)
regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4,
FIELD_PREP_WM16(RK3328_HDMI_SDA_5V, 1) |
FIELD_PREP_WM16(RK3328_HDMI_SCL_5V, 1));
else
regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4,
FIELD_PREP_WM16(RK3328_HDMI_SDA_5V, 0) |
FIELD_PREP_WM16(RK3328_HDMI_SCL_5V, 0));
return status;
}
static void dw_hdmi_rk3328_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
dw_hdmi_phy_setup_hpd(dw_hdmi, data);
/* Enable and map pins to 3V grf-controlled io-voltage */
regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4,
FIELD_PREP_WM16(RK3328_HDMI_HPD_SARADC, 0) |
FIELD_PREP_WM16(RK3328_HDMI_CEC_5V, 0) |
FIELD_PREP_WM16(RK3328_HDMI_SDA_5V, 0) |
FIELD_PREP_WM16(RK3328_HDMI_SCL_5V, 0) |
FIELD_PREP_WM16(RK3328_HDMI_HPD_5V, 0));
regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON3,
FIELD_PREP_WM16(RK3328_HDMI_SDA5V_GRF, 0) |
FIELD_PREP_WM16(RK3328_HDMI_SCL5V_GRF, 0) |
FIELD_PREP_WM16(RK3328_HDMI_HPD5V_GRF, 0) |
FIELD_PREP_WM16(RK3328_HDMI_CEC5V_GRF, 0));
regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON2,
FIELD_PREP_WM16(RK3328_HDMI_SDAIN_MSK, 1) |
FIELD_PREP_WM16(RK3328_HDMI_SCLIN_MSK, 1) |
FIELD_PREP_WM16(RK3328_HDMI_HPD_IOE, 0));
dw_hdmi_rk3328_read_hpd(dw_hdmi, data);
}
static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
.update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3228_setup_hpd,
};
static struct rockchip_hdmi_chip_data rk3228_chip_data = {
.lcdsel_grf_reg = -1,
.max_tmds_clock = 594000,
};
static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
.phy_data = &rk3228_chip_data,
.phy_ops = &rk3228_hdmi_phy_ops,
.phy_name = "inno_dw_hdmi_phy2",
.phy_force_vendor = true,
};
static struct rockchip_hdmi_chip_data rk3288_chip_data = {
.lcdsel_grf_reg = RK3288_GRF_SOC_CON6,
.lcdsel_big = FIELD_PREP_WM16_CONST(RK3288_HDMI_LCDC_SEL, 0),
.lcdsel_lit = FIELD_PREP_WM16_CONST(RK3288_HDMI_LCDC_SEL, 1),
.max_tmds_clock = 340000,
};
static const struct dw_hdmi_plat_data rk3288_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,
.phy_data = &rk3288_chip_data,
};
static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_rk3328_read_hpd,
.update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3328_setup_hpd,
};
static struct rockchip_hdmi_chip_data rk3328_chip_data = {
.lcdsel_grf_reg = -1,
.max_tmds_clock = 594000,
};
static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
.phy_data = &rk3328_chip_data,
.phy_ops = &rk3328_hdmi_phy_ops,
.phy_name = "inno_dw_hdmi_phy2",
.phy_force_vendor = true,
.use_drm_infoframe = true,
};
static struct rockchip_hdmi_chip_data rk3368_chip_data = {
.lcdsel_grf_reg = -1,
};
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,
.phy_data = &rk3368_chip_data,
.use_drm_infoframe = true,
};
static struct rockchip_hdmi_chip_data rk3399_chip_data = {
.lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
.lcdsel_big = FIELD_PREP_WM16_CONST(RK3399_HDMI_LCDC_SEL, 0),
.lcdsel_lit = FIELD_PREP_WM16_CONST(RK3399_HDMI_LCDC_SEL, 1),
.max_tmds_clock = 594000,
};
static const struct dw_hdmi_plat_data rk3399_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,
.phy_data = &rk3399_chip_data,
.use_drm_infoframe = true,
};
static struct rockchip_hdmi_chip_data rk3568_chip_data = {
.lcdsel_grf_reg = -1,
.max_tmds_clock = 594000,
};
static const struct dw_hdmi_plat_data rk3568_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,
.phy_data = &rk3568_chip_data,
.use_drm_infoframe = true,
};
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,rk3368-dw-hdmi",
.data = &rk3368_hdmi_drv_data
},
{ .compatible = "rockchip,rk3399-dw-hdmi",
.data = &rk3399_hdmi_drv_data
},
{ .compatible = "rockchip,rk3568-dw-hdmi",
.data = &rk3568_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);
struct device_node *np = dev_of_node(dev);
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, index;
if (!np)
return -ENODEV;
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
match = of_match_node(dw_hdmi_rockchip_dt_ids, np);
plat_data = devm_kmemdup(&pdev->dev, match->data,
sizeof(*plat_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
hdmi->dev = &pdev->dev;
hdmi->plat_data = plat_data;
hdmi->chip_data = plat_data->phy_data;
plat_data->phy_data = hdmi;
plat_data->priv_data = hdmi;
encoder = &hdmi->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, np);
rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
np, 0, 0);
/*
* 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) {
return dev_err_probe(hdmi->dev, ret, "Unable to parse OF data\n");
}
hdmi->phy = devm_phy_optional_get(dev, "hdmi");
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
return dev_err_probe(hdmi->dev, ret, "failed to get phy\n");
}
index = of_property_match_string(np, "phy-names", "hdmi");
if (index >= 0) {
struct of_phandle_args clkspec;
if (!of_parse_phandle_with_args(np, "phys", "#phy-cells", index,
&clkspec)) {
hdmi->hdmiphy_clk = of_clk_get_from_provider(&clkspec);
of_node_put(clkspec.np);
if (IS_ERR(hdmi->hdmiphy_clk))
hdmi->hdmiphy_clk = NULL;
}
}
if (hdmi->chip_data == &rk3568_chip_data) {
regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
FIELD_PREP_WM16(RK3568_HDMI_SDAIN_MSK, 1) |
FIELD_PREP_WM16(RK3568_HDMI_SCLIN_MSK, 1));
}
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
platform_set_drvdata(pdev, hdmi);
hdmi->hdmi = 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 (IS_ERR(hdmi->hdmi)) {
ret = PTR_ERR(hdmi->hdmi);
goto err_bind;
}
return 0;
err_bind:
drm_encoder_cleanup(encoder);
return ret;
}
static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
void *data)
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
dw_hdmi_unbind(hdmi->hdmi);
drm_encoder_cleanup(&hdmi->encoder.encoder);
}
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 void dw_hdmi_rockchip_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
}
static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev)
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
dw_hdmi_resume(hdmi->hdmi);
return 0;
}
static const struct dev_pm_ops dw_hdmi_rockchip_pm = {
SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume)
};
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_dt_ids,
},
};
@@ -0,0 +1,669 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
* Copyright (c) 2024 Collabora Ltd.
*
* Author: Algea Cao <algea.cao@rock-chips.com>
* Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
*/
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-hdmi.h>
#include <linux/regmap.h>
#include <linux/workqueue.h>
#include <drm/bridge/dw_hdmi_qp.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_managed.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#define RK3576_IOC_MISC_CON0 0xa400
#define RK3576_HDMI_HPD_INT_MSK BIT(2)
#define RK3576_HDMI_HPD_INT_CLR BIT(1)
#define RK3576_IOC_HDMI_HPD_STATUS 0xa440
#define RK3576_HDMI_LEVEL_INT BIT(3)
#define RK3576_VO0_GRF_SOC_CON1 0x0004
#define RK3576_HDMI_FRL_MOD BIT(0)
#define RK3576_HDMI_HDCP14_MEM_EN BIT(15)
#define RK3576_VO0_GRF_SOC_CON8 0x0020
#define RK3576_COLOR_DEPTH_MASK GENMASK(11, 8)
#define RK3576_8BPC 0x0
#define RK3576_10BPC 0x6
#define RK3576_COLOR_FORMAT_MASK GENMASK(7, 4)
#define RK3576_RGB 0x9
#define RK3576_YUV422 0x1
#define RK3576_YUV444 0x2
#define RK3576_YUV420 0x3
#define RK3576_CECIN_MASK BIT(3)
#define RK3576_VO0_GRF_SOC_CON14 0x0038
#define RK3576_I2S_SEL_MASK BIT(0)
#define RK3576_SPDIF_SEL_MASK BIT(1)
#define HDCP0_P1_GPIO_IN BIT(2)
#define RK3576_SCLIN_MASK BIT(4)
#define RK3576_SDAIN_MASK BIT(5)
#define RK3576_HDMI_GRANT_SEL BIT(6)
#define RK3588_GRF_SOC_CON2 0x0308
#define RK3588_HDMI0_HPD_INT_MSK BIT(13)
#define RK3588_HDMI0_HPD_INT_CLR BIT(12)
#define RK3588_HDMI1_HPD_INT_MSK BIT(15)
#define RK3588_HDMI1_HPD_INT_CLR BIT(14)
#define RK3588_GRF_SOC_CON7 0x031c
#define RK3588_HPD_HDMI0_IO_EN_MASK BIT(12)
#define RK3588_HPD_HDMI1_IO_EN_MASK BIT(13)
#define RK3588_GRF_SOC_STATUS1 0x0384
#define RK3588_HDMI0_LEVEL_INT BIT(16)
#define RK3588_HDMI1_LEVEL_INT BIT(24)
#define RK3588_GRF_VO1_CON3 0x000c
#define RK3588_GRF_VO1_CON6 0x0018
#define RK3588_COLOR_DEPTH_MASK GENMASK(7, 4)
#define RK3588_8BPC 0x0
#define RK3588_10BPC 0x6
#define RK3588_COLOR_FORMAT_MASK GENMASK(3, 0)
#define RK3588_RGB 0x0
#define RK3588_YUV420 0x3
#define RK3588_SCLIN_MASK BIT(9)
#define RK3588_SDAIN_MASK BIT(10)
#define RK3588_MODE_MASK BIT(11)
#define RK3588_I2S_SEL_MASK BIT(13)
#define RK3588_GRF_VO1_CON9 0x0024
#define RK3588_HDMI0_GRANT_SEL BIT(10)
#define RK3588_HDMI1_GRANT_SEL BIT(12)
#define HOTPLUG_DEBOUNCE_MS 150
#define MAX_HDMI_PORT_NUM 2
struct rockchip_hdmi_qp {
struct device *dev;
struct regmap *regmap;
struct regmap *vo_regmap;
struct rockchip_encoder encoder;
struct dw_hdmi_qp *hdmi;
struct phy *phy;
struct gpio_desc *frl_enable_gpio;
struct delayed_work hpd_work;
int port_id;
const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops;
unsigned long long tmds_char_rate;
};
struct rockchip_hdmi_qp_ctrl_ops {
void (*io_init)(struct rockchip_hdmi_qp *hdmi);
void (*enc_init)(struct rockchip_hdmi_qp *hdmi, struct rockchip_crtc_state *state);
irqreturn_t (*irq_callback)(int irq, void *dev_id);
irqreturn_t (*hardirq_callback)(int irq, void *dev_id);
};
static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct rockchip_hdmi_qp, encoder);
}
static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
struct drm_crtc *crtc = encoder->crtc;
/* Unconditionally switch to TMDS as FRL is not yet supported */
gpiod_set_value_cansleep(hdmi->frl_enable_gpio, 0);
if (!crtc || !crtc->state)
return;
if (hdmi->ctrl_ops->enc_init)
hdmi->ctrl_ops->enc_init(hdmi, to_rockchip_crtc_state(crtc->state));
}
static int
dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
union phy_configure_opts phy_cfg = {};
int ret;
if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate &&
s->output_bpc == conn_state->hdmi.output_bpc)
return 0;
phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate;
phy_cfg.hdmi.bpc = conn_state->hdmi.output_bpc;
ret = phy_configure(hdmi->phy, &phy_cfg);
if (!ret) {
hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
s->output_bpc = conn_state->hdmi.output_bpc;
} else {
dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret);
}
return ret;
}
static const struct
drm_encoder_helper_funcs dw_hdmi_qp_rockchip_encoder_helper_funcs = {
.enable = dw_hdmi_qp_rockchip_encoder_enable,
.atomic_check = dw_hdmi_qp_rockchip_encoder_atomic_check,
};
static int dw_hdmi_qp_rk3588_phy_init(struct dw_hdmi_qp *dw_hdmi, void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
return phy_power_on(hdmi->phy);
}
static void dw_hdmi_qp_rk3588_phy_disable(struct dw_hdmi_qp *dw_hdmi,
void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
phy_power_off(hdmi->phy);
}
static enum drm_connector_status
dw_hdmi_qp_rk3588_read_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
u32 val;
regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &val);
val &= hdmi->port_id ? RK3588_HDMI1_LEVEL_INT : RK3588_HDMI0_LEVEL_INT;
return val ? connector_status_connected : connector_status_disconnected;
}
static void dw_hdmi_qp_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
u32 val;
if (hdmi->port_id)
val = (FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_CLR, 1) |
FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_MSK, 0));
else
val = (FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_CLR, 1) |
FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_MSK, 0));
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
}
static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = {
.init = dw_hdmi_qp_rk3588_phy_init,
.disable = dw_hdmi_qp_rk3588_phy_disable,
.read_hpd = dw_hdmi_qp_rk3588_read_hpd,
.setup_hpd = dw_hdmi_qp_rk3588_setup_hpd,
};
static enum drm_connector_status
dw_hdmi_qp_rk3576_read_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
u32 val;
regmap_read(hdmi->regmap, RK3576_IOC_HDMI_HPD_STATUS, &val);
return val & RK3576_HDMI_LEVEL_INT ?
connector_status_connected : connector_status_disconnected;
}
static void dw_hdmi_qp_rk3576_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
{
struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
u32 val;
val = (FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_CLR, 1) |
FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_MSK, 0));
regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val);
regmap_write(hdmi->regmap, 0xa404, 0xffff0102);
}
static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = {
.init = dw_hdmi_qp_rk3588_phy_init,
.disable = dw_hdmi_qp_rk3588_phy_disable,
.read_hpd = dw_hdmi_qp_rk3576_read_hpd,
.setup_hpd = dw_hdmi_qp_rk3576_setup_hpd,
};
static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work)
{
struct rockchip_hdmi_qp *hdmi = container_of(work,
struct rockchip_hdmi_qp,
hpd_work.work);
struct drm_device *drm = hdmi->encoder.encoder.dev;
bool changed;
if (drm) {
changed = drm_helper_hpd_irq_event(drm);
if (changed)
dev_dbg(hdmi->dev, "connector status changed\n");
}
}
static irqreturn_t dw_hdmi_qp_rk3576_hardirq(int irq, void *dev_id)
{
struct rockchip_hdmi_qp *hdmi = dev_id;
u32 intr_stat, val;
regmap_read(hdmi->regmap, RK3576_IOC_HDMI_HPD_STATUS, &intr_stat);
if (intr_stat) {
val = FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_MSK, 1);
regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val);
return IRQ_WAKE_THREAD;
}
return IRQ_NONE;
}
static irqreturn_t dw_hdmi_qp_rk3576_irq(int irq, void *dev_id)
{
struct rockchip_hdmi_qp *hdmi = dev_id;
u32 val;
val = FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_CLR, 1);
regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val);
mod_delayed_work(system_percpu_wq, &hdmi->hpd_work,
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
val = FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_MSK, 0);
regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val);
return IRQ_HANDLED;
}
static irqreturn_t dw_hdmi_qp_rk3588_hardirq(int irq, void *dev_id)
{
struct rockchip_hdmi_qp *hdmi = dev_id;
u32 intr_stat, val;
regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &intr_stat);
if (intr_stat) {
if (hdmi->port_id)
val = FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_MSK, 1);
else
val = FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_MSK, 1);
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
return IRQ_WAKE_THREAD;
}
return IRQ_NONE;
}
static irqreturn_t dw_hdmi_qp_rk3588_irq(int irq, void *dev_id)
{
struct rockchip_hdmi_qp *hdmi = dev_id;
u32 val;
if (hdmi->port_id)
val = FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_CLR, 1);
else
val = FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_CLR, 1);
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
mod_delayed_work(system_percpu_wq, &hdmi->hpd_work,
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
if (hdmi->port_id)
val |= FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_MSK, 0);
else
val |= FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_MSK, 0);
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
return IRQ_HANDLED;
}
static void dw_hdmi_qp_rk3576_io_init(struct rockchip_hdmi_qp *hdmi)
{
u32 val;
val = FIELD_PREP_WM16(RK3576_SCLIN_MASK, 1) |
FIELD_PREP_WM16(RK3576_SDAIN_MASK, 1) |
FIELD_PREP_WM16(RK3576_HDMI_GRANT_SEL, 1) |
FIELD_PREP_WM16(RK3576_I2S_SEL_MASK, 1);
regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON14, val);
val = FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_MSK, 0);
regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val);
}
static void dw_hdmi_qp_rk3588_io_init(struct rockchip_hdmi_qp *hdmi)
{
u32 val;
val = FIELD_PREP_WM16(RK3588_SCLIN_MASK, 1) |
FIELD_PREP_WM16(RK3588_SDAIN_MASK, 1) |
FIELD_PREP_WM16(RK3588_MODE_MASK, 1) |
FIELD_PREP_WM16(RK3588_I2S_SEL_MASK, 1);
regmap_write(hdmi->vo_regmap,
hdmi->port_id ? RK3588_GRF_VO1_CON6 : RK3588_GRF_VO1_CON3,
val);
val = FIELD_PREP_WM16(RK3588_HPD_HDMI0_IO_EN_MASK, 1) |
FIELD_PREP_WM16(RK3588_HPD_HDMI1_IO_EN_MASK, 1);
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val);
if (hdmi->port_id)
val = FIELD_PREP_WM16(RK3588_HDMI1_GRANT_SEL, 1);
else
val = FIELD_PREP_WM16(RK3588_HDMI0_GRANT_SEL, 1);
regmap_write(hdmi->vo_regmap, RK3588_GRF_VO1_CON9, val);
if (hdmi->port_id)
val = FIELD_PREP_WM16(RK3588_HDMI1_HPD_INT_MSK, 1);
else
val = FIELD_PREP_WM16(RK3588_HDMI0_HPD_INT_MSK, 1);
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
}
static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi,
struct rockchip_crtc_state *state)
{
u32 val;
if (state->output_bpc == 10)
val = FIELD_PREP_WM16(RK3576_COLOR_DEPTH_MASK, RK3576_10BPC);
else
val = FIELD_PREP_WM16(RK3576_COLOR_DEPTH_MASK, RK3576_8BPC);
regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON8, val);
}
static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi,
struct rockchip_crtc_state *state)
{
u32 val;
if (state->output_bpc == 10)
val = FIELD_PREP_WM16(RK3588_COLOR_DEPTH_MASK, RK3588_10BPC);
else
val = FIELD_PREP_WM16(RK3588_COLOR_DEPTH_MASK, RK3588_8BPC);
regmap_write(hdmi->vo_regmap,
hdmi->port_id ? RK3588_GRF_VO1_CON6 : RK3588_GRF_VO1_CON3,
val);
}
static const struct rockchip_hdmi_qp_ctrl_ops rk3576_hdmi_ctrl_ops = {
.io_init = dw_hdmi_qp_rk3576_io_init,
.enc_init = dw_hdmi_qp_rk3576_enc_init,
.irq_callback = dw_hdmi_qp_rk3576_irq,
.hardirq_callback = dw_hdmi_qp_rk3576_hardirq,
};
static const struct rockchip_hdmi_qp_ctrl_ops rk3588_hdmi_ctrl_ops = {
.io_init = dw_hdmi_qp_rk3588_io_init,
.enc_init = dw_hdmi_qp_rk3588_enc_init,
.irq_callback = dw_hdmi_qp_rk3588_irq,
.hardirq_callback = dw_hdmi_qp_rk3588_hardirq,
};
struct rockchip_hdmi_qp_cfg {
unsigned int num_ports;
unsigned int port_ids[MAX_HDMI_PORT_NUM];
const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops;
const struct dw_hdmi_qp_phy_ops *phy_ops;
};
static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = {
.num_ports = 1,
.port_ids = {
0x27da0000,
},
.ctrl_ops = &rk3576_hdmi_ctrl_ops,
.phy_ops = &rk3576_hdmi_phy_ops,
};
static const struct rockchip_hdmi_qp_cfg rk3588_hdmi_cfg = {
.num_ports = 2,
.port_ids = {
0xfde80000,
0xfdea0000,
},
.ctrl_ops = &rk3588_hdmi_ctrl_ops,
.phy_ops = &rk3588_hdmi_phy_ops,
};
static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = {
{
.compatible = "rockchip,rk3576-dw-hdmi-qp",
.data = &rk3576_hdmi_cfg
}, {
.compatible = "rockchip,rk3588-dw-hdmi-qp",
.data = &rk3588_hdmi_cfg
},
{},
};
MODULE_DEVICE_TABLE(of, dw_hdmi_qp_rockchip_dt_ids);
static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dw_hdmi_qp_plat_data plat_data = {};
const struct rockchip_hdmi_qp_cfg *cfg;
struct drm_device *drm = data;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct rockchip_hdmi_qp *hdmi;
struct resource *res;
struct clk_bulk_data *clks;
struct clk *ref_clk;
int ret, irq, i;
if (!pdev->dev.of_node)
return -ENODEV;
hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
cfg = of_device_get_match_data(dev);
if (!cfg)
return -ENODEV;
if (!cfg->ctrl_ops || !cfg->ctrl_ops->io_init ||
!cfg->ctrl_ops->irq_callback || !cfg->ctrl_ops->hardirq_callback)
return dev_err_probe(dev, -ENODEV, "Missing platform ctrl ops\n");
hdmi->ctrl_ops = cfg->ctrl_ops;
hdmi->dev = &pdev->dev;
hdmi->port_id = -ENODEV;
/* Identify port ID by matching base IO address */
for (i = 0; i < cfg->num_ports; i++) {
if (res->start == cfg->port_ids[i]) {
hdmi->port_id = i;
break;
}
}
if (hdmi->port_id < 0)
return dev_err_probe(hdmi->dev, hdmi->port_id,
"Failed to match HDMI port ID\n");
plat_data.phy_ops = cfg->phy_ops;
plat_data.phy_data = hdmi;
plat_data.max_bpc = 10;
encoder = &hdmi->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
dev->of_node, 0, 0);
/*
* 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;
hdmi->regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(hdmi->regmap))
return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->regmap),
"Unable to get rockchip,grf\n");
hdmi->vo_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,vo-grf");
if (IS_ERR(hdmi->vo_regmap))
return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->vo_regmap),
"Unable to get rockchip,vo-grf\n");
ret = devm_clk_bulk_get_all_enabled(hdmi->dev, &clks);
if (ret < 0)
return dev_err_probe(hdmi->dev, ret, "Failed to get clocks\n");
ref_clk = clk_get(hdmi->dev, "ref");
if (IS_ERR(ref_clk))
return dev_err_probe(hdmi->dev, PTR_ERR(ref_clk),
"Failed to get ref clock\n");
plat_data.ref_clk_rate = clk_get_rate(ref_clk);
clk_put(ref_clk);
hdmi->frl_enable_gpio = devm_gpiod_get_optional(hdmi->dev, "frl-enable",
GPIOD_OUT_LOW);
if (IS_ERR(hdmi->frl_enable_gpio))
return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->frl_enable_gpio),
"Failed to request FRL enable GPIO\n");
hdmi->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0);
if (IS_ERR(hdmi->phy))
return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->phy),
"Failed to get phy\n");
cfg->ctrl_ops->io_init(hdmi);
INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work);
plat_data.main_irq = platform_get_irq_byname(pdev, "main");
if (plat_data.main_irq < 0)
return plat_data.main_irq;
plat_data.cec_irq = platform_get_irq_byname(pdev, "cec");
if (plat_data.cec_irq < 0)
return plat_data.cec_irq;
irq = platform_get_irq_byname(pdev, "hpd");
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(hdmi->dev, irq,
cfg->ctrl_ops->hardirq_callback,
cfg->ctrl_ops->irq_callback,
IRQF_SHARED, "dw-hdmi-qp-hpd",
hdmi);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs);
ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return dev_err_probe(hdmi->dev, ret, "Failed to init encoder");
platform_set_drvdata(pdev, hdmi);
hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data);
if (IS_ERR(hdmi->hdmi))
return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->hdmi),
"Failed to bind dw-hdmi-qp");
connector = drm_bridge_connector_init(drm, encoder);
if (IS_ERR(connector))
return dev_err_probe(hdmi->dev, PTR_ERR(connector),
"Failed to init bridge connector\n");
return 0;
}
static void dw_hdmi_qp_rockchip_unbind(struct device *dev,
struct device *master,
void *data)
{
struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev);
cancel_delayed_work_sync(&hdmi->hpd_work);
}
static const struct component_ops dw_hdmi_qp_rockchip_ops = {
.bind = dw_hdmi_qp_rockchip_bind,
.unbind = dw_hdmi_qp_rockchip_unbind,
};
static int dw_hdmi_qp_rockchip_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &dw_hdmi_qp_rockchip_ops);
}
static void dw_hdmi_qp_rockchip_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dw_hdmi_qp_rockchip_ops);
}
static int __maybe_unused dw_hdmi_qp_rockchip_suspend(struct device *dev)
{
struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev);
dw_hdmi_qp_suspend(dev, hdmi->hdmi);
return 0;
}
static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev)
{
struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev);
hdmi->ctrl_ops->io_init(hdmi);
dw_hdmi_qp_resume(dev, hdmi->hdmi);
if (hdmi->encoder.encoder.dev)
drm_helper_hpd_irq_event(hdmi->encoder.encoder.dev);
return 0;
}
static const struct dev_pm_ops dw_hdmi_qp_rockchip_pm = {
SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_qp_rockchip_suspend,
dw_hdmi_qp_rockchip_resume)
};
struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver = {
.probe = dw_hdmi_qp_rockchip_probe,
.remove = dw_hdmi_qp_rockchip_remove,
.driver = {
.name = "dwhdmiqp-rockchip",
.pm = &dw_hdmi_qp_rockchip_pm,
.of_match_table = dw_hdmi_qp_rockchip_dt_ids,
},
};
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Zheng Yang <zhengyang@rock-chips.com>
* Andy Yan <andy.yan@rock-chips.com>
*/
#include <linux/err.h>
#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/bridge/inno_hdmi.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_managed.h>
#include <drm/drm_of.h>
#include "rockchip_drm_drv.h"
#define HIWORD_UPDATE(val, mask) ((val) | (mask) << 16)
#define RK3036_GRF_SOC_CON2 0x148
#define RK3036_HDMI_PHSYNC BIT(4)
#define RK3036_HDMI_PVSYNC BIT(5)
enum inno_hdmi_dev_type {
RK3036_HDMI,
RK3128_HDMI,
};
struct inno_hdmi_connector_state {
struct drm_connector_state base;
unsigned int colorimetry;
};
struct rockchip_inno_hdmi {
struct inno_hdmi *base;
struct device *dev;
struct regmap *grf;
struct rockchip_encoder encoder;
};
static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = {
{ 74250000, 0x3f, 0xbb },
{ 165000000, 0x6f, 0xbb },
{ ~0UL, 0x00, 0x00 }
};
static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = {
{ 74250000, 0x3f, 0xaa },
{ 165000000, 0x5f, 0xaa },
{ ~0UL, 0x00, 0x00 }
};
static void inno_hdmi_rk3036_enable(struct device *dev, struct drm_display_mode *mode)
{
struct rockchip_inno_hdmi *hdmi = dev_get_drvdata(dev);
int value, psync;
psync = mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0;
value = FIELD_PREP_WM16(RK3036_HDMI_PHSYNC, psync);
psync = mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0;
value |= FIELD_PREP_WM16(RK3036_HDMI_PVSYNC, psync);
regmap_write(hdmi->grf, RK3036_GRF_SOC_CON2, value);
}
static int inno_hdmi_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_P888;
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
return 0;
}
static const struct drm_encoder_helper_funcs inno_hdmi_rockchip_encoder_helper_funcs = {
.atomic_check = inno_hdmi_encoder_atomic_check,
};
static int inno_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data)
{
struct drm_device *drm = data;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct rockchip_inno_hdmi *hdmi;
const struct inno_hdmi_plat_data *plat_data;
int ret;
hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->dev = dev;
plat_data = of_device_get_match_data(hdmi->dev);
if (!plat_data)
return -EINVAL;
if (of_device_is_compatible(dev->of_node, "rockchip,rk3036-inno-hdmi")) {
hdmi->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
if (IS_ERR(hdmi->grf))
return dev_err_probe(dev,
PTR_ERR(hdmi->grf), "Unable to get rockchip,grf\n");
}
encoder = &hdmi->encoder.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 = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &inno_hdmi_rockchip_encoder_helper_funcs);
dev_set_drvdata(dev, hdmi);
hdmi->base = inno_hdmi_bind(dev, encoder, plat_data);
connector = drm_bridge_connector_init(drm, encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
dev_err(hdmi->dev, "failed to init bridge connector: %d\n", ret);
return ret;
}
return 0;
}
static const struct component_ops inno_hdmi_rockchip_ops = {
.bind = inno_hdmi_rockchip_bind,
};
static int inno_hdmi_rockchip_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &inno_hdmi_rockchip_ops);
}
static void inno_hdmi_rockchip_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &inno_hdmi_rockchip_ops);
}
static const struct inno_hdmi_plat_ops rk3036_inno_hdmi_plat_ops = {
.enable = inno_hdmi_rk3036_enable,
};
static const struct inno_hdmi_plat_data rk3036_inno_hdmi_plat_data = {
.ops = &rk3036_inno_hdmi_plat_ops,
.phy_configs = rk3036_hdmi_phy_configs,
.default_phy_config = &rk3036_hdmi_phy_configs[1],
};
static const struct inno_hdmi_plat_data rk3128_inno_hdmi_plat_data = {
.phy_configs = rk3128_hdmi_phy_configs,
.default_phy_config = &rk3128_hdmi_phy_configs[1],
};
static const struct of_device_id inno_hdmi_rockchip_dt_ids[] = {
{ .compatible = "rockchip,rk3036-inno-hdmi",
.data = &rk3036_inno_hdmi_plat_data,
},
{ .compatible = "rockchip,rk3128-inno-hdmi",
.data = &rk3128_inno_hdmi_plat_data,
},
{},
};
MODULE_DEVICE_TABLE(of, inno_hdmi_rockchip_dt_ids);
struct platform_driver inno_hdmi_driver = {
.probe = inno_hdmi_rockchip_probe,
.remove = inno_hdmi_rockchip_remove,
.driver = {
.name = "innohdmi-rockchip",
.of_match_table = inno_hdmi_rockchip_dt_ids,
},
};
@@ -0,0 +1,853 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Zheng Yang <zhengyang@rock-chips.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_bridge_connector.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "rk3066_hdmi.h"
#include "rockchip_drm_drv.h"
#define DEFAULT_PLLA_RATE 30000000
struct hdmi_data_info {
int vic; /* The CEA Video ID (VIC) of the current drm display mode. */
unsigned int enc_out_format;
unsigned int colorimetry;
};
struct rk3066_hdmi_i2c {
struct i2c_adapter adap;
u8 ddc_addr;
u8 segment_addr;
u8 stat;
struct mutex i2c_lock; /* For i2c operation. */
struct completion cmpltn;
};
struct rk3066_hdmi {
struct device *dev;
struct drm_device *drm_dev;
struct regmap *grf_regmap;
int irq;
struct clk *hclk;
void __iomem *regs;
struct drm_bridge bridge;
struct drm_connector *connector;
struct rockchip_encoder encoder;
struct rk3066_hdmi_i2c *i2c;
unsigned int tmdsclk;
struct hdmi_data_info hdmi_data;
};
static struct rk3066_hdmi *bridge_to_rk3066_hdmi(struct drm_bridge *bridge)
{
return container_of(bridge, struct rk3066_hdmi, bridge);
}
static inline u8 hdmi_readb(struct rk3066_hdmi *hdmi, u16 offset)
{
return readl_relaxed(hdmi->regs + offset);
}
static inline void hdmi_writeb(struct rk3066_hdmi *hdmi, u16 offset, u32 val)
{
writel_relaxed(val, hdmi->regs + offset);
}
static inline void hdmi_modb(struct rk3066_hdmi *hdmi, u16 offset,
u32 msk, u32 val)
{
u8 temp = hdmi_readb(hdmi, offset) & ~msk;
temp |= val & msk;
hdmi_writeb(hdmi, offset, temp);
}
static void rk3066_hdmi_i2c_init(struct rk3066_hdmi *hdmi)
{
int ddc_bus_freq;
ddc_bus_freq = (hdmi->tmdsclk >> 2) / HDMI_SCL_RATE;
hdmi_writeb(hdmi, HDMI_DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
hdmi_writeb(hdmi, HDMI_DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
/* Clear the EDID interrupt flag and mute the interrupt. */
hdmi_modb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_EDID_MASK, 0);
hdmi_writeb(hdmi, HDMI_INTR_STATUS1, HDMI_INTR_EDID_MASK);
}
static inline u8 rk3066_hdmi_get_power_mode(struct rk3066_hdmi *hdmi)
{
return hdmi_readb(hdmi, HDMI_SYS_CTRL) & HDMI_SYS_POWER_MODE_MASK;
}
static void rk3066_hdmi_set_power_mode(struct rk3066_hdmi *hdmi, int mode)
{
u8 current_mode, next_mode;
u8 i = 0;
current_mode = rk3066_hdmi_get_power_mode(hdmi);
DRM_DEV_DEBUG(hdmi->dev, "mode :%d\n", mode);
DRM_DEV_DEBUG(hdmi->dev, "current_mode :%d\n", current_mode);
if (current_mode == mode)
return;
do {
if (current_mode > mode) {
next_mode = current_mode / 2;
} else {
if (current_mode < HDMI_SYS_POWER_MODE_A)
next_mode = HDMI_SYS_POWER_MODE_A;
else
next_mode = current_mode * 2;
}
DRM_DEV_DEBUG(hdmi->dev, "%d: next_mode :%d\n", i, next_mode);
if (next_mode != HDMI_SYS_POWER_MODE_D) {
hdmi_modb(hdmi, HDMI_SYS_CTRL,
HDMI_SYS_POWER_MODE_MASK, next_mode);
} else {
hdmi_writeb(hdmi, HDMI_SYS_CTRL,
HDMI_SYS_POWER_MODE_D |
HDMI_SYS_PLL_RESET_MASK);
usleep_range(90, 100);
hdmi_writeb(hdmi, HDMI_SYS_CTRL,
HDMI_SYS_POWER_MODE_D |
HDMI_SYS_PLLB_RESET);
usleep_range(90, 100);
hdmi_writeb(hdmi, HDMI_SYS_CTRL,
HDMI_SYS_POWER_MODE_D);
}
current_mode = next_mode;
i = i + 1;
} while ((next_mode != mode) && (i < 5));
/*
* When the IP controller isn't configured with accurate video timing,
* DDC_CLK should be equal to the PLLA frequency, which is 30MHz,
* so we need to init the TMDS rate to the PCLK rate and reconfigure
* the DDC clock.
*/
if (mode < HDMI_SYS_POWER_MODE_D)
hdmi->tmdsclk = DEFAULT_PLLA_RATE;
}
static int rk3066_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
hdmi_writeb(hdmi, HDMI_CP_BUF_INDEX, HDMI_INFOFRAME_AVI);
return 0;
}
static int
rk3066_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge)
{
/* FIXME: add support for this InfoFrame */
drm_warn_once(bridge->encoder->dev, "HDMI VSI not supported\n");
return 0;
}
static int
rk3066_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
ssize_t i;
rk3066_hdmi_bridge_clear_avi_infoframe(bridge);
for (i = 0; i < len; i++)
hdmi_writeb(hdmi, HDMI_CP_BUF_ACC_HB0 + i * 4, buffer[i]);
return 0;
}
static int
rk3066_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
rk3066_hdmi_bridge_clear_hdmi_infoframe(bridge);
/* FIXME: add support for this InfoFrame */
return 0;
}
static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi,
struct drm_display_mode *mode)
{
int value, vsync_offset;
/* Set the details for the external polarity and interlace mode. */
value = HDMI_EXT_VIDEO_SET_EN;
value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
HDMI_VIDEO_HSYNC_ACTIVE_HIGH : HDMI_VIDEO_HSYNC_ACTIVE_LOW;
value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
HDMI_VIDEO_VSYNC_ACTIVE_HIGH : HDMI_VIDEO_VSYNC_ACTIVE_LOW;
value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
HDMI_VIDEO_MODE_INTERLACE : HDMI_VIDEO_MODE_PROGRESSIVE;
if (hdmi->hdmi_data.vic == 2 || hdmi->hdmi_data.vic == 3)
vsync_offset = 6;
else
vsync_offset = 0;
value |= vsync_offset << HDMI_VIDEO_VSYNC_OFFSET_SHIFT;
hdmi_writeb(hdmi, HDMI_EXT_VIDEO_PARA, value);
/* Set the details for the external video timing. */
value = mode->htotal;
hdmi_writeb(hdmi, HDMI_EXT_HTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_EXT_HTOTAL_H, (value >> 8) & 0xFF);
value = mode->htotal - mode->hdisplay;
hdmi_writeb(hdmi, HDMI_EXT_HBLANK_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_EXT_HBLANK_H, (value >> 8) & 0xFF);
value = mode->htotal - mode->hsync_start;
hdmi_writeb(hdmi, HDMI_EXT_HDELAY_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_EXT_HDELAY_H, (value >> 8) & 0xFF);
value = mode->hsync_end - mode->hsync_start;
hdmi_writeb(hdmi, HDMI_EXT_HDURATION_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_EXT_HDURATION_H, (value >> 8) & 0xFF);
value = mode->vtotal;
hdmi_writeb(hdmi, HDMI_EXT_VTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_EXT_VTOTAL_H, (value >> 8) & 0xFF);
value = mode->vtotal - mode->vdisplay;
hdmi_writeb(hdmi, HDMI_EXT_VBLANK_L, value & 0xFF);
value = mode->vtotal - mode->vsync_start + vsync_offset;
hdmi_writeb(hdmi, HDMI_EXT_VDELAY, value & 0xFF);
value = mode->vsync_end - mode->vsync_start;
hdmi_writeb(hdmi, HDMI_EXT_VDURATION, value & 0xFF);
return 0;
}
static void
rk3066_hdmi_phy_write(struct rk3066_hdmi *hdmi, u16 offset, u8 value)
{
hdmi_writeb(hdmi, offset, value);
hdmi_modb(hdmi, HDMI_SYS_CTRL,
HDMI_SYS_PLL_RESET_MASK, HDMI_SYS_PLL_RESET);
usleep_range(90, 100);
hdmi_modb(hdmi, HDMI_SYS_CTRL, HDMI_SYS_PLL_RESET_MASK, 0);
usleep_range(900, 1000);
}
static void rk3066_hdmi_config_phy(struct rk3066_hdmi *hdmi)
{
/* TMDS uses the same frequency as dclk. */
hdmi_writeb(hdmi, HDMI_DEEP_COLOR_MODE, 0x22);
/*
* The semi-public documentation does not describe the hdmi registers
* used by the function rk3066_hdmi_phy_write(), so we keep using
* these magic values for now.
*/
if (hdmi->tmdsclk > 100000000) {
rk3066_hdmi_phy_write(hdmi, 0x158, 0x0E);
rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x168, 0xDA);
rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA1);
rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
rk3066_hdmi_phy_write(hdmi, 0x174, 0x22);
rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
} else if (hdmi->tmdsclk > 50000000) {
rk3066_hdmi_phy_write(hdmi, 0x158, 0x06);
rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x168, 0xCA);
rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA3);
rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
rk3066_hdmi_phy_write(hdmi, 0x174, 0x20);
rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
} else {
rk3066_hdmi_phy_write(hdmi, 0x158, 0x02);
rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
rk3066_hdmi_phy_write(hdmi, 0x168, 0xC2);
rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA2);
rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
rk3066_hdmi_phy_write(hdmi, 0x174, 0x20);
rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
}
}
static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
struct drm_atomic_commit *state)
{
struct drm_bridge *bridge = &hdmi->bridge;
struct drm_connector *connector;
struct drm_display_info *display;
struct drm_display_mode *mode;
struct drm_connector_state *new_conn_state;
struct drm_crtc_state *new_crtc_state;
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
new_conn_state = drm_atomic_get_new_connector_state(state, connector);
if (WARN_ON(!new_conn_state))
return -EINVAL;
new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
if (WARN_ON(!new_crtc_state))
return -EINVAL;
display = &connector->display_info;
mode = &new_crtc_state->adjusted_mode;
hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
if (hdmi->hdmi_data.vic == 6 || hdmi->hdmi_data.vic == 7 ||
hdmi->hdmi_data.vic == 21 || hdmi->hdmi_data.vic == 22 ||
hdmi->hdmi_data.vic == 2 || hdmi->hdmi_data.vic == 3 ||
hdmi->hdmi_data.vic == 17 || hdmi->hdmi_data.vic == 18)
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
else
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
hdmi->tmdsclk = mode->clock * 1000;
/* Mute video and audio output. */
hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_VIDEO_AUDIO_DISABLE_MASK,
HDMI_AUDIO_DISABLE | HDMI_VIDEO_DISABLE);
/* Set power state to mode B. */
if (rk3066_hdmi_get_power_mode(hdmi) != HDMI_SYS_POWER_MODE_B)
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_B);
/* Input video mode is RGB 24 bit. Use external data enable signal. */
hdmi_modb(hdmi, HDMI_AV_CTRL1,
HDMI_VIDEO_DE_MASK, HDMI_VIDEO_EXTERNAL_DE);
hdmi_writeb(hdmi, HDMI_VIDEO_CTRL1,
HDMI_VIDEO_OUTPUT_RGB444 |
HDMI_VIDEO_INPUT_DATA_DEPTH_8BIT |
HDMI_VIDEO_INPUT_COLOR_RGB);
hdmi_writeb(hdmi, HDMI_DEEP_COLOR_MODE, 0x20);
rk3066_hdmi_config_video_timing(hdmi, mode);
if (display->is_hdmi) {
hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK,
HDMI_VIDEO_MODE_HDMI);
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
} else {
hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0);
}
rk3066_hdmi_config_phy(hdmi);
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_E);
/*
* When the IP controller is configured with accurate video
* timing, the TMDS clock source should be switched to
* DCLK_LCDC, so we need to init the TMDS rate to the pixel mode
* clock rate and reconfigure the DDC clock.
*/
rk3066_hdmi_i2c_init(hdmi);
/* Unmute video output. */
hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
HDMI_VIDEO_AUDIO_DISABLE_MASK, HDMI_AUDIO_DISABLE);
return 0;
}
static void rk3066_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
int mux, val;
conn_state = drm_atomic_get_new_connector_state(state, hdmi->connector);
if (WARN_ON(!conn_state))
return;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
if (WARN_ON(!crtc_state))
return;
mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, &hdmi->encoder.encoder);
if (mux)
val = (HDMI_VIDEO_SEL << 16) | HDMI_VIDEO_SEL;
else
val = HDMI_VIDEO_SEL << 16;
regmap_write(hdmi->grf_regmap, GRF_SOC_CON0, val);
DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder enable select: vop%s\n",
(mux) ? "1" : "0");
rk3066_hdmi_setup(hdmi, state);
}
static void rk3066_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder disable\n");
if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_E) {
hdmi_writeb(hdmi, HDMI_VIDEO_CTRL2,
HDMI_VIDEO_AUDIO_DISABLE_MASK);
hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
HDMI_AUDIO_CP_LOGIC_RESET_MASK,
HDMI_AUDIO_CP_LOGIC_RESET);
usleep_range(500, 510);
}
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
}
static int
rk3066_hdmi_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_P888;
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
return 0;
}
static const
struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = {
.atomic_check = rk3066_hdmi_encoder_atomic_check,
};
static enum drm_connector_status
rk3066_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
return (hdmi_readb(hdmi, HDMI_HPG_MENS_STA) & HDMI_HPG_IN_STATUS_HIGH) ?
connector_status_connected : connector_status_disconnected;
}
static const struct drm_edid *
rk3066_hdmi_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge);
const struct drm_edid *drm_edid;
drm_edid = drm_edid_read_ddc(connector, bridge->ddc);
if (!drm_edid)
dev_dbg(hdmi->dev, "failed to get edid\n");
return drm_edid;
}
static enum drm_mode_status
rk3066_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
u32 vic = drm_match_cea_mode(mode);
if (vic > 1)
return MODE_OK;
else
return MODE_BAD;
}
static const struct drm_bridge_funcs rk3066_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_enable = rk3066_hdmi_bridge_atomic_enable,
.atomic_disable = rk3066_hdmi_bridge_atomic_disable,
.detect = rk3066_hdmi_bridge_detect,
.edid_read = rk3066_hdmi_bridge_edid_read,
.hdmi_clear_avi_infoframe = rk3066_hdmi_bridge_clear_avi_infoframe,
.hdmi_write_avi_infoframe = rk3066_hdmi_bridge_write_avi_infoframe,
.hdmi_clear_hdmi_infoframe = rk3066_hdmi_bridge_clear_hdmi_infoframe,
.hdmi_write_hdmi_infoframe = rk3066_hdmi_bridge_write_hdmi_infoframe,
.mode_valid = rk3066_hdmi_bridge_mode_valid,
};
static irqreturn_t rk3066_hdmi_hardirq(int irq, void *dev_id)
{
struct rk3066_hdmi *hdmi = dev_id;
irqreturn_t ret = IRQ_NONE;
u8 interrupt;
if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_A)
hdmi_writeb(hdmi, HDMI_SYS_CTRL, HDMI_SYS_POWER_MODE_B);
interrupt = hdmi_readb(hdmi, HDMI_INTR_STATUS1);
if (interrupt)
hdmi_writeb(hdmi, HDMI_INTR_STATUS1, interrupt);
if (interrupt & HDMI_INTR_EDID_MASK) {
hdmi->i2c->stat = interrupt;
complete(&hdmi->i2c->cmpltn);
}
if (interrupt & (HDMI_INTR_HOTPLUG | HDMI_INTR_MSENS))
ret = IRQ_WAKE_THREAD;
return ret;
}
static irqreturn_t rk3066_hdmi_irq(int irq, void *dev_id)
{
struct rk3066_hdmi *hdmi = dev_id;
drm_helper_hpd_irq_event(hdmi->connector->dev);
return IRQ_HANDLED;
}
static int rk3066_hdmi_i2c_read(struct rk3066_hdmi *hdmi, struct i2c_msg *msgs)
{
int length = msgs->len;
u8 *buf = msgs->buf;
int ret;
ret = wait_for_completion_timeout(&hdmi->i2c->cmpltn, HZ / 10);
if (!ret || hdmi->i2c->stat & HDMI_INTR_EDID_ERR)
return -EAGAIN;
while (length--)
*buf++ = hdmi_readb(hdmi, HDMI_DDC_READ_FIFO_ADDR);
return 0;
}
static int rk3066_hdmi_i2c_write(struct rk3066_hdmi *hdmi, struct i2c_msg *msgs)
{
/*
* The DDC module only supports read EDID message, so
* we assume that each word write to this i2c adapter
* should be the offset of the EDID word address.
*/
if (msgs->len != 1 ||
(msgs->addr != DDC_ADDR && msgs->addr != DDC_SEGMENT_ADDR))
return -EINVAL;
reinit_completion(&hdmi->i2c->cmpltn);
if (msgs->addr == DDC_SEGMENT_ADDR)
hdmi->i2c->segment_addr = msgs->buf[0];
if (msgs->addr == DDC_ADDR)
hdmi->i2c->ddc_addr = msgs->buf[0];
/* Set edid fifo first address. */
hdmi_writeb(hdmi, HDMI_EDID_FIFO_ADDR, 0x00);
/* Set edid word address 0x00/0x80. */
hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
/* Set edid segment pointer. */
hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
return 0;
}
static int rk3066_hdmi_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct rk3066_hdmi *hdmi = i2c_get_adapdata(adap);
struct rk3066_hdmi_i2c *i2c = hdmi->i2c;
int i, ret = 0;
mutex_lock(&i2c->i2c_lock);
rk3066_hdmi_i2c_init(hdmi);
/* Unmute HDMI EDID interrupt. */
hdmi_modb(hdmi, HDMI_INTR_MASK1,
HDMI_INTR_EDID_MASK, HDMI_INTR_EDID_MASK);
i2c->stat = 0;
for (i = 0; i < num; i++) {
DRM_DEV_DEBUG(hdmi->dev,
"xfer: num: %d/%d, len: %d, flags: %#x\n",
i + 1, num, msgs[i].len, msgs[i].flags);
if (msgs[i].flags & I2C_M_RD)
ret = rk3066_hdmi_i2c_read(hdmi, &msgs[i]);
else
ret = rk3066_hdmi_i2c_write(hdmi, &msgs[i]);
if (ret < 0)
break;
}
if (!ret)
ret = num;
/* Mute HDMI EDID interrupt. */
hdmi_modb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_EDID_MASK, 0);
mutex_unlock(&i2c->i2c_lock);
return ret;
}
static u32 rk3066_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm rk3066_hdmi_algorithm = {
.master_xfer = rk3066_hdmi_i2c_xfer,
.functionality = rk3066_hdmi_i2c_func,
};
static struct i2c_adapter *rk3066_hdmi_i2c_adapter(struct rk3066_hdmi *hdmi)
{
struct i2c_adapter *adap;
struct rk3066_hdmi_i2c *i2c;
int ret;
i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c)
return ERR_PTR(-ENOMEM);
mutex_init(&i2c->i2c_lock);
init_completion(&i2c->cmpltn);
adap = &i2c->adap;
adap->owner = THIS_MODULE;
adap->dev.parent = hdmi->dev;
adap->dev.of_node = hdmi->dev->of_node;
adap->algo = &rk3066_hdmi_algorithm;
strscpy(adap->name, "RK3066 HDMI", sizeof(adap->name));
i2c_set_adapdata(adap, hdmi);
ret = devm_i2c_add_adapter(hdmi->dev, adap);
if (ret) {
DRM_DEV_ERROR(hdmi->dev, "cannot add %s I2C adapter\n",
adap->name);
devm_kfree(hdmi->dev, i2c);
return ERR_PTR(ret);
}
hdmi->i2c = i2c;
DRM_DEV_DEBUG(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
return adap;
}
static int
rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
{
struct drm_encoder *encoder = &hdmi->encoder.encoder;
struct device *dev = hdmi->dev;
int ret;
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;
drm_encoder_helper_add(encoder, &rk3066_hdmi_encoder_helper_funcs);
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
hdmi->bridge.driver_private = hdmi;
hdmi->bridge.funcs = &rk3066_hdmi_bridge_funcs;
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT |
DRM_BRIDGE_OP_EDID |
DRM_BRIDGE_OP_HDMI |
DRM_BRIDGE_OP_HPD;
hdmi->bridge.of_node = hdmi->dev->of_node;
hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
hdmi->bridge.vendor = "Rockchip";
hdmi->bridge.product = "RK3066 HDMI";
hdmi->bridge.ddc = rk3066_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->bridge.ddc))
return PTR_ERR(hdmi->bridge.ddc);
if (IS_ERR(hdmi->bridge.ddc))
return PTR_ERR(hdmi->bridge.ddc);
ret = devm_drm_bridge_add(dev, &hdmi->bridge);
if (ret)
return ret;
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
return ret;
hdmi->connector = drm_bridge_connector_init(drm, encoder);
if (IS_ERR(hdmi->connector)) {
ret = PTR_ERR(hdmi->connector);
dev_err(hdmi->dev, "failed to init bridge connector: %d\n", ret);
return ret;
}
return 0;
}
static int rk3066_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct rk3066_hdmi *hdmi;
int irq;
int ret;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->dev = dev;
hdmi->drm_dev = drm;
hdmi->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
hdmi->hclk = devm_clk_get(dev, "hclk");
if (IS_ERR(hdmi->hclk)) {
DRM_DEV_ERROR(dev, "unable to get HDMI hclk clock\n");
return PTR_ERR(hdmi->hclk);
}
ret = clk_prepare_enable(hdmi->hclk);
if (ret) {
DRM_DEV_ERROR(dev, "cannot enable HDMI hclk clock: %d\n", ret);
return ret;
}
hdmi->grf_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(hdmi->grf_regmap)) {
DRM_DEV_ERROR(dev, "unable to get rockchip,grf\n");
ret = PTR_ERR(hdmi->grf_regmap);
goto err_disable_hclk;
}
/* internal hclk = hdmi_hclk / 25 */
hdmi_writeb(hdmi, HDMI_INTERNAL_CLK_DIVIDER, 25);
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_B);
usleep_range(999, 1000);
hdmi_writeb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_HOTPLUG);
hdmi_writeb(hdmi, HDMI_INTR_MASK2, 0);
hdmi_writeb(hdmi, HDMI_INTR_MASK3, 0);
hdmi_writeb(hdmi, HDMI_INTR_MASK4, 0);
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
ret = rk3066_hdmi_register(drm, hdmi);
if (ret)
goto err_disable_hclk;
dev_set_drvdata(dev, hdmi);
ret = devm_request_threaded_irq(dev, irq, rk3066_hdmi_hardirq,
rk3066_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
if (ret) {
DRM_DEV_ERROR(dev, "failed to request hdmi irq: %d\n", ret);
goto err_cleanup_hdmi;
}
return 0;
err_cleanup_hdmi:
hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
err_disable_hclk:
clk_disable_unprepare(hdmi->hclk);
return ret;
}
static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
clk_disable_unprepare(hdmi->hclk);
}
static const struct component_ops rk3066_hdmi_ops = {
.bind = rk3066_hdmi_bind,
.unbind = rk3066_hdmi_unbind,
};
static int rk3066_hdmi_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &rk3066_hdmi_ops);
}
static void rk3066_hdmi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &rk3066_hdmi_ops);
}
static const struct of_device_id rk3066_hdmi_dt_ids[] = {
{ .compatible = "rockchip,rk3066-hdmi" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rk3066_hdmi_dt_ids);
struct platform_driver rk3066_hdmi_driver = {
.probe = rk3066_hdmi_probe,
.remove = rk3066_hdmi_remove,
.driver = {
.name = "rockchip-rk3066-hdmi",
.of_match_table = rk3066_hdmi_dt_ids,
},
};
@@ -0,0 +1,229 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Zheng Yang <zhengyang@rock-chips.com>
*/
#ifndef __RK3066_HDMI_H__
#define __RK3066_HDMI_H__
#define GRF_SOC_CON0 0x150
#define HDMI_VIDEO_SEL BIT(14)
#define DDC_SEGMENT_ADDR 0x30
#define HDMI_SCL_RATE (50 * 1000)
#define HDMI_MAXIMUM_INFO_FRAME_SIZE 0x11
#define N_32K 0x1000
#define N_441K 0x1880
#define N_882K 0x3100
#define N_1764K 0x6200
#define N_48K 0x1800
#define N_96K 0x3000
#define N_192K 0x6000
#define HDMI_SYS_CTRL 0x000
#define HDMI_LR_SWAP_N3 0x004
#define HDMI_N2 0x008
#define HDMI_N1 0x00c
#define HDMI_SPDIF_FS_CTS_INT3 0x010
#define HDMI_CTS_INT2 0x014
#define HDMI_CTS_INT1 0x018
#define HDMI_CTS_EXT3 0x01c
#define HDMI_CTS_EXT2 0x020
#define HDMI_CTS_EXT1 0x024
#define HDMI_AUDIO_CTRL1 0x028
#define HDMI_AUDIO_CTRL2 0x02c
#define HDMI_I2S_AUDIO_CTRL 0x030
#define HDMI_I2S_SWAP 0x040
#define HDMI_AUDIO_STA_BIT_CTRL1 0x044
#define HDMI_AUDIO_STA_BIT_CTRL2 0x048
#define HDMI_AUDIO_SRC_NUM_AND_LENGTH 0x050
#define HDMI_AV_CTRL1 0x054
#define HDMI_VIDEO_CTRL1 0x058
#define HDMI_DEEP_COLOR_MODE 0x05c
#define HDMI_EXT_VIDEO_PARA 0x0c0
#define HDMI_EXT_HTOTAL_L 0x0c4
#define HDMI_EXT_HTOTAL_H 0x0c8
#define HDMI_EXT_HBLANK_L 0x0cc
#define HDMI_EXT_HBLANK_H 0x0d0
#define HDMI_EXT_HDELAY_L 0x0d4
#define HDMI_EXT_HDELAY_H 0x0d8
#define HDMI_EXT_HDURATION_L 0x0dc
#define HDMI_EXT_HDURATION_H 0x0e0
#define HDMI_EXT_VTOTAL_L 0x0e4
#define HDMI_EXT_VTOTAL_H 0x0e8
#define HDMI_AV_CTRL2 0x0ec
#define HDMI_EXT_VBLANK_L 0x0f4
#define HDMI_EXT_VBLANK_H 0x10c
#define HDMI_EXT_VDELAY 0x0f8
#define HDMI_EXT_VDURATION 0x0fc
#define HDMI_CP_MANU_SEND_CTRL 0x100
#define HDMI_CP_AUTO_SEND_CTRL 0x104
#define HDMI_AUTO_CHECKSUM_OPT 0x108
#define HDMI_VIDEO_CTRL2 0x114
#define HDMI_PHY_OPTION 0x144
#define HDMI_CP_BUF_INDEX 0x17c
#define HDMI_CP_BUF_ACC_HB0 0x180
#define HDMI_CP_BUF_ACC_HB1 0x184
#define HDMI_CP_BUF_ACC_HB2 0x188
#define HDMI_CP_BUF_ACC_PB0 0x18c
#define HDMI_DDC_READ_FIFO_ADDR 0x200
#define HDMI_DDC_BUS_FREQ_L 0x204
#define HDMI_DDC_BUS_FREQ_H 0x208
#define HDMI_DDC_BUS_CTRL 0x2dc
#define HDMI_DDC_I2C_LEN 0x278
#define HDMI_DDC_I2C_OFFSET 0x280
#define HDMI_DDC_I2C_CTRL 0x284
#define HDMI_DDC_I2C_READ_BUF0 0x288
#define HDMI_DDC_I2C_READ_BUF1 0x28c
#define HDMI_DDC_I2C_READ_BUF2 0x290
#define HDMI_DDC_I2C_READ_BUF3 0x294
#define HDMI_DDC_I2C_WRITE_BUF0 0x298
#define HDMI_DDC_I2C_WRITE_BUF1 0x29c
#define HDMI_DDC_I2C_WRITE_BUF2 0x2a0
#define HDMI_DDC_I2C_WRITE_BUF3 0x2a4
#define HDMI_DDC_I2C_WRITE_BUF4 0x2ac
#define HDMI_DDC_I2C_WRITE_BUF5 0x2b0
#define HDMI_DDC_I2C_WRITE_BUF6 0x2b4
#define HDMI_INTR_MASK1 0x248
#define HDMI_INTR_MASK2 0x24c
#define HDMI_INTR_STATUS1 0x250
#define HDMI_INTR_STATUS2 0x254
#define HDMI_INTR_MASK3 0x258
#define HDMI_INTR_MASK4 0x25c
#define HDMI_INTR_STATUS3 0x260
#define HDMI_INTR_STATUS4 0x264
#define HDMI_HDCP_CTRL 0x2bc
#define HDMI_EDID_SEGMENT_POINTER 0x310
#define HDMI_EDID_WORD_ADDR 0x314
#define HDMI_EDID_FIFO_ADDR 0x318
#define HDMI_HPG_MENS_STA 0x37c
#define HDMI_INTERNAL_CLK_DIVIDER 0x800
enum {
/* HDMI_SYS_CTRL */
HDMI_SYS_POWER_MODE_MASK = 0xf0,
HDMI_SYS_POWER_MODE_A = 0x10,
HDMI_SYS_POWER_MODE_B = 0x20,
HDMI_SYS_POWER_MODE_D = 0x40,
HDMI_SYS_POWER_MODE_E = 0x80,
HDMI_SYS_PLL_RESET_MASK = 0x0c,
HDMI_SYS_PLL_RESET = 0x0c,
HDMI_SYS_PLLB_RESET = 0x08,
/* HDMI_LR_SWAP_N3 */
HDMI_AUDIO_LR_SWAP_MASK = 0xf0,
HDMI_AUDIO_LR_SWAP_SUBPACKET0 = 0x10,
HDMI_AUDIO_LR_SWAP_SUBPACKET1 = 0x20,
HDMI_AUDIO_LR_SWAP_SUBPACKET2 = 0x40,
HDMI_AUDIO_LR_SWAP_SUBPACKET3 = 0x80,
HDMI_AUDIO_N_19_16_MASK = 0x0f,
/* HDMI_AUDIO_CTRL1 */
HDMI_AUDIO_EXTERNAL_CTS = BIT(7),
HDMI_AUDIO_INPUT_IIS = 0,
HDMI_AUDIO_INPUT_SPDIF = 0x08,
HDMI_AUDIO_INPUT_MCLK_ACTIVE = 0x04,
HDMI_AUDIO_INPUT_MCLK_DEACTIVE = 0,
HDMI_AUDIO_INPUT_MCLK_RATE_128X = 0,
HDMI_AUDIO_INPUT_MCLK_RATE_256X = 1,
HDMI_AUDIO_INPUT_MCLK_RATE_384X = 2,
HDMI_AUDIO_INPUT_MCLK_RATE_512X = 3,
/* HDMI_I2S_AUDIO_CTRL */
HDMI_AUDIO_I2S_FORMAT_STANDARD = 0,
HDMI_AUDIO_I2S_CHANNEL_1_2 = 0x04,
HDMI_AUDIO_I2S_CHANNEL_3_4 = 0x0c,
HDMI_AUDIO_I2S_CHANNEL_5_6 = 0x1c,
HDMI_AUDIO_I2S_CHANNEL_7_8 = 0x3c,
/* HDMI_AV_CTRL1 */
HDMI_AUDIO_SAMPLE_FRE_MASK = 0xf0,
HDMI_AUDIO_SAMPLE_FRE_32000 = 0x30,
HDMI_AUDIO_SAMPLE_FRE_44100 = 0,
HDMI_AUDIO_SAMPLE_FRE_48000 = 0x20,
HDMI_AUDIO_SAMPLE_FRE_88200 = 0x80,
HDMI_AUDIO_SAMPLE_FRE_96000 = 0xa0,
HDMI_AUDIO_SAMPLE_FRE_176400 = 0xc0,
HDMI_AUDIO_SAMPLE_FRE_192000 = 0xe0,
HDMI_AUDIO_SAMPLE_FRE_768000 = 0x90,
HDMI_VIDEO_INPUT_FORMAT_MASK = 0x0e,
HDMI_VIDEO_INPUT_RGB_YCBCR444 = 0,
HDMI_VIDEO_INPUT_YCBCR422 = 0x02,
HDMI_VIDEO_DE_MASK = 0x1,
HDMI_VIDEO_INTERNAL_DE = 0,
HDMI_VIDEO_EXTERNAL_DE = 0x01,
/* HDMI_VIDEO_CTRL1 */
HDMI_VIDEO_OUTPUT_FORMAT_MASK = 0xc0,
HDMI_VIDEO_OUTPUT_RGB444 = 0,
HDMI_VIDEO_OUTPUT_YCBCR444 = 0x40,
HDMI_VIDEO_OUTPUT_YCBCR422 = 0x80,
HDMI_VIDEO_INPUT_DATA_DEPTH_MASK = 0x30,
HDMI_VIDEO_INPUT_DATA_DEPTH_12BIT = 0,
HDMI_VIDEO_INPUT_DATA_DEPTH_10BIT = 0x10,
HDMI_VIDEO_INPUT_DATA_DEPTH_8BIT = 0x30,
HDMI_VIDEO_INPUT_COLOR_MASK = 1,
HDMI_VIDEO_INPUT_COLOR_RGB = 0,
HDMI_VIDEO_INPUT_COLOR_YCBCR = 1,
/* HDMI_EXT_VIDEO_PARA */
HDMI_VIDEO_VSYNC_OFFSET_SHIFT = 4,
HDMI_VIDEO_VSYNC_ACTIVE_HIGH = BIT(3),
HDMI_VIDEO_VSYNC_ACTIVE_LOW = 0,
HDMI_VIDEO_HSYNC_ACTIVE_HIGH = BIT(2),
HDMI_VIDEO_HSYNC_ACTIVE_LOW = 0,
HDMI_VIDEO_MODE_INTERLACE = BIT(1),
HDMI_VIDEO_MODE_PROGRESSIVE = 0,
HDMI_EXT_VIDEO_SET_EN = BIT(0),
/* HDMI_CP_AUTO_SEND_CTRL */
/* HDMI_VIDEO_CTRL2 */
HDMI_VIDEO_AV_MUTE_MASK = 0xc0,
HDMI_VIDEO_CLR_AV_MUTE = BIT(7),
HDMI_VIDEO_SET_AV_MUTE = BIT(6),
HDMI_AUDIO_CP_LOGIC_RESET_MASK = BIT(2),
HDMI_AUDIO_CP_LOGIC_RESET = BIT(2),
HDMI_VIDEO_AUDIO_DISABLE_MASK = 0x3,
HDMI_AUDIO_DISABLE = BIT(1),
HDMI_VIDEO_DISABLE = BIT(0),
/* HDMI_CP_BUF_INDEX */
HDMI_INFOFRAME_VSI = 0x05,
HDMI_INFOFRAME_AVI = 0x06,
HDMI_INFOFRAME_AAI = 0x08,
/* HDMI_INTR_MASK1 */
/* HDMI_INTR_STATUS1 */
HDMI_INTR_HOTPLUG = BIT(7),
HDMI_INTR_MSENS = BIT(6),
HDMI_INTR_VSYNC = BIT(5),
HDMI_INTR_AUDIO_FIFO_FULL = BIT(4),
HDMI_INTR_EDID_MASK = 0x6,
HDMI_INTR_EDID_READY = BIT(2),
HDMI_INTR_EDID_ERR = BIT(1),
/* HDMI_HDCP_CTRL */
HDMI_VIDEO_MODE_MASK = BIT(1),
HDMI_VIDEO_MODE_HDMI = BIT(1),
/* HDMI_HPG_MENS_STA */
HDMI_HPG_IN_STATUS_HIGH = BIT(7),
HDMI_MSENS_IN_STATUS_HIGH = BIT(6),
};
#endif /* __RK3066_HDMI_H__ */
@@ -0,0 +1,579 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*
* based on exynos_drm_drv.c
*/
#include <linux/aperture.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/component.h>
#include <linux/console.h>
#include <linux/iommu.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#if defined(CONFIG_ARM_DMA_USE_IOMMU)
#include <asm/dma-iommu.h>
#else
#define arm_iommu_detach_device(...) ({ })
#define arm_iommu_release_mapping(...) ({ })
#define to_dma_iommu_mapping(dev) NULL
#endif
#include "rockchip_drm_drv.h"
#include "rockchip_drm_fb.h"
#include "rockchip_drm_gem.h"
#define DRIVER_NAME "rockchip"
#define DRIVER_DESC "RockChip Soc DRM"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
static const struct drm_driver rockchip_drm_driver;
/*
* Attach a (component) device to the shared drm dma mapping from master drm
* device. This is used by the VOPs to map GEM buffers to a common DMA
* mapping.
*/
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
struct device *dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
int ret;
if (!private->domain)
return 0;
if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) {
struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
if (mapping) {
arm_iommu_detach_device(dev);
arm_iommu_release_mapping(mapping);
}
}
ret = iommu_attach_device(private->domain, dev);
if (ret) {
DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
return ret;
}
return 0;
}
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
struct device *dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
if (!private->domain)
return;
iommu_detach_device(private->domain, dev);
}
void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
struct device *dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
if (!device_iommu_mapped(dev))
private->iommu_dev = ERR_PTR(-ENODEV);
else if (!private->iommu_dev)
private->iommu_dev = dev;
if (!IS_ERR(private->iommu_dev))
drm_dev_set_dma_dev(drm_dev, private->iommu_dev);
}
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
struct iommu_domain_geometry *geometry;
u64 start, end;
int ret;
if (IS_ERR_OR_NULL(private->iommu_dev))
return 0;
private->domain = iommu_paging_domain_alloc(private->iommu_dev);
if (IS_ERR(private->domain)) {
ret = PTR_ERR(private->domain);
private->domain = NULL;
return ret;
}
geometry = &private->domain->geometry;
start = geometry->aperture_start;
end = geometry->aperture_end;
DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
start, end);
drm_mm_init(&private->mm, start, end - start + 1);
mutex_init(&private->mm_lock);
return 0;
}
static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
if (!private->domain)
return;
drm_mm_takedown(&private->mm);
iommu_domain_free(private->domain);
}
static int rockchip_drm_bind(struct device *dev)
{
struct drm_device *drm_dev;
struct rockchip_drm_private *private;
int ret;
/* Remove existing drivers that may own the framebuffer memory. */
ret = aperture_remove_all_conflicting_devices(rockchip_drm_driver.name);
if (ret) {
DRM_DEV_ERROR(dev,
"Failed to remove existing framebuffers - %d.\n",
ret);
return ret;
}
drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
if (IS_ERR(drm_dev))
return PTR_ERR(drm_dev);
dev_set_drvdata(dev, drm_dev);
private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
if (!private) {
ret = -ENOMEM;
goto err_free;
}
drm_dev->dev_private = private;
ret = drmm_mode_config_init(drm_dev);
if (ret)
goto err_free;
rockchip_drm_mode_config_init(drm_dev);
/* Try to bind all sub drivers. */
ret = component_bind_all(dev, drm_dev);
if (ret)
goto err_free;
ret = rockchip_drm_init_iommu(drm_dev);
if (ret)
goto err_unbind_all;
ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
if (ret)
goto err_iommu_cleanup;
drm_mode_config_reset(drm_dev);
/* init kms poll for handling hpd */
drm_kms_helper_poll_init(drm_dev);
ret = drm_dev_register(drm_dev, 0);
if (ret)
goto err_kms_helper_poll_fini;
drm_client_setup(drm_dev, NULL);
return 0;
err_kms_helper_poll_fini:
drm_kms_helper_poll_fini(drm_dev);
err_iommu_cleanup:
rockchip_iommu_cleanup(drm_dev);
err_unbind_all:
component_unbind_all(dev, drm_dev);
err_free:
drm_dev_put(drm_dev);
return ret;
}
static void rockchip_drm_unbind(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
drm_dev_unregister(drm_dev);
drm_kms_helper_poll_fini(drm_dev);
drm_atomic_helper_shutdown(drm_dev);
component_unbind_all(dev, drm_dev);
rockchip_iommu_cleanup(drm_dev);
drm_dev_put(drm_dev);
}
DEFINE_DRM_GEM_FOPS(rockchip_drm_driver_fops);
static const struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.dumb_create = rockchip_gem_dumb_create,
.gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table,
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &rockchip_drm_driver_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
};
#ifdef CONFIG_PM_SLEEP
static int rockchip_drm_sys_suspend(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(drm);
}
static int rockchip_drm_sys_resume(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(drm);
}
#endif
static const struct dev_pm_ops rockchip_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
rockchip_drm_sys_resume)
};
#define MAX_ROCKCHIP_SUB_DRIVERS 16
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
static int num_rockchip_sub_drivers;
/*
* Get the endpoint id of the remote endpoint of the given encoder. This
* information is used by the VOP2 driver to identify the encoder.
*
* @rkencoder: The encoder to get the remote endpoint id from
* @np: The encoder device node
* @port: The number of the port leading to the VOP2
* @reg: The endpoint number leading to the VOP2
*/
int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder,
struct device_node *np, int port, int reg)
{
struct of_endpoint ep;
struct device_node *en, *ren;
int ret;
en = of_graph_get_endpoint_by_regs(np, port, reg);
if (!en)
return -ENOENT;
ren = of_graph_get_remote_endpoint(en);
if (!ren)
return -ENOENT;
ret = of_graph_parse_endpoint(ren, &ep);
if (ret)
return ret;
rkencoder->crtc_endpoint_id = ep.id;
return 0;
}
/*
* Check if a vop endpoint is leading to a rockchip subdriver or bridge.
* Should be called from the component bind stage of the drivers
* to ensure that all subdrivers are probed.
*
* @ep: endpoint of a rockchip vop
*
* returns true if subdriver, false if external bridge and -ENODEV
* if remote port does not contain a device.
*/
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep)
{
struct device_node *node = of_graph_get_remote_port_parent(ep);
struct platform_device *pdev;
struct device_driver *drv;
int i;
if (!node)
return -ENODEV;
/* status disabled will prevent creation of platform-devices */
if (!of_device_is_available(node)) {
of_node_put(node);
return -ENODEV;
}
pdev = of_find_device_by_node(node);
of_node_put(node);
/* enabled non-platform-devices can immediately return here */
if (!pdev)
return false;
/*
* All rockchip subdrivers have probed at this point, so
* any device not having a driver now is an external bridge.
*/
drv = pdev->dev.driver;
if (!drv) {
platform_device_put(pdev);
return false;
}
for (i = 0; i < num_rockchip_sub_drivers; i++) {
if (rockchip_sub_drivers[i] == to_platform_driver(drv)) {
platform_device_put(pdev);
return true;
}
}
platform_device_put(pdev);
return false;
}
static void rockchip_drm_match_remove(struct device *dev)
{
struct device_link *link;
list_for_each_entry(link, &dev->links.consumers, s_node)
device_link_del(link);
}
/* list of preferred vop devices */
static const char *const rockchip_drm_match_preferred[] = {
"rockchip,rk3399-vop-big",
NULL,
};
static struct component_match *rockchip_drm_match_add(struct device *dev)
{
struct component_match *match = NULL;
struct device_node *port;
int i;
/* add preferred vop device match before adding driver device matches */
for (i = 0; ; i++) {
port = of_parse_phandle(dev->of_node, "ports", i);
if (!port)
break;
if (of_device_is_available(port->parent) &&
of_device_compatible_match(port->parent,
rockchip_drm_match_preferred))
drm_of_component_match_add(dev, &match,
component_compare_of,
port->parent);
of_node_put(port);
}
for (i = 0; i < num_rockchip_sub_drivers; i++) {
struct platform_driver *drv = rockchip_sub_drivers[i];
struct device *p = NULL, *d;
do {
d = platform_find_device_by_driver(p, &drv->driver);
put_device(p);
p = d;
if (!d)
break;
device_link_add(dev, d, DL_FLAG_STATELESS);
component_match_add(dev, &match, component_compare_dev, d);
} while (true);
}
if (IS_ERR(match))
rockchip_drm_match_remove(dev);
return match ?: ERR_PTR(-ENODEV);
}
static const struct component_master_ops rockchip_drm_ops = {
.bind = rockchip_drm_bind,
.unbind = rockchip_drm_unbind,
};
static int rockchip_drm_platform_of_probe(struct device *dev)
{
struct device_node *np = dev->of_node;
struct device_node *port;
bool found = false;
int i;
if (!np)
return -ENODEV;
for (i = 0;; i++) {
port = of_parse_phandle(np, "ports", i);
if (!port)
break;
if (!of_device_is_available(port->parent)) {
of_node_put(port);
continue;
}
found = true;
of_node_put(port);
}
if (i == 0) {
DRM_DEV_ERROR(dev, "missing 'ports' property\n");
return -ENODEV;
}
if (!found) {
DRM_DEV_ERROR(dev,
"No available vop found for display-subsystem.\n");
return -ENODEV;
}
return 0;
}
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct component_match *match = NULL;
int ret;
ret = rockchip_drm_platform_of_probe(dev);
if (ret)
return ret;
match = rockchip_drm_match_add(dev);
if (IS_ERR(match))
return PTR_ERR(match);
ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
if (ret < 0) {
rockchip_drm_match_remove(dev);
return ret;
}
return 0;
}
static void rockchip_drm_platform_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &rockchip_drm_ops);
rockchip_drm_match_remove(&pdev->dev);
}
static void rockchip_drm_platform_shutdown(struct platform_device *pdev)
{
if (component_master_is_bound(&pdev->dev, &rockchip_drm_ops)) {
struct drm_device *drm = platform_get_drvdata(pdev);
drm_atomic_helper_shutdown(drm);
}
}
static const struct of_device_id rockchip_drm_dt_ids[] = {
{ .compatible = "rockchip,display-subsystem", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
static struct platform_driver rockchip_drm_platform_driver = {
.probe = rockchip_drm_platform_probe,
.remove = rockchip_drm_platform_remove,
.shutdown = rockchip_drm_platform_shutdown,
.driver = {
.name = "rockchip-drm",
.of_match_table = rockchip_drm_dt_ids,
.pm = &rockchip_drm_pm_ops,
},
};
#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
if (IS_ENABLED(cond) && \
!WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
}
static int __init rockchip_drm_init(void)
{
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
num_rockchip_sub_drivers = 0;
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
CONFIG_ROCKCHIP_LVDS);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
CONFIG_ROCKCHIP_ANALOGIX_DP);
ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP);
ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
CONFIG_ROCKCHIP_DW_HDMI);
ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
CONFIG_ROCKCHIP_DW_HDMI_QP);
ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
CONFIG_ROCKCHIP_DW_MIPI_DSI);
ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi2_rockchip_driver,
CONFIG_ROCKCHIP_DW_MIPI_DSI2);
ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver,
CONFIG_ROCKCHIP_RK3066_HDMI);
ret = platform_register_drivers(rockchip_sub_drivers,
num_rockchip_sub_drivers);
if (ret)
return ret;
ret = platform_driver_register(&rockchip_drm_platform_driver);
if (ret)
goto err_unreg_drivers;
return 0;
err_unreg_drivers:
platform_unregister_drivers(rockchip_sub_drivers,
num_rockchip_sub_drivers);
return ret;
}
static void __exit rockchip_drm_fini(void)
{
platform_driver_unregister(&rockchip_drm_platform_driver);
platform_unregister_drivers(rockchip_sub_drivers,
num_rockchip_sub_drivers);
}
module_init(rockchip_drm_init);
module_exit(rockchip_drm_fini);
MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*
* based on exynos_drm_drv.h
*/
#ifndef _ROCKCHIP_DRM_DRV_H
#define _ROCKCHIP_DRM_DRV_H
#include <drm/drm_atomic_helper.h>
#include <drm/drm_gem.h>
#include <linux/bits.h>
#include <linux/component.h>
#include <linux/i2c.h>
#include <linux/module.h>
#define ROCKCHIP_MAX_FB_BUFFER 3
#define ROCKCHIP_MAX_CONNECTOR 2
#define ROCKCHIP_MAX_CRTC 4
/*
* display output interface supported by rockchip lcdc
*/
#define ROCKCHIP_OUT_MODE_P888 0
#define ROCKCHIP_OUT_MODE_BT1120 0
#define ROCKCHIP_OUT_MODE_P666 1
#define ROCKCHIP_OUT_MODE_P565 2
#define ROCKCHIP_OUT_MODE_BT656 5
#define ROCKCHIP_OUT_MODE_S888 8
#define ROCKCHIP_OUT_MODE_S888_DUMMY 12
#define ROCKCHIP_OUT_MODE_YUV420 14
/* for use special outface */
#define ROCKCHIP_OUT_MODE_AAAA 15
/* output flags */
#define ROCKCHIP_OUTPUT_DSI_DUAL BIT(0)
struct drm_device;
struct drm_connector;
struct iommu_domain;
struct rockchip_crtc_state {
struct drm_crtc_state base;
int output_type;
int output_mode;
int output_bpc;
int output_flags;
bool enable_afbc;
bool yuv_overlay;
u32 bus_format;
u32 bus_flags;
int color_space;
};
#define to_rockchip_crtc_state(s) \
container_of(s, struct rockchip_crtc_state, base)
/*
* Rockchip drm private structure.
*
* @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
* @num_pipe: number of pipes for this device.
* @mm_lock: protect drm_mm on multi-threads.
*/
struct rockchip_drm_private {
struct iommu_domain *domain;
struct device *iommu_dev;
struct mutex mm_lock;
struct drm_mm mm;
};
struct rockchip_encoder {
int crtc_endpoint_id;
struct drm_encoder encoder;
};
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
struct device *dev);
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
struct device *dev);
void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
struct device *dev);
int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder,
struct device_node *np, int port, int reg);
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
extern struct platform_driver cdn_dp_driver;
extern struct platform_driver dw_dp_driver;
extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver;
extern struct platform_driver dw_mipi_dsi_rockchip_driver;
extern struct platform_driver dw_mipi_dsi2_rockchip_driver;
extern struct platform_driver inno_hdmi_driver;
extern struct platform_driver rockchip_dp_driver;
extern struct platform_driver rockchip_lvds_driver;
extern struct platform_driver vop_platform_driver;
extern struct platform_driver rk3066_hdmi_driver;
extern struct platform_driver vop2_platform_driver;
static inline struct rockchip_encoder *to_rockchip_encoder(struct drm_encoder *encoder)
{
return container_of(encoder, struct rockchip_encoder, encoder);
}
#endif /* _ROCKCHIP_DRM_DRV_H_ */
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#include <linux/kernel.h>
#include <drm/drm.h>
#include <drm/drm_atomic.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_probe_helper.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_fb.h"
#include "rockchip_drm_gem.h"
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
.destroy = drm_gem_fb_destroy,
.create_handle = drm_gem_fb_create_handle,
.dirty = drm_atomic_helper_dirtyfb,
};
static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers = {
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
};
static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_afbc_framebuffer *afbc_fb;
int ret;
afbc_fb = kzalloc_obj(*afbc_fb);
if (!afbc_fb)
return ERR_PTR(-ENOMEM);
ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base,
file, info, mode_cmd,
&rockchip_drm_fb_funcs);
if (ret) {
kfree(afbc_fb);
return ERR_PTR(ret);
}
if (drm_is_afbc(mode_cmd->modifier[0])) {
ret = drm_gem_fb_afbc_init(dev, info, mode_cmd, afbc_fb);
if (ret) {
drm_framebuffer_put(&afbc_fb->base);
return ERR_PTR(ret);
}
}
return &afbc_fb->base;
}
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
void rockchip_drm_mode_config_init(struct drm_device *dev)
{
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
/*
* set max width and height as default value(4096x4096).
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
dev->mode_config.max_width = 4096;
dev->mode_config.max_height = 4096;
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
dev->mode_config.normalize_zpos = true;
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#ifndef _ROCKCHIP_DRM_FB_H
#define _ROCKCHIP_DRM_FB_H
void rockchip_drm_mode_config_init(struct drm_device *dev);
#endif /* _ROCKCHIP_DRM_FB_H */
@@ -0,0 +1,552 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#include <linux/dma-buf.h>
#include <linux/iommu.h>
#include <linux/vmalloc.h>
#include <drm/drm.h>
#include <drm/drm_dumb_buffers.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_prime.h>
#include <drm/drm_print.h>
#include <drm/drm_vma_manager.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_gem.h"
static int rockchip_gem_iommu_map(struct rockchip_gem_object *rk_obj)
{
struct drm_device *drm = rk_obj->base.dev;
struct rockchip_drm_private *private = drm->dev_private;
int prot = IOMMU_READ | IOMMU_WRITE;
ssize_t ret;
mutex_lock(&private->mm_lock);
ret = drm_mm_insert_node_generic(&private->mm, &rk_obj->mm,
rk_obj->base.size, PAGE_SIZE,
0, 0);
mutex_unlock(&private->mm_lock);
if (ret < 0) {
DRM_ERROR("out of I/O virtual memory: %zd\n", ret);
return ret;
}
rk_obj->dma_addr = rk_obj->mm.start;
ret = iommu_map_sgtable(private->domain, rk_obj->dma_addr, rk_obj->sgt,
prot);
if (ret < (ssize_t)rk_obj->base.size) {
DRM_ERROR("failed to map buffer: size=%zd request_size=%zd\n",
ret, rk_obj->base.size);
ret = -ENOMEM;
goto err_remove_node;
}
rk_obj->size = ret;
return 0;
err_remove_node:
mutex_lock(&private->mm_lock);
drm_mm_remove_node(&rk_obj->mm);
mutex_unlock(&private->mm_lock);
return ret;
}
static int rockchip_gem_iommu_unmap(struct rockchip_gem_object *rk_obj)
{
struct drm_device *drm = rk_obj->base.dev;
struct rockchip_drm_private *private = drm->dev_private;
iommu_unmap(private->domain, rk_obj->dma_addr, rk_obj->size);
mutex_lock(&private->mm_lock);
drm_mm_remove_node(&rk_obj->mm);
mutex_unlock(&private->mm_lock);
return 0;
}
static int rockchip_gem_get_pages(struct rockchip_gem_object *rk_obj)
{
struct drm_device *drm = rk_obj->base.dev;
int ret, i;
struct scatterlist *s;
rk_obj->pages = drm_gem_get_pages(&rk_obj->base);
if (IS_ERR(rk_obj->pages))
return PTR_ERR(rk_obj->pages);
rk_obj->num_pages = rk_obj->base.size >> PAGE_SHIFT;
rk_obj->sgt = drm_prime_pages_to_sg(rk_obj->base.dev,
rk_obj->pages, rk_obj->num_pages);
if (IS_ERR(rk_obj->sgt)) {
ret = PTR_ERR(rk_obj->sgt);
goto err_put_pages;
}
/*
* Fake up the SG table so that dma_sync_sg_for_device() can be used
* to flush the pages associated with it.
*
* TODO: Replace this by drm_clflush_sg() once it can be implemented
* without relying on symbols that are not exported.
*/
for_each_sgtable_sg(rk_obj->sgt, s, i)
sg_dma_address(s) = sg_phys(s);
dma_sync_sgtable_for_device(drm->dev, rk_obj->sgt, DMA_TO_DEVICE);
return 0;
err_put_pages:
drm_gem_put_pages(&rk_obj->base, rk_obj->pages, false, false);
return ret;
}
static void rockchip_gem_put_pages(struct rockchip_gem_object *rk_obj)
{
sg_free_table(rk_obj->sgt);
kfree(rk_obj->sgt);
drm_gem_put_pages(&rk_obj->base, rk_obj->pages, true, true);
}
static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
int ret;
ret = rockchip_gem_get_pages(rk_obj);
if (ret < 0)
return ret;
ret = rockchip_gem_iommu_map(rk_obj);
if (ret < 0)
goto err_free;
if (alloc_kmap) {
rk_obj->kvaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!rk_obj->kvaddr) {
DRM_ERROR("failed to vmap() buffer\n");
ret = -ENOMEM;
goto err_unmap;
}
}
return 0;
err_unmap:
rockchip_gem_iommu_unmap(rk_obj);
err_free:
rockchip_gem_put_pages(rk_obj);
return ret;
}
static int rockchip_gem_alloc_dma(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
struct drm_gem_object *obj = &rk_obj->base;
struct drm_device *drm = obj->dev;
rk_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE;
if (!alloc_kmap)
rk_obj->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
&rk_obj->dma_addr, GFP_KERNEL,
rk_obj->dma_attrs);
if (!rk_obj->kvaddr) {
DRM_ERROR("failed to allocate %zu byte dma buffer", obj->size);
return -ENOMEM;
}
return 0;
}
static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
struct drm_gem_object *obj = &rk_obj->base;
struct drm_device *drm = obj->dev;
struct rockchip_drm_private *private = drm->dev_private;
if (private->domain)
return rockchip_gem_alloc_iommu(rk_obj, alloc_kmap);
else
return rockchip_gem_alloc_dma(rk_obj, alloc_kmap);
}
static void rockchip_gem_free_iommu(struct rockchip_gem_object *rk_obj)
{
vunmap(rk_obj->kvaddr);
rockchip_gem_iommu_unmap(rk_obj);
rockchip_gem_put_pages(rk_obj);
}
static void rockchip_gem_free_dma(struct rockchip_gem_object *rk_obj)
{
struct drm_gem_object *obj = &rk_obj->base;
struct drm_device *drm = obj->dev;
dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
rk_obj->dma_attrs);
}
static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
{
if (rk_obj->pages)
rockchip_gem_free_iommu(rk_obj);
else
rockchip_gem_free_dma(rk_obj);
}
static int rockchip_drm_gem_object_mmap_iommu(struct drm_gem_object *obj,
struct vm_area_struct *vma)
{
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
unsigned int count = obj->size >> PAGE_SHIFT;
unsigned long user_count = vma_pages(vma);
if (user_count == 0)
return -ENXIO;
return vm_map_pages(vma, rk_obj->pages, count);
}
static int rockchip_drm_gem_object_mmap_dma(struct drm_gem_object *obj,
struct vm_area_struct *vma)
{
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
struct drm_device *drm = obj->dev;
return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
obj->size, rk_obj->dma_attrs);
}
static int rockchip_drm_gem_object_mmap(struct drm_gem_object *obj,
struct vm_area_struct *vma)
{
int ret;
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
/*
* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 and map the
* whole buffer from the start.
*/
vma->vm_pgoff = 0;
/*
* We allocated a struct page table for rk_obj, so clear
* VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap().
*/
vm_flags_mod(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP, VM_PFNMAP);
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
if (rk_obj->pages)
ret = rockchip_drm_gem_object_mmap_iommu(obj, vma);
else
ret = rockchip_drm_gem_object_mmap_dma(obj, vma);
return ret;
}
static void rockchip_gem_release_object(struct rockchip_gem_object *rk_obj)
{
drm_gem_object_release(&rk_obj->base);
kfree(rk_obj);
}
static const struct drm_gem_object_funcs rockchip_gem_object_funcs = {
.free = rockchip_gem_free_object,
.get_sg_table = rockchip_gem_prime_get_sg_table,
.vmap = rockchip_gem_prime_vmap,
.vunmap = rockchip_gem_prime_vunmap,
.mmap = rockchip_drm_gem_object_mmap,
.vm_ops = &drm_gem_dma_vm_ops,
};
static struct rockchip_gem_object *
rockchip_gem_alloc_object(struct drm_device *drm, unsigned int size)
{
struct rockchip_gem_object *rk_obj;
struct drm_gem_object *obj;
size = round_up(size, PAGE_SIZE);
rk_obj = kzalloc_obj(*rk_obj);
if (!rk_obj)
return ERR_PTR(-ENOMEM);
obj = &rk_obj->base;
obj->funcs = &rockchip_gem_object_funcs;
drm_gem_object_init(drm, obj, size);
return rk_obj;
}
struct rockchip_gem_object *
rockchip_gem_create_object(struct drm_device *drm, unsigned int size,
bool alloc_kmap)
{
struct rockchip_gem_object *rk_obj;
int ret;
rk_obj = rockchip_gem_alloc_object(drm, size);
if (IS_ERR(rk_obj))
return rk_obj;
ret = rockchip_gem_alloc_buf(rk_obj, alloc_kmap);
if (ret)
goto err_free_rk_obj;
return rk_obj;
err_free_rk_obj:
rockchip_gem_release_object(rk_obj);
return ERR_PTR(ret);
}
/*
* rockchip_gem_free_object - (struct drm_gem_object_funcs)->free
* callback function
*/
void rockchip_gem_free_object(struct drm_gem_object *obj)
{
struct drm_device *drm = obj->dev;
struct rockchip_drm_private *private = drm->dev_private;
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
if (drm_gem_is_imported(obj)) {
if (private->domain) {
rockchip_gem_iommu_unmap(rk_obj);
} else {
dma_unmap_sgtable(drm->dev, rk_obj->sgt,
DMA_BIDIRECTIONAL, 0);
}
drm_prime_gem_destroy(obj, rk_obj->sgt);
} else {
rockchip_gem_free_buf(rk_obj);
}
rockchip_gem_release_object(rk_obj);
}
/*
* rockchip_gem_create_with_handle - allocate an object with the given
* size and create a gem handle on it
*
* returns a struct rockchip_gem_object* on success or ERR_PTR values
* on failure.
*/
static struct rockchip_gem_object *
rockchip_gem_create_with_handle(struct drm_file *file_priv,
struct drm_device *drm, unsigned int size,
unsigned int *handle)
{
struct rockchip_gem_object *rk_obj;
struct drm_gem_object *obj;
bool is_framebuffer;
int ret;
is_framebuffer = drm->fb_helper && file_priv == drm->fb_helper->client.file;
rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer);
if (IS_ERR(rk_obj))
return ERR_CAST(rk_obj);
obj = &rk_obj->base;
/*
* allocate a id of idr table where the obj is registered
* and handle has the id what user can see.
*/
ret = drm_gem_handle_create(file_priv, obj, handle);
if (ret)
goto err_handle_create;
/* drop reference from allocate - handle holds it now. */
drm_gem_object_put(obj);
return rk_obj;
err_handle_create:
rockchip_gem_free_object(obj);
return ERR_PTR(ret);
}
/*
* rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
* function
*
* This aligns the pitch and size arguments to the minimum required. wrap
* this into your own function if you need bigger alignment.
*/
int rockchip_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct rockchip_gem_object *rk_obj;
int ret;
/* 64-byte alignment required by Mali */
ret = drm_mode_size_dumb(dev, args, SZ_64, 0);
if (ret)
return ret;
rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
&args->handle);
return PTR_ERR_OR_ZERO(rk_obj);
}
/*
* Allocate a sg_table for this GEM object.
* Note: Both the table's contents, and the sg_table itself must be freed by
* the caller.
* Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
*/
struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
{
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
struct drm_device *drm = obj->dev;
struct sg_table *sgt;
int ret;
if (rk_obj->pages)
return drm_prime_pages_to_sg(obj->dev, rk_obj->pages, rk_obj->num_pages);
sgt = kzalloc_obj(*sgt);
if (!sgt)
return ERR_PTR(-ENOMEM);
ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
rk_obj->dma_addr, obj->size,
rk_obj->dma_attrs);
if (ret) {
DRM_ERROR("failed to allocate sgt, %d\n", ret);
kfree(sgt);
return ERR_PTR(ret);
}
return sgt;
}
static int
rockchip_gem_iommu_map_sg(struct drm_device *drm,
struct dma_buf_attachment *attach,
struct sg_table *sg,
struct rockchip_gem_object *rk_obj)
{
rk_obj->sgt = sg;
return rockchip_gem_iommu_map(rk_obj);
}
static int
rockchip_gem_dma_map_sg(struct drm_device *drm,
struct dma_buf_attachment *attach,
struct sg_table *sg,
struct rockchip_gem_object *rk_obj)
{
int err = dma_map_sgtable(drm->dev, sg, DMA_BIDIRECTIONAL, 0);
if (err)
return err;
if (drm_prime_get_contiguous_size(sg) < attach->dmabuf->size) {
DRM_ERROR("failed to map sg_table to contiguous linear address.\n");
dma_unmap_sgtable(drm->dev, sg, DMA_BIDIRECTIONAL, 0);
return -EINVAL;
}
rk_obj->dma_addr = sg_dma_address(sg->sgl);
rk_obj->sgt = sg;
return 0;
}
struct drm_gem_object *
rockchip_gem_prime_import_sg_table(struct drm_device *drm,
struct dma_buf_attachment *attach,
struct sg_table *sg)
{
struct rockchip_drm_private *private = drm->dev_private;
struct rockchip_gem_object *rk_obj;
int ret;
rk_obj = rockchip_gem_alloc_object(drm, attach->dmabuf->size);
if (IS_ERR(rk_obj))
return ERR_CAST(rk_obj);
if (private->domain)
ret = rockchip_gem_iommu_map_sg(drm, attach, sg, rk_obj);
else
ret = rockchip_gem_dma_map_sg(drm, attach, sg, rk_obj);
if (ret < 0) {
DRM_ERROR("failed to import sg table: %d\n", ret);
goto err_free_rk_obj;
}
return &rk_obj->base;
err_free_rk_obj:
rockchip_gem_release_object(rk_obj);
return ERR_PTR(ret);
}
int rockchip_gem_prime_vmap(struct drm_gem_object *obj, struct iosys_map *map)
{
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
if (rk_obj->pages) {
void *vaddr;
if (rk_obj->kvaddr)
vaddr = rk_obj->kvaddr;
else
vaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!vaddr)
return -ENOMEM;
iosys_map_set_vaddr(map, vaddr);
return 0;
}
if (rk_obj->dma_attrs & DMA_ATTR_NO_KERNEL_MAPPING)
return -ENOMEM;
iosys_map_set_vaddr(map, rk_obj->kvaddr);
return 0;
}
void rockchip_gem_prime_vunmap(struct drm_gem_object *obj,
struct iosys_map *map)
{
struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
if (rk_obj->pages) {
if (map->vaddr != rk_obj->kvaddr)
vunmap(map->vaddr);
return;
}
/* Nothing to do if allocated by DMA mapping API. */
}
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#ifndef _ROCKCHIP_DRM_GEM_H
#define _ROCKCHIP_DRM_GEM_H
#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
struct rockchip_gem_object {
struct drm_gem_object base;
unsigned int flags;
void *kvaddr;
dma_addr_t dma_addr;
/* Used when IOMMU is disabled */
unsigned long dma_attrs;
/* Used when IOMMU is enabled */
struct drm_mm_node mm;
unsigned long num_pages;
struct page **pages;
struct sg_table *sgt;
size_t size;
};
struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
struct drm_gem_object *
rockchip_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sg);
int rockchip_gem_prime_vmap(struct drm_gem_object *obj, struct iosys_map *map);
void rockchip_gem_prime_vunmap(struct drm_gem_object *obj,
struct iosys_map *map);
struct rockchip_gem_object *
rockchip_gem_create_object(struct drm_device *drm, unsigned int size,
bool alloc_kmap);
void rockchip_gem_free_object(struct drm_gem_object *obj);
int rockchip_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
#endif /* _ROCKCHIP_DRM_GEM_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,426 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#ifndef _ROCKCHIP_DRM_VOP_H
#define _ROCKCHIP_DRM_VOP_H
/*
* major: IP major version, used for IP structure
* minor: big feature change under same structure
*/
#define VOP_VERSION(major, minor) ((major) << 8 | (minor))
#define VOP_MAJOR(version) ((version) >> 8)
#define VOP_MINOR(version) ((version) & 0xff)
#define NUM_YUV2YUV_COEFFICIENTS 12
/* AFBC supports a number of configurable modes. Relevant to us is block size
* (16x16 or 32x8), storage modifiers (SPARSE, SPLIT), and the YUV-like
* colourspace transform (YTR). 16x16 SPARSE mode is always used. SPLIT mode
* could be enabled via the hreg_block_split register, but is not currently
* handled. The colourspace transform is implicitly always assumed by the
* decoder, so consumers must use this transform as well.
*
* Failure to match modifiers will cause errors displaying AFBC buffers
* produced by conformant AFBC producers, including Mesa.
*/
#define ROCKCHIP_AFBC_MOD \
DRM_FORMAT_MOD_ARM_AFBC( \
AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | AFBC_FORMAT_MOD_SPARSE \
| AFBC_FORMAT_MOD_YTR \
)
enum vop_data_format {
VOP_FMT_ARGB8888 = 0,
VOP_FMT_RGB888,
VOP_FMT_RGB565,
VOP_FMT_YUV420SP = 4,
VOP_FMT_YUV422SP,
VOP_FMT_YUV444SP,
};
struct vop_rect {
int width;
int height;
};
struct vop_reg {
uint32_t mask;
uint16_t offset;
uint8_t shift;
bool write_mask;
bool relaxed;
};
struct vop_afbc {
struct vop_reg enable;
struct vop_reg win_sel;
struct vop_reg format;
struct vop_reg rb_swap;
struct vop_reg uv_swap;
struct vop_reg auto_gating_en;
struct vop_reg block_split_en;
struct vop_reg pic_vir_width;
struct vop_reg tile_num;
struct vop_reg hreg_block_split;
struct vop_reg pic_offset;
struct vop_reg pic_size;
struct vop_reg dsp_offset;
struct vop_reg transform_offset;
struct vop_reg hdr_ptr;
struct vop_reg half_block_en;
struct vop_reg xmirror;
struct vop_reg ymirror;
struct vop_reg rotate_270;
struct vop_reg rotate_90;
struct vop_reg rstn;
};
struct vop_modeset {
struct vop_reg htotal_pw;
struct vop_reg hact_st_end;
struct vop_reg hpost_st_end;
struct vop_reg vtotal_pw;
struct vop_reg vact_st_end;
struct vop_reg vpost_st_end;
};
struct vop_output {
struct vop_reg pin_pol;
struct vop_reg dp_pin_pol;
struct vop_reg dp_dclk_pol;
struct vop_reg edp_pin_pol;
struct vop_reg edp_dclk_pol;
struct vop_reg hdmi_pin_pol;
struct vop_reg hdmi_dclk_pol;
struct vop_reg mipi_pin_pol;
struct vop_reg mipi_dclk_pol;
struct vop_reg rgb_pin_pol;
struct vop_reg rgb_dclk_pol;
struct vop_reg dp_en;
struct vop_reg edp_en;
struct vop_reg hdmi_en;
struct vop_reg mipi_en;
struct vop_reg mipi_dual_channel_en;
struct vop_reg rgb_en;
};
struct vop_common {
struct vop_reg cfg_done;
struct vop_reg dsp_blank;
struct vop_reg data_blank;
struct vop_reg pre_dither_down;
struct vop_reg dither_down_sel;
struct vop_reg dither_down_mode;
struct vop_reg dither_down_en;
struct vop_reg dither_up;
struct vop_reg dsp_lut_en;
struct vop_reg update_gamma_lut;
struct vop_reg lut_buffer_index;
struct vop_reg gate_en;
struct vop_reg mmu_en;
struct vop_reg dma_stop;
struct vop_reg out_mode;
struct vop_reg standby;
};
struct vop_misc {
struct vop_reg global_regdone_en;
};
struct vop_intr {
const int *intrs;
uint32_t nintrs;
struct vop_reg line_flag_num[2];
struct vop_reg enable;
struct vop_reg clear;
struct vop_reg status;
};
struct vop_scl_extension {
struct vop_reg cbcr_vsd_mode;
struct vop_reg cbcr_vsu_mode;
struct vop_reg cbcr_hsd_mode;
struct vop_reg cbcr_ver_scl_mode;
struct vop_reg cbcr_hor_scl_mode;
struct vop_reg yrgb_vsd_mode;
struct vop_reg yrgb_vsu_mode;
struct vop_reg yrgb_hsd_mode;
struct vop_reg yrgb_ver_scl_mode;
struct vop_reg yrgb_hor_scl_mode;
struct vop_reg line_load_mode;
struct vop_reg cbcr_axi_gather_num;
struct vop_reg yrgb_axi_gather_num;
struct vop_reg vsd_cbcr_gt2;
struct vop_reg vsd_cbcr_gt4;
struct vop_reg vsd_yrgb_gt2;
struct vop_reg vsd_yrgb_gt4;
struct vop_reg bic_coe_sel;
struct vop_reg cbcr_axi_gather_en;
struct vop_reg yrgb_axi_gather_en;
struct vop_reg lb_mode;
};
struct vop_scl_regs {
const struct vop_scl_extension *ext;
struct vop_reg scale_yrgb_x;
struct vop_reg scale_yrgb_y;
struct vop_reg scale_cbcr_x;
struct vop_reg scale_cbcr_y;
};
struct vop_yuv2yuv_phy {
struct vop_reg y2r_coefficients[NUM_YUV2YUV_COEFFICIENTS];
};
struct vop_win_phy {
const struct vop_scl_regs *scl;
const uint32_t *data_formats;
uint32_t nformats;
const uint64_t *format_modifiers;
struct vop_reg enable;
struct vop_reg gate;
struct vop_reg format;
struct vop_reg fmt_10;
struct vop_reg rb_swap;
struct vop_reg uv_swap;
struct vop_reg act_info;
struct vop_reg dsp_info;
struct vop_reg dsp_st;
struct vop_reg yrgb_mst;
struct vop_reg uv_mst;
struct vop_reg yrgb_vir;
struct vop_reg uv_vir;
struct vop_reg y_mir_en;
struct vop_reg x_mir_en;
struct vop_reg dst_alpha_ctl;
struct vop_reg src_alpha_ctl;
struct vop_reg alpha_pre_mul;
struct vop_reg alpha_mode;
struct vop_reg alpha_en;
struct vop_reg channel;
};
struct vop_win_yuv2yuv_data {
uint32_t base;
const struct vop_yuv2yuv_phy *phy;
struct vop_reg y2r_en;
};
struct vop_win_data {
uint32_t base;
const struct vop_win_phy *phy;
enum drm_plane_type type;
};
struct vop_data {
uint32_t version;
const struct vop_intr *intr;
const struct vop_common *common;
const struct vop_misc *misc;
const struct vop_modeset *modeset;
const struct vop_output *output;
const struct vop_afbc *afbc;
const struct vop_win_yuv2yuv_data *win_yuv2yuv;
const struct vop_win_data *win;
unsigned int win_size;
unsigned int lut_size;
struct vop_rect max_output;
#define VOP_FEATURE_OUTPUT_RGB10 BIT(0)
#define VOP_FEATURE_INTERNAL_RGB BIT(1)
u64 feature;
};
/* interrupt define */
#define DSP_HOLD_VALID_INTR (1 << 0)
#define FS_INTR (1 << 1)
#define LINE_FLAG_INTR (1 << 2)
#define BUS_ERROR_INTR (1 << 3)
#define INTR_MASK (DSP_HOLD_VALID_INTR | FS_INTR | \
LINE_FLAG_INTR | BUS_ERROR_INTR)
#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
#define FS_INTR_EN(x) ((x) << 5)
#define LINE_FLAG_INTR_EN(x) ((x) << 6)
#define BUS_ERROR_INTR_EN(x) ((x) << 7)
#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
#define FS_INTR_MASK (1 << 5)
#define LINE_FLAG_INTR_MASK (1 << 6)
#define BUS_ERROR_INTR_MASK (1 << 7)
#define INTR_CLR_SHIFT 8
#define DSP_HOLD_VALID_INTR_CLR (1 << (INTR_CLR_SHIFT + 0))
#define FS_INTR_CLR (1 << (INTR_CLR_SHIFT + 1))
#define LINE_FLAG_INTR_CLR (1 << (INTR_CLR_SHIFT + 2))
#define BUS_ERROR_INTR_CLR (1 << (INTR_CLR_SHIFT + 3))
#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
#define DSP_LINE_NUM_MASK (0x1fff << 12)
/* src alpha ctrl define */
#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
/* dst alpha ctrl define */
#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
enum alpha_mode {
ALPHA_STRAIGHT,
ALPHA_INVERSE,
};
enum global_blend_mode {
ALPHA_GLOBAL,
ALPHA_PER_PIX,
ALPHA_PER_PIX_GLOBAL,
};
enum alpha_cal_mode {
ALPHA_SATURATION,
ALPHA_NO_SATURATION,
};
enum color_mode {
ALPHA_SRC_PRE_MUL,
ALPHA_SRC_NO_PRE_MUL,
};
enum factor_mode {
ALPHA_ZERO,
ALPHA_ONE,
ALPHA_SRC,
ALPHA_SRC_INVERSE,
ALPHA_SRC_GLOBAL,
};
enum scale_mode {
SCALE_NONE = 0x0,
SCALE_UP = 0x1,
SCALE_DOWN = 0x2
};
enum lb_mode {
LB_YUV_3840X5 = 0x0,
LB_YUV_2560X8 = 0x1,
LB_RGB_3840X2 = 0x2,
LB_RGB_2560X4 = 0x3,
LB_RGB_1920X5 = 0x4,
LB_RGB_1280X8 = 0x5
};
enum sacle_up_mode {
SCALE_UP_BIL = 0x0,
SCALE_UP_BIC = 0x1
};
enum scale_down_mode {
SCALE_DOWN_BIL = 0x0,
SCALE_DOWN_AVG = 0x1
};
enum dither_down_mode {
RGB888_TO_RGB565 = 0x0,
RGB888_TO_RGB666 = 0x1
};
enum dither_down_mode_sel {
DITHER_DOWN_ALLEGRO = 0x0,
DITHER_DOWN_FRC = 0x1
};
enum vop_pol {
HSYNC_POSITIVE = 0,
VSYNC_POSITIVE = 1,
DEN_NEGATIVE = 2
};
#define FRAC_16_16(mult, div) (((mult) << 16) / (div))
#define SCL_FT_DEFAULT_FIXPOINT_SHIFT 12
#define SCL_MAX_VSKIPLINES 4
#define MIN_SCL_FT_AFTER_VSKIP 1
static inline uint16_t scl_cal_scale(int src, int dst, int shift)
{
return ((src * 2 - 3) << (shift - 1)) / (dst - 1);
}
static inline uint16_t scl_cal_scale2(int src, int dst)
{
return ((src - 1) << 12) / (dst - 1);
}
#define GET_SCL_FT_BILI_DN(src, dst) scl_cal_scale(src, dst, 12)
#define GET_SCL_FT_BILI_UP(src, dst) scl_cal_scale(src, dst, 16)
#define GET_SCL_FT_BIC(src, dst) scl_cal_scale(src, dst, 16)
static inline uint16_t scl_get_bili_dn_vskip(int src_h, int dst_h,
int vskiplines)
{
int act_height;
act_height = DIV_ROUND_UP(src_h, vskiplines);
if (act_height == dst_h)
return GET_SCL_FT_BILI_DN(src_h, dst_h) / vskiplines;
return GET_SCL_FT_BILI_DN(act_height, dst_h);
}
static inline enum scale_mode scl_get_scl_mode(int src, int dst)
{
if (src < dst)
return SCALE_UP;
else if (src > dst)
return SCALE_DOWN;
return SCALE_NONE;
}
static inline int scl_get_vskiplines(uint32_t srch, uint32_t dsth)
{
uint32_t vskiplines;
for (vskiplines = SCL_MAX_VSKIPLINES; vskiplines > 1; vskiplines /= 2)
if (srch >= vskiplines * dsth * MIN_SCL_FT_AFTER_VSKIP)
break;
return vskiplines;
}
static inline int scl_vop_cal_lb_mode(int width, bool is_yuv)
{
int lb_mode;
if (is_yuv) {
if (width > 1280)
lb_mode = LB_YUV_3840X5;
else
lb_mode = LB_YUV_2560X8;
} else {
if (width > 2560)
lb_mode = LB_RGB_3840X2;
else if (width > 1920)
lb_mode = LB_RGB_2560X4;
else
lb_mode = LB_RGB_1920X5;
}
return lb_mode;
}
extern const struct component_ops vop_component_ops;
#endif /* _ROCKCHIP_DRM_VOP_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,870 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:Mark Yao <mark.yao@rock-chips.com>
*/
#ifndef _ROCKCHIP_DRM_VOP2_H
#define _ROCKCHIP_DRM_VOP2_H
#include <linux/regmap.h>
#include <drm/drm_modes.h>
#include <dt-bindings/soc/rockchip,vop2.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_vop.h"
#define VOP2_VERSION(major, minor, build) ((major) << 24 | (minor) << 16 | (build))
/* The VOP version of new SoC is bigger than the old */
#define VOP_VERSION_RK3568 VOP2_VERSION(0x40, 0x15, 0x8023)
#define VOP_VERSION_RK3588 VOP2_VERSION(0x40, 0x17, 0x6786)
#define VOP_VERSION_RK3528 VOP2_VERSION(0x50, 0x17, 0x1263)
#define VOP_VERSION_RK3562 VOP2_VERSION(0x50, 0x17, 0x4350)
#define VOP_VERSION_RK3576 VOP2_VERSION(0x50, 0x19, 0x9765)
#define VOP2_VP_FEATURE_OUTPUT_10BIT BIT(0)
#define VOP2_FEATURE_HAS_SYS_GRF BIT(0)
#define VOP2_FEATURE_HAS_VO0_GRF BIT(1)
#define VOP2_FEATURE_HAS_VO1_GRF BIT(2)
#define VOP2_FEATURE_HAS_VOP_GRF BIT(3)
#define VOP2_FEATURE_HAS_SYS_PMU BIT(4)
#define WIN_FEATURE_AFBDC BIT(0)
#define WIN_FEATURE_CLUSTER BIT(1)
/*
* the delay number of a window in different mode.
*/
enum win_dly_mode {
VOP2_DLY_MODE_DEFAULT, /**< default mode */
VOP2_DLY_MODE_HISO_S, /** HDR in SDR out mode, as a SDR window */
VOP2_DLY_MODE_HIHO_H, /** HDR in HDR out mode, as a HDR window */
VOP2_DLY_MODE_MAX,
};
enum vop2_dly_module {
VOP2_DLY_WIN, /** Win delay cycle for this VP */
VOP2_DLY_LAYER_MIX, /** Layer Mix delay cycle for this VP */
VOP2_DLY_HDR_MIX, /** HDR delay cycle for this VP */
VOP2_DLY_MAX,
};
enum vop2_scale_up_mode {
VOP2_SCALE_UP_NRST_NBOR,
VOP2_SCALE_UP_BIL,
VOP2_SCALE_UP_BIC,
};
enum vop2_scale_down_mode {
VOP2_SCALE_DOWN_NRST_NBOR,
VOP2_SCALE_DOWN_BIL,
VOP2_SCALE_DOWN_AVG,
};
/*
* vop2 internal power domain id,
* should be all none zero, 0 will be treat as invalid;
*/
#define VOP2_PD_CLUSTER0 BIT(0)
#define VOP2_PD_CLUSTER1 BIT(1)
#define VOP2_PD_CLUSTER2 BIT(2)
#define VOP2_PD_CLUSTER3 BIT(3)
#define VOP2_PD_DSC_8K BIT(5)
#define VOP2_PD_DSC_4K BIT(6)
#define VOP2_PD_ESMART BIT(7)
#define vop2_output_if_is_hdmi(x) ((x) == ROCKCHIP_VOP2_EP_HDMI0 || \
(x) == ROCKCHIP_VOP2_EP_HDMI1)
#define vop2_output_if_is_dp(x) ((x) == ROCKCHIP_VOP2_EP_DP0 || \
(x) == ROCKCHIP_VOP2_EP_DP1)
#define vop2_output_if_is_edp(x) ((x) == ROCKCHIP_VOP2_EP_EDP0 || \
(x) == ROCKCHIP_VOP2_EP_EDP1)
#define vop2_output_if_is_mipi(x) ((x) == ROCKCHIP_VOP2_EP_MIPI0 || \
(x) == ROCKCHIP_VOP2_EP_MIPI1)
#define vop2_output_if_is_lvds(x) ((x) == ROCKCHIP_VOP2_EP_LVDS0 || \
(x) == ROCKCHIP_VOP2_EP_LVDS1)
#define vop2_output_if_is_dpi(x) ((x) == ROCKCHIP_VOP2_EP_RGB0)
enum vop2_win_regs {
VOP2_WIN_ENABLE,
VOP2_WIN_FORMAT,
VOP2_WIN_CSC_MODE,
VOP2_WIN_XMIRROR,
VOP2_WIN_YMIRROR,
VOP2_WIN_RB_SWAP,
VOP2_WIN_UV_SWAP,
VOP2_WIN_ACT_INFO,
VOP2_WIN_DSP_INFO,
VOP2_WIN_DSP_ST,
VOP2_WIN_YRGB_MST,
VOP2_WIN_UV_MST,
VOP2_WIN_YRGB_VIR,
VOP2_WIN_UV_VIR,
VOP2_WIN_YUV_CLIP,
VOP2_WIN_Y2R_EN,
VOP2_WIN_R2Y_EN,
VOP2_WIN_COLOR_KEY,
VOP2_WIN_COLOR_KEY_EN,
VOP2_WIN_DITHER_UP,
VOP2_WIN_AXI_BUS_ID,
VOP2_WIN_AXI_YRGB_R_ID,
VOP2_WIN_AXI_UV_R_ID,
/* scale regs */
VOP2_WIN_SCALE_YRGB_X,
VOP2_WIN_SCALE_YRGB_Y,
VOP2_WIN_SCALE_CBCR_X,
VOP2_WIN_SCALE_CBCR_Y,
VOP2_WIN_YRGB_HOR_SCL_MODE,
VOP2_WIN_YRGB_HSCL_FILTER_MODE,
VOP2_WIN_YRGB_VER_SCL_MODE,
VOP2_WIN_YRGB_VSCL_FILTER_MODE,
VOP2_WIN_CBCR_VER_SCL_MODE,
VOP2_WIN_CBCR_HSCL_FILTER_MODE,
VOP2_WIN_CBCR_HOR_SCL_MODE,
VOP2_WIN_CBCR_VSCL_FILTER_MODE,
VOP2_WIN_VSD_CBCR_GT2,
VOP2_WIN_VSD_CBCR_GT4,
VOP2_WIN_VSD_YRGB_GT2,
VOP2_WIN_VSD_YRGB_GT4,
VOP2_WIN_BIC_COE_SEL,
/* cluster regs */
VOP2_WIN_CLUSTER_ENABLE,
VOP2_WIN_AFBC_ENABLE,
VOP2_WIN_CLUSTER_LB_MODE,
/* afbc regs */
VOP2_WIN_AFBC_FORMAT,
VOP2_WIN_AFBC_RB_SWAP,
VOP2_WIN_AFBC_UV_SWAP,
VOP2_WIN_AFBC_AUTO_GATING_EN,
VOP2_WIN_AFBC_BLOCK_SPLIT_EN,
VOP2_WIN_AFBC_PLD_OFFSET_EN,
VOP2_WIN_AFBC_PIC_VIR_WIDTH,
VOP2_WIN_AFBC_TILE_NUM,
VOP2_WIN_AFBC_PIC_OFFSET,
VOP2_WIN_AFBC_PIC_SIZE,
VOP2_WIN_AFBC_DSP_OFFSET,
VOP2_WIN_AFBC_PLD_OFFSET,
VOP2_WIN_TRANSFORM_OFFSET,
VOP2_WIN_AFBC_HDR_PTR,
VOP2_WIN_AFBC_HALF_BLOCK_EN,
VOP2_WIN_AFBC_ROTATE_270,
VOP2_WIN_AFBC_ROTATE_90,
VOP2_WIN_VP_SEL,
VOP2_WIN_DLY_NUM,
VOP2_WIN_MAX_REG,
};
struct vop2_regs_dump {
const char *name;
u32 base;
u32 size;
u32 en_reg;
u32 en_val;
u32 en_mask;
};
struct vop2_win_data {
const char *name;
unsigned int phys_id;
u32 base;
u32 possible_vp_mask;
enum drm_plane_type type;
u32 nformats;
const u32 *formats;
const uint64_t *format_modifiers;
const unsigned int supported_rotations;
/**
* @layer_sel_id: defined by register OVERLAY_LAYER_SEL or PORTn_LAYER_SEL
*/
unsigned int layer_sel_id[ROCKCHIP_MAX_CRTC];
uint64_t feature;
uint8_t axi_bus_id;
uint8_t axi_yrgb_r_id;
uint8_t axi_uv_r_id;
unsigned int max_upscale_factor;
unsigned int max_downscale_factor;
const u8 dly[VOP2_DLY_MODE_MAX];
};
struct vop2_win {
struct vop2 *vop2;
struct drm_plane base;
const struct vop2_win_data *data;
struct regmap_field *reg[VOP2_WIN_MAX_REG];
/**
* @win_id: graphic window id, a cluster may be split into two
* graphics windows.
*/
u8 win_id;
u8 delay;
u32 offset;
enum drm_plane_type type;
};
struct vop2_video_port_data {
unsigned int id;
u32 feature;
u16 gamma_lut_len;
u16 cubic_lut_len;
struct vop_rect max_output;
const u8 pre_scan_max_dly[4];
unsigned int offset;
/**
* @pixel_rate: pixel per cycle
*/
u8 pixel_rate;
};
struct vop2_video_port {
struct drm_crtc crtc;
struct vop2 *vop2;
struct clk *dclk;
struct clk *dclk_src;
unsigned int id;
const struct vop2_video_port_data *data;
struct completion dsp_hold_completion;
/**
* @win_mask: Bitmask of windows attached to the video port;
*/
u32 win_mask;
struct vop2_win *primary_plane;
struct drm_pending_vblank_event *event;
unsigned int nlayers;
};
/**
* struct vop2_ops - helper operations for vop2 hardware
*
* These hooks are used by the common part of the vop2 driver to
* implement the proper behaviour of different variants.
*/
struct vop2_ops {
unsigned long (*setup_intf_mux)(struct vop2_video_port *vp, int ep_id, u32 polflags);
void (*setup_bg_dly)(struct vop2_video_port *vp);
void (*setup_overlay)(struct vop2_video_port *vp);
};
struct vop2_data {
u8 nr_vps;
u64 feature;
u32 version;
const struct vop2_ops *ops;
const struct vop2_win_data *win;
const struct vop2_video_port_data *vp;
const struct reg_field *cluster_reg;
const struct reg_field *smart_reg;
const struct vop2_regs_dump *regs_dump;
struct vop_rect max_input;
struct vop_rect max_output;
unsigned int nr_cluster_regs;
unsigned int nr_smart_regs;
unsigned int win_size;
unsigned int regs_dump_size;
unsigned int soc_id;
};
struct vop2 {
u32 version;
struct device *dev;
struct drm_device *drm;
struct vop2_video_port vps[ROCKCHIP_MAX_CRTC];
const struct vop2_data *data;
const struct vop2_ops *ops;
/*
* Number of windows that are registered as plane, may be less than the
* total number of hardware windows.
*/
u32 registered_num_wins;
struct resource *res;
void __iomem *regs;
struct regmap *map;
struct regmap *sys_grf;
struct regmap *vop_grf;
struct regmap *vo1_grf;
struct regmap *sys_pmu;
/* physical map length of vop2 register */
u32 len;
void __iomem *lut_regs;
/* protects crtc enable/disable */
struct mutex vop2_lock;
int irq;
/*
* Some global resources are shared between all video ports(crtcs), so
* we need a ref counter here.
*/
unsigned int enable_count;
struct clk *hclk;
struct clk *aclk;
struct clk *pclk;
struct clk *pll_hdmiphy0;
struct clk *pll_hdmiphy1;
/* optional internal rgb encoder */
struct rockchip_rgb *rgb;
/*
* Used to record layer selection configuration on rk356x/rk3588
* as register RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are
* shared for all the Video Ports.
*/
u32 old_layer_sel;
u32 old_port_sel;
/*
* Ensure that the updates to these two registers(RKK3568_OVL_LAYER_SEL/RK3568_OVL_PORT_SEL)
* take effect in sequence.
*/
struct mutex ovl_lock;
/* must be put at the end of the struct */
struct vop2_win win[];
};
/* interrupt define */
#define FS_NEW_INTR BIT(4)
#define ADDR_SAME_INTR BIT(5)
#define LINE_FLAG1_INTR BIT(6)
#define WIN0_EMPTY_INTR BIT(7)
#define WIN1_EMPTY_INTR BIT(8)
#define WIN2_EMPTY_INTR BIT(9)
#define WIN3_EMPTY_INTR BIT(10)
#define HWC_EMPTY_INTR BIT(11)
#define POST_BUF_EMPTY_INTR BIT(12)
#define PWM_GEN_INTR BIT(13)
#define DMA_FINISH_INTR BIT(14)
#define FS_FIELD_INTR BIT(15)
#define FE_INTR BIT(16)
#define WB_UV_FIFO_FULL_INTR BIT(17)
#define WB_YRGB_FIFO_FULL_INTR BIT(18)
#define WB_COMPLETE_INTR BIT(19)
enum vop_csc_format {
CSC_BT601L,
CSC_BT709L,
CSC_BT601F,
CSC_BT2020,
};
enum src_factor_mode {
SRC_FAC_ALPHA_ZERO,
SRC_FAC_ALPHA_ONE,
SRC_FAC_ALPHA_DST,
SRC_FAC_ALPHA_DST_INVERSE,
SRC_FAC_ALPHA_SRC,
SRC_FAC_ALPHA_SRC_GLOBAL,
};
enum dst_factor_mode {
DST_FAC_ALPHA_ZERO,
DST_FAC_ALPHA_ONE,
DST_FAC_ALPHA_SRC,
DST_FAC_ALPHA_SRC_INVERSE,
DST_FAC_ALPHA_DST,
DST_FAC_ALPHA_DST_GLOBAL,
};
#define RK3568_GRF_VO_CON1 0x0364
#define RK3588_GRF_SOC_CON1 0x0304
#define RK3588_GRF_VOP_CON2 0x08
#define RK3588_GRF_VO1_CON0 0x00
/* System registers definition */
#define RK3568_REG_CFG_DONE 0x000
#define RK3568_VERSION_INFO 0x004
#define RK3568_SYS_AUTO_GATING_CTRL 0x008
#define RK3576_SYS_MMU_CTRL_IMD 0x020
#define RK3568_SYS_AXI_LUT_CTRL 0x024
#define RK3568_DSP_IF_EN 0x028
#define RK3576_SYS_PORT_CTRL_IMD 0x028
#define RK3568_DSP_IF_CTRL 0x02c
#define RK3568_DSP_IF_POL 0x030
#define RK3576_SYS_CLUSTER_PD_CTRL_IMD 0x030
#define RK3588_SYS_PD_CTRL 0x034
#define RK3568_WB_CTRL 0x40
#define RK3568_WB_XSCAL_FACTOR 0x44
#define RK3568_WB_YRGB_MST 0x48
#define RK3568_WB_CBR_MST 0x4C
#define RK3568_OTP_WIN_EN 0x050
#define RK3568_LUT_PORT_SEL 0x058
#define RK3568_SYS_STATUS0 0x060
#define RK3568_VP_LINE_FLAG(vp) (0x70 + (vp) * 0x4)
#define RK3568_SYS0_INT_EN 0x80
#define RK3568_SYS0_INT_CLR 0x84
#define RK3568_SYS0_INT_STATUS 0x88
#define RK3568_SYS1_INT_EN 0x90
#define RK3568_SYS1_INT_CLR 0x94
#define RK3568_SYS1_INT_STATUS 0x98
#define RK3568_VP_INT_EN(vp) (0xA0 + (vp) * 0x10)
#define RK3568_VP_INT_CLR(vp) (0xA4 + (vp) * 0x10)
#define RK3568_VP_INT_STATUS(vp) (0xA8 + (vp) * 0x10)
#define RK3568_VP_INT_RAW_STATUS(vp) (0xAC + (vp) * 0x10)
#define RK3576_WB_CTRL 0x100
#define RK3576_WB_XSCAL_FACTOR 0x104
#define RK3576_WB_YRGB_MST 0x108
#define RK3576_WB_CBR_MST 0x10C
#define RK3576_WB_VIR_STRIDE 0x110
#define RK3576_WB_TIMEOUT_CTRL 0x114
#define RK3576_MIPI0_IF_CTRL 0x180
#define RK3576_HDMI0_IF_CTRL 0x184
#define RK3576_EDP0_IF_CTRL 0x188
#define RK3576_DP0_IF_CTRL 0x18C
#define RK3576_RGB_IF_CTRL 0x194
#define RK3576_DP1_IF_CTRL 0x1A4
#define RK3576_DP2_IF_CTRL 0x1B0
/* Extra OVL register definition */
#define RK3576_SYS_EXTRA_ALPHA_CTRL 0x500
#define RK3576_CLUSTER0_MIX_SRC_COLOR_CTRL 0x530
#define RK3576_CLUSTER0_MIX_DST_COLOR_CTRL 0x534
#define RK3576_CLUSTER0_MIX_SRC_ALPHA_CTRL 0x538
#define RK3576_CLUSTER0_MIX_DST_ALPHA_CTRL 0x53c
#define RK3576_CLUSTER1_MIX_SRC_COLOR_CTRL 0x540
#define RK3576_CLUSTER1_MIX_DST_COLOR_CTRL 0x544
#define RK3576_CLUSTER1_MIX_SRC_ALPHA_CTRL 0x548
#define RK3576_CLUSTER1_MIX_DST_ALPHA_CTRL 0x54c
/* OVL registers for Video Port definition */
#define RK3576_OVL_CTRL(vp) (0x600 + (vp) * 0x100)
#define RK3576_OVL_LAYER_SEL(vp) (0x604 + (vp) * 0x100)
#define RK3576_OVL_MIX0_SRC_COLOR_CTRL(vp) (0x620 + (vp) * 0x100)
#define RK3576_OVL_MIX0_DST_COLOR_CTRL(vp) (0x624 + (vp) * 0x100)
#define RK3576_OVL_MIX0_SRC_ALPHA_CTRL(vp) (0x628 + (vp) * 0x100)
#define RK3576_OVL_MIX0_DST_ALPHA_CTRL(vp) (0x62C + (vp) * 0x100)
#define RK3576_OVL_MIX1_SRC_COLOR_CTRL(vp) (0x630 + (vp) * 0x100)
#define RK3576_OVL_MIX1_DST_COLOR_CTRL(vp) (0x634 + (vp) * 0x100)
#define RK3576_OVL_MIX1_SRC_ALPHA_CTRL(vp) (0x638 + (vp) * 0x100)
#define RK3576_OVL_MIX1_DST_ALPHA_CTRL(vp) (0x63C + (vp) * 0x100)
#define RK3576_OVL_MIX2_SRC_COLOR_CTRL(vp) (0x640 + (vp) * 0x100)
#define RK3576_OVL_MIX2_DST_COLOR_CTRL(vp) (0x644 + (vp) * 0x100)
#define RK3576_OVL_MIX2_SRC_ALPHA_CTRL(vp) (0x648 + (vp) * 0x100)
#define RK3576_OVL_MIX2_DST_ALPHA_CTRL(vp) (0x64C + (vp) * 0x100)
#define RK3576_EXTRA_OVL_SRC_COLOR_CTRL(vp) (0x650 + (vp) * 0x100)
#define RK3576_EXTRA_OVL_DST_COLOR_CTRL(vp) (0x654 + (vp) * 0x100)
#define RK3576_EXTRA_OVL_SRC_ALPHA_CTRL(vp) (0x658 + (vp) * 0x100)
#define RK3576_EXTRA_OVL_DST_ALPHA_CTRL(vp) (0x65C + (vp) * 0x100)
#define RK3576_OVL_HDR_SRC_COLOR_CTRL(vp) (0x660 + (vp) * 0x100)
#define RK3576_OVL_HDR_DST_COLOR_CTRL(vp) (0x664 + (vp) * 0x100)
#define RK3576_OVL_HDR_SRC_ALPHA_CTRL(vp) (0x668 + (vp) * 0x100)
#define RK3576_OVL_HDR_DST_ALPHA_CTRL(vp) (0x66C + (vp) * 0x100)
#define RK3576_OVL_BG_MIX_CTRL(vp) (0x670 + (vp) * 0x100)
/* Video Port registers definition */
#define RK3568_VP0_CTRL_BASE 0x0C00
#define RK3568_VP1_CTRL_BASE 0x0D00
#define RK3568_VP2_CTRL_BASE 0x0E00
#define RK3588_VP3_CTRL_BASE 0x0F00
#define RK3568_VP_DSP_CTRL 0x00
#define RK3568_VP_MIPI_CTRL 0x04
#define RK3568_VP_COLOR_BAR_CTRL 0x08
#define RK3588_VP_CLK_CTRL 0x0C
#define RK3568_VP_3D_LUT_CTRL 0x10
#define RK3568_VP_3D_LUT_MST 0x20
#define RK3568_VP_DSP_BG 0x2C
#define RK3568_VP_PRE_SCAN_HTIMING 0x30
#define RK3568_VP_POST_DSP_HACT_INFO 0x34
#define RK3568_VP_POST_DSP_VACT_INFO 0x38
#define RK3568_VP_POST_SCL_FACTOR_YRGB 0x3C
#define RK3568_VP_POST_SCL_CTRL 0x40
#define RK3568_VP_POST_DSP_VACT_INFO_F1 0x44
#define RK3568_VP_DSP_HTOTAL_HS_END 0x48
#define RK3568_VP_DSP_HACT_ST_END 0x4C
#define RK3568_VP_DSP_VTOTAL_VS_END 0x50
#define RK3568_VP_DSP_VACT_ST_END 0x54
#define RK3568_VP_DSP_VS_ST_END_F1 0x58
#define RK3568_VP_DSP_VACT_ST_END_F1 0x5C
#define RK3568_VP_BCSH_CTRL 0x60
#define RK3568_VP_BCSH_BCS 0x64
#define RK3568_VP_BCSH_H 0x68
#define RK3568_VP_BCSH_COLOR_BAR 0x6C
/* Overlay registers definition */
#define RK3568_OVL_CTRL 0x600
#define RK3568_OVL_LAYER_SEL 0x604
#define RK3568_OVL_PORT_SEL 0x608
#define RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL 0x610
#define RK3568_CLUSTER0_MIX_DST_COLOR_CTRL 0x614
#define RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL 0x618
#define RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL 0x61C
#define RK3568_MIX0_SRC_COLOR_CTRL 0x650
#define RK3568_MIX0_DST_COLOR_CTRL 0x654
#define RK3568_MIX0_SRC_ALPHA_CTRL 0x658
#define RK3568_MIX0_DST_ALPHA_CTRL 0x65C
#define RK3568_HDR0_SRC_COLOR_CTRL 0x6C0
#define RK3568_HDR0_DST_COLOR_CTRL 0x6C4
#define RK3568_HDR0_SRC_ALPHA_CTRL 0x6C8
#define RK3568_HDR0_DST_ALPHA_CTRL 0x6CC
#define RK3568_VP_BG_MIX_CTRL(vp) (0x6E0 + (vp) * 4)
#define RK3568_CLUSTER_DLY_NUM 0x6F0
#define RK3568_SMART_DLY_NUM 0x6F8
/* Cluster register definition, offset relative to window base */
#define RK3568_CLUSTER0_CTRL_BASE 0x1000
#define RK3568_CLUSTER1_CTRL_BASE 0x1200
#define RK3588_CLUSTER2_CTRL_BASE 0x1400
#define RK3588_CLUSTER3_CTRL_BASE 0x1600
#define RK3568_ESMART0_CTRL_BASE 0x1800
#define RK3568_ESMART1_CTRL_BASE 0x1A00
#define RK3568_SMART0_CTRL_BASE 0x1C00
#define RK3568_SMART1_CTRL_BASE 0x1E00
#define RK3588_ESMART2_CTRL_BASE 0x1C00
#define RK3588_ESMART3_CTRL_BASE 0x1E00
#define RK3568_CLUSTER_WIN_CTRL0 0x00
#define RK3568_CLUSTER_WIN_CTRL1 0x04
#define RK3568_CLUSTER_WIN_CTRL2 0x08
#define RK3568_CLUSTER_WIN_YRGB_MST 0x10
#define RK3568_CLUSTER_WIN_CBR_MST 0x14
#define RK3568_CLUSTER_WIN_VIR 0x18
#define RK3568_CLUSTER_WIN_ACT_INFO 0x20
#define RK3568_CLUSTER_WIN_DSP_INFO 0x24
#define RK3568_CLUSTER_WIN_DSP_ST 0x28
#define RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB 0x30
#define RK3568_CLUSTER_WIN_TRANSFORM_OFFSET 0x3C
#define RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL 0x50
#define RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE 0x54
#define RK3568_CLUSTER_WIN_AFBCD_HDR_PTR 0x58
#define RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH 0x5C
#define RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE 0x60
#define RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET 0x64
#define RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET 0x68
#define RK3568_CLUSTER_WIN_AFBCD_CTRL 0x6C
#define RK3576_CLUSTER_WIN_AFBCD_PLD_PTR_OFFSET 0x78
#define RK3568_CLUSTER_CTRL 0x100
#define RK3576_CLUSTER_PORT_SEL_IMD 0x1F4
#define RK3576_CLUSTER_DLY_NUM 0x1F8
/* (E)smart register definition, offset relative to window base */
#define RK3568_SMART_CTRL0 0x00
#define RK3568_SMART_CTRL1 0x04
#define RK3588_SMART_AXI_CTRL 0x08
#define RK3568_SMART_REGION0_CTRL 0x10
#define RK3568_SMART_REGION0_YRGB_MST 0x14
#define RK3568_SMART_REGION0_CBR_MST 0x18
#define RK3568_SMART_REGION0_VIR 0x1C
#define RK3568_SMART_REGION0_ACT_INFO 0x20
#define RK3568_SMART_REGION0_DSP_INFO 0x24
#define RK3568_SMART_REGION0_DSP_ST 0x28
#define RK3568_SMART_REGION0_SCL_CTRL 0x30
#define RK3568_SMART_REGION0_SCL_FACTOR_YRGB 0x34
#define RK3568_SMART_REGION0_SCL_FACTOR_CBR 0x38
#define RK3568_SMART_REGION0_SCL_OFFSET 0x3C
#define RK3568_SMART_REGION1_CTRL 0x40
#define RK3568_SMART_REGION1_YRGB_MST 0x44
#define RK3568_SMART_REGION1_CBR_MST 0x48
#define RK3568_SMART_REGION1_VIR 0x4C
#define RK3568_SMART_REGION1_ACT_INFO 0x50
#define RK3568_SMART_REGION1_DSP_INFO 0x54
#define RK3568_SMART_REGION1_DSP_ST 0x58
#define RK3568_SMART_REGION1_SCL_CTRL 0x60
#define RK3568_SMART_REGION1_SCL_FACTOR_YRGB 0x64
#define RK3568_SMART_REGION1_SCL_FACTOR_CBR 0x68
#define RK3568_SMART_REGION1_SCL_OFFSET 0x6C
#define RK3568_SMART_REGION2_CTRL 0x70
#define RK3568_SMART_REGION2_YRGB_MST 0x74
#define RK3568_SMART_REGION2_CBR_MST 0x78
#define RK3568_SMART_REGION2_VIR 0x7C
#define RK3568_SMART_REGION2_ACT_INFO 0x80
#define RK3568_SMART_REGION2_DSP_INFO 0x84
#define RK3568_SMART_REGION2_DSP_ST 0x88
#define RK3568_SMART_REGION2_SCL_CTRL 0x90
#define RK3568_SMART_REGION2_SCL_FACTOR_YRGB 0x94
#define RK3568_SMART_REGION2_SCL_FACTOR_CBR 0x98
#define RK3568_SMART_REGION2_SCL_OFFSET 0x9C
#define RK3568_SMART_REGION3_CTRL 0xA0
#define RK3568_SMART_REGION3_YRGB_MST 0xA4
#define RK3568_SMART_REGION3_CBR_MST 0xA8
#define RK3568_SMART_REGION3_VIR 0xAC
#define RK3568_SMART_REGION3_ACT_INFO 0xB0
#define RK3568_SMART_REGION3_DSP_INFO 0xB4
#define RK3568_SMART_REGION3_DSP_ST 0xB8
#define RK3568_SMART_REGION3_SCL_CTRL 0xC0
#define RK3568_SMART_REGION3_SCL_FACTOR_YRGB 0xC4
#define RK3568_SMART_REGION3_SCL_FACTOR_CBR 0xC8
#define RK3568_SMART_REGION3_SCL_OFFSET 0xCC
#define RK3568_SMART_COLOR_KEY_CTRL 0xD0
#define RK3576_SMART_ALPHA_MAP 0xD8
#define RK3576_SMART_PORT_SEL_IMD 0xF4
#define RK3576_SMART_DLY_NUM 0xF8
/* HDR register definition */
#define RK3568_HDR_LUT_CTRL 0x2000
#define RK3568_HDR_LUT_MST 0x2004
#define RK3568_SDR2HDR_CTRL 0x2010
#define RK3568_HDR2SDR_CTRL 0x2020
#define RK3568_HDR2SDR_SRC_RANGE 0x2024
#define RK3568_HDR2SDR_NORMFACEETF 0x2028
#define RK3568_HDR2SDR_DST_RANGE 0x202C
#define RK3568_HDR2SDR_NORMFACCGAMMA 0x2030
#define RK3568_HDR_EETF_OETF_Y0 0x203C
#define RK3568_HDR_SAT_Y0 0x20C0
#define RK3568_HDR_EOTF_OETF_Y0 0x20F0
#define RK3568_HDR_OETF_DX_POW1 0x2200
#define RK3568_HDR_OETF_XN1 0x2300
#define RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN BIT(15)
#define RK3568_VP_DSP_CTRL__STANDBY BIT(31)
#define RK3568_VP_DSP_CTRL__DSP_LUT_EN BIT(28)
#define RK3568_VP_DSP_CTRL__DITHER_DOWN_MODE BIT(20)
#define RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL GENMASK(19, 18)
#define RK3568_VP_DSP_CTRL__DITHER_DOWN_EN BIT(17)
#define RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN BIT(16)
#define RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y BIT(15)
#define RK3568_VP_DSP_CTRL__DSP_RG_SWAP BIT(10)
#define RK3568_VP_DSP_CTRL__DSP_RB_SWAP BIT(9)
#define RK3568_VP_DSP_CTRL__DSP_BG_SWAP BIT(8)
#define RK3568_VP_DSP_CTRL__DSP_INTERLACE BIT(7)
#define RK3568_VP_DSP_CTRL__DSP_FILED_POL BIT(6)
#define RK3568_VP_DSP_CTRL__P2I_EN BIT(5)
#define RK3568_VP_DSP_CTRL__CORE_DCLK_DIV BIT(4)
#define RK3568_VP_DSP_CTRL__OUT_MODE GENMASK(3, 0)
#define RK3588_VP_DSP_CTRL__GAMMA_UPDATE_EN BIT(22)
#define RK3588_VP_CLK_CTRL__DCLK_OUT_DIV GENMASK(3, 2)
#define RK3588_VP_CLK_CTRL__DCLK_CORE_DIV GENMASK(1, 0)
#define RK3568_VP_DSP_BG__DSP_BG_RED GENMASK(29, 20)
#define RK3568_VP_DSP_BG__DSP_BG_GREEN GENMASK(19, 10)
#define RK3568_VP_DSP_BG__DSP_BG_BLUE GENMASK(9, 0)
#define RK3568_VP_POST_SCL_CTRL__VSCALEDOWN BIT(1)
#define RK3568_VP_POST_SCL_CTRL__HSCALEDOWN BIT(0)
#define RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX GENMASK(26, 25)
#define RK3568_SYS_DSP_INFACE_EN_LVDS1 BIT(24)
#define RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21)
#define RK3568_SYS_DSP_INFACE_EN_MIPI1 BIT(20)
#define RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX GENMASK(19, 18)
#define RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(17, 16)
#define RK3568_SYS_DSP_INFACE_EN_EDP_MUX GENMASK(15, 14)
#define RK3568_SYS_DSP_INFACE_EN_HDMI_MUX GENMASK(11, 10)
#define RK3568_SYS_DSP_INFACE_EN_RGB_MUX GENMASK(9, 8)
#define RK3568_SYS_DSP_INFACE_EN_LVDS0 BIT(5)
#define RK3568_SYS_DSP_INFACE_EN_MIPI0 BIT(4)
#define RK3568_SYS_DSP_INFACE_EN_EDP BIT(3)
#define RK3568_SYS_DSP_INFACE_EN_HDMI BIT(1)
#define RK3568_SYS_DSP_INFACE_EN_RGB BIT(0)
#define RK3588_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21)
#define RK3588_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(20, 20)
#define RK3588_SYS_DSP_INFACE_EN_EDP_HDMI1_MUX GENMASK(19, 18)
#define RK3588_SYS_DSP_INFACE_EN_EDP_HDMI0_MUX GENMASK(17, 16)
#define RK3588_SYS_DSP_INFACE_EN_DP1_MUX GENMASK(15, 14)
#define RK3588_SYS_DSP_INFACE_EN_DP0_MUX GENMASK(13, 12)
#define RK3588_SYS_DSP_INFACE_EN_DPI GENMASK(9, 8)
#define RK3588_SYS_DSP_INFACE_EN_MIPI1 BIT(7)
#define RK3588_SYS_DSP_INFACE_EN_MIPI0 BIT(6)
#define RK3588_SYS_DSP_INFACE_EN_HDMI1 BIT(5)
#define RK3588_SYS_DSP_INFACE_EN_EDP1 BIT(4)
#define RK3588_SYS_DSP_INFACE_EN_HDMI0 BIT(3)
#define RK3588_SYS_DSP_INFACE_EN_EDP0 BIT(2)
#define RK3588_SYS_DSP_INFACE_EN_DP1 BIT(1)
#define RK3588_SYS_DSP_INFACE_EN_DP0 BIT(0)
#define RK3588_DSP_IF_MIPI1_PCLK_DIV GENMASK(27, 26)
#define RK3588_DSP_IF_MIPI0_PCLK_DIV GENMASK(25, 24)
#define RK3588_DSP_IF_EDP_HDMI1_PCLK_DIV GENMASK(22, 22)
#define RK3588_DSP_IF_EDP_HDMI1_DCLK_DIV GENMASK(21, 20)
#define RK3588_DSP_IF_EDP_HDMI0_PCLK_DIV GENMASK(18, 18)
#define RK3588_DSP_IF_EDP_HDMI0_DCLK_DIV GENMASK(17, 16)
#define RK3568_DSP_IF_POL__MIPI_PIN_POL GENMASK(19, 16)
#define RK3568_DSP_IF_POL__EDP_PIN_POL GENMASK(15, 12)
#define RK3568_DSP_IF_POL__HDMI_PIN_POL GENMASK(7, 4)
#define RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL GENMASK(3, 0)
#define RK3588_DSP_IF_POL__DP1_PIN_POL GENMASK(14, 12)
#define RK3588_DSP_IF_POL__DP0_PIN_POL GENMASK(10, 8)
#define RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL GENMASK(13, 12)
#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5)
#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4)
#define RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN BIT(31)
#define RK3568_DSP_IF_POL__CFG_DONE_IMD BIT(28)
#define VOP2_SYS_AXI_BUS_NUM 2
#define VOP2_CLUSTER_YUV444_10 0x12
#define VOP2_COLOR_KEY_MASK BIT(31)
#define RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL GENMASK(31, 30)
#define RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD BIT(28)
#define RK3568_OVL_CTRL__YUV_MODE(vp) BIT(vp)
#define RK3568_VP_BG_MIX_CTRL__BG_DLY GENMASK(31, 24)
#define RK3568_OVL_PORT_SEL__SEL_PORT GENMASK(31, 16)
#define RK3568_OVL_PORT_SEL__SMART1 GENMASK(31, 30)
#define RK3568_OVL_PORT_SEL__SMART0 GENMASK(29, 28)
#define RK3588_OVL_PORT_SEL__ESMART3 GENMASK(31, 30)
#define RK3588_OVL_PORT_SEL__ESMART2 GENMASK(29, 28)
#define RK3568_OVL_PORT_SEL__ESMART1 GENMASK(27, 26)
#define RK3568_OVL_PORT_SEL__ESMART0 GENMASK(25, 24)
#define RK3588_OVL_PORT_SEL__CLUSTER3 GENMASK(23, 22)
#define RK3588_OVL_PORT_SEL__CLUSTER2 GENMASK(21, 20)
#define RK3568_OVL_PORT_SEL__CLUSTER1 GENMASK(19, 18)
#define RK3568_OVL_PORT_SEL__CLUSTER0 GENMASK(17, 16)
#define RK3588_OVL_PORT_SET__PORT3_MUX GENMASK(15, 12)
#define RK3568_OVL_PORT_SET__PORT2_MUX GENMASK(11, 8)
#define RK3568_OVL_PORT_SET__PORT1_MUX GENMASK(7, 4)
#define RK3568_OVL_PORT_SET__PORT0_MUX GENMASK(3, 0)
#define RK3568_OVL_LAYER_SEL__LAYER(layer, x) ((x) << ((layer) * 4))
#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_1 GENMASK(31, 24)
#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_0 GENMASK(23, 16)
#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_1 GENMASK(15, 8)
#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_0 GENMASK(7, 0)
#define RK3568_CLUSTER_WIN_CTRL0__WIN0_EN BIT(0)
#define RK3568_SMART_REGION0_CTRL__WIN0_EN BIT(0)
#define RK3568_SMART_DLY_NUM__SMART1 GENMASK(31, 24)
#define RK3568_SMART_DLY_NUM__SMART0 GENMASK(23, 16)
#define RK3568_SMART_DLY_NUM__ESMART1 GENMASK(15, 8)
#define RK3568_SMART_DLY_NUM__ESMART0 GENMASK(7, 0)
#define VP_INT_DSP_HOLD_VALID BIT(6)
#define VP_INT_FS_FIELD BIT(5)
#define VP_INT_POST_BUF_EMPTY BIT(4)
#define VP_INT_LINE_FLAG1 BIT(3)
#define VP_INT_LINE_FLAG0 BIT(2)
#define VOP2_INT_BUS_ERRPR BIT(1)
#define VP_INT_FS BIT(0)
#define POLFLAG_DCLK_INV BIT(3)
#define RK3576_OVL_CTRL__YUV_MODE BIT(0)
#define RK3576_OVL_BG_MIX_CTRL__BG_DLY GENMASK(31, 24)
#define RK3576_DSP_IF_CFG_DONE_IMD BIT(31)
#define RK3576_DSP_IF_DCLK_SEL_OUT BIT(21)
#define RK3576_DSP_IF_PCLK_DIV BIT(20)
#define RK3576_DSP_IF_PIN_POL GENMASK(5, 4)
#define RK3576_DSP_IF_MUX GENMASK(3, 2)
#define RK3576_DSP_IF_CLK_OUT_EN BIT(1)
#define RK3576_DSP_IF_EN BIT(0)
enum vop2_layer_phy_id {
ROCKCHIP_VOP2_CLUSTER0 = 0,
ROCKCHIP_VOP2_CLUSTER1,
ROCKCHIP_VOP2_ESMART0,
ROCKCHIP_VOP2_ESMART1,
ROCKCHIP_VOP2_SMART0,
ROCKCHIP_VOP2_SMART1,
ROCKCHIP_VOP2_CLUSTER2,
ROCKCHIP_VOP2_CLUSTER3,
ROCKCHIP_VOP2_ESMART2,
ROCKCHIP_VOP2_ESMART3,
ROCKCHIP_VOP2_PHY_ID_INVALID = -1,
};
extern const struct component_ops vop2_component_ops;
static inline void vop2_writel(struct vop2 *vop2, u32 offset, u32 v)
{
regmap_write(vop2->map, offset, v);
}
static inline void vop2_vp_write(struct vop2_video_port *vp, u32 offset, u32 v)
{
regmap_write(vp->vop2->map, vp->data->offset + offset, v);
}
static inline u32 vop2_readl(struct vop2 *vop2, u32 offset)
{
u32 val;
regmap_read(vop2->map, offset, &val);
return val;
}
static inline u32 vop2_vp_read(struct vop2_video_port *vp, u32 offset)
{
u32 val;
regmap_read(vp->vop2->map, vp->data->offset + offset, &val);
return val;
}
static inline void vop2_win_write(const struct vop2_win *win, unsigned int reg, u32 v)
{
regmap_field_write(win->reg[reg], v);
}
static inline bool vop2_cluster_window(const struct vop2_win *win)
{
return win->data->feature & WIN_FEATURE_CLUSTER;
}
static inline struct vop2_video_port *to_vop2_video_port(struct drm_crtc *crtc)
{
return container_of(crtc, struct vop2_video_port, crtc);
}
static inline struct vop2_win *to_vop2_win(struct drm_plane *p)
{
return container_of(p, struct vop2_win, base);
}
/*
* Note:
* The write mask function is documented but missing on rk3566/8, writes
* to these bits have no effect. For newer soc(rk3588 and following) the
* write mask is needed for register writes.
*
* GLB_CFG_DONE_EN has no write mask bit.
*
*/
static inline void vop2_cfg_done(struct vop2_video_port *vp)
{
struct vop2 *vop2 = vp->vop2;
u32 val = RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN;
val |= BIT(vp->id) | (BIT(vp->id) << 16);
regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, val);
}
#endif /* _ROCKCHIP_DRM_VOP2_H */
@@ -0,0 +1,717 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:
* Mark Yao <mark.yao@rock-chips.com>
* Sandy Huang <hjc@rock-chips.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/devinfo.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#include "rockchip_lvds.h"
#define DISPLAY_OUTPUT_RGB 0
#define DISPLAY_OUTPUT_LVDS 1
#define DISPLAY_OUTPUT_DUAL_LVDS 2
struct rockchip_lvds;
/**
* struct rockchip_lvds_soc_data - rockchip lvds Soc private data
* @probe: LVDS platform probe function
* @helper_funcs: LVDS connector helper functions
*/
struct rockchip_lvds_soc_data {
int (*probe)(struct platform_device *pdev, struct rockchip_lvds *lvds);
const struct drm_encoder_helper_funcs *helper_funcs;
};
struct rockchip_lvds {
struct device *dev;
void __iomem *regs;
struct regmap *grf;
struct clk *pclk;
struct phy *dphy;
const struct rockchip_lvds_soc_data *soc_data;
int output; /* rgb lvds or dual lvds output */
int format; /* vesa or jeida format */
struct drm_device *drm_dev;
struct drm_panel *panel;
struct drm_bridge *bridge;
struct rockchip_encoder encoder;
struct dev_pin_info *pins;
};
static inline struct rockchip_lvds *brige_to_lvds(struct drm_bridge *bridge)
{
return (struct rockchip_lvds *)bridge->driver_private;
}
static inline struct rockchip_lvds *encoder_to_lvds(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct rockchip_lvds, encoder);
}
static inline void rk3288_writel(struct rockchip_lvds *lvds, u32 offset,
u32 val)
{
writel_relaxed(val, lvds->regs + offset);
if (lvds->output == DISPLAY_OUTPUT_LVDS)
return;
writel_relaxed(val, lvds->regs + offset + RK3288_LVDS_CH1_OFFSET);
}
static inline int rockchip_lvds_name_to_format(const char *s)
{
if (strncmp(s, "jeida-18", 8) == 0)
return LVDS_JEIDA_18;
else if (strncmp(s, "jeida-24", 8) == 0)
return LVDS_JEIDA_24;
else if (strncmp(s, "vesa-24", 7) == 0)
return LVDS_VESA_24;
return -EINVAL;
}
static inline int rockchip_lvds_name_to_output(const char *s)
{
if (strncmp(s, "rgb", 3) == 0)
return DISPLAY_OUTPUT_RGB;
else if (strncmp(s, "lvds", 4) == 0)
return DISPLAY_OUTPUT_LVDS;
else if (strncmp(s, "duallvds", 8) == 0)
return DISPLAY_OUTPUT_DUAL_LVDS;
return -EINVAL;
}
static int
rockchip_lvds_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct rockchip_lvds *lvds = brige_to_lvds(bridge);
struct drm_panel *panel = lvds->panel;
return drm_panel_get_modes(panel, connector);
}
static const
struct drm_bridge_funcs rockchip_lvds_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.get_modes = rockchip_lvds_bridge_get_modes,
};
static int
rockchip_lvds_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_P888;
s->output_type = DRM_MODE_CONNECTOR_LVDS;
return 0;
}
static int rk3288_lvds_poweron(struct rockchip_lvds *lvds)
{
int ret;
u32 val;
ret = clk_enable(lvds->pclk);
if (ret < 0) {
DRM_DEV_ERROR(lvds->dev, "failed to enable lvds pclk %d\n", ret);
return ret;
}
ret = pm_runtime_resume_and_get(lvds->dev);
if (ret < 0) {
DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
clk_disable(lvds->pclk);
return ret;
}
val = RK3288_LVDS_CH0_REG0_LANE4_EN | RK3288_LVDS_CH0_REG0_LANE3_EN |
RK3288_LVDS_CH0_REG0_LANE2_EN | RK3288_LVDS_CH0_REG0_LANE1_EN |
RK3288_LVDS_CH0_REG0_LANE0_EN;
if (lvds->output == DISPLAY_OUTPUT_RGB) {
val |= RK3288_LVDS_CH0_REG0_TTL_EN |
RK3288_LVDS_CH0_REG0_LANECK_EN;
rk3288_writel(lvds, RK3288_LVDS_CH0_REG0, val);
rk3288_writel(lvds, RK3288_LVDS_CH0_REG2,
RK3288_LVDS_PLL_FBDIV_REG2(0x46));
rk3288_writel(lvds, RK3288_LVDS_CH0_REG4,
RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE |
RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE |
RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE |
RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE |
RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE |
RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE);
rk3288_writel(lvds, RK3288_LVDS_CH0_REG5,
RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA |
RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA |
RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA |
RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA |
RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA |
RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA);
} else {
val |= RK3288_LVDS_CH0_REG0_LVDS_EN |
RK3288_LVDS_CH0_REG0_LANECK_EN;
rk3288_writel(lvds, RK3288_LVDS_CH0_REG0, val);
rk3288_writel(lvds, RK3288_LVDS_CH0_REG1,
RK3288_LVDS_CH0_REG1_LANECK_BIAS |
RK3288_LVDS_CH0_REG1_LANE4_BIAS |
RK3288_LVDS_CH0_REG1_LANE3_BIAS |
RK3288_LVDS_CH0_REG1_LANE2_BIAS |
RK3288_LVDS_CH0_REG1_LANE1_BIAS |
RK3288_LVDS_CH0_REG1_LANE0_BIAS);
rk3288_writel(lvds, RK3288_LVDS_CH0_REG2,
RK3288_LVDS_CH0_REG2_RESERVE_ON |
RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE |
RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE |
RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE |
RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE |
RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE |
RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE |
RK3288_LVDS_PLL_FBDIV_REG2(0x46));
rk3288_writel(lvds, RK3288_LVDS_CH0_REG4, 0x00);
rk3288_writel(lvds, RK3288_LVDS_CH0_REG5, 0x00);
}
rk3288_writel(lvds, RK3288_LVDS_CH0_REG3,
RK3288_LVDS_PLL_FBDIV_REG3(0x46));
rk3288_writel(lvds, RK3288_LVDS_CH0_REGD,
RK3288_LVDS_PLL_PREDIV_REGD(0x0a));
rk3288_writel(lvds, RK3288_LVDS_CH0_REG20,
RK3288_LVDS_CH0_REG20_LSB);
rk3288_writel(lvds, RK3288_LVDS_CFG_REGC,
RK3288_LVDS_CFG_REGC_PLL_ENABLE);
rk3288_writel(lvds, RK3288_LVDS_CFG_REG21,
RK3288_LVDS_CFG_REG21_TX_ENABLE);
return 0;
}
static void rk3288_lvds_poweroff(struct rockchip_lvds *lvds)
{
int ret;
u32 val;
rk3288_writel(lvds, RK3288_LVDS_CFG_REG21,
RK3288_LVDS_CFG_REG21_TX_ENABLE);
rk3288_writel(lvds, RK3288_LVDS_CFG_REGC,
RK3288_LVDS_CFG_REGC_PLL_ENABLE);
val = LVDS_DUAL | LVDS_TTL_EN | LVDS_CH0_EN | LVDS_CH1_EN | LVDS_PWRDN;
val |= val << 16;
ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON7, val);
if (ret != 0)
DRM_DEV_ERROR(lvds->dev, "Could not write to GRF: %d\n", ret);
pm_runtime_put(lvds->dev);
clk_disable(lvds->pclk);
}
static int rk3288_lvds_grf_config(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
u8 pin_hsync = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 1 : 0;
u8 pin_dclk = (mode->flags & DRM_MODE_FLAG_PCSYNC) ? 1 : 0;
u32 val;
int ret;
/* iomux to LCD data/sync mode */
if (lvds->output == DISPLAY_OUTPUT_RGB)
if (lvds->pins && !IS_ERR(lvds->pins->default_state))
pinctrl_select_state(lvds->pins->p,
lvds->pins->default_state);
val = lvds->format | LVDS_CH0_EN;
if (lvds->output == DISPLAY_OUTPUT_RGB)
val |= LVDS_TTL_EN | LVDS_CH1_EN;
else if (lvds->output == DISPLAY_OUTPUT_DUAL_LVDS)
val |= LVDS_DUAL | LVDS_CH1_EN;
if ((mode->htotal - mode->hsync_start) & 0x01)
val |= LVDS_START_PHASE_RST_1;
val |= (pin_dclk << 8) | (pin_hsync << 9);
val |= (0xffff << 16);
ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON7, val);
if (ret)
DRM_DEV_ERROR(lvds->dev, "Could not write to GRF: %d\n", ret);
return ret;
}
static int rk3288_lvds_set_vop_source(struct rockchip_lvds *lvds,
struct drm_encoder *encoder)
{
u32 val;
int ret;
ret = drm_of_encoder_active_endpoint_id(lvds->dev->of_node, encoder);
if (ret < 0)
return ret;
val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16;
if (ret)
val |= RK3288_LVDS_SOC_CON6_SEL_VOP_LIT;
ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON6, val);
if (ret < 0)
return ret;
return 0;
}
static void rk3288_lvds_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
int ret;
drm_panel_prepare(lvds->panel);
ret = rk3288_lvds_poweron(lvds);
if (ret < 0) {
DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
ret = rk3288_lvds_grf_config(encoder, mode);
if (ret) {
DRM_DEV_ERROR(lvds->dev, "failed to configure LVDS: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
ret = rk3288_lvds_set_vop_source(lvds, encoder);
if (ret) {
DRM_DEV_ERROR(lvds->dev, "failed to set VOP source: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
drm_panel_enable(lvds->panel);
}
static void rk3288_lvds_encoder_disable(struct drm_encoder *encoder)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
drm_panel_disable(lvds->panel);
rk3288_lvds_poweroff(lvds);
drm_panel_unprepare(lvds->panel);
}
static int px30_lvds_poweron(struct rockchip_lvds *lvds)
{
int ret;
ret = pm_runtime_resume_and_get(lvds->dev);
if (ret < 0) {
DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
return ret;
}
/* Enable LVDS mode */
ret = regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1),
PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1));
if (ret)
pm_runtime_put(lvds->dev);
return ret;
}
static void px30_lvds_poweroff(struct rockchip_lvds *lvds)
{
regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1),
PX30_LVDS_MODE_EN(0) | PX30_LVDS_P2S_EN(0));
pm_runtime_put(lvds->dev);
}
static int px30_lvds_grf_config(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
if (lvds->output != DISPLAY_OUTPUT_LVDS) {
DRM_DEV_ERROR(lvds->dev, "Unsupported display output %d\n",
lvds->output);
return -EINVAL;
}
/* Set format */
return regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
PX30_LVDS_FORMAT(lvds->format),
PX30_LVDS_FORMAT(lvds->format));
}
static int px30_lvds_set_vop_source(struct rockchip_lvds *lvds,
struct drm_encoder *encoder)
{
int vop;
vop = drm_of_encoder_active_endpoint_id(lvds->dev->of_node, encoder);
if (vop < 0)
return vop;
return regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
PX30_LVDS_VOP_SEL(1),
PX30_LVDS_VOP_SEL(vop));
}
static void px30_lvds_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
int ret;
drm_panel_prepare(lvds->panel);
ret = px30_lvds_poweron(lvds);
if (ret) {
DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
ret = px30_lvds_grf_config(encoder, mode);
if (ret) {
DRM_DEV_ERROR(lvds->dev, "failed to configure LVDS: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
ret = px30_lvds_set_vop_source(lvds, encoder);
if (ret) {
DRM_DEV_ERROR(lvds->dev, "failed to set VOP source: %d\n", ret);
drm_panel_unprepare(lvds->panel);
return;
}
drm_panel_enable(lvds->panel);
}
static void px30_lvds_encoder_disable(struct drm_encoder *encoder)
{
struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
drm_panel_disable(lvds->panel);
px30_lvds_poweroff(lvds);
drm_panel_unprepare(lvds->panel);
}
static const
struct drm_encoder_helper_funcs rk3288_lvds_encoder_helper_funcs = {
.enable = rk3288_lvds_encoder_enable,
.disable = rk3288_lvds_encoder_disable,
.atomic_check = rockchip_lvds_encoder_atomic_check,
};
static const
struct drm_encoder_helper_funcs px30_lvds_encoder_helper_funcs = {
.enable = px30_lvds_encoder_enable,
.disable = px30_lvds_encoder_disable,
.atomic_check = rockchip_lvds_encoder_atomic_check,
};
static int rk3288_lvds_probe(struct platform_device *pdev,
struct rockchip_lvds *lvds)
{
lvds->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(lvds->regs))
return PTR_ERR(lvds->regs);
lvds->pclk = devm_clk_get_prepared(lvds->dev, "pclk_lvds");
if (IS_ERR(lvds->pclk))
return dev_err_probe(lvds->dev, PTR_ERR(lvds->pclk),
"could not get or prepare pclk_lvds\n");
lvds->pins = devm_kzalloc(lvds->dev, sizeof(*lvds->pins),
GFP_KERNEL);
if (!lvds->pins)
return -ENOMEM;
lvds->pins->p = devm_pinctrl_get(lvds->dev);
if (IS_ERR(lvds->pins->p)) {
dev_warn(lvds->dev, "no pinctrl handle\n");
devm_kfree(lvds->dev, lvds->pins);
lvds->pins = NULL;
} else {
lvds->pins->default_state =
pinctrl_lookup_state(lvds->pins->p, "lcdc");
if (IS_ERR(lvds->pins->default_state)) {
dev_warn(lvds->dev, "no default pinctrl state\n");
devm_kfree(lvds->dev, lvds->pins);
lvds->pins = NULL;
}
}
return 0;
}
static int px30_lvds_probe(struct platform_device *pdev,
struct rockchip_lvds *lvds)
{
int ret;
/* MSB */
ret = regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
PX30_LVDS_MSBSEL(1),
PX30_LVDS_MSBSEL(1));
if (ret)
return ret;
/* PHY */
lvds->dphy = devm_phy_get(&pdev->dev, "dphy");
if (IS_ERR(lvds->dphy))
return PTR_ERR(lvds->dphy);
ret = phy_init(lvds->dphy);
if (ret)
return ret;
ret = phy_set_mode(lvds->dphy, PHY_MODE_LVDS);
if (ret)
return ret;
return phy_power_on(lvds->dphy);
}
static const struct rockchip_lvds_soc_data rk3288_lvds_data = {
.probe = rk3288_lvds_probe,
.helper_funcs = &rk3288_lvds_encoder_helper_funcs,
};
static const struct rockchip_lvds_soc_data px30_lvds_data = {
.probe = px30_lvds_probe,
.helper_funcs = &px30_lvds_encoder_helper_funcs,
};
static const struct of_device_id rockchip_lvds_dt_ids[] = {
{
.compatible = "rockchip,rk3288-lvds",
.data = &rk3288_lvds_data
},
{
.compatible = "rockchip,px30-lvds",
.data = &px30_lvds_data
},
{}
};
MODULE_DEVICE_TABLE(of, rockchip_lvds_dt_ids);
static int rockchip_lvds_bind(struct device *dev, struct device *master,
void *data)
{
struct rockchip_lvds *lvds = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct device_node *remote = NULL;
struct device_node *port, *endpoint;
int ret = 0, child_count = 0;
const char *name;
u32 endpoint_id = 0;
lvds->drm_dev = drm_dev;
port = of_graph_get_port_by_id(dev->of_node, 1);
if (!port)
return dev_err_probe(dev, -EINVAL,
"can't found port point, please init lvds panel port!\n");
for_each_child_of_node(port, endpoint) {
child_count++;
of_property_read_u32(endpoint, "reg", &endpoint_id);
ret = drm_of_find_panel_or_bridge(dev->of_node, 1, endpoint_id,
&lvds->panel, &lvds->bridge);
if (!ret) {
of_node_put(endpoint);
break;
}
}
if (!child_count) {
ret = dev_err_probe(dev, -EINVAL, "lvds port does not have any children\n");
goto err_put_port;
} else if (ret) {
dev_err_probe(dev, ret, "failed to find panel and bridge node\n");
goto err_put_port;
}
if (lvds->panel)
remote = lvds->panel->dev->of_node;
else
remote = lvds->bridge->of_node;
if (of_property_read_string(dev->of_node, "rockchip,output", &name))
/* default set it as output rgb */
lvds->output = DISPLAY_OUTPUT_RGB;
else
lvds->output = rockchip_lvds_name_to_output(name);
if (lvds->output < 0) {
ret = dev_err_probe(dev, lvds->output, "invalid output type [%s]\n", name);
goto err_put_remote;
}
if (of_property_read_string(remote, "data-mapping", &name))
/* default set it as format vesa 18 */
lvds->format = LVDS_VESA_18;
else
lvds->format = rockchip_lvds_name_to_format(name);
if (lvds->format < 0) {
ret = dev_err_probe(dev, lvds->format,
"invalid data-mapping format [%s]\n", name);
goto err_put_remote;
}
encoder = &lvds->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_LVDS);
if (ret < 0) {
drm_err(drm_dev,
"failed to initialize encoder: %d\n", ret);
goto err_put_remote;
}
drm_encoder_helper_add(encoder, lvds->soc_data->helper_funcs);
if (lvds->panel) {
lvds->bridge = drm_panel_bridge_add_typed(lvds->panel, DRM_MODE_CONNECTOR_LVDS);
if (IS_ERR(lvds->bridge)) {
ret = PTR_ERR(lvds->bridge);
goto err_free_encoder;
}
}
if (lvds->bridge) {
lvds->bridge->driver_private = lvds;
lvds->bridge->ops = DRM_BRIDGE_OP_MODES;
lvds->bridge->funcs = &rockchip_lvds_bridge_funcs;
ret = drm_bridge_attach(encoder, lvds->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
goto err_free_bridge;
connector = drm_bridge_connector_init(lvds->drm_dev, encoder);
if (IS_ERR(connector)) {
drm_err(drm_dev,
"failed to initialize bridge connector: %pe\n",
connector);
ret = PTR_ERR(connector);
goto err_free_bridge;
}
}
pm_runtime_enable(dev);
of_node_put(remote);
of_node_put(port);
return 0;
err_free_bridge:
drm_panel_bridge_remove(lvds->bridge);
err_free_encoder:
drm_encoder_cleanup(encoder);
err_put_remote:
of_node_put(remote);
err_put_port:
of_node_put(port);
return ret;
}
static void rockchip_lvds_unbind(struct device *dev, struct device *master,
void *data)
{
struct rockchip_lvds *lvds = dev_get_drvdata(dev);
const struct drm_encoder_helper_funcs *encoder_funcs;
encoder_funcs = lvds->soc_data->helper_funcs;
encoder_funcs->disable(&lvds->encoder.encoder);
pm_runtime_disable(dev);
}
static const struct component_ops rockchip_lvds_component_ops = {
.bind = rockchip_lvds_bind,
.unbind = rockchip_lvds_unbind,
};
static int rockchip_lvds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rockchip_lvds *lvds;
const struct of_device_id *match;
int ret;
if (!dev->of_node)
return -ENODEV;
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
if (!lvds)
return -ENOMEM;
lvds->dev = dev;
match = of_match_node(rockchip_lvds_dt_ids, dev->of_node);
if (!match)
return -ENODEV;
lvds->soc_data = match->data;
lvds->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(lvds->grf))
return dev_err_probe(dev, PTR_ERR(lvds->grf), "missing rockchip,grf property\n");
ret = lvds->soc_data->probe(pdev, lvds);
if (ret)
return dev_err_probe(dev, ret, "Platform initialization failed\n");
dev_set_drvdata(dev, lvds);
ret = component_add(&pdev->dev, &rockchip_lvds_component_ops);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to add component\n");
return 0;
}
static void rockchip_lvds_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &rockchip_lvds_component_ops);
}
struct platform_driver rockchip_lvds_driver = {
.probe = rockchip_lvds_probe,
.remove = rockchip_lvds_remove,
.driver = {
.name = "rockchip-lvds",
.of_match_table = rockchip_lvds_dt_ids,
},
};
@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:
* Sandy Huang <hjc@rock-chips.com>
* Mark Yao <mark.yao@rock-chips.com>
*/
#ifndef _ROCKCHIP_LVDS_
#define _ROCKCHIP_LVDS_
#include <linux/bits.h>
#include <linux/hw_bitfield.h>
#define RK3288_LVDS_CH0_REG0 0x00
#define RK3288_LVDS_CH0_REG0_LVDS_EN BIT(7)
#define RK3288_LVDS_CH0_REG0_TTL_EN BIT(6)
#define RK3288_LVDS_CH0_REG0_LANECK_EN BIT(5)
#define RK3288_LVDS_CH0_REG0_LANE4_EN BIT(4)
#define RK3288_LVDS_CH0_REG0_LANE3_EN BIT(3)
#define RK3288_LVDS_CH0_REG0_LANE2_EN BIT(2)
#define RK3288_LVDS_CH0_REG0_LANE1_EN BIT(1)
#define RK3288_LVDS_CH0_REG0_LANE0_EN BIT(0)
#define RK3288_LVDS_CH0_REG1 0x04
#define RK3288_LVDS_CH0_REG1_LANECK_BIAS BIT(5)
#define RK3288_LVDS_CH0_REG1_LANE4_BIAS BIT(4)
#define RK3288_LVDS_CH0_REG1_LANE3_BIAS BIT(3)
#define RK3288_LVDS_CH0_REG1_LANE2_BIAS BIT(2)
#define RK3288_LVDS_CH0_REG1_LANE1_BIAS BIT(1)
#define RK3288_LVDS_CH0_REG1_LANE0_BIAS BIT(0)
#define RK3288_LVDS_CH0_REG2 0x08
#define RK3288_LVDS_CH0_REG2_RESERVE_ON BIT(7)
#define RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE BIT(6)
#define RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE BIT(5)
#define RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE BIT(4)
#define RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE BIT(3)
#define RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE BIT(2)
#define RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE BIT(1)
#define RK3288_LVDS_CH0_REG2_PLL_FBDIV8 BIT(0)
#define RK3288_LVDS_CH0_REG3 0x0c
#define RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK 0xff
#define RK3288_LVDS_CH0_REG4 0x10
#define RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE BIT(5)
#define RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE BIT(4)
#define RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE BIT(3)
#define RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE BIT(2)
#define RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE BIT(1)
#define RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE BIT(0)
#define RK3288_LVDS_CH0_REG5 0x14
#define RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA BIT(5)
#define RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA BIT(4)
#define RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA BIT(3)
#define RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA BIT(2)
#define RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA BIT(1)
#define RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA BIT(0)
#define RK3288_LVDS_CFG_REGC 0x30
#define RK3288_LVDS_CFG_REGC_PLL_ENABLE 0x00
#define RK3288_LVDS_CFG_REGC_PLL_DISABLE 0xff
#define RK3288_LVDS_CH0_REGD 0x34
#define RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK 0x1f
#define RK3288_LVDS_CH0_REG20 0x80
#define RK3288_LVDS_CH0_REG20_MSB 0x45
#define RK3288_LVDS_CH0_REG20_LSB 0x44
#define RK3288_LVDS_CFG_REG21 0x84
#define RK3288_LVDS_CFG_REG21_TX_ENABLE 0x92
#define RK3288_LVDS_CFG_REG21_TX_DISABLE 0x00
#define RK3288_LVDS_CH1_OFFSET 0x100
#define RK3288_LVDS_GRF_SOC_CON6 0x025C
#define RK3288_LVDS_GRF_SOC_CON7 0x0260
/* fbdiv value is split over 2 registers, with bit8 in reg2 */
#define RK3288_LVDS_PLL_FBDIV_REG2(_fbd) \
(_fbd & BIT(8) ? RK3288_LVDS_CH0_REG2_PLL_FBDIV8 : 0)
#define RK3288_LVDS_PLL_FBDIV_REG3(_fbd) \
(_fbd & RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK)
#define RK3288_LVDS_PLL_PREDIV_REGD(_pd) \
(_pd & RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK)
#define RK3288_LVDS_SOC_CON6_SEL_VOP_LIT BIT(3)
#define LVDS_FMT_MASK (0x07 << 16)
#define LVDS_MSB BIT(3)
#define LVDS_DUAL BIT(4)
#define LVDS_FMT_1 BIT(5)
#define LVDS_TTL_EN BIT(6)
#define LVDS_START_PHASE_RST_1 BIT(7)
#define LVDS_DCLK_INV BIT(8)
#define LVDS_CH0_EN BIT(11)
#define LVDS_CH1_EN BIT(12)
#define LVDS_PWRDN BIT(15)
#define LVDS_24BIT (0 << 1)
#define LVDS_18BIT (1 << 1)
#define LVDS_FORMAT_VESA (0 << 0)
#define LVDS_FORMAT_JEIDA (1 << 0)
#define LVDS_VESA_24 0
#define LVDS_JEIDA_24 1
#define LVDS_VESA_18 2
#define LVDS_JEIDA_18 3
#define PX30_LVDS_GRF_PD_VO_CON0 0x434
#define PX30_LVDS_TIE_CLKS(val) FIELD_PREP_WM16(BIT(8), (val))
#define PX30_LVDS_INVERT_CLKS(val) FIELD_PREP_WM16(BIT(9), (val))
#define PX30_LVDS_INVERT_DCLK(val) FIELD_PREP_WM16(BIT(5), (val))
#define PX30_LVDS_GRF_PD_VO_CON1 0x438
#define PX30_LVDS_FORMAT(val) FIELD_PREP_WM16(GENMASK(14, 13), (val))
#define PX30_LVDS_MODE_EN(val) FIELD_PREP_WM16(BIT(12), (val))
#define PX30_LVDS_MSBSEL(val) FIELD_PREP_WM16(BIT(11), (val))
#define PX30_LVDS_P2S_EN(val) FIELD_PREP_WM16(BIT(6), (val))
#define PX30_LVDS_VOP_SEL(val) FIELD_PREP_WM16(BIT(1), (val))
#endif /* _ROCKCHIP_LVDS_ */
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:
* Sandy Huang <hjc@rock-chips.com>
*/
#include <linux/component.h>
#include <linux/media-bus-format.h>
#include <linux/of_graph.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#include "rockchip_rgb.h"
struct rockchip_rgb {
struct device *dev;
struct drm_device *drm_dev;
struct drm_bridge *bridge;
struct rockchip_encoder encoder;
struct drm_connector connector;
int output_mode;
};
static int
rockchip_rgb_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);
struct drm_connector *connector = conn_state->connector;
struct drm_display_info *info = &connector->display_info;
u32 bus_format;
if (info->num_bus_formats)
bus_format = info->bus_formats[0];
else
bus_format = MEDIA_BUS_FMT_RGB888_1X24;
switch (bus_format) {
case MEDIA_BUS_FMT_RGB666_1X18:
s->output_mode = ROCKCHIP_OUT_MODE_P666;
break;
case MEDIA_BUS_FMT_RGB565_1X16:
s->output_mode = ROCKCHIP_OUT_MODE_P565;
break;
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
default:
s->output_mode = ROCKCHIP_OUT_MODE_P888;
break;
}
s->output_type = DRM_MODE_CONNECTOR_LVDS;
return 0;
}
static const
struct drm_encoder_helper_funcs rockchip_rgb_encoder_helper_funcs = {
.atomic_check = rockchip_rgb_encoder_atomic_check,
};
struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
struct drm_crtc *crtc,
struct drm_device *drm_dev,
int video_port)
{
struct rockchip_rgb *rgb;
struct drm_encoder *encoder;
struct device_node *port, *endpoint;
u32 endpoint_id;
int ret = 0, child_count = 0;
struct drm_panel *panel;
struct drm_bridge *bridge;
struct drm_connector *connector;
rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
if (!rgb)
return ERR_PTR(-ENOMEM);
rgb->dev = dev;
rgb->drm_dev = drm_dev;
port = of_graph_get_port_by_id(dev->of_node, video_port);
if (!port)
return ERR_PTR(-EINVAL);
for_each_child_of_node(port, endpoint) {
if (of_property_read_u32(endpoint, "reg", &endpoint_id))
endpoint_id = 0;
/* if subdriver (> 0) or error case (< 0), ignore entry */
if (rockchip_drm_endpoint_is_subdriver(endpoint) != 0)
continue;
child_count++;
ret = drm_of_find_panel_or_bridge(dev->of_node, video_port,
endpoint_id, &panel, &bridge);
if (!ret) {
of_node_put(endpoint);
break;
}
}
of_node_put(port);
/* if the rgb output is not connected to anything, just return */
if (!child_count)
return NULL;
if (ret < 0) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "failed to find panel or bridge %d\n", ret);
return ERR_PTR(ret);
}
encoder = &rgb->encoder.encoder;
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_NONE);
if (ret < 0) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize encoder: %d\n", ret);
return ERR_PTR(ret);
}
drm_encoder_helper_add(encoder, &rockchip_rgb_encoder_helper_funcs);
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_LVDS);
if (IS_ERR(bridge))
return ERR_CAST(bridge);
}
rgb->bridge = bridge;
ret = drm_bridge_attach(encoder, rgb->bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
goto err_free_encoder;
connector = &rgb->connector;
connector = drm_bridge_connector_init(rgb->drm_dev, encoder);
if (IS_ERR(connector)) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize bridge connector: %pe\n",
connector);
ret = PTR_ERR(connector);
goto err_free_encoder;
}
rgb->encoder.crtc_endpoint_id = endpoint_id;
return rgb;
err_free_encoder:
drm_encoder_cleanup(encoder);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(rockchip_rgb_init);
void rockchip_rgb_fini(struct rockchip_rgb *rgb)
{
drm_panel_bridge_remove(rgb->bridge);
drm_connector_cleanup(&rgb->connector);
drm_encoder_cleanup(&rgb->encoder.encoder);
}
EXPORT_SYMBOL_GPL(rockchip_rgb_fini);
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author:
* Sandy Huang <hjc@rock-chips.com>
*/
#ifdef CONFIG_ROCKCHIP_RGB
struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
struct drm_crtc *crtc,
struct drm_device *drm_dev,
int video_port);
void rockchip_rgb_fini(struct rockchip_rgb *rgb);
#else
static inline struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
struct drm_crtc *crtc,
struct drm_device *drm_dev,
int video_port)
{
return NULL;
}
static inline void rockchip_rgb_fini(struct rockchip_rgb *rgb)
{
}
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff