| /*********************************************************************** |
| ** Copyright (C) 2003 ACX100 Open Source Project |
| ** |
| ** 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 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. |
| ** --------------------------------------------------------------------- |
| ** Inquiries regarding the ACX100 Open Source Project can be |
| ** made directly to: |
| ** |
| ** acx100-users@lists.sf.net |
| ** http://acx100.sf.net |
| ** --------------------------------------------------------------------- |
| */ |
| #define ACX_PCI 1 |
| |
| #include <linux/version.h> |
| #include <linux/compiler.h> /* required for Lx 2.6.8 ?? */ |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/skbuff.h> |
| #include <linux/slab.h> |
| #include <linux/if_arp.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/wireless.h> |
| #include <net/iw_handler.h> |
| #include <linux/netdevice.h> |
| #include <linux/ioport.h> |
| #include <linux/pci.h> |
| #include <linux/pm.h> |
| #include <linux/vmalloc.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/workqueue.h> |
| |
| #include "acx.h" |
| |
| |
| /*********************************************************************** |
| */ |
| #define PCI_TYPE (PCI_USES_MEM | PCI_ADDR0 | PCI_NO_ACPI_WAKE) |
| #define PCI_ACX100_REGION1 0x01 |
| #define PCI_ACX100_REGION1_SIZE 0x1000 /* Memory size - 4K bytes */ |
| #define PCI_ACX100_REGION2 0x02 |
| #define PCI_ACX100_REGION2_SIZE 0x10000 /* Memory size - 64K bytes */ |
| |
| #define PCI_ACX111_REGION1 0x00 |
| #define PCI_ACX111_REGION1_SIZE 0x2000 /* Memory size - 8K bytes */ |
| #define PCI_ACX111_REGION2 0x01 |
| #define PCI_ACX111_REGION2_SIZE 0x20000 /* Memory size - 128K bytes */ |
| |
| /* Texas Instruments Vendor ID */ |
| #define PCI_VENDOR_ID_TI 0x104c |
| |
| /* ACX100 22Mb/s WLAN controller */ |
| #define PCI_DEVICE_ID_TI_TNETW1100A 0x8400 |
| #define PCI_DEVICE_ID_TI_TNETW1100B 0x8401 |
| |
| /* ACX111 54Mb/s WLAN controller */ |
| #define PCI_DEVICE_ID_TI_TNETW1130 0x9066 |
| |
| /* PCI Class & Sub-Class code, Network-'Other controller' */ |
| #define PCI_CLASS_NETWORK_OTHERS 0x0280 |
| |
| #define CARD_EEPROM_ID_SIZE 6 |
| |
| #ifndef PCI_D0 |
| /* From include/linux/pci.h */ |
| #define PCI_D0 0 |
| #define PCI_D1 1 |
| #define PCI_D2 2 |
| #define PCI_D3hot 3 |
| #define PCI_D3cold 4 |
| #define PCI_UNKNOWN 5 |
| #define PCI_POWER_ERROR -1 |
| #endif |
| |
| |
| /*********************************************************************** |
| */ |
| static void acxpci_i_tx_timeout(struct net_device *ndev); |
| static irqreturn_t acxpci_i_interrupt(int irq, void *dev_id); |
| static void acxpci_i_set_multicast_list(struct net_device *ndev); |
| |
| static int acxpci_e_open(struct net_device *ndev); |
| static int acxpci_e_close(struct net_device *ndev); |
| static void acxpci_s_up(struct net_device *ndev); |
| static void acxpci_s_down(struct net_device *ndev); |
| |
| #if ACX_DEBUG |
| static int acxpci_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr); |
| #else |
| static int acxpci_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout); |
| #endif |
| static int acxpci_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf); |
| static int acxpci_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value); |
| static tx_t* acxpci_l_alloc_tx(acx_device_t *adev); |
| static void acxpci_l_dealloc_tx(tx_t *tx_opaque); |
| static void* acxpci_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque); |
| static void acxpci_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len); |
| |
| #ifdef MODULE_LICENSE |
| MODULE_LICENSE("Dual MPL/GPL"); |
| #endif |
| /* USB had this: MODULE_AUTHOR("Martin Wawro <martin.wawro AT uni-dortmund.de>"); */ |
| MODULE_AUTHOR("ACX100 Open Source Driver development team"); |
| MODULE_DESCRIPTION("Driver for TI ACX1xx based wireless cards (CardBus/PCI)"); |
| |
| |
| /*********************************************************************** |
| ** Register access |
| */ |
| |
| /* Pick one */ |
| /* #define INLINE_IO static */ |
| #define INLINE_IO static inline |
| |
| INLINE_IO u32 |
| read_reg32(acx_device_t *adev, unsigned int offset) |
| { |
| #if ACX_IO_WIDTH == 32 |
| return readl((u8 *)adev->iobase + adev->io[offset]); |
| #else |
| return readw((u8 *)adev->iobase + adev->io[offset]) |
| + (readw((u8 *)adev->iobase + adev->io[offset] + 2) << 16); |
| #endif |
| } |
| |
| INLINE_IO u16 |
| read_reg16(acx_device_t *adev, unsigned int offset) |
| { |
| return readw((u8 *)adev->iobase + adev->io[offset]); |
| } |
| |
| INLINE_IO u8 |
| read_reg8(acx_device_t *adev, unsigned int offset) |
| { |
| return readb((u8 *)adev->iobase + adev->io[offset]); |
| } |
| |
| INLINE_IO void |
| write_reg32(acx_device_t *adev, unsigned int offset, u32 val) |
| { |
| #if ACX_IO_WIDTH == 32 |
| writel(val, (u8 *)adev->iobase + adev->io[offset]); |
| #else |
| writew(val & 0xffff, (u8 *)adev->iobase + adev->io[offset]); |
| writew(val >> 16, (u8 *)adev->iobase + adev->io[offset] + 2); |
| #endif |
| } |
| |
| INLINE_IO void |
| write_reg16(acx_device_t *adev, unsigned int offset, u16 val) |
| { |
| writew(val, (u8 *)adev->iobase + adev->io[offset]); |
| } |
| |
| INLINE_IO void |
| write_reg8(acx_device_t *adev, unsigned int offset, u8 val) |
| { |
| writeb(val, (u8 *)adev->iobase + adev->io[offset]); |
| } |
| |
| /* Handle PCI posting properly: |
| * Make sure that writes reach the adapter in case they require to be executed |
| * *before* the next write, by reading a random (and safely accessible) register. |
| * This call has to be made if there is no read following (which would flush the data |
| * to the adapter), yet the written data has to reach the adapter immediately. */ |
| INLINE_IO void |
| write_flush(acx_device_t *adev) |
| { |
| /* readb(adev->iobase + adev->io[IO_ACX_INFO_MAILBOX_OFFS]); */ |
| /* faster version (accesses the first register, IO_ACX_SOFT_RESET, |
| * which should also be safe): */ |
| readb(adev->iobase); |
| } |
| |
| INLINE_IO int |
| adev_present(acx_device_t *adev) |
| { |
| /* fast version (accesses the first register, IO_ACX_SOFT_RESET, |
| * which should be safe): */ |
| return readl(adev->iobase) != 0xffffffff; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static inline txdesc_t* |
| get_txdesc(acx_device_t *adev, int index) |
| { |
| return (txdesc_t*) (((u8*)adev->txdesc_start) + index * adev->txdesc_size); |
| } |
| |
| static inline txdesc_t* |
| advance_txdesc(acx_device_t *adev, txdesc_t* txdesc, int inc) |
| { |
| return (txdesc_t*) (((u8*)txdesc) + inc * adev->txdesc_size); |
| } |
| |
| static txhostdesc_t* |
| get_txhostdesc(acx_device_t *adev, txdesc_t* txdesc) |
| { |
| int index = (u8*)txdesc - (u8*)adev->txdesc_start; |
| if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return NULL; |
| } |
| index /= adev->txdesc_size; |
| if (unlikely(ACX_DEBUG && (index >= TX_CNT))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return NULL; |
| } |
| return &adev->txhostdesc_start[index*2]; |
| } |
| |
| static inline client_t* |
| get_txc(acx_device_t *adev, txdesc_t* txdesc) |
| { |
| int index = (u8*)txdesc - (u8*)adev->txdesc_start; |
| if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return NULL; |
| } |
| index /= adev->txdesc_size; |
| if (unlikely(ACX_DEBUG && (index >= TX_CNT))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return NULL; |
| } |
| return adev->txc[index]; |
| } |
| |
| static inline u16 |
| get_txr(acx_device_t *adev, txdesc_t* txdesc) |
| { |
| int index = (u8*)txdesc - (u8*)adev->txdesc_start; |
| index /= adev->txdesc_size; |
| return adev->txr[index]; |
| } |
| |
| static inline void |
| put_txcr(acx_device_t *adev, txdesc_t* txdesc, client_t* c, u16 r111) |
| { |
| int index = (u8*)txdesc - (u8*)adev->txdesc_start; |
| if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return; |
| } |
| index /= adev->txdesc_size; |
| if (unlikely(ACX_DEBUG && (index >= TX_CNT))) { |
| printk("bad txdesc ptr %p\n", txdesc); |
| return; |
| } |
| adev->txc[index] = c; |
| adev->txr[index] = r111; |
| } |
| |
| |
| /*********************************************************************** |
| ** EEPROM and PHY read/write helpers |
| */ |
| /*********************************************************************** |
| ** acxpci_read_eeprom_byte |
| ** |
| ** Function called to read an octet in the EEPROM. |
| ** |
| ** This function is used by acxpci_e_probe to check if the |
| ** connected card is a legal one or not. |
| ** |
| ** Arguments: |
| ** adev ptr to acx_device structure |
| ** addr address to read in the EEPROM |
| ** charbuf ptr to a char. This is where the read octet |
| ** will be stored |
| */ |
| int |
| acxpci_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf) |
| { |
| int result; |
| int count; |
| |
| write_reg32(adev, IO_ACX_EEPROM_CFG, 0); |
| write_reg32(adev, IO_ACX_EEPROM_ADDR, addr); |
| write_flush(adev); |
| write_reg32(adev, IO_ACX_EEPROM_CTL, 2); |
| |
| count = 0xffff; |
| while (read_reg16(adev, IO_ACX_EEPROM_CTL)) { |
| /* scheduling away instead of CPU burning loop |
| * doesn't seem to work here at all: |
| * awful delay, sometimes also failure. |
| * Doesn't matter anyway (only small delay). */ |
| if (unlikely(!--count)) { |
| printk("%s: timeout waiting for EEPROM read\n", |
| adev->ndev->name); |
| result = NOT_OK; |
| goto fail; |
| } |
| cpu_relax(); |
| } |
| |
| *charbuf = read_reg8(adev, IO_ACX_EEPROM_DATA); |
| log(L_DEBUG, "EEPROM at 0x%04X = 0x%02X\n", addr, *charbuf); |
| result = OK; |
| |
| fail: |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** We don't lock hw accesses here since we never r/w eeprom in IRQ |
| ** Note: this function sleeps only because of GFP_KERNEL alloc |
| */ |
| #ifdef UNUSED |
| int |
| acxpci_s_write_eeprom(acx_device_t *adev, u32 addr, u32 len, const u8 *charbuf) |
| { |
| u8 *data_verify = NULL; |
| unsigned long flags; |
| int count, i; |
| int result = NOT_OK; |
| u16 gpio_orig; |
| |
| printk("acx: WARNING! I would write to EEPROM now. " |
| "Since I really DON'T want to unless you know " |
| "what you're doing (THIS CODE WILL PROBABLY " |
| "NOT WORK YET!), I will abort that now. And " |
| "definitely make sure to make a " |
| "/proc/driver/acx_wlan0_eeprom backup copy first!!! " |
| "(the EEPROM content includes the PCI config header!! " |
| "If you kill important stuff, then you WILL " |
| "get in trouble and people DID get in trouble already)\n"); |
| return OK; |
| |
| FN_ENTER; |
| |
| data_verify = kmalloc(len, GFP_KERNEL); |
| if (!data_verify) { |
| goto end; |
| } |
| |
| /* first we need to enable the OE (EEPROM Output Enable) GPIO line |
| * to be able to write to the EEPROM. |
| * NOTE: an EEPROM writing success has been reported, |
| * but you probably have to modify GPIO_OUT, too, |
| * and you probably need to activate a different GPIO |
| * line instead! */ |
| gpio_orig = read_reg16(adev, IO_ACX_GPIO_OE); |
| write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig & ~1); |
| write_flush(adev); |
| |
| /* ok, now start writing the data out */ |
| for (i = 0; i < len; i++) { |
| write_reg32(adev, IO_ACX_EEPROM_CFG, 0); |
| write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i); |
| write_reg32(adev, IO_ACX_EEPROM_DATA, *(charbuf + i)); |
| write_flush(adev); |
| write_reg32(adev, IO_ACX_EEPROM_CTL, 1); |
| |
| count = 0xffff; |
| while (read_reg16(adev, IO_ACX_EEPROM_CTL)) { |
| if (unlikely(!--count)) { |
| printk("WARNING, DANGER!!! " |
| "Timeout waiting for EEPROM write\n"); |
| goto end; |
| } |
| cpu_relax(); |
| } |
| } |
| |
| /* disable EEPROM writing */ |
| write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig); |
| write_flush(adev); |
| |
| /* now start a verification run */ |
| for (i = 0; i < len; i++) { |
| write_reg32(adev, IO_ACX_EEPROM_CFG, 0); |
| write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i); |
| write_flush(adev); |
| write_reg32(adev, IO_ACX_EEPROM_CTL, 2); |
| |
| count = 0xffff; |
| while (read_reg16(adev, IO_ACX_EEPROM_CTL)) { |
| if (unlikely(!--count)) { |
| printk("timeout waiting for EEPROM read\n"); |
| goto end; |
| } |
| cpu_relax(); |
| } |
| |
| data_verify[i] = read_reg16(adev, IO_ACX_EEPROM_DATA); |
| } |
| |
| if (0 == memcmp(charbuf, data_verify, len)) |
| result = OK; /* read data matches, success */ |
| |
| end: |
| kfree(data_verify); |
| FN_EXIT1(result); |
| return result; |
| } |
| #endif /* UNUSED */ |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_read_phy_reg |
| ** |
| ** Messing with rx/tx disabling and enabling here |
| ** (write_reg32(adev, IO_ACX_ENABLE, 0b000000xx)) kills traffic |
| */ |
| static int |
| acxpci_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf) |
| { |
| int result = NOT_OK; |
| int count; |
| |
| FN_ENTER; |
| |
| write_reg32(adev, IO_ACX_PHY_ADDR, reg); |
| write_flush(adev); |
| write_reg32(adev, IO_ACX_PHY_CTL, 2); |
| |
| count = 0xffff; |
| while (read_reg32(adev, IO_ACX_PHY_CTL)) { |
| /* scheduling away instead of CPU burning loop |
| * doesn't seem to work here at all: |
| * awful delay, sometimes also failure. |
| * Doesn't matter anyway (only small delay). */ |
| if (unlikely(!--count)) { |
| printk("%s: timeout waiting for phy read\n", |
| adev->ndev->name); |
| *charbuf = 0; |
| goto fail; |
| } |
| cpu_relax(); |
| } |
| |
| log(L_DEBUG, "count was %u\n", count); |
| *charbuf = read_reg8(adev, IO_ACX_PHY_DATA); |
| |
| log(L_DEBUG, "radio PHY at 0x%04X = 0x%02X\n", *charbuf, reg); |
| result = OK; |
| goto fail; /* silence compiler warning */ |
| fail: |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static int |
| acxpci_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value) |
| { |
| FN_ENTER; |
| |
| /* mprusko said that 32bit accesses result in distorted sensitivity |
| * on his card. Unconfirmed, looks like it's not true (most likely since we |
| * now properly flush writes). */ |
| write_reg32(adev, IO_ACX_PHY_DATA, value); |
| write_reg32(adev, IO_ACX_PHY_ADDR, reg); |
| write_flush(adev); |
| write_reg32(adev, IO_ACX_PHY_CTL, 1); |
| write_flush(adev); |
| log(L_DEBUG, "radio PHY write 0x%02X at 0x%04X\n", value, reg); |
| |
| FN_EXIT1(OK); |
| return OK; |
| } |
| |
| |
| #define NO_AUTO_INCREMENT 1 |
| |
| /*********************************************************************** |
| ** acxpci_s_write_fw |
| ** |
| ** Write the firmware image into the card. |
| ** |
| ** Arguments: |
| ** adev wlan device structure |
| ** fw_image firmware image. |
| ** |
| ** Returns: |
| ** 1 firmware image corrupted |
| ** 0 success |
| */ |
| static int |
| acxpci_s_write_fw(acx_device_t *adev, const firmware_image_t *fw_image, u32 offset) |
| { |
| int len, size; |
| u32 sum, v32; |
| /* we skip the first four bytes which contain the control sum */ |
| const u8 *p = (u8*)fw_image + 4; |
| |
| /* start the image checksum by adding the image size value */ |
| sum = p[0]+p[1]+p[2]+p[3]; |
| p += 4; |
| |
| write_reg32(adev, IO_ACX_SLV_END_CTL, 0); |
| |
| #if NO_AUTO_INCREMENT |
| write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */ |
| #else |
| write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */ |
| write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */ |
| write_flush(adev); |
| #endif |
| |
| len = 0; |
| size = le32_to_cpu(fw_image->size) & (~3); |
| |
| while (likely(len < size)) { |
| v32 = be32_to_cpu(*(u32*)p); |
| sum += p[0]+p[1]+p[2]+p[3]; |
| p += 4; |
| len += 4; |
| |
| #if NO_AUTO_INCREMENT |
| write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4); |
| write_flush(adev); |
| #endif |
| write_reg32(adev, IO_ACX_SLV_MEM_DATA, v32); |
| } |
| |
| log(L_DEBUG, "firmware written, size:%d sum1:%x sum2:%x\n", |
| size, sum, le32_to_cpu(fw_image->chksum)); |
| |
| /* compare our checksum with the stored image checksum */ |
| return (sum != le32_to_cpu(fw_image->chksum)); |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_validate_fw |
| ** |
| ** Compare the firmware image given with |
| ** the firmware image written into the card. |
| ** |
| ** Arguments: |
| ** adev wlan device structure |
| ** fw_image firmware image. |
| ** |
| ** Returns: |
| ** NOT_OK firmware image corrupted or not correctly written |
| ** OK success |
| */ |
| static int |
| acxpci_s_validate_fw(acx_device_t *adev, const firmware_image_t *fw_image, |
| u32 offset) |
| { |
| u32 sum, v32, w32; |
| int len, size; |
| int result = OK; |
| /* we skip the first four bytes which contain the control sum */ |
| const u8 *p = (u8*)fw_image + 4; |
| |
| /* start the image checksum by adding the image size value */ |
| sum = p[0]+p[1]+p[2]+p[3]; |
| p += 4; |
| |
| write_reg32(adev, IO_ACX_SLV_END_CTL, 0); |
| |
| #if NO_AUTO_INCREMENT |
| write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */ |
| #else |
| write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */ |
| write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */ |
| #endif |
| |
| len = 0; |
| size = le32_to_cpu(fw_image->size) & (~3); |
| |
| while (likely(len < size)) { |
| v32 = be32_to_cpu(*(u32*)p); |
| p += 4; |
| len += 4; |
| |
| #if NO_AUTO_INCREMENT |
| write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4); |
| #endif |
| w32 = read_reg32(adev, IO_ACX_SLV_MEM_DATA); |
| |
| if (unlikely(w32 != v32)) { |
| printk("acx: FATAL: firmware upload: " |
| "data parts at offset %d don't match (0x%08X vs. 0x%08X)! " |
| "I/O timing issues or defective memory, with DWL-xx0+? " |
| "ACX_IO_WIDTH=16 may help. Please report\n", |
| len, v32, w32); |
| result = NOT_OK; |
| break; |
| } |
| |
| sum += (u8)w32 + (u8)(w32>>8) + (u8)(w32>>16) + (u8)(w32>>24); |
| } |
| |
| /* sum control verification */ |
| if (result != NOT_OK) { |
| if (sum != le32_to_cpu(fw_image->chksum)) { |
| printk("acx: FATAL: firmware upload: " |
| "checksums don't match!\n"); |
| result = NOT_OK; |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_upload_fw |
| ** |
| ** Called from acx_reset_dev |
| */ |
| static int |
| acxpci_s_upload_fw(acx_device_t *adev) |
| { |
| firmware_image_t *fw_image = NULL; |
| int res = NOT_OK; |
| int try; |
| u32 file_size; |
| char filename[sizeof("tiacx1NNcNN")]; |
| |
| FN_ENTER; |
| |
| /* print exact chipset and radio ID to make sure people |
| * really get a clue on which files exactly they need to provide. |
| * Firmware loading is a frequent end-user PITA with these chipsets. */ |
| printk( "acx: need firmware for acx1%02d chipset with radio ID %02X\n" |
| "Please provide via firmware hotplug:\n" |
| "either combined firmware (single file named 'tiacx1%02dc%02X')\n" |
| "or two files (base firmware file 'tiacx1%02d' " |
| "+ radio fw 'tiacx1%02dr%02X')\n", |
| IS_ACX111(adev)*11, adev->radio_type, |
| IS_ACX111(adev)*11, adev->radio_type, |
| IS_ACX111(adev)*11, |
| IS_ACX111(adev)*11, adev->radio_type |
| ); |
| |
| /* Try combined, then main image */ |
| adev->need_radio_fw = 0; |
| snprintf(filename, sizeof(filename), "tiacx1%02dc%02X", |
| IS_ACX111(adev)*11, adev->radio_type); |
| |
| fw_image = acx_s_read_fw(&adev->pdev->dev, filename, &file_size); |
| if (!fw_image) { |
| adev->need_radio_fw = 1; |
| filename[sizeof("tiacx1NN")-1] = '\0'; |
| fw_image = acx_s_read_fw(&adev->pdev->dev, filename, &file_size); |
| if (!fw_image) { |
| FN_EXIT1(NOT_OK); |
| return NOT_OK; |
| } |
| } |
| |
| for (try = 1; try <= 5; try++) { |
| res = acxpci_s_write_fw(adev, fw_image, 0); |
| log(L_DEBUG|L_INIT, "acx_write_fw (main/combined): %d\n", res); |
| if (OK == res) { |
| res = acxpci_s_validate_fw(adev, fw_image, 0); |
| log(L_DEBUG|L_INIT, "acx_validate_fw " |
| "(main/combined): %d\n", res); |
| } |
| |
| if (OK == res) { |
| SET_BIT(adev->dev_state_mask, ACX_STATE_FW_LOADED); |
| break; |
| } |
| printk("acx: firmware upload attempt #%d FAILED, " |
| "retrying...\n", try); |
| acx_s_msleep(1000); /* better wait for a while... */ |
| } |
| |
| vfree(fw_image); |
| |
| FN_EXIT1(res); |
| return res; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_upload_radio |
| ** |
| ** Uploads the appropriate radio module firmware into the card. |
| */ |
| int |
| acxpci_s_upload_radio(acx_device_t *adev) |
| { |
| acx_ie_memmap_t mm; |
| firmware_image_t *radio_image; |
| acx_cmd_radioinit_t radioinit; |
| int res = NOT_OK; |
| int try; |
| u32 offset; |
| u32 size; |
| char filename[sizeof("tiacx1NNrNN")]; |
| |
| if (!adev->need_radio_fw) return OK; |
| |
| FN_ENTER; |
| |
| acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP); |
| offset = le32_to_cpu(mm.CodeEnd); |
| |
| snprintf(filename, sizeof(filename), "tiacx1%02dr%02X", |
| IS_ACX111(adev)*11, |
| adev->radio_type); |
| radio_image = acx_s_read_fw(&adev->pdev->dev, filename, &size); |
| if (!radio_image) { |
| printk("acx: can't load radio module '%s'\n", filename); |
| goto fail; |
| } |
| |
| acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0); |
| |
| for (try = 1; try <= 5; try++) { |
| res = acxpci_s_write_fw(adev, radio_image, offset); |
| log(L_DEBUG|L_INIT, "acx_write_fw (radio): %d\n", res); |
| if (OK == res) { |
| res = acxpci_s_validate_fw(adev, radio_image, offset); |
| log(L_DEBUG|L_INIT, "acx_validate_fw (radio): %d\n", res); |
| } |
| |
| if (OK == res) |
| break; |
| printk("acx: radio firmware upload attempt #%d FAILED, " |
| "retrying...\n", try); |
| acx_s_msleep(1000); /* better wait for a while... */ |
| } |
| |
| acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0); |
| radioinit.offset = cpu_to_le32(offset); |
| /* no endian conversion needed, remains in card CPU area: */ |
| radioinit.len = radio_image->size; |
| |
| vfree(radio_image); |
| |
| if (OK != res) |
| goto fail; |
| |
| /* will take a moment so let's have a big timeout */ |
| acx_s_issue_cmd_timeo(adev, ACX1xx_CMD_RADIOINIT, |
| &radioinit, sizeof(radioinit), CMD_TIMEOUT_MS(1000)); |
| |
| res = acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP); |
| fail: |
| FN_EXIT1(res); |
| return res; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_l_reset_mac |
| ** |
| ** MAC will be reset |
| ** Call context: reset_dev |
| */ |
| static void |
| acxpci_l_reset_mac(acx_device_t *adev) |
| { |
| u16 temp; |
| |
| FN_ENTER; |
| |
| /* halt eCPU */ |
| temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1; |
| write_reg16(adev, IO_ACX_ECPU_CTRL, temp); |
| |
| /* now do soft reset of eCPU, set bit */ |
| temp = read_reg16(adev, IO_ACX_SOFT_RESET) | 0x1; |
| log(L_DEBUG, "enable soft reset\n"); |
| write_reg16(adev, IO_ACX_SOFT_RESET, temp); |
| write_flush(adev); |
| |
| /* now clear bit again: deassert eCPU reset */ |
| log(L_DEBUG, "disable soft reset and go to init mode\n"); |
| write_reg16(adev, IO_ACX_SOFT_RESET, temp & ~0x1); |
| |
| /* now start a burst read from initial EEPROM */ |
| temp = read_reg16(adev, IO_ACX_EE_START) | 0x1; |
| write_reg16(adev, IO_ACX_EE_START, temp); |
| write_flush(adev); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_verify_init |
| */ |
| static int |
| acxpci_s_verify_init(acx_device_t *adev) |
| { |
| int result = NOT_OK; |
| unsigned long timeout; |
| |
| FN_ENTER; |
| |
| timeout = jiffies + 2*HZ; |
| for (;;) { |
| u16 irqstat = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES); |
| if (irqstat & HOST_INT_FCS_THRESHOLD) { |
| result = OK; |
| write_reg16(adev, IO_ACX_IRQ_ACK, HOST_INT_FCS_THRESHOLD); |
| break; |
| } |
| if (time_after(jiffies, timeout)) |
| break; |
| /* Init may take up to ~0.5 sec total */ |
| acx_s_msleep(50); |
| } |
| |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** A few low-level helpers |
| ** |
| ** Note: these functions are not protected by lock |
| ** and thus are never allowed to be called from IRQ. |
| ** Also they must not race with fw upload which uses same hw regs |
| */ |
| |
| /*********************************************************************** |
| ** acxpci_write_cmd_type_status |
| */ |
| |
| static inline void |
| acxpci_write_cmd_type_status(acx_device_t *adev, u16 type, u16 status) |
| { |
| writel(type | (status << 16), adev->cmd_area); |
| write_flush(adev); |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_read_cmd_type_status |
| */ |
| static u32 |
| acxpci_read_cmd_type_status(acx_device_t *adev) |
| { |
| u32 cmd_type, cmd_status; |
| |
| cmd_type = readl(adev->cmd_area); |
| cmd_status = (cmd_type >> 16); |
| cmd_type = (u16)cmd_type; |
| |
| log(L_CTL, "cmd_type:%04X cmd_status:%04X [%s]\n", |
| cmd_type, cmd_status, |
| acx_cmd_status_str(cmd_status)); |
| |
| return cmd_status; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_reset_dev |
| ** |
| ** Arguments: |
| ** netdevice that contains the adev variable |
| ** Returns: |
| ** NOT_OK on fail |
| ** OK on success |
| ** Side effects: |
| ** device is hard reset |
| ** Call context: |
| ** acxpci_e_probe |
| ** Comment: |
| ** This resets the device using low level hardware calls |
| ** as well as uploads and verifies the firmware to the card |
| */ |
| |
| static inline void |
| init_mboxes(acx_device_t *adev) |
| { |
| u32 cmd_offs, info_offs; |
| |
| cmd_offs = read_reg32(adev, IO_ACX_CMD_MAILBOX_OFFS); |
| info_offs = read_reg32(adev, IO_ACX_INFO_MAILBOX_OFFS); |
| adev->cmd_area = (u8 *)adev->iobase2 + cmd_offs; |
| adev->info_area = (u8 *)adev->iobase2 + info_offs; |
| log(L_DEBUG, "iobase2=%p\n" |
| "cmd_mbox_offset=%X cmd_area=%p\n" |
| "info_mbox_offset=%X info_area=%p\n", |
| adev->iobase2, |
| cmd_offs, adev->cmd_area, |
| info_offs, adev->info_area); |
| } |
| |
| |
| static inline void |
| read_eeprom_area(acx_device_t *adev) |
| { |
| #if ACX_DEBUG > 1 |
| int offs; |
| u8 tmp; |
| |
| for (offs = 0x8c; offs < 0xb9; offs++) |
| acxpci_read_eeprom_byte(adev, offs, &tmp); |
| #endif |
| } |
| |
| |
| static int |
| acxpci_s_reset_dev(acx_device_t *adev) |
| { |
| const char* msg = ""; |
| unsigned long flags; |
| int result = NOT_OK; |
| u16 hardware_info; |
| u16 ecpu_ctrl; |
| int count; |
| |
| FN_ENTER; |
| |
| /* reset the device to make sure the eCPU is stopped |
| * to upload the firmware correctly */ |
| |
| acx_lock(adev, flags); |
| |
| acxpci_l_reset_mac(adev); |
| |
| ecpu_ctrl = read_reg16(adev, IO_ACX_ECPU_CTRL) & 1; |
| if (!ecpu_ctrl) { |
| msg = "eCPU is already running. "; |
| goto end_unlock; |
| } |
| |
| #ifdef WE_DONT_NEED_THAT_DO_WE |
| if (read_reg16(adev, IO_ACX_SOR_CFG) & 2) { |
| /* eCPU most likely means "embedded CPU" */ |
| msg = "eCPU did not start after boot from flash. "; |
| goto end_unlock; |
| } |
| |
| /* check sense on reset flags */ |
| if (read_reg16(adev, IO_ACX_SOR_CFG) & 0x10) { |
| printk("%s: eCPU did not start after boot (SOR), " |
| "is this fatal?\n", adev->ndev->name); |
| } |
| #endif |
| /* scan, if any, is stopped now, setting corresponding IRQ bit */ |
| adev->irq_status |= HOST_INT_SCAN_COMPLETE; |
| |
| acx_unlock(adev, flags); |
| |
| /* need to know radio type before fw load */ |
| /* Need to wait for arrival of this information in a loop, |
| * most probably since eCPU runs some init code from EEPROM |
| * (started burst read in reset_mac()) which also |
| * sets the radio type ID */ |
| |
| count = 0xffff; |
| do { |
| hardware_info = read_reg16(adev, IO_ACX_EEPROM_INFORMATION); |
| if (!--count) { |
| msg = "eCPU didn't indicate radio type"; |
| goto end_fail; |
| } |
| cpu_relax(); |
| } while (!(hardware_info & 0xff00)); /* radio type still zero? */ |
| |
| /* printk("DEBUG: count %d\n", count); */ |
| adev->form_factor = hardware_info & 0xff; |
| adev->radio_type = hardware_info >> 8; |
| |
| /* load the firmware */ |
| if (OK != acxpci_s_upload_fw(adev)) |
| goto end_fail; |
| |
| /* acx_s_msleep(10); this one really shouldn't be required */ |
| |
| /* now start eCPU by clearing bit */ |
| write_reg16(adev, IO_ACX_ECPU_CTRL, ecpu_ctrl & ~0x1); |
| log(L_DEBUG, "booted eCPU up and waiting for completion...\n"); |
| |
| /* wait for eCPU bootup */ |
| if (OK != acxpci_s_verify_init(adev)) { |
| msg = "timeout waiting for eCPU. "; |
| goto end_fail; |
| } |
| log(L_DEBUG, "eCPU has woken up, card is ready to be configured\n"); |
| |
| init_mboxes(adev); |
| acxpci_write_cmd_type_status(adev, 0, 0); |
| |
| /* test that EEPROM is readable */ |
| read_eeprom_area(adev); |
| |
| result = OK; |
| goto end; |
| |
| /* Finish error message. Indicate which function failed */ |
| end_unlock: |
| acx_unlock(adev, flags); |
| end_fail: |
| printk("acx: %sreset_dev() FAILED\n", msg); |
| end: |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_issue_cmd_timeo |
| ** |
| ** Sends command to fw, extract result |
| ** |
| ** NB: we do _not_ take lock inside, so be sure to not touch anything |
| ** which may interfere with IRQ handler operation |
| ** |
| ** TODO: busy wait is a bit silly, so: |
| ** 1) stop doing many iters - go to sleep after first |
| ** 2) go to waitqueue based approach: wait, not poll! |
| */ |
| #undef FUNC |
| #define FUNC "issue_cmd" |
| |
| static int |
| #if !ACX_DEBUG |
| acxpci_s_issue_cmd_timeo( |
| acx_device_t *adev, |
| unsigned int cmd, |
| void *buffer, |
| unsigned buflen, |
| unsigned cmd_timeout) |
| { |
| #else |
| acxpci_s_issue_cmd_timeo_debug( |
| acx_device_t *adev, |
| unsigned cmd, |
| void *buffer, |
| unsigned buflen, |
| unsigned cmd_timeout, |
| const char* cmdstr) |
| { |
| unsigned long start = jiffies; |
| #endif |
| const char *devname; |
| unsigned counter; |
| u16 irqtype; |
| u16 cmd_status; |
| unsigned long timeout; |
| |
| FN_ENTER; |
| |
| devname = adev->ndev->name; |
| if (!devname || !devname[0] || devname[3]=='%') |
| devname = "acx"; |
| |
| log(L_CTL, FUNC"(cmd:%s,buflen:%u,timeout:%ums,type:0x%04X)\n", |
| cmdstr, buflen, cmd_timeout, |
| buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1); |
| |
| if (!(adev->dev_state_mask & ACX_STATE_FW_LOADED)) { |
| printk("%s: "FUNC"(): firmware is not loaded yet, " |
| "cannot execute commands!\n", devname); |
| goto bad; |
| } |
| |
| if ((acx_debug & L_DEBUG) && (cmd != ACX1xx_CMD_INTERROGATE)) { |
| printk("input buffer (len=%u):\n", buflen); |
| acx_dump_bytes(buffer, buflen); |
| } |
| |
| /* wait for firmware to become idle for our command submission */ |
| timeout = HZ/5; |
| counter = (timeout * 1000 / HZ) - 1; /* in ms */ |
| timeout += jiffies; |
| do { |
| cmd_status = acxpci_read_cmd_type_status(adev); |
| /* Test for IDLE state */ |
| if (!cmd_status) |
| break; |
| if (counter % 8 == 0) { |
| if (time_after(jiffies, timeout)) { |
| counter = 0; |
| break; |
| } |
| /* we waited 8 iterations, no luck. Sleep 8 ms */ |
| acx_s_msleep(8); |
| } |
| } while (likely(--counter)); |
| |
| if (!counter) { |
| /* the card doesn't get idle, we're in trouble */ |
| printk("%s: "FUNC"(): cmd_status is not IDLE: 0x%04X!=0\n", |
| devname, cmd_status); |
| goto bad; |
| } else if (counter < 190) { /* if waited >10ms... */ |
| log(L_CTL|L_DEBUG, FUNC"(): waited for IDLE %dms. " |
| "Please report\n", 199 - counter); |
| } |
| |
| /* now write the parameters of the command if needed */ |
| if (buffer && buflen) { |
| /* if it's an INTERROGATE command, just pass the length |
| * of parameters to read, as data */ |
| #if CMD_DISCOVERY |
| if (cmd == ACX1xx_CMD_INTERROGATE) |
| memset_io(adev->cmd_area + 4, 0xAA, buflen); |
| #endif |
| /* adev->cmd_area points to PCI device's memory, not to RAM! */ |
| memcpy_toio(adev->cmd_area + 4, buffer, |
| (cmd == ACX1xx_CMD_INTERROGATE) ? 4 : buflen); |
| } |
| /* now write the actual command type */ |
| acxpci_write_cmd_type_status(adev, cmd, 0); |
| /* execute command */ |
| write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_CMD); |
| write_flush(adev); |
| |
| /* wait for firmware to process command */ |
| |
| /* Ensure nonzero and not too large timeout. |
| ** Also converts e.g. 100->99, 200->199 |
| ** which is nice but not essential */ |
| cmd_timeout = (cmd_timeout-1) | 1; |
| if (unlikely(cmd_timeout > 1199)) |
| cmd_timeout = 1199; |
| /* clear CMD_COMPLETE bit. can be set only by IRQ handler: */ |
| adev->irq_status &= ~HOST_INT_CMD_COMPLETE; |
| |
| /* we schedule away sometimes (timeout can be large) */ |
| counter = cmd_timeout; |
| timeout = jiffies + cmd_timeout * HZ / 1000; |
| do { |
| if (!adev->irqs_active) { /* IRQ disabled: poll */ |
| irqtype = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES); |
| if (irqtype & HOST_INT_CMD_COMPLETE) { |
| write_reg16(adev, IO_ACX_IRQ_ACK, |
| HOST_INT_CMD_COMPLETE); |
| break; |
| } |
| } else { /* Wait when IRQ will set the bit */ |
| irqtype = adev->irq_status; |
| if (irqtype & HOST_INT_CMD_COMPLETE) |
| break; |
| } |
| |
| if (counter % 8 == 0) { |
| if (time_after(jiffies, timeout)) { |
| counter = 0; |
| break; |
| } |
| /* we waited 8 iterations, no luck. Sleep 8 ms */ |
| acx_s_msleep(8); |
| } |
| } while (likely(--counter)); |
| |
| /* save state for debugging */ |
| cmd_status = acxpci_read_cmd_type_status(adev); |
| |
| /* put the card in IDLE state */ |
| acxpci_write_cmd_type_status(adev, 0, 0); |
| |
| if (!counter) { /* timed out! */ |
| printk("%s: "FUNC"(): timed out %s for CMD_COMPLETE. " |
| "irq bits:0x%04X irq_status:0x%04X timeout:%dms " |
| "cmd_status:%d (%s)\n", |
| devname, (adev->irqs_active) ? "waiting" : "polling", |
| irqtype, adev->irq_status, cmd_timeout, |
| cmd_status, acx_cmd_status_str(cmd_status)); |
| goto bad; |
| } else if (cmd_timeout - counter > 30) { /* if waited >30ms... */ |
| log(L_CTL|L_DEBUG, FUNC"(): %s for CMD_COMPLETE %dms. " |
| "count:%d. Please report\n", |
| (adev->irqs_active) ? "waited" : "polled", |
| cmd_timeout - counter, counter); |
| } |
| |
| if (1 != cmd_status) { /* it is not a 'Success' */ |
| printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s). " |
| "Took %dms of %d\n", |
| devname, cmd_status, acx_cmd_status_str(cmd_status), |
| cmd_timeout - counter, cmd_timeout); |
| /* zero out result buffer |
| * WARNING: this will trash stack in case of illegally large input |
| * length! */ |
| if (buffer && buflen) |
| memset(buffer, 0, buflen); |
| goto bad; |
| } |
| |
| /* read in result parameters if needed */ |
| if (buffer && buflen && (cmd == ACX1xx_CMD_INTERROGATE)) { |
| /* adev->cmd_area points to PCI device's memory, not to RAM! */ |
| memcpy_fromio(buffer, adev->cmd_area + 4, buflen); |
| if (acx_debug & L_DEBUG) { |
| printk("output buffer (len=%u): ", buflen); |
| acx_dump_bytes(buffer, buflen); |
| } |
| } |
| /* ok: */ |
| log(L_CTL, FUNC"(%s): took %ld jiffies to complete\n", |
| cmdstr, jiffies - start); |
| FN_EXIT1(OK); |
| return OK; |
| |
| bad: |
| /* Give enough info so that callers can avoid |
| ** printing their own diagnostic messages */ |
| #if ACX_DEBUG |
| printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr); |
| #else |
| printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd); |
| #endif |
| dump_stack(); |
| FN_EXIT1(NOT_OK); |
| return NOT_OK; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| #ifdef NONESSENTIAL_FEATURES |
| typedef struct device_id { |
| unsigned char id[6]; |
| char *descr; |
| char *type; |
| } device_id_t; |
| |
| static const device_id_t |
| device_ids[] = |
| { |
| { |
| {'G', 'l', 'o', 'b', 'a', 'l'}, |
| NULL, |
| NULL, |
| }, |
| { |
| {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, |
| "uninitialized", |
| "SpeedStream SS1021 or Gigafast WF721-AEX" |
| }, |
| { |
| {0x80, 0x81, 0x82, 0x83, 0x84, 0x85}, |
| "non-standard", |
| "DrayTek Vigor 520" |
| }, |
| { |
| {'?', '?', '?', '?', '?', '?'}, |
| "non-standard", |
| "Level One WPC-0200" |
| }, |
| { |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
| "empty", |
| "DWL-650+ variant" |
| } |
| }; |
| |
| static void |
| acx_show_card_eeprom_id(acx_device_t *adev) |
| { |
| unsigned char buffer[CARD_EEPROM_ID_SIZE]; |
| int i; |
| |
| memset(&buffer, 0, CARD_EEPROM_ID_SIZE); |
| /* use direct EEPROM access */ |
| for (i = 0; i < CARD_EEPROM_ID_SIZE; i++) { |
| if (OK != acxpci_read_eeprom_byte(adev, |
| ACX100_EEPROM_ID_OFFSET + i, |
| &buffer[i])) { |
| printk("acx: reading EEPROM FAILED\n"); |
| break; |
| } |
| } |
| |
| for (i = 0; i < VEC_SIZE(device_ids); i++) { |
| if (!memcmp(&buffer, device_ids[i].id, CARD_EEPROM_ID_SIZE)) { |
| if (device_ids[i].descr) { |
| printk("acx: EEPROM card ID string check " |
| "found %s card ID: is this %s?\n", |
| device_ids[i].descr, device_ids[i].type); |
| } |
| break; |
| } |
| } |
| if (i == VEC_SIZE(device_ids)) { |
| printk("acx: EEPROM card ID string check found " |
| "unknown card: expected 'Global', got '%.*s\'. " |
| "Please report\n", CARD_EEPROM_ID_SIZE, buffer); |
| } |
| } |
| #endif /* NONESSENTIAL_FEATURES */ |
| |
| |
| /*********************************************************************** |
| ** acxpci_free_desc_queues |
| ** |
| ** Releases the queues that have been allocated, the |
| ** others have been initialised to NULL so this |
| ** function can be used if only part of the queues were allocated. |
| */ |
| |
| static inline void |
| free_coherent(struct pci_dev *hwdev, size_t size, |
| void *vaddr, dma_addr_t dma_handle) |
| { |
| dma_free_coherent(hwdev == NULL ? NULL : &hwdev->dev, |
| size, vaddr, dma_handle); |
| } |
| |
| void |
| acxpci_free_desc_queues(acx_device_t *adev) |
| { |
| #define ACX_FREE_QUEUE(size, ptr, phyaddr) \ |
| if (ptr) { \ |
| free_coherent(NULL, size, ptr, phyaddr); \ |
| ptr = NULL; \ |
| size = 0; \ |
| } |
| |
| FN_ENTER; |
| |
| ACX_FREE_QUEUE(adev->txhostdesc_area_size, adev->txhostdesc_start, adev->txhostdesc_startphy); |
| ACX_FREE_QUEUE(adev->txbuf_area_size, adev->txbuf_start, adev->txbuf_startphy); |
| |
| adev->txdesc_start = NULL; |
| |
| ACX_FREE_QUEUE(adev->rxhostdesc_area_size, adev->rxhostdesc_start, adev->rxhostdesc_startphy); |
| ACX_FREE_QUEUE(adev->rxbuf_area_size, adev->rxbuf_start, adev->rxbuf_startphy); |
| |
| adev->rxdesc_start = NULL; |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_delete_dma_regions |
| */ |
| static void |
| acxpci_s_delete_dma_regions(acx_device_t *adev) |
| { |
| unsigned long flags; |
| |
| FN_ENTER; |
| /* disable radio Tx/Rx. Shouldn't we use the firmware commands |
| * here instead? Or are we that much down the road that it's no |
| * longer possible here? */ |
| write_reg16(adev, IO_ACX_ENABLE, 0); |
| |
| acx_s_msleep(100); |
| |
| acx_lock(adev, flags); |
| acxpci_free_desc_queues(adev); |
| acx_unlock(adev, flags); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_e_probe |
| ** |
| ** Probe routine called when a PCI device w/ matching ID is found. |
| ** Here's the sequence: |
| ** - Allocate the PCI resources. |
| ** - Read the PCMCIA attribute memory to make sure we have a WLAN card |
| ** - Reset the MAC |
| ** - Initialize the dev and wlan data |
| ** - Initialize the MAC |
| ** |
| ** pdev - ptr to pci device structure containing info about pci configuration |
| ** id - ptr to the device id entry that matched this device |
| */ |
| static const u16 |
| IO_ACX100[] = |
| { |
| 0x0000, /* IO_ACX_SOFT_RESET */ |
| |
| 0x0014, /* IO_ACX_SLV_MEM_ADDR */ |
| 0x0018, /* IO_ACX_SLV_MEM_DATA */ |
| 0x001c, /* IO_ACX_SLV_MEM_CTL */ |
| 0x0020, /* IO_ACX_SLV_END_CTL */ |
| |
| 0x0034, /* IO_ACX_FEMR */ |
| |
| 0x007c, /* IO_ACX_INT_TRIG */ |
| 0x0098, /* IO_ACX_IRQ_MASK */ |
| 0x00a4, /* IO_ACX_IRQ_STATUS_NON_DES */ |
| 0x00a8, /* IO_ACX_IRQ_STATUS_CLEAR */ |
| 0x00ac, /* IO_ACX_IRQ_ACK */ |
| 0x00b0, /* IO_ACX_HINT_TRIG */ |
| |
| 0x0104, /* IO_ACX_ENABLE */ |
| |
| 0x0250, /* IO_ACX_EEPROM_CTL */ |
| 0x0254, /* IO_ACX_EEPROM_ADDR */ |
| 0x0258, /* IO_ACX_EEPROM_DATA */ |
| 0x025c, /* IO_ACX_EEPROM_CFG */ |
| |
| 0x0268, /* IO_ACX_PHY_ADDR */ |
| 0x026c, /* IO_ACX_PHY_DATA */ |
| 0x0270, /* IO_ACX_PHY_CTL */ |
| |
| 0x0290, /* IO_ACX_GPIO_OE */ |
| |
| 0x0298, /* IO_ACX_GPIO_OUT */ |
| |
| 0x02a4, /* IO_ACX_CMD_MAILBOX_OFFS */ |
| 0x02a8, /* IO_ACX_INFO_MAILBOX_OFFS */ |
| 0x02ac, /* IO_ACX_EEPROM_INFORMATION */ |
| |
| 0x02d0, /* IO_ACX_EE_START */ |
| 0x02d4, /* IO_ACX_SOR_CFG */ |
| 0x02d8 /* IO_ACX_ECPU_CTRL */ |
| }; |
| |
| static const u16 |
| IO_ACX111[] = |
| { |
| 0x0000, /* IO_ACX_SOFT_RESET */ |
| |
| 0x0014, /* IO_ACX_SLV_MEM_ADDR */ |
| 0x0018, /* IO_ACX_SLV_MEM_DATA */ |
| 0x001c, /* IO_ACX_SLV_MEM_CTL */ |
| 0x0020, /* IO_ACX_SLV_END_CTL */ |
| |
| 0x0034, /* IO_ACX_FEMR */ |
| |
| 0x00b4, /* IO_ACX_INT_TRIG */ |
| 0x00d4, /* IO_ACX_IRQ_MASK */ |
| /* we do mean NON_DES (0xf0), not NON_DES_MASK which is at 0xe0: */ |
| 0x00f0, /* IO_ACX_IRQ_STATUS_NON_DES */ |
| 0x00e4, /* IO_ACX_IRQ_STATUS_CLEAR */ |
| 0x00e8, /* IO_ACX_IRQ_ACK */ |
| 0x00ec, /* IO_ACX_HINT_TRIG */ |
| |
| 0x01d0, /* IO_ACX_ENABLE */ |
| |
| 0x0338, /* IO_ACX_EEPROM_CTL */ |
| 0x033c, /* IO_ACX_EEPROM_ADDR */ |
| 0x0340, /* IO_ACX_EEPROM_DATA */ |
| 0x0344, /* IO_ACX_EEPROM_CFG */ |
| |
| 0x0350, /* IO_ACX_PHY_ADDR */ |
| 0x0354, /* IO_ACX_PHY_DATA */ |
| 0x0358, /* IO_ACX_PHY_CTL */ |
| |
| 0x0374, /* IO_ACX_GPIO_OE */ |
| |
| 0x037c, /* IO_ACX_GPIO_OUT */ |
| |
| 0x0388, /* IO_ACX_CMD_MAILBOX_OFFS */ |
| 0x038c, /* IO_ACX_INFO_MAILBOX_OFFS */ |
| 0x0390, /* IO_ACX_EEPROM_INFORMATION */ |
| |
| 0x0100, /* IO_ACX_EE_START */ |
| 0x0104, /* IO_ACX_SOR_CFG */ |
| 0x0108, /* IO_ACX_ECPU_CTRL */ |
| }; |
| |
| static int __devinit |
| acxpci_e_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
| { |
| acx111_ie_configoption_t co; |
| unsigned long mem_region1 = 0; |
| unsigned long mem_region2 = 0; |
| unsigned long mem_region1_size; |
| unsigned long mem_region2_size; |
| unsigned long phymem1; |
| unsigned long phymem2; |
| void *mem1 = NULL; |
| void *mem2 = NULL; |
| acx_device_t *adev = NULL; |
| struct net_device *ndev = NULL; |
| const char *chip_name; |
| int result = -EIO; |
| int err; |
| u8 chip_type; |
| |
| FN_ENTER; |
| |
| /* Enable the PCI device */ |
| if (pci_enable_device(pdev)) { |
| printk("acx: pci_enable_device() FAILED\n"); |
| result = -ENODEV; |
| goto fail_pci_enable_device; |
| } |
| |
| /* enable busmastering (required for CardBus) */ |
| pci_set_master(pdev); |
| |
| /* FIXME: prism54 calls pci_set_mwi() here, |
| * should we do/support the same? */ |
| |
| /* chiptype is u8 but id->driver_data is ulong |
| ** Works for now (possible values are 1 and 2) */ |
| chip_type = (u8)id->driver_data; |
| /* acx100 and acx111 have different PCI memory regions */ |
| if (chip_type == CHIPTYPE_ACX100) { |
| chip_name = "ACX100"; |
| mem_region1 = PCI_ACX100_REGION1; |
| mem_region1_size = PCI_ACX100_REGION1_SIZE; |
| |
| mem_region2 = PCI_ACX100_REGION2; |
| mem_region2_size = PCI_ACX100_REGION2_SIZE; |
| } else if (chip_type == CHIPTYPE_ACX111) { |
| chip_name = "ACX111"; |
| mem_region1 = PCI_ACX111_REGION1; |
| mem_region1_size = PCI_ACX111_REGION1_SIZE; |
| |
| mem_region2 = PCI_ACX111_REGION2; |
| mem_region2_size = PCI_ACX111_REGION2_SIZE; |
| } else { |
| printk("acx: unknown chip type 0x%04X\n", chip_type); |
| goto fail_unknown_chiptype; |
| } |
| |
| /* Figure out our resources */ |
| phymem1 = pci_resource_start(pdev, mem_region1); |
| phymem2 = pci_resource_start(pdev, mem_region2); |
| if (!request_mem_region(phymem1, pci_resource_len(pdev, mem_region1), "acx_1")) { |
| printk("acx: cannot reserve PCI memory region 1 (are you sure " |
| "you have CardBus support in kernel?)\n"); |
| goto fail_request_mem_region1; |
| } |
| if (!request_mem_region(phymem2, pci_resource_len(pdev, mem_region2), "acx_2")) { |
| printk("acx: cannot reserve PCI memory region 2\n"); |
| goto fail_request_mem_region2; |
| } |
| |
| /* this used to be ioremap(), but ioremap_nocache() |
| * is much less risky, right? (and slower?) |
| * FIXME: we may want to go back to cached variant if it's |
| * certain that our code really properly handles |
| * cached operation (memory barriers, volatile?, ...) |
| * (but always keep this comment here regardless!) |
| * Possibly make this a driver config setting? */ |
| |
| mem1 = ioremap_nocache(phymem1, mem_region1_size); |
| if (!mem1) { |
| printk("acx: ioremap() FAILED\n"); |
| goto fail_ioremap1; |
| } |
| mem2 = ioremap_nocache(phymem2, mem_region2_size); |
| if (!mem2) { |
| printk("acx: ioremap() #2 FAILED\n"); |
| goto fail_ioremap2; |
| } |
| |
| printk("acx: found %s-based wireless network card at %s, irq:%d, " |
| "phymem1:0x%lX, phymem2:0x%lX, mem1:0x%p, mem1_size:%ld, " |
| "mem2:0x%p, mem2_size:%ld\n", |
| chip_name, pci_name(pdev), pdev->irq, phymem1, phymem2, |
| mem1, mem_region1_size, |
| mem2, mem_region2_size); |
| log(L_ANY, "initial debug setting is 0x%04X\n", acx_debug); |
| |
| if (0 == pdev->irq) { |
| printk("acx: can't use IRQ 0\n"); |
| goto fail_irq; |
| } |
| |
| ndev = alloc_ieee80211softmac(sizeof(*adev)); |
| /* (NB: memsets to 0 entire area) */ |
| if (!ndev) { |
| printk("acx: no memory for netdevice struct\n"); |
| goto fail_alloc_netdev; |
| } |
| ndev->open = &acxpci_e_open; |
| ndev->stop = &acxpci_e_close; |
| //sm ndev->hard_start_xmit = &acx_i_start_xmit; |
| ndev->get_stats = &acx_e_get_stats; |
| #if IW_HANDLER_VERSION <= 5 |
| ndev->get_wireless_stats = &acx_e_get_wireless_stats; |
| #endif |
| ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def; |
| ndev->set_multicast_list = &acxpci_i_set_multicast_list; //sm? bcm driver hasn't it |
| ndev->tx_timeout = &acxpci_i_tx_timeout; |
| ndev->change_mtu = &acx_e_change_mtu; //sm? bcm driver hasn't it |
| ndev->watchdog_timeo = 4 * HZ; |
| ndev->irq = pdev->irq; |
| ndev->base_addr = pci_resource_start(pdev, 0); |
| |
| adev = ndev2adev(ndev); |
| spin_lock_init(&adev->lock); /* initial state: unlocked */ |
| /* We do not start with downed sem: we want PARANOID_LOCKING to work */ |
| sema_init(&adev->sem, 1); /* initial state: 1 (upped) */ |
| /* since nobody can see new netdev yet, we can as well |
| ** just _presume_ that we're under sem (instead of actually taking it): */ |
| /* acx_sem_lock(adev); */ |
| adev->pdev = pdev; |
| adev->ndev = ndev; |
| adev->dev_type = DEVTYPE_PCI; |
| adev->chip_type = chip_type; |
| adev->chip_name = chip_name; |
| adev->io = (CHIPTYPE_ACX100 == chip_type) ? IO_ACX100 : IO_ACX111; |
| adev->membase = phymem1; |
| adev->iobase = mem1; |
| adev->membase2 = phymem2; |
| adev->iobase2 = mem2; |
| /* to find crashes due to weird driver access |
| * to unconfigured interface (ifup) */ |
| adev->mgmt_timer.function = (void (*)(unsigned long))0x0000dead; |
| //SM |
| adev->ieee = netdev_priv(ndev); |
| /* default to sw encryption for now */ |
| adev->ieee->host_build_iv = 0; |
| adev->ieee->host_encrypt = 1; |
| adev->ieee->host_decrypt = 1; |
| adev->ieee->iw_mode = IW_MODE_INFRA; |
| adev->ieee->tx_headroom = 0; |
| adev->ieee->set_security = acx_e_ieee80211_set_security; |
| adev->ieee->hard_start_xmit = acx_i_ieee80211_start_xmit; |
| adev->softmac = ieee80211_priv(ndev); |
| adev->softmac->set_channel = acx_e_ieee80211_set_chan; |
| |
| adev->ieee->sec.encrypt = 0; |
| adev->ieee->sec.enabled = 0; |
| adev->ieee->sec.auth_mode = WLAN_AUTH_OPEN; |
| |
| if (IS_ACX100(adev)) { |
| adev->ops.create_dma_regions = acx100_s_create_dma_regions; |
| } else { |
| adev->ops.create_dma_regions = acx111_s_create_dma_regions; |
| } |
| adev->ops.delete_dma_regions = acxpci_s_delete_dma_regions; |
| #if ACX_DEBUG |
| adev->ops.issue_cmd = acxpci_s_issue_cmd_timeo_debug; |
| #else |
| adev->ops.issue_cmd = acxpci_s_issue_cmd_timeo; |
| #endif /* ACX_DEBUG */ |
| adev->ops.alloc_tx = acxpci_l_alloc_tx; |
| adev->ops.dealloc_tx = acxpci_l_dealloc_tx; |
| adev->ops.get_txbuf = acxpci_l_get_txbuf; |
| adev->ops.tx_data = acxpci_l_tx_data; |
| adev->ops.write_phy_reg = acxpci_s_write_phy_reg; |
| adev->ops.read_phy_reg = acxpci_s_read_phy_reg; |
| |
| #ifdef NONESSENTIAL_FEATURES |
| acx_show_card_eeprom_id(adev); |
| #endif /* NONESSENTIAL_FEATURES */ |
| |
| #ifdef SET_MODULE_OWNER |
| SET_MODULE_OWNER(ndev); |
| #endif |
| SET_NETDEV_DEV(ndev, &pdev->dev); |
| |
| log(L_IRQ|L_INIT, "using IRQ %d\n", pdev->irq); |
| |
| /* need to be able to restore PCI state after a suspend */ |
| pci_save_state(pdev); |
| pci_set_drvdata(pdev, ndev); |
| |
| /* ok, pci setup is finished, now start initializing the card */ |
| |
| /* NB: read_reg() reads may return bogus data before reset_dev(), |
| * since the firmware which directly controls large parts of the I/O |
| * registers isn't initialized yet. |
| * acx100 seems to be more affected than acx111 */ |
| if (OK != acxpci_s_reset_dev(adev)) |
| goto fail_reset; |
| |
| if (IS_ACX100(adev)) { |
| /* ACX100: configopt struct in cmd mailbox - directly after reset */ |
| memcpy_fromio(&co, adev->cmd_area, sizeof(co)); |
| } |
| |
| if (OK != acx_s_init_mac(adev)) |
| goto fail_init_mac; |
| |
| if (IS_ACX111(adev)) { |
| /* ACX111: configopt struct needs to be queried after full init */ |
| acx_s_interrogate(adev, &co, ACX111_IE_CONFIG_OPTIONS); |
| } |
| |
| acx_s_parse_configoption(adev, &co); |
| acx_s_set_defaults(adev); |
| acx_s_get_firmware_version(adev); /* needs to be after acx_s_init_mac() */ |
| acx_display_hardware_details(adev); |
| |
| /* Register the card, AFTER everything else has been set up, |
| * since otherwise an ioctl could step on our feet due to |
| * firmware operations happening in parallel or uninitialized data */ |
| err = register_netdev(ndev); |
| if (OK != err) { |
| printk("acx: register_netdev() FAILED: %d\n", err); |
| goto fail_register_netdev; |
| } |
| |
| acx_proc_register_entries(ndev); |
| |
| /* Now we have our device, so make sure the kernel doesn't try |
| * to send packets even though we're not associated to a network yet */ |
| acx_stop_queue(ndev, "on probe"); |
| acx_carrier_off(ndev, "on probe"); |
| |
| /* after register_netdev() userspace may start working with dev |
| * (in particular, on other CPUs), we only need to up the sem */ |
| /* acx_sem_unlock(adev); */ |
| |
| printk("acx "ACX_RELEASE": net device %s, driver compiled " |
| "against wireless extensions %d\n", |
| ndev->name, WIRELESS_EXT); |
| |
| #if CMD_DISCOVERY |
| great_inquisitor(adev); |
| #endif |
| |
| result = OK; |
| goto done; |
| |
| /* error paths: undo everything in reverse order... */ |
| |
| fail_register_netdev: |
| |
| acxpci_s_delete_dma_regions(adev); |
| pci_set_drvdata(pdev, NULL); |
| |
| fail_init_mac: |
| fail_reset: |
| |
| free_netdev(ndev); |
| fail_alloc_netdev: |
| fail_irq: |
| |
| iounmap(mem2); |
| fail_ioremap2: |
| |
| iounmap(mem1); |
| fail_ioremap1: |
| |
| release_mem_region(pci_resource_start(pdev, mem_region2), |
| pci_resource_len(pdev, mem_region2)); |
| fail_request_mem_region2: |
| |
| release_mem_region(pci_resource_start(pdev, mem_region1), |
| pci_resource_len(pdev, mem_region1)); |
| fail_request_mem_region1: |
| fail_unknown_chiptype: |
| |
| pci_disable_device(pdev); |
| fail_pci_enable_device: |
| |
| pci_set_power_state(pdev, PCI_D3hot); |
| |
| done: |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_e_remove |
| ** |
| ** Shut device down (if not hot unplugged) |
| ** and deallocate PCI resources for the acx chip. |
| ** |
| ** pdev - ptr to PCI device structure containing info about pci configuration |
| */ |
| static void __devexit |
| acxpci_e_remove(struct pci_dev *pdev) |
| { |
| struct net_device *ndev; |
| acx_device_t *adev; |
| unsigned long mem_region1, mem_region2; |
| unsigned long flags; |
| |
| FN_ENTER; |
| |
| ndev = (struct net_device*) pci_get_drvdata(pdev); |
| if (!ndev) { |
| /* Never happens? */ |
| goto end; |
| } |
| |
| adev = ndev2adev(ndev); |
| |
| /* If device wasn't hot unplugged... */ |
| if (adev_present(adev)) { |
| |
| acx_sem_lock(adev); |
| |
| /* disable both Tx and Rx to shut radio down properly */ |
| acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0); |
| acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0); |
| |
| #ifdef REDUNDANT |
| /* put the eCPU to sleep to save power |
| * Halting is not possible currently, |
| * since not supported by all firmware versions */ |
| acx_s_issue_cmd(adev, ACX100_CMD_SLEEP, NULL, 0); |
| #endif |
| acx_lock(adev, flags); |
| /* disable power LED to save power :-) */ |
| log(L_INIT, "switching off power LED to save power\n"); |
| acxpci_l_power_led(adev, 0); |
| /* stop our eCPU */ |
| if (IS_ACX111(adev)) { |
| /* FIXME: does this actually keep halting the eCPU? |
| * I don't think so... |
| */ |
| acxpci_l_reset_mac(adev); |
| } else { |
| u16 temp; |
| /* halt eCPU */ |
| temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1; |
| write_reg16(adev, IO_ACX_ECPU_CTRL, temp); |
| write_flush(adev); |
| } |
| acx_unlock(adev, flags); |
| |
| acx_sem_unlock(adev); |
| } |
| |
| /* unregister the device to not let the kernel |
| * (e.g. ioctls) access a half-deconfigured device |
| * NB: this will cause acxpci_e_close() to be called, |
| * thus we shouldn't call it under sem! */ |
| log(L_INIT, "removing device %s\n", ndev->name); |
| unregister_netdev(ndev); |
| |
| /* unregister_netdev ensures that no references to us left. |
| * For paranoid reasons we continue to follow the rules */ |
| acx_sem_lock(adev); |
| |
| if (adev->dev_state_mask & ACX_STATE_IFACE_UP) { |
| acxpci_s_down(ndev); |
| CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP); |
| } |
| |
| acx_proc_unregister_entries(ndev); |
| |
| if (IS_ACX100(adev)) { |
| mem_region1 = PCI_ACX100_REGION1; |
| mem_region2 = PCI_ACX100_REGION2; |
| } else { |
| mem_region1 = PCI_ACX111_REGION1; |
| mem_region2 = PCI_ACX111_REGION2; |
| } |
| |
| /* finally, clean up PCI bus state */ |
| acxpci_s_delete_dma_regions(adev); |
| if (adev->iobase) iounmap(adev->iobase); |
| if (adev->iobase2) iounmap(adev->iobase2); |
| release_mem_region(pci_resource_start(pdev, mem_region1), |
| pci_resource_len(pdev, mem_region1)); |
| release_mem_region(pci_resource_start(pdev, mem_region2), |
| pci_resource_len(pdev, mem_region2)); |
| pci_disable_device(pdev); |
| |
| /* remove dev registration */ |
| pci_set_drvdata(pdev, NULL); |
| |
| acx_sem_unlock(adev); |
| |
| /* Free netdev (quite late, |
| * since otherwise we might get caught off-guard |
| * by a netdev timeout handler execution |
| * expecting to see a working dev...) */ |
| free_netdev(ndev); |
| |
| /* put device into ACPI D3 mode (shutdown) */ |
| pci_set_power_state(pdev, PCI_D3hot); |
| |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** TODO: PM code needs to be fixed / debugged / tested. |
| */ |
| #ifdef CONFIG_PM |
| static int |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) |
| acxpci_e_suspend(struct pci_dev *pdev, pm_message_t state) |
| #else |
| acxpci_e_suspend(struct pci_dev *pdev, u32 state) |
| #endif |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| acx_device_t *adev; |
| |
| FN_ENTER; |
| printk("acx: suspend handler is experimental!\n"); |
| printk("sus: dev %p\n", ndev); |
| |
| if (!netif_running(ndev)) |
| goto end; |
| |
| adev = ndev2adev(ndev); |
| printk("sus: adev %p\n", adev); |
| |
| acx_sem_lock(adev); |
| |
| netif_device_detach(ndev); /* this one cannot sleep */ |
| acxpci_s_down(ndev); |
| /* down() does not set it to 0xffff, but here we really want that */ |
| write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff); |
| write_reg16(adev, IO_ACX_FEMR, 0x0); |
| acxpci_s_delete_dma_regions(adev); |
| pci_save_state(pdev); |
| pci_set_power_state(pdev, PCI_D3hot); |
| |
| acx_sem_unlock(adev); |
| end: |
| FN_EXIT0; |
| return OK; |
| } |
| |
| |
| static int |
| acxpci_e_resume(struct pci_dev *pdev) |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| acx_device_t *adev; |
| |
| FN_ENTER; |
| |
| printk("acx: resume handler is experimental!\n"); |
| printk("rsm: got dev %p\n", ndev); |
| |
| if (!netif_running(ndev)) |
| goto end; |
| |
| adev = ndev2adev(ndev); |
| printk("rsm: got adev %p\n", adev); |
| |
| acx_sem_lock(adev); |
| |
| pci_set_power_state(pdev, PCI_D0); |
| printk("rsm: power state PCI_D0 set\n"); |
| pci_restore_state(pdev); |
| printk("rsm: PCI state restored\n"); |
| |
| if (OK != acxpci_s_reset_dev(adev)) |
| goto end_unlock; |
| printk("rsm: device reset done\n"); |
| if (OK != acx_s_init_mac(adev)) |
| goto end_unlock; |
| printk("rsm: init MAC done\n"); |
| |
| acxpci_s_up(ndev); |
| printk("rsm: acx up done\n"); |
| |
| /* now even reload all card parameters as they were before suspend, |
| * and possibly be back in the network again already :-) */ |
| if (ACX_STATE_IFACE_UP & adev->dev_state_mask) { |
| adev->set_mask = GETSET_ALL; |
| acx_s_update_card_settings(adev); |
| printk("rsm: settings updated\n"); |
| } |
| netif_device_attach(ndev); |
| printk("rsm: device attached\n"); |
| |
| end_unlock: |
| acx_sem_unlock(adev); |
| end: |
| /* we need to return OK here anyway, right? */ |
| FN_EXIT0; |
| return OK; |
| } |
| #endif /* CONFIG_PM */ |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_up |
| ** |
| ** This function is called by acxpci_e_open (when ifconfig sets the device as up) |
| ** |
| ** Side effects: |
| ** - Enables on-card interrupt requests |
| ** - calls acx_s_start |
| */ |
| |
| static void |
| enable_acx_irq(acx_device_t *adev) |
| { |
| FN_ENTER; |
| write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask); |
| write_reg16(adev, IO_ACX_FEMR, 0x8000); |
| adev->irqs_active = 1; |
| FN_EXIT0; |
| } |
| |
| static void |
| acxpci_s_up(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| enable_acx_irq(adev); |
| acx_unlock(adev, flags); |
| |
| /* acx fw < 1.9.3.e has a hardware timer, and older drivers |
| ** used to use it. But we don't do that anymore, our OS |
| ** has reliable software timers */ |
| init_timer(&adev->mgmt_timer); |
| adev->mgmt_timer.function = acx_i_timer; |
| adev->mgmt_timer.data = (unsigned long)adev; |
| |
| /* Need to set ACX_STATE_IFACE_UP first, or else |
| ** timer won't be started by acx_set_status() */ |
| SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP); |
| switch (adev->mode) { |
| case ACX_MODE_0_ADHOC: |
| case ACX_MODE_2_STA: |
| /* actual scan cmd will happen in start() */ |
| acx_set_status(adev, ACX_STATUS_1_SCANNING); break; |
| case ACX_MODE_3_AP: |
| case ACX_MODE_MONITOR: |
| acx_set_status(adev, ACX_STATUS_4_ASSOCIATED); break; |
| } |
| |
| acx_s_start(adev); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_s_down |
| ** |
| ** NB: device may be already hot unplugged if called from acxpci_e_remove() |
| ** |
| ** Disables on-card interrupt request, stops softirq and timer, stops queue, |
| ** sets status == STOPPED |
| */ |
| |
| static void |
| disable_acx_irq(acx_device_t *adev) |
| { |
| FN_ENTER; |
| |
| /* I guess mask is not 0xffff because acx100 won't signal |
| ** cmd completion then (needed for ifup). |
| ** Someone with acx100 please confirm */ |
| write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask_off); |
| write_reg16(adev, IO_ACX_FEMR, 0x0); |
| adev->irqs_active = 0; |
| FN_EXIT0; |
| } |
| |
| static void |
| acxpci_s_down(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| |
| FN_ENTER; |
| |
| /* Disable IRQs first, so that IRQs cannot race with us */ |
| /* then wait until interrupts have finished executing on other CPUs */ |
| acx_lock(adev, flags); |
| disable_acx_irq(adev); |
| synchronize_irq(adev->pdev->irq); |
| acx_unlock(adev, flags); |
| |
| /* we really don't want to have an asynchronous tasklet disturb us |
| ** after something vital for its job has been shut down, so |
| ** end all remaining work now. |
| ** |
| ** NB: carrier_off (done by set_status below) would lead to |
| ** not yet fully understood deadlock in flush_scheduled_work(). |
| ** That's why we do FLUSH first. |
| ** |
| ** NB2: we have a bad locking bug here: flush_scheduled_work() |
| ** waits for acx_e_after_interrupt_task to complete if it is running |
| ** on another CPU, but acx_e_after_interrupt_task |
| ** will sleep on sem forever, because it is taken by us! |
| ** Work around that by temporary sem unlock. |
| ** This will fail miserably if we'll be hit by concurrent |
| ** iwconfig or something in between. TODO! */ |
| acx_sem_unlock(adev); |
| flush_scheduled_work(); |
| acx_sem_lock(adev); |
| |
| /* This is possible: |
| ** flush_scheduled_work -> acx_e_after_interrupt_task -> |
| ** -> set_status(ASSOCIATED) -> wake_queue() |
| ** That's why we stop queue _after_ flush_scheduled_work |
| ** lock/unlock is just paranoia, maybe not needed */ |
| acx_lock(adev, flags); |
| acx_stop_queue(ndev, "on ifdown"); |
| acx_set_status(adev, ACX_STATUS_0_STOPPED); |
| acx_unlock(adev, flags); |
| |
| /* kernel/timer.c says it's illegal to del_timer_sync() |
| ** a timer which restarts itself. We guarantee this cannot |
| ** ever happen because acx_i_timer() never does this if |
| ** status is ACX_STATUS_0_STOPPED */ |
| del_timer_sync(&adev->mgmt_timer); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_e_open |
| ** |
| ** Called as a result of SIOCSIFFLAGS ioctl changing the flags bit IFF_UP |
| ** from clear to set. In other words: ifconfig up. |
| ** |
| ** Returns: |
| ** 0 success |
| ** >0 f/w reported error |
| ** <0 driver reported error |
| */ |
| static int |
| acxpci_e_open(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| int result = OK; |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| |
| acx_init_task_scheduler(adev); |
| |
| //TODO: pci_set_power_state(pdev, PCI_D0); ? |
| |
| /* request shared IRQ handler */ |
| if (request_irq(ndev->irq, acxpci_i_interrupt, SA_SHIRQ, ndev->name, ndev)) { |
| printk("%s: request_irq FAILED\n", ndev->name); |
| result = -EAGAIN; |
| goto done; |
| } |
| log(L_DEBUG|L_IRQ, "request_irq %d successful\n", ndev->irq); |
| |
| /* ifup device */ |
| acxpci_s_up(ndev); |
| |
| /* We don't currently have to do anything else. |
| * The setup of the MAC should be subsequently completed via |
| * the mlme commands. |
| * Higher layers know we're ready from dev->start==1 and |
| * dev->tbusy==0. Our rx path knows to pass up received/ |
| * frames because of dev->flags&IFF_UP is true. |
| */ |
| done: |
| acx_sem_unlock(adev); |
| |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_e_close |
| ** |
| ** Called as a result of SIOCSIIFFLAGS ioctl changing the flags bit IFF_UP |
| ** from set to clear. I.e. called by "ifconfig DEV down" |
| ** |
| ** Returns: |
| ** 0 success |
| ** >0 f/w reported error |
| ** <0 driver reported error |
| */ |
| static int |
| acxpci_e_close(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| |
| /* ifdown device */ |
| CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP); |
| if (netif_device_present(ndev)) { |
| acxpci_s_down(ndev); |
| } |
| |
| /* disable all IRQs, release shared IRQ handler */ |
| write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff); |
| write_reg16(adev, IO_ACX_FEMR, 0x0); |
| free_irq(ndev->irq, ndev); |
| |
| //TODO: pci_set_power_state(pdev, PCI_D3hot); ? |
| |
| /* We currently don't have to do anything else. |
| * Higher layers know we're not ready from dev->start==0 and |
| * dev->tbusy==1. Our rx path knows to not pass up received |
| * frames because of dev->flags&IFF_UP is false. |
| */ |
| acx_sem_unlock(adev); |
| |
| log(L_INIT, "closed device\n"); |
| FN_EXIT0; |
| return OK; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_i_tx_timeout |
| ** |
| ** Called from network core. Must not sleep! |
| */ |
| static void |
| acxpci_i_tx_timeout(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| unsigned int tx_num_cleaned; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| |
| /* clean processed tx descs, they may have been completely full */ |
| tx_num_cleaned = acxpci_l_clean_txdesc(adev); |
| |
| /* nothing cleaned, yet (almost) no free buffers available? |
| * --> clean all tx descs, no matter which status!! |
| * Note that I strongly suspect that doing emergency cleaning |
| * may confuse the firmware. This is a last ditch effort to get |
| * ANYTHING to work again... |
| * |
| * TODO: it's best to simply reset & reinit hw from scratch... |
| */ |
| if ((adev->tx_free <= TX_EMERG_CLEAN) && (tx_num_cleaned == 0)) { |
| printk("%s: FAILED to free any of the many full tx buffers. " |
| "Switching to emergency freeing. " |
| "Please report!\n", ndev->name); |
| acxpci_l_clean_txdesc_emergency(adev); |
| } |
| |
| if (acx_queue_stopped(ndev) && (ACX_STATUS_4_ASSOCIATED == adev->status)) |
| acx_wake_queue(ndev, "after tx timeout"); |
| |
| /* stall may have happened due to radio drift, so recalib radio */ |
| acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB); |
| |
| /* do unimportant work last */ |
| printk("%s: tx timeout!\n", ndev->name); |
| adev->stats.tx_errors++; |
| |
| acx_unlock(adev, flags); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_i_set_multicast_list |
| ** FIXME: most likely needs refinement |
| */ |
| static void |
| acxpci_i_set_multicast_list(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| |
| /* firmwares don't have allmulti capability, |
| * so just use promiscuous mode instead in this case. */ |
| if (ndev->flags & (IFF_PROMISC|IFF_ALLMULTI)) { |
| SET_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS); |
| CLEAR_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI); |
| SET_BIT(adev->set_mask, SET_RXCONFIG); |
| /* let kernel know in case *we* needed to set promiscuous */ |
| ndev->flags |= (IFF_PROMISC|IFF_ALLMULTI); |
| } else { |
| CLEAR_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS); |
| SET_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI); |
| SET_BIT(adev->set_mask, SET_RXCONFIG); |
| ndev->flags &= ~(IFF_PROMISC|IFF_ALLMULTI); |
| } |
| |
| /* cannot update card settings directly here, atomic context */ |
| acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG); |
| |
| acx_unlock(adev, flags); |
| |
| FN_EXIT0; |
| } |
| |
| |
| /*************************************************************** |
| ** acxpci_l_process_rxdesc |
| ** |
| ** Called directly and only from the IRQ handler |
| */ |
| |
| #if !ACX_DEBUG |
| static inline void log_rxbuffer(const acx_device_t *adev) {} |
| #else |
| static void |
| log_rxbuffer(const acx_device_t *adev) |
| { |
| register const struct rxhostdesc *rxhostdesc; |
| int i; |
| /* no FN_ENTER here, we don't want that */ |
| |
| rxhostdesc = adev->rxhostdesc_start; |
| if (unlikely(!rxhostdesc)) return; |
| for (i = 0; i < RX_CNT; i++) { |
| if ((rxhostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN)) |
| && (rxhostdesc->Status & cpu_to_le32(DESC_STATUS_FULL))) |
| printk("rx: buf %d full\n", i); |
| rxhostdesc++; |
| } |
| } |
| #endif |
| |
| static void |
| acxpci_l_process_rxdesc(acx_device_t *adev) |
| { |
| register rxhostdesc_t *hostdesc; |
| unsigned count, tail; |
| |
| FN_ENTER; |
| |
| if (unlikely(acx_debug & L_BUFR)) |
| log_rxbuffer(adev); |
| |
| /* First, have a loop to determine the first descriptor that's |
| * full, just in case there's a mismatch between our current |
| * rx_tail and the full descriptor we're supposed to handle. */ |
| tail = adev->rx_tail; |
| count = RX_CNT; |
| while (1) { |
| hostdesc = &adev->rxhostdesc_start[tail]; |
| /* advance tail regardless of outcome of the below test */ |
| tail = (tail + 1) % RX_CNT; |
| |
| if ((hostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN)) |
| && (hostdesc->Status & cpu_to_le32(DESC_STATUS_FULL))) |
| break; /* found it! */ |
| |
| if (unlikely(!--count)) /* hmm, no luck: all descs empty, bail out */ |
| goto end; |
| } |
| |
| /* now process descriptors, starting with the first we figured out */ |
| while (1) { |
| log(L_BUFR, "rx: tail=%u Ctl_16=%04X Status=%08X\n", |
| tail, hostdesc->Ctl_16, hostdesc->Status); |
| |
| acx_l_softmac_process_rxbuf(adev, hostdesc->data); //SM |
| |
| hostdesc->Status = 0; |
| /* flush all writes before adapter sees CTL_HOSTOWN change */ |
| wmb(); |
| /* Host no longer owns this, needs to be LAST */ |
| CLEAR_BIT(hostdesc->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN)); |
| |
| /* ok, descriptor is handled, now check the next descriptor */ |
| hostdesc = &adev->rxhostdesc_start[tail]; |
| |
| /* if next descriptor is empty, then bail out */ |
| if (!(hostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN)) |
| || !(hostdesc->Status & cpu_to_le32(DESC_STATUS_FULL))) |
| break; |
| |
| tail = (tail + 1) % RX_CNT; |
| } |
| end: |
| adev->rx_tail = tail; |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_i_interrupt |
| ** |
| ** IRQ handler (atomic context, must not sleep, blah, blah) |
| */ |
| |
| /* scan is complete. all frames now on the receive queue are valid */ |
| #define INFO_SCAN_COMPLETE 0x0001 |
| #define INFO_WEP_KEY_NOT_FOUND 0x0002 |
| /* hw has been reset as the result of a watchdog timer timeout */ |
| #define INFO_WATCH_DOG_RESET 0x0003 |
| /* failed to send out NULL frame from PS mode notification to AP */ |
| /* recommended action: try entering 802.11 PS mode again */ |
| #define INFO_PS_FAIL 0x0004 |
| /* encryption/decryption process on a packet failed */ |
| #define INFO_IV_ICV_FAILURE 0x0005 |
| |
| /* Info mailbox format: |
| 2 bytes: type |
| 2 bytes: status |
| more bytes may follow |
| rumors say about status: |
| 0x0000 info available (set by hw) |
| 0x0001 information received (must be set by host) |
| 0x1000 info available, mailbox overflowed (messages lost) (set by hw) |
| but in practice we've seen: |
| 0x9000 when we did not set status to 0x0001 on prev message |
| 0x1001 when we did set it |
| 0x0000 was never seen |
| conclusion: this is really a bitfield: |
| 0x1000 is 'info available' bit |
| 'mailbox overflowed' bit is 0x8000, not 0x1000 |
| value of 0x0000 probably means that there are no messages at all |
| P.S. I dunno how in hell hw is supposed to notice that messages are lost - |
| it does NOT clear bit 0x0001, and this bit will probably stay forever set |
| after we set it once. Let's hope this will be fixed in firmware someday |
| */ |
| |
| static void |
| handle_info_irq(acx_device_t *adev) |
| { |
| #if ACX_DEBUG |
| static const char * const info_type_msg[] = { |
| "(unknown)", |
| "scan complete", |
| "WEP key not found", |
| "internal watchdog reset was done", |
| "failed to send powersave (NULL frame) notification to AP", |
| "encrypt/decrypt on a packet has failed", |
| "TKIP tx keys disabled", |
| "TKIP rx keys disabled", |
| "TKIP rx: key ID not found", |
| "???", |
| "???", |
| "???", |
| "???", |
| "???", |
| "???", |
| "???", |
| "TKIP IV value exceeds thresh" |
| }; |
| #endif |
| u32 info_type, info_status; |
| |
| info_type = readl(adev->info_area); |
| info_status = (info_type >> 16); |
| info_type = (u16)info_type; |
| |
| /* inform fw that we have read this info message */ |
| writel(info_type | 0x00010000, adev->info_area); |
| write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_INFOACK); |
| write_flush(adev); |
| |
| log(L_CTL, "info_type:%04X info_status:%04X\n", |
| info_type, info_status); |
| |
| log(L_IRQ, "got Info IRQ: status %04X type %04X: %s\n", |
| info_status, info_type, |
| info_type_msg[(info_type >= VEC_SIZE(info_type_msg)) ? |
| 0 : info_type] |
| ); |
| } |
| |
| |
| static void |
| log_unusual_irq(u16 irqtype) { |
| /* |
| if (!printk_ratelimit()) |
| return; |
| */ |
| |
| printk("acx: got"); |
| if (irqtype & HOST_INT_RX_DATA) { |
| printk(" Rx_Data"); |
| } |
| /* HOST_INT_TX_COMPLETE */ |
| if (irqtype & HOST_INT_TX_XFER) { |
| printk(" Tx_Xfer"); |
| } |
| /* HOST_INT_RX_COMPLETE */ |
| if (irqtype & HOST_INT_DTIM) { |
| printk(" DTIM"); |
| } |
| if (irqtype & HOST_INT_BEACON) { |
| printk(" Beacon"); |
| } |
| if (irqtype & HOST_INT_TIMER) { |
| log(L_IRQ, " Timer"); |
| } |
| if (irqtype & HOST_INT_KEY_NOT_FOUND) { |
| printk(" Key_Not_Found"); |
| } |
| if (irqtype & HOST_INT_IV_ICV_FAILURE) { |
| printk(" IV_ICV_Failure (WEP?)"); |
| } |
| /* HOST_INT_CMD_COMPLETE */ |
| /* HOST_INT_INFO */ |
| if (irqtype & HOST_INT_OVERFLOW) { |
| printk(" Overflow"); |
| } |
| if (irqtype & HOST_INT_PROCESS_ERROR) { |
| printk(" Process_Error"); |
| } |
| /* HOST_INT_SCAN_COMPLETE */ |
| if (irqtype & HOST_INT_FCS_THRESHOLD) { |
| printk(" FCS_Threshold"); |
| } |
| if (irqtype & HOST_INT_UNKNOWN) { |
| printk(" Unknown"); |
| } |
| printk(" IRQ(s)\n"); |
| } |
| |
| |
| static void |
| update_link_quality_led(acx_device_t *adev) |
| { |
| int qual; |
| |
| qual = acx_signal_determine_quality(adev->wstats.qual.level, adev->wstats.qual.noise); |
| if (qual > adev->brange_max_quality) |
| qual = adev->brange_max_quality; |
| |
| if (time_after(jiffies, adev->brange_time_last_state_change + |
| (HZ/2 - HZ/2 * (unsigned long)qual / adev->brange_max_quality ) )) { |
| acxpci_l_power_led(adev, (adev->brange_last_state == 0)); |
| adev->brange_last_state ^= 1; /* toggle */ |
| adev->brange_time_last_state_change = jiffies; |
| } |
| } |
| |
| |
| #define MAX_IRQLOOPS_PER_JIFFY (20000/HZ) /* a la orinoco.c */ |
| |
| static irqreturn_t |
| acxpci_i_interrupt(int irq, void *dev_id) |
| { |
| acx_device_t *adev; |
| unsigned long flags; |
| unsigned int irqcount = MAX_IRQLOOPS_PER_JIFFY; |
| register u16 irqtype; |
| u16 unmasked; |
| |
| adev = ndev2adev((struct net_device*)dev_id); |
| |
| /* LOCKING: can just spin_lock() since IRQs are disabled anyway. |
| * I am paranoid */ |
| acx_lock(adev, flags); |
| |
| unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR); |
| if (unlikely(0xffff == unmasked)) { |
| /* 0xffff value hints at missing hardware, |
| * so don't do anything. |
| * Not very clean, but other drivers do the same... */ |
| log(L_IRQ, "IRQ type:FFFF - device removed? IRQ_NONE\n"); |
| goto none; |
| } |
| |
| /* We will check only "interesting" IRQ types */ |
| irqtype = unmasked & ~adev->irq_mask; |
| if (!irqtype) { |
| /* We are on a shared IRQ line and it wasn't our IRQ */ |
| log(L_IRQ, "IRQ type:%04X, mask:%04X - all are masked, IRQ_NONE\n", |
| unmasked, adev->irq_mask); |
| goto none; |
| } |
| |
| /* Done here because IRQ_NONEs taking three lines of log |
| ** drive me crazy */ |
| FN_ENTER; |
| |
| #define IRQ_ITERATE 1 |
| #if IRQ_ITERATE |
| if (jiffies != adev->irq_last_jiffies) { |
| adev->irq_loops_this_jiffy = 0; |
| adev->irq_last_jiffies = jiffies; |
| } |
| |
| /* safety condition; we'll normally abort loop below |
| * in case no IRQ type occurred */ |
| while (likely(--irqcount)) { |
| #endif |
| /* ACK all IRQs ASAP */ |
| write_reg16(adev, IO_ACX_IRQ_ACK, 0xffff); |
| |
| log(L_IRQ, "IRQ type:%04X, mask:%04X, type & ~mask:%04X\n", |
| unmasked, adev->irq_mask, irqtype); |
| |
| /* Handle most important IRQ types first */ |
| if (irqtype & HOST_INT_RX_COMPLETE) { |
| log(L_IRQ, "got Rx_Complete IRQ\n"); |
| acxpci_l_process_rxdesc(adev); |
| } |
| if (irqtype & HOST_INT_TX_COMPLETE) { |
| log(L_IRQ, "got Tx_Complete IRQ\n"); |
| /* don't clean up on each Tx complete, wait a bit |
| * unless we're going towards full, in which case |
| * we do it immediately, too (otherwise we might lockup |
| * with a full Tx buffer if we go into |
| * acxpci_l_clean_txdesc() at a time when we won't wakeup |
| * the net queue in there for some reason...) */ |
| if (adev->tx_free <= TX_START_CLEAN) { |
| #if TX_CLEANUP_IN_SOFTIRQ |
| acx_schedule_task(adev, ACX_AFTER_IRQ_TX_CLEANUP); |
| #else |
| acxpci_l_clean_txdesc(adev); |
| #endif |
| } |
| } |
| |
| /* Less frequent ones */ |
| if (irqtype & (0 |
| | HOST_INT_CMD_COMPLETE |
| | HOST_INT_INFO |
| | HOST_INT_SCAN_COMPLETE |
| )) { |
| if (irqtype & HOST_INT_CMD_COMPLETE) { |
| log(L_IRQ, "got Command_Complete IRQ\n"); |
| /* save the state for the running issue_cmd() */ |
| SET_BIT(adev->irq_status, HOST_INT_CMD_COMPLETE); |
| } |
| if (irqtype & HOST_INT_INFO) { |
| handle_info_irq(adev); |
| } |
| if (irqtype & HOST_INT_SCAN_COMPLETE) { |
| log(L_IRQ, "got Scan_Complete IRQ\n"); |
| /* need to do that in process context */ |
| acx_schedule_task(adev, ACX_AFTER_IRQ_COMPLETE_SCAN); |
| /* remember that fw is not scanning anymore */ |
| SET_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE); |
| } |
| } |
| |
| /* These we just log, but either they happen rarely |
| * or we keep them masked out */ |
| if (irqtype & (0 |
| | HOST_INT_RX_DATA |
| /* | HOST_INT_TX_COMPLETE */ |
| | HOST_INT_TX_XFER |
| /* | HOST_INT_RX_COMPLETE */ |
| | HOST_INT_DTIM |
| | HOST_INT_BEACON |
| | HOST_INT_TIMER |
| | HOST_INT_KEY_NOT_FOUND |
| | HOST_INT_IV_ICV_FAILURE |
| /* | HOST_INT_CMD_COMPLETE */ |
| /* | HOST_INT_INFO */ |
| | HOST_INT_OVERFLOW |
| | HOST_INT_PROCESS_ERROR |
| /* | HOST_INT_SCAN_COMPLETE */ |
| | HOST_INT_FCS_THRESHOLD |
| | HOST_INT_UNKNOWN |
| )) { |
| log_unusual_irq(irqtype); |
| } |
| |
| #if IRQ_ITERATE |
| unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR); |
| irqtype = unmasked & ~adev->irq_mask; |
| /* Bail out if no new IRQ bits or if all are masked out */ |
| if (!irqtype) |
| break; |
| |
| if (unlikely(++adev->irq_loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY)) { |
| printk(KERN_ERR "acx: too many interrupts per jiffy!\n"); |
| /* Looks like card floods us with IRQs! Try to stop that */ |
| write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff); |
| /* This will short-circuit all future attempts to handle IRQ. |
| * We cant do much more... */ |
| adev->irq_mask = 0; |
| break; |
| } |
| } |
| #endif |
| /* Routine to perform blink with range */ |
| if (unlikely(adev->led_power == 2)) |
| update_link_quality_led(adev); |
| |
| /* handled: */ |
| /* write_flush(adev); - not needed, last op was read anyway */ |
| acx_unlock(adev, flags); |
| FN_EXIT0; |
| return IRQ_HANDLED; |
| |
| none: |
| acx_unlock(adev, flags); |
| return IRQ_NONE; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_l_power_led |
| */ |
| void |
| acxpci_l_power_led(acx_device_t *adev, int enable) |
| { |
| u16 gpio_pled = IS_ACX111(adev) ? 0x0040 : 0x0800; |
| |
| /* A hack. Not moving message rate limiting to adev->xxx |
| * (it's only a debug message after all) */ |
| static int rate_limit = 0; |
| |
| if (rate_limit++ < 3) |
| log(L_IOCTL, "Please report in case toggling the power " |
| "LED doesn't work for your card!\n"); |
| if (enable) |
| write_reg16(adev, IO_ACX_GPIO_OUT, |
| read_reg16(adev, IO_ACX_GPIO_OUT) & ~gpio_pled); |
| else |
| write_reg16(adev, IO_ACX_GPIO_OUT, |
| read_reg16(adev, IO_ACX_GPIO_OUT) | gpio_pled); |
| } |
| |
| |
| /*********************************************************************** |
| ** Ioctls |
| */ |
| |
| /*********************************************************************** |
| */ |
| int |
| acx111pci_ioctl_info( |
| struct net_device *ndev, |
| struct iw_request_info *info, |
| struct iw_param *vwrq, |
| char *extra) |
| { |
| #if ACX_DEBUG > 1 |
| acx_device_t *adev = ndev2adev(ndev); |
| rxdesc_t *rxdesc; |
| txdesc_t *txdesc; |
| rxhostdesc_t *rxhostdesc; |
| txhostdesc_t *txhostdesc; |
| struct acx111_ie_memoryconfig memconf; |
| struct acx111_ie_queueconfig queueconf; |
| unsigned long flags; |
| int i; |
| char memmap[0x34]; |
| char rxconfig[0x8]; |
| char fcserror[0x8]; |
| char ratefallback[0x5]; |
| |
| if ( !(acx_debug & (L_IOCTL|L_DEBUG)) ) |
| return OK; |
| /* using printk() since we checked debug flag already */ |
| |
| acx_sem_lock(adev); |
| |
| if (!IS_ACX111(adev)) { |
| printk("acx111-specific function called " |
| "with non-acx111 chip, aborting\n"); |
| goto end_ok; |
| } |
| |
| /* get Acx111 Memory Configuration */ |
| memset(&memconf, 0, sizeof(memconf)); |
| /* BTW, fails with 12 (Write only) error code. |
| ** Retained for easy testing of issue_cmd error handling :) */ |
| acx_s_interrogate(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG); |
| |
| /* get Acx111 Queue Configuration */ |
| memset(&queueconf, 0, sizeof(queueconf)); |
| acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS); |
| |
| /* get Acx111 Memory Map */ |
| memset(memmap, 0, sizeof(memmap)); |
| acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP); |
| |
| /* get Acx111 Rx Config */ |
| memset(rxconfig, 0, sizeof(rxconfig)); |
| acx_s_interrogate(adev, &rxconfig, ACX1xx_IE_RXCONFIG); |
| |
| /* get Acx111 fcs error count */ |
| memset(fcserror, 0, sizeof(fcserror)); |
| acx_s_interrogate(adev, &fcserror, ACX1xx_IE_FCS_ERROR_COUNT); |
| |
| /* get Acx111 rate fallback */ |
| memset(ratefallback, 0, sizeof(ratefallback)); |
| acx_s_interrogate(adev, &ratefallback, ACX1xx_IE_RATE_FALLBACK); |
| |
| /* force occurrence of a beacon interrupt */ |
| /* TODO: comment why is this necessary */ |
| write_reg16(adev, IO_ACX_HINT_TRIG, HOST_INT_BEACON); |
| |
| /* dump Acx111 Mem Configuration */ |
| printk("dump mem config:\n" |
| "data read: %d, struct size: %d\n" |
| "Number of stations: %1X\n" |
| "Memory block size: %1X\n" |
| "tx/rx memory block allocation: %1X\n" |
| "count rx: %X / tx: %X queues\n" |
| "options %1X\n" |
| "fragmentation %1X\n" |
| "Rx Queue 1 Count Descriptors: %X\n" |
| "Rx Queue 1 Host Memory Start: %X\n" |
| "Tx Queue 1 Count Descriptors: %X\n" |
| "Tx Queue 1 Attributes: %X\n", |
| memconf.len, (int) sizeof(memconf), |
| memconf.no_of_stations, |
| memconf.memory_block_size, |
| memconf.tx_rx_memory_block_allocation, |
| memconf.count_rx_queues, memconf.count_tx_queues, |
| memconf.options, |
| memconf.fragmentation, |
| memconf.rx_queue1_count_descs, |
| acx2cpu(memconf.rx_queue1_host_rx_start), |
| memconf.tx_queue1_count_descs, |
| memconf.tx_queue1_attributes); |
| |
| /* dump Acx111 Queue Configuration */ |
| printk("dump queue head:\n" |
| "data read: %d, struct size: %d\n" |
| "tx_memory_block_address (from card): %X\n" |
| "rx_memory_block_address (from card): %X\n" |
| "rx1_queue address (from card): %X\n" |
| "tx1_queue address (from card): %X\n" |
| "tx1_queue attributes (from card): %X\n", |
| queueconf.len, (int) sizeof(queueconf), |
| queueconf.tx_memory_block_address, |
| queueconf.rx_memory_block_address, |
| queueconf.rx1_queue_address, |
| queueconf.tx1_queue_address, |
| queueconf.tx1_attributes); |
| |
| /* dump Acx111 Mem Map */ |
| printk("dump mem map:\n" |
| "data read: %d, struct size: %d\n" |
| "Code start: %X\n" |
| "Code end: %X\n" |
| "WEP default key start: %X\n" |
| "WEP default key end: %X\n" |
| "STA table start: %X\n" |
| "STA table end: %X\n" |
| "Packet template start: %X\n" |
| "Packet template end: %X\n" |
| "Queue memory start: %X\n" |
| "Queue memory end: %X\n" |
| "Packet memory pool start: %X\n" |
| "Packet memory pool end: %X\n" |
| "iobase: %p\n" |
| "iobase2: %p\n", |
| *((u16 *)&memmap[0x02]), (int) sizeof(memmap), |
| *((u32 *)&memmap[0x04]), |
| *((u32 *)&memmap[0x08]), |
| *((u32 *)&memmap[0x0C]), |
| *((u32 *)&memmap[0x10]), |
| *((u32 *)&memmap[0x14]), |
| *((u32 *)&memmap[0x18]), |
| *((u32 *)&memmap[0x1C]), |
| *((u32 *)&memmap[0x20]), |
| *((u32 *)&memmap[0x24]), |
| *((u32 *)&memmap[0x28]), |
| *((u32 *)&memmap[0x2C]), |
| *((u32 *)&memmap[0x30]), |
| adev->iobase, |
| adev->iobase2); |
| |
| /* dump Acx111 Rx Config */ |
| printk("dump rx config:\n" |
| "data read: %d, struct size: %d\n" |
| "rx config: %X\n" |
| "rx filter config: %X\n", |
| *((u16 *)&rxconfig[0x02]), (int) sizeof(rxconfig), |
| *((u16 *)&rxconfig[0x04]), |
| *((u16 *)&rxconfig[0x06])); |
| |
| /* dump Acx111 fcs error */ |
| printk("dump fcserror:\n" |
| "data read: %d, struct size: %d\n" |
| "fcserrors: %X\n", |
| *((u16 *)&fcserror[0x02]), (int) sizeof(fcserror), |
| *((u32 *)&fcserror[0x04])); |
| |
| /* dump Acx111 rate fallback */ |
| printk("dump rate fallback:\n" |
| "data read: %d, struct size: %d\n" |
| "ratefallback: %X\n", |
| *((u16 *)&ratefallback[0x02]), (int) sizeof(ratefallback), |
| *((u8 *)&ratefallback[0x04])); |
| |
| /* protect against IRQ */ |
| acx_lock(adev, flags); |
| |
| /* dump acx111 internal rx descriptor ring buffer */ |
| rxdesc = adev->rxdesc_start; |
| |
| /* loop over complete receive pool */ |
| if (rxdesc) for (i = 0; i < RX_CNT; i++) { |
| printk("\ndump internal rxdesc %d:\n" |
| "mem pos %p\n" |
| "next 0x%X\n" |
| "acx mem pointer (dynamic) 0x%X\n" |
| "CTL (dynamic) 0x%X\n" |
| "Rate (dynamic) 0x%X\n" |
| "RxStatus (dynamic) 0x%X\n" |
| "Mod/Pre (dynamic) 0x%X\n", |
| i, |
| rxdesc, |
| acx2cpu(rxdesc->pNextDesc), |
| acx2cpu(rxdesc->ACXMemPtr), |
| rxdesc->Ctl_8, |
| rxdesc->rate, |
| rxdesc->error, |
| rxdesc->SNR); |
| rxdesc++; |
| } |
| |
| /* dump host rx descriptor ring buffer */ |
| |
| rxhostdesc = adev->rxhostdesc_start; |
| |
| /* loop over complete receive pool */ |
| if (rxhostdesc) for (i = 0; i < RX_CNT; i++) { |
| printk("\ndump host rxdesc %d:\n" |
| "mem pos %p\n" |
| "buffer mem pos 0x%X\n" |
| "buffer mem offset 0x%X\n" |
| "CTL 0x%X\n" |
| "Length 0x%X\n" |
| "next 0x%X\n" |
| "Status 0x%X\n", |
| i, |
| rxhostdesc, |
| acx2cpu(rxhostdesc->data_phy), |
| rxhostdesc->data_offset, |
| le16_to_cpu(rxhostdesc->Ctl_16), |
| le16_to_cpu(rxhostdesc->length), |
| acx2cpu(rxhostdesc->desc_phy_next), |
| rxhostdesc->Status); |
| rxhostdesc++; |
| } |
| |
| /* dump acx111 internal tx descriptor ring buffer */ |
| txdesc = adev->txdesc_start; |
| |
| /* loop over complete transmit pool */ |
| if (txdesc) for (i = 0; i < TX_CNT; i++) { |
| printk("\ndump internal txdesc %d:\n" |
| "size 0x%X\n" |
| "mem pos %p\n" |
| "next 0x%X\n" |
| "acx mem pointer (dynamic) 0x%X\n" |
| "host mem pointer (dynamic) 0x%X\n" |
| "length (dynamic) 0x%X\n" |
| "CTL (dynamic) 0x%X\n" |
| "CTL2 (dynamic) 0x%X\n" |
| "Status (dynamic) 0x%X\n" |
| "Rate (dynamic) 0x%X\n", |
| i, |
| (int) sizeof(struct txdesc), |
| txdesc, |
| acx2cpu(txdesc->pNextDesc), |
| acx2cpu(txdesc->AcxMemPtr), |
| acx2cpu(txdesc->HostMemPtr), |
| le16_to_cpu(txdesc->total_length), |
| txdesc->Ctl_8, |
| txdesc->Ctl2_8, txdesc->error, |
| txdesc->u.r1.rate); |
| txdesc = advance_txdesc(adev, txdesc, 1); |
| } |
| |
| /* dump host tx descriptor ring buffer */ |
| |
| txhostdesc = adev->txhostdesc_start; |
| |
| /* loop over complete host send pool */ |
| if (txhostdesc) for (i = 0; i < TX_CNT * 2; i++) { |
| printk("\ndump host txdesc %d:\n" |
| "mem pos %p\n" |
| "buffer mem pos 0x%X\n" |
| "buffer mem offset 0x%X\n" |
| "CTL 0x%X\n" |
| "Length 0x%X\n" |
| "next 0x%X\n" |
| "Status 0x%X\n", |
| i, |
| txhostdesc, |
| acx2cpu(txhostdesc->data_phy), |
| txhostdesc->data_offset, |
| le16_to_cpu(txhostdesc->Ctl_16), |
| le16_to_cpu(txhostdesc->length), |
| acx2cpu(txhostdesc->desc_phy_next), |
| le32_to_cpu(txhostdesc->Status)); |
| txhostdesc++; |
| } |
| |
| /* write_reg16(adev, 0xb4, 0x4); */ |
| |
| acx_unlock(adev, flags); |
| end_ok: |
| |
| acx_sem_unlock(adev); |
| #endif /* ACX_DEBUG */ |
| return OK; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| int |
| acx100pci_ioctl_set_phy_amp_bias( |
| struct net_device *ndev, |
| struct iw_request_info *info, |
| struct iw_param *vwrq, |
| char *extra) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| u16 gpio_old; |
| |
| if (!IS_ACX100(adev)) { |
| /* WARNING!!! |
| * Removing this check *might* damage |
| * hardware, since we're tweaking GPIOs here after all!!! |
| * You've been warned... |
| * WARNING!!! */ |
| printk("acx: sorry, setting bias level for non-acx100 " |
| "is not supported yet\n"); |
| return OK; |
| } |
| |
| if (*extra > 7) { |
| printk("acx: invalid bias parameter, range is 0-7\n"); |
| return -EINVAL; |
| } |
| |
| acx_sem_lock(adev); |
| |
| /* Need to lock accesses to [IO_ACX_GPIO_OUT]: |
| * IRQ handler uses it to update LED */ |
| acx_lock(adev, flags); |
| gpio_old = read_reg16(adev, IO_ACX_GPIO_OUT); |
| write_reg16(adev, IO_ACX_GPIO_OUT, (gpio_old & 0xf8ff) | ((u16)*extra << 8)); |
| acx_unlock(adev, flags); |
| |
| log(L_DEBUG, "gpio_old: 0x%04X\n", gpio_old); |
| printk("%s: PHY power amplifier bias: old:%d, new:%d\n", |
| ndev->name, |
| (gpio_old & 0x0700) >> 8, (unsigned char)*extra); |
| |
| acx_sem_unlock(adev); |
| |
| return OK; |
| } |
| |
| |
| /*************************************************************** |
| ** acxpci_l_alloc_tx |
| ** Actually returns a txdesc_t* ptr |
| ** |
| ** FIXME: in case of fragments, should allocate multiple descrs |
| ** after figuring out how many we need and whether we still have |
| ** sufficiently many. |
| */ |
| static tx_t* |
| acxpci_l_alloc_tx(acx_device_t *adev) |
| { |
| struct txdesc *txdesc; |
| unsigned head; |
| u8 ctl8; |
| |
| FN_ENTER; |
| |
| if (unlikely(!adev->tx_free)) { |
| printk("acx: BUG: no free txdesc left\n"); |
| txdesc = NULL; |
| goto end; |
| } |
| |
| head = adev->tx_head; |
| txdesc = get_txdesc(adev, head); |
| ctl8 = txdesc->Ctl_8; |
| |
| /* 2005-10-11: there were several bug reports on this happening |
| ** but now cause seems to be understood & fixed */ |
| if (unlikely(DESC_CTL_HOSTOWN != (ctl8 & DESC_CTL_ACXDONE_HOSTOWN))) { |
| /* whoops, descr at current index is not free, so probably |
| * ring buffer already full */ |
| printk("acx: BUG: tx_head:%d Ctl8:0x%02X - failed to find " |
| "free txdesc\n", head, ctl8); |
| txdesc = NULL; |
| goto end; |
| } |
| |
| /* Needed in case txdesc won't be eventually submitted for tx */ |
| txdesc->Ctl_8 = DESC_CTL_ACXDONE_HOSTOWN; |
| |
| adev->tx_free--; |
| log(L_BUFT, "tx: got desc %u, %u remain\n", |
| head, adev->tx_free); |
| /* Keep a few free descs between head and tail of tx ring. |
| ** It is not absolutely needed, just feels safer */ |
| if (adev->tx_free < TX_STOP_QUEUE) { |
| log(L_BUF, "stop queue (%u tx desc left)\n", |
| adev->tx_free); |
| acx_stop_queue(adev->ndev, NULL); |
| } |
| |
| /* returning current descriptor, so advance to next free one */ |
| adev->tx_head = (head + 1) % TX_CNT; |
| end: |
| FN_EXIT0; |
| |
| return (tx_t*)txdesc; |
| } |
| |
| /* Dummy function. */ |
| static void |
| acxpci_l_dealloc_tx(tx_t *tx_opaque) |
| { |
| } |
| |
| /*********************************************************************** |
| */ |
| static void* |
| acxpci_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque) |
| { |
| return get_txhostdesc(adev, (txdesc_t*)tx_opaque)->data; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_l_tx_data |
| ** |
| ** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx). |
| ** Can be called from acx_i_start_xmit (data frames from net core). |
| ** |
| ** FIXME: in case of fragments, should loop over the number of |
| ** pre-allocated tx descrs, properly setting up transfer data and |
| ** CTL_xxx flags according to fragment number. |
| */ |
| static void |
| acxpci_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int len) |
| { |
| txdesc_t *txdesc = (txdesc_t*)tx_opaque; |
| txhostdesc_t *hostdesc1, *hostdesc2; |
| client_t *clt; |
| u16 rate_cur; |
| u8 Ctl_8, Ctl2_8; |
| |
| FN_ENTER; |
| |
| /* fw doesn't tx such packets anyhow */ |
| if (unlikely(len < WLAN_HDR_A3_LEN)) |
| goto end; |
| |
| hostdesc1 = get_txhostdesc(adev, txdesc); |
| /* modify flag status in separate variable to be able to write it back |
| * in one big swoop later (also in order to have less device memory |
| * accesses) */ |
| Ctl_8 = txdesc->Ctl_8; |
| Ctl2_8 = 0; /* really need to init it to 0, not txdesc->Ctl2_8, it seems */ |
| |
| hostdesc2 = hostdesc1 + 1; |
| |
| /* DON'T simply set Ctl field to 0 here globally, |
| * it needs to maintain a consistent flag status (those are state flags!!), |
| * otherwise it may lead to severe disruption. Only set or reset particular |
| * flags at the exact moment this is needed... */ |
| |
| /* let chip do RTS/CTS handshaking before sending |
| * in case packet size exceeds threshold */ |
| if (len > adev->rts_threshold) |
| SET_BIT(Ctl2_8, DESC_CTL2_RTS); |
| else |
| CLEAR_BIT(Ctl2_8, DESC_CTL2_RTS); |
| |
| switch (adev->mode) { |
| case ACX_MODE_0_ADHOC: |
| case ACX_MODE_3_AP: |
| clt = NULL; //sm? acx_l_sta_list_get(adev, ((struct ieee80211_hdr_3addr*)hostdesc1->data)->addr1); |
| break; |
| case ACX_MODE_2_STA: |
| clt = adev->ap_client; |
| break; |
| #if 0 |
| /* testing was done on acx111: */ |
| case ACX_MODE_MONITOR: |
| SET_BIT(Ctl2_8, 0 |
| /* sends CTS to self before packet */ |
| + DESC_CTL2_SEQ /* don't increase sequence field */ |
| /* not working (looks like good fcs is still added) */ |
| + DESC_CTL2_FCS /* don't add the FCS */ |
| /* not tested */ |
| + DESC_CTL2_MORE_FRAG |
| /* not tested */ |
| + DESC_CTL2_RETRY /* don't increase retry field */ |
| /* not tested */ |
| + DESC_CTL2_POWER /* don't increase power mgmt. field */ |
| /* no effect */ |
| + DESC_CTL2_WEP /* encrypt this frame */ |
| /* not tested */ |
| + DESC_CTL2_DUR /* don't increase duration field */ |
| ); |
| /* fallthrough */ |
| #endif |
| default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */ |
| clt = NULL; |
| break; |
| } |
| |
| rate_cur = clt ? clt->rate_cur : adev->rate_bcast; |
| if (unlikely(!rate_cur)) { |
| printk("acx: driver bug! bad ratemask\n"); |
| goto end; |
| } |
| |
| /* used in tx cleanup routine for auto rate and accounting: */ |
| put_txcr(adev, txdesc, clt, rate_cur); |
| |
| txdesc->total_length = cpu_to_le16(len); |
| hostdesc2->length = cpu_to_le16(len - WLAN_HDR_A3_LEN); |
| if (IS_ACX111(adev)) { |
| /* note that if !txdesc->do_auto, txrate->cur |
| ** has only one nonzero bit */ |
| txdesc->u.r2.rate111 = cpu_to_le16( |
| rate_cur |
| /* WARNING: I was never able to make it work with prism54 AP. |
| ** It was falling down to 1Mbit where shortpre is not applicable, |
| ** and not working at all at "5,11 basic rates only" setting. |
| ** I even didn't see tx packets in radio packet capture. |
| ** Disabled for now --vda */ |
| /*| ((clt->shortpre && clt->cur!=RATE111_1) ? RATE111_SHORTPRE : 0) */ |
| ); |
| #ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS |
| /* should add this to rate111 above as necessary */ |
| | (clt->pbcc511 ? RATE111_PBCC511 : 0) |
| #endif |
| hostdesc1->length = cpu_to_le16(len); |
| } else { /* ACX100 */ |
| u8 rate_100 = clt ? clt->rate_100 : adev->rate_bcast100; |
| txdesc->u.r1.rate = rate_100; |
| #ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS |
| if (clt->pbcc511) { |
| if (n == RATE100_5 || n == RATE100_11) |
| n |= RATE100_PBCC511; |
| } |
| |
| if (clt->shortpre && (clt->cur != RATE111_1)) |
| SET_BIT(Ctl_8, DESC_CTL_SHORT_PREAMBLE); /* set Short Preamble */ |
| #endif |
| /* set autodma and reclaim and 1st mpdu */ |
| SET_BIT(Ctl_8, DESC_CTL_AUTODMA | DESC_CTL_RECLAIM | DESC_CTL_FIRSTFRAG); |
| #if ACX_FRAGMENTATION |
| /* SET_BIT(Ctl2_8, DESC_CTL2_MORE_FRAG); cannot set it unconditionally, needs to be set for all non-last fragments */ |
| #endif |
| hostdesc1->length = cpu_to_le16(WLAN_HDR_A3_LEN); |
| } |
| /* don't need to clean ack/rts statistics here, already |
| * done on descr cleanup */ |
| |
| /* clears HOSTOWN and ACXDONE bits, thus telling that the descriptors |
| * are now owned by the acx100; do this as LAST operation */ |
| CLEAR_BIT(Ctl_8, DESC_CTL_ACXDONE_HOSTOWN); |
| /* flush writes before we release hostdesc to the adapter here */ |
| wmb(); |
| CLEAR_BIT(hostdesc1->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN)); |
| CLEAR_BIT(hostdesc2->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN)); |
| |
| /* write back modified flags */ |
| txdesc->Ctl2_8 = Ctl2_8; |
| txdesc->Ctl_8 = Ctl_8; |
| /* unused: txdesc->tx_time = cpu_to_le32(jiffies); */ |
| |
| /* flush writes before we tell the adapter that it's its turn now */ |
| mmiowb(); |
| write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC); |
| write_flush(adev); |
| |
| /* log the packet content AFTER sending it, |
| * in order to not delay sending any further than absolutely needed |
| * Do separate logs for acx100/111 to have human-readable rates */ |
| if (unlikely(acx_debug & (L_XFER|L_DATA))) { |
| u16 fc = ((struct ieee80211_hdr_3addr*)hostdesc1->data)->frame_ctl; |
| if (IS_ACX111(adev)) |
| printk("tx: pkt (%s): len %d " |
| "rate %04X%s status %u\n", |
| acx_get_packet_type_string(le16_to_cpu(fc)), len, |
| le16_to_cpu(txdesc->u.r2.rate111), |
| (le16_to_cpu(txdesc->u.r2.rate111) & RATE111_SHORTPRE) ? "(SPr)" : "", |
| adev->status); |
| else |
| printk("tx: pkt (%s): len %d rate %03u%s status %u\n", |
| acx_get_packet_type_string(fc), len, |
| txdesc->u.r1.rate, |
| (Ctl_8 & DESC_CTL_SHORT_PREAMBLE) ? "(SPr)" : "", |
| adev->status); |
| |
| if (acx_debug & L_DATA) { |
| printk("tx: 802.11 [%d]: ", len); |
| acx_dump_bytes(hostdesc1->data, len); |
| } |
| } |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxpci_l_clean_txdesc |
| ** |
| ** This function resets the txdescs' status when the ACX100 |
| ** signals the TX done IRQ (txdescs have been processed), starting with |
| ** the pool index of the descriptor which we would use next, |
| ** in order to make sure that we can be as fast as possible |
| ** in filling new txdescs. |
|