diff options
| author | Tom Rini <[email protected]> | 2025-04-11 09:11:38 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2025-04-11 09:11:38 -0600 |
| commit | 295376ce8a32268a1bb0879eb0448e0c0e580727 (patch) | |
| tree | 920c68e2656b02c2c3eb3ec49de6dec42214fb69 /drivers | |
| parent | dea298c62e904dd697e7b91bd3dae5d839f31d8f (diff) | |
| parent | ba71e4ef4d3d3edc05c29e14f0ed84ecce4bb051 (diff) | |
Merge tag 'u-boot-imx-master-20250411' of https://gitlab.denx.de/u-boot/custodians/u-boot-imx
CI: https://source.denx.de/u-boot/custodians/u-boot-imx/-/pipelines/25652
- Add i.MX8MP LDB support.
- Various phycore-imx93 environment improvements.
- Add support for Toradex SMARC iMX8MP.
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/clk/clk-uclass.c | 19 | ||||
| -rw-r--r-- | drivers/clk/imx/clk-imx8mp.c | 69 | ||||
| -rw-r--r-- | drivers/core/ofnode_graph.c | 2 | ||||
| -rw-r--r-- | drivers/core/uclass.c | 19 | ||||
| -rw-r--r-- | drivers/firmware/scmi/sandbox-scmi_devices.c | 1 | ||||
| -rw-r--r-- | drivers/firmware/scmi/smt.c | 13 | ||||
| -rw-r--r-- | drivers/power/domain/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/power/domain/Makefile | 1 | ||||
| -rw-r--r-- | drivers/power/domain/imx8m-power-domain.c | 17 | ||||
| -rw-r--r-- | drivers/power/domain/imx8mp-mediamix.c | 208 | ||||
| -rw-r--r-- | drivers/power/domain/power-domain-uclass.c | 40 | ||||
| -rw-r--r-- | drivers/power/domain/sandbox-power-domain-test.c | 1 | ||||
| -rw-r--r-- | drivers/video/Makefile | 2 | ||||
| -rw-r--r-- | drivers/video/imx/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/video/imx/Makefile | 4 | ||||
| -rw-r--r-- | drivers/video/imx/lcdif.c | 314 | ||||
| -rw-r--r-- | drivers/video/imx/ldb.c | 251 |
17 files changed, 967 insertions, 10 deletions
diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 90b70529a47..4b3d812f9c6 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -623,14 +623,27 @@ int clk_set_parent(struct clk *clk, struct clk *parent) if (!ops->set_parent) return -ENOSYS; + ret = clk_enable(parent); + if (ret) { + printf("Cannot enable parent %s\n", parent->dev->name); + return ret; + } + ret = ops->set_parent(clk, parent); - if (ret) + if (ret) { + clk_disable(parent); return ret; + } - if (CONFIG_IS_ENABLED(CLK_CCF)) + if (CONFIG_IS_ENABLED(CLK_CCF)) { ret = device_reparent(clk->dev, parent->dev); + if (ret) { + clk_disable(parent); + return ret; + } + } - return ret; + return 0; } int clk_enable(struct clk *clk) diff --git a/drivers/clk/imx/clk-imx8mp.c b/drivers/clk/imx/clk-imx8mp.c index bad579f8d5e..3d8ed21d3b9 100644 --- a/drivers/clk/imx/clk-imx8mp.c +++ b/drivers/clk/imx/clk-imx8mp.c @@ -14,7 +14,14 @@ #include "clk.h" +#if CONFIG_IS_ENABLED(VIDEO) +static u32 share_count_media; +#endif + static const char * const pll_ref_sels[] = { "osc_24m", "dummy", "dummy", "dummy", }; +#if CONFIG_IS_ENABLED(VIDEO) +static const char * const video_pll1_bypass_sels[] = {"video_pll1", "video_pll1_ref_sel", }; +#endif static const char * const dram_pll_bypass_sels[] = {"dram_pll", "dram_pll_ref_sel", }; static const char * const arm_pll_bypass_sels[] = {"arm_pll", "arm_pll_ref_sel", }; static const char * const sys_pll1_bypass_sels[] = {"sys_pll1", "sys_pll1_ref_sel", }; @@ -31,6 +38,12 @@ static const char * const imx8mp_hsio_axi_sels[] = {"osc_24m", "sys_pll2_500m", "sys_pll2_100m", "sys_pll2_200m", "clk_ext2", "clk_ext4", "audio_pll2_out", }; +#if CONFIG_IS_ENABLED(VIDEO) +static const char * const imx8mp_media_isp_sels[] = {"osc_24m", "sys_pll2_1000m", "sys_pll1_800m", + "sys_pll3_out", "sys_pll1_400m", "audio_pll2_out", + "clk_ext1", "sys_pll2_500m", }; +#endif + static const char * const imx8mp_main_axi_sels[] = {"osc_24m", "sys_pll2_333m", "sys_pll1_800m", "sys_pll2_250m", "sys_pll2_1000m", "audio_pll1_out", "video_pll1_out", "sys_pll1_100m",}; @@ -43,6 +56,16 @@ static const char * const imx8mp_nand_usdhc_sels[] = {"osc_24m", "sys_pll1_266m" "sys_pll2_200m", "sys_pll1_133m", "sys_pll3_out", "sys_pll2_250m", "audio_pll1_out", }; +#if CONFIG_IS_ENABLED(VIDEO) +static const char * const imx8mp_media_axi_sels[] = {"osc_24m", "sys_pll2_1000m", "sys_pll1_800m", + "sys_pll3_out", "sys_pll1_40m", "audio_pll2_out", + "clk_ext1", "sys_pll2_500m", }; + +static const char * const imx8mp_media_apb_sels[] = {"osc_24m", "sys_pll2_125m", "sys_pll1_800m", + "sys_pll3_out", "sys_pll1_40m", "audio_pll2_out", + "clk_ext1", "sys_pll1_133m", }; +#endif + static const char * const imx8mp_noc_sels[] = {"osc_24m", "sys_pll1_800m", "sys_pll3_out", "sys_pll2_1000m", "sys_pll2_500m", "audio_pll1_out", "video_pll1_out", "audio_pll2_out", }; @@ -175,6 +198,17 @@ static const char * const imx8mp_usdhc3_sels[] = {"osc_24m", "sys_pll1_400m", "s "sys_pll2_500m", "sys_pll3_out", "sys_pll1_266m", "audio_pll2_out", "sys_pll1_100m", }; +#if CONFIG_IS_ENABLED(VIDEO) +static const char * const imx8mp_media_disp_pix_sels[] = {"osc_24m", "video_pll1_out", "audio_pll2_out", + "audio_pll1_out", "sys_pll1_800m", + "sys_pll2_1000m", "sys_pll3_out", "clk_ext4", }; + +static const char * const imx8mp_media_ldb_sels[] = {"osc_24m", "sys_pll2_333m", "sys_pll2_100m", + "sys_pll1_800m", "sys_pll2_1000m", + "clk_ext2", "audio_pll2_out", + "video_pll1_out", }; +#endif + static const char * const imx8mp_enet_ref_sels[] = {"osc_24m", "sys_pll2_125m", "sys_pll2_50m", "sys_pll2_100m", "sys_pll1_160m", "audio_pll1_out", "video_pll1_out", "clk_ext4", }; @@ -199,12 +233,19 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_CLK_DUMMY, clk_register_fixed_rate(NULL, "dummy", 0)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_VIDEO_PLL1_REF_SEL, imx_clk_mux(dev, "video_pll1_ref_sel", base + 0x28, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); +#endif clk_dm(IMX8MP_DRAM_PLL_REF_SEL, imx_clk_mux(dev, "dram_pll_ref_sel", base + 0x50, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); clk_dm(IMX8MP_ARM_PLL_REF_SEL, imx_clk_mux(dev, "arm_pll_ref_sel", base + 0x84, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); clk_dm(IMX8MP_SYS_PLL1_REF_SEL, imx_clk_mux(dev, "sys_pll1_ref_sel", base + 0x94, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); clk_dm(IMX8MP_SYS_PLL2_REF_SEL, imx_clk_mux(dev, "sys_pll2_ref_sel", base + 0x104, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); clk_dm(IMX8MP_SYS_PLL3_REF_SEL, imx_clk_mux(dev, "sys_pll3_ref_sel", base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels))); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_VIDEO_PLL1, imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, + &imx_1443x_pll)); +#endif clk_dm(IMX8MP_DRAM_PLL, imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll)); clk_dm(IMX8MP_ARM_PLL, imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel", base + 0x84, @@ -216,12 +257,18 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_SYS_PLL3, imx_clk_pll14xx("sys_pll3", "sys_pll3_ref_sel", base + 0x114, &imx_1416x_pll)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_VIDEO_PLL1_BYPASS, imx_clk_mux_flags(dev, "video_pll1_bypass", base + 0x28, 16, 1, video_pll1_bypass_sels, ARRAY_SIZE(video_pll1_bypass_sels), CLK_SET_RATE_PARENT)); +#endif clk_dm(IMX8MP_DRAM_PLL_BYPASS, imx_clk_mux_flags(dev, "dram_pll_bypass", base + 0x50, 4, 1, dram_pll_bypass_sels, ARRAY_SIZE(dram_pll_bypass_sels), CLK_SET_RATE_PARENT)); clk_dm(IMX8MP_ARM_PLL_BYPASS, imx_clk_mux_flags(dev, "arm_pll_bypass", base + 0x84, 4, 1, arm_pll_bypass_sels, ARRAY_SIZE(arm_pll_bypass_sels), CLK_SET_RATE_PARENT)); clk_dm(IMX8MP_SYS_PLL1_BYPASS, imx_clk_mux_flags(dev, "sys_pll1_bypass", base + 0x94, 4, 1, sys_pll1_bypass_sels, ARRAY_SIZE(sys_pll1_bypass_sels), CLK_SET_RATE_PARENT)); clk_dm(IMX8MP_SYS_PLL2_BYPASS, imx_clk_mux_flags(dev, "sys_pll2_bypass", base + 0x104, 4, 1, sys_pll2_bypass_sels, ARRAY_SIZE(sys_pll2_bypass_sels), CLK_SET_RATE_PARENT)); clk_dm(IMX8MP_SYS_PLL3_BYPASS, imx_clk_mux_flags(dev, "sys_pll3_bypass", base + 0x114, 4, 1, sys_pll3_bypass_sels, ARRAY_SIZE(sys_pll3_bypass_sels), CLK_SET_RATE_PARENT)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_VIDEO_PLL1_OUT, imx_clk_gate(dev, "video_pll1_out", "video_pll1_bypass", base + 0x28, 13)); +#endif clk_dm(IMX8MP_DRAM_PLL_OUT, imx_clk_gate(dev, "dram_pll_out", "dram_pll_bypass", base + 0x50, 13)); clk_dm(IMX8MP_ARM_PLL_OUT, imx_clk_gate(dev, "arm_pll_out", "arm_pll_bypass", base + 0x84, 11)); clk_dm(IMX8MP_SYS_PLL1_OUT, imx_clk_gate(dev, "sys_pll1_out", "sys_pll1_bypass", base + 0x94, 11)); @@ -267,13 +314,23 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_CLK_A53_DIV, imx_clk_divider2(dev, "arm_a53_div", "arm_a53_cg", base + 0x8000, 0, 3)); clk_dm(IMX8MP_CLK_HSIO_AXI, imx8m_clk_composite(dev, "hsio_axi", imx8mp_hsio_axi_sels, base + 0x8380)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_CLK_MEDIA_ISP, imx8m_clk_composite(dev, "media_isp", imx8mp_media_isp_sels, base + 0x8400)); +#endif clk_dm(IMX8MP_CLK_MAIN_AXI, imx8m_clk_composite_critical(dev, "main_axi", imx8mp_main_axi_sels, base + 0x8800)); clk_dm(IMX8MP_CLK_ENET_AXI, imx8m_clk_composite_critical(dev, "enet_axi", imx8mp_enet_axi_sels, base + 0x8880)); clk_dm(IMX8MP_CLK_NAND_USDHC_BUS, imx8m_clk_composite_critical(dev, "nand_usdhc_bus", imx8mp_nand_usdhc_sels, base + 0x8900)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_CLK_MEDIA_AXI, imx8m_clk_composite(dev, "media_axi", imx8mp_media_axi_sels, base + 0x8a00)); + clk_dm(IMX8MP_CLK_MEDIA_APB, imx8m_clk_composite(dev, "media_apb", imx8mp_media_apb_sels, base + 0x8a80)); +#endif clk_dm(IMX8MP_CLK_NOC, imx8m_clk_composite_critical(dev, "noc", imx8mp_noc_sels, base + 0x8d00)); clk_dm(IMX8MP_CLK_NOC_IO, imx8m_clk_composite_critical(dev, "noc_io", imx8mp_noc_io_sels, base + 0x8d80)); clk_dm(IMX8MP_CLK_AHB, imx8m_clk_composite_critical(dev, "ahb_root", imx8mp_ahb_sels, base + 0x9000)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_CLK_MEDIA_DISP2_PIX, imx8m_clk_composite(dev, "media_disp2_pix", imx8mp_media_disp_pix_sels, base + 0x9300)); +#endif clk_dm(IMX8MP_CLK_IPG_ROOT, imx_clk_divider2(dev, "ipg_root", "ahb_root", base + 0x9080, 0, 1)); @@ -312,6 +369,10 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_CLK_WDOG, imx8m_clk_composite(dev, "wdog", imx8mp_wdog_sels, base + 0xb900)); clk_dm(IMX8MP_CLK_USDHC3, imx8m_clk_composite(dev, "usdhc3", imx8mp_usdhc3_sels, base + 0xbc80)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_CLK_MEDIA_DISP1_PIX, imx8m_clk_composite(dev, "media_disp1_pix", imx8mp_media_disp_pix_sels, base + 0xbe00)); + clk_dm(IMX8MP_CLK_MEDIA_LDB, imx8m_clk_composite(dev, "media_ldb", imx8mp_media_ldb_sels, base + 0xbf00)); +#endif clk_dm(IMX8MP_CLK_DRAM_ALT_ROOT, imx_clk_fixed_factor(dev, "dram_alt_root", "dram_alt", 1, 4)); clk_dm(IMX8MP_CLK_DRAM_CORE, imx_clk_mux2_flags(dev, "dram_core_clk", base + 0x9800, 24, 1, imx8mp_dram_core_sels, ARRAY_SIZE(imx8mp_dram_core_sels), CLK_IS_CRITICAL)); @@ -355,6 +416,14 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_CLK_WDOG2_ROOT, imx_clk_gate4(dev, "wdog2_root_clk", "wdog", base + 0x4540, 0)); clk_dm(IMX8MP_CLK_WDOG3_ROOT, imx_clk_gate4(dev, "wdog3_root_clk", "wdog", base + 0x4550, 0)); clk_dm(IMX8MP_CLK_HSIO_ROOT, imx_clk_gate4(dev, "hsio_root_clk", "ipg_root", base + 0x45c0, 0)); +#if CONFIG_IS_ENABLED(VIDEO) + clk_dm(IMX8MP_CLK_MEDIA_APB_ROOT, imx_clk_gate2_shared2(dev, "media_apb_root_clk", "media_apb", base + 0x45d0, 0, &share_count_media)); + clk_dm(IMX8MP_CLK_MEDIA_AXI_ROOT, imx_clk_gate2_shared2(dev, "media_axi_root_clk", "media_axi", base + 0x45d0, 0, &share_count_media)); + clk_dm(IMX8MP_CLK_MEDIA_DISP1_PIX_ROOT, imx_clk_gate2_shared2(dev, "media_disp1_pix_root_clk", "media_disp1_pix", base + 0x45d0, 0, &share_count_media)); + clk_dm(IMX8MP_CLK_MEDIA_DISP2_PIX_ROOT, imx_clk_gate2_shared2(dev, "media_disp2_pix_root_clk", "media_disp2_pix", base + 0x45d0, 0, &share_count_media)); + clk_dm(IMX8MP_CLK_MEDIA_LDB_ROOT, imx_clk_gate2_shared2(dev, "media_ldb_root_clk", "media_ldb", base + 0x45d0, 0, &share_count_media)); + clk_dm(IMX8MP_CLK_MEDIA_ISP_ROOT, imx_clk_gate2_shared2(dev, "media_isp_root_clk", "media_isp", base + 0x45d0, 0, &share_count_media)); +#endif clk_dm(IMX8MP_CLK_USDHC3_ROOT, imx_clk_gate4(dev, "usdhc3_root_clk", "usdhc3", base + 0x45e0, 0)); diff --git a/drivers/core/ofnode_graph.c b/drivers/core/ofnode_graph.c index 90c92af3258..175ac768771 100644 --- a/drivers/core/ofnode_graph.c +++ b/drivers/core/ofnode_graph.c @@ -98,7 +98,7 @@ ofnode ofnode_graph_get_port_by_id(ofnode parent, u32 id) * @id: id for the endpoint * * Return: ofnode in given endpoint or ofnode_null() if not found. - * reg and port_reg are ignored when they are -1. + * reg_id and id are ignored when they are -1. */ ofnode ofnode_graph_get_endpoint_by_regs(ofnode parent, int reg_id, int id) { diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index f846a35d6b2..ce5e61bbaa6 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -16,6 +16,7 @@ #include <dm/device.h> #include <dm/device-internal.h> #include <dm/lists.h> +#include <dm/ofnode_graph.h> #include <dm/uclass.h> #include <dm/uclass-internal.h> #include <dm/util.h> @@ -582,6 +583,24 @@ int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent, ret = uclass_find_device_by_phandle(id, parent, name, &dev); return uclass_get_device_tail(dev, ret, devp); } + +int uclass_get_device_by_endpoint(enum uclass_id class_id, struct udevice *dev, + int port_idx, int ep_idx, struct udevice **devp) +{ + ofnode node_source = dev_ofnode(dev); + ofnode node_dest = ofnode_graph_get_remote_node(node_source, port_idx, ep_idx); + struct udevice *target = NULL; + int ret; + + if (!ofnode_valid(node_dest)) + return -EINVAL; + + ret = uclass_find_device_by_ofnode(class_id, node_dest, &target); + if (ret) + return -ENODEV; + + return uclass_get_device_tail(target, 0, devp); +} #endif /* diff --git a/drivers/firmware/scmi/sandbox-scmi_devices.c b/drivers/firmware/scmi/sandbox-scmi_devices.c index 96c2922b067..9f253b0fd40 100644 --- a/drivers/firmware/scmi/sandbox-scmi_devices.c +++ b/drivers/firmware/scmi/sandbox-scmi_devices.c @@ -163,4 +163,5 @@ U_BOOT_DRIVER(sandbox_scmi_devices) = { .priv_auto = sizeof(struct sandbox_scmi_device_priv), .remove = sandbox_scmi_devices_remove, .probe = sandbox_scmi_devices_probe, + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, }; diff --git a/drivers/firmware/scmi/smt.c b/drivers/firmware/scmi/smt.c index 67d2f450024..3253f4211d6 100644 --- a/drivers/firmware/scmi/smt.c +++ b/drivers/firmware/scmi/smt.c @@ -20,6 +20,16 @@ #include "smt.h" +static void scmi_smt_enable_intr(struct scmi_smt *smt, bool enable) +{ + struct scmi_smt_header *hdr = (void *)smt->buf; + + if (enable) + hdr->flags |= SCMI_SHMEM_FLAG_INTR_ENABLED; + else + hdr->flags &= ~SCMI_SHMEM_FLAG_INTR_ENABLED; +} + /** * Get shared memory configuration defined by the referred DT phandle * Return with a errno compliant value. @@ -48,6 +58,9 @@ int scmi_dt_get_smt_buffer(struct udevice *dev, struct scmi_smt *smt) if (!smt->buf) return -ENOMEM; + if (device_is_compatible(dev, "arm,scmi") && ofnode_has_property(dev_ofnode(dev), "mboxes")) + scmi_smt_enable_intr(smt, true); + #ifdef CONFIG_ARM if (dcache_status()) mmu_set_region_dcache_behaviour(ALIGN_DOWN((uintptr_t)smt->buf, MMU_SECTION_SIZE), diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig index bd82d2f7044..5f5218bd8b5 100644 --- a/drivers/power/domain/Kconfig +++ b/drivers/power/domain/Kconfig @@ -47,6 +47,13 @@ config IMX8MP_HSIOMIX_BLKCTRL help Enable support for manipulating NXP i.MX8MP on-SoC HSIOMIX block controller. +config IMX8MP_MEDIAMIX_BLKCTRL + bool "Enable i.MX8MP MEDIAMIX domain driver" + depends on POWER_DOMAIN && IMX8MP + select CLK + help + Enable support for manipulating NXP i.MX8MP on-SoC MEDIAMIX block controller. + config MTK_POWER_DOMAIN bool "Enable the MediaTek power domain driver" depends on POWER_DOMAIN && ARCH_MEDIATEK diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile index 110646c503a..356ec07163f 100644 --- a/drivers/power/domain/Makefile +++ b/drivers/power/domain/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_BCM6328_POWER_DOMAIN) += bcm6328-power-domain.o obj-$(CONFIG_IMX8_POWER_DOMAIN) += imx8-power-domain-legacy.o imx8-power-domain.o obj-$(CONFIG_IMX8M_POWER_DOMAIN) += imx8m-power-domain.o obj-$(CONFIG_IMX8MP_HSIOMIX_BLKCTRL) += imx8mp-hsiomix.o +obj-$(CONFIG_IMX8MP_MEDIAMIX_BLKCTRL) += imx8mp-mediamix.o obj-$(CONFIG_MTK_POWER_DOMAIN) += mtk-power-domain.o obj-$(CONFIG_MESON_GX_VPU_POWER_DOMAIN) += meson-gx-pwrc-vpu.o obj-$(CONFIG_MESON_EE_POWER_DOMAIN) += meson-ee-pwrc.o diff --git a/drivers/power/domain/imx8m-power-domain.c b/drivers/power/domain/imx8m-power-domain.c index c22fbe60675..e54ba5d9a54 100644 --- a/drivers/power/domain/imx8m-power-domain.c +++ b/drivers/power/domain/imx8m-power-domain.c @@ -40,6 +40,7 @@ DECLARE_GLOBAL_DATA_PTR; #define IMX8MN_MIPI_A53_DOMAIN BIT(2) #define IMX8MP_HSIOMIX_A53_DOMAIN BIT(19) +#define IMX8MP_MEDIAMIX_A53_DOMAIN BIT(12) #define IMX8MP_USB2_PHY_A53_DOMAIN BIT(5) #define IMX8MP_USB1_PHY_A53_DOMAIN BIT(4) #define IMX8MP_PCIE_PHY_A53_DOMAIN BIT(3) @@ -63,6 +64,7 @@ DECLARE_GLOBAL_DATA_PTR; #define IMX8MN_MIPI_SW_Pxx_REQ BIT(0) #define IMX8MP_HSIOMIX_Pxx_REQ BIT(17) +#define IMX8MP_MEDIAMIX_Pxx_REQ BIT(10) #define IMX8MP_USB2_PHY_Pxx_REQ BIT(3) #define IMX8MP_USB1_PHY_Pxx_REQ BIT(2) #define IMX8MP_PCIE_PHY_SW_Pxx_REQ BIT(1) @@ -81,6 +83,9 @@ DECLARE_GLOBAL_DATA_PTR; #define IMX8MP_HSIOMIX_PWRDNACKN BIT(28) #define IMX8MP_HSIOMIX_PWRDNREQN BIT(12) +#define IMX8MP_MEDIAMIX_PWRDNACKN BIT(30) +#define IMX8MP_MEDIAMIX_PWRDNREQN BIT(14) + /* * The PGC offset values in Reference Manual * (Rev. 1, 01/2018 and the older ones) GPC chapter's @@ -101,6 +106,7 @@ DECLARE_GLOBAL_DATA_PTR; #define IMX8MP_PGC_PCIE 13 #define IMX8MP_PGC_USB1 14 #define IMX8MP_PGC_USB2 15 +#define IMX8MP_PGC_MEDIAMIX 22 #define IMX8MP_PGC_HSIOMIX 29 #define GPC_PGC_CTRL(n) (0x800 + (n) * 0x40) @@ -303,6 +309,17 @@ static const struct imx_pgc_domain imx8mp_pgc_domains[] = { .pgc = BIT(IMX8MP_PGC_HSIOMIX), .keep_clocks = true, }, + + [IMX8MP_POWER_DOMAIN_MEDIAMIX] = { + .bits = { + .pxx = IMX8MP_MEDIAMIX_Pxx_REQ, + .map = IMX8MP_MEDIAMIX_A53_DOMAIN, + .hskreq = IMX8MP_MEDIAMIX_PWRDNREQN, + .hskack = IMX8MP_MEDIAMIX_PWRDNACKN, + }, + .pgc = BIT(IMX8MP_PGC_MEDIAMIX), + .keep_clocks = true, + }, }; static const struct imx_pgc_regs imx8mp_pgc_regs = { diff --git a/drivers/power/domain/imx8mp-mediamix.c b/drivers/power/domain/imx8mp-mediamix.c new file mode 100644 index 00000000000..78c32ca3d3a --- /dev/null +++ b/drivers/power/domain/imx8mp-mediamix.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i.MX8 MEDIAMIX control block driver + * Copyright (C) 2024 Miquel Raynal <[email protected]> + * Inspired from Marek Vasut <[email protected]> work on the hsiomix driver. + */ + +#include <asm/io.h> +#include <clk.h> +#include <dm.h> +#include <power-domain-uclass.h> +#include <linux/delay.h> + +#include <dt-bindings/power/imx8mp-power.h> + +#define BLK_SFT_RSTN 0x0 +#define BLK_CLK_EN 0x4 + +struct imx8mp_mediamix_priv { + void __iomem *base; + struct clk clk_apb; + struct clk clk_axi; + struct clk clk_disp2; + struct power_domain pd_bus; + struct power_domain pd_lcdif2; +}; + +static int imx8mp_mediamix_on(struct power_domain *power_domain) +{ + struct udevice *dev = power_domain->dev; + struct imx8mp_mediamix_priv *priv = dev_get_priv(dev); + struct power_domain *domain; + struct clk *clk; + u32 reset; + int ret; + + switch (power_domain->id) { + case IMX8MP_MEDIABLK_PD_LCDIF_2: + domain = &priv->pd_lcdif2; + clk = &priv->clk_disp2; + reset = BIT(11) | BIT(12) | BIT(24); + break; + default: + return -EINVAL; + } + + /* Make sure bus domain is awake */ + ret = power_domain_on(&priv->pd_bus); + if (ret) + return ret; + + /* Put devices into reset */ + clrbits_le32(priv->base + BLK_SFT_RSTN, reset); + + /* Enable upstream clocks */ + ret = clk_enable(&priv->clk_apb); + if (ret) + goto dis_bus_pd; + + ret = clk_enable(&priv->clk_axi); + if (ret) + goto dis_apb_clk; + + /* Enable blk-ctrl clock to allow reset to propagate */ + ret = clk_enable(clk); + if (ret) + goto dis_axi_clk; + setbits_le32(priv->base + BLK_CLK_EN, reset); + + /* Power up upstream GPC domain */ + ret = power_domain_on(domain); + if (ret) + goto dis_lcdif_clk; + + /* Wait for reset to propagate */ + udelay(5); + + /* Release reset */ + setbits_le32(priv->base + BLK_SFT_RSTN, reset); + + return 0; + +dis_lcdif_clk: + clk_disable(clk); +dis_axi_clk: + clk_disable(&priv->clk_axi); +dis_apb_clk: + clk_disable(&priv->clk_apb); +dis_bus_pd: + power_domain_off(&priv->pd_bus); + + return ret; +} + +static int imx8mp_mediamix_off(struct power_domain *power_domain) +{ + struct udevice *dev = power_domain->dev; + struct imx8mp_mediamix_priv *priv = dev_get_priv(dev); + struct power_domain *domain; + struct clk *clk; + u32 reset; + + switch (power_domain->id) { + case IMX8MP_MEDIABLK_PD_LCDIF_2: + domain = &priv->pd_lcdif2; + clk = &priv->clk_disp2; + reset = BIT(11) | BIT(12) | BIT(24); + break; + default: + return -EINVAL; + } + + /* Put devices into reset and disable clocks */ + clrbits_le32(priv->base + BLK_SFT_RSTN, reset); + clrbits_le32(priv->base + BLK_CLK_EN, reset); + + /* Power down upstream GPC domain */ + power_domain_off(domain); + + clk_disable(clk); + clk_disable(&priv->clk_axi); + clk_disable(&priv->clk_apb); + + /* Allow bus domain to suspend */ + power_domain_off(&priv->pd_bus); + + return 0; +} + +static int imx8mp_mediamix_of_xlate(struct power_domain *power_domain, + struct ofnode_phandle_args *args) +{ + power_domain->id = args->args[0]; + + return 0; +} + +static int imx8mp_mediamix_bind(struct udevice *dev) +{ + /* Bind child lcdif */ + return dm_scan_fdt_dev(dev); +} + +static int imx8mp_mediamix_probe(struct udevice *dev) +{ + struct imx8mp_mediamix_priv *priv = dev_get_priv(dev); + int ret; + + priv->base = dev_read_addr_ptr(dev); + + ret = clk_get_by_name(dev, "apb", &priv->clk_apb); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "axi", &priv->clk_axi); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "disp2", &priv->clk_disp2); + if (ret < 0) + return ret; + + ret = power_domain_get_by_name(dev, &priv->pd_bus, "bus"); + if (ret < 0) + return ret; + + ret = power_domain_get_by_name(dev, &priv->pd_lcdif2, "lcdif2"); + if (ret < 0) + goto free_bus_pd; + + return 0; + +free_bus_pd: + power_domain_free(&priv->pd_bus); + return ret; +} + +static int imx8mp_mediamix_remove(struct udevice *dev) +{ + struct imx8mp_mediamix_priv *priv = dev_get_priv(dev); + + power_domain_free(&priv->pd_lcdif2); + power_domain_free(&priv->pd_bus); + + return 0; +} + +static const struct udevice_id imx8mp_mediamix_ids[] = { + { .compatible = "fsl,imx8mp-media-blk-ctrl" }, + { } +}; + +struct power_domain_ops imx8mp_mediamix_ops = { + .on = imx8mp_mediamix_on, + .off = imx8mp_mediamix_off, + .of_xlate = imx8mp_mediamix_of_xlate, +}; + +U_BOOT_DRIVER(imx8mp_mediamix) = { + .name = "imx8mp_mediamix", + .id = UCLASS_POWER_DOMAIN, + .of_match = imx8mp_mediamix_ids, + .bind = imx8mp_mediamix_bind, + .probe = imx8mp_mediamix_probe, + .remove = imx8mp_mediamix_remove, + .priv_auto = sizeof(struct imx8mp_mediamix_priv), + .ops = &imx8mp_mediamix_ops, +}; diff --git a/drivers/power/domain/power-domain-uclass.c b/drivers/power/domain/power-domain-uclass.c index 938bd8cbc9f..a6e5f9ed036 100644 --- a/drivers/power/domain/power-domain-uclass.c +++ b/drivers/power/domain/power-domain-uclass.c @@ -12,6 +12,10 @@ #include <power-domain-uclass.h> #include <dm/device-internal.h> +struct power_domain_priv { + int on_count; +}; + static inline struct power_domain_ops *power_domain_dev_ops(struct udevice *dev) { return (struct power_domain_ops *)dev->driver->ops; @@ -107,22 +111,49 @@ int power_domain_free(struct power_domain *power_domain) return ops->rfree ? ops->rfree(power_domain) : 0; } -int power_domain_on(struct power_domain *power_domain) +int power_domain_on_lowlevel(struct power_domain *power_domain) { + struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev); struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); + int ret; debug("%s(power_domain=%p)\n", __func__, power_domain); - return ops->on ? ops->on(power_domain) : 0; + if (priv->on_count++ > 0) + return -EALREADY; + + ret = ops->on ? ops->on(power_domain) : 0; + if (ret) { + priv->on_count--; + return ret; + } + + return 0; } -int power_domain_off(struct power_domain *power_domain) +int power_domain_off_lowlevel(struct power_domain *power_domain) { + struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev); struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); + int ret; debug("%s(power_domain=%p)\n", __func__, power_domain); - return ops->off ? ops->off(power_domain) : 0; + if (priv->on_count <= 0) { + debug("Power domain %s already off.\n", power_domain->dev->name); + return -EALREADY; + } + + if (priv->on_count-- > 1) + return -EBUSY; + + ret = ops->off ? ops->off(power_domain) : 0; + if (ret) { + priv->on_count++; + return ret; + } + + return 0; } #if CONFIG_IS_ENABLED(OF_REAL) @@ -180,4 +211,5 @@ int dev_power_domain_off(struct udevice *dev) UCLASS_DRIVER(power_domain) = { .id = UCLASS_POWER_DOMAIN, .name = "power_domain", + .per_device_auto = sizeof(struct power_domain_priv), }; diff --git a/drivers/power/domain/sandbox-power-domain-test.c b/drivers/power/domain/sandbox-power-domain-test.c index 08c15ef342b..5b530974e94 100644 --- a/drivers/power/domain/sandbox-power-domain-test.c +++ b/drivers/power/domain/sandbox-power-domain-test.c @@ -51,4 +51,5 @@ U_BOOT_DRIVER(sandbox_power_domain_test) = { .id = UCLASS_MISC, .of_match = sandbox_power_domain_test_ids, .priv_auto = sizeof(struct sandbox_power_domain_test), + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, }; diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 6073bc5234a..8ebd596e115 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -53,7 +53,7 @@ obj-$(CONFIG_VIDEO_COREBOOT) += coreboot.o obj-$(CONFIG_VIDEO_DW_HDMI) += dw_hdmi.o obj-$(CONFIG_VIDEO_DW_MIPI_DSI) += dw_mipi_dsi.o obj-$(CONFIG_VIDEO_EFI) += efi.o -obj-$(CONFIG_VIDEO_IPUV3) += imx/ +obj-y += imx/ obj-$(CONFIG_VIDEO_IVYBRIDGE_IGD) += ivybridge_igd.o obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o obj-$(CONFIG_VIDEO_LCD_ENDEAVORU) += endeavoru-panel.o diff --git a/drivers/video/imx/Kconfig b/drivers/video/imx/Kconfig index 34e8b640595..12f11c2eea8 100644 --- a/drivers/video/imx/Kconfig +++ b/drivers/video/imx/Kconfig @@ -13,3 +13,12 @@ config IMX_VIDEO_SKIP config IMX_HDMI bool "Enable HDMI support in IPUv3" depends on VIDEO_IPUV3 + +config IMX_LDB + bool "Freescale i.MX8MP LDB bridge" + depends on VIDEO_BRIDGE + help + Support for i.MX8MP DPI-to-LVDS on-SoC encoder. + +config IMX_LCDIF + bool "i.MX LCDIFv3 LCD controller" diff --git a/drivers/video/imx/Makefile b/drivers/video/imx/Makefile index 179ea651fe8..1edf5a6bdf0 100644 --- a/drivers/video/imx/Makefile +++ b/drivers/video/imx/Makefile @@ -3,4 +3,6 @@ # (C) Copyright 2000-2007 # Wolfgang Denk, DENX Software Engineering, [email protected]. -obj-y += mxc_ipuv3_fb.o ipu_common.o ipu_disp.o +obj-$(CONFIG_VIDEO_IPUV3) += mxc_ipuv3_fb.o ipu_common.o ipu_disp.o +obj-$(CONFIG_IMX_LDB) += ldb.o +obj-$(CONFIG_IMX_LCDIF) += lcdif.o diff --git a/drivers/video/imx/lcdif.c b/drivers/video/imx/lcdif.c new file mode 100644 index 00000000000..9f4fc7f5152 --- /dev/null +++ b/drivers/video/imx/lcdif.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX8 LCD interface driver inspired from the Linux driver + * Copyright 2019 NXP + * Copyright 2024 Bootlin + * Adapted by Miquel Raynal <[email protected]> + */ + +#include <asm/io.h> +#include <asm/mach-imx/dma.h> +#include <clk.h> +#include <dm.h> +#include <panel.h> +#include <power-domain.h> +#include <video.h> +#include <video_bridge.h> +#include <linux/delay.h> + +#include "../videomodes.h" + +#define LCDIFV3_CTRL 0x0 +#define LCDIFV3_CTRL_SET 0x4 +#define LCDIFV3_CTRL_CLR 0x8 +#define CTRL_INV_HS BIT(0) +#define CTRL_INV_VS BIT(1) +#define CTRL_INV_DE BIT(2) +#define CTRL_INV_PXCK BIT(3) +#define CTRL_CLK_GATE BIT(30) +#define CTRL_SW_RESET BIT(31) + +#define LCDIFV3_DISP_PARA 0x10 +#define DISP_PARA_DISP_MODE_NORMAL 0 +#define DISP_PARA_LINE_PATTERN_RGB_YUV 0 +#define DISP_PARA_DISP_ON BIT(31) + +#define LCDIFV3_DISP_SIZE 0x14 +#define DISP_SIZE_DELTA_X(x) ((x) & 0xffff) +#define DISP_SIZE_DELTA_Y(x) ((x) << 16) + +#define LCDIFV3_HSYN_PARA 0x18 +#define HSYN_PARA_FP_H(x) ((x) & 0xffff) +#define HSYN_PARA_BP_H(x) ((x) << 16) + +#define LCDIFV3_VSYN_PARA 0x1C +#define VSYN_PARA_FP_V(x) ((x) & 0xffff) +#define VSYN_PARA_BP_V(x) ((x) << 16) + +#define LCDIFV3_VSYN_HSYN_WIDTH 0x20 +#define VSYN_HSYN_PW_H(x) ((x) & 0xffff) +#define VSYN_HSYN_PW_V(x) ((x) << 16) + +#define LCDIFV3_CTRLDESCL0_1 0x200 +#define CTRLDESCL0_1_WIDTH(x) ((x) & 0xffff) +#define CTRLDESCL0_1_HEIGHT(x) ((x) << 16) + +#define LCDIFV3_CTRLDESCL0_3 0x208 +#define CTRLDESCL0_3_PITCH(x) ((x) & 0xFFFF) + +#define LCDIFV3_CTRLDESCL_LOW0_4 0x20C +#define LCDIFV3_CTRLDESCL_HIGH0_4 0x210 + +#define LCDIFV3_CTRLDESCL0_5 0x214 +#define CTRLDESCL0_5_YUV_FORMAT(x) (((x) & 0x3) << 14) +#define CTRLDESCL0_5_BPP(x) (((x) & 0xf) << 24) +#define BPP32_ARGB8888 0x9 +#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30) +#define CTRLDESCL0_5_EN BIT(31) + +struct lcdifv3_priv { + void __iomem *base; + struct clk pix_clk; + struct power_domain pd; + struct udevice *panel; + struct udevice *bridge; +}; + +static void lcdifv3_set_mode(struct lcdifv3_priv *priv, + struct display_timing *timings) +{ + u32 reg; + + writel(DISP_SIZE_DELTA_X(timings->hactive.typ) | + DISP_SIZE_DELTA_Y(timings->vactive.typ), + priv->base + LCDIFV3_DISP_SIZE); + + writel(HSYN_PARA_FP_H(timings->hfront_porch.typ) | + HSYN_PARA_BP_H(timings->hback_porch.typ), + priv->base + LCDIFV3_HSYN_PARA); + + writel(VSYN_PARA_BP_V(timings->vback_porch.typ) | + VSYN_PARA_FP_V(timings->vfront_porch.typ), + priv->base + LCDIFV3_VSYN_PARA); + + writel(VSYN_HSYN_PW_H(timings->hsync_len.typ) | + VSYN_HSYN_PW_V(timings->vsync_len.typ), + priv->base + LCDIFV3_VSYN_HSYN_WIDTH); + + writel(CTRLDESCL0_1_WIDTH(timings->hactive.typ) | + CTRLDESCL0_1_HEIGHT(timings->vactive.typ), + priv->base + LCDIFV3_CTRLDESCL0_1); + + if (timings->flags & DISPLAY_FLAGS_HSYNC_LOW) + writel(CTRL_INV_HS, priv->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_HS, priv->base + LCDIFV3_CTRL_CLR); + + if (timings->flags & DISPLAY_FLAGS_VSYNC_LOW) + writel(CTRL_INV_VS, priv->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_VS, priv->base + LCDIFV3_CTRL_CLR); + + if (timings->flags & DISPLAY_FLAGS_DE_LOW) + writel(CTRL_INV_DE, priv->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_DE, priv->base + LCDIFV3_CTRL_CLR); + + if (timings->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + writel(CTRL_INV_PXCK, priv->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_PXCK, priv->base + LCDIFV3_CTRL_CLR); + + writel(0, priv->base + LCDIFV3_DISP_PARA); + + reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5); + reg &= ~(CTRLDESCL0_5_BPP(0xf) | CTRLDESCL0_5_YUV_FORMAT(0x3)); + reg |= CTRLDESCL0_5_BPP(BPP32_ARGB8888); + writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5); +} + +static void lcdifv3_enable_controller(struct lcdifv3_priv *priv) +{ + u32 reg; + + reg = readl(priv->base + LCDIFV3_DISP_PARA); + reg |= DISP_PARA_DISP_ON; + writel(reg, priv->base + LCDIFV3_DISP_PARA); + + reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5); + reg |= CTRLDESCL0_5_EN; + writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5); +} + +static int lcdifv3_video_sync(struct udevice *dev) +{ + struct lcdifv3_priv *priv = dev_get_priv(dev); + u32 reg; + + reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5); + reg |= CTRLDESCL0_5_SHADOW_LOAD_EN; + writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5); + + return 0; +} + +static void lcdifv3_init(struct udevice *dev, struct display_timing *timings) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + struct lcdifv3_priv *priv = dev_get_priv(dev); + + clk_set_rate(&priv->pix_clk, timings->pixelclock.typ); + + writel(CTRL_SW_RESET | CTRL_CLK_GATE, priv->base + LCDIFV3_CTRL_CLR); + udelay(10); + + lcdifv3_set_mode(priv, timings); + + writel(plat->base & 0xFFFFFFFF, priv->base + LCDIFV3_CTRLDESCL_LOW0_4); + writel(plat->base >> 32, priv->base + LCDIFV3_CTRLDESCL_HIGH0_4); + + writel(CTRLDESCL0_3_PITCH(timings->hactive.typ * 4), /* 32bpp */ + priv->base + LCDIFV3_CTRLDESCL0_3); + + lcdifv3_enable_controller(priv); +} + +static int lcdifv3_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); + struct lcdifv3_priv *priv = dev_get_priv(dev); + struct clk axi_clk, disp_axi_clk; + struct display_timing timings; + u32 fb_start, fb_end; + int ret; + + ret = power_domain_get(dev, &priv->pd); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "pix", &priv->pix_clk); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "axi", &axi_clk); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "disp_axi", &disp_axi_clk); + if (ret < 0) + return ret; + + ret = power_domain_on(&priv->pd); + if (ret) + return ret; + + ret = clk_enable(&priv->pix_clk); + if (ret) + goto dis_pd; + + ret = clk_enable(&axi_clk); + if (ret) + goto dis_pix_clk; + + ret = clk_enable(&disp_axi_clk); + if (ret) + goto dis_axi_clk; + + priv->base = dev_remap_addr(dev); + if (!priv->base) { + ret = -EINVAL; + goto dis_clks; + } + + /* Attach bridge */ + ret = uclass_get_device_by_endpoint(UCLASS_VIDEO_BRIDGE, dev, + -1, -1, &priv->bridge); + if (ret) + goto dis_clks; + + ret = video_bridge_attach(priv->bridge); + if (ret) + goto dis_clks; + + ret = video_bridge_set_backlight(priv->bridge, 80); + if (ret) + goto dis_clks; + + /* Attach panels */ + ret = uclass_get_device_by_endpoint(UCLASS_PANEL, priv->bridge, + 1, -1, &priv->panel); + if (ret) { + ret = uclass_get_device_by_endpoint(UCLASS_PANEL, priv->bridge, + 2, -1, &priv->panel); + if (ret) + goto dis_clks; + } + + ret = panel_get_display_timing(priv->panel, &timings); + if (ret) { + ret = ofnode_decode_display_timing(dev_ofnode(priv->panel), + 0, &timings); + if (ret) { + printf("Cannot decode panel timings (%d)\n", ret); + goto dis_clks; + } + } + + lcdifv3_init(dev, &timings); + + /* Only support 32bpp for now */ + uc_priv->bpix = VIDEO_BPP32; + uc_priv->xsize = timings.hactive.typ; + uc_priv->ysize = timings.vactive.typ; + + /* Enable dcache for the frame buffer */ + fb_start = plat->base & ~(MMU_SECTION_SIZE - 1); + fb_end = ALIGN(plat->base + plat->size, 1 << MMU_SECTION_SHIFT); + mmu_set_region_dcache_behaviour(fb_start, fb_end - fb_start, + DCACHE_WRITEBACK); + video_set_flush_dcache(dev, true); + + return 0; + +dis_clks: + clk_disable(&disp_axi_clk); +dis_axi_clk: + clk_disable(&axi_clk); +dis_pix_clk: + clk_disable(&priv->pix_clk); +dis_pd: + power_domain_off(&priv->pd); + + return ret; +} + +static int lcdifv3_video_bind(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + + /* Max size supported by LCDIF */ + plat->size = 1920 * 1080 * VNBYTES(VIDEO_BPP32); + + return 0; +} + +static const struct udevice_id lcdifv3_video_ids[] = { + { .compatible = "fsl,imx8mp-lcdif" }, + { } +}; + +static struct video_ops lcdifv3_video_ops = { + .video_sync = lcdifv3_video_sync, +}; + +U_BOOT_DRIVER(lcdifv3_video) = { + .name = "lcdif", + .id = UCLASS_VIDEO, + .of_match = lcdifv3_video_ids, + .bind = lcdifv3_video_bind, + .ops = &lcdifv3_video_ops, + .probe = lcdifv3_video_probe, + .priv_auto = sizeof(struct lcdifv3_priv), + .flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE, +}; diff --git a/drivers/video/imx/ldb.c b/drivers/video/imx/ldb.c new file mode 100644 index 00000000000..e918341c0a3 --- /dev/null +++ b/drivers/video/imx/ldb.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Derived work from: + * Philippe Cornu <[email protected]> + * Yannick Fertre <[email protected]> + * Adapted by Miquel Raynal <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_VIDEO_BRIDGE + +#include <clk.h> +#include <dm.h> +#include <log.h> +#include <panel.h> +#include <video_bridge.h> +#include <asm/io.h> +#include <linux/delay.h> + +#define LDB_CTRL_CH0_ENABLE BIT(0) +#define LDB_CTRL_CH1_ENABLE BIT(2) +#define LDB_CTRL_CH0_DATA_WIDTH BIT(5) +#define LDB_CTRL_CH0_BIT_MAPPING BIT(6) +#define LDB_CTRL_CH1_DATA_WIDTH BIT(7) +#define LDB_CTRL_CH1_BIT_MAPPING BIT(8) +#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9) +#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10) + +#define LVDS_CTRL_CH0_EN BIT(0) +#define LVDS_CTRL_CH1_EN BIT(1) +#define LVDS_CTRL_VBG_EN BIT(2) +#define LVDS_CTRL_PRE_EMPH_EN BIT(4) +#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5) +#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11) + +struct imx_ldb_priv { + struct clk ldb_clk; + void __iomem *ldb_ctrl; + void __iomem *lvds_ctrl; + struct udevice *lvds1; + struct udevice *lvds2; +}; + +static int imx_ldb_set_backlight(struct udevice *dev, int percent) +{ + struct imx_ldb_priv *priv = dev_get_priv(dev); + int ret; + + if (priv->lvds1) { + ret = panel_enable_backlight(priv->lvds1); + if (ret) { + debug("ldb: Cannot enable lvds1 backlight\n"); + return ret; + } + + ret = panel_set_backlight(priv->lvds1, percent); + if (ret) + return ret; + } + + if (priv->lvds2) { + ret = panel_enable_backlight(priv->lvds2); + if (ret) { + debug("ldb: Cannot enable lvds2 backlight\n"); + return ret; + } + + ret = panel_set_backlight(priv->lvds2, percent); + if (ret) + return ret; + } + + return 0; +} + +static int imx_ldb_of_to_plat(struct udevice *dev) +{ + struct imx_ldb_priv *priv = dev_get_priv(dev); + int ret; + + uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 1, -1, &priv->lvds1); + uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 2, -1, &priv->lvds2); + if (!priv->lvds1 && !priv->lvds2) { + debug("ldb: No remote panel for '%s' (ret=%d)\n", + dev_read_name(dev), ret); + return ret; + } + + return 0; +} + +/* The block has a mysterious x7 internal divisor (x3.5 in dual configuration) */ +#define IMX_LDB_INTERNAL_DIVISOR(x) (((x) * 70) / 10) +#define IMX_LDB_INTERNAL_DIVISOR_DUAL(x) (((x) * 35) / 10) + +static ulong imx_ldb_input_rate(struct imx_ldb_priv *priv, + struct display_timing *timings) +{ + ulong target_rate = timings->pixelclock.typ; + + if (priv->lvds1 && priv->lvds2) + return IMX_LDB_INTERNAL_DIVISOR_DUAL(target_rate); + + return IMX_LDB_INTERNAL_DIVISOR(target_rate); +} + +static int imx_ldb_attach(struct udevice *dev) +{ + struct imx_ldb_priv *priv = dev_get_priv(dev); + struct display_timing timings; + bool format_jeida = false; + bool format_24bpp = true; + u32 ldb_ctrl = 0, lvds_ctrl; + ulong ldb_rate; + int ret; + + /* TODO: update the 24bpp/jeida booleans with proper checks when they + * will be supported. + */ + if (priv->lvds1) { + ret = panel_get_display_timing(priv->lvds1, &timings); + if (ret) { + ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds1), + 0, &timings); + if (ret) { + printf("Cannot decode lvds1 timings (%d)\n", ret); + return ret; + } + } + + ldb_ctrl |= LDB_CTRL_CH0_ENABLE; + if (format_24bpp) + ldb_ctrl |= LDB_CTRL_CH0_DATA_WIDTH; + if (format_jeida) + ldb_ctrl |= LDB_CTRL_CH0_BIT_MAPPING; + if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH) + ldb_ctrl |= LDB_CTRL_DI0_VSYNC_POLARITY; + } + + if (priv->lvds2) { + ret = panel_get_display_timing(priv->lvds2, &timings); + if (ret) { + ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds2), + 0, &timings); + if (ret) { + printf("Cannot decode lvds2 timings (%d)\n", ret); + return ret; + } + } + + ldb_ctrl |= LDB_CTRL_CH1_ENABLE; + if (format_24bpp) + ldb_ctrl |= LDB_CTRL_CH1_DATA_WIDTH; + if (format_jeida) + ldb_ctrl |= LDB_CTRL_CH1_BIT_MAPPING; + if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH) + ldb_ctrl |= LDB_CTRL_DI1_VSYNC_POLARITY; + } + + /* + * Not all pixel clocks will work, as the final rate (after internal + * integer division) should be identical to the LCDIF clock, otherwise + * the rendering will appear resized/shimmering. + */ + ldb_rate = imx_ldb_input_rate(priv, &timings); + clk_set_rate(&priv->ldb_clk, ldb_rate); + + writel(ldb_ctrl, priv->ldb_ctrl); + + lvds_ctrl = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN | + LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN; + writel(lvds_ctrl, priv->lvds_ctrl); + + /* Wait for VBG to stabilize. */ + udelay(15); + + if (priv->lvds1) + lvds_ctrl |= LVDS_CTRL_CH0_EN; + if (priv->lvds2) + lvds_ctrl |= LVDS_CTRL_CH1_EN; + + writel(lvds_ctrl, priv->lvds_ctrl); + + return 0; +} + +static int imx_ldb_probe(struct udevice *dev) +{ + struct imx_ldb_priv *priv = dev_get_priv(dev); + struct udevice *parent = dev_get_parent(dev); + fdt_addr_t parent_addr, child_addr; + int ret; + + ret = clk_get_by_name(dev, "ldb", &priv->ldb_clk); + if (ret < 0) + return ret; + + parent_addr = dev_read_addr(parent); + if (parent_addr == FDT_ADDR_T_NONE) + return -EINVAL; + + child_addr = dev_read_addr_name(dev, "ldb"); + if (child_addr == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->ldb_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE); + if (!priv->ldb_ctrl) + return -EINVAL; + + child_addr = dev_read_addr_name(dev, "lvds"); + if (child_addr == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->lvds_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE); + if (!priv->lvds_ctrl) + return -EINVAL; + + ret = clk_enable(&priv->ldb_clk); + if (ret) + return ret; + + ret = video_bridge_set_active(dev, true); + if (ret) + goto dis_clk; + + return 0; + +dis_clk: + clk_disable(&priv->ldb_clk); + + return ret; +} + +struct video_bridge_ops imx_ldb_ops = { + .attach = imx_ldb_attach, + .set_backlight = imx_ldb_set_backlight, +}; + +static const struct udevice_id imx_ldb_ids[] = { + { .compatible = "fsl,imx8mp-ldb"}, + { } +}; + +U_BOOT_DRIVER(imx_ldb) = { + .name = "imx-lvds-display-bridge", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = imx_ldb_ids, + .probe = imx_ldb_probe, + .of_to_plat = imx_ldb_of_to_plat, + .ops = &imx_ldb_ops, + .priv_auto = sizeof(struct imx_ldb_priv), +}; |
