forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
729 lines
21 KiB
729 lines
21 KiB
/* |
|
* Copyright (c) 2004-2008 Reyk Floeter <[email protected]> |
|
* Copyright (c) 2006-2008 Nick Kossifidis <[email protected]> |
|
* |
|
* Permission to use, copy, modify, and 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. |
|
* |
|
*/ |
|
|
|
/********************************************\ |
|
Queue Control Unit, DCF Control Unit Functions |
|
\********************************************/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include "ath5k.h" |
|
#include "reg.h" |
|
#include "debug.h" |
|
#include <linux/log2.h> |
|
|
|
/** |
|
* DOC: Queue Control Unit (QCU)/DCF Control Unit (DCU) functions |
|
* |
|
* Here we setup parameters for the 12 available TX queues. Note that |
|
* on the various registers we can usually only map the first 10 of them so |
|
* basically we have 10 queues to play with. Each queue has a matching |
|
* QCU that controls when the queue will get triggered and multiple QCUs |
|
* can be mapped to a single DCU that controls the various DFS parameters |
|
* for the various queues. In our setup we have a 1:1 mapping between QCUs |
|
* and DCUs allowing us to have different DFS settings for each queue. |
|
* |
|
* When a frame goes into a TX queue, QCU decides when it'll trigger a |
|
* transmission based on various criteria (such as how many data we have inside |
|
* it's buffer or -if it's a beacon queue- if it's time to fire up the queue |
|
* based on TSF etc), DCU adds backoff, IFSes etc and then a scheduler |
|
* (arbitrator) decides the priority of each QCU based on it's configuration |
|
* (e.g. beacons are always transmitted when they leave DCU bypassing all other |
|
* frames from other queues waiting to be transmitted). After a frame leaves |
|
* the DCU it goes to PCU for further processing and then to PHY for |
|
* the actual transmission. |
|
*/ |
|
|
|
|
|
/******************\ |
|
* Helper functions * |
|
\******************/ |
|
|
|
/** |
|
* ath5k_hw_num_tx_pending() - Get number of pending frames for a given queue |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
*/ |
|
u32 |
|
ath5k_hw_num_tx_pending(struct ath5k_hw *ah, unsigned int queue) |
|
{ |
|
u32 pending; |
|
AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); |
|
|
|
/* Return if queue is declared inactive */ |
|
if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE) |
|
return false; |
|
|
|
/* XXX: How about AR5K_CFG_TXCNT ? */ |
|
if (ah->ah_version == AR5K_AR5210) |
|
return false; |
|
|
|
pending = ath5k_hw_reg_read(ah, AR5K_QUEUE_STATUS(queue)); |
|
pending &= AR5K_QCU_STS_FRMPENDCNT; |
|
|
|
/* It's possible to have no frames pending even if TXE |
|
* is set. To indicate that q has not stopped return |
|
* true */ |
|
if (!pending && AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue)) |
|
return true; |
|
|
|
return pending; |
|
} |
|
|
|
/** |
|
* ath5k_hw_release_tx_queue() - Set a transmit queue inactive |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
*/ |
|
void |
|
ath5k_hw_release_tx_queue(struct ath5k_hw *ah, unsigned int queue) |
|
{ |
|
if (WARN_ON(queue >= ah->ah_capabilities.cap_queues.q_tx_num)) |
|
return; |
|
|
|
/* This queue will be skipped in further operations */ |
|
ah->ah_txq[queue].tqi_type = AR5K_TX_QUEUE_INACTIVE; |
|
/*For SIMR setup*/ |
|
AR5K_Q_DISABLE_BITS(ah->ah_txq_status, queue); |
|
} |
|
|
|
/** |
|
* ath5k_cw_validate() - Make sure the given cw is valid |
|
* @cw_req: The contention window value to check |
|
* |
|
* Make sure cw is a power of 2 minus 1 and smaller than 1024 |
|
*/ |
|
static u16 |
|
ath5k_cw_validate(u16 cw_req) |
|
{ |
|
cw_req = min(cw_req, (u16)1023); |
|
|
|
/* Check if cw_req + 1 a power of 2 */ |
|
if (is_power_of_2(cw_req + 1)) |
|
return cw_req; |
|
|
|
/* Check if cw_req is a power of 2 */ |
|
if (is_power_of_2(cw_req)) |
|
return cw_req - 1; |
|
|
|
/* If none of the above is correct |
|
* find the closest power of 2 */ |
|
cw_req = (u16) roundup_pow_of_two(cw_req) - 1; |
|
|
|
return cw_req; |
|
} |
|
|
|
/** |
|
* ath5k_hw_get_tx_queueprops() - Get properties for a transmit queue |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
* @queue_info: The &struct ath5k_txq_info to fill |
|
*/ |
|
int |
|
ath5k_hw_get_tx_queueprops(struct ath5k_hw *ah, int queue, |
|
struct ath5k_txq_info *queue_info) |
|
{ |
|
memcpy(queue_info, &ah->ah_txq[queue], sizeof(struct ath5k_txq_info)); |
|
return 0; |
|
} |
|
|
|
/** |
|
* ath5k_hw_set_tx_queueprops() - Set properties for a transmit queue |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
* @qinfo: The &struct ath5k_txq_info to use |
|
* |
|
* Returns 0 on success or -EIO if queue is inactive |
|
*/ |
|
int |
|
ath5k_hw_set_tx_queueprops(struct ath5k_hw *ah, int queue, |
|
const struct ath5k_txq_info *qinfo) |
|
{ |
|
struct ath5k_txq_info *qi; |
|
|
|
AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); |
|
|
|
qi = &ah->ah_txq[queue]; |
|
|
|
if (qi->tqi_type == AR5K_TX_QUEUE_INACTIVE) |
|
return -EIO; |
|
|
|
/* copy and validate values */ |
|
qi->tqi_type = qinfo->tqi_type; |
|
qi->tqi_subtype = qinfo->tqi_subtype; |
|
qi->tqi_flags = qinfo->tqi_flags; |
|
/* |
|
* According to the docs: Although the AIFS field is 8 bit wide, |
|
* the maximum supported value is 0xFC. Setting it higher than that |
|
* will cause the DCU to hang. |
|
*/ |
|
qi->tqi_aifs = min(qinfo->tqi_aifs, (u8)0xFC); |
|
qi->tqi_cw_min = ath5k_cw_validate(qinfo->tqi_cw_min); |
|
qi->tqi_cw_max = ath5k_cw_validate(qinfo->tqi_cw_max); |
|
qi->tqi_cbr_period = qinfo->tqi_cbr_period; |
|
qi->tqi_cbr_overflow_limit = qinfo->tqi_cbr_overflow_limit; |
|
qi->tqi_burst_time = qinfo->tqi_burst_time; |
|
qi->tqi_ready_time = qinfo->tqi_ready_time; |
|
|
|
/*XXX: Is this supported on 5210 ?*/ |
|
/*XXX: Is this correct for AR5K_WME_AC_VI,VO ???*/ |
|
if ((qinfo->tqi_type == AR5K_TX_QUEUE_DATA && |
|
((qinfo->tqi_subtype == AR5K_WME_AC_VI) || |
|
(qinfo->tqi_subtype == AR5K_WME_AC_VO))) || |
|
qinfo->tqi_type == AR5K_TX_QUEUE_UAPSD) |
|
qi->tqi_flags |= AR5K_TXQ_FLAG_POST_FR_BKOFF_DIS; |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* ath5k_hw_setup_tx_queue() - Initialize a transmit queue |
|
* @ah: The &struct ath5k_hw |
|
* @queue_type: One of enum ath5k_tx_queue |
|
* @queue_info: The &struct ath5k_txq_info to use |
|
* |
|
* Returns 0 on success, -EINVAL on invalid arguments |
|
*/ |
|
int |
|
ath5k_hw_setup_tx_queue(struct ath5k_hw *ah, enum ath5k_tx_queue queue_type, |
|
struct ath5k_txq_info *queue_info) |
|
{ |
|
unsigned int queue; |
|
int ret; |
|
|
|
/* |
|
* Get queue by type |
|
*/ |
|
/* 5210 only has 2 queues */ |
|
if (ah->ah_capabilities.cap_queues.q_tx_num == 2) { |
|
switch (queue_type) { |
|
case AR5K_TX_QUEUE_DATA: |
|
queue = AR5K_TX_QUEUE_ID_NOQCU_DATA; |
|
break; |
|
case AR5K_TX_QUEUE_BEACON: |
|
case AR5K_TX_QUEUE_CAB: |
|
queue = AR5K_TX_QUEUE_ID_NOQCU_BEACON; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
} else { |
|
switch (queue_type) { |
|
case AR5K_TX_QUEUE_DATA: |
|
queue = queue_info->tqi_subtype; |
|
break; |
|
case AR5K_TX_QUEUE_UAPSD: |
|
queue = AR5K_TX_QUEUE_ID_UAPSD; |
|
break; |
|
case AR5K_TX_QUEUE_BEACON: |
|
queue = AR5K_TX_QUEUE_ID_BEACON; |
|
break; |
|
case AR5K_TX_QUEUE_CAB: |
|
queue = AR5K_TX_QUEUE_ID_CAB; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
/* |
|
* Setup internal queue structure |
|
*/ |
|
memset(&ah->ah_txq[queue], 0, sizeof(struct ath5k_txq_info)); |
|
ah->ah_txq[queue].tqi_type = queue_type; |
|
|
|
if (queue_info != NULL) { |
|
queue_info->tqi_type = queue_type; |
|
ret = ath5k_hw_set_tx_queueprops(ah, queue, queue_info); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
/* |
|
* We use ah_txq_status to hold a temp value for |
|
* the Secondary interrupt mask registers on 5211+ |
|
* check out ath5k_hw_reset_tx_queue |
|
*/ |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_status, queue); |
|
|
|
return queue; |
|
} |
|
|
|
|
|
/*******************************\ |
|
* Single QCU/DCU initialization * |
|
\*******************************/ |
|
|
|
/** |
|
* ath5k_hw_set_tx_retry_limits() - Set tx retry limits on DCU |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
* |
|
* This function is used when initializing a queue, to set |
|
* retry limits based on ah->ah_retry_* and the chipset used. |
|
*/ |
|
void |
|
ath5k_hw_set_tx_retry_limits(struct ath5k_hw *ah, |
|
unsigned int queue) |
|
{ |
|
/* Single data queue on AR5210 */ |
|
if (ah->ah_version == AR5K_AR5210) { |
|
struct ath5k_txq_info *tq = &ah->ah_txq[queue]; |
|
|
|
if (queue > 0) |
|
return; |
|
|
|
ath5k_hw_reg_write(ah, |
|
(tq->tqi_cw_min << AR5K_NODCU_RETRY_LMT_CW_MIN_S) |
|
| AR5K_REG_SM(ah->ah_retry_long, |
|
AR5K_NODCU_RETRY_LMT_SLG_RETRY) |
|
| AR5K_REG_SM(ah->ah_retry_short, |
|
AR5K_NODCU_RETRY_LMT_SSH_RETRY) |
|
| AR5K_REG_SM(ah->ah_retry_long, |
|
AR5K_NODCU_RETRY_LMT_LG_RETRY) |
|
| AR5K_REG_SM(ah->ah_retry_short, |
|
AR5K_NODCU_RETRY_LMT_SH_RETRY), |
|
AR5K_NODCU_RETRY_LMT); |
|
/* DCU on AR5211+ */ |
|
} else { |
|
ath5k_hw_reg_write(ah, |
|
AR5K_REG_SM(ah->ah_retry_long, |
|
AR5K_DCU_RETRY_LMT_RTS) |
|
| AR5K_REG_SM(ah->ah_retry_long, |
|
AR5K_DCU_RETRY_LMT_STA_RTS) |
|
| AR5K_REG_SM(max(ah->ah_retry_long, ah->ah_retry_short), |
|
AR5K_DCU_RETRY_LMT_STA_DATA), |
|
AR5K_QUEUE_DFS_RETRY_LIMIT(queue)); |
|
} |
|
} |
|
|
|
/** |
|
* ath5k_hw_reset_tx_queue() - Initialize a single hw queue |
|
* @ah: The &struct ath5k_hw |
|
* @queue: One of enum ath5k_tx_queue_id |
|
* |
|
* Set DCF properties for the given transmit queue on DCU |
|
* and configures all queue-specific parameters. |
|
*/ |
|
int |
|
ath5k_hw_reset_tx_queue(struct ath5k_hw *ah, unsigned int queue) |
|
{ |
|
struct ath5k_txq_info *tq = &ah->ah_txq[queue]; |
|
|
|
AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); |
|
|
|
/* Skip if queue inactive or if we are on AR5210 |
|
* that doesn't have QCU/DCU */ |
|
if ((ah->ah_version == AR5K_AR5210) || |
|
(tq->tqi_type == AR5K_TX_QUEUE_INACTIVE)) |
|
return 0; |
|
|
|
/* |
|
* Set contention window (cw_min/cw_max) |
|
* and arbitrated interframe space (aifs)... |
|
*/ |
|
ath5k_hw_reg_write(ah, |
|
AR5K_REG_SM(tq->tqi_cw_min, AR5K_DCU_LCL_IFS_CW_MIN) | |
|
AR5K_REG_SM(tq->tqi_cw_max, AR5K_DCU_LCL_IFS_CW_MAX) | |
|
AR5K_REG_SM(tq->tqi_aifs, AR5K_DCU_LCL_IFS_AIFS), |
|
AR5K_QUEUE_DFS_LOCAL_IFS(queue)); |
|
|
|
/* |
|
* Set tx retry limits for this queue |
|
*/ |
|
ath5k_hw_set_tx_retry_limits(ah, queue); |
|
|
|
|
|
/* |
|
* Set misc registers |
|
*/ |
|
|
|
/* Enable DCU to wait for next fragment from QCU */ |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), |
|
AR5K_DCU_MISC_FRAG_WAIT); |
|
|
|
/* On Maui and Spirit use the global seqnum on DCU */ |
|
if (ah->ah_mac_version < AR5K_SREV_AR5211) |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), |
|
AR5K_DCU_MISC_SEQNUM_CTL); |
|
|
|
/* Constant bit rate period */ |
|
if (tq->tqi_cbr_period) { |
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_cbr_period, |
|
AR5K_QCU_CBRCFG_INTVAL) | |
|
AR5K_REG_SM(tq->tqi_cbr_overflow_limit, |
|
AR5K_QCU_CBRCFG_ORN_THRES), |
|
AR5K_QUEUE_CBRCFG(queue)); |
|
|
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_FRSHED_CBR); |
|
|
|
if (tq->tqi_cbr_overflow_limit) |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_CBR_THRES_ENABLE); |
|
} |
|
|
|
/* Ready time interval */ |
|
if (tq->tqi_ready_time && (tq->tqi_type != AR5K_TX_QUEUE_CAB)) |
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_ready_time, |
|
AR5K_QCU_RDYTIMECFG_INTVAL) | |
|
AR5K_QCU_RDYTIMECFG_ENABLE, |
|
AR5K_QUEUE_RDYTIMECFG(queue)); |
|
|
|
if (tq->tqi_burst_time) { |
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_burst_time, |
|
AR5K_DCU_CHAN_TIME_DUR) | |
|
AR5K_DCU_CHAN_TIME_ENABLE, |
|
AR5K_QUEUE_DFS_CHANNEL_TIME(queue)); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE) |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_RDY_VEOL_POLICY); |
|
} |
|
|
|
/* Enable/disable Post frame backoff */ |
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_BACKOFF_DISABLE) |
|
ath5k_hw_reg_write(ah, AR5K_DCU_MISC_POST_FR_BKOFF_DIS, |
|
AR5K_QUEUE_DFS_MISC(queue)); |
|
|
|
/* Enable/disable fragmentation burst backoff */ |
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE) |
|
ath5k_hw_reg_write(ah, AR5K_DCU_MISC_BACKOFF_FRAG, |
|
AR5K_QUEUE_DFS_MISC(queue)); |
|
|
|
/* |
|
* Set registers by queue type |
|
*/ |
|
switch (tq->tqi_type) { |
|
case AR5K_TX_QUEUE_BEACON: |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_FRSHED_DBA_GT | |
|
AR5K_QCU_MISC_CBREXP_BCN_DIS | |
|
AR5K_QCU_MISC_BCN_ENABLE); |
|
|
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), |
|
(AR5K_DCU_MISC_ARBLOCK_CTL_GLOBAL << |
|
AR5K_DCU_MISC_ARBLOCK_CTL_S) | |
|
AR5K_DCU_MISC_ARBLOCK_IGNORE | |
|
AR5K_DCU_MISC_POST_FR_BKOFF_DIS | |
|
AR5K_DCU_MISC_BCN_ENABLE); |
|
break; |
|
|
|
case AR5K_TX_QUEUE_CAB: |
|
/* XXX: use BCN_SENT_GT, if we can figure out how */ |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_FRSHED_DBA_GT | |
|
AR5K_QCU_MISC_CBREXP_DIS | |
|
AR5K_QCU_MISC_CBREXP_BCN_DIS); |
|
|
|
ath5k_hw_reg_write(ah, ((tq->tqi_ready_time - |
|
(AR5K_TUNE_SW_BEACON_RESP - |
|
AR5K_TUNE_DMA_BEACON_RESP) - |
|
AR5K_TUNE_ADDITIONAL_SWBA_BACKOFF) * 1024) | |
|
AR5K_QCU_RDYTIMECFG_ENABLE, |
|
AR5K_QUEUE_RDYTIMECFG(queue)); |
|
|
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), |
|
(AR5K_DCU_MISC_ARBLOCK_CTL_GLOBAL << |
|
AR5K_DCU_MISC_ARBLOCK_CTL_S)); |
|
break; |
|
|
|
case AR5K_TX_QUEUE_UAPSD: |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), |
|
AR5K_QCU_MISC_CBREXP_DIS); |
|
break; |
|
|
|
case AR5K_TX_QUEUE_DATA: |
|
default: |
|
break; |
|
} |
|
|
|
/* TODO: Handle frame compression */ |
|
|
|
/* |
|
* Enable interrupts for this tx queue |
|
* in the secondary interrupt mask registers |
|
*/ |
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXOKINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txok, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXERRINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txerr, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXURNINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txurn, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXDESCINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txdesc, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXEOLINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txeol, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_CBRORNINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_cbrorn, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_CBRURNINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_cbrurn, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_QTRIGINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_qtrig, queue); |
|
|
|
if (tq->tqi_flags & AR5K_TXQ_FLAG_TXNOFRMINT_ENABLE) |
|
AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_nofrm, queue); |
|
|
|
/* Update secondary interrupt mask registers */ |
|
|
|
/* Filter out inactive queues */ |
|
ah->ah_txq_imr_txok &= ah->ah_txq_status; |
|
ah->ah_txq_imr_txerr &= ah->ah_txq_status; |
|
ah->ah_txq_imr_txurn &= ah->ah_txq_status; |
|
ah->ah_txq_imr_txdesc &= ah->ah_txq_status; |
|
ah->ah_txq_imr_txeol &= ah->ah_txq_status; |
|
ah->ah_txq_imr_cbrorn &= ah->ah_txq_status; |
|
ah->ah_txq_imr_cbrurn &= ah->ah_txq_status; |
|
ah->ah_txq_imr_qtrig &= ah->ah_txq_status; |
|
ah->ah_txq_imr_nofrm &= ah->ah_txq_status; |
|
|
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_txok, |
|
AR5K_SIMR0_QCU_TXOK) | |
|
AR5K_REG_SM(ah->ah_txq_imr_txdesc, |
|
AR5K_SIMR0_QCU_TXDESC), |
|
AR5K_SIMR0); |
|
|
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_txerr, |
|
AR5K_SIMR1_QCU_TXERR) | |
|
AR5K_REG_SM(ah->ah_txq_imr_txeol, |
|
AR5K_SIMR1_QCU_TXEOL), |
|
AR5K_SIMR1); |
|
|
|
/* Update SIMR2 but don't overwrite rest simr2 settings */ |
|
AR5K_REG_DISABLE_BITS(ah, AR5K_SIMR2, AR5K_SIMR2_QCU_TXURN); |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_SIMR2, |
|
AR5K_REG_SM(ah->ah_txq_imr_txurn, |
|
AR5K_SIMR2_QCU_TXURN)); |
|
|
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_cbrorn, |
|
AR5K_SIMR3_QCBRORN) | |
|
AR5K_REG_SM(ah->ah_txq_imr_cbrurn, |
|
AR5K_SIMR3_QCBRURN), |
|
AR5K_SIMR3); |
|
|
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_qtrig, |
|
AR5K_SIMR4_QTRIG), AR5K_SIMR4); |
|
|
|
/* Set TXNOFRM_QCU for the queues with TXNOFRM enabled */ |
|
ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_nofrm, |
|
AR5K_TXNOFRM_QCU), AR5K_TXNOFRM); |
|
|
|
/* No queue has TXNOFRM enabled, disable the interrupt |
|
* by setting AR5K_TXNOFRM to zero */ |
|
if (ah->ah_txq_imr_nofrm == 0) |
|
ath5k_hw_reg_write(ah, 0, AR5K_TXNOFRM); |
|
|
|
/* Set QCU mask for this DCU to save power */ |
|
AR5K_REG_WRITE_Q(ah, AR5K_QUEUE_QCUMASK(queue), queue); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/**************************\ |
|
* Global QCU/DCU functions * |
|
\**************************/ |
|
|
|
/** |
|
* ath5k_hw_set_ifs_intervals() - Set global inter-frame spaces on DCU |
|
* @ah: The &struct ath5k_hw |
|
* @slot_time: Slot time in us |
|
* |
|
* Sets the global IFS intervals on DCU (also works on AR5210) for |
|
* the given slot time and the current bwmode. |
|
*/ |
|
int ath5k_hw_set_ifs_intervals(struct ath5k_hw *ah, unsigned int slot_time) |
|
{ |
|
struct ieee80211_channel *channel = ah->ah_current_channel; |
|
enum nl80211_band band; |
|
struct ieee80211_supported_band *sband; |
|
struct ieee80211_rate *rate; |
|
u32 ack_tx_time, eifs, eifs_clock, sifs, sifs_clock; |
|
u32 slot_time_clock = ath5k_hw_htoclock(ah, slot_time); |
|
u32 rate_flags, i; |
|
|
|
if (slot_time < 6 || slot_time_clock > AR5K_SLOT_TIME_MAX) |
|
return -EINVAL; |
|
|
|
sifs = ath5k_hw_get_default_sifs(ah); |
|
sifs_clock = ath5k_hw_htoclock(ah, sifs - 2); |
|
|
|
/* EIFS |
|
* Txtime of ack at lowest rate + SIFS + DIFS |
|
* (DIFS = SIFS + 2 * Slot time) |
|
* |
|
* Note: HAL has some predefined values for EIFS |
|
* Turbo: (37 + 2 * 6) |
|
* Default: (74 + 2 * 9) |
|
* Half: (149 + 2 * 13) |
|
* Quarter: (298 + 2 * 21) |
|
* |
|
* (74 + 2 * 6) for AR5210 default and turbo ! |
|
* |
|
* According to the formula we have |
|
* ack_tx_time = 25 for turbo and |
|
* ack_tx_time = 42.5 * clock multiplier |
|
* for default/half/quarter. |
|
* |
|
* This can't be right, 42 is what we would get |
|
* from ath5k_hw_get_frame_dur_for_bwmode or |
|
* ieee80211_generic_frame_duration for zero frame |
|
* length and without SIFS ! |
|
* |
|
* Also we have different lowest rate for 802.11a |
|
*/ |
|
if (channel->band == NL80211_BAND_5GHZ) |
|
band = NL80211_BAND_5GHZ; |
|
else |
|
band = NL80211_BAND_2GHZ; |
|
|
|
switch (ah->ah_bwmode) { |
|
case AR5K_BWMODE_5MHZ: |
|
rate_flags = IEEE80211_RATE_SUPPORTS_5MHZ; |
|
break; |
|
case AR5K_BWMODE_10MHZ: |
|
rate_flags = IEEE80211_RATE_SUPPORTS_10MHZ; |
|
break; |
|
default: |
|
rate_flags = 0; |
|
break; |
|
} |
|
sband = &ah->sbands[band]; |
|
rate = NULL; |
|
for (i = 0; i < sband->n_bitrates; i++) { |
|
if ((rate_flags & sband->bitrates[i].flags) != rate_flags) |
|
continue; |
|
rate = &sband->bitrates[i]; |
|
break; |
|
} |
|
if (WARN_ON(!rate)) |
|
return -EINVAL; |
|
|
|
ack_tx_time = ath5k_hw_get_frame_duration(ah, band, 10, rate, false); |
|
|
|
/* ack_tx_time includes an SIFS already */ |
|
eifs = ack_tx_time + sifs + 2 * slot_time; |
|
eifs_clock = ath5k_hw_htoclock(ah, eifs); |
|
|
|
/* Set IFS settings on AR5210 */ |
|
if (ah->ah_version == AR5K_AR5210) { |
|
u32 pifs, pifs_clock, difs, difs_clock; |
|
|
|
/* Set slot time */ |
|
ath5k_hw_reg_write(ah, slot_time_clock, AR5K_SLOT_TIME); |
|
|
|
/* Set EIFS */ |
|
eifs_clock = AR5K_REG_SM(eifs_clock, AR5K_IFS1_EIFS); |
|
|
|
/* PIFS = Slot time + SIFS */ |
|
pifs = slot_time + sifs; |
|
pifs_clock = ath5k_hw_htoclock(ah, pifs); |
|
pifs_clock = AR5K_REG_SM(pifs_clock, AR5K_IFS1_PIFS); |
|
|
|
/* DIFS = SIFS + 2 * Slot time */ |
|
difs = sifs + 2 * slot_time; |
|
difs_clock = ath5k_hw_htoclock(ah, difs); |
|
|
|
/* Set SIFS/DIFS */ |
|
ath5k_hw_reg_write(ah, (difs_clock << |
|
AR5K_IFS0_DIFS_S) | sifs_clock, |
|
AR5K_IFS0); |
|
|
|
/* Set PIFS/EIFS and preserve AR5K_INIT_CARR_SENSE_EN */ |
|
ath5k_hw_reg_write(ah, pifs_clock | eifs_clock | |
|
(AR5K_INIT_CARR_SENSE_EN << AR5K_IFS1_CS_EN_S), |
|
AR5K_IFS1); |
|
|
|
return 0; |
|
} |
|
|
|
/* Set IFS slot time */ |
|
ath5k_hw_reg_write(ah, slot_time_clock, AR5K_DCU_GBL_IFS_SLOT); |
|
|
|
/* Set EIFS interval */ |
|
ath5k_hw_reg_write(ah, eifs_clock, AR5K_DCU_GBL_IFS_EIFS); |
|
|
|
/* Set SIFS interval in usecs */ |
|
AR5K_REG_WRITE_BITS(ah, AR5K_DCU_GBL_IFS_MISC, |
|
AR5K_DCU_GBL_IFS_MISC_SIFS_DUR_USEC, |
|
sifs); |
|
|
|
/* Set SIFS interval in clock cycles */ |
|
ath5k_hw_reg_write(ah, sifs_clock, AR5K_DCU_GBL_IFS_SIFS); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/** |
|
* ath5k_hw_init_queues() - Initialize tx queues |
|
* @ah: The &struct ath5k_hw |
|
* |
|
* Initializes all tx queues based on information on |
|
* ah->ah_txq* set by the driver |
|
*/ |
|
int |
|
ath5k_hw_init_queues(struct ath5k_hw *ah) |
|
{ |
|
int i, ret; |
|
|
|
/* TODO: HW Compression support for data queues */ |
|
/* TODO: Burst prefetch for data queues */ |
|
|
|
/* |
|
* Reset queues and start beacon timers at the end of the reset routine |
|
* This also sets QCU mask on each DCU for 1:1 qcu to dcu mapping |
|
* Note: If we want we can assign multiple qcus on one dcu. |
|
*/ |
|
if (ah->ah_version != AR5K_AR5210) |
|
for (i = 0; i < ah->ah_capabilities.cap_queues.q_tx_num; i++) { |
|
ret = ath5k_hw_reset_tx_queue(ah, i); |
|
if (ret) { |
|
ATH5K_ERR(ah, |
|
"failed to reset TX queue #%d\n", i); |
|
return ret; |
|
} |
|
} |
|
else |
|
/* No QCU/DCU on AR5210, just set tx |
|
* retry limits. We set IFS parameters |
|
* on ath5k_hw_set_ifs_intervals */ |
|
ath5k_hw_set_tx_retry_limits(ah, 0); |
|
|
|
/* Set the turbo flag when operating on 40MHz */ |
|
if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) |
|
AR5K_REG_ENABLE_BITS(ah, AR5K_DCU_GBL_IFS_MISC, |
|
AR5K_DCU_GBL_IFS_MISC_TURBO_MODE); |
|
|
|
/* If we didn't set IFS timings through |
|
* ath5k_hw_set_coverage_class make sure |
|
* we set them here */ |
|
if (!ah->ah_coverage_class) { |
|
unsigned int slot_time = ath5k_hw_get_default_slottime(ah); |
|
ath5k_hw_set_ifs_intervals(ah, slot_time); |
|
} |
|
|
|
return 0; |
|
}
|
|
|