video: mxc: ldb: add support for NL12880BC20 and VGA timings (used on Ka-Ro MB7 baseb...
[karo-tx-linux.git] / drivers / video / mxc / ldb.c
index f194995..48381d3 100644 (file)
 #include <linux/spinlock.h>
 #include <linux/of_device.h>
 #include <linux/mod_devicetable.h>
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
 #include "mxc_dispdrv.h"
 
 #define DISPDRV_LDB    "ldb"
@@ -136,29 +140,50 @@ static int g_ldb_mode;
 
 static struct fb_videomode ldb_modedb[] = {
        {
-        "LDB-WXGA", 60, 1280, 800, 14065,
-        40, 40,
-        10, 3,
-        80, 10,
-        0,
-        FB_VMODE_NONINTERLACED,
-        FB_MODE_IS_DETAILED,},
+               "LDB-WXGA", 60, 1280, 800, 14065,
+               40, 40,
+               10, 3,
+               80, 10,
+               0,
+               FB_VMODE_NONINTERLACED,
+               FB_MODE_IS_DETAILED,
+       },
+       {
+               "LDB-XGA", 60, 1024, 768, 15385,
+               220, 40,
+               21, 7,
+               60, 10,
+               0,
+               FB_VMODE_NONINTERLACED,
+               FB_MODE_IS_DETAILED,
+       },
        {
-        "LDB-XGA", 60, 1024, 768, 15385,
-        220, 40,
-        21, 7,
-        60, 10,
-        0,
-        FB_VMODE_NONINTERLACED,
-        FB_MODE_IS_DETAILED,},
+               "LDB-1080P60", 60, 1920, 1080, 7692,
+               100, 40,
+               30, 3,
+               10, 2,
+               0,
+               FB_VMODE_NONINTERLACED,
+               FB_MODE_IS_DETAILED,
+       },
+       {
+               "LDB-NL12880BC20", 60, 1280, 800, KHZ2PICOS(71000),
+               50, 50,
+               5, 5,
+               60, 13,
+               0,
+               FB_VMODE_NONINTERLACED,
+               FB_MODE_IS_DETAILED,
+       },
        {
-        "LDB-1080P60", 60, 1920, 1080, 7692,
-        100, 40,
-        30, 3,
-        10, 2,
-        0,
-        FB_VMODE_NONINTERLACED,
-        FB_MODE_IS_DETAILED,},
+               "LDB-VGA", 60, 640, 480, KHZ2PICOS(25200),
+               48, 16,
+               31, 12,
+               96, 2,
+               0,
+               FB_VMODE_NONINTERLACED,
+               FB_MODE_IS_DETAILED,
+       },
 };
 static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb);
 
@@ -227,13 +252,13 @@ static int parse_ldb_mode(char *mode)
  *    "ldb=sin0/1"       --      single mode on LVDS0/1
  *    "ldb=sep0/1"      --      separate mode begin from LVDS0/1
  *
- *    there are two LVDS channels(LVDS0 and LVDS1) which can transfer video
- *    datas, there two channels can be used as split/dual/single/separate mode.
+ *    There are two LVDS channels (LVDS0 and LVDS1) which can transfer video
+ *    data. These two channels can be used as split/dual/single/separate mode.
  *
- *    split mode means display data from DI0 or DI1 will send to both channels
+ *    split mode means display data from DI0 or DI1 will be sent to both channels
  *    LVDS0+LVDS1.
  *    dual mode means display data from DI0 or DI1 will be duplicated on LVDS0
- *    and LVDS1, it said, LVDS0 and LVDS1 has the same content.
+ *    and LVDS1, i.e. LVDS0 and LVDS1 has the same content.
  *    single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1.
  *    separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work
  *    at the same time.
@@ -306,11 +331,9 @@ static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi)
        char id[16];
        int i;
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < ARRAY_SIZE(id_di); i++) {
                if (ldb->setting[i].active) {
-                       memset(id, 0, 16);
-                       memcpy(id, id_di[ldb->setting[i].di],
-                               strlen(id_di[ldb->setting[i].di]));
+                       strlcpy(id, id_di[ldb->setting[i].di], sizeof(id));
                        id[4] += ldb->setting[i].ipu;
                        if (!strcmp(id, fbi->fix.id))
                                return i;
@@ -483,7 +506,7 @@ static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb)
                        (ROUTE_IPU_DI(ipu, di) << LVDS1_MUX_CTL_OFFS);
                dev_dbg(&ldb->pdev->dev,
                        "Dual/Split mode both channels route to IPU%d-DI%d\n",
-                       ipu, di);
+                       ipu + 1, di);
        } else if ((mode == LDB_SIN0) || (mode == LDB_SIN1)) {
                reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK);
                channel = mode - LDB_SIN0;
