Skip to content

Commit

Permalink
tifm_sd: restructure initialization, removal and command handling
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alex Dubov authored and Pierre Ossman committed Feb 4, 2007
1 parent 83d420b commit 8e02f85
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 108 deletions.
3 changes: 2 additions & 1 deletion drivers/misc/tifm_7xx1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
11 changes: 2 additions & 9 deletions drivers/misc/tifm_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
205 changes: 110 additions & 95 deletions drivers/mmc/tifm_sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ typedef enum {

enum {
FIFO_RDY = 0x0001, /* hardware dependent value */
HOST_REG = 0x0002,
EJECT = 0x0004,
EJECT_DONE = 0x0008,
CARD_BUSY = 0x0010,
Expand All @@ -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;
Expand Down Expand Up @@ -317,7 +316,7 @@ static void tifm_sd_process_cmd(struct tifm_dev *sock, struct tifm_sd *host,
}
break;
case READY:
queue_work(sock->wq, &host->cmd_handler);
tasklet_schedule(&host->finish_tasklet);
return;
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -517,9 +514,9 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq)
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;
Expand Down Expand Up @@ -616,9 +613,9 @@ static void tifm_sd_request_nodma(struct mmc_host *mmc, struct mmc_request *mrq)
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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -801,105 +866,55 @@ 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;
}

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);
Expand Down
5 changes: 2 additions & 3 deletions include/linux/tifm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 8e02f85

Please sign in to comment.