MLK-17537-8: drm/mxsfb: Add support for mode_valid
authorRobert Chiras <robert.chiras@nxp.com>
Mon, 26 Nov 2018 12:12:07 +0000 (14:12 +0200)
committerLeonard Crestez <leonard.crestez@nxp.com>
Wed, 17 Apr 2019 23:51:34 +0000 (02:51 +0300)
Implement mode_valid and check functions from
drm_simple_display_pipe_funcs such that we can filter-out modes that
cannot be driven by this controller.
Add 3 new clocks:
- video_pll: this is the PLL that provides the pixel clock; it's rate
  needs to be set such that the pixel clock can be achieved
- osc_25: this is an oscillater that can be used as source clock for the
  video_pll; default freq is 25MHz
- osc_27: same as above, but with freq of 27MHz

Depending on the display mode used, the video_pll needs to have it's
clock source a 25MHz or 27MHz oscillator. Also, the video_pll rate needs
to be set to a rate that can be evenly divided to obtain the required
pixel clock. All these settings (clock source and video_pll rate) are
saved in mode_valid, then applied before mode needs to be set in the
check function.

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
Reviewed-by: Laurentiu Palcu <laurentiu.palcu@nxp.com>
Documentation/devicetree/bindings/display/mxsfb.txt
drivers/gpu/drm/mxsfb/mxsfb_crtc.c
drivers/gpu/drm/mxsfb/mxsfb_drv.c
drivers/gpu/drm/mxsfb/mxsfb_drv.h

index 7040fb0..234481c 100644 (file)
@@ -13,6 +13,8 @@ Required properties:
 - clock-names: A list of clock names. For MXSFB it should contain:
     - "pix" for the LCDIF block clock
     - (MX6SX-only) "axi", "disp_axi" for the bus interface clock
+    - (MX8-only) "video_pll, "osc_25", "osc_27" for the VIDEO_PLL,
+      OSC_25M and OSC_27M clocks
 
 Required sub-nodes:
   - port: The connection to an encoder chip.
index 64ad135..94a6d16 100644 (file)
@@ -28,6 +28,7 @@
 #include <drm/drm_simple_kms_helper.h>
 #include <linux/busfreq-imx.h>
 #include <linux/clk.h>
+#include <linux/clk-provider.h>
 #include <linux/iopoll.h>
 #include <linux/of_graph.h>
 #include <linux/platform_data/simplefb.h>
@@ -343,6 +344,8 @@ static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb)
        const u32 bus_flags = mxsfb->connector->display_info.bus_flags;
        u32 vdctrl0, vsync_pulse_len, hsync_pulse_len;
        int err;
+       u32 pixclock = m->clock * 1000;
+       struct mode_config *config;
 
        /*
         * It seems, you can't re-program the controller if it is still
@@ -355,6 +358,27 @@ static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb)
        if (err)
                return;
 
+       /*
+        * Before setting the clock rate, we need to be sure that the clock
+        * has the right source to output the required rate.
+        */
+       list_for_each_entry(config, &mxsfb->valid_modes, list) {
+               if (config->clock == pixclock) {
+                       struct clk *src;
+
+                       src = clk_get_parent(mxsfb->clk_sel);
+                       if (!clk_is_match(src, config->clk_src))
+                               clk_set_parent(mxsfb->clk_sel, config->clk_src);
+                       if (clk_get_rate(mxsfb->clk_pll) != config->out_rate)
+                               clk_set_rate(mxsfb->clk_pll, config->out_rate);
+                       DRM_DEV_DEBUG_DRIVER(mxsfb->dev,
+                               "pll rate: %ld (actual %ld)\n",
+                               config->out_rate, clk_get_rate(mxsfb->clk_pll));
+                       pixclock = config->mode_clock;
+                       break;
+               }
+       }
+
        /* Clear the FIFOs */
        writel(CTRL1_FIFO_CLEAR, mxsfb->base + LCDC_CTRL1 + REG_SET);
 
@@ -362,9 +386,9 @@ static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb)
        if (err)
                return;
 
-       clk_set_rate(mxsfb->clk, m->crtc_clock * 1000);
+       clk_set_rate(mxsfb->clk, pixclock);
        DRM_DEV_DEBUG_DRIVER(mxsfb->dev, "Pixel clock: %dkHz (actual: %dkHz)\n",
-               m->crtc_clock, (int)(clk_get_rate(mxsfb->clk) / 1000));
+               pixclock / 1000, (int)(clk_get_rate(mxsfb->clk) / 1000));
 
        DRM_DEV_DEBUG_DRIVER(mxsfb->dev,
                "Connector bus_flags: 0x%08X\n", bus_flags);
index 2f20c62..d235686 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/module.h>
 #include <linux/spinlock.h>
 #include <linux/clk.h>
+#include <linux/clk-provider.h>
 #include <linux/component.h>
 #include <linux/list.h>
 #include <linux/of_device.h>
 /* The eLCDIF max possible CRTCs */
 #define MAX_CRTCS 1
 
