From 842593660195f52d119f240459f634e7b2110348 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Fri, 19 Sep 2025 04:44:47 +0800 Subject: [PATCH 01/33] FROMLIST: riscv: dts: thead: add xtheadvector to the th1520 devicetree The th1520 support xtheadvector [1] so it can be included in the devicetree. Also include vlenb for the cpu. And set vlenb=16 [2]. This can be tested by passing the "mitigations=off" kernel parameter. Link: https://lore.kernel.org/linux-riscv/20241113-xtheadvector-v11-4-236c22791ef9@rivosinc.com/ [1] Link: https://lore.kernel.org/linux-riscv/aCO44SAoS2kIP61r@ghost/ [2] Signed-off-by: Han Gao Reviewed-by: Drew Fustini Link: https://lore.kernel.org/r/1ff3fb07b24fb375fcf9d3067aa50583f47c35fe.1758228055.git.rabenda.cn@gmail.com Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index e680d1a7c821f3..0b57699ba398d5 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -25,7 +25,8 @@ riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm"; + "zifencei", "zihpm", "xtheadvector"; + thead,vlenb = <16>; reg = <0>; i-cache-block-size = <64>; i-cache-size = <65536>; @@ -49,7 +50,8 @@ riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm"; + "zifencei", "zihpm", "xtheadvector"; + thead,vlenb = <16>; reg = <1>; i-cache-block-size = <64>; i-cache-size = <65536>; @@ -73,7 +75,8 @@ riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm"; + "zifencei", "zihpm", "xtheadvector"; + thead,vlenb = <16>; reg = <2>; i-cache-block-size = <64>; i-cache-size = <65536>; @@ -97,7 +100,8 @@ riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm"; + "zifencei", "zihpm", "xtheadvector"; + thead,vlenb = <16>; reg = <3>; i-cache-block-size = <64>; i-cache-size = <65536>; From 71b47e8248f594c37d63c668c56eddd35c11d961 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Fri, 19 Sep 2025 04:44:48 +0800 Subject: [PATCH 02/33] FROMLIST: riscv: dts: thead: add ziccrse for th1520 Existing rv64 hardware conforms to the rva20 profile. Ziccrse is an additional extension required by the rva20 profile, so th1520 has this extension. Signed-off-by: Han Gao Reviewed-by: Drew Fustini Link: https://lore.kernel.org/r/71ac2ff73a63bd8674c4bc91fd287390d5339609.1758228055.git.rabenda.cn@gmail.com Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 0b57699ba398d5..8e50e24040c2b5 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -24,8 +24,10 @@ device_type = "cpu"; riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; - riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm", "xtheadvector"; + riscv,isa-extensions = "i", "m", "a", "f", "d", "c", + "ziccrse", "zicntr", "zicsr", + "zifencei", "zihpm", + "xtheadvector"; thead,vlenb = <16>; reg = <0>; i-cache-block-size = <64>; @@ -49,8 +51,10 @@ device_type = "cpu"; riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; - riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm", "xtheadvector"; + riscv,isa-extensions = "i", "m", "a", "f", "d", "c", + "ziccrse", "zicntr", "zicsr", + "zifencei", "zihpm", + "xtheadvector"; thead,vlenb = <16>; reg = <1>; i-cache-block-size = <64>; @@ -74,8 +78,10 @@ device_type = "cpu"; riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; - riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm", "xtheadvector"; + riscv,isa-extensions = "i", "m", "a", "f", "d", "c", + "ziccrse", "zicntr", "zicsr", + "zifencei", "zihpm", + "xtheadvector"; thead,vlenb = <16>; reg = <2>; i-cache-block-size = <64>; @@ -99,8 +105,10 @@ device_type = "cpu"; riscv,isa = "rv64imafdc"; riscv,isa-base = "rv64i"; - riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "zicntr", "zicsr", - "zifencei", "zihpm", "xtheadvector"; + riscv,isa-extensions = "i", "m", "a", "f", "d", "c", + "ziccrse", "zicntr", "zicsr", + "zifencei", "zihpm", + "xtheadvector"; thead,vlenb = <16>; reg = <3>; i-cache-block-size = <64>; From 3289e0e1217ced3a76e95ababc55be47e26aa89e Mon Sep 17 00:00:00 2001 From: Han Gao Date: Fri, 19 Sep 2025 04:44:49 +0800 Subject: [PATCH 03/33] FROMLIST: riscv: dts: thead: add zfh for th1520 th1520 support Zfh ISA extension. It supports the same RISC-V extensions as SG2042. commit cb074bed1186 ("riscv: dts: sophgo: add zfh for sg2042") Signed-off-by: Han Gao Reviewed-by: Drew Fustini Link: https://lore.kernel.org/r/38abc793c2fcb9dd26c89bab10dd4450d91ced4c.1758228055.git.rabenda.cn@gmail.com Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 8e50e24040c2b5..dfc868e5b19aad 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -26,7 +26,7 @@ riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "ziccrse", "zicntr", "zicsr", - "zifencei", "zihpm", + "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; reg = <0>; @@ -53,7 +53,7 @@ riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "ziccrse", "zicntr", "zicsr", - "zifencei", "zihpm", + "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; reg = <1>; @@ -80,7 +80,7 @@ riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "ziccrse", "zicntr", "zicsr", - "zifencei", "zihpm", + "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; reg = <2>; @@ -107,7 +107,7 @@ riscv,isa-base = "rv64i"; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "ziccrse", "zicntr", "zicsr", - "zifencei", "zihpm", + "zifencei", "zihpm", "zfh", "xtheadvector"; thead,vlenb = <16>; reg = <3>; From f7ec2a2b0cc49ae78e3f8129c4425ee1370c5df6 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:10 +0000 Subject: [PATCH 04/33] FROMLIST: dt-bindings: clock: thead,th1520-clk-ap: Add ID for C910 bus clock Add binding ID for C910 bus clock, which takes CLK_C910 as parent and is essential for C910 cluster's operation. Signed-off-by: Yao Zi Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20251120131416.26236-2-ziyao@disroot.org Signed-off-by: Han Gao --- include/dt-bindings/clock/thead,th1520-clk-ap.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/clock/thead,th1520-clk-ap.h b/include/dt-bindings/clock/thead,th1520-clk-ap.h index 09a9aa7b3ab19a..68b35cc6120413 100644 --- a/include/dt-bindings/clock/thead,th1520-clk-ap.h +++ b/include/dt-bindings/clock/thead,th1520-clk-ap.h @@ -93,6 +93,7 @@ #define CLK_SRAM3 83 #define CLK_PLL_GMAC_100M 84 #define CLK_UART_SCLK 85 +#define CLK_C910_BUS 86 /* VO clocks */ #define CLK_AXI4_VO_ACLK 0 From ca3c96ddb2d07af4686c7f26cfb7de3ba9386a6a Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:11 +0000 Subject: [PATCH 05/33] FROMLIST: clk: thead: th1520-ap: Poll for PLL lock and wait for stability All PLLs found on TH1520 SoC take 21250ns at maximum to lock, and their lock status is indicated by register PLL_STS (offset 0x80 inside AP clock controller). We should poll the register to ensure the PLL actually locks after enabling it. Furthermore, a 30us delay is added after enabling the PLL, after which the PLL could be considered stable as stated by vendor clock code. Fixes: 56a48c1833aa ("clk: thead: add support for enabling/disabling PLLs") Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-3-ziyao@disroot.org Signed-off-by: Han Gao --- drivers/clk/thead/clk-th1520-ap.c | 34 +++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index 71ad03a998e8e1..d870f0c665f8a4 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -8,11 +8,14 @@ #include #include #include +#include #include #include #include #include +#define TH1520_PLL_STS 0x80 + #define TH1520_PLL_POSTDIV2 GENMASK(26, 24) #define TH1520_PLL_POSTDIV1 GENMASK(22, 20) #define TH1520_PLL_FBDIV GENMASK(19, 8) @@ -23,6 +26,13 @@ #define TH1520_PLL_FRAC GENMASK(23, 0) #define TH1520_PLL_FRAC_BITS 24 +/* + * All PLLs in TH1520 take 21250ns at maximum to lock, let's take its double + * for safety. + */ +#define TH1520_PLL_LOCK_TIMEOUT_US 44 +#define TH1520_PLL_STABLE_DELAY_US 30 + struct ccu_internal { u8 shift; u8 width; @@ -64,6 +74,7 @@ struct ccu_div { struct ccu_pll { struct ccu_common common; + u32 lock_sts_mask; }; #define TH_CCU_ARG(_shift, _width) \ @@ -299,9 +310,21 @@ static void ccu_pll_disable(struct clk_hw *hw) static int ccu_pll_enable(struct clk_hw *hw) { struct ccu_pll *pll = hw_to_ccu_pll(hw); + u32 reg; + int ret; - return regmap_clear_bits(pll->common.map, pll->common.cfg1, - TH1520_PLL_VCO_RST); + regmap_clear_bits(pll->common.map, pll->common.cfg1, + TH1520_PLL_VCO_RST); + + ret = regmap_read_poll_timeout_atomic(pll->common.map, TH1520_PLL_STS, + reg, reg & pll->lock_sts_mask, + 5, TH1520_PLL_LOCK_TIMEOUT_US); + if (ret) + return ret; + + udelay(TH1520_PLL_STABLE_DELAY_US); + + return 0; } static int ccu_pll_is_enabled(struct clk_hw *hw) @@ -389,6 +412,7 @@ static struct ccu_pll cpu_pll0_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(1), }; static struct ccu_pll cpu_pll1_clk = { @@ -401,6 +425,7 @@ static struct ccu_pll cpu_pll1_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(4), }; static struct ccu_pll gmac_pll_clk = { @@ -413,6 +438,7 @@ static struct ccu_pll gmac_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(3), }; static const struct clk_hw *gmac_pll_clk_parent[] = { @@ -433,6 +459,7 @@ static struct ccu_pll video_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(7), }; static const struct clk_hw *video_pll_clk_parent[] = { @@ -453,6 +480,7 @@ static struct ccu_pll dpu0_pll_clk = { &clk_pll_ops, 0), }, + .lock_sts_mask = BIT(8), }; static const struct clk_hw *dpu0_pll_clk_parent[] = { @@ -469,6 +497,7 @@ static struct ccu_pll dpu1_pll_clk = { &clk_pll_ops, 0), }, + .lock_sts_mask = BIT(9), }; static const struct clk_hw *dpu1_pll_clk_parent[] = { @@ -485,6 +514,7 @@ static struct ccu_pll tee_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(10), }; static const struct clk_parent_data c910_i0_parents[] = { From 4ed2d6c3fbc237fc9009560dff9d8f6310f44d77 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:12 +0000 Subject: [PATCH 06/33] FROMLIST: clk: thead: th1520-ap: Add C910 bus clock This divider takes c910_clk as parent and is essential for the C910 cluster to operate, thus is marked as CLK_IS_CRITICAL. Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-4-ziyao@disroot.org Signed-off-by: Han Gao --- drivers/clk/thead/clk-th1520-ap.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index d870f0c665f8a4..b820d47387bb94 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -539,6 +539,20 @@ static struct ccu_mux c910_clk = { .mux = TH_CCU_MUX("c910", c910_parents, 0, 1), }; +static struct ccu_div c910_bus_clk = { + .enable = BIT(7), + .div_en = BIT(11), + .div = TH_CCU_DIV_FLAGS(8, 3, 0), + .common = { + .clkid = CLK_C910_BUS, + .cfg0 = 0x100, + .hw.init = CLK_HW_INIT_HW("c910-bus", + &c910_clk.mux.hw, + &ccu_div_ops, + CLK_IS_CRITICAL), + }, +}; + static const struct clk_parent_data ahb2_cpusys_parents[] = { { .hw = &gmac_pll_clk.common.hw }, { .index = 0 } @@ -1051,6 +1065,7 @@ static struct ccu_common *th1520_pll_clks[] = { }; static struct ccu_common *th1520_div_clks[] = { + &c910_bus_clk.common, &ahb2_cpusys_hclk.common, &apb3_cpusys_pclk.common, &axi4_cpusys2_aclk.common, @@ -1194,7 +1209,7 @@ static const struct th1520_plat_data th1520_ap_platdata = { .th1520_mux_clks = th1520_mux_clks, .th1520_gate_clks = th1520_gate_clks, - .nr_clks = CLK_UART_SCLK + 1, + .nr_clks = CLK_C910_BUS + 1, .nr_pll_clks = ARRAY_SIZE(th1520_pll_clks), .nr_div_clks = ARRAY_SIZE(th1520_div_clks), From 032b66d0558520b8bb49a74c5247f43b87327c34 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:13 +0000 Subject: [PATCH 07/33] FROMLIST: clk: thead: th1520-ap: Support setting PLL rates TH1520 ships several PLLs that could operate in either integer or fractional mode. However, the TRM only lists a few configuration whose stability is considered guaranteed. Add a table-lookup rate determination logic to support PLL rate setting, and fill up frequency-configuration tables for AP-subsystem PLLs. Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-5-ziyao@disroot.org Signed-off-by: Han Gao --- drivers/clk/thead/clk-th1520-ap.c | 142 ++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index b820d47387bb94..bf8e80c39a9ed2 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -22,6 +22,7 @@ #define TH1520_PLL_REFDIV GENMASK(5, 0) #define TH1520_PLL_BYPASS BIT(30) #define TH1520_PLL_VCO_RST BIT(29) +#define TH1520_PLL_DACPD BIT(25) #define TH1520_PLL_DSMPD BIT(24) #define TH1520_PLL_FRAC GENMASK(23, 0) #define TH1520_PLL_FRAC_BITS 24 @@ -72,9 +73,19 @@ struct ccu_div { struct ccu_common common; }; +struct ccu_pll_cfg { + unsigned long freq; + u32 fbdiv; + u32 frac; + u32 postdiv1; + u32 postdiv2; +}; + struct ccu_pll { struct ccu_common common; u32 lock_sts_mask; + int cfgnum; + const struct ccu_pll_cfg *cfgs; }; #define TH_CCU_ARG(_shift, _width) \ @@ -391,17 +402,102 @@ static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, return rate; } +static const struct ccu_pll_cfg *ccu_pll_lookup_best_cfg(struct ccu_pll *pll, + unsigned long rate) +{ + unsigned long best_delta = ULONG_MAX; + const struct ccu_pll_cfg *best_cfg; + int i; + + for (i = 0; i < pll->cfgnum; i++) { + const struct ccu_pll_cfg *cfg = &pll->cfgs[i]; + unsigned long delta; + + delta = abs_diff(cfg->freq, rate); + if (delta < best_delta) { + best_delta = delta; + best_cfg = cfg; + } + } + + return best_cfg; +} + +static int ccu_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_pll *pll = hw_to_ccu_pll(hw); + + req->rate = ccu_pll_lookup_best_cfg(pll, req->rate)->freq; + + return 0; +} + +static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_pll *pll = hw_to_ccu_pll(hw); + const struct ccu_pll_cfg *cfg; + + cfg = ccu_pll_lookup_best_cfg(pll, rate); + + ccu_pll_disable(hw); + + regmap_write(pll->common.map, pll->common.cfg0, + FIELD_PREP(TH1520_PLL_REFDIV, 1) | + FIELD_PREP(TH1520_PLL_FBDIV, cfg->fbdiv) | + FIELD_PREP(TH1520_PLL_POSTDIV1, cfg->postdiv1) | + FIELD_PREP(TH1520_PLL_POSTDIV2, cfg->postdiv2)); + + regmap_update_bits(pll->common.map, pll->common.cfg1, + TH1520_PLL_DACPD | TH1520_PLL_DSMPD | + TH1520_PLL_FRAC, + cfg->frac ? cfg->frac : + TH1520_PLL_DACPD | TH1520_PLL_DSMPD); + + return ccu_pll_enable(hw); +} + static const struct clk_ops clk_pll_ops = { .disable = ccu_pll_disable, .enable = ccu_pll_enable, .is_enabled = ccu_pll_is_enabled, .recalc_rate = ccu_pll_recalc_rate, + .determine_rate = ccu_pll_determine_rate, + .set_rate = ccu_pll_set_rate, }; static const struct clk_parent_data osc_24m_clk[] = { { .index = 0 } }; +static const struct ccu_pll_cfg cpu_pll_cfgs[] = { + { 125000000, 125, 0, 6, 4 }, + { 200000000, 125, 0, 5, 3 }, + { 300000000, 125, 0, 5, 2 }, + { 400000000, 100, 0, 3, 2 }, + { 500000000, 125, 0, 6, 1 }, + { 600000000, 125, 0, 5, 1 }, + { 702000000, 117, 0, 4, 1 }, + { 800000000, 100, 0, 3, 1 }, + { 900000000, 75, 0, 2, 1 }, + { 1000000000, 125, 0, 3, 1 }, + { 1104000000, 92, 0, 2, 1 }, + { 1200000000, 100, 0, 2, 1 }, + { 1296000000, 108, 0, 2, 1 }, + { 1404000000, 117, 0, 2, 1 }, + { 1500000000, 125, 0, 2, 1 }, + { 1608000000, 67, 0, 1, 1 }, + { 1704000000, 71, 0, 1, 1 }, + { 1800000000, 75, 0, 1, 1 }, + { 1896000000, 79, 0, 1, 1 }, + { 1992000000, 83, 0, 1, 1 }, + { 2112000000, 88, 0, 1, 1 }, + { 2208000000, 92, 0, 1, 1 }, + { 2304000000, 96, 0, 1, 1 }, + { 2400000000, 100, 0, 1, 1 }, +}; + static struct ccu_pll cpu_pll0_clk = { .common = { .clkid = CLK_CPU_PLL0, @@ -413,6 +509,8 @@ static struct ccu_pll cpu_pll0_clk = { CLK_IS_CRITICAL), }, .lock_sts_mask = BIT(1), + .cfgnum = ARRAY_SIZE(cpu_pll_cfgs), + .cfgs = cpu_pll_cfgs, }; static struct ccu_pll cpu_pll1_clk = { @@ -426,6 +524,16 @@ static struct ccu_pll cpu_pll1_clk = { CLK_IS_CRITICAL), }, .lock_sts_mask = BIT(4), + .cfgnum = ARRAY_SIZE(cpu_pll_cfgs), + .cfgs = cpu_pll_cfgs, +}; + +static const struct ccu_pll_cfg gmac_pll_cfg = { + .freq = 1000000000, + .fbdiv = 125, + .frac = 0, + .postdiv1 = 3, + .postdiv2 = 1, }; static struct ccu_pll gmac_pll_clk = { @@ -439,6 +547,8 @@ static struct ccu_pll gmac_pll_clk = { CLK_IS_CRITICAL), }, .lock_sts_mask = BIT(3), + .cfgnum = 1, + .cfgs = &gmac_pll_cfg, }; static const struct clk_hw *gmac_pll_clk_parent[] = { @@ -449,6 +559,14 @@ static const struct clk_parent_data gmac_pll_clk_pd[] = { { .hw = &gmac_pll_clk.common.hw } }; +static const struct ccu_pll_cfg video_pll_cfg = { + .freq = 792000000, + .fbdiv = 99, + .frac = 0, + .postdiv1 = 3, + .postdiv2 = 1, +}; + static struct ccu_pll video_pll_clk = { .common = { .clkid = CLK_VIDEO_PLL, @@ -460,6 +578,8 @@ static struct ccu_pll video_pll_clk = { CLK_IS_CRITICAL), }, .lock_sts_mask = BIT(7), + .cfgnum = 1, + .cfgs = &video_pll_cfg, }; static const struct clk_hw *video_pll_clk_parent[] = { @@ -470,6 +590,14 @@ static const struct clk_parent_data video_pll_clk_pd[] = { { .hw = &video_pll_clk.common.hw } }; +static const struct ccu_pll_cfg dpu_pll_cfg = { + .freq = 1188000000, + .fbdiv = 99, + .frac = 0, + .postdiv1 = 2, + .postdiv2 = 1, +}; + static struct ccu_pll dpu0_pll_clk = { .common = { .clkid = CLK_DPU0_PLL, @@ -481,6 +609,8 @@ static struct ccu_pll dpu0_pll_clk = { 0), }, .lock_sts_mask = BIT(8), + .cfgnum = 1, + .cfgs = &dpu_pll_cfg, }; static const struct clk_hw *dpu0_pll_clk_parent[] = { @@ -498,12 +628,22 @@ static struct ccu_pll dpu1_pll_clk = { 0), }, .lock_sts_mask = BIT(9), + .cfgnum = 1, + .cfgs = &dpu_pll_cfg, }; static const struct clk_hw *dpu1_pll_clk_parent[] = { &dpu1_pll_clk.common.hw }; +static const struct ccu_pll_cfg tee_pll_cfg = { + .freq = 792000000, + .fbdiv = 99, + .frac = 0, + .postdiv1 = 3, + .postdiv2 = 1, +}; + static struct ccu_pll tee_pll_clk = { .common = { .clkid = CLK_TEE_PLL, @@ -515,6 +655,8 @@ static struct ccu_pll tee_pll_clk = { CLK_IS_CRITICAL), }, .lock_sts_mask = BIT(10), + .cfgnum = 1, + .cfgs = &tee_pll_cfg, }; static const struct clk_parent_data c910_i0_parents[] = { From 3e053ee0a0bfa420bad44e7042631653e625febe Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:14 +0000 Subject: [PATCH 08/33] FROMLIST: clk: thead: th1520-ap: Add macro to define multiplexers with flags The new macro, TH_CCU_MUX_FLAGS, extends TH_CCU_MUX macro by adding two parameters to specify clock flags and multiplexer flags. Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-6-ziyao@disroot.org Signed-off-by: Han Gao --- drivers/clk/thead/clk-th1520-ap.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index bf8e80c39a9ed2..79f001a047b2c8 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -101,17 +101,22 @@ struct ccu_pll { .flags = _flags, \ } -#define TH_CCU_MUX(_name, _parents, _shift, _width) \ +#define TH_CCU_MUX_FLAGS(_name, _parents, _shift, _width, _flags, \ + _mux_flags) \ { \ .mask = GENMASK(_width - 1, 0), \ .shift = _shift, \ + .flags = _mux_flags, \ .hw.init = CLK_HW_INIT_PARENTS_DATA( \ _name, \ _parents, \ &clk_mux_ops, \ - 0), \ + _flags), \ } +#define TH_CCU_MUX(_name, _parents, _shift, _width) \ + TH_CCU_MUX_FLAGS(_name, _parents, _shift, _width, 0, 0) + #define CCU_GATE(_clkid, _struct, _name, _parent, _reg, _bit, _flags) \ struct ccu_gate _struct = { \ .clkid = _clkid, \ From 1bcd90555274d86b9003d2c6e3e78bea90e477b2 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:15 +0000 Subject: [PATCH 09/33] FROMLIST: clk: thead: th1520-ap: Support CPU frequency scaling On TH1520 SoC, c910_clk feeds the CPU cluster. It could be glitchlessly reparented to one of the two PLLs: either to cpu_pll0 indirectly through c910_i0_clk, or to cpu_pll1 directly. To achieve glitchless rate change, customized clock operations are implemented for c910_clk: on rate change, the PLL not currently in use is configured to the requested rate first, then c910_clk reparents to it. Additionally, c910_bus_clk, which in turn takes c910_clk as parent, has a frequency limit of 750MHz. A clock notifier is registered on c910_clk to adjust c910_bus_clk on c910_clk rate change. Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-7-ziyao@disroot.org Signed-off-by: Han Gao --- drivers/clk/thead/clk-th1520-ap.c | 148 +++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index 79f001a047b2c8..cd3b396c9a3dba 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -7,9 +7,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -34,6 +36,9 @@ #define TH1520_PLL_LOCK_TIMEOUT_US 44 #define TH1520_PLL_STABLE_DELAY_US 30 +/* c910_bus_clk must be kept below 750MHz for stability */ +#define TH1520_C910_BUS_MAX_RATE (750 * 1000 * 1000) + struct ccu_internal { u8 shift; u8 width; @@ -472,6 +477,72 @@ static const struct clk_ops clk_pll_ops = { .set_rate = ccu_pll_set_rate, }; +/* + * c910_clk could be reparented glitchlessly for DVFS. There are two parents, + * - c910_i0_clk, dervided from cpu_pll0_clk or osc_24m. + * - cpu_pll1_clk, which provides the exact same set of rates as cpu_pll0_clk. + * + * During rate setting, always forward the request to the unused parent, and + * then switch c910_clk to it to avoid glitch. + */ +static u8 c910_clk_get_parent(struct clk_hw *hw) +{ + return clk_mux_ops.get_parent(hw); +} + +static int c910_clk_set_parent(struct clk_hw *hw, u8 index) +{ + return clk_mux_ops.set_parent(hw, index); +} + +static unsigned long c910_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return parent_rate; +} + +static int c910_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + u8 alt_parent_index = !c910_clk_get_parent(hw); + struct clk_hw *alt_parent; + + alt_parent = clk_hw_get_parent_by_index(hw, alt_parent_index); + + req->rate = clk_hw_round_rate(alt_parent, req->rate); + req->best_parent_hw = alt_parent; + req->best_parent_rate = req->rate; + + return 0; +} + +static int c910_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return -EOPNOTSUPP; +} + +static int c910_clk_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate, u8 index) +{ + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, index); + + clk_set_rate(parent->clk, parent_rate); + + c910_clk_set_parent(hw, index); + + return 0; +} + +static const struct clk_ops c910_clk_ops = { + .get_parent = c910_clk_get_parent, + .set_parent = c910_clk_set_parent, + .recalc_rate = c910_clk_recalc_rate, + .determine_rate = c910_clk_determine_rate, + .set_rate = c910_clk_set_rate, + .set_rate_and_parent = c910_clk_set_rate_and_parent, +}; + static const struct clk_parent_data osc_24m_clk[] = { { .index = 0 } }; @@ -672,7 +743,8 @@ static const struct clk_parent_data c910_i0_parents[] = { static struct ccu_mux c910_i0_clk = { .clkid = CLK_C910_I0, .reg = 0x100, - .mux = TH_CCU_MUX("c910-i0", c910_i0_parents, 1, 1), + .mux = TH_CCU_MUX_FLAGS("c910-i0", c910_i0_parents, 1, 1, + CLK_SET_RATE_PARENT, CLK_MUX_ROUND_CLOSEST), }; static const struct clk_parent_data c910_parents[] = { @@ -683,7 +755,14 @@ static const struct clk_parent_data c910_parents[] = { static struct ccu_mux c910_clk = { .clkid = CLK_C910, .reg = 0x100, - .mux = TH_CCU_MUX("c910", c910_parents, 0, 1), + .mux = { + .mask = BIT(0), + .shift = 0, + .hw.init = CLK_HW_INIT_PARENTS_DATA("c910", + c910_parents, + &c910_clk_ops, + CLK_SET_RATE_PARENT), + }, }; static struct ccu_div c910_bus_clk = { @@ -1372,11 +1451,69 @@ static const struct th1520_plat_data th1520_vo_platdata = { .nr_gate_clks = ARRAY_SIZE(th1520_vo_gate_clks), }; +/* + * Maintain clock rate of c910_bus_clk below TH1520_C910_BUS_MAX_RATE (750MHz) + * when its parent, c910_clk, changes the rate. + * + * Additionally, TRM is unclear about c910_bus_clk behavior with divisor set to + * 2, thus we should ensure the new divisor stays in (2, MAXDIVISOR). + */ +static unsigned long c910_bus_clk_divisor(struct ccu_div *cd, + unsigned long parent_rate) +{ + return clamp(DIV_ROUND_UP(parent_rate, TH1520_C910_BUS_MAX_RATE), + 2U, 1U << cd->div.width); +} + +static int c910_clk_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct clk_notifier_data *cnd = data; + unsigned long new_divisor, ref_rate; + + if (action != PRE_RATE_CHANGE && action != POST_RATE_CHANGE) + return NOTIFY_DONE; + + new_divisor = c910_bus_clk_divisor(&c910_bus_clk, cnd->new_rate); + + if (cnd->new_rate > cnd->old_rate) { + /* + * Scaling up. Adjust c910_bus_clk divisor + * - before c910_clk rate change to ensure the constraints + * aren't broken after scaling to higher rates, + * - after c910_clk rate change to keep c910_bus_clk as high as + * possible + */ + ref_rate = action == PRE_RATE_CHANGE ? + cnd->old_rate : cnd->new_rate; + clk_set_rate(c910_bus_clk.common.hw.clk, + ref_rate / new_divisor); + } else if (cnd->new_rate < cnd->old_rate && + action == POST_RATE_CHANGE) { + /* + * Scaling down. Adjust c910_bus_clk divisor only after + * c910_clk rate change to keep c910_bus_clk as high as + * possible, Scaling down never breaks the constraints. + */ + clk_set_rate(c910_bus_clk.common.hw.clk, + cnd->new_rate / new_divisor); + } else { + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static struct notifier_block c910_clk_notifier = { + .notifier_call = c910_clk_notifier_cb, +}; + static int th1520_clk_probe(struct platform_device *pdev) { const struct th1520_plat_data *plat_data; struct device *dev = &pdev->dev; struct clk_hw_onecell_data *priv; + struct clk *notifier_clk; struct regmap *map; void __iomem *base; @@ -1463,6 +1600,13 @@ static int th1520_clk_probe(struct platform_device *pdev) ret = devm_clk_hw_register(dev, &emmc_sdio_ref_clk.hw); if (ret) return ret; + + notifier_clk = devm_clk_hw_get_clk(dev, &c910_clk.mux.hw, + "dvfs"); + ret = devm_clk_notifier_register(dev, notifier_clk, + &c910_clk_notifier); + if (ret) + return ret; } ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv); From 195d9b61e7c3bd33303bbe2a8dbaa333cf3bb190 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:16 +0000 Subject: [PATCH 10/33] FROMLIST: NFU: riscv: dts: thead: Add CPU clock and OPP table for TH1520 Add operating point table for CPU cores, and wire up clocks for CPU nodes. This patch isn't intended for upstreaming but only for testing purpose, since the PMIC driver for scaling CPU voltage isn't ready yet. Only operating points whose voltage is satisified by Lichee Module 4A's PMIC default, i.e. <= 1.5GHz, are enabled. Signed-off-by: Yao Zi Link: https://lore.kernel.org/r/20251120131416.26236-8-ziyao@disroot.org Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index dfc868e5b19aad..3b922e1df6ea55 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -38,6 +38,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu0_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -65,6 +67,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu1_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -92,6 +96,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu2_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -119,6 +125,8 @@ d-cache-sets = <512>; next-level-cache = <&l2_cache>; mmu-type = "riscv,sv39"; + operating-points-v2 = <&cpu_opp>; + clocks = <&clk CLK_C910>; cpu3_intc: interrupt-controller { compatible = "riscv,cpu-intc"; @@ -137,6 +145,33 @@ }; }; + cpu_opp: opp-table-cpu { + compatible = "operating-points-v2"; + opp-shared; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + opp-microvolt = <600000>; + }; + + opp-800000000 { + opp-hz = /bits/ 64 <800000000>; + opp-microvolt = <700000>; + }; + + opp-1500000000 { + opp-hz = /bits/ 64 <1500000000>; + opp-microvolt = <800000>; + }; + +/* + opp-1848000000 { + opp-hz = /bits/ 64 <1848000000>; + opp-microvolt = <1000000>; + }; + */ + }; + pmu { compatible = "riscv,pmu"; riscv,event-to-mhpmcounters = From 1d11321be043c044b2b5f6303a0e1868d4fde0ee Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:18 +0800 Subject: [PATCH 11/33] FROMLIST: dt-bindings: vendor-prefixes: add verisilicon VeriSilicon is a Silicon IP vendor, which is the current owner of Vivante series video-related IPs and Hantro series video codec IPs. Add a vendor prefix for this company. Signed-off-by: Icenowy Zheng Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20251124105226.2860845-2-uwu@icenowy.me Signed-off-by: Han Gao --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index f1d1882009ba9e..88a83031f4d6be 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1721,6 +1721,8 @@ patternProperties: description: Variscite Ltd. "^vdl,.*": description: Van der Laan b.v. + "^verisilicon,.*": + description: VeriSilicon Microelectronics (Shanghai) Co., Ltd. "^vertexcom,.*": description: Vertexcom Technologies, Inc. "^via,.*": From 77517e58434814b1488a649162d60411029fba9f Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:19 +0800 Subject: [PATCH 12/33] FROMLIST: dt-bindings: display: add verisilicon,dc Verisilicon has a series of display controllers prefixed with DC and with self-identification facility like their GC series GPUs. Add a device tree binding for it. Depends on the specific DC model, it can have either one or two display outputs, and each display output could be set to DPI signal or "DP" signal (which seems to be some plain parallel bus to HDMI controllers). Signed-off-by: Icenowy Zheng Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-3-uwu@icenowy.me Signed-off-by: Han Gao --- .../bindings/display/verisilicon,dc.yaml | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml new file mode 100644 index 00000000000000..522a544498beae --- /dev/null +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Verisilicon DC-series display controllers + +maintainers: + - Icenowy Zheng + +properties: + $nodename: + pattern: "^display@[0-9a-f]+$" + + compatible: + items: + - enum: + - thead,th1520-dc8200 + - const: verisilicon,dc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + minItems: 4 + items: + - description: DC Core clock + - description: DMA AXI bus clock + - description: Configuration AHB bus clock + - description: Pixel clock of output 0 + - description: Pixel clock of output 1 + + clock-names: + minItems: 4 + items: + - const: core + - const: axi + - const: ahb + - const: pix0 + - const: pix1 + + resets: + items: + - description: DC Core reset + - description: DMA AXI bus reset + - description: Configuration AHB bus reset + + reset-names: + items: + - const: core + - const: axi + - const: ahb + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: The first output channel , endpoint 0 should be + used for DPI format output and endpoint 1 should be used + for DP format output. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: The second output channel if the DC variant + supports. Follow the same endpoint addressing rule with + the first port. + + required: + - port@0 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - ports + +allOf: + - if: + properties: + compatible: + contains: + const: thead,th1520-dc8200 + then: + properties: + clocks: + minItems: 5 + ports: + required: + - port@0 + - port@1 + +additionalProperties: false + +examples: + - | + #include + #include + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + + display@ffef600000 { + compatible = "thead,th1520-dc8200", "verisilicon,dc"; + reg = <0xff 0xef600000 0x0 0x100000>; + interrupts = <93 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vo CLK_DPU_CCLK>, + <&clk_vo CLK_DPU_ACLK>, + <&clk_vo CLK_DPU_HCLK>, + <&clk_vo CLK_DPU_PIXELCLK0>, + <&clk_vo CLK_DPU_PIXELCLK1>; + clock-names = "core", "axi", "ahb", "pix0", "pix1"; + resets = <&rst TH1520_RESET_ID_DPU_CORE>, + <&rst TH1520_RESET_ID_DPU_AXI>, + <&rst TH1520_RESET_ID_DPU_AHB>; + reset-names = "core", "axi", "ahb"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + }; + + port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + dpu_out_dp1: endpoint@1 { + reg = <1>; + remote-endpoint = <&hdmi_in>; + }; + }; + }; + }; + }; From f5813c9dbc5138cb402229aab628d240e76772cc Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:20 +0800 Subject: [PATCH 13/33] FROMLIST: drm: verisilicon: add a driver for Verisilicon display controllers This is a from-scratch driver targeting Verisilicon DC-series display controllers, which feature self-identification functionality like their GC-series GPUs. Only DC8200 is being supported now, and only the main framebuffer is set up (as the DRM primary plane). Support for more DC models and more features is my further targets. As the display controller is delivered to SoC vendors as a whole part, this driver does not use component framework and extra bridges inside a SoC is expected to be implemented as dedicated bridges (this driver properly supports bridge chaining). Signed-off-by: Icenowy Zheng Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-4-uwu@icenowy.me Signed-off-by: Han Gao --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/verisilicon/Kconfig | 15 + drivers/gpu/drm/verisilicon/Makefile | 5 + drivers/gpu/drm/verisilicon/vs_bridge.c | 330 ++++++++++++++++++ drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++ drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 54 +++ drivers/gpu/drm/verisilicon/vs_crtc.c | 217 ++++++++++++ drivers/gpu/drm/verisilicon/vs_crtc.h | 29 ++ drivers/gpu/drm/verisilicon/vs_crtc_regs.h | 60 ++++ drivers/gpu/drm/verisilicon/vs_dc.c | 205 +++++++++++ drivers/gpu/drm/verisilicon/vs_dc.h | 39 +++ drivers/gpu/drm/verisilicon/vs_dc_top_regs.h | 27 ++ drivers/gpu/drm/verisilicon/vs_drm.c | 177 ++++++++++ drivers/gpu/drm/verisilicon/vs_drm.h | 29 ++ drivers/gpu/drm/verisilicon/vs_hwdb.c | 150 ++++++++ drivers/gpu/drm/verisilicon/vs_hwdb.h | 29 ++ drivers/gpu/drm/verisilicon/vs_plane.c | 102 ++++++ drivers/gpu/drm/verisilicon/vs_plane.h | 68 ++++ .../gpu/drm/verisilicon/vs_primary_plane.c | 157 +++++++++ .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++ 21 files changed, 1789 insertions(+) create mode 100644 drivers/gpu/drm/verisilicon/Kconfig create mode 100644 drivers/gpu/drm/verisilicon/Makefile create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7e6bc0b3a589ce..41363da2cc59f2 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -398,6 +398,8 @@ source "drivers/gpu/drm/imagination/Kconfig" source "drivers/gpu/drm/tyr/Kconfig" +source "drivers/gpu/drm/verisilicon/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && HYPERV_VMBUS diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 48fa9f00b3750e..21098629f5cabf 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -232,6 +232,7 @@ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ obj-$(CONFIG_DRM_POWERVR) += imagination/ +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon/ # Ensure drm headers are self-contained and pass kernel-doc hdrtest-files := \ diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig new file mode 100644 index 00000000000000..0235577c728243 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_VERISILICON_DC + tristate "DRM Support for Verisilicon DC-series display controllers" + depends on DRM && COMMON_CLK + depends on RISCV || COMPILER_TEST + select DRM_CLIENT_SELECTION + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_BRIDGE_CONNECTOR + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have a SoC with Verisilicon DC-series + display controllers. If M is selected, the module will be called + verisilicon-dc. diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile new file mode 100644 index 00000000000000..fd8d805fbcde13 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o + +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c new file mode 100644 index 00000000000000..9696e574bcc7c5 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vs_bridge.h" +#include "vs_bridge_regs.h" +#include "vs_crtc.h" +#include "vs_dc.h" + +static int vs_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + + return drm_bridge_attach(encoder, vbridge->next, + bridge, flags); +} + +struct vsdc_dp_format { + u32 linux_fmt; + bool is_yuv; + u32 vsdc_fmt; +}; + +static struct vsdc_dp_format vsdc_dp_supported_fmts[] = { + /* default to RGB888 */ + { MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 }, + { MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 }, + { MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 }, + { MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 }, + { MEDIA_BUS_FMT_RGB101010_1X30, + false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 }, + { MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 }, + { MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 }, + { MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 }, + { MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 }, + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, + true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 }, + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, + true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 }, +}; + +static u32 *vs_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + u32 *output_fmts; + unsigned int i; + + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) + *num_output_fmts = 2; + else + *num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts); + + output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) { + /* TODO: support more DPI output formats */ + output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + output_fmts[1] = MEDIA_BUS_FMT_FIXED; + } else { + for (i = 0; i < *num_output_fmts; i++) + output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt; + } + + return output_fmts; +} + +static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) + if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt) + return true; + + return false; +} + +static u32 *vs_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP && + !vs_bridge_out_dp_fmt_supported(output_fmt)) { + *num_input_fmts = 0; + return NULL; + } + + return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state, + crtc_state, + conn_state, + output_fmt, + num_input_fmts); +} + +static int vs_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP && + !vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format)) + return -EINVAL; + + vbridge->output_bus_fmt = bridge_state->output_bus_cfg.format; + + return 0; +} + +static void vs_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + struct drm_bridge_state *br_state = drm_atomic_get_bridge_state(state, + bridge); + struct vs_crtc *crtc = vbridge->crtc; + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + u32 dp_fmt; + unsigned int i; + + DRM_DEBUG_DRIVER("Enabling output %u\n", output); + + switch (vbridge->intf) { + case VSDC_OUTPUT_INTERFACE_DPI: + regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output), + VSDC_DISP_DP_CONFIG_DP_EN); + regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output), + VSDC_DISP_DPI_CONFIG_FMT_RGB888); + break; + case VSDC_OUTPUT_INTERFACE_DP: + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) { + if (vsdc_dp_supported_fmts[i].linux_fmt == + vbridge->output_bus_fmt) + break; + } + if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts))) + return; + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt; + dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN; + regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt); + regmap_assign_bits(dc->regs, + VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_YUV, + vsdc_dp_supported_fmts[i].is_yuv); + break; + } + + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DAT_POL); + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DE_POL, + br_state->output_bus_cfg.flags & + DRM_BUS_FLAG_DE_LOW); + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_CLK_POL, + br_state->output_bus_cfg.flags & + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DE_EN | + VSDC_DISP_PANEL_CONFIG_DAT_EN | + VSDC_DISP_PANEL_CONFIG_CLK_EN); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_RUNNING); + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_MULTI_DISP_SYNC); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_RUNNING(output)); + + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id), + VSDC_DISP_PANEL_CONFIG_EX_COMMIT); +} + +static void vs_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + struct vs_crtc *crtc = vbridge->crtc; + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + + DRM_DEBUG_DRIVER("Disabling output %u\n", output); + + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_MULTI_DISP_SYNC | + VSDC_DISP_PANEL_START_RUNNING(output)); + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_RUNNING); + + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id), + VSDC_DISP_PANEL_CONFIG_EX_COMMIT); +} + +static const struct drm_bridge_funcs vs_bridge_funcs = { + .attach = vs_bridge_attach, + .atomic_enable = vs_bridge_atomic_enable, + .atomic_disable = vs_bridge_atomic_disable, + .atomic_check = vs_bridge_atomic_check, + .atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, +}; + +static int vs_bridge_detect_output_interface(struct device_node *of_node, + unsigned int output) +{ + int ret; + struct device_node *remote; + + remote = of_graph_get_remote_node(of_node, output, + VSDC_OUTPUT_INTERFACE_DPI); + if (remote) { + ret = VSDC_OUTPUT_INTERFACE_DPI; + } else { + remote = of_graph_get_remote_node(of_node, output, + VSDC_OUTPUT_INTERFACE_DP); + if (remote) + ret = VSDC_OUTPUT_INTERFACE_DP; + else + ret = -ENODEV; + } + + if (remote) + of_node_put(remote); + + return ret; +} + +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev, + struct vs_crtc *crtc) +{ + unsigned int output = crtc->id; + struct vs_bridge *bridge; + struct drm_bridge *next; + enum vs_bridge_output_interface intf; + int ret, enctype; + + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node, + output); + if (intf == -ENODEV) { + dev_info(drm_dev->dev, "Skipping output %u\n", output); + return NULL; + } + + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node, + output, intf); + if (IS_ERR(next)) { + ret = PTR_ERR(next); + dev_err_probe(drm_dev->dev, ret, + "Cannot get downstream bridge of output %u\n", + output); + return ERR_PTR(ret); + } + + bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base, + &vs_bridge_funcs); + if (!bridge) + return ERR_PTR(-ENOMEM); + + bridge->crtc = crtc; + bridge->intf = intf; + bridge->next = next; + + if (intf == VSDC_OUTPUT_INTERFACE_DPI) + enctype = DRM_MODE_ENCODER_DPI; + else + enctype = DRM_MODE_ENCODER_NONE; + + bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL); + if (IS_ERR(bridge->enc)) { + dev_err(drm_dev->dev, + "Cannot initialize encoder for output %u\n", output); + ret = PTR_ERR(bridge->enc); + return ERR_PTR(ret); + } + + bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base); + + ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(drm_dev->dev, + "Cannot attach bridge for output %u\n", output); + return ERR_PTR(ret); + } + + bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc); + if (IS_ERR(bridge->conn)) { + dev_err(drm_dev->dev, + "Cannot create connector for output %u\n", output); + ret = PTR_ERR(bridge->conn); + return ERR_PTR(ret); + } + drm_connector_attach_encoder(bridge->conn, bridge->enc); + + return bridge; +} diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h new file mode 100644 index 00000000000000..4120abafdaed6d --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#ifndef _VS_BRIDGE_H_ +#define _VS_BRIDGE_H_ + +#include + +#include +#include +#include + +struct vs_crtc; + +enum vs_bridge_output_interface { + VSDC_OUTPUT_INTERFACE_DPI = 0, + VSDC_OUTPUT_INTERFACE_DP = 1 +}; + +struct vs_bridge { + struct drm_bridge base; + struct drm_encoder *enc; + struct drm_connector *conn; + + struct vs_crtc *crtc; + struct drm_bridge *next; + enum vs_bridge_output_interface intf; + u32 output_bus_fmt; +}; + +static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct vs_bridge, base); +} + +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev, + struct vs_crtc *crtc); +#endif /* _VS_BRIDGE_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h new file mode 100644 index 00000000000000..9eb30e4564beb6 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_BRIDGE_REGS_H_ +#define _VS_BRIDGE_REGS_H_ + +#include + +#define VSDC_DISP_PANEL_CONFIG(n) (0x1418 + 0x4 * (n)) +#define VSDC_DISP_PANEL_CONFIG_DE_EN BIT(0) +#define VSDC_DISP_PANEL_CONFIG_DE_POL BIT(1) +#define VSDC_DISP_PANEL_CONFIG_DAT_EN BIT(4) +#define VSDC_DISP_PANEL_CONFIG_DAT_POL BIT(5) +#define VSDC_DISP_PANEL_CONFIG_CLK_EN BIT(8) +#define VSDC_DISP_PANEL_CONFIG_CLK_POL BIT(9) +#define VSDC_DISP_PANEL_CONFIG_RUNNING BIT(12) +#define VSDC_DISP_PANEL_CONFIG_GAMMA BIT(13) +#define VSDC_DISP_PANEL_CONFIG_YUV BIT(16) + +#define VSDC_DISP_DPI_CONFIG(n) (0x14B8 + 0x4 * (n)) +#define VSDC_DISP_DPI_CONFIG_FMT_MASK GENMASK(2, 0) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB565 (0) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB666 (3) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB888 (5) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010 (6) + +#define VSDC_DISP_PANEL_START 0x1CCC +#define VSDC_DISP_PANEL_START_RUNNING(n) BIT(n) +#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC BIT(3) + +#define VSDC_DISP_DP_CONFIG(n) (0x1CD0 + 0x4 * (n)) +#define VSDC_DISP_DP_CONFIG_DP_EN BIT(3) +#define VSDC_DISP_DP_CONFIG_FMT_MASK GENMASK(2, 0) +#define VSDC_DISP_DP_CONFIG_FMT_RGB565 (0) +#define VSDC_DISP_DP_CONFIG_FMT_RGB666 (1) +#define VSDC_DISP_DP_CONFIG_FMT_RGB888 (2) +#define VSDC_DISP_DP_CONFIG_FMT_RGB101010 (3) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK GENMASK(7, 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 (2 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 (4 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 (8 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 (10 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 (12 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 (13 << 4) + +#define VSDC_DISP_PANEL_CONFIG_EX(n) (0x2518 + 0x4 * (n)) +#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT BIT(0) + +#endif /* _VS_BRIDGE_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c new file mode 100644 index 00000000000000..8825b77ed3ca0a --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include +#include + +#include +#include +#include +#include + +#include "vs_crtc_regs.h" +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_plane.h" + +static void vs_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct drm_pending_vblank_event *event = crtc_state->event; + + DRM_DEBUG_DRIVER("Flushing CRTC %u vblank events\n", vcrtc->id); + + if (event) { + crtc_state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static void vs_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + DRM_DEBUG_DRIVER("Disabling CRTC %u\n", output); + + drm_crtc_vblank_off(crtc); + + clk_disable_unprepare(dc->pix_clk[output]); +} + +static void vs_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + DRM_DEBUG_DRIVER("Enabling CRTC %u\n", output); + + WARN_ON(clk_prepare_enable(dc->pix_clk[output])); + + drm_crtc_vblank_on(crtc); +} + +static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + DRM_DEBUG_DRIVER("Setting mode on CRTC %u\n", output); + + regmap_write(dc->regs, VSDC_DISP_HSIZE(output), + VSDC_DISP_HSIZE_DISP(mode->hdisplay) | + VSDC_DISP_HSIZE_TOTAL(mode->htotal)); + regmap_write(dc->regs, VSDC_DISP_VSIZE(output), + VSDC_DISP_VSIZE_DISP(mode->vdisplay) | + VSDC_DISP_VSIZE_TOTAL(mode->vtotal)); + regmap_write(dc->regs, VSDC_DISP_HSYNC(output), + VSDC_DISP_HSYNC_START(mode->hsync_start) | + VSDC_DISP_HSYNC_END(mode->hsync_end) | + VSDC_DISP_HSYNC_EN); + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output), + VSDC_DISP_HSYNC_POL); + regmap_write(dc->regs, VSDC_DISP_VSYNC(output), + VSDC_DISP_VSYNC_START(mode->vsync_start) | + VSDC_DISP_VSYNC_END(mode->vsync_end) | + VSDC_DISP_VSYNC_EN); + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output), + VSDC_DISP_VSYNC_POL); + + WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000)); +} + +static enum drm_mode_status +vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + long rate; + + if (mode->htotal > 0x7FFF) + return MODE_BAD_HVALUE; + if (mode->vtotal > 0x7FFF) + return MODE_BAD_VVALUE; + + rate = clk_round_rate(dc->pix_clk[output], mode->clock * 1000); + if (rate <= 0) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static bool vs_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *m, + struct drm_display_mode *adjusted_mode) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + long clk_rate; + + drm_mode_set_crtcinfo(adjusted_mode, 0); + + /* Feedback the pixel clock to crtc_clock */ + clk_rate = adjusted_mode->crtc_clock * 1000; + clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate); + if (clk_rate <= 0) + return false; + + adjusted_mode->crtc_clock = clk_rate / 1000; + + return true; +} + +static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = { + .atomic_flush = vs_crtc_atomic_flush, + .atomic_enable = vs_crtc_atomic_enable, + .atomic_disable = vs_crtc_atomic_disable, + .mode_set_nofb = vs_crtc_mode_set_nofb, + .mode_valid = vs_crtc_mode_valid, + .mode_fixup = vs_crtc_mode_fixup, +}; + +static int vs_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + + DRM_DEBUG_DRIVER("Enabling VBLANK on CRTC %u\n", vcrtc->id); + regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); + + return 0; +} + +static void vs_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + + DRM_DEBUG_DRIVER("Disabling VBLANK on CRTC %u\n", vcrtc->id); + regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); +} + +static const struct drm_crtc_funcs vs_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = vs_crtc_enable_vblank, + .disable_vblank = vs_crtc_disable_vblank, +}; + +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, + unsigned int output) +{ + struct vs_crtc *vcrtc; + struct drm_plane *primary; + int ret; + + vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL); + if (!vcrtc) + return ERR_PTR(-ENOMEM); + vcrtc->dc = dc; + vcrtc->id = output; + + /* Create our primary plane */ + primary = vs_primary_plane_init(drm_dev, dc); + if (IS_ERR(primary)) { + dev_err(drm_dev->dev, "Couldn't create the primary plane\n"); + return ERR_PTR(PTR_ERR(primary)); + } + + ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base, + primary, + NULL, + &vs_crtc_funcs, + NULL); + if (ret) { + dev_err(drm_dev->dev, "Couldn't initialize CRTC\n"); + return ERR_PTR(ret); + } + + drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs); + + return vcrtc; +} diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h new file mode 100644 index 00000000000000..6f862d609b984f --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#ifndef _VS_CRTC_H_ +#define _VS_CRTC_H_ + +#include +#include + +struct vs_dc; + +struct vs_crtc { + struct drm_crtc base; + + struct vs_dc *dc; + unsigned int id; +}; + +static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct vs_crtc, base); +} + +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, + unsigned int output); + +#endif /* _VS_CRTC_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h new file mode 100644 index 00000000000000..c7930e817635c1 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_CRTC_REGS_H_ +#define _VS_CRTC_REGS_H_ + +#include + +#define VSDC_DISP_DITHER_CONFIG(n) (0x1410 + 0x4 * (n)) + +#define VSDC_DISP_DITHER_TABLE_LOW(n) (0x1420 + 0x4 * (n)) +#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT 0x7B48F3C0 + +#define VSDC_DISP_DITHER_TABLE_HIGH(n) (0x1428 + 0x4 * (n)) +#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT 0x596AD1E2 + +#define VSDC_DISP_HSIZE(n) (0x1430 + 0x4 * (n)) +#define VSDC_DISP_HSIZE_DISP_MASK GENMASK(14, 0) +#define VSDC_DISP_HSIZE_DISP(v) ((v) << 0) +#define VSDC_DISP_HSIZE_TOTAL_MASK GENMASK(30, 16) +#define VSDC_DISP_HSIZE_TOTAL(v) ((v) << 16) + +#define VSDC_DISP_HSYNC(n) (0x1438 + 0x4 * (n)) +#define VSDC_DISP_HSYNC_START_MASK GENMASK(14, 0) +#define VSDC_DISP_HSYNC_START(v) ((v) << 0) +#define VSDC_DISP_HSYNC_END_MASK GENMASK(29, 15) +#define VSDC_DISP_HSYNC_END(v) ((v) << 15) +#define VSDC_DISP_HSYNC_EN BIT(30) +#define VSDC_DISP_HSYNC_POL BIT(31) + +#define VSDC_DISP_VSIZE(n) (0x1440 + 0x4 * (n)) +#define VSDC_DISP_VSIZE_DISP_MASK GENMASK(14, 0) +#define VSDC_DISP_VSIZE_DISP(v) ((v) << 0) +#define VSDC_DISP_VSIZE_TOTAL_MASK GENMASK(30, 16) +#define VSDC_DISP_VSIZE_TOTAL(v) ((v) << 16) + +#define VSDC_DISP_VSYNC(n) (0x1448 + 0x4 * (n)) +#define VSDC_DISP_VSYNC_START_MASK GENMASK(14, 0) +#define VSDC_DISP_VSYNC_START(v) ((v) << 0) +#define VSDC_DISP_VSYNC_END_MASK GENMASK(29, 15) +#define VSDC_DISP_VSYNC_END(v) ((v) << 15) +#define VSDC_DISP_VSYNC_EN BIT(30) +#define VSDC_DISP_VSYNC_POL BIT(31) + +#define VSDC_DISP_CURRENT_LOCATION(n) (0x1450 + 0x4 * (n)) + +#define VSDC_DISP_GAMMA_INDEX(n) (0x1458 + 0x4 * (n)) + +#define VSDC_DISP_GAMMA_DATA(n) (0x1460 + 0x4 * (n)) + +#define VSDC_DISP_IRQ_STA 0x147C + +#define VSDC_DISP_IRQ_EN 0x1480 + +#endif /* _VS_CRTC_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c new file mode 100644 index 00000000000000..a413479c6cfff3 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include +#include +#include + +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_drm.h" +#include "vs_hwdb.h" + +static const struct regmap_config vs_dc_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + /* VSDC_OVL_CONFIG_EX(1) */ + .max_register = 0x2544, +}; + +static const struct of_device_id vs_dc_driver_dt_match[] = { + { .compatible = "verisilicon,dc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match); + +static irqreturn_t vs_dc_irq_handler(int irq, void *private) +{ + struct vs_dc *dc = private; + u32 irqs; + + regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs); + + return vs_drm_handle_irq(dc, irqs); +} + +static int vs_dc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vs_dc *dc; + void __iomem *regs; + unsigned int outputs, i; + /* pix0/pix1 */ + char pixclk_name[5]; + int irq, ret; + + if (!dev->of_node) { + dev_err(dev, "can't find DC devices\n"); + return -ENODEV; + } + + outputs = of_graph_get_port_count(dev->of_node); + if (!outputs) { + dev_err(dev, "can't find DC downstream ports\n"); + return -ENODEV; + } + if (outputs > VSDC_MAX_OUTPUTS) { + dev_err(dev, "too many DC downstream ports than possible\n"); + return -EINVAL; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "No suitable DMA available\n"); + return ret; + } + + dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL); + if (!dc) + return -ENOMEM; + + dc->outputs = outputs; + + dc->rsts[0].id = "core"; + dc->rsts[1].id = "axi"; + dc->rsts[2].id = "ahb"; + + ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT, + dc->rsts); + if (ret) { + dev_err(dev, "can't get reset lines\n"); + return ret; + } + + dc->core_clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(dc->core_clk)) { + dev_err(dev, "can't get core clock\n"); + return PTR_ERR(dc->core_clk); + } + + dc->axi_clk = devm_clk_get_enabled(dev, "axi"); + if (IS_ERR(dc->axi_clk)) { + dev_err(dev, "can't get axi clock\n"); + return PTR_ERR(dc->axi_clk); + } + + dc->ahb_clk = devm_clk_get_enabled(dev, "ahb"); + if (IS_ERR(dc->ahb_clk)) { + dev_err(dev, "can't get ahb clock\n"); + return PTR_ERR(dc->ahb_clk); + } + + for (i = 0; i < outputs; i++) { + snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i); + dc->pix_clk[i] = devm_clk_get(dev, pixclk_name); + if (IS_ERR(dc->pix_clk[i])) { + dev_err(dev, "can't get pixel clk %u\n", i); + return PTR_ERR(dc->pix_clk[i]); + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "can't get irq\n"); + return irq; + } + + ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts); + if (ret) { + dev_err(dev, "can't deassert reset lines\n"); + return ret; + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + dev_err(dev, "can't map registers"); + ret = PTR_ERR(regs); + goto err_rst_assert; + } + + dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg); + if (IS_ERR(dc->regs)) { + ret = PTR_ERR(dc->regs); + goto err_rst_assert; + } + + ret = vs_fill_chip_identity(dc->regs, &dc->identity); + if (ret) + goto err_rst_assert; + + dev_info(dev, "DC%x rev %x customer %x\n", dc->identity.model, + dc->identity.revision, dc->identity.customer_id); + + if (outputs > dc->identity.display_count) { + dev_err(dev, "too many downstream ports than HW capability\n"); + ret = -EINVAL; + goto err_rst_assert; + } + + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0, + dev_name(dev), dc); + if (ret) { + dev_err(dev, "can't request irq\n"); + goto err_rst_assert; + } + + dev_set_drvdata(dev, dc); + + ret = vs_drm_initialize(dc, pdev); + if (ret) + goto err_rst_assert; + + return 0; + +err_rst_assert: + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); + return ret; +} + +static void vs_dc_remove(struct platform_device *pdev) +{ + struct vs_dc *dc = dev_get_drvdata(&pdev->dev); + + vs_drm_finalize(dc); + + dev_set_drvdata(&pdev->dev, NULL); + + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); +} + +static void vs_dc_shutdown(struct platform_device *pdev) +{ + struct vs_dc *dc = dev_get_drvdata(&pdev->dev); + + vs_drm_shutdown_handler(dc); +} + +struct platform_driver vs_dc_platform_driver = { + .probe = vs_dc_probe, + .remove = vs_dc_remove, + .shutdown = vs_dc_shutdown, + .driver = { + .name = "verisilicon-dc", + .of_match_table = vs_dc_driver_dt_match, + }, +}; + +module_platform_driver(vs_dc_platform_driver); + +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_DESCRIPTION("Verisilicon display controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h new file mode 100644 index 00000000000000..5e071501b1c382 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_DC_H_ +#define _VS_DC_H_ + +#include +#include +#include + +#include + +#include "vs_hwdb.h" + +#define VSDC_MAX_OUTPUTS 2 +#define VSDC_RESET_COUNT 3 + +struct vs_drm_dev; +struct vs_crtc; + +struct vs_dc { + struct regmap *regs; + struct clk *core_clk; + struct clk *axi_clk; + struct clk *ahb_clk; + struct clk *pix_clk[VSDC_MAX_OUTPUTS]; + struct reset_control_bulk_data rsts[VSDC_RESET_COUNT]; + + struct vs_drm_dev *drm_dev; + struct vs_chip_identity identity; + unsigned int outputs; +}; + +#endif /* _VS_DC_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h new file mode 100644 index 00000000000000..50509bbbff08fb --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_DC_TOP_H_ +#define _VS_DC_TOP_H_ + +#include + +#define VSDC_TOP_RST 0x0000 + +#define VSDC_TOP_IRQ_ACK 0x0010 +#define VSDC_TOP_IRQ_VSYNC(n) BIT(n) + +#define VSDC_TOP_IRQ_EN 0x0014 + +#define VSDC_TOP_CHIP_MODEL 0x0020 + +#define VSDC_TOP_CHIP_REV 0x0024 + +#define VSDC_TOP_CHIP_CUSTOMER_ID 0x0030 + +#endif /* _VS_DC_TOP_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c new file mode 100644 index 00000000000000..f356d7832c4490 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drm.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vs_bridge.h" +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_drm.h" + +#define DRIVER_NAME "verisilicon" +#define DRIVER_DESC "Verisilicon DC-series display controller driver" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static int vs_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + /* The hardware wants 128B-aligned pitches for linear buffers. */ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 128); + + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + +DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops); + +static const struct drm_driver vs_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &vs_drm_driver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + + /* GEM Operations */ + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, +}; + +static const struct drm_mode_config_funcs vs_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = { + .atomic_commit_tail = drm_atomic_helper_commit_tail, +}; + +static void vs_mode_config_init(struct drm_device *drm) +{ + drm_mode_config_reset(drm); + + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 8192; + drm->mode_config.max_height = 8192; + drm->mode_config.funcs = &vs_mode_config_funcs; + drm->mode_config.helper_private = &vs_mode_config_helper_funcs; +} + +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vs_drm_dev *vdrm; + struct drm_device *drm; + struct vs_crtc *crtc; + struct vs_bridge *bridge; + unsigned int i; + int ret; + + vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base); + if (IS_ERR(vdrm)) + return PTR_ERR(vdrm); + + drm = &vdrm->base; + vdrm->dc = dc; + dc->drm_dev = vdrm; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + for (i = 0; i < dc->outputs; i++) { + crtc = vs_crtc_init(drm, dc, i); + if (IS_ERR(crtc)) + return PTR_ERR(crtc); + + bridge = vs_bridge_init(drm, crtc); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + vdrm->crtcs[i] = crtc; + } + + ret = drm_vblank_init(drm, dc->outputs); + if (ret) + return ret; + + /* Remove early framebuffers (ie. simplefb) */ + ret = aperture_remove_all_conflicting_devices(DRIVER_NAME); + if (ret) + return ret; + + vs_mode_config_init(drm); + + /* Enable connectors polling */ + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_fini_poll; + + drm_client_setup(drm, NULL); + + return 0; + +err_fini_poll: + drm_kms_helper_poll_fini(drm); + return ret; +} + +void vs_drm_finalize(struct vs_dc *dc) +{ + struct vs_drm_dev *vdrm = dc->drm_dev; + struct drm_device *drm = &vdrm->base; + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + dc->drm_dev = NULL; +} + +void vs_drm_shutdown_handler(struct vs_dc *dc) +{ + struct vs_drm_dev *vdrm = dc->drm_dev; + + drm_atomic_helper_shutdown(&vdrm->base); +} + +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs) +{ + unsigned int i; + + for (i = 0; i < dc->outputs; i++) { + if (irqs & VSDC_TOP_IRQ_VSYNC(i)) { + irqs &= ~VSDC_TOP_IRQ_VSYNC(i); + if (dc->drm_dev->crtcs[i]) + drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base); + } + } + + if (irqs) + pr_warn("Unknown Verisilicon DC interrupt 0x%x fired!\n", irqs); + + return IRQ_HANDLED; +} diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h new file mode 100644 index 00000000000000..bbcd2e527deb61 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drm.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#ifndef _VS_DRM_H_ +#define _VS_DRM_H_ + +#include +#include +#include + +#include + +struct vs_dc; + +struct vs_drm_dev { + struct drm_device base; + + struct vs_dc *dc; + struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS]; +}; + +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev); +void vs_drm_finalize(struct vs_dc *dc); +void vs_drm_shutdown_handler(struct vs_dc *dc); +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs); + +#endif /* _VS_DRM_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c new file mode 100644 index 00000000000000..4a87e5d4701f3b --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include + +#include + +#include "vs_dc_top_regs.h" +#include "vs_hwdb.h" + +static const u32 vs_formats_array_no_yuv444[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + /* TODO: non-RGB formats */ +}; + +static const u32 vs_formats_array_with_yuv444[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + /* TODO: non-RGB formats */ +}; + +static const struct vs_formats vs_formats_no_yuv444 = { + .array = vs_formats_array_no_yuv444, + .num = ARRAY_SIZE(vs_formats_array_no_yuv444) +}; + +static const struct vs_formats vs_formats_with_yuv444 = { + .array = vs_formats_array_with_yuv444, + .num = ARRAY_SIZE(vs_formats_array_with_yuv444) +}; + +static struct vs_chip_identity vs_chip_identities[] = { + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = ~0U, + + .display_count = 2, + .formats = &vs_formats_no_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5721, + .customer_id = 0x30B, + + .display_count = 2, + .formats = &vs_formats_no_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = 0x310, + + .display_count = 2, + .formats = &vs_formats_with_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = 0x311, + + .display_count = 2, + .formats = &vs_formats_no_yuv444, + }, +}; + +int vs_fill_chip_identity(struct regmap *regs, + struct vs_chip_identity *ident) +{ + u32 model; + u32 revision; + u32 customer_id; + int i; + + regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model); + regmap_read(regs, VSDC_TOP_CHIP_REV, &revision); + regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id); + + for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) { + if (vs_chip_identities[i].model == model && + vs_chip_identities[i].revision == revision && + (vs_chip_identities[i].customer_id == customer_id || + vs_chip_identities[i].customer_id == ~0U)) { + memcpy(ident, &vs_chip_identities[i], sizeof(*ident)); + ident->customer_id = customer_id; + return 0; + } + } + + return -EINVAL; +} diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h new file mode 100644 index 00000000000000..92192e4fa08625 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#ifndef _VS_HWDB_H_ +#define _VS_HWDB_H_ + +#include +#include + +struct vs_formats { + const u32 *array; + unsigned int num; +}; + +struct vs_chip_identity { + u32 model; + u32 revision; + u32 customer_id; + + u32 display_count; + const struct vs_formats *formats; +}; + +int vs_fill_chip_identity(struct regmap *regs, + struct vs_chip_identity *ident); + +#endif /* _VS_HWDB_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c new file mode 100644 index 00000000000000..f3c9963b6a4ead --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include + +#include +#include + +#include "vs_plane.h" + +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format) +{ + switch (drm_format) { + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_BGRX4444: + vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_BGRA4444: + vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4; + break; + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_BGRX5551: + vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGRA5551: + vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5; + break; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + vs_format->color = VSDC_COLOR_FORMAT_R5G6B5; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRX8888: + vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8; + break; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_BGRA1010102: + vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10; + break; + default: + DRM_WARN("Unexpected drm format!\n"); + } + + switch (drm_format) { + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBA1010102: + vs_format->swizzle = VSDC_SWIZZLE_RGBA; + break; + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ABGR2101010: + vs_format->swizzle = VSDC_SWIZZLE_ABGR; + break; + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRA1010102: + vs_format->swizzle = VSDC_SWIZZLE_BGRA; + break; + default: + /* N/A for YUV formats */ + vs_format->swizzle = VSDC_SWIZZLE_ARGB; + } + + /* N/A for non-YUV formats */ + vs_format->uv_swizzle = false; +} diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h new file mode 100644 index 00000000000000..3595267c89b53a --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_PLANE_H_ +#define _VS_PLANE_H_ + +#include + +#include +#include + +#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15)) +#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15)) + +struct vs_dc; + +enum vs_color_format { + VSDC_COLOR_FORMAT_X4R4G4B4, + VSDC_COLOR_FORMAT_A4R4G4B4, + VSDC_COLOR_FORMAT_X1R5G5B5, + VSDC_COLOR_FORMAT_A1R5G5B5, + VSDC_COLOR_FORMAT_R5G6B5, + VSDC_COLOR_FORMAT_X8R8G8B8, + VSDC_COLOR_FORMAT_A8R8G8B8, + VSDC_COLOR_FORMAT_YUY2, + VSDC_COLOR_FORMAT_UYVY, + VSDC_COLOR_FORMAT_INDEX8, + VSDC_COLOR_FORMAT_MONOCHROME, + VSDC_COLOR_FORMAT_YV12 = 0xf, + VSDC_COLOR_FORMAT_A8, + VSDC_COLOR_FORMAT_NV12, + VSDC_COLOR_FORMAT_NV16, + VSDC_COLOR_FORMAT_RG16, + VSDC_COLOR_FORMAT_R8, + VSDC_COLOR_FORMAT_NV12_10BIT, + VSDC_COLOR_FORMAT_A2R10G10B10, + VSDC_COLOR_FORMAT_NV16_10BIT, + VSDC_COLOR_FORMAT_INDEX1, + VSDC_COLOR_FORMAT_INDEX2, + VSDC_COLOR_FORMAT_INDEX4, + VSDC_COLOR_FORMAT_P010, + VSDC_COLOR_FORMAT_YUV444, + VSDC_COLOR_FORMAT_YUV444_10BIT +}; + +enum vs_swizzle { + VSDC_SWIZZLE_ARGB, + VSDC_SWIZZLE_RGBA, + VSDC_SWIZZLE_ABGR, + VSDC_SWIZZLE_BGRA, +}; + +struct vs_format { + enum vs_color_format color; + enum vs_swizzle swizzle; + bool uv_swizzle; +}; + +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format); + +struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc); + +#endif /* _VS_PLANE_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c new file mode 100644 index 00000000000000..5b03783d4f284c --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vs_crtc.h" +#include "vs_plane.h" +#include "vs_dc.h" +#include "vs_primary_plane_regs.h" + +static int vs_primary_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, true); +} + + +static void vs_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct drm_gem_dma_object *gem; + struct vs_dc *dc; + struct vs_crtc *vcrtc; + struct vs_format fmt; + unsigned int output, bpp; + dma_addr_t dma_addr; + + if (!crtc) + return; + + vcrtc = drm_crtc_to_vs_crtc(crtc); + output = vcrtc->id; + dc = vcrtc->dc; + + DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output); + + regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK, + VSDC_FB_CONFIG_EX_DISPLAY_ID(output)); + + if (!state->visible || !fb) { + regmap_write(dc->regs, VSDC_FB_CONFIG(output), 0); + regmap_write(dc->regs, VSDC_FB_CONFIG_EX(output), 0); + goto commit; + } else { + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_FB_EN); + } + + drm_format_to_vs_format(state->fb->format->format, &fmt); + + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_FMT_MASK, + VSDC_FB_CONFIG_FMT(fmt.color)); + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_SWIZZLE_MASK, + VSDC_FB_CONFIG_SWIZZLE(fmt.swizzle)); + regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_UV_SWIZZLE_EN, fmt.uv_swizzle); + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_dma_get_gem_obj(fb, 0); + + /* Compute the start of the displayed memory */ + bpp = fb->format->cpp[0]; + dma_addr = gem->dma_addr + fb->offsets[0]; + + /* Fixup framebuffer address for src coordinates */ + dma_addr += (state->src.x1 >> 16) * bpp; + dma_addr += (state->src.y1 >> 16) * fb->pitches[0]; + + regmap_write(dc->regs, VSDC_FB_ADDRESS(output), + lower_32_bits(dma_addr)); + regmap_write(dc->regs, VSDC_FB_STRIDE(output), + fb->pitches[0]); + + regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output), + VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y)); + regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output), + VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w, + state->crtc_y + state->crtc_h)); + regmap_write(dc->regs, VSDC_FB_SIZE(output), + VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h)); + + regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output), + VSDC_FB_BLEND_CONFIG_BLEND_DISABLE); +commit: + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_COMMIT); +} + +static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = { + .atomic_check = vs_primary_plane_atomic_check, + .atomic_update = vs_primary_plane_atomic_update, +}; + +static const struct drm_plane_funcs vs_primary_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc) +{ + struct drm_plane *plane; + + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, + &vs_primary_plane_funcs, + dc->identity.formats->array, + dc->identity.formats->num, + NULL, + DRM_PLANE_TYPE_PRIMARY, + NULL); + + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs); + + return plane; +} diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h new file mode 100644 index 00000000000000..cbb125c46b3903 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_PRIMARY_PLANE_REGS_H_ +#define _VS_PRIMARY_PLANE_REGS_H_ + +#include + +#define VSDC_FB_ADDRESS(n) (0x1400 + 0x4 * (n)) + +#define VSDC_FB_STRIDE(n) (0x1408 + 0x4 * (n)) + +#define VSDC_FB_CONFIG(n) (0x1518 + 0x4 * (n)) +#define VSDC_FB_CONFIG_CLEAR_EN BIT(8) +#define VSDC_FB_CONFIG_ROT_MASK GENMASK(13, 11) +#define VSDC_FB_CONFIG_ROT(v) ((v) << 11) +#define VSDC_FB_CONFIG_YUV_SPACE_MASK GENMASK(16, 14) +#define VSDC_FB_CONFIG_YUV_SPACE(v) ((v) << 14) +#define VSDC_FB_CONFIG_TILE_MODE_MASK GENMASK(21, 17) +#define VSDC_FB_CONFIG_TILE_MODE(v) ((v) << 14) +#define VSDC_FB_CONFIG_SCALE_EN BIT(22) +#define VSDC_FB_CONFIG_SWIZZLE_MASK GENMASK(24, 23) +#define VSDC_FB_CONFIG_SWIZZLE(v) ((v) << 23) +#define VSDC_FB_CONFIG_UV_SWIZZLE_EN BIT(25) +#define VSDC_FB_CONFIG_FMT_MASK GENMASK(31, 26) +#define VSDC_FB_CONFIG_FMT(v) ((v) << 26) + +#define VSDC_FB_SIZE(n) (0x1810 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */ + +#define VSDC_FB_CONFIG_EX(n) (0x1CC0 + 0x4 * (n)) +#define VSDC_FB_CONFIG_EX_COMMIT BIT(12) +#define VSDC_FB_CONFIG_EX_FB_EN BIT(13) +#define VSDC_FB_CONFIG_EX_ZPOS_MASK GENMASK(18, 16) +#define VSDC_FB_CONFIG_EX_ZPOS(v) ((v) << 16) +#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK GENMASK(19, 19) +#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v) ((v) << 19) + +#define VSDC_FB_TOP_LEFT(n) (0x24D8 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */ + +#define VSDC_FB_BOTTOM_RIGHT(n) (0x24E0 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */ + +#define VSDC_FB_BLEND_CONFIG(n) (0x2510 + 0x4 * (n)) +#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE BIT(1) + +#endif /* _VS_PRIMARY_PLANE_REGS_H_ */ From b163211fe4e810b84ed961669582d951e00e1790 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:21 +0800 Subject: [PATCH 14/33] FROMLIST: dt-bindings: display/bridge: add binding for TH1520 HDMI controller T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller paired with DesignWare HDMI PHY, with an extra clock gate for HDMI pixel clock and two reset controls. Add a device tree binding to it. Signed-off-by: Icenowy Zheng Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251124105226.2860845-5-uwu@icenowy.me Signed-off-by: Han Gao --- .../display/bridge/thead,th1520-dw-hdmi.yaml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml new file mode 100644 index 00000000000000..68fff885ce15b4 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/thead,th1520-dw-hdmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: T-Head TH1520 DesignWare HDMI TX Encoder + +maintainers: + - Icenowy Zheng + +description: + The HDMI transmitter is a Synopsys DesignWare HDMI TX controller + paired with a DesignWare HDMI Gen2 TX PHY. + +allOf: + - $ref: /schemas/display/bridge/synopsys,dw-hdmi.yaml# + +properties: + compatible: + enum: + - thead,th1520-dw-hdmi + + reg-io-width: + const: 4 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: iahb + - const: isfr + - const: cec + - const: pix + + resets: + items: + - description: Main reset + - description: Configuration APB reset + + reset-names: + items: + - const: main + - const: apb + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Input port connected to DC8200 DPU "DP" output + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: HDMI output port + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reg-io-width + - clocks + - clock-names + - resets + - reset-names + - interrupts + - ports + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + hdmi@ffef540000 { + compatible = "thead,th1520-dw-hdmi"; + reg = <0xff 0xef540000 0x0 0x40000>; + reg-io-width = <4>; + interrupts = <111 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vo CLK_HDMI_PCLK>, + <&clk_vo CLK_HDMI_SFR>, + <&clk_vo CLK_HDMI_CEC>, + <&clk_vo CLK_HDMI_PIXCLK>; + clock-names = "iahb", "isfr", "cec", "pix"; + resets = <&rst_vo TH1520_RESET_ID_HDMI>, + <&rst_vo TH1520_RESET_ID_HDMI_APB>; + reset-names = "main", "apb"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + + hdmi_in: endpoint { + remote-endpoint = <&dpu_out_dp1>; + }; + }; + + port@1 { + reg = <1>; + + hdmi_out_conn: endpoint { + remote-endpoint = <&hdmi_conn_in>; + }; + }; + }; + }; + }; From b7f537a80bcdbad4a38627469263e42bca617842 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:22 +0800 Subject: [PATCH 15/33] FROMLIST: drm/bridge: add a driver for T-Head TH1520 HDMI controller T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller (paired with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from the display controller. Add a driver for this controller utilizing the common DesignWare HDMI code in the kernel. Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-6-uwu@icenowy.me Signed-off-by: Han Gao --- MAINTAINERS | 1 + drivers/gpu/drm/bridge/Kconfig | 10 ++ drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173 ++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c diff --git a/MAINTAINERS b/MAINTAINERS index 6b5c07de386600..a97bd602442965 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22233,6 +22233,7 @@ F: Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml F: arch/riscv/boot/dts/thead/ F: drivers/clk/thead/clk-th1520-ap.c F: drivers/firmware/thead,th1520-aon.c +F: drivers/gpu/drm/bridge/th1520-dw-hdmi.c F: drivers/mailbox/mailbox-th1520.c F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c F: drivers/pinctrl/pinctrl-th1520.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a250afd8d66220..8e19f5fb9ad7c3 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -335,6 +335,16 @@ config DRM_THINE_THC63LVD1024 help Thine THC63LVD1024 LVDS/parallel converter driver. +config DRM_THEAD_TH1520_DW_HDMI + tristate "T-Head TH1520 DesignWare HDMI bridge" + depends on OF + depends on COMMON_CLK + depends on ARCH_THEAD || COMPILE_TEST + select DRM_DW_HDMI + help + Choose this to enable support for the internal HDMI bridge found + on the T-Head TH1520 SoC. + config DRM_TOSHIBA_TC358762 tristate "TC358762 DSI/DPI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c7dc03182e5927..085b5db45d6fd8 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o +obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c new file mode 100644 index 00000000000000..389eead5f1c45f --- /dev/null +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Icenowy Zheng + * + * Based on rcar_dw_hdmi.c, which is: + * Copyright (C) 2016 Renesas Electronics Corporation + * Based on imx8mp-hdmi-tx.c, which is: + * Copyright (C) 2022 Pengutronix, Lucas Stach + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define TH1520_HDMI_PHY_OPMODE_PLLCFG 0x06 /* Mode of operation and PLL dividers */ +#define TH1520_HDMI_PHY_CKSYMTXCTRL 0x09 /* Clock Symbol and Transmitter Control Register */ +#define TH1520_HDMI_PHY_VLEVCTRL 0x0e /* Voltage Level Control Register */ +#define TH1520_HDMI_PHY_PLLCURRGMPCTRL 0x10 /* PLL current and Gmp (conductance) */ +#define TH1520_HDMI_PHY_PLLDIVCTRL 0x11 /* PLL dividers */ +#define TH1520_HDMI_PHY_TXTERM 0x19 /* Transmission Termination Register */ + +struct th1520_hdmi_phy_params { + unsigned long mpixelclock; + u16 opmode_pllcfg; + u16 pllcurrgmpctrl; + u16 plldivctrl; + u16 cksymtxctrl; + u16 vlevctrl; + u16 txterm; +}; + +static const struct th1520_hdmi_phy_params th1520_hdmi_phy_params[] = { + { 35500000, 0x0003, 0x0283, 0x0628, 0x8088, 0x01a0, 0x0007 }, + { 44900000, 0x0003, 0x0285, 0x0228, 0x8088, 0x01a0, 0x0007 }, + { 71000000, 0x0002, 0x1183, 0x0614, 0x8088, 0x01a0, 0x0007 }, + { 90000000, 0x0002, 0x1142, 0x0214, 0x8088, 0x01a0, 0x0007 }, + { 121750000, 0x0001, 0x20c0, 0x060a, 0x8088, 0x01a0, 0x0007 }, + { 165000000, 0x0001, 0x2080, 0x020a, 0x8088, 0x01a0, 0x0007 }, + { 198000000, 0x0000, 0x3040, 0x0605, 0x83c8, 0x0120, 0x0004 }, + { 297000000, 0x0000, 0x3041, 0x0205, 0x81dc, 0x0200, 0x0005 }, + { 371250000, 0x0640, 0x3041, 0x0205, 0x80f6, 0x0140, 0x0000 }, + { 495000000, 0x0640, 0x3080, 0x0005, 0x80f6, 0x0140, 0x0000 }, + { 594000000, 0x0640, 0x3080, 0x0005, 0x80fa, 0x01e0, 0x0004 }, +}; + +struct th1520_hdmi { + struct dw_hdmi_plat_data plat_data; + struct dw_hdmi *dw_hdmi; + struct clk *pixclk; + struct reset_control *mainrst, *prst; +}; + +static enum drm_mode_status +th1520_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + /* + * The maximum supported clock frequency is 594 MHz, as shown in the PHY + * parameters table. + */ + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void th1520_hdmi_phy_set_params(struct dw_hdmi *hdmi, + const struct th1520_hdmi_phy_params *params) +{ + dw_hdmi_phy_i2c_write(hdmi, params->opmode_pllcfg, + TH1520_HDMI_PHY_OPMODE_PLLCFG); + dw_hdmi_phy_i2c_write(hdmi, params->pllcurrgmpctrl, + TH1520_HDMI_PHY_PLLCURRGMPCTRL); + dw_hdmi_phy_i2c_write(hdmi, params->plldivctrl, + TH1520_HDMI_PHY_PLLDIVCTRL); + dw_hdmi_phy_i2c_write(hdmi, params->vlevctrl, + TH1520_HDMI_PHY_VLEVCTRL); + dw_hdmi_phy_i2c_write(hdmi, params->cksymtxctrl, + TH1520_HDMI_PHY_CKSYMTXCTRL); + dw_hdmi_phy_i2c_write(hdmi, params->txterm, + TH1520_HDMI_PHY_TXTERM); +} + +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data, + unsigned long mpixelclock) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(th1520_hdmi_phy_params); i++) { + if (mpixelclock <= th1520_hdmi_phy_params[i].mpixelclock) { + th1520_hdmi_phy_set_params(hdmi, + &th1520_hdmi_phy_params[i]); + return 0; + } + } + + return -EINVAL; +} + +static int th1520_dw_hdmi_probe(struct platform_device *pdev) +{ + struct th1520_hdmi *hdmi; + struct dw_hdmi_plat_data *plat_data; + struct device *dev = &pdev->dev; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + plat_data = &hdmi->plat_data; + + hdmi->pixclk = devm_clk_get_enabled(dev, "pix"); + if (IS_ERR(hdmi->pixclk)) + return dev_err_probe(dev, PTR_ERR(hdmi->pixclk), + "Unable to get pixel clock\n"); + + hdmi->mainrst = devm_reset_control_get_exclusive_deasserted(dev, "main"); + if (IS_ERR(hdmi->mainrst)) + return dev_err_probe(dev, PTR_ERR(hdmi->mainrst), + "Unable to get main reset\n"); + + hdmi->prst = devm_reset_control_get_exclusive_deasserted(dev, "apb"); + if (IS_ERR(hdmi->prst)) + return dev_err_probe(dev, PTR_ERR(hdmi->prst), + "Unable to get apb reset\n"); + + plat_data->output_port = 1; + plat_data->mode_valid = th1520_hdmi_mode_valid; + plat_data->configure_phy = th1520_hdmi_phy_configure; + plat_data->priv_data = hdmi; + + hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return PTR_ERR(hdmi); + + platform_set_drvdata(pdev, hdmi); + + return 0; +} + +static void th1520_dw_hdmi_remove(struct platform_device *pdev) +{ + struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + + dw_hdmi_remove(hdmi); +} + +static const struct of_device_id th1520_dw_hdmi_of_table[] = { + { .compatible = "thead,th1520-dw-hdmi" }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, th1520_dw_hdmi_of_table); + +static struct platform_driver th1520_dw_hdmi_platform_driver = { + .probe = th1520_dw_hdmi_probe, + .remove = th1520_dw_hdmi_remove, + .driver = { + .name = "th1520-dw-hdmi", + .of_match_table = th1520_dw_hdmi_of_table, + }, +}; + +module_platform_driver(th1520_dw_hdmi_platform_driver); + +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_DESCRIPTION("T-Head TH1520 HDMI Encoder Driver"); +MODULE_LICENSE("GPL"); From 92d8c27a07d727c7397f4fb45cdc6d115ce45185 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:23 +0800 Subject: [PATCH 16/33] FROMLIST: riscv: dts: thead: add DPU and HDMI device tree nodes T-Head TH1520 SoC contains a Verisilicon DC8200 display controller (called DPU in manual) and a Synopsys DesignWare HDMI TX controller. Add device tree nodes to them. Signed-off-by: Icenowy Zheng Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-7-uwu@icenowy.me Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 3b922e1df6ea55..8dbc52b4dec576 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -583,6 +583,76 @@ #clock-cells = <1>; }; + hdmi: hdmi@ffef540000 { + compatible = "thead,th1520-dw-hdmi"; + reg = <0xff 0xef540000 0x0 0x40000>; + reg-io-width = <4>; + interrupts = <111 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vo CLK_HDMI_PCLK>, + <&clk_vo CLK_HDMI_SFR>, + <&clk_vo CLK_HDMI_CEC>, + <&clk_vo CLK_HDMI_PIXCLK>; + clock-names = "iahb", "isfr", "cec", "pix"; + resets = <&rst TH1520_RESET_ID_HDMI>, + <&rst TH1520_RESET_ID_HDMI_APB>; + reset-names = "main", "apb"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi_in: endpoint { + remote-endpoint = <&dpu_out_dp1>; + }; + }; + + hdmi_out_port: port@1 { + reg = <1>; + }; + }; + }; + + dpu: display@ffef600000 { + compatible = "thead,th1520-dc8200", "verisilicon,dc"; + reg = <0xff 0xef600000 0x0 0x100000>; + interrupts = <93 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vo CLK_DPU_CCLK>, + <&clk_vo CLK_DPU_ACLK>, + <&clk_vo CLK_DPU_HCLK>, + <&clk_vo CLK_DPU_PIXELCLK0>, + <&clk_vo CLK_DPU_PIXELCLK1>; + clock-names = "core", "axi", "ahb", "pix0", "pix1"; + resets = <&rst TH1520_RESET_ID_DPU_CORE>, + <&rst TH1520_RESET_ID_DPU_AXI>, + <&rst TH1520_RESET_ID_DPU_AHB>; + reset-names = "core", "axi", "ahb"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dpu_port0: port@0 { + reg = <0>; + }; + + dpu_port1: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + dpu_out_dp1: endpoint@1 { + reg = <1>; + remote-endpoint = <&hdmi_in>; + }; + }; + }; + }; + dmac0: dma-controller@ffefc00000 { compatible = "snps,axi-dma-1.01a"; reg = <0xff 0xefc00000 0x0 0x1000>; From 57951ce447b50121d2d1f312161015c273034085 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:24 +0800 Subject: [PATCH 17/33] FROMLIST: riscv: dts: thead: lichee-pi-4a: enable HDMI Lichee Pi 4A board features a HDMI Type-A connector connected to the HDMI TX controller of TH1520 SoC. Add a device tree node describing the connector, connect it to the HDMI controller, and enable everything on this display pipeline. Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-8-uwu@icenowy.me Signed-off-by: Han Gao --- .../boot/dts/thead/th1520-lichee-pi-4a.dts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index 4020c727f09e8e..3e99f905dc316e 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -28,6 +28,17 @@ chosen { stdout-path = "serial0:115200n8"; }; + + hdmi-connector { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; }; &padctrl0_apsys { @@ -54,6 +65,20 @@ }; }; +&dpu { + status = "okay"; +}; + +&hdmi { + status = "okay"; +}; + +&hdmi_out_port { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; From 1018474eb45739a165590bf1022ab3690b2e7595 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:25 +0800 Subject: [PATCH 18/33] FROMLIST: MAINTAINERS: assign myself as maintainer for verisilicon DC driver As I am the author of this rewritten driver, it makes sense for me to be the maintainer. Confirm this in MAINTAINERS file. Signed-off-by: Icenowy Zheng Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-9-uwu@icenowy.me Signed-off-by: Han Gao --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a97bd602442965..6015d03d8a0e58 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8557,6 +8557,13 @@ F: Documentation/devicetree/bindings/display/brcm,bcm2835-*.yaml F: drivers/gpu/drm/vc4/ F: include/uapi/drm/vc4_drm.h +DRM DRIVERS FOR VERISILICON DISPLAY CONTROLLER IP +M: Icenowy Zheng +L: dri-devel@lists.freedesktop.org +S: Maintained +F: Documentation/devicetree/bindings/display/verisilicon,dc.yaml +F: drivers/gpu/drm/verisilicon/ + DRM DRIVERS FOR VIVANTE GPU IP M: Lucas Stach R: Russell King From 4e92fc477fcd8e05b0b8cbf3aac68574f5e7add1 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Mon, 24 Nov 2025 18:52:26 +0800 Subject: [PATCH 19/33] FROMLIST: mailmap: map all Icenowy Zheng's mail addresses Map all mail addresses Icenowy Zheng had used to the personal mailbox prefixed "uwu". All these mailboxes, except the one of Sipeed (which was only used during a summer vacation internship), can accept mails now. Signed-off-by: Icenowy Zheng Signed-off-by: Icenowy Zheng Link: https://lore.kernel.org/r/20251124105226.2860845-10-uwu@icenowy.me Signed-off-by: Han Gao --- .mailmap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.mailmap b/.mailmap index 369cfe467932d7..faae878dff27bf 100644 --- a/.mailmap +++ b/.mailmap @@ -310,6 +310,10 @@ Henrik Rydberg Herbert Xu Huacai Chen Huacai Chen +Icenowy Zheng +Icenowy Zheng +Icenowy Zheng +Icenowy Zheng Ike Panhc J. Bruce Fields J. Bruce Fields From 802b63823457988371d7d432916ab26e7c347592 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 28 Sep 2023 00:42:21 +0800 Subject: [PATCH 20/33] FROMLIST: dt-bindings: usb: Add T-HEAD TH1520 USB controller T-HEAD TH1520 platform's USB has a wrapper module around the DesignWare USB3 DRD controller. Add binding information doc for it. Signed-off-by: Jisheng Zhang Link: https://lore.kernel.org/r/20230927164222.3505-2-jszhang@kernel.org Signed-off-by: Han Gao --- .../bindings/usb/thead,th1520-usb.yaml | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/thead,th1520-usb.yaml diff --git a/Documentation/devicetree/bindings/usb/thead,th1520-usb.yaml b/Documentation/devicetree/bindings/usb/thead,th1520-usb.yaml new file mode 100644 index 00000000000000..afb618eb501390 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/thead,th1520-usb.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/thead,th1520-usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: T-HEAD TH1520 DWC3 USB Controller Glue + +maintainers: + - Jisheng Zhang + +properties: + compatible: + const: thead,th1520-usb + + reg: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: ref + - const: bus_early + - const: phy + - const: suspend + + ranges: true + + '#address-cells': + enum: [ 1, 2 ] + + '#size-cells': + enum: [ 1, 2 ] + +# Required child node: + +patternProperties: + "^usb@[0-9a-f]+$": + $ref: snps,dwc3.yaml# + +required: + - compatible + - reg + - clocks + - clock-names + - ranges + +additionalProperties: false + +examples: + - | + + usb { + compatible = "thead,th1520-usb"; + reg = <0xec03f000 0x1000>; + clocks = <&clk 1>, + <&clk 2>, + <&clk 3>, + <&clk 4>; + clock-names = "ref", "bus_early", "phy", "suspend"; + ranges; + #address-cells = <1>; + #size-cells = <1>; + + usb@e7040000 { + compatible = "snps,dwc3"; + reg = <0xe7040000 0x10000>; + interrupts = <68>; + dr_mode = "host"; + }; + }; From 4f67dab9fa109714b84de8b9b1da347611a0227d Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 28 Sep 2023 00:42:22 +0800 Subject: [PATCH 21/33] FROMLIST: usb: dwc3: add T-HEAD TH1520 usb driver Adds TH1520 Glue layer to support USB controller on T-HEAD TH1520 SoC. There is a DesignWare USB3 DRD core in TH1520 SoCs, the dwc3 core is the child of this USB wrapper module device. Signed-off-by: Jisheng Zhang Link: https://lore.kernel.org/r/20230927164222.3505-3-jszhang@kernel.org [ Han Gao: fix build in 6.18 ] Signed-off-by: Han Gao --- MAINTAINERS | 1 + drivers/usb/dwc3/Kconfig | 9 +++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-thead.c | 119 ++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-thead.c diff --git a/MAINTAINERS b/MAINTAINERS index 6015d03d8a0e58..3c188074afb982 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22248,6 +22248,7 @@ F: drivers/pmdomain/thead/ F: drivers/power/reset/th1520-aon-reboot.c F: drivers/power/sequencing/pwrseq-thead-gpu.c F: drivers/reset/reset-th1520.c +F: drivers/usb/dwc3/dwc3-thead.c F: include/dt-bindings/clock/thead,th1520-clk-ap.h F: include/dt-bindings/power/thead,th1520-power.h F: include/dt-bindings/reset/thead,th1520-reset.h diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 4925d15084f816..3a6679a5130772 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -200,4 +200,13 @@ config USB_DWC3_GENERIC_PLAT the dwc3 child node in the device tree. Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. +config USB_DWC3_THEAD + tristate "T-HEAD Platform" + depends on ARCH_THEAD || COMPILE_TEST + default USB_DWC3 + help + Support T-HEAD platform with DesignWare Core USB3 IP. + Only the host mode is currently supported. + Say 'Y' or 'M' here if you have one such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 96469e48ff9d18..bef4aa926053b1 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o +obj-$(CONFIG_USB_DWC3_THEAD) += dwc3-thead.o diff --git a/drivers/usb/dwc3/dwc3-thead.c b/drivers/usb/dwc3/dwc3-thead.c new file mode 100644 index 00000000000000..9a4cc0b5a64615 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-thead.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-thead.c - T-HEAD platform specific glue layer + * + * Inspired by dwc3-of-simple.c + * + * Copyright (C) 2021 Alibaba Group Holding Limited. + * Copyright (C) 2023 Jisheng Zhang + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" + +#define USB_SSP_EN 0x34 +#define REF_SSP_EN BIT(0) +#define USB_SYS 0x3c +#define COMMONONN BIT(0) + +#define USB3_DRD_SWRST 0x14 +#define USB3_DRD_PRST BIT(0) +#define USB3_DRD_PHYRST BIT(1) +#define USB3_DRD_VCCRST BIT(2) +#define USB3_DRD_RSTMASK (USB3_DRD_PRST | USB3_DRD_PHYRST | USB3_DRD_VCCRST) + +struct dwc3_thead { + void __iomem *base; + struct regmap *misc_sysreg; + struct regulator *vbus; +}; + +static void dwc3_thead_optimize_power(struct dwc3_thead *thead) +{ + u32 val; + + /* config usb top within USB ctrl & PHY reset */ + regmap_update_bits(thead->misc_sysreg, USB3_DRD_SWRST, + USB3_DRD_RSTMASK, USB3_DRD_PRST); + + /* + * dwc reg also need to be configed to save power + * 1. set USB_SYS[COMMONONN] + * 2. set DWC3_GCTL[SOFITPSYNC](done by core.c) + * 3. set GUSB3PIPECTL[SUSPENDEN] (done by core.c) + */ + val = readl(thead->base + USB_SYS); + val |= COMMONONN; + writel(val, thead->base + USB_SYS); + val = readl(thead->base + USB_SSP_EN); + val |= REF_SSP_EN; + writel(val, thead->base + USB_SSP_EN); + + regmap_update_bits(thead->misc_sysreg, USB3_DRD_SWRST, + USB3_DRD_RSTMASK, USB3_DRD_RSTMASK); +} + +static int dwc3_thead_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct dwc3_thead *thead; + int ret; + + thead = devm_kzalloc(&pdev->dev, sizeof(*thead), GFP_KERNEL); + if (!thead) + return -ENOMEM; + + platform_set_drvdata(pdev, thead); + + ret = devm_regulator_get_enable_optional(dev, "vbus"); + if (ret < 0 && ret != -ENODEV) + return ret; + + thead->misc_sysreg = syscon_regmap_lookup_by_phandle(np, "thead,misc-sysreg"); + if (IS_ERR(thead->misc_sysreg)) + return PTR_ERR(thead->misc_sysreg); + + thead->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(thead->base)) + return PTR_ERR(thead->base); + + dwc3_thead_optimize_power(thead); + + return of_platform_populate(np, NULL, NULL, dev); +} + +static void dwc3_thead_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); +} + +static const struct of_device_id dwc3_thead_of_match[] = { + { .compatible = "thead,th1520-usb" }, + { }, +}; +MODULE_DEVICE_TABLE(of, dwc3_thead_of_match); + +static struct platform_driver dwc3_thead_driver = { + .probe = dwc3_thead_probe, + .remove = dwc3_thead_remove, + .driver = { + .name = "dwc3-thead", + .of_match_table = dwc3_thead_of_match, + }, +}; +module_platform_driver(dwc3_thead_driver); + +MODULE_ALIAS("platform:dwc3-thead"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare DWC3 T-HEAD Glue Driver"); +MODULE_AUTHOR("Jisheng Zhang "); From 246830e3e0115a288cdfc92898c6d4e615144ffc Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 5 Oct 2023 21:05:18 +0800 Subject: [PATCH 22/33] FROMLIST: dt-bindings: pwm: Add T-HEAD PWM controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T-HEAD SoCs such as the TH1520 contain a PWM controller used to control the LCD backlight, fan and so on. Signed-off-by: Jisheng Zhang Reviewed-by: Rob Herring Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20231005130519.3864-2-jszhang@kernel.org Signed-off-by: Han Gao --- .../bindings/pwm/thead,th1520-pwm.yaml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml new file mode 100644 index 00000000000000..e75d8e9f24c50b --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/thead,th1520-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: T-HEAD TH1520 PWM + +maintainers: + - Jisheng Zhang + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + enum: + - thead,th1520-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + + pwm@ec01c000 { + compatible = "thead,th1520-pwm"; + reg = <0xec01c000 0x1000>; + clocks = <&clk 1>; + #pwm-cells = <3>; + }; From 49846ab9dbc5d8c0b061455686b6bd3c456a3c57 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 5 Oct 2023 21:05:19 +0800 Subject: [PATCH 23/33] BACKPORT: FROMLIST: pwm: add T-HEAD PWM driver T-HEAD SoCs such as the TH1520 contain a PWM controller used to control the LCD backlight, fan and so on. Add driver for it. Signed-off-by: Jisheng Zhang Tested-by: Thomas Bonnefille Link: https://lore.kernel.org/r/20231005130519.3864-3-jszhang@kernel.org [ Han Gao: fix build in 6.18 ] Signed-off-by: Han Gao --- drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-thead.c | 271 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 drivers/pwm/pwm-thead.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c2fd3f4b62d9ea..fac181117e4c56 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -748,6 +748,17 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. +config PWM_THEAD + tristate "T-HEAD PWM support" + depends on ARCH_THEAD || COMPILE_TEST + depends on HAS_IOMEM + help + Generic PWM framework driver for the PWFM controller found on THEAD + SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-thead. + config PWM_TIECAP tristate "ECAP PWM support" depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index dfa8b4966ee19a..c840f8bc909d6c 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o +obj-$(CONFIG_PWM_THEAD) += pwm-thead.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o obj-$(CONFIG_PWM_TWL) += pwm-twl.o diff --git a/drivers/pwm/pwm-thead.c b/drivers/pwm/pwm-thead.c new file mode 100644 index 00000000000000..31590f91aad7c5 --- /dev/null +++ b/drivers/pwm/pwm-thead.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * T-HEAD PWM driver + * + * Copyright (C) 2021 Alibaba Group Holding Limited. + * Copyright (C) 2023 Jisheng Zhang + * + * Limitations: + * - The THEAD_PWM_CTRL_START bit is only effective when 0 -> 1, which is used + * to start the channel, 1 -> 0 doesn't change anything. so 0 % duty cycle + * is used to "disable" the channel. + * - The THEAD_PWM_CTRL_START bit is automatically cleared once PWM channel is + * started. + * - The THEAD_PWM_CFG_UPDATE atomically updates and only updates period and duty. + * - To update period and duty, THEAD_PWM_CFG_UPDATE needs to go through 0 -> 1 + * step, I.E if THEAD_PWM_CFG_UPDATE is already 1, it's necessary to clear it + * to 0 beforehand. + * - Polarity can only be changed if never started before. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THEAD_PWM_MAX_NUM 6 +#define THEAD_PWM_MAX_PERIOD GENMASK(31, 0) +#define THEAD_PWM_MAX_DUTY GENMASK(31, 0) + +#define THEAD_PWM_CHN_BASE(n) ((n) * 0x20) +#define THEAD_PWM_CTRL(n) (THEAD_PWM_CHN_BASE(n) + 0x00) +#define THEAD_PWM_CTRL_START BIT(0) +#define THEAD_PWM_CTRL_SOFT_RST BIT(1) +#define THEAD_PWM_CTRL_CFG_UPDATE BIT(2) +#define THEAD_PWM_CTRL_INTEN BIT(3) +#define THEAD_PWM_CTRL_MODE GENMASK(5, 4) +#define THEAD_PWM_CTRL_MODE_CONTINUOUS FIELD_PREP(THEAD_PWM_CTRL_MODE, 2) +#define THEAD_PWM_CTRL_EVTRIG GENMASK(7, 6) +#define THEAD_PWM_CTRL_FPOUT BIT(8) +#define THEAD_PWM_CTRL_INFACTOUT BIT(9) +#define THEAD_PWM_RPT(n) (THEAD_PWM_CHN_BASE(n) + 0x04) +#define THEAD_PWM_PER(n) (THEAD_PWM_CHN_BASE(n) + 0x08) +#define THEAD_PWM_FP(n) (THEAD_PWM_CHN_BASE(n) + 0x0c) +#define THEAD_PWM_STATUS(n) (THEAD_PWM_CHN_BASE(n) + 0x10) +#define THEAD_PWM_STATUS_CYCLE GENMASK(7, 0) + +struct thead_pwm_chip { + void __iomem *mmio_base; + struct clk *clk; + u8 channel_ever_started; +}; + +static inline struct thead_pwm_chip *thead_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int thead_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct thead_pwm_chip *priv = thead_pwm_from_chip(chip); + u32 val = THEAD_PWM_CTRL_INFACTOUT | THEAD_PWM_CTRL_FPOUT | THEAD_PWM_CTRL_MODE_CONTINUOUS; + u64 period_cycle, duty_cycle, rate; + int ret; + + /* if ever started, can't change the polarity */ + if ((priv->channel_ever_started & (1 << pwm->hwpwm)) && + state->polarity != pwm->state.polarity) + return -EINVAL; + + if (!state->enabled) { + if (pwm->state.enabled) { + val = readl(priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + val &= ~THEAD_PWM_CTRL_CFG_UPDATE; + writel(val, priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + + writel(0, priv->mmio_base + THEAD_PWM_FP(pwm->hwpwm)); + + val |= THEAD_PWM_CTRL_CFG_UPDATE; + writel(val, priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + pm_runtime_put_sync(&chip->dev); + } + return 0; + } + + if (!pwm->state.enabled) { + ret = pm_runtime_resume_and_get(pwmchip_parent(chip)); + if (ret < 0) + return ret; + } + + if (state->polarity == PWM_POLARITY_INVERSED) + val &= ~THEAD_PWM_CTRL_FPOUT; + + writel(val, priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + + rate = clk_get_rate(priv->clk); + /* + * The following calculations might overflow if clk is bigger + * than 1 GHz. In practise it's 24MHz, so this limitation + * is only theoretic. + */ + if (rate > NSEC_PER_SEC) + return -EINVAL; + + period_cycle = mul_u64_u64_div_u64(rate, state->period, NSEC_PER_SEC); + if (period_cycle > THEAD_PWM_MAX_PERIOD) + period_cycle = THEAD_PWM_MAX_PERIOD; + /* + * With limitation above we have period_cycle <= THEAD_PWM_MAX_PERIOD, + * so this cannot overflow. + */ + writel(period_cycle, priv->mmio_base + THEAD_PWM_PER(pwm->hwpwm)); + + duty_cycle = mul_u64_u64_div_u64(rate, state->duty_cycle, NSEC_PER_SEC); + if (duty_cycle > THEAD_PWM_MAX_DUTY) + duty_cycle = THEAD_PWM_MAX_DUTY; + /* + * With limitation above we have duty_cycle <= THEAD_PWM_MAX_DUTY, + * so this cannot overflow. + */ + writel(duty_cycle, priv->mmio_base + THEAD_PWM_FP(pwm->hwpwm)); + + val |= THEAD_PWM_CTRL_CFG_UPDATE; + writel(val, priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + + if (!pwm->state.enabled) { + val |= THEAD_PWM_CTRL_START; + writel(val, priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + priv->channel_ever_started |= 1 << pwm->hwpwm; + } + + return 0; +} + +static int thead_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct thead_pwm_chip *priv = thead_pwm_from_chip(chip); + u64 rate = clk_get_rate(priv->clk); + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(pwmchip_parent(chip)); + if (ret < 0) + return ret; + + val = readl(priv->mmio_base + THEAD_PWM_CTRL(pwm->hwpwm)); + if (val & THEAD_PWM_CTRL_FPOUT) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + val = readl(priv->mmio_base + THEAD_PWM_PER(pwm->hwpwm)); + /* + * val 32 bits, multiply NSEC_PER_SEC, won't overflow. + */ + state->period = DIV64_U64_ROUND_UP((u64)val * NSEC_PER_SEC, rate); + + val = readl(priv->mmio_base + THEAD_PWM_FP(pwm->hwpwm)); + state->enabled = !!val; + /* + * val 32 bits, multiply NSEC_PER_SEC, won't overflow. + */ + state->duty_cycle = DIV64_U64_ROUND_UP((u64)val * NSEC_PER_SEC, rate); + + pm_runtime_put_sync(&chip->dev); + + return 0; +} + +static const struct pwm_ops thead_pwm_ops = { + .apply = thead_pwm_apply, + .get_state = thead_pwm_get_state, +}; + +static int __maybe_unused thead_pwm_runtime_suspend(struct device *dev) +{ + struct thead_pwm_chip *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int __maybe_unused thead_pwm_runtime_resume(struct device *dev) +{ + struct thead_pwm_chip *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + dev_err(dev, "failed to enable pwm clock(%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int thead_pwm_probe(struct platform_device *pdev) +{ + struct thead_pwm_chip *priv; + struct pwm_chip *chip; + int ret, i; + u32 val; + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + + chip = devm_pwmchip_alloc(&pdev->dev, THEAD_PWM_MAX_NUM, sizeof(*priv)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + priv = thead_pwm_from_chip(chip); + + platform_set_drvdata(pdev, priv); + + priv->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->mmio_base)) + return PTR_ERR(priv->mmio_base); + + priv->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + chip->ops = &thead_pwm_ops; + + /* check whether PWM is ever started or not */ + for (i = 0; i < chip->npwm; i++) { + val = readl(priv->mmio_base + THEAD_PWM_FP(i)); + if (val) + priv->channel_ever_started |= 1 << i; + } + + ret = devm_pwmchip_add(&pdev->dev, chip); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id thead_pwm_dt_ids[] = { + {.compatible = "thead,th1520-pwm",}, + {/* sentinel */} +}; +MODULE_DEVICE_TABLE(of, thead_pwm_dt_ids); + +static const struct dev_pm_ops thead_pwm_pm_ops = { + SET_RUNTIME_PM_OPS(thead_pwm_runtime_suspend, thead_pwm_runtime_resume, NULL) +}; + +static struct platform_driver thead_pwm_driver = { + .driver = { + .name = "thead-pwm", + .of_match_table = thead_pwm_dt_ids, + .pm = &thead_pwm_pm_ops, + }, + .probe = thead_pwm_probe, +}; +module_platform_driver(thead_pwm_driver); + +MODULE_AUTHOR("Wei Liu "); +MODULE_AUTHOR("Jisheng Zhang "); +MODULE_DESCRIPTION("T-HEAD pwm driver"); +MODULE_LICENSE("GPL v2"); From 40072e6ec14489eba5fcd774cbe114236ba638a8 Mon Sep 17 00:00:00 2001 From: Xiangyi Zeng Date: Sun, 1 Sep 2024 01:49:37 +0800 Subject: [PATCH 24/33] XUANTIE: drivers: pwm: fix pwm enable status check error From: https://github.com/revyos/th1520-linux-kernel/commit/e40c8171f8c1116d31e3eb856e2c7334119eac3f Signed-off-by: Xiangyi Zeng Signed-off-by: Han Gao --- drivers/pwm/pwm-thead.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-thead.c b/drivers/pwm/pwm-thead.c index 31590f91aad7c5..01db22918e3954 100644 --- a/drivers/pwm/pwm-thead.c +++ b/drivers/pwm/pwm-thead.c @@ -51,6 +51,7 @@ #define THEAD_PWM_FP(n) (THEAD_PWM_CHN_BASE(n) + 0x0c) #define THEAD_PWM_STATUS(n) (THEAD_PWM_CHN_BASE(n) + 0x10) #define THEAD_PWM_STATUS_CYCLE GENMASK(7, 0) +#define THEAD_PWM_STATUS_BUSY BIT(8) struct thead_pwm_chip { void __iomem *mmio_base; @@ -165,8 +166,8 @@ static int thead_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, */ state->period = DIV64_U64_ROUND_UP((u64)val * NSEC_PER_SEC, rate); - val = readl(priv->mmio_base + THEAD_PWM_FP(pwm->hwpwm)); - state->enabled = !!val; + val = readl(priv->mmio_base + THEAD_PWM_STATUS(pwm->hwpwm)); + state->enabled = !!(val & THEAD_PWM_STATUS_BUSY); /* * val 32 bits, multiply NSEC_PER_SEC, won't overflow. */ From 0d4eef3796c254ca10d41b9945fa0cc981355c59 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Mon, 24 Nov 2025 20:38:44 +0800 Subject: [PATCH 25/33] XUANTIE: riscv: dts: th1520: add licheepi4a 16g support From: https://github.com/revyos/th1520-linux-kernel/commit/01a510898e41e704bee1fe58a2c0c0a29cb96548 Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/Makefile | 1 + .../boot/dts/thead/th1520-lichee-pi-4a-16g.dts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts diff --git a/arch/riscv/boot/dts/thead/Makefile b/arch/riscv/boot/dts/thead/Makefile index b55a17127c2bc0..281849e71ccb84 100644 --- a/arch/riscv/boot/dts/thead/Makefile +++ b/arch/riscv/boot/dts/thead/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 dtb-$(CONFIG_ARCH_THEAD) += th1520-lichee-pi-4a.dtb th1520-beaglev-ahead.dtb +dtb-$(CONFIG_ARCH_THEAD) += th1520-lichee-pi-4a-16g.dtb diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts new file mode 100644 index 00000000000000..a3a991baf716be --- /dev/null +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a-16g.dts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (C) 2023 Han Gao + */ + +/dts-v1/; + +#include "th1520-lichee-pi-4a.dts" + +/ { + model = "Sipeed Lichee Pi 4A 16G"; + compatible = "sipeed,lichee-pi-4a", "sipeed,lichee-module-4a", "thead,th1520"; + + memory@0 { + device_type = "memory"; + reg = <0x0 0x00000000 0x4 0x00000000>; + }; +}; From ae7853e96b85879c581af1217c83466e41b35b9a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 21 Sep 2023 14:00:40 +0800 Subject: [PATCH 26/33] XUANTIE: riscv: dts: thead: Add TH1520 PWM node From: https://github.com/revyos/th1520-linux-kernel/commit/882a91b410d99402c06f48c04e188e62841093b8 Signed-off-by: Jisheng Zhang Signed-off-by: Emil Renner Berthing [ Han Gao: use clk pwm ] Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 8dbc52b4dec576..ef0ef5e3e9b67b 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -653,6 +653,13 @@ }; }; + pwm: pwm@ffec01c000 { + compatible = "thead,th1520-pwm"; + reg = <0xff 0xec01c000 0x0 0x4000>; + clocks = <&clk CLK_PWM>; + #pwm-cells = <3>; + }; + dmac0: dma-controller@ffefc00000 { compatible = "snps,axi-dma-1.01a"; reg = <0xff 0xefc00000 0x0 0x1000>; From 20b5ae373a5f310ef430516ddb3c23927b8e7bc6 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 21 Sep 2023 16:08:30 +0800 Subject: [PATCH 27/33] XUANTIE: riscv: dts: thead: Enable Lichee Pi 4A PWM fan Signed-off-by: Jisheng Zhang [esmil: add fan pinctrl] Signed-off-by: Emil Renner Berthing Signed-off-by: Han Gao --- .../boot/dts/thead/th1520-lichee-pi-4a.dts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index 3e99f905dc316e..fd06c4adcb3770 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -39,9 +39,75 @@ }; }; }; + + fan: pwm-fan { + pinctrl-names = "default"; + pinctrl-0 = <&fan_pins>; + compatible = "pwm-fan"; + #cooling-cells = <2>; + pwms = <&pwm 1 10000000 0>; + cooling-levels = <0 66 196 255>; + }; + + thermal-zones { + cpu-thermal { + polling-delay = <1000>; + polling-delay-passive = <1000>; + thermal-sensors = <&pvt 0>; + + trips { + trip_active0: active-0 { + temperature = <39000>; + hysteresis = <5000>; + type = "active"; + }; + + trip_active1: active-1 { + temperature = <50000>; + hysteresis = <5000>; + type = "active"; + }; + + trip_active2: active-2 { + temperature = <60000>; + hysteresis = <5000>; + type = "active"; + }; + }; + + cooling-maps { + map-active-0 { + cooling-device = <&fan 1 1>; + trip = <&trip_active0>; + }; + + map-active-1 { + cooling-device = <&fan 2 2>; + trip = <&trip_active1>; + }; + + map-active-2 { + cooling-device = <&fan 3 3>; + trip = <&trip_active2>; + }; + }; + }; + }; }; &padctrl0_apsys { + fan_pins: fan-0 { + pwm1-pins { + pins = "GPIO3_3"; /* PWM1 */ + function = "pwm"; + bias-disable; + drive-strength = <25>; + input-disable; + input-schmitt-disable; + slew-rate = <0>; + }; + }; + uart0_pins: uart0-0 { tx-pins { pins = "UART0_TXD"; From ee50844b334c69894be01e2761c915b666d6e166 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 21 Sep 2023 13:51:56 +0800 Subject: [PATCH 28/33] XUANTIE: riscv: dts: thead: Add TH1520 USB nodes From: https://github.com/revyos/th1520-linux-kernel/commit/2bd78742752e4f0eb07b421c3b3fc662c953aff0 Signed-off-by: Jisheng Zhang Signed-off-by: Emil Renner Berthing Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index ef0ef5e3e9b67b..b8bfa35745fe9f 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -660,6 +660,29 @@ #pwm-cells = <3>; }; + misc_sysreg: misc_sysreg@ffec02c000 { + compatible = "thead,th1520-misc-sysreg", "syscon"; + reg = <0xff 0xec02c000 0x0 0x1000>; + }; + + usb: usb@ffec03f000 { + compatible = "thead,th1520-usb"; + reg = <0xff 0xec03f000 0x0 0x1000>; + thead,misc-sysreg = <&misc_sysreg>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + usb_dwc3: usb@ffe7040000 { + compatible = "snps,dwc3"; + reg = <0xff 0xe7040000 0x0 0x10000>; + interrupts = <68 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "host"; + snps,usb3_lpm_capable; + status = "disabled"; + }; + }; + dmac0: dma-controller@ffefc00000 { compatible = "snps,axi-dma-1.01a"; reg = <0xff 0xefc00000 0x0 0x1000>; From a22f3969f609dbe937b0fc85a572cc9ab0dc6360 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 21 Sep 2023 13:50:07 +0800 Subject: [PATCH 29/33] XUANTIE: riscv: dts: thead: Add TH1520 I2C nodes From: https://github.com/revyos/th1520-linux-kernel/commit/ef4ac920a874b013f7609fb1f88fa08bcd445a01 Signed-off-by: Jisheng Zhang Signed-off-by: Emil Renner Berthing Signed-off-by: Han Gao --- arch/riscv/boot/dts/thead/th1520.dtsi | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index b8bfa35745fe9f..8be9a72b84917d 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -440,6 +440,36 @@ status = "disabled"; }; + i2c0: i2c@ffe7f20000 { + compatible = "snps,designware-i2c"; + reg = <0xff 0xe7f20000 0x0 0x1000>; + clocks = <&clk CLK_I2C0>; + interrupts = <44 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c1: i2c@ffe7f24000 { + compatible = "snps,designware-i2c"; + reg = <0xff 0xe7f24000 0x0 0x1000>; + clocks = <&clk CLK_I2C1>; + interrupts = <45 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c4: i2c@ffe7f28000 { + compatible = "snps,designware-i2c"; + reg = <0xff 0xe7f28000 0x0 0x1000>; + clocks = <&clk CLK_I2C4>; + interrupts = <48 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + gpio@ffe7f34000 { compatible = "snps,dw-apb-gpio"; reg = <0xff 0xe7f34000 0x0 0x1000>; @@ -538,6 +568,16 @@ thead,pad-group = <3>; }; + i2c2: i2c@ffec00c000 { + compatible = "snps,designware-i2c"; + reg = <0xff 0xec00c000 0x0 0x1000>; + clocks = <&clk CLK_I2C2>; + interrupts = <46 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + uart2: serial@ffec010000 { compatible = "snps,dw-apb-uart"; reg = <0xff 0xec010000 0x0 0x4000>; @@ -653,6 +693,16 @@ }; }; + i2c3: i2c@ffec014000 { + compatible = "snps,designware-i2c"; + reg = <0xff 0xec014000 0x0 0x1000>; + clocks = <&clk CLK_I2C3>; + interrupts = <47 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + pwm: pwm@ffec01c000 { compatible = "thead,th1520-pwm"; reg = <0xff 0xec01c000 0x0 0x4000>; From f35581f4bbe04e7bdabec01d514ce0d215b89191 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Wed, 13 Dec 2023 01:58:00 +0100 Subject: [PATCH 30/33] XUANTIE: riscv: dts: thead: Add Lichee Pi 4A IO expansions From: https://github.com/revyos/th1520-linux-kernel/commit/b41720b46f4b203f7935d7cf5613c1782949774b Signed-off-by: Emil Renner Berthing Signed-off-by: Han Gao --- .../boot/dts/thead/th1520-lichee-pi-4a.dts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index fd06c4adcb3770..44e45b49599521 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -95,6 +95,76 @@ }; }; +&i2c0 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + clock-frequency = <100000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp1: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "cam0_dvdd12", + "cam0_avdd28", + "cam0_dovdd18"; + }; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp2: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "", + "cam0_reset", + "cam1_reset", + "cam2_reset", + "wl_host_wake", + "bt_resetn", + "", + "bt_host_wake"; + }; +}; + +&i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pins>; + clock-frequency = <100000>; + i2c-sda-hold-time-ns = <300>; + i2c-sda-falling-time-ns = <510>; + i2c-scl-falling-time-ns = <510>; + status = "okay"; + + ioexp3: gpio@18 { + compatible = "nxp,pca9557"; + reg = <0x18>; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "tp0_rst", + "", + "", + "vcc5v_usb", + "vdd28_tp0", + "vdd33_lcd0", + "vdd18_lcd0", + "lcd0_reset"; + }; +}; + &padctrl0_apsys { fan_pins: fan-0 { pwm1-pins { @@ -108,6 +178,18 @@ }; }; + i2c3_pins: i2c3-0 { + i2c-pins { + pins = "I2C3_SCL", "I2C3_SDA"; + function = "i2c"; + bias-disable; + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; + }; + uart0_pins: uart0-0 { tx-pins { pins = "UART0_TXD"; @@ -142,6 +224,33 @@ &hdmi_out_port { hdmi_out_con: endpoint { remote-endpoint = <&hdmi_con_in>; + + }; +}; + +&padctrl1_apsys { + i2c0_pins: i2c0-0 { + i2c-pins { + pins = "I2C0_SCL", "I2C0_SDA"; + function = "i2c"; + bias-disable; + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; + }; + + i2c1_pins: i2c1-0 { + i2c-pins { + pins = "I2C1_SCL", "I2C1_SDA"; + function = "i2c"; + bias-disable; + drive-strength = <7>; + input-enable; + input-schmitt-enable; + slew-rate = <0>; + }; }; }; From 4f7eb252ae8efeb4629a7825ad4cd4208e72394e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 21 Sep 2023 13:56:37 +0800 Subject: [PATCH 31/33] XUANTIE: riscv: dts: thead: Enable Lichee Pi 4A USB From: https://github.com/revyos/th1520-linux-kernel/commit/9f2a969ac4596062d55412ee6b7f25a6774b5358 Signed-off-by: Jisheng Zhang [esmil: fix gpio references] Signed-off-by: Emil Renner Berthing [ Han Gao: remove audio_i2c ] Signed-off-by: Han Gao --- .../boot/dts/thead/th1520-lichee-pi-4a.dts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index 44e45b49599521..7a827e3b8496da 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -4,6 +4,7 @@ */ #include "th1520-lichee-module-4a.dtsi" +#include / { model = "Sipeed Lichee Pi 4A"; @@ -23,6 +24,11 @@ serial4 = &uart4; serial5 = &uart5; spi0 = &spi0; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c2 = &i2c2; + i2c3 = &i2c3; + i2c4 = &i2c4; }; chosen { @@ -49,6 +55,26 @@ cooling-levels = <0 66 196 255>; }; + hub_5v: regulator-hub_5v { + compatible = "regulator-fixed"; + regulator-name = "HUB_5V"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&ioexp3 3 GPIO_ACTIVE_HIGH>; + enable-active-high; + regulator-always-on; + }; + + vcc5v_usb: regulator-vcc5v_usb { + compatible = "regulator-fixed"; + regulator-name = "VCC5V_USB"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio1 22 GPIO_ACTIVE_HIGH>; + enable-active-high; + regulator-always-on; + }; + thermal-zones { cpu-thermal { polling-delay = <1000>; @@ -95,6 +121,14 @@ }; }; +&aogpio { + sel-usb-hub-hog { + gpio-hog; + gpios = <4 GPIO_ACTIVE_HIGH>; + output-high; + }; +}; + &i2c0 { pinctrl-names = "default"; pinctrl-0 = <&i2c0_pins>; @@ -259,3 +293,28 @@ pinctrl-0 = <&uart0_pins>; status = "okay"; }; + +&usb { + status = "okay"; +}; + +&usb_dwc3 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + peer-hub = <&hub_3_0>; + vdd-supply = <&hub_5v>; + vbus-supply = <&vcc5v_usb>; + }; + + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + peer-hub = <&hub_2_0>; + vbus-supply = <&vcc5v_usb>; + }; +}; From 2389d5201f3dfa63670710e0a8fcb6bd965e1958 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Wed, 14 May 2025 08:16:15 +0800 Subject: [PATCH 32/33] REVYOS: HACK: riscv: dts: th1520: rename thead to xuantie From: https://github.com/revyos/th1520-linux-kernel/commit/fe55f7b77b81749bfd2a5eac9328e016f299d7a6 HACK patch for compatibility with thead-u-boot and vendor opensbi Signed-off-by: Han Gao [Icenowy: preserve the original compatible to allow Linux to match] Signed-off-by: Icenowy Zheng --- arch/riscv/boot/dts/thead/th1520.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 8be9a72b84917d..2b23d1fbd3fa76 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -286,7 +286,7 @@ }; aon: aon { - compatible = "thead,th1520-aon"; + compatible = "xuantie,th1520-aon", "thead,th1520-aon"; mboxes = <&mbox_910t 1>; mbox-names = "aon"; resets = <&rst TH1520_RESET_ID_GPU_CLKGEN>; From e0e2cd2d3d75324fc96f7f7995260fa3ca4b2dac Mon Sep 17 00:00:00 2001 From: Han Gao Date: Wed, 14 May 2025 08:27:18 +0800 Subject: [PATCH 33/33] REVYOS: HACK: riscv: dts: th1520: add xuantie,th1520-mbox-r From: https://github.com/revyos/th1520-linux-kernel/commit/1e57185ba18320bb4fb747a6089f9404f970964c HACK patch for compatibility with thead-u-boot and vendor opensbi Signed-off-by: Han Gao [Icenowy: remove the interrupt-controller property] Signed-off-by: Icenowy Zheng --- arch/riscv/boot/dts/thead/th1520.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 2b23d1fbd3fa76..0502e4661b12db 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -292,6 +292,24 @@ resets = <&rst TH1520_RESET_ID_GPU_CLKGEN>; reset-names = "gpu-clkgen"; #power-domain-cells = <1>; + opensbi-mboxes = <&mbox_910r>; + status = "okay"; + }; + + mbox_910r: mbox@ffefc53000 { + compatible = "xuantie,th1520-mbox-r"; + reg = <0xff 0xefc53000 0x0 0x4000>, + <0xff 0xefc3f000 0x0 0x1000>, + <0xff 0xefc47000 0x0 0x1000>, + <0xff 0xefc4f000 0x0 0x1000>; + reg-names = "local_base", + "remote_icu0", + "remote_icu1", + "remote_icu2"; + clocks = <&clk CLK_PERI_APB_PCLK>; + clock-names = "ipg"; + icu_cpu_id = <3>; + #mbox-cells = <2>; }; soc {