From dab16ef495dbb3cabb355b6c80f0771a4a25e35d Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Fri, 20 Aug 2021 22:44:52 +0200 Subject: [PATCH] ath9k: fetch calibration data via nvmem subsystem On most embedded ath9k devices (like range extenders, routers, accesspoints, ...) the calibration data is stored in a MTD partitions named "ART", or "caldata"/ "calibration". Ever since commit 4b361cfa8624 ("mtd: core: add OTP nvmem provider support") all MTD partitions are all automatically available through the nvmem subsystem. This allows drivers like ath9k to read the necessary data without needing any userspace helpers that would do this extraction. Signed-off-by: Christian Lamparter --- includes: From 57671351379b2051cfb07fc14e0bead9916a0880 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 11 Oct 2021 18:18:01 +0300 Subject: ath9k: fix an IS_ERR() vs NULL check The devm_kmemdup() function doesn't return error pointers, it returns NULL on error. Fixes: eb3a97a69be8 ("ath9k: fetch calibration data via nvmem subsystem") Signed-off-by: Dan Carpenter Signed-off-by: Kalle Valo Link: https://lore.kernel.org/r/20211011123533.GA15188@kili --- --- a/drivers/net/wireless/ath/ath9k/eeprom.c +++ b/drivers/net/wireless/ath/ath9k/eeprom.c @@ -135,13 +135,23 @@ static bool ath9k_hw_nvram_read_firmware offset, data); } +static bool ath9k_hw_nvram_read_nvmem(struct ath_hw *ah, off_t offset, + u16 *data) +{ + return ath9k_hw_nvram_read_array(ah->nvmem_blob, + ah->nvmem_blob_len / sizeof(u16), + offset, data); +} + bool ath9k_hw_nvram_read(struct ath_hw *ah, u32 off, u16 *data) { struct ath_common *common = ath9k_hw_common(ah); struct ath9k_platform_data *pdata = ah->dev->platform_data; bool ret; - if (ah->eeprom_blob) + if (ah->nvmem_blob) + ret = ath9k_hw_nvram_read_nvmem(ah, off, data); + else if (ah->eeprom_blob) ret = ath9k_hw_nvram_read_firmware(ah->eeprom_blob, off, data); else if (pdata && !pdata->use_eeprom) ret = ath9k_hw_nvram_read_pdata(pdata, off, data); --- a/drivers/net/wireless/ath/ath9k/hw.h +++ b/drivers/net/wireless/ath/ath9k/hw.h @@ -988,6 +988,8 @@ struct ath_hw { bool disable_5ghz; const struct firmware *eeprom_blob; + u16 *nvmem_blob; /* devres managed */ + size_t nvmem_blob_len; struct ath_dynack dynack; --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -568,6 +569,57 @@ static void ath9k_eeprom_release(struct release_firmware(sc->sc_ah->eeprom_blob); } +static int ath9k_nvmem_request_eeprom(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct nvmem_cell *cell; + void *buf; + size_t len; + int err; + + cell = devm_nvmem_cell_get(sc->dev, "calibration"); + if (IS_ERR(cell)) { + err = PTR_ERR(cell); + + /* nvmem cell might not be defined, or the nvmem + * subsystem isn't included. In this case, follow + * the established "just return 0;" convention of + * ath9k_init_platform to say: + * "All good. Nothing to see here. Please go on." + */ + if (err == -ENOENT || err == -EOPNOTSUPP) + return 0; + + return err; + } + + buf = nvmem_cell_read(cell, &len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* run basic sanity checks on the returned nvram cell length. + * That length has to be a multiple of a "u16" (i.e.: & 1). + * Furthermore, it has to be more than "let's say" 512 bytes + * but less than the maximum of AR9300_EEPROM_SIZE (16kb). + */ + if (((len & 1) == 1) || (len < 512) || (len >= AR9300_EEPROM_SIZE)) { + kfree(buf); + return -EINVAL; + } + + /* devres manages the calibration values release on shutdown */ + ah->nvmem_blob = (u16 *)devm_kmemdup(sc->dev, buf, len, GFP_KERNEL); + kfree(buf); + if (!ah->nvmem_blob) + return -ENOMEM; + + ah->nvmem_blob_len = len; + ah->ah_flags &= ~AH_USE_EEPROM; + ah->ah_flags |= AH_NO_EEP_SWAP; + + return 0; +} + static int ath9k_init_platform(struct ath_softc *sc) { struct ath9k_platform_data *pdata = sc->dev->platform_data; @@ -710,6 +762,10 @@ static int ath9k_init_softc(u16 devid, s if (ret) return ret; + ret = ath9k_nvmem_request_eeprom(sc); + if (ret) + return ret; + if (ath9k_led_active_high != -1) ah->config.led_active_high = ath9k_led_active_high == 1;