summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/arm/msm/qcom,llcc.txt19
-rw-r--r--Documentation/devicetree/bindings/firmware/qcom,scm.txt33
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/edac/Kconfig14
-rw-r--r--drivers/edac/Makefile1
-rw-r--r--drivers/edac/qcom_edac.c414
-rw-r--r--drivers/firmware/qcom_scm.c74
-rw-r--r--drivers/soc/qcom/Kconfig21
-rw-r--r--drivers/soc/qcom/apr.c6
-rw-r--r--drivers/soc/qcom/llcc-slice.c74
-rw-r--r--drivers/soc/qcom/qcom-geni-se.c41
-rw-r--r--drivers/soc/qcom/rmtfs_mem.c5
-rw-r--r--drivers/soc/qcom/rpmh-rsc.c2
-rw-r--r--drivers/soc/qcom/smem.c174
-rw-r--r--drivers/soc/qcom/spm.c3
-rw-r--r--drivers/soc/qcom/wcnss_ctrl.c2
-rw-r--r--include/linux/qcom-geni-se.h13
-rw-r--r--include/linux/soc/qcom/llcc-qcom.h30
18 files changed, 732 insertions, 202 deletions
diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,llcc.txt b/Documentation/devicetree/bindings/arm/msm/qcom,llcc.txt
index 5e85749262ae..eaee06b2d8f2 100644
--- a/Documentation/devicetree/bindings/arm/msm/qcom,llcc.txt
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,llcc.txt
@@ -16,11 +16,26 @@ Properties:
- reg:
Usage: required
Value Type: <prop-encoded-array>
- Definition: Start address and the the size of the register region.
+ Definition: The first element specifies the llcc base start address and
+ the size of the register region. The second element specifies
+ the llcc broadcast base address and size of the register region.
+
+- reg-names:
+ Usage: required
+ Value Type: <stringlist>
+ Definition: Register region names. Must be "llcc_base", "llcc_broadcast_base".
+
+- interrupts:
+ Usage: required
+ Definition: The interrupt is associated with the llcc edac device.
+ It's used for llcc cache single and double bit error detection
+ and reporting.
Example:
cache-controller@1100000 {
compatible = "qcom,sdm845-llcc";
- reg = <0x1100000 0x250000>;
+ reg = <0x1100000 0x200000>, <0x1300000 0x50000> ;
+ reg-names = "llcc_base", "llcc_broadcast_base";
+ interrupts = <GIC_SPI 582 IRQ_TYPE_LEVEL_HIGH>;
};
diff --git a/Documentation/devicetree/bindings/firmware/qcom,scm.txt b/Documentation/devicetree/bindings/firmware/qcom,scm.txt
index fcf6979c0b6d..41f133a4e2fa 100644
--- a/Documentation/devicetree/bindings/firmware/qcom,scm.txt
+++ b/Documentation/devicetree/bindings/firmware/qcom,scm.txt
@@ -7,16 +7,23 @@ assorted actions.
Required properties:
- compatible: must contain one of the following:
- * "qcom,scm-apq8064" for APQ8064 platforms
- * "qcom,scm-msm8660" for MSM8660 platforms
- * "qcom,scm-msm8690" for MSM8690 platforms
- * "qcom,scm-msm8996" for MSM8996 platforms
- * "qcom,scm-ipq4019" for IPQ4019 platforms
- * "qcom,scm" for later processors (MSM8916, APQ8084, MSM8974, etc)
-- clocks: One to three clocks may be required based on compatible.
- * No clock required for "qcom,scm-msm8996", "qcom,scm-ipq4019"
- * Only core clock required for "qcom,scm-apq8064", "qcom,scm-msm8660", and "qcom,scm-msm8960"
- * Core, iface, and bus clocks required for "qcom,scm"
+ * "qcom,scm-apq8064"
+ * "qcom,scm-apq8084"
+ * "qcom,scm-msm8660"
+ * "qcom,scm-msm8916"
+ * "qcom,scm-msm8960"
+ * "qcom,scm-msm8974"
+ * "qcom,scm-msm8996"
+ * "qcom,scm-msm8998"
+ * "qcom,scm-ipq4019"
+ * "qcom,scm-sdm845"
+ and:
+ * "qcom,scm"
+- clocks: Specifies clocks needed by the SCM interface, if any:
+ * core clock required for "qcom,scm-apq8064", "qcom,scm-msm8660" and
+ "qcom,scm-msm8960"
+ * core, iface and bus clocks required for "qcom,scm-apq8084",
+ "qcom,scm-msm8916" and "qcom,scm-msm8974"
- clock-names: Must contain "core" for the core clock, "iface" for the interface
clock and "bus" for the bus clock per the requirements of the compatible.
- qcom,dload-mode: phandle to the TCSR hardware block and offset of the
@@ -26,8 +33,10 @@ Example for MSM8916:
firmware {
scm {
- compatible = "qcom,scm";
- clocks = <&gcc GCC_CRYPTO_CLK> , <&gcc GCC_CRYPTO_AXI_CLK>, <&gcc GCC_CRYPTO_AHB_CLK>;
+ compatible = "qcom,msm8916", "qcom,scm";
+ clocks = <&gcc GCC_CRYPTO_CLK> ,
+ <&gcc GCC_CRYPTO_AXI_CLK>,
+ <&gcc GCC_CRYPTO_AHB_CLK>;
clock-names = "core", "bus", "iface";
};
};
diff --git a/MAINTAINERS b/MAINTAINERS
index d870cb57c887..9e536acfd5f8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5347,6 +5347,14 @@ L: linux-edac@vger.kernel.org
S: Maintained
F: drivers/edac/ti_edac.c
+EDAC-QCOM
+M: Channagoud Kadabi <ckadabi@codeaurora.org>
+M: Venkata Narendra Kumar Gutta <vnkgutta@codeaurora.org>
+L: linux-arm-msm@vger.kernel.org
+L: linux-edac@vger.kernel.org
+S: Maintained
+F: drivers/edac/qcom_edac.c
+
EDIROL UA-101/UA-1000 DRIVER
M: Clemens Ladisch <clemens@ladisch.de>
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 57304b2e989f..df9467eef32a 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -460,4 +460,18 @@ config EDAC_TI
Support for error detection and correction on the
TI SoCs.
+config EDAC_QCOM
+ tristate "QCOM EDAC Controller"
+ depends on ARCH_QCOM && QCOM_LLCC
+ help
+ Support for error detection and correction on the
+ Qualcomm Technologies, Inc. SoCs.
+
+ This driver reports Single Bit Errors (SBEs) and Double Bit Errors (DBEs).
+ As of now, it supports error reporting for Last Level Cache Controller (LLCC)
+ of Tag RAM and Data RAM.
+
+ For debugging issues having to do with stability and overall system
+ health, you should probably say 'Y' here.
+
endif # EDAC
diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 02b43a7d8c3e..716096d08ea0 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -77,3 +77,4 @@ obj-$(CONFIG_EDAC_ALTERA) += altera_edac.o
obj-$(CONFIG_EDAC_SYNOPSYS) += synopsys_edac.o
obj-$(CONFIG_EDAC_XGENE) += xgene_edac.o
obj-$(CONFIG_EDAC_TI) += ti_edac.o
+obj-$(CONFIG_EDAC_QCOM) += qcom_edac.o
diff --git a/drivers/edac/qcom_edac.c b/drivers/edac/qcom_edac.c
new file mode 100644
index 000000000000..82bd775124f2
--- /dev/null
+++ b/drivers/edac/qcom_edac.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/edac.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/soc/qcom/llcc-qcom.h>
+
+#include "edac_mc.h"
+#include "edac_device.h"
+
+#define EDAC_LLCC "qcom_llcc"
+
+#define LLCC_ERP_PANIC_ON_UE 1
+
+#define TRP_SYN_REG_CNT 6
+#define DRP_SYN_REG_CNT 8
+
+#define LLCC_COMMON_STATUS0 0x0003000c
+#define LLCC_LB_CNT_MASK GENMASK(31, 28)
+#define LLCC_LB_CNT_SHIFT 28
+
+/* Single & double bit syndrome register offsets */
+#define TRP_ECC_SB_ERR_SYN0 0x0002304c
+#define TRP_ECC_DB_ERR_SYN0 0x00020370
+#define DRP_ECC_SB_ERR_SYN0 0x0004204c
+#define DRP_ECC_DB_ERR_SYN0 0x00042070
+
+/* Error register offsets */
+#define TRP_ECC_ERROR_STATUS1 0x00020348
+#define TRP_ECC_ERROR_STATUS0 0x00020344
+#define DRP_ECC_ERROR_STATUS1 0x00042048
+#define DRP_ECC_ERROR_STATUS0 0x00042044
+
+/* TRP, DRP interrupt register offsets */
+#define DRP_INTERRUPT_STATUS 0x00041000
+#define TRP_INTERRUPT_0_STATUS 0x00020480
+#define DRP_INTERRUPT_CLEAR 0x00041008
+#define DRP_ECC_ERROR_CNTR_CLEAR 0x00040004
+#define TRP_INTERRUPT_0_CLEAR 0x00020484
+#define TRP_ECC_ERROR_CNTR_CLEAR 0x00020440
+
+/* Mask and shift macros */
+#define ECC_DB_ERR_COUNT_MASK GENMASK(4, 0)
+#define ECC_DB_ERR_WAYS_MASK GENMASK(31, 16)
+#define ECC_DB_ERR_WAYS_SHIFT BIT(4)
+
+#define ECC_SB_ERR_COUNT_MASK GENMASK(23, 16)
+#define ECC_SB_ERR_COUNT_SHIFT BIT(4)
+#define ECC_SB_ERR_WAYS_MASK GENMASK(15, 0)
+
+#define SB_ECC_ERROR BIT(0)
+#define DB_ECC_ERROR BIT(1)
+
+#define DRP_TRP_INT_CLEAR GENMASK(1, 0)
+#define DRP_TRP_CNT_CLEAR GENMASK(1, 0)
+
+/* Config registers offsets*/
+#define DRP_ECC_ERROR_CFG 0x00040000
+
+/* Tag RAM, Data RAM interrupt register offsets */
+#define CMN_INTERRUPT_0_ENABLE 0x0003001c
+#define CMN_INTERRUPT_2_ENABLE 0x0003003c
+#define TRP_INTERRUPT_0_ENABLE 0x00020488
+#define DRP_INTERRUPT_ENABLE 0x0004100c
+
+#define SB_ERROR_THRESHOLD 0x1
+#define SB_ERROR_THRESHOLD_SHIFT 24
+#define SB_DB_TRP_INTERRUPT_ENABLE 0x3
+#define TRP0_INTERRUPT_ENABLE 0x1
+#define DRP0_INTERRUPT_ENABLE BIT(6)
+#define SB_DB_DRP_INTERRUPT_ENABLE 0x3
+
+enum {
+ LLCC_DRAM_CE = 0,
+ LLCC_DRAM_UE,
+ LLCC_TRAM_CE,
+ LLCC_TRAM_UE,
+};
+
+static const struct llcc_edac_reg_data edac_reg_data[] = {
+ [LLCC_DRAM_CE] = {
+ .name = "DRAM Single-bit",
+ .synd_reg = DRP_ECC_SB_ERR_SYN0,
+ .count_status_reg = DRP_ECC_ERROR_STATUS1,
+ .ways_status_reg = DRP_ECC_ERROR_STATUS0,
+ .reg_cnt = DRP_SYN_REG_CNT,
+ .count_mask = ECC_SB_ERR_COUNT_MASK,
+ .ways_mask = ECC_SB_ERR_WAYS_MASK,
+ .count_shift = ECC_SB_ERR_COUNT_SHIFT,
+ },
+ [LLCC_DRAM_UE] = {
+ .name = "DRAM Double-bit",
+ .synd_reg = DRP_ECC_DB_ERR_SYN0,
+ .count_status_reg = DRP_ECC_ERROR_STATUS1,
+ .ways_status_reg = DRP_ECC_ERROR_STATUS0,
+ .reg_cnt = DRP_SYN_REG_CNT,
+ .count_mask = ECC_DB_ERR_COUNT_MASK,
+ .ways_mask = ECC_DB_ERR_WAYS_MASK,
+ .ways_shift = ECC_DB_ERR_WAYS_SHIFT,
+ },
+ [LLCC_TRAM_CE] = {
+ .name = "TRAM Single-bit",
+ .synd_reg = TRP_ECC_SB_ERR_SYN0,
+ .count_status_reg = TRP_ECC_ERROR_STATUS1,
+ .ways_status_reg = TRP_ECC_ERROR_STATUS0,
+ .reg_cnt = TRP_SYN_REG_CNT,
+ .count_mask = ECC_SB_ERR_COUNT_MASK,
+ .ways_mask = ECC_SB_ERR_WAYS_MASK,
+ .count_shift = ECC_SB_ERR_COUNT_SHIFT,
+ },
+ [LLCC_TRAM_UE] = {
+ .name = "TRAM Double-bit",
+ .synd_reg = TRP_ECC_DB_ERR_SYN0,
+ .count_status_reg = TRP_ECC_ERROR_STATUS1,
+ .ways_status_reg = TRP_ECC_ERROR_STATUS0,
+ .reg_cnt = TRP_SYN_REG_CNT,
+ .count_mask = ECC_DB_ERR_COUNT_MASK,
+ .ways_mask = ECC_DB_ERR_WAYS_MASK,
+ .ways_shift = ECC_DB_ERR_WAYS_SHIFT,
+ },
+};
+
+static int qcom_llcc_core_setup(struct regmap *llcc_bcast_regmap)
+{
+ u32 sb_err_threshold;
+ int ret;
+
+ /*
+ * Configure interrupt enable registers such that Tag, Data RAM related
+ * interrupts are propagated to interrupt controller for servicing
+ */
+ ret = regmap_update_bits(llcc_bcast_regmap, CMN_INTERRUPT_2_ENABLE,
+ TRP0_INTERRUPT_ENABLE,
+ TRP0_INTERRUPT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(llcc_bcast_regmap, TRP_INTERRUPT_0_ENABLE,
+ SB_DB_TRP_INTERRUPT_ENABLE,
+ SB_DB_TRP_INTERRUPT_ENABLE);
+ if (ret)
+ return ret;
+
+ sb_err_threshold = (SB_ERROR_THRESHOLD << SB_ERROR_THRESHOLD_SHIFT);
+ ret = regmap_write(llcc_bcast_regmap, DRP_ECC_ERROR_CFG,
+ sb_err_threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(llcc_bcast_regmap, CMN_INTERRUPT_2_ENABLE,
+ DRP0_INTERRUPT_ENABLE,
+ DRP0_INTERRUPT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(llcc_bcast_regmap, DRP_INTERRUPT_ENABLE,
+ SB_DB_DRP_INTERRUPT_ENABLE);
+ return ret;
+}
+
+/* Clear the error interrupt and counter registers */
+static int
+qcom_llcc_clear_error_status(int err_type, struct llcc_drv_data *drv)
+{
+ int ret = 0;
+
+ switch (err_type) {
+ case LLCC_DRAM_CE:
+ case LLCC_DRAM_UE:
+ ret = regmap_write(drv->bcast_regmap, DRP_INTERRUPT_CLEAR,
+ DRP_TRP_INT_CLEAR);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(drv->bcast_regmap, DRP_ECC_ERROR_CNTR_CLEAR,
+ DRP_TRP_CNT_CLEAR);
+ if (ret)
+ return ret;
+ break;
+ case LLCC_TRAM_CE:
+ case LLCC_TRAM_UE:
+ ret = regmap_write(drv->bcast_regmap, TRP_INTERRUPT_0_CLEAR,
+ DRP_TRP_INT_CLEAR);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(drv->bcast_regmap, TRP_ECC_ERROR_CNTR_CLEAR,
+ DRP_TRP_CNT_CLEAR);
+ if (ret)
+ return ret;
+ break;
+ default:
+ ret = -EINVAL;
+ edac_printk(KERN_CRIT, EDAC_LLCC, "Unexpected error type: %d\n",
+ err_type);
+ }
+ return ret;
+}
+
+/* Dump Syndrome registers data for Tag RAM, Data RAM bit errors*/
+static int
+dump_syn_reg_values(struct llcc_drv_data *drv, u32 bank, int err_type)
+{
+ struct llcc_edac_reg_data reg_data = edac_reg_data[err_type];
+ int err_cnt, err_ways, ret, i;
+ u32 synd_reg, synd_val;
+
+ for (i = 0; i < reg_data.reg_cnt; i++) {
+ synd_reg = reg_data.synd_reg + (i * 4);
+ ret = regmap_read(drv->regmap, drv->offsets[bank] + synd_reg,
+ &synd_val);
+ if (ret)
+ goto clear;
+
+ edac_printk(KERN_CRIT, EDAC_LLCC, "%s: ECC_SYN%d: 0x%8x\n",
+ reg_data.name, i, synd_val);
+ }
+
+ ret = regmap_read(drv->regmap,
+ drv->offsets[bank] + reg_data.count_status_reg,
+ &err_cnt);
+ if (ret)
+ goto clear;
+
+ err_cnt &= reg_data.count_mask;
+ err_cnt >>= reg_data.count_shift;
+ edac_printk(KERN_CRIT, EDAC_LLCC, "%s: Error count: 0x%4x\n",
+ reg_data.name, err_cnt);
+
+ ret = regmap_read(drv->regmap,
+ drv->offsets[bank] + reg_data.ways_status_reg,
+ &err_ways);
+ if (ret)
+ goto clear;
+
+ err_ways &= reg_data.ways_mask;
+ err_ways >>= reg_data.ways_shift;
+
+ edac_printk(KERN_CRIT, EDAC_LLCC, "%s: Error ways: 0x%4x\n",
+ reg_data.name, err_ways);
+
+clear:
+ return qcom_llcc_clear_error_status(err_type, drv);
+}
+
+static int
+dump_syn_reg(struct edac_device_ctl_info *edev_ctl, int err_type, u32 bank)
+{
+ struct llcc_drv_data *drv = edev_ctl->pvt_info;
+ int ret;
+
+ ret = dump_syn_reg_values(drv, bank, err_type);
+ if (ret)
+ return ret;
+
+ switch (err_type) {
+ case LLCC_DRAM_CE:
+ edac_device_handle_ce(edev_ctl, 0, bank,
+ "LLCC Data RAM correctable Error");
+ break;
+ case LLCC_DRAM_UE:
+ edac_device_handle_ue(edev_ctl, 0, bank,
+ "LLCC Data RAM uncorrectable Error");
+ break;
+ case LLCC_TRAM_CE:
+ edac_device_handle_ce(edev_ctl, 0, bank,
+ "LLCC Tag RAM correctable Error");
+ break;
+ case LLCC_TRAM_UE:
+ edac_device_handle_ue(edev_ctl, 0, bank,
+ "LLCC Tag RAM uncorrectable Error");
+ break;
+ default:
+ ret = -EINVAL;
+ edac_printk(KERN_CRIT, EDAC_LLCC, "Unexpected error type: %d\n",
+ err_type);
+ }
+
+ return ret;
+}
+
+static irqreturn_t
+llcc_ecc_irq_handler(int irq, void *edev_ctl)
+{
+ struct edac_device_ctl_info *edac_dev_ctl = edev_ctl;
+ struct llcc_drv_data *drv = edac_dev_ctl->pvt_info;
+ irqreturn_t irq_rc = IRQ_NONE;
+ u32 drp_error, trp_error, i;
+ bool irq_handled;
+ int ret;
+
+ /* Iterate over the banks and look for Tag RAM or Data RAM errors */
+ for (i = 0; i < drv->num_banks; i++) {
+ ret = regmap_read(drv->regmap,
+ drv->offsets[i] + DRP_INTERRUPT_STATUS,
+ &drp_error);
+
+ if (!ret && (drp_error & SB_ECC_ERROR)) {
+ edac_printk(KERN_CRIT, EDAC_LLCC,
+ "Single Bit Error detected in Data RAM\n");
+ ret = dump_syn_reg(edev_ctl, LLCC_DRAM_CE, i);
+ } else if (!ret && (drp_error & DB_ECC_ERROR)) {
+ edac_printk(KERN_CRIT, EDAC_LLCC,
+ "Double Bit Error detected in Data RAM\n");
+ ret = dump_syn_reg(edev_ctl, LLCC_DRAM_UE, i);
+ }
+ if (!ret)
+ irq_handled = true;
+
+ ret = regmap_read(drv->regmap,
+ drv->offsets[i] + TRP_INTERRUPT_0_STATUS,
+ &trp_error);
+
+ if (!ret && (trp_error & SB_ECC_ERROR)) {
+ edac_printk(KERN_CRIT, EDAC_LLCC,
+ "Single Bit Error detected in Tag RAM\n");
+ ret = dump_syn_reg(edev_ctl, LLCC_TRAM_CE, i);
+ } else if (!ret && (trp_error & DB_ECC_ERROR)) {
+ edac_printk(KERN_CRIT, EDAC_LLCC,
+ "Double Bit Error detected in Tag RAM\n");
+ ret = dump_syn_reg(edev_ctl, LLCC_TRAM_UE, i);
+ }
+ if (!ret)
+ irq_handled = true;
+ }
+
+ if (irq_handled)
+ irq_rc = IRQ_HANDLED;
+
+ return irq_rc;
+}
+
+static int qcom_llcc_edac_probe(struct platform_device *pdev)
+{
+ struct llcc_drv_data *llcc_driv_data = pdev->dev.platform_data;
+ struct edac_device_ctl_info *edev_ctl;
+ struct device *dev = &pdev->dev;
+ int ecc_irq;
+ int rc;
+
+ rc = qcom_llcc_core_setup(llcc_driv_data->bcast_regmap);
+ if (rc)
+ return rc;
+
+ /* Allocate edac control info */
+ edev_ctl = edac_device_alloc_ctl_info(0, "qcom-llcc", 1, "bank",
+ llcc_driv_data->num_banks, 1,
+ NULL, 0,
+ edac_device_alloc_index());
+
+ if (!edev_ctl)
+ return -ENOMEM;
+
+ edev_ctl->dev = dev;
+ edev_ctl->mod_name = dev_name(dev);
+ edev_ctl->dev_name = dev_name(dev);
+ edev_ctl->ctl_name = "llcc";
+ edev_ctl->panic_on_ue = LLCC_ERP_PANIC_ON_UE;
+ edev_ctl->pvt_info = llcc_driv_data;
+
+ rc = edac_device_add_device(edev_ctl);
+ if (rc)
+ goto out_mem;
+
+ platform_set_drvdata(pdev, edev_ctl);
+
+ /* Request for ecc irq */
+ ecc_irq = llcc_driv_data->ecc_irq;
+ if (ecc_irq < 0) {
+ rc = -ENODEV;
+ goto out_dev;
+ }
+ rc = devm_request_irq(dev, ecc_irq, llcc_ecc_irq_handler,
+ IRQF_TRIGGER_HIGH, "llcc_ecc", edev_ctl);
+ if (rc)
+ goto out_dev;
+
+ return rc;
+
+out_dev:
+ edac_device_del_device(edev_ctl->dev);
+out_mem:
+ edac_device_free_ctl_info(edev_ctl);
+
+ return rc;
+}
+
+static int qcom_llcc_edac_remove(struct platform_device *pdev)
+{
+ struct edac_device_ctl_info *edev_ctl = dev_get_drvdata(&pdev->dev);
+
+ edac_device_del_device(edev_ctl->dev);
+ edac_device_free_ctl_info(edev_ctl);
+
+ return 0;
+}
+
+static struct platform_driver qcom_llcc_edac_driver = {
+ .probe = qcom_llcc_edac_probe,
+ .remove = qcom_llcc_edac_remove,
+ .driver = {
+ .name = "qcom_llcc_edac",
+ },
+};
+module_platform_driver(qcom_llcc_edac_driver);
+
+MODULE_DESCRIPTION("QCOM EDAC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
index e778af766fae..af4eee86919d 100644
--- a/drivers/firmware/qcom_scm.c
+++ b/drivers/firmware/qcom_scm.c
@@ -525,34 +525,44 @@ static int qcom_scm_probe(struct platform_device *pdev)
return ret;
clks = (unsigned long)of_device_get_match_data(&pdev->dev);
- if (clks & SCM_HAS_CORE_CLK) {
- scm->core_clk = devm_clk_get(&pdev->dev, "core");
- if (IS_ERR(scm->core_clk)) {
- if (PTR_ERR(scm->core_clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "failed to acquire core clk\n");
+
+ scm->core_clk = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(scm->core_clk)) {
+ if (PTR_ERR(scm->core_clk) == -EPROBE_DEFER)
+ return PTR_ERR(scm->core_clk);
+
+ if (clks & SCM_HAS_CORE_CLK) {
+ dev_err(&pdev->dev, "failed to acquire core clk\n");
return PTR_ERR(scm->core_clk);
}
+
+ scm->core_clk = NULL;
}
- if (clks & SCM_HAS_IFACE_CLK) {
- scm->iface_clk = devm_clk_get(&pdev->dev, "iface");
- if (IS_ERR(scm->iface_clk)) {
- if (PTR_ERR(scm->iface_clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "failed to acquire iface clk\n");
+ scm->iface_clk = devm_clk_get(&pdev->dev, "iface");
+ if (IS_ERR(scm->iface_clk)) {
+ if (PTR_ERR(scm->iface_clk) == -EPROBE_DEFER)
+ return PTR_ERR(scm->iface_clk);
+
+ if (clks & SCM_HAS_IFACE_CLK) {
+ dev_err(&pdev->dev, "failed to acquire iface clk\n");
return PTR_ERR(scm->iface_clk);
}
+
+ scm->iface_clk = NULL;
}
- if (clks & SCM_HAS_BUS_CLK) {
- scm->bus_clk = devm_clk_get(&pdev->dev, "bus");
- if (IS_ERR(scm->bus_clk)) {
- if (PTR_ERR(scm->bus_clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "failed to acquire bus clk\n");
+ scm->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(scm->bus_clk)) {
+ if (PTR_ERR(scm->bus_clk) == -EPROBE_DEFER)
+ return PTR_ERR(scm->bus_clk);
+
+ if (clks & SCM_HAS_BUS_CLK) {
+ dev_err(&pdev->dev, "failed to acquire bus clk\n");
return PTR_ERR(scm->bus_clk);
}
+
+ scm->bus_clk = NULL;
}
scm->reset.ops = &qcom_scm_pas_reset_ops;
@@ -594,23 +604,23 @@ static const struct of_device_id qcom_scm_dt_match[] = {
{ .compatible = "qcom,scm-apq8064",
/* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */
},
- { .compatible = "qcom,scm-msm8660",
- .data = (void *) SCM_HAS_CORE_CLK,
- },
- { .compatible = "qcom,scm-msm8960",
- .data = (void *) SCM_HAS_CORE_CLK,
- },
- { .compatible = "qcom,scm-msm8996",
- .data = NULL, /* no clocks */
+ { .compatible = "qcom,scm-apq8084", .data = (void *)(SCM_HAS_CORE_CLK |
+ SCM_HAS_IFACE_CLK |
+ SCM_HAS_BUS_CLK)
},
- { .compatible = "qcom,scm-ipq4019",
- .data = NULL, /* no clocks */
+ { .compatible = "qcom,scm-ipq4019" },
+ { .compatible = "qcom,scm-msm8660", .data = (void *) SCM_HAS_CORE_CLK },
+ { .compatible = "qcom,scm-msm8960", .data = (void *) SCM_HAS_CORE_CLK },
+ { .compatible = "qcom,scm-msm8916", .data = (void *)(SCM_HAS_CORE_CLK |
+ SCM_HAS_IFACE_CLK |
+ SCM_HAS_BUS_CLK)
},
- { .compatible = "qcom,scm",
- .data = (void *)(SCM_HAS_CORE_CLK
- | SCM_HAS_IFACE_CLK
- | SCM_HAS_BUS_CLK),
+ { .compatible = "qcom,scm-msm8974", .data = (void *)(SCM_HAS_CORE_CLK |
+ SCM_HAS_IFACE_CLK |
+ SCM_HAS_BUS_CLK)
},
+ { .compatible = "qcom,scm-msm8996" },
+ { .compatible = "qcom,scm" },
{}
};
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index ba79b609aca2..684cb51694d1 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -33,7 +33,7 @@ config QCOM_GLINK_SSR
config QCOM_GSBI
tristate "QCOM General Serial Bus Interface"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
select MFD_SYSCON
help
Say y here to enable GSBI support. The GSBI provides control
@@ -42,7 +42,7 @@ config QCOM_GSBI
config QCOM_LLCC
tristate "Qualcomm Technologies, Inc. LLCC driver"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
help
Qualcomm Technologies, Inc. platform specific
Last Level Cache Controller(LLCC) driver. This provides interfaces
@@ -73,7 +73,8 @@ config QCOM_PM
config QCOM_QMI_HELPERS
tristate
- depends on ARCH_QCOM && NET
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on NET
help
Helper library for handling QMI encoded messages. QMI encoded
messages are used in communication between the majority of QRTR
@@ -94,7 +95,7 @@ config QCOM_RMTFS_MEM
config QCOM_RPMH
bool "Qualcomm RPM-Hardened (RPMH) Communication"
- depends on ARCH_QCOM && ARM64 && OF || COMPILE_TEST
+ depends on ARCH_QCOM && ARM64 || COMPILE_TEST
help
Support for communication with the hardened-RPM blocks in
Qualcomm Technologies Inc (QTI) SoCs. RPMH communication uses an
@@ -104,7 +105,7 @@ config QCOM_RPMH
config QCOM_SMEM
tristate "Qualcomm Shared Memory Manager (SMEM)"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
depends on HWSPINLOCK
help
Say y here to enable support for the Qualcomm Shared Memory Manager.
@@ -113,8 +114,8 @@ config QCOM_SMEM
config QCOM_SMD_RPM
tristate "Qualcomm Resource Power Manager (RPM) over SMD"
- depends on ARCH_QCOM
- depends on RPMSG && OF
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on RPMSG
help
If you say yes to this option, support will be included for the
Resource Power Manager system found in the Qualcomm 8974 based
@@ -134,6 +135,7 @@ config QCOM_SMP2P
depends on MAILBOX
depends on QCOM_SMEM
select QCOM_SMEM_STATE
+ select IRQ_DOMAIN
help
Say yes here to support the Qualcomm Shared Memory Point to Point
protocol.
@@ -142,13 +144,14 @@ config QCOM_SMSM
tristate "Qualcomm Shared Memory State Machine"
depends on QCOM_SMEM
select QCOM_SMEM_STATE
+ select IRQ_DOMAIN
help
Say yes here to support the Qualcomm Shared Memory State Machine.
The state machine is represented by bits in shared memory.
config QCOM_WCNSS_CTRL
tristate "Qualcomm WCNSS control driver"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
depends on RPMSG
help
Client driver for the WCNSS_CTRL SMD channel, used to download nv
@@ -156,7 +159,7 @@ config QCOM_WCNSS_CTRL
config QCOM_APR
tristate "Qualcomm APR Bus (Asynchronous Packet Router)"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
depends on RPMSG
help
Enable APR IPC protocol support between
diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c
index 57af8a537332..716762d59c1f 100644
--- a/drivers/soc/qcom/apr.c
+++ b/drivers/soc/qcom/apr.c
@@ -87,7 +87,7 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
}
if (hdr->pkt_size < APR_HDR_SIZE || hdr->pkt_size != len) {
- dev_err(apr->dev, "APR: Wrong paket size\n");
+ dev_err(apr->dev, "APR: Wrong packet size\n");
return -EINVAL;
}
@@ -219,9 +219,9 @@ static int apr_add_device(struct device *dev, struct device_node *np,
adev->domain_id = id->domain_id;
adev->version = id->svc_version;
if (np)
- strncpy(adev->name, np->name, APR_NAME_SIZE);
+ strscpy(adev->name, np->name, APR_NAME_SIZE);
else
- strncpy(adev->name, id->name, APR_NAME_SIZE);
+ strscpy(adev->name, id->name, APR_NAME_SIZE);
dev_set_name(&adev->dev, "aprsvc:%s:%x:%x", adev->name,
id->domain_id, id->svc_id);
diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c
index 54063a31132f..192ca761b2cb 100644
--- a/drivers/soc/qcom/llcc-slice.c
+++ b/drivers/soc/qcom/llcc-slice.c
@@ -13,6 +13,7 @@
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
+#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/soc/qcom/llcc-qcom.h>
@@ -106,22 +107,24 @@ static int llcc_update_act_ctrl(u32 sid,
u32 slice_status;
int ret;
- act_ctrl_reg = drv_data->bcast_off + LLCC_TRP_ACT_CTRLn(sid);
- status_reg = drv_data->bcast_off + LLCC_TRP_STATUSn(sid);
+ act_ctrl_reg = LLCC_TRP_ACT_CTRLn(sid);
+ status_reg = LLCC_TRP_STATUSn(sid);
/* Set the ACTIVE trigger */
act_ctrl_reg_val |= ACT_CTRL_ACT_TRIG;
- ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val);
+ ret = regmap_write(drv_data->bcast_regmap, act_ctrl_reg,
+ act_ctrl_reg_val);
if (ret)
return ret;
/* Clear the ACTIVE trigger */
act_ctrl_reg_val &= ~ACT_CTRL_ACT_TRIG;
- ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val);
+ ret = regmap_write(drv_data->bcast_regmap, act_ctrl_reg,
+ act_ctrl_reg_val);
if (ret)
return ret;
- ret = regmap_read_poll_timeout(drv_data->regmap, status_reg,
+ ret = regmap_read_poll_timeout(drv_data->bcast_regmap, status_reg,
slice_status, !(slice_status & status),
0, LLCC_STATUS_READ_DELAY);
return ret;
@@ -223,19 +226,16 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev)
u32 attr0_val;
u32 max_cap_cacheline;
u32 sz;
- int ret;
+ int ret = 0;
const struct llcc_slice_config *llcc_table;
struct llcc_slice_desc desc;
- u32 bcast_off = drv_data->bcast_off;
sz = drv_data->cfg_size;
llcc_table = drv_data->cfg;
for (i = 0; i < sz; i++) {
- attr1_cfg = bcast_off +
- LLCC_TRP_ATTR1_CFGn(llcc_table[i].slice_id);
- attr0_cfg = bcast_off +
- LLCC_TRP_ATTR0_CFGn(llcc_table[i].slice_id);
+ attr1_cfg = LLCC_TRP_ATTR1_CFGn(llcc_table[i].slice_id);
+ attr0_cfg = LLCC_TRP_ATTR0_CFGn(llcc_table[i].slice_id);
attr1_val = llcc_table[i].cache_mode;
attr1_val |= llcc_table[i].probe_target_ways <<
@@ -260,10 +260,12 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev)
attr0_val = llcc_table[i].res_ways & ATTR0_RES_WAYS_MASK;
attr0_val |= llcc_table[i].bonus_ways << ATTR0_BONUS_WAYS_SHIFT;
- ret = regmap_write(drv_data->regmap, attr1_cfg, attr1_val);
+ ret = regmap_write(drv_data->bcast_regmap, attr1_cfg,
+ attr1_val);
if (ret)
return ret;
- ret = regmap_write(drv_data->regmap, attr0_cfg, attr0_val);
+ ret = regmap_write(drv_data->bcast_regmap, attr0_cfg,
+ attr0_val);
if (ret)
return ret;
if (llcc_table[i].activate_on_init) {
@@ -279,24 +281,37 @@ int qcom_llcc_probe(struct platform_device *pdev,
{
u32 num_banks;
struct device *dev = &pdev->dev;
- struct resource *res;
- void __iomem *base;
+ struct resource *llcc_banks_res, *llcc_bcast_res;
+ void __iomem *llcc_banks_base, *llcc_bcast_base;
int ret, i;
+ struct platform_device *llcc_edac;
drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(base))
- return PTR_ERR(base);
+ llcc_banks_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "llcc_base");
+ llcc_banks_base = devm_ioremap_resource(&pdev->dev, llcc_banks_res);
+ if (IS_ERR(llcc_banks_base))
+ return PTR_ERR(llcc_banks_base);
- drv_data->regmap = devm_regmap_init_mmio(dev, base,
- &llcc_regmap_config);
+ drv_data->regmap = devm_regmap_init_mmio(dev, llcc_banks_base,
+ &llcc_regmap_config);
if (IS_ERR(drv_data->regmap))
return PTR_ERR(drv_data->regmap);
+ llcc_bcast_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "llcc_broadcast_base");
+ llcc_bcast_base = devm_ioremap_resource(&pdev->dev, llcc_bcast_res);
+ if (IS_ERR(llcc_bcast_base))
+ return PTR_ERR(llcc_bcast_base);
+
+ drv_data->bcast_regmap = devm_regmap_init_mmio(dev, llcc_bcast_base,
+ &llcc_regmap_config);
+ if (IS_ERR(drv_data->bcast_regmap))
+ return PTR_ERR(drv_data->bcast_regmap);
+
ret = regmap_read(drv_data->regmap, LLCC_COMMON_STATUS0,
&num_banks);
if (ret)
@@ -318,8 +333,6 @@ int qcom_llcc_probe(struct platform_device *pdev,
for (i = 0; i < num_banks; i++)
drv_data->offsets[i] = i * BANK_OFFSET_STRIDE;
- drv_data->bcast_off = num_banks * BANK_OFFSET_STRIDE;
-
drv_data->bitmap = devm_kcalloc(dev,
BITS_TO_LONGS(drv_data->max_slices), sizeof(unsigned long),
GFP_KERNEL);
@@ -331,7 +344,20 @@ int qcom_llcc_probe(struct platform_device *pdev,
mutex_init(&drv_data->lock);
platform_set_drvdata(pdev, drv_data);
- return qcom_llcc_cfg_program(pdev);
+ ret = qcom_llcc_cfg_program(pdev);
+ if (ret)
+ return ret;
+
+ drv_data->ecc_irq = platform_get_irq(pdev, 0);
+ if (drv_data->ecc_irq >= 0) {
+ llcc_edac = platform_device_register_data(&pdev->dev,
+ "qcom_llcc_edac", -1, drv_data,
+ sizeof(*drv_data));
+ if (IS_ERR(llcc_edac))
+ dev_err(dev, "Failed to register llcc edac driver\n");
+ }
+
+ return ret;
}
EXPORT_SYMBOL_GPL(qcom_llcc_probe);
diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c
index feed3db21c10..ee89ffb6dde8 100644
--- a/drivers/soc/qcom/qcom-geni-se.c
+++ b/drivers/soc/qcom/qcom-geni-se.c
@@ -513,7 +513,7 @@ EXPORT_SYMBOL(geni_se_resources_on);
*/
int geni_se_clk_tbl_get(struct geni_se *se, unsigned long **tbl)
{
- unsigned long freq = 0;
+ long freq = 0;
int i;
if (se->clk_perf_tbl) {
@@ -529,7 +529,7 @@ int geni_se_clk_tbl_get(struct geni_se *se, unsigned long **tbl)
for (i = 0; i < MAX_CLK_PERF_LEVEL; i++) {
freq = clk_round_rate(se->clk, freq + 1);
- if (!freq || freq == se->clk_perf_tbl[i - 1])
+ if (freq <= 0 || freq == se->clk_perf_tbl[i - 1])
break;
se->clk_perf_tbl[i] = freq;
}
@@ -544,16 +544,17 @@ EXPORT_SYMBOL(geni_se_clk_tbl_get);
* @se: Pointer to the concerned serial engine.
* @req_freq: Requested clock frequency.
* @index: Index of the resultant frequency in the table.
- * @res_freq: Resultant frequency which matches or is closer to the
- * requested frequency.
+ * @res_freq: Resultant frequency of the source clock.
* @exact: Flag to indicate exact multiple requirement of the requested
* frequency.
*
- * This function is called by the protocol drivers to determine the matching
- * or exact multiple of the requested frequency, as provided by the serial
- * engine clock in order to meet the performance requirements. If there is
- * no matching or exact multiple of the requested frequency found, then it
- * selects the closest floor frequency, if exact flag is not set.
+ * This function is called by the protocol drivers to determine the best match
+ * of the requested frequency as provided by the serial engine clock in order
+ * to meet the performance requirements.
+ *
+ * If we return success:
+ * - if @exact is true then @res_freq / <an_integer> == @req_freq
+ * - if @exact is false then @res_freq / <an_integer> <= @req_freq
*
* Return: 0 on success, standard Linux error codes on failure.
*/
@@ -564,6 +565,9 @@ int geni_se_clk_freq_match(struct geni_se *se, unsigned long req_freq,
unsigned long *tbl;
int num_clk_levels;
int i;
+ unsigned long best_delta;
+ unsigned long new_delta;
+ unsigned int divider;
num_clk_levels = geni_se_clk_tbl_get(se, &tbl);
if (num_clk_levels < 0)
@@ -572,18 +576,21 @@ int geni_se_clk_freq_match(struct geni_se *se, unsigned long req_freq,
if (num_clk_levels == 0)
return -EINVAL;
- *res_freq = 0;
+ best_delta = ULONG_MAX;
for (i = 0; i < num_clk_levels; i++) {
- if (!(tbl[i] % req_freq)) {
+ divider = DIV_ROUND_UP(tbl[i], req_freq);
+ new_delta = req_freq - tbl[i] / divider;
+ if (new_delta < best_delta) {
+ /* We have a new best! */
*index = i;
*res_freq = tbl[i];
- return 0;
- }
- if (!(*res_freq) || ((tbl[i] > *res_freq) &&
- (tbl[i] < req_freq))) {
- *index = i;
- *res_freq = tbl[i];
+ /* If the new best is exact then we're done */
+ if (new_delta == 0)
+ return 0;
+
+ /* Record how close we got */
+ best_delta = new_delta;
}
}
diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c
index 8a3678c2e83c..97bb5989aa21 100644
--- a/drivers/soc/qcom/rmtfs_mem.c
+++ b/drivers/soc/qcom/rmtfs_mem.c
@@ -212,6 +212,11 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to parse qcom,vmid\n");
goto remove_cdev;
} else if (!ret) {
+ if (!qcom_scm_is_available()) {
+ ret = -EPROBE_DEFER;
+ goto remove_cdev;
+ }
+
perms[0].vmid = QCOM_SCM_VMID_HLOS;
perms[0].perm = QCOM_SCM_PERM_RW;
perms[1].vmid = vmid;
diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
index ee75da66d64b..75bd9a83aef0 100644
--- a/drivers/soc/qcom/rpmh-rsc.c
+++ b/drivers/soc/qcom/rpmh-rsc.c
@@ -121,6 +121,7 @@ static int tcs_invalidate(struct rsc_drv *drv, int type)
return -EAGAIN;
}
write_tcs_reg_sync(drv, RSC_DRV_CMD_ENABLE, m, 0);
+ write_tcs_reg_sync(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, m, 0);
}
bitmap_zero(tcs->slots, MAX_TCS_SLOTS);
spin_unlock(&tcs->lock);
@@ -239,6 +240,7 @@ static irqreturn_t tcs_tx_done(int irq, void *p)
skip:
/* Reclaim the TCS */
write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0);
+ write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, i, 0);
write_tcs_reg(drv, RSC_DRV_IRQ_CLEAR, 0, BIT(i));
spin_lock(&drv->lock);
clear_bit(i, drv->tcs_in_use);
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index bf4bd71ab53f..f80d040601fd 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -18,6 +18,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
+#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
@@ -277,7 +278,7 @@ struct qcom_smem {
u32 item_count;
unsigned num_regions;
- struct smem_region regions[0];
+ struct smem_region regions[];
};
static void *
@@ -489,7 +490,7 @@ static void *qcom_smem_get_global(struct qcom_smem *smem,
size_t *size)
{
struct smem_header *header;
- struct smem_region *area;
+ struct smem_region *region;
struct smem_global_entry *entry;
u32 aux_base;
unsigned i;
@@ -502,12 +503,12 @@ static void *qcom_smem_get_global(struct qcom_smem *smem,
aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK;
for (i = 0; i < smem->num_regions; i++) {
- area = &smem->regions[i];
+ region = &smem->regions[i];
- if (area->aux_base == aux_base || !aux_base) {
+ if (region->aux_base == aux_base || !aux_base) {
if (size != NULL)
*size = le32_to_cpu(entry->size);
- return area->virt_base + le32_to_cpu(entry->offset);
+ return region->virt_base + le32_to_cpu(entry->offset);
}
}
@@ -722,12 +723,59 @@ static u32 qcom_smem_get_item_count(struct qcom_smem *smem)
return le16_to_cpu(info->num_items);
}
+/*
+ * Validate the partition header for a partition whose partition
+ * table entry is supplied. Returns a pointer to its header if
+ * valid, or a null pointer otherwise.
+ */
+static struct smem_partition_header *
+qcom_smem_partition_header(struct qcom_smem *smem,
+ struct smem_ptable_entry *entry, u16 host0, u16 host1)
+{
+ struct smem_partition_header *header;
+ u32 size;
+
+ header = smem->regions[0].virt_base + le32_to_cpu(entry->offset);
+
+ if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) {
+ dev_err(smem->dev, "bad partition magic %02x %02x %02x %02x\n",
+ header->magic[0], header->magic[1],
+ header->magic[2], header->magic[3]);
+ return NULL;
+ }
+
+ if (host0 != le16_to_cpu(header->host0)) {
+ dev_err(smem->dev, "bad host0 (%hu != %hu)\n",
+ host0, le16_to_cpu(header->host0));
+ return NULL;
+ }
+ if (host1 != le16_to_cpu(header->host1)) {
+ dev_err(smem->dev, "bad host1 (%hu != %hu)\n",
+ host1, le16_to_cpu(header->host1));
+ return NULL;
+ }
+
+ size = le32_to_cpu(header->size);
+ if (size != le32_to_cpu(entry->size)) {
+ dev_err(smem->dev, "bad partition size (%u != %u)\n",
+ size, le32_to_cpu(entry->size));
+ return NULL;
+ }
+
+ if (le32_to_cpu(header->offset_free_uncached) > size) {
+ dev_err(smem->dev, "bad partition free uncached (%u > %u)\n",
+ le32_to_cpu(header->offset_free_uncached), size);
+ return NULL;
+ }
+
+ return header;
+}
+
static int qcom_smem_set_global_partition(struct qcom_smem *smem)
{
struct smem_partition_header *header;
struct smem_ptable_entry *entry;
struct smem_ptable *ptable;
- u32 host0, host1, size;
bool found = false;
int i;
@@ -742,10 +790,15 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) {
entry = &ptable->entry[i];
- host0 = le16_to_cpu(entry->host0);
- host1 = le16_to_cpu(entry->host1);
+ if (!le32_to_cpu(entry->offset))
+ continue;
+ if (!le32_to_cpu(entry->size))
+ continue;
+
+ if (le16_to_cpu(entry->host0) != SMEM_GLOBAL_HOST)
+ continue;
- if (host0 == SMEM_GLOBAL_HOST && host0 == host1) {
+ if (le16_to_cpu(entry->host1) == SMEM_GLOBAL_HOST) {
found = true;
break;
}
@@ -756,36 +809,10 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
return -EINVAL;
}
- if (!le32_to_cpu(entry->offset) || !le32_to_cpu(entry->size)) {
- dev_err(smem->dev, "Invalid entry for global partition\n");
+ header = qcom_smem_partition_header(smem, entry,
+ SMEM_GLOBAL_HOST, SMEM_GLOBAL_HOST);
+ if (!header)
return -EINVAL;
- }
-
- header = smem->regions[0].virt_base + le32_to_cpu(entry->offset);
- host0 = le16_to_cpu(header->host0);
- host1 = le16_to_cpu(header->host1);
-
- if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) {
- dev_err(smem->dev, "Global partition has invalid magic\n");
- return -EINVAL;
- }
-
- if (host0 != SMEM_GLOBAL_HOST && host1 != SMEM_GLOBAL_HOST) {
- dev_err(smem->dev, "Global partition hosts are invalid\n");
- return -EINVAL;
- }
-
- if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) {
- dev_err(smem->dev, "Global partition has invalid size\n");
- return -EINVAL;
- }
-
- size = le32_to_cpu(header->offset_free_uncached);
- if (size > le32_to_cpu(header->size)) {
- dev_err(smem->dev,
- "Global partition has invalid free pointer\n");
- return -EINVAL;
- }
smem->global_partition = header;
smem->global_cacheline = le32_to_cpu(entry->cacheline);
@@ -793,14 +820,14 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
return 0;
}
-static int qcom_smem_enumerate_partitions(struct qcom_smem *smem,
- unsigned int local_host)
+static int
+qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host)
{
struct smem_partition_header *header;
struct smem_ptable_entry *entry;
struct smem_ptable *ptable;
unsigned int remote_host;
- u32 host0, host1;
+ u16 host0, host1;
int i;
ptable = qcom_smem_get_ptable(smem);
@@ -809,71 +836,33 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem,
for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) {
entry = &ptable->entry[i];
- host0 = le16_to_cpu(entry->host0);
- host1 = le16_to_cpu(entry->host1);
-
- if (host0 != local_host && host1 != local_host)
- continue;
-
if (!le32_to_cpu(entry->offset))
continue;
-
if (!le32_to_cpu(entry->size))
continue;
+ host0 = le16_to_cpu(entry->host0);
+ host1 = le16_to_cpu(entry->host1);
if (host0 == local_host)
remote_host = host1;
- else
+ else if (host1 == local_host)
remote_host = host0;
+ else
+ continue;
if (remote_host >= SMEM_HOST_COUNT) {
- dev_err(smem->dev,
- "Invalid remote host %d\n",
- remote_host);
+ dev_err(smem->dev, "bad host %hu\n", remote_host);
return -EINVAL;
}
if (smem->partitions[remote_host]) {
- dev_err(smem->dev,
- "Already found a partition for host %d\n",
- remote_host);
- return -EINVAL;
- }
-
- header = smem->regions[0].virt_base + le32_to_cpu(entry->offset);
- host0 = le16_to_cpu(header->host0);
- host1 = le16_to_cpu(header->host1);
-
- if (memcmp(header->magic, SMEM_PART_MAGIC,
- sizeof(header->magic))) {
- dev_err(smem->dev,
- "Partition %d has invalid magic\n", i);
+ dev_err(smem->dev, "duplicate host %hu\n", remote_host);
return -EINVAL;
}
- if (host0 != local_host && host1 != local_host) {
- dev_err(smem->dev,
- "Partition %d hosts are invalid\n", i);
+ header = qcom_smem_partition_header(smem, entry, host0, host1);
+ if (!header)
return -EINVAL;
- }
-
- if (host0 != remote_host && host1 != remote_host) {
- dev_err(smem->dev,
- "Partition %d hosts are invalid\n", i);
- return -EINVAL;
- }
-
- if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) {
- dev_err(smem->dev,
- "Partition %d has invalid size\n", i);
- return -EINVAL;
- }
-
- if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) {
- dev_err(smem->dev,
- "Partition %d has invalid free pointer\n", i);
- return -EINVAL;
- }
smem->partitions[remote_host] = header;
smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline);
@@ -887,6 +876,7 @@ static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev,
{
struct device_node *np;
struct resource r;
+ resource_size_t size;
int ret;
np = of_parse_phandle(dev->of_node, name, 0);
@@ -899,12 +889,13 @@ static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev,
of_node_put(np);
if (ret)
return ret;
+ size = resource_size(&r);
- smem->regions[i].aux_base = (u32)r.start;
- smem->regions[i].size = resource_size(&r);
- smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, resource_size(&r));
+ smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, size);
if (!smem->regions[i].virt_base)
return -ENOMEM;
+ smem->regions[i].aux_base = (u32)r.start;
+ smem->regions[i].size = size;
return 0;
}
@@ -962,6 +953,7 @@ static int qcom_smem_probe(struct platform_device *pdev)
return -EINVAL;
}
+ BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT);
ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS);
if (ret < 0 && ret != -ENOENT)
return ret;
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
index f9d7a85b2822..53807e839664 100644
--- a/drivers/soc/qcom/spm.c
+++ b/drivers/soc/qcom/spm.c
@@ -219,6 +219,9 @@ static int __init qcom_cpuidle_init(struct device_node *cpu_node, int cpu)
cpumask_t mask;
bool use_scm_power_down = false;
+ if (!qcom_scm_is_available())
+ return -EPROBE_DEFER;
+
for (i = 0; ; i++) {
state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i);
if (!state_node)
diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c
index df3ccb30bc2d..373400dd816d 100644
--- a/drivers/soc/qcom/wcnss_ctrl.c
+++ b/drivers/soc/qcom/wcnss_ctrl.c
@@ -281,7 +281,7 @@ struct rpmsg_endpoint *qcom_wcnss_open_channel(void *wcnss, const char *name, rp
struct rpmsg_channel_info chinfo;
struct wcnss_ctrl *_wcnss = wcnss;
- strncpy(chinfo.name, name, sizeof(chinfo.name));
+ strscpy(chinfo.name, name, sizeof(chinfo.name));
chinfo.src = RPMSG_ADDR_ANY;
chinfo.dst = RPMSG_ADDR_ANY;
diff --git a/include/linux/qcom-geni-se.h b/include/linux/qcom-geni-se.h
index 5d6144977828..3bcd67fd5548 100644
--- a/include/linux/qcom-geni-se.h
+++ b/include/linux/qcom-geni-se.h
@@ -225,19 +225,14 @@ struct geni_se {
#define HW_VER_MINOR_SHFT 16
#define HW_VER_STEP_MASK GENMASK(15, 0)
+#define GENI_SE_VERSION_MAJOR(ver) ((ver & HW_VER_MAJOR_MASK) >> HW_VER_MAJOR_SHFT)
+#define GENI_SE_VERSION_MINOR(ver) ((ver & HW_VER_MINOR_MASK) >> HW_VER_MINOR_SHFT)
+#define GENI_SE_VERSION_STEP(ver) (ver & HW_VER_STEP_MASK)
+
#if IS_ENABLED(CONFIG_QCOM_GENI_SE)
u32 geni_se_get_qup_hw_version(struct geni_se *se);
-#define geni_se_get_wrapper_version(se, major, minor, step) do { \
- u32 ver; \
-\
- ver = geni_se_get_qup_hw_version(se); \
- major = (ver & HW_VER_MAJOR_MASK) >> HW_VER_MAJOR_SHFT; \
- minor = (ver & HW_VER_MINOR_MASK) >> HW_VER_MINOR_SHFT; \
- step = version & HW_VER_STEP_MASK; \
-} while (0)
-
/**
* geni_se_read_proto() - Read the protocol configured for a serial engine
* @se: Pointer to the concerned serial engine.
diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h
index 7e3b9c605ab2..69c285b1c990 100644
--- a/include/linux/soc/qcom/llcc-qcom.h
+++ b/include/linux/soc/qcom/llcc-qcom.h
@@ -70,25 +70,51 @@ struct llcc_slice_config {
/**
* llcc_drv_data - Data associated with the llcc driver
* @regmap: regmap associated with the llcc device
+ * @bcast_regmap: regmap associated with llcc broadcast offset
* @cfg: pointer to the data structure for slice configuration
* @lock: mutex associated with each slice
* @cfg_size: size of the config data table
* @max_slices: max slices as read from device tree
- * @bcast_off: Offset of the broadcast bank
* @num_banks: Number of llcc banks
* @bitmap: Bit map to track the active slice ids
* @offsets: Pointer to the bank offsets array
+ * @ecc_irq: interrupt for llcc cache error detection and reporting
*/
struct llcc_drv_data {
struct regmap *regmap;
+ struct regmap *bcast_regmap;
const struct llcc_slice_config *cfg;
struct mutex lock;
u32 cfg_size;
u32 max_slices;
- u32 bcast_off;
u32 num_banks;
unsigned long *bitmap;
u32 *offsets;
+ int ecc_irq;
+};
+
+/**
+ * llcc_edac_reg_data - llcc edac registers data for each error type
+ * @name: Name of the error
+ * @synd_reg: Syndrome register address
+ * @count_status_reg: Status register address to read the error count
+ * @ways_status_reg: Status register address to read the error ways
+ * @reg_cnt: Number of registers
+ * @count_mask: Mask value to get the error count
+ * @ways_mask: Mask value to get the error ways
+ * @count_shift: Shift value to get the error count
+ * @ways_shift: Shift value to get the error ways
+ */
+struct llcc_edac_reg_data {
+ char *name;
+ u64 synd_reg;
+ u64 count_status_reg;
+ u64 ways_status_reg;
+ u32 reg_cnt;
+ u32 count_mask;
+ u32 ways_mask;
+ u8 count_shift;
+ u8 ways_shift;
};
#if IS_ENABLED(CONFIG_QCOM_LLCC)