summaryrefslogtreecommitdiff
path: root/drivers/video/stm32
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/stm32')
-rw-r--r--drivers/video/stm32/Kconfig9
-rw-r--r--drivers/video/stm32/Makefile1
-rw-r--r--drivers/video/stm32/stm32_dsi.c490
-rw-r--r--drivers/video/stm32/stm32_ltdc.c143
4 files changed, 583 insertions, 60 deletions
diff --git a/drivers/video/stm32/Kconfig b/drivers/video/stm32/Kconfig
index 78b1facad4b..95d51bb4e96 100644
--- a/drivers/video/stm32/Kconfig
+++ b/drivers/video/stm32/Kconfig
@@ -13,6 +13,15 @@ menuconfig VIDEO_STM32
DSI. This option enables these supports which can be used on
devices which have RGB TFT or DSI display connected.
+config VIDEO_STM32_DSI
+ bool "Enable STM32 DSI video support"
+ depends on VIDEO_STM32
+ select VIDEO_BRIDGE
+ select VIDEO_DW_MIPI_DSI
+ help
+ This option enables support DSI internal bridge which can be used on
+ devices which have DSI devices connected.
+
config VIDEO_STM32_MAX_XRES
int "Maximum horizontal resolution (for memory allocation purposes)"
depends on VIDEO_STM32
diff --git a/drivers/video/stm32/Makefile b/drivers/video/stm32/Makefile
index 7297e5f57a6..f8b42d1a4d1 100644
--- a/drivers/video/stm32/Makefile
+++ b/drivers/video/stm32/Makefile
@@ -6,3 +6,4 @@
# Yannick Fertre <yannick.fertre@st.com>
obj-${CONFIG_VIDEO_STM32} = stm32_ltdc.o
+obj-${CONFIG_VIDEO_STM32_DSI} += stm32_dsi.o
diff --git a/drivers/video/stm32/stm32_dsi.c b/drivers/video/stm32/stm32_dsi.c
new file mode 100644
index 00000000000..cb89576e1d6
--- /dev/null
+++ b/drivers/video/stm32/stm32_dsi.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ * Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This MIPI DSI controller driver is based on the Linux Kernel driver from
+ * drivers/gpu/drm/stm/dw_mipi_dsi-stm.c.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dsi_host.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <reset.h>
+#include <video.h>
+#include <video_bridge.h>
+#include <asm/io.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include <linux/iopoll.h>
+#include <power/regulator.h>
+
+#define HWVER_130 0x31333000 /* IP version 1.30 */
+#define HWVER_131 0x31333100 /* IP version 1.31 */
+
+/* DSI digital registers & bit definitions */
+#define DSI_VERSION 0x00
+#define VERSION GENMASK(31, 8)
+
+/*
+ * DSI wrapper registers & bit definitions
+ * Note: registers are named as in the Reference Manual
+ */
+#define DSI_WCFGR 0x0400 /* Wrapper ConFiGuration Reg */
+#define WCFGR_DSIM BIT(0) /* DSI Mode */
+#define WCFGR_COLMUX GENMASK(3, 1) /* COLor MUltipleXing */
+
+#define DSI_WCR 0x0404 /* Wrapper Control Reg */
+#define WCR_DSIEN BIT(3) /* DSI ENable */
+
+#define DSI_WISR 0x040C /* Wrapper Interrupt and Status Reg */
+#define WISR_PLLLS BIT(8) /* PLL Lock Status */
+#define WISR_RRS BIT(12) /* Regulator Ready Status */
+
+#define DSI_WPCR0 0x0418 /* Wrapper Phy Conf Reg 0 */
+#define WPCR0_UIX4 GENMASK(5, 0) /* Unit Interval X 4 */
+#define WPCR0_TDDL BIT(16) /* Turn Disable Data Lanes */
+
+#define DSI_WRPCR 0x0430 /* Wrapper Regulator & Pll Ctrl Reg */
+#define WRPCR_PLLEN BIT(0) /* PLL ENable */
+#define WRPCR_NDIV GENMASK(8, 2) /* pll loop DIVision Factor */
+#define WRPCR_IDF GENMASK(14, 11) /* pll Input Division Factor */
+#define WRPCR_ODF GENMASK(17, 16) /* pll Output Division Factor */
+#define WRPCR_REGEN BIT(24) /* REGulator ENable */
+#define WRPCR_BGREN BIT(28) /* BandGap Reference ENable */
+#define IDF_MIN 1
+#define IDF_MAX 7
+#define NDIV_MIN 10
+#define NDIV_MAX 125
+#define ODF_MIN 1
+#define ODF_MAX 8
+
+/* dsi color format coding according to the datasheet */
+enum dsi_color {
+ DSI_RGB565_CONF1,
+ DSI_RGB565_CONF2,
+ DSI_RGB565_CONF3,
+ DSI_RGB666_CONF1,
+ DSI_RGB666_CONF2,
+ DSI_RGB888,
+};
+
+#define LANE_MIN_KBPS 31250
+#define LANE_MAX_KBPS 500000
+
+/* Timeout for regulator on/off, pll lock/unlock & fifo empty */
+#define TIMEOUT_US 200000
+
+struct stm32_dsi_priv {
+ struct mipi_dsi_device device;
+ void __iomem *base;
+ struct udevice *panel;
+ u32 pllref_clk;
+ u32 hw_version;
+ int lane_min_kbps;
+ int lane_max_kbps;
+ struct udevice *vdd_reg;
+ struct udevice *dsi_host;
+};
+
+static inline void dsi_write(struct stm32_dsi_priv *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct stm32_dsi_priv *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static inline void dsi_set(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
+}
+
+static inline void dsi_clear(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) & ~mask);
+}
+
+static inline void dsi_update_bits(struct stm32_dsi_priv *dsi, u32 reg,
+ u32 mask, u32 val)
+{
+ dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
+}
+
+static enum dsi_color dsi_color_from_mipi(u32 fmt)
+{
+ switch (fmt) {
+ case MIPI_DSI_FMT_RGB888:
+ return DSI_RGB888;
+ case MIPI_DSI_FMT_RGB666:
+ return DSI_RGB666_CONF2;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return DSI_RGB666_CONF1;
+ case MIPI_DSI_FMT_RGB565:
+ return DSI_RGB565_CONF1;
+ default:
+ pr_err("MIPI color invalid, so we use rgb888\n");
+ }
+ return DSI_RGB888;
+}
+
+static int dsi_pll_get_clkout_khz(int clkin_khz, int idf, int ndiv, int odf)
+{
+ int divisor = idf * odf;
+
+ /* prevent from division by 0 */
+ if (!divisor)
+ return 0;
+
+ return DIV_ROUND_CLOSEST(clkin_khz * ndiv, divisor);
+}
+
+static int dsi_pll_get_params(struct stm32_dsi_priv *dsi,
+ int clkin_khz, int clkout_khz,
+ int *idf, int *ndiv, int *odf)
+{
+ int i, o, n, n_min, n_max;
+ int fvco_min, fvco_max, delta, best_delta; /* all in khz */
+
+ /* Early checks preventing division by 0 & odd results */
+ if (clkin_khz <= 0 || clkout_khz <= 0)
+ return -EINVAL;
+
+ fvco_min = dsi->lane_min_kbps * 2 * ODF_MAX;
+ fvco_max = dsi->lane_max_kbps * 2 * ODF_MIN;
+
+ best_delta = 1000000; /* big started value (1000000khz) */
+
+ for (i = IDF_MIN; i <= IDF_MAX; i++) {
+ /* Compute ndiv range according to Fvco */
+ n_min = ((fvco_min * i) / (2 * clkin_khz)) + 1;
+ n_max = (fvco_max * i) / (2 * clkin_khz);
+
+ /* No need to continue idf loop if we reach ndiv max */
+ if (n_min >= NDIV_MAX)
+ break;
+
+ /* Clamp ndiv to valid values */
+ if (n_min < NDIV_MIN)
+ n_min = NDIV_MIN;
+ if (n_max > NDIV_MAX)
+ n_max = NDIV_MAX;
+
+ for (o = ODF_MIN; o <= ODF_MAX; o *= 2) {
+ n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
+ /* Check ndiv according to vco range */
+ if (n < n_min || n > n_max)
+ continue;
+ /* Check if new delta is better & saves parameters */
+ delta = dsi_pll_get_clkout_khz(clkin_khz, i, n, o) -
+ clkout_khz;
+ if (delta < 0)
+ delta = -delta;
+ if (delta < best_delta) {
+ *idf = i;
+ *ndiv = n;
+ *odf = o;
+ best_delta = delta;
+ }
+ /* fast return in case of "perfect result" */
+ if (!delta)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int dsi_phy_init(void *priv_data)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+ u32 val;
+ int ret;
+
+ debug("Initialize DSI physical layer\n");
+
+ /* Enable the regulator */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
+ ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_RRS,
+ TIMEOUT_US);
+ if (ret) {
+ debug("!TIMEOUT! waiting REGU\n");
+ return ret;
+ }
+
+ /* Enable the DSI PLL & wait for its lock */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_PLLEN);
+ ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_PLLLS,
+ TIMEOUT_US);
+ if (ret) {
+ debug("!TIMEOUT! waiting PLL\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dsi_phy_post_set_mode(void *priv_data, unsigned long mode_flags)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+
+ debug("Set mode %p enable %ld\n", dsi,
+ mode_flags & MIPI_DSI_MODE_VIDEO);
+
+ if (!dsi)
+ return;
+
+ /*
+ * DSI wrapper must be enabled in video mode & disabled in command mode.
+ * If wrapper is enabled in command mode, the display controller
+ * register access will hang.
+ */
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO)
+ dsi_set(dsi, DSI_WCR, WCR_DSIEN);
+ else
+ dsi_clear(dsi, DSI_WCR, WCR_DSIEN);
+}
+
+static int dsi_get_lane_mbps(void *priv_data, struct display_timing *timings,
+ u32 lanes, u32 format, unsigned int *lane_mbps)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+ int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ int ret, bpp;
+ u32 val;
+
+ /* Update lane capabilities according to hw version */
+ dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
+ dsi->lane_min_kbps = LANE_MIN_KBPS;
+ dsi->lane_max_kbps = LANE_MAX_KBPS;
+ if (dsi->hw_version == HWVER_131) {
+ dsi->lane_min_kbps *= 2;
+ dsi->lane_max_kbps *= 2;
+ }
+
+ pll_in_khz = dsi->pllref_clk / 1000;
+
+ /* Compute requested pll out */
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ pll_out_khz = (timings->pixelclock.typ / 1000) * bpp / lanes;
+ /* Add 20% to pll out to be higher than pixel bw (burst mode only) */
+ pll_out_khz = (pll_out_khz * 12) / 10;
+ if (pll_out_khz > dsi->lane_max_kbps) {
+ pll_out_khz = dsi->lane_max_kbps;
+ dev_warn(dev, "Warning max phy mbps is used\n");
+ }
+ if (pll_out_khz < dsi->lane_min_kbps) {
+ pll_out_khz = dsi->lane_min_kbps;
+ dev_warn(dev, "Warning min phy mbps is used\n");
+ }
+
+ /* Compute best pll parameters */
+ idf = 0;
+ ndiv = 0;
+ odf = 0;
+ ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz,
+ &idf, &ndiv, &odf);
+ if (ret) {
+ dev_err(dev, "Warning dsi_pll_get_params(): bad params\n");
+ return ret;
+ }
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ /* Set the PLL division factors */
+ dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
+ (ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
+
+ /* Compute uix4 & set the bit period in high-speed mode */
+ val = 4000000 / pll_out_khz;
+ dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
+
+ /* Select video mode by resetting DSIM bit */
+ dsi_clear(dsi, DSI_WCFGR, WCFGR_DSIM);
+
+ /* Select the color coding */
+ dsi_update_bits(dsi, DSI_WCFGR, WCFGR_COLMUX,
+ dsi_color_from_mipi(format) << 1);
+
+ *lane_mbps = pll_out_khz / 1000;
+
+ debug("pll_in %ukHz pll_out %ukHz lane_mbps %uMHz\n",
+ pll_in_khz, pll_out_khz, *lane_mbps);
+
+ return 0;
+}
+
+static const struct mipi_dsi_phy_ops dsi_stm_phy_ops = {
+ .init = dsi_phy_init,
+ .get_lane_mbps = dsi_get_lane_mbps,
+ .post_set_mode = dsi_phy_post_set_mode,
+};
+
+static int stm32_dsi_attach(struct udevice *dev)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct mipi_dsi_panel_plat *mplat;
+ struct display_timing timings;
+ int ret;
+
+ ret = uclass_first_device(UCLASS_PANEL, &priv->panel);
+ if (ret) {
+ dev_err(dev, "panel device error %d\n", ret);
+ return ret;
+ }
+
+ mplat = dev_get_platdata(priv->panel);
+ mplat->device = &priv->device;
+
+ ret = panel_get_display_timing(priv->panel, &timings);
+ if (ret) {
+ ret = fdtdec_decode_display_timing(gd->fdt_blob,
+ dev_of_offset(priv->panel),
+ 0, &timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = uclass_get_device(UCLASS_DSI_HOST, 0, &priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "No video dsi host detected %d\n", ret);
+ return ret;
+ }
+
+ ret = dsi_host_init(priv->dsi_host, device, &timings, 2,
+ &dsi_stm_phy_ops);
+ if (ret) {
+ dev_err(dev, "failed to initialize mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stm32_dsi_set_backlight(struct udevice *dev, int percent)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = panel_enable_backlight(priv->panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ priv->panel->name, ret);
+ return ret;
+ }
+
+ ret = dsi_host_enable(priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "failed to enable mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stm32_dsi_probe(struct udevice *dev)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct reset_ctl rst;
+ struct clk clk;
+ int ret;
+
+ device->dev = dev;
+
+ priv->base = (void *)dev_read_addr(dev);
+ if ((fdt_addr_t)priv->base == FDT_ADDR_T_NONE) {
+ dev_err(dev, "dsi dt register address error\n");
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
+ ret = device_get_supply_regulator(dev, "phy-dsi-supply",
+ &priv->vdd_reg);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "Warning: cannot get phy dsi supply\n");
+ return -ENODEV;
+ }
+
+ if (ret != -ENOENT) {
+ ret = regulator_set_enable(priv->vdd_reg, true);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = clk_get_by_name(device->dev, "pclk", &clk);
+ if (ret) {
+ dev_err(dev, "peripheral clock get error %d\n", ret);
+ goto err_reg;
+ }
+
+ ret = clk_enable(&clk);
+ if (ret) {
+ dev_err(dev, "peripheral clock enable error %d\n", ret);
+ goto err_reg;
+ }
+
+ ret = clk_get_by_name(dev, "ref", &clk);
+ if (ret) {
+ dev_err(dev, "pll reference clock get error %d\n", ret);
+ goto err_clk;
+ }
+
+ priv->pllref_clk = (unsigned int)clk_get_rate(&clk);
+
+ ret = reset_get_by_index(device->dev, 0, &rst);
+ if (ret) {
+ dev_err(dev, "missing dsi hardware reset\n");
+ goto err_clk;
+ }
+
+ /* Reset */
+ reset_deassert(&rst);
+
+ return 0;
+err_clk:
+ clk_disable(&clk);
+err_reg:
+ if (IS_ENABLED(CONFIG_DM_REGULATOR))
+ regulator_set_enable(priv->vdd_reg, false);
+
+ return ret;
+}
+
+struct video_bridge_ops stm32_dsi_ops = {
+ .attach = stm32_dsi_attach,
+ .set_backlight = stm32_dsi_set_backlight,
+};
+
+static const struct udevice_id stm32_dsi_ids[] = {
+ { .compatible = "st,stm32-dsi"},
+ { }
+};
+
+U_BOOT_DRIVER(stm32_dsi) = {
+ .name = "stm32-display-dsi",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = stm32_dsi_ids,
+ .bind = dm_scan_fdt_dev,
+ .probe = stm32_dsi_probe,
+ .ops = &stm32_dsi_ops,
+ .priv_auto_alloc_size = sizeof(struct stm32_dsi_priv),
+};
diff --git a/drivers/video/stm32/stm32_ltdc.c b/drivers/video/stm32/stm32_ltdc.c
index dc6c88902f6..59ff692b0b4 100644
--- a/drivers/video/stm32/stm32_ltdc.c
+++ b/drivers/video/stm32/stm32_ltdc.c
@@ -7,19 +7,18 @@
#include <common.h>
#include <clk.h>
+#include <display.h>
#include <dm.h>
#include <panel.h>
#include <reset.h>
#include <video.h>
+#include <video_bridge.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include <dm/device-internal.h>
-DECLARE_GLOBAL_DATA_PTR;
-
struct stm32_ltdc_priv {
void __iomem *regs;
- struct display_timing timing;
enum video_log2_bpp l2bpp;
u32 bg_col_argb;
u32 crop_x, crop_y, crop_w, crop_h;
@@ -174,8 +173,8 @@ static u32 stm32_ltdc_get_pixel_format(enum video_log2_bpp l2bpp)
case VIDEO_BPP2:
case VIDEO_BPP4:
default:
- debug("%s: warning %dbpp not supported yet, %dbpp instead\n",
- __func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
+ pr_warn("%s: warning %dbpp not supported yet, %dbpp instead\n",
+ __func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
pf = PF_RGB565;
break;
}
@@ -209,23 +208,23 @@ static void stm32_ltdc_enable(struct stm32_ltdc_priv *priv)
setbits_le32(priv->regs + LTDC_GCR, GCR_LTDCEN);
}
-static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
+static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv,
+ struct display_timing *timings)
{
void __iomem *regs = priv->regs;
- struct display_timing *timing = &priv->timing;
u32 hsync, vsync, acc_hbp, acc_vbp, acc_act_w, acc_act_h;
u32 total_w, total_h;
u32 val;
/* Convert video timings to ltdc timings */
- hsync = timing->hsync_len.typ - 1;
- vsync = timing->vsync_len.typ - 1;
- acc_hbp = hsync + timing->hback_porch.typ;
- acc_vbp = vsync + timing->vback_porch.typ;
- acc_act_w = acc_hbp + timing->hactive.typ;
- acc_act_h = acc_vbp + timing->vactive.typ;
- total_w = acc_act_w + timing->hfront_porch.typ;
- total_h = acc_act_h + timing->vfront_porch.typ;
+ hsync = timings->hsync_len.typ - 1;
+ vsync = timings->vsync_len.typ - 1;
+ acc_hbp = hsync + timings->hback_porch.typ;
+ acc_vbp = vsync + timings->vback_porch.typ;
+ acc_act_w = acc_hbp + timings->hactive.typ;
+ acc_act_h = acc_vbp + timings->vactive.typ;
+ total_w = acc_act_w + timings->hfront_porch.typ;
+ total_h = acc_act_h + timings->vfront_porch.typ;
/* Synchronization sizes */
val = (hsync << 16) | vsync;
@@ -247,14 +246,14 @@ static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
/* Signal polarities */
val = 0;
- debug("%s: timing->flags 0x%08x\n", __func__, timing->flags);
- if (timing->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ debug("%s: timing->flags 0x%08x\n", __func__, timings->flags);
+ if (timings->flags & DISPLAY_FLAGS_HSYNC_HIGH)
val |= GCR_HSPOL;
- if (timing->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ if (timings->flags & DISPLAY_FLAGS_VSYNC_HIGH)
val |= GCR_VSPOL;
- if (timing->flags & DISPLAY_FLAGS_DE_HIGH)
+ if (timings->flags & DISPLAY_FLAGS_DE_HIGH)
val |= GCR_DEPOL;
- if (timing->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
+ if (timings->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
val |= GCR_PCPOL;
clrsetbits_le32(regs + LTDC_GCR,
GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val);
@@ -330,96 +329,120 @@ static int stm32_ltdc_probe(struct udevice *dev)
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct stm32_ltdc_priv *priv = dev_get_priv(dev);
- struct udevice *panel;
+ struct udevice *bridge = NULL;
+ struct udevice *panel = NULL;
+ struct display_timing timings;
struct clk pclk;
struct reset_ctl rst;
- int rate, ret;
+ int ret;
priv->regs = (void *)dev_read_addr(dev);
if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) {
- debug("%s: ltdc dt register address error\n", __func__);
+ dev_err(dev, "ltdc dt register address error\n");
return -EINVAL;
}
ret = clk_get_by_index(dev, 0, &pclk);
if (ret) {
- debug("%s: peripheral clock get error %d\n", __func__, ret);
+ dev_err(dev, "peripheral clock get error %d\n", ret);
return ret;
}
ret = clk_enable(&pclk);
if (ret) {
- debug("%s: peripheral clock enable error %d\n",
- __func__, ret);
+ dev_err(dev, "peripheral clock enable error %d\n", ret);
return ret;
}
- ret = reset_get_by_index(dev, 0, &rst);
+ ret = uclass_first_device_err(UCLASS_PANEL, &panel);
if (ret) {
- debug("%s: missing ltdc hardware reset\n", __func__);
- return -ENODEV;
+ if (ret != -ENODEV)
+ dev_err(dev, "panel device error %d\n", ret);
+ return ret;
}
- /* Reset */
- reset_deassert(&rst);
-
- ret = uclass_first_device(UCLASS_PANEL, &panel);
+ ret = panel_get_display_timing(panel, &timings);
if (ret) {
- debug("%s: panel device error %d\n", __func__, ret);
- return ret;
+ ret = fdtdec_decode_display_timing(gd->fdt_blob,
+ dev_of_offset(panel),
+ 0, &timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
}
- ret = panel_enable_backlight(panel);
+ ret = clk_set_rate(&pclk, timings.pixelclock.typ);
+ if (ret)
+ dev_warn(dev, "fail to set pixel clock %d hz\n",
+ timings.pixelclock.typ);
+
+ debug("%s: Set pixel clock req %d hz get %ld hz\n", __func__,
+ timings.pixelclock.typ, clk_get_rate(&pclk));
+
+ ret = reset_get_by_index(dev, 0, &rst);
if (ret) {
- debug("%s: panel %s enable backlight error %d\n",
- __func__, panel->name, ret);
+ dev_err(dev, "missing ltdc hardware reset\n");
return ret;
}
- ret = fdtdec_decode_display_timing(gd->fdt_blob,
- dev_of_offset(dev), 0,
- &priv->timing);
- if (ret) {
- debug("%s: decode display timing error %d\n",
- __func__, ret);
- return -EINVAL;
- }
+ /* Reset */
+ reset_deassert(&rst);
- rate = clk_set_rate(&pclk, priv->timing.pixelclock.typ);
- if (rate < 0) {
- debug("%s: fail to set pixel clock %d hz %d hz\n",
- __func__, priv->timing.pixelclock.typ, rate);
- return rate;
+ if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
+ ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge);
+ if (ret)
+ debug("No video bridge, or no backlight on bridge\n");
+
+ if (bridge) {
+ ret = video_bridge_attach(bridge);
+ if (ret) {
+ dev_err(dev, "fail to attach bridge\n");
+ return ret;
+ }
+ }
}
- debug("%s: Set pixel clock req %d hz get %d hz\n", __func__,
- priv->timing.pixelclock.typ, rate);
-
/* TODO Below parameters are hard-coded for the moment... */
priv->l2bpp = VIDEO_BPP16;
priv->bg_col_argb = 0xFFFFFFFF; /* white no transparency */
priv->crop_x = 0;
priv->crop_y = 0;
- priv->crop_w = priv->timing.hactive.typ;
- priv->crop_h = priv->timing.vactive.typ;
+ priv->crop_w = timings.hactive.typ;
+ priv->crop_h = timings.vactive.typ;
priv->alpha = 0xFF;
debug("%s: %dx%d %dbpp frame buffer at 0x%lx\n", __func__,
- priv->timing.hactive.typ, priv->timing.vactive.typ,
+ timings.hactive.typ, timings.vactive.typ,
VNBITS(priv->l2bpp), uc_plat->base);
debug("%s: crop %d,%d %dx%d bg 0x%08x alpha %d\n", __func__,
priv->crop_x, priv->crop_y, priv->crop_w, priv->crop_h,
priv->bg_col_argb, priv->alpha);
/* Configure & start LTDC */
- stm32_ltdc_set_mode(priv);
+ stm32_ltdc_set_mode(priv, &timings);
stm32_ltdc_set_layer1(priv, uc_plat->base);
stm32_ltdc_enable(priv);
- uc_priv->xsize = priv->timing.hactive.typ;
- uc_priv->ysize = priv->timing.vactive.typ;
+ uc_priv->xsize = timings.hactive.typ;
+ uc_priv->ysize = timings.vactive.typ;
uc_priv->bpix = priv->l2bpp;
+ if (!bridge) {
+ ret = panel_enable_backlight(panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ panel->name, ret);
+ return ret;
+ }
+ } else if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
+ ret = video_bridge_set_backlight(bridge, 80);
+ if (ret) {
+ dev_err(dev, "fail to set backlight\n");
+ return ret;
+ }
+ }
+
video_set_flush_dcache(dev, true);
return 0;