Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion drivers/soc/qcom/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
qcom_rpmh-y += rpmh-rsc.o
qcom_rpmh-y += rpmh.o
obj-$(CONFIG_QCOM_SMD_RPM) += rpm-proc.o smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) += smem.o
qcom_smem-y += smem.o smem_dramc.o
obj-$(CONFIG_QCOM_SMEM) += qcom_smem.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
CFLAGS_smp2p.o := -I$(src)
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
Expand Down
14 changes: 13 additions & 1 deletion drivers/soc/qcom/smem.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/

#include <linux/debugfs.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/module.h>
Expand All @@ -16,6 +17,8 @@
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/socinfo.h>

#include "smem.h"

/*
* The Qualcomm shared memory system is a allocate only heap structure that
* consists of one of more memory areas that can be accessed by the processors
Expand Down Expand Up @@ -284,6 +287,8 @@ struct qcom_smem {
struct smem_partition global_partition;
struct smem_partition partitions[SMEM_HOST_COUNT];

struct dentry *debugfs_dir;

unsigned num_regions;
struct smem_region regions[] __counted_by(num_regions);
};
Expand Down Expand Up @@ -1230,17 +1235,24 @@ static int qcom_smem_probe(struct platform_device *pdev)

__smem = smem;

smem->debugfs_dir = smem_dram_parse(smem->dev);

smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",
PLATFORM_DEVID_NONE, NULL,
0);
if (IS_ERR(smem->socinfo))
if (IS_ERR(smem->socinfo)) {
debugfs_remove_recursive(smem->debugfs_dir);

dev_dbg(&pdev->dev, "failed to register socinfo device\n");
}

return 0;
}

static void qcom_smem_remove(struct platform_device *pdev)
{
debugfs_remove_recursive(__smem->debugfs_dir);

platform_device_unregister(__smem->socinfo);

hwspin_lock_free(__smem->hwlock);
Expand Down
9 changes: 9 additions & 0 deletions drivers/soc/qcom/smem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __QCOM_SMEM_INTERNAL__
#define __QCOM_SMEM_INTERNAL__

#include <linux/device.h>

struct dentry *smem_dram_parse(struct device *dev);

#endif
287 changes: 287 additions & 0 deletions drivers/soc/qcom/smem_dramc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
*/

#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/soc/qcom/smem.h>
#include <linux/units.h>
#include <linux/soc/qcom/smem.h>

#include "smem.h"

#define SMEM_DDR_INFO_ID 603

#define MAX_DDR_FREQ_NUM_V3 13
#define MAX_DDR_FREQ_NUM_V5 14

#define MAX_DDR_REGION_NUM 6
#define MAX_CHAN_NUM 8
#define MAX_RANK_NUM 2

static struct smem_dram *__dram;

enum ddr_info_version {
INFO_UNKNOWN,
INFO_V3,
INFO_V3_WITH_14_FREQS,
INFO_V4,
INFO_V5,
INFO_V5_WITH_6_REGIONS,
};

struct smem_dram {
unsigned long frequencies[MAX_DDR_FREQ_NUM_V5];
u32 num_frequencies;
u8 hbb;
};

enum ddr_type {
DDR_TYPE_NODDR = 0,
DDR_TYPE_LPDDR1 = 1,
DDR_TYPE_LPDDR2 = 2,
DDR_TYPE_PCDDR2 = 3,
DDR_TYPE_PCDDR3 = 4,
DDR_TYPE_LPDDR3 = 5,
DDR_TYPE_LPDDR4 = 6,
DDR_TYPE_LPDDR4X = 7,
DDR_TYPE_LPDDR5 = 8,
DDR_TYPE_LPDDR5X = 9,
};

/* The data structures below are NOT __packed on purpose! */

/* Structs used across multiple versions */
struct ddr_part_details {
__le16 revision_id1;
__le16 revision_id2;
__le16 width;
__le16 density;
};

struct ddr_freq_table {
u32 freq_khz;
u8 enabled;
};

/* V3 */
struct ddr_freq_plan_v3 {
struct ddr_freq_table ddr_freq[MAX_DDR_FREQ_NUM_V3]; /* NOTE: some have 14 like v5 */
u8 num_ddr_freqs;
phys_addr_t clk_period_address;
};

