[PATCH 2/3] soc: qcom: llcc-qcom: get SCT descriptors from fw-populated memory

From: Francisco Munoz Ruiz

Date: Wed Apr 01 2026 - 23:06:30 EST


Retrieve System Cache Table (SCT) descriptors from a shared memory
region populated by firmware.

SCT initialization and programming are performed entirely by firmware
outside of Linux. The LLCC driver only consumes the pre-initialized
descriptor data and does not configure SCT itself.

Support this mechanism for future SoCs that provide SCT programming
via firmware.

Signed-off-by: Francisco Munoz Ruiz <francisco.ruiz@xxxxxxxxxxxxxxxx>
Reviewed-by: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>
---
drivers/soc/qcom/llcc-qcom.c | 269 ++++++++++++++++++++++++++++++++-----
include/linux/soc/qcom/llcc-qcom.h | 8 +-
2 files changed, 240 insertions(+), 37 deletions(-)

diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c
index 0161ceec8842..f8cd35b205eb 100644
--- a/drivers/soc/qcom/llcc-qcom.c
+++ b/drivers/soc/qcom/llcc-qcom.c
@@ -5,7 +5,6 @@
*/

#include <linux/bitfield.h>
-#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/io.h>
@@ -14,6 +13,7 @@
#include <linux/mutex.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
#include <linux/regmap.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -76,6 +76,12 @@
#define LLCC_VERSION_4_1_0_0 0x04010000
#define LLCC_VERSION_6_0_0_0 0X06000000

