Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
16
drivers/mmc/core/Kconfig
Normal file
16
drivers/mmc/core/Kconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# MMC core configuration
|
||||
#
|
||||
|
||||
config MMC_UNSAFE_RESUME
|
||||
bool "Allow unsafe resume (DANGEROUS)"
|
||||
help
|
||||
If you say Y here, the MMC layer will assume that all cards
|
||||
stayed in their respective slots during the suspend. The
|
||||
normal behaviour is to remove them at suspend and
|
||||
redetecting them at resume. Breaking this assumption will
|
||||
in most cases result in data corruption.
|
||||
|
||||
This option is usually just for embedded systems which use
|
||||
a MMC/SD card for rootfs. Most people should say N here.
|
||||
|
||||
14
drivers/mmc/core/Makefile
Normal file
14
drivers/mmc/core/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# Makefile for the kernel mmc core.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_MMC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_MMC) += mmc_core.o
|
||||
mmc_core-y := core.o sysfs.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
|
||||
|
||||
303
drivers/mmc/core/bus.c
Normal file
303
drivers/mmc/core/bus.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/bus.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC card bus driver model
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
#include "core.h"
|
||||
#include "sdio_cis.h"
|
||||
#include "bus.h"
|
||||
|
||||
#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
|
||||
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
|
||||
|
||||
static ssize_t mmc_type_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
return sprintf(buf, "MMC\n");
|
||||
case MMC_TYPE_SD:
|
||||
return sprintf(buf, "SD\n");
|
||||
case MMC_TYPE_SDIO:
|
||||
return sprintf(buf, "SDIO\n");
|
||||
default:
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(type),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* This currently matches any MMC driver to any MMC card - drivers
|
||||
* themselves make the decision whether to drive this card in their
|
||||
* probe method.
|
||||
*/
|
||||
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// To fit 2.6.21.
|
||||
static int
|
||||
mmc_bus_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
const char *type;
|
||||
int i = 0;
|
||||
int length = 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;
|
||||
default:
|
||||
type = NULL;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "MMC_TYPE=%s", type))
|
||||
return -ENOMEM;;
|
||||
}
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "MMC_NAME=%s", mmc_card_name(card)))
|
||||
return -ENOMEM;
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
static int mmc_bus_probe(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
return drv->probe(card);
|
||||
}
|
||||
|
||||
static int mmc_bus_remove(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
drv->remove(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->suspend)
|
||||
ret = drv->suspend(card, state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
ret = drv->resume(card);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bus_type mmc_bus_type = {
|
||||
.name = "mmc",
|
||||
.dev_attrs = mmc_dev_attrs,
|
||||
.match = mmc_bus_match,
|
||||
.uevent = mmc_bus_uevent,
|
||||
.probe = mmc_bus_probe,
|
||||
.remove = mmc_bus_remove,
|
||||
.suspend = mmc_bus_suspend,
|
||||
.resume = mmc_bus_resume,
|
||||
};
|
||||
|
||||
int mmc_register_bus(void)
|
||||
{
|
||||
return bus_register(&mmc_bus_type);
|
||||
}
|
||||
|
||||
void mmc_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&mmc_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_register_driver - register a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
int mmc_register_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_register_driver);
|
||||
|
||||
/**
|
||||
* mmc_unregister_driver - unregister a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
void mmc_unregister_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_unregister_driver);
|
||||
|
||||
static void mmc_release_card(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = dev_to_mmc_card(dev);
|
||||
|
||||
sdio_free_common_cis(card);
|
||||
|
||||
if (card->info)
|
||||
kfree(card->info);
|
||||
|
||||
kfree(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new MMC card structure.
|
||||
*/
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
||||
card = 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;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new MMC card with the driver model.
|
||||
*/
|
||||
int mmc_add_card(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
const char *type;
|
||||
|
||||
snprintf(card->dev.bus_id, sizeof(card->dev.bus_id),
|
||||
"%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))
|
||||
type = "SDHC";
|
||||
break;
|
||||
case MMC_TYPE_SDIO:
|
||||
type = "SDIO";
|
||||
break;
|
||||
default:
|
||||
type = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
printk(KERN_INFO "%s: new %s%s card on SPI\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_highspeed(card) ? "high speed " : "",
|
||||
type);
|
||||
} else {
|
||||
printk(KERN_INFO "%s: new %s%s card at address %04x\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_highspeed(card) ? "high speed " : "",
|
||||
type, card->rca);
|
||||
}
|
||||
|
||||
card->dev.uevent_suppress = 1;
|
||||
|
||||
ret = device_add(&card->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (card->host->bus_ops->sysfs_add) {
|
||||
ret = card->host->bus_ops->sysfs_add(card->host, card);
|
||||
if (ret) {
|
||||
device_del(&card->dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
card->dev.uevent_suppress = 0;
|
||||
|
||||
kobject_uevent(&card->dev.kobj, KOBJ_ADD);
|
||||
|
||||
mmc_card_set_present(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a new MMC card with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void mmc_remove_card(struct mmc_card *card)
|
||||
{
|
||||
if (mmc_card_present(card)) {
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
printk(KERN_INFO "%s: SPI card removed\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
printk(KERN_INFO "%s: card %04x removed\n",
|
||||
mmc_hostname(card->host), card->rca);
|
||||
}
|
||||
|
||||
if (card->host->bus_ops->sysfs_remove)
|
||||
card->host->bus_ops->sysfs_remove(card->host, card);
|
||||
device_del(&card->dev);
|
||||
}
|
||||
|
||||
put_device(&card->dev);
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/bus.h
Normal file
22
drivers/mmc/core/bus.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/bus.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_BUS_H
|
||||
#define _MMC_CORE_BUS_H
|
||||
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host);
|
||||
int mmc_add_card(struct mmc_card *card);
|
||||
void mmc_remove_card(struct mmc_card *card);
|
||||
|
||||
int mmc_register_bus(void);
|
||||
void mmc_unregister_bus(void);
|
||||
|
||||
#endif
|
||||
|
||||
833
drivers/mmc/core/core.c
Normal file
833
drivers/mmc/core/core.c
Normal file
@@ -0,0 +1,833 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/core.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
* MMCv4 support Copyright (C) 2006 Philip Langdale, 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.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "host.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr);
|
||||
extern int mmc_attach_sd(struct mmc_host *host, u32 ocr);
|
||||
extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr);
|
||||
|
||||
static struct workqueue_struct *workqueue;
|
||||
|
||||
/*
|
||||
* Enabling software CRCs on the data blocks can be a significant (30%)
|
||||
* performance cost, and for other reasons may not always be desired.
|
||||
* So we allow it it to be disabled.
|
||||
*/
|
||||
int use_spi_crc = 1;
|
||||
module_param(use_spi_crc, bool, 0);
|
||||
|
||||
/*
|
||||
* Internal function. Schedule delayed work in the MMC work queue.
|
||||
*/
|
||||
static int mmc_schedule_delayed_work(struct delayed_work *work,
|
||||
unsigned long delay)
|
||||
{
|
||||
return queue_delayed_work(workqueue, work, delay);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function. Flush all scheduled work from the MMC work queue.
|
||||
*/
|
||||
static void mmc_flush_scheduled_work(void)
|
||||
{
|
||||
flush_workqueue(workqueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_request_done - finish processing an MMC request
|
||||
* @host: MMC host which completed request
|
||||
* @mrq: MMC request which request
|
||||
*
|
||||
* MMC drivers should call this function when they have completed
|
||||
* their processing of a request.
|
||||
*/
|
||||
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
struct mmc_command *cmd = mrq->cmd;
|
||||
int err = cmd->error;
|
||||
|
||||
if (err && cmd->retries && mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
cmd->retries = 0;
|
||||
}
|
||||
|
||||
if (err && cmd->retries) {
|
||||
pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
|
||||
mmc_hostname(host), cmd->opcode, err);
|
||||
|
||||
cmd->retries--;
|
||||
cmd->error = 0;
|
||||
host->ops->request(host, mrq);
|
||||
} else {
|
||||
led_trigger_event(host->led, LED_OFF);
|
||||
|
||||
pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n",
|
||||
mmc_hostname(host), cmd->opcode, err,
|
||||
cmd->resp[0], cmd->resp[1],
|
||||
cmd->resp[2], cmd->resp[3]);
|
||||
|
||||
if (mrq->data) {
|
||||
pr_debug("%s: %d bytes transferred: %d\n",
|
||||
mmc_hostname(host),
|
||||
mrq->data->bytes_xfered, mrq->data->error);
|
||||
}
|
||||
|
||||
if (mrq->stop) {
|
||||
pr_debug("%s: (CMD%u): %d: %08x %08x %08x %08x\n",
|
||||
mmc_hostname(host), mrq->stop->opcode,
|
||||
mrq->stop->error,
|
||||
mrq->stop->resp[0], mrq->stop->resp[1],
|
||||
mrq->stop->resp[2], mrq->stop->resp[3]);
|
||||
}
|
||||
|
||||
if (mrq->done)
|
||||
mrq->done(mrq);
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_request_done);
|
||||
|
||||
static void
|
||||
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned int i, sz;
|
||||
#endif
|
||||
|
||||
pr_debug("%s: starting CMD%u arg %08x flags %08x\n",
|
||||
mmc_hostname(host), mrq->cmd->opcode,
|
||||
mrq->cmd->arg, mrq->cmd->flags);
|
||||
|
||||
if (mrq->data) {
|
||||
pr_debug("%s: blksz %d blocks %d flags %08x "
|
||||
"tsac %d ms nsac %d\n",
|
||||
mmc_hostname(host), mrq->data->blksz,
|
||||
mrq->data->blocks, mrq->data->flags,
|
||||
mrq->data->timeout_ns / 1000000,
|
||||
mrq->data->timeout_clks);
|
||||
}
|
||||
|
||||
if (mrq->stop) {
|
||||
pr_debug("%s: CMD%u arg %08x flags %08x\n",
|
||||
mmc_hostname(host), mrq->stop->opcode,
|
||||
mrq->stop->arg, mrq->stop->flags);
|
||||
}
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
led_trigger_event(host->led, LED_FULL);
|
||||
|
||||
mrq->cmd->error = 0;
|
||||
mrq->cmd->mrq = mrq;
|
||||
if (mrq->data) {
|
||||
BUG_ON(mrq->data->blksz > host->max_blk_size);
|
||||
BUG_ON(mrq->data->blocks > host->max_blk_count);
|
||||
BUG_ON(mrq->data->blocks * mrq->data->blksz >
|
||||
host->max_req_size);
|
||||
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
sz = 0;
|
||||
for (i = 0;i < mrq->data->sg_len;i++)
|
||||
sz += mrq->data->sg[i].length;
|
||||
BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);
|
||||
#endif
|
||||
|
||||
mrq->cmd->data = mrq->data;
|
||||
mrq->data->error = 0;
|
||||
mrq->data->mrq = mrq;
|
||||
if (mrq->stop) {
|
||||
mrq->data->stop = mrq->stop;
|
||||
mrq->stop->error = 0;
|
||||
mrq->stop->mrq = mrq;
|
||||
}
|
||||
}
|
||||
host->ops->request(host, mrq);
|
||||
}
|
||||
|
||||
static void mmc_wait_done(struct mmc_request *mrq)
|
||||
{
|
||||
complete(mrq->done_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_wait_for_req - start a request and wait for completion
|
||||
* @host: MMC host to start command
|
||||
* @mrq: MMC request to start
|
||||
*
|
||||
* Start a new MMC custom command request for a host, and wait
|
||||
* for the command to complete. Does not attempt to parse the
|
||||
* response.
|
||||
*/
|
||||
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(complete);
|
||||
|
||||
mrq->done_data = &complete;
|
||||
mrq->done = mmc_wait_done;
|
||||
|
||||
mmc_start_request(host, mrq);
|
||||
|
||||
wait_for_completion(&complete);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_req);
|
||||
|
||||
/**
|
||||
* mmc_wait_for_cmd - start a command and wait for completion
|
||||
* @host: MMC host to start command
|
||||
* @cmd: MMC command to start
|
||||
* @retries: maximum number of retries
|
||||
*
|
||||
* Start a new MMC command for a host, and wait for the command
|
||||
* to complete. Return any error that occurred while the command
|
||||
* was executing. Do not attempt to parse the response.
|
||||
*/
|
||||
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
memset(cmd->resp, 0, sizeof(cmd->resp));
|
||||
cmd->retries = retries;
|
||||
|
||||
mrq.cmd = cmd;
|
||||
cmd->data = NULL;
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
return cmd->error;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_cmd);
|
||||
|
||||
/**
|
||||
* mmc_set_data_timeout - set the timeout for a data command
|
||||
* @data: data phase for command
|
||||
* @card: the MMC card associated with the data transfer
|
||||
*
|
||||
* Computes the data timeout parameters according to the
|
||||
* correct algorithm given the card type.
|
||||
*/
|
||||
void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
|
||||
{
|
||||
unsigned int mult;
|
||||
|
||||
/*
|
||||
* SDIO cards only define an upper 1 s limit on access.
|
||||
*/
|
||||
if (mmc_card_sdio(card)) {
|
||||
data->timeout_ns = 1000000000;
|
||||
data->timeout_clks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SD cards use a 100 multiplier rather than 10
|
||||
*/
|
||||
mult = mmc_card_sd(card) ? 100 : 10;
|
||||
|
||||
/*
|
||||
* Scale up the multiplier (and therefore the timeout) by
|
||||
* the r2w factor for writes.
|
||||
*/
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
mult <<= card->csd.r2w_factor;
|
||||
|
||||
data->timeout_ns = card->csd.tacc_ns * mult;
|
||||
data->timeout_clks = card->csd.tacc_clks * mult;
|
||||
|
||||
/*
|
||||
* SD cards also have an upper limit on the timeout.
|
||||
*/
|
||||
if (mmc_card_sd(card)) {
|
||||
unsigned int timeout_us, limit_us;
|
||||
|
||||
timeout_us = data->timeout_ns / 1000;
|
||||
timeout_us += data->timeout_clks * 1000 /
|
||||
(card->host->ios.clock / 1000);
|
||||
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
limit_us = 250000;
|
||||
else
|
||||
limit_us = 100000;
|
||||
|
||||
/*
|
||||
* SDHC cards always use these fixed values.
|
||||
*/
|
||||
if (timeout_us > limit_us || mmc_card_blockaddr(card)) {
|
||||
data->timeout_ns = limit_us * 1000;
|
||||
data->timeout_clks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_set_data_timeout);
|
||||
|
||||
/**
|
||||
* __mmc_claim_host - exclusively claim a host
|
||||
* @host: mmc host to claim
|
||||
* @abort: whether or not the operation should be aborted
|
||||
*
|
||||
* Claim a host for a set of operations. If @abort is non null and
|
||||
* dereference a non-zero value then this will return prematurely with
|
||||
* that non-zero value without acquiring the lock. Returns zero
|
||||
* with the lock held otherwise.
|
||||
*/
|
||||
int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
|
||||
{
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
unsigned long flags;
|
||||
int stop;
|
||||
|
||||
might_sleep();
|
||||
|
||||
add_wait_queue(&host->wq, &wait);
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
while (1) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
stop = abort ? atomic_read(abort) : 0;
|
||||
if (stop || !host->claimed)
|
||||
break;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
schedule();
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
if (!stop)
|
||||
host->claimed = 1;
|
||||
else
|
||||
wake_up(&host->wq);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
remove_wait_queue(&host->wq, &wait);
|
||||
return stop;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(__mmc_claim_host);
|
||||
|
||||
/**
|
||||
* mmc_release_host - release a host
|
||||
* @host: mmc host to release
|
||||
*
|
||||
* Release a MMC host, allowing others to claim the host
|
||||
* for their operations.
|
||||
*/
|
||||
void mmc_release_host(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->claimed = 0;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
wake_up(&host->wq);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_release_host);
|
||||
|
||||
/*
|
||||
* Internal function that does the actual ios call to the host driver,
|
||||
* optionally printing some debug output.
|
||||
*/
|
||||
static inline void mmc_set_ios(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_ios *ios = &host->ios;
|
||||
|
||||
pr_debug("%s: clock %uHz busmode %u powermode %u cs %u Vdd %u "
|
||||
"width %u timing %u\n",
|
||||
mmc_hostname(host), ios->clock, ios->bus_mode,
|
||||
ios->power_mode, ios->chip_select, ios->vdd,
|
||||
ios->bus_width, ios->timing);
|
||||
|
||||
host->ops->set_ios(host, ios);
|
||||
}
|
||||
|
||||
/*
|
||||
* Control chip select pin on a host.
|
||||
*/
|
||||
void mmc_set_chip_select(struct mmc_host *host, int mode)
|
||||
{
|
||||
host->ios.chip_select = mode;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the host clock to the highest possible frequency that
|
||||
* is below "hz".
|
||||
*/
|
||||
void mmc_set_clock(struct mmc_host *host, unsigned int hz)
|
||||
{
|
||||
WARN_ON(hz < host->f_min);
|
||||
|
||||
if (hz > host->f_max)
|
||||
hz = host->f_max;
|
||||
|
||||
host->ios.clock = hz;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the bus mode (open drain/push-pull) of a host.
|
||||
*/
|
||||
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
|
||||
{
|
||||
host->ios.bus_mode = mode;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change data bus width of a host.
|
||||
*/
|
||||
void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
|
||||
{
|
||||
host->ios.bus_width = width;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mask off any voltages we don't support and select
|
||||
* the lowest voltage
|
||||
*/
|
||||
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int bit;
|
||||
|
||||
ocr &= host->ocr_avail;
|
||||
|
||||
bit = ffs(ocr);
|
||||
if (bit) {
|
||||
bit -= 1;
|
||||
|
||||
ocr &= 3 << bit;
|
||||
|
||||
host->ios.vdd = bit;
|
||||
mmc_set_ios(host);
|
||||
} else {
|
||||
pr_debug("%s: host doesn't support card's voltages\n",
|
||||
mmc_hostname(host));
|
||||
ocr = 0;
|
||||
}
|
||||
|
||||
return ocr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select timing parameters for host.
|
||||
*/
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing)
|
||||
{
|
||||
host->ios.timing = timing;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply power to the MMC stack. This is a two-stage process.
|
||||
* First, we enable power to the card without the clock running.
|
||||
* We then wait a bit for the power to stabilise. Finally,
|
||||
* enable the bus drivers and clock to the card.
|
||||
*
|
||||
* We must _NOT_ enable the clock prior to power stablising.
|
||||
*
|
||||
* If a host does all the power sequencing itself, ignore the
|
||||
* initial MMC_POWER_UP stage.
|
||||
*/
|
||||
static void mmc_power_up(struct mmc_host *host)
|
||||
{
|
||||
int bit = fls(host->ocr_avail) - 1;
|
||||
|
||||
host->ios.vdd = bit;
|
||||
if (mmc_host_is_spi(host)) {
|
||||
host->ios.chip_select = MMC_CS_HIGH;
|
||||
host->ios.bus_mode = MMC_BUSMODE_PUSHPULL;
|
||||
} else {
|
||||
host->ios.chip_select = MMC_CS_DONTCARE;
|
||||
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
|
||||
}
|
||||
host->ios.power_mode = MMC_POWER_UP;
|
||||
host->ios.bus_width = MMC_BUS_WIDTH_1;
|
||||
host->ios.timing = MMC_TIMING_LEGACY;
|
||||
mmc_set_ios(host);
|
||||
|
||||
/*
|
||||
* This delay should be sufficient to allow the power supply
|
||||
* to reach the minimum voltage.
|
||||
*/
|
||||
mmc_delay(2);
|
||||
|
||||
host->ios.clock = host->f_min;
|
||||
host->ios.power_mode = MMC_POWER_ON;
|
||||
mmc_set_ios(host);
|
||||
|
||||
/*
|
||||
* This delay must be at least 74 clock sizes, or 1 ms, or the
|
||||
* time required to reach a stable voltage.
|
||||
*/
|
||||
mmc_delay(2);
|
||||
}
|
||||
|
||||
static void mmc_power_off(struct mmc_host *host)
|
||||
{
|
||||
host->ios.clock = 0;
|
||||
host->ios.vdd = 0;
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
|
||||
host->ios.chip_select = MMC_CS_DONTCARE;
|
||||
}
|
||||
host->ios.power_mode = MMC_POWER_OFF;
|
||||
host->ios.bus_width = MMC_BUS_WIDTH_1;
|
||||
host->ios.timing = MMC_TIMING_LEGACY;
|
||||
mmc_set_ios(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup when the last reference to the bus operator is dropped.
|
||||
*/
|
||||
void __mmc_release_bus(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(host->bus_refs);
|
||||
BUG_ON(!host->bus_dead);
|
||||
|
||||
host->bus_ops = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase reference count of bus operator
|
||||
*/
|
||||
static inline void mmc_bus_get(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->bus_refs++;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrease reference count of bus operator and free it if
|
||||
* it is the last reference.
|
||||
*/
|
||||
static inline void mmc_bus_put(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->bus_refs--;
|
||||
if ((host->bus_refs == 0) && host->bus_ops)
|
||||
__mmc_release_bus(host);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a mmc bus handler to a host. Only one bus handler may control a
|
||||
* host at any given time.
|
||||
*/
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!ops);
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
BUG_ON(host->bus_ops);
|
||||
BUG_ON(host->bus_refs);
|
||||
|
||||
host->bus_ops = ops;
|
||||
host->bus_refs = 1;
|
||||
host->bus_dead = 0;
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the current bus handler from a host. Assumes that there are
|
||||
* no interesting cards left, so the bus is powered down.
|
||||
*/
|
||||
void mmc_detach_bus(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
WARN_ON(!host->bus_ops);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
host->bus_dead = 1;
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
mmc_power_off(host);
|
||||
|
||||
mmc_bus_put(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_detect_change - process change of state on a MMC socket
|
||||
* @host: host which changed state.
|
||||
* @delay: optional delay to wait before detection (jiffies)
|
||||
*
|
||||
* MMC drivers should call this when they detect a card has been
|
||||
* inserted or removed. The MMC layer will confirm that any
|
||||
* present card is still functional, and initialize any newly
|
||||
* inserted.
|
||||
*/
|
||||
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// Fix compile error.
|
||||
WARN_ON(!(!host->removed));
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
#endif
|
||||
|
||||
mmc_schedule_delayed_work(&host->detect, delay);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_detect_change);
|
||||
|
||||
|
||||
void mmc_rescan(struct work_struct *work)
|
||||
{
|
||||
struct mmc_host *host =
|
||||
container_of(work, struct mmc_host, detect.work);
|
||||
u32 ocr;
|
||||
int err;
|
||||
|
||||
mmc_bus_get(host);
|
||||
|
||||
if (host->bus_ops == NULL) {
|
||||
/*
|
||||
* Only we can add a new handler, so it's safe to
|
||||
* release the lock here.
|
||||
*/
|
||||
mmc_bus_put(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
mmc_power_up(host);
|
||||
mmc_go_idle(host);
|
||||
|
||||
mmc_send_if_cond(host, host->ocr_avail);
|
||||
|
||||
/*
|
||||
* First we search for SDIO...
|
||||
*/
|
||||
err = mmc_send_io_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_sdio(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* ...then normal SD...
|
||||
*/
|
||||
err = mmc_send_app_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_sd(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* ...and finally MMC.
|
||||
*/
|
||||
err = mmc_send_op_cond(host, 0, &ocr);
|
||||
if (!err) {
|
||||
if (mmc_attach_mmc(host, ocr))
|
||||
mmc_power_off(host);
|
||||
return;
|
||||
}
|
||||
|
||||
mmc_release_host(host);
|
||||
mmc_power_off(host);
|
||||
} else {
|
||||
if (host->bus_ops->detect && !host->bus_dead)
|
||||
host->bus_ops->detect(host);
|
||||
|
||||
mmc_bus_put(host);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_start_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_power_off(host);
|
||||
mmc_detect_change(host, 0);
|
||||
}
|
||||
|
||||
void mmc_stop_host(struct mmc_host *host)
|
||||
{
|
||||
#ifdef CONFIG_MMC_DEBUG
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->removed = 1;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
#endif
|
||||
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
if (host->bus_ops->remove)
|
||||
host->bus_ops->remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
BUG_ON(host->card);
|
||||
|
||||
mmc_power_off(host);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/**
|
||||
* mmc_suspend_host - suspend a host
|
||||
* @host: mmc host
|
||||
* @state: suspend mode (PM_SUSPEND_xxx)
|
||||
*/
|
||||
int mmc_suspend_host(struct mmc_host *host, pm_message_t state)
|
||||
{
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
if (host->bus_ops->suspend)
|
||||
host->bus_ops->suspend(host);
|
||||
if (!host->bus_ops->resume) {
|
||||
if (host->bus_ops->remove)
|
||||
host->bus_ops->remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
mmc_power_off(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_suspend_host);
|
||||
|
||||
/**
|
||||
* mmc_resume_host - resume a previously suspended host
|
||||
* @host: mmc host
|
||||
*/
|
||||
int mmc_resume_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
mmc_power_up(host);
|
||||
BUG_ON(!host->bus_ops->resume);
|
||||
host->bus_ops->resume(host);
|
||||
}
|
||||
mmc_bus_put(host);
|
||||
|
||||
/*
|
||||
* We add a slight delay here so that resume can progress
|
||||
* in parallel.
|
||||
*/
|
||||
mmc_detect_change(host, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_resume_host);
|
||||
|
||||
#endif
|
||||
|
||||
static int __init mmc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
workqueue = create_singlethread_workqueue("kmmcd");
|
||||
if (!workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mmc_register_bus();
|
||||
if (ret)
|
||||
goto destroy_workqueue;
|
||||
|
||||
ret = mmc_register_host_class();
|
||||
if (ret)
|
||||
goto unregister_bus;
|
||||
|
||||
ret = sdio_register_bus();
|
||||
if (ret)
|
||||
goto unregister_host_class;
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_host_class:
|
||||
mmc_unregister_host_class();
|
||||
unregister_bus:
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue:
|
||||
destroy_workqueue(workqueue);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mmc_exit(void)
|
||||
{
|
||||
sdio_unregister_bus();
|
||||
mmc_unregister_host_class();
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue(workqueue);
|
||||
}
|
||||
|
||||
subsys_initcall(mmc_init);
|
||||
module_exit(mmc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
54
drivers/mmc/core/core.h
Normal file
54
drivers/mmc/core/core.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 (*sysfs_add)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);
|
||||
void (*suspend)(struct mmc_host *);
|
||||
void (*resume)(struct mmc_host *);
|
||||
};
|
||||
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
|
||||
void mmc_detach_bus(struct mmc_host *host);
|
||||
|
||||
void mmc_set_chip_select(struct mmc_host *host, int mode);
|
||||
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
|
||||
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);
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
|
||||
|
||||
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);
|
||||
|
||||
extern int use_spi_crc;
|
||||
|
||||
#endif
|
||||
|
||||
167
drivers/mmc/core/host.c
Normal file
167
drivers/mmc/core/host.c
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/host.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC host class device management
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "host.h"
|
||||
|
||||
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||||
|
||||
static void mmc_host_classdev_release(struct device *dev)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
kfree(host);
|
||||
}
|
||||
|
||||
static struct class mmc_host_class = {
|
||||
.name = "mmc_host",
|
||||
.dev_release = mmc_host_classdev_release,
|
||||
};
|
||||
|
||||
int mmc_register_host_class(void)
|
||||
{
|
||||
return class_register(&mmc_host_class);
|
||||
}
|
||||
|
||||
void mmc_unregister_host_class(void)
|
||||
{
|
||||
class_unregister(&mmc_host_class);
|
||||
}
|
||||
|
||||
static DEFINE_IDR(mmc_host_idr);
|
||||
static DEFINE_SPINLOCK(mmc_host_lock);
|
||||
|
||||
/**
|
||||
* mmc_alloc_host - initialise the per-host structure.
|
||||
* @extra: sizeof private data structure
|
||||
* @dev: pointer to host device model structure
|
||||
*
|
||||
* Initialise the per-host structure.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
|
||||
if (!host)
|
||||
return NULL;
|
||||
|
||||
host->parent = dev;
|
||||
host->class_dev.parent = dev;
|
||||
host->class_dev.class = &mmc_host_class;
|
||||
device_initialize(&host->class_dev);
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
*/
|
||||
host->max_hw_segs = 1;
|
||||
host->max_phys_segs = 1;
|
||||
host->max_seg_size = PAGE_CACHE_SIZE;
|
||||
|
||||
host->max_req_size = PAGE_CACHE_SIZE;
|
||||
host->max_blk_size = 512;
|
||||
host->max_blk_count = PAGE_CACHE_SIZE / 512;
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_alloc_host);
|
||||
|
||||
/**
|
||||
* mmc_add_host - initialise host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* 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);
|
||||
|
||||
if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
err = idr_get_new(&mmc_host_idr, host, &host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snprintf(host->class_dev.bus_id, BUS_ID_SIZE,
|
||||
"mmc%d", host->index);
|
||||
|
||||
led_trigger_register_simple(host->class_dev.bus_id, &host->led);
|
||||
|
||||
err = device_add(&host->class_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mmc_start_host(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_add_host);
|
||||
|
||||
/**
|
||||
* mmc_remove_host - remove host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Unregister and remove all cards associated with this host,
|
||||
* and power down the MMC bus. No new requests will be issued
|
||||
* after this function has returned.
|
||||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
{
|
||||
mmc_stop_host(host);
|
||||
|
||||
device_del(&host->class_dev);
|
||||
|
||||
led_trigger_unregister_simple(host->led);
|
||||
|
||||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_remove_host);
|
||||
|
||||
/**
|
||||
* mmc_free_host - free the host structure
|
||||
* @host: mmc host
|
||||
*
|
||||
* Free the host once all references to it have been dropped.
|
||||
*/
|
||||
void mmc_free_host(struct mmc_host *host)
|
||||
{
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_free_host);
|
||||
|
||||
18
drivers/mmc/core/host.h
Normal file
18
drivers/mmc/core/host.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/host.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_HOST_H
|
||||
#define _MMC_CORE_HOST_H
|
||||
|
||||
int mmc_register_host_class(void);
|
||||
void mmc_unregister_host_class(void);
|
||||
|
||||
#endif
|
||||
|
||||
639
drivers/mmc/core/mmc.c
Normal file
639
drivers/mmc/core/mmc.c
Normal file
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/mmc.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
|
||||
* MMCv4 support Copyright (C) 2006 Philip Langdale, 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.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
10000, 100000, 1000000, 10000000,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const unsigned char tran_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_exp[] = {
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Given the decoded CSD structure, decode the raw CID to our CID structure.
|
||||
*/
|
||||
static int mmc_decode_cid(struct mmc_card *card)
|
||||
{
|
||||
u32 *resp = card->raw_cid;
|
||||
|
||||
/*
|
||||
* The selection of the format here is based upon published
|
||||
* specs from sandisk and from what people have reported.
|
||||
*/
|
||||
switch (card->csd.mmca_vsn) {
|
||||
case 0: /* MMC v1.0 - v1.2 */
|
||||
case 1: /* MMC v1.4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 104, 24);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 24);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
case 2: /* MMC v2.0 - v2.2 */
|
||||
case 3: /* MMC v3.1 - v3.3 */
|
||||
case 4: /* MMC v4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_ERR "%s: card has unknown MMCA version %d\n",
|
||||
mmc_hostname(card->host), card->csd.mmca_vsn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 128-bit response, decode to our card CSD structure.
|
||||
*/
|
||||
static int mmc_decode_csd(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_csd *csd = &card->csd;
|
||||
unsigned int e, m, csd_struct;
|
||||
u32 *resp = card->raw_csd;
|
||||
|
||||
/*
|
||||
* We only understand CSD structure v1.1 and v1.2.
|
||||
* v1.2 has extra information in bits 15, 11 and 10.
|
||||
*/
|
||||
csd_struct = UNSTUFF_BITS(resp, 126, 2);
|
||||
if (csd_struct != 1 && csd_struct != 2) {
|
||||
printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
|
||||
mmc_hostname(card->host), csd_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4);
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
|
||||
csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and decode extended CSD.
|
||||
*/
|
||||
static int mmc_read_ext_csd(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *ext_csd;
|
||||
unsigned int ext_csd_struct;
|
||||
|
||||
BUG_ON(!card);
|
||||
|
||||
if (card->csd.mmca_vsn < CSD_SPEC_VER_4)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* As the ext_csd is so large and mostly unused, we don't store the
|
||||
* raw block in mmc_card.
|
||||
*/
|
||||
ext_csd = kmalloc(512, GFP_KERNEL);
|
||||
if (!ext_csd) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer to "
|
||||
"receive the ext_csd.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_send_ext_csd(card, ext_csd);
|
||||
if (err) {
|
||||
/*
|
||||
* We all hosts that cannot perform the command
|
||||
* to fail more gracefully
|
||||
*/
|
||||
if (err != -EINVAL)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* High capacity cards should have this "magic" size
|
||||
* stored in their CSD.
|
||||
*/
|
||||
if (card->csd.capacity == (4096 * 512)) {
|
||||
printk(KERN_ERR "%s: unable to read EXT_CSD "
|
||||
"on a possible high capacity card. "
|
||||
"Card will be ignored.\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
printk(KERN_WARNING "%s: unable to read "
|
||||
"EXT_CSD, performance might "
|
||||
"suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
err = 0;
|
||||
}
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
ext_csd_struct = ext_csd[EXT_CSD_REV];
|
||||
if (ext_csd_struct > 2) {
|
||||
printk(KERN_ERR "%s: unrecognised EXT_CSD structure "
|
||||
"version %d\n", mmc_hostname(card->host),
|
||||
ext_csd_struct);
|
||||
//err = -EINVAL; //A3U, for moviNAND
|
||||
//goto out;
|
||||
}
|
||||
|
||||
if (ext_csd_struct >= 2) {
|
||||
card->ext_csd.sectors =
|
||||
ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
|
||||
ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
|
||||
if (card->ext_csd.sectors)
|
||||
mmc_card_set_blockaddr(card);
|
||||
}
|
||||
|
||||
switch (ext_csd[EXT_CSD_CARD_TYPE]) {
|
||||
case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26:
|
||||
card->ext_csd.hs_max_dtr = 52000000;
|
||||
break;
|
||||
case EXT_CSD_CARD_TYPE_26:
|
||||
card->ext_csd.hs_max_dtr = 26000000;
|
||||
break;
|
||||
default:
|
||||
/* MMC v4 spec says this cannot happen */
|
||||
printk(KERN_WARNING "%s: card is mmc v4 but doesn't "
|
||||
"support any high-speed modes.\n",
|
||||
mmc_hostname(card->host));
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(ext_csd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
struct mmc_card *oldcard)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
int err;
|
||||
u32 cid[4];
|
||||
unsigned int max_dtr;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
/*
|
||||
* Since we're changing the OCR value, we seem to
|
||||
* need to tell some cards to go back to the idle
|
||||
* state. We wait 1ms to give cards time to
|
||||
* respond.
|
||||
*/
|
||||
mmc_go_idle(host);
|
||||
|
||||
/* The extra bit indicates that we support high capacity */
|
||||
err = mmc_send_op_cond(host, ocr | (1 << 30), NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch CID from card.
|
||||
*/
|
||||
if (mmc_host_is_spi(host))
|
||||
err = mmc_send_cid(host, cid);
|
||||
else
|
||||
err = mmc_all_send_cid(host, cid);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
if (oldcard) {
|
||||
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
|
||||
err = -ENOENT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
card = oldcard;
|
||||
} else {
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_MMC;
|
||||
card->rca = 1;
|
||||
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
|
||||
}
|
||||
|
||||
/*
|
||||
* For native busses: set card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_set_relative_addr(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch CSD from card.
|
||||
*/
|
||||
err = mmc_send_csd(card, card->raw_csd);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
err = mmc_decode_cid(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch and process extended CSD.
|
||||
*/
|
||||
err = mmc_read_ext_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Activate high speed (if supported)
|
||||
*/
|
||||
if ((card->ext_csd.hs_max_dtr != 0) &&
|
||||
(host->caps & MMC_CAP_MMC_HIGHSPEED)) {
|
||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||
EXT_CSD_HS_TIMING, 1);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_card_set_highspeed(card);
|
||||
|
||||
mmc_set_timing(card->host, MMC_TIMING_MMC_HS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute bus speed.
|
||||
*/
|
||||
max_dtr = (unsigned int)-1;
|
||||
|
||||
if (mmc_card_highspeed(card)) {
|
||||
if (max_dtr > card->ext_csd.hs_max_dtr)
|
||||
max_dtr = card->ext_csd.hs_max_dtr;
|
||||
} else if (max_dtr > card->csd.max_dtr) {
|
||||
max_dtr = card->csd.max_dtr;
|
||||
}
|
||||
|
||||
mmc_set_clock(host, max_dtr);
|
||||
|
||||
/*
|
||||
* Activate wide bus (if supported).
|
||||
*/
|
||||
if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
|
||||
(host->caps & MMC_CAP_4_BIT_DATA)) {
|
||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||
EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
|
||||
}
|
||||
|
||||
if (!oldcard)
|
||||
host->card = card;
|
||||
|
||||
return 0;
|
||||
|
||||
free_card:
|
||||
if (!oldcard)
|
||||
mmc_remove_card(card);
|
||||
err:
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_remove(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_send_status(host->card, NULL);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
* Suspend callback from host.
|
||||
*/
|
||||
static void mmc_suspend(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
if (!mmc_host_is_spi(host))
|
||||
mmc_deselect_cards(host);
|
||||
host->card->state &= ~MMC_STATE_HIGHSPEED;
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume callback from host.
|
||||
*
|
||||
* This function tries to determine if the same card is still present
|
||||
* and, if so, restore all state to it.
|
||||
*/
|
||||
static void mmc_resume(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
err = mmc_init_card(host, host->ocr, host->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define mmc_suspend NULL
|
||||
#define mmc_resume NULL
|
||||
|
||||
#endif
|
||||
|
||||
static const struct mmc_bus_ops mmc_ops = {
|
||||
.remove = mmc_remove,
|
||||
.detect = mmc_detect,
|
||||
.sysfs_add = mmc_sysfs_add,
|
||||
.sysfs_remove = mmc_sysfs_remove,
|
||||
.suspend = mmc_suspend,
|
||||
.resume = mmc_resume,
|
||||
};
|
||||
|
||||
/*
|
||||
* Starting point for MMC card init.
|
||||
*/
|
||||
int mmc_attach_mmc(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_ops);
|
||||
|
||||
/*
|
||||
* We need to get OCR a different way for SPI.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_read_ocr(host, 1, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage of the card?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
err = mmc_init_card(host, host->ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_card;
|
||||
|
||||
return 0;
|
||||
|
||||
remove_card:
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_claim_host(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising MMC card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
397
drivers/mmc/core/mmc_ops.c
Normal file
397
drivers/mmc/core/mmc_ops.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 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/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"
|
||||
|
||||
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int mmc_go_idle(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
/*
|
||||
* 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 accomodate 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);
|
||||
}
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cid);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cxd);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
|
||||
u32 opcode, void *buf, unsigned len)
|
||||
{
|
||||
struct mmc_request mrq;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
void *data_buf;
|
||||
|
||||
/* 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 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
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 (card)
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
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;
|
||||
|
||||
if (!mmc_host_is_spi(card->host))
|
||||
return mmc_send_cxd_native(card->host, card->rca << 16,
|
||||
csd, MMC_SEND_CSD);
|
||||
|
||||
ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
csd[i] = be32_to_cpu(csd[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_cid(struct mmc_host *host, u32 *cid)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
cid[i] = be32_to_cpu(cid[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
int err;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
cmd.opcode = MMC_SWITCH;
|
||||
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
||||
(index << 16) |
|
||||
(value << 8) |
|
||||
set;
|
||||
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
30
drivers/mmc/core/mmc_ops.h
Normal file
30
drivers/mmc/core/mmc_ops.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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_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_switch(struct mmc_card *card, u8 set, u8 index, u8 value);
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
709
drivers/mmc/core/sd.c
Normal file
709
drivers/mmc/core/sd.c
Normal file
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sd.c
|
||||
*
|
||||
* Copyright (C) 2003-2004 Russell King, All Rights Reserved.
|
||||
* SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
|
||||
* Copyright (C) 2005-2007 Pierre Ossman, 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.
|
||||
*/
|
||||
|
||||
#include <linux/err.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 "sysfs.h"
|
||||
#include "bus.h"
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
|
||||
static const unsigned int tran_exp[] = {
|
||||
10000, 100000, 1000000, 10000000,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const unsigned char tran_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_exp[] = {
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
|
||||
};
|
||||
|
||||
static const unsigned int tacc_mant[] = {
|
||||
0, 10, 12, 13, 15, 20, 25, 30,
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Given the decoded CSD structure, decode the raw CID to our CID structure.
|
||||
*/
|
||||
static void mmc_decode_cid(struct mmc_card *card)
|
||||
{
|
||||
u32 *resp = card->raw_cid;
|
||||
|
||||
memset(&card->cid, 0, sizeof(struct mmc_cid));
|
||||
|
||||
/*
|
||||
* SD doesn't currently have a version field so we will
|
||||
* have to assume we can parse this.
|
||||
*/
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 24, 32);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 12, 8);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 8, 4);
|
||||
|
||||
card->cid.year += 2000; /* SD cards year offset */
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 128-bit response, decode to our card CSD structure.
|
||||
*/
|
||||
static int mmc_decode_csd(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_csd *csd = &card->csd;
|
||||
unsigned int e, m, csd_struct;
|
||||
u32 *resp = card->raw_csd;
|
||||
|
||||
csd_struct = UNSTUFF_BITS(resp, 126, 2);
|
||||
|
||||
switch (csd_struct) {
|
||||
case 0:
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
|
||||
csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
break;
|
||||
case 1:
|
||||
/*
|
||||
* This is a block-addressed SDHC card. Most
|
||||
* interesting fields are unused and have fixed
|
||||
* values. To avoid getting tripped by buggy cards,
|
||||
* we assume those fixed values ourselves.
|
||||
*/
|
||||
mmc_card_set_blockaddr(card);
|
||||
|
||||
csd->tacc_ns = 0; /* Unused */
|
||||
csd->tacc_clks = 0; /* Unused */
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
|
||||
m = UNSTUFF_BITS(resp, 48, 22);
|
||||
csd->capacity = (1 + m) << 10;
|
||||
|
||||
csd->read_blkbits = 9;
|
||||
csd->read_partial = 0;
|
||||
csd->write_misalign = 0;
|
||||
csd->read_misalign = 0;
|
||||
csd->r2w_factor = 4; /* Unused */
|
||||
csd->write_blkbits = 9;
|
||||
csd->write_partial = 0;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
|
||||
mmc_hostname(card->host), csd_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 64-bit response, decode to our card SCR structure.
|
||||
*/
|
||||
static int mmc_decode_scr(struct mmc_card *card)
|
||||
{
|
||||
struct sd_scr *scr = &card->scr;
|
||||
unsigned int scr_struct;
|
||||
u32 resp[4];
|
||||
|
||||
resp[3] = card->raw_scr[1];
|
||||
resp[2] = card->raw_scr[0];
|
||||
|
||||
scr_struct = UNSTUFF_BITS(resp, 60, 4);
|
||||
if (scr_struct != 0) {
|
||||
printk(KERN_ERR "%s: unrecognised SCR structure version %d\n",
|
||||
mmc_hostname(card->host), scr_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4);
|
||||
scr->bus_widths = UNSTUFF_BITS(resp, 48, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetches and decodes switch information
|
||||
*/
|
||||
static int mmc_read_switch(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *status;
|
||||
|
||||
if (card->scr.sda_vsn < SCR_SPEC_VER_1)
|
||||
return 0;
|
||||
|
||||
if (!(card->csd.cmdclass & CCC_SWITCH)) {
|
||||
printk(KERN_WARNING "%s: card lacks mandatory switch "
|
||||
"function, performance might suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = -EIO;
|
||||
|
||||
status = kmalloc(64, GFP_KERNEL);
|
||||
if (!status) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer for "
|
||||
"switch capabilities.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_sd_switch(card, 0, 0, 1, status);
|
||||
if (err) {
|
||||
/*
|
||||
* We all hosts that cannot perform the command
|
||||
* to fail more gracefully
|
||||
*/
|
||||
if (err != -EINVAL)
|
||||
goto out;
|
||||
|
||||
printk(KERN_WARNING "%s: problem reading switch "
|
||||
"capabilities, performance might suffer.\n",
|
||||
mmc_hostname(card->host));
|
||||
err = 0;
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status[13] & 0x02)
|
||||
card->sw_caps.hs_max_dtr = 50000000;
|
||||
|
||||
out:
|
||||
kfree(status);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if the card supports high-speed mode and, if so, switch to it.
|
||||
*/
|
||||
static int mmc_switch_hs(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
u8 *status;
|
||||
|
||||
if (card->scr.sda_vsn < SCR_SPEC_VER_1)
|
||||
return 0;
|
||||
|
||||
if (!(card->csd.cmdclass & CCC_SWITCH))
|
||||
return 0;
|
||||
|
||||
if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
|
||||
return 0;
|
||||
|
||||
if (card->sw_caps.hs_max_dtr == 0)
|
||||
return 0;
|
||||
|
||||
err = -EIO;
|
||||
|
||||
status = kmalloc(64, GFP_KERNEL);
|
||||
if (!status) {
|
||||
printk(KERN_ERR "%s: could not allocate a buffer for "
|
||||
"switch capabilities.\n", mmc_hostname(card->host));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mmc_sd_switch(card, 1, 0, 1, status);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if ((status[16] & 0xF) != 1) {
|
||||
printk(KERN_WARNING "%s: Problem switching card "
|
||||
"into high-speed mode!\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
mmc_card_set_highspeed(card);
|
||||
mmc_set_timing(card->host, MMC_TIMING_SD_HS);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(status);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the detection and initialisation of a card.
|
||||
*
|
||||
* In the case of a resume, "curcard" will contain the card
|
||||
* we're trying to reinitialise.
|
||||
*/
|
||||
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
|
||||
struct mmc_card *oldcard)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
int err;
|
||||
u32 cid[4];
|
||||
unsigned int max_dtr;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
/*
|
||||
* Since we're changing the OCR value, we seem to
|
||||
* need to tell some cards to go back to the idle
|
||||
* state. We wait 1ms to give cards time to
|
||||
* respond.
|
||||
*/
|
||||
mmc_go_idle(host);
|
||||
|
||||
/*
|
||||
* If SD_SEND_IF_COND indicates an SD 2.0
|
||||
* compliant card and we should set bit 30
|
||||
* of the ocr to indicate that we can handle
|
||||
* block-addressed SDHC cards.
|
||||
*/
|
||||
err = mmc_send_if_cond(host, ocr);
|
||||
if (!err)
|
||||
ocr |= 1 << 30;
|
||||
|
||||
err = mmc_send_app_op_cond(host, ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch CID from card.
|
||||
*/
|
||||
if (mmc_host_is_spi(host))
|
||||
err = mmc_send_cid(host, cid);
|
||||
else
|
||||
err = mmc_all_send_cid(host, cid);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
if (oldcard) {
|
||||
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
|
||||
err = -ENOENT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
card = oldcard;
|
||||
} else {
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_SD;
|
||||
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
|
||||
}
|
||||
|
||||
/*
|
||||
* For native busses: get card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_send_relative_addr(host, &card->rca);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch CSD from card.
|
||||
*/
|
||||
err = mmc_send_csd(card, card->raw_csd);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_csd(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_decode_cid(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
if (!oldcard) {
|
||||
/*
|
||||
* Fetch SCR from card.
|
||||
*/
|
||||
err = mmc_app_send_scr(card, card->raw_scr);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
err = mmc_decode_scr(card);
|
||||
if (err < 0)
|
||||
goto free_card;
|
||||
|
||||
/*
|
||||
* Fetch switch information from card.
|
||||
*/
|
||||
err = mmc_read_switch(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to change to high-speed (if supported)
|
||||
*/
|
||||
err = mmc_switch_hs(card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
/*
|
||||
* Compute bus speed.
|
||||
*/
|
||||
max_dtr = (unsigned int)-1;
|
||||
|
||||
if (mmc_card_highspeed(card)) {
|
||||
if (max_dtr > card->sw_caps.hs_max_dtr)
|
||||
max_dtr = card->sw_caps.hs_max_dtr;
|
||||
} else if (max_dtr > card->csd.max_dtr) {
|
||||
max_dtr = card->csd.max_dtr;
|
||||
}
|
||||
|
||||
mmc_set_clock(host, max_dtr);
|
||||
|
||||
/*
|
||||
* Switch to wider bus (if supported).
|
||||
*/
|
||||
if ((host->caps & MMC_CAP_4_BIT_DATA) &&
|
||||
(card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
|
||||
err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if read-only switch is active.
|
||||
*/
|
||||
if (!oldcard) {
|
||||
if (!host->ops->get_ro) {
|
||||
printk(KERN_WARNING "%s: host does not "
|
||||
"support reading read-only "
|
||||
"switch. assuming write-enable.\n",
|
||||
mmc_hostname(host));
|
||||
} else {
|
||||
if (host->ops->get_ro(host))
|
||||
mmc_card_set_readonly(card);
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldcard)
|
||||
host->card = card;
|
||||
|
||||
return 0;
|
||||
|
||||
free_card:
|
||||
if (!oldcard)
|
||||
mmc_remove_card(card);
|
||||
err:
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_sd_remove(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_sd_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_send_status(host->card, NULL);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sd_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
MMC_ATTR_FN(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
|
||||
card->raw_cid[2], card->raw_cid[3]);
|
||||
MMC_ATTR_FN(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
card->raw_csd[2], card->raw_csd[3]);
|
||||
MMC_ATTR_FN(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]);
|
||||
MMC_ATTR_FN(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_ATTR_FN(fwrev, "0x%x\n", card->cid.fwrev);
|
||||
MMC_ATTR_FN(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_ATTR_FN(manfid, "0x%06x\n", card->cid.manfid);
|
||||
MMC_ATTR_FN(name, "%s\n", card->cid.prod_name);
|
||||
MMC_ATTR_FN(oemid, "0x%04x\n", card->cid.oemid);
|
||||
MMC_ATTR_FN(serial, "0x%08x\n", card->cid.serial);
|
||||
|
||||
static struct device_attribute mmc_sd_dev_attrs[] = {
|
||||
MMC_ATTR_RO(cid),
|
||||
MMC_ATTR_RO(csd),
|
||||
MMC_ATTR_RO(scr),
|
||||
MMC_ATTR_RO(date),
|
||||
MMC_ATTR_RO(fwrev),
|
||||
MMC_ATTR_RO(hwrev),
|
||||
MMC_ATTR_RO(manfid),
|
||||
MMC_ATTR_RO(name),
|
||||
MMC_ATTR_RO(oemid),
|
||||
MMC_ATTR_RO(serial),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds sysfs entries as relevant.
|
||||
*/
|
||||
static int mmc_sd_sysfs_add(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mmc_add_attrs(card, mmc_sd_dev_attrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the sysfs entries added by mmc_sysfs_add().
|
||||
*/
|
||||
static void mmc_sd_sysfs_remove(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
mmc_remove_attrs(card, mmc_sd_dev_attrs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
||||
|
||||
/*
|
||||
* Suspend callback from host.
|
||||
*/
|
||||
static void mmc_sd_suspend(struct mmc_host *host)
|
||||
{
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
if (!mmc_host_is_spi(host))
|
||||
mmc_deselect_cards(host);
|
||||
host->card->state &= ~MMC_STATE_HIGHSPEED;
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume callback from host.
|
||||
*
|
||||
* This function tries to determine if the same card is still present
|
||||
* and, if so, restore all state to it.
|
||||
*/
|
||||
static void mmc_sd_resume(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
err = mmc_sd_init_card(host, host->ocr, host->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sd_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define mmc_sd_suspend NULL
|
||||
#define mmc_sd_resume NULL
|
||||
|
||||
#endif
|
||||
|
||||
static const struct mmc_bus_ops mmc_sd_ops = {
|
||||
.remove = mmc_sd_remove,
|
||||
.detect = mmc_sd_detect,
|
||||
.sysfs_add = mmc_sd_sysfs_add,
|
||||
.sysfs_remove = mmc_sd_sysfs_remove,
|
||||
.suspend = mmc_sd_suspend,
|
||||
.resume = mmc_sd_resume,
|
||||
};
|
||||
|
||||
/*
|
||||
* Starting point for SD card init.
|
||||
*/
|
||||
int mmc_attach_sd(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_sd_ops);
|
||||
|
||||
/*
|
||||
* We need to get OCR a different way for SPI.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
mmc_go_idle(host);
|
||||
|
||||
err = mmc_spi_read_ocr(host, 0, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
if (ocr & MMC_VDD_165_195) {
|
||||
printk(KERN_WARNING "%s: SD card claims to support the "
|
||||
"incompletely defined 'low voltage range'. This "
|
||||
"will be ignored.\n", mmc_hostname(host));
|
||||
ocr &= ~MMC_VDD_165_195;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage(s) of the card(s)?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
err = mmc_sd_init_card(host, host->ocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_card;
|
||||
|
||||
return 0;
|
||||
|
||||
remove_card:
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
mmc_claim_host(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising SD card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
350
drivers/mmc/core/sd_ops.c
Normal file
350
drivers/mmc/core/sd_ops.c
Normal file
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* 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/types.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"
|
||||
|
||||
static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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++) {
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
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;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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 (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;
|
||||
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;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!rca);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
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;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
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, scr, 8);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
/* NOTE: caller guarantees resp is heap-allocated */
|
||||
|
||||
mode = !!mode;
|
||||
value &= 0xF;
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
24
drivers/mmc/core/sd_ops.h
Normal file
24
drivers/mmc/core/sd_ops.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
#endif
|
||||
|
||||
395
drivers/mmc/core/sdio.c
Normal file
395
drivers/mmc/core/sdio.c
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* linux/drivers/mmc/sdio.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/err.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "sdio_bus.h"
|
||||
#include "mmc_ops.h"
|
||||
#include "sd_ops.h"
|
||||
#include "sdio_ops.h"
|
||||
#include "sdio_cis.h"
|
||||
|
||||
static int sdio_read_fbr(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char data;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
data &= 0x0f;
|
||||
|
||||
if (data == 0x0f) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
func->class = data;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_init_func(struct mmc_card *card, unsigned int fn)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func;
|
||||
|
||||
BUG_ON(fn > SDIO_MAX_FUNCS);
|
||||
|
||||
func = sdio_alloc_func(card);
|
||||
if (IS_ERR(func))
|
||||
return PTR_ERR(func);
|
||||
|
||||
func->num = fn;
|
||||
|
||||
ret = sdio_read_fbr(func);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = sdio_read_func_cis(func);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
card->sdio_func[fn - 1] = func;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/*
|
||||
* It is okay to remove the function here even though we hold
|
||||
* the host lock as we haven't registered the device yet.
|
||||
*/
|
||||
sdio_remove_func(func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_read_cccr(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
int cccr_vsn;
|
||||
unsigned char data;
|
||||
|
||||
memset(&card->cccr, 0, sizeof(struct sdio_cccr));
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
cccr_vsn = data & 0x0f;
|
||||
|
||||
if (cccr_vsn > SDIO_CCCR_REV_1_20) {
|
||||
printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n",
|
||||
mmc_hostname(card->host), cccr_vsn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
card->cccr.sdio_vsn = (data & 0xf0) >> 4;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_CCCR_CAP_SMB)
|
||||
card->cccr.multi_block = 1;
|
||||
if (data & SDIO_CCCR_CAP_LSC)
|
||||
card->cccr.low_speed = 1;
|
||||
if (data & SDIO_CCCR_CAP_4BLS)
|
||||
card->cccr.wide_bus = 1;
|
||||
|
||||
if (cccr_vsn >= SDIO_CCCR_REV_1_10) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_POWER_SMPC)
|
||||
card->cccr.high_power = 1;
|
||||
}
|
||||
|
||||
if (cccr_vsn >= SDIO_CCCR_REV_1_20) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (data & SDIO_SPEED_SHS)
|
||||
card->cccr.high_speed = 1;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_enable_wide(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
u8 ctrl;
|
||||
|
||||
if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
|
||||
return 0;
|
||||
|
||||
if (card->cccr.low_speed && !card->cccr.wide_bus)
|
||||
return 0;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctrl |= SDIO_BUS_WIDTH_4BIT;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Host is being removed. Free up the current card.
|
||||
*/
|
||||
static void mmc_sdio_remove(struct mmc_host *host)
|
||||
{
|
||||
int i;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
for (i = 0;i < host->card->sdio_funcs;i++) {
|
||||
if (host->card->sdio_func[i]) {
|
||||
sdio_remove_func(host->card->sdio_func[i]);
|
||||
host->card->sdio_func[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mmc_remove_card(host->card);
|
||||
host->card = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card detection callback from host.
|
||||
*/
|
||||
static void mmc_sdio_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
|
||||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
err = mmc_select_card(host->card);
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
if (err) {
|
||||
mmc_sdio_remove(host);
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct mmc_bus_ops mmc_sdio_ops = {
|
||||
.remove = mmc_sdio_remove,
|
||||
.detect = mmc_sdio_detect,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Starting point for SDIO card init.
|
||||
*/
|
||||
int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
int err;
|
||||
int i, funcs;
|
||||
struct mmc_card *card;
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
mmc_attach_bus(host, &mmc_sdio_ops);
|
||||
|
||||
/*
|
||||
* Sanity check the voltages that the card claims to
|
||||
* support.
|
||||
*/
|
||||
if (ocr & 0x7F) {
|
||||
printk(KERN_WARNING "%s: card claims to support voltages "
|
||||
"below the defined range. These will be ignored.\n",
|
||||
mmc_hostname(host));
|
||||
ocr &= ~0x7F;
|
||||
}
|
||||
|
||||
if (ocr & MMC_VDD_165_195) {
|
||||
printk(KERN_WARNING "%s: SDIO card claims to support the "
|
||||
"incompletely defined 'low voltage range'. This "
|
||||
"will be ignored.\n", mmc_hostname(host));
|
||||
ocr &= ~MMC_VDD_165_195;
|
||||
}
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
|
||||
/*
|
||||
* Can we support the voltage(s) of the card(s)?
|
||||
*/
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inform the card of the voltage
|
||||
*/
|
||||
err = mmc_send_io_op_cond(host, host->ocr, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For SPI, enable CRC as appropriate.
|
||||
*/
|
||||
if (mmc_host_is_spi(host)) {
|
||||
err = mmc_spi_set_crc(host, use_spi_crc);
|
||||
if (err)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The number of functions on the card is encoded inside
|
||||
* the ocr.
|
||||
*/
|
||||
funcs = (ocr & 0x70000000) >> 28;
|
||||
|
||||
/*
|
||||
* Allocate card structure.
|
||||
*/
|
||||
card = mmc_alloc_card(host);
|
||||
if (IS_ERR(card)) {
|
||||
err = PTR_ERR(card);
|
||||
goto err;
|
||||
}
|
||||
|
||||
card->type = MMC_TYPE_SDIO;
|
||||
card->sdio_funcs = funcs;
|
||||
|
||||
host->card = card;
|
||||
|
||||
/*
|
||||
* For native busses: set card RCA and quit open drain mode.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_send_relative_addr(host, &card->rca);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select card, as all following commands rely on that.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
err = mmc_select_card(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the common registers.
|
||||
*/
|
||||
err = sdio_read_cccr(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* Read the common CIS tuples.
|
||||
*/
|
||||
err = sdio_read_common_cis(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* No support for high-speed yet, so just set
|
||||
* the card's maximum speed.
|
||||
*/
|
||||
mmc_set_clock(host, card->cis.max_dtr);
|
||||
|
||||
/*
|
||||
* Switch to wider bus (if supported).
|
||||
*/
|
||||
err = sdio_enable_wide(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
|
||||
/*
|
||||
* Initialize (but don't add) all present functions.
|
||||
*/
|
||||
for (i = 0;i < funcs;i++) {
|
||||
err = sdio_init_func(host->card, i + 1);
|
||||
if (err)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
mmc_release_host(host);
|
||||
|
||||
/*
|
||||
* First add the card to the driver model...
|
||||
*/
|
||||
err = mmc_add_card(host->card);
|
||||
if (err)
|
||||
goto remove_added;
|
||||
|
||||
/*
|
||||
* ...then the SDIO functions.
|
||||
*/
|
||||
for (i = 0;i < funcs;i++) {
|
||||
err = sdio_add_func(host->card->sdio_func[i]);
|
||||
if (err)
|
||||
goto remove_added;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
remove_added:
|
||||
/* Remove without lock if the device has been added. */
|
||||
mmc_sdio_remove(host);
|
||||
mmc_claim_host(host);
|
||||
remove:
|
||||
/* And with lock if it hasn't been added. */
|
||||
if (host->card)
|
||||
mmc_sdio_remove(host);
|
||||
err:
|
||||
mmc_detach_bus(host);
|
||||
mmc_release_host(host);
|
||||
|
||||
printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",
|
||||
mmc_hostname(host), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
271
drivers/mmc/core/sdio_bus.c
Normal file
271
drivers/mmc/core/sdio_bus.c
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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/mmc/card.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_cis.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev)
|
||||
#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)
|
||||
|
||||
/* 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); \
|
||||
}
|
||||
|
||||
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 struct device_attribute sdio_dev_attrs[] = {
|
||||
__ATTR_RO(class),
|
||||
__ATTR_RO(vendor),
|
||||
__ATTR_RO(device),
|
||||
__ATTR_RO(modalias),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 { */
|
||||
// To fit 2.6.21.
|
||||
static int
|
||||
sdio_bus_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
int i = 0;
|
||||
int length = 0;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"SDIO_CLASS=%02X", func->class))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"SDIO_ID=%04X:%04X", func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"MODALIAS=sdio:c%02Xv%04Xd%04X",
|
||||
func->class, func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Qsida, Daniel Lee, 2009/07/21, e600 } */
|
||||
|
||||
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;
|
||||
|
||||
/* 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)
|
||||
return ret;
|
||||
|
||||
return drv->probe(func, id);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
drv->remove(func);
|
||||
|
||||
if (func->irq_handler) {
|
||||
printk(KERN_WARNING "WARNING: driver %s did not remove "
|
||||
"its interrupt handler!\n", drv->name);
|
||||
sdio_claim_host(func);
|
||||
sdio_release_irq(func);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bus_type sdio_bus_type = {
|
||||
.name = "sdio",
|
||||
.dev_attrs = sdio_dev_attrs,
|
||||
.match = sdio_bus_match,
|
||||
.uevent = sdio_bus_uevent,
|
||||
.probe = sdio_bus_probe,
|
||||
.remove = sdio_bus_remove,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
sdio_free_func_cis(func);
|
||||
|
||||
if (func->info)
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new SDIO function with the driver model.
|
||||
*/
|
||||
int sdio_add_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
snprintf(func->dev.bus_id, sizeof(func->dev.bus_id),
|
||||
"%s:%d", mmc_card_id(func->card), func->num);
|
||||
|
||||
ret = device_add(&func->dev);
|
||||
if (ret == 0)
|
||||
sdio_func_set_present(func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a SDIO function with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void sdio_remove_func(struct sdio_func *func)
|
||||
{
|
||||
if (sdio_func_present(func))
|
||||
device_del(&func->dev);
|
||||
|
||||
put_device(&func->dev);
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/sdio_bus.h
Normal file
22
drivers/mmc/core/sdio_bus.h
Normal 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
|
||||
|
||||
355
drivers/mmc/core/sdio_cis.c
Normal file
355
drivers/mmc/core/sdio_cis.c
Normal file
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* 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/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;
|
||||
|
||||
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 (buf[i-1] != '\0') {
|
||||
printk(KERN_WARNING "SDIO: ignoring broken CISTPL_VERS_1\n");
|
||||
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 };
|
||||
|
||||
static int cistpl_funce_common(struct mmc_card *card,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
if (size < 0x04 || buf[0] != 0)
|
||||
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];
|
||||
|
||||
/* Qisda, Daniel Lee, 2009/07/22, e600 { */
|
||||
// MT5921 SDIO host interface protocol timing max value is 25MHz.
|
||||
if (card->cis.max_dtr >= 25000000 && card->host->index == 1) {
|
||||
printk(KERN_INFO "%s: card->cis.max_dtr(%u), reduce to 25MHz \n",
|
||||
mmc_hostname(card->host), card->cis.max_dtr);
|
||||
card->cis.max_dtr = 25000000;
|
||||
}
|
||||
/* Qisda, Daniel Lee, 2009/07/22, e600 } */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_funce_func(struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned vsn;
|
||||
unsigned min_size;
|
||||
|
||||
vsn = func->card->cccr.sdio_vsn;
|
||||
min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
|
||||
|
||||
if (size < min_size || buf[0] != 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* TPLFE_MAX_BLK_SIZE */
|
||||
func->max_blksize = buf[12] | (buf[13] << 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* There should be two versions of the CISTPL_FUNCE tuple,
|
||||
* one for the common CIS (function 0) and a version used by
|
||||
* the individual function's CIS (1-7). Yet, the later has a
|
||||
* different length depending on the SDIO spec version.
|
||||
*/
|
||||
if (func)
|
||||
ret = cistpl_funce_func(func, buf, size);
|
||||
else
|
||||
ret = cistpl_funce_common(card, buf, size);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u "
|
||||
"type %u\n", mmc_hostname(card->host), size, buf[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 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 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;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link);
|
||||
if (ret)
|
||||
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;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++)
|
||||
if (cis_tpl_list[i].code == tpl_code)
|
||||
break;
|
||||
if (i >= ARRAY_SIZE(cis_tpl_list)) {
|
||||
/* this tuple is unknown to the core */
|
||||
this->next = NULL;
|
||||
this->code = tpl_code;
|
||||
this->size = tpl_link;
|
||||
*prev = this;
|
||||
prev = &this->next;
|
||||
printk(KERN_DEBUG
|
||||
"%s: queuing CIS tuple 0x%02x length %u\n",
|
||||
mmc_hostname(card->host), tpl_code, tpl_link);
|
||||
} else {
|
||||
const struct cis_tpl *tpl = cis_tpl_list + i;
|
||||
if (tpl_link < tpl->min_size) {
|
||||
printk(KERN_ERR
|
||||
"%s: bad CIS tuple 0x%02x (length = %u, expected >= %u)\n",
|
||||
mmc_hostname(card->host),
|
||||
tpl_code, tpl_link, tpl->min_size);
|
||||
ret = -EINVAL;
|
||||
} else if (tpl->parse) {
|
||||
ret = tpl->parse(card, func,
|
||||
this->data, tpl_link);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
23
drivers/mmc/core/sdio_cis.h
Normal file
23
drivers/mmc/core/sdio_cis.h
Normal 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
|
||||
548
drivers/mmc/core/sdio_io.c
Normal file
548
drivers/mmc/core/sdio_io.c
Normal file
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_io.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.
|
||||
*/
|
||||
|
||||
#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, ®);
|
||||
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;
|
||||
|
||||
/*
|
||||
* FIXME: This should timeout based on information in the CIS,
|
||||
* but we don't have card to parse that yet.
|
||||
*/
|
||||
timeout = jiffies + HZ;
|
||||
|
||||
while (1) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®);
|
||||
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, ®);
|
||||
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(min(
|
||||
func->max_blksize,
|
||||
func->card->host->max_blk_size),
|
||||
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);
|
||||
|
||||
/* 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) {
|
||||
/* Blocks per command is limited by host count, host transfer
|
||||
* size (we only use a single sg entry) and the maximum for
|
||||
* IO_RW_EXTENDED of 511 blocks. */
|
||||
max_blocks = min(min(
|
||||
func->card->host->max_blk_count,
|
||||
func->card->host->max_seg_size / func->cur_blksize),
|
||||
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 = remainder;
|
||||
if (size > func->cur_blksize)
|
||||
size = func->cur_blksize;
|
||||
if (size > 512)
|
||||
size = 512; /* maximum size for byte mode */
|
||||
|
||||
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
|
||||
incr_addr, buf, 1, 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.
|
||||
*/
|
||||
unsigned char sdio_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, func->num, addr, 0, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb);
|
||||
|
||||
/**
|
||||
* 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, unsigned char 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_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.
|
||||
*/
|
||||
unsigned short 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_cpu(*(u16*)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, unsigned short b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u16*)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.
|
||||
*/
|
||||
unsigned long 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_cpu(*(u32*)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, unsigned long b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(u32*)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) {
|
||||
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);
|
||||
271
drivers/mmc/core/sdio_irq.c
Normal file
271
drivers/mmc/core/sdio_irq.c
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sdio_irq.c
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 18, 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kthread.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_card *card)
|
||||
{
|
||||
int i, ret, count;
|
||||
unsigned char pending;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
|
||||
if (ret) {
|
||||
printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n",
|
||||
mmc_card_id(card), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (i = 1; i <= 7; i++) {
|
||||
if (pending & (1 << i)) {
|
||||
struct sdio_func *func = card->sdio_func[i - 1];
|
||||
if (!func) {
|
||||
printk(KERN_WARNING "%s: pending IRQ for "
|
||||
"non-existant function\n",
|
||||
mmc_card_id(card));
|
||||
ret = -EINVAL;
|
||||
} else if (func->irq_handler) {
|
||||
func->irq_handler(func);
|
||||
count++;
|
||||
} else {
|
||||
printk(KERN_WARNING "%s: pending IRQ with no handler\n",
|
||||
sdio_func_id(func));
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count)
|
||||
return count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
//Hiko: for new MT5621 WiFi driver compatible
|
||||
current->flags |= PF_NOFREEZE;
|
||||
|
||||
|
||||
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
||||
|
||||
/*
|
||||
* 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->card);
|
||||
mmc_release_host(host);
|
||||
|
||||
/*
|
||||
* Give other threads a chance to run in the presence of
|
||||
* errors. FIXME: determine if due to card removal and
|
||||
* possibly exit this thread if so.
|
||||
*/
|
||||
if (ret < 0)
|
||||
ssleep(1);
|
||||
|
||||
/*
|
||||
* 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_task_state(current, TASK_INTERRUPTIBLE);
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ)
|
||||
host->ops->enable_sdio_irq(host, 1);
|
||||
if (!kthread_should_stop())
|
||||
schedule_timeout(period);
|
||||
set_task_state(current, TASK_RUNNING);
|
||||
} while (!kthread_should_stop());
|
||||
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ)
|
||||
host->ops->enable_sdio_irq(host, 0);
|
||||
|
||||
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++) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 0);
|
||||
host->sdio_irq_thread =
|
||||
kthread_run(sdio_irq_thread, host, "ksdiorqd");
|
||||
if (IS_ERR(host->sdio_irq_thread)) {
|
||||
int err = PTR_ERR(host->sdio_irq_thread);
|
||||
host->sdio_irqs--;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 1);
|
||||
kthread_stop(host->sdio_irq_thread);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, ®);
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®);
|
||||
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);
|
||||
|
||||
175
drivers/mmc/core/sdio_ops.c
Normal file
175
drivers/mmc/core/sdio_ops.c
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8* out)
|
||||
{
|
||||
struct mmc_command cmd;
|
||||
int err;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(fn > 7);
|
||||
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
|
||||
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(card->host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
if (mmc_host_is_spi(card->host))
|
||||
*out = (cmd.resp[0] >> 8) & 0xFF;
|
||||
else
|
||||
*out = cmd.resp[0] & 0xFF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
struct mmc_command cmd;
|
||||
struct mmc_data data;
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(fn > 7);
|
||||
BUG_ON(blocks == 1 && blksz > 512);
|
||||
WARN_ON(blocks == 0);
|
||||
WARN_ON(blksz == 0);
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
memset(&cmd, 0, sizeof(struct mmc_command));
|
||||
memset(&data, 0, sizeof(struct mmc_data));
|
||||
|
||||
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 == 1 && blksz <= 512)
|
||||
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;
|
||||
data.blocks = blocks;
|
||||
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, buf, blksz * blocks);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
22
drivers/mmc/core/sdio_ops.h
Normal file
22
drivers/mmc/core/sdio_ops.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
#endif
|
||||
|
||||
43
drivers/mmc/core/sysfs.c
Normal file
43
drivers/mmc/core/sysfs.c
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sysfs.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC sysfs/driver model support.
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; attr_name(attrs[i]); i++) {
|
||||
error = device_create_file(&card->dev, &attrs[i]);
|
||||
if (error) {
|
||||
while (--i >= 0)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; attr_name(attrs[i]); i++)
|
||||
device_remove_file(&card->dev, &attrs[i]);
|
||||
}
|
||||
|
||||
26
drivers/mmc/core/sysfs.h
Normal file
26
drivers/mmc/core/sysfs.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* linux/drivers/mmc/core/sysfs.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_SYSFS_H
|
||||
#define _MMC_CORE_SYSFS_H
|
||||
|
||||
#define MMC_ATTR_FN(name, fmt, args...) \
|
||||
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct mmc_card *card = container_of(dev, struct mmc_card, dev);\
|
||||
return sprintf(buf, fmt, args); \
|
||||
}
|
||||
|
||||
#define MMC_ATTR_RO(name) __ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
|
||||
|
||||
int mmc_add_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
void mmc_remove_attrs(struct mmc_card *card, struct device_attribute *attrs);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user