diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath9k')
-rw-r--r-- | drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c | 105 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/debug.c | 57 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/debug.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/eeprom.c | 12 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/hw.h | 2 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/init.c | 58 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/main.c | 4 |
7 files changed, 204 insertions, 35 deletions
diff --git a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c index 56d1a7764b9f..708c8969b503 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c +++ b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c @@ -19,9 +19,14 @@ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/ath9k_platform.h> +#include <linux/nvmem-consumer.h> +#include <linux/workqueue.h> struct owl_ctx { + struct pci_dev *pdev; struct completion eeprom_load; + struct work_struct work; + struct nvmem_cell *cell; }; #define EEPROM_FILENAME_LEN 100 @@ -42,6 +47,12 @@ static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, u32 bar0; bool swap_needed = false; + /* also note that we are doing *u16 operations on the file */ + if (cal_len > 4096 || cal_len < 0x200 || (cal_len & 1) == 1) { + dev_err(&pdev->dev, "eeprom has an invalid size.\n"); + return -EINVAL; + } + if (*cal_data != AR5416_EEPROM_MAGIC) { if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { dev_err(&pdev->dev, "invalid calibration data\n"); @@ -99,38 +110,31 @@ static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, return 0; } -static void owl_fw_cb(const struct firmware *fw, void *context) +static void owl_rescan(struct pci_dev *pdev) { - struct pci_dev *pdev = (struct pci_dev *)context; - struct owl_ctx *ctx = (struct owl_ctx *)pci_get_drvdata(pdev); - struct pci_bus *bus; - - complete(&ctx->eeprom_load); - - if (!fw) { - dev_err(&pdev->dev, "no eeprom data received.\n"); - goto release; - } - - /* also note that we are doing *u16 operations on the file */ - if (fw->size > 4096 || fw->size < 0x200 || (fw->size & 1) == 1) { - dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); - goto release; - } - - if (ath9k_pci_fixup(pdev, (const u16 *)fw->data, fw->size)) - goto release; + struct pci_bus *bus = pdev->bus; pci_lock_rescan_remove(); - bus = pdev->bus; pci_stop_and_remove_bus_device(pdev); /* the device should come back with the proper * ProductId. But we have to initiate a rescan. */ pci_rescan_bus(bus); pci_unlock_rescan_remove(); +} + +static void owl_fw_cb(const struct firmware *fw, void *context) +{ + struct owl_ctx *ctx = (struct owl_ctx *)context; + + complete(&ctx->eeprom_load); -release: + if (fw) { + ath9k_pci_fixup(ctx->pdev, (const u16 *)fw->data, fw->size); + owl_rescan(ctx->pdev); + } else { + dev_err(&ctx->pdev->dev, "no eeprom data received.\n"); + } release_firmware(fw); } @@ -152,6 +156,43 @@ static const char *owl_get_eeprom_name(struct pci_dev *pdev) return eeprom_name; } +static void owl_nvmem_work(struct work_struct *work) +{ + struct owl_ctx *ctx = container_of(work, struct owl_ctx, work); + void *buf; + size_t len; + + complete(&ctx->eeprom_load); + + buf = nvmem_cell_read(ctx->cell, &len); + if (!IS_ERR(buf)) { + ath9k_pci_fixup(ctx->pdev, buf, len); + kfree(buf); + owl_rescan(ctx->pdev); + } else { + dev_err(&ctx->pdev->dev, "no nvmem data received.\n"); + } +} + +static int owl_nvmem_probe(struct owl_ctx *ctx) +{ + int err; + + ctx->cell = devm_nvmem_cell_get(&ctx->pdev->dev, "calibration"); + if (IS_ERR(ctx->cell)) { + err = PTR_ERR(ctx->cell); + if (err == -ENOENT || err == -EOPNOTSUPP) + return 1; /* not present, try firmware_request */ + + return err; + } + + INIT_WORK(&ctx->work, owl_nvmem_work); + schedule_work(&ctx->work); + + return 0; +} + static int owl_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -164,21 +205,27 @@ static int owl_probe(struct pci_dev *pdev, pcim_pin_device(pdev); - eeprom_name = owl_get_eeprom_name(pdev); - if (!eeprom_name) { - dev_err(&pdev->dev, "no eeprom filename found.\n"); - return -ENODEV; - } - ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; init_completion(&ctx->eeprom_load); + ctx->pdev = pdev; pci_set_drvdata(pdev, ctx); + + err = owl_nvmem_probe(ctx); + if (err <= 0) + return err; + + eeprom_name = owl_get_eeprom_name(pdev); + if (!eeprom_name) { + dev_err(&pdev->dev, "no eeprom filename found.\n"); + return -ENODEV; + } + err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, - &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); + &pdev->dev, GFP_KERNEL, ctx, owl_fw_cb); if (err) dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); diff --git a/drivers/net/wireless/ath/ath9k/debug.c b/drivers/net/wireless/ath/ath9k/debug.c index 4c81b1d7f417..fb7a2952d0ce 100644 --- a/drivers/net/wireless/ath/ath9k/debug.c +++ b/drivers/net/wireless/ath/ath9k/debug.c @@ -749,9 +749,9 @@ static int read_file_misc(struct seq_file *file, void *data) static int read_file_reset(struct seq_file *file, void *data) { - struct ieee80211_hw *hw = dev_get_drvdata(file->private); - struct ath_softc *sc = hw->priv; + struct ath_softc *sc = file->private; static const char * const reset_cause[__RESET_TYPE_MAX] = { + [RESET_TYPE_USER] = "User reset", [RESET_TYPE_BB_HANG] = "Baseband Hang", [RESET_TYPE_BB_WATCHDOG] = "Baseband Watchdog", [RESET_TYPE_FATAL_INT] = "Fatal HW Error", @@ -779,6 +779,55 @@ static int read_file_reset(struct seq_file *file, void *data) return 0; } +static int open_file_reset(struct inode *inode, struct file *f) +{ + return single_open(f, read_file_reset, inode->i_private); +} + +static ssize_t write_file_reset(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file_inode(file)->i_private; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val != 1) + return -EINVAL; + + /* avoid rearming hw_reset_work on shutdown */ + mutex_lock(&sc->mutex); + if (test_bit(ATH_OP_INVALID, &common->op_flags)) { + mutex_unlock(&sc->mutex); + return -EBUSY; + } + + ath9k_queue_reset(sc, RESET_TYPE_USER); + mutex_unlock(&sc->mutex); + + return count; +} + +static const struct file_operations fops_reset = { + .read = seq_read, + .write = write_file_reset, + .open = open_file_reset, + .owner = THIS_MODULE, + .llseek = seq_lseek, + .release = single_release, +}; + void ath_debug_stat_tx(struct ath_softc *sc, struct ath_buf *bf, struct ath_tx_status *ts, struct ath_txq *txq, unsigned int flags) @@ -1393,8 +1442,8 @@ int ath9k_init_debug(struct ath_hw *ah) read_file_queues); debugfs_create_devm_seqfile(sc->dev, "misc", sc->debug.debugfs_phy, read_file_misc); - debugfs_create_devm_seqfile(sc->dev, "reset", sc->debug.debugfs_phy, - read_file_reset); + debugfs_create_file("reset", 0600, sc->debug.debugfs_phy, + sc, &fops_reset); ath9k_cmn_debug_recv(sc->debug.debugfs_phy, &sc->debug.stats.rxstats); ath9k_cmn_debug_phy_err(sc->debug.debugfs_phy, &sc->debug.stats.rxstats); diff --git a/drivers/net/wireless/ath/ath9k/debug.h b/drivers/net/wireless/ath/ath9k/debug.h index 33826aa13687..389459c04d14 100644 --- a/drivers/net/wireless/ath/ath9k/debug.h +++ b/drivers/net/wireless/ath/ath9k/debug.h @@ -39,6 +39,7 @@ struct fft_sample_tlv; #endif enum ath_reset_type { + RESET_TYPE_USER, RESET_TYPE_BB_HANG, RESET_TYPE_BB_WATCHDOG, RESET_TYPE_FATAL_INT, diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c index c22d457dbc54..e6b3cd49ea18 100644 --- 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(const struct firmware *eeprom_blob, 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); diff --git a/drivers/net/wireless/ath/ath9k/hw.h b/drivers/net/wireless/ath/ath9k/hw.h index b7b65b1c90e8..096a206f49ed 100644 --- a/drivers/net/wireless/ath/ath9k/hw.h +++ b/drivers/net/wireless/ath/ath9k/hw.h @@ -977,6 +977,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; diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index e9a36dd7144f..4f00400c7ffb 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -22,6 +22,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_net.h> +#include <linux/nvmem-consumer.h> #include <linux/relay.h> #include <linux/dmi.h> #include <net/ieee80211_radiotap.h> @@ -568,6 +569,57 @@ static void ath9k_eeprom_release(struct ath_softc *sc) 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; @@ -704,6 +756,10 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc, 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; @@ -1038,6 +1094,8 @@ int ath9k_init_device(u16 devid, struct ath_softc *sc, ARRAY_SIZE(ath9k_tpt_blink)); #endif + wiphy_read_of_freq_limits(hw->wiphy); + /* Register with mac80211 */ error = ieee80211_register_hw(hw); if (error) diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index 139831539da3..98090e40e1cf 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -533,8 +533,10 @@ irqreturn_t ath_isr(int irq, void *dev) ath9k_debug_sync_cause(sc, sync_cause); status &= ah->imask; /* discard unasked-for bits */ - if (test_bit(ATH_OP_HW_RESET, &common->op_flags)) + if (test_bit(ATH_OP_HW_RESET, &common->op_flags)) { + ath9k_hw_kill_interrupts(sc->sc_ah); return IRQ_HANDLED; + } /* * If there are no status bits set, then this interrupt was not |