// SPDX-License-Identifier: GPL-2.0 /* Phytium display drm driver * * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. */ #include "phytium_display_drv.h" #include "px210_reg.h" #include "phytium_dp.h" #include "px210_dp.h" static uint8_t px210_dp_source_lane_count[3] = {4, 4, 1}; /* [reg][ling_rate 1.62->8.1] */ static int vco_val[12][4] = { {0x0509, 0x0509, 0x0509, 0x0509}, // CP_PADJ {0x0f00, 0x0f00, 0x0f00, 0x0f00}, // CP_IADJ {0x0F08, 0x0F08, 0x0F08, 0x0F08}, // FILT_PADJ {0x0061, 0x006C, 0x006C, 0x0051}, // INTDIV {0x3333, 0x0000, 0x0000, 0x0000}, // FRACDIVL {0x0000, 0x0000, 0x0000, 0x0000}, // FRACDIVH {0x0042, 0x0048, 0x0048, 0x0036}, // HIGH_THR {0x0002, 0x0002, 0x0002, 0x0002}, // PDIAG_CTRL {0x0c5e, 0x0c5e, 0x0c5e, 0x0c5e}, // VCOCAL_PLLCNT_START {0x00c7, 0x00c7, 0x00c7, 0x00c7}, // LOCK_PEFCNT {0x00c7, 0x00c7, 0x00c7, 0x00c7}, // LOCK_PLLCNT_START {0x0005, 0x0005, 0x0005, 0x0005}, // LOCK_PLLCNT_THR }; static int mgnfs_val[4][4][4] = // [link_rate][swing][emphasis] { /* 1.62Gbps */ { {0x0026, 0x001f, 0x0012, 0x0000}, {0x0013, 0x0013, 0x0000, 0x0000}, {0x0006, 0x0000, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 2.7Gbps */ { {0x0026, 0x001f, 0x0012, 0x0000}, {0x0013, 0x0013, 0x0000, 0x0000}, {0x0006, 0x0000, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 5.4Gbps */ { {0x0026, 0x0013, 0x005, 0x0000}, {0x0018, 0x006, 0x0000, 0x0000}, {0x000c, 0x0000, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 8.1Gbps */ { {0x0026, 0x0013, 0x005, 0x0000}, {0x0013, 0x006, 0x0000, 0x0000}, {0x0006, 0x0000, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, }; static int cpost_val[4][4][4] = // [link_rate][swing][emphasis] { /* 1.62Gbps */ { {0x0000, 0x0014, 0x0020, 0x002a}, {0x0000, 0x0010, 0x001f, 0x0000}, {0x0000, 0x0013, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 2.7Gbps */ { {0x0000, 0x0014, 0x0020, 0x002a}, {0x0000, 0x0010, 0x001f, 0x0000}, {0x0000, 0x0013, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 5.4Gbps */ { {0x0000, 0x0014, 0x0022, 0x002e}, {0x0000, 0x0013, 0x0020, 0x0000}, {0x0000, 0x0013, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, /* 8.1Gbps */ { {0x0000, 0x0014, 0x0022, 0x002e}, {0x0000, 0x0013, 0x0020, 0x0000}, {0x0000, 0x0013, 0x0000, 0x0000}, {0x0000, 0x0000, 0x0000, 0x0000}, }, }; static int px210_dp_hw_set_phy_lane_and_rate(struct phytium_dp_device *phytium_dp, uint8_t link_lane_count, uint32_t link_rate) { int port = phytium_dp->port%3; int i = 0, data, tmp, tmp1, index = 0, mask; int timeout = 500, ret = 0; if (port == 0 || port == 1) { /* set pma powerdown */ data = 0; mask = 0; for (i = 0; i < phytium_dp->source_max_lane_count; i++) { data |= (A3_POWERDOWN3 << i*A3_POWERDOWN3_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (PLL_EN << i*PLL_EN_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (PLL_EN << i*PLL_EN_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (A0_ACTIVE << i*A0_ACTIVE_SHIFT); mask |= (((1<port%3; int voltage_swing = 0; int pre_emphasis = 0, link_rate_index = 0; switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: default: voltage_swing = 0; break; case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: voltage_swing = 1; break; case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: voltage_swing = 2; break; case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: voltage_swing = 3; break; } switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { case DP_TRAIN_PRE_EMPH_LEVEL_0: default: pre_emphasis = 0; break; case DP_TRAIN_PRE_EMPH_LEVEL_1: pre_emphasis = 1; break; case DP_TRAIN_PRE_EMPH_LEVEL_2: pre_emphasis = 2; break; case DP_TRAIN_PRE_EMPH_LEVEL_3: pre_emphasis = 3; break; } switch (link_rate) { case 810000: link_rate_index = 3; break; case 540000: link_rate_index = 2; break; case 270000: link_rate_index = 1; break; case 162000: link_rate_index = 0; break; default: DRM_ERROR("phytium dp rate(%d) not support\n", link_rate); link_rate_index = 2; break; } if (port == 0) { phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DIAG_ACYA, LOCK); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_TXCC_CTRL, TX_TXCC_CTRL); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DRV, TX_DRV); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_MGNFS, mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_CPOST, cpost_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL0_TX_DIAG_ACYA, UNLOCK); } else if (port == 1) { phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DIAG_ACYA, LOCK); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_TXCC_CTRL, TX_TXCC_CTRL); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DRV, TX_DRV); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_MGNFS, mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_CPOST, cpost_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_CPOST1, cpost_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY0_PLL1_TX_DIAG_ACYA, UNLOCK); } else { phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DIAG_ACYA, LOCK); phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_TXCC_CTRL, TX_TXCC_CTRL); phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DRV, TX_DRV); phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_MGNFS, mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_CPOST, cpost_val[link_rate_index][voltage_swing][pre_emphasis]); phytium_phy_writel(phytium_dp, PX210_PHY1_PLL0_TX_DIAG_ACYA, UNLOCK); } } static int px210_dp_hw_init_phy(struct phytium_dp_device *phytium_dp) { int port = phytium_dp->port; int i = 0, data, tmp, mask; int timeout = 500, ret = 0; if (port == 0 || port == 1) { phytium_phy_writel(phytium_dp, PX210_PHY0_APB_RESET, APB_RESET); phytium_phy_writel(phytium_dp, PX210_PHY0_PIPE_RESET, RESET); /* config lane to dp mode */ data = 0; mask = 0; for (i = 0; i < phytium_dp->source_max_lane_count; i++) { data |= (LANE_BIT << i*LANE_BIT_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (LANE_MASTER << i*LANE_MASTER_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (PLL_EN << i*PLL_EN_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (BIT_20 << i*BIT_20_SHIFT); mask |= (((1<source_max_lane_count; i++) { data |= (A0_ACTIVE << i*A0_ACTIVE_SHIFT); mask |= (((1<dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; uint32_t group_offset = priv->dcreq_reg_base[port]; int ret = 0; phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_ENABLE, group_offset, PX210_DCREQ_CMD_REGISTER); ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, FLAG_REQUEST, FLAG_REPLY); if (ret < 0) DRM_ERROR("%s: failed to poweron panel\n", __func__); } static void px210_dp_hw_poweroff_panel(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; uint32_t group_offset = priv->dcreq_reg_base[port]; int ret = 0; phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_DISABLE, group_offset, PX210_DCREQ_CMD_REGISTER); ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, FLAG_REQUEST, FLAG_REPLY); if (ret < 0) DRM_ERROR("%s: failed to poweroff panel\n", __func__); } static void px210_dp_hw_enable_backlight(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port, ret = 0; uint32_t group_offset = priv->dcreq_reg_base[port]; phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_ENABLE, group_offset, PX210_DCREQ_CMD_REGISTER); ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, FLAG_REQUEST, FLAG_REPLY); if (ret < 0) DRM_ERROR("%s: failed to enable backlight\n", __func__); } static void px210_dp_hw_disable_backlight(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; uint32_t group_offset = priv->dcreq_reg_base[port]; int ret = 0; phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_DISABLE, group_offset, PX210_DCREQ_CMD_REGISTER); ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, FLAG_REQUEST, FLAG_REPLY); if (ret < 0) DRM_ERROR("%s: failed to disable backlight\n", __func__); } static uint32_t px210_dp_hw_get_backlight(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int config; uint32_t group_offset = priv->address_transform_base; config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE); return ((config >> BACKLIGHT_VALUE_SHIFT) & BACKLIGHT_VALUE_MASK); } static int px210_dp_hw_set_backlight(struct phytium_dp_device *phytium_dp, uint32_t level) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; uint32_t group_offset = priv->dcreq_reg_base[port]; int config = 0; int ret = 0; if (level > PX210_DP_BACKLIGHT_MAX) { ret = -EINVAL; goto out; } config = FLAG_REQUEST | CMD_BACKLIGHT | ((level & BACKLIGHT_MASK) << BACKLIGHT_SHIFT); phytium_writel_reg(priv, config, group_offset, PX210_DCREQ_CMD_REGISTER); ret = phytium_wait_cmd_done(priv, group_offset + PX210_DCREQ_CMD_REGISTER, FLAG_REQUEST, FLAG_REPLY); if (ret < 0) DRM_ERROR("%s: failed to set backlight\n", __func__); out: return ret; } bool px210_dp_hw_spread_is_enable(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port, config; uint32_t group_offset = priv->address_transform_base; config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); return ((config & DP_SPREAD_ENABLE(port)) ? true:false); } int px210_dp_hw_reset(struct phytium_dp_device *phytium_dp) { struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; int timeout = 100, config, ret = 0; uint32_t group_offset = priv->address_transform_base; uint32_t group_offset_dp = priv->dp_reg_base[port]; config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); config &= (~DC_DP_RESET_STATUS(port)); phytium_writel_reg(priv, config, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); phytium_writel_reg(priv, FLAG_REQUEST | CMD_DC_DP_RESET, priv->dcreq_reg_base[port], PX210_DCREQ_CMD_REGISTER); do { mdelay(10); timeout--; config = phytium_readl_reg(priv, group_offset, PX210_DC_ADDRESS_TRANSFORM_DP_RESET_STATUS); if (config & DC_DP_RESET_STATUS(port)) break; } while (timeout); if (timeout == 0) { DRM_ERROR("reset dc/dp pipe(%d) failed\n", port); ret = -1; } phytium_writel_reg(priv, AUX_CLK_DIVIDER, group_offset_dp, PHYTIUM_DP_AUX_CLK_DIVIDER); return ret; } uint8_t px210_dp_hw_get_source_lane_count(struct phytium_dp_device *phytium_dp) { return px210_dp_source_lane_count[phytium_dp->port]; } static struct phytium_dp_func px210_dp_funcs = { .dp_hw_get_source_lane_count = px210_dp_hw_get_source_lane_count, .dp_hw_reset = px210_dp_hw_reset, .dp_hw_spread_is_enable = px210_dp_hw_spread_is_enable, .dp_hw_set_backlight = px210_dp_hw_set_backlight, .dp_hw_get_backlight = px210_dp_hw_get_backlight, .dp_hw_disable_backlight = px210_dp_hw_disable_backlight, .dp_hw_enable_backlight = px210_dp_hw_enable_backlight, .dp_hw_poweroff_panel = px210_dp_hw_poweroff_panel, .dp_hw_poweron_panel = px210_dp_hw_poweron_panel, .dp_hw_init_phy = px210_dp_hw_init_phy, .dp_hw_set_phy_lane_setting = px210_dp_hw_set_phy_lane_setting, .dp_hw_set_phy_lane_and_rate = px210_dp_hw_set_phy_lane_and_rate, }; void px210_dp_func_register(struct phytium_dp_device *phytium_dp) { phytium_dp->funcs = &px210_dp_funcs; }