soc: qcom: llcc-qcom: get SCT descriptors from fw-populated memory

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@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20260407-external_llcc_changes2set-v2-2-b5017ce2020b@oss.qualcomm.com
Signed-off-by: Bjorn Andersson <andersson@kernel.org>
This commit is contained in:
Francisco Munoz Ruiz 2026-04-07 14:51:48 -07:00 committed by Bjorn Andersson
parent cb4a414bac
commit ac23106a9b
2 changed files with 242 additions and 39 deletions

View File

@ -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;
for (i = 0; cfg && i < sz; i++, cfg++)
if (cfg->usecase_id == uid)
break;
if (i == sz)
if (IS_ERR_OR_NULL(drv_data->desc))
return ERR_PTR(-ENODEV);
return &drv_data->desc[i];
for (u32 i = 0; i < drv_data->cfg_size; i++) {
if (uid == drv_data->desc[i].uid)
return &drv_data->desc[i];
}
dev_err(drv_data->dev, "Failed to get slice desc for uid: %u\n", uid);
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;
}
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);
}
drv_data->cfg = llcc_cfg;
drv_data->cfg_size = sz;
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);
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);
ret = qcom_llcc_cfg_program(pdev, cfg);
if (ret)
goto err;
if (!drv_data->desc) {
ret = -ENOMEM;
goto err;
}
ret = qcom_llcc_cfg_program(pdev, cfg);
if (ret)
goto err;
}
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;
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},

View File

@ -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);