@@ -491,7 +514,7 @@ static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb)
                reg |= ROUTE_IPU_DI(ipu, di) << shift;
                dev_dbg(&ldb->pdev->dev,
                        "Single mode channel %d route to IPU%d-DI%d\n",
-                               channel, ipu, di);
+                               channel, ipu + 1, di);
        } else {
                static bool first = true;
 
@@ -519,7 +542,7 @@ static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb)
 
                dev_dbg(&ldb->pdev->dev,
                        "Separate mode channel %d route to IPU%d-DI%d\n",
-                       channel, ipu, di);
+                       channel, ipu + 1, di);
        }
        writel(reg, ldb->gpr3_reg);
 
@@ -544,8 +567,7 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
 
        /* if input format not valid, make RGB666 as default*/
        if (!valid_mode(setting->if_fmt)) {
-               dev_warn(&ldb->pdev->dev, "Input pixel format not valid"
-                                       " use default RGB666\n");
+               dev_warn(&ldb->pdev->dev, "Input pixel format not valid use default RGB666\n");
                setting->if_fmt = IPU_PIX_FMT_RGB666;
        }
 
@@ -567,7 +589,7 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
 
                reg = readl(ldb->control_reg);
 
-               /* refrence resistor select */
+               /* reference resistor select */
                reg &= ~LDB_BGREF_RMODE_MASK;
                if (plat_data->ext_ref)
                        reg |= LDB_BGREF_RMODE_EXT;
@@ -596,26 +618,23 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
                        ret = ldb->mode - LDB_SIN0;
                        if (plat_data->disp_id != ret) {
                                dev_warn(&ldb->pdev->dev,
-                                       "change IPU DI%d to IPU DI%d for LDB "
-                                       "channel%d.\n",
+                                       "change IPU DI%d to IPU DI%d for LDB channel%d.\n",
                                        plat_data->disp_id, ret, ret);
                                plat_data->disp_id = ret;
                        }
                } else if (((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1))
                                && is_imx6_ldb(plat_data)) {
-                       if (plat_data->disp_id == plat_data->sec_disp_id) {
+                       if (plat_data->ipu_id == plat_data->sec_ipu_id &&
+                               plat_data->disp_id == plat_data->sec_disp_id) {
                                dev_err(&ldb->pdev->dev,
-                                       "For LVDS separate mode,"
-                                       "two DIs should be different!\n");
+                                       "For LVDS separate mode, two DIs should be different!\n");
                                return -EINVAL;
                        }
 
-                       if (((!plat_data->disp_id) && (ldb->mode == LDB_SEP1))
-                               || ((plat_data->disp_id) &&
-                                       (ldb->mode == LDB_SEP0))) {
+                       if ((!plat_data->disp_id && ldb->mode == LDB_SEP1) ||
+                               (plat_data->disp_id && ldb->mode == LDB_SEP0)) {
                                dev_dbg(&ldb->pdev->dev,
-                                       "LVDS separate mode:"
-                                       "swap DI configuration!\n");
+                                       "LVDS separate mode: swap DI configuration!\n");
                                ipu_id = plat_data->ipu_id;
                                disp_id = plat_data->disp_id;
                                plat_data->ipu_id = plat_data->sec_ipu_id;
@@ -659,7 +678,7 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
                                reg |= LDB_CH1_MODE_EN_TO_DI1;
                        ch_mask = LDB_CH1_MODE_MASK;
                        ch_val = reg & LDB_CH1_MODE_MASK;
-               } else { /* separate mode*/
+               } else { /* separate mode */
                        setting->disp_id = plat_data->disp_id;
 
                        /* first output is LVDS0 or LVDS1 */
@@ -707,8 +726,7 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
                        (ldb->mode == LDB_DUL_DI1) ||
                        (ldb->mode == LDB_SIN0) ||
                        (ldb->mode == LDB_SIN1)) {
-                       dev_err(&ldb->pdev->dev, "for second ldb disp"
-                                       "ldb mode should in separate mode\n");
+                       dev_err(&ldb->pdev->dev, "for second ldb disp ldb mode should be in separate mode\n");
                        return -EINVAL;
                }
 
@@ -720,9 +738,9 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
                        setting->dev_id = plat_data->ipu_id;
                        setting->disp_id = !plat_data->disp_id;
                }
