diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/Kconfig linux-6.15.6/drivers/net/wireless/intersil/Kconfig --- linux-6.15.6.orig/drivers/net/wireless/intersil/Kconfig 2025-07-10 16:08:55.000000000 +0200 +++ linux-6.15.6/drivers/net/wireless/intersil/Kconfig 2025-08-03 00:20:27.566744589 +0200 @@ -12,6 +12,7 @@ if WLAN_VENDOR_INTERSIL +source "drivers/net/wireless/intersil/orinoco/Kconfig" source "drivers/net/wireless/intersil/p54/Kconfig" endif # WLAN_VENDOR_INTERSIL diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/Makefile linux-6.15.6/drivers/net/wireless/intersil/Makefile --- linux-6.15.6.orig/drivers/net/wireless/intersil/Makefile 2025-07-10 16:08:55.000000000 +0200 +++ linux-6.15.6/drivers/net/wireless/intersil/Makefile 2025-08-03 00:20:54.161967083 +0200 @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_HERMES) += orinoco/ obj-$(CONFIG_P54_COMMON) += p54/ diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/airport.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/airport.c --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/airport.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/airport.c 2025-08-03 15:01:59.313131673 +0200 @@ -0,0 +1,268 @@ +/* airport.c + * + * A driver for "Hermes" chipset based Apple Airport wireless + * card. + * + * Copyright notice & release notes in file main.c + * + * Note specific to airport stub: + * + * 0.05 : first version of the new split driver + * 0.06 : fix possible hang on powerup, add sleep support + */ + +#define DRIVER_NAME "airport" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" + +#define AIRPORT_IO_LEN (0x1000) /* one page */ + +struct airport { + struct macio_dev *mdev; + void __iomem *vaddr; + unsigned int irq; + int irq_requested; + int ndev_registered; +}; + +static int +airport_suspend(struct macio_dev *mdev, pm_message_t state) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct net_device *dev = priv->ndev; + struct airport *card = priv->card; + unsigned long flags; + int err; + + printk(KERN_DEBUG "%s: Airport entering sleep mode\n", dev->name); + + err = orinoco_lock(priv, &flags); + if (err) { + printk(KERN_ERR "%s: hw_unavailable on PBOOK_SLEEP_NOW\n", + dev->name); + return 0; + } + + orinoco_down(priv); + orinoco_unlock(priv, &flags); + + disable_irq(card->irq); + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 0); + + return 0; +} + +static int +airport_resume(struct macio_dev *mdev) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct net_device *dev = priv->ndev; + struct airport *card = priv->card; + unsigned long flags; + int err; + + printk(KERN_DEBUG "%s: Airport waking up\n", dev->name); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 1); + msleep(200); + + enable_irq(card->irq); + + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + err = orinoco_up(priv); + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); + + return err; +} + +static int +airport_detach(struct macio_dev *mdev) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct airport *card = priv->card; + + if (card->ndev_registered) + orinoco_if_del(priv); + card->ndev_registered = 0; + + if (card->irq_requested) + free_irq(card->irq, priv); + card->irq_requested = 0; + + if (card->vaddr) + iounmap(card->vaddr); + card->vaddr = NULL; + + macio_release_resource(mdev, 0); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 0); + ssleep(1); + + macio_set_drvdata(mdev, NULL); + free_orinocodev(priv); + + return 0; +} + +static int airport_hard_reset(struct orinoco_private *priv) +{ + /* It would be nice to power cycle the Airport for a real hard + * reset, but for some reason although it appears to + * re-initialize properly, it falls in a screaming heap + * shortly afterwards. */ +#if 0 + struct airport *card = priv->card; + + /* Vitally important. If we don't do this it seems we get an + * interrupt somewhere during the power cycle, since + * hw_unavailable is already set it doesn't get ACKed, we get + * into an interrupt loop and the PMU decides to turn us + * off. */ + disable_irq(card->irq); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(card->mdev), 0, 0); + ssleep(1); + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(card->mdev), 0, 1); + ssleep(1); + + enable_irq(card->irq); + ssleep(1); +#endif + + return 0; +} + +static int +airport_attach(struct macio_dev *mdev, const struct of_device_id *match) +{ + struct orinoco_private *priv; + struct airport *card; + unsigned long phys_addr; + struct hermes *hw; + + if (macio_resource_count(mdev) < 1 || macio_irq_count(mdev) < 1) { + printk(KERN_ERR PFX "Wrong interrupt/addresses in OF tree\n"); + return -ENODEV; + } + + /* Allocate space for private device-specific data */ + priv = alloc_orinocodev(sizeof(*card), &mdev->ofdev.dev, + airport_hard_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + return -ENODEV; + } + card = priv->card; + + hw = &priv->hw; + card->mdev = mdev; + + if (macio_request_resource(mdev, 0, DRIVER_NAME)) { + printk(KERN_ERR PFX "can't request IO resource !\n"); + free_orinocodev(priv); + return -EBUSY; + } + + macio_set_drvdata(mdev, priv); + + /* Setup interrupts & base address */ + card->irq = macio_irq(mdev, 0); + phys_addr = macio_resource_start(mdev, 0); /* Physical address */ + printk(KERN_DEBUG PFX "Physical address %lx\n", phys_addr); + card->vaddr = ioremap(phys_addr, AIRPORT_IO_LEN); + if (!card->vaddr) { + printk(KERN_ERR PFX "ioremap() failed\n"); + goto failed; + } + + hermes_struct_init(hw, card->vaddr, HERMES_16BIT_REGSPACING); + + /* Power up card */ + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 1); + ssleep(1); + + /* Reset it before we get the interrupt */ + hw->ops->init(hw); + + if (request_irq(card->irq, orinoco_interrupt, 0, DRIVER_NAME, priv)) { + printk(KERN_ERR PFX "Couldn't get IRQ %d\n", card->irq); + goto failed; + } + card->irq_requested = 1; + + /* Initialise the main driver */ + if (orinoco_init(priv) != 0) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto failed; + } + + /* Register an interface with the stack */ + if (orinoco_if_add(priv, phys_addr, card->irq, NULL) != 0) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto failed; + } + card->ndev_registered = 1; + return 0; + failed: + airport_detach(mdev); + return -ENODEV; +} /* airport_attach */ + + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Benjamin Herrenschmidt )"; +MODULE_AUTHOR("Benjamin Herrenschmidt "); +MODULE_DESCRIPTION("Driver for the Apple Airport wireless card."); +MODULE_LICENSE("Dual MPL/GPL"); + +static const struct of_device_id airport_match[] = { + { + .name = "radio", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, airport_match); + +static struct macio_driver airport_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = airport_match, + }, + .probe = airport_attach, + .remove = airport_detach, + .suspend = airport_suspend, + .resume = airport_resume, +}; + +static int __init +init_airport(void) +{ + printk(KERN_DEBUG "%s\n", version); + + return macio_register_driver(&airport_driver); +} + +static void __exit +exit_airport(void) +{ + macio_unregister_driver(&airport_driver); +} + +module_init(init_airport); +module_exit(exit_airport); diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.c --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.c 2025-08-03 16:35:08.118093622 +0200 @@ -0,0 +1,292 @@ +/* cfg80211 support + * + * See copyright notice in main.c + */ +#include +#include +#include "hw.h" +#include "main.h" +#include "orinoco.h" + +#include "cfg.h" + +/* Supported bitrates. Must agree with hw.c */ +static struct ieee80211_rate orinoco_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20 }, + { .bitrate = 55 }, + { .bitrate = 110 }, +}; + +static const void * const orinoco_wiphy_privid = &orinoco_wiphy_privid; + +/* Called after orinoco_private is allocated. */ +void orinoco_wiphy_init(struct wiphy *wiphy) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + + wiphy->privid = orinoco_wiphy_privid; + + set_wiphy_dev(wiphy, priv->dev); +} + +/* Called after firmware is initialised */ +int orinoco_wiphy_register(struct wiphy *wiphy) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int i, channels = 0; + + if (priv->firmware_type == FIRMWARE_TYPE_AGERE) + wiphy->max_scan_ssids = 1; + else + wiphy->max_scan_ssids = 0; + + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + + /* TODO: should we set if we only have demo ad-hoc? + * (priv->has_port3) + */ + if (priv->has_ibss) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); + + if (!priv->broken_monitor || force_monitor) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + + priv->band.bitrates = orinoco_rates; + priv->band.n_bitrates = ARRAY_SIZE(orinoco_rates); + + /* Only support channels allowed by the card EEPROM */ + for (i = 0; i < NUM_CHANNELS; i++) { + if (priv->channel_mask & (1 << i)) { + priv->channels[i].center_freq = + ieee80211_channel_to_frequency(i + 1, + NL80211_BAND_2GHZ); + channels++; + } + } + priv->band.channels = priv->channels; + priv->band.n_channels = channels; + + wiphy->bands[NL80211_BAND_2GHZ] = &priv->band; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + i = 0; + if (priv->has_wep) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP40; + i++; + + if (priv->has_big_wep) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP104; + i++; + } + } + if (priv->has_wpa) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_TKIP; + i++; + } + wiphy->cipher_suites = priv->cipher_suites; + wiphy->n_cipher_suites = i; + + wiphy->rts_threshold = priv->rts_thresh; + if (!priv->has_mwo) + wiphy->frag_threshold = priv->frag_thresh + 1; + wiphy->retry_short = priv->short_retry_limit; + wiphy->retry_long = priv->long_retry_limit; + + return wiphy_register(wiphy); +} + +static int orinoco_change_vif(struct wiphy *wiphy, struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err = 0; + unsigned long lock; + + if (orinoco_lock(priv, &lock) != 0) + return -EBUSY; + + switch (type) { + case NL80211_IFTYPE_ADHOC: + if (!priv->has_ibss && !priv->has_port3) + err = -EINVAL; + break; + + case NL80211_IFTYPE_STATION: + break; + + case NL80211_IFTYPE_MONITOR: + if (priv->broken_monitor && !force_monitor) { + wiphy_warn(wiphy, + "Monitor mode support is buggy in this firmware, not enabling\n"); + err = -EINVAL; + } + break; + + default: + err = -EINVAL; + } + + if (!err) { + priv->iw_mode = type; + set_port_type(priv); + err = orinoco_commit(priv); + } + + orinoco_unlock(priv, &lock); + + return err; +} + +static int orinoco_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err; + + if (!request) + return -EINVAL; + + if (priv->scan_request && priv->scan_request != request) + return -EBUSY; + + priv->scan_request = request; + + err = orinoco_hw_trigger_scan(priv, request->ssids); + /* On error the we aren't processing the request */ + if (err) + priv->scan_request = NULL; + + return err; +} + +static int orinoco_set_monitor_channel(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err = 0; + unsigned long flags; + int channel; + + if (!chandef->chan) + return -EINVAL; + + if (cfg80211_get_chandef_type(chandef) != NL80211_CHAN_NO_HT) + return -EINVAL; + + if (chandef->chan->band != NL80211_BAND_2GHZ) + return -EINVAL; + + channel = ieee80211_frequency_to_channel(chandef->chan->center_freq); + + if ((channel < 1) || (channel > NUM_CHANNELS) || + !(priv->channel_mask & (1 << (channel - 1)))) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + priv->channel = channel; + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + /* Fast channel change - no commit if successful */ + struct hermes *hw = &priv->hw; + err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST | + HERMES_TEST_SET_CHANNEL, + channel, NULL); + } + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int frag_value = -1; + int rts_value = -1; + int err = 0; + + if (changed & WIPHY_PARAM_RETRY_SHORT) { + /* Setting short retry not supported */ + err = -EINVAL; + } + + if (changed & WIPHY_PARAM_RETRY_LONG) { + /* Setting long retry not supported */ + err = -EINVAL; + } + + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { + /* Set fragmentation */ + if (priv->has_mwo) { + if (wiphy->frag_threshold == -1) + frag_value = 0; + else { + printk(KERN_WARNING "%s: Fixed fragmentation " + "is not supported on this firmware. " + "Using MWO robust instead.\n", + priv->ndev->name); + frag_value = 1; + } + } else { + if (wiphy->frag_threshold == -1) + frag_value = 2346; + else if ((wiphy->frag_threshold < 257) || + (wiphy->frag_threshold > 2347)) + err = -EINVAL; + else + /* cfg80211 value is 257-2347 (odd only) + * orinoco rid has range 256-2346 (even only) */ + frag_value = wiphy->frag_threshold & ~0x1; + } + } + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + /* Set RTS. + * + * Prism documentation suggests default of 2432, + * and a range of 0-3000. + * + * Current implementation uses 2347 as the default and + * the upper limit. + */ + + if (wiphy->rts_threshold == -1) + rts_value = 2347; + else if (wiphy->rts_threshold > 2347) + err = -EINVAL; + else + rts_value = wiphy->rts_threshold; + } + + if (!err) { + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if (frag_value >= 0) { + if (priv->has_mwo) + priv->mwo_robust = frag_value; + else + priv->frag_thresh = frag_value; + } + if (rts_value >= 0) + priv->rts_thresh = rts_value; + + err = orinoco_commit(priv); + + orinoco_unlock(priv, &flags); + } + + return err; +} + +const struct cfg80211_ops orinoco_cfg_ops = { + .change_virtual_intf = orinoco_change_vif, + .set_monitor_channel = orinoco_set_monitor_channel, + .scan = orinoco_scan, + .set_wiphy_params = orinoco_set_wiphy_params, +}; diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.h --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.h 2025-08-03 15:01:59.313131673 +0200 @@ -0,0 +1,15 @@ +/* cfg80211 support. + * + * See copyright notice in main.c + */ +#ifndef ORINOCO_CFG_H +#define ORINOCO_CFG_H + +#include + +extern const struct cfg80211_ops orinoco_cfg_ops; + +void orinoco_wiphy_init(struct wiphy *wiphy); +int orinoco_wiphy_register(struct wiphy *wiphy); + +#endif /* ORINOCO_CFG_H */ diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.c --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.c 2025-08-03 15:01:59.313131673 +0200 @@ -0,0 +1,387 @@ +/* Firmware file reading and download helpers + * + * See copyright notice in main.c + */ +#include +#include +#include +#include +#include + +#include "hermes.h" +#include "hermes_dld.h" +#include "orinoco.h" + +#include "fw.h" + +/* End markers (for Symbol firmware only) */ +#define TEXT_END 0x1A /* End of text header */ + +struct fw_info { + char *pri_fw; + char *sta_fw; + char *ap_fw; + u32 pda_addr; + u16 pda_size; +}; + +static const struct fw_info orinoco_fw[] = { + { NULL, "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 }, + { NULL, "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 }, + { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", NULL, 0x00003100, 512 } +}; +MODULE_FIRMWARE("agere_sta_fw.bin"); +MODULE_FIRMWARE("agere_ap_fw.bin"); +MODULE_FIRMWARE("prism_sta_fw.bin"); +MODULE_FIRMWARE("prism_ap_fw.bin"); +MODULE_FIRMWARE("symbol_sp24t_prim_fw"); +MODULE_FIRMWARE("symbol_sp24t_sec_fw"); + +/* Structure used to access fields in FW + * Make sure LE decoding macros are used + */ +struct orinoco_fw_header { + char hdr_vers[6]; /* ASCII string for header version */ + __le16 headersize; /* Total length of header */ + __le32 entry_point; /* NIC entry point */ + __le32 blocks; /* Number of blocks to program */ + __le32 block_offset; /* Offset of block data from eof header */ + __le32 pdr_offset; /* Offset to PDR data from eof header */ + __le32 pri_offset; /* Offset to primary plug data */ + __le32 compat_offset; /* Offset to compatibility data*/ + char signature[]; /* FW signature length headersize-20 */ +} __packed; + +/* Check the range of various header entries. Return a pointer to a + * description of the problem, or NULL if everything checks out. */ +static const char *validate_fw(const struct orinoco_fw_header *hdr, size_t len) +{ + u16 hdrsize; + + if (len < sizeof(*hdr)) + return "image too small"; + if (memcmp(hdr->hdr_vers, "HFW", 3) != 0) + return "format not recognised"; + + hdrsize = le16_to_cpu(hdr->headersize); + if (hdrsize > len) + return "bad headersize"; + if ((hdrsize + le32_to_cpu(hdr->block_offset)) > len) + return "bad block offset"; + if ((hdrsize + le32_to_cpu(hdr->pdr_offset)) > len) + return "bad PDR offset"; + if ((hdrsize + le32_to_cpu(hdr->pri_offset)) > len) + return "bad PRI offset"; + if ((hdrsize + le32_to_cpu(hdr->compat_offset)) > len) + return "bad compat offset"; + + /* TODO: consider adding a checksum or CRC to the firmware format */ + return NULL; +} + +#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +static inline const struct firmware * +orinoco_cached_fw_get(struct orinoco_private *priv, bool primary) +{ + if (primary) + return priv->cached_pri_fw; + else + return priv->cached_fw; +} +#else +#define orinoco_cached_fw_get(priv, primary) (NULL) +#endif + +/* Download either STA or AP firmware into the card. */ +static int +orinoco_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw, + int ap) +{ + /* Plug Data Area (PDA) */ + __le16 *pda; + + struct hermes *hw = &priv->hw; + const struct firmware *fw_entry; + const struct orinoco_fw_header *hdr; + const unsigned char *first_block; + const void *end; + const char *firmware; + const char *fw_err; + struct device *dev = priv->dev; + int err = 0; + + pda = kzalloc(fw->pda_size, GFP_KERNEL); + if (!pda) + return -ENOMEM; + + if (ap) + firmware = fw->ap_fw; + else + firmware = fw->sta_fw; + + dev_dbg(dev, "Attempting to download firmware %s\n", firmware); + + /* Read current plug data */ + err = hw->ops->read_pda(hw, pda, fw->pda_addr, fw->pda_size); + dev_dbg(dev, "Read PDA returned %d\n", err); + if (err) + goto free; + + if (!orinoco_cached_fw_get(priv, false)) { + err = request_firmware(&fw_entry, firmware, priv->dev); + + if (err) { + dev_err(dev, "Cannot find firmware %s\n", firmware); + err = -ENOENT; + goto free; + } + } else + fw_entry = orinoco_cached_fw_get(priv, false); + + hdr = (const struct orinoco_fw_header *) fw_entry->data; + + fw_err = validate_fw(hdr, fw_entry->size); + if (fw_err) { + dev_warn(dev, "Invalid firmware image detected (%s). " + "Aborting download\n", fw_err); + err = -EINVAL; + goto abort; + } + + /* Enable aux port to allow programming */ + err = hw->ops->program_init(hw, le32_to_cpu(hdr->entry_point)); + dev_dbg(dev, "Program init returned %d\n", err); + if (err != 0) + goto abort; + + /* Program data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->block_offset)); + end = fw_entry->data + fw_entry->size; + + err = hermes_program(hw, first_block, end); + dev_dbg(dev, "Program returned %d\n", err); + if (err != 0) + goto abort; + + /* Update production data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->pdr_offset)); + + err = hermes_apply_pda_with_defaults(hw, first_block, end, pda, + &pda[fw->pda_size / sizeof(*pda)]); + dev_dbg(dev, "Apply PDA returned %d\n", err); + if (err) + goto abort; + + /* Tell card we've finished */ + err = hw->ops->program_end(hw); + dev_dbg(dev, "Program end returned %d\n", err); + if (err != 0) + goto abort; + + /* Check if we're running */ + dev_dbg(dev, "hermes_present returned %d\n", hermes_present(hw)); + +abort: + /* If we requested the firmware, release it. */ + if (!orinoco_cached_fw_get(priv, false)) + release_firmware(fw_entry); + +free: + kfree(pda); + return err; +} + +/* + * Process a firmware image - stop the card, load the firmware, reset + * the card and make sure it responds. For the secondary firmware take + * care of the PDA - read it and then write it on top of the firmware. + */ +static int +symbol_dl_image(struct orinoco_private *priv, const struct fw_info *fw, + const unsigned char *image, const void *end, + int secondary) +{ + struct hermes *hw = &priv->hw; + int ret = 0; + const unsigned char *ptr; + const unsigned char *first_block; + + /* Plug Data Area (PDA) */ + __le16 *pda = NULL; + + /* Binary block begins after the 0x1A marker */ + ptr = image; + while (*ptr++ != TEXT_END); + first_block = ptr; + + /* Read the PDA from EEPROM */ + if (secondary) { + pda = kzalloc(fw->pda_size, GFP_KERNEL); + if (!pda) + return -ENOMEM; + + ret = hw->ops->read_pda(hw, pda, fw->pda_addr, fw->pda_size); + if (ret) + goto free; + } + + /* Stop the firmware, so that it can be safely rewritten */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 1); + if (ret) + goto free; + } + + /* Program the adapter with new firmware */ + ret = hermes_program(hw, first_block, end); + if (ret) + goto free; + + /* Write the PDA to the adapter */ + if (secondary) { + size_t len = hermes_blocks_length(first_block, end); + ptr = first_block + len; + ret = hermes_apply_pda(hw, ptr, end, pda, + &pda[fw->pda_size / sizeof(*pda)]); + kfree(pda); + if (ret) + return ret; + } + + /* Run the firmware */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 0); + if (ret) + return ret; + } + + /* Reset hermes chip and make sure it responds */ + ret = hw->ops->init(hw); + + /* hermes_reset() should return 0 with the secondary firmware */ + if (secondary && ret != 0) + return -ENODEV; + + /* And this should work with any firmware */ + if (!hermes_present(hw)) + return -ENODEV; + + return 0; + +free: + kfree(pda); + return ret; +} + + +/* + * Download the firmware into the card, this also does a PCMCIA soft + * reset on the card, to make sure it's in a sane state. + */ +static int +symbol_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw) +{ + struct device *dev = priv->dev; + int ret; + const struct firmware *fw_entry; + + if (!orinoco_cached_fw_get(priv, true)) { + if (request_firmware(&fw_entry, fw->pri_fw, priv->dev) != 0) { + dev_err(dev, "Cannot find firmware: %s\n", fw->pri_fw); + return -ENOENT; + } + } else + fw_entry = orinoco_cached_fw_get(priv, true); + + /* Load primary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 0); + + if (!orinoco_cached_fw_get(priv, true)) + release_firmware(fw_entry); + if (ret) { + dev_err(dev, "Primary firmware download failed\n"); + return ret; + } + + if (!orinoco_cached_fw_get(priv, false)) { + if (request_firmware(&fw_entry, fw->sta_fw, priv->dev) != 0) { + dev_err(dev, "Cannot find firmware: %s\n", fw->sta_fw); + return -ENOENT; + } + } else + fw_entry = orinoco_cached_fw_get(priv, false); + + /* Load secondary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 1); + if (!orinoco_cached_fw_get(priv, false)) + release_firmware(fw_entry); + if (ret) + dev_err(dev, "Secondary firmware download failed\n"); + + return ret; +} + +int orinoco_download(struct orinoco_private *priv) +{ + int err = 0; + /* Reload firmware */ + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* case FIRMWARE_TYPE_INTERSIL: */ + err = orinoco_dl_firmware(priv, + &orinoco_fw[priv->firmware_type], 0); + break; + + case FIRMWARE_TYPE_SYMBOL: + err = symbol_dl_firmware(priv, + &orinoco_fw[priv->firmware_type]); + break; + case FIRMWARE_TYPE_INTERSIL: + break; + } + /* TODO: if we fail we probably need to reinitialise + * the driver */ + + return err; +} + +#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +void orinoco_cache_fw(struct orinoco_private *priv, int ap) +{ + const struct firmware *fw_entry = NULL; + const char *pri_fw; + const char *fw; + + pri_fw = orinoco_fw[priv->firmware_type].pri_fw; + if (ap) + fw = orinoco_fw[priv->firmware_type].ap_fw; + else + fw = orinoco_fw[priv->firmware_type].sta_fw; + + if (pri_fw) { + if (request_firmware(&fw_entry, pri_fw, priv->dev) == 0) + priv->cached_pri_fw = fw_entry; + } + + if (fw) { + if (request_firmware(&fw_entry, fw, priv->dev) == 0) + priv->cached_fw = fw_entry; + } +} + +void orinoco_uncache_fw(struct orinoco_private *priv) +{ + release_firmware(priv->cached_pri_fw); + release_firmware(priv->cached_fw); + priv->cached_pri_fw = NULL; + priv->cached_fw = NULL; +} +#endif diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.h --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.h 2025-08-03 15:01:59.313131673 +0200 @@ -0,0 +1,21 @@ +/* Firmware file reading and download helpers + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_FW_H_ +#define _ORINOCO_FW_H_ + +/* Forward declations */ +struct orinoco_private; + +int orinoco_download(struct orinoco_private *priv); + +#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +void orinoco_cache_fw(struct orinoco_private *priv, int ap); +void orinoco_uncache_fw(struct orinoco_private *priv); +#else +#define orinoco_cache_fw(priv, ap) do { } while (0) +#define orinoco_uncache_fw(priv) do { } while (0) +#endif + +#endif /* _ORINOCO_FW_H_ */ diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes.c --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes.c 2025-08-03 22:42:47.279513981 +0200 @@ -0,0 +1,778 @@ +/* hermes.c + * + * Driver core for the "Hermes" wireless MAC controller, as used in + * the Lucent Orinoco and Cabletron RoamAbout cards. It should also + * work on the hfa3841 and hfa3842 MAC controller chips used in the + * Prism II chipsets. + * + * This is not a complete driver, just low-level access routines for + * the MAC controller itself. + * + * Based on the prism2 driver from Absolute Value Systems' linux-wlan + * project, the Linux wvlan_cs driver, Lucent's HCF-Light + * (wvlan_hcf.c) library, and the NetBSD wireless driver (in no + * particular order). + * + * Copyright (C) 2000, David Gibson, Linuxcare Australia. + * (C) Copyright David Gibson, IBM Corp. 2001-2003. + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#include +#include +#include +#include + +#include "hermes.h" + +/* These are maximum timeouts. Most often, card wil react much faster */ +#define CMD_BUSY_TIMEOUT (100) /* In iterations of ~1us */ +#define CMD_INIT_TIMEOUT (50000) /* in iterations of ~10us */ +#define CMD_COMPL_TIMEOUT (20000) /* in iterations of ~10us */ +#define ALLOC_COMPL_TIMEOUT (1000) /* in iterations of ~10us */ + +/* + * AUX port access. To unlock the AUX port write the access keys to the + * PARAM0-2 registers, then write HERMES_AUX_ENABLE to the HERMES_CONTROL + * register. Then read it and make sure it's HERMES_AUX_ENABLED. + */ +#define HERMES_AUX_ENABLE 0x8000 /* Enable auxiliary port access */ +#define HERMES_AUX_DISABLE 0x4000 /* Disable to auxiliary port access */ +#define HERMES_AUX_ENABLED 0xC000 /* Auxiliary port is open */ +#define HERMES_AUX_DISABLED 0x0000 /* Auxiliary port is closed */ + +#define HERMES_AUX_PW0 0xFE01 +#define HERMES_AUX_PW1 0xDC23 +#define HERMES_AUX_PW2 0xBA45 + +/* HERMES_CMD_DOWNLD */ +#define HERMES_PROGRAM_DISABLE (0x0000 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_VOLATILE (0x0100 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_NON_VOLATILE (0x0200 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_NON_VOLATILE (0x0300 | HERMES_CMD_DOWNLD) + +/* + * Debugging helpers + */ + +#define DMSG(stuff...) do {printk(KERN_DEBUG "hermes @ %p: " , hw->iobase); \ + printk(stuff); } while (0) + +#undef HERMES_DEBUG +#ifdef HERMES_DEBUG + +#define DEBUG(lvl, stuff...) if ((lvl) <= HERMES_DEBUG) DMSG(stuff) + +#else /* ! HERMES_DEBUG */ + +#define DEBUG(lvl, stuff...) do { } while (0) + +#endif /* ! HERMES_DEBUG */ + +static const struct hermes_ops hermes_ops_local; + +/* + * Internal functions + */ + +/* Issue a command to the chip. Waiting for it to complete is the caller's + problem. + + Returns -EBUSY if the command register is busy, 0 on success. + + Callable from any context. +*/ +static int hermes_issue_cmd(struct hermes *hw, u16 cmd, u16 param0, + u16 param1, u16 param2) +{ + int k = CMD_BUSY_TIMEOUT; + u16 reg; + + /* First wait for the command register to unbusy */ + reg = hermes_read_regn(hw, CMD); + while ((reg & HERMES_CMD_BUSY) && k) { + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + if (reg & HERMES_CMD_BUSY) + return -EBUSY; + + hermes_write_regn(hw, PARAM2, param2); + hermes_write_regn(hw, PARAM1, param1); + hermes_write_regn(hw, PARAM0, param0); + hermes_write_regn(hw, CMD, cmd); + + return 0; +} + +/* + * Function definitions + */ + +/* For doing cmds that wipe the magic constant in SWSUPPORT0 */ +static int hermes_doicmd_wait(struct hermes *hw, u16 cmd, + u16 parm0, u16 parm1, u16 parm2, + struct hermes_response *resp) +{ + int err = 0; + int k; + u16 status, reg; + + err = hermes_issue_cmd(hw, cmd, parm0, parm1, parm2); + if (err) + return err; + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_INIT_TIMEOUT; + while ((!(reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + hermes_write_regn(hw, SWSUPPORT0, HERMES_MAGIC); + + if (!hermes_present(hw)) { + DEBUG(0, "hermes @ 0x%x: Card removed during reset.\n", + hw->iobase); + err = -ENODEV; + goto out; + } + + if (!(reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %p: " + "Timeout waiting for card to reset (reg=0x%04x)!\n", + hw->iobase, reg); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + if (resp) { + resp->status = status; + resp->resp0 = hermes_read_regn(hw, RESP0); + resp->resp1 = hermes_read_regn(hw, RESP1); + resp->resp2 = hermes_read_regn(hw, RESP2); + } + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; +out: + return err; +} + +void hermes_struct_init(struct hermes *hw, void __iomem *address, + int reg_spacing) +{ + hw->iobase = address; + hw->reg_spacing = reg_spacing; + hw->inten = 0x0; + hw->eeprom_pda = false; + hw->ops = &hermes_ops_local; +} +EXPORT_SYMBOL(hermes_struct_init); + +static int hermes_init(struct hermes *hw) +{ + u16 reg; + int err = 0; + int k; + + /* We don't want to be interrupted while resetting the chipset */ + hw->inten = 0x0; + hermes_write_regn(hw, INTEN, 0); + hermes_write_regn(hw, EVACK, 0xffff); + + /* Normally it's a "can't happen" for the command register to + be busy when we go to issue a command because we are + serializing all commands. However we want to have some + chance of resetting the card even if it gets into a stupid + state, so we actually wait to see if the command register + will unbusy itself here. */ + k = CMD_BUSY_TIMEOUT; + reg = hermes_read_regn(hw, CMD); + while (k && (reg & HERMES_CMD_BUSY)) { + if (reg == 0xffff) /* Special case - the card has probably been + removed, so don't wait for the timeout */ + return -ENODEV; + + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* No need to explicitly handle the timeout - if we've timed + out hermes_issue_cmd() will probably return -EBUSY below */ + + /* According to the documentation, EVSTAT may contain + obsolete event occurrence information. We have to acknowledge + it by writing EVACK. */ + reg = hermes_read_regn(hw, EVSTAT); + hermes_write_regn(hw, EVACK, reg); + + /* We don't use hermes_docmd_wait here, because the reset wipes + the magic constant in SWSUPPORT0 away, and it gets confused */ + err = hermes_doicmd_wait(hw, HERMES_CMD_INIT, 0, 0, 0, NULL); + + return err; +} + +/* Issue a command to the chip, and (busy!) wait for it to + * complete. + * + * Returns: + * < 0 on internal error + * 0 on success + * > 0 on error returned by the firmware + * + * Callable from any context, but locking is your problem. */ +static int hermes_docmd_wait(struct hermes *hw, u16 cmd, u16 parm0, + struct hermes_response *resp) +{ + int err; + int k; + u16 reg; + u16 status; + + err = hermes_issue_cmd(hw, cmd, parm0, 0, 0); + if (err) { + if (!hermes_present(hw)) { + if (net_ratelimit()) + printk(KERN_WARNING "hermes @ %p: " + "Card removed while issuing command " + "0x%04x.\n", hw->iobase, cmd); + err = -ENODEV; + } else + if (net_ratelimit()) + printk(KERN_ERR "hermes @ %p: " + "Error %d issuing command 0x%04x.\n", + hw->iobase, err, cmd); + goto out; + } + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_COMPL_TIMEOUT; + while ((!(reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (!hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %p: Card removed " + "while waiting for command 0x%04x completion.\n", + hw->iobase, cmd); + err = -ENODEV; + goto out; + } + + if (!(reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %p: Timeout waiting for " + "command 0x%04x completion.\n", hw->iobase, cmd); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + if (resp) { + resp->status = status; + resp->resp0 = hermes_read_regn(hw, RESP0); + resp->resp1 = hermes_read_regn(hw, RESP1); + resp->resp2 = hermes_read_regn(hw, RESP2); + } + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; + + out: + return err; +} + +static int hermes_allocate(struct hermes *hw, u16 size, u16 *fid) +{ + int err = 0; + int k; + u16 reg; + + if ((size < HERMES_ALLOC_LEN_MIN) || (size > HERMES_ALLOC_LEN_MAX)) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ALLOC, size, NULL); + if (err) + return err; + + reg = hermes_read_regn(hw, EVSTAT); + k = ALLOC_COMPL_TIMEOUT; + while ((!(reg & HERMES_EV_ALLOC)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (!hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %p: " + "Card removed waiting for frame allocation.\n", + hw->iobase); + return -ENODEV; + } + + if (!(reg & HERMES_EV_ALLOC)) { + printk(KERN_ERR "hermes @ %p: " + "Timeout waiting for frame allocation\n", + hw->iobase); + return -ETIMEDOUT; + } + + *fid = hermes_read_regn(hw, ALLOCFID); + hermes_write_regn(hw, EVACK, HERMES_EV_ALLOC); + + return 0; +} + +/* Set up a BAP to read a particular chunk of data from card's internal buffer. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error + * from firmware + * + * Callable from any context */ +static int hermes_bap_seek(struct hermes *hw, int bap, u16 id, u16 offset) +{ + int sreg = bap ? HERMES_SELECT1 : HERMES_SELECT0; + int oreg = bap ? HERMES_OFFSET1 : HERMES_OFFSET0; + int k; + u16 reg; + + /* Paranoia.. */ + if ((offset > HERMES_BAP_OFFSET_MAX) || (offset % 2)) + return -EINVAL; + + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ((reg & HERMES_OFFSET_BUSY) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + + if (reg & HERMES_OFFSET_BUSY) + return -ETIMEDOUT; + + /* Now we actually set up the transfer */ + hermes_write_reg(hw, sreg, id); + hermes_write_reg(hw, oreg, offset); + + /* Wait for the BAP to be ready */ + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ((reg & (HERMES_OFFSET_BUSY | HERMES_OFFSET_ERR)) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + + if (reg != offset) { + printk(KERN_ERR "hermes @ %p: BAP%d offset %s: " + "reg=0x%x id=0x%x offset=0x%x\n", hw->iobase, bap, + (reg & HERMES_OFFSET_BUSY) ? "timeout" : "error", + reg, id, offset); + + if (reg & HERMES_OFFSET_BUSY) + return -ETIMEDOUT; + + return -EIO; /* error or wrong offset */ + } + + return 0; +} + +/* Read a block of data from the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. len + * must be even. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error from firmware + */ +static int hermes_bap_pread(struct hermes *hw, int bap, void *buf, int len, + u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if ((len < 0) || (len % 2)) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_read_words(hw, dreg, buf, len / 2); + + out: + return err; +} + +/* Write a block of data to the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error from firmware + */ +static int hermes_bap_pwrite(struct hermes *hw, int bap, const void *buf, + int len, u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if (len < 0) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_write_bytes(hw, dreg, buf, len); + + out: + return err; +} + +/* Read a Length-Type-Value record from the card. + * + * If length is NULL, we ignore the length read from the card, and + * read the entire buffer regardless. This is useful because some of + * the configuration records appear to have incorrect lengths in + * practice. + * + * Callable from user or bh context. */ +static int hermes_read_ltv(struct hermes *hw, int bap, u16 rid, + unsigned bufsize, u16 *length, void *buf) +{ + int err = 0; + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + u16 rlength, rtype; + unsigned nwords; + + if (bufsize % 2) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS, rid, NULL); + if (err) + return err; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + rlength = hermes_read_reg(hw, dreg); + + if (!rlength) + return -ENODATA; + + rtype = hermes_read_reg(hw, dreg); + + if (length) + *length = rlength; + + if (rtype != rid) + printk(KERN_WARNING "hermes @ %p: %s(): " + "rid (0x%04x) does not match type (0x%04x)\n", + hw->iobase, __func__, rid, rtype); + if (HERMES_RECLEN_TO_BYTES(rlength) > bufsize) + printk(KERN_WARNING "hermes @ %p: " + "Truncating LTV record from %d to %d bytes. " + "(rid=0x%04x, len=0x%04x)\n", hw->iobase, + HERMES_RECLEN_TO_BYTES(rlength), bufsize, rid, rlength); + + nwords = min((unsigned)rlength - 1, bufsize / 2); + hermes_read_words(hw, dreg, buf, nwords); + + return 0; +} + +static int hermes_write_ltv(struct hermes *hw, int bap, u16 rid, + u16 length, const void *value) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + unsigned count; + + if (length == 0) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + hermes_write_reg(hw, dreg, length); + hermes_write_reg(hw, dreg, rid); + + count = length - 1; + + hermes_write_bytes(hw, dreg, value, count << 1); + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS | HERMES_CMD_WRITE, + rid, NULL); + + return err; +} + +/*** Hermes AUX control ***/ + +static inline void +hermes_aux_setaddr(struct hermes *hw, u32 addr) +{ + hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7)); + hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F)); +} + +static inline int +hermes_aux_control(struct hermes *hw, int enabled) +{ + int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED; + int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE; + int i; + + /* Already open? */ + if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state) + return 0; + + hermes_write_reg(hw, HERMES_PARAM0, HERMES_AUX_PW0); + hermes_write_reg(hw, HERMES_PARAM1, HERMES_AUX_PW1); + hermes_write_reg(hw, HERMES_PARAM2, HERMES_AUX_PW2); + hermes_write_reg(hw, HERMES_CONTROL, action); + + for (i = 0; i < 20; i++) { + udelay(10); + if (hermes_read_reg(hw, HERMES_CONTROL) == + desired_state) + return 0; + } + + return -EBUSY; +} + +/*** Hermes programming ***/ + +/* About to start programming data (Hermes I) + * offset is the entry point + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_init(struct hermes *hw, u32 offset) +{ + int err; + + /* Disable interrupts?*/ + /*hw->inten = 0x0;*/ + /*hermes_write_regn(hw, INTEN, 0);*/ + /*hermes_set_irqmask(hw, 0);*/ + + /* Acknowledge any outstanding command */ + hermes_write_regn(hw, EVACK, 0xFFFF); + + /* Using init_cmd_wait rather than cmd_wait */ + err = hw->ops->init_cmd_wait(hw, + 0x0100 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + if (err) + return err; + + err = hw->ops->init_cmd_wait(hw, + 0x0000 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + if (err) + return err; + + err = hermes_aux_control(hw, 1); + pr_debug("AUX enable returned %d\n", err); + + if (err) + return err; + + pr_debug("Enabling volatile, EP 0x%08x\n", offset); + err = hw->ops->init_cmd_wait(hw, + HERMES_PROGRAM_ENABLE_VOLATILE, + offset & 0xFFFFu, + offset >> 16, + 0, + NULL); + pr_debug("PROGRAM_ENABLE returned %d\n", err); + + return err; +} + +/* Done programming data (Hermes I) + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_end(struct hermes *hw) +{ + struct hermes_response resp; + int rc = 0; + int err; + + rc = hw->ops->cmd_wait(hw, HERMES_PROGRAM_DISABLE, 0, &resp); + + pr_debug("PROGRAM_DISABLE returned %d, " + "r0 0x%04x, r1 0x%04x, r2 0x%04x\n", + rc, resp.resp0, resp.resp1, resp.resp2); + + if ((rc == 0) && + ((resp.status & HERMES_STATUS_CMDCODE) != HERMES_CMD_DOWNLD)) + rc = -EIO; + + err = hermes_aux_control(hw, 0); + pr_debug("AUX disable returned %d\n", err); + + /* Acknowledge any outstanding command */ + hermes_write_regn(hw, EVACK, 0xFFFF); + + /* Reinitialise, ignoring return */ + (void) hw->ops->init_cmd_wait(hw, 0x0000 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + + return rc ? rc : err; +} + +static int hermes_program_bytes(struct hermes *hw, const char *data, + u32 addr, u32 len) +{ + /* wl lkm splits the programming into chunks of 2000 bytes. + * This restriction appears to come from USB. The PCMCIA + * adapters can program the whole lot in one go */ + hermes_aux_setaddr(hw, addr); + hermes_write_bytes(hw, HERMES_AUXDATA, data, len); + return 0; +} + +/* Read PDA from the adapter */ +static int hermes_read_pda(struct hermes *hw, __le16 *pda, u32 pda_addr, + u16 pda_len) +{ + int ret; + u16 pda_size; + u16 data_len = pda_len; + __le16 *data = pda; + + if (hw->eeprom_pda) { + /* PDA of spectrum symbol is in eeprom */ + + /* Issue command to read EEPROM */ + ret = hw->ops->cmd_wait(hw, HERMES_CMD_READMIF, 0, NULL); + if (ret) + return ret; + } else { + /* wl_lkm does not include PDA size in the PDA area. + * We will pad the information into pda, so other routines + * don't have to be modified */ + pda[0] = cpu_to_le16(pda_len - 2); + /* Includes CFG_PROD_DATA but not itself */ + pda[1] = cpu_to_le16(0x0800); /* CFG_PROD_DATA */ + data_len = pda_len - 4; + data = pda + 2; + } + + /* Open auxiliary port */ + ret = hermes_aux_control(hw, 1); + pr_debug("AUX enable returned %d\n", ret); + if (ret) + return ret; + + /* Read PDA */ + hermes_aux_setaddr(hw, pda_addr); + hermes_read_words(hw, HERMES_AUXDATA, data, data_len / 2); + + /* Close aux port */ + ret = hermes_aux_control(hw, 0); + pr_debug("AUX disable returned %d\n", ret); + + /* Check PDA length */ + pda_size = le16_to_cpu(pda[0]); + pr_debug("Actual PDA length %d, Max allowed %d\n", + pda_size, pda_len); + if (pda_size > pda_len) + return -EINVAL; + + return 0; +} + +static void hermes_lock_irqsave(spinlock_t *lock, + unsigned long *flags) __acquires(lock) +{ + spin_lock_irqsave(lock, *flags); +} + +static void hermes_unlock_irqrestore(spinlock_t *lock, + unsigned long *flags) __releases(lock) +{ + spin_unlock_irqrestore(lock, *flags); +} + +static void hermes_lock_irq(spinlock_t *lock) __acquires(lock) +{ + spin_lock_irq(lock); +} + +static void hermes_unlock_irq(spinlock_t *lock) __releases(lock) +{ + spin_unlock_irq(lock); +} + +/* Hermes operations for local buses */ +static const struct hermes_ops hermes_ops_local = { + .init = hermes_init, + .cmd_wait = hermes_docmd_wait, + .init_cmd_wait = hermes_doicmd_wait, + .allocate = hermes_allocate, + .read_ltv = hermes_read_ltv, + .read_ltv_pr = hermes_read_ltv, + .write_ltv = hermes_write_ltv, + .bap_pread = hermes_bap_pread, + .bap_pwrite = hermes_bap_pwrite, + .read_pda = hermes_read_pda, + .program_init = hermesi_program_init, + .program_end = hermesi_program_end, + .program = hermes_program_bytes, + .lock_irqsave = hermes_lock_irqsave, + .unlock_irqrestore = hermes_unlock_irqrestore, + .lock_irq = hermes_lock_irq, + .unlock_irq = hermes_unlock_irq, +}; diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes_dld.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes_dld.c --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes_dld.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes_dld.c 2025-08-03 15:01:59.325131328 +0200 @@ -0,0 +1,477 @@ +/* + * Hermes download helper. + * + * This helper: + * - is capable of writing to the volatile area of the hermes device + * - is currently not capable of writing to non-volatile areas + * - provide helpers to identify and update plugin data + * - is not capable of interpreting a fw image directly. That is up to + * the main card driver. + * - deals with Hermes I devices. It can probably be modified to deal + * with Hermes II devices + * + * Copyright (C) 2007, David Kilroy + * + * Plug data code slightly modified from spectrum_cs driver + * Copyright (C) 2002-2005 Pavel Roskin + * Portions based on information in wl_lkm_718 Agere driver + * COPYRIGHT (C) 2001-2004 by Agere Systems Inc. All Rights Reserved + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#include +#include +#include "hermes.h" +#include "hermes_dld.h" + +#define PFX "hermes_dld: " + +/* End markers used in dblocks */ +#define PDI_END 0x00000000 /* End of PDA */ +#define BLOCK_END 0xFFFFFFFF /* Last image block */ +#define TEXT_END 0x1A /* End of text header */ + +/* + * The following structures have little-endian fields denoted by + * the leading underscore. Don't access them directly - use inline + * functions defined below. + */ + +/* + * The binary image to be downloaded consists of series of data blocks. + * Each block has the following structure. + */ +struct dblock { + __le32 addr; /* adapter address where to write the block */ + __le16 len; /* length of the data only, in bytes */ + char data[]; /* data to be written */ +} __packed; + +/* + * Plug Data References are located in the image after the last data + * block. They refer to areas in the adapter memory where the plug data + * items with matching ID should be written. + */ +struct pdr { + __le32 id; /* record ID */ + __le32 addr; /* adapter address where to write the data */ + __le32 len; /* expected length of the data, in bytes */ + char next[]; /* next PDR starts here */ +} __packed; + +/* + * Plug Data Items are located in the EEPROM read from the adapter by + * primary firmware. They refer to the device-specific data that should + * be plugged into the secondary firmware. + */ +struct pdi { + __le16 len; /* length of ID and data, in words */ + __le16 id; /* record ID */ + char data[]; /* plug data */ +} __packed; + +/*** FW data block access functions ***/ + +static inline u32 +dblock_addr(const struct dblock *blk) +{ + return le32_to_cpu(blk->addr); +} + +static inline u32 +dblock_len(const struct dblock *blk) +{ + return le16_to_cpu(blk->len); +} + +/*** PDR Access functions ***/ + +static inline u32 +pdr_id(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->id); +} + +static inline u32 +pdr_addr(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->addr); +} + +static inline u32 +pdr_len(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->len); +} + +/*** PDI Access functions ***/ + +static inline u32 +pdi_id(const struct pdi *pdi) +{ + return le16_to_cpu(pdi->id); +} + +/* Return length of the data only, in bytes */ +static inline u32 +pdi_len(const struct pdi *pdi) +{ + return 2 * (le16_to_cpu(pdi->len) - 1); +} + +/*** Plug Data Functions ***/ + +/* + * Scan PDR for the record with the specified RECORD_ID. + * If it's not found, return NULL. + */ +static const struct pdr * +hermes_find_pdr(const struct pdr *first_pdr, u32 record_id, const void *end) +{ + const struct pdr *pdr = first_pdr; + + end -= sizeof(struct pdr); + + while (((void *) pdr <= end) && + (pdr_id(pdr) != PDI_END)) { + /* + * PDR area is currently not terminated by PDI_END. + * It's followed by CRC records, which have the type + * field where PDR has length. The type can be 0 or 1. + */ + if (pdr_len(pdr) < 2) + return NULL; + + /* If the record ID matches, we are done */ + if (pdr_id(pdr) == record_id) + return pdr; + + pdr = (struct pdr *) pdr->next; + } + return NULL; +} + +/* Scan production data items for a particular entry */ +static const struct pdi * +hermes_find_pdi(const struct pdi *first_pdi, u32 record_id, const void *end) +{ + const struct pdi *pdi = first_pdi; + + end -= sizeof(struct pdi); + + while (((void *) pdi <= end) && + (pdi_id(pdi) != PDI_END)) { + + /* If the record ID matches, we are done */ + if (pdi_id(pdi) == record_id) + return pdi; + + pdi = (struct pdi *) &pdi->data[pdi_len(pdi)]; + } + return NULL; +} + +/* Process one Plug Data Item - find corresponding PDR and plug it */ +static int +hermes_plug_pdi(struct hermes *hw, const struct pdr *first_pdr, + const struct pdi *pdi, const void *pdr_end) +{ + const struct pdr *pdr; + + /* Find the PDR corresponding to this PDI */ + pdr = hermes_find_pdr(first_pdr, pdi_id(pdi), pdr_end); + + /* No match is found, safe to ignore */ + if (!pdr) + return 0; + + /* Lengths of the data in PDI and PDR must match */ + if (pdi_len(pdi) != pdr_len(pdr)) + return -EINVAL; + + /* do the actual plugging */ + hw->ops->program(hw, pdi->data, pdr_addr(pdr), pdi_len(pdi)); + + return 0; +} + +/* Parse PDA and write the records into the adapter + * + * Attempt to write every records that is in the specified pda + * which also has a valid production data record for the firmware. + */ +int hermes_apply_pda(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end) +{ + int ret; + const struct pdi *pdi; + const struct pdr *pdr; + + pdr = (const struct pdr *) first_pdr; + pda_end -= sizeof(struct pdi); + + /* Go through every PDI and plug them into the adapter */ + pdi = (const struct pdi *) (pda + 2); + while (((void *) pdi <= pda_end) && + (pdi_id(pdi) != PDI_END)) { + ret = hermes_plug_pdi(hw, pdr, pdi, pdr_end); + if (ret) + return ret; + + /* Increment to the next PDI */ + pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)]; + } + return 0; +} + +/* Identify the total number of bytes in all blocks + * including the header data. + */ +size_t +hermes_blocks_length(const char *first_block, const void *end) +{ + const struct dblock *blk = (const struct dblock *) first_block; + int total_len = 0; + int len; + + end -= sizeof(*blk); + + /* Skip all blocks to locate Plug Data References + * (Spectrum CS) */ + while (((void *) blk <= end) && + (dblock_addr(blk) != BLOCK_END)) { + len = dblock_len(blk); + total_len += sizeof(*blk) + len; + blk = (struct dblock *) &blk->data[len]; + } + + return total_len; +} + +/*** Hermes programming ***/ + +/* Program the data blocks */ +int hermes_program(struct hermes *hw, const char *first_block, const void *end) +{ + const struct dblock *blk; + u32 blkaddr; + u32 blklen; + int err = 0; + + blk = (const struct dblock *) first_block; + + if ((void *) blk > (end - sizeof(*blk))) + return -EIO; + + blkaddr = dblock_addr(blk); + blklen = dblock_len(blk); + + while ((blkaddr != BLOCK_END) && + (((void *) blk + blklen) <= end)) { + pr_debug(PFX "Programming block of length %d " + "to address 0x%08x\n", blklen, blkaddr); + + err = hw->ops->program(hw, blk->data, blkaddr, blklen); + if (err) + break; + + blk = (const struct dblock *) &blk->data[blklen]; + + if ((void *) blk > (end - sizeof(*blk))) + return -EIO; + + blkaddr = dblock_addr(blk); + blklen = dblock_len(blk); + } + return err; +} + +/*** Default plugging data for Hermes I ***/ +/* Values from wl_lkm_718/hcf/dhf.c */ + +#define DEFINE_DEFAULT_PDR(pid, length, data) \ +static const struct { \ + __le16 len; \ + __le16 id; \ + u8 val[length]; \ +} __packed default_pdr_data_##pid = { \ + cpu_to_le16((sizeof(default_pdr_data_##pid)/ \ + sizeof(__le16)) - 1), \ + cpu_to_le16(pid), \ + data \ +} + +#define DEFAULT_PDR(pid) default_pdr_data_##pid + +/* HWIF Compatibility */ +DEFINE_DEFAULT_PDR(0x0005, 10, "\x00\x00\x06\x00\x01\x00\x01\x00\x01\x00"); + +/* PPPPSign */ +DEFINE_DEFAULT_PDR(0x0108, 4, "\x00\x00\x00\x00"); + +/* PPPPProf */ +DEFINE_DEFAULT_PDR(0x0109, 10, "\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00"); + +/* Antenna diversity */ +DEFINE_DEFAULT_PDR(0x0150, 2, "\x00\x3F"); + +/* Modem VCO band Set-up */ +DEFINE_DEFAULT_PDR(0x0160, 28, + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00"); + +/* Modem Rx Gain Table Values */ +DEFINE_DEFAULT_PDR(0x0161, 256, + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3E\01\x3E\x01\x3D\x01" + "\x3D\x01\x3C\01\x3C\x01\x3B\x01" + "\x3B\x01\x3A\01\x3A\x01\x39\x01" + "\x39\x01\x38\01\x38\x01\x37\x01" + "\x37\x01\x36\01\x36\x01\x35\x01" + "\x35\x01\x34\01\x34\x01\x33\x01" + "\x33\x01\x32\x01\x32\x01\x31\x01" + "\x31\x01\x30\x01\x30\x01\x7B\x01" + "\x7B\x01\x7A\x01\x7A\x01\x79\x01" + "\x79\x01\x78\x01\x78\x01\x77\x01" + "\x77\x01\x76\x01\x76\x01\x75\x01" + "\x75\x01\x74\x01\x74\x01\x73\x01" + "\x73\x01\x72\x01\x72\x01\x71\x01" + "\x71\x01\x70\x01\x70\x01\x68\x01" + "\x68\x01\x67\x01\x67\x01\x66\x01" + "\x66\x01\x65\x01\x65\x01\x57\x01" + "\x57\x01\x56\x01\x56\x01\x55\x01" + "\x55\x01\x54\x01\x54\x01\x53\x01" + "\x53\x01\x52\x01\x52\x01\x51\x01" + "\x51\x01\x50\x01\x50\x01\x48\x01" + "\x48\x01\x47\x01\x47\x01\x46\x01" + "\x46\x01\x45\x01\x45\x01\x44\x01" + "\x44\x01\x43\x01\x43\x01\x42\x01" + "\x42\x01\x41\x01\x41\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01"); + +/* Write PDA according to certain rules. + * + * For every production data record, look for a previous setting in + * the pda, and use that. + * + * For certain records, use defaults if they are not found in pda. + */ +int hermes_apply_pda_with_defaults(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end) +{ + const struct pdr *pdr = (const struct pdr *) first_pdr; + const struct pdi *first_pdi = (const struct pdi *) &pda[2]; + const struct pdi *pdi; + const struct pdi *default_pdi = NULL; + const struct pdi *outdoor_pdi; + int record_id; + + pdr_end -= sizeof(struct pdr); + + while (((void *) pdr <= pdr_end) && + (pdr_id(pdr) != PDI_END)) { + /* + * For spectrum_cs firmwares, + * PDR area is currently not terminated by PDI_END. + * It's followed by CRC records, which have the type + * field where PDR has length. The type can be 0 or 1. + */ + if (pdr_len(pdr) < 2) + break; + record_id = pdr_id(pdr); + + pdi = hermes_find_pdi(first_pdi, record_id, pda_end); + if (pdi) + pr_debug(PFX "Found record 0x%04x at %p\n", + record_id, pdi); + + switch (record_id) { + case 0x110: /* Modem REFDAC values */ + case 0x120: /* Modem VGDAC values */ + outdoor_pdi = hermes_find_pdi(first_pdi, record_id + 1, + pda_end); + default_pdi = NULL; + if (outdoor_pdi) { + pdi = outdoor_pdi; + pr_debug(PFX + "Using outdoor record 0x%04x at %p\n", + record_id + 1, pdi); + } + break; + case 0x5: /* HWIF Compatibility */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0005); + break; + case 0x108: /* PPPPSign */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0108); + break; + case 0x109: /* PPPPProf */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0109); + break; + case 0x150: /* Antenna diversity */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0150); + break; + case 0x160: /* Modem VCO band Set-up */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0160); + break; + case 0x161: /* Modem Rx Gain Table Values */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0161); + break; + default: + default_pdi = NULL; + break; + } + if (!pdi && default_pdi) { + /* Use default */ + pdi = default_pdi; + pr_debug(PFX "Using default record 0x%04x at %p\n", + record_id, pdi); + } + + if (pdi) { + /* Lengths of the data in PDI and PDR must match */ + if ((pdi_len(pdi) == pdr_len(pdr)) && + ((void *) pdi->data + pdi_len(pdi) < pda_end)) { + /* do the actual plugging */ + hw->ops->program(hw, pdi->data, pdr_addr(pdr), + pdi_len(pdi)); + } + } + + pdr++; + } + return 0; +} diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes_dld.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes_dld.h --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes_dld.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes_dld.h 2025-08-03 15:01:59.325131328 +0200 @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007, David Kilroy + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ +#ifndef _HERMES_DLD_H +#define _HERMES_DLD_H + +#include "hermes.h" + +int hermesi_program_init(struct hermes *hw, u32 offset); +int hermesi_program_end(struct hermes *hw); +int hermes_program(struct hermes *hw, const char *first_block, const void *end); + +int hermes_read_pda(struct hermes *hw, + __le16 *pda, + u32 pda_addr, + u16 pda_len, + int use_eeprom); +int hermes_apply_pda(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end); +int hermes_apply_pda_with_defaults(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end); + +size_t hermes_blocks_length(const char *first_block, const void *end); + +#endif /* _HERMES_DLD_H */ diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes.h --- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-