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: mmc: at91_mci: fix hanging and rework to match flowcharts mmc: at91_mci typo sdhci: Fix "Unexpected interrupt" handling mmc: fix silly copy-and-paste error mmc: move layer init and workqueue to core file mmc: refactor host class handling mmc: refactor bus operations sdhci: add ene controller id mmc: bounce requests for simple hosts
This commit is contained in:
commit
4c75f7416f
|
@ -14,3 +14,21 @@ config MMC_BLOCK
|
|||
mount the filesystem. Almost everyone wishing MMC support
|
||||
should say Y or M here.
|
||||
|
||||
config MMC_BLOCK_BOUNCE
|
||||
bool "Use bounce buffer for simple hosts"
|
||||
depends on MMC_BLOCK
|
||||
default y
|
||||
help
|
||||
SD/MMC is a high latency protocol where it is crucial to
|
||||
send large requests in order to get high performance. Many
|
||||
controllers, however, are restricted to continuous memory
|
||||
(i.e. they can't do scatter-gather), something the kernel
|
||||
rarely can provide.
|
||||
|
||||
Say Y here to help these restricted hosts by bouncing
|
||||
requests back and forth from a large buffer. You will get
|
||||
a big performance gain at the cost of up to 64 KiB of
|
||||
physical memory.
|
||||
|
||||
If unsure, say Y here.
|
||||
|
||||
|
|
|
@ -262,7 +262,9 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
|||
}
|
||||
|
||||
brq.data.sg = mq->sg;
|
||||
brq.data.sg_len = blk_rq_map_sg(req->q, req, brq.data.sg);
|
||||
brq.data.sg_len = mmc_queue_map_sg(mq);
|
||||
|
||||
mmc_queue_bounce_pre(mq);
|
||||
|
||||
if (brq.data.blocks !=
|
||||
(req->nr_sectors >> (md->block_bits - 9))) {
|
||||
|
@ -279,6 +281,9 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
|||
}
|
||||
|
||||
mmc_wait_for_req(card->host, &brq.mrq);
|
||||
|
||||
mmc_queue_bounce_post(mq);
|
||||
|
||||
if (brq.cmd.error) {
|
||||
printk(KERN_ERR "%s: error %d sending read/write command\n",
|
||||
req->rq_disk->disk_name, brq.cmd.error);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <linux/mmc/host.h>
|
||||
#include "queue.h"
|
||||
|
||||
#define MMC_QUEUE_BOUNCESZ 65536
|
||||
|
||||
#define MMC_QUEUE_SUSPENDED (1 << 0)
|
||||
|
||||
/*
|
||||
|
@ -118,6 +120,7 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock
|
|||
struct mmc_host *host = card->host;
|
||||
u64 limit = BLK_BOUNCE_HIGH;
|
||||
int ret;
|
||||
unsigned int bouncesz;
|
||||
|
||||
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
|
||||
limit = *mmc_dev(host)->dma_mask;
|
||||
|
@ -127,21 +130,61 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock
|
|||
if (!mq->queue)
|
||||
return -ENOMEM;
|
||||
|
||||
blk_queue_prep_rq(mq->queue, mmc_prep_request);
|
||||
blk_queue_bounce_limit(mq->queue, limit);
|
||||
blk_queue_max_sectors(mq->queue, host->max_req_size / 512);
|
||||
blk_queue_max_phys_segments(mq->queue, host->max_phys_segs);
|
||||
blk_queue_max_hw_segments(mq->queue, host->max_hw_segs);
|
||||
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
|
||||
|
||||
mq->queue->queuedata = mq;
|
||||
mq->req = NULL;
|
||||
|
||||
mq->sg = kmalloc(sizeof(struct scatterlist) * host->max_phys_segs,
|
||||
GFP_KERNEL);
|
||||
if (!mq->sg) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_queue;
|
||||
blk_queue_prep_rq(mq->queue, mmc_prep_request);
|
||||
|
||||
#ifdef CONFIG_MMC_BLOCK_BOUNCE
|
||||
if (host->max_hw_segs == 1) {
|
||||
bouncesz = MMC_QUEUE_BOUNCESZ;
|
||||
|
||||
if (bouncesz > host->max_req_size)
|
||||
bouncesz = host->max_req_size;
|
||||
if (bouncesz > host->max_seg_size)
|
||||
bouncesz = host->max_seg_size;
|
||||
|
||||
mq->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
|
||||
if (!mq->bounce_buf) {
|
||||
printk(KERN_WARNING "%s: unable to allocate "
|
||||
"bounce buffer\n", mmc_card_name(card));
|
||||
} else {
|
||||
blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_HIGH);
|
||||
blk_queue_max_sectors(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_phys_segments(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_hw_segments(mq->queue, bouncesz / 512);
|
||||
blk_queue_max_segment_size(mq->queue, bouncesz);
|
||||
|
||||
mq->sg = kmalloc(sizeof(struct scatterlist),
|
||||
GFP_KERNEL);
|
||||
if (!mq->sg) {
|
||||
ret = -ENOMEM;
|
||||
goto free_bounce_buf;
|
||||
}
|
||||
|
||||
mq->bounce_sg = kmalloc(sizeof(struct scatterlist) *
|
||||
bouncesz / 512, GFP_KERNEL);
|
||||
if (!mq->bounce_sg) {
|
||||
ret = -ENOMEM;
|
||||
goto free_sg;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mq->bounce_buf) {
|
||||
blk_queue_bounce_limit(mq->queue, limit);
|
||||
blk_queue_max_sectors(mq->queue, host->max_req_size / 512);
|
||||
blk_queue_max_phys_segments(mq->queue, host->max_phys_segs);
|
||||
blk_queue_max_hw_segments(mq->queue, host->max_hw_segs);
|
||||
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
|
||||
|
||||
mq->sg = kmalloc(sizeof(struct scatterlist) *
|
||||
host->max_phys_segs, GFP_KERNEL);
|
||||
if (!mq->sg) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_queue;
|
||||
}
|
||||
}
|
||||
|
||||
init_MUTEX(&mq->thread_sem);
|
||||
|
@ -149,14 +192,21 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock
|
|||
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");
|
||||
if (IS_ERR(mq->thread)) {
|
||||
ret = PTR_ERR(mq->thread);
|
||||
goto free_sg;
|
||||
goto free_bounce_sg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_bounce_sg:
|
||||
if (mq->bounce_sg)
|
||||
kfree(mq->bounce_sg);
|
||||
mq->bounce_sg = NULL;
|
||||
free_sg:
|
||||
kfree(mq->sg);
|
||||
mq->sg = NULL;
|
||||
free_bounce_buf:
|
||||
if (mq->bounce_buf)
|
||||
kfree(mq->bounce_buf);
|
||||
mq->bounce_buf = NULL;
|
||||
cleanup_queue:
|
||||
blk_cleanup_queue(mq->queue);
|
||||
return ret;
|
||||
|
@ -178,9 +228,17 @@ void mmc_cleanup_queue(struct mmc_queue *mq)
|
|||
/* Then terminate our worker thread */
|
||||
kthread_stop(mq->thread);
|
||||
|
||||
if (mq->bounce_sg)
|
||||
kfree(mq->bounce_sg);
|
||||
mq->bounce_sg = NULL;
|
||||
|
||||
kfree(mq->sg);
|
||||
mq->sg = NULL;
|
||||
|
||||
if (mq->bounce_buf)
|
||||
kfree(mq->bounce_buf);
|
||||
mq->bounce_buf = NULL;
|
||||
|
||||
blk_cleanup_queue(mq->queue);
|
||||
|
||||
mq->card = NULL;
|
||||
|
@ -231,3 +289,108 @@ void mmc_queue_resume(struct mmc_queue *mq)
|
|||
}
|
||||
}
|
||||
|
||||
static void copy_sg(struct scatterlist *dst, unsigned int dst_len,
|
||||
struct scatterlist *src, unsigned int src_len)
|
||||
{
|
||||
unsigned int chunk;
|
||||
char *dst_buf, *src_buf;
|
||||
unsigned int dst_size, src_size;
|
||||
|
||||
dst_buf = NULL;
|
||||
src_buf = NULL;
|
||||
dst_size = 0;
|
||||
src_size = 0;
|
||||
|
||||
while (src_len) {
|
||||
BUG_ON(dst_len == 0);
|
||||
|
||||
if (dst_size == 0) {
|
||||
dst_buf = page_address(dst->page) + dst->offset;
|
||||
dst_size = dst->length;
|
||||
}
|
||||
|
||||
if (src_size == 0) {
|
||||
src_buf = page_address(src->page) + src->offset;
|
||||
src_size = src->length;
|
||||
}
|
||||
|
||||
chunk = min(dst_size, src_size);
|
||||
|
||||
memcpy(dst_buf, src_buf, chunk);
|
||||
|
||||
dst_buf += chunk;
|
||||
src_buf += chunk;
|
||||
dst_size -= chunk;
|
||||
src_size -= chunk;
|
||||
|
||||
if (dst_size == 0) {
|
||||
dst++;
|
||||
dst_len--;
|
||||
}
|
||||
|
||||
if (src_size == 0) {
|
||||
src++;
|
||||
src_len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int mmc_queue_map_sg(struct mmc_queue *mq)
|
||||
{
|
||||
unsigned int sg_len;
|
||||
|
||||
if (!mq->bounce_buf)
|
||||
return blk_rq_map_sg(mq->queue, mq->req, mq->sg);
|
||||
|
||||
BUG_ON(!mq->bounce_sg);
|
||||
|
||||
sg_len = blk_rq_map_sg(mq->queue, mq->req, mq->bounce_sg);
|
||||
|
||||
mq->bounce_sg_len = sg_len;
|
||||
|
||||
/*
|
||||
* Shortcut in the event we only get a single entry.
|
||||
*/
|
||||
if (sg_len == 1) {
|
||||
memcpy(mq->sg, mq->bounce_sg, sizeof(struct scatterlist));
|
||||
return 1;
|
||||
}
|
||||
|
||||
mq->sg[0].page = virt_to_page(mq->bounce_buf);
|
||||
mq->sg[0].offset = offset_in_page(mq->bounce_buf);
|
||||
mq->sg[0].length = 0;
|
||||
|
||||
while (sg_len) {
|
||||
mq->sg[0].length += mq->bounce_sg[sg_len - 1].length;
|
||||
sg_len--;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void mmc_queue_bounce_pre(struct mmc_queue *mq)
|
||||
{
|
||||
if (!mq->bounce_buf)
|
||||
return;
|
||||
|
||||
if (mq->bounce_sg_len == 1)
|
||||
return;
|
||||
if (rq_data_dir(mq->req) != WRITE)
|
||||
return;
|
||||
|
||||
copy_sg(mq->sg, 1, mq->bounce_sg, mq->bounce_sg_len);
|
||||
}
|
||||
|
||||
void mmc_queue_bounce_post(struct mmc_queue *mq)
|
||||
{
|
||||
if (!mq->bounce_buf)
|
||||
return;
|
||||
|
||||
if (mq->bounce_sg_len == 1)
|
||||
return;
|
||||
if (rq_data_dir(mq->req) != READ)
|
||||
return;
|
||||
|
||||
copy_sg(mq->bounce_sg, mq->bounce_sg_len, mq->sg, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ struct mmc_queue {
|
|||
void *data;
|
||||
struct request_queue *queue;
|
||||
struct scatterlist *sg;
|
||||
char *bounce_buf;
|
||||
struct scatterlist *bounce_sg;
|
||||
unsigned int bounce_sg_len;
|
||||
};
|
||||
|
||||
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *);
|
||||
|
@ -21,4 +24,8 @@ extern void mmc_cleanup_queue(struct mmc_queue *);
|
|||
extern void mmc_queue_suspend(struct mmc_queue *);
|
||||
extern void mmc_queue_resume(struct mmc_queue *);
|
||||
|
||||
extern unsigned int mmc_queue_map_sg(struct mmc_queue *);
|
||||
extern void mmc_queue_bounce_pre(struct mmc_queue *);
|
||||
extern void mmc_queue_bounce_post(struct mmc_queue *);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,5 +7,6 @@ ifeq ($(CONFIG_MMC_DEBUG),y)
|
|||
endif
|
||||
|
||||
obj-$(CONFIG_MMC) += mmc_core.o
|
||||
mmc_core-y := core.o sysfs.o mmc.o mmc_ops.o sd.o sd_ops.o
|
||||
mmc_core-y := core.o sysfs.o bus.o host.o \
|
||||
mmc.o mmc_ops.o sd.o sd_ops.o
|
||||
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/bus.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC card bus driver model
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
|
||||
#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
|
||||
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
|
||||
|
||||
static ssize_t mmc_type_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
return sprintf(buf, "MMC\n");
|
||||
case MMC_TYPE_SD:
|
||||
return sprintf(buf, "SD\n");
|
||||
default:
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(type),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* This currently matches any MMC driver to any MMC card - drivers
|
||||
* themselves make the decision whether to drive this card in their
|
||||
* probe method.
|
||||
*/
|
||||
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int retval = 0, i = 0, length = 0;
|
||||
|
||||
#define add_env(fmt,val) do { \
|
||||
retval = add_uevent_var(envp, num_envp, &i, \
|
||||
buf, buf_size, &length, \
|
||||
fmt, val); \
|
||||
if (retval) \
|
||||
return retval; \
|
||||
} while (0);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
add_env("MMC_TYPE=%s", "MMC");
|
||||
break;
|
||||
case MMC_TYPE_SD:
|
||||
add_env("MMC_TYPE=%s", "SD");
|
||||
break;
|
||||
}
|
||||
|
||||
add_env("MMC_NAME=%s", mmc_card_name(card));
|
||||
|
||||
#undef add_env
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_bus_probe(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
return drv->probe(card);
|
||||
}
|
||||
|
||||
static int mmc_bus_remove(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
drv->remove(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->suspend)
|
||||
ret = drv->suspend(card, state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
ret = drv->resume(card);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bus_type mmc_bus_type = {
|
||||
.name = "mmc",
|
||||
.dev_attrs = mmc_dev_attrs,
|
||||
.match = mmc_bus_match,
|
||||
.uevent = mmc_bus_uevent,
|
||||
.probe = mmc_bus_probe,
|
||||
.remove = mmc_bus_remove,
|
||||
.suspend = mmc_bus_suspend,
|
||||
.resume = mmc_bus_resume,
|
||||
};
|
||||
|
||||
int mmc_register_bus(void)
|
||||
{
|
||||
return bus_register(&mmc_bus_type);
|
||||
}
|
||||
|
||||
void mmc_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&mmc_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_register_driver - register a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
int mmc_register_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_register_driver);
|
||||
|
||||
/**
|
||||
* mmc_unregister_driver - unregister a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
void mmc_unregister_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_unregister_driver);
|
||||
|
||||
static void mmc_release_card(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
kfree(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new MMC card structure.
|
||||
*/
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
||||
card = kmalloc(sizeof(struct mmc_card), GFP_KERNEL);
|
||||
if (!card)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
memset(card, 0, sizeof(struct mmc_card));
|
||||
|
||||
card->host = host;
|
||||
|
||||
device_initialize(&card->dev);
|
||||
|
||||
card->dev.parent = mmc_classdev(host);
|
||||
card->dev.bus = &mmc_bus_type;
|
||||
card->dev.release = mmc_release_card;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new MMC card with the driver model.
|
||||
*/
|
||||
int mmc_add_card(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
snprintf(card->dev.bus_id, sizeof(card->dev.bus_id),
|
||||
"%s:%04x", mmc_hostname(card->host), card->rca);
|
||||
|
||||
card->dev.uevent_suppress = 1;
|
||||
|
||||
ret = device_add(&card->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (card->host->bus_ops->sysfs_add) {
|
||||
ret = card->host->bus_ops->sysfs_add(card->host, card);
|
||||
if (ret) {
|
||||
device_del(&card->dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
card->dev.uevent_suppress = 0;
|
||||
|
||||
kobject_uevent(&card->dev.kobj, KOBJ_ADD);
|
||||
|
||||
mmc_card_set_present(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a new MMC card with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void mmc_remove_card(struct mmc_card *card)
|
||||
{
|
||||
if (mmc_card_present(card)) {
|
||||
if (card->host->bus_ops->sysfs_remove)
|
||||
card->host->bus_ops->sysfs_remove(card->host, card);
|
||||
device_del(&card->dev);
|
||||
}
|
||||
|
||||
put_device(&card->dev);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/bus.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_BUS_H
|
||||
#define _MMC_CORE_BUS_H
|
||||
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host);
|
||||
int mmc_add_card(struct mmc_card *card);
|
||||
void mmc_remove_card(struct mmc_card *card);
|
||||
|
||||
int mmc_register_bus(void);
|
||||
void mmc_unregister_bus(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -27,7 +27,8 @@
|
|||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "host.h"
|
||||
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
|
@ -35,6 +36,25 @@
|
|||
extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr);
|
||||
extern int mmc_attach_sd(struct mmc_host *host, u32 ocr);
|
||||
|
||||
static struct workqueue_struct *workqueue;
|
||||
|
||||
/*
|
||||
* Internal function. Schedule delayed work in the MMC work queue.
|
||||
*/
|
||||
static int mmc_schedule_delayed_work(struct delayed_work *work,
|
||||
unsigned long delay)
|
||||
{
|
||||
return queue_delayed_work(workqueue, work, delay);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Flush all scheduled work from the MMC work queue.
|
||||
*/
|
||||
static void mmc_flush_scheduled_work(void)
|
||||
{
|
||||
flush_workqueue(workqueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_request_done - finish processing an MMC request
|
||||
* @host: MMC host which completed request
|
||||
|
@ -368,22 +388,6 @@ void mmc_set_timing(struct mmc_host *host, unsigned int timing)
|
|||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new MMC card
|
||||
*/
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
||||
card = kmalloc(sizeof(struct mmc_card), GFP_KERNEL);
|
||||
if (!card)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mmc_init_card(card, host);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply power to the MMC stack. This is a two-stage process.
|
||||
* First, we enable power to the card without the clock running.
|
||||
|
@ -512,7 +516,7 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay)
|
|||
EXPORT_SYMBOL(mmc_detect_change);
|
||||
|
||||
|
||||
static void mmc_rescan(struct work_struct *work)
|
||||
void mmc_rescan(struct work_struct *work)
|
||||
{
|
||||
struct mmc_host *host =
|
||||
container_of(work, struct mmc_host, detect.work);
|
||||
|
@ -561,69 +565,13 @@ static void mmc_rescan(struct work_struct *work)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mmc_alloc_host - initialise the per-host structure.
|
||||
* @extra: sizeof private data structure
|
||||
* @dev: pointer to host device model structure
|
||||
*
|
||||
* Initialise the per-host structure.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
void mmc_start_host(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
host = mmc_alloc_host_sysfs(extra, dev);
|
||||
if (host) {
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
*/
|
||||
host->max_hw_segs = 1;
|
||||
host->max_phys_segs = 1;
|
||||
host->max_seg_size = PAGE_CACHE_SIZE;
|
||||
|
||||
host->max_req_size = PAGE_CACHE_SIZE;
|
||||
host->max_blk_size = 512;
|
||||
host->max_blk_count = PAGE_CACHE_SIZE / 512;
|
||||
}
|
||||
|
||||
return host;
|
||||
mmc_power_off(host);
|
||||
mmc_detect_change(host, 0);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_alloc_host);
|
||||
|
||||
/**
|
||||
* mmc_add_host - initialise host hardware
|
||||
* @host: mmc host
|
||||
*/
|
||||
int mmc_add_host(struct mmc_host *host)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_host_sysfs(host);
|
||||
if (ret == 0) {
|
||||
mmc_power_off(host);
|
||||
mmc_detect_change(host, 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_add_host);
|
||||
|
||||
/**
|
||||
* mmc_remove_host - remove host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Unregister and remove all cards associated with this host,
|
||||
* and power down the MMC bus.
|
||||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
void mmc_stop_host(struct mmc_host *host)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned long flags;
|
||||
|
@ -648,24 +596,8 @@ void mmc_remove_host(struct mmc_host *host)
|
|||
BUG_ON(host->card);
|
||||
|
||||
mmc_power_off(host);
|
||||
mmc_remove_host_sysfs(host);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_remove_host);
|
||||
|
||||
/**
|
||||
* mmc_free_host - free the host structure
|
||||
* @host: mmc host
|
||||
*
|
||||
* Free the host once all references to it have been dropped.
|
||||
*/
|
||||
void mmc_free_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_free_host_sysfs(host);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_free_host);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/**
|
||||
|
@ -726,4 +658,31 @@ EXPORT_SYMBOL(mmc_resume_host);
|
|||
|
||||
#endif
|
||||
|
||||
static int __init mmc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
workqueue = create_singlethread_workqueue("kmmcd");
|
||||
if (!workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mmc_register_bus();
|
||||
if (ret == 0) {
|
||||
ret = mmc_register_host_class();
|
||||
if (ret)
|
||||
mmc_unregister_bus();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mmc_exit(void)
|
||||
{
|
||||
mmc_unregister_host_class();
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue(workqueue);
|
||||
}
|
||||
|
||||
module_init(mmc_init);
|
||||
module_exit(mmc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
struct mmc_bus_ops {
|
||||
void (*remove)(struct mmc_host *);
|
||||
void (*detect)(struct mmc_host *);
|
||||
int (*sysfs_add)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*suspend)(struct mmc_host *);
|
||||
void (*resume)(struct mmc_host *);
|
||||
};
|
||||
|
@ -54,8 +56,6 @@ void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
|
|||
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
|
||||
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host);
|
||||
|
||||
static inline void mmc_delay(unsigned int ms)
|
||||
{
|
||||
if (ms < 1000 / HZ) {
|
||||
|
@ -66,5 +66,9 @@ static inline void mmc_delay(unsigned int ms)
|
|||
}
|
||||
}
|
||||
|
||||
void mmc_rescan(struct work_struct *work);
|
||||
void mmc_start_host(struct mmc_host *host);
|
||||
void mmc_stop_host(struct mmc_host *host);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/host.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC host class device management
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/pagemap.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "host.h"
|
||||
|
||||
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||||
|
||||
static void mmc_host_classdev_release(struct device *dev)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
kfree(host);
|
||||
}
|
||||
|
||||
static struct class mmc_host_class = {
|
||||
.name = "mmc_host",
|
||||
.dev_release = mmc_host_classdev_release,
|
||||
};
|
||||
|
||||
int mmc_register_host_class(void)
|
||||
{
|
||||
return class_register(&mmc_host_class);
|
||||
}
|
||||
|
||||
void mmc_unregister_host_class(void)
|
||||
{
|
||||
class_unregister(&mmc_host_class);
|
||||
}
|
||||
|
||||
static DEFINE_IDR(mmc_host_idr);
|
||||
static DEFINE_SPINLOCK(mmc_host_lock);
|
||||
|
||||
/**
|
||||
* mmc_alloc_host - initialise the per-host structure.
|
||||
* @extra: sizeof private data structure
|
||||
* @dev: pointer to host device model structure
|
||||
*
|
||||
* Initialise the per-host structure.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
host = kmalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
|
||||
if (!host)
|
||||
return NULL;
|
||||
|
||||
memset(host, 0, sizeof(struct mmc_host) + extra);
|
||||
|
||||
host->parent = dev;
|
||||
host->class_dev.parent = dev;
|
||||
host->class_dev.class = &mmc_host_class;
|
||||
device_initialize(&host->class_dev);
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
*/
|
||||
host->max_hw_segs = 1;
|
||||
host->max_phys_segs = 1;
|
||||
host->max_seg_size = PAGE_CACHE_SIZE;
|
||||
|
||||
host->max_req_size = PAGE_CACHE_SIZE;
|
||||
host->max_blk_size = 512;
|
||||
host->max_blk_count = PAGE_CACHE_SIZE / 512;
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_alloc_host);
|
||||
|
||||
/**
|
||||
* mmc_add_host - initialise host hardware
|
||||
* @host: mmc host
|
||||
*/
|
||||
int mmc_add_host(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
err = idr_get_new(&mmc_host_idr, host, &host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snprintf(host->class_dev.bus_id, BUS_ID_SIZE,
|
||||
"mmc%d", host->index);
|
||||
|
||||
err = device_add(&host->class_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mmc_start_host(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_add_host);
|
||||
|
||||
/**
|
||||
* mmc_remove_host - remove host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Unregister and remove all cards associated with this host,
|
||||
* and power down the MMC bus.
|
||||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_stop_host(host);
|
||||
|
||||
device_del(&host->class_dev);
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_remove_host);
|
||||
|
||||
/**
|
||||
* mmc_free_host - free the host structure
|
||||
* @host: mmc host
|
||||
*
|
||||
* Free the host once all references to it have been dropped.
|
||||
*/
|
||||
void mmc_free_host(struct mmc_host *host)
|
||||
{
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_free_host);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/host.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_HOST_H
|
||||
#define _MMC_CORE_HOST_H
|
||||
|
||||
int mmc_register_host_class(void);
|
||||
void mmc_unregister_host_class(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
|
@ -236,7 +237,7 @@ out:
|
|||
* In the case of a resume, "curcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
|
||||
static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
struct mmc_card *oldcard)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
@ -413,8 +414,7 @@ static void mmc_detect(struct mmc_host *host)
|
|||
mmc_release_host(host);
|
||||
|
||||
if (err != MMC_ERR_NONE) {
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
|
@ -422,6 +422,53 @@ static void mmc_detect(struct mmc_host *host)
|
|||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
|
@ -453,11 +500,9 @@ static void mmc_resume(struct mmc_host *host)
|
|||
|
||||
mmc_claim_host(host);
|
||||
|
||||
err = mmc_sd_init_card(host, host->ocr, host->card);
|
||||
err = mmc_init_card(host, host->ocr, host->card);
|
||||
if (err != MMC_ERR_NONE) {
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
|
||||
mmc_remove(host);
|
||||
mmc_detach_bus(host);
|
||||
}
|
||||
|
||||
|
@ -474,6 +519,8 @@ static void mmc_resume(struct mmc_host *host)
|
|||
static const struct mmc_bus_ops mmc_ops = {
|
||||
.remove = mmc_remove,
|
||||
.detect = mmc_detect,
|
||||
.sysfs_add = mmc_sysfs_add,
|
||||
.sysfs_remove = mmc_sysfs_remove,
|
||||
.suspend = mmc_suspend,
|
||||
.resume = mmc_resume,
|
||||
};
|
||||
|
@ -512,13 +559,13 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr)
|
|||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
err = mmc_sd_init_card(host, host->ocr, NULL);
|
||||
err = mmc_init_card(host, host->ocr, NULL);
|
||||
if (err != MMC_ERR_NONE)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_register_card(host->card);
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto reclaim_host;
|
||||
|
||||
|
|
|
@ -19,11 +19,10 @@
|
|||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
10000, 100000, 1000000, 10000000,
|
||||
0, 0, 0, 0
|
||||
|
@ -487,8 +486,7 @@ static void mmc_sd_detect(struct mmc_host *host)
|
|||
mmc_release_host(host);
|
||||
|
||||
if (err != MMC_ERR_NONE) {
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_sd_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
|
@ -496,6 +494,55 @@ static void mmc_sd_detect(struct mmc_host *host)
|
|||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_sd_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(scr),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sd_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_sd_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sd_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_sd_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
|
@ -529,9 +576,7 @@ static void mmc_sd_resume(struct mmc_host *host)
|
|||
|
||||
err = mmc_sd_init_card(host, host->ocr, host->card);
|
||||
if (err != MMC_ERR_NONE) {
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
|
||||
mmc_sd_remove(host);
|
||||
mmc_detach_bus(host);
|
||||
}
|
||||
|
||||
|
@ -548,6 +593,8 @@ static void mmc_sd_resume(struct mmc_host *host)
|
|||
static const struct mmc_bus_ops mmc_sd_ops = {
|
||||
.remove = mmc_sd_remove,
|
||||
.detect = mmc_sd_detect,
|
||||
.sysfs_add = mmc_sd_sysfs_add,
|
||||
.sysfs_remove = mmc_sd_sysfs_remove,
|
||||
.suspend = mmc_sd_suspend,
|
||||
.resume = mmc_sd_resume,
|
||||
};
|
||||
|
@ -599,7 +646,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr)
|
|||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_register_card(host->card);
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto reclaim_host;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* linux/drivers/mmc/core/sysfs.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
|
@ -9,352 +10,34 @@
|
|||
*
|
||||
* MMC sysfs/driver model support.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
|
||||
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
|
||||
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||||
|
||||
#define MMC_ATTR(name, fmt, args...) \
|
||||
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct mmc_card *card = dev_to_mmc_card(dev); \
|
||||
return sprintf(buf, fmt, args); \
|
||||
}
|
||||
|
||||
MMC_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]);
|
||||
MMC_ATTR(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
#define MMC_ATTR_RO(name) __ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL
|
||||
};
|
||||
|
||||
static struct device_attribute mmc_dev_attr_scr = MMC_ATTR_RO(scr);
|
||||
|
||||
|
||||
static void mmc_release_card(struct device *dev)
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
kfree(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* This currently matches any MMC driver to any MMC card - drivers
|
||||
* themselves make the decision whether to drive this card in their
|
||||
* probe method.
|
||||
*/
|
||||
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
char ccc[13];
|
||||
int retval = 0, i = 0, length = 0;
|
||||
|
||||
#define add_env(fmt,val) do { \
|
||||
retval = add_uevent_var(envp, num_envp, &i, \
|
||||
buf, buf_size, &length, \
|
||||
fmt, val); \
|
||||
if (retval) \
|
||||
return retval; \
|
||||
} while (0);
|
||||
|
||||
for (i = 0; i < 12; i++)
|
||||
ccc[i] = card->csd.cmdclass & (1 << i) ? '1' : '0';
|
||||
ccc[12] = '\0';
|
||||
|
||||
add_env("MMC_CCC=%s", ccc);
|
||||
add_env("MMC_MANFID=%06x", card->cid.manfid);
|
||||
add_env("MMC_NAME=%s", mmc_card_name(card));
|
||||
add_env("MMC_OEMID=%04x", card->cid.oemid);
|
||||
#undef add_env
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->suspend)
|
||||
ret = drv->suspend(card, state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
ret = drv->resume(card);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_probe(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
return drv->probe(card);
|
||||
}
|
||||
|
||||
static int mmc_bus_remove(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
drv->remove(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bus_type mmc_bus_type = {
|
||||
.name = "mmc",
|
||||
.dev_attrs = mmc_dev_attrs,
|
||||
.match = mmc_bus_match,
|
||||
.uevent = mmc_bus_uevent,
|
||||
.probe = mmc_bus_probe,
|
||||
.remove = mmc_bus_remove,
|
||||
.suspend = mmc_bus_suspend,
|
||||
.resume = mmc_bus_resume,
|
||||
};
|
||||
|
||||
/**
|
||||
* mmc_register_driver - register a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
int mmc_register_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_register_driver);
|
||||
|
||||
/**
|
||||
* mmc_unregister_driver - unregister a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
void mmc_unregister_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_unregister_driver);
|
||||
|
||||
|
||||
/*
|
||||
* Internal function. Initialise a MMC card structure.
|
||||
*/
|
||||
void mmc_init_card(struct mmc_card *card, struct mmc_host *host)
|
||||
{
|
||||
memset(card, 0, sizeof(struct mmc_card));
|
||||
card->host = host;
|
||||
device_initialize(&card->dev);
|
||||
card->dev.parent = mmc_classdev(host);
|
||||
card->dev.bus = &mmc_bus_type;
|
||||
card->dev.release = mmc_release_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Register a new MMC card with the driver model.
|
||||
*/
|
||||
int mmc_register_card(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
snprintf(card->dev.bus_id, sizeof(card->dev.bus_id),
|
||||
"%s:%04x", mmc_hostname(card->host), card->rca);
|
||||
|
||||
ret = device_add(&card->dev);
|
||||
if (ret == 0) {
|
||||
if (mmc_card_sd(card)) {
|
||||
ret = device_create_file(&card->dev, &mmc_dev_attr_scr);
|
||||
if (ret)
|
||||
device_del(&card->dev);
|
||||
for (i = 0; attr_name(attrs[i]); i++) {
|
||||
error = device_create_file(&card->dev, &attrs[i]);
|
||||
if (error) {
|
||||
while (--i >= 0)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret == 0)
|
||||
mmc_card_set_present(card);
|
||||
return ret;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Unregister a new MMC card with the
|
||||
* driver model, and (eventually) free it.
|
||||
*/
|
||||
void mmc_remove_card(struct mmc_card *card)
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
if (mmc_card_present(card)) {
|
||||
if (mmc_card_sd(card))
|
||||
device_remove_file(&card->dev, &mmc_dev_attr_scr);
|
||||
int i;
|
||||
|
||||
device_del(&card->dev);
|
||||
}
|
||||
|
||||
put_device(&card->dev);
|
||||
for (i = 0; attr_name(attrs[i]); i++)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
}
|
||||
|
||||
|
||||
static void mmc_host_classdev_release(struct device *dev)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
kfree(host);
|
||||
}
|
||||
|
||||
static struct class mmc_host_class = {
|
||||
.name = "mmc_host",
|
||||
.dev_release = mmc_host_classdev_release,
|
||||
};
|
||||
|
||||
static DEFINE_IDR(mmc_host_idr);
|
||||
static DEFINE_SPINLOCK(mmc_host_lock);
|
||||
|
||||
/*
|
||||
* Internal function. Allocate a new MMC host.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host_sysfs(int extra, struct device *dev)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
host = kmalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
|
||||
if (host) {
|
||||
memset(host, 0, sizeof(struct mmc_host) + extra);
|
||||
|
||||
host->parent = dev;
|
||||
host->class_dev.parent = dev;
|
||||
host->class_dev.class = &mmc_host_class;
|
||||
device_initialize(&host->class_dev);
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Register a new MMC host with the MMC class.
|
||||
*/
|
||||
int mmc_add_host_sysfs(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
err = idr_get_new(&mmc_host_idr, host, &host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snprintf(host->class_dev.bus_id, BUS_ID_SIZE,
|
||||
"mmc%d", host->index);
|
||||
|
||||
return device_add(&host->class_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Unregister a MMC host with the MMC class.
|
||||
*/
|
||||
void mmc_remove_host_sysfs(struct mmc_host *host)
|
||||
{
|
||||
device_del(&host->class_dev);
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Free a MMC host.
|
||||
*/
|
||||
void mmc_free_host_sysfs(struct mmc_host *host)
|
||||
{
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
||||
static struct workqueue_struct *workqueue;
|
||||
|
||||
/*
|
||||
* Internal function. Schedule delayed work in the MMC work queue.
|
||||
*/
|
||||
int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay)
|
||||
{
|
||||
return queue_delayed_work(workqueue, work, delay);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Flush all scheduled work from the MMC work queue.
|
||||
*/
|
||||
void mmc_flush_scheduled_work(void)
|
||||
{
|
||||
flush_workqueue(workqueue);
|
||||
}
|
||||
|
||||
static int __init mmc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
workqueue = create_singlethread_workqueue("kmmcd");
|
||||
if (!workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = bus_register(&mmc_bus_type);
|
||||
if (ret == 0) {
|
||||
ret = class_register(&mmc_host_class);
|
||||
if (ret)
|
||||
bus_unregister(&mmc_bus_type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mmc_exit(void)
|
||||
{
|
||||
class_unregister(&mmc_host_class);
|
||||
bus_unregister(&mmc_bus_type);
|
||||
destroy_workqueue(workqueue);
|
||||
}
|
||||
|
||||
module_init(mmc_init);
|
||||
module_exit(mmc_exit);
|
||||
|
|
|
@ -11,17 +11,16 @@
|
|||
#ifndef _MMC_CORE_SYSFS_H
|
||||
#define _MMC_CORE_SYSFS_H
|
||||
|
||||
void mmc_init_card(struct mmc_card *card, struct mmc_host *host);
|
||||
int mmc_register_card(struct mmc_card *card);
|
||||
void mmc_remove_card(struct mmc_card *card);
|
||||
#define MMC_ATTR_FN(name, fmt, args...) \
|
||||
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct mmc_card *card = container_of(dev, struct mmc_card, dev);\
|
||||
return sprintf(buf, fmt, args); \
|
||||
}
|
||||
|
||||
struct mmc_host *mmc_alloc_host_sysfs(int extra, struct device *dev);
|
||||
int mmc_add_host_sysfs(struct mmc_host *host);
|
||||
void mmc_remove_host_sysfs(struct mmc_host *host);
|
||||
void mmc_free_host_sysfs(struct mmc_host *host);
|
||||
#define MMC_ATTR_RO(name) __ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
|
||||
|
||||
int mmc_schedule_work(struct work_struct *work);
|
||||
int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay);
|
||||
void mmc_flush_scheduled_work(void);
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -78,8 +78,6 @@
|
|||
|
||||
#define DRIVER_NAME "at91_mci"
|
||||
|
||||
#undef SUPPORT_4WIRE
|
||||
|
||||
#define FL_SENT_COMMAND (1 << 0)
|
||||
#define FL_SENT_STOP (1 << 1)
|
||||
|
||||
|
@ -131,7 +129,7 @@ struct at91mci_host
|
|||
/*
|
||||
* Copy from sg to a dma block - used for transfers
|
||||
*/
|
||||
static inline void at91mci_sg_to_dma(struct at91mci_host *host, struct mmc_data *data)
|
||||
static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data *data)
|
||||
{
|
||||
unsigned int len, i, size;
|
||||
unsigned *dmabuf = host->buffer;
|
||||
|
@ -180,7 +178,7 @@ static inline void at91mci_sg_to_dma(struct at91mci_host *host, struct mmc_data
|
|||
/*
|
||||
* Prepare a dma read
|
||||
*/
|
||||
static void at91mci_pre_dma_read(struct at91mci_host *host)
|
||||
static void at91_mci_pre_dma_read(struct at91mci_host *host)
|
||||
{
|
||||
int i;
|
||||
struct scatterlist *sg;
|
||||
|
@ -248,7 +246,7 @@ static void at91mci_pre_dma_read(struct at91mci_host *host)
|
|||
/*
|
||||
* Handle after a dma read
|
||||
*/
|
||||
static void at91mci_post_dma_read(struct at91mci_host *host)
|
||||
static void at91_mci_post_dma_read(struct at91mci_host *host)
|
||||
{
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
@ -268,8 +266,6 @@ static void at91mci_post_dma_read(struct at91mci_host *host)
|
|||
}
|
||||
|
||||
while (host->in_use_index < host->transfer_index) {
|
||||
unsigned int *buffer;
|
||||
|
||||
struct scatterlist *sg;
|
||||
|
||||
pr_debug("finishing index %d\n", host->in_use_index);
|
||||
|
@ -280,29 +276,31 @@ static void at91mci_post_dma_read(struct at91mci_host *host)
|
|||
|
||||
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
|
||||
|
||||
/* Swap the contents of the buffer */
|
||||
buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
|
||||
pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
|
||||
|
||||
data->bytes_xfered += sg->length;
|
||||
|
||||
if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
|
||||
unsigned int *buffer;
|
||||
int index;
|
||||
|
||||
/* Swap the contents of the buffer */
|
||||
buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
|
||||
pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
|
||||
|
||||
for (index = 0; index < (sg->length / 4); index++)
|
||||
buffer[index] = swab32(buffer[index]);
|
||||
|
||||
kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
|
||||
}
|
||||
|
||||
kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
|
||||
flush_dcache_page(sg->page);
|
||||
}
|
||||
|
||||
/* Is there another transfer to trigger? */
|
||||
if (host->transfer_index < data->sg_len)
|
||||
at91mci_pre_dma_read(host);
|
||||
at91_mci_pre_dma_read(host);
|
||||
else {
|
||||
at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_ENDRX);
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_RXBUFF);
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
||||
}
|
||||
|
||||
pr_debug("post dma read done\n");
|
||||
|
@ -323,7 +321,6 @@ static void at91_mci_handle_transmitted(struct at91mci_host *host)
|
|||
|
||||
/* Now wait for cmd ready */
|
||||
at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_TXBUFE);
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
|
||||
|
||||
cmd = host->cmd;
|
||||
if (!cmd) return;
|
||||
|
@ -331,18 +328,53 @@ static void at91_mci_handle_transmitted(struct at91mci_host *host)
|
|||
data = cmd->data;
|
||||
if (!data) return;
|
||||
|
||||
if (cmd->data->flags & MMC_DATA_MULTI) {
|
||||
pr_debug("multiple write : wait for BLKE...\n");
|
||||
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;
|
||||
}
|
||||
|
||||
/*Handle after command sent ready*/
|
||||
static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
|
||||
{
|
||||
if (!host->cmd)
|
||||
return 1;
|
||||
else if (!host->cmd->data) {
|
||||
if (host->flags & FL_SENT_STOP) {
|
||||
/*After multi block write, we must wait for NOTBUSY*/
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
|
||||
} 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, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
|
||||
}
|
||||
|
||||
/* command not completed, have to wait */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Enable the controller
|
||||
*/
|
||||
static void at91_mci_enable(struct at91mci_host *host)
|
||||
{
|
||||
unsigned int mr;
|
||||
|
||||
at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
|
||||
at91_mci_write(host, AT91_MCI_DTOR, AT91_MCI_DTOMUL_1M | AT91_MCI_DTOCYC);
|
||||
at91_mci_write(host, AT91_MCI_MR, AT91_MCI_PDCMODE | 0x34a);
|
||||
mr = AT91_MCI_PDCMODE | 0x34a;
|
||||
|
||||
if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
|
||||
mr |= AT91_MCI_RDPROOF | AT91_MCI_WRPROOF;
|
||||
|
||||
at91_mci_write(host, AT91_MCI_MR, mr);
|
||||
|
||||
/* use Slot A or B (only one at same time) */
|
||||
at91_mci_write(host, AT91_MCI_SDCR, host->board->slot_b);
|
||||
|
@ -358,9 +390,8 @@ static void at91_mci_disable(struct at91mci_host *host)
|
|||
|
||||
/*
|
||||
* Send a command
|
||||
* return the interrupts to enable
|
||||
*/
|
||||
static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
|
||||
static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
|
||||
{
|
||||
unsigned int cmdr, mr;
|
||||
unsigned int block_length;
|
||||
|
@ -371,8 +402,7 @@ static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_
|
|||
|
||||
host->cmd = cmd;
|
||||
|
||||
/* Not sure if this is needed */
|
||||
#if 0
|
||||
/* Needed for leaving busy state before CMD1 */
|
||||
if ((at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_RTOE) && (cmd->opcode == 1)) {
|
||||
pr_debug("Clearing timeout\n");
|
||||
at91_mci_write(host, AT91_MCI_ARGR, 0);
|
||||
|
@ -382,7 +412,7 @@ static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_
|
|||
pr_debug("Clearing: SR = %08X\n", at91_mci_read(host, AT91_MCI_SR));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
cmdr = cmd->opcode;
|
||||
|
||||
if (mmc_resp_type(cmd) == MMC_RSP_NONE)
|
||||
|
@ -439,50 +469,48 @@ static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_
|
|||
at91_mci_write(host, ATMEL_PDC_TCR, 0);
|
||||
at91_mci_write(host, ATMEL_PDC_TNPR, 0);
|
||||
at91_mci_write(host, ATMEL_PDC_TNCR, 0);
|
||||
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);
|
||||
|
||||
at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
|
||||
at91_mci_write(host, AT91_MCI_CMDR, cmdr);
|
||||
return AT91_MCI_CMDRDY;
|
||||
}
|
||||
/*
|
||||
* Disable the PDC controller
|
||||
*/
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
||||
|
||||
mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff; /* zero block length and PDC mode */
|
||||
at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
|
||||
if (cmdr & AT91_MCI_TRCMD_START) {
|
||||
data->bytes_xfered = 0;
|
||||
host->transfer_index = 0;
|
||||
host->in_use_index = 0;
|
||||
if (cmdr & AT91_MCI_TRDIR) {
|
||||
/*
|
||||
* Handle a read
|
||||
*/
|
||||
host->buffer = NULL;
|
||||
host->total_length = 0;
|
||||
|
||||
/*
|
||||
* Disable the PDC controller
|
||||
*/
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
||||
at91_mci_pre_dma_read(host);
|
||||
ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Handle a write
|
||||
*/
|
||||
host->total_length = block_length * blocks;
|
||||
host->buffer = dma_alloc_coherent(NULL,
|
||||
host->total_length,
|
||||
&host->physical_address, GFP_KERNEL);
|
||||
|
||||
if (cmdr & AT91_MCI_TRCMD_START) {
|
||||
data->bytes_xfered = 0;
|
||||
host->transfer_index = 0;
|
||||
host->in_use_index = 0;
|
||||
if (cmdr & AT91_MCI_TRDIR) {
|
||||
/*
|
||||
* Handle a read
|
||||
*/
|
||||
host->buffer = NULL;
|
||||
host->total_length = 0;
|
||||
at91_mci_sg_to_dma(host, data);
|
||||
|
||||
at91mci_pre_dma_read(host);
|
||||
ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Handle a write
|
||||
*/
|
||||
host->total_length = block_length * blocks;
|
||||
host->buffer = dma_alloc_coherent(NULL,
|
||||
host->total_length,
|
||||
&host->physical_address, GFP_KERNEL);
|
||||
pr_debug("Transmitting %d bytes\n", host->total_length);
|
||||
|
||||
at91mci_sg_to_dma(host, data);
|
||||
|
||||
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);
|
||||
ier = AT91_MCI_TXBUFE;
|
||||
at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
|
||||
at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
|
||||
ier = AT91_MCI_CMDRDY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,39 +525,24 @@ static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_
|
|||
if (cmdr & AT91_MCI_TRCMD_START) {
|
||||
if (cmdr & AT91_MCI_TRDIR)
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
|
||||
else
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
|
||||
}
|
||||
return ier;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for a command to complete
|
||||
*/
|
||||
static void at91mci_process_command(struct at91mci_host *host, struct mmc_command *cmd)
|
||||
{
|
||||
unsigned int ier;
|
||||
|
||||
ier = at91_mci_send_command(host, cmd);
|
||||
|
||||
pr_debug("setting ier to %08X\n", ier);
|
||||
|
||||
/* Stop on errors or the required value */
|
||||
/* Enable selected interrupts */
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_ERRORS | ier);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the next step in the request
|
||||
*/
|
||||
static void at91mci_process_next(struct at91mci_host *host)
|
||||
static void at91_mci_process_next(struct at91mci_host *host)
|
||||
{
|
||||
if (!(host->flags & FL_SENT_COMMAND)) {
|
||||
host->flags |= FL_SENT_COMMAND;
|
||||
at91mci_process_command(host, host->request->cmd);
|
||||
at91_mci_send_command(host, host->request->cmd);
|
||||
}
|
||||
else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
|
||||
host->flags |= FL_SENT_STOP;
|
||||
at91mci_process_command(host, host->request->stop);
|
||||
at91_mci_send_command(host, host->request->stop);
|
||||
}
|
||||
else
|
||||
mmc_request_done(host->mmc, host->request);
|
||||
|
@ -538,7 +551,7 @@ static void at91mci_process_next(struct at91mci_host *host)
|
|||
/*
|
||||
* Handle a command that has been completed
|
||||
*/
|
||||
static void at91mci_completed_command(struct at91mci_host *host)
|
||||
static void at91_mci_completed_command(struct at91mci_host *host)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
unsigned int status;
|
||||
|
@ -583,7 +596,7 @@ static void at91mci_completed_command(struct at91mci_host *host)
|
|||
else
|
||||
cmd->error = MMC_ERR_NONE;
|
||||
|
||||
at91mci_process_next(host);
|
||||
at91_mci_process_next(host);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -595,7 +608,7 @@ static void at91_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|||
host->request = mrq;
|
||||
host->flags = 0;
|
||||
|
||||
at91mci_process_next(host);
|
||||
at91_mci_process_next(host);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -698,29 +711,33 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
|
|||
at91_mci_handle_transmitted(host);
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_ENDRX) {
|
||||
pr_debug("ENDRX\n");
|
||||
at91_mci_post_dma_read(host);
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_RXBUFF) {
|
||||
pr_debug("RX buffer full\n");
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
|
||||
at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
||||
at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_RXBUFF | AT91_MCI_ENDRX);
|
||||
completed = 1;
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_ENDTX)
|
||||
pr_debug("Transmit has ended\n");
|
||||
|
||||
if (int_status & AT91_MCI_ENDRX) {
|
||||
pr_debug("Receive has ended\n");
|
||||
at91mci_post_dma_read(host);
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_NOTBUSY) {
|
||||
pr_debug("Card is ready\n");
|
||||
at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
|
||||
completed = 1;
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_DTIP)
|
||||
pr_debug("Data transfer in progress\n");
|
||||
|
||||
if (int_status & AT91_MCI_BLKE)
|
||||
if (int_status & AT91_MCI_BLKE) {
|
||||
pr_debug("Block transfer has ended\n");
|
||||
completed = 1;
|
||||
}
|
||||
|
||||
if (int_status & AT91_MCI_TXRDY)
|
||||
pr_debug("Ready to transmit\n");
|
||||
|
@ -730,14 +747,14 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
|
|||
|
||||
if (int_status & AT91_MCI_CMDRDY) {
|
||||
pr_debug("Command ready\n");
|
||||
completed = 1;
|
||||
completed = at91_mci_handle_cmdrdy(host);
|
||||
}
|
||||
}
|
||||
|
||||
if (completed) {
|
||||
pr_debug("Completed command\n");
|
||||
at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
|
||||
at91mci_completed_command(host);
|
||||
at91_mci_completed_command(host);
|
||||
} else
|
||||
at91_mci_write(host, AT91_MCI_IDR, int_status);
|
||||
|
||||
|
@ -830,11 +847,11 @@ static int __init at91_mci_probe(struct platform_device *pdev)
|
|||
host->bus_mode = 0;
|
||||
host->board = pdev->dev.platform_data;
|
||||
if (host->board->wire4) {
|
||||
#ifdef SUPPORT_4WIRE
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
#else
|
||||
printk("AT91 MMC: 4 wire bus mode not supported by this driver - using 1 wire\n");
|
||||
#endif
|
||||
if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
else
|
||||
printk("AT91 MMC: 4 wire bus mode not supported"
|
||||
" - using 1 wire\n");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -70,6 +70,14 @@ static const struct pci_device_id pci_ids[] __devinitdata = {
|
|||
.driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE,
|
||||
},
|
||||
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_ENE,
|
||||
.device = PCI_DEVICE_ID_ENE_CB712_SD_2,
|
||||
.subvendor = PCI_ANY_ID,
|
||||
.subdevice = PCI_ANY_ID,
|
||||
.driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE,
|
||||
},
|
||||
|
||||
{ /* Generic SD host controller */
|
||||
PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
|
||||
},
|
||||
|
@ -1022,7 +1030,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
|
|||
writel(SDHCI_INT_BUS_POWER, host->ioaddr + SDHCI_INT_STATUS);
|
||||
}
|
||||
|
||||
intmask &= SDHCI_INT_BUS_POWER;
|
||||
intmask &= ~SDHCI_INT_BUS_POWER;
|
||||
|
||||
if (intmask) {
|
||||
printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n",
|
||||
|
|
|
@ -2000,6 +2000,7 @@
|
|||
|
||||
#define PCI_VENDOR_ID_ENE 0x1524
|
||||
#define PCI_DEVICE_ID_ENE_CB712_SD 0x0550
|
||||
#define PCI_DEVICE_ID_ENE_CB712_SD_2 0x0551
|
||||
#define PCI_DEVICE_ID_ENE_1211 0x1211
|
||||
#define PCI_DEVICE_ID_ENE_1225 0x1225
|
||||
#define PCI_DEVICE_ID_ENE_1410 0x1410
|
||||
|
|
Loading…
Reference in New Issue