-               if (setting->disp_id == ldb->setting[0].di) {
-                       dev_err(&ldb->pdev->dev, "Err: for second ldb disp in"
-                               "separate mode, DI should be different!\n");
+               if (setting->dev_id == ldb->setting[0].ipu &&
+                       setting->disp_id == ldb->setting[0].di) {
+                       dev_err(&ldb->pdev->dev, "Err: for second ldb disp in separate mode, DI should be different!\n");
                        return -EINVAL;
                }
 
@@ -775,27 +793,31 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
        ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev,
                                                        ldb_clk);
        if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) {
-               dev_err(&ldb->pdev->dev, "get ldb clk failed\n");
+               dev_err(&ldb->pdev->dev, "Failed to get %s clk: %ld\n",
+                       ldb_clk, PTR_ERR(ldb->setting[setting_idx].ldb_di_clk));
                return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk);
        }
 
        ldb->setting[setting_idx].div_3_5_clk = clk_get(&ldb->pdev->dev,
                                                        div_3_5_clk);
        if (IS_ERR(ldb->setting[setting_idx].div_3_5_clk)) {
-               dev_err(&ldb->pdev->dev, "get div 3.5 clk failed\n");
+               dev_err(&ldb->pdev->dev, "Failed to get %s clk: %ld\n",
+                       div_3_5_clk, PTR_ERR(ldb->setting[setting_idx].div_3_5_clk));
                return PTR_ERR(ldb->setting[setting_idx].div_3_5_clk);
        }
        ldb->setting[setting_idx].div_7_clk = clk_get(&ldb->pdev->dev,
                                                        div_7_clk);
        if (IS_ERR(ldb->setting[setting_idx].div_7_clk)) {
-               dev_err(&ldb->pdev->dev, "get div 7 clk failed\n");
+               dev_err(&ldb->pdev->dev, "Failed to get %s clk: %ld\n",
+                       div_7_clk, PTR_ERR(ldb->setting[setting_idx].div_7_clk));
                return PTR_ERR(ldb->setting[setting_idx].div_7_clk);
        }
 
        ldb->setting[setting_idx].div_sel_clk = clk_get(&ldb->pdev->dev,
                                                        div_sel_clk);
        if (IS_ERR(ldb->setting[setting_idx].div_sel_clk)) {
-               dev_err(&ldb->pdev->dev, "get div sel clk failed\n");
+               dev_err(&ldb->pdev->dev, "Failed to get %s clk: %ld\n",
+                       div_sel_clk, PTR_ERR(ldb->setting[setting_idx].div_sel_clk));
                return PTR_ERR(ldb->setting[setting_idx].div_sel_clk);
        }
 
@@ -804,7 +826,8 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
        ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev,
                                                        di_clk);
        if (IS_ERR(ldb->setting[setting_idx].di_clk)) {
-               dev_err(&ldb->pdev->dev, "get di clk failed\n");
+               dev_err(&ldb->pdev->dev, "Failed to get %s clk: %ld\n",
+                       di_clk, PTR_ERR(ldb->setting[setting_idx].di_clk));
                return PTR_ERR(ldb->setting[setting_idx].di_clk);
        }
 
@@ -814,6 +837,11 @@ static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
        if (is_imx6_ldb(plat_data))
                ldb_ipu_ldb_route(setting->dev_id, setting->disp_id, ldb);
 
+       if (setting->fbmode) {
+               ldb_modedb[0] = *setting->fbmode;
+               ldb_modedb_sz = 1;
+       }
+
        /* must use spec video mode defined by driver */
        ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
                                ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp);
@@ -852,8 +880,7 @@ static int ldb_post_disp_init(struct mxc_dispdrv_handle *disp,
        ret = clk_set_parent(ldb->setting[setting_idx].di_clk,
                        ldb->setting[setting_idx].ldb_di_clk);
        if (ret) {
-               dev_err(&ldb->pdev->dev, "fail to set ldb_di clk as"
-                       "the parent of ipu_di clk\n");
+               dev_err(&ldb->pdev->dev, "fail to set ldb_di clk as the parent of ipu_di clk\n");
                return ret;
        }
 
@@ -861,16 +888,14 @@ static int ldb_post_disp_init(struct mxc_dispdrv_handle *disp,
                ret = clk_set_parent(ldb->setting[setting_idx].div_sel_clk,
                                ldb->setting[setting_idx].div_3_5_clk);
                if (ret) {
-                       dev_err(&ldb->pdev->dev, "fail to set div 3.5 clk as"
-                               "the parent of div sel clk\n");
+                       dev_err(&ldb->pdev->dev, "fail to set div 3.5 clk as the parent of div sel clk\n");
                        return ret;
                }
        } else {
                ret = clk_set_parent(ldb->setting[setting_idx].div_sel_clk,
                                ldb->setting[setting_idx].div_7_clk);
                if (ret) {
-                       dev_err(&ldb->pdev->dev, "fail to set div 7 clk as"
-                               "the parent of div sel clk\n");
+                       dev_err(&ldb->pdev->dev, "fail to set div 7 clk as the parent of div sel clk\n");
                        return ret;
                }
        }
@@ -901,8 +926,8 @@ static void ldb_disp_deinit(struct mxc_dispdrv_handle *disp)
 }
 
 static struct mxc_dispdrv_driver ldb_drv = {
-       .name   = DISPDRV_LDB,
-       .init   = ldb_disp_init,
+       .name   = DISPDRV_LDB,
+       .init   = ldb_disp_init,
        .post_init = ldb_post_disp_init,
        .deinit = ldb_disp_deinit,
        .setup = ldb_disp_setup,
@@ -935,12 +960,8 @@ static int ldb_resume(struct platform_device *pdev)
 }
 
 static struct platform_device_id imx_ldb_devtype[] = {
-       {
-               .name = "ldb-imx6",
-               .driver_data = LDB_IMX6,
-       }, {
-               /* sentinel */
-       }
+       { "ldb-imx6", LDB_IMX6, },
+       { /* sentinel */ }
 };
 
 static const struct of_device_id imx_ldb_dt_ids[] = {