/*
|
* Copyright (c) 2015 South Silicon Valley Microelectronics Inc.
|
* Copyright (c) 2015 iComm Corporation
|
*
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
*/
|
|
#include <linux/irq.h>
|
#include <linux/module.h>
|
#include <linux/vmalloc.h>
|
#include <linux/platform_device.h>
|
#include <linux/mmc/sdio.h>
|
#include <linux/mmc/sdio_func.h>
|
#include <linux/mmc/sdio_ids.h>
|
#include <linux/mmc/card.h>
|
#include <linux/mmc/host.h>
|
#include "sdio_def.h"
|
#include <linux/pm_runtime.h>
|
#include <linux/version.h>
|
#include <linux/firmware.h>
|
#include <linux/reboot.h>
|
#ifdef CONFIG_FW_ALIGNMENT_CHECK
|
#include <linux/skbuff.h>
|
#endif
|
#define SDIO_USE_SLOW_CLOCK
|
#define LOW_SPEED_SDIO_CLOCK (25000000)
|
#define HIGH_SPEED_SDIO_CLOCK (50000000)
|
static struct ssv6xxx_platform_data wlan_data;
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
|
#include <linux/printk.h>
|
#else
|
#include <linux/kernel.h>
|
#endif
|
#include <ssv6200.h>
|
#define MAX_RX_FRAME_SIZE 0x900
|
#define SSV_VENDOR_ID 0x3030
|
#define SSV_CABRIO_DEVID 0x3030
|
#define ENABLE_FW_SELF_CHECK 1
|
#define FW_BLOCK_SIZE 0x8000
|
#define CHECKSUM_BLOCK_SIZE 1024
|
#define FW_CHECKSUM_INIT (0x12345678)
|
#define FW_STATUS_REG ADR_TX_SEG
|
#define FW_STATUS_MASK (0x00FF0000)
|
#ifdef CONFIG_PM
|
static int ssv6xxx_sdio_trigger_pmu(struct device *dev);
|
static void ssv6xxx_sdio_reset(struct device *child);
|
#else
|
static void ssv6xxx_sdio_reset(struct device *child) { ; }
|
#endif
|
static void ssv6xxx_high_sdio_clk(struct sdio_func *func);
|
static void ssv6xxx_low_sdio_clk(struct sdio_func *func);
|
extern void *ssv6xxx_ifdebug_info[];
|
extern int ssv_devicetype;
|
extern void ssv6xxx_deinit_prepare(void);
|
static int ssv6xxx_sdio_status = 0;
|
u32 sdio_sr_bhvr = SUSPEND_RESUME_0;
|
EXPORT_SYMBOL(sdio_sr_bhvr);
|
|
static DEFINE_MUTEX(reboot_lock);
|
u32 shutdown_flags = SSV_SYS_REBOOT;
|
|
struct ssv6xxx_sdio_glue
|
{
|
struct device *dev;
|
struct platform_device *core;
|
#ifdef CONFIG_FW_ALIGNMENT_CHECK
|
struct sk_buff *dmaSkb;
|
#endif
|
#ifdef CONFIG_PM
|
struct sk_buff *cmd_skb;
|
#endif
|
unsigned int dataIOPort;
|
unsigned int regIOPort;
|
irq_handler_t irq_handler;
|
void *irq_dev;
|
bool dev_ready;
|
};
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
static const struct sdio_device_id ssv6xxx_sdio_devices[] __devinitconst =
|
#else
|
static const struct sdio_device_id ssv6xxx_sdio_devices[] =
|
#endif
|
{
|
{ SDIO_DEVICE(SSV_VENDOR_ID, SSV_CABRIO_DEVID) },
|
{}
|
};
|
MODULE_DEVICE_TABLE(sdio, ssv6xxx_sdio_devices);
|
static bool ssv6xxx_is_ready (struct device *child)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return false;
|
return glue->dev_ready;
|
}
|
static int ssv6xxx_sdio_cmd52_read(struct device *child, u32 addr,
|
u32 *value)
|
{
|
int ret = -1;
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
*value = sdio_readb(func, addr, &ret);
|
sdio_release_host(func);
|
}
|
return ret;
|
}
|
static int ssv6xxx_sdio_cmd52_write(struct device *child, u32 addr,
|
u32 value)
|
{
|
int ret = -1;
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
sdio_writeb(func, value, addr, &ret);
|
sdio_release_host(func);
|
}
|
return ret;
|
}
|
static int __must_check ssv6xxx_sdio_read_reg(struct device *child, u32 addr,
|
u32 *buf)
|
{
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func ;
|
u8 data[4];
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
data[0] = (addr >> ( 0 )) &0xff;
|
data[1] = (addr >> ( 8 )) &0xff;
|
data[2] = (addr >> ( 16 )) &0xff;
|
data[3] = (addr >> ( 24 )) &0xff;
|
ret = sdio_memcpy_toio(func, glue->regIOPort, data, 4);
|
if (WARN_ON(ret))
|
{
|
dev_err(child->parent, "sdio read reg write address failed (%d)\n", ret);
|
goto io_err;
|
}
|
ret = sdio_memcpy_fromio(func, data, glue->regIOPort, 4);
|
if (WARN_ON(ret))
|
{
|
dev_err(child->parent, "sdio read reg from I/O failed (%d)\n",ret);
|
goto io_err;
|
}
|
if(ret == 0)
|
{
|
*buf = (data[0]&0xff);
|
*buf = *buf | ((data[1]&0xff)<<( 8 ));
|
*buf = *buf | ((data[2]&0xff)<<( 16 ));
|
*buf = *buf | ((data[3]&0xff)<<( 24 ));
|
}
|
else
|
*buf = 0xffffffff;
|
io_err:
|
sdio_release_host(func);
|
}
|
else
|
{
|
dev_err(child->parent, "sdio read reg glue == NULL!!!\n");
|
}
|
return ret;
|
}
|
#ifdef ENABLE_WAKE_IO_ISR_WHEN_HCI_ENQUEUE
|
static int ssv6xxx_sdio_trigger_tx_rx (struct device *child)
|
{
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
struct mmc_host *host;
|
if (glue == NULL)
|
return ret;
|
func = dev_to_sdio_func(glue->dev);
|
host = func->card->host;
|
mmc_signal_sdio_irq(host);
|
return 0;
|
}
|
#endif
|
static int __must_check ssv6xxx_sdio_write_reg(struct device *child, u32 addr,
|
u32 buf)
|
{
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
u8 data[8];
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
dev_dbg(child->parent, "sdio write reg addr 0x%x, 0x%x\n",addr, buf);
|
sdio_claim_host(func);
|
data[0] = (addr >> ( 0 )) &0xff;
|
data[1] = (addr >> ( 8 )) &0xff;
|
data[2] = (addr >> ( 16 )) &0xff;
|
data[3] = (addr >> ( 24 )) &0xff;
|
data[4] = (buf >> ( 0 )) &0xff;
|
data[5] = (buf >> ( 8 )) &0xff;
|
data[6] = (buf >> ( 16 )) &0xff;
|
data[7] = (buf >> ( 24 )) &0xff;
|
ret = sdio_memcpy_toio(func, glue->regIOPort, data, 8);
|
sdio_release_host(func);
|
#ifdef __x86_64
|
udelay(50);
|
#endif
|
}
|
else
|
{
|
dev_err(child->parent, "sdio write reg glue == NULL!!!\n");
|
}
|
return ret;
|
}
|
static int ssv6xxx_sdio_write_sram(struct device *child, u32 addr, u8 *data, u32 size)
|
{
|
int ret = -1;
|
struct ssv6xxx_sdio_glue *glue;
|
struct sdio_func *func=NULL;
|
glue = dev_get_drvdata(child->parent);
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
do {
|
if (ssv6xxx_sdio_write_reg(child,0xc0000860,addr)) ;
|
sdio_writeb(func, 0x2, REG_Fn1_STATUS, &ret);
|
if (unlikely(ret)) break;
|
ret = sdio_memcpy_toio(func, glue->dataIOPort, data, size);
|
if (unlikely(ret)) return ret;
|
sdio_writeb(func, 0, REG_Fn1_STATUS, &ret);
|
if (unlikely(ret)) return ret;
|
} while (0);
|
sdio_release_host(func);
|
return ret;
|
}
|
void * ssv6xxx_open_firmware(char *user_mainfw)
|
{
|
struct file *fp;
|
fp = filp_open(user_mainfw, O_RDONLY, 0);
|
if (IS_ERR(fp))
|
{
|
printk("ssv6xxx_open_firmware failed!!\n");
|
fp = NULL;
|
}
|
return fp;
|
}
|
int ssv6xxx_read_fw_block(char *buf, int len, void *image)
|
{
|
struct file *fp = (struct file *)image;
|
int rdlen;
|
if (!image)
|
return 0;
|
rdlen = kernel_read(fp, fp->f_pos, buf, len);
|
if (rdlen > 0)
|
fp->f_pos += rdlen;
|
return rdlen;
|
}
|
void ssv6xxx_close_firmware(void *image)
|
{
|
if (image)
|
filp_close((struct file *)image, NULL);
|
}
|
static int ssv6xxx_sdio_load_firmware_openfile(struct device *child, u8 *firmware_name)
|
{
|
int ret = 0;
|
struct ssv6xxx_sdio_glue *glue;
|
u8 *fw_buffer = NULL;
|
u32 sram_addr = 0x00000000;
|
u32 block_count = 0;
|
u32 res_size=0,len=0,tolen=0;
|
void *fw_fp=NULL;
|
#ifdef ENABLE_FW_SELF_CHECK
|
u32 checksum = FW_CHECKSUM_INIT;
|
u32 fw_checksum,fw_clkcnt;
|
u32 retry_count = 3;
|
u32 *fw_data32;
|
#else
|
int writesize=0;
|
u32 retry_count = 1;
|
#endif
|
u32 word_count,i;
|
u32 j,jblk;
|
#ifndef SDIO_USE_SLOW_CLOCK
|
struct sdio_func *func=NULL;
|
struct mmc_card *card = NULL;
|
struct mmc_host *host = NULL;
|
#endif
|
glue = dev_get_drvdata(child->parent);
|
if ( (wlan_data.is_enabled != false)
|
|| (glue != NULL)
|
|| (glue->dev_ready != false))
|
{
|
#ifndef SDIO_USE_SLOW_CLOCK
|
func = dev_to_sdio_func(glue->dev);
|
card = func->card;
|
host = card->host;
|
#endif
|
fw_fp = ssv6xxx_open_firmware(firmware_name);
|
if (!fw_fp) {
|
printk("failed to find firmware (%s)\n", firmware_name);
|
ret = -1;
|
goto out;
|
}
|
fw_buffer = (u8 *)kzalloc(FW_BLOCK_SIZE, GFP_KERNEL);
|
if (fw_buffer == NULL) {
|
printk("Failed to allocate buffer for firmware.\n");
|
goto out;
|
}
|
do {
|
u32 clk_en;
|
if(1){
|
if(ssv6xxx_sdio_write_reg(child, ADR_BRG_SW_RST, 0x0));
|
if(ssv6xxx_sdio_write_reg(child, ADR_BOOT, 0x01));
|
if(ssv6xxx_sdio_read_reg(child, ADR_PLATFORM_CLOCK_ENABLE, &clk_en));
|
if(ssv6xxx_sdio_write_reg(child, ADR_PLATFORM_CLOCK_ENABLE, clk_en | (1 << 2)));
|
}
|
printk("Writing firmware to SSV6XXX...\n");
|
memset(fw_buffer, 0xA5, FW_BLOCK_SIZE);
|
while ((len = ssv6xxx_read_fw_block((char*)fw_buffer, FW_BLOCK_SIZE, fw_fp))) {
|
tolen += len;
|
if(len < FW_BLOCK_SIZE){
|
res_size = len;
|
break;
|
}
|
if(0)
|
{
|
jblk = len / 128;
|
for(j=0;j<jblk;j++)
|
{
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)(fw_buffer+j*128), 128);
|
if (ret){
|
printk("ssv6xxx_sdio_write_sram failed!!\n");
|
break;
|
}
|
sram_addr += 128;
|
}
|
}else{
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, FW_BLOCK_SIZE);
|
if (ret)
|
break;
|
sram_addr += FW_BLOCK_SIZE;
|
}
|
word_count = (len / sizeof(u32));
|
fw_data32 = (u32 *)fw_buffer;
|
for (i = 0; i < word_count; i++){
|
checksum += fw_data32[i];
|
}
|
memset(fw_buffer, 0xA5, FW_BLOCK_SIZE);
|
}
|
if(res_size)
|
{
|
u32 cks_blk_cnt,cks_blk_res;
|
cks_blk_cnt = res_size / CHECKSUM_BLOCK_SIZE;
|
cks_blk_res = res_size % CHECKSUM_BLOCK_SIZE;
|
if(0)
|
{
|
jblk = ((cks_blk_cnt+1)*CHECKSUM_BLOCK_SIZE)/128;
|
for(j=0;j<jblk;j++){
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)(fw_buffer+j*128), 128);
|
sram_addr += 128;
|
}
|
}else{
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, (cks_blk_cnt+1)*CHECKSUM_BLOCK_SIZE);
|
}
|
word_count = (cks_blk_cnt * CHECKSUM_BLOCK_SIZE / sizeof(u32));
|
fw_data32 = (u32 *)fw_buffer;
|
for (i = 0; i < word_count; i++)
|
checksum += *fw_data32++;
|
if(cks_blk_res)
|
{
|
word_count = (CHECKSUM_BLOCK_SIZE / sizeof(u32));
|
for (i = 0; i < word_count; i++) {
|
checksum += *fw_data32++;
|
}
|
}
|
}
|
checksum = ((checksum >> 24) + (checksum >> 16) + (checksum >> 8) + checksum) & 0x0FF;
|
checksum <<= 16;
|
if (ret == 0) {
|
block_count = tolen / CHECKSUM_BLOCK_SIZE;
|
res_size = tolen % CHECKSUM_BLOCK_SIZE;
|
if(res_size)
|
block_count++;
|
if(ssv6xxx_sdio_write_reg(child, FW_STATUS_REG, (block_count << 16)));
|
if(ssv6xxx_sdio_read_reg(child, FW_STATUS_REG, &fw_clkcnt));
|
printk("(block_count << 16) = %x,reg =%x\n",(block_count << 16),fw_clkcnt);
|
if(ssv6xxx_sdio_write_reg(child, ADR_BRG_SW_RST, 0x1));
|
printk("Firmware \"%s\" loaded\n", firmware_name);
|
msleep(50);
|
if(ssv6xxx_sdio_read_reg(child, FW_STATUS_REG, &fw_checksum));
|
fw_checksum = fw_checksum & FW_STATUS_MASK;
|
if (fw_checksum == checksum) {
|
if(ssv6xxx_sdio_write_reg(child, FW_STATUS_REG, (~checksum & FW_STATUS_MASK)));
|
ret = 0;
|
printk("Firmware check OK.%04x = %04x\n", fw_checksum, checksum);
|
break;
|
} else {
|
printk("FW checksum error: %04x != %04x\n", fw_checksum, checksum);
|
ret = -1;
|
}
|
} else {
|
printk("Firmware \"%s\" download failed. (%d)\n", firmware_name, ret);
|
ret = -1;
|
}
|
} while (--retry_count);
|
if (ret)
|
goto out;
|
ret = 0;
|
}
|
out:
|
if(fw_fp)
|
ssv6xxx_close_firmware(fw_fp);
|
if (fw_buffer != NULL)
|
kfree(fw_buffer);
|
msleep(50);
|
return ret;
|
}
|
int ssv6xxx_get_firmware(struct device *dev,
|
char *user_mainfw,
|
const struct firmware **mainfw)
|
{
|
int ret;
|
BUG_ON(mainfw == NULL);
|
if (*user_mainfw) {
|
ret = request_firmware(mainfw, user_mainfw, dev);
|
if (ret) {
|
dev_err(dev, "couldn't find main firmware %s\n",user_mainfw);
|
goto fail;
|
}
|
if (*mainfw)
|
return 0;
|
}
|
fail:
|
if (*mainfw) {
|
release_firmware(*mainfw);
|
*mainfw = NULL;
|
}
|
return -ENOENT;
|
}
|
static int ssv6xxx_sdio_load_firmware_request(struct device *child ,u8 *firmware_name)
|
{
|
int ret = 0;
|
const struct firmware *ssv6xxx_fw = NULL;
|
struct ssv6xxx_sdio_glue *glue;
|
u8 *fw_buffer = NULL;
|
u32 sram_addr = 0x00000000;
|
u32 block_count = 0;
|
u32 block_idx = 0;
|
u32 res_size;
|
u8 *fw_data;
|
#ifdef ENABLE_FW_SELF_CHECK
|
u32 checksum = FW_CHECKSUM_INIT;
|
u32 fw_checksum;
|
u32 retry_count = 3;
|
u32 *fw_data32;
|
#else
|
int writesize=0;
|
u32 retry_count = 1;
|
#endif
|
#ifndef SDIO_USE_SLOW_CLOCK
|
struct sdio_func *func=NULL;
|
struct mmc_card *card = NULL;
|
struct mmc_host *host = NULL;
|
#endif
|
glue = dev_get_drvdata(child->parent);
|
if ( (wlan_data.is_enabled != false)
|
|| (glue != NULL)
|
|| (glue->dev_ready != false))
|
{
|
#ifndef SDIO_USE_SLOW_CLOCK
|
func = dev_to_sdio_func(glue->dev);
|
card = func->card;
|
host = card->host;
|
#endif
|
ret = ssv6xxx_get_firmware(glue->dev, firmware_name, &ssv6xxx_fw);
|
if (ret) {
|
pr_err("failed to find firmware (%d)\n", ret);
|
goto out;
|
}
|
fw_buffer = (u8 *)kzalloc(FW_BLOCK_SIZE, GFP_KERNEL);
|
if (fw_buffer == NULL) {
|
pr_err("Failed to allocate buffer for firmware.\n");
|
goto out;
|
}
|
#ifdef ENABLE_FW_SELF_CHECK
|
block_count = ssv6xxx_fw->size / CHECKSUM_BLOCK_SIZE;
|
res_size = ssv6xxx_fw->size % CHECKSUM_BLOCK_SIZE;
|
{
|
int word_count = (int)(block_count * CHECKSUM_BLOCK_SIZE / sizeof(u32));
|
int i;
|
fw_data32 = (u32 *)ssv6xxx_fw->data;
|
for (i = 0; i < word_count; i++)
|
checksum += fw_data32[i];
|
if(res_size)
|
{
|
memset(fw_buffer, 0xA5, CHECKSUM_BLOCK_SIZE);
|
memcpy(fw_buffer, &ssv6xxx_fw->data[block_count * CHECKSUM_BLOCK_SIZE], res_size);
|
word_count = (int)(CHECKSUM_BLOCK_SIZE / sizeof(u32));
|
fw_data32 = (u32 *)fw_buffer;
|
for (i = 0; i < word_count; i++) {
|
checksum += fw_data32[i];
|
}
|
}
|
}
|
checksum = ((checksum >> 24) + (checksum >> 16) + (checksum >> 8) + checksum) & 0x0FF;
|
checksum <<= 16;
|
#endif
|
do {
|
u32 clk_en;
|
if(ssv6xxx_sdio_write_reg(child, ADR_BRG_SW_RST, 0x0));
|
if(ssv6xxx_sdio_write_reg(child, ADR_BOOT, 0x01));
|
if(ssv6xxx_sdio_read_reg(child, ADR_PLATFORM_CLOCK_ENABLE, &clk_en));
|
if(ssv6xxx_sdio_write_reg(child, ADR_PLATFORM_CLOCK_ENABLE, clk_en | (1 << 2)));
|
#ifdef ENABLE_FW_SELF_CHECK
|
block_count = ssv6xxx_fw->size / FW_BLOCK_SIZE;
|
res_size = ssv6xxx_fw->size % FW_BLOCK_SIZE;
|
printk("Writing %d blocks to SSV6XXX...", block_count);
|
for (block_idx = 0, fw_data = (u8 *)ssv6xxx_fw->data, sram_addr = 0;block_idx < block_count;
|
block_idx++, fw_data += FW_BLOCK_SIZE, sram_addr += FW_BLOCK_SIZE) {
|
memcpy(fw_buffer, fw_data, FW_BLOCK_SIZE);
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, FW_BLOCK_SIZE);
|
if (ret)
|
break;
|
}
|
if(res_size)
|
{
|
memset(fw_buffer, 0xA5, FW_BLOCK_SIZE);
|
memcpy(fw_buffer, &ssv6xxx_fw->data[block_count * FW_BLOCK_SIZE], res_size);
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, ((res_size/CHECKSUM_BLOCK_SIZE)+1)*CHECKSUM_BLOCK_SIZE);
|
}
|
#else
|
block_count = ssv6xxx_fw->size / FW_BLOCK_SIZE;
|
res_size = ssv6xxx_fw->size % FW_BLOCK_SIZE;
|
writesize = sdio_align_size(func,res_size);
|
printk("Writing %d blocks to SSV6XXX...", block_count);
|
for (block_idx = 0, fw_data = (u8 *)ssv6xxx_fw->data, sram_addr = 0;block_idx < block_count;
|
block_idx++, fw_data += FW_BLOCK_SIZE, sram_addr += FW_BLOCK_SIZE) {
|
memcpy(fw_buffer, fw_data, FW_BLOCK_SIZE);
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, FW_BLOCK_SIZE);
|
if (ret)
|
break;
|
}
|
if(res_size)
|
{
|
memcpy(fw_buffer, &ssv6xxx_fw->data[block_count * FW_BLOCK_SIZE], res_size);
|
ret = ssv6xxx_sdio_write_sram(child, sram_addr, (u8 *)fw_buffer, writesize);
|
}
|
#endif
|
if (ret == 0) {
|
#ifdef ENABLE_FW_SELF_CHECK
|
block_count = ssv6xxx_fw->size / CHECKSUM_BLOCK_SIZE;
|
res_size = ssv6xxx_fw->size % CHECKSUM_BLOCK_SIZE;
|
if(res_size)
|
block_count++;
|
if(ssv6xxx_sdio_write_reg(child, FW_STATUS_REG, (block_count << 16)));
|
#endif
|
if(ssv6xxx_sdio_write_reg(child, ADR_BRG_SW_RST, 0x1));
|
printk("Firmware \"%s\" loaded\n", firmware_name);
|
#ifdef ENABLE_FW_SELF_CHECK
|
msleep(50);
|
if(ssv6xxx_sdio_read_reg(child, FW_STATUS_REG, &fw_checksum));
|
fw_checksum = fw_checksum & FW_STATUS_MASK;
|
if (fw_checksum == checksum) {
|
if(ssv6xxx_sdio_write_reg(child, FW_STATUS_REG, (~checksum & FW_STATUS_MASK)));
|
ret = 0;
|
printk("Firmware check OK.\n");
|
break;
|
} else {
|
printk("FW checksum error: %04x != %04x\n", fw_checksum, checksum);
|
ret = -1;
|
}
|
#endif
|
} else {
|
printk("Firmware \"%s\" download failed. (%d)\n", firmware_name, ret);
|
ret = -1;
|
}
|
} while (--retry_count);
|
if (ret)
|
goto out;
|
ret = 0;
|
}
|
out:
|
if (ssv6xxx_fw)
|
release_firmware(ssv6xxx_fw);
|
if (fw_buffer != NULL)
|
kfree(fw_buffer);
|
msleep(50);
|
return ret;
|
}
|
static int ssv6xxx_sdio_load_firmware(struct device *child ,u8 *firmware_name, u8 openfile)
|
{
|
int ret = -1;
|
struct ssv6xxx_sdio_glue *glue;
|
struct sdio_func *func;
|
glue = dev_get_drvdata(child->parent);
|
if(openfile)
|
ret = ssv6xxx_sdio_load_firmware_openfile(child,firmware_name);
|
else
|
ret = ssv6xxx_sdio_load_firmware_request(child,firmware_name);
|
if(glue != NULL)
|
{
|
func = dev_to_sdio_func(glue->dev);
|
ssv6xxx_high_sdio_clk(func);
|
}
|
return ret;
|
}
|
static int ssv6xxx_sdio_irq_getstatus(struct device *child,int *status)
|
{
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue;
|
struct sdio_func *func;
|
glue = dev_get_drvdata(child->parent);
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
*status = sdio_readb(func, REG_INT_STATUS, &ret);
|
sdio_release_host(func);
|
}
|
return ret;
|
}
|
#if 0
|
static void _sdio_hexdump(const u8 *buf,
|
size_t len)
|
{
|
size_t i;
|
printk("\n-----------------------------\n");
|
printk("hexdump(len=%lu):\n", (unsigned long) len);
|
{
|
for (i = 0; i < len; i++){
|
printk(" %02x", buf[i]);
|
if((i+1)%40 ==0)
|
printk("\n");
|
}
|
}
|
printk("\n-----------------------------\n");
|
}
|
#endif
|
static int __must_check ssv6xxx_sdio_read(struct device *child,
|
void *buf, size_t *size)
|
{
|
int ret = (-1), readsize = 0;
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func ;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
*size = (uint)sdio_readb(func, REG_CARD_PKT_LEN_0, &ret);
|
if (ret)
|
dev_err(child->parent, "sdio read hight len failed ret[%d]\n",ret);
|
if (ret == 0)
|
{
|
*size = *size | ((uint)sdio_readb(func, REG_CARD_PKT_LEN_1, &ret)<<0x8);
|
if (ret)
|
dev_err(child->parent, "sdio read low len failed ret[%d]\n",ret);
|
}
|
if (ret == 0)
|
{
|
readsize = sdio_align_size(func,*size);
|
ret = sdio_memcpy_fromio(func, buf, glue->dataIOPort, readsize);
|
if (ret)
|
dev_err(child->parent, "sdio read failed size ret[%d]\n",ret);
|
}
|
sdio_release_host(func);
|
}
|
#if 0
|
if(*size > 1500)
|
_sdio_hexdump(buf,*size);
|
#endif
|
return ret;
|
}
|
static int __must_check ssv6xxx_sdio_write(struct device *child,
|
void *buf, size_t len,u8 queue_num)
|
{
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
int writesize;
|
void *tempPointer;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
#ifdef CONFIG_FW_ALIGNMENT_CHECK
|
#ifdef CONFIG_ARM64
|
if (((u64)buf) & 3) {
|
#else
|
if (((u32)buf) & 3) {
|
#endif
|
memcpy(glue->dmaSkb->data,buf,len);
|
tempPointer = glue->dmaSkb->data;
|
}
|
else
|
#endif
|
tempPointer = buf;
|
#if 0
|
if(len > 1500)
|
_sdio_hexdump(buf,len);
|
#endif
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
writesize = sdio_align_size(func,len);
|
do
|
{
|
ret = sdio_memcpy_toio(func, glue->dataIOPort, tempPointer, writesize);
|
if ( ret == -EILSEQ || ret == -ETIMEDOUT )
|
{
|
ret = -1;
|
break;
|
}
|
else
|
{
|
if(ret)
|
dev_err(glue->dev,"Unexpected return value ret=[%d]\n",ret);
|
}
|
}
|
while( ret == -EILSEQ || ret == -ETIMEDOUT);
|
sdio_release_host(func);
|
if (ret)
|
dev_err(glue->dev, "sdio write failed (%d)\n", ret);
|
}
|
return ret;
|
}
|
static void ssv6xxx_sdio_irq_handler(struct sdio_func *func)
|
{
|
int status;
|
struct ssv6xxx_sdio_glue *glue = sdio_get_drvdata(func);
|
struct ssv6xxx_platform_data *pwlan_data = &wlan_data;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return;
|
if ( glue != NULL && glue->irq_handler != NULL )
|
{
|
atomic_set(&pwlan_data->irq_handling, 1);
|
sdio_release_host(func);
|
if ( glue->irq_handler != NULL )
|
status = glue->irq_handler(0,glue->irq_dev);
|
sdio_claim_host(func);
|
atomic_set(&pwlan_data->irq_handling, 0);
|
}
|
}
|
static void ssv6xxx_sdio_irq_setmask(struct device *child,int mask)
|
{
|
int err_ret;
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
sdio_writeb(func,mask, REG_INT_MASK, &err_ret);
|
sdio_release_host(func);
|
}
|
}
|
static void ssv6xxx_sdio_irq_trigger(struct device *child)
|
{
|
int err_ret;
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
sdio_writeb(func,0x2, REG_INT_TRIGGER, &err_ret);
|
sdio_release_host(func);
|
}
|
}
|
static int ssv6xxx_sdio_irq_getmask(struct device *child, u32 *mask)
|
{
|
u8 imask = 0;
|
int ret = (-1);
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return ret;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
imask = sdio_readb(func,REG_INT_MASK, &ret);
|
*mask = imask;
|
sdio_release_host(func);
|
}
|
return ret;
|
}
|
static void ssv6xxx_sdio_irq_enable(struct device *child)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
int ret;
|
struct ssv6xxx_platform_data *pwlan_data = &wlan_data;
|
if ( (pwlan_data->is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
sdio_claim_host(func);
|
ret = sdio_claim_irq(func, ssv6xxx_sdio_irq_handler);
|
if (ret)
|
dev_err(child->parent, "Failed to claim sdio irq: %d\n", ret);
|
sdio_release_host(func);
|
}
|
|
printk("ssv6xxx_sdio_irq_enable\n");
|
}
|
static void ssv6xxx_sdio_irq_disable(struct device *child,bool iswaitirq)
|
{
|
struct ssv6xxx_sdio_glue *glue = NULL;
|
struct sdio_func *func;
|
struct ssv6xxx_platform_data *pwlan_data = &wlan_data;
|
int ret;
|
printk("ssv6xxx_sdio_irq_disable\n");
|
if ( (wlan_data.is_enabled == false)
|
|| (child->parent == NULL))
|
return;
|
glue = dev_get_drvdata(child->parent);
|
if ( (glue == NULL)
|
|| (glue->dev_ready == false)
|
|| (glue->dev == NULL))
|
return;
|
{
|
func = dev_to_sdio_func(glue->dev);
|
if(func == NULL){
|
printk("func == NULL\n");
|
return;
|
}
|
sdio_claim_host(func);
|
while(atomic_read(&pwlan_data->irq_handling)){
|
sdio_release_host(func);
|
schedule_timeout(HZ / 10);
|
sdio_claim_host(func);
|
}
|
ret = sdio_release_irq(func);
|
if (ret)
|
dev_err(child->parent, "Failed to release sdio irq: %d\n", ret);
|
sdio_release_host(func);
|
}
|
}
|
static void ssv6xxx_sdio_irq_request(struct device *child,irq_handler_t irq_handler,void *irq_dev)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
bool isIrqEn = false;
|
if ( (wlan_data.is_enabled == false)
|
|| (glue == NULL)
|
|| (glue->dev_ready == false))
|
return;
|
if ( glue != NULL )
|
{
|
func = dev_to_sdio_func(glue->dev);
|
glue->irq_handler = irq_handler;
|
glue->irq_dev = irq_dev;
|
if (isIrqEn )
|
{
|
ssv6xxx_sdio_irq_enable(child);
|
}
|
}
|
}
|
static void ssv6xxx_sdio_read_parameter(struct sdio_func *func,
|
struct ssv6xxx_sdio_glue *glue)
|
{
|
int err_ret;
|
sdio_claim_host(func);
|
glue->dataIOPort = 0;
|
glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_0, &err_ret) << ( 8*0 ));
|
glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_1, &err_ret) << ( 8*1 ));
|
glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_2, &err_ret) << ( 8*2 ));
|
glue->regIOPort = 0;
|
glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_0, &err_ret) << ( 8*0 ));
|
glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_1, &err_ret) << ( 8*1 ));
|
glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_2, &err_ret) << ( 8*2 ));
|
dev_err(&func->dev, "dataIOPort 0x%x regIOPort 0x%x\n",glue->dataIOPort,glue->regIOPort);
|
#ifdef CONFIG_PLATFORM_SDIO_BLOCK_SIZE
|
err_ret = sdio_set_block_size(func,CONFIG_PLATFORM_SDIO_BLOCK_SIZE);
|
#else
|
err_ret = sdio_set_block_size(func,SDIO_DEF_BLOCK_SIZE);
|
#endif
|
if (err_ret != 0) {
|
printk("SDIO setting SDIO_DEF_BLOCK_SIZE fail!!\n");
|
}
|
#ifdef CONFIG_PLATFORM_SDIO_OUTPUT_TIMING
|
sdio_writeb(func, CONFIG_PLATFORM_SDIO_OUTPUT_TIMING,REG_OUTPUT_TIMING_REG, &err_ret);
|
#else
|
sdio_writeb(func, SDIO_DEF_OUTPUT_TIMING,REG_OUTPUT_TIMING_REG, &err_ret);
|
#endif
|
sdio_writeb(func, 0x00,REG_Fn1_STATUS, &err_ret);
|
#if 0
|
sdio_writeb(func,SDIO_TX_ALLOC_SIZE_SHIFT|SDIO_TX_ALLOC_ENABLE,REG_SDIO_TX_ALLOC_SHIFT, &err_ret);
|
#endif
|
sdio_release_host(func);
|
}
|
static void ssv6xxx_do_sdio_wakeup(struct sdio_func *func)
|
{
|
int err_ret;
|
if(func != NULL)
|
{
|
sdio_claim_host(func);
|
sdio_writeb(func, 0x01, REG_PMU_WAKEUP, &err_ret);
|
mdelay(10);
|
sdio_writeb(func, 0x00, REG_PMU_WAKEUP, &err_ret);
|
sdio_release_host(func);
|
}
|
}
|
static void ssv6xxx_sdio_pmu_wakeup(struct device *child)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
if (glue != NULL) {
|
func = dev_to_sdio_func(glue->dev);
|
ssv6xxx_do_sdio_wakeup(func);
|
}
|
}
|
static bool ssv6xxx_sdio_support_scatter(struct device *child)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
bool support = false;
|
do{
|
if(!glue){
|
dev_err(child->parent, "ssv6xxx_sdio_enable_scatter glue == NULL!!!\n");
|
break;
|
}
|
func = dev_to_sdio_func(glue->dev);
|
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,0)
|
if (func->card->host->max_segs < MAX_SCATTER_ENTRIES_PER_REQ) {
|
dev_err(child->parent, "host controller only supports scatter of :%d entries, driver need: %d\n",
|
func->card->host->max_segs,
|
MAX_SCATTER_ENTRIES_PER_REQ);
|
break;
|
}
|
support = true;
|
#endif
|
}while(0);
|
return support;
|
}
|
static void ssv6xxx_sdio_setup_scat_data(struct sdio_scatter_req *scat_req,
|
struct mmc_data *data)
|
{
|
struct scatterlist *sg;
|
int i;
|
data->blksz = SDIO_DEF_BLOCK_SIZE;
|
data->blocks = scat_req->len / SDIO_DEF_BLOCK_SIZE;
|
printk("scatter: (%s) (block len: %d, block count: %d) , (tot:%d,sg:%d)\n",
|
(scat_req->req & SDIO_WRITE) ? "WR" : "RD",
|
data->blksz, data->blocks, scat_req->len,
|
scat_req->scat_entries);
|
data->flags = (scat_req->req & SDIO_WRITE) ? MMC_DATA_WRITE :
|
MMC_DATA_READ;
|
sg = scat_req->sgentries;
|
sg_init_table(sg, scat_req->scat_entries);
|
for (i = 0; i < scat_req->scat_entries; i++, sg++) {
|
printk("%d: addr:0x%p, len:%d\n",
|
i, scat_req->scat_list[i].buf,
|
scat_req->scat_list[i].len);
|
sg_set_buf(sg, scat_req->scat_list[i].buf,
|
scat_req->scat_list[i].len);
|
}
|
data->sg = scat_req->sgentries;
|
data->sg_len = scat_req->scat_entries;
|
}
|
static inline void ssv6xxx_sdio_set_cmd53_arg(u32 *arg, u8 rw, u8 func,
|
u8 mode, u8 opcode, u32 addr,
|
u16 blksz)
|
{
|
*arg = (((rw & 1) << 31) |
|
((func & 0x7) << 28) |
|
((mode & 1) << 27) |
|
((opcode & 1) << 26) |
|
((addr & 0x1FFFF) << 9) |
|
(blksz & 0x1FF));
|
}
|
static int ssv6xxx_sdio_rw_scatter(struct device *child,
|
struct sdio_scatter_req *scat_req)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func;
|
struct mmc_request mmc_req;
|
struct mmc_command cmd;
|
struct mmc_data data;
|
u8 opcode, rw;
|
int status = 1;
|
do{
|
if(!glue){
|
dev_err(child->parent, "ssv6xxx_sdio_enable_scatter glue == NULL!!!\n");
|
break;
|
}
|
func = dev_to_sdio_func(glue->dev);
|
memset(&mmc_req, 0, sizeof(struct mmc_request));
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
memset(&data, 0, sizeof(struct mmc_data));
|
ssv6xxx_sdio_setup_scat_data(scat_req, &data);
|
opcode = 0;
|
rw = (scat_req->req & SDIO_WRITE) ? CMD53_ARG_WRITE : CMD53_ARG_READ;
|
ssv6xxx_sdio_set_cmd53_arg(&cmd.arg, rw, func->num,
|
CMD53_ARG_BLOCK_BASIS, opcode, glue->dataIOPort,
|
data.blocks);
|
cmd.opcode = SD_IO_RW_EXTENDED;
|
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
|
mmc_req.cmd = &cmd;
|
mmc_req.data = &data;
|
mmc_set_data_timeout(&data, func->card);
|
mmc_wait_for_req(func->card->host, &mmc_req);
|
status = cmd.error ? cmd.error : data.error;
|
if (cmd.error)
|
return cmd.error;
|
if (data.error)
|
return data.error;
|
}while(0);
|
return status;
|
}
|
static void ssv6xxx_set_sdio_clk(struct sdio_func *func,u32 sdio_hz)
|
{
|
struct mmc_host *host;
|
host = func->card->host;
|
if(sdio_hz < host->f_min )
|
sdio_hz = host->f_min;
|
else if(sdio_hz > host->f_max)
|
sdio_hz = host->f_max;
|
printk("%s:set sdio clk %dHz\n", __FUNCTION__,sdio_hz);
|
sdio_claim_host(func);
|
host->ios.clock = sdio_hz;
|
host->ops->set_ios(host, &host->ios);
|
mdelay(20);
|
sdio_release_host(func);
|
}
|
static void ssv6xxx_low_sdio_clk(struct sdio_func *func)
|
{
|
ssv6xxx_set_sdio_clk(func,LOW_SPEED_SDIO_CLOCK);
|
}
|
static void ssv6xxx_high_sdio_clk(struct sdio_func *func)
|
{
|
#ifndef SDIO_USE_SLOW_CLOCK
|
ssv6xxx_set_sdio_clk(func,HIGH_SPEED_SDIO_CLOCK);
|
#endif
|
}
|
static struct ssv6xxx_hwif_ops sdio_ops =
|
{
|
.read = ssv6xxx_sdio_read,
|
.write = ssv6xxx_sdio_write,
|
.readreg = ssv6xxx_sdio_read_reg,
|
.writereg = ssv6xxx_sdio_write_reg,
|
#ifdef ENABLE_WAKE_IO_ISR_WHEN_HCI_ENQUEUE
|
.trigger_tx_rx = ssv6xxx_sdio_trigger_tx_rx,
|
#endif
|
.irq_getmask = ssv6xxx_sdio_irq_getmask,
|
.irq_setmask = ssv6xxx_sdio_irq_setmask,
|
.irq_enable = ssv6xxx_sdio_irq_enable,
|
.irq_disable = ssv6xxx_sdio_irq_disable,
|
.irq_getstatus = ssv6xxx_sdio_irq_getstatus,
|
.irq_request = ssv6xxx_sdio_irq_request,
|
.irq_trigger = ssv6xxx_sdio_irq_trigger,
|
.pmu_wakeup = ssv6xxx_sdio_pmu_wakeup,
|
.load_fw = ssv6xxx_sdio_load_firmware,
|
.cmd52_read = ssv6xxx_sdio_cmd52_read,
|
.cmd52_write = ssv6xxx_sdio_cmd52_write,
|
.support_scatter = ssv6xxx_sdio_support_scatter,
|
.rw_scatter = ssv6xxx_sdio_rw_scatter,
|
.is_ready = ssv6xxx_is_ready,
|
.write_sram = ssv6xxx_sdio_write_sram,
|
.interface_reset = ssv6xxx_sdio_reset,
|
};
|
#ifdef CONFIG_PCIEASPM
|
#include <linux/pci.h>
|
#include <linux/pci-aspm.h>
|
static int cabrio_sdio_pm_check(struct sdio_func *func)
|
{
|
struct pci_dev *pci_dev = NULL;
|
struct mmc_card *card = func->card;
|
struct mmc_host *host = card->host;
|
if (strcmp(host->parent->bus->name, "pci"))
|
{
|
dev_info(&func->dev, "SDIO host is not PCI device, but \"%s\".", host->parent->bus->name);
|
return 0;
|
}
|
for_each_pci_dev(pci_dev) {
|
if ( ((pci_dev->class >> 8) != PCI_CLASS_SYSTEM_SDHCI)
|
&& ( (pci_dev->driver == NULL)
|
|| (strcmp(pci_dev->driver->name, "sdhci-pci") != 0)))
|
continue;
|
if (pci_is_pcie(pci_dev)) {
|
u8 aspm;
|
int pos;
|
pos = pci_pcie_cap(pci_dev);
|
if (pos) {
|
struct pci_dev *parent = pci_dev->bus->self;
|
pci_read_config_byte(pci_dev, pos + PCI_EXP_LNKCTL, &aspm);
|
aspm &= ~(PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1);
|
pci_write_config_byte(pci_dev, pos + PCI_EXP_LNKCTL, aspm);
|
pos = pci_pcie_cap(parent);
|
pci_read_config_byte(parent, pos + PCI_EXP_LNKCTL, &aspm);
|
aspm &= ~(PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1);
|
pci_write_config_byte(parent, pos + PCI_EXP_LNKCTL, aspm);
|
dev_info(&pci_dev->dev, "Clear PCI-E device and its parent link state L0S and L1 and CLKPM.\n");
|
}
|
}
|
}
|
return 0;
|
}
|
#endif
|
static int ssv6xxx_sdio_power_on(struct ssv6xxx_platform_data * pdata, struct sdio_func *func)
|
{
|
int ret = 0;
|
if (pdata->is_enabled == true)
|
return 0;
|
printk("ssv6xxx_sdio_power_on\n");
|
sdio_claim_host(func);
|
ret = sdio_enable_func(func);
|
sdio_release_host(func);
|
if (ret) {
|
printk("Unable to enable sdio func: %d)\n", ret);
|
return ret;
|
}
|
msleep(10);
|
pdata->is_enabled = true;
|
return ret;
|
}
|
static int ssv6xxx_sdio_power_off(struct ssv6xxx_platform_data * pdata, struct sdio_func *func)
|
{
|
int ret;
|
if (pdata->is_enabled == false)
|
return 0;
|
printk("ssv6xxx_sdio_power_off\n");
|
sdio_claim_host(func);
|
ret = sdio_disable_func(func);
|
sdio_release_host(func);
|
if (ret)
|
return ret;
|
pdata->is_enabled = false;
|
return ret;
|
}
|
int ssv6xxx_get_dev_status(void)
|
{
|
return ssv6xxx_sdio_status;
|
}
|
EXPORT_SYMBOL(ssv6xxx_get_dev_status);
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
static int __devinit ssv6xxx_sdio_probe(struct sdio_func *func,
|
const struct sdio_device_id *id)
|
#else
|
static int ssv6xxx_sdio_probe(struct sdio_func *func,
|
const struct sdio_device_id *id)
|
#endif
|
{
|
struct ssv6xxx_platform_data *pwlan_data = &wlan_data;
|
struct ssv6xxx_sdio_glue *glue;
|
int ret = -ENOMEM;
|
const char *chip_family = "ssv6200";
|
if (ssv_devicetype != 0) {
|
printk(KERN_INFO "Not using SSV6200 normal SDIO driver.\n");
|
return -ENODEV;
|
}
|
printk(KERN_INFO "=======================================\n");
|
printk(KERN_INFO "== RUN SDIO ==\n");
|
printk(KERN_INFO "=======================================\n");
|
if (func->num != 0x01)
|
return -ENODEV;
|
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
|
if (!glue)
|
{
|
dev_err(&func->dev, "can't allocate glue\n");
|
goto out;
|
}
|
ssv6xxx_sdio_status = 1;
|
ssv6xxx_low_sdio_clk(func);
|
#ifdef CONFIG_FW_ALIGNMENT_CHECK
|
glue->dmaSkb=__dev_alloc_skb(SDIO_DMA_BUFFER_LEN , GFP_KERNEL);
|
#endif
|
#ifdef CONFIG_PM
|
glue->cmd_skb=__dev_alloc_skb(SDIO_COMMAND_BUFFER_LEN , GFP_KERNEL);
|
#endif
|
memset(pwlan_data, 0, sizeof(struct ssv6xxx_platform_data));
|
atomic_set(&pwlan_data->irq_handling, 0);
|
glue->dev = &func->dev;
|
func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
|
func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE;
|
glue->dev_ready = true;
|
pwlan_data->vendor = func->vendor;
|
pwlan_data->device = func->device;
|
dev_err(glue->dev, "vendor = 0x%x device = 0x%x\n", pwlan_data->vendor,pwlan_data->device);
|
#ifdef CONFIG_PCIEASPM
|
cabrio_sdio_pm_check(func);
|
#endif
|
pwlan_data->ops = &sdio_ops;
|
sdio_set_drvdata(func, glue);
|
#ifdef CONFIG_PM
|
ssv6xxx_do_sdio_wakeup(func);
|
#endif
|
ssv6xxx_sdio_power_on(pwlan_data, func);
|
ssv6xxx_sdio_read_parameter(func,glue);
|
glue->core = platform_device_alloc(chip_family, -1);
|
if (!glue->core)
|
{
|
dev_err(glue->dev, "can't allocate platform_device");
|
ret = -ENOMEM;
|
goto out_free_glue;
|
}
|
glue->core->dev.parent = &func->dev;
|
ret = platform_device_add_data(glue->core, pwlan_data,
|
sizeof(*pwlan_data));
|
if (ret)
|
{
|
dev_err(glue->dev, "can't add platform data\n");
|
goto out_dev_put;
|
}
|
ret = platform_device_add(glue->core);
|
if (ret)
|
{
|
dev_err(glue->dev, "can't add platform device\n");
|
goto out_dev_put;
|
}
|
ssv6xxx_sdio_irq_setmask(&glue->core->dev,0xff);
|
#if 0
|
ssv6xxx_sdio_irq_enable(&glue->core->dev);
|
#else
|
#endif
|
#if 0
|
glue->dev->platform_data = (void *)pwlan_data;
|
ret = ssv6xxx_dev_probe(glue->dev);
|
if (ret)
|
{
|
dev_err(glue->dev, "failed to initial ssv6xxx device !!\n");
|
platform_device_del(glue->core);
|
goto out_dev_put;
|
}
|
#endif
|
ssv6xxx_ifdebug_info[0] = (void *)&glue->core->dev;
|
ssv6xxx_ifdebug_info[1] = (void *)glue->core;
|
ssv6xxx_ifdebug_info[2] = (void *)&sdio_ops;
|
return 0;
|
out_dev_put:
|
platform_device_put(glue->core);
|
out_free_glue:
|
kfree(glue);
|
out:
|
return ret;
|
}
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
static void __devexit ssv6xxx_sdio_remove(struct sdio_func *func)
|
#else
|
static void ssv6xxx_sdio_remove(struct sdio_func *func)
|
#endif
|
{
|
struct ssv6xxx_sdio_glue *glue = sdio_get_drvdata(func);
|
struct ssv6xxx_platform_data *pwlan_data = &wlan_data;
|
printk("ssv6xxx_sdio_remove..........\n");
|
ssv6xxx_sdio_status = 0;
|
if ( glue )
|
{
|
printk("ssv6xxx_sdio_remove - ssv6xxx_sdio_irq_disable\n");
|
ssv6xxx_sdio_irq_disable(&glue->core->dev,false);
|
glue->dev_ready = false;
|
#if 0
|
ssv6xxx_dev_remove(glue->dev);
|
#endif
|
ssv6xxx_low_sdio_clk(func);
|
#ifdef CONFIG_FW_ALIGNMENT_CHECK
|
if(glue->dmaSkb != NULL)
|
dev_kfree_skb(glue->dmaSkb);
|
#endif
|
printk("ssv6xxx_sdio_remove - disable mask\n");
|
ssv6xxx_sdio_irq_setmask(&glue->core->dev,0xff);
|
#ifdef CONFIG_PM
|
ssv6xxx_sdio_trigger_pmu(glue->dev);
|
if(glue->cmd_skb != NULL)
|
dev_kfree_skb(glue->cmd_skb);
|
#endif
|
ssv6xxx_sdio_power_off(pwlan_data, func);
|
printk("platform_device_del \n");
|
platform_device_del(glue->core);
|
printk("platform_device_put \n");
|
platform_device_put(glue->core);
|
kfree(glue);
|
}
|
sdio_set_drvdata(func, NULL);
|
printk("ssv6xxx_sdio_remove leave..........\n");
|
}
|
#ifdef CONFIG_PM
|
static int ssv6xxx_sdio_trigger_pmu(struct device *dev)
|
{
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
struct ssv6xxx_sdio_glue *glue = sdio_get_drvdata(func);
|
struct cfg_host_cmd *host_cmd;
|
int writesize;
|
int ret = 0;
|
void *tempPointer;
|
#ifdef SSV_WAKEUP_HOST
|
if(ssv6xxx_sdio_write_reg(dev, ADR_RX_FLOW_MNG, M_ENG_MACRX|(M_ENG_CPU<<4)|(M_ENG_HWHCI<<8)));
|
if(ssv6xxx_sdio_write_reg(dev, ADR_RX_FLOW_DATA, M_ENG_MACRX|(M_ENG_CPU<<4)|(M_ENG_HWHCI<<8)));
|
if(ssv6xxx_sdio_write_reg(dev, ADR_MRX_FLT_TB0+6*4, (sc->mac_deci_tbl[6]|1)));
|
#else
|
if(ssv6xxx_sdio_write_reg(dev, ADR_RX_FLOW_MNG, M_ENG_MACRX|(M_ENG_TRASH_CAN<<4)));
|
if(ssv6xxx_sdio_write_reg(dev, ADR_RX_FLOW_DATA, M_ENG_MACRX|(M_ENG_TRASH_CAN<<4)));
|
if(ssv6xxx_sdio_write_reg(dev, ADR_RX_FLOW_CTRL, M_ENG_MACRX|(M_ENG_TRASH_CAN<<4)));
|
#endif
|
host_cmd = (struct cfg_host_cmd *)glue->cmd_skb->data;
|
host_cmd->c_type = HOST_CMD;
|
host_cmd->RSVD0 = 0;
|
host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_PS;
|
host_cmd->len = sizeof(struct cfg_host_cmd);
|
#ifdef SSV_WAKEUP_HOST
|
host_cmd->dummy = sc->ps_aid;
|
#else
|
host_cmd->dummy = 0;
|
#endif
|
{
|
tempPointer = glue->cmd_skb->data;
|
sdio_claim_host(func);
|
writesize = sdio_align_size(func,sizeof(struct cfg_host_cmd));
|
do
|
{
|
ret = sdio_memcpy_toio(func, glue->dataIOPort, tempPointer, writesize);
|
if ( ret == -EILSEQ || ret == -ETIMEDOUT )
|
{
|
ret = -1;
|
break;
|
}
|
else
|
{
|
if(ret)
|
dev_err(glue->dev,"Unexpected return value ret=[%d]\n",ret);
|
}
|
}
|
while( ret == -EILSEQ || ret == -ETIMEDOUT);
|
sdio_release_host(func);
|
if (ret)
|
dev_err(glue->dev, "sdio write failed (%d)\n", ret);
|
}
|
return ret;
|
}
|
static void ssv6xxx_sdio_reset(struct device *child)
|
{
|
struct ssv6xxx_sdio_glue *glue = dev_get_drvdata(child->parent);
|
struct sdio_func *func = dev_to_sdio_func(glue->dev);
|
printk("%s\n", __FUNCTION__);
|
if(glue == NULL || glue->dev == NULL || func == NULL)
|
return;
|
ssv6xxx_sdio_trigger_pmu(glue->dev);
|
ssv6xxx_do_sdio_wakeup( func);
|
}
|
#ifdef AML_WIFI_MAC
|
extern void sdio_reinit(void);
|
extern void extern_wifi_set_enable(int is_on);
|
#endif
|
static int ssv6xxx_sdio_suspend(struct device *dev)
|
{
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
mmc_pm_flag_t flags = sdio_get_host_pm_caps(func);
|
#ifdef AML_WIFI_MAC
|
if (!(flags & MMC_PM_KEEP_POWER) || sdio_sr_bhvr == SUSPEND_RESUME_1) {
|
printk("%s : module will not get power support during suspend. %u\n", __func__, sdio_sr_bhvr);
|
ssv6xxx_deinit_prepare();
|
ssv6xxx_sdio_remove(func);
|
mdelay(100);
|
extern_wifi_set_enable(0);
|
mdelay(10);
|
return 0;
|
}else
|
#endif
|
{
|
int ret = 0;
|
dev_info(dev, "%s: suspend: PM flags = 0x%x\n",
|
sdio_func_id(func), flags);
|
ssv6xxx_low_sdio_clk(func);
|
ret = ssv6xxx_sdio_trigger_pmu(dev);
|
if (ret)
|
printk("ssv6xxx_sdio_trigger_pmu fail!!\n");
|
if (!(flags & MMC_PM_KEEP_POWER))
|
{
|
dev_err(dev, "%s: cannot remain alive while host is suspended\n",
|
sdio_func_id(func));
|
}
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
if (ret)
|
return ret;
|
mdelay(10);
|
#ifdef SSV_WAKEUP_HOST
|
ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
|
#endif
|
#if 0
|
if (softc->wow_enabled)
|
{
|
sdio_flags = sdio_get_host_pm_caps(func);
|
if (!(sdio_flags & MMC_PM_KEEP_POWER))
|
{
|
dev_err(dev, "can't keep power while host "
|
"is suspended\n");
|
ret = -EINVAL;
|
goto out;
|
}
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
if (ret)
|
{
|
dev_err(dev, "error while trying to keep power\n");
|
goto out;
|
}
|
}else{
|
ssv6xxx_sdio_irq_disable(&glue->core->dev,true);
|
}
|
#endif
|
return ret;
|
}
|
}
|
static int ssv6xxx_sdio_resume(struct device *dev)
|
{
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
#ifdef AML_WIFI_MAC
|
mmc_pm_flag_t flags = sdio_get_host_pm_caps(func);
|
mdelay(100);
|
if (!(flags & MMC_PM_KEEP_POWER) || sdio_sr_bhvr == SUSPEND_RESUME_1) {
|
printk("%s : module is reset, run probe now !! %u\n", __func__, sdio_sr_bhvr);
|
extern_wifi_set_enable(1);
|
mdelay(100);
|
sdio_reinit();
|
mdelay(150);
|
ssv6xxx_sdio_probe(func, NULL);
|
return 0;
|
}else
|
#endif
|
{
|
printk("ssv6xxx_sdio_resume\n");
|
{
|
ssv6xxx_do_sdio_wakeup(func);
|
mdelay(10);
|
ssv6xxx_high_sdio_clk(func);
|
mdelay(10);
|
}
|
}
|
return 0;
|
}
|
static const struct dev_pm_ops ssv6xxx_sdio_pm_ops =
|
{
|
.suspend = ssv6xxx_sdio_suspend,
|
.resume = ssv6xxx_sdio_resume,
|
};
|
#endif
|
|
|
#ifdef AML_WIFI_MAC
|
static void ssv6xxx_sdio_shutdown(struct device *dev)
|
{
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
printk("%s shutdown_flags:%d \n", __func__,shutdown_flags);
|
switch(shutdown_flags){
|
case SSV_SYS_REBOOT :
|
case SSV_SYS_HALF:
|
printk("%s ,system reboot ..\n", __func__);
|
break;
|
case SSV_SYS_POWER_OFF :
|
printk("%s ,system shutdown .. \n", __func__);
|
ssv6xxx_deinit_prepare();
|
ssv6xxx_sdio_remove(func);
|
mdelay(100);
|
extern_wifi_set_enable(0);
|
mdelay(100);
|
break;
|
default:
|
printk("%s,unknown event code ..", __func__);
|
}
|
|
}
|
|
static int ssv6xxx_reboot_notify(struct notifier_block *nb,
|
unsigned long event, void *p)
|
{
|
|
printk("%s, code = %ld \n",__FUNCTION__,event);
|
switch (event){
|
case SYS_DOWN:
|
shutdown_flags = SYS_DOWN;
|
break;
|
case SYS_HALT:
|
shutdown_flags = SYS_HALT;
|
break;
|
case SYS_POWER_OFF:
|
shutdown_flags = SYS_POWER_OFF;
|
break;
|
default:
|
shutdown_flags = event;
|
break;
|
}
|
return NOTIFY_DONE;
|
}
|
static struct notifier_block ssv6xxx_reboot_notifier = {
|
.notifier_call = ssv6xxx_reboot_notify,
|
.next = NULL,
|
.priority = 0,
|
};
|
#endif
|
|
struct sdio_driver ssv6xxx_sdio_driver =
|
{
|
.name = "SSV6XXX_SDIO",
|
.id_table = ssv6xxx_sdio_devices,
|
.probe = ssv6xxx_sdio_probe,
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
|
.remove = __devexit_p(ssv6xxx_sdio_remove),
|
#else
|
.remove = ssv6xxx_sdio_remove,
|
#endif
|
#ifdef CONFIG_PM
|
.drv = {
|
.pm = &ssv6xxx_sdio_pm_ops,
|
#ifdef AML_WIFI_MAC
|
.shutdown = ssv6xxx_sdio_shutdown,
|
#endif
|
},
|
#endif
|
};
|
EXPORT_SYMBOL(ssv6xxx_sdio_driver);
|
#if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO))
|
int ssv6xxx_sdio_init(void)
|
#else
|
static int __init ssv6xxx_sdio_init(void)
|
#endif
|
{
|
printk(KERN_INFO "ssv6xxx_sdio_init\n");
|
#ifdef AML_WIFI_MAC
|
register_reboot_notifier(&ssv6xxx_reboot_notifier);
|
#endif
|
return sdio_register_driver(&ssv6xxx_sdio_driver);
|
}
|
#if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO))
|
void ssv6xxx_sdio_exit(void)
|
#else
|
static void __exit ssv6xxx_sdio_exit(void)
|
#endif
|
{
|
printk(KERN_INFO "ssv6xxx_sdio_exit\n");
|
#ifdef AML_WIFI_MAC
|
unregister_reboot_notifier(&ssv6xxx_reboot_notifier);
|
#endif
|
sdio_unregister_driver(&ssv6xxx_sdio_driver);
|
}
|
#if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO))
|
EXPORT_SYMBOL(ssv6xxx_sdio_init);
|
EXPORT_SYMBOL(ssv6xxx_sdio_exit);
|
#else
|
module_init(ssv6xxx_sdio_init);
|
module_exit(ssv6xxx_sdio_exit);
|
#endif
|
MODULE_LICENSE("GPL");
|