Hi Robert,
On Fri, Aug 28, 2020 at 02:13:28PM +0300, Robert Chiras (OSS) wrote:
> From: Robert Chiras <robert.chi...@nxp.com>
> 
> This patch adds support for a new clock 'video_pll' in order to better
> set the video_pll clock to a clock-rate that satisfies a mode's clock.
> The video PLL, on i.MX8MQ, can drive both DC pixel-clock and DSI phy_ref
> clock. When used with a bridge that can drive multiple modes, like a DSI
> to HDMI bridge, the DSI can't be statically configured for a specific
> mode in the DTS file.
> So, this patch adds access to the video PLL inside the DSI driver, so
> that modes can be filtered-out if the required combination of phy_ref
> and pixel-clock can't be achieved using the video PLL.
> 
> Signed-off-by: Robert Chiras <robert.chi...@nxp.com>
> ---
>  drivers/gpu/drm/bridge/nwl-dsi.c | 318 
> ++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 313 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c 
> b/drivers/gpu/drm/bridge/nwl-dsi.c
> index ce94f79..1228466 100644
> --- a/drivers/gpu/drm/bridge/nwl-dsi.c
> +++ b/drivers/gpu/drm/bridge/nwl-dsi.c
> @@ -39,6 +39,20 @@
>  
>  #define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
>  
> +/* Maximum Video PLL frequency: 1.2GHz */
> +#define MAX_PLL_FREQ 1200000000
> +
> +#define MBPS(x) ((x) * 1000000)
> +#define MIN_PHY_RATE MBPS(24)
> +#define MAX_PHY_RATE MBPS(30)
> +
> +/* Possible valid PHY reference clock rates*/

nitpick: missing ' ' at end of comment.