struct ddr_details_v3 {
u8 manufacturer_id;
u8 device_type;
struct ddr_part_details ddr_params[MAX_CHAN_NUM];
struct ddr_freq_plan_v3 ddr_freq_tbl;
u8 num_channels;
};

/* V4 */
struct ddr_details_v4 {
u8 manufacturer_id;
u8 device_type;
struct ddr_part_details ddr_params[MAX_CHAN_NUM];
struct ddr_freq_plan_v3 ddr_freq_tbl;
u8 num_channels;
u8 num_ranks[MAX_CHAN_NUM];
u8 highest_bank_addr_bit[MAX_CHAN_NUM][MAX_RANK_NUM];
};

/* V5 */
struct ddr_freq_plan_v5 {
struct ddr_freq_table ddr_freq[MAX_DDR_FREQ_NUM_V5];
u8 num_ddr_freqs;
phys_addr_t clk_period_address;
u32 max_nom_ddr_freq;
};

struct ddr_region_v5 {
u64 start_address;
u64 size;
u64 mem_controller_address;
u32 granule_size; /* MiB */
u8 ddr_rank;
#define DDR_RANK_0 BIT(0)
#define DDR_RANK_1 BIT(1)
u8 segments_start_index;
u64 segments_start_offset;
};

struct ddr_regions_v5 {
u32 ddr_region_num; /* We expect this to always be 4 or 6 */
u64 ddr_rank0_size;
u64 ddr_rank1_size;
u64 ddr_cs0_start_addr;
u64 ddr_cs1_start_addr;
u32 highest_bank_addr_bit;
struct ddr_region_v5 ddr_region[] __counted_by(ddr_region_num);
};

struct ddr_details_v5 {
u8 manufacturer_id;
u8 device_type;
struct ddr_part_details ddr_params[MAX_CHAN_NUM];
struct ddr_freq_plan_v5 ddr_freq_tbl;
u8 num_channels;
struct ddr_regions_v5 ddr_regions;
};

/**
* qcom_smem_dram_get_hbb(): Get the Highest bank address bit
*
* Context: Check qcom_smem_is_available() before calling this function.
* Because __dram * is initialized by smem_dram_parse(), which is in turn
* called from * qcom_smem_probe(), __dram will only be NULL if the data
* couldn't have been found/interpreted correctly.
*
* If the function fails, the argument is left unmodified.
*
* Return: 0 on success, -ENODATA on failure.
*/
int qcom_smem_dram_get_hbb(void)
{
return __dram ? __dram->hbb : -ENODATA;
}
EXPORT_SYMBOL_GPL(qcom_smem_dram_get_hbb);

static void smem_dram_parse_v3_data(struct smem_dram *dram, void *data, bool additional_freq_entry)
{
/* This may be 13 or 14 */
int num_freq_entries = MAX_DDR_FREQ_NUM_V3;
struct ddr_details_v3 *details = data;

if (additional_freq_entry)
num_freq_entries++;

for (int i = 0; i < num_freq_entries; i++) {
struct ddr_freq_table *freq_entry = &details->ddr_freq_tbl.ddr_freq[i];

if (freq_entry->freq_khz && freq_entry->enabled)
dram->frequencies[dram->num_frequencies++] = 1000 * freq_entry->freq_khz;
}
}

static void smem_dram_parse_v4_data(struct smem_dram *dram, void *data)
{
struct ddr_details_v4 *details = data;

/* Rank 0 channel 0 entry holds the correct value */
dram->hbb = details->highest_bank_addr_bit[0][0];

for (int i = 0; i < MAX_DDR_FREQ_NUM_V3; i++) {
struct ddr_freq_table *freq_entry = &details->ddr_freq_tbl.ddr_freq[i];

if (freq_entry->freq_khz && freq_entry->enabled)
dram->frequencies[dram->num_frequencies++] = 1000 * freq_entry->freq_khz;
}
}

