/*
 * Copyright (c) 2013 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

#include "if_pci.h"

/*chaninfo*/
#define  CHANINFOMEM_S2_READ_MASK               0x00000008
#define  CHANINFO_CTRL_CAPTURE_CHAN_INFO_MASK   0x00000001
#define  CHANINFO_CTRL_CHANINFOMEM_BW_MASK      0x00000030
#define  MULTICHAIN_ENABLE_RX_CHAIN_MASK_MASK   0x00000007

/*agc*/
#define GAINS_MIN_OFFSETS_CF_AGC_HIST_ENABLE_MASK              0x00040000
#define GAINS_MIN_OFFSETS_CF_AGC_HIST_GC_MASK                  0x00080000
#define GAINS_MIN_OFFSETS_CF_AGC_HIST_VOTING_MASK              0x00100000
#define GAINS_MIN_OFFSETS_CF_AGC_HIST_PHY_ERR_MASK             0x00200000
#define  AGC_HISTORY_DUMP_MASK    GAINS_MIN_OFFSETS_CF_AGC_HIST_ENABLE_MASK| \
                                  GAINS_MIN_OFFSETS_CF_AGC_HIST_GC_MASK| \
                                  GAINS_MIN_OFFSETS_CF_AGC_HIST_VOTING_MASK| \
                                  GAINS_MIN_OFFSETS_CF_AGC_HIST_PHY_ERR_MASK

#define BB_chaninfo_ctrl         0x1a370
#define BB_multichain_enable     0x1a2a0
#define BB_chn_tables_intf_addr  0x19894
#define BB_chn1_tables_intf_addr 0x1a894
#define BB_chn_tables_intf_data  0x19898
#define BB_chn1_tables_intf_data 0x1a898
#define BB_gains_min_offsets     0x19e08
#define BB_chaninfo_tab_b0       0x03200
#define BB_chaninfo_tab_b1       0x03300
#define BB_watchdog_status       0x1a7c0
#define BB_watchdog_ctrl_1       0x1a7c4
#define BB_watchdog_ctrl_2       0x1a7c8
#define BB_watchdog_status_B     0x1a7e0

#define PHY_BB_CHN_TABLES_INTF_ADDR 0x19894
#define PHY_BB_CHN_TABLES_INTF_DATA 0x19898

#define PHY_BB_CHN1_TABLES_INTF_ADDR 0x1a894
#define PHY_BB_CHN1_TABLES_INTF_DATA 0x1a898


struct priv_ctrl_ctx {
    u_int32_t chaninfo_ctrl_orig;
    u_int32_t gain_min_offsets_orig;
    u_int32_t anyreg_start;
    u_int32_t anyreg_len;
};

static struct priv_ctrl_ctx g_Priv_Dump_Ctx;

static INLINE void set_target_reg_bits(struct hif_pci_softc* hif, u_int32_t reg, u_int32_t bitmask, u_int32_t val)
{
    u_int32_t value = A_PCI_READ32(hif->mem + (reg));
    u_int32_t shift = 0;
    value &= ~(bitmask);
    while (!((bitmask >> shift) & 0x01)) {
        shift++;
    }
    value |= (((val)<<shift)&(bitmask));
    A_PCI_WRITE32(hif->mem + (reg), value);
}

static INLINE u_int32_t get_target_reg_bits(struct hif_pci_softc* hif, u_int32_t reg, u_int32_t bitmask)
{
    u_int32_t value = A_PCI_READ32(hif->mem + (reg));
    u_int32_t shift = 0;
    while (!((bitmask >> shift) & 0x01)) {
        shift++;
    }
    return (value>>shift)&bitmask;
}

void priv_start_cap_chaninfo(struct hif_pci_softc* hif_sc)
{
    set_target_reg_bits(hif_sc, BB_chaninfo_ctrl, CHANINFO_CTRL_CAPTURE_CHAN_INFO_MASK, 1);
}

void priv_start_agc(struct hif_pci_softc* hif_sc)
{
    g_Priv_Dump_Ctx.gain_min_offsets_orig = A_PCI_READ32(hif_sc->mem + BB_gains_min_offsets);
    set_target_reg_bits(hif_sc, BB_gains_min_offsets, AGC_HISTORY_DUMP_MASK, 0x0f);
}

void priv_stop_agc(struct hif_pci_softc* hif_sc)
{
    set_target_reg_bits(hif_sc, BB_gains_min_offsets, AGC_HISTORY_DUMP_MASK, 0);
}