+/* Maximum Video PLL frequency */
+#define MAX_PLL_FREQ 1200000000
+/* Mininum pixel clock in Hz */
+#define MIN_PIX_CLK  74250000
 enum mxsfb_devtype {
        MXSFB_V3,
        MXSFB_V4,
@@ -160,6 +165,153 @@ static const struct drm_mode_config_helper_funcs mxsfb_mode_config_helpers = {
        .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
 };
 
+static struct clk *mxsfb_find_src_clk(struct mxsfb_drm_private *mxsfb,
+              int crtc_clock,
+              unsigned long *out_rate)
+{
+       struct clk *src = NULL;
+       struct clk *p = mxsfb->clk;
+       struct clk *src_clk[MAX_CLK_SRC];
+       int num_src_clk = ARRAY_SIZE(mxsfb->clk_src);
+       unsigned long src_rate;
+       int i;
+
+       for (i = 0; i < num_src_clk; i++)
+               src_clk[i] = mxsfb->clk_src[i];
+
+       /*
+        * First, check the current clock source and find the clock
+        * selector
+        */
+       while (p) {
+               struct clk *pp = clk_get_parent(p);
+
+               for (i = 0; i < num_src_clk; i++)
+                       if (src_clk[i] && clk_is_match(pp, src_clk[i])) {
+                               src = pp;
+                               mxsfb->clk_sel = p;
+                               src_clk[i] = NULL;
+                               break;
+                       }
+
+               if (src)
+                       break;
+
+               p = pp;
+       }
+
+       while (!IS_ERR_OR_NULL(src)) {
+               /* Check if current rate satisfies our needs */
+               src_rate = clk_get_rate(src);
+               *out_rate = clk_get_rate(mxsfb->clk_pll);
+               if (!(*out_rate % crtc_clock))
+                       break;
+
+               /* Find the highest rate that fits our needs */
+               *out_rate = crtc_clock * (MAX_PLL_FREQ / crtc_clock);
+               if (!(*out_rate % src_rate))
+                       break;
+
+               /* Get the next clock source available */
+               src = NULL;
+               for (i = 0; i < num_src_clk; i++) {
+                       if (IS_ERR_OR_NULL(src_clk[i]))
+                               continue;
+                       src = src_clk[i];
+                       src_clk[i] = NULL;
+                       break;
+               }
+       }
+
+       return src;
+}
+
+static enum drm_mode_status
+mxsfb_pipe_mode_valid(struct drm_crtc *crtc,
+                     const struct drm_display_mode *mode)
+{
+       struct drm_simple_display_pipe *pipe =
+              container_of(crtc, struct drm_simple_display_pipe, crtc);
+       struct mxsfb_drm_private *mxsfb = drm_pipe_to_mxsfb_drm_private(pipe);
+       struct clk *src = NULL;
+       int clock = mode->clock * 1000;
+       int crtc_clock = mode->crtc_clock * 1000;
+       unsigned long out_rate;
+       struct mode_config *config;
+
+       /*
+        * In order to verify possible clock sources we need to have at least
+        * two of them.
+        */
+       if (!mxsfb->clk_src[0] || !mxsfb->clk_src[1])
+               return MODE_OK;
+
+       /*
+        * TODO: Currently, only modes with pixel clock higher or equal to
+        * 74250kHz are working. Limit to these modes until we figure out how
+        * to handle the rest of the display modes.
+        */
+       if (clock < MIN_PIX_CLK)
+               return MODE_NOCLOCK;
+
+       if (!crtc_clock)
+               crtc_clock = clock;
+
+       DRM_DEV_DEBUG_DRIVER(mxsfb->dev, "Validating mode:\n");
+       drm_mode_debug_printmodeline(mode);
+       /* Skip saving the config again */
+       list_for_each_entry(config, &mxsfb->valid_modes, list)
+               if (config->clock == clock)
+                       return MODE_OK;
+
+       src = mxsfb_find_src_clk(mxsfb, crtc_clock, &out_rate);
+
+       if (IS_ERR_OR_NULL(src))
+               return MODE_NOCLOCK;
+
+       clk_set_rate(mxsfb->clk_pll, out_rate);
+
+       /* Save this configuration for later use */
+       config = devm_kzalloc(mxsfb->dev,
+                sizeof(struct mode_config), GFP_KERNEL);
+       config->clk_src = src;
+       config->out_rate = out_rate;
+       config->clock = clock;
+       config->mode_clock = crtc_clock;
+       list_add(&config->list, &mxsfb->valid_modes);
+
+       return MODE_OK;
+}
+
+static int mxsfb_pipe_check(struct drm_simple_display_pipe *pipe,
+                    struct drm_plane_state *plane_state,
+                    struct drm_crtc_state *crtc_state)
+{
+       struct mxsfb_drm_private *mxsfb = drm_pipe_to_mxsfb_drm_private(pipe);
+       struct drm_display_mode *mode = &crtc_state->mode;
+       struct mode_config *config;
+       struct clk *src;
+
+       DRM_DEV_DEBUG_DRIVER(mxsfb->dev, "Checking mode:\n");
+       drm_mode_debug_printmodeline(mode);
+
+       /* Make sure that current mode can get the required clock */
+       list_for_each_entry(config, &mxsfb->valid_modes, list)
+               if (config->clock == mode->clock * 1000) {
+                       src = clk_get_parent(mxsfb->clk_sel);
+                       if (!clk_is_match(src, config->clk_src))
+                               clk_set_parent(mxsfb->clk_sel, config->clk_src);
+                       if (clk_get_rate(mxsfb->clk_pll) != config->out_rate)
+                               clk_set_rate(mxsfb->clk_pll, config->out_rate);
+                       DRM_DEV_DEBUG_DRIVER(mxsfb->dev,
+                               "pll rate: %ld (actual %ld)\n",
+                               config->out_rate, clk_get_rate(mxsfb->clk_pll));
+                       break;
+               }
+
+       return 0;
+}
+
 static void mxsfb_pipe_enable(struct drm_simple_display_pipe *pipe,
                              struct drm_crtc_state *crtc_state,
                              struct drm_plane_state *plane_state)
@@ -254,6 +406,8 @@ static void mxsfb_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
 }
 
 static struct drm_simple_display_pipe_funcs mxsfb_funcs = {
+       .mode_valid     = mxsfb_pipe_mode_valid,
+       .check          = mxsfb_pipe_check,
        .enable         = mxsfb_pipe_enable,
        .disable        = mxsfb_pipe_disable,
        .update         = mxsfb_pipe_update,
@@ -297,6 +451,20 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags)
        if (IS_ERR(mxsfb->clk_disp_axi))
                mxsfb->clk_disp_axi = NULL;
 
