MLK-17537-9: gpu/imx: dcss: Add support for mode_valid
authorRobert Chiras <robert.chiras@nxp.com>
Thu, 7 Feb 2019 13:04:53 +0000 (15:04 +0200)
committerLeonard Crestez <leonard.crestez@nxp.com>
Wed, 17 Apr 2019 23:51:34 +0000 (02:51 +0300)
Implement mode_valid and mode_fixup functions for the dcss-crtc
driver so that DCSS can filter-out unsupported modes and save the
configuration for the supported ones.
Use mode_fixup to apply the saved configuration of a supported mode.
The mechanism to determine if a mode is supported or not is made in
dcss-dtg.

Also, add 2 new clocks:
- pll: this is the video PLL that provides the pixel clock; it's rate
  needs to be set such that the pixel clock can be achieved
- pll_src*: this is an oscillator that can be used as source clock for
  the video pll; currently, there are possible maximum 3 pll sources,
  defined as pll_src1, pll_src2 and pll_src3. The actual clocks that
  can be used as pll source are: CLK_25M, CLK_27M and CLK_PHY_27MHZ

Removed the pdiv_clk and pout_clk and replaced them with pix_clk,
since out of those two only one was used: pdiv_clk, representing the pixel
clock.

In dcss-dtg, each mode is tested and if we can achieve it's pixel
clock we save this mode configuration into an internal list and apply this
configuration later on when mode_fixup is called.

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
Reviewed-by: Laurentiu Palcu <laurentiu.palcu@nxp.com>
drivers/gpu/imx/dcss/dcss-common.c
drivers/gpu/imx/dcss/dcss-dtg.c
drivers/gpu/imx/dcss/dcss-prv.h
include/video/imx-dcss.h

