diff options
Diffstat (limited to 'drivers/net/wireless/mwifiex/sdio.c')
| -rw-r--r-- | drivers/net/wireless/mwifiex/sdio.c | 235 | 
1 files changed, 226 insertions, 9 deletions
diff --git a/drivers/net/wireless/mwifiex/sdio.c b/drivers/net/wireless/mwifiex/sdio.c index 4ce3d7b33991..1770fa3fc1e6 100644 --- a/drivers/net/wireless/mwifiex/sdio.c +++ b/drivers/net/wireless/mwifiex/sdio.c @@ -1,7 +1,7 @@  /*   * Marvell Wireless LAN device driver: SDIO specific handling   * - * Copyright (C) 2011, Marvell International Ltd. + * Copyright (C) 2011-2014, Marvell International Ltd.   *   * This software file (the "File") is distributed by Marvell International   * Ltd. under the terms of the GNU General Public License Version 2, June 1991 @@ -50,6 +50,24 @@ static struct mwifiex_if_ops sdio_ops;  static struct semaphore add_remove_card_sem; +static struct memory_type_mapping mem_type_mapping_tbl[] = { +	{"ITCM", NULL, 0, 0xF0}, +	{"DTCM", NULL, 0, 0xF1}, +	{"SQRAM", NULL, 0, 0xF2}, +	{"APU", NULL, 0, 0xF3}, +	{"CIU", NULL, 0, 0xF4}, +	{"ICU", NULL, 0, 0xF5}, +	{"MAC", NULL, 0, 0xF6}, +	{"EXT7", NULL, 0, 0xF7}, +	{"EXT8", NULL, 0, 0xF8}, +	{"EXT9", NULL, 0, 0xF9}, +	{"EXT10", NULL, 0, 0xFA}, +	{"EXT11", NULL, 0, 0xFB}, +	{"EXT12", NULL, 0, 0xFC}, +	{"EXT13", NULL, 0, 0xFD}, +	{"EXTLAST", NULL, 0, 0xFE}, +}; +  /*   * SDIO probe.   * @@ -87,6 +105,7 @@ mwifiex_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)  		card->tx_buf_size = data->tx_buf_size;  		card->mp_tx_agg_buf_size = data->mp_tx_agg_buf_size;  		card->mp_rx_agg_buf_size = data->mp_rx_agg_buf_size; +		card->supports_fw_dump = data->supports_fw_dump;  	}  	sdio_claim_host(func); @@ -179,6 +198,8 @@ mwifiex_sdio_remove(struct sdio_func *func)  	if (!adapter || !adapter->priv_num)  		return; +	cancel_work_sync(&adapter->iface_work); +  	if (user_rmmod) {  		if (adapter->is_suspended)  			mwifiex_sdio_resume(adapter->dev); @@ -1777,6 +1798,8 @@ static int mwifiex_register_dev(struct mwifiex_adapter *adapter)  	adapter->dev = &func->dev;  	strcpy(adapter->fw_name, card->firmware); +	adapter->mem_type_mapping_tbl = mem_type_mapping_tbl; +	adapter->num_mem_types = ARRAY_SIZE(mem_type_mapping_tbl);  	return 0;  } @@ -1914,10 +1937,10 @@ mwifiex_update_mp_end_port(struct mwifiex_adapter *adapter, u16 port)  		port, card->mp_data_port_mask);  } -static struct mmc_host *reset_host; -static void sdio_card_reset_worker(struct work_struct *work) +static void mwifiex_sdio_card_reset_work(struct mwifiex_adapter *adapter)  { -	struct mmc_host *target = reset_host; +	struct sdio_mmc_card *card = adapter->card; +	struct mmc_host *target = card->func->card->host;  	/* The actual reset operation must be run outside of driver thread.  	 * This is because mmc_remove_host() will cause the device to be @@ -1931,17 +1954,210 @@ static void sdio_card_reset_worker(struct work_struct *work)  	mmc_remove_host(target);  	/* 20ms delay is based on experiment with sdhci controller */  	mdelay(20); +	target->rescan_entered = 0; /* rescan non-removable cards */  	mmc_add_host(target);  } -static DECLARE_WORK(card_reset_work, sdio_card_reset_worker); + +/* This function read/write firmware */ +static enum +rdwr_status mwifiex_sdio_rdwr_firmware(struct mwifiex_adapter *adapter, +				       u8 doneflag) +{ +	struct sdio_mmc_card *card = adapter->card; +	int ret, tries; +	u8 ctrl_data = 0; + +	sdio_writeb(card->func, FW_DUMP_HOST_READY, card->reg->fw_dump_ctrl, +		    &ret); +	if (ret) { +		dev_err(adapter->dev, "SDIO Write ERR\n"); +		return RDWR_STATUS_FAILURE; +	} +	for (tries = 0; tries < MAX_POLL_TRIES; tries++) { +		ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl, +				       &ret); +		if (ret) { +			dev_err(adapter->dev, "SDIO read err\n"); +			return RDWR_STATUS_FAILURE; +		} +		if (ctrl_data == FW_DUMP_DONE) +			break; +		if (doneflag && ctrl_data == doneflag) +			return RDWR_STATUS_DONE; +		if (ctrl_data != FW_DUMP_HOST_READY) { +			dev_info(adapter->dev, +				 "The ctrl reg was changed, re-try again!\n"); +			sdio_writeb(card->func, FW_DUMP_HOST_READY, +				    card->reg->fw_dump_ctrl, &ret); +			if (ret) { +				dev_err(adapter->dev, "SDIO write err\n"); +				return RDWR_STATUS_FAILURE; +			} +		} +		usleep_range(100, 200); +	} +	if (ctrl_data == FW_DUMP_HOST_READY) { +		dev_err(adapter->dev, "Fail to pull ctrl_data\n"); +		return RDWR_STATUS_FAILURE; +	} + +	return RDWR_STATUS_SUCCESS; +} + +/* This function dump firmware memory to file */ +static void mwifiex_sdio_fw_dump_work(struct work_struct *work) +{ +	struct mwifiex_adapter *adapter = +			container_of(work, struct mwifiex_adapter, iface_work); +	struct sdio_mmc_card *card = adapter->card; +	int ret = 0; +	unsigned int reg, reg_start, reg_end; +	u8 *dbg_ptr, *end_ptr, dump_num, idx, i, read_reg, doneflag = 0; +	enum rdwr_status stat; +	u32 memory_size; +	static char *env[] = { "DRIVER=mwifiex_sdio", "EVENT=fw_dump", NULL }; + +	if (!card->supports_fw_dump) +		return; + +	for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) { +		struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + +		if (entry->mem_ptr) { +			vfree(entry->mem_ptr); +			entry->mem_ptr = NULL; +		} +		entry->mem_size = 0; +	} + +	mwifiex_pm_wakeup_card(adapter); +	sdio_claim_host(card->func); + +	dev_info(adapter->dev, "== mwifiex firmware dump start ==\n"); + +	stat = mwifiex_sdio_rdwr_firmware(adapter, doneflag); +	if (stat == RDWR_STATUS_FAILURE) +		goto done; + +	reg = card->reg->fw_dump_start; +	/* Read the number of the memories which will dump */ +	dump_num = sdio_readb(card->func, reg, &ret); +	if (ret) { +		dev_err(adapter->dev, "SDIO read memory length err\n"); +		goto done; +	} + +	/* Read the length of every memory which will dump */ +	for (idx = 0; idx < dump_num; idx++) { +		struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + +		stat = mwifiex_sdio_rdwr_firmware(adapter, doneflag); +		if (stat == RDWR_STATUS_FAILURE) +			goto done; + +		memory_size = 0; +		reg = card->reg->fw_dump_start; +		for (i = 0; i < 4; i++) { +			read_reg = sdio_readb(card->func, reg, &ret); +			if (ret) { +				dev_err(adapter->dev, "SDIO read err\n"); +				goto done; +			} +			memory_size |= (read_reg << i*8); +			reg++; +		} + +		if (memory_size == 0) { +			dev_info(adapter->dev, "Firmware dump Finished!\n"); +			break; +		} + +		dev_info(adapter->dev, +			 "%s_SIZE=0x%x\n", entry->mem_name, memory_size); +		entry->mem_ptr = vmalloc(memory_size + 1); +		entry->mem_size = memory_size; +		if (!entry->mem_ptr) { +			dev_err(adapter->dev, "Vmalloc %s failed\n", +				entry->mem_name); +			goto done; +		} +		dbg_ptr = entry->mem_ptr; +		end_ptr = dbg_ptr + memory_size; + +		doneflag = entry->done_flag; +		dev_info(adapter->dev, "Start %s output, please wait...\n", +			 entry->mem_name); + +		do { +			stat = mwifiex_sdio_rdwr_firmware(adapter, doneflag); +			if (stat == RDWR_STATUS_FAILURE) +				goto done; + +			reg_start = card->reg->fw_dump_start; +			reg_end = card->reg->fw_dump_end; +			for (reg = reg_start; reg <= reg_end; reg++) { +				*dbg_ptr = sdio_readb(card->func, reg, &ret); +				if (ret) { +					dev_err(adapter->dev, +						"SDIO read err\n"); +					goto done; +				} +				if (dbg_ptr < end_ptr) +					dbg_ptr++; +				else +					dev_err(adapter->dev, +						"Allocated buf not enough\n"); +			} + +			if (stat != RDWR_STATUS_DONE) +				continue; + +			dev_info(adapter->dev, "%s done: size=0x%tx\n", +				 entry->mem_name, dbg_ptr - entry->mem_ptr); +			break; +		} while (1); +	} +	dev_info(adapter->dev, "== mwifiex firmware dump end ==\n"); + +	kobject_uevent_env(&adapter->wiphy->dev.kobj, KOBJ_CHANGE, env); + +done: +	sdio_release_host(card->func); +	adapter->curr_mem_idx = 0; +} + +static void mwifiex_sdio_work(struct work_struct *work) +{ +	struct mwifiex_adapter *adapter = +			container_of(work, struct mwifiex_adapter, iface_work); + +	if (test_and_clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, +			       &adapter->iface_work_flags)) +		mwifiex_sdio_card_reset_work(adapter); +	if (test_and_clear_bit(MWIFIEX_IFACE_WORK_FW_DUMP, +			       &adapter->iface_work_flags)) +		mwifiex_sdio_fw_dump_work(work); +}  /* This function resets the card */  static void mwifiex_sdio_card_reset(struct mwifiex_adapter *adapter)  { -	struct sdio_mmc_card *card = adapter->card; +	if (test_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &adapter->iface_work_flags)) +		return; + +	set_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &adapter->iface_work_flags); + +	schedule_work(&adapter->iface_work); +} + +/* This function dumps FW information */ +static void mwifiex_sdio_fw_dump(struct mwifiex_adapter *adapter) +{ +	if (test_bit(MWIFIEX_IFACE_WORK_FW_DUMP, &adapter->iface_work_flags)) +		return; -	reset_host = card->func->card->host; -	schedule_work(&card_reset_work); +	set_bit(MWIFIEX_IFACE_WORK_FW_DUMP, &adapter->iface_work_flags); +	schedule_work(&adapter->iface_work);  }  static struct mwifiex_if_ops sdio_ops = { @@ -1964,6 +2180,8 @@ static struct mwifiex_if_ops sdio_ops = {  	.cmdrsp_complete = mwifiex_sdio_cmdrsp_complete,  	.event_complete = mwifiex_sdio_event_complete,  	.card_reset = mwifiex_sdio_card_reset, +	.iface_work = mwifiex_sdio_work, +	.fw_dump = mwifiex_sdio_fw_dump,  };  /* @@ -2001,7 +2219,6 @@ mwifiex_sdio_cleanup_module(void)  	/* Set the flag as user is removing this module. */  	user_rmmod = 1; -	cancel_work_sync(&card_reset_work);  	sdio_unregister_driver(&mwifiex_sdio);  }  | 