> +static u32 phyref_rates[] = {
> +     27000000,
> +     25000000,
> +     24000000,
> +};
> +
>  enum transfer_direction {
>       DSI_PACKET_SEND,
>       DSI_PACKET_RECEIVE,
> @@ -67,6 +81,17 @@ struct nwl_dsi_transfer {
>       size_t rx_len; /* in bytes */
>  };
>  
> +struct mode_config {

Maybe use nwl_mode_config ? There's so much other mode_config around an
this makes it obvious it's driver specific.

> +     int                             clock;
> +     int                             crtc_clock;
> +     unsigned int                    lanes;
> +     unsigned long                   bitclock;
> +     unsigned long                   phy_rates[3];
> +     unsigned long                   pll_rates[3];
> +     int                             phy_rate_idx;
> +     struct list_head                list;
> +};
> +
>  struct nwl_dsi {
>       struct drm_bridge bridge;
>       struct mipi_dsi_host dsi_host;
> @@ -101,6 +126,7 @@ struct nwl_dsi {
>       struct clk *rx_esc_clk;
>       struct clk *tx_esc_clk;
>       struct clk *core_clk;
> +     struct clk *pll_clk;
>       /*
>        * hardware bug: the i.MX8MQ needs this clock on during reset
>        * even when not using LCDIF.
> @@ -115,6 +141,7 @@ struct nwl_dsi {
>       int error;
>  
>       struct nwl_dsi_transfer *xfer;
> +     struct list_head valid_modes;
>  };
>  
>  static const struct regmap_config nwl_dsi_regmap_config = {
> @@ -778,6 +805,207 @@ static void nwl_dsi_bridge_disable(struct drm_bridge 
> *bridge)
>       pm_runtime_put(dsi->dev);
>  }
>  
> +static unsigned long nwl_dsi_get_bit_clock(struct nwl_dsi *dsi,
> +             unsigned long pixclock, u32 lanes)
> +{
> +     int bpp;
> +
> +     if (lanes < 1 || lanes > 4)
> +             return 0;
> +
> +     bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> +
> +     return (pixclock * bpp) / lanes;
> +}
> +
> +/*
> + * Utility function to calculate least commom multiple, using an
> improved

s/commom/common/

> + * version of the Euclidean algorithm for greatest common factor.
> + */
> +static unsigned long nwl_dsi_get_lcm(unsigned long a, unsigned long b)
> +{
> +     u32 gcf = 0; /* greatest common factor */
> +     unsigned long tmp_a = a;
> +     unsigned long tmp_b = b;
> +
> +     if (!a || !b)
> +             return 0;
> +
> +     while (tmp_a % tmp_b) {
> +             gcf = tmp_a % tmp_b;
> +             tmp_a = tmp_b;
> +             tmp_b = gcf;
> +     }
> +
> +     if (!gcf)
> +             return a;
> +
> +     return ((unsigned long long)a * b) / gcf;
> +}
> +
> +/*
> + * This function tries to adjust the crtc_clock for a DSI device in such a 
> way
> + * that the video pll will be able to satisfy both Display Controller pixel
> + * clock (feeding out DPI interface) and our input phy_ref clock.
> + */
> +static void nwl_dsi_setup_pll_config(struct mode_config *config)
> +{
> +     unsigned long pll_rate;
> +     int div;
> +     size_t i, num_rates = ARRAY_SIZE(config->phy_rates);
> +
> +     config->crtc_clock = 0;
> +
> +     for (i = 0; i < num_rates; i++) {
> +             int crtc_clock;
> +
> +             if (!config->phy_rates[i])
> +                     break;
> +             /*
> +              * First, we need to check if phy_ref can actually be obtained
> +              * from pixel clock. To do this, we check their lowest common
> +              * multiple, which has to be in PLL range.
> +              */
> +             pll_rate = nwl_dsi_get_lcm(config->clock, config->phy_rates[i]);
> +             if (pll_rate > MAX_PLL_FREQ) {
> +                     /* Drop pll_rate to a realistic value */
> +                     while (pll_rate > MAX_PLL_FREQ)
> +                             pll_rate >>= 1;
> +                     /* Make sure pll_rate can provide phy_ref rate */
> +                     div = DIV_ROUND_UP(pll_rate, config->phy_rates[i]);
> +                     pll_rate = config->phy_rates[i] * div;
> +             } else {
> +                     /*
> +                      * Increase the pll rate to highest possible rate for
> +                      * better accuracy.
> +                      */
> +                     while (pll_rate <= MAX_PLL_FREQ)
> +                             pll_rate <<= 1;
> +                     pll_rate >>= 1;
> +             }
> +
> +             /*
> +              * Next, we need to tweak the pll_rate to a value that can also
> +              * satisfy the crtc_clock.
> +              */
> +             div = DIV_ROUND_CLOSEST(pll_rate, config->clock);
> +             if (lvl)
> +                     pll_rate -= config->phy_rates[i] * lvl;

lvl gets never defined so it doesn't compile breaking bisection.

> +             crtc_clock = pll_rate / div;
> +             config->pll_rates[i] = pll_rate;
> +
> +             /*
> +              * Pick a crtc_clock which is closest to pixel clock.
> +              * Also, make sure that the pixel clock is a multiply of
> +              * 50Hz.
> +              */
> +             if (!(crtc_clock % 50) &&
> +                 abs(config->clock - crtc_clock) <
> +                 abs(config->clock - config->crtc_clock)) {
> +                     config->crtc_clock = crtc_clock;
> +                     config->phy_rate_idx = i;
> +             }
> +     }
> +}
> +
> +
> +/*
> + * This function will try the required phy speed for current mode
> + * If the phy speed can be achieved, the phy will save the speed
> + * configuration
> + */
> +static struct mode_config *nwl_dsi_mode_probe(struct nwl_dsi *dsi,
> +                         const struct drm_display_mode *mode)
> +{
> +     struct device *dev = dsi->dev;
> +     struct mode_config *config;
> +     union phy_configure_opts phy_opts;
> +     unsigned long clock = mode->clock * 1000;
> +     unsigned long bit_clk = 0;
> +     unsigned long phy_rates[3] = {0};
> +     int match_rates = 0;
> +     u32 lanes = dsi->lanes;
> +     size_t i = 0, num_rates = ARRAY_SIZE(phyref_rates);
> +
> +     list_for_each_entry(config, &dsi->valid_modes, list)
> +             if (config->clock == clock)
> +                     return config;
> +
> +     phy_mipi_dphy_get_default_config(clock,
> +                     mipi_dsi_pixel_format_to_bpp(dsi->format),
> +                     lanes, &phy_opts.mipi_dphy);
> +     phy_opts.mipi_dphy.lp_clk_rate = clk_get_rate(dsi->tx_esc_clk);
> +
> +     while (i < num_rates) {
> +             int ret;
> +
> +             bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
> +
> +             clk_set_rate(dsi->pll_clk, phyref_rates[i] * 32);
> +             clk_set_rate(dsi->phy_ref_clk, phyref_rates[i]);
> +             ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, &phy_opts);
> +
> +             /* Pick the non-failing rate, and search for more */
> +             if (!ret) {
> +                     phy_rates[match_rates++] = phyref_rates[i++];
> +                     continue;
> +             }
> +
> +             if (match_rates)
> +                     break;
> +
> +             /* Reached the end of phyref_rates, try another lane config */
> +             if ((i++ == num_rates - 1) && (--lanes > 2)) {
> +                     i = 0;
> +                     continue;
> +             }
> +     }
> +
> +     /*
> +      * Try swinging between min and max pll rates and see what rate (in 
> terms
> +      * of kHz) we can custom use to get the required bit-clock.
> +      */
> +     if (!match_rates) {
> +             int min_div, max_div;
> +             int bit_clk_khz;
> +
> +             lanes = dsi->lanes;
> +             bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
> +
> +             min_div = DIV_ROUND_UP(bit_clk, MAX_PHY_RATE);
> +             max_div = DIV_ROUND_DOWN_ULL(bit_clk, MIN_PHY_RATE);
> +             bit_clk_khz = bit_clk / 1000;
> +
> +             for (i = max_div; i > min_div; i--) {
> +                     if (!(bit_clk_khz % i)) {
> +                             phy_rates[0] = bit_clk / i;
> +                             match_rates = 1;
> +                             break;
> +                     }
> +             }
> +     }
> +
> +     if (!match_rates) {
> +             DRM_DEV_DEBUG_DRIVER(dev,
> +                     "Cannot setup PHY for mode: %ux%u @%d kHz\n",
> +                     mode->hdisplay,
> +                     mode->vdisplay,
> +                     mode->clock);
> +
> +             return NULL;
> +     }
> +
> +     config = devm_kzalloc(dsi->dev, sizeof(struct mode_config), GFP_KERNEL);
> +     config->clock = clock;
> +     config->lanes = lanes;
> +     config->bitclock = bit_clk;
> +     memcpy(&config->phy_rates, &phy_rates, sizeof(phy_rates));
> +     list_add(&config->list, &dsi->valid_modes);
> +
> +     return config;
> +}
> +
> +
>  static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
>                                  const struct drm_display_mode *mode,
>                                  union phy_configure_opts *phy_opts)
> @@ -809,6 +1037,38 @@ static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge 
> *bridge,
>                                     const struct drm_display_mode *mode,
>                                     struct drm_display_mode *adjusted_mode)
>  {
> +     struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> +     struct mode_config *config;
> +     unsigned long pll_rate;
> +
> +     DRM_DEV_DEBUG_DRIVER(dsi->dev, "Fixup mode:\n");
> +     drm_mode_debug_printmodeline(adjusted_mode);
> +
> +     config = nwl_dsi_mode_probe(dsi, adjusted_mode);

Since this ends up calling `clk_set_rate` doesn't this violate the
`Drivers MUST NOT touch any persistent state` rule of the fixup
function? So would it be better to teach the phy about it's possible
rates rather then NWL? I guess that get's tricky your clock-drop-level
is dependent on the phy rate later on.

> +     if (!config)
> +             return false;
> +
> +     DRM_DEV_DEBUG_DRIVER(dsi->dev, "lanes=%u, data_rate=%lu\n",
> +                          config->lanes, config->bitclock);
> +     if (config->lanes < 2 || config->lanes > 4)
> +             return false;
> +
> +     /* Max data rate for this controller is 1.5Gbps */
> +     if (config->bitclock > 1500000000)
> +             return false;
> +
> +     pll_rate = config->pll_rates[config->phy_rate_idx];
> +     if (dsi->pll_clk && pll_rate) {
> +             clk_set_rate(dsi->pll_clk, pll_rate);
> +             DRM_DEV_DEBUG_DRIVER(dsi->dev,
> +                                  "Video pll rate: %lu (actual: %lu)",
> +                                  pll_rate, clk_get_rate(dsi->pll_clk));
> +     }
> +     /* Update the crtc_clock to be used by display controller */
> +     if (config->crtc_clock)
> +             adjusted_mode->crtc_clock = config->crtc_clock / 1000;
> +
> +
>       /* At least LCDIF + NWL needs active high sync */
>       adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
>       adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
> @@ -822,14 +1082,29 @@ nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
>                         const struct drm_display_mode *mode)
>  {
>       struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> -     int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> +     struct mode_config *config;
> +     unsigned long pll_rate;
> +     int bit_rate;
>  
> -     if (mode->clock * bpp > 15000000 * dsi->lanes)
> +     bit_rate = nwl_dsi_get_bit_clock(dsi, mode->clock * 1000, dsi->lanes);
> +
> +     DRM_DEV_DEBUG_DRIVER(dsi->dev, "Validating mode:");
> +     drm_mode_debug_printmodeline(mode);

These (and other locations) are a bit confusings since
`drm_mode_debug_printmodeline` uses DRM_DEBUG_KMS so if one only enabled
driver debugging (0x2) one gets: `Validating mode:` but then nothing.

> +
> +     if (bit_rate > MBPS(1500))
>               return MODE_CLOCK_HIGH;
>  
> -     if (mode->clock * bpp < 80000 * dsi->lanes)
> +     if (bit_rate < MBPS(80))
>               return MODE_CLOCK_LOW;
>  
> +     config = nwl_dsi_mode_probe(dsi, mode);
> +     if (!config)
> +             return MODE_NOCLOCK;
> +
> +     pll_rate = config->pll_rates[config->phy_rate_idx];
> +     if (dsi->pll_clk && !pll_rate)
> +             nwl_dsi_setup_pll_config(config);
> +
>       return MODE_OK;
>  }
>  
> @@ -842,8 +1117,22 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
>       struct device *dev = dsi->dev;
>       union phy_configure_opts new_cfg;
>       unsigned long phy_ref_rate;
> +     struct mode_config *config;
>       int ret;
>  
> +     DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setting mode:\n");
> +     drm_mode_debug_printmodeline(adjusted_mode);
> +
> +     config = nwl_dsi_mode_probe(dsi, adjusted_mode);
> +     /* New mode? This should NOT happen */
> +     if (!config) {
> +             DRM_DEV_ERROR(dsi->dev, "Unsupported mode provided:\n");
> +             drm_mode_debug_printmodeline(adjusted_mode);
> +             return;
> +     }
> +
> +     phy_ref_rate = config->phy_rates[config->phy_rate_idx];
> +     clk_set_rate(dsi->phy_ref_clk, phy_ref_rate);
>       ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
>       if (ret < 0)
>               return;
> @@ -855,8 +1144,10 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
>       if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate)
>               return;
>  
> -     phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
> -     DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
> +     DRM_DEV_DEBUG_DRIVER(dev,
> +                          "PHY at ref rate: %lu (actual: %lu)\n",
> +                          phy_ref_rate, clk_get_rate(dsi->phy_ref_clk));
> +
>       /* Save the new desired phy config */
>       memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
>  
> @@ -1014,6 +1305,12 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
>       }
>       dsi->tx_esc_clk = clk;
>  
> +     /* The video_pll clock is optional */
> +     clk = devm_clk_get(dsi->dev, "video_pll");

