Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

35
drivers/mmc/Kconfig Normal file
View file

@ -0,0 +1,35 @@
#
# MMC subsystem configuration
#
menuconfig MMC
tristate "MMC/SD/SDIO card support"
depends on HAS_IOMEM
help
This selects MultiMediaCard, Secure Digital and Secure
Digital I/O support.
If you want MMC/SD/SDIO support, you should say Y here and
also to your specific host controller driver.
config MMC_DEBUG
bool "MMC debugging"
depends on MMC != n
help
This is an option for use by developers; most people should
say N here. This enables MMC core and driver debugging.
config MMC_BLOCK_DEFERRED_RESUME
bool "Enable Deferred Resume"
depends on MMC != n
default n
if MMC
source "drivers/mmc/core/Kconfig"
source "drivers/mmc/card/Kconfig"
source "drivers/mmc/host/Kconfig"
endif # MMC

9
drivers/mmc/Makefile Normal file
View file

@ -0,0 +1,9 @@
#
# Makefile for the kernel mmc device drivers.
#
subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG
obj-$(CONFIG_MMC) += core/
obj-$(CONFIG_MMC) += card/
obj-$(subst m,y,$(CONFIG_MMC)) += host/

70
drivers/mmc/card/Kconfig Normal file
View file

@ -0,0 +1,70 @@
#
# MMC/SD card drivers
#
comment "MMC/SD/SDIO Card Drivers"
config MMC_BLOCK
tristate "MMC block device driver"
depends on BLOCK
default y
help
Say Y here to enable the MMC block device driver support.
This provides a block device driver, which you can use to
mount the filesystem. Almost everyone wishing MMC support
should say Y or M here.
config MMC_BLOCK_MINORS
int "Number of minors per block device"
depends on MMC_BLOCK
range 4 256
default 8
help
Number of minors per block device. One is needed for every
partition on the disk (plus one for the whole disk).
Number of total MMC minors available is 256, so your number
of supported block devices will be limited to 256 divided
by this number.
Default is 8 to be backwards compatible with previous
hardwired device numbering.
If unsure, say 8 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.
config SDIO_UART
tristate "SDIO UART/GPS class support"
depends on TTY
help
SDIO function driver for SDIO cards that implements the UART
class, as well as the GPS class which appears like a UART.
config MMC_TEST
tristate "MMC host test driver"
help
Development driver that performs a series of reads and writes
to a memory card in order to expose certain well known bugs
in host controllers. The tests are executed by writing to the
"test" file in debugfs under each card. Note that whatever is
on your card will be overwritten by these tests.
This driver is only of interest to those developing or
testing a host driver. Most people should say N here.

10
drivers/mmc/card/Makefile Normal file
View file

@ -0,0 +1,10 @@
#
# Makefile for MMC/SD card drivers
#
obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
mmc_block-objs := block.o queue.o
obj-$(CONFIG_MMC_TEST) += mmc_test.o
obj-$(CONFIG_SDIO_UART) += sdio_uart.o

2716
drivers/mmc/card/block.c Normal file

File diff suppressed because it is too large Load diff

3054
drivers/mmc/card/mmc_test.c Normal file

File diff suppressed because it is too large Load diff

592
drivers/mmc/card/queue.c Normal file
View file

@ -0,0 +1,592 @@
/*
* linux/drivers/mmc/card/queue.c
*
* Copyright (C) 2003 Russell King, All Rights Reserved.
* Copyright 2006-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.
*
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include <linux/scatterlist.h>
#include <linux/dma-mapping.h>
#include <linux/version.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include "queue.h"
#define MMC_QUEUE_BOUNCESZ 65536
/*
* Prepare a MMC request. This just filters out odd stuff.
*/
static int mmc_prep_request(struct request_queue *q, struct request *req)
{
struct mmc_queue *mq = q->queuedata;
/*
* We only like normal block requests and discards.
*/
if (req->cmd_type != REQ_TYPE_FS && !(req->cmd_flags & REQ_DISCARD)) {
blk_dump_rq_flags(req, "MMC bad request");
return BLKPREP_KILL;
}
if (mq && mmc_card_removed(mq->card))
return BLKPREP_KILL;
req->cmd_flags |= REQ_DONTPREP;
return BLKPREP_OK;
}
static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
current->flags |= PF_MEMALLOC;
down(&mq->thread_sem);
do {
struct request *req = NULL;
struct mmc_queue_req *tmp;
unsigned int cmd_flags = 0;
spin_lock_irq(q->queue_lock);
set_current_state(TASK_INTERRUPTIBLE);
req = blk_fetch_request(q);
mq->mqrq_cur->req = req;
spin_unlock_irq(q->queue_lock);
if (req || mq->mqrq_prev->req) {
set_current_state(TASK_RUNNING);
cmd_flags = req ? req->cmd_flags : 0;
mq->issue_fn(mq, req);
if (mq->flags & MMC_QUEUE_NEW_REQUEST) {
mq->flags &= ~MMC_QUEUE_NEW_REQUEST;
continue; /* fetch again */
}
/*
* Current request becomes previous request
* and vice versa.
* In case of special requests, current request
* has been finished. Do not assign it to previous
* request.
*/
if (cmd_flags & MMC_REQ_SPECIAL_MASK)
mq->mqrq_cur->req = NULL;
mq->mqrq_prev->brq.mrq.data = NULL;
mq->mqrq_prev->req = NULL;
tmp = mq->mqrq_prev;
mq->mqrq_prev = mq->mqrq_cur;
mq->mqrq_cur = tmp;
} else {
if (kthread_should_stop()) {
set_current_state(TASK_RUNNING);
break;
}
up(&mq->thread_sem);
schedule();
down(&mq->thread_sem);
}
} while (1);
up(&mq->thread_sem);
return 0;
}
/*
* Generic MMC request handler. This is called for any queue on a
* particular host. When the host is not busy, we look for a request
* on any queue on this host, and attempt to issue it. This may
* not be the queue we were asked to process.
*/
static void mmc_request_fn(struct request_queue *q)
{
struct mmc_queue *mq = q->queuedata;
struct request *req;
unsigned long flags;
struct mmc_context_info *cntx;
if (!mq) {
while ((req = blk_fetch_request(q)) != NULL) {
req->cmd_flags |= REQ_QUIET;
__blk_end_request_all(req, -EIO);
}
return;
}
cntx = &mq->card->host->context_info;
if (!mq->mqrq_cur->req && mq->mqrq_prev->req) {
/*
* New MMC request arrived when MMC thread may be
* blocked on the previous request to be complete
* with no current request fetched
*/
spin_lock_irqsave(&cntx->lock, flags);
if (cntx->is_waiting_last_req) {
cntx->is_new_req = true;
wake_up_interruptible(&cntx->wait);
}
spin_unlock_irqrestore(&cntx->lock, flags);
} else if (!mq->mqrq_cur->req && !mq->mqrq_prev->req)
wake_up_process(mq->thread);
}
static struct scatterlist *mmc_alloc_sg(int sg_len, int *err)
{
struct scatterlist *sg;
sg = kmalloc(sizeof(struct scatterlist)*sg_len, GFP_KERNEL);
if (!sg)
*err = -ENOMEM;
else {
*err = 0;
sg_init_table(sg, sg_len);
}
return sg;
}
static void mmc_queue_setup_discard(struct request_queue *q,
struct mmc_card *card)
{
unsigned max_discard;
max_discard = mmc_calc_max_discard(card);
if (!max_discard)
return;
queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
q->limits.max_discard_sectors = max_discard;
if (card->erased_byte == 0 && !mmc_can_discard(card))
q->limits.discard_zeroes_data = 1;
q->limits.discard_granularity = card->pref_erase << 9;
/* granularity must not be greater than max. discard */
if (card->pref_erase > max_discard)
q->limits.discard_granularity = 0;
if (mmc_can_secure_erase_trim(card))
queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q);
}
/**
* mmc_init_queue - initialise a queue structure.
* @mq: mmc queue
* @card: mmc card to attach this queue
* @lock: queue lock
* @subname: partition subname
*
* Initialise a MMC card request queue.
*/
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname)
{
struct mmc_host *host = card->host;
u64 limit = BLK_BOUNCE_HIGH;
int ret;
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
limit = (u64)dma_max_pfn(mmc_dev(host)) << PAGE_SHIFT;
mq->card = card;
mq->queue = blk_init_queue(mmc_request_fn, lock);
if (!mq->queue)
return -ENOMEM;
mq->mqrq_cur = mqrq_cur;
mq->mqrq_prev = mqrq_prev;
mq->queue->queuedata = mq;
blk_queue_prep_rq(mq->queue, mmc_prep_request);
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, mq->queue);
if (mmc_can_erase(card))
mmc_queue_setup_discard(mq->queue, card);
#ifdef CONFIG_MMC_BLOCK_BOUNCE
if (host->max_segs == 1) {
unsigned int bouncesz;
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;
if (bouncesz > (host->max_blk_count * 512))
bouncesz = host->max_blk_count * 512;
if (bouncesz > 512) {
mqrq_cur->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
if (!mqrq_cur->bounce_buf) {
pr_warn("%s: unable to allocate bounce cur buffer\n",
mmc_card_name(card));
}
mqrq_prev->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
if (!mqrq_prev->bounce_buf) {
pr_warn("%s: unable to allocate bounce prev buffer\n",
mmc_card_name(card));
kfree(mqrq_cur->bounce_buf);
mqrq_cur->bounce_buf = NULL;
}
}
if (mqrq_cur->bounce_buf && mqrq_prev->bounce_buf) {
blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY);
blk_queue_max_hw_sectors(mq->queue, bouncesz / 512);
blk_queue_max_segments(mq->queue, bouncesz / 512);
blk_queue_max_segment_size(mq->queue, bouncesz);
mqrq_cur->sg = mmc_alloc_sg(1, &ret);
if (ret)
goto cleanup_queue;
mqrq_cur->bounce_sg =
mmc_alloc_sg(bouncesz / 512, &ret);
if (ret)
goto cleanup_queue;
mqrq_prev->sg = mmc_alloc_sg(1, &ret);
if (ret)
goto cleanup_queue;
mqrq_prev->bounce_sg =
mmc_alloc_sg(bouncesz / 512, &ret);
if (ret)
goto cleanup_queue;
}
}
#endif
if (!mqrq_cur->bounce_buf && !mqrq_prev->bounce_buf) {
blk_queue_bounce_limit(mq->queue, limit);
blk_queue_max_hw_sectors(mq->queue,
min(host->max_blk_count, host->max_req_size / 512));
blk_queue_max_segments(mq->queue, host->max_segs);
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
mqrq_cur->sg = mmc_alloc_sg(host->max_segs, &ret);
if (ret)
goto cleanup_queue;
mqrq_prev->sg = mmc_alloc_sg(host->max_segs, &ret);
if (ret)
goto cleanup_queue;
}
sema_init(&mq->thread_sem, 1);
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
host->index, subname ? subname : "");
if (IS_ERR(mq->thread)) {
ret = PTR_ERR(mq->thread);
goto free_bounce_sg;
}
return 0;
free_bounce_sg:
kfree(mqrq_cur->bounce_sg);
mqrq_cur->bounce_sg = NULL;
kfree(mqrq_prev->bounce_sg);
mqrq_prev->bounce_sg = NULL;
cleanup_queue:
kfree(mqrq_cur->sg);
mqrq_cur->sg = NULL;
kfree(mqrq_cur->bounce_buf);
mqrq_cur->bounce_buf = NULL;
kfree(mqrq_prev->sg);
mqrq_prev->sg = NULL;
kfree(mqrq_prev->bounce_buf);
mqrq_prev->bounce_buf = NULL;
blk_cleanup_queue(mq->queue);
return ret;
}
void mmc_cleanup_queue(struct mmc_queue *mq)
{
struct request_queue *q = mq->queue;
unsigned long flags;
struct mmc_queue_req *mqrq_cur = mq->mqrq_cur;
struct mmc_queue_req *mqrq_prev = mq->mqrq_prev;
/* Make sure the queue isn't suspended, as that will deadlock */
mmc_queue_resume(mq);
/* Then terminate our worker thread */
kthread_stop(mq->thread);
/* Empty the queue */
spin_lock_irqsave(q->queue_lock, flags);
q->queuedata = NULL;
blk_start_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
kfree(mqrq_cur->bounce_sg);
mqrq_cur->bounce_sg = NULL;
kfree(mqrq_cur->sg);
mqrq_cur->sg = NULL;
kfree(mqrq_cur->bounce_buf);
mqrq_cur->bounce_buf = NULL;
kfree(mqrq_prev->bounce_sg);
mqrq_prev->bounce_sg = NULL;
kfree(mqrq_prev->sg);
mqrq_prev->sg = NULL;
kfree(mqrq_prev->bounce_buf);
mqrq_prev->bounce_buf = NULL;
mq->card = NULL;
}
EXPORT_SYMBOL(mmc_cleanup_queue);
int mmc_packed_init(struct mmc_queue *mq, struct mmc_card *card)
{
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
int ret = 0;
mqrq_cur->packed = kzalloc(sizeof(struct mmc_packed), GFP_KERNEL);
if (!mqrq_cur->packed) {
pr_warn("%s: unable to allocate packed cmd for mqrq_cur\n",
mmc_card_name(card));
ret = -ENOMEM;
goto out;
}
mqrq_prev->packed = kzalloc(sizeof(struct mmc_packed), GFP_KERNEL);
if (!mqrq_prev->packed) {
pr_warn("%s: unable to allocate packed cmd for mqrq_prev\n",
mmc_card_name(card));
kfree(mqrq_cur->packed);
mqrq_cur->packed = NULL;
ret = -ENOMEM;
goto out;
}
INIT_LIST_HEAD(&mqrq_cur->packed->list);
INIT_LIST_HEAD(&mqrq_prev->packed->list);
out:
return ret;
}
void mmc_packed_clean(struct mmc_queue *mq)
{
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
kfree(mqrq_cur->packed);
mqrq_cur->packed = NULL;
kfree(mqrq_prev->packed);
mqrq_prev->packed = NULL;
}
/**
* mmc_queue_suspend - suspend a MMC request queue
* @mq: MMC queue to suspend
*
* Stop the block request queue, and wait for our thread to
* complete any outstanding requests. This ensures that we
* won't suspend while a request is being processed.
*/
int mmc_queue_suspend(struct mmc_queue *mq, int wait)
{
struct request_queue *q = mq->queue;
struct request *req;
unsigned long flags;
int rc = 0;
if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
mq->flags |= MMC_QUEUE_SUSPENDED;
spin_lock_irqsave(q->queue_lock, flags);
blk_stop_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
rc = down_trylock(&mq->thread_sem);
if (rc && !wait) {
mq->flags &= ~MMC_QUEUE_SUSPENDED;
spin_lock_irqsave(q->queue_lock, flags);
blk_start_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
rc = -EBUSY;
} else if (wait) {
printk("%s: mq->flags: %x, q->queue_flags: 0x%lx, \
q->in_flight (%d, %d) \n",
mmc_hostname(mq->card->host), mq->flags,
q->queue_flags, q->in_flight[0], q->in_flight[1]);
mutex_lock(&q->sysfs_lock);
if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)) {
queue_flag_set_unlocked(QUEUE_FLAG_DYING, q);
spin_lock_irqsave(q->queue_lock, flags);
queue_flag_set(QUEUE_FLAG_DYING, q);
} else if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)) {
queue_flag_set_unlocked(QUEUE_FLAG_DEAD, q);
spin_lock_irqsave(q->queue_lock, flags);
queue_flag_set(QUEUE_FLAG_DEAD, q);
}
while ((req = blk_fetch_request(q)) != NULL) {
req->cmd_flags |= REQ_QUIET;
__blk_end_request_all(req, -EIO);
}
spin_unlock_irqrestore(q->queue_lock, flags);
mutex_unlock(&q->sysfs_lock);
if (rc) {
down(&mq->thread_sem);
rc = 0;
}
}
}
return rc;
}
/**
* mmc_queue_resume - resume a previously suspended MMC request queue
* @mq: MMC queue to resume
*/
void mmc_queue_resume(struct mmc_queue *mq)
{
struct request_queue *q = mq->queue;
unsigned long flags;
if (mq->flags & MMC_QUEUE_SUSPENDED) {
mq->flags &= ~MMC_QUEUE_SUSPENDED;
up(&mq->thread_sem);
spin_lock_irqsave(q->queue_lock, flags);
blk_start_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
}
}
static unsigned int mmc_queue_packed_map_sg(struct mmc_queue *mq,
struct mmc_packed *packed,
struct scatterlist *sg,
enum mmc_packed_type cmd_type)
{
struct scatterlist *__sg = sg;
unsigned int sg_len = 0;
struct request *req;
if (mmc_packed_wr(cmd_type)) {
unsigned int hdr_sz = mmc_large_sector(mq->card) ? 4096 : 512;
unsigned int max_seg_sz = queue_max_segment_size(mq->queue);
unsigned int len, remain, offset = 0;
u8 *buf = (u8 *)packed->cmd_hdr;
remain = hdr_sz;
do {
len = min(remain, max_seg_sz);
sg_set_buf(__sg, buf + offset, len);
offset += len;
remain -= len;
(__sg++)->page_link &= ~0x02;
sg_len++;
} while (remain);
}
list_for_each_entry(req, &packed->list, queuelist) {
sg_len += blk_rq_map_sg(mq->queue, req, __sg);
__sg = sg + (sg_len - 1);
(__sg++)->page_link &= ~0x02;
}
sg_mark_end(sg + (sg_len - 1));
return sg_len;
}
/*
* Prepare the sg list(s) to be handed of to the host driver
*/
unsigned int mmc_queue_map_sg(struct mmc_queue *mq, struct mmc_queue_req *mqrq)
{
unsigned int sg_len;
size_t buflen;
struct scatterlist *sg;
enum mmc_packed_type cmd_type;
int i;
cmd_type = mqrq->cmd_type;
if (!mqrq->bounce_buf) {
if (mmc_packed_cmd(cmd_type))
return mmc_queue_packed_map_sg(mq, mqrq->packed,
mqrq->sg, cmd_type);
else
return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg);
}
BUG_ON(!mqrq->bounce_sg);
if (mmc_packed_cmd(cmd_type))
sg_len = mmc_queue_packed_map_sg(mq, mqrq->packed,
mqrq->bounce_sg, cmd_type);
else
sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg);
mqrq->bounce_sg_len = sg_len;
buflen = 0;
for_each_sg(mqrq->bounce_sg, sg, sg_len, i)
buflen += sg->length;
sg_init_one(mqrq->sg, mqrq->bounce_buf, buflen);
return 1;
}
/*
* If writing, bounce the data to the buffer before the request
* is sent to the host driver
*/
void mmc_queue_bounce_pre(struct mmc_queue_req *mqrq)
{
if (!mqrq->bounce_buf)
return;
if (rq_data_dir(mqrq->req) != WRITE)
return;
sg_copy_to_buffer(mqrq->bounce_sg, mqrq->bounce_sg_len,
mqrq->bounce_buf, mqrq->sg[0].length);
}
/*
* If reading, bounce the data from the buffer after the request
* has been handled by the host driver
*/
void mmc_queue_bounce_post(struct mmc_queue_req *mqrq)
{
if (!mqrq->bounce_buf)
return;
if (rq_data_dir(mqrq->req) != READ)
return;
sg_copy_from_buffer(mqrq->bounce_sg, mqrq->bounce_sg_len,
mqrq->bounce_buf, mqrq->sg[0].length);
}

76
drivers/mmc/card/queue.h Normal file
View file

@ -0,0 +1,76 @@
#ifndef MMC_QUEUE_H
#define MMC_QUEUE_H
#define MMC_REQ_SPECIAL_MASK (REQ_DISCARD | REQ_FLUSH)
struct request;
struct task_struct;
struct mmc_blk_request {
struct mmc_request mrq;
struct mmc_command sbc;
struct mmc_command cmd;
struct mmc_command stop;
struct mmc_data data;
};
enum mmc_packed_type {
MMC_PACKED_NONE = 0,
MMC_PACKED_WRITE,
};
#define mmc_packed_cmd(type) ((type) != MMC_PACKED_NONE)
#define mmc_packed_wr(type) ((type) == MMC_PACKED_WRITE)
struct mmc_packed {
struct list_head list;
u32 cmd_hdr[1024];
unsigned int blocks;
u8 nr_entries;
u8 retries;
s16 idx_failure;
};
struct mmc_queue_req {
struct request *req;
struct mmc_blk_request brq;
struct scatterlist *sg;
char *bounce_buf;
struct scatterlist *bounce_sg;
unsigned int bounce_sg_len;
struct mmc_async_req mmc_active;
enum mmc_packed_type cmd_type;
struct mmc_packed *packed;
};
struct mmc_queue {
struct mmc_card *card;
struct task_struct *thread;
struct semaphore thread_sem;
unsigned int flags;
#define MMC_QUEUE_SUSPENDED (1 << 0)
#define MMC_QUEUE_NEW_REQUEST (1 << 1)
int (*issue_fn)(struct mmc_queue *, struct request *);
void *data;
struct request_queue *queue;
struct mmc_queue_req mqrq[2];
struct mmc_queue_req *mqrq_cur;
struct mmc_queue_req *mqrq_prev;
};
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
const char *);
extern void mmc_cleanup_queue(struct mmc_queue *);
extern int mmc_queue_suspend(struct mmc_queue *, int wait);
extern void mmc_queue_resume(struct mmc_queue *);
extern unsigned int mmc_queue_map_sg(struct mmc_queue *,
struct mmc_queue_req *);
extern void mmc_queue_bounce_pre(struct mmc_queue_req *);
extern void mmc_queue_bounce_post(struct mmc_queue_req *);
extern int mmc_packed_init(struct mmc_queue *, struct mmc_card *);
extern void mmc_packed_clean(struct mmc_queue *);
#endif

1202
drivers/mmc/card/sdio_uart.c Normal file

File diff suppressed because it is too large Load diff

28
drivers/mmc/core/Kconfig Normal file
View file

@ -0,0 +1,28 @@
#
# MMC core configuration
#
config MMC_CLKGATE
bool "MMC host clock gating"
help
This will attempt to aggressively gate the clock to the MMC card.
This is done to save power due to gating off the logic and bus
noise when the MMC card is not in use. Your host driver has to
support handling this in order for it to be of any use.
If unsure, say N.
config MMC_EMBEDDED_SDIO
boolean "MMC embedded SDIO device support (EXPERIMENTAL)"
help
If you say Y here, support will be added for embedded SDIO
devices which do not contain the necessary enumeration
support in hardware to be properly detected.
config MMC_PARANOID_SD_INIT
bool "Enable paranoid SD card initialization (EXPERIMENTAL)"
help
If you say Y here, the MMC layer will be extra paranoid
about re-trying SD init requests. This can be a useful
work-around for buggy controllers and hardware. Enable
if you are experiencing issues with SD detection.

12
drivers/mmc/core/Makefile Normal file
View file

@ -0,0 +1,12 @@
#
# Makefile for the kernel mmc core.
#
obj-$(CONFIG_MMC) += mmc_core.o
mmc_core-y := core.o bus.o host.o \
mmc.o mmc_ops.o sd.o sd_ops.o \
sdio.o sdio_ops.o sdio_bus.o \
sdio_cis.o sdio_io.o sdio_irq.o \
quirks.o slot-gpio.o
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o

394
drivers/mmc/core/bus.c Normal file
View file

@ -0,0 +1,394 @@
/*
* 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/export.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/pm_runtime.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include "core.h"
#include "sdio_cis.h"
#include "bus.h"
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
static ssize_t type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mmc_card *card = mmc_dev_to_card(dev);
switch (card->type) {
case MMC_TYPE_MMC:
return sprintf(buf, "MMC\n");
case MMC_TYPE_SD:
return sprintf(buf, "SD\n");
case MMC_TYPE_SDIO:
return sprintf(buf, "SDIO\n");
case MMC_TYPE_SD_COMBO:
return sprintf(buf, "SDcombo\n");
default:
return -EFAULT;
}
}
static DEVICE_ATTR_RO(type);
static struct attribute *mmc_dev_attrs[] = {
&dev_attr_type.attr,
NULL,
};
ATTRIBUTE_GROUPS(mmc_dev);
/*
* 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, struct kobj_uevent_env *env)
{
struct mmc_card *card = mmc_dev_to_card(dev);
const char *type;
int retval = 0;
switch (card->type) {
case MMC_TYPE_MMC:
type = "MMC";
break;
case MMC_TYPE_SD:
type = "SD";
break;
case MMC_TYPE_SDIO:
type = "SDIO";
break;
case MMC_TYPE_SD_COMBO:
type = "SDcombo";
break;
default:
type = NULL;
}
if (type) {
retval = add_uevent_var(env, "MMC_TYPE=%s", type);
if (retval)
return retval;
}
retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card));
if (retval)
return retval;
/*
* Request the mmc_block device. Note: that this is a direct request
* for the module it carries no information as to what is inserted.
*/
retval = add_uevent_var(env, "MODALIAS=mmc:block");
return retval;
}
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_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 = mmc_dev_to_card(dev);
drv->remove(card);
return 0;
}
static void mmc_bus_shutdown(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
int ret;
if (dev->driver && drv->shutdown)
drv->shutdown(card);
if (host->bus_ops->shutdown) {
ret = host->bus_ops->shutdown(host);
if (ret)
pr_warn("%s: error %d during shutdown\n",
mmc_hostname(host), ret);
}
}
#ifdef CONFIG_PM_SLEEP
static int mmc_bus_suspend(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
int ret;
if (dev->driver && drv->suspend) {
ret = drv->suspend(card);
if (ret)
return ret;
}
if (mmc_bus_needs_resume(host))
return 0;
ret = host->bus_ops->suspend(host);
return ret;
}
static int mmc_bus_resume(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
int ret = 0;
if (mmc_bus_manual_resume(host) ) {
host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME;
} else {
ret = host->bus_ops->resume(host);
if (ret)
pr_warn("%s: error %d during resume (card was removed?)\n",
mmc_hostname(host), ret);
}
if (dev->driver && drv->resume)
ret = drv->resume(card);
return ret;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int mmc_runtime_suspend(struct device *dev)
{
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
return host->bus_ops->runtime_suspend(host);
}
static int mmc_runtime_resume(struct device *dev)
{
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
return host->bus_ops->runtime_resume(host);
}
#endif /* !CONFIG_PM_RUNTIME */
static const struct dev_pm_ops mmc_bus_pm_ops = {
SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume)
};
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_groups = mmc_dev_groups,
.match = mmc_bus_match,
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe,
.remove = mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops,
};
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 = mmc_dev_to_card(dev);
sdio_free_common_cis(card);
kfree(card->info);
kfree(card);
}
/*
* Allocate and initialise a new MMC card structure.
*/
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
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;
card->dev.type = type;
return card;
}
/*
* Register a new MMC card with the driver model.
*/
int mmc_add_card(struct mmc_card *card)
{
int ret;
const char *type;
const char *uhs_bus_speed_mode = "";
static const char *const uhs_speeds[] = {
[UHS_SDR12_BUS_SPEED] = "SDR12 ",
[UHS_SDR25_BUS_SPEED] = "SDR25 ",
[UHS_SDR50_BUS_SPEED] = "SDR50 ",
[UHS_SDR104_BUS_SPEED] = "SDR104 ",
[UHS_DDR50_BUS_SPEED] = "DDR50 ",
};
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
switch (card->type) {
case MMC_TYPE_MMC:
type = "MMC";
break;
case MMC_TYPE_SD:
type = "SD";
if (mmc_card_blockaddr(card)) {
if (mmc_card_ext_capacity(card))
type = "SDXC";
else
type = "SDHC";
}
break;
case MMC_TYPE_SDIO:
type = "SDIO";
break;
case MMC_TYPE_SD_COMBO:
type = "SD-combo";
if (mmc_card_blockaddr(card))
type = "SDHC-combo";
break;
default:
type = "?";
break;
}
if (mmc_card_uhs(card) &&
(card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))
uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];
if (mmc_host_is_spi(card->host)) {
pr_info("%s: new %s%s%s card on SPI\n",
mmc_hostname(card->host),
mmc_card_hs(card) ? "high speed " : "",
mmc_card_ddr52(card) ? "DDR " : "",
type);
} else {
pr_info("%s: new %s%s%s%s%s card at address %04x\n",
mmc_hostname(card->host),
mmc_card_uhs(card) ? "ultra high speed " :
(mmc_card_hs(card) ? "high speed " : ""),
mmc_card_hs400(card) ? "HS400 " :
(mmc_card_hs200(card) ? "HS200 " : ""),
mmc_card_ddr52(card) ? "DDR " : "",
uhs_bus_speed_mode, type, card->rca);
}
#ifdef CONFIG_DEBUG_FS
mmc_add_card_debugfs(card);
#endif
mmc_init_context_info(card->host);
ret = device_add(&card->dev);
if (ret)
return ret;
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)
{
#ifdef CONFIG_DEBUG_FS
mmc_remove_card_debugfs(card);
#endif
if (mmc_card_present(card)) {
if (mmc_host_is_spi(card->host)) {
pr_info("%s: SPI card removed\n",
mmc_hostname(card->host));
} else {
pr_info("%s: card %04x removed\n",
mmc_hostname(card->host), card->rca);
}
device_del(&card->dev);
}
put_device(&card->dev);
}

31
drivers/mmc/core/bus.h Normal file
View file

@ -0,0 +1,31 @@
/*
* 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
#define MMC_DEV_ATTR(name, fmt, args...) \
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct mmc_card *card = mmc_dev_to_card(dev); \
return sprintf(buf, fmt, args); \
} \
static DEVICE_ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
struct mmc_card *mmc_alloc_card(struct mmc_host *host,
struct device_type *type);
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

2813
drivers/mmc/core/core.c Normal file

File diff suppressed because it is too large Load diff

85
drivers/mmc/core/core.h Normal file
View file

@ -0,0 +1,85 @@
/*
* linux/drivers/mmc/core/core.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_CORE_H
#define _MMC_CORE_CORE_H
#include <linux/delay.h>
#define MMC_CMD_RETRIES 3
struct mmc_bus_ops {
void (*remove)(struct mmc_host *);
void (*detect)(struct mmc_host *);
int (*pre_suspend)(struct mmc_host *);
int (*suspend)(struct mmc_host *);
int (*resume)(struct mmc_host *);
int (*runtime_suspend)(struct mmc_host *);
int (*runtime_resume)(struct mmc_host *);
int (*power_save)(struct mmc_host *);
int (*power_restore)(struct mmc_host *);
int (*alive)(struct mmc_host *);
int (*shutdown)(struct mmc_host *);
};
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
void mmc_detach_bus(struct mmc_host *host);
void mmc_init_erase(struct mmc_card *card);
void mmc_set_chip_select(struct mmc_host *host, int mode);
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
void mmc_gate_clock(struct mmc_host *host);
void mmc_ungate_clock(struct mmc_host *host);
void mmc_set_ungated(struct mmc_host *host);
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr);
int __mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage);
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
void mmc_power_up(struct mmc_host *host, u32 ocr);
void mmc_power_off(struct mmc_host *host);
void mmc_power_cycle(struct mmc_host *host, u32 ocr);
static inline void mmc_delay(unsigned int ms)
{
if (ms < 1000 / HZ) {
cond_resched();
mdelay(ms);
} else {
msleep(ms);
}
}
void mmc_rescan(struct work_struct *work);
void mmc_start_host(struct mmc_host *host);
void mmc_stop_host(struct mmc_host *host);
int _mmc_detect_card_removed(struct mmc_host *host);
int mmc_attach_mmc(struct mmc_host *host);
int mmc_attach_sd(struct mmc_host *host);
int mmc_attach_sdio(struct mmc_host *host);
/* Module parameters */
extern bool use_spi_crc;
/* Debugfs information for hosts and cards */
void mmc_add_host_debugfs(struct mmc_host *host);
void mmc_remove_host_debugfs(struct mmc_host *host);
void mmc_add_card_debugfs(struct mmc_card *card);
void mmc_remove_card_debugfs(struct mmc_card *card);
void mmc_init_context_info(struct mmc_host *host);
#endif

386
drivers/mmc/core/debugfs.c Normal file
View file

@ -0,0 +1,386 @@
/*
* Debugfs support for hosts and cards
*
* Copyright (C) 2008 Atmel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/moduleparam.h>
#include <linux/export.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/fault-inject.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include "core.h"
#include "mmc_ops.h"
#ifdef CONFIG_FAIL_MMC_REQUEST
static DECLARE_FAULT_ATTR(fail_default_attr);
static char *fail_request;
module_param(fail_request, charp, 0);
#endif /* CONFIG_FAIL_MMC_REQUEST */
/* The debugfs functions are optimized away when CONFIG_DEBUG_FS isn't set. */
static int mmc_ios_show(struct seq_file *s, void *data)
{
static const char *vdd_str[] = {
[8] = "2.0",
[9] = "2.1",
[10] = "2.2",
[11] = "2.3",
[12] = "2.4",
[13] = "2.5",
[14] = "2.6",
[15] = "2.7",
[16] = "2.8",
[17] = "2.9",
[18] = "3.0",
[19] = "3.1",
[20] = "3.2",
[21] = "3.3",
[22] = "3.4",
[23] = "3.5",
[24] = "3.6",
};
struct mmc_host *host = s->private;
struct mmc_ios *ios = &host->ios;
const char *str;
seq_printf(s, "clock:\t\t%u Hz\n", ios->clock);
if (host->actual_clock)
seq_printf(s, "actual clock:\t%u Hz\n", host->actual_clock);
seq_printf(s, "vdd:\t\t%u ", ios->vdd);
if ((1 << ios->vdd) & MMC_VDD_165_195)
seq_printf(s, "(1.65 - 1.95 V)\n");
else if (ios->vdd < (ARRAY_SIZE(vdd_str) - 1)
&& vdd_str[ios->vdd] && vdd_str[ios->vdd + 1])
seq_printf(s, "(%s ~ %s V)\n", vdd_str[ios->vdd],
vdd_str[ios->vdd + 1]);
else
seq_printf(s, "(invalid)\n");
switch (ios->bus_mode) {
case MMC_BUSMODE_OPENDRAIN:
str = "open drain";
break;
case MMC_BUSMODE_PUSHPULL:
str = "push-pull";
break;
default:
str = "invalid";
break;
}
seq_printf(s, "bus mode:\t%u (%s)\n", ios->bus_mode, str);
switch (ios->chip_select) {
case MMC_CS_DONTCARE:
str = "don't care";
break;
case MMC_CS_HIGH:
str = "active high";
break;
case MMC_CS_LOW:
str = "active low";
break;
default:
str = "invalid";
break;
}
seq_printf(s, "chip select:\t%u (%s)\n", ios->chip_select, str);
switch (ios->power_mode) {
case MMC_POWER_OFF:
str = "off";
break;
case MMC_POWER_UP:
str = "up";
break;
case MMC_POWER_ON:
str = "on";
break;
default:
str = "invalid";
break;
}
seq_printf(s, "power mode:\t%u (%s)\n", ios->power_mode, str);
seq_printf(s, "bus width:\t%u (%u bits)\n",
ios->bus_width, 1 << ios->bus_width);
switch (ios->timing) {
case MMC_TIMING_LEGACY:
str = "legacy";
break;
case MMC_TIMING_MMC_HS:
str = "mmc high-speed";
break;
case MMC_TIMING_SD_HS:
str = "sd high-speed";
break;
case MMC_TIMING_UHS_SDR50:
str = "sd uhs SDR50";
break;
case MMC_TIMING_UHS_SDR104:
str = "sd uhs SDR104";
break;
case MMC_TIMING_UHS_DDR50:
str = "sd uhs DDR50";
break;
case MMC_TIMING_MMC_DDR52:
str = "mmc DDR52";
break;
case MMC_TIMING_MMC_HS200:
str = "mmc HS200";
break;
case MMC_TIMING_MMC_HS400:
str = "mmc HS400";
break;
default:
str = "invalid";
break;
}
seq_printf(s, "timing spec:\t%u (%s)\n", ios->timing, str);
switch (ios->signal_voltage) {
case MMC_SIGNAL_VOLTAGE_330:
str = "3.30 V";
break;
case MMC_SIGNAL_VOLTAGE_180:
str = "1.80 V";
break;
case MMC_SIGNAL_VOLTAGE_120:
str = "1.20 V";
break;
default:
str = "invalid";
break;
}
seq_printf(s, "signal voltage:\t%u (%s)\n", ios->chip_select, str);
return 0;
}
static int mmc_ios_open(struct inode *inode, struct file *file)
{
return single_open(file, mmc_ios_show, inode->i_private);
}
static const struct file_operations mmc_ios_fops = {
.open = mmc_ios_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int mmc_clock_opt_get(void *data, u64 *val)
{
struct mmc_host *host = data;
*val = host->ios.clock;
return 0;
}
static int mmc_clock_opt_set(void *data, u64 val)
{
struct mmc_host *host = data;
/* We need this check due to input value is u64 */
if (val > host->f_max)
return -EINVAL;
mmc_claim_host(host);
mmc_set_clock(host, (unsigned int) val);
mmc_release_host(host);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set,
"%llu\n");
void mmc_add_host_debugfs(struct mmc_host *host)
{
struct dentry *root;
root = debugfs_create_dir(mmc_hostname(host), NULL);
if (IS_ERR(root))
/* Don't complain -- debugfs just isn't enabled */
return;
if (!root)
/* Complain -- debugfs is enabled, but it failed to
* create the directory. */
goto err_root;
host->debugfs_root = root;
if (!debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops))
goto err_node;
if (!debugfs_create_file("clock", S_IRUSR | S_IWUSR, root, host,
&mmc_clock_fops))
goto err_node;
#ifdef CONFIG_MMC_CLKGATE
if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR),
root, &host->clk_delay))
goto err_node;
#endif
#ifdef CONFIG_FAIL_MMC_REQUEST
if (fail_request)
setup_fault_attr(&fail_default_attr, fail_request);
host->fail_mmc_request = fail_default_attr;
if (IS_ERR(fault_create_debugfs_attr("fail_mmc_request",
root,
&host->fail_mmc_request)))
goto err_node;
#endif
return;
err_node:
debugfs_remove_recursive(root);
host->debugfs_root = NULL;
err_root:
dev_err(&host->class_dev, "failed to initialize debugfs\n");
}
void mmc_remove_host_debugfs(struct mmc_host *host)
{
debugfs_remove_recursive(host->debugfs_root);
}
static int mmc_dbg_card_status_get(void *data, u64 *val)
{
struct mmc_card *card = data;
u32 status;
int ret;
mmc_get_card(card);
ret = mmc_send_status(data, &status);
if (!ret)
*val = status;
mmc_put_card(card);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(mmc_dbg_card_status_fops, mmc_dbg_card_status_get,
NULL, "%08llx\n");
#define EXT_CSD_STR_LEN 1025
static int mmc_ext_csd_open(struct inode *inode, struct file *filp)
{
struct mmc_card *card = inode->i_private;
char *buf;
ssize_t n = 0;
u8 *ext_csd;
int err, i;
buf = kmalloc(EXT_CSD_STR_LEN + 1, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ext_csd = kmalloc(512, GFP_KERNEL);
if (!ext_csd) {
err = -ENOMEM;
goto out_free;
}
mmc_get_card(card);
err = mmc_send_ext_csd(card, ext_csd);
mmc_put_card(card);
if (err)
goto out_free;
for (i = 0; i < 512; i++)
n += sprintf(buf + n, "%02x", ext_csd[i]);
n += sprintf(buf + n, "\n");
BUG_ON(n != EXT_CSD_STR_LEN);
filp->private_data = buf;
kfree(ext_csd);
return 0;
out_free:
kfree(buf);
kfree(ext_csd);
return err;
}
static ssize_t mmc_ext_csd_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
char *buf = filp->private_data;
return simple_read_from_buffer(ubuf, cnt, ppos,
buf, EXT_CSD_STR_LEN);
}
static int mmc_ext_csd_release(struct inode *inode, struct file *file)
{
kfree(file->private_data);
return 0;
}
static const struct file_operations mmc_dbg_ext_csd_fops = {
.open = mmc_ext_csd_open,
.read = mmc_ext_csd_read,
.release = mmc_ext_csd_release,
.llseek = default_llseek,
};
void mmc_add_card_debugfs(struct mmc_card *card)
{
struct mmc_host *host = card->host;
struct dentry *root;
if (!host->debugfs_root)
return;
root = debugfs_create_dir(mmc_card_id(card), host->debugfs_root);
if (IS_ERR(root))
/* Don't complain -- debugfs just isn't enabled */
return;
if (!root)
/* Complain -- debugfs is enabled, but it failed to
* create the directory. */
goto err;
card->debugfs_root = root;
if (!debugfs_create_x32("state", S_IRUSR, root, &card->state))
goto err;
if (mmc_card_mmc(card) || mmc_card_sd(card))
if (!debugfs_create_file("status", S_IRUSR, root, card,
&mmc_dbg_card_status_fops))
goto err;
if (mmc_card_mmc(card))
if (!debugfs_create_file("ext_csd", S_IRUSR, root, card,
&mmc_dbg_ext_csd_fops))
goto err;
return;
err:
debugfs_remove_recursive(root);
card->debugfs_root = NULL;
dev_err(&card->dev, "failed to initialize debugfs\n");
}
void mmc_remove_card_debugfs(struct mmc_card *card)
{
debugfs_remove_recursive(card->debugfs_root);
}

628
drivers/mmc/core/host.c Normal file
View file

@ -0,0 +1,628 @@
/*
* linux/drivers/mmc/core/host.c
*
* Copyright (C) 2003 Russell King, All Rights Reserved.
* Copyright (C) 2007-2008 Pierre Ossman
* Copyright (C) 2010 Linus Walleij
*
* 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/of.h>
#include <linux/of_gpio.h>
#include <linux/pagemap.h>
#include <linux/export.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/slot-gpio.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);
mutex_destroy(&host->slot.lock);
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);
#ifdef CONFIG_MMC_CLKGATE
static ssize_t clkgate_delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
return snprintf(buf, PAGE_SIZE, "%lu\n", host->clkgate_delay);
}
static ssize_t clkgate_delay_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
unsigned long flags, value;
if (kstrtoul(buf, 0, &value))
return -EINVAL;
spin_lock_irqsave(&host->clk_lock, flags);
host->clkgate_delay = value;
spin_unlock_irqrestore(&host->clk_lock, flags);
return count;
}
/*
* Enabling clock gating will make the core call out to the host
* once up and once down when it performs a request or card operation
* intermingled in any fashion. The driver will see this through
* set_ios() operations with ios.clock field set to 0 to gate (disable)
* the block clock, and to the old frequency to enable it again.
*/
static void mmc_host_clk_gate_delayed(struct mmc_host *host)
{
unsigned long tick_ns;
unsigned long freq = host->ios.clock;
unsigned long flags;
if (!freq) {
pr_debug("%s: frequency set to 0 in disable function, "
"this means the clock is already disabled.\n",
mmc_hostname(host));
return;
}
/*
* New requests may have appeared while we were scheduling,
* then there is no reason to delay the check before
* clk_disable().
*/
spin_lock_irqsave(&host->clk_lock, flags);
/*
* Delay n bus cycles (at least 8 from MMC spec) before attempting
* to disable the MCI block clock. The reference count may have
* gone up again after this delay due to rescheduling!
*/
if (!host->clk_requests) {
spin_unlock_irqrestore(&host->clk_lock, flags);
tick_ns = DIV_ROUND_UP(1000000000, freq);
ndelay(host->clk_delay * tick_ns);
} else {
/* New users appeared while waiting for this work */
spin_unlock_irqrestore(&host->clk_lock, flags);
return;
}
mutex_lock(&host->clk_gate_mutex);
spin_lock_irqsave(&host->clk_lock, flags);
if (!host->clk_requests) {
spin_unlock_irqrestore(&host->clk_lock, flags);
/* This will set host->ios.clock to 0 */
mmc_gate_clock(host);
spin_lock_irqsave(&host->clk_lock, flags);
pr_debug("%s: gated MCI clock\n", mmc_hostname(host));
}
spin_unlock_irqrestore(&host->clk_lock, flags);
mutex_unlock(&host->clk_gate_mutex);
}
/*
* Internal work. Work to disable the clock at some later point.
*/
static void mmc_host_clk_gate_work(struct work_struct *work)
{
struct mmc_host *host = container_of(work, struct mmc_host,
clk_gate_work.work);
mmc_host_clk_gate_delayed(host);
}
/**
* mmc_host_clk_hold - ungate hardware MCI clocks
* @host: host to ungate.
*
* Makes sure the host ios.clock is restored to a non-zero value
* past this call. Increase clock reference count and ungate clock
* if we're the first user.
*/
void mmc_host_clk_hold(struct mmc_host *host)
{
unsigned long flags;
/* cancel any clock gating work scheduled by mmc_host_clk_release() */
cancel_delayed_work_sync(&host->clk_gate_work);
mutex_lock(&host->clk_gate_mutex);
spin_lock_irqsave(&host->clk_lock, flags);
if (host->clk_gated) {
spin_unlock_irqrestore(&host->clk_lock, flags);
mmc_ungate_clock(host);
spin_lock_irqsave(&host->clk_lock, flags);
pr_debug("%s: ungated MCI clock\n", mmc_hostname(host));
}
host->clk_requests++;
spin_unlock_irqrestore(&host->clk_lock, flags);
mutex_unlock(&host->clk_gate_mutex);
}
/**
* mmc_host_may_gate_card - check if this card may be gated
* @card: card to check.
*/
static bool mmc_host_may_gate_card(struct mmc_card *card)
{
/* If there is no card we may gate it */
if (!card)
return true;
/*
* Don't gate SDIO cards! These need to be clocked at all times
* since they may be independent systems generating interrupts
* and other events. The clock requests counter from the core will
* go down to zero since the core does not need it, but we will not
* gate the clock, because there is somebody out there that may still
* be using it.
*/
return !(card->quirks & MMC_QUIRK_BROKEN_CLK_GATING);
}
/**
* mmc_host_clk_release - gate off hardware MCI clocks
* @host: host to gate.
*
* Calls the host driver with ios.clock set to zero as often as possible
* in order to gate off hardware MCI clocks. Decrease clock reference
* count and schedule disabling of clock.
*/
void mmc_host_clk_release(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->clk_lock, flags);
host->clk_requests--;
if (mmc_host_may_gate_card(host->card) &&
!host->clk_requests)
schedule_delayed_work(&host->clk_gate_work,
msecs_to_jiffies(host->clkgate_delay));
spin_unlock_irqrestore(&host->clk_lock, flags);
}
/**
* mmc_host_clk_rate - get current clock frequency setting
* @host: host to get the clock frequency for.
*
* Returns current clock frequency regardless of gating.
*/
unsigned int mmc_host_clk_rate(struct mmc_host *host)
{
unsigned long freq;
unsigned long flags;
spin_lock_irqsave(&host->clk_lock, flags);
if (host->clk_gated)
freq = host->clk_old;
else
freq = host->ios.clock;
spin_unlock_irqrestore(&host->clk_lock, flags);
return freq;
}
/**
* mmc_host_clk_init - set up clock gating code
* @host: host with potential clock to control
*/
static inline void mmc_host_clk_init(struct mmc_host *host)
{
host->clk_requests = 0;
/* Hold MCI clock for 8 cycles by default */
host->clk_delay = 8;
/*
* Default clock gating delay is 0ms to avoid wasting power.
* This value can be tuned by writing into sysfs entry.
*/
host->clkgate_delay = 0;
host->clk_gated = false;
INIT_DELAYED_WORK(&host->clk_gate_work, mmc_host_clk_gate_work);
spin_lock_init(&host->clk_lock);
mutex_init(&host->clk_gate_mutex);
}
/**
* mmc_host_clk_exit - shut down clock gating code
* @host: host with potential clock to control
*/
static inline void mmc_host_clk_exit(struct mmc_host *host)
{
/*
* Wait for any outstanding gate and then make sure we're
* ungated before exiting.
*/
if (cancel_delayed_work_sync(&host->clk_gate_work))
mmc_host_clk_gate_delayed(host);
if (host->clk_gated)
mmc_host_clk_hold(host);
/* There should be only one user now */
WARN_ON(host->clk_requests > 1);
}
static inline void mmc_host_clk_sysfs_init(struct mmc_host *host)
{
host->clkgate_delay_attr.show = clkgate_delay_show;
host->clkgate_delay_attr.store = clkgate_delay_store;
sysfs_attr_init(&host->clkgate_delay_attr.attr);
host->clkgate_delay_attr.attr.name = "clkgate_delay";
host->clkgate_delay_attr.attr.mode = S_IRUGO | S_IWUSR;
if (device_create_file(&host->class_dev, &host->clkgate_delay_attr))
pr_err("%s: Failed to create clkgate_delay sysfs entry\n",
mmc_hostname(host));
}
#else
static inline void mmc_host_clk_init(struct mmc_host *host)
{
}
static inline void mmc_host_clk_exit(struct mmc_host *host)
{
}
static inline void mmc_host_clk_sysfs_init(struct mmc_host *host)
{
}
#endif
/**
* mmc_of_parse() - parse host's device-tree node
* @host: host whose node should be parsed.
*
* To keep the rest of the MMC subsystem unaware of whether DT has been
* used to to instantiate and configure this host instance or not, we
* parse the properties and set respective generic mmc-host flags and
* parameters.
*/
int mmc_of_parse(struct mmc_host *host)
{
struct device_node *np;
u32 bus_width;
int len, ret;
bool cd_cap_invert, cd_gpio_invert = false;
bool ro_cap_invert, ro_gpio_invert = false;
u32 device_strength;
if (!host->parent || !host->parent->of_node)
return 0;
np = host->parent->of_node;
/* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
dev_dbg(host->parent,
"\"bus-width\" property is missing, assuming 1 bit.\n");
bus_width = 1;
}
switch (bus_width) {
case 8:
host->caps |= MMC_CAP_8_BIT_DATA;
/* Hosts capable of 8-bit transfers can also do 4 bits */
case 4:
host->caps |= MMC_CAP_4_BIT_DATA;
break;
case 1:
break;
default:
dev_err(host->parent,
"Invalid \"bus-width\" value %u!\n", bus_width);
return -EINVAL;
}
/* f_max is obtained from the optional "max-frequency" property */
of_property_read_u32(np, "max-frequency", &host->f_max);
/*
* Configure CD and WP pins. They are both by default active low to
* match the SDHCI spec. If GPIOs are provided for CD and / or WP, the
* mmc-gpio helpers are used to attach, configure and use them. If
* polarity inversion is specified in DT, one of MMC_CAP2_CD_ACTIVE_HIGH
* and MMC_CAP2_RO_ACTIVE_HIGH capability-2 flags is set. If the
* "broken-cd" property is provided, the MMC_CAP_NEEDS_POLL capability
* is set. If the "non-removable" property is found, the
* MMC_CAP_NONREMOVABLE capability is set and no card-detection
* configuration is performed.
*/
/* Parse Card Detection */
if (of_find_property(np, "non-removable", &len)) {
host->caps |= MMC_CAP_NONREMOVABLE;
} else {
cd_cap_invert = of_property_read_bool(np, "cd-inverted");
if (of_find_property(np, "broken-cd", &len))
host->caps |= MMC_CAP_NEEDS_POLL;
ret = mmc_gpiod_request_cd(host, "cd", 0, true,
0, &cd_gpio_invert);
if (ret) {
if (ret == -EPROBE_DEFER)
return ret;
if (ret != -ENOENT) {
dev_err(host->parent,
"Failed to request CD GPIO: %d\n",
ret);
}
} else
dev_info(host->parent, "Got CD GPIO\n");
/*
* There are two ways to flag that the CD line is inverted:
* through the cd-inverted flag and by the GPIO line itself
* being inverted from the GPIO subsystem. This is a leftover
* from the times when the GPIO subsystem did not make it
* possible to flag a line as inverted.
*
* If the capability on the host AND the GPIO line are
* both inverted, the end result is that the CD line is
* not inverted.
*/
if (cd_cap_invert ^ cd_gpio_invert)
host->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
}
/* Parse Write Protection */
ro_cap_invert = of_property_read_bool(np, "wp-inverted");
ret = mmc_gpiod_request_ro(host, "wp", 0, false, 0, &ro_gpio_invert);
if (ret) {
if (ret == -EPROBE_DEFER)
goto out;
if (ret != -ENOENT) {
dev_err(host->parent,
"Failed to request WP GPIO: %d\n",
ret);
}
} else
dev_info(host->parent, "Got WP GPIO\n");
/* See the comment on CD inversion above */
if (ro_cap_invert ^ ro_gpio_invert)
host->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH;
if (of_find_property(np, "cap-sd-highspeed", &len))
host->caps |= MMC_CAP_SD_HIGHSPEED;
if (of_find_property(np, "cap-mmc-highspeed", &len))
host->caps |= MMC_CAP_MMC_HIGHSPEED;
if (of_find_property(np, "sd-uhs-sdr12", &len))
host->caps |= MMC_CAP_UHS_SDR12;
if (of_find_property(np, "sd-uhs-sdr25", &len))
host->caps |= MMC_CAP_UHS_SDR25;
if (of_find_property(np, "sd-uhs-sdr50", &len))
host->caps |= MMC_CAP_UHS_SDR50;
if (of_find_property(np, "sd-uhs-sdr104", &len))
host->caps |= MMC_CAP_UHS_SDR104;
if (of_find_property(np, "sd-uhs-ddr50", &len))
host->caps |= MMC_CAP_UHS_DDR50;
if (of_find_property(np, "cap-power-off-card", &len))
host->caps |= MMC_CAP_POWER_OFF_CARD;
if (of_find_property(np, "cap-sdio-irq", &len))
host->caps |= MMC_CAP_SDIO_IRQ;
if (of_find_property(np, "full-pwr-cycle", &len))
host->caps2 |= MMC_CAP2_FULL_PWR_CYCLE;
if (of_find_property(np, "keep-power-in-suspend", &len))
host->pm_caps |= MMC_PM_KEEP_POWER;
if (of_find_property(np, "enable-sdio-wakeup", &len))
host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
if (of_find_property(np, "mmc-ddr-1_8v", &len))
host->caps |= MMC_CAP_1_8V_DDR;
if (of_find_property(np, "mmc-ddr-1_2v", &len))
host->caps |= MMC_CAP_1_2V_DDR;
if (of_find_property(np, "mmc-hs200-1_8v", &len))
host->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
if (of_find_property(np, "mmc-hs200-1_2v", &len))
host->caps2 |= MMC_CAP2_HS200_1_2V_SDR;
if (of_find_property(np, "mmc-hs400-1_8v", &len))
host->caps2 |= MMC_CAP2_HS400_1_8V | MMC_CAP2_HS200_1_8V_SDR;
if (of_find_property(np, "mmc-hs400-1_2v", &len))
host->caps2 |= MMC_CAP2_HS400_1_2V | MMC_CAP2_HS200_1_2V_SDR;
if (of_find_property(np, "supports-hs400-enhanced-strobe", NULL))
host->caps2 |= MMC_CAP2_STROBE_ENHANCED;
if (of_find_property(np, "skip-init-mmc-scan", NULL))
host->caps2 |= MMC_CAP2_SKIP_INIT_SCAN;
host->dsr_req = !of_property_read_u32(np, "dsr", &host->dsr);
if (host->dsr_req && (host->dsr & ~0xffff)) {
dev_err(host->parent,
"device tree specified broken value for DSR: 0x%x, ignoring\n",
host->dsr);
host->dsr_req = 0;
}
if (!of_property_read_u32(np, "device_drv", &device_strength))
host->device_drv = device_strength << 4;
else
host->device_drv = MMC_DRIVER_TYPE_0;
return 0;
out:
mmc_gpio_free_cd(host);
return ret;
}
EXPORT_SYMBOL(mmc_of_parse);
/**
* 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)
{
int err;
struct mmc_host *host;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
/* scanning will be enabled when we're ready */
host->rescan_disable = 1;
idr_preload(GFP_KERNEL);
spin_lock(&mmc_host_lock);
err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
if (err >= 0)
host->index = err;
spin_unlock(&mmc_host_lock);
idr_preload_end();
if (err < 0)
goto free;
dev_set_name(&host->class_dev, "mmc%d", host->index);
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
mmc_host_clk_init(host);
mutex_init(&host->slot.lock);
host->slot.cd_irq = -EINVAL;
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
host->wlock_name = kasprintf(GFP_KERNEL,
"%s_detect", mmc_hostname(host));
wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND,
host->wlock_name);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
#ifdef CONFIG_PM
host->pm_notify.notifier_call = mmc_pm_notify;
#endif
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_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;
free:
kfree(host);
return NULL;
}
EXPORT_SYMBOL(mmc_alloc_host);
/**
* mmc_add_host - initialise host hardware
* @host: mmc host
*
* Register the host with the driver model. The host must be
* prepared to start servicing requests before this function
* completes.
*/
int mmc_add_host(struct mmc_host *host)
{
int err;
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
err = device_add(&host->class_dev);
if (err)
return err;
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
mmc_host_clk_sysfs_init(host);
mmc_start_host(host);
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
register_pm_notifier(&host->pm_notify);
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. No new requests will be issued
* after this function has returned.
*/
void mmc_remove_host(struct mmc_host *host)
{
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
unregister_pm_notifier(&host->pm_notify);
mmc_stop_host(host);
#ifdef CONFIG_DEBUG_FS
mmc_remove_host_debugfs(host);
#endif
device_del(&host->class_dev);
led_trigger_unregister_simple(host->led);
mmc_host_clk_exit(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)
{
spin_lock(&mmc_host_lock);
idr_remove(&mmc_host_idr, host->index);
spin_unlock(&mmc_host_lock);
wake_lock_destroy(&host->detect_wake_lock);
put_device(&host->class_dev);
}
EXPORT_SYMBOL(mmc_free_host);

19
drivers/mmc/core/host.h Normal file
View file

@ -0,0 +1,19 @@
/*
* 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
#include <linux/mmc/host.h>
int mmc_register_host_class(void);
void mmc_unregister_host_class(void);
#endif

2108
drivers/mmc/core/mmc.c Normal file

File diff suppressed because it is too large Load diff

677
drivers/mmc/core/mmc_ops.c Normal file
View file

@ -0,0 +1,677 @@
/*
* linux/drivers/mmc/core/mmc_ops.h
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/scatterlist.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include "core.h"
#include "mmc_ops.h"
#define MMC_OPS_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */
static inline int __mmc_send_status(struct mmc_card *card, u32 *status,
bool ignore_crc)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!card);
BUG_ON(!card->host);
cmd.opcode = MMC_SEND_STATUS;
if (!mmc_host_is_spi(card->host))
cmd.arg = card->rca << 16;
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
if (ignore_crc)
cmd.flags &= ~MMC_RSP_CRC;
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
/* NOTE: callers are required to understand the difference
* between "native" and SPI format status words!
*/
if (status)
*status = cmd.resp[0];
return 0;
}
int mmc_send_status(struct mmc_card *card, u32 *status)
{
return __mmc_send_status(card, status, false);
}
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!host);
cmd.opcode = MMC_SELECT_CARD;
if (card) {
cmd.arg = card->rca << 16;
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
} else {
cmd.arg = 0;
cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
}
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
return 0;
}
int mmc_select_card(struct mmc_card *card)
{
BUG_ON(!card);
return _mmc_select_card(card->host, card);
}
int mmc_deselect_cards(struct mmc_host *host)
{
return _mmc_select_card(host, NULL);
}
/*
* Write the value specified in the device tree or board code into the optional
* 16 bit Driver Stage Register. This can be used to tune raise/fall times and
* drive strength of the DAT and CMD outputs. The actual meaning of a given
* value is hardware dependant.
* The presence of the DSR register can be determined from the CSD register,
* bit 76.
*/
int mmc_set_dsr(struct mmc_host *host)
{
struct mmc_command cmd = {0};
cmd.opcode = MMC_SET_DSR;
cmd.arg = (host->dsr << 16) | 0xffff;
cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
}
int mmc_go_idle(struct mmc_host *host)
{
int err;
struct mmc_command cmd = {0};
/*
* Non-SPI hosts need to prevent chipselect going active during
* GO_IDLE; that would put chips into SPI mode. Remind them of
* that in case of hardware that won't pull up DAT3/nCS otherwise.
*
* SPI hosts ignore ios.chip_select; it's managed according to
* rules that must accommodate non-MMC slaves which this layer
* won't even know about.
*/
if (!mmc_host_is_spi(host)) {
mmc_set_chip_select(host, MMC_CS_HIGH);
mmc_delay(1);
}
cmd.opcode = MMC_GO_IDLE_STATE;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
err = mmc_wait_for_cmd(host, &cmd, 0);
mmc_delay(1);
if (!mmc_host_is_spi(host)) {
mmc_set_chip_select(host, MMC_CS_DONTCARE);
mmc_delay(1);
}
host->use_spi_crc = 0;
return err;
}
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {0};
int i, err = 0;
BUG_ON(!host);
cmd.opcode = MMC_SEND_OP_COND;
cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
for (i = 100; i; i--) {
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
break;
/* if we're just probing, do a single pass */
if (ocr == 0)
break;
/* otherwise wait until reset completes */
if (mmc_host_is_spi(host)) {
if (!(cmd.resp[0] & R1_SPI_IDLE))
break;
} else {
if (cmd.resp[0] & MMC_CARD_BUSY)
break;
}
err = -ETIMEDOUT;
mmc_delay(10);
}
if (rocr && !mmc_host_is_spi(host))
*rocr = cmd.resp[0];
return err;
}
int mmc_all_send_cid(struct mmc_host *host, u32 *cid)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!host);
BUG_ON(!cid);
cmd.opcode = MMC_ALL_SEND_CID;
cmd.arg = 0;
cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
memcpy(cid, cmd.resp, sizeof(u32) * 4);
return 0;
}
int mmc_set_relative_addr(struct mmc_card *card)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!card);
BUG_ON(!card->host);
cmd.opcode = MMC_SET_RELATIVE_ADDR;
cmd.arg = card->rca << 16;
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
return 0;
}
static int
mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!host);
BUG_ON(!cxd);
cmd.opcode = opcode;
cmd.arg = arg;
cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
memcpy(cxd, cmd.resp, sizeof(u32) * 4);
return 0;
}
/*
* NOTE: void *buf, caller for the buf is required to use DMA-capable
* buffer or on-stack buffer (with some overhead in callee).
*/
static int
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
u32 opcode, void *buf, unsigned len)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
void *data_buf;
int is_on_stack;
is_on_stack = object_is_on_stack(buf);
if (is_on_stack) {
/*
* dma onto stack is unsafe/nonportable, but callers to this
* routine normally provide temporary on-stack buffers ...
*/
data_buf = kmalloc(len, GFP_KERNEL);
if (!data_buf)
return -ENOMEM;
} else
data_buf = buf;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = opcode;
cmd.arg = 0;
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
* rely on callers to never use this with "native" calls for reading
* CSD or CID. Native versions of those commands use the R2 type,
* not R1 plus a data block.
*/
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = len;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, data_buf, len);
if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
/*
* The spec states that CSR and CID accesses have a timeout
* of 64 clock cycles.
*/
data.timeout_ns = 0;
data.timeout_clks = 64;
} else
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(host, &mrq);
if (is_on_stack) {
memcpy(buf, data_buf, len);
kfree(data_buf);
}
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return 0;
}
int mmc_send_csd(struct mmc_card *card, u32 *csd)
{
int ret, i;
u32 *csd_tmp;
if (!mmc_host_is_spi(card->host))
return mmc_send_cxd_native(card->host, card->rca << 16,
csd, MMC_SEND_CSD);
csd_tmp = kmalloc(16, GFP_KERNEL);
if (!csd_tmp)
return -ENOMEM;
ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd_tmp, 16);
if (ret)
goto err;
for (i = 0;i < 4;i++)
csd[i] = be32_to_cpu(csd_tmp[i]);
err:
kfree(csd_tmp);
return ret;
}
int mmc_send_cid(struct mmc_host *host, u32 *cid)
{
int ret, i;
u32 *cid_tmp;
if (!mmc_host_is_spi(host)) {
if (!host->card)
return -EINVAL;
return mmc_send_cxd_native(host, host->card->rca << 16,
cid, MMC_SEND_CID);
}
cid_tmp = kmalloc(16, GFP_KERNEL);
if (!cid_tmp)
return -ENOMEM;
ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid_tmp, 16);
if (ret)
goto err;
for (i = 0;i < 4;i++)
cid[i] = be32_to_cpu(cid_tmp[i]);
err:
kfree(cid_tmp);
return ret;
}
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
{
return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
ext_csd, 512);
}
EXPORT_SYMBOL_GPL(mmc_send_ext_csd);
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
{
struct mmc_command cmd = {0};
int err;
cmd.opcode = MMC_SPI_READ_OCR;
cmd.arg = highcap ? (1 << 30) : 0;
cmd.flags = MMC_RSP_SPI_R3;
err = mmc_wait_for_cmd(host, &cmd, 0);
*ocrp = cmd.resp[1];
return err;
}
int mmc_spi_set_crc(struct mmc_host *host, int use_crc)
{
struct mmc_command cmd = {0};
int err;
cmd.opcode = MMC_SPI_CRC_ON_OFF;
cmd.flags = MMC_RSP_SPI_R1;
cmd.arg = use_crc;
err = mmc_wait_for_cmd(host, &cmd, 0);
if (!err)
host->use_spi_crc = use_crc;
return err;
}
/**
* __mmc_switch - modify EXT_CSD register
* @card: the MMC card associated with the data transfer
* @set: cmd set values
* @index: EXT_CSD register index
* @value: value to program into EXT_CSD register
* @timeout_ms: timeout (ms) for operation performed by register write,
* timeout of zero implies maximum possible timeout
* @use_busy_signal: use the busy signal as response type
* @send_status: send status cmd to poll for busy
* @ignore_crc: ignore CRC errors when sending status cmd to poll for busy
*
* Modifies the EXT_CSD register for selected card.
*/
int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms, bool use_busy_signal, bool send_status,
bool ignore_crc)
{
struct mmc_host *host = card->host;
int err;
struct mmc_command cmd = {0};
unsigned long timeout;
u32 status = 0;
bool use_r1b_resp = use_busy_signal;
/*
* If the cmd timeout and the max_busy_timeout of the host are both
* specified, let's validate them. A failure means we need to prevent
* the host from doing hw busy detection, which is done by converting
* to a R1 response instead of a R1B.
*/
if (timeout_ms && host->max_busy_timeout &&
(timeout_ms > host->max_busy_timeout))
use_r1b_resp = false;
cmd.opcode = MMC_SWITCH;
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(index << 16) |
(value << 8) |
set;
cmd.flags = MMC_CMD_AC;
if (use_r1b_resp) {
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
/*
* A busy_timeout of zero means the host can decide to use
* whatever value it finds suitable.
*/
cmd.busy_timeout = timeout_ms;
} else {
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
}
if (index == EXT_CSD_SANITIZE_START)
cmd.sanitize_busy = true;
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
/* No need to check card status in case of unblocking command */
if (!use_busy_signal)
return 0;
/*
* CRC errors shall only be ignored in cases were CMD13 is used to poll
* to detect busy completion.
*/
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
ignore_crc = false;
/* We have an unspecified cmd timeout, use the fallback value. */
if (!timeout_ms)
timeout_ms = MMC_OPS_TIMEOUT_MS;
/* Must check status to be sure of no errors. */
timeout = jiffies + msecs_to_jiffies(timeout_ms);
do {
if (send_status) {
err = __mmc_send_status(card, &status, ignore_crc);
if (err)
return err;
}
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
break;
if (mmc_host_is_spi(host))
break;
/*
* We are not allowed to issue a status command and the host
* does'nt support MMC_CAP_WAIT_WHILE_BUSY, then we can only
* rely on waiting for the stated timeout to be sufficient.
*/
if (!send_status) {
mmc_delay(timeout_ms);
return 0;
}
/* Timeout if the device never leaves the program state. */
if (time_after(jiffies, timeout)) {
pr_err("%s: Card stuck in programming state! %s\n",
mmc_hostname(host), __func__);
return -ETIMEDOUT;
}
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
if (mmc_host_is_spi(host)) {
if (status & R1_SPI_ILLEGAL_COMMAND)
return -EBADMSG;
} else {
if (status & 0xFDFFA000)
pr_warn("%s: unexpected status %#x after switch\n",
mmc_hostname(host), status);
if (status & R1_SWITCH_ERROR)
return -EBADMSG;
}
return 0;
}
EXPORT_SYMBOL_GPL(__mmc_switch);
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms)
{
return __mmc_switch(card, set, index, value, timeout_ms, true, true,
false);
}
EXPORT_SYMBOL_GPL(mmc_switch);
static int
mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
u8 len)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
u8 *data_buf;
u8 *test_buf;
int i, err;
static u8 testdata_8bit[8] = { 0x55, 0xaa, 0, 0, 0, 0, 0, 0 };
static u8 testdata_4bit[4] = { 0x5a, 0, 0, 0 };
/* dma onto stack is unsafe/nonportable, but callers to this
* routine normally provide temporary on-stack buffers ...
*/
data_buf = kmalloc(len, GFP_KERNEL);
if (!data_buf)
return -ENOMEM;
if (len == 8)
test_buf = testdata_8bit;
else if (len == 4)
test_buf = testdata_4bit;
else {
pr_err("%s: Invalid bus_width %d\n",
mmc_hostname(host), len);
kfree(data_buf);
return -EINVAL;
}
if (opcode == MMC_BUS_TEST_W)
memcpy(data_buf, test_buf, len);
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = opcode;
cmd.arg = 0;
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
* rely on callers to never use this with "native" calls for reading
* CSD or CID. Native versions of those commands use the R2 type,
* not R1 plus a data block.
*/
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = len;
data.blocks = 1;
if (opcode == MMC_BUS_TEST_R)
data.flags = MMC_DATA_READ;
else
data.flags = MMC_DATA_WRITE;
data.sg = &sg;
data.sg_len = 1;
mmc_set_data_timeout(&data, card);
sg_init_one(&sg, data_buf, len);
mmc_wait_for_req(host, &mrq);
err = 0;
if (opcode == MMC_BUS_TEST_R) {
for (i = 0; i < len / 4; i++)
if ((test_buf[i] ^ data_buf[i]) != 0xff) {
err = -EIO;
break;
}
}
kfree(data_buf);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return err;
}
int mmc_bus_test(struct mmc_card *card, u8 bus_width)
{
int err, width;
if (bus_width == MMC_BUS_WIDTH_8)
width = 8;
else if (bus_width == MMC_BUS_WIDTH_4)
width = 4;
else if (bus_width == MMC_BUS_WIDTH_1)
return 0; /* no need for test */
else
return -EINVAL;
/*
* Ignore errors from BUS_TEST_W. BUS_TEST_R will fail if there
* is a problem. This improves chances that the test will work.
*/
mmc_send_bus_test(card, card->host, MMC_BUS_TEST_W, width);
err = mmc_send_bus_test(card, card->host, MMC_BUS_TEST_R, width);
return err;
}
int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status)
{
struct mmc_command cmd = {0};
unsigned int opcode;
int err;
if (!card->ext_csd.hpi) {
pr_warn("%s: Card didn't support HPI command\n",
mmc_hostname(card->host));
return -EINVAL;
}
opcode = card->ext_csd.hpi_cmd;
if (opcode == MMC_STOP_TRANSMISSION)
cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
else if (opcode == MMC_SEND_STATUS)
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
cmd.opcode = opcode;
cmd.arg = card->rca << 16 | 1;
err = mmc_wait_for_cmd(card->host, &cmd, 0);
if (err) {
pr_warn("%s: error %d interrupting operation. "
"HPI command response %#x\n", mmc_hostname(card->host),
err, cmd.resp[0]);
return err;
}
if (status)
*status = cmd.resp[0];
return 0;
}

View file

@ -0,0 +1,32 @@
/*
* linux/drivers/mmc/core/mmc_ops.h
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#ifndef _MMC_MMC_OPS_H
#define _MMC_MMC_OPS_H
int mmc_select_card(struct mmc_card *card);
int mmc_deselect_cards(struct mmc_host *host);
int mmc_set_dsr(struct mmc_host *host);
int mmc_go_idle(struct mmc_host *host);
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
int mmc_all_send_cid(struct mmc_host *host, u32 *cid);
int mmc_set_relative_addr(struct mmc_card *card);
int mmc_send_csd(struct mmc_card *card, u32 *csd);
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
int mmc_send_status(struct mmc_card *card, u32 *status);
int mmc_send_cid(struct mmc_host *host, u32 *cid);
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
int mmc_bus_test(struct mmc_card *card, u8 bus_width);
int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status);
#endif

99
drivers/mmc/core/quirks.c Normal file
View file

@ -0,0 +1,99 @@
/*
* This file contains work-arounds for many known SD/MMC
* and SDIO hardware bugs.
*
* Copyright (c) 2011 Andrei Warkentin <andreiw@motorola.com>
* Copyright (c) 2011 Pierre Tardy <tardyp@gmail.com>
* Inspired from pci fixup code:
* Copyright (c) 1999 Martin Mares <mj@ucw.cz>
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio_ids.h>
#ifndef SDIO_VENDOR_ID_TI
#define SDIO_VENDOR_ID_TI 0x0097
#endif
#ifndef SDIO_DEVICE_ID_TI_WL1271
#define SDIO_DEVICE_ID_TI_WL1271 0x4076
#endif
#ifndef SDIO_VENDOR_ID_STE
#define SDIO_VENDOR_ID_STE 0x0020
#endif
#ifndef SDIO_DEVICE_ID_STE_CW1200
#define SDIO_DEVICE_ID_STE_CW1200 0x2280
#endif
#ifndef SDIO_DEVICE_ID_MARVELL_8797_F0
#define SDIO_DEVICE_ID_MARVELL_8797_F0 0x9128
#endif
/*
* This hook just adds a quirk for all sdio devices
*/
static void add_quirk_for_sdio_devices(struct mmc_card *card, int data)
{
if (mmc_card_sdio(card))
card->quirks |= data;
}
static const struct mmc_fixup mmc_fixup_methods[] = {
/* by default sdio devices are considered CLK_GATING broken */
/* good cards will be whitelisted as they are tested */
SDIO_FIXUP(SDIO_ANY_ID, SDIO_ANY_ID,
add_quirk_for_sdio_devices,
MMC_QUIRK_BROKEN_CLK_GATING),
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING),
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
add_quirk, MMC_QUIRK_NONSTD_FUNC_IF),
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
add_quirk, MMC_QUIRK_DISABLE_CD),
SDIO_FIXUP(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200,
add_quirk, MMC_QUIRK_BROKEN_BYTE_MODE_512),
SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8797_F0,
add_quirk, MMC_QUIRK_BROKEN_IRQ_POLLING),
END_FIXUP
};
void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table)
{
const struct mmc_fixup *f;
u64 rev = cid_rev_card(card);
/* Non-core specific workarounds. */
if (!table)
table = mmc_fixup_methods;
for (f = table; f->vendor_fixup; f++) {
if ((f->manfid == CID_MANFID_ANY ||
f->manfid == card->cid.manfid) &&
(f->oemid == CID_OEMID_ANY ||
f->oemid == card->cid.oemid) &&
(f->name == CID_NAME_ANY ||
!strncmp(f->name, card->cid.prod_name,
sizeof(card->cid.prod_name))) &&
(f->cis_vendor == card->cis.vendor ||
f->cis_vendor == (u16) SDIO_ANY_ID) &&
(f->cis_device == card->cis.device ||
f->cis_device == (u16) SDIO_ANY_ID) &&
rev >= f->rev_start && rev <= f->rev_end) {
dev_dbg(&card->dev, "calling %pf\n", f->vendor_fixup);
f->vendor_fixup(card, f->data);
}
}
}
EXPORT_SYMBOL(mmc_fixup_device);

1367
drivers/mmc/core/sd.c Normal file

File diff suppressed because it is too large Load diff

16
drivers/mmc/core/sd.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef _MMC_CORE_SD_H
#define _MMC_CORE_SD_H
#include <linux/mmc/card.h>
extern struct device_type sd_type;
int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr);
int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card);
void mmc_decode_cid(struct mmc_card *card);
int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
bool reinit);
unsigned mmc_sd_get_max_clock(struct mmc_card *card);
int mmc_sd_switch_hs(struct mmc_card *card);
#endif

395
drivers/mmc/core/sd_ops.c Normal file
View file

@ -0,0 +1,395 @@
/*
* linux/drivers/mmc/core/sd_ops.h
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/export.h>
#include <linux/scatterlist.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include "core.h"
#include "sd_ops.h"
int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!host);
BUG_ON(card && (card->host != host));
cmd.opcode = MMC_APP_CMD;
if (card) {
cmd.arg = card->rca << 16;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
} else {
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
}
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
return err;
/* Check that card supported application commands */
if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
return -EOPNOTSUPP;
return 0;
}
EXPORT_SYMBOL_GPL(mmc_app_cmd);
/**
* mmc_wait_for_app_cmd - start an application command and wait for
completion
* @host: MMC host to start command
* @card: Card to send MMC_APP_CMD to
* @cmd: MMC command to start
* @retries: maximum number of retries
*
* Sends a MMC_APP_CMD, checks the card response, sends the command
* in the parameter and waits for it to complete. Return any error
* that occurred while the command was executing. Do not attempt to
* parse the response.
*/
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
struct mmc_command *cmd, int retries)
{
struct mmc_request mrq = {NULL};
int i, err;
BUG_ON(!cmd);
BUG_ON(retries < 0);
err = -EIO;
/*
* We have to resend MMC_APP_CMD for each attempt so
* we cannot use the retries field in mmc_command.
*/
for (i = 0;i <= retries;i++) {
err = mmc_app_cmd(host, card);
if (err) {
/* no point in retrying; no APP commands allowed */
if (mmc_host_is_spi(host)) {
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
break;
}
continue;
}
memset(&mrq, 0, sizeof(struct mmc_request));
memset(cmd->resp, 0, sizeof(cmd->resp));
cmd->retries = 0;
mrq.cmd = cmd;
cmd->data = NULL;
mmc_wait_for_req(host, &mrq);
err = cmd->error;
if (!cmd->error)
break;
/* no point in retrying illegal APP commands */
if (mmc_host_is_spi(host)) {
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
break;
}
}
return err;
}
EXPORT_SYMBOL(mmc_wait_for_app_cmd);
int mmc_app_set_bus_width(struct mmc_card *card, int width)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!card);
BUG_ON(!card->host);
cmd.opcode = SD_APP_SET_BUS_WIDTH;
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
switch (width) {
case MMC_BUS_WIDTH_1:
cmd.arg = SD_BUS_WIDTH_1;
break;
case MMC_BUS_WIDTH_4:
cmd.arg = SD_BUS_WIDTH_4;
break;
default:
return -EINVAL;
}
err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
return 0;
}
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {0};
int i, err = 0;
BUG_ON(!host);
cmd.opcode = SD_APP_OP_COND;
if (mmc_host_is_spi(host))
cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */
else
cmd.arg = ocr;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
for (i = 100; i; i--) {
err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
if (err)
break;
/* if we're just probing, do a single pass */
if (ocr == 0)
break;
/* otherwise wait until reset completes */
if (mmc_host_is_spi(host)) {
if (!(cmd.resp[0] & R1_SPI_IDLE))
break;
} else {
if (cmd.resp[0] & MMC_CARD_BUSY)
break;
}
err = -ETIMEDOUT;
mmc_delay(10);
}
if (!i)
pr_err("%s: card never left busy state\n", mmc_hostname(host));
if (rocr && !mmc_host_is_spi(host))
*rocr = cmd.resp[0];
return err;
}
int mmc_send_if_cond(struct mmc_host *host, u32 ocr)
{
struct mmc_command cmd = {0};
int err;
static const u8 test_pattern = 0xAA;
u8 result_pattern;
/*
* To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND
* before SD_APP_OP_COND. This command will harmlessly fail for
* SD 1.0 cards.
*/
cmd.opcode = SD_SEND_IF_COND;
cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;
cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
return err;
if (mmc_host_is_spi(host))
result_pattern = cmd.resp[1] & 0xFF;
else
result_pattern = cmd.resp[0] & 0xFF;
if (result_pattern != test_pattern)
return -EIO;
return 0;
}
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!host);
BUG_ON(!rca);
cmd.opcode = SD_SEND_RELATIVE_ADDR;
cmd.arg = 0;
cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
*rca = cmd.resp[0] >> 16;
return 0;
}
int mmc_app_send_scr(struct mmc_card *card, u32 *scr)
{
int err;
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
void *data_buf;
BUG_ON(!card);
BUG_ON(!card->host);
BUG_ON(!scr);
/* NOTE: caller guarantees scr is heap-allocated */
err = mmc_app_cmd(card->host, card);
if (err)
return err;
/* dma onto stack is unsafe/nonportable, but callers to this
* routine normally provide temporary on-stack buffers ...
*/
data_buf = kmalloc(sizeof(card->raw_scr), GFP_KERNEL);
if (data_buf == NULL)
return -ENOMEM;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_APP_SEND_SCR;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = 8;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, data_buf, 8);
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
memcpy(scr, data_buf, sizeof(card->raw_scr));
kfree(data_buf);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
scr[0] = be32_to_cpu(scr[0]);
scr[1] = be32_to_cpu(scr[1]);
return 0;
}
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
u8 value, u8 *resp)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
BUG_ON(!card);
BUG_ON(!card->host);
/* NOTE: caller guarantees resp is heap-allocated */
mode = !!mode;
value &= 0xF;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_SWITCH;
cmd.arg = mode << 31 | 0x00FFFFFF;
cmd.arg &= ~(0xF << (group * 4));
cmd.arg |= value << (group * 4);
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = 64;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, resp, 64);
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return 0;
}
int mmc_app_sd_status(struct mmc_card *card, void *ssr)
{
int err;
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
BUG_ON(!card);
BUG_ON(!card->host);
BUG_ON(!ssr);
/* NOTE: caller guarantees ssr is heap-allocated */
err = mmc_app_cmd(card->host, card);
if (err)
return err;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_APP_SD_STATUS;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = 64;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, ssr, 64);
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return 0;
}

25
drivers/mmc/core/sd_ops.h Normal file
View file

@ -0,0 +1,25 @@
/*
* linux/drivers/mmc/core/sd_ops.h
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#ifndef _MMC_SD_OPS_H
#define _MMC_SD_OPS_H
int mmc_app_set_bus_width(struct mmc_card *card, int width);
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
int mmc_app_send_scr(struct mmc_card *card, u32 *scr);
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
u8 value, u8 *resp);
int mmc_app_sd_status(struct mmc_card *card, void *ssr);
#endif

1280
drivers/mmc/core/sdio.c Normal file

File diff suppressed because it is too large Load diff

351
drivers/mmc/core/sdio_bus.c Normal file
View file

@ -0,0 +1,351 @@
/*
* linux/drivers/mmc/core/sdio_bus.c
*
* 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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* SDIO function driver model
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/acpi.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_cis.h"
#include "sdio_bus.h"
#ifdef CONFIG_MMC_EMBEDDED_SDIO
#include <linux/mmc/host.h>
#endif
/* show configuration fields */
#define sdio_config_attr(field, format_string) \
static ssize_t \
field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct sdio_func *func; \
\
func = dev_to_sdio_func (dev); \
return sprintf (buf, format_string, func->field); \
} \
static DEVICE_ATTR_RO(field)
sdio_config_attr(class, "0x%02x\n");
sdio_config_attr(vendor, "0x%04x\n");
sdio_config_attr(device, "0x%04x\n");
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sdio_func *func = dev_to_sdio_func (dev);
return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n",
func->class, func->vendor, func->device);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *sdio_dev_attrs[] = {
&dev_attr_class.attr,
&dev_attr_vendor.attr,
&dev_attr_device.attr,
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(sdio_dev);
static const struct sdio_device_id *sdio_match_one(struct sdio_func *func,
const struct sdio_device_id *id)
{
if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class)
return NULL;
if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor)
return NULL;
if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device)
return NULL;
return id;
}
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
struct sdio_driver *sdrv)
{
const struct sdio_device_id *ids;
ids = sdrv->id_table;
if (ids) {
while (ids->class || ids->vendor || ids->device) {
if (sdio_match_one(func, ids))
return ids;
ids++;
}
}
return NULL;
}
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct sdio_driver *sdrv = to_sdio_driver(drv);
if (sdio_match_device(func, sdrv))
return 1;
return 0;
}
static int
sdio_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct sdio_func *func = dev_to_sdio_func(dev);
if (add_uevent_var(env,
"SDIO_CLASS=%02X", func->class))
return -ENOMEM;
if (add_uevent_var(env,
"SDIO_ID=%04X:%04X", func->vendor, func->device))
return -ENOMEM;
if (add_uevent_var(env,
"MODALIAS=sdio:c%02Xv%04Xd%04X",
func->class, func->vendor, func->device))
return -ENOMEM;
return 0;
}
static int sdio_bus_probe(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
const struct sdio_device_id *id;
int ret;
id = sdio_match_device(func, drv);
if (!id)
return -ENODEV;
/* Unbound SDIO functions are always suspended.
* During probe, the function is set active and the usage count
* is incremented. If the driver supports runtime PM,
* it should call pm_runtime_put_noidle() in its probe routine and
* pm_runtime_get_noresume() in its remove routine.
*/
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
ret = pm_runtime_get_sync(dev);
if (ret < 0)
goto disable_runtimepm;
}
/* Set the default block size so the driver is sure it's something
* sensible. */
sdio_claim_host(func);
ret = sdio_set_block_size(func, 0);
sdio_release_host(func);
if (ret)
goto disable_runtimepm;
ret = drv->probe(func, id);
if (ret)
goto disable_runtimepm;
return 0;
disable_runtimepm:
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
return ret;
}
static int sdio_bus_remove(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
int ret = 0;
/* Make sure card is powered before invoking ->remove() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_get_sync(dev);
drv->remove(func);
if (func->irq_handler) {
pr_warn("WARNING: driver %s did not remove its interrupt handler!\n",
drv->name);
sdio_claim_host(func);
sdio_release_irq(func);
sdio_release_host(func);
}
/* First, undo the increment made directly above */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
/* Then undo the runtime PM settings in sdio_bus_probe() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_sync(dev);
return ret;
}
#ifdef CONFIG_PM
static const struct dev_pm_ops sdio_bus_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_generic_suspend, pm_generic_resume)
SET_RUNTIME_PM_OPS(
pm_generic_runtime_suspend,
pm_generic_runtime_resume,
NULL
)
};
#define SDIO_PM_OPS_PTR (&sdio_bus_pm_ops)
#else /* !CONFIG_PM */
#define SDIO_PM_OPS_PTR NULL
#endif /* !CONFIG_PM */
static struct bus_type sdio_bus_type = {
.name = "sdio",
.dev_groups = sdio_dev_groups,
.match = sdio_bus_match,
.uevent = sdio_bus_uevent,
.probe = sdio_bus_probe,
.remove = sdio_bus_remove,
.pm = SDIO_PM_OPS_PTR,
};
int sdio_register_bus(void)
{
return bus_register(&sdio_bus_type);
}
void sdio_unregister_bus(void)
{
bus_unregister(&sdio_bus_type);
}
/**
* sdio_register_driver - register a function driver
* @drv: SDIO function driver
*/
int sdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type;
return driver_register(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_register_driver);
/**
* sdio_unregister_driver - unregister a function driver
* @drv: SDIO function driver
*/
void sdio_unregister_driver(struct sdio_driver *drv)
{
drv->drv.bus = &sdio_bus_type;
driver_unregister(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_unregister_driver);
static void sdio_release_func(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
#ifdef CONFIG_MMC_EMBEDDED_SDIO
/*
* If this device is embedded then we never allocated
* cis tables for this func
*/
if (!func->card->host->embedded_sdio_data.funcs)
#endif
sdio_free_func_cis(func);
kfree(func->info);
kfree(func);
}
/*
* Allocate and initialise a new SDIO function structure.
*/
struct sdio_func *sdio_alloc_func(struct mmc_card *card)
{
struct sdio_func *func;
func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
if (!func)
return ERR_PTR(-ENOMEM);
func->card = card;
device_initialize(&func->dev);
func->dev.parent = &card->dev;
func->dev.bus = &sdio_bus_type;
func->dev.release = sdio_release_func;
return func;
}
#ifdef CONFIG_ACPI
static void sdio_acpi_set_handle(struct sdio_func *func)
{
struct mmc_host *host = func->card->host;
u64 addr = (host->slotno << 16) | func->num;
acpi_preset_companion(&func->dev, ACPI_COMPANION(host->parent), addr);
}
#else
static inline void sdio_acpi_set_handle(struct sdio_func *func) {}
#endif
/*
* Register a new SDIO function with the driver model.
*/
int sdio_add_func(struct sdio_func *func)
{
int ret;
dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);
sdio_acpi_set_handle(func);
ret = device_add(&func->dev);
if (ret == 0) {
sdio_func_set_present(func);
dev_pm_domain_attach(&func->dev, false);
}
return ret;
}
/*
* Unregister a SDIO function with the driver model, and
* (eventually) free it.
* This function can be called through error paths where sdio_add_func() was
* never executed (because a failure occurred at an earlier point).
*/
void sdio_remove_func(struct sdio_func *func)
{
if (!sdio_func_present(func))
return;
dev_pm_domain_detach(&func->dev, false);
device_del(&func->dev);
put_device(&func->dev);
}

View file

@ -0,0 +1,22 @@
/*
* linux/drivers/mmc/core/sdio_bus.h
*
* 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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#ifndef _MMC_CORE_SDIO_BUS_H
#define _MMC_CORE_SDIO_BUS_H
struct sdio_func *sdio_alloc_func(struct mmc_card *card);
int sdio_add_func(struct sdio_func *func);
void sdio_remove_func(struct sdio_func *func);
int sdio_register_bus(void);
void sdio_unregister_bus(void);
#endif

412
drivers/mmc/core/sdio_cis.c Normal file
View file

@ -0,0 +1,412 @@
/*
* linux/drivers/mmc/core/sdio_cis.c
*
* Author: Nicolas Pitre
* Created: June 11, 2007
* Copyright: MontaVista Software Inc.
*
* 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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_cis.h"
#include "sdio_ops.h"
static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func,
const unsigned char *buf, unsigned size)
{
unsigned i, nr_strings;
char **buffer, *string;
/* Find all null-terminated (including zero length) strings in
the TPLLV1_INFO field. Trailing garbage is ignored. */
buf += 2;
size -= 2;
nr_strings = 0;
for (i = 0; i < size; i++) {
if (buf[i] == 0xff)
break;
if (buf[i] == 0)
nr_strings++;
}
if (nr_strings == 0)
return 0;
size = i;
buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
string = (char*)(buffer + nr_strings);
for (i = 0; i < nr_strings; i++) {
buffer[i] = string;
strcpy(string, buf);
string += strlen(string) + 1;
buf += strlen(buf) + 1;
}
if (func) {
func->num_info = nr_strings;
func->info = (const char**)buffer;
} else {
card->num_info = nr_strings;
card->info = (const char**)buffer;
}
return 0;
}
static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func,
const unsigned char *buf, unsigned size)
{
unsigned int vendor, device;
/* TPLMID_MANF */
vendor = buf[0] | (buf[1] << 8);
/* TPLMID_CARD */
device = buf[2] | (buf[3] << 8);
if (func) {
func->vendor = vendor;
func->device = device;
} else {
card->cis.vendor = vendor;
card->cis.device = device;
}
return 0;
}
static const unsigned char speed_val[16] =
{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
static const unsigned int speed_unit[8] =
{ 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
const unsigned char *, unsigned);
struct cis_tpl {
unsigned char code;
unsigned char min_size;
tpl_parse_t *parse;
};
static int cis_tpl_parse(struct mmc_card *card, struct sdio_func *func,
const char *tpl_descr,
const struct cis_tpl *tpl, int tpl_count,
unsigned char code,
const unsigned char *buf, unsigned size)
{
int i, ret;
/* look for a matching code in the table */
for (i = 0; i < tpl_count; i++, tpl++) {
if (tpl->code == code)
break;
}
if (i < tpl_count) {
if (size >= tpl->min_size) {
if (tpl->parse)
ret = tpl->parse(card, func, buf, size);
else
ret = -EILSEQ; /* known tuple, not parsed */
} else {
/* invalid tuple */
ret = -EINVAL;
}
if (ret && ret != -EILSEQ && ret != -ENOENT) {
pr_err("%s: bad %s tuple 0x%02x (%u bytes)\n",
mmc_hostname(card->host), tpl_descr, code, size);
}
} else {
/* unknown tuple */
ret = -ENOENT;
}
return ret;
}
static int cistpl_funce_common(struct mmc_card *card, struct sdio_func *func,
const unsigned char *buf, unsigned size)
{
/* Only valid for the common CIS (function 0) */
if (func)
return -EINVAL;
/* TPLFE_FN0_BLK_SIZE */
card->cis.blksize = buf[1] | (buf[2] << 8);
/* TPLFE_MAX_TRAN_SPEED */
card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] *
speed_unit[buf[3] & 7];
return 0;
}
static int cistpl_funce_func(struct mmc_card *card, struct sdio_func *func,
const unsigned char *buf, unsigned size)
{
unsigned vsn;
unsigned min_size;
/* Only valid for the individual function's CIS (1-7) */
if (!func)
return -EINVAL;
/*
* This tuple has a different length depending on the SDIO spec
* version.
*/
vsn = func->card->cccr.sdio_vsn;
min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
if (size < min_size)
return -EINVAL;
/* TPLFE_MAX_BLK_SIZE */
func->max_blksize = buf[12] | (buf[13] << 8);
/* TPLFE_ENABLE_TIMEOUT_VAL, present in ver 1.1 and above */
if (vsn > SDIO_SDIO_REV_1_00)
func->enable_timeout = (buf[28] | (buf[29] << 8)) * 10;
else
func->enable_timeout = jiffies_to_msecs(HZ);
return 0;
}
/*
* Known TPLFE_TYPEs table for CISTPL_FUNCE tuples.
*
* Note that, unlike PCMCIA, CISTPL_FUNCE tuples are not parsed depending
* on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO
* TPLFID_FUNCTION is always hardcoded to 0x0C.
*/
static const struct cis_tpl cis_tpl_funce_list[] = {
{ 0x00, 4, cistpl_funce_common },
{ 0x01, 0, cistpl_funce_func },
{ 0x04, 1+1+6, /* CISTPL_FUNCE_LAN_NODE_ID */ },
};
static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
const unsigned char *buf, unsigned size)
{
if (size < 1)
return -EINVAL;
return cis_tpl_parse(card, func, "CISTPL_FUNCE",
cis_tpl_funce_list,
ARRAY_SIZE(cis_tpl_funce_list),
buf[0], buf, size);
}
/* Known TPL_CODEs table for CIS tuples */
static const struct cis_tpl cis_tpl_list[] = {
{ 0x15, 3, cistpl_vers_1 },
{ 0x20, 4, cistpl_manfid },
{ 0x21, 2, /* cistpl_funcid */ },
{ 0x22, 0, cistpl_funce },
};
static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func)
{
int ret;
struct sdio_func_tuple *this, **prev;
unsigned i, ptr = 0;
/*
* Note that this works for the common CIS (function number 0) as
* well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS
* have the same offset.
*/
for (i = 0; i < 3; i++) {
unsigned char x, fn;
if (func)
fn = func->num;
else
fn = 0;
ret = mmc_io_rw_direct(card, 0, 0,
SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x);
if (ret)
return ret;
ptr |= x << (i * 8);
}
if (func)
prev = &func->tuples;
else
prev = &card->tuples;
BUG_ON(*prev);
do {
unsigned char tpl_code, tpl_link;
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code);
if (ret)
break;
/* 0xff means we're done */
if (tpl_code == 0xff)
break;
/* null entries have no link field or data */
if (tpl_code == 0x00)
continue;
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link);
if (ret)
break;
/* a size of 0xff also means we're done */
if (tpl_link == 0xff)
break;
this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL);
if (!this)
return -ENOMEM;
for (i = 0; i < tpl_link; i++) {
ret = mmc_io_rw_direct(card, 0, 0,
ptr + i, 0, &this->data[i]);
if (ret)
break;
}
if (ret) {
kfree(this);
break;
}
/* Try to parse the CIS tuple */
ret = cis_tpl_parse(card, func, "CIS",
cis_tpl_list, ARRAY_SIZE(cis_tpl_list),
tpl_code, this->data, tpl_link);
if (ret == -EILSEQ || ret == -ENOENT) {
/*
* The tuple is unknown or known but not parsed.
* Queue the tuple for the function driver.
*/
this->next = NULL;
this->code = tpl_code;
this->size = tpl_link;
*prev = this;
prev = &this->next;
if (ret == -ENOENT) {
/* warn about unknown tuples */
pr_warn_ratelimited("%s: queuing unknown"
" CIS tuple 0x%02x (%u bytes)\n",
mmc_hostname(card->host),
tpl_code, tpl_link);
}
/* keep on analyzing tuples */
ret = 0;
} else {
/*
* We don't need the tuple anymore if it was
* successfully parsed by the SDIO core or if it is
* not going to be queued for a driver.
*/
kfree(this);
}
ptr += tpl_link;
} while (!ret);
/*
* Link in all unknown tuples found in the common CIS so that
* drivers don't have to go digging in two places.
*/
if (func)
*prev = card->tuples;
return ret;
}
int sdio_read_common_cis(struct mmc_card *card)
{
return sdio_read_cis(card, NULL);
}
void sdio_free_common_cis(struct mmc_card *card)
{
struct sdio_func_tuple *tuple, *victim;
tuple = card->tuples;
while (tuple) {
victim = tuple;
tuple = tuple->next;
kfree(victim);
}
card->tuples = NULL;
}
int sdio_read_func_cis(struct sdio_func *func)
{
int ret;
ret = sdio_read_cis(func->card, func);
if (ret)
return ret;
/*
* Since we've linked to tuples in the card structure,
* we must make sure we have a reference to it.
*/
get_device(&func->card->dev);
/*
* Vendor/device id is optional for function CIS, so
* copy it from the card structure as needed.
*/
if (func->vendor == 0) {
func->vendor = func->card->cis.vendor;
func->device = func->card->cis.device;
}
return 0;
}
void sdio_free_func_cis(struct sdio_func *func)
{
struct sdio_func_tuple *tuple, *victim;
tuple = func->tuples;
while (tuple && tuple != func->card->tuples) {
victim = tuple;
tuple = tuple->next;
kfree(victim);
}
func->tuples = NULL;
/*
* We have now removed the link to the tuples in the
* card structure, so remove the reference.
*/
put_device(&func->card->dev);
}

View file

@ -0,0 +1,23 @@
/*
* linux/drivers/mmc/core/sdio_cis.h
*
* Author: Nicolas Pitre
* Created: June 11, 2007
* Copyright: MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#ifndef _MMC_SDIO_CIS_H
#define _MMC_SDIO_CIS_H
int sdio_read_common_cis(struct mmc_card *card);
void sdio_free_common_cis(struct mmc_card *card);
int sdio_read_func_cis(struct sdio_func *func);
void sdio_free_func_cis(struct sdio_func *func);
#endif

755
drivers/mmc/core/sdio_io.c Executable file
View file

@ -0,0 +1,755 @@
/*
* linux/drivers/mmc/core/sdio_io.c
*
* Copyright 2007-2008 Pierre Ossman
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/export.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_ops.h"
/**
* sdio_claim_host - exclusively claim a bus for a certain SDIO function
* @func: SDIO function that will be accessed
*
* Claim a bus for a set of operations. The SDIO function given
* is used to figure out which bus is relevant.
*/
void sdio_claim_host(struct sdio_func *func)
{
BUG_ON(!func);
BUG_ON(!func->card);
mmc_claim_host(func->card->host);
}
EXPORT_SYMBOL_GPL(sdio_claim_host);
/**
* sdio_release_host - release a bus for a certain SDIO function
* @func: SDIO function that was accessed
*
* Release a bus, allowing others to claim the bus for their
* operations.
*/
void sdio_release_host(struct sdio_func *func)
{
BUG_ON(!func);
BUG_ON(!func->card);
mmc_release_host(func->card->host);
}
EXPORT_SYMBOL_GPL(sdio_release_host);
/**
* sdio_enable_func - enables a SDIO function for usage
* @func: SDIO function to enable
*
* Powers up and activates a SDIO function so that register
* access is possible.
*/
int sdio_enable_func(struct sdio_func *func)
{
int ret;
unsigned char reg;
unsigned long timeout;
BUG_ON(!func);
BUG_ON(!func->card);
pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func));
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, &reg);
if (ret)
goto err;
reg |= 1 << func->num;
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
if (ret)
goto err;
timeout = jiffies + msecs_to_jiffies(func->enable_timeout);
while (1) {
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, &reg);
if (ret)
goto err;
if (reg & (1 << func->num))
break;
ret = -ETIME;
if (time_after(jiffies, timeout))
goto err;
}
pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func));
return 0;
err:
pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func));
return ret;
}
EXPORT_SYMBOL_GPL(sdio_enable_func);
/**
* sdio_disable_func - disable a SDIO function
* @func: SDIO function to disable
*
* Powers down and deactivates a SDIO function. Register access
* to this function will fail until the function is reenabled.
*/
int sdio_disable_func(struct sdio_func *func)
{
int ret;
unsigned char reg;
BUG_ON(!func);
BUG_ON(!func->card);
pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func));
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, &reg);
if (ret)
goto err;
reg &= ~(1 << func->num);
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
if (ret)
goto err;
pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func));
return 0;
err:
pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func));
return -EIO;
}
EXPORT_SYMBOL_GPL(sdio_disable_func);
/**
* sdio_set_block_size - set the block size of an SDIO function
* @func: SDIO function to change
* @blksz: new block size or 0 to use the default.
*
* The default block size is the largest supported by both the function
* and the host, with a maximum of 512 to ensure that arbitrarily sized
* data transfer use the optimal (least) number of commands.
*
* A driver may call this to override the default block size set by the
* core. This can be used to set a block size greater than the maximum
* that reported by the card; it is the driver's responsibility to ensure
* it uses a value that the card supports.
*
* Returns 0 on success, -EINVAL if the host does not support the
* requested block size, or -EIO (etc.) if one of the resultant FBR block
* size register writes failed.
*
*/
int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
{
int ret;
if (blksz > func->card->host->max_blk_size)
return -EINVAL;
if (blksz == 0) {
blksz = min(func->max_blksize, func->card->host->max_blk_size);
blksz = min(blksz, 512u);
}
ret = mmc_io_rw_direct(func->card, 1, 0,
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
blksz & 0xff, NULL);
if (ret)
return ret;
ret = mmc_io_rw_direct(func->card, 1, 0,
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
(blksz >> 8) & 0xff, NULL);
if (ret)
return ret;
func->cur_blksize = blksz;
return 0;
}
EXPORT_SYMBOL_GPL(sdio_set_block_size);
/*
* Calculate the maximum byte mode transfer size
*/
static inline unsigned int sdio_max_byte_size(struct sdio_func *func)
{
unsigned mval = func->card->host->max_blk_size;
if (mmc_blksz_for_byte_mode(func->card))
mval = min(mval, func->cur_blksize);
else
mval = min(mval, func->max_blksize);
if (mmc_card_broken_byte_mode_512(func->card))
return min(mval, 511u);
return min(mval, 512u); /* maximum size for byte mode */
}
/**
* sdio_align_size - pads a transfer size to a more optimal value
* @func: SDIO function
* @sz: original transfer size
*
* Pads the original data size with a number of extra bytes in
* order to avoid controller bugs and/or performance hits
* (e.g. some controllers revert to PIO for certain sizes).
*
* If possible, it will also adjust the size so that it can be
* handled in just a single request.
*
* Returns the improved size, which might be unmodified.
*/
unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz)
{
unsigned int orig_sz;
unsigned int blk_sz, byte_sz;
unsigned chunk_sz;
orig_sz = sz;
/*
* Do a first check with the controller, in case it
* wants to increase the size up to a point where it
* might need more than one block.
*/
sz = mmc_align_data_size(func->card, sz);
/*
* If we can still do this with just a byte transfer, then
* we're done.
*/
if (sz <= sdio_max_byte_size(func))
return sz;
if (func->card->cccr.multi_block) {
/*
* Check if the transfer is already block aligned
*/
if ((sz % func->cur_blksize) == 0)
return sz;
/*
* Realign it so that it can be done with one request,
* and recheck if the controller still likes it.
*/
blk_sz = ((sz + func->cur_blksize - 1) /
func->cur_blksize) * func->cur_blksize;
blk_sz = mmc_align_data_size(func->card, blk_sz);
/*
* This value is only good if it is still just
* one request.
*/
if ((blk_sz % func->cur_blksize) == 0)
return blk_sz;
/*
* We failed to do one request, but at least try to
* pad the remainder properly.
*/
byte_sz = mmc_align_data_size(func->card,
sz % func->cur_blksize);
if (byte_sz <= sdio_max_byte_size(func)) {
blk_sz = sz / func->cur_blksize;
return blk_sz * func->cur_blksize + byte_sz;
}
} else {
/*
* We need multiple requests, so first check that the
* controller can handle the chunk size;
*/
chunk_sz = mmc_align_data_size(func->card,
sdio_max_byte_size(func));
if (chunk_sz == sdio_max_byte_size(func)) {
/*
* Fix up the size of the remainder (if any)
*/
byte_sz = orig_sz % chunk_sz;
if (byte_sz) {
byte_sz = mmc_align_data_size(func->card,
byte_sz);
}
return (orig_sz / chunk_sz) * chunk_sz + byte_sz;
}
}
/*
* The controller is simply incapable of transferring the size
* we want in decent manner, so just return the original size.
*/
return orig_sz;
}
EXPORT_SYMBOL_GPL(sdio_align_size);
/* Split an arbitrarily sized data transfer into several
* IO_RW_EXTENDED commands. */
static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
unsigned addr, int incr_addr, u8 *buf, unsigned size)
{
unsigned remainder = size;
unsigned max_blocks;
int ret;
/* Do the bulk of the transfer using block mode (if supported). */
if (func->card->cccr.multi_block && (size > sdio_max_byte_size(func))) {
/* Blocks per command is limited by host count, host transfer
* size and the maximum for IO_RW_EXTENDED of 511 blocks. */
max_blocks = min(func->card->host->max_blk_count, 511u);
while (remainder >= func->cur_blksize) {
unsigned blocks;
blocks = remainder / func->cur_blksize;
if (blocks > max_blocks)
blocks = max_blocks;
size = blocks * func->cur_blksize;
ret = mmc_io_rw_extended(func->card, write,
func->num, addr, incr_addr, buf,
blocks, func->cur_blksize);
if (ret)
return ret;
remainder -= size;
buf += size;
if (incr_addr)
addr += size;
}
}
/* Write the remainder using byte mode. */
while (remainder > 0) {
size = min(remainder, sdio_max_byte_size(func));
/* Indicate byte mode by setting "blocks" = 0 */
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
incr_addr, buf, 0, size);
if (ret)
return ret;
remainder -= size;
buf += size;
if (incr_addr)
addr += size;
}
return 0;
}
/**
* sdio_readb - read a single byte from a SDIO function
* @func: SDIO function to access
* @addr: address to read
* @err_ret: optional status value from transfer
*
* Reads a single byte from the address space of a given SDIO
* function. If there is a problem reading the address, 0xff
* is returned and @err_ret will contain the error code.
*/
u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
{
int ret;
u8 val;
BUG_ON(!func);
if (err_ret)
*err_ret = 0;
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val);
if (ret) {
if (err_ret)
*err_ret = ret;
return 0xFF;
}
return val;
}
EXPORT_SYMBOL_GPL(sdio_readb);
/**
* sdio_readb_ext - read a single byte from a SDIO function
* @func: SDIO function to access
* @addr: address to read
* @err_ret: optional status value from transfer
* @in: value to add to argument
*
* Reads a single byte from the address space of a given SDIO
* function. If there is a problem reading the address, 0xff
* is returned and @err_ret will contain the error code.
*/
unsigned char sdio_readb_ext(struct sdio_func *func, unsigned int addr,
int *err_ret, unsigned in)
{
int ret;
unsigned char val;
BUG_ON(!func);
if (err_ret)
*err_ret = 0;
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, (u8)in, &val);
if (ret) {
if (err_ret)
*err_ret = ret;
return 0xFF;
}
return val;
}
EXPORT_SYMBOL_GPL(sdio_readb_ext);
/**
* sdio_writeb - write a single byte to a SDIO function
* @func: SDIO function to access
* @b: byte to write
* @addr: address to write to
* @err_ret: optional status value from transfer
*
* Writes a single byte to the address space of a given SDIO
* function. @err_ret will contain the status of the actual
* transfer.
*/
void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
{
int ret;
BUG_ON(!func);
ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL);
if (err_ret)
*err_ret = ret;
}
EXPORT_SYMBOL_GPL(sdio_writeb);
/**
* sdio_writeb_readb - write and read a byte from SDIO function
* @func: SDIO function to access
* @write_byte: byte to write
* @addr: address to write to
* @err_ret: optional status value from transfer
*
* Performs a RAW (Read after Write) operation as defined by SDIO spec -
* single byte is written to address space of a given SDIO function and
* response is read back from the same address, both using single request.
* If there is a problem with the operation, 0xff is returned and
* @err_ret will contain the error code.
*/
u8 sdio_writeb_readb(struct sdio_func *func, u8 write_byte,
unsigned int addr, int *err_ret)
{
int ret;
u8 val;
ret = mmc_io_rw_direct(func->card, 1, func->num, addr,
write_byte, &val);
if (err_ret)
*err_ret = ret;
if (ret)
val = 0xff;
return val;
}
EXPORT_SYMBOL_GPL(sdio_writeb_readb);
/**
* sdio_memcpy_fromio - read a chunk of memory from a SDIO function
* @func: SDIO function to access
* @dst: buffer to store the data
* @addr: address to begin reading from
* @count: number of bytes to read
*
* Reads from the address space of a given SDIO function. Return
* value indicates if the transfer succeeded or not.
*/
int sdio_memcpy_fromio(struct sdio_func *func, void *dst,
unsigned int addr, int count)
{
return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count);
}
EXPORT_SYMBOL_GPL(sdio_memcpy_fromio);
/**
* sdio_memcpy_toio - write a chunk of memory to a SDIO function
* @func: SDIO function to access
* @addr: address to start writing to
* @src: buffer that contains the data to write
* @count: number of bytes to write
*
* Writes to the address space of a given SDIO function. Return
* value indicates if the transfer succeeded or not.
*/
int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr,
void *src, int count)
{
return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count);
}
EXPORT_SYMBOL_GPL(sdio_memcpy_toio);
/**
* sdio_readsb - read from a FIFO on a SDIO function
* @func: SDIO function to access
* @dst: buffer to store the data
* @addr: address of (single byte) FIFO
* @count: number of bytes to read
*
* Reads from the specified FIFO of a given SDIO function. Return
* value indicates if the transfer succeeded or not.
*/
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
int count)
{
return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
}
EXPORT_SYMBOL_GPL(sdio_readsb);
/**
* sdio_writesb - write to a FIFO of a SDIO function
* @func: SDIO function to access
* @addr: address of (single byte) FIFO
* @src: buffer that contains the data to write
* @count: number of bytes to write
*
* Writes to the specified FIFO of a given SDIO function. Return
* value indicates if the transfer succeeded or not.
*/
int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src,
int count)
{
return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count);
}
EXPORT_SYMBOL_GPL(sdio_writesb);
/**
* sdio_readw - read a 16 bit integer from a SDIO function
* @func: SDIO function to access
* @addr: address to read
* @err_ret: optional status value from transfer
*
* Reads a 16 bit integer from the address space of a given SDIO
* function. If there is a problem reading the address, 0xffff
* is returned and @err_ret will contain the error code.
*/
u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret)
{
int ret;
if (err_ret)
*err_ret = 0;
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2);
if (ret) {
if (err_ret)
*err_ret = ret;
return 0xFFFF;
}
return le16_to_cpup((__le16 *)func->tmpbuf);
}
EXPORT_SYMBOL_GPL(sdio_readw);
/**
* sdio_writew - write a 16 bit integer to a SDIO function
* @func: SDIO function to access
* @b: integer to write
* @addr: address to write to
* @err_ret: optional status value from transfer
*
* Writes a 16 bit integer to the address space of a given SDIO
* function. @err_ret will contain the status of the actual
* transfer.
*/
void sdio_writew(struct sdio_func *func, u16 b, unsigned int addr, int *err_ret)
{
int ret;
*(__le16 *)func->tmpbuf = cpu_to_le16(b);
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
if (err_ret)
*err_ret = ret;
}
EXPORT_SYMBOL_GPL(sdio_writew);
/**
* sdio_readl - read a 32 bit integer from a SDIO function
* @func: SDIO function to access
* @addr: address to read
* @err_ret: optional status value from transfer
*
* Reads a 32 bit integer from the address space of a given SDIO
* function. If there is a problem reading the address,
* 0xffffffff is returned and @err_ret will contain the error
* code.
*/
u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret)
{
int ret;
if (err_ret)
*err_ret = 0;
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4);
if (ret) {
if (err_ret)
*err_ret = ret;
return 0xFFFFFFFF;
}
return le32_to_cpup((__le32 *)func->tmpbuf);
}
EXPORT_SYMBOL_GPL(sdio_readl);
/**
* sdio_writel - write a 32 bit integer to a SDIO function
* @func: SDIO function to access
* @b: integer to write
* @addr: address to write to
* @err_ret: optional status value from transfer
*
* Writes a 32 bit integer to the address space of a given SDIO
* function. @err_ret will contain the status of the actual
* transfer.
*/
void sdio_writel(struct sdio_func *func, u32 b, unsigned int addr, int *err_ret)
{
int ret;
*(__le32 *)func->tmpbuf = cpu_to_le32(b);
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
if (err_ret)
*err_ret = ret;
}
EXPORT_SYMBOL_GPL(sdio_writel);
/**
* sdio_f0_readb - read a single byte from SDIO function 0
* @func: an SDIO function of the card
* @addr: address to read
* @err_ret: optional status value from transfer
*
* Reads a single byte from the address space of SDIO function 0.
* If there is a problem reading the address, 0xff is returned
* and @err_ret will contain the error code.
*/
unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr,
int *err_ret)
{
int ret;
unsigned char val;
BUG_ON(!func);
if (err_ret)
*err_ret = 0;
ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val);
if (ret) {
if (err_ret)
*err_ret = ret;
return 0xFF;
}
return val;
}
EXPORT_SYMBOL_GPL(sdio_f0_readb);
/**
* sdio_f0_writeb - write a single byte to SDIO function 0
* @func: an SDIO function of the card
* @b: byte to write
* @addr: address to write to
* @err_ret: optional status value from transfer
*
* Writes a single byte to the address space of SDIO function 0.
* @err_ret will contain the status of the actual transfer.
*
* Only writes to the vendor specific CCCR registers (0xF0 -
* 0xFF) are permiited; @err_ret will be set to -EINVAL for *
* writes outside this range.
*/
void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
int *err_ret)
{
int ret;
BUG_ON(!func);
if ((addr < 0xF0 || addr > 0xFF) && (!mmc_card_lenient_fn0(func->card))) {
if (err_ret)
*err_ret = -EINVAL;
return;
}
ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL);
if (err_ret)
*err_ret = ret;
}
EXPORT_SYMBOL_GPL(sdio_f0_writeb);
/**
* sdio_get_host_pm_caps - get host power management capabilities
* @func: SDIO function attached to host
*
* Returns a capability bitmask corresponding to power management
* features supported by the host controller that the card function
* might rely upon during a system suspend. The host doesn't need
* to be claimed, nor the function active, for this information to be
* obtained.
*/
mmc_pm_flag_t sdio_get_host_pm_caps(struct sdio_func *func)
{
BUG_ON(!func);
BUG_ON(!func->card);
return func->card->host->pm_caps;
}
EXPORT_SYMBOL_GPL(sdio_get_host_pm_caps);
/**
* sdio_set_host_pm_flags - set wanted host power management capabilities
* @func: SDIO function attached to host
*
* Set a capability bitmask corresponding to wanted host controller
* power management features for the upcoming suspend state.
* This must be called, if needed, each time the suspend method of
* the function driver is called, and must contain only bits that
* were returned by sdio_get_host_pm_caps().
* The host doesn't need to be claimed, nor the function active,
* for this information to be set.
*/
int sdio_set_host_pm_flags(struct sdio_func *func, mmc_pm_flag_t flags)
{
struct mmc_host *host;
BUG_ON(!func);
BUG_ON(!func->card);
host = func->card->host;
if (flags & ~host->pm_caps)
return -EINVAL;
/* function suspend methods are serialized, hence no lock needed */
host->pm_flags |= flags;
return 0;
}
EXPORT_SYMBOL_GPL(sdio_set_host_pm_flags);

345
drivers/mmc/core/sdio_irq.c Normal file
View file

@ -0,0 +1,345 @@
/*
* linux/drivers/mmc/core/sdio_irq.c
*
* Author: Nicolas Pitre
* Created: June 18, 2007
* Copyright: MontaVista Software Inc.
*
* Copyright 2008 Pierre Ossman
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/export.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/mmc/core.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_ops.h"
static int process_sdio_pending_irqs(struct mmc_host *host)
{
struct mmc_card *card = host->card;
int i, ret, count;
unsigned char pending;
struct sdio_func *func;
/*
* Optimization, if there is only 1 function interrupt registered
* and we know an IRQ was signaled then call irq handler directly.
* Otherwise do the full probe.
*/
func = card->sdio_single_irq;
if (func && host->sdio_irq_pending) {
func->irq_handler(func);
return 1;
}
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
if (ret) {
pr_debug("%s: error %d reading SDIO_CCCR_INTx\n",
mmc_card_id(card), ret);
return ret;
}
if (pending && mmc_card_broken_irq_polling(card) &&
!(host->caps & MMC_CAP_SDIO_IRQ)) {
unsigned char dummy;
/* A fake interrupt could be created when we poll SDIO_CCCR_INTx
* register with a Marvell SD8797 card. A dummy CMD52 read to
* function 0 register 0xff can avoid this.
*/
mmc_io_rw_direct(card, 0, 0, 0xff, 0, &dummy);
}
count = 0;
for (i = 1; i <= 7; i++) {
if (pending & (1 << i)) {
func = card->sdio_func[i - 1];
if (!func) {
pr_warn("%s: pending IRQ for non-existent function\n",
mmc_card_id(card));
ret = -EINVAL;
} else if (func->irq_handler) {
func->irq_handler(func);
count++;
} else {
pr_warn("%s: pending IRQ with no handler\n",
sdio_func_id(func));
ret = -EINVAL;
}
}
}
if (count)
return count;
return ret;
}
void sdio_run_irqs(struct mmc_host *host)
{
mmc_claim_host(host);
host->sdio_irq_pending = true;
process_sdio_pending_irqs(host);
mmc_release_host(host);
}
EXPORT_SYMBOL_GPL(sdio_run_irqs);
static int sdio_irq_thread(void *_host)
{
struct mmc_host *host = _host;
struct sched_param param = { .sched_priority = 1 };
unsigned long period, idle_period;
int ret;
sched_setscheduler(current, SCHED_FIFO, &param);
/*
* We want to allow for SDIO cards to work even on non SDIO
* aware hosts. One thing that non SDIO host cannot do is
* asynchronous notification of pending SDIO card interrupts
* hence we poll for them in that case.
*/
idle_period = msecs_to_jiffies(10);
period = (host->caps & MMC_CAP_SDIO_IRQ) ?
MAX_SCHEDULE_TIMEOUT : idle_period;
pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
mmc_hostname(host), period);
do {
/*
* We claim the host here on drivers behalf for a couple
* reasons:
*
* 1) it is already needed to retrieve the CCCR_INTx;
* 2) we want the driver(s) to clear the IRQ condition ASAP;
* 3) we need to control the abort condition locally.
*
* Just like traditional hard IRQ handlers, we expect SDIO
* IRQ handlers to be quick and to the point, so that the
* holding of the host lock does not cover too much work
* that doesn't require that lock to be held.
*/
ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort);
if (ret)
break;
ret = process_sdio_pending_irqs(host);
host->sdio_irq_pending = false;
mmc_release_host(host);
/*
* Give other threads a chance to run in the presence of
* errors.
*/
if (ret < 0) {
set_current_state(TASK_INTERRUPTIBLE);
if (!kthread_should_stop())
schedule_timeout(HZ);
set_current_state(TASK_RUNNING);
}
/*
* Adaptive polling frequency based on the assumption
* that an interrupt will be closely followed by more.
* This has a substantial benefit for network devices.
*/
if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
if (ret > 0)
period /= 2;
else {
period++;
if (period > idle_period)
period = idle_period;
}
}
set_current_state(TASK_INTERRUPTIBLE);
if (host->caps & MMC_CAP_SDIO_IRQ) {
mmc_host_clk_hold(host);
host->ops->enable_sdio_irq(host, 1);
mmc_host_clk_release(host);
}
if (!kthread_should_stop())
schedule_timeout(period);
set_current_state(TASK_RUNNING);
} while (!kthread_should_stop());
if (host->caps & MMC_CAP_SDIO_IRQ) {
mmc_host_clk_hold(host);
host->ops->enable_sdio_irq(host, 0);
mmc_host_clk_release(host);
}
pr_debug("%s: IRQ thread exiting with code %d\n",
mmc_hostname(host), ret);
return ret;
}
static int sdio_card_irq_get(struct mmc_card *card)
{
struct mmc_host *host = card->host;
WARN_ON(!host->claimed);
if (!host->sdio_irqs++) {
if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
atomic_set(&host->sdio_irq_thread_abort, 0);
host->sdio_irq_thread =
kthread_run(sdio_irq_thread, host,
"ksdioirqd/%s", mmc_hostname(host));
if (IS_ERR(host->sdio_irq_thread)) {
int err = PTR_ERR(host->sdio_irq_thread);
host->sdio_irqs--;
return err;
}
} else if (host->caps & MMC_CAP_SDIO_IRQ) {
mmc_host_clk_hold(host);
host->ops->enable_sdio_irq(host, 1);
mmc_host_clk_release(host);
}
}
return 0;
}
static int sdio_card_irq_put(struct mmc_card *card)
{
struct mmc_host *host = card->host;
WARN_ON(!host->claimed);
BUG_ON(host->sdio_irqs < 1);
if (!--host->sdio_irqs) {
if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
atomic_set(&host->sdio_irq_thread_abort, 1);
kthread_stop(host->sdio_irq_thread);
} else if (host->caps & MMC_CAP_SDIO_IRQ) {
mmc_host_clk_hold(host);
host->ops->enable_sdio_irq(host, 0);
mmc_host_clk_release(host);
}
}
return 0;
}
/* If there is only 1 function registered set sdio_single_irq */
static void sdio_single_irq_set(struct mmc_card *card)
{
struct sdio_func *func;
int i;
card->sdio_single_irq = NULL;
if ((card->host->caps & MMC_CAP_SDIO_IRQ) &&
card->host->sdio_irqs == 1)
for (i = 0; i < card->sdio_funcs; i++) {
func = card->sdio_func[i];
if (func && func->irq_handler) {
card->sdio_single_irq = func;
break;
}
}
}
/**
* sdio_claim_irq - claim the IRQ for a SDIO function
* @func: SDIO function
* @handler: IRQ handler callback
*
* Claim and activate the IRQ for the given SDIO function. The provided
* handler will be called when that IRQ is asserted. The host is always
* claimed already when the handler is called so the handler must not
* call sdio_claim_host() nor sdio_release_host().
*/
int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
{
int ret;
unsigned char reg;
BUG_ON(!func);
BUG_ON(!func->card);
pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
if (func->irq_handler) {
pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
return -EBUSY;
}
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
if (ret)
return ret;
reg |= 1 << func->num;
reg |= 1; /* Master interrupt enable */
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
if (ret)
return ret;
func->irq_handler = handler;
ret = sdio_card_irq_get(func->card);
if (ret)
func->irq_handler = NULL;
sdio_single_irq_set(func->card);
return ret;
}
EXPORT_SYMBOL_GPL(sdio_claim_irq);
/**
* sdio_release_irq - release the IRQ for a SDIO function
* @func: SDIO function
*
* Disable and release the IRQ for the given SDIO function.
*/
int sdio_release_irq(struct sdio_func *func)
{
int ret;
unsigned char reg;
BUG_ON(!func);
BUG_ON(!func->card);
pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
if (func->irq_handler) {
func->irq_handler = NULL;
sdio_card_irq_put(func->card);
sdio_single_irq_set(func->card);
}
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
if (ret)
return ret;
reg &= ~(1 << func->num);
/* Disable master interrupt with the last function interrupt */
if (!(reg & 0xFE))
reg = 0;
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
if (ret)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(sdio_release_irq);

223
drivers/mmc/core/sdio_ops.c Normal file
View file

@ -0,0 +1,223 @@
/*
* linux/drivers/mmc/sdio_ops.c
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/scatterlist.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include "core.h"
#include "sdio_ops.h"
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {0};
int i, err = 0;
BUG_ON(!host);
cmd.opcode = SD_IO_SEND_OP_COND;
cmd.arg = ocr;
cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR;
for (i = 100; i; i--) {
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
break;
/* if we're just probing, do a single pass */
if (ocr == 0)
break;
/* otherwise wait until reset completes */
if (mmc_host_is_spi(host)) {
/*
* Both R1_SPI_IDLE and MMC_CARD_BUSY indicate
* an initialized card under SPI, but some cards
* (Marvell's) only behave when looking at this
* one.
*/
if (cmd.resp[1] & MMC_CARD_BUSY)
break;
} else {
if (cmd.resp[0] & MMC_CARD_BUSY)
break;
}
err = -ETIMEDOUT;
mmc_delay(10);
}
if (rocr)
*rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0];
return err;
}
static int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn,
unsigned addr, u8 in, u8 *out)
{
struct mmc_command cmd = {0};
int err;
BUG_ON(!host);
BUG_ON(fn > 7);
/* sanity check */
if (addr & ~0x1FFFF)
return -EINVAL;
cmd.opcode = SD_IO_RW_DIRECT;
cmd.arg = write ? 0x80000000 : 0x00000000;
cmd.arg |= fn << 28;
cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
cmd.arg |= addr << 9;
cmd.arg |= in;
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
return err;
if (mmc_host_is_spi(host)) {
/* host driver already reported errors */
} else {
if (cmd.resp[0] & R5_ERROR)
return -EIO;
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
return -EINVAL;
if (cmd.resp[0] & R5_OUT_OF_RANGE)
return -ERANGE;
}
if (out) {
if (mmc_host_is_spi(host))
*out = (cmd.resp[0] >> 8) & 0xFF;
else
*out = cmd.resp[0] & 0xFF;
}
return 0;
}
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
unsigned addr, u8 in, u8 *out)
{
BUG_ON(!card);
return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out);
}
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg, *sg_ptr;
struct sg_table sgtable;
unsigned int nents, left_size, i;
unsigned int seg_size = card->host->max_seg_size;
BUG_ON(!card);
BUG_ON(fn > 7);
WARN_ON(blksz == 0);
/* sanity check */
if (addr & ~0x1FFFF)
return -EINVAL;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_IO_RW_EXTENDED;
cmd.arg = write ? 0x80000000 : 0x00000000;
cmd.arg |= fn << 28;
cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
cmd.arg |= addr << 9;
if (blocks == 0)
cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
else
cmd.arg |= 0x08000000 | blocks; /* block mode */
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
data.blksz = blksz;
/* Code in host drivers/fwk assumes that "blocks" always is >=1 */
data.blocks = blocks ? blocks : 1;
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
left_size = data.blksz * data.blocks;
nents = (left_size - 1) / seg_size + 1;
if (nents > 1) {
if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
return -ENOMEM;
data.sg = sgtable.sgl;
data.sg_len = nents;
for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)),
min(seg_size, left_size),
offset_in_page(buf + (i * seg_size)));
left_size = left_size - seg_size;
}
} else {
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, buf, left_size);
}
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (nents > 1)
sg_free_table(&sgtable);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
if (mmc_host_is_spi(card->host)) {
/* host driver already reported errors */
} else {
if (cmd.resp[0] & R5_ERROR)
return -EIO;
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
return -EINVAL;
if (cmd.resp[0] & R5_OUT_OF_RANGE)
return -ERANGE;
}
return 0;
}
int sdio_reset(struct mmc_host *host)
{
int ret;
u8 abort;
/* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */
ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort);
if (ret)
abort = 0x08;
else
abort |= 0x08;
ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL);
return ret;
}

View file

@ -0,0 +1,23 @@
/*
* linux/drivers/mmc/sdio_ops.c
*
* Copyright 2006-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 as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#ifndef _MMC_SDIO_OPS_H
#define _MMC_SDIO_OPS_H
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
unsigned addr, u8 in, u8* out);
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
int sdio_reset(struct mmc_host *host);
#endif

View file

@ -0,0 +1,407 @@
/*
* Generic GPIO card-detect helper
*
* Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/mmc/host.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/module.h>
#include <linux/slab.h>
struct mmc_gpio {
struct gpio_desc *ro_gpio;
struct gpio_desc *cd_gpio;
bool override_ro_active_level;
bool override_cd_active_level;
char *ro_label;
char cd_label[0];
};
static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
/* Schedule a card detection after a debounce timeout */
struct mmc_host *host = dev_id;
host->trigger_card_event = true;
mmc_detect_change(host, msecs_to_jiffies(200));
return IRQ_HANDLED;
}
static int mmc_gpio_alloc(struct mmc_host *host)
{
size_t len = strlen(dev_name(host->parent)) + 4;
struct mmc_gpio *ctx;
mutex_lock(&host->slot.lock);
ctx = host->slot.handler_priv;
if (!ctx) {
/*
* devm_kzalloc() can be called after device_initialize(), even
* before device_add(), i.e., between mmc_alloc_host() and
* mmc_add_host()
*/
ctx = devm_kzalloc(&host->class_dev, sizeof(*ctx) + 2 * len,
GFP_KERNEL);
if (ctx) {
ctx->ro_label = ctx->cd_label + len;
snprintf(ctx->cd_label, len, "%s cd", dev_name(host->parent));
snprintf(ctx->ro_label, len, "%s ro", dev_name(host->parent));
host->slot.handler_priv = ctx;
}
}
mutex_unlock(&host->slot.lock);
return ctx ? 0 : -ENOMEM;
}
int mmc_gpio_get_ro(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
if (!ctx || !ctx->ro_gpio)
return -ENOSYS;
if (ctx->override_ro_active_level)
return !gpiod_get_raw_value_cansleep(ctx->ro_gpio) ^
!!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH);
return gpiod_get_value_cansleep(ctx->ro_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_ro);
int mmc_gpio_get_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
if (!ctx || !ctx->cd_gpio)
return -ENOSYS;
if (ctx->override_cd_active_level)
return !gpiod_get_raw_value_cansleep(ctx->cd_gpio) ^
!!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
return gpiod_get_value_cansleep(ctx->cd_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_cd);
/**
* mmc_gpio_request_ro - request a gpio for write-protection
* @host: mmc host
* @gpio: gpio number requested
*
* As devm_* managed functions are used in mmc_gpio_request_ro(), client
* drivers do not need to explicitly call mmc_gpio_free_ro() for freeing up,
* if the requesting and freeing are only needed at probing and unbinding time
* for once. However, if client drivers do something special like runtime
* switching for write-protection, they are responsible for calling
* mmc_gpio_request_ro() and mmc_gpio_free_ro() as a pair on their own.
*
* Returns zero on success, else an error.
*/
int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio)
{
struct mmc_gpio *ctx;
int ret;
if (!gpio_is_valid(gpio))
return -EINVAL;
ret = mmc_gpio_alloc(host);
if (ret < 0)
return ret;
ctx = host->slot.handler_priv;
ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN,
ctx->ro_label);
if (ret < 0)
return ret;
ctx->override_ro_active_level = true;
ctx->ro_gpio = gpio_to_desc(gpio);
return 0;
}
EXPORT_SYMBOL(mmc_gpio_request_ro);
void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int ret, irq;
if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
return;
irq = gpiod_to_irq(ctx->cd_gpio);
/*
* Even if gpiod_to_irq() returns a valid IRQ number, the platform might
* still prefer to poll, e.g., because that IRQ number is already used
* by another unit and cannot be shared.
*/
if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL)
irq = -EINVAL;
if (irq >= 0) {
ret = devm_request_threaded_irq(&host->class_dev, irq,
NULL, mmc_gpio_cd_irqt,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
ctx->cd_label, host);
if (ret < 0)
irq = ret;
}
host->slot.cd_irq = irq;
if (irq < 0)
host->caps |= MMC_CAP_NEEDS_POLL;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd_irq);
/**
* mmc_gpio_request_cd - request a gpio for card-detection
* @host: mmc host
* @gpio: gpio number requested
* @debounce: debounce time in microseconds
*
* As devm_* managed functions are used in mmc_gpio_request_cd(), client
* drivers do not need to explicitly call mmc_gpio_free_cd() for freeing up,
* if the requesting and freeing are only needed at probing and unbinding time
* for once. However, if client drivers do something special like runtime
* switching for card-detection, they are responsible for calling
* mmc_gpio_request_cd() and mmc_gpio_free_cd() as a pair on their own.
*
* If GPIO debouncing is desired, set the debounce parameter to a non-zero
* value. The caller is responsible for ensuring that the GPIO driver associated
* with the GPIO supports debouncing, otherwise an error will be returned.
*
* Returns zero on success, else an error.
*/
int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio,
unsigned int debounce)
{
struct mmc_gpio *ctx;
int ret;
ret = mmc_gpio_alloc(host);
if (ret < 0)
return ret;
ctx = host->slot.handler_priv;
ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN,
ctx->cd_label);
if (ret < 0)
/*
* don't bother freeing memory. It might still get used by other
* slot functions, in any case it will be freed, when the device
* is destroyed.
*/
return ret;
if (debounce) {
ret = gpio_set_debounce(gpio, debounce);
if (ret < 0)
return ret;
}
ctx->override_cd_active_level = true;
ctx->cd_gpio = gpio_to_desc(gpio);
return 0;
}
EXPORT_SYMBOL(mmc_gpio_request_cd);
/**
* mmc_gpio_free_ro - free the write-protection gpio
* @host: mmc host
*
* It's provided only for cases that client drivers need to manually free
* up the write-protection gpio requested by mmc_gpio_request_ro().
*/
void mmc_gpio_free_ro(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int gpio;
if (!ctx || !ctx->ro_gpio)
return;
gpio = desc_to_gpio(ctx->ro_gpio);
ctx->ro_gpio = NULL;
devm_gpio_free(&host->class_dev, gpio);
}
EXPORT_SYMBOL(mmc_gpio_free_ro);
/**
* mmc_gpio_free_cd - free the card-detection gpio
* @host: mmc host
*
* It's provided only for cases that client drivers need to manually free
* up the card-detection gpio requested by mmc_gpio_request_cd().
*/
void mmc_gpio_free_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
int gpio;
if (!ctx || !ctx->cd_gpio)
return;
if (host->slot.cd_irq >= 0) {
devm_free_irq(&host->class_dev, host->slot.cd_irq, host);
host->slot.cd_irq = -EINVAL;
}
gpio = desc_to_gpio(ctx->cd_gpio);
ctx->cd_gpio = NULL;
devm_gpio_free(&host->class_dev, gpio);
}
EXPORT_SYMBOL(mmc_gpio_free_cd);
/**
* mmc_gpiod_request_cd - request a gpio descriptor for card-detection
* @host: mmc host
* @con_id: function within the GPIO consumer
* @idx: index of the GPIO to obtain in the consumer
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
* @debounce: debounce time in microseconds
* @gpio_invert: will return whether the GPIO line is inverted or not, set
* to NULL to ignore
*
* Use this function in place of mmc_gpio_request_cd() to use the GPIO
* descriptor API. Note that it is paired with mmc_gpiod_free_cd() not
* mmc_gpio_free_cd(). Note also that it must be called prior to mmc_add_host()
* otherwise the caller must also call mmc_gpiod_request_cd_irq().
*
* Returns zero on success, else an error.
*/
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
unsigned int idx, bool override_active_level,
unsigned int debounce, bool *gpio_invert)
{
struct mmc_gpio *ctx;
struct gpio_desc *desc;
int ret;
ret = mmc_gpio_alloc(host);
if (ret < 0)
return ret;
ctx = host->slot.handler_priv;
if (!con_id)
con_id = ctx->cd_label;
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
if (IS_ERR(desc))
return PTR_ERR(desc);
if (debounce) {
ret = gpiod_set_debounce(desc, debounce);
if (ret < 0)
return ret;
}
if (gpio_invert)
*gpio_invert = !gpiod_is_active_low(desc);
ctx->override_cd_active_level = override_active_level;
ctx->cd_gpio = desc;
return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);
/**
* mmc_gpiod_request_ro - request a gpio descriptor for write protection
* @host: mmc host
* @con_id: function within the GPIO consumer
* @idx: index of the GPIO to obtain in the consumer
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
* @debounce: debounce time in microseconds
* @gpio_invert: will return whether the GPIO line is inverted or not,
* set to NULL to ignore
*
* Use this function in place of mmc_gpio_request_ro() to use the GPIO
* descriptor API. Note that it is paired with mmc_gpiod_free_ro() not
* mmc_gpio_free_ro().
*
* Returns zero on success, else an error.
*/
int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
unsigned int idx, bool override_active_level,
unsigned int debounce, bool *gpio_invert)
{
struct mmc_gpio *ctx;
struct gpio_desc *desc;
int ret;
ret = mmc_gpio_alloc(host);
if (ret < 0)
return ret;
ctx = host->slot.handler_priv;
if (!con_id)
con_id = ctx->ro_label;
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
if (IS_ERR(desc))
return PTR_ERR(desc);
if (debounce) {
ret = gpiod_set_debounce(desc, debounce);
if (ret < 0)
return ret;
}
if (gpio_invert)
*gpio_invert = !gpiod_is_active_low(desc);
ctx->override_ro_active_level = override_active_level;
ctx->ro_gpio = desc;
return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_ro);
/**
* mmc_gpiod_free_cd - free the card-detection gpio descriptor
* @host: mmc host
*
* It's provided only for cases that client drivers need to manually free
* up the card-detection gpio requested by mmc_gpiod_request_cd().
*/
void mmc_gpiod_free_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
if (!ctx || !ctx->cd_gpio)
return;
if (host->slot.cd_irq >= 0) {
devm_free_irq(&host->class_dev, host->slot.cd_irq, host);
host->slot.cd_irq = -EINVAL;
}
devm_gpiod_put(host->parent, ctx->cd_gpio);
ctx->cd_gpio = NULL;
}
EXPORT_SYMBOL(mmc_gpiod_free_cd);

795
drivers/mmc/host/Kconfig Normal file
View file

@ -0,0 +1,795 @@
#
# MMC/SD host controller drivers
#
comment "MMC/SD/SDIO Host Controller Drivers"
config MMC_ARMMMCI
tristate "ARM AMBA Multimedia Card Interface support"
depends on ARM_AMBA
help
This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card
Interface (PL180 and PL181) support. If you have an ARM(R)
platform with a Multimedia Card slot, say Y or M here.
If unsure, say N.
config MMC_QCOM_DML
tristate "Qualcomm Data Mover for SD Card Controller"
depends on MMC_ARMMMCI && QCOM_BAM_DMA
default y
help
This selects the Qualcomm Data Mover lite/local on SD Card controller.
This option will enable the dma to work correctly, if you are using
Qcom SOCs and MMC, you would probably need this option to get DMA working.
if unsure, say N.
config MMC_PXA
tristate "Intel PXA25x/26x/27x Multimedia Card Interface support"
depends on ARCH_PXA
help
This selects the Intel(R) PXA(R) Multimedia card Interface.
If you have a PXA(R) platform with a Multimedia Card slot,
say Y or M here.
If unsure, say N.
config MMC_SDHCI
tristate "Secure Digital Host Controller Interface support"
depends on HAS_DMA
help
This selects the generic Secure Digital Host Controller Interface.
It is used by manufacturers such as Texas Instruments(R), Ricoh(R)
and Toshiba(R). Most controllers found in laptops are of this type.
If you have a controller with this interface, say Y or M here. You
also need to enable an appropriate bus interface.
If unsure, say N.
config MMC_SDHCI_IO_ACCESSORS
bool
depends on MMC_SDHCI
help
This is silent Kconfig symbol that is selected by the drivers that
need to overwrite SDHCI IO memory accessors.
config MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
bool
select MMC_SDHCI_IO_ACCESSORS
help
This option is selected by drivers running on big endian hosts
and performing I/O to a SDHCI controller through a bus that
implements a hardware byte swapper using a 32-bit datum.
This endian mapping mode is called "data invariance" and
has the effect of scrambling the addresses and formats of data
accessed in sizes other than the datum size.
This is the case for the Freescale eSDHC and Nintendo Wii SDHCI.
config MMC_SDHCI_PCI
tristate "SDHCI support on PCI bus"
depends on MMC_SDHCI && PCI
help
This selects the PCI Secure Digital Host Controller Interface.
Most controllers found today are PCI devices.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_RICOH_MMC
bool "Ricoh MMC Controller Disabler"
depends on MMC_SDHCI_PCI
help
This adds a pci quirk to disable Ricoh MMC Controller. This
proprietary controller is unnecessary because the SDHCI driver
supports MMC cards on the SD controller, but if it is not
disabled, it will steal the MMC cards away - rendering them
useless. It is safe to select this even if you don't
have a Ricoh based card reader.
If unsure, say Y.
config MMC_SDHCI_ACPI
tristate "SDHCI support for ACPI enumerated SDHCI controllers"
depends on MMC_SDHCI && ACPI
help
This selects support for ACPI enumerated SDHCI controllers,
identified by ACPI Compatibility ID PNP0D40 or specific
ACPI Hardware IDs.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_PLTFM
tristate "SDHCI platform and OF driver helper"
depends on MMC_SDHCI
help
This selects the common helper functions support for Secure Digital
Host Controller Interface based platform and OF drivers.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_OF_ARASAN
tristate "SDHCI OF support for the Arasan SDHCI controllers"
depends on MMC_SDHCI_PLTFM
depends on OF
help
This selects the Arasan Secure Digital Host Controller Interface
(SDHCI). This hardware is found e.g. in Xilinx' Zynq SoC.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_OF_ESDHC
tristate "SDHCI OF support for the Freescale eSDHC controller"
depends on MMC_SDHCI_PLTFM
depends on PPC_OF
select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
help
This selects the Freescale eSDHC controller support.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_OF_HLWD
tristate "SDHCI OF support for the Nintendo Wii SDHCI controllers"
depends on MMC_SDHCI_PLTFM
depends on PPC_OF
select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
help
This selects the Secure Digital Host Controller Interface (SDHCI)
found in the "Hollywood" chipset of the Nintendo Wii video game
console.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_CNS3XXX
tristate "SDHCI support on the Cavium Networks CNS3xxx SoC"
depends on ARCH_CNS3XXX
depends on MMC_SDHCI_PLTFM
help
This selects the SDHCI support for CNS3xxx System-on-Chip devices.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_ESDHC_IMX
tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller"
depends on ARCH_MXC
depends on MMC_SDHCI_PLTFM
select MMC_SDHCI_IO_ACCESSORS
help
This selects the Freescale eSDHC/uSDHC controller support
found on i.MX25, i.MX35 i.MX5x and i.MX6x.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_DOVE
tristate "SDHCI support on Marvell's Dove SoC"
depends on ARCH_DOVE || MACH_DOVE
depends on MMC_SDHCI_PLTFM
select MMC_SDHCI_IO_ACCESSORS
help
This selects the Secure Digital Host Controller Interface in
Marvell's Dove SoC.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_TEGRA
tristate "SDHCI platform support for the Tegra SD/MMC Controller"
depends on ARCH_TEGRA
depends on MMC_SDHCI_PLTFM
select MMC_SDHCI_IO_ACCESSORS
help
This selects the Tegra SD/MMC controller. If you have a Tegra
platform with SD or MMC devices, say Y or M here.
If unsure, say N.
config MMC_SDHCI_S3C
tristate "SDHCI support on Samsung S3C SoC"
depends on MMC_SDHCI && PLAT_SAMSUNG
help
This selects the Secure Digital Host Controller Interface (SDHCI)
often referrered to as the HSMMC block in some of the Samsung S3C
range of SoC.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_SIRF
tristate "SDHCI support on CSR SiRFprimaII and SiRFmarco SoCs"
depends on ARCH_SIRF
depends on MMC_SDHCI_PLTFM
help
This selects the SDHCI support for SiRF System-on-Chip devices.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_PXAV3
tristate "Marvell MMP2 SD Host Controller support (PXAV3)"
depends on CLKDEV_LOOKUP
depends on MMC_SDHCI_PLTFM
default CPU_MMP2
help
This selects the Marvell(R) PXAV3 SD Host Controller.
If you have a MMP2 platform with SD Host Controller
and a card slot, say Y or M here.
If unsure, say N.
config MMC_SDHCI_PXAV2
tristate "Marvell PXA9XX SD Host Controller support (PXAV2)"
depends on CLKDEV_LOOKUP
depends on MMC_SDHCI_PLTFM
default CPU_PXA910
help
This selects the Marvell(R) PXAV2 SD Host Controller.
If you have a PXA9XX platform with SD Host Controller
and a card slot, say Y or M here.
If unsure, say N.
config MMC_SDHCI_SPEAR
tristate "SDHCI support on ST SPEAr platform"
depends on MMC_SDHCI && PLAT_SPEAR
help
This selects the Secure Digital Host Controller Interface (SDHCI)
often referrered to as the HSMMC block in some of the ST SPEAR range
of SoC
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_SDHCI_S3C_DMA
bool "DMA support on S3C SDHCI"
depends on MMC_SDHCI_S3C
help
Enable DMA support on the Samsung S3C SDHCI glue. The DMA
has proved to be problematic if the controller encounters
certain errors, and thus should be treated with care.
YMMV.
config MMC_SDHCI_BCM_KONA
tristate "SDHCI support on Broadcom KONA platform"
depends on ARCH_BCM_MOBILE
depends on MMC_SDHCI_PLTFM
help
This selects the Broadcom Kona Secure Digital Host Controller
Interface(SDHCI) support.
This is used in Broadcom mobile SoCs.
If you have a controller with this interface, say Y or M here.
config MMC_SDHCI_BCM2835
tristate "SDHCI platform support for the BCM2835 SD/MMC Controller"
depends on ARCH_BCM2835
depends on MMC_SDHCI_PLTFM
select MMC_SDHCI_IO_ACCESSORS
help
This selects the BCM2835 SD/MMC controller. If you have a BCM2835
platform with SD or MMC devices, say Y or M here.
If unsure, say N.
config MMC_MOXART
tristate "MOXART SD/MMC Host Controller support"
depends on ARCH_MOXART && MMC
help
This selects support for the MOXART SD/MMC Host Controller.
MOXA provides one multi-functional card reader which can
be found on some embedded hardware such as UC-7112-LX.
If you have a controller with this interface, say Y here.
config MMC_SDHCI_ST
tristate "SDHCI support on STMicroelectronics SoC"
depends on ARCH_STI
depends on MMC_SDHCI_PLTFM
select MMC_SDHCI_IO_ACCESSORS
help
This selects the Secure Digital Host Controller Interface in
STMicroelectronics SoCs.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_OMAP
tristate "TI OMAP Multimedia Card Interface support"
depends on ARCH_OMAP
depends on TPS65010 || !MACH_OMAP_H2
help
This selects the TI OMAP Multimedia card Interface.
If you have an OMAP board with a Multimedia Card slot,
say Y or M here.
If unsure, say N.
config MMC_OMAP_HS
tristate "TI OMAP High Speed Multimedia Card Interface support"
depends on HAS_DMA
depends on ARCH_OMAP2PLUS || COMPILE_TEST
help
This selects the TI OMAP High Speed Multimedia card Interface.
If you have an omap2plus board with a Multimedia Card slot,
say Y or M here.
If unsure, say N.
config MMC_WBSD
tristate "Winbond W83L51xD SD/MMC Card Interface support"
depends on ISA_DMA_API
help
This selects the Winbond(R) W83L51xD Secure digital and
Multimedia card Interface.
If you have a machine with a integrated W83L518D or W83L519D
SD/MMC card reader, say Y or M here.
If unsure, say N.
config MMC_AU1X
tristate "Alchemy AU1XX0 MMC Card Interface support"
depends on MIPS_ALCHEMY
help
This selects the AMD Alchemy(R) Multimedia card interface.
If you have a Alchemy platform with a MMC slot, say Y or M here.
If unsure, say N.
config MMC_ATMELMCI
tristate "Atmel SD/MMC Driver (Multimedia Card Interface)"
depends on AVR32 || ARCH_AT91
help
This selects the Atmel Multimedia Card Interface driver. If
you have an AT32 (AVR32) or AT91 platform with a Multimedia
Card slot, say Y or M here.
If unsure, say N.
config MMC_SDHCI_MSM
tristate "Qualcomm SDHCI Controller Support"
depends on ARCH_QCOM || (ARM && COMPILE_TEST)
depends on MMC_SDHCI_PLTFM
help
This selects the Secure Digital Host Controller Interface (SDHCI)
support present in Qualcomm SOCs. The controller supports
SD/MMC/SDIO devices.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_MSM
tristate "Qualcomm SDCC Controller Support"
depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50)
help
This provides support for the SD/MMC cell found in the
MSM and QSD SOCs from Qualcomm. The controller also has
support for SDIO devices.
config MMC_MXC
tristate "Freescale i.MX21/27/31 or MPC512x Multimedia Card support"
depends on ARCH_MXC || PPC_MPC512x
help
This selects the Freescale i.MX21, i.MX27, i.MX31 or MPC512x
Multimedia Card Interface. If you have an i.MX or MPC512x platform
with a Multimedia Card slot, say Y or M here.
If unsure, say N.
config MMC_MXS
tristate "Freescale MXS Multimedia Card Interface support"
depends on ARCH_MXS && MXS_DMA
help
This selects the Freescale SSP MMC controller found on MXS based
platforms like mx23/28.
If unsure, say N.
config MMC_TIFM_SD
tristate "TI Flash Media MMC/SD Interface support"
depends on PCI
select TIFM_CORE
help
Say Y here if you want to be able to access MMC/SD cards with
the Texas Instruments(R) Flash Media card reader, found in many
laptops.
This option 'selects' (turns on, enables) 'TIFM_CORE', but you
probably also need appropriate card reader host adapter, such as
'Misc devices: TI Flash Media PCI74xx/PCI76xx host adapter support
(TIFM_7XX1)'.
To compile this driver as a module, choose M here: the
module will be called tifm_sd.
config MMC_MVSDIO
tristate "Marvell MMC/SD/SDIO host driver"
depends on PLAT_ORION
---help---
This selects the Marvell SDIO host driver.
SDIO may currently be found on the Kirkwood 88F6281 and 88F6192
SoC controllers.
To compile this driver as a module, choose M here: the
module will be called mvsdio.
config MMC_DAVINCI
tristate "TI DAVINCI Multimedia Card Interface support"
depends on ARCH_DAVINCI
help
This selects the TI DAVINCI Multimedia card Interface.
If you have an DAVINCI board with a Multimedia Card slot,
say Y or M here. If unsure, say N.
config MMC_GOLDFISH
tristate "goldfish qemu Multimedia Card Interface support"
depends on GOLDFISH
help
This selects the Goldfish Multimedia card Interface emulation
found on the Goldfish Android virtual device emulation.
config MMC_SPI
tristate "MMC/SD/SDIO over SPI"
depends on SPI_MASTER && !HIGHMEM && HAS_DMA
select CRC7
select CRC_ITU_T
help
Some systems access MMC/SD/SDIO cards using a SPI controller
instead of using a "native" MMC/SD/SDIO controller. This has a
disadvantage of being relatively high overhead, but a compensating
advantage of working on many systems without dedicated MMC/SD/SDIO
controllers.
If unsure, or if your system has no SPI master driver, say N.
config MMC_S3C
tristate "Samsung S3C SD/MMC Card Interface support"
depends on ARCH_S3C24XX
depends on S3C24XX_DMAC
help
This selects a driver for the MCI interface found in
Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs.
If you have a board based on one of those and a MMC/SD
slot, say Y or M here.
If unsure, say N.
config MMC_S3C_HW_SDIO_IRQ
bool "Hardware support for SDIO IRQ"
depends on MMC_S3C
help
Enable the hardware support for SDIO interrupts instead of using
the generic polling code.
choice
prompt "Samsung S3C SD/MMC transfer code"
depends on MMC_S3C
config MMC_S3C_PIO
bool "Use PIO transfers only"
help
Use PIO to transfer data between memory and the hardware.
PIO is slower than DMA as it requires CPU instructions to
move the data. This has been the traditional default for
the S3C MCI driver.
config MMC_S3C_DMA
bool "Use DMA transfers only"
help
Use DMA to transfer data between memory and the hardare.
Currently, the DMA support in this driver seems to not be
working properly and needs to be debugged before this
option is useful.
endchoice
config MMC_SDRICOH_CS
tristate "MMC/SD driver for Ricoh Bay1Controllers"
depends on PCI && PCMCIA
help
Say Y here if your Notebook reports a Ricoh Bay1Controller PCMCIA
card whenever you insert a MMC or SD card into the card slot.
To compile this driver as a module, choose M here: the
module will be called sdricoh_cs.
config MMC_TMIO_CORE
tristate
config MMC_TMIO
tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support"
depends on MFD_TMIO || MFD_ASIC3
select MMC_TMIO_CORE
help
This provides support for the SD/MMC cell found in TC6393XB,
T7L66XB and also HTC ASIC3
config MMC_SDHI
tristate "SH-Mobile SDHI SD/SDIO controller support"
depends on SUPERH || ARM
depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST
select MMC_TMIO_CORE
help
This provides support for the SDHI SD/SDIO controller found in
SuperH and ARM SH-Mobile SoCs
config MMC_CB710
tristate "ENE CB710 MMC/SD Interface support"
depends on PCI
select CB710_CORE
help
This option enables support for MMC/SD part of ENE CB710/720 Flash
memory card reader found in some laptops (ie. some versions of
HP Compaq nx9500).
This driver can also be built as a module. If so, the module
will be called cb710-mmc.
config MMC_VIA_SDMMC
tristate "VIA SD/MMC Card Reader Driver"
depends on PCI
help
This selects the VIA SD/MMC Card Reader driver, say Y or M here.
VIA provides one multi-functional card reader which integrated into
some motherboards manufactured by VIA. This card reader supports
SD/MMC/SDHC.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config SDH_BFIN
tristate "Blackfin Secure Digital Host support"
depends on (BF54x && !BF544) || (BF51x && !BF512)
help
If you say yes here you will get support for the Blackfin on-chip
Secure Digital Host interface. This includes support for MMC and
SD cards.
To compile this driver as a module, choose M here: the
module will be called bfin_sdh.
If unsure, say N.
config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
bool "Blackfin EZkit Missing SDH_CMD Pull Up Resistor Workaround"
depends on SDH_BFIN
help
If you say yes here SD-Cards may work on the EZkit.
config MMC_DW
tristate "Synopsys DesignWare Memory Card Interface"
depends on HAS_DMA
depends on ARC || ARM || ARM64 || MIPS || COMPILE_TEST
help
This selects support for the Synopsys DesignWare Mobile Storage IP
block, this provides host support for SD and MMC interfaces, in both
PIO and external DMA modes.
config MMC_DW_FMP_DM_CRYPT
bool "FMP acceleration for DM_CRYPT"
depends on DM_CRYPT && FMP_MMC
help
This option accelerates dm-crypt by using the flash
memory protector feature of the eMMC controller.
config MMC_DW_FMP_ECRYPT_FS
bool "FMP acceleration for eCrypt filesystem"
depends on ECRYPT_FS && FMP_MMC
help
This option activates flash memory protector of the eMMC controller to
accelerate eCryptfs.
config MMC_DW_IDMAC
bool "Internal DMAC interface"
depends on MMC_DW
help
This selects support for the internal DMAC block within the Synopsys
Designware Mobile Storage IP block. This disables the external DMA
interface.
config MMC_DW_64BIT_DESC
bool "64bit desctriptor interface"
depends on MMC_DW
help
This selects support for the 64bit internal DMAC block within the Synopsys
Designware Mobile Storage IP block. This disables the 32bit internal DMA
interface.
config MMC_DW_PLTFM
tristate "Synopsys Designware MCI Support as platform device"
depends on MMC_DW
default y
help
This selects the common helper functions support for Host Controller
Interface based platform driver. Please select this option if the IP
is present as a platform device. This is the common interface for the
Synopsys Designware IP.
If you have a controller with this interface, say Y or M here.
If unsure, say Y.
config MMC_DW_EXYNOS
tristate "Exynos specific extensions for Synopsys DW Memory Card Interface"
depends on MMC_DW
select MMC_DW_PLTFM
help
This selects support for Samsung Exynos SoC specific extensions to the
Synopsys DesignWare Memory Card Interface driver. Select this option
for platforms based on Exynos4 and Exynos5 SoC's.
config MMC_DW_EXYNOS_EMMC_POWERCTRL
tristate "Exynos specific extensions for Synopsys DWMCI needed eMMC power ctrl"
depends on MMC_DW_EXYNOS
select MMC_DW_PLTFM
help
This selects support for Samsung Exynos SoC specific extensions to the
Synopsys DesignWare Memory Card Interface driver.
config MMC_DW_DEBUG
bool "Samsung DWMCI debug feature"
depends on MMC_DW
help
This selects support for infomation logging for debugging.
Select this option if this feature is needed on working.
config MMC_DW_K3
tristate "K3 specific extensions for Synopsys DW Memory Card Interface"
depends on MMC_DW
select MMC_DW_PLTFM
select MMC_DW_IDMAC
help
This selects support for Hisilicon K3 SoC specific extensions to the
Synopsys DesignWare Memory Card Interface driver. Select this option
for platforms based on Hisilicon K3 SoC's.
config MMC_DW_PCI
tristate "Synopsys Designware MCI support on PCI bus"
depends on MMC_DW && PCI
help
This selects the PCI bus for the Synopsys Designware Mobile Storage IP.
Select this option if the IP is present on PCI platform.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_DW_ROCKCHIP
tristate "Rockchip specific extensions for Synopsys DW Memory Card Interface"
depends on MMC_DW && ARCH_ROCKCHIP
select MMC_DW_PLTFM
help
This selects support for Rockchip SoC specific extensions to the
Synopsys DesignWare Memory Card Interface driver. Select this option
for platforms based on RK3066, RK3188 and RK3288 SoC's.
config MMC_DW_FORCE_32BIT_SFR_RW
bool "Force to use 32bit rw for 64bit SFR rw"
depends on MMC_DW
help
This selects converting 64bit SFR read/write by two separate 32bit
read/write when 64bit read/write is not allowed for SFR on some DW
MMC implementation. Consider using this with FIFO SFR on PIO mode.
config MMC_SH_MMCIF
tristate "SuperH Internal MMCIF support"
depends on MMC_BLOCK && HAS_DMA
depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST
help
This selects the MMC Host Interface controller (MMCIF).
This driver supports MMCIF in sh7724/sh7757/sh7372.
config MMC_JZ4740
tristate "JZ4740 SD/Multimedia Card Interface support"
depends on MACH_JZ4740
help
This selects support for the SD/MMC controller on Ingenic JZ4740
SoCs.
If you have a board based on such a SoC and with a SD/MMC slot,
say Y or M here.
config MMC_VUB300
tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
depends on USB
help
This selects support for Elan Digital Systems' VUB300 chip.
The VUB300 is a USB-SDIO Host Controller Interface chip
that enables the host computer to use SDIO/SD/MMC cards
via a USB 2.0 or USB 1.1 host.
The VUB300 chip will be found in both physically separate
USB to SDIO/SD/MMC adapters and embedded on some motherboards.
The VUB300 chip supports SD and MMC memory cards in addition
to single and multifunction SDIO cards.
Some SDIO cards will need a firmware file to be loaded and
sent to VUB300 chip in order to achieve better data throughput.
Download these "Offload Pseudocode" from Elan Digital Systems'
web-site http://www.elandigitalsystems.com/support/downloads.php
and put them in /lib/firmware. Note that without these additional
firmware files the VUB300 chip will still function, but not at
the best obtainable data rate.
To compile this mmc host controller driver as a module,
choose M here: the module will be called vub300.
If you have a computer with an embedded VUB300 chip
or if you intend connecting a USB adapter based on a
VUB300 chip say Y or M here.
config MMC_USHC
tristate "USB SD Host Controller (USHC) support"
depends on USB
help
This selects support for USB SD Host Controllers based on
the Cypress Astoria chip with firmware compliant with CSR's
USB SD Host Controller specification (CS-118793-SP).
CSR boards with this device include: USB<>SDIO (M1985v2),
and Ultrasira.
Note: These controllers only support SDIO cards and do not
support MMC or SD memory cards.
config MMC_WMT
tristate "Wondermedia SD/MMC Host Controller support"
depends on ARCH_VT8500
default y
help
This selects support for the SD/MMC Host Controller on
Wondermedia WM8505/WM8650 based SoCs.
To compile this driver as a module, choose M here: the
module will be called wmt-sdmmc.
config MMC_USDHI6ROL0
tristate "Renesas USDHI6ROL0 SD/SDIO Host Controller support"
depends on HAS_DMA
help
This selects support for the Renesas USDHI6ROL0 SD/SDIO
Host Controller
config MMC_REALTEK_PCI
tristate "Realtek PCI-E SD/MMC Card Interface Driver"
depends on MFD_RTSX_PCI
help
Say Y here to include driver code to support SD/MMC card interface
of Realtek PCI-E card reader
config MMC_REALTEK_USB
tristate "Realtek USB SD/MMC Card Interface Driver"
depends on MFD_RTSX_USB
help
Say Y here to include driver code to support SD/MMC card interface
of Realtek RTS5129/39 series card reader
config MMC_SUNXI
tristate "Allwinner sunxi SD/MMC Host Controller support"
depends on ARCH_SUNXI
help
This selects support for the SD/MMC Host Controller on
Allwinner sunxi SoCs.

77
drivers/mmc/host/Makefile Normal file
View file

@ -0,0 +1,77 @@
#
# Makefile for MMC/SD host controller drivers
#
obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
obj-$(CONFIG_MMC_QCOM_DML) += mmci_qcom_dml.o
obj-$(CONFIG_MMC_PXA) += pxamci.o
obj-$(CONFIG_MMC_MXC) += mxcmmc.o
obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
obj-$(subst m,y,$(CONFIG_MMC_SDHCI_PCI)) += sdhci-pci-data.o
obj-$(subst m,y,$(CONFIG_MMC_SDHCI_PCI)) += sdhci-pci-o2micro.o
obj-$(CONFIG_MMC_SDHCI_ACPI) += sdhci-acpi.o
obj-$(CONFIG_MMC_SDHCI_PXAV3) += sdhci-pxav3.o
obj-$(CONFIG_MMC_SDHCI_PXAV2) += sdhci-pxav2.o
obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o
obj-$(CONFIG_MMC_SDHCI_SIRF) += sdhci-sirf.o
obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o
obj-$(CONFIG_MMC_WBSD) += wbsd.o
obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
obj-$(CONFIG_MMC_OMAP) += omap.o
obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc.o
obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o
obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o
obj-$(CONFIG_MMC_MSM) += msm_sdcc.o
obj-$(CONFIG_MMC_MVSDIO) += mvsdio.o
obj-$(CONFIG_MMC_DAVINCI) += davinci_mmc.o
obj-$(CONFIG_MMC_GOLDFISH) += android-goldfish.o
obj-$(CONFIG_MMC_SPI) += mmc_spi.o
ifeq ($(CONFIG_OF),y)
obj-$(CONFIG_MMC_SPI) += of_mmc_spi.o
endif
obj-$(CONFIG_MMC_S3C) += s3cmci.o
obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o
obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o
obj-$(CONFIG_MMC_TMIO_CORE) += tmio_mmc_core.o
tmio_mmc_core-y := tmio_mmc_pio.o
tmio_mmc_core-$(subst m,y,$(CONFIG_MMC_SDHI)) += tmio_mmc_dma.o
obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
obj-$(CONFIG_MMC_DW) += dw_mmc.o
obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
obj-$(CONFIG_MMC_VUB300) += vub300.o
obj-$(CONFIG_MMC_USHC) += ushc.o
obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o
obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o
obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o
obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o
obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o
obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o
obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o
obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o
obj-$(CONFIG_MMC_SDHCI_OF_ARASAN) += sdhci-of-arasan.o
obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG
endif

View file

@ -0,0 +1,568 @@
/*
* Copyright 2007, Google Inc.
* Copyright 2012, Intel Inc.
*
* based on omap.c driver, which was
* Copyright (C) 2004 Nokia Corporation
* Written by Tuukka Tikkanen and Juha Yrjölä <juha.yrjola@nokia.com>
* Misc hacks here and there by Tony Lindgren <tony@atomide.com>
* Other hacks (DMA, SD, etc) by David Brownell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/major.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/kdev_t.h>
#include <linux/blkdev.h>
#include <linux/mutex.h>
#include <linux/scatterlist.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/scatterlist.h>
#include <asm/types.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "goldfish_mmc"
#define BUFFER_SIZE 16384
#define GOLDFISH_MMC_READ(host, addr) (readl(host->reg_base + addr))
#define GOLDFISH_MMC_WRITE(host, addr, x) (writel(x, host->reg_base + addr))
enum {
/* status register */
MMC_INT_STATUS = 0x00,
/* set this to enable IRQ */
MMC_INT_ENABLE = 0x04,
/* set this to specify buffer address */
MMC_SET_BUFFER = 0x08,
/* MMC command number */
MMC_CMD = 0x0C,
/* MMC argument */
MMC_ARG = 0x10,
/* MMC response (or R2 bits 0 - 31) */
MMC_RESP_0 = 0x14,
/* MMC R2 response bits 32 - 63 */
MMC_RESP_1 = 0x18,
/* MMC R2 response bits 64 - 95 */
MMC_RESP_2 = 0x1C,
/* MMC R2 response bits 96 - 127 */
MMC_RESP_3 = 0x20,
MMC_BLOCK_LENGTH = 0x24,
MMC_BLOCK_COUNT = 0x28,
/* MMC state flags */
MMC_STATE = 0x2C,
/* MMC_INT_STATUS bits */
MMC_STAT_END_OF_CMD = 1U << 0,
MMC_STAT_END_OF_DATA = 1U << 1,
MMC_STAT_STATE_CHANGE = 1U << 2,
MMC_STAT_CMD_TIMEOUT = 1U << 3,
/* MMC_STATE bits */
MMC_STATE_INSERTED = 1U << 0,
MMC_STATE_READ_ONLY = 1U << 1,
};
/*
* Command types
*/
#define OMAP_MMC_CMDTYPE_BC 0
#define OMAP_MMC_CMDTYPE_BCR 1
#define OMAP_MMC_CMDTYPE_AC 2
#define OMAP_MMC_CMDTYPE_ADTC 3
struct goldfish_mmc_host {
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_host *mmc;
struct device *dev;
unsigned char id; /* 16xx chips have 2 MMC blocks */
void __iomem *virt_base;
unsigned int phys_base;
int irq;
unsigned char bus_mode;
unsigned char hw_bus_mode;
unsigned int sg_len;
unsigned dma_done:1;
unsigned dma_in_use:1;
void __iomem *reg_base;
};
static inline int
goldfish_mmc_cover_is_open(struct goldfish_mmc_host *host)
{
return 0;
}
static ssize_t
goldfish_mmc_show_cover_switch(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct goldfish_mmc_host *host = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", goldfish_mmc_cover_is_open(host) ? "open" :
"closed");
}
static DEVICE_ATTR(cover_switch, S_IRUGO, goldfish_mmc_show_cover_switch, NULL);
static void
goldfish_mmc_start_command(struct goldfish_mmc_host *host, struct mmc_command *cmd)
{
u32 cmdreg;
u32 resptype;
u32 cmdtype;
host->cmd = cmd;
resptype = 0;
cmdtype = 0;
/* Our hardware needs to know exact type */
switch (mmc_resp_type(cmd)) {
case MMC_RSP_NONE:
break;
case MMC_RSP_R1:
case MMC_RSP_R1B:
/* resp 1, 1b, 6, 7 */
resptype = 1;
break;
case MMC_RSP_R2:
resptype = 2;
break;
case MMC_RSP_R3:
resptype = 3;
break;
default:
dev_err(mmc_dev(host->mmc),
"Invalid response type: %04x\n", mmc_resp_type(cmd));
break;
}
if (mmc_cmd_type(cmd) == MMC_CMD_ADTC)
cmdtype = OMAP_MMC_CMDTYPE_ADTC;
else if (mmc_cmd_type(cmd) == MMC_CMD_BC)
cmdtype = OMAP_MMC_CMDTYPE_BC;
else if (mmc_cmd_type(cmd) == MMC_CMD_BCR)
cmdtype = OMAP_MMC_CMDTYPE_BCR;
else
cmdtype = OMAP_MMC_CMDTYPE_AC;
cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12);
if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
cmdreg |= 1 << 6;
if (cmd->flags & MMC_RSP_BUSY)
cmdreg |= 1 << 11;
if (host->data && !(host->data->flags & MMC_DATA_WRITE))
cmdreg |= 1 << 15;
GOLDFISH_MMC_WRITE(host, MMC_ARG, cmd->arg);
GOLDFISH_MMC_WRITE(host, MMC_CMD, cmdreg);
}
static void goldfish_mmc_xfer_done(struct goldfish_mmc_host *host,
struct mmc_data *data)
{
if (host->dma_in_use) {
enum dma_data_direction dma_data_dir;
if (data->flags & MMC_DATA_WRITE)
dma_data_dir = DMA_TO_DEVICE;
else
dma_data_dir = DMA_FROM_DEVICE;
if (dma_data_dir == DMA_FROM_DEVICE) {
/*
* We don't really have DMA, so we need
* to copy from our platform driver buffer
*/
uint8_t *dest = (uint8_t *)sg_virt(data->sg);
memcpy(dest, host->virt_base, data->sg->length);
}
host->data->bytes_xfered += data->sg->length;
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
dma_data_dir);
}
host->data = NULL;
host->sg_len = 0;
/*
* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing
* dozens of requests until the card finishes writing data.
* It'd be cheaper to just wait till an EOFB interrupt arrives...
*/
if (!data->stop) {
host->mrq = NULL;
mmc_request_done(host->mmc, data->mrq);
return;
}
goldfish_mmc_start_command(host, data->stop);
}
static void goldfish_mmc_end_of_data(struct goldfish_mmc_host *host,
struct mmc_data *data)
{
if (!host->dma_in_use) {
goldfish_mmc_xfer_done(host, data);
return;
}
if (host->dma_done)
goldfish_mmc_xfer_done(host, data);
}
static void goldfish_mmc_cmd_done(struct goldfish_mmc_host *host,
struct mmc_command *cmd)
{
host->cmd = NULL;
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
/* response type 2 */
cmd->resp[3] =
GOLDFISH_MMC_READ(host, MMC_RESP_0);
cmd->resp[2] =
GOLDFISH_MMC_READ(host, MMC_RESP_1);
cmd->resp[1] =
GOLDFISH_MMC_READ(host, MMC_RESP_2);
cmd->resp[0] =
GOLDFISH_MMC_READ(host, MMC_RESP_3);
} else {
/* response types 1, 1b, 3, 4, 5, 6 */
cmd->resp[0] =
GOLDFISH_MMC_READ(host, MMC_RESP_0);
}
}
if (host->data == NULL || cmd->error) {
host->mrq = NULL;
mmc_request_done(host->mmc, cmd->mrq);
}
}
static irqreturn_t goldfish_mmc_irq(int irq, void *dev_id)
{
struct goldfish_mmc_host *host = (struct goldfish_mmc_host *)dev_id;
u16 status;
int end_command = 0;
int end_transfer = 0;
int transfer_error = 0;
int state_changed = 0;
int cmd_timeout = 0;
while ((status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS)) != 0) {
GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status);
if (status & MMC_STAT_END_OF_CMD)
end_command = 1;
if (status & MMC_STAT_END_OF_DATA)
end_transfer = 1;
if (status & MMC_STAT_STATE_CHANGE)
state_changed = 1;
if (status & MMC_STAT_CMD_TIMEOUT) {
end_command = 0;
cmd_timeout = 1;
}
}
if (cmd_timeout) {
struct mmc_request *mrq = host->mrq;
mrq->cmd->error = -ETIMEDOUT;
host->mrq = NULL;
mmc_request_done(host->mmc, mrq);
}
if (end_command)
goldfish_mmc_cmd_done(host, host->cmd);
if (transfer_error)
goldfish_mmc_xfer_done(host, host->data);
else if (end_transfer) {
host->dma_done = 1;
goldfish_mmc_end_of_data(host, host->data);
} else if (host->data != NULL) {
/*
* WORKAROUND -- after porting this driver from 2.6 to 3.4,
* during device initialization, cases where host->data is
* non-null but end_transfer is false would occur. Doing
* nothing in such cases results in no further interrupts,
* and initialization failure.
* TODO -- find the real cause.
*/
host->dma_done = 1;
goldfish_mmc_end_of_data(host, host->data);
}
if (state_changed) {
u32 state = GOLDFISH_MMC_READ(host, MMC_STATE);
pr_info("%s: Card detect now %d\n", __func__,
(state & MMC_STATE_INSERTED));
mmc_detect_change(host->mmc, 0);
}
if (!end_command && !end_transfer &&
!transfer_error && !state_changed && !cmd_timeout) {
status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS);
dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status);
if (status != 0) {
GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status);
GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE, 0);
}
}
return IRQ_HANDLED;
}
static void goldfish_mmc_prepare_data(struct goldfish_mmc_host *host,
struct mmc_request *req)
{
struct mmc_data *data = req->data;
int block_size;
unsigned sg_len;
enum dma_data_direction dma_data_dir;
host->data = data;
if (data == NULL) {
GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, 0);
GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, 0);
host->dma_in_use = 0;
return;
}
block_size = data->blksz;
GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, data->blocks - 1);
GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, block_size - 1);
/*
* Cope with calling layer confusion; it issues "single
* block" writes using multi-block scatterlists.
*/
sg_len = (data->blocks == 1) ? 1 : data->sg_len;
if (data->flags & MMC_DATA_WRITE)
dma_data_dir = DMA_TO_DEVICE;
else
dma_data_dir = DMA_FROM_DEVICE;
host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
sg_len, dma_data_dir);
host->dma_done = 0;
host->dma_in_use = 1;
if (dma_data_dir == DMA_TO_DEVICE) {
/*
* We don't really have DMA, so we need to copy to our
* platform driver buffer
*/
const uint8_t *src = (uint8_t *)sg_virt(data->sg);
memcpy(host->virt_base, src, data->sg->length);
}
}
static void goldfish_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
{
struct goldfish_mmc_host *host = mmc_priv(mmc);
WARN_ON(host->mrq != NULL);
host->mrq = req;
goldfish_mmc_prepare_data(host, req);
goldfish_mmc_start_command(host, req->cmd);
/*
* This is to avoid accidentally being detected as an SDIO card
* in mmc_attach_sdio().
*/
if (req->cmd->opcode == SD_IO_SEND_OP_COND &&
req->cmd->flags == (MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR))
req->cmd->error = -EINVAL;
}
static void goldfish_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct goldfish_mmc_host *host = mmc_priv(mmc);
host->bus_mode = ios->bus_mode;
host->hw_bus_mode = host->bus_mode;
}
static int goldfish_mmc_get_ro(struct mmc_host *mmc)
{
uint32_t state;
struct goldfish_mmc_host *host = mmc_priv(mmc);
state = GOLDFISH_MMC_READ(host, MMC_STATE);
return ((state & MMC_STATE_READ_ONLY) != 0);
}
static const struct mmc_host_ops goldfish_mmc_ops = {
.request = goldfish_mmc_request,
.set_ios = goldfish_mmc_set_ios,
.get_ro = goldfish_mmc_get_ro,
};
static int goldfish_mmc_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct goldfish_mmc_host *host = NULL;
struct resource *res;
int ret = 0;
int irq;
dma_addr_t buf_addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (res == NULL || irq < 0)
return -ENXIO;
mmc = mmc_alloc_host(sizeof(struct goldfish_mmc_host), &pdev->dev);
if (mmc == NULL) {
ret = -ENOMEM;
goto err_alloc_host_failed;
}
host = mmc_priv(mmc);
host->mmc = mmc;
pr_err("mmc: Mapping %lX to %lX\n", (long)res->start, (long)res->end);
host->reg_base = ioremap(res->start, resource_size(res));
if (host->reg_base == NULL) {
ret = -ENOMEM;
goto ioremap_failed;
}
host->virt_base = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE,
&buf_addr, GFP_KERNEL);
if (host->virt_base == 0) {
ret = -ENOMEM;
goto dma_alloc_failed;
}
host->phys_base = buf_addr;
host->id = pdev->id;
host->irq = irq;
mmc->ops = &goldfish_mmc_ops;
mmc->f_min = 400000;
mmc->f_max = 24000000;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA;
/* Use scatterlist DMA to reduce per-transfer costs.
* NOTE max_seg_size assumption that small blocks aren't
* normally used (except e.g. for reading SD registers).
*/
mmc->max_segs = 32;
mmc->max_blk_size = 2048; /* MMC_BLOCK_LENGTH is 11 bits (+1) */
mmc->max_blk_count = 2048; /* MMC_BLOCK_COUNT is 11 bits (+1) */
mmc->max_req_size = BUFFER_SIZE;
mmc->max_seg_size = mmc->max_req_size;
ret = request_irq(host->irq, goldfish_mmc_irq, 0, DRIVER_NAME, host);
if (ret) {
dev_err(&pdev->dev, "Failed IRQ Adding goldfish MMC\n");
goto err_request_irq_failed;
}
host->dev = &pdev->dev;
platform_set_drvdata(pdev, host);
ret = device_create_file(&pdev->dev, &dev_attr_cover_switch);
if (ret)
dev_warn(mmc_dev(host->mmc),
"Unable to create sysfs attributes\n");
GOLDFISH_MMC_WRITE(host, MMC_SET_BUFFER, host->phys_base);
GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE,
MMC_STAT_END_OF_CMD | MMC_STAT_END_OF_DATA |
MMC_STAT_STATE_CHANGE | MMC_STAT_CMD_TIMEOUT);
mmc_add_host(mmc);
return 0;
err_request_irq_failed:
dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base,
host->phys_base);
dma_alloc_failed:
iounmap(host->reg_base);
ioremap_failed:
mmc_free_host(host->mmc);
err_alloc_host_failed:
return ret;
}
static int goldfish_mmc_remove(struct platform_device *pdev)
{
struct goldfish_mmc_host *host = platform_get_drvdata(pdev);
BUG_ON(host == NULL);
mmc_remove_host(host->mmc);
free_irq(host->irq, host);
dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base, host->phys_base);
iounmap(host->reg_base);
mmc_free_host(host->mmc);
return 0;
}
static struct platform_driver goldfish_mmc_driver = {
.probe = goldfish_mmc_probe,
.remove = goldfish_mmc_remove,
.driver = {
.name = DRIVER_NAME,
},
};
module_platform_driver(goldfish_mmc_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,164 @@
/*
* Atmel MultiMedia Card Interface driver
*
* Copyright (C) 2004-2006 Atmel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* Superset of MCI IP registers integrated in Atmel AVR32 and AT91 Processors
* Registers and bitfields marked with [2] are only available in MCI2
*/
#ifndef __DRIVERS_MMC_ATMEL_MCI_H__
#define __DRIVERS_MMC_ATMEL_MCI_H__
/* MCI Register Definitions */
#define ATMCI_CR 0x0000 /* Control */
# define ATMCI_CR_MCIEN ( 1 << 0) /* MCI Enable */
# define ATMCI_CR_MCIDIS ( 1 << 1) /* MCI Disable */
# define ATMCI_CR_PWSEN ( 1 << 2) /* Power Save Enable */
# define ATMCI_CR_PWSDIS ( 1 << 3) /* Power Save Disable */
# define ATMCI_CR_SWRST ( 1 << 7) /* Software Reset */
#define ATMCI_MR 0x0004 /* Mode */
# define ATMCI_MR_CLKDIV(x) ((x) << 0) /* Clock Divider */
# define ATMCI_MR_PWSDIV(x) ((x) << 8) /* Power Saving Divider */
# define ATMCI_MR_RDPROOF ( 1 << 11) /* Read Proof */
# define ATMCI_MR_WRPROOF ( 1 << 12) /* Write Proof */
# define ATMCI_MR_PDCFBYTE ( 1 << 13) /* Force Byte Transfer */
# define ATMCI_MR_PDCPADV ( 1 << 14) /* Padding Value */
# define ATMCI_MR_PDCMODE ( 1 << 15) /* PDC-oriented Mode */
# define ATMCI_MR_CLKODD(x) ((x) << 16) /* LSB of Clock Divider */
#define ATMCI_DTOR 0x0008 /* Data Timeout */
# define ATMCI_DTOCYC(x) ((x) << 0) /* Data Timeout Cycles */
# define ATMCI_DTOMUL(x) ((x) << 4) /* Data Timeout Multiplier */
#define ATMCI_SDCR 0x000c /* SD Card / SDIO */
# define ATMCI_SDCSEL_SLOT_A ( 0 << 0) /* Select SD slot A */
# define ATMCI_SDCSEL_SLOT_B ( 1 << 0) /* Select SD slot A */
# define ATMCI_SDCSEL_MASK ( 3 << 0)
# define ATMCI_SDCBUS_1BIT ( 0 << 6) /* 1-bit data bus */
# define ATMCI_SDCBUS_4BIT ( 2 << 6) /* 4-bit data bus */
# define ATMCI_SDCBUS_8BIT ( 3 << 6) /* 8-bit data bus[2] */
# define ATMCI_SDCBUS_MASK ( 3 << 6)
#define ATMCI_ARGR 0x0010 /* Command Argument */
#define ATMCI_CMDR 0x0014 /* Command */
# define ATMCI_CMDR_CMDNB(x) ((x) << 0) /* Command Opcode */
# define ATMCI_CMDR_RSPTYP_NONE ( 0 << 6) /* No response */
# define ATMCI_CMDR_RSPTYP_48BIT ( 1 << 6) /* 48-bit response */
# define ATMCI_CMDR_RSPTYP_136BIT ( 2 << 6) /* 136-bit response */
# define ATMCI_CMDR_SPCMD_INIT ( 1 << 8) /* Initialization command */
# define ATMCI_CMDR_SPCMD_SYNC ( 2 << 8) /* Synchronized command */
# define ATMCI_CMDR_SPCMD_INT ( 4 << 8) /* Interrupt command */
# define ATMCI_CMDR_SPCMD_INTRESP ( 5 << 8) /* Interrupt response */
# define ATMCI_CMDR_OPDCMD ( 1 << 11) /* Open Drain */
# define ATMCI_CMDR_MAXLAT_5CYC ( 0 << 12) /* Max latency 5 cycles */
# define ATMCI_CMDR_MAXLAT_64CYC ( 1 << 12) /* Max latency 64 cycles */
# define ATMCI_CMDR_START_XFER ( 1 << 16) /* Start data transfer */
# define ATMCI_CMDR_STOP_XFER ( 2 << 16) /* Stop data transfer */
# define ATMCI_CMDR_TRDIR_WRITE ( 0 << 18) /* Write data */
# define ATMCI_CMDR_TRDIR_READ ( 1 << 18) /* Read data */
# define ATMCI_CMDR_BLOCK ( 0 << 19) /* Single-block transfer */
# define ATMCI_CMDR_MULTI_BLOCK ( 1 << 19) /* Multi-block transfer */
# define ATMCI_CMDR_STREAM ( 2 << 19) /* MMC Stream transfer */
# define ATMCI_CMDR_SDIO_BYTE ( 4 << 19) /* SDIO Byte transfer */
# define ATMCI_CMDR_SDIO_BLOCK ( 5 << 19) /* SDIO Block transfer */
# define ATMCI_CMDR_SDIO_SUSPEND ( 1 << 24) /* SDIO Suspend Command */
# define ATMCI_CMDR_SDIO_RESUME ( 2 << 24) /* SDIO Resume Command */
#define ATMCI_BLKR 0x0018 /* Block */
# define ATMCI_BCNT(x) ((x) << 0) /* Data Block Count */
# define ATMCI_BLKLEN(x) ((x) << 16) /* Data Block Length */
#define ATMCI_CSTOR 0x001c /* Completion Signal Timeout[2] */
# define ATMCI_CSTOCYC(x) ((x) << 0) /* CST cycles */
# define ATMCI_CSTOMUL(x) ((x) << 4) /* CST multiplier */
#define ATMCI_RSPR 0x0020 /* Response 0 */
#define ATMCI_RSPR1 0x0024 /* Response 1 */
#define ATMCI_RSPR2 0x0028 /* Response 2 */
#define ATMCI_RSPR3 0x002c /* Response 3 */
#define ATMCI_RDR 0x0030 /* Receive Data */
#define ATMCI_TDR 0x0034 /* Transmit Data */
#define ATMCI_SR 0x0040 /* Status */
#define ATMCI_IER 0x0044 /* Interrupt Enable */
#define ATMCI_IDR 0x0048 /* Interrupt Disable */
#define ATMCI_IMR 0x004c /* Interrupt Mask */
# define ATMCI_CMDRDY ( 1 << 0) /* Command Ready */
# define ATMCI_RXRDY ( 1 << 1) /* Receiver Ready */
# define ATMCI_TXRDY ( 1 << 2) /* Transmitter Ready */
# define ATMCI_BLKE ( 1 << 3) /* Data Block Ended */
# define ATMCI_DTIP ( 1 << 4) /* Data Transfer In Progress */
# define ATMCI_NOTBUSY ( 1 << 5) /* Data Not Busy */
# define ATMCI_ENDRX ( 1 << 6) /* End of RX Buffer */
# define ATMCI_ENDTX ( 1 << 7) /* End of TX Buffer */
# define ATMCI_SDIOIRQA ( 1 << 8) /* SDIO IRQ in slot A */
# define ATMCI_SDIOIRQB ( 1 << 9) /* SDIO IRQ in slot B */
# define ATMCI_SDIOWAIT ( 1 << 12) /* SDIO Read Wait Operation Status */
# define ATMCI_CSRCV ( 1 << 13) /* CE-ATA Completion Signal Received */
# define ATMCI_RXBUFF ( 1 << 14) /* RX Buffer Full */
# define ATMCI_TXBUFE ( 1 << 15) /* TX Buffer Empty */
# define ATMCI_RINDE ( 1 << 16) /* Response Index Error */
# define ATMCI_RDIRE ( 1 << 17) /* Response Direction Error */
# define ATMCI_RCRCE ( 1 << 18) /* Response CRC Error */
# define ATMCI_RENDE ( 1 << 19) /* Response End Bit Error */
# define ATMCI_RTOE ( 1 << 20) /* Response Time-Out Error */
# define ATMCI_DCRCE ( 1 << 21) /* Data CRC Error */
# define ATMCI_DTOE ( 1 << 22) /* Data Time-Out Error */
# define ATMCI_CSTOE ( 1 << 23) /* Completion Signal Time-out Error */
# define ATMCI_BLKOVRE ( 1 << 24) /* DMA Block Overrun Error */
# define ATMCI_DMADONE ( 1 << 25) /* DMA Transfer Done */
# define ATMCI_FIFOEMPTY ( 1 << 26) /* FIFO Empty Flag */
# define ATMCI_XFRDONE ( 1 << 27) /* Transfer Done Flag */
# define ATMCI_ACKRCV ( 1 << 28) /* Boot Operation Acknowledge Received */
# define ATMCI_ACKRCVE ( 1 << 29) /* Boot Operation Acknowledge Error */
# define ATMCI_OVRE ( 1 << 30) /* RX Overrun Error */
# define ATMCI_UNRE ( 1 << 31) /* TX Underrun Error */
#define ATMCI_DMA 0x0050 /* DMA Configuration[2] */
# define ATMCI_DMA_OFFSET(x) ((x) << 0) /* DMA Write Buffer Offset */
# define ATMCI_DMA_CHKSIZE(x) ((x) << 4) /* DMA Channel Read and Write Chunk Size */
# define ATMCI_DMAEN ( 1 << 8) /* DMA Hardware Handshaking Enable */
#define ATMCI_CFG 0x0054 /* Configuration[2] */
# define ATMCI_CFG_FIFOMODE_1DATA ( 1 << 0) /* MCI Internal FIFO control mode */
# define ATMCI_CFG_FERRCTRL_COR ( 1 << 4) /* Flow Error flag reset control mode */
# define ATMCI_CFG_HSMODE ( 1 << 8) /* High Speed Mode */
# define ATMCI_CFG_LSYNC ( 1 << 12) /* Synchronize on the last block */
#define ATMCI_WPMR 0x00e4 /* Write Protection Mode[2] */
# define ATMCI_WP_EN ( 1 << 0) /* WP Enable */
# define ATMCI_WP_KEY (0x4d4349 << 8) /* WP Key */
#define ATMCI_WPSR 0x00e8 /* Write Protection Status[2] */
# define ATMCI_GET_WP_VS(x) ((x) & 0x0f)
# define ATMCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff)
#define ATMCI_VERSION 0x00FC /* Version */
#define ATMCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */
/* This is not including the FIFO Aperture on MCI2 */
#define ATMCI_REGS_SIZE 0x100
/* Register access macros */
#define atmci_readl(port,reg) \
__raw_readl((port)->regs + reg)
#define atmci_writel(port,reg,value) \
__raw_writel((value), (port)->regs + reg)
/* On AVR chips the Peripheral DMA Controller is not connected to MCI. */
#ifdef CONFIG_AVR32
# define ATMCI_PDC_CONNECTED 0
#else
# define ATMCI_PDC_CONNECTED 1
#endif
/*
* Fix sconfig's burst size according to atmel MCI. We need to convert them as:
* 1 -> 0, 4 -> 1, 8 -> 2, 16 -> 3.
*
* This can be done by finding most significant bit set.
*/
static inline unsigned int atmci_convert_chksize(unsigned int maxburst)
{
if (maxburst > 1)
return fls(maxburst) - 2;
else
return 0;
}
#endif /* __DRIVERS_MMC_ATMEL_MCI_H__ */

2561
drivers/mmc/host/atmel-mci.c Normal file

File diff suppressed because it is too large Load diff

1241
drivers/mmc/host/au1xmmc.c Normal file

File diff suppressed because it is too large Load diff

682
drivers/mmc/host/bfin_sdh.c Normal file
View file

@ -0,0 +1,682 @@
/*
* bfin_sdh.c - Analog Devices Blackfin SDH Controller
*
* Copyright (C) 2007-2009 Analog Device Inc.
*
* Licensed under the GPL-2 or later.
*/
#define DRIVER_NAME "bfin-sdh"
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/host.h>
#include <linux/proc_fs.h>
#include <linux/gfp.h>
#include <asm/cacheflush.h>
#include <asm/dma.h>
#include <asm/portmux.h>
#include <asm/bfin_sdh.h>
#if defined(CONFIG_BF51x) || defined(__ADSPBF60x__)
#define bfin_read_SDH_CLK_CTL bfin_read_RSI_CLK_CTL
#define bfin_write_SDH_CLK_CTL bfin_write_RSI_CLK_CTL
#define bfin_write_SDH_ARGUMENT bfin_write_RSI_ARGUMENT
#define bfin_write_SDH_COMMAND bfin_write_RSI_COMMAND
#define bfin_write_SDH_DATA_TIMER bfin_write_RSI_DATA_TIMER
#define bfin_read_SDH_RESPONSE0 bfin_read_RSI_RESPONSE0
#define bfin_read_SDH_RESPONSE1 bfin_read_RSI_RESPONSE1
#define bfin_read_SDH_RESPONSE2 bfin_read_RSI_RESPONSE2
#define bfin_read_SDH_RESPONSE3 bfin_read_RSI_RESPONSE3
#define bfin_write_SDH_DATA_LGTH bfin_write_RSI_DATA_LGTH
#define bfin_read_SDH_DATA_CTL bfin_read_RSI_DATA_CTL
#define bfin_write_SDH_DATA_CTL bfin_write_RSI_DATA_CTL
#define bfin_read_SDH_DATA_CNT bfin_read_RSI_DATA_CNT
#define bfin_write_SDH_STATUS_CLR bfin_write_RSI_STATUS_CLR
#define bfin_read_SDH_E_STATUS bfin_read_RSI_E_STATUS
#define bfin_write_SDH_E_STATUS bfin_write_RSI_E_STATUS
#define bfin_read_SDH_STATUS bfin_read_RSI_STATUS
#define bfin_write_SDH_MASK0 bfin_write_RSI_MASK0
#define bfin_write_SDH_E_MASK bfin_write_RSI_E_MASK
#define bfin_read_SDH_CFG bfin_read_RSI_CFG
#define bfin_write_SDH_CFG bfin_write_RSI_CFG
# if defined(__ADSPBF60x__)
# define bfin_read_SDH_BLK_SIZE bfin_read_RSI_BLKSZ
# define bfin_write_SDH_BLK_SIZE bfin_write_RSI_BLKSZ
# else
# define bfin_read_SDH_PWR_CTL bfin_read_RSI_PWR_CTL
# define bfin_write_SDH_PWR_CTL bfin_write_RSI_PWR_CTL
# endif
#endif
struct sdh_host {
struct mmc_host *mmc;
spinlock_t lock;
struct resource *res;
void __iomem *base;
int irq;
int stat_irq;
int dma_ch;
int dma_dir;
struct dma_desc_array *sg_cpu;
dma_addr_t sg_dma;
int dma_len;
unsigned long sclk;
unsigned int imask;
unsigned int power_mode;
unsigned int clk_div;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
};
static struct bfin_sd_host *get_sdh_data(struct platform_device *pdev)
{
return pdev->dev.platform_data;
}
static void sdh_stop_clock(struct sdh_host *host)
{
bfin_write_SDH_CLK_CTL(bfin_read_SDH_CLK_CTL() & ~CLK_E);
SSYNC();
}
static void sdh_enable_stat_irq(struct sdh_host *host, unsigned int mask)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->imask |= mask;
bfin_write_SDH_MASK0(mask);
SSYNC();
spin_unlock_irqrestore(&host->lock, flags);
}
static void sdh_disable_stat_irq(struct sdh_host *host, unsigned int mask)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->imask &= ~mask;
bfin_write_SDH_MASK0(host->imask);
SSYNC();
spin_unlock_irqrestore(&host->lock, flags);
}
static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data)
{
unsigned int length;
unsigned int data_ctl;
unsigned int dma_cfg;
unsigned int cycle_ns, timeout;
dev_dbg(mmc_dev(host->mmc), "%s enter flags: 0x%x\n", __func__, data->flags);
host->data = data;
data_ctl = 0;
dma_cfg = 0;
length = data->blksz * data->blocks;
bfin_write_SDH_DATA_LGTH(length);
if (data->flags & MMC_DATA_STREAM)
data_ctl |= DTX_MODE;
if (data->flags & MMC_DATA_READ)
data_ctl |= DTX_DIR;
/* Only supports power-of-2 block size */
if (data->blksz & (data->blksz - 1))
return -EINVAL;
#ifndef RSI_BLKSZ
data_ctl |= ((ffs(data->blksz) - 1) << 4);
#else
bfin_write_SDH_BLK_SIZE(data->blksz);
#endif
bfin_write_SDH_DATA_CTL(data_ctl);
/* the time of a host clock period in ns */
cycle_ns = 1000000000 / (host->sclk / (2 * (host->clk_div + 1)));
timeout = data->timeout_ns / cycle_ns;
timeout += data->timeout_clks;
bfin_write_SDH_DATA_TIMER(timeout);
SSYNC();
if (data->flags & MMC_DATA_READ) {
host->dma_dir = DMA_FROM_DEVICE;
dma_cfg |= WNR;
} else
host->dma_dir = DMA_TO_DEVICE;
sdh_enable_stat_irq(host, (DAT_CRC_FAIL | DAT_TIME_OUT | DAT_END));
host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma_dir);
#if defined(CONFIG_BF54x) || defined(CONFIG_BF60x)
dma_cfg |= DMAFLOW_ARRAY | RESTART | WDSIZE_32 | DMAEN;
# ifdef RSI_BLKSZ
dma_cfg |= PSIZE_32 | NDSIZE_3;
# else
dma_cfg |= NDSIZE_5;
# endif
{
struct scatterlist *sg;
int i;
for_each_sg(data->sg, sg, host->dma_len, i) {
host->sg_cpu[i].start_addr = sg_dma_address(sg);
host->sg_cpu[i].cfg = dma_cfg;
host->sg_cpu[i].x_count = sg_dma_len(sg) / 4;
host->sg_cpu[i].x_modify = 4;
dev_dbg(mmc_dev(host->mmc), "%d: start_addr:0x%lx, "
"cfg:0x%lx, x_count:0x%lx, x_modify:0x%lx\n",
i, host->sg_cpu[i].start_addr,
host->sg_cpu[i].cfg, host->sg_cpu[i].x_count,
host->sg_cpu[i].x_modify);
}
}
flush_dcache_range((unsigned int)host->sg_cpu,
(unsigned int)host->sg_cpu +
host->dma_len * sizeof(struct dma_desc_array));
/* Set the last descriptor to stop mode */
host->sg_cpu[host->dma_len - 1].cfg &= ~(DMAFLOW | NDSIZE);
host->sg_cpu[host->dma_len - 1].cfg |= DI_EN;
set_dma_curr_desc_addr(host->dma_ch, (unsigned long *)host->sg_dma);
set_dma_x_count(host->dma_ch, 0);
set_dma_x_modify(host->dma_ch, 0);
SSYNC();
set_dma_config(host->dma_ch, dma_cfg);
#elif defined(CONFIG_BF51x)
/* RSI DMA doesn't work in array mode */
dma_cfg |= WDSIZE_32 | DMAEN;
set_dma_start_addr(host->dma_ch, sg_dma_address(&data->sg[0]));
set_dma_x_count(host->dma_ch, length / 4);
set_dma_x_modify(host->dma_ch, 4);
SSYNC();
set_dma_config(host->dma_ch, dma_cfg);
#endif
bfin_write_SDH_DATA_CTL(bfin_read_SDH_DATA_CTL() | DTX_DMA_E | DTX_E);
SSYNC();
dev_dbg(mmc_dev(host->mmc), "%s exit\n", __func__);
return 0;
}
static void sdh_start_cmd(struct sdh_host *host, struct mmc_command *cmd)
{
unsigned int sdh_cmd;
unsigned int stat_mask;
dev_dbg(mmc_dev(host->mmc), "%s enter cmd: 0x%p\n", __func__, cmd);
WARN_ON(host->cmd != NULL);
host->cmd = cmd;
sdh_cmd = 0;
stat_mask = 0;
sdh_cmd |= cmd->opcode;
if (cmd->flags & MMC_RSP_PRESENT) {
sdh_cmd |= CMD_RSP;
stat_mask |= CMD_RESP_END;
} else {
stat_mask |= CMD_SENT;
}
if (cmd->flags & MMC_RSP_136)
sdh_cmd |= CMD_L_RSP;
stat_mask |= CMD_CRC_FAIL | CMD_TIME_OUT;
sdh_enable_stat_irq(host, stat_mask);
bfin_write_SDH_ARGUMENT(cmd->arg);
bfin_write_SDH_COMMAND(sdh_cmd | CMD_E);
bfin_write_SDH_CLK_CTL(bfin_read_SDH_CLK_CTL() | CLK_E);
SSYNC();
}
static void sdh_finish_request(struct sdh_host *host, struct mmc_request *mrq)
{
dev_dbg(mmc_dev(host->mmc), "%s enter\n", __func__);
host->mrq = NULL;
host->cmd = NULL;
host->data = NULL;
mmc_request_done(host->mmc, mrq);
}
static int sdh_cmd_done(struct sdh_host *host, unsigned int stat)
{
struct mmc_command *cmd = host->cmd;
int ret = 0;
dev_dbg(mmc_dev(host->mmc), "%s enter cmd: %p\n", __func__, cmd);
if (!cmd)
return 0;
host->cmd = NULL;
if (cmd->flags & MMC_RSP_PRESENT) {
cmd->resp[0] = bfin_read_SDH_RESPONSE0();
if (cmd->flags & MMC_RSP_136) {
cmd->resp[1] = bfin_read_SDH_RESPONSE1();
cmd->resp[2] = bfin_read_SDH_RESPONSE2();
cmd->resp[3] = bfin_read_SDH_RESPONSE3();
}
}
if (stat & CMD_TIME_OUT)
cmd->error = -ETIMEDOUT;
else if (stat & CMD_CRC_FAIL && cmd->flags & MMC_RSP_CRC)
cmd->error = -EILSEQ;
sdh_disable_stat_irq(host, (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT | CMD_CRC_FAIL));
if (host->data && !cmd->error) {
if (host->data->flags & MMC_DATA_WRITE) {
ret = sdh_setup_data(host, host->data);
if (ret)
return 0;
}
sdh_enable_stat_irq(host, DAT_END | RX_OVERRUN | TX_UNDERRUN | DAT_TIME_OUT);
} else
sdh_finish_request(host, host->mrq);
return 1;
}
static int sdh_data_done(struct sdh_host *host, unsigned int stat)
{
struct mmc_data *data = host->data;
dev_dbg(mmc_dev(host->mmc), "%s enter stat: 0x%x\n", __func__, stat);
if (!data)
return 0;
disable_dma(host->dma_ch);
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
host->dma_dir);
if (stat & DAT_TIME_OUT)
data->error = -ETIMEDOUT;
else if (stat & DAT_CRC_FAIL)
data->error = -EILSEQ;
else if (stat & (RX_OVERRUN | TX_UNDERRUN))
data->error = -EIO;
if (!data->error)
data->bytes_xfered = data->blocks * data->blksz;
else
data->bytes_xfered = 0;
bfin_write_SDH_STATUS_CLR(DAT_END_STAT | DAT_TIMEOUT_STAT | \
DAT_CRC_FAIL_STAT | DAT_BLK_END_STAT | RX_OVERRUN | TX_UNDERRUN);
bfin_write_SDH_DATA_CTL(0);
SSYNC();
host->data = NULL;
if (host->mrq->stop) {
sdh_stop_clock(host);
sdh_start_cmd(host, host->mrq->stop);
} else {
sdh_finish_request(host, host->mrq);
}
return 1;
}
static void sdh_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdh_host *host = mmc_priv(mmc);
int ret = 0;
dev_dbg(mmc_dev(host->mmc), "%s enter, mrp:%p, cmd:%p\n", __func__, mrq, mrq->cmd);
WARN_ON(host->mrq != NULL);
spin_lock(&host->lock);
host->mrq = mrq;
host->data = mrq->data;
if (mrq->data && mrq->data->flags & MMC_DATA_READ) {
ret = sdh_setup_data(host, mrq->data);
if (ret)
goto data_err;
}
sdh_start_cmd(host, mrq->cmd);
data_err:
spin_unlock(&host->lock);
}
static void sdh_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct sdh_host *host;
u16 clk_ctl = 0;
#ifndef RSI_BLKSZ
u16 pwr_ctl = 0;
#endif
u16 cfg;
host = mmc_priv(mmc);
spin_lock(&host->lock);
cfg = bfin_read_SDH_CFG();
cfg |= MWE;
switch (ios->bus_width) {
case MMC_BUS_WIDTH_4:
#ifndef RSI_BLKSZ
cfg &= ~PD_SDDAT3;
#endif
cfg |= PUP_SDDAT3;
/* Enable 4 bit SDIO */
cfg |= SD4E;
clk_ctl |= WIDE_BUS_4;
break;
case MMC_BUS_WIDTH_8:
#ifndef RSI_BLKSZ
cfg &= ~PD_SDDAT3;
#endif
cfg |= PUP_SDDAT3;
/* Disable 4 bit SDIO */
cfg &= ~SD4E;
clk_ctl |= BYTE_BUS_8;
break;
default:
cfg &= ~PUP_SDDAT3;
/* Disable 4 bit SDIO */
cfg &= ~SD4E;
}
bfin_write_SDH_CFG(cfg);
host->power_mode = ios->power_mode;
#ifndef RSI_BLKSZ
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) {
pwr_ctl |= ROD_CTL;
# ifndef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
pwr_ctl |= SD_CMD_OD;
# endif
}
if (ios->power_mode != MMC_POWER_OFF)
pwr_ctl |= PWR_ON;
else
pwr_ctl &= ~PWR_ON;
bfin_write_SDH_PWR_CTL(pwr_ctl);
#else
# ifndef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
cfg |= SD_CMD_OD;
else
cfg &= ~SD_CMD_OD;
# endif
if (ios->power_mode != MMC_POWER_OFF)
cfg |= PWR_ON;
else
cfg &= ~PWR_ON;
bfin_write_SDH_CFG(cfg);
#endif
SSYNC();
if (ios->power_mode == MMC_POWER_ON && ios->clock) {
unsigned char clk_div;
clk_div = (get_sclk() / ios->clock - 1) / 2;
clk_div = min_t(unsigned char, clk_div, 0xFF);
clk_ctl |= clk_div;
clk_ctl |= CLK_E;
host->clk_div = clk_div;
bfin_write_SDH_CLK_CTL(clk_ctl);
} else
sdh_stop_clock(host);
/* set up sdh interrupt mask*/
if (ios->power_mode == MMC_POWER_ON)
bfin_write_SDH_MASK0(DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL |
RX_OVERRUN | TX_UNDERRUN | CMD_SENT | CMD_RESP_END |
CMD_TIME_OUT | CMD_CRC_FAIL);
else
bfin_write_SDH_MASK0(0);
SSYNC();
spin_unlock(&host->lock);
dev_dbg(mmc_dev(host->mmc), "SDH: clk_div = 0x%x actual clock:%ld expected clock:%d\n",
host->clk_div,
host->clk_div ? get_sclk() / (2 * (host->clk_div + 1)) : 0,
ios->clock);
}
static const struct mmc_host_ops sdh_ops = {
.request = sdh_request,
.set_ios = sdh_set_ios,
};
static irqreturn_t sdh_dma_irq(int irq, void *devid)
{
struct sdh_host *host = devid;
dev_dbg(mmc_dev(host->mmc), "%s enter, irq_stat: 0x%04lx\n", __func__,
get_dma_curr_irqstat(host->dma_ch));
clear_dma_irqstat(host->dma_ch);
SSYNC();
return IRQ_HANDLED;
}
static irqreturn_t sdh_stat_irq(int irq, void *devid)
{
struct sdh_host *host = devid;
unsigned int status;
int handled = 0;
dev_dbg(mmc_dev(host->mmc), "%s enter\n", __func__);
spin_lock(&host->lock);
status = bfin_read_SDH_E_STATUS();
if (status & SD_CARD_DET) {
mmc_detect_change(host->mmc, 0);
bfin_write_SDH_E_STATUS(SD_CARD_DET);
}
status = bfin_read_SDH_STATUS();
if (status & (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT | CMD_CRC_FAIL)) {
handled |= sdh_cmd_done(host, status);
bfin_write_SDH_STATUS_CLR(CMD_SENT_STAT | CMD_RESP_END_STAT | \
CMD_TIMEOUT_STAT | CMD_CRC_FAIL_STAT);
SSYNC();
}
status = bfin_read_SDH_STATUS();
if (status & (DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN | TX_UNDERRUN))
handled |= sdh_data_done(host, status);
spin_unlock(&host->lock);
dev_dbg(mmc_dev(host->mmc), "%s exit\n\n", __func__);
return IRQ_RETVAL(handled);
}
static void sdh_reset(void)
{
#if defined(CONFIG_BF54x)
/* Secure Digital Host shares DMA with Nand controller */
bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1);
#endif
bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN);
SSYNC();
/* Disable card inserting detection pin. set MMC_CAP_NEEDS_POLL, and
* mmc stack will do the detection.
*/
bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3));
SSYNC();
}
static int sdh_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct sdh_host *host;
struct bfin_sd_host *drv_data = get_sdh_data(pdev);
int ret;
if (!drv_data) {
dev_err(&pdev->dev, "missing platform driver data\n");
ret = -EINVAL;
goto out;
}
mmc = mmc_alloc_host(sizeof(struct sdh_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}
mmc->ops = &sdh_ops;
#if defined(CONFIG_BF51x)
mmc->max_segs = 1;
#else
mmc->max_segs = PAGE_SIZE / sizeof(struct dma_desc_array);
#endif
#ifdef RSI_BLKSZ
mmc->max_seg_size = -1;
#else
mmc->max_seg_size = 1 << 16;
#endif
mmc->max_blk_size = 1 << 11;
mmc->max_blk_count = 1 << 11;
mmc->max_req_size = PAGE_SIZE;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->f_max = get_sclk();
mmc->f_min = mmc->f_max >> 9;
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_NEEDS_POLL;
host = mmc_priv(mmc);
host->mmc = mmc;
host->sclk = get_sclk();
spin_lock_init(&host->lock);
host->irq = drv_data->irq_int0;
host->dma_ch = drv_data->dma_chan;
ret = request_dma(host->dma_ch, DRIVER_NAME "DMA");
if (ret) {
dev_err(&pdev->dev, "unable to request DMA channel\n");
goto out1;
}
ret = set_dma_callback(host->dma_ch, sdh_dma_irq, host);
if (ret) {
dev_err(&pdev->dev, "unable to request DMA irq\n");
goto out2;
}
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
if (host->sg_cpu == NULL) {
ret = -ENOMEM;
goto out2;
}
platform_set_drvdata(pdev, mmc);
ret = request_irq(host->irq, sdh_stat_irq, 0, "SDH Status IRQ", host);
if (ret) {
dev_err(&pdev->dev, "unable to request status irq\n");
goto out3;
}
ret = peripheral_request_list(drv_data->pin_req, DRIVER_NAME);
if (ret) {
dev_err(&pdev->dev, "unable to request peripheral pins\n");
goto out4;
}
sdh_reset();
mmc_add_host(mmc);
return 0;
out4:
free_irq(host->irq, host);
out3:
mmc_remove_host(mmc);
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
out2:
free_dma(host->dma_ch);
out1:
mmc_free_host(mmc);
out:
return ret;
}
static int sdh_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
if (mmc) {
struct sdh_host *host = mmc_priv(mmc);
mmc_remove_host(mmc);
sdh_stop_clock(host);
free_irq(host->irq, host);
free_dma(host->dma_ch);
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
mmc_free_host(mmc);
}
return 0;
}
#ifdef CONFIG_PM
static int sdh_suspend(struct platform_device *dev, pm_message_t state)
{
struct bfin_sd_host *drv_data = get_sdh_data(dev);
peripheral_free_list(drv_data->pin_req);
return 0;
}
static int sdh_resume(struct platform_device *dev)
{
struct bfin_sd_host *drv_data = get_sdh_data(dev);
int ret = 0;
ret = peripheral_request_list(drv_data->pin_req, DRIVER_NAME);
if (ret) {
dev_err(&dev->dev, "unable to request peripheral pins\n");
return ret;
}
sdh_reset();
return ret;
}
#else
# define sdh_suspend NULL
# define sdh_resume NULL
#endif
static struct platform_driver sdh_driver = {
.probe = sdh_probe,
.remove = sdh_remove,
.suspend = sdh_suspend,
.resume = sdh_resume,
.driver = {
.name = DRIVER_NAME,
},
};
module_platform_driver(sdh_driver);
MODULE_DESCRIPTION("Blackfin Secure Digital Host Driver");
MODULE_AUTHOR("Cliff Cai, Roy Huang");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,780 @@
/*
* cb710/mmc.c
*
* Copyright by Michał Mirosław, 2008-2009
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include "cb710-mmc.h"
static const u8 cb710_clock_divider_log2[8] = {
/* 1, 2, 4, 8, 16, 32, 128, 512 */
0, 1, 2, 3, 4, 5, 7, 9
};
#define CB710_MAX_DIVIDER_IDX \
(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
static const u8 cb710_src_freq_mhz[16] = {
33, 10, 20, 25, 30, 35, 40, 45,
50, 55, 60, 65, 70, 75, 80, 85
};
static void cb710_mmc_select_clock_divider(struct mmc_host *mmc, int hz)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
u32 src_freq_idx;
u32 divider_idx;
int src_hz;
/* on CB710 in HP nx9500:
* src_freq_idx == 0
* indexes 1-7 work as written in the table
* indexes 0,8-15 give no clock output
*/
pci_read_config_dword(pdev, 0x48, &src_freq_idx);
src_freq_idx = (src_freq_idx >> 16) & 0xF;
src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
break;
}
if (src_freq_idx)
divider_idx |= 0x8;
else if (divider_idx == 0)
divider_idx = 1;
cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
dev_dbg(cb710_slot_dev(slot),
"clock set to %d Hz, wanted %d Hz; src_freq_idx = %d, divider_idx = %d|%d\n",
src_hz >> cb710_clock_divider_log2[divider_idx & 7],
hz, src_freq_idx, divider_idx & 7, divider_idx & 8);
}
static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
unsigned short enable, unsigned short mask)
{
/* clear global IE
* - it gets set later if any interrupt sources are enabled */
mask |= CB710_MMC_IE_IRQ_ENABLE;
/* look like interrupt is fired whenever
* WORD[0x0C] & WORD[0x10] != 0;
* -> bit 15 port 0x0C seems to be global interrupt enable
*/
enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
& ~mask) | enable;
if (enable)
enable |= CB710_MMC_IE_IRQ_ENABLE;
cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
}
static void cb710_mmc_enable_irq(struct cb710_slot *slot,
unsigned short enable, unsigned short mask)
{
struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
unsigned long flags;
spin_lock_irqsave(&reader->irq_lock, flags);
/* this is the only thing irq_lock protects */
__cb710_mmc_enable_irq(slot, enable, mask);
spin_unlock_irqrestore(&reader->irq_lock, flags);
}
static void cb710_mmc_reset_events(struct cb710_slot *slot)
{
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
}
static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
{
if (enable)
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
CB710_MMC_C1_4BIT_DATA_BUS, 0);
else
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
0, CB710_MMC_C1_4BIT_DATA_BUS);
}
static int cb710_check_event(struct cb710_slot *slot, u8 what)
{
u16 status;
status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
/* it is just a guess, so log it */
dev_dbg(cb710_slot_dev(slot),
"CHECK : ignoring bit 6 in status %04X\n", status);
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
CB710_MMC_S0_FIFO_UNDERFLOW);
status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
}
if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
dev_dbg(cb710_slot_dev(slot),
"CHECK : returning EIO on status %04X\n", status);
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
CB710_MMC_S1_RESET);
return -EIO;
}
/* 'what' is a bit in MMC_STATUS1 */
if ((status >> 8) & what) {
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
return 1;
}
return 0;
}
static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
{
int err = 0;
unsigned limit = 2000000; /* FIXME: real timeout */
#ifdef CONFIG_CB710_DEBUG
u32 e, x;
e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
#endif
while (!(err = cb710_check_event(slot, what))) {
if (!--limit) {
cb710_dump_regs(cb710_slot_to_chip(slot),
CB710_DUMP_REGS_MMC);
err = -ETIMEDOUT;
break;
}
udelay(1);
}
#ifdef CONFIG_CB710_DEBUG
x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
limit = 2000000 - limit;
if (limit > 100)
dev_dbg(cb710_slot_dev(slot),
"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
limit, what, e, x);
#endif
return err < 0 ? err : 0;
}
static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
{
unsigned limit = 500000; /* FIXME: real timeout */
int err = 0;
#ifdef CONFIG_CB710_DEBUG
u32 e, x;
e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
#endif
while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
if (!--limit) {
cb710_dump_regs(cb710_slot_to_chip(slot),
CB710_DUMP_REGS_MMC);
err = -ETIMEDOUT;
break;
}
udelay(1);
}
#ifdef CONFIG_CB710_DEBUG
x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
limit = 500000 - limit;
if (limit > 100)
dev_dbg(cb710_slot_dev(slot),
"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
limit, mask, e, x);
#endif
return err;
}
static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
size_t count, size_t blocksize)
{
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
((count - 1) << 16)|(blocksize - 1));
dev_vdbg(cb710_slot_dev(slot), "set up for %zu block%s of %zu bytes\n",
count, count == 1 ? "" : "s", blocksize);
}
static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
{
/* without this, received data is prepended with 8-bytes of zeroes */
u32 r1, r2;
int ok = 0;
r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
& CB710_MMC_S0_FIFO_UNDERFLOW) {
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
CB710_MMC_S0_FIFO_UNDERFLOW);
ok = 1;
}
dev_dbg(cb710_slot_dev(slot),
"FIFO-read-hack: expected STATUS0 bit was %s\n",
ok ? "set." : "NOT SET!");
dev_dbg(cb710_slot_dev(slot),
"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
}
static int cb710_mmc_receive_pio(struct cb710_slot *slot,
struct sg_mapping_iter *miter, size_t dw_count)
{
if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
int err = cb710_wait_for_event(slot,
CB710_MMC_S1_PIO_TRANSFER_DONE);
if (err)
return err;
}
cb710_sg_dwiter_write_from_io(miter,
slot->iobase + CB710_MMC_DATA_PORT, dw_count);
return 0;
}
static bool cb710_is_transfer_size_supported(struct mmc_data *data)
{
return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
}
static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
{
struct sg_mapping_iter miter;
size_t len, blocks = data->blocks;
int err = 0;
/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
* except single 8B block */
if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
return -EINVAL;
sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG);
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
cb710_mmc_fifo_hack(slot);
while (blocks-- > 0) {
len = data->blksz;
while (len >= 16) {
err = cb710_mmc_receive_pio(slot, &miter, 4);
if (err)
goto out;
len -= 16;
}
if (!len)
continue;
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
len = (len >= 8) ? 4 : 2;
err = cb710_mmc_receive_pio(slot, &miter, len);
if (err)
goto out;
}
out:
sg_miter_stop(&miter);
return err;
}
static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
{
struct sg_mapping_iter miter;
size_t len, blocks = data->blocks;
int err = 0;
/* TODO: I don't know how/if the hardware handles multiple
* non-16B-boundary blocks */
if (unlikely(data->blocks > 1 && data->blksz & 15))
return -EINVAL;
sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG);
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
while (blocks-- > 0) {
len = (data->blksz + 15) >> 4;
do {
if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
& CB710_MMC_S2_FIFO_EMPTY)) {
err = cb710_wait_for_event(slot,
CB710_MMC_S1_PIO_TRANSFER_DONE);
if (err)
goto out;
}
cb710_sg_dwiter_read_to_io(&miter,
slot->iobase + CB710_MMC_DATA_PORT, 4);
} while (--len);
}
out:
sg_miter_stop(&miter);
return err;
}
static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
struct mmc_command *cmd)
{
unsigned int flags = cmd->flags;
u16 cb_flags = 0;
/* Windows driver returned 0 for commands for which no response
* is expected. It happened that there were only two such commands
* used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
* as well be a bug in that driver.
*
* Original driver set bit 14 for MMC/SD application
* commands. There's no difference 'on the wire' and
* it apparently works without it anyway.
*/
switch (flags & MMC_CMD_MASK) {
case MMC_CMD_AC: cb_flags = CB710_MMC_CMD_AC; break;
case MMC_CMD_ADTC: cb_flags = CB710_MMC_CMD_ADTC; break;
case MMC_CMD_BC: cb_flags = CB710_MMC_CMD_BC; break;
case MMC_CMD_BCR: cb_flags = CB710_MMC_CMD_BCR; break;
}
if (flags & MMC_RSP_BUSY)
cb_flags |= CB710_MMC_RSP_BUSY;
cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
cb_flags |= CB710_MMC_DATA_READ;
if (flags & MMC_RSP_PRESENT) {
/* Windows driver set 01 at bits 4,3 except for
* MMC_SET_BLOCKLEN where it set 10. Maybe the
* hardware can do something special about this
* command? The original driver looks buggy/incomplete
* anyway so we ignore this for now.
*
* I assume that 00 here means no response is expected.
*/
cb_flags |= CB710_MMC_RSP_PRESENT;
if (flags & MMC_RSP_136)
cb_flags |= CB710_MMC_RSP_136;
if (!(flags & MMC_RSP_CRC))
cb_flags |= CB710_MMC_RSP_NO_CRC;
}
return cb_flags;
}
static void cb710_receive_response(struct cb710_slot *slot,
struct mmc_command *cmd)
{
unsigned rsp_opcode, wanted_opcode;
/* Looks like final byte with CRC is always stripped (same as SDHCI) */
if (cmd->flags & MMC_RSP_136) {
u32 resp[4];
resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
rsp_opcode = resp[0] >> 24;
cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
cmd->resp[3] = (resp[3] << 8);
} else {
rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
}
wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
if (rsp_opcode != wanted_opcode)
cmd->error = -EILSEQ;
}
static int cb710_mmc_transfer_data(struct cb710_slot *slot,
struct mmc_data *data)
{
int error, to;
if (data->flags & MMC_DATA_READ)
error = cb710_mmc_receive(slot, data);
else
error = cb710_mmc_send(slot, data);
to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
if (!error)
error = to;
if (!error)
data->bytes_xfered = data->blksz * data->blocks;
return error;
}
static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
struct cb710_mmc_reader *reader = mmc_priv(mmc);
struct mmc_data *data = cmd->data;
u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
if (data) {
if (!cb710_is_transfer_size_supported(data)) {
data->error = -EINVAL;
return -1;
}
cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
}
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
cb710_mmc_reset_events(slot);
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
if (cmd->error)
return -1;
if (cmd->flags & MMC_RSP_PRESENT) {
cb710_receive_response(slot, cmd);
if (cmd->error)
return -1;
}
if (data)
data->error = cb710_mmc_transfer_data(slot, data);
return 0;
}
static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
struct cb710_mmc_reader *reader = mmc_priv(mmc);
WARN_ON(reader->mrq != NULL);
reader->mrq = mrq;
cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
cb710_mmc_command(mmc, mrq->stop);
tasklet_schedule(&reader->finish_req_tasklet);
}
static int cb710_mmc_powerup(struct cb710_slot *slot)
{
#ifdef CONFIG_CB710_DEBUG
struct cb710_chip *chip = cb710_slot_to_chip(slot);
#endif
int err;
/* a lot of magic for now */
dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
if (unlikely(err))
return err;
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
mdelay(1);
dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
if (unlikely(err))
return err;
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
mdelay(1);
dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
if (unlikely(err))
return err;
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
mdelay(2);
dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
if (unlikely(err))
return err;
/* This port behaves weird: quick byte reads of 0x08,0x09 return
* 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
* read/written from userspace... What am I missing here?
* (it doesn't depend on write-to-read delay) */
cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
return cb710_check_event(slot, 0);
}
static void cb710_mmc_powerdown(struct cb710_slot *slot)
{
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
}
static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
struct cb710_mmc_reader *reader = mmc_priv(mmc);
int err;
cb710_mmc_select_clock_divider(mmc, ios->clock);
if (ios->power_mode != reader->last_power_mode)
switch (ios->power_mode) {
case MMC_POWER_ON:
err = cb710_mmc_powerup(slot);
if (err) {
dev_warn(cb710_slot_dev(slot),
"powerup failed (%d)- retrying\n", err);
cb710_mmc_powerdown(slot);
udelay(1);
err = cb710_mmc_powerup(slot);
if (err)
dev_warn(cb710_slot_dev(slot),
"powerup retry failed (%d) - expect errors\n",
err);
}
reader->last_power_mode = MMC_POWER_ON;
break;
case MMC_POWER_OFF:
cb710_mmc_powerdown(slot);
reader->last_power_mode = MMC_POWER_OFF;
break;
case MMC_POWER_UP:
default:
/* ignore */;
}
cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
}
static int cb710_mmc_get_ro(struct mmc_host *mmc)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
& CB710_MMC_S3_WRITE_PROTECTED;
}
static int cb710_mmc_get_cd(struct mmc_host *mmc)
{
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
& CB710_MMC_S3_CARD_DETECTED;
}
static int cb710_mmc_irq_handler(struct cb710_slot *slot)
{
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
struct cb710_mmc_reader *reader = mmc_priv(mmc);
u32 status, config1, config2, irqen;
status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
"ie: %08X, c2: %08X, c1: %08X\n",
status, irqen, config2, config1);
if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
/* ack the event */
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
CB710_MMC_S1_CARD_CHANGED);
if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
== CB710_MMC_IE_CISTATUS_MASK)
mmc_detect_change(mmc, HZ/5);
} else {
dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
spin_lock(&reader->irq_lock);
__cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
spin_unlock(&reader->irq_lock);
}
return 1;
}
static void cb710_mmc_finish_request_tasklet(unsigned long data)
{
struct mmc_host *mmc = (void *)data;
struct cb710_mmc_reader *reader = mmc_priv(mmc);
struct mmc_request *mrq = reader->mrq;
reader->mrq = NULL;
mmc_request_done(mmc, mrq);
}
static const struct mmc_host_ops cb710_mmc_host = {
.request = cb710_mmc_request,
.set_ios = cb710_mmc_set_ios,
.get_ro = cb710_mmc_get_ro,
.get_cd = cb710_mmc_get_cd,
};
#ifdef CONFIG_PM
static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
{
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
cb710_mmc_enable_irq(slot, 0, ~0);
return 0;
}
static int cb710_mmc_resume(struct platform_device *pdev)
{
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
cb710_mmc_enable_irq(slot, 0, ~0);
return 0;
}
#endif /* CONFIG_PM */
static int cb710_mmc_init(struct platform_device *pdev)
{
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
struct cb710_chip *chip = cb710_slot_to_chip(slot);
struct mmc_host *mmc;
struct cb710_mmc_reader *reader;
int err;
u32 val;
mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
if (!mmc)
return -ENOMEM;
platform_set_drvdata(pdev, mmc);
/* harmless (maybe) magic */
pci_read_config_dword(chip->pdev, 0x48, &val);
val = cb710_src_freq_mhz[(val >> 16) & 0xF];
dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
val *= 1000000;
mmc->ops = &cb710_mmc_host;
mmc->f_max = val;
mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA;
reader = mmc_priv(mmc);
tasklet_init(&reader->finish_req_tasklet,
cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
spin_lock_init(&reader->irq_lock);
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
cb710_mmc_enable_irq(slot, 0, ~0);
cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
err = mmc_add_host(mmc);
if (unlikely(err))
goto err_free_mmc;
dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
mmc_hostname(mmc));
cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
return 0;
err_free_mmc:
dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
cb710_set_irq_handler(slot, NULL);
mmc_free_host(mmc);
return err;
}
static int cb710_mmc_exit(struct platform_device *pdev)
{
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
struct cb710_mmc_reader *reader = mmc_priv(mmc);
cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
mmc_remove_host(mmc);
/* IRQs should be disabled now, but let's stay on the safe side */
cb710_mmc_enable_irq(slot, 0, ~0);
cb710_set_irq_handler(slot, NULL);
/* clear config ports - just in case */
cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
tasklet_kill(&reader->finish_req_tasklet);
mmc_free_host(mmc);
return 0;
}
static struct platform_driver cb710_mmc_driver = {
.driver.name = "cb710-mmc",
.probe = cb710_mmc_init,
.remove = cb710_mmc_exit,
#ifdef CONFIG_PM
.suspend = cb710_mmc_suspend,
.resume = cb710_mmc_resume,
#endif
};
module_platform_driver(cb710_mmc_driver);
MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:cb710-mmc");

View file

@ -0,0 +1,104 @@
/*
* cb710/cb710-mmc.h
*
* Copyright by Michał Mirosław, 2008-2009
*
* 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 LINUX_CB710_MMC_H
#define LINUX_CB710_MMC_H
#include <linux/cb710.h>
/* per-MMC-reader structure */
struct cb710_mmc_reader {
struct tasklet_struct finish_req_tasklet;
struct mmc_request *mrq;
spinlock_t irq_lock;
unsigned char last_power_mode;
};
/* some device struct walking */
static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
{
return platform_get_drvdata(&slot->pdev);
}
static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
{
struct platform_device *pdev = container_of(mmc_dev(mmc),
struct platform_device, dev);
return cb710_pdev_to_slot(pdev);
}
/* registers (this might be all wrong ;) */
#define CB710_MMC_DATA_PORT 0x00
#define CB710_MMC_CONFIG_PORT 0x04
#define CB710_MMC_CONFIG0_PORT 0x04
#define CB710_MMC_CONFIG1_PORT 0x05
#define CB710_MMC_C1_4BIT_DATA_BUS 0x40
#define CB710_MMC_CONFIG2_PORT 0x06
#define CB710_MMC_C2_READ_PIO_SIZE_MASK 0x0F /* N-1 */
#define CB710_MMC_CONFIG3_PORT 0x07
#define CB710_MMC_CONFIGB_PORT 0x08
#define CB710_MMC_IRQ_ENABLE_PORT 0x0C
#define CB710_MMC_IE_TEST_MASK 0x00BF
#define CB710_MMC_IE_CARD_INSERTION_STATUS 0x1000
#define CB710_MMC_IE_IRQ_ENABLE 0x8000
#define CB710_MMC_IE_CISTATUS_MASK \
(CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
#define CB710_MMC_STATUS_PORT 0x10
#define CB710_MMC_STATUS_ERROR_EVENTS 0x60FF
#define CB710_MMC_STATUS0_PORT 0x10
#define CB710_MMC_S0_FIFO_UNDERFLOW 0x40
#define CB710_MMC_STATUS1_PORT 0x11
#define CB710_MMC_S1_COMMAND_SENT 0x01
#define CB710_MMC_S1_DATA_TRANSFER_DONE 0x02
#define CB710_MMC_S1_PIO_TRANSFER_DONE 0x04
#define CB710_MMC_S1_CARD_CHANGED 0x10
#define CB710_MMC_S1_RESET 0x20
#define CB710_MMC_STATUS2_PORT 0x12
#define CB710_MMC_S2_FIFO_READY 0x01
#define CB710_MMC_S2_FIFO_EMPTY 0x02
#define CB710_MMC_S2_BUSY_10 0x10
#define CB710_MMC_S2_BUSY_20 0x20
#define CB710_MMC_STATUS3_PORT 0x13
#define CB710_MMC_S3_CARD_DETECTED 0x02
#define CB710_MMC_S3_WRITE_PROTECTED 0x04
#define CB710_MMC_CMD_TYPE_PORT 0x14
#define CB710_MMC_RSP_TYPE_MASK 0x0007
#define CB710_MMC_RSP_R1 (0)
#define CB710_MMC_RSP_136 (5)
#define CB710_MMC_RSP_NO_CRC (2)
#define CB710_MMC_RSP_PRESENT_MASK 0x0018
#define CB710_MMC_RSP_NONE (0 << 3)
#define CB710_MMC_RSP_PRESENT (1 << 3)
#define CB710_MMC_RSP_PRESENT_X (2 << 3)
#define CB710_MMC_CMD_TYPE_MASK 0x0060
#define CB710_MMC_CMD_BC (0 << 5)
#define CB710_MMC_CMD_BCR (1 << 5)
#define CB710_MMC_CMD_AC (2 << 5)
#define CB710_MMC_CMD_ADTC (3 << 5)
#define CB710_MMC_DATA_READ 0x0080
#define CB710_MMC_CMD_CODE_MASK 0x3F00
#define CB710_MMC_CMD_CODE_SHIFT 8
#define CB710_MMC_IS_APP_CMD 0x4000
#define CB710_MMC_RSP_BUSY 0x8000
#define CB710_MMC_CMD_PARAM_PORT 0x18
#define CB710_MMC_TRANSFER_SIZE_PORT 0x1C
#define CB710_MMC_RESPONSE0_PORT 0x20
#define CB710_MMC_RESPONSE1_PORT 0x24
#define CB710_MMC_RESPONSE2_PORT 0x28
#define CB710_MMC_RESPONSE3_PORT 0x2C
#endif /* LINUX_CB710_MMC_H */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,247 @@
/*
* Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
*
* Copyright (C) 2012, Samsung Electronics Co., Ltd.
* Copyright (C) 2013, The Chromium OS Authors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef _EXYNOS_DWMCI_H_
#define _EXYNOS_DWMCI_H_
#define NUM_PINS(x) (x + 2)
#define MAX_TUNING_RETRIES 6
#define MAX_TUNING_LOOP (MAX_TUNING_RETRIES * 8 * 2)
#define EXYNOS_CCLKIN_MIN 25000000 /* unit: HZ */
/* Exynos implementation specific driver private data */
struct dw_mci_exynos_priv_data {
u8 ctrl_type;
u8 ciu_div;
u32 sdr_timing;
u32 sdr_hs_timing;
u32 ddr_timing;
u32 hs400_timing;
u32 tuned_sample;
u32 cur_speed;
u32 saved_dqs_en;
u32 saved_strobe_ctrl;
u32 hs200_timing;
u32 ddr200_timing;
u32 ddr200_ulp_timing;
u32 ddr200_tx_t_fastlimit;
u32 ddr200_tx_t_initval;
u32 sdr104_timing;
u32 sdr50_timing;
u32 *ref_clk;
u32 delay_line;
u32 tx_delay_line;
struct pinctrl *pinctrl;
u32 clk_drive_number;
u32 clk_drive_tuning;
struct pinctrl_state *clk_drive_base;
struct pinctrl_state *clk_drive_str[6];
int cd_gpio;
int sec_sd_slot_type;
#define SEC_NO_DET_SD_SLOT 0 /* No detect GPIO SD slot case */
#define SEC_HOTPLUG_SD_SLOT 1 /* detect GPIO SD slot without Tray */
#define SEC_HYBRID_SD_SLOT 2 /* detect GPIO SD slot with Tray */
u32 caps;
u32 ctrl_flag;
u32 ctrl_windows;
u32 ignore_phase;
u32 selclk_drv;
u32 voltage_int_extra;
#define DW_MMC_EXYNOS_USE_FINE_TUNING BIT(0)
#define DW_MMC_EXYNOS_BYPASS_FOR_ALL_PASS BIT(1)
#define DW_MMC_EXYNOS_ENABLE_SHIFT BIT(2)
};
extern int dw_mci_exynos_request_status(void);
extern void dw_mci_reg_dump(struct dw_mci *host);
/*****************/
/* SFR addresses */
/*****************/
#define SFR_OFFSET 0x0004
/*
* Registers to support idmac 64-bit address mode
*/
#define SDMMC_DBADDRL 0x0088
#define SDMMC_DBADDRU (SDMMC_DBADDRL + SFR_OFFSET)
#define SDMMC_IDSTS64 (SDMMC_DBADDRU + SFR_OFFSET)
#define SDMMC_IDINTEN64 (SDMMC_IDSTS64 + SFR_OFFSET)
#define SDMMC_DSCADDRL (SDMMC_IDINTEN64 + SFR_OFFSET)
#define SDMMC_DSCADDRU (SDMMC_DSCADDRL + SFR_OFFSET)
#define SDMMC_BUFADDRL (SDMMC_DSCADDRU + SFR_OFFSET)
#define SDMMC_BUFADDRU (SDMMC_BUFADDRL + SFR_OFFSET)
#if defined(CONFIG_MMC_DW_64BIT_DESC)
#define SDMMC_AXI_BURST_LEN 0x00b4
#define SDMMC_SECTOR_NUM_INC 0x01F8
#define SDMMC_CLKSEL (SDMMC_BUFADDRU + SFR_OFFSET) /* specific to Samsung Exynos */
#else
#define SDMMC_CLKSEL (SDMMC_BUFADDR + SFR_OFFSET) /* specific to Samsung Exynos */
#define SDMMC_AXI_BURST_LEN 0xffff /*not used*/
#define SDMMC_SECTOR_NUM_INC 0xffff /*not used*/
#endif
#define FORCE_SWRESET BIT(15)
#define SDMMC_CDTHRCTL 0x100
#define SDMMC_DATA(x) (x)
#define SDMMC_DDR200_ENABLE_SHIFT 0x110
#define SDMMC_DDR200_RDDQS_EN 0x180
#define SDMMC_DDR200_ASYNC_FIFO_CTRL 0x184
#define SDMMC_DDR200_DLINE_CTRL 0x188
/* Extended Register's Offset */
#define SDMMC_HS400_DQS_EN 0x180
#define SDMMC_HS400_ASYNC_FIFO_CTRL 0x184
#define SDMMC_HS400_DLINE_CTRL 0x188
#define SDMMC_SHA_CMD_IE 0x190
#define SDMMC_SHA_CMD_IS 0x194
#define SDMMC_DEBUG_INFO1 0x1A0
#define SDMMC_DEBUG_INFO2 0x1A4
#define SDMMC_EMMCP_BASE 0x1000
#define SDMMC_MPSTAT (SDMMC_EMMCP_BASE + 0x0008)
#define SDMMC_MPSECURITY (SDMMC_EMMCP_BASE + 0x0010)
#define SDMMC_MPENCKEY (SDMMC_EMMCP_BASE + 0x0020)
#define SDMMC_MPSBEGIN0 (SDMMC_EMMCP_BASE + 0x0200)
#define SDMMC_MPSEND0 (SDMMC_EMMCP_BASE + 0x0204)
#define SDMMC_MPSLUN0 (SDMMC_EMMCP_BASE + 0x0208)
#define SDMMC_MPSCTRL0 (SDMMC_EMMCP_BASE + 0x020C)
#define SDMMC_MPSBEGIN1 (SDMMC_EMMCP_BASE + 0x0210)
#define SDMMC_MPSEND1 (SDMMC_EMMCP_BASE + 0x0214)
#define SDMMC_MPSCTRL1 (SDMMC_EMMCP_BASE + 0x021C)
#define SDMMC_FORCE_CLK_STOP 0x0b0
/******************/
/* Derived macros */
/******************/
/* SDMMC_CLKSEL */
#define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0)
#define SDMMC_CLKSEL_CCLK_FINE_SAMPLE(x) (((x) & 0xF) << 0)
#define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16)
#define SDMMC_CLKSEL_CCLK_FINE_DRIVE(x) (((x) & 3) << 22)
#define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24)
#define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7)
#define SDMMC_CLKSEL_GET_DIVRATIO(x) ((((x) >> 24) & 0x7) + 1)
#define SDMMC_CLKSEL_TIMING(div, f_drv, drv, sample) \
(SDMMC_CLKSEL_CCLK_DIVIDER(div) | \
SDMMC_CLKSEL_CCLK_FINE_DRIVE(f_drv) | \
SDMMC_CLKSEL_CCLK_DRIVE(drv) | \
SDMMC_CLKSEL_CCLK_SAMPLE(sample))
#define SDMMC_CLKSEL_GET_DIV(x) (((x) >> 24) & 0x7)
#define SDMMC_CLKSEL_UP_SAMPLE(x, y) (((x) & ~SDMMC_CLKSEL_CCLK_SAMPLE(7)) |\
SDMMC_CLKSEL_CCLK_SAMPLE(y))
#define SDMMC_CLKSEL_TIMING_MASK SDMMC_CLKSEL_TIMING(0x7, 0x7, 0x7 ,0x7)
/* RCLK_EN register defines */
#define DATA_STROBE_EN BIT(0)
#define AXI_NON_BLOCKING_WR BIT(7)
/* SDMMC_DDR200_RDDQS_EN */
#define DWMCI_TXDT_CRC_TIMER_FASTLIMIT(x) (((x) & 0xFF) << 16)
#define DWMCI_TXDT_CRC_TIMER_INITVAL(x) (((x) & 0xFF) << 8)
#define DWMCI_TXDT_CRC_TIMER_SET(x, y) (DWMCI_TXDT_CRC_TIMER_FASTLIMIT(x) | \
DWMCI_TXDT_CRC_TIMER_INITVAL(y))
#define DWMCI_AXI_NON_BLOCKING_WRITE BIT(7)
#define DWMCI_RESP_RCLK_MODE BIT(5)
#define DWMCI_BUSY_CHK_CLK_STOP_EN BIT(2)
#define DWMCI_RXDATA_START_BIT_SEL BIT(1)
#define DWMCI_RDDQS_EN BIT(0)
#define DWMCI_DDR200_RDDQS_EN_DEF (DWMCI_TXDT_CRC_TIMER_FASTLIMIT(0x13) | \
DWMCI_TXDT_CRC_TIMER_INITVAL(0x15))
/* SDMMC_DDR200_ASYNC_FIFO_CTRL */
#define DWMCI_ASYNC_FIFO_RESET BIT(0)
/* SDMMC_DDR200_DLINE_CTRL */
#define DWMCI_WD_DQS_DELAY_CTRL(x) (((x) & 0x3FF) << 20)
#define DWMCI_FIFO_CLK_DELAY_CTRL(x) (((x) & 0x3) << 16)
#define DWMCI_RD_DQS_DELAY_CTRL(x) ((x) & 0x3FF)
#define DWMCI_DDR200_DLINE_CTRL_SET(x, y, z) (DWMCI_WD_DQS_DELAY_CTRL(x) | \
DWMCI_FIFO_CLK_DELAY_CTRL(y) | \
DWMCI_RD_DQS_DELAY_CTRL(z))
#define DWMCI_DDR200_DLINE_CTRL_DEF (DWMCI_FIFO_CLK_DELAY_CTRL(0x2) | \
DWMCI_RD_DQS_DELAY_CTRL(0x40))
/* SDMMC_SECTOR_NUM_INC */
#define DWMCI_BURST_LENGTH_MASK (0xF)
#define DWMCI_BURST_LENGTH_CTRL(x) (((x)&DWMCI_BURST_LENGTH_MASK) | \
(((x)&DWMCI_BURST_LENGTH_MASK)<<16))
/* SDMMC_SECTOR_NUM_INC */
#define DWMCI_SECTOR_SIZE_MASK (0x1FFF)
#define DWMCI_SECTOR_SIZE_CTRL(x) ((x)&DWMCI_SECTOR_SIZE_MASK)
/* SDMMC_DDR200_ENABLE_SHIFT */
#define DWMCI_ENABLE_SHIFT_MASK (0x3)
#define DWMCI_ENABLE_SHIFT(x) ((x) & DWMCI_ENABLE_SHIFT_MASK)
/* Block number in eMMC */
#define DWMCI_BLOCK_NUM 0xFFFFFFFF
/* SMU control bits */
#define DWMCI_MPSCTRL_SECURE_READ_BIT BIT(7)
#define DWMCI_MPSCTRL_SECURE_WRITE_BIT BIT(6)
#define DWMCI_MPSCTRL_NON_SECURE_READ_BIT BIT(5)
#define DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT BIT(4)
#define DWMCI_MPSCTRL_USE_FUSE_KEY BIT(3)
#define DWMCI_MPSCTRL_ECB_MODE BIT(2)
#define DWMCI_MPSCTRL_ENCRYPTION BIT(1)
#define DWMCI_MPSCTRL_VALID BIT(0)
#define DWMCI_MPSCTRL_BYPASS (DWMCI_MPSCTRL_SECURE_READ_BIT |\
DWMCI_MPSCTRL_SECURE_WRITE_BIT |\
DWMCI_MPSCTRL_NON_SECURE_READ_BIT |\
DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT |\
DWMCI_MPSCTRL_VALID)
#define SDMMC_CLKSEL_WAKEUP_INT BIT(11)
#define EXYNOS4210_FIXED_CIU_CLK_DIV 2
#define EXYNOS4412_FIXED_CIU_CLK_DIV 4
#define HS400_FIXED_CIU_CLK_DIV 1
/* FMP SECURITY bits */
#define DWMCI_MPSECURITY_PROTBYTZPC BIT(31)
#define DWMCI_MPSECURITY_MMC_SFR_PROT_ON BIT(29)
#define DWMCI_MPSECURITY_FMP_ENC_ON BIT(28)
#define DWMCI_MPSECURITY_DESCTYPE(type) ((type & 0x3) << 19)
/* FMP configuration */
#define DW_MMC_BYPASS_SECTOR_BEGIN 0x0
#define DW_MMC_ENCRYPTION_SECTOR_BEGIN 0x0000FFFF
#define DW_MMC_FILE_ENCRYPTION_SECTOR_BEGIN 0xFFFF0000
/* FMP logical unit number */
#define DW_MMC_LUN_BEGIN 0
#define DW_MMC_LUN_END 0xff
#define DWMCI_SET_LUN(s, e) \
(((s & 0xff) << 16) | (e & 0xff))
/* HWACG Control */
#define MMC_HWACG_CONTROL BIT(4)
#define HWACG_Q_ACTIVE_EN 1
#define HWACG_Q_ACTIVE_DIS 0
#endif /* _EXYNOS_DWMCI_H_ */

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2013 Linaro Ltd.
* Copyright (c) 2013 Hisilicon Limited.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
#include "dw_mmc.h"
#include "dw_mmc-pltfm.h"
static void dw_mci_k3_set_ios(struct dw_mci *host, struct mmc_ios *ios)
{
int ret;
ret = clk_set_rate(host->ciu_clk, ios->clock);
if (ret)
dev_warn(host->dev, "failed to set rate %uHz\n", ios->clock);
host->bus_hz = clk_get_rate(host->ciu_clk);
}
static const struct dw_mci_drv_data k3_drv_data = {
.set_ios = dw_mci_k3_set_ios,
};
static const struct of_device_id dw_mci_k3_match[] = {
{ .compatible = "hisilicon,hi4511-dw-mshc", .data = &k3_drv_data, },
{},
};
MODULE_DEVICE_TABLE(of, dw_mci_k3_match);
static int dw_mci_k3_probe(struct platform_device *pdev)
{
const struct dw_mci_drv_data *drv_data;
const struct of_device_id *match;
match = of_match_node(dw_mci_k3_match, pdev->dev.of_node);
drv_data = match->data;
return dw_mci_pltfm_register(pdev, drv_data);
}
#ifdef CONFIG_PM_SLEEP
static int dw_mci_k3_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
int ret;
ret = dw_mci_suspend(host);
if (!ret)
clk_disable_unprepare(host->ciu_clk);
return ret;
}
static int dw_mci_k3_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(host->ciu_clk);
if (ret) {
dev_err(host->dev, "failed to enable ciu clock\n");
return ret;
}
return dw_mci_resume(host);
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);
static struct platform_driver dw_mci_k3_pltfm_driver = {
.probe = dw_mci_k3_probe,
.remove = dw_mci_pltfm_remove,
.driver = {
.name = "dwmmc_k3",
.of_match_table = dw_mci_k3_match,
.pm = &dw_mci_k3_pmops,
},
};
module_platform_driver(dw_mci_k3_pltfm_driver);
MODULE_DESCRIPTION("K3 Specific DW-MSHC Driver Extension");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dwmmc-k3");

View file

@ -0,0 +1,122 @@
/*
* Synopsys DesignWare Multimedia Card PCI Interface driver
*
* Copyright (C) 2012 Vayavya Labs Pvt. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/dw_mmc.h>
#include "dw_mmc.h"
#define PCI_BAR_NO 2
#define SYNOPSYS_DW_MCI_VENDOR_ID 0x700
#define SYNOPSYS_DW_MCI_DEVICE_ID 0x1107
/* Defining the Capabilities */
#define DW_MCI_CAPABILITIES (MMC_CAP_4_BIT_DATA | MMC_CAP_MMC_HIGHSPEED |\
MMC_CAP_SD_HIGHSPEED | MMC_CAP_8_BIT_DATA |\
MMC_CAP_SDIO_IRQ)
static struct dw_mci_board pci_board_data = {
.num_slots = 1,
.caps = DW_MCI_CAPABILITIES,
.bus_hz = 33 * 1000 * 1000,
.detect_delay_ms = 200,
.fifo_depth = 32,
};
static int dw_mci_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *entries)
{
struct dw_mci *host;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->irq = pdev->irq;
host->irq_flags = IRQF_SHARED;
host->dev = &pdev->dev;
host->pdata = &pci_board_data;
ret = pcim_iomap_regions(pdev, 1 << PCI_BAR_NO, pci_name(pdev));
if (ret)
return ret;
host->regs = pcim_iomap_table(pdev)[PCI_BAR_NO];
pci_set_master(pdev);
ret = dw_mci_probe(host);
if (ret)
return ret;
pci_set_drvdata(pdev, host);
return 0;
}
static void dw_mci_pci_remove(struct pci_dev *pdev)
{
struct dw_mci *host = pci_get_drvdata(pdev);
dw_mci_remove(host);
}
#ifdef CONFIG_PM_SLEEP
static int dw_mci_pci_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct dw_mci *host = pci_get_drvdata(pdev);
return dw_mci_suspend(host);
}
static int dw_mci_pci_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct dw_mci *host = pci_get_drvdata(pdev);
return dw_mci_resume(host);
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(dw_mci_pci_pmops, dw_mci_pci_suspend, dw_mci_pci_resume);
static const struct pci_device_id dw_mci_pci_id[] = {
{ PCI_DEVICE(SYNOPSYS_DW_MCI_VENDOR_ID, SYNOPSYS_DW_MCI_DEVICE_ID) },
{}
};
MODULE_DEVICE_TABLE(pci, dw_mci_pci_id);
static struct pci_driver dw_mci_pci_driver = {
.name = "dw_mmc_pci",
.id_table = dw_mci_pci_id,
.probe = dw_mci_pci_probe,
.remove = dw_mci_pci_remove,
.driver = {
.pm = &dw_mci_pci_pmops
},
};
module_pci_driver(dw_mci_pci_driver);
MODULE_DESCRIPTION("DW Multimedia Card PCI Interface driver");
MODULE_AUTHOR("Shashidhar Hiremath <shashidharh@vayavyalabs.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,134 @@
/*
* Synopsys DesignWare Multimedia Card Interface driver
*
* Copyright (C) 2009 NXP Semiconductors
* Copyright (C) 2009, 2010 Imagination Technologies Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of.h>
#include <linux/clk.h>
#include "dw_mmc.h"
#include "dw_mmc-pltfm.h"
static void dw_mci_pltfm_prepare_command(struct dw_mci *host, u32 *cmdr)
{
*cmdr |= SDMMC_CMD_USE_HOLD_REG;
}
static const struct dw_mci_drv_data socfpga_drv_data = {
.prepare_command = dw_mci_pltfm_prepare_command,
};
int dw_mci_pltfm_register(struct platform_device *pdev,
const struct dw_mci_drv_data *drv_data)
{
struct dw_mci *host;
struct resource *regs;
host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->irq = platform_get_irq(pdev, 0);
if (host->irq < 0)
return host->irq;
host->drv_data = drv_data;
host->dev = &pdev->dev;
host->irq_flags = 0;
host->pdata = pdev->dev.platform_data;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->regs = devm_ioremap_resource(&pdev->dev, regs);
if (IS_ERR(host->regs))
return PTR_ERR(host->regs);
platform_set_drvdata(pdev, host);
return dw_mci_probe(host);
}
EXPORT_SYMBOL_GPL(dw_mci_pltfm_register);
#ifdef CONFIG_PM_SLEEP
/*
* TODO: we should probably disable the clock to the card in the suspend path.
*/
static int dw_mci_pltfm_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
return dw_mci_suspend(host);
}
static int dw_mci_pltfm_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
return dw_mci_resume(host);
}
#endif /* CONFIG_PM_SLEEP */
SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume);
EXPORT_SYMBOL_GPL(dw_mci_pltfm_pmops);
static const struct of_device_id dw_mci_pltfm_match[] = {
{ .compatible = "snps,dw-mshc", },
{ .compatible = "altr,socfpga-dw-mshc",
.data = &socfpga_drv_data },
{},
};
MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match);
static int dw_mci_pltfm_probe(struct platform_device *pdev)
{
const struct dw_mci_drv_data *drv_data = NULL;
const struct of_device_id *match;
if (pdev->dev.of_node) {
match = of_match_node(dw_mci_pltfm_match, pdev->dev.of_node);
drv_data = match->data;
}
return dw_mci_pltfm_register(pdev, drv_data);
}
int dw_mci_pltfm_remove(struct platform_device *pdev)
{
struct dw_mci *host = platform_get_drvdata(pdev);
dw_mci_remove(host);
return 0;
}
EXPORT_SYMBOL_GPL(dw_mci_pltfm_remove);
static struct platform_driver dw_mci_pltfm_driver = {
.probe = dw_mci_pltfm_probe,
.remove = dw_mci_pltfm_remove,
.driver = {
.name = "dw_mmc",
.of_match_table = dw_mci_pltfm_match,
.pm = &dw_mci_pltfm_pmops,
},
};
module_platform_driver(dw_mci_pltfm_driver);
MODULE_DESCRIPTION("DW Multimedia Card Interface driver");
MODULE_AUTHOR("NXP Semiconductor VietNam");
MODULE_AUTHOR("Imagination Technologies Ltd");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,20 @@
/*
* Synopsys DesignWare Multimedia Card Interface Platform driver
*
* Copyright (C) 2012, Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef _DW_MMC_PLTFM_H_
#define _DW_MMC_PLTFM_H_
extern int dw_mci_pltfm_register(struct platform_device *pdev,
const struct dw_mci_drv_data *drv_data);
extern int dw_mci_pltfm_remove(struct platform_device *pdev);
extern const struct dev_pm_ops dw_mci_pltfm_pmops;
#endif /* _DW_MMC_PLTFM_H_ */

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
#include "dw_mmc.h"
#include "dw_mmc-pltfm.h"
#define RK3288_CLKGEN_DIV 2
static void dw_mci_rockchip_prepare_command(struct dw_mci *host, u32 *cmdr)
{
*cmdr |= SDMMC_CMD_USE_HOLD_REG;
}
static int dw_mci_rk3288_setup_clock(struct dw_mci *host)
{
host->bus_hz /= RK3288_CLKGEN_DIV;
return 0;
}
static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios)
{
int ret;
unsigned int cclkin;
u32 bus_hz;
/*
* cclkin: source clock of mmc controller
* bus_hz: card interface clock generated by CLKGEN
* bus_hz = cclkin / RK3288_CLKGEN_DIV
* ios->clock = (div == 0) ? bus_hz : (bus_hz / (2 * div))
*
* Note: div can only be 0 or 1
* if DDR50 8bit mode(only emmc work in 8bit mode),
* div must be set 1
*/
if (ios->bus_width == MMC_BUS_WIDTH_8 &&
ios->timing == MMC_TIMING_MMC_DDR52)
cclkin = 2 * ios->clock * RK3288_CLKGEN_DIV;
else
cclkin = ios->clock * RK3288_CLKGEN_DIV;
ret = clk_set_rate(host->ciu_clk, cclkin);
if (ret)
dev_warn(host->dev, "failed to set rate %uHz\n", ios->clock);
bus_hz = clk_get_rate(host->ciu_clk) / RK3288_CLKGEN_DIV;
if (bus_hz != host->bus_hz) {
host->bus_hz = bus_hz;
/* force dw_mci_setup_bus() */
host->current_speed = 0;
}
}
static const struct dw_mci_drv_data rk2928_drv_data = {
.prepare_command = dw_mci_rockchip_prepare_command,
};
static const struct dw_mci_drv_data rk3288_drv_data = {
.prepare_command = dw_mci_rockchip_prepare_command,
.set_ios = dw_mci_rk3288_set_ios,
.setup_clock = dw_mci_rk3288_setup_clock,
};
static const struct of_device_id dw_mci_rockchip_match[] = {
{ .compatible = "rockchip,rk2928-dw-mshc",
.data = &rk2928_drv_data },
{ .compatible = "rockchip,rk3288-dw-mshc",
.data = &rk3288_drv_data },
{},
};
MODULE_DEVICE_TABLE(of, dw_mci_rockchip_match);
static int dw_mci_rockchip_probe(struct platform_device *pdev)
{
const struct dw_mci_drv_data *drv_data;
const struct of_device_id *match;
if (!pdev->dev.of_node)
return -ENODEV;
match = of_match_node(dw_mci_rockchip_match, pdev->dev.of_node);
drv_data = match->data;
return dw_mci_pltfm_register(pdev, drv_data);
}
#ifdef CONFIG_PM_SLEEP
static int dw_mci_rockchip_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
return dw_mci_suspend(host);
}
static int dw_mci_rockchip_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
return dw_mci_resume(host);
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(dw_mci_rockchip_pmops,
dw_mci_rockchip_suspend,
dw_mci_rockchip_resume);
static struct platform_driver dw_mci_rockchip_pltfm_driver = {
.probe = dw_mci_rockchip_probe,
.remove = __exit_p(dw_mci_pltfm_remove),
.driver = {
.name = "dwmmc_rockchip",
.of_match_table = dw_mci_rockchip_match,
.pm = &dw_mci_rockchip_pmops,
},
};
module_platform_driver(dw_mci_rockchip_pltfm_driver);
MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Specific DW-MSHC Driver Extension");
MODULE_ALIAS("platform:dwmmc-rockchip");
MODULE_LICENSE("GPL v2");

4116
drivers/mmc/host/dw_mmc.c Normal file

File diff suppressed because it is too large Load diff

471
drivers/mmc/host/dw_mmc.h Normal file
View file

@ -0,0 +1,471 @@
/*
* Synopsys DesignWare Multimedia Card Interface driver
* (Based on NXP driver for lpc 31xx)
*
* Copyright (C) 2009 NXP Semiconductors
* Copyright (C) 2009, 2010 Imagination Technologies Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef _DW_MMC_H_
#define _DW_MMC_H_
#define DW_MMC_MAX_TRANSFER_SIZE 4096
#define DW_MMC_SECTOR_SIZE 512
#ifdef CONFIG_MMC_DW_FMP_DM_CRYPT
#define MMC_DW_IDMAC_MULTIPLIER \
(DW_MMC_MAX_TRANSFER_SIZE / DW_MMC_SECTOR_SIZE)
#else
#define MMC_DW_IDMAC_MULTIPLIER 1
#endif
#define DW_MMC_240A 0x240a
#define DW_MMC_260A 0x260a
#define SDMMC_CTRL 0x000
#define SDMMC_PWREN 0x004
#define SDMMC_CLKDIV 0x008
#define SDMMC_CLKSRC 0x00c
#define SDMMC_CLKENA 0x010
#define SDMMC_TMOUT 0x014
#define SDMMC_CTYPE 0x018
#define SDMMC_BLKSIZ 0x01c
#define SDMMC_BYTCNT 0x020
#define SDMMC_INTMASK 0x024
#define SDMMC_CMDARG 0x028
#define SDMMC_CMD 0x02c
#define SDMMC_RESP0 0x030
#define SDMMC_RESP1 0x034
#define SDMMC_RESP2 0x038
#define SDMMC_RESP3 0x03c
#define SDMMC_MINTSTS 0x040
#define SDMMC_RINTSTS 0x044
#define SDMMC_STATUS 0x048
#define SDMMC_FIFOTH 0x04c
#define SDMMC_CDETECT 0x050
#define SDMMC_WRTPRT 0x054
#define SDMMC_GPIO 0x058
#define SDMMC_TCBCNT 0x05c
#define SDMMC_TBBCNT 0x060
#define SDMMC_DEBNCE 0x064
#define SDMMC_USRID 0x068
#define SDMMC_VERID 0x06c
#define SDMMC_HCON 0x070
#define SDMMC_UHS_REG 0x074
#define SDMMC_BMOD 0x080
#define SDMMC_PLDMND 0x084
#define SDMMC_DBADDR 0x088
#define SDMMC_IDSTS 0x08c
#define SDMMC_IDINTEN 0x090
#define SDMMC_DSCADDR 0x094
#define SDMMC_BUFADDR 0x098
#define SDMMC_RESP_TAT 0x0AC
#define SDMMC_CDTHRCTL 0x100
#define SDMMC_EMMC_DDR_REG 0x10C
#define SDMMC_DATA(x) (x)
/*
* Data offset is difference according to Version
* Lower than 2.40a : data register offest is 0x100
*/
#define DATA_OFFSET 0x100
#define DATA_240A_OFFSET 0x200
/* shift bit field */
#define _SBF(f, v) ((v) << (f))
/* Control register defines */
#define SDMMC_CTRL_USE_IDMAC BIT(25)
#define SDMMC_CTRL_CEATA_INT_EN BIT(11)
#define SDMMC_CTRL_SEND_AS_CCSD BIT(10)
#define SDMMC_CTRL_SEND_CCSD BIT(9)
#define SDMMC_CTRL_ABRT_READ_DATA BIT(8)
#define SDMMC_CTRL_SEND_IRQ_RESP BIT(7)
#define SDMMC_CTRL_READ_WAIT BIT(6)
#define SDMMC_CTRL_DMA_ENABLE BIT(5)
#define SDMMC_CTRL_INT_ENABLE BIT(4)
#define SDMMC_CTRL_DMA_RESET BIT(2)
#define SDMMC_CTRL_FIFO_RESET BIT(1)
#define SDMMC_CTRL_RESET BIT(0)
/* Clock Enable register defines */
#define SDMMC_CLKEN_LOW_PWR BIT(16)
#define SDMMC_CLKEN_ENABLE BIT(0)
/* time-out register defines */
#define SDMMC_TMOUT_DATA(n) _SBF(8, (n))
#define SDMMC_TMOUT_DATA_MSK 0xFFFFFF00
#define SDMMC_TMOUT_RESP(n) ((n) & 0xFF)
#define SDMMC_TMOUT_RESP_MSK 0xFF
/* card-type register defines */
#define SDMMC_CTYPE_8BIT BIT(16)
#define SDMMC_CTYPE_4BIT BIT(0)
#define SDMMC_CTYPE_1BIT 0
/* Interrupt status & mask register defines */
#define SDMMC_INT_SDIO(n) BIT(16 + (n))
#define SDMMC_INT_EBE BIT(15)
#define SDMMC_INT_ACD BIT(14)
#define SDMMC_INT_SBE BIT(13)
#define SDMMC_INT_HLE BIT(12)
#define SDMMC_INT_FRUN BIT(11)
#define SDMMC_INT_HTO BIT(10)
#define SDMMC_INT_VOLT_SWITCH BIT(10) /* overloads bit 10! */
#define SDMMC_INT_DRTO BIT(9)
#define SDMMC_INT_RTO BIT(8)
#define SDMMC_INT_DCRC BIT(7)
#define SDMMC_INT_RCRC BIT(6)
#define SDMMC_INT_RXDR BIT(5)
#define SDMMC_INT_TXDR BIT(4)
#define SDMMC_INT_DATA_OVER BIT(3)
#define SDMMC_INT_CMD_DONE BIT(2)
#define SDMMC_INT_RESP_ERR BIT(1)
#define SDMMC_INT_CD BIT(0)
#define SDMMC_INT_ERROR 0xbfc2
/* Command register defines */
#define SDMMC_CMD_START BIT(31)
#define SDMMC_CMD_USE_HOLD_REG BIT(29)
#define SDMMC_CMD_VOLT_SWITCH BIT(28)
#define SDMMC_CMD_CCS_EXP BIT(23)
#define SDMMC_CMD_CEATA_RD BIT(22)
#define SDMMC_CMD_UPD_CLK BIT(21)
#define SDMMC_CMD_INIT BIT(15)
#define SDMMC_CMD_STOP BIT(14)
#define SDMMC_CMD_PRV_DAT_WAIT BIT(13)
#define SDMMC_CMD_SEND_STOP BIT(12)
#define SDMMC_CMD_STRM_MODE BIT(11)
#define SDMMC_CMD_DAT_WR BIT(10)
#define SDMMC_CMD_DAT_EXP BIT(9)
#define SDMMC_CMD_RESP_CRC BIT(8)
#define SDMMC_CMD_RESP_LONG BIT(7)
#define SDMMC_CMD_RESP_EXP BIT(6)
#define SDMMC_CMD_INDX(n) ((n) & 0x1F)
/* Status register defines */
#define SDMMC_GET_FCNT(x) (((x)>>17) & 0x1FFF)
#define SDMMC_STATUS_DMA_REQ BIT(31)
#define SDMMC_STATUS_BUSY BIT(9)
/* FIFOTH register defines */
#define SDMMC_SET_FIFOTH(m, r, t) (((m) & 0x7) << 28 | \
((r) & 0xFFF) << 16 | \
((t) & 0xFFF))
#define SDMMC_FIFOTH_DMA_MULTI_TRANS_SIZE 28
#define SDMMC_FIFOTH_RX_WMARK 16
/* Internal DMAC interrupt defines */
#define SDMMC_IDMAC_INT_AI BIT(9)
#define SDMMC_IDMAC_INT_NI BIT(8)
#define SDMMC_IDMAC_INT_CES BIT(5)
#define SDMMC_IDMAC_INT_DU BIT(4)
#define SDMMC_IDMAC_INT_FBE BIT(2)
#define SDMMC_IDMAC_INT_RI BIT(1)
#define SDMMC_IDMAC_INT_TI BIT(0)
/* Internal DMAC bus mode bits */
#define SDMMC_IDMAC_ENABLE BIT(7)
#define SDMMC_IDMAC_FB BIT(1)
#define SDMMC_IDMAC_SWRESET BIT(0)
/* Version ID register define */
#define SDMMC_GET_VERID(x) ((x) & 0xFFFF)
/* Card read threshold */
#define SDMMC_SET_RD_THLD(v, x) (((v) & 0x1FFF) << 16 | (x))
#define SDMMC_UHS_18V BIT(0)
/* All ctrl reset bits */
#define SDMMC_CTRL_ALL_RESET_FLAGS \
(SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET)
/* Register access macros */
#define mci_readl(dev, reg) \
__raw_readl((dev)->regs + SDMMC_##reg)
#define mci_writel(dev, reg, value) \
__raw_writel((value), (dev)->regs + SDMMC_##reg)
/* timeout */
#define dw_mci_set_timeout(host, value) mci_writel(host, TMOUT, value)
/* 16-bit FIFO access macros */
#define mci_readw(dev, reg) \
__raw_readw((dev)->regs + SDMMC_##reg)
#define mci_writew(dev, reg, value) \
__raw_writew((value), (dev)->regs + SDMMC_##reg)
/* 64-bit FIFO access macros */
#ifdef readq
#ifdef CONFIG_MMC_DW_FORCE_32BIT_SFR_RW
#define mci_readq(dev, reg) ({\
u64 __ret = 0;\
u32* ptr = (u32*)&__ret;\
*ptr++ = __raw_readl((dev)->regs + SDMMC_##reg);\
*ptr = __raw_readl((dev)->regs + SDMMC_##reg + 0x4);\
__ret;\
})
#define mci_writeq(dev, reg, value) ({\
u32 *ptr = (u32*)&(value);\
__raw_writel(*ptr++, (dev)->regs + SDMMC_##reg);\
__raw_writel(*ptr, (dev)->regs + SDMMC_##reg + 0x4);\
})
#else
#define mci_readq(dev, reg) \
__raw_readq((dev)->regs + SDMMC_##reg)
#define mci_writeq(dev, reg, value) \
__raw_writeq((value), (dev)->regs + SDMMC_##reg)
#endif /* CONFIG_MMC_DW_FORCE_32BIT_SFR_RW */
#else
/*
* Dummy readq implementation for architectures that don't define it.
*
* We would assume that none of these architectures would configure
* the IP block with a 64bit FIFO width, so this code will never be
* executed on those machines. Defining these macros here keeps the
* rest of the code free from ifdefs.
*/
#define mci_readq(dev, reg) \
(*(volatile u64 __force *)((dev)->regs + SDMMC_##reg))
#define mci_writeq(dev, reg, value) \
(*(volatile u64 __force *)((dev)->regs + SDMMC_##reg) = (value))
#endif
/*
* platform-dependent miscellaneous control
*
* Input arguments for platform-dependent control may be different
* for each one, respectively. If we would add functions like them
* whenever we need to do that, this common header file(dw_mmc.h)
* will be modified so frequently.
* The following enumeration type is to minimize an amount of changes
* of common files.
*/
enum dw_mci_misc_control {
CTRL_RESTORE_CLKSEL = 0,
CTRL_REQUEST_EXT_IRQ,
CTRL_CHECK_CD,
};
#define SDMMC_DATA_TMOUT_SHIFT 11
#define SDMMC_RESP_TMOUT 0xFF
#define SDMMC_DATA_TMOUT_CRT 8
#define SDMMC_DATA_TMOUT_EXT 0x7
#define SDMMC_DATA_TMOUT_EXT_SHIFT 8
extern u32 dw_mci_calc_timeout(struct dw_mci *host);
extern int dw_mci_probe(struct dw_mci *host);
extern void dw_mci_remove(struct dw_mci *host);
#ifdef CONFIG_PM_SLEEP
extern int dw_mci_suspend(struct dw_mci *host);
extern int dw_mci_resume(struct dw_mci *host);
#endif
/**
* struct dw_mci_slot - MMC slot state
* @mmc: The mmc_host representing this slot.
* @host: The MMC controller this slot is using.
* @quirks: Slot-level quirks (DW_MCI_SLOT_QUIRK_XXX)
* @ctype: Card type for this slot.
* @mrq: mmc_request currently being processed or waiting to be
* processed, or NULL when the slot is idle.
* @queue_node: List node for placing this node in the @queue list of
* &struct dw_mci.
* @clock: Clock rate configured by set_ios(). Protected by host->lock.
* @__clk_old: The last updated clock with reflecting clock divider.
* Keeping track of this helps us to avoid spamming the console
* with CONFIG_MMC_CLKGATE.
* @flags: Random state bits associated with the slot.
* @id: Number of this slot.
* @last_detect_state: Most recently observed card detect state.
*/
struct dw_mci_slot {
struct mmc_host *mmc;
struct dw_mci *host;
int quirks;
u32 ctype;
struct mmc_request *mrq;
struct list_head queue_node;
unsigned int clock;
unsigned int __clk_old;
unsigned long flags;
#define DW_MMC_CARD_PRESENT 0
#define DW_MMC_CARD_NEED_INIT 1
int id;
int last_detect_state;
};
/**
* struct dw_mci_debug_data - DwMMC debugging infomation
* @host_count: a number of all hosts
* @info_count: a number of set of debugging information
* @info_index: index of debugging information for each host
* @host: pointer of each dw_mci structure
* @debug_info: debugging information structure
*/
struct dw_mci_cmd_log {
u64 send_time;
u64 done_time;
u8 cmd;
u32 arg;
u8 data_size;
/* no data CMD = CD, data CMD = DTO */
/*
* 0b1000 0000 : new_cmd with without send_cmd
* 0b0000 1000 : error occurs
* 0b0000 0100 : data_done : DTO(Data Transfer Over)
* 0b0000 0010 : resp : CD(Command Done)
* 0b0000 0001 : send_cmd : set 1 only start_command
*/
u8 seq_status; /* 0bxxxx xxxx : error data_done resp send */
#define DW_MCI_FLAG_SEND_CMD BIT(0)
#define DW_MCI_FLAG_CD BIT(1)
#define DW_MCI_FLAG_DTO BIT(2)
#define DW_MCI_FLAG_ERROR BIT(3)
#define DW_MCI_FLAG_NEW_CMD_ERR BIT(7)
u16 rint_sts; /* RINTSTS value in case of error */
u8 status_count; /* TBD : It can be changed */
};
enum dw_mci_req_log_state {
STATE_REQ_START = 0,
STATE_REQ_CMD_PROCESS,
STATE_REQ_DATA_PROCESS,
STATE_REQ_END,
};
struct dw_mci_req_log {
u64 timestamp;
u32 info0;
u32 info1;
u32 info2;
u32 info3;
u32 pending_events;
u32 completed_events;
enum dw_mci_state state;
enum dw_mci_state state_cmd;
enum dw_mci_state state_dat;
enum dw_mci_req_log_state log_state;
};
#define DWMCI_LOG_MAX 0x80
#define DWMCI_REQ_LOG_MAX 0x40
struct dw_mci_debug_info {
struct dw_mci_cmd_log cmd_log[DWMCI_LOG_MAX];
atomic_t cmd_log_count;
struct dw_mci_req_log req_log[DWMCI_REQ_LOG_MAX];
atomic_t req_log_count;
unsigned char en_logging;
#define DW_MCI_DEBUG_ON_CMD BIT(0)
#define DW_MCI_DEBUG_ON_REQ BIT(1)
};
#define DWMCI_DBG_NUM_HOST 3
#define DWMCI_DBG_NUM_INFO 2 /* configurable */
#define DWMCI_DBG_MASK_INFO (BIT(0) | BIT(1) | BIT(2)) /* configurable */
#define DWMCI_DBG_BIT_HOST(x) BIT(x)
struct dw_mci_debug_data {
unsigned char host_count;
unsigned char info_count;
unsigned char info_index[DWMCI_DBG_NUM_HOST];
struct dw_mci *host[DWMCI_DBG_NUM_HOST];
struct dw_mci_debug_info debug_info[DWMCI_DBG_NUM_INFO];
};
struct dw_mci_tuning_data {
const u8 *blk_pattern;
unsigned int blksz;
};
/**
* dw_mci driver data - dw-mshc implementation specific driver data.
* @caps: mmc subsystem specified capabilities of the controller(s).
* @init: early implementation specific initialization.
* @setup_clock: implementation specific clock configuration.
* @prepare_command: handle CMD register extensions.
* @set_ios: handle bus specific extensions.
* @parse_dt: parse implementation specific device tree properties.
* @execute_tuning: implementation specific tuning procedure.
* @cfg_smu: to configure security management unit
*
* Provide controller implementation specific extensions. The usage of this
* data structure is fully optional and usage of each member in this structure
* is optional as well.
*/
struct dw_mci_drv_data {
unsigned long *caps;
int (*init)(struct dw_mci *host);
int (*setup_clock)(struct dw_mci *host);
void (*prepare_command)(struct dw_mci *host, u32 *cmdr);
void (*set_ios)(struct dw_mci *host, struct mmc_ios *ios);
int (*parse_dt)(struct dw_mci *host);
int (*execute_tuning)(struct dw_mci_slot *slot, u32 opcode,
struct dw_mci_tuning_data *tuning_data);
void (*cfg_smu)(struct dw_mci *host);
int (*misc_control)(struct dw_mci *host,
enum dw_mci_misc_control control, void *priv);
};
struct dw_mci_sfe_ram_dump {
u32 contrl;
u32 pwren;
u32 clkdiv;
u32 clkena;
u32 clksrc;
u32 tmout;
u32 ctype;
u32 blksiz;
u32 bytcnt;
u32 intmask;
u32 cmdarg;
u32 cmd;
u32 mintsts;
u32 rintsts;
u32 status;
u32 fifoth;
u32 tcbcnt;
u32 tbbcnt;
u32 debnce;
u32 uhs_reg;
u32 bmod;
u32 pldmnd;
u32 dbaddrl;
u32 dbaddru;
u32 dscaddrl;
u32 dscaddru;
u32 bufaddru;
u32 dbaddr;
u32 dscaddr;
u32 bufaddr;
u32 clksel;
u32 idsts64;
u32 idinten64;
u32 force_clk_stop;
u32 cdthrctl;
u32 ddr200_rdqs_en;
u32 ddr200_acync_fifo_ctrl;
u32 ddr200_dline_ctrl;
u32 fmp_emmcp_base;
u32 mpsecurity;
u32 mpstat;
u32 mpsbegin;
u32 mpsend;
u32 mpsctrl;
u32 cmd_status;
u32 data_status;
u32 pending_events;
u32 completed_events;
u32 host_state;
u32 cmd_index;
u32 fifo_count;
u32 data_busy;
u32 data_3_state;
u32 fifo_tx_watermark;
u32 fifo_rx_watermark;
};
#endif /* _DW_MMC_H_ */

File diff suppressed because it is too large Load diff

1531
drivers/mmc/host/mmc_spi.c Normal file

File diff suppressed because it is too large Load diff

1922
drivers/mmc/host/mmci.c Normal file

File diff suppressed because it is too large Load diff

247
drivers/mmc/host/mmci.h Normal file
View file

@ -0,0 +1,247 @@
/*
* linux/drivers/mmc/host/mmci.h - ARM PrimeCell MMCI PL180/1 driver
*
* Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define MMCIPOWER 0x000
#define MCI_PWR_OFF 0x00
#define MCI_PWR_UP 0x02
#define MCI_PWR_ON 0x03
#define MCI_OD (1 << 6)
#define MCI_ROD (1 << 7)
/*
* The ST Micro version does not have ROD and reuse the voltage registers for
* direction settings.
*/
#define MCI_ST_DATA2DIREN (1 << 2)
#define MCI_ST_CMDDIREN (1 << 3)
#define MCI_ST_DATA0DIREN (1 << 4)
#define MCI_ST_DATA31DIREN (1 << 5)
#define MCI_ST_FBCLKEN (1 << 7)
#define MCI_ST_DATA74DIREN (1 << 8)
#define MMCICLOCK 0x004
#define MCI_CLK_ENABLE (1 << 8)
#define MCI_CLK_PWRSAVE (1 << 9)
#define MCI_CLK_BYPASS (1 << 10)
#define MCI_4BIT_BUS (1 << 11)
/*
* 8bit wide buses, hardware flow contronl, negative edges and clock inversion
* supported in ST Micro U300 and Ux500 versions
*/
#define MCI_ST_8BIT_BUS (1 << 12)
#define MCI_ST_U300_HWFCEN (1 << 13)
#define MCI_ST_UX500_NEG_EDGE (1 << 13)
#define MCI_ST_UX500_HWFCEN (1 << 14)
#define MCI_ST_UX500_CLK_INV (1 << 15)
/* Modified PL180 on Versatile Express platform */
#define MCI_ARM_HWFCEN (1 << 12)
/* Modified on Qualcomm Integrations */
#define MCI_QCOM_CLK_WIDEBUS_8 (BIT(10) | BIT(11))
#define MCI_QCOM_CLK_FLOWENA BIT(12)
#define MCI_QCOM_CLK_INVERTOUT BIT(13)
/* select in latch data and command in */
#define MCI_QCOM_CLK_SELECT_IN_FBCLK BIT(15)
#define MCI_QCOM_CLK_SELECT_IN_DDR_MODE (BIT(14) | BIT(15))
#define MMCIARGUMENT 0x008
#define MMCICOMMAND 0x00c
#define MCI_CPSM_RESPONSE (1 << 6)
#define MCI_CPSM_LONGRSP (1 << 7)
#define MCI_CPSM_INTERRUPT (1 << 8)
#define MCI_CPSM_PENDING (1 << 9)
#define MCI_CPSM_ENABLE (1 << 10)
/* Argument flag extenstions in the ST Micro versions */
#define MCI_ST_SDIO_SUSP (1 << 11)
#define MCI_ST_ENCMD_COMPL (1 << 12)
#define MCI_ST_NIEN (1 << 13)
#define MCI_ST_CE_ATACMD (1 << 14)
/* Modified on Qualcomm Integrations */
#define MCI_QCOM_CSPM_DATCMD BIT(12)
#define MCI_QCOM_CSPM_MCIABORT BIT(13)
#define MCI_QCOM_CSPM_CCSENABLE BIT(14)
#define MCI_QCOM_CSPM_CCSDISABLE BIT(15)
#define MCI_QCOM_CSPM_AUTO_CMD19 BIT(16)
#define MCI_QCOM_CSPM_AUTO_CMD21 BIT(21)
#define MMCIRESPCMD 0x010
#define MMCIRESPONSE0 0x014
#define MMCIRESPONSE1 0x018
#define MMCIRESPONSE2 0x01c
#define MMCIRESPONSE3 0x020
#define MMCIDATATIMER 0x024
#define MMCIDATALENGTH 0x028
#define MMCIDATACTRL 0x02c
#define MCI_DPSM_ENABLE (1 << 0)
#define MCI_DPSM_DIRECTION (1 << 1)
#define MCI_DPSM_MODE (1 << 2)
#define MCI_DPSM_DMAENABLE (1 << 3)
#define MCI_DPSM_BLOCKSIZE (1 << 4)
/* Control register extensions in the ST Micro U300 and Ux500 versions */
#define MCI_ST_DPSM_RWSTART (1 << 8)
#define MCI_ST_DPSM_RWSTOP (1 << 9)
#define MCI_ST_DPSM_RWMOD (1 << 10)
#define MCI_ST_DPSM_SDIOEN (1 << 11)
/* Control register extensions in the ST Micro Ux500 versions */
#define MCI_ST_DPSM_DMAREQCTL (1 << 12)
#define MCI_ST_DPSM_DBOOTMODEEN (1 << 13)
#define MCI_ST_DPSM_BUSYMODE (1 << 14)
#define MCI_ST_DPSM_DDRMODE (1 << 15)
#define MMCIDATACNT 0x030
#define MMCISTATUS 0x034
#define MCI_CMDCRCFAIL (1 << 0)
#define MCI_DATACRCFAIL (1 << 1)
#define MCI_CMDTIMEOUT (1 << 2)
#define MCI_DATATIMEOUT (1 << 3)
#define MCI_TXUNDERRUN (1 << 4)
#define MCI_RXOVERRUN (1 << 5)
#define MCI_CMDRESPEND (1 << 6)
#define MCI_CMDSENT (1 << 7)
#define MCI_DATAEND (1 << 8)
#define MCI_STARTBITERR (1 << 9)
#define MCI_DATABLOCKEND (1 << 10)
#define MCI_CMDACTIVE (1 << 11)
#define MCI_TXACTIVE (1 << 12)
#define MCI_RXACTIVE (1 << 13)
#define MCI_TXFIFOHALFEMPTY (1 << 14)
#define MCI_RXFIFOHALFFULL (1 << 15)
#define MCI_TXFIFOFULL (1 << 16)
#define MCI_RXFIFOFULL (1 << 17)
#define MCI_TXFIFOEMPTY (1 << 18)
#define MCI_RXFIFOEMPTY (1 << 19)
#define MCI_TXDATAAVLBL (1 << 20)
#define MCI_RXDATAAVLBL (1 << 21)
/* Extended status bits for the ST Micro variants */
#define MCI_ST_SDIOIT (1 << 22)
#define MCI_ST_CEATAEND (1 << 23)
#define MCI_ST_CARDBUSY (1 << 24)
#define MMCICLEAR 0x038
#define MCI_CMDCRCFAILCLR (1 << 0)
#define MCI_DATACRCFAILCLR (1 << 1)
#define MCI_CMDTIMEOUTCLR (1 << 2)
#define MCI_DATATIMEOUTCLR (1 << 3)
#define MCI_TXUNDERRUNCLR (1 << 4)
#define MCI_RXOVERRUNCLR (1 << 5)
#define MCI_CMDRESPENDCLR (1 << 6)
#define MCI_CMDSENTCLR (1 << 7)
#define MCI_DATAENDCLR (1 << 8)
#define MCI_STARTBITERRCLR (1 << 9)
#define MCI_DATABLOCKENDCLR (1 << 10)
/* Extended status bits for the ST Micro variants */
#define MCI_ST_SDIOITC (1 << 22)
#define MCI_ST_CEATAENDC (1 << 23)
#define MCI_ST_BUSYENDC (1 << 24)
#define MMCIMASK0 0x03c
#define MCI_CMDCRCFAILMASK (1 << 0)
#define MCI_DATACRCFAILMASK (1 << 1)
#define MCI_CMDTIMEOUTMASK (1 << 2)
#define MCI_DATATIMEOUTMASK (1 << 3)
#define MCI_TXUNDERRUNMASK (1 << 4)
#define MCI_RXOVERRUNMASK (1 << 5)
#define MCI_CMDRESPENDMASK (1 << 6)
#define MCI_CMDSENTMASK (1 << 7)
#define MCI_DATAENDMASK (1 << 8)
#define MCI_STARTBITERRMASK (1 << 9)
#define MCI_DATABLOCKENDMASK (1 << 10)
#define MCI_CMDACTIVEMASK (1 << 11)
#define MCI_TXACTIVEMASK (1 << 12)
#define MCI_RXACTIVEMASK (1 << 13)
#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
#define MCI_RXFIFOHALFFULLMASK (1 << 15)
#define MCI_TXFIFOFULLMASK (1 << 16)
#define MCI_RXFIFOFULLMASK (1 << 17)
#define MCI_TXFIFOEMPTYMASK (1 << 18)
#define MCI_RXFIFOEMPTYMASK (1 << 19)
#define MCI_TXDATAAVLBLMASK (1 << 20)
#define MCI_RXDATAAVLBLMASK (1 << 21)
/* Extended status bits for the ST Micro variants */
#define MCI_ST_SDIOITMASK (1 << 22)
#define MCI_ST_CEATAENDMASK (1 << 23)
#define MCI_ST_BUSYEND (1 << 24)
#define MMCIMASK1 0x040
#define MMCIFIFOCNT 0x048
#define MMCIFIFO 0x080 /* to 0x0bc */
#define MCI_IRQENABLE \
(MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_STARTBITERRMASK)
/* These interrupts are directed to IRQ1 when two IRQ lines are available */
#define MCI_IRQ1MASK \
(MCI_RXFIFOHALFFULLMASK | MCI_RXDATAAVLBLMASK | \
MCI_TXFIFOHALFEMPTYMASK)
#define NR_SG 128
struct clk;
struct variant_data;
struct dma_chan;
struct mmci_host_next {
struct dma_async_tx_descriptor *dma_desc;
struct dma_chan *dma_chan;
s32 cookie;
};
struct mmci_host {
phys_addr_t phybase;
void __iomem *base;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_host *mmc;
struct clk *clk;
bool singleirq;
spinlock_t lock;
unsigned int mclk;
/* cached value of requested clk in set_ios */
unsigned int clock_cache;
unsigned int cclk;
u32 pwr_reg;
u32 pwr_reg_add;
u32 clk_reg;
u32 datactrl_reg;
u32 busy_status;
bool vqmmc_enabled;
struct mmci_platform_data *plat;
struct variant_data *variant;
u8 hw_designer;
u8 hw_revision:4;
struct timer_list timer;
unsigned int oldstat;
/* pio stuff */
struct sg_mapping_iter sg_miter;
unsigned int size;
int (*get_rx_fifocnt)(struct mmci_host *h, u32 status, int remain);
#ifdef CONFIG_DMA_ENGINE
/* DMA stuff */
struct dma_chan *dma_current;
struct dma_chan *dma_rx_channel;
struct dma_chan *dma_tx_channel;
struct dma_async_tx_descriptor *dma_desc_current;
struct mmci_host_next next_data;
#define dma_inprogress(host) ((host)->dma_current)
#else
#define dma_inprogress(host) (0)
#endif
};

View file

@ -0,0 +1,177 @@
/*
*
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/of.h>
#include <linux/of_dma.h>
#include <linux/bitops.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include "mmci.h"
/* Registers */
#define DML_CONFIG 0x00
#define PRODUCER_CRCI_MSK GENMASK(1, 0)
#define PRODUCER_CRCI_DISABLE 0
#define PRODUCER_CRCI_X_SEL BIT(0)
#define PRODUCER_CRCI_Y_SEL BIT(1)
#define CONSUMER_CRCI_MSK GENMASK(3, 2)
#define CONSUMER_CRCI_DISABLE 0
#define CONSUMER_CRCI_X_SEL BIT(2)
#define CONSUMER_CRCI_Y_SEL BIT(3)
#define PRODUCER_TRANS_END_EN BIT(4)
#define BYPASS BIT(16)
#define DIRECT_MODE BIT(17)
#define INFINITE_CONS_TRANS BIT(18)
#define DML_SW_RESET 0x08
#define DML_PRODUCER_START 0x0c
#define DML_CONSUMER_START 0x10
#define DML_PRODUCER_PIPE_LOGICAL_SIZE 0x14
#define DML_CONSUMER_PIPE_LOGICAL_SIZE 0x18
#define DML_PIPE_ID 0x1c
#define PRODUCER_PIPE_ID_SHFT 0
#define PRODUCER_PIPE_ID_MSK GENMASK(4, 0)
#define CONSUMER_PIPE_ID_SHFT 16
#define CONSUMER_PIPE_ID_MSK GENMASK(20, 16)
#define DML_PRODUCER_BAM_BLOCK_SIZE 0x24
#define DML_PRODUCER_BAM_TRANS_SIZE 0x28
/* other definitions */
#define PRODUCER_PIPE_LOGICAL_SIZE 4096
#define CONSUMER_PIPE_LOGICAL_SIZE 4096
#define DML_OFFSET 0x800
void dml_start_xfer(struct mmci_host *host, struct mmc_data *data)
{
u32 config;
void __iomem *base = host->base + DML_OFFSET;
if (data->flags & MMC_DATA_READ) {
/* Read operation: configure DML for producer operation */
/* Set producer CRCI-x and disable consumer CRCI */
config = readl_relaxed(base + DML_CONFIG);
config = (config & ~PRODUCER_CRCI_MSK) | PRODUCER_CRCI_X_SEL;
config = (config & ~CONSUMER_CRCI_MSK) | CONSUMER_CRCI_DISABLE;
writel_relaxed(config, base + DML_CONFIG);
/* Set the Producer BAM block size */
writel_relaxed(data->blksz, base + DML_PRODUCER_BAM_BLOCK_SIZE);
/* Set Producer BAM Transaction size */
writel_relaxed(data->blocks * data->blksz,
base + DML_PRODUCER_BAM_TRANS_SIZE);
/* Set Producer Transaction End bit */
config = readl_relaxed(base + DML_CONFIG);
config |= PRODUCER_TRANS_END_EN;
writel_relaxed(config, base + DML_CONFIG);
/* Trigger producer */
writel_relaxed(1, base + DML_PRODUCER_START);
} else {
/* Write operation: configure DML for consumer operation */
/* Set consumer CRCI-x and disable producer CRCI*/
config = readl_relaxed(base + DML_CONFIG);
config = (config & ~CONSUMER_CRCI_MSK) | CONSUMER_CRCI_X_SEL;
config = (config & ~PRODUCER_CRCI_MSK) | PRODUCER_CRCI_DISABLE;
writel_relaxed(config, base + DML_CONFIG);
/* Clear Producer Transaction End bit */
config = readl_relaxed(base + DML_CONFIG);
config &= ~PRODUCER_TRANS_END_EN;
writel_relaxed(config, base + DML_CONFIG);
/* Trigger consumer */
writel_relaxed(1, base + DML_CONSUMER_START);
}
/* make sure the dml is configured before dma is triggered */
wmb();
}
static int of_get_dml_pipe_index(struct device_node *np, const char *name)
{
int index;
struct of_phandle_args dma_spec;
index = of_property_match_string(np, "dma-names", name);
if (index < 0)
return -ENODEV;
if (of_parse_phandle_with_args(np, "dmas", "#dma-cells", index,
&dma_spec))
return -ENODEV;
if (dma_spec.args_count)
return dma_spec.args[0];
return -ENODEV;
}
/* Initialize the dml hardware connected to SD Card controller */
int dml_hw_init(struct mmci_host *host, struct device_node *np)
{
u32 config;
void __iomem *base;
int consumer_id, producer_id;
consumer_id = of_get_dml_pipe_index(np, "tx");
producer_id = of_get_dml_pipe_index(np, "rx");
if (producer_id < 0 || consumer_id < 0)
return -ENODEV;
base = host->base + DML_OFFSET;
/* Reset the DML block */
writel_relaxed(1, base + DML_SW_RESET);
/* Disable the producer and consumer CRCI */
config = (PRODUCER_CRCI_DISABLE | CONSUMER_CRCI_DISABLE);
/*
* Disable the bypass mode. Bypass mode will only be used
* if data transfer is to happen in PIO mode and don't
* want the BAM interface to connect with SDCC-DML.
*/
config &= ~BYPASS;
/*
* Disable direct mode as we don't DML to MASTER the AHB bus.
* BAM connected with DML should MASTER the AHB bus.
*/
config &= ~DIRECT_MODE;
/*
* Disable infinite mode transfer as we won't be doing any
* infinite size data transfers. All data transfer will be
* of finite data size.
*/
config &= ~INFINITE_CONS_TRANS;
writel_relaxed(config, base + DML_CONFIG);
/*
* Initialize the logical BAM pipe size for producer
* and consumer.
*/
writel_relaxed(PRODUCER_PIPE_LOGICAL_SIZE,
base + DML_PRODUCER_PIPE_LOGICAL_SIZE);
writel_relaxed(CONSUMER_PIPE_LOGICAL_SIZE,
base + DML_CONSUMER_PIPE_LOGICAL_SIZE);
/* Initialize Producer/consumer pipe id */
writel_relaxed(producer_id | (consumer_id << CONSUMER_PIPE_ID_SHFT),
base + DML_PIPE_ID);
/* Make sure dml intialization is finished */
mb();
return 0;
}

View file

@ -0,0 +1,31 @@
/*
*
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __MMC_QCOM_DML_H__
#define __MMC_QCOM_DML_H__
#ifdef CONFIG_MMC_QCOM_DML
int dml_hw_init(struct mmci_host *host, struct device_node *np);
void dml_start_xfer(struct mmci_host *host, struct mmc_data *data);
#else
static inline int dml_hw_init(struct mmci_host *host, struct device_node *np)
{
return -ENOSYS;
}
static inline void dml_start_xfer(struct mmci_host *host, struct mmc_data *data)
{
}
#endif /* CONFIG_MMC_QCOM_DML */
#endif /* __MMC_QCOM_DML_H__ */

View file

@ -0,0 +1,728 @@
/*
* MOXA ART MMC host driver.
*
* Copyright (C) 2014 Jonas Jensen
*
* Jonas Jensen <jonas.jensen@gmail.com>
*
* Based on code from
* Moxa Technologies Co., Ltd. <www.moxa.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/blkdev.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sd.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/clk.h>
#include <linux/bitops.h>
#include <linux/of_dma.h>
#include <linux/spinlock.h>
#define REG_COMMAND 0
#define REG_ARGUMENT 4
#define REG_RESPONSE0 8
#define REG_RESPONSE1 12
#define REG_RESPONSE2 16
#define REG_RESPONSE3 20
#define REG_RESPONSE_COMMAND 24
#define REG_DATA_CONTROL 28
#define REG_DATA_TIMER 32
#define REG_DATA_LENGTH 36
#define REG_STATUS 40
#define REG_CLEAR 44
#define REG_INTERRUPT_MASK 48
#define REG_POWER_CONTROL 52
#define REG_CLOCK_CONTROL 56
#define REG_BUS_WIDTH 60
#define REG_DATA_WINDOW 64
#define REG_FEATURE 68
#define REG_REVISION 72
/* REG_COMMAND */
#define CMD_SDC_RESET BIT(10)
#define CMD_EN BIT(9)
#define CMD_APP_CMD BIT(8)
#define CMD_LONG_RSP BIT(7)
#define CMD_NEED_RSP BIT(6)
#define CMD_IDX_MASK 0x3f
/* REG_RESPONSE_COMMAND */
#define RSP_CMD_APP BIT(6)
#define RSP_CMD_IDX_MASK 0x3f
/* REG_DATA_CONTROL */
#define DCR_DATA_FIFO_RESET BIT(8)
#define DCR_DATA_THRES BIT(7)
#define DCR_DATA_EN BIT(6)
#define DCR_DMA_EN BIT(5)
#define DCR_DATA_WRITE BIT(4)
#define DCR_BLK_SIZE 0x0f
/* REG_DATA_LENGTH */
#define DATA_LEN_MASK 0xffffff
/* REG_STATUS */
#define WRITE_PROT BIT(12)
#define CARD_DETECT BIT(11)
/* 1-10 below can be sent to either registers, interrupt or clear. */
#define CARD_CHANGE BIT(10)
#define FIFO_ORUN BIT(9)
#define FIFO_URUN BIT(8)
#define DATA_END BIT(7)
#define CMD_SENT BIT(6)
#define DATA_CRC_OK BIT(5)
#define RSP_CRC_OK BIT(4)
#define DATA_TIMEOUT BIT(3)
#define RSP_TIMEOUT BIT(2)
#define DATA_CRC_FAIL BIT(1)
#define RSP_CRC_FAIL BIT(0)
#define MASK_RSP (RSP_TIMEOUT | RSP_CRC_FAIL | \
RSP_CRC_OK | CARD_DETECT | CMD_SENT)
#define MASK_DATA (DATA_CRC_OK | DATA_END | \
DATA_CRC_FAIL | DATA_TIMEOUT)
#define MASK_INTR_PIO (FIFO_URUN | FIFO_ORUN | CARD_CHANGE)
/* REG_POWER_CONTROL */
#define SD_POWER_ON BIT(4)
#define SD_POWER_MASK 0x0f
/* REG_CLOCK_CONTROL */
#define CLK_HISPD BIT(9)
#define CLK_OFF BIT(8)
#define CLK_SD BIT(7)
#define CLK_DIV_MASK 0x7f
/* REG_BUS_WIDTH */
#define BUS_WIDTH_8 BIT(2)
#define BUS_WIDTH_4 BIT(1)
#define BUS_WIDTH_1 BIT(0)
#define MMC_VDD_360 23
#define MIN_POWER (MMC_VDD_360 - SD_POWER_MASK)
#define MAX_RETRIES 500000
struct moxart_host {
spinlock_t lock;
void __iomem *base;
phys_addr_t reg_phys;
struct dma_chan *dma_chan_tx;
struct dma_chan *dma_chan_rx;
struct dma_async_tx_descriptor *tx_desc;
struct mmc_host *mmc;
struct mmc_request *mrq;
struct scatterlist *cur_sg;
struct completion dma_complete;
struct completion pio_complete;
u32 num_sg;
u32 data_remain;
u32 data_len;
u32 fifo_width;
u32 timeout;
u32 rate;
long sysclk;
bool have_dma;
bool is_removed;
};
static inline void moxart_init_sg(struct moxart_host *host,
struct mmc_data *data)
{
host->cur_sg = data->sg;
host->num_sg = data->sg_len;
host->data_remain = host->cur_sg->length;
if (host->data_remain > host->data_len)
host->data_remain = host->data_len;
}
static inline int moxart_next_sg(struct moxart_host *host)
{
int remain;
struct mmc_data *data = host->mrq->cmd->data;
host->cur_sg++;
host->num_sg--;
if (host->num_sg > 0) {
host->data_remain = host->cur_sg->length;
remain = host->data_len - data->bytes_xfered;
if (remain > 0 && remain < host->data_remain)
host->data_remain = remain;
}
return host->num_sg;
}
static int moxart_wait_for_status(struct moxart_host *host,
u32 mask, u32 *status)
{
int ret = -ETIMEDOUT;
u32 i;
for (i = 0; i < MAX_RETRIES; i++) {
*status = readl(host->base + REG_STATUS);
if (!(*status & mask)) {
udelay(5);
continue;
}
writel(*status & mask, host->base + REG_CLEAR);
ret = 0;
break;
}
if (ret)
dev_err(mmc_dev(host->mmc), "timed out waiting for status\n");
return ret;
}
static void moxart_send_command(struct moxart_host *host,
struct mmc_command *cmd)
{
u32 status, cmdctrl;
writel(RSP_TIMEOUT | RSP_CRC_OK |
RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR);
writel(cmd->arg, host->base + REG_ARGUMENT);
cmdctrl = cmd->opcode & CMD_IDX_MASK;
if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
cmdctrl |= CMD_APP_CMD;
if (cmd->flags & MMC_RSP_PRESENT)
cmdctrl |= CMD_NEED_RSP;
if (cmd->flags & MMC_RSP_136)
cmdctrl |= CMD_LONG_RSP;
writel(cmdctrl | CMD_EN, host->base + REG_COMMAND);
if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT)
cmd->error = -ETIMEDOUT;
if (status & RSP_TIMEOUT) {
cmd->error = -ETIMEDOUT;
return;
}
if (status & RSP_CRC_FAIL) {
cmd->error = -EIO;
return;
}
if (status & RSP_CRC_OK) {
if (cmd->flags & MMC_RSP_136) {
cmd->resp[3] = readl(host->base + REG_RESPONSE0);
cmd->resp[2] = readl(host->base + REG_RESPONSE1);
cmd->resp[1] = readl(host->base + REG_RESPONSE2);
cmd->resp[0] = readl(host->base + REG_RESPONSE3);
} else {
cmd->resp[0] = readl(host->base + REG_RESPONSE0);
}
}
}
static void moxart_dma_complete(void *param)
{
struct moxart_host *host = param;
complete(&host->dma_complete);
}
static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
{
u32 len, dir_data, dir_slave;
unsigned long dma_time;
struct dma_async_tx_descriptor *desc = NULL;
struct dma_chan *dma_chan;
if (host->data_len == data->bytes_xfered)
return;
if (data->flags & MMC_DATA_WRITE) {
dma_chan = host->dma_chan_tx;
dir_data = DMA_TO_DEVICE;
dir_slave = DMA_MEM_TO_DEV;
} else {
dma_chan = host->dma_chan_rx;
dir_data = DMA_FROM_DEVICE;
dir_slave = DMA_DEV_TO_MEM;
}
len = dma_map_sg(dma_chan->device->dev, data->sg,
data->sg_len, dir_data);
if (len > 0) {
desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
len, dir_slave,
DMA_PREP_INTERRUPT |
DMA_CTRL_ACK);
} else {
dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
}
if (desc) {
host->tx_desc = desc;
desc->callback = moxart_dma_complete;
desc->callback_param = host;
dmaengine_submit(desc);
dma_async_issue_pending(dma_chan);
}
data->bytes_xfered += host->data_remain;
dma_time = wait_for_completion_interruptible_timeout(
&host->dma_complete, host->timeout);
dma_unmap_sg(dma_chan->device->dev,
data->sg, data->sg_len,
dir_data);
}
static void moxart_transfer_pio(struct moxart_host *host)
{
struct mmc_data *data = host->mrq->cmd->data;
u32 *sgp, len = 0, remain, status;
if (host->data_len == data->bytes_xfered)
return;
sgp = sg_virt(host->cur_sg);
remain = host->data_remain;
if (data->flags & MMC_DATA_WRITE) {
while (remain > 0) {
if (moxart_wait_for_status(host, FIFO_URUN, &status)
== -ETIMEDOUT) {
data->error = -ETIMEDOUT;
complete(&host->pio_complete);
return;
}
for (len = 0; len < remain && len < host->fifo_width;) {
iowrite32(*sgp, host->base + REG_DATA_WINDOW);
sgp++;
len += 4;
}
remain -= len;
}
} else {
while (remain > 0) {
if (moxart_wait_for_status(host, FIFO_ORUN, &status)
== -ETIMEDOUT) {
data->error = -ETIMEDOUT;
complete(&host->pio_complete);
return;
}
for (len = 0; len < remain && len < host->fifo_width;) {
/* SCR data must be read in big endian. */
if (data->mrq->cmd->opcode == SD_APP_SEND_SCR)
*sgp = ioread32be(host->base +
REG_DATA_WINDOW);
else
*sgp = ioread32(host->base +
REG_DATA_WINDOW);
sgp++;
len += 4;
}
remain -= len;
}
}
data->bytes_xfered += host->data_remain - remain;
host->data_remain = remain;
if (host->data_len != data->bytes_xfered)
moxart_next_sg(host);
else
complete(&host->pio_complete);
}
static void moxart_prepare_data(struct moxart_host *host)
{
struct mmc_data *data = host->mrq->cmd->data;
u32 datactrl;
int blksz_bits;
if (!data)
return;
host->data_len = data->blocks * data->blksz;
blksz_bits = ffs(data->blksz) - 1;
BUG_ON(1 << blksz_bits != data->blksz);
moxart_init_sg(host, data);
datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE);
if (data->flags & MMC_DATA_WRITE)
datactrl |= DCR_DATA_WRITE;
if ((host->data_len > host->fifo_width) && host->have_dma)
datactrl |= DCR_DMA_EN;
writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL);
writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR);
writel(host->rate, host->base + REG_DATA_TIMER);
writel(host->data_len, host->base + REG_DATA_LENGTH);
writel(datactrl, host->base + REG_DATA_CONTROL);
}
static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct moxart_host *host = mmc_priv(mmc);
unsigned long pio_time, flags;
u32 status;
spin_lock_irqsave(&host->lock, flags);
init_completion(&host->dma_complete);
init_completion(&host->pio_complete);
host->mrq = mrq;
if (readl(host->base + REG_STATUS) & CARD_DETECT) {
mrq->cmd->error = -ETIMEDOUT;
goto request_done;
}
moxart_prepare_data(host);
moxart_send_command(host, host->mrq->cmd);
if (mrq->cmd->data) {
if ((host->data_len > host->fifo_width) && host->have_dma) {
writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
spin_unlock_irqrestore(&host->lock, flags);
moxart_transfer_dma(mrq->cmd->data, host);
spin_lock_irqsave(&host->lock, flags);
} else {
writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK);
spin_unlock_irqrestore(&host->lock, flags);
/* PIO transfers start from interrupt. */
pio_time = wait_for_completion_interruptible_timeout(
&host->pio_complete, host->timeout);
spin_lock_irqsave(&host->lock, flags);
}
if (host->is_removed) {
dev_err(mmc_dev(host->mmc), "card removed\n");
mrq->cmd->error = -ETIMEDOUT;
goto request_done;
}
if (moxart_wait_for_status(host, MASK_DATA, &status)
== -ETIMEDOUT) {
mrq->cmd->data->error = -ETIMEDOUT;
goto request_done;
}
if (status & DATA_CRC_FAIL)
mrq->cmd->data->error = -ETIMEDOUT;
if (mrq->cmd->data->stop)
moxart_send_command(host, mrq->cmd->data->stop);
}
request_done:
spin_unlock_irqrestore(&host->lock, flags);
mmc_request_done(host->mmc, mrq);
}
static irqreturn_t moxart_irq(int irq, void *devid)
{
struct moxart_host *host = (struct moxart_host *)devid;
u32 status;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
status = readl(host->base + REG_STATUS);
if (status & CARD_CHANGE) {
host->is_removed = status & CARD_DETECT;
if (host->is_removed && host->have_dma) {
dmaengine_terminate_all(host->dma_chan_tx);
dmaengine_terminate_all(host->dma_chan_rx);
}
host->mrq = NULL;
writel(MASK_INTR_PIO, host->base + REG_CLEAR);
writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
mmc_detect_change(host->mmc, 0);
}
if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq)
moxart_transfer_pio(host);
spin_unlock_irqrestore(&host->lock, flags);
return IRQ_HANDLED;
}
static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct moxart_host *host = mmc_priv(mmc);
unsigned long flags;
u8 power, div;
u32 ctrl;
spin_lock_irqsave(&host->lock, flags);
if (ios->clock) {
for (div = 0; div < CLK_DIV_MASK; ++div) {
if (ios->clock >= host->sysclk / (2 * (div + 1)))
break;
}
ctrl = CLK_SD | div;
host->rate = host->sysclk / (2 * (div + 1));
if (host->rate > host->sysclk)
ctrl |= CLK_HISPD;
writel(ctrl, host->base + REG_CLOCK_CONTROL);
}
if (ios->power_mode == MMC_POWER_OFF) {
writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON,
host->base + REG_POWER_CONTROL);
} else {
if (ios->vdd < MIN_POWER)
power = 0;
else
power = ios->vdd - MIN_POWER;
writel(SD_POWER_ON | (u32) power,
host->base + REG_POWER_CONTROL);
}
switch (ios->bus_width) {
case MMC_BUS_WIDTH_4:
writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH);
break;
case MMC_BUS_WIDTH_8:
writel(BUS_WIDTH_8, host->base + REG_BUS_WIDTH);
break;
default:
writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH);
break;
}
spin_unlock_irqrestore(&host->lock, flags);
}
static int moxart_get_ro(struct mmc_host *mmc)
{
struct moxart_host *host = mmc_priv(mmc);
return !!(readl(host->base + REG_STATUS) & WRITE_PROT);
}
static struct mmc_host_ops moxart_ops = {
.request = moxart_request,
.set_ios = moxart_set_ios,
.get_ro = moxart_get_ro,
};
static int moxart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct resource res_mmc;
struct mmc_host *mmc;
struct moxart_host *host = NULL;
struct dma_slave_config cfg;
struct clk *clk;
void __iomem *reg_mmc;
dma_cap_mask_t mask;
int irq, ret;
u32 i;
mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
if (!mmc) {
dev_err(dev, "mmc_alloc_host failed\n");
ret = -ENOMEM;
goto out;
}
ret = of_address_to_resource(node, 0, &res_mmc);
if (ret) {
dev_err(dev, "of_address_to_resource failed\n");
goto out;
}
irq = irq_of_parse_and_map(node, 0);
if (irq <= 0) {
dev_err(dev, "irq_of_parse_and_map failed\n");
ret = -EINVAL;
goto out;
}
clk = of_clk_get(node, 0);
if (IS_ERR(clk)) {
dev_err(dev, "of_clk_get failed\n");
ret = PTR_ERR(clk);
goto out;
}
reg_mmc = devm_ioremap_resource(dev, &res_mmc);
if (IS_ERR(reg_mmc)) {
ret = PTR_ERR(reg_mmc);
goto out;
}
mmc_of_parse(mmc);
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
host = mmc_priv(mmc);
host->mmc = mmc;
host->base = reg_mmc;
host->reg_phys = res_mmc.start;
host->timeout = msecs_to_jiffies(1000);
host->sysclk = clk_get_rate(clk);
host->fifo_width = readl(host->base + REG_FEATURE) << 2;
host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
spin_lock_init(&host->lock);
mmc->ops = &moxart_ops;
mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2);
mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2);
mmc->ocr_avail = 0xffff00; /* Support 2.0v - 3.6v power. */
if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
dev_dbg(dev, "PIO mode transfer enabled\n");
host->have_dma = false;
} else {
dev_dbg(dev, "DMA channels found (%p,%p)\n",
host->dma_chan_tx, host->dma_chan_rx);
host->have_dma = true;
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.direction = DMA_MEM_TO_DEV;
cfg.src_addr = 0;
cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW;
dmaengine_slave_config(host->dma_chan_tx, &cfg);
cfg.direction = DMA_DEV_TO_MEM;
cfg.src_addr = host->reg_phys + REG_DATA_WINDOW;
cfg.dst_addr = 0;
dmaengine_slave_config(host->dma_chan_rx, &cfg);
}
switch ((readl(host->base + REG_BUS_WIDTH) >> 3) & 3) {
case 1:
mmc->caps |= MMC_CAP_4_BIT_DATA;
break;
case 2:
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
break;
default:
break;
}
writel(0, host->base + REG_INTERRUPT_MASK);
writel(CMD_SDC_RESET, host->base + REG_COMMAND);
for (i = 0; i < MAX_RETRIES; i++) {
if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET))
break;
udelay(5);
}
ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
if (ret)
goto out;
dev_set_drvdata(dev, mmc);
mmc_add_host(mmc);
dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width);
return 0;
out:
if (mmc)
mmc_free_host(mmc);
return ret;
}
static int moxart_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
struct moxart_host *host = mmc_priv(mmc);
dev_set_drvdata(&pdev->dev, NULL);
if (mmc) {
if (!IS_ERR(host->dma_chan_tx))
dma_release_channel(host->dma_chan_tx);
if (!IS_ERR(host->dma_chan_rx))
dma_release_channel(host->dma_chan_rx);
mmc_remove_host(mmc);
mmc_free_host(mmc);
writel(0, host->base + REG_INTERRUPT_MASK);
writel(0, host->base + REG_POWER_CONTROL);
writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF,
host->base + REG_CLOCK_CONTROL);
}
kfree(host);
return 0;
}
static const struct of_device_id moxart_mmc_match[] = {
{ .compatible = "moxa,moxart-mmc" },
{ .compatible = "faraday,ftsdc010" },
{ }
};
static struct platform_driver moxart_mmc_driver = {
.probe = moxart_probe,
.remove = moxart_remove,
.driver = {
.name = "mmc-moxart",
.of_match_table = moxart_mmc_match,
},
};
module_platform_driver(moxart_mmc_driver);
MODULE_ALIAS("platform:mmc-moxart");
MODULE_DESCRIPTION("MOXA ART MMC driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");

1474
drivers/mmc/host/msm_sdcc.c Normal file

File diff suppressed because it is too large Load diff

256
drivers/mmc/host/msm_sdcc.h Normal file
View file

@ -0,0 +1,256 @@
/*
* linux/drivers/mmc/host/msmsdcc.h - QCT MSM7K SDC Controller
*
* Copyright (C) 2008 Google, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* - Based on mmci.h
*/
#ifndef _MSM_SDCC_H
#define _MSM_SDCC_H
#define MSMSDCC_CRCI_SDC1 6
#define MSMSDCC_CRCI_SDC2 7
#define MSMSDCC_CRCI_SDC3 12
#define MSMSDCC_CRCI_SDC4 13
#define MMCIPOWER 0x000
#define MCI_PWR_OFF 0x00
#define MCI_PWR_UP 0x02
#define MCI_PWR_ON 0x03
#define MCI_OD (1 << 6)
#define MMCICLOCK 0x004
#define MCI_CLK_ENABLE (1 << 8)
#define MCI_CLK_PWRSAVE (1 << 9)
#define MCI_CLK_WIDEBUS (1 << 10)
#define MCI_CLK_FLOWENA (1 << 12)
#define MCI_CLK_INVERTOUT (1 << 13)
#define MCI_CLK_SELECTIN (1 << 14)
#define MMCIARGUMENT 0x008
#define MMCICOMMAND 0x00c
#define MCI_CPSM_RESPONSE (1 << 6)
#define MCI_CPSM_LONGRSP (1 << 7)
#define MCI_CPSM_INTERRUPT (1 << 8)
#define MCI_CPSM_PENDING (1 << 9)
#define MCI_CPSM_ENABLE (1 << 10)
#define MCI_CPSM_PROGENA (1 << 11)
#define MCI_CSPM_DATCMD (1 << 12)
#define MCI_CSPM_MCIABORT (1 << 13)
#define MCI_CSPM_CCSENABLE (1 << 14)
#define MCI_CSPM_CCSDISABLE (1 << 15)
#define MMCIRESPCMD 0x010
#define MMCIRESPONSE0 0x014
#define MMCIRESPONSE1 0x018
#define MMCIRESPONSE2 0x01c
#define MMCIRESPONSE3 0x020
#define MMCIDATATIMER 0x024
#define MMCIDATALENGTH 0x028
#define MMCIDATACTRL 0x02c
#define MCI_DPSM_ENABLE (1 << 0)
#define MCI_DPSM_DIRECTION (1 << 1)
#define MCI_DPSM_MODE (1 << 2)
#define MCI_DPSM_DMAENABLE (1 << 3)
#define MMCIDATACNT 0x030
#define MMCISTATUS 0x034
#define MCI_CMDCRCFAIL (1 << 0)
#define MCI_DATACRCFAIL (1 << 1)
#define MCI_CMDTIMEOUT (1 << 2)
#define MCI_DATATIMEOUT (1 << 3)
#define MCI_TXUNDERRUN (1 << 4)
#define MCI_RXOVERRUN (1 << 5)
#define MCI_CMDRESPEND (1 << 6)
#define MCI_CMDSENT (1 << 7)
#define MCI_DATAEND (1 << 8)
#define MCI_DATABLOCKEND (1 << 10)
#define MCI_CMDACTIVE (1 << 11)
#define MCI_TXACTIVE (1 << 12)
#define MCI_RXACTIVE (1 << 13)
#define MCI_TXFIFOHALFEMPTY (1 << 14)
#define MCI_RXFIFOHALFFULL (1 << 15)
#define MCI_TXFIFOFULL (1 << 16)
#define MCI_RXFIFOFULL (1 << 17)
#define MCI_TXFIFOEMPTY (1 << 18)
#define MCI_RXFIFOEMPTY (1 << 19)
#define MCI_TXDATAAVLBL (1 << 20)
#define MCI_RXDATAAVLBL (1 << 21)
#define MCI_SDIOINTR (1 << 22)
#define MCI_PROGDONE (1 << 23)
#define MCI_ATACMDCOMPL (1 << 24)
#define MCI_SDIOINTOPER (1 << 25)
#define MCI_CCSTIMEOUT (1 << 26)
#define MMCICLEAR 0x038
#define MCI_CMDCRCFAILCLR (1 << 0)
#define MCI_DATACRCFAILCLR (1 << 1)
#define MCI_CMDTIMEOUTCLR (1 << 2)
#define MCI_DATATIMEOUTCLR (1 << 3)
#define MCI_TXUNDERRUNCLR (1 << 4)
#define MCI_RXOVERRUNCLR (1 << 5)
#define MCI_CMDRESPENDCLR (1 << 6)
#define MCI_CMDSENTCLR (1 << 7)
#define MCI_DATAENDCLR (1 << 8)
#define MCI_DATABLOCKENDCLR (1 << 10)
#define MMCIMASK0 0x03c
#define MCI_CMDCRCFAILMASK (1 << 0)
#define MCI_DATACRCFAILMASK (1 << 1)
#define MCI_CMDTIMEOUTMASK (1 << 2)
#define MCI_DATATIMEOUTMASK (1 << 3)
#define MCI_TXUNDERRUNMASK (1 << 4)
#define MCI_RXOVERRUNMASK (1 << 5)
#define MCI_CMDRESPENDMASK (1 << 6)
#define MCI_CMDSENTMASK (1 << 7)
#define MCI_DATAENDMASK (1 << 8)
#define MCI_DATABLOCKENDMASK (1 << 10)
#define MCI_CMDACTIVEMASK (1 << 11)
#define MCI_TXACTIVEMASK (1 << 12)
#define MCI_RXACTIVEMASK (1 << 13)
#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
#define MCI_RXFIFOHALFFULLMASK (1 << 15)
#define MCI_TXFIFOFULLMASK (1 << 16)
#define MCI_RXFIFOFULLMASK (1 << 17)
#define MCI_TXFIFOEMPTYMASK (1 << 18)
#define MCI_RXFIFOEMPTYMASK (1 << 19)
#define MCI_TXDATAAVLBLMASK (1 << 20)
#define MCI_RXDATAAVLBLMASK (1 << 21)
#define MCI_SDIOINTMASK (1 << 22)
#define MCI_PROGDONEMASK (1 << 23)
#define MCI_ATACMDCOMPLMASK (1 << 24)
#define MCI_SDIOINTOPERMASK (1 << 25)
#define MCI_CCSTIMEOUTMASK (1 << 26)
#define MMCIMASK1 0x040
#define MMCIFIFOCNT 0x044
#define MCICCSTIMER 0x058
#define MMCIFIFO 0x080 /* to 0x0bc */
#define MCI_IRQENABLE \
(MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATAENDMASK|MCI_PROGDONEMASK)
#define MCI_IRQ_PIO \
(MCI_RXDATAAVLBLMASK | MCI_TXDATAAVLBLMASK | MCI_RXFIFOEMPTYMASK | \
MCI_TXFIFOEMPTYMASK | MCI_RXFIFOFULLMASK | MCI_TXFIFOFULLMASK | \
MCI_RXFIFOHALFFULLMASK | MCI_TXFIFOHALFEMPTYMASK | \
MCI_RXACTIVEMASK | MCI_TXACTIVEMASK)
/*
* The size of the FIFO in bytes.
*/
#define MCI_FIFOSIZE (16*4)
#define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2)
#define NR_SG 32
struct clk;
struct msmsdcc_nc_dmadata {
dmov_box cmd[NR_SG];
uint32_t cmdptr;
};
struct msmsdcc_dma_data {
struct msmsdcc_nc_dmadata *nc;
dma_addr_t nc_busaddr;
dma_addr_t cmd_busaddr;
dma_addr_t cmdptr_busaddr;
struct msm_dmov_cmd hdr;
enum dma_data_direction dir;
struct scatterlist *sg;
int num_ents;
int channel;
struct msmsdcc_host *host;
int busy; /* Set if DM is busy */
int active;
unsigned int result;
struct msm_dmov_errdata err;
};
struct msmsdcc_pio_data {
struct scatterlist *sg;
unsigned int sg_len;
unsigned int sg_off;
};
struct msmsdcc_curr_req {
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
unsigned int xfer_size; /* Total data size */
unsigned int xfer_remain; /* Bytes remaining to send */
unsigned int data_xfered; /* Bytes acked by BLKEND irq */
int got_dataend;
int user_pages;
};
struct msmsdcc_stats {
unsigned int reqs;
unsigned int cmds;
unsigned int cmdpoll_hits;
unsigned int cmdpoll_misses;
};
struct msmsdcc_host {
struct resource *cmd_irqres;
struct resource *memres;
struct resource *dmares;
void __iomem *base;
int pdev_id;
unsigned int stat_irq;
struct msmsdcc_curr_req curr;
struct mmc_host *mmc;
struct clk *clk; /* main MMC bus clock */
struct clk *pclk; /* SDCC peripheral bus clock */
unsigned int clks_on; /* set if clocks are enabled */
struct timer_list busclk_timer;
unsigned int eject; /* eject state */
spinlock_t lock;
unsigned int clk_rate; /* Current clock rate */
unsigned int pclk_rate;
u32 pwr;
u32 saved_irq0mask; /* MMCIMASK0 reg value */
struct msm_mmc_platform_data *plat;
struct timer_list timer;
unsigned int oldstat;
struct msmsdcc_dma_data dma;
struct msmsdcc_pio_data pio;
int cmdpoll;
struct msmsdcc_stats stats;
struct tasklet_struct dma_tlet;
/* Command parameters */
unsigned int cmd_timeout;
unsigned int cmd_pio_irqmask;
unsigned int cmd_datactrl;
struct mmc_command *cmd_cmd;
u32 cmd_c;
bool gpio_config_status;
bool prog_scan;
bool prog_enable;
};
#endif

881
drivers/mmc/host/mvsdio.c Normal file
View file

@ -0,0 +1,881 @@
/*
* Marvell MMC/SD/SDIO driver
*
* Authors: Maen Suleiman, Nicolas Pitre
* Copyright (C) 2008-2009 Marvell Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mbus.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/mmc/host.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/pinctrl/consumer.h>
#include <asm/sizes.h>
#include <asm/unaligned.h>
#include <linux/platform_data/mmc-mvsdio.h>
#include "mvsdio.h"
#define DRIVER_NAME "mvsdio"
static int maxfreq;
static int nodma;
struct mvsd_host {
void __iomem *base;
struct mmc_request *mrq;
spinlock_t lock;
unsigned int xfer_mode;
unsigned int intr_en;
unsigned int ctrl;
unsigned int pio_size;
void *pio_ptr;
unsigned int sg_frags;
unsigned int ns_per_clk;
unsigned int clock;
unsigned int base_clock;
struct timer_list timer;
struct mmc_host *mmc;
struct device *dev;
struct clk *clk;
};
#define mvsd_write(offs, val) writel(val, iobase + (offs))
#define mvsd_read(offs) readl(iobase + (offs))
static int mvsd_setup_data(struct mvsd_host *host, struct mmc_data *data)
{
void __iomem *iobase = host->base;
unsigned int tmout;
int tmout_index;
/*
* Hardware weirdness. The FIFO_EMPTY bit of the HW_STATE
* register is sometimes not set before a while when some
* "unusual" data block sizes are used (such as with the SWITCH
* command), even despite the fact that the XFER_DONE interrupt
* was raised. And if another data transfer starts before
* this bit comes to good sense (which eventually happens by
* itself) then the new transfer simply fails with a timeout.
*/
if (!(mvsd_read(MVSD_HW_STATE) & (1 << 13))) {
unsigned long t = jiffies + HZ;
unsigned int hw_state, count = 0;
do {
hw_state = mvsd_read(MVSD_HW_STATE);
if (time_after(jiffies, t)) {
dev_warn(host->dev, "FIFO_EMPTY bit missing\n");
break;
}
count++;
} while (!(hw_state & (1 << 13)));
dev_dbg(host->dev, "*** wait for FIFO_EMPTY bit "
"(hw=0x%04x, count=%d, jiffies=%ld)\n",
hw_state, count, jiffies - (t - HZ));
}
/* If timeout=0 then maximum timeout index is used. */
tmout = DIV_ROUND_UP(data->timeout_ns, host->ns_per_clk);
tmout += data->timeout_clks;
tmout_index = fls(tmout - 1) - 12;
if (tmout_index < 0)
tmout_index = 0;
if (tmout_index > MVSD_HOST_CTRL_TMOUT_MAX)
tmout_index = MVSD_HOST_CTRL_TMOUT_MAX;
dev_dbg(host->dev, "data %s at 0x%08x: blocks=%d blksz=%d tmout=%u (%d)\n",
(data->flags & MMC_DATA_READ) ? "read" : "write",
(u32)sg_virt(data->sg), data->blocks, data->blksz,
tmout, tmout_index);
host->ctrl &= ~MVSD_HOST_CTRL_TMOUT_MASK;
host->ctrl |= MVSD_HOST_CTRL_TMOUT(tmout_index);
mvsd_write(MVSD_HOST_CTRL, host->ctrl);
mvsd_write(MVSD_BLK_COUNT, data->blocks);
mvsd_write(MVSD_BLK_SIZE, data->blksz);
if (nodma || (data->blksz | data->sg->offset) & 3) {
/*
* We cannot do DMA on a buffer which offset or size
* is not aligned on a 4-byte boundary.
*/
host->pio_size = data->blocks * data->blksz;
host->pio_ptr = sg_virt(data->sg);
if (!nodma)
dev_dbg(host->dev, "fallback to PIO for data at 0x%p size %d\n",
host->pio_ptr, host->pio_size);
return 1;
} else {
dma_addr_t phys_addr;
int dma_dir = (data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE;
host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, dma_dir);
phys_addr = sg_dma_address(data->sg);
mvsd_write(MVSD_SYS_ADDR_LOW, (u32)phys_addr & 0xffff);
mvsd_write(MVSD_SYS_ADDR_HI, (u32)phys_addr >> 16);
return 0;
}
}
static void mvsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct mvsd_host *host = mmc_priv(mmc);
void __iomem *iobase = host->base;
struct mmc_command *cmd = mrq->cmd;
u32 cmdreg = 0, xfer = 0, intr = 0;
unsigned long flags;
BUG_ON(host->mrq != NULL);
host->mrq = mrq;
dev_dbg(host->dev, "cmd %d (hw state 0x%04x)\n",
cmd->opcode, mvsd_read(MVSD_HW_STATE));
cmdreg = MVSD_CMD_INDEX(cmd->opcode);
if (cmd->flags & MMC_RSP_BUSY)
cmdreg |= MVSD_CMD_RSP_48BUSY;
else if (cmd->flags & MMC_RSP_136)
cmdreg |= MVSD_CMD_RSP_136;
else if (cmd->flags & MMC_RSP_PRESENT)
cmdreg |= MVSD_CMD_RSP_48;
else
cmdreg |= MVSD_CMD_RSP_NONE;
if (cmd->flags & MMC_RSP_CRC)
cmdreg |= MVSD_CMD_CHECK_CMDCRC;
if (cmd->flags & MMC_RSP_OPCODE)
cmdreg |= MVSD_CMD_INDX_CHECK;
if (cmd->flags & MMC_RSP_PRESENT) {
cmdreg |= MVSD_UNEXPECTED_RESP;
intr |= MVSD_NOR_UNEXP_RSP;
}
if (mrq->data) {
struct mmc_data *data = mrq->data;
int pio;
cmdreg |= MVSD_CMD_DATA_PRESENT | MVSD_CMD_CHECK_DATACRC16;
xfer |= MVSD_XFER_MODE_HW_WR_DATA_EN;
if (data->flags & MMC_DATA_READ)
xfer |= MVSD_XFER_MODE_TO_HOST;
pio = mvsd_setup_data(host, data);
if (pio) {
xfer |= MVSD_XFER_MODE_PIO;
/* PIO section of mvsd_irq has comments on those bits */
if (data->flags & MMC_DATA_WRITE)
intr |= MVSD_NOR_TX_AVAIL;
else if (host->pio_size > 32)
intr |= MVSD_NOR_RX_FIFO_8W;
else
intr |= MVSD_NOR_RX_READY;
}
if (data->stop) {
struct mmc_command *stop = data->stop;
u32 cmd12reg = 0;
mvsd_write(MVSD_AUTOCMD12_ARG_LOW, stop->arg & 0xffff);
mvsd_write(MVSD_AUTOCMD12_ARG_HI, stop->arg >> 16);
if (stop->flags & MMC_RSP_BUSY)
cmd12reg |= MVSD_AUTOCMD12_BUSY;
if (stop->flags & MMC_RSP_OPCODE)
cmd12reg |= MVSD_AUTOCMD12_INDX_CHECK;
cmd12reg |= MVSD_AUTOCMD12_INDEX(stop->opcode);
mvsd_write(MVSD_AUTOCMD12_CMD, cmd12reg);
xfer |= MVSD_XFER_MODE_AUTO_CMD12;
intr |= MVSD_NOR_AUTOCMD12_DONE;
} else {
intr |= MVSD_NOR_XFER_DONE;
}
} else {
intr |= MVSD_NOR_CMD_DONE;
}
mvsd_write(MVSD_ARG_LOW, cmd->arg & 0xffff);
mvsd_write(MVSD_ARG_HI, cmd->arg >> 16);
spin_lock_irqsave(&host->lock, flags);
host->xfer_mode &= MVSD_XFER_MODE_INT_CHK_EN;
host->xfer_mode |= xfer;
mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
mvsd_write(MVSD_NOR_INTR_STATUS, ~MVSD_NOR_CARD_INT);
mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
mvsd_write(MVSD_CMD, cmdreg);
host->intr_en &= MVSD_NOR_CARD_INT;
host->intr_en |= intr | MVSD_NOR_ERROR;
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
mvsd_write(MVSD_ERR_INTR_EN, 0xffff);
mod_timer(&host->timer, jiffies + 5 * HZ);
spin_unlock_irqrestore(&host->lock, flags);
}
static u32 mvsd_finish_cmd(struct mvsd_host *host, struct mmc_command *cmd,
u32 err_status)
{
void __iomem *iobase = host->base;
if (cmd->flags & MMC_RSP_136) {
unsigned int response[8], i;
for (i = 0; i < 8; i++)
response[i] = mvsd_read(MVSD_RSP(i));
cmd->resp[0] = ((response[0] & 0x03ff) << 22) |
((response[1] & 0xffff) << 6) |
((response[2] & 0xfc00) >> 10);
cmd->resp[1] = ((response[2] & 0x03ff) << 22) |
((response[3] & 0xffff) << 6) |
((response[4] & 0xfc00) >> 10);
cmd->resp[2] = ((response[4] & 0x03ff) << 22) |
((response[5] & 0xffff) << 6) |
((response[6] & 0xfc00) >> 10);
cmd->resp[3] = ((response[6] & 0x03ff) << 22) |
((response[7] & 0x3fff) << 8);
} else if (cmd->flags & MMC_RSP_PRESENT) {
unsigned int response[3], i;
for (i = 0; i < 3; i++)
response[i] = mvsd_read(MVSD_RSP(i));
cmd->resp[0] = ((response[2] & 0x003f) << (8 - 8)) |
((response[1] & 0xffff) << (14 - 8)) |
((response[0] & 0x03ff) << (30 - 8));
cmd->resp[1] = ((response[0] & 0xfc00) >> 10);
cmd->resp[2] = 0;
cmd->resp[3] = 0;
}
if (err_status & MVSD_ERR_CMD_TIMEOUT) {
cmd->error = -ETIMEDOUT;
} else if (err_status & (MVSD_ERR_CMD_CRC | MVSD_ERR_CMD_ENDBIT |
MVSD_ERR_CMD_INDEX | MVSD_ERR_CMD_STARTBIT)) {
cmd->error = -EILSEQ;
}
err_status &= ~(MVSD_ERR_CMD_TIMEOUT | MVSD_ERR_CMD_CRC |
MVSD_ERR_CMD_ENDBIT | MVSD_ERR_CMD_INDEX |
MVSD_ERR_CMD_STARTBIT);
return err_status;
}
static u32 mvsd_finish_data(struct mvsd_host *host, struct mmc_data *data,
u32 err_status)
{
void __iomem *iobase = host->base;
if (host->pio_ptr) {
host->pio_ptr = NULL;
host->pio_size = 0;
} else {
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_frags,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
}
if (err_status & MVSD_ERR_DATA_TIMEOUT)
data->error = -ETIMEDOUT;
else if (err_status & (MVSD_ERR_DATA_CRC | MVSD_ERR_DATA_ENDBIT))
data->error = -EILSEQ;
else if (err_status & MVSD_ERR_XFER_SIZE)
data->error = -EBADE;
err_status &= ~(MVSD_ERR_DATA_TIMEOUT | MVSD_ERR_DATA_CRC |
MVSD_ERR_DATA_ENDBIT | MVSD_ERR_XFER_SIZE);
dev_dbg(host->dev, "data done: blocks_left=%d, bytes_left=%d\n",
mvsd_read(MVSD_CURR_BLK_LEFT), mvsd_read(MVSD_CURR_BYTE_LEFT));
data->bytes_xfered =
(data->blocks - mvsd_read(MVSD_CURR_BLK_LEFT)) * data->blksz;
/* We can't be sure about the last block when errors are detected */
if (data->bytes_xfered && data->error)
data->bytes_xfered -= data->blksz;
/* Handle Auto cmd 12 response */
if (data->stop) {
unsigned int response[3], i;
for (i = 0; i < 3; i++)
response[i] = mvsd_read(MVSD_AUTO_RSP(i));
data->stop->resp[0] = ((response[2] & 0x003f) << (8 - 8)) |
((response[1] & 0xffff) << (14 - 8)) |
((response[0] & 0x03ff) << (30 - 8));
data->stop->resp[1] = ((response[0] & 0xfc00) >> 10);
data->stop->resp[2] = 0;
data->stop->resp[3] = 0;
if (err_status & MVSD_ERR_AUTOCMD12) {
u32 err_cmd12 = mvsd_read(MVSD_AUTOCMD12_ERR_STATUS);
dev_dbg(host->dev, "c12err 0x%04x\n", err_cmd12);
if (err_cmd12 & MVSD_AUTOCMD12_ERR_NOTEXE)
data->stop->error = -ENOEXEC;
else if (err_cmd12 & MVSD_AUTOCMD12_ERR_TIMEOUT)
data->stop->error = -ETIMEDOUT;
else if (err_cmd12)
data->stop->error = -EILSEQ;
err_status &= ~MVSD_ERR_AUTOCMD12;
}
}
return err_status;
}
static irqreturn_t mvsd_irq(int irq, void *dev)
{
struct mvsd_host *host = dev;
void __iomem *iobase = host->base;
u32 intr_status, intr_done_mask;
int irq_handled = 0;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
dev_dbg(host->dev, "intr 0x%04x intr_en 0x%04x hw_state 0x%04x\n",
intr_status, mvsd_read(MVSD_NOR_INTR_EN),
mvsd_read(MVSD_HW_STATE));
/*
* It looks like, SDIO IP can issue one late, spurious irq
* although all irqs should be disabled. To work around this,
* bail out early, if we didn't expect any irqs to occur.
*/
if (!mvsd_read(MVSD_NOR_INTR_EN) && !mvsd_read(MVSD_ERR_INTR_EN)) {
dev_dbg(host->dev, "spurious irq detected intr 0x%04x intr_en 0x%04x erri 0x%04x erri_en 0x%04x\n",
mvsd_read(MVSD_NOR_INTR_STATUS),
mvsd_read(MVSD_NOR_INTR_EN),
mvsd_read(MVSD_ERR_INTR_STATUS),
mvsd_read(MVSD_ERR_INTR_EN));
return IRQ_HANDLED;
}
spin_lock(&host->lock);
/* PIO handling, if needed. Messy business... */
if (host->pio_size &&
(intr_status & host->intr_en &
(MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W))) {
u16 *p = host->pio_ptr;
int s = host->pio_size;
while (s >= 32 && (intr_status & MVSD_NOR_RX_FIFO_8W)) {
readsw(iobase + MVSD_FIFO, p, 16);
p += 16;
s -= 32;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
}
/*
* Normally we'd use < 32 here, but the RX_FIFO_8W bit
* doesn't appear to assert when there is exactly 32 bytes
* (8 words) left to fetch in a transfer.
*/
if (s <= 32) {
while (s >= 4 && (intr_status & MVSD_NOR_RX_READY)) {
put_unaligned(mvsd_read(MVSD_FIFO), p++);
put_unaligned(mvsd_read(MVSD_FIFO), p++);
s -= 4;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
}
if (s && s < 4 && (intr_status & MVSD_NOR_RX_READY)) {
u16 val[2] = {0, 0};
val[0] = mvsd_read(MVSD_FIFO);
val[1] = mvsd_read(MVSD_FIFO);
memcpy(p, ((void *)&val) + 4 - s, s);
s = 0;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
}
if (s == 0) {
host->intr_en &=
~(MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W);
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
} else if (host->intr_en & MVSD_NOR_RX_FIFO_8W) {
host->intr_en &= ~MVSD_NOR_RX_FIFO_8W;
host->intr_en |= MVSD_NOR_RX_READY;
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
}
}
dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
s, intr_status, mvsd_read(MVSD_HW_STATE));
host->pio_ptr = p;
host->pio_size = s;
irq_handled = 1;
} else if (host->pio_size &&
(intr_status & host->intr_en &
(MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W))) {
u16 *p = host->pio_ptr;
int s = host->pio_size;
/*
* The TX_FIFO_8W bit is unreliable. When set, bursting
* 16 halfwords all at once in the FIFO drops data. Actually
* TX_AVAIL does go off after only one word is pushed even if
* TX_FIFO_8W remains set.
*/
while (s >= 4 && (intr_status & MVSD_NOR_TX_AVAIL)) {
mvsd_write(MVSD_FIFO, get_unaligned(p++));
mvsd_write(MVSD_FIFO, get_unaligned(p++));
s -= 4;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
}
if (s < 4) {
if (s && (intr_status & MVSD_NOR_TX_AVAIL)) {
u16 val[2] = {0, 0};
memcpy(((void *)&val) + 4 - s, p, s);
mvsd_write(MVSD_FIFO, val[0]);
mvsd_write(MVSD_FIFO, val[1]);
s = 0;
intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
}
if (s == 0) {
host->intr_en &=
~(MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W);
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
}
}
dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
s, intr_status, mvsd_read(MVSD_HW_STATE));
host->pio_ptr = p;
host->pio_size = s;
irq_handled = 1;
}
mvsd_write(MVSD_NOR_INTR_STATUS, intr_status);
intr_done_mask = MVSD_NOR_CARD_INT | MVSD_NOR_RX_READY |
MVSD_NOR_RX_FIFO_8W | MVSD_NOR_TX_FIFO_8W;
if (intr_status & host->intr_en & ~intr_done_mask) {
struct mmc_request *mrq = host->mrq;
struct mmc_command *cmd = mrq->cmd;
u32 err_status = 0;
del_timer(&host->timer);
host->mrq = NULL;
host->intr_en &= MVSD_NOR_CARD_INT;
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
mvsd_write(MVSD_ERR_INTR_EN, 0);
spin_unlock(&host->lock);
if (intr_status & MVSD_NOR_UNEXP_RSP) {
cmd->error = -EPROTO;
} else if (intr_status & MVSD_NOR_ERROR) {
err_status = mvsd_read(MVSD_ERR_INTR_STATUS);
dev_dbg(host->dev, "err 0x%04x\n", err_status);
}
err_status = mvsd_finish_cmd(host, cmd, err_status);
if (mrq->data)
err_status = mvsd_finish_data(host, mrq->data, err_status);
if (err_status) {
dev_err(host->dev, "unhandled error status %#04x\n",
err_status);
cmd->error = -ENOMSG;
}
mmc_request_done(host->mmc, mrq);
irq_handled = 1;
} else
spin_unlock(&host->lock);
if (intr_status & MVSD_NOR_CARD_INT) {
mmc_signal_sdio_irq(host->mmc);
irq_handled = 1;
}
if (irq_handled)
return IRQ_HANDLED;
dev_err(host->dev, "unhandled interrupt status=0x%04x en=0x%04x pio=%d\n",
intr_status, host->intr_en, host->pio_size);
return IRQ_NONE;
}
static void mvsd_timeout_timer(unsigned long data)
{
struct mvsd_host *host = (struct mvsd_host *)data;
void __iomem *iobase = host->base;
struct mmc_request *mrq;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
mrq = host->mrq;
if (mrq) {
dev_err(host->dev, "Timeout waiting for hardware interrupt.\n");
dev_err(host->dev, "hw_state=0x%04x, intr_status=0x%04x intr_en=0x%04x\n",
mvsd_read(MVSD_HW_STATE),
mvsd_read(MVSD_NOR_INTR_STATUS),
mvsd_read(MVSD_NOR_INTR_EN));
host->mrq = NULL;
mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
host->xfer_mode &= MVSD_XFER_MODE_INT_CHK_EN;
mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
host->intr_en &= MVSD_NOR_CARD_INT;
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
mvsd_write(MVSD_ERR_INTR_EN, 0);
mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
mrq->cmd->error = -ETIMEDOUT;
mvsd_finish_cmd(host, mrq->cmd, 0);
if (mrq->data) {
mrq->data->error = -ETIMEDOUT;
mvsd_finish_data(host, mrq->data, 0);
}
}
spin_unlock_irqrestore(&host->lock, flags);
if (mrq)
mmc_request_done(host->mmc, mrq);
}
static void mvsd_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct mvsd_host *host = mmc_priv(mmc);
void __iomem *iobase = host->base;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
if (enable) {
host->xfer_mode |= MVSD_XFER_MODE_INT_CHK_EN;
host->intr_en |= MVSD_NOR_CARD_INT;
} else {
host->xfer_mode &= ~MVSD_XFER_MODE_INT_CHK_EN;
host->intr_en &= ~MVSD_NOR_CARD_INT;
}
mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
spin_unlock_irqrestore(&host->lock, flags);
}
static void mvsd_power_up(struct mvsd_host *host)
{
void __iomem *iobase = host->base;
dev_dbg(host->dev, "power up\n");
mvsd_write(MVSD_NOR_INTR_EN, 0);
mvsd_write(MVSD_ERR_INTR_EN, 0);
mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
mvsd_write(MVSD_XFER_MODE, 0);
mvsd_write(MVSD_NOR_STATUS_EN, 0xffff);
mvsd_write(MVSD_ERR_STATUS_EN, 0xffff);
mvsd_write(MVSD_NOR_INTR_STATUS, 0xffff);
mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
}
static void mvsd_power_down(struct mvsd_host *host)
{
void __iomem *iobase = host->base;
dev_dbg(host->dev, "power down\n");
mvsd_write(MVSD_NOR_INTR_EN, 0);
mvsd_write(MVSD_ERR_INTR_EN, 0);
mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
mvsd_write(MVSD_XFER_MODE, MVSD_XFER_MODE_STOP_CLK);
mvsd_write(MVSD_NOR_STATUS_EN, 0);
mvsd_write(MVSD_ERR_STATUS_EN, 0);
mvsd_write(MVSD_NOR_INTR_STATUS, 0xffff);
mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
}
static void mvsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct mvsd_host *host = mmc_priv(mmc);
void __iomem *iobase = host->base;
u32 ctrl_reg = 0;
if (ios->power_mode == MMC_POWER_UP)
mvsd_power_up(host);
if (ios->clock == 0) {
mvsd_write(MVSD_XFER_MODE, MVSD_XFER_MODE_STOP_CLK);
mvsd_write(MVSD_CLK_DIV, MVSD_BASE_DIV_MAX);
host->clock = 0;
dev_dbg(host->dev, "clock off\n");
} else if (ios->clock != host->clock) {
u32 m = DIV_ROUND_UP(host->base_clock, ios->clock) - 1;
if (m > MVSD_BASE_DIV_MAX)
m = MVSD_BASE_DIV_MAX;
mvsd_write(MVSD_CLK_DIV, m);
host->clock = ios->clock;
host->ns_per_clk = 1000000000 / (host->base_clock / (m+1));
dev_dbg(host->dev, "clock=%d (%d), div=0x%04x\n",
ios->clock, host->base_clock / (m+1), m);
}
/* default transfer mode */
ctrl_reg |= MVSD_HOST_CTRL_BIG_ENDIAN;
ctrl_reg &= ~MVSD_HOST_CTRL_LSB_FIRST;
/* default to maximum timeout */
ctrl_reg |= MVSD_HOST_CTRL_TMOUT_MASK;
ctrl_reg |= MVSD_HOST_CTRL_TMOUT_EN;
if (ios->bus_mode == MMC_BUSMODE_PUSHPULL)
ctrl_reg |= MVSD_HOST_CTRL_PUSH_PULL_EN;
if (ios->bus_width == MMC_BUS_WIDTH_4)
ctrl_reg |= MVSD_HOST_CTRL_DATA_WIDTH_4_BITS;
/*
* The HI_SPEED_EN bit is causing trouble with many (but not all)
* high speed SD, SDHC and SDIO cards. Not enabling that bit
* makes all cards work. So let's just ignore that bit for now
* and revisit this issue if problems for not enabling this bit
* are ever reported.
*/
#if 0
if (ios->timing == MMC_TIMING_MMC_HS ||
ios->timing == MMC_TIMING_SD_HS)
ctrl_reg |= MVSD_HOST_CTRL_HI_SPEED_EN;
#endif
host->ctrl = ctrl_reg;
mvsd_write(MVSD_HOST_CTRL, ctrl_reg);
dev_dbg(host->dev, "ctrl 0x%04x: %s %s %s\n", ctrl_reg,
(ctrl_reg & MVSD_HOST_CTRL_PUSH_PULL_EN) ?
"push-pull" : "open-drain",
(ctrl_reg & MVSD_HOST_CTRL_DATA_WIDTH_4_BITS) ?
"4bit-width" : "1bit-width",
(ctrl_reg & MVSD_HOST_CTRL_HI_SPEED_EN) ?
"high-speed" : "");
if (ios->power_mode == MMC_POWER_OFF)
mvsd_power_down(host);
}
static const struct mmc_host_ops mvsd_ops = {
.request = mvsd_request,
.get_ro = mmc_gpio_get_ro,
.set_ios = mvsd_set_ios,
.enable_sdio_irq = mvsd_enable_sdio_irq,
};
static void
mv_conf_mbus_windows(struct mvsd_host *host,
const struct mbus_dram_target_info *dram)
{
void __iomem *iobase = host->base;
int i;
for (i = 0; i < 4; i++) {
writel(0, iobase + MVSD_WINDOW_CTRL(i));
writel(0, iobase + MVSD_WINDOW_BASE(i));
}
for (i = 0; i < dram->num_cs; i++) {
const struct mbus_dram_window *cs = dram->cs + i;
writel(((cs->size - 1) & 0xffff0000) |
(cs->mbus_attr << 8) |
(dram->mbus_dram_target_id << 4) | 1,
iobase + MVSD_WINDOW_CTRL(i));
writel(cs->base, iobase + MVSD_WINDOW_BASE(i));
}
}
static int mvsd_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct mmc_host *mmc = NULL;
struct mvsd_host *host = NULL;
const struct mbus_dram_target_info *dram;
struct resource *r;
int ret, irq;
struct pinctrl *pinctrl;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!r || irq < 0)
return -ENXIO;
mmc = mmc_alloc_host(sizeof(struct mvsd_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}
host = mmc_priv(mmc);
host->mmc = mmc;
host->dev = &pdev->dev;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "no pins associated\n");
/*
* Some non-DT platforms do not pass a clock, and the clock
* frequency is passed through platform_data. On DT platforms,
* a clock must always be passed, even if there is no gatable
* clock associated to the SDIO interface (it can simply be a
* fixed rate clock).
*/
host->clk = devm_clk_get(&pdev->dev, NULL);
if (!IS_ERR(host->clk))
clk_prepare_enable(host->clk);
mmc->ops = &mvsd_ops;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->f_min = DIV_ROUND_UP(host->base_clock, MVSD_BASE_DIV_MAX);
mmc->f_max = MVSD_CLOCKRATE_MAX;
mmc->max_blk_size = 2048;
mmc->max_blk_count = 65535;
mmc->max_segs = 1;
mmc->max_seg_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
if (np) {
if (IS_ERR(host->clk)) {
dev_err(&pdev->dev, "DT platforms must have a clock associated\n");
ret = -EINVAL;
goto out;
}
host->base_clock = clk_get_rate(host->clk) / 2;
ret = mmc_of_parse(mmc);
if (ret < 0)
goto out;
} else {
const struct mvsdio_platform_data *mvsd_data;
mvsd_data = pdev->dev.platform_data;
if (!mvsd_data) {
ret = -ENXIO;
goto out;
}
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ |
MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
host->base_clock = mvsd_data->clock / 2;
/* GPIO 0 regarded as invalid for backward compatibility */
if (mvsd_data->gpio_card_detect &&
gpio_is_valid(mvsd_data->gpio_card_detect)) {
ret = mmc_gpio_request_cd(mmc,
mvsd_data->gpio_card_detect,
0);
if (ret)
goto out;
} else {
mmc->caps |= MMC_CAP_NEEDS_POLL;
}
if (mvsd_data->gpio_write_protect &&
gpio_is_valid(mvsd_data->gpio_write_protect))
mmc_gpio_request_ro(mmc, mvsd_data->gpio_write_protect);
}
if (maxfreq)
mmc->f_max = maxfreq;
spin_lock_init(&host->lock);
host->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(host->base)) {
ret = PTR_ERR(host->base);
goto out;
}
/* (Re-)program MBUS remapping windows if we are asked to. */
dram = mv_mbus_dram_info();
if (dram)
mv_conf_mbus_windows(host, dram);
mvsd_power_down(host);
ret = devm_request_irq(&pdev->dev, irq, mvsd_irq, 0, DRIVER_NAME, host);
if (ret) {
dev_err(&pdev->dev, "cannot assign irq %d\n", irq);
goto out;
}
setup_timer(&host->timer, mvsd_timeout_timer, (unsigned long)host);
platform_set_drvdata(pdev, mmc);
ret = mmc_add_host(mmc);
if (ret)
goto out;
if (!(mmc->caps & MMC_CAP_NEEDS_POLL))
dev_dbg(&pdev->dev, "using GPIO for card detection\n");
else
dev_dbg(&pdev->dev, "lacking card detect (fall back to polling)\n");
return 0;
out:
if (mmc) {
mmc_gpio_free_cd(mmc);
mmc_gpio_free_ro(mmc);
if (!IS_ERR(host->clk))
clk_disable_unprepare(host->clk);
mmc_free_host(mmc);
}
return ret;
}
static int mvsd_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
struct mvsd_host *host = mmc_priv(mmc);
mmc_gpio_free_cd(mmc);
mmc_gpio_free_ro(mmc);
mmc_remove_host(mmc);
del_timer_sync(&host->timer);
mvsd_power_down(host);
if (!IS_ERR(host->clk))
clk_disable_unprepare(host->clk);
mmc_free_host(mmc);
return 0;
}
static const struct of_device_id mvsdio_dt_ids[] = {
{ .compatible = "marvell,orion-sdio" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mvsdio_dt_ids);
static struct platform_driver mvsd_driver = {
.probe = mvsd_probe,
.remove = mvsd_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mvsdio_dt_ids,
},
};
module_platform_driver(mvsd_driver);
/* maximum card clock frequency (default 50MHz) */
module_param(maxfreq, int, 0);
/* force PIO transfers all the time */
module_param(nodma, int, 0);
MODULE_AUTHOR("Maen Suleiman, Nicolas Pitre");
MODULE_DESCRIPTION("Marvell MMC,SD,SDIO Host Controller driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mvsdio");

190
drivers/mmc/host/mvsdio.h Normal file
View file

@ -0,0 +1,190 @@
/*
* Copyright (C) 2008 Marvell Semiconductors, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __MVSDIO_H
#define __MVSDIO_H
/*
* Clock rates
*/
#define MVSD_CLOCKRATE_MAX 50000000
#define MVSD_BASE_DIV_MAX 0x7ff
/*
* Register offsets
*/
#define MVSD_SYS_ADDR_LOW 0x000
#define MVSD_SYS_ADDR_HI 0x004
#define MVSD_BLK_SIZE 0x008
#define MVSD_BLK_COUNT 0x00c
#define MVSD_ARG_LOW 0x010
#define MVSD_ARG_HI 0x014
#define MVSD_XFER_MODE 0x018
#define MVSD_CMD 0x01c
#define MVSD_RSP(i) (0x020 + ((i)<<2))
#define MVSD_RSP0 0x020
#define MVSD_RSP1 0x024
#define MVSD_RSP2 0x028
#define MVSD_RSP3 0x02c
#define MVSD_RSP4 0x030
#define MVSD_RSP5 0x034
#define MVSD_RSP6 0x038
#define MVSD_RSP7 0x03c
#define MVSD_FIFO 0x040
#define MVSD_RSP_CRC7 0x044
#define MVSD_HW_STATE 0x048
#define MVSD_HOST_CTRL 0x050
#define MVSD_BLK_GAP_CTRL 0x054
#define MVSD_CLK_CTRL 0x058
#define MVSD_SW_RESET 0x05c
#define MVSD_NOR_INTR_STATUS 0x060
#define MVSD_ERR_INTR_STATUS 0x064
#define MVSD_NOR_STATUS_EN 0x068
#define MVSD_ERR_STATUS_EN 0x06c
#define MVSD_NOR_INTR_EN 0x070
#define MVSD_ERR_INTR_EN 0x074
#define MVSD_AUTOCMD12_ERR_STATUS 0x078
#define MVSD_CURR_BYTE_LEFT 0x07c
#define MVSD_CURR_BLK_LEFT 0x080
#define MVSD_AUTOCMD12_ARG_LOW 0x084
#define MVSD_AUTOCMD12_ARG_HI 0x088
#define MVSD_AUTOCMD12_CMD 0x08c
#define MVSD_AUTO_RSP(i) (0x090 + ((i)<<2))
#define MVSD_AUTO_RSP0 0x090
#define MVSD_AUTO_RSP1 0x094
#define MVSD_AUTO_RSP2 0x098
#define MVSD_CLK_DIV 0x128
#define MVSD_WINDOW_CTRL(i) (0x108 + ((i) << 3))
#define MVSD_WINDOW_BASE(i) (0x10c + ((i) << 3))
/*
* MVSD_CMD
*/
#define MVSD_CMD_RSP_NONE (0 << 0)
#define MVSD_CMD_RSP_136 (1 << 0)
#define MVSD_CMD_RSP_48 (2 << 0)
#define MVSD_CMD_RSP_48BUSY (3 << 0)
#define MVSD_CMD_CHECK_DATACRC16 (1 << 2)
#define MVSD_CMD_CHECK_CMDCRC (1 << 3)
#define MVSD_CMD_INDX_CHECK (1 << 4)
#define MVSD_CMD_DATA_PRESENT (1 << 5)
#define MVSD_UNEXPECTED_RESP (1 << 7)
#define MVSD_CMD_INDEX(x) ((x) << 8)
/*
* MVSD_AUTOCMD12_CMD
*/
#define MVSD_AUTOCMD12_BUSY (1 << 0)
#define MVSD_AUTOCMD12_INDX_CHECK (1 << 1)
#define MVSD_AUTOCMD12_INDEX(x) ((x) << 8)
/*
* MVSD_XFER_MODE
*/
#define MVSD_XFER_MODE_WR_DATA_START (1 << 0)
#define MVSD_XFER_MODE_HW_WR_DATA_EN (1 << 1)
#define MVSD_XFER_MODE_AUTO_CMD12 (1 << 2)
#define MVSD_XFER_MODE_INT_CHK_EN (1 << 3)
#define MVSD_XFER_MODE_TO_HOST (1 << 4)
#define MVSD_XFER_MODE_STOP_CLK (1 << 5)
#define MVSD_XFER_MODE_PIO (1 << 6)
/*
* MVSD_HOST_CTRL
*/
#define MVSD_HOST_CTRL_PUSH_PULL_EN (1 << 0)
#define MVSD_HOST_CTRL_CARD_TYPE_MEM_ONLY (0 << 1)
#define MVSD_HOST_CTRL_CARD_TYPE_IO_ONLY (1 << 1)
#define MVSD_HOST_CTRL_CARD_TYPE_IO_MEM_COMBO (2 << 1)
#define MVSD_HOST_CTRL_CARD_TYPE_IO_MMC (3 << 1)
#define MVSD_HOST_CTRL_CARD_TYPE_MASK (3 << 1)
#define MVSD_HOST_CTRL_BIG_ENDIAN (1 << 3)
#define MVSD_HOST_CTRL_LSB_FIRST (1 << 4)
#define MVSD_HOST_CTRL_DATA_WIDTH_4_BITS (1 << 9)
#define MVSD_HOST_CTRL_HI_SPEED_EN (1 << 10)
#define MVSD_HOST_CTRL_TMOUT_MAX 0xf
#define MVSD_HOST_CTRL_TMOUT_MASK (0xf << 11)
#define MVSD_HOST_CTRL_TMOUT(x) ((x) << 11)
#define MVSD_HOST_CTRL_TMOUT_EN (1 << 15)
/*
* MVSD_SW_RESET
*/
#define MVSD_SW_RESET_NOW (1 << 8)
/*
* Normal interrupt status bits
*/
#define MVSD_NOR_CMD_DONE (1 << 0)
#define MVSD_NOR_XFER_DONE (1 << 1)
#define MVSD_NOR_BLK_GAP_EVT (1 << 2)
#define MVSD_NOR_DMA_DONE (1 << 3)
#define MVSD_NOR_TX_AVAIL (1 << 4)
#define MVSD_NOR_RX_READY (1 << 5)
#define MVSD_NOR_CARD_INT (1 << 8)
#define MVSD_NOR_READ_WAIT_ON (1 << 9)
#define MVSD_NOR_RX_FIFO_8W (1 << 10)
#define MVSD_NOR_TX_FIFO_8W (1 << 11)
#define MVSD_NOR_SUSPEND_ON (1 << 12)
#define MVSD_NOR_AUTOCMD12_DONE (1 << 13)
#define MVSD_NOR_UNEXP_RSP (1 << 14)
#define MVSD_NOR_ERROR (1 << 15)
/*
* Error status bits
*/
#define MVSD_ERR_CMD_TIMEOUT (1 << 0)
#define MVSD_ERR_CMD_CRC (1 << 1)
#define MVSD_ERR_CMD_ENDBIT (1 << 2)
#define MVSD_ERR_CMD_INDEX (1 << 3)
#define MVSD_ERR_DATA_TIMEOUT (1 << 4)
#define MVSD_ERR_DATA_CRC (1 << 5)
#define MVSD_ERR_DATA_ENDBIT (1 << 6)
#define MVSD_ERR_AUTOCMD12 (1 << 8)
#define MVSD_ERR_CMD_STARTBIT (1 << 9)
#define MVSD_ERR_XFER_SIZE (1 << 10)
#define MVSD_ERR_RESP_T_BIT (1 << 11)
#define MVSD_ERR_CRC_ENDBIT (1 << 12)
#define MVSD_ERR_CRC_STARTBIT (1 << 13)
#define MVSD_ERR_CRC_STATUS (1 << 14)
/*
* CMD12 error status bits
*/
#define MVSD_AUTOCMD12_ERR_NOTEXE (1 << 0)
#define MVSD_AUTOCMD12_ERR_TIMEOUT (1 << 1)
#define MVSD_AUTOCMD12_ERR_CRC (1 << 2)
#define MVSD_AUTOCMD12_ERR_ENDBIT (1 << 3)
#define MVSD_AUTOCMD12_ERR_INDEX (1 << 4)
#define MVSD_AUTOCMD12_ERR_RESP_T_BIT (1 << 5)
#define MVSD_AUTOCMD12_ERR_RESP_STARTBIT (1 << 6)
#endif

1251
drivers/mmc/host/mxcmmc.c Normal file

File diff suppressed because it is too large Load diff

750
drivers/mmc/host/mxs-mmc.c Normal file
View file

@ -0,0 +1,750 @@
/*
* Portions copyright (C) 2003 Russell King, PXA MMCI Driver
* Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver
*
* Copyright 2008 Embedded Alley Solutions, Inc.
* Copyright 2009-2011 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/highmem.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/completion.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/module.h>
#include <linux/stmp_device.h>
#include <linux/spi/mxs-spi.h>
#define DRIVER_NAME "mxs-mmc"
#define MXS_MMC_IRQ_BITS (BM_SSP_CTRL1_SDIO_IRQ | \
BM_SSP_CTRL1_RESP_ERR_IRQ | \
BM_SSP_CTRL1_RESP_TIMEOUT_IRQ | \
BM_SSP_CTRL1_DATA_TIMEOUT_IRQ | \
BM_SSP_CTRL1_DATA_CRC_IRQ | \
BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ | \
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \
BM_SSP_CTRL1_FIFO_OVERRUN_IRQ)
/* card detect polling timeout */
#define MXS_MMC_DETECT_TIMEOUT (HZ/2)
struct mxs_mmc_host {
struct mxs_ssp ssp;
struct mmc_host *mmc;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
unsigned char bus_width;
spinlock_t lock;
int sdio_irq_en;
bool broken_cd;
};
static int mxs_mmc_get_cd(struct mmc_host *mmc)
{
struct mxs_mmc_host *host = mmc_priv(mmc);
struct mxs_ssp *ssp = &host->ssp;
int present, ret;
if (host->broken_cd)
return -ENOSYS;
ret = mmc_gpio_get_cd(mmc);
if (ret >= 0)
return ret;
present = mmc->caps & MMC_CAP_NEEDS_POLL ||
!(readl(ssp->base + HW_SSP_STATUS(ssp)) &
BM_SSP_STATUS_CARD_DETECT);
if (mmc->caps2 & MMC_CAP2_CD_ACTIVE_HIGH)
present = !present;
return present;
}
static int mxs_mmc_reset(struct mxs_mmc_host *host)
{
struct mxs_ssp *ssp = &host->ssp;
u32 ctrl0, ctrl1;
int ret;
ret = stmp_reset_block(ssp->base);
if (ret)
return ret;
ctrl0 = BM_SSP_CTRL0_IGNORE_CRC;
ctrl1 = BF_SSP(0x3, CTRL1_SSP_MODE) |
BF_SSP(0x7, CTRL1_WORD_LENGTH) |
BM_SSP_CTRL1_DMA_ENABLE |
BM_SSP_CTRL1_POLARITY |
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN |
BM_SSP_CTRL1_DATA_CRC_IRQ_EN |
BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN |
BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN |
BM_SSP_CTRL1_RESP_ERR_IRQ_EN;
writel(BF_SSP(0xffff, TIMING_TIMEOUT) |
BF_SSP(2, TIMING_CLOCK_DIVIDE) |
BF_SSP(0, TIMING_CLOCK_RATE),
ssp->base + HW_SSP_TIMING(ssp));
if (host->sdio_irq_en) {
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
ctrl1 |= BM_SSP_CTRL1_SDIO_IRQ_EN;
}
writel(ctrl0, ssp->base + HW_SSP_CTRL0);
writel(ctrl1, ssp->base + HW_SSP_CTRL1(ssp));
return 0;
}
static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
struct mmc_command *cmd);
static void mxs_mmc_request_done(struct mxs_mmc_host *host)
{
struct mmc_command *cmd = host->cmd;
struct mmc_data *data = host->data;
struct mmc_request *mrq = host->mrq;
struct mxs_ssp *ssp = &host->ssp;
if (mmc_resp_type(cmd) & MMC_RSP_PRESENT) {
if (mmc_resp_type(cmd) & MMC_RSP_136) {
cmd->resp[3] = readl(ssp->base + HW_SSP_SDRESP0(ssp));
cmd->resp[2] = readl(ssp->base + HW_SSP_SDRESP1(ssp));
cmd->resp[1] = readl(ssp->base + HW_SSP_SDRESP2(ssp));
cmd->resp[0] = readl(ssp->base + HW_SSP_SDRESP3(ssp));
} else {
cmd->resp[0] = readl(ssp->base + HW_SSP_SDRESP0(ssp));
}
}
if (data) {
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, ssp->dma_dir);
/*
* If there was an error on any block, we mark all
* data blocks as being in error.
*/
if (!data->error)
data->bytes_xfered = data->blocks * data->blksz;
else
data->bytes_xfered = 0;
host->data = NULL;
if (mrq->stop) {
mxs_mmc_start_cmd(host, mrq->stop);
return;
}
}
host->mrq = NULL;
mmc_request_done(host->mmc, mrq);
}
static void mxs_mmc_dma_irq_callback(void *param)
{
struct mxs_mmc_host *host = param;
mxs_mmc_request_done(host);
}
static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id)
{
struct mxs_mmc_host *host = dev_id;
struct mmc_command *cmd = host->cmd;
struct mmc_data *data = host->data;
struct mxs_ssp *ssp = &host->ssp;
u32 stat;
spin_lock(&host->lock);
stat = readl(ssp->base + HW_SSP_CTRL1(ssp));
writel(stat & MXS_MMC_IRQ_BITS,
ssp->base + HW_SSP_CTRL1(ssp) + STMP_OFFSET_REG_CLR);
spin_unlock(&host->lock);
if ((stat & BM_SSP_CTRL1_SDIO_IRQ) && (stat & BM_SSP_CTRL1_SDIO_IRQ_EN))
mmc_signal_sdio_irq(host->mmc);
if (stat & BM_SSP_CTRL1_RESP_TIMEOUT_IRQ)
cmd->error = -ETIMEDOUT;
else if (stat & BM_SSP_CTRL1_RESP_ERR_IRQ)
cmd->error = -EIO;
if (data) {
if (stat & (BM_SSP_CTRL1_DATA_TIMEOUT_IRQ |
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ))
data->error = -ETIMEDOUT;
else if (stat & BM_SSP_CTRL1_DATA_CRC_IRQ)
data->error = -EILSEQ;
else if (stat & (BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ |
BM_SSP_CTRL1_FIFO_OVERRUN_IRQ))
data->error = -EIO;
}
return IRQ_HANDLED;
}
static struct dma_async_tx_descriptor *mxs_mmc_prep_dma(
struct mxs_mmc_host *host, unsigned long flags)
{
struct mxs_ssp *ssp = &host->ssp;
struct dma_async_tx_descriptor *desc;
struct mmc_data *data = host->data;
struct scatterlist * sgl;
unsigned int sg_len;
if (data) {
/* data */
dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, ssp->dma_dir);
sgl = data->sg;
sg_len = data->sg_len;
} else {
/* pio */
sgl = (struct scatterlist *) ssp->ssp_pio_words;
sg_len = SSP_PIO_NUM;
}
desc = dmaengine_prep_slave_sg(ssp->dmach,
sgl, sg_len, ssp->slave_dirn, flags);
if (desc) {
desc->callback = mxs_mmc_dma_irq_callback;
desc->callback_param = host;
} else {
if (data)
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, ssp->dma_dir);
}
return desc;
}
static void mxs_mmc_bc(struct mxs_mmc_host *host)
{
struct mxs_ssp *ssp = &host->ssp;
struct mmc_command *cmd = host->cmd;
struct dma_async_tx_descriptor *desc;
u32 ctrl0, cmd0, cmd1;
ctrl0 = BM_SSP_CTRL0_ENABLE | BM_SSP_CTRL0_IGNORE_CRC;
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD) | BM_SSP_CMD0_APPEND_8CYC;
cmd1 = cmd->arg;
if (host->sdio_irq_en) {
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
}
ssp->ssp_pio_words[0] = ctrl0;
ssp->ssp_pio_words[1] = cmd0;
ssp->ssp_pio_words[2] = cmd1;
ssp->dma_dir = DMA_NONE;
ssp->slave_dirn = DMA_TRANS_NONE;
desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK);
if (!desc)
goto out;
dmaengine_submit(desc);
dma_async_issue_pending(ssp->dmach);
return;
out:
dev_warn(mmc_dev(host->mmc),
"%s: failed to prep dma\n", __func__);
}
static void mxs_mmc_ac(struct mxs_mmc_host *host)
{
struct mxs_ssp *ssp = &host->ssp;
struct mmc_command *cmd = host->cmd;
struct dma_async_tx_descriptor *desc;
u32 ignore_crc, get_resp, long_resp;
u32 ctrl0, cmd0, cmd1;
ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
0 : BM_SSP_CTRL0_IGNORE_CRC;
get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
BM_SSP_CTRL0_GET_RESP : 0;
long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
BM_SSP_CTRL0_LONG_RESP : 0;
ctrl0 = BM_SSP_CTRL0_ENABLE | ignore_crc | get_resp | long_resp;
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
cmd1 = cmd->arg;
if (host->sdio_irq_en) {
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
}
ssp->ssp_pio_words[0] = ctrl0;
ssp->ssp_pio_words[1] = cmd0;
ssp->ssp_pio_words[2] = cmd1;
ssp->dma_dir = DMA_NONE;
ssp->slave_dirn = DMA_TRANS_NONE;
desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK);
if (!desc)
goto out;
dmaengine_submit(desc);
dma_async_issue_pending(ssp->dmach);
return;
out:
dev_warn(mmc_dev(host->mmc),
"%s: failed to prep dma\n", __func__);
}
static unsigned short mxs_ns_to_ssp_ticks(unsigned clock_rate, unsigned ns)
{
const unsigned int ssp_timeout_mul = 4096;
/*
* Calculate ticks in ms since ns are large numbers
* and might overflow
*/
const unsigned int clock_per_ms = clock_rate / 1000;
const unsigned int ms = ns / 1000;
const unsigned int ticks = ms * clock_per_ms;
const unsigned int ssp_ticks = ticks / ssp_timeout_mul;
WARN_ON(ssp_ticks == 0);
return ssp_ticks;
}
static void mxs_mmc_adtc(struct mxs_mmc_host *host)
{
struct mmc_command *cmd = host->cmd;
struct mmc_data *data = cmd->data;
struct dma_async_tx_descriptor *desc;
struct scatterlist *sgl = data->sg, *sg;
unsigned int sg_len = data->sg_len;
unsigned int i;
unsigned short dma_data_dir, timeout;
enum dma_transfer_direction slave_dirn;
unsigned int data_size = 0, log2_blksz;
unsigned int blocks = data->blocks;
struct mxs_ssp *ssp = &host->ssp;
u32 ignore_crc, get_resp, long_resp, read;
u32 ctrl0, cmd0, cmd1, val;
ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
0 : BM_SSP_CTRL0_IGNORE_CRC;
get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
BM_SSP_CTRL0_GET_RESP : 0;
long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
BM_SSP_CTRL0_LONG_RESP : 0;
if (data->flags & MMC_DATA_WRITE) {
dma_data_dir = DMA_TO_DEVICE;
slave_dirn = DMA_MEM_TO_DEV;
read = 0;
} else {
dma_data_dir = DMA_FROM_DEVICE;
slave_dirn = DMA_DEV_TO_MEM;
read = BM_SSP_CTRL0_READ;
}
ctrl0 = BF_SSP(host->bus_width, CTRL0_BUS_WIDTH) |
ignore_crc | get_resp | long_resp |
BM_SSP_CTRL0_DATA_XFER | read |
BM_SSP_CTRL0_WAIT_FOR_IRQ |
BM_SSP_CTRL0_ENABLE;
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
/* get logarithm to base 2 of block size for setting register */
log2_blksz = ilog2(data->blksz);
/*
* take special care of the case that data size from data->sg
* is not equal to blocks x blksz
*/
for_each_sg(sgl, sg, sg_len, i)
data_size += sg->length;
if (data_size != data->blocks * data->blksz)
blocks = 1;
/* xfer count, block size and count need to be set differently */
if (ssp_is_old(ssp)) {
ctrl0 |= BF_SSP(data_size, CTRL0_XFER_COUNT);
cmd0 |= BF_SSP(log2_blksz, CMD0_BLOCK_SIZE) |
BF_SSP(blocks - 1, CMD0_BLOCK_COUNT);
} else {
writel(data_size, ssp->base + HW_SSP_XFER_SIZE);
writel(BF_SSP(log2_blksz, BLOCK_SIZE_BLOCK_SIZE) |
BF_SSP(blocks - 1, BLOCK_SIZE_BLOCK_COUNT),
ssp->base + HW_SSP_BLOCK_SIZE);
}
if ((cmd->opcode == MMC_STOP_TRANSMISSION) ||
(cmd->opcode == SD_IO_RW_EXTENDED))
cmd0 |= BM_SSP_CMD0_APPEND_8CYC;
cmd1 = cmd->arg;
if (host->sdio_irq_en) {
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
}
/* set the timeout count */
timeout = mxs_ns_to_ssp_ticks(ssp->clk_rate, data->timeout_ns);
val = readl(ssp->base + HW_SSP_TIMING(ssp));
val &= ~(BM_SSP_TIMING_TIMEOUT);
val |= BF_SSP(timeout, TIMING_TIMEOUT);
writel(val, ssp->base + HW_SSP_TIMING(ssp));
/* pio */
ssp->ssp_pio_words[0] = ctrl0;
ssp->ssp_pio_words[1] = cmd0;
ssp->ssp_pio_words[2] = cmd1;
ssp->dma_dir = DMA_NONE;
ssp->slave_dirn = DMA_TRANS_NONE;
desc = mxs_mmc_prep_dma(host, 0);
if (!desc)
goto out;
/* append data sg */
WARN_ON(host->data != NULL);
host->data = data;
ssp->dma_dir = dma_data_dir;
ssp->slave_dirn = slave_dirn;
desc = mxs_mmc_prep_dma(host, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc)
goto out;
dmaengine_submit(desc);
dma_async_issue_pending(ssp->dmach);
return;
out:
dev_warn(mmc_dev(host->mmc),
"%s: failed to prep dma\n", __func__);
}
static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
struct mmc_command *cmd)
{
host->cmd = cmd;
switch (mmc_cmd_type(cmd)) {
case MMC_CMD_BC:
mxs_mmc_bc(host);
break;
case MMC_CMD_BCR:
mxs_mmc_ac(host);
break;
case MMC_CMD_AC:
mxs_mmc_ac(host);
break;
case MMC_CMD_ADTC:
mxs_mmc_adtc(host);
break;
default:
dev_warn(mmc_dev(host->mmc),
"%s: unknown MMC command\n", __func__);
break;
}
}
static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct mxs_mmc_host *host = mmc_priv(mmc);
WARN_ON(host->mrq != NULL);
host->mrq = mrq;
mxs_mmc_start_cmd(host, mrq->cmd);
}
static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct mxs_mmc_host *host = mmc_priv(mmc);
if (ios->bus_width == MMC_BUS_WIDTH_8)
host->bus_width = 2;
else if (ios->bus_width == MMC_BUS_WIDTH_4)
host->bus_width = 1;
else
host->bus_width = 0;
if (ios->clock)
mxs_ssp_set_clk_rate(&host->ssp, ios->clock);
}
static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct mxs_mmc_host *host = mmc_priv(mmc);
struct mxs_ssp *ssp = &host->ssp;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->sdio_irq_en = enable;
if (enable) {
writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET);
writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
ssp->base + HW_SSP_CTRL1(ssp) + STMP_OFFSET_REG_SET);
} else {
writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR);
writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
ssp->base + HW_SSP_CTRL1(ssp) + STMP_OFFSET_REG_CLR);
}
spin_unlock_irqrestore(&host->lock, flags);
if (enable && readl(ssp->base + HW_SSP_STATUS(ssp)) &
BM_SSP_STATUS_SDIO_IRQ)
mmc_signal_sdio_irq(host->mmc);
}
static const struct mmc_host_ops mxs_mmc_ops = {
.request = mxs_mmc_request,
.get_ro = mmc_gpio_get_ro,
.get_cd = mxs_mmc_get_cd,
.set_ios = mxs_mmc_set_ios,
.enable_sdio_irq = mxs_mmc_enable_sdio_irq,
};
static struct platform_device_id mxs_ssp_ids[] = {
{
.name = "imx23-mmc",
.driver_data = IMX23_SSP,
}, {
.name = "imx28-mmc",
.driver_data = IMX28_SSP,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(platform, mxs_ssp_ids);
static const struct of_device_id mxs_mmc_dt_ids[] = {
{ .compatible = "fsl,imx23-mmc", .data = (void *) IMX23_SSP, },
{ .compatible = "fsl,imx28-mmc", .data = (void *) IMX28_SSP, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_mmc_dt_ids);
static int mxs_mmc_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(mxs_mmc_dt_ids, &pdev->dev);
struct device_node *np = pdev->dev.of_node;
struct mxs_mmc_host *host;
struct mmc_host *mmc;
struct resource *iores;
int ret = 0, irq_err;
struct regulator *reg_vmmc;
struct mxs_ssp *ssp;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq_err = platform_get_irq(pdev, 0);
if (!iores || irq_err < 0)
return -EINVAL;
mmc = mmc_alloc_host(sizeof(struct mxs_mmc_host), &pdev->dev);
if (!mmc)
return -ENOMEM;
host = mmc_priv(mmc);
ssp = &host->ssp;
ssp->dev = &pdev->dev;
ssp->base = devm_ioremap_resource(&pdev->dev, iores);
if (IS_ERR(ssp->base)) {
ret = PTR_ERR(ssp->base);
goto out_mmc_free;
}
ssp->devid = (enum mxs_ssp_id) of_id->data;
host->mmc = mmc;
host->sdio_irq_en = 0;
reg_vmmc = devm_regulator_get(&pdev->dev, "vmmc");
if (!IS_ERR(reg_vmmc)) {
ret = regulator_enable(reg_vmmc);
if (ret) {
dev_err(&pdev->dev,
"Failed to enable vmmc regulator: %d\n", ret);
goto out_mmc_free;
}
}
ssp->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ssp->clk)) {
ret = PTR_ERR(ssp->clk);
goto out_mmc_free;
}
clk_prepare_enable(ssp->clk);
ret = mxs_mmc_reset(host);
if (ret) {
dev_err(&pdev->dev, "Failed to reset mmc: %d\n", ret);
goto out_clk_disable;
}
ssp->dmach = dma_request_slave_channel(&pdev->dev, "rx-tx");
if (!ssp->dmach) {
dev_err(mmc_dev(host->mmc),
"%s: failed to request dma\n", __func__);
ret = -ENODEV;
goto out_clk_disable;
}
/* set mmc core parameters */
mmc->ops = &mxs_mmc_ops;
mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
MMC_CAP_SDIO_IRQ | MMC_CAP_NEEDS_POLL;
host->broken_cd = of_property_read_bool(np, "broken-cd");
mmc->f_min = 400000;
mmc->f_max = 288000000;
ret = mmc_of_parse(mmc);
if (ret)
goto out_clk_disable;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->max_segs = 52;
mmc->max_blk_size = 1 << 0xf;
mmc->max_blk_count = (ssp_is_old(ssp)) ? 0xff : 0xffffff;
mmc->max_req_size = (ssp_is_old(ssp)) ? 0xffff : 0xffffffff;
mmc->max_seg_size = dma_get_max_seg_size(ssp->dmach->device->dev);
platform_set_drvdata(pdev, mmc);
ret = devm_request_irq(&pdev->dev, irq_err, mxs_mmc_irq_handler, 0,
DRIVER_NAME, host);
if (ret)
goto out_free_dma;
spin_lock_init(&host->lock);
ret = mmc_add_host(mmc);
if (ret)
goto out_free_dma;
dev_info(mmc_dev(host->mmc), "initialized\n");
return 0;
out_free_dma:
if (ssp->dmach)
dma_release_channel(ssp->dmach);
out_clk_disable:
clk_disable_unprepare(ssp->clk);
out_mmc_free:
mmc_free_host(mmc);
return ret;
}
static int mxs_mmc_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
struct mxs_mmc_host *host = mmc_priv(mmc);
struct mxs_ssp *ssp = &host->ssp;
mmc_remove_host(mmc);
if (ssp->dmach)
dma_release_channel(ssp->dmach);
clk_disable_unprepare(ssp->clk);
mmc_free_host(mmc);
return 0;
}
#ifdef CONFIG_PM
static int mxs_mmc_suspend(struct device *dev)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
struct mxs_mmc_host *host = mmc_priv(mmc);
struct mxs_ssp *ssp = &host->ssp;
clk_disable_unprepare(ssp->clk);
return 0;
}
static int mxs_mmc_resume(struct device *dev)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
struct mxs_mmc_host *host = mmc_priv(mmc);
struct mxs_ssp *ssp = &host->ssp;
clk_prepare_enable(ssp->clk);
return 0;
}
static const struct dev_pm_ops mxs_mmc_pm_ops = {
.suspend = mxs_mmc_suspend,
.resume = mxs_mmc_resume,
};
#endif
static struct platform_driver mxs_mmc_driver = {
.probe = mxs_mmc_probe,
.remove = mxs_mmc_remove,
.id_table = mxs_ssp_ids,
.driver = {
.name = DRIVER_NAME,
#ifdef CONFIG_PM
.pm = &mxs_mmc_pm_ops,
#endif
.of_match_table = mxs_mmc_dt_ids,
},
};
module_platform_driver(mxs_mmc_driver);
MODULE_DESCRIPTION("FREESCALE MXS MMC peripheral");
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);

View file

@ -0,0 +1,159 @@
/*
* OpenFirmware bindings for the MMC-over-SPI driver
*
* Copyright (c) MontaVista Software, Inc. 2008.
*
* Author: Anton Vorontsov <avorontsov@ru.mvista.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>
#include <linux/spi/mmc_spi.h>
#include <linux/mmc/core.h>
#include <linux/mmc/host.h>
/* For archs that don't support NO_IRQ (such as mips), provide a dummy value */
#ifndef NO_IRQ
#define NO_IRQ 0
#endif
MODULE_LICENSE("GPL");
enum {
CD_GPIO = 0,
WP_GPIO,
NUM_GPIOS,
};
struct of_mmc_spi {
int gpios[NUM_GPIOS];
bool alow_gpios[NUM_GPIOS];
int detect_irq;
struct mmc_spi_platform_data pdata;
};
static struct of_mmc_spi *to_of_mmc_spi(struct device *dev)
{
return container_of(dev->platform_data, struct of_mmc_spi, pdata);
}
static int of_mmc_spi_init(struct device *dev,
irqreturn_t (*irqhandler)(int, void *), void *mmc)
{
struct of_mmc_spi *oms = to_of_mmc_spi(dev);
return request_threaded_irq(oms->detect_irq, NULL, irqhandler, 0,
dev_name(dev), mmc);
}
static void of_mmc_spi_exit(struct device *dev, void *mmc)
{
struct of_mmc_spi *oms = to_of_mmc_spi(dev);
free_irq(oms->detect_irq, mmc);
}
struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct device_node *np = dev->of_node;
struct of_mmc_spi *oms;
const u32 *voltage_ranges;
int num_ranges;
int i;
int ret = -EINVAL;
if (dev->platform_data || !np)
return dev->platform_data;
oms = kzalloc(sizeof(*oms), GFP_KERNEL);
if (!oms)
return NULL;
voltage_ranges = of_get_property(np, "voltage-ranges", &num_ranges);
num_ranges = num_ranges / sizeof(*voltage_ranges) / 2;
if (!voltage_ranges || !num_ranges) {
dev_err(dev, "OF: voltage-ranges unspecified\n");
goto err_ocr;
}
for (i = 0; i < num_ranges; i++) {
const int j = i * 2;
u32 mask;
mask = mmc_vddrange_to_ocrmask(be32_to_cpu(voltage_ranges[j]),
be32_to_cpu(voltage_ranges[j + 1]));
if (!mask) {
ret = -EINVAL;
dev_err(dev, "OF: voltage-range #%d is invalid\n", i);
goto err_ocr;
}
oms->pdata.ocr_mask |= mask;
}
for (i = 0; i < ARRAY_SIZE(oms->gpios); i++) {
enum of_gpio_flags gpio_flags;
oms->gpios[i] = of_get_gpio_flags(np, i, &gpio_flags);
if (!gpio_is_valid(oms->gpios[i]))
continue;
if (gpio_flags & OF_GPIO_ACTIVE_LOW)
oms->alow_gpios[i] = true;
}
if (gpio_is_valid(oms->gpios[CD_GPIO])) {
oms->pdata.cd_gpio = oms->gpios[CD_GPIO];
oms->pdata.flags |= MMC_SPI_USE_CD_GPIO;
if (!oms->alow_gpios[CD_GPIO])
oms->pdata.caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
}
if (gpio_is_valid(oms->gpios[WP_GPIO])) {
oms->pdata.ro_gpio = oms->gpios[WP_GPIO];
oms->pdata.flags |= MMC_SPI_USE_RO_GPIO;
if (!oms->alow_gpios[WP_GPIO])
oms->pdata.caps2 |= MMC_CAP2_RO_ACTIVE_HIGH;
}
oms->detect_irq = irq_of_parse_and_map(np, 0);
if (oms->detect_irq != 0) {
oms->pdata.init = of_mmc_spi_init;
oms->pdata.exit = of_mmc_spi_exit;
} else {
oms->pdata.caps |= MMC_CAP_NEEDS_POLL;
}
dev->platform_data = &oms->pdata;
return dev->platform_data;
err_ocr:
kfree(oms);
return NULL;
}
EXPORT_SYMBOL(mmc_spi_get_pdata);
void mmc_spi_put_pdata(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct device_node *np = dev->of_node;
struct of_mmc_spi *oms = to_of_mmc_spi(dev);
if (!dev->platform_data || !np)
return;
kfree(oms);
dev->platform_data = NULL;
}
EXPORT_SYMBOL(mmc_spi_put_pdata);

1505
drivers/mmc/host/omap.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

896
drivers/mmc/host/pxamci.c Normal file
View file

@ -0,0 +1,896 @@
/*
* linux/drivers/mmc/host/pxa.c - PXA MMCI driver
*
* Copyright (C) 2003 Russell King, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This hardware is really sick:
* - No way to clear interrupts.
* - Have to turn off the clock whenever we touch the device.
* - Doesn't tell you how many data blocks were transferred.
* Yuck!
*
* 1 and 3 byte data transfers not supported
* max block length up to 1023
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/mmc/host.h>
#include <linux/io.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio.h>
#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <asm/sizes.h>
#include <mach/hardware.h>
#include <mach/dma.h>
#include <linux/platform_data/mmc-pxamci.h>
#include "pxamci.h"
#define DRIVER_NAME "pxa2xx-mci"
#define NR_SG 1
#define CLKRT_OFF (~0)
#define mmc_has_26MHz() (cpu_is_pxa300() || cpu_is_pxa310() \
|| cpu_is_pxa935())
struct pxamci_host {
struct mmc_host *mmc;
spinlock_t lock;
struct resource *res;
void __iomem *base;
struct clk *clk;
unsigned long clkrate;
int irq;
int dma;
unsigned int clkrt;
unsigned int cmdat;
unsigned int imask;
unsigned int power_mode;
struct pxamci_platform_data *pdata;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
dma_addr_t sg_dma;
struct pxa_dma_desc *sg_cpu;
unsigned int dma_len;
unsigned int dma_dir;
unsigned int dma_drcmrrx;
unsigned int dma_drcmrtx;
struct regulator *vcc;
};
static inline void pxamci_init_ocr(struct pxamci_host *host)
{
#ifdef CONFIG_REGULATOR
host->vcc = regulator_get_optional(mmc_dev(host->mmc), "vmmc");
if (IS_ERR(host->vcc))
host->vcc = NULL;
else {
host->mmc->ocr_avail = mmc_regulator_get_ocrmask(host->vcc);
if (host->pdata && host->pdata->ocr_mask)
dev_warn(mmc_dev(host->mmc),
"ocr_mask/setpower will not be used\n");
}
#endif
if (host->vcc == NULL) {
/* fall-back to platform data */
host->mmc->ocr_avail = host->pdata ?
host->pdata->ocr_mask :
MMC_VDD_32_33 | MMC_VDD_33_34;
}
}
static inline int pxamci_set_power(struct pxamci_host *host,
unsigned char power_mode,
unsigned int vdd)
{
int on;
if (host->vcc) {
int ret;
if (power_mode == MMC_POWER_UP) {
ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
if (ret)
return ret;
} else if (power_mode == MMC_POWER_OFF) {
ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0);
if (ret)
return ret;
}
}
if (!host->vcc && host->pdata &&
gpio_is_valid(host->pdata->gpio_power)) {
on = ((1 << vdd) & host->pdata->ocr_mask);
gpio_set_value(host->pdata->gpio_power,
!!on ^ host->pdata->gpio_power_invert);
}
if (!host->vcc && host->pdata && host->pdata->setpower)
return host->pdata->setpower(mmc_dev(host->mmc), vdd);
return 0;
}
static void pxamci_stop_clock(struct pxamci_host *host)
{
if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
unsigned long timeout = 10000;
unsigned int v;
writel(STOP_CLOCK, host->base + MMC_STRPCL);
do {
v = readl(host->base + MMC_STAT);
if (!(v & STAT_CLK_EN))
break;
udelay(1);
} while (timeout--);
if (v & STAT_CLK_EN)
dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
}
}
static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->imask &= ~mask;
writel(host->imask, host->base + MMC_I_MASK);
spin_unlock_irqrestore(&host->lock, flags);
}
static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->imask |= mask;
writel(host->imask, host->base + MMC_I_MASK);
spin_unlock_irqrestore(&host->lock, flags);
}
static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
{
unsigned int nob = data->blocks;
unsigned long long clks;
unsigned int timeout;
bool dalgn = 0;
u32 dcmd;
int i;
host->data = data;
if (data->flags & MMC_DATA_STREAM)
nob = 0xffff;
writel(nob, host->base + MMC_NOB);
writel(data->blksz, host->base + MMC_BLKLEN);
clks = (unsigned long long)data->timeout_ns * host->clkrate;
do_div(clks, 1000000000UL);
timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
writel((timeout + 255) / 256, host->base + MMC_RDTO);
if (data->flags & MMC_DATA_READ) {
host->dma_dir = DMA_FROM_DEVICE;
dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC;
DRCMR(host->dma_drcmrtx) = 0;
DRCMR(host->dma_drcmrrx) = host->dma | DRCMR_MAPVLD;
} else {
host->dma_dir = DMA_TO_DEVICE;
dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
DRCMR(host->dma_drcmrrx) = 0;
DRCMR(host->dma_drcmrtx) = host->dma | DRCMR_MAPVLD;
}
dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
host->dma_dir);
for (i = 0; i < host->dma_len; i++) {
unsigned int length = sg_dma_len(&data->sg[i]);
host->sg_cpu[i].dcmd = dcmd | length;
if (length & 31 && !(data->flags & MMC_DATA_READ))
host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN;
/* Not aligned to 8-byte boundary? */
if (sg_dma_address(&data->sg[i]) & 0x7)
dalgn = 1;
if (data->flags & MMC_DATA_READ) {
host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
} else {
host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
}
host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
sizeof(struct pxa_dma_desc);
}
host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
wmb();
/*
* The PXA27x DMA controller encounters overhead when working with
* unaligned (to 8-byte boundaries) data, so switch on byte alignment
* mode only if we have unaligned data.
*/
if (dalgn)
DALGN |= (1 << host->dma);
else
DALGN &= ~(1 << host->dma);
DDADR(host->dma) = host->sg_dma;
/*
* workaround for erratum #91:
* only start DMA now if we are doing a read,
* otherwise we wait until CMD/RESP has finished
* before starting DMA.
*/
if (!cpu_is_pxa27x() || data->flags & MMC_DATA_READ)
DCSR(host->dma) = DCSR_RUN;
}
static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
{
WARN_ON(host->cmd != NULL);
host->cmd = cmd;
if (cmd->flags & MMC_RSP_BUSY)
cmdat |= CMDAT_BUSY;
#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
switch (RSP_TYPE(mmc_resp_type(cmd))) {
case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
cmdat |= CMDAT_RESP_SHORT;
break;
case RSP_TYPE(MMC_RSP_R3):
cmdat |= CMDAT_RESP_R3;
break;
case RSP_TYPE(MMC_RSP_R2):
cmdat |= CMDAT_RESP_R2;
break;
default:
break;
}
writel(cmd->opcode, host->base + MMC_CMD);
writel(cmd->arg >> 16, host->base + MMC_ARGH);
writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
writel(cmdat, host->base + MMC_CMDAT);
writel(host->clkrt, host->base + MMC_CLKRT);
writel(START_CLOCK, host->base + MMC_STRPCL);
pxamci_enable_irq(host, END_CMD_RES);
}
static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
{
host->mrq = NULL;
host->cmd = NULL;
host->data = NULL;
mmc_request_done(host->mmc, mrq);
}
static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
{
struct mmc_command *cmd = host->cmd;
int i;
u32 v;
if (!cmd)
return 0;
host->cmd = NULL;
/*
* Did I mention this is Sick. We always need to
* discard the upper 8 bits of the first 16-bit word.
*/
v = readl(host->base + MMC_RES) & 0xffff;
for (i = 0; i < 4; i++) {
u32 w1 = readl(host->base + MMC_RES) & 0xffff;
u32 w2 = readl(host->base + MMC_RES) & 0xffff;
cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
v = w2;
}
if (stat & STAT_TIME_OUT_RESPONSE) {
cmd->error = -ETIMEDOUT;
} else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
/*
* workaround for erratum #42:
* Intel PXA27x Family Processor Specification Update Rev 001
* A bogus CRC error can appear if the msb of a 136 bit
* response is a one.
*/
if (cpu_is_pxa27x() &&
(cmd->flags & MMC_RSP_136 && cmd->resp[0] & 0x80000000))
pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode);
else
cmd->error = -EILSEQ;
}
pxamci_disable_irq(host, END_CMD_RES);
if (host->data && !cmd->error) {
pxamci_enable_irq(host, DATA_TRAN_DONE);
/*
* workaround for erratum #91, if doing write
* enable DMA late
*/
if (cpu_is_pxa27x() && host->data->flags & MMC_DATA_WRITE)
DCSR(host->dma) = DCSR_RUN;
} else {
pxamci_finish_request(host, host->mrq);
}
return 1;
}
static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
{
struct mmc_data *data = host->data;
if (!data)
return 0;
DCSR(host->dma) = 0;
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
host->dma_dir);
if (stat & STAT_READ_TIME_OUT)
data->error = -ETIMEDOUT;
else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
data->error = -EILSEQ;
/*
* There appears to be a hardware design bug here. There seems to
* be no way to find out how much data was transferred to the card.
* This means that if there was an error on any block, we mark all
* data blocks as being in error.
*/
if (!data->error)
data->bytes_xfered = data->blocks * data->blksz;
else
data->bytes_xfered = 0;
pxamci_disable_irq(host, DATA_TRAN_DONE);
host->data = NULL;
if (host->mrq->stop) {
pxamci_stop_clock(host);
pxamci_start_cmd(host, host->mrq->stop, host->cmdat);
} else {
pxamci_finish_request(host, host->mrq);
}
return 1;
}
static irqreturn_t pxamci_irq(int irq, void *devid)
{
struct pxamci_host *host = devid;
unsigned int ireg;
int handled = 0;
ireg = readl(host->base + MMC_I_REG) & ~readl(host->base + MMC_I_MASK);
if (ireg) {
unsigned stat = readl(host->base + MMC_STAT);
pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);
if (ireg & END_CMD_RES)
handled |= pxamci_cmd_done(host, stat);
if (ireg & DATA_TRAN_DONE)
handled |= pxamci_data_done(host, stat);
if (ireg & SDIO_INT) {
mmc_signal_sdio_irq(host->mmc);
handled = 1;
}
}
return IRQ_RETVAL(handled);
}
static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct pxamci_host *host = mmc_priv(mmc);
unsigned int cmdat;
WARN_ON(host->mrq != NULL);
host->mrq = mrq;
pxamci_stop_clock(host);
cmdat = host->cmdat;
host->cmdat &= ~CMDAT_INIT;
if (mrq->data) {
pxamci_setup_data(host, mrq->data);
cmdat &= ~CMDAT_BUSY;
cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
if (mrq->data->flags & MMC_DATA_WRITE)
cmdat |= CMDAT_WRITE;
if (mrq->data->flags & MMC_DATA_STREAM)
cmdat |= CMDAT_STREAM;
}
pxamci_start_cmd(host, mrq->cmd, cmdat);
}
static int pxamci_get_ro(struct mmc_host *mmc)
{
struct pxamci_host *host = mmc_priv(mmc);
if (host->pdata && gpio_is_valid(host->pdata->gpio_card_ro)) {
if (host->pdata->gpio_card_ro_invert)
return !gpio_get_value(host->pdata->gpio_card_ro);
else
return gpio_get_value(host->pdata->gpio_card_ro);
}
if (host->pdata && host->pdata->get_ro)
return !!host->pdata->get_ro(mmc_dev(mmc));
/*
* Board doesn't support read only detection; let the mmc core
* decide what to do.
*/
return -ENOSYS;
}
static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct pxamci_host *host = mmc_priv(mmc);
if (ios->clock) {
unsigned long rate = host->clkrate;
unsigned int clk = rate / ios->clock;
if (host->clkrt == CLKRT_OFF)
clk_prepare_enable(host->clk);
if (ios->clock == 26000000) {
/* to support 26MHz */
host->clkrt = 7;
} else {
/* to handle (19.5MHz, 26MHz) */
if (!clk)
clk = 1;
/*
* clk might result in a lower divisor than we
* desire. check for that condition and adjust
* as appropriate.
*/
if (rate / clk > ios->clock)
clk <<= 1;
host->clkrt = fls(clk) - 1;
}
/*
* we write clkrt on the next command
*/
} else {
pxamci_stop_clock(host);
if (host->clkrt != CLKRT_OFF) {
host->clkrt = CLKRT_OFF;
clk_disable_unprepare(host->clk);
}
}
if (host->power_mode != ios->power_mode) {
int ret;
host->power_mode = ios->power_mode;
ret = pxamci_set_power(host, ios->power_mode, ios->vdd);
if (ret) {
dev_err(mmc_dev(mmc), "unable to set power\n");
/*
* The .set_ios() function in the mmc_host_ops
* struct return void, and failing to set the
* power should be rare so we print an error and
* return here.
*/
return;
}
if (ios->power_mode == MMC_POWER_ON)
host->cmdat |= CMDAT_INIT;
}
if (ios->bus_width == MMC_BUS_WIDTH_4)
host->cmdat |= CMDAT_SD_4DAT;
else
host->cmdat &= ~CMDAT_SD_4DAT;
dev_dbg(mmc_dev(mmc), "PXAMCI: clkrt = %x cmdat = %x\n",
host->clkrt, host->cmdat);
}
static void pxamci_enable_sdio_irq(struct mmc_host *host, int enable)
{
struct pxamci_host *pxa_host = mmc_priv(host);
if (enable)
pxamci_enable_irq(pxa_host, SDIO_INT);
else
pxamci_disable_irq(pxa_host, SDIO_INT);
}
static const struct mmc_host_ops pxamci_ops = {
.request = pxamci_request,
.get_ro = pxamci_get_ro,
.set_ios = pxamci_set_ios,
.enable_sdio_irq = pxamci_enable_sdio_irq,
};
static void pxamci_dma_irq(int dma, void *devid)
{
struct pxamci_host *host = devid;
int dcsr = DCSR(dma);
DCSR(dma) = dcsr & ~DCSR_STOPIRQEN;
if (dcsr & DCSR_ENDINTR) {
writel(BUF_PART_FULL, host->base + MMC_PRTBUF);
} else {
pr_err("%s: DMA error on channel %d (DCSR=%#x)\n",
mmc_hostname(host->mmc), dma, dcsr);
host->data->error = -EIO;
pxamci_data_done(host, 0);
}
}
static irqreturn_t pxamci_detect_irq(int irq, void *devid)
{
struct pxamci_host *host = mmc_priv(devid);
mmc_detect_change(devid, msecs_to_jiffies(host->pdata->detect_delay_ms));
return IRQ_HANDLED;
}
#ifdef CONFIG_OF
static const struct of_device_id pxa_mmc_dt_ids[] = {
{ .compatible = "marvell,pxa-mmc" },
{ }
};
MODULE_DEVICE_TABLE(of, pxa_mmc_dt_ids);
static int pxamci_of_init(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct pxamci_platform_data *pdata;
u32 tmp;
if (!np)
return 0;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
pdata->gpio_card_detect =
of_get_named_gpio(np, "cd-gpios", 0);
pdata->gpio_card_ro =
of_get_named_gpio(np, "wp-gpios", 0);
/* pxa-mmc specific */
pdata->gpio_power =
of_get_named_gpio(np, "pxa-mmc,gpio-power", 0);
if (of_property_read_u32(np, "pxa-mmc,detect-delay-ms", &tmp) == 0)
pdata->detect_delay_ms = tmp;
pdev->dev.platform_data = pdata;
return 0;
}
#else
static int pxamci_of_init(struct platform_device *pdev)
{
return 0;
}
#endif
static int pxamci_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct pxamci_host *host = NULL;
struct resource *r, *dmarx, *dmatx;
int ret, irq, gpio_cd = -1, gpio_ro = -1, gpio_power = -1;
ret = pxamci_of_init(pdev);
if (ret)
return ret;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!r || irq < 0)
return -ENXIO;
r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
if (!r)
return -EBUSY;
mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}
mmc->ops = &pxamci_ops;
/*
* We can do SG-DMA, but we don't because we never know how much
* data we successfully wrote to the card.
*/
mmc->max_segs = NR_SG;
/*
* Our hardware DMA can handle a maximum of one page per SG entry.
*/
mmc->max_seg_size = PAGE_SIZE;
/*
* Block length register is only 10 bits before PXA27x.
*/
mmc->max_blk_size = cpu_is_pxa25x() ? 1023 : 2048;
/*
* Block count register is 16 bits.
*/
mmc->max_blk_count = 65535;
host = mmc_priv(mmc);
host->mmc = mmc;
host->dma = -1;
host->pdata = pdev->dev.platform_data;
host->clkrt = CLKRT_OFF;
host->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
host->clk = NULL;
goto out;
}
host->clkrate = clk_get_rate(host->clk);
/*
* Calculate minimum clock rate, rounding up.
*/
mmc->f_min = (host->clkrate + 63) / 64;
mmc->f_max = (mmc_has_26MHz()) ? 26000000 : host->clkrate;
pxamci_init_ocr(host);
mmc->caps = 0;
host->cmdat = 0;
if (!cpu_is_pxa25x()) {
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
host->cmdat |= CMDAT_SDIO_INT_EN;
if (mmc_has_26MHz())
mmc->caps |= MMC_CAP_MMC_HIGHSPEED |
MMC_CAP_SD_HIGHSPEED;
}
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
if (!host->sg_cpu) {
ret = -ENOMEM;
goto out;
}
spin_lock_init(&host->lock);
host->res = r;
host->irq = irq;
host->imask = MMC_I_MASK_ALL;
host->base = ioremap(r->start, SZ_4K);
if (!host->base) {
ret = -ENOMEM;
goto out;
}
/*
* Ensure that the host controller is shut down, and setup
* with our defaults.
*/
pxamci_stop_clock(host);
writel(0, host->base + MMC_SPI);
writel(64, host->base + MMC_RESTO);
writel(host->imask, host->base + MMC_I_MASK);
host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
pxamci_dma_irq, host);
if (host->dma < 0) {
ret = -EBUSY;
goto out;
}
ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
if (ret)
goto out;
platform_set_drvdata(pdev, mmc);
dmarx = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmarx) {
ret = -ENXIO;
goto out;
}
host->dma_drcmrrx = dmarx->start;
dmatx = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmatx) {
ret = -ENXIO;
goto out;
}
host->dma_drcmrtx = dmatx->start;
if (host->pdata) {
gpio_cd = host->pdata->gpio_card_detect;
gpio_ro = host->pdata->gpio_card_ro;
gpio_power = host->pdata->gpio_power;
}
if (gpio_is_valid(gpio_power)) {
ret = gpio_request(gpio_power, "mmc card power");
if (ret) {
dev_err(&pdev->dev, "Failed requesting gpio_power %d\n", gpio_power);
goto out;
}
gpio_direction_output(gpio_power,
host->pdata->gpio_power_invert);
}
if (gpio_is_valid(gpio_ro)) {
ret = gpio_request(gpio_ro, "mmc card read only");
if (ret) {
dev_err(&pdev->dev, "Failed requesting gpio_ro %d\n", gpio_ro);
goto err_gpio_ro;
}
gpio_direction_input(gpio_ro);
}
if (gpio_is_valid(gpio_cd)) {
ret = gpio_request(gpio_cd, "mmc card detect");
if (ret) {
dev_err(&pdev->dev, "Failed requesting gpio_cd %d\n", gpio_cd);
goto err_gpio_cd;
}
gpio_direction_input(gpio_cd);
ret = request_irq(gpio_to_irq(gpio_cd), pxamci_detect_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"mmc card detect", mmc);
if (ret) {
dev_err(&pdev->dev, "failed to request card detect IRQ\n");
goto err_request_irq;
}
}
if (host->pdata && host->pdata->init)
host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);
if (gpio_is_valid(gpio_power) && host->pdata->setpower)
dev_warn(&pdev->dev, "gpio_power and setpower() both defined\n");
if (gpio_is_valid(gpio_ro) && host->pdata->get_ro)
dev_warn(&pdev->dev, "gpio_ro and get_ro() both defined\n");
mmc_add_host(mmc);
return 0;
err_request_irq:
gpio_free(gpio_cd);
err_gpio_cd:
gpio_free(gpio_ro);
err_gpio_ro:
gpio_free(gpio_power);
out:
if (host) {
if (host->dma >= 0)
pxa_free_dma(host->dma);
if (host->base)
iounmap(host->base);
if (host->sg_cpu)
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
if (host->clk)
clk_put(host->clk);
}
if (mmc)
mmc_free_host(mmc);
release_resource(r);
return ret;
}
static int pxamci_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
int gpio_cd = -1, gpio_ro = -1, gpio_power = -1;
if (mmc) {
struct pxamci_host *host = mmc_priv(mmc);
mmc_remove_host(mmc);
if (host->pdata) {
gpio_cd = host->pdata->gpio_card_detect;
gpio_ro = host->pdata->gpio_card_ro;
gpio_power = host->pdata->gpio_power;
}
if (gpio_is_valid(gpio_cd)) {
free_irq(gpio_to_irq(gpio_cd), mmc);
gpio_free(gpio_cd);
}
if (gpio_is_valid(gpio_ro))
gpio_free(gpio_ro);
if (gpio_is_valid(gpio_power))
gpio_free(gpio_power);
if (host->vcc)
regulator_put(host->vcc);
if (host->pdata && host->pdata->exit)
host->pdata->exit(&pdev->dev, mmc);
pxamci_stop_clock(host);
writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
host->base + MMC_I_MASK);
DRCMR(host->dma_drcmrrx) = 0;
DRCMR(host->dma_drcmrtx) = 0;
free_irq(host->irq, host);
pxa_free_dma(host->dma);
iounmap(host->base);
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
clk_put(host->clk);
release_resource(host->res);
mmc_free_host(mmc);
}
return 0;
}
static struct platform_driver pxamci_driver = {
.probe = pxamci_probe,
.remove = pxamci_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(pxa_mmc_dt_ids),
},
};
module_platform_driver(pxamci_driver);
MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa2xx-mci");

90
drivers/mmc/host/pxamci.h Normal file
View file

@ -0,0 +1,90 @@
#define MMC_STRPCL 0x0000
#define STOP_CLOCK (1 << 0)
#define START_CLOCK (2 << 0)
#define MMC_STAT 0x0004
#define STAT_END_CMD_RES (1 << 13)
#define STAT_PRG_DONE (1 << 12)
#define STAT_DATA_TRAN_DONE (1 << 11)
#define STAT_CLK_EN (1 << 8)
#define STAT_RECV_FIFO_FULL (1 << 7)
#define STAT_XMIT_FIFO_EMPTY (1 << 6)
#define STAT_RES_CRC_ERR (1 << 5)
#define STAT_SPI_READ_ERROR_TOKEN (1 << 4)
#define STAT_CRC_READ_ERROR (1 << 3)
#define STAT_CRC_WRITE_ERROR (1 << 2)
#define STAT_TIME_OUT_RESPONSE (1 << 1)
#define STAT_READ_TIME_OUT (1 << 0)
#define MMC_CLKRT 0x0008 /* 3 bit */
#define MMC_SPI 0x000c
#define SPI_CS_ADDRESS (1 << 3)
#define SPI_CS_EN (1 << 2)
#define CRC_ON (1 << 1)
#define SPI_EN (1 << 0)
#define MMC_CMDAT 0x0010
#define CMDAT_SDIO_INT_EN (1 << 11)
#define CMDAT_SD_4DAT (1 << 8)
#define CMDAT_DMAEN (1 << 7)
#define CMDAT_INIT (1 << 6)
#define CMDAT_BUSY (1 << 5)
#define CMDAT_STREAM (1 << 4) /* 1 = stream */
#define CMDAT_WRITE (1 << 3) /* 1 = write */
#define CMDAT_DATAEN (1 << 2)
#define CMDAT_RESP_NONE (0 << 0)
#define CMDAT_RESP_SHORT (1 << 0)
#define CMDAT_RESP_R2 (2 << 0)
#define CMDAT_RESP_R3 (3 << 0)
#define MMC_RESTO 0x0014 /* 7 bit */
#define MMC_RDTO 0x0018 /* 16 bit */
#define MMC_BLKLEN 0x001c /* 10 bit */
#define MMC_NOB 0x0020 /* 16 bit */
#define MMC_PRTBUF 0x0024
#define BUF_PART_FULL (1 << 0)
#define MMC_I_MASK 0x0028
/*PXA27x MMC interrupts*/
#define SDIO_SUSPEND_ACK (1 << 12)
#define SDIO_INT (1 << 11)
#define RD_STALLED (1 << 10)
#define RES_ERR (1 << 9)
#define DAT_ERR (1 << 8)
#define TINT (1 << 7)
/*PXA2xx MMC interrupts*/
#define TXFIFO_WR_REQ (1 << 6)
#define RXFIFO_RD_REQ (1 << 5)
#define CLK_IS_OFF (1 << 4)
#define STOP_CMD (1 << 3)
#define END_CMD_RES (1 << 2)
#define PRG_DONE (1 << 1)
#define DATA_TRAN_DONE (1 << 0)
#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx)
#define MMC_I_MASK_ALL 0x00001fff
#else
#define MMC_I_MASK_ALL 0x0000007f
#endif
#define MMC_I_REG 0x002c
/* same as MMC_I_MASK */
#define MMC_CMD 0x0030
#define MMC_ARGH 0x0034 /* 16 bit */
#define MMC_ARGL 0x0038 /* 16 bit */
#define MMC_RES 0x003c /* 16 bit */
#define MMC_RXFIFO 0x0040 /* 8 bit */
#define MMC_TXFIFO 0x0044 /* 8 bit */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1889
drivers/mmc/host/s3cmci.c Normal file

File diff suppressed because it is too large Load diff

80
drivers/mmc/host/s3cmci.h Normal file
View file

@ -0,0 +1,80 @@
/*
* linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver
*
* Copyright (C) 2004-2006 Thomas Kleffel, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
enum s3cmci_waitfor {
COMPLETION_NONE,
COMPLETION_FINALIZE,
COMPLETION_CMDSENT,
COMPLETION_RSPFIN,
COMPLETION_XFERFINISH,
COMPLETION_XFERFINISH_RSPFIN,
};
struct s3cmci_host {
struct platform_device *pdev;
struct s3c24xx_mci_pdata *pdata;
struct mmc_host *mmc;
struct resource *mem;
struct clk *clk;
void __iomem *base;
int irq;
int irq_cd;
struct dma_chan *dma;
unsigned long clk_rate;
unsigned long clk_div;
unsigned long real_rate;
u8 prescaler;
int is2440;
unsigned sdiimsk;
unsigned sdidata;
bool irq_disabled;
bool irq_enabled;
bool irq_state;
int sdio_irqen;
struct mmc_request *mrq;
int cmd_is_stop;
spinlock_t complete_lock;
enum s3cmci_waitfor complete_what;
int dma_complete;
u32 pio_sgptr;
u32 pio_bytes;
u32 pio_count;
u32 *pio_ptr;
#define XFER_NONE 0
#define XFER_READ 1
#define XFER_WRITE 2
u32 pio_active;
int bus_width;
char dbgmsg_cmd[301];
char dbgmsg_dat[301];
char *status;
unsigned int ccnt, dcnt;
struct tasklet_struct pio_tasklet;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_root;
struct dentry *debug_state;
struct dentry *debug_regs;
#endif
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};

View file

@ -0,0 +1,464 @@
/*
* Secure Digital Host Controller Interface ACPI driver.
*
* Copyright (c) 2012, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/compiler.h>
#include <linux/stddef.h>
#include <linux/bitops.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/acpi.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/mmc/host.h>
#include <linux/mmc/pm.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/mmc/sdhci.h>
#include "sdhci.h"
enum {
SDHCI_ACPI_SD_CD = BIT(0),
SDHCI_ACPI_RUNTIME_PM = BIT(1),
SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL = BIT(2),
};
struct sdhci_acpi_chip {
const struct sdhci_ops *ops;
unsigned int quirks;
unsigned int quirks2;
unsigned long caps;
unsigned int caps2;
mmc_pm_flag_t pm_caps;
};
struct sdhci_acpi_slot {
const struct sdhci_acpi_chip *chip;
unsigned int quirks;
unsigned int quirks2;
unsigned long caps;
unsigned int caps2;
mmc_pm_flag_t pm_caps;
unsigned int flags;
int (*probe_slot)(struct platform_device *, const char *, const char *);
int (*remove_slot)(struct platform_device *);
};
struct sdhci_acpi_host {
struct sdhci_host *host;
const struct sdhci_acpi_slot *slot;
struct platform_device *pdev;
bool use_runtime_pm;
};
static inline bool sdhci_acpi_flag(struct sdhci_acpi_host *c, unsigned int flag)
{
return c->slot && (c->slot->flags & flag);
}
static int sdhci_acpi_enable_dma(struct sdhci_host *host)
{
return 0;
}
static void sdhci_acpi_int_hw_reset(struct sdhci_host *host)
{
u8 reg;
reg = sdhci_readb(host, SDHCI_POWER_CONTROL);
reg |= 0x10;
sdhci_writeb(host, reg, SDHCI_POWER_CONTROL);
/* For eMMC, minimum is 1us but give it 9us for good measure */
udelay(9);
reg &= ~0x10;
sdhci_writeb(host, reg, SDHCI_POWER_CONTROL);
/* For eMMC, minimum is 200us but give it 300us for good measure */
usleep_range(300, 1000);
}
static const struct sdhci_ops sdhci_acpi_ops_dflt = {
.set_clock = sdhci_set_clock,
.enable_dma = sdhci_acpi_enable_dma,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static const struct sdhci_ops sdhci_acpi_ops_int = {
.set_clock = sdhci_set_clock,
.enable_dma = sdhci_acpi_enable_dma,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.hw_reset = sdhci_acpi_int_hw_reset,
};
static const struct sdhci_acpi_chip sdhci_acpi_chip_int = {
.ops = &sdhci_acpi_ops_int,
};
static int sdhci_acpi_emmc_probe_slot(struct platform_device *pdev,
const char *hid, const char *uid)
{
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
struct sdhci_host *host;
if (!c || !c->host)
return 0;
host = c->host;
/* Platform specific code during emmc proble slot goes here */
if (hid && uid && !strcmp(hid, "80860F14") && !strcmp(uid, "1") &&
sdhci_readl(host, SDHCI_CAPABILITIES) == 0x446cc8b2 &&
sdhci_readl(host, SDHCI_CAPABILITIES_1) == 0x00000807)
host->timeout_clk = 1000; /* 1000 kHz i.e. 1 MHz */
return 0;
}
static int sdhci_acpi_sdio_probe_slot(struct platform_device *pdev,
const char *hid, const char *uid)
{
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
struct sdhci_host *host;
if (!c || !c->host)
return 0;
host = c->host;
/* Platform specific code during emmc proble slot goes here */
return 0;
}
static int sdhci_acpi_sd_probe_slot(struct platform_device *pdev,
const char *hid, const char *uid)
{
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
struct sdhci_host *host;
if (!c || !c->host || !c->slot)
return 0;
host = c->host;
/* Platform specific code during emmc proble slot goes here */
return 0;
}
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_emmc = {
.chip = &sdhci_acpi_chip_int,
.caps = MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE |
MMC_CAP_HW_RESET | MMC_CAP_1_8V_DDR,
.caps2 = MMC_CAP2_HC_ERASE_SZ,
.flags = SDHCI_ACPI_RUNTIME_PM,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_STOP_WITH_TC,
.probe_slot = sdhci_acpi_emmc_probe_slot,
};
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = {
.quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION,
.quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON,
.caps = MMC_CAP_NONREMOVABLE | MMC_CAP_POWER_OFF_CARD,
.flags = SDHCI_ACPI_RUNTIME_PM,
.pm_caps = MMC_PM_KEEP_POWER,
.probe_slot = sdhci_acpi_sdio_probe_slot,
};
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = {
.flags = SDHCI_ACPI_SD_CD | SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL |
SDHCI_ACPI_RUNTIME_PM,
.quirks2 = SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON |
SDHCI_QUIRK2_STOP_WITH_TC,
.probe_slot = sdhci_acpi_sd_probe_slot,
};
struct sdhci_acpi_uid_slot {
const char *hid;
const char *uid;
const struct sdhci_acpi_slot *slot;
};
static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = {
{ "80860F14" , "1" , &sdhci_acpi_slot_int_emmc },
{ "80860F14" , "3" , &sdhci_acpi_slot_int_sd },
{ "80860F16" , NULL, &sdhci_acpi_slot_int_sd },
{ "INT33BB" , "2" , &sdhci_acpi_slot_int_sdio },
{ "INT33BB" , "3" , &sdhci_acpi_slot_int_sd },
{ "INT33C6" , NULL, &sdhci_acpi_slot_int_sdio },
{ "INT3436" , NULL, &sdhci_acpi_slot_int_sdio },
{ "PNP0D40" },
{ },
};
static const struct acpi_device_id sdhci_acpi_ids[] = {
{ "80860F14" },
{ "80860F16" },
{ "INT33BB" },
{ "INT33C6" },
{ "INT3436" },
{ "PNP0D40" },
{ },
};
MODULE_DEVICE_TABLE(acpi, sdhci_acpi_ids);
static const struct sdhci_acpi_slot *sdhci_acpi_get_slot(const char *hid,
const char *uid)
{
const struct sdhci_acpi_uid_slot *u;
for (u = sdhci_acpi_uids; u->hid; u++) {
if (strcmp(u->hid, hid))
continue;
if (!u->uid)
return u->slot;
if (uid && !strcmp(u->uid, uid))
return u->slot;
}
return NULL;
}
static int sdhci_acpi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
acpi_handle handle = ACPI_HANDLE(dev);
struct acpi_device *device;
struct sdhci_acpi_host *c;
struct sdhci_host *host;
struct resource *iomem;
resource_size_t len;
const char *hid;
const char *uid;
int err;
if (acpi_bus_get_device(handle, &device))
return -ENODEV;
if (acpi_bus_get_status(device) || !device->status.present)
return -ENODEV;
hid = acpi_device_hid(device);
uid = device->pnp.unique_id;
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iomem)
return -ENOMEM;
len = resource_size(iomem);
if (len < 0x100)
dev_err(dev, "Invalid iomem size!\n");
if (!devm_request_mem_region(dev, iomem->start, len, dev_name(dev)))
return -ENOMEM;
host = sdhci_alloc_host(dev, sizeof(struct sdhci_acpi_host));
if (IS_ERR(host))
return PTR_ERR(host);
c = sdhci_priv(host);
c->host = host;
c->slot = sdhci_acpi_get_slot(hid, uid);
c->pdev = pdev;
c->use_runtime_pm = sdhci_acpi_flag(c, SDHCI_ACPI_RUNTIME_PM);
platform_set_drvdata(pdev, c);
host->hw_name = "ACPI";
host->ops = &sdhci_acpi_ops_dflt;
host->irq = platform_get_irq(pdev, 0);
host->ioaddr = devm_ioremap_nocache(dev, iomem->start,
resource_size(iomem));
if (host->ioaddr == NULL) {
err = -ENOMEM;
goto err_free;
}
if (!dev->dma_mask) {
u64 dma_mask;
if (sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) {
/* 64-bit DMA is not supported at present */
dma_mask = DMA_BIT_MASK(32);
} else {
dma_mask = DMA_BIT_MASK(32);
}
err = dma_coerce_mask_and_coherent(dev, dma_mask);
if (err)
goto err_free;
}
if (c->slot) {
if (c->slot->probe_slot) {
err = c->slot->probe_slot(pdev, hid, uid);
if (err)
goto err_free;
}
if (c->slot->chip) {
host->ops = c->slot->chip->ops;
host->quirks |= c->slot->chip->quirks;
host->quirks2 |= c->slot->chip->quirks2;
host->mmc->caps |= c->slot->chip->caps;
host->mmc->caps2 |= c->slot->chip->caps2;
host->mmc->pm_caps |= c->slot->chip->pm_caps;
}
host->quirks |= c->slot->quirks;
host->quirks2 |= c->slot->quirks2;
host->mmc->caps |= c->slot->caps;
host->mmc->caps2 |= c->slot->caps2;
host->mmc->pm_caps |= c->slot->pm_caps;
}
host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;
if (sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD)) {
bool v = sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL);
if (mmc_gpiod_request_cd(host->mmc, NULL, 0, v, 0, NULL)) {
dev_warn(dev, "failed to setup card detect gpio\n");
c->use_runtime_pm = false;
}
}
err = sdhci_add_host(host);
if (err)
goto err_free;
if (c->use_runtime_pm) {
pm_runtime_set_active(dev);
pm_suspend_ignore_children(dev, 1);
pm_runtime_set_autosuspend_delay(dev, 50);
pm_runtime_use_autosuspend(dev);
pm_runtime_enable(dev);
}
return 0;
err_free:
sdhci_free_host(c->host);
return err;
}
static int sdhci_acpi_remove(struct platform_device *pdev)
{
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
int dead;
if (c->use_runtime_pm) {
pm_runtime_get_sync(dev);
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
}
if (c->slot && c->slot->remove_slot)
c->slot->remove_slot(pdev);
dead = (sdhci_readl(c->host, SDHCI_INT_STATUS) == ~0);
sdhci_remove_host(c->host, dead);
sdhci_free_host(c->host);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_acpi_suspend(struct device *dev)
{
struct sdhci_acpi_host *c = dev_get_drvdata(dev);
return sdhci_suspend_host(c->host);
}
static int sdhci_acpi_resume(struct device *dev)
{
struct sdhci_acpi_host *c = dev_get_drvdata(dev);
return sdhci_resume_host(c->host);
}
#else
#define sdhci_acpi_suspend NULL
#define sdhci_acpi_resume NULL
#endif
#ifdef CONFIG_PM_RUNTIME
static int sdhci_acpi_runtime_suspend(struct device *dev)
{
struct sdhci_acpi_host *c = dev_get_drvdata(dev);
return sdhci_runtime_suspend_host(c->host);
}
static int sdhci_acpi_runtime_resume(struct device *dev)
{
struct sdhci_acpi_host *c = dev_get_drvdata(dev);
return sdhci_runtime_resume_host(c->host);
}
static int sdhci_acpi_runtime_idle(struct device *dev)
{
return 0;
}
#endif
static const struct dev_pm_ops sdhci_acpi_pm_ops = {
.suspend = sdhci_acpi_suspend,
.resume = sdhci_acpi_resume,
SET_RUNTIME_PM_OPS(sdhci_acpi_runtime_suspend,
sdhci_acpi_runtime_resume, sdhci_acpi_runtime_idle)
};
static struct platform_driver sdhci_acpi_driver = {
.driver = {
.name = "sdhci-acpi",
.owner = THIS_MODULE,
.acpi_match_table = sdhci_acpi_ids,
.pm = &sdhci_acpi_pm_ops,
},
.probe = sdhci_acpi_probe,
.remove = sdhci_acpi_remove,
};
module_platform_driver(sdhci_acpi_driver);
MODULE_DESCRIPTION("Secure Digital Host Controller Interface ACPI driver");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,372 @@
/*
* Copyright (C) 2013 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/highmem.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/mmc/slot-gpio.h>
#include "sdhci-pltfm.h"
#include "sdhci.h"
#define SDHCI_SOFT_RESET 0x01000000
#define KONA_SDHOST_CORECTRL 0x8000
#define KONA_SDHOST_CD_PINCTRL 0x00000008
#define KONA_SDHOST_STOP_HCLK 0x00000004
#define KONA_SDHOST_RESET 0x00000002
#define KONA_SDHOST_EN 0x00000001
#define KONA_SDHOST_CORESTAT 0x8004
#define KONA_SDHOST_WP 0x00000002
#define KONA_SDHOST_CD_SW 0x00000001
#define KONA_SDHOST_COREIMR 0x8008
#define KONA_SDHOST_IP 0x00000001
#define KONA_SDHOST_COREISR 0x800C
#define KONA_SDHOST_COREIMSR 0x8010
#define KONA_SDHOST_COREDBG1 0x8014
#define KONA_SDHOST_COREGPO_MASK 0x8018
#define SD_DETECT_GPIO_DEBOUNCE_128MS 128
#define KONA_MMC_AUTOSUSPEND_DELAY (50)
struct sdhci_bcm_kona_dev {
struct mutex write_lock; /* protect back to back writes */
struct clk *external_clk;
};
static int sdhci_bcm_kona_sd_reset(struct sdhci_host *host)
{
unsigned int val;
unsigned long timeout;
/* This timeout should be sufficent for core to reset */
timeout = jiffies + msecs_to_jiffies(100);
/* reset the host using the top level reset */
val = sdhci_readl(host, KONA_SDHOST_CORECTRL);
val |= KONA_SDHOST_RESET;
sdhci_writel(host, val, KONA_SDHOST_CORECTRL);
while (!(sdhci_readl(host, KONA_SDHOST_CORECTRL) & KONA_SDHOST_RESET)) {
if (time_is_before_jiffies(timeout)) {
pr_err("Error: sd host is stuck in reset!!!\n");
return -EFAULT;
}
}
/* bring the host out of reset */
val = sdhci_readl(host, KONA_SDHOST_CORECTRL);
val &= ~KONA_SDHOST_RESET;
/*
* Back-to-Back register write needs a delay of 1ms at bootup (min 10uS)
* Back-to-Back writes to same register needs delay when SD bus clock
* is very low w.r.t AHB clock, mainly during boot-time and during card
* insert-removal.
*/
usleep_range(1000, 5000);
sdhci_writel(host, val, KONA_SDHOST_CORECTRL);
return 0;
}
static void sdhci_bcm_kona_sd_init(struct sdhci_host *host)
{
unsigned int val;
/* enable the interrupt from the IP core */
val = sdhci_readl(host, KONA_SDHOST_COREIMR);
val |= KONA_SDHOST_IP;
sdhci_writel(host, val, KONA_SDHOST_COREIMR);
/* Enable the AHB clock gating module to the host */
val = sdhci_readl(host, KONA_SDHOST_CORECTRL);
val |= KONA_SDHOST_EN;
/*
* Back-to-Back register write needs a delay of 1ms at bootup (min 10uS)
* Back-to-Back writes to same register needs delay when SD bus clock
* is very low w.r.t AHB clock, mainly during boot-time and during card
* insert-removal.
*/
usleep_range(1000, 5000);
sdhci_writel(host, val, KONA_SDHOST_CORECTRL);
}
/*
* Software emulation of the SD card insertion/removal. Set insert=1 for insert
* and insert=0 for removal. The card detection is done by GPIO. For Broadcom
* IP to function properly the bit 0 of CORESTAT register needs to be set/reset
* to generate the CD IRQ handled in sdhci.c which schedules card_tasklet.
*/
static int sdhci_bcm_kona_sd_card_emulate(struct sdhci_host *host, int insert)
{
struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host);
struct sdhci_bcm_kona_dev *kona_dev = sdhci_pltfm_priv(pltfm_priv);
u32 val;
/*
* Back-to-Back register write needs a delay of min 10uS.
* Back-to-Back writes to same register needs delay when SD bus clock
* is very low w.r.t AHB clock, mainly during boot-time and during card
* insert-removal.
* We keep 20uS
*/
mutex_lock(&kona_dev->write_lock);
udelay(20);
val = sdhci_readl(host, KONA_SDHOST_CORESTAT);
if (insert) {
int ret;
ret = mmc_gpio_get_ro(host->mmc);
if (ret >= 0)
val = (val & ~KONA_SDHOST_WP) |
((ret) ? KONA_SDHOST_WP : 0);
val |= KONA_SDHOST_CD_SW;
sdhci_writel(host, val, KONA_SDHOST_CORESTAT);
} else {
val &= ~KONA_SDHOST_CD_SW;
sdhci_writel(host, val, KONA_SDHOST_CORESTAT);
}
mutex_unlock(&kona_dev->write_lock);
return 0;
}
/*
* SD card interrupt event callback
*/
static void sdhci_bcm_kona_card_event(struct sdhci_host *host)
{
if (mmc_gpio_get_cd(host->mmc) > 0) {
dev_dbg(mmc_dev(host->mmc),
"card inserted\n");
sdhci_bcm_kona_sd_card_emulate(host, 1);
} else {
dev_dbg(mmc_dev(host->mmc),
"card removed\n");
sdhci_bcm_kona_sd_card_emulate(host, 0);
}
}
/*
* Get the base clock. Use central clock source for now. Not sure if different
* clock speed to each dev is allowed
*/
static unsigned int sdhci_bcm_kona_get_max_clk(struct sdhci_host *host)
{
struct sdhci_bcm_kona_dev *kona_dev;
struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host);
kona_dev = sdhci_pltfm_priv(pltfm_priv);
return host->mmc->f_max;
}
static unsigned int sdhci_bcm_kona_get_timeout_clock(struct sdhci_host *host)
{
return sdhci_bcm_kona_get_max_clk(host);
}
static void sdhci_bcm_kona_init_74_clocks(struct sdhci_host *host,
u8 power_mode)
{
/*
* JEDEC and SD spec specify supplying 74 continuous clocks to
* device after power up. With minimum bus (100KHz) that
* that translates to 740us
*/
if (power_mode != MMC_POWER_OFF)
udelay(740);
}
static struct sdhci_ops sdhci_bcm_kona_ops = {
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_bcm_kona_get_max_clk,
.get_timeout_clock = sdhci_bcm_kona_get_timeout_clock,
.platform_send_init_74_clocks = sdhci_bcm_kona_init_74_clocks,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.card_event = sdhci_bcm_kona_card_event,
};
static struct sdhci_pltfm_data sdhci_pltfm_data_kona = {
.ops = &sdhci_bcm_kona_ops,
.quirks = SDHCI_QUIRK_NO_CARD_NO_RESET |
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE | SDHCI_QUIRK_32BIT_ADMA_SIZE |
SDHCI_QUIRK_FORCE_BLK_SZ_2048 |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
};
static const struct of_device_id sdhci_bcm_kona_of_match[] = {
{ .compatible = "brcm,kona-sdhci"},
{ .compatible = "bcm,kona-sdhci"}, /* deprecated name */
{}
};
MODULE_DEVICE_TABLE(of, sdhci_bcm_kona_of_match);
static int sdhci_bcm_kona_probe(struct platform_device *pdev)
{
struct sdhci_bcm_kona_dev *kona_dev = NULL;
struct sdhci_pltfm_host *pltfm_priv;
struct device *dev = &pdev->dev;
struct sdhci_host *host;
int ret;
ret = 0;
host = sdhci_pltfm_init(pdev, &sdhci_pltfm_data_kona,
sizeof(*kona_dev));
if (IS_ERR(host))
return PTR_ERR(host);
dev_dbg(dev, "%s: inited. IOADDR=%p\n", __func__, host->ioaddr);
pltfm_priv = sdhci_priv(host);
kona_dev = sdhci_pltfm_priv(pltfm_priv);
mutex_init(&kona_dev->write_lock);
mmc_of_parse(host->mmc);
if (!host->mmc->f_max) {
dev_err(&pdev->dev, "Missing max-freq for SDHCI cfg\n");
ret = -ENXIO;
goto err_pltfm_free;
}
/* Get and enable the external clock */
kona_dev->external_clk = devm_clk_get(dev, NULL);
if (IS_ERR(kona_dev->external_clk)) {
dev_err(dev, "Failed to get external clock\n");
ret = PTR_ERR(kona_dev->external_clk);
goto err_pltfm_free;
}
if (clk_set_rate(kona_dev->external_clk, host->mmc->f_max) != 0) {
dev_err(dev, "Failed to set rate external clock\n");
goto err_pltfm_free;
}
if (clk_prepare_enable(kona_dev->external_clk) != 0) {
dev_err(dev, "Failed to enable external clock\n");
goto err_pltfm_free;
}
dev_dbg(dev, "non-removable=%c\n",
(host->mmc->caps & MMC_CAP_NONREMOVABLE) ? 'Y' : 'N');
dev_dbg(dev, "cd_gpio %c, wp_gpio %c\n",
(mmc_gpio_get_cd(host->mmc) != -ENOSYS) ? 'Y' : 'N',
(mmc_gpio_get_ro(host->mmc) != -ENOSYS) ? 'Y' : 'N');
if (host->mmc->caps & MMC_CAP_NONREMOVABLE)
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
dev_dbg(dev, "is_8bit=%c\n",
(host->mmc->caps | MMC_CAP_8_BIT_DATA) ? 'Y' : 'N');
ret = sdhci_bcm_kona_sd_reset(host);
if (ret)
goto err_clk_disable;
sdhci_bcm_kona_sd_init(host);
ret = sdhci_add_host(host);
if (ret) {
dev_err(dev, "Failed sdhci_add_host\n");
goto err_reset;
}
/* if device is eMMC, emulate card insert right here */
if (host->mmc->caps & MMC_CAP_NONREMOVABLE) {
ret = sdhci_bcm_kona_sd_card_emulate(host, 1);
if (ret) {
dev_err(dev,
"unable to emulate card insertion\n");
goto err_remove_host;
}
}
/*
* Since the card detection GPIO interrupt is configured to be
* edge sensitive, check the initial GPIO value here, emulate
* only if the card is present
*/
if (mmc_gpio_get_cd(host->mmc) > 0)
sdhci_bcm_kona_sd_card_emulate(host, 1);
dev_dbg(dev, "initialized properly\n");
return 0;
err_remove_host:
sdhci_remove_host(host, 0);
err_reset:
sdhci_bcm_kona_sd_reset(host);
err_clk_disable:
clk_disable_unprepare(kona_dev->external_clk);
err_pltfm_free:
sdhci_pltfm_free(pdev);
dev_err(dev, "Probing of sdhci-pltfm failed: %d\n", ret);
return ret;
}
static int sdhci_bcm_kona_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host);
struct sdhci_bcm_kona_dev *kona_dev = sdhci_pltfm_priv(pltfm_priv);
int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
sdhci_remove_host(host, dead);
clk_disable_unprepare(kona_dev->external_clk);
sdhci_pltfm_free(pdev);
return 0;
}
static struct platform_driver sdhci_bcm_kona_driver = {
.driver = {
.name = "sdhci-kona",
.pm = SDHCI_PLTFM_PMOPS,
.of_match_table = sdhci_bcm_kona_of_match,
},
.probe = sdhci_bcm_kona_probe,
.remove = sdhci_bcm_kona_remove,
};
module_platform_driver(sdhci_bcm_kona_driver);
MODULE_DESCRIPTION("SDHCI driver for Broadcom Kona platform");
MODULE_AUTHOR("Broadcom");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,207 @@
/*
* BCM2835 SDHCI
* Copyright (C) 2012 Stephen Warren
* Based on U-Boot's MMC driver for the BCM2835 by Oleksandr Tymoshenko & me
* Portions of the code there were obviously based on the Linux kernel at:
* git://github.com/raspberrypi/linux.git rpi-3.6.y
* commit f5b930b "Main bcm2708 linux port" signed-off-by Dom Cobley.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mmc/host.h>
#include "sdhci-pltfm.h"
/*
* 400KHz is max freq for card ID etc. Use that as min card clock. We need to
* know the min to enable static calculation of max BCM2835_SDHCI_WRITE_DELAY.
*/
#define MIN_FREQ 400000
/*
* The Arasan has a bugette whereby it may lose the content of successive
* writes to registers that are within two SD-card clock cycles of each other
* (a clock domain crossing problem). It seems, however, that the data
* register does not have this problem, which is just as well - otherwise we'd
* have to nobble the DMA engine too.
*
* This should probably be dynamically calculated based on the actual card
* frequency. However, this is the longest we'll have to wait, and doesn't
* seem to slow access down too much, so the added complexity doesn't seem
* worth it for now.
*
* 1/MIN_FREQ is (max) time per tick of eMMC clock.
* 2/MIN_FREQ is time for two ticks.
* Multiply by 1000000 to get uS per two ticks.
* *1000000 for uSecs.
* +1 for hack rounding.
*/
#define BCM2835_SDHCI_WRITE_DELAY (((2 * 1000000) / MIN_FREQ) + 1)
struct bcm2835_sdhci {
u32 shadow;
};
static void bcm2835_sdhci_writel(struct sdhci_host *host, u32 val, int reg)
{
writel(val, host->ioaddr + reg);
udelay(BCM2835_SDHCI_WRITE_DELAY);
}
static inline u32 bcm2835_sdhci_readl(struct sdhci_host *host, int reg)
{
u32 val = readl(host->ioaddr + reg);
if (reg == SDHCI_CAPABILITIES)
val |= SDHCI_CAN_VDD_330;
return val;
}
static void bcm2835_sdhci_writew(struct sdhci_host *host, u16 val, int reg)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv;
u32 oldval = (reg == SDHCI_COMMAND) ? bcm2835_host->shadow :
bcm2835_sdhci_readl(host, reg & ~3);
u32 word_num = (reg >> 1) & 1;
u32 word_shift = word_num * 16;
u32 mask = 0xffff << word_shift;
u32 newval = (oldval & ~mask) | (val << word_shift);
if (reg == SDHCI_TRANSFER_MODE)
bcm2835_host->shadow = newval;
else
bcm2835_sdhci_writel(host, newval, reg & ~3);
}
static u16 bcm2835_sdhci_readw(struct sdhci_host *host, int reg)
{
u32 val = bcm2835_sdhci_readl(host, (reg & ~3));
u32 word_num = (reg >> 1) & 1;
u32 word_shift = word_num * 16;
u32 word = (val >> word_shift) & 0xffff;
return word;
}
static void bcm2835_sdhci_writeb(struct sdhci_host *host, u8 val, int reg)
{
u32 oldval = bcm2835_sdhci_readl(host, reg & ~3);
u32 byte_num = reg & 3;
u32 byte_shift = byte_num * 8;
u32 mask = 0xff << byte_shift;
u32 newval = (oldval & ~mask) | (val << byte_shift);
bcm2835_sdhci_writel(host, newval, reg & ~3);
}
static u8 bcm2835_sdhci_readb(struct sdhci_host *host, int reg)
{
u32 val = bcm2835_sdhci_readl(host, (reg & ~3));
u32 byte_num = reg & 3;
u32 byte_shift = byte_num * 8;
u32 byte = (val >> byte_shift) & 0xff;
return byte;
}
static unsigned int bcm2835_sdhci_get_min_clock(struct sdhci_host *host)
{
return MIN_FREQ;
}
static const struct sdhci_ops bcm2835_sdhci_ops = {
.write_l = bcm2835_sdhci_writel,
.write_w = bcm2835_sdhci_writew,
.write_b = bcm2835_sdhci_writeb,
.read_l = bcm2835_sdhci_readl,
.read_w = bcm2835_sdhci_readw,
.read_b = bcm2835_sdhci_readb,
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_min_clock = bcm2835_sdhci_get_min_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static const struct sdhci_pltfm_data bcm2835_sdhci_pdata = {
.quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
.ops = &bcm2835_sdhci_ops,
};
static int bcm2835_sdhci_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct bcm2835_sdhci *bcm2835_host;
struct sdhci_pltfm_host *pltfm_host;
int ret;
host = sdhci_pltfm_init(pdev, &bcm2835_sdhci_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
bcm2835_host = devm_kzalloc(&pdev->dev, sizeof(*bcm2835_host),
GFP_KERNEL);
if (!bcm2835_host) {
dev_err(mmc_dev(host->mmc),
"failed to allocate bcm2835_sdhci\n");
return -ENOMEM;
}
pltfm_host = sdhci_priv(host);
pltfm_host->priv = bcm2835_host;
pltfm_host->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pltfm_host->clk)) {
ret = PTR_ERR(pltfm_host->clk);
goto err;
}
return sdhci_add_host(host);
err:
sdhci_pltfm_free(pdev);
return ret;
}
static int bcm2835_sdhci_remove(struct platform_device *pdev)
{
return sdhci_pltfm_unregister(pdev);
}
static const struct of_device_id bcm2835_sdhci_of_match[] = {
{ .compatible = "brcm,bcm2835-sdhci" },
{ }
};
MODULE_DEVICE_TABLE(of, bcm2835_sdhci_of_match);
static struct platform_driver bcm2835_sdhci_driver = {
.driver = {
.name = "sdhci-bcm2835",
.of_match_table = bcm2835_sdhci_of_match,
.pm = SDHCI_PLTFM_PMOPS,
},
.probe = bcm2835_sdhci_probe,
.remove = bcm2835_sdhci_remove,
};
module_platform_driver(bcm2835_sdhci_driver);
MODULE_DESCRIPTION("BCM2835 SDHCI driver");
MODULE_AUTHOR("Stephen Warren");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,120 @@
/*
* SDHCI support for CNS3xxx SoC
*
* Copyright 2008 Cavium Networks
* Copyright 2010 MontaVista Software, LLC.
*
* Authors: Scott Shu
* Anton Vorontsov <avorontsov@mvista.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.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/mmc/host.h>
#include <linux/module.h>
#include "sdhci-pltfm.h"
static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host)
{
return 150000000;
}
static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct device *dev = mmc_dev(host->mmc);
int div = 1;
u16 clk;
unsigned long timeout;
host->mmc->actual_clock = 0;
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
if (clock == 0)
return;
while (host->max_clk / div > clock) {
/*
* On CNS3xxx divider grows linearly up to 4, and then
* exponentially up to 256.
*/
if (div < 4)
div += 1;
else if (div < 256)
div *= 2;
else
break;
}
dev_dbg(dev, "desired SD clock: %d, actual: %d\n",
clock, host->max_clk / div);
/* Divide by 3 is special. */
if (div != 3)
div >>= 1;
clk = div << SDHCI_DIVIDER_SHIFT;
clk |= SDHCI_CLOCK_INT_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
timeout = 20;
while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
& SDHCI_CLOCK_INT_STABLE)) {
if (timeout == 0) {
dev_warn(dev, "clock is unstable");
break;
}
timeout--;
mdelay(1);
}
clk |= SDHCI_CLOCK_CARD_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
}
static const struct sdhci_ops sdhci_cns3xxx_ops = {
.get_max_clock = sdhci_cns3xxx_get_max_clk,
.set_clock = sdhci_cns3xxx_set_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static const struct sdhci_pltfm_data sdhci_cns3xxx_pdata = {
.ops = &sdhci_cns3xxx_ops,
.quirks = SDHCI_QUIRK_BROKEN_DMA |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
};
static int sdhci_cns3xxx_probe(struct platform_device *pdev)
{
return sdhci_pltfm_register(pdev, &sdhci_cns3xxx_pdata, 0);
}
static int sdhci_cns3xxx_remove(struct platform_device *pdev)
{
return sdhci_pltfm_unregister(pdev);
}
static struct platform_driver sdhci_cns3xxx_driver = {
.driver = {
.name = "sdhci-cns3xxx",
.pm = SDHCI_PLTFM_PMOPS,
},
.probe = sdhci_cns3xxx_probe,
.remove = sdhci_cns3xxx_remove,
};
module_platform_driver(sdhci_cns3xxx_driver);
MODULE_DESCRIPTION("SDHCI driver for CNS3xxx");
MODULE_AUTHOR("Scott Shu, "
"Anton Vorontsov <avorontsov@mvista.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,161 @@
/*
* sdhci-dove.c Support for SDHCI on Marvell's Dove SoC
*
* Author: Saeed Bishara <saeed@marvell.com>
* Mike Rapoport <mike@compulab.co.il>
* Based on sdhci-cns3xxx.c
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mmc/host.h>
#include <linux/module.h>
#include <linux/of.h>
#include "sdhci-pltfm.h"
struct sdhci_dove_priv {
struct clk *clk;
};
static u16 sdhci_dove_readw(struct sdhci_host *host, int reg)
{
u16 ret;
switch (reg) {
case SDHCI_HOST_VERSION:
case SDHCI_SLOT_INT_STATUS:
/* those registers don't exist */
return 0;
default:
ret = readw(host->ioaddr + reg);
}
return ret;
}
static u32 sdhci_dove_readl(struct sdhci_host *host, int reg)
{
u32 ret;
ret = readl(host->ioaddr + reg);
switch (reg) {
case SDHCI_CAPABILITIES:
/* Mask the support for 3.0V */
ret &= ~SDHCI_CAN_VDD_300;
break;
}
return ret;
}
static const struct sdhci_ops sdhci_dove_ops = {
.read_w = sdhci_dove_readw,
.read_l = sdhci_dove_readl,
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static const struct sdhci_pltfm_data sdhci_dove_pdata = {
.ops = &sdhci_dove_ops,
.quirks = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
SDHCI_QUIRK_NO_BUSY_IRQ |
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
SDHCI_QUIRK_FORCE_DMA |
SDHCI_QUIRK_NO_HISPD_BIT,
};
static int sdhci_dove_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_dove_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_dove_priv),
GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "unable to allocate private data");
return -ENOMEM;
}
priv->clk = devm_clk_get(&pdev->dev, NULL);
host = sdhci_pltfm_init(pdev, &sdhci_dove_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
pltfm_host->priv = priv;
if (!IS_ERR(priv->clk))
clk_prepare_enable(priv->clk);
ret = mmc_of_parse(host->mmc);
if (ret)
goto err_sdhci_add;
ret = sdhci_add_host(host);
if (ret)
goto err_sdhci_add;
return 0;
err_sdhci_add:
if (!IS_ERR(priv->clk))
clk_disable_unprepare(priv->clk);
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_dove_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_dove_priv *priv = pltfm_host->priv;
sdhci_pltfm_unregister(pdev);
if (!IS_ERR(priv->clk))
clk_disable_unprepare(priv->clk);
return 0;
}
static const struct of_device_id sdhci_dove_of_match_table[] = {
{ .compatible = "marvell,dove-sdhci", },
{}
};
MODULE_DEVICE_TABLE(of, sdhci_dove_of_match_table);
static struct platform_driver sdhci_dove_driver = {
.driver = {
.name = "sdhci-dove",
.pm = SDHCI_PLTFM_PMOPS,
.of_match_table = sdhci_dove_of_match_table,
},
.probe = sdhci_dove_probe,
.remove = sdhci_dove_remove,
};
module_platform_driver(sdhci_dove_driver);
MODULE_DESCRIPTION("SDHCI driver for Dove");
MODULE_AUTHOR("Saeed Bishara <saeed@marvell.com>, "
"Mike Rapoport <mike@compulab.co.il>");
MODULE_LICENSE("GPL v2");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/*
* Freescale eSDHC controller driver generics for OF and pltfm.
*
* Copyright (c) 2007 Freescale Semiconductor, Inc.
* Copyright (c) 2009 MontaVista Software, Inc.
* Copyright (c) 2010 Pengutronix e.K.
* Author: Wolfram Sang <w.sang@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*/
#ifndef _DRIVERS_MMC_SDHCI_ESDHC_H
#define _DRIVERS_MMC_SDHCI_ESDHC_H
/*
* Ops and quirks for the Freescale eSDHC controller.
*/
#define ESDHC_DEFAULT_QUIRKS (SDHCI_QUIRK_FORCE_BLK_SZ_2048 | \
SDHCI_QUIRK_NO_BUSY_IRQ | \
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | \
SDHCI_QUIRK_PIO_NEEDS_DELAY)
#define ESDHC_SYSTEM_CONTROL 0x2c
#define ESDHC_CLOCK_MASK 0x0000fff0
#define ESDHC_PREDIV_SHIFT 8
#define ESDHC_DIVIDER_SHIFT 4
#define ESDHC_CLOCK_PEREN 0x00000004
#define ESDHC_CLOCK_HCKEN 0x00000002
#define ESDHC_CLOCK_IPGEN 0x00000001
/* pltfm-specific */
#define ESDHC_HOST_CONTROL_LE 0x20
/*
* P2020 interpretation of the SDHCI_HOST_CONTROL register
*/
#define ESDHC_CTRL_4BITBUS (0x1 << 1)
#define ESDHC_CTRL_8BITBUS (0x2 << 1)
#define ESDHC_CTRL_BUSWIDTH_MASK (0x3 << 1)
/* OF-specific */
#define ESDHC_DMA_SYSCTL 0x40c
#define ESDHC_DMA_SNOOP 0x00000040
#define ESDHC_HOST_CONTROL_RES 0x05
#endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */

View file

@ -0,0 +1,602 @@
/*
* drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver
*
* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/delay.h>
#include <linux/mmc/mmc.h>
#include <linux/slab.h>
#include "sdhci-pltfm.h"
#define CORE_HC_MODE 0x78
#define HC_MODE_EN 0x1
#define CORE_POWER 0x0
#define CORE_SW_RST BIT(7)
#define MAX_PHASES 16
#define CORE_DLL_LOCK BIT(7)
#define CORE_DLL_EN BIT(16)
#define CORE_CDR_EN BIT(17)
#define CORE_CK_OUT_EN BIT(18)
#define CORE_CDR_EXT_EN BIT(19)
#define CORE_DLL_PDN BIT(29)
#define CORE_DLL_RST BIT(30)
#define CORE_DLL_CONFIG 0x100
#define CORE_DLL_STATUS 0x108
#define CORE_VENDOR_SPEC 0x10c
#define CORE_CLK_PWRSAVE BIT(1)
#define CDR_SELEXT_SHIFT 20
#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT)
#define CMUX_SHIFT_PHASE_SHIFT 24
#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT)
struct sdhci_msm_host {
struct platform_device *pdev;
void __iomem *core_mem; /* MSM SDCC mapped address */
struct clk *clk; /* main SD/MMC bus clock */
struct clk *pclk; /* SDHC peripheral bus clock */
struct clk *bus_clk; /* SDHC bus voter clock */
struct mmc_host *mmc;
struct sdhci_pltfm_data sdhci_msm_pdata;
};
/* Platform specific tuning */
static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll)
{
u32 wait_cnt = 50;
u8 ck_out_en;
struct mmc_host *mmc = host->mmc;
/* Poll for CK_OUT_EN bit. max. poll time = 50us */
ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
CORE_CK_OUT_EN);
while (ck_out_en != poll) {
if (--wait_cnt == 0) {
dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n",
mmc_hostname(mmc), poll);
return -ETIMEDOUT;
}
udelay(1);
ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
CORE_CK_OUT_EN);
}
return 0;
}
static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
{
int rc;
static const u8 grey_coded_phase_table[] = {
0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8
};
unsigned long flags;
u32 config;
struct mmc_host *mmc = host->mmc;
spin_lock_irqsave(&host->lock, flags);
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN);
config |= (CORE_CDR_EXT_EN | CORE_DLL_EN);
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */
rc = msm_dll_poll_ck_out_en(host, 0);
if (rc)
goto err_out;
/*
* Write the selected DLL clock output phase (0 ... 15)
* to CDR_SELEXT bit field of DLL_CONFIG register.
*/
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
config &= ~CDR_SELEXT_MASK;
config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT;
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
/* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG);
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */
rc = msm_dll_poll_ck_out_en(host, 1);
if (rc)
goto err_out;
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
config |= CORE_CDR_EN;
config &= ~CORE_CDR_EXT_EN;
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
goto out;
err_out:
dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n",
mmc_hostname(mmc), phase);
out:
spin_unlock_irqrestore(&host->lock, flags);
return rc;
}
/*
* Find out the greatest range of consecuitive selected
* DLL clock output phases that can be used as sampling
* setting for SD3.0 UHS-I card read operation (in SDR104
* timing mode) or for eMMC4.5 card read operation (in HS200
* timing mode).
* Select the 3/4 of the range and configure the DLL with the
* selected DLL clock output phase.
*/
static int msm_find_most_appropriate_phase(struct sdhci_host *host,
u8 *phase_table, u8 total_phases)
{
int ret;
u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} };
u8 phases_per_row[MAX_PHASES] = { 0 };
int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0;
int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0;
bool phase_0_found = false, phase_15_found = false;
struct mmc_host *mmc = host->mmc;
if (!total_phases || (total_phases > MAX_PHASES)) {
dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n",
mmc_hostname(mmc), total_phases);
return -EINVAL;
}
for (cnt = 0; cnt < total_phases; cnt++) {
ranges[row_index][col_index] = phase_table[cnt];
phases_per_row[row_index] += 1;
col_index++;
if ((cnt + 1) == total_phases) {
continue;
/* check if next phase in phase_table is consecutive or not */
} else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) {
row_index++;
col_index = 0;
}
}
if (row_index >= MAX_PHASES)
return -EINVAL;
/* Check if phase-0 is present in first valid window? */
if (!ranges[0][0]) {
phase_0_found = true;
phase_0_raw_index = 0;
/* Check if cycle exist between 2 valid windows */
for (cnt = 1; cnt <= row_index; cnt++) {
if (phases_per_row[cnt]) {
for (i = 0; i < phases_per_row[cnt]; i++) {
if (ranges[cnt][i] == 15) {
phase_15_found = true;
phase_15_raw_index = cnt;
break;
}
}
}
}
}
/* If 2 valid windows form cycle then merge them as single window */
if (phase_0_found && phase_15_found) {
/* number of phases in raw where phase 0 is present */
u8 phases_0 = phases_per_row[phase_0_raw_index];
/* number of phases in raw where phase 15 is present */
u8 phases_15 = phases_per_row[phase_15_raw_index];
if (phases_0 + phases_15 >= MAX_PHASES)
/*
* If there are more than 1 phase windows then total
* number of phases in both the windows should not be
* more than or equal to MAX_PHASES.
*/
return -EINVAL;
/* Merge 2 cyclic windows */
i = phases_15;
for (cnt = 0; cnt < phases_0; cnt++) {
ranges[phase_15_raw_index][i] =
ranges[phase_0_raw_index][cnt];
if (++i >= MAX_PHASES)
break;
}
phases_per_row[phase_0_raw_index] = 0;
phases_per_row[phase_15_raw_index] = phases_15 + phases_0;
}
for (cnt = 0; cnt <= row_index; cnt++) {
if (phases_per_row[cnt] > curr_max) {
curr_max = phases_per_row[cnt];
selected_row_index = cnt;
}
}
i = (curr_max * 3) / 4;
if (i)
i--;
ret = ranges[selected_row_index][i];
if (ret >= MAX_PHASES) {
ret = -EINVAL;
dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n",
mmc_hostname(mmc), ret);
}
return ret;
}
static inline void msm_cm_dll_set_freq(struct sdhci_host *host)
{
u32 mclk_freq = 0, config;
/* Program the MCLK value to MCLK_FREQ bit field */
if (host->clock <= 112000000)
mclk_freq = 0;
else if (host->clock <= 125000000)
mclk_freq = 1;
else if (host->clock <= 137000000)
mclk_freq = 2;
else if (host->clock <= 150000000)
mclk_freq = 3;
else if (host->clock <= 162000000)
mclk_freq = 4;
else if (host->clock <= 175000000)
mclk_freq = 5;
else if (host->clock <= 187000000)
mclk_freq = 6;
else if (host->clock <= 200000000)
mclk_freq = 7;
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
config &= ~CMUX_SHIFT_PHASE_MASK;
config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT;
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
}
/* Initialize the DLL (Programmable Delay Line) */
static int msm_init_cm_dll(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
int wait_cnt = 50;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
/*
* Make sure that clock is always enabled when DLL
* tuning is in progress. Keeping PWRSAVE ON may
* turn off the clock.
*/
writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC)
& ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC);
/* Write 1 to DLL_RST bit of DLL_CONFIG register */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG);
/* Write 1 to DLL_PDN bit of DLL_CONFIG register */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG);
msm_cm_dll_set_freq(host);
/* Write 0 to DLL_RST bit of DLL_CONFIG register */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
& ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG);
/* Write 0 to DLL_PDN bit of DLL_CONFIG register */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
& ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG);
/* Set DLL_EN bit to 1. */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG);
/* Set CK_OUT_EN bit to 1. */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG);
/* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */
while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) &
CORE_DLL_LOCK)) {
/* max. wait for 50us sec for LOCK bit to be set */
if (--wait_cnt == 0) {
dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n",
mmc_hostname(mmc));
spin_unlock_irqrestore(&host->lock, flags);
return -ETIMEDOUT;
}
udelay(1);
}
spin_unlock_irqrestore(&host->lock, flags);
return 0;
}
static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
{
int tuning_seq_cnt = 3;
u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0;
const u8 *tuning_block_pattern = tuning_blk_pattern_4bit;
int size = sizeof(tuning_blk_pattern_4bit);
int rc;
struct mmc_host *mmc = host->mmc;
struct mmc_ios ios = host->mmc->ios;
/*
* Tuning is required for SDR104, HS200 and HS400 cards and
* if clock frequency is greater than 100MHz in these modes.
*/
if (host->clock <= 100 * 1000 * 1000 ||
!((ios.timing == MMC_TIMING_MMC_HS200) ||
(ios.timing == MMC_TIMING_UHS_SDR104)))
return 0;
if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) &&
(mmc->ios.bus_width == MMC_BUS_WIDTH_8)) {
tuning_block_pattern = tuning_blk_pattern_8bit;
size = sizeof(tuning_blk_pattern_8bit);
}
data_buf = kmalloc(size, GFP_KERNEL);
if (!data_buf)
return -ENOMEM;
retry:
/* First of all reset the tuning block */
rc = msm_init_cm_dll(host);
if (rc)
goto out;
phase = 0;
do {
struct mmc_command cmd = { 0 };
struct mmc_data data = { 0 };
struct mmc_request mrq = {
.cmd = &cmd,
.data = &data
};
struct scatterlist sg;
/* Set the phase in delay line hw block */
rc = msm_config_cm_dll_phase(host, phase);
if (rc)
goto out;
cmd.opcode = opcode;
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
data.blksz = size;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.timeout_ns = NSEC_PER_SEC; /* 1 second */
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, data_buf, size);
memset(data_buf, 0, size);
mmc_wait_for_req(mmc, &mrq);
if (!cmd.error && !data.error &&
!memcmp(data_buf, tuning_block_pattern, size)) {
/* Tuning is successful at this tuning point */
tuned_phases[tuned_phase_cnt++] = phase;
dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n",
mmc_hostname(mmc), phase);
}
} while (++phase < ARRAY_SIZE(tuned_phases));
if (tuned_phase_cnt) {
rc = msm_find_most_appropriate_phase(host, tuned_phases,
tuned_phase_cnt);
if (rc < 0)
goto out;
else
phase = rc;
/*
* Finally set the selected phase in delay
* line hw block.
*/
rc = msm_config_cm_dll_phase(host, phase);
if (rc)
goto out;
dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n",
mmc_hostname(mmc), phase);
} else {
if (--tuning_seq_cnt)
goto retry;
/* Tuning failed */
dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n",
mmc_hostname(mmc));
rc = -EIO;
}
out:
kfree(data_buf);
return rc;
}
static const struct of_device_id sdhci_msm_dt_match[] = {
{ .compatible = "qcom,sdhci-msm-v4" },
{},
};
MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);
static struct sdhci_ops sdhci_msm_ops = {
.platform_execute_tuning = sdhci_msm_execute_tuning,
.reset = sdhci_reset,
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static int sdhci_msm_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_msm_host *msm_host;
struct resource *core_memres;
int ret;
u16 host_version;
msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL);
if (!msm_host)
return -ENOMEM;
msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
pltfm_host->priv = msm_host;
msm_host->mmc = host->mmc;
msm_host->pdev = pdev;
ret = mmc_of_parse(host->mmc);
if (ret)
goto pltfm_free;
sdhci_get_of_property(pdev);
/* Setup SDCC bus voter clock. */
msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus");
if (!IS_ERR(msm_host->bus_clk)) {
/* Vote for max. clk rate for max. performance */
ret = clk_set_rate(msm_host->bus_clk, INT_MAX);
if (ret)
goto pltfm_free;
ret = clk_prepare_enable(msm_host->bus_clk);
if (ret)
goto pltfm_free;
}
/* Setup main peripheral bus clock */
msm_host->pclk = devm_clk_get(&pdev->dev, "iface");
if (IS_ERR(msm_host->pclk)) {
ret = PTR_ERR(msm_host->pclk);
dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret);
goto bus_clk_disable;
}
ret = clk_prepare_enable(msm_host->pclk);
if (ret)
goto bus_clk_disable;
/* Setup SDC MMC clock */
msm_host->clk = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(msm_host->clk)) {
ret = PTR_ERR(msm_host->clk);
dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret);
goto pclk_disable;
}
ret = clk_prepare_enable(msm_host->clk);
if (ret)
goto pclk_disable;
core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1);
msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres);
if (IS_ERR(msm_host->core_mem)) {
dev_err(&pdev->dev, "Failed to remap registers\n");
ret = PTR_ERR(msm_host->core_mem);
goto clk_disable;
}
/* Reset the core and Enable SDHC mode */
writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) |
CORE_SW_RST, msm_host->core_mem + CORE_POWER);
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
usleep_range(1000, 5000);
if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) {
dev_err(&pdev->dev, "Stuck in reset\n");
ret = -ETIMEDOUT;
goto clk_disable;
}
/* Set HC_MODE_EN bit in HC_MODE register */
writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE));
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE;
host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
SDHCI_VENDOR_VER_SHIFT));
ret = sdhci_add_host(host);
if (ret)
goto clk_disable;
return 0;
clk_disable:
clk_disable_unprepare(msm_host->clk);
pclk_disable:
clk_disable_unprepare(msm_host->pclk);
bus_clk_disable:
if (!IS_ERR(msm_host->bus_clk))
clk_disable_unprepare(msm_host->bus_clk);
pltfm_free:
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_msm_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = pltfm_host->priv;
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
0xffffffff);
sdhci_remove_host(host, dead);
sdhci_pltfm_free(pdev);
clk_disable_unprepare(msm_host->clk);
clk_disable_unprepare(msm_host->pclk);
if (!IS_ERR(msm_host->bus_clk))
clk_disable_unprepare(msm_host->bus_clk);
return 0;
}
static struct platform_driver sdhci_msm_driver = {
.probe = sdhci_msm_probe,
.remove = sdhci_msm_remove,
.driver = {
.name = "sdhci_msm",
.of_match_table = sdhci_msm_dt_match,
},
};
module_platform_driver(sdhci_msm_driver);
MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,227 @@
/*
* Arasan Secure Digital Host Controller Interface.
* Copyright (C) 2011 - 2012 Michal Simek <monstr@monstr.eu>
* Copyright (c) 2012 Wind River Systems, Inc.
* Copyright (C) 2013 Pengutronix e.K.
* Copyright (C) 2013 Xilinx Inc.
*
* Based on sdhci-of-esdhc.c
*
* Copyright (c) 2007 Freescale Semiconductor, Inc.
* Copyright (c) 2009 MontaVista Software, Inc.
*
* Authors: Xiaobo Xie <X.Xie@freescale.com>
* Anton Vorontsov <avorontsov@ru.mvista.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/module.h>
#include "sdhci-pltfm.h"
#define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c
#define CLK_CTRL_TIMEOUT_SHIFT 16
#define CLK_CTRL_TIMEOUT_MASK (0xf << CLK_CTRL_TIMEOUT_SHIFT)
#define CLK_CTRL_TIMEOUT_MIN_EXP 13
/**
* struct sdhci_arasan_data
* @clk_ahb: Pointer to the AHB clock
*/
struct sdhci_arasan_data {
struct clk *clk_ahb;
};
static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host)
{
u32 div;
unsigned long freq;
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
div = readl(host->ioaddr + SDHCI_ARASAN_CLK_CTRL_OFFSET);
div = (div & CLK_CTRL_TIMEOUT_MASK) >> CLK_CTRL_TIMEOUT_SHIFT;
freq = clk_get_rate(pltfm_host->clk);
freq /= 1 << (CLK_CTRL_TIMEOUT_MIN_EXP + div);
return freq;
}
static struct sdhci_ops sdhci_arasan_ops = {
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_timeout_clock = sdhci_arasan_get_timeout_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static struct sdhci_pltfm_data sdhci_arasan_pdata = {
.ops = &sdhci_arasan_ops,
};
#ifdef CONFIG_PM_SLEEP
/**
* sdhci_arasan_suspend - Suspend method for the driver
* @dev: Address of the device structure
* Returns 0 on success and error value on error
*
* Put the device in a low power state.
*/
static int sdhci_arasan_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_arasan_data *sdhci_arasan = pltfm_host->priv;
int ret;
ret = sdhci_suspend_host(host);
if (ret)
return ret;
clk_disable(pltfm_host->clk);
clk_disable(sdhci_arasan->clk_ahb);
return 0;
}
/**
* sdhci_arasan_resume - Resume method for the driver
* @dev: Address of the device structure
* Returns 0 on success and error value on error
*
* Resume operation after suspend
*/
static int sdhci_arasan_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_arasan_data *sdhci_arasan = pltfm_host->priv;
int ret;
ret = clk_enable(sdhci_arasan->clk_ahb);
if (ret) {
dev_err(dev, "Cannot enable AHB clock.\n");
return ret;
}
ret = clk_enable(pltfm_host->clk);
if (ret) {
dev_err(dev, "Cannot enable SD clock.\n");
clk_disable(sdhci_arasan->clk_ahb);
return ret;
}
return sdhci_resume_host(host);
}
#endif /* ! CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend,
sdhci_arasan_resume);
static int sdhci_arasan_probe(struct platform_device *pdev)
{
int ret;
struct clk *clk_xin;
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_arasan_data *sdhci_arasan;
sdhci_arasan = devm_kzalloc(&pdev->dev, sizeof(*sdhci_arasan),
GFP_KERNEL);
if (!sdhci_arasan)
return -ENOMEM;
sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb");
if (IS_ERR(sdhci_arasan->clk_ahb)) {
dev_err(&pdev->dev, "clk_ahb clock not found.\n");
return PTR_ERR(sdhci_arasan->clk_ahb);
}
clk_xin = devm_clk_get(&pdev->dev, "clk_xin");
if (IS_ERR(clk_xin)) {
dev_err(&pdev->dev, "clk_xin clock not found.\n");
return PTR_ERR(clk_xin);
}
ret = clk_prepare_enable(sdhci_arasan->clk_ahb);
if (ret) {
dev_err(&pdev->dev, "Unable to enable AHB clock.\n");
return ret;
}
ret = clk_prepare_enable(clk_xin);
if (ret) {
dev_err(&pdev->dev, "Unable to enable SD clock.\n");
goto clk_dis_ahb;
}
host = sdhci_pltfm_init(pdev, &sdhci_arasan_pdata, 0);
if (IS_ERR(host)) {
ret = PTR_ERR(host);
dev_err(&pdev->dev, "platform init failed (%u)\n", ret);
goto clk_disable_all;
}
sdhci_get_of_property(pdev);
pltfm_host = sdhci_priv(host);
pltfm_host->priv = sdhci_arasan;
pltfm_host->clk = clk_xin;
ret = sdhci_add_host(host);
if (ret) {
dev_err(&pdev->dev, "platform register failed (%u)\n", ret);
goto err_pltfm_free;
}
return 0;
err_pltfm_free:
sdhci_pltfm_free(pdev);
clk_disable_all:
clk_disable_unprepare(clk_xin);
clk_dis_ahb:
clk_disable_unprepare(sdhci_arasan->clk_ahb);
return ret;
}
static int sdhci_arasan_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_arasan_data *sdhci_arasan = pltfm_host->priv;
clk_disable_unprepare(pltfm_host->clk);
clk_disable_unprepare(sdhci_arasan->clk_ahb);
return sdhci_pltfm_unregister(pdev);
}
static const struct of_device_id sdhci_arasan_of_match[] = {
{ .compatible = "arasan,sdhci-8.9a" },
{ }
};
MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
static struct platform_driver sdhci_arasan_driver = {
.driver = {
.name = "sdhci-arasan",
.of_match_table = sdhci_arasan_of_match,
.pm = &sdhci_arasan_dev_pm_ops,
},
.probe = sdhci_arasan_probe,
.remove = sdhci_arasan_remove,
};
module_platform_driver(sdhci_arasan_driver);
MODULE_DESCRIPTION("Driver for the Arasan SDHCI Controller");
MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,403 @@
/*
* Freescale eSDHC controller driver.
*
* Copyright (c) 2007, 2010, 2012 Freescale Semiconductor, Inc.
* Copyright (c) 2009 MontaVista Software, Inc.
*
* Authors: Xiaobo Xie <X.Xie@freescale.com>
* Anton Vorontsov <avorontsov@ru.mvista.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mmc/host.h>
#include "sdhci-pltfm.h"
#include "sdhci-esdhc.h"
#define VENDOR_V_22 0x12
#define VENDOR_V_23 0x13
static u32 esdhc_readl(struct sdhci_host *host, int reg)
{
u32 ret;
ret = in_be32(host->ioaddr + reg);
/*
* The bit of ADMA flag in eSDHC is not compatible with standard
* SDHC register, so set fake flag SDHCI_CAN_DO_ADMA2 when ADMA is
* supported by eSDHC.
* And for many FSL eSDHC controller, the reset value of field
* SDHCI_CAN_DO_ADMA1 is one, but some of them can't support ADMA,
* only these vendor version is greater than 2.2/0x12 support ADMA.
* For FSL eSDHC, must aligned 4-byte, so use 0xFC to read the
* the verdor version number, oxFE is SDHCI_HOST_VERSION.
*/
if ((reg == SDHCI_CAPABILITIES) && (ret & SDHCI_CAN_DO_ADMA1)) {
u32 tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS);
tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT;
if (tmp > VENDOR_V_22)
ret |= SDHCI_CAN_DO_ADMA2;
}
return ret;
}
static u16 esdhc_readw(struct sdhci_host *host, int reg)
{
u16 ret;
int base = reg & ~0x3;
int shift = (reg & 0x2) * 8;
if (unlikely(reg == SDHCI_HOST_VERSION))
ret = in_be32(host->ioaddr + base) & 0xffff;
else
ret = (in_be32(host->ioaddr + base) >> shift) & 0xffff;
return ret;
}
static u8 esdhc_readb(struct sdhci_host *host, int reg)
{
int base = reg & ~0x3;
int shift = (reg & 0x3) * 8;
u8 ret = (in_be32(host->ioaddr + base) >> shift) & 0xff;
/*
* "DMA select" locates at offset 0x28 in SD specification, but on
* P5020 or P3041, it locates at 0x29.
*/
if (reg == SDHCI_HOST_CONTROL) {
u32 dma_bits;
dma_bits = in_be32(host->ioaddr + reg);
/* DMA select is 22,23 bits in Protocol Control Register */
dma_bits = (dma_bits >> 5) & SDHCI_CTRL_DMA_MASK;
/* fixup the result */
ret &= ~SDHCI_CTRL_DMA_MASK;
ret |= dma_bits;
}
return ret;
}
static void esdhc_writel(struct sdhci_host *host, u32 val, int reg)
{
/*
* Enable IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE]
* when SYSCTL[RSTD]) is set for some special operations.
* No any impact other operation.
*/
if (reg == SDHCI_INT_ENABLE)
val |= SDHCI_INT_BLK_GAP;
sdhci_be32bs_writel(host, val, reg);
}
static void esdhc_writew(struct sdhci_host *host, u16 val, int reg)
{
if (reg == SDHCI_BLOCK_SIZE) {
/*
* Two last DMA bits are reserved, and first one is used for
* non-standard blksz of 4096 bytes that we don't support
* yet. So clear the DMA boundary bits.
*/
val &= ~SDHCI_MAKE_BLKSZ(0x7, 0);
}
sdhci_be32bs_writew(host, val, reg);
}
static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg)
{
/*
* "DMA select" location is offset 0x28 in SD specification, but on
* P5020 or P3041, it's located at 0x29.
*/
if (reg == SDHCI_HOST_CONTROL) {
u32 dma_bits;
/*
* If host control register is not standard, exit
* this function
*/
if (host->quirks2 & SDHCI_QUIRK2_BROKEN_HOST_CONTROL)
return;
/* DMA select is 22,23 bits in Protocol Control Register */
dma_bits = (val & SDHCI_CTRL_DMA_MASK) << 5;
clrsetbits_be32(host->ioaddr + reg , SDHCI_CTRL_DMA_MASK << 5,
dma_bits);
val &= ~SDHCI_CTRL_DMA_MASK;
val |= in_be32(host->ioaddr + reg) & SDHCI_CTRL_DMA_MASK;
}
/* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */
if (reg == SDHCI_HOST_CONTROL)
val &= ~ESDHC_HOST_CONTROL_RES;
sdhci_be32bs_writeb(host, val, reg);
}
/*
* For Abort or Suspend after Stop at Block Gap, ignore the ADMA
* error(IRQSTAT[ADMAE]) if both Transfer Complete(IRQSTAT[TC])
* and Block Gap Event(IRQSTAT[BGE]) are also set.
* For Continue, apply soft reset for data(SYSCTL[RSTD]);
* and re-issue the entire read transaction from beginning.
*/
static void esdhci_of_adma_workaround(struct sdhci_host *host, u32 intmask)
{
u32 tmp;
bool applicable;
dma_addr_t dmastart;
dma_addr_t dmanow;
tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS);
tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT;
applicable = (intmask & SDHCI_INT_DATA_END) &&
(intmask & SDHCI_INT_BLK_GAP) &&
(tmp == VENDOR_V_23);
if (!applicable)
return;
host->data->error = 0;
dmastart = sg_dma_address(host->data->sg);
dmanow = dmastart + host->data->bytes_xfered;
/*
* Force update to the next DMA block boundary.
*/
dmanow = (dmanow & ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
SDHCI_DEFAULT_BOUNDARY_SIZE;
host->data->bytes_xfered = dmanow - dmastart;
sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
}
static int esdhc_of_enable_dma(struct sdhci_host *host)
{
setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP);
return 0;
}
static unsigned int esdhc_of_get_max_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return pltfm_host->clock;
}
static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return pltfm_host->clock / 256 / 16;
}
static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
{
int pre_div = 2;
int div = 1;
u32 temp;
host->mmc->actual_clock = 0;
if (clock == 0)
return;
/* Workaround to reduce the clock frequency for p1010 esdhc */
if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) {
if (clock > 20000000)
clock -= 5000000;
if (clock > 40000000)
clock -= 5000000;
}
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
| ESDHC_CLOCK_MASK);
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
while (host->max_clk / pre_div / 16 > clock && pre_div < 256)
pre_div *= 2;
while (host->max_clk / pre_div / div > clock && div < 16)
div++;
dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n",
clock, host->max_clk / pre_div / div);
pre_div >>= 1;
div--;
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp |= (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
| (div << ESDHC_DIVIDER_SHIFT)
| (pre_div << ESDHC_PREDIV_SHIFT));
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
mdelay(1);
}
static void esdhc_of_platform_init(struct sdhci_host *host)
{
u32 vvn;
vvn = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS);
vvn = (vvn & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT;
if (vvn == VENDOR_V_22)
host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23;
if (vvn > VENDOR_V_22)
host->quirks &= ~SDHCI_QUIRK_NO_BUSY_IRQ;
}
static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width)
{
u32 ctrl;
switch (width) {
case MMC_BUS_WIDTH_8:
ctrl = ESDHC_CTRL_8BITBUS;
break;
case MMC_BUS_WIDTH_4:
ctrl = ESDHC_CTRL_4BITBUS;
break;
default:
ctrl = 0;
break;
}
clrsetbits_be32(host->ioaddr + SDHCI_HOST_CONTROL,
ESDHC_CTRL_BUSWIDTH_MASK, ctrl);
}
static const struct sdhci_ops sdhci_esdhc_ops = {
.read_l = esdhc_readl,
.read_w = esdhc_readw,
.read_b = esdhc_readb,
.write_l = esdhc_writel,
.write_w = esdhc_writew,
.write_b = esdhc_writeb,
.set_clock = esdhc_of_set_clock,
.enable_dma = esdhc_of_enable_dma,
.get_max_clock = esdhc_of_get_max_clock,
.get_min_clock = esdhc_of_get_min_clock,
.platform_init = esdhc_of_platform_init,
.adma_workaround = esdhci_of_adma_workaround,
.set_bus_width = esdhc_pltfm_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
#ifdef CONFIG_PM
static u32 esdhc_proctl;
static int esdhc_of_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
esdhc_proctl = sdhci_be32bs_readl(host, SDHCI_HOST_CONTROL);
return sdhci_suspend_host(host);
}
static int esdhc_of_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
int ret = sdhci_resume_host(host);
if (ret == 0) {
/* Isn't this already done by sdhci_resume_host() ? --rmk */
esdhc_of_enable_dma(host);
sdhci_be32bs_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL);
}
return ret;
}
static const struct dev_pm_ops esdhc_pmops = {
.suspend = esdhc_of_suspend,
.resume = esdhc_of_resume,
};
#define ESDHC_PMOPS (&esdhc_pmops)
#else
#define ESDHC_PMOPS NULL
#endif
static const struct sdhci_pltfm_data sdhci_esdhc_pdata = {
/*
* card detection could be handled via GPIO
* eSDHC cannot support End Attribute in NOP ADMA descriptor
*/
.quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION
| SDHCI_QUIRK_NO_CARD_NO_RESET
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
.ops = &sdhci_esdhc_ops,
};
static int sdhci_esdhc_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct device_node *np;
int ret;
host = sdhci_pltfm_init(pdev, &sdhci_esdhc_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
sdhci_get_of_property(pdev);
np = pdev->dev.of_node;
if (of_device_is_compatible(np, "fsl,p2020-esdhc")) {
/*
* Freescale messed up with P2020 as it has a non-standard
* host control register
*/
host->quirks2 |= SDHCI_QUIRK2_BROKEN_HOST_CONTROL;
}
/* call to generic mmc_of_parse to support additional capabilities */
mmc_of_parse(host->mmc);
mmc_of_parse_voltage(np, &host->ocr_mask);
ret = sdhci_add_host(host);
if (ret)
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_esdhc_remove(struct platform_device *pdev)
{
return sdhci_pltfm_unregister(pdev);
}
static const struct of_device_id sdhci_esdhc_of_match[] = {
{ .compatible = "fsl,mpc8379-esdhc" },
{ .compatible = "fsl,mpc8536-esdhc" },
{ .compatible = "fsl,esdhc" },
{ }
};
MODULE_DEVICE_TABLE(of, sdhci_esdhc_of_match);
static struct platform_driver sdhci_esdhc_driver = {
.driver = {
.name = "sdhci-esdhc",
.of_match_table = sdhci_esdhc_of_match,
.pm = ESDHC_PMOPS,
},
.probe = sdhci_esdhc_probe,
.remove = sdhci_esdhc_remove,
};
module_platform_driver(sdhci_esdhc_driver);
MODULE_DESCRIPTION("SDHCI OF driver for Freescale MPC eSDHC");
MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, "
"Anton Vorontsov <avorontsov@ru.mvista.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,103 @@
/*
* drivers/mmc/host/sdhci-of-hlwd.c
*
* Nintendo Wii Secure Digital Host Controller Interface.
* Copyright (C) 2009 The GameCube Linux Team
* Copyright (C) 2009 Albert Herranz
*
* Based on sdhci-of-esdhc.c
*
* Copyright (c) 2007 Freescale Semiconductor, Inc.
* Copyright (c) 2009 MontaVista Software, Inc.
*
* Authors: Xiaobo Xie <X.Xie@freescale.com>
* Anton Vorontsov <avorontsov@ru.mvista.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mmc/host.h>
#include "sdhci-pltfm.h"
/*
* Ops and quirks for the Nintendo Wii SDHCI controllers.
*/
/*
* We need a small delay after each write, or things go horribly wrong.
*/
#define SDHCI_HLWD_WRITE_DELAY 5 /* usecs */
static void sdhci_hlwd_writel(struct sdhci_host *host, u32 val, int reg)
{
sdhci_be32bs_writel(host, val, reg);
udelay(SDHCI_HLWD_WRITE_DELAY);
}
static void sdhci_hlwd_writew(struct sdhci_host *host, u16 val, int reg)
{
sdhci_be32bs_writew(host, val, reg);
udelay(SDHCI_HLWD_WRITE_DELAY);
}
static void sdhci_hlwd_writeb(struct sdhci_host *host, u8 val, int reg)
{
sdhci_be32bs_writeb(host, val, reg);
udelay(SDHCI_HLWD_WRITE_DELAY);
}
static const struct sdhci_ops sdhci_hlwd_ops = {
.read_l = sdhci_be32bs_readl,
.read_w = sdhci_be32bs_readw,
.read_b = sdhci_be32bs_readb,
.write_l = sdhci_hlwd_writel,
.write_w = sdhci_hlwd_writew,
.write_b = sdhci_hlwd_writeb,
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static const struct sdhci_pltfm_data sdhci_hlwd_pdata = {
.quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE,
.ops = &sdhci_hlwd_ops,
};
static int sdhci_hlwd_probe(struct platform_device *pdev)
{
return sdhci_pltfm_register(pdev, &sdhci_hlwd_pdata, 0);
}
static int sdhci_hlwd_remove(struct platform_device *pdev)
{
return sdhci_pltfm_unregister(pdev);
}
static const struct of_device_id sdhci_hlwd_of_match[] = {
{ .compatible = "nintendo,hollywood-sdhci" },
{ }
};
MODULE_DEVICE_TABLE(of, sdhci_hlwd_of_match);
static struct platform_driver sdhci_hlwd_driver = {
.driver = {
.name = "sdhci-hlwd",
.of_match_table = sdhci_hlwd_of_match,
.pm = SDHCI_PLTFM_PMOPS,
},
.probe = sdhci_hlwd_probe,
.remove = sdhci_hlwd_remove,
};
module_platform_driver(sdhci_hlwd_driver);
MODULE_DESCRIPTION("Nintendo Wii SDHCI OF driver");
MODULE_AUTHOR("The GameCube Linux Team, Albert Herranz");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,5 @@
#include <linux/module.h>
#include <linux/mmc/sdhci-pci-data.h>
struct sdhci_pci_data *(*sdhci_pci_get_data)(struct pci_dev *pdev, int slotno);
EXPORT_SYMBOL_GPL(sdhci_pci_get_data);

View file

@ -0,0 +1,395 @@
/*
* Copyright (C) 2013 BayHub Technology Ltd.
*
* Authors: Peter Guo <peter.guo@bayhubtech.com>
* Adam Lee <adam.lee@canonical.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/pci.h>
#include "sdhci.h"
#include "sdhci-pci.h"
#include "sdhci-pci-o2micro.h"
static void o2_pci_set_baseclk(struct sdhci_pci_chip *chip, u32 value)
{
u32 scratch_32;
pci_read_config_dword(chip->pdev,
O2_SD_PLL_SETTING, &scratch_32);
scratch_32 &= 0x0000FFFF;
scratch_32 |= value;
pci_write_config_dword(chip->pdev,
O2_SD_PLL_SETTING, scratch_32);
}
static void o2_pci_led_enable(struct sdhci_pci_chip *chip)
{
int ret;
u32 scratch_32;
/* Set led of SD host function enable */
ret = pci_read_config_dword(chip->pdev,
O2_SD_FUNC_REG0, &scratch_32);
if (ret)
return;
scratch_32 &= ~O2_SD_FREG0_LEDOFF;
pci_write_config_dword(chip->pdev,
O2_SD_FUNC_REG0, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_TEST_REG, &scratch_32);
if (ret)
return;
scratch_32 |= O2_SD_LED_ENABLE;
pci_write_config_dword(chip->pdev,
O2_SD_TEST_REG, scratch_32);
}
void sdhci_pci_o2_fujin2_pci_init(struct sdhci_pci_chip *chip)
{
u32 scratch_32;
int ret;
/* Improve write performance for SD3.0 */
ret = pci_read_config_dword(chip->pdev, O2_SD_DEV_CTRL, &scratch_32);
if (ret)
return;
scratch_32 &= ~((1 << 12) | (1 << 13) | (1 << 14));
pci_write_config_dword(chip->pdev, O2_SD_DEV_CTRL, scratch_32);
/* Enable Link abnormal reset generating Reset */
ret = pci_read_config_dword(chip->pdev, O2_SD_MISC_REG5, &scratch_32);
if (ret)
return;
scratch_32 &= ~((1 << 19) | (1 << 11));
scratch_32 |= (1 << 10);
pci_write_config_dword(chip->pdev, O2_SD_MISC_REG5, scratch_32);
/* set card power over current protection */
ret = pci_read_config_dword(chip->pdev, O2_SD_TEST_REG, &scratch_32);
if (ret)
return;
scratch_32 |= (1 << 4);
pci_write_config_dword(chip->pdev, O2_SD_TEST_REG, scratch_32);
/* adjust the output delay for SD mode */
pci_write_config_dword(chip->pdev, O2_SD_DELAY_CTRL, 0x00002492);
/* Set the output voltage setting of Aux 1.2v LDO */
ret = pci_read_config_dword(chip->pdev, O2_SD_LD0_CTRL, &scratch_32);
if (ret)
return;
scratch_32 &= ~(3 << 12);
pci_write_config_dword(chip->pdev, O2_SD_LD0_CTRL, scratch_32);
/* Set Max power supply capability of SD host */
ret = pci_read_config_dword(chip->pdev, O2_SD_CAP_REG0, &scratch_32);
if (ret)
return;
scratch_32 &= ~(0x01FE);
scratch_32 |= 0x00CC;
pci_write_config_dword(chip->pdev, O2_SD_CAP_REG0, scratch_32);
/* Set DLL Tuning Window */
ret = pci_read_config_dword(chip->pdev,
O2_SD_TUNING_CTRL, &scratch_32);
if (ret)
return;
scratch_32 &= ~(0x000000FF);
scratch_32 |= 0x00000066;
pci_write_config_dword(chip->pdev, O2_SD_TUNING_CTRL, scratch_32);
/* Set UHS2 T_EIDLE */
ret = pci_read_config_dword(chip->pdev,
O2_SD_UHS2_L1_CTRL, &scratch_32);
if (ret)
return;
scratch_32 &= ~(0x000000FC);
scratch_32 |= 0x00000084;
pci_write_config_dword(chip->pdev, O2_SD_UHS2_L1_CTRL, scratch_32);
/* Set UHS2 Termination */
ret = pci_read_config_dword(chip->pdev, O2_SD_FUNC_REG3, &scratch_32);
if (ret)
return;
scratch_32 &= ~((1 << 21) | (1 << 30));
pci_write_config_dword(chip->pdev, O2_SD_FUNC_REG3, scratch_32);
/* Set L1 Entrance Timer */
ret = pci_read_config_dword(chip->pdev, O2_SD_CAPS, &scratch_32);
if (ret)
return;
scratch_32 &= ~(0xf0000000);
scratch_32 |= 0x30000000;
pci_write_config_dword(chip->pdev, O2_SD_CAPS, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_MISC_CTRL4, &scratch_32);
if (ret)
return;
scratch_32 &= ~(0x000f0000);
scratch_32 |= 0x00080000;
pci_write_config_dword(chip->pdev, O2_SD_MISC_CTRL4, scratch_32);
}
EXPORT_SYMBOL_GPL(sdhci_pci_o2_fujin2_pci_init);
int sdhci_pci_o2_probe_slot(struct sdhci_pci_slot *slot)
{
struct sdhci_pci_chip *chip;
struct sdhci_host *host;
u32 reg;
chip = slot->chip;
host = slot->host;
switch (chip->pdev->device) {
case PCI_DEVICE_ID_O2_SDS0:
case PCI_DEVICE_ID_O2_SEABIRD0:
case PCI_DEVICE_ID_O2_SEABIRD1:
case PCI_DEVICE_ID_O2_SDS1:
case PCI_DEVICE_ID_O2_FUJIN2:
reg = sdhci_readl(host, O2_SD_VENDOR_SETTING);
if (reg & 0x1)
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
if (chip->pdev->device != PCI_DEVICE_ID_O2_FUJIN2)
break;
/* set dll watch dog timer */
reg = sdhci_readl(host, O2_SD_VENDOR_SETTING2);
reg |= (1 << 12);
sdhci_writel(host, reg, O2_SD_VENDOR_SETTING2);
break;
default:
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sdhci_pci_o2_probe_slot);
int sdhci_pci_o2_probe(struct sdhci_pci_chip *chip)
{
int ret;
u8 scratch;
u32 scratch_32;
switch (chip->pdev->device) {
case PCI_DEVICE_ID_O2_8220:
case PCI_DEVICE_ID_O2_8221:
case PCI_DEVICE_ID_O2_8320:
case PCI_DEVICE_ID_O2_8321:
/* This extra setup is required due to broken ADMA. */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch &= 0x7f;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
/* Set Multi 3 to VCC3V# */
pci_write_config_byte(chip->pdev, O2_SD_MULTI_VCC3V, 0x08);
/* Disable CLK_REQ# support after media DET */
ret = pci_read_config_byte(chip->pdev,
O2_SD_CLKREQ, &scratch);
if (ret)
return ret;
scratch |= 0x20;
pci_write_config_byte(chip->pdev, O2_SD_CLKREQ, scratch);
/* Choose capabilities, enable SDMA. We have to write 0x01
* to the capabilities register first to unlock it.
*/
ret = pci_read_config_byte(chip->pdev, O2_SD_CAPS, &scratch);
if (ret)
return ret;
scratch |= 0x01;
pci_write_config_byte(chip->pdev, O2_SD_CAPS, scratch);
pci_write_config_byte(chip->pdev, O2_SD_CAPS, 0x73);
/* Disable ADMA1/2 */
pci_write_config_byte(chip->pdev, O2_SD_ADMA1, 0x39);
pci_write_config_byte(chip->pdev, O2_SD_ADMA2, 0x08);
/* Disable the infinite transfer mode */
ret = pci_read_config_byte(chip->pdev,
O2_SD_INF_MOD, &scratch);
if (ret)
return ret;
scratch |= 0x08;
pci_write_config_byte(chip->pdev, O2_SD_INF_MOD, scratch);
/* Lock WP */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch |= 0x80;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
break;
case PCI_DEVICE_ID_O2_SDS0:
case PCI_DEVICE_ID_O2_SDS1:
case PCI_DEVICE_ID_O2_FUJIN2:
/* UnLock WP */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch &= 0x7f;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
/* DevId=8520 subId= 0x11 or 0x12 Type Chip support */
if (chip->pdev->device == PCI_DEVICE_ID_O2_FUJIN2) {
ret = pci_read_config_dword(chip->pdev,
O2_SD_FUNC_REG0,
&scratch_32);
scratch_32 = ((scratch_32 & 0xFF000000) >> 24);
/* Check Whether subId is 0x11 or 0x12 */
if ((scratch_32 == 0x11) || (scratch_32 == 0x12)) {
scratch_32 = 0x2c280000;
/* Set Base Clock to 208MZ */
o2_pci_set_baseclk(chip, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_FUNC_REG4,
&scratch_32);
/* Enable Base Clk setting change */
scratch_32 |= O2_SD_FREG4_ENABLE_CLK_SET;
pci_write_config_dword(chip->pdev,
O2_SD_FUNC_REG4,
scratch_32);
/* Set Tuning Window to 4 */
pci_write_config_byte(chip->pdev,
O2_SD_TUNING_CTRL, 0x44);
break;
}
}
/* Enable 8520 led function */
o2_pci_led_enable(chip);
/* Set timeout CLK */
ret = pci_read_config_dword(chip->pdev,
O2_SD_CLK_SETTING, &scratch_32);
if (ret)
return ret;
scratch_32 &= ~(0xFF00);
scratch_32 |= 0x07E0C800;
pci_write_config_dword(chip->pdev,
O2_SD_CLK_SETTING, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_CLKREQ, &scratch_32);
if (ret)
return ret;
scratch_32 |= 0x3;
pci_write_config_dword(chip->pdev, O2_SD_CLKREQ, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_PLL_SETTING, &scratch_32);
if (ret)
return ret;
scratch_32 &= ~(0x1F3F070E);
scratch_32 |= 0x18270106;
pci_write_config_dword(chip->pdev,
O2_SD_PLL_SETTING, scratch_32);
/* Disable UHS1 funciton */
ret = pci_read_config_dword(chip->pdev,
O2_SD_CAP_REG2, &scratch_32);
if (ret)
return ret;
scratch_32 &= ~(0xE0);
pci_write_config_dword(chip->pdev,
O2_SD_CAP_REG2, scratch_32);
if (chip->pdev->device == PCI_DEVICE_ID_O2_FUJIN2)
sdhci_pci_o2_fujin2_pci_init(chip);
/* Lock WP */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch |= 0x80;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
break;
case PCI_DEVICE_ID_O2_SEABIRD0:
case PCI_DEVICE_ID_O2_SEABIRD1:
/* UnLock WP */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch &= 0x7f;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
ret = pci_read_config_dword(chip->pdev,
O2_SD_PLL_SETTING, &scratch_32);
if ((scratch_32 & 0xff000000) == 0x01000000) {
scratch_32 &= 0x0000FFFF;
scratch_32 |= 0x1F340000;
pci_write_config_dword(chip->pdev,
O2_SD_PLL_SETTING, scratch_32);
} else {
scratch_32 &= 0x0000FFFF;
scratch_32 |= 0x2c280000;
pci_write_config_dword(chip->pdev,
O2_SD_PLL_SETTING, scratch_32);
ret = pci_read_config_dword(chip->pdev,
O2_SD_FUNC_REG4,
&scratch_32);
scratch_32 |= (1 << 22);
pci_write_config_dword(chip->pdev,
O2_SD_FUNC_REG4, scratch_32);
}
/* Set Tuning Windows to 5 */
pci_write_config_byte(chip->pdev,
O2_SD_TUNING_CTRL, 0x55);
/* Lock WP */
ret = pci_read_config_byte(chip->pdev,
O2_SD_LOCK_WP, &scratch);
if (ret)
return ret;
scratch |= 0x80;
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sdhci_pci_o2_probe);
int sdhci_pci_o2_resume(struct sdhci_pci_chip *chip)
{
sdhci_pci_o2_probe(chip);
return 0;
}
EXPORT_SYMBOL_GPL(sdhci_pci_o2_resume);

View file

@ -0,0 +1,75 @@
/*
* Copyright (C) 2013 BayHub Technology Ltd.
*
* Authors: Peter Guo <peter.guo@bayhubtech.com>
* Adam Lee <adam.lee@canonical.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __SDHCI_PCI_O2MICRO_H
#define __SDHCI_PCI_O2MICRO_H
#include "sdhci-pci.h"
/*
* O2Micro device IDs
*/
#define PCI_DEVICE_ID_O2_SDS0 0x8420
#define PCI_DEVICE_ID_O2_SDS1 0x8421
#define PCI_DEVICE_ID_O2_FUJIN2 0x8520
#define PCI_DEVICE_ID_O2_SEABIRD0 0x8620
#define PCI_DEVICE_ID_O2_SEABIRD1 0x8621
/*
* O2Micro device registers
*/
#define O2_SD_MISC_REG5 0x64
#define O2_SD_LD0_CTRL 0x68
#define O2_SD_DEV_CTRL 0x88
#define O2_SD_LOCK_WP 0xD3
#define O2_SD_TEST_REG 0xD4
#define O2_SD_FUNC_REG0 0xDC
#define O2_SD_MULTI_VCC3V 0xEE
#define O2_SD_CLKREQ 0xEC
#define O2_SD_CAPS 0xE0
#define O2_SD_ADMA1 0xE2
#define O2_SD_ADMA2 0xE7
#define O2_SD_INF_MOD 0xF1
#define O2_SD_MISC_CTRL4 0xFC
#define O2_SD_TUNING_CTRL 0x300
#define O2_SD_PLL_SETTING 0x304
#define O2_SD_CLK_SETTING 0x328
#define O2_SD_CAP_REG2 0x330
#define O2_SD_CAP_REG0 0x334
#define O2_SD_UHS1_CAP_SETTING 0x33C
#define O2_SD_DELAY_CTRL 0x350
#define O2_SD_UHS2_L1_CTRL 0x35C
#define O2_SD_FUNC_REG3 0x3E0
#define O2_SD_FUNC_REG4 0x3E4
#define O2_SD_LED_ENABLE BIT(6)
#define O2_SD_FREG0_LEDOFF BIT(13)
#define O2_SD_FREG4_ENABLE_CLK_SET BIT(22)
#define O2_SD_VENDOR_SETTING 0x110
#define O2_SD_VENDOR_SETTING2 0x1C8
extern void sdhci_pci_o2_fujin2_pci_init(struct sdhci_pci_chip *chip);
extern int sdhci_pci_o2_probe_slot(struct sdhci_pci_slot *slot);
extern int sdhci_pci_o2_probe(struct sdhci_pci_chip *chip);
extern int sdhci_pci_o2_resume(struct sdhci_pci_chip *chip);
#endif /* __SDHCI_PCI_O2MICRO_H */

1645
drivers/mmc/host/sdhci-pci.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
#ifndef __SDHCI_PCI_H
#define __SDHCI_PCI_H
/*
* PCI device IDs
*/
#define PCI_DEVICE_ID_INTEL_PCH_SDIO0 0x8809
#define PCI_DEVICE_ID_INTEL_PCH_SDIO1 0x880a
#define PCI_DEVICE_ID_INTEL_BYT_EMMC 0x0f14
#define PCI_DEVICE_ID_INTEL_BYT_SDIO 0x0f15
#define PCI_DEVICE_ID_INTEL_BYT_SD 0x0f16
#define PCI_DEVICE_ID_INTEL_BYT_EMMC2 0x0f50
#define PCI_DEVICE_ID_INTEL_BSW_EMMC 0x2294
#define PCI_DEVICE_ID_INTEL_BSW_SDIO 0x2295
#define PCI_DEVICE_ID_INTEL_BSW_SD 0x2296
#define PCI_DEVICE_ID_INTEL_MRFL_MMC 0x1190
#define PCI_DEVICE_ID_INTEL_CLV_SDIO0 0x08f9
#define PCI_DEVICE_ID_INTEL_CLV_SDIO1 0x08fa
#define PCI_DEVICE_ID_INTEL_CLV_SDIO2 0x08fb
#define PCI_DEVICE_ID_INTEL_CLV_EMMC0 0x08e5
#define PCI_DEVICE_ID_INTEL_CLV_EMMC1 0x08e6
#define PCI_DEVICE_ID_INTEL_QRK_SD 0x08A7
/*
* PCI registers
*/
#define PCI_SDHCI_IFPIO 0x00
#define PCI_SDHCI_IFDMA 0x01
#define PCI_SDHCI_IFVENDOR 0x02
#define PCI_SLOT_INFO 0x40 /* 8 bits */
#define PCI_SLOT_INFO_SLOTS(x) ((x >> 4) & 7)
#define PCI_SLOT_INFO_FIRST_BAR_MASK 0x07
#define MAX_SLOTS 8
struct sdhci_pci_chip;
struct sdhci_pci_slot;
struct sdhci_pci_fixes {
unsigned int quirks;
unsigned int quirks2;
bool allow_runtime_pm;
bool own_cd_for_runtime_pm;
int (*probe) (struct sdhci_pci_chip *);
int (*probe_slot) (struct sdhci_pci_slot *);
void (*remove_slot) (struct sdhci_pci_slot *, int);
int (*suspend) (struct sdhci_pci_chip *);
int (*resume) (struct sdhci_pci_chip *);
};
struct sdhci_pci_slot {
struct sdhci_pci_chip *chip;
struct sdhci_host *host;
struct sdhci_pci_data *data;
int pci_bar;
int rst_n_gpio;
int cd_gpio;
int cd_irq;
char *cd_con_id;
int cd_idx;
bool cd_override_level;
void (*hw_reset)(struct sdhci_host *host);
};
struct sdhci_pci_chip {
struct pci_dev *pdev;
unsigned int quirks;
unsigned int quirks2;
bool allow_runtime_pm;
const struct sdhci_pci_fixes *fixes;
int num_slots; /* Slots on controller */
struct sdhci_pci_slot *slots[MAX_SLOTS]; /* Pointers to host slots */
};
#endif /* __SDHCI_PCI_H */

View file

@ -0,0 +1,276 @@
/*
* sdhci-pltfm.c Support for SDHCI platform devices
* Copyright (c) 2009 Intel Corporation
*
* Copyright (c) 2007, 2011 Freescale Semiconductor, Inc.
* Copyright (c) 2009 MontaVista Software, Inc.
*
* Authors: Xiaobo Xie <X.Xie@freescale.com>
* Anton Vorontsov <avorontsov@ru.mvista.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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Supports:
* SDHCI platform devices
*
* Inspired by sdhci-pci.c, by Pierre Ossman
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#ifdef CONFIG_PPC
#include <asm/machdep.h>
#endif
#include "sdhci-pltfm.h"
unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return clk_get_rate(pltfm_host->clk);
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock);
static const struct sdhci_ops sdhci_pltfm_ops = {
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
#ifdef CONFIG_OF
static bool sdhci_of_wp_inverted(struct device_node *np)
{
if (of_get_property(np, "sdhci,wp-inverted", NULL) ||
of_get_property(np, "wp-inverted", NULL))
return true;
/* Old device trees don't have the wp-inverted property. */
#ifdef CONFIG_PPC
return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds);
#else
return false;
#endif /* CONFIG_PPC */
}
void sdhci_get_of_property(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
const __be32 *clk;
u32 bus_width;
int size;
if (of_device_is_available(np)) {
if (of_get_property(np, "sdhci,auto-cmd12", NULL))
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
if (of_get_property(np, "sdhci,1-bit-only", NULL) ||
(of_property_read_u32(np, "bus-width", &bus_width) == 0 &&
bus_width == 1))
host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
if (sdhci_of_wp_inverted(np))
host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
if (of_get_property(np, "broken-cd", NULL))
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
if (of_get_property(np, "no-1-8-v", NULL))
host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
if (of_device_is_compatible(np, "fsl,p2020-rev1-esdhc"))
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
if (of_device_is_compatible(np, "fsl,p2020-esdhc") ||
of_device_is_compatible(np, "fsl,p1010-esdhc") ||
of_device_is_compatible(np, "fsl,t4240-esdhc") ||
of_device_is_compatible(np, "fsl,mpc8536-esdhc"))
host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
clk = of_get_property(np, "clock-frequency", &size);
if (clk && size == sizeof(*clk) && *clk)
pltfm_host->clock = be32_to_cpup(clk);
if (of_find_property(np, "keep-power-in-suspend", NULL))
host->mmc->pm_caps |= MMC_PM_KEEP_POWER;
if (of_find_property(np, "enable-sdio-wakeup", NULL))
host->mmc->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
}
}
#else
void sdhci_get_of_property(struct platform_device *pdev) {}
#endif /* CONFIG_OF */
EXPORT_SYMBOL_GPL(sdhci_get_of_property);
struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size)
{
struct sdhci_host *host;
struct resource *iomem;
int ret;
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iomem) {
ret = -ENOMEM;
goto err;
}
if (resource_size(iomem) < 0x100)
dev_err(&pdev->dev, "Invalid iomem size!\n");
host = sdhci_alloc_host(&pdev->dev,
sizeof(struct sdhci_pltfm_host) + priv_size);
if (IS_ERR(host)) {
ret = PTR_ERR(host);
goto err;
}
host->hw_name = dev_name(&pdev->dev);
if (pdata && pdata->ops)
host->ops = pdata->ops;
else
host->ops = &sdhci_pltfm_ops;
if (pdata) {
host->quirks = pdata->quirks;
host->quirks2 = pdata->quirks2;
}
host->irq = platform_get_irq(pdev, 0);
if (!request_mem_region(iomem->start, resource_size(iomem),
mmc_hostname(host->mmc))) {
dev_err(&pdev->dev, "cannot request region\n");
ret = -EBUSY;
goto err_request;
}
host->ioaddr = ioremap(iomem->start, resource_size(iomem));
if (!host->ioaddr) {
dev_err(&pdev->dev, "failed to remap registers\n");
ret = -ENOMEM;
goto err_remap;
}
/*
* Some platforms need to probe the controller to be able to
* determine which caps should be used.
*/
if (host->ops && host->ops->platform_init)
host->ops->platform_init(host);
platform_set_drvdata(pdev, host);
return host;
err_remap:
release_mem_region(iomem->start, resource_size(iomem));
err_request:
sdhci_free_host(host);
err:
dev_err(&pdev->dev, "%s failed %d\n", __func__, ret);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_init);
void sdhci_pltfm_free(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
iounmap(host->ioaddr);
release_mem_region(iomem->start, resource_size(iomem));
sdhci_free_host(host);
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_free);
int sdhci_pltfm_register(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size)
{
struct sdhci_host *host;
int ret = 0;
host = sdhci_pltfm_init(pdev, pdata, priv_size);
if (IS_ERR(host))
return PTR_ERR(host);
sdhci_get_of_property(pdev);
ret = sdhci_add_host(host);
if (ret)
sdhci_pltfm_free(pdev);
return ret;
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_register);
int sdhci_pltfm_unregister(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
sdhci_remove_host(host, dead);
sdhci_pltfm_free(pdev);
return 0;
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_unregister);
#ifdef CONFIG_PM
int sdhci_pltfm_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
return sdhci_suspend_host(host);
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_suspend);
int sdhci_pltfm_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
return sdhci_resume_host(host);
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_resume);
const struct dev_pm_ops sdhci_pltfm_pmops = {
.suspend = sdhci_pltfm_suspend,
.resume = sdhci_pltfm_resume,
};
EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops);
#endif /* CONFIG_PM */
static int __init sdhci_pltfm_drv_init(void)
{
pr_info("sdhci-pltfm: SDHCI platform and OF driver helper\n");
return 0;
}
module_init(sdhci_pltfm_drv_init);
static void __exit sdhci_pltfm_drv_exit(void)
{
}
module_exit(sdhci_pltfm_drv_exit);
MODULE_DESCRIPTION("SDHCI platform and OF driver helper");
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,122 @@
/*
* Copyright 2010 MontaVista Software, LLC.
*
* Author: Anton Vorontsov <avorontsov@ru.mvista.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.
*/
#ifndef _DRIVERS_MMC_SDHCI_PLTFM_H
#define _DRIVERS_MMC_SDHCI_PLTFM_H
#include <linux/clk.h>
#include <linux/platform_device.h>
#include "sdhci.h"
struct sdhci_pltfm_data {
const struct sdhci_ops *ops;
unsigned int quirks;
unsigned int quirks2;
};
struct sdhci_pltfm_host {
struct clk *clk;
void *priv; /* to handle quirks across io-accessor calls */
/* migrate from sdhci_of_host */
unsigned int clock;
u16 xfer_mode_shadow;
unsigned long private[0] ____cacheline_aligned;
};
#ifdef CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
/*
* These accessors are designed for big endian hosts doing I/O to
* little endian controllers incorporating a 32-bit hardware byte swapper.
*/
static inline u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg)
{
return in_be32(host->ioaddr + reg);
}
static inline u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg)
{
return in_be16(host->ioaddr + (reg ^ 0x2));
}
static inline u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg)
{
return in_8(host->ioaddr + (reg ^ 0x3));
}
static inline void sdhci_be32bs_writel(struct sdhci_host *host,
u32 val, int reg)
{
out_be32(host->ioaddr + reg, val);
}
static inline void sdhci_be32bs_writew(struct sdhci_host *host,
u16 val, int reg)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
int base = reg & ~0x3;
int shift = (reg & 0x2) * 8;
switch (reg) {
case SDHCI_TRANSFER_MODE:
/*
* Postpone this write, we must do it together with a
* command write that is down below.
*/
pltfm_host->xfer_mode_shadow = val;
return;
case SDHCI_COMMAND:
sdhci_be32bs_writel(host,
val << 16 | pltfm_host->xfer_mode_shadow,
SDHCI_TRANSFER_MODE);
return;
}
clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift);
}
static inline void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg)
{
int base = reg & ~0x3;
int shift = (reg & 0x3) * 8;
clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift);
}
#endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */
extern void sdhci_get_of_property(struct platform_device *pdev);
extern struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size);
extern void sdhci_pltfm_free(struct platform_device *pdev);
extern int sdhci_pltfm_register(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size);
extern int sdhci_pltfm_unregister(struct platform_device *pdev);
extern unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host);
static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host)
{
return (void *)host->private;
}
#ifdef CONFIG_PM
extern int sdhci_pltfm_suspend(struct device *dev);
extern int sdhci_pltfm_resume(struct device *dev);
extern const struct dev_pm_ops sdhci_pltfm_pmops;
#define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops)
#else
#define SDHCI_PLTFM_PMOPS NULL
#endif
#endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */

View file

@ -0,0 +1,278 @@
/*
* Copyright (C) 2010 Marvell International Ltd.
* Zhangfei Gao <zhangfei.gao@marvell.com>
* Kevin Wang <dwang4@marvell.com>
* Jun Nie <njun@marvell.com>
* Qiming Wu <wuqm@marvell.com>
* Philip Rakity <prakity@marvell.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/platform_data/pxa_sdhci.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include "sdhci.h"
#include "sdhci-pltfm.h"
#define SD_FIFO_PARAM 0xe0
#define DIS_PAD_SD_CLK_GATE 0x0400 /* Turn on/off Dynamic SD Clock Gating */
#define CLK_GATE_ON 0x0200 /* Disable/enable Clock Gate */
#define CLK_GATE_CTL 0x0100 /* Clock Gate Control */
#define CLK_GATE_SETTING_BITS (DIS_PAD_SD_CLK_GATE | \
CLK_GATE_ON | CLK_GATE_CTL)
#define SD_CLOCK_BURST_SIZE_SETUP 0xe6
#define SDCLK_SEL_SHIFT 8
#define SDCLK_SEL_MASK 0x3
#define SDCLK_DELAY_SHIFT 10
#define SDCLK_DELAY_MASK 0x3c
#define SD_CE_ATA_2 0xea
#define MMC_CARD 0x1000
#define MMC_WIDTH 0x0100
static void pxav2_reset(struct sdhci_host *host, u8 mask)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
sdhci_reset(host, mask);
if (mask == SDHCI_RESET_ALL) {
u16 tmp = 0;
/*
* tune timing of read data/command when crc error happen
* no performance impact
*/
if (pdata && pdata->clk_delay_sel == 1) {
tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT);
tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK)
<< SDCLK_DELAY_SHIFT;
tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT);
tmp |= (1 & SDCLK_SEL_MASK) << SDCLK_SEL_SHIFT;
writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
}
if (pdata && (pdata->flags & PXA_FLAG_ENABLE_CLOCK_GATING)) {
tmp = readw(host->ioaddr + SD_FIFO_PARAM);
tmp &= ~CLK_GATE_SETTING_BITS;
writew(tmp, host->ioaddr + SD_FIFO_PARAM);
} else {
tmp = readw(host->ioaddr + SD_FIFO_PARAM);
tmp &= ~CLK_GATE_SETTING_BITS;
tmp |= CLK_GATE_SETTING_BITS;
writew(tmp, host->ioaddr + SD_FIFO_PARAM);
}
}
}
static void pxav2_mmc_set_bus_width(struct sdhci_host *host, int width)
{
u8 ctrl;
u16 tmp;
ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL);
tmp = readw(host->ioaddr + SD_CE_ATA_2);
if (width == MMC_BUS_WIDTH_8) {
ctrl &= ~SDHCI_CTRL_4BITBUS;
tmp |= MMC_CARD | MMC_WIDTH;
} else {
tmp &= ~(MMC_CARD | MMC_WIDTH);
if (width == MMC_BUS_WIDTH_4)
ctrl |= SDHCI_CTRL_4BITBUS;
else
ctrl &= ~SDHCI_CTRL_4BITBUS;
}
writew(tmp, host->ioaddr + SD_CE_ATA_2);
writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
}
static const struct sdhci_ops pxav2_sdhci_ops = {
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.set_bus_width = pxav2_mmc_set_bus_width,
.reset = pxav2_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
#ifdef CONFIG_OF
static const struct of_device_id sdhci_pxav2_of_match[] = {
{
.compatible = "mrvl,pxav2-mmc",
},
{},
};
MODULE_DEVICE_TABLE(of, sdhci_pxav2_of_match);
static struct sdhci_pxa_platdata *pxav2_get_mmc_pdata(struct device *dev)
{
struct sdhci_pxa_platdata *pdata;
struct device_node *np = dev->of_node;
u32 bus_width;
u32 clk_delay_cycles;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
if (of_find_property(np, "non-removable", NULL))
pdata->flags |= PXA_FLAG_CARD_PERMANENT;
of_property_read_u32(np, "bus-width", &bus_width);
if (bus_width == 8)
pdata->flags |= PXA_FLAG_SD_8_BIT_CAPABLE_SLOT;
of_property_read_u32(np, "mrvl,clk-delay-cycles", &clk_delay_cycles);
if (clk_delay_cycles > 0) {
pdata->clk_delay_sel = 1;
pdata->clk_delay_cycles = clk_delay_cycles;
}
return pdata;
}
#else
static inline struct sdhci_pxa_platdata *pxav2_get_mmc_pdata(struct device *dev)
{
return NULL;
}
#endif
static int sdhci_pxav2_probe(struct platform_device *pdev)
{
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct sdhci_host *host = NULL;
struct sdhci_pxa *pxa = NULL;
const struct of_device_id *match;
int ret;
struct clk *clk;
pxa = kzalloc(sizeof(struct sdhci_pxa), GFP_KERNEL);
if (!pxa)
return -ENOMEM;
host = sdhci_pltfm_init(pdev, NULL, 0);
if (IS_ERR(host)) {
kfree(pxa);
return PTR_ERR(host);
}
pltfm_host = sdhci_priv(host);
pltfm_host->priv = pxa;
clk = clk_get(dev, "PXA-SDHCLK");
if (IS_ERR(clk)) {
dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(clk);
goto err_clk_get;
}
pltfm_host->clk = clk;
clk_prepare_enable(clk);
host->quirks = SDHCI_QUIRK_BROKEN_ADMA
| SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
match = of_match_device(of_match_ptr(sdhci_pxav2_of_match), &pdev->dev);
if (match) {
pdata = pxav2_get_mmc_pdata(dev);
}
if (pdata) {
if (pdata->flags & PXA_FLAG_CARD_PERMANENT) {
/* on-chip device */
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
}
/* If slot design supports 8 bit data, indicate this to MMC. */
if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT)
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
if (pdata->quirks)
host->quirks |= pdata->quirks;
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
if (pdata->pm_caps)
host->mmc->pm_caps |= pdata->pm_caps;
}
host->ops = &pxav2_sdhci_ops;
ret = sdhci_add_host(host);
if (ret) {
dev_err(&pdev->dev, "failed to add host\n");
goto err_add_host;
}
platform_set_drvdata(pdev, host);
return 0;
err_add_host:
clk_disable_unprepare(clk);
clk_put(clk);
err_clk_get:
sdhci_pltfm_free(pdev);
kfree(pxa);
return ret;
}
static int sdhci_pxav2_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_pxa *pxa = pltfm_host->priv;
sdhci_remove_host(host, 1);
clk_disable_unprepare(pltfm_host->clk);
clk_put(pltfm_host->clk);
sdhci_pltfm_free(pdev);
kfree(pxa);
return 0;
}
static struct platform_driver sdhci_pxav2_driver = {
.driver = {
.name = "sdhci-pxav2",
#ifdef CONFIG_OF
.of_match_table = sdhci_pxav2_of_match,
#endif
.pm = SDHCI_PLTFM_PMOPS,
},
.probe = sdhci_pxav2_probe,
.remove = sdhci_pxav2_remove,
};
module_platform_driver(sdhci_pxav2_driver);
MODULE_DESCRIPTION("SDHCI driver for pxav2");
MODULE_AUTHOR("Marvell International Ltd.");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,541 @@
/*
* Copyright (C) 2010 Marvell International Ltd.
* Zhangfei Gao <zhangfei.gao@marvell.com>
* Kevin Wang <dwang4@marvell.com>
* Mingwei Wang <mwwang@marvell.com>
* Philip Rakity <prakity@marvell.com>
* Mark Brown <markb@marvell.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/platform_data/pxa_sdhci.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/mbus.h>
#include "sdhci.h"
#include "sdhci-pltfm.h"
#define PXAV3_RPM_DELAY_MS 50
#define SD_CLOCK_BURST_SIZE_SETUP 0x10A
#define SDCLK_SEL 0x100
#define SDCLK_DELAY_SHIFT 9
#define SDCLK_DELAY_MASK 0x1f
#define SD_CFG_FIFO_PARAM 0x100
#define SDCFG_GEN_PAD_CLK_ON (1<<6)
#define SDCFG_GEN_PAD_CLK_CNT_MASK 0xFF
#define SDCFG_GEN_PAD_CLK_CNT_SHIFT 24
#define SD_SPI_MODE 0x108
#define SD_CE_ATA_1 0x10C
#define SD_CE_ATA_2 0x10E
#define SDCE_MISC_INT (1<<2)
#define SDCE_MISC_INT_EN (1<<1)
/*
* These registers are relative to the second register region, for the
* MBus bridge.
*/
#define SDHCI_WINDOW_CTRL(i) (0x80 + ((i) << 3))
#define SDHCI_WINDOW_BASE(i) (0x84 + ((i) << 3))
#define SDHCI_MAX_WIN_NUM 8
static int mv_conf_mbus_windows(struct platform_device *pdev,
const struct mbus_dram_target_info *dram)
{
int i;
void __iomem *regs;
struct resource *res;
if (!dram) {
dev_err(&pdev->dev, "no mbus dram info\n");
return -EINVAL;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(&pdev->dev, "cannot get mbus registers\n");
return -EINVAL;
}
regs = ioremap(res->start, resource_size(res));
if (!regs) {
dev_err(&pdev->dev, "cannot map mbus registers\n");
return -ENOMEM;
}
for (i = 0; i < SDHCI_MAX_WIN_NUM; i++) {
writel(0, regs + SDHCI_WINDOW_CTRL(i));
writel(0, regs + SDHCI_WINDOW_BASE(i));
}
for (i = 0; i < dram->num_cs; i++) {
const struct mbus_dram_window *cs = dram->cs + i;
/* Write size, attributes and target id to control register */
writel(((cs->size - 1) & 0xffff0000) |
(cs->mbus_attr << 8) |
(dram->mbus_dram_target_id << 4) | 1,
regs + SDHCI_WINDOW_CTRL(i));
/* Write base address to base register */
writel(cs->base, regs + SDHCI_WINDOW_BASE(i));
}
iounmap(regs);
return 0;
}
static int armada_38x_quirks(struct platform_device *pdev,
struct sdhci_host *host)
{
struct device_node *np = pdev->dev.of_node;
host->quirks |= SDHCI_QUIRK_MISSING_CAPS;
/*
* According to erratum 'FE-2946959' both SDR50 and DDR50
* modes require specific clock adjustments in SDIO3
* Configuration register, if the adjustment is not done,
* remove them from the capabilities.
*/
host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1);
host->caps1 &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
/*
* According to erratum 'ERR-7878951' Armada 38x SDHCI
* controller has different capabilities than the ones shown
* in its registers
*/
host->caps = sdhci_readl(host, SDHCI_CAPABILITIES);
if (of_property_read_bool(np, "no-1-8-v")) {
host->caps &= ~SDHCI_CAN_VDD_180;
host->mmc->caps &= ~MMC_CAP_1_8V_DDR;
} else {
host->caps &= ~SDHCI_CAN_VDD_330;
}
host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_USE_SDR50_TUNING);
return 0;
}
static void pxav3_reset(struct sdhci_host *host, u8 mask)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
sdhci_reset(host, mask);
if (mask == SDHCI_RESET_ALL) {
/*
* tune timing of read data/command when crc error happen
* no performance impact
*/
if (pdata && 0 != pdata->clk_delay_cycles) {
u16 tmp;
tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK)
<< SDCLK_DELAY_SHIFT;
tmp |= SDCLK_SEL;
writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
}
}
}
#define MAX_WAIT_COUNT 5
static void pxav3_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_pxa *pxa = pltfm_host->priv;
u16 tmp;
int count;
if (pxa->power_mode == MMC_POWER_UP
&& power_mode == MMC_POWER_ON) {
dev_dbg(mmc_dev(host->mmc),
"%s: slot->power_mode = %d,"
"ios->power_mode = %d\n",
__func__,
pxa->power_mode,
power_mode);
/* set we want notice of when 74 clocks are sent */
tmp = readw(host->ioaddr + SD_CE_ATA_2);
tmp |= SDCE_MISC_INT_EN;
writew(tmp, host->ioaddr + SD_CE_ATA_2);
/* start sending the 74 clocks */
tmp = readw(host->ioaddr + SD_CFG_FIFO_PARAM);
tmp |= SDCFG_GEN_PAD_CLK_ON;
writew(tmp, host->ioaddr + SD_CFG_FIFO_PARAM);
/* slowest speed is about 100KHz or 10usec per clock */
udelay(740);
count = 0;
while (count++ < MAX_WAIT_COUNT) {
if ((readw(host->ioaddr + SD_CE_ATA_2)
& SDCE_MISC_INT) == 0)
break;
udelay(10);
}
if (count == MAX_WAIT_COUNT)
dev_warn(mmc_dev(host->mmc), "74 clock interrupt not cleared\n");
/* clear the interrupt bit if posted */
tmp = readw(host->ioaddr + SD_CE_ATA_2);
tmp |= SDCE_MISC_INT;
writew(tmp, host->ioaddr + SD_CE_ATA_2);
}
pxa->power_mode = power_mode;
}
static void pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
{
u16 ctrl_2;
/*
* Set V18_EN -- UHS modes do not work without this.
* does not change signaling voltage
*/
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
/* Select Bus Speed Mode for host */
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
switch (uhs) {
case MMC_TIMING_UHS_SDR12:
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
break;
case MMC_TIMING_UHS_SDR25:
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
break;
case MMC_TIMING_UHS_SDR50:
ctrl_2 |= SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180;
break;
case MMC_TIMING_UHS_SDR104:
ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180;
break;
case MMC_TIMING_UHS_DDR50:
ctrl_2 |= SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180;
break;
}
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
dev_dbg(mmc_dev(host->mmc),
"%s uhs = %d, ctrl_2 = %04X\n",
__func__, uhs, ctrl_2);
}
static const struct sdhci_ops pxav3_sdhci_ops = {
.set_clock = sdhci_set_clock,
.platform_send_init_74_clocks = pxav3_gen_init_74_clocks,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = pxav3_reset,
.set_uhs_signaling = pxav3_set_uhs_signaling,
};
static struct sdhci_pltfm_data sdhci_pxav3_pdata = {
.quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
| SDHCI_QUIRK_32BIT_ADMA_SIZE
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
.ops = &pxav3_sdhci_ops,
};
#ifdef CONFIG_OF
static const struct of_device_id sdhci_pxav3_of_match[] = {
{
.compatible = "mrvl,pxav3-mmc",
},
{
.compatible = "marvell,armada-380-sdhci",
},
{},
};
MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
{
struct sdhci_pxa_platdata *pdata;
struct device_node *np = dev->of_node;
u32 clk_delay_cycles;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
if (!of_property_read_u32(np, "mrvl,clk-delay-cycles",
&clk_delay_cycles))
pdata->clk_delay_cycles = clk_delay_cycles;
return pdata;
}
#else
static inline struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
{
return NULL;
}
#endif
static int sdhci_pxav3_probe(struct platform_device *pdev)
{
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct sdhci_host *host = NULL;
struct sdhci_pxa *pxa = NULL;
const struct of_device_id *match;
int ret;
struct clk *clk;
pxa = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_pxa), GFP_KERNEL);
if (!pxa)
return -ENOMEM;
host = sdhci_pltfm_init(pdev, &sdhci_pxav3_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
/* enable 1/8V DDR capable */
host->mmc->caps |= MMC_CAP_1_8V_DDR;
if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) {
ret = armada_38x_quirks(pdev, host);
if (ret < 0)
goto err_clk_get;
ret = mv_conf_mbus_windows(pdev, mv_mbus_dram_info());
if (ret < 0)
goto err_mbus_win;
}
pltfm_host = sdhci_priv(host);
pltfm_host->priv = pxa;
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(clk);
goto err_clk_get;
}
pltfm_host->clk = clk;
clk_prepare_enable(clk);
match = of_match_device(of_match_ptr(sdhci_pxav3_of_match), &pdev->dev);
if (match) {
ret = mmc_of_parse(host->mmc);
if (ret)
goto err_of_parse;
sdhci_get_of_property(pdev);
pdata = pxav3_get_mmc_pdata(dev);
} else if (pdata) {
/* on-chip device */
if (pdata->flags & PXA_FLAG_CARD_PERMANENT)
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
/* If slot design supports 8 bit data, indicate this to MMC. */
if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT)
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
if (pdata->quirks)
host->quirks |= pdata->quirks;
if (pdata->quirks2)
host->quirks2 |= pdata->quirks2;
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
if (pdata->host_caps2)
host->mmc->caps2 |= pdata->host_caps2;
if (pdata->pm_caps)
host->mmc->pm_caps |= pdata->pm_caps;
if (gpio_is_valid(pdata->ext_cd_gpio)) {
ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio,
0);
if (ret) {
dev_err(mmc_dev(host->mmc),
"failed to allocate card detect gpio\n");
goto err_cd_req;
}
}
}
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, PXAV3_RPM_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_suspend_ignore_children(&pdev->dev, 1);
ret = sdhci_add_host(host);
if (ret) {
dev_err(&pdev->dev, "failed to add host\n");
goto err_add_host;
}
platform_set_drvdata(pdev, host);
if (host->mmc->pm_caps & MMC_PM_KEEP_POWER) {
device_init_wakeup(&pdev->dev, 1);
host->mmc->pm_flags |= MMC_PM_WAKE_SDIO_IRQ;
} else {
device_init_wakeup(&pdev->dev, 0);
}
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
err_add_host:
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
err_of_parse:
err_cd_req:
clk_disable_unprepare(clk);
err_clk_get:
err_mbus_win:
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_pxav3_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
pm_runtime_get_sync(&pdev->dev);
sdhci_remove_host(host, 1);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(pltfm_host->clk);
sdhci_pltfm_free(pdev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_pxav3_suspend(struct device *dev)
{
int ret;
struct sdhci_host *host = dev_get_drvdata(dev);
pm_runtime_get_sync(dev);
ret = sdhci_suspend_host(host);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static int sdhci_pxav3_resume(struct device *dev)
{
int ret;
struct sdhci_host *host = dev_get_drvdata(dev);
pm_runtime_get_sync(dev);
ret = sdhci_resume_host(host);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int sdhci_pxav3_runtime_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
unsigned long flags;
if (pltfm_host->clk) {
spin_lock_irqsave(&host->lock, flags);
host->runtime_suspended = true;
spin_unlock_irqrestore(&host->lock, flags);
clk_disable_unprepare(pltfm_host->clk);
}
return 0;
}
static int sdhci_pxav3_runtime_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
unsigned long flags;
if (pltfm_host->clk) {
clk_prepare_enable(pltfm_host->clk);
spin_lock_irqsave(&host->lock, flags);
host->runtime_suspended = false;
spin_unlock_irqrestore(&host->lock, flags);
}
return 0;
}
#endif
#ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_pxav3_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(sdhci_pxav3_suspend, sdhci_pxav3_resume)
SET_RUNTIME_PM_OPS(sdhci_pxav3_runtime_suspend,
sdhci_pxav3_runtime_resume, NULL)
};
#define SDHCI_PXAV3_PMOPS (&sdhci_pxav3_pmops)
#else
#define SDHCI_PXAV3_PMOPS NULL
#endif
static struct platform_driver sdhci_pxav3_driver = {
.driver = {
.name = "sdhci-pxav3",
#ifdef CONFIG_OF
.of_match_table = sdhci_pxav3_of_match,
#endif
.pm = SDHCI_PXAV3_PMOPS,
},
.probe = sdhci_pxav3_probe,
.remove = sdhci_pxav3_remove,
};
module_platform_driver(sdhci_pxav3_driver);
MODULE_DESCRIPTION("SDHCI driver for pxav3");
MODULE_AUTHOR("Marvell International Ltd.");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,87 @@
/* linux/arch/arm/plat-s3c/include/plat/regs-sdhci.h
*
* Copyright 2008 Openmoko, Inc.
* Copyright 2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C Platform - SDHCI (HSMMC) register definitions
*
* 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 __PLAT_S3C_SDHCI_REGS_H
#define __PLAT_S3C_SDHCI_REGS_H __FILE__
#define S3C_SDHCI_CONTROL2 (0x80)
#define S3C_SDHCI_CONTROL3 (0x84)
#define S3C64XX_SDHCI_CONTROL4 (0x8C)
#define S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR (1 << 31)
#define S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK (1 << 30)
#define S3C_SDHCI_CTRL2_CDINVRXD3 (1 << 29)
#define S3C_SDHCI_CTRL2_SLCARDOUT (1 << 28)
#define S3C_SDHCI_CTRL2_FLTCLKSEL_MASK (0xf << 24)
#define S3C_SDHCI_CTRL2_FLTCLKSEL_SHIFT (24)
#define S3C_SDHCI_CTRL2_FLTCLKSEL(_x) ((_x) << 24)
#define S3C_SDHCI_CTRL2_LVLDAT_MASK (0xff << 16)
#define S3C_SDHCI_CTRL2_LVLDAT_SHIFT (16)
#define S3C_SDHCI_CTRL2_LVLDAT(_x) ((_x) << 16)
#define S3C_SDHCI_CTRL2_ENFBCLKTX (1 << 15)
#define S3C_SDHCI_CTRL2_ENFBCLKRX (1 << 14)
#define S3C_SDHCI_CTRL2_SDCDSEL (1 << 13)
#define S3C_SDHCI_CTRL2_SDSIGPC (1 << 12)
#define S3C_SDHCI_CTRL2_ENBUSYCHKTXSTART (1 << 11)
#define S3C_SDHCI_CTRL2_DFCNT_MASK (0x3 << 9)
#define S3C_SDHCI_CTRL2_DFCNT_SHIFT (9)
#define S3C_SDHCI_CTRL2_DFCNT_NONE (0x0 << 9)
#define S3C_SDHCI_CTRL2_DFCNT_4SDCLK (0x1 << 9)
#define S3C_SDHCI_CTRL2_DFCNT_16SDCLK (0x2 << 9)
#define S3C_SDHCI_CTRL2_DFCNT_64SDCLK (0x3 << 9)
#define S3C_SDHCI_CTRL2_ENCLKOUTHOLD (1 << 8)
#define S3C_SDHCI_CTRL2_RWAITMODE (1 << 7)
#define S3C_SDHCI_CTRL2_DISBUFRD (1 << 6)
#define S3C_SDHCI_CTRL2_SELBASECLK_MASK (0x3 << 4)
#define S3C_SDHCI_CTRL2_SELBASECLK_SHIFT (4)
#define S3C_SDHCI_CTRL2_PWRSYNC (1 << 3)
#define S3C_SDHCI_CTRL2_ENCLKOUTMSKCON (1 << 1)
#define S3C_SDHCI_CTRL2_HWINITFIN (1 << 0)
#define S3C_SDHCI_CTRL3_FCSEL3 (1 << 31)
#define S3C_SDHCI_CTRL3_FCSEL2 (1 << 23)
#define S3C_SDHCI_CTRL3_FCSEL1 (1 << 15)
#define S3C_SDHCI_CTRL3_FCSEL0 (1 << 7)
#define S3C_SDHCI_CTRL3_FIA3_MASK (0x7f << 24)
#define S3C_SDHCI_CTRL3_FIA3_SHIFT (24)
#define S3C_SDHCI_CTRL3_FIA3(_x) ((_x) << 24)
#define S3C_SDHCI_CTRL3_FIA2_MASK (0x7f << 16)
#define S3C_SDHCI_CTRL3_FIA2_SHIFT (16)
#define S3C_SDHCI_CTRL3_FIA2(_x) ((_x) << 16)
#define S3C_SDHCI_CTRL3_FIA1_MASK (0x7f << 8)
#define S3C_SDHCI_CTRL3_FIA1_SHIFT (8)
#define S3C_SDHCI_CTRL3_FIA1(_x) ((_x) << 8)
#define S3C_SDHCI_CTRL3_FIA0_MASK (0x7f << 0)
#define S3C_SDHCI_CTRL3_FIA0_SHIFT (0)
#define S3C_SDHCI_CTRL3_FIA0(_x) ((_x) << 0)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_MASK (0x3 << 16)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_SHIFT (16)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_2mA (0x0 << 16)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_4mA (0x1 << 16)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_7mA (0x2 << 16)
#define S3C64XX_SDHCI_CONTROL4_DRIVE_9mA (0x3 << 16)
#define S3C64XX_SDHCI_CONTROL4_BUSY (1)
#endif /* __PLAT_S3C_SDHCI_REGS_H */

View file

@ -0,0 +1,761 @@
/* linux/drivers/mmc/host/sdhci-s3c.c
*
* Copyright 2008 Openmoko Inc.
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* SDHCI (HSMMC) support for Samsung SoC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/platform_data/mmc-sdhci-s3c.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/mmc/host.h>
#include "sdhci-s3c-regs.h"
#include "sdhci.h"
#define MAX_BUS_CLK (4)
/**
* struct sdhci_s3c - S3C SDHCI instance
* @host: The SDHCI host created
* @pdev: The platform device we where created from.
* @ioarea: The resource created when we claimed the IO area.
* @pdata: The platform data for this controller.
* @cur_clk: The index of the current bus clock.
* @clk_io: The clock for the internal bus interface.
* @clk_bus: The clocks that are available for the SD/MMC bus clock.
*/
struct sdhci_s3c {
struct sdhci_host *host;
struct platform_device *pdev;
struct resource *ioarea;
struct s3c_sdhci_platdata *pdata;
int cur_clk;
int ext_cd_irq;
int ext_cd_gpio;
struct clk *clk_io;
struct clk *clk_bus[MAX_BUS_CLK];
unsigned long clk_rates[MAX_BUS_CLK];
bool no_divider;
};
/**
* struct sdhci_s3c_driver_data - S3C SDHCI platform specific driver data
* @sdhci_quirks: sdhci host specific quirks.
*
* Specifies platform specific configuration of sdhci controller.
* Note: A structure for driver specific platform data is used for future
* expansion of its usage.
*/
struct sdhci_s3c_drv_data {
unsigned int sdhci_quirks;
bool no_divider;
};
static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host)
{
return sdhci_priv(host);
}
/**
* sdhci_s3c_get_max_clk - callback to get maximum clock frequency.
* @host: The SDHCI host instance.
*
* Callback to return the maximum clock rate acheivable by the controller.
*/
static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
unsigned long rate, max = 0;
int src;
for (src = 0; src < MAX_BUS_CLK; src++) {
rate = ourhost->clk_rates[src];
if (rate > max)
max = rate;
}
return max;
}
/**
* sdhci_s3c_consider_clock - consider one the bus clocks for current setting
* @ourhost: Our SDHCI instance.
* @src: The source clock index.
* @wanted: The clock frequency wanted.
*/
static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
unsigned int src,
unsigned int wanted)
{
unsigned long rate;
struct clk *clksrc = ourhost->clk_bus[src];
int shift;
if (IS_ERR(clksrc))
return UINT_MAX;
/*
* If controller uses a non-standard clock division, find the best clock
* speed possible with selected clock source and skip the division.
*/
if (ourhost->no_divider) {
rate = clk_round_rate(clksrc, wanted);
return wanted - rate;
}
rate = ourhost->clk_rates[src];
for (shift = 0; shift <= 8; ++shift) {
if ((rate >> shift) <= wanted)
break;
}
if (shift > 8) {
dev_dbg(&ourhost->pdev->dev,
"clk %d: rate %ld, min rate %lu > wanted %u\n",
src, rate, rate / 256, wanted);
return UINT_MAX;
}
dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
src, rate, wanted, rate >> shift);
return wanted - (rate >> shift);
}
/**
* sdhci_s3c_set_clock - callback on clock change
* @host: The SDHCI host being changed
* @clock: The clock rate being requested.
*
* When the card's clock is going to be changed, look at the new frequency
* and find the best clock source to go with it.
*/
static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_s3c *ourhost = to_s3c(host);
unsigned int best = UINT_MAX;
unsigned int delta;
int best_src = 0;
int src;
u32 ctrl;
host->mmc->actual_clock = 0;
/* don't bother if the clock is going off. */
if (clock == 0) {
sdhci_set_clock(host, clock);
return;
}
for (src = 0; src < MAX_BUS_CLK; src++) {
delta = sdhci_s3c_consider_clock(ourhost, src, clock);
if (delta < best) {
best = delta;
best_src = src;
}
}
dev_dbg(&ourhost->pdev->dev,
"selected source %d, clock %d, delta %d\n",
best_src, clock, best);
/* select the new clock source */
if (ourhost->cur_clk != best_src) {
struct clk *clk = ourhost->clk_bus[best_src];
clk_prepare_enable(clk);
if (ourhost->cur_clk >= 0)
clk_disable_unprepare(
ourhost->clk_bus[ourhost->cur_clk]);
ourhost->cur_clk = best_src;
host->max_clk = ourhost->clk_rates[best_src];
}
/* turn clock off to card before changing clock source */
writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
/* reprogram default hardware configuration */
writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA,
host->ioaddr + S3C64XX_SDHCI_CONTROL4);
ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR |
S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK |
S3C_SDHCI_CTRL2_ENFBCLKRX |
S3C_SDHCI_CTRL2_DFCNT_NONE |
S3C_SDHCI_CTRL2_ENCLKOUTHOLD);
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
/* reconfigure the controller for new clock rate */
ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0);
if (clock < 25 * 1000000)
ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2);
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3);
sdhci_set_clock(host, clock);
}
/**
* sdhci_s3c_get_min_clock - callback to get minimal supported clock value
* @host: The SDHCI host being queried
*
* To init mmc host properly a minimal clock value is needed. For high system
* bus clock's values the standard formula gives values out of allowed range.
* The clock still can be set to lower values, if clock source other then
* system bus is selected.
*/
static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
unsigned long rate, min = ULONG_MAX;
int src;
for (src = 0; src < MAX_BUS_CLK; src++) {
rate = ourhost->clk_rates[src] / 256;
if (!rate)
continue;
if (rate < min)
min = rate;
}
return min;
}
/* sdhci_cmu_get_max_clk - callback to get maximum clock frequency.*/
static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
unsigned long rate, max = 0;
int src;
for (src = 0; src < MAX_BUS_CLK; src++) {
struct clk *clk;
clk = ourhost->clk_bus[src];
if (IS_ERR(clk))
continue;
rate = clk_round_rate(clk, ULONG_MAX);
if (rate > max)
max = rate;
}
return max;
}
/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */
static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host)
{
struct sdhci_s3c *ourhost = to_s3c(host);
unsigned long rate, min = ULONG_MAX;
int src;
for (src = 0; src < MAX_BUS_CLK; src++) {
struct clk *clk;
clk = ourhost->clk_bus[src];
if (IS_ERR(clk))
continue;
rate = clk_round_rate(clk, 0);
if (rate < min)
min = rate;
}
return min;
}
/* sdhci_cmu_set_clock - callback on clock change.*/
static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_s3c *ourhost = to_s3c(host);
struct device *dev = &ourhost->pdev->dev;
unsigned long timeout;
u16 clk = 0;
host->mmc->actual_clock = 0;
/* If the clock is going off, set to 0 at clock control register */
if (clock == 0) {
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
return;
}
sdhci_s3c_set_clock(host, clock);
clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock);
clk = SDHCI_CLOCK_INT_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
/* Wait max 20 ms */
timeout = 20;
while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
& SDHCI_CLOCK_INT_STABLE)) {
if (timeout == 0) {
dev_err(dev, "%s: Internal clock never stabilised.\n",
mmc_hostname(host->mmc));
return;
}
timeout--;
mdelay(1);
}
clk |= SDHCI_CLOCK_CARD_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
}
/**
* sdhci_s3c_set_bus_width - support 8bit buswidth
* @host: The SDHCI host being queried
* @width: MMC_BUS_WIDTH_ macro for the bus width being requested
*
* We have 8-bit width support but is not a v3 controller.
* So we add platform_bus_width() and support 8bit width.
*/
static void sdhci_s3c_set_bus_width(struct sdhci_host *host, int width)
{
u8 ctrl;
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
switch (width) {
case MMC_BUS_WIDTH_8:
ctrl |= SDHCI_CTRL_8BITBUS;
ctrl &= ~SDHCI_CTRL_4BITBUS;
break;
case MMC_BUS_WIDTH_4:
ctrl |= SDHCI_CTRL_4BITBUS;
ctrl &= ~SDHCI_CTRL_8BITBUS;
break;
default:
ctrl &= ~SDHCI_CTRL_4BITBUS;
ctrl &= ~SDHCI_CTRL_8BITBUS;
break;
}
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
static struct sdhci_ops sdhci_s3c_ops = {
.get_max_clock = sdhci_s3c_get_max_clk,
.set_clock = sdhci_s3c_set_clock,
.get_min_clock = sdhci_s3c_get_min_clock,
.set_bus_width = sdhci_s3c_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
#ifdef CONFIG_OF
static int sdhci_s3c_parse_dt(struct device *dev,
struct sdhci_host *host, struct s3c_sdhci_platdata *pdata)
{
struct device_node *node = dev->of_node;
u32 max_width;
/* if the bus-width property is not specified, assume width as 1 */
if (of_property_read_u32(node, "bus-width", &max_width))
max_width = 1;
pdata->max_width = max_width;
/* get the card detection method */
if (of_get_property(node, "broken-cd", NULL)) {
pdata->cd_type = S3C_SDHCI_CD_NONE;
return 0;
}
if (of_get_property(node, "non-removable", NULL)) {
pdata->cd_type = S3C_SDHCI_CD_PERMANENT;
return 0;
}
if (of_get_named_gpio(node, "cd-gpios", 0))
return 0;
/* assuming internal card detect that will be configured by pinctrl */
pdata->cd_type = S3C_SDHCI_CD_INTERNAL;
return 0;
}
#else
static int sdhci_s3c_parse_dt(struct device *dev,
struct sdhci_host *host, struct s3c_sdhci_platdata *pdata)
{
return -EINVAL;
}
#endif
static const struct of_device_id sdhci_s3c_dt_match[];
static inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data(
struct platform_device *pdev)
{
#ifdef CONFIG_OF
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(sdhci_s3c_dt_match, pdev->dev.of_node);
return (struct sdhci_s3c_drv_data *)match->data;
}
#endif
return (struct sdhci_s3c_drv_data *)
platform_get_device_id(pdev)->driver_data;
}
static int sdhci_s3c_probe(struct platform_device *pdev)
{
struct s3c_sdhci_platdata *pdata;
struct sdhci_s3c_drv_data *drv_data;
struct device *dev = &pdev->dev;
struct sdhci_host *host;
struct sdhci_s3c *sc;
struct resource *res;
int ret, irq, ptr, clks;
if (!pdev->dev.platform_data && !pdev->dev.of_node) {
dev_err(dev, "no device data specified\n");
return -ENOENT;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "no irq specified\n");
return irq;
}
host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
if (IS_ERR(host)) {
dev_err(dev, "sdhci_alloc_host() failed\n");
return PTR_ERR(host);
}
sc = sdhci_priv(host);
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
ret = -ENOMEM;
goto err_pdata_io_clk;
}
if (pdev->dev.of_node) {
ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata);
if (ret)
goto err_pdata_io_clk;
} else {
memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));
sc->ext_cd_gpio = -1; /* invalid gpio number */
}
drv_data = sdhci_s3c_get_driver_data(pdev);
sc->host = host;
sc->pdev = pdev;
sc->pdata = pdata;
sc->cur_clk = -1;
platform_set_drvdata(pdev, host);
sc->clk_io = devm_clk_get(dev, "hsmmc");
if (IS_ERR(sc->clk_io)) {
dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(sc->clk_io);
goto err_pdata_io_clk;
}
/* enable the local io clock and keep it running for the moment. */
clk_prepare_enable(sc->clk_io);
for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
char name[14];
snprintf(name, 14, "mmc_busclk.%d", ptr);
sc->clk_bus[ptr] = devm_clk_get(dev, name);
if (IS_ERR(sc->clk_bus[ptr]))
continue;
clks++;
sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]);
dev_info(dev, "clock source %d: %s (%ld Hz)\n",
ptr, name, sc->clk_rates[ptr]);
}
if (clks == 0) {
dev_err(dev, "failed to find any bus clocks\n");
ret = -ENOENT;
goto err_no_busclks;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->ioaddr)) {
ret = PTR_ERR(host->ioaddr);
goto err_req_regs;
}
/* Ensure we have minimal gpio selected CMD/CLK/Detect */
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev, pdata->max_width);
host->hw_name = "samsung-hsmmc";
host->ops = &sdhci_s3c_ops;
host->quirks = 0;
host->quirks2 = 0;
host->irq = irq;
/* Setup quirks for the controller */
host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
if (drv_data) {
host->quirks |= drv_data->sdhci_quirks;
sc->no_divider = drv_data->no_divider;
}
#ifndef CONFIG_MMC_SDHCI_S3C_DMA
/* we currently see overruns on errors, so disable the SDMA
* support as well. */
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
#endif /* CONFIG_MMC_SDHCI_S3C_DMA */
/* It seems we do not get an DATA transfer complete on non-busy
* transfers, not sure if this is a problem with this specific
* SDHCI block, or a missing configuration that needs to be set. */
host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
/* This host supports the Auto CMD12 */
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
/* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */
host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;
if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->mmc->caps = MMC_CAP_NONREMOVABLE;
switch (pdata->max_width) {
case 8:
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
case 4:
host->mmc->caps |= MMC_CAP_4_BIT_DATA;
break;
}
if (pdata->pm_caps)
host->mmc->pm_caps |= pdata->pm_caps;
host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE);
/* HSMMC on Samsung SoCs uses SDCLK as timeout clock */
host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;
/*
* If controller does not have internal clock divider,
* we can use overriding functions instead of default.
*/
if (sc->no_divider) {
sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock;
sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock;
sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock;
}
/* It supports additional host capabilities if needed */
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
if (pdata->host_caps2)
host->mmc->caps2 |= pdata->host_caps2;
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
pm_runtime_use_autosuspend(&pdev->dev);
pm_suspend_ignore_children(&pdev->dev, 1);
mmc_of_parse(host->mmc);
ret = sdhci_add_host(host);
if (ret) {
dev_err(dev, "sdhci_add_host() failed\n");
goto err_req_regs;
}
#ifdef CONFIG_PM_RUNTIME
if (pdata->cd_type != S3C_SDHCI_CD_INTERNAL)
clk_disable_unprepare(sc->clk_io);
#endif
return 0;
err_req_regs:
pm_runtime_disable(&pdev->dev);
err_no_busclks:
clk_disable_unprepare(sc->clk_io);
err_pdata_io_clk:
sdhci_free_host(host);
return ret;
}
static int sdhci_s3c_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_s3c *sc = sdhci_priv(host);
if (sc->ext_cd_irq)
free_irq(sc->ext_cd_irq, sc);
#ifdef CONFIG_PM_RUNTIME
if (sc->pdata->cd_type != S3C_SDHCI_CD_INTERNAL)
clk_prepare_enable(sc->clk_io);
#endif
sdhci_remove_host(host, 1);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(sc->clk_io);
sdhci_free_host(host);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_s3c_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
return sdhci_suspend_host(host);
}
static int sdhci_s3c_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
return sdhci_resume_host(host);
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int sdhci_s3c_runtime_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_s3c *ourhost = to_s3c(host);
struct clk *busclk = ourhost->clk_io;
int ret;
ret = sdhci_runtime_suspend_host(host);
if (ourhost->cur_clk >= 0)
clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]);
clk_disable_unprepare(busclk);
return ret;
}
static int sdhci_s3c_runtime_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_s3c *ourhost = to_s3c(host);
struct clk *busclk = ourhost->clk_io;
int ret;
clk_prepare_enable(busclk);
if (ourhost->cur_clk >= 0)
clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]);
ret = sdhci_runtime_resume_host(host);
return ret;
}
#endif
#ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_s3c_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(sdhci_s3c_suspend, sdhci_s3c_resume)
SET_RUNTIME_PM_OPS(sdhci_s3c_runtime_suspend, sdhci_s3c_runtime_resume,
NULL)
};
#define SDHCI_S3C_PMOPS (&sdhci_s3c_pmops)
#else
#define SDHCI_S3C_PMOPS NULL
#endif
#if defined(CONFIG_CPU_EXYNOS4210) || defined(CONFIG_SOC_EXYNOS4212)
static struct sdhci_s3c_drv_data exynos4_sdhci_drv_data = {
.no_divider = true,
};
#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)&exynos4_sdhci_drv_data)
#else
#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)NULL)
#endif
static struct platform_device_id sdhci_s3c_driver_ids[] = {
{
.name = "s3c-sdhci",
.driver_data = (kernel_ulong_t)NULL,
}, {
.name = "exynos4-sdhci",
.driver_data = EXYNOS4_SDHCI_DRV_DATA,
},
{ }
};
MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id sdhci_s3c_dt_match[] = {
{ .compatible = "samsung,s3c6410-sdhci", },
{ .compatible = "samsung,exynos4210-sdhci",
.data = (void *)EXYNOS4_SDHCI_DRV_DATA },
{},
};
MODULE_DEVICE_TABLE(of, sdhci_s3c_dt_match);
#endif
static struct platform_driver sdhci_s3c_driver = {
.probe = sdhci_s3c_probe,
.remove = sdhci_s3c_remove,
.id_table = sdhci_s3c_driver_ids,
.driver = {
.name = "s3c-sdhci",
.of_match_table = of_match_ptr(sdhci_s3c_dt_match),
.pm = SDHCI_S3C_PMOPS,
},
};
module_platform_driver(sdhci_s3c_driver);
MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue");
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:s3c-sdhci");

View file

@ -0,0 +1,206 @@
/*
* SDHCI support for SiRF primaII and marco SoCs
*
* Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company.
*
* Licensed under GPLv2 or later.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/mmc/host.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/mmc/slot-gpio.h>
#include "sdhci-pltfm.h"
#define SDHCI_SIRF_8BITBUS BIT(3)
struct sdhci_sirf_priv {
struct clk *clk;
int gpio_cd;
};
static unsigned int sdhci_sirf_get_max_clk(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_sirf_priv *priv = sdhci_pltfm_priv(pltfm_host);
return clk_get_rate(priv->clk);
}
static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width)
{
u8 ctrl;
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
ctrl &= ~(SDHCI_CTRL_4BITBUS | SDHCI_SIRF_8BITBUS);
/*
* CSR atlas7 and prima2 SD host version is not 3.0
* 8bit-width enable bit of CSR SD hosts is 3,
* while stardard hosts use bit 5
*/
if (width == MMC_BUS_WIDTH_8)
ctrl |= SDHCI_SIRF_8BITBUS;
else if (width == MMC_BUS_WIDTH_4)
ctrl |= SDHCI_CTRL_4BITBUS;
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
static struct sdhci_ops sdhci_sirf_ops = {
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_sirf_get_max_clk,
.set_bus_width = sdhci_sirf_set_bus_width,
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
static struct sdhci_pltfm_data sdhci_sirf_pdata = {
.ops = &sdhci_sirf_ops,
.quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
SDHCI_QUIRK_DELAY_AFTER_POWER,
};
static int sdhci_sirf_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_sirf_priv *priv;
struct clk *clk;
int gpio_cd;
int ret;
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get clock");
return PTR_ERR(clk);
}
if (pdev->dev.of_node)
gpio_cd = of_get_named_gpio(pdev->dev.of_node, "cd-gpios", 0);
else
gpio_cd = -EINVAL;
host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, sizeof(struct sdhci_sirf_priv));
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
priv = sdhci_pltfm_priv(pltfm_host);
priv->clk = clk;
priv->gpio_cd = gpio_cd;
sdhci_get_of_property(pdev);
ret = clk_prepare_enable(priv->clk);
if (ret)
goto err_clk_prepare;
ret = sdhci_add_host(host);
if (ret)
goto err_sdhci_add;
/*
* We must request the IRQ after sdhci_add_host(), as the tasklet only
* gets setup in sdhci_add_host() and we oops.
*/
if (gpio_is_valid(priv->gpio_cd)) {
ret = mmc_gpio_request_cd(host->mmc, priv->gpio_cd, 0);
if (ret) {
dev_err(&pdev->dev, "card detect irq request failed: %d\n",
ret);
goto err_request_cd;
}
mmc_gpiod_request_cd_irq(host->mmc);
}
return 0;
err_request_cd:
sdhci_remove_host(host, 0);
err_sdhci_add:
clk_disable_unprepare(priv->clk);
err_clk_prepare:
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_sirf_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_sirf_priv *priv = sdhci_pltfm_priv(pltfm_host);
sdhci_pltfm_unregister(pdev);
if (gpio_is_valid(priv->gpio_cd))
mmc_gpio_free_cd(host->mmc);
clk_disable_unprepare(priv->clk);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_sirf_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_sirf_priv *priv = sdhci_pltfm_priv(pltfm_host);
int ret;
ret = sdhci_suspend_host(host);
if (ret)
return ret;
clk_disable(priv->clk);
return 0;
}
static int sdhci_sirf_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_sirf_priv *priv = sdhci_pltfm_priv(pltfm_host);
int ret;
ret = clk_enable(priv->clk);
if (ret) {
dev_dbg(dev, "Resume: Error enabling clock\n");
return ret;
}
return sdhci_resume_host(host);
}
static SIMPLE_DEV_PM_OPS(sdhci_sirf_pm_ops, sdhci_sirf_suspend, sdhci_sirf_resume);
#endif
static const struct of_device_id sdhci_sirf_of_match[] = {
{ .compatible = "sirf,prima2-sdhc" },
{ }
};
MODULE_DEVICE_TABLE(of, sdhci_sirf_of_match);
static struct platform_driver sdhci_sirf_driver = {
.driver = {
.name = "sdhci-sirf",
.of_match_table = sdhci_sirf_of_match,
#ifdef CONFIG_PM_SLEEP
.pm = &sdhci_sirf_pm_ops,
#endif
},
.probe = sdhci_sirf_probe,
.remove = sdhci_sirf_remove,
};
module_platform_driver(sdhci_sirf_driver);
MODULE_DESCRIPTION("SDHCI driver for SiRFprimaII/SiRFmarco");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL v2");

Some files were not shown because too many files have changed in this diff Show more