mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
tifm_sd: restructure initialization, removal and command handling
In order to support correct suspend and resume several changes were needed: 1. Switch from work_struct to tasklet for command handling. When device suspend is called workqueues are already frozen and can not be used. 2. Separate host initialization code from driver's probe and don't rely on interrupts for host initialization. This, in turn, addresses two problems: a) Resume needs to re-initialize the host, but can not assume that device interrupts were already re-armed. b) Previously, probe will return successfully before really knowing the state of the host, as host interrupts were not armed in time. Now it uses polling to determine the real host state before returning. 3. Separate termination code from driver's remove. Termination may be caused by resume, if media changed type or became unavailable during suspend. Signed-off-by: Alex Dubov <oakad@yahoo.com> Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
This commit is contained in:
parent
83d420ba92
commit
8e02f8581c
@ -201,11 +201,12 @@ static void tifm_7xx1_insert_media(struct work_struct *work)
|
||||
fm->max_sockets == 2);
|
||||
if (media_id) {
|
||||
ok_to_register = 0;
|
||||
new_sock = tifm_alloc_device(fm, cnt);
|
||||
new_sock = tifm_alloc_device(fm);
|
||||
if (new_sock) {
|
||||
new_sock->addr = tifm_7xx1_sock_addr(fm->addr,
|
||||
cnt);
|
||||
new_sock->media_id = media_id;
|
||||
new_sock->socket_id = cnt;
|
||||
switch (media_id) {
|
||||
case 1:
|
||||
card_name = "xd";
|
||||
|
@ -141,24 +141,17 @@ EXPORT_SYMBOL(tifm_remove_adapter);
|
||||
void tifm_free_device(struct device *dev)
|
||||
{
|
||||
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
|
||||
if (fm_dev->wq)
|
||||
destroy_workqueue(fm_dev->wq);
|
||||
kfree(fm_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(tifm_free_device);
|
||||
|
||||
struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm, unsigned int id)
|
||||
struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm)
|
||||
{
|
||||
struct tifm_dev *dev = kzalloc(sizeof(struct tifm_dev), GFP_KERNEL);
|
||||
|
||||
if (dev) {
|
||||
spin_lock_init(&dev->lock);
|
||||
snprintf(dev->wq_name, KOBJ_NAME_LEN, "tifm%u:%u", fm->id, id);
|
||||
dev->wq = create_singlethread_workqueue(dev->wq_name);
|
||||
if (!dev->wq) {
|
||||
kfree(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->dev.parent = fm->dev;
|
||||
dev->dev.bus = &tifm_bus_type;
|
||||
dev->dev.release = tifm_free_device;
|
||||
|
@ -79,7 +79,6 @@ typedef enum {
|
||||
|
||||
enum {
|
||||
FIFO_RDY = 0x0001, /* hardware dependent value */
|
||||
HOST_REG = 0x0002,
|
||||
EJECT = 0x0004,
|
||||
EJECT_DONE = 0x0008,
|
||||
CARD_BUSY = 0x0010,
|
||||
@ -97,10 +96,10 @@ struct tifm_sd {
|
||||
unsigned int clk_div;
|
||||
unsigned long timeout_jiffies;
|
||||
|
||||
struct tasklet_struct finish_tasklet;
|
||||
struct timer_list timer;
|
||||
struct mmc_request *req;
|
||||
struct work_struct cmd_handler;
|
||||
wait_queue_head_t can_eject;
|
||||
wait_queue_head_t notify;
|
||||
|
||||
size_t written_blocks;
|
||||
size_t buffer_size;
|
||||
@ -317,7 +316,7 @@ change_state:
|
||||
}
|
||||
break;
|
||||
case READY:
|
||||
queue_work(sock->wq, &host->cmd_handler);
|
||||
tasklet_schedule(&host->finish_tasklet);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -345,8 +344,6 @@ static unsigned int tifm_sd_signal_irq(struct tifm_dev *sock,
|
||||
host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
|
||||
writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
|
||||
|
||||
if (!(host->flags & HOST_REG))
|
||||
queue_work(sock->wq, &host->cmd_handler);
|
||||
if (!host->req)
|
||||
goto done;
|
||||
|
||||
@ -517,9 +514,9 @@ err_out:
|
||||
mmc_request_done(mmc, mrq);
|
||||
}
|
||||
|
||||
static void tifm_sd_end_cmd(struct work_struct *work)
|
||||
static void tifm_sd_end_cmd(unsigned long data)
|
||||
{
|
||||
struct tifm_sd *host = container_of(work, struct tifm_sd, cmd_handler);
|
||||
struct tifm_sd *host = (struct tifm_sd*)data;
|
||||
struct tifm_dev *sock = host->dev;
|
||||
struct mmc_host *mmc = tifm_get_drvdata(sock);
|
||||
struct mmc_request *mrq;
|
||||
@ -616,9 +613,9 @@ err_out:
|
||||
mmc_request_done(mmc, mrq);
|
||||
}
|
||||
|
||||
static void tifm_sd_end_cmd_nodma(struct work_struct *work)
|
||||
static void tifm_sd_end_cmd_nodma(unsigned long data)
|
||||
{
|
||||
struct tifm_sd *host = container_of(work, struct tifm_sd, cmd_handler);
|
||||
struct tifm_sd *host = (struct tifm_sd*)data;
|
||||
struct tifm_dev *sock = host->dev;
|
||||
struct mmc_host *mmc = tifm_get_drvdata(sock);
|
||||
struct mmc_request *mrq;
|
||||
@ -666,11 +663,33 @@ static void tifm_sd_end_cmd_nodma(struct work_struct *work)
|
||||
mmc_request_done(mmc, mrq);
|
||||
}
|
||||
|
||||
static void tifm_sd_terminate(struct tifm_sd *host)
|
||||
{
|
||||
struct tifm_dev *sock = host->dev;
|
||||
unsigned long flags;
|
||||
|
||||
writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
mmiowb();
|
||||
spin_lock_irqsave(&sock->lock, flags);
|
||||
host->flags |= EJECT;
|
||||
if (host->req) {
|
||||
writel(TIFM_FIFO_INT_SETALL,
|
||||
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
|
||||
writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
|
||||
tasklet_schedule(&host->finish_tasklet);
|
||||
}
|
||||
spin_unlock_irqrestore(&sock->lock, flags);
|
||||
}
|
||||
|
||||
static void tifm_sd_abort(unsigned long data)
|
||||
{
|
||||
struct tifm_sd *host = (struct tifm_sd*)data;
|
||||
|
||||
printk(KERN_ERR DRIVER_NAME
|
||||
": card failed to respond for a long period of time");
|
||||
tifm_eject(((struct tifm_sd*)data)->dev);
|
||||
|
||||
tifm_sd_terminate(host);
|
||||
tifm_eject(host->dev);
|
||||
}
|
||||
|
||||
static void tifm_sd_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
@ -739,7 +758,7 @@ static void tifm_sd_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
// allow removal.
|
||||
if ((host->flags & EJECT) && ios->power_mode == MMC_POWER_OFF) {
|
||||
host->flags |= EJECT_DONE;
|
||||
wake_up_all(&host->can_eject);
|
||||
wake_up_all(&host->notify);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&sock->lock, flags);
|
||||
@ -767,21 +786,67 @@ static struct mmc_host_ops tifm_sd_ops = {
|
||||
.get_ro = tifm_sd_ro
|
||||
};
|
||||
|
||||
static void tifm_sd_register_host(struct work_struct *work)
|
||||
static int tifm_sd_initialize_host(struct tifm_sd *host)
|
||||
{
|
||||
struct tifm_sd *host = container_of(work, struct tifm_sd, cmd_handler);
|
||||
int rc;
|
||||
unsigned int host_status = 0;
|
||||
struct tifm_dev *sock = host->dev;
|
||||
struct mmc_host *mmc = tifm_get_drvdata(sock);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&sock->lock, flags);
|
||||
del_timer(&host->timer);
|
||||
host->flags |= HOST_REG;
|
||||
PREPARE_WORK(&host->cmd_handler,
|
||||
no_dma ? tifm_sd_end_cmd_nodma : tifm_sd_end_cmd);
|
||||
spin_unlock_irqrestore(&sock->lock, flags);
|
||||
dev_dbg(&sock->dev, "adding host\n");
|
||||
mmc_add_host(mmc);
|
||||
writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
mmiowb();
|
||||
host->clk_div = 61;
|
||||
host->clk_freq = 20000000;
|
||||
writel(TIFM_MMCSD_RESET, sock->addr + SOCK_MMCSD_SYSTEM_CONTROL);
|
||||
writel(host->clk_div | TIFM_MMCSD_POWER,
|
||||
sock->addr + SOCK_MMCSD_CONFIG);
|
||||
|
||||
/* wait up to 0.51 sec for reset */
|
||||
for (rc = 2; rc <= 256; rc <<= 1) {
|
||||
if (1 & readl(sock->addr + SOCK_MMCSD_SYSTEM_STATUS)) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
msleep(rc);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
printk(KERN_ERR DRIVER_NAME
|
||||
": controller failed to reset\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
writel(0, sock->addr + SOCK_MMCSD_NUM_BLOCKS);
|
||||
writel(host->clk_div | TIFM_MMCSD_POWER,
|
||||
sock->addr + SOCK_MMCSD_CONFIG);
|
||||
writel(TIFM_MMCSD_RXDE, sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
|
||||
|
||||
// command timeout fixed to 64 clocks for now
|
||||
writel(64, sock->addr + SOCK_MMCSD_COMMAND_TO);
|
||||
writel(TIFM_MMCSD_INAB, sock->addr + SOCK_MMCSD_COMMAND);
|
||||
|
||||
/* INAB should take much less than reset */
|
||||
for (rc = 1; rc <= 16; rc <<= 1) {
|
||||
host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
|
||||
writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
|
||||
if (!(host_status & TIFM_MMCSD_ERRMASK)
|
||||
&& (host_status & TIFM_MMCSD_EOC)) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
msleep(rc);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
printk(KERN_ERR DRIVER_NAME
|
||||
": card not ready - probe failed on initialization\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
writel(TIFM_MMCSD_DATAMASK | TIFM_MMCSD_ERRMASK,
|
||||
sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
mmiowb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tifm_sd_probe(struct tifm_dev *sock)
|
||||
@ -801,77 +866,37 @@ static int tifm_sd_probe(struct tifm_dev *sock)
|
||||
return -ENOMEM;
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->dev = sock;
|
||||
host->clk_div = 61;
|
||||
init_waitqueue_head(&host->can_eject);
|
||||
INIT_WORK(&host->cmd_handler, tifm_sd_register_host);
|
||||
setup_timer(&host->timer, tifm_sd_abort, (unsigned long)host);
|
||||
|
||||
tifm_set_drvdata(sock, mmc);
|
||||
sock->signal_irq = tifm_sd_signal_irq;
|
||||
|
||||
host->clk_freq = 20000000;
|
||||
host->dev = sock;
|
||||
host->timeout_jiffies = msecs_to_jiffies(1000);
|
||||
|
||||
init_waitqueue_head(&host->notify);
|
||||
tasklet_init(&host->finish_tasklet,
|
||||
no_dma ? tifm_sd_end_cmd_nodma : tifm_sd_end_cmd,
|
||||
(unsigned long)host);
|
||||
setup_timer(&host->timer, tifm_sd_abort, (unsigned long)host);
|
||||
|
||||
tifm_sd_ops.request = no_dma ? tifm_sd_request_nodma : tifm_sd_request;
|
||||
mmc->ops = &tifm_sd_ops;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;
|
||||
mmc->f_min = 20000000 / 60;
|
||||
mmc->f_max = 24000000;
|
||||
mmc->max_hw_segs = 1;
|
||||
mmc->max_phys_segs = 1;
|
||||
mmc->max_sectors = 127;
|
||||
mmc->max_seg_size = mmc->max_sectors << 11; //2k maximum hw block length
|
||||
sock->signal_irq = tifm_sd_signal_irq;
|
||||
rc = tifm_sd_initialize_host(host);
|
||||
|
||||
writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
writel(TIFM_MMCSD_RESET, sock->addr + SOCK_MMCSD_SYSTEM_CONTROL);
|
||||
writel(host->clk_div | TIFM_MMCSD_POWER,
|
||||
sock->addr + SOCK_MMCSD_CONFIG);
|
||||
|
||||
for (rc = 0; rc < 50; rc++) {
|
||||
/* Wait for reset ack */
|
||||
if (1 & readl(sock->addr + SOCK_MMCSD_SYSTEM_STATUS)) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
msleep(10);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
printk(KERN_ERR DRIVER_NAME
|
||||
": card not ready - probe failed\n");
|
||||
mmc_free_host(mmc);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
writel(0, sock->addr + SOCK_MMCSD_NUM_BLOCKS);
|
||||
writel(host->clk_div | TIFM_MMCSD_POWER,
|
||||
sock->addr + SOCK_MMCSD_CONFIG);
|
||||
writel(TIFM_MMCSD_RXDE, sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
|
||||
writel(TIFM_MMCSD_DATAMASK | TIFM_MMCSD_ERRMASK,
|
||||
sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
|
||||
writel(64, sock->addr + SOCK_MMCSD_COMMAND_TO); // command timeout 64 clocks for now
|
||||
writel(TIFM_MMCSD_INAB, sock->addr + SOCK_MMCSD_COMMAND);
|
||||
writel(host->clk_div | TIFM_MMCSD_POWER,
|
||||
sock->addr + SOCK_MMCSD_CONFIG);
|
||||
|
||||
mod_timer(&host->timer, jiffies + host->timeout_jiffies);
|
||||
if (!rc)
|
||||
rc = mmc_add_host(mmc);
|
||||
if (rc)
|
||||
goto out_free_mmc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tifm_sd_host_is_down(struct tifm_dev *sock)
|
||||
{
|
||||
struct mmc_host *mmc = tifm_get_drvdata(sock);
|
||||
struct tifm_sd *host = mmc_priv(mmc);
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
|
||||
spin_lock_irqsave(&sock->lock, flags);
|
||||
rc = (host->flags & EJECT_DONE);
|
||||
spin_unlock_irqrestore(&sock->lock, flags);
|
||||
out_free_mmc:
|
||||
mmc_free_host(mmc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -879,27 +904,17 @@ static void tifm_sd_remove(struct tifm_dev *sock)
|
||||
{
|
||||
struct mmc_host *mmc = tifm_get_drvdata(sock);
|
||||
struct tifm_sd *host = mmc_priv(mmc);
|
||||
unsigned long flags;
|
||||
|
||||
del_timer_sync(&host->timer);
|
||||
spin_lock_irqsave(&sock->lock, flags);
|
||||
host->flags |= EJECT;
|
||||
if (host->req)
|
||||
queue_work(sock->wq, &host->cmd_handler);
|
||||
spin_unlock_irqrestore(&sock->lock, flags);
|
||||
wait_event_timeout(host->can_eject, tifm_sd_host_is_down(sock),
|
||||
host->timeout_jiffies);
|
||||
|
||||
if (host->flags & HOST_REG)
|
||||
mmc_remove_host(mmc);
|
||||
tifm_sd_terminate(host);
|
||||
wait_event_timeout(host->notify, host->flags & EJECT_DONE,
|
||||
host->timeout_jiffies);
|
||||
tasklet_kill(&host->finish_tasklet);
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
/* The meaning of the bit majority in this constant is unknown. */
|
||||
writel(0xfff8 & readl(sock->addr + SOCK_CONTROL),
|
||||
sock->addr + SOCK_CONTROL);
|
||||
writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
|
||||
writel(TIFM_FIFO_INT_SETALL,
|
||||
sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
|
||||
writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
|
||||
|
||||
tifm_set_drvdata(sock, NULL);
|
||||
mmc_free_host(mmc);
|
||||
|
@ -89,8 +89,7 @@ struct tifm_dev {
|
||||
char __iomem *addr;
|
||||
spinlock_t lock;
|
||||
tifm_media_id media_id;
|
||||
char wq_name[KOBJ_NAME_LEN];
|
||||
struct workqueue_struct *wq;
|
||||
unsigned int socket_id;
|
||||
|
||||
unsigned int (*signal_irq)(struct tifm_dev *sock,
|
||||
unsigned int sock_irq_status);
|
||||
@ -132,7 +131,7 @@ void tifm_free_device(struct device *dev);
|
||||
void tifm_free_adapter(struct tifm_adapter *fm);
|
||||
int tifm_add_adapter(struct tifm_adapter *fm);
|
||||
void tifm_remove_adapter(struct tifm_adapter *fm);
|
||||
struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm, unsigned int id);
|
||||
struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm);
|
||||
int tifm_register_driver(struct tifm_driver *drv);
|
||||
void tifm_unregister_driver(struct tifm_driver *drv);
|
||||
void tifm_eject(struct tifm_dev *sock);
|
||||
|
Loading…
Reference in New Issue
Block a user