summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFancy Fang <chen.fang@nxp.com>2019-03-17 12:16:26 +0800
committerFancy Fang <chen.fang@nxp.com>2019-03-20 18:26:48 +0800
commita73fdd5e48fe0df47685cfc197fe66edc1e28405 (patch)
tree89317fda672982e209c83f7171552551b2813779
parenta9fafe8108505f8a1580af898ff5fa9c26d03680 (diff)
MLK-21150-4 drm/bridge: sec-dsim: a general way to compute PLL PMS
A fixed PLL PMS setting for attached panel is obviously not enough for any other mipi panel which needs a different PLL output clock frequency, and besides, for the CEA-861 standard display modes, the 'pll_pms' table also can not cover all the modes requirements. So a general way is created to solve this problem which can provide an optimum solution to output a PLL bit clock to match the request frequency in a maximum degree and also satisfy the input clock and intermediate clocks limit according to the PLL specification. Signed-off-by: Fancy Fang <chen.fang@nxp.com>
-rw-r--r--drivers/gpu/drm/bridge/sec-dsim.c202
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_dsim-imx.c4
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_pll_1432x.h49
-rw-r--r--include/drm/bridge/sec_mipi_dsim.h20
4 files changed, 212 insertions, 63 deletions
diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c
index 08f721b8dee5..0fa4e09cab30 100644
--- a/drivers/gpu/drm/bridge/sec-dsim.c
+++ b/drivers/gpu/drm/bridge/sec-dsim.c
@@ -18,6 +18,8 @@
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/gcd.h>
+#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <drm/bridge/sec_mipi_dsim.h>
@@ -273,15 +275,6 @@
#define conn_to_sec_mipi_dsim(conn) \
container_of(conn, struct sec_mipi_dsim, connector)
-/* DSIM PLL configuration from spec:
- *
- * Fout(DDR) = (M * Fin) / (P * 2^S), so Fout / Fin = M / (P * 2^S)
- * Fin_pll = Fin / P (6 ~ 12 MHz)
- * S: [2:0], M: [12:3], P: [18:13], so
- * TODO: 'S' is in [0 ~ 3], 'M' is in, 'P' is in [1 ~ 33]
- *
- */
-
/* used for CEA standard modes */
struct dsim_hblank_par {
char *name; /* drm display mode name */
@@ -297,6 +290,7 @@ struct dsim_pll_pms {
uint32_t p;
uint32_t m;
uint32_t s;
+ uint32_t k;
};
struct sec_mipi_dsim {
@@ -388,19 +382,6 @@ static const struct dsim_hblank_par hblank_2lanes[] = {
{ DSIM_HBLANK_PARAM("640x480" , 60, 18, 66, 138, 2), },
};
-static const struct dsim_pll_pms pll_pms[] = {
- { DSIM_PLL_PMS(891000, 1, 66, 1), },
- { DSIM_PLL_PMS(890112, 1, 66, 1), },
- { DSIM_PLL_PMS(594000, 3, 66, 0), },
- { DSIM_PLL_PMS(593408, 3, 66, 0), },
- { DSIM_PLL_PMS(445500, 1, 66, 2), },
- { DSIM_PLL_PMS(445056, 1, 66, 2), },
- { DSIM_PLL_PMS(324000, 3, 72, 1), },
- { DSIM_PLL_PMS(324324, 3, 72, 1), },
- { DSIM_PLL_PMS(162000, 3, 72, 2), },
- { DSIM_PLL_PMS(162162, 3, 72, 2), },
-};
-
static const struct dsim_hblank_par *sec_mipi_dsim_get_hblank_par(const char *name,
int vrefresh,
int lanes)
@@ -446,13 +427,16 @@ static int sec_mipi_dsim_set_pref_rate(struct sec_mipi_dsim *dsim)
int ret;
uint32_t rate;
struct device *dev = dsim->dev;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ const struct sec_mipi_dsim_pll *dpll = pdata->dphy_pll;
+ const struct sec_mipi_dsim_range *fin_range = &dpll->fin;
ret = of_property_read_u32(dev->of_node, "pref-rate", &rate);
if (ret < 0) {
dev_dbg(dev, "no valid rate assigned for pref clock\n");
dsim->pref_clk = PHY_REF_CLK;
} else {
- if (unlikely(rate < 6000 || rate > 300000)) {
+ if (unlikely(rate < fin_range->min || rate > fin_range->max)) {
dev_warn(dev, "pref-rate get is invalid: %uKHz\n",
rate);
dsim->pref_clk = PHY_REF_CLK;
@@ -493,21 +477,6 @@ set_rate:
return 0;
}
-static const struct dsim_pll_pms *sec_mipi_dsim_get_pms(uint32_t bit_clk)
-{
- int i;
- const struct dsim_pll_pms *pms;
-
- for (i = 0; i < ARRAY_SIZE(pll_pms); i++) {
- pms = &pll_pms[i];
-
- if (bit_clk == pms->bit_clk)
- return pms;
- }
-
- return NULL;
-}
-
static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim);
/* For now, dsim only support one device attached */
@@ -936,7 +905,7 @@ static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim)
bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
/* calculate hfp & hbp word counts */
- if (dsim->panel || !dsim->hpar) {
+ if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3),
dsim->lanes);
hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ?
@@ -956,7 +925,7 @@ static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim)
dsim_write(dsim, mhporch, DSIM_MHPORCH);
/* calculate hsa word counts */
- if (dsim->panel || !dsim->hpar) {
+ if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3),
dsim->lanes);
hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ?
@@ -1170,15 +1139,131 @@ static void sec_mipi_dsim_set_standby(struct sec_mipi_dsim *dsim,
dsim_write(dsim, mdresol, DSIM_MDRESOL);
}
+struct dsim_pll_pms *sec_mipi_dsim_calc_pmsk(struct sec_mipi_dsim *dsim)
+{
+ uint32_t p, m, s;
+ uint32_t best_p, best_m, best_s;
+ uint32_t fin, fout;
+ uint32_t s_pow_2, raw_s;
+ uint64_t mfin, pfvco, pfout, psfout;
+ uint32_t delta, best_delta = ~0U;
+ struct dsim_pll_pms *pll_pms;
+ struct device *dev = dsim->dev;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ struct sec_mipi_dsim_pll dpll = *pdata->dphy_pll;
+ struct sec_mipi_dsim_range *prange = &dpll.p;
+ struct sec_mipi_dsim_range *mrange = &dpll.m;
+ struct sec_mipi_dsim_range *srange = &dpll.s;
+ struct sec_mipi_dsim_range *krange = &dpll.k;
+ struct sec_mipi_dsim_range *fvco_range = &dpll.fvco;
+ struct sec_mipi_dsim_range *fpref_range = &dpll.fpref;
+ struct sec_mipi_dsim_range pr_new = *prange;
+ struct sec_mipi_dsim_range sr_new = *srange;
+
+ pll_pms = devm_kzalloc(dev, sizeof(*pll_pms), GFP_KERNEL);
+ if (!pll_pms) {
+ dev_err(dev, "Unable to allocate 'pll_pms'\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fout = dsim->bit_clk;
+ fin = dsim->pref_clk;
+
+ /* TODO: ignore 'k' for PMS calculation,
+ * only use 'p', 'm' and 's' to generate
+ * the requested PLL output clock.
+ */
+ krange->min = 0;
+ krange->max = 0;
+
+ /* narrow 'p' range via 'Fpref' limitation:
+ * Fpref : [2MHz ~ 30MHz] (Fpref = Fin / p)
+ */
+ prange->min = max(prange->min, DIV_ROUND_UP(fin, fpref_range->max));
+ prange->max = min(prange->max, fin / fpref_range->min);
+
+ /* narrow 'm' range via 'Fvco' limitation:
+ * Fvco: [1050MHz ~ 2100MHz] (Fvco = ((m + k / 65536) * Fin) / p)
+ * So, m = Fvco * p / Fin and Fvco > Fin;
+ */
+ pfvco = fvco_range->min * prange->min;
+ mrange->min = max_t(uint32_t, mrange->min,
+ DIV_ROUND_UP_ULL(pfvco, fin));
+ pfvco = fvco_range->max * prange->max;
+ mrange->max = min_t(uint32_t, mrange->max,
+ DIV_ROUND_UP_ULL(pfvco, fin));
+
+ dev_dbg(dev, "p: min = %u, max = %u, "
+ "m: min = %u, max = %u, "
+ "s: min = %u, max = %u\n",
+ prange->min, prange->max, mrange->min,
+ mrange->max, srange->min, srange->max);
+
+ /* first determine 'm', then can determine 'p', last determine 's' */
+ for (m = mrange->min; m <= mrange->max; m++) {
+ /* p = m * Fin / Fvco */
+ mfin = m * fin;
+ pr_new.min = max_t(uint32_t, prange->min,
+ DIV_ROUND_UP_ULL(mfin, fvco_range->max));
+ pr_new.max = min_t(uint32_t, prange->max,
+ (mfin / fvco_range->min));
+
+ if (pr_new.max < pr_new.min || pr_new.min < prange->min)
+ continue;
+
+ for (p = pr_new.min; p <= pr_new.max; p++) {
+ /* s = order_pow_of_two((m * Fin) / (p * Fout)) */
+ pfout = p * fout;
+ raw_s = DIV_ROUND_CLOSEST_ULL(mfin, pfout);
+
+ s_pow_2 = rounddown_pow_of_two(raw_s);
+ sr_new.min = max_t(uint32_t, srange->min,
+ order_base_2(s_pow_2));
+
+ s_pow_2 = roundup_pow_of_two(DIV_ROUND_CLOSEST_ULL(mfin, pfout));
+ sr_new.max = min_t(uint32_t, srange->max,
+ order_base_2(s_pow_2));
+
+ if (sr_new.max < sr_new.min || sr_new.min < srange->min)
+ continue;
+
+ for (s = sr_new.min; s <= sr_new.max; s++) {
+ /* fout = m * Fin / (p * 2^s) */
+ psfout = pfout * (1 << s);
+ delta = abs(psfout - mfin);
+ if (delta < best_delta) {
+ best_p = p;
+ best_m = m;
+ best_s = s;
+ best_delta = delta;
+ }
+ }
+ }
+ }
+
+ if (best_delta == ~0U)
+ return ERR_PTR(-EINVAL);
+
+ pll_pms->p = best_p;
+ pll_pms->m = best_m;
+ pll_pms->s = best_s;
+
+ dev_dbg(dev, "fout = %u, fin = %u, m = %u, "
+ "p = %u, s = %u, best_delta = %u\n",
+ fout, fin, pll_pms->m, pll_pms->p, pll_pms->s, best_delta);
+
+ return pll_pms;
+}
+
int sec_mipi_dsim_check_pll_out(void *driver_private,
const struct drm_display_mode *mode)
{
int bpp;
- uint32_t pix_clk, bit_clk, ref_clk;
+ uint32_t pix_clk, bit_clk;
struct sec_mipi_dsim *dsim = driver_private;
const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
const struct dsim_hblank_par *hpar;
- const struct dsim_pll_pms *pms;
+ const struct dsim_pll_pms *pmsk;
bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
if (bpp < 0)
@@ -1195,32 +1280,27 @@ int sec_mipi_dsim_check_pll_out(void *driver_private,
dsim->pix_clk = pix_clk;
dsim->bit_clk = bit_clk;
-
- dsim->pms = 0x4210;
dsim->hpar = NULL;
- if (dsim->panel)
- return 0;
+
+ pmsk = sec_mipi_dsim_calc_pmsk(dsim);
+ if (IS_ERR(pmsk)) {
+ dev_err(dsim->dev,
+ "failed to get pmsk for: fin = %u, fout = %u\n",
+ dsim->pref_clk, dsim->bit_clk);
+ return -EINVAL;
+ }
+
+ dsim->pms = PLLCTRL_SET_P(pmsk->p) |
+ PLLCTRL_SET_M(pmsk->m) |
+ PLLCTRL_SET_S(pmsk->s);
if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
hpar = sec_mipi_dsim_get_hblank_par(mode->name,
mode->vrefresh,
dsim->lanes);
- if (!hpar)
- return -EINVAL;
dsim->hpar = hpar;
-
- pms = sec_mipi_dsim_get_pms(dsim->bit_clk);
- if (WARN_ON(!pms))
- return -EINVAL;
-
- ref_clk = PHY_REF_CLK;
- /* TODO: add PMS calculate and check
- * Only support '1080p@60Hz' for now,
- * add other modes support later
- */
- dsim->pms = PLLCTRL_SET_P(pms->p) |
- PLLCTRL_SET_M(pms->m) |
- PLLCTRL_SET_S(pms->s);
+ if (!hpar)
+ dev_dbg(dsim->dev, "no pre-exist hpar can be used\n");
}
return 0;
diff --git a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
index d267039cfa88..427fb1aa39dc 100644
--- a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
+++ b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
@@ -1,7 +1,7 @@
/*
* Samsung MIPI DSI Host Controller on IMX
*
- * Copyright 2018 NXP
+ * Copyright 2018-2019 NXP
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@
#include "imx-drm.h"
#include "sec_mipi_dphy_ln14lpp.h"
+#include "sec_mipi_pll_1432x.h"
#define DRIVER_NAME "imx_sec_dsim_drv"
@@ -197,6 +198,7 @@ static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = {
.version = 0x1060200,
.max_data_lanes = 4,
.max_data_rate = 1500000000ULL,
+ .dphy_pll = &pll_1432x,
.dphy_timing = dphy_timing_ln14lpp_v1p2,
.num_dphy_timing = ARRAY_SIZE(dphy_timing_ln14lpp_v1p2),
.dphy_timing_cmp = dphy_timing_default_cmp,
diff --git a/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h
new file mode 100644
index 000000000000..c387b2facd59
--- /dev/null
+++ b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h
@@ -0,0 +1,49 @@
+/*
+ * Samsung MIPI DSIM PLL_1432X
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SEC_DSIM_PLL_1432X_H__
+#define __SEC_DSIM_PLL_1432X_H__
+
+#include <drm/bridge/sec_mipi_dsim.h>
+/*
+ * DSIM PLL_1432X setting guide from spec:
+ *
+ * Fout(bitclk) = ((m + k / 65536) * Fin) / (p * 2^s), and
+ * p = P[5:0], m = M[9:0], s = S[2:0], k = K[15:0];
+ *
+ * Fpref = Fin / p
+ * Fin: [6MHz ~ 300MHz], Fpref: [2MHz ~ 30MHz]
+ *
+ * Fvco = ((m + k / 65536) * Fin) / p
+ * Fvco: [1050MHz ~ 2100MHz]
+ *
+ * 1 <= P[5:0] <= 63, 64 <= M[9:0] <= 1023,
+ * 0 <= S[2:0] <= 5, -32768 <= K[15:0] <= 32767
+ *
+ */
+
+const struct sec_mipi_dsim_pll pll_1432x = {
+ .p = { .min = 1, .max = 63, },
+ .m = { .min = 64, .max = 1023, },
+ .s = { .min = 0, .max = 5, },
+ .k = { .min = 0, .max = 32768, }, /* abs(k) */
+ .fin = { .min = 6000, .max = 300000, }, /* in KHz */
+ .fpref = { .min = 2000, .max = 30000, }, /* in KHz */
+ .fvco = { .min = 1050000, .max = 2100000, }, /* in KHz */
+};
+
+#endif
+
diff --git a/include/drm/bridge/sec_mipi_dsim.h b/include/drm/bridge/sec_mipi_dsim.h
index 2b125b26b675..2f9233563ee6 100644
--- a/include/drm/bridge/sec_mipi_dsim.h
+++ b/include/drm/bridge/sec_mipi_dsim.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 NXP
+ * Copyright 2018-2019 NXP
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
#include <linux/bsearch.h>
struct sec_mipi_dsim_dphy_timing;
+struct sec_mipi_dsim_pll;
struct sec_mipi_dsim_plat_data {
uint32_t version;
@@ -26,11 +27,28 @@ struct sec_mipi_dsim_plat_data {
uint64_t max_data_rate;
const struct sec_mipi_dsim_dphy_timing *dphy_timing;
uint32_t num_dphy_timing;
+ const struct sec_mipi_dsim_pll *dphy_pll;
int (*dphy_timing_cmp)(const void *key, const void *elt);
enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
struct drm_display_mode *mode);
};
+/* DPHY PLL structure */
+struct sec_mipi_dsim_range {
+ uint32_t min;
+ uint32_t max;
+};
+
+struct sec_mipi_dsim_pll {
+ struct sec_mipi_dsim_range p;
+ struct sec_mipi_dsim_range m;
+ struct sec_mipi_dsim_range s;
+ struct sec_mipi_dsim_range k;
+ struct sec_mipi_dsim_range fin;
+ struct sec_mipi_dsim_range fpref;
+ struct sec_mipi_dsim_range fvco;
+};
+
/* DPHY timings structure */
struct sec_mipi_dsim_dphy_timing {
uint32_t bit_clk; /* MHz */