blob: c47e11427249535e12519ef9334551d59dd0f6b5 [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2005 - 2014 Intel Corporation. All rights reserved.
* 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 "src/iwlwifi/iwl-phy-db.h"
#include "src/iwlwifi/iwl-debug.h"
#include "src/iwlwifi/iwl-drv.h"
#include "src/iwlwifi/iwl-op-mode.h"
#include "src/iwlwifi/iwl-trans.h"
#include "src/iwlwifi/platform/compiler.h"
#define CHANNEL_NUM_SIZE 4 /* num of channels in calib_ch size */
#define PHY_DB_CMD 0x6c
/* for parsing of tx power channel group data that comes from the firmware*/
struct iwl_phy_db_chg_txp {
__le32 space;
__le16 max_channel_idx;
} __packed;
struct iwl_phy_db* iwl_phy_db_init(struct iwl_trans* trans) {
struct iwl_phy_db* phy_db = calloc(1, sizeof(struct iwl_phy_db));
if (!phy_db) {
return phy_db;
}
phy_db->trans = trans;
phy_db->n_group_txp = -1;
phy_db->n_group_papd = -1;
/* TODO: add default values of the phy db. */
return phy_db;
}
/*
* get phy db section: returns a pointer to a phy db section specified by
* type and channel group id.
*/
struct iwl_phy_db_entry* iwl_phy_db_get_section(struct iwl_phy_db* phy_db,
enum iwl_phy_db_section_type type,
uint16_t chg_id) {
if (!phy_db || type >= IWL_PHY_DB_MAX) {
return NULL;
}
switch (type) {
case IWL_PHY_DB_CFG:
return &phy_db->cfg;
case IWL_PHY_DB_CALIB_NCH:
return &phy_db->calib_nch;
case IWL_PHY_DB_CALIB_CHG_PAPD:
if (chg_id >= phy_db->n_group_papd) {
return NULL;
}
return &phy_db->calib_ch_group_papd[chg_id];
case IWL_PHY_DB_CALIB_CHG_TXP:
if (chg_id >= phy_db->n_group_txp) {
return NULL;
}
return &phy_db->calib_ch_group_txp[chg_id];
default:
return NULL;
}
return NULL;
}
static void iwl_phy_db_free_section(struct iwl_phy_db* phy_db, enum iwl_phy_db_section_type type,
uint16_t chg_id) {
struct iwl_phy_db_entry* entry = iwl_phy_db_get_section(phy_db, type, chg_id);
if (!entry) {
return;
}
kfree(entry->data);
entry->data = NULL;
entry->size = 0;
}
void iwl_phy_db_free(struct iwl_phy_db* phy_db) {
uint16_t i;
if (!phy_db) {
return;
}
iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CFG, 0);
iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_NCH, 0);
for (i = 0; i < phy_db->n_group_papd; i++) {
iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, i);
}
kfree(phy_db->calib_ch_group_papd);
for (i = 0; i < phy_db->n_group_txp; i++) {
iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, i);
}
kfree(phy_db->calib_ch_group_txp);
kfree(phy_db);
}
// Parse the notification packet from the firmware, then populate to mvm->phy_db.
//
zx_status_t iwl_phy_db_set_section(struct iwl_phy_db* phy_db, struct iwl_rx_packet* pkt) {
struct iwl_calib_res_notif_phy_db* phy_db_notif = (struct iwl_calib_res_notif_phy_db*)pkt->data;
enum iwl_phy_db_section_type type = le16_to_cpu(phy_db_notif->type);
uint16_t size = le16_to_cpu(phy_db_notif->length);
struct iwl_phy_db_entry* entry;
uint16_t chg_id = 0;
if (!phy_db) {
return ZX_ERR_INVALID_ARGS;
}
if (type == IWL_PHY_DB_CALIB_CHG_PAPD) {
chg_id = le16_to_cpup((__le16*)phy_db_notif->data);
if (!phy_db->calib_ch_group_papd) {
/*
* Firmware sends the largest index first, so we can use
* it to know how much we should allocate.
*/
phy_db->calib_ch_group_papd = calloc(chg_id + 1, sizeof(struct iwl_phy_db_entry));
if (!phy_db->calib_ch_group_papd) {
return ZX_ERR_NO_MEMORY;
}
phy_db->n_group_papd = chg_id + 1;
}
} else if (type == IWL_PHY_DB_CALIB_CHG_TXP) {
chg_id = le16_to_cpup((__le16*)phy_db_notif->data);
if (!phy_db->calib_ch_group_txp) {
/*
* Firmware sends the largest index first, so we can use
* it to know how much we should allocate.
*/
phy_db->calib_ch_group_txp = calloc(chg_id + 1, sizeof(struct iwl_phy_db_entry));
if (!phy_db->calib_ch_group_txp) {
return ZX_ERR_NO_MEMORY;
}
phy_db->n_group_txp = chg_id + 1;
}
}
entry = iwl_phy_db_get_section(phy_db, type, chg_id);
if (!entry) {
return ZX_ERR_INVALID_ARGS;
}
free(entry->data);
entry->data = kmemdup(phy_db_notif->data, size);
if (!entry->data) {
entry->size = 0;
return ZX_ERR_NO_MEMORY;
}
entry->size = size;
IWL_DEBUG_INFO(phy_db->trans, "%s(%d): [PHYDB]SET: Type %d , Size: %d\n", __func__, __LINE__,
type, size);
return ZX_OK;
}
static int is_valid_channel(uint16_t ch_id) {
if (ch_id <= 14 || (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) ||
(100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) ||
(145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) {
return 1;
}
return 0;
}
static uint8_t ch_id_to_ch_index(uint16_t ch_id) {
if (WARN_ON(!is_valid_channel(ch_id))) {
return 0xff;
}
if (ch_id <= 14) {
return (uint8_t)(ch_id - 1);
}
if (ch_id <= 64) {
return (uint8_t)((ch_id + 20) / 4);
}
if (ch_id <= 140) {
return (uint8_t)((ch_id - 12) / 4);
}
return (uint8_t)((ch_id - 13) / 4);
}
static uint16_t channel_id_to_papd(uint16_t ch_id) {
if (WARN_ON(!is_valid_channel(ch_id))) {
return 0xff;
}
if (1 <= ch_id && ch_id <= 14) {
return 0;
}
if (36 <= ch_id && ch_id <= 64) {
return 1;
}
if (100 <= ch_id && ch_id <= 140) {
return 2;
}
return 3;
}
static uint16_t channel_id_to_txp(struct iwl_phy_db* phy_db, uint16_t ch_id) {
struct iwl_phy_db_chg_txp* txp_chg;
uint16_t i;
uint8_t ch_index = ch_id_to_ch_index(ch_id);
if (ch_index == 0xff) {
return 0xff;
}
for (i = 0; i < phy_db->n_group_txp; i++) {
txp_chg = (void*)phy_db->calib_ch_group_txp[i].data;
if (!txp_chg) {
return 0xff;
}
/*
* Looking for the first channel group that its max channel is
* higher then wanted channel.
*/
if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) {
return i;
}
}
return 0xff;
}
static zx_status_t iwl_phy_db_get_section_data(struct iwl_phy_db* phy_db, uint32_t type,
uint8_t** data, uint16_t* size, uint16_t ch_id) {
struct iwl_phy_db_entry* entry;
uint16_t ch_group_id = 0;
if (!phy_db) {
return ZX_ERR_INVALID_ARGS;
}
/* find wanted channel group */
if (type == IWL_PHY_DB_CALIB_CHG_PAPD) {
ch_group_id = channel_id_to_papd(ch_id);
} else if (type == IWL_PHY_DB_CALIB_CHG_TXP) {
ch_group_id = channel_id_to_txp(phy_db, ch_id);
}
entry = iwl_phy_db_get_section(phy_db, type, ch_group_id);
if (!entry) {
return ZX_ERR_INVALID_ARGS;
}
*data = entry->data;
*size = entry->size;
IWL_DEBUG_INFO(phy_db->trans, "%s(%d): [PHYDB] GET: Type %d , Size: %d\n", __func__, __LINE__,
type, *size);
return ZX_OK;
}
static zx_status_t iwl_send_phy_db_cmd(struct iwl_phy_db* phy_db, uint16_t type, uint16_t length,
void* data) {
struct iwl_phy_db_cmd phy_db_cmd;
struct iwl_host_cmd cmd = {
.id = PHY_DB_CMD,
};
IWL_DEBUG_INFO(phy_db->trans, "Sending PHY-DB hcmd of type %d, of length %d\n", type, length);
/* Set phy db cmd variables */
phy_db_cmd.type = cpu_to_le16(type);
phy_db_cmd.length = cpu_to_le16(length);
/* Set hcmd variables */
cmd.data[0] = &phy_db_cmd;
cmd.len[0] = sizeof(struct iwl_phy_db_cmd);
cmd.data[1] = data;
cmd.len[1] = length;
cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY;
return iwl_trans_send_cmd(phy_db->trans, &cmd);
}
zx_status_t iwl_phy_db_send_all_channel_groups(struct iwl_phy_db* phy_db,
enum iwl_phy_db_section_type type,
int max_ch_groups) {
zx_status_t err;
struct iwl_phy_db_entry* entry;
/* Send all the channel specific groups to operational fw */
for (int i = 0; i < max_ch_groups; i++) {
entry = iwl_phy_db_get_section(phy_db, type, (uint16_t)i);
if (!entry) {
return ZX_ERR_INVALID_ARGS;
}
if (!entry->size) {
continue;
}
/* Send the requested PHY DB section */
err = iwl_send_phy_db_cmd(phy_db, (uint16_t)type, entry->size, entry->data);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Can't SEND phy_db section %d (%d), err %d\n", type, i, err);
return err;
}
IWL_DEBUG_INFO(phy_db->trans, "Sent PHY_DB HCMD, type = %d num = %d\n", type, i);
}
return ZX_OK;
}
zx_status_t iwl_send_phy_db_data(struct iwl_phy_db* phy_db) {
uint8_t* data = NULL;
uint16_t size = 0;
zx_status_t err;
IWL_DEBUG_INFO(phy_db->trans, "Sending phy db data and configuration to runtime image\n");
/* Send PHY DB CFG section */
err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CFG, &data, &size, 0);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot get Phy DB cfg section\n");
return err;
}
err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CFG, size, data);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot send HCMD of Phy DB cfg section\n");
return err;
}
err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CALIB_NCH, &data, &size, 0);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot get Phy DB non specific channel section\n");
return err;
}
err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CALIB_NCH, size, data);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot send HCMD of Phy DB non specific channel section\n");
return err;
}
/* Send all the TXP channel specific data */
err = iwl_phy_db_send_all_channel_groups(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, phy_db->n_group_papd);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot send channel specific PAPD groups\n");
return err;
}
/* Send all the TXP channel specific data */
err = iwl_phy_db_send_all_channel_groups(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, phy_db->n_group_txp);
if (err != ZX_OK) {
IWL_ERR(phy_db->trans, "Cannot send channel specific TX power groups\n");
return err;
}
IWL_DEBUG_INFO(phy_db->trans, "Finished sending phy db non channel data\n");
return ZX_OK;
}