static void smem_dram_parse_v5_data(struct smem_dram *dram, void *data)
{
struct ddr_details_v5 *details = data;
struct ddr_regions_v5 *region = &details->ddr_regions;

dram->hbb = region[0].highest_bank_addr_bit;

for (int i = 0; i < MAX_DDR_FREQ_NUM_V5; i++) {
struct ddr_freq_table *freq_entry = &details->ddr_freq_tbl.ddr_freq[i];

if (freq_entry->freq_khz && freq_entry->enabled)
dram->frequencies[dram->num_frequencies++] = 1000 * freq_entry->freq_khz;
}
}

/* The structure contains no version field, so we have to perform some guesswork.. */
static int smem_dram_infer_struct_version(size_t size)
{
/* Some early versions provided less bytes of less useful data */
if (size < sizeof(struct ddr_details_v3))
return -EINVAL;
if (size == sizeof(struct ddr_details_v3))
return INFO_V3;
else if (size == sizeof(struct ddr_details_v3) + sizeof(struct ddr_freq_table))
return INFO_V3_WITH_14_FREQS;
else if (size == sizeof(struct ddr_details_v4))
return INFO_V4;
else if (size == sizeof(struct ddr_details_v5) + 4 * sizeof(struct ddr_region_v5))
return INFO_V5;
else if (size == sizeof(struct ddr_details_v5) + 6 * sizeof(struct ddr_region_v5))
return INFO_V5_WITH_6_REGIONS;

return INFO_UNKNOWN;
}

static int smem_dram_frequencies_show(struct seq_file *s, void *unused)
{
struct smem_dram *dram = s->private;

for (int i = 0; i < dram->num_frequencies; i++)
seq_printf(s, "%lu\n", dram->frequencies[i]);

return 0;
}
DEFINE_SHOW_ATTRIBUTE(smem_dram_frequencies);

struct dentry *smem_dram_parse(struct device *dev)
{
struct dentry *debugfs_dir;
enum ddr_info_version ver;
struct smem_dram *dram;
size_t actual_size;
void *data = NULL;

/* No need to check qcom_smem_is_available(), this func is called by the SMEM driver */
data = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_DDR_INFO_ID, &actual_size);
if (IS_ERR_OR_NULL(data))
return ERR_PTR(-ENODATA);

ver = smem_dram_infer_struct_version(actual_size);
if (ver < 0) {
/* Some SoCs don't provide data that's useful for us */
return ERR_PTR(-ENODATA);
} else if (ver == INFO_UNKNOWN) {
/* In other cases, we may not have added support for a newer struct revision */
pr_err("Found an unknown type of DRAM info struct (size = %zu)\n", actual_size);
return ERR_PTR(-EINVAL);
}

dram = devm_kzalloc(dev, sizeof(*dram), GFP_KERNEL);
if (!dram)
return ERR_PTR(-ENOMEM);

switch (ver) {
case INFO_V3:
smem_dram_parse_v3_data(dram, data, false);
break;
case INFO_V3_WITH_14_FREQS:
smem_dram_parse_v3_data(dram, data, true);
break;
case INFO_V4:
smem_dram_parse_v4_data(dram, data);
break;
case INFO_V5:
case INFO_V5_WITH_6_REGIONS:
smem_dram_parse_v5_data(dram, data);
break;
default:
return ERR_PTR(-EINVAL);
}

/* Both the entry and its parent dir will be cleaned up by debugfs_remove_recursive */
debugfs_dir = debugfs_create_dir("qcom_smem", NULL);
debugfs_create_file("dram_frequencies", 0444, debugfs_dir,
dram, &smem_dram_frequencies_fops);

/* If there was no failure so far, assign the global variable */
__dram = dram;

return debugfs_dir;
}
4 changes: 4 additions & 0 deletions include/linux/soc/qcom/smem.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#ifndef __QCOM_SMEM_H__
#define __QCOM_SMEM_H__

#include <linux/platform_device.h>

#define QCOM_SMEM_HOST_ANY -1

bool qcom_smem_is_available(void);
Expand All @@ -17,4 +19,6 @@ int qcom_smem_get_feature_code(u32 *code);

int qcom_smem_bust_hwspin_lock_by_host(unsigned int host);

int qcom_smem_dram_get_hbb(void);

#endif