index 639cc91..cb15533 100644 (file)
@@ -271,27 +271,30 @@ static int dcss_clks_init(struct dcss_soc *dcss)
        struct {
                const char *id;
                struct clk **clk;
+               bool optional;
        } clks[] = {
-               {"apb",   &dcss->apb_clk},
-               {"axi",   &dcss->axi_clk},
-               {"pix_div", &dcss->pdiv_clk},
-               {"pix_out", &dcss->pout_clk},
-               {"rtrm",  &dcss->rtrm_clk},
-               {"dtrc",  &dcss->dtrc_clk},
-               {"pll_src",  &dcss->pll_src_clk},
-               {"pll_phy_ref",  &dcss->pll_phy_ref_clk},
+               {"apb",         &dcss->apb_clk,         false},
+               {"axi",         &dcss->axi_clk,         false},
+               {"pix",         &dcss->pix_clk,         false},
+               {"rtrm",        &dcss->rtrm_clk,        false},
+               {"dtrc",        &dcss->dtrc_clk,        false},
+               {"pll",         &dcss->pll_clk,         true},
+               {"pll_src1",    &dcss->src_clk[0],      true},
+               {"pll_src2",    &dcss->src_clk[1],      true},
+               {"pll_src3",    &dcss->src_clk[2],      true},
        };
 
        for (i = 0; i < ARRAY_SIZE(clks); i++) {
                *clks[i].clk = devm_clk_get(dcss->dev, clks[i].id);
-               if (IS_ERR(*clks[i].clk)) {
+               if (IS_ERR(*clks[i].clk) && !clks[i].optional) {
                        dev_err(dcss->dev, "failed to get %s clock\n",
                                clks[i].id);
                        ret = PTR_ERR(*clks[i].clk);
                        goto err;
                }
 
-               clk_prepare_enable(*clks[i].clk);
+               if (!clks[i].optional)
+                       clk_prepare_enable(*clks[i].clk);
        }
 
        dcss->clks_on = true;
@@ -308,25 +311,19 @@ err:
 static void dcss_clocks_enable(struct dcss_soc *dcss, bool en)
 {
        if (en && !dcss->clks_on) {
-               clk_prepare_enable(dcss->pll_phy_ref_clk);
-               clk_prepare_enable(dcss->pll_src_clk);
                clk_prepare_enable(dcss->axi_clk);
                clk_prepare_enable(dcss->apb_clk);
                clk_prepare_enable(dcss->rtrm_clk);
                clk_prepare_enable(dcss->dtrc_clk);
-               clk_prepare_enable(dcss->pdiv_clk);
-               clk_prepare_enable(dcss->pout_clk);
+               clk_prepare_enable(dcss->pix_clk);
        }
 
        if (!en && dcss->clks_on) {
-               clk_disable_unprepare(dcss->pout_clk);
-               clk_disable_unprepare(dcss->pdiv_clk);
+               clk_disable_unprepare(dcss->pix_clk);
                clk_disable_unprepare(dcss->dtrc_clk);
                clk_disable_unprepare(dcss->rtrm_clk);
                clk_disable_unprepare(dcss->apb_clk);
                clk_disable_unprepare(dcss->axi_clk);
-               clk_disable_unprepare(dcss->pll_src_clk);
-               clk_disable_unprepare(dcss->pll_phy_ref_clk);
        }
 
        dcss->clks_on = en;
index d32050b..52a4cb2 100644 (file)
 #define DCSS_DTG_DBY_BL                                        0x78
 #define DCSS_DTG_DBY_EL                                        0x7C
 
+/* Maximum Video PLL frequency */
+#define MAX_PLL_FREQ 1200000000
+/* Mininum pixel clock in kHz */
+#define MIN_PIX_CLK 74250
+
 static struct dcss_debug_reg dtg_debug_reg[] = {
        DCSS_DBG_REG(DCSS_DTG_TC_CONTROL_STATUS),
        DCSS_DBG_REG(DCSS_DTG_TC_DTG),
@@ -124,6 +129,14 @@ static struct dcss_debug_reg dtg_debug_reg[] = {
        DCSS_DBG_REG(DCSS_DTG_DBY_EL),
 };
 
+struct mode_config {
+       struct clk *clk_src;
+       unsigned long out_rate;
+       int clock;
+       int mode_clock;
+       struct list_head list;
+};
+
 struct dcss_dtg_priv {
        struct dcss_soc *dcss;
        void __iomem *base_reg;
@@ -143,6 +156,7 @@ struct dcss_dtg_priv {
 
        int ctxld_kick_irq;
        bool ctxld_kick_irq_en;
+       struct list_head valid_modes;
 
        /*
         * This will be passed on by DRM CRTC so that we can signal when DTG has
@@ -257,15 +271,195 @@ int dcss_dtg_init(struct dcss_soc *dcss, unsigned long dtg_base)
        dtg->control_status |= OVL_DATA_MODE | BLENDER_VIDEO_ALPHA_SEL |
                ((dtg->alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK);
 
+       INIT_LIST_HEAD(&dtg->valid_modes);
+
        return dcss_dtg_irq_config(dtg);
 }
 
 void dcss_dtg_exit(struct dcss_soc *dcss)
 {
        struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+       struct mode_config *config;
+       struct list_head *pos, *tmp;
 
        /* stop DTG */
        dcss_writel(DTG_START, dtg->base_reg + DCSS_DTG_TC_CONTROL_STATUS);
+
+       list_for_each_safe(pos, tmp, &dtg->valid_modes) {
+               config = list_entry(pos, struct mode_config, list);
+               list_del(pos);
+               devm_kfree(dcss->dev, config);
+       }
+}
+
+static struct clk *dcss_dtg_find_src_clk(struct dcss_soc *dcss, int crtc_clock,
+              unsigned long *out_rate)
+{
+       struct clk *src = NULL;
+       struct clk *p = dcss->pix_clk;
+       struct clk *src_clk[MAX_CLK_SRC];
+       int num_src_clk = ARRAY_SIZE(dcss->src_clk);
+       unsigned long src_rate;
+       int i;
+
+       for (i = 0; i < num_src_clk; i++)
+               src_clk[i] = dcss->src_clk[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;
+                               dcss->sel_clk = 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(dcss->pll_clk);
+               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;
+}
+
+int dcss_dtg_mode_valid(struct dcss_soc *dcss, int clock, int crtc_clock)
+{
+       struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+       struct clk *src = NULL;
+       unsigned long out_rate;
+       struct mode_config *config;
+
+       /*
+        * In order to verify possible clock sources we need to have at least
+        * two of them. Also, do not check the clock if the output is hdmi.
+        */
+       if (dtg->hdmi_output || !dcss->src_clk[0] || !dcss->src_clk[1])
+               return 0;
+
+       /*
+        * 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 1;
+
+       /* Transform clocks in Hz */
+       clock *= 1000;
+       crtc_clock *= 1000;
+
+       if (!crtc_clock)
+               crtc_clock = clock;
+
+       /* Skip saving the config again */
+       list_for_each_entry(config, &dtg->valid_modes, list)
+               if (config->clock == clock)
+                       return 0;
+
+       src = dcss_dtg_find_src_clk(dcss, crtc_clock, &out_rate);
+
+       if (IS_ERR_OR_NULL(src))
+               return 1;
+
+       clk_set_rate(dcss->pll_clk, out_rate);
+
+       /* Save this configuration for later use */
+       config = devm_kzalloc(dcss->dev,
+                sizeof(struct mode_config), GFP_KERNEL);
+       list_add(&config->list, &dtg->valid_modes);
+       config->clk_src = src;
+       config->out_rate = out_rate;
+       config->clock = clock;
+       config->mode_clock = crtc_clock;
+
+       return 0;
+}
+EXPORT_SYMBOL(dcss_dtg_mode_valid);
+
+int dcss_dtg_mode_fixup(struct dcss_soc *dcss, int clock)
+{
+       struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+       struct mode_config *config;
+       struct clk *src;
+
+       /* Make sure that current mode can get the required clock */
+       list_for_each_entry(config, &dtg->valid_modes, list)
+               if (config->clock == clock * 1000) {
+                       if (dcss->clks_on)
+                               clk_disable_unprepare(dcss->pix_clk);
+                       src = clk_get_parent(dcss->sel_clk);
+                       if (!clk_is_match(src, config->clk_src))
+                               clk_set_parent(dcss->sel_clk, config->clk_src);
+                       if (clk_get_rate(dcss->pll_clk) != config->out_rate)
+                               clk_set_rate(dcss->pll_clk, config->out_rate);
+                       dev_dbg(dcss->dev, "pll rate: %ld (actual %ld)\n",
+                               config->out_rate, clk_get_rate(dcss->pll_clk));
+                       if (dcss->clks_on)
+                               clk_prepare_enable(dcss->pix_clk);
+                       break;
+               }
+
+       return 0;
+
+}
+EXPORT_SYMBOL(dcss_dtg_mode_fixup);
+
+static void dcss_dtg_set_clock(struct dcss_soc *dcss, unsigned long clock)
+{
+       struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+       struct mode_config *config;
+
+       /*
+        * 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, &dtg->valid_modes, list) {
+               if (config->clock == clock) {
+                       struct clk *src;
+
+                       src = clk_get_parent(dcss->sel_clk);
+                       if (!clk_is_match(src, config->clk_src))
+                               clk_set_parent(dcss->sel_clk, config->clk_src);
+                       if (clk_get_rate(dcss->pll_clk) != config->out_rate)
+                               clk_set_rate(dcss->pll_clk, config->out_rate);
+                       dev_dbg(dcss->dev, "pll rate: %ld (actual %ld)\n",
+                               config->out_rate, clk_get_rate(dcss->pll_clk));
+                       clock = config->mode_clock;
+                       break;
+               }
+       }
+
+       clk_set_rate(dcss->pix_clk, clock);
 }
 
 void dcss_dtg_sync_set(struct dcss_soc *dcss, struct videomode *vm)
@@ -275,6 +469,7 @@ void dcss_dtg_sync_set(struct dcss_soc *dcss, struct videomode *vm)
        u16 dis_ulc_x, dis_ulc_y;
        u16 dis_lrc_x, dis_lrc_y;
        u32 sb_ctxld_trig, db_ctxld_trig;
+       u32 pixclock = vm->pixelclock;
        u32 actual_clk;
 
        dev_dbg(dcss->dev, "hfront_porch = %d\n", vm->hfront_porch);
@@ -297,24 +492,30 @@ void dcss_dtg_sync_set(struct dcss_soc *dcss, struct videomode *vm)
        dis_lrc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch +
                    vm->vactive - 1;
 
+       clk_disable_unprepare(dcss->pix_clk);
        if (dtg->hdmi_output) {
                int err;
-               clk_disable_unprepare(dcss->pout_clk);
-               clk_disable_unprepare(dcss->pll_src_clk);
-               err = clk_set_parent(dcss->pll_src_clk, dcss->pll_phy_ref_clk);
+               /*
+                * At this point, since pix_clk is disabled, the pll_clk
+                * should also be disabled, so re-parenting should be safe
+                */
+               err = clk_set_parent(dcss->pll_clk, dcss->src_clk[0]);
                if (err < 0)
                        dev_warn(dcss->dev, "clk_set_parent() returned %d",
                                 err);
-               clk_set_rate(dcss->pout_clk, vm->pixelclock);
-               clk_prepare_enable(dcss->pll_src_clk);
-               clk_prepare_enable(dcss->pout_clk);
+               clk_set_rate(dcss->pix_clk, vm->pixelclock);
        } else {
-               clk_disable_unprepare(dcss->pout_clk);
-               clk_disable_unprepare(dcss->pdiv_clk);
-               clk_set_rate(dcss->pdiv_clk, vm->pixelclock);
-               actual_clk = clk_get_rate(dcss->pdiv_clk);
-               clk_prepare_enable(dcss->pdiv_clk);
-               clk_prepare_enable(dcss->pout_clk);
+               dcss_dtg_set_clock(dcss, pixclock);
+       }
+       clk_prepare_enable(dcss->pix_clk);
+
+       actual_clk = clk_get_rate(dcss->pix_clk);
+       if (pixclock != actual_clk) {
+               dev_info(dcss->dev,
+                        "Pixel clock set to %u kHz instead of %u kHz, "
+                        "error is %d Hz\n",
+                        (actual_clk / 1000), (pixclock / 1000),
+                        (int)(actual_clk - pixclock));
        }
 
        msleep(50);
index efab4b2..db6fc66 100644 (file)
@@ -21,6 +21,8 @@
 #define CLR 0x08
 #define TGL 0x0C
 
+#define MAX_CLK_SRC 3
+
 #define dcss_writel(v, c)   writel((v), (c))
 #define dcss_readl(c)      readl(c)
 #define dcss_set(v, c)     writel((v), (c) + SET)
@@ -65,12 +67,12 @@ struct dcss_soc {
 
        struct clk *apb_clk;
        struct clk *axi_clk;
-       struct clk *pdiv_clk;
-       struct clk *pout_clk;
+       struct clk *pix_clk;
        struct clk *rtrm_clk;
        struct clk *dtrc_clk;
-       struct clk *pll_src_clk;
-       struct clk *pll_phy_ref_clk;
+       struct clk *sel_clk;
+       struct clk *pll_clk;
+       struct clk *src_clk[MAX_CLK_SRC];
 
        void (*dcss_disable_callback)(void *data);
 
index ef2f0a7..83a9796 100644 (file)
@@ -84,6 +84,8 @@ void dcss_dpr_tile_derive(struct dcss_soc *dcss,
 void dcss_dpr_set_rotation(struct dcss_soc *dcss, int ch_num, u32 rotation);
 
 /* DTG */
+int dcss_dtg_mode_valid(struct dcss_soc *dcss, int clock, int crtc_clock);
+int dcss_dtg_mode_fixup(struct dcss_soc *dcss, int clock);
 void dcss_dtg_sync_set(struct dcss_soc *dcss, struct videomode *vm);
 void dcss_dtg_plane_pos_set(struct dcss_soc *dcss, int ch_num,
                            int px, int py, int pw, int ph);