File Index
Files and directories
Sign in
FILE

eeprom.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/mediatek/mt76/eeprom.c C
  1// SPDX-License-Identifier: BSD-3-Clause-Clear
  2/*
  3 * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
  4 */
  5#include <linux/of.h>
  6#include <linux/of_net.h>
  7#include <linux/mtd/mtd.h>
  8#include <linux/mtd/partitions.h>
  9#include <linux/nvmem-consumer.h>
 10#include <linux/etherdevice.h>
 11#include "mt76.h"
 12
 13static int mt76_get_of_eeprom_data(struct mt76_dev *dev, void *eep, int len)
 14{
 15	struct device_node *np = dev->dev->of_node;
 16	const void *data;
 17	int size;
 18
 19	data = of_get_property(np, "mediatek,eeprom-data", &size);
 20	if (!data)
 21		return -ENOENT;
 22
 23	if (size > len)
 24		return -EINVAL;
 25
 26	memcpy(eep, data, size);
 27
 28	return 0;
 29}
 30
 31int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int len)
 32{
 33#ifdef CONFIG_MTD
 34	struct device_node *np = dev->dev->of_node;
 35	struct mtd_info *mtd;
 36	const __be32 *list;
 37	const char *part;
 38	phandle phandle;
 39	size_t retlen;
 40	int size;
 41	int ret;
 42
 43	list = of_get_property(np, "mediatek,mtd-eeprom", &size);
 44	if (!list)
 45		return -ENOENT;
 46
 47	phandle = be32_to_cpup(list++);
 48	if (!phandle)
 49		return -ENOENT;
 50
 51	np = of_find_node_by_phandle(phandle);
 52	if (!np)
 53		return -EINVAL;
 54
 55	part = of_get_property(np, "label", NULL);
 56	if (!part)
 57		part = np->name;
 58
 59	mtd = get_mtd_device_nm(part);
 60	if (IS_ERR(mtd)) {
 61		ret =  PTR_ERR(mtd);
 62		goto out_put_node;
 63	}
 64
 65	if (size <= sizeof(*list)) {
 66		ret = -EINVAL;
 67		goto out_put_node;
 68	}
 69
 70	offset += be32_to_cpup(list);
 71	ret = mtd_read(mtd, offset, len, &retlen, eep);
 72	put_mtd_device(mtd);
 73	if (mtd_is_bitflip(ret))
 74		ret = 0;
 75	if (ret) {
 76		dev_err(dev->dev, "reading EEPROM from mtd %s failed: %i\n",
 77			part, ret);
 78		goto out_put_node;
 79	}
 80
 81	if (retlen < len) {
 82		ret = -EINVAL;
 83		goto out_put_node;
 84	}
 85
 86	if (of_property_read_bool(dev->dev->of_node, "big-endian")) {
 87		u8 *data = (u8 *)eep;
 88		int i;
 89
 90		/* convert eeprom data in Little Endian */
 91		for (i = 0; i < round_down(len, 2); i += 2)
 92			put_unaligned_le16(get_unaligned_be16(&data[i]),
 93					   &data[i]);
 94	}
 95
 96#ifdef CONFIG_NL80211_TESTMODE
 97	dev->test_mtd.name = devm_kstrdup(dev->dev, part, GFP_KERNEL);
 98	if (!dev->test_mtd.name) {
 99		ret = -ENOMEM;
100		goto out_put_node;
101	}
102	dev->test_mtd.offset = offset;
103#endif
104
105out_put_node:
106	of_node_put(np);
107	return ret;
108#else
109	return -ENOENT;
110#endif
111}
112EXPORT_SYMBOL_GPL(mt76_get_of_data_from_mtd);
113
114int mt76_get_of_data_from_nvmem(struct mt76_dev *dev, void *eep,
115				const char *cell_name, int len)
116{
117	struct device_node *np = dev->dev->of_node;
118	struct nvmem_cell *cell;
119	const void *data;
120	size_t retlen;
121	int ret = 0;
122
123	cell = of_nvmem_cell_get(np, cell_name);
124	if (IS_ERR(cell))
125		return PTR_ERR(cell);
126
127	data = nvmem_cell_read(cell, &retlen);
128	nvmem_cell_put(cell);
129
130	if (IS_ERR(data))
131		return PTR_ERR(data);
132
133	if (retlen < len) {
134		ret = -EINVAL;
135		goto exit;
136	}
137
138	memcpy(eep, data, len);
139
140exit:
141	kfree(data);
142
143	return ret;
144}
145EXPORT_SYMBOL_GPL(mt76_get_of_data_from_nvmem);
146
147static int mt76_get_of_eeprom(struct mt76_dev *dev, void *eep, int len)
148{
149	struct device_node *np = dev->dev->of_node;
150	int ret;
151
152	if (!np)
153		return -ENOENT;
154
155	ret = mt76_get_of_eeprom_data(dev, eep, len);
156	if (!ret)
157		return 0;
158
159	ret = mt76_get_of_data_from_mtd(dev, eep, 0, len);
160	if (!ret)
161		return 0;
162
163	return mt76_get_of_data_from_nvmem(dev, eep, "eeprom", len);
164}
165
166int
167mt76_eeprom_override(struct mt76_phy *phy)
168{
169	struct mt76_dev *dev = phy->dev;
170	struct device_node *np = dev->dev->of_node;
171	int err;
172
173	err = of_get_mac_address(np, phy->macaddr);
174	if (err == -EPROBE_DEFER)
175		return err;
176
177	if (!is_valid_ether_addr(phy->macaddr)) {
178		eth_random_addr(phy->macaddr);
179		dev_info(dev->dev,
180			 "Invalid MAC address, using random address %pM\n",
181			 phy->macaddr);
182	}
183
184	return 0;
185}
186EXPORT_SYMBOL_GPL(mt76_eeprom_override);
187
188static bool mt76_string_prop_find(struct property *prop, const char *str)
189{
190	const char *cp = NULL;
191
192	if (!prop || !str || !str[0])
193		return false;
194
195	while ((cp = of_prop_next_string(prop, cp)) != NULL)
196		if (!strcasecmp(cp, str))
197			return true;
198
199	return false;
200}
201
202struct device_node *
203mt76_find_power_limits_node(struct mt76_dev *dev)
204{
205	struct device_node *np = dev->dev->of_node;
206	const char *const region_names[] = {
207		[NL80211_DFS_UNSET] = "ww",
208		[NL80211_DFS_ETSI] = "etsi",
209		[NL80211_DFS_FCC] = "fcc",
210		[NL80211_DFS_JP] = "jp",
211	};
212	struct device_node *cur, *fallback = NULL;
213	const char *region_name = NULL;
214
215	if (dev->region < ARRAY_SIZE(region_names))
216		region_name = region_names[dev->region];
217
218	np = of_get_child_by_name(np, "power-limits");
219	if (!np)
220		return NULL;
221
222	for_each_child_of_node(np, cur) {
223		struct property *country = of_find_property(cur, "country", NULL);
224		struct property *regd = of_find_property(cur, "regdomain", NULL);
225
226		if (!country && !regd) {
227			fallback = cur;
228			continue;
229		}
230
231		if (mt76_string_prop_find(country, dev->alpha2) ||
232		    mt76_string_prop_find(regd, region_name)) {
233			of_node_put(np);
234			return cur;
235		}
236	}
237
238	of_node_put(np);
239	return fallback;
240}
241EXPORT_SYMBOL_GPL(mt76_find_power_limits_node);
242
243static const __be32 *
244mt76_get_of_array(struct device_node *np, char *name, size_t *len, int min)
245{
246	struct property *prop = of_find_property(np, name, NULL);
247
248	if (!prop || !prop->value || prop->length < min * 4)
249		return NULL;
250
251	*len = prop->length;
252
253	return prop->value;
254}
255
256static const s8 *
257mt76_get_of_array_s8(struct device_node *np, char *name, size_t *len, int min)
258{
259	struct property *prop = of_find_property(np, name, NULL);
260
261	if (!prop || !prop->value || prop->length < min)
262		return NULL;
263
264	*len = prop->length;
265
266	return prop->value;
267}
268
269struct device_node *
270mt76_find_channel_node(struct device_node *np, struct ieee80211_channel *chan)
271{
272	struct device_node *cur;
273	const __be32 *val;
274	size_t len;
275
276	for_each_child_of_node(np, cur) {
277		val = mt76_get_of_array(cur, "channels", &len, 2);
278		if (!val)
279			continue;
280
281		while (len >= 2 * sizeof(*val)) {
282			if (chan->hw_value >= be32_to_cpu(val[0]) &&
283			    chan->hw_value <= be32_to_cpu(val[1]))
284				return cur;
285
286			val += 2;
287			len -= 2 * sizeof(*val);
288		}
289	}
290
291	return NULL;
292}
293EXPORT_SYMBOL_GPL(mt76_find_channel_node);
294
295
296static s8
297mt76_get_txs_delta(struct device_node *np, u8 nss)
298{
299	const __be32 *val;
300	size_t len;
301
302	val = mt76_get_of_array(np, "txs-delta", &len, nss);
303	if (!val)
304		return 0;
305
306	return be32_to_cpu(val[nss - 1]);
307}
308
309static void
310mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const s8 *data,
311		       s8 target_power, s8 nss_delta, s8 *max_power)
312{
313	int i;
314
315	if (!data)
316		return;
317
318	for (i = 0; i < pwr_len; i++) {
319		pwr[i] = min_t(s8, target_power, data[i] + nss_delta);
320		*max_power = max(*max_power, pwr[i]);
321	}
322}
323
324static void
325mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num,
326			     const s8 *data, size_t len, s8 target_power,
327			     s8 nss_delta)
328{
329	int i, cur;
330	s8 max_power = -128;
331
332	if (!data)
333		return;
334
335	cur = data[0];
336	for (i = 0; i < pwr_num; i++) {
337		if (len < pwr_len + 1)
338			break;
339
340		mt76_apply_array_limit(pwr + pwr_len * i, pwr_len, data + 1,
341				       target_power, nss_delta, &max_power);
342		if (--cur > 0)
343			continue;
344
345		data += pwr_len + 1;
346		len -= pwr_len + 1;
347		if (!len)
348			break;
349
350		cur = data[0];
351	}
352}
353
354s8 mt76_get_rate_power_limits(struct mt76_phy *phy,
355			      struct ieee80211_channel *chan,
356			      struct mt76_power_limits *dest,
357			      s8 target_power)
358{
359	struct mt76_dev *dev = phy->dev;
360	struct device_node *np;
361	const s8 *val;
362	char name[16];
363	u32 mcs_rates = dev->drv->mcs_rates;
364	u32 ru_rates = ARRAY_SIZE(dest->ru[0]);
365	char band;
366	size_t len;
367	s8 max_power = 0;
368	s8 max_power_backoff = -127;
369	s8 txs_delta;
370	int n_chains = hweight16(phy->chainmask);
371	s8 target_power_combine = target_power + mt76_tx_power_path_delta(n_chains);
372
373	if (!mcs_rates)
374		mcs_rates = 10;
375
376	memset(dest, target_power, sizeof(*dest) - sizeof(dest->path));
377	memset(&dest->path, 0, sizeof(dest->path));
378
379	if (!IS_ENABLED(CONFIG_OF))
380		return target_power;
381
382	np = mt76_find_power_limits_node(dev);
383	if (!np)
384		return target_power;
385
386	switch (chan->band) {
387	case NL80211_BAND_2GHZ:
388		band = '2';
389		break;
390	case NL80211_BAND_5GHZ:
391		band = '5';
392		break;
393	case NL80211_BAND_6GHZ:
394		band = '6';
395		break;
396	default:
397		return target_power;
398	}
399
400	snprintf(name, sizeof(name), "txpower-%cg", band);
401	np = of_get_child_by_name(np, name);
402	if (!np)
403		return target_power;
404
405	np = mt76_find_channel_node(np, chan);
406	if (!np)
407		return target_power;
408
409	txs_delta = mt76_get_txs_delta(np, hweight16(phy->chainmask));
410
411	val = mt76_get_of_array_s8(np, "rates-cck", &len, ARRAY_SIZE(dest->cck));
412	mt76_apply_array_limit(dest->cck, ARRAY_SIZE(dest->cck), val,
413			       target_power, txs_delta, &max_power);
414
415	val = mt76_get_of_array_s8(np, "rates-ofdm",
416				   &len, ARRAY_SIZE(dest->ofdm));
417	mt76_apply_array_limit(dest->ofdm, ARRAY_SIZE(dest->ofdm), val,
418			       target_power, txs_delta, &max_power);
419
420	val = mt76_get_of_array_s8(np, "rates-mcs", &len, mcs_rates + 1);
421	mt76_apply_multi_array_limit(dest->mcs[0], ARRAY_SIZE(dest->mcs[0]),
422				     ARRAY_SIZE(dest->mcs), val, len,
423				     target_power, txs_delta);
424
425	val = mt76_get_of_array_s8(np, "rates-ru", &len, ru_rates + 1);
426	mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]),
427				     ARRAY_SIZE(dest->ru), val, len,
428				     target_power, txs_delta);
429
430	max_power_backoff = max_power;
431	val = mt76_get_of_array_s8(np, "paths-cck", &len, ARRAY_SIZE(dest->path.cck));
432	mt76_apply_array_limit(dest->path.cck, ARRAY_SIZE(dest->path.cck), val,
433			       target_power_combine, txs_delta, &max_power_backoff);
434
435	val = mt76_get_of_array_s8(np, "paths-ofdm", &len, ARRAY_SIZE(dest->path.ofdm));
436	mt76_apply_array_limit(dest->path.ofdm, ARRAY_SIZE(dest->path.ofdm), val,
437			       target_power_combine, txs_delta, &max_power_backoff);
438
439	val = mt76_get_of_array_s8(np, "paths-ofdm-bf", &len, ARRAY_SIZE(dest->path.ofdm_bf));
440	mt76_apply_array_limit(dest->path.ofdm_bf, ARRAY_SIZE(dest->path.ofdm_bf), val,
441			       target_power_combine, txs_delta, &max_power_backoff);
442
443	val = mt76_get_of_array_s8(np, "paths-ru", &len, ARRAY_SIZE(dest->path.ru[0]) + 1);
444	mt76_apply_multi_array_limit(dest->path.ru[0], ARRAY_SIZE(dest->path.ru[0]),
445				     ARRAY_SIZE(dest->path.ru), val, len,
446				     target_power_combine, txs_delta);
447
448	val = mt76_get_of_array_s8(np, "paths-ru-bf", &len, ARRAY_SIZE(dest->path.ru_bf[0]) + 1);
449	mt76_apply_multi_array_limit(dest->path.ru_bf[0], ARRAY_SIZE(dest->path.ru_bf[0]),
450				     ARRAY_SIZE(dest->path.ru_bf), val, len,
451				     target_power_combine, txs_delta);
452
453	return max_power;
454}
455EXPORT_SYMBOL_GPL(mt76_get_rate_power_limits);
456
457int
458mt76_eeprom_init(struct mt76_dev *dev, int len)
459{
460	dev->eeprom.size = len;
461	dev->eeprom.data = devm_kzalloc(dev->dev, len, GFP_KERNEL);
462	if (!dev->eeprom.data)
463		return -ENOMEM;
464
465	return !mt76_get_of_eeprom(dev, dev->eeprom.data, len);
466}
467EXPORT_SYMBOL_GPL(mt76_eeprom_init);
~/ Read-only file preview.