+#define SLC_SCT_MEM_LAYOUT_VERSION1 1 /* SCT Memory layout version */
+#define SLC_SCT_DONE 0x00534354444f4e45 /* SCT programming OK */
+#define SLC_SCT_FAIL 0x005343544641494c /* SCT programming failed */
+#define SLC_SCT_NAME_LEN 15
+#define SLC_SCT_SLICE_ACT_ON_BOOT BIT(25)
+
/**
* struct llcc_slice_config - Data associated with the llcc slice
* @usecase_id: Unique id for the client's use case
@@ -143,6 +149,87 @@ struct llcc_slice_config {
u32 parent_slice_id;
};

+/*
+ * struct slc_sct_error - Represents SCT error
+ * @code: FW code status
+ * @param: Holds the SCT programming error
+ */
+struct slc_sct_error {
+ __le64 code;
+ __le64 param;
+} __packed;
+
+/*
+ * struct slc_sct_status - SCT programming status
+ * @program_status: Indicates programming success or failure
+ * @version: SCT mem layout version
+ * @error: Error enum and its param
+ */
+struct slc_sct_status {
+ __le64 program_status;
+ /* Use the lower 8 bits */
+ __le64 version;
+ struct slc_sct_error error;
+} __packed;
+
+/*
+ * struct slc_sct_details - SCT details
+ * @revision: revision of the SCT table
+ * @name: name of the SCT table
+ */
+struct slc_sct_details {
+ u8 revision;
+ char name[SLC_SCT_NAME_LEN];
+} __packed;
+
+/*
+ * struct tcm_mem_info - SC TCM Shared memory details
+ * @is_present: is TCM region present
+ * @offset: offset of TCM shared memory details
+ */
+struct slc_tcm_mem_info {
+ __le32 is_present;
+ __le32 offset;
+} __packed;
+
+/*
+ * struct slc_sct_slice_desc - Slice descriptor definition used in shmem
+ * @slice_id: SCID of the slice
+ * @usecase_id: Usecase ID of the slice
+ * @slice_properties:
+ * slice_size: Contains the slice descriptor size - 20 bit wide
+ * rsvd: Reserved space - 4 bit wide
+ * flags: Flags for descriptors - 3 bit wide
+ * MPAM SCID: Bit 24
+ * Activate on boot: Bit 25
+ * Non-HLOS SCID: Bit 26
+ * HWMutex: Ensures only one processor (CPU or MCU) at a time can
+ * access the LLCC hardware resources - 5 bit wide
+ */
+struct slc_sct_slice_desc {
+ __le16 slice_id;
+ __le16 usecase_id;
+ __le32 slice_properties;
+} __packed;
+
+/*
+ * struct slc_sct_mem - Shared memory structure
+ * @sct_status: Status of SCT programming
+ * @sct_details: Sct revision and name details
+ * @tcm_mem_info: TCM shared memory presence & offset info
+ * @slice_descs_count: Number of slice desc present in SCT
+ * @scid_max: Maximum no. of SCIDs supported
+ * @slice_descs: Array of SCT slice desc
+ */
+struct slc_sct_mem {
+ struct slc_sct_status sct_status;
+ struct slc_sct_details sct_details;
+ struct slc_tcm_mem_info tcm_mem_info;
+ __le32 slice_descs_count;
+ __le32 scid_max;
+ struct slc_sct_slice_desc slice_descs[] __counted_by_le(slice_descs_count);
+} __packed;
+
struct qcom_llcc_config {
const struct llcc_slice_config *sct_data;
const u32 *reg_offset;
@@ -4141,6 +4228,15 @@ static const u32 llcc_v6_reg_offset[] = {
[LLCC_TRP_WRS_CACHEABLE_EN] = 0x00042088,
};

+static const struct qcom_llcc_config hawi_sct_cfg[] = {
+ {
+ .sct_data = NULL,
+ .size = 0,
+ .reg_offset = llcc_v6_reg_offset,
+ .edac_reg_offset = &llcc_v6_edac_reg_offset,
+ },
+};
+
static const struct qcom_llcc_config kaanapali_cfg[] = {
{
.sct_data = kaanapali_data,
@@ -4397,6 +4493,11 @@ static const struct qcom_llcc_config x1e80100_cfg[] = {
},
};

+static const struct qcom_sct_config hawi_sct_cfgs = {
+ .llcc_config = hawi_sct_cfg,
+ .num_config = ARRAY_SIZE(hawi_sct_cfg),
+};
+
static const struct qcom_sct_config kaanapali_cfgs = {
.llcc_config = kaanapali_cfg,
.num_config = ARRAY_SIZE(kaanapali_cfg),
@@ -4533,23 +4634,20 @@ static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER;
*/
struct llcc_slice_desc *llcc_slice_getd(u32 uid)
{
- const struct llcc_slice_config *cfg;
- u32 sz, i;
-
if (IS_ERR(drv_data))
return ERR_CAST(drv_data);

- cfg = drv_data->cfg;
- sz = drv_data->cfg_size;
+ if (IS_ERR_OR_NULL(drv_data->desc))
+ return ERR_PTR(-ENODEV);

- for (i = 0; cfg && i < sz; i++, cfg++)
- if (cfg->usecase_id == uid)
- break;
+ for (u32 i = 0; i < drv_data->cfg_size; i++) {
+ if (uid == drv_data->desc[i].uid)
+ return &drv_data->desc[i];
+ }

- if (i == sz)
- return ERR_PTR(-ENODEV);
+ dev_err(drv_data->dev, "Failed to get slice desc for uid: %u\n", uid);

- return &drv_data->desc[i];
+ return ERR_PTR(-EINVAL);
}
EXPORT_SYMBOL_GPL(llcc_slice_getd);

@@ -5029,6 +5127,12 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev,
sz = drv_data->cfg_size;
llcc_table = drv_data->cfg;

+ for (i = 0; i < sz; i++) {
+ drv_data->desc[i].uid = llcc_table[i].usecase_id;
+ drv_data->desc[i].slice_id = llcc_table[i].slice_id;
+ drv_data->desc[i].slice_size = llcc_table[i].max_cap;
+ }
+
if (drv_data->version >= LLCC_VERSION_6_0_0_0) {
for (i = 0; i < sz; i++) {
ret = _qcom_llcc_cfg_program_v6(&llcc_table[i], cfg);
@@ -5064,6 +5168,101 @@ static int qcom_llcc_get_cfg_index(struct platform_device *pdev, u8 *cfg_index,
return ret;
}

+static int qcom_llcc_verify_fw_config(struct device *dev,
+ const struct slc_sct_mem *slc_mem)
+{
+ u64 program_status;
+
+ program_status = le64_to_cpu(slc_mem->sct_status.program_status);
+
+ if (program_status == SLC_SCT_DONE) {
+ u32 desc_count = le32_to_cpu(slc_mem->slice_descs_count);
+ u32 scid_max = le32_to_cpu(slc_mem->scid_max);
+
+ if (desc_count > scid_max) {
+ dev_err(dev, "Descriptor count above max limit (%u > %u)\n",
+ desc_count, scid_max);
+ return -EINVAL;
+ }
+
+ u8 revision = slc_mem->sct_details.revision;
+ char name_buf[SLC_SCT_NAME_LEN];
+
+ memcpy(name_buf, slc_mem->sct_details.name,
+ SLC_SCT_NAME_LEN - 1);
+ name_buf[SLC_SCT_NAME_LEN - 1] = '\0';
+
+ dev_dbg(dev, "SCT init: desc_count=%u, rev=%u, name=%s\n",
+ desc_count, revision, name_buf);
+
+ return 0;
+ } else if (program_status == SLC_SCT_FAIL) {
+ u8 version = (u8)(le64_to_cpu(slc_mem->sct_status.version));
+ u64 code = le64_to_cpu(slc_mem->sct_status.error.code);
+ u64 param = le64_to_cpu(slc_mem->sct_status.error.param);
+
+ if (version == SLC_SCT_MEM_LAYOUT_VERSION1) {
+ dev_err(dev, "SCT init failed: code = %llu, param = %llu, version = 0x%x\n",
+ code, param, version);
+ } else {
+ dev_err(dev, "Found unsupported version %u\n", version);
+ }
+ } else {
+ dev_err(dev, "Unknown SCT Initialization error\n");
+ }
+
+ return -EINVAL;
+}
+
+static int qcom_llcc_get_fw_config(struct platform_device *pdev)
+{
+ const struct slc_sct_mem *slc_mem = NULL;
+ const struct slc_sct_slice_desc *memslice;
+ struct device *dev = &pdev->dev;
+ u32 slice_properties;
+ struct resource res;
+ u32 i, sz;
+ int ret;
+
+ ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
+ if (ret) {
+ dev_err(dev, "Unable to locate DT /reserved-memory resource\n");
+ return ret;
+ }
+
+ slc_mem = devm_memremap(dev, res.start, resource_size(&res), MEMREMAP_WB);
+ if (!slc_mem) {
+ dev_err(dev, "Failed to memremap SLC shared memory\n");
+ return -ENOMEM;
+ }
+
+ ret = qcom_llcc_verify_fw_config(dev, slc_mem);
+ if (ret)
+ return ret;
+
+ sz = le32_to_cpu(slc_mem->slice_descs_count);
+
+ drv_data->desc = devm_kcalloc(dev, sz, sizeof(struct llcc_slice_desc),
+ GFP_KERNEL);
+ if (!drv_data->desc)
+ return -ENOMEM;
+
+ for (i = 0; i < sz; i++) {
+ memslice = &slc_mem->slice_descs[i];
+ drv_data->desc[i].slice_id = le16_to_cpu(memslice->slice_id);
+ drv_data->desc[i].uid = le16_to_cpu(memslice->usecase_id);
+ slice_properties = le32_to_cpu(memslice->slice_properties);
+ /* Set refcount to 1 if FW already activated this descriptor */
+ if (FIELD_GET(SLC_SCT_SLICE_ACT_ON_BOOT, slice_properties))
+ refcount_set(&drv_data->desc[i].refcount, 1);
+ }
+
+ drv_data->cfg = NULL;
+ drv_data->cfg_size = sz;
+
+ return 0;
+}
+
static void qcom_llcc_remove(struct platform_device *pdev)
{
/* Set the global pointer to a error code to avoid referencing it */
@@ -5096,8 +5295,6 @@ static int qcom_llcc_probe(struct platform_device *pdev)
struct platform_device *llcc_edac;
const struct qcom_sct_config *cfgs;
const struct qcom_llcc_config *cfg;
- const struct llcc_slice_config *llcc_cfg;
- u32 sz;
u8 cfg_index;
u32 version;
struct regmap *regmap;
@@ -5190,32 +5387,31 @@ static int qcom_llcc_probe(struct platform_device *pdev)
}
}

- llcc_cfg = cfg->sct_data;
- sz = cfg->size;
- drv_data->desc = devm_kcalloc(dev, sz, sizeof(struct llcc_slice_desc), GFP_KERNEL);
- if (!drv_data->desc) {
- ret = -ENOMEM;
- goto err;
- }
+ mutex_init(&drv_data->lock);
+ if (!cfg->size) {
+ ret = qcom_llcc_get_fw_config(pdev);
+ if (ret)
+ goto err;
+ } else {
+ drv_data->cfg = cfg->sct_data;
+ drv_data->cfg_size = cfg->size;
+ drv_data->desc = devm_kcalloc(dev, cfg->size,
+ sizeof(struct llcc_slice_desc), GFP_KERNEL);

- for (i = 0; i < sz; i++) {
- drv_data->desc[i].slice_id = llcc_cfg[i].slice_id;
- drv_data->desc[i].slice_size = llcc_cfg[i].max_cap;
- refcount_set(&drv_data->desc[i].refcount, 0);
+ if (!drv_data->desc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = qcom_llcc_cfg_program(pdev, cfg);
+ if (ret)
+ goto err;
}

- drv_data->cfg = llcc_cfg;
- drv_data->cfg_size = sz;
+ drv_data->ecc_irq = platform_get_irq_optional(pdev, 0);
drv_data->edac_reg_offset = cfg->edac_reg_offset;
drv_data->ecc_irq_configured = cfg->irq_configured;
- mutex_init(&drv_data->lock);
- platform_set_drvdata(pdev, drv_data);
-
- ret = qcom_llcc_cfg_program(pdev, cfg);
- if (ret)
- goto err;
-
- drv_data->ecc_irq = platform_get_irq_optional(pdev, 0);
+ drv_data->dev = dev;

/*
* On some platforms, the access to EDAC registers will be locked by
@@ -5231,6 +5427,8 @@ static int qcom_llcc_probe(struct platform_device *pdev)
dev_err(dev, "Failed to register llcc edac driver\n");
}

+ platform_set_drvdata(pdev, drv_data);
+
return 0;
err:
drv_data = ERR_PTR(-ENODEV);
@@ -5239,6 +5437,7 @@ static int qcom_llcc_probe(struct platform_device *pdev)

static const struct of_device_id qcom_llcc_of_match[] = {
{ .compatible = "qcom,glymur-llcc", .data = &glymur_cfgs },
+ { .compatible = "qcom,hawi-llcc", .data = &hawi_sct_cfgs },
{ .compatible = "qcom,ipq5424-llcc", .data = &ipq5424_cfgs},
{ .compatible = "qcom,kaanapali-llcc", .data = &kaanapali_cfgs},
{ .compatible = "qcom,qcs615-llcc", .data = &qcs615_cfgs},
diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h
index 227125d84318..b5e917154998 100644
--- a/include/linux/soc/qcom/llcc-qcom.h
+++ b/include/linux/soc/qcom/llcc-qcom.h
@@ -90,11 +90,13 @@
/**
* struct llcc_slice_desc - Cache slice descriptor
* @slice_id: llcc slice id
+ * @uid: Unique ID associated with the llcc device
* @slice_size: Size allocated for the llcc slice
* @refcount: Atomic counter to track activate/deactivate calls
*/
struct llcc_slice_desc {
u32 slice_id;
+ u32 uid;
size_t slice_size;
refcount_t refcount;
};
@@ -147,6 +149,7 @@ struct llcc_edac_reg_offset {

/**
* struct llcc_drv_data - Data associated with the llcc driver
+ * @dev: device back-pointer for this llcc instance
* @regmaps: regmaps associated with the llcc device
* @bcast_regmap: regmap associated with llcc broadcast OR offset
* @bcast_and_regmap: regmap associated with llcc broadcast AND offset
@@ -157,10 +160,11 @@ struct llcc_edac_reg_offset {
* @num_banks: Number of llcc banks
* @ecc_irq: interrupt for llcc cache error detection and reporting
* @ecc_irq_configured: 'True' if firmware has already configured the irq propagation
- * @desc: Array pointer of pre-allocated LLCC slice descriptors
* @version: Indicates the LLCC version
+ * @desc: Array pointer of pre-allocated LLCC slice descriptors
*/
struct llcc_drv_data {
+ struct device *dev;
struct regmap **regmaps;
struct regmap *bcast_regmap;
struct regmap *bcast_and_regmap;
@@ -183,7 +187,7 @@ struct llcc_drv_data {
struct llcc_slice_desc *llcc_slice_getd(u32 uid);

/**
- * llcc_slice_putd - llcc slice descritpor
+ * llcc_slice_putd - llcc slice descriptor
* @desc: Pointer to llcc slice descriptor
*/
void llcc_slice_putd(struct llcc_slice_desc *desc);

--
2.34.1