Q6 based firmware loading is also present on IPQ9574, when coupled with a wifi-6 device, such as QCN5024. Populate driver data for IPQ9574 with values from the downstream 5.4 kerrnel.
Add the new sequences for the WCSS reset and stop. The downstream 5.4 kernel calls these "Q6V7", so keep the name. This is still worth using with the "q6v5" driver because all other parts of the driver can be seamlessly reused. The IPQ9574 uses two sets of clocks. the first, dubbed "q6_clocks" must be enabled before the Q6 is started by writing the Q6SS_RST_EVB register. The second set of clocks, "clks" should only be enabled after the Q6 is placed out of reset. Otherwise, the host CPU core that tries to start the remoteproc will hang. The downstream kernel had a funny comment, "Pray god and wait for reset to complete", which I decided to keep for entertainment value. Signed-off-by: Alexandru Gagniuc <[email protected]> --- drivers/remoteproc/qcom_q6v5_wcss.c | 241 +++++++++++++++++++++++++++- 1 file changed, 234 insertions(+), 7 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c index c27200159a88a..b62e97c92d058 100644 --- a/drivers/remoteproc/qcom_q6v5_wcss.c +++ b/drivers/remoteproc/qcom_q6v5_wcss.c @@ -31,8 +31,13 @@ #define Q6SS_MEM_PWR_CTL 0x0B0 #define Q6SS_STRAP_ACC 0x110 #define Q6SS_CGC_OVERRIDE 0x034 +#define Q6SS_BOOT_CORE_START 0x400 +#define Q6SS_BOOT_CMD 0x404 +#define Q6SS_BOOT_STATUS 0x408 #define Q6SS_BCR_REG 0x6000 +#define Q6SS_TIMEOUT_US 1000 + /* AXI Halt Register Offsets */ #define AXI_HALTREQ_REG 0x0 #define AXI_HALTACK_REG 0x4 @@ -67,6 +72,7 @@ #define HALT_CHECK_MAX_LOOPS 200 #define Q6SS_XO_CBCR GENMASK(5, 3) #define Q6SS_SLEEP_CBCR GENMASK(5, 2) +#define Q6SS_CORE_CBCR BIT(5) /* Q6SS config/status registers */ #define TCSR_GLOBAL_CFG0 0x0 @@ -77,9 +83,11 @@ #define Q6SS_RST_EVB 0x10 #define BHS_EN_REST_ACK BIT(0) +#define WCSS_HM_RET BIT(1) #define SSCAON_ENABLE BIT(13) #define SSCAON_BUS_EN BIT(15) #define SSCAON_BUS_MUX_MASK GENMASK(18, 16) +#define SSCAON_MASK GENMASK(17, 15) #define MEM_BANKS 19 #define TCSR_WCSS_CLK_MASK 0x1F @@ -88,6 +96,7 @@ #define MAX_HALT_REG 4 enum { WCSS_IPQ8074, + WCSS_IPQ9574, WCSS_QCS404, }; @@ -128,6 +137,12 @@ struct q6v5_wcss { struct clk *qdsp6ss_xo_cbcr; struct clk *qdsp6ss_core_gfmux; struct clk *lcc_bcr_sleep; + struct clk_bulk_data *clks; + /* clocks that must be started before the Q6 is booted */ + struct clk_bulk_data *q6_clks; + int num_clks; + int num_q6_clks; + struct regulator *cx_supply; struct qcom_sysmon *sysmon; @@ -236,6 +251,87 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) return 0; } +static int q6v7_wcss_reset(struct q6v5_wcss *wcss, struct rproc *rproc) +{ + int ret; + u32 val; + + /*1. Set TCSR GLOBAL CFG1*/ + ret = regmap_update_bits(wcss->halt_map, + wcss->halt_nc + TCSR_GLOBAL_CFG1, + 0xff00, 0x1100); + if (ret) { + dev_err(wcss->dev, "TCSE_GLOBAL_CFG1 failed\n"); + return ret; + } + + /* Enable Q6 clocks */ + ret = clk_bulk_prepare_enable(wcss->num_q6_clks, wcss->q6_clks); + if (ret) { + dev_err(wcss->dev, "failed to enable clocks, err=%d\n", ret); + return ret; + }; + + /* Write bootaddr to EVB so that Q6WCSS will jump there after reset */ + writel(rproc->bootaddr >> 4, wcss->reg_base + Q6SS_RST_EVB); + + /*2. Deassert AON Reset */ + ret = reset_control_deassert(wcss->wcss_aon_reset); + if (ret) { + dev_err(wcss->dev, "wcss_aon_reset failed\n"); + clk_bulk_disable_unprepare(wcss->num_clks, wcss->clks); + return ret; + } + + /*8. Set mpm configs*/ + /*set CFG[18:15]=1*/ + val = readl(wcss->rmb_base + SSCAON_CONFIG); + val &= ~SSCAON_MASK; + val |= SSCAON_BUS_EN; + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + /*9. Wait for SSCAON_STATUS */ + ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS, + val, (val & 0xffff) == 0x10, 1000, + Q6SS_TIMEOUT_US * 1000); + if (ret) { + dev_err(wcss->dev, " Boot Error, SSCAON=0x%08X\n", val); + return ret; + } + + /*3. BHS require xo cbcr to be enabled */ + val = readl(wcss->reg_base + Q6SS_XO_CBCR); + val |= 0x1; + writel(val, wcss->reg_base + Q6SS_XO_CBCR); + + /*4. Enable core cbcr*/ + val = readl(wcss->reg_base + Q6SS_CORE_CBCR); + val |= 0x1; + writel(val, wcss->reg_base + Q6SS_CORE_CBCR); + + /*5. Enable sleep cbcr*/ + val = readl(wcss->reg_base + Q6SS_SLEEP_CBCR); + val |= 0x1; + writel(val, wcss->reg_base + Q6SS_SLEEP_CBCR); + + /*6. Boot core start */ + writel(0x1, wcss->reg_base + Q6SS_BOOT_CORE_START); + writel(0x1, wcss->reg_base + Q6SS_BOOT_CMD); + + /*7. Pray god and wait for reset to complete*/ + ret = readl_poll_timeout(wcss->reg_base + Q6SS_BOOT_STATUS, val, + (val & 0x01), 20000, 1000); + + /* Enable non-Q6 clocks */ + ret = clk_bulk_prepare_enable(wcss->num_clks, wcss->clks); + if (ret) { + dev_err(wcss->dev, "failed to enable clocks, err=%d\n", ret); + return ret; + }; + + return 0; +} + static int q6v5_wcss_start(struct rproc *rproc) { struct q6v5_wcss *wcss = rproc->priv; @@ -270,10 +366,20 @@ static int q6v5_wcss_start(struct rproc *rproc) if (ret) goto wcss_q6_reset; - /* Write bootaddr to EVB so that Q6WCSS will jump there after reset */ - writel(rproc->bootaddr >> 4, wcss->reg_base + Q6SS_RST_EVB); + switch (wcss->version) { + case WCSS_QCS404: + case WCSS_IPQ8074: + /* Write bootaddr to EVB so that Q6WCSS will jump there after + * reset. + */ + writel(rproc->bootaddr >> 4, wcss->reg_base + Q6SS_RST_EVB); + ret = q6v5_wcss_reset(wcss); + break; + case WCSS_IPQ9574: + ret = q6v7_wcss_reset(wcss, rproc); + break; + } - ret = q6v5_wcss_reset(wcss); if (ret) goto wcss_q6_reset; @@ -638,6 +744,41 @@ static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss) return 0; } +static int q6v7_wcss_powerdown(struct q6v5_wcss *wcss) +{ + uint32_t val; + int ret; + + q6v5_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_wcss); + + val = readl(wcss->rmb_base + SSCAON_CONFIG); + val &= ~SSCAON_MASK; + val |= SSCAON_BUS_EN; + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + val |= WCSS_HM_RET; + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS, + val, (val & 0xffff) == 0x400, 1000, + HALT_CHECK_MAX_LOOPS); + if (ret) { + dev_err(wcss->dev, + "can't get SSCAON_STATUS rc:%d)\n", ret); + return ret; + } + + usleep_range(2000, 4000); + + reset_control_assert(wcss->wcss_aon_reset); + + val = readl(wcss->rmb_base + SSCAON_CONFIG); + val &= ~WCSS_HM_RET; + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + return 0; +} + static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) { int ret; @@ -705,6 +846,25 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) return 0; } +static void q6v7_q6_powerdown(struct q6v5_wcss *wcss) +{ + uint32_t val; + + q6v5_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_q6); + + /* Disable Q6 Core clock -- we don't know what bit 0 means */ + val = readl(wcss->reg_base + Q6SS_GFMUX_CTL_REG); + val &= ~BIT(0); + writel(val, wcss->reg_base + Q6SS_GFMUX_CTL_REG); + + clk_bulk_disable_unprepare(wcss->num_clks, wcss->clks); + clk_bulk_disable_unprepare(wcss->num_q6_clks, wcss->q6_clks); + + reset_control_assert(wcss->wcss_q6_reset); + usleep_range(1000, 2000); + reset_control_assert(wcss->wcss_reset); +} + static int q6v5_wcss_stop(struct rproc *rproc) { struct q6v5_wcss *wcss = rproc->priv; @@ -719,11 +879,21 @@ static int q6v5_wcss_stop(struct rproc *rproc) } } - if (wcss->version == WCSS_QCS404) { + switch (wcss->version) { + case WCSS_QCS404: ret = q6v5_qcs404_wcss_shutdown(wcss); if (ret) return ret; - } else { + break; + case WCSS_IPQ9574: + ret = q6v7_wcss_powerdown(wcss); + if (ret) + return ret; + + q6v7_q6_powerdown(wcss); + + break; + default: ret = q6v5_wcss_powerdown(wcss); if (ret) return ret; @@ -732,6 +902,7 @@ static int q6v5_wcss_stop(struct rproc *rproc) ret = q6v5_q6_powerdown(wcss); if (ret) return ret; + break; } qcom_q6v5_unprepare(&wcss->q6v5); @@ -838,7 +1009,9 @@ static int q6v5_wcss_init_mmio(struct q6v5_wcss *wcss, if (!wcss->reg_base) return -ENOMEM; - if (wcss->version == WCSS_IPQ8074) { + switch (wcss->version) { + case WCSS_IPQ8074: + case WCSS_IPQ9574: wcss->rmb_base = devm_platform_ioremap_resource_byname(pdev, "rmb"); if (IS_ERR(wcss->rmb_base)) return PTR_ERR(wcss->rmb_base); @@ -965,6 +1138,43 @@ static int q6v5_wcss_init_regulator(struct q6v5_wcss *wcss) return 0; } +static int ipq9574_init_clocks(struct q6v5_wcss *wcss) +{ + static const char *const q6_clks[] = { + "anoc_wcss_axi_m", "q6_ahb", "q6_ahb_s", "q6_axim", "q6ss_boot", + "mem_noc_q6_axi", "sys_noc_wcss_ahb", "wcss_acmt", "wcss_ecahb", + "wcss_q6_tbu" }; + static const char *const clks[] = { + "q6_axim2", "wcss_ahb_s", "wcss_axi_m" }; + int i, ret; + + wcss->num_clks = ARRAY_SIZE(clks); + wcss->num_q6_clks = ARRAY_SIZE(q6_clks); + + wcss->q6_clks = devm_kcalloc(wcss->dev, wcss->num_q6_clks, + sizeof(*wcss->q6_clks), GFP_KERNEL); + if (!wcss->q6_clks) + return -ENOMEM; + + wcss->clks = devm_kcalloc(wcss->dev, wcss->num_clks, + sizeof(*wcss->clks), GFP_KERNEL); + if (!wcss->clks) + return -ENOMEM; + + for (i = 0; i < wcss->num_q6_clks; i++) + wcss->q6_clks[i].id = q6_clks[i]; + + for (i = 0; i < wcss->num_clks; i++) + wcss->clks[i].id = clks[i]; + + ret = devm_clk_bulk_get(wcss->dev, wcss->num_q6_clks, wcss->q6_clks); + if (ret < 0) + return ret; + + return devm_clk_bulk_get(wcss->dev, wcss->num_clks, wcss->clks); +} + + static int q6v5_wcss_probe(struct platform_device *pdev) { const struct wcss_data *desc; @@ -997,7 +1207,8 @@ static int q6v5_wcss_probe(struct platform_device *pdev) if (ret) return ret; - if (wcss->version == WCSS_QCS404) { + switch (wcss->version) { + case WCSS_QCS404: ret = q6v5_wcss_init_clock(wcss); if (ret) return ret; @@ -1005,6 +1216,11 @@ static int q6v5_wcss_probe(struct platform_device *pdev) ret = q6v5_wcss_init_regulator(wcss); if (ret) return ret; + break; + case WCSS_IPQ9574: + ret = ipq9574_init_clocks(wcss); + if (ret) + return ret; } ret = q6v5_wcss_init_reset(wcss, desc); @@ -1067,6 +1283,16 @@ static const struct wcss_data wcss_ipq8074_res_init = { .requires_force_stop = true, }; +static const struct wcss_data wcss_ipq9574_res_init = { + .firmware_name = "IPQ9574/q6_fw.mdt", + .version = WCSS_IPQ9574, + .crash_reason_smem = WCSS_CRASH_REASON, + .aon_reset_required = true, + .ssr_name = "q6wcss", + .ops = &q6v5_wcss_ipq8074_ops, + .requires_force_stop = true, +}; + static const struct wcss_data wcss_qcs404_res_init = { .crash_reason_smem = WCSS_CRASH_REASON, .firmware_name = "wcnss.mdt", @@ -1082,6 +1308,7 @@ static const struct wcss_data wcss_qcs404_res_init = { static const struct of_device_id q6v5_wcss_of_match[] = { { .compatible = "qcom,ipq8074-wcss-pil", .data = &wcss_ipq8074_res_init }, + { .compatible = "qcom,ipq9574-wcss-pil", .data = &wcss_ipq9574_res_init }, { .compatible = "qcom,qcs404-wcss-pil", .data = &wcss_qcs404_res_init }, { }, }; -- 2.45.1