Using devm_clk_get_optional return NULL so we can return a proper error
on other failures.

> +     if (!IS_ERR(clk))
> +             dsi->pll_clk = clk;
> +
> +

Drop one empty line.
Cheers,
 -- Guido

>       dsi->mux = devm_mux_control_get(dsi->dev, NULL);
>       if (IS_ERR(dsi->mux)) {
>               ret = PTR_ERR(dsi->mux);
> @@ -1066,6 +1363,9 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
>                             PTR_ERR(dsi->rst_dpi));
>               return PTR_ERR(dsi->rst_dpi);
>       }
> +
> +     INIT_LIST_HEAD(&dsi->valid_modes);
> +
>       return 0;
>  }
>  
> @@ -1184,6 +1484,14 @@ static int nwl_dsi_probe(struct platform_device *pdev)
>  static int nwl_dsi_remove(struct platform_device *pdev)
>  {
>       struct nwl_dsi *dsi = platform_get_drvdata(pdev);
> +     struct mode_config *config;
> +     struct list_head *pos, *tmp;
> +
> +     list_for_each_safe(pos, tmp, &dsi->valid_modes) {
> +             config = list_entry(pos, struct mode_config, list);
> +             list_del(pos);
> +             devm_kfree(dsi->dev, config);
> +     }
>  
>       nwl_dsi_deselect_input(dsi);
>       mipi_dsi_host_unregister(&dsi->dsi_host);
> -- 
> 2.7.4
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to