diff options
Diffstat (limited to 'drivers/net/wireless/rsi/rsi_91x_sdio_ops.c')
| -rw-r--r-- | drivers/net/wireless/rsi/rsi_91x_sdio_ops.c | 564 | 
1 files changed, 564 insertions, 0 deletions
diff --git a/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c new file mode 100644 index 00000000000..20d11ccfffe --- /dev/null +++ b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c @@ -0,0 +1,564 @@ +/** + * Copyright (c) 2014 Redpine Signals 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. + * + */ + +#include <linux/firmware.h> +#include "rsi_sdio.h" +#include "rsi_common.h" + +/** + * rsi_sdio_master_access_msword() - This function sets the AHB master access + *				     MS word in the SDIO slave registers. + * @adapter: Pointer to the adapter structure. + * @ms_word: ms word need to be initialized. + * + * Return: status: 0 on success, -1 on failure. + */ +static int rsi_sdio_master_access_msword(struct rsi_hw *adapter, +					 u16 ms_word) +{ +	u8 byte; +	u8 function = 0; +	int status = 0; + +	byte = (u8)(ms_word & 0x00FF); + +	rsi_dbg(INIT_ZONE, +		"%s: MASTER_ACCESS_MSBYTE:0x%x\n", __func__, byte); + +	status = rsi_sdio_write_register(adapter, +					 function, +					 SDIO_MASTER_ACCESS_MSBYTE, +					 &byte); +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: fail to access MASTER_ACCESS_MSBYTE\n", +			__func__); +		return -1; +	} + +	byte = (u8)(ms_word >> 8); + +	rsi_dbg(INIT_ZONE, "%s:MASTER_ACCESS_LSBYTE:0x%x\n", __func__, byte); +	status = rsi_sdio_write_register(adapter, +					 function, +					 SDIO_MASTER_ACCESS_LSBYTE, +					 &byte); +	return status; +} + +/** + * rsi_copy_to_card() - This function includes the actual funtionality of + *			copying the TA firmware to the card.Basically this + *			function includes opening the TA file,reading the + *			TA file and writing their values in blocks of data. + * @common: Pointer to the driver private structure. + * @fw: Pointer to the firmware value to be written. + * @len: length of firmware file. + * @num_blocks: Number of blocks to be written to the card. + * + * Return: 0 on success and -1 on failure. + */ +static int rsi_copy_to_card(struct rsi_common *common, +			    const u8 *fw, +			    u32 len, +			    u32 num_blocks) +{ +	struct rsi_hw *adapter = common->priv; +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; +	u32 indx, ii; +	u32 block_size = dev->tx_blk_size; +	u32 lsb_address; +	__le32 data[] = { TA_HOLD_THREAD_VALUE, TA_SOFT_RST_CLR, +			  TA_PC_ZERO, TA_RELEASE_THREAD_VALUE }; +	u32 address[] = { TA_HOLD_THREAD_REG, TA_SOFT_RESET_REG, +			  TA_TH0_PC_REG, TA_RELEASE_THREAD_REG }; +	u32 base_address; +	u16 msb_address; + +	base_address = TA_LOAD_ADDRESS; +	msb_address = base_address >> 16; + +	for (indx = 0, ii = 0; ii < num_blocks; ii++, indx += block_size) { +		lsb_address = ((u16) base_address | RSI_SD_REQUEST_MASTER); +		if (rsi_sdio_write_register_multiple(adapter, +						     lsb_address, +						     (u8 *)(fw + indx), +						     block_size)) { +			rsi_dbg(ERR_ZONE, +				"%s: Unable to load %s blk\n", __func__, +				FIRMWARE_RSI9113); +			return -1; +		} +		rsi_dbg(INIT_ZONE, "%s: loading block: %d\n", __func__, ii); +		base_address += block_size; +		if ((base_address >> 16) != msb_address) { +			msb_address += 1; +			if (rsi_sdio_master_access_msword(adapter, +							  msb_address)) { +				rsi_dbg(ERR_ZONE, +					"%s: Unable to set ms word reg\n", +					__func__); +				return -1; +			} +		} +	} + +	if (len % block_size) { +		lsb_address = ((u16) base_address | RSI_SD_REQUEST_MASTER); +		if (rsi_sdio_write_register_multiple(adapter, +						     lsb_address, +						     (u8 *)(fw + indx), +						     len % block_size)) { +			rsi_dbg(ERR_ZONE, +				"%s: Unable to load f/w\n", __func__); +			return -1; +		} +	} +	rsi_dbg(INIT_ZONE, +		"%s: Succesfully loaded TA instructions\n", __func__); + +	if (rsi_sdio_master_access_msword(adapter, TA_BASE_ADDR)) { +		rsi_dbg(ERR_ZONE, +			"%s: Unable to set ms word to common reg\n", +			__func__); +		return -1; +	} + +	for (ii = 0; ii < ARRAY_SIZE(data); ii++) { +		/* Bringing TA out of reset */ +		if (rsi_sdio_write_register_multiple(adapter, +						     (address[ii] | +						     RSI_SD_REQUEST_MASTER), +						     (u8 *)&data[ii], +						     4)) { +			rsi_dbg(ERR_ZONE, +				"%s: Unable to hold TA threads\n", __func__); +			return -1; +		} +	} + +	rsi_dbg(INIT_ZONE, "%s: loaded firmware\n", __func__); +	return 0; +} + +/** + * rsi_load_ta_instructions() - This function includes the actual funtionality + *				of loading the TA firmware.This function also + *				includes opening the TA file,reading the TA + *				file and writing their value in blocks of data. + * @common: Pointer to the driver private structure. + * + * Return: status: 0 on success, -1 on failure. + */ +static int rsi_load_ta_instructions(struct rsi_common *common) +{ +	struct rsi_hw *adapter = common->priv; +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; +	u32 len; +	u32 num_blocks; +	const u8 *fw; +	const struct firmware *fw_entry = NULL; +	u32 block_size = dev->tx_blk_size; +	int status = 0; +	u32 base_address; +	u16 msb_address; + +	if (rsi_sdio_master_access_msword(adapter, TA_BASE_ADDR)) { +		rsi_dbg(ERR_ZONE, +			"%s: Unable to set ms word to common reg\n", +			__func__); +		return -1; +	} +	base_address = TA_LOAD_ADDRESS; +	msb_address = (base_address >> 16); + +	if (rsi_sdio_master_access_msword(adapter, msb_address)) { +		rsi_dbg(ERR_ZONE, +			"%s: Unable to set ms word reg\n", __func__); +		return -1; +	} + +	status = request_firmware(&fw_entry, FIRMWARE_RSI9113, adapter->device); +	if (status < 0) { +		rsi_dbg(ERR_ZONE, "%s Firmware file %s not found\n", +			__func__, FIRMWARE_RSI9113); +		return status; +	} + +	fw = kmemdup(fw_entry->data, fw_entry->size, GFP_KERNEL); +	len = fw_entry->size; + +	if (len % 4) +		len += (4 - (len % 4)); + +	num_blocks = (len / block_size); + +	rsi_dbg(INIT_ZONE, "%s: Instruction size:%d\n", __func__, len); +	rsi_dbg(INIT_ZONE, "%s: num blocks: %d\n", __func__, num_blocks); + +	status = rsi_copy_to_card(common, fw, len, num_blocks); +	release_firmware(fw_entry); +	return status; +} + +/** + * rsi_process_pkt() - This Function reads rx_blocks register and figures out + *		       the size of the rx pkt. + * @common: Pointer to the driver private structure. + * + * Return: 0 on success, -1 on failure. + */ +static int rsi_process_pkt(struct rsi_common *common) +{ +	struct rsi_hw *adapter = common->priv; +	u8 num_blks = 0; +	u32 rcv_pkt_len = 0; +	int status = 0; + +	status = rsi_sdio_read_register(adapter, +					SDIO_RX_NUM_BLOCKS_REG, +					&num_blks); + +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: Failed to read pkt length from the card:\n", +			__func__); +		return status; +	} +	rcv_pkt_len = (num_blks * 256); + +	common->rx_data_pkt = kmalloc(rcv_pkt_len, GFP_KERNEL); +	if (!common->rx_data_pkt) { +		rsi_dbg(ERR_ZONE, "%s: Failed in memory allocation\n", +			__func__); +		return -ENOMEM; +	} + +	status = rsi_sdio_host_intf_read_pkt(adapter, +					     common->rx_data_pkt, +					     rcv_pkt_len); +	if (status) { +		rsi_dbg(ERR_ZONE, "%s: Failed to read packet from card\n", +			__func__); +		goto fail; +	} + +	status = rsi_read_pkt(common, rcv_pkt_len); + +fail: +	kfree(common->rx_data_pkt); +	return status; +} + +/** + * rsi_init_sdio_slave_regs() - This function does the actual initialization + *				of SDBUS slave registers. + * @adapter: Pointer to the adapter structure. + * + * Return: status: 0 on success, -1 on failure. + */ +int rsi_init_sdio_slave_regs(struct rsi_hw *adapter) +{ +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; +	u8 function = 0; +	u8 byte; +	int status = 0; + +	if (dev->next_read_delay) { +		byte = dev->next_read_delay; +		status = rsi_sdio_write_register(adapter, +						 function, +						 SDIO_NXT_RD_DELAY2, +						 &byte); +		if (status) { +			rsi_dbg(ERR_ZONE, +				"%s: Failed to write SDIO_NXT_RD_DELAY2\n", +				__func__); +			return -1; +		} +	} + +	if (dev->sdio_high_speed_enable) { +		rsi_dbg(INIT_ZONE, "%s: Enabling SDIO High speed\n", __func__); +		byte = 0x3; + +		status = rsi_sdio_write_register(adapter, +						 function, +						 SDIO_REG_HIGH_SPEED, +						 &byte); +		if (status) { +			rsi_dbg(ERR_ZONE, +				"%s: Failed to enable SDIO high speed\n", +				__func__); +			return -1; +		} +	} + +	/* This tells SDIO FIFO when to start read to host */ +	rsi_dbg(INIT_ZONE, "%s: Initialzing SDIO read start level\n", __func__); +	byte = 0x24; + +	status = rsi_sdio_write_register(adapter, +					 function, +					 SDIO_READ_START_LVL, +					 &byte); +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: Failed to write SDIO_READ_START_LVL\n", __func__); +		return -1; +	} + +	rsi_dbg(INIT_ZONE, "%s: Initialzing FIFO ctrl registers\n", __func__); +	byte = (128 - 32); + +	status = rsi_sdio_write_register(adapter, +					 function, +					 SDIO_READ_FIFO_CTL, +					 &byte); +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: Failed to write SDIO_READ_FIFO_CTL\n", __func__); +		return -1; +	} + +	byte = 32; +	status = rsi_sdio_write_register(adapter, +					 function, +					 SDIO_WRITE_FIFO_CTL, +					 &byte); +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: Failed to write SDIO_WRITE_FIFO_CTL\n", __func__); +		return -1; +	} + +	return 0; +} + +/** + * rsi_interrupt_handler() - This function read and process SDIO interrupts. + * @adapter: Pointer to the adapter structure. + * + * Return: None. + */ +void rsi_interrupt_handler(struct rsi_hw *adapter) +{ +	struct rsi_common *common = adapter->priv; +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; +	int status; +	enum sdio_interrupt_type isr_type; +	u8 isr_status = 0; +	u8 fw_status = 0; + +	dev->rx_info.sdio_int_counter++; + +	do { +		mutex_lock(&common->tx_rxlock); +		status = rsi_sdio_read_register(common->priv, +						RSI_FN1_INT_REGISTER, +						&isr_status); +		if (status) { +			rsi_dbg(ERR_ZONE, +				"%s: Failed to Read Intr Status Register\n", +				__func__); +			mutex_unlock(&common->tx_rxlock); +			return; +		} + +		if (isr_status == 0) { +			rsi_set_event(&common->tx_thread.event); +			dev->rx_info.sdio_intr_status_zero++; +			mutex_unlock(&common->tx_rxlock); +			return; +		} + +		rsi_dbg(ISR_ZONE, "%s: Intr_status = %x %d %d\n", +			__func__, isr_status, (1 << MSDU_PKT_PENDING), +			(1 << FW_ASSERT_IND)); + +		do { +			RSI_GET_SDIO_INTERRUPT_TYPE(isr_status, isr_type); + +			switch (isr_type) { +			case BUFFER_AVAILABLE: +				dev->rx_info.watch_bufferfull_count = 0; +				dev->rx_info.buffer_full = false; +				dev->rx_info.mgmt_buffer_full = false; +				rsi_sdio_ack_intr(common->priv, +						  (1 << PKT_BUFF_AVAILABLE)); +				rsi_set_event((&common->tx_thread.event)); +				rsi_dbg(ISR_ZONE, +					"%s: ==> BUFFER_AVILABLE <==\n", +					__func__); +				dev->rx_info.buf_avilable_counter++; +				break; + +			case FIRMWARE_ASSERT_IND: +				rsi_dbg(ERR_ZONE, +					"%s: ==> FIRMWARE Assert <==\n", +					__func__); +				status = rsi_sdio_read_register(common->priv, +							SDIO_FW_STATUS_REG, +							&fw_status); +				if (status) { +					rsi_dbg(ERR_ZONE, +						"%s: Failed to read f/w reg\n", +						__func__); +				} else { +					rsi_dbg(ERR_ZONE, +						"%s: Firmware Status is 0x%x\n", +						__func__ , fw_status); +					rsi_sdio_ack_intr(common->priv, +							  (1 << FW_ASSERT_IND)); +				} + +				common->fsm_state = FSM_CARD_NOT_READY; +				break; + +			case MSDU_PACKET_PENDING: +				rsi_dbg(ISR_ZONE, "Pkt pending interrupt\n"); +				dev->rx_info.total_sdio_msdu_pending_intr++; + +				status = rsi_process_pkt(common); +				if (status) { +					rsi_dbg(ERR_ZONE, +						"%s: Failed to read pkt\n", +						__func__); +					mutex_unlock(&common->tx_rxlock); +					return; +				} +				break; +			default: +				rsi_sdio_ack_intr(common->priv, isr_status); +				dev->rx_info.total_sdio_unknown_intr++; +				isr_status = 0; +				rsi_dbg(ISR_ZONE, +					"Unknown Interrupt %x\n", +					isr_status); +				break; +			} +			isr_status ^= BIT(isr_type - 1); +		} while (isr_status); +		mutex_unlock(&common->tx_rxlock); +	} while (1); +} + +/** + * rsi_device_init() - This Function Initializes The HAL. + * @common: Pointer to the driver private structure. + * + * Return: 0 on success, -1 on failure. + */ +int rsi_sdio_device_init(struct rsi_common *common) +{ +	if (rsi_load_ta_instructions(common)) +		return -1; + +	if (rsi_sdio_master_access_msword(common->priv, MISC_CFG_BASE_ADDR)) { +		rsi_dbg(ERR_ZONE, "%s: Unable to set ms word reg\n", +			__func__); +		return -1; +	} +	rsi_dbg(INIT_ZONE, +		"%s: Setting ms word to 0x41050000\n", __func__); + +	return 0; +} + +/** + * rsi_sdio_read_buffer_status_register() - This function is used to the read + *					    buffer status register and set + *					    relevant fields in + *					    rsi_91x_sdiodev struct. + * @adapter: Pointer to the driver hw structure. + * @q_num: The Q number whose status is to be found. + * + * Return: status: -1 on failure or else queue full/stop is indicated. + */ +int rsi_sdio_read_buffer_status_register(struct rsi_hw *adapter, u8 q_num) +{ +	struct rsi_common *common = adapter->priv; +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; +	u8 buf_status = 0; +	int status = 0; + +	status = rsi_sdio_read_register(common->priv, +					RSI_DEVICE_BUFFER_STATUS_REGISTER, +					&buf_status); + +	if (status) { +		rsi_dbg(ERR_ZONE, +			"%s: Failed to read status register\n", __func__); +		return -1; +	} + +	if (buf_status & (BIT(PKT_MGMT_BUFF_FULL))) { +		if (!dev->rx_info.mgmt_buffer_full) +			dev->rx_info.mgmt_buf_full_counter++; +		dev->rx_info.mgmt_buffer_full = true; +	} else { +		dev->rx_info.mgmt_buffer_full = false; +	} + +	if (buf_status & (BIT(PKT_BUFF_FULL))) { +		if (!dev->rx_info.buffer_full) +			dev->rx_info.buf_full_counter++; +		dev->rx_info.buffer_full = true; +	} else { +		dev->rx_info.buffer_full = false; +	} + +	if (buf_status & (BIT(PKT_BUFF_SEMI_FULL))) { +		if (!dev->rx_info.semi_buffer_full) +			dev->rx_info.buf_semi_full_counter++; +		dev->rx_info.semi_buffer_full = true; +	} else { +		dev->rx_info.semi_buffer_full = false; +	} + +	if ((q_num == MGMT_SOFT_Q) && (dev->rx_info.mgmt_buffer_full)) +		return QUEUE_FULL; + +	if (dev->rx_info.buffer_full) +		return QUEUE_FULL; + +	return QUEUE_NOT_FULL; +} + +/** + * rsi_sdio_determine_event_timeout() - This Function determines the event + *					timeout duration. + * @adapter: Pointer to the adapter structure. + * + * Return: timeout duration is returned. + */ +int rsi_sdio_determine_event_timeout(struct rsi_hw *adapter) +{ +	struct rsi_91x_sdiodev *dev = +		(struct rsi_91x_sdiodev *)adapter->rsi_dev; + +	/* Once buffer full is seen, event timeout to occur every 2 msecs */ +	if (dev->rx_info.buffer_full) +		return 2; + +	return EVENT_WAIT_FOREVER; +}  | 
