📄 if_sdio.c
字号:
/* * 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 + -