[PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

Laurent Pinchart laurent.pinchart at ideasonboard.com
Mon Mar 18 17:52:42 UTC 2024


On Sat, Mar 16, 2024 at 07:56:52PM +0200, Dmitry Baryshkov wrote:
> On Sat, 16 Mar 2024 at 01:09, Sean Anderson <sean.anderson at linux.dev> wrote:
> >
> > Add a debugfs interface for exercising the various test modes supported
> > by the DisplayPort controller. This allows performing compliance
> > testing, or performing signal integrity measurements on a failing link.
> > At the moment, we do not support sink-driven link quality testing,
> > although such support would be fairly easy to add.
> 
> Could you please point out how this is used for compliance testing? We
> have been using the msm_dp_compliance tool [1].
> 
> I think it would be nice to rework our drivers towards a common
> debugfs interface used for DP connectors, maybe defining generic
> internal interface/helpers like Maxime is implementing for HDMI
> connectors.

This would be really nice :-)

> [1] https://gitlab.freedesktop.org/drm/igt-gpu-tools/-/blob/master/tools/msm_dp_compliance.c?ref_type=heads
> 
> >
> > Signed-off-by: Sean Anderson <sean.anderson at linux.dev>
> > ---
> >
> >  drivers/gpu/drm/xlnx/zynqmp_dp.c | 591 ++++++++++++++++++++++++++++++-
> >  1 file changed, 590 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > index 040f7b88ee51..57032186e1ca 100644
> > --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > @@ -18,7 +18,9 @@
> >  #include <drm/drm_modes.h>
> >  #include <drm/drm_of.h>
> >
> > +#include <linux/bitfield.h>
> >  #include <linux/clk.h>
> > +#include <linux/debugfs.h>
> >  #include <linux/delay.h>
> >  #include <linux/device.h>
> >  #include <linux/io.h>
> > @@ -50,6 +52,7 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in msec (default: 4)");
> >  #define ZYNQMP_DP_LANE_COUNT_SET                       0x4
> >  #define ZYNQMP_DP_ENHANCED_FRAME_EN                    0x8
> >  #define ZYNQMP_DP_TRAINING_PATTERN_SET                 0xc
> > +#define ZYNQMP_DP_LINK_QUAL_PATTERN_SET                        0x10
> >  #define ZYNQMP_DP_SCRAMBLING_DISABLE                   0x14
> >  #define ZYNQMP_DP_DOWNSPREAD_CTL                       0x18
> >  #define ZYNQMP_DP_SOFTWARE_RESET                       0x1c
> > @@ -63,6 +66,9 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in msec (default: 4)");
> >                                                          ZYNQMP_DP_SOFTWARE_RESET_STREAM3 | \
> >                                                          ZYNQMP_DP_SOFTWARE_RESET_STREAM4 | \
> >                                                          ZYNQMP_DP_SOFTWARE_RESET_AUX)
> > +#define ZYNQMP_DP_COMP_PATTERN_80BIT_1                 0x20
> > +#define ZYNQMP_DP_COMP_PATTERN_80BIT_2                 0x24
> > +#define ZYNQMP_DP_COMP_PATTERN_80BIT_3                 0x28
> >
> >  /* Core enable registers */
> >  #define ZYNQMP_DP_TRANSMITTER_ENABLE                   0x80
> > @@ -206,6 +212,7 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in msec (default: 4)");
> >  #define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_2             BIT(2)
> >  #define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_3             BIT(3)
> >  #define ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL                        0xf
> > +#define ZYNQMP_DP_TRANSMIT_PRBS7                       0x230
> >  #define ZYNQMP_DP_PHY_PRECURSOR_LANE_0                 0x23c
> >  #define ZYNQMP_DP_PHY_PRECURSOR_LANE_1                 0x240
> >  #define ZYNQMP_DP_PHY_PRECURSOR_LANE_2                 0x244
> > @@ -273,6 +280,69 @@ struct zynqmp_dp_config {
> >         u8 bpp;
> >  };
> >
> > +/**
> > + * enum test_pattern - Test patterns for test testing
> > + * TEST_VIDEO: Use regular video input
> > + * TEST_SYMBOL_ERROR: Symbol error measurement pattern
> > + * TEST_PRBS7: Output of the PRBS7 (x^7 + x^6 + 1) polynomial
> > + * TEST_80BIT_CUSTOM: A custom 80-bit pattern
> > + * TEST_CP2520: HBR2 compliance eye pattern
> > + * TEST_TPS1: Link training symbol pattern TPS1 (/D10.2/)
> > + * TEST_TPS2: Link training symbol pattern TPS2
> > + * TEST_TPS3: Link training symbol pattern TPS3 (for HBR2)
> > + */
> > +enum test_pattern {
> > +       TEST_VIDEO,
> > +       TEST_TPS1,
> > +       TEST_TPS2,
> > +       TEST_TPS3,
> > +       TEST_SYMBOL_ERROR,
> > +       TEST_PRBS7,
> > +       TEST_80BIT_CUSTOM,
> > +       TEST_CP2520,
> > +};
> > +
> > +static const char *const test_pattern_str[] = {
> > +       [TEST_VIDEO] = "video",
> > +       [TEST_TPS1] = "tps1",
> > +       [TEST_TPS2] = "tps2",
> > +       [TEST_TPS3] = "tps3",
> > +       [TEST_SYMBOL_ERROR] = "symbol-error",
> > +       [TEST_PRBS7] = "prbs7",
> > +       [TEST_80BIT_CUSTOM] = "80bit-custom",
> > +       [TEST_CP2520] = "cp2520",
> > +};
> > +
> > +/**
> > + * struct zynqmp_dp_test - Configuration for test mode
> > + * @pattern: The test pattern
> > + * @enhanced: Use enhanced framing
> > + * @downspread: Use SSC
> > + * @active: Whether test mode is active
> > + * @custom: Custom pattern for %TEST_80BIT_CUSTOM
> > + * @train_set: Voltage/preemphasis settings
> > + * @bw_code: Bandwidth code for the link
> > + * @link_cnt: Number of lanes
> > + */
> > +struct zynqmp_dp_test {
> > +       enum test_pattern pattern;
> > +       bool enhanced, downspread, active;
> > +       u8 custom[10];
> > +       u8 train_set[ZYNQMP_DP_MAX_LANES];
> > +       u8 bw_code;
> > +       u8 link_cnt;
> > +};
> > +
> > +/**
> > + * struct zynqmp_dp_train_set_priv - Private data for train_set debugfs files
> > + * @dp: DisplayPort IP core structure
> > + * @lane: The lane for this file
> > + */
> > +struct zynqmp_dp_train_set_priv {
> > +       struct zynqmp_dp *dp;
> > +       int lane;
> > +};
> > +
> >  /**
> >   * struct zynqmp_dp - Xilinx DisplayPort core
> >   * @dev: device structure
> > @@ -283,6 +353,7 @@ struct zynqmp_dp_config {
> >   * @irq: irq
> >   * @bridge: DRM bridge for the DP encoder
> >   * @next_bridge: The downstream bridge
> > + * @test: Configuration for test mode
> >   * @config: IP core configuration from DTS
> >   * @aux: aux channel
> >   * @phy: PHY handles for DP lanes
> > @@ -294,6 +365,7 @@ struct zynqmp_dp_config {
> >   * @link_config: common link configuration between IP core and sink device
> >   * @mode: current mode between IP core and sink device
> >   * @train_set: set of training data
> > + * @debugfs_train_set: Debugfs private data for @train_set
> >   */
> >  struct zynqmp_dp {
> >         struct device *dev;
> > @@ -306,6 +378,7 @@ struct zynqmp_dp {
> >         struct drm_bridge bridge;
> >         struct drm_bridge *next_bridge;
> >
> > +       struct zynqmp_dp_test test;
> >         struct zynqmp_dp_config config;
> >         struct drm_dp_aux aux;
> >         struct phy *phy[ZYNQMP_DP_MAX_LANES];
> > @@ -318,6 +391,7 @@ struct zynqmp_dp {
> >         struct zynqmp_dp_link_config link_config;
> >         struct zynqmp_dp_mode mode;
> >         u8 train_set[ZYNQMP_DP_MAX_LANES];
> > +       struct zynqmp_dp_train_set_priv debugfs_train_set[ZYNQMP_DP_MAX_LANES];
> >  };
> >
> >  static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
> > @@ -1599,6 +1673,510 @@ static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge,
> >         return drm_get_edid(connector, &dp->aux.ddc);
> >  }
> >
> > +/* -----------------------------------------------------------------------------
> > + * debugfs
> > + */
> > +
> > +/**
> > + * zynqmp_dp_set_test_pattern() - Configure the link for a test pattern
> > + * @dp: DisplayPort IP core structure
> > + */
> > +static void zynqmp_dp_set_test_pattern(struct zynqmp_dp *dp,
> > +                                      enum test_pattern pattern,
> > +                                      u8 *const custom)
> > +{
> > +       bool scramble = false;
> > +       u32 train_pattern = 0;
> > +       u32 link_pattern = 0;
> > +       u8 dpcd_train = 0;
> > +       u8 dpcd_link = 0;
> > +       int err;
> > +
> > +       switch (pattern) {
> > +       case TEST_TPS1:
> > +               train_pattern = 1;
> > +               break;
> > +       case TEST_TPS2:
> > +               train_pattern = 2;
> > +               break;
> > +       case TEST_TPS3:
> > +               train_pattern = 3;
> > +               break;
> > +       case TEST_SYMBOL_ERROR:
> > +               scramble = true;
> > +               link_pattern = DP_PHY_TEST_PATTERN_ERROR_COUNT;
> > +               break;
> > +       case TEST_PRBS7:
> > +               /* We use a dedicated register to enable PRBS7 */
> > +               dpcd_link = DP_LINK_QUAL_PATTERN_ERROR_RATE;
> > +               break;
> > +       case TEST_80BIT_CUSTOM: {
> > +               const u8 *p = custom;
> > +
> > +               link_pattern = DP_LINK_QUAL_PATTERN_80BIT_CUSTOM;
> > +
> > +               zynqmp_dp_write(dp, ZYNQMP_DP_COMP_PATTERN_80BIT_1,
> > +                               (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
> > +               zynqmp_dp_write(dp, ZYNQMP_DP_COMP_PATTERN_80BIT_2,
> > +                               (p[7] << 24) | (p[6] << 16) | (p[5] << 8) | p[4]);
> > +               zynqmp_dp_write(dp, ZYNQMP_DP_COMP_PATTERN_80BIT_3,
> > +                               (p[9] << 8) | p[8]);
> > +               break;
> > +       }
> > +       case TEST_CP2520:
> > +               link_pattern = DP_LINK_QUAL_PATTERN_CP2520_PAT_1;
> > +               break;
> > +       default:
> > +               WARN_ON_ONCE(1);
> > +               fallthrough;
> > +       case TEST_VIDEO:
> > +               scramble = true;
> > +       }
> > +
> > +       zynqmp_dp_write(dp, ZYNQMP_DP_SCRAMBLING_DISABLE, !scramble);
> > +       zynqmp_dp_write(dp, ZYNQMP_DP_TRAINING_PATTERN_SET, train_pattern);
> > +       zynqmp_dp_write(dp, ZYNQMP_DP_LINK_QUAL_PATTERN_SET, link_pattern);
> > +       zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMIT_PRBS7, pattern == TEST_PRBS7);
> > +
> > +       dpcd_link = dpcd_link ?: link_pattern;
> > +       dpcd_train = train_pattern;
> > +       if (!scramble)
> > +               dpcd_train |= DP_LINK_SCRAMBLING_DISABLE;
> > +
> > +       if (dp->dpcd[DP_DPCD_REV] < 0x12) {
> > +               if (pattern == TEST_CP2520)
> > +                       dev_warn(dp->dev,
> > +                               "can't set sink link quality pattern to CP2520 for DPCD < r1.2; error counters will be invalid\n");
> > +               else
> > +                       dpcd_train |= FIELD_PREP(DP_LINK_QUAL_PATTERN_11_MASK,
> > +                                                dpcd_link);
> > +       } else {
> > +               u8 dpcd_link_lane[ZYNQMP_DP_MAX_LANES];
> > +
> > +               memset(dpcd_link_lane, dpcd_link, ZYNQMP_DP_MAX_LANES);
> > +               err = drm_dp_dpcd_write(&dp->aux, DP_LINK_QUAL_LANE0_SET,
> > +                                       dpcd_link_lane, ZYNQMP_DP_MAX_LANES);
> > +               if (err < 0)
> > +                       dev_err(dp->dev, "failed to set quality pattern\n");
> > +       }
> > +
> > +       err = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, dpcd_train);
> > +       if (err < 0)
> > +               dev_err(dp->dev, "failed to set training pattern\n");
> > +}
> > +
> > +static int zynqmp_dp_test_setup(struct zynqmp_dp *dp)
> > +{
> > +       return zynqmp_dp_setup(dp, dp->test.bw_code, dp->test.link_cnt,
> > +                              dp->test.enhanced, dp->test.downspread, true);
> > +}
> > +
> > +static ssize_t zynqmp_dp_pattern_read(struct file *file, char __user *user_buf,
> > +                                     size_t count, loff_t *ppos)
> > +{
> > +       struct dentry *dentry = file->f_path.dentry;
> > +       struct zynqmp_dp *dp = file->private_data;
> > +       char buf[16];
> > +       ssize_t ret;
> > +
> > +       ret = debugfs_file_get(dentry);
> > +       if (unlikely(ret))
> > +               return ret;
> > +
> > +       mutex_lock(&dp->lock);
> > +       ret = snprintf(buf, sizeof(buf), "%s\n",
> > +                      test_pattern_str[dp->test.pattern]);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       debugfs_file_put(dentry);
> > +       return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
> > +}
> > +
> > +static ssize_t zynqmp_dp_pattern_write(struct file *file,
> > +                                      const char __user *user_buf,
> > +                                      size_t count, loff_t *ppos)
> > +{
> > +
> > +       struct dentry *dentry = file->f_path.dentry;
> > +       struct zynqmp_dp *dp = file->private_data;
> > +       char buf[16];
> > +       ssize_t ret;
> > +       int pattern;
> > +
> > +       ret = debugfs_file_get(dentry);
> > +       if (unlikely(ret))
> > +               return ret;
> > +
> > +       ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf,
> > +                                    count);
> > +       if (ret < 0)
> > +               goto out;
> > +       buf[ret] = '\0';
> > +
> > +       pattern = sysfs_match_string(test_pattern_str, buf);
> > +       if (pattern < 0) {
> > +               ret = -EINVAL;
> > +               goto out;
> > +       }
> > +
> > +       mutex_lock(&dp->lock);
> > +       dp->test.pattern = pattern;
> > +       if (dp->test.active)
> > +               zynqmp_dp_set_test_pattern(dp, dp->test.pattern,
> > +                                          dp->test.custom);
> > +       mutex_unlock(&dp->lock);
> > +
> > +out:
> > +       debugfs_file_put(dentry);
> > +       return ret;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_dp_pattern = {
> > +       .read = zynqmp_dp_pattern_read,
> > +       .write = zynqmp_dp_pattern_write,
> > +       .open = simple_open,
> > +       .llseek = noop_llseek,
> > +};
> > +
> > +static int zynqmp_dp_enhanced_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = dp->test.enhanced;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_enhanced_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +       int ret = 0;
> > +
> > +       mutex_lock(&dp->lock);
> > +       dp->test.enhanced = val;
> > +       if (dp->test.active)
> > +               ret = zynqmp_dp_test_setup(dp);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_enhanced, zynqmp_dp_enhanced_get,
> > +                        zynqmp_dp_enhanced_set, "%llu\n");
> > +
> > +static int zynqmp_dp_downspread_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = dp->test.downspread;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_downspread_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +       int ret = 0;
> > +
> > +       mutex_lock(&dp->lock);
> > +       dp->test.downspread = val;
> > +       if (dp->test.active)
> > +               ret = zynqmp_dp_test_setup(dp);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_downspread, zynqmp_dp_downspread_get,
> > +                        zynqmp_dp_downspread_set, "%llu\n");
> > +
> > +static int zynqmp_dp_active_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = dp->test.active;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_active_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +       int ret = 0;
> > +
> > +       mutex_lock(&dp->lock);
> > +       if (val) {
> > +               if (val < 2) {
> > +                       ret = zynqmp_dp_test_setup(dp);
> > +                       if (ret)
> > +                               goto out;
> > +               }
> > +
> > +               zynqmp_dp_set_test_pattern(dp, dp->test.pattern,
> > +                                          dp->test.custom);
> > +               zynqmp_dp_update_vs_emph(dp, dp->test.train_set, true);
> > +               dp->test.active = true;
> > +       } else {
> > +               dp->test.active = false;
> > +               zynqmp_dp_set_test_pattern(dp, TEST_VIDEO, NULL);
> > +               zynqmp_dp_train_loop(dp);
> > +       }
> > +out:
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_active, zynqmp_dp_active_get,
> > +                        zynqmp_dp_active_set, "%llu\n");
> > +
> > +static ssize_t zynqmp_dp_custom_read(struct file *file, char __user *user_buf,
> > +                                     size_t count, loff_t *ppos)
> > +{
> > +       struct dentry *dentry = file->f_path.dentry;
> > +       struct zynqmp_dp *dp = file->private_data;
> > +       ssize_t ret;
> > +
> > +       ret = debugfs_file_get(dentry);
> > +       if (unlikely(ret))
> > +               return ret;
> > +
> > +       mutex_lock(&dp->lock);
> > +       ret = simple_read_from_buffer(user_buf, count, ppos, &dp->test.custom,
> > +                                     sizeof(dp->test.custom));
> > +       mutex_unlock(&dp->lock);
> > +
> > +       debugfs_file_put(dentry);
> > +       return ret;
> > +}
> > +
> > +static ssize_t zynqmp_dp_custom_write(struct file *file,
> > +                                     const char __user *user_buf,
> > +                                     size_t count, loff_t *ppos)
> > +{
> > +
> > +       struct dentry *dentry = file->f_path.dentry;
> > +       struct zynqmp_dp *dp = file->private_data;
> > +       ssize_t ret;
> > +       char buf[sizeof(dp->test.custom)];
> > +
> > +       ret = debugfs_file_get(dentry);
> > +       if (unlikely(ret))
> > +               return ret;
> > +
> > +       ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count);
> > +       if (ret < 0)
> > +               goto out;
> > +
> > +       mutex_lock(&dp->lock);
> > +       memcpy(dp->test.custom, buf, ret);
> > +       if (dp->test.active)
> > +               zynqmp_dp_set_test_pattern(dp, dp->test.pattern,
> > +                                          dp->test.custom);
> > +       mutex_unlock(&dp->lock);
> > +
> > +out:
> > +       debugfs_file_put(dentry);
> > +       return ret;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_dp_custom = {
> > +       .read = zynqmp_dp_custom_read,
> > +       .write = zynqmp_dp_custom_write,
> > +       .open = simple_open,
> > +       .llseek = noop_llseek,
> > +};
> > +
> > +static int zynqmp_dp_swing_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp_train_set_priv *priv = data;
> > +       struct zynqmp_dp *dp = priv->dp;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = dp->test.train_set[priv->lane] & DP_TRAIN_VOLTAGE_SWING_MASK;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_swing_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp_train_set_priv *priv = data;
> > +       struct zynqmp_dp *dp = priv->dp;
> > +       u8 *train_set = &dp->test.train_set[priv->lane];
> > +       int ret = 0;
> > +
> > +       if (val > 3)
> > +               return -EINVAL;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *train_set &= ~(DP_TRAIN_MAX_SWING_REACHED |
> > +                       DP_TRAIN_VOLTAGE_SWING_MASK);
> > +       *train_set |= val;
> > +       if (val == 3)
> > +               *train_set |= DP_TRAIN_MAX_SWING_REACHED;
> > +
> > +       if (dp->test.active)
> > +               zynqmp_dp_update_vs_emph(dp, dp->test.train_set, true);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_swing, zynqmp_dp_swing_get,
> > +                        zynqmp_dp_swing_set, "%llu\n");
> > +
> > +static int zynqmp_dp_preemphasis_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp_train_set_priv *priv = data;
> > +       struct zynqmp_dp *dp = priv->dp;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = FIELD_GET(DP_TRAIN_PRE_EMPHASIS_MASK,
> > +                        dp->test.train_set[priv->lane]);
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_preemphasis_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp_train_set_priv *priv = data;
> > +       struct zynqmp_dp *dp = priv->dp;
> > +       u8 *train_set = &dp->test.train_set[priv->lane];
> > +       int ret = 0;
> > +
> > +       if (val > 2)
> > +               return -EINVAL;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *train_set &= ~(DP_TRAIN_MAX_PRE_EMPHASIS_REACHED |
> > +                       DP_TRAIN_PRE_EMPHASIS_MASK);
> > +       *train_set |= val;
> > +       if (val == 2)
> > +               *train_set |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
> > +
> > +       if (dp->test.active)
> > +               zynqmp_dp_update_vs_emph(dp, dp->test.train_set, true);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_preemphasis, zynqmp_dp_preemphasis_get,
> > +                        zynqmp_dp_preemphasis_set, "%llu\n");
> > +
> > +static int zynqmp_dp_lanes_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = dp->test.link_cnt;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_lanes_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +       int ret = 0;
> > +
> > +       if (val > ZYNQMP_DP_MAX_LANES)
> > +               return -EINVAL;
> > +
> > +       mutex_lock(&dp->lock);
> > +       if (val > dp->num_lanes) {
> > +               ret = -EINVAL;
> > +       } else {
> > +               dp->test.link_cnt = val;
> > +               if (dp->test.active)
> > +                       ret = zynqmp_dp_test_setup(dp);
> > +       }
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_lanes, zynqmp_dp_lanes_get,
> > +                        zynqmp_dp_lanes_set, "%llu\n");
> > +
> > +static int zynqmp_dp_rate_get(void *data, u64 *val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +
> > +       mutex_lock(&dp->lock);
> > +       *val = drm_dp_bw_code_to_link_rate(dp->test.bw_code) * 10000;
> > +       mutex_unlock(&dp->lock);
> > +       return 0;
> > +}
> > +
> > +static int zynqmp_dp_rate_set(void *data, u64 val)
> > +{
> > +       struct zynqmp_dp *dp = data;
> > +       u8 bw_code = drm_dp_link_rate_to_bw_code(val / 10000);
> > +       int link_rate = drm_dp_bw_code_to_link_rate(bw_code);
> > +       int ret = 0;
> > +
> > +       if (val / 10000 != link_rate)
> > +               return -EINVAL;
> > +
> > +       if (bw_code != DP_LINK_BW_1_62 && bw_code != DP_LINK_BW_2_7 &&
> > +           bw_code != DP_LINK_BW_5_4)
> > +               return -EINVAL;
> > +
> > +       mutex_lock(&dp->lock);
> > +       dp->test.bw_code = bw_code;
> > +       if (dp->test.active)
> > +               ret = zynqmp_dp_test_setup(dp);
> > +       mutex_unlock(&dp->lock);
> > +
> > +       return ret;
> > +}
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_rate, zynqmp_dp_rate_get,
> > +                        zynqmp_dp_rate_set, "%llu\n");
> > +
> > +static void zynqmp_dp_bridge_debugfs_init(struct drm_bridge *bridge,
> > +                                         struct dentry *root)
> > +{
> > +       struct zynqmp_dp *dp = bridge_to_dp(bridge);
> > +       struct dentry *test;
> > +       int i;
> > +
> > +       dp->test.bw_code = DP_LINK_BW_5_4;
> > +       dp->test.link_cnt = dp->num_lanes;
> > +
> > +       test = debugfs_create_dir("test", root);
> > +#define CREATE_FILE(name) \
> > +       debugfs_create_file(#name, 0600, test, dp, &fops_zynqmp_dp_##name)
> > +       CREATE_FILE(pattern);
> > +       CREATE_FILE(enhanced);
> > +       CREATE_FILE(downspread);
> > +       CREATE_FILE(active);
> > +       CREATE_FILE(custom);
> > +       CREATE_FILE(rate);
> > +       CREATE_FILE(lanes);
> > +
> > +       for (i = 0; i < dp->num_lanes; i++) {
> > +               static const char fmt[] = "lane%d_preemphasis";
> > +               char name[sizeof(fmt)];
> > +
> > +               dp->debugfs_train_set[i].dp = dp;
> > +               dp->debugfs_train_set[i].lane = i;
> > +
> > +               sprintf(name, fmt, i);
> > +               debugfs_create_file(name, 0600, test,
> > +                                   &dp->debugfs_train_set[i],
> > +                                   &fops_zynqmp_dp_preemphasis);
> > +
> > +               sprintf(name, "lane%d_swing", i);
> > +               debugfs_create_file(name, 0600, test,
> > +                                   &dp->debugfs_train_set[i],
> > +                                   &fops_zynqmp_dp_swing);
> > +       }
> > +}
> > +
> >  static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = {
> >         .attach = zynqmp_dp_bridge_attach,
> >         .detach = zynqmp_dp_bridge_detach,
> > @@ -1611,6 +2189,7 @@ static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = {
> >         .atomic_check = zynqmp_dp_bridge_atomic_check,
> >         .detect = zynqmp_dp_bridge_detect,
> >         .get_edid = zynqmp_dp_bridge_get_edid,
> > +       .debugfs_init = zynqmp_dp_bridge_debugfs_init,
> >  };
> >
> >  /* -----------------------------------------------------------------------------
> > @@ -1645,6 +2224,9 @@ static void zynqmp_dp_hpd_work_func(struct work_struct *work)
> >                                             hpd_work.work);
> >         enum drm_connector_status status;
> >
> > +       if (dp->test.active)
> > +               return;
> > +
> >         status = zynqmp_dp_bridge_detect(&dp->bridge);
> >         drm_bridge_hpd_notify(&dp->bridge, status);
> >  }
> > @@ -1666,7 +2248,14 @@ static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
> >                 if (status[4] & DP_LINK_STATUS_UPDATED ||
> >                     !drm_dp_clock_recovery_ok(&status[2], dp->mode.lane_cnt) ||
> >                     !drm_dp_channel_eq_ok(&status[2], dp->mode.lane_cnt)) {
> > -                       zynqmp_dp_train_loop(dp);
> > +                       if (dp->test.active) {
> > +                               dev_dbg(dp->dev, "Ignoring HPD IRQ in test mode\n");
> > +                       } else {
> > +                               dev_dbg(dp->dev,
> > +                                       "Retraining due to HPD IRQ (status is [%*ph])\n",
> > +                                       (int)sizeof(status), status);
> > +                               zynqmp_dp_train_loop(dp);
> > +                       }
> >                 }
> >         }
> >         mutex_unlock(&dp->lock);

-- 
Regards,

Laurent Pinchart


More information about the dri-devel mailing list