/* * Copyright (c) 2010 Broadcom Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #ifdef mips #include #endif /* mips */ #include #include #include #include #include #include #define PCI_CFG_RETRY 10 #define OS_HANDLE_MAGIC 0x1234abcd /* Magic # to recognise osh */ #define BCM_MEM_FILENAME_LEN 24 /* Mem. filename length */ struct osl_info { osl_pubinfo_t pub; uint magic; void *pdev; uint failed; uint bustype; }; /* Global ASSERT type flag */ u32 g_assert_type; #ifdef BRCM_FULLMAC static s16 linuxbcmerrormap[] = { 0, /* 0 */ -EINVAL, /* BCME_ERROR */ -EINVAL, /* BCME_BADARG */ -EINVAL, /* BCME_BADOPTION */ -EINVAL, /* BCME_NOTUP */ -EINVAL, /* BCME_NOTDOWN */ -EINVAL, /* BCME_NOTAP */ -EINVAL, /* BCME_NOTSTA */ -EINVAL, /* BCME_BADKEYIDX */ -EINVAL, /* BCME_RADIOOFF */ -EINVAL, /* BCME_NOTBANDLOCKED */ -EINVAL, /* BCME_NOCLK */ -EINVAL, /* BCME_BADRATESET */ -EINVAL, /* BCME_BADBAND */ -E2BIG, /* BCME_BUFTOOSHORT */ -E2BIG, /* BCME_BUFTOOLONG */ -EBUSY, /* BCME_BUSY */ -EINVAL, /* BCME_NOTASSOCIATED */ -EINVAL, /* BCME_BADSSIDLEN */ -EINVAL, /* BCME_OUTOFRANGECHAN */ -EINVAL, /* BCME_BADCHAN */ -EFAULT, /* BCME_BADADDR */ -ENOMEM, /* BCME_NORESOURCE */ -EOPNOTSUPP, /* BCME_UNSUPPORTED */ -EMSGSIZE, /* BCME_BADLENGTH */ -EINVAL, /* BCME_NOTREADY */ -EPERM, /* BCME_NOTPERMITTED */ -ENOMEM, /* BCME_NOMEM */ -EINVAL, /* BCME_ASSOCIATED */ -ERANGE, /* BCME_RANGE */ -EINVAL, /* BCME_NOTFOUND */ -EINVAL, /* BCME_WME_NOT_ENABLED */ -EINVAL, /* BCME_TSPEC_NOTFOUND */ -EINVAL, /* BCME_ACM_NOTSUPPORTED */ -EINVAL, /* BCME_NOT_WME_ASSOCIATION */ -EIO, /* BCME_SDIO_ERROR */ -ENODEV, /* BCME_DONGLE_DOWN */ -EINVAL, /* BCME_VERSION */ -EIO, /* BCME_TXFAIL */ -EIO, /* BCME_RXFAIL */ -EINVAL, /* BCME_NODEVICE */ -EINVAL, /* BCME_NMODE_DISABLED */ -ENODATA, /* BCME_NONRESIDENT */ /* When an new error code is added to bcmutils.h, add os * spcecific error translation here as well */ /* check if BCME_LAST changed since the last time this function was updated */ #if BCME_LAST != -42 #error "You need to add a OS error translation in the linuxbcmerrormap \ for new error code defined in bcmutils.h" #endif }; /* translate bcmerrors into linux errors */ int osl_error(int bcmerror) { if (bcmerror > 0) bcmerror = 0; else if (bcmerror < BCME_LAST) bcmerror = BCME_ERROR; /* Array bounds covered by ASSERT in osl_attach */ return linuxbcmerrormap[-bcmerror]; } #endif /* BRCM_FULLMAC */ osl_t *osl_attach(void *pdev, uint bustype, bool pkttag) { osl_t *osh; osh = kmalloc(sizeof(osl_t), GFP_ATOMIC); ASSERT(osh); bzero(osh, sizeof(osl_t)); #ifdef BRCM_FULLMAC /* Check that error map has the right number of entries in it */ ASSERT(ABS(BCME_LAST) == (ARRAY_SIZE(linuxbcmerrormap) - 1)); #endif /* BRCM_FULLMAC */ osh->magic = OS_HANDLE_MAGIC; osh->failed = 0; osh->pdev = pdev; osh->pub.pkttag = pkttag; osh->bustype = bustype; switch (bustype) { case PCI_BUS: case SI_BUS: case PCMCIA_BUS: osh->pub.mmbus = true; break; case JTAG_BUS: case SDIO_BUS: case USB_BUS: case SPI_BUS: case RPC_BUS: osh->pub.mmbus = false; break; default: ASSERT(false); break; } #if defined(BCMDBG) && !defined(BRCM_FULLMAC) if (pkttag) { struct sk_buff *skb; ASSERT(OSL_PKTTAG_SZ <= sizeof(skb->cb)); } #endif return osh; } void osl_detach(osl_t *osh) { if (osh == NULL) return; ASSERT(osh->magic == OS_HANDLE_MAGIC); kfree(osh); } /* Return a new packet. zero out pkttag */ void *BCMFASTPATH osl_pktget(osl_t *osh, uint len) { struct sk_buff *skb; skb = dev_alloc_skb(len); if (skb) { skb_put(skb, len); skb->priority = 0; osh->pub.pktalloced++; } return (void *)skb; } /* Free the driver packet. Free the tag if present */ void BCMFASTPATH osl_pktfree(osl_t *osh, void *p, bool send) { struct sk_buff *skb, *nskb; int nest = 0; skb = (struct sk_buff *)p; ASSERT(skb); if (send && osh->pub.tx_fn) osh->pub.tx_fn(osh->pub.tx_ctx, p, 0); /* perversion: we use skb->next to chain multi-skb packets */ while (skb) { nskb = skb->next; skb->next = NULL; if (skb->destructor) /* cannot kfree_skb() on hard IRQ (net/core/skbuff.c) if * destructor exists */ dev_kfree_skb_any(skb); else /* can free immediately (even in_irq()) if destructor * does not exist */ dev_kfree_skb(skb); osh->pub.pktalloced--; nest++; skb = nskb; } } u32 osl_pci_read_config(osl_t *osh, uint offset, uint size) { uint val = 0; uint retry = PCI_CFG_RETRY; ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); /* only 4byte access supported */ ASSERT(size == 4); do { pci_read_config_dword(osh->pdev, offset, &val); if (val != 0xffffffff) break; } while (retry--); #ifdef BCMDBG if (retry < PCI_CFG_RETRY) printk("PCI CONFIG READ access to %d required %d retries\n", offset, (PCI_CFG_RETRY - retry)); #endif /* BCMDBG */ return val; } void osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val) { uint retry = PCI_CFG_RETRY; ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); /* only 4byte access supported */ ASSERT(size == 4); do { pci_write_config_dword(osh->pdev, offset, val); if (offset != PCI_BAR0_WIN) break; if (osl_pci_read_config(osh, offset, size) == val) break; } while (retry--); #if defined(BCMDBG) && !defined(BRCM_FULLMAC) if (retry < PCI_CFG_RETRY) printk("PCI CONFIG WRITE access to %d required %d retries\n", offset, (PCI_CFG_RETRY - retry)); #endif /* BCMDBG */ } /* return bus # for the pci device pointed by osh->pdev */ uint osl_pci_bus(osl_t *osh) { ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); return ((struct pci_dev *)osh->pdev)->bus->number; } /* return slot # for the pci device pointed by osh->pdev */ uint osl_pci_slot(osl_t *osh) { ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); return PCI_SLOT(((struct pci_dev *)osh->pdev)->devfn); } uint osl_dma_consistent_align(void) { return PAGE_SIZE; } void *osl_dma_alloc_consistent(osl_t *osh, uint size, u16 align_bits, uint *alloced, unsigned long *pap) { ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); if (align_bits) { u16 align = (1 << align_bits); if (!IS_ALIGNED(DMA_CONSISTENT_ALIGN, align)) size += align; *alloced = size; } return pci_alloc_consistent(osh->pdev, size, (dma_addr_t *) pap); } void osl_dma_free_consistent(osl_t *osh, void *va, uint size, unsigned long pa) { ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); pci_free_consistent(osh->pdev, size, va, (dma_addr_t) pa); } uint BCMFASTPATH osl_dma_map(osl_t *osh, void *va, uint size, int direction) { int dir; ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE; return pci_map_single(osh->pdev, va, size, dir); } void BCMFASTPATH osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction) { int dir; ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE; pci_unmap_single(osh->pdev, (u32) pa, size, dir); } #if defined(BCMDBG_ASSERT) void osl_assert(char *exp, char *file, int line) { char tempbuf[256]; char *basename; basename = strrchr(file, '/'); /* skip the '/' */ if (basename) basename++; if (!basename) basename = file; #ifdef BCMDBG_ASSERT snprintf(tempbuf, 256, "assertion \"%s\" failed: file \"%s\", line %d\n", exp, basename, line); /* Print assert message and give it time to be written to /var/log/messages */ if (!in_interrupt()) { const int delay = 3; printk(KERN_ERR "%s", tempbuf); printk(KERN_ERR "panic in %d seconds\n", delay); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(delay * HZ); } switch (g_assert_type) { case 0: panic(KERN_ERR "%s", tempbuf); break; case 1: printk(KERN_ERR "%s", tempbuf); BUG(); break; case 2: printk(KERN_ERR "%s", tempbuf); break; default: break; } #endif /* BCMDBG_ASSERT */ } #endif /* defined(BCMDBG_ASSERT) */ #if defined(BCMSDIO) && !defined(BRCM_FULLMAC) u8 osl_readb(osl_t *osh, volatile u8 *r) { osl_rreg_fn_t rreg = ((osl_pubinfo_t *) osh)->rreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; return (u8) ((rreg) (ctx, (void *)r, sizeof(u8))); } u16 osl_readw(osl_t *osh, volatile u16 *r) { osl_rreg_fn_t rreg = ((osl_pubinfo_t *) osh)->rreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; return (u16) ((rreg) (ctx, (void *)r, sizeof(u16))); } u32 osl_readl(osl_t *osh, volatile u32 *r) { osl_rreg_fn_t rreg = ((osl_pubinfo_t *) osh)->rreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; return (u32) ((rreg) (ctx, (void *)r, sizeof(u32))); } void osl_writeb(osl_t *osh, volatile u8 *r, u8 v) { osl_wreg_fn_t wreg = ((osl_pubinfo_t *) osh)->wreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; ((wreg) (ctx, (void *)r, v, sizeof(u8))); } void osl_writew(osl_t *osh, volatile u16 *r, u16 v) { osl_wreg_fn_t wreg = ((osl_pubinfo_t *) osh)->wreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; ((wreg) (ctx, (void *)r, v, sizeof(u16))); } void osl_writel(osl_t *osh, volatile u32 *r, u32 v) { osl_wreg_fn_t wreg = ((osl_pubinfo_t *) osh)->wreg_fn; void *ctx = ((osl_pubinfo_t *) osh)->reg_ctx; ((wreg) (ctx, (void *)r, v, sizeof(u32))); } #endif /* BCMSDIO */