From 02bb9976e1d4412f122f2c5dfebe689f8160805d Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 8 Jan 2024 18:45:00 +0200 Subject: video: simple_panel: simplify platform data pass Pass MIPI DSI platform data to simple DSI panel directly from driver data on panel probe. Signed-off-by: Svyatoslav Ryhel --- drivers/video/simple_panel.c | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c index efb122b534a..064c113d2d0 100644 --- a/drivers/video/simple_panel.c +++ b/drivers/video/simple_panel.c @@ -19,19 +19,6 @@ struct simple_panel_priv { struct gpio_desc enable; }; -/* List of supported DSI panels */ -enum { - PANEL_NON_DSI, - PANASONIC_VVX10F004B00, -}; - -static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = { - .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | - MIPI_DSI_CLOCK_NON_CONTINUOUS, - .format = MIPI_DSI_FMT_RGB888, - .lanes = 4, -}; - static int simple_panel_enable_backlight(struct udevice *dev) { struct simple_panel_priv *priv = dev_get_priv(dev); @@ -111,7 +98,8 @@ static int simple_panel_probe(struct udevice *dev) { struct simple_panel_priv *priv = dev_get_priv(dev); struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); - const u32 dsi_data = dev_get_driver_data(dev); + struct mipi_dsi_panel_plat *dsi_data = + (struct mipi_dsi_panel_plat *)dev_get_driver_data(dev); int ret; ret = regulator_set_enable_if_allowed(priv->reg, true); @@ -121,15 +109,8 @@ static int simple_panel_probe(struct udevice *dev) return ret; } - switch (dsi_data) { - case PANASONIC_VVX10F004B00: - memcpy(plat, &panasonic_vvx10f004b00, - sizeof(panasonic_vvx10f004b00)); - break; - case PANEL_NON_DSI: - default: - break; - } + if (dsi_data) + memcpy(plat, dsi_data, sizeof(struct mipi_dsi_panel_plat)); return 0; } @@ -140,6 +121,13 @@ static const struct panel_ops simple_panel_ops = { .get_display_timing = simple_panel_get_display_timing, }; +static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = { + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + static const struct udevice_id simple_panel_ids[] = { { .compatible = "simple-panel" }, { .compatible = "auo,b133xtn01" }, @@ -150,7 +138,7 @@ static const struct udevice_id simple_panel_ids[] = { { .compatible = "sharp,lq123p1jx31" }, { .compatible = "boe,nv101wxmn51" }, { .compatible = "panasonic,vvx10f004b00", - .data = PANASONIC_VVX10F004B00 }, + .data = (ulong)&panasonic_vvx10f004b00 }, { } }; -- cgit v1.3.1 From 25ca3314fbc6ad2c55201792605038aa491d1548 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 8 Jan 2024 18:45:01 +0200 Subject: video: simple_panel: add EDID support Support timing parsing from EDID if panel device tree node provides DDC i2c bus instead of timings node. Tested-by: Robert Eckelmann # ASUS TF201 Tested-by: Agneli # Toshiba AC100 T20 Signed-off-by: Svyatoslav Ryhel [agust: reworked to fix dm_i2c_* build errors and to big img size] Signed-off-by: Anatolij Gustschin --- drivers/video/simple_panel.c | 66 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c index 064c113d2d0..76a30427a59 100644 --- a/drivers/video/simple_panel.c +++ b/drivers/video/simple_panel.c @@ -7,12 +7,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#define EDID_I2C_ADDR 0x50 + struct simple_panel_priv { struct udevice *reg; struct udevice *backlight; @@ -49,13 +53,71 @@ static int simple_panel_set_backlight(struct udevice *dev, int percent) return 0; } +#if CONFIG_IS_ENABLED(I2C_EDID) && CONFIG_IS_ENABLED(DM_I2C) +static int simple_panel_get_edid_timing(struct udevice *dev, + struct display_timing *timings) +{ + struct udevice *panel_ddc, *panel_edid; + struct display_timing edid_timing; + u8 edid_buf[EDID_SIZE] = { 0 }; + int ret, bpc; + /* Check for DDC i2c if no timings are provided */ + ret = uclass_get_device_by_phandle(UCLASS_I2C, dev, + "ddc-i2c-bus", + &panel_ddc); + if (ret) { + log_debug("%s: cannot get DDC i2c bus: error %d\n", + __func__, ret); + return ret; + } + + ret = dm_i2c_probe(panel_ddc, EDID_I2C_ADDR, 0, &panel_edid); + if (ret) { + log_debug("%s: cannot probe EDID: error %d\n", + __func__, ret); + return ret; + } + + ret = dm_i2c_read(panel_edid, 0, edid_buf, sizeof(edid_buf)); + if (ret) { + log_debug("%s: cannot dump EDID buffer: error %d\n", + __func__, ret); + return ret; + } + + ret = edid_get_timing(edid_buf, sizeof(edid_buf), + &edid_timing, &bpc); + if (ret) { + log_debug("%s: cannot decode EDID info: error %d\n", + __func__, ret); + return ret; + } + + memcpy(timings, &edid_timing, sizeof(*timings)); + + return 0; +} +#else +static int simple_panel_get_edid_timing(struct udevice *dev, + struct display_timing *timings) +{ + return -ENOTSUPP; +} +#endif + static int simple_panel_get_display_timing(struct udevice *dev, struct display_timing *timings) { const void *blob = gd->fdt_blob; + int ret; + + /* Check for timing subnode if panel node first */ + ret = fdtdec_decode_display_timing(blob, dev_of_offset(dev), + 0, timings); + if (!ret) + return ret; - return fdtdec_decode_display_timing(blob, dev_of_offset(dev), - 0, timings); + return simple_panel_get_edid_timing(dev, timings); } static int simple_panel_of_to_plat(struct udevice *dev) -- cgit v1.3.1 From 41524d7f3add5a58f2fd5951d0182d1e94dd3d03 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:38 +0530 Subject: video: rockchip: hdmi: Detect hpd after controller init HDP is a hardware connector event, so detect the same once the controller and attached PHY initialization are done. Signed-off-by: Jagan Teki --- drivers/video/rockchip/rk_hdmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c index 044a29ee47a..9f907d9f015 100644 --- a/drivers/video/rockchip/rk_hdmi.c +++ b/drivers/video/rockchip/rk_hdmi.c @@ -111,14 +111,14 @@ int rk_hdmi_probe(struct udevice *dev) struct dw_hdmi *hdmi = &priv->hdmi; int ret; + dw_hdmi_init(hdmi); + dw_hdmi_phy_init(hdmi); + ret = dw_hdmi_phy_wait_for_hpd(hdmi); if (ret < 0) { debug("hdmi can not get hpd signal\n"); return -1; } - dw_hdmi_init(hdmi); - dw_hdmi_phy_init(hdmi); - return 0; } -- cgit v1.3.1 From f889491d57ea14dfe57dd74a23a6393e3aad5e5c Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:39 +0530 Subject: video: dw_hdmi: Add Vendor PHY handling DW HDMI support Vendor PHY like Rockchip RK3328 Inno HDMI PHY. Extend the vendor phy handling by adding platform phy hooks. Signed-off-by: Jagan Teki Reviewed-by: Neil Armstrong --- drivers/video/dw_hdmi.c | 10 +++++++++- drivers/video/meson/meson_dw_hdmi.c | 6 +++++- drivers/video/rockchip/rk_hdmi.c | 1 - drivers/video/sunxi/sunxi_dw_hdmi.c | 6 +++++- include/dw_hdmi.h | 8 +++++++- 5 files changed, 26 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index c4fbb182944..4914ba61464 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -988,7 +988,7 @@ int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid) hdmi_av_composer(hdmi, edid); - ret = hdmi->phy_set(hdmi, edid->pixelclock.typ); + ret = hdmi->ops->phy_set(hdmi, edid->pixelclock.typ); if (ret) return ret; @@ -1009,10 +1009,18 @@ int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid) return 0; } +static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { + .phy_set = dw_hdmi_phy_cfg, +}; + void dw_hdmi_init(struct dw_hdmi *hdmi) { uint ih_mute; + /* hook Synopsys PHYs ops */ + if (!hdmi->ops) + hdmi->ops = &dw_hdmi_synopsys_phy_ops; + /* * boot up defaults are: * hdmi_ih_mute = 0x03 (disabled) diff --git a/drivers/video/meson/meson_dw_hdmi.c b/drivers/video/meson/meson_dw_hdmi.c index 5db01904b53..259af1b4571 100644 --- a/drivers/video/meson/meson_dw_hdmi.c +++ b/drivers/video/meson/meson_dw_hdmi.c @@ -375,6 +375,10 @@ static int meson_dw_hdmi_wait_hpd(struct dw_hdmi *hdmi) return -ETIMEDOUT; } +static const struct dw_hdmi_phy_ops dw_hdmi_meson_phy_ops = { + .phy_set = meson_dw_hdmi_phy_init, +}; + static int meson_dw_hdmi_probe(struct udevice *dev) { struct meson_dw_hdmi *priv = dev_get_priv(dev); @@ -397,7 +401,7 @@ static int meson_dw_hdmi_probe(struct udevice *dev) priv->hdmi.hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; priv->hdmi.hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_YUV8_1X24; - priv->hdmi.phy_set = meson_dw_hdmi_phy_init; + priv->hdmi.ops = &dw_hdmi_meson_phy_ops; if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_G12A)) priv->hdmi.reg_io_width = 1; else { diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c index 9f907d9f015..84b6a7ebcf1 100644 --- a/drivers/video/rockchip/rk_hdmi.c +++ b/drivers/video/rockchip/rk_hdmi.c @@ -89,7 +89,6 @@ int rk_hdmi_of_to_plat(struct udevice *dev) /* hdmi->i2c_clk_{high,low} are set up by the SoC driver */ hdmi->reg_io_width = 4; - hdmi->phy_set = dw_hdmi_phy_cfg; priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c index 0324a050d03..986e69d66b1 100644 --- a/drivers/video/sunxi/sunxi_dw_hdmi.c +++ b/drivers/video/sunxi/sunxi_dw_hdmi.c @@ -369,6 +369,10 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev) return 0; } +static const struct dw_hdmi_phy_ops dw_hdmi_sunxi_phy_ops = { + .phy_set = sunxi_dw_hdmi_phy_cfg, +}; + static int sunxi_dw_hdmi_of_to_plat(struct udevice *dev) { struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev); @@ -379,7 +383,7 @@ static int sunxi_dw_hdmi_of_to_plat(struct udevice *dev) hdmi->i2c_clk_high = 0xd8; hdmi->i2c_clk_low = 0xfe; hdmi->reg_io_width = 1; - hdmi->phy_set = sunxi_dw_hdmi_phy_cfg; + hdmi->ops = &dw_hdmi_sunxi_phy_ops; ret = reset_get_bulk(dev, &priv->resets); if (ret) diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h index 8acae3839fb..17bdd2dbf9e 100644 --- a/include/dw_hdmi.h +++ b/include/dw_hdmi.h @@ -534,6 +534,12 @@ struct hdmi_data_info { struct hdmi_vmode video_mode; }; +struct dw_hdmi; + +struct dw_hdmi_phy_ops { + int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock); +}; + struct dw_hdmi { ulong ioaddr; const struct hdmi_mpll_config *mpll_cfg; @@ -543,8 +549,8 @@ struct dw_hdmi { u8 reg_io_width; struct hdmi_data_info hdmi_data; struct udevice *ddc_bus; + const struct dw_hdmi_phy_ops *ops; - int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock); void (*write_reg)(struct dw_hdmi *hdmi, u8 val, int offset); u8 (*read_reg)(struct dw_hdmi *hdmi, int offset); }; -- cgit v1.3.1 From 5eacb920710364d4c9134886725c3879d89972b1 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:40 +0530 Subject: video: dw_hdmi: Extend the HPD detection HPD detection on some DW HDMI designed SoC's would need to read and setup the HPD status explicitly. So, extend the HPD detection code by adding the dw_hdmi_detect_hpd function and move the default detection code caller there. The new read and setup hdp will integrate the same function in later patches. Signed-off-by: Jagan Teki Reviewed-by: Neil Armstrong --- drivers/video/dw_hdmi.c | 13 +++++++++++++ drivers/video/rockchip/rk_hdmi.c | 8 +++----- drivers/video/sunxi/sunxi_dw_hdmi.c | 8 +++----- include/dw_hdmi.h | 1 + 4 files changed, 20 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index 4914ba61464..3a3b9b7a21d 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -936,6 +936,19 @@ int dw_hdmi_phy_wait_for_hpd(struct dw_hdmi *hdmi) return -1; } +int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi) +{ + int ret; + + ret = dw_hdmi_phy_wait_for_hpd(hdmi); + if (ret < 0) { + debug("hdmi can not get hpd signal\n"); + return -ENODEV; + } + + return 0; +} + void dw_hdmi_phy_init(struct dw_hdmi *hdmi) { /* enable phy i2cm done irq */ diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c index 84b6a7ebcf1..d31f6a4ff81 100644 --- a/drivers/video/rockchip/rk_hdmi.c +++ b/drivers/video/rockchip/rk_hdmi.c @@ -113,11 +113,9 @@ int rk_hdmi_probe(struct udevice *dev) dw_hdmi_init(hdmi); dw_hdmi_phy_init(hdmi); - ret = dw_hdmi_phy_wait_for_hpd(hdmi); - if (ret < 0) { - debug("hdmi can not get hpd signal\n"); - return -1; - } + ret = dw_hdmi_detect_hpd(hdmi); + if (ret < 0) + return ret; return 0; } diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c index 986e69d66b1..a5e8d39e98f 100644 --- a/drivers/video/sunxi/sunxi_dw_hdmi.c +++ b/drivers/video/sunxi/sunxi_dw_hdmi.c @@ -358,11 +358,9 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev) sunxi_dw_hdmi_phy_init(&priv->hdmi); - ret = dw_hdmi_phy_wait_for_hpd(&priv->hdmi); - if (ret < 0) { - debug("hdmi can not get hpd signal\n"); - return -1; - } + ret = dw_hdmi_detect_hpd(&priv->hdmi); + if (ret < 0) + return ret; dw_hdmi_init(&priv->hdmi); diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h index 17bdd2dbf9e..ba2ce5ea7fe 100644 --- a/include/dw_hdmi.h +++ b/include/dw_hdmi.h @@ -562,5 +562,6 @@ void dw_hdmi_phy_init(struct dw_hdmi *hdmi); int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid); int dw_hdmi_read_edid(struct dw_hdmi *hdmi, u8 *buf, int buf_size); void dw_hdmi_init(struct dw_hdmi *hdmi); +int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi); #endif -- cgit v1.3.1 From 054a0ca8c16a229c76de033b9d3c7375b1e0ed0f Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:41 +0530 Subject: video: dw_hdmi: Add read_hpd hook Add support for DW HDMI Read HPD status. Signed-off-by: Jagan Teki Reviewed-by: Neil Armstrong --- drivers/video/dw_hdmi.c | 3 +++ include/dw_hdmi.h | 1 + 2 files changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index 3a3b9b7a21d..989b7ab2db1 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -946,6 +946,9 @@ int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi) return -ENODEV; } + if (hdmi->ops && hdmi->ops->read_hpd) + hdmi->ops->read_hpd(hdmi, true); + return 0; } diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h index ba2ce5ea7fe..a1f0e64507d 100644 --- a/include/dw_hdmi.h +++ b/include/dw_hdmi.h @@ -538,6 +538,7 @@ struct dw_hdmi; struct dw_hdmi_phy_ops { int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock); + void (*read_hpd)(struct dw_hdmi *hdmi, bool hdp_status); }; struct dw_hdmi { -- cgit v1.3.1 From 25353b5b8b2be4402ec449946235b07670217666 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:42 +0530 Subject: video: dw_hdmi: Add setup_hpd hook Add support for DW HDMI Setup HPD status. Signed-off-by: Jagan Teki Reviewed-by: Neil Armstrong --- drivers/video/dw_hdmi.c | 3 +++ include/dw_hdmi.h | 1 + 2 files changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index 989b7ab2db1..ab4811cfc76 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -1061,4 +1061,7 @@ void dw_hdmi_init(struct dw_hdmi *hdmi) /* enable i2c client nack % arbitration error irq */ hdmi_write(hdmi, ~0x44, HDMI_I2CM_CTLINT); + + if (hdmi->ops && hdmi->ops->setup_hpd) + hdmi->ops->setup_hpd(hdmi); } diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h index a1f0e64507d..f4d66edacee 100644 --- a/include/dw_hdmi.h +++ b/include/dw_hdmi.h @@ -539,6 +539,7 @@ struct dw_hdmi; struct dw_hdmi_phy_ops { int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock); void (*read_hpd)(struct dw_hdmi *hdmi, bool hdp_status); + void (*setup_hpd)(struct dw_hdmi *hdmi); }; struct dw_hdmi { -- cgit v1.3.1 From 3c0f45c632bb5240208a07b104e279d0f8f5eb9a Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:43 +0530 Subject: video: rockchip: vop: Simplify rkvop_enable Get the regs from priv pointer instead of passing it an argument. This would simplify the code and better readability. Signed-off-by: Jagan Teki --- drivers/video/rockchip/rk_vop.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c index c514e2a0e44..158ba7cbf69 100644 --- a/drivers/video/rockchip/rk_vop.c +++ b/drivers/video/rockchip/rk_vop.c @@ -39,11 +39,13 @@ enum vop_pol { DCLK_INVERT = 3 }; -static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbbase, +static void rkvop_enable(struct udevice *dev, ulong fbbase, int fb_bits_per_pixel, const struct display_timing *edid, struct reset_ctl *dclk_rst) { + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; u32 lb_mode; u32 rgb_mode; u32 hactive = edid->hactive.typ; @@ -243,9 +245,7 @@ static void rkvop_mode_set(struct udevice *dev, static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node) { struct video_priv *uc_priv = dev_get_uclass_priv(dev); - struct rk_vop_priv *priv = dev_get_priv(dev); int vop_id, remote_vop_id; - struct rk3288_vop *regs = priv->regs; struct display_timing timing; struct udevice *disp; int ret; @@ -380,7 +380,7 @@ static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node) return ret; } - rkvop_enable(dev, regs, fbbase, 1 << l2bpp, &timing, &dclk_rst); + rkvop_enable(dev, fbbase, 1 << l2bpp, &timing, &dclk_rst); ret = display_enable(disp, 1 << l2bpp, &timing); if (ret) -- cgit v1.3.1 From 41b612ee5f74ddc3de1fad4f3fb86f5354fe6d70 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:44 +0530 Subject: video: rockchip: vop: Add win offset support Unlike RK3399, RK3288 the Newer Rockchip SoC's like RK3328 have different offsets for win registers. Group the win register set via win_regs pointers so that win_offset would point the win_regs to access for any changes in the offset value. Signed-off-by: Jagan Teki --- drivers/video/rockchip/rk_vop.c | 22 +++++++++++++--------- drivers/video/rockchip/rk_vop.h | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c index 158ba7cbf69..b719a4e4eaf 100644 --- a/drivers/video/rockchip/rk_vop.c +++ b/drivers/video/rockchip/rk_vop.c @@ -46,6 +46,7 @@ static void rkvop_enable(struct udevice *dev, ulong fbbase, { struct rk_vop_priv *priv = dev_get_priv(dev); struct rk3288_vop *regs = priv->regs; + struct rk3288_vop *win_regs = priv->regs + priv->win_offset; u32 lb_mode; u32 rgb_mode; u32 hactive = edid->hactive.typ; @@ -53,32 +54,32 @@ static void rkvop_enable(struct udevice *dev, ulong fbbase, int ret; writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1), - ®s->win0_act_info); + &win_regs->win0_act_info); writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) | V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ), - ®s->win0_dsp_st); + &win_regs->win0_dsp_st); writel(V_DSP_WIDTH(hactive - 1) | V_DSP_HEIGHT(vactive - 1), - ®s->win0_dsp_info); + &win_regs->win0_dsp_info); - clrsetbits_le32(®s->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR, + clrsetbits_le32(&win_regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR, V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0)); switch (fb_bits_per_pixel) { case 16: rgb_mode = RGB565; - writel(V_RGB565_VIRWIDTH(hactive), ®s->win0_vir); + writel(V_RGB565_VIRWIDTH(hactive), &win_regs->win0_vir); break; case 24: rgb_mode = RGB888; - writel(V_RGB888_VIRWIDTH(hactive), ®s->win0_vir); + writel(V_RGB888_VIRWIDTH(hactive), &win_regs->win0_vir); break; case 32: default: rgb_mode = ARGB8888; - writel(V_ARGB888_VIRWIDTH(hactive), ®s->win0_vir); + writel(V_ARGB888_VIRWIDTH(hactive), &win_regs->win0_vir); break; } @@ -91,12 +92,12 @@ static void rkvop_enable(struct udevice *dev, ulong fbbase, else lb_mode = LB_RGB_1280X8; - clrsetbits_le32(®s->win0_ctrl0, + clrsetbits_le32(&win_regs->win0_ctrl0, M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN, V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) | V_WIN0_EN(1)); - writel(fbbase, ®s->win0_yrgb_mst); + writel(fbbase, &win_regs->win0_yrgb_mst); writel(0x01, ®s->reg_cfg_done); /* enable reg config */ ret = reset_assert(dclk_rst); @@ -415,6 +416,8 @@ int rk_vop_probe(struct udevice *dev) { struct video_uc_plat *plat = dev_get_uclass_plat(dev); struct rk_vop_priv *priv = dev_get_priv(dev); + struct rkvop_driverdata *ops = + (struct rkvop_driverdata *)dev_get_driver_data(dev); int ret = 0; ofnode port, node; struct reset_ctl ahb_rst; @@ -448,6 +451,7 @@ int rk_vop_probe(struct udevice *dev) #endif priv->regs = dev_read_addr_ptr(dev); + priv->win_offset = ops->win_offset; /* * Try all the ports until we find one that works. In practice this diff --git a/drivers/video/rockchip/rk_vop.h b/drivers/video/rockchip/rk_vop.h index 0528fb23f59..909f5602e53 100644 --- a/drivers/video/rockchip/rk_vop.h +++ b/drivers/video/rockchip/rk_vop.h @@ -11,6 +11,7 @@ struct rk_vop_priv { void *grf; void *regs; + int win_offset; }; enum vop_features { @@ -18,6 +19,7 @@ enum vop_features { }; struct rkvop_driverdata { + int win_offset; /* configuration */ u32 features; /* block-specific setters/getters */ -- cgit v1.3.1 From 7cebb300ae87e0226b9ad1b75a4f3c9730dad5cf Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:45 +0530 Subject: video: rockchip: vop: Add dsp offset support Unlike RK3399, RK3288 the Newer Rockchip SoC's like RK3328 have different offsets for dsp registers. Group the dsp register set via dsp_regs pointers so that dsp_offset would point the dsp_regs to access for any changes in the offset value. Signed-off-by: Jagan Teki --- drivers/video/rockchip/rk_vop.c | 14 ++++++++------ drivers/video/rockchip/rk_vop.h | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c index b719a4e4eaf..acc02e5d7c7 100644 --- a/drivers/video/rockchip/rk_vop.c +++ b/drivers/video/rockchip/rk_vop.c @@ -165,6 +165,7 @@ static void rkvop_mode_set(struct udevice *dev, { struct rk_vop_priv *priv = dev_get_priv(dev); struct rk3288_vop *regs = priv->regs; + struct rk3288_vop *dsp_regs = priv->regs + priv->dsp_offset; struct rkvop_driverdata *data = (struct rkvop_driverdata *)dev_get_driver_data(dev); @@ -198,27 +199,27 @@ static void rkvop_mode_set(struct udevice *dev, writel(V_HSYNC(hsync_len) | V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch), - ®s->dsp_htotal_hs_end); + &dsp_regs->dsp_htotal_hs_end); writel(V_HEAP(hsync_len + hback_porch + hactive) | V_HASP(hsync_len + hback_porch), - ®s->dsp_hact_st_end); + &dsp_regs->dsp_hact_st_end); writel(V_VSYNC(vsync_len) | V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch), - ®s->dsp_vtotal_vs_end); + &dsp_regs->dsp_vtotal_vs_end); writel(V_VAEP(vsync_len + vback_porch + vactive)| V_VASP(vsync_len + vback_porch), - ®s->dsp_vact_st_end); + &dsp_regs->dsp_vact_st_end); writel(V_HEAP(hsync_len + hback_porch + hactive) | V_HASP(hsync_len + hback_porch), - ®s->post_dsp_hact_info); + &dsp_regs->post_dsp_hact_info); writel(V_VAEP(vsync_len + vback_porch + vactive)| V_VASP(vsync_len + vback_porch), - ®s->post_dsp_vact_info); + &dsp_regs->post_dsp_vact_info); writel(0x01, ®s->reg_cfg_done); /* enable reg config */ } @@ -452,6 +453,7 @@ int rk_vop_probe(struct udevice *dev) priv->regs = dev_read_addr_ptr(dev); priv->win_offset = ops->win_offset; + priv->dsp_offset = ops->dsp_offset; /* * Try all the ports until we find one that works. In practice this diff --git a/drivers/video/rockchip/rk_vop.h b/drivers/video/rockchip/rk_vop.h index 909f5602e53..eba68d87c4b 100644 --- a/drivers/video/rockchip/rk_vop.h +++ b/drivers/video/rockchip/rk_vop.h @@ -12,6 +12,7 @@ struct rk_vop_priv { void *grf; void *regs; int win_offset; + int dsp_offset; }; enum vop_features { @@ -20,6 +21,7 @@ enum vop_features { struct rkvop_driverdata { int win_offset; + int dsp_offset; /* configuration */ u32 features; /* block-specific setters/getters */ -- cgit v1.3.1 From 92edae779f8c94a49d2f3e215b1ca0adf1241d53 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:46 +0530 Subject: clk: rockchip: rk3328: Add VOP clk support VOP get and set clock would needed for VOP drivers. Add support for it. Signed-off-by: Jagan Teki --- arch/arm/include/asm/arch-rockchip/cru_rk3328.h | 34 ++++++++++ drivers/clk/rockchip/clk_rk3328.c | 88 ++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h index 226744d67d9..4ad1d33e056 100644 --- a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h +++ b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h @@ -62,6 +62,40 @@ check_member(rk3328_cru, sdmmc_ext_con[1], 0x39c); enum apll_frequencies { APLL_816_MHZ, APLL_600_MHZ, + + /* CRU_CLK_SEL37_CON */ + ACLK_VIO_PLL_SEL_CPLL = 0, + ACLK_VIO_PLL_SEL_GPLL = 1, + ACLK_VIO_PLL_SEL_HDMIPHY = 2, + ACLK_VIO_PLL_SEL_USB480M = 3, + ACLK_VIO_PLL_SEL_SHIFT = 6, + ACLK_VIO_PLL_SEL_MASK = 3 << ACLK_VIO_PLL_SEL_SHIFT, + ACLK_VIO_DIV_CON_SHIFT = 0, + ACLK_VIO_DIV_CON_MASK = 0x1f << ACLK_VIO_DIV_CON_SHIFT, + HCLK_VIO_DIV_CON_SHIFT = 8, + HCLK_VIO_DIV_CON_MASK = 0x1f << HCLK_VIO_DIV_CON_SHIFT, + + /* CRU_CLK_SEL39_CON */ + ACLK_VOP_PLL_SEL_CPLL = 0, + ACLK_VOP_PLL_SEL_GPLL = 1, + ACLK_VOP_PLL_SEL_HDMIPHY = 2, + ACLK_VOP_PLL_SEL_USB480M = 3, + ACLK_VOP_PLL_SEL_SHIFT = 6, + ACLK_VOP_PLL_SEL_MASK = 3 << ACLK_VOP_PLL_SEL_SHIFT, + ACLK_VOP_DIV_CON_SHIFT = 0, + ACLK_VOP_DIV_CON_MASK = 0x1f << ACLK_VOP_DIV_CON_SHIFT, + + /* CRU_CLK_SEL40_CON */ + DCLK_LCDC_PLL_SEL_GPLL = 0, + DCLK_LCDC_PLL_SEL_CPLL = 1, + DCLK_LCDC_PLL_SEL_SHIFT = 0, + DCLK_LCDC_PLL_SEL_MASK = 1 << DCLK_LCDC_PLL_SEL_SHIFT, + DCLK_LCDC_SEL_HDMIPHY = 0, + DCLK_LCDC_SEL_PLL = 1, + DCLK_LCDC_SEL_SHIFT = 1, + DCLK_LCDC_SEL_MASK = 1 << DCLK_LCDC_SEL_SHIFT, + DCLK_LCDC_DIV_CON_SHIFT = 8, + DCLK_LCDC_DIV_CON_MASK = 0xFf << DCLK_LCDC_DIV_CON_SHIFT, }; void rk3328_configure_cpu(struct rk3328_cru *cru, diff --git a/drivers/clk/rockchip/clk_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c index cfec1d974ac..df6076e4dcb 100644 --- a/drivers/clk/rockchip/clk_rk3328.c +++ b/drivers/clk/rockchip/clk_rk3328.c @@ -580,6 +580,86 @@ static ulong rk3328_spi_set_clk(struct rk3328_cru *cru, uint hz) return rk3328_spi_get_clk(cru); } +#ifndef CONFIG_SPL_BUILD +static ulong rk3328_vop_get_clk(struct rk3328_clk_priv *priv, ulong clk_id) +{ + struct rk3328_cru *cru = priv->cru; + u32 div, con, parent; + + switch (clk_id) { + case ACLK_VOP_PRE: + con = readl(&cru->clksel_con[39]); + div = (con & ACLK_VOP_DIV_CON_MASK) >> ACLK_VOP_DIV_CON_SHIFT; + parent = GPLL_HZ; + break; + case ACLK_VIO_PRE: + con = readl(&cru->clksel_con[37]); + div = (con & ACLK_VIO_DIV_CON_MASK) >> ACLK_VIO_DIV_CON_SHIFT; + parent = GPLL_HZ; + break; + case DCLK_LCDC: + con = readl(&cru->clksel_con[40]); + div = (con & DCLK_LCDC_DIV_CON_MASK) >> DCLK_LCDC_DIV_CON_SHIFT; + parent = GPLL_HZ; + break; + default: + printf("%s: Unsupported vop get clk#%ld\n", __func__, clk_id); + return -ENOENT; + } + + return DIV_TO_RATE(parent, div); +} + +static ulong rk3328_vop_set_clk(struct rk3328_clk_priv *priv, + ulong clk_id, uint hz) +{ + struct rk3328_cru *cru = priv->cru; + int src_clk_div; + u32 con, parent; + + src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz); + assert(src_clk_div - 1 < 31); + + switch (clk_id) { + case ACLK_VOP_PRE: + rk_clrsetreg(&cru->clksel_con[39], + ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK, + ACLK_VOP_PLL_SEL_CPLL << ACLK_VOP_PLL_SEL_SHIFT | + (src_clk_div - 1) << ACLK_VOP_DIV_CON_SHIFT); + break; + case ACLK_VIO_PRE: + rk_clrsetreg(&cru->clksel_con[37], + ACLK_VIO_PLL_SEL_MASK | ACLK_VIO_DIV_CON_MASK, + ACLK_VIO_PLL_SEL_CPLL << ACLK_VIO_PLL_SEL_SHIFT | + (src_clk_div - 1) << ACLK_VIO_DIV_CON_SHIFT); + break; + case DCLK_LCDC: + con = readl(&cru->clksel_con[40]); + con = (con & DCLK_LCDC_SEL_MASK) >> DCLK_LCDC_SEL_SHIFT; + if (con) { + parent = readl(&cru->clksel_con[40]); + parent = (parent & DCLK_LCDC_PLL_SEL_MASK) >> + DCLK_LCDC_PLL_SEL_SHIFT; + if (parent) + src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz); + else + src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz); + + rk_clrsetreg(&cru->clksel_con[40], + DCLK_LCDC_DIV_CON_MASK, + (src_clk_div - 1) << + DCLK_LCDC_DIV_CON_SHIFT); + } + break; + default: + printf("%s: Unable to set vop clk#%ld\n", __func__, clk_id); + return -EINVAL; + } + + return rk3328_vop_get_clk(priv, clk_id); +} +#endif + static ulong rk3328_clk_get_rate(struct clk *clk) { struct rk3328_clk_priv *priv = dev_get_priv(clk->dev); @@ -648,7 +728,13 @@ static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate) case SCLK_SPI: ret = rk3328_spi_set_clk(priv->cru, rate); break; +#ifndef CONFIG_SPL_BUILD case DCLK_LCDC: + case ACLK_VOP_PRE: + case ACLK_VIO_PRE: + rate = rk3328_vop_set_clk(priv, clk->id, rate); + break; +#endif case SCLK_PDM: case SCLK_RTC32K: case SCLK_UART0: @@ -663,11 +749,9 @@ static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate) case ACLK_PERI_PRE: case HCLK_PERI: case PCLK_PERI: - case ACLK_VIO_PRE: case HCLK_VIO_PRE: case ACLK_RGA_PRE: case SCLK_RGA: - case ACLK_VOP_PRE: case ACLK_RKVDEC_PRE: case ACLK_RKVENC: case ACLK_VPU_PRE: -- cgit v1.3.1 From f7f4789814188cae77c3610228ffd9661a5fae9a Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:47 +0530 Subject: clk: rk3328: Add get hdmiphy clock Add support to get the hdmiphy clock for RK3328 PCLK_HDMIPHY. Signed-off-by: Jagan Teki --- drivers/clk/rockchip/clk_rk3328.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/clk/rockchip/clk_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c index df6076e4dcb..87075ec7134 100644 --- a/drivers/clk/rockchip/clk_rk3328.c +++ b/drivers/clk/rockchip/clk_rk3328.c @@ -178,6 +178,10 @@ enum { CLK_I2C3_DIV_CON_SHIFT = 8, CLK_I2C2_PLL_SEL_SHIFT = 7, CLK_I2C2_DIV_CON_SHIFT = 0, + + /* CLKSEL_CON40 */ + CLK_HDMIPHY_DIV_CON_SHIFT = 3, + CLK_HDMIPHY_DIV_CON_MASK = 0x7 << CLK_HDMIPHY_DIV_CON_SHIFT, }; #define VCO_MAX_KHZ (3200 * (MHz / KHz)) @@ -660,6 +664,16 @@ static ulong rk3328_vop_set_clk(struct rk3328_clk_priv *priv, } #endif +static ulong rk3328_hdmiphy_get_clk(struct rk3328_cru *cru) +{ + u32 div, con; + + con = readl(&cru->clksel_con[40]); + div = (con & CLK_HDMIPHY_DIV_CON_MASK) >> CLK_HDMIPHY_DIV_CON_SHIFT; + + return DIV_TO_RATE(GPLL_HZ, div); +} + static ulong rk3328_clk_get_rate(struct clk *clk) { struct rk3328_clk_priv *priv = dev_get_priv(clk->dev); @@ -689,6 +703,9 @@ static ulong rk3328_clk_get_rate(struct clk *clk) case SCLK_SPI: rate = rk3328_spi_get_clk(priv->cru); break; + case PCLK_HDMIPHY: + rate = rk3328_hdmiphy_get_clk(priv->cru); + break; default: return -ENOENT; } -- cgit v1.3.1 From aa22711846036b4fb8b30da560a1ea5f90d50551 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:48 +0530 Subject: phy: rockchip: Add Rockchip INNO HDMI PHY driver Add Rockchip INNO HDMI PHY driver for RK3328. Reference from linux-next phy-rockchip-inno-hdmi driver. Signed-off-by: Jagan Teki --- drivers/phy/rockchip/Kconfig | 7 + drivers/phy/rockchip/Makefile | 1 + drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 885 ++++++++++++++++++++++++++ 3 files changed, 893 insertions(+) create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-hdmi.c (limited to 'drivers') diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 0247d93ab40..80128335d52 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -12,6 +12,13 @@ config PHY_ROCKCHIP_INNO_DSIDPHY help Support for Rockchip MIPI DPHY with Innosilicon IP block. +config PHY_ROCKCHIP_INNO_HDMI + bool "Rockchip INNO HDMI PHY Driver" + depends on ARCH_ROCKCHIP + select PHY + help + Enable this to support the Rockchip Innosilicon HDMI PHY. + config PHY_ROCKCHIP_INNO_USB2 bool "Rockchip INNO USB2PHY Driver" depends on ARCH_ROCKCHIP diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index 7fdbd107976..04200174254 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -3,6 +3,7 @@ # Copyright (C) 2020 Amarula Solutions(India) # +obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY) += phy-rockchip-naneng-combphy.o obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c new file mode 100644 index 00000000000..3bb1a254ffb --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c @@ -0,0 +1,885 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Rockchip Innosilicon HDMI PHY + * + * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd. + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l))) + +/* REG: 0x01 */ +#define RK3328_BYPASS_RXSENSE_EN BIT(2) +#define RK3328_BYPASS_POWERON_EN BIT(1) +#define RK3328_BYPASS_PLLPD_EN BIT(0) +/* REG: 0x02 */ +#define RK3328_INT_POL_HIGH BIT(7) +#define RK3328_BYPASS_PDATA_EN BIT(4) +#define RK3328_PDATA_EN BIT(0) +/* REG:0x05 */ +#define RK3328_INT_TMDS_CLK(x) UPDATE(x, 7, 4) +#define RK3328_INT_TMDS_D2(x) UPDATE(x, 3, 0) +/* REG:0x07 */ +#define RK3328_INT_TMDS_D1(x) UPDATE(x, 7, 4) +#define RK3328_INT_TMDS_D0(x) UPDATE(x, 3, 0) +/* for all RK3328_INT_TMDS_*, ESD_DET as defined in 0xc8-0xcb */ +#define RK3328_INT_AGND_LOW_PULSE_LOCKED BIT(3) +#define RK3328_INT_RXSENSE_LOW_PULSE_LOCKED BIT(2) +#define RK3328_INT_VSS_AGND_ESD_DET BIT(1) +#define RK3328_INT_AGND_VSS_ESD_DET BIT(0) +/* REG: 0xa0 */ +#define RK3328_PCLK_VCO_DIV_5_MASK BIT(1) +#define RK3328_PCLK_VCO_DIV_5(x) UPDATE(x, 1, 1) +#define RK3328_PRE_PLL_POWER_DOWN BIT(0) +/* REG: 0xa1 */ +#define RK3328_PRE_PLL_PRE_DIV_MASK GENMASK(5, 0) +#define RK3328_PRE_PLL_PRE_DIV(x) UPDATE(x, 5, 0) +/* REG: 0xa2 */ +/* unset means center spread */ +#define RK3328_SPREAD_SPECTRUM_MOD_DOWN BIT(7) +#define RK3328_SPREAD_SPECTRUM_MOD_DISABLE BIT(6) +#define RK3328_PRE_PLL_FRAC_DIV_DISABLE UPDATE(3, 5, 4) +#define RK3328_PRE_PLL_FB_DIV_11_8_MASK GENMASK(3, 0) +#define RK3328_PRE_PLL_FB_DIV_11_8(x) UPDATE((x) >> 8, 3, 0) +/* REG: 0xa3 */ +#define RK3328_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xa4*/ +#define RK3328_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(1, 0) +#define RK3328_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 1, 0) +#define RK3328_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(3, 2) +#define RK3328_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 3, 2) +#define RK3328_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(5, 4) +#define RK3328_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 5, 4) +/* REG: 0xa5 */ +#define RK3328_PRE_PLL_PCLK_DIV_B_SHIFT 5 +#define RK3328_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0) +#define RK3328_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0) +/* REG: 0xa6 */ +#define RK3328_PRE_PLL_PCLK_DIV_C_SHIFT 5 +#define RK3328_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0) +#define RK3328_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0) +/* REG: 0xa9 */ +#define RK3328_PRE_PLL_LOCK_STATUS BIT(0) +/* REG: 0xaa */ +#define RK3328_POST_PLL_POST_DIV_ENABLE GENMASK(3, 2) +#define RK3328_POST_PLL_REFCLK_SEL_TMDS BIT(1) +#define RK3328_POST_PLL_POWER_DOWN BIT(0) +/* REG:0xab */ +#define RK3328_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7) +#define RK3328_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0) +/* REG: 0xac */ +#define RK3328_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xad */ +#define RK3328_POST_PLL_POST_DIV_MASK GENMASK(1, 0) +#define RK3328_POST_PLL_POST_DIV_2 0x0 +#define RK3328_POST_PLL_POST_DIV_4 0x1 +#define RK3328_POST_PLL_POST_DIV_8 0x3 +/* REG: 0xaf */ +#define RK3328_POST_PLL_LOCK_STATUS BIT(0) +/* REG: 0xb0 */ +#define RK3328_BANDGAP_ENABLE BIT(2) +/* REG: 0xb2 */ +#define RK3328_TMDS_CLK_DRIVER_EN BIT(3) +#define RK3328_TMDS_D2_DRIVER_EN BIT(2) +#define RK3328_TMDS_D1_DRIVER_EN BIT(1) +#define RK3328_TMDS_D0_DRIVER_EN BIT(0) +#define RK3328_TMDS_DRIVER_ENABLE (RK3328_TMDS_CLK_DRIVER_EN | \ + RK3328_TMDS_D2_DRIVER_EN | \ + RK3328_TMDS_D1_DRIVER_EN | \ + RK3328_TMDS_D0_DRIVER_EN) +/* REG:0xc5 */ +#define RK3328_BYPASS_TERM_RESISTOR_CALIB BIT(7) +#define RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(x) UPDATE((x) >> 8, 6, 0) +/* REG:0xc6 */ +#define RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(x) UPDATE(x, 7, 0) +/* REG:0xc7 */ +#define RK3328_TERM_RESISTOR_50 UPDATE(0, 2, 1) +#define RK3328_TERM_RESISTOR_62_5 UPDATE(1, 2, 1) +#define RK3328_TERM_RESISTOR_75 UPDATE(2, 2, 1) +#define RK3328_TERM_RESISTOR_100 UPDATE(3, 2, 1) +/* REG 0xc8 - 0xcb */ +#define RK3328_ESD_DETECT_MASK GENMASK(7, 6) +#define RK3328_ESD_DETECT_340MV (0x0 << 6) +#define RK3328_ESD_DETECT_280MV (0x1 << 6) +#define RK3328_ESD_DETECT_260MV (0x2 << 6) +#define RK3328_ESD_DETECT_240MV (0x3 << 6) +/* resistors can be used in parallel */ +#define RK3328_TMDS_TERM_RESIST_MASK GENMASK(5, 0) +#define RK3328_TMDS_TERM_RESIST_75 BIT(5) +#define RK3328_TMDS_TERM_RESIST_150 BIT(4) +#define RK3328_TMDS_TERM_RESIST_300 BIT(3) +#define RK3328_TMDS_TERM_RESIST_600 BIT(2) +#define RK3328_TMDS_TERM_RESIST_1000 BIT(1) +#define RK3328_TMDS_TERM_RESIST_2000 BIT(0) +/* REG: 0xd1 */ +#define RK3328_PRE_PLL_FRAC_DIV_23_16(x) UPDATE((x) >> 16, 7, 0) +/* REG: 0xd2 */ +#define RK3328_PRE_PLL_FRAC_DIV_15_8(x) UPDATE((x) >> 8, 7, 0) +/* REG: 0xd3 */ +#define RK3328_PRE_PLL_FRAC_DIV_7_0(x) UPDATE(x, 7, 0) + +struct phy_config { + unsigned long tmdsclock; + u8 regs[14]; +}; + +struct pre_pll_config { + unsigned long pixclock; + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 tmds_div_a; + u8 tmds_div_b; + u8 tmds_div_c; + u8 pclk_div_a; + u8 pclk_div_b; + u8 pclk_div_c; + u8 pclk_div_d; + u8 vco_div_5_en; + u32 fracdiv; +}; + +struct post_pll_config { + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 postdiv; + u8 version; +}; + +struct inno_hdmi_phy_plat_ops { + void (*init)(struct phy *phy); + int (*power_on)(struct phy *phy, const struct post_pll_config *cfg, + const struct phy_config *phy_cfg); + void (*power_off)(struct phy *phy); + void (*clk_enable)(struct phy *phy); + void (*clk_disable)(struct phy *phy); + unsigned long (*clk_recalc_rate)(struct phy *phy, + unsigned long parent_rate); + long (*clk_round_rate)(struct phy *phy, unsigned long rate); + int (*clk_set_rate)(struct phy *phy, unsigned long rate, + unsigned long parent_rate); +}; + +enum inno_hdmi_phy_type { + INNO_HDMI_PHY_RK3328, +}; + +struct inno_hdmi_phy_data { + enum inno_hdmi_phy_type phy_type; + const struct inno_hdmi_phy_plat_ops *plat_ops; + const struct phy_config *phy_cfg_table; +}; + +struct inno_hdmi_phy { + struct udevice *dev; + ofnode node; + void *regs; + + struct clk refoclk; + struct clk sysclk; + unsigned long tmdsclock; + unsigned long pixclock; + u32 bus_width; + struct phy_config *phy_cfg; + const struct inno_hdmi_phy_data *data; +}; + +static const struct pre_pll_config pre_pll_cfg_table[] = { + { 25175000, 25175000, 3, 125, 3, 1, 1, 1, 3, 3, 4, 0, 0xe00000}, + { 25175000, 31468750, 1, 41, 0, 3, 3, 1, 3, 3, 4, 0, 0xf5554f}, + { 27000000, 27000000, 1, 36, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 27000000, 33750000, 1, 45, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 31500000, 31500000, 1, 42, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 31500000, 39375000, 1, 105, 1, 3, 3, 10, 0, 3, 4, 0, 0x0}, + { 33750000, 33750000, 1, 45, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 33750000, 42187500, 1, 169, 2, 3, 3, 15, 0, 3, 4, 0, 0x0}, + { 35500000, 35500000, 1, 71, 2, 2, 2, 6, 0, 3, 4, 0, 0x0}, + { 35500000, 44375000, 1, 74, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + { 36000000, 36000000, 1, 36, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 36000000, 45000000, 1, 45, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + { 40000000, 40000000, 1, 40, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 40000000, 50000000, 1, 50, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + { 49500000, 49500000, 1, 66, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 49500000, 61875000, 1, 165, 1, 3, 3, 10, 0, 3, 4, 0, 0x0}, + { 50000000, 50000000, 1, 50, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 50000000, 62500000, 1, 125, 2, 2, 2, 15, 0, 2, 2, 0, 0x0}, + { 54000000, 54000000, 1, 36, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + { 54000000, 67500000, 1, 45, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + { 56250000, 56250000, 1, 75, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 56250000, 70312500, 1, 117, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + { 59341000, 59341000, 1, 118, 2, 2, 2, 6, 0, 3, 4, 0, 0xae978d}, + { 59341000, 74176250, 2, 148, 2, 1, 1, 15, 0, 1, 1, 0, 0x5a3d70}, + { 59400000, 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0, 0x0}, + { 59400000, 74250000, 1, 99, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 65000000, 65000000, 1, 65, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 65000000, 81250000, 3, 325, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 68250000, 68250000, 1, 91, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 68250000, 85312500, 1, 142, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + { 71000000, 71000000, 1, 71, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 71000000, 88750000, 3, 355, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 72000000, 72000000, 1, 36, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + { 72000000, 90000000, 1, 60, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + { 73250000, 73250000, 3, 293, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 73250000, 91562500, 1, 61, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + { 74176000, 74176000, 1, 37, 2, 0, 0, 1, 1, 2, 2, 0, 0x16872b}, + { 74176000, 92720000, 2, 185, 2, 1, 1, 15, 0, 1, 1, 0, 0x70a3d7}, + { 74250000, 74250000, 1, 99, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 74250000, 92812500, 4, 495, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 75000000, 75000000, 1, 50, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + { 75000000, 93750000, 1, 125, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 78750000, 78750000, 1, 105, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 78750000, 98437500, 1, 164, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + { 79500000, 79500000, 1, 53, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + { 79500000, 99375000, 1, 199, 2, 2, 2, 15, 0, 2, 2, 0, 0x0}, + { 83500000, 83500000, 2, 167, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + { 83500000, 104375000, 1, 104, 2, 1, 1, 15, 0, 1, 1, 0, 0x600000}, + { 85500000, 85500000, 1, 57, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + { 85500000, 106875000, 1, 178, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + { 85750000, 85750000, 3, 343, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 85750000, 107187500, 1, 143, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + { 88750000, 88750000, 3, 355, 0, 3, 3, 1, 2, 3, 4, 0, 0x0}, + { 88750000, 110937500, 1, 110, 2, 1, 1, 15, 0, 1, 1, 0, 0xf00000}, + { 94500000, 94500000, 1, 63, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + { 94500000, 118125000, 1, 197, 3, 1, 1, 25, 0, 1, 1, 0, 0x0}, + {101000000, 101000000, 1, 101, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {101000000, 126250000, 1, 42, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {102250000, 102250000, 4, 409, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {102250000, 127812500, 1, 128, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + {106500000, 106500000, 1, 71, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {106500000, 133125000, 1, 133, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + {108000000, 108000000, 1, 36, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {108000000, 135000000, 1, 45, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {115500000, 115500000, 1, 77, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {115500000, 144375000, 1, 48, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {117500000, 117500000, 2, 235, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {117500000, 146875000, 1, 49, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {119000000, 119000000, 1, 119, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {119000000, 148750000, 3, 148, 0, 1, 1, 1, 3, 1, 1, 0, 0xc00000}, + {121750000, 121750000, 4, 487, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {121750000, 152187500, 1, 203, 0, 3, 3, 1, 3, 3, 4, 0, 0x0}, + {122500000, 122500000, 2, 245, 2, 1, 1, 1, 1, 3, 4, 0, 0x0}, + {122500000, 153125000, 1, 51, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {135000000, 135000000, 1, 45, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {135000000, 168750000, 1, 169, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + {136750000, 136750000, 1, 68, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000}, + {136750000, 170937500, 1, 113, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f}, + {140250000, 140250000, 2, 187, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {140250000, 175312500, 1, 117, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {146250000, 146250000, 2, 195, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {146250000, 182812500, 1, 61, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {148250000, 148250000, 3, 222, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000}, + {148250000, 185312500, 1, 123, 0, 2, 2, 1, 3, 2, 2, 0, 0x8aaab0}, + {148352000, 148352000, 2, 148, 2, 0, 0, 1, 1, 2, 2, 0, 0x5a1cac}, + {148352000, 185440000, 3, 185, 0, 1, 1, 1, 3, 1, 1, 0, 0x70a3d7}, + {148500000, 148500000, 1, 99, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {148500000, 185625000, 4, 495, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {154000000, 154000000, 1, 77, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + {154000000, 192500000, 1, 64, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {156000000, 156000000, 1, 52, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {156000000, 195000000, 1, 65, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {156750000, 156750000, 2, 209, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {156750000, 195937500, 1, 196, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + {157000000, 157000000, 2, 157, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + {157000000, 196250000, 1, 131, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {157500000, 157500000, 1, 105, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {157500000, 196875000, 1, 197, 2, 1, 1, 15, 0, 1, 1, 0, 0x0}, + {162000000, 162000000, 1, 54, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {162000000, 202500000, 2, 135, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {175500000, 175500000, 1, 117, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {175500000, 219375000, 1, 73, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {179500000, 179500000, 3, 359, 0, 2, 2, 1, 0, 3, 4, 0, 0x0}, + {179500000, 224375000, 1, 75, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {182750000, 182750000, 1, 91, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000}, + {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0}, + {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0}, + {187000000, 187000000, 2, 187, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + {187000000, 233750000, 1, 39, 0, 0, 0, 1, 3, 0, 0, 1, 0x0}, + {187250000, 187250000, 3, 280, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000}, + {187250000, 234062500, 1, 156, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0}, + {189000000, 189000000, 1, 63, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {189000000, 236250000, 1, 79, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {193250000, 193250000, 3, 289, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000}, + {193250000, 241562500, 1, 161, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0}, + {202500000, 202500000, 2, 135, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {202500000, 253125000, 1, 169, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {204750000, 204750000, 4, 273, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {204750000, 255937500, 1, 171, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {208000000, 208000000, 1, 104, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + {208000000, 260000000, 1, 173, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {214750000, 214750000, 1, 107, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000}, + {214750000, 268437500, 1, 178, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f}, + {218250000, 218250000, 4, 291, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {218250000, 272812500, 1, 91, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {229500000, 229500000, 2, 153, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {229500000, 286875000, 1, 191, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {234000000, 234000000, 1, 39, 0, 0, 0, 1, 0, 1, 1, 0, 0x0}, + {234000000, 292500000, 1, 195, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {241500000, 241500000, 2, 161, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {241500000, 301875000, 1, 201, 0, 2, 2, 1, 3, 2, 2, 0, 0x0}, + {245250000, 245250000, 4, 327, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {245250000, 306562500, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0}, + {245500000, 245500000, 4, 491, 2, 0, 0, 1, 1, 2, 2, 0, 0x0}, + {245500000, 306875000, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0}, + {261000000, 261000000, 1, 87, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {261000000, 326250000, 1, 109, 0, 1, 1, 1, 3, 1, 1, 0, 0x0}, + {268250000, 268250000, 9, 402, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000}, + {268250000, 335312500, 1, 111, 0, 1, 1, 1, 3, 1, 1, 0, 0xc5554f}, + {268500000, 268500000, 2, 179, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {268500000, 335625000, 1, 56, 0, 0, 0, 1, 3, 0, 0, 1, 0x0}, + {281250000, 281250000, 4, 375, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {281250000, 351562500, 1, 117, 0, 3, 1, 1, 3, 1, 1, 0, 0x0}, + {288000000, 288000000, 1, 48, 0, 0, 0, 1, 0, 1, 1, 0, 0x0}, + {288000000, 360000000, 1, 60, 0, 2, 0, 1, 3, 0, 0, 1, 0x0}, + {296703000, 296703000, 1, 49, 0, 0, 0, 1, 0, 1, 1, 0, 0x7353f7}, + {296703000, 370878750, 1, 123, 0, 3, 1, 1, 3, 1, 1, 0, 0xa051eb}, + {297000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {297000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 1, 1, 0, 0x0}, + {312250000, 312250000, 9, 468, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000}, + {312250000, 390312500, 1, 130, 0, 3, 1, 1, 3, 1, 1, 0, 0x1aaab0}, + {317000000, 317000000, 3, 317, 0, 1, 1, 1, 0, 2, 2, 0, 0x0}, + {317000000, 396250000, 1, 66, 0, 2, 0, 1, 3, 0, 0, 1, 0x0}, + {319750000, 319750000, 3, 159, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000}, + {319750000, 399687500, 3, 199, 0, 2, 0, 1, 3, 0, 0, 1, 0xd80000}, + {333250000, 333250000, 9, 499, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000}, + {333250000, 416562500, 1, 138, 0, 3, 1, 1, 3, 1, 1, 0, 0xdaaab0}, + {348500000, 348500000, 9, 522, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000}, + {348500000, 435625000, 1, 145, 0, 3, 1, 1, 3, 1, 1, 0, 0x35554f}, + {356500000, 356500000, 9, 534, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000}, + {356500000, 445625000, 1, 148, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0}, + {380500000, 380500000, 9, 570, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000}, + {380500000, 475625000, 1, 158, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0}, + {443250000, 443250000, 1, 73, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000}, + {443250000, 554062500, 1, 92, 0, 2, 0, 1, 3, 0, 0, 1, 0x580000}, + {505250000, 505250000, 9, 757, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000}, + {552750000, 552750000, 3, 276, 0, 2, 0, 1, 0, 1, 1, 0, 0x600000}, + {593407000, 296703500, 3, 296, 0, 1, 1, 1, 0, 1, 1, 0, 0xb41893}, + {593407000, 370879375, 4, 494, 0, 3, 1, 1, 3, 0, 0, 1, 0x817e4a}, + {593407000, 593407000, 3, 296, 0, 2, 0, 1, 0, 1, 1, 0, 0xb41893}, + {594000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 1, 1, 0, 0x0}, + {594000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 0, 0, 1, 0x0}, + {594000000, 594000000, 1, 99, 0, 2, 0, 1, 0, 1, 1, 0, 0x0}, + { /* sentinel */ } +}; + +static const struct post_pll_config post_pll_cfg_table[] = { + {33750000, 1, 40, 8, 1}, + {33750000, 1, 80, 8, 2}, + {74250000, 1, 40, 8, 1}, + {74250000, 18, 80, 8, 2}, + {148500000, 2, 40, 4, 3}, + {297000000, 4, 40, 2, 3}, + {594000000, 8, 40, 1, 3}, + { /* sentinel */ } +}; + +/* phy tuning values for an undocumented set of registers */ +static const struct phy_config rk3328_phy_cfg[] = { + { 165000000, { + 0x07, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { + 340000000, { + 0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x3f, 0xac, 0xcc, 0xcd, 0xdd, + }, + }, { + 594000000, { + 0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { /* sentinel */ }, +}; + +static inline void inno_write(struct inno_hdmi_phy *inno, u32 reg, u8 val) +{ + writel(val, inno->regs + (reg * 4)); +} + +static inline u8 inno_read(struct inno_hdmi_phy *inno, u32 reg) +{ + u32 val; + + val = readl(inno->regs + (reg * 4)); + + return val; +} + +static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg, + u8 mask, u8 val) +{ + u32 tmp, orig; + + orig = inno_read(inno, reg); + tmp = orig & ~mask; + tmp |= val & mask; + inno_write(inno, reg, tmp); +} + +#define inno_poll(reg, val, cond, sleep_us, timeout_us) \ + readl_poll_sleep_timeout((reg) * 4, val, cond, sleep_us, timeout_us) + +static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno, + unsigned long rate) +{ + int bus_width = inno->bus_width; + + switch (bus_width) { + case 4: + case 5: + case 6: + case 10: + case 12: + case 16: + return (u64)rate * bus_width / 8; + default: + return rate; + } +} + +static +unsigned long inno_hdmi_phy_rk3328_clk_recalc_rate(struct phy *phy, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + unsigned long frac; + u8 nd, no_a, no_b, no_d; + u64 vco; + u16 nf; + + nd = inno_read(inno, 0xa1) & RK3328_PRE_PLL_PRE_DIV_MASK; + nf = ((inno_read(inno, 0xa2) & RK3328_PRE_PLL_FB_DIV_11_8_MASK) << 8); + nf |= inno_read(inno, 0xa3); + vco = parent_rate * nf; + + if (!(inno_read(inno, 0xa2) & RK3328_PRE_PLL_FRAC_DIV_DISABLE)) { + frac = inno_read(inno, 0xd3) | + (inno_read(inno, 0xd2) << 8) | + (inno_read(inno, 0xd1) << 16); + vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24)); + } + + if (inno_read(inno, 0xa0) & RK3328_PCLK_VCO_DIV_5_MASK) { + do_div(vco, nd * 5); + } else { + no_a = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_A_MASK; + no_b = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_B_MASK; + no_b >>= RK3328_PRE_PLL_PCLK_DIV_B_SHIFT; + no_b += 2; + no_d = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_D_MASK; + + do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2)); + } + + inno->pixclock = DIV_ROUND_CLOSEST((unsigned long)vco, 1000) * 1000; + + dev_info(phy->dev, "rate %lu vco %llu\n", inno->pixclock, vco); + + return inno->pixclock; +} + +static long inno_hdmi_phy_rk3328_clk_round_rate(struct phy *phy, + unsigned long rate) +{ + const struct pre_pll_config *cfg = pre_pll_cfg_table; + + rate = (rate / 1000) * 1000; + + for (; cfg->pixclock != 0; cfg++) + if (cfg->pixclock == rate) + break; + + if (cfg->pixclock == 0) + return -EINVAL; + + return cfg->pixclock; +} + +static const +struct pre_pll_config *inno_hdmi_phy_get_pre_pll_cfg(struct inno_hdmi_phy *inno, + unsigned long rate) +{ + const struct pre_pll_config *cfg = pre_pll_cfg_table; + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); + + for (; cfg->pixclock != 0; cfg++) + if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclock) + break; + + if (cfg->pixclock == 0) + return ERR_PTR(-EINVAL); + + return cfg; +} + +static int +inno_hdmi_phy_rk3328_clk_set_rate(struct phy *phy, + unsigned long rate, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); + const struct pre_pll_config *cfg; + u32 val; + int ret; + + dev_info(phy->dev, "rate %lu tmdsclk %lu\n", rate, tmdsclock); + + if (inno->pixclock == rate && inno->tmdsclock == tmdsclock) + return 0; + + cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate); + if (IS_ERR(cfg)) + return PTR_ERR(cfg); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, + RK3328_PRE_PLL_POWER_DOWN); + + /* Configure pre-pll */ + inno_update_bits(inno, 0xa0, RK3328_PCLK_VCO_DIV_5_MASK, + RK3328_PCLK_VCO_DIV_5(cfg->vco_div_5_en)); + inno_write(inno, 0xa1, RK3328_PRE_PLL_PRE_DIV(cfg->prediv)); + + val = RK3328_SPREAD_SPECTRUM_MOD_DISABLE; + if (!cfg->fracdiv) + val |= RK3328_PRE_PLL_FRAC_DIV_DISABLE; + inno_write(inno, 0xa2, RK3328_PRE_PLL_FB_DIV_11_8(cfg->fbdiv) | val); + inno_write(inno, 0xa3, RK3328_PRE_PLL_FB_DIV_7_0(cfg->fbdiv)); + inno_write(inno, 0xa5, RK3328_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a) | + RK3328_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b)); + inno_write(inno, 0xa6, RK3328_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) | + RK3328_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d)); + inno_write(inno, 0xa4, RK3328_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) | + RK3328_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) | + RK3328_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b)); + inno_write(inno, 0xd3, RK3328_PRE_PLL_FRAC_DIV_7_0(cfg->fracdiv)); + inno_write(inno, 0xd2, RK3328_PRE_PLL_FRAC_DIV_15_8(cfg->fracdiv)); + inno_write(inno, 0xd1, RK3328_PRE_PLL_FRAC_DIV_23_16(cfg->fracdiv)); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0); + + /* Wait for Pre-PLL lock */ + ret = inno_poll(0xa9, val, val & RK3328_PRE_PLL_LOCK_STATUS, + 1000, 10000); + if (ret) { + dev_err(phy->dev, "Pre-PLL locking failed\n"); + return ret; + } + + inno->pixclock = rate; + inno->tmdsclock = tmdsclock; + + return 0; +} + +static void inno_hdmi_phy_rk3328_clk_enable(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0); +} + +static void inno_hdmi_phy_rk3328_clk_disable(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, + RK3328_PRE_PLL_POWER_DOWN); +} + +static int +inno_hdmi_phy_rk3328_power_on(struct phy *phy, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + int ret; + u32 v; + + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0); + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, + RK3328_POST_PLL_POWER_DOWN); + + inno_write(inno, 0xac, RK3328_POST_PLL_FB_DIV_7_0(cfg->fbdiv)); + if (cfg->postdiv == 1) { + inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) | + RK3328_POST_PLL_PRE_DIV(cfg->prediv)); + inno_write(inno, 0xaa, RK3328_POST_PLL_REFCLK_SEL_TMDS | + RK3328_POST_PLL_POWER_DOWN); + } else { + v = (cfg->postdiv / 2) - 1; + v &= RK3328_POST_PLL_POST_DIV_MASK; + inno_write(inno, 0xad, v); + inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) | + RK3328_POST_PLL_PRE_DIV(cfg->prediv)); + inno_write(inno, 0xaa, RK3328_POST_PLL_POST_DIV_ENABLE | + RK3328_POST_PLL_REFCLK_SEL_TMDS | + RK3328_POST_PLL_POWER_DOWN); + } + + for (v = 0; v < 14; v++) + inno_write(inno, 0xb5 + v, phy_cfg->regs[v]); + + /* set ESD detection threshold for TMDS CLK, D2, D1 and D0 */ + for (v = 0; v < 4; v++) + inno_update_bits(inno, 0xc8 + v, RK3328_ESD_DETECT_MASK, + RK3328_ESD_DETECT_340MV); + + if (phy_cfg->tmdsclock > 340000000) { + /* Set termination resistor to 100ohm */ + v = clk_get_rate(&inno->sysclk) / 100000; + inno_write(inno, 0xc5, RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(v) + | RK3328_BYPASS_TERM_RESISTOR_CALIB); + inno_write(inno, 0xc6, RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(v)); + inno_write(inno, 0xc7, RK3328_TERM_RESISTOR_100); + inno_update_bits(inno, 0xc5, + RK3328_BYPASS_TERM_RESISTOR_CALIB, 0); + } else { + inno_write(inno, 0xc5, RK3328_BYPASS_TERM_RESISTOR_CALIB); + + /* clk termination resistor is 50ohm (parallel resistors) */ + if (phy_cfg->tmdsclock > 165000000) + inno_update_bits(inno, 0xc8, + RK3328_TMDS_TERM_RESIST_MASK, + RK3328_TMDS_TERM_RESIST_75 | + RK3328_TMDS_TERM_RESIST_150); + + /* data termination resistor for D2, D1 and D0 is 150ohm */ + for (v = 0; v < 3; v++) + inno_update_bits(inno, 0xc9 + v, + RK3328_TMDS_TERM_RESIST_MASK, + RK3328_TMDS_TERM_RESIST_150); + } + + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, 0); + inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, + RK3328_BANDGAP_ENABLE); + inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, + RK3328_TMDS_DRIVER_ENABLE); + + /* Wait for post PLL lock */ + ret = inno_poll(0xaf, v, v & RK3328_POST_PLL_LOCK_STATUS, + 1000, 10000); + if (ret) { + dev_err(phy->dev, "Post-PLL locking failed\n"); + return ret; + } + + if (phy_cfg->tmdsclock > 340000000) + mdelay(100); + + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN); + + /* Enable PHY IRQ */ + inno_write(inno, 0x05, RK3328_INT_TMDS_CLK(RK3328_INT_VSS_AGND_ESD_DET) + | RK3328_INT_TMDS_D2(RK3328_INT_VSS_AGND_ESD_DET)); + inno_write(inno, 0x07, RK3328_INT_TMDS_D1(RK3328_INT_VSS_AGND_ESD_DET) + | RK3328_INT_TMDS_D0(RK3328_INT_VSS_AGND_ESD_DET)); + + return 0; +} + +static void inno_hdmi_phy_rk3328_power_off(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + + inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, 0); + inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, 0); + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, + RK3328_POST_PLL_POWER_DOWN); + + /* Disable PHY IRQ */ + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); +} + +static void inno_hdmi_phy_rk3328_init(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops; + + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ + inno_write(inno, 0x01, RK3328_BYPASS_RXSENSE_EN | + RK3328_BYPASS_POWERON_EN | + RK3328_BYPASS_PLLPD_EN); + inno_write(inno, 0x02, RK3328_INT_POL_HIGH | RK3328_BYPASS_PDATA_EN | + RK3328_PDATA_EN); + + /* Disable phy irq */ + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); + + if (plat_ops->clk_recalc_rate) + plat_ops->clk_recalc_rate(phy, clk_get_rate(&inno->refoclk)); + + if (plat_ops->clk_round_rate) + plat_ops->clk_round_rate(phy, inno->pixclock); +} + +static const struct inno_hdmi_phy_plat_ops rk3328_hdmi_phy_plat_ops = { + .init = inno_hdmi_phy_rk3328_init, + .power_on = inno_hdmi_phy_rk3328_power_on, + .power_off = inno_hdmi_phy_rk3328_power_off, + .clk_enable = inno_hdmi_phy_rk3328_clk_enable, + .clk_disable = inno_hdmi_phy_rk3328_clk_disable, + .clk_recalc_rate = inno_hdmi_phy_rk3328_clk_recalc_rate, + .clk_round_rate = inno_hdmi_phy_rk3328_clk_round_rate, + .clk_set_rate = inno_hdmi_phy_rk3328_clk_set_rate, +}; + +static int inno_hdmi_phy_power_on(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + const struct post_pll_config *cfg = post_pll_cfg_table; + const struct phy_config *phy_cfg = inno->data->phy_cfg_table; + u32 tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, inno->pixclock); + const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops; + int ret; + + if (!tmdsclock) { + dev_err(phy->dev, "TMDS clock is zero!\n"); + return -EINVAL; + } + + if (!plat_ops->power_on) + return -EINVAL; + + dev_info(phy->dev, "TMDS clock = %d\n", tmdsclock); + + for (; cfg->tmdsclock != ~0UL; cfg++) + if (tmdsclock <= cfg->tmdsclock) + break; + + for (; phy_cfg->tmdsclock != ~0UL; phy_cfg++) + if (tmdsclock <= phy_cfg->tmdsclock) + break; + + if (cfg->tmdsclock == 0 || phy_cfg->tmdsclock == 0) + return -EINVAL; + + if (plat_ops->clk_set_rate) { + ret = plat_ops->clk_set_rate(phy, inno->pixclock, 24000000); + if (ret) + return ret; + } + + if (plat_ops->clk_enable) + plat_ops->clk_enable(phy); + + if (plat_ops->power_on) { + ret = plat_ops->power_on(phy, cfg, phy_cfg); + if (ret) { + if (plat_ops->clk_disable) + plat_ops->clk_disable(phy); + return ret; + } + } + + return 0; +} + +static int inno_hdmi_phy_power_off(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops; + + if (!plat_ops->power_off) + return -EINVAL; + + plat_ops->power_off(phy); + + if (plat_ops->clk_disable) + plat_ops->clk_disable(phy); + + inno->tmdsclock = 0; + + return 0; +} + +static int inno_hdmi_phy_init(struct phy *phy) +{ + struct inno_hdmi_phy *inno = dev_get_priv(phy->dev); + + if (inno->data->plat_ops->init) + inno->data->plat_ops->init(phy); + + return 0; +} + +static struct phy_ops inno_hdmi_phy_ops = { + .init = inno_hdmi_phy_init, + .power_on = inno_hdmi_phy_power_on, + .power_off = inno_hdmi_phy_power_off, +}; + +static int inno_hdmi_phy_probe(struct udevice *dev) +{ + struct inno_hdmi_phy *inno = dev_get_priv(dev); + int ret; + + inno->regs = dev_read_addr_ptr(dev); + if (!inno->regs) + return -ENOMEM; + + inno->data = (const struct inno_hdmi_phy_data *)dev_get_driver_data(dev); + if (!inno->data) + return -EINVAL; + + inno->bus_width = 8; + + ret = clk_get_by_name(dev, "refoclk", &inno->refoclk); + if (ret) { + dev_err(dev, "failed to get the refoclk (ret=%d)\n", ret); + return ret; + } + + ret = clk_get_by_name(dev, "sysclk", &inno->sysclk); + if (ret) { + dev_err(dev, "failed to get the sysclk (ret=%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct inno_hdmi_phy_data rk3328_inno_hdmi_phy_drv_data = { + .phy_type = INNO_HDMI_PHY_RK3328, + .plat_ops = &rk3328_hdmi_phy_plat_ops, + .phy_cfg_table = rk3328_phy_cfg, +}; + +static const struct udevice_id inno_hdmi_phy_ids[] = { + { + .compatible = "rockchip,rk3328-hdmi-phy", + .data = (ulong)&rk3328_inno_hdmi_phy_drv_data, + }, + { /* sentile */ } +}; + +U_BOOT_DRIVER(inno_hdmi_phy) = { + .name = "inno_hdmi_phy", + .id = UCLASS_PHY, + .of_match = inno_hdmi_phy_ids, + .ops = &inno_hdmi_phy_ops, + .probe = inno_hdmi_phy_probe, + .priv_auto = sizeof(struct inno_hdmi_phy), +}; -- cgit v1.3.1 From f3ea872970d603d4c678320d2d6e445fb807278f Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:49 +0530 Subject: video: rockchip: Add rk3328 hdmi support Add Rockchip RK3328 HDMI Out driver. Signed-off-by: Jagan Teki --- drivers/video/rockchip/Makefile | 1 + drivers/video/rockchip/rk3328_hdmi.c | 126 +++++++++++++++++++++++++++++++++++ drivers/video/rockchip/rk_hdmi.h | 3 + 3 files changed, 130 insertions(+) create mode 100644 drivers/video/rockchip/rk3328_hdmi.c (limited to 'drivers') diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile index 8128289cc82..4991303c737 100644 --- a/drivers/video/rockchip/Makefile +++ b/drivers/video/rockchip/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_ROCKCHIP_RK3399) += rk3399_vop.o obj-$(CONFIG_DISPLAY_ROCKCHIP_EDP) += rk_edp.o obj-$(CONFIG_DISPLAY_ROCKCHIP_LVDS) += rk_lvds.o obj-hdmi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_hdmi.o +obj-hdmi-$(CONFIG_ROCKCHIP_RK3328) += rk3328_hdmi.o obj-hdmi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_hdmi.o obj-$(CONFIG_DISPLAY_ROCKCHIP_HDMI) += rk_hdmi.o $(obj-hdmi-y) obj-mipi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_mipi.o diff --git a/drivers/video/rockchip/rk3328_hdmi.c b/drivers/video/rockchip/rk3328_hdmi.c new file mode 100644 index 00000000000..763669c09be --- /dev/null +++ b/drivers/video/rockchip/rk3328_hdmi.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include "rk_hdmi.h" + +#define RK3328_IO_3V_DOMAIN (7 << (9 + 16)) +#define RK3328_IO_5V_DOMAIN ((7 << 9) | (3 << (9 + 16))) +#define RK3328_IO_DDC_IN_MSK ((3 << 10) | (3 << (10 + 16))) +#define RK3328_IO_CTRL_BY_HDMI ((1 << 13) | (1 << (13 + 16))) + +static int rk3328_hdmi_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + + return dw_hdmi_enable(&priv->hdmi, edid); +} + +static int rk3328_dw_hdmi_phy_cfg(struct dw_hdmi *hdmi, uint pixclock) +{ + struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi); + int ret; + + ret = generic_phy_init(&priv->phy); + if (ret) { + printf("failed to init phy (ret=%d)\n", ret); + return ret; + } + + ret = generic_phy_power_on(&priv->phy); + if (ret) { + printf("failed to power on phy (ret=%d)\n", ret); + return ret; + } + + return 0; +} + +static void rk3328_dw_hdmi_setup_hpd(struct dw_hdmi *hdmi) +{ + struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi); + struct rk3328_grf_regs *grf = priv->grf; + + writel(RK3328_IO_DDC_IN_MSK, &grf->soc_con[2]); + writel(RK3328_IO_CTRL_BY_HDMI, &grf->soc_con[3]); +} + +static void rk3328_dw_hdmi_read_hpd(struct dw_hdmi *hdmi, bool hpd_status) +{ + struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi); + struct rk3328_grf_regs *grf = priv->grf; + + if (hpd_status) + writel(RK3328_IO_5V_DOMAIN, &grf->soc_con[4]); + else + writel(RK3328_IO_3V_DOMAIN, &grf->soc_con[4]); +} + +static const struct dw_hdmi_phy_ops dw_hdmi_rk3328_phy_ops = { + .phy_set = rk3328_dw_hdmi_phy_cfg, + .setup_hpd = rk3328_dw_hdmi_setup_hpd, + .read_hpd = rk3328_dw_hdmi_read_hpd, +}; + +static int rk3328_hdmi_of_to_plat(struct udevice *dev) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct dw_hdmi *hdmi = &priv->hdmi; + + hdmi->i2c_clk_high = 0x71; + hdmi->i2c_clk_low = 0x76; + + rk_hdmi_of_to_plat(dev); + + hdmi->ops = &dw_hdmi_rk3328_phy_ops; + + return 0; +} + +static int rk3328_hdmi_probe(struct udevice *dev) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + int ret; + + ret = generic_phy_get_by_name(dev, "hdmi", &priv->phy); + if (ret) { + printf("failed to get hdmi phy\n"); + return ret; + }; + + ret = rk_hdmi_probe(dev); + if (ret) { + printf("failed to probe rk hdmi\n"); + return ret; + } + + return 0; +} + +static const struct dm_display_ops rk3328_hdmi_ops = { + .read_edid = rk_hdmi_read_edid, + .enable = rk3328_hdmi_enable, +}; + +static const struct udevice_id rk3328_hdmi_ids[] = { + { .compatible = "rockchip,rk3328-dw-hdmi" }, + { } +}; + +U_BOOT_DRIVER(rk3328_hdmi_rockchip) = { + .name = "rk3328_hdmi_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rk3328_hdmi_ids, + .ops = &rk3328_hdmi_ops, + .of_to_plat = rk3328_hdmi_of_to_plat, + .probe = rk3328_hdmi_probe, + .priv_auto = sizeof(struct rk_hdmi_priv), +}; diff --git a/drivers/video/rockchip/rk_hdmi.h b/drivers/video/rockchip/rk_hdmi.h index 200dbaea740..dcfba3d3d7e 100644 --- a/drivers/video/rockchip/rk_hdmi.h +++ b/drivers/video/rockchip/rk_hdmi.h @@ -6,6 +6,8 @@ #ifndef __RK_HDMI_H__ #define __RK_HDMI_H__ +#include + struct rkhdmi_driverdata { /* configuration */ u8 i2c_clk_high; @@ -19,6 +21,7 @@ struct rkhdmi_driverdata { struct rk_hdmi_priv { struct dw_hdmi hdmi; + struct phy phy; void *grf; }; -- cgit v1.3.1 From 804838a496fbb1b53f21529ead0f41fbf6713c7c Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 17 Jan 2024 13:21:50 +0530 Subject: video: rockchip: Add rk3328 vop support Add support for Rockchip RK3328 VOP. Require VOP cleanup before handoff to Linux by writing reset values to WIN registers. Without this Linux VOP trigger page fault as below [ 0.752016] Loading compiled-in X.509 certificates [ 0.787796] inno_hdmi_phy_rk3328_clk_recalc_rate: parent 24000000 [ 0.788391] inno-hdmi-phy ff430000.phy: inno_hdmi_phy_rk3328_clk_recalc_rate rate 148500000 vco 148500000 [ 0.798353] rockchip-drm display-subsystem: bound ff370000.vop (ops vop_component_ops) [ 0.799403] dwhdmi-rockchip ff3c0000.hdmi: supply avdd-0v9 not found, using dummy regulator [ 0.800288] rk_iommu ff373f00.iommu: Enable stall request timed out, status: 0x00004b [ 0.801131] dwhdmi-rockchip ff3c0000.hdmi: supply avdd-1v8 not found, using dummy regulator [ 0.802056] rk_iommu ff373f00.iommu: Disable paging request timed out, status: 0x00004b [ 0.803233] dwhdmi-rockchip ff3c0000.hdmi: Detected HDMI TX controller v2.11a with HDCP (inno_dw_hdmi_phy2) [ 0.805355] dwhdmi-rockchip ff3c0000.hdmi: registered DesignWare HDMI I2C bus driver [ 0.808769] rockchip-drm display-subsystem: bound ff3c0000.hdmi (ops dw_hdmi_rockchip_ops) [ 0.810869] [drm] Initialized rockchip 1.0.0 20140818 for display-subsystem on minor 0 Signed-off-by: Jagan Teki --- drivers/video/rockchip/Makefile | 1 + drivers/video/rockchip/rk3328_vop.c | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 drivers/video/rockchip/rk3328_vop.c (limited to 'drivers') diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile index 4991303c737..f55beceebf1 100644 --- a/drivers/video/rockchip/Makefile +++ b/drivers/video/rockchip/Makefile @@ -6,6 +6,7 @@ ifdef CONFIG_VIDEO_ROCKCHIP obj-y += rk_vop.o obj-$(CONFIG_ROCKCHIP_RK3288) += rk3288_vop.o +obj-$(CONFIG_ROCKCHIP_RK3328) += rk3328_vop.o obj-$(CONFIG_ROCKCHIP_RK3399) += rk3399_vop.o obj-$(CONFIG_DISPLAY_ROCKCHIP_EDP) += rk_edp.o obj-$(CONFIG_DISPLAY_ROCKCHIP_LVDS) += rk_lvds.o diff --git a/drivers/video/rockchip/rk3328_vop.c b/drivers/video/rockchip/rk3328_vop.c new file mode 100644 index 00000000000..55233f19eeb --- /dev/null +++ b/drivers/video/rockchip/rk3328_vop.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd. + */ + +#include +#include +#include +#include "rk_vop.h" + +DECLARE_GLOBAL_DATA_PTR; + +static void rk3328_set_pin_polarity(struct udevice *dev, + enum vop_modes mode, u32 polarity) +{ + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; + + switch (mode) { + case VOP_MODE_HDMI: + clrsetbits_le32(®s->dsp_ctrl1, + M_RK3399_DSP_HDMI_POL, + V_RK3399_DSP_HDMI_POL(polarity)); + break; + default: + debug("%s: unsupported output mode %x\n", __func__, mode); + } +} + +static int rk3328_vop_probe(struct udevice *dev) +{ + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + return rk_vop_probe(dev); +} + +static int rk3328_vop_remove(struct udevice *dev) +{ + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; + struct rk3288_vop *win_regs = priv->regs + priv->win_offset; + + /* FIXME: Explicit disabling of WIN0 is needed to avoid iommu + * page-fault in Linux, better handling of iommu-address in + * Linux might drop this. + */ + clrbits_le32(&win_regs->win0_ctrl0, M_WIN0_EN); + writel(0x01, ®s->reg_cfg_done); + + return 0; +} + +struct rkvop_driverdata rk3328_driverdata = { + .dsp_offset = 0x490, + .win_offset = 0xd0, + .features = VOP_FEATURE_OUTPUT_10BIT, + .set_pin_polarity = rk3328_set_pin_polarity, +}; + +static const struct udevice_id rk3328_vop_ids[] = { + { + .compatible = "rockchip,rk3328-vop", + .data = (ulong)&rk3328_driverdata + }, + { /* sentile */ } +}; + +static const struct video_ops rk3328_vop_ops = { +}; + +U_BOOT_DRIVER(rk3328_vop) = { + .name = "rk3328_vop", + .id = UCLASS_VIDEO, + .of_match = rk3328_vop_ids, + .ops = &rk3328_vop_ops, + .bind = rk_vop_bind, + .probe = rk3328_vop_probe, + .remove = rk3328_vop_remove, + .priv_auto = sizeof(struct rk_vop_priv), + .flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE, +}; -- cgit v1.3.1 From ddc75bc020b37d5cb45ba5588e27e4f4c60ce805 Mon Sep 17 00:00:00 2001 From: Khem Raj Date: Sat, 27 Jan 2024 14:54:59 -0800 Subject: video: dw_hdmi: Fix compiler warnings with gcc-14 GCC-14 find more warnings like "make pointer from integer without a cast" fix them by adding a type cast. Signed-off-by: Khem Raj Cc: Anatolij Gustschin Cc: Tom Rini --- drivers/video/dw_hdmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index ab4811cfc76..c217af97878 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -78,10 +78,10 @@ static void dw_hdmi_write(struct dw_hdmi *hdmi, u8 val, int offset) { switch (hdmi->reg_io_width) { case 1: - writeb(val, hdmi->ioaddr + offset); + writeb(val, (void *)(hdmi->ioaddr + offset)); break; case 4: - writel(val, hdmi->ioaddr + (offset << 2)); + writel(val, (void *)(hdmi->ioaddr + (offset << 2))); break; default: debug("reg_io_width has unsupported width!\n"); @@ -93,9 +93,9 @@ static u8 dw_hdmi_read(struct dw_hdmi *hdmi, int offset) { switch (hdmi->reg_io_width) { case 1: - return readb(hdmi->ioaddr + offset); + return readb((void *)(hdmi->ioaddr + offset)); case 4: - return readl(hdmi->ioaddr + (offset << 2)); + return readl((void *)(hdmi->ioaddr + (offset << 2))); default: debug("reg_io_width has unsupported width!\n"); break; -- cgit v1.3.1 From e88d02695d9023e927127069628436f534856d0e Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:16 +0200 Subject: video: tegra20: dc: diverge DC per-SOC Diverge DC driver setup to better fit each of supported generations of Tegra SOC. Tested-by: Agneli # Toshiba AC100 T20 Tested-by: Robert Eckelmann # ASUS TF101 Tested-by: Andreas Westman Dorcsak # ASUS Grouper E1565 Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel --- arch/arm/dts/tegra114-u-boot.dtsi | 13 ++++ arch/arm/dts/tegra114.dtsi | 4 +- arch/arm/dts/tegra30-u-boot.dtsi | 4 + arch/arm/dts/tegra30.dtsi | 2 +- arch/arm/include/asm/arch-tegra114/display.h | 28 +++++++ arch/arm/include/asm/arch-tegra114/pwm.h | 13 ++++ drivers/video/tegra20/tegra-dc.c | 107 ++++++++++++++++++--------- 7 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 arch/arm/include/asm/arch-tegra114/display.h create mode 100644 arch/arm/include/asm/arch-tegra114/pwm.h (limited to 'drivers') diff --git a/arch/arm/dts/tegra114-u-boot.dtsi b/arch/arm/dts/tegra114-u-boot.dtsi index 7c119725528..6a02714a258 100644 --- a/arch/arm/dts/tegra114-u-boot.dtsi +++ b/arch/arm/dts/tegra114-u-boot.dtsi @@ -1,3 +1,16 @@ #include #include "tegra-u-boot.dtsi" + +/ { + host1x@50000000 { + bootph-all; + dc@54200000 { + bootph-all; + }; + + dc@54240000 { + bootph-all; + }; + }; +}; diff --git a/arch/arm/dts/tegra114.dtsi b/arch/arm/dts/tegra114.dtsi index 68ee7f31656..250d692f6bf 100644 --- a/arch/arm/dts/tegra114.dtsi +++ b/arch/arm/dts/tegra114.dtsi @@ -42,7 +42,7 @@ }; dc@54200000 { - compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc"; + compatible = "nvidia,tegra114-dc"; reg = <0x54200000 0x00040000>; interrupts = ; clocks = <&tegra_car TEGRA114_CLK_DISP1>, @@ -61,7 +61,7 @@ }; dc@54240000 { - compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc"; + compatible = "nvidia,tegra114-dc"; reg = <0x54240000 0x00040000>; interrupts = ; clocks = <&tegra_car TEGRA114_CLK_DISP2>, diff --git a/arch/arm/dts/tegra30-u-boot.dtsi b/arch/arm/dts/tegra30-u-boot.dtsi index 3038227dbed..6a02714a258 100644 --- a/arch/arm/dts/tegra30-u-boot.dtsi +++ b/arch/arm/dts/tegra30-u-boot.dtsi @@ -8,5 +8,9 @@ dc@54200000 { bootph-all; }; + + dc@54240000 { + bootph-all; + }; }; }; diff --git a/arch/arm/dts/tegra30.dtsi b/arch/arm/dts/tegra30.dtsi index f198bc0edbe..1177e2ab1f4 100644 --- a/arch/arm/dts/tegra30.dtsi +++ b/arch/arm/dts/tegra30.dtsi @@ -158,7 +158,7 @@ }; dc@54200000 { - compatible = "nvidia,tegra30-dc", "nvidia,tegra20-dc"; + compatible = "nvidia,tegra30-dc"; reg = <0x54200000 0x00040000>; interrupts = ; clocks = <&tegra_car TEGRA30_CLK_DISP1>, diff --git a/arch/arm/include/asm/arch-tegra114/display.h b/arch/arm/include/asm/arch-tegra114/display.h new file mode 100644 index 00000000000..9411525799d --- /dev/null +++ b/arch/arm/include/asm/arch-tegra114/display.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation + */ + +#ifndef __ASM_ARCH_TEGRA_DISPLAY_H +#define __ASM_ARCH_TEGRA_DISPLAY_H + +#include + +/* This holds information about a window which can be displayed */ +struct disp_ctl_win { + enum win_color_depth_id fmt; /* Color depth/format */ + unsigned int bpp; /* Bits per pixel */ + phys_addr_t phys_addr; /* Physical address in memory */ + unsigned int x; /* Horizontal address offset (bytes) */ + unsigned int y; /* Veritical address offset (bytes) */ + unsigned int w; /* Width of source window */ + unsigned int h; /* Height of source window */ + unsigned int stride; /* Number of bytes per line */ + unsigned int out_x; /* Left edge of output window (col) */ + unsigned int out_y; /* Top edge of output window (row) */ + unsigned int out_w; /* Width of output window in pixels */ + unsigned int out_h; /* Height of output window in pixels */ +}; + +#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/ diff --git a/arch/arm/include/asm/arch-tegra114/pwm.h b/arch/arm/include/asm/arch-tegra114/pwm.h new file mode 100644 index 00000000000..af391518035 --- /dev/null +++ b/arch/arm/include/asm/arch-tegra114/pwm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Tegra pulse width frequency modulator definitions + * + * Copyright (c) 2011 The Chromium OS Authors. + */ + +#ifndef __ASM_ARCH_TEGRA114_PWM_H +#define __ASM_ARCH_TEGRA114_PWM_H + +#include + +#endif /* __ASM_ARCH_TEGRA114_PWM_H */ diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index f53ad463970..7605e77bc1f 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -3,7 +3,6 @@ * Copyright (c) 2011 The Chromium OS Authors. */ -#include #include #include #include @@ -23,10 +22,15 @@ #include #include #include -#include DECLARE_GLOBAL_DATA_PTR; +/* Holder of Tegra per-SOC DC differences */ +struct tegra_dc_soc_info { + bool has_timer; + bool has_rgb; +}; + /* Information about the display controller */ struct tegra_lcd_priv { int width; /* width in pixels */ @@ -35,6 +39,7 @@ struct tegra_lcd_priv { struct display_timing timing; struct udevice *panel; struct dc_ctlr *dc; /* Display controller regmap */ + const struct tegra_dc_soc_info *soc; fdt_addr_t frame_buffer; /* Address of frame buffer */ unsigned pixel_clock; /* Pixel clock in Hz */ int dc_clk[2]; /* Contains clk and its parent */ @@ -43,8 +48,8 @@ struct tegra_lcd_priv { enum { /* Maximum LCD size we support */ - LCD_MAX_WIDTH = 1920, - LCD_MAX_HEIGHT = 1200, + LCD_MAX_WIDTH = 2560, + LCD_MAX_HEIGHT = 1600, LCD_MAX_LOG2_BPP = VIDEO_BPP16, }; @@ -110,9 +115,9 @@ static void update_window(struct tegra_lcd_priv *priv, writel(val, &dc->cmd.state_ctrl); } -static int update_display_mode(struct dc_disp_reg *disp, - struct tegra_lcd_priv *priv) +static int update_display_mode(struct tegra_lcd_priv *priv) { + struct dc_disp_reg *disp = &priv->dc->disp; struct display_timing *dt = &priv->timing; unsigned long val; unsigned long rate; @@ -128,14 +133,16 @@ static int update_display_mode(struct dc_disp_reg *disp, &disp->front_porch); writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active); - val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; - val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; - writel(val, &disp->data_enable_opt); + if (priv->soc->has_rgb) { + val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; + val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; + writel(val, &disp->data_enable_opt); - val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; - val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; - val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; - writel(val, &disp->disp_interface_ctrl); + val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; + val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; + val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; + writel(val, &disp->disp_interface_ctrl); + } /* * The pixel clock divider is in 7.1 format (where the bottom bit @@ -147,7 +154,8 @@ static int update_display_mode(struct dc_disp_reg *disp, div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2; debug("Display clock %lu, divider %lu\n", rate, div); - writel(0x00010001, &disp->shift_clk_opt); + if (priv->soc->has_rgb) + writel(0x00010001, &disp->shift_clk_opt); val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; val |= div << SHIFT_CLK_DIVIDER_SHIFT; @@ -174,6 +182,7 @@ static void basic_init(struct dc_cmd_reg *cmd) writel(val, &cmd->disp_pow_ctrl); val = readl(&cmd->disp_cmd); + val &= ~CTRL_MODE_MASK; val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; writel(val, &cmd->disp_cmd); } @@ -229,8 +238,8 @@ static void rgb_enable(struct dc_com_reg *com) writel(rgb_sel_tab[i], &com->pin_output_sel[i]); } -static int setup_window(struct disp_ctl_win *win, - struct tegra_lcd_priv *priv) +static int setup_window(struct tegra_lcd_priv *priv, + struct disp_ctl_win *win) { if (priv->rotation) { win->x = priv->width * 2; @@ -274,12 +283,11 @@ static int setup_window(struct disp_ctl_win *win, * You should pass in the U-Boot address here, and check the contents of * struct tegra_lcd_priv to see what was actually chosen. * - * @param blob Device tree blob * @param priv Driver's private data * @param default_lcd_base Default address of LCD frame buffer * Return: 0 if ok, -1 on error (unsupported bits per pixel) */ -static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv, +static int tegra_display_probe(struct tegra_lcd_priv *priv, void *default_lcd_base) { struct disp_ctl_win window; @@ -288,7 +296,7 @@ static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv, priv->frame_buffer = (u32)default_lcd_base; /* - * We halve the rate if DISP1 paret is PLLD, since actual parent + * We halve the rate if DISP1 parent is PLLD, since actual parent * is plld_out0 which is PLLD divided by 2. */ if (priv->dc_clk[1] == CLOCK_ID_DISPLAY) @@ -303,13 +311,17 @@ static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv, rate); basic_init(&priv->dc->cmd); - basic_init_timer(&priv->dc->disp); - rgb_enable(&priv->dc->com); + + if (priv->soc->has_timer) + basic_init_timer(&priv->dc->disp); + + if (priv->soc->has_rgb) + rgb_enable(&priv->dc->com); if (priv->pixel_clock) - update_display_mode(&priv->dc->disp, priv); + update_display_mode(priv); - if (setup_window(&window, priv)) + if (setup_window(priv, &window)) return -1; update_window(priv, &window); @@ -322,7 +334,6 @@ static int tegra_lcd_probe(struct udevice *dev) struct video_uc_plat *plat = dev_get_uclass_plat(dev); struct video_priv *uc_priv = dev_get_uclass_priv(dev); struct tegra_lcd_priv *priv = dev_get_priv(dev); - const void *blob = gd->fdt_blob; int ret; /* Initialize the Tegra display controller */ @@ -330,8 +341,8 @@ static int tegra_lcd_probe(struct udevice *dev) funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT); #endif - if (tegra_display_probe(blob, priv, (void *)plat->base)) { - printf("%s: Failed to probe display driver\n", __func__); + if (tegra_display_probe(priv, (void *)plat->base)) { + debug("%s: Failed to probe display driver\n", __func__); return -1; } @@ -383,6 +394,8 @@ static int tegra_lcd_of_to_plat(struct udevice *dev) return -EINVAL; } + priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev); + ret = clock_decode_pair(dev, priv->dc_clk); if (ret < 0) { debug("%s: Cannot decode clocks for '%s' (ret = %d)\n", @@ -464,19 +477,43 @@ static int tegra_lcd_bind(struct udevice *dev) static const struct video_ops tegra_lcd_ops = { }; +static const struct tegra_dc_soc_info tegra20_dc_soc_info = { + .has_timer = true, + .has_rgb = true, +}; + +static const struct tegra_dc_soc_info tegra30_dc_soc_info = { + .has_timer = false, + .has_rgb = true, +}; + +static const struct tegra_dc_soc_info tegra114_dc_soc_info = { + .has_timer = false, + .has_rgb = false, +}; + static const struct udevice_id tegra_lcd_ids[] = { - { .compatible = "nvidia,tegra20-dc" }, - { .compatible = "nvidia,tegra30-dc" }, - { } + { + .compatible = "nvidia,tegra20-dc", + .data = (ulong)&tegra20_dc_soc_info + }, { + .compatible = "nvidia,tegra30-dc", + .data = (ulong)&tegra30_dc_soc_info + }, { + .compatible = "nvidia,tegra114-dc", + .data = (ulong)&tegra114_dc_soc_info + }, { + /* sentinel */ + } }; U_BOOT_DRIVER(tegra_lcd) = { - .name = "tegra_lcd", - .id = UCLASS_VIDEO, - .of_match = tegra_lcd_ids, - .ops = &tegra_lcd_ops, - .bind = tegra_lcd_bind, - .probe = tegra_lcd_probe, + .name = "tegra_lcd", + .id = UCLASS_VIDEO, + .of_match = tegra_lcd_ids, + .ops = &tegra_lcd_ops, + .bind = tegra_lcd_bind, + .probe = tegra_lcd_probe, .of_to_plat = tegra_lcd_of_to_plat, .priv_auto = sizeof(struct tegra_lcd_priv), }; -- cgit v1.3.1 From dc43aa6a79083a909e373d9b5c6bff9d6da0a865 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:17 +0200 Subject: video: tegra20: dc: fix image shift on rotated panels Subtracting 1 from x and y fixes image shifting on rotated panels. Tested-by: Robert Eckelmann # ASUS Grouper E1565 Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 7605e77bc1f..bcc27486238 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -242,8 +242,8 @@ static int setup_window(struct tegra_lcd_priv *priv, struct disp_ctl_win *win) { if (priv->rotation) { - win->x = priv->width * 2; - win->y = priv->height; + win->x = priv->width * 2 - 1; + win->y = priv->height - 1; } else { win->x = 0; win->y = 0; -- cgit v1.3.1 From d5e1eaf97e57625d094a617eb19bd127045cec03 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:18 +0200 Subject: video: tegra20: consolidate DC header Consolidate HD headers and place the result into video/tegra20 since it is used only by devices from this directory. Tested-by: Agneli # Toshiba AC100 T20 Tested-by: Robert Eckelmann # ASUS TF101 Tested-by: Andreas Westman Dorcsak # ASUS Grouper E1565 Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- arch/arm/include/asm/arch-tegra/dc.h | 8 ------ arch/arm/include/asm/arch-tegra114/display.h | 28 ------------------- arch/arm/include/asm/arch-tegra20/display.h | 28 ------------------- arch/arm/include/asm/arch-tegra30/display.h | 28 ------------------- drivers/video/tegra20/tegra-dc.c | 3 +- drivers/video/tegra20/tegra-dc.h | 41 ++++++++++++++++++++++++++++ drivers/video/tegra20/tegra-dsi.c | 2 +- drivers/video/tegra20/tegra-pwm-backlight.c | 3 +- 8 files changed, 46 insertions(+), 95 deletions(-) delete mode 100644 arch/arm/include/asm/arch-tegra114/display.h delete mode 100644 arch/arm/include/asm/arch-tegra20/display.h delete mode 100644 arch/arm/include/asm/arch-tegra30/display.h create mode 100644 drivers/video/tegra20/tegra-dc.h (limited to 'drivers') diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h index 7613d84f221..6444af2993a 100644 --- a/arch/arm/include/asm/arch-tegra/dc.h +++ b/arch/arm/include/asm/arch-tegra/dc.h @@ -569,12 +569,4 @@ enum { #define DC_N_WINDOWS 5 #define DC_REG_SAVE_SPACE (DC_N_WINDOWS + 5) -#define TEGRA_DSI_A "dsi@54300000" -#define TEGRA_DSI_B "dsi@54400000" - -struct tegra_dc_plat { - struct udevice *dev; /* Display controller device */ - struct dc_ctlr *dc; /* Display controller regmap */ -}; - #endif /* __ASM_ARCH_TEGRA_DC_H */ diff --git a/arch/arm/include/asm/arch-tegra114/display.h b/arch/arm/include/asm/arch-tegra114/display.h deleted file mode 100644 index 9411525799d..00000000000 --- a/arch/arm/include/asm/arch-tegra114/display.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * (C) Copyright 2010 - * NVIDIA Corporation - */ - -#ifndef __ASM_ARCH_TEGRA_DISPLAY_H -#define __ASM_ARCH_TEGRA_DISPLAY_H - -#include - -/* This holds information about a window which can be displayed */ -struct disp_ctl_win { - enum win_color_depth_id fmt; /* Color depth/format */ - unsigned int bpp; /* Bits per pixel */ - phys_addr_t phys_addr; /* Physical address in memory */ - unsigned int x; /* Horizontal address offset (bytes) */ - unsigned int y; /* Veritical address offset (bytes) */ - unsigned int w; /* Width of source window */ - unsigned int h; /* Height of source window */ - unsigned int stride; /* Number of bytes per line */ - unsigned int out_x; /* Left edge of output window (col) */ - unsigned int out_y; /* Top edge of output window (row) */ - unsigned int out_w; /* Width of output window in pixels */ - unsigned int out_h; /* Height of output window in pixels */ -}; - -#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/ diff --git a/arch/arm/include/asm/arch-tegra20/display.h b/arch/arm/include/asm/arch-tegra20/display.h deleted file mode 100644 index e7b3cffd466..00000000000 --- a/arch/arm/include/asm/arch-tegra20/display.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * (C) Copyright 2010 - * NVIDIA Corporation - */ - -#ifndef __ASM_ARCH_TEGRA_DISPLAY_H -#define __ASM_ARCH_TEGRA_DISPLAY_H - -#include - -/* This holds information about a window which can be displayed */ -struct disp_ctl_win { - enum win_color_depth_id fmt; /* Color depth/format */ - unsigned bpp; /* Bits per pixel */ - phys_addr_t phys_addr; /* Physical address in memory */ - unsigned x; /* Horizontal address offset (bytes) */ - unsigned y; /* Veritical address offset (bytes) */ - unsigned w; /* Width of source window */ - unsigned h; /* Height of source window */ - unsigned stride; /* Number of bytes per line */ - unsigned out_x; /* Left edge of output window (col) */ - unsigned out_y; /* Top edge of output window (row) */ - unsigned out_w; /* Width of output window in pixels */ - unsigned out_h; /* Height of output window in pixels */ -}; - -#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/ diff --git a/arch/arm/include/asm/arch-tegra30/display.h b/arch/arm/include/asm/arch-tegra30/display.h deleted file mode 100644 index 9411525799d..00000000000 --- a/arch/arm/include/asm/arch-tegra30/display.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * (C) Copyright 2010 - * NVIDIA Corporation - */ - -#ifndef __ASM_ARCH_TEGRA_DISPLAY_H -#define __ASM_ARCH_TEGRA_DISPLAY_H - -#include - -/* This holds information about a window which can be displayed */ -struct disp_ctl_win { - enum win_color_depth_id fmt; /* Color depth/format */ - unsigned int bpp; /* Bits per pixel */ - phys_addr_t phys_addr; /* Physical address in memory */ - unsigned int x; /* Horizontal address offset (bytes) */ - unsigned int y; /* Veritical address offset (bytes) */ - unsigned int w; /* Width of source window */ - unsigned int h; /* Height of source window */ - unsigned int stride; /* Number of bytes per line */ - unsigned int out_x; /* Left edge of output window (col) */ - unsigned int out_y; /* Top edge of output window (row) */ - unsigned int out_w; /* Width of output window in pixels */ - unsigned int out_h; /* Height of output window in pixels */ -}; - -#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/ diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index bcc27486238..5d8874f323a 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -21,7 +21,8 @@ #include #include #include -#include + +#include "tegra-dc.h" DECLARE_GLOBAL_DATA_PTR; diff --git a/drivers/video/tegra20/tegra-dc.h b/drivers/video/tegra20/tegra-dc.h new file mode 100644 index 00000000000..5c05221038c --- /dev/null +++ b/drivers/video/tegra20/tegra-dc.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation + */ + +#ifndef _TEGRA_DC_H +#define _TEGRA_DC_H + +#ifndef __ASSEMBLY__ +#include +#endif + +/* arch-tegra/dc exists only because T124 uses it */ +#include + +#define TEGRA_DSI_A "dsi@54300000" +#define TEGRA_DSI_B "dsi@54400000" + +struct tegra_dc_plat { + struct udevice *dev; /* Display controller device */ + struct dc_ctlr *dc; /* Display controller regmap */ +}; + +/* This holds information about a window which can be displayed */ +struct disp_ctl_win { + enum win_color_depth_id fmt; /* Color depth/format */ + unsigned int bpp; /* Bits per pixel */ + phys_addr_t phys_addr; /* Physical address in memory */ + unsigned int x; /* Horizontal address offset (bytes) */ + unsigned int y; /* Veritical address offset (bytes) */ + unsigned int w; /* Width of source window */ + unsigned int h; /* Height of source window */ + unsigned int stride; /* Number of bytes per line */ + unsigned int out_x; /* Left edge of output window (col) */ + unsigned int out_y; /* Top edge of output window (row) */ + unsigned int out_w; /* Width of output window in pixels */ + unsigned int out_h; /* Height of output window in pixels */ +}; + +#endif /* _TEGRA_DC_H */ diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index a48f9c85d0f..72b91ed26bf 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -20,9 +20,9 @@ #include #include #include -#include #include +#include "tegra-dc.h" #include "mipi-phy.h" struct tegra_dsi_priv { diff --git a/drivers/video/tegra20/tegra-pwm-backlight.c b/drivers/video/tegra20/tegra-pwm-backlight.c index bb677daa8a1..5f93f57fe90 100644 --- a/drivers/video/tegra20/tegra-pwm-backlight.c +++ b/drivers/video/tegra20/tegra-pwm-backlight.c @@ -15,7 +15,8 @@ #include #include -#include + +#include "tegra-dc.h" #define TEGRA_DISPLAY_A_BASE 0x54200000 #define TEGRA_DISPLAY_B_BASE 0x54240000 -- cgit v1.3.1 From b9ef623c1145c3897deef009c860c0576bc6a310 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:19 +0200 Subject: video: tegra20: dc: pass DC id to internal devices Tegra SoC has 2 independent display controllers called DC_A and DC_B, they are handled differently by internal video devices like DSI and HDMI controllers so it is important for last to know which display controller is used to properly set up registers. To achieve this, a pipe field was added to pdata to pass display controller id to internal Tegra SoC devices. Tested-by: Agneli # Toshiba AC100 T20 Tested-by: Robert Eckelmann # ASUS TF101 Tested-by: Andreas Westman Dorcsak # ASUS Grouper E1565 Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel --- drivers/video/tegra20/tegra-dc.c | 6 ++++++ drivers/video/tegra20/tegra-dc.h | 3 +++ 2 files changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 5d8874f323a..0e94e665efc 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -45,6 +45,7 @@ struct tegra_lcd_priv { unsigned pixel_clock; /* Pixel clock in Hz */ int dc_clk[2]; /* Contains clk and its parent */ bool rotation; /* 180 degree panel turn */ + bool pipe; /* DC controller: 0 for A, 1 for B */ }; enum { @@ -406,6 +407,9 @@ static int tegra_lcd_of_to_plat(struct udevice *dev) priv->rotation = dev_read_bool(dev, "nvidia,180-rotation"); + if (!strcmp(dev->name, TEGRA_DC_B)) + priv->pipe = 1; + rgb = fdt_subnode_offset(blob, node, "rgb"); if (rgb < 0) { debug("%s: Cannot find rgb subnode for '%s' (ret=%d)\n", @@ -431,12 +435,14 @@ static int tegra_lcd_of_to_plat(struct udevice *dev) return ret; } + /* Fill the platform data for internal devices */ if (!strcmp(priv->panel->name, TEGRA_DSI_A) || !strcmp(priv->panel->name, TEGRA_DSI_B)) { struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel); dc_plat->dev = dev; dc_plat->dc = priv->dc; + dc_plat->pipe = priv->pipe; } ret = panel_get_display_timing(priv->panel, &priv->timing); diff --git a/drivers/video/tegra20/tegra-dc.h b/drivers/video/tegra20/tegra-dc.h index 5c05221038c..75fc0fa4de4 100644 --- a/drivers/video/tegra20/tegra-dc.h +++ b/drivers/video/tegra20/tegra-dc.h @@ -14,12 +14,15 @@ /* arch-tegra/dc exists only because T124 uses it */ #include +#define TEGRA_DC_A "dc@54200000" +#define TEGRA_DC_B "dc@54240000" #define TEGRA_DSI_A "dsi@54300000" #define TEGRA_DSI_B "dsi@54400000" struct tegra_dc_plat { struct udevice *dev; /* Display controller device */ struct dc_ctlr *dc; /* Display controller regmap */ + bool pipe; /* DC number: 0 for A, 1 for B */ }; /* This holds information about a window which can be displayed */ -- cgit v1.3.1 From 97b6914e2b125ae47ae6711cb0975a18b75c1634 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:20 +0200 Subject: video: tegra20: dc: add PLLD2 parent support T30+ SOC have second PLLD - PLLD2 which can be actively used by DC and act as main DISP1/2 clock parent. Tested-by: Agneli # Toshiba AC100 T20 Tested-by: Robert Eckelmann # ASUS TF101 Tested-by: Andreas Westman Dorcsak # ASUS Grouper E1565 Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 0e94e665efc..56a23b3c979 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -304,6 +304,12 @@ static int tegra_display_probe(struct tegra_lcd_priv *priv, if (priv->dc_clk[1] == CLOCK_ID_DISPLAY) rate /= 2; +#ifndef CONFIG_TEGRA20 + /* PLLD2 obeys same rules as PLLD but it is present only on T30+ */ + if (priv->dc_clk[1] == CLOCK_ID_DISPLAY2) + rate /= 2; +#endif + /* * HOST1X is init by default at 150MHz with PLLC as parent */ -- cgit v1.3.1 From 8a8bfd8c137ced359958b8409b73d7f72466b89d Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:22 +0200 Subject: video: tegra20: dc: add powergate Add powergate use on T114 to complete resetting of DC. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 56a23b3c979..cc4b5b70015 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "tegra-dc.h" @@ -30,6 +31,7 @@ DECLARE_GLOBAL_DATA_PTR; struct tegra_dc_soc_info { bool has_timer; bool has_rgb; + bool has_pgate; }; /* Information about the display controller */ @@ -349,6 +351,28 @@ static int tegra_lcd_probe(struct udevice *dev) funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT); #endif + if (priv->soc->has_pgate) { + uint powergate; + + if (priv->pipe) + powergate = TEGRA_POWERGATE_DISB; + else + powergate = TEGRA_POWERGATE_DIS; + + ret = tegra_powergate_power_off(powergate); + if (ret < 0) { + log_err("failed to power off DISP gate: %d", ret); + return ret; + } + + ret = tegra_powergate_sequence_power_up(powergate, + priv->dc_clk[0]); + if (ret < 0) { + log_err("failed to power up DISP gate: %d", ret); + return ret; + } + } + if (tegra_display_probe(priv, (void *)plat->base)) { debug("%s: Failed to probe display driver\n", __func__); return -1; @@ -493,16 +517,19 @@ static const struct video_ops tegra_lcd_ops = { static const struct tegra_dc_soc_info tegra20_dc_soc_info = { .has_timer = true, .has_rgb = true, + .has_pgate = false, }; static const struct tegra_dc_soc_info tegra30_dc_soc_info = { .has_timer = false, .has_rgb = true, + .has_pgate = false, }; static const struct tegra_dc_soc_info tegra114_dc_soc_info = { .has_timer = false, .has_rgb = false, + .has_pgate = true, }; static const struct udevice_id tegra_lcd_ids[] = { -- cgit v1.3.1 From 8c0eb06fbe2cc9bca76a54c861852165c4888963 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:23 +0200 Subject: video: tegra20: dc: configure behavior if PLLD/D2 is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The cause of this is not quite clear. This can be overcomed by further halving the PLLD/D2 if the target parent rate is over 800MHz. This way DISP1 and DSI clocks will have the same frequency. The shift divider in this case has to be calculated from the original PLLD/D2 frequency and is passed from the DSI driver. Tested-by: Andreas Westman Dorcsak # ASUS Grouper E1565 Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Tested-by: Jonas Schwöbel # Microsoft Surface 2 Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Acked-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 34 +++++++++++++++++++++------------- drivers/video/tegra20/tegra-dc.h | 1 + drivers/video/tegra20/tegra-dsi.c | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index cc4b5b70015..2c9017807f2 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -46,6 +46,7 @@ struct tegra_lcd_priv { fdt_addr_t frame_buffer; /* Address of frame buffer */ unsigned pixel_clock; /* Pixel clock in Hz */ int dc_clk[2]; /* Contains clk and its parent */ + ulong scdiv; /* Clock divider used by disp_clk_ctrl */ bool rotation; /* 180 degree panel turn */ bool pipe; /* DC controller: 0 for A, 1 for B */ }; @@ -124,8 +125,6 @@ static int update_display_mode(struct tegra_lcd_priv *priv) struct dc_disp_reg *disp = &priv->dc->disp; struct display_timing *dt = &priv->timing; unsigned long val; - unsigned long rate; - unsigned long div; writel(0x0, &disp->disp_timing_opt); @@ -148,21 +147,11 @@ static int update_display_mode(struct tegra_lcd_priv *priv) writel(val, &disp->disp_interface_ctrl); } - /* - * The pixel clock divider is in 7.1 format (where the bottom bit - * represents 0.5). Here we calculate the divider needed to get from - * the display clock (typically 600MHz) to the pixel clock. We round - * up or down as requried. - */ - rate = clock_get_periph_rate(priv->dc_clk[0], priv->dc_clk[1]); - div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2; - debug("Display clock %lu, divider %lu\n", rate, div); - if (priv->soc->has_rgb) writel(0x00010001, &disp->shift_clk_opt); val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; - val |= div << SHIFT_CLK_DIVIDER_SHIFT; + val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT; writel(val, &disp->disp_clk_ctrl); return 0; @@ -312,6 +301,17 @@ static int tegra_display_probe(struct tegra_lcd_priv *priv, rate /= 2; #endif + /* + * The pixel clock divider is in 7.1 format (where the bottom bit + * represents 0.5). Here we calculate the divider needed to get from + * the display clock (typically 600MHz) to the pixel clock. We round + * up or down as required. + */ + if (!priv->scdiv) + priv->scdiv = ((rate * 2 + priv->pixel_clock / 2) + / priv->pixel_clock) - 2; + debug("Display clock %lu, divider %lu\n", rate, priv->scdiv); + /* * HOST1X is init by default at 150MHz with PLLC as parent */ @@ -373,6 +373,14 @@ static int tegra_lcd_probe(struct udevice *dev) } } + /* Get shift clock divider from Tegra DSI if used */ + if (!strcmp(priv->panel->name, TEGRA_DSI_A) || + !strcmp(priv->panel->name, TEGRA_DSI_B)) { + struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel); + + priv->scdiv = dc_plat->scdiv; + } + if (tegra_display_probe(priv, (void *)plat->base)) { debug("%s: Failed to probe display driver\n", __func__); return -1; diff --git a/drivers/video/tegra20/tegra-dc.h b/drivers/video/tegra20/tegra-dc.h index 75fc0fa4de4..05042dab1c6 100644 --- a/drivers/video/tegra20/tegra-dc.h +++ b/drivers/video/tegra20/tegra-dc.h @@ -23,6 +23,7 @@ struct tegra_dc_plat { struct udevice *dev; /* Display controller device */ struct dc_ctlr *dc; /* Display controller regmap */ bool pipe; /* DC number: 0 for A, 1 for B */ + ulong scdiv; /* Shift clock divider */ }; /* This holds information about a window which can be displayed */ diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index 72b91ed26bf..de225ed3766 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -743,6 +743,7 @@ static int tegra_dsi_panel_timings(struct udevice *dev, static void tegra_dsi_init_clocks(struct udevice *dev) { struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); struct mipi_dsi_device *device = &priv->device; unsigned int mul, div; unsigned long bclk, plld; @@ -754,6 +755,19 @@ static void tegra_dsi_init_clocks(struct udevice *dev) plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC); + dc_plat->scdiv = ((plld * USEC_PER_SEC + + priv->timing.pixelclock.typ / 2) / + priv->timing.pixelclock.typ) - 2; + + /* + * BUG: If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The + * cause of this is not quite clear. This can be overcomed by + * halving the PLLD/D2 if the target rate is > 800MHz. This way + * DISP1 and DSI clocks will be equal. + */ + if (plld > 800) + plld /= 2; + switch (clock_get_osc_freq()) { case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */ -- cgit v1.3.1 From de903ac9bacaff912ea38145e3b48d0dbf44a1ce Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:24 +0200 Subject: video: tegra20: dc: fix printing of framebuffer address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Framebuffer address should not be a pointer. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel --- drivers/video/tegra20/tegra-dc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 2c9017807f2..856d507cc5c 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -412,7 +412,7 @@ static int tegra_lcd_probe(struct udevice *dev) uc_priv->xsize = priv->width; uc_priv->ysize = priv->height; uc_priv->bpix = priv->log2_bpp; - debug("LCD frame buffer at %pa, size %x\n", &priv->frame_buffer, + debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer, plat->size); return 0; -- cgit v1.3.1 From 6b4559ba6cc5e5aed51972ad35e44bfa80acea5d Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:25 +0200 Subject: video: tegra20: dc: enable backlight after DC is configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal of panel_set_backlight() is to enable backlight. Hence, it should be called at the probe end. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 856d507cc5c..07d6ac9d718 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -397,12 +397,6 @@ static int tegra_lcd_probe(struct udevice *dev) return ret; } - ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); - if (ret) { - debug("%s: Cannot set backlight to default, ret=%d\n", __func__, ret); - return ret; - } - mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size, DCACHE_WRITETHROUGH); @@ -415,7 +409,7 @@ static int tegra_lcd_probe(struct udevice *dev) debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer, plat->size); - return 0; + return panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); } static int tegra_lcd_of_to_plat(struct udevice *dev) -- cgit v1.3.1 From eb817000180261167ef96bfecc939208f9726247 Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:26 +0200 Subject: video: tegra20: dc: clean framebuffer memory block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the framebuffer memory with zeros to avoid visual glitches. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dc.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 07d6ac9d718..10ad21efb0f 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -381,6 +382,10 @@ static int tegra_lcd_probe(struct udevice *dev) priv->scdiv = dc_plat->scdiv; } + /* Clean the framebuffer area */ + memset((u8 *)plat->base, 0, plat->size); + flush_dcache_all(); + if (tegra_display_probe(priv, (void *)plat->base)) { debug("%s: Failed to probe display driver\n", __func__); return -1; -- cgit v1.3.1 From 8fea3369eea665ae1aafba8e24f337decf1268c5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:27 +0200 Subject: video: tegra20: dc: parameterize V- and H-sync polarities Based on Thierry Reding's Linux commit: 'commit 1716b1891e1de05e2c20ccafa9f58550f3539717 ("drm/tegra: rgb: Parameterize V- and H-sync polarities")' Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- arch/arm/include/asm/arch-tegra/dc.h | 5 +++++ drivers/video/tegra20/tegra-dc.c | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h index 6444af2993a..ca3718411ab 100644 --- a/arch/arm/include/asm/arch-tegra/dc.h +++ b/arch/arm/include/asm/arch-tegra/dc.h @@ -443,6 +443,11 @@ enum win_color_depth_id { #define WINDOW_D_SELECT BIT(7) #define WINDOW_H_SELECT BIT(8) +/* DC_COM_PIN_OUTPUT_POLARITY1 0x307 */ +#define LHS_OUTPUT_POLARITY_LOW BIT(30) +#define LVS_OUTPUT_POLARITY_LOW BIT(28) +#define LSC0_OUTPUT_POLARITY_LOW BIT(24) + /* DC_DISP_DISP_WIN_OPTIONS 0x402 */ #define CURSOR_ENABLE BIT(16) #define SOR_ENABLE BIT(25) diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c index 10ad21efb0f..d073da7d7d4 100644 --- a/drivers/video/tegra20/tegra-dc.c +++ b/drivers/video/tegra20/tegra-dc.c @@ -218,8 +218,11 @@ static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = { 0x00020000, }; -static void rgb_enable(struct dc_com_reg *com) +static void rgb_enable(struct tegra_lcd_priv *priv) { + struct dc_com_reg *com = &priv->dc->com; + struct display_timing *dt = &priv->timing; + u32 value; int i; for (i = 0; i < PIN_REG_COUNT; i++) { @@ -228,6 +231,21 @@ static void rgb_enable(struct dc_com_reg *com) writel(rgb_data_tab[i], &com->pin_output_data[i]); } + /* configure H- and V-sync signal polarities */ + value = readl(&com->pin_output_polarity[1]); + + if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) + value |= LHS_OUTPUT_POLARITY_LOW; + else + value &= ~LHS_OUTPUT_POLARITY_LOW; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW) + value |= LVS_OUTPUT_POLARITY_LOW; + else + value &= ~LVS_OUTPUT_POLARITY_LOW; + + writel(value, &com->pin_output_polarity[1]); + for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++) writel(rgb_sel_tab[i], &com->pin_output_sel[i]); } @@ -327,7 +345,7 @@ static int tegra_display_probe(struct tegra_lcd_priv *priv, basic_init_timer(&priv->dc->disp); if (priv->soc->has_rgb) - rgb_enable(&priv->dc->com); + rgb_enable(priv); if (priv->pixel_clock) update_display_mode(priv); -- cgit v1.3.1 From c68d08be492f2dcbd4f625e47eee81c32966bd7c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:28 +0200 Subject: video: tegra20: add MIPI calibration driver Dedicated MIPI calibration driver is used on T114 and newer. Before T114 MIPI calibration registers were part of VI and CSI. Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/Makefile | 2 +- drivers/video/tegra20/tegra-mipi.c | 188 +++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 drivers/video/tegra20/tegra-mipi.c (limited to 'drivers') diff --git a/drivers/video/tegra20/Makefile b/drivers/video/tegra20/Makefile index f0b534c5794..a75aea2a875 100644 --- a/drivers/video/tegra20/Makefile +++ b/drivers/video/tegra20/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o -obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o mipi-phy.o +obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o tegra-mipi.o mipi-phy.o obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += tegra-pwm-backlight.o diff --git a/drivers/video/tegra20/tegra-mipi.c b/drivers/video/tegra20/tegra-mipi.c new file mode 100644 index 00000000000..2df3c1a9942 --- /dev/null +++ b/drivers/video/tegra20/tegra-mipi.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2023 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include + +#include + +/* MIPI control registers 0x00 ~ 0x60 */ +struct mipi_ctlr { + uint mipi_cal_ctrl; + uint mipi_cal_autocal_ctrl; + uint mipi_cal_status; + + uint unused1[2]; + + uint mipi_cal_config_csia; + uint mipi_cal_config_csib; + uint mipi_cal_config_csic; + uint mipi_cal_config_csid; + uint mipi_cal_config_csie; + + uint unused2[4]; + + uint mipi_cal_config_dsia; + uint mipi_cal_config_dsib; + uint mipi_cal_config_dsic; + uint mipi_cal_config_dsid; + + uint unused3[4]; + + uint mipi_cal_bias_pad_cfg0; + uint mipi_cal_bias_pad_cfg1; + uint mipi_cal_bias_pad_cfg2; +}; + +#define MIPI_CAL_CTRL_NOISE_FILTER(x) (((x) & 0xf) << 26) +#define MIPI_CAL_CTRL_PRESCALE(x) (((x) & 0x3) << 24) +#define MIPI_CAL_CTRL_CLKEN_OVR BIT(4) +#define MIPI_CAL_CTRL_START BIT(0) + +#define MIPI_CAL_STATUS_DONE BIT(16) +#define MIPI_CAL_STATUS_ACTIVE BIT(0) + +#define MIPI_CAL_OVERIDE(x) (((x) & 0x1) << 30) +#define MIPI_CAL_SEL(x) (((x) & 0x1) << 21) +#define MIPI_CAL_HSPDOS(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOS(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_TERMOS(x) (((x) & 0x1f) << 0) + +#define MIPI_CAL_BIAS_PAD_PDVCLAMP BIT(1) +#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF BIT(0) + +#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8) + +#define MIPI_CAL_BIAS_PAD_VCLAMP(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_VAUXP(x) (((x) & 0x7) << 4) +#define MIPI_CAL_BIAS_PAD_PDVREG BIT(1) + +struct tegra_mipi_priv { + struct mipi_ctlr *mipi; + struct clk *mipi_cal; +}; + +static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf, + int size) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) | + MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7); + value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) | + MIPI_CAL_TERMOS(0x5); + writel(value, &priv->mipi->mipi_cal_config_dsia); + writel(value, &priv->mipi->mipi_cal_config_dsib); + + /* Deselect PAD C */ + value = readl(&priv->mipi->mipi_cal_config_dsic); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsic); + + /* Deselect PAD D */ + value = readl(&priv->mipi->mipi_cal_config_dsid); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsid); + + value = readl(&priv->mipi->mipi_cal_ctrl); + value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf); + value &= ~MIPI_CAL_CTRL_PRESCALE(0x3); + value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) | + MIPI_CAL_CTRL_PRESCALE(0x2) | + MIPI_CAL_CTRL_CLKEN_OVR; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* clear any pending status bits */ + value = readl(&priv->mipi->mipi_cal_status); + writel(value, &priv->mipi->mipi_cal_status); + + value = readl(&priv->mipi->mipi_cal_ctrl); + value |= MIPI_CAL_CTRL_START; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* + * Wait for min 72uS to let calibration logic finish calibration + * sequence codes before waiting for pads idle state to apply the + * results. + */ + udelay(80); + + return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value, + !(value & MIPI_CAL_STATUS_ACTIVE) && + (value & MIPI_CAL_STATUS_DONE), 100, + 250000); +} + +static int tegra_mipi_enable(struct udevice *dev, bool val) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + clk_enable(priv->mipi_cal); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0); + value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP; + value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_PDVREG; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + return 0; +} + +static const struct misc_ops tegra_mipi_ops = { + .write = tegra_mipi_calibrate, + .set_enabled = tegra_mipi_enable, +}; + +static int tegra_mipi_probe(struct udevice *dev) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + + priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->mipi) { + log_debug("%s: no MIPI controller address\n", __func__); + return -EINVAL; + } + + priv->mipi_cal = devm_clk_get(dev, NULL); + if (IS_ERR(priv->mipi_cal)) { + log_debug("%s: Could not get MIPI clock: %ld\n", + __func__, PTR_ERR(priv->mipi_cal)); + return PTR_ERR(priv->mipi_cal); + } + + return 0; +} + +static const struct udevice_id tegra_mipi_ids[] = { + { .compatible = "nvidia,tegra114-mipi" }, + { } +}; + +U_BOOT_DRIVER(tegra_mipi) = { + .name = "tegra_mipi", + .id = UCLASS_MISC, + .ops = &tegra_mipi_ops, + .of_match = tegra_mipi_ids, + .probe = tegra_mipi_probe, + .priv_auto = sizeof(struct tegra_mipi_priv), +}; -- cgit v1.3.1 From f1b1f5e61d2d9bba2e721e877b33fb7d6ff16344 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:29 +0200 Subject: video: tegra20: dsi: add T114 support Existing Tegra DSI driver mostly fits T114 apart MIPI calibration which on T114 has dedicated driver. To resolve this MIPI calibration logic was split for pre-T114 and T114+ devices. Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- arch/arm/include/asm/arch-tegra30/dsi.h | 217 ----------------------------- drivers/video/tegra20/tegra-dsi.c | 78 ++++++++++- drivers/video/tegra20/tegra-dsi.h | 235 ++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+), 220 deletions(-) delete mode 100644 arch/arm/include/asm/arch-tegra30/dsi.h create mode 100644 drivers/video/tegra20/tegra-dsi.h (limited to 'drivers') diff --git a/arch/arm/include/asm/arch-tegra30/dsi.h b/arch/arm/include/asm/arch-tegra30/dsi.h deleted file mode 100644 index 7ade132613f..00000000000 --- a/arch/arm/include/asm/arch-tegra30/dsi.h +++ /dev/null @@ -1,217 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * (C) Copyright 2010 - * NVIDIA Corporation - */ - -#ifndef __ASM_ARCH_TEGRA_DSI_H -#define __ASM_ARCH_TEGRA_DSI_H - -#ifndef __ASSEMBLY__ -#include -#endif - -/* Register definitions for the Tegra display serial interface */ - -/* DSI syncpoint register 0x000 ~ 0x002 */ -struct dsi_syncpt_reg { - /* Address 0x000 ~ 0x002 */ - uint incr_syncpt; /* _INCR_SYNCPT_0 */ - uint incr_syncpt_ctrl; /* _INCR_SYNCPT_CNTRL_0 */ - uint incr_syncpt_err; /* _INCR_SYNCPT_ERROR_0 */ -}; - -/* DSI misc register 0x008 ~ 0x015 */ -struct dsi_misc_reg { - /* Address 0x008 ~ 0x015 */ - uint ctxsw; /* _CTXSW_0 */ - uint dsi_rd_data; /* _DSI_RD_DATA_0 */ - uint dsi_wr_data; /* _DSI_WR_DATA_0 */ - uint dsi_pwr_ctrl; /* _DSI_POWER_CONTROL_0 */ - uint int_enable; /* _INT_ENABLE_0 */ - uint int_status; /* _INT_STATUS_0 */ - uint int_mask; /* _INT_MASK_0 */ - uint host_dsi_ctrl; /* _HOST_DSI_CONTROL_0 */ - uint dsi_ctrl; /* _DSI_CONTROL_0 */ - uint dsi_sol_delay; /* _DSI_SOL_DELAY_0 */ - uint dsi_max_threshold; /* _DSI_MAX_THRESHOLD_0 */ - uint dsi_trigger; /* _DSI_TRIGGER_0 */ - uint dsi_tx_crc; /* _DSI_TX_CRC_0 */ - uint dsi_status; /* _DSI_STATUS_0 */ -}; - -/* DSI init sequence register 0x01a ~ 0x022 */ -struct dsi_init_seq_reg { - /* Address 0x01a ~ 0x022 */ - uint dsi_init_seq_ctrl; /* _DSI_INIT_SEQ_CONTROL_0 */ - uint dsi_init_seq_data_0; /* _DSI_INIT_SEQ_DATA_0_0 */ - uint dsi_init_seq_data_1; /* _DSI_INIT_SEQ_DATA_1_0 */ - uint dsi_init_seq_data_2; /* _DSI_INIT_SEQ_DATA_2_0 */ - uint dsi_init_seq_data_3; /* _DSI_INIT_SEQ_DATA_3_0 */ - uint dsi_init_seq_data_4; /* _DSI_INIT_SEQ_DATA_4_0 */ - uint dsi_init_seq_data_5; /* _DSI_INIT_SEQ_DATA_5_0 */ - uint dsi_init_seq_data_6; /* _DSI_INIT_SEQ_DATA_6_0 */ - uint dsi_init_seq_data_7; /* _DSI_INIT_SEQ_DATA_7_0 */ -}; - -/* DSI packet sequence register 0x023 ~ 0x02e */ -struct dsi_pkt_seq_reg { - /* Address 0x023 ~ 0x02e */ - uint dsi_pkt_seq_0_lo; /* _DSI_PKT_SEQ_0_LO_0 */ - uint dsi_pkt_seq_0_hi; /* _DSI_PKT_SEQ_0_HI_0 */ - uint dsi_pkt_seq_1_lo; /* _DSI_PKT_SEQ_1_LO_0 */ - uint dsi_pkt_seq_1_hi; /* _DSI_PKT_SEQ_1_HI_0 */ - uint dsi_pkt_seq_2_lo; /* _DSI_PKT_SEQ_2_LO_0 */ - uint dsi_pkt_seq_2_hi; /* _DSI_PKT_SEQ_2_HI_0 */ - uint dsi_pkt_seq_3_lo; /* _DSI_PKT_SEQ_3_LO_0 */ - uint dsi_pkt_seq_3_hi; /* _DSI_PKT_SEQ_3_HI_0 */ - uint dsi_pkt_seq_4_lo; /* _DSI_PKT_SEQ_4_LO_0 */ - uint dsi_pkt_seq_4_hi; /* _DSI_PKT_SEQ_4_HI_0 */ - uint dsi_pkt_seq_5_lo; /* _DSI_PKT_SEQ_5_LO_0 */ - uint dsi_pkt_seq_5_hi; /* _DSI_PKT_SEQ_5_HI_0 */ -}; - -/* DSI packet length register 0x033 ~ 0x037 */ -struct dsi_pkt_len_reg { - /* Address 0x033 ~ 0x037 */ - uint dsi_dcs_cmds; /* _DSI_DCS_CMDS_0 */ - uint dsi_pkt_len_0_1; /* _DSI_PKT_LEN_0_1_0 */ - uint dsi_pkt_len_2_3; /* _DSI_PKT_LEN_2_3_0 */ - uint dsi_pkt_len_4_5; /* _DSI_PKT_LEN_4_5_0 */ - uint dsi_pkt_len_6_7; /* _DSI_PKT_LEN_6_7_0 */ -}; - -/* DSI PHY timing register 0x03c ~ 0x03f */ -struct dsi_timing_reg { - /* Address 0x03c ~ 0x03f */ - uint dsi_phy_timing_0; /* _DSI_PHY_TIMING_0_0 */ - uint dsi_phy_timing_1; /* _DSI_PHY_TIMING_1_0 */ - uint dsi_phy_timing_2; /* _DSI_PHY_TIMING_2_0 */ - uint dsi_bta_timing; /* _DSI_BTA_TIMING_0 */ -}; - -/* DSI timeout register 0x044 ~ 0x046 */ -struct dsi_timeout_reg { - /* Address 0x044 ~ 0x046 */ - uint dsi_timeout_0; /* _DSI_TIMEOUT_0_0 */ - uint dsi_timeout_1; /* _DSI_TIMEOUT_1_0 */ - uint dsi_to_tally; /* _DSI_TO_TALLY_0 */ -}; - -/* DSI PAD control register 0x04b ~ 0x04e */ -struct dsi_pad_ctrl_reg { - /* Address 0x04b ~ 0x04e */ - uint pad_ctrl; /* _PAD_CONTROL_0 */ - uint pad_ctrl_cd; /* _PAD_CONTROL_CD_0 */ - uint pad_cd_status; /* _PAD_CD_STATUS_0 */ - uint dsi_vid_mode_control; /* _DSI_VID_MODE_CONTROL_0 */ -}; - -/* Display Serial Interface (DSI_) regs */ -struct dsi_ctlr { - struct dsi_syncpt_reg syncpt; /* SYNCPT register 0x000 ~ 0x002 */ - uint reserved0[5]; /* reserved_0[5] */ - - struct dsi_misc_reg misc; /* MISC register 0x008 ~ 0x015 */ - uint reserved1[4]; /* reserved_1[4] */ - - struct dsi_init_seq_reg init; /* INIT register 0x01a ~ 0x022 */ - struct dsi_pkt_seq_reg pkt; /* PKT register 0x023 ~ 0x02e */ - uint reserved2[4]; /* reserved_2[4] */ - - struct dsi_pkt_len_reg len; /* LEN registers 0x033 ~ 0x037 */ - uint reserved3[4]; /* reserved_3[4] */ - - struct dsi_timing_reg ptiming; /* TIMING registers 0x03c ~ 0x03f */ - uint reserved4[4]; /* reserved_4[4] */ - - struct dsi_timeout_reg timeout; /* TIMEOUT registers 0x044 ~ 0x046 */ - uint reserved5[4]; /* reserved_5[4] */ - - struct dsi_pad_ctrl_reg pad; /* PAD registers 0x04b ~ 0x04e */ -}; - -#define DSI_POWER_CONTROL_ENABLE BIT(0) - -#define DSI_HOST_CONTROL_FIFO_RESET BIT(21) -#define DSI_HOST_CONTROL_CRC_RESET BIT(20) -#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12) -#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12) -#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12) -#define DSI_HOST_CONTROL_RAW BIT(6) -#define DSI_HOST_CONTROL_HS BIT(5) -#define DSI_HOST_CONTROL_FIFO_SEL BIT(4) -#define DSI_HOST_CONTROL_IMM_BTA BIT(3) -#define DSI_HOST_CONTROL_PKT_BTA BIT(2) -#define DSI_HOST_CONTROL_CS BIT(1) -#define DSI_HOST_CONTROL_ECC BIT(0) - -#define DSI_CONTROL_HS_CLK_CTRL BIT(20) -#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16) -#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12) -#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8) -#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4) -#define DSI_CONTROL_DCS_ENABLE BIT(3) -#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2) -#define DSI_CONTROL_VIDEO_ENABLE BIT(1) -#define DSI_CONTROL_HOST_ENABLE BIT(0) - -#define DSI_TRIGGER_HOST BIT(1) -#define DSI_TRIGGER_VIDEO BIT(0) - -#define DSI_STATUS_IDLE BIT(10) -#define DSI_STATUS_UNDERFLOW BIT(9) -#define DSI_STATUS_OVERFLOW BIT(8) - -#define DSI_TIMING_FIELD(value, period, hwinc) \ - ((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff) - -#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16) -#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0) -#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16) -#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0) - -#define DSI_TALLY_TA(x) (((x) & 0xff) << 16) -#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) -#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) - -#define DSI_PAD_CONTROL_PAD_PULLDN_ENAB(x) (((x) & 0x1) << 28) -#define DSI_PAD_CONTROL_PAD_SLEWUPADJ(x) (((x) & 0x7) << 24) -#define DSI_PAD_CONTROL_PAD_SLEWDNADJ(x) (((x) & 0x7) << 20) -#define DSI_PAD_CONTROL_PAD_PREEMP_EN(x) (((x) & 0x1) << 19) -#define DSI_PAD_CONTROL_PAD_PDIO_CLK(x) (((x) & 0x1) << 18) -#define DSI_PAD_CONTROL_PAD_PDIO(x) (((x) & 0x3) << 16) -#define DSI_PAD_CONTROL_PAD_LPUPADJ(x) (((x) & 0x3) << 14) -#define DSI_PAD_CONTROL_PAD_LPDNADJ(x) (((x) & 0x3) << 12) - -/* - * pixel format as used in the DSI_CONTROL_FORMAT field - */ -enum tegra_dsi_format { - TEGRA_DSI_FORMAT_16P, - TEGRA_DSI_FORMAT_18NP, - TEGRA_DSI_FORMAT_18P, - TEGRA_DSI_FORMAT_24P, -}; - -/* DSI calibration in VI region */ -#define TEGRA_VI_BASE 0x54080000 - -#define CSI_CILA_MIPI_CAL_CONFIG_0 0x22a -#define MIPI_CAL_TERMOSA(x) (((x) & 0x1f) << 0) - -#define CSI_CILB_MIPI_CAL_CONFIG_0 0x22b -#define MIPI_CAL_TERMOSB(x) (((x) & 0x1f) << 0) - -#define CSI_CIL_PAD_CONFIG 0x229 -#define PAD_CIL_PDVREG(x) (((x) & 0x01) << 1) - -#define CSI_DSI_MIPI_CAL_CONFIG 0x234 -#define MIPI_CAL_HSPDOSD(x) (((x) & 0x1f) << 16) -#define MIPI_CAL_HSPUOSD(x) (((x) & 0x1f) << 8) - -#define CSI_MIPIBIAS_PAD_CONFIG 0x235 -#define PAD_DRIV_DN_REF(x) (((x) & 0x7) << 16) -#define PAD_DRIV_UP_REF(x) (((x) & 0x7) << 8) - -#endif /* __ASM_ARCH_TEGRA_DSI_H */ diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index de225ed3766..25a629535ec 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -20,17 +20,24 @@ #include #include #include -#include #include "tegra-dc.h" +#include "tegra-dsi.h" #include "mipi-phy.h" +/* List of supported DSI bridges */ +enum { + DSI_V0, + DSI_V1, +}; + struct tegra_dsi_priv { struct mipi_dsi_host host; struct mipi_dsi_device device; struct mipi_dphy_timing dphy_timing; struct udevice *panel; + struct udevice *mipi; struct display_timing timing; struct dsi_ctlr *dsi; @@ -41,6 +48,8 @@ struct tegra_dsi_priv { int dsi_clk; int video_fifo_depth; int host_fifo_depth; + + u32 version; }; static void tegra_dc_enable_controller(struct udevice *dev) @@ -501,6 +510,41 @@ static void tegra_dsi_pad_calibrate(struct dsi_pad_ctrl_reg *pad) writel(value, TEGRA_VI_BASE + (CSI_CIL_PAD_CONFIG << 2)); } +static void tegra_dsi_mipi_calibrate(struct tegra_dsi_priv *priv) +{ + struct dsi_pad_ctrl_reg *pad = &priv->dsi->pad; + u32 value; + int ret; + + ret = misc_set_enabled(priv->mipi, true); + if (ret) + log_debug("%s: failed to enable MIPI calibration: %d\n", + __func__, ret); + + writel(0, &pad->pad_ctrl); + writel(0, &pad->pad_ctrl_1); + writel(0, &pad->pad_ctrl_2); + writel(0, &pad->pad_ctrl_3); + writel(0, &pad->pad_ctrl_4); + + /* DSI pad enable */ + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + writel(value, &pad->pad_ctrl); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + writel(value, &pad->pad_ctrl_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + writel(value, &pad->pad_ctrl_3); + + ret = misc_write(priv->mipi, 0, NULL, 0); + if (ret) + log_debug("%s: MIPI calibration failed %d\n", __func__, ret); +} + static void tegra_dsi_set_timeout(struct dsi_timeout_reg *rtimeout, unsigned long bclk, unsigned int vrefresh) @@ -664,10 +708,25 @@ static int tegra_dsi_encoder_enable(struct udevice *dev) u32 value; int ret; + /* If for some reasone DSI is enabled then it needs to + * be disabled in order for the panel initialization + * commands to be properly sent. + */ + value = readl(&misc->dsi_pwr_ctrl); + + if (value & DSI_POWER_CONTROL_ENABLE) { + value = readl(&misc->dsi_pwr_ctrl); + value &= ~DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + } + /* Disable interrupt */ writel(0, &misc->int_enable); - tegra_dsi_pad_calibrate(&priv->dsi->pad); + if (priv->version) + tegra_dsi_mipi_calibrate(priv); + else + tegra_dsi_pad_calibrate(&priv->dsi->pad); tegra_dsi_get_muldiv(device->format, &mul, &div); @@ -806,6 +865,8 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) struct mipi_dsi_panel_plat *mipi_plat; int ret; + priv->version = dev_get_driver_data(dev); + priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev); if (!priv->dsi) { printf("%s: No display controller address\n", __func__); @@ -828,6 +889,16 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) return log_ret(ret); } + if (priv->version) { + ret = uclass_get_device_by_phandle(UCLASS_MISC, dev, + "nvidia,mipi-calibrate", + &priv->mipi); + if (ret) { + log_debug("%s: cannot get MIPI: error %d\n", __func__, ret); + return ret; + } + } + panel_get_display_timing(priv->panel, &priv->timing); mipi_plat = dev_get_plat(priv->panel); @@ -859,7 +930,8 @@ static const struct panel_ops tegra_dsi_bridge_ops = { }; static const struct udevice_id tegra_dsi_bridge_ids[] = { - { .compatible = "nvidia,tegra30-dsi" }, + { .compatible = "nvidia,tegra30-dsi", .data = DSI_V0 }, + { .compatible = "nvidia,tegra114-dsi", .data = DSI_V1 }, { } }; diff --git a/drivers/video/tegra20/tegra-dsi.h b/drivers/video/tegra20/tegra-dsi.h new file mode 100644 index 00000000000..69dac4bd1b8 --- /dev/null +++ b/drivers/video/tegra20/tegra-dsi.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation + */ + +#ifndef _TEGRA_DSI_H +#define _TEGRA_DSI_H + +#ifndef __ASSEMBLY__ +#include +#endif + +/* Register definitions for the Tegra display serial interface */ + +/* DSI syncpoint register 0x000 ~ 0x002 */ +struct dsi_syncpt_reg { + /* Address 0x000 ~ 0x002 */ + uint incr_syncpt; /* _INCR_SYNCPT_0 */ + uint incr_syncpt_ctrl; /* _INCR_SYNCPT_CNTRL_0 */ + uint incr_syncpt_err; /* _INCR_SYNCPT_ERROR_0 */ +}; + +/* DSI misc register 0x008 ~ 0x015 */ +struct dsi_misc_reg { + /* Address 0x008 ~ 0x015 */ + uint ctxsw; /* _CTXSW_0 */ + uint dsi_rd_data; /* _DSI_RD_DATA_0 */ + uint dsi_wr_data; /* _DSI_WR_DATA_0 */ + uint dsi_pwr_ctrl; /* _DSI_POWER_CONTROL_0 */ + uint int_enable; /* _INT_ENABLE_0 */ + uint int_status; /* _INT_STATUS_0 */ + uint int_mask; /* _INT_MASK_0 */ + uint host_dsi_ctrl; /* _HOST_DSI_CONTROL_0 */ + uint dsi_ctrl; /* _DSI_CONTROL_0 */ + uint dsi_sol_delay; /* _DSI_SOL_DELAY_0 */ + uint dsi_max_threshold; /* _DSI_MAX_THRESHOLD_0 */ + uint dsi_trigger; /* _DSI_TRIGGER_0 */ + uint dsi_tx_crc; /* _DSI_TX_CRC_0 */ + uint dsi_status; /* _DSI_STATUS_0 */ +}; + +/* DSI init sequence register 0x01a ~ 0x022 */ +struct dsi_init_seq_reg { + /* Address 0x01a ~ 0x022 */ + uint dsi_init_seq_ctrl; /* _DSI_INIT_SEQ_CONTROL_0 */ + uint dsi_init_seq_data_0; /* _DSI_INIT_SEQ_DATA_0_0 */ + uint dsi_init_seq_data_1; /* _DSI_INIT_SEQ_DATA_1_0 */ + uint dsi_init_seq_data_2; /* _DSI_INIT_SEQ_DATA_2_0 */ + uint dsi_init_seq_data_3; /* _DSI_INIT_SEQ_DATA_3_0 */ + uint dsi_init_seq_data_4; /* _DSI_INIT_SEQ_DATA_4_0 */ + uint dsi_init_seq_data_5; /* _DSI_INIT_SEQ_DATA_5_0 */ + uint dsi_init_seq_data_6; /* _DSI_INIT_SEQ_DATA_6_0 */ + uint dsi_init_seq_data_7; /* _DSI_INIT_SEQ_DATA_7_0 */ +}; + +/* DSI packet sequence register 0x023 ~ 0x02e */ +struct dsi_pkt_seq_reg { + /* Address 0x023 ~ 0x02e */ + uint dsi_pkt_seq_0_lo; /* _DSI_PKT_SEQ_0_LO_0 */ + uint dsi_pkt_seq_0_hi; /* _DSI_PKT_SEQ_0_HI_0 */ + uint dsi_pkt_seq_1_lo; /* _DSI_PKT_SEQ_1_LO_0 */ + uint dsi_pkt_seq_1_hi; /* _DSI_PKT_SEQ_1_HI_0 */ + uint dsi_pkt_seq_2_lo; /* _DSI_PKT_SEQ_2_LO_0 */ + uint dsi_pkt_seq_2_hi; /* _DSI_PKT_SEQ_2_HI_0 */ + uint dsi_pkt_seq_3_lo; /* _DSI_PKT_SEQ_3_LO_0 */ + uint dsi_pkt_seq_3_hi; /* _DSI_PKT_SEQ_3_HI_0 */ + uint dsi_pkt_seq_4_lo; /* _DSI_PKT_SEQ_4_LO_0 */ + uint dsi_pkt_seq_4_hi; /* _DSI_PKT_SEQ_4_HI_0 */ + uint dsi_pkt_seq_5_lo; /* _DSI_PKT_SEQ_5_LO_0 */ + uint dsi_pkt_seq_5_hi; /* _DSI_PKT_SEQ_5_HI_0 */ +}; + +/* DSI packet length register 0x033 ~ 0x037 */ +struct dsi_pkt_len_reg { + /* Address 0x033 ~ 0x037 */ + uint dsi_dcs_cmds; /* _DSI_DCS_CMDS_0 */ + uint dsi_pkt_len_0_1; /* _DSI_PKT_LEN_0_1_0 */ + uint dsi_pkt_len_2_3; /* _DSI_PKT_LEN_2_3_0 */ + uint dsi_pkt_len_4_5; /* _DSI_PKT_LEN_4_5_0 */ + uint dsi_pkt_len_6_7; /* _DSI_PKT_LEN_6_7_0 */ +}; + +/* DSI PHY timing register 0x03c ~ 0x03f */ +struct dsi_timing_reg { + /* Address 0x03c ~ 0x03f */ + uint dsi_phy_timing_0; /* _DSI_PHY_TIMING_0_0 */ + uint dsi_phy_timing_1; /* _DSI_PHY_TIMING_1_0 */ + uint dsi_phy_timing_2; /* _DSI_PHY_TIMING_2_0 */ + uint dsi_bta_timing; /* _DSI_BTA_TIMING_0 */ +}; + +/* DSI timeout register 0x044 ~ 0x046 */ +struct dsi_timeout_reg { + /* Address 0x044 ~ 0x046 */ + uint dsi_timeout_0; /* _DSI_TIMEOUT_0_0 */ + uint dsi_timeout_1; /* _DSI_TIMEOUT_1_0 */ + uint dsi_to_tally; /* _DSI_TO_TALLY_0 */ +}; + +/* DSI PAD control register 0x04b ~ 0x04e */ +struct dsi_pad_ctrl_reg { + /* Address 0x04b ~ 0x04e */ + uint pad_ctrl; /* _PAD_CONTROL_0 */ + uint pad_ctrl_cd; /* _PAD_CONTROL_CD_0 */ + uint pad_cd_status; /* _PAD_CD_STATUS_0 */ + uint dsi_vid_mode_control; /* _DSI_VID_MODE_CONTROL_0 */ + uint pad_ctrl_1; /* _PAD_CONTROL_1 */ + uint pad_ctrl_2; /* _PAD_CONTROL_2 */ + uint pad_ctrl_3; /* _PAD_CONTROL_3 */ + uint pad_ctrl_4; /* _PAD_CONTROL_4 */ +}; + +/* Display Serial Interface (DSI_) regs */ +struct dsi_ctlr { + struct dsi_syncpt_reg syncpt; /* SYNCPT register 0x000 ~ 0x002 */ + uint reserved0[5]; /* reserved_0[5] */ + + struct dsi_misc_reg misc; /* MISC register 0x008 ~ 0x015 */ + uint reserved1[4]; /* reserved_1[4] */ + + struct dsi_init_seq_reg init; /* INIT register 0x01a ~ 0x022 */ + struct dsi_pkt_seq_reg pkt; /* PKT register 0x023 ~ 0x02e */ + uint reserved2[4]; /* reserved_2[4] */ + + struct dsi_pkt_len_reg len; /* LEN registers 0x033 ~ 0x037 */ + uint reserved3[4]; /* reserved_3[4] */ + + struct dsi_timing_reg ptiming; /* TIMING registers 0x03c ~ 0x03f */ + uint reserved4[4]; /* reserved_4[4] */ + + struct dsi_timeout_reg timeout; /* TIMEOUT registers 0x044 ~ 0x046 */ + uint reserved5[4]; /* reserved_5[4] */ + + struct dsi_pad_ctrl_reg pad; /* PAD registers 0x04b ~ 0x04e */ +}; + +#define DSI_POWER_CONTROL_ENABLE BIT(0) + +#define DSI_HOST_CONTROL_FIFO_RESET BIT(21) +#define DSI_HOST_CONTROL_CRC_RESET BIT(20) +#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12) +#define DSI_HOST_CONTROL_RAW BIT(6) +#define DSI_HOST_CONTROL_HS BIT(5) +#define DSI_HOST_CONTROL_FIFO_SEL BIT(4) +#define DSI_HOST_CONTROL_IMM_BTA BIT(3) +#define DSI_HOST_CONTROL_PKT_BTA BIT(2) +#define DSI_HOST_CONTROL_CS BIT(1) +#define DSI_HOST_CONTROL_ECC BIT(0) + +#define DSI_CONTROL_HS_CLK_CTRL BIT(20) +#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16) +#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12) +#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8) +#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4) +#define DSI_CONTROL_DCS_ENABLE BIT(3) +#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2) +#define DSI_CONTROL_VIDEO_ENABLE BIT(1) +#define DSI_CONTROL_HOST_ENABLE BIT(0) + +#define DSI_TRIGGER_HOST BIT(1) +#define DSI_TRIGGER_VIDEO BIT(0) + +#define DSI_STATUS_IDLE BIT(10) +#define DSI_STATUS_UNDERFLOW BIT(9) +#define DSI_STATUS_OVERFLOW BIT(8) + +#define DSI_TIMING_FIELD(value, period, hwinc) \ + ((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff) + +#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0) +#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0) + +#define DSI_TALLY_TA(x) (((x) & 0xff) << 16) +#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) +#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) + +#define DSI_PAD_CONTROL_PAD_PULLDN_ENAB(x) (((x) & 0x1) << 28) +#define DSI_PAD_CONTROL_PAD_SLEWUPADJ(x) (((x) & 0x7) << 24) +#define DSI_PAD_CONTROL_PAD_SLEWDNADJ(x) (((x) & 0x7) << 20) +#define DSI_PAD_CONTROL_PAD_PREEMP_EN(x) (((x) & 0x1) << 19) +#define DSI_PAD_CONTROL_PAD_PDIO_CLK(x) (((x) & 0x1) << 18) +#define DSI_PAD_CONTROL_PAD_PDIO(x) (((x) & 0x3) << 16) +#define DSI_PAD_CONTROL_PAD_LPUPADJ(x) (((x) & 0x3) << 14) +#define DSI_PAD_CONTROL_PAD_LPDNADJ(x) (((x) & 0x3) << 12) + +#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0) +#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16) + +#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0) +#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4) +#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8) +#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12) +#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16) + +#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12) +#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8) +#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4) +#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0) + +/* + * pixel format as used in the DSI_CONTROL_FORMAT field + */ +enum tegra_dsi_format { + TEGRA_DSI_FORMAT_16P, + TEGRA_DSI_FORMAT_18NP, + TEGRA_DSI_FORMAT_18P, + TEGRA_DSI_FORMAT_24P, +}; + +/* DSI calibration in VI region */ +#define TEGRA_VI_BASE 0x54080000 + +#define CSI_CILA_MIPI_CAL_CONFIG_0 0x22a +#define MIPI_CAL_TERMOSA(x) (((x) & 0x1f) << 0) + +#define CSI_CILB_MIPI_CAL_CONFIG_0 0x22b +#define MIPI_CAL_TERMOSB(x) (((x) & 0x1f) << 0) + +#define CSI_CIL_PAD_CONFIG 0x229 +#define PAD_CIL_PDVREG(x) (((x) & 0x01) << 1) + +#define CSI_DSI_MIPI_CAL_CONFIG 0x234 +#define MIPI_CAL_HSPDOSD(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOSD(x) (((x) & 0x1f) << 8) + +#define CSI_MIPIBIAS_PAD_CONFIG 0x235 +#define PAD_DRIV_DN_REF(x) (((x) & 0x7) << 16) +#define PAD_DRIV_UP_REF(x) (((x) & 0x7) << 8) + +#endif /* _TEGRA_DSI_H */ -- cgit v1.3.1 From 6c4dc8965959c17c314c4b1519aa999ae0651467 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 23 Jan 2024 19:16:30 +0200 Subject: video: tegra20: dsi: add reset support Implement reset use to discard any changes which could have been applied to DSI before and can interfere with current configuration. Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Svyatoslav Ryhel --- drivers/video/tegra20/tegra-dsi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index 25a629535ec..fc9ca1310a4 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -863,6 +864,7 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) struct tegra_dsi_priv *priv = dev_get_priv(dev); struct mipi_dsi_device *device = &priv->device; struct mipi_dsi_panel_plat *mipi_plat; + struct reset_ctl reset_ctl; int ret; priv->version = dev_get_driver_data(dev); @@ -876,6 +878,13 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) priv->video_fifo_depth = 480; priv->host_fifo_depth = 64; + ret = reset_get_by_name(dev, "dsi", &reset_ctl); + if (ret) { + log_debug("%s: reset_get_by_name() failed: %d\n", + __func__, ret); + return ret; + } + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, "avdd-dsi-csi-supply", &priv->avdd); if (ret) @@ -914,12 +923,17 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) tegra_dsi_get_format(device->format, &priv->format); + reset_assert(&reset_ctl); + ret = regulator_set_enable_if_allowed(priv->avdd, true); if (ret && ret != -ENOSYS) return ret; tegra_dsi_init_clocks(dev); + mdelay(2); + reset_deassert(&reset_ctl); + return 0; } -- cgit v1.3.1 From 2702c6ae5512964eebdf69eca7144db5d03550cd Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:31 +0200 Subject: video: tegra20: dsi: remove pre-configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configuration for DC driver command mode is not required for every panel. Removed. Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dsi.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index fc9ca1310a4..ddd74540f27 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -766,8 +766,6 @@ static int tegra_dsi_encoder_enable(struct udevice *dev) if (ret) return ret; - tegra_dsi_configure(dev, 0); - ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); if (ret) return ret; -- cgit v1.3.1 From 580a4442888af60188adc70eadedcf7136497da4 Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:32 +0200 Subject: video: tegra20: dsi: set correct fifo depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to Thierry Reding's commit in the linux kernel 976cebc35bed0456a42bf96073a26f251d23b264 "drm/tegra: dsi: Make FIFO depths host parameters" correct depth of the video FIFO is 1920 *words* no *bytes* Tested-by: Ion Agorria # HTC One X Tested-by: Svyatoslav Ryhel # Nvidia Tegratab T114 Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index ddd74540f27..7d63557d1b9 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -873,7 +873,7 @@ static int tegra_dsi_bridge_probe(struct udevice *dev) return -EINVAL; } - priv->video_fifo_depth = 480; + priv->video_fifo_depth = 1920; priv->host_fifo_depth = 64; ret = reset_get_by_name(dev, "dsi", &reset_ctl); -- cgit v1.3.1 From edb8a528f96abb621d7d3ee51b026ec05a4e8049 Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Tue, 23 Jan 2024 19:16:33 +0200 Subject: video: tegra20: dsi: use set_backlight for backlight only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shift the backlight set further to prevent visual glitches on panel init. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Reviewed-by: Thierry Reding --- drivers/video/tegra20/tegra-dsi.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c index 7d63557d1b9..13dae37806f 100644 --- a/drivers/video/tegra20/tegra-dsi.c +++ b/drivers/video/tegra20/tegra-dsi.c @@ -766,10 +766,6 @@ static int tegra_dsi_encoder_enable(struct udevice *dev) if (ret) return ret; - ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); - if (ret) - return ret; - tegra_dsi_configure(dev, device->mode_flags); tegra_dc_enable_controller(dev); @@ -784,8 +780,10 @@ static int tegra_dsi_encoder_enable(struct udevice *dev) static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent) { - /* Is not used in tegra dc */ - return 0; + struct tegra_dsi_priv *priv = dev_get_priv(dev); + + /* Turn on/off backlight */ + return panel_set_backlight(priv->panel, percent); } static int tegra_dsi_panel_timings(struct udevice *dev, -- cgit v1.3.1 From 3cb31745c488c280ab190943d4a97ec50dc87b8c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 31 Jan 2024 08:57:15 +0200 Subject: video: panel: add LG LG070WX3 MIPI DSI panel driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LD070WX3 is a Color Active Matrix Liquid Crystal Display with an integral Light Emitting Diode (LED) backlight system. The matrix employs a-Si Thin Film Transistor as the active element. It is a transmissive type display operating in the normally Black mode. This TFT-LCD has 7.0 inches diagonally measured active display area with WXGA resolution (800 by 1280 pixel array). Signed-off-by: Svyatoslav Ryhel --- drivers/video/Kconfig | 8 ++ drivers/video/Makefile | 1 + drivers/video/lg-ld070wx3.c | 186 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 drivers/video/lg-ld070wx3.c (limited to 'drivers') diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 39c82521be1..742ea6edee6 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -522,6 +522,14 @@ config VIDEO_LCD_ORISETECH_OTM8009A Say Y here if you want to enable support for Orise Technology otm8009a 480x800 dsi 2dl panel. +config VIDEO_LCD_LG_LD070WX3 + bool "LD070WX3 DSI LCD panel support" + depends on PANEL && BACKLIGHT + select VIDEO_MIPI_DSI + help + Say Y here if you want to enable support for LG LD070WX3 + 800x1280 DSI video mode panel. + config VIDEO_LCD_RAYDIUM_RM68200 bool "RM68200 DSI LCD panel support" select VIDEO_MIPI_DSI diff --git a/drivers/video/Makefile b/drivers/video/Makefile index fdc29376324..bb6d9b74b93 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o obj-$(CONFIG_VIDEO_LCD_ENDEAVORU) += endeavoru-panel.o obj-$(CONFIG_VIDEO_LCD_HIMAX_HX8394) += himax-hx8394.o obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o +obj-$(CONFIG_VIDEO_LCD_LG_LD070WX3) += lg-ld070wx3.o obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o obj-$(CONFIG_VIDEO_LCD_RENESAS_R61307) += renesas-r61307.o diff --git a/drivers/video/lg-ld070wx3.c b/drivers/video/lg-ld070wx3.c new file mode 100644 index 00000000000..610a06ffe7b --- /dev/null +++ b/drivers/video/lg-ld070wx3.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LG LD070WX3-SL01 DSI panel driver + * + * Copyright (c) 2023 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include +#include +#include + +struct lg_ld070wx3_priv { + struct udevice *vdd; + struct udevice *vcc; + + struct udevice *backlight; +}; + +static struct display_timing default_timing = { + .pixelclock.typ = 70000000, + .hactive.typ = 800, + .hfront_porch.typ = 32, + .hback_porch.typ = 48, + .hsync_len.typ = 8, + .vactive.typ = 1280, + .vfront_porch.typ = 5, + .vback_porch.typ = 3, + .vsync_len.typ = 1, +}; + +static void dcs_write_one(struct mipi_dsi_device *dsi, u8 cmd, u8 data) +{ + mipi_dsi_dcs_write(dsi, cmd, &data, 1); +} + +static int lg_ld070wx3_enable_backlight(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + struct mipi_dsi_device *dsi = plat->device; + int ret; + + ret = mipi_dsi_dcs_soft_reset(dsi); + if (ret < 0) { + log_debug("%s: failed to soft reset panel: %d\n", + __func__, ret); + return ret; + } + + /* Delay before sending new command after soft reset */ + mdelay(20); + + /* Differential input impedance selection */ + dcs_write_one(dsi, 0xAE, 0x0B); + + /* Enter test mode 1 and 2*/ + dcs_write_one(dsi, 0xEE, 0xEA); + dcs_write_one(dsi, 0xEF, 0x5F); + + /* Increased MIPI CLK driving ability */ + dcs_write_one(dsi, 0xF2, 0x68); + + /* Exit test mode 1 and 2 */ + dcs_write_one(dsi, 0xEE, 0x00); + dcs_write_one(dsi, 0xEF, 0x00); + + return 0; +} + +static int lg_ld070wx3_set_backlight(struct udevice *dev, int percent) +{ + struct lg_ld070wx3_priv *priv = dev_get_priv(dev); + int ret; + + ret = backlight_enable(priv->backlight); + if (ret) + return ret; + + return backlight_set_brightness(priv->backlight, percent); +} + +static int lg_ld070wx3_timings(struct udevice *dev, + struct display_timing *timing) +{ + memcpy(timing, &default_timing, sizeof(*timing)); + return 0; +} + +static int lg_ld070wx3_of_to_plat(struct udevice *dev) +{ + struct lg_ld070wx3_priv *priv = dev_get_priv(dev); + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &priv->backlight); + if (ret) { + log_debug("%s: cannot get backlight: ret = %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "vdd-supply", &priv->vdd); + if (ret) { + log_debug("%s: cannot get vdd-supply: ret = %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "vcc-supply", &priv->vcc); + if (ret) { + log_debug("%s: cannot get vcc-supply: ret = %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lg_ld070wx3_hw_init(struct udevice *dev) +{ + struct lg_ld070wx3_priv *priv = dev_get_priv(dev); + int ret; + + ret = regulator_set_enable_if_allowed(priv->vcc, 1); + if (ret) { + log_debug("%s: enabling vcc-supply failed (%d)\n", + __func__, ret); + return ret; + } + + ret = regulator_set_enable_if_allowed(priv->vdd, 1); + if (ret) { + log_debug("%s: enabling vdd-supply failed (%d)\n", + __func__, ret); + return ret; + } + + /* + * According to spec delay between enabling supply is 0, + * for regulators to reach required voltage ~5ms needed. + * MIPI interface signal for setup requires additional + * 110ms which in total results in 115ms. + */ + mdelay(115); + + return 0; +} + +static int lg_ld070wx3_probe(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + + /* fill characteristics of DSI data link */ + plat->lanes = 4; + plat->format = MIPI_DSI_FMT_RGB888; + plat->mode_flags = MIPI_DSI_MODE_VIDEO; + + return lg_ld070wx3_hw_init(dev); +} + +static const struct panel_ops lg_ld070wx3_ops = { + .enable_backlight = lg_ld070wx3_enable_backlight, + .set_backlight = lg_ld070wx3_set_backlight, + .get_display_timing = lg_ld070wx3_timings, +}; + +static const struct udevice_id lg_ld070wx3_ids[] = { + { .compatible = "lg,ld070wx3-sl01" }, + { } +}; + +U_BOOT_DRIVER(lg_ld070wx3) = { + .name = "lg_ld070wx3", + .id = UCLASS_PANEL, + .of_match = lg_ld070wx3_ids, + .ops = &lg_ld070wx3_ops, + .of_to_plat = lg_ld070wx3_of_to_plat, + .probe = lg_ld070wx3_probe, + .plat_auto = sizeof(struct mipi_dsi_panel_plat), + .priv_auto = sizeof(struct lg_ld070wx3_priv), +}; -- cgit v1.3.1 From d6a6dd90790b886316e7596db6c0d99248ded0e2 Mon Sep 17 00:00:00 2001 From: Anton Bambura Date: Wed, 31 Jan 2024 08:57:16 +0200 Subject: video: panel: add Samsung LTL106HL02 MIPI DSI panel driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LTL106HL02 is a color active matrix TFT (Thin Film Transistor) liquid crystal display (LCD) that uses amorphous silicon TFT as switching devices. This model is composed of a TFT LCD panel, a driver circuit and a backlight unit. The resolution of a 10.6" contains 1920 x 1080 pixels and can display up to 16,8M color with wide viewing angle. Co-developed-by: Jonas Schwöbel Signed-off-by: Jonas Schwöbel Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Anton Bambura --- drivers/video/Kconfig | 9 +++ drivers/video/Makefile | 1 + drivers/video/samsung-ltl106hl02.c | 157 +++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 drivers/video/samsung-ltl106hl02.c (limited to 'drivers') diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 742ea6edee6..7808ae7919e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -555,6 +555,15 @@ config VIDEO_LCD_RENESAS_R69328 IPS-LCD module with Renesas R69328 IC. The panel has a 720x1280 resolution and uses 24 bit RGB per pixel. +config VIDEO_LCD_SAMSUNG_LTL106HL02 + tristate "Samsung LTL106HL02 1920x1080 DSI video mode panel" + depends on PANEL && BACKLIGHT + select VIDEO_MIPI_DSI + help + Say Y here if you want to enable support for Samsung LTL106HL02 + LCD module found in Microsoft Surface 2. The panel has a FullHD + resolution (1920x1080). + config VIDEO_LCD_SSD2828 bool "SSD2828 bridge chip" ---help--- diff --git a/drivers/video/Makefile b/drivers/video/Makefile index bb6d9b74b93..f3f70cd04a1 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o obj-$(CONFIG_VIDEO_LCD_RENESAS_R61307) += renesas-r61307.o obj-$(CONFIG_VIDEO_LCD_RENESAS_R69328) += renesas-r69328.o +obj-$(CONFIG_VIDEO_LCD_SAMSUNG_LTL106HL02) += samsung-ltl106hl02.o obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o obj-$(CONFIG_VIDEO_LCD_TDO_TL070WSH30) += tdo-tl070wsh30.o obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o diff --git a/drivers/video/samsung-ltl106hl02.c b/drivers/video/samsung-ltl106hl02.c new file mode 100644 index 00000000000..5e6c11c4be3 --- /dev/null +++ b/drivers/video/samsung-ltl106hl02.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Samsung LTL106HL02-001 DSI panel driver + * + * Copyright (c) 2020 Anton Bambura + * Copyright (c) 2023 Svyatoslav Ryhel + * Copyright (c) 2024 Jonas Schwöbel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct samsung_ltl106hl02_priv { + struct udevice *vdd; + struct udevice *backlight; + + struct gpio_desc reset_gpio; +}; + +static struct display_timing default_timing = { + .pixelclock.typ = 137000000, + .hactive.typ = 1920, + .hfront_porch.typ = 32, + .hback_porch.typ = 64, + .hsync_len.typ = 32, + .vactive.typ = 1080, + .vfront_porch.typ = 2, + .vback_porch.typ = 26, + .vsync_len.typ = 3, +}; + +static int samsung_ltl106hl02_enable_backlight(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + struct mipi_dsi_device *dsi = plat->device; + int ret; + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + log_debug("%s: failed to exit sleep mode: %d\n", + __func__, ret); + return ret; + } + mdelay(70); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + log_debug("%s: failed to enable display: %d\n", + __func__, ret); + return ret; + } + mdelay(5); + + return 0; +} + +static int samsung_ltl106hl02_set_backlight(struct udevice *dev, int percent) +{ + struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev); + int ret; + + ret = backlight_enable(priv->backlight); + if (ret) + return ret; + + return backlight_set_brightness(priv->backlight, percent); +} + +static int samsung_ltl106hl02_timings(struct udevice *dev, + struct display_timing *timing) +{ + memcpy(timing, &default_timing, sizeof(*timing)); + return 0; +} + +static int samsung_ltl106hl02_of_to_plat(struct udevice *dev) +{ + struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev); + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &priv->backlight); + if (ret) { + log_debug("%s: cannot get backlight: ret = %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "vdd-supply", &priv->vdd); + if (ret) + log_debug("%s: cannot get vdd-supply: error %d\n", + __func__, ret); + + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->reset_gpio, GPIOD_IS_OUT); + if (ret) + log_debug("%s: cannot get reset-gpios: error %d\n", + __func__, ret); + + return 0; +} + +static int samsung_ltl106hl02_hw_init(struct udevice *dev) +{ + struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev); + + dm_gpio_set_value(&priv->reset_gpio, 1); + regulator_set_enable_if_allowed(priv->vdd, 1); + + /* Dataheets states at least 8.5 msec for vdd stabilization */ + mdelay(10); + + dm_gpio_set_value(&priv->reset_gpio, 0); + + return 0; +} + +static int samsung_ltl106hl02_probe(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + + /* fill characteristics of DSI data link */ + plat->lanes = 4; + plat->format = MIPI_DSI_FMT_RGB888; + plat->mode_flags = MIPI_DSI_MODE_VIDEO; + + return samsung_ltl106hl02_hw_init(dev); +} + +static const struct panel_ops samsung_ltl106hl02_ops = { + .enable_backlight = samsung_ltl106hl02_enable_backlight, + .set_backlight = samsung_ltl106hl02_set_backlight, + .get_display_timing = samsung_ltl106hl02_timings, +}; + +static const struct udevice_id samsung_ltl106hl02_ids[] = { + { .compatible = "samsung,ltl106hl02-001" }, + { } +}; + +U_BOOT_DRIVER(samsung_ltl106hl02) = { + .name = "samsung_ltl106hl02", + .id = UCLASS_PANEL, + .of_match = samsung_ltl106hl02_ids, + .ops = &samsung_ltl106hl02_ops, + .of_to_plat = samsung_ltl106hl02_of_to_plat, + .probe = samsung_ltl106hl02_probe, + .plat_auto = sizeof(struct mipi_dsi_panel_plat), + .priv_auto = sizeof(struct samsung_ltl106hl02_priv), +}; -- cgit v1.3.1 From da16cdeb4ed3148744ae31c955bf5fba307e31eb Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 31 Jan 2024 08:57:17 +0200 Subject: video: bridge: add Toshiba TC358768 RGB to DSI bridge support Add initial support for the Toshiba TC358768 RGB to DSI bridge. The driver is based on the mainline Linux Toshiba TC358768 bridge driver and implements the same set of features. Tested-by: Andreas Westman Dorcsak # ASUS TF700T Signed-off-by: Svyatoslav Ryhel --- drivers/video/bridge/Kconfig | 9 + drivers/video/bridge/Makefile | 1 + drivers/video/bridge/tc358768.c | 985 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 995 insertions(+) create mode 100644 drivers/video/bridge/tc358768.c (limited to 'drivers') diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig index 2311ca2d1a5..6a9e7c1454c 100644 --- a/drivers/video/bridge/Kconfig +++ b/drivers/video/bridge/Kconfig @@ -40,3 +40,12 @@ config VIDEO_BRIDGE_SOLOMON_SSD2825 select VIDEO_MIPI_DSI help Solomon SSD2824 SPI RGB-DSI bridge driver wrapped into panel uClass. + +config VIDEO_BRIDGE_TOSHIBA_TC358768 + bool "Support Toshiba TC358768 MIPI DSI bridge" + depends on PANEL && DM_GPIO + select VIDEO_MIPI_DSI + select DM_I2C + help + Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver. + Found in Asus Transformer Infinity TF700T. diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile index 22625c8bc67..ed3e7e9ce1e 100644 --- a/drivers/video/bridge/Makefile +++ b/drivers/video/bridge/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o obj-$(CONFIG_VIDEO_BRIDGE_SOLOMON_SSD2825) += ssd2825.o +obj-$(CONFIG_VIDEO_BRIDGE_TOSHIBA_TC358768) += tc358768.o diff --git a/drivers/video/bridge/tc358768.c b/drivers/video/bridge/tc358768.c new file mode 100644 index 00000000000..19b6ca29d3e --- /dev/null +++ b/drivers/video/bridge/tc358768.c @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Texas Instruments Incorporated + * Copyright (C) 2022 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Global (16-bit addressable) */ +#define TC358768_CHIPID 0x0000 +#define TC358768_SYSCTL 0x0002 +#define TC358768_CONFCTL 0x0004 +#define TC358768_VSDLY 0x0006 +#define TC358768_DATAFMT 0x0008 +#define TC358768_GPIOEN 0x000E +#define TC358768_GPIODIR 0x0010 +#define TC358768_GPIOIN 0x0012 +#define TC358768_GPIOOUT 0x0014 +#define TC358768_PLLCTL0 0x0016 +#define TC358768_PLLCTL1 0x0018 +#define TC358768_CMDBYTE 0x0022 +#define TC358768_PP_MISC 0x0032 +#define TC358768_DSITX_DT 0x0050 +#define TC358768_FIFOSTATUS 0x00F8 + +/* Debug (16-bit addressable) */ +#define TC358768_VBUFCTRL 0x00E0 +#define TC358768_DBG_WIDTH 0x00E2 +#define TC358768_DBG_VBLANK 0x00E4 +#define TC358768_DBG_DATA 0x00E8 + +/* TX PHY (32-bit addressable) */ +#define TC358768_CLW_DPHYCONTTX 0x0100 +#define TC358768_D0W_DPHYCONTTX 0x0104 +#define TC358768_D1W_DPHYCONTTX 0x0108 +#define TC358768_D2W_DPHYCONTTX 0x010C +#define TC358768_D3W_DPHYCONTTX 0x0110 +#define TC358768_CLW_CNTRL 0x0140 +#define TC358768_D0W_CNTRL 0x0144 +#define TC358768_D1W_CNTRL 0x0148 +#define TC358768_D2W_CNTRL 0x014C +#define TC358768_D3W_CNTRL 0x0150 + +/* TX PPI (32-bit addressable) */ +#define TC358768_STARTCNTRL 0x0204 +#define TC358768_DSITXSTATUS 0x0208 +#define TC358768_LINEINITCNT 0x0210 +#define TC358768_LPTXTIMECNT 0x0214 +#define TC358768_TCLK_HEADERCNT 0x0218 +#define TC358768_TCLK_TRAILCNT 0x021C +#define TC358768_THS_HEADERCNT 0x0220 +#define TC358768_TWAKEUP 0x0224 +#define TC358768_TCLK_POSTCNT 0x0228 +#define TC358768_THS_TRAILCNT 0x022C +#define TC358768_HSTXVREGCNT 0x0230 +#define TC358768_HSTXVREGEN 0x0234 +#define TC358768_TXOPTIONCNTRL 0x0238 +#define TC358768_BTACNTRL1 0x023C + +/* TX CTRL (32-bit addressable) */ +#define TC358768_DSI_CONTROL 0x040C +#define TC358768_DSI_STATUS 0x0410 +#define TC358768_DSI_INT 0x0414 +#define TC358768_DSI_INT_ENA 0x0418 +#define TC358768_DSICMD_RDFIFO 0x0430 +#define TC358768_DSI_ACKERR 0x0434 +#define TC358768_DSI_ACKERR_INTENA 0x0438 +#define TC358768_DSI_ACKERR_HALT 0x043c +#define TC358768_DSI_RXERR 0x0440 +#define TC358768_DSI_RXERR_INTENA 0x0444 +#define TC358768_DSI_RXERR_HALT 0x0448 +#define TC358768_DSI_ERR 0x044C +#define TC358768_DSI_ERR_INTENA 0x0450 +#define TC358768_DSI_ERR_HALT 0x0454 +#define TC358768_DSI_CONFW 0x0500 +#define TC358768_DSI_LPCMD 0x0500 +#define TC358768_DSI_RESET 0x0504 +#define TC358768_DSI_INT_CLR 0x050C +#define TC358768_DSI_START 0x0518 + +/* DSITX CTRL (16-bit addressable) */ +#define TC358768_DSICMD_TX 0x0600 +#define TC358768_DSICMD_TYPE 0x0602 +#define TC358768_DSICMD_WC 0x0604 +#define TC358768_DSICMD_WD0 0x0610 +#define TC358768_DSICMD_WD1 0x0612 +#define TC358768_DSICMD_WD2 0x0614 +#define TC358768_DSICMD_WD3 0x0616 +#define TC358768_DSI_EVENT 0x0620 +#define TC358768_DSI_VSW 0x0622 +#define TC358768_DSI_VBPR 0x0624 +#define TC358768_DSI_VACT 0x0626 +#define TC358768_DSI_HSW 0x0628 +#define TC358768_DSI_HBPR 0x062A +#define TC358768_DSI_HACT 0x062C + +/* TC358768_DSI_CONTROL (0x040C) register */ +#define TC358768_DSI_CONTROL_DIS_MODE BIT(15) +#define TC358768_DSI_CONTROL_TXMD BIT(7) +#define TC358768_DSI_CONTROL_HSCKMD BIT(5) +#define TC358768_DSI_CONTROL_EOTDIS BIT(0) + +/* TC358768_DSI_CONFW (0x0500) register */ +#define TC358768_DSI_CONFW_MODE_SET (5 << 29) +#define TC358768_DSI_CONFW_MODE_CLR (6 << 29) +#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL (3 << 24) + +#define NANO 1000000000UL +#define PICO 1000000000000ULL + +struct tc358768_priv { + struct mipi_dsi_host host; + struct mipi_dsi_device device; + + struct udevice *panel; + struct display_timing timing; + + struct udevice *vddc; + struct udevice *vddmipi; + struct udevice *vddio; + + struct clk *refclk; + + struct gpio_desc reset_gpio; + + u32 pd_lines; /* number of Parallel Port Input Data Lines */ + u32 dsi_lanes; /* number of DSI Lanes */ + + /* Parameters for PLL programming */ + u32 fbd; /* PLL feedback divider */ + u32 prd; /* PLL input divider */ + u32 frs; /* PLL Freqency range for HSCK (post divider) */ + + u32 dsiclk; /* pll_clk / 2 */ +}; + +static void tc358768_read(struct udevice *dev, u32 reg, u32 *val) +{ + int count; + u8 buf[4] = { 0, 0, 0, 0 }; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) + count = 2; + else + count = 4; + + dm_i2c_read(dev, reg, buf, count); + *val = (buf[0] << 8) | (buf[1] & 0xff) | + (buf[2] << 24) | (buf[3] << 16); + + log_debug("%s 0x%04x >> 0x%08x\n", + __func__, reg, *val); +} + +static void tc358768_write(struct udevice *dev, u32 reg, u32 val) +{ + int count; + u8 buf[4]; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) + count = 2; + else + count = 4; + + buf[0] = val >> 8; + buf[1] = val & 0xff; + buf[2] = val >> 24; + buf[3] = val >> 16; + + log_debug("%s 0x%04x << 0x%08x\n", + __func__, reg, val); + + dm_i2c_write(dev, reg, buf, count); +} + +static void tc358768_update_bits(struct udevice *dev, u32 reg, u32 mask, + u32 val) +{ + u32 tmp, orig; + + tc358768_read(dev, reg, &orig); + + tmp = orig & ~mask; + tmp |= val & mask; + if (tmp != orig) + tc358768_write(dev, reg, tmp); +} + +static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct udevice *dev = (struct udevice *)host->dev; + struct mipi_dsi_packet packet; + int ret; + + if (msg->rx_len) { + log_debug("%s: MIPI rx is not supported\n", __func__); + return -EOPNOTSUPP; + } + + if (msg->tx_len > 8) { + log_debug("%s: Maximum 8 byte MIPI tx is supported\n", __func__); + return -EOPNOTSUPP; + } + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) + return ret; + + if (mipi_dsi_packet_format_is_short(msg->type)) { + tc358768_write(dev, TC358768_DSICMD_TYPE, + (0x10 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(dev, TC358768_DSICMD_WC, 0); + tc358768_write(dev, TC358768_DSICMD_WD0, + (packet.header[2] << 8) | packet.header[1]); + } else { + int i; + + tc358768_write(dev, TC358768_DSICMD_TYPE, + (0x40 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(dev, TC358768_DSICMD_WC, packet.payload_length); + for (i = 0; i < packet.payload_length; i += 2) { + u16 val = packet.payload[i]; + + if (i + 1 < packet.payload_length) + val |= packet.payload[i + 1] << 8; + + tc358768_write(dev, TC358768_DSICMD_WD0 + i, val); + } + } + + /* start transfer */ + tc358768_write(dev, TC358768_DSICMD_TX, 1); + + return packet.size; +} + +static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = { + .transfer = tc358768_dsi_host_transfer, +}; + +static void tc358768_sw_reset(struct udevice *dev) +{ + /* Assert Reset */ + tc358768_write(dev, TC358768_SYSCTL, 1); + mdelay(5); + + /* Release Reset, Exit Sleep */ + tc358768_write(dev, TC358768_SYSCTL, 0); +} + +static void tc358768_hw_enable(struct tc358768_priv *priv) +{ + int ret; + + ret = clk_prepare_enable(priv->refclk); + if (ret) + log_debug("%s: error enabling refclk (%d)\n", __func__, ret); + + ret = regulator_set_enable_if_allowed(priv->vddc, true); + if (ret) + log_debug("%s: error enabling vddc (%d)\n", __func__, ret); + + ret = regulator_set_enable_if_allowed(priv->vddmipi, true); + if (ret) + log_debug("%s: error enabling vddmipi (%d)\n", __func__, ret); + + mdelay(10); + + ret = regulator_set_enable_if_allowed(priv->vddio, true); + if (ret) + log_debug("%s: error enabling vddio (%d)\n", __func__, ret); + + mdelay(2); + + /* + * The RESX is active low (GPIO_ACTIVE_LOW). + * DEASSERT (value = 0) the reset_gpio to enable the chip + */ + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) + log_debug("%s: error changing reset-gpio (%d)\n", __func__, ret); + + /* wait for encoder clocks to stabilize */ + mdelay(2); +} + +static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk) +{ + return (u32)div_u64((u64)pclk * priv->pd_lines, priv->dsi_lanes); +} + +static int tc358768_calc_pll(struct tc358768_priv *priv, + struct display_timing *dt) +{ + static const u32 frs_limits[] = { + 1000000000, + 500000000, + 250000000, + 125000000, + 62500000 + }; + unsigned long refclk; + u32 prd, target_pll, i, max_pll, min_pll; + u32 frs, best_diff, best_pll, best_prd, best_fbd; + + target_pll = tc358768_pclk_to_pll(priv, dt->pixelclock.typ); + + /* pll_clk = RefClk * FBD / PRD * (1 / (2^FRS)) */ + + for (i = 0; i < ARRAY_SIZE(frs_limits); i++) + if (target_pll >= frs_limits[i]) + break; + + if (i == ARRAY_SIZE(frs_limits) || i == 0) + return -EINVAL; + + frs = i - 1; + max_pll = frs_limits[i - 1]; + min_pll = frs_limits[i]; + + refclk = clk_get_rate(priv->refclk); + + best_diff = UINT_MAX; + best_pll = 0; + best_prd = 0; + best_fbd = 0; + + for (prd = 1; prd <= 16; ++prd) { + u32 divisor = prd * (1 << frs); + u32 fbd; + + for (fbd = 1; fbd <= 512; ++fbd) { + u32 pll, diff, pll_in; + + pll = (u32)div_u64((u64)refclk * fbd, divisor); + + if (pll >= max_pll || pll < min_pll) + continue; + + pll_in = (u32)div_u64((u64)refclk, prd); + if (pll_in < 4000000) + continue; + + diff = max(pll, target_pll) - min(pll, target_pll); + + if (diff < best_diff) { + best_diff = diff; + best_pll = pll; + best_prd = prd; + best_fbd = fbd; + + if (best_diff == 0) + goto found; + } + } + } + + if (best_diff == UINT_MAX) { + log_debug("%s: could not find suitable PLL setup\n", __func__); + return -EINVAL; + } + +found: + priv->fbd = best_fbd; + priv->prd = best_prd; + priv->frs = frs; + priv->dsiclk = best_pll / 2; + + return 0; +} + +static void tc358768_setup_pll(struct udevice *dev) +{ + struct tc358768_priv *priv = dev_get_priv(dev); + u32 fbd, prd, frs; + int ret; + + ret = tc358768_calc_pll(priv, &priv->timing); + if (ret) + log_debug("%s: PLL calculation failed: %d\n", __func__, ret); + + fbd = priv->fbd; + prd = priv->prd; + frs = priv->frs; + + log_debug("%s: PLL: refclk %lu, fbd %u, prd %u, frs %u\n", __func__, + clk_get_rate(priv->refclk), fbd, prd, frs); + log_debug("%s: PLL: pll_clk: %u, DSIClk %u, HSByteClk %u\n", __func__, + priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4); + + /* PRD[15:12] FBD[8:0] */ + tc358768_write(dev, TC358768_PLLCTL0, ((prd - 1) << 12) | (fbd - 1)); + + /* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */ + tc358768_write(dev, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0)); + + /* wait for lock */ + mdelay(5); + + /* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */ + tc358768_write(dev, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0)); +} + +static u32 tc358768_ns_to_cnt(u32 ns, u32 period_ps) +{ + return DIV_ROUND_UP(ns * 1000, period_ps); +} + +static u32 tc358768_ps_to_ns(u32 ps) +{ + return ps / 1000; +} + +static u32 tc358768_dpi_to_ns(u32 val, u32 pclk) +{ + return (u32)div_u64((u64)val * NANO, pclk); +} + +/* Convert value in DPI pixel clock units to DSI byte count */ +static u32 tc358768_dpi_to_dsi_bytes(struct tc358768_priv *priv, u32 val) +{ + u64 m = (u64)val * priv->dsiclk / 4 * priv->dsi_lanes; + u64 n = priv->timing.pixelclock.typ; + + return (u32)div_u64(m + n - 1, n); +} + +static u32 tc358768_dsi_bytes_to_ns(struct tc358768_priv *priv, u32 val) +{ + u64 m = (u64)val * NANO; + u64 n = priv->dsiclk / 4 * priv->dsi_lanes; + + return (u32)div_u64(m, n); +} + +static int tc358768_attach(struct udevice *dev) +{ + struct tc358768_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *dt = &priv->timing; + u32 val, val2, lptxcnt, hact, data_type; + s32 raw_val; + u32 hsbyteclk_ps, dsiclk_ps, ui_ps; + u32 dsiclk, hsbyteclk; + int i; + /* In pixelclock units */ + u32 dpi_htot, dpi_data_start; + /* In byte units */ + u32 dsi_dpi_htot, dsi_dpi_data_start; + u32 dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp; + const u32 dsi_hss = 4; /* HSS is a short packet (4 bytes) */ + /* In hsbyteclk units */ + u32 dsi_vsdly; + const u32 internal_dly = 40; + + if (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + debug("%s: Non-continuous mode unimplemented, falling back to continuous\n", __func__); + device->mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS; + } + + tc358768_hw_enable(priv); + tc358768_sw_reset(dev); + + tc358768_setup_pll(dev); + + dsiclk = priv->dsiclk; + hsbyteclk = dsiclk / 4; + + /* Data Format Control Register */ + val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */ + switch (device->format) { + case MIPI_DSI_FMT_RGB888: + val |= (0x3 << 4); + hact = dt->hactive.typ * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; + break; + case MIPI_DSI_FMT_RGB666: + val |= (0x4 << 4); + hact = dt->hactive.typ * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + val |= (0x4 << 4) | BIT(3); + hact = dt->hactive.typ * 18 / 8; + data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + break; + case MIPI_DSI_FMT_RGB565: + val |= (0x5 << 4); + hact = dt->hactive.typ * 2; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; + break; + default: + log_debug("%s: Invalid data format (%u)\n", + __func__, device->format); + return -EINVAL; + } + + /* + * There are three important things to make TC358768 work correctly, + * which are not trivial to manage: + * + * 1. Keep the DPI line-time and the DSI line-time as close to each + * other as possible. + * 2. TC358768 goes to LP mode after each line's active area. The DSI + * HFP period has to be long enough for entering and exiting LP mode. + * But it is not clear how to calculate this. + * 3. VSDly (video start delay) has to be long enough to ensure that the + * DSI TX does not start transmitting until we have started receiving + * pixel data from the DPI input. It is not clear how to calculate + * this either. + */ + + dpi_htot = dt->hactive.typ + dt->hfront_porch.typ + + dt->hsync_len.typ + dt->hback_porch.typ; + dpi_data_start = dt->hsync_len.typ + dt->hback_porch.typ; + + log_debug("%s: dpi horiz timing (pclk): %u + %u + %u + %u = %u\n", __func__, + dt->hsync_len.typ, dt->hback_porch.typ, dt->hactive.typ, + dt->hfront_porch.typ, dpi_htot); + + log_debug("%s: dpi horiz timing (ns): %u + %u + %u + %u = %u\n", __func__, + tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dt->hactive.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dt->hfront_porch.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dpi_htot, dt->pixelclock.typ)); + + log_debug("%s: dpi data start (ns): %u + %u = %u\n", __func__, + tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ), + tc358768_dpi_to_ns(dpi_data_start, dt->pixelclock.typ)); + + dsi_dpi_htot = tc358768_dpi_to_dsi_bytes(priv, dpi_htot); + dsi_dpi_data_start = tc358768_dpi_to_dsi_bytes(priv, dpi_data_start); + + if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + dsi_hsw = tc358768_dpi_to_dsi_bytes(priv, dt->hsync_len.typ); + dsi_hbp = tc358768_dpi_to_dsi_bytes(priv, dt->hback_porch.typ); + } else { + /* HBP is included in HSW in event mode */ + dsi_hbp = 0; + dsi_hsw = tc358768_dpi_to_dsi_bytes(priv, + dt->hsync_len.typ + + dt->hback_porch.typ); + + /* + * The pixel packet includes the actual pixel data, and: + * DSI packet header = 4 bytes + * DCS code = 1 byte + * DSI packet footer = 2 bytes + */ + dsi_hact = hact + 4 + 1 + 2; + + dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss; + + /* + * Here we should check if HFP is long enough for entering LP + * and exiting LP, but it's not clear how to calculate that. + * Instead, this is a naive algorithm that just adjusts the HFP + * and HSW so that HFP is (at least) roughly 2/3 of the total + * blanking time. + */ + if (dsi_hfp < (dsi_hfp + dsi_hsw + dsi_hss) * 2 / 3) { + u32 old_hfp = dsi_hfp; + u32 old_hsw = dsi_hsw; + u32 tot = dsi_hfp + dsi_hsw + dsi_hss; + + dsi_hsw = tot / 3; + + /* + * Seems like sometimes HSW has to be divisible by num-lanes, but + * not always... + */ + dsi_hsw = roundup(dsi_hsw, priv->dsi_lanes); + + dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss; + + log_debug("%s: hfp too short, adjusting dsi hfp and dsi hsw from %u, %u to %u, %u\n", + __func__, old_hfp, old_hsw, dsi_hfp, dsi_hsw); + } + + log_debug("%s: dsi horiz timing (bytes): %u, %u + %u + %u + %u = %u\n", __func__, + dsi_hss, dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp, + dsi_hss + dsi_hsw + dsi_hbp + dsi_hact + dsi_hfp); + + log_debug("%s: dsi horiz timing (ns): %u + %u + %u + %u + %u = %u\n", __func__, + tc358768_dsi_bytes_to_ns(priv, dsi_hss), + tc358768_dsi_bytes_to_ns(priv, dsi_hsw), + tc358768_dsi_bytes_to_ns(priv, dsi_hbp), + tc358768_dsi_bytes_to_ns(priv, dsi_hact), + tc358768_dsi_bytes_to_ns(priv, dsi_hfp), + tc358768_dsi_bytes_to_ns(priv, dsi_hss + dsi_hsw + + dsi_hbp + dsi_hact + dsi_hfp)); + } + + /* VSDly calculation */ + + /* Start with the HW internal delay */ + dsi_vsdly = internal_dly; + + /* Convert to byte units as the other variables are in byte units */ + dsi_vsdly *= priv->dsi_lanes; + + /* Do we need more delay, in addition to the internal? */ + if (dsi_dpi_data_start > dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp) { + dsi_vsdly = dsi_dpi_data_start - dsi_hss - dsi_hsw - dsi_hbp; + dsi_vsdly = roundup(dsi_vsdly, priv->dsi_lanes); + } + + log_debug("%s: dsi data start (bytes) %u + %u + %u + %u = %u\n", __func__, + dsi_vsdly, dsi_hss, dsi_hsw, dsi_hbp, + dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp); + + log_debug("%s: dsi data start (ns) %u + %u + %u + %u = %u\n", __func__, + tc358768_dsi_bytes_to_ns(priv, dsi_vsdly), + tc358768_dsi_bytes_to_ns(priv, dsi_hss), + tc358768_dsi_bytes_to_ns(priv, dsi_hsw), + tc358768_dsi_bytes_to_ns(priv, dsi_hbp), + tc358768_dsi_bytes_to_ns(priv, dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp)); + + /* Convert back to hsbyteclk */ + dsi_vsdly /= priv->dsi_lanes; + + /* + * The docs say that there is an internal delay of 40 cycles. + * However, we get underflows if we follow that rule. If we + * instead ignore the internal delay, things work. So either + * the docs are wrong or the calculations are wrong. + * + * As a temporary fix, add the internal delay here, to counter + * the subtraction when writing the register. + */ + dsi_vsdly += internal_dly; + + /* Clamp to the register max */ + if (dsi_vsdly - internal_dly > 0x3ff) { + log_warning("%s: VSDly too high, underflows likely\n", __func__); + dsi_vsdly = 0x3ff + internal_dly; + } + + /* VSDly[9:0] */ + tc358768_write(dev, TC358768_VSDLY, dsi_vsdly - internal_dly); + + tc358768_write(dev, TC358768_DATAFMT, val); + tc358768_write(dev, TC358768_DSITX_DT, data_type); + + /* Enable D-PHY (HiZ->LP11) */ + tc358768_write(dev, TC358768_CLW_CNTRL, 0x0000); + /* Enable lanes */ + for (i = 0; i < device->lanes; i++) + tc358768_write(dev, TC358768_D0W_CNTRL + i * 4, 0x0000); + + /* Set up D-PHY CONTTX */ + tc358768_write(dev, TC358768_CLW_DPHYCONTTX, 0x0203); + /* Adjust lanes */ + for (i = 0; i < device->lanes; i++) + tc358768_write(dev, TC358768_D0W_DPHYCONTTX + i * 4, 0x0203); + + /* DSI Timings */ + hsbyteclk_ps = (u32)div_u64(PICO, hsbyteclk); + dsiclk_ps = (u32)div_u64(PICO, dsiclk); + ui_ps = dsiclk_ps / 2; + log_debug("%s: dsiclk: %u ps, ui %u ps, hsbyteclk %u ps\n", + __func__, dsiclk_ps, ui_ps, hsbyteclk_ps); + + /* LP11 > 100us for D-PHY Rx Init */ + val = tc358768_ns_to_cnt(100 * 1000, hsbyteclk_ps) - 1; + log_debug("%s: LINEINITCNT: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_LINEINITCNT, val); + + /* LPTimeCnt > 50ns */ + val = tc358768_ns_to_cnt(50, hsbyteclk_ps) - 1; + lptxcnt = val; + log_debug("%s: LPTXTIMECNT: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_LPTXTIMECNT, val); + + /* 38ns < TCLK_PREPARE < 95ns */ + val = tc358768_ns_to_cnt(65, hsbyteclk_ps) - 1; + log_debug("%s: TCLK_PREPARECNT: 0x%x\n", __func__, val); + /* TCLK_PREPARE + TCLK_ZERO > 300ns */ + val2 = tc358768_ns_to_cnt(300 - tc358768_ps_to_ns(2 * ui_ps), + hsbyteclk_ps) - 2; + log_debug("%s: TCLK_ZEROCNT: 0x%x\n", __func__, val2); + val |= val2 << 8; + tc358768_write(dev, TC358768_TCLK_HEADERCNT, val); + + /* TCLK_TRAIL > 60ns AND TEOT <= 105 ns + 12*UI */ + raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(2 * ui_ps), + hsbyteclk_ps) - 5; + val = clamp(raw_val, 0, 127); + log_debug("%s: TCLK_TRAILCNT: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_TCLK_TRAILCNT, val); + + /* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */ + val = 50 + tc358768_ps_to_ns(4 * ui_ps); + val = tc358768_ns_to_cnt(val, hsbyteclk_ps) - 1; + log_debug("%s: THS_PREPARECNT: 0x%x\n", __func__, val); + /* THS_PREPARE + THS_ZERO > 145ns + 10*UI */ + raw_val = tc358768_ns_to_cnt(145 - tc358768_ps_to_ns(3 * ui_ps), + hsbyteclk_ps) - 10; + val2 = clamp(raw_val, 0, 127); + log_debug("%s: THS_ZEROCNT: 0x%x\n", __func__, val2); + val |= val2 << 8; + tc358768_write(dev, TC358768_THS_HEADERCNT, val); + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358768_ns_to_cnt(1020000, hsbyteclk_ps); + val = val / (lptxcnt + 1) - 1; + log_debug("%s: TWAKEUP: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_TWAKEUP, val); + + /* TCLK_POSTCNT > 60ns + 52*UI */ + val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(52 * ui_ps), + hsbyteclk_ps) - 3; + log_debug("%s: TCLK_POSTCNT: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_TCLK_POSTCNT, val); + + /* max(60ns + 4*UI, 8*UI) < THS_TRAILCNT < 105ns + 12*UI */ + raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(18 * ui_ps), + hsbyteclk_ps) - 4; + val = clamp(raw_val, 0, 15); + log_debug("%s: THS_TRAILCNT: 0x%x\n", __func__, val); + tc358768_write(dev, TC358768_THS_TRAILCNT, val); + + val = BIT(0); + for (i = 0; i < device->lanes; i++) + val |= BIT(i + 1); + tc358768_write(dev, TC358768_HSTXVREGEN, val); + + tc358768_write(dev, TC358768_TXOPTIONCNTRL, + (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0 : BIT(0)); + + /* TXTAGOCNT[26:16] RXTASURECNT[10:0] */ + val = tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps * 4); + val = tc358768_ns_to_cnt(val, hsbyteclk_ps) / 4 - 1; + log_debug("%s: TXTAGOCNT: 0x%x\n", __func__, val); + val2 = tc358768_ns_to_cnt(tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps), + hsbyteclk_ps) - 2; + log_debug("%s: RXTASURECNT: 0x%x\n", __func__, val2); + val = val << 16 | val2; + tc358768_write(dev, TC358768_BTACNTRL1, val); + + /* START[0] */ + tc358768_write(dev, TC358768_STARTCNTRL, 1); + + if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + /* Set pulse mode */ + tc358768_write(dev, TC358768_DSI_EVENT, 0); + + /* vact */ + tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ); + /* vsw */ + tc358768_write(dev, TC358768_DSI_VSW, dt->vsync_len.typ); + /* vbp */ + tc358768_write(dev, TC358768_DSI_VBPR, dt->vback_porch.typ); + } else { + /* Set event mode */ + tc358768_write(dev, TC358768_DSI_EVENT, 1); + + /* vact */ + tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ); + + /* vsw (+ vbp) */ + tc358768_write(dev, TC358768_DSI_VSW, + dt->vsync_len.typ + dt->vback_porch.typ); + /* vbp (not used in event mode) */ + tc358768_write(dev, TC358768_DSI_VBPR, 0); + } + + /* hsw (bytes) */ + tc358768_write(dev, TC358768_DSI_HSW, dsi_hsw); + + /* hbp (bytes) */ + tc358768_write(dev, TC358768_DSI_HBPR, dsi_hbp); + + /* hact (bytes) */ + tc358768_write(dev, TC358768_DSI_HACT, hact); + + /* VSYNC polarity */ + tc358768_update_bits(dev, TC358768_CONFCTL, BIT(5), + (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) ? BIT(5) : 0); + + /* HSYNC polarity */ + tc358768_update_bits(dev, TC358768_PP_MISC, BIT(0), + (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) ? BIT(0) : 0); + + /* Start DSI Tx */ + tc358768_write(dev, TC358768_DSI_START, 0x1); + + /* Configure DSI_Control register */ + val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD | + 0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS; + tc358768_write(dev, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= (device->lanes - 1) << 1; + + val |= TC358768_DSI_CONTROL_TXMD; + + if (!(device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + val |= TC358768_DSI_CONTROL_HSCKMD; + + /* + * TODO: Actually MIPI_DSI_MODE_NO_EOT_PACKET + * + * Many of the DSI flags have names opposite to their + * actual effects, e.g. MIPI_DSI_MODE_EOT_PACKET means + * that EoT packets will actually be disabled. + */ + if (device->mode_flags & MIPI_DSI_MODE_EOT_PACKET) + val |= TC358768_DSI_CONTROL_EOTDIS; + + tc358768_write(dev, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_CLR | + TC358768_DSI_CONFW_ADDR_DSI_CONTROL | + TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */ + tc358768_write(dev, TC358768_DSI_CONFW, val); + + /* clear FrmStop and RstPtr */ + tc358768_update_bits(dev, TC358768_PP_MISC, 0x3 << 14, 0); + + /* set PP_en */ + tc358768_update_bits(dev, TC358768_CONFCTL, BIT(6), BIT(6)); + + /* Set up panel configuration */ + return panel_enable_backlight(priv->panel); +} + +static int tc358768_set_backlight(struct udevice *dev, int percent) +{ + struct tc358768_priv *priv = dev_get_priv(dev); + + return panel_set_backlight(priv->panel, percent); +} + +static int tc358768_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tc358768_priv *priv = dev_get_priv(dev); + + /* Default to positive sync */ + + if (!(priv->timing.flags & + (DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_HSYNC_HIGH))) + priv->timing.flags |= DISPLAY_FLAGS_HSYNC_HIGH; + + if (!(priv->timing.flags & + (DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_VSYNC_HIGH))) + priv->timing.flags |= DISPLAY_FLAGS_VSYNC_HIGH; + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static int tc358768_setup(struct udevice *dev) +{ + struct tc358768_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct mipi_dsi_panel_plat *mipi_plat; + int ret; + + /* The bridge uses 16 bit registers */ + ret = i2c_set_chip_offset_len(dev, 2); + if (ret) { + log_debug("%s: set_chip_offset_len failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, + "panel", &priv->panel); + if (ret) { + log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret); + return log_ret(ret); + } + + panel_get_display_timing(priv->panel, &priv->timing); + + mipi_plat = dev_get_plat(priv->panel); + mipi_plat->device = device; + + priv->host.dev = (struct device *)dev; + priv->host.ops = &tc358768_dsi_host_ops; + + device->host = &priv->host; + device->lanes = mipi_plat->lanes; + device->format = mipi_plat->format; + device->mode_flags = mipi_plat->mode_flags; + + priv->pd_lines = mipi_dsi_pixel_format_to_bpp(device->format); + priv->dsi_lanes = device->lanes; + + /* get regulators */ + ret = device_get_supply_regulator(dev, "vddc-supply", &priv->vddc); + if (ret) { + log_debug("%s: vddc regulator error: %d\n", __func__, ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + ret = device_get_supply_regulator(dev, "vddmipi-supply", &priv->vddmipi); + if (ret) { + log_debug("%s: vddmipi regulator error: %d\n", __func__, ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + ret = device_get_supply_regulator(dev, "vddio-supply", &priv->vddio); + if (ret) { + log_debug("%s: vddio regulator error: %d\n", __func__, ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + /* get clk */ + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) { + log_debug("%s: Could not get refclk: %ld\n", + __func__, PTR_ERR(priv->refclk)); + return PTR_ERR(priv->refclk); + } + + /* get gpios */ + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->reset_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: Could not decode reset-gpios (%d)\n", __func__, ret); + return ret; + } + + dm_gpio_set_value(&priv->reset_gpio, 1); + + return 0; +} + +static int tc358768_probe(struct udevice *dev) +{ + if (device_get_uclass_id(dev->parent) != UCLASS_I2C) + return -EPROTONOSUPPORT; + + return tc358768_setup(dev); +} + +struct panel_ops tc358768_ops = { + .enable_backlight = tc358768_attach, + .set_backlight = tc358768_set_backlight, + .get_display_timing = tc358768_panel_timings, +}; + +static const struct udevice_id tc358768_ids[] = { + { .compatible = "toshiba,tc358768" }, + { .compatible = "toshiba,tc358778" }, + { } +}; + +U_BOOT_DRIVER(tc358768) = { + .name = "tc358768", + .id = UCLASS_PANEL, + .of_match = tc358768_ids, + .ops = &tc358768_ops, + .probe = tc358768_probe, + .priv_auto = sizeof(struct tc358768_priv), +}; -- cgit v1.3.1 From 0afdc32d7c6bbc74169caff94541f54f7368aa16 Mon Sep 17 00:00:00 2001 From: Jonas Schwöbel Date: Wed, 31 Jan 2024 08:57:18 +0200 Subject: video: bridge: add basic support for the Parade DP501 transmitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Parade DP501 is a DP & DVI/HDMI dual-mode transmitter. It enables an RGB/Parallel SOC output to be converted, packed and serialized into either DP or TMDS output device. Only DisplayPort functionality of this transmitter has been implemented and tested. Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel --- drivers/video/bridge/Kconfig | 10 + drivers/video/bridge/Makefile | 1 + drivers/video/bridge/dp501.c | 579 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 590 insertions(+) create mode 100644 drivers/video/bridge/dp501.c (limited to 'drivers') diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig index 6a9e7c1454c..ab917273720 100644 --- a/drivers/video/bridge/Kconfig +++ b/drivers/video/bridge/Kconfig @@ -7,6 +7,16 @@ config VIDEO_BRIDGE requires LVDS, an eDP->LVDS bridge chip can be used to provide the necessary conversion. This option enables support for these devices. +config VIDEO_BRIDGE_PARADE_DP501 + bool "Support Parade DP501 DP & DVI/HDMI dual mode transmitter" + depends on PANEL && DM_GPIO + select DM_I2C + help + The Parade DP501 is a DP & DVI/HDMI dual-mode transmitter. It + enables an RGB/Parallel SOC output to be converted, packed and + serialized into either DP or TMDS output device. Only DisplayPort + functionality of this transmitter has been implemented and tested. + config VIDEO_BRIDGE_PARADE_PS862X bool "Support Parade PS862X DP->LVDS bridge" depends on VIDEO_BRIDGE diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile index ed3e7e9ce1e..58697e3cbe9 100644 --- a/drivers/video/bridge/Makefile +++ b/drivers/video/bridge/Makefile @@ -4,6 +4,7 @@ # Written by Simon Glass obj-$(CONFIG_VIDEO_BRIDGE) += video-bridge-uclass.o +obj-$(CONFIG_VIDEO_BRIDGE_PARADE_DP501) += dp501.o obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o diff --git a/drivers/video/bridge/dp501.c b/drivers/video/bridge/dp501.c new file mode 100644 index 00000000000..095e3e71fed --- /dev/null +++ b/drivers/video/bridge/dp501.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Jonas Schwöbel + * Copyright (C) 2024 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* TOP */ +#define TOPCFG0 0x00 +#define ROMI2C_PRESCALE 0x01 +#define HDCPI2C_PRESCALE 0x02 +#define GPIO 0x03 +#define GPIO_OUT_ENB 0x04 +#define TESTI2C_CTL 0x05 +#define I2CMTIMEOUT 0x06 +#define TOPCFG1 0x07 +#define TOPCFG2 0x08 +#define TOPCFG3 0x09 +#define TOPCFG4 0x0A +#define CLKSWRST 0x0B +#define CADETB_CTL 0x0C + +/* Video Attribute */ +#define HTOTAL_L 0x10 +#define HTOTAL_H 0x11 +#define HSTART_L 0x12 +#define HSTART_H 0x13 +#define HWIDTH_L 0x14 +#define HWIDTH_H 0x15 +#define VTOTAL_L 0x16 +#define VTOTAL_H 0x17 +#define VSTART_L 0x18 +#define VSTART_H 0x19 +#define VHEIGHT_L 0x1A +#define VHEIGHT_H 0x1B +#define HSPHSW_L 0x1C +#define HSPHSW_H 0x1D +#define VSPVSW_L 0x1E +#define VSPVSW_H 0x1F +#define MISC0 0x20 +#define MISC1 0x21 + +/* Video Capture */ +#define VCAPCTRL0 0x24 +#define VCAPCTRL1 0x25 +#define VCAPCTRL2 0x26 +#define VCAPCTRL3 0x27 +#define VCAPCTRL4 0x28 +#define VCAP_MEASURE 0x29 + +/* Main Link Control */ +#define NVID_L 0x2C +#define NVID_M 0x2D +#define NVID_H 0x2E +#define LINK_CTRL0 0x2F +#define LINK_CTRL1 0x30 +#define LINK_DEBUG 0x31 +#define ERR_POS 0x32 +#define ERR_PAT 0x33 +#define LINK_DEB_SEL 0x34 +#define IDLE_PATTERN 0x35 +#define TU_SIZE 0x36 +#define CRC_CTRL 0x37 +#define CRC_OUT 0x38 + +/* AVI-2 InfoFrame */ +#define SD_CTRL0 0x3A +#define SD_CTRL1 0x3B +#define SD_HB0 0x3C +#define SD_HB1 0x3D +#define SD_HB2 0x3E +#define SD_HB3 0x3F +#define SD_DB0 0x40 +#define SD_DB1 0x41 +#define SD_DB2 0x42 +#define SD_DB3 0x43 +#define SD_DB4 0x44 +#define SD_DB5 0x45 +#define SD_DB6 0x46 +#define SD_DB7 0x47 +#define SD_DB8 0x48 +#define SD_DB9 0x49 +#define SD_DB10 0x4A +#define SD_DB11 0x4B +#define SD_DB12 0x4C +#define SD_DB13 0x4D +#define SD_DB14 0x4E +#define SD_DB15 0x4F + +/* Aux Channel and PCS */ +#define DPCD_REV 0X50 +#define MAX_LINK_RATE 0x51 +#define MAX_LANE_COUNT 0x52 +#define MAX_DOWNSPREAD 0x53 +#define NORP 0x54 +#define DOWNSTRMPORT_PRE 0x55 +#define MLINK_CH_CODING 0x56 +#define RCV_P0_CAP0 0x58 +#define RCV_P0_CAP1 0x59 +#define RCV_P1_CAP0 0x5A +#define RCV_P1_CAP1 0x5B +#define DOWNSPREAD_CTL 0x5C +#define LINK_BW 0x5D +#define LANE_CNT 0x5E +#define TRAINING_CTL 0x5F +#define QUALTEST_CTL 0x60 +#define SINK_COUNT 0x61 +#define DEV_SERVICE_IRQ 0x62 +#define LANE01_STATUS 0x63 +#define LANE23_STATUS 0x64 +#define LANE_STATUS_UPDATE 0x65 +#define SINK_STATUS 0x66 +#define AUX_NOISE 0x67 +#define TEST_MODE 0x69 +#define TEST_PATTERN0 0x6A +#define TEST_PATTERN1 0x6B +#define TEST_PATTERN2 0x6C +#define SIGNATURE 0x6D +#define PCSCFG 0x6E +#define AUXCTRL0 0x6f +#define AUXCTRL2 0x70 +#define AUXCTRL1 0x71 +#define HPDCTL0 0x72 +#define HPDCTL1 0x73 +#define LINK_STATE_CTRL 0x74 +#define SWRST 0x75 +#define LINK_IRQ 0x76 +#define AUXIRQ_CTRL 0x77 +#define HPD2_IRQ_CTRL 0x78 +#define SW_TRAIN_CTRL 0x79 +#define SW_DRV_SET 0x7A +#define SW_PRE_SET 0x7B +#define DPCD_ADDR_L 0x7D +#define DPCD_ADDR_M 0x7E +#define DPCD_ADDR_H 0x7F +#define DPCD_LENGTH 0x80 +#define DPCD_WDATA 0x81 +#define DPCD_RDATA 0x82 +#define DPCD_CTL 0x83 +#define DPCD_STATUS 0x84 +#define AUX_STATUS 0x85 +#define I2CTOAUX_RELENGTH 0x86 +#define AUX_RETRY_CTRL 0x87 +#define TIMEOUT_CTRL 0x88 +#define I2CCMD_OPT1 0x89 +#define AUXCMD_ERR_IRQ 0x8A +#define AUXCMD_OPT2 0x8B +#define HDCP_Reserved 0x8C + +/* Audio InfoFrame */ +#define TX_MVID0 0x90 +#define TX_MVID1 0x91 +#define TX_MVID2 0x92 +#define TX_MVID_OFF 0x93 +#define TX_MAUD0 0x94 +#define TX_MAUD1 0x95 +#define TX_MAUD2 0x96 +#define TX_MAUD_OFF 0x97 +#define MN_CTRL 0x98 +#define MOUT0 0x99 +#define MOUT1 0x9A +#define MOUT2 0x9B + +/* Audio Control */ +#define NAUD_L 0x9F +#define NAUD_M 0xA0 +#define NAUD_H 0xA1 +#define AUD_CTRL0 0xA2 +#define AUD_CTRL1 0xA3 +#define LANE_POL 0xAA +#define LANE_EN 0xAB +#define LANE_MAP 0xAC +#define SCR_POLY0 0xAD +#define SCR_POLY1 0xAE +#define PRBS7_POLY 0xAF + +/* Video Pre-process */ +#define MISC_SHDOW 0xB0 +#define VCAPCPCTL0 0xB1 +#define VCAPCPCTL1 0xB2 +#define VCAPCPCTL2 0xB3 +#define CSCPAR 0xB4 +#define I2CTODPCDSTATUS2 0xBA +#define AUXCTL_REG 0xBB + +/* Page 2 */ +#define SEL_PIO1 0x24 +#define SEL_PIO2 0x25 +#define SEL_PIO3 0x26 +#define CHIP_VER_L 0x82 + +struct dp501_priv { + struct udevice *panel; + struct display_timing timing; + + struct udevice *chip2; + + struct udevice *vdd; + struct gpio_desc reset_gpio; + struct gpio_desc enable_gpio; +}; + +static int dp501_sw_init(struct udevice *dev) +{ + struct dp501_priv *priv = dev_get_priv(dev); + int i; + u8 val; + + dm_i2c_reg_write(dev, TOPCFG4, 0x30); + udelay(200); + dm_i2c_reg_write(dev, TOPCFG4, 0x0c); + dm_i2c_reg_write(dev, 0x8f, 0x02); + + /* check for connected panel during 1 msec */ + for (i = 0; i < 5; i++) { + val = dm_i2c_reg_read(dev, 0x8d); + val &= BIT(2); + if (val) + break; + + udelay(200); + } + + if (!val) { + log_debug("%s: panel is not connected!\n", __func__); + return -ENODEV; + } + + dm_i2c_reg_write(priv->chip2, SEL_PIO1, 0x02); + dm_i2c_reg_write(priv->chip2, SEL_PIO2, 0x04); + dm_i2c_reg_write(priv->chip2, SEL_PIO3, 0x10); + + dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0xa0); + dm_i2c_reg_write(dev, 0x8f, 0x02); + dm_i2c_reg_write(dev, TOPCFG1, 0x16); + dm_i2c_reg_write(dev, TOPCFG0, 0x24); + dm_i2c_reg_write(dev, HPD2_IRQ_CTRL, 0x30); + dm_i2c_reg_write(dev, AUXIRQ_CTRL, 0xff); + dm_i2c_reg_write(dev, LINK_IRQ, 0xff); + + /* auto detect DVO timing */ + dm_i2c_reg_write(dev, VCAPCTRL3, 0x30); + + /* reset tpfifo at v blank */ + dm_i2c_reg_write(dev, LINK_CTRL0, 0x82); + + dm_i2c_reg_write(dev, VCAPCTRL4, 0x07); + dm_i2c_reg_write(dev, AUX_RETRY_CTRL, 0x7f); + dm_i2c_reg_write(dev, TIMEOUT_CTRL, 0x1e); + dm_i2c_reg_write(dev, AUXCTL_REG, 0x06); + + /* DPCD readable */ + dm_i2c_reg_write(dev, HPDCTL0, 0xa9); + + /* Scramble on */ + dm_i2c_reg_write(dev, QUALTEST_CTL, 0x00); + + dm_i2c_reg_write(dev, 0x8f, 0x02); + + dm_i2c_reg_write(dev, VCAPCTRL0, 0xc4); + + /* set color depth 8bit (0x00: 6bit; 0x20: 8bit; 0x40: 10bit) */ + dm_i2c_reg_write(dev, MISC0, 0x20); + + dm_i2c_reg_write(dev, VCAPCPCTL2, 0x01); + + /* check if bridge returns ready status */ + for (i = 0; i < 5; i++) { + val = dm_i2c_reg_read(dev, LINK_IRQ); + val &= BIT(0); + if (val) + break; + + udelay(200); + } + + if (!val) { + log_debug("%s: bridge is not ready\n", __func__); + return -ENODEV; + } + + return 0; +} + +static void dpcd_configure(struct udevice *dev, u32 config, bool write) +{ + dm_i2c_reg_write(dev, DPCD_ADDR_L, (u8)(config >> 8)); + dm_i2c_reg_write(dev, DPCD_ADDR_M, (u8)(config >> 16)); + dm_i2c_reg_write(dev, DPCD_ADDR_H, (u8)((config >> 24) | BIT(7))); + dm_i2c_reg_write(dev, DPCD_LENGTH, 0x00); + dm_i2c_reg_write(dev, LINK_IRQ, 0x20); + + if (write) + dm_i2c_reg_write(dev, DPCD_WDATA, (u8)(config & 0xff)); + + dm_i2c_reg_write(dev, DPCD_CTL, 0x01); + + udelay(10); +} + +static int dump_dpcd_data(struct udevice *dev, u32 config, u8 *data) +{ + int i; + u8 value; + + dpcd_configure(dev, config, false); + + value = dm_i2c_reg_read(dev, DPCD_CTL); + if (value) + return -ENODATA; + + for (i = 0; i < 5; i++) { + value = dm_i2c_reg_read(dev, LINK_IRQ); + value &= BIT(5); + if (value) + break; + + udelay(100); + } + + if (!value) + return -ENODATA; + + value = dm_i2c_reg_read(dev, DPCD_STATUS); + if (!(value & 0xe0)) + *data = dm_i2c_reg_read(dev, DPCD_RDATA); + else + return -ENODATA; + + return 0; +} + +static int dp501_dpcd_dump(struct udevice *dev, u32 config, u8 *data) +{ + int i, ret; + + for (i = 0; i < 5; i++) { + ret = dump_dpcd_data(dev, config, data); + if (!ret) + break; + + udelay(100); + } + + return ret; +} + +static int dp501_reset_link(struct udevice *dev) +{ + dm_i2c_reg_write(dev, TRAINING_CTL, 0x00); + dm_i2c_reg_write(dev, SWRST, 0xf8); + dm_i2c_reg_write(dev, SWRST, 0x00); + + return -ENODEV; +} + +static int dp501_link_training(struct udevice *dev) +{ + int i, ret; + u8 lane, link, link_out; + u8 lane_cnt, lane01, lane23; + + dpcd_configure(dev, 0x030000, true); + dpcd_configure(dev, 0x03011c, true); + dpcd_configure(dev, 0x0301f8, true); + + ret = dp501_dpcd_dump(dev, 0x90000100, &link); + if (ret) { + log_debug("%s: link dump failed %d\n", __func__, ret); + return dp501_reset_link(dev); + } + + ret = dp501_dpcd_dump(dev, 0x90000200, &lane); + if (ret) { + log_debug("%s: lane dump failed %d\n", __func__, ret); + return dp501_reset_link(dev); + } + + /* Software trainig */ + for (i = 10; i > 0; i--) { + dm_i2c_reg_write(dev, LINK_BW, link); + dm_i2c_reg_write(dev, LANE_CNT, lane | BIT(7)); + + link_out = dm_i2c_reg_read(dev, LINK_BW); + lane_cnt = dm_i2c_reg_read(dev, LANE_CNT); + + if (link_out == link && + (lane_cnt == (lane | BIT(7)))) + break; + + udelay(500); + } + + if (!i) + return dp501_reset_link(dev); + + dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0x00); + dm_i2c_reg_write(dev, TRAINING_CTL, 0x0d); + + /* check if bridge returns link ready status */ + for (i = 0; i < 100; i++) { + link_out = dm_i2c_reg_read(dev, LINK_IRQ); + link_out &= BIT(1); + if (link_out) { + dm_i2c_reg_write(dev, LINK_IRQ, 0xff); + break; + } + + udelay(100); + } + + if (!link_out) { + log_debug("%s: link prepare failed %d\n", + __func__, link_out); + return dp501_reset_link(dev); + } + + lane01 = dm_i2c_reg_read(dev, LANE01_STATUS); + lane23 = dm_i2c_reg_read(dev, LANE23_STATUS); + + switch (lane_cnt & 0xf) { + case 4: + if (lane01 == 0x77 && + lane23 == 0x77) + return 0; + break; + + case 2: + if (lane01 == 0x77) + return 0; + break; + + default: + if ((lane01 & 7) == 7) + return 0; + break; + } + + return dp501_reset_link(dev); +} + +static int dp501_attach(struct udevice *dev) +{ + struct dp501_priv *priv = dev_get_priv(dev); + int ret; + + ret = dp501_sw_init(dev); + if (ret) + return ret; + + mdelay(90); + + ret = dp501_link_training(dev); + if (ret) + return ret; + + /* Perform panel HW setup */ + return panel_enable_backlight(priv->panel); +} + +static int dp501_set_backlight(struct udevice *dev, int percent) +{ + struct dp501_priv *priv = dev_get_priv(dev); + + return panel_set_backlight(priv->panel, percent); +} + +static int dp501_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct dp501_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + return 0; +} + +static void dp501_hw_init(struct dp501_priv *priv) +{ + dm_gpio_set_value(&priv->reset_gpio, 1); + + regulator_set_enable_if_allowed(priv->vdd, 1); + dm_gpio_set_value(&priv->enable_gpio, 1); + + udelay(100); + + dm_gpio_set_value(&priv->reset_gpio, 0); + mdelay(80); +} + +static int dp501_setup(struct udevice *dev) +{ + struct dm_i2c_chip *chip = dev_get_parent_plat(dev); + struct dp501_priv *priv = dev_get_priv(dev); + struct udevice *bus = dev_get_parent(dev); + int ret; + + /* get panel */ + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, + "panel", &priv->panel); + if (ret) { + log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret); + return log_ret(ret); + } + + /* get regulators */ + ret = device_get_supply_regulator(dev, "power-supply", &priv->vdd); + if (ret) { + log_debug("%s: vddc regulator error: %d\n", __func__, ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + /* get gpios */ + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->reset_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: Could not decode reset-gpios (%d)\n", + __func__, ret); + return ret; + } + + ret = gpio_request_by_name(dev, "enable-gpios", 0, + &priv->enable_gpio, GPIOD_IS_OUT); + if (ret) { + log_debug("%s: Could not decode enable-gpios (%d)\n", + __func__, ret); + return ret; + } + + ret = i2c_get_chip(bus, chip->chip_addr + 2, 1, &priv->chip2); + if (ret) { + log_debug("%s: cannot get second PMIC I2C chip (err %d)\n", + __func__, ret); + return ret; + } + + dp501_hw_init(priv); + + /* get EDID */ + return panel_get_display_timing(priv->panel, &priv->timing); +} + +static int dp501_probe(struct udevice *dev) +{ + if (device_get_uclass_id(dev->parent) != UCLASS_I2C) + return -EPROTONOSUPPORT; + + return dp501_setup(dev); +} + +struct panel_ops dp501_ops = { + .enable_backlight = dp501_attach, + .set_backlight = dp501_set_backlight, + .get_display_timing = dp501_panel_timings, +}; + +static const struct udevice_id dp501_ids[] = { + { .compatible = "parade,dp501" }, + { } +}; + +U_BOOT_DRIVER(dp501) = { + .name = "dp501", + .id = UCLASS_PANEL, + .of_match = dp501_ids, + .ops = &dp501_ops, + .probe = dp501_probe, + .priv_auto = sizeof(struct dp501_priv), +}; -- cgit v1.3.1 From 2a9ce5f127477235863611d40339b63e79cce39e Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 31 Jan 2024 08:57:19 +0200 Subject: video: endeavoru-panel: shift the init sequence by one step earlier Shift all setup stages one step earlier to better fit the existing uclass. Signed-off-by: Svyatoslav Ryhel --- drivers/video/endeavoru-panel.c | 128 +++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 60 deletions(-) (limited to 'drivers') diff --git a/drivers/video/endeavoru-panel.c b/drivers/video/endeavoru-panel.c index 79a272128b8..1bff641434e 100644 --- a/drivers/video/endeavoru-panel.c +++ b/drivers/video/endeavoru-panel.c @@ -57,61 +57,8 @@ static void dcs_write_one(struct mipi_dsi_device *dsi, u8 cmd, u8 data) static int endeavoru_panel_enable_backlight(struct udevice *dev) { - struct endeavoru_panel_priv *priv = dev_get_priv(dev); - int ret; - - ret = dm_gpio_set_value(&priv->reset_gpio, 1); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(5); - - ret = regulator_set_enable_if_allowed(priv->vddio, 1); - if (ret) { - log_err("error enabling iovcc-supply (%d)\n", ret); - return ret; - } - mdelay(1); - - ret = regulator_set_enable_if_allowed(priv->vdd, 1); - if (ret) { - log_err("error enabling vcc-supply (%d)\n", ret); - return ret; - } - mdelay(20); - - ret = dm_gpio_set_value(&priv->reset_gpio, 0); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(2); - - /* Reset panel */ - ret = dm_gpio_set_value(&priv->reset_gpio, 1); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(1); - - ret = dm_gpio_set_value(&priv->reset_gpio, 0); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(25); - - return 0; -} - -static int endeavoru_panel_set_backlight(struct udevice *dev, int percent) -{ - struct endeavoru_panel_priv *priv = dev_get_priv(dev); struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); struct mipi_dsi_device *dsi = plat->device; - int ret; dcs_write_one(dsi, 0xc2, 0x08); @@ -160,18 +107,22 @@ static int endeavoru_panel_set_backlight(struct udevice *dev, int percent) dcs_write_one(dsi, 0x55, 0x80); dcs_write_one(dsi, 0x5e, 0x06); - ret = backlight_enable(priv->backlight); - if (ret) - return ret; - /* Set backlight */ dcs_write_one(dsi, 0x51, 0x96); - ret = backlight_set_brightness(priv->backlight, percent); + return 0; +} + +static int endeavoru_panel_set_backlight(struct udevice *dev, int percent) +{ + struct endeavoru_panel_priv *priv = dev_get_priv(dev); + int ret; + + ret = backlight_enable(priv->backlight); if (ret) return ret; - return 0; + return backlight_set_brightness(priv->backlight, percent); } static int endeavoru_panel_timings(struct udevice *dev, @@ -217,6 +168,63 @@ static int endeavoru_panel_of_to_plat(struct udevice *dev) return 0; } +static int endeavoru_panel_hw_init(struct udevice *dev) +{ + struct endeavoru_panel_priv *priv = dev_get_priv(dev); + int ret; + + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(5); + + ret = regulator_set_enable_if_allowed(priv->vddio, 1); + if (ret) { + log_debug("%s: error enabling iovcc-supply (%d)\n", + __func__, ret); + return ret; + } + mdelay(1); + + ret = regulator_set_enable_if_allowed(priv->vdd, 1); + if (ret) { + log_debug("%s: error enabling vcc-supply (%d)\n", + __func__, ret); + return ret; + } + mdelay(20); + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(2); + + /* Reset panel */ + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(1); + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(25); + + return 0; +} + static int endeavoru_panel_probe(struct udevice *dev) { struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); @@ -226,7 +234,7 @@ static int endeavoru_panel_probe(struct udevice *dev) plat->format = MIPI_DSI_FMT_RGB888; plat->mode_flags = MIPI_DSI_MODE_VIDEO; - return 0; + return endeavoru_panel_hw_init(dev); } static const struct panel_ops endeavoru_panel_ops = { -- cgit v1.3.1 From 3573f2f64c832820a44e9d6b470c2bc23b77a406 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 31 Jan 2024 08:57:20 +0200 Subject: video: bridge: ssd2825: shift the init sequence by one step earlier Shift all setup stages one step earlier to better fit the existing uclass. Signed-off-by: Svyatoslav Ryhel --- drivers/video/bridge/ssd2825.c | 86 ++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 41 deletions(-) (limited to 'drivers') diff --git a/drivers/video/bridge/ssd2825.c b/drivers/video/bridge/ssd2825.c index cea20dcffa5..f0ef3dafb93 100644 --- a/drivers/video/bridge/ssd2825.c +++ b/drivers/video/bridge/ssd2825.c @@ -349,39 +349,6 @@ static int ssd2825_bridge_enable_panel(struct udevice *dev) struct ssd2825_bridge_priv *priv = dev_get_priv(dev); struct mipi_dsi_device *device = &priv->device; struct display_timing *dt = &priv->timing; - int ret; - - ret = clk_prepare_enable(priv->tx_clk); - if (ret) { - log_err("error enabling tx_clk (%d)\n", ret); - return ret; - } - - ret = dm_gpio_set_value(&priv->power_gpio, 1); - if (ret) { - log_err("error changing power-gpios (%d)\n", ret); - return ret; - } - mdelay(10); - - ret = dm_gpio_set_value(&priv->reset_gpio, 0); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(10); - - ret = dm_gpio_set_value(&priv->reset_gpio, 1); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(10); - - /* Perform panel HW setup */ - ret = panel_enable_backlight(priv->panel); - if (ret) - return ret; /* Perform SW reset */ ssd2825_write_register(dev, SSD2825_OPERATION_CTRL_REG, 0x0100); @@ -417,17 +384,15 @@ static int ssd2825_bridge_enable_panel(struct udevice *dev) SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT); ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000); - /* Set up SW panel configuration */ - ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); - if (ret) - return ret; - - return 0; + /* Perform panel setup */ + return panel_enable_backlight(priv->panel); } static int ssd2825_bridge_set_panel(struct udevice *dev, int percent) { - return 0; + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + + return panel_set_backlight(priv->panel, percent); } static int ssd2825_bridge_panel_timings(struct udevice *dev, @@ -440,6 +405,45 @@ static int ssd2825_bridge_panel_timings(struct udevice *dev, return 0; } +static int ssd2825_bridge_hw_init(struct udevice *dev) +{ + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + int ret; + + ret = clk_prepare_enable(priv->tx_clk); + if (ret) { + log_debug("%s: error enabling tx_clk (%d)\n", + __func__, ret); + return ret; + } + + ret = dm_gpio_set_value(&priv->power_gpio, 1); + if (ret) { + log_debug("%s: error changing power-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(10); + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(10); + + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(10); + + return 0; +} + static int ssd2825_bridge_probe(struct udevice *dev) { struct ssd2825_bridge_priv *priv = dev_get_priv(dev); @@ -496,7 +500,7 @@ static int ssd2825_bridge_probe(struct udevice *dev) return PTR_ERR(priv->tx_clk); } - return 0; + return ssd2825_bridge_hw_init(dev); } static const struct panel_ops ssd2825_bridge_ops = { -- cgit v1.3.1 From 9970634f83f313e5eac18a77f740a89d9f83a712 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 31 Jan 2024 08:57:21 +0200 Subject: video: renesas: shift the init sequence by one step earlier Shift all setup stages one step earlier to better fit the existing uclass. Signed-off-by: Svyatoslav Ryhel --- drivers/video/renesas-r61307.c | 93 +++++++++++++++++++++++------------------- drivers/video/renesas-r69328.c | 81 +++++++++++++++++++----------------- 2 files changed, 95 insertions(+), 79 deletions(-) (limited to 'drivers') diff --git a/drivers/video/renesas-r61307.c b/drivers/video/renesas-r61307.c index 426fdc6224a..1eccaf6b1be 100644 --- a/drivers/video/renesas-r61307.c +++ b/drivers/video/renesas-r61307.c @@ -118,42 +118,6 @@ static struct display_timing default_timing = { }; static int renesas_r61307_enable_backlight(struct udevice *dev) -{ - struct renesas_r61307_priv *priv = dev_get_priv(dev); - int ret; - - ret = regulator_set_enable_if_allowed(priv->vcc, 1); - if (ret) { - log_err("enabling vcc-supply failed (%d)\n", ret); - return ret; - } - mdelay(5); - - ret = regulator_set_enable_if_allowed(priv->iovcc, 1); - if (ret) { - log_err("enabling iovcc-supply failed (%d)\n", ret); - return ret; - } - - ret = dm_gpio_set_value(&priv->reset_gpio, 0); - if (ret) { - log_err("changing reset-gpio failed (%d)\n", ret); - return ret; - } - mdelay(5); - - ret = dm_gpio_set_value(&priv->reset_gpio, 1); - if (ret) { - log_err("changing reset-gpio failed (%d)\n", ret); - return ret; - } - - mdelay(5); - - return 0; -} - -static int renesas_r61307_set_backlight(struct udevice *dev, int percent) { struct renesas_r61307_priv *priv = dev_get_priv(dev); struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); @@ -205,18 +169,23 @@ static int renesas_r61307_set_backlight(struct udevice *dev, int percent) log_err("failed to set display on: %d\n", ret); return ret; } - mdelay(50); + return 0; +} + +static int renesas_r61307_set_backlight(struct udevice *dev, int percent) +{ + struct renesas_r61307_priv *priv = dev_get_priv(dev); + int ret; + ret = backlight_enable(priv->backlight); if (ret) return ret; - ret = backlight_set_brightness(priv->backlight, percent); - if (ret) - return ret; + mdelay(5); - return 0; + return backlight_set_brightness(priv->backlight, percent); } static int renesas_r61307_timings(struct udevice *dev, @@ -266,6 +235,46 @@ static int renesas_r61307_of_to_plat(struct udevice *dev) return 0; } +static int renesas_r61307_hw_init(struct udevice *dev) +{ + struct renesas_r61307_priv *priv = dev_get_priv(dev); + int ret; + + ret = regulator_set_enable_if_allowed(priv->vcc, 1); + if (ret) { + log_debug("%s: enabling vcc-supply failed (%d)\n", + __func__, ret); + return ret; + } + mdelay(5); + + ret = regulator_set_enable_if_allowed(priv->iovcc, 1); + if (ret) { + log_debug("%s: enabling iovcc-supply failed (%d)\n", + __func__, ret); + return ret; + } + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_debug("%s: changing reset-gpio failed (%d)\n", + __func__, ret); + return ret; + } + mdelay(5); + + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_debug("%s: changing reset-gpio failed (%d)\n", + __func__, ret); + return ret; + } + + mdelay(5); + + return 0; +} + static int renesas_r61307_probe(struct udevice *dev) { struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); @@ -275,7 +284,7 @@ static int renesas_r61307_probe(struct udevice *dev) plat->format = MIPI_DSI_FMT_RGB888; plat->mode_flags = MIPI_DSI_MODE_VIDEO; - return 0; + return renesas_r61307_hw_init(dev); } static const struct panel_ops renesas_r61307_ops = { diff --git a/drivers/video/renesas-r69328.c b/drivers/video/renesas-r69328.c index d2f71694681..ecf89ec021b 100644 --- a/drivers/video/renesas-r69328.c +++ b/drivers/video/renesas-r69328.c @@ -65,37 +65,6 @@ static struct display_timing default_timing = { static int renesas_r69328_enable_backlight(struct udevice *dev) { - struct renesas_r69328_priv *priv = dev_get_priv(dev); - int ret; - - ret = dm_gpio_set_value(&priv->enable_gpio, 1); - if (ret) { - log_err("error changing enable-gpios (%d)\n", ret); - return ret; - } - mdelay(5); - - ret = dm_gpio_set_value(&priv->reset_gpio, 0); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - mdelay(5); - - ret = dm_gpio_set_value(&priv->reset_gpio, 1); - if (ret) { - log_err("error changing reset-gpios (%d)\n", ret); - return ret; - } - - mdelay(5); - - return 0; -} - -static int renesas_r69328_set_backlight(struct udevice *dev, int percent) -{ - struct renesas_r69328_priv *priv = dev_get_priv(dev); struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); struct mipi_dsi_device *dsi = plat->device; int ret; @@ -153,18 +122,23 @@ static int renesas_r69328_set_backlight(struct udevice *dev, int percent) log_err("failed to set display on: %d\n", ret); return ret; } - mdelay(50); + return 0; +} + +static int renesas_r69328_set_backlight(struct udevice *dev, int percent) +{ + struct renesas_r69328_priv *priv = dev_get_priv(dev); + int ret; + ret = backlight_enable(priv->backlight); if (ret) return ret; - ret = backlight_set_brightness(priv->backlight, percent); - if (ret) - return ret; + mdelay(5); - return 0; + return backlight_set_brightness(priv->backlight, percent); } static int renesas_r69328_timings(struct udevice *dev, @@ -203,6 +177,39 @@ static int renesas_r69328_of_to_plat(struct udevice *dev) return 0; } +static int renesas_r69328_hw_init(struct udevice *dev) +{ + struct renesas_r69328_priv *priv = dev_get_priv(dev); + int ret; + + ret = dm_gpio_set_value(&priv->enable_gpio, 1); + if (ret) { + log_debug("%s: error changing enable-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(5); + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + mdelay(5); + + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_debug("%s: error changing reset-gpios (%d)\n", + __func__, ret); + return ret; + } + + mdelay(5); + + return 0; +} + static int renesas_r69328_probe(struct udevice *dev) { struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); @@ -212,7 +219,7 @@ static int renesas_r69328_probe(struct udevice *dev) plat->format = MIPI_DSI_FMT_RGB888; plat->mode_flags = MIPI_DSI_MODE_VIDEO; - return 0; + return renesas_r69328_hw_init(dev); } static const struct panel_ops renesas_r69328_ops = { -- cgit v1.3.1 From 91e9687b49e7929effcc4464659c31d2ccb46e7b Mon Sep 17 00:00:00 2001 From: Caleb Connolly Date: Fri, 16 Feb 2024 18:38:06 +0000 Subject: video: simplefb: modernise DT parsing simplefb was using old style FDT parsing which doesn't behave well in combination with livetree. Update it to use ofnode instead and add a missing null check for the "format" property. Standardise the error logging while we're here. Fixes: 971d7e64245d ("video: simplefb") Signed-off-by: Caleb Connolly --- drivers/video/simplefb.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/video/simplefb.c b/drivers/video/simplefb.c index 235ec761f70..33bb78bc3a3 100644 --- a/drivers/video/simplefb.c +++ b/drivers/video/simplefb.c @@ -15,14 +15,14 @@ static int simple_video_probe(struct udevice *dev) { struct video_uc_plat *plat = dev_get_uclass_plat(dev); struct video_priv *uc_priv = dev_get_uclass_priv(dev); - const void *blob = gd->fdt_blob; - const int node = dev_of_offset(dev); + ofnode node = dev_ofnode(dev); const char *format; + int ret; fdt_addr_t base; fdt_size_t size; + u32 width, height, rot; - base = fdtdec_get_addr_size_auto_parent(blob, dev_of_offset(dev->parent), - node, "reg", 0, &size, false); + base = dev_read_addr_size(dev, &size); if (base == FDT_ADDR_T_NONE) { debug("%s: Failed to decode memory region\n", __func__); return -EINVAL; @@ -41,17 +41,25 @@ static int simple_video_probe(struct udevice *dev) debug("%s: Query resolution...\n", __func__); - uc_priv->xsize = fdtdec_get_uint(blob, node, "width", 0); - uc_priv->ysize = fdtdec_get_uint(blob, node, "height", 0); - uc_priv->rot = fdtdec_get_uint(blob, node, "rot", 0); - if (uc_priv->rot > 3) { - log_debug("%s: invalid rot\n", __func__); - return log_msg_ret("rot", -EINVAL); + ret = ofnode_read_u32(node, "width", &width); + ret = ret ?: ofnode_read_u32(node, "height", &height); + if (ret || !width || !height) { + log_err("%s: invalid width or height: %d\n", __func__, ret); + return ret ?: -EINVAL; } + ofnode_read_u32(node, "rot", &rot); + uc_priv->rot = rot; + uc_priv->xsize = width; + uc_priv->ysize = height; - format = fdt_getprop(blob, node, "format", NULL); + format = ofnode_read_string(node, "format"); debug("%s: %dx%d@%s\n", __func__, uc_priv->xsize, uc_priv->ysize, format); + if (!format) { + log_err("%s: please add required property \"format\"\n", __func__); + return -EINVAL; + } + if (strcmp(format, "r5g6b5") == 0) { uc_priv->bpix = VIDEO_BPP16; } else if (strcmp(format, "a8b8g8r8") == 0 || @@ -67,7 +75,7 @@ static int simple_video_probe(struct udevice *dev) uc_priv->bpix = VIDEO_BPP32; uc_priv->format = VIDEO_X2R10G10B10; } else { - printf("%s: invalid format: %s\n", __func__, format); + log_err("%s: invalid format: %s\n", __func__, format); return -EINVAL; } -- cgit v1.3.1 From 4ac7ffb60ee9a342337dce6e4fda3cbac827c398 Mon Sep 17 00:00:00 2001 From: Devarsh Thakkar Date: Thu, 22 Feb 2024 18:38:09 +0530 Subject: video: Assume video to be active if SPL is passing video hand-off If SPL is passing video handoff structure to U-boot then it is safe to assume that SPL has already enabled video and that's why it is passing video handoff structure to U-boot so that U-boot can preserve the framebuffer. Signed-off-by: Devarsh Thakkar Reviewed-by: Nikhil M Jain --- drivers/video/video-uclass.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 3571e62ba2d..7b5d1dfbb3b 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -404,6 +404,10 @@ bool video_is_active(void) { struct udevice *dev; + /* Assume video to be active if SPL passed video hand-off to U-boot */ + if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && spl_phase() > PHASE_SPL) + return true; + for (uclass_find_first_device(UCLASS_VIDEO, &dev); dev; uclass_find_next_device(&dev)) { -- cgit v1.3.1