/* * carl9170 firmware - used by the ar9170 wireless device * * DMA descriptor handling functions * * Copyright (c) 2000-2005 ZyDAS Technology Corporation * Copyright (c) 2007-2009 Atheros Communications, Inc. * Copyright 2009 Johannes Berg * Copyright 2009-2011 Christian Lamparter * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "carl9170.h" #include "wl.h" #include "printf.h" struct ar9170_dma_memory dma_mem __section(sram); static void copy_dma_desc(struct dma_desc *dst, struct dma_desc *src) { memcpy(dst, src, sizeof(struct dma_desc)); } static void clear_descriptor(struct dma_desc *d) { d->status = AR9170_OWN_BITS_SW; d->ctrl = 0; d->dataSize = 0; d->totalLen = 0; d->lastAddr = d; d->dataAddr = NULL; d->nextAddr = d; } static void fill_descriptor(struct dma_desc *d, uint16_t size, uint8_t *data) { d->status = AR9170_OWN_BITS_SW; d->ctrl = 0; d->dataSize = size; d->totalLen = 0; d->lastAddr = d; d->dataAddr = data; d->nextAddr = NULL; } static void init_queue(struct dma_queue *q, struct dma_desc *d) { q->head = q->terminator = d; } /* * - Init up_queue, down_queue, tx_queue[5], rx_queue. * - Setup descriptors and data buffer address. * - Ring descriptors rx_queue and down_queue by dma_reclaim(). * * NOTE: LastAddr tempary point (same) to nextAddr after initialize. * Because LastAddr is don't care in function dma_reclaim(). */ void dma_init_descriptors(void) { unsigned int i, j; for (i = 0; i < ARRAY_SIZE(dma_mem.terminator); i++) clear_descriptor(&dma_mem.terminator[i]); /* Assign terminators to DMA queues */ i = 0; init_queue(&fw.pta.up_queue, &dma_mem.terminator[i++]); init_queue(&fw.pta.down_queue, &dma_mem.terminator[i++]); for (j = 0; j < __AR9170_NUM_TX_QUEUES; j++) init_queue(&fw.wlan.tx_queue[j], &dma_mem.terminator[i++]); init_queue(&fw.wlan.tx_retry, &dma_mem.terminator[i++]); init_queue(&fw.wlan.rx_queue, &dma_mem.terminator[i++]); fw.usb.int_desc = &dma_mem.terminator[i++]; fw.wlan.fw_desc = &dma_mem.terminator[i++]; #ifdef CONFIG_CARL9170FW_CAB_QUEUE for (j = 0; j < CARL9170_INTF_NUM; j++) init_queue(&fw.wlan.cab_queue[j], &dma_mem.terminator[i++]); #endif /* CONFIG_CARL9170FW_CAB_QUEUE */ BUG_ON(AR9170_TERMINATOR_NUMBER != i); DBG("Blocks:%d [tx:%d, rx:%d] Terminators:%d/%d\n", AR9170_BLOCK_NUMBER, AR9170_TX_BLOCK_NUMBER, AR9170_RX_BLOCK_NUMBER, AR9170_TERMINATOR_NUMBER, i); /* Init descriptors and memory blocks */ for (i = 0; i < AR9170_BLOCK_NUMBER; i++) { fill_descriptor(&dma_mem.block[i], AR9170_BLOCK_SIZE, dma_mem.data[i].data); if (i < AR9170_TX_BLOCK_NUMBER) dma_reclaim(&fw.pta.down_queue, &dma_mem.block[i]); else dma_reclaim(&fw.wlan.rx_queue, &dma_mem.block[i]); } /* Set DMA address registers */ set(AR9170_PTA_REG_DN_DMA_ADDRH, (uint32_t) fw.pta.down_queue.head >> 16); set(AR9170_PTA_REG_DN_DMA_ADDRL, (uint32_t) fw.pta.down_queue.head & 0xffff); set(AR9170_PTA_REG_UP_DMA_ADDRH, (uint32_t) fw.pta.up_queue.head >> 16); set(AR9170_PTA_REG_UP_DMA_ADDRL, (uint32_t) fw.pta.up_queue.head & 0xffff); for (i = 0; i < __AR9170_NUM_TX_QUEUES; i++) set_wlan_txq_dma_addr(i, (uint32_t) fw.wlan.tx_queue[i].head); set(AR9170_MAC_REG_DMA_RXQ_ADDR, (uint32_t) fw.wlan.rx_queue.head); fw.usb.int_desc->dataSize = AR9170_BLOCK_SIZE; fw.usb.int_desc->dataAddr = (void *) &dma_mem.reserved.rsp; memset(DESC_PAYLOAD(fw.usb.int_desc), 0xff, AR9170_INT_MAGIC_HEADER_SIZE); memset(DESC_PAYLOAD_OFF(fw.usb.int_desc, AR9170_INT_MAGIC_HEADER_SIZE), 0, AR9170_BLOCK_SIZE - AR9170_INT_MAGIC_HEADER_SIZE); /* rsp is now available for use */ fw.usb.int_desc_available = 1; memset(DESC_PAYLOAD(fw.wlan.fw_desc), 0, 128); fw.wlan.fw_desc_available = 1; } /* * Free descriptor. * * Exchange the terminator and the first descriptor of the packet * for hardware ascy... */ void dma_reclaim(struct dma_queue *q, struct dma_desc *desc) { struct dma_desc *tmpDesc, *last; struct dma_desc tdesc; /* 1. Set OWN bit to HW for all TDs to be added, clear ctrl and size */ tmpDesc = desc; last = desc->lastAddr; while (1) { tmpDesc->status = AR9170_OWN_BITS_HW; tmpDesc->ctrl = 0; tmpDesc->totalLen = 0; tmpDesc->dataSize = AR9170_BLOCK_SIZE; /* TODO : Exception handle */ tmpDesc->lastAddr = tmpDesc; if (tmpDesc == last) break; tmpDesc = tmpDesc->nextAddr; } /* 2. Next address of Last TD to be added = first TD */ tmpDesc->nextAddr = desc; /* Link first TD to self */ desc->lastAddr = q->terminator; /* 3. Copy first TD to be added to TTD */ copy_dma_desc(&tdesc, desc); /* 4. Initialize new terminator */ clear_descriptor(desc); /* 5. Copy TTD to last TD */ tdesc.status = 0; copy_dma_desc((void *)q->terminator, (void *)&tdesc); q->terminator->status |= AR9170_OWN_BITS_HW; /* Update terminator pointer */ q->terminator = desc; } /* * Put a complete packet into the tail of the Queue q. * Exchange the terminator and the first descriptor of the packet * for hardware ascy... */ void dma_put(struct dma_queue *q, struct dma_desc *desc) { struct dma_desc *tmpDesc; struct dma_desc tdesc; tmpDesc = desc; while (1) { /* update totalLen */ tmpDesc->totalLen = desc->totalLen; /* 1. Set OWN bit to HW for all TDs to be added */ tmpDesc->status = AR9170_OWN_BITS_HW; /* TODO : Exception handle */ tmpDesc->lastAddr = desc->lastAddr; if (desc->lastAddr == tmpDesc) break; tmpDesc = tmpDesc->nextAddr; } /* 2. Next address of Last TD to be added = first TD */ desc->lastAddr->nextAddr = desc; /* If there is only one descriptor, update pointer of last descriptor */ if (desc->lastAddr == desc) desc->lastAddr = q->terminator; /* 3. Copy first TD to be added to TTD */ copy_dma_desc(&tdesc, desc); /* 4. Initialize new terminator */ clear_descriptor(desc); /* 5. Copy TTD to last TD */ tdesc.status &= (~AR9170_OWN_BITS); copy_dma_desc((void *)q->terminator, (void *)&tdesc); q->terminator->status |= AR9170_OWN_BITS_HW; /* Update terminator pointer */ q->terminator = desc; } struct dma_desc *dma_unlink_head(struct dma_queue *queue) { struct dma_desc *desc; if (queue_empty(queue)) return NULL; desc = queue->head; queue->head = desc->lastAddr->nextAddr; /* poison nextAddr address */ desc->lastAddr->nextAddr = desc->lastAddr; desc->lastAddr->lastAddr = desc->lastAddr; return desc; }