drm/tegra: sor - Don't hardcode link parameters
authorThierry Reding <treding@nvidia.com>
Thu, 5 Jun 2014 14:31:10 +0000 (16:31 +0200)
committerThierry Reding <treding@nvidia.com>
Mon, 9 Jun 2014 10:02:47 +0000 (12:02 +0200)
The currently hardcoded link parameters don't work on all eDP panels, so
compute the parameters at runtime depending on the mode and panel type
to allow the driver to cope with a wider variety of panels.

Note that the number of bits per pixel of the panel is still hardcoded,
but this can be addressed in a separate patch.

This is largely based on a patch by Stéphane Marchesin but the algorithm
was largely rewritten to be more readable and concise.

Signed-off-by: Stéphane Marchesin <marcheu@chromium.org>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/sor.c

index f082ea22f32eb6abdb76be284734e76ca16fa88b..4b554908be5eba32fdf717628ce159c40c42507e 100644 (file)
@@ -40,6 +40,16 @@ struct tegra_sor {
        struct dentry *debugfs;
 };
 
+struct tegra_sor_config {
+       u32 bits_per_pixel;
+
+       u32 active_polarity;
+       u32 active_count;
+       u32 tu_size;
+       u32 active_frac;
+       u32 watermark;
+};
+
 static inline struct tegra_sor *
 host1x_client_to_sor(struct host1x_client *client)
 {
@@ -293,12 +303,173 @@ static int tegra_sor_power_up(struct tegra_sor *sor, unsigned long timeout)
        return -ETIMEDOUT;
 }
 
+struct tegra_sor_params {
+       /* number of link clocks per line */
+       unsigned int num_clocks;
+       /* ratio between input and output */
+       u64 ratio;
+       /* precision factor */
+       u64 precision;
+
+       unsigned int active_polarity;
+       unsigned int active_count;
+       unsigned int active_frac;
+       unsigned int tu_size;
+       unsigned int error;
+};
+
+static int tegra_sor_compute_params(struct tegra_sor *sor,
+                                   struct tegra_sor_params *params,
+                                   unsigned int tu_size)
+{
+       u64 active_sym, active_count, frac, approx;
+       u32 active_polarity, active_frac = 0;
+       const u64 f = params->precision;
+       s64 error;
+
+       active_sym = params->ratio * tu_size;
+       active_count = div_u64(active_sym, f) * f;
+       frac = active_sym - active_count;
+
+       /* fraction < 0.5 */
+       if (frac >= (f / 2)) {
+               active_polarity = 1;
+               frac = f - frac;
+       } else {
+               active_polarity = 0;
+       }
+
+       if (frac != 0) {
+               frac = div_u64(f * f,  frac); /* 1/fraction */
+               if (frac <= (15 * f)) {
+                       active_frac = div_u64(frac, f);
+
+                       /* round up */
+                       if (active_polarity)
+                               active_frac++;
+               } else {
+                       active_frac = active_polarity ? 1 : 15;
+               }
+       }
+
+       if (active_frac == 1)
+               active_polarity = 0;
+
+       if (active_polarity == 1) {
+               if (active_frac) {
+                       approx = active_count + (active_frac * (f - 1)) * f;
+                       approx = div_u64(approx, active_frac * f);
+               } else {
+                       approx = active_count + f;
+               }
+       } else {
+               if (active_frac)
+                       approx = active_count + div_u64(f, active_frac);
+               else
+                       approx = active_count;
+       }
+
+       error = div_s64(active_sym - approx, tu_size);
+       error *= params->num_clocks;
+
+       if (error <= 0 && abs64(error) < params->error) {
+               params->active_count = div_u64(active_count, f);
+               params->active_polarity = active_polarity;
+               params->active_frac = active_frac;
+               params->error = abs64(error);
+               params->tu_size = tu_size;
+
+               if (error == 0)
+                       return true;
+       }
+
+       return false;
+}
+
+static int tegra_sor_calc_config(struct tegra_sor *sor,
+                                struct drm_display_mode *mode,
+                                struct tegra_sor_config *config,
+                                struct drm_dp_link *link)
+{
+       const u64 f = 100000, link_rate = link->rate * 1000;
+       const u64 pclk = mode->clock * 1000;
+       struct tegra_sor_params params;
+       u64 input, output, watermark;
+       u32 num_syms_per_line;
+       unsigned int i;
+
+       if (!link_rate || !link->num_lanes || !pclk || !config->bits_per_pixel)
+               return -EINVAL;
+
+       output = link_rate * 8 * link->num_lanes;
+       input = pclk * config->bits_per_pixel;
+
+       if (input >= output)
+               return -ERANGE;
+
+       memset(&params, 0, sizeof(params));
+       params.ratio = div64_u64(input * f, output);
+       params.num_clocks = div_u64(link_rate * mode->hdisplay, pclk);
+       params.precision = f;
+       params.error = 64 * f;
+       params.tu_size = 64;
+
+       for (i = params.tu_size; i >= 32; i--)
+               if (tegra_sor_compute_params(sor, &params, i))
+                       break;
+
+       if (params.active_frac == 0) {
+               config->active_polarity = 0;
+               config->active_count = params.active_count;
+
+               if (!params.active_polarity)
+                       config->active_count--;
+
+               config->tu_size = params.tu_size;
+               config->active_frac = 1;
+       } else {
+               config->active_polarity = params.active_polarity;
+               config->active_count = params.active_count;
+               config->active_frac = params.active_frac;
+               config->tu_size = params.tu_size;
+       }
+
+       dev_dbg(sor->dev,
+               "polarity: %d active count: %d tu size: %d active frac: %d\n",
+               config->active_polarity, config->active_count,
+               config->tu_size, config->active_frac);
+
+       watermark = params.ratio * config->tu_size * (f - params.ratio);
+       watermark = div_u64(watermark, f);
+
+       watermark = div_u64(watermark + params.error, f);
+       config->watermark = watermark + (config->bits_per_pixel / 8) + 2;
+       num_syms_per_line = (mode->hdisplay * config->bits_per_pixel) *
+                           (link->num_lanes * 8);
+
+       if (config->watermark > 30) {
+               config->watermark = 30;
+               dev_err(sor->dev,
+                       "unable to compute TU size, forcing watermark to %u\n",
+                       config->watermark);
+       } else if (config->watermark > num_syms_per_line) {
+               config->watermark = num_syms_per_line;
+               dev_err(sor->dev, "watermark too high, forcing to %u\n",
+                       config->watermark);
+       }
+
+       return 0;
+}
+
 static int tegra_output_sor_enable(struct tegra_output *output)
 {
        struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
        struct drm_display_mode *mode = &dc->base.mode;
        unsigned int vbe, vse, hbe, hse, vbs, hbs, i;
        struct tegra_sor *sor = to_sor(output);
+       struct tegra_sor_config config;
+       struct drm_dp_link link;
+       struct drm_dp_aux *aux;
        unsigned long value;
        int err = 0;
 
@@ -313,16 +484,34 @@ static int tegra_output_sor_enable(struct tegra_output *output)
 
        reset_control_deassert(sor->rst);
 
+       /* FIXME: properly convert to struct drm_dp_aux */
+       aux = (struct drm_dp_aux *)sor->dpaux;
+
        if (sor->dpaux) {
                err = tegra_dpaux_enable(sor->dpaux);
                if (err < 0)
                        dev_err(sor->dev, "failed to enable DP: %d\n", err);
+
+               err = drm_dp_link_probe(aux, &link);
+               if (err < 0) {
+                       dev_err(sor->dev, "failed to probe eDP link: %d\n",
+                               err);
+                       return err;
+               }
        }
 
        err = clk_set_parent(sor->clk, sor->clk_safe);
        if (err < 0)
                dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
 
+       memset(&config, 0, sizeof(config));
+       config.bits_per_pixel = 24; /* XXX: don't hardcode? */
+
+       err = tegra_sor_calc_config(sor, mode, &config, &link);
+       if (err < 0)
+               dev_err(sor->dev, "failed to compute link configuration: %d\n",
+                       err);
+
        value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
        value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK;
        value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK;
@@ -460,7 +649,7 @@ static int tegra_output_sor_enable(struct tegra_output *output)
        value |= SOR_DP_LINKCTL_ENABLE;
 
        value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK;
-       value |= SOR_DP_LINKCTL_TU_SIZE(59); /* XXX: don't hardcode? */
+       value |= SOR_DP_LINKCTL_TU_SIZE(config.tu_size);
 
        value |= SOR_DP_LINKCTL_ENHANCED_FRAME;
        tegra_sor_writel(sor, value, SOR_DP_LINKCTL_0);
@@ -476,15 +665,18 @@ static int tegra_output_sor_enable(struct tegra_output *output)
 
        value = tegra_sor_readl(sor, SOR_DP_CONFIG_0);
        value &= ~SOR_DP_CONFIG_WATERMARK_MASK;
-       value |= SOR_DP_CONFIG_WATERMARK(14); /* XXX: don't hardcode? */
+       value |= SOR_DP_CONFIG_WATERMARK(config.watermark);
 
        value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK;
-       value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(47); /* XXX: don't hardcode? */
+       value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config.active_count);
 
        value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK;