void priv_dump_chaninfo(struct hif_pci_softc* hif_sc)
{
    u_int32_t bw, val;
    u_int32_t len, i, tmp;
    u_int32_t chain_mask;
    u_int32_t chain0, chain1;

    chain_mask = get_target_reg_bits(hif_sc, BB_multichain_enable, MULTICHAIN_ENABLE_RX_CHAIN_MASK_MASK);
    chain0 = chain_mask & 1;
    chain1 = chain_mask & 2;

    printk("\nChannel info dump:\n");
    bw = get_target_reg_bits(hif_sc, BB_chaninfo_ctrl, CHANINFO_CTRL_CHANINFOMEM_BW_MASK);

    if (bw == 0) {
        len = 53;
    } else if (bw == 1) {
        len = 57;
    } else if (bw == 2) {
        len = 59*2-1;
    } else {
        len = 60*2+61*2;
    }

    /*
     * each tone is 16 bit valid, write to 32bit buffer each.
     * bw==0(legacy20): 53 tones.
     * bw==1(ht/vht20): 57 tones.
     * bw==2(ht/vht40): 59+58 tones.
     * bw==3(vht80): 60*2+61*2 tones.
     */

    if (chain0) {
        A_PCI_WRITE32(hif_sc->mem+BB_chn_tables_intf_addr, 0x80003200);
    }
    if (chain1) {
        A_PCI_WRITE32(hif_sc->mem+BB_chn1_tables_intf_addr, 0x80003200);
    }

    set_target_reg_bits(hif_sc, BB_chaninfo_ctrl, CHANINFOMEM_S2_READ_MASK, 0);

    if (chain0) {
        if (bw < 2) {
            len = (bw == 0) ? 53 : 57;
            for (i=0; i<len; i++) {
                val = A_PCI_READ32(hif_sc->mem+BB_chn_tables_intf_data) & 0x0000ffff;
                printk("0x%x\t",val);
                if (i%4 == 0)
                    printk("\n");
            }
        } else {
            len = (bw == 2) ? 59 : 60;
            for (i=0; i<len; i++) {
                tmp = A_PCI_READ32(hif_sc->mem+BB_chn_tables_intf_data);
                printk("0x%x\t", ((tmp>>16) & 0x0000ffff));
                printk("0x%x\t", (tmp & 0x0000ffff));
                if (i%2 == 0)
                    printk("\n");
            }
            if (bw > 2) {
                //bw == 3 for vht80
                A_PCI_WRITE32(hif_sc->mem+BB_chn_tables_intf_addr, 0x80003300);
                len = 61;
                for (i=0; i<len; i++) {
                    tmp = A_PCI_READ32(hif_sc->mem+BB_chn_tables_intf_data);
                    printk("0x%x\t", ((tmp>>16) & 0x0000ffff));
                    printk("0x%x\t", (tmp & 0x0000ffff));
                    if (i%2 == 0)
                        printk("\n");
                }
            }
        }
    }
    if (chain1) {
        if (bw < 2) {
            len = (bw == 0) ? 53 : 57;
            for (i=0; i<len; i++) {
                val = A_PCI_READ32(hif_sc->mem+BB_chn1_tables_intf_data) & 0x0000ffff;
                printk("0x%x\t",val);
                if (i%4 == 0)
                    printk("\n");
            }
        } else {
            len = (bw == 2) ? 59 : 60;
            for (i=0; i<len; i++) {
                tmp = A_PCI_READ32(hif_sc->mem+BB_chn1_tables_intf_data);
                printk("0x%x\n", (tmp>>16) & 0x0000ffff);
                printk("0x%x\n", tmp & 0x0000ffff);
                if (i%2 == 0)
                    printk("\n");
            }
            if (bw > 2) {
                //bw == 3 for vht80
                A_PCI_WRITE32(hif_sc->mem+BB_chn1_tables_intf_addr, 0x80003300);
                len = 61;
                for (i=0; i<len; i++) {
                    tmp = A_PCI_READ32(hif_sc->mem+BB_chn1_tables_intf_data);
                    printk("0x%x\t", ((tmp>>16) & 0x0000ffff));
                    printk("0x%x\t", (tmp & 0x0000ffff));
                    if (i%2 == 0)
                        printk("\n");
                }
            }
        }
    }
    printk("\nChannel info dump complete\n");
}

void priv_dump_agc(struct hif_pci_softc* hif_sc)
{
    int i, len = 30;  //check this value for Rome and Peregrine
    u_int32_t chain0, chain1, chain_mask, val;
    A_target_id_t targid = TARGID(hif_sc);

    A_TARGET_ACCESS_BEGIN(targid);

    chain_mask = get_target_reg_bits(hif_sc, BB_multichain_enable, MULTICHAIN_ENABLE_RX_CHAIN_MASK_MASK);
    chain0 = chain_mask & 1;
    chain1 = chain_mask & 2;

    len = len<<1;  //each agc item is 64bit, total*2
    priv_stop_agc(hif_sc);

    set_target_reg_bits(hif_sc, BB_chaninfo_ctrl, CHANINFOMEM_S2_READ_MASK, 0);

    printk("AGC history buffer dump:\n");
    if (chain0) {
        for (i = 0; i < len; i++) {
            A_PCI_WRITE32(hif_sc->mem + PHY_BB_CHN_TABLES_INTF_ADDR,
                  BB_chaninfo_tab_b0 + i*4);
            val = A_PCI_READ32(hif_sc->mem + PHY_BB_CHN_TABLES_INTF_DATA);
            printk("0x%x\t", val);
            if (i%4 == 0)
                printk("\n");
        }
    }
    if (chain1) {
        for (i = 0; i < len; i++) {
            A_PCI_WRITE32(hif_sc->mem + PHY_BB_CHN1_TABLES_INTF_ADDR,
                  BB_chaninfo_tab_b0 + i*4);
            val = A_PCI_READ32(hif_sc->mem + PHY_BB_CHN1_TABLES_INTF_DATA);
            printk("0x%x\t", val);
            if (i%4 == 0)
                printk("\n");
        }
    }
    printk("\nAGC history buffer dump complete\n");
    //restore original value
    A_PCI_WRITE32(hif_sc->mem + BB_gains_min_offsets, g_Priv_Dump_Ctx.gain_min_offsets_orig);

    A_TARGET_ACCESS_END(targid);

    return;
}

void priv_dump_bbwatchdog(struct hif_pci_softc* hif_sc)
{
    u_int32_t val;

    printk("BB watchdog dump\n");
    val = A_PCI_READ32(hif_sc->mem + BB_watchdog_status);
    printk("0x%x\t",val);
    val = A_PCI_READ32(hif_sc->mem + BB_watchdog_ctrl_1);
    printk("0x%x\t",val);
    val = A_PCI_READ32(hif_sc->mem + BB_watchdog_ctrl_2);
    printk("0x%x\t",val);
    val = A_PCI_READ32(hif_sc->mem + BB_watchdog_status_B);
    printk("0x%x\n",val);
}