+       mxsfb->clk_pll = devm_clk_get(drm->dev, "video_pll");
+       if (IS_ERR(mxsfb->clk_pll))
+               mxsfb->clk_pll = NULL;
+
+       mxsfb->clk_src[0] = devm_clk_get(drm->dev, "osc_25");
+       if (IS_ERR(mxsfb->clk_src[0]))
+               mxsfb->clk_src[0] = NULL;
+
+       mxsfb->clk_src[1] = devm_clk_get(drm->dev, "osc_27");
+       if (IS_ERR(mxsfb->clk_src[1]))
+               mxsfb->clk_src[1] = NULL;
+
+       INIT_LIST_HEAD(&mxsfb->valid_modes);
+
        ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
        if (ret)
                return ret;
@@ -401,6 +569,8 @@ err_irq:
 static void mxsfb_unload(struct drm_device *drm)
 {
        struct mxsfb_drm_private *mxsfb = drm->dev_private;
+       struct mode_config *config;
+       struct list_head *pos, *tmp;
 
        if (mxsfb->fbdev)
                drm_fbdev_cma_fini(mxsfb->fbdev);
@@ -412,6 +582,12 @@ static void mxsfb_unload(struct drm_device *drm)
        drm_irq_uninstall(drm);
        pm_runtime_put_sync(drm->dev);
 
+       list_for_each_safe(pos, tmp, &mxsfb->valid_modes) {
+               config = list_entry(pos, struct mode_config, list);
+               list_del(pos);
+               devm_kfree(mxsfb->dev, config);
+       }
+
        drm->dev_private = NULL;
 
        pm_runtime_disable(drm->dev);
index d081329..24d573f 100644 (file)
@@ -16,6 +16,8 @@
 #ifndef __MXSFB_DRV_H__
 #define __MXSFB_DRV_H__
 
+#define MAX_CLK_SRC 2
+
 struct mxsfb_devdata {
        unsigned int     transfer_count;
        unsigned int     cur_buf;
@@ -28,6 +30,14 @@ struct mxsfb_devdata {
        unsigned int     num_formats;
 };
 
+struct mode_config {
+       struct clk *clk_src;
+       unsigned long out_rate;
+       int clock;
+       int mode_clock;
+       struct list_head list;
+};
+
 struct mxsfb_drm_private {
        struct device                   *dev;
        const struct mxsfb_devdata      *devdata;
@@ -36,6 +46,8 @@ struct mxsfb_drm_private {
        struct clk                      *clk;
        struct clk                      *clk_axi;
        struct clk                      *clk_disp_axi;
+       struct clk                      *clk_src[MAX_CLK_SRC];
+       struct clk                      *clk_sel, *clk_pll;
 
        struct drm_simple_display_pipe  pipe;
        struct drm_connector            panel_connector;
@@ -43,6 +55,8 @@ struct mxsfb_drm_private {
        struct drm_panel                *panel;
        struct drm_bridge               *bridge;
        struct drm_fbdev_cma            *fbdev;
+
+       struct list_head                valid_modes;
 };
 
 int mxsfb_setup_crtc(struct drm_device *dev);