mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 06:31:52 +00:00
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc: (68 commits) sdio_uart: Fix SDIO break control to now return success or an error mmc: host driver for Ricoh Bay1Controllers sdio: sdio_io.c Fix sparse warnings sdio: fix the use of hard coded timeout value. mmc: OLPC: update vdd/powerup quirk comment mmc: fix spares errors of sdhci.c mmc: remove multiwrite capability wbsd: fix bad dma_addr_t conversion atmel-mci: Driver for Atmel on-chip MMC controllers mmc: fix sdio_io sparse errors mmc: wbsd.c fix shadowing of 'dma' variable MMC: S3C24XX: Refuse incorrectly aligned transfers MMC: S3C24XX: Add maintainer entry MMC: S3C24XX: Update error debugging. MMC: S3C24XX: Add media presence test to request handling. MMC: S3C24XX: Fix use of msecs where jiffies are needed MMC: S3C24XX: Add MODULE_ALIAS() entries for the platform devices MMC: S3C24XX: Fix s3c2410_dma_request() return code check. MMC: S3C24XX: Allow card-detect on non-IRQ capable pin MMC: S3C24XX: Ensure host->mrq->data is valid ... Manually fixed up bogus executable bits on drivers/mmc/core/sdio_io.c and include/linux/mmc/sdio_func.h when merging.
This commit is contained in:
commit
8a0ca91e1d
17
MAINTAINERS
17
MAINTAINERS
@ -348,7 +348,9 @@ W: http://www.linux-usb.org/SpeedTouch/
|
||||
S: Maintained
|
||||
|
||||
ALCHEMY AU1XX0 MMC DRIVER
|
||||
S: Orphan
|
||||
P: Manuel Lauss
|
||||
M: manuel.lauss@gmail.com
|
||||
S: Maintained
|
||||
|
||||
ALI1563 I2C DRIVER
|
||||
P: Rudolf Marek
|
||||
@ -3559,6 +3561,13 @@ L: linux-s390@vger.kernel.org
|
||||
W: http://www.ibm.com/developerworks/linux/linux390/
|
||||
S: Supported
|
||||
|
||||
S3C24XX SD/MMC Driver
|
||||
P: Ben Dooks
|
||||
M: ben-linux@fluff.org
|
||||
L: linux-arm-kernel@lists.arm.linux.org.uk (subscribers-only)
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Supported
|
||||
|
||||
SAA7146 VIDEO4LINUX-2 DRIVER
|
||||
P: Michael Hunold
|
||||
M: michael@mihu.de
|
||||
@ -3631,6 +3640,12 @@ P: Jim Cromie
|
||||
M: jim.cromie@gmail.com
|
||||
S: Maintained
|
||||
|
||||
SDRICOH_CS MMC/SD HOST CONTROLLER INTERFACE DRIVER
|
||||
P: Sascha Sommer
|
||||
M: saschasommer@freenet.de
|
||||
L: sdricohcs-devel@lists.sourceforge.net (subscribers-only)
|
||||
S: Maintained
|
||||
|
||||
SECURITY CONTACT
|
||||
P: Security Officers
|
||||
M: security@kernel.org
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/leds.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <asm/atmel-mci.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/setup.h>
|
||||
|
||||
@ -51,6 +52,11 @@ static struct spi_board_info spi0_board_info[] __initdata = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct mci_platform_data __initdata mci0_data = {
|
||||
.detect_pin = GPIO_PIN_PC(25),
|
||||
.wp_pin = GPIO_PIN_PE(0),
|
||||
};
|
||||
|
||||
/*
|
||||
* The next two functions should go away as the boot loader is
|
||||
* supposed to initialize the macb address registers with a valid
|
||||
@ -170,6 +176,7 @@ static int __init atngw100_init(void)
|
||||
set_hw_addr(at32_add_device_eth(1, ð_data[1]));
|
||||
|
||||
at32_add_device_spi(0, spi0_board_info, ARRAY_SIZE(spi0_board_info));
|
||||
at32_add_device_mci(0, &mci0_data);
|
||||
at32_add_device_usba(0, NULL);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ngw_leds); i++) {
|
||||
|
@ -234,6 +234,9 @@ static int __init atstk1002_init(void)
|
||||
#ifdef CONFIG_BOARD_ATSTK100X_SPI1
|
||||
at32_add_device_spi(1, spi1_board_info, ARRAY_SIZE(spi1_board_info));
|
||||
#endif
|
||||
#ifndef CONFIG_BOARD_ATSTK1002_SW2_CUSTOM
|
||||
at32_add_device_mci(0, NULL);
|
||||
#endif
|
||||
#ifdef CONFIG_BOARD_ATSTK1002_SW5_CUSTOM
|
||||
set_hw_addr(at32_add_device_eth(1, ð_data[1]));
|
||||
#else
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/usb/atmel_usba_udc.h>
|
||||
|
||||
#include <asm/atmel-mci.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
|
||||
@ -1278,20 +1279,32 @@ static struct clk atmel_mci0_pclk = {
|
||||
.index = 9,
|
||||
};
|
||||
|
||||
struct platform_device *__init at32_add_device_mci(unsigned int id)
|
||||
struct platform_device *__init
|
||||
at32_add_device_mci(unsigned int id, struct mci_platform_data *data)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct mci_platform_data _data;
|
||||
struct platform_device *pdev;
|
||||
struct dw_dma_slave *dws;
|
||||
|
||||
if (id != 0)
|
||||
return NULL;
|
||||
|
||||
pdev = platform_device_alloc("atmel_mci", id);
|
||||
if (!pdev)
|
||||
return NULL;
|
||||
goto fail;
|
||||
|
||||
if (platform_device_add_resources(pdev, atmel_mci0_resource,
|
||||
ARRAY_SIZE(atmel_mci0_resource)))
|
||||
goto err_add_resources;
|
||||
goto fail;
|
||||
|
||||
if (!data) {
|
||||
data = &_data;
|
||||
memset(data, 0, sizeof(struct mci_platform_data));
|
||||
}
|
||||
|
||||
if (platform_device_add_data(pdev, data,
|
||||
sizeof(struct mci_platform_data)))
|
||||
goto fail;
|
||||
|
||||
select_peripheral(PA(10), PERIPH_A, 0); /* CLK */
|
||||
select_peripheral(PA(11), PERIPH_A, 0); /* CMD */
|
||||
@ -1300,12 +1313,19 @@ struct platform_device *__init at32_add_device_mci(unsigned int id)
|
||||
select_peripheral(PA(14), PERIPH_A, 0); /* DATA2 */
|
||||
select_peripheral(PA(15), PERIPH_A, 0); /* DATA3 */
|
||||
|
||||
if (data) {
|
||||
if (data->detect_pin != GPIO_PIN_NONE)
|
||||
at32_select_gpio(data->detect_pin, 0);
|
||||
if (data->wp_pin != GPIO_PIN_NONE)
|
||||
at32_select_gpio(data->wp_pin, 0);
|
||||
}
|
||||
|
||||
atmel_mci0_pclk.dev = &pdev->dev;
|
||||
|
||||
platform_device_add(pdev);
|
||||
return pdev;
|
||||
|
||||
err_add_resources:
|
||||
fail:
|
||||
platform_device_put(pdev);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Block driver for media (i.e., flash cards)
|
||||
*
|
||||
* Copyright 2002 Hewlett-Packard Company
|
||||
* Copyright 2005-2007 Pierre Ossman
|
||||
* Copyright 2005-2008 Pierre Ossman
|
||||
*
|
||||
* Use consistent with the GNU GPL is permitted,
|
||||
* provided that this copyright notice is
|
||||
@ -237,17 +237,6 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
||||
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) {
|
||||
/* SPI multiblock writes terminate using a special
|
||||
* token, not a STOP_TRANSMISSION request.
|
||||
@ -296,22 +285,24 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
||||
|
||||
mmc_queue_bounce_post(mq);
|
||||
|
||||
/*
|
||||
* Check for errors here, but don't jump to cmd_err
|
||||
* until later as we need to wait for the card to leave
|
||||
* programming mode even when things go wrong.
|
||||
*/
|
||||
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) {
|
||||
@ -344,6 +335,9 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
||||
#endif
|
||||
}
|
||||
|
||||
if (brq.cmd.error || brq.data.error || brq.stop.error)
|
||||
goto cmd_err;
|
||||
|
||||
/*
|
||||
* A block was successfully transferred.
|
||||
*/
|
||||
@ -362,30 +356,32 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
||||
* 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.
|
||||
* as reported by the controller (which might be less than
|
||||
* the real number of written sectors, but never more).
|
||||
*
|
||||
* 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;
|
||||
if (rq_data_dir(req) != READ) {
|
||||
if (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;
|
||||
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 = __blk_end_request(req, 0, bytes);
|
||||
spin_unlock_irq(&md->lock);
|
||||
}
|
||||
} else {
|
||||
spin_lock_irq(&md->lock);
|
||||
ret = __blk_end_request(req, 0, bytes);
|
||||
ret = __blk_end_request(req, 0, brq.data.bytes_xfered);
|
||||
spin_unlock_irq(&md->lock);
|
||||
}
|
||||
} else if (rq_data_dir(req) != READ &&
|
||||
(card->host->caps & MMC_CAP_MULTIWRITE)) {
|
||||
spin_lock_irq(&md->lock);
|
||||
ret = __blk_end_request(req, 0, brq.data.bytes_xfered);
|
||||
spin_unlock_irq(&md->lock);
|
||||
}
|
||||
|
||||
mmc_release_host(card->host);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* linux/drivers/mmc/card/mmc_test.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
* Copyright 2007-2008 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
|
||||
@ -26,13 +26,17 @@
|
||||
struct mmc_test_card {
|
||||
struct mmc_card *card;
|
||||
|
||||
u8 scratch[BUFFER_SIZE];
|
||||
u8 *buffer;
|
||||
};
|
||||
|
||||
/*******************************************************************/
|
||||
/* Helper functions */
|
||||
/* General helper functions */
|
||||
/*******************************************************************/
|
||||
|
||||
/*
|
||||
* Configure correct block size in card
|
||||
*/
|
||||
static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
@ -48,117 +52,61 @@ static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __mmc_test_transfer(struct mmc_test_card *test, int write,
|
||||
unsigned broken_xfer, u8 *buffer, unsigned addr,
|
||||
unsigned blocks, unsigned blksz)
|
||||
/*
|
||||
* Fill in the mmc_request structure given a set of transfer parameters.
|
||||
*/
|
||||
static void mmc_test_prepare_mrq(struct mmc_test_card *test,
|
||||
struct mmc_request *mrq, struct scatterlist *sg, unsigned sg_len,
|
||||
unsigned dev_addr, unsigned blocks, unsigned blksz, int write)
|
||||
{
|
||||
BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
|
||||
|
||||
if (blocks > 1) {
|
||||
mrq->cmd->opcode = write ?
|
||||
MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
|
||||
} else {
|
||||
mrq->cmd->opcode = write ?
|
||||
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
|
||||
}
|
||||
|
||||
mrq->cmd->arg = dev_addr;
|
||||
mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
if (blocks == 1)
|
||||
mrq->stop = NULL;
|
||||
else {
|
||||
mrq->stop->opcode = MMC_STOP_TRANSMISSION;
|
||||
mrq->stop->arg = 0;
|
||||
mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
|
||||
}
|
||||
|
||||
mrq->data->blksz = blksz;
|
||||
mrq->data->blocks = blocks;
|
||||
mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
|
||||
mrq->data->sg = sg;
|
||||
mrq->data->sg_len = sg_len;
|
||||
|
||||
mmc_set_data_timeout(mrq->data, test->card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for the card to finish the busy state
|
||||
*/
|
||||
static int mmc_test_wait_busy(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, busy;
|
||||
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_command stop;
|
||||
struct mmc_data data;
|
||||
|
||||
struct scatterlist sg;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
if (broken_xfer) {
|
||||
if (blocks > 1) {
|
||||
cmd.opcode = write ?
|
||||
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
|
||||
} else {
|
||||
cmd.opcode = MMC_SEND_STATUS;
|
||||
}
|
||||
} else {
|
||||
if (blocks > 1) {
|
||||
cmd.opcode = write ?
|
||||
MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
|
||||
} else {
|
||||
cmd.opcode = write ?
|
||||
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (broken_xfer && blocks == 1)
|
||||
cmd.arg = test->card->rca << 16;
|
||||
else
|
||||
cmd.arg = addr;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
memset(&stop, 0, sizeof(struct mmc_command));
|
||||
|
||||
if (!broken_xfer && (blocks > 1)) {
|
||||
stop.opcode = MMC_STOP_TRANSMISSION;
|
||||
stop.arg = 0;
|
||||
stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
|
||||
|
||||
mrq.stop = &stop;
|
||||
}
|
||||
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
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, buffer, blocks * blksz);
|
||||
|
||||
mmc_set_data_timeout(&data, test->card);
|
||||
|
||||
mmc_wait_for_req(test->card->host, &mrq);
|
||||
|
||||
ret = 0;
|
||||
|
||||
if (broken_xfer) {
|
||||
if (!ret && cmd.error)
|
||||
ret = cmd.error;
|
||||
if (!ret && data.error == 0)
|
||||
ret = RESULT_FAIL;
|
||||
if (!ret && data.error != -ETIMEDOUT)
|
||||
ret = data.error;
|
||||
if (!ret && stop.error)
|
||||
ret = stop.error;
|
||||
if (blocks > 1) {
|
||||
if (!ret && data.bytes_xfered > blksz)
|
||||
ret = RESULT_FAIL;
|
||||
} else {
|
||||
if (!ret && data.bytes_xfered > 0)
|
||||
ret = RESULT_FAIL;
|
||||
}
|
||||
} else {
|
||||
if (!ret && cmd.error)
|
||||
ret = cmd.error;
|
||||
if (!ret && data.error)
|
||||
ret = data.error;
|
||||
if (!ret && stop.error)
|
||||
ret = stop.error;
|
||||
if (!ret && data.bytes_xfered != blocks * blksz)
|
||||
ret = RESULT_FAIL;
|
||||
}
|
||||
|
||||
if (ret == -EINVAL)
|
||||
ret = RESULT_UNSUP_HOST;
|
||||
|
||||
busy = 0;
|
||||
do {
|
||||
int ret2;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SEND_STATUS;
|
||||
cmd.arg = test->card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
ret2 = mmc_wait_for_cmd(test->card->host, &cmd, 0);
|
||||
if (ret2)
|
||||
ret = mmc_wait_for_cmd(test->card->host, &cmd, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) {
|
||||
@ -172,14 +120,57 @@ static int __mmc_test_transfer(struct mmc_test_card *test, int write,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_test_transfer(struct mmc_test_card *test, int write,
|
||||
u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz)
|
||||
/*
|
||||
* Transfer a single sector of kernel addressable data
|
||||
*/
|
||||
static int mmc_test_buffer_transfer(struct mmc_test_card *test,
|
||||
u8 *buffer, unsigned addr, unsigned blksz, int write)
|
||||
{
|
||||
return __mmc_test_transfer(test, write, 0, buffer,
|
||||
addr, blocks, blksz);
|
||||
int ret;
|
||||
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_command stop;
|
||||
struct mmc_data data;
|
||||
|
||||
struct scatterlist sg;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
memset(&stop, 0, sizeof(struct mmc_command));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
mrq.stop = &stop;
|
||||
|
||||
sg_init_one(&sg, buffer, blksz);
|
||||
|
||||
mmc_test_prepare_mrq(test, &mrq, &sg, 1, addr, 1, blksz, write);
|
||||
|
||||
mmc_wait_for_req(test->card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
ret = mmc_test_wait_busy(test);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
|
||||
/*******************************************************************/
|
||||
/* Test preparation and cleanup */
|
||||
/*******************************************************************/
|
||||
|
||||
/*
|
||||
* Fill the first couple of sectors of the card with known data
|
||||
* so that bad reads/writes can be detected
|
||||
*/
|
||||
static int __mmc_test_prepare(struct mmc_test_card *test, int write)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
@ -188,15 +179,14 @@ static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
|
||||
return ret;
|
||||
|
||||
if (write)
|
||||
memset(test->buffer, 0xDF, BUFFER_SIZE);
|
||||
memset(test->buffer, 0xDF, 512);
|
||||
else {
|
||||
for (i = 0;i < BUFFER_SIZE;i++)
|
||||
for (i = 0;i < 512;i++)
|
||||
test->buffer[i] = i;
|
||||
}
|
||||
|
||||
for (i = 0;i < BUFFER_SIZE / 512;i++) {
|
||||
ret = mmc_test_transfer(test, 1, test->buffer + i * 512,
|
||||
i * 512, 1, 512);
|
||||
ret = mmc_test_buffer_transfer(test, test->buffer, i * 512, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -204,41 +194,218 @@ static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_test_prepare_verify_write(struct mmc_test_card *test)
|
||||
static int mmc_test_prepare_write(struct mmc_test_card *test)
|
||||
{
|
||||
return mmc_test_prepare_verify(test, 1);
|
||||
return __mmc_test_prepare(test, 1);
|
||||
}
|
||||
|
||||
static int mmc_test_prepare_verify_read(struct mmc_test_card *test)
|
||||
static int mmc_test_prepare_read(struct mmc_test_card *test)
|
||||
{
|
||||
return mmc_test_prepare_verify(test, 0);
|
||||
return __mmc_test_prepare(test, 0);
|
||||
}
|
||||
|
||||
static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
|
||||
u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz)
|
||||
static int mmc_test_cleanup(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i, sectors;
|
||||
int ret, i;
|
||||
|
||||
/*
|
||||
* It is assumed that the above preparation has been done.
|
||||
*/
|
||||
ret = mmc_test_set_blksize(test, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(test->buffer, 0, BUFFER_SIZE);
|
||||
memset(test->buffer, 0, 512);
|
||||
|
||||
for (i = 0;i < BUFFER_SIZE / 512;i++) {
|
||||
ret = mmc_test_buffer_transfer(test, test->buffer, i * 512, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
/* Test execution helpers */
|
||||
/*******************************************************************/
|
||||
|
||||
/*
|
||||
* Modifies the mmc_request to perform the "short transfer" tests
|
||||
*/
|
||||
static void mmc_test_prepare_broken_mrq(struct mmc_test_card *test,
|
||||
struct mmc_request *mrq, int write)
|
||||
{
|
||||
BUG_ON(!mrq || !mrq->cmd || !mrq->data);
|
||||
|
||||
if (mrq->data->blocks > 1) {
|
||||
mrq->cmd->opcode = write ?
|
||||
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
|
||||
mrq->stop = NULL;
|
||||
} else {
|
||||
mrq->cmd->opcode = MMC_SEND_STATUS;
|
||||
mrq->cmd->arg = test->card->rca << 16;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that a normal transfer didn't have any errors
|
||||
*/
|
||||
static int mmc_test_check_result(struct mmc_test_card *test,
|
||||
struct mmc_request *mrq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!mrq || !mrq->cmd || !mrq->data);
|
||||
|
||||
ret = 0;
|
||||
|
||||
if (!ret && mrq->cmd->error)
|
||||
ret = mrq->cmd->error;
|
||||
if (!ret && mrq->data->error)
|
||||
ret = mrq->data->error;
|
||||
if (!ret && mrq->stop && mrq->stop->error)
|
||||
ret = mrq->stop->error;
|
||||
if (!ret && mrq->data->bytes_xfered !=
|
||||
mrq->data->blocks * mrq->data->blksz)
|
||||
ret = RESULT_FAIL;
|
||||
|
||||
if (ret == -EINVAL)
|
||||
ret = RESULT_UNSUP_HOST;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that a "short transfer" behaved as expected
|
||||
*/
|
||||
static int mmc_test_check_broken_result(struct mmc_test_card *test,
|
||||
struct mmc_request *mrq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!mrq || !mrq->cmd || !mrq->data);
|
||||
|
||||
ret = 0;
|
||||
|
||||
if (!ret && mrq->cmd->error)
|
||||
ret = mrq->cmd->error;
|
||||
if (!ret && mrq->data->error == 0)
|
||||
ret = RESULT_FAIL;
|
||||
if (!ret && mrq->data->error != -ETIMEDOUT)
|
||||
ret = mrq->data->error;
|
||||
if (!ret && mrq->stop && mrq->stop->error)
|
||||
ret = mrq->stop->error;
|
||||
if (mrq->data->blocks > 1) {
|
||||
if (!ret && mrq->data->bytes_xfered > mrq->data->blksz)
|
||||
ret = RESULT_FAIL;
|
||||
} else {
|
||||
if (!ret && mrq->data->bytes_xfered > 0)
|
||||
ret = RESULT_FAIL;
|
||||
}
|
||||
|
||||
if (ret == -EINVAL)
|
||||
ret = RESULT_UNSUP_HOST;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests a basic transfer with certain parameters
|
||||
*/
|
||||
static int mmc_test_simple_transfer(struct mmc_test_card *test,
|
||||
struct scatterlist *sg, unsigned sg_len, unsigned dev_addr,
|
||||
unsigned blocks, unsigned blksz, int write)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_command stop;
|
||||
struct mmc_data data;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
memset(&stop, 0, sizeof(struct mmc_command));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
mrq.stop = &stop;
|
||||
|
||||
mmc_test_prepare_mrq(test, &mrq, sg, sg_len, dev_addr,
|
||||
blocks, blksz, write);
|
||||
|
||||
mmc_wait_for_req(test->card->host, &mrq);
|
||||
|
||||
mmc_test_wait_busy(test);
|
||||
|
||||
return mmc_test_check_result(test, &mrq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests a transfer where the card will fail completely or partly
|
||||
*/
|
||||
static int mmc_test_broken_transfer(struct mmc_test_card *test,
|
||||
unsigned blocks, unsigned blksz, int write)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_command stop;
|
||||
struct mmc_data data;
|
||||
|
||||
struct scatterlist sg;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
memset(&stop, 0, sizeof(struct mmc_command));
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
mrq.stop = &stop;
|
||||
|
||||
sg_init_one(&sg, test->buffer, blocks * blksz);
|
||||
|
||||
mmc_test_prepare_mrq(test, &mrq, &sg, 1, 0, blocks, blksz, write);
|
||||
mmc_test_prepare_broken_mrq(test, &mrq, write);
|
||||
|
||||
mmc_wait_for_req(test->card->host, &mrq);
|
||||
|
||||
mmc_test_wait_busy(test);
|
||||
|
||||
return mmc_test_check_broken_result(test, &mrq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Does a complete transfer test where data is also validated
|
||||
*
|
||||
* Note: mmc_test_prepare() must have been done before this call
|
||||
*/
|
||||
static int mmc_test_transfer(struct mmc_test_card *test,
|
||||
struct scatterlist *sg, unsigned sg_len, unsigned dev_addr,
|
||||
unsigned blocks, unsigned blksz, int write)
|
||||
{
|
||||
int ret, i;
|
||||
unsigned long flags;
|
||||
|
||||
if (write) {
|
||||
for (i = 0;i < blocks * blksz;i++)
|
||||
buffer[i] = i;
|
||||
test->scratch[i] = i;
|
||||
} else {
|
||||
memset(test->scratch, 0, BUFFER_SIZE);
|
||||
}
|
||||
local_irq_save(flags);
|
||||
sg_copy_from_buffer(sg, sg_len, test->scratch, BUFFER_SIZE);
|
||||
local_irq_restore(flags);
|
||||
|
||||
ret = mmc_test_set_blksize(test, blksz);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mmc_test_transfer(test, write, buffer, addr, blocks, blksz);
|
||||
ret = mmc_test_simple_transfer(test, sg, sg_len, dev_addr,
|
||||
blocks, blksz, write);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (write) {
|
||||
int sectors;
|
||||
|
||||
ret = mmc_test_set_blksize(test, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -253,9 +420,9 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
|
||||
memset(test->buffer, 0, sectors * 512);
|
||||
|
||||
for (i = 0;i < sectors;i++) {
|
||||
ret = mmc_test_transfer(test, 0,
|
||||
ret = mmc_test_buffer_transfer(test,
|
||||
test->buffer + i * 512,
|
||||
addr + i * 512, 1, 512);
|
||||
dev_addr + i * 512, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -270,8 +437,11 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
|
||||
return RESULT_FAIL;
|
||||
}
|
||||
} else {
|
||||
local_irq_save(flags);
|
||||
sg_copy_to_buffer(sg, sg_len, test->scratch, BUFFER_SIZE);
|
||||
local_irq_restore(flags);
|
||||
for (i = 0;i < blocks * blksz;i++) {
|
||||
if (buffer[i] != (u8)i)
|
||||
if (test->scratch[i] != (u8)i)
|
||||
return RESULT_FAIL;
|
||||
}
|
||||
}
|
||||
@ -279,26 +449,6 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_test_cleanup_verify(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
ret = mmc_test_set_blksize(test, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(test->buffer, 0, BUFFER_SIZE);
|
||||
|
||||
for (i = 0;i < BUFFER_SIZE / 512;i++) {
|
||||
ret = mmc_test_transfer(test, 1, test->buffer + i * 512,
|
||||
i * 512, 1, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
/* Tests */
|
||||
/*******************************************************************/
|
||||
@ -314,12 +464,15 @@ struct mmc_test_case {
|
||||
static int mmc_test_basic_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
struct scatterlist sg;
|
||||
|
||||
ret = mmc_test_set_blksize(test, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mmc_test_transfer(test, 1, test->buffer, 0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer, 512);
|
||||
|
||||
ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -329,12 +482,15 @@ static int mmc_test_basic_write(struct mmc_test_card *test)
|
||||
static int mmc_test_basic_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
struct scatterlist sg;
|
||||
|
||||
ret = mmc_test_set_blksize(test, 512);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mmc_test_transfer(test, 0, test->buffer, 0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer, 512);
|
||||
|
||||
ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -344,8 +500,11 @@ static int mmc_test_basic_read(struct mmc_test_card *test)
|
||||
static int mmc_test_verify_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
struct scatterlist sg;
|
||||
|
||||
ret = mmc_test_verified_transfer(test, 1, test->buffer, 0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer, 512);
|
||||
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -355,8 +514,11 @@ static int mmc_test_verify_write(struct mmc_test_card *test)
|
||||
static int mmc_test_verify_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
struct scatterlist sg;
|
||||
|
||||
ret = mmc_test_verified_transfer(test, 0, test->buffer, 0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer, 512);
|
||||
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -367,6 +529,7 @@ static int mmc_test_multi_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
unsigned int size;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (test->card->host->max_blk_count == 1)
|
||||
return RESULT_UNSUP_HOST;
|
||||
@ -379,8 +542,9 @@ static int mmc_test_multi_write(struct mmc_test_card *test)
|
||||
if (size < 1024)
|
||||
return RESULT_UNSUP_HOST;
|
||||
|
||||
ret = mmc_test_verified_transfer(test, 1, test->buffer, 0,
|
||||
size / 512, 512);
|
||||
sg_init_one(&sg, test->buffer, size);
|
||||
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -391,6 +555,7 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret;
|
||||
unsigned int size;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (test->card->host->max_blk_count == 1)
|
||||
return RESULT_UNSUP_HOST;
|
||||
@ -403,8 +568,9 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
|
||||
if (size < 1024)
|
||||
return RESULT_UNSUP_HOST;
|
||||
|
||||
ret = mmc_test_verified_transfer(test, 0, test->buffer, 0,
|
||||
size / 512, 512);
|
||||
sg_init_one(&sg, test->buffer, size);
|
||||
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -414,13 +580,14 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
|
||||
static int mmc_test_pow2_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (!test->card->csd.write_partial)
|
||||
return RESULT_UNSUP_CARD;
|
||||
|
||||
for (i = 1; i < 512;i <<= 1) {
|
||||
ret = mmc_test_verified_transfer(test, 1,
|
||||
test->buffer, 0, 1, i);
|
||||
sg_init_one(&sg, test->buffer, i);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -431,13 +598,14 @@ static int mmc_test_pow2_write(struct mmc_test_card *test)
|
||||
static int mmc_test_pow2_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (!test->card->csd.read_partial)
|
||||
return RESULT_UNSUP_CARD;
|
||||
|
||||
for (i = 1; i < 512;i <<= 1) {
|
||||
ret = mmc_test_verified_transfer(test, 0,
|
||||
test->buffer, 0, 1, i);
|
||||
sg_init_one(&sg, test->buffer, i);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -448,13 +616,14 @@ static int mmc_test_pow2_read(struct mmc_test_card *test)
|
||||
static int mmc_test_weird_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (!test->card->csd.write_partial)
|
||||
return RESULT_UNSUP_CARD;
|
||||
|
||||
for (i = 3; i < 512;i += 7) {
|
||||
ret = mmc_test_verified_transfer(test, 1,
|
||||
test->buffer, 0, 1, i);
|
||||
sg_init_one(&sg, test->buffer, i);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -465,13 +634,14 @@ static int mmc_test_weird_write(struct mmc_test_card *test)
|
||||
static int mmc_test_weird_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (!test->card->csd.read_partial)
|
||||
return RESULT_UNSUP_CARD;
|
||||
|
||||
for (i = 3; i < 512;i += 7) {
|
||||
ret = mmc_test_verified_transfer(test, 0,
|
||||
test->buffer, 0, 1, i);
|
||||
sg_init_one(&sg, test->buffer, i);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -482,10 +652,11 @@ static int mmc_test_weird_read(struct mmc_test_card *test)
|
||||
static int mmc_test_align_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
for (i = 1;i < 4;i++) {
|
||||
ret = mmc_test_verified_transfer(test, 1, test->buffer + i,
|
||||
0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer + i, 512);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -496,10 +667,11 @@ static int mmc_test_align_write(struct mmc_test_card *test)
|
||||
static int mmc_test_align_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
struct scatterlist sg;
|
||||
|
||||
for (i = 1;i < 4;i++) {
|
||||
ret = mmc_test_verified_transfer(test, 0, test->buffer + i,
|
||||
0, 1, 512);
|
||||
sg_init_one(&sg, test->buffer + i, 512);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -511,6 +683,7 @@ static int mmc_test_align_multi_write(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
unsigned int size;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (test->card->host->max_blk_count == 1)
|
||||
return RESULT_UNSUP_HOST;
|
||||
@ -524,8 +697,8 @@ static int mmc_test_align_multi_write(struct mmc_test_card *test)
|
||||
return RESULT_UNSUP_HOST;
|
||||
|
||||
for (i = 1;i < 4;i++) {
|
||||
ret = mmc_test_verified_transfer(test, 1, test->buffer + i,
|
||||
0, size / 512, 512);
|
||||
sg_init_one(&sg, test->buffer + i, size);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -537,6 +710,7 @@ static int mmc_test_align_multi_read(struct mmc_test_card *test)
|
||||
{
|
||||
int ret, i;
|
||||
unsigned int size;
|
||||
struct scatterlist sg;
|
||||
|
||||
if (test->card->host->max_blk_count == 1)
|
||||
return RESULT_UNSUP_HOST;
|
||||
@ -550,8 +724,8 @@ static int mmc_test_align_multi_read(struct mmc_test_card *test)
|
||||
return RESULT_UNSUP_HOST;
|
||||
|
||||
for (i = 1;i < 4;i++) {
|
||||
ret = mmc_test_verified_transfer(test, 0, test->buffer + i,
|
||||
0, size / 512, 512);
|
||||
sg_init_one(&sg, test->buffer + i, size);
|
||||
ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -567,7 +741,7 @@ static int mmc_test_xfersize_write(struct mmc_test_card *test)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 1, 512);
|
||||
ret = mmc_test_broken_transfer(test, 1, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -582,7 +756,7 @@ static int mmc_test_xfersize_read(struct mmc_test_card *test)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 1, 512);
|
||||
ret = mmc_test_broken_transfer(test, 1, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -600,7 +774,7 @@ static int mmc_test_multi_xfersize_write(struct mmc_test_card *test)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 2, 512);
|
||||
ret = mmc_test_broken_transfer(test, 2, 512, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -618,7 +792,7 @@ static int mmc_test_multi_xfersize_read(struct mmc_test_card *test)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 2, 512);
|
||||
ret = mmc_test_broken_transfer(test, 2, 512, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -638,86 +812,86 @@ static const struct mmc_test_case mmc_test_cases[] = {
|
||||
|
||||
{
|
||||
.name = "Basic write (with data verification)",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_verify_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Basic read (with data verification)",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_verify_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Multi-block write",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_multi_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Multi-block read",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_multi_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Power of two block writes",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_pow2_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Power of two block reads",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_pow2_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Weird sized block writes",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_weird_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Weird sized block reads",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_weird_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Badly aligned write",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_align_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Badly aligned read",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_align_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Badly aligned multi-block write",
|
||||
.prepare = mmc_test_prepare_verify_write,
|
||||
.prepare = mmc_test_prepare_write,
|
||||
.run = mmc_test_align_multi_write,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Badly aligned multi-block read",
|
||||
.prepare = mmc_test_prepare_verify_read,
|
||||
.prepare = mmc_test_prepare_read,
|
||||
.run = mmc_test_align_multi_read,
|
||||
.cleanup = mmc_test_cleanup_verify,
|
||||
.cleanup = mmc_test_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
@ -743,7 +917,7 @@ static const struct mmc_test_case mmc_test_cases[] = {
|
||||
|
||||
static struct mutex mmc_test_lock;
|
||||
|
||||
static void mmc_test_run(struct mmc_test_card *test)
|
||||
static void mmc_test_run(struct mmc_test_card *test, int testcase)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
@ -753,6 +927,9 @@ static void mmc_test_run(struct mmc_test_card *test)
|
||||
mmc_claim_host(test->card->host);
|
||||
|
||||
for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
|
||||
if (testcase && ((i + 1) != testcase))
|
||||
continue;
|
||||
|
||||
printk(KERN_INFO "%s: Test case %d. %s...\n",
|
||||
mmc_hostname(test->card->host), i + 1,
|
||||
mmc_test_cases[i].name);
|
||||
@ -824,9 +1001,12 @@ static ssize_t mmc_test_store(struct device *dev,
|
||||
{
|
||||
struct mmc_card *card;
|
||||
struct mmc_test_card *test;
|
||||
int testcase;
|
||||
|
||||
card = container_of(dev, struct mmc_card, dev);
|
||||
|
||||
testcase = simple_strtol(buf, NULL, 10);
|
||||
|
||||
test = kzalloc(sizeof(struct mmc_test_card), GFP_KERNEL);
|
||||
if (!test)
|
||||
return -ENOMEM;
|
||||
@ -836,7 +1016,7 @@ static ssize_t mmc_test_store(struct device *dev,
|
||||
test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
|
||||
if (test->buffer) {
|
||||
mutex_lock(&mmc_test_lock);
|
||||
mmc_test_run(test);
|
||||
mmc_test_run(test, testcase);
|
||||
mutex_unlock(&mmc_test_lock);
|
||||
}
|
||||
|
||||
@ -852,6 +1032,9 @@ static int mmc_test_probe(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((card->type != MMC_TYPE_MMC) && (card->type != MMC_TYPE_SD))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_init(&mmc_test_lock);
|
||||
|
||||
ret = device_create_file(&card->dev, &dev_attr_test);
|
||||
|
@ -885,12 +885,14 @@ static void sdio_uart_set_termios(struct tty_struct *tty, struct ktermios *old_t
|
||||
sdio_uart_release_func(port);
|
||||
}
|
||||
|
||||
static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
|
||||
static int sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
|
||||
{
|
||||
struct sdio_uart_port *port = tty->driver_data;
|
||||
int result;
|
||||
|
||||
if (sdio_uart_claim_func(port) != 0)
|
||||
return;
|
||||
result = sdio_uart_claim_func(port);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
if (break_state == -1)
|
||||
port->lcr |= UART_LCR_SBC;
|
||||
@ -899,6 +901,7 @@ static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
|
||||
sdio_out(port, UART_LCR, port->lcr);
|
||||
|
||||
sdio_uart_release_func(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdio_uart_tiocmget(struct tty_struct *tty, struct file *file)
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* 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.
|
||||
* Copyright (C) 2005-2008 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
|
||||
@ -294,6 +294,33 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_set_data_timeout);
|
||||
|
||||
/**
|
||||
* mmc_align_data_size - pads a transfer size to a more optimal value
|
||||
* @card: the MMC card associated with the data transfer
|
||||
* @sz: original transfer size
|
||||
*
|
||||
* Pads the original data size with a number of extra bytes in
|
||||
* order to avoid controller bugs and/or performance hits
|
||||
* (e.g. some controllers revert to PIO for certain sizes).
|
||||
*
|
||||
* Returns the improved size, which might be unmodified.
|
||||
*
|
||||
* Note that this function is only relevant when issuing a
|
||||
* single scatter gather entry.
|
||||
*/
|
||||
unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz)
|
||||
{
|
||||
/*
|
||||
* FIXME: We don't have a system for the controller to tell
|
||||
* the core about its problems yet, so for now we just 32-bit
|
||||
* align the size.
|
||||
*/
|
||||
sz = ((sz + 3) / 4) * 4;
|
||||
|
||||
return sz;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_align_data_size);
|
||||
|
||||
/**
|
||||
* __mmc_claim_host - exclusively claim a host
|
||||
* @host: mmc host to claim
|
||||
@ -638,6 +665,9 @@ void mmc_rescan(struct work_struct *work)
|
||||
*/
|
||||
mmc_bus_put(host);
|
||||
|
||||
if (host->ops->get_cd && host->ops->get_cd(host) == 0)
|
||||
goto out;
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
mmc_power_up(host);
|
||||
@ -652,7 +682,7 @@ void mmc_rescan(struct work_struct *work)
|
||||
if (!err) {
|
||||
if (mmc_attach_sdio(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -662,7 +692,7 @@ void mmc_rescan(struct work_struct *work)
|
||||
if (!err) {
|
||||
if (mmc_attach_sd(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -672,7 +702,7 @@ void mmc_rescan(struct work_struct *work)
|
||||
if (!err) {
|
||||
if (mmc_attach_mmc(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mmc_release_host(host);
|
||||
@ -683,6 +713,9 @@ void mmc_rescan(struct work_struct *work)
|
||||
|
||||
mmc_bus_put(host);
|
||||
}
|
||||
out:
|
||||
if (host->caps & MMC_CAP_NEEDS_POLL)
|
||||
mmc_schedule_delayed_work(&host->detect, HZ);
|
||||
}
|
||||
|
||||
void mmc_start_host(struct mmc_host *host)
|
||||
|
@ -288,7 +288,7 @@ static struct device_type mmc_type = {
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* In the case of a resume, "oldcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
|
@ -326,7 +326,7 @@ static struct device_type sd_type = {
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* In the case of a resume, "oldcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
|
||||
@ -494,13 +494,13 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
|
||||
* Check if read-only switch is active.
|
||||
*/
|
||||
if (!oldcard) {
|
||||
if (!host->ops->get_ro) {
|
||||
if (!host->ops->get_ro || host->ops->get_ro(host) < 0) {
|
||||
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))
|
||||
if (host->ops->get_ro(host) > 0)
|
||||
mmc_card_set_readonly(card);
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +129,12 @@ static int cistpl_funce_func(struct sdio_func *func,
|
||||
/* TPLFE_MAX_BLK_SIZE */
|
||||
func->max_blksize = buf[12] | (buf[13] << 8);
|
||||
|
||||
/* TPLFE_ENABLE_TIMEOUT_VAL, present in ver 1.1 and above */
|
||||
if (vsn > SDIO_SDIO_REV_1_00)
|
||||
func->enable_timeout = (buf[28] | (buf[29] << 8)) * 10;
|
||||
else
|
||||
func->enable_timeout = jiffies_to_msecs(HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_io.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
* Copyright 2007-2008 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
|
||||
@ -76,11 +76,7 @@ int sdio_enable_func(struct sdio_func *func)
|
||||
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;
|
||||
timeout = jiffies + msecs_to_jiffies(func->enable_timeout);
|
||||
|
||||
while (1) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®);
|
||||
@ -167,10 +163,8 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
|
||||
return -EINVAL;
|
||||
|
||||
if (blksz == 0) {
|
||||
blksz = min(min(
|
||||
func->max_blksize,
|
||||
func->card->host->max_blk_size),
|
||||
512u);
|
||||
blksz = min(func->max_blksize, func->card->host->max_blk_size);
|
||||
blksz = min(blksz, 512u);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0,
|
||||
@ -186,9 +180,116 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
|
||||
func->cur_blksize = blksz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sdio_set_block_size);
|
||||
|
||||
/*
|
||||
* Calculate the maximum byte mode transfer size
|
||||
*/
|
||||
static inline unsigned int sdio_max_byte_size(struct sdio_func *func)
|
||||
{
|
||||
unsigned mval = min(func->card->host->max_seg_size,
|
||||
func->card->host->max_blk_size);
|
||||
mval = min(mval, func->max_blksize);
|
||||
return min(mval, 512u); /* maximum size for byte mode */
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_align_size - pads a transfer size to a more optimal value
|
||||
* @func: SDIO function
|
||||
* @sz: original transfer size
|
||||
*
|
||||
* Pads the original data size with a number of extra bytes in
|
||||
* order to avoid controller bugs and/or performance hits
|
||||
* (e.g. some controllers revert to PIO for certain sizes).
|
||||
*
|
||||
* If possible, it will also adjust the size so that it can be
|
||||
* handled in just a single request.
|
||||
*
|
||||
* Returns the improved size, which might be unmodified.
|
||||
*/
|
||||
unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz)
|
||||
{
|
||||
unsigned int orig_sz;
|
||||
unsigned int blk_sz, byte_sz;
|
||||
unsigned chunk_sz;
|
||||
|
||||
orig_sz = sz;
|
||||
|
||||
/*
|
||||
* Do a first check with the controller, in case it
|
||||
* wants to increase the size up to a point where it
|
||||
* might need more than one block.
|
||||
*/
|
||||
sz = mmc_align_data_size(func->card, sz);
|
||||
|
||||
/*
|
||||
* If we can still do this with just a byte transfer, then
|
||||
* we're done.
|
||||
*/
|
||||
if (sz <= sdio_max_byte_size(func))
|
||||
return sz;
|
||||
|
||||
if (func->card->cccr.multi_block) {
|
||||
/*
|
||||
* Check if the transfer is already block aligned
|
||||
*/
|
||||
if ((sz % func->cur_blksize) == 0)
|
||||
return sz;
|
||||
|
||||
/*
|
||||
* Realign it so that it can be done with one request,
|
||||
* and recheck if the controller still likes it.
|
||||
*/
|
||||
blk_sz = ((sz + func->cur_blksize - 1) /
|
||||
func->cur_blksize) * func->cur_blksize;
|
||||
blk_sz = mmc_align_data_size(func->card, blk_sz);
|
||||
|
||||
/*
|
||||
* This value is only good if it is still just
|
||||
* one request.
|
||||
*/
|
||||
if ((blk_sz % func->cur_blksize) == 0)
|
||||
return blk_sz;
|
||||
|
||||
/*
|
||||
* We failed to do one request, but at least try to
|
||||
* pad the remainder properly.
|
||||
*/
|
||||
byte_sz = mmc_align_data_size(func->card,
|
||||
sz % func->cur_blksize);
|
||||
if (byte_sz <= sdio_max_byte_size(func)) {
|
||||
blk_sz = sz / func->cur_blksize;
|
||||
return blk_sz * func->cur_blksize + byte_sz;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We need multiple requests, so first check that the
|
||||
* controller can handle the chunk size;
|
||||
*/
|
||||
chunk_sz = mmc_align_data_size(func->card,
|
||||
sdio_max_byte_size(func));
|
||||
if (chunk_sz == sdio_max_byte_size(func)) {
|
||||
/*
|
||||
* Fix up the size of the remainder (if any)
|
||||
*/
|
||||
byte_sz = orig_sz % chunk_sz;
|
||||
if (byte_sz) {
|
||||
byte_sz = mmc_align_data_size(func->card,
|
||||
byte_sz);
|
||||
}
|
||||
|
||||
return (orig_sz / chunk_sz) * chunk_sz + byte_sz;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The controller is simply incapable of transferring the size
|
||||
* we want in decent manner, so just return the original size.
|
||||
*/
|
||||
return orig_sz;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_align_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,
|
||||
@ -199,14 +300,13 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
|
||||
int ret;
|
||||
|
||||
/* Do the bulk of the transfer using block mode (if supported). */
|
||||
if (func->card->cccr.multi_block) {
|
||||
if (func->card->cccr.multi_block && (size > sdio_max_byte_size(func))) {
|
||||
/* 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);
|
||||
max_blocks = min(func->card->host->max_blk_count,
|
||||
func->card->host->max_seg_size / func->cur_blksize);
|
||||
max_blocks = min(max_blocks, 511u);
|
||||
|
||||
while (remainder > func->cur_blksize) {
|
||||
unsigned blocks;
|
||||
@ -231,11 +331,7 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
|
||||
|
||||
/* 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 */
|
||||
size = min(remainder, sdio_max_byte_size(func));
|
||||
|
||||
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
|
||||
incr_addr, buf, 1, size);
|
||||
@ -260,11 +356,10 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
|
||||
* 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)
|
||||
u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
u8 val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
@ -293,8 +388,7 @@ EXPORT_SYMBOL_GPL(sdio_readb);
|
||||
* 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)
|
||||
void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -355,7 +449,6 @@ int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sdio_readsb);
|
||||
|
||||
/**
|
||||
@ -385,8 +478,7 @@ EXPORT_SYMBOL_GPL(sdio_writesb);
|
||||
* 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)
|
||||
u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -400,7 +492,7 @@ unsigned short sdio_readw(struct sdio_func *func, unsigned int addr,
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
return le16_to_cpu(*(u16*)func->tmpbuf);
|
||||
return le16_to_cpup((__le16 *)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readw);
|
||||
|
||||
@ -415,12 +507,11 @@ EXPORT_SYMBOL_GPL(sdio_readw);
|
||||
* 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)
|
||||
void sdio_writew(struct sdio_func *func, u16 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u16*)func->tmpbuf = cpu_to_le16(b);
|
||||
*(__le16 *)func->tmpbuf = cpu_to_le16(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
|
||||
if (err_ret)
|
||||
@ -439,8 +530,7 @@ EXPORT_SYMBOL_GPL(sdio_writew);
|
||||
* 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)
|
||||
u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -454,7 +544,7 @@ unsigned long sdio_readl(struct sdio_func *func, unsigned int addr,
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
return le32_to_cpu(*(u32*)func->tmpbuf);
|
||||
return le32_to_cpup((__le32 *)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readl);
|
||||
|
||||
@ -469,12 +559,11 @@ EXPORT_SYMBOL_GPL(sdio_readl);
|
||||
* 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)
|
||||
void sdio_writel(struct sdio_func *func, u32 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u32*)func->tmpbuf = cpu_to_le32(b);
|
||||
*(__le32 *)func->tmpbuf = cpu_to_le32(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
|
||||
if (err_ret)
|
||||
|
@ -26,18 +26,31 @@ config MMC_PXA
|
||||
|
||||
config MMC_SDHCI
|
||||
tristate "Secure Digital Host Controller Interface support"
|
||||
depends on PCI
|
||||
depends on HAS_DMA
|
||||
help
|
||||
This select the generic Secure Digital Host Controller Interface.
|
||||
This selects 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. You
|
||||
also need to enable an appropriate bus interface.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_SDHCI_PCI
|
||||
tristate "SDHCI support on PCI bus"
|
||||
depends on MMC_SDHCI && PCI
|
||||
help
|
||||
This selects the PCI Secure Digital Host Controller Interface.
|
||||
Most controllers found today are PCI devices.
|
||||
|
||||
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
|
||||
depends on MMC_SDHCI_PCI
|
||||
help
|
||||
This selects the disabler for the Ricoh MMC Controller. This
|
||||
proprietary controller is unnecessary because the SDHCI driver
|
||||
@ -91,6 +104,16 @@ config MMC_AT91
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_ATMELMCI
|
||||
tristate "Atmel Multimedia Card Interface support"
|
||||
depends on AVR32
|
||||
help
|
||||
This selects the Atmel Multimedia Card Interface driver. If
|
||||
you have an AT32 (AVR32) platform with a Multimedia Card
|
||||
slot, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_IMX
|
||||
tristate "Motorola i.MX Multimedia Card Interface support"
|
||||
depends on ARCH_IMX
|
||||
@ -130,3 +153,24 @@ config MMC_SPI
|
||||
|
||||
If unsure, or if your system has no SPI master driver, say N.
|
||||
|
||||
config MMC_S3C
|
||||
tristate "Samsung S3C SD/MMC Card Interface support"
|
||||
depends on ARCH_S3C2410 && MMC
|
||||
help
|
||||
This selects a driver for the MCI interface found in
|
||||
Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs.
|
||||
If you have a board based on one of those and a MMC/SD
|
||||
slot, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_SDRICOH_CS
|
||||
tristate "MMC/SD driver for Ricoh Bay1Controllers (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL && MMC && PCI && PCMCIA
|
||||
help
|
||||
Say Y here if your Notebook reports a Ricoh Bay1Controller PCMCIA
|
||||
card whenever you insert a MMC or SD card into the card slot.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called sdricoh_cs.
|
||||
|
||||
|
@ -10,11 +10,15 @@ 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_SDHCI_PCI) += sdhci-pci.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_ATMELMCI) += atmel-mci.o
|
||||
obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o
|
||||
obj-$(CONFIG_MMC_SPI) += mmc_spi.o
|
||||
obj-$(CONFIG_MMC_S3C) += s3cmci.o
|
||||
obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o
|
||||
|
||||
|
@ -125,8 +125,71 @@ struct at91mci_host
|
||||
|
||||
/* Latest in the scatterlist that has been enabled for transfer */
|
||||
int transfer_index;
|
||||
|
||||
/* Timer for timeouts */
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
/*
|
||||
* Reset the controller and restore most of the state
|
||||
*/
|
||||
static void at91_reset_host(struct at91mci_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 mr;
|
||||
u32 sdcr;
|
||||
u32 dtor;
|
||||
u32 imr;
|
||||
|
||||
local_irq_save(flags);
|
||||
imr = at91_mci_read(host, AT91_MCI_IMR);
|
||||
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
|
||||
|
||||
/* save current state */
|
||||
mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
|
||||
sdcr = at91_mci_read(host, AT91_MCI_SDCR);
|
||||
dtor = at91_mci_read(host, AT91_MCI_DTOR);
|
||||
|
||||
/* reset the controller */
|
||||
at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
|
||||
|
||||
/* restore state */
|
||||
at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
|
||||
at91_mci_write(host, AT91_MCI_MR, mr);
|
||||
at91_mci_write(host, AT91_MCI_SDCR, sdcr);
|
||||
at91_mci_write(host, AT91_MCI_DTOR, dtor);
|
||||
at91_mci_write(host, AT91_MCI_IER, imr);
|
||||
|
||||
/* make sure sdio interrupts will fire */
|
||||
at91_mci_read(host, AT91_MCI_SR);
|
||||
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void at91_timeout_timer(unsigned long data)
|
||||
{
|
||||
struct at91mci_host *host;
|
||||
|
||||
host = (struct at91mci_host *)data;
|
||||
|
||||
if (host->request) {
|
||||
dev_err(host->mmc->parent, "Timeout waiting end of packet\n");
|
||||
|
||||
if (host->cmd && host->cmd->data) {
|
||||
host->cmd->data->error = -ETIMEDOUT;
|
||||
} else {
|
||||
if (host->cmd)
|
||||
host->cmd->error = -ETIMEDOUT;
|
||||
else
|
||||
host->request->cmd->error = -ETIMEDOUT;
|
||||
}
|
||||
|
||||
at91_reset_host(host);
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy from sg to a dma block - used for transfers
|
||||
*/
|
||||
@ -135,9 +198,14 @@ static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data
|
||||
unsigned int len, i, size;
|
||||
unsigned *dmabuf = host->buffer;
|
||||
|
||||
size = host->total_length;
|
||||
size = data->blksz * data->blocks;
|
||||
len = data->sg_len;
|
||||
|
||||
/* AT91SAM926[0/3] Data Write Operation and number of bytes erratum */
|
||||
if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
|
||||
if (host->total_length == 12)
|
||||
memset(dmabuf, 0, 12);
|
||||
|
||||
/*
|
||||
* Just loop through all entries. Size might not
|
||||
* be the entire list though so make sure that
|
||||
@ -159,9 +227,10 @@ static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data
|
||||
|
||||
for (index = 0; index < (amount / 4); index++)
|
||||
*dmabuf++ = swab32(sgbuffer[index]);
|
||||
}
|
||||
else
|
||||
} else {
|
||||
memcpy(dmabuf, sgbuffer, amount);
|
||||
dmabuf += amount;
|
||||
}
|
||||
|
||||
kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
|
||||
|
||||
@ -233,11 +302,11 @@ static void at91_mci_pre_dma_read(struct at91mci_host *host)
|
||||
|
||||
if (i == 0) {
|
||||
at91_mci_write(host, ATMEL_PDC_RPR, sg->dma_address);
|
||||
at91_mci_write(host, ATMEL_PDC_RCR, sg->length / 4);
|
||||
at91_mci_write(host, ATMEL_PDC_RCR, (data->blksz & 0x3) ? sg->length : sg->length / 4);
|
||||
}
|
||||
else {
|
||||
at91_mci_write(host, ATMEL_PDC_RNPR, sg->dma_address);
|
||||
at91_mci_write(host, ATMEL_PDC_RNCR, sg->length / 4);
|
||||
at91_mci_write(host, ATMEL_PDC_RNCR, (data->blksz & 0x3) ? sg->length : sg->length / 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,8 +346,6 @@ static void at91_mci_post_dma_read(struct at91mci_host *host)
|
||||
|
||||
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
|
||||
|
||||
data->bytes_xfered += sg->length;
|
||||
|
||||
if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
|
||||
unsigned int *buffer;
|
||||
int index;
|
||||
@ -294,6 +361,8 @@ static void at91_mci_post_dma_read(struct at91mci_host *host)
|
||||
}
|
||||
|
||||
flush_dcache_page(sg_page(sg));
|
||||
|
||||
data->bytes_xfered += sg->length;
|
||||
}
|
||||
|
||||
/* Is there another transfer to trigger? */
|
||||
@ -334,10 +403,32 @@ static void at91_mci_handle_transmitted(struct at91mci_host *host)
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
|
||||
} else
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
|
||||
|
||||
data->bytes_xfered = host->total_length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update bytes tranfered count during a write operation
|
||||
*/
|
||||
static void at91_mci_update_bytes_xfered(struct at91mci_host *host)
|
||||
{
|
||||
struct mmc_data *data;
|
||||
|
||||
/* always deal with the effective request (and not the current cmd) */
|
||||
|
||||
if (host->request->cmd && host->request->cmd->error != 0)
|
||||
return;
|
||||
|
||||
if (host->request->data) {
|
||||
data = host->request->data;
|
||||
if (data->flags & MMC_DATA_WRITE) {
|
||||
/* card is in IDLE mode now */
|
||||
pr_debug("-> bytes_xfered %d, total_length = %d\n",
|
||||
data->bytes_xfered, host->total_length);
|
||||
data->bytes_xfered = data->blksz * data->blocks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*Handle after command sent ready*/
|
||||
static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
|
||||
{
|
||||
@ -350,8 +441,7 @@ static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
|
||||
} else return 1;
|
||||
} else if (host->cmd->data->flags & MMC_DATA_WRITE) {
|
||||
/*After sendding multi-block-write command, start DMA transfer*/
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE);
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE | AT91_MCI_BLKE);
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
|
||||
}
|
||||
|
||||
@ -430,11 +520,19 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
|
||||
|
||||
if (data) {
|
||||
|
||||
if ( data->blksz & 0x3 ) {
|
||||
pr_debug("Unsupported block size\n");
|
||||
cmd->error = -EINVAL;
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
return;
|
||||
if (cpu_is_at91rm9200() || cpu_is_at91sam9261()) {
|
||||
if (data->blksz & 0x3) {
|
||||
pr_debug("Unsupported block size\n");
|
||||
cmd->error = -EINVAL;
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
return;
|
||||
}
|
||||
if (data->flags & MMC_DATA_STREAM) {
|
||||
pr_debug("Stream commands not supported\n");
|
||||
cmd->error = -EINVAL;
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
block_length = data->blksz;
|
||||
@ -481,8 +579,16 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
|
||||
ier = AT91_MCI_CMDRDY;
|
||||
} else {
|
||||
/* zero block length and PDC mode */
|
||||
mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
|
||||
at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
|
||||
mr = at91_mci_read(host, AT91_MCI_MR) & 0x5fff;
|
||||
mr |= (data->blksz & 0x3) ? AT91_MCI_PDCFBYTE : 0;
|
||||
mr |= (block_length << 16);
|
||||
mr |= AT91_MCI_PDCMODE;
|
||||
at91_mci_write(host, AT91_MCI_MR, mr);
|
||||
|
||||
if (!(cpu_is_at91rm9200() || cpu_is_at91sam9261()))
|
||||
at91_mci_write(host, AT91_MCI_BLKR,
|
||||
AT91_MCI_BLKR_BCNT(blocks) |
|
||||
AT91_MCI_BLKR_BLKLEN(block_length));
|
||||
|
||||
/*
|
||||
* Disable the PDC controller
|
||||
@ -508,6 +614,13 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
|
||||
* Handle a write
|
||||
*/
|
||||
host->total_length = block_length * blocks;
|
||||
/*
|
||||
* AT91SAM926[0/3] Data Write Operation and
|
||||
* number of bytes erratum
|
||||
*/
|
||||
if (cpu_is_at91sam9260 () || cpu_is_at91sam9263())
|
||||
if (host->total_length < 12)
|
||||
host->total_length = 12;
|
||||
host->buffer = dma_alloc_coherent(NULL,
|
||||
host->total_length,
|
||||
&host->physical_address, GFP_KERNEL);
|
||||
@ -517,7 +630,9 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
|
||||
pr_debug("Transmitting %d bytes\n", host->total_length);
|
||||
|
||||
at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
|
||||
at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
|
||||
at91_mci_write(host, ATMEL_PDC_TCR, (data->blksz & 0x3) ?
|
||||
host->total_length : host->total_length / 4);
|
||||
|
||||
ier = AT91_MCI_CMDRDY;
|
||||
}
|
||||
}
|
||||
@ -552,20 +667,26 @@ static void at91_mci_process_next(struct at91mci_host *host)
|
||||
else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
|
||||
host->flags |= FL_SENT_STOP;
|
||||
at91_mci_send_command(host, host->request->stop);
|
||||
}
|
||||
else
|
||||
} else {
|
||||
del_timer(&host->timer);
|
||||
/* the at91rm9200 mci controller hangs after some transfers,
|
||||
* and the workaround is to reset it after each transfer.
|
||||
*/
|
||||
if (cpu_is_at91rm9200())
|
||||
at91_reset_host(host);
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a command that has been completed
|
||||
*/
|
||||
static void at91_mci_completed_command(struct at91mci_host *host)
|
||||
static void at91_mci_completed_command(struct at91mci_host *host, unsigned int status)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
unsigned int status;
|
||||
struct mmc_data *data = cmd->data;
|
||||
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
|
||||
|
||||
cmd->resp[0] = at91_mci_read(host, AT91_MCI_RSPR(0));
|
||||
cmd->resp[1] = at91_mci_read(host, AT91_MCI_RSPR(1));
|
||||
@ -577,25 +698,34 @@ static void at91_mci_completed_command(struct at91mci_host *host)
|
||||
host->buffer = NULL;
|
||||
}
|
||||
|
||||
status = at91_mci_read(host, AT91_MCI_SR);
|
||||
|
||||
pr_debug("Status = %08X [%08X %08X %08X %08X]\n",
|
||||
status, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
|
||||
pr_debug("Status = %08X/%08x [%08X %08X %08X %08X]\n",
|
||||
status, at91_mci_read(host, AT91_MCI_SR),
|
||||
cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
|
||||
|
||||
if (status & AT91_MCI_ERRORS) {
|
||||
if ((status & AT91_MCI_RCRCE) && !(mmc_resp_type(cmd) & MMC_RSP_CRC)) {
|
||||
cmd->error = 0;
|
||||
}
|
||||
else {
|
||||
if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE))
|
||||
cmd->error = -ETIMEDOUT;
|
||||
else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE))
|
||||
cmd->error = -EILSEQ;
|
||||
else
|
||||
cmd->error = -EIO;
|
||||
if (status & (AT91_MCI_DTOE | AT91_MCI_DCRCE)) {
|
||||
if (data) {
|
||||
if (status & AT91_MCI_DTOE)
|
||||
data->error = -ETIMEDOUT;
|
||||
else if (status & AT91_MCI_DCRCE)
|
||||
data->error = -EILSEQ;
|
||||
}
|
||||
} else {
|
||||
if (status & AT91_MCI_RTOE)
|
||||
cmd->error = -ETIMEDOUT;
|
||||
else if (status & AT91_MCI_RCRCE)
|
||||
cmd->error = -EILSEQ;
|
||||
else
|
||||
cmd->error = -EIO;
|
||||
}
|
||||
|
||||
pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n",
|
||||
cmd->error, cmd->opcode, cmd->retries);
|
||||
pr_debug("Error detected and set to %d/%d (cmd = %d, retries = %d)\n",
|
||||
cmd->error, data ? data->error : 0,
|
||||
cmd->opcode, cmd->retries);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -613,6 +743,8 @@ static void at91_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
host->request = mrq;
|
||||
host->flags = 0;
|
||||
|
||||
mod_timer(&host->timer, jiffies + HZ);
|
||||
|
||||
at91_mci_process_next(host);
|
||||
}
|
||||
|
||||
@ -736,6 +868,7 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
|
||||
|
||||
if (int_status & AT91_MCI_NOTBUSY) {
|
||||
pr_debug("Card is ready\n");
|
||||
at91_mci_update_bytes_xfered(host);
|
||||
completed = 1;
|
||||
}
|
||||
|
||||
@ -744,9 +877,21 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
|
||||
|
||||
if (int_status & AT91_MCI_BLKE) {
|
||||
pr_debug("Block transfer has ended\n");
|
||||
completed = 1;
|
||||
if (host->request->data && host->request->data->blocks > 1) {
|
||||
/* multi block write : complete multi write
|
||||
* command and send stop */
|
||||
completed = 1;
|
||||
} else {
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
|
||||
}
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_SDIOIRQA)
|
||||
mmc_signal_sdio_irq(host->mmc);
|
||||
|
||||
if (int_status & AT91_MCI_SDIOIRQB)
|
||||
mmc_signal_sdio_irq(host->mmc);
|
||||
|
||||
if (int_status & AT91_MCI_TXRDY)
|
||||
pr_debug("Ready to transmit\n");
|
||||
|
||||
@ -761,10 +906,10 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
|
||||
|
||||
if (completed) {
|
||||
pr_debug("Completed command\n");
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
|
||||
at91_mci_completed_command(host);
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
|
||||
at91_mci_completed_command(host, int_status);
|
||||
} else
|
||||
at91_mci_write(host, AT91_MCI_IDR, int_status);
|
||||
at91_mci_write(host, AT91_MCI_IDR, int_status & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@ -793,25 +938,33 @@ static irqreturn_t at91_mmc_det_irq(int irq, void *_host)
|
||||
|
||||
static int at91_mci_get_ro(struct mmc_host *mmc)
|
||||
{
|
||||
int read_only = 0;
|
||||
struct at91mci_host *host = mmc_priv(mmc);
|
||||
|
||||
if (host->board->wp_pin) {
|
||||
read_only = gpio_get_value(host->board->wp_pin);
|
||||
printk(KERN_WARNING "%s: card is %s\n", mmc_hostname(mmc),
|
||||
(read_only ? "read-only" : "read-write") );
|
||||
}
|
||||
else {
|
||||
printk(KERN_WARNING "%s: host does not support reading read-only "
|
||||
"switch. Assuming write-enable.\n", mmc_hostname(mmc));
|
||||
}
|
||||
return read_only;
|
||||
if (host->board->wp_pin)
|
||||
return !!gpio_get_value(host->board->wp_pin);
|
||||
/*
|
||||
* Board doesn't support read only detection; let the mmc core
|
||||
* decide what to do.
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static void at91_mci_enable_sdio_irq(struct mmc_host *mmc, int enable)
|
||||
{
|
||||
struct at91mci_host *host = mmc_priv(mmc);
|
||||
|
||||
pr_debug("%s: sdio_irq %c : %s\n", mmc_hostname(host->mmc),
|
||||
host->board->slot_b ? 'B':'A', enable ? "enable" : "disable");
|
||||
at91_mci_write(host, enable ? AT91_MCI_IER : AT91_MCI_IDR,
|
||||
host->board->slot_b ? AT91_MCI_SDIOIRQB : AT91_MCI_SDIOIRQA);
|
||||
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops at91_mci_ops = {
|
||||
.request = at91_mci_request,
|
||||
.set_ios = at91_mci_set_ios,
|
||||
.get_ro = at91_mci_get_ro,
|
||||
.enable_sdio_irq = at91_mci_enable_sdio_irq,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -842,6 +995,7 @@ static int __init at91_mci_probe(struct platform_device *pdev)
|
||||
mmc->f_min = 375000;
|
||||
mmc->f_max = 25000000;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps = MMC_CAP_SDIO_IRQ;
|
||||
|
||||
mmc->max_blk_size = 4095;
|
||||
mmc->max_blk_count = mmc->max_req_size;
|
||||
@ -935,6 +1089,8 @@ static int __init at91_mci_probe(struct platform_device *pdev)
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
setup_timer(&host->timer, at91_timeout_timer, (unsigned long)host);
|
||||
|
||||
/*
|
||||
* monitor card insertion/removal if we can
|
||||
*/
|
||||
@ -995,6 +1151,7 @@ static int __exit at91_mci_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
at91_mci_disable(host);
|
||||
del_timer_sync(&host->timer);
|
||||
mmc_remove_host(mmc);
|
||||
free_irq(host->irq, host);
|
||||
|
||||
|
91
drivers/mmc/host/atmel-mci-regs.h
Normal file
91
drivers/mmc/host/atmel-mci-regs.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Atmel MultiMedia Card Interface driver
|
||||
*
|
||||
* Copyright (C) 2004-2006 Atmel Corporation
|
||||
*
|
||||
* 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 __DRIVERS_MMC_ATMEL_MCI_H__
|
||||
#define __DRIVERS_MMC_ATMEL_MCI_H__
|
||||
|
||||
/* MCI Register Definitions */
|
||||
#define MCI_CR 0x0000 /* Control */
|
||||
# define MCI_CR_MCIEN ( 1 << 0) /* MCI Enable */
|
||||
# define MCI_CR_MCIDIS ( 1 << 1) /* MCI Disable */
|
||||
# define MCI_CR_SWRST ( 1 << 7) /* Software Reset */
|
||||
#define MCI_MR 0x0004 /* Mode */
|
||||
# define MCI_MR_CLKDIV(x) ((x) << 0) /* Clock Divider */
|
||||
# define MCI_MR_RDPROOF ( 1 << 11) /* Read Proof */
|
||||
# define MCI_MR_WRPROOF ( 1 << 12) /* Write Proof */
|
||||
#define MCI_DTOR 0x0008 /* Data Timeout */
|
||||
# define MCI_DTOCYC(x) ((x) << 0) /* Data Timeout Cycles */
|
||||
# define MCI_DTOMUL(x) ((x) << 4) /* Data Timeout Multiplier */
|
||||
#define MCI_SDCR 0x000c /* SD Card / SDIO */
|
||||
# define MCI_SDCSEL_SLOT_A ( 0 << 0) /* Select SD slot A */
|
||||
# define MCI_SDCSEL_SLOT_B ( 1 << 0) /* Select SD slot A */
|
||||
# define MCI_SDCBUS_1BIT ( 0 << 7) /* 1-bit data bus */
|
||||
# define MCI_SDCBUS_4BIT ( 1 << 7) /* 4-bit data bus */
|
||||
#define MCI_ARGR 0x0010 /* Command Argument */
|
||||
#define MCI_CMDR 0x0014 /* Command */
|
||||
# define MCI_CMDR_CMDNB(x) ((x) << 0) /* Command Opcode */
|
||||
# define MCI_CMDR_RSPTYP_NONE ( 0 << 6) /* No response */
|
||||
# define MCI_CMDR_RSPTYP_48BIT ( 1 << 6) /* 48-bit response */
|
||||
# define MCI_CMDR_RSPTYP_136BIT ( 2 << 6) /* 136-bit response */
|
||||
# define MCI_CMDR_SPCMD_INIT ( 1 << 8) /* Initialization command */
|
||||
# define MCI_CMDR_SPCMD_SYNC ( 2 << 8) /* Synchronized command */
|
||||
# define MCI_CMDR_SPCMD_INT ( 4 << 8) /* Interrupt command */
|
||||
# define MCI_CMDR_SPCMD_INTRESP ( 5 << 8) /* Interrupt response */
|
||||
# define MCI_CMDR_OPDCMD ( 1 << 11) /* Open Drain */
|
||||
# define MCI_CMDR_MAXLAT_5CYC ( 0 << 12) /* Max latency 5 cycles */
|
||||
# define MCI_CMDR_MAXLAT_64CYC ( 1 << 12) /* Max latency 64 cycles */
|
||||
# define MCI_CMDR_START_XFER ( 1 << 16) /* Start data transfer */
|
||||
# define MCI_CMDR_STOP_XFER ( 2 << 16) /* Stop data transfer */
|
||||
# define MCI_CMDR_TRDIR_WRITE ( 0 << 18) /* Write data */
|
||||
# define MCI_CMDR_TRDIR_READ ( 1 << 18) /* Read data */
|
||||
# define MCI_CMDR_BLOCK ( 0 << 19) /* Single-block transfer */
|
||||
# define MCI_CMDR_MULTI_BLOCK ( 1 << 19) /* Multi-block transfer */
|
||||
# define MCI_CMDR_STREAM ( 2 << 19) /* MMC Stream transfer */
|
||||
# define MCI_CMDR_SDIO_BYTE ( 4 << 19) /* SDIO Byte transfer */
|
||||
# define MCI_CMDR_SDIO_BLOCK ( 5 << 19) /* SDIO Block transfer */
|
||||
# define MCI_CMDR_SDIO_SUSPEND ( 1 << 24) /* SDIO Suspend Command */
|
||||
# define MCI_CMDR_SDIO_RESUME ( 2 << 24) /* SDIO Resume Command */
|
||||
#define MCI_BLKR 0x0018 /* Block */
|
||||
# define MCI_BCNT(x) ((x) << 0) /* Data Block Count */
|
||||
# define MCI_BLKLEN(x) ((x) << 16) /* Data Block Length */
|
||||
#define MCI_RSPR 0x0020 /* Response 0 */
|
||||
#define MCI_RSPR1 0x0024 /* Response 1 */
|
||||
#define MCI_RSPR2 0x0028 /* Response 2 */
|
||||
#define MCI_RSPR3 0x002c /* Response 3 */
|
||||
#define MCI_RDR 0x0030 /* Receive Data */
|
||||
#define MCI_TDR 0x0034 /* Transmit Data */
|
||||
#define MCI_SR 0x0040 /* Status */
|
||||
#define MCI_IER 0x0044 /* Interrupt Enable */
|
||||
#define MCI_IDR 0x0048 /* Interrupt Disable */
|
||||
#define MCI_IMR 0x004c /* Interrupt Mask */
|
||||
# define MCI_CMDRDY ( 1 << 0) /* Command Ready */
|
||||
# define MCI_RXRDY ( 1 << 1) /* Receiver Ready */
|
||||
# define MCI_TXRDY ( 1 << 2) /* Transmitter Ready */
|
||||
# define MCI_BLKE ( 1 << 3) /* Data Block Ended */
|
||||
# define MCI_DTIP ( 1 << 4) /* Data Transfer In Progress */
|
||||
# define MCI_NOTBUSY ( 1 << 5) /* Data Not Busy */
|
||||
# define MCI_SDIOIRQA ( 1 << 8) /* SDIO IRQ in slot A */
|
||||
# define MCI_SDIOIRQB ( 1 << 9) /* SDIO IRQ in slot B */
|
||||
# define MCI_RINDE ( 1 << 16) /* Response Index Error */
|
||||
# define MCI_RDIRE ( 1 << 17) /* Response Direction Error */
|
||||
# define MCI_RCRCE ( 1 << 18) /* Response CRC Error */
|
||||
# define MCI_RENDE ( 1 << 19) /* Response End Bit Error */
|
||||
# define MCI_RTOE ( 1 << 20) /* Response Time-Out Error */
|
||||
# define MCI_DCRCE ( 1 << 21) /* Data CRC Error */
|
||||
# define MCI_DTOE ( 1 << 22) /* Data Time-Out Error */
|
||||
# define MCI_OVRE ( 1 << 30) /* RX Overrun Error */
|
||||
# define MCI_UNRE ( 1 << 31) /* TX Underrun Error */
|
||||
|
||||
/* Register access macros */
|
||||
#define mci_readl(port,reg) \
|
||||
__raw_readl((port)->regs + MCI_##reg)
|
||||
#define mci_writel(port,reg,value) \
|
||||
__raw_writel((value), (port)->regs + MCI_##reg)
|
||||
|
||||
#endif /* __DRIVERS_MMC_ATMEL_MCI_H__ */
|
981
drivers/mmc/host/atmel-mci.c
Normal file
981
drivers/mmc/host/atmel-mci.c
Normal file
@ -0,0 +1,981 @@
|
||||
/*
|
||||
* Atmel MultiMedia Card Interface driver
|
||||
*
|
||||
* Copyright (C) 2004-2008 Atmel Corporation
|
||||
*
|
||||
* 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/blkdev.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include <asm/atmel-mci.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include <asm/arch/board.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
|
||||
#include "atmel-mci-regs.h"
|
||||
|
||||
#define ATMCI_DATA_ERROR_FLAGS (MCI_DCRCE | MCI_DTOE | MCI_OVRE | MCI_UNRE)
|
||||
|
||||
enum {
|
||||
EVENT_CMD_COMPLETE = 0,
|
||||
EVENT_DATA_ERROR,
|
||||
EVENT_DATA_COMPLETE,
|
||||
EVENT_STOP_SENT,
|
||||
EVENT_STOP_COMPLETE,
|
||||
EVENT_XFER_COMPLETE,
|
||||
};
|
||||
|
||||
struct atmel_mci {
|
||||
struct mmc_host *mmc;
|
||||
void __iomem *regs;
|
||||
|
||||
struct scatterlist *sg;
|
||||
unsigned int pio_offset;
|
||||
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
u32 cmd_status;
|
||||
u32 data_status;
|
||||
u32 stop_status;
|
||||
u32 stop_cmdr;
|
||||
|
||||
u32 mode_reg;
|
||||
u32 sdc_reg;
|
||||
|
||||
struct tasklet_struct tasklet;
|
||||
unsigned long pending_events;
|
||||
unsigned long completed_events;
|
||||
|
||||
int present;
|
||||
int detect_pin;
|
||||
int wp_pin;
|
||||
|
||||
/* For detect pin debouncing */
|
||||
struct timer_list detect_timer;
|
||||
|
||||
unsigned long bus_hz;
|
||||
unsigned long mapbase;
|
||||
struct clk *mck;
|
||||
struct platform_device *pdev;
|
||||
};
|
||||
|
||||
#define atmci_is_completed(host, event) \
|
||||
test_bit(event, &host->completed_events)
|
||||
#define atmci_test_and_clear_pending(host, event) \
|
||||
test_and_clear_bit(event, &host->pending_events)
|
||||
#define atmci_test_and_set_completed(host, event) \
|
||||
test_and_set_bit(event, &host->completed_events)
|
||||
#define atmci_set_completed(host, event) \
|
||||
set_bit(event, &host->completed_events)
|
||||
#define atmci_set_pending(host, event) \
|
||||
set_bit(event, &host->pending_events)
|
||||
#define atmci_clear_pending(host, event) \
|
||||
clear_bit(event, &host->pending_events)
|
||||
|
||||
|
||||
static void atmci_enable(struct atmel_mci *host)
|
||||
{
|
||||
clk_enable(host->mck);
|
||||
mci_writel(host, CR, MCI_CR_MCIEN);
|
||||
mci_writel(host, MR, host->mode_reg);
|
||||
mci_writel(host, SDCR, host->sdc_reg);
|
||||
}
|
||||
|
||||
static void atmci_disable(struct atmel_mci *host)
|
||||
{
|
||||
mci_writel(host, CR, MCI_CR_SWRST);
|
||||
|
||||
/* Stall until write is complete, then disable the bus clock */
|
||||
mci_readl(host, SR);
|
||||
clk_disable(host->mck);
|
||||
}
|
||||
|
||||
static inline unsigned int ns_to_clocks(struct atmel_mci *host,
|
||||
unsigned int ns)
|
||||
{
|
||||
return (ns * (host->bus_hz / 1000000) + 999) / 1000;
|
||||
}
|
||||
|
||||
static void atmci_set_timeout(struct atmel_mci *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
static unsigned dtomul_to_shift[] = {
|
||||
0, 4, 7, 8, 10, 12, 16, 20
|
||||
};
|
||||
unsigned timeout;
|
||||
unsigned dtocyc;
|
||||
unsigned dtomul;
|
||||
|
||||
timeout = ns_to_clocks(host, data->timeout_ns) + data->timeout_clks;
|
||||
|
||||
for (dtomul = 0; dtomul < 8; dtomul++) {
|
||||
unsigned shift = dtomul_to_shift[dtomul];
|
||||
dtocyc = (timeout + (1 << shift) - 1) >> shift;
|
||||
if (dtocyc < 15)
|
||||
break;
|
||||
}
|
||||
|
||||
if (dtomul >= 8) {
|
||||
dtomul = 7;
|
||||
dtocyc = 15;
|
||||
}
|
||||
|
||||
dev_vdbg(&host->mmc->class_dev, "setting timeout to %u cycles\n",
|
||||
dtocyc << dtomul_to_shift[dtomul]);
|
||||
mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Return mask with command flags to be enabled for this command.
|
||||
*/
|
||||
static u32 atmci_prepare_command(struct mmc_host *mmc,
|
||||
struct mmc_command *cmd)
|
||||
{
|
||||
struct mmc_data *data;
|
||||
u32 cmdr;
|
||||
|
||||
cmd->error = -EINPROGRESS;
|
||||
|
||||
cmdr = MCI_CMDR_CMDNB(cmd->opcode);
|
||||
|
||||
if (cmd->flags & MMC_RSP_PRESENT) {
|
||||
if (cmd->flags & MMC_RSP_136)
|
||||
cmdr |= MCI_CMDR_RSPTYP_136BIT;
|
||||
else
|
||||
cmdr |= MCI_CMDR_RSPTYP_48BIT;
|
||||
}
|
||||
|
||||
/*
|
||||
* This should really be MAXLAT_5 for CMD2 and ACMD41, but
|
||||
* it's too difficult to determine whether this is an ACMD or
|
||||
* not. Better make it 64.
|
||||
*/
|
||||
cmdr |= MCI_CMDR_MAXLAT_64CYC;
|
||||
|
||||
if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN)
|
||||
cmdr |= MCI_CMDR_OPDCMD;
|
||||
|
||||
data = cmd->data;
|
||||
if (data) {
|
||||
cmdr |= MCI_CMDR_START_XFER;
|
||||
if (data->flags & MMC_DATA_STREAM)
|
||||
cmdr |= MCI_CMDR_STREAM;
|
||||
else if (data->blocks > 1)
|
||||
cmdr |= MCI_CMDR_MULTI_BLOCK;
|
||||
else
|
||||
cmdr |= MCI_CMDR_BLOCK;
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
cmdr |= MCI_CMDR_TRDIR_READ;
|
||||
}
|
||||
|
||||
return cmdr;
|
||||
}
|
||||
|
||||
static void atmci_start_command(struct atmel_mci *host,
|
||||
struct mmc_command *cmd,
|
||||
u32 cmd_flags)
|
||||
{
|
||||
/* Must read host->cmd after testing event flags */
|
||||
smp_rmb();
|
||||
WARN_ON(host->cmd);
|
||||
host->cmd = cmd;
|
||||
|
||||
dev_vdbg(&host->mmc->class_dev,
|
||||
"start command: ARGR=0x%08x CMDR=0x%08x\n",
|
||||
cmd->arg, cmd_flags);
|
||||
|
||||
mci_writel(host, ARGR, cmd->arg);
|
||||
mci_writel(host, CMDR, cmd_flags);
|
||||
}
|
||||
|
||||
static void send_stop_cmd(struct mmc_host *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
atmci_start_command(host, data->stop, host->stop_cmdr);
|
||||
mci_writel(host, IER, MCI_CMDRDY);
|
||||
}
|
||||
|
||||
static void atmci_request_end(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
WARN_ON(host->cmd || host->data);
|
||||
host->mrq = NULL;
|
||||
|
||||
atmci_disable(host);
|
||||
|
||||
mmc_request_done(mmc, mrq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a mask of interrupt flags to be enabled after the whole
|
||||
* request has been prepared.
|
||||
*/
|
||||
static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
u32 iflags;
|
||||
|
||||
data->error = -EINPROGRESS;
|
||||
|
||||
WARN_ON(host->data);
|
||||
host->sg = NULL;
|
||||
host->data = data;
|
||||
|
||||
mci_writel(host, BLKR, MCI_BCNT(data->blocks)
|
||||
| MCI_BLKLEN(data->blksz));
|
||||
dev_vdbg(&mmc->class_dev, "BLKR=0x%08x\n",
|
||||
MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz));
|
||||
|
||||
iflags = ATMCI_DATA_ERROR_FLAGS;
|
||||
host->sg = data->sg;
|
||||
host->pio_offset = 0;
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
iflags |= MCI_RXRDY;
|
||||
else
|
||||
iflags |= MCI_TXRDY;
|
||||
|
||||
return iflags;
|
||||
}
|
||||
|
||||
static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
struct mmc_data *data;
|
||||
struct mmc_command *cmd;
|
||||
u32 iflags;
|
||||
u32 cmdflags = 0;
|
||||
|
||||
iflags = mci_readl(host, IMR);
|
||||
if (iflags)
|
||||
dev_warn(&mmc->class_dev, "WARNING: IMR=0x%08x\n",
|
||||
mci_readl(host, IMR));
|
||||
|
||||
WARN_ON(host->mrq != NULL);
|
||||
|
||||
/*
|
||||
* We may "know" the card is gone even though there's still an
|
||||
* electrical connection. If so, we really need to communicate
|
||||
* this to the MMC core since there won't be any more
|
||||
* interrupts as the card is completely removed. Otherwise,
|
||||
* the MMC core might believe the card is still there even
|
||||
* though the card was just removed very slowly.
|
||||
*/
|
||||
if (!host->present) {
|
||||
mrq->cmd->error = -ENOMEDIUM;
|
||||
mmc_request_done(mmc, mrq);
|
||||
return;
|
||||
}
|
||||
|
||||
host->mrq = mrq;
|
||||
host->pending_events = 0;
|
||||
host->completed_events = 0;
|
||||
|
||||
atmci_enable(host);
|
||||
|
||||
/* We don't support multiple blocks of weird lengths. */
|
||||
data = mrq->data;
|
||||
if (data) {
|
||||
if (data->blocks > 1 && data->blksz & 3)
|
||||
goto fail;
|
||||
atmci_set_timeout(host, data);
|
||||
}
|
||||
|
||||
iflags = MCI_CMDRDY;
|
||||
cmd = mrq->cmd;
|
||||
cmdflags = atmci_prepare_command(mmc, cmd);
|
||||
atmci_start_command(host, cmd, cmdflags);
|
||||
|
||||
if (data)
|
||||
iflags |= atmci_submit_data(mmc, data);
|
||||
|
||||
if (mrq->stop) {
|
||||
host->stop_cmdr = atmci_prepare_command(mmc, mrq->stop);
|
||||
host->stop_cmdr |= MCI_CMDR_STOP_XFER;
|
||||
if (!(data->flags & MMC_DATA_WRITE))
|
||||
host->stop_cmdr |= MCI_CMDR_TRDIR_READ;
|
||||
if (data->flags & MMC_DATA_STREAM)
|
||||
host->stop_cmdr |= MCI_CMDR_STREAM;
|
||||
else
|
||||
host->stop_cmdr |= MCI_CMDR_MULTI_BLOCK;
|
||||
}
|
||||
|
||||
/*
|
||||
* We could have enabled interrupts earlier, but I suspect
|
||||
* that would open up a nice can of interesting race
|
||||
* conditions (e.g. command and data complete, but stop not
|
||||
* prepared yet.)
|
||||
*/
|
||||
mci_writel(host, IER, iflags);
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
atmci_disable(host);
|
||||
host->mrq = NULL;
|
||||
mrq->cmd->error = -EINVAL;
|
||||
mmc_request_done(mmc, mrq);
|
||||
}
|
||||
|
||||
static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
if (ios->clock) {
|
||||
u32 clkdiv;
|
||||
|
||||
/* Set clock rate */
|
||||
clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * ios->clock) - 1;
|
||||
if (clkdiv > 255) {
|
||||
dev_warn(&mmc->class_dev,
|
||||
"clock %u too slow; using %lu\n",
|
||||
ios->clock, host->bus_hz / (2 * 256));
|
||||
clkdiv = 255;
|
||||
}
|
||||
|
||||
host->mode_reg = MCI_MR_CLKDIV(clkdiv) | MCI_MR_WRPROOF
|
||||
| MCI_MR_RDPROOF;
|
||||
}
|
||||
|
||||
switch (ios->bus_width) {
|
||||
case MMC_BUS_WIDTH_1:
|
||||
host->sdc_reg = 0;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
host->sdc_reg = MCI_SDCBUS_4BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ios->power_mode) {
|
||||
case MMC_POWER_ON:
|
||||
/* Send init sequence (74 clock cycles) */
|
||||
atmci_enable(host);
|
||||
mci_writel(host, CMDR, MCI_CMDR_SPCMD_INIT);
|
||||
while (!(mci_readl(host, SR) & MCI_CMDRDY))
|
||||
cpu_relax();
|
||||
atmci_disable(host);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* TODO: None of the currently available AVR32-based
|
||||
* boards allow MMC power to be turned off. Implement
|
||||
* power control when this can be tested properly.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int atmci_get_ro(struct mmc_host *mmc)
|
||||
{
|
||||
int read_only = 0;
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
if (host->wp_pin >= 0) {
|
||||
read_only = gpio_get_value(host->wp_pin);
|
||||
dev_dbg(&mmc->class_dev, "card is %s\n",
|
||||
read_only ? "read-only" : "read-write");
|
||||
} else {
|
||||
dev_dbg(&mmc->class_dev,
|
||||
"no pin for checking read-only switch."
|
||||
" Assuming write-enable.\n");
|
||||
}
|
||||
|
||||
return read_only;
|
||||
}
|
||||
|
||||
static struct mmc_host_ops atmci_ops = {
|
||||
.request = atmci_request,
|
||||
.set_ios = atmci_set_ios,
|
||||
.get_ro = atmci_get_ro,
|
||||
};
|
||||
|
||||
static void atmci_command_complete(struct atmel_mci *host,
|
||||
struct mmc_command *cmd, u32 status)
|
||||
{
|
||||
/* Read the response from the card (up to 16 bytes) */
|
||||
cmd->resp[0] = mci_readl(host, RSPR);
|
||||
cmd->resp[1] = mci_readl(host, RSPR);
|
||||
cmd->resp[2] = mci_readl(host, RSPR);
|
||||
cmd->resp[3] = mci_readl(host, RSPR);
|
||||
|
||||
if (status & MCI_RTOE)
|
||||
cmd->error = -ETIMEDOUT;
|
||||
else if ((cmd->flags & MMC_RSP_CRC) && (status & MCI_RCRCE))
|
||||
cmd->error = -EILSEQ;
|
||||
else if (status & (MCI_RINDE | MCI_RDIRE | MCI_RENDE))
|
||||
cmd->error = -EIO;
|
||||
else
|
||||
cmd->error = 0;
|
||||
|
||||
if (cmd->error) {
|
||||
dev_dbg(&host->mmc->class_dev,
|
||||
"command error: status=0x%08x\n", status);
|
||||
|
||||
if (cmd->data) {
|
||||
host->data = NULL;
|
||||
mci_writel(host, IDR, MCI_NOTBUSY
|
||||
| MCI_TXRDY | MCI_RXRDY
|
||||
| ATMCI_DATA_ERROR_FLAGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void atmci_detect_change(unsigned long data)
|
||||
{
|
||||
struct atmel_mci *host = (struct atmel_mci *)data;
|
||||
struct mmc_request *mrq = host->mrq;
|
||||
int present;
|
||||
|
||||
/*
|
||||
* atmci_remove() sets detect_pin to -1 before freeing the
|
||||
* interrupt. We must not re-enable the interrupt if it has
|
||||
* been freed.
|
||||
*/
|
||||
smp_rmb();
|
||||
if (host->detect_pin < 0)
|
||||
return;
|
||||
|
||||
enable_irq(gpio_to_irq(host->detect_pin));
|
||||
present = !gpio_get_value(host->detect_pin);
|
||||
|
||||
dev_vdbg(&host->pdev->dev, "detect change: %d (was %d)\n",
|
||||
present, host->present);
|
||||
|
||||
if (present != host->present) {
|
||||
dev_dbg(&host->mmc->class_dev, "card %s\n",
|
||||
present ? "inserted" : "removed");
|
||||
host->present = present;
|
||||
|
||||
/* Reset controller if card is gone */
|
||||
if (!present) {
|
||||
mci_writel(host, CR, MCI_CR_SWRST);
|
||||
mci_writel(host, IDR, ~0UL);
|
||||
mci_writel(host, CR, MCI_CR_MCIEN);
|
||||
}
|
||||
|
||||
/* Clean up queue if present */
|
||||
if (mrq) {
|
||||
/*
|
||||
* Reset controller to terminate any ongoing
|
||||
* commands or data transfers.
|
||||
*/
|
||||
mci_writel(host, CR, MCI_CR_SWRST);
|
||||
|
||||
if (!atmci_is_completed(host, EVENT_CMD_COMPLETE))
|
||||
mrq->cmd->error = -ENOMEDIUM;
|
||||
|
||||
if (mrq->data && !atmci_is_completed(host,
|
||||
EVENT_DATA_COMPLETE)) {
|
||||
host->data = NULL;
|
||||
mrq->data->error = -ENOMEDIUM;
|
||||
}
|
||||
if (mrq->stop && !atmci_is_completed(host,
|
||||
EVENT_STOP_COMPLETE))
|
||||
mrq->stop->error = -ENOMEDIUM;
|
||||
|
||||
host->cmd = NULL;
|
||||
atmci_request_end(host->mmc, mrq);
|
||||
}
|
||||
|
||||
mmc_detect_change(host->mmc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void atmci_tasklet_func(unsigned long priv)
|
||||
{
|
||||
struct mmc_host *mmc = (struct mmc_host *)priv;
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
struct mmc_request *mrq = host->mrq;
|
||||
struct mmc_data *data = host->data;
|
||||
|
||||
dev_vdbg(&mmc->class_dev,
|
||||
"tasklet: pending/completed/mask %lx/%lx/%x\n",
|
||||
host->pending_events, host->completed_events,
|
||||
mci_readl(host, IMR));
|
||||
|
||||
if (atmci_test_and_clear_pending(host, EVENT_CMD_COMPLETE)) {
|
||||
/*
|
||||
* host->cmd must be set to NULL before the interrupt
|
||||
* handler sees EVENT_CMD_COMPLETE
|
||||
*/
|
||||
host->cmd = NULL;
|
||||
smp_wmb();
|
||||
atmci_set_completed(host, EVENT_CMD_COMPLETE);
|
||||
atmci_command_complete(host, mrq->cmd, host->cmd_status);
|
||||
|
||||
if (!mrq->cmd->error && mrq->stop
|
||||
&& atmci_is_completed(host, EVENT_XFER_COMPLETE)
|
||||
&& !atmci_test_and_set_completed(host,
|
||||
EVENT_STOP_SENT))
|
||||
send_stop_cmd(host->mmc, mrq->data);
|
||||
}
|
||||
if (atmci_test_and_clear_pending(host, EVENT_STOP_COMPLETE)) {
|
||||
/*
|
||||
* host->cmd must be set to NULL before the interrupt
|
||||
* handler sees EVENT_STOP_COMPLETE
|
||||
*/
|
||||
host->cmd = NULL;
|
||||
smp_wmb();
|
||||
atmci_set_completed(host, EVENT_STOP_COMPLETE);
|
||||
atmci_command_complete(host, mrq->stop, host->stop_status);
|
||||
}
|
||||
if (atmci_test_and_clear_pending(host, EVENT_DATA_ERROR)) {
|
||||
u32 status = host->data_status;
|
||||
|
||||
dev_vdbg(&mmc->class_dev, "data error: status=%08x\n", status);
|
||||
|
||||
atmci_set_completed(host, EVENT_DATA_ERROR);
|
||||
atmci_set_completed(host, EVENT_DATA_COMPLETE);
|
||||
|
||||
if (status & MCI_DTOE) {
|
||||
dev_dbg(&mmc->class_dev,
|
||||
"data timeout error\n");
|
||||
data->error = -ETIMEDOUT;
|
||||
} else if (status & MCI_DCRCE) {
|
||||
dev_dbg(&mmc->class_dev, "data CRC error\n");
|
||||
data->error = -EILSEQ;
|
||||
} else {
|
||||
dev_dbg(&mmc->class_dev,
|
||||
"data FIFO error (status=%08x)\n",
|
||||
status);
|
||||
data->error = -EIO;
|
||||
}
|
||||
|
||||
if (host->present && data->stop
|
||||
&& atmci_is_completed(host, EVENT_CMD_COMPLETE)
|
||||
&& !atmci_test_and_set_completed(
|
||||
host, EVENT_STOP_SENT))
|
||||
send_stop_cmd(host->mmc, data);
|
||||
|
||||
host->data = NULL;
|
||||
}
|
||||
if (atmci_test_and_clear_pending(host, EVENT_DATA_COMPLETE)) {
|
||||
atmci_set_completed(host, EVENT_DATA_COMPLETE);
|
||||
|
||||
if (!atmci_is_completed(host, EVENT_DATA_ERROR)) {
|
||||
data->bytes_xfered = data->blocks * data->blksz;
|
||||
data->error = 0;
|
||||
}
|
||||
|
||||
host->data = NULL;
|
||||
}
|
||||
|
||||
if (host->mrq && !host->cmd && !host->data)
|
||||
atmci_request_end(mmc, host->mrq);
|
||||
}
|
||||
|
||||
static void atmci_read_data_pio(struct atmel_mci *host)
|
||||
{
|
||||
struct scatterlist *sg = host->sg;
|
||||
void *buf = sg_virt(sg);
|
||||
unsigned int offset = host->pio_offset;
|
||||
struct mmc_data *data = host->data;
|
||||
u32 value;
|
||||
u32 status;
|
||||
unsigned int nbytes = 0;
|
||||
|
||||
do {
|
||||
value = mci_readl(host, RDR);
|
||||
if (likely(offset + 4 <= sg->length)) {
|
||||
put_unaligned(value, (u32 *)(buf + offset));
|
||||
|
||||
offset += 4;
|
||||
nbytes += 4;
|
||||
|
||||
if (offset == sg->length) {
|
||||
host->sg = sg = sg_next(sg);
|
||||
if (!sg)
|
||||
goto done;
|
||||
|
||||
offset = 0;
|
||||
buf = sg_virt(sg);
|
||||
}
|
||||
} else {
|
||||
unsigned int remaining = sg->length - offset;
|
||||
memcpy(buf + offset, &value, remaining);
|
||||
nbytes += remaining;
|
||||
|
||||
flush_dcache_page(sg_page(sg));
|
||||
host->sg = sg = sg_next(sg);
|
||||
if (!sg)
|
||||
goto done;
|
||||
|
||||
offset = 4 - remaining;
|
||||
buf = sg_virt(sg);
|
||||
memcpy(buf, (u8 *)&value + remaining, offset);
|
||||
nbytes += offset;
|
||||
}
|
||||
|
||||
status = mci_readl(host, SR);
|
||||
if (status & ATMCI_DATA_ERROR_FLAGS) {
|
||||
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY
|
||||
| ATMCI_DATA_ERROR_FLAGS));
|
||||
host->data_status = status;
|
||||
atmci_set_pending(host, EVENT_DATA_ERROR);
|
||||
tasklet_schedule(&host->tasklet);
|
||||
break;
|
||||
}
|
||||
} while (status & MCI_RXRDY);
|
||||
|
||||
host->pio_offset = offset;
|
||||
data->bytes_xfered += nbytes;
|
||||
|
||||
return;
|
||||
|
||||
done:
|
||||
mci_writel(host, IDR, MCI_RXRDY);
|
||||
mci_writel(host, IER, MCI_NOTBUSY);
|
||||
data->bytes_xfered += nbytes;
|
||||
atmci_set_completed(host, EVENT_XFER_COMPLETE);
|
||||
if (data->stop && atmci_is_completed(host, EVENT_CMD_COMPLETE)
|
||||
&& !atmci_test_and_set_completed(host, EVENT_STOP_SENT))
|
||||
send_stop_cmd(host->mmc, data);
|
||||
}
|
||||
|
||||
static void atmci_write_data_pio(struct atmel_mci *host)
|
||||
{
|
||||
struct scatterlist *sg = host->sg;
|
||||
void *buf = sg_virt(sg);
|
||||
unsigned int offset = host->pio_offset;
|
||||
struct mmc_data *data = host->data;
|
||||
u32 value;
|
||||
u32 status;
|
||||
unsigned int nbytes = 0;
|
||||
|
||||
do {
|
||||
if (likely(offset + 4 <= sg->length)) {
|
||||
value = get_unaligned((u32 *)(buf + offset));
|
||||
mci_writel(host, TDR, value);
|
||||
|
||||
offset += 4;
|
||||
nbytes += 4;
|
||||
if (offset == sg->length) {
|
||||
host->sg = sg = sg_next(sg);
|
||||
if (!sg)
|
||||
goto done;
|
||||
|
||||
offset = 0;
|
||||
buf = sg_virt(sg);
|
||||
}
|
||||
} else {
|
||||
unsigned int remaining = sg->length - offset;
|
||||
|
||||
value = 0;
|
||||
memcpy(&value, buf + offset, remaining);
|
||||
nbytes += remaining;
|
||||
|
||||
host->sg = sg = sg_next(sg);
|
||||
if (!sg) {
|
||||
mci_writel(host, TDR, value);
|
||||
goto done;
|
||||
}
|
||||
|
||||
offset = 4 - remaining;
|
||||
buf = sg_virt(sg);
|
||||
memcpy((u8 *)&value + remaining, buf, offset);
|
||||
mci_writel(host, TDR, value);
|
||||
nbytes += offset;
|
||||
}
|
||||
|
||||
status = mci_readl(host, SR);
|
||||
if (status & ATMCI_DATA_ERROR_FLAGS) {
|
||||
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY
|
||||
| ATMCI_DATA_ERROR_FLAGS));
|
||||
host->data_status = status;
|
||||
atmci_set_pending(host, EVENT_DATA_ERROR);
|
||||
tasklet_schedule(&host->tasklet);
|
||||
break;
|
||||
}
|
||||
} while (status & MCI_TXRDY);
|
||||
|
||||
host->pio_offset = offset;
|
||||
data->bytes_xfered += nbytes;
|
||||
|
||||
return;
|
||||
|
||||
done:
|
||||
mci_writel(host, IDR, MCI_TXRDY);
|
||||
mci_writel(host, IER, MCI_NOTBUSY);
|
||||
data->bytes_xfered += nbytes;
|
||||
atmci_set_completed(host, EVENT_XFER_COMPLETE);
|
||||
if (data->stop && atmci_is_completed(host, EVENT_CMD_COMPLETE)
|
||||
&& !atmci_test_and_set_completed(host, EVENT_STOP_SENT))
|
||||
send_stop_cmd(host->mmc, data);
|
||||
}
|
||||
|
||||
static void atmci_cmd_interrupt(struct mmc_host *mmc, u32 status)
|
||||
{
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
mci_writel(host, IDR, MCI_CMDRDY);
|
||||
|
||||
if (atmci_is_completed(host, EVENT_STOP_SENT)) {
|
||||
host->stop_status = status;
|
||||
atmci_set_pending(host, EVENT_STOP_COMPLETE);
|
||||
} else {
|
||||
host->cmd_status = status;
|
||||
atmci_set_pending(host, EVENT_CMD_COMPLETE);
|
||||
}
|
||||
|
||||
tasklet_schedule(&host->tasklet);
|
||||
}
|
||||
|
||||
static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct mmc_host *mmc = dev_id;
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
u32 status, mask, pending;
|
||||
unsigned int pass_count = 0;
|
||||
|
||||
spin_lock(&mmc->lock);
|
||||
|
||||
do {
|
||||
status = mci_readl(host, SR);
|
||||
mask = mci_readl(host, IMR);
|
||||
pending = status & mask;
|
||||
if (!pending)
|
||||
break;
|
||||
|
||||
if (pending & ATMCI_DATA_ERROR_FLAGS) {
|
||||
mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS
|
||||
| MCI_RXRDY | MCI_TXRDY);
|
||||
pending &= mci_readl(host, IMR);
|
||||
host->data_status = status;
|
||||
atmci_set_pending(host, EVENT_DATA_ERROR);
|
||||
tasklet_schedule(&host->tasklet);
|
||||
}
|
||||
if (pending & MCI_NOTBUSY) {
|
||||
mci_writel(host, IDR, (MCI_NOTBUSY
|
||||
| ATMCI_DATA_ERROR_FLAGS));
|
||||
atmci_set_pending(host, EVENT_DATA_COMPLETE);
|
||||
tasklet_schedule(&host->tasklet);
|
||||
}
|
||||
if (pending & MCI_RXRDY)
|
||||
atmci_read_data_pio(host);
|
||||
if (pending & MCI_TXRDY)
|
||||
atmci_write_data_pio(host);
|
||||
|
||||
if (pending & MCI_CMDRDY)
|
||||
atmci_cmd_interrupt(mmc, status);
|
||||
} while (pass_count++ < 5);
|
||||
|
||||
spin_unlock(&mmc->lock);
|
||||
|
||||
return pass_count ? IRQ_HANDLED : IRQ_NONE;
|
||||
}
|
||||
|
||||
static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct mmc_host *mmc = dev_id;
|
||||
struct atmel_mci *host = mmc_priv(mmc);
|
||||
|
||||
/*
|
||||
* Disable interrupts until the pin has stabilized and check
|
||||
* the state then. Use mod_timer() since we may be in the
|
||||
* middle of the timer routine when this interrupt triggers.
|
||||
*/
|
||||
disable_irq_nosync(irq);
|
||||
mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(20));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int __init atmci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mci_platform_data *pdata;
|
||||
struct atmel_mci *host;
|
||||
struct mmc_host *mmc;
|
||||
struct resource *regs;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs)
|
||||
return -ENXIO;
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (!pdata)
|
||||
return -ENXIO;
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
mmc = mmc_alloc_host(sizeof(struct atmel_mci), &pdev->dev);
|
||||
if (!mmc)
|
||||
return -ENOMEM;
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->pdev = pdev;
|
||||
host->mmc = mmc;
|
||||
host->detect_pin = pdata->detect_pin;
|
||||
host->wp_pin = pdata->wp_pin;
|
||||
|
||||
host->mck = clk_get(&pdev->dev, "mci_clk");
|
||||
if (IS_ERR(host->mck)) {
|
||||
ret = PTR_ERR(host->mck);
|
||||
goto err_clk_get;
|
||||
}
|
||||
|
||||
ret = -ENOMEM;
|
||||
host->regs = ioremap(regs->start, regs->end - regs->start + 1);
|
||||
if (!host->regs)
|
||||
goto err_ioremap;
|
||||
|
||||
clk_enable(host->mck);
|
||||
mci_writel(host, CR, MCI_CR_SWRST);
|
||||
host->bus_hz = clk_get_rate(host->mck);
|
||||
clk_disable(host->mck);
|
||||
|
||||
host->mapbase = regs->start;
|
||||
|
||||
mmc->ops = &atmci_ops;
|
||||
mmc->f_min = (host->bus_hz + 511) / 512;
|
||||
mmc->f_max = host->bus_hz / 2;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
|
||||
mmc->max_hw_segs = 64;
|
||||
mmc->max_phys_segs = 64;
|
||||
mmc->max_req_size = 32768 * 512;
|
||||
mmc->max_blk_size = 32768;
|
||||
mmc->max_blk_count = 512;
|
||||
|
||||
tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)mmc);
|
||||
|
||||
ret = request_irq(irq, atmci_interrupt, 0, pdev->dev.bus_id, mmc);
|
||||
if (ret)
|
||||
goto err_request_irq;
|
||||
|
||||
/* Assume card is present if we don't have a detect pin */
|
||||
host->present = 1;
|
||||
if (host->detect_pin >= 0) {
|
||||
if (gpio_request(host->detect_pin, "mmc_detect")) {
|
||||
dev_dbg(&mmc->class_dev, "no detect pin available\n");
|
||||
host->detect_pin = -1;
|
||||
} else {
|
||||
host->present = !gpio_get_value(host->detect_pin);
|
||||
}
|
||||
}
|
||||
if (host->wp_pin >= 0) {
|
||||
if (gpio_request(host->wp_pin, "mmc_wp")) {
|
||||
dev_dbg(&mmc->class_dev, "no WP pin available\n");
|
||||
host->wp_pin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, host);
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
if (host->detect_pin >= 0) {
|
||||
setup_timer(&host->detect_timer, atmci_detect_change,
|
||||
(unsigned long)host);
|
||||
|
||||
ret = request_irq(gpio_to_irq(host->detect_pin),
|
||||
atmci_detect_interrupt,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
||||
"mmc-detect", mmc);
|
||||
if (ret) {
|
||||
dev_dbg(&mmc->class_dev,
|
||||
"could not request IRQ %d for detect pin\n",
|
||||
gpio_to_irq(host->detect_pin));
|
||||
gpio_free(host->detect_pin);
|
||||
host->detect_pin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(&mmc->class_dev,
|
||||
"Atmel MCI controller at 0x%08lx irq %d\n",
|
||||
host->mapbase, irq);
|
||||
|
||||
return 0;
|
||||
|
||||
err_request_irq:
|
||||
iounmap(host->regs);
|
||||
err_ioremap:
|
||||
clk_put(host->mck);
|
||||
err_clk_get:
|
||||
mmc_free_host(mmc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit atmci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct atmel_mci *host = platform_get_drvdata(pdev);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
if (host) {
|
||||
if (host->detect_pin >= 0) {
|
||||
int pin = host->detect_pin;
|
||||
|
||||
/* Make sure the timer doesn't enable the interrupt */
|
||||
host->detect_pin = -1;
|
||||
smp_wmb();
|
||||
|
||||
free_irq(gpio_to_irq(pin), host->mmc);
|
||||
del_timer_sync(&host->detect_timer);
|
||||
gpio_free(pin);
|
||||
}
|
||||
|
||||
mmc_remove_host(host->mmc);
|
||||
|
||||
clk_enable(host->mck);
|
||||
mci_writel(host, IDR, ~0UL);
|
||||
mci_writel(host, CR, MCI_CR_MCIDIS);
|
||||
mci_readl(host, SR);
|
||||
clk_disable(host->mck);
|
||||
|
||||
if (host->wp_pin >= 0)
|
||||
gpio_free(host->wp_pin);
|
||||
|
||||
free_irq(platform_get_irq(pdev, 0), host->mmc);
|
||||
iounmap(host->regs);
|
||||
|
||||
clk_put(host->mck);
|
||||
|
||||
mmc_free_host(host->mmc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver atmci_driver = {
|
||||
.remove = __exit_p(atmci_remove),
|
||||
.driver = {
|
||||
.name = "atmel_mci",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init atmci_init(void)
|
||||
{
|
||||
return platform_driver_probe(&atmci_driver, atmci_probe);
|
||||
}
|
||||
|
||||
static void __exit atmci_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&atmci_driver);
|
||||
}
|
||||
|
||||
module_init(atmci_init);
|
||||
module_exit(atmci_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver");
|
||||
MODULE_AUTHOR("Haavard Skinnemoen <haavard.skinnemoen@atmel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
File diff suppressed because it is too large
Load Diff
@ -1,96 +0,0 @@
|
||||
#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
|
@ -892,9 +892,12 @@ static int imxmci_get_ro(struct mmc_host *mmc)
|
||||
struct imxmci_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;
|
||||
return !!host->pdata->get_ro(mmc_dev(mmc));
|
||||
/*
|
||||
* Board doesn't support read only detection; let the mmc core
|
||||
* decide what to do.
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1126,16 +1126,28 @@ static int mmc_spi_get_ro(struct mmc_host *mmc)
|
||||
struct mmc_spi_host *host = mmc_priv(mmc);
|
||||
|
||||
if (host->pdata && host->pdata->get_ro)
|
||||
return host->pdata->get_ro(mmc->parent);
|
||||
/* board doesn't support read only detection; assume writeable */
|
||||
return 0;
|
||||
return !!host->pdata->get_ro(mmc->parent);
|
||||
/*
|
||||
* Board doesn't support read only detection; let the mmc core
|
||||
* decide what to do.
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int mmc_spi_get_cd(struct mmc_host *mmc)
|
||||
{
|
||||
struct mmc_spi_host *host = mmc_priv(mmc);
|
||||
|
||||
if (host->pdata && host->pdata->get_cd)
|
||||
return !!host->pdata->get_cd(mmc->parent);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops mmc_spi_ops = {
|
||||
.request = mmc_spi_request,
|
||||
.set_ios = mmc_spi_set_ios,
|
||||
.get_ro = mmc_spi_get_ro,
|
||||
.get_cd = mmc_spi_get_cd,
|
||||
};
|
||||
|
||||
|
||||
@ -1240,10 +1252,7 @@ static int mmc_spi_probe(struct spi_device *spi)
|
||||
mmc->ops = &mmc_spi_ops;
|
||||
mmc->max_blk_size = MMC_SPI_BLOCKSIZE;
|
||||
|
||||
/* As long as we keep track of the number of successfully
|
||||
* transmitted blocks, we're good for multiwrite.
|
||||
*/
|
||||
mmc->caps = MMC_CAP_SPI | MMC_CAP_MULTIWRITE;
|
||||
mmc->caps = MMC_CAP_SPI;
|
||||
|
||||
/* SPI doesn't need the lowspeed device identification thing for
|
||||
* MMC or SD cards, since it never comes up in open drain mode.
|
||||
@ -1319,17 +1328,23 @@ static int mmc_spi_probe(struct spi_device *spi)
|
||||
goto fail_glue_init;
|
||||
}
|
||||
|
||||
/* pass platform capabilities, if any */
|
||||
if (host->pdata)
|
||||
mmc->caps |= host->pdata->caps;
|
||||
|
||||
status = mmc_add_host(mmc);
|
||||
if (status != 0)
|
||||
goto fail_add_host;
|
||||
|
||||
dev_info(&spi->dev, "SD/MMC host %s%s%s%s\n",
|
||||
dev_info(&spi->dev, "SD/MMC host %s%s%s%s%s\n",
|
||||
mmc->class_dev.bus_id,
|
||||
host->dma_dev ? "" : ", no DMA",
|
||||
(host->pdata && host->pdata->get_ro)
|
||||
? "" : ", no WP",
|
||||
(host->pdata && host->pdata->setpower)
|
||||
? "" : ", no poweroff");
|
||||
? "" : ", no poweroff",
|
||||
(mmc->caps & MMC_CAP_NEEDS_POLL)
|
||||
? ", cd polling" : "");
|
||||
return 0;
|
||||
|
||||
fail_add_host:
|
||||
|
@ -535,7 +535,6 @@ static int mmci_probe(struct amba_device *dev, void *id)
|
||||
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
|
||||
|
@ -1317,7 +1317,7 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id)
|
||||
|
||||
host->slots[id] = slot;
|
||||
|
||||
mmc->caps = MMC_CAP_MULTIWRITE;
|
||||
mmc->caps = 0;
|
||||
if (host->pdata->conf.wire4)
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
|
||||
|
@ -374,9 +374,12 @@ 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;
|
||||
return !!host->pdata->get_ro(mmc_dev(mmc));
|
||||
/*
|
||||
* Board doesn't support read only detection; let the mmc core
|
||||
* decide what to do.
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
|
1446
drivers/mmc/host/s3cmci.c
Normal file
1446
drivers/mmc/host/s3cmci.c
Normal file
File diff suppressed because it is too large
Load Diff
70
drivers/mmc/host/s3cmci.h
Normal file
70
drivers/mmc/host/s3cmci.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver
|
||||
*
|
||||
* Copyright (C) 2004-2006 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.
|
||||
*/
|
||||
|
||||
/* FIXME: DMA Resource management ?! */
|
||||
#define S3CMCI_DMA 0
|
||||
|
||||
enum s3cmci_waitfor {
|
||||
COMPLETION_NONE,
|
||||
COMPLETION_FINALIZE,
|
||||
COMPLETION_CMDSENT,
|
||||
COMPLETION_RSPFIN,
|
||||
COMPLETION_XFERFINISH,
|
||||
COMPLETION_XFERFINISH_RSPFIN,
|
||||
};
|
||||
|
||||
struct s3cmci_host {
|
||||
struct platform_device *pdev;
|
||||
struct s3c24xx_mci_pdata *pdata;
|
||||
struct mmc_host *mmc;
|
||||
struct resource *mem;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
int irq_cd;
|
||||
int dma;
|
||||
|
||||
unsigned long clk_rate;
|
||||
unsigned long clk_div;
|
||||
unsigned long real_rate;
|
||||
u8 prescaler;
|
||||
|
||||
int is2440;
|
||||
unsigned sdiimsk;
|
||||
unsigned sdidata;
|
||||
int dodma;
|
||||
int dmatogo;
|
||||
|
||||
struct mmc_request *mrq;
|
||||
int cmd_is_stop;
|
||||
|
||||
spinlock_t complete_lock;
|
||||
enum s3cmci_waitfor complete_what;
|
||||
|
||||
int dma_complete;
|
||||
|
||||
u32 pio_sgptr;
|
||||
u32 pio_words;
|
||||
u32 pio_count;
|
||||
u32 *pio_ptr;
|
||||
#define XFER_NONE 0
|
||||
#define XFER_READ 1
|
||||
#define XFER_WRITE 2
|
||||
u32 pio_active;
|
||||
|
||||
int bus_width;
|
||||
|
||||
char dbgmsg_cmd[301];
|
||||
char dbgmsg_dat[301];
|
||||
char *status;
|
||||
|
||||
unsigned int ccnt, dcnt;
|
||||
struct tasklet_struct pio_tasklet;
|
||||
};
|
732
drivers/mmc/host/sdhci-pci.c
Normal file
732
drivers/mmc/host/sdhci-pci.c
Normal file
@ -0,0 +1,732 @@
|
||||
/* linux/drivers/mmc/host/sdhci-pci.c - SDHCI on PCI bus interface
|
||||
*
|
||||
* Copyright (C) 2005-2008 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.
|
||||
*
|
||||
* Thanks to the following companies for their support:
|
||||
*
|
||||
* - JMicron (hardware and technical support)
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include <asm/scatterlist.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "sdhci.h"
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
||||
#define MAX_SLOTS 8
|
||||
|
||||
struct sdhci_pci_chip;
|
||||
struct sdhci_pci_slot;
|
||||
|
||||
struct sdhci_pci_fixes {
|
||||
unsigned int quirks;
|
||||
|
||||
int (*probe)(struct sdhci_pci_chip*);
|
||||
|
||||
int (*probe_slot)(struct sdhci_pci_slot*);
|
||||
void (*remove_slot)(struct sdhci_pci_slot*, int);
|
||||
|
||||
int (*suspend)(struct sdhci_pci_chip*,
|
||||
pm_message_t);
|
||||
int (*resume)(struct sdhci_pci_chip*);
|
||||
};
|
||||
|
||||
struct sdhci_pci_slot {
|
||||
struct sdhci_pci_chip *chip;
|
||||
struct sdhci_host *host;
|
||||
|
||||
int pci_bar;
|
||||
};
|
||||
|
||||
struct sdhci_pci_chip {
|
||||
struct pci_dev *pdev;
|
||||
|
||||
unsigned int quirks;
|
||||
const struct sdhci_pci_fixes *fixes;
|
||||
|
||||
int num_slots; /* Slots on controller */
|
||||
struct sdhci_pci_slot *slots[MAX_SLOTS]; /* Pointers to host slots */
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Hardware specific quirk handling *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static int ricoh_probe(struct sdhci_pci_chip *chip)
|
||||
{
|
||||
if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_IBM)
|
||||
chip->quirks |= SDHCI_QUIRK_CLOCK_BEFORE_RESET;
|
||||
|
||||
if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG)
|
||||
chip->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sdhci_pci_fixes sdhci_ricoh = {
|
||||
.probe = ricoh_probe,
|
||||
.quirks = SDHCI_QUIRK_32BIT_DMA_ADDR,
|
||||
};
|
||||
|
||||
static const struct sdhci_pci_fixes sdhci_ene_712 = {
|
||||
.quirks = SDHCI_QUIRK_SINGLE_POWER_WRITE |
|
||||
SDHCI_QUIRK_BROKEN_DMA,
|
||||
};
|
||||
|
||||
static const struct sdhci_pci_fixes sdhci_ene_714 = {
|
||||
.quirks = SDHCI_QUIRK_SINGLE_POWER_WRITE |
|
||||
SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS |
|
||||
SDHCI_QUIRK_BROKEN_DMA,
|
||||
};
|
||||
|
||||
static const struct sdhci_pci_fixes sdhci_cafe = {
|
||||
.quirks = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
|
||||
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
|
||||
};
|
||||
|
||||
static int jmicron_pmos(struct sdhci_pci_chip *chip, int on)
|
||||
{
|
||||
u8 scratch;
|
||||
int ret;
|
||||
|
||||
ret = pci_read_config_byte(chip->pdev, 0xAE, &scratch);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Turn PMOS on [bit 0], set over current detection to 2.4 V
|
||||
* [bit 1:2] and enable over current debouncing [bit 6].
|
||||
*/
|
||||
if (on)
|
||||
scratch |= 0x47;
|
||||
else
|
||||
scratch &= ~0x47;
|
||||
|
||||
ret = pci_write_config_byte(chip->pdev, 0xAE, scratch);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jmicron_probe(struct sdhci_pci_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (chip->pdev->revision == 0) {
|
||||
chip->quirks |= SDHCI_QUIRK_32BIT_DMA_ADDR |
|
||||
SDHCI_QUIRK_32BIT_DMA_SIZE |
|
||||
SDHCI_QUIRK_32BIT_ADMA_SIZE |
|
||||
SDHCI_QUIRK_RESET_AFTER_REQUEST;
|
||||
}
|
||||
|
||||
/*
|
||||
* JMicron chips can have two interfaces to the same hardware
|
||||
* in order to work around limitations in Microsoft's driver.
|
||||
* We need to make sure we only bind to one of them.
|
||||
*
|
||||
* This code assumes two things:
|
||||
*
|
||||
* 1. The PCI code adds subfunctions in order.
|
||||
*
|
||||
* 2. The MMC interface has a lower subfunction number
|
||||
* than the SD interface.
|
||||
*/
|
||||
if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) {
|
||||
struct pci_dev *sd_dev;
|
||||
|
||||
sd_dev = NULL;
|
||||
while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON,
|
||||
PCI_DEVICE_ID_JMICRON_JMB38X_MMC, sd_dev)) != NULL) {
|
||||
if ((PCI_SLOT(chip->pdev->devfn) ==
|
||||
PCI_SLOT(sd_dev->devfn)) &&
|
||||
(chip->pdev->bus == sd_dev->bus))
|
||||
break;
|
||||
}
|
||||
|
||||
if (sd_dev) {
|
||||
pci_dev_put(sd_dev);
|
||||
dev_info(&chip->pdev->dev, "Refusing to bind to "
|
||||
"secondary interface.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* JMicron chips need a bit of a nudge to enable the power
|
||||
* output pins.
|
||||
*/
|
||||
ret = jmicron_pmos(chip, 1);
|
||||
if (ret) {
|
||||
dev_err(&chip->pdev->dev, "Failure enabling card power\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jmicron_enable_mmc(struct sdhci_host *host, int on)
|
||||
{
|
||||
u8 scratch;
|
||||
|
||||
scratch = readb(host->ioaddr + 0xC0);
|
||||
|
||||
if (on)
|
||||
scratch |= 0x01;
|
||||
else
|
||||
scratch &= ~0x01;
|
||||
|
||||
writeb(scratch, host->ioaddr + 0xC0);
|
||||
}
|
||||
|
||||
static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
|
||||
{
|
||||
if (slot->chip->pdev->revision == 0) {
|
||||
u16 version;
|
||||
|
||||
version = readl(slot->host->ioaddr + SDHCI_HOST_VERSION);
|
||||
version = (version & SDHCI_VENDOR_VER_MASK) >>
|
||||
SDHCI_VENDOR_VER_SHIFT;
|
||||
|
||||
/*
|
||||
* Older versions of the chip have lots of nasty glitches
|
||||
* in the ADMA engine. It's best just to avoid it
|
||||
* completely.
|
||||
*/
|
||||
if (version < 0xAC)
|
||||
slot->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
|
||||
}
|
||||
|
||||
/*
|
||||
* The secondary interface requires a bit set to get the
|
||||
* interrupts.
|
||||
*/
|
||||
if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
|
||||
jmicron_enable_mmc(slot->host, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)
|
||||
{
|
||||
if (dead)
|
||||
return;
|
||||
|
||||
if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
|
||||
jmicron_enable_mmc(slot->host, 0);
|
||||
}
|
||||
|
||||
static int jmicron_suspend(struct sdhci_pci_chip *chip, pm_message_t state)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
|
||||
for (i = 0;i < chip->num_slots;i++)
|
||||
jmicron_enable_mmc(chip->slots[i]->host, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jmicron_resume(struct sdhci_pci_chip *chip)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
|
||||
for (i = 0;i < chip->num_slots;i++)
|
||||
jmicron_enable_mmc(chip->slots[i]->host, 1);
|
||||
}
|
||||
|
||||
ret = jmicron_pmos(chip, 1);
|
||||
if (ret) {
|
||||
dev_err(&chip->pdev->dev, "Failure enabling card power\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sdhci_pci_fixes sdhci_jmicron = {
|
||||
.probe = jmicron_probe,
|
||||
|
||||
.probe_slot = jmicron_probe_slot,
|
||||
.remove_slot = jmicron_remove_slot,
|
||||
|
||||
.suspend = jmicron_suspend,
|
||||
.resume = jmicron_resume,
|
||||
};
|
||||
|
||||
static const struct pci_device_id pci_ids[] __devinitdata = {
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_RICOH,
|
||||
.device = PCI_DEVICE_ID_RICOH_R5C822,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_ricoh,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_ENE,
|
||||
.device = PCI_DEVICE_ID_ENE_CB712_SD,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_ene_712,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_ENE,
|
||||
.device = PCI_DEVICE_ID_ENE_CB712_SD_2,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_ene_712,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_ENE,
|
||||
.device = PCI_DEVICE_ID_ENE_CB714_SD,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_ene_714,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_ENE,
|
||||
.device = PCI_DEVICE_ID_ENE_CB714_SD_2,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_ene_714,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_MARVELL,
|
||||
.device = PCI_DEVICE_ID_MARVELL_CAFE_SD,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_cafe,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_JMICRON,
|
||||
.device = PCI_DEVICE_ID_JMICRON_JMB38X_SD,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_jmicron,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_JMICRON,
|
||||
.device = PCI_DEVICE_ID_JMICRON_JMB38X_MMC,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = (kernel_ulong_t)&sdhci_jmicron,
|
||||
},
|
||||
|
||||
{ /* Generic SD host controller */
|
||||
PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
|
||||
},
|
||||
|
||||
{ /* end: all zeroes */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, pci_ids);
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* SDHCI core callbacks *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static int sdhci_pci_enable_dma(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pci_slot *slot;
|
||||
struct pci_dev *pdev;
|
||||
int ret;
|
||||
|
||||
slot = sdhci_priv(host);
|
||||
pdev = slot->chip->pdev;
|
||||
|
||||
if (((pdev->class & 0xFFFF00) == (PCI_CLASS_SYSTEM_SDHCI << 8)) &&
|
||||
((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) &&
|
||||
(host->flags & SDHCI_USE_DMA)) {
|
||||
dev_warn(&pdev->dev, "Will use DMA mode even though HW "
|
||||
"doesn't fully claim to support it.\n");
|
||||
}
|
||||
|
||||
ret = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sdhci_ops sdhci_pci_ops = {
|
||||
.enable_dma = sdhci_pci_enable_dma,
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Suspend/resume *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state)
|
||||
{
|
||||
struct sdhci_pci_chip *chip;
|
||||
struct sdhci_pci_slot *slot;
|
||||
int i, ret;
|
||||
|
||||
chip = pci_get_drvdata(pdev);
|
||||
if (!chip)
|
||||
return 0;
|
||||
|
||||
for (i = 0;i < chip->num_slots;i++) {
|
||||
slot = chip->slots[i];
|
||||
if (!slot)
|
||||
continue;
|
||||
|
||||
ret = sdhci_suspend_host(slot->host, state);
|
||||
|
||||
if (ret) {
|
||||
for (i--;i >= 0;i--)
|
||||
sdhci_resume_host(chip->slots[i]->host);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (chip->fixes && chip->fixes->suspend) {
|
||||
ret = chip->fixes->suspend(chip, state);
|
||||
if (ret) {
|
||||
for (i = chip->num_slots - 1;i >= 0;i--)
|
||||
sdhci_resume_host(chip->slots[i]->host);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pci_save_state(pdev);
|
||||
pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
|
||||
pci_disable_device(pdev);
|
||||
pci_set_power_state(pdev, pci_choose_state(pdev, state));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdhci_pci_resume (struct pci_dev *pdev)
|
||||
{
|
||||
struct sdhci_pci_chip *chip;
|
||||
struct sdhci_pci_slot *slot;
|
||||
int i, ret;
|
||||
|
||||
chip = pci_get_drvdata(pdev);
|
||||
if (!chip)
|
||||
return 0;
|
||||
|
||||
pci_set_power_state(pdev, PCI_D0);
|
||||
pci_restore_state(pdev);
|
||||
ret = pci_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (chip->fixes && chip->fixes->resume) {
|
||||
ret = chip->fixes->resume(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0;i < chip->num_slots;i++) {
|
||||
slot = chip->slots[i];
|
||||
if (!slot)
|
||||
continue;
|
||||
|
||||
ret = sdhci_resume_host(slot->host);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* CONFIG_PM */
|
||||
|
||||
#define sdhci_pci_suspend NULL
|
||||
#define sdhci_pci_resume NULL
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Device probing/removal *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
|
||||
struct pci_dev *pdev, struct sdhci_pci_chip *chip, int bar)
|
||||
{
|
||||
struct sdhci_pci_slot *slot;
|
||||
struct sdhci_host *host;
|
||||
|
||||
resource_size_t addr;
|
||||
|
||||
int ret;
|
||||
|
||||
if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
|
||||
dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
if (pci_resource_len(pdev, bar) != 0x100) {
|
||||
dev_err(&pdev->dev, "Invalid iomem size. You may "
|
||||
"experience problems.\n");
|
||||
}
|
||||
|
||||
if ((pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
|
||||
dev_err(&pdev->dev, "Vendor specific interface. Aborting.\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
if ((pdev->class & 0x0000FF) > PCI_SDHCI_IFVENDOR) {
|
||||
dev_err(&pdev->dev, "Unknown interface. Aborting.\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pci_slot));
|
||||
if (IS_ERR(host)) {
|
||||
ret = PTR_ERR(host);
|
||||
goto unmap;
|
||||
}
|
||||
|
||||
slot = sdhci_priv(host);
|
||||
|
||||
slot->chip = chip;
|
||||
slot->host = host;
|
||||
slot->pci_bar = bar;
|
||||
|
||||
host->hw_name = "PCI";
|
||||
host->ops = &sdhci_pci_ops;
|
||||
host->quirks = chip->quirks;
|
||||
|
||||
host->irq = pdev->irq;
|
||||
|
||||
ret = pci_request_region(pdev, bar, mmc_hostname(host->mmc));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot request region\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
addr = pci_resource_start(pdev, bar);
|
||||
host->ioaddr = ioremap_nocache(addr, pci_resource_len(pdev, bar));
|
||||
if (!host->ioaddr) {
|
||||
dev_err(&pdev->dev, "failed to remap registers\n");
|
||||
goto release;
|
||||
}
|
||||
|
||||
if (chip->fixes && chip->fixes->probe_slot) {
|
||||
ret = chip->fixes->probe_slot(slot);
|
||||
if (ret)
|
||||
goto unmap;
|
||||
}
|
||||
|
||||
ret = sdhci_add_host(host);
|
||||
if (ret)
|
||||
goto remove;
|
||||
|
||||
return slot;
|
||||
|
||||
remove:
|
||||
if (chip->fixes && chip->fixes->remove_slot)
|
||||
chip->fixes->remove_slot(slot, 0);
|
||||
|
||||
unmap:
|
||||
iounmap(host->ioaddr);
|
||||
|
||||
release:
|
||||
pci_release_region(pdev, bar);
|
||||
sdhci_free_host(host);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
|
||||
{
|
||||
int dead;
|
||||
u32 scratch;
|
||||
|
||||
dead = 0;
|
||||
scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);
|
||||
if (scratch == (u32)-1)
|
||||
dead = 1;
|
||||
|
||||
sdhci_remove_host(slot->host, dead);
|
||||
|
||||
if (slot->chip->fixes && slot->chip->fixes->remove_slot)
|
||||
slot->chip->fixes->remove_slot(slot, dead);
|
||||
|
||||
pci_release_region(slot->chip->pdev, slot->pci_bar);
|
||||
|
||||
sdhci_free_host(slot->host);
|
||||
}
|
||||
|
||||
static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
struct sdhci_pci_chip *chip;
|
||||
struct sdhci_pci_slot *slot;
|
||||
|
||||
u8 slots, rev, first_bar;
|
||||
int ret, i;
|
||||
|
||||
BUG_ON(pdev == NULL);
|
||||
BUG_ON(ent == NULL);
|
||||
|
||||
pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
|
||||
|
||||
dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n",
|
||||
(int)pdev->vendor, (int)pdev->device, (int)rev);
|
||||
|
||||
ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
slots = PCI_SLOT_INFO_SLOTS(slots) + 1;
|
||||
dev_dbg(&pdev->dev, "found %d slot(s)\n", slots);
|
||||
if (slots == 0)
|
||||
return -ENODEV;
|
||||
|
||||
BUG_ON(slots > MAX_SLOTS);
|
||||
|
||||
ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK;
|
||||
|
||||
if (first_bar > 5) {
|
||||
dev_err(&pdev->dev, "Invalid first BAR. Aborting.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = pci_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chip = kzalloc(sizeof(struct sdhci_pci_chip), GFP_KERNEL);
|
||||
if (!chip) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
chip->pdev = pdev;
|
||||
chip->fixes = (const struct sdhci_pci_fixes*)ent->driver_data;
|
||||
if (chip->fixes)
|
||||
chip->quirks = chip->fixes->quirks;
|
||||
chip->num_slots = slots;
|
||||
|
||||
pci_set_drvdata(pdev, chip);
|
||||
|
||||
if (chip->fixes && chip->fixes->probe) {
|
||||
ret = chip->fixes->probe(chip);
|
||||
if (ret)
|
||||
goto free;
|
||||
}
|
||||
|
||||
for (i = 0;i < slots;i++) {
|
||||
slot = sdhci_pci_probe_slot(pdev, chip, first_bar + i);
|
||||
if (IS_ERR(slot)) {
|
||||
for (i--;i >= 0;i--)
|
||||
sdhci_pci_remove_slot(chip->slots[i]);
|
||||
ret = PTR_ERR(slot);
|
||||
goto free;
|
||||
}
|
||||
|
||||
chip->slots[i] = slot;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free:
|
||||
pci_set_drvdata(pdev, NULL);
|
||||
kfree(chip);
|
||||
|
||||
err:
|
||||
pci_disable_device(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __devexit sdhci_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
struct sdhci_pci_chip *chip;
|
||||
|
||||
chip = pci_get_drvdata(pdev);
|
||||
|
||||
if (chip) {
|
||||
for (i = 0;i < chip->num_slots; i++)
|
||||
sdhci_pci_remove_slot(chip->slots[i]);
|
||||
|
||||
pci_set_drvdata(pdev, NULL);
|
||||
kfree(chip);
|
||||
}
|
||||
|
||||
pci_disable_device(pdev);
|
||||
}
|
||||
|
||||
static struct pci_driver sdhci_driver = {
|
||||
.name = "sdhci-pci",
|
||||
.id_table = pci_ids,
|
||||
.probe = sdhci_pci_probe,
|
||||
.remove = __devexit_p(sdhci_pci_remove),
|
||||
.suspend = sdhci_pci_suspend,
|
||||
.resume = sdhci_pci_resume,
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Driver init/exit *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static int __init sdhci_drv_init(void)
|
||||
{
|
||||
return pci_register_driver(&sdhci_driver);
|
||||
}
|
||||
|
||||
static void __exit sdhci_drv_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&sdhci_driver);
|
||||
}
|
||||
|
||||
module_init(sdhci_drv_init);
|
||||
module_exit(sdhci_drv_exit);
|
||||
|
||||
MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
|
||||
MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver");
|
||||
MODULE_LICENSE("GPL");
|
File diff suppressed because it is too large
Load Diff
@ -9,18 +9,6 @@
|
||||
* 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
|
||||
*/
|
||||
@ -72,6 +60,11 @@
|
||||
#define SDHCI_CTRL_LED 0x01
|
||||
#define SDHCI_CTRL_4BITBUS 0x02
|
||||
#define SDHCI_CTRL_HISPD 0x04
|
||||
#define SDHCI_CTRL_DMA_MASK 0x18
|
||||
#define SDHCI_CTRL_SDMA 0x00
|
||||
#define SDHCI_CTRL_ADMA1 0x08
|
||||
#define SDHCI_CTRL_ADMA32 0x10
|
||||
#define SDHCI_CTRL_ADMA64 0x18
|
||||
|
||||
#define SDHCI_POWER_CONTROL 0x29
|
||||
#define SDHCI_POWER_ON 0x01
|
||||
@ -117,6 +110,7 @@
|
||||
#define SDHCI_INT_DATA_END_BIT 0x00400000
|
||||
#define SDHCI_INT_BUS_POWER 0x00800000
|
||||
#define SDHCI_INT_ACMD12ERR 0x01000000
|
||||
#define SDHCI_INT_ADMA_ERROR 0x02000000
|
||||
|
||||
#define SDHCI_INT_NORMAL_MASK 0x00007FFF
|
||||
#define SDHCI_INT_ERROR_MASK 0xFFFF8000
|
||||
@ -140,11 +134,14 @@
|
||||
#define SDHCI_CLOCK_BASE_SHIFT 8
|
||||
#define SDHCI_MAX_BLOCK_MASK 0x00030000
|
||||
#define SDHCI_MAX_BLOCK_SHIFT 16
|
||||
#define SDHCI_CAN_DO_ADMA2 0x00080000
|
||||
#define SDHCI_CAN_DO_ADMA1 0x00100000
|
||||
#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
|
||||
#define SDHCI_CAN_64BIT 0x10000000
|
||||
|
||||
/* 44-47 reserved for more caps */
|
||||
|
||||
@ -152,7 +149,16 @@
|
||||
|
||||
/* 4C-4F reserved for more max current */
|
||||
|
||||
/* 50-FB reserved */
|
||||
#define SDHCI_SET_ACMD12_ERROR 0x50
|
||||
#define SDHCI_SET_INT_ERROR 0x52
|
||||
|
||||
#define SDHCI_ADMA_ERROR 0x54
|
||||
|
||||
/* 55-57 reserved */
|
||||
|
||||
#define SDHCI_ADMA_ADDRESS 0x58
|
||||
|
||||
/* 60-FB reserved */
|
||||
|
||||
#define SDHCI_SLOT_INT_STATUS 0xFC
|
||||
|
||||
@ -161,11 +167,50 @@
|
||||
#define SDHCI_VENDOR_VER_SHIFT 8
|
||||
#define SDHCI_SPEC_VER_MASK 0x00FF
|
||||
#define SDHCI_SPEC_VER_SHIFT 0
|
||||
#define SDHCI_SPEC_100 0
|
||||
#define SDHCI_SPEC_200 1
|
||||
|
||||
struct sdhci_chip;
|
||||
struct sdhci_ops;
|
||||
|
||||
struct sdhci_host {
|
||||
struct sdhci_chip *chip;
|
||||
/* Data set by hardware interface driver */
|
||||
const char *hw_name; /* Hardware bus name */
|
||||
|
||||
unsigned int quirks; /* Deviations from spec. */
|
||||
|
||||
/* Controller doesn't honor resets unless we touch the clock register */
|
||||
#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
|
||||
/* Controller has bad caps bits, but really supports DMA */
|
||||
#define SDHCI_QUIRK_FORCE_DMA (1<<1)
|
||||
/* Controller doesn't like to be reset when there is no card inserted. */
|
||||
#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)
|
||||
/* Controller doesn't like clearing the power reg before a change */
|
||||
#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)
|
||||
/* Controller has flaky internal state so reset it on each ios change */
|
||||
#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4)
|
||||
/* Controller has an unusable DMA engine */
|
||||
#define SDHCI_QUIRK_BROKEN_DMA (1<<5)
|
||||
/* Controller has an unusable ADMA engine */
|
||||
#define SDHCI_QUIRK_BROKEN_ADMA (1<<6)
|
||||
/* Controller can only DMA from 32-bit aligned addresses */
|
||||
#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<7)
|
||||
/* Controller can only DMA chunk sizes that are a multiple of 32 bits */
|
||||
#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<8)
|
||||
/* Controller can only ADMA chunks that are a multiple of 32 bits */
|
||||
#define SDHCI_QUIRK_32BIT_ADMA_SIZE (1<<9)
|
||||
/* Controller needs to be reset after each request to stay stable */
|
||||
#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<10)
|
||||
/* Controller needs voltage and power writes to happen separately */
|
||||
#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER (1<<11)
|
||||
/* Controller provides an incorrect timeout value for transfers */
|
||||
#define SDHCI_QUIRK_BROKEN_TIMEOUT_VAL (1<<12)
|
||||
|
||||
int irq; /* Device IRQ */
|
||||
void __iomem * ioaddr; /* Mapped address */
|
||||
|
||||
const struct sdhci_ops *ops; /* Low level hw interface */
|
||||
|
||||
/* Internal data */
|
||||
struct mmc_host *mmc; /* MMC structure */
|
||||
|
||||
#ifdef CONFIG_LEDS_CLASS
|
||||
@ -176,7 +221,11 @@ struct sdhci_host {
|
||||
|
||||
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. */
|
||||
#define SDHCI_USE_ADMA (1<<1) /* Host is ADMA capable */
|
||||
#define SDHCI_REQ_USE_DMA (1<<2) /* Use DMA for this req. */
|
||||
#define SDHCI_DEVICE_DEAD (1<<3) /* Device unresponsive */
|
||||
|
||||
unsigned int version; /* SDHCI spec. version */
|
||||
|
||||
unsigned int max_clk; /* Max possible freq (MHz) */
|
||||
unsigned int timeout_clk; /* Timeout freq (KHz) */
|
||||
@ -194,22 +243,41 @@ struct sdhci_host {
|
||||
int offset; /* Offset into current sg */
|
||||
int remain; /* Bytes left in current */
|
||||
|
||||
int irq; /* Device IRQ */
|
||||
int bar; /* PCI BAR index */
|
||||
unsigned long addr; /* Bus address */
|
||||
void __iomem * ioaddr; /* Mapped address */
|
||||
int sg_count; /* Mapped sg entries */
|
||||
|
||||
u8 *adma_desc; /* ADMA descriptor table */
|
||||
u8 *align_buffer; /* Bounce buffer */
|
||||
|
||||
dma_addr_t adma_addr; /* Mapped ADMA descr. table */
|
||||
dma_addr_t align_addr; /* Mapped bounce buffer */
|
||||
|
||||
struct tasklet_struct card_tasklet; /* Tasklet structures */
|
||||
struct tasklet_struct finish_tasklet;
|
||||
|
||||
struct timer_list timer; /* Timer for timeouts */
|
||||
|
||||
unsigned long private[0] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
struct sdhci_chip {
|
||||
struct pci_dev *pdev;
|
||||
|
||||
unsigned long quirks;
|
||||
|
||||
int num_slots; /* Slots on controller */
|
||||
struct sdhci_host *hosts[0]; /* Pointers to hosts */
|
||||
struct sdhci_ops {
|
||||
int (*enable_dma)(struct sdhci_host *host);
|
||||
};
|
||||
|
||||
|
||||
extern struct sdhci_host *sdhci_alloc_host(struct device *dev,
|
||||
size_t priv_size);
|
||||
extern void sdhci_free_host(struct sdhci_host *host);
|
||||
|
||||
static inline void *sdhci_priv(struct sdhci_host *host)
|
||||
{
|
||||
return (void *)host->private;
|
||||
}
|
||||
|
||||
extern int sdhci_add_host(struct sdhci_host *host);
|
||||
extern void sdhci_remove_host(struct sdhci_host *host, int dead);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
|
||||
extern int sdhci_resume_host(struct sdhci_host *host);
|
||||
#endif
|
||||
|
575
drivers/mmc/host/sdricoh_cs.c
Normal file
575
drivers/mmc/host/sdricoh_cs.c
Normal file
@ -0,0 +1,575 @@
|
||||
/*
|
||||
* sdricoh_cs.c - driver for Ricoh Secure Digital Card Readers that can be
|
||||
* found on some Ricoh RL5c476 II cardbus bridge
|
||||
*
|
||||
* Copyright (C) 2006 - 2008 Sascha Sommer <saschasommer@freenet.de>
|
||||
*
|
||||
* 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
#define DEBUG
|
||||
#define VERBOSE_DEBUG
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include <pcmcia/cs_types.h>
|
||||
#include <pcmcia/cs.h>
|
||||
#include <pcmcia/cistpl.h>
|
||||
#include <pcmcia/ds.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#define DRIVER_NAME "sdricoh_cs"
|
||||
|
||||
static unsigned int switchlocked;
|
||||
|
||||
/* i/o region */
|
||||
#define SDRICOH_PCI_REGION 0
|
||||
#define SDRICOH_PCI_REGION_SIZE 0x1000
|
||||
|
||||
/* registers */
|
||||
#define R104_VERSION 0x104
|
||||
#define R200_CMD 0x200
|
||||
#define R204_CMD_ARG 0x204
|
||||
#define R208_DATAIO 0x208
|
||||
#define R20C_RESP 0x20c
|
||||
#define R21C_STATUS 0x21c
|
||||
#define R2E0_INIT 0x2e0
|
||||
#define R2E4_STATUS_RESP 0x2e4
|
||||
#define R2F0_RESET 0x2f0
|
||||
#define R224_MODE 0x224
|
||||
#define R226_BLOCKSIZE 0x226
|
||||
#define R228_POWER 0x228
|
||||
#define R230_DATA 0x230
|
||||
|
||||
/* flags for the R21C_STATUS register */
|
||||
#define STATUS_CMD_FINISHED 0x00000001
|
||||
#define STATUS_TRANSFER_FINISHED 0x00000004
|
||||
#define STATUS_CARD_INSERTED 0x00000020
|
||||
#define STATUS_CARD_LOCKED 0x00000080
|
||||
#define STATUS_CMD_TIMEOUT 0x00400000
|
||||
#define STATUS_READY_TO_READ 0x01000000
|
||||
#define STATUS_READY_TO_WRITE 0x02000000
|
||||
#define STATUS_BUSY 0x40000000
|
||||
|
||||
/* timeouts */
|
||||
#define INIT_TIMEOUT 100
|
||||
#define CMD_TIMEOUT 100000
|
||||
#define TRANSFER_TIMEOUT 100000
|
||||
#define BUSY_TIMEOUT 32767
|
||||
|
||||
/* list of supported pcmcia devices */
|
||||
static struct pcmcia_device_id pcmcia_ids[] = {
|
||||
/* vendor and device strings followed by their crc32 hashes */
|
||||
PCMCIA_DEVICE_PROD_ID12("RICOH", "Bay1Controller", 0xd9f522ed,
|
||||
0xc3901202),
|
||||
PCMCIA_DEVICE_NULL,
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pcmcia, pcmcia_ids);
|
||||
|
||||
/* mmc privdata */
|
||||
struct sdricoh_host {
|
||||
struct device *dev;
|
||||
struct mmc_host *mmc; /* MMC structure */
|
||||
unsigned char __iomem *iobase;
|
||||
struct pci_dev *pci_dev;
|
||||
int app_cmd;
|
||||
};
|
||||
|
||||
/***************** register i/o helper functions *****************************/
|
||||
|
||||
static inline unsigned int sdricoh_readl(struct sdricoh_host *host,
|
||||
unsigned int reg)
|
||||
{
|
||||
unsigned int value = readl(host->iobase + reg);
|
||||
dev_vdbg(host->dev, "rl %x 0x%x\n", reg, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void sdricoh_writel(struct sdricoh_host *host, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
writel(value, host->iobase + reg);
|
||||
dev_vdbg(host->dev, "wl %x 0x%x\n", reg, value);
|
||||
|
||||
}
|
||||
|
||||
static inline unsigned int sdricoh_readw(struct sdricoh_host *host,
|
||||
unsigned int reg)
|
||||
{
|
||||
unsigned int value = readw(host->iobase + reg);
|
||||
dev_vdbg(host->dev, "rb %x 0x%x\n", reg, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void sdricoh_writew(struct sdricoh_host *host, unsigned int reg,
|
||||
unsigned short value)
|
||||
{
|
||||
writew(value, host->iobase + reg);
|
||||
dev_vdbg(host->dev, "ww %x 0x%x\n", reg, value);
|
||||
}
|
||||
|
||||
static inline unsigned int sdricoh_readb(struct sdricoh_host *host,
|
||||
unsigned int reg)
|
||||
{
|
||||
unsigned int value = readb(host->iobase + reg);
|
||||
dev_vdbg(host->dev, "rb %x 0x%x\n", reg, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static int sdricoh_query_status(struct sdricoh_host *host, unsigned int wanted,
|
||||
unsigned int timeout){
|
||||
unsigned int loop;
|
||||
unsigned int status = 0;
|
||||
struct device *dev = host->dev;
|
||||
for (loop = 0; loop < timeout; loop++) {
|
||||
status = sdricoh_readl(host, R21C_STATUS);
|
||||
sdricoh_writel(host, R2E4_STATUS_RESP, status);
|
||||
if (status & wanted)
|
||||
break;
|
||||
}
|
||||
|
||||
if (loop == timeout) {
|
||||
dev_err(dev, "query_status: timeout waiting for %x\n", wanted);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* do not do this check in the loop as some commands fail otherwise */
|
||||
if (status & 0x7F0000) {
|
||||
dev_err(dev, "waiting for status bit %x failed\n", wanted);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int sdricoh_mmc_cmd(struct sdricoh_host *host, unsigned char opcode,
|
||||
unsigned int arg)
|
||||
{
|
||||
unsigned int status;
|
||||
int result = 0;
|
||||
unsigned int loop = 0;
|
||||
/* reset status reg? */
|
||||
sdricoh_writel(host, R21C_STATUS, 0x18);
|
||||
/* fill parameters */
|
||||
sdricoh_writel(host, R204_CMD_ARG, arg);
|
||||
sdricoh_writel(host, R200_CMD, (0x10000 << 8) | opcode);
|
||||
/* wait for command completion */
|
||||
if (opcode) {
|
||||
for (loop = 0; loop < CMD_TIMEOUT; loop++) {
|
||||
status = sdricoh_readl(host, R21C_STATUS);
|
||||
sdricoh_writel(host, R2E4_STATUS_RESP, status);
|
||||
if (status & STATUS_CMD_FINISHED)
|
||||
break;
|
||||
}
|
||||
/* don't check for timeout in the loop it is not always
|
||||
reset correctly
|
||||
*/
|
||||
if (loop == CMD_TIMEOUT || status & STATUS_CMD_TIMEOUT)
|
||||
result = -ETIMEDOUT;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static int sdricoh_reset(struct sdricoh_host *host)
|
||||
{
|
||||
dev_dbg(host->dev, "reset\n");
|
||||
sdricoh_writel(host, R2F0_RESET, 0x10001);
|
||||
sdricoh_writel(host, R2E0_INIT, 0x10000);
|
||||
if (sdricoh_readl(host, R2E0_INIT) != 0x10000)
|
||||
return -EIO;
|
||||
sdricoh_writel(host, R2E0_INIT, 0x10007);
|
||||
|
||||
sdricoh_writel(host, R224_MODE, 0x2000000);
|
||||
sdricoh_writel(host, R228_POWER, 0xe0);
|
||||
|
||||
|
||||
/* status register ? */
|
||||
sdricoh_writel(host, R21C_STATUS, 0x18);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdricoh_blockio(struct sdricoh_host *host, int read,
|
||||
u8 *buf, int len)
|
||||
{
|
||||
int size;
|
||||
u32 data = 0;
|
||||
/* wait until the data is available */
|
||||
if (read) {
|
||||
if (sdricoh_query_status(host, STATUS_READY_TO_READ,
|
||||
TRANSFER_TIMEOUT))
|
||||
return -ETIMEDOUT;
|
||||
sdricoh_writel(host, R21C_STATUS, 0x18);
|
||||
/* read data */
|
||||
while (len) {
|
||||
data = sdricoh_readl(host, R230_DATA);
|
||||
size = min(len, 4);
|
||||
len -= size;
|
||||
while (size) {
|
||||
*buf = data & 0xFF;
|
||||
buf++;
|
||||
data >>= 8;
|
||||
size--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sdricoh_query_status(host, STATUS_READY_TO_WRITE,
|
||||
TRANSFER_TIMEOUT))
|
||||
return -ETIMEDOUT;
|
||||
sdricoh_writel(host, R21C_STATUS, 0x18);
|
||||
/* write data */
|
||||
while (len) {
|
||||
size = min(len, 4);
|
||||
len -= size;
|
||||
while (size) {
|
||||
data >>= 8;
|
||||
data |= (u32)*buf << 24;
|
||||
buf++;
|
||||
size--;
|
||||
}
|
||||
sdricoh_writel(host, R230_DATA, data);
|
||||
}
|
||||
}
|
||||
|
||||
if (len)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdricoh_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct sdricoh_host *host = mmc_priv(mmc);
|
||||
struct mmc_command *cmd = mrq->cmd;
|
||||
struct mmc_data *data = cmd->data;
|
||||
struct device *dev = host->dev;
|
||||
unsigned char opcode = cmd->opcode;
|
||||
int i;
|
||||
|
||||
dev_dbg(dev, "=============================\n");
|
||||
dev_dbg(dev, "sdricoh_request opcode=%i\n", opcode);
|
||||
|
||||
sdricoh_writel(host, R21C_STATUS, 0x18);
|
||||
|
||||
/* MMC_APP_CMDs need some special handling */
|
||||
if (host->app_cmd) {
|
||||
opcode |= 64;
|
||||
host->app_cmd = 0;
|
||||
} else if (opcode == 55)
|
||||
host->app_cmd = 1;
|
||||
|
||||
/* read/write commands seem to require this */
|
||||
if (data) {
|
||||
sdricoh_writew(host, R226_BLOCKSIZE, data->blksz);
|
||||
sdricoh_writel(host, R208_DATAIO, 0);
|
||||
}
|
||||
|
||||
cmd->error = sdricoh_mmc_cmd(host, opcode, cmd->arg);
|
||||
|
||||
/* read response buffer */
|
||||
if (cmd->flags & MMC_RSP_PRESENT) {
|
||||
if (cmd->flags & MMC_RSP_136) {
|
||||
/* CRC is stripped so we need to do some shifting. */
|
||||
for (i = 0; i < 4; i++) {
|
||||
cmd->resp[i] =
|
||||
sdricoh_readl(host,
|
||||
R20C_RESP + (3 - i) * 4) << 8;
|
||||
if (i != 3)
|
||||
cmd->resp[i] |=
|
||||
sdricoh_readb(host, R20C_RESP +
|
||||
(3 - i) * 4 - 1);
|
||||
}
|
||||
} else
|
||||
cmd->resp[0] = sdricoh_readl(host, R20C_RESP);
|
||||
}
|
||||
|
||||
/* transfer data */
|
||||
if (data && cmd->error == 0) {
|
||||
dev_dbg(dev, "transfer: blksz %i blocks %i sg_len %i "
|
||||
"sg length %i\n", data->blksz, data->blocks,
|
||||
data->sg_len, data->sg->length);
|
||||
|
||||
/* enter data reading mode */
|
||||
sdricoh_writel(host, R21C_STATUS, 0x837f031e);
|
||||
for (i = 0; i < data->blocks; i++) {
|
||||
size_t len = data->blksz;
|
||||
u8 *buf;
|
||||
struct page *page;
|
||||
int result;
|
||||
page = sg_page(data->sg);
|
||||
|
||||
buf = kmap(page) + data->sg->offset + (len * i);
|
||||
result =
|
||||
sdricoh_blockio(host,
|
||||
data->flags & MMC_DATA_READ, buf, len);
|
||||
kunmap(page);
|
||||
flush_dcache_page(page);
|
||||
if (result) {
|
||||
dev_err(dev, "sdricoh_request: cmd %i "
|
||||
"block transfer failed\n", cmd->opcode);
|
||||
cmd->error = result;
|
||||
break;
|
||||
} else
|
||||
data->bytes_xfered += len;
|
||||
}
|
||||
|
||||
sdricoh_writel(host, R208_DATAIO, 1);
|
||||
|
||||
if (sdricoh_query_status(host, STATUS_TRANSFER_FINISHED,
|
||||
TRANSFER_TIMEOUT)) {
|
||||
dev_err(dev, "sdricoh_request: transfer end error\n");
|
||||
cmd->error = -EINVAL;
|
||||
}
|
||||
}
|
||||
/* FIXME check busy flag */
|
||||
|
||||
mmc_request_done(mmc, mrq);
|
||||
dev_dbg(dev, "=============================\n");
|
||||
}
|
||||
|
||||
static void sdricoh_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct sdricoh_host *host = mmc_priv(mmc);
|
||||
dev_dbg(host->dev, "set_ios\n");
|
||||
|
||||
if (ios->power_mode == MMC_POWER_ON) {
|
||||
sdricoh_writel(host, R228_POWER, 0xc0e0);
|
||||
|
||||
if (ios->bus_width == MMC_BUS_WIDTH_4) {
|
||||
sdricoh_writel(host, R224_MODE, 0x2000300);
|
||||
sdricoh_writel(host, R228_POWER, 0x40e0);
|
||||
} else {
|
||||
sdricoh_writel(host, R224_MODE, 0x2000340);
|
||||
}
|
||||
|
||||
} else if (ios->power_mode == MMC_POWER_UP) {
|
||||
sdricoh_writel(host, R224_MODE, 0x2000320);
|
||||
sdricoh_writel(host, R228_POWER, 0xe0);
|
||||
}
|
||||
}
|
||||
|
||||
static int sdricoh_get_ro(struct mmc_host *mmc)
|
||||
{
|
||||
struct sdricoh_host *host = mmc_priv(mmc);
|
||||
unsigned int status;
|
||||
|
||||
status = sdricoh_readl(host, R21C_STATUS);
|
||||
sdricoh_writel(host, R2E4_STATUS_RESP, status);
|
||||
|
||||
/* some notebooks seem to have the locked flag switched */
|
||||
if (switchlocked)
|
||||
return !(status & STATUS_CARD_LOCKED);
|
||||
|
||||
return (status & STATUS_CARD_LOCKED);
|
||||
}
|
||||
|
||||
static struct mmc_host_ops sdricoh_ops = {
|
||||
.request = sdricoh_request,
|
||||
.set_ios = sdricoh_set_ios,
|
||||
.get_ro = sdricoh_get_ro,
|
||||
};
|
||||
|
||||
/* initialize the control and register it to the mmc framework */
|
||||
static int sdricoh_init_mmc(struct pci_dev *pci_dev,
|
||||
struct pcmcia_device *pcmcia_dev)
|
||||
{
|
||||
int result = 0;
|
||||
void __iomem *iobase = NULL;
|
||||
struct mmc_host *mmc = NULL;
|
||||
struct sdricoh_host *host = NULL;
|
||||
struct device *dev = &pcmcia_dev->dev;
|
||||
/* map iomem */
|
||||
if (pci_resource_len(pci_dev, SDRICOH_PCI_REGION) !=
|
||||
SDRICOH_PCI_REGION_SIZE) {
|
||||
dev_dbg(dev, "unexpected pci resource len\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
iobase =
|
||||
pci_iomap(pci_dev, SDRICOH_PCI_REGION, SDRICOH_PCI_REGION_SIZE);
|
||||
if (!iobase) {
|
||||
dev_err(dev, "unable to map iobase\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
/* check version? */
|
||||
if (readl(iobase + R104_VERSION) != 0x4000) {
|
||||
dev_dbg(dev, "no supported mmc controller found\n");
|
||||
result = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
/* allocate privdata */
|
||||
mmc = pcmcia_dev->priv =
|
||||
mmc_alloc_host(sizeof(struct sdricoh_host), &pcmcia_dev->dev);
|
||||
if (!mmc) {
|
||||
dev_err(dev, "mmc_alloc_host failed\n");
|
||||
result = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
host = mmc_priv(mmc);
|
||||
|
||||
host->iobase = iobase;
|
||||
host->dev = dev;
|
||||
host->pci_dev = pci_dev;
|
||||
|
||||
mmc->ops = &sdricoh_ops;
|
||||
|
||||
/* FIXME: frequency and voltage handling is done by the controller
|
||||
*/
|
||||
mmc->f_min = 450000;
|
||||
mmc->f_max = 24000000;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
|
||||
mmc->max_seg_size = 1024 * 512;
|
||||
mmc->max_blk_size = 512;
|
||||
|
||||
/* reset the controler */
|
||||
if (sdricoh_reset(host)) {
|
||||
dev_dbg(dev, "could not reset\n");
|
||||
result = -EIO;
|
||||
goto err;
|
||||
|
||||
}
|
||||
|
||||
result = mmc_add_host(mmc);
|
||||
|
||||
if (!result) {
|
||||
dev_dbg(dev, "mmc host registered\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
err:
|
||||
if (iobase)
|
||||
iounmap(iobase);
|
||||
if (mmc)
|
||||
mmc_free_host(mmc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* search for supported mmc controllers */
|
||||
static int sdricoh_pcmcia_probe(struct pcmcia_device *pcmcia_dev)
|
||||
{
|
||||
struct pci_dev *pci_dev = NULL;
|
||||
|
||||
dev_info(&pcmcia_dev->dev, "Searching MMC controller for pcmcia device"
|
||||
" %s %s ...\n", pcmcia_dev->prod_id[0], pcmcia_dev->prod_id[1]);
|
||||
|
||||
/* search pci cardbus bridge that contains the mmc controler */
|
||||
/* the io region is already claimed by yenta_socket... */
|
||||
while ((pci_dev =
|
||||
pci_get_device(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C476,
|
||||
pci_dev))) {
|
||||
/* try to init the device */
|
||||
if (!sdricoh_init_mmc(pci_dev, pcmcia_dev)) {
|
||||
dev_info(&pcmcia_dev->dev, "MMC controller found\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
dev_err(&pcmcia_dev->dev, "No MMC controller was found.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void sdricoh_pcmcia_detach(struct pcmcia_device *link)
|
||||
{
|
||||
struct mmc_host *mmc = link->priv;
|
||||
|
||||
dev_dbg(&link->dev, "detach\n");
|
||||
|
||||
/* remove mmc host */
|
||||
if (mmc) {
|
||||
struct sdricoh_host *host = mmc_priv(mmc);
|
||||
mmc_remove_host(mmc);
|
||||
pci_iounmap(host->pci_dev, host->iobase);
|
||||
pci_dev_put(host->pci_dev);
|
||||
mmc_free_host(mmc);
|
||||
}
|
||||
pcmcia_disable_device(link);
|
||||
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int sdricoh_pcmcia_suspend(struct pcmcia_device *link)
|
||||
{
|
||||
struct mmc_host *mmc = link->priv;
|
||||
dev_dbg(&link->dev, "suspend\n");
|
||||
mmc_suspend_host(mmc, PMSG_SUSPEND);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdricoh_pcmcia_resume(struct pcmcia_device *link)
|
||||
{
|
||||
struct mmc_host *mmc = link->priv;
|
||||
dev_dbg(&link->dev, "resume\n");
|
||||
sdricoh_reset(mmc_priv(mmc));
|
||||
mmc_resume_host(mmc);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define sdricoh_pcmcia_suspend NULL
|
||||
#define sdricoh_pcmcia_resume NULL
|
||||
#endif
|
||||
|
||||
static struct pcmcia_driver sdricoh_driver = {
|
||||
.drv = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = sdricoh_pcmcia_probe,
|
||||
.remove = sdricoh_pcmcia_detach,
|
||||
.id_table = pcmcia_ids,
|
||||
.suspend = sdricoh_pcmcia_suspend,
|
||||
.resume = sdricoh_pcmcia_resume,
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
* *
|
||||
* Driver init/exit *
|
||||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static int __init sdricoh_drv_init(void)
|
||||
{
|
||||
return pcmcia_register_driver(&sdricoh_driver);
|
||||
}
|
||||
|
||||
static void __exit sdricoh_drv_exit(void)
|
||||
{
|
||||
pcmcia_unregister_driver(&sdricoh_driver);
|
||||
}
|
||||
|
||||
module_init(sdricoh_drv_init);
|
||||
module_exit(sdricoh_drv_exit);
|
||||
|
||||
module_param(switchlocked, uint, 0444);
|
||||
|
||||
MODULE_AUTHOR("Sascha Sommer <saschasommer@freenet.de>");
|
||||
MODULE_DESCRIPTION("Ricoh PCMCIA Secure Digital Interface driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
MODULE_PARM_DESC(switchlocked, "Switch the cards locked status."
|
||||
"Use this when unlocked cards are shown readonly (default 0)");
|
@ -973,7 +973,7 @@ static int tifm_sd_probe(struct tifm_dev *sock)
|
||||
|
||||
mmc->ops = &tifm_sd_ops;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA;
|
||||
mmc->f_min = 20000000 / 60;
|
||||
mmc->f_max = 24000000;
|
||||
|
||||
|
@ -68,16 +68,16 @@ static const int unlock_codes[] = { 0x83, 0x87 };
|
||||
|
||||
static const int valid_ids[] = {
|
||||
0x7112,
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
static unsigned int nopnp = 0;
|
||||
static unsigned int param_nopnp = 0;
|
||||
#else
|
||||
static const unsigned int nopnp = 1;
|
||||
static const unsigned int param_nopnp = 1;
|
||||
#endif
|
||||
static unsigned int io = 0x248;
|
||||
static unsigned int irq = 6;
|
||||
static int dma = 2;
|
||||
static unsigned int param_io = 0x248;
|
||||
static unsigned int param_irq = 6;
|
||||
static int param_dma = 2;
|
||||
|
||||
/*
|
||||
* Basic functions
|
||||
@ -939,7 +939,7 @@ static int wbsd_get_ro(struct mmc_host *mmc)
|
||||
|
||||
spin_unlock_bh(&host->lock);
|
||||
|
||||
return csr & WBSD_WRPT;
|
||||
return !!(csr & WBSD_WRPT);
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops wbsd_ops = {
|
||||
@ -1219,7 +1219,7 @@ static int __devinit wbsd_alloc_mmc(struct device *dev)
|
||||
mmc->f_min = 375000;
|
||||
mmc->f_max = 24000000;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA;
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
|
||||
@ -1420,7 +1420,7 @@ kfree:
|
||||
|
||||
dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
|
||||
WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
|
||||
host->dma_addr = (dma_addr_t)NULL;
|
||||
host->dma_addr = 0;
|
||||
|
||||
kfree(host->dma_buffer);
|
||||
host->dma_buffer = NULL;
|
||||
@ -1445,7 +1445,7 @@ static void wbsd_release_dma(struct wbsd_host *host)
|
||||
|
||||
host->dma = -1;
|
||||
host->dma_buffer = NULL;
|
||||
host->dma_addr = (dma_addr_t)NULL;
|
||||
host->dma_addr = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1765,7 +1765,7 @@ static void __devexit wbsd_shutdown(struct device *dev, int pnp)
|
||||
static int __devinit wbsd_probe(struct platform_device *dev)
|
||||
{
|
||||
/* Use the module parameters for resources */
|
||||
return wbsd_init(&dev->dev, io, irq, dma, 0);
|
||||
return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0);
|
||||
}
|
||||
|
||||
static int __devexit wbsd_remove(struct platform_device *dev)
|
||||
@ -1979,14 +1979,14 @@ static int __init wbsd_drv_init(void)
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
|
||||
if (!nopnp) {
|
||||
if (!param_nopnp) {
|
||||
result = pnp_register_driver(&wbsd_pnp_driver);
|
||||
if (result < 0)
|
||||
return result;
|
||||
}
|
||||
#endif /* CONFIG_PNP */
|
||||
|
||||
if (nopnp) {
|
||||
if (param_nopnp) {
|
||||
result = platform_driver_register(&wbsd_driver);
|
||||
if (result < 0)
|
||||
return result;
|
||||
@ -2012,12 +2012,12 @@ static void __exit wbsd_drv_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_PNP
|
||||
|
||||
if (!nopnp)
|
||||
if (!param_nopnp)
|
||||
pnp_unregister_driver(&wbsd_pnp_driver);
|
||||
|
||||
#endif /* CONFIG_PNP */
|
||||
|
||||
if (nopnp) {
|
||||
if (param_nopnp) {
|
||||
platform_device_unregister(wbsd_device);
|
||||
|
||||
platform_driver_unregister(&wbsd_driver);
|
||||
@ -2029,11 +2029,11 @@ static void __exit wbsd_drv_exit(void)
|
||||
module_init(wbsd_drv_init);
|
||||
module_exit(wbsd_drv_exit);
|
||||
#ifdef CONFIG_PNP
|
||||
module_param(nopnp, uint, 0444);
|
||||
module_param_named(nopnp, param_nopnp, uint, 0444);
|
||||
#endif
|
||||
module_param(io, uint, 0444);
|
||||
module_param(irq, uint, 0444);
|
||||
module_param(dma, int, 0444);
|
||||
module_param_named(io, param_io, uint, 0444);
|
||||
module_param_named(irq, param_irq, uint, 0444);
|
||||
module_param_named(dma, param_dma, int, 0444);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* linux/drivers/net/wireless/libertas/if_sdio.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
* Copyright 2007-2008 Pierre Ossman
|
||||
*
|
||||
* Inspired by if_cs.c, Copyright 2007 Holger Schurig
|
||||
*
|
||||
@ -266,13 +266,10 @@ static int if_sdio_card_to_host(struct if_sdio_card *card)
|
||||
|
||||
/*
|
||||
* The transfer must be in one transaction or the firmware
|
||||
* goes suicidal.
|
||||
* goes suicidal. There's no way to guarantee that for all
|
||||
* controllers, but we can at least try.
|
||||
*/
|
||||
chunk = size;
|
||||
if ((chunk > card->func->cur_blksize) || (chunk > 512)) {
|
||||
chunk = (chunk + card->func->cur_blksize - 1) /
|
||||
card->func->cur_blksize * card->func->cur_blksize;
|
||||
}
|
||||
chunk = sdio_align_size(card->func, size);
|
||||
|
||||
ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
|
||||
if (ret)
|
||||
@ -696,13 +693,10 @@ static int if_sdio_host_to_card(struct lbs_private *priv,
|
||||
|
||||
/*
|
||||
* The transfer must be in one transaction or the firmware
|
||||
* goes suicidal.
|
||||
* goes suicidal. There's no way to guarantee that for all
|
||||
* controllers, but we can at least try.
|
||||
*/
|
||||
size = nb + 4;
|
||||
if ((size > card->func->cur_blksize) || (size > 512)) {
|
||||
size = (size + card->func->cur_blksize - 1) /
|
||||
card->func->cur_blksize * card->func->cur_blksize;
|
||||
}
|
||||
size = sdio_align_size(card->func, nb + 4);
|
||||
|
||||
packet = kzalloc(sizeof(struct if_sdio_packet) + size,
|
||||
GFP_ATOMIC);
|
||||
|
@ -75,6 +75,10 @@
|
||||
#define AT91_MCI_TRTYP_MULTIPLE (1 << 19)
|
||||
#define AT91_MCI_TRTYP_STREAM (2 << 19)
|
||||
|
||||
#define AT91_MCI_BLKR 0x18 /* Block Register */
|
||||
#define AT91_MCI_BLKR_BCNT(n) ((0xffff & (n)) << 0) /* Block count */
|
||||
#define AT91_MCI_BLKR_BLKLEN(n) ((0xffff & (n)) << 16) /* Block lenght */
|
||||
|
||||
#define AT91_MCI_RSPR(n) (0x20 + ((n) * 4)) /* Response Registers 0-3 */
|
||||
#define AT91_MCR_RDR 0x30 /* Receive Data Register */
|
||||
#define AT91_MCR_TDR 0x34 /* Transmit Data Register */
|
||||
|
@ -28,9 +28,15 @@
|
||||
#define S3C2410_SDIDCNT (0x30)
|
||||
#define S3C2410_SDIDSTA (0x34)
|
||||
#define S3C2410_SDIFSTA (0x38)
|
||||
|
||||
#define S3C2410_SDIDATA (0x3C)
|
||||
#define S3C2410_SDIIMSK (0x40)
|
||||
|
||||
#define S3C2440_SDIDATA (0x40)
|
||||
#define S3C2440_SDIIMSK (0x3C)
|
||||
|
||||
#define S3C2440_SDICON_SDRESET (1<<8)
|
||||
#define S3C2440_SDICON_MMCCLOCK (1<<5)
|
||||
#define S3C2410_SDICON_BYTEORDER (1<<4)
|
||||
#define S3C2410_SDICON_SDIOIRQ (1<<3)
|
||||
#define S3C2410_SDICON_RWAITEN (1<<2)
|
||||
@ -42,7 +48,8 @@
|
||||
#define S3C2410_SDICMDCON_LONGRSP (1<<10)
|
||||
#define S3C2410_SDICMDCON_WAITRSP (1<<9)
|
||||
#define S3C2410_SDICMDCON_CMDSTART (1<<8)
|
||||
#define S3C2410_SDICMDCON_INDEX (0xff)
|
||||
#define S3C2410_SDICMDCON_SENDERHOST (1<<6)
|
||||
#define S3C2410_SDICMDCON_INDEX (0x3f)
|
||||
|
||||
#define S3C2410_SDICMDSTAT_CRCFAIL (1<<12)
|
||||
#define S3C2410_SDICMDSTAT_CMDSENT (1<<11)
|
||||
@ -51,6 +58,9 @@
|
||||
#define S3C2410_SDICMDSTAT_XFERING (1<<8)
|
||||
#define S3C2410_SDICMDSTAT_INDEX (0xff)
|
||||
|
||||
#define S3C2440_SDIDCON_DS_BYTE (0<<22)
|
||||
#define S3C2440_SDIDCON_DS_HALFWORD (1<<22)
|
||||
#define S3C2440_SDIDCON_DS_WORD (2<<22)
|
||||
#define S3C2410_SDIDCON_IRQPERIOD (1<<21)
|
||||
#define S3C2410_SDIDCON_TXAFTERRESP (1<<20)
|
||||
#define S3C2410_SDIDCON_RXAFTERCMD (1<<19)
|
||||
@ -59,6 +69,7 @@
|
||||
#define S3C2410_SDIDCON_WIDEBUS (1<<16)
|
||||
#define S3C2410_SDIDCON_DMAEN (1<<15)
|
||||
#define S3C2410_SDIDCON_STOP (1<<14)
|
||||
#define S3C2440_SDIDCON_DATSTART (1<<14)
|
||||
#define S3C2410_SDIDCON_DATMODE (3<<12)
|
||||
#define S3C2410_SDIDCON_BLKNUM (0x7ff)
|
||||
|
||||
@ -68,6 +79,7 @@
|
||||
#define S3C2410_SDIDCON_XFER_RXSTART (2<<12)
|
||||
#define S3C2410_SDIDCON_XFER_TXSTART (3<<12)
|
||||
|
||||
#define S3C2410_SDIDCON_BLKNUM_MASK (0xFFF)
|
||||
#define S3C2410_SDIDCNT_BLKNUM_SHIFT (12)
|
||||
|
||||
#define S3C2410_SDIDSTA_RDYWAITREQ (1<<10)
|
||||
@ -82,10 +94,12 @@
|
||||
#define S3C2410_SDIDSTA_TXDATAON (1<<1)
|
||||
#define S3C2410_SDIDSTA_RXDATAON (1<<0)
|
||||
|
||||
#define S3C2440_SDIFSTA_FIFORESET (1<<16)
|
||||
#define S3C2440_SDIFSTA_FIFOFAIL (3<<14) /* 3 is correct (2 bits) */
|
||||
#define S3C2410_SDIFSTA_TFDET (1<<13)
|
||||
#define S3C2410_SDIFSTA_RFDET (1<<12)
|
||||
#define S3C2410_SDIFSTA_TXHALF (1<<11)
|
||||
#define S3C2410_SDIFSTA_TXEMPTY (1<<10)
|
||||
#define S3C2410_SDIFSTA_TFHALF (1<<11)
|
||||
#define S3C2410_SDIFSTA_TFEMPTY (1<<10)
|
||||
#define S3C2410_SDIFSTA_RFLAST (1<<9)
|
||||
#define S3C2410_SDIFSTA_RFFULL (1<<8)
|
||||
#define S3C2410_SDIFSTA_RFHALF (1<<7)
|
||||
|
15
include/asm-arm/plat-s3c24xx/mci.h
Normal file
15
include/asm-arm/plat-s3c24xx/mci.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef _ARCH_MCI_H
|
||||
#define _ARCH_MCI_H
|
||||
|
||||
struct s3c24xx_mci_pdata {
|
||||
unsigned int wprotect_invert : 1;
|
||||
unsigned int detect_invert : 1; /* set => detect active high. */
|
||||
|
||||
unsigned int gpio_detect;
|
||||
unsigned int gpio_wprotect;
|
||||
unsigned long ocr_avail;
|
||||
void (*set_power)(unsigned char power_mode,
|
||||
unsigned short vdd);
|
||||
};
|
||||
|
||||
#endif /* _ARCH_NCI_H */
|
@ -77,7 +77,11 @@ struct i2c_board_info;
|
||||
struct platform_device *at32_add_device_twi(unsigned int id,
|
||||
struct i2c_board_info *b,
|
||||
unsigned int n);
|
||||
struct platform_device *at32_add_device_mci(unsigned int id);
|
||||
|
||||
struct mci_platform_data;
|
||||
struct platform_device *
|
||||
at32_add_device_mci(unsigned int id, struct mci_platform_data *data);
|
||||
|
||||
struct platform_device *at32_add_device_ac97c(unsigned int id);
|
||||
struct platform_device *at32_add_device_abdac(unsigned int id);
|
||||
struct platform_device *at32_add_device_psif(unsigned int id);
|
||||
|
9
include/asm-avr32/atmel-mci.h
Normal file
9
include/asm-avr32/atmel-mci.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __ASM_AVR32_ATMEL_MCI_H
|
||||
#define __ASM_AVR32_ATMEL_MCI_H
|
||||
|
||||
struct mci_platform_data {
|
||||
int detect_pin;
|
||||
int wp_pin;
|
||||
};
|
||||
|
||||
#endif /* __ASM_AVR32_ATMEL_MCI_H */
|
@ -38,15 +38,15 @@
|
||||
#ifndef __ASM_AU1100_MMC_H
|
||||
#define __ASM_AU1100_MMC_H
|
||||
|
||||
#include <linux/leds.h>
|
||||
|
||||
#define NUM_AU1100_MMC_CONTROLLERS 2
|
||||
|
||||
#if defined(CONFIG_SOC_AU1100)
|
||||
#define AU1100_SD_IRQ AU1100_SD_INT
|
||||
#elif defined(CONFIG_SOC_AU1200)
|
||||
#define AU1100_SD_IRQ AU1200_SD_INT
|
||||
#endif
|
||||
|
||||
struct au1xmmc_platform_data {
|
||||
int(*cd_setup)(void *mmc_host, int on);
|
||||
int(*card_inserted)(void *mmc_host);
|
||||
int(*card_readonly)(void *mmc_host);
|
||||
void(*set_power)(void *mmc_host, int state);
|
||||
struct led_classdev *led;
|
||||
};
|
||||
|
||||
#define SD0_BASE 0xB0600000
|
||||
#define SD1_BASE 0xB0680000
|
||||
|
@ -135,6 +135,7 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *,
|
||||
struct mmc_command *, int);
|
||||
|
||||
extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *);
|
||||
extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int);
|
||||
|
||||
extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort);
|
||||
extern void mmc_release_host(struct mmc_host *host);
|
||||
|
@ -51,8 +51,30 @@ struct mmc_ios {
|
||||
|
||||
struct mmc_host_ops {
|
||||
void (*request)(struct mmc_host *host, struct mmc_request *req);
|
||||
/*
|
||||
* Avoid calling these three functions too often or in a "fast path",
|
||||
* since underlaying controller might implement them in an expensive
|
||||
* and/or slow way.
|
||||
*
|
||||
* Also note that these functions might sleep, so don't call them
|
||||
* in the atomic contexts!
|
||||
*
|
||||
* Return values for the get_ro callback should be:
|
||||
* 0 for a read/write card
|
||||
* 1 for a read-only card
|
||||
* -ENOSYS when not supported (equal to NULL callback)
|
||||
* or a negative errno value when something bad happened
|
||||
*
|
||||
* Return values for the get_ro callback should be:
|
||||
* 0 for a absent card
|
||||
* 1 for a present card
|
||||
* -ENOSYS when not supported (equal to NULL callback)
|
||||
* or a negative errno value when something bad happened
|
||||
*/
|
||||
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
|
||||
int (*get_ro)(struct mmc_host *host);
|
||||
int (*get_cd)(struct mmc_host *host);
|
||||
|
||||
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
|
||||
};
|
||||
|
||||
@ -89,11 +111,11 @@ struct mmc_host {
|
||||
unsigned long caps; /* Host capabilities */
|
||||
|
||||
#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */
|
||||
#define MMC_CAP_MULTIWRITE (1 << 1) /* Can accurately report bytes sent to card on error */
|
||||
#define MMC_CAP_MMC_HIGHSPEED (1 << 2) /* Can do MMC high-speed timing */
|
||||
#define MMC_CAP_SD_HIGHSPEED (1 << 3) /* Can do SD high-speed timing */
|
||||
#define MMC_CAP_SDIO_IRQ (1 << 4) /* Can signal pending SDIO IRQs */
|
||||
#define MMC_CAP_SPI (1 << 5) /* Talks only SPI protocols */
|
||||
#define MMC_CAP_MMC_HIGHSPEED (1 << 1) /* Can do MMC high-speed timing */
|
||||
#define MMC_CAP_SD_HIGHSPEED (1 << 2) /* Can do SD high-speed timing */
|
||||
#define MMC_CAP_SDIO_IRQ (1 << 3) /* Can signal pending SDIO IRQs */
|
||||
#define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */
|
||||
#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */
|
||||
|
||||
/* host specific block data */
|
||||
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
|
||||
|
@ -16,7 +16,6 @@
|
||||
* Based strongly on code by:
|
||||
*
|
||||
* Author: Yong-iL Joh <tolkien@mizi.com>
|
||||
* Date : $Date: 2002/06/18 12:37:30 $
|
||||
*
|
||||
* Author: Andrew Christian
|
||||
* 15 May 2002
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* include/linux/mmc/sdio_func.h
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
* Copyright 2007-2008 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
|
||||
@ -46,6 +46,8 @@ struct sdio_func {
|
||||
unsigned max_blksize; /* maximum block size */
|
||||
unsigned cur_blksize; /* current block size */
|
||||
|
||||
unsigned enable_timeout; /* max enable timeout in msec */
|
||||
|
||||
unsigned int state; /* function state */
|
||||
#define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */
|
||||
|
||||
@ -120,23 +122,22 @@ extern int sdio_set_block_size(struct sdio_func *func, unsigned blksz);
|
||||
extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler);
|
||||
extern int sdio_release_irq(struct sdio_func *func);
|
||||
|
||||
extern unsigned char sdio_readb(struct sdio_func *func,
|
||||
unsigned int addr, int *err_ret);
|
||||
extern unsigned short sdio_readw(struct sdio_func *func,
|
||||
unsigned int addr, int *err_ret);
|
||||
extern unsigned long sdio_readl(struct sdio_func *func,
|
||||
unsigned int addr, int *err_ret);
|
||||
extern unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz);
|
||||
|
||||
extern u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret);
|
||||
extern u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret);
|
||||
extern u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret);
|
||||
|
||||
extern int sdio_memcpy_fromio(struct sdio_func *func, void *dst,
|
||||
unsigned int addr, int count);
|
||||
extern int sdio_readsb(struct sdio_func *func, void *dst,
|
||||
unsigned int addr, int count);
|
||||
|
||||
extern void sdio_writeb(struct sdio_func *func, unsigned char b,
|
||||
extern void sdio_writeb(struct sdio_func *func, u8 b,
|
||||
unsigned int addr, int *err_ret);
|
||||
extern void sdio_writew(struct sdio_func *func, unsigned short b,
|
||||
extern void sdio_writew(struct sdio_func *func, u16 b,
|
||||
unsigned int addr, int *err_ret);
|
||||
extern void sdio_writel(struct sdio_func *func, unsigned long b,
|
||||
extern void sdio_writel(struct sdio_func *func, u32 b,
|
||||
unsigned int addr, int *err_ret);
|
||||
|
||||
extern int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr,
|
||||
|
@ -2190,6 +2190,7 @@
|
||||
#define PCI_DEVICE_ID_JMICRON_JMB366 0x2366
|
||||
#define PCI_DEVICE_ID_JMICRON_JMB368 0x2368
|
||||
#define PCI_DEVICE_ID_JMICRON_JMB38X_SD 0x2381
|
||||
#define PCI_DEVICE_ID_JMICRON_JMB38X_MMC 0x2382
|
||||
#define PCI_DEVICE_ID_JMICRON_JMB38X_MS 0x2383
|
||||
|
||||
#define PCI_VENDOR_ID_KORENIX 0x1982
|
||||
|
@ -23,6 +23,15 @@ struct mmc_spi_platform_data {
|
||||
/* sense switch on sd cards */
|
||||
int (*get_ro)(struct device *);
|
||||
|
||||
/*
|
||||
* If board does not use CD interrupts, driver can optimize polling
|
||||
* using this function.
|
||||
*/
|
||||
int (*get_cd)(struct device *);
|
||||
|
||||
/* Capabilities to pass into mmc core (e.g. MMC_CAP_NEEDS_POLL). */
|
||||
unsigned long caps;
|
||||
|
||||
/* how long to debounce card detect, in msecs */
|
||||
u16 detect_delay;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user