From: Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx> This adds support for Cadence MHDP DPI to DP bridge. Basically, it takes a DPI stream as input and output it encoded in DP format. It supports SST and MST modes. Changes made in the low level driver (cdn-dp-reg.*): - moved it to from drivers/gpu/drm/rockchip to drivers/gpu/drm/bridge/cdns-mhdp-common.c and include/drm/bridge/cdns-mhdp-common.h - functions for sending/receiving commands are now public - added functions for reading registers and link training adjustment Changes made in RK's driver (cdn-dp-core.*): - Moved audio_info and audio_pdev fields from cdn_dp_device to cdns_mhdp_device structure. Signed-off-by: Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx> Signed-off-by: Piotr Sroka <piotrs@xxxxxxxxxxx> Signed-off-by: Damian Kos <dkos@xxxxxxxxxxx> --- drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 3 + .../cdns-mhdp-common.c} | 186 ++- drivers/gpu/drm/bridge/cdns-mhdp-mst.c | 549 +++++++ drivers/gpu/drm/bridge/cdns-mhdp.c | 1349 +++++++++++++++++ drivers/gpu/drm/bridge/cdns-mhdp.h | 209 +++ drivers/gpu/drm/rockchip/Kconfig | 4 +- drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.c | 34 +- drivers/gpu/drm/rockchip/cdn-dp-core.h | 5 +- include/drm/bridge/cdns-mhdp-cbs.h | 29 + .../drm/bridge/cdns-mhdp-common.h | 68 +- 12 files changed, 2384 insertions(+), 63 deletions(-) rename drivers/gpu/drm/{rockchip/cdn-dp-reg.c => bridge/cdns-mhdp-common.c} (85%) create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-mst.c create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp.c create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp.h create mode 100644 include/drm/bridge/cdns-mhdp-cbs.h rename drivers/gpu/drm/rockchip/cdn-dp-reg.h => include/drm/bridge/cdns-mhdp-common.h (91%) diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 2fee47b0d50b..2b7c568756fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -35,6 +35,15 @@ config DRM_CDNS_DSI Support Cadence DPI to DSI bridge. This is an internal bridge and is meant to be directly embedded in a SoC. +config DRM_CDNS_MHDP + tristate "Cadence DPI/DP bridge" + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + depends on OF + help + Support Cadence DPI to DP bridge. This is an internal + bridge and is meant to be directly embedded in a SoC. + config DRM_DUMB_VGA_DAC tristate "Dumb VGA DAC Bridge support" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 4934fcf5a6f8..b80f3d6ed2a6 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,4 +16,7 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o +obj-$(CONFIG_DRM_CDNS_MHDP) += mhdp8546.o obj-y += synopsys/ + +mhdp8546-objs := cdns-mhdp-common.o cdns-mhdp.o cdns-mhdp-mst.o diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/bridge/cdns-mhdp-common.c similarity index 85% rename from drivers/gpu/drm/rockchip/cdn-dp-reg.c rename to drivers/gpu/drm/bridge/cdns-mhdp-common.c index a32ee26fd36b..f0160df52aed 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-reg.c +++ b/drivers/gpu/drm/bridge/cdns-mhdp-common.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd * Author: Chris Zhong <zyw@xxxxxxxxxxxxxx> @@ -13,14 +14,17 @@ */ #include <linux/clk.h> -#include <linux/device.h> #include <linux/delay.h> +#include <linux/device.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/reset.h> -#include "cdn-dp-core.h" -#include "cdn-dp-reg.h" +#include <asm/unaligned.h> + +#include <drm/bridge/cdns-mhdp-common.h> +#include <drm/drm_modes.h> +#include <drm/drm_print.h> #define CDNS_DP_SPDIF_CLK 200000000 #define FW_ALIVE_TIMEOUT_US 1000000 @@ -29,10 +33,27 @@ #define LINK_TRAINING_RETRY_MS 20 #define LINK_TRAINING_TIMEOUT_MS 500 +static inline u32 get_unaligned_be24(const void *p) +{ + const u8 *_p = p; + + return _p[0] << 16 | _p[1] << 8 | _p[2]; +} + +static inline void put_unaligned_be24(u32 val, void *p) +{ + u8 *_p = p; + + _p[0] = val >> 16; + _p[1] = val >> 8; + _p[2] = val; +} + void cdns_mhdp_set_fw_clk(struct cdns_mhdp_device *mhdp, unsigned long clk) { writel(clk / 1000000, mhdp->regs + SW_CLK_H); } +EXPORT_SYMBOL(cdns_mhdp_set_fw_clk); void cdns_mhdp_clock_reset(struct cdns_mhdp_device *mhdp) { @@ -82,6 +103,7 @@ void cdns_mhdp_clock_reset(struct cdns_mhdp_device *mhdp) /* enable Mailbox and PIF interrupt */ writel(0, mhdp->regs + APB_INT_MASK); } +EXPORT_SYMBOL(cdns_mhdp_clock_reset); static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp) { @@ -128,7 +150,7 @@ static int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp, header[i] = ret; } - mbox_size = (header[2] << 8) | header[3]; + mbox_size = get_unaligned_be16(header + 2); if (opcode != header[0] || module_id != header[1] || req_size != mbox_size) { @@ -171,8 +193,7 @@ static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id, header[0] = opcode; header[1] = module_id; - header[2] = (size >> 8) & 0xff; - header[3] = size & 0xff; + put_unaligned_be16(size, header + 2); for (i = 0; i < 4; i++) { ret = cdp_dp_mailbox_write(mhdp, header[i]); @@ -189,37 +210,78 @@ static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id, return 0; } -static int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val) +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value) +{ + u8 msg[4], resp[8]; + int ret; + + if (addr == 0) { + ret = -EINVAL; + goto err_reg_read; + } + + put_unaligned_be32(addr, msg); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_REGISTER_READ, + sizeof(msg), msg); + if (ret) + goto err_reg_read; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_REGISTER_READ, + sizeof(resp)); + if (ret) + goto err_reg_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp)); + if (ret) + goto err_reg_read; + + /* Returned address value should be the same as requested */ + if (memcmp(msg, resp, sizeof(msg))) { + ret = -EINVAL; + goto err_reg_read; + } + + *value = get_unaligned_be32(resp + 4); + +err_reg_read: + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n"); + *value = 0; + } + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_reg_read); + +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, 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; + put_unaligned_be16(addr, msg); + put_unaligned_be32(val, msg + 2); + return cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER, sizeof(msg), msg); } +EXPORT_SYMBOL(cdns_mhdp_reg_write); -static int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr, +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr, u8 start_bit, u8 bits_no, u32 val) { u8 field[8]; - field[0] = (addr >> 8) & 0xff; - field[1] = addr & 0xff; + put_unaligned_be16(addr, field); 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; + put_unaligned_be32(val, field + 4); return cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD, sizeof(field), field); } +EXPORT_SYMBOL(cdns_mhdp_reg_write_bit); int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp, u32 addr, u8 *data, u16 len) @@ -227,11 +289,9 @@ int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp, 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; + put_unaligned_be16(len, msg); + put_unaligned_be24(addr, msg + 2); + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD, sizeof(msg), msg); if (ret) @@ -252,18 +312,17 @@ int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp, err_dpcd_read: return ret; } +EXPORT_SYMBOL(cdns_mhdp_dpcd_read); int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, 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; + put_unaligned_be16(1, msg); + put_unaligned_be24(addr, msg + 2); msg[5] = value; + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD, sizeof(msg), msg); if (ret) @@ -278,7 +337,7 @@ int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value) if (ret) goto err_dpcd_write; - if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4])) + if (addr != get_unaligned_be24(reg + 2)) ret = -EINVAL; err_dpcd_write: @@ -286,6 +345,7 @@ int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value) DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret); return ret; } +EXPORT_SYMBOL(cdns_mhdp_dpcd_write); int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp, const u32 *i_mem, u32 i_size, const u32 *d_mem, u32 d_size) @@ -328,6 +388,7 @@ int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp, const u32 *i_mem, return 0; } +EXPORT_SYMBOL(cdns_mhdp_load_firmware); int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable) { @@ -362,6 +423,7 @@ int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable) DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n"); return ret; } +EXPORT_SYMBOL(cdns_mhdp_set_firmware_active); int cdns_mhdp_set_host_cap(struct cdns_mhdp_device *mhdp, u8 lanes, bool flip) { @@ -391,6 +453,7 @@ int cdns_mhdp_set_host_cap(struct cdns_mhdp_device *mhdp, u8 lanes, bool flip) DRM_DEV_ERROR(mhdp->dev, "set host cap failed: %d\n", ret); return ret; } +EXPORT_SYMBOL(cdns_mhdp_set_host_cap); int cdns_mhdp_event_config(struct cdns_mhdp_device *mhdp) { @@ -408,11 +471,13 @@ int cdns_mhdp_event_config(struct cdns_mhdp_device *mhdp) return ret; } +EXPORT_SYMBOL(cdns_mhdp_event_config); u32 cdns_mhdp_get_event(struct cdns_mhdp_device *mhdp) { return readl(mhdp->regs + SW_EVENTS0); } +EXPORT_SYMBOL(cdns_mhdp_get_event); int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp) { @@ -440,6 +505,7 @@ int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp) DRM_DEV_ERROR(mhdp->dev, "get hpd status failed: %d\n", ret); return ret; } +EXPORT_SYMBOL(cdns_mhdp_get_hpd_status); int cdns_mhdp_get_edid_block(void *data, u8 *edid, unsigned int block, size_t length) @@ -482,6 +548,7 @@ int cdns_mhdp_get_edid_block(void *data, u8 *edid, return ret; } +EXPORT_SYMBOL(cdns_mhdp_get_edid_block); static int cdns_mhdp_training_start(struct cdns_mhdp_device *mhdp) { @@ -580,6 +647,7 @@ int cdns_mhdp_train_link(struct cdns_mhdp_device *mhdp) mhdp->link.num_lanes); return ret; } +EXPORT_SYMBOL(cdns_mhdp_train_link); int cdns_mhdp_set_video_status(struct cdns_mhdp_device *mhdp, int active) { @@ -595,6 +663,7 @@ int cdns_mhdp_set_video_status(struct cdns_mhdp_device *mhdp, int active) return ret; } +EXPORT_SYMBOL(cdns_mhdp_set_video_status); static int cdns_mhdp_get_msa_misc(struct video_info *video, struct drm_display_mode *mode) @@ -797,6 +866,7 @@ int cdns_mhdp_config_video(struct cdns_mhdp_device *mhdp) DRM_DEV_ERROR(mhdp->dev, "config video failed: %d\n", ret); return ret; } +EXPORT_SYMBOL(cdns_mhdp_config_video); int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp, struct audio_info *audio) @@ -831,6 +901,7 @@ int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp, return 0; } +EXPORT_SYMBOL(cdns_mhdp_audio_stop); int cdns_mhdp_audio_mute(struct cdns_mhdp_device *mhdp, bool enable) { @@ -842,6 +913,7 @@ int cdns_mhdp_audio_mute(struct cdns_mhdp_device *mhdp, bool enable) return ret; } +EXPORT_SYMBOL(cdns_mhdp_audio_mute); static void cdns_mhdp_audio_config_i2s(struct cdns_mhdp_device *mhdp, struct audio_info *audio) @@ -977,3 +1049,55 @@ int cdns_mhdp_audio_config(struct cdns_mhdp_device *mhdp, DRM_DEV_ERROR(mhdp->dev, "audio config failed: %d\n", ret); return ret; } +EXPORT_SYMBOL(cdns_mhdp_audio_config); + +int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp, + u8 nlanes, u16 udelay, u8 *lanes_data, u8 *dpcd) +{ + u8 payload[7]; + u8 hdr[5]; /* For DPCD read response header */ + u32 addr; + u8 const nregs = 6; /* Registers 0x202-0x207 */ + int ret; + + if (nlanes != 4 && nlanes != 2 && nlanes != 1) { + DRM_DEV_ERROR(mhdp->dev, "invalid number of lanes: %d\n", + nlanes); + ret = -EINVAL; + goto err_adjust_lt; + } + + payload[0] = nlanes; + put_unaligned_be16(udelay, payload + 1); + memcpy(payload + 3, lanes_data, nlanes); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_ADJUST_LT, + sizeof(payload), payload); + if (ret) + goto err_adjust_lt; + + /* Yes, read the DPCD read command response */ + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, + sizeof(hdr) + nregs); + if (ret) + goto err_adjust_lt; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, hdr, sizeof(hdr)); + if (ret) + goto err_adjust_lt; + + addr = get_unaligned_be24(hdr + 2); + if (addr != DP_LANE0_1_STATUS) + goto err_adjust_lt; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, dpcd, nregs); + +err_adjust_lt: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "Failed to adjust Link Training.\n"); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_adjust_lt); diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-mst.c b/drivers/gpu/drm/bridge/cdns-mhdp-mst.c new file mode 100644 index 000000000000..628e26cb561f --- /dev/null +++ b/drivers/gpu/drm/bridge/cdns-mhdp-mst.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence MHDP DP MST bridge driver. + * + * Copyright: 2018 Cadence Design Systems, Inc. + * + * Author: Piotr Sroka <piotrs@xxxxxxxxxxx> + */ +#include <drm/drm_atomic_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fixed.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <linux/iopoll.h> + +#include <drm/bridge/cdns-mhdp-common.h> +#include "cdns-mhdp.h" + + +static void cdns_mhdp_mst_stream_enable(struct cdns_mhdp_bridge *mhdp_bridge, + const bool enable) +{ + u32 reg; + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + const u8 stream_id = mhdp_bridge->stream_id; + + cdns_mhdp_reg_read(mhdp, CDNS_DP_MST_STREAM_CONFIG(stream_id), ®); + + if (enable) { + reg |= CDNS_DP_MST_STRM_CFG_STREAM_EN; + reg &= ~CDNS_DP_MST_STRM_CFG_NO_VIDEO; + } else { + reg &= ~CDNS_DP_MST_STRM_CFG_STREAM_EN; + reg |= CDNS_DP_MST_STRM_CFG_NO_VIDEO; + } + + cdns_mhdp_reg_write(mhdp, CDNS_DP_MST_STREAM_CONFIG(stream_id), reg); +} + +static inline s64 calc_fixed_avg_slots(const u32 pbn, const u32 pbn_div) +{ + s64 fixed_pbn, fixed_pbn_div, fixed_targ_avg_slots; + + fixed_pbn = drm_int2fixp(pbn); + fixed_pbn_div = drm_int2fixp(pbn_div); + fixed_targ_avg_slots = drm_fixp_div(fixed_pbn, fixed_pbn_div); + + return fixed_targ_avg_slots; +} + +static void cdns_mhdp_set_rate_governing(struct cdns_mhdp_bridge *bridge, + const bool enable) +{ + struct cdns_mhdp_device *mhdp = bridge->mhdp; + const u8 stream_id = bridge->stream_id; + s64 fixed_targ_avg_slots, fixed_y; + u32 x, y; + + if (!enable) { + cdns_mhdp_reg_write(mhdp, CDNS_DP_RATE_GOVERNING(stream_id), 0); + return; + } + + fixed_targ_avg_slots = calc_fixed_avg_slots(bridge->pbn, + mhdp->mst_mgr.pbn_div); + x = bridge->pbn / mhdp->mst_mgr.pbn_div; + + fixed_y = fixed_targ_avg_slots - drm_int2fixp(x); + fixed_y *= 16; + + y = drm_fixp2int_ceil(fixed_y); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_RATE_GOVERNING(stream_id), + CDNS_DP_RG_TARG_AV_SLOTS_Y(y) | + CDNS_DP_RG_TARG_AV_SLOTS_X(x) | + CDNS_DP_RG_ENABLE); +} + + +static struct drm_dp_payload * +cdns_mhdp_get_payload(struct cdns_mhdp_bridge *bridge) +{ + const int vcpi = bridge->connector->port->vcpi.vcpi; + struct cdns_mhdp_device *mhdp = bridge->mhdp; + int i; + + for (i = 0; i < mhdp->mst_mgr.max_payloads; i++) { + struct drm_dp_payload *payload = &mhdp->mst_mgr.payloads[i]; + + if (payload->vcpi == vcpi) + return payload; + } + + return NULL; +} + + +static int +cdns_mhdp_set_act_enable(struct cdns_mhdp_device *mhdp) +{ + u32 reg; + int ret; + + cdns_mhdp_reg_read(mhdp, CDNS_DP_MTPH_CONTROL, ®); + cdns_mhdp_reg_write(mhdp, CDNS_DP_MTPH_CONTROL, + reg | CDNS_DP_MTPH_ACT_EN); + + ret = readl_poll_timeout(mhdp->regs + CDNS_DP_MTPH_STATUS, reg, + ((reg & CDNS_DP_MTPH_ACT_STATUS) == 0), 0, + 30); + if (ret) { + dev_err(mhdp->dev, + "ACT sequence cannot complete in 30us\n"); + return -EIO; + } + + return drm_dp_check_act_status(&mhdp->mst_mgr); +} + + +static int +cdns_mhdp_apply_slot_allocation(struct cdns_mhdp_bridge *mhdp_bridge) +{ + struct drm_dp_payload *payload; + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + u8 stream_id = mhdp_bridge->stream_id; + + payload = cdns_mhdp_get_payload(mhdp_bridge); + + if (!payload) { + DRM_ERROR("payload is not found\n"); + return -EIO; + } + + cdns_mhdp_reg_write(mhdp, CDNS_DP_MST_SLOT_ALLOCATE(stream_id), + CDNS_DP_S_ALLOC_START_SLOT(payload->start_slot) | + CDNS_DP_S_ALLOC_END_SLOT(payload->start_slot + + payload->num_slots - 1)); + + return 0; +} + +static void +cdns_mhdp_update_slot_allocation(struct cdns_mhdp_bridge *mhdp_bridge) +{ + struct drm_device *dev = mhdp_bridge->base.dev; + struct drm_connector *connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + struct cdns_mhdp_connector *mhdp_connector; + + if (!connector->encoder) + continue; + + mhdp_connector = to_mhdp_connector(connector); + if (!mhdp_connector->is_mst_connector) + continue; + + if (mhdp_connector->bridge->stream_id != -1) + cdns_mhdp_apply_slot_allocation(mhdp_connector->bridge); + } +} + +static enum drm_connector_status +cdns_dp_mst_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status stat; + struct cdns_mhdp_connector *mhdp_connector; + struct cdns_mhdp_device *mhdp; + + mhdp_connector = to_mhdp_connector(connector); + mhdp = mhdp_connector->bridge->mhdp; + + stat = drm_dp_mst_detect_port(connector, &mhdp->mst_mgr, + mhdp_connector->port); + return stat; +} + +static void cdns_dp_mst_connector_destroy(struct drm_connector *connector) +{ + struct cdns_mhdp_connector *mhdp_connector; + struct cdns_mhdp_bridge *mhdp_bridge; + + mhdp_connector = to_mhdp_connector(connector); + mhdp_bridge = mhdp_connector->bridge; + + drm_connector_cleanup(&mhdp_connector->base); + drm_bridge_remove(&mhdp_bridge->base); + kfree(mhdp_connector); + kfree(mhdp_bridge); +} + +static const struct drm_connector_funcs cdns_mhdp_mst_connector_funcs = { + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .reset = drm_atomic_helper_connector_reset, + .dpms = drm_helper_connector_dpms, + .detect = cdns_dp_mst_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = cdns_dp_mst_connector_destroy, +}; + +static int cdns_mhdp_mst_get_ddc_modes(struct drm_connector *connector) +{ + struct cdns_mhdp_connector *mhdp_connector; + struct cdns_mhdp_device *mhdp; + struct edid *edid; + int num_modes = 0; + + mhdp_connector = to_mhdp_connector(connector); + mhdp = mhdp_connector->bridge->mhdp; + + edid = drm_dp_mst_get_edid(connector, &mhdp->mst_mgr, + mhdp_connector->port); + if (edid) { + DRM_DEBUG_KMS("edid retrieved %p\n", edid); + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + } else + drm_connector_update_edid_property(connector, NULL); + + return num_modes; +} + +static int cdns_mhdp_mst_get_modes(struct drm_connector *connector) +{ + return cdns_mhdp_mst_get_ddc_modes(connector); +} + +static struct +drm_encoder *cdns_mhdp_mst_best_encoder(struct drm_connector *connector) +{ + struct cdns_mhdp_connector *mhdp_connector; + + mhdp_connector = to_mhdp_connector(connector); + + return mhdp_connector->bridge->base.encoder; +} + +static const struct drm_connector_helper_funcs cdns_mhdp_mst_conn_helper_fun = { + .get_modes = cdns_mhdp_mst_get_modes, + .best_encoder = cdns_mhdp_mst_best_encoder, +}; + +void cdns_mhdp_mst_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + struct drm_display_info *disp_info; + struct drm_display_mode *mode; + struct cdns_mhdp_connector *mhdp_connector; + u32 bpp; + enum pixel_format pxlfmt; + int ret, slots, stream_id; + + disp_info = &mhdp_bridge->connector->base.display_info; + + pxlfmt = cdns_mhdp_get_pxlfmt(disp_info->color_formats); + bpp = cdns_mhdp_get_bpp(disp_info->bpc, pxlfmt); + + mhdp_connector = mhdp_bridge->connector; + if (mhdp_bridge->stream_id > -1) { + DRM_ERROR("stream id is attached before bridge is enabled\n"); + return; + } + + stream_id = bridge->encoder->crtc->index; + + mode = &bridge->encoder->crtc->state->adjusted_mode; + mhdp_bridge->pbn = drm_dp_calc_pbn_mode(mode->clock, bpp); + + slots = drm_dp_find_vcpi_slots(&mhdp->mst_mgr, mhdp_bridge->pbn); + ret = drm_dp_mst_allocate_vcpi(&mhdp->mst_mgr, + mhdp_connector->port, + mhdp_bridge->pbn, slots); + if (ret == false) { + DRM_ERROR("failed to allocate vcpi\n"); + return; + } + ret = drm_dp_update_payload_part1(&mhdp->mst_mgr); + if (ret < 0) + DRM_ERROR("failed update_payload_part1\n"); + + mhdp_bridge->stream_id = stream_id; + mhdp_bridge->is_active = true; + + cdns_mhdp_mst_stream_enable(mhdp_bridge, true); + cdns_mhdp_configure_video(bridge); + + ret = cdns_mhdp_apply_slot_allocation(mhdp_bridge); + if (ret < 0) { + cdns_mhdp_mst_stream_enable(mhdp_bridge, false); + mhdp_bridge->stream_id = -1; + mhdp_bridge->is_active = false; + return; + } + + ret = cdns_mhdp_set_act_enable(mhdp); + if (ret) + DRM_ERROR("failed ACT sequence\n"); + + cdns_mhdp_set_rate_governing(mhdp_bridge, true); + + drm_dp_update_payload_part2(&mhdp->mst_mgr); +} + +void cdns_mhdp_mst_disable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + struct cdns_mhdp_connector *connector = mhdp_bridge->connector; + + drm_dp_mst_reset_vcpi_slots(&mhdp->mst_mgr, connector->port); + drm_dp_update_payload_part1(&mhdp->mst_mgr); + + cdns_mhdp_update_slot_allocation(mhdp_bridge); + + drm_dp_check_act_status(&mhdp->mst_mgr); + + drm_dp_update_payload_part2(&mhdp->mst_mgr); + + drm_dp_mst_deallocate_vcpi(&mhdp->mst_mgr, connector->port); + + cdns_mhdp_set_rate_governing(mhdp_bridge, false); + cdns_mhdp_mst_stream_enable(mhdp_bridge, false); + mhdp_bridge->stream_id = -1; + mhdp_bridge->is_active = false; +} + +static const struct drm_bridge_funcs cdns_mhdp_mst_bridge_funcs = { + .enable = cdns_mhdp_enable, + .disable = cdns_mhdp_mst_disable, +}; + + +static struct cdns_mhdp_bridge* +cdns_mhpd_create_fake_mst_bridge(struct cdns_mhdp_device *mhdp, + struct cdns_mhdp_connector *mhdp_connector) +{ + struct cdns_mhdp_bridge *mhdp_bridge; + struct drm_encoder *encoder = NULL; + struct cdns_mhdp_mst_cbs *cbs = &mhdp->cbs; + + mhdp_bridge = kzalloc(sizeof(*mhdp_bridge), GFP_KERNEL); + if (!mhdp_bridge) + return NULL; + + mhdp_bridge->mhdp = mhdp; + mhdp_bridge->stream_id = -1; + mhdp_bridge->connector = mhdp_connector; + mhdp_bridge->is_active = false; + + mhdp_bridge->base.funcs = &cdns_mhdp_mst_bridge_funcs; + + drm_bridge_add(&mhdp_bridge->base); + + if (cbs->funcs.create_mst_encoder) + encoder = cbs->funcs.create_mst_encoder(cbs->priv_data, + &mhdp_bridge->base); + if (encoder) { + int ret; + /* use the same drm device as is in the first encoder */ + encoder->dev = mhdp->bridge.base.encoder->dev; + encoder->possible_crtcs &= ((1 << CDNS_MHDP_MAX_STREAMS) - 1); + ret = drm_bridge_attach(encoder, &mhdp_bridge->base, NULL); + if (ret) { + dev_err(mhdp->dev, "bridge attaching error %d\n", ret); + return NULL; + } + + ret = drm_connector_attach_encoder(&mhdp_connector->base, + encoder); + if (ret) { + dev_err(mhdp->dev, "failed to attach connector to encoder\n"); + return NULL; + } + } + + return mhdp_bridge; +} + +static struct drm_connector * +cdns_mhdp_mst_cbs_add_connector(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + const char *pathprop) +{ + struct cdns_mhdp_device *mhdp = mgr_to_mhdp(mgr); + struct drm_device *dev = mhdp->bridge.base.dev; + struct cdns_mhdp_connector *mhdp_connector; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + int ret; + + mhdp_connector = kzalloc(sizeof(struct cdns_mhdp_connector), + GFP_KERNEL); + if (!mhdp_connector) + return NULL; + + mhdp_connector->is_mst_connector = true; + connector = &mhdp_connector->base; + mhdp_connector->port = port; + DRM_DEBUG_KMS("\n"); + + conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL); + if (!conn_state) + return NULL; + + __drm_atomic_helper_connector_reset(connector, + conn_state); + + drm_connector_init(dev, connector, &cdns_mhdp_mst_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + drm_connector_helper_add(connector, &cdns_mhdp_mst_conn_helper_fun); + mhdp_connector->bridge = + cdns_mhpd_create_fake_mst_bridge(mhdp, mhdp_connector); + + drm_object_attach_property(&connector->base, + dev->mode_config.path_property, 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tile_property, 0); + ret = drm_connector_set_path_property(connector, pathprop); + + if (ret) + DRM_ERROR("set path propertty failed\n"); + + return connector; +} + +static void +cdns_mhdp_mst_cbs_destroy_connector(struct drm_dp_mst_topology_mgr *mgr, + struct drm_connector *connector) +{ + struct cdns_mhdp_connector *mhdp_connector; + struct cdns_mhdp_device *mhdp; + struct cdns_mhdp_bridge *mhdp_bridge; + + mhdp_connector = to_mhdp_connector(connector); + mhdp_bridge = mhdp_connector->bridge; + mhdp = mhdp_bridge->mhdp; + + drm_connector_unregister(&mhdp_connector->base); + + if (mhdp->cbs.funcs.create_mst_encoder) + mhdp->cbs.funcs.destroy_mst_encoder(mhdp->cbs.priv_data, + &mhdp_bridge->base); + drm_connector_put(&mhdp_connector->base); +} + +static void +cdns_mhdp_mst_cbs_register_connector(struct drm_connector *connector) +{ + int ret; + + ret = drm_connector_register(connector); + if (ret) + DRM_ERROR("Register connector failed\n"); + +} + +static const struct drm_dp_mst_topology_cbs mst_cbs = { + .add_connector = cdns_mhdp_mst_cbs_add_connector, + .register_connector = cdns_mhdp_mst_cbs_register_connector, + .destroy_connector = cdns_mhdp_mst_cbs_destroy_connector, +}; + +static void cdns_mhdp_set_mst_enable(struct cdns_mhdp_device *mhdp, bool enable) +{ + u32 reg_val; + + cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, ®_val); + + if (enable) + reg_val |= CDNS_DP_MST_EN; + else + reg_val &= ~CDNS_DP_MST_EN; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg_val); +} + +bool cdns_mhdp_mst_probe(struct cdns_mhdp_device *mhdp) +{ + u8 mstm_cap; + u8 dpcd_cap[DP_RECEIVER_CAP_SIZE]; + + bool is_mst; + + if (!mhdp->can_mst) + return false; + + drm_dp_dpcd_read(&mhdp->aux, DP_DPCD_REV, dpcd_cap, + DP_RECEIVER_CAP_SIZE); + + if (dpcd_cap[DP_DPCD_REV] < 0x12) + return false; + + if (drm_dp_dpcd_readb(&mhdp->aux, DP_MSTM_CAP, &mstm_cap) != 1) + return false; + + if (mstm_cap & DP_MST_CAP) { + DRM_DEBUG_KMS("Sink is MST capable\n"); + is_mst = true; + } else { + DRM_DEBUG_KMS("Sink is not MST capable\n"); + is_mst = false; + } + + if (is_mst != mhdp->is_mst) { + mhdp->is_mst = is_mst; + cdns_mhdp_set_mst_enable(mhdp, mhdp->is_mst); + + drm_dp_mst_topology_mgr_set_mst(&mhdp->mst_mgr, + mhdp->is_mst); + } + + return mhdp->is_mst; +} + +int cdns_mhdp_mst_init(struct cdns_mhdp_device *mhdp) +{ + struct cdns_mhdp_bridge *bridge = &mhdp->bridge; + struct drm_device *dev = bridge->base.dev; + struct cdns_mhdp_connector *connector = bridge->connector; + int ret; + + mhdp->mst_mgr.cbs = &mst_cbs; + ret = drm_dp_mst_topology_mgr_init(&mhdp->mst_mgr, dev, + &mhdp->aux, 16, + CDNS_MHDP_MAX_STREAMS, + connector->base.base.id); + mhdp->can_mst = ret ? false : true; + mhdp->is_mst = false; + bridge->stream_id = -1; + + return ret; +} + +void cdns_mhdp_mst_deinit(struct cdns_mhdp_device *mhdp) +{ + if (mhdp->is_mst) { + mhdp->is_mst = false; + drm_dp_mst_topology_mgr_set_mst(&mhdp->mst_mgr, + mhdp->is_mst); + } + + if (mhdp->can_mst) + drm_dp_mst_topology_mgr_destroy(&mhdp->mst_mgr); +} diff --git a/drivers/gpu/drm/bridge/cdns-mhdp.c b/drivers/gpu/drm/bridge/cdns-mhdp.c new file mode 100644 index 000000000000..5fe1d7fcc596 --- /dev/null +++ b/drivers/gpu/drm/bridge/cdns-mhdp.c @@ -0,0 +1,1349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence MHDP DP bridge driver. + * + * Copyright: 2018 Cadence Design Systems, Inc. + * + * Author: Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/phy/phy.h> + +#include <drm/bridge/cdns-mhdp-common.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> + +#include <sound/hdmi-codec.h> +#include <linux/irq.h> +#include <linux/of_irq.h> + +#include "cdns-mhdp.h" + +#define FW_NAME "cadence/mhdp8546.bin" +#define CDNS_MHDP_IMEM 0x10000 + +#define CDNS_DP_TRAINING_PATTERN_4 0x7 + +#define CDNS_KEEP_ALIVE_TIMEOUT 2000 + +static const struct of_device_id mhdp_ids[] = { + { .compatible = "cdns,mhdp8546", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mhdp_ids); + +#define CDNS_LANE_1 BIT(0) +#define CDNS_LANE_2 BIT(1) +#define CDNS_LANE_4 BIT(2) +#define CDNS_SSC BIT(3) +#define CDNS_SCRAMBLER BIT(4) + +#define CDNS_VOLT_SWING(x) ((x) & GENMASK(1, 0)) +#define CDNS_FORCE_VOLT_SWING BIT(2) + +#define CDNS_PRE_EMPHASIS(x) ((x) & GENMASK(1, 0)) +#define CDNS_FORCE_PRE_EMPHASIS BIT(2) + +#define CDNS_SUPPORT_TPS(x) BIT((x) - 1) + +#define CDNS_FAST_LINK_TRAINING BIT(0) + +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x) ((x) & GENMASK(1, 0)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x) ((x) & GENMASK(3, 2)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x) ((x) & GENMASK(5, 4)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x) ((x) & GENMASK(7, 6)) +#define CDNS_LANE_MAPPING_NORMAL 0xe4 +#define CDNS_LANE_MAPPING_FLIPPED 0x1b + +#define CDNS_DP_MAX_NUM_LANES 4 +#define CDNS_DP_TEST_VSC_SDP (1 << 6) /* 1.3+ */ +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY (1 << 7) + +static inline struct cdns_mhdp_device *connector_to_mhdp( + struct drm_connector *conn) +{ + struct cdns_mhdp_connector *mhdp_connector = to_mhdp_connector(conn); + + return mhdp_connector->bridge->mhdp; +} + +static inline struct cdns_mhdp_device *bridge_to_mhdp( + struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + + return mhdp_bridge->mhdp; +} + +static unsigned int max_link_rate(struct cdns_mhdp_host host, + struct cdns_mhdp_sink sink) +{ + return min(host.link_rate, sink.link_rate); +} + +static u8 eq_training_pattern_supported(struct cdns_mhdp_host host, + struct cdns_mhdp_sink sink) +{ + return fls(host.pattern_supp & sink.pattern_supp); +} + +static void mhdp_hotplug_work_func(struct work_struct *work) +{ + struct cdns_mhdp_device *mhdp; + int dret, retry; + u8 esi[16] = { 0 }; + bool new_irq_handled; + struct drm_connector *connector; + bool old_plugged; + + mhdp = container_of(work, struct cdns_mhdp_device, + hotplug_work.work); + connector = &mhdp->connector.base; + + old_plugged = mhdp->plugged; + connector->status = connector->funcs->detect(connector, false); + + if (old_plugged != mhdp->plugged) { + drm_kms_helper_hotplug_event(mhdp->bridge.base.dev); + return; + } + + if (!mhdp->plugged) + return; + + dret = drm_dp_dpcd_read(&mhdp->aux, + DP_SINK_COUNT_ESI, esi, 8); + if (dret == 8) { + drm_dp_mst_hpd_irq(&mhdp->mst_mgr, + esi, + &new_irq_handled); + } + + if (!new_irq_handled) + return; + + for (retry = 0; retry < 3; retry++) { + int wret; + + wret = drm_dp_dpcd_write(&mhdp->aux, + DP_SINK_COUNT_ESI+1, + &esi[1], 3); + if (wret == 3) + break; + } +} + +static irqreturn_t mhdp_irq_handler(int irq, void *data) +{ + struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data; + + if (readl(mhdp->regs + CDNS_SW_EVENT0) & CDNS_DPTX_HPD) + schedule_delayed_work(&mhdp->hotplug_work, 0); + + return IRQ_HANDLED; +} + +static ssize_t mhdp_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev); + int ret; + + if (msg->request != DP_AUX_NATIVE_WRITE && + msg->request != DP_AUX_NATIVE_READ) + return -ENOTSUPP; + + if (msg->request == DP_AUX_NATIVE_WRITE) { + const u8 *buf = msg->buffer; + int i; + + for (i = 0; i < msg->size; ++i) { + ret = cdns_mhdp_dpcd_write(mhdp, + msg->address + i, buf[i]); + if (!ret) + continue; + + DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n"); + + return ret; + } + } else { + ret = cdns_mhdp_dpcd_read(mhdp, msg->address, + msg->buffer, msg->size); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to read DPCD\n"); + + return ret; + } + } + + return msg->size; +} + +static int cdns_mhdp_get_modes(struct drm_connector *connector) +{ + struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector); + struct edid *edid; + int num_modes; + + edid = drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp); + if (!edid) { + DRM_DEV_ERROR(mhdp->dev, "Failed to read EDID\n"); + + return 0; + } + + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + + return num_modes; +} + +static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = { + .get_modes = cdns_mhdp_get_modes, +}; + +static enum drm_connector_status cdns_mhdp_detect(struct drm_connector *conn, + bool force) +{ + struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn); + int ret; + + ret = cdns_mhdp_get_hpd_status(mhdp); + if (ret > 0) { + mhdp->plugged = true; + cdns_mhdp_mst_probe(mhdp); + // for MST mode this connector will be always disconnected + if (!mhdp->is_mst) + return connector_status_connected; + } + if (ret < 0) + dev_err(mhdp->dev, "Failed to obtain HPD state\n"); + + mhdp->plugged = false; + + return connector_status_disconnected; +} + +static const struct drm_connector_funcs cdns_mhdp_conn_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .reset = drm_atomic_helper_connector_reset, + .destroy = drm_connector_cleanup, + .dpms = drm_helper_connector_dpms, + .detect = cdns_mhdp_detect, +}; + +static int cdns_mhdp_attach(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + struct drm_connector *conn = &mhdp->connector.base; + int ret; + + if (&mhdp->bridge.base != bridge) + return -ENODEV; + + cdns_mhdp_mst_init(mhdp); + + conn->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + dev_err(mhdp->dev, "failed to init connector\n"); + return ret; + } + + drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs); + + ret = drm_connector_attach_encoder(conn, bridge->encoder); + if (ret) { + dev_err(mhdp->dev, "failed to attach connector to encoder\n"); + return ret; + } + + /* enable interrupts */ + writel(~CDNS_APB_INT_MASK_SW_EVENT_INT, mhdp->regs + CDNS_APB_INT_MASK); + + return 0; +} + +static void mhdp_link_training_init(struct cdns_mhdp_device *mhdp) +{ + u32 reg32; + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + /* Reset PHY configuration */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!(mhdp->host.lanes_cnt & CDNS_SCRAMBLER)) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD, + mhdp->sink.enhanced & mhdp->host.enhanced); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN, + CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes)); + + drm_dp_link_configure(&mhdp->aux, &mhdp->link); + + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, + CDNS_PHY_COMMON_CONFIG | + CDNS_PHY_TRAINING_EN | + CDNS_PHY_TRAINING_TYPE(1) | + CDNS_PHY_SCRAMBLER_BYPASS); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE); +} + +static void mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE], + u8 lanes_data[CDNS_DP_MAX_NUM_LANES]) +{ + unsigned int i; + u8 adjust, max_pre_emphasis, max_volt_swing; + + max_pre_emphasis = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis) + << DP_TRAIN_PRE_EMPHASIS_SHIFT; + max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing); + + for (i = 0; i < mhdp->link.num_lanes; i++) { + adjust = drm_dp_get_adjust_request_voltage(link_status, i); + lanes_data[i] = min_t(u8, adjust, max_volt_swing); + if (lanes_data[i] != adjust) + lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED; + + adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + lanes_data[i] |= min_t(u8, adjust, max_pre_emphasis); + if ((lanes_data[i] >> DP_TRAIN_PRE_EMPHASIS_SHIFT) != adjust) + lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + } +} + +static void mhdp_set_adjust_request_voltage( + u8 link_status[DP_LINK_STATUS_SIZE], int lane, u8 volt) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + int idx = i - DP_LANE0_1_STATUS; + + link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s); + link_status[idx] |= volt << s; +} + +static void mhdp_set_adjust_request_pre_emphasis( + u8 link_status[DP_LINK_STATUS_SIZE], int lane, u8 pre_emphasis) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + int idx = i - DP_LANE0_1_STATUS; + + link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s); + link_status[idx] |= pre_emphasis << s; +} + +static void mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + unsigned int i; + u8 pre, volt, max_pre = CDNS_VOLT_SWING(mhdp->host.volt_swing), + max_volt = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + + for (i = 0; i < mhdp->link.num_lanes; i++) { + volt = drm_dp_get_adjust_request_voltage(link_status, i); + pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + if (volt + pre > 3) + mhdp_set_adjust_request_voltage(link_status, i, + 3 - pre); + if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING) + mhdp_set_adjust_request_voltage(link_status, i, + max_volt); + if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS) + mhdp_set_adjust_request_pre_emphasis(link_status, i, + max_pre); + } +} + +static bool mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp, + u8 eq_tps, + unsigned int training_interval) +{ + u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0; + u8 dpcd[DP_LINK_STATUS_SIZE]; + u32 reg32; + + dev_dbg(mhdp->dev, "Link training - Starting EQ phase\n"); + + /* Enable link training TPS[eq_tps] in PHY */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN | + CDNS_PHY_TRAINING_TYPE(eq_tps); + if (eq_tps != 4) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE : + CDNS_DP_TRAINING_PATTERN_4); + + drm_dp_dpcd_read_link_status(&mhdp->aux, dpcd); + + do { + mhdp_get_adjust_train(mhdp, dpcd, lanes_data); + + cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, + training_interval, lanes_data, dpcd); + + if (!drm_dp_clock_recovery_ok(dpcd, mhdp->link.num_lanes)) + goto err; + + if (drm_dp_channel_eq_ok(dpcd, mhdp->link.num_lanes)) { + dev_dbg(mhdp->dev, + "Link training: EQ phase succeeded\n"); + return true; + } + + fail_counter_short++; + + mhdp_adjust_requested_eq(mhdp, dpcd); + } while (fail_counter_short < 5); + +err: + dev_dbg(mhdp->dev, + "Link training - EQ phase failed for %d lanes and %d rate\n", + mhdp->link.num_lanes, mhdp->link.rate); + + return false; +} + +static void mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE], + u8 *req_volt, u8 *req_pre) +{ + const u32 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing), + max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + unsigned int i; + + for (i = 0; i < mhdp->link.num_lanes; i++) { + unsigned int val; + + val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ? + max_volt : req_volt[i]; + mhdp_set_adjust_request_voltage(link_status, i, val); + + val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ? + max_pre : req_pre[i]; + mhdp_set_adjust_request_pre_emphasis(link_status, i, val); + } +} + +static void mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done, + bool *same_before_adjust, bool *max_swing_reached, + u8 before_cr[DP_LINK_STATUS_SIZE], + u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt, + u8 *req_pre) +{ + const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing), + max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + bool same_pre, same_volt; + unsigned int i; + + *same_before_adjust = false; + *max_swing_reached = false; + *cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes); + + for (i = 0; i < mhdp->link.num_lanes; i++) { + u8 tmp; + + tmp = drm_dp_get_adjust_request_voltage(after_cr, i); + req_volt[i] = min_t(u8, tmp, max_volt); + + tmp = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; + req_pre[i] = min_t(u8, tmp, max_pre); + + same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) == + req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT; + same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) == + req_volt[i]; + if (same_pre && same_volt) + *same_before_adjust = true; + + /* 3.1.5.2 in DP Standard v1.4. Table 3-1 */ + if (!*cr_done && req_volt[i] + req_pre[i] >= 3) { + *max_swing_reached = true; + return; + } + } +} + +static bool mhdp_link_training_clock_recovery(struct cdns_mhdp_device *mhdp) +{ + u8 lanes_data[CDNS_DP_MAX_NUM_LANES], + fail_counter_short = 0, fail_counter_cr_long = 0; + u8 dpcd[DP_LINK_STATUS_SIZE]; + bool cr_done; + + dev_dbg(mhdp->dev, "Link training starting CR phase\n"); + + mhdp_link_training_init(mhdp); + + drm_dp_dpcd_read_link_status(&mhdp->aux, dpcd); + + do { + u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {}, + requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {}; + bool same_before_adjust, max_swing_reached; + + mhdp_get_adjust_train(mhdp, dpcd, lanes_data); + + cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100, + lanes_data, dpcd); + + mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust, + &max_swing_reached, lanes_data, dpcd, + requested_adjust_volt_swing, + requested_adjust_pre_emphasis); + + if (max_swing_reached) + goto err; + + if (cr_done) { + dev_dbg(mhdp->dev, + "Link training: CR phase succeeded\n"); + return true; + } + + /* Not all CR_DONE bits set */ + fail_counter_cr_long++; + + if (same_before_adjust) { + fail_counter_short++; + continue; + } + + fail_counter_short = 0; + /* + * Voltage swing/pre-emphasis adjust requested + * during CR phase + */ + mhdp_adjust_requested_cr(mhdp, dpcd, + requested_adjust_volt_swing, + requested_adjust_pre_emphasis); + } while (fail_counter_short < 5 && fail_counter_cr_long < 10); + +err: + dev_dbg(mhdp->dev, + "Link training: CR phase failed for %d lanes and %d rate\n", + mhdp->link.num_lanes, mhdp->link.rate); + + return false; +} + +static void lower_link_rate(struct drm_dp_link *link) +{ + switch (drm_dp_link_rate_to_bw_code(link->rate)) { + case DP_LINK_BW_2_7: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62); + break; + case DP_LINK_BW_5_4: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7); + break; + case DP_LINK_BW_8_1: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4); + break; + } +} + +static int mhdp_link_training(struct cdns_mhdp_device *mhdp, + unsigned int video_mode, + unsigned int training_interval) +{ + u32 reg32; + const u8 eq_tps = eq_training_pattern_supported(mhdp->host, mhdp->sink); + + while (1) { + if (!mhdp_link_training_clock_recovery(mhdp)) { + if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) != + DP_LINK_BW_1_62) { + dev_dbg(mhdp->dev, + "Reducing link rate during CR phase\n"); + lower_link_rate(&mhdp->link); + drm_dp_link_configure(&mhdp->aux, &mhdp->link); + + continue; + } else if (mhdp->link.num_lanes > 1) { + dev_dbg(mhdp->dev, + "Reducing lanes number during CR phase\n"); + mhdp->link.num_lanes >>= 1; + mhdp->link.rate = max_link_rate(mhdp->host, + mhdp->sink); + drm_dp_link_configure(&mhdp->aux, &mhdp->link); + + continue; + } + + dev_dbg(mhdp->dev, + "Link training failed during CR phase\n"); + goto err; + } + + if (mhdp_link_training_channel_eq(mhdp, eq_tps, + training_interval)) + break; + + if (mhdp->link.num_lanes > 1) { + dev_dbg(mhdp->dev, + "Reducing lanes number during EQ phase\n"); + mhdp->link.num_lanes >>= 1; + drm_dp_link_configure(&mhdp->aux, &mhdp->link); + + continue; + } else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) != + DP_LINK_BW_1_62) { + dev_dbg(mhdp->dev, + "Reducing link rate during EQ phase\n"); + lower_link_rate(&mhdp->link); + drm_dp_link_configure(&mhdp->aux, &mhdp->link); + + continue; + } + + dev_dbg(mhdp->dev, "Link training failed during EQ phase\n"); + goto err; + } + + dev_dbg(mhdp->dev, "Link training successful\n"); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + (mhdp->host.lanes_cnt & CDNS_SCRAMBLER) ? 0 : + DP_LINK_SCRAMBLING_DISABLE); + + /* SW reset DPTX framer */ + cdns_mhdp_reg_write(mhdp, CDNS_DP_SW_RESET, 1); + cdns_mhdp_reg_write(mhdp, CDNS_DP_SW_RESET, 0); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, + CDNS_DP_NUM_LANES(mhdp->link.num_lanes) | + CDNS_DP_DISABLE_PHY_RST | + CDNS_DP_WR_FAILING_EDGE_VSYNC | + (mhdp->is_mst ? CDNS_DP_MST_EN : 0) | + (!video_mode ? CDNS_DP_NO_VIDEO_MODE : 0)); + + /* Reset PHY config */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!(mhdp->host.lanes_cnt & CDNS_SCRAMBLER)) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + return 0; +err: + /* Reset PHY config */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!(mhdp->host.lanes_cnt & CDNS_SCRAMBLER)) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + return -EIO; +} + +static void cdns_mhdp_disable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + cdns_mhdp_set_video_status(mhdp, 0); + + mhdp->link_up = false; + + drm_dp_link_power_down(&mhdp->aux, &mhdp->link); + if (mhdp->is_mst) + cdns_mhdp_mst_disable(bridge); +} + +static u32 get_training_interval_us(struct cdns_mhdp_device *mhdp, + u32 interval) +{ + if (interval == 0) + return 400; + if (interval < 5) + return 4000 << (interval - 1); + dev_err(mhdp->dev, + "wrong training interval returned by DPCD: %d\n", interval); + return 0; +} + +static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp) +{ + u32 resp, dp_framer_global_config, video_mode; + u8 reg0[DP_RECEIVER_CAP_SIZE], amp[2]; + + /* + * Upon power-on reset/device disconnection: [2:0] bits should be 0b001 + * and [7:5] bits 0b000. + */ + drm_dp_dpcd_writeb(&mhdp->aux, DP_SET_POWER, 1); + + drm_dp_link_probe(&mhdp->aux, &mhdp->link); + + dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n"); + drm_dp_link_power_up(&mhdp->aux, &mhdp->link); + /* FIXME (CDNS): do we have to wait for 100ms before going on? */ + mdelay(100); + + mhdp->sink.link_rate = mhdp->link.rate; + mhdp->sink.lanes_cnt = mhdp->link.num_lanes; + mhdp->sink.enhanced = !!(mhdp->link.capabilities & + DP_LINK_CAP_ENHANCED_FRAMING); + + drm_dp_dpcd_read(&mhdp->aux, DP_DPCD_REV, reg0, DP_RECEIVER_CAP_SIZE); + + mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2); + if (drm_dp_tps3_supported(reg0)) + mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3); + if (drm_dp_tps4_supported(reg0)) + mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4); + + mhdp->sink.fast_link = !!(reg0[DP_MAX_DOWNSPREAD] & + DP_NO_AUX_HANDSHAKE_LINK_TRAINING); + + mhdp->link.rate = max_link_rate(mhdp->host, mhdp->sink); + mhdp->link.num_lanes = min_t(u8, mhdp->sink.lanes_cnt, + mhdp->host.lanes_cnt & GENMASK(2, 0)); + cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp); + + dp_framer_global_config = be32_to_cpu(resp); + + cdns_mhdp_mst_probe(mhdp); + if (mhdp->is_mst) + dp_framer_global_config |= CDNS_DP_MST_EN; + + video_mode = !(dp_framer_global_config & CDNS_DP_NO_VIDEO_MODE); + + if (dp_framer_global_config & CDNS_DP_FRAMER_EN) + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, + dp_framer_global_config & + ~CDNS_DP_FRAMER_EN); + + /* Spread AMP if required, enable 8b/10b coding */ + amp[0] = (mhdp->host.lanes_cnt & CDNS_SSC) ? DP_SPREAD_AMP_0_5 : 0; + amp[1] = DP_SET_ANSI_8B10B; + drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2); + + if (mhdp->host.fast_link & mhdp->sink.fast_link) { + /* FIXME: implement fastlink */ + dev_dbg(mhdp->dev, "fastlink\n"); + } else { + const u32 interval = reg0[DP_TRAINING_AUX_RD_INTERVAL] & + DP_TRAINING_AUX_RD_MASK; + const u32 interval_us = get_training_interval_us(mhdp, + interval); + if (!interval_us || + mhdp_link_training(mhdp, video_mode, interval_us)) { + dev_err(mhdp->dev, "Link training failed. Exiting.\n"); + return -EIO; + } + } + + mhdp->link_up = true; + + return 0; +} + +u32 cdns_mhdp_get_bpp(u32 bpc, enum pixel_format pxlfmt) +{ + u32 bpp; + + switch (pxlfmt) { + case PIXEL_FORMAT_RGB: + case PIXEL_FORMAT_YCBCR_444: + bpp = bpc * 3; + break; + case PIXEL_FORMAT_YCBCR_422: + bpp = bpc * 2; + break; + case PIXEL_FORMAT_YCBCR_420: + bpp = bpc * 3 / 2; + break; + default: + bpp = bpc; + } + return bpp; +} + +static int cdns_mhdp_sst_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + u32 rate, vs, vs_f, required_bandwidth, available_bandwidth; + u32 bpp, tu_size = 30, line_thresh1, line_thresh2, line_thresh = 0; + struct drm_display_mode *mode; + int pxlclock; + enum pixel_format pxlfmt; + struct drm_display_info *disp_info = &mhdp->connector.base.display_info; + + mode = &bridge->encoder->crtc->state->adjusted_mode; + pxlclock = mode->crtc_clock; + + mhdp_bridge->stream_id = 0; + + rate = mhdp->link.rate / 1000; + + pxlfmt = cdns_mhdp_get_pxlfmt(disp_info->color_formats); + bpp = cdns_mhdp_get_bpp(disp_info->bpc, pxlfmt); + + /* find optimal tu_size */ + required_bandwidth = pxlclock * bpp / 8; + available_bandwidth = mhdp->link.num_lanes * rate; + do { + tu_size += 2; + + vs_f = tu_size * required_bandwidth / available_bandwidth; + vs = vs_f / 1000; + vs_f = vs_f % 1000; + /* + * FIXME (CDNS): downspreading? + * It's unused is what I've been told. + */ + } while ((vs == 1 || ((vs_f > 850 || vs_f < 100) && vs_f != 0) || + tu_size - vs < 2) && tu_size < 64); + + if (vs > 64) + return -EINVAL; + + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU, + CDNS_DP_FRAMER_TU_VS(vs) | + CDNS_DP_FRAMER_TU_SIZE(tu_size) | + CDNS_DP_FRAMER_TU_CNT_RST_EN); + + line_thresh1 = ((vs + 1) << 5) * 8 / bpp; + line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5); + line_thresh = line_thresh1 - line_thresh2 / mhdp->link.num_lanes; + line_thresh = (line_thresh >> 5) + 2; + cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0), + line_thresh & GENMASK(5, 0)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0), + CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ? + 0 : tu_size - vs)); + + cdns_mhdp_configure_video(bridge); + + cdns_mhdp_set_video_status(mhdp, 1); + + return 0; +} + +enum pixel_format cdns_mhdp_get_pxlfmt(u32 color_formats) +{ + /* FIXME: what about Y_ONLY? how is it handled in the kernel? */ + if (color_formats & DRM_COLOR_FORMAT_YCRCB444) + return PIXEL_FORMAT_YCBCR_444; + if (color_formats & DRM_COLOR_FORMAT_YCRCB422) + return PIXEL_FORMAT_YCBCR_422; + if (color_formats & DRM_COLOR_FORMAT_YCRCB420) + return PIXEL_FORMAT_YCBCR_420; + return PIXEL_FORMAT_RGB; +} + +void cdns_mhdp_configure_video(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + struct drm_display_info *disp_info; + unsigned int bpp, dp_framer_sp = 0, msa_horizontal_1, + msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl, + misc0 = 0, misc1 = 0, pxl_repr, + front_porch, back_porch, msa_h0, msa_v0, hsync, vsync, + dp_vertical_1; + struct drm_display_mode *mode; + enum pixel_format pxlfmt; + int pxlclock; + u32 tmp; + u8 stream_id = mhdp_bridge->stream_id; + + mode = &bridge->encoder->crtc->state->adjusted_mode; + pxlclock = mode->crtc_clock; + + disp_info = &mhdp_bridge->connector->base.display_info; + pxlfmt = cdns_mhdp_get_pxlfmt(disp_info->color_formats); + + /* if YCBCR supported and stream not SD, use ITU709 */ + /* FIXME: handle ITU version with YCBCR420 when supported */ + if ((pxlfmt == PIXEL_FORMAT_YCBCR_444 || + pxlfmt == PIXEL_FORMAT_YCBCR_422) && mode->crtc_vdisplay >= 720) + misc0 = DP_YCBCR_COEFFICIENTS_ITU709; + + bpp = cdns_mhdp_get_bpp(disp_info->bpc, pxlfmt); + + switch (pxlfmt) { + case PIXEL_FORMAT_RGB: + pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_RGB; + break; + case PIXEL_FORMAT_YCBCR_444: + pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA; + break; + case PIXEL_FORMAT_YCBCR_422: + pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA; + break; + case PIXEL_FORMAT_YCBCR_420: + pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT; + break; + default: + pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT; + } + + switch (disp_info->bpc) { + case 6: + misc0 |= DP_TEST_BIT_DEPTH_6; + pxl_repr |= CDNS_DP_FRAMER_6_BPC; + break; + case 8: + misc0 |= DP_TEST_BIT_DEPTH_8; + pxl_repr |= CDNS_DP_FRAMER_8_BPC; + break; + case 10: + misc0 |= DP_TEST_BIT_DEPTH_10; + pxl_repr |= CDNS_DP_FRAMER_10_BPC; + break; + case 12: + misc0 |= DP_TEST_BIT_DEPTH_12; + pxl_repr |= CDNS_DP_FRAMER_12_BPC; + break; + case 16: + misc0 |= DP_TEST_BIT_DEPTH_16; + pxl_repr |= CDNS_DP_FRAMER_16_BPC; + break; + } + + bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT; + + cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id), + bnd_hsync2vsync); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE && + mode->flags & DRM_MODE_FLAG_PHSYNC) + hsync2vsync_pol_ctrl = CDNS_H2V_HSYNC_POL_ACTIVE_LOW | + CDNS_H2V_VSYNC_POL_ACTIVE_LOW; + else + hsync2vsync_pol_ctrl = 0; + + cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id), + hsync2vsync_pol_ctrl); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp); + + front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay; + back_porch = mode->crtc_htotal - mode->crtc_hsync_end; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id), + CDNS_DP_FRONT_PORCH(front_porch) | + CDNS_DP_BACK_PORCH(back_porch)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id), + mode->crtc_hdisplay * bpp / 8); + + msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id), + CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) | + CDNS_DP_MSAH0_HSYNC_START(msa_h0)); + + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) | + CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay); + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id), + msa_horizontal_1); + + msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id), + CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) | + CDNS_DP_MSAV0_VSYNC_START(msa_v0)); + + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) | + CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id), + msa_vertical_1); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && + mode->crtc_vtotal % 2 == 0) + misc1 = DP_TEST_INTERLACED; + if (pxlfmt == PIXEL_FORMAT_Y_ONLY) + misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY; + /* FIXME: use VSC SDP for Y420 */ + /* FIXME: (CDNS) no code for Y420 in bare metal test */ + if (pxlfmt == PIXEL_FORMAT_YCBCR_420) + misc1 = CDNS_DP_TEST_VSC_SDP; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id), + misc0 | (misc1 << 8)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id), + CDNS_DP_H_HSYNC_WIDTH(hsync) | + CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id), + CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) | + CDNS_DP_V0_VSTART(msa_v0)); + + dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal); + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && + mode->crtc_vtotal % 2 == 0) + dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1); + + cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? + CDNS_DP_VB_ID_INTERLACED : 0); + + + cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &tmp); + tmp |= CDNS_DP_FRAMER_EN; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, tmp); +} + +void cdns_mhdp_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge *mhdp_bridge = to_mhdp_bridge(bridge); + struct cdns_mhdp_device *mhdp = mhdp_bridge->mhdp; + + if (!mhdp->link_up) + cdns_mhdp_link_up(mhdp); + + if (mhdp->is_mst) + cdns_mhdp_mst_enable(bridge); + else + cdns_mhdp_sst_enable(bridge); +} + +static void cdns_mhdp_detach(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + struct cdns_mhdp_mst_cbs cbs_null = { 0 }; + + cdns_mhdp_mst_deinit(mhdp); + mhdp->cbs = cbs_null; +} + +static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = { + .enable = cdns_mhdp_enable, + .disable = cdns_mhdp_disable, + .attach = cdns_mhdp_attach, + .detach = cdns_mhdp_detach, +}; + +static int load_firmware(struct cdns_mhdp_device *mhdp, const char *name, + unsigned int addr) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, name, mhdp->dev); + if (ret) { + dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n", + name, ret); + return ret; + } + + memcpy_toio(mhdp->regs + addr, fw->data, fw->size); + + release_firmware(fw); + + return 0; +} + +static int cdns_mhdp_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + struct audio_info audio = { + .sample_width = params->sample_width, + .sample_rate = params->sample_rate, + .channels = params->channels, + }; + int ret; + + if (daifmt->fmt != HDMI_I2S) { + DRM_DEV_ERROR(dev, "Invalid format %d\n", daifmt->fmt); + return -EINVAL; + } + + audio.format = AFMT_I2S; + + ret = cdns_mhdp_audio_config(mhdp, &audio); + if (!ret) + mhdp->audio_info = audio; + + return 0; +} + +static void cdns_mhdp_audio_shutdown(struct device *dev, void *data) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + int ret; + + ret = cdns_mhdp_audio_stop(mhdp, &mhdp->audio_info); + if (!ret) + mhdp->audio_info.format = AFMT_UNUSED; +} + +static int cdns_mhdp_audio_digital_mute(struct device *dev, void *data, + bool enable) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + return cdns_mhdp_audio_mute(mhdp, enable); +} + +static int cdns_mhdp_audio_get_eld(struct device *dev, void *data, + u8 *buf, size_t len) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + memcpy(buf, mhdp->connector.base.eld, + min(sizeof(mhdp->connector.base.eld), len)); + + return 0; +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = cdns_mhdp_audio_hw_params, + .audio_shutdown = cdns_mhdp_audio_shutdown, + .digital_mute = cdns_mhdp_audio_digital_mute, + .get_eld = cdns_mhdp_audio_get_eld, +}; + +static int mhdp_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct cdns_mhdp_device *mhdp; + struct clk *clk; + int ret; + unsigned int reg; + unsigned long rate; + u32 resp; + int irq; + + struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .max_i2s_channels = 8, + .ops = &audio_codec_ops, + }; + + mhdp = devm_kzalloc(&pdev->dev, sizeof(struct cdns_mhdp_device), + GFP_KERNEL); + if (!mhdp) + return -ENOMEM; + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "couldn't get clk: %ld\n", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + mhdp->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, mhdp); + + drm_dp_aux_init(&mhdp->aux); + mhdp->aux.dev = &pdev->dev; + mhdp->aux.transfer = mhdp_transfer; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mhdp->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(mhdp->regs)) + return PTR_ERR(mhdp->regs); + + mhdp->phy = devm_phy_get(&pdev->dev, "dpphy"); + if (IS_ERR(mhdp->phy)) { + dev_err(&pdev->dev, "no PHY configured\n"); + return PTR_ERR(mhdp->phy); + } + + platform_set_drvdata(pdev, mhdp); + + ret = load_firmware(mhdp, FW_NAME, CDNS_MHDP_IMEM); + if (ret) + return ret; + + rate = clk_get_rate(clk); + writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L); + writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H); + + /* Leave debug mode */ + writel(0, mhdp->regs + CDNS_APB_CTRL); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(mhdp->dev, irq, mhdp_irq_handler, 0, + "mhdp8546", mhdp); + if (ret) { + dev_err(&pdev->dev, + "cannot install IRQ %d\n", irq); + return -EIO; + } + + /* + * Wait for the KEEP_ALIVE "message" on the first 8 bits. + * Updated each sched "tick" (~2ms) + */ + ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg, + reg & CDNS_KEEP_ALIVE_MASK, 500, + CDNS_KEEP_ALIVE_TIMEOUT); + if (ret) { + dev_err(&pdev->dev, + "device didn't give any life sign: reg %d\n", reg); + return -EIO; + } + + /* + * FIXME (CDNS): how are the characteristics/features of the host + * defined? Will they be always hardcoded? + */ + /* FIXME: link rate 2.7; num_lanes = 2, */ + /* FIXME: read capabilities from PHY */ + mhdp->host.link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4); + mhdp->host.lanes_cnt = CDNS_LANE_4 | CDNS_SCRAMBLER; + mhdp->host.volt_swing = CDNS_VOLT_SWING(3); + mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(2); + mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) | + CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) | + CDNS_SUPPORT_TPS(4); + mhdp->host.fast_link = 0; + mhdp->host.lane_mapping = CDNS_LANE_MAPPING_FLIPPED; + mhdp->host.enhanced = true; + + mhdp->bridge.base.of_node = pdev->dev.of_node; + mhdp->bridge.base.funcs = &cdns_mhdp_bridge_funcs; + + /* Init events to 0 as it's not cleared by FW at boot but on read */ + readl(mhdp->regs + CDNS_SW_EVENT0); + readl(mhdp->regs + CDNS_SW_EVENT1); + readl(mhdp->regs + CDNS_SW_EVENT2); + readl(mhdp->regs + CDNS_SW_EVENT3); + + /* Activate uCPU */ + ret = cdns_mhdp_set_firmware_active(mhdp, true); + if (ret) { + dev_err(mhdp->dev, "Failed to activate DP\n"); + return ret; + } + + mhdp->audio_pdev = platform_device_register_data( + mhdp->dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + phy_init(mhdp->phy); + + /* Enable VIF clock for stream 0 */ + cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp); + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR, + resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN); + + INIT_DELAYED_WORK(&mhdp->hotplug_work, mhdp_hotplug_work_func); + + mhdp->bridge.connector = &mhdp->connector; + mhdp->connector.bridge = &mhdp->bridge; + mhdp->bridge.mhdp = mhdp; + mhdp->bridge.is_active = false; + + drm_bridge_add(&mhdp->bridge.base); + + return 0; +} + +MODULE_FIRMWARE(FW_NAME); + +static int mhdp_remove(struct platform_device *pdev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev); + int ret; + + flush_delayed_work(&mhdp->hotplug_work); + platform_device_unregister(mhdp->audio_pdev); + + cdns_mhdp_mst_deinit(mhdp); + drm_bridge_remove(&mhdp->bridge.base); + + ret = cdns_mhdp_set_firmware_active(mhdp, false); + if (ret) { + dev_err(mhdp->dev, "Failed to de-activate DP\n"); + return ret; + } + + /* FIXME: check for missing functions */ + + return 0; +} + +int mhdp_bridge_attach_mst_cbs(struct drm_bridge *bridge, + struct cdns_mhdp_mst_cbs *cbs) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + mhdp->cbs = *cbs; + + return 0; +} +EXPORT_SYMBOL_GPL(mhdp_bridge_attach_mst_cbs); + +static struct platform_driver mhdp_driver = { + .driver = { + .name = "cdns-mhdp", + .of_match_table = of_match_ptr(mhdp_ids), + }, + .probe = mhdp_probe, + .remove = mhdp_remove, +}; +module_platform_driver(mhdp_driver); + +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx>"); +MODULE_AUTHOR("Przemyslaw Gaj <pgaj@xxxxxxxxxxx>"); +MODULE_AUTHOR("Damian Kos <dkos@xxxxxxxxxxx>"); +MODULE_AUTHOR("Piotr Sroka <piotrs@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Cadence MHDP DP bridge driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdns-mhdp"); diff --git a/drivers/gpu/drm/bridge/cdns-mhdp.h b/drivers/gpu/drm/bridge/cdns-mhdp.h new file mode 100644 index 000000000000..399c3f6f86ad --- /dev/null +++ b/drivers/gpu/drm/bridge/cdns-mhdp.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence MHDP DP MST bridge driver. + * + * Copyright: 2018 Cadence Design Systems, Inc. + * + * Author: Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx> + */ + + +#ifndef CDNS_MHDP_H +#define CDNS_MHDP_H + +#include <drm/drm_dp_mst_helper.h> + +#define CDNS_APB_CFG 0x00000 +#define CDNS_APB_CTRL (CDNS_APB_CFG + 0x00) +#define CDNS_MAILBOX_FULL (CDNS_APB_CFG + 0x08) +#define CDNS_MAILBOX_EMPTY (CDNS_APB_CFG + 0x0c) +#define CDNS_MAILBOX_TX_DATA (CDNS_APB_CFG + 0x10) +#define CDNS_MAILBOX_RX_DATA (CDNS_APB_CFG + 0x14) +#define CDNS_KEEP_ALIVE (CDNS_APB_CFG + 0x18) +#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0) + +#define CDNS_MB_INT_MASK (CDNS_APB_CFG + 0x34) + +#define CDNS_SW_CLK_L (CDNS_APB_CFG + 0x3c) +#define CDNS_SW_CLK_H (CDNS_APB_CFG + 0x40) +#define CDNS_SW_EVENT0 (CDNS_APB_CFG + 0x44) +#define CDNS_DPTX_HPD BIT(0) + +#define CDNS_SW_EVENT1 (CDNS_APB_CFG + 0x48) +#define CDNS_SW_EVENT2 (CDNS_APB_CFG + 0x4c) +#define CDNS_SW_EVENT3 (CDNS_APB_CFG + 0x50) + +#define CDNS_APB_INT_MASK (CDNS_APB_CFG + 0x6C) +#define CDNS_APB_INT_MASK_MAILBOX_INT BIT(0) +#define CDNS_APB_INT_MASK_SW_EVENT_INT BIT(1) + +#define CDNS_DPTX_CAR (CDNS_APB_CFG + 0x904) +#define CDNS_VIF_CLK_EN BIT(0) +#define CDNS_VIF_CLK_RSTN BIT(1) + +#define CDNS_SOURCE_VIDEO_IF(s) (0x00b00 + (s * 0x20)) +#define CDNS_BND_HSYNC2VSYNC(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x00) +#define CDNS_IP_DTCT_WIN GENMASK(11, 0) +#define CDNS_IP_DET_INTERLACE_FORMAT BIT(12) +#define CDNS_IP_BYPASS_V_INTERFACE BIT(13) + +#define CDNS_HSYNC2VSYNC_POL_CTRL(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x10) +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW BIT(1) +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW BIT(2) + +#define CDNS_DPTX_PHY_CONFIG 0x02000 +#define CDNS_PHY_TRAINING_EN BIT(0) +#define CDNS_PHY_TRAINING_TYPE(x) (((x) & GENMASK(3, 0)) << 1) +#define CDNS_PHY_SCRAMBLER_BYPASS BIT(5) +#define CDNS_PHY_ENCODER_BYPASS BIT(6) +#define CDNS_PHY_SKEW_BYPASS BIT(7) +#define CDNS_PHY_TRAINING_AUTO BIT(8) +#define CDNS_PHY_LANE0_SKEW(x) (((x) & GENMASK(2, 0)) << 9) +#define CDNS_PHY_LANE1_SKEW(x) (((x) & GENMASK(2, 0)) << 12) +#define CDNS_PHY_LANE2_SKEW(x) (((x) & GENMASK(2, 0)) << 15) +#define CDNS_PHY_LANE3_SKEW(x) (((x) & GENMASK(2, 0)) << 18) +#define CDNS_PHY_COMMON_CONFIG (CDNS_PHY_LANE1_SKEW(1) | \ + CDNS_PHY_LANE2_SKEW(2) | \ + CDNS_PHY_LANE3_SKEW(3)) +#define CDNS_PHY_10BIT_EN BIT(21) + +#define CDNS_DPTX_FRAMER 0x02200 +#define CDNS_DP_FRAMER_GLOBAL_CONFIG (CDNS_DPTX_FRAMER + 0x00) +#define CDNS_DP_NUM_LANES(x) (x - 1) +#define CDNS_DP_MST_EN BIT(2) +#define CDNS_DP_FRAMER_EN BIT(3) +#define CDNS_DP_RATE_GOVERNOR_EN BIT(4) +#define CDNS_DP_NO_VIDEO_MODE BIT(5) +#define CDNS_DP_DISABLE_PHY_RST BIT(6) +#define CDNS_DP_WR_FAILING_EDGE_VSYNC BIT(7) + +#define CDNS_DP_SW_RESET (CDNS_DPTX_FRAMER + 0x04) +#define CDNS_DP_FRAMER_TU (CDNS_DPTX_FRAMER + 0x08) +#define CDNS_DP_FRAMER_TU_SIZE(x) (((x) & GENMASK(6, 0)) << 8) +#define CDNS_DP_FRAMER_TU_VS(x) ((x) & GENMASK(5, 0)) +#define CDNS_DP_FRAMER_TU_CNT_RST_EN BIT(15) + +#define CDNS_DPTX_STREAM(s) (0x03000 + s * 0x80) +#define CDNS_DP_MSA_HORIZONTAL_0(s) (CDNS_DPTX_STREAM(s) + 0x00) +#define CDNS_DP_MSAH0_H_TOTAL(x) (x) +#define CDNS_DP_MSAH0_HSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_HORIZONTAL_1(s) (CDNS_DPTX_STREAM(s) + 0x04) +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAH1_HSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAH1_HDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x08) +#define CDNS_DP_MSAV0_V_TOTAL(x) (x) +#define CDNS_DP_MSAV0_VSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x0c) +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAV1_VSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAV1_VDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_MISC(s) (CDNS_DPTX_STREAM(s) + 0x10) +#define CDNS_DP_STREAM_CONFIGs(s) (CDNS_DPTX_STREAM(s) + 0x14) +#define CDNS_DP_STREAM_CONFIG_2(s) (CDNS_DPTX_STREAM(s) + 0x2c) +#define CDNS_DP_SC2_TU_VS_DIFF(x) ((x) << 8) + +#define CDNS_DP_HORIZONTAL(s) (CDNS_DPTX_STREAM(s) + 0x30) +#define CDNS_DP_H_HSYNC_WIDTH(x) (x) +#define CDNS_DP_H_H_TOTAL(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x34) +#define CDNS_DP_V0_VHEIGHT(x) (x) +#define CDNS_DP_V0_VSTART(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x38) +#define CDNS_DP_V1_VTOTAL(x) (x) +#define CDNS_DP_V1_VTOTAL_EVEN BIT(16) + +#define CDNS_DP_FRAMER_PXL_REPR(s) (CDNS_DPTX_STREAM(s) + 0x4c) +#define CDNS_DP_FRAMER_6_BPC BIT(0) +#define CDNS_DP_FRAMER_8_BPC BIT(1) +#define CDNS_DP_FRAMER_10_BPC BIT(2) +#define CDNS_DP_FRAMER_12_BPC BIT(3) +#define CDNS_DP_FRAMER_16_BPC BIT(4) +#define CDNS_DP_FRAMER_PXL_FORMAT 0x8 +#define CDNS_DP_FRAMER_RGB BIT(0) +#define CDNS_DP_FRAMER_YCBCR444 BIT(1) +#define CDNS_DP_FRAMER_YCBCR422 BIT(2) +#define CDNS_DP_FRAMER_YCBCR420 BIT(3) +#define CDNS_DP_FRAMER_Y_ONLY BIT(4) + +#define CDNS_DP_FRAMER_SP(s) (CDNS_DPTX_STREAM(s) + 0x10) +#define CDNS_DP_FRAMER_VSYNC_POL_LOW BIT(0) +#define CDNS_DP_FRAMER_HSYNC_POL_LOW BIT(1) +#define CDNS_DP_FRAMER_INTERLACE BIT(2) + +#define CDNS_DP_LINE_THRESH(s) (CDNS_DPTX_STREAM(s) + 0x64) +#define CDNS_DP_ACTIVE_LINE_THRESH(x) (x) + +#define CDNS_DP_VB_ID(s) (CDNS_DPTX_STREAM(s) + 0x68) +#define CDNS_DP_VB_ID_INTERLACED BIT(2) +#define CDNS_DP_VB_ID_COMPRESSED BIT(6) + +#define CDNS_DP_FRONT_BACK_PORCH(s) (CDNS_DPTX_STREAM(s) + 0x78) +#define CDNS_DP_BACK_PORCH(x) (x) +#define CDNS_DP_FRONT_PORCH(x) ((x) << 16) + +#define CDNS_DP_BYTE_COUNT(s) (CDNS_DPTX_STREAM(s) + 0x7c) +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT 16 + +#define CDNS_DP_MST_STREAM_CONFIG(s) (CDNS_DPTX_STREAM(s) + 0x14) +#define CDNS_DP_MST_STRM_CFG_STREAM_EN BIT(0) +#define CDNS_DP_MST_STRM_CFG_NO_VIDEO BIT(1) + +#define CDNS_DP_MST_SLOT_ALLOCATE(s) (CDNS_DPTX_STREAM(s) + 0x44) +#define CDNS_DP_S_ALLOC_START_SLOT(x) (x) +#define CDNS_DP_S_ALLOC_END_SLOT(x) ((x) << 8) + +#define CDNS_DP_RATE_GOVERNING(s) (CDNS_DPTX_STREAM(s) + 0x48) +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x) (x) +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x) (x << 4) +#define CDNS_DP_RG_ENABLE BIT(10) + +#define CDNS_DP_MTPH_CONTROL 0x2264 +#define CDNS_DP_MTPH_ECF_EN BIT(0) +#define CDNS_DP_MTPH_ACT_EN BIT(1) +#define CDNS_DP_MTPH_LVP_EN BIT(2) + +#define CDNS_DP_MTPH_STATUS 0x226C +#define CDNS_DP_MTPH_ACT_STATUS BIT(0) + + +#define CDNS_DPTX_GLOBAL 0x02300 +#define CDNS_DP_LANE_EN (CDNS_DPTX_GLOBAL + 0x00) +#define CDNS_DP_LANE_EN_LANES(x) GENMASK(x - 1, 0) +#define CDNS_DP_ENHNCD (CDNS_DPTX_GLOBAL + 0x04) + + +#define to_mhdp_connector(x) container_of(x, struct cdns_mhdp_connector, base) +#define to_mhdp_bridge(x) container_of(x, struct cdns_mhdp_bridge, base) +#define mgr_to_mhdp(x) container_of(x, struct cdns_mhdp_device, mst_mgr) + +#define CDNS_MHDP_MAX_STREAMS 4 + +enum pixel_format { + PIXEL_FORMAT_RGB = 1, + PIXEL_FORMAT_YCBCR_444 = 2, + PIXEL_FORMAT_YCBCR_422 = 4, + PIXEL_FORMAT_YCBCR_420 = 8, + PIXEL_FORMAT_Y_ONLY = 16, +}; + + +int cdns_mhdp_mst_init(struct cdns_mhdp_device *mhdp); +void cdns_mhdp_mst_deinit(struct cdns_mhdp_device *mhdp); +bool cdns_mhdp_mst_probe(struct cdns_mhdp_device *mhdp); +enum pixel_format cdns_mhdp_get_pxlfmt(u32 color_formats); +u32 cdns_mhdp_get_bpp(u32 bpc, u32 color_formats); +void cdns_mhdp_configure_video(struct drm_bridge *bridge); +void cdns_mhdp_mst_enable(struct drm_bridge *bridge); +void cdns_mhdp_mst_disable(struct drm_bridge *bridge); +void cdns_mhdp_enable(struct drm_bridge *bridge); + +#endif diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 1e75196f9659..069fb8725927 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -28,7 +28,9 @@ config ROCKCHIP_ANALOGIX_DP config ROCKCHIP_CDN_DP bool "Rockchip cdn DP" - depends on EXTCON=y || (EXTCON=m && DRM_ROCKCHIP=m) + depends on DRM_ROCKCHIP + select EXTCON + select DRM_CDNS_MHDP help This selects support for Rockchip SoC specific extensions for the cdn DP driver. If you want to enable Dp on diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index f6fc9d5dd0ad..0f4de0681bc0 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -9,7 +9,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.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_CDN_DP) += cdn-dp-core.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 2026bc7ffcce..54989f1479af 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -34,7 +34,7 @@ #include "rockchip_drm_vop.h" #define connector_to_dp(c) \ - container_of(c, struct cdn_dp_device, mhdp.connector) + container_of(c, struct cdn_dp_device, mhdp.connector.base) #define encoder_to_dp(c) \ container_of(c, struct cdn_dp_device, encoder) @@ -291,7 +291,7 @@ static int cdn_dp_connector_mode_valid(struct drm_connector *connector, { struct cdn_dp_device *dp = connector_to_dp(connector); struct drm_display_info *display_info = - &dp->mhdp.connector.display_info; + &dp->mhdp.connector.base.display_info; u32 requested, actual, rate, sink_max, source_max = 0; u8 lanes, bpc; @@ -387,7 +387,7 @@ static int cdn_dp_get_sink_capability(struct cdn_dp_device *dp) } kfree(dp->edid); - dp->edid = drm_do_get_edid(&dp->mhdp.connector, + dp->edid = drm_do_get_edid(&dp->mhdp.connector.base, cdns_mhdp_get_edid_block, &dp->mhdp); return 0; } @@ -559,7 +559,7 @@ static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder, { struct cdn_dp_device *dp = encoder_to_dp(encoder); struct drm_display_info *display_info = - &dp->mhdp.connector.display_info; + &dp->mhdp.connector.base.display_info; struct video_info *video = &dp->mhdp.video_info; switch (display_info->bpc) { @@ -816,7 +816,7 @@ static int cdn_dp_audio_hw_params(struct device *dev, void *data, ret = cdns_mhdp_audio_config(&dp->mhdp, &audio); if (!ret) - dp->audio_info = audio; + dp->mhdp.audio_info = audio; out: mutex_unlock(&dp->lock); @@ -832,9 +832,9 @@ static void cdn_dp_audio_shutdown(struct device *dev, void *data) if (!dp->active) goto out; - ret = cdns_mhdp_audio_stop(&dp->mhdp, &dp->audio_info); + ret = cdns_mhdp_audio_stop(&dp->mhdp, &dp->mhdp.audio_info); if (!ret) - dp->audio_info.format = AFMT_UNUSED; + dp->mhdp.audio_info.format = AFMT_UNUSED; out: mutex_unlock(&dp->lock); } @@ -863,8 +863,8 @@ static int cdn_dp_audio_get_eld(struct device *dev, void *data, { struct cdn_dp_device *dp = dev_get_drvdata(dev); - memcpy(buf, dp->mhdp.connector.eld, - min(sizeof(dp->mhdp.connector.eld), len)); + memcpy(buf, dp->mhdp.connector.base.eld, + min(sizeof(dp->mhdp.connector.base.eld), len)); return 0; } @@ -886,11 +886,11 @@ static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp, .max_i2s_channels = 8, }; - dp->audio_pdev = platform_device_register_data( - dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, - &codec_data, sizeof(codec_data)); + dp->mhdp.audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); - return PTR_ERR_OR_ZERO(dp->audio_pdev); + return PTR_ERR_OR_ZERO(dp->mhdp.audio_pdev); } static int cdn_dp_request_firmware(struct cdn_dp_device *dp) @@ -936,7 +936,7 @@ static void cdn_dp_pd_event_work(struct work_struct *work) { struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device, event_work); - struct drm_connector *connector = &dp->mhdp.connector; + struct drm_connector *connector = &dp->mhdp.connector.base; enum drm_connector_status old_status; struct device *dev = dp->mhdp.dev; @@ -1062,7 +1062,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs); - connector = &dp->mhdp.connector; + connector = &dp->mhdp.connector.base; connector->polled = DRM_CONNECTOR_POLL_HPD; connector->dpms = DRM_MODE_DPMS_OFF; @@ -1113,7 +1113,7 @@ static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) { struct cdn_dp_device *dp = dev_get_drvdata(dev); struct drm_encoder *encoder = &dp->encoder; - struct drm_connector *connector = &dp->mhdp.connector; + struct drm_connector *connector = &dp->mhdp.connector.base; cancel_work_sync(&dp->event_work); cdn_dp_encoder_disable(encoder); @@ -1217,7 +1217,7 @@ static int cdn_dp_remove(struct platform_device *pdev) { struct cdn_dp_device *dp = platform_get_drvdata(pdev); - platform_device_unregister(dp->audio_pdev); + platform_device_unregister(dp->mhdp.audio_pdev); cdn_dp_suspend(dp->mhdp.dev); component_del(&pdev->dev, &cdn_dp_component_ops); diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index 5573a6277621..3e1279d255ea 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -15,12 +15,13 @@ #ifndef _CDN_DP_CORE_H #define _CDN_DP_CORE_H +#include <drm/bridge/cdns-mhdp-common.h> #include <drm/drmP.h> #include <drm/drm_dp_helper.h> #include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> + #include "rockchip_drm_drv.h" -#include "cdn-dp-reg.h" #define MAX_PHY 2 @@ -45,7 +46,6 @@ struct cdn_dp_device { struct cdns_mhdp_device mhdp; struct drm_device *drm_dev; struct drm_encoder encoder; - struct platform_device *audio_pdev; struct work_struct event_work; struct edid *edid; @@ -64,7 +64,6 @@ struct cdn_dp_device { struct reset_control *dptx_rst; struct reset_control *apb_rst; struct reset_control *core_rst; - struct audio_info audio_info; struct cdn_dp_port *port[MAX_PHY]; u8 ports; u8 lanes; diff --git a/include/drm/bridge/cdns-mhdp-cbs.h b/include/drm/bridge/cdns-mhdp-cbs.h new file mode 100644 index 000000000000..ed67e184d3ed --- /dev/null +++ b/include/drm/bridge/cdns-mhdp-cbs.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence MHDP DP bridge callbacks. + * + * Copyright: 2018 Cadence Design Systems, Inc. + * + * Author: Piotr Sroka <piotrs@xxxxxxxxxxx> + */ + +#ifndef CDNS_MHDP_CBS_H +#define CDNS_MHDP_CBS_H + +#include <drm/drm_bridge.h> + +struct cdns_mhdp_mst_cbs_funcs { + struct drm_encoder *(*create_mst_encoder)(void *priv_data, + struct drm_bridge *bridge); + void (*destroy_mst_encoder)(void *priv_data, struct drm_bridge *bridge); +}; + +struct cdns_mhdp_mst_cbs { + struct cdns_mhdp_mst_cbs_funcs funcs; + void *priv_data; +}; + +int mhdp_bridge_attach_mst_cbs(struct drm_bridge *bridge, + struct cdns_mhdp_mst_cbs *cbs); + +#endif diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/include/drm/bridge/cdns-mhdp-common.h similarity index 91% rename from drivers/gpu/drm/rockchip/cdn-dp-reg.h rename to include/drm/bridge/cdns-mhdp-common.h index 3cb40d719515..4ad9d88b0bec 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-reg.h +++ b/include/drm/bridge/cdns-mhdp-common.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd * Author: Chris Zhong <zyw@xxxxxxxxxxxxxx> @@ -12,8 +13,14 @@ * GNU General Public License for more details. */ -#ifndef _CDN_DP_REG_H -#define _CDN_DP_REG_H +#ifndef CDNS_MHDP_COMMON_H_ +#define CDNS_MHDP_COMMON_H_ + +#include <drm/bridge/cdns-mhdp-cbs.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_dp_mst_helper.h> #include <linux/bitops.h> @@ -323,10 +330,11 @@ #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 GENERAL_MAIN_CONTROL 0x01 +#define GENERAL_TEST_ECHO 0x02 +#define GENERAL_BUS_SETTINGS 0x03 +#define GENERAL_TEST_ACCESS 0x04 +#define GENERAL_REGISTER_READ 0x07 #define DPTX_SET_POWER_MNG 0x00 #define DPTX_SET_HOST_CAPABILITIES 0x01 @@ -346,6 +354,7 @@ #define DPTX_SET_LINK_BREAK_POINT 0x0f #define DPTX_FORCE_LANES 0x10 #define DPTX_HPD_STATE 0x11 +#define DPTX_ADJUST_LT 0x12 #define FW_STANDBY 0 #define FW_ACTIVE 1 @@ -507,26 +516,58 @@ struct cdns_mhdp_sink { u8 enhanced; }; +struct cdns_mhdp_bridge; +struct cdns_mhdp_connector; + +struct cdns_mhdp_bridge { + struct cdns_mhdp_device *mhdp; + struct drm_bridge base; + int pbn; + int8_t stream_id; + struct cdns_mhdp_connector *connector; + bool is_active; +}; + + +struct cdns_mhdp_connector { + struct drm_connector base; + bool is_mst_connector; + struct drm_dp_mst_port *port; + struct cdns_mhdp_bridge *bridge; +}; + + struct cdns_mhdp_device { void __iomem *regs; struct device *dev; struct drm_dp_link link; - struct drm_connector connector; + struct cdns_mhdp_connector connector; struct clk *spdif_clk; struct reset_control *spdif_rst; + struct platform_device *audio_pdev; + struct audio_info audio_info; + struct drm_dp_aux aux; struct cdns_mhdp_host host; struct cdns_mhdp_sink sink; - struct drm_bridge bridge; + struct cdns_mhdp_bridge bridge; struct phy *phy; - void __iomem *dbg_regs; struct video_info video_info; struct drm_display_mode mode; unsigned int fw_version; + + struct drm_dp_mst_topology_mgr mst_mgr; + struct delayed_work hotplug_work; + + struct cdns_mhdp_mst_cbs cbs; + bool is_mst; + bool can_mst; + bool link_up; + bool plugged; }; void cdns_mhdp_clock_reset(struct cdns_mhdp_device *mhdp); @@ -551,4 +592,11 @@ int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp, int cdns_mhdp_audio_mute(struct cdns_mhdp_device *mhdp, bool enable); int cdns_mhdp_audio_config(struct cdns_mhdp_device *mhdp, struct audio_info *audio); -#endif /* _CDN_DP_REG_H */ +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value); +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val); +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr, + u8 start_bit, u8 bits_no, u32 val); +int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp, u8 nlanes, + u16 udelay, u8 *lanes_data, + u8 *dpcd); +#endif /* CDNS_MHDP_COMMON_H_ */ -- 2.17.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel