diff options
| -rw-r--r-- | MAINTAINERS | 1 | ||||
| -rw-r--r-- | drivers/video/zynqmp/zynqmp_dpsub.c | 2197 | ||||
| -rw-r--r-- | drivers/video/zynqmp/zynqmp_dpsub.h | 676 |
3 files changed, 2855 insertions, 19 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 228d8af433d..23659e72583 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -755,6 +755,7 @@ F: drivers/spi/zynq_qspi.c F: drivers/spi/zynq_spi.c F: drivers/timer/cadence-ttc.c F: drivers/video/seps525.c +F: drivers/video/zynqmp/ F: drivers/watchdog/cdns_wdt.c F: include/zynqmppl.h F: include/zynqmp_firmware.h diff --git a/drivers/video/zynqmp/zynqmp_dpsub.c b/drivers/video/zynqmp/zynqmp_dpsub.c index 4ead663cd59..c287b475a8d 100644 --- a/drivers/video/zynqmp/zynqmp_dpsub.c +++ b/drivers/video/zynqmp/zynqmp_dpsub.c @@ -1,53 +1,2212 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2021 Xilinx Inc. + * Copyright (C) 2021 - 2022, Xilinx Inc. + * Copyright (C) 2022 - 2023, Advanced Micro Devices, Inc. + * + * Xilinx displayport(DP) Tx Subsytem driver */ #include <common.h> +#include <clk.h> #include <cpu_func.h> #include <dm.h> #include <errno.h> +#include <generic-phy.h> +#include <stdlib.h> #include <video.h> +#include <wait_bit.h> #include <dm/device_compat.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <dm/device_compat.h> +#include <asm/global_data.h> + +#include "zynqmp_dpsub.h" + +DECLARE_GLOBAL_DATA_PTR; + +/* Maximum supported resolution */ +#define WIDTH 640 +#define HEIGHT 480 + +static struct dp_dma dp_dma; +static struct dp_dma_descriptor cur_desc __aligned(256); + +static void dma_init_video_descriptor(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct dp_dma_frame_buffer *frame_buffer = &dp_sub->frame_buffer; + + cur_desc.control = DPDMA_DESC_PREAMBLE | DPDMA_DESC_IGNR_DONE | + DPDMA_DESC_LAST_FRAME; + cur_desc.dscr_id = 0; + cur_desc.xfer_size = frame_buffer->size; + cur_desc.line_size_stride = ((frame_buffer->stride >> 4) << + DPDMA_DESCRIPTOR_LINE_SIZE_STRIDE_SHIFT) | + (frame_buffer->line_size); + cur_desc.addr_ext = (((u32)(frame_buffer->address >> + DPDMA_DESCRIPTOR_SRC_ADDR_WIDTH) << + DPDMA_DESCRIPTOR_ADDR_EXT_SRC_ADDR_EXT_SHIFT) | + (upper_32_bits((u64)&cur_desc))); + cur_desc.next_desr = lower_32_bits((u64)&cur_desc); + cur_desc.src_addr = lower_32_bits((u64)gd->fb_base); +} + +static void dma_set_descriptor_address(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + flush_dcache_range((u64)&cur_desc, + ALIGN(((u64)&cur_desc + sizeof(cur_desc)), + CONFIG_SYS_CACHELINE_SIZE)); + writel(upper_32_bits((u64)&cur_desc), dp_sub->dp_dma->base_addr + + DPDMA_CH3_DSCR_STRT_ADDRE); + writel(lower_32_bits((u64)&cur_desc), dp_sub->dp_dma->base_addr + + DPDMA_CH3_DSCR_STRT_ADDR); +} + +static void dma_setup_channel(struct udevice *dev) +{ + dma_init_video_descriptor(dev); + dma_set_descriptor_address(dev); +} -#define WIDTH 640 -#define HEIGHT 480 +static void dma_set_channel_state(struct udevice *dev) +{ + u32 mask = 0, regval = 0; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + mask = DPDMA_CH_CNTL_EN_MASK | DPDMA_CH_CNTL_PAUSE_MASK; + regval = DPDMA_CH_CNTL_EN_MASK; + + clrsetbits_le32(dp_sub->dp_dma->base_addr + DPDMA_CH3_CNTL, + mask, regval); +} + +static void dma_trigger(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 trigger; + + trigger = DPDMA_GBL_TRG_CH3_MASK; + dp_sub->dp_dma->gfx.trigger_status = DPDMA_TRIGGER_DONE; + writel(trigger, dp_sub->dp_dma->base_addr + DPDMA_GBL); +} + +static void dma_vsync_intr_handler(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + dma_setup_channel(dev); + dma_set_channel_state(dev); + dma_trigger(dev); + + /* Clear VSync Interrupt */ + writel(DPDMA_ISR_VSYNC_INT_MASK, dp_sub->dp_dma->base_addr + DPDMA_ISR); +} /** - * struct zynqmp_dpsub_priv - Private structure - * @dev: Device uclass for video_ops + * wait_phy_ready() - Wait for the DisplayPort PHY to come out of reset + * @dev: The DP device + * + * Return: 0 if wait succeeded, -ve if error occurred */ -struct zynqmp_dpsub_priv { - struct udevice *dev; -}; +static int wait_phy_ready(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 timeout = 100, phy_status; + u8 phy_ready_mask = DP_PHY_STATUS_RESET_LANE_0_DONE_MASK | + DP_PHY_STATUS_GT_PLL_LOCK_MASK; + + /* Wait until the PHY is ready. */ + do { + udelay(20); + phy_status = readl(dp_sub->base_addr + DP_PHY_STATUS); + phy_status &= phy_ready_mask; + /* Protect against an infinite loop. */ + if (!timeout--) + return -ETIMEDOUT; + } while (phy_status != phy_ready_mask); + + return 0; +} + +static int init_dp_tx(struct udevice *dev) +{ + u32 status, phyval, regval, rate; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + phyval = readl(dp_sub->base_addr + DP_PHY_CONFIG); + writel(DP_SOFT_RESET_EN, dp_sub->base_addr + DP_SOFT_RESET); + status = readl(dp_sub->base_addr + DP_SOFT_RESET); + writel(DP_DISABLE, dp_sub->base_addr + DP_ENABLE); + + regval = (readl(dp_sub->base_addr + DP_AUX_CLK_DIVIDER) & + ~DP_AUX_CLK_DIVIDER_VAL_MASK) | + (60 << 8) | + (dp_sub->clock / 1000000); + writel(regval, dp_sub->base_addr + DP_AUX_CLK_DIVIDER); + + writel(DP_PHY_CLOCK_SELECT_540GBPS, dp_sub->base_addr + DP_PHY_CLOCK_SELECT); + + regval = phyval & ~DP_PHY_CONFIG_GT_ALL_RESET_MASK; + writel(regval, dp_sub->base_addr + DP_PHY_CONFIG); + status = wait_phy_ready(dev); + if (status) + return -EINVAL; + + writel(DP_ENABLE, dp_sub->base_addr + DP_ENABLE); + + rate = ~DP_INTR_HPD_PULSE_DETECTED_MASK & ~DP_INTR_HPD_EVENT_MASK + & ~DP_INTR_HPD_IRQ_MASK; + writel(rate, dp_sub->base_addr + DP_INTR_MASK); + return 0; +} + +static int set_nonlive_gfx_format(struct udevice *dev, enum av_buf_video_format format) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct av_buf_vid_attribute *ptr = (struct av_buf_vid_attribute *)avbuf_supported_formats; + + while (1) { + dev_dbg(dev, "Format %d\n", ptr->video_format); + + if (!ptr->video_format) + return -EINVAL; + + if (ptr->video_format == format) { + dp_sub->non_live_graphics = ptr; + break; + } + ptr++; + } + dev_dbg(dev, "Video format found. BPP %d\n", dp_sub->non_live_graphics->bpp); + return 0; +} + +/* DP dma setup */ +static void set_qos(struct udevice *dev, u8 qos) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 index; + u32 regval = 0, mask; + + regval = (((u32)qos << DPDMA_CH_CNTL_QOS_DATA_RD_SHIFT) | + ((u32)qos << DPDMA_CH_CNTL_QOS_DSCR_RD_SHIFT) | + ((u32)qos << DPDMA_CH_CNTL_QOS_DSCR_WR_SHIFT)); + + mask = DPDMA_CH_CNTL_QOS_DATA_RD_MASK | + DPDMA_CH_CNTL_QOS_DSCR_RD_MASK | + DPDMA_CH_CNTL_QOS_DSCR_WR_MASK; + for (index = 0; index <= DPDMA_AUDIO_CHANNEL1; index++) { + clrsetbits_le32(dp_sub->dp_dma->base_addr + + DPDMA_CH0_CNTL + + (DPDMA_CH_OFFSET * (u32)index), + mask, regval); + } +} + +static void enable_gfx_buffers(struct udevice *dev, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 regval = 0; + + regval = (0xF << AVBUF_CHBUF3_BURST_LEN_SHIFT) | + AVBUF_CHBUF3_FLUSH_MASK; + writel(regval, dp_sub->base_addr + AVBUF_CHBUF3); + if (enable) { + regval = (0xF << AVBUF_CHBUF3_BURST_LEN_SHIFT) | + AVBUF_CHBUF0_EN_MASK; + writel(regval, dp_sub->base_addr + AVBUF_CHBUF3); + } +} + +static void avbuf_video_select(struct udevice *dev, enum av_buf_video_stream vid_stream, + enum av_buf_gfx_stream gfx_stream) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + dp_sub->av_mode.video_src = vid_stream; + dp_sub->av_mode.gfx_src = gfx_stream; + + clrsetbits_le32(dp_sub->base_addr + + AVBUF_BUF_OUTPUT_AUD_VID_SELECT, + AVBUF_BUF_OUTPUT_AUD_VID_SELECT_VID_STREAM2_SEL_MASK | + AVBUF_BUF_OUTPUT_AUD_VID_SELECT_VID_STREAM1_SEL_MASK, + vid_stream | gfx_stream); +} + +static void config_gfx_pipeline(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u16 *csc_matrix, *offset_matrix; + u32 regval = 0, index = 0, *scaling_factors = NULL; + u16 rgb_coeffs[] = { 0x1000, 0x0000, 0x0000, + 0x0000, 0x1000, 0x0000, + 0x0000, 0x0000, 0x1000 }; + u16 rgb_offset[] = { 0x0000, 0x0000, 0x0000 }; + struct av_buf_vid_attribute *video = dp_sub->non_live_graphics; + + scaling_factors = video->sf; + + clrsetbits_le32(dp_sub->base_addr + AVBUF_BUF_FORMAT, + AVBUF_BUF_FORMAT_NL_GRAPHX_FORMAT_MASK, + (video->value) << AVBUF_BUF_FORMAT_NL_GRAPHX_FORMAT_SHIFT); + + for (index = 0; index < 3; index++) { + writel(scaling_factors[index], dp_sub->base_addr + + AVBUF_BUF_GRAPHICS_COMP0_SCALE_FACTOR + (index * 4)); + } + regval = (video->is_rgb << AVBUF_V_BLEND_LAYER0_CONTROL_RGB_MODE_SHIFT) | + video->sampling_en; + writel(regval, dp_sub->base_addr + AVBUF_V_BLEND_LAYER1_CONTROL); + + if (video->is_rgb) { + csc_matrix = rgb_coeffs; + offset_matrix = rgb_offset; + } + /* Program Colorspace conversion coefficients */ + for (index = 9; index < 12; index++) { + writel(offset_matrix[index - 9], dp_sub->base_addr + + AVBUF_V_BLEND_IN2CSC_COEFF0 + (index * 4)); + } + + /* Program Colorspace conversion matrix */ + for (index = 0; index < 9; index++) { + writel(csc_matrix[index], dp_sub->base_addr + + AVBUF_V_BLEND_IN2CSC_COEFF0 + (index * 4)); + } +} + +static void set_blender_alpha(struct udevice *dev, u8 alpha, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 regval; + + regval = enable; + regval |= alpha << AVBUF_V_BLEND_SET_GLOBAL_ALPHA_REG_VALUE_SHIFT; + writel(regval, dp_sub->base_addr + + AVBUF_V_BLEND_SET_GLOBAL_ALPHA_REG); +} + +static void config_output_video(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 regval = 0, index; + u16 rgb_coeffs[] = { 0x1000, 0x0000, 0x0000, + 0x0000, 0x1000, 0x0000, + 0x0000, 0x0000, 0x1000 }; + u16 rgb_offset[] = { 0x0000, 0x0000, 0x0000 }; + u16 *matrix_coeff = rgb_coeffs, *matrix_offset = rgb_offset; + + struct av_buf_vid_attribute *output_video = dp_sub->non_live_graphics; + + regval |= output_video->sampling_en << + AVBUF_V_BLEND_OUTPUT_VID_FORMAT_EN_DOWNSAMPLE_SHIFT; + regval |= output_video->value; + writel(regval, dp_sub->base_addr + AVBUF_V_BLEND_OUTPUT_VID_FORMAT); + + for (index = 0; index < 9; index++) { + writel(matrix_coeff[index], dp_sub->base_addr + + AVBUF_V_BLEND_RGB2YCBCR_COEFF0 + (index * 4)); + } + + for (index = 0; index < 3; index++) { + writel((matrix_offset[index] << + AVBUF_V_BLEND_LUMA_IN1CSC_OFFSET_POST_OFFSET_SHIFT), + dp_sub->base_addr + + AVBUF_V_BLEND_LUMA_OUTCSC_OFFSET + + (index * 4)); + } + + set_blender_alpha(dev, 0, 0); +} + +static void config_msa_sync_clk_mode(struct udevice *dev, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct main_stream_attributes *msa_config; + + msa_config = &dp_sub->msa_config; + msa_config->synchronous_clock_mode = enable; + + if (enable == 1) { + msa_config->misc0 |= (1 << + DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_SHIFT); + } else { + msa_config->misc0 &= ~(1 << + DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_SHIFT); + } +} + +static void av_buf_soft_reset(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + writel(AVBUF_BUF_SRST_REG_VID_RST_MASK, + dp_sub->base_addr + AVBUF_BUF_SRST_REG); + writel(0, dp_sub->base_addr + AVBUF_BUF_SRST_REG); +} + +static void set_video_clk_source(struct udevice *dev, u8 video_clk, u8 audio_clk) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 regval = 0; + + if (dp_sub->av_mode.video_src != AVBUF_VIDSTREAM1_LIVE && + dp_sub->av_mode.gfx_src != AVBUF_VIDSTREAM2_LIVE_GFX) { + regval = 1 << AVBUF_BUF_AUD_VID_CLK_SOURCE_VID_TIMING_SRC_SHIFT; + } else if (dp_sub->av_mode.video_src == AVBUF_VIDSTREAM1_LIVE || + dp_sub->av_mode.gfx_src == AVBUF_VIDSTREAM2_LIVE_GFX) { + video_clk = AVBUF_PL_CLK; + } + + regval |= (video_clk << AVBUF_BUF_AUD_VID_CLK_SOURCE_VID_CLK_SRC_SHIFT) | + (audio_clk << AVBUF_BUF_AUD_VID_CLK_SOURCE_AUD_CLK_SRC_SHIFT); + writel(regval, dp_sub->base_addr + AVBUF_BUF_AUD_VID_CLK_SOURCE); + + av_buf_soft_reset(dev); +} + +static int init_dpdma_subsys(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + dp_sub->dp_dma->base_addr = DPDMA_BASE_ADDRESS; + dp_sub->dp_dma->gfx.channel.cur = NULL; + dp_sub->dp_dma->gfx.trigger_status = DPDMA_TRIGGER_DONE; + + set_qos(dev, 11); + return 0; +} + +/** + * is_dp_connected() - Check if there is a connected RX device + * @dev: The DP device + * + * + * Return: true if a connected RX device was detected, false otherwise + */ +static bool is_dp_connected(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + u8 retries = 0; + + do { + status = readl(dp_sub->base_addr + + DP_INTERRUPT_SIG_STATE) + & DP_INTERRUPT_SIG_STATE_HPD_STATE_MASK; + + if (retries > DP_IS_CONNECTED_MAX_TIMEOUT_COUNT) + return 0; + + retries++; + udelay(1000); + } while (status == 0); + + return 1; +} + +/** + * aux_wait_ready() - Wait until another request is no longer in progress + * @dev: The DP device + * + * Return: 0 if wait succeeded, -ve if error occurred + */ +static int aux_wait_ready(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status, timeout = 100; + + do { + status = readl(dp_sub->base_addr + + DP_INTERRUPT_SIG_STATE); + if (!timeout--) + return -ETIMEDOUT; + + udelay(20); + } while (status & DP_REPLY_STATUS_REPLY_IN_PROGRESS_MASK); + + return 0; +} + +/** + * aux_wait_reply() - Wait for reply on AUX channel + * @dev: The DP device + * + * Wait for a reply indicating that the most recent AUX request + * has been received by the RX device. + * + * Return: 0 if wait succeeded, -ve if error occurred + */ +static int aux_wait_reply(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 timeout = DP_AUX_MAX_WAIT, status; + + while (timeout > 0) { + status = readl(dp_sub->base_addr + DP_REPLY_STATUS); + if (status & DP_REPLY_STATUS_REPLY_ERROR_MASK) + return -ETIMEDOUT; + + if ((status & DP_REPLY_STATUS_REPLY_RECEIVED_MASK) && + !(status & DP_REPLY_STATUS_REQUEST_IN_PROGRESS_MASK) && + !(status & DP_REPLY_STATUS_REPLY_IN_PROGRESS_MASK)) { + return 0; + } + timeout--; + udelay(20); + } + return -ETIMEDOUT; +} + +/** + * aux_request_send() - Send request on the AUX channel + * @dev: The DP device + * @request: The request to send + * + * Submit the supplied AUX request to the RX device over the AUX + * channel by writing the command, the destination address, (the write buffer + * for write commands), and the data size to the DisplayPort TX core. + * + * This is the lower-level sending routine, which is called by aux_request(). + * + * Return: 0 if request was sent successfully, -ve on error + */ +static int aux_request_send(struct udevice *dev, struct aux_transaction *request) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 timeout_count = 0, status; + u8 index; + + do { + status = readl(dp_sub->base_addr + + DP_REPLY_STATUS); + + udelay(20); + timeout_count++; + if (timeout_count >= DP_AUX_MAX_TIMEOUT_COUNT) + return -ETIMEDOUT; + + } while ((status & DP_REPLY_STATUS_REQUEST_IN_PROGRESS_MASK) || + (status & DP_REPLY_STATUS_REPLY_IN_PROGRESS_MASK)); + /* Set the address for the request. */ + writel(request->address, dp_sub->base_addr + DP_AUX_ADDRESS); + + if (request->cmd_code == DP_AUX_CMD_WRITE || + request->cmd_code == DP_AUX_CMD_I2C_WRITE || + request->cmd_code == DP_AUX_CMD_I2C_WRITE_MOT) { + /* Feed write data into the DisplayPort TX core's write FIFO. */ + for (index = 0; index < request->num_bytes; index++) { + writel(request->data[index], + dp_sub->base_addr + + DP_AUX_WRITE_FIFO); + } + } + + status = ((request->cmd_code << DP_AUX_CMD_SHIFT) | + ((request->num_bytes - 1) & + DP_AUX_CMD_NBYTES_TRANSFER_MASK)); + + /* Submit the command and the data size. */ + writel(((request->cmd_code << DP_AUX_CMD_SHIFT) | + ((request->num_bytes - 1) & DP_AUX_CMD_NBYTES_TRANSFER_MASK)), + dp_sub->base_addr + DP_AUX_CMD); + + /* Check for a reply from the RX device to the submitted request. */ + status = aux_wait_reply(dev); + if (status) + /* Waiting for a reply timed out. */ + return -ETIMEDOUT; + + /* Analyze the reply. */ + status = readl(dp_sub->base_addr + DP_AUX_REPLY_CODE); + if (status == DP_AUX_REPLY_CODE_DEFER || + status == DP_AUX_REPLY_CODE_I2C_DEFER) { + /* The request was deferred. */ + return -EAGAIN; + } else if (status == DP_AUX_REPLY_CODE_NACK || + status == DP_AUX_REPLY_CODE_I2C_NACK) { + /* The request was not acknowledged. */ + return -EIO; + } + + /* The request was acknowledged. */ + if (request->cmd_code == DP_AUX_CMD_READ || + request->cmd_code == DP_AUX_CMD_I2C_READ || + request->cmd_code == DP_AUX_CMD_I2C_READ_MOT) { + /* Wait until all data has been received. */ + timeout_count = 0; + do { + status = readl(dp_sub->base_addr + + DP_REPLY_DATA_COUNT); + udelay(100); + timeout_count++; + if (timeout_count >= DP_AUX_MAX_TIMEOUT_COUNT) + return -ETIMEDOUT; + } while (status != request->num_bytes); + + /* Obtain the read data from the reply FIFO. */ + for (index = 0; index < request->num_bytes; index++) { + request->data[index] = readl(dp_sub->base_addr + + DP_AUX_REPLY_DATA); + } + } + return 0; +} + +/** + * aux_request() - Submit request on the AUX channel + * @dev: The DP device + * @request: The request to submit + * + * Submit the supplied AUX request to the RX device over the AUX + * channel. If waiting for a reply times out, or if the DisplayPort TX core + * indicates that the request was deferred, the request is sent again (up to a + * maximum specified by DP_AUX_MAX_DEFER_COUNT|DP_AUX_MAX_TIMEOUT_COUNT). + * + * Return: 0 if request was submitted successfully, -ve on error + */ +static int aux_request(struct udevice *dev, struct aux_transaction *request) +{ + u32 status, defer_count = 0, timeout_count = 0; + + do { + status = aux_wait_ready(dev); + if (status) { + /* The RX device isn't ready yet. */ + timeout_count++; + continue; + } + /* Send the request. */ + status = aux_request_send(dev, request); + if (status == -EAGAIN) { + /* The request was deferred. */ + defer_count++; + } else if (status == -ETIMEDOUT) { + /* Waiting for a reply timed out. */ + timeout_count++; + } else { + return status; + } + + udelay(100); + } while ((defer_count < DP_AUX_MAX_DEFER_COUNT) && + (timeout_count < DP_AUX_MAX_TIMEOUT_COUNT)); + + /* The request was not successfully received by the RX device. */ + return -ETIMEDOUT; +} + +/** + * aux_common() - Common (read/write) AUX communication transmission + * @dev: The DP device + * @cmd_type: Command code of the transaction + * @address: The DPCD address of the transaction + * @num_bytes: Number of bytes in the payload data + * @data: The payload data of the AUX command + * + * Common sequence of submitting an AUX command for AUX read, AUX write, + * I2C-over-AUX read, and I2C-over-AUX write transactions. If required, the + * reads and writes are split into multiple requests, each acting on a maximum + * of 16 bytes. + * + * Return: 0 if OK, -ve on error + */ +static int aux_common(struct udevice *dev, u32 cmd_type, u32 address, + u32 num_bytes, u8 *data) +{ + u32 status, bytes_left; + struct aux_transaction request; + + if (!is_dp_connected(dev)) + return -ENODEV; + + /* + * Set the start address for AUX transactions. For I2C transactions, + * this is the address of the I2C bus. + */ + request.address = address; + bytes_left = num_bytes; + while (bytes_left > 0) { + request.cmd_code = cmd_type; + + if (cmd_type == DP_AUX_CMD_READ || + cmd_type == DP_AUX_CMD_WRITE) { + /* Increment address for normal AUX transactions. */ + request.address = address + (num_bytes - bytes_left); + } + + /* Increment the pointer to the supplied data buffer. */ + request.data = &data[num_bytes - bytes_left]; + + if (bytes_left > 16) + request.num_bytes = 16; + else + request.num_bytes = bytes_left; + + bytes_left -= request.num_bytes; + + if (cmd_type == DP_AUX_CMD_I2C_READ && bytes_left > 0) { + /* + * Middle of a transaction I2C read request. Override + * the command code that was set to CmdType. + */ + request.cmd_code = DP_AUX_CMD_I2C_READ_MOT; + } else if (cmd_type == DP_AUX_CMD_I2C_WRITE && bytes_left > 0) { + /* + * Middle of a transaction I2C write request. Override + * the command code that was set to CmdType. + */ + request.cmd_code = DP_AUX_CMD_I2C_WRITE_MOT; + } + + status = aux_request(dev, &request); + if (status) + return status; + } + return 0; +} + +/** + * aux_write() - Issue AUX write request + * @dev: The DP device + * @dpcd_address: The DPCD address to write to + * @bytes_to_write: Number of bytes to write + * @write_data: Buffer containig data to be written + * + * Issue a write request over the AUX channel that will write to + * the RX device's DisplayPort Configuration data (DPCD) address space. The + * write message will be divided into multiple transactions which write a + * maximum of 16 bytes each. + * + * Return: 0 if write operation was successful, -ve on error + */ +static int aux_write(struct udevice *dev, u32 dpcd_address, u32 bytes_to_write, + void *write_data) +{ + return aux_common(dev, DP_AUX_CMD_WRITE, dpcd_address, + bytes_to_write, (u8 *)write_data); +} + +/** + * aux_read() - Issue AUX read request + * @dev: The DP device + * @dpcd_address: The DPCD address to read from + * @bytes_to_read: Number of bytes to read + * @read_data: Buffer to receive the read data + * + * Issue a read request over the AUX channel that will read from the RX + * device's DisplayPort Configuration data (DPCD) address space. The read + * message will be divided into multiple transactions which read a maximum of + * 16 bytes each. + * + * Return: 0 if read operation was successful, -ve on error + */ +static int aux_read(struct udevice *dev, u32 dpcd_address, u32 bytes_to_read, void *read_data) +{ + return aux_common(dev, DP_AUX_CMD_READ, dpcd_address, + bytes_to_read, (u8 *)read_data); +} + +static int dp_tx_wakeup(struct udevice *dev) +{ + u32 status; + u8 aux_data; + + aux_data = 0x1; + status = aux_write(dev, DP_DPCD_SET_POWER_DP_PWR_VOLTAGE, 1, &aux_data); + if (status) + debug("! 1st power wake-up - AUX write failed.\n"); + status = aux_write(dev, DP_DPCD_SET_POWER_DP_PWR_VOLTAGE, 1, &aux_data); + if (status) + debug("! 2nd power wake-up - AUX write failed.\n"); + + return status; +} + +/** + * enable_main_link() - Switch on main link for a device + * @dev: The DP device + */ +static void enable_main_link(struct udevice *dev, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + /* Reset the scrambler. */ + writel(1, dp_sub->base_addr + DP_FORCE_SCRAMBLER_RESET); + /* Enable the main stream. */ + writel(enable, dp_sub->base_addr + DP_ENABLE_MAIN_STREAM); +} + +/** + * get_rx_capabilities() - Check if capabilities of RX device are valid for TX + * device + * @dev: The DP device + * + * Return: 0 if the capabilities of the RX device are valid for the TX device, + * -ve if not, of an error occurred during capability determination + */ +static int get_rx_capabilities(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 rx_max_link_rate, rx_max_lane_count, *dpcd = NULL; + u32 status; + struct link_config *link_config = NULL; + + dpcd = dp_sub->dpcd_rx_caps; + link_config = &dp_sub->link_config; + + status = aux_read(dev, DP_DPCD_RECEIVER_CAP_FIELD_START, 16, dpcd); + if (status) + return status; + + rx_max_link_rate = dpcd[DP_DPCD_MAX_LINK_RATE]; + rx_max_lane_count = dpcd[DP_DPCD_MAX_LANE_COUNT] & DP_DPCD_MAX_LANE_COUNT_MASK; + link_config->max_link_rate = (rx_max_link_rate > DP_0_LINK_RATE) ? + DP_0_LINK_RATE : rx_max_link_rate; + link_config->max_lane_count = (rx_max_lane_count > DP_0_LANE_COUNT) ? + DP_0_LANE_COUNT : rx_max_lane_count; + link_config->support_enhanced_framing_mode = dpcd[DP_DPCD_MAX_LANE_COUNT] & + DP_DPCD_ENHANCED_FRAME_SUPPORT_MASK; + link_config->support_downspread_control = dpcd[DP_DPCD_MAX_DOWNSPREAD] & + DP_DPCD_MAX_DOWNSPREAD_MASK; + + return 0; +} + +/** + * set_enhanced_frame_mode() - Enable/Disable enhanced frame mode + * @dev: The DP device + * @enable: Flag to determine whether to enable (1) or disable (0) the enhanced + * frame mode + * + * Enable or disable the enhanced framing symbol sequence for + * both the DisplayPort TX core and the RX device. + * + * Return: 0 if enabling/disabling the enhanced frame mode was successful, -ve + * on error + */ +static int set_enhanced_frame_mode(struct udevice *dev, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + u8 regval; + + dp_sub->link_config.enhanced_framing_mode = enable; + /* Write enhanced frame mode enable to the DisplayPort TX core. */ + writel(dp_sub->link_config.enhanced_framing_mode, + dp_sub->base_addr + DP_ENHANCED_FRAME_EN); + + /* Preserve the current RX device settings. */ + status = aux_read(dev, DP_DPCD_LANE_COUNT_SET, 0x1, ®val); + if (status) + return status; + + if (dp_sub->link_config.enhanced_framing_mode) + regval |= DP_DPCD_ENHANCED_FRAME_EN_MASK; + else + regval &= ~DP_DPCD_ENHANCED_FRAME_EN_MASK; + + /* Write enhanced frame mode enable to the RX device. */ + return aux_write(dev, DP_DPCD_LANE_COUNT_SET, 0x1, ®val); +} + +/** + * set_lane_count() - Set the lane count + * @dev: The DP device + * @lane_count: Lane count to set + * + * Set the number of lanes to be used by the main link for both + * the DisplayPort TX core and the RX device. + * + * Return: 0 if setting the lane count was successful, -ve on error + */ +static int set_lane_count(struct udevice *dev, u8 lane_count) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + u8 regval; + + dp_sub->link_config.lane_count = lane_count; + /* Write the new lane count to the DisplayPort TX core. */ + writel(dp_sub->link_config.lane_count, + dp_sub->base_addr + DP_LANE_COUNT_SET); + + /* Preserve the current RX device settings. */ + status = aux_read(dev, DP_DPCD_LANE_COUNT_SET, 0x1, ®val); + if (status) + return status; + + regval &= ~DP_DPCD_LANE_COUNT_SET_MASK; + regval |= dp_sub->link_config.lane_count; + + /* Write the new lane count to the RX device. */ + return aux_write(dev, DP_DPCD_LANE_COUNT_SET, 0x1, ®val); +} + +/** + * set_clk_speed() - Set DP phy clock speed + * @dev: The DP device + * @speed: The clock frquency to set (one of PHY_CLOCK_SELECT_*) + * + * Set the clock frequency for the DisplayPort PHY corresponding to a desired + * data rate. + * + * Return: 0 if setting the DP phy clock speed was successful, -ve on error + */ +static int set_clk_speed(struct udevice *dev, u32 speed) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 regval; + + /* Disable the DisplayPort TX core first. */ + regval = readl(dp_sub->base_addr + DP_ENABLE); + writel(0, dp_sub->base_addr + DP_ENABLE); + + /* Change speed of the feedback clock. */ + writel(speed, dp_sub->base_addr + DP_PHY_CLOCK_SELECT); + + /* Re-enable the DisplayPort TX core if it was previously enabled. */ + if (regval) + writel(regval, dp_sub->base_addr + DP_ENABLE); + + /* Wait until the PHY is ready. */ + return wait_phy_ready(dev); +} + +/** + * set_link_rate() - Set the link rate + * @dev: The DP device + * @link_rate: The link rate to set (one of LINK_BW_SET_*) + * + * Set the data rate to be used by the main link for both the DisplayPort TX + * core and the RX device. + * + * Return: 0 if setting the link rate was successful, -ve on error + */ +static int set_link_rate(struct udevice *dev, u8 link_rate) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + + /* Write a corresponding clock frequency to the DisplayPort TX core. */ + switch (link_rate) { + case DP_LINK_BW_SET_162GBPS: + status = set_clk_speed(dev, DP_PHY_CLOCK_SELECT_162GBPS); + break; + case DP_LINK_BW_SET_270GBPS: + status = set_clk_speed(dev, DP_PHY_CLOCK_SELECT_270GBPS); + break; + case DP_LINK_BW_SET_540GBPS: + status = set_clk_speed(dev, DP_PHY_CLOCK_SELECT_540GBPS); + break; + default: + status = -EINVAL; + break; + } + if (status) + return status; + + dp_sub->link_config.link_rate = link_rate; + /* Write new link rate to the DisplayPort TX core. */ + writel(dp_sub->link_config.link_rate, + dp_sub->base_addr + + DP_LINK_BW_SET); + + /* Write new link rate to the RX device. */ + return aux_write(dev, DP_DPCD_LINK_BW_SET, 0x1, + &dp_sub->link_config.link_rate); +} + +static int set_downspread(struct udevice *dev, u8 enable) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + u8 regval; + + dp_sub->link_config.support_downspread_control = enable; + /* Write downspread enable to the DisplayPort TX core. */ + writel(dp_sub->link_config.support_downspread_control, + dp_sub->base_addr + DP_DOWNSPREAD_CTRL); + + /* Preserve the current RX device settings. */ + status = aux_read(dev, DP_DPCD_DOWNSPREAD_CTRL, 0x1, ®val); + if (status) + return status; + + if (dp_sub->link_config.support_downspread_control) + regval |= DP_DPCD_SPREAD_AMP_MASK; + else + regval &= ~DP_DPCD_SPREAD_AMP_MASK; + + /* Write downspread enable to the RX device. */ + return aux_write(dev, DP_DPCD_DOWNSPREAD_CTRL, 0x1, ®val); +} + +static void set_serdes_vswing_preemp(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 index; + u8 vs_level_rx = dp_sub->link_config.vs_level; + u8 pe_level_rx = dp_sub->link_config.pe_level; + + for (index = 0; index < dp_sub->link_config.lane_count; index++) { + /* Write new voltage swing levels to the TX registers. */ + writel(vs[pe_level_rx][vs_level_rx], (ulong)SERDES_BASEADDR + + SERDES_L0_TX_MARGININGF + index * SERDES_LANE_OFFSET); + /* Write new pre-emphasis levels to the TX registers. */ + writel(pe[pe_level_rx][vs_level_rx], (ulong)SERDES_BASEADDR + + SERDES_L0_TX_DEEMPHASIS + index * SERDES_LANE_OFFSET); + } +} + +/** + * set_vswing_preemp() - Build AUX data to set voltage swing and pre-emphasis + * @dev: The DP device + * @aux_data: Buffer to receive the built AUX data + * + * Build AUX data to set current voltage swing and pre-emphasis level settings; + * the necessary data is taken from the link_config structure. + */ +static void set_vswing_preemp(struct udevice *dev, u8 *aux_data) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 data = 0; + u8 vs_level_rx = dp_sub->link_config.vs_level; + u8 pe_level_rx = dp_sub->link_config.pe_level; + + if (vs_level_rx >= DP_MAXIMUM_VS_LEVEL) + data |= DP_DPCD_TRAINING_LANEX_SET_MAX_VS_MASK; + + /* The maximum pre-emphasis level has been reached. */ + if (pe_level_rx >= DP_MAXIMUM_PE_LEVEL) + data |= DP_DPCD_TRAINING_LANEX_SET_MAX_PE_MASK; + + /* Set up the data buffer for writing to the RX device. */ + data |= (pe_level_rx << DP_DPCD_TRAINING_LANEX_SET_PE_SHIFT) | + vs_level_rx; + memset(aux_data, data, 4); + + set_serdes_vswing_preemp(dev); +} + +static int set_training_pattern(struct udevice *dev, u32 pattern) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 aux_data[5]; + + writel(pattern, dp_sub->base_addr + TRAINING_PATTERN_SET); + + aux_data[0] = pattern; + switch (pattern) { + case TRAINING_PATTERN_SET_OFF: + writel(0, dp_sub->base_addr + SCRAMBLING_DISABLE); + dp_sub->link_config.scrambler_en = 1; + break; + case TRAINING_PATTERN_SET_TP1: + case TRAINING_PATTERN_SET_TP2: + case TRAINING_PATTERN_SET_TP3: + aux_data[0] |= DP_DPCD_TP_SET_SCRAMB_DIS_MASK; + writel(1, dp_sub->base_addr + SCRAMBLING_DISABLE); + dp_sub->link_config.scrambler_en = 0; + break; + default: + break; + } + /* + * Make the adjustments to both the DisplayPort TX core and the RX + * device. + */ + set_vswing_preemp(dev, &aux_data[1]); + /* + * Write the voltage swing and pre-emphasis levels for each lane to the + * RX device. + */ + if (pattern == TRAINING_PATTERN_SET_OFF) + return aux_write(dev, DP_DPCD_TP_SET, 1, aux_data); + else + return aux_write(dev, DP_DPCD_TP_SET, 5, aux_data); +} + +static int get_lane_status_adj_reqs(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + u8 aux_data[8]; + + status = aux_read(dev, DP_DPCD_SINK_COUNT, 8, aux_data); + if (status) + return status; + + /* Save XDPPSU_DPCD_SINK_COUNT contents. */ + dp_sub->sink_count = + ((aux_data[0] & DP_DPCD_SINK_COUNT_HIGH_MASK) >> + DP_DPCD_SINK_COUNT_HIGH_LOW_SHIFT) | + (aux_data[0] & DP_DPCD_SINK_COUNT_LOW_MASK); + memcpy(dp_sub->lane_status_ajd_reqs, &aux_data[2], 6); + return 0; +} + +/** + * check_clock_recovery() - Check clock recovery success + * @dev: The LogiCore DP TX device in question + * @lane_count: The number of lanes for which to check clock recovery success + * + * Check if the RX device's DisplayPort Configuration data (DPCD) indicates + * that the clock recovery sequence during link training was successful - the + * RX device's link clock and data recovery unit has realized and maintained + * the frequency lock for all lanes currently in use. + * + * Return: 0 if clock recovery was successful on all lanes in question, -ve if + * not + */ +static int check_clock_recovery(struct udevice *dev, u8 lane_count) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 *lane_status = dp_sub->lane_status_ajd_reqs; + + switch (lane_count) { + case DP_LANE_COUNT_SET_2: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_1_CR_DONE_MASK)) + return -EINVAL; + case DP_LANE_COUNT_SET_1: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_0_CR_DONE_MASK)) + return -EINVAL; + default: + /* All (LaneCount) lanes have achieved clock recovery. */ + break; + } + return 0; +} + +/** + * adj_vswing_preemp() - Adjust voltage swing and pre-emphasis + * @dev: The DP device + * + * Set new voltage swing and pre-emphasis levels using the + * adjustment requests obtained from the RX device. + * + * Return: 0 if voltage swing and pre-emphasis could be adjusted successfully, + * -ve on error + */ +static int adj_vswing_preemp(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 index, vs_level_adj_req[4], pe_level_adj_req[4]; + u8 aux_data[4]; + u8 *adj_reqs = &dp_sub->lane_status_ajd_reqs[4]; + + /* + * Analyze the adjustment requests for changes in voltage swing and + * pre-emphasis levels. + */ + vs_level_adj_req[0] = adj_reqs[0] & DP_DPCD_ADJ_REQ_LANE_0_2_VS_MASK; + vs_level_adj_req[1] = (adj_reqs[0] & DP_DPCD_ADJ_REQ_LANE_1_3_VS_MASK) >> + DP_DPCD_ADJ_REQ_LANE_1_3_VS_SHIFT; + pe_level_adj_req[0] = (adj_reqs[0] & DP_DPCD_ADJ_REQ_LANE_0_2_PE_MASK) >> + DP_DPCD_ADJ_REQ_LANE_0_2_PE_SHIFT; + pe_level_adj_req[1] = (adj_reqs[0] & DP_DPCD_ADJ_REQ_LANE_1_3_PE_MASK) >> + DP_DPCD_ADJ_REQ_LANE_1_3_PE_SHIFT; + + /* + * Change the drive settings to match the adjustment requests. Use the + * greatest level requested. + */ + dp_sub->link_config.vs_level = 0; + dp_sub->link_config.pe_level = 0; + for (index = 0; index < dp_sub->link_config.lane_count; index++) { + if (vs_level_adj_req[index] > dp_sub->link_config.vs_level) + dp_sub->link_config.vs_level = vs_level_adj_req[index]; + + if (pe_level_adj_req[index] > dp_sub->link_config.pe_level) + dp_sub->link_config.pe_level = pe_level_adj_req[index]; + } + + if (dp_sub->link_config.pe_level > DP_MAXIMUM_PE_LEVEL) + dp_sub->link_config.pe_level = DP_MAXIMUM_PE_LEVEL; + + if (dp_sub->link_config.vs_level > DP_MAXIMUM_VS_LEVEL) + dp_sub->link_config.vs_level = DP_MAXIMUM_VS_LEVEL; + + if (dp_sub->link_config.pe_level > + (4 - dp_sub->link_config.vs_level)) { + dp_sub->link_config.pe_level = + 4 - dp_sub->link_config.vs_level; + } + /* + * Make the adjustments to both the DisplayPort TX core and the RX + * device. + */ + set_vswing_preemp(dev, aux_data); + /* + * Write the voltage swing and pre-emphasis levels for each lane to the + * RX device. + */ + return aux_write(dev, DP_DPCD_TRAINING_LANE0_SET, 2, aux_data); +} + +/** + * get_training_delay() - Get training delay + * @dev: The DP device + * @training_state: The training state for which the required training delay + * should be queried + * + * Determine what the RX device's required training delay is for + * link training. + * + * Return: The training delay in us + */ +static u32 get_training_delay(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 *dpcd = dp_sub->dpcd_rx_caps; + + if (dpcd[DP_DPCD_TRAIN_AUX_RD_INTERVAL]) + return 400 * dpcd[DP_DPCD_TRAIN_AUX_RD_INTERVAL] * 10; + + return 400; +} + +/** + * training_state_clock_recovery() - Run clock recovery part of link training + * @dev: The DP device + * + * Run the clock recovery sequence as part of link training. The + * sequence is as follows: + * + * 0) Start signaling at the minimum voltage swing, pre-emphasis, and + * post- cursor levels. + * 1) Transmit training pattern 1 over the main link with symbol + * scrambling disabled. + * 2) The clock recovery loop. If clock recovery is unsuccessful after + * MaxIterations loop iterations, return. + * 2a) Wait for at least the period of time specified in the RX device's + * DisplayPort Configuration data (DPCD) register, + * TRAINING_AUX_RD_INTERVAL. + * 2b) Check if all lanes have achieved clock recovery lock. If so, + * return. + * 2c) Check if the same voltage swing level has been used 5 consecutive + * times or if the maximum level has been reached. If so, return. + * 2d) Adjust the voltage swing, pre-emphasis, and post-cursor levels as + * requested by the RX device. + * 2e) Loop back to 2a. + * + * For a more detailed description of the clock recovery sequence, see section + * 3.5.1.2.1 of the DisplayPort 1.2a specification document. + * + * Return: The next state machine state to advance to + */ +static enum link_training_states training_state_clock_recovery(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status, delay_us; + u8 prev_vs_level = 0, same_vs_level_count = 0; + struct link_config *link_config = &dp_sub->link_config; + + delay_us = get_training_delay(dev); + /* Start CRLock. */ + /* Start from minimal voltage swing and pre-emphasis levels. */ + dp_sub->link_config.vs_level = 0; + dp_sub->link_config.pe_level = 0; + /* Transmit training pattern 1. */ + status = set_training_pattern(dev, TRAINING_PATTERN_SET_TP1); + if (status) + return TS_FAILURE; + + while (1) { + /* Wait delay specified in TRAINING_AUX_RD_INTERVAL. */ + udelay(delay_us); + /* Get lane and adjustment requests. */ + status = get_lane_status_adj_reqs(dev); + if (status) + /* The AUX read failed. */ + return TS_FAILURE; + + /* + * Check if all lanes have realized and maintained the frequency + * lock and get adjustment requests. + */ + status = check_clock_recovery(dev, dp_sub->link_config.lane_count); + if (status == 0) + return TS_CHANNEL_EQUALIZATION; + /* + * Check if the same voltage swing for each lane has been used 5 + * consecutive times. + */ + if (prev_vs_level == link_config->vs_level) { + same_vs_level_count++; + } else { + same_vs_level_count = 0; + prev_vs_level = link_config->vs_level; + } + if (same_vs_level_count >= 5) + break; + + /* Only try maximum voltage swing once. */ + if (link_config->vs_level == DP_MAXIMUM_VS_LEVEL) + break; + + /* Adjust the drive settings as requested by the RX device. */ + status = adj_vswing_preemp(dev); + if (status) + /* The AUX write failed. */ + return TS_FAILURE; + } + return TS_ADJUST_LINK_RATE; +} + +/** + * check_channel_equalization() - Check channel equalization success + * @dev: The DP device + * @lane_count: The number of lanes for which to check channel equalization + * success + * + * Check if the RX device's DisplayPort Configuration data (DPCD) indicates + * that the channel equalization sequence during link training was successful - + * the RX device has achieved channel equalization, symbol lock, and interlane + * alignment for all lanes currently in use. + * + * Return: 0 if channel equalization was successful on all lanes in question, + * -ve if not + */ +static int check_channel_equalization(struct udevice *dev, u8 lane_count) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 *lane_status = dp_sub->lane_status_ajd_reqs; + + /* Check that all LANEx_CHANNEL_EQ_DONE bits are set. */ + switch (lane_count) { + case DP_LANE_COUNT_SET_2: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_1_CE_DONE_MASK)) + return -EINVAL; + case DP_LANE_COUNT_SET_1: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_0_CE_DONE_MASK)) + return -EINVAL; + default: + /* All (LaneCount) lanes have achieved channel equalization. */ + break; + } + + /* Check that all LANEx_SYMBOL_LOCKED bits are set. */ + switch (lane_count) { + case DP_LANE_COUNT_SET_2: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_1_SL_DONE_MASK)) + return -EINVAL; + case DP_LANE_COUNT_SET_1: + if (!(lane_status[0] & DP_DPCD_STATUS_LANE_0_SL_DONE_MASK)) + return -EINVAL; + default: + /* All (LaneCount) lanes have achieved symbol lock. */ + break; + } + + /* Check that interlane alignment is done. */ + if (!(lane_status[2] & DP_DPCD_LANE_ALIGN_STATUS_UPDATED_IA_DONE_MASK)) + return -EINVAL; + return 0; +} + +/** + * training_state_channel_equalization() - Run channel equalization part of + * link training + * @dev: The DP device + * + * Run the channel equalization sequence as part of link + * training. The sequence is as follows: + * + * 0) Start signaling with the same drive settings used at the end of the + * clock recovery sequence. + * 1) Transmit training pattern 2 (or 3) over the main link with symbol + * scrambling disabled. + * 2) The channel equalization loop. If channel equalization is + * unsuccessful after 5 loop iterations, return. + * 2a) Wait for at least the period of time specified in the RX device's + * DisplayPort Configuration data (DPCD) register, + * TRAINING_AUX_RD_INTERVAL. + * 2b) Check if all lanes have achieved channel equalization, symbol lock, + * and interlane alignment. If so, return. + * 2c) Check if the same voltage swing level has been used 5 consecutive + * times or if the maximum level has been reached. If so, return. + * 2d) Adjust the voltage swing, pre-emphasis, and post-cursor levels as + * requested by the RX device. + * 2e) Loop back to 2a. + * + * For a more detailed description of the channel equalization sequence, see + * section 3.5.1.2.2 of the DisplayPort 1.2a specification document. + * + * Return: The next state machine state to advance to + */ +static enum link_training_states training_state_channel_equalization(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status, delay_us = 400, iteration_count = 0; + + /* Write the current drive settings. */ + /* Transmit training pattern 2/3. */ + if (dp_sub->dpcd_rx_caps[DP_DPCD_MAX_LANE_COUNT] & + DP_DPCD_TPS3_SUPPORT_MASK) + status = set_training_pattern(dev, TRAINING_PATTERN_SET_TP3); + else + status = set_training_pattern(dev, TRAINING_PATTERN_SET_TP2); + + if (status) + return TS_FAILURE; + + while (iteration_count < 5) { + /* Wait delay specified in TRAINING_AUX_RD_INTERVAL. */ + udelay(delay_us); + + /* Get lane and adjustment requests. */ + status = get_lane_status_adj_reqs(dev); + if (status) + /* The AUX read failed. */ + return TS_FAILURE; + + /* Adjust the drive settings as requested by the RX device. */ + status = adj_vswing_preemp(dev); + if (status) + /* The AUX write failed. */ + return TS_FAILURE; + + /* Check that all lanes still have their clocks locked. */ + status = check_clock_recovery(dev, dp_sub->link_config.lane_count); + if (status) + break; + /* + * Check that all lanes have accomplished channel + * equalization, symbol lock, and interlane alignment. + */ + status = check_channel_equalization(dev, dp_sub->link_config.lane_count); + if (status == 0) + return TS_SUCCESS; + iteration_count++; + } + + /* + * Tried 5 times with no success. Try a reduced bitrate first, then + * reduce the number of lanes. + */ + return TS_ADJUST_LINK_RATE; +} + +static int check_lane_align(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u8 *lane_status = dp_sub->lane_status_ajd_reqs; + + if (!(lane_status[2] & DP_DPCD_LANE_ALIGN_STATUS_UPDATED_IA_DONE_MASK)) + return -EINVAL; + return 0; +} + +/** + * check_link_status() - Check status of link + * @dev: The DP device + * @lane_count: The lane count to use for the check + * + * Check if the receiver's DisplayPort Configuration data (DPCD) indicates the + * receiver has achieved and maintained clock recovery, channel equalization, + * symbol lock, and interlane alignment for all lanes currently in use. + * + * Return: 0 if the link status is OK, -ve if a error occurred during checking + */ +static int check_link_status(struct udevice *dev, u8 lane_count) +{ + u32 status; + + status = get_lane_status_adj_reqs(dev); + if (status) + /* The AUX read failed. */ + return status; + + /* Check if the link needs training. */ + if ((check_clock_recovery(dev, lane_count) == 0) && + (check_channel_equalization(dev, lane_count) == 0) && + (check_lane_align(dev) == 0)) { + return 0; + } + return -EINVAL; +} + +/** + * run_training() - Run link training + * @dev: The DP device + * + * Run the link training process. It is implemented as a state machine, with + * each state returning the next state. First, the clock recovery sequence will + * be run; if successful, the channel equalization sequence will run. If either + * the clock recovery or channel equalization sequence failed, the link rate or + * the number of lanes used will be reduced and training will be re-attempted. + * If training fails at the minimal data rate, 1.62 Gbps with a single lane, + * training will no longer re-attempt and fail. + * + * There are undocumented timeout constraints in the link training process. In + * DP v1.2a spec, Chapter 3.5.1.2.2 a 10ms limit for the complete training + * process is mentioned. Which individual timeouts are derived and implemented + * by sink manufacturers is unknown. So each step should be as short as + * possible and link training should start as soon as possible after HPD. + * + * Return: 0 if the training sequence ran successfully, -ve if a error occurred + * or the training failed + */ +static int run_training(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status; + enum link_training_states training_state = TS_CLOCK_RECOVERY; + + while (1) { + switch (training_state) { + case TS_CLOCK_RECOVERY: + training_state = training_state_clock_recovery(dev); + break; + case TS_CHANNEL_EQUALIZATION: + training_state = training_state_channel_equalization(dev); + break; + default: + break; + } + + if (training_state == TS_SUCCESS) + break; + else if (training_state == TS_FAILURE) + return -EINVAL; + + if (training_state == TS_ADJUST_LANE_COUNT || + training_state == TS_ADJUST_LINK_RATE) { + status = set_training_pattern(dev, TRAINING_PATTERN_SET_OFF); + if (status) + return -EINVAL; + } + } + + /* Final status check. */ + return check_link_status(dev, dp_sub->link_config.lane_count); +} + +void reset_dp_phy(struct udevice *dev, u32 reset) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 phyval, regval; + + writel(0, dp_sub->base_addr + DP_ENABLE); + phyval = readl(dp_sub->base_addr + DP_PHY_CONFIG); + regval = phyval | reset; + writel(regval, dp_sub->base_addr + DP_PHY_CONFIG); + /* Remove the reset. */ + writel(phyval, dp_sub->base_addr + DP_PHY_CONFIG); + /* Wait for the PHY to be ready. */ + wait_phy_ready(dev); + + writel(1, dp_sub->base_addr + DP_ENABLE); +} + +/** + * establish_link() - Establish a link + * @dev: The DP device + * + * Check if the link needs training and run the training sequence if training + * is required. + * + * Return: 0 if the link was established successfully, -ve on error + */ +static int establish_link(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 status, re_enable_main_link; + + reset_dp_phy(dev, DP_PHY_CONFIG_TX_PHY_8B10BEN_MASK | + DP_PHY_CONFIG_PHY_RESET_MASK); + + re_enable_main_link = readl(dp_sub->base_addr + DP_ENABLE_MAIN_STREAM); + if (re_enable_main_link) + enable_main_link(dev, 0); + + status = run_training(dev); + if (status) + return status; + + status = set_training_pattern(dev, TRAINING_PATTERN_SET_OFF); + if (status) + return status; + + if (re_enable_main_link) + enable_main_link(dev, 1); + + return check_link_status(dev, dp_sub->link_config.lane_count); +} + +static int dp_hpd_train(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct link_config *link_config = &dp_sub->link_config; + u32 status; + + status = get_rx_capabilities(dev); + if (status) { + debug("! Error getting RX caps.\n"); + return status; + } + + status = set_enhanced_frame_mode(dev, link_config->support_enhanced_framing_mode ? 1 : 0); + if (status) { + debug("! EFM set failed.\n"); + return status; + } + + status = set_lane_count(dev, (dp_sub->use_max_lane_count) ? + link_config->max_lane_count : dp_sub->lane_count); + if (status) { + debug("! Lane count set failed.\n"); + return status; + } + + status = set_link_rate(dev, (dp_sub->use_max_link_rate) ? + link_config->max_link_rate : dp_sub->link_rate); + if (status) { + debug("! Link rate set failed.\n"); + return status; + } + + status = set_downspread(dev, link_config->support_downspread_control); + if (status) { + debug("! Setting downspread failed.\n"); + return status; + } + + debug("Lane count =%d\n", dp_sub->link_config.lane_count); + debug("Link rate =%d\n", dp_sub->link_config.link_rate); + + debug("Starting Training...\n"); + status = establish_link(dev); + if (status == 0) + debug("! Training succeeded.\n"); + else + debug("! Training failed.\n"); + + return status; +} + +static void display_gfx_frame_buffer(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + if (!dp_sub->dp_dma->gfx.channel.cur) + dp_sub->dp_dma->gfx.trigger_status = DPDMA_TRIGGER_EN; +} + +static void set_color_encode(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct main_stream_attributes *msa_config = &dp_sub->msa_config; + + msa_config->y_cb_cr_colorimetry = 0; + msa_config->dynamic_range = 0; + msa_config->component_format = 0; + msa_config->misc0 = 0; + msa_config->misc1 = 0; + msa_config->component_format = DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_RGB; +} + +static void config_msa_recalculate(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + u32 video_bw, link_bw, words_per_line; + u8 bits_per_pixel; + struct main_stream_attributes *msa_config; + struct link_config *link_config; + + msa_config = &dp_sub->msa_config; + link_config = &dp_sub->link_config; + + msa_config->user_pixel_width = 1; + + /* Compute the rest of the MSA values. */ + msa_config->n_vid = 27 * 1000 * link_config->link_rate; + msa_config->h_start = msa_config->vid_timing_mode.video_timing.h_sync_width + + msa_config->vid_timing_mode.video_timing.h_back_porch; + msa_config->v_start = msa_config->vid_timing_mode.video_timing.f0_pv_sync_width + + msa_config->vid_timing_mode.video_timing.f0_pv_back_porch; + + /* Miscellaneous attributes. */ + if (msa_config->bits_per_color == 6) + msa_config->misc0 = DP_MAIN_STREAM_MISC0_BDC_6BPC; + else if (msa_config->bits_per_color == 8) + msa_config->misc0 = DP_MAIN_STREAM_MISC0_BDC_8BPC; + else if (msa_config->bits_per_color == 10) + msa_config->misc0 = DP_MAIN_STREAM_MISC0_BDC_10BPC; + else if (msa_config->bits_per_color == 12) + msa_config->misc0 = DP_MAIN_STREAM_MISC0_BDC_12BPC; + else if (msa_config->bits_per_color == 16) + msa_config->misc0 = DP_MAIN_STREAM_MISC0_BDC_16BPC; + + msa_config->misc0 <<= DP_MAIN_STREAM_MISC0_BDC_SHIFT; + + /* Need to set this. */ + msa_config->misc0 |= msa_config->component_format << + DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_SHIFT; + + msa_config->misc0 |= msa_config->dynamic_range << + DP_MAIN_STREAM_MISC0_DYNAMIC_RANGE_SHIFT; + + msa_config->misc0 |= msa_config->y_cb_cr_colorimetry << + DP_MAIN_STREAM_MISC0_YCBCR_COLORIMETRY_SHIFT; + + msa_config->misc0 |= msa_config->synchronous_clock_mode; + /* + * Determine the number of bits per pixel for the specified color + * component format. + */ + if (msa_config->misc1 == DP_MAIN_STREAM_MISC1_Y_ONLY_EN_MASK) + bits_per_pixel = msa_config->bits_per_color; + else if (msa_config->component_format == + DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_YCBCR422) + /* YCbCr422 color component format. */ + bits_per_pixel = msa_config->bits_per_color * 2; + else + /* RGB or YCbCr 4:4:4 color component format. */ + bits_per_pixel = msa_config->bits_per_color * 3; + + /* Calculate the data per lane. */ + words_per_line = msa_config->vid_timing_mode.video_timing.h_active * bits_per_pixel; + if (words_per_line % 16) + words_per_line += 16; + + words_per_line /= 16; + msa_config->data_per_lane = words_per_line - link_config->lane_count; + if (words_per_line % link_config->lane_count) + msa_config->data_per_lane += (words_per_line % link_config->lane_count); + + /* Allocate a fixed size for single-stream transport (SST) operation. */ + msa_config->transfer_unit_size = 64; + + /* + * Calculate the average number of bytes per transfer unit. + * Note: Both the integer and the fractional part is stored in + * AvgBytesPerTU. + */ + video_bw = ((msa_config->pixel_clock_hz / 1000) * bits_per_pixel) / 8; + link_bw = (link_config->lane_count * link_config->link_rate * 27); + msa_config->avg_bytes_per_tu = ((10 * + (video_bw * msa_config->transfer_unit_size) + / link_bw) + 5) / 10; + /* + * The number of initial wait cycles at the start of a new line by the + * framing logic. This allows enough data to be buffered in the input + * FIFO before video is sent. + */ + if ((msa_config->avg_bytes_per_tu / 1000) <= 4) + msa_config->init_wait = 64; + else + msa_config->init_wait = msa_config->transfer_unit_size - + (msa_config->avg_bytes_per_tu / 1000); +} + +static void set_msa_bpc(struct udevice *dev, u8 bits_per_color) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + dp_sub->msa_config.bits_per_color = bits_per_color; + /* Calculate the rest of the MSA values. */ + config_msa_recalculate(dev); +} + +const struct video_timing_mode *get_video_mode_data(enum video_mode vm_id) +{ + if (vm_id < VIDC_VM_NUM_SUPPORTED) + return &vidc_video_timing_modes[vm_id]; + + return NULL; +} + +static u64 get_pixelclk_by_vmid(enum video_mode vm_id) +{ + const struct video_timing_mode *vm; + u64 clk_hz; + + vm = get_video_mode_data(vm_id); + /* For progressive mode, use only frame 0 vertical total. */ + clk_hz = vm->video_timing.f0_pv_total; + /* Multiply the number of pixels by the frame rate. */ + clk_hz *= vm->frame_rate; + + /* + * Multiply the vertical total by the horizontal total for number of + * pixels. + */ + clk_hz *= vm->video_timing.h_total; + + return clk_hz; +} + +/** + * config_msa_video_mode() - Enable video output + * @dev: The DP device + * @msa: The MSA values to set for the device + * + * Return: 0 if the video was enabled successfully, -ve on error + */ +static void config_msa_video_mode(struct udevice *dev, enum video_mode videomode) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct main_stream_attributes *msa_config; + + msa_config = &dp_sub->msa_config; + + /* Configure the MSA values from the display monitor DMT table. */ + msa_config->vid_timing_mode.vid_mode = vidc_video_timing_modes[videomode].vid_mode; + msa_config->vid_timing_mode.frame_rate = vidc_video_timing_modes[videomode].frame_rate; + msa_config->vid_timing_mode.video_timing.h_active = + vidc_video_timing_modes[videomode].video_timing.h_active; + msa_config->vid_timing_mode.video_timing.h_front_porch = + vidc_video_timing_modes[videomode].video_timing.h_front_porch; + msa_config->vid_timing_mode.video_timing.h_sync_width = + vidc_video_timing_modes[videomode].video_timing.h_sync_width; + msa_config->vid_timing_mode.video_timing.h_back_porch = + vidc_video_timing_modes[videomode].video_timing.h_back_porch; + msa_config->vid_timing_mode.video_timing.h_total = + vidc_video_timing_modes[videomode].video_timing.h_total; + msa_config->vid_timing_mode.video_timing.h_sync_polarity = + vidc_video_timing_modes[videomode].video_timing.h_sync_polarity; + msa_config->vid_timing_mode.video_timing.v_active = + vidc_video_timing_modes[videomode].video_timing.v_active; + msa_config->vid_timing_mode.video_timing.f0_pv_front_porch = + vidc_video_timing_modes[videomode].video_timing.f0_pv_front_porch; + msa_config->vid_timing_mode.video_timing.f0_pv_sync_width = + vidc_video_timing_modes[videomode].video_timing.f0_pv_sync_width; + msa_config->vid_timing_mode.video_timing.f0_pv_back_porch = + vidc_video_timing_modes[videomode].video_timing.f0_pv_back_porch; + msa_config->vid_timing_mode.video_timing.f0_pv_total = + vidc_video_timing_modes[videomode].video_timing.f0_pv_total; + msa_config->vid_timing_mode.video_timing.f1_v_front_porch = + vidc_video_timing_modes[videomode].video_timing.f1_v_front_porch; + msa_config->vid_timing_mode.video_timing.f1_v_sync_width = + vidc_video_timing_modes[videomode].video_timing.f1_v_sync_width; + msa_config->vid_timing_mode.video_timing.f1_v_back_porch = + vidc_video_timing_modes[videomode].video_timing.f1_v_back_porch; + msa_config->vid_timing_mode.video_timing.f1_v_total = + vidc_video_timing_modes[videomode].video_timing.f1_v_total; + msa_config->vid_timing_mode.video_timing.v_sync_polarity = + vidc_video_timing_modes[videomode].video_timing.v_sync_polarity; + msa_config->pixel_clock_hz = get_pixelclk_by_vmid(msa_config->vid_timing_mode.vid_mode); + + /* Calculate the rest of the MSA values. */ + config_msa_recalculate(dev); +} + +static void set_pixel_clock(u64 freq_hz) +{ + u64 ext_divider, vco, vco_int_frac; + u32 pll_assigned, frac_int_fb_div, fraction, regpll = 0; + u8 pll; + + pll_assigned = readl(CLK_FPD_BASEADDR + VIDEO_REF_CTRL) & VIDEO_REF_CTRL_SRCSEL_MASK; + if (pll_assigned) + pll = VPLL; + + ext_divider = PLL_OUT_FREQ / freq_hz; + vco = freq_hz * ext_divider * 2; + vco_int_frac = (vco * INPUT_FREQ_PRECISION * SHIFT_DECIMAL) / + AVBUF_INPUT_REF_CLK; + frac_int_fb_div = vco_int_frac >> PRECISION; + fraction = vco_int_frac & AVBUF_DECIMAL; + + regpll |= ENABLE_BIT << PLL_CTRL_BYPASS_SHIFT; + regpll |= frac_int_fb_div << PLL_CTRL_FBDIV_SHIFT; + regpll |= (1 << PLL_CTRL_DIV2_SHIFT); + regpll |= (PSS_REF_CLK << PLL_CTRL_PRE_SRC_SHIFT); + writel(regpll, CLK_FPD_BASEADDR + VPLL_CTRL); + + regpll = 0; + regpll |= VPLL_CFG_CP << PLL_CFG_CP_SHIFT; + regpll |= VPLL_CFG_RES << PLL_CFG_RES_SHIFT; + regpll |= VPLL_CFG_LFHF << PLL_CFG_LFHF_SHIFT; + regpll |= VPLL_CFG_LOCK_DLY << PLL_CFG_LOCK_DLY_SHIFT; + regpll |= VPLL_CFG_LOCK_CNT << PLL_CFG_LOCK_CNT_SHIFT; + writel(regpll, CLK_FPD_BASEADDR + VPLL_CFG); + + regpll = (1U << PLL_FRAC_CFG_ENABLED_SHIFT) | + (fraction << PLL_FRAC_CFG_DATA_SHIFT); + writel(regpll, CLK_FPD_BASEADDR + VPLL_FRAC_CFG); + + clrsetbits_le32(CLK_FPD_BASEADDR + VPLL_CTRL, + PLL_CTRL_RESET_MASK, + (ENABLE_BIT << PLL_CTRL_RESET_SHIFT)); + + /* Deassert reset to the PLL. */ + clrsetbits_le32(CLK_FPD_BASEADDR + VPLL_CTRL, + PLL_CTRL_RESET_MASK, + (DISABLE_BIT << PLL_CTRL_RESET_SHIFT)); + + while (!(readl(CLK_FPD_BASEADDR + PLL_STATUS) & + (1 << PLL_STATUS_VPLL_LOCK))) + ; + + /* Deassert Bypass. */ + clrsetbits_le32(CLK_FPD_BASEADDR + VPLL_CTRL, + PLL_CTRL_BYPASS_MASK, + (DISABLE_BIT << PLL_CTRL_BYPASS_SHIFT)); + udelay(1); + + clrsetbits_le32(CLK_FPD_BASEADDR + VIDEO_REF_CTRL, + VIDEO_REF_CTRL_CLKACT_MASK, + (DISABLE_BIT << VIDEO_REF_CTRL_CLKACT_SHIFT)); + + clrsetbits_le32(CLK_FPD_BASEADDR + VIDEO_REF_CTRL, + VIDEO_REF_CTRL_DIVISOR1_MASK, + (ENABLE_BIT << VIDEO_REF_CTRL_DIVISOR1_SHIFT)); + + clrsetbits_le32(CLK_FPD_BASEADDR + VIDEO_REF_CTRL, + VIDEO_REF_CTRL_DIVISOR0_MASK, + (ext_divider << VIDEO_REF_CTRL_DIVISOR0_SHIFT)); + + clrsetbits_le32(CLK_FPD_BASEADDR + VIDEO_REF_CTRL, + VIDEO_REF_CTRL_CLKACT_MASK, + (ENABLE_BIT << VIDEO_REF_CTRL_CLKACT_SHIFT)); +} + +/** + * set_msa_values() - Set MSA values + * @dev: The DP device + * + * Set the main stream attributes registers of the DisplayPort TX + * core with the values specified in the main stream attributes configuration + * structure. + */ +static void set_msa_values(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct main_stream_attributes *msa_config; + + msa_config = &dp_sub->msa_config; + + /* + * Set the main stream attributes to the associated DisplayPort TX core + * registers. + */ + writel(msa_config->vid_timing_mode.video_timing.h_total, + dp_sub->base_addr + DP_MAIN_STREAM_HTOTAL); + writel(msa_config->vid_timing_mode.video_timing.f0_pv_total, + dp_sub->base_addr + DP_MAIN_STREAM_VTOTAL); + writel(msa_config->vid_timing_mode.video_timing.h_sync_polarity | + (msa_config->vid_timing_mode.video_timing.v_sync_polarity + << DP_MAIN_STREAM_POLARITY_VSYNC_POL_SHIFT), + dp_sub->base_addr + DP_MAIN_STREAM_POLARITY); + writel(msa_config->vid_timing_mode.video_timing.h_sync_width, + dp_sub->base_addr + DP_MAIN_STREAM_HSWIDTH); + writel(msa_config->vid_timing_mode.video_timing.f0_pv_sync_width, + dp_sub->base_addr + DP_MAIN_STREAM_VSWIDTH); + writel(msa_config->vid_timing_mode.video_timing.h_active, + dp_sub->base_addr + DP_MAIN_STREAM_HRES); + writel(msa_config->vid_timing_mode.video_timing.v_active, + dp_sub->base_addr + DP_MAIN_STREAM_VRES); + writel(msa_config->h_start, dp_sub->base_addr + DP_MAIN_STREAM_HSTART); + writel(msa_config->v_start, dp_sub->base_addr + DP_MAIN_STREAM_VSTART); + writel(msa_config->misc0, dp_sub->base_addr + DP_MAIN_STREAM_MISC0); + writel(msa_config->misc1, dp_sub->base_addr + DP_MAIN_STREAM_MISC1); + writel(msa_config->pixel_clock_hz / 1000, dp_sub->base_addr + DP_M_VID); + writel(msa_config->n_vid, dp_sub->base_addr + DP_N_VID); + writel(msa_config->user_pixel_width, dp_sub->base_addr + DP_USER_PIXEL_WIDTH); + writel(msa_config->data_per_lane, dp_sub->base_addr + DP_USER_DATA_COUNT_PER_LANE); + /* + * Set the transfer unit values to the associated DisplayPort TX core + * registers. + */ + writel(msa_config->transfer_unit_size, dp_sub->base_addr + DP_TU_SIZE); + writel(msa_config->avg_bytes_per_tu / 1000, + dp_sub->base_addr + DP_MIN_BYTES_PER_TU); + writel((msa_config->avg_bytes_per_tu % 1000) * 1000, + dp_sub->base_addr + DP_FRAC_BYTES_PER_TU); + writel(msa_config->init_wait, dp_sub->base_addr + DP_INIT_WAIT); +} + +static void setup_video_stream(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + struct main_stream_attributes *msa_config = &dp_sub->msa_config; + + set_color_encode(dev); + set_msa_bpc(dev, dp_sub->bpc); + config_msa_video_mode(dev, dp_sub->video_mode); + + /* Set pixel clock. */ + dp_sub->pix_clk = msa_config->pixel_clock_hz; + set_pixel_clock(dp_sub->pix_clk); + + /* Reset the transmitter. */ + writel(1, dp_sub->base_addr + DP_SOFT_RESET); + udelay(10); + writel(0, dp_sub->base_addr + DP_SOFT_RESET); + + set_msa_values(dev); + + /* Issuing a soft-reset (AV_BUF_SRST_REG). */ + writel(3, dp_sub->base_addr + AVBUF_BUF_SRST_REG); // Assert reset. + udelay(10); + writel(0, dp_sub->base_addr + AVBUF_BUF_SRST_REG); // De-ssert reset. + + enable_main_link(dev, 1); + + debug("DONE!\n"); +} + +static int dp_tx_start_link_training(struct udevice *dev) +{ + u32 status; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + enable_main_link(dev, 0); + + if (!is_dp_connected(dev)) { + debug("! Disconnected.\n"); + return -ENODEV; + } + + status = dp_tx_wakeup(dev); + if (status) { + debug("! Wakeup failed.\n"); + return -EIO; + } + + do { + mdelay(100); + status = dp_hpd_train(dev); + if (status == -EINVAL) { + debug("Lost connection\n\r"); + return -EIO; + } else if (status) { + continue; + } + display_gfx_frame_buffer(dev); + setup_video_stream(dev); + status = check_link_status(dev, dp_sub->link_config.lane_count); + if (status == -EINVAL) + return -EIO; + } while (status != 0); + + return 0; +} + +static void init_run_config(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + dp_sub->dp_dma = &dp_dma; + dp_sub->video_mode = VIDC_VM_640x480_60_P; + dp_sub->bpc = VIDC_BPC_8; + dp_sub->color_encode = DP_CENC_RGB; + dp_sub->use_max_cfg_caps = 1; + dp_sub->lane_count = LANE_COUNT_1; + dp_sub->link_rate = LINK_RATE_540GBPS; + dp_sub->en_sync_clk_mode = 0; + dp_sub->use_max_lane_count = 1; + dp_sub->use_max_link_rate = 1; +} + +static int dpdma_setup(struct udevice *dev) +{ + int status; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + writel(DPDMA_ISR_VSYNC_INT_MASK, dp_sub->dp_dma->base_addr + DPDMA_IEN); + status = wait_for_bit_le32((u32 *)dp_sub->dp_dma->base_addr + DPDMA_ISR, + DPDMA_ISR_VSYNC_INT_MASK, false, 1000, false); + if (status) { + debug("%s: INTR TIMEDOUT\n", __func__); + return status; + } + debug("INTR dma_vsync_intr_handler called...\n"); + dma_vsync_intr_handler(dev); + + return 0; +} + +static int zynqmp_dpsub_init(struct udevice *dev) +{ + int status; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + /* Initialize the dpdma configuration */ + status = init_dpdma_subsys(dev); + if (status) + return -EINVAL; + + config_msa_sync_clk_mode(dev, dp_sub->en_sync_clk_mode); + set_video_clk_source(dev, AVBUF_PS_CLK, AVBUF_PS_CLK); + + return 0; +} + +static int dp_tx_run(struct udevice *dev) +{ + u32 interrupt_signal_state, interrupt_status, hpd_state, hpd_event; + u32 hpd_pulse_detected, hpd_duration, status; + int attempts = 0; + struct zynqmp_dpsub_priv *dp_sub = dev_get_priv(dev); + + /* Continuously poll for HPD events. */ + while (attempts < 5) { + /* Read interrupt registers. */ + interrupt_signal_state = readl(dp_sub->base_addr + DP_INTERRUPT_SIG_STATE); + interrupt_status = readl(dp_sub->base_addr + DP_INTR_STATUS); + /* Check for HPD events. */ + hpd_state = interrupt_signal_state & DP_INTERRUPT_SIG_STATE_HPD_STATE_MASK; + hpd_event = interrupt_status & DP_INTR_HPD_EVENT_MASK; + hpd_pulse_detected = interrupt_status & DP_INTR_HPD_PULSE_DETECTED_MASK; + if (hpd_pulse_detected) + hpd_duration = readl(dp_sub->base_addr + DP_HPD_DURATION); + else + attempts++; + + /* HPD event handling. */ + if (hpd_state && hpd_event) { + debug("+===> HPD connection event detected.\n"); + /* Initiate link training. */ + status = dp_tx_start_link_training(dev); + if (status) { + debug("Link training failed\n"); + return status; + } + return 0; + } else if (hpd_state && hpd_pulse_detected && (hpd_duration >= 250)) { + debug("===> HPD pulse detected.\n"); + /* Re-train if needed. */ + status = dp_tx_start_link_training(dev); + if (status) { + debug("HPD pulse detection failed\n"); + return status; + } + return 0; + } else if (!hpd_state && hpd_event) { + debug("+===> HPD disconnection event detected.\n\n"); + /* Disable main link. */ + enable_main_link(dev, 0); + break; + } + } + return -EINVAL; +} static int zynqmp_dpsub_probe(struct udevice *dev) { struct video_priv *uc_priv = dev_get_uclass_priv(dev); struct zynqmp_dpsub_priv *priv = dev_get_priv(dev); + struct clk clk; + int ret; + int mode = RGBA8888; - uc_priv->bpix = VIDEO_BPP16; - uc_priv->xsize = WIDTH; - uc_priv->ysize = HEIGHT; - uc_priv->rot = 0; + ret = clk_get_by_name(dev, "dp_apb_clk", &clk); + if (ret < 0) { + dev_err(dev, "failed to get clock\n"); + return ret; + } - priv->dev = dev; + priv->clock = clk_get_rate(&clk); + if (IS_ERR_VALUE(priv->clock)) { + dev_err(dev, "failed to get rate\n"); + return priv->clock; + } - /* Only placeholder for power domain driver */ - return 0; + ret = clk_enable(&clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + dev_dbg(dev, "Base addr 0x%x, clock %d\n", (u32)priv->base_addr, + priv->clock); + + /* Initialize the DisplayPort TX core. */ + ret = init_dp_tx(dev); + if (ret) + return -EINVAL; + + /* Initialize the runtime configuration */ + init_run_config(dev); + /* Set the format graphics frame for Video Pipeline */ + ret = set_nonlive_gfx_format(dev, mode); + if (ret) + return ret; + + uc_priv->bpix = ffs(priv->non_live_graphics->bpp) - 1; + dev_dbg(dev, "BPP in bits %d, bpix %d\n", + priv->non_live_graphics->bpp, uc_priv->bpix); + + uc_priv->fb = (void *)gd->fb_base; + uc_priv->xsize = vidc_video_timing_modes[priv->video_mode].video_timing.h_active; + uc_priv->ysize = vidc_video_timing_modes[priv->video_mode].video_timing.v_active; + /* Calculated by core but need it for my own setup */ + uc_priv->line_length = uc_priv->xsize * VNBYTES(uc_priv->bpix); + /* Will be calculated again in video_post_probe() but I need that value now */ + uc_priv->fb_size = uc_priv->line_length * uc_priv->ysize; + + switch (mode) { + case RGBA8888: + uc_priv->format = VIDEO_RGBA8888; + break; + default: + debug("Unsupported mode\n"); + return -EINVAL; + } + + video_set_flush_dcache(dev, true); + debug("Video: WIDTH[%d]xHEIGHT[%d]xBPP[%d/%d] -- line length %d\n", uc_priv->xsize, + uc_priv->ysize, uc_priv->bpix, VNBYTES(uc_priv->bpix), uc_priv->line_length); + + enable_gfx_buffers(dev, 1); + avbuf_video_select(dev, AVBUF_VIDSTREAM1_NONE, AVBUF_VIDSTREAM2_NONLIVE_GFX); + config_gfx_pipeline(dev); + config_output_video(dev); + + ret = zynqmp_dpsub_init(dev); + if (ret) + return ret; + + /* Populate the FrameBuffer structure with the frame attributes */ + priv->frame_buffer.stride = uc_priv->line_length; + priv->frame_buffer.line_size = priv->frame_buffer.stride; + priv->frame_buffer.size = priv->frame_buffer.line_size * uc_priv->ysize; + + ret = dp_tx_run(dev); + if (ret) + return ret; + + return dpdma_setup(dev); } static int zynqmp_dpsub_bind(struct udevice *dev) { struct video_uc_plat *plat = dev_get_uclass_plat(dev); - plat->size = WIDTH * HEIGHT * 16; + /* This is maximum size to allocate - it depends on BPP setting */ + plat->size = WIDTH * HEIGHT * 4; + /* plat->align is not defined that's why 1MB alignment is used */ + + /* + * plat->base can be used for allocating own location for FB + * if not defined then it is allocated by u-boot itself + */ return 0; } -static const struct video_ops zynqmp_dpsub_ops = { -}; +static int zynqmp_dpsub_of_to_plat(struct udevice *dev) +{ + struct zynqmp_dpsub_priv *priv = dev_get_priv(dev); + struct resource res; + int ret; + + ret = dev_read_resource_byname(dev, "dp", &res); + if (ret) + return ret; + + priv->base_addr = res.start; + + return 0; +} static const struct udevice_id zynqmp_dpsub_ids[] = { { .compatible = "xlnx,zynqmp-dpsub-1.7" }, @@ -58,9 +2217,9 @@ U_BOOT_DRIVER(zynqmp_dpsub_video) = { .name = "zynqmp_dpsub_video", .id = UCLASS_VIDEO, .of_match = zynqmp_dpsub_ids, - .ops = &zynqmp_dpsub_ops, .plat_auto = sizeof(struct video_uc_plat), .bind = zynqmp_dpsub_bind, .probe = zynqmp_dpsub_probe, .priv_auto = sizeof(struct zynqmp_dpsub_priv), + .of_to_plat = zynqmp_dpsub_of_to_plat, }; diff --git a/drivers/video/zynqmp/zynqmp_dpsub.h b/drivers/video/zynqmp/zynqmp_dpsub.h new file mode 100644 index 00000000000..d2a6f1f4c7c --- /dev/null +++ b/drivers/video/zynqmp/zynqmp_dpsub.h @@ -0,0 +1,676 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023, Advanced Micro Devices, Inc. + * + */ + +#ifndef _VIDEO_ZYNQMP_DPSUB_H +#define _VIDEO_ZYNQMP_DPSUB_H + +enum video_mode { + VIDC_VM_640x480_60_P = 0, +}; + +enum { + LANE_COUNT_1 = 1, + LANE_COUNT_2 = 2, +}; + +enum { + LINK_RATE_162GBPS = 0x06, + LINK_RATE_270GBPS = 0x0A, + LINK_RATE_540GBPS = 0x14, +}; + +enum video_color_depth { + VIDC_BPC_6 = 6, + VIDC_BPC_8 = 8, + VIDC_BPC_10 = 10, + VIDC_BPC_12 = 12, + VIDC_BPC_14 = 14, + VIDC_BPC_16 = 16, + VIDC_BPC_NUM_SUPPORTED = 6, + VIDC_BPC_UNKNOWN +}; + +enum video_color_encoding { + DP_CENC_RGB = 0, + DP_CENC_YONLY, +}; + +enum dp_dma_channel_type { + VIDEO_CHAN, + GRAPHICS_CHAN, +}; + +enum dp_dma_channel_state { + DPDMA_DISABLE, + DPDMA_ENABLE, + DPDMA_IDLE, + DPDMA_PAUSE +}; + +enum link_training_states { + TS_CLOCK_RECOVERY, + TS_CHANNEL_EQUALIZATION, + TS_ADJUST_LINK_RATE, + TS_ADJUST_LANE_COUNT, + TS_FAILURE, + TS_SUCCESS +}; + +enum video_frame_rate { + VIDC_FR_60HZ = 60, + VIDC_FR_NUM_SUPPORTED = 2, + VIDC_FR_UNKNOWN +}; + +enum av_buf_video_modes { + INTERLEAVED, + SEMIPLANAR +}; + +enum av_buf_video_format { + RGBA8888 = 1, +}; + +enum av_buf_video_stream { + AVBUF_VIDSTREAM1_LIVE, + AVBUF_VIDSTREAM1_NONLIVE, + AVBUF_VIDSTREAM1_TPG, + AVBUF_VIDSTREAM1_NONE, +}; + +enum av_buf_gfx_stream { + AVBUF_VIDSTREAM2_DISABLEGFX = 0x0, + AVBUF_VIDSTREAM2_NONLIVE_GFX = 0x4, + AVBUF_VIDSTREAM2_LIVE_GFX = 0x8, + AVBUF_VIDSTREAM2_NONE = 0xC0, +}; + +/** + * struct aux_transaction - Description of an AUX channel transaction + * @cmd_code: Command code of the transaction + * @num_bytes: The number of bytes in the transaction's payload data + * @address: The DPCD address of the transaction + * @data: Payload data of the AUX channel transaction + */ +struct aux_transaction { + u16 cmd_code; + u8 num_bytes; + u32 address; + u8 *data; +}; + +/** + * struct link_config - Description of link configuration + * @lane_count: Currently selected lane count for this link + * @link_rate: Currently selected link rate for this link + * @scrambler_en: Flag to determine whether the scrambler is + * enabled for this link + * @enhanced_framing_mode: Flag to determine whether enhanced framing + * mode is active for this link + * @max_lane_count: Maximum lane count for this link + * @max_link_rate: Maximum link rate for this link + * @support_enhanced_framing_mode: Flag to indicate whether the link supports + * enhanced framing mode + * @vs_level: Voltage swing for each lane + * @pe_level: Pre-emphasis/cursor level for each lane + * @pattern: The current pattern currently in use over the main link + */ +struct link_config { + u8 lane_count; + u8 link_rate; + u8 scrambler_en; + u8 enhanced_framing_mode; + u8 max_lane_count; + u8 max_link_rate; + u8 support_enhanced_framing_mode; + u8 support_downspread_control; + u8 vs_level; + u8 pe_level; + u8 pattern; +}; + +struct video_timing { + u16 h_active; + u16 h_front_porch; + u16 h_sync_width; + u16 h_back_porch; + u16 h_total; + bool h_sync_polarity; + u16 v_active; + u16 f0_pv_front_porch; + u16 f0_pv_sync_width; + u16 f0_pv_back_porch; + u16 f0_pv_total; + u16 f1_v_front_porch; + u16 f1_v_sync_width; + u16 f1_v_back_porch; + u16 f1_v_total; + bool v_sync_polarity; +}; + +struct video_timing_mode { + enum video_mode vid_mode; + char name[21]; + enum video_frame_rate frame_rate; + struct video_timing video_timing; +}; + +/* + * struct main_stream_attributes - Main Stream Attributes (MSA) + * @pixel_clock_hz: The pixel clock of the stream (in Hz) + * @h_start: Horizontal blank start (in pixels) + * @v_start: Vertical blank start (in lines). + * @misc0: Miscellaneous stream attributes 0 + * @misc1: Miscellaneous stream attributes 1 + * @n_vid N value for the video stream + * @user_pixel_width: The width of the user data input port. + * @data_per_plane: Used to translate the number of pixels per + * line to the native internal 16-bit datapath. + * @avg_bytes_per_tu: Average number of bytes per transfer unit, + * scaled up by a factor of 1000. + * @transfer_unit_size: Size of the transfer unit in the + * framing logic. + * @init_wait: Number of initial wait cycles at the start + * of a new line by the framing logic. + * @bits_per_color: Number of bits per color component. + * @component_format: The component format currently in + * use by the video stream. + * @dynamic_range: The dynamic range currently in use + * by the video stream. + * @y_cb_cr_colorimetry: The YCbCr colorimetry currently in + * use by the video stream. + * @synchronous_clock_mode: Synchronous clock mode is currently + * in use by the video stream. + */ +struct main_stream_attributes { + struct video_timing_mode vid_timing_mode; + u32 pixel_clock_hz; + u32 h_start; + u32 v_start; + u32 misc0; + u32 misc1; + u32 n_vid; + u32 user_pixel_width; + u32 data_per_lane; + u32 avg_bytes_per_tu; + u32 transfer_unit_size; + u32 init_wait; + u32 bits_per_color; + u8 component_format; + u8 dynamic_range; + u8 y_cb_cr_colorimetry; + u8 synchronous_clock_mode; +}; + +struct av_buf_vid_attribute { + enum av_buf_video_format video_format; + u8 value; + enum av_buf_video_modes mode; + u32 sf[3]; + u8 sampling_en; + u8 is_rgb; + u8 swap; + u8 bpp; +}; + +struct av_buf_mode { + enum av_buf_video_stream video_src; + enum av_buf_gfx_stream gfx_src; + u8 video_clk; +}; + +struct dp_dma_descriptor { + u32 control; + u32 dscr_id; + u32 xfer_size; + u32 line_size_stride; + u32 lsb_timestamp; + u32 msb_timestamp; + u32 addr_ext; + u32 next_desr; + u32 src_addr; + u32 addr_ext_23; + u32 addr_ext_45; + u32 src_addr2; + u32 src_addr3; + u32 src_addr4; + u32 src_addr5; + u32 crc; +}; + +struct dp_dma_channel { + struct dp_dma_descriptor *cur; +}; + +struct dp_dma_frame_buffer { + u64 address; + u32 size; + u32 stride; + u32 line_size; +}; + +struct dp_dma_gfx_channel { + struct dp_dma_channel channel; + u8 trigger_status; + u8 av_buf_en; + struct dp_dma_frame_buffer *frame_buffer; +}; + +struct dp_dma { + phys_addr_t base_addr; + struct dp_dma_gfx_channel gfx; +}; + +/** + * struct zynqmp_dpsub_priv - Private structure + * @dev: Device uclass for video_ops + */ +struct zynqmp_dpsub_priv { + phys_addr_t base_addr; + u32 clock; + struct av_buf_vid_attribute *non_live_graphics; + struct av_buf_mode av_mode; + struct dp_dma_frame_buffer frame_buffer; + + struct link_config link_config; + struct main_stream_attributes msa_config; + struct dp_dma *dp_dma; + enum video_mode video_mode; + enum video_color_depth bpc; + enum video_color_encoding color_encode; + u32 pix_clk; + u8 dpcd_rx_caps[16]; + u8 lane_status_ajd_reqs[6]; + u8 sink_count; + u8 use_max_lane_count; + u8 use_max_link_rate; + u8 lane_count; + u8 link_rate; + u8 use_max_cfg_caps; + u8 en_sync_clk_mode; +}; + +/**************************** Variable Definitions ****************************/ +#define TRAINING_PATTERN_SET 0x000C +#define TRAINING_PATTERN_SET_OFF 0x0 +#define SCRAMBLING_DISABLE 0x0014 +#define TRAINING_PATTERN_SET_TP1 0x1 +#define TRAINING_PATTERN_SET_TP2 0x2 +#define TRAINING_PATTERN_SET_TP3 0x3 + +#define AVBUF_BUF_4BIT_SF 0x11111 +#define AVBUF_BUF_5BIT_SF 0x10842 +#define AVBUF_BUF_6BIT_SF 0x10410 +#define AVBUF_BUF_8BIT_SF 0x10101 +#define AVBUF_BUF_10BIT_SF 0x10040 +#define AVBUF_BUF_12BIT_SF 0x10000 +#define AVBUF_BUF_6BPC 0x000 +#define AVBUF_BUF_8BPC 0x001 +#define AVBUF_BUF_10BPC 0x010 +#define AVBUF_BUF_12BPC 0x011 +#define AVBUF_CHBUF3 0x0000B01C +#define AVBUF_CHBUF3_BURST_LEN_SHIFT 2 +#define AVBUF_CHBUF3_FLUSH_MASK 0x00000002 +#define AVBUF_CHBUF0_EN_MASK 0x00000001 +#define AVBUF_BUF_OUTPUT_AUD_VID_SELECT 0x0000B070 +#define AVBUF_BUF_OUTPUT_AUD_VID_SELECT_VID_STREAM2_SEL_MASK 0x0000000C +#define AVBUF_BUF_OUTPUT_AUD_VID_SELECT_VID_STREAM1_SEL_MASK 0x00000003 +#define AVBUF_BUF_OUTPUT_AUD_VID_SELECT 0x0000B070 +#define AVBUF_BUF_GRAPHICS_COMP0_SCALE_FACTOR 0x0000B200 +#define AVBUF_V_BLEND_LAYER1_CONTROL 0x0000A01C +#define AVBUF_V_BLEND_IN2CSC_COEFF0 0x0000A080 +#define AVBUF_BUF_FORMAT 0x0000B000 +#define AVBUF_BUF_FORMAT_NL_VID_FORMAT_MASK 0x0000001F +#define AVBUF_BUF_FORMAT_NL_GRAPHX_FORMAT_MASK 0x00000F00 +#define AVBUF_BUF_FORMAT_NL_GRAPHX_FORMAT_SHIFT 8 +#define AVBUF_V_BLEND_LAYER0_CONTROL_RGB_MODE_SHIFT 1 +#define AVBUF_V_BLEND_OUTPUT_VID_FORMAT_EN_DOWNSAMPLE_SHIFT 4 +#define AVBUF_V_BLEND_OUTPUT_VID_FORMAT 0x0000A014 +#define AVBUF_V_BLEND_RGB2YCBCR_COEFF0 0x0000A020 +#define AVBUF_V_BLEND_LUMA_OUTCSC_OFFSET 0x0000A074 +#define AVBUF_V_BLEND_LUMA_IN1CSC_OFFSET_POST_OFFSET_SHIFT 16 +#define AVBUF_V_BLEND_SET_GLOBAL_ALPHA_REG_VALUE_SHIFT 1 +#define AVBUF_V_BLEND_SET_GLOBAL_ALPHA_REG 0x0000A00C +#define DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_SHIFT 1 +#define DP_MAIN_STREAM_MISC0_DYNAMIC_RANGE_SHIFT 3 +#define DP_MAIN_STREAM_MISC0_YCBCR_COLORIMETRY_SHIFT 4 +#define DP_MAIN_STREAM_MISC1_Y_ONLY_EN_MASK 0x00000080 +#define DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_YCBCR422 0x1 +#define AVBUF_PL_CLK 0x0 +#define AVBUF_PS_CLK 0x1 +#define AVBUF_BUF_AUD_VID_CLK_SOURCE_VID_TIMING_SRC_SHIFT 2 +#define AVBUF_BUF_AUD_VID_CLK_SOURCE_VID_CLK_SRC_SHIFT 0 +#define AVBUF_BUF_AUD_VID_CLK_SOURCE_AUD_CLK_SRC_SHIFT 1 +#define AVBUF_BUF_AUD_VID_CLK_SOURCE 0x0000B120 +#define AVBUF_BUF_SRST_REG 0x0000B124 +#define AVBUF_BUF_SRST_REG_VID_RST_MASK 0x00000002 +#define AVBUF_CLK_FPD_BASEADDR 0xFD1A0000 +#define AVBUF_CLK_LPD_BASEADDR 0xFF5E0000 +#define AVBUF_LPD_CTRL_OFFSET 16 +#define AVBUF_FPD_CTRL_OFFSET 12 +#define AVBUF_EXTERNAL_DIVIDER 2 +#define AVBUF_VIDEO_REF_CTRL 0x00000070 +#define AVBUF_VIDEO_REF_CTRL_SRCSEL_MASK 0x00000007 +#define AVBUF_VPLL_SRC_SEL 0 +#define AVBUF_DPLL_SRC_SEL 2 +#define AVBUF_RPLL_TO_FPD_SRC_SEL 3 +#define AVBUF_INPUT_REF_CLK 3333333333 +#define AVBUF_PLL_OUT_FREQ 1450000000 +#define AVBUF_INPUT_FREQ_PRECISION 100 +#define AVBUF_PRECISION 16 +#define AVBUF_SHIFT_DECIMAL BIT(16) +#define AVBUF_DECIMAL (AVBUF_SHIFT_DECIMAL - 1) +#define AVBUF_ENABLE_BIT 1 +#define AVBUF_DISABLE_BIT 0 +#define AVBUF_PLL_CTRL_BYPASS_SHIFT 3 +#define AVBUF_PLL_CTRL_FBDIV_SHIFT 8 +#define AVBUF_PLL_CTRL_DIV2_SHIFT 16 +#define AVBUF_PLL_CTRL_PRE_SRC_SHIFT 20 +#define AVBUF_PLL_CTRL 0x00000020 +#define AVBUF_PLL_CFG_CP_SHIFT 5 +#define AVBUF_PLL_CFG_RES_SHIFT 0 +#define AVBUF_PLL_CFG_LFHF_SHIFT 10 +#define AVBUF_PLL_CFG_LOCK_DLY_SHIFT 25 +#define AVBUF_PLL_CFG_LOCK_CNT_SHIFT 13 +#define AVBUF_PLL_FRAC_CFG 0x00000028 +#define AVBUF_PLL_FRAC_CFG_ENABLED_SHIFT 31 +#define AVBUF_PLL_FRAC_CFG_DATA_SHIFT 0 +#define AVBUF_PLL_CTRL_RESET_MASK 0x00000001 +#define AVBUF_PLL_CTRL_RESET_SHIFT 0 +#define AVBUF_PLL_STATUS 0x00000044 +#define AVBUF_REG_OFFSET 4 +#define AVBUF_PLL_CTRL_BYPASS_MASK 0x00000008 +#define AVBUF_PLL_CTRL_BYPASS_SHIFT 3 +#define AVBUF_DOMAIN_SWITCH_CTRL 0x00000044 +#define AVBUF_DOMAIN_SWITCH_DIVISOR0_MASK 0x00003F00 +#define AVBUF_DOMAIN_SWITCH_DIVISOR0_SHIFT 8 +#define AVBUF_PLL_CFG 0x00000024 +#define AVBUF_BUF_AUD_VID_CLK_SOURCE_VID_CLK_SRC_SHIFT 0 +#define AVBUF_VIDEO_REF_CTRL_CLKACT_MASK 0x01000000 +#define AVBUF_VIDEO_REF_CTRL_CLKACT_SHIFT 24 +#define AVBUF_VIDEO_REF_CTRL_DIVISOR1_MASK 0x003F0000 +#define AVBUF_VIDEO_REF_CTRL_DIVISOR1_SHIFT 16 +#define AVBUF_VIDEO_REF_CTRL_DIVISOR0_MASK 0x00003F00 +#define AVBUF_VIDEO_REF_CTRL_DIVISOR0_SHIFT 8 +#define AVBUF_VIDEO_REF_CTRL_CLKACT_MASK 0x01000000 +#define AVBUF_VIDEO_REF_CTRL_CLKACT_SHIFT 24 + +#define DP_INTERRUPT_SIG_STATE 0x0130 +#define DP_INTR_STATUS 0x03A0 +#define DP_INTERRUPT_SIG_STATE_HPD_STATE_MASK 0x00000001 +#define DP_INTR_HPD_EVENT_MASK 0x00000002 +#define DP_INTR_HPD_PULSE_DETECTED_MASK 0x00000010 +#define DP_HPD_DURATION 0x0150 +#define DP_FORCE_SCRAMBLER_RESET 0x00C0 +#define DP_ENABLE_MAIN_STREAM 0x0084 +#define DP_IS_CONNECTED_MAX_TIMEOUT_COUNT 50 +#define DP_0_LINK_RATE 20 +#define DP_0_LANE_COUNT 1 +#define DP_ENHANCED_FRAME_EN 0x0008 +#define DP_LANE_COUNT_SET 0x0004 +#define DP_LINK_BW_SET_162GBPS 0x06 +#define DP_LINK_BW_SET_270GBPS 0x0A +#define DP_LINK_BW_SET_540GBPS 0x14 +#define DP_LINK_BW_SET 0x0000 +#define DP_DOWNSPREAD_CTRL 0x0018 +#define DP_SCRAMBLING_DISABLE 0x0014 +#define DP_AUX_CMD_READ 0x9 +#define DP_AUX_CMD_WRITE 0x8 +#define DP_AUX_CMD_I2C_READ 0x1 +#define DP_AUX_CMD_I2C_READ_MOT 0x5 +#define DP_AUX_CMD_I2C_WRITE 0x0 +#define DP_AUX_CMD_I2C_WRITE_MOT 0x4 +#define DP_REPLY_STATUS_REPLY_IN_PROGRESS_MASK 0x00000002 +#define DP_REPLY_STATUS_REQUEST_IN_PROGRESS_MASK 0x00000004 +#define DP_REPLY_STATUS 0x014C +#define DP_AUX_MAX_TIMEOUT_COUNT 50 +#define DP_AUX_MAX_DEFER_COUNT 50 +#define DP_AUX_ADDRESS 0x0108 +#define DP_AUX_WRITE_FIFO 0x0104 +#define DP_AUX_CMD 0x0100 +#define DP_AUX_CMD_SHIFT 8 +#define DP_AUX_CMD_NBYTES_TRANSFER_MASK 0x0000000F +#define DP_AUX_REPLY_CODE 0x0138 +#define DP_AUX_REPLY_CODE_DEFER 0x2 +#define DP_AUX_REPLY_CODE_I2C_DEFER 0x8 +#define DP_AUX_REPLY_CODE_NACK 0x1 +#define DP_AUX_REPLY_CODE_I2C_NACK 0x4 +#define DP_REPLY_DATA_COUNT 0x0148 +#define DP_AUX_REPLY_DATA 0x0134 +#define DP_LANE_COUNT_SET_1 0x01 +#define DP_LANE_COUNT_SET_2 0x02 +#define DP_MAXIMUM_PE_LEVEL 2 +#define DP_MAXIMUM_VS_LEVEL 3 +#define DP_MAIN_STREAM_MISC0_COMPONENT_FORMAT_RGB 0x0 +#define DP_MAIN_STREAM_MISC0_BDC_6BPC 0x0 +#define DP_MAIN_STREAM_MISC0_BDC_8BPC 0x1 +#define DP_MAIN_STREAM_MISC0_BDC_10BPC 0x2 +#define DP_MAIN_STREAM_MISC0_BDC_12BPC 0x3 +#define DP_MAIN_STREAM_MISC0_BDC_16BPC 0x4 +#define DP_MAIN_STREAM_MISC0_BDC_SHIFT 5 +#define DP_PHY_CONFIG_TX_PHY_8B10BEN_MASK 0x0010000 +#define DP_PHY_CONFIG_PHY_RESET_MASK 0x0000001 +#define DP_ENABLE_MAIN_STREAM 0x0084 +#define DP_SOFT_RESET 0x001C +#define DP_MAIN_STREAM_HTOTAL 0x0180 +#define DP_MAIN_STREAM_VTOTAL 0x0184 +#define DP_MAIN_STREAM_POLARITY 0x0188 +#define DP_MAIN_STREAM_POLARITY_VSYNC_POL_SHIFT 1 +#define DP_MAIN_STREAM_HSWIDTH 0x018C +#define DP_MAIN_STREAM_VSWIDTH 0x0190 +#define DP_MAIN_STREAM_HRES 0x0194 +#define DP_MAIN_STREAM_VRES 0x0198 +#define DP_MAIN_STREAM_HSTART 0x019C +#define DP_MAIN_STREAM_VSTART 0x01A0 +#define DP_MAIN_STREAM_MISC0 0x01A4 +#define DP_MAIN_STREAM_MISC1 0x01A8 +#define DP_M_VID 0x01AC +#define DP_N_VID 0x01B4 +#define DP_USER_PIXEL_WIDTH 0x01B8 +#define DP_USER_DATA_COUNT_PER_LANE 0x01BC +#define DP_TU_SIZE 0x01B0 +#define DP_MIN_BYTES_PER_TU 0x01C4 +#define DP_FRAC_BYTES_PER_TU 0x01C8 +#define DP_INIT_WAIT 0x01CC +#define DP_PHY_CLOCK_SELECT_162GBPS 0x1 +#define DP_PHY_CLOCK_SELECT_270GBPS 0x3 +#define DP_PHY_CLOCK_SELECT_540GBPS 0x5 +#define DP_PHY_STATUS 0x0280 +#define DP_PHY_STATUS_ALL_LANES_READY_MASK 0x00000013 +#define DP_PHY_STATUS_GT_PLL_LOCK_MASK 0x00000010 +#define DP_PHY_STATUS_RESET_LANE_0_DONE_MASK 0x00000001 +#define DP_INTR_HPD_IRQ_MASK 0x00000001 +#define DP_INTR_MASK 0x03A4 +#define DP_DP_ENABLE 0x1 +#define DP_PHY_CONFIG_GT_ALL_RESET_MASK 0x0000003 +#define DP_PHY_CLOCK_SELECT 0x0234 +#define DP_AUX_CLK_DIVIDER_VAL_MASK 0x000000FF +#define DP_AUX_CLK_DIVIDER 0x010C +#define DP_DISABLE 0x0 +#define DP_ENABLE 0x0080 +#define DP_SOFT_RESET_EN 0x1 +#define DP_PHY_CONFIG 0x0200 +#define DP_REPLY_STATUS_REPLY_RECEIVED_MASK 0x00000001 +#define DP_REPLY_STATUS_REPLY_IN_PROGRESS_MASK 0x00000002 +#define DP_REPLY_STATUS_REPLY_ERROR_MASK 0x00000008 +#define DP_AUX_MAX_WAIT 20000 + +#define DP_DPCD_SINK_COUNT 0x00200 +#define DP_DPCD_TP_SET_SCRAMB_DIS_MASK 0x20 +#define DP_DPCD_STATUS_LANE_1_CR_DONE_MASK 0x10 +#define DP_DPCD_STATUS_LANE_0_CR_DONE_MASK 0x01 +#define DP_DPCD_STATUS_LANE_1_CE_DONE_MASK 0x20 +#define DP_DPCD_STATUS_LANE_0_CE_DONE_MASK 0x02 +#define DP_DPCD_STATUS_LANE_1_SL_DONE_MASK 0x40 +#define DP_DPCD_STATUS_LANE_0_SL_DONE_MASK 0x04 +#define DP_DPCD_LANE_ALIGN_STATUS_UPDATED_IA_DONE_MASK 0x01 +#define DP_DPCD_ADJ_REQ_LANE_0_2_VS_MASK 0x03 +#define DP_DPCD_ADJ_REQ_LANE_1_3_VS_MASK 0x30 +#define DP_DPCD_ADJ_REQ_LANE_1_3_VS_SHIFT 4 +#define DP_DPCD_ADJ_REQ_LANE_0_2_PE_MASK 0x0C +#define DP_DPCD_ADJ_REQ_LANE_0_2_PE_SHIFT 2 +#define DP_DPCD_ADJ_REQ_LANE_1_3_PE_MASK 0xC0 +#define DP_DPCD_ADJ_REQ_LANE_1_3_PE_SHIFT 6 +#define DP_DPCD_TRAINING_LANE0_SET 0x00103 +#define DP_DPCD_TRAINING_LANEX_SET_MAX_VS_MASK 0x04 +#define DP_DPCD_TRAINING_LANEX_SET_MAX_PE_MASK 0x20 +#define DP_DPCD_TRAINING_LANEX_SET_PE_SHIFT 3 +#define DP_DPCD_SET_POWER_DP_PWR_VOLTAGE 0x00600 +#define DP_DPCD_RECEIVER_CAP_FIELD_START 0x00000 +#define DP_DPCD_MAX_LINK_RATE 0x00001 +#define DP_DPCD_MAX_LANE_COUNT 0x00002 +#define DP_DPCD_MAX_LANE_COUNT_MASK 0x1F +#define DP_DPCD_ENHANCED_FRAME_SUPPORT_MASK 0x80 +#define DP_DPCD_MAX_DOWNSPREAD 0x00003 +#define DP_DPCD_MAX_DOWNSPREAD_MASK 0x01 +#define DP_DPCD_LANE_COUNT_SET 0x00101 +#define DP_DPCD_ENHANCED_FRAME_EN_MASK 0x80 +#define DP_DPCD_LINK_BW_SET 0x00100 +#define DP_DPCD_DOWNSPREAD_CTRL 0x00107 +#define DP_DPCD_SPREAD_AMP_MASK 0x10 +#define DP_DPCD_LANE_COUNT_SET_MASK 0x1F +#define DP_DPCD_TPS3_SUPPORT_MASK 0x40 +#define DP_DPCD_TRAIN_AUX_RD_INTERVAL 0x0000E +#define DP_DPCD_SINK_COUNT_HIGH_MASK 0x80 +#define DP_DPCD_SINK_COUNT_HIGH_LOW_SHIFT 1 +#define DP_DPCD_SINK_COUNT_LOW_MASK 0x3F +#define DP_DPCD_TP_SET 0x00102 + +#define SERDES_BASEADDR 0xFD400000 +#define SERDES_L0_TX_MARGININGF 0x0CC0 +#define SERDES_L0_TX_DEEMPHASIS 0x0048 +#define SERDES_LANE_OFFSET 0x4000 + +#define DPDMA_TRIGGER_EN 1U +#define DPDMA_RETRIGGER_EN 2U +#define DPDMA_DESC_PREAMBLE 0xA5U +#define DPDMA_DESC_IGNR_DONE 0x400U +#define DPDMA_DESC_LAST_FRAME 0x200000U +#define DPDMA_DESCRIPTOR_LINE_SIZE_STRIDE_SHIFT 18 +#define DPDMA_DESCRIPTOR_SRC_ADDR_WIDTH 32U +#define DPDMA_DESCRIPTOR_ADDR_EXT_SRC_ADDR_EXT_SHIFT 16U +#define DPDMA_CH0_DSCR_STRT_ADDR 0X0204U +#define DPDMA_CH_OFFSET 0x100U +#define DPDMA_CH0_CNTL 0x0218U +#define DPDMA_CH3_CNTL 0x0518U +#define DPDMA_CH0_DSCR_STRT_ADDRE 0x0200U +#define DPDMA_CH3_DSCR_STRT_ADDR 0x0504 +#define DPDMA_CH3_DSCR_STRT_ADDRE 0x0500 +#define DPDMA_CH_CNTL_EN_MASK 0x1U +#define DPDMA_CH_CNTL_PAUSE_MASK 0x2U +#define DPDMA_GBL 0x0104U +#define DPDMA_GBL_TRG_CH3_MASK 0x8 +#define DPDMA_TRIGGER_DONE 0U +#define DPDMA_CH_CNTL_EN_MASK 0x1U +#define DPDMA_CH_CNTL_PAUSE_MASK 0x2U +#define DPDMA_CH_CNTL_QOS_DATA_RD_SHIFT 10U +#define DPDMA_CH_CNTL_QOS_DATA_RD_MASK 0x3C00U +#define DPDMA_CH_CNTL_QOS_DSCR_RD_SHIFT 6U +#define DPDMA_CH_CNTL_QOS_DSCR_RD_MASK 0x03C0U +#define DPDMA_CH_CNTL_QOS_DSCR_WR_SHIFT 2U +#define DPDMA_CH_CNTL_QOS_DSCR_WR_MASK 0x3CU +#define DPDMA_CH_OFFSET 0x100U +#define DPDMA_WAIT_TIMEOUT 10000U +#define DPDMA_AUDIO_ALIGNMENT 128U +#define DPDMA_VIDEO_CHANNEL0 0U +#define DPDMA_VIDEO_CHANNEL1 1U +#define DPDMA_VIDEO_CHANNEL2 2U +#define DPDMA_GRAPHICS_CHANNEL 3U +#define DPDMA_AUDIO_CHANNEL0 4U +#define DPDMA_AUDIO_CHANNEL1 5U +#define DPDMA_DESC_PREAMBLE 0xA5U +#define DPDMA_DESC_IGNR_DONE 0x400U +#define DPDMA_DESC_UPDATE 0x200U +#define DPDMA_DESC_COMP_INTR 0x100U +#define DPDMA_DESC_LAST_FRAME 0x200000U +#define DPDMA_DESC_DONE_SHIFT 31U +#define DPDMA_QOS_MIN 4U +#define DPDMA_QOS_MAX 11U +#define DPDMA_BASE_ADDRESS 0xFD4C0000 +#define DPDMA_ISR 0x0004U +#define DPDMA_IEN 0x000CU +#define DPDMA_ISR_VSYNC_INT_MASK 0x08000000 + +#define CLK_FPD_BASEADDR 0xFD1A0000 +#define VIDEO_REF_CTRL 0x00000070 +#define VIDEO_REF_CTRL_SRCSEL_MASK 0x00000007 +#define PLL_OUT_FREQ 1450000000 +#define INPUT_FREQ_PRECISION 100 +#define PRECISION 16 +#define SHIFT_DECIMAL BIT(16) +#define ENABLE_BIT 1 +#define DISABLE_BIT 0 +#define PLL_CTRL_BYPASS_SHIFT 3 +#define PLL_CTRL_FBDIV_SHIFT 8 +#define PLL_CTRL_DIV2_SHIFT 16 +#define PLL_CTRL_PRE_SRC_SHIFT 20 +#define PLL_CTRL 0x00000020 +#define VPLL_CTRL 0x00000038 +#define PLL_CFG 0x00000024 +#define VPLL 2 +#define VPLL_CFG 0x0000003C +#define VPLL_CFG_CP 4 +#define VPLL_CFG_RES 6 +#define VPLL_CFG_LFHF 3 +#define VPLL_CFG_LOCK_DLY 63 +#define VPLL_CFG_LOCK_CNT 600 +#define PLL_STATUS_VPLL_LOCK 2 +#define PLL_CFG_CP_SHIFT 5 +#define PLL_CFG_RES_SHIFT 0 +#define PLL_CFG_LFHF_SHIFT 10 +#define PLL_CFG_LOCK_DLY_SHIFT 25 +#define PLL_CFG_LOCK_CNT_SHIFT 13 +#define PLL_FRAC_CFG 0x00000028 +#define VPLL_FRAC_CFG 0x00000040 +#define PLL_FRAC_CFG_ENABLED_SHIFT 31 +#define PLL_FRAC_CFG_DATA_SHIFT 0 +#define PLL_CTRL_RESET_MASK 0x00000001 +#define PLL_CTRL_RESET_SHIFT 0 +#define PLL_STATUS 0x00000044 +#define REG_OFFSET 4 +#define PLL_CTRL_BYPASS_MASK 0x00000008 +#define PLL_CTRL_BYPASS_SHIFT 3 +#define DOMAIN_SWITCH_CTRL 0x00000044 +#define DOMAIN_SWITCH_DIVISOR0_MASK 0x00003F00 +#define DOMAIN_SWITCH_DIVISOR0_SHIFT 8 +#define VIDEO_REF_CTRL_CLKACT_MASK 0x01000000 +#define VIDEO_REF_CTRL_CLKACT_SHIFT 24 +#define VIDEO_REF_CTRL_DIVISOR1_MASK 0x003F0000 +#define VIDEO_REF_CTRL_DIVISOR1_SHIFT 16 +#define VIDEO_REF_CTRL_DIVISOR0_MASK 0x00003F00 +#define VIDEO_REF_CTRL_DIVISOR0_SHIFT 8 +#define PSS_REF_CLK 0 +#define FPD_CTRL_OFFSET 12 +#define VIDC_VM_NUM_SUPPORTED 1 + +static const u32 vs[4][4] = { + { 0x2a, 0x27, 0x24, 0x20 }, + { 0x27, 0x23, 0x20, 0xff }, + { 0x24, 0x20, 0xff, 0xff }, + { 0xff, 0xff, 0xff, 0xff }, +}; + +static const u32 pe[4][4] = { + { 0x02, 0x02, 0x02, 0x02 }, + { 0x01, 0x01, 0x01, 0xff }, + { 0x00, 0x00, 0xff, 0xff }, + { 0xff, 0xff, 0xff, 0xff }, +}; + +const struct video_timing_mode vidc_video_timing_modes[VIDC_VM_NUM_SUPPORTED] = { + { VIDC_VM_640x480_60_P, "640x480@60Hz", VIDC_FR_60HZ, + {640, 16, 96, 48, 800, 0, + 480, 10, 2, 33, 525, 0, 0, 0, 0, 0} }, +}; + +const struct av_buf_vid_attribute avbuf_supported_formats[] = { + /* Non-Live Graphics formats */ + { RGBA8888, 0, INTERLEAVED, + {AVBUF_BUF_8BIT_SF, AVBUF_BUF_8BIT_SF, AVBUF_BUF_8BIT_SF}, + 0, 1, 0, 32}, +}; + +#endif |