-       value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(9); /* XXX: don't hardcode? */
+       value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config.active_frac);
 
-       value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; /* XXX: don't hardcode? */
+       if (config.active_polarity)
+               value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
+       else
+               value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
 
        value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE;
        value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE; /* XXX: don't hardcode? */
@@ -506,9 +698,6 @@ static int tegra_output_sor_enable(struct tegra_output *output)
        tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
 
        if (sor->dpaux) {
-               /* FIXME: properly convert to struct drm_dp_aux */
-               struct drm_dp_aux *aux = (struct drm_dp_aux *)sor->dpaux;
-               struct drm_dp_link link;
                u8 rate, lanes;
 
                err = drm_dp_link_probe(aux, &link);
@@ -592,12 +781,26 @@ static int tegra_output_sor_enable(struct tegra_output *output)
         * configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete
         * raster, associate with display controller)
         */
-       value = SOR_STATE_ASY_PIXELDEPTH_BPP_24_444 |
-               SOR_STATE_ASY_VSYNCPOL |
+       value = SOR_STATE_ASY_VSYNCPOL |
                SOR_STATE_ASY_HSYNCPOL |
                SOR_STATE_ASY_PROTOCOL_DP_A |
                SOR_STATE_ASY_CRC_MODE_COMPLETE |
                SOR_STATE_ASY_OWNER(dc->pipe + 1);
+
+       switch (config.bits_per_pixel) {
+       case 24:
+               value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
+               break;
+
+       case 18:
+               value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444;
+               break;
+
+       default:
+               BUG();
+               break;
+       }
+
        tegra_sor_writel(sor, value, SOR_STATE_1);
 
        /*