// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include #include #include #include #include #include #include #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_CHAN_NUM 8 #define MAX_RANK_NUM 2 #define DDR_HBB_MIN 13 #define DDR_HBB_MAX 19 #define MAX_SHUB_ENTRIES 8 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, INFO_V6, /* INFO_V6 seems to only have shipped with 6 DDR regions, unlike V7 */ INFO_V7, INFO_V7_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 { __le32 freq_khz; u8 enabled; }; /* V3 */ struct ddr_freq_plan_v3 { struct ddr_freq_table ddr_freq[MAX_DDR_FREQ_NUM_V3]; 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; }; /* Some V3 structs have an additional frequency level */ struct ddr_freq_plan_v3_14freqs { struct ddr_freq_table ddr_freq[MAX_DDR_FREQ_NUM_V3 + 1]; u8 num_ddr_freqs; phys_addr_t clk_period_address; }; struct ddr_details_v3_14freqs { u8 manufacturer_id; u8 device_type; struct ddr_part_details ddr_params[MAX_CHAN_NUM]; struct ddr_freq_plan_v3_14freqs 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 shub_freq_table { u8 enable; __le32 freq_khz; }; struct shub_freq_plan_entry { u8 num_shub_freqs; struct shub_freq_table shub_freq[MAX_SHUB_ENTRIES]; }; struct ddr_xbl2quantum_smem_data { phys_addr_t ssr_cookie_addr; __le32 reserved[10]; }; 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; __le32 max_nom_ddr_freq; }; struct ddr_region_v5 { __le64 start_address; __le64 size; __le64 mem_controller_address; __le32 granule_size; /* MiB */ u8 ddr_rank; #define DDR_RANK_0 BIT(0) #define DDR_RANK_1 BIT(1) u8 segments_start_index; __le64 segments_start_offset; }; struct ddr_regions_v5 { __le32 ddr_region_num; /* We expect this to always be 4 or 6 */ __le64 ddr_rank0_size; __le64 ddr_rank1_size; __le64 ddr_cs0_start_addr; __le64 ddr_cs1_start_addr; __le32 highest_bank_addr_bit; struct ddr_region_v5 ddr_region[] __counted_by_le(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; u8 _padding; struct ddr_regions_v5 ddr_regions; }; /* V6 */ struct ddr_misc_info_v6 { __le32 dsf_version; __le32 reserved[10]; }; /* V7 */ struct ddr_details_v7 { 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; u8 sct_config; 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. * * Return: highest bank bit on success, -ENODATA on failure. */ int qcom_smem_dram_get_hbb(void) { if (!__dram || !__dram->hbb) return -ENODATA; if (__dram->hbb < DDR_HBB_MIN || __dram->hbb > DDR_HBB_MAX) return -ENODATA; return __dram->hbb; } EXPORT_SYMBOL_GPL(qcom_smem_dram_get_hbb); static void smem_dram_parse_v3_data(struct smem_dram *dram, void *data) { struct ddr_details_v3 *details = data; 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) { u32 freq_khz = le32_to_cpu(freq_entry->freq_khz); dram->frequencies[dram->num_frequencies++] = 1000 * freq_khz; } } } static void smem_dram_parse_v3_14freqs_data(struct smem_dram *dram, void *data) { struct ddr_details_v3_14freqs *details = data; for (int i = 0; i < MAX_DDR_FREQ_NUM_V3 + 1; 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) { u32 freq_khz = le32_to_cpu(freq_entry->freq_khz); dram->frequencies[dram->num_frequencies++] = 1000 * 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 = le32_to_cpu(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) { u32 freq_khz = le32_to_cpu(freq_entry->freq_khz); dram->frequencies[dram->num_frequencies++] = 1000 * freq_khz; } } } static void smem_dram_parse_v7_data(struct smem_dram *dram, void *data) { struct ddr_details_v7 *details = data; struct ddr_regions_v5 *region = &details->ddr_regions; dram->hbb = le32_to_cpu(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) { u32 freq_khz = le32_to_cpu(freq_entry->freq_khz); dram->frequencies[dram->num_frequencies++] = 1000 * 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; if (size == sizeof(struct ddr_details_v3_14freqs)) return INFO_V3_WITH_14_FREQS; if (size == sizeof(struct ddr_details_v4)) return INFO_V4; if (size == sizeof(struct ddr_details_v5) + 4 * sizeof(struct ddr_region_v5)) return INFO_V5; if (size == sizeof(struct ddr_details_v5) + 4 * sizeof(struct ddr_region_v5) + sizeof(struct ddr_xbl2quantum_smem_data) + sizeof(struct shub_freq_plan_entry)) return INFO_V5; if (size == sizeof(struct ddr_details_v5) + 6 * sizeof(struct ddr_region_v5)) return INFO_V5_WITH_6_REGIONS; if (size == sizeof(struct ddr_details_v5) + 6 * sizeof(struct ddr_region_v5) + sizeof(struct ddr_xbl2quantum_smem_data) + sizeof(struct shub_freq_plan_entry)) return INFO_V5_WITH_6_REGIONS; if (size == sizeof(struct ddr_details_v5) + 6 * sizeof(struct ddr_region_v5) + sizeof(struct ddr_misc_info_v6) + sizeof(struct shub_freq_plan_entry)) return INFO_V6; if (size == sizeof(struct ddr_details_v7) + 4 * sizeof(struct ddr_region_v5) + sizeof(struct ddr_misc_info_v6) + sizeof(struct shub_freq_plan_entry)) return INFO_V7; if (size == sizeof(struct ddr_details_v7) + 6 * sizeof(struct ddr_region_v5) + sizeof(struct ddr_misc_info_v6) + sizeof(struct shub_freq_plan_entry)) return INFO_V7_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); static int smem_hbb_show(struct seq_file *s, void *unused) { struct smem_dram *dram = s->private; if (!dram->hbb) return -EINVAL; seq_printf(s, "%d\n", dram->hbb); return 0; } DEFINE_SHOW_ATTRIBUTE(smem_hbb); struct dentry *smem_dram_parse(struct qcom_smem *smem, struct device *dev) { struct dentry *debugfs_dir; enum ddr_info_version ver; struct smem_dram *dram; size_t actual_size; void *data; /* No need to check qcom_smem_is_available(), this func is called by the SMEM driver */ data = __qcom_smem_get(smem, 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 */ dev_err(dev, "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); break; case INFO_V3_WITH_14_FREQS: smem_dram_parse_v3_14freqs_data(dram, data); break; case INFO_V4: smem_dram_parse_v4_data(dram, data); break; case INFO_V5: case INFO_V5_WITH_6_REGIONS: case INFO_V6: smem_dram_parse_v5_data(dram, data); break; case INFO_V7: case INFO_V7_WITH_6_REGIONS: smem_dram_parse_v7_data(dram, data); break; default: return ERR_PTR(-EINVAL); } debugfs_dir = debugfs_create_dir("qcom_smem", NULL); debugfs_create_file("dram_frequencies", 0444, debugfs_dir, dram, &smem_dram_frequencies_fops); debugfs_create_file("hbb", 0444, debugfs_dir, dram, &smem_hbb_fops); __dram = dram; return debugfs_dir; }