Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
29
drivers/mmc/Kconfig
Normal file
29
drivers/mmc/Kconfig
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# MMC subsystem configuration
|
||||
#
|
||||
|
||||
menuconfig MMC
|
||||
tristate "MMC/SD card support"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
MMC is the "multi-media card" bus protocol.
|
||||
|
||||
If you want MMC support, you should say Y here and also
|
||||
to the specific driver for your MMC interface.
|
||||
|
||||
config MMC_DEBUG
|
||||
bool "MMC debugging"
|
||||
depends on MMC != n
|
||||
help
|
||||
This is an option for use by developers; most people should
|
||||
say N here. This enables MMC core and driver debugging.
|
||||
|
||||
if MMC
|
||||
|
||||
source "drivers/mmc/core/Kconfig"
|
||||
|
||||
source "drivers/mmc/card/Kconfig"
|
||||
|
||||
source "drivers/mmc/host/Kconfig"
|
||||
|
||||
endif # MMC
|
||||
12
drivers/mmc/Makefile
Normal file
12
drivers/mmc/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# Makefile for the kernel mmc device drivers.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MMC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_MMC) += core/
|
||||
obj-$(CONFIG_MMC) += card/
|
||||
obj-$(CONFIG_MMC) += host/
|
||||
|
||||
41
drivers/mmc/card/Kconfig
Normal file
41
drivers/mmc/card/Kconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# MMC/SD card drivers
|
||||
#
|
||||
|
||||
comment "MMC/SD Card Drivers"
|
||||
|
||||
config MMC_BLOCK
|
||||
tristate "MMC block device driver"
|
||||
depends on BLOCK
|
||||
default y
|
||||
help
|
||||
Say Y here to enable the MMC block device driver support.
|
||||
This provides a block device driver, which you can use to
|
||||
mount the filesystem. Almost everyone wishing MMC support
|
||||
should say Y or M here.
|
||||
|
||||
config MMC_BLOCK_BOUNCE
|
||||
bool "Use bounce buffer for simple hosts"
|
||||
depends on MMC_BLOCK
|
||||
default y
|
||||
help
|
||||
SD/MMC is a high latency protocol where it is crucial to
|
||||
send large requests in order to get high performance. Many
|
||||
controllers, however, are restricted to continuous memory
|
||||
(i.e. they can't do scatter-gather), something the kernel
|
||||
rarely can provide.
|
||||
|
||||
Say Y here to help these restricted hosts by bouncing
|
||||
requests back and forth from a large buffer. You will get
|
||||
a big performance gain at the cost of up to 64 KiB of
|
||||
physical memory.
|
||||
|
||||
If unsure, say Y here.
|
||||
|
||||
config SDIO_UART
|
||||
tristate "SDIO UART/GPS class support"
|
||||
depends on MMC
|
||||
help
|
||||
SDIO function driver for SDIO cards that implements the UART
|
||||
class, as well as the GPS class which appears like a UART.
|
||||
|
||||
13
drivers/mmc/card/Makefile
Normal file
13
drivers/mmc/card/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Makefile for MMC/SD card drivers
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MMC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
|
||||
mmc_block-objs := block.o queue.o
|
||||
|
||||
obj-$(CONFIG_SDIO_UART) += sdio_uart.o
|
||||
|
||||
658
drivers/mmc/card/block.c
Normal file
658
drivers/mmc/card/block.c
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* Block driver for media (i.e., flash cards)
|
||||
*
|
||||
* Copyright 2002 Hewlett-Packard Company
|
||||
* Copyright 2005-2007 Pierre Ossman
|
||||
*
|
||||
* Use consistent with the GNU GPL is permitted,
|
||||
* provided that this copyright notice is
|
||||
* preserved in its entirety in all copies and derived works.
|
||||
*
|
||||
* HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
|
||||
* AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
|
||||
* FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*
|
||||
* Many thanks to Alessandro Rubini and Jonathan Corbet!
|
||||
*
|
||||
* Author: Andrew Christian
|
||||
* 28 May 2002
|
||||
*/
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include <asm/system.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
/*
|
||||
* max 8 partitions per card
|
||||
*/
|
||||
#define MMC_SHIFT 3
|
||||
#define MMC_NUM_MINORS (256 >> MMC_SHIFT)
|
||||
|
||||
static unsigned long dev_use[MMC_NUM_MINORS/(8*sizeof(unsigned long))];
|
||||
|
||||
/*
|
||||
* There is one mmc_blk_data per slot.
|
||||
*/
|
||||
struct mmc_blk_data {
|
||||
spinlock_t lock;
|
||||
struct gendisk *disk;
|
||||
struct mmc_queue queue;
|
||||
|
||||
unsigned int usage;
|
||||
unsigned int block_bits;
|
||||
unsigned int read_only;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(open_lock);
|
||||
|
||||
static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
|
||||
mutex_lock(&open_lock);
|
||||
md = disk->private_data;
|
||||
if (md && md->usage == 0)
|
||||
md = NULL;
|
||||
if (md)
|
||||
md->usage++;
|
||||
mutex_unlock(&open_lock);
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
static void mmc_blk_put(struct mmc_blk_data *md)
|
||||
{
|
||||
mutex_lock(&open_lock);
|
||||
md->usage--;
|
||||
if (md->usage == 0) {
|
||||
int devidx = md->disk->first_minor >> MMC_SHIFT;
|
||||
__clear_bit(devidx, dev_use);
|
||||
|
||||
put_disk(md->disk);
|
||||
kfree(md);
|
||||
}
|
||||
mutex_unlock(&open_lock);
|
||||
}
|
||||
|
||||
static int mmc_blk_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
int ret = -ENXIO;
|
||||
|
||||
md = mmc_blk_get(inode->i_bdev->bd_disk);
|
||||
if (md) {
|
||||
if (md->usage == 2)
|
||||
check_disk_change(inode->i_bdev);
|
||||
ret = 0;
|
||||
|
||||
if ((filp->f_mode & FMODE_WRITE) && md->read_only)
|
||||
ret = -EROFS;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_blk_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct mmc_blk_data *md = inode->i_bdev->bd_disk->private_data;
|
||||
|
||||
mmc_blk_put(md);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
|
||||
{
|
||||
geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
|
||||
geo->heads = 4;
|
||||
geo->sectors = 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct block_device_operations mmc_bdops = {
|
||||
.open = mmc_blk_open,
|
||||
.release = mmc_blk_release,
|
||||
.getgeo = mmc_blk_getgeo,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct mmc_blk_request {
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_command stop;
|
||||
struct mmc_data data;
|
||||
};
|
||||
|
||||
static u32 mmc_sd_num_wr_blocks(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u32 blocks;
|
||||
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
unsigned int timeout_us;
|
||||
|
||||
struct scatterlist sg;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_APP_CMD;
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, 0);
|
||||
if (err)
|
||||
return (u32)-1;
|
||||
if (!mmc_host_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD))
|
||||
return (u32)-1;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_APP_SEND_NUM_WR_BLKS;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
data.timeout_ns = card->csd.tacc_ns * 100;
|
||||
data.timeout_clks = card->csd.tacc_clks * 100;
|
||||
|
||||
timeout_us = data.timeout_ns / 1000;
|
||||
timeout_us += data.timeout_clks * 1000 /
|
||||
(card->host->ios.clock / 1000);
|
||||
|
||||
if (timeout_us > 100000) {
|
||||
data.timeout_ns = 100000000;
|
||||
data.timeout_clks = 0;
|
||||
}
|
||||
|
||||
data.blksz = 4;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
sg_init_one(&sg, &blocks, 4);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error || data.error)
|
||||
return (u32)-1;
|
||||
|
||||
blocks = ntohl(blocks);
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
||||
{
|
||||
struct mmc_blk_data *md = mq->data;
|
||||
struct mmc_card *card = md->queue.card;
|
||||
struct mmc_blk_request brq;
|
||||
int ret = 1, sg_pos, data_size;
|
||||
|
||||
mmc_claim_host(card->host);
|
||||
|
||||
do {
|
||||
struct mmc_command cmd;
|
||||
u32 readcmd, writecmd;
|
||||
|
||||
memset(&brq, 0, sizeof(struct mmc_blk_request));
|
||||
brq.mrq.cmd = &brq.cmd;
|
||||
brq.mrq.data = &brq.data;
|
||||
|
||||
brq.cmd.arg = req->sector;
|
||||
if (!mmc_card_blockaddr(card))
|
||||
brq.cmd.arg <<= 9;
|
||||
brq.cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
brq.data.blksz = 1 << md->block_bits;
|
||||
brq.stop.opcode = MMC_STOP_TRANSMISSION;
|
||||
brq.stop.arg = 0;
|
||||
brq.stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
||||
brq.data.blocks = req->nr_sectors >> (md->block_bits - 9);
|
||||
if (brq.data.blocks > card->host->max_blk_count)
|
||||
brq.data.blocks = card->host->max_blk_count;
|
||||
|
||||
/*
|
||||
* If the host doesn't support multiple block writes, force
|
||||
* block writes to single block. SD cards are excepted from
|
||||
* this rule as they support querying the number of
|
||||
* successfully written sectors.
|
||||
*/
|
||||
if (rq_data_dir(req) != READ &&
|
||||
!(card->host->caps & MMC_CAP_MULTIWRITE) &&
|
||||
!mmc_card_sd(card))
|
||||
brq.data.blocks = 1;
|
||||
|
||||
if (brq.data.blocks > 1) {
|
||||
|
||||
/* Qisda, Daniel Lee, 2009/07/21, e600 { */
|
||||
// For S3C2416
|
||||
brq.data.flags |= MMC_DATA_MULTI;
|
||||
/* Qisda, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
/* SPI multiblock writes terminate using a special
|
||||
* token, not a STOP_TRANSMISSION request.
|
||||
*/
|
||||
if (!mmc_host_is_spi(card->host)
|
||||
|| rq_data_dir(req) == READ)
|
||||
brq.mrq.stop = &brq.stop;
|
||||
readcmd = MMC_READ_MULTIPLE_BLOCK;
|
||||
writecmd = MMC_WRITE_MULTIPLE_BLOCK;
|
||||
} else {
|
||||
brq.mrq.stop = NULL;
|
||||
readcmd = MMC_READ_SINGLE_BLOCK;
|
||||
writecmd = MMC_WRITE_BLOCK;
|
||||
}
|
||||
|
||||
if (rq_data_dir(req) == READ) {
|
||||
brq.cmd.opcode = readcmd;
|
||||
brq.data.flags |= MMC_DATA_READ;
|
||||
} else {
|
||||
brq.cmd.opcode = writecmd;
|
||||
brq.data.flags |= MMC_DATA_WRITE;
|
||||
}
|
||||
|
||||
mmc_set_data_timeout(&brq.data, card);
|
||||
|
||||
brq.data.sg = mq->sg;
|
||||
brq.data.sg_len = mmc_queue_map_sg(mq);
|
||||
|
||||
mmc_queue_bounce_pre(mq);
|
||||
|
||||
if (brq.data.blocks !=
|
||||
(req->nr_sectors >> (md->block_bits - 9))) {
|
||||
data_size = brq.data.blocks * brq.data.blksz;
|
||||
for (sg_pos = 0; sg_pos < brq.data.sg_len; sg_pos++) {
|
||||
data_size -= mq->sg[sg_pos].length;
|
||||
if (data_size <= 0) {
|
||||
mq->sg[sg_pos].length += data_size;
|
||||
sg_pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
brq.data.sg_len = sg_pos;
|
||||
}
|
||||
|
||||
mmc_wait_for_req(card->host, &brq.mrq);
|
||||
|
||||
mmc_queue_bounce_post(mq);
|
||||
|
||||
if (brq.cmd.error) {
|
||||
printk(KERN_ERR "%s: error %d sending read/write command\n",
|
||||
req->rq_disk->disk_name, brq.cmd.error);
|
||||
goto cmd_err;
|
||||
}
|
||||
|
||||
if (brq.data.error) {
|
||||
printk(KERN_ERR "%s: error %d transferring data\n",
|
||||
req->rq_disk->disk_name, brq.data.error);
|
||||
goto cmd_err;
|
||||
}
|
||||
|
||||
if (brq.stop.error) {
|
||||
printk(KERN_ERR "%s: error %d sending stop command\n",
|
||||
req->rq_disk->disk_name, brq.stop.error);
|
||||
goto cmd_err;
|
||||
}
|
||||
|
||||
if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
|
||||
do {
|
||||
int err;
|
||||
|
||||
cmd.opcode = MMC_SEND_STATUS;
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, 5);
|
||||
if (err) {
|
||||
printk(KERN_ERR "%s: error %d requesting status\n",
|
||||
req->rq_disk->disk_name, err);
|
||||
goto cmd_err;
|
||||
}
|
||||
/*
|
||||
* Some cards mishandle the status bits,
|
||||
* so make sure to check both the busy
|
||||
* indication and the card state.
|
||||
*/
|
||||
} while (!(cmd.resp[0] & R1_READY_FOR_DATA) ||
|
||||
(R1_CURRENT_STATE(cmd.resp[0]) == 7));
|
||||
|
||||
#if 0
|
||||
if (cmd.resp[0] & ~0x00000900)
|
||||
printk(KERN_ERR "%s: status = %08x\n",
|
||||
req->rq_disk->disk_name, cmd.resp[0]);
|
||||
if (mmc_decode_status(cmd.resp))
|
||||
goto cmd_err;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* A block was successfully transferred.
|
||||
*/
|
||||
spin_lock_irq(&md->lock);
|
||||
ret = end_that_request_chunk(req, 1, brq.data.bytes_xfered);
|
||||
if (!ret) {
|
||||
/*
|
||||
* The whole request completed successfully.
|
||||
*/
|
||||
add_disk_randomness(req->rq_disk);
|
||||
blkdev_dequeue_request(req);
|
||||
end_that_request_last(req, 1);
|
||||
}
|
||||
spin_unlock_irq(&md->lock);
|
||||
} while (ret);
|
||||
|
||||
mmc_release_host(card->host);
|
||||
|
||||
return 1;
|
||||
|
||||
cmd_err:
|
||||
/*
|
||||
* If this is an SD card and we're writing, we can first
|
||||
* mark the known good sectors as ok.
|
||||
*
|
||||
* If the card is not SD, we can still ok written sectors
|
||||
* if the controller can do proper error reporting.
|
||||
*
|
||||
* For reads we just fail the entire chunk as that should
|
||||
* be safe in all cases.
|
||||
*/
|
||||
if (rq_data_dir(req) != READ && mmc_card_sd(card)) {
|
||||
u32 blocks;
|
||||
unsigned int bytes;
|
||||
|
||||
blocks = mmc_sd_num_wr_blocks(card);
|
||||
if (blocks != (u32)-1) {
|
||||
if (card->csd.write_partial)
|
||||
bytes = blocks << md->block_bits;
|
||||
else
|
||||
bytes = blocks << 9;
|
||||
spin_lock_irq(&md->lock);
|
||||
ret = end_that_request_chunk(req, 1, bytes);
|
||||
spin_unlock_irq(&md->lock);
|
||||
}
|
||||
} else if (rq_data_dir(req) != READ &&
|
||||
(card->host->caps & MMC_CAP_MULTIWRITE)) {
|
||||
spin_lock_irq(&md->lock);
|
||||
ret = end_that_request_chunk(req, 1, brq.data.bytes_xfered);
|
||||
spin_unlock_irq(&md->lock);
|
||||
}
|
||||
|
||||
mmc_release_host(card->host);
|
||||
|
||||
spin_lock_irq(&md->lock);
|
||||
while (ret) {
|
||||
ret = end_that_request_chunk(req, 0,
|
||||
req->current_nr_sectors << 9);
|
||||
}
|
||||
|
||||
add_disk_randomness(req->rq_disk);
|
||||
blkdev_dequeue_request(req);
|
||||
end_that_request_last(req, 0);
|
||||
spin_unlock_irq(&md->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int mmc_blk_readonly(struct mmc_card *card)
|
||||
{
|
||||
return mmc_card_readonly(card) ||
|
||||
!(card->csd.cmdclass & CCC_BLOCK_WRITE);
|
||||
}
|
||||
|
||||
static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
int devidx, ret;
|
||||
|
||||
devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS);
|
||||
if (devidx >= MMC_NUM_MINORS)
|
||||
return ERR_PTR(-ENOSPC);
|
||||
__set_bit(devidx, dev_use);
|
||||
|
||||
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
|
||||
if (!md) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set the read-only status based on the supported commands
|
||||
* and the write protect switch.
|
||||
*/
|
||||
md->read_only = mmc_blk_readonly(card);
|
||||
|
||||
/*
|
||||
* Both SD and MMC specifications state (although a bit
|
||||
* unclearly in the MMC case) that a block size of 512
|
||||
* bytes must always be supported by the card.
|
||||
*/
|
||||
md->block_bits = 9;
|
||||
|
||||
md->disk = alloc_disk(1 << MMC_SHIFT);
|
||||
if (md->disk == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_kfree;
|
||||
}
|
||||
|
||||
spin_lock_init(&md->lock);
|
||||
md->usage = 1;
|
||||
|
||||
ret = mmc_init_queue(&md->queue, card, &md->lock);
|
||||
if (ret)
|
||||
goto err_putdisk;
|
||||
|
||||
md->queue.issue_fn = mmc_blk_issue_rq;
|
||||
md->queue.data = md;
|
||||
|
||||
md->disk->major = MMC_BLOCK_MAJOR;
|
||||
md->disk->first_minor = devidx << MMC_SHIFT;
|
||||
md->disk->fops = &mmc_bdops;
|
||||
md->disk->private_data = md;
|
||||
md->disk->queue = md->queue.queue;
|
||||
md->disk->driverfs_dev = &card->dev;
|
||||
|
||||
/*
|
||||
* As discussed on lkml, GENHD_FL_REMOVABLE should:
|
||||
*
|
||||
* - be set for removable media with permanent block devices
|
||||
* - be unset for removable block devices with permanent media
|
||||
*
|
||||
* Since MMC block devices clearly fall under the second
|
||||
* case, we do not set GENHD_FL_REMOVABLE. Userspace
|
||||
* should use the block device creation/destruction hotplug
|
||||
* messages to tell when the card is present.
|
||||
*/
|
||||
|
||||
sprintf(md->disk->disk_name, "mmcblk%d", devidx);
|
||||
|
||||
blk_queue_hardsect_size(md->queue.queue, 1 << md->block_bits);
|
||||
|
||||
if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
|
||||
/*
|
||||
* The EXT_CSD sector count is in number or 512 byte
|
||||
* sectors.
|
||||
*/
|
||||
set_capacity(md->disk, card->ext_csd.sectors);
|
||||
} else {
|
||||
/*
|
||||
* The CSD capacity field is in units of read_blkbits.
|
||||
* set_capacity takes units of 512 bytes.
|
||||
*/
|
||||
set_capacity(md->disk,
|
||||
card->csd.capacity << (card->csd.read_blkbits - 9));
|
||||
}
|
||||
return md;
|
||||
|
||||
err_putdisk:
|
||||
put_disk(md->disk);
|
||||
err_kfree:
|
||||
kfree(md);
|
||||
out:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
/* Block-addressed cards ignore MMC_SET_BLOCKLEN. */
|
||||
if (mmc_card_blockaddr(card))
|
||||
return 0;
|
||||
|
||||
mmc_claim_host(card->host);
|
||||
cmd.opcode = MMC_SET_BLOCKLEN;
|
||||
cmd.arg = 1 << md->block_bits;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, 5);
|
||||
mmc_release_host(card->host);
|
||||
|
||||
if (err) {
|
||||
printk(KERN_ERR "%s: unable to set block size to %d: %d\n",
|
||||
md->disk->disk_name, cmd.arg, err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_blk_probe(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Check that the card supports the command class(es) we need.
|
||||
*/
|
||||
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
|
||||
return -ENODEV;
|
||||
|
||||
md = mmc_blk_alloc(card);
|
||||
if (IS_ERR(md))
|
||||
return PTR_ERR(md);
|
||||
|
||||
err = mmc_blk_set_blksize(md, card);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
printk(KERN_INFO "%s: %s %s %lluKiB %s\n",
|
||||
md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
|
||||
(unsigned long long)(get_capacity(md->disk) >> 1),
|
||||
md->read_only ? "(ro)" : "");
|
||||
|
||||
mmc_set_drvdata(card, md);
|
||||
add_disk(md->disk);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
mmc_blk_put(md);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mmc_blk_remove(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_get_drvdata(card);
|
||||
|
||||
if (md) {
|
||||
/* Stop new requests from getting into the queue */
|
||||
del_gendisk(md->disk);
|
||||
|
||||
/* Then flush out any already in there */
|
||||
mmc_cleanup_queue(&md->queue);
|
||||
|
||||
mmc_blk_put(md);
|
||||
}
|
||||
mmc_set_drvdata(card, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mmc_blk_suspend(struct mmc_card *card, pm_message_t state)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_get_drvdata(card);
|
||||
|
||||
if (md) {
|
||||
mmc_queue_suspend(&md->queue);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_blk_resume(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_get_drvdata(card);
|
||||
|
||||
if (md) {
|
||||
mmc_blk_set_blksize(md, card);
|
||||
mmc_queue_resume(&md->queue);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define mmc_blk_suspend NULL
|
||||
#define mmc_blk_resume NULL
|
||||
#endif
|
||||
|
||||
static struct mmc_driver mmc_driver = {
|
||||
.drv = {
|
||||
.name = "mmcblk",
|
||||
},
|
||||
.probe = mmc_blk_probe,
|
||||
.remove = mmc_blk_remove,
|
||||
.suspend = mmc_blk_suspend,
|
||||
.resume = mmc_blk_resume,
|
||||
};
|
||||
|
||||
static int __init mmc_blk_init(void)
|
||||
{
|
||||
int res = -ENOMEM;
|
||||
|
||||
res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");
|
||||
if (res)
|
||||
goto out;
|
||||
|
||||
return mmc_register_driver(&mmc_driver);
|
||||
|
||||
out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void __exit mmc_blk_exit(void)
|
||||
{
|
||||
mmc_unregister_driver(&mmc_driver);
|
||||
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
|
||||
}
|
||||
|
||||
module_init(mmc_blk_init);
|
||||
module_exit(mmc_blk_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Multimedia Card (MMC) block device driver");
|
||||
|
||||
416
drivers/mmc/card/queue.c
Normal file
416
drivers/mmc/card/queue.c
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* linux/drivers/mmc/card/queue.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include "queue.h"
|
||||
|
||||
#define MMC_QUEUE_BOUNCESZ 65536
|
||||
|
||||
#define MMC_QUEUE_SUSPENDED (1 << 0)
|
||||
|
||||
/*
|
||||
* Prepare a MMC request. This just filters out odd stuff.
|
||||
*/
|
||||
static int mmc_prep_request(struct request_queue *q, struct request *req)
|
||||
{
|
||||
/*
|
||||
* We only like normal block requests.
|
||||
*/
|
||||
if (!blk_fs_request(req) && !blk_pc_request(req)) {
|
||||
blk_dump_rq_flags(req, "MMC bad request");
|
||||
return BLKPREP_KILL;
|
||||
}
|
||||
|
||||
req->cmd_flags |= REQ_DONTPREP;
|
||||
|
||||
return BLKPREP_OK;
|
||||
}
|
||||
|
||||
static int mmc_queue_thread(void *d)
|
||||
{
|
||||
struct mmc_queue *mq = d;
|
||||
struct request_queue *q = mq->queue;
|
||||
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.21
|
||||
/*
|
||||
* Set iothread to ensure that we aren't put to sleep by
|
||||
* the process freezing. We handle suspension ourselves.
|
||||
*/
|
||||
current->flags |= PF_MEMALLOC | PF_NOFREEZE;
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
down(&mq->thread_sem);
|
||||
do {
|
||||
struct request *req = NULL;
|
||||
|
||||
spin_lock_irq(q->queue_lock);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (!blk_queue_plugged(q))
|
||||
req = elv_next_request(q);
|
||||
mq->req = req;
|
||||
spin_unlock_irq(q->queue_lock);
|
||||
|
||||
if (!req) {
|
||||
if (kthread_should_stop()) {
|
||||
set_current_state(TASK_RUNNING);
|
||||
break;
|
||||
}
|
||||
up(&mq->thread_sem);
|
||||
schedule();
|
||||
down(&mq->thread_sem);
|
||||
continue;
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
|
||||
mq->issue_fn(mq, req);
|
||||
} while (1);
|
||||
up(&mq->thread_sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic MMC request handler. This is called for any queue on a
|
||||
* particular host. When the host is not busy, we look for a request
|
||||
* on any queue on this host, and attempt to issue it. This may
|
||||
* not be the queue we were asked to process.
|
||||
*/
|
||||
static void mmc_request(struct request_queue *q)
|
||||
{
|
||||
struct mmc_queue *mq = q->queuedata;
|
||||
struct request *req;
|
||||
int ret;
|
||||
|
||||
if (!mq) {
|
||||
printk(KERN_ERR "MMC: killing requests for dead queue\n");
|
||||
while ((req = elv_next_request(q)) != NULL) {
|
||||
do {
|
||||
ret = end_that_request_chunk(req, 0,
|
||||
req->current_nr_sectors << 9);
|
||||
} while (ret);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mq->req)
|
||||
wake_up_process(mq->thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_init_queue - initialise a queue structure.
|
||||
* @mq: mmc queue
|
||||
* @card: mmc card to attach this queue
|
||||
* @lock: queue lock
|
||||
*
|
||||
* Initialise a MMC card request queue.
|
||||
*/
|
||||
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
u64 limit = BLK_BOUNCE_HIGH;
|
||||
int ret;
|
||||
|
||||
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
|
||||
limit = *mmc_dev(host)->dma_mask;
|
||||
|
||||
mq->card = card;
|
||||
mq->queue = blk_init_queue(mmc_request, lock);
|
||||
if (!mq->queue)
|
||||
return -ENOMEM;
|
||||
|
||||
mq->queue->queuedata = mq;
|
||||
mq->req = NULL;
|
||||
|
||||
blk_queue_prep_rq(mq->queue, mmc_prep_request);
|
||||
|
||||
#ifdef CONFIG_MMC_BLOCK_BOUNCE
|
||||
if (host->max_hw_segs == 1) {
|
||||
unsigned int bouncesz;
|
||||
|
||||
bouncesz = MMC_QUEUE_BOUNCESZ;
|
||||
|
||||
if (bouncesz > host->max_req_size)
|
||||
bouncesz = host->max_req_size;
|
||||
if (bouncesz > host->max_seg_size)
|
||||
bouncesz = host->max_seg_size;
|
||||
|
||||
mq->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
|
||||
if (!mq->bounce_buf) {
|
||||
printk(KERN_WARNING "%s: unable to allocate "
|
||||
"bounce buffer\n", mmc_card_name(card));
|
||||
} else {
|
||||
blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_HIGH);
|
||||
blk_queue_max_sectors(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_phys_segments(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_hw_segments(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_segment_size(mq->queue, bouncesz);
|
||||
|
||||
mq->sg = kmalloc(sizeof(struct scatterlist),
|
||||
GFP_KERNEL);
|
||||
if (!mq->sg) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_queue;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.23.17.
|
||||
//sg_init_table(mq->sg, 1);
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
mq->bounce_sg = kmalloc(sizeof(struct scatterlist) *
|
||||
bouncesz / 512, GFP_KERNEL);
|
||||
if (!mq->bounce_sg) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_queue;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.23.17.
|
||||
//sg_init_table(mq->bounce_sg, bouncesz / 512);
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mq->bounce_buf) {
|
||||
blk_queue_bounce_limit(mq->queue, limit);
|
||||
blk_queue_max_sectors(mq->queue, host->max_req_size / 512);
|
||||
blk_queue_max_phys_segments(mq->queue, host->max_phys_segs);
|
||||
blk_queue_max_hw_segments(mq->queue, host->max_hw_segs);
|
||||
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
|
||||
|
||||
mq->sg = kmalloc(sizeof(struct scatterlist) *
|
||||
host->max_phys_segs, GFP_KERNEL);
|
||||
if (!mq->sg) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_queue;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.23.17.
|
||||
//sg_init_table(mq->sg, host->max_phys_segs);
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
}
|
||||
|
||||
init_MUTEX(&mq->thread_sem);
|
||||
|
||||
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");
|
||||
if (IS_ERR(mq->thread)) {
|
||||
ret = PTR_ERR(mq->thread);
|
||||
goto free_bounce_sg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
free_bounce_sg:
|
||||
if (mq->bounce_sg)
|
||||
kfree(mq->bounce_sg);
|
||||
mq->bounce_sg = NULL;
|
||||
cleanup_queue:
|
||||
if (mq->sg)
|
||||
kfree(mq->sg);
|
||||
mq->sg = NULL;
|
||||
if (mq->bounce_buf)
|
||||
kfree(mq->bounce_buf);
|
||||
mq->bounce_buf = NULL;
|
||||
blk_cleanup_queue(mq->queue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mmc_cleanup_queue(struct mmc_queue *mq)
|
||||
{
|
||||
struct request_queue *q = mq->queue;
|
||||
unsigned long flags;
|
||||
|
||||
/* Mark that we should start throwing out stragglers */
|
||||
spin_lock_irqsave(q->queue_lock, flags);
|
||||
q->queuedata = NULL;
|
||||
spin_unlock_irqrestore(q->queue_lock, flags);
|
||||
|
||||
/* Make sure the queue isn't suspended, as that will deadlock */
|
||||
mmc_queue_resume(mq);
|
||||
|
||||
/* Then terminate our worker thread */
|
||||
kthread_stop(mq->thread);
|
||||
|
||||
if (mq->bounce_sg)
|
||||
kfree(mq->bounce_sg);
|
||||
mq->bounce_sg = NULL;
|
||||
|
||||
kfree(mq->sg);
|
||||
mq->sg = NULL;
|
||||
|
||||
if (mq->bounce_buf)
|
||||
kfree(mq->bounce_buf);
|
||||
mq->bounce_buf = NULL;
|
||||
|
||||
blk_cleanup_queue(mq->queue);
|
||||
|
||||
mq->card = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_cleanup_queue);
|
||||
|
||||
/**
|
||||
* mmc_queue_suspend - suspend a MMC request queue
|
||||
* @mq: MMC queue to suspend
|
||||
*
|
||||
* Stop the block request queue, and wait for our thread to
|
||||
* complete any outstanding requests. This ensures that we
|
||||
* won't suspend while a request is being processed.
|
||||
*/
|
||||
void mmc_queue_suspend(struct mmc_queue *mq)
|
||||
{
|
||||
struct request_queue *q = mq->queue;
|
||||
unsigned long flags;
|
||||
|
||||
if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
|
||||
mq->flags |= MMC_QUEUE_SUSPENDED;
|
||||
|
||||
spin_lock_irqsave(q->queue_lock, flags);
|
||||
blk_stop_queue(q);
|
||||
spin_unlock_irqrestore(q->queue_lock, flags);
|
||||
|
||||
down(&mq->thread_sem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_queue_resume - resume a previously suspended MMC request queue
|
||||
* @mq: MMC queue to resume
|
||||
*/
|
||||
void mmc_queue_resume(struct mmc_queue *mq)
|
||||
{
|
||||
struct request_queue *q = mq->queue;
|
||||
unsigned long flags;
|
||||
|
||||
if (mq->flags & MMC_QUEUE_SUSPENDED) {
|
||||
mq->flags &= ~MMC_QUEUE_SUSPENDED;
|
||||
|
||||
up(&mq->thread_sem);
|
||||
|
||||
spin_lock_irqsave(q->queue_lock, flags);
|
||||
blk_start_queue(q);
|
||||
spin_unlock_irqrestore(q->queue_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_sg(struct scatterlist *dst, unsigned int dst_len,
|
||||
struct scatterlist *src, unsigned int src_len)
|
||||
{
|
||||
unsigned int chunk;
|
||||
char *dst_buf, *src_buf;
|
||||
unsigned int dst_size, src_size;
|
||||
|
||||
dst_buf = NULL;
|
||||
src_buf = NULL;
|
||||
dst_size = 0;
|
||||
src_size = 0;
|
||||
|
||||
while (src_len) {
|
||||
BUG_ON(dst_len == 0);
|
||||
|
||||
if (dst_size == 0) {
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.23.17.
|
||||
dst_buf = page_address(dst->page) + dst->offset;
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
dst_size = dst->length;
|
||||
}
|
||||
|
||||
if (src_size == 0) {
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Refer to 2.6.23.17.
|
||||
src_buf = page_address(src->page) + src->offset;
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
src_size = src->length;
|
||||
}
|
||||
|
||||
chunk = min(dst_size, src_size);
|
||||
|
||||
memcpy(dst_buf, src_buf, chunk);
|
||||
|
||||
dst_buf += chunk;
|
||||
src_buf += chunk;
|
||||
dst_size -= chunk;
|
||||
src_size -= chunk;
|
||||
|
||||
if (dst_size == 0) {
|
||||
dst++;
|
||||
dst_len--;
|
||||
}
|
||||
|
||||
if (src_size == 0) {
|
||||
src++;
|
||||
src_len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int mmc_queue_map_sg(struct mmc_queue *mq)
|
||||
{
|
||||
unsigned int sg_len;
|
||||
|
||||
if (!mq->bounce_buf)
|
||||
return blk_rq_map_sg(mq->queue, mq->req, mq->sg);
|
||||
|
||||
BUG_ON(!mq->bounce_sg);
|
||||
|
||||
sg_len = blk_rq_map_sg(mq->queue, mq->req, mq->bounce_sg);
|
||||
|
||||
mq->bounce_sg_len = sg_len;
|
||||
|
||||
/*
|
||||
* Shortcut in the event we only get a single entry.
|
||||
*/
|
||||
if (sg_len == 1) {
|
||||
memcpy(mq->sg, mq->bounce_sg, sizeof(struct scatterlist));
|
||||
return 1;
|
||||
}
|
||||
|
||||
sg_init_one(mq->sg, mq->bounce_buf, 0);
|
||||
|
||||
while (sg_len) {
|
||||
mq->sg[0].length += mq->bounce_sg[sg_len - 1].length;
|
||||
sg_len--;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void mmc_queue_bounce_pre(struct mmc_queue *mq)
|
||||
{
|
||||
if (!mq->bounce_buf)
|
||||
return;
|
||||
|
||||
if (mq->bounce_sg_len == 1)
|
||||
return;
|
||||
if (rq_data_dir(mq->req) != WRITE)
|
||||
return;
|
||||
|
||||
copy_sg(mq->sg, 1, mq->bounce_sg, mq->bounce_sg_len);
|
||||
}
|
||||
|
||||
void mmc_queue_bounce_post(struct mmc_queue *mq)
|
||||
{
|
||||
if (!mq->bounce_buf)
|
||||
return;
|
||||
|
||||
if (mq->bounce_sg_len == 1)
|
||||
return;
|
||||
if (rq_data_dir(mq->req) != READ)
|
||||
return;
|
||||
|
||||
copy_sg(mq->bounce_sg, mq->bounce_sg_len, mq->sg, 1);
|
||||
}
|
||||
|
||||
31
drivers/mmc/card/queue.h
Normal file
31
drivers/mmc/card/queue.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef MMC_QUEUE_H
|
||||
#define MMC_QUEUE_H
|
||||
|
||||
struct request;
|
||||
struct task_struct;
|
||||
|
||||
struct mmc_queue {
|
||||
struct mmc_card *card;
|
||||
struct task_struct *thread;
|
||||
struct semaphore thread_sem;
|
||||
unsigned int flags;
|
||||
struct request *req;
|
||||
int (*issue_fn)(struct mmc_queue *, struct request *);
|
||||
void *data;
|
||||
struct request_queue *queue;
|
||||
struct scatterlist *sg;
|
||||
char *bounce_buf;
|
||||
struct scatterlist *bounce_sg;
|
||||
unsigned int bounce_sg_len;
|
||||
};
|
||||
|
||||
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *);
|
||||
extern void mmc_cleanup_queue(struct mmc_queue *);
|
||||
extern void mmc_queue_suspend(struct mmc_queue *);
|
||||
extern void mmc_queue_resume(struct mmc_queue *);
|
||||
|
||||
extern unsigned int mmc_queue_map_sg(struct mmc_queue *);
|
||||
extern void mmc_queue_bounce_pre(struct mmc_queue *);
|
||||
extern void mmc_queue_bounce_post(struct mmc_queue *);
|
||||
|
||||
#endif
|
||||
1158
drivers/mmc/card/sdio_uart.c
Normal file
1158
drivers/mmc/card/sdio_uart.c
Normal file
File diff suppressed because it is too large
Load Diff
16
drivers/mmc/core/Kconfig
Normal file
16
drivers/mmc/core/Kconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# MMC core configuration
|
||||
#
|
||||
|
||||
config MMC_UNSAFE_RESUME
|
||||
bool "Allow unsafe resume (DANGEROUS)"
|
||||
help
|
||||
If you say Y here, the MMC layer will assume that all cards
|
||||
stayed in their respective slots during the suspend. The
|
||||
normal behaviour is to remove them at suspend and
|
||||
redetecting them at resume. Breaking this assumption will
|
||||
in most cases result in data corruption.
|
||||
|
||||
This option is usually just for embedded systems which use
|
||||
a MMC/SD card for rootfs. Most people should say N here.
|
||||
|
||||
14
drivers/mmc/core/Makefile
Normal file
14
drivers/mmc/core/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# Makefile for the kernel mmc core.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MMC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_MMC) += mmc_core.o
|
||||
mmc_core-y := core.o sysfs.o bus.o host.o \
|
||||
mmc.o mmc_ops.o sd.o sd_ops.o \
|
||||
sdio.o sdio_ops.o sdio_bus.o \
|
||||
sdio_cis.o sdio_io.o sdio_irq.o
|
||||
|
||||
303
drivers/mmc/core/bus.c
Normal file
303
drivers/mmc/core/bus.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/bus.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC card bus driver model
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
#include "core.h"
|
||||
#include "sdio_cis.h"
|
||||
#include "bus.h"
|
||||
|
||||
#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
|
||||
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
|
||||
|
||||
static ssize_t mmc_type_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
return sprintf(buf, "MMC\n");
|
||||
case MMC_TYPE_SD:
|
||||
return sprintf(buf, "SD\n");
|
||||
case MMC_TYPE_SDIO:
|
||||
return sprintf(buf, "SDIO\n");
|
||||
default:
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(type),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* This currently matches any MMC driver to any MMC card - drivers
|
||||
* themselves make the decision whether to drive this card in their
|
||||
* probe method.
|
||||
*/
|
||||
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// To fit 2.6.21.
|
||||
static int
|
||||
mmc_bus_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
const char *type;
|
||||
int i = 0;
|
||||
int length = 0;
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
type = "MMC";
|
||||
break;
|
||||
case MMC_TYPE_SD:
|
||||
type = "SD";
|
||||
break;
|
||||
case MMC_TYPE_SDIO:
|
||||
type = "SDIO";
|
||||
break;
|
||||
default:
|
||||
type = NULL;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "MMC_TYPE=%s", type))
|
||||
return -ENOMEM;;
|
||||
}
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "MMC_NAME=%s", mmc_card_name(card)))
|
||||
return -ENOMEM;
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
static int mmc_bus_probe(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
return drv->probe(card);
|
||||
}
|
||||
|
||||
static int mmc_bus_remove(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
drv->remove(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->suspend)
|
||||
ret = drv->suspend(card, state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
ret = drv->resume(card);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bus_type mmc_bus_type = {
|
||||
.name = "mmc",
|
||||
.dev_attrs = mmc_dev_attrs,
|
||||
.match = mmc_bus_match,
|
||||
.uevent = mmc_bus_uevent,
|
||||
.probe = mmc_bus_probe,
|
||||
.remove = mmc_bus_remove,
|
||||
.suspend = mmc_bus_suspend,
|
||||
.resume = mmc_bus_resume,
|
||||
};
|
||||
|
||||
int mmc_register_bus(void)
|
||||
{
|
||||
return bus_register(&mmc_bus_type);
|
||||
}
|
||||
|
||||
void mmc_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&mmc_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_register_driver - register a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
int mmc_register_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_register_driver);
|
||||
|
||||
/**
|
||||
* mmc_unregister_driver - unregister a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
void mmc_unregister_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_unregister_driver);
|
||||
|
||||
static void mmc_release_card(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
sdio_free_common_cis(card);
|
||||
|
||||
if (card->info)
|
||||
kfree(card->info);
|
||||
|
||||
kfree(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new MMC card structure.
|
||||
*/
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
||||
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
|
||||
if (!card)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
card->host = host;
|
||||
|
||||
device_initialize(&card->dev);
|
||||
|
||||
card->dev.parent = mmc_classdev(host);
|
||||
card->dev.bus = &mmc_bus_type;
|
||||
card->dev.release = mmc_release_card;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new MMC card with the driver model.
|
||||
*/
|
||||
int mmc_add_card(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
const char *type;
|
||||
|
||||
snprintf(card->dev.bus_id, sizeof(card->dev.bus_id),
|
||||
"%s:%04x", mmc_hostname(card->host), card->rca);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
type = "MMC";
|
||||
break;
|
||||
case MMC_TYPE_SD:
|
||||
type = "SD";
|
||||
if (mmc_card_blockaddr(card))
|
||||
type = "SDHC";
|
||||
break;
|
||||
case MMC_TYPE_SDIO:
|
||||
type = "SDIO";
|
||||
break;
|
||||
default:
|
||||
type = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
printk(KERN_INFO "%s: new %s%s card on SPI\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_highspeed(card) ? "high speed " : "",
|
||||
type);
|
||||
} else {
|
||||
printk(KERN_INFO "%s: new %s%s card at address %04x\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_highspeed(card) ? "high speed " : "",
|
||||
type, card->rca);
|
||||
}
|
||||
|
||||
card->dev.uevent_suppress = 1;
|
||||
|
||||
ret = device_add(&card->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (card->host->bus_ops->sysfs_add) {
|
||||
ret = card->host->bus_ops->sysfs_add(card->host, card);
|
||||
if (ret) {
|
||||
device_del(&card->dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
card->dev.uevent_suppress = 0;
|
||||
|
||||
kobject_uevent(&card->dev.kobj, KOBJ_ADD);
|
||||
|
||||
mmc_card_set_present(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a new MMC card with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void mmc_remove_card(struct mmc_card *card)
|
||||
{
|
||||
if (mmc_card_present(card)) {
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
printk(KERN_INFO "%s: SPI card removed\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
printk(KERN_INFO "%s: card %04x removed\n",
|
||||
mmc_hostname(card->host), card->rca);
|
||||
}
|
||||
|
||||
if (card->host->bus_ops->sysfs_remove)
|
||||
card->host->bus_ops->sysfs_remove(card->host, card);
|
||||
device_del(&card->dev);
|
||||
}
|
||||
|
||||
put_device(&card->dev);
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/bus.h
Normal file
22
drivers/mmc/core/bus.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/bus.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_BUS_H
|
||||
#define _MMC_CORE_BUS_H
|
||||
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host);
|
||||
int mmc_add_card(struct mmc_card *card);
|
||||
void mmc_remove_card(struct mmc_card *card);
|
||||
|
||||
int mmc_register_bus(void);
|
||||
void mmc_unregister_bus(void);
|
||||
|
||||
#endif
|
||||
|
||||
833
drivers/mmc/core/core.c
Normal file
833
drivers/mmc/core/core.c
Normal file
@@ -0,0 +1,833 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/core.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
* MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "host.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr);
|
||||
extern int mmc_attach_sd(struct mmc_host *host, u32 ocr);
|
||||
extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr);
|
||||
|
||||
static struct workqueue_struct *workqueue;
|
||||
|
||||
/*
|
||||
* Enabling software CRCs on the data blocks can be a significant (30%)
|
||||
* performance cost, and for other reasons may not always be desired.
|
||||
* So we allow it it to be disabled.
|
||||
*/
|
||||
int use_spi_crc = 1;
|
||||
module_param(use_spi_crc, bool, 0);
|
||||
|
||||
/*
|
||||
* Internal function. Schedule delayed work in the MMC work queue.
|
||||
*/
|
||||
static int mmc_schedule_delayed_work(struct delayed_work *work,
|
||||
unsigned long delay)
|
||||
{
|
||||
return queue_delayed_work(workqueue, work, delay);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Flush all scheduled work from the MMC work queue.
|
||||
*/
|
||||
static void mmc_flush_scheduled_work(void)
|
||||
{
|
||||
flush_workqueue(workqueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_request_done - finish processing an MMC request
|
||||
* @host: MMC host which completed request
|
||||
* @mrq: MMC request which request
|
||||
*
|
||||
* MMC drivers should call this function when they have completed
|
||||
* their processing of a request.
|
||||
*/
|
||||
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
struct mmc_command *cmd = mrq->cmd;
|
||||
int err = cmd->error;
|
||||
|
||||
if (err && cmd->retries && mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
cmd->retries = 0;
|
||||
}
|
||||
|
||||
if (err && cmd->retries) {
|
||||
pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
|
||||
mmc_hostname(host), cmd->opcode, err);
|
||||
|
||||
cmd->retries--;
|
||||
cmd->error = 0;
|
||||
host->ops->request(host, mrq);
|
||||
} else {
|
||||
led_trigger_event(host->led, LED_OFF);
|
||||
|
||||
pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n",
|
||||
mmc_hostname(host), cmd->opcode, err,
|
||||
cmd->resp[0], cmd->resp[1],
|
||||
cmd->resp[2], cmd->resp[3]);
|
||||
|
||||
if (mrq->data) {
|
||||
pr_debug("%s: %d bytes transferred: %d\n",
|
||||
mmc_hostname(host),
|
||||
mrq->data->bytes_xfered, mrq->data->error);
|
||||
}
|
||||
|
||||
if (mrq->stop) {
|
||||
pr_debug("%s: (CMD%u): %d: %08x %08x %08x %08x\n",
|
||||
mmc_hostname(host), mrq->stop->opcode,
|
||||
mrq->stop->error,
|
||||
mrq->stop->resp[0], mrq->stop->resp[1],
|
||||
mrq->stop->resp[2], mrq->stop->resp[3]);
|
||||
}
|
||||
|
||||
if (mrq->done)
|
||||
mrq->done(mrq);
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_request_done);
|
||||
|
||||
static void
|
||||
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned int i, sz;
|
||||
#endif
|
||||
|
||||
pr_debug("%s: starting CMD%u arg %08x flags %08x\n",
|
||||
mmc_hostname(host), mrq->cmd->opcode,
|
||||
mrq->cmd->arg, mrq->cmd->flags);
|
||||
|
||||
if (mrq->data) {
|
||||
pr_debug("%s: blksz %d blocks %d flags %08x "
|
||||
"tsac %d ms nsac %d\n",
|
||||
mmc_hostname(host), mrq->data->blksz,
|
||||
mrq->data->blocks, mrq->data->flags,
|
||||
mrq->data->timeout_ns / 1000000,
|
||||
mrq->data->timeout_clks);
|
||||
}
|
||||
|
||||
if (mrq->stop) {
|
||||
pr_debug("%s: CMD%u arg %08x flags %08x\n",
|
||||
mmc_hostname(host), mrq->stop->opcode,
|
||||
mrq->stop->arg, mrq->stop->flags);
|
||||
}
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
led_trigger_event(host->led, LED_FULL);
|
||||
|
||||
mrq->cmd->error = 0;
|
||||
mrq->cmd->mrq = mrq;
|
||||
if (mrq->data) {
|
||||
BUG_ON(mrq->data->blksz > host->max_blk_size);
|
||||
BUG_ON(mrq->data->blocks > host->max_blk_count);
|
||||
BUG_ON(mrq->data->blocks * mrq->data->blksz >
|
||||
host->max_req_size);
|
||||
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
sz = 0;
|
||||
for (i = 0;i < mrq->data->sg_len;i++)
|
||||
sz += mrq->data->sg[i].length;
|
||||
BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);
|
||||
#endif
|
||||
|
||||
mrq->cmd->data = mrq->data;
|
||||
mrq->data->error = 0;
|
||||
mrq->data->mrq = mrq;
|
||||
if (mrq->stop) {
|
||||
mrq->data->stop = mrq->stop;
|
||||
mrq->stop->error = 0;
|
||||
mrq->stop->mrq = mrq;
|
||||
}
|
||||
}
|
||||
host->ops->request(host, mrq);
|
||||
}
|
||||
|
||||
static void mmc_wait_done(struct mmc_request *mrq)
|
||||
{
|
||||
complete(mrq->done_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_wait_for_req - start a request and wait for completion
|
||||
* @host: MMC host to start command
|
||||
* @mrq: MMC request to start
|
||||
*
|
||||
* Start a new MMC custom command request for a host, and wait
|
||||
* for the command to complete. Does not attempt to parse the
|
||||
* response.
|
||||
*/
|
||||
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(complete);
|
||||
|
||||
mrq->done_data = &complete;
|
||||
mrq->done = mmc_wait_done;
|
||||
|
||||
mmc_start_request(host, mrq);
|
||||
|
||||
wait_for_completion(&complete);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_req);
|
||||
|
||||
/**
|
||||
* mmc_wait_for_cmd - start a command and wait for completion
|
||||
* @host: MMC host to start command
|
||||
* @cmd: MMC command to start
|
||||
* @retries: maximum number of retries
|
||||
*
|
||||
* Start a new MMC command for a host, and wait for the command
|
||||
* to complete. Return any error that occurred while the command
|
||||
* was executing. Do not attempt to parse the response.
|
||||
*/
|
||||
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
memset(cmd->resp, 0, sizeof(cmd->resp));
|
||||
cmd->retries = retries;
|
||||
|
||||
mrq.cmd = cmd;
|
||||
cmd->data = NULL;
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
return cmd->error;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_cmd);
|
||||
|
||||
/**
|
||||
* mmc_set_data_timeout - set the timeout for a data command
|
||||
* @data: data phase for command
|
||||
* @card: the MMC card associated with the data transfer
|
||||
*
|
||||
* Computes the data timeout parameters according to the
|
||||
* correct algorithm given the card type.
|
||||
*/
|
||||
void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
|
||||
{
|
||||
unsigned int mult;
|
||||
|
||||
/*
|
||||
* SDIO cards only define an upper 1 s limit on access.
|
||||
*/
|
||||
if (mmc_card_sdio(card)) {
|
||||
data->timeout_ns = 1000000000;
|
||||
data->timeout_clks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SD cards use a 100 multiplier rather than 10
|
||||
*/
|
||||
mult = mmc_card_sd(card) ? 100 : 10;
|
||||
|
||||
/*
|
||||
* Scale up the multiplier (and therefore the timeout) by
|
||||
* the r2w factor for writes.
|
||||
*/
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
mult <<= card->csd.r2w_factor;
|
||||
|
||||
data->timeout_ns = card->csd.tacc_ns * mult;
|
||||
data->timeout_clks = card->csd.tacc_clks * mult;
|
||||
|
||||
/*
|
||||
* SD cards also have an upper limit on the timeout.
|
||||
*/
|
||||
if (mmc_card_sd(card)) {
|
||||
unsigned int timeout_us, limit_us;
|
||||
|
||||
timeout_us = data->timeout_ns / 1000;
|
||||
timeout_us += data->timeout_clks * 1000 /
|
||||
(card->host->ios.clock / 1000);
|
||||
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
limit_us = 250000;
|
||||
else
|
||||
limit_us = 100000;
|
||||
|
||||
/*
|
||||
* SDHC cards always use these fixed values.
|
||||
*/
|
||||
if (timeout_us > limit_us || mmc_card_blockaddr(card)) {
|
||||
data->timeout_ns = limit_us * 1000;
|
||||
data->timeout_clks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_set_data_timeout);
|
||||
|
||||
/**
|
||||
* __mmc_claim_host - exclusively claim a host
|
||||
* @host: mmc host to claim
|
||||
* @abort: whether or not the operation should be aborted
|
||||
*
|
||||
* Claim a host for a set of operations. If @abort is non null and
|
||||
* dereference a non-zero value then this will return prematurely with
|
||||
* that non-zero value without acquiring the lock. Returns zero
|
||||
* with the lock held otherwise.
|
||||
*/
|
||||
int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
|
||||
{
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
unsigned long flags;
|
||||
int stop;
|
||||
|
||||
might_sleep();
|
||||
|
||||
add_wait_queue(&host->wq, &wait);
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
while (1) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
stop = abort ? atomic_read(abort) : 0;
|
||||
if (stop || !host->claimed)
|
||||
break;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
schedule();
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
if (!stop)
|
||||
host->claimed = 1;
|
||||
else
|
||||
wake_up(&host->wq);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
remove_wait_queue(&host->wq, &wait);
|
||||
return stop;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(__mmc_claim_host);
|
||||
|
||||
/**
|
||||
* mmc_release_host - release a host
|
||||
* @host: mmc host to release
|
||||
*
|
||||
* Release a MMC host, allowing others to claim the host
|
||||
* for their operations.
|
||||
*/
|
||||
void mmc_release_host(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->claimed = 0;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
wake_up(&host->wq);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_release_host);
|
||||
|
||||
/*
|
||||
* Internal function that does the actual ios call to the host driver,
|
||||
* optionally printing some debug output.
|
||||
*/
|
||||
static inline void mmc_set_ios(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_ios *ios = &host->ios;
|
||||
|
||||
pr_debug("%s: clock %uHz busmode %u powermode %u cs %u Vdd %u "
|
||||
"width %u timing %u\n",
|
||||
mmc_hostname(host), ios->clock, ios->bus_mode,
|
||||
ios->power_mode, ios->chip_select, ios->vdd,
|
||||
ios->bus_width, ios->timing);
|
||||
|
||||
host->ops->set_ios(host, ios);
|
||||
}
|
||||
|
||||
/*
|
||||
* Control chip select pin on a host.
|
||||
*/
|
||||
void mmc_set_chip_select(struct mmc_host *host, int mode)
|
||||
{
|
||||
host->ios.chip_select = mode;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the host clock to the highest possible frequency that
|
||||
* is below "hz".
|
||||
*/
|
||||
void mmc_set_clock(struct mmc_host *host, unsigned int hz)
|
||||
{
|
||||
WARN_ON(hz < host->f_min);
|
||||
|
||||
if (hz > host->f_max)
|
||||
hz = host->f_max;
|
||||
|
||||
host->ios.clock = hz;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the bus mode (open drain/push-pull) of a host.
|
||||
*/
|
||||
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
|
||||
{
|
||||
host->ios.bus_mode = mode;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change data bus width of a host.
|
||||
*/
|
||||
void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
|
||||
{
|
||||
host->ios.bus_width = width;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mask off any voltages we don't support and select
|
||||
* the lowest voltage
|
||||
*/
|
||||
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int bit;
|
||||
|
||||
ocr &= host->ocr_avail;
|
||||
|
||||
bit = ffs(ocr);
|
||||
if (bit) {
|
||||
bit -= 1;
|
||||
|
||||
ocr &= 3 << bit;
|
||||
|
||||
host->ios.vdd = bit;
|
||||
mmc_set_ios(host);
|
||||
} else {
|
||||
pr_debug("%s: host doesn't support card's voltages\n",
|
||||
mmc_hostname(host));
|
||||
ocr = 0;
|
||||
}
|
||||
|
||||
return ocr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select timing parameters for host.
|
||||
*/
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing)
|
||||
{
|
||||
host->ios.timing = timing;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply power to the MMC stack. This is a two-stage process.
|
||||
* First, we enable power to the card without the clock running.
|
||||
* We then wait a bit for the power to stabilise. Finally,
|
||||
* enable the bus drivers and clock to the card.
|
||||
*
|
||||
* We must _NOT_ enable the clock prior to power stablising.
|
||||
*
|
||||
* If a host does all the power sequencing itself, ignore the
|
||||
* initial MMC_POWER_UP stage.
|
||||
*/
|
||||
static void mmc_power_up(struct mmc_host *host)
|
||||
{
|
||||
int bit = fls(host->ocr_avail) - 1;
|
||||
|
||||
host->ios.vdd = bit;
|
||||
if (mmc_host_is_spi(host)) {
|
||||
host->ios.chip_select = MMC_CS_HIGH;
|
||||
host->ios.bus_mode = MMC_BUSMODE_PUSHPULL;
|
||||
} else {
|
||||
host->ios.chip_select = MMC_CS_DONTCARE;
|
||||
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
|
||||
}
|
||||
host->ios.power_mode = MMC_POWER_UP;
|
||||
host->ios.bus_width = MMC_BUS_WIDTH_1;
|
||||
host->ios.timing = MMC_TIMING_LEGACY;
|
||||
mmc_set_ios(host);
|
||||
|
||||
/*
|
||||
* This delay should be sufficient to allow the power supply
|
||||
* to reach the minimum voltage.
|
||||
*/
|
||||
mmc_delay(2);
|
||||
|
||||
host->ios.clock = host->f_min;
|
||||
host->ios.power_mode = MMC_POWER_ON;
|
||||
mmc_set_ios(host);
|
||||
|
||||
/*
|
||||
* This delay must be at least 74 clock sizes, or 1 ms, or the
|
||||
* time required to reach a stable voltage.
|
||||
*/
|
||||
mmc_delay(2);
|
||||
}
|
||||
|
||||
static void mmc_power_off(struct mmc_host *host)
|
||||
{
|
||||
host->ios.clock = 0;
|
||||
host->ios.vdd = 0;
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
|
||||
host->ios.chip_select = MMC_CS_DONTCARE;
|
||||
}
|
||||
host->ios.power_mode = MMC_POWER_OFF;
|
||||
host->ios.bus_width = MMC_BUS_WIDTH_1;
|
||||
host->ios.timing = MMC_TIMING_LEGACY;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup when the last reference to the bus operator is dropped.
|
||||
*/
|
||||
void __mmc_release_bus(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(host->bus_refs);
|
||||
BUG_ON(!host->bus_dead);
|
||||
|
||||
host->bus_ops = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase reference count of bus operator
|
||||
*/
|
||||
static inline void mmc_bus_get(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->bus_refs++;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrease reference count of bus operator and free it if
|
||||
* it is the last reference.
|
||||
*/
|
||||
static inline void mmc_bus_put(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->bus_refs--;
|
||||
if ((host->bus_refs == 0) && host->bus_ops)
|
||||
__mmc_release_bus(host);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a mmc bus handler to a host. Only one bus handler may control a
|
||||
* host at any given time.
|
||||
*/
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!ops);
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
BUG_ON(host->bus_ops);
|
||||
BUG_ON(host->bus_refs);
|
||||
|
||||
host->bus_ops = ops;
|
||||
host->bus_refs = 1;
|
||||
host->bus_dead = 0;
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the current bus handler from a host. Assumes that there are
|
||||
* no interesting cards left, so the bus is powered down.
|
||||
*/
|
||||
void mmc_detach_bus(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
WARN_ON(!host->bus_ops);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
host->bus_dead = 1;
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
mmc_power_off(host);
|
||||
|
||||
mmc_bus_put(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_detect_change - process change of state on a MMC socket
|
||||
* @host: host which changed state.
|
||||
* @delay: optional delay to wait before detection (jiffies)
|
||||
*
|
||||
* MMC drivers should call this when they detect a card has been
|
||||
* inserted or removed. The MMC layer will confirm that any
|
||||
* present card is still functional, and initialize any newly
|
||||
* inserted.
|
||||
*/
|
||||
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Fix compile error.
|
||||
WARN_ON(!(!host->removed));
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
#endif
|
||||
|
||||
mmc_schedule_delayed_work(&host->detect, delay);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_detect_change);
|
||||
|
||||
|
||||
void mmc_rescan(struct work_struct *work)
|
||||
{
|
||||
struct mmc_host *host =
|
||||
container_of(work, struct mmc_host, detect.work);
|
||||
u32 ocr;
|
||||
int err;
|
||||
|
||||
mmc_bus_get(host);
|
||||
|
||||
if (host->bus_ops == NULL) {
|
||||
/*
|
||||
* Only we can add a new handler, so it's safe to
|
||||
* release the lock here.
|
||||
*/
|
||||
mmc_bus_put(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
mmc_power_up(host);
|
||||
mmc_go_idle(host);
|
||||
|
||||
mmc_send_if_cond(host, host->ocr_avail);
|
||||
|
||||
/*
|
||||
* First we search for SDIO...
|
||||
*/
|
||||
err = mmc_send_io_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_sdio(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* ...then normal SD...
|
||||
*/
|
||||
err = mmc_send_app_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_sd(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* ...and finally MMC.
|
||||
*/
|
||||
err = mmc_send_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_mmc(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
mmc_release_host(host);
|
||||
mmc_power_off(host);
|
||||
} else {
|
||||
if (host->bus_ops->detect && !host->bus_dead)
|
||||
host->bus_ops->detect(host);
|
||||
|
||||
mmc_bus_put(host);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_start_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_power_off(host);
|
||||
mmc_detect_change(host, 0);
|
||||
}
|
||||
|
||||
void mmc_stop_host(struct mmc_host *host)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->removed = 1;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
#endif
|
||||
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
if (host->bus_ops->remove)
|
||||
host->bus_ops->remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
BUG_ON(host->card);
|
||||
|
||||
mmc_power_off(host);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/**
|
||||
* mmc_suspend_host - suspend a host
|
||||
* @host: mmc host
|
||||
* @state: suspend mode (PM_SUSPEND_xxx)
|
||||
*/
|
||||
int mmc_suspend_host(struct mmc_host *host, pm_message_t state)
|
||||
{
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
if (host->bus_ops->suspend)
|
||||
host->bus_ops->suspend(host);
|
||||
if (!host->bus_ops->resume) {
|
||||
if (host->bus_ops->remove)
|
||||
host->bus_ops->remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
mmc_power_off(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_suspend_host);
|
||||
|
||||
/**
|
||||
* mmc_resume_host - resume a previously suspended host
|
||||
* @host: mmc host
|
||||
*/
|
||||
int mmc_resume_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
mmc_power_up(host);
|
||||
BUG_ON(!host->bus_ops->resume);
|
||||
host->bus_ops->resume(host);
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
/*
|
||||
* We add a slight delay here so that resume can progress
|
||||
* in parallel.
|
||||
*/
|
||||
mmc_detect_change(host, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_resume_host);
|
||||
|
||||
#endif
|
||||
|
||||
static int __init mmc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
workqueue = create_singlethread_workqueue("kmmcd");
|
||||
if (!workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mmc_register_bus();
|
||||
if (ret)
|
||||
goto destroy_workqueue;
|
||||
|
||||
ret = mmc_register_host_class();
|
||||
if (ret)
|
||||
goto unregister_bus;
|
||||
|
||||
ret = sdio_register_bus();
|
||||
if (ret)
|
||||
goto unregister_host_class;
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_host_class:
|
||||
mmc_unregister_host_class();
|
||||
unregister_bus:
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue:
|
||||
destroy_workqueue(workqueue);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mmc_exit(void)
|
||||
{
|
||||
sdio_unregister_bus();
|
||||
mmc_unregister_host_class();
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue(workqueue);
|
||||
}
|
||||
|
||||
subsys_initcall(mmc_init);
|
||||
module_exit(mmc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
54
drivers/mmc/core/core.h
Normal file
54
drivers/mmc/core/core.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/core.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_CORE_H
|
||||
#define _MMC_CORE_CORE_H
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define MMC_CMD_RETRIES 3
|
||||
|
||||
struct mmc_bus_ops {
|
||||
void (*remove)(struct mmc_host *);
|
||||
void (*detect)(struct mmc_host *);
|
||||
int (*sysfs_add)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*suspend)(struct mmc_host *);
|
||||
void (*resume)(struct mmc_host *);
|
||||
};
|
||||
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
|
||||
void mmc_detach_bus(struct mmc_host *host);
|
||||
|
||||
void mmc_set_chip_select(struct mmc_host *host, int mode);
|
||||
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
|
||||
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
|
||||
void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
|
||||
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
|
||||
|
||||
static inline void mmc_delay(unsigned int ms)
|
||||
{
|
||||
if (ms < 1000 / HZ) {
|
||||
cond_resched();
|
||||
mdelay(ms);
|
||||
} else {
|
||||
msleep(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_rescan(struct work_struct *work);
|
||||
void mmc_start_host(struct mmc_host *host);
|
||||
void mmc_stop_host(struct mmc_host *host);
|
||||
|
||||
extern int use_spi_crc;
|
||||
|
||||
#endif
|
||||
|
||||
167
drivers/mmc/core/host.c
Normal file
167
drivers/mmc/core/host.c
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/host.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC host class device management
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "host.h"
|
||||
|
||||
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||||
|
||||
static void mmc_host_classdev_release(struct device *dev)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
kfree(host);
|
||||
}
|
||||
|
||||
static struct class mmc_host_class = {
|
||||
.name = "mmc_host",
|
||||
.dev_release = mmc_host_classdev_release,
|
||||
};
|
||||
|
||||
int mmc_register_host_class(void)
|
||||
{
|
||||
return class_register(&mmc_host_class);
|
||||
}
|
||||
|
||||
void mmc_unregister_host_class(void)
|
||||
{
|
||||
class_unregister(&mmc_host_class);
|
||||
}
|
||||
|
||||
static DEFINE_IDR(mmc_host_idr);
|
||||
static DEFINE_SPINLOCK(mmc_host_lock);
|
||||
|
||||
/**
|
||||
* mmc_alloc_host - initialise the per-host structure.
|
||||
* @extra: sizeof private data structure
|
||||
* @dev: pointer to host device model structure
|
||||
*
|
||||
* Initialise the per-host structure.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
|
||||
if (!host)
|
||||
return NULL;
|
||||
|
||||
host->parent = dev;
|
||||
host->class_dev.parent = dev;
|
||||
host->class_dev.class = &mmc_host_class;
|
||||
device_initialize(&host->class_dev);
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
*/
|
||||
host->max_hw_segs = 1;
|
||||
host->max_phys_segs = 1;
|
||||
host->max_seg_size = PAGE_CACHE_SIZE;
|
||||
|
||||
host->max_req_size = PAGE_CACHE_SIZE;
|
||||
host->max_blk_size = 512;
|
||||
host->max_blk_count = PAGE_CACHE_SIZE / 512;
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_alloc_host);
|
||||
|
||||
/**
|
||||
* mmc_add_host - initialise host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Register the host with the driver model. The host must be
|
||||
* prepared to start servicing requests before this function
|
||||
* completes.
|
||||
*/
|
||||
int mmc_add_host(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
|
||||
!host->ops->enable_sdio_irq);
|
||||
|
||||
if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
err = idr_get_new(&mmc_host_idr, host, &host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snprintf(host->class_dev.bus_id, BUS_ID_SIZE,
|
||||
"mmc%d", host->index);
|
||||
|
||||
led_trigger_register_simple(host->class_dev.bus_id, &host->led);
|
||||
|
||||
err = device_add(&host->class_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mmc_start_host(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_add_host);
|
||||
|
||||
/**
|
||||
* mmc_remove_host - remove host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Unregister and remove all cards associated with this host,
|
||||
* and power down the MMC bus. No new requests will be issued
|
||||
* after this function has returned.
|
||||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_stop_host(host);
|
||||
|
||||
device_del(&host->class_dev);
|
||||
|
||||
led_trigger_unregister_simple(host->led);
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_remove_host);
|
||||
|
||||
/**
|
||||
* mmc_free_host - free the host structure
|
||||
* @host: mmc host
|
||||
*
|
||||
* Free the host once all references to it have been dropped.
|
||||
*/
|
||||
void mmc_free_host(struct mmc_host *host)
|
||||
{
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_free_host);
|
||||
|
||||
18
drivers/mmc/core/host.h
Normal file
18
drivers/mmc/core/host.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/host.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_HOST_H
|
||||
#define _MMC_CORE_HOST_H
|
||||
|
||||
int mmc_register_host_class(void);
|
||||
void mmc_unregister_host_class(void);
|
||||
|
||||
#endif
|
||||
|
||||
639
drivers/mmc/core/mmc.c
Normal file
639
drivers/mmc/core/mmc.c
Normal file
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/mmc.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
* MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
10000, 100000, 1000000, 10000000,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const unsigned char tran_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_exp[] = {
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Given the decoded CSD structure, decode the raw CID to our CID structure.
|
||||
*/
|
||||
static int mmc_decode_cid(struct mmc_card *card)
|
||||
{
|
||||
u32 *resp = card->raw_cid;
|
||||
|
||||
/*
|
||||
* The selection of the format here is based upon published
|
||||
* specs from sandisk and from what people have reported.
|
||||
*/
|
||||
switch (card->csd.mmca_vsn) {
|
||||
case 0: /* MMC v1.0 - v1.2 */
|
||||
case 1: /* MMC v1.4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 104, 24);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 24);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
case 2: /* MMC v2.0 - v2.2 */
|
||||
case 3: /* MMC v3.1 - v3.3 */
|
||||
case 4: /* MMC v4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_ERR "%s: card has unknown MMCA version %d\n",
|
||||
mmc_hostname(card->host), card->csd.mmca_vsn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 128-bit response, decode to our card CSD structure.
|
||||
*/
|
||||
static int mmc_decode_csd(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_csd *csd = &card->csd;
|
||||
unsigned int e, m, csd_struct;
|
||||
u32 *resp = card->raw_csd;
|
||||
|
||||
/*
|
||||
* We only understand CSD structure v1.1 and v1.2.
|
||||
* v1.2 has extra information in bits 15, 11 and 10.
|
||||
*/
|
||||
csd_struct = UNSTUFF_BITS(resp, 126, 2);
|
||||
if (csd_struct != 1 && csd_struct != 2) {
|
||||
printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
|
||||
mmc_hostname(card->host), csd_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4);
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
|
||||
csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and decode extended CSD.
|
||||
*/
|
||||
static int mmc_read_ext_csd(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *ext_csd;
|
||||
unsigned int ext_csd_struct;
|
||||
|
||||
BUG_ON(!card);
|
||||
|
||||
if (card->csd.mmca_vsn < CSD_SPEC_VER_4)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* As the ext_csd is so large and mostly unused, we don't store the
|
||||
* raw block in mmc_card.
|
||||
*/
|
||||
ext_csd = kmalloc(512, GFP_KERNEL);
|
||||
if (!ext_csd) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer to "
|
||||
"receive the ext_csd.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_send_ext_csd(card, ext_csd);
|
||||
if (err) {
|
||||
/*
|
||||
* We all hosts that cannot perform the command
|
||||
* to fail more gracefully
|
||||
*/
|
||||
if (err != -EINVAL)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* High capacity cards should have this "magic" size
|
||||
* stored in their CSD.
|
||||
*/
|
||||
if (card->csd.capacity == (4096 * 512)) {
|
||||
printk(KERN_ERR "%s: unable to read EXT_CSD "
|
||||
"on a possible high capacity card. "
|
||||
"Card will be ignored.\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
printk(KERN_WARNING "%s: unable to read "
|
||||
"EXT_CSD, performance might "
|
||||
"suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
err = 0;
|
||||
}
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
ext_csd_struct = ext_csd[EXT_CSD_REV];
|
||||
if (ext_csd_struct > 2) {
|
||||
printk(KERN_ERR "%s: unrecognised EXT_CSD structure "
|
||||
"version %d\n", mmc_hostname(card->host),
|
||||
ext_csd_struct);
|
||||
//err = -EINVAL; //A3U, for moviNAND
|
||||
//goto out;
|
||||
}
|
||||
|
||||
if (ext_csd_struct >= 2) {
|
||||
card->ext_csd.sectors =
|
||||
ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
|
||||
if (card->ext_csd.sectors)
|
||||
mmc_card_set_blockaddr(card);
|
||||
}
|
||||
|
||||
switch (ext_csd[EXT_CSD_CARD_TYPE]) {
|
||||
case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26:
|
||||
card->ext_csd.hs_max_dtr = 52000000;
|
||||
break;
|
||||
case EXT_CSD_CARD_TYPE_26:
|
||||
card->ext_csd.hs_max_dtr = 26000000;
|
||||
break;
|
||||
default:
|
||||
/* MMC v4 spec says this cannot happen */
|
||||
printk(KERN_WARNING "%s: card is mmc v4 but doesn't "
|
||||
"support any high-speed modes.\n",
|
||||
mmc_hostname(card->host));
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(ext_csd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
struct mmc_card *oldcard)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
int err;
|
||||
u32 cid[4];
|
||||
unsigned int max_dtr;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
/*
|
||||
* Since we're changing the OCR value, we seem to
|
||||
* need to tell some cards to go back to the idle
|
||||
* state. We wait 1ms to give cards time to
|
||||
* respond.
|
||||
*/
|
||||
mmc_go_idle(host);
|
||||
|
||||
/* The extra bit indicates that we support high capacity */
|
||||
err = mmc_send_op_cond(host, ocr | (1 << 30), NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch CID from card.
|
||||
*/
|
||||
if (mmc_host_is_spi(host))
|
||||
err = mmc_send_cid(host, cid);
|
||||
else
|
||||
err = mmc_all_send_cid(host, cid);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
if (oldcard) {
|
||||
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
|
||||
err = -ENOENT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
card = oldcard;
|
||||
} else {
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_MMC;
|
||||
card->rca = 1;
|
||||
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
|
||||
}
|
||||
|
||||
/*
|
||||
* For native busses: set card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_set_relative_addr(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch CSD from card.
|
||||
*/
|
||||
err = mmc_send_csd(card, card->raw_csd);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
err = mmc_decode_cid(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch and process extended CSD.
|
||||
*/
|
||||
err = mmc_read_ext_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Activate high speed (if supported)
|
||||
*/
|
||||
if ((card->ext_csd.hs_max_dtr != 0) &&
|
||||
(host->caps & MMC_CAP_MMC_HIGHSPEED)) {
|
||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||
EXT_CSD_HS_TIMING, 1);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_card_set_highspeed(card);
|
||||
|
||||
mmc_set_timing(card->host, MMC_TIMING_MMC_HS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute bus speed.
|
||||
*/
|
||||
max_dtr = (unsigned int)-1;
|
||||
|
||||
if (mmc_card_highspeed(card)) {
|
||||
if (max_dtr > card->ext_csd.hs_max_dtr)
|
||||
max_dtr = card->ext_csd.hs_max_dtr;
|
||||
} else if (max_dtr > card->csd.max_dtr) {
|
||||
max_dtr = card->csd.max_dtr;
|
||||
}
|
||||
|
||||
mmc_set_clock(host, max_dtr);
|
||||
|
||||
/*
|
||||
* Activate wide bus (if supported).
|
||||
*/
|
||||
if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
|
||||
(host->caps & MMC_CAP_4_BIT_DATA)) {
|
||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||
EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
|
||||
}
|
||||
|
||||
if (!oldcard)
|
||||
host->card = card;
|
||||
|
||||
return 0;
|
||||
|
||||
free_card:
|
||||
if (!oldcard)
|
||||
mmc_remove_card(card);
|
||||
err:
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_remove(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_send_status(host->card, NULL);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
* Suspend callback from host.
|
||||
*/
|
||||
static void mmc_suspend(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
if (!mmc_host_is_spi(host))
|
||||
mmc_deselect_cards(host);
|
||||
host->card->state &= ~MMC_STATE_HIGHSPEED;
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume callback from host.
|
||||
*
|
||||
* This function tries to determine if the same card is still present
|
||||
* and, if so, restore all state to it.
|
||||
*/
|
||||
static void mmc_resume(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
err = mmc_init_card(host, host->ocr, host->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define mmc_suspend NULL
|
||||
#define mmc_resume NULL
|
||||
|
||||
#endif
|
||||
|
||||
static const struct mmc_bus_ops mmc_ops = {
|
||||
.remove = mmc_remove,
|
||||
.detect = mmc_detect,
|
||||
.sysfs_add = mmc_sysfs_add,
|
||||
.sysfs_remove = mmc_sysfs_remove,
|
||||
.suspend = mmc_suspend,
|
||||
.resume = mmc_resume,
|
||||
};
|
||||
|
||||
/*
|
||||
* Starting point for MMC card init.
|
||||
*/
|
||||
int mmc_attach_mmc(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_ops);
|
||||
|
||||
/*
|
||||
* We need to get OCR a different way for SPI.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_read_ocr(host, 1, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage of the card?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
err = mmc_init_card(host, host->ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_card;
|
||||
|
||||
return 0;
|
||||
|
||||
remove_card:
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_claim_host(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising MMC card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
397
drivers/mmc/core/mmc_ops.c
Normal file
397
drivers/mmc/core/mmc_ops.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/mmc_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SELECT_CARD;
|
||||
|
||||
if (card) {
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
} else {
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_select_card(struct mmc_card *card)
|
||||
{
|
||||
BUG_ON(!card);
|
||||
|
||||
return _mmc_select_card(card->host, card);
|
||||
}
|
||||
|
||||
int mmc_deselect_cards(struct mmc_host *host)
|
||||
{
|
||||
return _mmc_select_card(host, NULL);
|
||||
}
|
||||
|
||||
int mmc_go_idle(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
/*
|
||||
* Non-SPI hosts need to prevent chipselect going active during
|
||||
* GO_IDLE; that would put chips into SPI mode. Remind them of
|
||||
* that in case of hardware that won't pull up DAT3/nCS otherwise.
|
||||
*
|
||||
* SPI hosts ignore ios.chip_select; it's managed according to
|
||||
* rules that must accomodate non-MMC slaves which this layer
|
||||
* won't even know about.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
mmc_set_chip_select(host, MMC_CS_HIGH);
|
||||
mmc_delay(1);
|
||||
}
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_GO_IDLE_STATE;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
|
||||
mmc_delay(1);
|
||||
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
mmc_set_chip_select(host, MMC_CS_DONTCARE);
|
||||
mmc_delay(1);
|
||||
}
|
||||
|
||||
host->use_spi_crc = 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SEND_OP_COND;
|
||||
cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (!(cmd.resp[0] & R1_SPI_IDLE))
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (rocr && !mmc_host_is_spi(host))
|
||||
*rocr = cmd.resp[0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_all_send_cid(struct mmc_host *host, u32 *cid)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cid);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_ALL_SEND_CID;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(cid, cmd.resp, sizeof(u32) * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_set_relative_addr(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SET_RELATIVE_ADDR;
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cxd);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = arg;
|
||||
cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(cxd, cmd.resp, sizeof(u32) * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
|
||||
u32 opcode, void *buf, unsigned len)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
void *data_buf;
|
||||
|
||||
/* dma onto stack is unsafe/nonportable, but callers to this
|
||||
* routine normally provide temporary on-stack buffers ...
|
||||
*/
|
||||
data_buf = kmalloc(len, GFP_KERNEL);
|
||||
if (data_buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = 0;
|
||||
|
||||
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
|
||||
* rely on callers to never use this with "native" calls for reading
|
||||
* CSD or CID. Native versions of those commands use the R2 type,
|
||||
* not R1 plus a data block.
|
||||
*/
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = len;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, data_buf, len);
|
||||
|
||||
if (card)
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
memcpy(buf, data_buf, len);
|
||||
kfree(data_buf);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_csd(struct mmc_card *card, u32 *csd)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
if (!mmc_host_is_spi(card->host))
|
||||
return mmc_send_cxd_native(card->host, card->rca << 16,
|
||||
csd, MMC_SEND_CSD);
|
||||
|
||||
ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
csd[i] = be32_to_cpu(csd[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_cid(struct mmc_host *host, u32 *cid)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
if (!host->card)
|
||||
return -EINVAL;
|
||||
return mmc_send_cxd_native(host, host->card->rca << 16,
|
||||
cid, MMC_SEND_CID);
|
||||
}
|
||||
|
||||
ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
cid[i] = be32_to_cpu(cid[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
|
||||
{
|
||||
return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
|
||||
ext_csd, 512);
|
||||
}
|
||||
|
||||
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SPI_READ_OCR;
|
||||
cmd.arg = highcap ? (1 << 30) : 0;
|
||||
cmd.flags = MMC_RSP_SPI_R3;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
|
||||
*ocrp = cmd.resp[1];
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_spi_set_crc(struct mmc_host *host, int use_crc)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SPI_CRC_ON_OFF;
|
||||
cmd.flags = MMC_RSP_SPI_R1;
|
||||
cmd.arg = use_crc;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (!err)
|
||||
host->use_spi_crc = use_crc;
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SWITCH;
|
||||
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
||||
(index << 16) |
|
||||
(value << 8) |
|
||||
set;
|
||||
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SEND_STATUS;
|
||||
if (!mmc_host_is_spi(card->host))
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* NOTE: callers are required to understand the difference
|
||||
* between "native" and SPI format status words!
|
||||
*/
|
||||
if (status)
|
||||
*status = cmd.resp[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
30
drivers/mmc/core/mmc_ops.h
Normal file
30
drivers/mmc/core/mmc_ops.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/mmc_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_MMC_OPS_H
|
||||
#define _MMC_MMC_OPS_H
|
||||
|
||||
int mmc_select_card(struct mmc_card *card);
|
||||
int mmc_deselect_cards(struct mmc_host *host);
|
||||
int mmc_go_idle(struct mmc_host *host);
|
||||
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_all_send_cid(struct mmc_host *host, u32 *cid);
|
||||
int mmc_set_relative_addr(struct mmc_card *card);
|
||||
int mmc_send_csd(struct mmc_card *card, u32 *csd);
|
||||
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
|
||||
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value);
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status);
|
||||
int mmc_send_cid(struct mmc_host *host, u32 *cid);
|
||||
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
|
||||
int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
|
||||
|
||||
#endif
|
||||
|
||||
709
drivers/mmc/core/sd.c
Normal file
709
drivers/mmc/core/sd.c
Normal file
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sd.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
10000, 100000, 1000000, 10000000,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const unsigned char tran_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_exp[] = {
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Given the decoded CSD structure, decode the raw CID to our CID structure.
|
||||
*/
|
||||
static void mmc_decode_cid(struct mmc_card *card)
|
||||
{
|
||||
u32 *resp = card->raw_cid;
|
||||
|
||||
memset(&card->cid, 0, sizeof(struct mmc_cid));
|
||||
|
||||
/*
|
||||
* SD doesn't currently have a version field so we will
|
||||
* have to assume we can parse this.
|
||||
*/
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 24, 32);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 12, 8);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 8, 4);
|
||||
|
||||
card->cid.year += 2000; /* SD cards year offset */
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 128-bit response, decode to our card CSD structure.
|
||||
*/
|
||||
static int mmc_decode_csd(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_csd *csd = &card->csd;
|
||||
unsigned int e, m, csd_struct;
|
||||
u32 *resp = card->raw_csd;
|
||||
|
||||
csd_struct = UNSTUFF_BITS(resp, 126, 2);
|
||||
|
||||
switch (csd_struct) {
|
||||
case 0:
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
|
||||
csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
break;
|
||||
case 1:
|
||||
/*
|
||||
* This is a block-addressed SDHC card. Most
|
||||
* interesting fields are unused and have fixed
|
||||
* values. To avoid getting tripped by buggy cards,
|
||||
* we assume those fixed values ourselves.
|
||||
*/
|
||||
mmc_card_set_blockaddr(card);
|
||||
|
||||
csd->tacc_ns = 0; /* Unused */
|
||||
csd->tacc_clks = 0; /* Unused */
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
m = UNSTUFF_BITS(resp, 48, 22);
|
||||
csd->capacity = (1 + m) << 10;
|
||||
|
||||
csd->read_blkbits = 9;
|
||||
csd->read_partial = 0;
|
||||
csd->write_misalign = 0;
|
||||
csd->read_misalign = 0;
|
||||
csd->r2w_factor = 4; /* Unused */
|
||||
csd->write_blkbits = 9;
|
||||
csd->write_partial = 0;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
|
||||
mmc_hostname(card->host), csd_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 64-bit response, decode to our card SCR structure.
|
||||
*/
|
||||
static int mmc_decode_scr(struct mmc_card *card)
|
||||
{
|
||||
struct sd_scr *scr = &card->scr;
|
||||
unsigned int scr_struct;
|
||||
u32 resp[4];
|
||||
|
||||
resp[3] = card->raw_scr[1];
|
||||
resp[2] = card->raw_scr[0];
|
||||
|
||||
scr_struct = UNSTUFF_BITS(resp, 60, 4);
|
||||
if (scr_struct != 0) {
|
||||
printk(KERN_ERR "%s: unrecognised SCR structure version %d\n",
|
||||
mmc_hostname(card->host), scr_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4);
|
||||
scr->bus_widths = UNSTUFF_BITS(resp, 48, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetches and decodes switch information
|
||||
*/
|
||||
static int mmc_read_switch(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *status;
|
||||
|
||||
if (card->scr.sda_vsn < SCR_SPEC_VER_1)
|
||||
return 0;
|
||||
|
||||
if (!(card->csd.cmdclass & CCC_SWITCH)) {
|
||||
printk(KERN_WARNING "%s: card lacks mandatory switch "
|
||||
"function, performance might suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = -EIO;
|
||||
|
||||
status = kmalloc(64, GFP_KERNEL);
|
||||
if (!status) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer for "
|
||||
"switch capabilities.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_sd_switch(card, 0, 0, 1, status);
|
||||
if (err) {
|
||||
/*
|
||||
* We all hosts that cannot perform the command
|
||||
* to fail more gracefully
|
||||
*/
|
||||
if (err != -EINVAL)
|
||||
goto out;
|
||||
|
||||
printk(KERN_WARNING "%s: problem reading switch "
|
||||
"capabilities, performance might suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
err = 0;
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status[13] & 0x02)
|
||||
card->sw_caps.hs_max_dtr = 50000000;
|
||||
|
||||
out:
|
||||
kfree(status);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if the card supports high-speed mode and, if so, switch to it.
|
||||
*/
|
||||
static int mmc_switch_hs(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *status;
|
||||
|
||||
if (card->scr.sda_vsn < SCR_SPEC_VER_1)
|
||||
return 0;
|
||||
|
||||
if (!(card->csd.cmdclass & CCC_SWITCH))
|
||||
return 0;
|
||||
|
||||
if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
|
||||
return 0;
|
||||
|
||||
if (card->sw_caps.hs_max_dtr == 0)
|
||||
return 0;
|
||||
|
||||
err = -EIO;
|
||||
|
||||
status = kmalloc(64, GFP_KERNEL);
|
||||
if (!status) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer for "
|
||||
"switch capabilities.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_sd_switch(card, 1, 0, 1, status);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if ((status[16] & 0xF) != 1) {
|
||||
printk(KERN_WARNING "%s: Problem switching card "
|
||||
"into high-speed mode!\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
mmc_card_set_highspeed(card);
|
||||
mmc_set_timing(card->host, MMC_TIMING_SD_HS);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(status);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
|
||||
struct mmc_card *oldcard)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
int err;
|
||||
u32 cid[4];
|
||||
unsigned int max_dtr;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
/*
|
||||
* Since we're changing the OCR value, we seem to
|
||||
* need to tell some cards to go back to the idle
|
||||
* state. We wait 1ms to give cards time to
|
||||
* respond.
|
||||
*/
|
||||
mmc_go_idle(host);
|
||||
|
||||
/*
|
||||
* If SD_SEND_IF_COND indicates an SD 2.0
|
||||
* compliant card and we should set bit 30
|
||||
* of the ocr to indicate that we can handle
|
||||
* block-addressed SDHC cards.
|
||||
*/
|
||||
err = mmc_send_if_cond(host, ocr);
|
||||
if (!err)
|
||||
ocr |= 1 << 30;
|
||||
|
||||
err = mmc_send_app_op_cond(host, ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch CID from card.
|
||||
*/
|
||||
if (mmc_host_is_spi(host))
|
||||
err = mmc_send_cid(host, cid);
|
||||
else
|
||||
err = mmc_all_send_cid(host, cid);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
if (oldcard) {
|
||||
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
|
||||
err = -ENOENT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
card = oldcard;
|
||||
} else {
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_SD;
|
||||
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
|
||||
}
|
||||
|
||||
/*
|
||||
* For native busses: get card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_send_relative_addr(host, &card->rca);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch CSD from card.
|
||||
*/
|
||||
err = mmc_send_csd(card, card->raw_csd);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_decode_cid(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch SCR from card.
|
||||
*/
|
||||
err = mmc_app_send_scr(card, card->raw_scr);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_scr(card);
|
||||
if (err < 0)
|
||||
goto free_card;
|
||||
|
||||
/*
|
||||
* Fetch switch information from card.
|
||||
*/
|
||||
err = mmc_read_switch(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to change to high-speed (if supported)
|
||||
*/
|
||||
err = mmc_switch_hs(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
/*
|
||||
* Compute bus speed.
|
||||
*/
|
||||
max_dtr = (unsigned int)-1;
|
||||
|
||||
if (mmc_card_highspeed(card)) {
|
||||
if (max_dtr > card->sw_caps.hs_max_dtr)
|
||||
max_dtr = card->sw_caps.hs_max_dtr;
|
||||
} else if (max_dtr > card->csd.max_dtr) {
|
||||
max_dtr = card->csd.max_dtr;
|
||||
}
|
||||
|
||||
mmc_set_clock(host, max_dtr);
|
||||
|
||||
/*
|
||||
* Switch to wider bus (if supported).
|
||||
*/
|
||||
if ((host->caps & MMC_CAP_4_BIT_DATA) &&
|
||||
(card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
|
||||
err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if read-only switch is active.
|
||||
*/
|
||||
if (!oldcard) {
|
||||
if (!host->ops->get_ro) {
|
||||
printk(KERN_WARNING "%s: host does not "
|
||||
"support reading read-only "
|
||||
"switch. assuming write-enable.\n",
|
||||
mmc_hostname(host));
|
||||
} else {
|
||||
if (host->ops->get_ro(host))
|
||||
mmc_card_set_readonly(card);
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldcard)
|
||||
host->card = card;
|
||||
|
||||
return 0;
|
||||
|
||||
free_card:
|
||||
if (!oldcard)
|
||||
mmc_remove_card(card);
|
||||
err:
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_sd_remove(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_sd_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_send_status(host->card, NULL);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sd_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_sd_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(scr),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sd_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_sd_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sd_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_sd_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
* Suspend callback from host.
|
||||
*/
|
||||
static void mmc_sd_suspend(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
if (!mmc_host_is_spi(host))
|
||||
mmc_deselect_cards(host);
|
||||
host->card->state &= ~MMC_STATE_HIGHSPEED;
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume callback from host.
|
||||
*
|
||||
* This function tries to determine if the same card is still present
|
||||
* and, if so, restore all state to it.
|
||||
*/
|
||||
static void mmc_sd_resume(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
err = mmc_sd_init_card(host, host->ocr, host->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sd_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define mmc_sd_suspend NULL
|
||||
#define mmc_sd_resume NULL
|
||||
|
||||
#endif
|
||||
|
||||
static const struct mmc_bus_ops mmc_sd_ops = {
|
||||
.remove = mmc_sd_remove,
|
||||
.detect = mmc_sd_detect,
|
||||
.sysfs_add = mmc_sd_sysfs_add,
|
||||
.sysfs_remove = mmc_sd_sysfs_remove,
|
||||
.suspend = mmc_sd_suspend,
|
||||
.resume = mmc_sd_resume,
|
||||
};
|
||||
|
||||
/*
|
||||
* Starting point for SD card init.
|
||||
*/
|
||||
int mmc_attach_sd(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_sd_ops);
|
||||
|
||||
/*
|
||||
* We need to get OCR a different way for SPI.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
mmc_go_idle(host);
|
||||
|
||||
err = mmc_spi_read_ocr(host, 0, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
if (ocr & MMC_VDD_165_195) {
|
||||
printk(KERN_WARNING "%s: SD card claims to support the "
|
||||
"incompletely defined 'low voltage range'. This "
|
||||
"will be ignored.\n", mmc_hostname(host));
|
||||
ocr &= ~MMC_VDD_165_195;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage(s) of the card(s)?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
err = mmc_sd_init_card(host, host->ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_card;
|
||||
|
||||
return 0;
|
||||
|
||||
remove_card:
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_claim_host(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising SD card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
350
drivers/mmc/core/sd_ops.c
Normal file
350
drivers/mmc/core/sd_ops.c
Normal file
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sd_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sd_ops.h"
|
||||
|
||||
static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(card && (card->host != host));
|
||||
|
||||
cmd.opcode = MMC_APP_CMD;
|
||||
|
||||
if (card) {
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
} else {
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Check that card supported application commands */
|
||||
if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_wait_for_app_cmd - start an application command and wait for
|
||||
completion
|
||||
* @host: MMC host to start command
|
||||
* @card: Card to send MMC_APP_CMD to
|
||||
* @cmd: MMC command to start
|
||||
* @retries: maximum number of retries
|
||||
*
|
||||
* Sends a MMC_APP_CMD, checks the card response, sends the command
|
||||
* in the parameter and waits for it to complete. Return any error
|
||||
* that occurred while the command was executing. Do not attempt to
|
||||
* parse the response.
|
||||
*/
|
||||
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
|
||||
struct mmc_command *cmd, int retries)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
|
||||
int i, err;
|
||||
|
||||
BUG_ON(!cmd);
|
||||
BUG_ON(retries < 0);
|
||||
|
||||
err = -EIO;
|
||||
|
||||
/*
|
||||
* We have to resend MMC_APP_CMD for each attempt so
|
||||
* we cannot use the retries field in mmc_command.
|
||||
*/
|
||||
for (i = 0;i <= retries;i++) {
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
err = mmc_app_cmd(host, card);
|
||||
if (err) {
|
||||
/* no point in retrying; no APP commands allowed */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
memset(cmd->resp, 0, sizeof(cmd->resp));
|
||||
cmd->retries = 0;
|
||||
|
||||
mrq.cmd = cmd;
|
||||
cmd->data = NULL;
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
err = cmd->error;
|
||||
if (!cmd->error)
|
||||
break;
|
||||
|
||||
/* no point in retrying illegal APP commands */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_app_cmd);
|
||||
|
||||
int mmc_app_set_bus_width(struct mmc_card *card, int width)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_APP_SET_BUS_WIDTH;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
switch (width) {
|
||||
case MMC_BUS_WIDTH_1:
|
||||
cmd.arg = SD_BUS_WIDTH_1;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
cmd.arg = SD_BUS_WIDTH_4;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_APP_OP_COND;
|
||||
if (mmc_host_is_spi(host))
|
||||
cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */
|
||||
else
|
||||
cmd.arg = ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (!(cmd.resp[0] & R1_SPI_IDLE))
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (rocr && !mmc_host_is_spi(host))
|
||||
*rocr = cmd.resp[0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_send_if_cond(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
static const u8 test_pattern = 0xAA;
|
||||
u8 result_pattern;
|
||||
|
||||
/*
|
||||
* To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND
|
||||
* before SD_APP_OP_COND. This command will harmlessly fail for
|
||||
* SD 1.0 cards.
|
||||
*/
|
||||
cmd.opcode = SD_SEND_IF_COND;
|
||||
cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;
|
||||
cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (mmc_host_is_spi(host))
|
||||
result_pattern = cmd.resp[1] & 0xFF;
|
||||
else
|
||||
result_pattern = cmd.resp[0] & 0xFF;
|
||||
|
||||
if (result_pattern != test_pattern)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!rca);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_SEND_RELATIVE_ADDR;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*rca = cmd.resp[0] >> 16;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_app_send_scr(struct mmc_card *card, u32 *scr)
|
||||
{
|
||||
int err;
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
BUG_ON(!scr);
|
||||
|
||||
/* NOTE: caller guarantees scr is heap-allocated */
|
||||
|
||||
err = mmc_app_cmd(card->host, card);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_APP_SEND_SCR;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = 8;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, scr, 8);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
scr[0] = be32_to_cpu(scr[0]);
|
||||
scr[1] = be32_to_cpu(scr[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
|
||||
u8 value, u8 *resp)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
/* NOTE: caller guarantees resp is heap-allocated */
|
||||
|
||||
mode = !!mode;
|
||||
value &= 0xF;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_SWITCH;
|
||||
cmd.arg = mode << 31 | 0x00FFFFFF;
|
||||
cmd.arg &= ~(0xF << (group * 4));
|
||||
cmd.arg |= value << (group * 4);
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = 64;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, resp, 64);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
24
drivers/mmc/core/sd_ops.h
Normal file
24
drivers/mmc/core/sd_ops.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sd_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_SD_OPS_H
|
||||
#define _MMC_SD_OPS_H
|
||||
|
||||
int mmc_app_set_bus_width(struct mmc_card *card, int width);
|
||||
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
|
||||
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
|
||||
int mmc_app_send_scr(struct mmc_card *card, u32 *scr);
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
|
||||
u8 value, u8 *resp);
|
||||
|
||||
#endif
|
||||
|
||||
395
drivers/mmc/core/sdio.c
Normal file
395
drivers/mmc/core/sdio.c
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* linux/drivers/mmc/sdio.c
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "sdio_bus.h"
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
#include "sdio_ops.h"
|
||||
#include "sdio_cis.h"
|
||||
|
||||
static int sdio_read_fbr(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char data;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
data &= 0x0f;
|
||||
|
||||
if (data == 0x0f) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
func->class = data;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_init_func(struct mmc_card *card, unsigned int fn)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func;
|
||||
|
||||
BUG_ON(fn > SDIO_MAX_FUNCS);
|
||||
|
||||
func = sdio_alloc_func(card);
|
||||
if (IS_ERR(func))
|
||||
return PTR_ERR(func);
|
||||
|
||||
func->num = fn;
|
||||
|
||||
ret = sdio_read_fbr(func);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = sdio_read_func_cis(func);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
card->sdio_func[fn - 1] = func;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/*
|
||||
* It is okay to remove the function here even though we hold
|
||||
* the host lock as we haven't registered the device yet.
|
||||
*/
|
||||
sdio_remove_func(func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_read_cccr(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
int cccr_vsn;
|
||||
unsigned char data;
|
||||
|
||||
memset(&card->cccr, 0, sizeof(struct sdio_cccr));
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
cccr_vsn = data & 0x0f;
|
||||
|
||||
if (cccr_vsn > SDIO_CCCR_REV_1_20) {
|
||||
printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n",
|
||||
mmc_hostname(card->host), cccr_vsn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
card->cccr.sdio_vsn = (data & 0xf0) >> 4;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_CCCR_CAP_SMB)
|
||||
card->cccr.multi_block = 1;
|
||||
if (data & SDIO_CCCR_CAP_LSC)
|
||||
card->cccr.low_speed = 1;
|
||||
if (data & SDIO_CCCR_CAP_4BLS)
|
||||
card->cccr.wide_bus = 1;
|
||||
|
||||
if (cccr_vsn >= SDIO_CCCR_REV_1_10) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_POWER_SMPC)
|
||||
card->cccr.high_power = 1;
|
||||
}
|
||||
|
||||
if (cccr_vsn >= SDIO_CCCR_REV_1_20) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_SPEED_SHS)
|
||||
card->cccr.high_speed = 1;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_enable_wide(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
u8 ctrl;
|
||||
|
||||
if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
|
||||
return 0;
|
||||
|
||||
if (card->cccr.low_speed && !card->cccr.wide_bus)
|
||||
return 0;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctrl |= SDIO_BUS_WIDTH_4BIT;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_sdio_remove(struct mmc_host *host)
|
||||
{
|
||||
int i;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
for (i = 0;i < host->card->sdio_funcs;i++) {
|
||||
if (host->card->sdio_func[i]) {
|
||||
sdio_remove_func(host->card->sdio_func[i]);
|
||||
host->card->sdio_func[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_sdio_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_select_card(host->card);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sdio_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct mmc_bus_ops mmc_sdio_ops = {
|
||||
.remove = mmc_sdio_remove,
|
||||
.detect = mmc_sdio_detect,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Starting point for SDIO card init.
|
||||
*/
|
||||
int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
int i, funcs;
|
||||
struct mmc_card *card;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_sdio_ops);
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
if (ocr & MMC_VDD_165_195) {
|
||||
printk(KERN_WARNING "%s: SDIO card claims to support the "
|
||||
"incompletely defined 'low voltage range'. This "
|
||||
"will be ignored.\n", mmc_hostname(host));
|
||||
ocr &= ~MMC_VDD_165_195;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage(s) of the card(s)?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inform the card of the voltage
|
||||
*/
|
||||
err = mmc_send_io_op_cond(host, host->ocr, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The number of functions on the card is encoded inside
|
||||
* the ocr.
|
||||
*/
|
||||
funcs = (ocr & 0x70000000) >> 28;
|
||||
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_SDIO;
|
||||
card->sdio_funcs = funcs;
|
||||
|
||||
host->card = card;
|
||||
|
||||
/*
|
||||
* For native busses: set card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_send_relative_addr(host, &card->rca);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the common registers.
|
||||
*/
|
||||
err = sdio_read_cccr(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* Read the common CIS tuples.
|
||||
*/
|
||||
err = sdio_read_common_cis(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* No support for high-speed yet, so just set
|
||||
* the card's maximum speed.
|
||||
*/
|
||||
mmc_set_clock(host, card->cis.max_dtr);
|
||||
|
||||
/*
|
||||
* Switch to wider bus (if supported).
|
||||
*/
|
||||
err = sdio_enable_wide(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* Initialize (but don't add) all present functions.
|
||||
*/
|
||||
for (i = 0;i < funcs;i++) {
|
||||
err = sdio_init_func(host->card, i + 1);
|
||||
if (err)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
/*
|
||||
* First add the card to the driver model...
|
||||
*/
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_added;
|
||||
|
||||
/*
|
||||
* ...then the SDIO functions.
|
||||
*/
|
||||
for (i = 0;i < funcs;i++) {
|
||||
err = sdio_add_func(host->card->sdio_func[i]);
|
||||
if (err)
|
||||
goto remove_added;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
remove_added:
|
||||
/* Remove without lock if the device has been added. */
|
||||
mmc_sdio_remove(host);
|
||||
mmc_claim_host(host);
|
||||
remove:
|
||||
/* And with lock if it hasn't been added. */
|
||||
if (host->card)
|
||||
mmc_sdio_remove(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
271
drivers/mmc/core/sdio_bus.c
Normal file
271
drivers/mmc/core/sdio_bus.c
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_bus.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* SDIO function driver model
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_cis.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev)
|
||||
#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)
|
||||
|
||||
/* show configuration fields */
|
||||
#define sdio_config_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct sdio_func *func; \
|
||||
\
|
||||
func = dev_to_sdio_func (dev); \
|
||||
return sprintf (buf, format_string, func->field); \
|
||||
}
|
||||
|
||||
sdio_config_attr(class, "0x%02x\n");
|
||||
sdio_config_attr(vendor, "0x%04x\n");
|
||||
sdio_config_attr(device, "0x%04x\n");
|
||||
|
||||
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func (dev);
|
||||
|
||||
return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n",
|
||||
func->class, func->vendor, func->device);
|
||||
}
|
||||
|
||||
static struct device_attribute sdio_dev_attrs[] = {
|
||||
__ATTR_RO(class),
|
||||
__ATTR_RO(vendor),
|
||||
__ATTR_RO(device),
|
||||
__ATTR_RO(modalias),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
static const struct sdio_device_id *sdio_match_one(struct sdio_func *func,
|
||||
const struct sdio_device_id *id)
|
||||
{
|
||||
if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class)
|
||||
return NULL;
|
||||
if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor)
|
||||
return NULL;
|
||||
if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device)
|
||||
return NULL;
|
||||
return id;
|
||||
}
|
||||
|
||||
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
|
||||
struct sdio_driver *sdrv)
|
||||
{
|
||||
const struct sdio_device_id *ids;
|
||||
|
||||
ids = sdrv->id_table;
|
||||
|
||||
if (ids) {
|
||||
while (ids->class || ids->vendor || ids->device) {
|
||||
if (sdio_match_one(func, ids))
|
||||
return ids;
|
||||
ids++;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
struct sdio_driver *sdrv = to_sdio_driver(drv);
|
||||
|
||||
if (sdio_match_device(func, sdrv))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// To fit 2.6.21.
|
||||
static int
|
||||
sdio_bus_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
int i = 0;
|
||||
int length = 0;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"SDIO_CLASS=%02X", func->class))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"SDIO_ID=%04X:%04X", func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"MODALIAS=sdio:c%02Xv%04Xd%04X",
|
||||
func->class, func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
static int sdio_bus_probe(struct device *dev)
|
||||
{
|
||||
struct sdio_driver *drv = to_sdio_driver(dev->driver);
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
const struct sdio_device_id *id;
|
||||
int ret;
|
||||
|
||||
id = sdio_match_device(func, drv);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
/* Set the default block size so the driver is sure it's something
|
||||
* sensible. */
|
||||
sdio_claim_host(func);
|
||||
ret = sdio_set_block_size(func, 0);
|
||||
sdio_release_host(func);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return drv->probe(func, id);
|
||||
}
|
||||
|
||||
static int sdio_bus_remove(struct device *dev)
|
||||
{
|
||||
struct sdio_driver *drv = to_sdio_driver(dev->driver);
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
|
||||
drv->remove(func);
|
||||
|
||||
if (func->irq_handler) {
|
||||
printk(KERN_WARNING "WARNING: driver %s did not remove "
|
||||
"its interrupt handler!\n", drv->name);
|
||||
sdio_claim_host(func);
|
||||
sdio_release_irq(func);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bus_type sdio_bus_type = {
|
||||
.name = "sdio",
|
||||
.dev_attrs = sdio_dev_attrs,
|
||||
.match = sdio_bus_match,
|
||||
.uevent = sdio_bus_uevent,
|
||||
.probe = sdio_bus_probe,
|
||||
.remove = sdio_bus_remove,
|
||||
};
|
||||
|
||||
int sdio_register_bus(void)
|
||||
{
|
||||
return bus_register(&sdio_bus_type);
|
||||
}
|
||||
|
||||
void sdio_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&sdio_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_register_driver - register a function driver
|
||||
* @drv: SDIO function driver
|
||||
*/
|
||||
int sdio_register_driver(struct sdio_driver *drv)
|
||||
{
|
||||
drv->drv.name = drv->name;
|
||||
drv->drv.bus = &sdio_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_register_driver);
|
||||
|
||||
/**
|
||||
* sdio_unregister_driver - unregister a function driver
|
||||
* @drv: SDIO function driver
|
||||
*/
|
||||
void sdio_unregister_driver(struct sdio_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &sdio_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_unregister_driver);
|
||||
|
||||
static void sdio_release_func(struct device *dev)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
|
||||
sdio_free_func_cis(func);
|
||||
|
||||
if (func->info)
|
||||
kfree(func->info);
|
||||
|
||||
kfree(func);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new SDIO function structure.
|
||||
*/
|
||||
struct sdio_func *sdio_alloc_func(struct mmc_card *card)
|
||||
{
|
||||
struct sdio_func *func;
|
||||
|
||||
func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
|
||||
if (!func)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
func->card = card;
|
||||
|
||||
device_initialize(&func->dev);
|
||||
|
||||
func->dev.parent = &card->dev;
|
||||
func->dev.bus = &sdio_bus_type;
|
||||
func->dev.release = sdio_release_func;
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new SDIO function with the driver model.
|
||||
*/
|
||||
int sdio_add_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
snprintf(func->dev.bus_id, sizeof(func->dev.bus_id),
|
||||
"%s:%d", mmc_card_id(func->card), func->num);
|
||||
|
||||
ret = device_add(&func->dev);
|
||||
if (ret == 0)
|
||||
sdio_func_set_present(func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a SDIO function with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void sdio_remove_func(struct sdio_func *func)
|
||||
{
|
||||
if (sdio_func_present(func))
|
||||
device_del(&func->dev);
|
||||
|
||||
put_device(&func->dev);
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/sdio_bus.h
Normal file
22
drivers/mmc/core/sdio_bus.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_bus.h
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef _MMC_CORE_SDIO_BUS_H
|
||||
#define _MMC_CORE_SDIO_BUS_H
|
||||
|
||||
struct sdio_func *sdio_alloc_func(struct mmc_card *card);
|
||||
int sdio_add_func(struct sdio_func *func);
|
||||
void sdio_remove_func(struct sdio_func *func);
|
||||
|
||||
int sdio_register_bus(void);
|
||||
void sdio_unregister_bus(void);
|
||||
|
||||
#endif
|
||||
|
||||
355
drivers/mmc/core/sdio_cis.c
Normal file
355
drivers/mmc/core/sdio_cis.c
Normal file
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_cis.c
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 11, 2007
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_cis.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned i, nr_strings;
|
||||
char **buffer, *string;
|
||||
|
||||
buf += 2;
|
||||
size -= 2;
|
||||
|
||||
nr_strings = 0;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (buf[i] == 0xff)
|
||||
break;
|
||||
if (buf[i] == 0)
|
||||
nr_strings++;
|
||||
}
|
||||
|
||||
if (buf[i-1] != '\0') {
|
||||
printk(KERN_WARNING "SDIO: ignoring broken CISTPL_VERS_1\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = i;
|
||||
|
||||
buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
string = (char*)(buffer + nr_strings);
|
||||
|
||||
for (i = 0; i < nr_strings; i++) {
|
||||
buffer[i] = string;
|
||||
strcpy(string, buf);
|
||||
string += strlen(string) + 1;
|
||||
buf += strlen(buf) + 1;
|
||||
}
|
||||
|
||||
if (func) {
|
||||
func->num_info = nr_strings;
|
||||
func->info = (const char**)buffer;
|
||||
} else {
|
||||
card->num_info = nr_strings;
|
||||
card->info = (const char**)buffer;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned int vendor, device;
|
||||
|
||||
/* TPLMID_MANF */
|
||||
vendor = buf[0] | (buf[1] << 8);
|
||||
|
||||
/* TPLMID_CARD */
|
||||
device = buf[2] | (buf[3] << 8);
|
||||
|
||||
if (func) {
|
||||
func->vendor = vendor;
|
||||
func->device = device;
|
||||
} else {
|
||||
card->cis.vendor = vendor;
|
||||
card->cis.device = device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const unsigned char speed_val[16] =
|
||||
{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
|
||||
static const unsigned int speed_unit[8] =
|
||||
{ 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
|
||||
|
||||
static int cistpl_funce_common(struct mmc_card *card,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
if (size < 0x04 || buf[0] != 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* TPLFE_FN0_BLK_SIZE */
|
||||
card->cis.blksize = buf[1] | (buf[2] << 8);
|
||||
|
||||
/* TPLFE_MAX_TRAN_SPEED */
|
||||
card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] *
|
||||
speed_unit[buf[3] & 7];
|
||||
|
||||
/* Qisda, Daniel Lee, 2009/07/22, e600 { */
|
||||
// MT5921 SDIO host interface protocol timing max value is 25MHz.
|
||||
if (card->cis.max_dtr >= 25000000 && card->host->index == 1) {
|
||||
printk(KERN_INFO "%s: card->cis.max_dtr(%u), reduce to 25MHz \n",
|
||||
mmc_hostname(card->host), card->cis.max_dtr);
|
||||
card->cis.max_dtr = 25000000;
|
||||
}
|
||||
/* Qisda, Daniel Lee, 2009/07/22, e600 } */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_funce_func(struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned vsn;
|
||||
unsigned min_size;
|
||||
|
||||
vsn = func->card->cccr.sdio_vsn;
|
||||
min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
|
||||
|
||||
if (size < min_size || buf[0] != 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* TPLFE_MAX_BLK_SIZE */
|
||||
func->max_blksize = buf[12] | (buf[13] << 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* There should be two versions of the CISTPL_FUNCE tuple,
|
||||
* one for the common CIS (function 0) and a version used by
|
||||
* the individual function's CIS (1-7). Yet, the later has a
|
||||
* different length depending on the SDIO spec version.
|
||||
*/
|
||||
if (func)
|
||||
ret = cistpl_funce_func(func, buf, size);
|
||||
else
|
||||
ret = cistpl_funce_common(card, buf, size);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u "
|
||||
"type %u\n", mmc_hostname(card->host), size, buf[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
|
||||
const unsigned char *, unsigned);
|
||||
|
||||
struct cis_tpl {
|
||||
unsigned char code;
|
||||
unsigned char min_size;
|
||||
tpl_parse_t *parse;
|
||||
};
|
||||
|
||||
static const struct cis_tpl cis_tpl_list[] = {
|
||||
{ 0x15, 3, cistpl_vers_1 },
|
||||
{ 0x20, 4, cistpl_manfid },
|
||||
{ 0x21, 2, /* cistpl_funcid */ },
|
||||
{ 0x22, 0, cistpl_funce },
|
||||
};
|
||||
|
||||
static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func_tuple *this, **prev;
|
||||
unsigned i, ptr = 0;
|
||||
|
||||
/*
|
||||
* Note that this works for the common CIS (function number 0) as
|
||||
* well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS
|
||||
* have the same offset.
|
||||
*/
|
||||
for (i = 0; i < 3; i++) {
|
||||
unsigned char x, fn;
|
||||
|
||||
if (func)
|
||||
fn = func->num;
|
||||
else
|
||||
fn = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0,
|
||||
SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x);
|
||||
if (ret)
|
||||
return ret;
|
||||
ptr |= x << (i * 8);
|
||||
}
|
||||
|
||||
if (func)
|
||||
prev = &func->tuples;
|
||||
else
|
||||
prev = &card->tuples;
|
||||
|
||||
BUG_ON(*prev);
|
||||
|
||||
do {
|
||||
unsigned char tpl_code, tpl_link;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* 0xff means we're done */
|
||||
if (tpl_code == 0xff)
|
||||
break;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL);
|
||||
if (!this)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < tpl_link; i++) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0,
|
||||
ptr + i, 0, &this->data[i]);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
if (ret) {
|
||||
kfree(this);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++)
|
||||
if (cis_tpl_list[i].code == tpl_code)
|
||||
break;
|
||||
if (i >= ARRAY_SIZE(cis_tpl_list)) {
|
||||
/* this tuple is unknown to the core */
|
||||
this->next = NULL;
|
||||
this->code = tpl_code;
|
||||
this->size = tpl_link;
|
||||
*prev = this;
|
||||
prev = &this->next;
|
||||
printk(KERN_DEBUG
|
||||
"%s: queuing CIS tuple 0x%02x length %u\n",
|
||||
mmc_hostname(card->host), tpl_code, tpl_link);
|
||||
} else {
|
||||
const struct cis_tpl *tpl = cis_tpl_list + i;
|
||||
if (tpl_link < tpl->min_size) {
|
||||
printk(KERN_ERR
|
||||
"%s: bad CIS tuple 0x%02x (length = %u, expected >= %u)\n",
|
||||
mmc_hostname(card->host),
|
||||
tpl_code, tpl_link, tpl->min_size);
|
||||
ret = -EINVAL;
|
||||
} else if (tpl->parse) {
|
||||
ret = tpl->parse(card, func,
|
||||
this->data, tpl_link);
|
||||
}
|
||||
kfree(this);
|
||||
}
|
||||
|
||||
ptr += tpl_link;
|
||||
} while (!ret);
|
||||
|
||||
/*
|
||||
* Link in all unknown tuples found in the common CIS so that
|
||||
* drivers don't have to go digging in two places.
|
||||
*/
|
||||
if (func)
|
||||
*prev = card->tuples;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sdio_read_common_cis(struct mmc_card *card)
|
||||
{
|
||||
return sdio_read_cis(card, NULL);
|
||||
}
|
||||
|
||||
void sdio_free_common_cis(struct mmc_card *card)
|
||||
{
|
||||
struct sdio_func_tuple *tuple, *victim;
|
||||
|
||||
tuple = card->tuples;
|
||||
|
||||
while (tuple) {
|
||||
victim = tuple;
|
||||
tuple = tuple->next;
|
||||
kfree(victim);
|
||||
}
|
||||
|
||||
card->tuples = NULL;
|
||||
}
|
||||
|
||||
int sdio_read_func_cis(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdio_read_cis(func->card, func);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Since we've linked to tuples in the card structure,
|
||||
* we must make sure we have a reference to it.
|
||||
*/
|
||||
get_device(&func->card->dev);
|
||||
|
||||
/*
|
||||
* Vendor/device id is optional for function CIS, so
|
||||
* copy it from the card structure as needed.
|
||||
*/
|
||||
if (func->vendor == 0) {
|
||||
func->vendor = func->card->cis.vendor;
|
||||
func->device = func->card->cis.device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sdio_free_func_cis(struct sdio_func *func)
|
||||
{
|
||||
struct sdio_func_tuple *tuple, *victim;
|
||||
|
||||
tuple = func->tuples;
|
||||
|
||||
while (tuple && tuple != func->card->tuples) {
|
||||
victim = tuple;
|
||||
tuple = tuple->next;
|
||||
kfree(victim);
|
||||
}
|
||||
|
||||
func->tuples = NULL;
|
||||
|
||||
/*
|
||||
* We have now removed the link to the tuples in the
|
||||
* card structure, so remove the reference.
|
||||
*/
|
||||
put_device(&func->card->dev);
|
||||
}
|
||||
|
||||
23
drivers/mmc/core/sdio_cis.h
Normal file
23
drivers/mmc/core/sdio_cis.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_cis.h
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 11, 2007
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_SDIO_CIS_H
|
||||
#define _MMC_SDIO_CIS_H
|
||||
|
||||
int sdio_read_common_cis(struct mmc_card *card);
|
||||
void sdio_free_common_cis(struct mmc_card *card);
|
||||
|
||||
int sdio_read_func_cis(struct sdio_func *func);
|
||||
void sdio_free_func_cis(struct sdio_func *func);
|
||||
|
||||
#endif
|
||||
548
drivers/mmc/core/sdio_io.c
Normal file
548
drivers/mmc/core/sdio_io.c
Normal file
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_io.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_ops.h"
|
||||
|
||||
/**
|
||||
* sdio_claim_host - exclusively claim a bus for a certain SDIO function
|
||||
* @func: SDIO function that will be accessed
|
||||
*
|
||||
* Claim a bus for a set of operations. The SDIO function given
|
||||
* is used to figure out which bus is relevant.
|
||||
*/
|
||||
void sdio_claim_host(struct sdio_func *func)
|
||||
{
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
mmc_claim_host(func->card->host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_claim_host);
|
||||
|
||||
/**
|
||||
* sdio_release_host - release a bus for a certain SDIO function
|
||||
* @func: SDIO function that was accessed
|
||||
*
|
||||
* Release a bus, allowing others to claim the bus for their
|
||||
* operations.
|
||||
*/
|
||||
void sdio_release_host(struct sdio_func *func)
|
||||
{
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
mmc_release_host(func->card->host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_release_host);
|
||||
|
||||
/**
|
||||
* sdio_enable_func - enables a SDIO function for usage
|
||||
* @func: SDIO function to enable
|
||||
*
|
||||
* Powers up and activates a SDIO function so that register
|
||||
* access is possible.
|
||||
*/
|
||||
int sdio_enable_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
unsigned long timeout;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func));
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
reg |= 1 << func->num;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* FIXME: This should timeout based on information in the CIS,
|
||||
* but we don't have card to parse that yet.
|
||||
*/
|
||||
timeout = jiffies + HZ;
|
||||
|
||||
while (1) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®);
|
||||
if (ret)
|
||||
goto err;
|
||||
if (reg & (1 << func->num))
|
||||
break;
|
||||
ret = -ETIME;
|
||||
if (time_after(jiffies, timeout))
|
||||
goto err;
|
||||
}
|
||||
|
||||
pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func));
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func));
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_enable_func);
|
||||
|
||||
/**
|
||||
* sdio_disable_func - disable a SDIO function
|
||||
* @func: SDIO function to disable
|
||||
*
|
||||
* Powers down and deactivates a SDIO function. Register access
|
||||
* to this function will fail until the function is reenabled.
|
||||
*/
|
||||
int sdio_disable_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func));
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
reg &= ~(1 << func->num);
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func));
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func));
|
||||
return -EIO;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_disable_func);
|
||||
|
||||
/**
|
||||
* sdio_set_block_size - set the block size of an SDIO function
|
||||
* @func: SDIO function to change
|
||||
* @blksz: new block size or 0 to use the default.
|
||||
*
|
||||
* The default block size is the largest supported by both the function
|
||||
* and the host, with a maximum of 512 to ensure that arbitrarily sized
|
||||
* data transfer use the optimal (least) number of commands.
|
||||
*
|
||||
* A driver may call this to override the default block size set by the
|
||||
* core. This can be used to set a block size greater than the maximum
|
||||
* that reported by the card; it is the driver's responsibility to ensure
|
||||
* it uses a value that the card supports.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if the host does not support the
|
||||
* requested block size, or -EIO (etc.) if one of the resultant FBR block
|
||||
* size register writes failed.
|
||||
*
|
||||
*/
|
||||
int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (blksz > func->card->host->max_blk_size)
|
||||
return -EINVAL;
|
||||
|
||||
if (blksz == 0) {
|
||||
blksz = min(min(
|
||||
func->max_blksize,
|
||||
func->card->host->max_blk_size),
|
||||
512u);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
|
||||
blksz & 0xff, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
|
||||
(blksz >> 8) & 0xff, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
func->cur_blksize = blksz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sdio_set_block_size);
|
||||
|
||||
/* Split an arbitrarily sized data transfer into several
|
||||
* IO_RW_EXTENDED commands. */
|
||||
static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned size)
|
||||
{
|
||||
unsigned remainder = size;
|
||||
unsigned max_blocks;
|
||||
int ret;
|
||||
|
||||
/* Do the bulk of the transfer using block mode (if supported). */
|
||||
if (func->card->cccr.multi_block) {
|
||||
/* Blocks per command is limited by host count, host transfer
|
||||
* size (we only use a single sg entry) and the maximum for
|
||||
* IO_RW_EXTENDED of 511 blocks. */
|
||||
max_blocks = min(min(
|
||||
func->card->host->max_blk_count,
|
||||
func->card->host->max_seg_size / func->cur_blksize),
|
||||
511u);
|
||||
|
||||
while (remainder > func->cur_blksize) {
|
||||
unsigned blocks;
|
||||
|
||||
blocks = remainder / func->cur_blksize;
|
||||
if (blocks > max_blocks)
|
||||
blocks = max_blocks;
|
||||
size = blocks * func->cur_blksize;
|
||||
|
||||
ret = mmc_io_rw_extended(func->card, write,
|
||||
func->num, addr, incr_addr, buf,
|
||||
blocks, func->cur_blksize);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
remainder -= size;
|
||||
buf += size;
|
||||
if (incr_addr)
|
||||
addr += size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the remainder using byte mode. */
|
||||
while (remainder > 0) {
|
||||
size = remainder;
|
||||
if (size > func->cur_blksize)
|
||||
size = func->cur_blksize;
|
||||
if (size > 512)
|
||||
size = 512; /* maximum size for byte mode */
|
||||
|
||||
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
|
||||
incr_addr, buf, 1, size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
remainder -= size;
|
||||
buf += size;
|
||||
if (incr_addr)
|
||||
addr += size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_readb - read a single byte from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a single byte from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned char sdio_readb(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb);
|
||||
|
||||
/**
|
||||
* sdio_writeb - write a single byte to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: byte to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a single byte to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writeb);
|
||||
|
||||
/**
|
||||
* sdio_memcpy_fromio - read a chunk of memory from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @dst: buffer to store the data
|
||||
* @addr: address to begin reading from
|
||||
* @count: number of bytes to read
|
||||
*
|
||||
* Reads from the address space of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_memcpy_fromio(struct sdio_func *func, void *dst,
|
||||
unsigned int addr, int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_memcpy_fromio);
|
||||
|
||||
/**
|
||||
* sdio_memcpy_toio - write a chunk of memory to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to start writing to
|
||||
* @src: buffer that contains the data to write
|
||||
* @count: number of bytes to write
|
||||
*
|
||||
* Writes to the address space of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr,
|
||||
void *src, int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_memcpy_toio);
|
||||
|
||||
/**
|
||||
* sdio_readsb - read from a FIFO on a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @dst: buffer to store the data
|
||||
* @addr: address of (single byte) FIFO
|
||||
* @count: number of bytes to read
|
||||
*
|
||||
* Reads from the specified FIFO of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
|
||||
int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sdio_readsb);
|
||||
|
||||
/**
|
||||
* sdio_writesb - write to a FIFO of a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address of (single byte) FIFO
|
||||
* @src: buffer that contains the data to write
|
||||
* @count: number of bytes to write
|
||||
*
|
||||
* Writes to the specified FIFO of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src,
|
||||
int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writesb);
|
||||
|
||||
/**
|
||||
* sdio_readw - read a 16 bit integer from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a 16 bit integer from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xffff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned short sdio_readw(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
return le16_to_cpu(*(u16*)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readw);
|
||||
|
||||
/**
|
||||
* sdio_writew - write a 16 bit integer to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: integer to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a 16 bit integer to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u16*)func->tmpbuf = cpu_to_le16(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writew);
|
||||
|
||||
/**
|
||||
* sdio_readl - read a 32 bit integer from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a 32 bit integer from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address,
|
||||
* 0xffffffff is returned and @err_ret will contain the error
|
||||
* code.
|
||||
*/
|
||||
unsigned long sdio_readl(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
return le32_to_cpu(*(u32*)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readl);
|
||||
|
||||
/**
|
||||
* sdio_writel - write a 32 bit integer to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: integer to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a 32 bit integer to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u32*)func->tmpbuf = cpu_to_le32(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writel);
|
||||
|
||||
/**
|
||||
* sdio_f0_readb - read a single byte from SDIO function 0
|
||||
* @func: an SDIO function of the card
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a single byte from the address space of SDIO function 0.
|
||||
* If there is a problem reading the address, 0xff is returned
|
||||
* and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_f0_readb);
|
||||
|
||||
/**
|
||||
* sdio_f0_writeb - write a single byte to SDIO function 0
|
||||
* @func: an SDIO function of the card
|
||||
* @b: byte to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a single byte to the address space of SDIO function 0.
|
||||
* @err_ret will contain the status of the actual transfer.
|
||||
*
|
||||
* Only writes to the vendor specific CCCR registers (0xF0 -
|
||||
* 0xFF) are permiited; @err_ret will be set to -EINVAL for *
|
||||
* writes outside this range.
|
||||
*/
|
||||
void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (addr < 0xF0 || addr > 0xFF) {
|
||||
if (err_ret)
|
||||
*err_ret = -EINVAL;
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_f0_writeb);
|
||||
271
drivers/mmc/core/sdio_irq.c
Normal file
271
drivers/mmc/core/sdio_irq.c
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_irq.c
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 18, 2007
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/mmc/core.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_ops.h"
|
||||
|
||||
static int process_sdio_pending_irqs(struct mmc_card *card)
|
||||
{
|
||||
int i, ret, count;
|
||||
unsigned char pending;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
|
||||
if (ret) {
|
||||
printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n",
|
||||
mmc_card_id(card), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (i = 1; i <= 7; i++) {
|
||||
if (pending & (1 << i)) {
|
||||
struct sdio_func *func = card->sdio_func[i - 1];
|
||||
if (!func) {
|
||||
printk(KERN_WARNING "%s: pending IRQ for "
|
||||
"non-existant function\n",
|
||||
mmc_card_id(card));
|
||||
ret = -EINVAL;
|
||||
} else if (func->irq_handler) {
|
||||
func->irq_handler(func);
|
||||
count++;
|
||||
} else {
|
||||
printk(KERN_WARNING "%s: pending IRQ with no handler\n",
|
||||
sdio_func_id(func));
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count)
|
||||
return count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_irq_thread(void *_host)
|
||||
{
|
||||
struct mmc_host *host = _host;
|
||||
struct sched_param param = { .sched_priority = 1 };
|
||||
unsigned long period, idle_period;
|
||||
int ret;
|
||||
|
||||
//Hiko: for new MT5621 WiFi driver compatible
|
||||
current->flags |= PF_NOFREEZE;
|
||||
|
||||
|
||||
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
||||
|
||||
/*
|
||||
* We want to allow for SDIO cards to work even on non SDIO
|
||||
* aware hosts. One thing that non SDIO host cannot do is
|
||||
* asynchronous notification of pending SDIO card interrupts
|
||||
* hence we poll for them in that case.
|
||||
*/
|
||||
idle_period = msecs_to_jiffies(10);
|
||||
period = (host->caps & MMC_CAP_SDIO_IRQ) ?
|
||||
MAX_SCHEDULE_TIMEOUT : idle_period;
|
||||
|
||||
pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
|
||||
mmc_hostname(host), period);
|
||||
|
||||
do {
|
||||
/*
|
||||
* We claim the host here on drivers behalf for a couple
|
||||
* reasons:
|
||||
*
|
||||
* 1) it is already needed to retrieve the CCCR_INTx;
|
||||
* 2) we want the driver(s) to clear the IRQ condition ASAP;
|
||||
* 3) we need to control the abort condition locally.
|
||||
*
|
||||
* Just like traditional hard IRQ handlers, we expect SDIO
|
||||
* IRQ handlers to be quick and to the point, so that the
|
||||
* holding of the host lock does not cover too much work
|
||||
* that doesn't require that lock to be held.
|
||||
*/
|
||||
ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort);
|
||||
if (ret)
|
||||
break;
|
||||
ret = process_sdio_pending_irqs(host->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
/*
|
||||
* Give other threads a chance to run in the presence of
|
||||
* errors. FIXME: determine if due to card removal and
|
||||
* possibly exit this thread if so.
|
||||
*/
|
||||
if (ret < 0)
|
||||
ssleep(1);
|
||||
|
||||
/*
|
||||
* Adaptive polling frequency based on the assumption
|
||||
* that an interrupt will be closely followed by more.
|
||||
* This has a substantial benefit for network devices.
|
||||
*/
|
||||
if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
|
||||
if (ret > 0)
|
||||
period /= 2;
|
||||
else {
|
||||
period++;
|
||||
if (period > idle_period)
|
||||
period = idle_period;
|
||||
}
|
||||
}
|
||||
|
||||
set_task_state(current, TASK_INTERRUPTIBLE);
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ)
|
||||
host->ops->enable_sdio_irq(host, 1);
|
||||
if (!kthread_should_stop())
|
||||
schedule_timeout(period);
|
||||
set_task_state(current, TASK_RUNNING);
|
||||
} while (!kthread_should_stop());
|
||||
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ)
|
||||
host->ops->enable_sdio_irq(host, 0);
|
||||
|
||||
pr_debug("%s: IRQ thread exiting with code %d\n",
|
||||
mmc_hostname(host), ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_card_irq_get(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
if (!host->sdio_irqs++) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 0);
|
||||
host->sdio_irq_thread =
|
||||
kthread_run(sdio_irq_thread, host, "ksdiorqd");
|
||||
if (IS_ERR(host->sdio_irq_thread)) {
|
||||
int err = PTR_ERR(host->sdio_irq_thread);
|
||||
host->sdio_irqs--;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdio_card_irq_put(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
BUG_ON(host->sdio_irqs < 1);
|
||||
|
||||
if (!--host->sdio_irqs) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 1);
|
||||
kthread_stop(host->sdio_irq_thread);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_claim_irq - claim the IRQ for a SDIO function
|
||||
* @func: SDIO function
|
||||
* @handler: IRQ handler callback
|
||||
*
|
||||
* Claim and activate the IRQ for the given SDIO function. The provided
|
||||
* handler will be called when that IRQ is asserted. The host is always
|
||||
* claimed already when the handler is called so the handler must not
|
||||
* call sdio_claim_host() nor sdio_release_host().
|
||||
*/
|
||||
int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
|
||||
|
||||
if (func->irq_handler) {
|
||||
pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg |= 1 << func->num;
|
||||
|
||||
reg |= 1; /* Master interrupt enable */
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
func->irq_handler = handler;
|
||||
ret = sdio_card_irq_get(func->card);
|
||||
if (ret)
|
||||
func->irq_handler = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_claim_irq);
|
||||
|
||||
/**
|
||||
* sdio_release_irq - release the IRQ for a SDIO function
|
||||
* @func: SDIO function
|
||||
*
|
||||
* Disable and release the IRQ for the given SDIO function.
|
||||
*/
|
||||
int sdio_release_irq(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
|
||||
|
||||
if (func->irq_handler) {
|
||||
func->irq_handler = NULL;
|
||||
sdio_card_irq_put(func->card);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg &= ~(1 << func->num);
|
||||
|
||||
/* Disable master interrupt with the last function interrupt */
|
||||
if (!(reg & 0xFE))
|
||||
reg = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_release_irq);
|
||||
|
||||
175
drivers/mmc/core/sdio_ops.c
Normal file
175
drivers/mmc/core/sdio_ops.c
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* linux/drivers/mmc/sdio_ops.c
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_IO_SEND_OP_COND;
|
||||
cmd.arg = ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
/*
|
||||
* Both R1_SPI_IDLE and MMC_CARD_BUSY indicate
|
||||
* an initialized card under SPI, but some cards
|
||||
* (Marvell's) only behave when looking at this
|
||||
* one.
|
||||
*/
|
||||
if (cmd.resp[1] & MMC_CARD_BUSY)
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (rocr)
|
||||
*rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8* out)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(fn > 7);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = SD_IO_RW_DIRECT;
|
||||
cmd.arg = write ? 0x80000000 : 0x00000000;
|
||||
cmd.arg |= fn << 28;
|
||||
cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
|
||||
cmd.arg |= addr << 9;
|
||||
cmd.arg |= in;
|
||||
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
/* host driver already reported errors */
|
||||
} else {
|
||||
if (cmd.resp[0] & R5_ERROR)
|
||||
return -EIO;
|
||||
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
|
||||
return -EINVAL;
|
||||
if (cmd.resp[0] & R5_OUT_OF_RANGE)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
if (mmc_host_is_spi(card->host))
|
||||
*out = (cmd.resp[0] >> 8) & 0xFF;
|
||||
else
|
||||
*out = cmd.resp[0] & 0xFF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(fn > 7);
|
||||
BUG_ON(blocks == 1 && blksz > 512);
|
||||
WARN_ON(blocks == 0);
|
||||
WARN_ON(blksz == 0);
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_IO_RW_EXTENDED;
|
||||
cmd.arg = write ? 0x80000000 : 0x00000000;
|
||||
cmd.arg |= fn << 28;
|
||||
cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
|
||||
cmd.arg |= addr << 9;
|
||||
if (blocks == 1 && blksz <= 512)
|
||||
cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
|
||||
else
|
||||
cmd.arg |= 0x08000000 | blocks; /* block mode */
|
||||
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = blksz;
|
||||
data.blocks = blocks;
|
||||
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, buf, blksz * blocks);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
/* host driver already reported errors */
|
||||
} else {
|
||||
if (cmd.resp[0] & R5_ERROR)
|
||||
return -EIO;
|
||||
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
|
||||
return -EINVAL;
|
||||
if (cmd.resp[0] & R5_OUT_OF_RANGE)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/sdio_ops.h
Normal file
22
drivers/mmc/core/sdio_ops.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* linux/drivers/mmc/sdio_ops.c
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_SDIO_OPS_H
|
||||
#define _MMC_SDIO_OPS_H
|
||||
|
||||
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8* out);
|
||||
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
|
||||
|
||||
#endif
|
||||
|
||||
43
drivers/mmc/core/sysfs.c
Normal file
43
drivers/mmc/core/sysfs.c
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sysfs.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC sysfs/driver model support.
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; attr_name(attrs[i]); i++) {
|
||||
error = device_create_file(&card->dev, &attrs[i]);
|
||||
if (error) {
|
||||
while (--i >= 0)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; attr_name(attrs[i]); i++)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
}
|
||||
|
||||
26
drivers/mmc/core/sysfs.h
Normal file
26
drivers/mmc/core/sysfs.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sysfs.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_SYSFS_H
|
||||
#define _MMC_CORE_SYSFS_H
|
||||
|
||||
#define MMC_ATTR_FN(name, fmt, args...) \
|
||||
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct mmc_card *card = container_of(dev, struct mmc_card, dev);\
|
||||
return sprintf(buf, fmt, args); \
|
||||
}
|
||||
|
||||
#define MMC_ATTR_RO(name) __ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
|
||||
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
|
||||
#endif
|
||||
189
drivers/mmc/host/Kconfig
Normal file
189
drivers/mmc/host/Kconfig
Normal file
@@ -0,0 +1,189 @@
|
||||
#
|
||||
# MMC/SD host controller drivers
|
||||
#
|
||||
|
||||
comment "MMC/SD Host Controller Drivers"
|
||||
|
||||
config MMC_ARMMMCI
|
||||
tristate "ARM AMBA Multimedia Card Interface support"
|
||||
depends on ARM_AMBA
|
||||
help
|
||||
This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card
|
||||
Interface (PL180 and PL181) support. If you have an ARM(R)
|
||||
platform with a Multimedia Card slot, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_PXA
|
||||
tristate "Intel PXA25x/26x/27x Multimedia Card Interface support"
|
||||
depends on ARCH_PXA
|
||||
help
|
||||
This selects the Intel(R) PXA(R) Multimedia card Interface.
|
||||
If you have a PXA(R) platform with a Multimedia Card slot,
|
||||
say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_SDHCI
|
||||
tristate "Secure Digital Host Controller Interface support (EXPERIMENTAL)"
|
||||
depends on PCI && EXPERIMENTAL
|
||||
help
|
||||
This select the generic Secure Digital Host Controller Interface.
|
||||
It is used by manufacturers such as Texas Instruments(R), Ricoh(R)
|
||||
and Toshiba(R). Most controllers found in laptops are of this type.
|
||||
If you have a controller with this interface, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_RICOH_MMC
|
||||
tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)"
|
||||
depends on PCI && EXPERIMENTAL && MMC_SDHCI
|
||||
help
|
||||
This selects the disabler for the Ricoh MMC Controller. This
|
||||
proprietary controller is unnecessary because the SDHCI driver
|
||||
supports MMC cards on the SD controller, but if it is not
|
||||
disabled, it will steal the MMC cards away - rendering them
|
||||
useless. It is safe to select this driver even if you don't
|
||||
have a Ricoh based card reader.
|
||||
|
||||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called ricoh_mmc.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config MMC_OMAP
|
||||
tristate "TI OMAP Multimedia Card Interface support"
|
||||
depends on ARCH_OMAP
|
||||
select TPS65010 if MACH_OMAP_H2
|
||||
help
|
||||
This selects the TI OMAP Multimedia card Interface.
|
||||
If you have an OMAP board with a Multimedia Card slot,
|
||||
say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_WBSD
|
||||
tristate "Winbond W83L51xD SD/MMC Card Interface support"
|
||||
depends on ISA_DMA_API
|
||||
help
|
||||
This selects the Winbond(R) W83L51xD Secure digital and
|
||||
Multimedia card Interface.
|
||||
If you have a machine with a integrated W83L518D or W83L519D
|
||||
SD/MMC card reader, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_AU1X
|
||||
tristate "Alchemy AU1XX0 MMC Card Interface support"
|
||||
depends on SOC_AU1200
|
||||
help
|
||||
This selects the AMD Alchemy(R) Multimedia card interface.
|
||||
If you have a Alchemy platform with a MMC slot, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_AT91
|
||||
tristate "AT91 SD/MMC Card Interface support"
|
||||
depends on ARCH_AT91
|
||||
help
|
||||
This selects the AT91 MCI controller.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_IMX
|
||||
tristate "Motorola i.MX Multimedia Card Interface support"
|
||||
depends on ARCH_IMX
|
||||
help
|
||||
This selects the Motorola i.MX Multimedia card Interface.
|
||||
If you have a i.MX platform with a Multimedia Card slot,
|
||||
say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_TIFM_SD
|
||||
tristate "TI Flash Media MMC/SD Interface support (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL && PCI
|
||||
select TIFM_CORE
|
||||
help
|
||||
Say Y here if you want to be able to access MMC/SD cards with
|
||||
the Texas Instruments(R) Flash Media card reader, found in many
|
||||
laptops.
|
||||
This option 'selects' (turns on, enables) 'TIFM_CORE', but you
|
||||
probably also need appropriate card reader host adapter, such as
|
||||
'Misc devices: TI Flash Media PCI74xx/PCI76xx host adapter support
|
||||
(TIFM_7XX1)'.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called tifm_sd.
|
||||
|
||||
config MMC_SPI
|
||||
tristate "MMC/SD over SPI (EXPERIMENTAL)"
|
||||
depends on MMC && SPI_MASTER && !HIGHMEM && EXPERIMENTAL
|
||||
select CRC7
|
||||
select CRC_ITU_T
|
||||
help
|
||||
Some systems accss MMC/SD cards using a SPI controller instead of
|
||||
using a "native" MMC/SD controller. This has a disadvantage of
|
||||
being relatively high overhead, but a compensating advantage of
|
||||
working on many systems without dedicated MMC/SD controllers.
|
||||
|
||||
If unsure, or if your system has no SPI master driver, say N.
|
||||
|
||||
config MMC_S3C
|
||||
tristate "Samsung S3C Multimedia Card Interface support"
|
||||
select MMC_BLOCK
|
||||
select MMC_SUPPORT_MOVINAND
|
||||
depends on ARCH_S3C2410 && MMC && !(CPU_S3C6400 || CPU_S3C6410)
|
||||
help
|
||||
This selects the Samsung S3C Multimedia Card Interface
|
||||
support.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config HSMMC_S3C
|
||||
tristate "Samsung S3C High Speed MMC Interface support"
|
||||
select MMC_BLOCK
|
||||
select MMC_SUPPORT_MOVINAND
|
||||
depends on (CPU_S3C2443 || CPU_S3C2450 || CPU_S3C2416 || CPU_S3C6400 || CPU_S3C6410) && MMC
|
||||
help
|
||||
This selects the Samsung S3C Multimedia Card Interface
|
||||
support.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config USE_MMC_AS_ROOT
|
||||
bool "MMC can be used as a Root Filesystem Device"
|
||||
depends on HSMMC_S3C && !MACH_SANJOSE2
|
||||
default y
|
||||
help
|
||||
Say Y here when you want to use MMC as a root file system.
|
||||
|
||||
config HSMMC_S3C_IRQ_WORKAROUND
|
||||
bool "Fix IRQ Hanging in HS-MMC"
|
||||
depends on HSMMC_S3C
|
||||
default y
|
||||
help
|
||||
Say Y here when you are using S3C6400 or S3C6410.
|
||||
|
||||
config HSMMC_SCATTERGATHER
|
||||
bool "Support Scatter and Gather in HS-MMC"
|
||||
depends on HSMMC_S3C
|
||||
help
|
||||
Say Y here. This option can support scatter and gather
|
||||
using DMA boundary in HS-MMC DMA engine.
|
||||
|
||||
config MMC_SUPPORT_MOVINAND
|
||||
bool "Support moviNAND from Samsung Electronics"
|
||||
depends on (MMC_S3C || HSMMC_S3C) && MMC
|
||||
help
|
||||
Say Y here to enable MMC Spec 4.2 and support Samsung
|
||||
moviNAND.
|
||||
|
||||
config S3CMMC_DEBUG
|
||||
bool "To debug interface, enable all debug messages"
|
||||
depends on (MMC_S3C || HSMMC_S3C)
|
||||
help
|
||||
Say Y here to enable Debug Messages.
|
||||
|
||||
|
||||
21
drivers/mmc/host/Makefile
Normal file
21
drivers/mmc/host/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# Makefile for MMC/SD host controller drivers
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MMC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
|
||||
obj-$(CONFIG_MMC_PXA) += pxamci.o
|
||||
obj-$(CONFIG_MMC_IMX) += imxmmc.o
|
||||
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
|
||||
obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o
|
||||
obj-$(CONFIG_MMC_WBSD) += wbsd.o
|
||||
obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
|
||||
obj-$(CONFIG_MMC_OMAP) += omap.o
|
||||
obj-$(CONFIG_MMC_AT91) += at91_mci.o
|
||||
obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o
|
||||
obj-$(CONFIG_MMC_SPI) += mmc_spi.o
|
||||
obj-$(CONFIG_HSMMC_S3C) += s3c-hsmmc.o
|
||||
|
||||
1031
drivers/mmc/host/at91_mci.c
Normal file
1031
drivers/mmc/host/at91_mci.c
Normal file
File diff suppressed because it is too large
Load Diff
1022
drivers/mmc/host/au1xmmc.c
Normal file
1022
drivers/mmc/host/au1xmmc.c
Normal file
File diff suppressed because it is too large
Load Diff
96
drivers/mmc/host/au1xmmc.h
Normal file
96
drivers/mmc/host/au1xmmc.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef _AU1XMMC_H_
|
||||
#define _AU1XMMC_H_
|
||||
|
||||
/* Hardware definitions */
|
||||
|
||||
#define AU1XMMC_DESCRIPTOR_COUNT 1
|
||||
#define AU1XMMC_DESCRIPTOR_SIZE 2048
|
||||
|
||||
#define AU1XMMC_OCR ( MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | \
|
||||
MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 | \
|
||||
MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36)
|
||||
|
||||
/* Easy access macros */
|
||||
|
||||
#define HOST_STATUS(h) ((h)->iobase + SD_STATUS)
|
||||
#define HOST_CONFIG(h) ((h)->iobase + SD_CONFIG)
|
||||
#define HOST_ENABLE(h) ((h)->iobase + SD_ENABLE)
|
||||
#define HOST_TXPORT(h) ((h)->iobase + SD_TXPORT)
|
||||
#define HOST_RXPORT(h) ((h)->iobase + SD_RXPORT)
|
||||
#define HOST_CMDARG(h) ((h)->iobase + SD_CMDARG)
|
||||
#define HOST_BLKSIZE(h) ((h)->iobase + SD_BLKSIZE)
|
||||
#define HOST_CMD(h) ((h)->iobase + SD_CMD)
|
||||
#define HOST_CONFIG2(h) ((h)->iobase + SD_CONFIG2)
|
||||
#define HOST_TIMEOUT(h) ((h)->iobase + SD_TIMEOUT)
|
||||
#define HOST_DEBUG(h) ((h)->iobase + SD_DEBUG)
|
||||
|
||||
#define DMA_CHANNEL(h) \
|
||||
( ((h)->flags & HOST_F_XMIT) ? (h)->tx_chan : (h)->rx_chan)
|
||||
|
||||
/* This gives us a hard value for the stop command that we can write directly
|
||||
* to the command register
|
||||
*/
|
||||
|
||||
#define STOP_CMD (SD_CMD_RT_1B|SD_CMD_CT_7|(0xC << SD_CMD_CI_SHIFT)|SD_CMD_GO)
|
||||
|
||||
/* This is the set of interrupts that we configure by default */
|
||||
|
||||
#if 0
|
||||
#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | SD_CONFIG_DD | \
|
||||
SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
|
||||
#endif
|
||||
|
||||
#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | \
|
||||
SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
|
||||
/* The poll event (looking for insert/remove events runs twice a second */
|
||||
#define AU1XMMC_DETECT_TIMEOUT (HZ/2)
|
||||
|
||||
struct au1xmmc_host {
|
||||
struct mmc_host *mmc;
|
||||
struct mmc_request *mrq;
|
||||
|
||||
u32 id;
|
||||
|
||||
u32 flags;
|
||||
u32 iobase;
|
||||
u32 clock;
|
||||
u32 bus_width;
|
||||
u32 power_mode;
|
||||
|
||||
int status;
|
||||
|
||||
struct {
|
||||
int len;
|
||||
int dir;
|
||||
} dma;
|
||||
|
||||
struct {
|
||||
int index;
|
||||
int offset;
|
||||
int len;
|
||||
} pio;
|
||||
|
||||
u32 tx_chan;
|
||||
u32 rx_chan;
|
||||
|
||||
struct timer_list timer;
|
||||
struct tasklet_struct finish_task;
|
||||
struct tasklet_struct data_task;
|
||||
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
/* Status flags used by the host structure */
|
||||
|
||||
#define HOST_F_XMIT 0x0001
|
||||
#define HOST_F_RECV 0x0002
|
||||
#define HOST_F_DMA 0x0010
|
||||
#define HOST_F_ACTIVE 0x0100
|
||||
#define HOST_F_STOP 0x1000
|
||||
|
||||
#define HOST_S_IDLE 0x0001
|
||||
#define HOST_S_CMD 0x0002
|
||||
#define HOST_S_DATA 0x0003
|
||||
#define HOST_S_STOP 0x0004
|
||||
|
||||
#endif
|
||||
1149
drivers/mmc/host/imxmmc.c
Normal file
1149
drivers/mmc/host/imxmmc.c
Normal file
File diff suppressed because it is too large
Load Diff
67
drivers/mmc/host/imxmmc.h
Normal file
67
drivers/mmc/host/imxmmc.h
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
# define __REG16(x) (*((volatile u16 *)IO_ADDRESS(x)))
|
||||
|
||||
#define MMC_STR_STP_CLK __REG16(IMX_MMC_BASE + 0x00)
|
||||
#define MMC_STATUS __REG16(IMX_MMC_BASE + 0x04)
|
||||
#define MMC_CLK_RATE __REG16(IMX_MMC_BASE + 0x08)
|
||||
#define MMC_CMD_DAT_CONT __REG16(IMX_MMC_BASE + 0x0C)
|
||||
#define MMC_RES_TO __REG16(IMX_MMC_BASE + 0x10)
|
||||
#define MMC_READ_TO __REG16(IMX_MMC_BASE + 0x14)
|
||||
#define MMC_BLK_LEN __REG16(IMX_MMC_BASE + 0x18)
|
||||
#define MMC_NOB __REG16(IMX_MMC_BASE + 0x1C)
|
||||
#define MMC_REV_NO __REG16(IMX_MMC_BASE + 0x20)
|
||||
#define MMC_INT_MASK __REG16(IMX_MMC_BASE + 0x24)
|
||||
#define MMC_CMD __REG16(IMX_MMC_BASE + 0x28)
|
||||
#define MMC_ARGH __REG16(IMX_MMC_BASE + 0x2C)
|
||||
#define MMC_ARGL __REG16(IMX_MMC_BASE + 0x30)
|
||||
#define MMC_RES_FIFO __REG16(IMX_MMC_BASE + 0x34)
|
||||
#define MMC_BUFFER_ACCESS __REG16(IMX_MMC_BASE + 0x38)
|
||||
#define MMC_BUFFER_ACCESS_OFS 0x38
|
||||
|
||||
|
||||
#define STR_STP_CLK_ENDIAN (1<<5)
|
||||
#define STR_STP_CLK_RESET (1<<3)
|
||||
#define STR_STP_CLK_ENABLE (1<<2)
|
||||
#define STR_STP_CLK_START_CLK (1<<1)
|
||||
#define STR_STP_CLK_STOP_CLK (1<<0)
|
||||
#define STATUS_CARD_PRESENCE (1<<15)
|
||||
#define STATUS_SDIO_INT_ACTIVE (1<<14)
|
||||
#define STATUS_END_CMD_RESP (1<<13)
|
||||
#define STATUS_WRITE_OP_DONE (1<<12)
|
||||
#define STATUS_DATA_TRANS_DONE (1<<11)
|
||||
#define STATUS_WR_CRC_ERROR_CODE_MASK (3<<10)
|
||||
#define STATUS_CARD_BUS_CLK_RUN (1<<8)
|
||||
#define STATUS_APPL_BUFF_FF (1<<7)
|
||||
#define STATUS_APPL_BUFF_FE (1<<6)
|
||||
#define STATUS_RESP_CRC_ERR (1<<5)
|
||||
#define STATUS_CRC_READ_ERR (1<<3)
|
||||
#define STATUS_CRC_WRITE_ERR (1<<2)
|
||||
#define STATUS_TIME_OUT_RESP (1<<1)
|
||||
#define STATUS_TIME_OUT_READ (1<<0)
|
||||
#define STATUS_ERR_MASK 0x2f
|
||||
#define CLK_RATE_PRESCALER(x) ((x) & 0x7)
|
||||
#define CLK_RATE_CLK_RATE(x) (((x) & 0x7) << 3)
|
||||
#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1<<12)
|
||||
#define CMD_DAT_CONT_STOP_READWAIT (1<<11)
|
||||
#define CMD_DAT_CONT_START_READWAIT (1<<10)
|
||||
#define CMD_DAT_CONT_BUS_WIDTH_1 (0<<8)
|
||||
#define CMD_DAT_CONT_BUS_WIDTH_4 (2<<8)
|
||||
#define CMD_DAT_CONT_INIT (1<<7)
|
||||
#define CMD_DAT_CONT_BUSY (1<<6)
|
||||
#define CMD_DAT_CONT_STREAM_BLOCK (1<<5)
|
||||
#define CMD_DAT_CONT_WRITE (1<<4)
|
||||
#define CMD_DAT_CONT_DATA_ENABLE (1<<3)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R1 (1)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R2 (2)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R3 (3)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R4 (4)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R5 (5)
|
||||
#define CMD_DAT_CONT_RESPONSE_FORMAT_R6 (6)
|
||||
#define INT_MASK_AUTO_CARD_DETECT (1<<6)
|
||||
#define INT_MASK_DAT0_EN (1<<5)
|
||||
#define INT_MASK_SDIO (1<<4)
|
||||
#define INT_MASK_BUF_READY (1<<3)
|
||||
#define INT_MASK_END_CMD_RES (1<<2)
|
||||
#define INT_MASK_WRITE_OP_DONE (1<<1)
|
||||
#define INT_MASK_DATA_TRAN (1<<0)
|
||||
#define INT_ALL (0x7f)
|
||||
1413
drivers/mmc/host/mmc_spi.c
Normal file
1413
drivers/mmc/host/mmc_spi.c
Normal file
File diff suppressed because it is too large
Load Diff
711
drivers/mmc/host/mmci.c
Normal file
711
drivers/mmc/host/mmci.c
Normal file
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* linux/drivers/mmc/host/mmci.c - ARM PrimeCell MMCI PL180/1 driver
|
||||
*
|
||||
* Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/div64.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
#include <asm/mach/mmc.h>
|
||||
|
||||
#include "mmci.h"
|
||||
|
||||
#define DRIVER_NAME "mmci-pl18x"
|
||||
|
||||
#define DBG(host,fmt,args...) \
|
||||
pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args)
|
||||
|
||||
static unsigned int fmax = 515633;
|
||||
|
||||
static void
|
||||
mmci_request_end(struct mmci_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
writel(0, host->base + MMCICOMMAND);
|
||||
|
||||
BUG_ON(host->data);
|
||||
|
||||
host->mrq = NULL;
|
||||
host->cmd = NULL;
|
||||
|
||||
if (mrq->data)
|
||||
mrq->data->bytes_xfered = host->data_xfered;
|
||||
|
||||
/*
|
||||
* Need to drop the host lock here; mmc_request_done may call
|
||||
* back into the driver...
|
||||
*/
|
||||
spin_unlock(&host->lock);
|
||||
mmc_request_done(host->mmc, mrq);
|
||||
spin_lock(&host->lock);
|
||||
}
|
||||
|
||||
static void mmci_stop_data(struct mmci_host *host)
|
||||
{
|
||||
writel(0, host->base + MMCIDATACTRL);
|
||||
writel(0, host->base + MMCIMASK1);
|
||||
host->data = NULL;
|
||||
}
|
||||
|
||||
static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
|
||||
{
|
||||
unsigned int datactrl, timeout, irqmask;
|
||||
unsigned long long clks;
|
||||
void __iomem *base;
|
||||
int blksz_bits;
|
||||
|
||||
DBG(host, "blksz %04x blks %04x flags %08x\n",
|
||||
data->blksz, data->blocks, data->flags);
|
||||
|
||||
host->data = data;
|
||||
host->size = data->blksz;
|
||||
host->data_xfered = 0;
|
||||
|
||||
mmci_init_sg(host, data);
|
||||
|
||||
clks = (unsigned long long)data->timeout_ns * host->cclk;
|
||||
do_div(clks, 1000000000UL);
|
||||
|
||||
timeout = data->timeout_clks + (unsigned int)clks;
|
||||
|
||||
base = host->base;
|
||||
writel(timeout, base + MMCIDATATIMER);
|
||||
writel(host->size, base + MMCIDATALENGTH);
|
||||
|
||||
blksz_bits = ffs(data->blksz) - 1;
|
||||
BUG_ON(1 << blksz_bits != data->blksz);
|
||||
|
||||
datactrl = MCI_DPSM_ENABLE | blksz_bits << 4;
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
datactrl |= MCI_DPSM_DIRECTION;
|
||||
irqmask = MCI_RXFIFOHALFFULLMASK;
|
||||
|
||||
/*
|
||||
* If we have less than a FIFOSIZE of bytes to transfer,
|
||||
* trigger a PIO interrupt as soon as any data is available.
|
||||
*/
|
||||
if (host->size < MCI_FIFOSIZE)
|
||||
irqmask |= MCI_RXDATAAVLBLMASK;
|
||||
} else {
|
||||
/*
|
||||
* We don't actually need to include "FIFO empty" here
|
||||
* since its implicit in "FIFO half empty".
|
||||
*/
|
||||
irqmask = MCI_TXFIFOHALFEMPTYMASK;
|
||||
}
|
||||
|
||||
writel(datactrl, base + MMCIDATACTRL);
|
||||
writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0);
|
||||
writel(irqmask, base + MMCIMASK1);
|
||||
}
|
||||
|
||||
static void
|
||||
mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
|
||||
{
|
||||
void __iomem *base = host->base;
|
||||
|
||||
DBG(host, "op %02x arg %08x flags %08x\n",
|
||||
cmd->opcode, cmd->arg, cmd->flags);
|
||||
|
||||
if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
|
||||
writel(0, base + MMCICOMMAND);
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
c |= cmd->opcode | MCI_CPSM_ENABLE;
|
||||
if (cmd->flags & MMC_RSP_PRESENT) {
|
||||
if (cmd->flags & MMC_RSP_136)
|
||||
c |= MCI_CPSM_LONGRSP;
|
||||
c |= MCI_CPSM_RESPONSE;
|
||||
}
|
||||
if (/*interrupt*/0)
|
||||
c |= MCI_CPSM_INTERRUPT;
|
||||
|
||||
host->cmd = cmd;
|
||||
|
||||
writel(cmd->arg, base + MMCIARGUMENT);
|
||||
writel(c, base + MMCICOMMAND);
|
||||
}
|
||||
|
||||
static void
|
||||
mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
|
||||
unsigned int status)
|
||||
{
|
||||
if (status & MCI_DATABLOCKEND) {
|
||||
host->data_xfered += data->blksz;
|
||||
}
|
||||
if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) {
|
||||
if (status & MCI_DATACRCFAIL)
|
||||
data->error = -EILSEQ;
|
||||
else if (status & MCI_DATATIMEOUT)
|
||||
data->error = -ETIMEDOUT;
|
||||
else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN))
|
||||
data->error = -EIO;
|
||||
status |= MCI_DATAEND;
|
||||
|
||||
/*
|
||||
* We hit an error condition. Ensure that any data
|
||||
* partially written to a page is properly coherent.
|
||||
*/
|
||||
if (host->sg_len && data->flags & MMC_DATA_READ)
|
||||
flush_dcache_page(sg_page(host->sg_ptr));
|
||||
}
|
||||
if (status & MCI_DATAEND) {
|
||||
mmci_stop_data(host);
|
||||
|
||||
if (!data->stop) {
|
||||
mmci_request_end(host, data->mrq);
|
||||
} else {
|
||||
mmci_start_command(host, data->stop, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
|
||||
unsigned int status)
|
||||
{
|
||||
void __iomem *base = host->base;
|
||||
|
||||
host->cmd = NULL;
|
||||
|
||||
cmd->resp[0] = readl(base + MMCIRESPONSE0);
|
||||
cmd->resp[1] = readl(base + MMCIRESPONSE1);
|
||||
cmd->resp[2] = readl(base + MMCIRESPONSE2);
|
||||
cmd->resp[3] = readl(base + MMCIRESPONSE3);
|
||||
|
||||
if (status & MCI_CMDTIMEOUT) {
|
||||
cmd->error = -ETIMEDOUT;
|
||||
} else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) {
|
||||
cmd->error = -EILSEQ;
|
||||
}
|
||||
|
||||
if (!cmd->data || cmd->error) {
|
||||
if (host->data)
|
||||
mmci_stop_data(host);
|
||||
mmci_request_end(host, cmd->mrq);
|
||||
} else if (!(cmd->data->flags & MMC_DATA_READ)) {
|
||||
mmci_start_data(host, cmd->data);
|
||||
}
|
||||
}
|
||||
|
||||
static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int remain)
|
||||
{
|
||||
void __iomem *base = host->base;
|
||||
char *ptr = buffer;
|
||||
u32 status;
|
||||
|
||||
do {
|
||||
int count = host->size - (readl(base + MMCIFIFOCNT) << 2);
|
||||
|
||||
if (count > remain)
|
||||
count = remain;
|
||||
|
||||
if (count <= 0)
|
||||
break;
|
||||
|
||||
readsl(base + MMCIFIFO, ptr, count >> 2);
|
||||
|
||||
ptr += count;
|
||||
remain -= count;
|
||||
|
||||
if (remain == 0)
|
||||
break;
|
||||
|
||||
status = readl(base + MMCISTATUS);
|
||||
} while (status & MCI_RXDATAAVLBL);
|
||||
|
||||
return ptr - buffer;
|
||||
}
|
||||
|
||||
static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int remain, u32 status)
|
||||
{
|
||||
void __iomem *base = host->base;
|
||||
char *ptr = buffer;
|
||||
|
||||
do {
|
||||
unsigned int count, maxcnt;
|
||||
|
||||
maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE : MCI_FIFOHALFSIZE;
|
||||
count = min(remain, maxcnt);
|
||||
|
||||
writesl(base + MMCIFIFO, ptr, count >> 2);
|
||||
|
||||
ptr += count;
|
||||
remain -= count;
|
||||
|
||||
if (remain == 0)
|
||||
break;
|
||||
|
||||
status = readl(base + MMCISTATUS);
|
||||
} while (status & MCI_TXFIFOHALFEMPTY);
|
||||
|
||||
return ptr - buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* PIO data transfer IRQ handler.
|
||||
*/
|
||||
static irqreturn_t mmci_pio_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct mmci_host *host = dev_id;
|
||||
void __iomem *base = host->base;
|
||||
u32 status;
|
||||
|
||||
status = readl(base + MMCISTATUS);
|
||||
|
||||
DBG(host, "irq1 %08x\n", status);
|
||||
|
||||
do {
|
||||
unsigned long flags;
|
||||
unsigned int remain, len;
|
||||
char *buffer;
|
||||
|
||||
/*
|
||||
* For write, we only need to test the half-empty flag
|
||||
* here - if the FIFO is completely empty, then by
|
||||
* definition it is more than half empty.
|
||||
*
|
||||
* For read, check for data available.
|
||||
*/
|
||||
if (!(status & (MCI_TXFIFOHALFEMPTY|MCI_RXDATAAVLBL)))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Map the current scatter buffer.
|
||||
*/
|
||||
buffer = mmci_kmap_atomic(host, &flags) + host->sg_off;
|
||||
remain = host->sg_ptr->length - host->sg_off;
|
||||
|
||||
len = 0;
|
||||
if (status & MCI_RXACTIVE)
|
||||
len = mmci_pio_read(host, buffer, remain);
|
||||
if (status & MCI_TXACTIVE)
|
||||
len = mmci_pio_write(host, buffer, remain, status);
|
||||
|
||||
/*
|
||||
* Unmap the buffer.
|
||||
*/
|
||||
mmci_kunmap_atomic(host, buffer, &flags);
|
||||
|
||||
host->sg_off += len;
|
||||
host->size -= len;
|
||||
remain -= len;
|
||||
|
||||
if (remain)
|
||||
break;
|
||||
|
||||
/*
|
||||
* If we were reading, and we have completed this
|
||||
* page, ensure that the data cache is coherent.
|
||||
*/
|
||||
if (status & MCI_RXACTIVE)
|
||||
flush_dcache_page(sg_page(host->sg_ptr));
|
||||
|
||||
if (!mmci_next_sg(host))
|
||||
break;
|
||||
|
||||
status = readl(base + MMCISTATUS);
|
||||
} while (1);
|
||||
|
||||
/*
|
||||
* If we're nearing the end of the read, switch to
|
||||
* "any data available" mode.
|
||||
*/
|
||||
if (status & MCI_RXACTIVE && host->size < MCI_FIFOSIZE)
|
||||
writel(MCI_RXDATAAVLBLMASK, base + MMCIMASK1);
|
||||
|
||||
/*
|
||||
* If we run out of data, disable the data IRQs; this
|
||||
* prevents a race where the FIFO becomes empty before
|
||||
* the chip itself has disabled the data path, and
|
||||
* stops us racing with our data end IRQ.
|
||||
*/
|
||||
if (host->size == 0) {
|
||||
writel(0, base + MMCIMASK1);
|
||||
writel(readl(base + MMCIMASK0) | MCI_DATAENDMASK, base + MMCIMASK0);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle completion of command and data transfers.
|
||||
*/
|
||||
static irqreturn_t mmci_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct mmci_host *host = dev_id;
|
||||
u32 status;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(&host->lock);
|
||||
|
||||
do {
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
status = readl(host->base + MMCISTATUS);
|
||||
status &= readl(host->base + MMCIMASK0);
|
||||
writel(status, host->base + MMCICLEAR);
|
||||
|
||||
DBG(host, "irq0 %08x\n", status);
|
||||
|
||||
data = host->data;
|
||||
if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|
|
||||
MCI_RXOVERRUN|MCI_DATAEND|MCI_DATABLOCKEND) && data)
|
||||
mmci_data_irq(host, data, status);
|
||||
|
||||
cmd = host->cmd;
|
||||
if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd)
|
||||
mmci_cmd_irq(host, cmd, status);
|
||||
|
||||
ret = 1;
|
||||
} while (status);
|
||||
|
||||
spin_unlock(&host->lock);
|
||||
|
||||
return IRQ_RETVAL(ret);
|
||||
}
|
||||
|
||||
static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
|
||||
WARN_ON(host->mrq != NULL);
|
||||
|
||||
if (mrq->data && !is_power_of_2(mrq->data->blksz)) {
|
||||
printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n",
|
||||
mmc_hostname(mmc), mrq->data->blksz);
|
||||
mrq->cmd->error = -EINVAL;
|
||||
mmc_request_done(mmc, mrq);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irq(&host->lock);
|
||||
|
||||
host->mrq = mrq;
|
||||
|
||||
if (mrq->data && mrq->data->flags & MMC_DATA_READ)
|
||||
mmci_start_data(host, mrq->data);
|
||||
|
||||
mmci_start_command(host, mrq->cmd, 0);
|
||||
|
||||
spin_unlock_irq(&host->lock);
|
||||
}
|
||||
|
||||
static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
u32 clk = 0, pwr = 0;
|
||||
|
||||
if (ios->clock) {
|
||||
if (ios->clock >= host->mclk) {
|
||||
clk = MCI_CLK_BYPASS;
|
||||
host->cclk = host->mclk;
|
||||
} else {
|
||||
clk = host->mclk / (2 * ios->clock) - 1;
|
||||
if (clk > 256)
|
||||
clk = 255;
|
||||
host->cclk = host->mclk / (2 * (clk + 1));
|
||||
}
|
||||
clk |= MCI_CLK_ENABLE;
|
||||
}
|
||||
|
||||
if (host->plat->translate_vdd)
|
||||
pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd);
|
||||
|
||||
switch (ios->power_mode) {
|
||||
case MMC_POWER_OFF:
|
||||
break;
|
||||
case MMC_POWER_UP:
|
||||
pwr |= MCI_PWR_UP;
|
||||
break;
|
||||
case MMC_POWER_ON:
|
||||
pwr |= MCI_PWR_ON;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
|
||||
pwr |= MCI_ROD;
|
||||
|
||||
writel(clk, host->base + MMCICLOCK);
|
||||
|
||||
if (host->pwr != pwr) {
|
||||
host->pwr = pwr;
|
||||
writel(pwr, host->base + MMCIPOWER);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops mmci_ops = {
|
||||
.request = mmci_request,
|
||||
.set_ios = mmci_set_ios,
|
||||
};
|
||||
|
||||
static void mmci_check_status(unsigned long data)
|
||||
{
|
||||
struct mmci_host *host = (struct mmci_host *)data;
|
||||
unsigned int status;
|
||||
|
||||
status = host->plat->status(mmc_dev(host->mmc));
|
||||
if (status ^ host->oldstat)
|
||||
mmc_detect_change(host->mmc, 0);
|
||||
|
||||
host->oldstat = status;
|
||||
mod_timer(&host->timer, jiffies + HZ);
|
||||
}
|
||||
|
||||
static int mmci_probe(struct amba_device *dev, void *id)
|
||||
{
|
||||
struct mmc_platform_data *plat = dev->dev.platform_data;
|
||||
struct mmci_host *host;
|
||||
struct mmc_host *mmc;
|
||||
int ret;
|
||||
|
||||
/* must have platform data */
|
||||
if (!plat) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = amba_request_regions(dev, DRIVER_NAME);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
|
||||
if (!mmc) {
|
||||
ret = -ENOMEM;
|
||||
goto rel_regions;
|
||||
}
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->clk = clk_get(&dev->dev, "MCLK");
|
||||
if (IS_ERR(host->clk)) {
|
||||
ret = PTR_ERR(host->clk);
|
||||
host->clk = NULL;
|
||||
goto host_free;
|
||||
}
|
||||
|
||||
ret = clk_enable(host->clk);
|
||||
if (ret)
|
||||
goto clk_free;
|
||||
|
||||
host->plat = plat;
|
||||
host->mclk = clk_get_rate(host->clk);
|
||||
host->mmc = mmc;
|
||||
host->base = ioremap(dev->res.start, SZ_4K);
|
||||
if (!host->base) {
|
||||
ret = -ENOMEM;
|
||||
goto clk_disable;
|
||||
}
|
||||
|
||||
mmc->ops = &mmci_ops;
|
||||
mmc->f_min = (host->mclk + 511) / 512;
|
||||
mmc->f_max = min(host->mclk, fmax);
|
||||
mmc->ocr_avail = plat->ocr_mask;
|
||||
mmc->caps = MMC_CAP_MULTIWRITE;
|
||||
|
||||
/*
|
||||
* We can do SGIO
|
||||
*/
|
||||
mmc->max_hw_segs = 16;
|
||||
mmc->max_phys_segs = NR_SG;
|
||||
|
||||
/*
|
||||
* Since we only have a 16-bit data length register, we must
|
||||
* ensure that we don't exceed 2^16-1 bytes in a single request.
|
||||
*/
|
||||
mmc->max_req_size = 65535;
|
||||
|
||||
/*
|
||||
* Set the maximum segment size. Since we aren't doing DMA
|
||||
* (yet) we are only limited by the data length register.
|
||||
*/
|
||||
mmc->max_seg_size = mmc->max_req_size;
|
||||
|
||||
/*
|
||||
* Block size can be up to 2048 bytes, but must be a power of two.
|
||||
*/
|
||||
mmc->max_blk_size = 2048;
|
||||
|
||||
/*
|
||||
* No limit on the number of blocks transferred.
|
||||
*/
|
||||
mmc->max_blk_count = mmc->max_req_size;
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
|
||||
writel(0, host->base + MMCIMASK0);
|
||||
writel(0, host->base + MMCIMASK1);
|
||||
writel(0xfff, host->base + MMCICLEAR);
|
||||
|
||||
ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
|
||||
if (ret)
|
||||
goto unmap;
|
||||
|
||||
ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
|
||||
if (ret)
|
||||
goto irq0_free;
|
||||
|
||||
writel(MCI_IRQENABLE, host->base + MMCIMASK0);
|
||||
|
||||
amba_set_drvdata(dev, mmc);
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%016llx irq %d,%d\n",
|
||||
mmc_hostname(mmc), amba_rev(dev), amba_config(dev),
|
||||
(unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]);
|
||||
|
||||
init_timer(&host->timer);
|
||||
host->timer.data = (unsigned long)host;
|
||||
host->timer.function = mmci_check_status;
|
||||
host->timer.expires = jiffies + HZ;
|
||||
add_timer(&host->timer);
|
||||
|
||||
return 0;
|
||||
|
||||
irq0_free:
|
||||
free_irq(dev->irq[0], host);
|
||||
unmap:
|
||||
iounmap(host->base);
|
||||
clk_disable:
|
||||
clk_disable(host->clk);
|
||||
clk_free:
|
||||
clk_put(host->clk);
|
||||
host_free:
|
||||
mmc_free_host(mmc);
|
||||
rel_regions:
|
||||
amba_release_regions(dev);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmci_remove(struct amba_device *dev)
|
||||
{
|
||||
struct mmc_host *mmc = amba_get_drvdata(dev);
|
||||
|
||||
amba_set_drvdata(dev, NULL);
|
||||
|
||||
if (mmc) {
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
|
||||
del_timer_sync(&host->timer);
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
writel(0, host->base + MMCIMASK0);
|
||||
writel(0, host->base + MMCIMASK1);
|
||||
|
||||
writel(0, host->base + MMCICOMMAND);
|
||||
writel(0, host->base + MMCIDATACTRL);
|
||||
|
||||
free_irq(dev->irq[0], host);
|
||||
free_irq(dev->irq[1], host);
|
||||
|
||||
iounmap(host->base);
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
|
||||
mmc_free_host(mmc);
|
||||
|
||||
amba_release_regions(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mmci_suspend(struct amba_device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_host *mmc = amba_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (mmc) {
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
|
||||
ret = mmc_suspend_host(mmc, state);
|
||||
if (ret == 0)
|
||||
writel(0, host->base + MMCIMASK0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmci_resume(struct amba_device *dev)
|
||||
{
|
||||
struct mmc_host *mmc = amba_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (mmc) {
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
|
||||
writel(MCI_IRQENABLE, host->base + MMCIMASK0);
|
||||
|
||||
ret = mmc_resume_host(mmc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
#define mmci_suspend NULL
|
||||
#define mmci_resume NULL
|
||||
#endif
|
||||
|
||||
static struct amba_id mmci_ids[] = {
|
||||
{
|
||||
.id = 0x00041180,
|
||||
.mask = 0x000fffff,
|
||||
},
|
||||
{
|
||||
.id = 0x00041181,
|
||||
.mask = 0x000fffff,
|
||||
},
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
static struct amba_driver mmci_driver = {
|
||||
.drv = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = mmci_probe,
|
||||
.remove = mmci_remove,
|
||||
.suspend = mmci_suspend,
|
||||
.resume = mmci_resume,
|
||||
.id_table = mmci_ids,
|
||||
};
|
||||
|
||||
static int __init mmci_init(void)
|
||||
{
|
||||
return amba_driver_register(&mmci_driver);
|
||||
}
|
||||
|
||||
static void __exit mmci_exit(void)
|
||||
{
|
||||
amba_driver_unregister(&mmci_driver);
|
||||
}
|
||||
|
||||
module_init(mmci_init);
|
||||
module_exit(mmci_exit);
|
||||
module_param(fmax, uint, 0444);
|
||||
|
||||
MODULE_DESCRIPTION("ARM PrimeCell PL180/181 Multimedia Card Interface driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
179
drivers/mmc/host/mmci.h
Normal file
179
drivers/mmc/host/mmci.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* linux/drivers/mmc/host/mmci.h - ARM PrimeCell MMCI PL180/1 driver
|
||||
*
|
||||
* Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#define MMCIPOWER 0x000
|
||||
#define MCI_PWR_OFF 0x00
|
||||
#define MCI_PWR_UP 0x02
|
||||
#define MCI_PWR_ON 0x03
|
||||
#define MCI_OD (1 << 6)
|
||||
#define MCI_ROD (1 << 7)
|
||||
|
||||
#define MMCICLOCK 0x004
|
||||
#define MCI_CLK_ENABLE (1 << 8)
|
||||
#define MCI_CLK_PWRSAVE (1 << 9)
|
||||
#define MCI_CLK_BYPASS (1 << 10)
|
||||
|
||||
#define MMCIARGUMENT 0x008
|
||||
#define MMCICOMMAND 0x00c
|
||||
#define MCI_CPSM_RESPONSE (1 << 6)
|
||||
#define MCI_CPSM_LONGRSP (1 << 7)
|
||||
#define MCI_CPSM_INTERRUPT (1 << 8)
|
||||
#define MCI_CPSM_PENDING (1 << 9)
|
||||
#define MCI_CPSM_ENABLE (1 << 10)
|
||||
|
||||
#define MMCIRESPCMD 0x010
|
||||
#define MMCIRESPONSE0 0x014
|
||||
#define MMCIRESPONSE1 0x018
|
||||
#define MMCIRESPONSE2 0x01c
|
||||
#define MMCIRESPONSE3 0x020
|
||||
#define MMCIDATATIMER 0x024
|
||||
#define MMCIDATALENGTH 0x028
|
||||
#define MMCIDATACTRL 0x02c
|
||||
#define MCI_DPSM_ENABLE (1 << 0)
|
||||
#define MCI_DPSM_DIRECTION (1 << 1)
|
||||
#define MCI_DPSM_MODE (1 << 2)
|
||||
#define MCI_DPSM_DMAENABLE (1 << 3)
|
||||
|
||||
#define MMCIDATACNT 0x030
|
||||
#define MMCISTATUS 0x034
|
||||
#define MCI_CMDCRCFAIL (1 << 0)
|
||||
#define MCI_DATACRCFAIL (1 << 1)
|
||||
#define MCI_CMDTIMEOUT (1 << 2)
|
||||
#define MCI_DATATIMEOUT (1 << 3)
|
||||
#define MCI_TXUNDERRUN (1 << 4)
|
||||
#define MCI_RXOVERRUN (1 << 5)
|
||||
#define MCI_CMDRESPEND (1 << 6)
|
||||
#define MCI_CMDSENT (1 << 7)
|
||||
#define MCI_DATAEND (1 << 8)
|
||||
#define MCI_DATABLOCKEND (1 << 10)
|
||||
#define MCI_CMDACTIVE (1 << 11)
|
||||
#define MCI_TXACTIVE (1 << 12)
|
||||
#define MCI_RXACTIVE (1 << 13)
|
||||
#define MCI_TXFIFOHALFEMPTY (1 << 14)
|
||||
#define MCI_RXFIFOHALFFULL (1 << 15)
|
||||
#define MCI_TXFIFOFULL (1 << 16)
|
||||
#define MCI_RXFIFOFULL (1 << 17)
|
||||
#define MCI_TXFIFOEMPTY (1 << 18)
|
||||
#define MCI_RXFIFOEMPTY (1 << 19)
|
||||
#define MCI_TXDATAAVLBL (1 << 20)
|
||||
#define MCI_RXDATAAVLBL (1 << 21)
|
||||
|
||||
#define MMCICLEAR 0x038
|
||||
#define MCI_CMDCRCFAILCLR (1 << 0)
|
||||
#define MCI_DATACRCFAILCLR (1 << 1)
|
||||
#define MCI_CMDTIMEOUTCLR (1 << 2)
|
||||
#define MCI_DATATIMEOUTCLR (1 << 3)
|
||||
#define MCI_TXUNDERRUNCLR (1 << 4)
|
||||
#define MCI_RXOVERRUNCLR (1 << 5)
|
||||
#define MCI_CMDRESPENDCLR (1 << 6)
|
||||
#define MCI_CMDSENTCLR (1 << 7)
|
||||
#define MCI_DATAENDCLR (1 << 8)
|
||||
#define MCI_DATABLOCKENDCLR (1 << 10)
|
||||
|
||||
#define MMCIMASK0 0x03c
|
||||
#define MCI_CMDCRCFAILMASK (1 << 0)
|
||||
#define MCI_DATACRCFAILMASK (1 << 1)
|
||||
#define MCI_CMDTIMEOUTMASK (1 << 2)
|
||||
#define MCI_DATATIMEOUTMASK (1 << 3)
|
||||
#define MCI_TXUNDERRUNMASK (1 << 4)
|
||||
#define MCI_RXOVERRUNMASK (1 << 5)
|
||||
#define MCI_CMDRESPENDMASK (1 << 6)
|
||||
#define MCI_CMDSENTMASK (1 << 7)
|
||||
#define MCI_DATAENDMASK (1 << 8)
|
||||
#define MCI_DATABLOCKENDMASK (1 << 10)
|
||||
#define MCI_CMDACTIVEMASK (1 << 11)
|
||||
#define MCI_TXACTIVEMASK (1 << 12)
|
||||
#define MCI_RXACTIVEMASK (1 << 13)
|
||||
#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
|
||||
#define MCI_RXFIFOHALFFULLMASK (1 << 15)
|
||||
#define MCI_TXFIFOFULLMASK (1 << 16)
|
||||
#define MCI_RXFIFOFULLMASK (1 << 17)
|
||||
#define MCI_TXFIFOEMPTYMASK (1 << 18)
|
||||
#define MCI_RXFIFOEMPTYMASK (1 << 19)
|
||||
#define MCI_TXDATAAVLBLMASK (1 << 20)
|
||||
#define MCI_RXDATAAVLBLMASK (1 << 21)
|
||||
|
||||
#define MMCIMASK1 0x040
|
||||
#define MMCIFIFOCNT 0x048
|
||||
#define MMCIFIFO 0x080 /* to 0x0bc */
|
||||
|
||||
#define MCI_IRQENABLE \
|
||||
(MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
|
||||
MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
|
||||
MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATABLOCKENDMASK)
|
||||
|
||||
/*
|
||||
* The size of the FIFO in bytes.
|
||||
*/
|
||||
#define MCI_FIFOSIZE (16*4)
|
||||
|
||||
#define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2)
|
||||
|
||||
#define NR_SG 16
|
||||
|
||||
struct clk;
|
||||
|
||||
struct mmci_host {
|
||||
void __iomem *base;
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
struct mmc_host *mmc;
|
||||
struct clk *clk;
|
||||
|
||||
unsigned int data_xfered;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
unsigned int mclk;
|
||||
unsigned int cclk;
|
||||
u32 pwr;
|
||||
struct mmc_platform_data *plat;
|
||||
|
||||
struct timer_list timer;
|
||||
unsigned int oldstat;
|
||||
|
||||
unsigned int sg_len;
|
||||
|
||||
/* pio stuff */
|
||||
struct scatterlist *sg_ptr;
|
||||
unsigned int sg_off;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
static inline void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
|
||||
{
|
||||
/*
|
||||
* Ideally, we want the higher levels to pass us a scatter list.
|
||||
*/
|
||||
host->sg_len = data->sg_len;
|
||||
host->sg_ptr = data->sg;
|
||||
host->sg_off = 0;
|
||||
}
|
||||
|
||||
static inline int mmci_next_sg(struct mmci_host *host)
|
||||
{
|
||||
host->sg_ptr++;
|
||||
host->sg_off = 0;
|
||||
return --host->sg_len;
|
||||
}
|
||||
|
||||
static inline char *mmci_kmap_atomic(struct mmci_host *host, unsigned long *flags)
|
||||
{
|
||||
struct scatterlist *sg = host->sg_ptr;
|
||||
|
||||
local_irq_save(*flags);
|
||||
return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
|
||||
}
|
||||
|
||||
static inline void mmci_kunmap_atomic(struct mmci_host *host, void *buffer, unsigned long *flags)
|
||||
{
|
||||
kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
|
||||
local_irq_restore(*flags);
|
||||
}
|
||||
1277
drivers/mmc/host/omap.c
Normal file
1277
drivers/mmc/host/omap.c
Normal file
File diff suppressed because it is too large
Load Diff
684
drivers/mmc/host/pxamci.c
Normal file
684
drivers/mmc/host/pxamci.c
Normal file
@@ -0,0 +1,684 @@
|
||||
/*
|
||||
* linux/drivers/mmc/host/pxa.c - PXA MMCI driver
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This hardware is really sick:
|
||||
* - No way to clear interrupts.
|
||||
* - Have to turn off the clock whenever we touch the device.
|
||||
* - Doesn't tell you how many data blocks were transferred.
|
||||
* Yuck!
|
||||
*
|
||||
* 1 and 3 byte data transfers not supported
|
||||
* max block length up to 1023
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
|
||||
#include <asm/arch/pxa-regs.h>
|
||||
#include <asm/arch/mmc.h>
|
||||
|
||||
#include "pxamci.h"
|
||||
|
||||
#define DRIVER_NAME "pxa2xx-mci"
|
||||
|
||||
#define NR_SG 1
|
||||
#define CLKRT_OFF (~0)
|
||||
|
||||
struct pxamci_host {
|
||||
struct mmc_host *mmc;
|
||||
spinlock_t lock;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
unsigned long clkrate;
|
||||
int irq;
|
||||
int dma;
|
||||
unsigned int clkrt;
|
||||
unsigned int cmdat;
|
||||
unsigned int imask;
|
||||
unsigned int power_mode;
|
||||
struct pxamci_platform_data *pdata;
|
||||
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
dma_addr_t sg_dma;
|
||||
struct pxa_dma_desc *sg_cpu;
|
||||
unsigned int dma_len;
|
||||
|
||||
unsigned int dma_dir;
|
||||
};
|
||||
|
||||
static void pxamci_stop_clock(struct pxamci_host *host)
|
||||
{
|
||||
if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
|
||||
unsigned long timeout = 10000;
|
||||
unsigned int v;
|
||||
|
||||
writel(STOP_CLOCK, host->base + MMC_STRPCL);
|
||||
|
||||
do {
|
||||
v = readl(host->base + MMC_STAT);
|
||||
if (!(v & STAT_CLK_EN))
|
||||
break;
|
||||
udelay(1);
|
||||
} while (timeout--);
|
||||
|
||||
if (v & STAT_CLK_EN)
|
||||
dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->imask &= ~mask;
|
||||
writel(host->imask, host->base + MMC_I_MASK);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->imask |= mask;
|
||||
writel(host->imask, host->base + MMC_I_MASK);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
|
||||
{
|
||||
unsigned int nob = data->blocks;
|
||||
unsigned long long clks;
|
||||
unsigned int timeout;
|
||||
u32 dcmd;
|
||||
int i;
|
||||
|
||||
host->data = data;
|
||||
|
||||
if (data->flags & MMC_DATA_STREAM)
|
||||
nob = 0xffff;
|
||||
|
||||
writel(nob, host->base + MMC_NOB);
|
||||
writel(data->blksz, host->base + MMC_BLKLEN);
|
||||
|
||||
clks = (unsigned long long)data->timeout_ns * host->clkrate;
|
||||
do_div(clks, 1000000000UL);
|
||||
timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
|
||||
writel((timeout + 255) / 256, host->base + MMC_RDTO);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
host->dma_dir = DMA_FROM_DEVICE;
|
||||
dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG;
|
||||
DRCMRTXMMC = 0;
|
||||
DRCMRRXMMC = host->dma | DRCMR_MAPVLD;
|
||||
} else {
|
||||
host->dma_dir = DMA_TO_DEVICE;
|
||||
dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC;
|
||||
DRCMRRXMMC = 0;
|
||||
DRCMRTXMMC = host->dma | DRCMR_MAPVLD;
|
||||
}
|
||||
|
||||
dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
|
||||
|
||||
host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
|
||||
host->dma_dir);
|
||||
|
||||
for (i = 0; i < host->dma_len; i++) {
|
||||
unsigned int length = sg_dma_len(&data->sg[i]);
|
||||
host->sg_cpu[i].dcmd = dcmd | length;
|
||||
if (length & 31 && !(data->flags & MMC_DATA_READ))
|
||||
host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN;
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
|
||||
host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
|
||||
} else {
|
||||
host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
|
||||
host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
|
||||
}
|
||||
host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
|
||||
sizeof(struct pxa_dma_desc);
|
||||
}
|
||||
host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
|
||||
wmb();
|
||||
|
||||
DDADR(host->dma) = host->sg_dma;
|
||||
DCSR(host->dma) = DCSR_RUN;
|
||||
}
|
||||
|
||||
static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
|
||||
{
|
||||
WARN_ON(host->cmd != NULL);
|
||||
host->cmd = cmd;
|
||||
|
||||
if (cmd->flags & MMC_RSP_BUSY)
|
||||
cmdat |= CMDAT_BUSY;
|
||||
|
||||
#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
|
||||
switch (RSP_TYPE(mmc_resp_type(cmd))) {
|
||||
case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
|
||||
cmdat |= CMDAT_RESP_SHORT;
|
||||
break;
|
||||
case RSP_TYPE(MMC_RSP_R3):
|
||||
cmdat |= CMDAT_RESP_R3;
|
||||
break;
|
||||
case RSP_TYPE(MMC_RSP_R2):
|
||||
cmdat |= CMDAT_RESP_R2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(cmd->opcode, host->base + MMC_CMD);
|
||||
writel(cmd->arg >> 16, host->base + MMC_ARGH);
|
||||
writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
|
||||
writel(cmdat, host->base + MMC_CMDAT);
|
||||
writel(host->clkrt, host->base + MMC_CLKRT);
|
||||
|
||||
writel(START_CLOCK, host->base + MMC_STRPCL);
|
||||
|
||||
pxamci_enable_irq(host, END_CMD_RES);
|
||||
}
|
||||
|
||||
static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
host->mrq = NULL;
|
||||
host->cmd = NULL;
|
||||
host->data = NULL;
|
||||
mmc_request_done(host->mmc, mrq);
|
||||
}
|
||||
|
||||
static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
int i;
|
||||
u32 v;
|
||||
|
||||
if (!cmd)
|
||||
return 0;
|
||||
|
||||
host->cmd = NULL;
|
||||
|
||||
/*
|
||||
* Did I mention this is Sick. We always need to
|
||||
* discard the upper 8 bits of the first 16-bit word.
|
||||
*/
|
||||
v = readl(host->base + MMC_RES) & 0xffff;
|
||||
for (i = 0; i < 4; i++) {
|
||||
u32 w1 = readl(host->base + MMC_RES) & 0xffff;
|
||||
u32 w2 = readl(host->base + MMC_RES) & 0xffff;
|
||||
cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
|
||||
v = w2;
|
||||
}
|
||||
|
||||
if (stat & STAT_TIME_OUT_RESPONSE) {
|
||||
cmd->error = -ETIMEDOUT;
|
||||
} else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
|
||||
#ifdef CONFIG_PXA27x
|
||||
/*
|
||||
* workaround for erratum #42:
|
||||
* Intel PXA27x Family Processor Specification Update Rev 001
|
||||
* A bogus CRC error can appear if the msb of a 136 bit
|
||||
* response is a one.
|
||||
*/
|
||||
if (cmd->flags & MMC_RSP_136 && cmd->resp[0] & 0x80000000) {
|
||||
pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode);
|
||||
} else
|
||||
#endif
|
||||
cmd->error = -EILSEQ;
|
||||
}
|
||||
|
||||
pxamci_disable_irq(host, END_CMD_RES);
|
||||
if (host->data && !cmd->error) {
|
||||
pxamci_enable_irq(host, DATA_TRAN_DONE);
|
||||
} else {
|
||||
pxamci_finish_request(host, host->mrq);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
|
||||
{
|
||||
struct mmc_data *data = host->data;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
DCSR(host->dma) = 0;
|
||||
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
|
||||
host->dma_dir);
|
||||
|
||||
if (stat & STAT_READ_TIME_OUT)
|
||||
data->error = -ETIMEDOUT;
|
||||
else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
|
||||
data->error = -EILSEQ;
|
||||
|
||||
/*
|
||||
* There appears to be a hardware design bug here. There seems to
|
||||
* be no way to find out how much data was transferred to the card.
|
||||
* This means that if there was an error on any block, we mark all
|
||||
* data blocks as being in error.
|
||||
*/
|
||||
if (!data->error)
|
||||
data->bytes_xfered = data->blocks * data->blksz;
|
||||
else
|
||||
data->bytes_xfered = 0;
|
||||
|
||||
pxamci_disable_irq(host, DATA_TRAN_DONE);
|
||||
|
||||
host->data = NULL;
|
||||
if (host->mrq->stop) {
|
||||
pxamci_stop_clock(host);
|
||||
pxamci_start_cmd(host, host->mrq->stop, host->cmdat);
|
||||
} else {
|
||||
pxamci_finish_request(host, host->mrq);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static irqreturn_t pxamci_irq(int irq, void *devid)
|
||||
{
|
||||
struct pxamci_host *host = devid;
|
||||
unsigned int ireg;
|
||||
int handled = 0;
|
||||
|
||||
ireg = readl(host->base + MMC_I_REG) & ~readl(host->base + MMC_I_MASK);
|
||||
|
||||
if (ireg) {
|
||||
unsigned stat = readl(host->base + MMC_STAT);
|
||||
|
||||
pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);
|
||||
|
||||
if (ireg & END_CMD_RES)
|
||||
handled |= pxamci_cmd_done(host, stat);
|
||||
if (ireg & DATA_TRAN_DONE)
|
||||
handled |= pxamci_data_done(host, stat);
|
||||
if (ireg & SDIO_INT) {
|
||||
mmc_signal_sdio_irq(host->mmc);
|
||||
handled = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_RETVAL(handled);
|
||||
}
|
||||
|
||||
static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct pxamci_host *host = mmc_priv(mmc);
|
||||
unsigned int cmdat;
|
||||
|
||||
WARN_ON(host->mrq != NULL);
|
||||
|
||||
host->mrq = mrq;
|
||||
|
||||
pxamci_stop_clock(host);
|
||||
|
||||
cmdat = host->cmdat;
|
||||
host->cmdat &= ~CMDAT_INIT;
|
||||
|
||||
if (mrq->data) {
|
||||
pxamci_setup_data(host, mrq->data);
|
||||
|
||||
cmdat &= ~CMDAT_BUSY;
|
||||
cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
|
||||
if (mrq->data->flags & MMC_DATA_WRITE)
|
||||
cmdat |= CMDAT_WRITE;
|
||||
|
||||
if (mrq->data->flags & MMC_DATA_STREAM)
|
||||
cmdat |= CMDAT_STREAM;
|
||||
}
|
||||
|
||||
pxamci_start_cmd(host, mrq->cmd, cmdat);
|
||||
}
|
||||
|
||||
static int pxamci_get_ro(struct mmc_host *mmc)
|
||||
{
|
||||
struct pxamci_host *host = mmc_priv(mmc);
|
||||
|
||||
if (host->pdata && host->pdata->get_ro)
|
||||
return host->pdata->get_ro(mmc_dev(mmc));
|
||||
/* Host doesn't support read only detection so assume writeable */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct pxamci_host *host = mmc_priv(mmc);
|
||||
|
||||
if (ios->clock) {
|
||||
unsigned long rate = host->clkrate;
|
||||
unsigned int clk = rate / ios->clock;
|
||||
|
||||
if (host->clkrt == CLKRT_OFF)
|
||||
clk_enable(host->clk);
|
||||
|
||||
/*
|
||||
* clk might result in a lower divisor than we
|
||||
* desire. check for that condition and adjust
|
||||
* as appropriate.
|
||||
*/
|
||||
if (rate / clk > ios->clock)
|
||||
clk <<= 1;
|
||||
host->clkrt = fls(clk) - 1;
|
||||
|
||||
/*
|
||||
* we write clkrt on the next command
|
||||
*/
|
||||
} else {
|
||||
pxamci_stop_clock(host);
|
||||
if (host->clkrt != CLKRT_OFF) {
|
||||
host->clkrt = CLKRT_OFF;
|
||||
clk_disable(host->clk);
|
||||
}
|
||||
}
|
||||
|
||||
if (host->power_mode != ios->power_mode) {
|
||||
host->power_mode = ios->power_mode;
|
||||
|
||||
if (host->pdata && host->pdata->setpower)
|
||||
host->pdata->setpower(mmc_dev(mmc), ios->vdd);
|
||||
|
||||
if (ios->power_mode == MMC_POWER_ON)
|
||||
host->cmdat |= CMDAT_INIT;
|
||||
}
|
||||
|
||||
if (ios->bus_width == MMC_BUS_WIDTH_4)
|
||||
host->cmdat |= CMDAT_SD_4DAT;
|
||||
else
|
||||
host->cmdat &= ~CMDAT_SD_4DAT;
|
||||
|
||||
pr_debug("PXAMCI: clkrt = %x cmdat = %x\n",
|
||||
host->clkrt, host->cmdat);
|
||||
}
|
||||
|
||||
static void pxamci_enable_sdio_irq(struct mmc_host *host, int enable)
|
||||
{
|
||||
struct pxamci_host *pxa_host = mmc_priv(host);
|
||||
|
||||
if (enable)
|
||||
pxamci_enable_irq(pxa_host, SDIO_INT);
|
||||
else
|
||||
pxamci_disable_irq(pxa_host, SDIO_INT);
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops pxamci_ops = {
|
||||
.request = pxamci_request,
|
||||
.get_ro = pxamci_get_ro,
|
||||
.set_ios = pxamci_set_ios,
|
||||
.enable_sdio_irq = pxamci_enable_sdio_irq,
|
||||
};
|
||||
|
||||
static void pxamci_dma_irq(int dma, void *devid)
|
||||
{
|
||||
struct pxamci_host *host = devid;
|
||||
int dcsr = DCSR(dma);
|
||||
DCSR(dma) = dcsr & ~DCSR_STOPIRQEN;
|
||||
|
||||
if (dcsr & DCSR_ENDINTR) {
|
||||
writel(BUF_PART_FULL, host->base + MMC_PRTBUF);
|
||||
} else {
|
||||
printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n",
|
||||
mmc_hostname(host->mmc), dma, dcsr);
|
||||
host->data->error = -EIO;
|
||||
pxamci_data_done(host, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t pxamci_detect_irq(int irq, void *devid)
|
||||
{
|
||||
struct pxamci_host *host = mmc_priv(devid);
|
||||
|
||||
mmc_detect_change(devid, host->pdata->detect_delay);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int pxamci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mmc_host *mmc;
|
||||
struct pxamci_host *host = NULL;
|
||||
struct resource *r;
|
||||
int ret, irq;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (!r || irq < 0)
|
||||
return -ENXIO;
|
||||
|
||||
r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
|
||||
if (!r)
|
||||
return -EBUSY;
|
||||
|
||||
mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
|
||||
if (!mmc) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mmc->ops = &pxamci_ops;
|
||||
|
||||
/*
|
||||
* We can do SG-DMA, but we don't because we never know how much
|
||||
* data we successfully wrote to the card.
|
||||
*/
|
||||
mmc->max_phys_segs = NR_SG;
|
||||
|
||||
/*
|
||||
* Our hardware DMA can handle a maximum of one page per SG entry.
|
||||
*/
|
||||
mmc->max_seg_size = PAGE_SIZE;
|
||||
|
||||
/*
|
||||
* Block length register is only 10 bits before PXA27x.
|
||||
*/
|
||||
mmc->max_blk_size = (cpu_is_pxa21x() || cpu_is_pxa25x()) ? 1023 : 2048;
|
||||
|
||||
/*
|
||||
* Block count register is 16 bits.
|
||||
*/
|
||||
mmc->max_blk_count = 65535;
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->mmc = mmc;
|
||||
host->dma = -1;
|
||||
host->pdata = pdev->dev.platform_data;
|
||||
host->clkrt = CLKRT_OFF;
|
||||
|
||||
host->clk = clk_get(&pdev->dev, "MMCCLK");
|
||||
if (IS_ERR(host->clk)) {
|
||||
ret = PTR_ERR(host->clk);
|
||||
host->clk = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
host->clkrate = clk_get_rate(host->clk);
|
||||
|
||||
/*
|
||||
* Calculate minimum clock rate, rounding up.
|
||||
*/
|
||||
mmc->f_min = (host->clkrate + 63) / 64;
|
||||
mmc->f_max = host->clkrate;
|
||||
|
||||
mmc->ocr_avail = host->pdata ?
|
||||
host->pdata->ocr_mask :
|
||||
MMC_VDD_32_33|MMC_VDD_33_34;
|
||||
mmc->caps = 0;
|
||||
host->cmdat = 0;
|
||||
if (!cpu_is_pxa21x() && !cpu_is_pxa25x()) {
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
|
||||
host->cmdat |= CMDAT_SDIO_INT_EN;
|
||||
}
|
||||
|
||||
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
|
||||
if (!host->sg_cpu) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
host->res = r;
|
||||
host->irq = irq;
|
||||
host->imask = MMC_I_MASK_ALL;
|
||||
|
||||
host->base = ioremap(r->start, SZ_4K);
|
||||
if (!host->base) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that the host controller is shut down, and setup
|
||||
* with our defaults.
|
||||
*/
|
||||
pxamci_stop_clock(host);
|
||||
writel(0, host->base + MMC_SPI);
|
||||
writel(64, host->base + MMC_RESTO);
|
||||
writel(host->imask, host->base + MMC_I_MASK);
|
||||
|
||||
host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
|
||||
pxamci_dma_irq, host);
|
||||
if (host->dma < 0) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
platform_set_drvdata(pdev, mmc);
|
||||
|
||||
if (host->pdata && host->pdata->init)
|
||||
host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (host) {
|
||||
if (host->dma >= 0)
|
||||
pxa_free_dma(host->dma);
|
||||
if (host->base)
|
||||
iounmap(host->base);
|
||||
if (host->sg_cpu)
|
||||
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
|
||||
if (host->clk)
|
||||
clk_put(host->clk);
|
||||
}
|
||||
if (mmc)
|
||||
mmc_free_host(mmc);
|
||||
release_resource(r);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxamci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mmc_host *mmc = platform_get_drvdata(pdev);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
if (mmc) {
|
||||
struct pxamci_host *host = mmc_priv(mmc);
|
||||
|
||||
if (host->pdata && host->pdata->exit)
|
||||
host->pdata->exit(&pdev->dev, mmc);
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
pxamci_stop_clock(host);
|
||||
writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
|
||||
END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
|
||||
host->base + MMC_I_MASK);
|
||||
|
||||
DRCMRRXMMC = 0;
|
||||
DRCMRTXMMC = 0;
|
||||
|
||||
free_irq(host->irq, host);
|
||||
pxa_free_dma(host->dma);
|
||||
iounmap(host->base);
|
||||
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
|
||||
|
||||
clk_put(host->clk);
|
||||
|
||||
release_resource(host->res);
|
||||
|
||||
mmc_free_host(mmc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pxamci_suspend(struct platform_device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_host *mmc = platform_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (mmc)
|
||||
ret = mmc_suspend_host(mmc, state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pxamci_resume(struct platform_device *dev)
|
||||
{
|
||||
struct mmc_host *mmc = platform_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (mmc)
|
||||
ret = mmc_resume_host(mmc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
#define pxamci_suspend NULL
|
||||
#define pxamci_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver pxamci_driver = {
|
||||
.probe = pxamci_probe,
|
||||
.remove = pxamci_remove,
|
||||
.suspend = pxamci_suspend,
|
||||
.resume = pxamci_resume,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init pxamci_init(void)
|
||||
{
|
||||
return platform_driver_register(&pxamci_driver);
|
||||
}
|
||||
|
||||
static void __exit pxamci_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pxamci_driver);
|
||||
}
|
||||
|
||||
module_init(pxamci_init);
|
||||
module_exit(pxamci_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
90
drivers/mmc/host/pxamci.h
Normal file
90
drivers/mmc/host/pxamci.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#define MMC_STRPCL 0x0000
|
||||
#define STOP_CLOCK (1 << 0)
|
||||
#define START_CLOCK (2 << 0)
|
||||
|
||||
#define MMC_STAT 0x0004
|
||||
#define STAT_END_CMD_RES (1 << 13)
|
||||
#define STAT_PRG_DONE (1 << 12)
|
||||
#define STAT_DATA_TRAN_DONE (1 << 11)
|
||||
#define STAT_CLK_EN (1 << 8)
|
||||
#define STAT_RECV_FIFO_FULL (1 << 7)
|
||||
#define STAT_XMIT_FIFO_EMPTY (1 << 6)
|
||||
#define STAT_RES_CRC_ERR (1 << 5)
|
||||
#define STAT_SPI_READ_ERROR_TOKEN (1 << 4)
|
||||
#define STAT_CRC_READ_ERROR (1 << 3)
|
||||
#define STAT_CRC_WRITE_ERROR (1 << 2)
|
||||
#define STAT_TIME_OUT_RESPONSE (1 << 1)
|
||||
#define STAT_READ_TIME_OUT (1 << 0)
|
||||
|
||||
#define MMC_CLKRT 0x0008 /* 3 bit */
|
||||
|
||||
#define MMC_SPI 0x000c
|
||||
#define SPI_CS_ADDRESS (1 << 3)
|
||||
#define SPI_CS_EN (1 << 2)
|
||||
#define CRC_ON (1 << 1)
|
||||
#define SPI_EN (1 << 0)
|
||||
|
||||
#define MMC_CMDAT 0x0010
|
||||
#define CMDAT_SDIO_INT_EN (1 << 11)
|
||||
#define CMDAT_SD_4DAT (1 << 8)
|
||||
#define CMDAT_DMAEN (1 << 7)
|
||||
#define CMDAT_INIT (1 << 6)
|
||||
#define CMDAT_BUSY (1 << 5)
|
||||
#define CMDAT_STREAM (1 << 4) /* 1 = stream */
|
||||
#define CMDAT_WRITE (1 << 3) /* 1 = write */
|
||||
#define CMDAT_DATAEN (1 << 2)
|
||||
#define CMDAT_RESP_NONE (0 << 0)
|
||||
#define CMDAT_RESP_SHORT (1 << 0)
|
||||
#define CMDAT_RESP_R2 (2 << 0)
|
||||
#define CMDAT_RESP_R3 (3 << 0)
|
||||
|
||||
#define MMC_RESTO 0x0014 /* 7 bit */
|
||||
|
||||
#define MMC_RDTO 0x0018 /* 16 bit */
|
||||
|
||||
#define MMC_BLKLEN 0x001c /* 10 bit */
|
||||
|
||||
#define MMC_NOB 0x0020 /* 16 bit */
|
||||
|
||||
#define MMC_PRTBUF 0x0024
|
||||
#define BUF_PART_FULL (1 << 0)
|
||||
|
||||
#define MMC_I_MASK 0x0028
|
||||
|
||||
/*PXA27x MMC interrupts*/
|
||||
#define SDIO_SUSPEND_ACK (1 << 12)
|
||||
#define SDIO_INT (1 << 11)
|
||||
#define RD_STALLED (1 << 10)
|
||||
#define RES_ERR (1 << 9)
|
||||
#define DAT_ERR (1 << 8)
|
||||
#define TINT (1 << 7)
|
||||
|
||||
/*PXA2xx MMC interrupts*/
|
||||
#define TXFIFO_WR_REQ (1 << 6)
|
||||
#define RXFIFO_RD_REQ (1 << 5)
|
||||
#define CLK_IS_OFF (1 << 4)
|
||||
#define STOP_CMD (1 << 3)
|
||||
#define END_CMD_RES (1 << 2)
|
||||
#define PRG_DONE (1 << 1)
|
||||
#define DATA_TRAN_DONE (1 << 0)
|
||||
|
||||
#ifdef CONFIG_PXA27x
|
||||
#define MMC_I_MASK_ALL 0x00001fff
|
||||
#else
|
||||
#define MMC_I_MASK_ALL 0x0000007f
|
||||
#endif
|
||||
|
||||
#define MMC_I_REG 0x002c
|
||||
/* same as MMC_I_MASK */
|
||||
|
||||
#define MMC_CMD 0x0030
|
||||
|
||||
#define MMC_ARGH 0x0034 /* 16 bit */
|
||||
|
||||
#define MMC_ARGL 0x0038 /* 16 bit */
|
||||
|
||||
#define MMC_RES 0x003c /* 16 bit */
|
||||
|
||||
#define MMC_RXFIFO 0x0040 /* 8 bit */
|
||||
|
||||
#define MMC_TXFIFO 0x0044 /* 8 bit */
|
||||
151
drivers/mmc/host/ricoh_mmc.c
Normal file
151
drivers/mmc/host/ricoh_mmc.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* ricoh_mmc.c - Dummy driver to disable the Rioch MMC controller.
|
||||
*
|
||||
* Copyright (C) 2007 Philip Langdale, All Rights Reserved.
|
||||
*
|
||||
* 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 is a conceptually ridiculous driver, but it is required by the way
|
||||
* the Ricoh multi-function R5C832 works. This chip implements firewire
|
||||
* and four different memory card controllers. Two of those controllers are
|
||||
* an SDHCI controller and a proprietary MMC controller. The linux SDHCI
|
||||
* driver supports MMC cards but the chip detects MMC cards in hardware
|
||||
* and directs them to the MMC controller - so the SDHCI driver never sees
|
||||
* them. To get around this, we must disable the useless MMC controller.
|
||||
* At that point, the SDHCI controller will start seeing them. As a bonus,
|
||||
* a detection event occurs immediately, even if the MMC card is already
|
||||
* in the reader.
|
||||
*
|
||||
* The relevant registers live on the firewire function, so this is unavoidably
|
||||
* ugly. Such is life.
|
||||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
|
||||
#define DRIVER_NAME "ricoh-mmc"
|
||||
|
||||
static const struct pci_device_id pci_ids[] __devinitdata = {
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_RICOH,
|
||||
.device = PCI_DEVICE_ID_RICOH_R5C843,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
},
|
||||
{ /* end: all zeroes */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, pci_ids);
|
||||
|
||||
static int __devinit ricoh_mmc_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
u8 rev;
|
||||
|
||||
struct pci_dev *fw_dev = NULL;
|
||||
|
||||
BUG_ON(pdev == NULL);
|
||||
BUG_ON(ent == NULL);
|
||||
|
||||
pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
|
||||
|
||||
printk(KERN_INFO DRIVER_NAME
|
||||
": Ricoh MMC controller found at %s [%04x:%04x] (rev %x)\n",
|
||||
pci_name(pdev), (int)pdev->vendor, (int)pdev->device,
|
||||
(int)rev);
|
||||
|
||||
while ((fw_dev = pci_get_device(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_R5C832, fw_dev))) {
|
||||
if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) &&
|
||||
pdev->bus == fw_dev->bus) {
|
||||
u8 write_enable;
|
||||
u8 disable;
|
||||
|
||||
pci_read_config_byte(fw_dev, 0xCB, &disable);
|
||||
if (disable & 0x02) {
|
||||
printk(KERN_INFO DRIVER_NAME
|
||||
": Controller already disabled. Nothing to do.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pci_read_config_byte(fw_dev, 0xCA, &write_enable);
|
||||
pci_write_config_byte(fw_dev, 0xCA, 0x57);
|
||||
pci_write_config_byte(fw_dev, 0xCB, disable | 0x02);
|
||||
pci_write_config_byte(fw_dev, 0xCA, write_enable);
|
||||
|
||||
pci_set_drvdata(pdev, fw_dev);
|
||||
|
||||
printk(KERN_INFO DRIVER_NAME
|
||||
": Controller is now disabled.\n");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pci_get_drvdata(pdev) == NULL) {
|
||||
printk(KERN_WARNING DRIVER_NAME
|
||||
": Main firewire function not found. Cannot disable controller.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __devexit ricoh_mmc_remove(struct pci_dev *pdev)
|
||||
{
|
||||
u8 write_enable;
|
||||
u8 disable;
|
||||
struct pci_dev *fw_dev = NULL;
|
||||
|
||||
fw_dev = pci_get_drvdata(pdev);
|
||||
BUG_ON(fw_dev == NULL);
|
||||
|
||||
pci_read_config_byte(fw_dev, 0xCA, &write_enable);
|
||||
pci_read_config_byte(fw_dev, 0xCB, &disable);
|
||||
pci_write_config_byte(fw_dev, 0xCA, 0x57);
|
||||
pci_write_config_byte(fw_dev, 0xCB, disable & ~0x02);
|
||||
pci_write_config_byte(fw_dev, 0xCA, write_enable);
|
||||
|
||||
printk(KERN_INFO DRIVER_NAME
|
||||
": Controller is now re-enabled.\n");
|
||||
|
||||
pci_set_drvdata(pdev, NULL);
|
||||
}
|
||||
|
||||
static struct pci_driver ricoh_mmc_driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.id_table = pci_ids,
|
||||
.probe = ricoh_mmc_probe,
|
||||
.remove = __devexit_p(ricoh_mmc_remove),
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Driver init/exit *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static int __init ricoh_mmc_drv_init(void)
|
||||
{
|
||||
printk(KERN_INFO DRIVER_NAME
|
||||
": Ricoh MMC Controller disabling driver\n");
|
||||
printk(KERN_INFO DRIVER_NAME ": Copyright(c) Philip Langdale\n");
|
||||
|
||||
return pci_register_driver(&ricoh_mmc_driver);
|
||||
}
|
||||
|
||||
static void __exit ricoh_mmc_drv_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&ricoh_mmc_driver);
|
||||
}
|
||||
|
||||
module_init(ricoh_mmc_drv_init);
|
||||
module_exit(ricoh_mmc_drv_exit);
|
||||
|
||||
MODULE_AUTHOR("Philip Langdale <philipl@alumni.utexas.net>");
|
||||
MODULE_DESCRIPTION("Ricoh MMC Controller disabling driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
1822
drivers/mmc/host/s3c-hsmmc.c
Normal file
1822
drivers/mmc/host/s3c-hsmmc.c
Normal file
File diff suppressed because it is too large
Load Diff
94
drivers/mmc/host/s3c-hsmmc.h
Normal file
94
drivers/mmc/host/s3c-hsmmc.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* linux/drivers/mmc/s3c-hsmmc.h - Samsung S3C HS-MMC Interface driver
|
||||
*
|
||||
* $Id: s3c-hsmmc.h,v 1.14 2008/04/11 04:27:18 jsgood Exp $
|
||||
*
|
||||
* Copyright (C) 2004 Thomas Kleffel, All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef MHZ
|
||||
#define MHZ (1000*1000)
|
||||
#endif
|
||||
|
||||
#define s3c_hsmmc_readl(x) readl((host->base)+(x))
|
||||
#define s3c_hsmmc_readw(x) readw((host->base)+(x))
|
||||
#define s3c_hsmmc_readb(x) readb((host->base)+(x))
|
||||
|
||||
#define s3c_hsmmc_writel(v,x) writel((v),(host->base)+(x))
|
||||
#define s3c_hsmmc_writew(v,x) writew((v),(host->base)+(x))
|
||||
#define s3c_hsmmc_writeb(v,x) writeb((v),(host->base)+(x))
|
||||
|
||||
#define S3C_HSMMC_CLOCK_ON 1
|
||||
#define S3C_HSMMC_CLOCK_OFF 0
|
||||
|
||||
#ifndef CONFIG_S3C_HSMMC_MAX_HW_SEGS
|
||||
#define CONFIG_S3C_HSMMC_MAX_HW_SEGS 32
|
||||
#endif
|
||||
|
||||
|
||||
/* For SDMA */
|
||||
struct s3c_hsmmc_dma_blk {
|
||||
dma_addr_t dma_address; /* dma address */
|
||||
uint length; /* length */
|
||||
uint boundary; /* Host DMA Buffer Boundary */
|
||||
void *original;
|
||||
};
|
||||
|
||||
/* For ADMA2 */
|
||||
struct s3c_hsmmc_adma_descr {
|
||||
volatile u32 length_attr; /* length + attribute */
|
||||
volatile u32 dma_address; /* dma address */
|
||||
} __packed;
|
||||
|
||||
|
||||
struct s3c_hsmmc_host {
|
||||
void __iomem *base;
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
struct mmc_host *mmc;
|
||||
struct clk *clk[3];
|
||||
struct resource *mem;
|
||||
|
||||
struct timer_list timer;
|
||||
|
||||
struct s3c_hsmmc_cfg *plat_data;
|
||||
|
||||
int irq;
|
||||
int irq_cd;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
struct tasklet_struct card_tasklet; /* Tasklet structures */
|
||||
struct tasklet_struct finish_tasklet;
|
||||
|
||||
unsigned int clock; /* Current clock (MHz) */
|
||||
|
||||
#define S3C_HSMMC_USE_DMA (1<<0)
|
||||
int flags; /* Host attributes */
|
||||
|
||||
uint dma_dir;
|
||||
|
||||
#ifdef CONFIG_HSMMC_SCATTERGATHER
|
||||
uint sg_len; /* size of scatter list */
|
||||
|
||||
/* For SDMA */
|
||||
uint dma_blk; /* total dmablk number */
|
||||
uint next_blk; /* next block to send */
|
||||
struct s3c_hsmmc_dma_blk dblk[CONFIG_S3C_HSMMC_MAX_HW_SEGS*4];
|
||||
|
||||
/* when pseudo algo cannot deal with sglist */
|
||||
#define S3C_HSMMC_MALLOC_SIZE PAGE_SIZE
|
||||
#define S3C_HSMMC_MAX_SUB_BUF CONFIG_S3C_HSMMC_MAX_HW_SEGS
|
||||
void *sub_block[S3C_HSMMC_MAX_SUB_BUF];
|
||||
|
||||
/* For ADMA2 */
|
||||
struct s3c_hsmmc_adma_descr sdma_descr_tbl[CONFIG_S3C_HSMMC_MAX_HW_SEGS];
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
1667
drivers/mmc/host/sdhci.c
Normal file
1667
drivers/mmc/host/sdhci.c
Normal file
File diff suppressed because it is too large
Load Diff
213
drivers/mmc/host/sdhci.h
Normal file
213
drivers/mmc/host/sdhci.h
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* linux/drivers/mmc/host/sdhci.h - Secure Digital Host Controller Interface driver
|
||||
*
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PCI registers
|
||||
*/
|
||||
|
||||
#define PCI_SDHCI_IFPIO 0x00
|
||||
#define PCI_SDHCI_IFDMA 0x01
|
||||
#define PCI_SDHCI_IFVENDOR 0x02
|
||||
|
||||
#define PCI_SLOT_INFO 0x40 /* 8 bits */
|
||||
#define PCI_SLOT_INFO_SLOTS(x) ((x >> 4) & 7)
|
||||
#define PCI_SLOT_INFO_FIRST_BAR_MASK 0x07
|
||||
|
||||
/*
|
||||
* Controller registers
|
||||
*/
|
||||
|
||||
#define SDHCI_DMA_ADDRESS 0x00
|
||||
|
||||
#define SDHCI_BLOCK_SIZE 0x04
|
||||
#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
|
||||
|
||||
#define SDHCI_BLOCK_COUNT 0x06
|
||||
|
||||
#define SDHCI_ARGUMENT 0x08
|
||||
|
||||
#define SDHCI_TRANSFER_MODE 0x0C
|
||||
#define SDHCI_TRNS_DMA 0x01
|
||||
#define SDHCI_TRNS_BLK_CNT_EN 0x02
|
||||
#define SDHCI_TRNS_ACMD12 0x04
|
||||
#define SDHCI_TRNS_READ 0x10
|
||||
#define SDHCI_TRNS_MULTI 0x20
|
||||
|
||||
#define SDHCI_COMMAND 0x0E
|
||||
#define SDHCI_CMD_RESP_MASK 0x03
|
||||
#define SDHCI_CMD_CRC 0x08
|
||||
#define SDHCI_CMD_INDEX 0x10
|
||||
#define SDHCI_CMD_DATA 0x20
|
||||
|
||||
#define SDHCI_CMD_RESP_NONE 0x00
|
||||
#define SDHCI_CMD_RESP_LONG 0x01
|
||||
#define SDHCI_CMD_RESP_SHORT 0x02
|
||||
#define SDHCI_CMD_RESP_SHORT_BUSY 0x03
|
||||
|
||||
#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff))
|
||||
|
||||
#define SDHCI_RESPONSE 0x10
|
||||
|
||||
#define SDHCI_BUFFER 0x20
|
||||
|
||||
#define SDHCI_PRESENT_STATE 0x24
|
||||
#define SDHCI_CMD_INHIBIT 0x00000001
|
||||
#define SDHCI_DATA_INHIBIT 0x00000002
|
||||
#define SDHCI_DOING_WRITE 0x00000100
|
||||
#define SDHCI_DOING_READ 0x00000200
|
||||
#define SDHCI_SPACE_AVAILABLE 0x00000400
|
||||
#define SDHCI_DATA_AVAILABLE 0x00000800
|
||||
#define SDHCI_CARD_PRESENT 0x00010000
|
||||
#define SDHCI_WRITE_PROTECT 0x00080000
|
||||
|
||||
#define SDHCI_HOST_CONTROL 0x28
|
||||
#define SDHCI_CTRL_LED 0x01
|
||||
#define SDHCI_CTRL_4BITBUS 0x02
|
||||
#define SDHCI_CTRL_HISPD 0x04
|
||||
|
||||
#define SDHCI_POWER_CONTROL 0x29
|
||||
#define SDHCI_POWER_ON 0x01
|
||||
#define SDHCI_POWER_180 0x0A
|
||||
#define SDHCI_POWER_300 0x0C
|
||||
#define SDHCI_POWER_330 0x0E
|
||||
|
||||
#define SDHCI_BLOCK_GAP_CONTROL 0x2A
|
||||
|
||||
#define SDHCI_WAKE_UP_CONTROL 0x2B
|
||||
|
||||
#define SDHCI_CLOCK_CONTROL 0x2C
|
||||
#define SDHCI_DIVIDER_SHIFT 8
|
||||
#define SDHCI_CLOCK_CARD_EN 0x0004
|
||||
#define SDHCI_CLOCK_INT_STABLE 0x0002
|
||||
#define SDHCI_CLOCK_INT_EN 0x0001
|
||||
|
||||
#define SDHCI_TIMEOUT_CONTROL 0x2E
|
||||
|
||||
#define SDHCI_SOFTWARE_RESET 0x2F
|
||||
#define SDHCI_RESET_ALL 0x01
|
||||
#define SDHCI_RESET_CMD 0x02
|
||||
#define SDHCI_RESET_DATA 0x04
|
||||
|
||||
#define SDHCI_INT_STATUS 0x30
|
||||
#define SDHCI_INT_ENABLE 0x34
|
||||
#define SDHCI_SIGNAL_ENABLE 0x38
|
||||
#define SDHCI_INT_RESPONSE 0x00000001
|
||||
#define SDHCI_INT_DATA_END 0x00000002
|
||||
#define SDHCI_INT_DMA_END 0x00000008
|
||||
#define SDHCI_INT_SPACE_AVAIL 0x00000010
|
||||
#define SDHCI_INT_DATA_AVAIL 0x00000020
|
||||
#define SDHCI_INT_CARD_INSERT 0x00000040
|
||||
#define SDHCI_INT_CARD_REMOVE 0x00000080
|
||||
#define SDHCI_INT_CARD_INT 0x00000100
|
||||
#define SDHCI_INT_ERROR 0x00008000
|
||||
#define SDHCI_INT_TIMEOUT 0x00010000
|
||||
#define SDHCI_INT_CRC 0x00020000
|
||||
#define SDHCI_INT_END_BIT 0x00040000
|
||||
#define SDHCI_INT_INDEX 0x00080000
|
||||
#define SDHCI_INT_DATA_TIMEOUT 0x00100000
|
||||
#define SDHCI_INT_DATA_CRC 0x00200000
|
||||
#define SDHCI_INT_DATA_END_BIT 0x00400000
|
||||
#define SDHCI_INT_BUS_POWER 0x00800000
|
||||
#define SDHCI_INT_ACMD12ERR 0x01000000
|
||||
|
||||
#define SDHCI_INT_NORMAL_MASK 0x00007FFF
|
||||
#define SDHCI_INT_ERROR_MASK 0xFFFF8000
|
||||
|
||||
#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \
|
||||
SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX)
|
||||
#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \
|
||||
SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \
|
||||
SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \
|
||||
SDHCI_INT_DATA_END_BIT)
|
||||
|
||||
#define SDHCI_ACMD12_ERR 0x3C
|
||||
|
||||
/* 3E-3F reserved */
|
||||
|
||||
#define SDHCI_CAPABILITIES 0x40
|
||||
#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
|
||||
#define SDHCI_TIMEOUT_CLK_SHIFT 0
|
||||
#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080
|
||||
#define SDHCI_CLOCK_BASE_MASK 0x00003F00
|
||||
#define SDHCI_CLOCK_BASE_SHIFT 8
|
||||
#define SDHCI_MAX_BLOCK_MASK 0x00030000
|
||||
#define SDHCI_MAX_BLOCK_SHIFT 16
|
||||
#define SDHCI_CAN_DO_HISPD 0x00200000
|
||||
#define SDHCI_CAN_DO_DMA 0x00400000
|
||||
#define SDHCI_CAN_VDD_330 0x01000000
|
||||
#define SDHCI_CAN_VDD_300 0x02000000
|
||||
#define SDHCI_CAN_VDD_180 0x04000000
|
||||
|
||||
/* 44-47 reserved for more caps */
|
||||
|
||||
#define SDHCI_MAX_CURRENT 0x48
|
||||
|
||||
/* 4C-4F reserved for more max current */
|
||||
|
||||
/* 50-FB reserved */
|
||||
|
||||
#define SDHCI_SLOT_INT_STATUS 0xFC
|
||||
|
||||
#define SDHCI_HOST_VERSION 0xFE
|
||||
#define SDHCI_VENDOR_VER_MASK 0xFF00
|
||||
#define SDHCI_VENDOR_VER_SHIFT 8
|
||||
#define SDHCI_SPEC_VER_MASK 0x00FF
|
||||
#define SDHCI_SPEC_VER_SHIFT 0
|
||||
|
||||
struct sdhci_chip;
|
||||
|
||||
struct sdhci_host {
|
||||
struct sdhci_chip *chip;
|
||||
struct mmc_host *mmc; /* MMC structure */
|
||||
|
||||
spinlock_t lock; /* Mutex */
|
||||
|
||||
int flags; /* Host attributes */
|
||||
#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */
|
||||
#define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */
|
||||
|
||||
unsigned int max_clk; /* Max possible freq (MHz) */
|
||||
unsigned int timeout_clk; /* Timeout freq (KHz) */
|
||||
|
||||
unsigned int clock; /* Current clock (MHz) */
|
||||
unsigned short power; /* Current voltage */
|
||||
|
||||
struct mmc_request *mrq; /* Current request */
|
||||
struct mmc_command *cmd; /* Current command */
|
||||
struct mmc_data *data; /* Current data request */
|
||||
int data_early:1; /* Data finished before cmd */
|
||||
|
||||
struct scatterlist *cur_sg; /* We're working on this */
|
||||
int num_sg; /* Entries left */
|
||||
int offset; /* Offset into current sg */
|
||||
int remain; /* Bytes left in current */
|
||||
|
||||
char slot_descr[20]; /* Name for reservations */
|
||||
|
||||
int irq; /* Device IRQ */
|
||||
int bar; /* PCI BAR index */
|
||||
unsigned long addr; /* Bus address */
|
||||
void __iomem * ioaddr; /* Mapped address */
|
||||
|
||||
struct tasklet_struct card_tasklet; /* Tasklet structures */
|
||||
struct tasklet_struct finish_tasklet;
|
||||
|
||||
struct timer_list timer; /* Timer for timeouts */
|
||||
};
|
||||
|
||||
struct sdhci_chip {
|
||||
struct pci_dev *pdev;
|
||||
|
||||
unsigned long quirks;
|
||||
|
||||
int num_slots; /* Slots on controller */
|
||||
struct sdhci_host *hosts[0]; /* Pointers to hosts */
|
||||
};
|
||||
1095
drivers/mmc/host/tifm_sd.c
Normal file
1095
drivers/mmc/host/tifm_sd.c
Normal file
File diff suppressed because it is too large
Load Diff
2047
drivers/mmc/host/wbsd.c
Normal file
2047
drivers/mmc/host/wbsd.c
Normal file
File diff suppressed because it is too large
Load Diff
185
drivers/mmc/host/wbsd.h
Normal file
185
drivers/mmc/host/wbsd.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* linux/drivers/mmc/host/wbsd.h - Winbond W83L51xD SD/MMC driver
|
||||
*
|
||||
* Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define LOCK_CODE 0xAA
|
||||
|
||||
#define WBSD_CONF_SWRST 0x02
|
||||
#define WBSD_CONF_DEVICE 0x07
|
||||
#define WBSD_CONF_ID_HI 0x20
|
||||
#define WBSD_CONF_ID_LO 0x21
|
||||
#define WBSD_CONF_POWER 0x22
|
||||
#define WBSD_CONF_PME 0x23
|
||||
#define WBSD_CONF_PMES 0x24
|
||||
|
||||
#define WBSD_CONF_ENABLE 0x30
|
||||
#define WBSD_CONF_PORT_HI 0x60
|
||||
#define WBSD_CONF_PORT_LO 0x61
|
||||
#define WBSD_CONF_IRQ 0x70
|
||||
#define WBSD_CONF_DRQ 0x74
|
||||
|
||||
#define WBSD_CONF_PINS 0xF0
|
||||
|
||||
#define DEVICE_SD 0x03
|
||||
|
||||
#define WBSD_PINS_DAT3_HI 0x20
|
||||
#define WBSD_PINS_DAT3_OUT 0x10
|
||||
#define WBSD_PINS_GP11_HI 0x04
|
||||
#define WBSD_PINS_DETECT_GP11 0x02
|
||||
#define WBSD_PINS_DETECT_DAT3 0x01
|
||||
|
||||
#define WBSD_CMDR 0x00
|
||||
#define WBSD_DFR 0x01
|
||||
#define WBSD_EIR 0x02
|
||||
#define WBSD_ISR 0x03
|
||||
#define WBSD_FSR 0x04
|
||||
#define WBSD_IDXR 0x05
|
||||
#define WBSD_DATAR 0x06
|
||||
#define WBSD_CSR 0x07
|
||||
|
||||
#define WBSD_EINT_CARD 0x40
|
||||
#define WBSD_EINT_FIFO_THRE 0x20
|
||||
#define WBSD_EINT_CRC 0x10
|
||||
#define WBSD_EINT_TIMEOUT 0x08
|
||||
#define WBSD_EINT_PROGEND 0x04
|
||||
#define WBSD_EINT_BUSYEND 0x02
|
||||
#define WBSD_EINT_TC 0x01
|
||||
|
||||
#define WBSD_INT_PENDING 0x80
|
||||
#define WBSD_INT_CARD 0x40
|
||||
#define WBSD_INT_FIFO_THRE 0x20
|
||||
#define WBSD_INT_CRC 0x10
|
||||
#define WBSD_INT_TIMEOUT 0x08
|
||||
#define WBSD_INT_PROGEND 0x04
|
||||
#define WBSD_INT_BUSYEND 0x02
|
||||
#define WBSD_INT_TC 0x01
|
||||
|
||||
#define WBSD_FIFO_EMPTY 0x80
|
||||
#define WBSD_FIFO_FULL 0x40
|
||||
#define WBSD_FIFO_EMTHRE 0x20
|
||||
#define WBSD_FIFO_FUTHRE 0x10
|
||||
#define WBSD_FIFO_SZMASK 0x0F
|
||||
|
||||
#define WBSD_MSLED 0x20
|
||||
#define WBSD_POWER_N 0x10
|
||||
#define WBSD_WRPT 0x04
|
||||
#define WBSD_CARDPRESENT 0x01
|
||||
|
||||
#define WBSD_IDX_CLK 0x01
|
||||
#define WBSD_IDX_PBSMSB 0x02
|
||||
#define WBSD_IDX_TAAC 0x03
|
||||
#define WBSD_IDX_NSAC 0x04
|
||||
#define WBSD_IDX_PBSLSB 0x05
|
||||
#define WBSD_IDX_SETUP 0x06
|
||||
#define WBSD_IDX_DMA 0x07
|
||||
#define WBSD_IDX_FIFOEN 0x08
|
||||
#define WBSD_IDX_STATUS 0x10
|
||||
#define WBSD_IDX_RSPLEN 0x1E
|
||||
#define WBSD_IDX_RESP0 0x1F
|
||||
#define WBSD_IDX_RESP1 0x20
|
||||
#define WBSD_IDX_RESP2 0x21
|
||||
#define WBSD_IDX_RESP3 0x22
|
||||
#define WBSD_IDX_RESP4 0x23
|
||||
#define WBSD_IDX_RESP5 0x24
|
||||
#define WBSD_IDX_RESP6 0x25
|
||||
#define WBSD_IDX_RESP7 0x26
|
||||
#define WBSD_IDX_RESP8 0x27
|
||||
#define WBSD_IDX_RESP9 0x28
|
||||
#define WBSD_IDX_RESP10 0x29
|
||||
#define WBSD_IDX_RESP11 0x2A
|
||||
#define WBSD_IDX_RESP12 0x2B
|
||||
#define WBSD_IDX_RESP13 0x2C
|
||||
#define WBSD_IDX_RESP14 0x2D
|
||||
#define WBSD_IDX_RESP15 0x2E
|
||||
#define WBSD_IDX_RESP16 0x2F
|
||||
#define WBSD_IDX_CRCSTATUS 0x30
|
||||
#define WBSD_IDX_ISR 0x3F
|
||||
|
||||
#define WBSD_CLK_375K 0x00
|
||||
#define WBSD_CLK_12M 0x01
|
||||
#define WBSD_CLK_16M 0x02
|
||||
#define WBSD_CLK_24M 0x03
|
||||
|
||||
#define WBSD_DATA_WIDTH 0x01
|
||||
|
||||
#define WBSD_DAT3_H 0x08
|
||||
#define WBSD_FIFO_RESET 0x04
|
||||
#define WBSD_SOFT_RESET 0x02
|
||||
#define WBSD_INC_INDEX 0x01
|
||||
|
||||
#define WBSD_DMA_SINGLE 0x02
|
||||
#define WBSD_DMA_ENABLE 0x01
|
||||
|
||||
#define WBSD_FIFOEN_EMPTY 0x20
|
||||
#define WBSD_FIFOEN_FULL 0x10
|
||||
#define WBSD_FIFO_THREMASK 0x0F
|
||||
|
||||
#define WBSD_BLOCK_READ 0x80
|
||||
#define WBSD_BLOCK_WRITE 0x40
|
||||
#define WBSD_BUSY 0x20
|
||||
#define WBSD_CARDTRAFFIC 0x04
|
||||
#define WBSD_SENDCMD 0x02
|
||||
#define WBSD_RECVRES 0x01
|
||||
|
||||
#define WBSD_RSP_SHORT 0x00
|
||||
#define WBSD_RSP_LONG 0x01
|
||||
|
||||
#define WBSD_CRC_MASK 0x1F
|
||||
#define WBSD_CRC_OK 0x05 /* S010E (00101) */
|
||||
#define WBSD_CRC_FAIL 0x0B /* S101E (01011) */
|
||||
|
||||
#define WBSD_DMA_SIZE 65536
|
||||
|
||||
struct wbsd_host
|
||||
{
|
||||
struct mmc_host* mmc; /* MMC structure */
|
||||
|
||||
spinlock_t lock; /* Mutex */
|
||||
|
||||
int flags; /* Driver states */
|
||||
|
||||
#define WBSD_FCARD_PRESENT (1<<0) /* Card is present */
|
||||
#define WBSD_FIGNORE_DETECT (1<<1) /* Ignore card detection */
|
||||
|
||||
struct mmc_request* mrq; /* Current request */
|
||||
|
||||
u8 isr; /* Accumulated ISR */
|
||||
|
||||
struct scatterlist* cur_sg; /* Current SG entry */
|
||||
unsigned int num_sg; /* Number of entries left */
|
||||
|
||||
unsigned int offset; /* Offset into current entry */
|
||||
unsigned int remain; /* Data left in curren entry */
|
||||
|
||||
char* dma_buffer; /* ISA DMA buffer */
|
||||
dma_addr_t dma_addr; /* Physical address for same */
|
||||
|
||||
int firsterr; /* See fifo functions */
|
||||
|
||||
u8 clk; /* Current clock speed */
|
||||
unsigned char bus_width; /* Current bus width */
|
||||
|
||||
int config; /* Config port */
|
||||
u8 unlock_code; /* Code to unlock config */
|
||||
|
||||
int chip_id; /* ID of controller */
|
||||
|
||||
int base; /* I/O port base */
|
||||
int irq; /* Interrupt */
|
||||
int dma; /* DMA channel */
|
||||
|
||||
struct tasklet_struct card_tasklet; /* Tasklet structures */
|
||||
struct tasklet_struct fifo_tasklet;
|
||||
struct tasklet_struct crc_tasklet;
|
||||
struct tasklet_struct timeout_tasklet;
|
||||
struct tasklet_struct finish_tasklet;
|
||||
|
||||
struct timer_list ignore_timer; /* Ignore detection timer */
|
||||
};
|
||||
Reference in New Issue
Block a user