summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/smem_dramc.c
blob: 63ebe7f6db5905889486bbfd9ca456540462cf58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
 */

#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 "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;
}