blob: f1ce06937e8a1b3d5b48944aa4bda0f3581adaff [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2018 Linaro Ltd.
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2018, 2021 The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include <linux/qcom_scm.h>
#include <linux/interrupt.h>
#ifdef CONFIG_IPQ_SUBSYSTEM_RAMDUMP
#include <soc/qcom/ramdump.h>
#endif
#include "qcom_common.h"
#include "qcom_q6v5.h"
#define WCSS_CRASH_REASON 421
#define Q6_BOOT_TRIG_SVC_ID 0x5
#define Q6_BOOT_TRIG_CMD_ID 0x2
/* Q6SS Register Offsets */
#define Q6SS_RESET_REG 0x014
#define Q6SS_DBG_CFG 0x018
#define Q6SS_GFMUX_CTL_REG 0x020
#define Q6SS_PWR_CTL_REG 0x030
#define Q6SS_MEM_PWR_CTL 0x0B0
#define Q6SS_STRAP_ACC 0x110
#define Q6SS_CGC_OVERRIDE 0x034
#define Q6SS_BCR_REG 0x6000
#define Q6SS_BOOT_CORE_START 0x400
#define Q6SS_BOOT_STATUS 0x408
#define Q6SS_AHB_UPPER 0x104
#define Q6SS_AHB_LOWER 0x108
/* AXI Halt Register Offsets */
#define AXI_HALTREQ_REG 0x0
#define AXI_HALTACK_REG 0x4
#define AXI_IDLE_REG 0x8
#define HALT_ACK_TIMEOUT_MS 100
/* Q6SS_RESET */
#define Q6SS_STOP_CORE BIT(0)
#define Q6SS_CORE_ARES BIT(1)
#define Q6SS_BUS_ARES_ENABLE BIT(2)
/* Q6SS_BRC_RESET */
#define Q6SS_BRC_BLK_ARES BIT(0)
/* Q6SS_GFMUX_CTL */
#define Q6SS_CLK_ENABLE BIT(1)
#define Q6SS_SWITCH_CLK_SRC BIT(8)
/* Q6SS_PWR_CTL */
#define Q6SS_L2DATA_STBY_N BIT(18)
#define Q6SS_SLP_RET_N BIT(19)
#define Q6SS_CLAMP_IO BIT(20)
#define QDSS_BHS_ON BIT(21)
#define QDSS_Q6_MEMORIES GENMASK(15, 0)
/* Q6SS parameters */
#define Q6SS_LDO_BYP BIT(25)
#define Q6SS_BHS_ON BIT(24)
#define Q6SS_CLAMP_WL BIT(21)
#define Q6SS_CLAMP_QMC_MEM BIT(22)
#define HALT_CHECK_MAX_LOOPS 200
#define Q6SS_XO_CBCR GENMASK(5, 3)
#define Q6SS_SLEEP_CBCR GENMASK(5, 2)
#define Q6SS_TIMEOUT_US 1000
/* Q6SS config/status registers */
#define TCSR_GLOBAL_CFG0 0x0
#define TCSR_GLOBAL_CFG1 0x4
#define SSCAON_CONFIG 0x8
#define SSCAON_STATUS 0xc
#define Q6SS_BHS_STATUS 0x78
#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
#define TCSR_WCSS_CLK_ENABLE 0x14
#define MAX_HALT_REG 4
#define WCNSS_PAS_ID 6
#define MPD_WCNSS_PAS_ID 0xD
#define MAX_SEGMENTS 10
#define BUF_SIZE 35
#define REMOTE_PID 1
#define VERSION 1
#define Q6_BOOT_ARGS_SMEM_SIZE 4096
static int debug_wcss;
static int crash_cnt;
/**
* enum state - state of a wcss (private)
* @WCSS_NORMAL: subsystem is operating normally
* @WCSS_CRASHED: subsystem has crashed and hasn't been shutdown
* @WCSS_RESTARTING: subsystem has been shutdown and is now restarting
*
*/
enum q6_wcss_state {
WCSS_NORMAL,
WCSS_CRASHED,
WCSS_RESTARTING,
};
enum {
Q6_IPQ,
WCSS_AHB_IPQ,
WCSS_PCIE_IPQ,
};
struct q6_wcss {
struct device *dev;
void __iomem *reg_base;
void __iomem *rmb_base;
void __iomem *tcsr_msip_base;
void __iomem *tcsr_q6_base;
struct regmap *halt_map;
u32 halt_q6;
u32 halt_wcss;
u32 halt_nc;
u32 reset_cmd_id;
struct clk *xo;
struct clk *ahbfabric_cbcr_clk;
struct clk *gcc_abhs_cbcr; //"gcc_wcss_ahb_s_clk"
struct clk *gcc_axim_cbcr; //"gcc_q6_axim_clk"
struct clk *lcc_csr_cbcr;
struct clk *ahbs_cbcr; //"gcc_q6_ahb_s_clk"
struct clk *tcm_slave_cbcr;
struct clk *qdsp6ss_abhm_cbcr; //"gcc_q6_ahb_clk"
struct clk *qdsp6ss_sleep_cbcr;
struct clk *qdsp6ss_axim_cbcr; //"gcc_wcss_axi_m_clk"
struct clk *qdsp6ss_xo_cbcr;
struct clk *qdsp6ss_core_gfmux;
struct clk *lcc_bcr_sleep;
struct clk *prng_clk;
struct clk *eachb_clk; //"gcc_wcss_ecahb_clk"
struct clk *acmt_clk; //"gcc_wcss_acmt_clk"
struct clk *gcc_axim2_clk; //"gcc_q6_axim2_clk"
struct clk *axmis_clk; //"gcc_q6_axis_clk"
struct clk *axi_s_clk; //"gcc_wcss_axi_s_clk"
struct qcom_rproc_glink glink_subdev;
struct qcom_rproc_ssr ssr_subdev;
struct qcom_sysmon *sysmon;
struct reset_control *wcss_aon_reset;
struct reset_control *wcss_reset;
struct reset_control *wcss_q6_reset;
struct reset_control *ce_reset;
struct qcom_q6v5 q6;
phys_addr_t mem_phys;
phys_addr_t mem_reloc;
void *mem_region;
size_t mem_size;
int crash_reason_smem;
u32 version;
bool requires_force_stop;
bool need_mem_protection;
bool is_q6v6;
u8 pd_asid;
enum q6_wcss_state state;
};
struct wcss_data {
int (*init_clock)(struct q6_wcss *wcss);
int (*init_irq)(struct qcom_q6v5 *q6, struct platform_device *pdev,
struct rproc *rproc, int crash_reason,
void (*handover)(struct qcom_q6v5 *q6));
const char *q6_firmware_name;
int crash_reason_smem;
u32 version;
u32 reset_cmd_id;
bool aon_reset_required;
bool wcss_q6_reset_required;
bool wcss_reset_required;
bool ce_reset_required;
const char *ssr_name;
const char *sysmon_name;
int ssctl_id;
const struct rproc_ops *ops;
bool requires_force_stop;
bool need_mem_protection;
bool need_auto_boot;
bool is_q6v6;
bool glink_subdev_required;
u8 pd_asid;
};
static int qcom_get_pd_fw_info(struct q6_wcss *wcss, const struct firmware *fw,
struct ramdump_segment *segs, int index,
struct qcom_pd_fw_info *fw_info)
{
int ret = get_pd_fw_info(wcss->dev, fw, wcss->mem_phys, wcss->mem_size,
wcss->pd_asid, fw_info);
if (ret) {
dev_err(wcss->dev, "couldn't get PD firmware info : %d\n", ret);
return ret;
}
segs[index].address = fw_info->paddr;
segs[index].size = fw_info->size;
segs[index].v_address = ioremap(segs[index].address,
segs[index].size);
return ret;
}
#ifdef CONFIG_IPQ_SUBSYSTEM_RAMDUMP
static void crashdump_init(struct rproc *rproc,
struct rproc_dump_segment *segment,
void *dest)
{
void *handle;
struct ramdump_segment segs[MAX_SEGMENTS];
int ret, index = 0;
struct qcom_pd_fw_info fw_info = {0};
struct q6_wcss *wcss = rproc->priv;
struct device *dev = wcss->dev;
struct device_node *node = NULL, *np = dev->of_node, *upd_np;
const struct firmware *fw;
handle = create_ramdump_device(rproc->name, &rproc->dev);
if (!handle) {
dev_err(&rproc->dev, "unable to create ramdump device"
"for %s\n", rproc->name);
return;
}
if (create_ramdump_device_file(handle)) {
dev_err(&rproc->dev, "unable to create ramdump device"
"for %s\n", rproc->name);
goto free_device;
}
while (index < MAX_SEGMENTS) {
node = of_parse_phandle(np, "memory-region", index);
if (!node)
break;
ret = of_property_read_u32_index(node, "reg", 1,
(u32 *)&segs[index].address);
if (ret) {
pr_err("Could not retrieve reg addr %d\n", ret);
of_node_put(node);
goto put_node;
}
ret = of_property_read_u32_index(node, "reg", 3,
(u32 *)&segs[index].size);
if (ret) {
pr_err("Could not retrieve reg size %d\n", ret);
of_node_put(node);
goto put_node;
}
segs[index].v_address = ioremap(segs[index].address,
segs[index].size);
of_node_put(node);
index++;
}
/* Get the PD firmware info */
ret = request_firmware(&fw, rproc->firmware, dev);
if (ret < 0) {
dev_err(dev, "request_firmware failed: %d\n", ret);
goto free_device;
}
if (wcss->pd_asid) {
ret = qcom_get_pd_fw_info(wcss, fw, segs, index, &fw_info);
if (ret)
goto free_device;
index++;
wcss->state = WCSS_RESTARTING;
} else {
for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
struct platform_device *upd_pdev;
struct rproc *upd_rproc;
struct q6_wcss *upd_wcss;
if (strstr(upd_np->name, "pd") == NULL)
continue;
upd_pdev = of_find_device_by_node(upd_np);
upd_rproc = platform_get_drvdata(upd_pdev);
upd_wcss = upd_rproc->priv;
memset(&fw_info, 0, sizeof(fw_info));
ret = qcom_get_pd_fw_info(upd_wcss, fw, segs, index,
&fw_info);
if (ret)
goto free_device;
index++;
upd_wcss->state = WCSS_RESTARTING;
}
}
release_firmware(fw);
do_elf_ramdump(handle, segs, index);
put_node:
of_node_put(np);
free_device:
destroy_ramdump_device(handle);
}
#else
static void crashdump_init(struct rproc *rproc,
struct rproc_dump_segment *segment, void *dest)
{
}
#endif
static int ipq5018_clks_prepare_enable(struct q6_wcss *wcss)
{
int ret;
ret = clk_prepare_enable(wcss->gcc_abhs_cbcr);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->eachb_clk);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->acmt_clk);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->gcc_axim_cbcr);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->qdsp6ss_axim_cbcr);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->gcc_axim2_clk);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->qdsp6ss_abhm_cbcr);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->ahbs_cbcr);
if (ret)
return ret;
ret = clk_prepare_enable(wcss->axi_s_clk);
if (ret)
return ret;
return 0;
}
static void ipq5018_clks_prepare_disable(struct q6_wcss *wcss)
{
clk_disable_unprepare(wcss->gcc_abhs_cbcr);
clk_disable_unprepare(wcss->eachb_clk);
clk_disable_unprepare(wcss->acmt_clk);
clk_disable_unprepare(wcss->gcc_axim_cbcr);
clk_disable_unprepare(wcss->qdsp6ss_axim_cbcr);
clk_disable_unprepare(wcss->gcc_axim2_clk);
clk_disable_unprepare(wcss->qdsp6ss_abhm_cbcr);
clk_disable_unprepare(wcss->ahbs_cbcr);
clk_disable_unprepare(wcss->axi_s_clk);
}
static void q6v6_wcss_reset(struct q6_wcss *wcss)
{
u32 val;
int ret;
int temp = 0;
unsigned int cookie;
/*Assert Q6 BLK Reset*/
ret = reset_control_assert(wcss->wcss_q6_reset);
if (ret) {
dev_err(wcss->dev, "wcss_q6_reset failed\n");
return;
}
/* AON Reset */
ret = reset_control_deassert(wcss->wcss_aon_reset);
if (ret) {
dev_err(wcss->dev, "wcss_aon_reset failed\n");
return;
}
/*Enable Q6 AXIS CLOCK RESET*/
ret = clk_prepare_enable(wcss->axmis_clk);
if (ret) {
dev_err(wcss->dev, "wcss clk(s) enable failed");
return;
}
/*Disable Q6 AXIS CLOCK RESET*/
clk_disable_unprepare(wcss->axmis_clk);
/*De assert Q6 BLK reset*/
ret = reset_control_deassert(wcss->wcss_q6_reset);
if (ret) {
dev_err(wcss->dev, "wcss_q6_reset failed\n");
return;
}
/*Prepare Q6 clocks*/
ret = ipq5018_clks_prepare_enable(wcss);
if (ret) {
dev_err(wcss->dev, "wcss clk(s) enable failed");
return;
}
/*Secure access to WIFI phy register*/
regmap_update_bits(wcss->halt_map,
wcss->halt_nc + TCSR_GLOBAL_CFG1,
TCSR_WCSS_CLK_MASK,
0x18);
/*Disable Q6 AXI2 select*/
regmap_update_bits(wcss->halt_map,
wcss->halt_nc + TCSR_GLOBAL_CFG0, 0x40, 0xF0);
/*wcss axib ib status*/
regmap_update_bits(wcss->halt_map,
wcss->halt_nc + TCSR_GLOBAL_CFG0, 0x100, 0x100);
/*Q6 AHB upper & lower address*/
writel(0x00cdc000, wcss->reg_base + Q6SS_AHB_UPPER);
writel(0x00ca0000, wcss->reg_base + Q6SS_AHB_LOWER);
/*Configure MSIP*/
if (wcss->tcsr_msip_base)
writel(0x1, wcss->tcsr_msip_base + 0x00);
if (debug_wcss)
writel(0x20000001, wcss->reg_base + Q6SS_DBG_CFG);
else
writel(0x0, wcss->reg_base + Q6SS_DBG_CFG);
cookie = 1;
ret = qti_scm_wcss_boot(Q6_BOOT_TRIG_SVC_ID,
Q6_BOOT_TRIG_CMD_ID, &cookie);
if (ret) {
dev_err(wcss->dev, "q6-boot trigger scm failed\n");
return;
}
/* Boot core start */
writel(0x1, wcss->reg_base + Q6SS_BOOT_CORE_START);
while (temp < 20) {
val = readl(wcss->reg_base + Q6SS_BOOT_STATUS);
if (val & 0x01)
break;
mdelay(1);
temp += 1;
}
pr_err("%s: start %s\n", wcss->q6.rproc->name,
val == 1 ? "successful" : "failed");
wcss->q6.running = val == 1 ? true : false;
}
static int q6_wcss_reset(struct q6_wcss *wcss)
{
int ret;
u32 val;
int i;
/* Assert resets, stop core */
val = readl(wcss->reg_base + Q6SS_RESET_REG);
val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE;
writel(val, wcss->reg_base + Q6SS_RESET_REG);
/* 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);
/* Read CLKOFF bit to go low indicating CLK is enabled */
ret = readl_poll_timeout(wcss->reg_base + Q6SS_XO_CBCR,
val, !(val & BIT(31)), 1,
HALT_CHECK_MAX_LOOPS);
if (ret) {
dev_err(wcss->dev,
"xo cbcr enabling timed out (rc:%d)\n", ret);
return ret;
}
/* Enable power block headswitch and wait for it to stabilize */
val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
val |= Q6SS_BHS_ON;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
udelay(1);
/* Put LDO in bypass mode */
val |= Q6SS_LDO_BYP;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* Deassert Q6 compiler memory clamp */
val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
val &= ~Q6SS_CLAMP_QMC_MEM;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* Deassert memory peripheral sleep and L2 memory standby */
val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* Turn on L1, L2, ETB and JU memories 1 at a time */
val = readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
for (i = MEM_BANKS; i >= 0; i--) {
val |= BIT(i);
writel(val, wcss->reg_base + Q6SS_MEM_PWR_CTL);
/*
* Read back value to ensure the write is done then
* wait for 1us for both memory peripheral and data
* array to turn on.
*/
val |= readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
udelay(1);
}
/* Remove word line clamp */
val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
val &= ~Q6SS_CLAMP_WL;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* Remove IO clamp */
val &= ~Q6SS_CLAMP_IO;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* Bring core out of reset */
val = readl(wcss->reg_base + Q6SS_RESET_REG);
val &= ~Q6SS_CORE_ARES;
writel(val, wcss->reg_base + Q6SS_RESET_REG);
/* Turn on core clock */
val = readl(wcss->reg_base + Q6SS_GFMUX_CTL_REG);
val |= Q6SS_CLK_ENABLE;
writel(val, wcss->reg_base + Q6SS_GFMUX_CTL_REG);
/* Start core execution */
val = readl(wcss->reg_base + Q6SS_RESET_REG);
val &= ~Q6SS_STOP_CORE;
writel(val, wcss->reg_base + Q6SS_RESET_REG);
return 0;
}
static int q6_wcss_start(struct rproc *rproc)
{
struct q6_wcss *wcss = rproc->priv;
const struct firmware *firmware_p;
int ret;
qcom_q6v5_prepare(&wcss->q6);
if (wcss->need_mem_protection) {
ret = qcom_scm_pas_auth_and_reset(MPD_WCNSS_PAS_ID, debug_wcss,
wcss->reset_cmd_id);
if (ret) {
dev_err(wcss->dev, "wcss_reset failed\n");
return ret;
}
goto wait_for_reset;
}
/* Release Q6 and WCSS reset */
ret = reset_control_deassert(wcss->wcss_reset);
if (ret) {
dev_err(wcss->dev, "wcss_reset failed\n");
return ret;
}
ret = reset_control_deassert(wcss->wcss_q6_reset);
if (ret) {
dev_err(wcss->dev, "wcss_q6_reset failed\n");
goto wcss_reset;
}
/* Lithium configuration - clock gating and bus arbitration */
ret = regmap_update_bits(wcss->halt_map,
wcss->halt_nc + TCSR_GLOBAL_CFG0,
TCSR_WCSS_CLK_MASK,
TCSR_WCSS_CLK_ENABLE);
if (ret)
goto wcss_q6_reset;
ret = regmap_update_bits(wcss->halt_map,
wcss->halt_nc + TCSR_GLOBAL_CFG1,
1, 0);
if (ret)
goto wcss_q6_reset;
if (debug_wcss && !wcss->is_q6v6)
writel(0x20000001, wcss->reg_base + Q6SS_DBG_CFG);
/* Write bootaddr to EVB so that Q6WCSS will jump there after reset */
writel(rproc->bootaddr >> 4, wcss->reg_base + Q6SS_RST_EVB);
if (wcss->is_q6v6)
q6v6_wcss_reset(wcss);
else
ret = q6_wcss_reset(wcss);
if (ret)
goto wcss_q6_reset;
wait_for_reset:
ret = qcom_q6v5_wait_for_start(&wcss->q6, 5 * HZ);
if (ret == -ETIMEDOUT) {
if (debug_wcss)
goto wait_for_reset;
else
dev_err(wcss->dev, "start timed out\n");
}
/*reset done clear the debug register*/
if (debug_wcss && !wcss->is_q6v6)
writel(0x0, wcss->reg_base + Q6SS_DBG_CFG);
/* start userpd's, if root pd getting recovered*/
if (crash_cnt < rproc->crash_cnt) {
struct device_node *upd_np;
struct platform_device *upd_pdev;
struct rproc *upd_rproc;
for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
if (strstr(upd_np->name, "pd") == NULL)
continue;
upd_pdev = of_find_device_by_node(upd_np);
upd_rproc = platform_get_drvdata(upd_pdev);
if (upd_rproc->state != RPROC_SUSPENDED)
continue;
/* load firmware */
ret = request_firmware(&firmware_p, upd_rproc->firmware,
&upd_pdev->dev);
if (ret < 0) {
dev_err(&upd_pdev->dev,
"request_firmware failed: %d\n", ret);
continue;
}
/* start the userpd rproc*/
ret = rproc_start(upd_rproc, firmware_p);
if (ret)
dev_err(&upd_pdev->dev, "failed to start %s\n",
upd_rproc->name);
release_firmware(firmware_p);
}
crash_cnt = rproc->crash_cnt;
}
return ret;
wcss_q6_reset:
reset_control_assert(wcss->wcss_q6_reset);
wcss_reset:
reset_control_assert(wcss->wcss_reset);
if (debug_wcss)
writel(0x0, wcss->reg_base + Q6SS_DBG_CFG);
return ret;
}
static int q6_wcss_spawn_pd(struct rproc *rproc)
{
int ret;
struct q6_wcss *wcss = rproc->priv;
ret = qcom_q6v5_request_spawn(&wcss->q6);
if (ret == -ETIMEDOUT) {
pr_err("%s spawn timedout\n", rproc->name);
return ret;
}
ret = qcom_q6v5_wait_for_start(&wcss->q6, 5 * HZ);
if (ret == -ETIMEDOUT) {
pr_err("%s start timedout\n", rproc->name);
wcss->q6.running = false;
return ret;
}
wcss->q6.running = true;
return ret;
}
static int wcss_ahb_pd_start(struct rproc *rproc)
{
struct q6_wcss *wcss = rproc->priv;
int ret;
u32 val;
if (wcss->need_mem_protection) {
ret = qti_scm_int_radio_powerup(MPD_WCNSS_PAS_ID);
if (ret) {
dev_err(wcss->dev, "failed to power up ahb pd\n");
return ret;
}
goto spawn_pd;
}
/* AON Reset */
ret = reset_control_deassert(wcss->wcss_aon_reset);
if (ret) {
dev_err(wcss->dev, "wcss_aon_reset failed\n");
return ret;
}
/*Enable AHB_S clock*/
ret = clk_prepare_enable(wcss->gcc_abhs_cbcr);
if (ret) {
dev_err(wcss->dev,
"failed to enable wcss ahb_s clock\n");
return ret;
}
/*Enable ACMT clock*/
ret = clk_prepare_enable(wcss->acmt_clk);
if (ret) {
dev_err(wcss->dev,
"failed to enable wcss acmt clock\n");
return ret;
}
/*Enable AXI_M clock*/
ret = clk_prepare_enable(wcss->gcc_axim_cbcr);
if (ret) {
dev_err(wcss->dev,
"failed to enable wcss axi_m clock\n");
return ret;
}
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val &= ~SSCAON_MASK;
val |= SSCAON_BUS_EN;
writel(val, wcss->rmb_base + SSCAON_CONFIG);
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val &= ~(1<<1);
writel(val, wcss->rmb_base + SSCAON_CONFIG);
mdelay(2);
/* 5 - wait for SSCAON_STATUS */
ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS,
val, (val & 0xffff) == 0x10, 1000,
Q6SS_TIMEOUT_US * 10);
if (ret) {
dev_err(wcss->dev,
"can't get SSCAON_STATUS rc:%d)\n", ret);
return ret;
}
/*Deassert ce reset*/
ret = reset_control_deassert(wcss->ce_reset);
if (ret) {
dev_err(wcss->dev, "ce_reset failed\n");
return ret;
}
spawn_pd:
ret = q6_wcss_spawn_pd(rproc);
if (ret)
return ret;
wcss->state = WCSS_NORMAL;
return ret;
}
static int wcss_pcie_pd_start(struct rproc *rproc)
{
int ret = q6_wcss_spawn_pd(rproc);
if (!ret) {
struct q6_wcss *wcss = rproc->priv;
wcss->state = WCSS_NORMAL;
}
return ret;
}
static void q6v6_wcss_halt_axi_port(struct q6_wcss *wcss,
struct regmap *halt_map,
u32 offset)
{
unsigned long timeout;
unsigned int val;
int ret;
/* Assert halt request */
regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1);
/* Wait for halt */
timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS);
for (;;) {
ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val);
if (ret || val || time_after(jiffies, timeout))
break;
msleep(1);
}
if (offset == wcss->halt_q6) {
ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val);
if (ret || !val)
dev_err(wcss->dev, "port failed halt\n");
}
/* Clear halt request (port will remain halted until reset) */
regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0);
}
static void q6_wcss_halt_axi_port(struct q6_wcss *wcss,
struct regmap *halt_map,
u32 offset)
{
unsigned long timeout;
unsigned int val;
int ret;
/* Check if we're already idle */
ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val);
if (!ret && val)
return;
/* Assert halt request */
regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1);
/* Wait for halt */
timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS);
for (;;) {
ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val);
if (ret || val || time_after(jiffies, timeout))
break;
msleep(1);
}
ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val);
if (ret || !val)
dev_err(wcss->dev, "port failed halt\n");
/* Clear halt request (port will remain halted until reset) */
regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0);
}
static int q6_wcss_powerdown(struct q6_wcss *wcss)
{
int ret;
u32 val;
/* 1 - Assert WCSS/Q6 HALTREQ */
if (wcss->is_q6v6)
q6v6_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_wcss);
else
q6_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_wcss);
if (wcss->is_q6v6) {
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val &= ~SSCAON_MASK;
val |= SSCAON_BUS_EN;
writel(val, wcss->rmb_base + SSCAON_CONFIG);
} else {
/* 2 - Enable WCSSAON_CONFIG */
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val |= SSCAON_ENABLE;
writel(val, wcss->rmb_base + SSCAON_CONFIG);
/* 3 - Set SSCAON_CONFIG */
val |= SSCAON_BUS_EN;
val &= ~SSCAON_BUS_MUX_MASK;
writel(val, wcss->rmb_base + SSCAON_CONFIG);
}
/* 4 - SSCAON_CONFIG 1 */
val |= BIT(1);
writel(val, wcss->rmb_base + SSCAON_CONFIG);
/* 5 - wait for SSCAON_STATUS */
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;
}
mdelay(2);
/* 6 - De-assert WCSS_AON reset */
reset_control_assert(wcss->wcss_aon_reset);
if (wcss->is_q6v6) {
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val &= ~(1<<1);
writel(val, wcss->rmb_base + SSCAON_CONFIG);
} else {
/* 7 - Disable WCSSAON_CONFIG 13 */
val = readl(wcss->rmb_base + SSCAON_CONFIG);
val &= ~SSCAON_ENABLE;
writel(val, wcss->rmb_base + SSCAON_CONFIG);
}
/* 8 - De-assert WCSS/Q6 HALTREQ */
reset_control_assert(wcss->wcss_reset);
return 0;
}
static void q6v6_powerdown(struct q6_wcss *wcss)
{
int ret;
unsigned int cookie;
/*Disable clocks*/
ipq5018_clks_prepare_disable(wcss);
cookie = 0;
ret = qti_scm_wcss_boot(Q6_BOOT_TRIG_SVC_ID,
Q6_BOOT_TRIG_CMD_ID, &cookie);
if (ret) {
dev_err(wcss->dev, "q6-stop trigger scm failed\n");
return;
}
}
static int q6_powerdown(struct q6_wcss *wcss)
{
int ret;
u32 val;
int i;
/* 1 - Halt Q6 bus interface */
if (wcss->is_q6v6)
q6v6_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_q6);
else
q6_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_q6);
/* 2 - Disable Q6 Core clock */
val = readl(wcss->reg_base + Q6SS_GFMUX_CTL_REG);
val &= ~Q6SS_CLK_ENABLE;
writel(val, wcss->reg_base + Q6SS_GFMUX_CTL_REG);
if (wcss->is_q6v6) {
q6v6_powerdown(wcss);
goto reset;
}
/* 3 - Clamp I/O */
val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
val |= Q6SS_CLAMP_IO;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* 4 - Clamp WL */
val |= QDSS_BHS_ON;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* 5 - Clear Erase standby */
val &= ~Q6SS_L2DATA_STBY_N;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* 6 - Clear Sleep RTN */
val &= ~Q6SS_SLP_RET_N;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* 7 - turn off Q6 memory foot/head switch one bank at a time */
for (i = 0; i < 20; i++) {
val = readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
val &= ~BIT(i);
writel(val, wcss->reg_base + Q6SS_MEM_PWR_CTL);
mdelay(1);
}
/* 8 - Assert QMC memory RTN */
val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
val |= Q6SS_CLAMP_QMC_MEM;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
/* 9 - Turn off BHS */
val &= ~Q6SS_BHS_ON;
writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
udelay(1);
/* 10 - Wait till BHS Reset is done */
ret = readl_poll_timeout(wcss->reg_base + Q6SS_BHS_STATUS,
val, !(val & BHS_EN_REST_ACK), 1000,
HALT_CHECK_MAX_LOOPS);
if (ret) {
dev_err(wcss->dev, "BHS_STATUS not OFF (rc:%d)\n", ret);
return ret;
}
reset:
/* 11 - Assert WCSS reset */
reset_control_assert(wcss->wcss_reset);
/* 12 - Assert Q6 reset */
reset_control_assert(wcss->wcss_q6_reset);
return 0;
}
static int q6_wcss_stop(struct rproc *rproc)
{
struct q6_wcss *wcss = rproc->priv;
int ret;
/* stop userpd's, if root pd getting crashed*/
if (rproc->state == RPROC_CRASHED) {
struct device_node *upd_np;
struct platform_device *upd_pdev;
struct rproc *upd_rproc;
struct q6_wcss *upd_wcss;
for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
if (strstr(upd_np->name, "pd") == NULL)
continue;
upd_pdev = of_find_device_by_node(upd_np);
upd_rproc = platform_get_drvdata(upd_pdev);
upd_wcss = upd_rproc->priv;
if (upd_rproc->state == RPROC_OFFLINE)
continue;
upd_rproc->state = RPROC_CRASHED;
upd_wcss->state = WCSS_CRASHED;
/* stop the userpd rproc*/
ret = rproc_stop(upd_rproc, true);
if (ret)
dev_err(&upd_pdev->dev, "failed to stop %s\n",
upd_rproc->name);
upd_rproc->state = RPROC_SUSPENDED;
}
}
if (wcss->need_mem_protection) {
ret = qcom_scm_pas_shutdown(MPD_WCNSS_PAS_ID);
if (ret) {
dev_err(wcss->dev, "not able to shutdown\n");
return ret;
}
goto pas_done;
}
if (wcss->requires_force_stop) {
ret = qcom_q6v5_request_stop(&wcss->q6);
if (ret == -ETIMEDOUT) {
dev_err(wcss->dev, "timed out on wait\n");
return ret;
}
}
/* Q6 Power down */
ret = q6_powerdown(wcss);
if (ret)
return ret;
pas_done:
qcom_q6v5_unprepare(&wcss->q6);
return 0;
}
static int wcss_pcie_pd_stop(struct rproc *rproc)
{
struct q6_wcss *wcss = rproc->priv;
int ret = 0;
struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
if (rproc->state != RPROC_CRASHED) {
ret = qcom_q6v5_request_stop(&wcss->q6);
if (ret) {
dev_err(&rproc->dev, "ahb pd not stopped\n");
return ret;
}
}
/*Shut down rootpd, if userpd not crashed*/
if (rproc->state != RPROC_CRASHED)
rproc_shutdown(rpd_rproc);
return ret;
}
static int wcss_ahb_pd_stop(struct rproc *rproc)
{
struct q6_wcss *wcss = rproc->priv;
int ret;
struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
if (rproc->state != RPROC_CRASHED) {
ret = qcom_q6v5_request_stop(&wcss->q6);
if (ret) {
dev_err(&rproc->dev, "ahb pd not stopped\n");
return ret;
}
}
if (wcss->need_mem_protection) {
ret = qti_scm_int_radio_powerdown(MPD_WCNSS_PAS_ID);
if (ret) {
dev_err(wcss->dev, "failed to power down ahb pd\n");
return ret;
}
goto shut_dn_rpd;
}
/* WCSS powerdown */
ret = q6_wcss_powerdown(wcss);
if (ret) {
dev_err(wcss->dev, "failed to power down ahb pd wcss %d\n",
ret);
return ret;
}
/*Assert ce reset*/
reset_control_assert(wcss->ce_reset);
mdelay(2);
/*Disable AHB_S clock*/
clk_disable_unprepare(wcss->gcc_abhs_cbcr);
/*Disable ACMT clock*/
clk_disable_unprepare(wcss->acmt_clk);
/*Disable AXI_M clock*/
clk_disable_unprepare(wcss->gcc_axim_cbcr);
/* Deassert WCSS reset */
reset_control_deassert(wcss->wcss_reset);
shut_dn_rpd:
if (rproc->state != RPROC_CRASHED)
rproc_shutdown(rpd_rproc);
return ret;
}
static void *q6_wcss_da_to_va(struct rproc *rproc, u64 da, int len)
{
struct q6_wcss *wcss = rproc->priv;
int offset;
offset = da - wcss->mem_reloc;
if (offset < 0 || offset + len > wcss->mem_size)
return NULL;
return wcss->mem_region + offset;
}
static int share_bootargs_to_q6(struct device *dev)
{
int ret;
u32 smem_id, rd_val;
const char *key = "qcom,bootargs_smem";
size_t size;
u16 cnt, tmp, version;
void *ptr;
u8 *bootargs_arr;
struct device_node *np = dev->of_node;
ret = of_property_read_u32(np, key, &smem_id);
if (ret) {
pr_err("failed to get smem id\n");
return ret;
}
ret = qcom_smem_alloc(REMOTE_PID, smem_id,
Q6_BOOT_ARGS_SMEM_SIZE);
if (ret && ret != -EEXIST) {
pr_err("failed to allocate q6 bootargs smem segment\n");
return ret;
}
ptr = qcom_smem_get(REMOTE_PID, smem_id, &size);
if (IS_ERR(ptr)) {
pr_err("Unable to acquire smp2p item(%d) ret:%ld\n",
smem_id, PTR_ERR(ptr));
return PTR_ERR(ptr);
}
/*Version*/
version = VERSION;
memcpy_toio(ptr, &version, sizeof(version));
ptr += sizeof(version);
cnt = ret = of_property_count_u32_elems(np, "boot-args");
if (ret < 0) {
pr_err("failed to read boot args ret:%d\n", ret);
return ret;
}
/* No of elements */
memcpy_toio(ptr, &cnt, sizeof(u16));
ptr += sizeof(u16);
bootargs_arr = kzalloc(cnt, GFP_KERNEL);
if (!bootargs_arr) {
pr_err("failed to allocate memory\n");
return PTR_ERR(bootargs_arr);
}
for (tmp = 0; tmp < cnt; tmp++) {
ret = of_property_read_u32_index(np, "boot-args", tmp, &rd_val);
if (ret) {
pr_err("failed to read boot args\n");
kfree(bootargs_arr);
return ret;
}
bootargs_arr[tmp] = (u8)rd_val;
}
/* Copy bootargs */
memcpy_toio(ptr, bootargs_arr, cnt);
ptr += (cnt);
of_node_put(np);
kfree(bootargs_arr);
return ret;
}
static int q6_wcss_load(struct rproc *rproc, const struct firmware *fw)
{
struct q6_wcss *wcss = rproc->priv;
const struct firmware *m3_fw;
int ret;
struct device *dev = wcss->dev;
const char *m3_fw_name;
struct device_node *upd_np;
struct platform_device *upd_pdev;
/* Share boot args to Q6 remote processor */
ret = share_bootargs_to_q6(wcss->dev);
if (ret) {
dev_err(wcss->dev,
"boot args sharing with q6 failed %d\n",
ret);
return ret;
}
/* load m3 firmware of userpd's */
for_each_available_child_of_node(wcss->dev->of_node, upd_np) {
if (strstr(upd_np->name, "pd") == NULL)
continue;
upd_pdev = of_find_device_by_node(upd_np);
of_property_read_string(upd_np, "m3_firmware",
&m3_fw_name);
if (m3_fw_name) {
ret = request_firmware(&m3_fw, m3_fw_name,
&upd_pdev->dev);
if (ret)
continue;
ret = qcom_mdt_load_no_init(wcss->dev, m3_fw,
m3_fw_name, 0,
wcss->mem_region, wcss->mem_phys,
wcss->mem_size, &wcss->mem_reloc);
release_firmware(m3_fw);
if (ret) {
dev_err(wcss->dev,
"can't load m3_fw.bXX ret:%d\n", ret);
return ret;
}
}
}
of_property_read_string(dev->of_node, "m3_firmware", &m3_fw_name);
if (m3_fw_name) {
ret = request_firmware(&m3_fw, m3_fw_name,
wcss->dev);
if (ret)
goto skip_m3;
ret = qcom_mdt_load_no_init(wcss->dev, m3_fw,
m3_fw_name, 0,
wcss->mem_region, wcss->mem_phys,
wcss->mem_size, &wcss->mem_reloc);
release_firmware(m3_fw);
if (ret) {
dev_err(wcss->dev, "can't load m3_fw.bXX ret:%d\n",
ret);
return ret;
}
}
skip_m3:
if (wcss->need_mem_protection)
return qcom_mdt_load(wcss->dev, fw, rproc->firmware,
MPD_WCNSS_PAS_ID, wcss->mem_region,
wcss->mem_phys, wcss->mem_size,
&wcss->mem_reloc);
return qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
0, wcss->mem_region, wcss->mem_phys,
wcss->mem_size, &wcss->mem_reloc);
}
static int wcss_ahb_pcie_pd_load(struct rproc *rproc, const struct firmware *fw)
{
struct q6_wcss *wcss = rproc->priv;
struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
int ret;
/* Don't boot rootpd rproc incase userpd receovering after crash */
if (wcss->state != WCSS_RESTARTING) {
/*Boot rootpd rproc*/
ret = rproc_boot(rpd_rproc);
if (ret)
return ret;
}
if (wcss->need_mem_protection)
return qcom_mdt_load(wcss->dev, fw, rproc->firmware,
MPD_WCNSS_PAS_ID, wcss->mem_region,
wcss->mem_phys, wcss->mem_size,
&wcss->mem_reloc);
return qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
0, wcss->mem_region, wcss->mem_phys,
wcss->mem_size, &wcss->mem_reloc);
}
int q6_wcss_register_dump_segments(struct rproc *rproc,
const struct firmware *fw)
{
/*
* Registering custom coredump function with a dummy dump segment
* as the dump regions are taken care by the dump function itself
*/
return rproc_coredump_add_custom_segment(rproc, 0, 0, crashdump_init,
NULL);
}
static const struct rproc_ops wcss_pcie_ipq5018_ops = {
.start = wcss_pcie_pd_start,
.stop = wcss_pcie_pd_stop,
.load = wcss_ahb_pcie_pd_load,
.parse_fw = q6_wcss_register_dump_segments,
};
static const struct rproc_ops wcss_ahb_ipq5018_ops = {
.start = wcss_ahb_pd_start,
.stop = wcss_ahb_pd_stop,
.load = wcss_ahb_pcie_pd_load,
.parse_fw = q6_wcss_register_dump_segments,
};
static const struct rproc_ops q6_wcss_ipq5018_ops = {
.start = q6_wcss_start,
.stop = q6_wcss_stop,
.da_to_va = q6_wcss_da_to_va,
.load = q6_wcss_load,
.get_boot_addr = rproc_elf_get_boot_addr,
.parse_fw = q6_wcss_register_dump_segments,
};
static int q6_wcss_init_reset(struct q6_wcss *wcss,
const struct wcss_data *desc)
{
struct device *dev = wcss->dev;
if (desc->aon_reset_required) {
wcss->wcss_aon_reset =
devm_reset_control_get_shared(dev, "wcss_aon_reset");
if (IS_ERR(wcss->wcss_aon_reset)) {
dev_err(wcss->dev,
"fail to acquire wcss_aon_reset, ret:%ld\n",
PTR_ERR(wcss->wcss_aon_reset));
return PTR_ERR(wcss->wcss_aon_reset);
}
}
if (desc->wcss_reset_required) {
wcss->wcss_reset =
devm_reset_control_get_exclusive(dev, "wcss_reset");
if (IS_ERR(wcss->wcss_reset)) {
dev_err(wcss->dev, "unable to acquire wcss_reset\n");
return PTR_ERR(wcss->wcss_reset);
}
}
if (desc->wcss_q6_reset_required) {
wcss->wcss_q6_reset =
devm_reset_control_get_exclusive(dev, "wcss_q6_reset");
if (IS_ERR(wcss->wcss_q6_reset)) {
dev_err(wcss->dev, "unable to acquire wcss_q6_reset\n");
return PTR_ERR(wcss->wcss_q6_reset);
}
}
if (desc->ce_reset_required) {
wcss->ce_reset = devm_reset_control_get_exclusive(dev,
"ce_reset");
if (IS_ERR(wcss->ce_reset)) {
dev_err(wcss->dev, "unable to acquire ce_reset\n");
return PTR_ERR(wcss->ce_reset);
}
}
return 0;
}
static int q6_init_mmio(struct q6_wcss *wcss,
struct platform_device *pdev)
{
struct resource *res;
u32 i;
for (i = 0; i < pdev->num_resources; i++) {
struct resource *r = &pdev->resource[i];
if (unlikely(!r->name))
continue;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6");
if (!res) {
dev_err(&pdev->dev, "qdsp6 resource not available\n");
return -EINVAL;
}
wcss->reg_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (IS_ERR(wcss->reg_base))
return PTR_ERR(wcss->reg_base);
if (wcss->is_q6v6) {
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "tcsr-msip");
wcss->tcsr_msip_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(wcss->tcsr_msip_base))
return PTR_ERR(wcss->tcsr_msip_base);
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "tcsr-q6");
wcss->tcsr_q6_base = devm_ioremap_resource(&pdev->dev,
res);
if (IS_ERR(wcss->tcsr_q6_base))
return PTR_ERR(wcss->tcsr_q6_base);
}
return 0;
}
static int q6_wcss_init_mmio(struct q6_wcss *wcss,
struct platform_device *pdev)
{
unsigned int halt_reg[MAX_HALT_REG] = {0};
struct device_node *syscon;
struct resource *res;
int ret;
if (wcss->version == Q6_IPQ) {
ret = q6_init_mmio(wcss, pdev);
if (ret)
return ret;
} else if (wcss->version == WCSS_AHB_IPQ) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb");
wcss->rmb_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(wcss->rmb_base))
return PTR_ERR(wcss->rmb_base);
}
syscon = of_parse_phandle(pdev->dev.of_node,
"qcom,halt-regs", 0);
if (!syscon) {
dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n");
return -EINVAL;
}
wcss->halt_map = syscon_node_to_regmap(syscon);
of_node_put(syscon);
if (IS_ERR(wcss->halt_map))
return PTR_ERR(wcss->halt_map);
ret = of_property_read_variable_u32_array(pdev->dev.of_node,
"qcom,halt-regs",
halt_reg, 0,
MAX_HALT_REG);
if (ret < 0) {
dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n");
return -EINVAL;
}
wcss->halt_q6 = halt_reg[1];
wcss->halt_wcss = halt_reg[2];
wcss->halt_nc = halt_reg[3];
return 0;
}
static int q6_alloc_memory_region(struct q6_wcss *wcss)
{
struct reserved_mem *rmem = NULL;
struct device_node *node;
struct device *dev = wcss->dev;
if (wcss->version == Q6_IPQ) {
node = of_parse_phandle(dev->of_node, "memory-region", 0);
if (node)
rmem = of_reserved_mem_lookup(node);
of_node_put(node);
if (!rmem) {
dev_err(dev, "unable to acquire memory-region\n");
return -EINVAL;
}
} else {
struct rproc *rpd_rproc = dev_get_drvdata(dev->parent);
struct q6_wcss *rpd_wcss = rpd_rproc->priv;
wcss->mem_phys = rpd_wcss->mem_phys;
wcss->mem_reloc = rpd_wcss->mem_reloc;
wcss->mem_size = rpd_wcss->mem_size;
wcss->mem_region = rpd_wcss->mem_region;
return 0;
}
wcss->mem_phys = rmem->base;
wcss->mem_reloc = rmem->base;
wcss->mem_size = rmem->size;
wcss->mem_region = devm_ioremap_wc(dev, wcss->mem_phys, wcss->mem_size);
if (!wcss->mem_region) {
dev_err(dev, "unable to map memory region: %pa+%pa\n",
&rmem->base, &rmem->size);
return -EBUSY;
}
return 0;
}
static int ipq5018_init_wcss_clock(struct q6_wcss *wcss)
{
int ret;
wcss->gcc_abhs_cbcr = devm_clk_get(wcss->dev, "gcc_wcss_ahb_s_clk");
if (IS_ERR(wcss->gcc_abhs_cbcr)) {
ret = PTR_ERR(wcss->gcc_abhs_cbcr);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get gcc ahbs clock");
return PTR_ERR(wcss->gcc_abhs_cbcr);
}
wcss->acmt_clk = devm_clk_get(wcss->dev, "gcc_wcss_acmt_clk");
if (IS_ERR(wcss->acmt_clk)) {
ret = PTR_ERR(wcss->acmt_clk);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get acmt clk\n");
return PTR_ERR(wcss->acmt_clk);
}
wcss->gcc_axim_cbcr = devm_clk_get(wcss->dev, "gcc_wcss_axi_m_clk");
if (IS_ERR(wcss->gcc_axim_cbcr)) {
ret = PTR_ERR(wcss->gcc_axim_cbcr);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get gcc axim clock\n");
return PTR_ERR(wcss->gcc_axim_cbcr);
}
return 0;
}
static int ipq5018_init_q6_clock(struct q6_wcss *wcss)
{
int ret;
wcss->ahbs_cbcr = devm_clk_get(wcss->dev, "gcc_q6_ahb_s_clk");
if (IS_ERR(wcss->ahbs_cbcr)) {
ret = PTR_ERR(wcss->ahbs_cbcr);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get ahbs clock\n");
return PTR_ERR(wcss->ahbs_cbcr);
}
wcss->qdsp6ss_abhm_cbcr = devm_clk_get(wcss->dev, "gcc_q6_ahb_clk");
if (IS_ERR(wcss->qdsp6ss_abhm_cbcr)) {
ret = PTR_ERR(wcss->qdsp6ss_abhm_cbcr);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get csr cbcr clk\n");
return PTR_ERR(wcss->qdsp6ss_abhm_cbcr);
}
wcss->eachb_clk = devm_clk_get(wcss->dev, "gcc_wcss_ecahb_clk");
if (IS_ERR(wcss->eachb_clk)) {
ret = PTR_ERR(wcss->eachb_clk);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get ahbs_cbcr clk\n");
return PTR_ERR(wcss->eachb_clk);
}
wcss->gcc_axim2_clk = devm_clk_get(wcss->dev, "gcc_q6_axim2_clk");
if (IS_ERR(wcss->gcc_axim2_clk)) {
ret = PTR_ERR(wcss->gcc_axim2_clk);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get gcc_axim2_clk\n");
return PTR_ERR(wcss->gcc_axim2_clk);
}
wcss->axmis_clk = devm_clk_get(wcss->dev, "gcc_q6_axis_clk");
if (IS_ERR(wcss->axmis_clk)) {
ret = PTR_ERR(wcss->axmis_clk);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get axmis clk\n");
return PTR_ERR(wcss->axmis_clk);
}
wcss->axi_s_clk = devm_clk_get(wcss->dev, "gcc_wcss_axi_s_clk");
if (IS_ERR(wcss->axi_s_clk)) {
ret = PTR_ERR(wcss->axi_s_clk);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to get axi_s_clk clk\n");
return PTR_ERR(wcss->axi_s_clk);
}
wcss->qdsp6ss_axim_cbcr = devm_clk_get(wcss->dev, "gcc_q6_axim_clk");
if (IS_ERR(wcss->qdsp6ss_axim_cbcr)) {
ret = PTR_ERR(wcss->qdsp6ss_axim_cbcr);
if (ret != -EPROBE_DEFER)
dev_err(wcss->dev, "failed to axim clk\n");
return PTR_ERR(wcss->qdsp6ss_axim_cbcr);
}
return 0;
}
static int q6_get_inbound_irq(struct qcom_q6v5 *q6,
struct platform_device *pdev, const char *int_name,
irqreturn_t (*handler)(int irq, void *data))
{
int ret, irq;
char *interrupt, *tmp = (char *)int_name;
struct q6_wcss *wcss = q6->rproc->priv;
irq = ret = platform_get_irq_byname(pdev, int_name);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev,
"failed to retrieve %s IRQ: %d\n",
int_name, ret);
return ret;
}
if (!strcmp(int_name, "fatal"))
q6->fatal_irq = irq;
else if (!strcmp(int_name, "stop-ack")) {
q6->stop_irq = irq;
tmp = "stop_ack";
} else if (!strcmp(int_name, "ready"))
q6->ready_irq = irq;
else if (!strcmp(int_name, "handover"))
q6->handover_irq = irq;
else if (!strcmp(int_name, "spawn-ack")) {
q6->spawn_irq = irq;
tmp = "spawn_ack";
} else {
dev_err(&pdev->dev, "unknown interrupt\n");
return -EINVAL;
}
interrupt = devm_kzalloc(&pdev->dev, BUF_SIZE, GFP_KERNEL);
if (!interrupt)
return -ENOMEM;
snprintf(interrupt, BUF_SIZE, "q6v5_wcss_userpd%d", wcss->pd_asid);
strlcat(interrupt, "_", BUF_SIZE);
strlcat(interrupt, tmp, BUF_SIZE);
ret = devm_request_threaded_irq(&pdev->dev, irq,
NULL, handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
interrupt, q6);
if (ret) {
dev_err(&pdev->dev, "failed to acquire %s irq\n",
interrupt);
return ret;
}
return 0;
}
static int q6_get_outbound_irq(struct qcom_q6v5 *q6,
struct platform_device *pdev, const char *int_name)
{
struct qcom_smem_state *tmp_state;
unsigned bit;
tmp_state = qcom_smem_state_get(&pdev->dev, int_name, &bit);
if (IS_ERR(tmp_state)) {
dev_err(&pdev->dev, "failed to acquire %s state\n", int_name);
return PTR_ERR(tmp_state);
}
if (!strcmp(int_name, "stop")) {
q6->state = tmp_state;
q6->stop_bit = bit;
} else if (!strcmp(int_name, "spawn")) {
q6->spawn_state = tmp_state;
q6->spawn_bit = bit;
}
return 0;
}
static int ipq5018_init_irq(struct qcom_q6v5 *q6,
struct platform_device *pdev,
struct rproc *rproc, int crash_reason,
void (*handover)(struct qcom_q6v5 *q6))
{
int ret;
q6->rproc = rproc;
q6->dev = &pdev->dev;
q6->crash_reason = crash_reason;
q6->handover = handover;
init_completion(&q6->start_done);
init_completion(&q6->stop_done);
init_completion(&q6->spawn_done);
ret = q6_get_inbound_irq(q6, pdev, "fatal",
q6v5_fatal_interrupt);
if (ret)
return ret;
ret = q6_get_inbound_irq(q6, pdev, "ready",
q6v5_ready_interrupt);
if (ret)
return ret;
ret = q6_get_inbound_irq(q6, pdev, "stop-ack",
q6v5_stop_interrupt);
if (ret)
return ret;
ret = q6_get_inbound_irq(q6, pdev, "spawn-ack",
q6v5_spawn_interrupt);
if (ret)
return ret;
ret = q6_get_outbound_irq(q6, pdev, "stop");
if (ret)
return ret;
ret = q6_get_outbound_irq(q6, pdev, "spawn");
if (ret)
return ret;
return 0;
}
static int q6_wcss_probe(struct platform_device *pdev)
{
const struct wcss_data *desc;
struct q6_wcss *wcss;
struct rproc *rproc;
int ret;
char *subdev_name;
desc = of_device_get_match_data(&pdev->dev);
if (!desc)
return -EINVAL;
if (desc->need_mem_protection && !qcom_scm_is_available())
return -EPROBE_DEFER;
rproc = rproc_alloc(&pdev->dev, pdev->name, desc->ops,
desc->q6_firmware_name, sizeof(*wcss));
if (!rproc) {
dev_err(&pdev->dev, "failed to allocate rproc\n");
return -ENOMEM;
}
wcss = rproc->priv;
wcss->dev = &pdev->dev;
wcss->version = desc->version;
wcss->requires_force_stop = desc->requires_force_stop;
wcss->need_mem_protection = desc->need_mem_protection;
wcss->reset_cmd_id = desc->reset_cmd_id;
wcss->is_q6v6 = desc->is_q6v6;
if (wcss->version != WCSS_PCIE_IPQ) {
ret = q6_wcss_init_mmio(wcss, pdev);
if (ret)
goto free_rproc;
}
ret = q6_alloc_memory_region(wcss);
if (ret)
goto free_rproc;
if (desc->init_clock) {
ret = desc->init_clock(wcss);
if (ret)
goto free_rproc;
}
ret = q6_wcss_init_reset(wcss, desc);
if (ret)
goto free_rproc;
wcss->pd_asid = qcom_get_pd_asid(wcss->dev->of_node);
if (desc->init_irq)
ret = desc->init_irq(&wcss->q6, pdev, rproc,
desc->crash_reason_smem, NULL);
else
ret = qcom_q6v5_init(&wcss->q6, pdev, rproc,
desc->crash_reason_smem, NULL);
if (ret)
goto free_rproc;
if (desc->glink_subdev_required)
qcom_add_glink_subdev(rproc, &wcss->glink_subdev);
subdev_name = (char *)(desc->ssr_name ? desc->ssr_name : pdev->name);
qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, subdev_name);
if (desc->sysmon_name)
wcss->sysmon = qcom_add_sysmon_subdev(rproc,
desc->sysmon_name,
desc->ssctl_id);
rproc->auto_boot = desc->need_auto_boot;
ret = rproc_add(rproc);
if (ret)
goto free_rproc;
platform_set_drvdata(pdev, rproc);
if (wcss->version == Q6_IPQ) {
ret = of_platform_populate(wcss->dev->of_node, NULL,
NULL, wcss->dev);
if (ret) {
dev_err(&pdev->dev, "failed to populate wcss pd nodes\n");
goto free_rproc;
}
}
return 0;
free_rproc:
rproc_free(rproc);
return ret;
}
static int q6_wcss_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
rproc_del(rproc);
rproc_free(rproc);
return 0;
}
static const struct wcss_data q6_ipq5018_res_init = {
.init_clock = ipq5018_init_q6_clock,
.q6_firmware_name = "IPQ5018/q6_fw.mdt",
.crash_reason_smem = WCSS_CRASH_REASON,
.aon_reset_required = true,
.wcss_q6_reset_required = true,
.ssr_name = "q6wcss",
.reset_cmd_id = 0x14,
.ops = &q6_wcss_ipq5018_ops,
.need_mem_protection = true,
.need_auto_boot = false,
.is_q6v6 = true,
.glink_subdev_required = true,
};
static const struct wcss_data wcss_ahb_ipq5018_res_init = {
.init_clock = ipq5018_init_wcss_clock,
.init_irq = ipq5018_init_irq,
.q6_firmware_name = "IPQ5018/q6_fw.mdt",
.crash_reason_smem = WCSS_CRASH_REASON,
.aon_reset_required = true,
.wcss_reset_required = true,
.ce_reset_required = true,
.ops = &wcss_ahb_ipq5018_ops,
.need_mem_protection = true,
.need_auto_boot = false,
.is_q6v6 = true,
.version = WCSS_AHB_IPQ,
};
static const struct wcss_data wcss_pcie_ipq5018_res_init = {
.init_irq = ipq5018_init_irq,
.q6_firmware_name = "IPQ5018/q6_fw.mdt",
.crash_reason_smem = WCSS_CRASH_REASON,
.ops = &wcss_pcie_ipq5018_ops,
.need_mem_protection = true,
.need_auto_boot = false,
.is_q6v6 = true,
.version = WCSS_PCIE_IPQ,
};
static const struct of_device_id q6_wcss_of_match[] = {
{ .compatible = "qcom,ipq5018-q6-mpd", .data = &q6_ipq5018_res_init },
{ .compatible = "qcom,ipq5018-wcss-ahb-mpd",
.data = &wcss_ahb_ipq5018_res_init },
{ .compatible = "qcom,ipq5018-wcss-pcie-mpd",
.data = &wcss_pcie_ipq5018_res_init },
{ },
};
MODULE_DEVICE_TABLE(of, q6_wcss_of_match);
static struct platform_driver q6_wcss_driver = {
.probe = q6_wcss_probe,
.remove = q6_wcss_remove,
.driver = {
.name = "qcom-q6-mpd",
.of_match_table = q6_wcss_of_match,
},
};
module_platform_driver(q6_wcss_driver);
module_param(debug_wcss, int, 0644);
MODULE_DESCRIPTION("Hexagon WCSS Multipd Peripheral Image Loader");
MODULE_LICENSE("GPL v2");