blob: 5a71070340a2dc00938533bcd73c73041b05956a [file] [log] [blame]
/******************************************************************************
*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* GPL LICENSE SUMMARY
*
* Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved.
* Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* 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,
* USA
*
* The full GNU General Public License is included in this distribution
* in the file called COPYING.
*
* Contact Information:
* Intel Linux Wireless <ilw@linux.intel.com>
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
*
* BSD LICENSE
*
* Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
* Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*****************************************************************************/
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/log2.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_internal.h"
#include "sdio_tx.h"
#include "sdio_tx_policy.h"
#include "iwl-prph.h"
#include "iwl-fh.h"
#include "iwl-csr.h"
#include "iwl-io.h"
#include "iwl-scd.h"
#include "mvm/fw-api-tx.h"
/* align to DW to the lower address */
#define ALIGN_DW_LOW(x) ((x) & (~3))
#define PAD_DTU_LEN (sizeof(struct iwl_sdio_tx_dtu_hdr) + \
sizeof(struct iwl_sdio_adma_desc))
static int iwl_sdio_txq_set_ratid_map(struct iwl_trans *trans, u16 ra_tid,
u16 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
u32 tbl_dw_addr;
u32 tbl_dw;
u16 scd_q2ratid;
scd_q2ratid = ra_tid & SCD_QUEUE_RA_TID_MAP_RATID_MSK;
tbl_dw_addr = trans_sdio->scd_base_addr +
SCD_TRANS_TBL_OFFSET_QUEUE(txq_id);
tbl_dw = iwl_trans_read_mem32(trans, tbl_dw_addr);
if (txq_id & 0x1)
tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF);
else
tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000);
iwl_trans_write_mem32(trans, tbl_dw_addr, tbl_dw);
return 0;
}
static void iwl_sdio_txq_disable_wk(struct work_struct *wk)
{
struct iwl_trans_sdio *trans_sdio =
container_of(wk, struct iwl_trans_sdio,
iwl_sdio_disable_txq_wk);
struct iwl_trans *trans = trans_sdio->trans;
static const u32 zero_data[4] = {};
int txq_id;
for (txq_id = 0; txq_id < ARRAY_SIZE(trans_sdio->txq); txq_id++) {
if (!trans_sdio->txq[txq_id].disabling)
continue;
iwl_scd_txq_set_inactive(trans, txq_id);
iwl_trans_write_mem(trans, trans_sdio->scd_base_addr +
SCD_TX_STTS_QUEUE_OFFSET(txq_id),
zero_data, ARRAY_SIZE(zero_data));
trans_sdio->txq[txq_id].disabling = false;
/* FIXME: any other configs? */
iwl_slv_free_data_queue(trans, txq_id);
IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d finished\n",
txq_id);
}
}
void iwl_trans_sdio_txq_disable(struct iwl_trans *trans, int txq_id,
bool configure_scd)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
if (configure_scd) {
trans_sdio->txq[txq_id].disabling = true;
schedule_work(&trans_sdio->iwl_sdio_disable_txq_wk);
IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d spawned\n",
txq_id);
return;
}
/* FIXME: any other configs? */
iwl_slv_free_data_queue(trans, txq_id);
IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d finished\n", txq_id);
}
void iwl_trans_sdio_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn,
const struct iwl_trans_txq_scd_cfg *cfg,
unsigned int wdg_timeout)
{
struct iwl_trans_slv *trans_slv = IWL_TRANS_GET_SLV_TRANS(trans);
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
/* make sure we finished to disable the queues before allocation */
flush_work(&trans_sdio->iwl_sdio_disable_txq_wk);
if (cfg) {
/* Disable the scheduler prior configuring the cmd queue */
if (txq_id == trans_slv->cmd_queue)
iwl_scd_enable_set_active(trans, 0);
/* Stop this Tx queue before configuring it */
iwl_scd_txq_set_inactive(trans, txq_id);
/*
* Set this queue as a chain-building queue
* unless it is CMD queue
*/
if (txq_id != trans_slv->cmd_queue)
iwl_scd_txq_set_chain(trans, txq_id);
/*
* If this queue is mapped to a certain station:
* it is an AGG queue
*/
if (cfg->aggregate) {
u16 ra_tid = BUILD_RAxTID(cfg->sta_id, cfg->tid);
/* Map receiver-address / traffic-ID to this queue */
iwl_sdio_txq_set_ratid_map(trans, ra_tid, txq_id);
/* enable aggregations for the queue */
iwl_scd_txq_enable_agg(trans, txq_id);
} else {
/*
* disable aggregations for the queue, this will also
* make the ra_tid mapping configuration irrelevant
* since it is now a non-AGG queue.
*/
iwl_scd_txq_disable_agg(trans, txq_id);
ssn = trans_sdio->txq[txq_id].scd_write_ptr;
}
}
trans_sdio->txq[txq_id].scd_write_ptr = ssn & (TFD_QUEUE_SIZE_MAX - 1);
iwl_trans_slv_tx_set_ssn(trans, txq_id, ssn);
/* Configure first TFD */
iwl_write_direct32(trans, HBUS_TARG_WRPTR,
(ssn & (TFD_QUEUE_SIZE_MAX - 1)) |
(txq_id << 8));
if (cfg) {
u8 frame_limit = cfg->frame_limit;
iwl_write_prph(trans, SCD_QUEUE_RDPTR(txq_id), ssn);
/* tx window and frame sizes for this queue */
iwl_trans_write_mem32(trans, trans_sdio->scd_base_addr +
SCD_CONTEXT_QUEUE_OFFSET(txq_id), 0);
iwl_trans_write_mem32(trans, trans_sdio->scd_base_addr +
SCD_CONTEXT_QUEUE_OFFSET(txq_id) + sizeof(u32),
((frame_limit << SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) &
SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) |
((frame_limit << SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) &
SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK));
/*
* Set up Status area in SRAM, map to Tx DMA/FIFO,
* activate the queue
*/
iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id),
(1 << SCD_QUEUE_STTS_REG_POS_ACTIVE) |
(cfg->fifo << SCD_QUEUE_STTS_REG_POS_TXF) |
(1 << SCD_QUEUE_STTS_REG_POS_WSL) |
SCD_QUEUE_STTS_REG_MSK);
/*
* Enable the scheduler by setting the cmd queue bit in
* ucode reg.
*/
if (txq_id == trans_slv->cmd_queue)
iwl_scd_enable_set_active(trans, BIT(txq_id));
IWL_DEBUG_TX_QUEUES(trans,
"Activate queue %d on FIFO %d WrPtr: %d\n",
txq_id, cfg->fifo,
ssn & (TFD_QUEUE_SIZE_MAX - 1));
} else {
IWL_DEBUG_TX_QUEUES(trans, "Activate queue %d WrPtr: %d\n",
txq_id, ssn & (TFD_QUEUE_SIZE_MAX - 1));
}
trans_sdio->txq[txq_id].ptfd_cur_row =
IWL_SDIO_SRAM_TABLE_EMPTY_PTFD_CELL;
trans_sdio->txq[txq_id].bye_count0 = 0;
trans_sdio->txq[txq_id].bye_count1 = 0;
}
static void iwl_sdio_scd_reset(struct iwl_trans *trans, u32 scd_base_addr)
{
u32 addr;
addr = scd_base_addr + SCD_CONTEXT_MEM_LOWER_BOUND;
/* reset context data memory */
for (; addr < scd_base_addr + SCD_CONTEXT_MEM_UPPER_BOUND; addr += 4)
iwl_trans_write_mem32(trans, addr, 0);
/* reset tx status memory */
for (; addr < scd_base_addr + SCD_TX_STTS_MEM_UPPER_BOUND; addr += 4)
iwl_trans_write_mem32(trans, addr, 0);
for (; addr < scd_base_addr +
SCD_TRANS_TBL_OFFSET_QUEUE(trans->cfg->base_params->num_of_queues);
addr += 4)
iwl_trans_write_mem32(trans, addr, 0);
}
void iwl_sdio_tx_start(struct iwl_trans *trans, u32 scd_base_addr)
{
struct iwl_trans_slv *trans_slv = IWL_TRANS_GET_SLV_TRANS(trans);
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
u32 i, data;
trans_sdio->send_buf = kzalloc(IWL_SDIO_SEND_BUF_LEN, GFP_KERNEL);
if (!trans_sdio->send_buf)
return;
trans_sdio->send_buf_idx = 0;
trans_sdio->scd_base_addr = iwl_read_prph(trans, SCD_SRAM_BASE_ADDR);
WARN_ON(scd_base_addr != 0 &&
scd_base_addr != trans_sdio->scd_base_addr);
iwl_sdio_scd_reset(trans, scd_base_addr);
/* set BC table address */
iwl_write_prph(trans, SCD_DRAM_BASE_ADDR,
trans_sdio->sf_mem_addresses->bc_base_addr >> 10);
/* set SCD CB size to 64 */
iwl_write_prph(trans, SCD_CB_SIZE, 0);
/* Set CB base pointer
* Transaction to FH maps from PTFD entry in SNF to the actual TFD;
* The FH assumes the data is address bits [35..8]
*/
for (i = 0; i < IWL_SDIO_CB_QUEUES_NUM; i++) {
data = i << 5;
iwl_write_direct32(trans, FH_MEM_CBBC_QUEUE(i), data);
}
/* enable command queue */
iwl_trans_ac_txq_enable(trans, trans_slv->cmd_queue,
trans_slv->cmd_fifo, 0);
iwl_scd_activate_fifos(trans);
for (i = 0; i < FH_TCSR_CHNL_NUM; i++) {
data = FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE |
FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE;
iwl_write_direct32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(i), data);
}
/* update chicken bits */
data = iwl_read_prph(trans, FH_TX_CHICKEN_BITS_REG);
iwl_write_prph(trans, FH_TX_CHICKEN_BITS_REG,
data | FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN);
IWL_DEBUG_TX(trans, "Tx Started\n");
}
void iwl_sdio_tx_stop(struct iwl_trans *trans)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
int i, ret;
if (!trans_sdio->send_buf)
return;
iwl_scd_deactivate_fifos(trans);
for (i = 0; i < FH_TCSR_CHNL_NUM; i++) {
iwl_write_direct32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(i), 0);
ret = iwl_poll_direct_bit(trans, FH_TSSR_TX_STATUS_REG,
FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(i),
1000);
if (ret < 0)
IWL_ERR(trans,
"Failing on timeout while stopping DMA channel %d [0x%08x]\n",
i, iwl_read_direct32(trans,
FH_TSSR_TX_STATUS_REG));
}
kfree(trans_sdio->send_buf);
trans_sdio->send_buf = NULL;
flush_work(&trans_sdio->iwl_sdio_disable_txq_wk);
IWL_DEBUG_TX(trans, "Tx Stopped\n");
}
void iwl_sdio_tx_free(struct iwl_trans *trans)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
if (WARN_ON_ONCE(!trans_sdio)) {
IWL_ERR(trans, "No transport?\n");
return;
}
if (trans_sdio->dtu_cfg_pool) {
kmem_cache_destroy(trans_sdio->dtu_cfg_pool);
trans_sdio->dtu_cfg_pool = NULL;
}
iwl_slv_al_mem_pool_deinit(&trans_sdio->slv_tx.tfd_pool);
iwl_slv_al_mem_pool_deinit(&trans_sdio->slv_tx.tb_pool);
}
int iwl_sdio_tx_init(struct iwl_trans *trans)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
int ret;
/* header, trailer and meta-data */
trans_sdio->cfg_pool_size = sizeof(struct iwl_sdio_dtu_info) +
sizeof(struct iwl_sdio_tx_dtu_hdr) +
sizeof(struct iwl_sdio_adma_desc) *
IWL_SDIO_ADMA_DESC_MAX_NUM +
sizeof(struct iwl_sdio_tfd) +
IWL_SDIO_BLOCK_SIZE;
trans_sdio->dtu_cfg_pool = kmem_cache_create("iwl_sdio_tx_dtu_cfg_pool",
trans_sdio->cfg_pool_size,
sizeof(void *), 0, NULL);
if (unlikely(!trans_sdio->dtu_cfg_pool)) {
ret = -ENOMEM;
goto exit;
}
spin_lock_init(&trans_sdio->slv_tx.mem_rsrc_lock);
INIT_WORK(&trans_sdio->iwl_sdio_disable_txq_wk,
iwl_sdio_txq_disable_wk);
ret = iwl_slv_al_mem_pool_init(&trans_sdio->slv_tx.tfd_pool,
IWL_SDIO_TFD_POOL_SIZE);
if (ret)
goto error_free;
ret = iwl_slv_al_mem_pool_init(&trans_sdio->slv_tx.tb_pool,
IWL_SDIO_DEFAULT_TB_POOL_SIZE);
if (ret)
goto error_free;
iwl_set_bits_prph(trans, SCD_GP_CTRL,
SCD_GP_CTRL_AUTO_ACTIVE_MODE);
if (trans->cfg->base_params->num_of_queues > 20)
iwl_set_bits_prph(trans, SCD_GP_CTRL,
SCD_GP_CTRL_ENABLE_31_QUEUES);
return 0;
error_free:
iwl_sdio_tx_free(trans);
exit:
return ret;
}
void iwl_sdio_tx_calc_desc_num(struct iwl_trans *trans,
struct iwl_slv_tx_chunk_info *chunk_info)
{
struct iwl_trans_slv *trans_slv = IWL_TRANS_GET_SLV_TRANS(trans);
chunk_info->desc_num = DIV_ROUND_UP(chunk_info->len,
trans_slv->config.tb_size);
}
void iwl_sdio_tx_free_dtu_mem(struct iwl_trans *trans, void **data)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = *data;
if (!dtu_info || !trans_sdio->dtu_cfg_pool)
return;
kmem_cache_free(trans_sdio->dtu_cfg_pool, dtu_info);
*data = NULL;
return;
}
void iwl_sdio_tx_clean_dtu(struct iwl_trans *trans, void *data)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = data;
int ret, i;
if (WARN_ON(!data))
return;
spin_lock_bh(&trans_sdio->slv_tx.mem_rsrc_lock);
if (dtu_info->sram_alloc.tfd == IWL_SLV_AL_INVALID)
goto exit;
ret = iwl_slv_al_mem_pool_free(&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tfd_pool,
dtu_info->sram_alloc.tfd);
if (ret)
IWL_WARN(trans, "%s failed to free TFD\n", __func__);
dtu_info->sram_alloc.tfd = IWL_SLV_AL_INVALID;
for (i = 0; i < dtu_info->sram_alloc.num_fragments; i++) {
int k;
for (k = 0; k < dtu_info->sram_alloc.fragments[i].size; k++) {
ret = iwl_slv_al_mem_pool_free(
&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tb_pool,
dtu_info->sram_alloc.fragments[i].index + k);
if (ret)
IWL_WARN(trans,
"failed to free TB pool resources\n");
}
}
exit:
spin_unlock_bh(&trans_sdio->slv_tx.mem_rsrc_lock);
iwl_sdio_tx_free_dtu_mem(trans, &data);
return;
}
static int
iwl_sdio_tx_get_resources(struct iwl_trans *trans, u8 txq_id,
struct iwl_slv_txq_entry *txq_entry)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_trans_slv *trans_slv = IWL_TRANS_GET_SLV_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = txq_entry->reclaim_info;
int free_tfds, free_tbs, ret;
u8 tbs_to_allocate = DIV_ROUND_UP(txq_entry->dtu_meta.total_len,
trans_slv->config.tb_size);
u32 max_order = IWL_SDIO_MAX_ORDER;
u8 order;
spin_lock_bh(&trans_sdio->slv_tx.mem_rsrc_lock);
/* check resources availability according to policy */
free_tbs = iwl_slv_al_mem_pool_free_count(&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tb_pool);
free_tfds = iwl_slv_al_mem_pool_free_count(&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tfd_pool);
IWL_DEBUG_TX(trans, "q %d free_tfds %d free_tbs %d tbs_to_alloc %d\n",
txq_id, free_tfds, free_tbs, tbs_to_allocate);
if (!iwl_sdio_policy_check_alloc(trans_slv, txq_id,
free_tfds, free_tbs, 1,
tbs_to_allocate)) {
dtu_info->sram_alloc.tfd = IWL_SLV_AL_INVALID;
ret = -ENOMEM;
goto error;
}
/* Allocate ptfds up to the last row, not inclusive. The last row is
* shared with the first ptfds in the queue, since the rows are written
* in full, the first ptfds will be overwritten when ptfd_cur_row is
* initialized
*/
if (atomic_read(&trans_slv->txqs[txq_id].sent_count) >=
IWL_SDIO_CB_QUEUE_SIZE - sizeof(trans_sdio->txq[0].ptfd_cur_row)) {
dtu_info->sram_alloc.tfd = IWL_SLV_AL_INVALID;
ret = -ENOSPC;
goto error;
}
dtu_info->sram_alloc.txq_id = txq_id;
/* allocate TFD */
ret = iwl_slv_al_mem_pool_alloc(&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tfd_pool);
if (ret < 0) {
IWL_WARN(trans, "mem_pool_alloc failed tfd %d\n", ret);
dtu_info->sram_alloc.tfd = IWL_SLV_AL_INVALID;
goto error;
}
dtu_info->sram_alloc.tfd = ret;
/* allocate TBs */
dtu_info->sram_alloc.num_fragments = 0;
/* capped by bits reserved for size 32 - IWL_SDIO_DMA_DESC_LEN_SHIFT */
order = min_t(u32, max_order, ilog2(tbs_to_allocate));
while (tbs_to_allocate > 0 &&
dtu_info->sram_alloc.num_fragments < IWL_SDIO_MAX_TBS_NUM) {
int idx;
u16 size = 1 << order;
ret = iwl_slv_al_mem_pool_alloc_range(
&trans_sdio->slv_tx,
&trans_sdio->slv_tx.tb_pool, order);
if (ret < 0) {
/* look for smaller region size */
if (!order)
break;
order--;
continue;
}
idx = dtu_info->sram_alloc.num_fragments;
dtu_info->sram_alloc.fragments[idx].index = ret;
dtu_info->sram_alloc.fragments[idx].size = size;
dtu_info->sram_alloc.num_fragments++;
tbs_to_allocate -= size;
if (tbs_to_allocate && tbs_to_allocate < size)
order = min_t(u32, max_order, ilog2(tbs_to_allocate));
}
if (tbs_to_allocate > 0) {
IWL_DEBUG_TX(trans, "could not allocate %d tbs\n",
txq_entry->dtu_meta.total_desc_num);
goto error;
}
spin_unlock_bh(&trans_sdio->slv_tx.mem_rsrc_lock);
return 0;
error:
spin_unlock_bh(&trans_sdio->slv_tx.mem_rsrc_lock);
iwl_sdio_tx_clean_dtu(trans, txq_entry->reclaim_info);
return ret;
}
static int iwl_sdio_alloc_dtu_mem(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
/* header & trailer for SDTM */
txq_entry->reclaim_info = kmem_cache_alloc(trans_sdio->dtu_cfg_pool,
GFP_KERNEL);
if (unlikely(!txq_entry->reclaim_info))
return -ENOMEM;
memset(txq_entry->reclaim_info, 0, trans_sdio->cfg_pool_size);
/* PTFD, TFD, TB */
return iwl_sdio_tx_get_resources(trans, txq_id, txq_entry);
}
/* set ADMA descriptors defining the destination of the data stream */
static void iwl_sdio_config_adma(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry, u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = txq_entry->reclaim_info;
struct iwl_sdio_tx_dtu_hdr *dtu_hdr = dtu_info->ctrl_buf;
struct iwl_sdio_adma_desc *adma_list = dtu_hdr->adma_list;
struct iwl_sdio_tfd *tfd;
u32 tfd_num = trans_sdio->txq[txq_id].scd_write_ptr &
(IWL_SDIO_CB_QUEUE_SIZE - 1);
u8 desc_idx;
u32 bc_addr;
u32 adma_dsc_mem_base =
trans_sdio->sf_mem_addresses->adma_dsc_mem_base;
int i;
*((__le64 *)&(dtu_hdr->dma_desc)) =
cpu_to_le64(((u64)adma_dsc_mem_base <<
IWL_SDIO_DMA_DESC_ADDR_SHIFT) |
((dtu_info->adma_desc_num *
sizeof(struct iwl_sdio_adma_desc)) <<
IWL_SDIO_DMA_DESC_LEN_SHIFT));
tfd = (void *)((u8 *)dtu_info->ctrl_buf +
sizeof(struct iwl_sdio_tx_dtu_hdr) +
sizeof(struct iwl_sdio_adma_desc) *
dtu_info->adma_desc_num);
/* ADMA desc for TFD */
desc_idx = 0;
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_VALID | IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
adma_list[desc_idx].length =
cpu_to_le16(sizeof(struct iwl_sdio_tfd));
adma_list[desc_idx].addr =
cpu_to_le32(trans_sdio->sf_mem_addresses->tfd_base_addr +
dtu_info->sram_alloc.tfd *
sizeof(struct iwl_sdio_tfd));
desc_idx++;
/* data mapping */
for (i = 0; i < tfd->num_fragments; i++) {
u32 tb_len, aligned_len;
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_VALID | IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
tb_len = le32_to_cpu(tfd->tbs[i]) >>
IWL_SDIO_DMA_TB_LEN_SHIFT;
aligned_len = round_up(tb_len, 4);
dtu_info->data_pad_len = aligned_len - tb_len;
adma_list[desc_idx].length = cpu_to_le16(aligned_len);
adma_list[desc_idx].addr =
cpu_to_le32(trans_sdio->sf_mem_addresses->tb_base_addr +
IWL_SDIO_TB_SIZE *
dtu_info->sram_alloc.fragments[i].index);
desc_idx++;
}
/* PTFD descriptor */
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_VALID | IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
adma_list[desc_idx].length = cpu_to_le16(4);
adma_list[desc_idx].addr =
cpu_to_le32(trans_sdio->sf_mem_addresses->tfdi_base_addr +
txq_id * IWL_SDIO_CB_QUEUE_SIZE +
ALIGN_DW_LOW(tfd_num));
desc_idx++;
/* SCD BC descriptor */
bc_addr = ALIGN_DW_LOW(trans_sdio->sf_mem_addresses->bc_base_addr +
IWL_SDIO_BC_TABLE_ENTRY_SIZE_BYTES *
((2 * txq_id) * IWL_SDIO_CB_QUEUE_SIZE + tfd_num));
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_VALID | IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
adma_list[desc_idx].length = cpu_to_le16(4);
adma_list[desc_idx].addr = cpu_to_le32(bc_addr);
desc_idx++;
bc_addr = ALIGN_DW_LOW(trans_sdio->sf_mem_addresses->bc_base_addr +
IWL_SDIO_BC_TABLE_ENTRY_SIZE_BYTES *
((2 * txq_id + 1) * IWL_SDIO_CB_QUEUE_SIZE + tfd_num));
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_VALID | IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
adma_list[desc_idx].length = cpu_to_le16(4);
adma_list[desc_idx].addr = cpu_to_le32(bc_addr);
desc_idx++;
/* SCD WR PTR descriptor */
adma_list[desc_idx].attr =
IWL_SDIO_ADMA_ATTR_END | IWL_SDIO_ADMA_ATTR_VALID |
IWL_SDIO_ADMA_ATTR_ACT2;
adma_list[desc_idx].reserved = 0;
adma_list[desc_idx].length = cpu_to_le16(4);
adma_list[desc_idx].addr = cpu_to_le32(IWL_SDIO_SCD_WR_PTR_ADDR);
desc_idx++;
}
/**
* iwl_sdio_comp_ptfd() - compute ptfd row
* @trans_sdio: transport
* @txq_id: the queue index
*
* Since ADMA can work only with dword aligned addresses, each time
* the PTFD table is updated - the whole row should be written.
*/
static u32 iwl_sdio_comp_ptfd(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = (void *)txq_entry->reclaim_info;
u32 ptfd_cur_row = trans_sdio->txq[txq_id].ptfd_cur_row;
u8 shift = (trans_sdio->txq[txq_id].scd_write_ptr & 0x3)
* BITS_PER_BYTE;
if (!(trans_sdio->txq[txq_id].scd_write_ptr & 0x3))
ptfd_cur_row = IWL_SDIO_SRAM_TABLE_EMPTY_PTFD_CELL;
WARN_ON_ONCE(dtu_info->sram_alloc.tfd & ~0xff);
/* clear the byte that we need to write and write the tfd index */
ptfd_cur_row &= ~(0xff << shift);
ptfd_cur_row |= (dtu_info->sram_alloc.tfd & 0xff) << shift;
trans_sdio->txq[txq_id].ptfd_cur_row = ptfd_cur_row;
return ptfd_cur_row;
}
#define IWL_TX_CRC_SIZE 4
#define IWL_TX_DELIMITER_SIZE 4
/**
* iwl_sdio_comp_bc() - compute BC row
* @trans_sdio:
* @txq_id:
*
* BC should be in dwords instead of bytes. Each row contains two BCs
* because of the ADMA address alignment requirement. The BC row layout
* is as follows:
* 0..11 bc 12..15 sta_id 16..27 bc 28..31 sta_id
*
*/
static void iwl_sdio_comp_bc(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry, u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_device_cmd *cmd =
(void *)txq_entry->dtu_meta.chunk_info[0].addr;
struct iwl_tx_cmd *tx_cmd = (void *)cmd->payload;
u16 len, val;
len = le16_to_cpu(tx_cmd->len) +
IWL_TX_CRC_SIZE +
IWL_TX_DELIMITER_SIZE;
switch (tx_cmd->sec_ctl & TX_CMD_SEC_MSK) {
case TX_CMD_SEC_CCM:
len += IEEE80211_CCMP_MIC_LEN;
break;
case TX_CMD_SEC_TKIP:
len += IEEE80211_TKIP_ICV_LEN;
break;
case TX_CMD_SEC_WEP:
len += IEEE80211_WEP_IV_LEN + IEEE80211_WEP_ICV_LEN;
break;
}
if (trans_sdio->bc_table_dword)
len = DIV_ROUND_UP(len, 4);
val = len | (tx_cmd->sta_id << 12);
if (trans_sdio->txq[txq_id].scd_write_ptr & 0x1)
trans_sdio->txq[txq_id].bye_count1 = val;
else {
trans_sdio->txq[txq_id].bye_count0 = val;
trans_sdio->txq[txq_id].bye_count1 = 0;
}
}
static inline void iwl_sdio_inc_scd_wr_ptr(struct iwl_trans *trans,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
trans_sdio->txq[txq_id].scd_write_ptr++;
trans_sdio->txq[txq_id].scd_write_ptr &= (TFD_QUEUE_SIZE_MAX - 1);
}
static void iwl_sdio_config_dtu_trailer(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = txq_entry->reclaim_info;
struct iwl_sdio_tx_dtu_trailer *dtu_trailer;
u32 val;
dtu_trailer = (struct iwl_sdio_tx_dtu_trailer *)
((u8 *)dtu_info->ctrl_buf +
sizeof(struct iwl_sdio_tx_dtu_hdr) +
sizeof(struct iwl_sdio_adma_desc) *
dtu_info->adma_desc_num +
sizeof(struct iwl_sdio_tfd));
val = iwl_sdio_comp_ptfd(trans, txq_entry, txq_id);
dtu_trailer->ptfd = cpu_to_le32(val);
iwl_sdio_comp_bc(trans, txq_entry, txq_id);
dtu_trailer->scd_bc0_dup = dtu_trailer->scd_bc0 =
cpu_to_le16(trans_sdio->txq[txq_id].bye_count0);
dtu_trailer->scd_bc1_dup = dtu_trailer->scd_bc1 =
cpu_to_le16(trans_sdio->txq[txq_id].bye_count1);
iwl_sdio_inc_scd_wr_ptr(trans, txq_id);
dtu_trailer->scd_wr_ptr =
cpu_to_le32(trans_sdio->txq[txq_id].scd_write_ptr |
(txq_id << 8));
}
static void iwl_sdio_config_tfd(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info =
(struct iwl_sdio_dtu_info *)txq_entry->reclaim_info;
struct iwl_sdio_tfd *tfd;
u32 tb_addr, tb_len, last_tb, tb_base;
int cur_len;
int idx;
tb_base = trans_sdio->sf_mem_addresses->tb_base_addr -
IWL_SDIO_SF_MEM_BASE_ADDR;
tfd = (void *)((u8 *)dtu_info->ctrl_buf +
sizeof(struct iwl_sdio_tx_dtu_hdr) +
sizeof(struct iwl_sdio_adma_desc) *
dtu_info->adma_desc_num);
tfd->num_fragments = dtu_info->sram_alloc.num_fragments;
for (idx = 0; idx < tfd->num_fragments; idx++) {
cur_len = IWL_SDIO_TB_SIZE *
dtu_info->sram_alloc.fragments[idx].size;
if (idx == tfd->num_fragments - 1) {
/*
* last tb len isn't necessarily congrouent to tb size
* but sums up exactly to the packet length.
*/
last_tb = (txq_entry->dtu_meta.total_len - 1) %
IWL_SDIO_TB_SIZE + 1;
cur_len -= IWL_SDIO_TB_SIZE;
cur_len += last_tb;
}
tb_len = cur_len << IWL_SDIO_DMA_TB_LEN_SHIFT;
tb_addr = tb_base +
dtu_info->sram_alloc.fragments[idx].index *
IWL_SDIO_TB_SIZE;
tfd->tbs[idx] = cpu_to_le32(tb_addr | tb_len);
}
}
static void iwl_sdio_build_dtu(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = txq_entry->reclaim_info;
struct iwl_sdio_tx_dtu_hdr *dtu_hdr = dtu_info->ctrl_buf;
dtu_hdr->hdr.op_code = IWL_SDIO_OP_CODE_TX_DATA | IWL_SDIO_EOT_BIT;
dtu_hdr->hdr.seq_number = iwl_sdio_get_cmd_seq(trans_sdio, true);
dtu_hdr->hdr.signature = cpu_to_le16(IWL_SDIO_CMD_HEADER_SIGNATURE);
/* the number of system (not data) fields in DTU is 5 */
dtu_info->adma_desc_num = dtu_info->sram_alloc.num_fragments + 5;
iwl_sdio_config_tfd(trans, txq_entry);
iwl_sdio_config_adma(trans, txq_entry, txq_id);
iwl_sdio_config_dtu_trailer(trans, txq_entry, txq_id);
}
int iwl_sdio_flush_dtus(struct iwl_trans *trans)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct sdio_func *func = IWL_TRANS_SDIO_GET_FUNC(trans);
int ret = 0;
if (!trans_sdio->send_buf_idx)
goto out;
if (test_bit(STATUS_TRANS_DEAD, &trans->status))
return -EINVAL;
/* Only block size transfers are supported. In order to pad the whole
* transfer, add a trailing command for writing padding bytes to the
* trash buffer register
*/
if (!IS_ALIGNED(trans_sdio->send_buf_idx, IWL_SDIO_BLOCK_SIZE)) {
u8 *cur;
struct iwl_sdio_tx_dtu_hdr *dtu_hdr;
struct iwl_sdio_adma_desc *dma_desc;
u32 adma_dsc_mem_base =
trans_sdio->sf_mem_addresses->adma_dsc_mem_base;
int len, aligned_len;
len = trans_sdio->send_buf_idx + PAD_DTU_LEN;
aligned_len = ALIGN(len, IWL_SDIO_BLOCK_SIZE);
cur = (u8 *)&trans_sdio->send_buf[trans_sdio->send_buf_idx];
dtu_hdr = (struct iwl_sdio_tx_dtu_hdr *)cur;
dtu_hdr->hdr.op_code = IWL_SDIO_OP_CODE_TX_DATA | IWL_SDIO_EOT_BIT;
dtu_hdr->hdr.seq_number = iwl_sdio_get_cmd_seq(trans_sdio, true);
dtu_hdr->hdr.signature = cpu_to_le16(IWL_SDIO_CMD_HEADER_SIGNATURE);
memset(dtu_hdr->reserved, 0, sizeof(dtu_hdr->reserved));
*((__le64 *)&(dtu_hdr->dma_desc)) =
cpu_to_le64(((u64)adma_dsc_mem_base <<
IWL_SDIO_DMA_DESC_ADDR_SHIFT) |
(sizeof(*dma_desc) <<
IWL_SDIO_DMA_DESC_LEN_SHIFT));
cur += sizeof(*dtu_hdr);
dma_desc = (struct iwl_sdio_adma_desc *)cur;
dma_desc->attr = IWL_SDIO_ADMA_ATTR_END |
IWL_SDIO_ADMA_ATTR_VALID |
IWL_SDIO_ADMA_ATTR_ACT2;
dma_desc->reserved = 0;
dma_desc->length = cpu_to_le16(aligned_len - len);
dma_desc->addr = cpu_to_le32(IWL_SDIO_TRASH_BUF_REG);
trans_sdio->send_buf_idx = aligned_len;
memset(&trans_sdio->send_buf[len],
IWL_SDIO_CMD_PAD_BYTE, aligned_len - len);
}
sdio_claim_host(func);
ret = sdio_writesb(func, IWL_SDIO_DATA_ADDR, trans_sdio->send_buf,
trans_sdio->send_buf_idx);
if (ret) {
IWL_ERR(trans, "Cannot send buffer %d\n", ret);
set_bit(STATUS_TRANS_DEAD, &trans->status);
} else {
trans_sdio->send_buf_idx = 0;
}
sdio_release_host(func);
out:
return ret;
}
static int iwl_sdio_send_dtu(struct iwl_trans *trans,
struct iwl_slv_txq_entry *txq_entry,
u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio = IWL_TRANS_GET_SDIO_TRANS(trans);
struct iwl_sdio_dtu_info *dtu_info = txq_entry->reclaim_info;
u8 *cur, *dtu;
int i, send_len, ret;
int adma_desc_len;
adma_desc_len = sizeof(struct iwl_sdio_adma_desc) *
dtu_info->adma_desc_num;
send_len = txq_entry->dtu_meta.total_len +
dtu_info->data_pad_len +
sizeof(struct iwl_sdio_tx_dtu_hdr) +
adma_desc_len +
sizeof(struct iwl_sdio_tfd) +
sizeof(struct iwl_sdio_tx_dtu_trailer);
if (trans_sdio->send_buf_idx + send_len + PAD_DTU_LEN >
IWL_SDIO_SEND_BUF_LEN) {
IWL_DEBUG_TX(trans, "send buf too big. flushing\n");
ret = iwl_sdio_flush_dtus(trans);
if (ret)
return ret;
}
cur = &trans_sdio->send_buf[trans_sdio->send_buf_idx];
trans_sdio->send_buf_idx += send_len;
dtu = (u8 *)dtu_info->ctrl_buf;
memcpy(cur, dtu, sizeof(struct iwl_sdio_tx_dtu_hdr));
dtu += sizeof(struct iwl_sdio_tx_dtu_hdr);
cur += sizeof(struct iwl_sdio_tx_dtu_hdr);
memcpy(cur, dtu, adma_desc_len);
dtu += adma_desc_len;
cur += adma_desc_len;
memcpy(cur, dtu, sizeof(struct iwl_sdio_tfd));
dtu += sizeof(struct iwl_sdio_tfd);
cur += sizeof(struct iwl_sdio_tfd);
for (i = 0; i < txq_entry->dtu_meta.chunks_num; i++) {
memcpy(cur, txq_entry->dtu_meta.chunk_info[i].addr,
txq_entry->dtu_meta.chunk_info[i].len);
cur += txq_entry->dtu_meta.chunk_info[i].len;
}
memset(cur, 0xac, dtu_info->data_pad_len);
cur += dtu_info->data_pad_len;
memcpy(cur, dtu, sizeof(struct iwl_sdio_tx_dtu_trailer));
return 0;
}
/*
* send a single skb at a time.
* This is done in coherence with the QoS enablement logic.
*/
int iwl_sdio_process_dtu(struct iwl_trans_slv *trans_slv, u8 txq_id)
{
struct iwl_trans_sdio *trans_sdio =
IWL_TRANS_SLV_GET_SDIO_TRANS(trans_slv);
struct iwl_slv_txq_entry *txq_entry;
int ret;
txq_entry = iwl_slv_txq_pop_entry(trans_slv, txq_id);
if (!txq_entry)
return -ENOENT;
ret = iwl_sdio_alloc_dtu_mem(trans_sdio->trans, txq_entry, txq_id);
if (ret) {
iwl_slv_txq_pushback_entry(trans_slv, txq_id, txq_entry);
return ret;
}
iwl_sdio_build_dtu(trans_sdio->trans, txq_entry, txq_id);
ret = iwl_sdio_send_dtu(trans_sdio->trans, txq_entry, txq_id);
/* FIXME: what if there is a failure here ? */
#ifdef CPTCFG_MAC80211_LATENCY_MEASUREMENTS
iwl_slv_tx_lat_add_ts_write(trans_slv, txq_id, txq_entry);
#endif
iwl_slv_txq_add_to_sent(trans_slv, txq_id, txq_entry);
return ret;
}