linux/drivers/memstick/host/tifm_ms.c
Alex Dubov 92b22d935f tifm: fix the MemoryStick host fifo handling code
Additional input received from JMicron on MemoryStick host interfaces showed
that some assumtions in fifo handling code were incorrect.  This patch also
fixes data corruption used to occure during PIO transfers.

Signed-off-by: Alex Dubov <oakad@yahoo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2008-03-10 18:01:18 -07:00

685 lines
16 KiB
C

/*
* TI FlashMedia driver
*
* Copyright (C) 2007 Alex Dubov <oakad@yahoo.com>
*
* 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.
*
* Special thanks to Carlos Corbacho for providing various MemoryStick cards
* that made this driver possible.
*
*/
#include <linux/tifm.h>
#include <linux/memstick.h>
#include <linux/highmem.h>
#include <linux/scatterlist.h>
#include <linux/log2.h>
#include <asm/io.h>
#define DRIVER_NAME "tifm_ms"
static int no_dma;
module_param(no_dma, bool, 0644);
/*
* Some control bits of TIFM appear to conform to Sony's reference design,
* so I'm just assuming they all are.
*/
#define TIFM_MS_STAT_DRQ 0x04000
#define TIFM_MS_STAT_MSINT 0x02000
#define TIFM_MS_STAT_RDY 0x01000
#define TIFM_MS_STAT_CRC 0x00200
#define TIFM_MS_STAT_TOE 0x00100
#define TIFM_MS_STAT_EMP 0x00020
#define TIFM_MS_STAT_FUL 0x00010
#define TIFM_MS_STAT_CED 0x00008
#define TIFM_MS_STAT_ERR 0x00004
#define TIFM_MS_STAT_BRQ 0x00002
#define TIFM_MS_STAT_CNK 0x00001
#define TIFM_MS_SYS_DMA 0x10000
#define TIFM_MS_SYS_RESET 0x08000
#define TIFM_MS_SYS_SRAC 0x04000
#define TIFM_MS_SYS_INTEN 0x02000
#define TIFM_MS_SYS_NOCRC 0x01000
#define TIFM_MS_SYS_INTCLR 0x00800
#define TIFM_MS_SYS_MSIEN 0x00400
#define TIFM_MS_SYS_FCLR 0x00200
#define TIFM_MS_SYS_FDIR 0x00100
#define TIFM_MS_SYS_DAM 0x00080
#define TIFM_MS_SYS_DRM 0x00040
#define TIFM_MS_SYS_DRQSL 0x00020
#define TIFM_MS_SYS_REI 0x00010
#define TIFM_MS_SYS_REO 0x00008
#define TIFM_MS_SYS_BSY_MASK 0x00007
#define TIFM_MS_SYS_FIFO (TIFM_MS_SYS_INTEN | TIFM_MS_SYS_MSIEN \
| TIFM_MS_SYS_FCLR | TIFM_MS_SYS_BSY_MASK)
/* Hardware flags */
enum {
CMD_READY = 0x01,
FIFO_READY = 0x02,
CARD_INT = 0x04
};
struct tifm_ms {
struct tifm_dev *dev;
struct timer_list timer;
struct memstick_request *req;
unsigned int mode_mask;
unsigned int block_pos;
unsigned long timeout_jiffies;
unsigned char eject:1,
use_dma:1;
unsigned char cmd_flags;
unsigned char io_pos;
unsigned int io_word;
};
static unsigned int tifm_ms_read_data(struct tifm_ms *host,
unsigned char *buf, unsigned int length)
{
struct tifm_dev *sock = host->dev;
unsigned int off = 0;
while (host->io_pos && length) {
buf[off++] = host->io_word & 0xff;
host->io_word >>= 8;
length--;
host->io_pos--;
}
if (!length)
return off;
while (!(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
if (length < 4)
break;
*(unsigned int *)(buf + off) = __raw_readl(sock->addr
+ SOCK_MS_DATA);
length -= 4;
off += 4;
}
if (length
&& !(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
host->io_word = readl(sock->addr + SOCK_MS_DATA);
for (host->io_pos = 4; host->io_pos; --host->io_pos) {
buf[off++] = host->io_word & 0xff;
host->io_word >>= 8;
length--;
if (!length)
break;
}
}
return off;
}
static unsigned int tifm_ms_write_data(struct tifm_ms *host,
unsigned char *buf, unsigned int length)
{
struct tifm_dev *sock = host->dev;
unsigned int off = 0;
if (host->io_pos) {
while (host->io_pos < 4 && length) {
host->io_word |= buf[off++] << (host->io_pos * 8);
host->io_pos++;
length--;
}
}
if (host->io_pos == 4
&& !(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
sock->addr + SOCK_MS_SYSTEM);
writel(host->io_word, sock->addr + SOCK_MS_DATA);
host->io_pos = 0;
host->io_word = 0;
} else if (host->io_pos) {
return off;
}
if (!length)
return off;
while (!(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
if (length < 4)
break;
writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
sock->addr + SOCK_MS_SYSTEM);
__raw_writel(*(unsigned int *)(buf + off),
sock->addr + SOCK_MS_DATA);
length -= 4;
off += 4;
}
switch (length) {
case 3:
host->io_word |= buf[off + 2] << 16;
host->io_pos++;
case 2:
host->io_word |= buf[off + 1] << 8;
host->io_pos++;
case 1:
host->io_word |= buf[off];
host->io_pos++;
}
off += host->io_pos;
return off;
}
static unsigned int tifm_ms_transfer_data(struct tifm_ms *host)
{
struct tifm_dev *sock = host->dev;
unsigned int length;
unsigned int off;
unsigned int t_size, p_off, p_cnt;
unsigned char *buf;
struct page *pg;
unsigned long flags = 0;
if (host->req->long_data) {
length = host->req->sg.length - host->block_pos;
off = host->req->sg.offset + host->block_pos;
} else {
length = host->req->data_len - host->block_pos;
off = 0;
}
dev_dbg(&sock->dev, "fifo data transfer, %d, %d\n", length,
host->block_pos);
while (length) {
if (host->req->long_data) {
pg = nth_page(sg_page(&host->req->sg),
off >> PAGE_SHIFT);
p_off = offset_in_page(off);
p_cnt = PAGE_SIZE - p_off;
p_cnt = min(p_cnt, length);
local_irq_save(flags);
buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + p_off;
} else {
buf = host->req->data + host->block_pos;
p_cnt = host->req->data_len - host->block_pos;
}
t_size = host->req->data_dir == WRITE
? tifm_ms_write_data(host, buf, p_cnt)
: tifm_ms_read_data(host, buf, p_cnt);
if (host->req->long_data) {
kunmap_atomic(buf - p_off, KM_BIO_SRC_IRQ);
local_irq_restore(flags);
}
if (!t_size)
break;
host->block_pos += t_size;
length -= t_size;
off += t_size;
}
dev_dbg(&sock->dev, "fifo data transfer, %d remaining\n", length);
if (!length && (host->req->data_dir == WRITE)) {
if (host->io_pos) {
writel(TIFM_MS_SYS_FDIR
| readl(sock->addr + SOCK_MS_SYSTEM),
sock->addr + SOCK_MS_SYSTEM);
writel(host->io_word, sock->addr + SOCK_MS_DATA);
}
writel(TIFM_MS_SYS_FDIR
| readl(sock->addr + SOCK_MS_SYSTEM),
sock->addr + SOCK_MS_SYSTEM);
writel(0, sock->addr + SOCK_MS_DATA);
} else {
readl(sock->addr + SOCK_MS_DATA);
}
return length;
}
static int tifm_ms_issue_cmd(struct tifm_ms *host)
{
struct tifm_dev *sock = host->dev;
unsigned char *data;
unsigned int data_len, cmd, sys_param;
host->cmd_flags = 0;
host->block_pos = 0;
host->io_pos = 0;
host->io_word = 0;
host->cmd_flags = 0;
data = host->req->data;
host->use_dma = !no_dma;
if (host->req->long_data) {
data_len = host->req->sg.length;
if (!is_power_of_2(data_len))
host->use_dma = 0;
} else {
data_len = host->req->data_len;
host->use_dma = 0;
}
writel(TIFM_FIFO_INT_SETALL,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
writel(TIFM_FIFO_ENABLE,
sock->addr + SOCK_FIFO_CONTROL);
if (host->use_dma) {
if (1 != tifm_map_sg(sock, &host->req->sg, 1,
host->req->data_dir == READ
? PCI_DMA_FROMDEVICE
: PCI_DMA_TODEVICE)) {
host->req->error = -ENOMEM;
return host->req->error;
}
data_len = sg_dma_len(&host->req->sg);
writel(ilog2(data_len) - 2,
sock->addr + SOCK_FIFO_PAGE_SIZE);
writel(TIFM_FIFO_INTMASK,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
sys_param = TIFM_DMA_EN | (1 << 8);
if (host->req->data_dir == WRITE)
sys_param |= TIFM_DMA_TX;
writel(TIFM_FIFO_INTMASK,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
writel(sg_dma_address(&host->req->sg),
sock->addr + SOCK_DMA_ADDRESS);
writel(sys_param, sock->addr + SOCK_DMA_CONTROL);
} else {
writel(host->mode_mask | TIFM_MS_SYS_FIFO,
sock->addr + SOCK_MS_SYSTEM);
writel(TIFM_FIFO_MORE,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
}
mod_timer(&host->timer, jiffies + host->timeout_jiffies);
writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL),
sock->addr + SOCK_CONTROL);
host->req->error = 0;
sys_param = readl(sock->addr + SOCK_MS_SYSTEM);
sys_param |= TIFM_MS_SYS_INTCLR;
if (host->use_dma)
sys_param |= TIFM_MS_SYS_DMA;
else
sys_param &= ~TIFM_MS_SYS_DMA;
writel(sys_param, sock->addr + SOCK_MS_SYSTEM);
cmd = (host->req->tpc & 0xf) << 12;
cmd |= data_len;
writel(cmd, sock->addr + SOCK_MS_COMMAND);
dev_dbg(&sock->dev, "executing TPC %x, %x\n", cmd, sys_param);
return 0;
}
static void tifm_ms_complete_cmd(struct tifm_ms *host)
{
struct tifm_dev *sock = host->dev;
struct memstick_host *msh = tifm_get_drvdata(sock);
int rc;
del_timer(&host->timer);
if (host->use_dma)
tifm_unmap_sg(sock, &host->req->sg, 1,
host->req->data_dir == READ
? PCI_DMA_FROMDEVICE
: PCI_DMA_TODEVICE);
writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL),
sock->addr + SOCK_CONTROL);
dev_dbg(&sock->dev, "TPC complete\n");
do {
rc = memstick_next_req(msh, &host->req);
} while (!rc && tifm_ms_issue_cmd(host));
}
static int tifm_ms_check_status(struct tifm_ms *host)
{
if (!host->req->error) {
if (!(host->cmd_flags & CMD_READY))
return 1;
if (!(host->cmd_flags & FIFO_READY))
return 1;
if (host->req->need_card_int
&& !(host->cmd_flags & CARD_INT))
return 1;
}
return 0;
}
/* Called from interrupt handler */
static void tifm_ms_data_event(struct tifm_dev *sock)
{
struct tifm_ms *host;
unsigned int fifo_status = 0, host_status = 0;
int rc = 1;
spin_lock(&sock->lock);
host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);
host_status = readl(sock->addr + SOCK_MS_STATUS);
dev_dbg(&sock->dev,
"data event: fifo_status %x, host_status %x, flags %x\n",
fifo_status, host_status, host->cmd_flags);
if (host->req) {
if (host->use_dma && (fifo_status & 1)) {
host->cmd_flags |= FIFO_READY;
rc = tifm_ms_check_status(host);
}
if (!host->use_dma && (fifo_status & TIFM_FIFO_MORE)) {
if (!tifm_ms_transfer_data(host)) {
host->cmd_flags |= FIFO_READY;
rc = tifm_ms_check_status(host);
}
}
}
writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);
if (!rc)
tifm_ms_complete_cmd(host);
spin_unlock(&sock->lock);
}
/* Called from interrupt handler */
static void tifm_ms_card_event(struct tifm_dev *sock)
{
struct tifm_ms *host;
unsigned int host_status = 0;
int rc = 1;
spin_lock(&sock->lock);
host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
host_status = readl(sock->addr + SOCK_MS_STATUS);
dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",
host_status, host->cmd_flags);
if (host->req) {
if (host_status & TIFM_MS_STAT_TOE)
host->req->error = -ETIME;
else if (host_status & TIFM_MS_STAT_CRC)
host->req->error = -EILSEQ;
if (host->req->error) {
writel(TIFM_FIFO_INT_SETALL,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
}
if (host_status & TIFM_MS_STAT_RDY)
host->cmd_flags |= CMD_READY;
if (host_status & TIFM_MS_STAT_MSINT)
host->cmd_flags |= CARD_INT;
rc = tifm_ms_check_status(host);
}
writel(TIFM_MS_SYS_INTCLR | readl(sock->addr + SOCK_MS_SYSTEM),
sock->addr + SOCK_MS_SYSTEM);
if (!rc)
tifm_ms_complete_cmd(host);
spin_unlock(&sock->lock);
return;
}
static void tifm_ms_request(struct memstick_host *msh)
{
struct tifm_ms *host = memstick_priv(msh);
struct tifm_dev *sock = host->dev;
unsigned long flags;
int rc;
spin_lock_irqsave(&sock->lock, flags);
if (host->req) {
printk(KERN_ERR "%s : unfinished request detected\n",
sock->dev.bus_id);
spin_unlock_irqrestore(&sock->lock, flags);
tifm_eject(host->dev);
return;
}
if (host->eject) {
do {
rc = memstick_next_req(msh, &host->req);
if (!rc)
host->req->error = -ETIME;
} while (!rc);
spin_unlock_irqrestore(&sock->lock, flags);
return;
}
do {
rc = memstick_next_req(msh, &host->req);
} while (!rc && tifm_ms_issue_cmd(host));
spin_unlock_irqrestore(&sock->lock, flags);
return;
}
static void tifm_ms_set_param(struct memstick_host *msh,
enum memstick_param param,
int value)
{
struct tifm_ms *host = memstick_priv(msh);
struct tifm_dev *sock = host->dev;
unsigned long flags;
spin_lock_irqsave(&sock->lock, flags);
switch (param) {
case MEMSTICK_POWER:
/* this is set by card detection mechanism */
break;
case MEMSTICK_INTERFACE:
if (value == MEMSTICK_SERIAL) {
host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
writel((~TIFM_CTRL_FAST_CLK)
& readl(sock->addr + SOCK_CONTROL),
sock->addr + SOCK_CONTROL);
} else if (value == MEMSTICK_PAR4) {
host->mode_mask = 0;
writel(TIFM_CTRL_FAST_CLK
| readl(sock->addr + SOCK_CONTROL),
sock->addr + SOCK_CONTROL);
}
break;
};
spin_unlock_irqrestore(&sock->lock, flags);
}
static void tifm_ms_abort(unsigned long data)
{
struct tifm_ms *host = (struct tifm_ms *)data;
dev_dbg(&host->dev->dev, "status %x\n",
readl(host->dev->addr + SOCK_MS_STATUS));
printk(KERN_ERR
"%s : card failed to respond for a long period of time "
"(%x, %x)\n",
host->dev->dev.bus_id, host->req ? host->req->tpc : 0,
host->cmd_flags);
tifm_eject(host->dev);
}
static int tifm_ms_initialize_host(struct tifm_ms *host)
{
struct tifm_dev *sock = host->dev;
struct memstick_host *msh = tifm_get_drvdata(sock);
host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
writel(TIFM_MS_SYS_RESET, sock->addr + SOCK_MS_SYSTEM);
writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
sock->addr + SOCK_MS_SYSTEM);
writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
if (tifm_has_ms_pif(sock))
msh->caps |= MEMSTICK_CAP_PAR4;
return 0;
}
static int tifm_ms_probe(struct tifm_dev *sock)
{
struct memstick_host *msh;
struct tifm_ms *host;
int rc = -EIO;
if (!(TIFM_SOCK_STATE_OCCUPIED
& readl(sock->addr + SOCK_PRESENT_STATE))) {
printk(KERN_WARNING "%s : card gone, unexpectedly\n",
sock->dev.bus_id);
return rc;
}
msh = memstick_alloc_host(sizeof(struct tifm_ms), &sock->dev);
if (!msh)
return -ENOMEM;
host = memstick_priv(msh);
tifm_set_drvdata(sock, msh);
host->dev = sock;
host->timeout_jiffies = msecs_to_jiffies(1000);
setup_timer(&host->timer, tifm_ms_abort, (unsigned long)host);
msh->request = tifm_ms_request;
msh->set_param = tifm_ms_set_param;
sock->card_event = tifm_ms_card_event;
sock->data_event = tifm_ms_data_event;
rc = tifm_ms_initialize_host(host);
if (!rc)
rc = memstick_add_host(msh);
if (!rc)
return 0;
memstick_free_host(msh);
return rc;
}
static void tifm_ms_remove(struct tifm_dev *sock)
{
struct memstick_host *msh = tifm_get_drvdata(sock);
struct tifm_ms *host = memstick_priv(msh);
int rc = 0;
unsigned long flags;
spin_lock_irqsave(&sock->lock, flags);
host->eject = 1;
if (host->req) {
del_timer(&host->timer);
writel(TIFM_FIFO_INT_SETALL,
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
if (host->use_dma)
tifm_unmap_sg(sock, &host->req->sg, 1,
host->req->data_dir == READ
? PCI_DMA_TODEVICE
: PCI_DMA_FROMDEVICE);
host->req->error = -ETIME;
do {
rc = memstick_next_req(msh, &host->req);
if (!rc)
host->req->error = -ETIME;
} while (!rc);
}
spin_unlock_irqrestore(&sock->lock, flags);
memstick_remove_host(msh);
writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
sock->addr + SOCK_MS_SYSTEM);
writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
memstick_free_host(msh);
}
#ifdef CONFIG_PM
static int tifm_ms_suspend(struct tifm_dev *sock, pm_message_t state)
{
struct memstick_host *msh = tifm_get_drvdata(sock);
memstick_suspend_host(msh);
return 0;
}
static int tifm_ms_resume(struct tifm_dev *sock)
{
struct memstick_host *msh = tifm_get_drvdata(sock);
memstick_resume_host(msh);
return 0;
}
#else
#define tifm_ms_suspend NULL
#define tifm_ms_resume NULL
#endif /* CONFIG_PM */
static struct tifm_device_id tifm_ms_id_tbl[] = {
{ TIFM_TYPE_MS }, { 0 }
};
static struct tifm_driver tifm_ms_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE
},
.id_table = tifm_ms_id_tbl,
.probe = tifm_ms_probe,
.remove = tifm_ms_remove,
.suspend = tifm_ms_suspend,
.resume = tifm_ms_resume
};
static int __init tifm_ms_init(void)
{
return tifm_register_driver(&tifm_ms_driver);
}
static void __exit tifm_ms_exit(void)
{
tifm_unregister_driver(&tifm_ms_driver);
}
MODULE_AUTHOR("Alex Dubov");
MODULE_DESCRIPTION("TI FlashMedia MemoryStick driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(tifm, tifm_ms_id_tbl);
module_init(tifm_ms_init);
module_exit(tifm_ms_exit);