⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 if_sdio.c

📁 linux内核源码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* *  linux/drivers/net/wireless/libertas/if_sdio.c * *  Copyright 2007 Pierre Ossman * * Inspired by if_cs.c, Copyright 2007 Holger Schurig * * 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 2 of the License, or (at * your option) any later version. * * This hardware has more or less no CMD53 support, so all registers * must be accessed using sdio_readb()/sdio_writeb(). * * Transfers must be in one transaction or the firmware goes bonkers. * This means that the transfer must either be small enough to do a * byte based transfer or it must be padded to a multiple of the * current block size. * * As SDIO is still new to the kernel, it is unfortunately common with * bugs in the host controllers related to that. One such bug is that  * controllers cannot do transfers that aren't a multiple of 4 bytes. * If you don't have time to fix the host controller driver, you can * work around the problem by modifying if_sdio_host_to_card() and * if_sdio_card_to_host() to pad the data. */#include <linux/moduleparam.h>#include <linux/firmware.h>#include <linux/netdevice.h>#include <linux/delay.h>#include <linux/mmc/card.h>#include <linux/mmc/sdio_func.h>#include <linux/mmc/sdio_ids.h>#include "host.h"#include "decl.h"#include "defs.h"#include "dev.h"#include "if_sdio.h"static char *libertas_helper_name = NULL;module_param_named(helper_name, libertas_helper_name, charp, 0644);static char *libertas_fw_name = NULL;module_param_named(fw_name, libertas_fw_name, charp, 0644);static const struct sdio_device_id if_sdio_ids[] = {	{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_LIBERTAS) },	{ /* end: all zeroes */						},};MODULE_DEVICE_TABLE(sdio, if_sdio_ids);struct if_sdio_model {	int model;	const char *helper;	const char *firmware;};static struct if_sdio_model if_sdio_models[] = {	{		/* 8385 */		.model = 0x04,		.helper = "sd8385_helper.bin",		.firmware = "sd8385.bin",	},	{		/* 8686 */		.model = 0x0B,		.helper = "sd8686_helper.bin",		.firmware = "sd8686.bin",	},};struct if_sdio_packet {	struct if_sdio_packet	*next;	u16			nb;	u8			buffer[0] __attribute__((aligned(4)));};struct if_sdio_card {	struct sdio_func	*func;	wlan_private		*priv;	int			model;	unsigned long		ioport;	const char		*helper;	const char		*firmware;	u8			buffer[65536];	u8			int_cause;	u32			event;	spinlock_t		lock;	struct if_sdio_packet	*packets;	struct work_struct	packet_worker;};/********************************************************************//* I/O                                                              *//********************************************************************/static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err){	int ret, reg;	u16 scratch;	if (card->model == 0x04)		reg = IF_SDIO_SCRATCH_OLD;	else		reg = IF_SDIO_SCRATCH;	scratch = sdio_readb(card->func, reg, &ret);	if (!ret)		scratch |= sdio_readb(card->func, reg + 1, &ret) << 8;	if (err)		*err = ret;	if (ret)		return 0xffff;	return scratch;}static int if_sdio_handle_cmd(struct if_sdio_card *card,		u8 *buffer, unsigned size){	int ret;	unsigned long flags;	lbs_deb_enter(LBS_DEB_SDIO);	spin_lock_irqsave(&card->priv->adapter->driver_lock, flags);	if (!card->priv->adapter->cur_cmd) {		lbs_deb_sdio("discarding spurious response\n");		ret = 0;		goto out;	}	if (size > MRVDRV_SIZE_OF_CMD_BUFFER) {		lbs_deb_sdio("response packet too large (%d bytes)\n",			(int)size);		ret = -E2BIG;		goto out;	}	memcpy(card->priv->adapter->cur_cmd->bufvirtualaddr, buffer, size);	card->priv->upld_len = size;	card->int_cause |= MRVDRV_CMD_UPLD_RDY;	libertas_interrupt(card->priv->dev);	ret = 0;out:	spin_unlock_irqrestore(&card->priv->adapter->driver_lock, flags);	lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);	return ret;}static int if_sdio_handle_data(struct if_sdio_card *card,		u8 *buffer, unsigned size){	int ret;	struct sk_buff *skb;	char *data;	lbs_deb_enter(LBS_DEB_SDIO);	if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {		lbs_deb_sdio("response packet too large (%d bytes)\n",			(int)size);		ret = -E2BIG;		goto out;	}	skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN);	if (!skb) {		ret = -ENOMEM;		goto out;	}	skb_reserve(skb, NET_IP_ALIGN);	data = skb_put(skb, size);	memcpy(data, buffer, size);	libertas_process_rxed_packet(card->priv, skb);	ret = 0;out:	lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);	return ret;}static int if_sdio_handle_event(struct if_sdio_card *card,		u8 *buffer, unsigned size){	int ret;	unsigned long flags;	u32 event;	lbs_deb_enter(LBS_DEB_SDIO);	if (card->model == 0x04) {		event = sdio_readb(card->func, IF_SDIO_EVENT, &ret);		if (ret)			goto out;	} else {		if (size < 4) {			lbs_deb_sdio("event packet too small (%d bytes)\n",				(int)size);			ret = -EINVAL;			goto out;		}		event = buffer[3] << 24;		event |= buffer[2] << 16;		event |= buffer[1] << 8;		event |= buffer[0] << 0;		event <<= SBI_EVENT_CAUSE_SHIFT;	}	spin_lock_irqsave(&card->priv->adapter->driver_lock, flags);	card->event = event;	card->int_cause |= MRVDRV_CARDEVENT;	libertas_interrupt(card->priv->dev);	spin_unlock_irqrestore(&card->priv->adapter->driver_lock, flags);	ret = 0;out:	lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);	return ret;}static int if_sdio_card_to_host(struct if_sdio_card *card){	int ret;	u8 status;	u16 size, type, chunk;	unsigned long timeout;	lbs_deb_enter(LBS_DEB_SDIO);	size = if_sdio_read_scratch(card, &ret);	if (ret)		goto out;	if (size < 4) {		lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n",			(int)size);		ret = -EINVAL;		goto out;	}	timeout = jiffies + HZ;	while (1) {		status = sdio_readb(card->func, IF_SDIO_STATUS, &ret);		if (ret)			goto out;		if (status & IF_SDIO_IO_RDY)			break;		if (time_after(jiffies, timeout)) {			ret = -ETIMEDOUT;			goto out;		}		mdelay(1);	}	/*	 * The transfer must be in one transaction or the firmware	 * goes suicidal.	 */	chunk = size;	if ((chunk > card->func->cur_blksize) || (chunk > 512)) {		chunk = (chunk + card->func->cur_blksize - 1) /			card->func->cur_blksize * card->func->cur_blksize;	}	ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);	if (ret)		goto out;	chunk = card->buffer[0] | (card->buffer[1] << 8);	type = card->buffer[2] | (card->buffer[3] << 8);	lbs_deb_sdio("packet of type %d and size %d bytes\n",		(int)type, (int)chunk);	if (chunk > size) {		lbs_deb_sdio("packet fragment (%d > %d)\n",			(int)chunk, (int)size);		ret = -EINVAL;		goto out;	}	if (chunk < size) {		lbs_deb_sdio("packet fragment (%d < %d)\n",			(int)chunk, (int)size);	}	switch (type) {	case MVMS_CMD:		ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);		if (ret)			goto out;		break;	case MVMS_DAT:		ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);		if (ret)			goto out;		break;	case MVMS_EVENT:		ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);		if (ret)			goto out;		break;	default:		lbs_deb_sdio("invalid type (%d) from firmware\n",				(int)type);		ret = -EINVAL;		goto out;	}out:	if (ret)		lbs_pr_err("problem fetching packet from firmware\n");	lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);	return ret;}static void if_sdio_host_to_card_worker(struct work_struct *work){	struct if_sdio_card *card;	struct if_sdio_packet *packet;	unsigned long timeout;	u8 status;	int ret;	unsigned long flags;	lbs_deb_enter(LBS_DEB_SDIO);	card = container_of(work, struct if_sdio_card, packet_worker);	while (1) {		spin_lock_irqsave(&card->lock, flags);		packet = card->packets;		if (packet)			card->packets = packet->next;		spin_unlock_irqrestore(&card->lock, flags);		if (!packet)			break;		sdio_claim_host(card->func);		timeout = jiffies + HZ;		while (1) {			status = sdio_readb(card->func, IF_SDIO_STATUS, &ret);			if (ret)				goto release;			if (status & IF_SDIO_IO_RDY)				break;			if (time_after(jiffies, timeout)) {				ret = -ETIMEDOUT;				goto release;			}			mdelay(1);		}		ret = sdio_writesb(card->func, card->ioport,				packet->buffer, packet->nb);		if (ret)			goto release;release:		sdio_release_host(card->func);		kfree(packet);	}	lbs_deb_leave(LBS_DEB_SDIO);}/********************************************************************//* Firmware                                                         *//********************************************************************/static int if_sdio_prog_helper(struct if_sdio_card *card){	int ret;	u8 status;	const struct firmware *fw;	unsigned long timeout;	u8 *chunk_buffer;	u32 chunk_size;	u8 *firmware;	size_t size;	lbs_deb_enter(LBS_DEB_SDIO);	ret = request_firmware(&fw, card->helper, &card->func->dev);	if (ret) {		lbs_pr_err("can't load helper firmware\n");		goto out;	}	chunk_buffer = kzalloc(64, GFP_KERNEL);	if (!chunk_buffer) {		ret = -ENOMEM;		goto release_fw;	}	sdio_claim_host(card->func);	ret = sdio_set_block_size(card->func, 32);	if (ret)		goto release;	firmware = fw->data;	size = fw->size;	while (size) {		timeout = jiffies + HZ;		while (1) {			status = sdio_readb(card->func, IF_SDIO_STATUS, &ret);			if (ret)				goto release;			if ((status & IF_SDIO_IO_RDY) &&					(status & IF_SDIO_DL_RDY))				break;			if (time_after(jiffies, timeout)) {				ret = -ETIMEDOUT;				goto release;			}			mdelay(1);		}		chunk_size = min(size, (size_t)60);		*((u32*)chunk_buffer) = cpu_to_le32(chunk_size);		memcpy(chunk_buffer + 4, firmware, chunk_size);/*		lbs_deb_sdio("sending %d bytes chunk\n", chunk_size);*/		ret = sdio_writesb(card->func, card->ioport,				chunk_buffer, 64);		if (ret)			goto release;		firmware += chunk_size;		size -= chunk_size;	}	/* an empty block marks the end of the transfer */	memset(chunk_buffer, 0, 4);	ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64);	if (ret)		goto release;	lbs_deb_sdio("waiting for helper to boot...\n");	/* wait for the helper to boot by looking at the size register */	timeout = jiffies + HZ;	while (1) {		u16 req_size;		req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret);		if (ret)			goto release;		req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8;		if (ret)			goto release;		if (req_size != 0)			break;		if (time_after(jiffies, timeout)) {			ret = -ETIMEDOUT;			goto release;		}		msleep(10);	}	ret = 0;release:	sdio_set_block_size(card->func, 0);	sdio_release_host(card->func);	kfree(chunk_buffer);release_fw:	release_firmware(fw);out:	if (ret)		lbs_pr_err("failed to load helper firmware\n");	lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);	return ret;}static int if_sdio_prog_real(struct if_sdio_card *card){	int ret;	u8 status;	const struct firmware *fw;	unsigned long timeout;	u8 *chunk_buffer;	u32 chunk_size;	u8 *firmware;	size_t size, req_size;	lbs_deb_enter(LBS_DEB_SDIO);	ret = request_firmware(&fw, card->firmware, &card->func->dev);	if (ret) {		lbs_pr_err("can't load firmware\n");		goto out;	}	chunk_buffer = kzalloc(512, GFP_KERNEL);	if (!chunk_buffer) {		ret = -ENOMEM;		goto release_fw;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -