mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
28
drivers/mmc/core/Kconfig
Normal file
28
drivers/mmc/core/Kconfig
Normal file
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# MMC core configuration
|
||||
#
|
||||
|
||||
config MMC_CLKGATE
|
||||
bool "MMC host clock gating"
|
||||
help
|
||||
This will attempt to aggressively gate the clock to the MMC card.
|
||||
This is done to save power due to gating off the logic and bus
|
||||
noise when the MMC card is not in use. Your host driver has to
|
||||
support handling this in order for it to be of any use.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_EMBEDDED_SDIO
|
||||
boolean "MMC embedded SDIO device support (EXPERIMENTAL)"
|
||||
help
|
||||
If you say Y here, support will be added for embedded SDIO
|
||||
devices which do not contain the necessary enumeration
|
||||
support in hardware to be properly detected.
|
||||
|
||||
config MMC_PARANOID_SD_INIT
|
||||
bool "Enable paranoid SD card initialization (EXPERIMENTAL)"
|
||||
help
|
||||
If you say Y here, the MMC layer will be extra paranoid
|
||||
about re-trying SD init requests. This can be a useful
|
||||
work-around for buggy controllers and hardware. Enable
|
||||
if you are experiencing issues with SD detection.
|
12
drivers/mmc/core/Makefile
Normal file
12
drivers/mmc/core/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Makefile for the kernel mmc core.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MMC) += mmc_core.o
|
||||
mmc_core-y := core.o bus.o host.o \
|
||||
mmc.o mmc_ops.o sd.o sd_ops.o \
|
||||
sdio.o sdio_ops.o sdio_bus.o \
|
||||
sdio_cis.o sdio_io.o sdio_irq.o \
|
||||
quirks.o slot-gpio.o
|
||||
|
||||
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
|
394
drivers/mmc/core/bus.c
Normal file
394
drivers/mmc/core/bus.c
Normal file
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/bus.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC card bus driver model
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sdio_cis.h"
|
||||
#include "bus.h"
|
||||
|
||||
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
|
||||
|
||||
static ssize_t type_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
return sprintf(buf, "MMC\n");
|
||||
case MMC_TYPE_SD:
|
||||
return sprintf(buf, "SD\n");
|
||||
case MMC_TYPE_SDIO:
|
||||
return sprintf(buf, "SDIO\n");
|
||||
case MMC_TYPE_SD_COMBO:
|
||||
return sprintf(buf, "SDcombo\n");
|
||||
default:
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
static DEVICE_ATTR_RO(type);
|
||||
|
||||
static struct attribute *mmc_dev_attrs[] = {
|
||||
&dev_attr_type.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(mmc_dev);
|
||||
|
||||
/*
|
||||
* This currently matches any MMC driver to any MMC card - drivers
|
||||
* themselves make the decision whether to drive this card in their
|
||||
* probe method.
|
||||
*/
|
||||
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
const char *type;
|
||||
int retval = 0;
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
type = "MMC";
|
||||
break;
|
||||
case MMC_TYPE_SD:
|
||||
type = "SD";
|
||||
break;
|
||||
case MMC_TYPE_SDIO:
|
||||
type = "SDIO";
|
||||
break;
|
||||
case MMC_TYPE_SD_COMBO:
|
||||
type = "SDcombo";
|
||||
break;
|
||||
default:
|
||||
type = NULL;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
retval = add_uevent_var(env, "MMC_TYPE=%s", type);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card));
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/*
|
||||
* Request the mmc_block device. Note: that this is a direct request
|
||||
* for the module it carries no information as to what is inserted.
|
||||
*/
|
||||
retval = add_uevent_var(env, "MODALIAS=mmc:block");
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int mmc_bus_probe(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
|
||||
return drv->probe(card);
|
||||
}
|
||||
|
||||
static int mmc_bus_remove(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
|
||||
drv->remove(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mmc_bus_shutdown(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
struct mmc_host *host = card->host;
|
||||
int ret;
|
||||
|
||||
if (dev->driver && drv->shutdown)
|
||||
drv->shutdown(card);
|
||||
|
||||
if (host->bus_ops->shutdown) {
|
||||
ret = host->bus_ops->shutdown(host);
|
||||
if (ret)
|
||||
pr_warn("%s: error %d during shutdown\n",
|
||||
mmc_hostname(host), ret);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mmc_bus_suspend(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
struct mmc_host *host = card->host;
|
||||
int ret;
|
||||
|
||||
if (dev->driver && drv->suspend) {
|
||||
ret = drv->suspend(card);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mmc_bus_needs_resume(host))
|
||||
return 0;
|
||||
|
||||
ret = host->bus_ops->suspend(host);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_bus_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_driver *drv = to_mmc_driver(dev->driver);
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
struct mmc_host *host = card->host;
|
||||
int ret = 0;
|
||||
|
||||
if (mmc_bus_manual_resume(host) ) {
|
||||
host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME;
|
||||
} else {
|
||||
ret = host->bus_ops->resume(host);
|
||||
if (ret)
|
||||
pr_warn("%s: error %d during resume (card was removed?)\n",
|
||||
mmc_hostname(host), ret);
|
||||
}
|
||||
|
||||
if (dev->driver && drv->resume)
|
||||
ret = drv->resume(card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int mmc_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
return host->bus_ops->runtime_suspend(host);
|
||||
}
|
||||
|
||||
static int mmc_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
return host->bus_ops->runtime_resume(host);
|
||||
}
|
||||
#endif /* !CONFIG_PM_RUNTIME */
|
||||
|
||||
static const struct dev_pm_ops mmc_bus_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume)
|
||||
};
|
||||
|
||||
static struct bus_type mmc_bus_type = {
|
||||
.name = "mmc",
|
||||
.dev_groups = mmc_dev_groups,
|
||||
.match = mmc_bus_match,
|
||||
.uevent = mmc_bus_uevent,
|
||||
.probe = mmc_bus_probe,
|
||||
.remove = mmc_bus_remove,
|
||||
.shutdown = mmc_bus_shutdown,
|
||||
.pm = &mmc_bus_pm_ops,
|
||||
};
|
||||
|
||||
int mmc_register_bus(void)
|
||||
{
|
||||
return bus_register(&mmc_bus_type);
|
||||
}
|
||||
|
||||
void mmc_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&mmc_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_register_driver - register a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
int mmc_register_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_register_driver);
|
||||
|
||||
/**
|
||||
* mmc_unregister_driver - unregister a media driver
|
||||
* @drv: MMC media driver
|
||||
*/
|
||||
void mmc_unregister_driver(struct mmc_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &mmc_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_unregister_driver);
|
||||
|
||||
static void mmc_release_card(struct device *dev)
|
||||
{
|
||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||
|
||||
sdio_free_common_cis(card);
|
||||
|
||||
kfree(card->info);
|
||||
|
||||
kfree(card);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new MMC card structure.
|
||||
*/
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
|
||||
{
|
||||
struct mmc_card *card;
|
||||
|
||||
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
|
||||
if (!card)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
card->host = host;
|
||||
|
||||
device_initialize(&card->dev);
|
||||
|
||||
card->dev.parent = mmc_classdev(host);
|
||||
card->dev.bus = &mmc_bus_type;
|
||||
card->dev.release = mmc_release_card;
|
||||
card->dev.type = type;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a new MMC card with the driver model.
|
||||
*/
|
||||
int mmc_add_card(struct mmc_card *card)
|
||||
{
|
||||
int ret;
|
||||
const char *type;
|
||||
const char *uhs_bus_speed_mode = "";
|
||||
static const char *const uhs_speeds[] = {
|
||||
[UHS_SDR12_BUS_SPEED] = "SDR12 ",
|
||||
[UHS_SDR25_BUS_SPEED] = "SDR25 ",
|
||||
[UHS_SDR50_BUS_SPEED] = "SDR50 ",
|
||||
[UHS_SDR104_BUS_SPEED] = "SDR104 ",
|
||||
[UHS_DDR50_BUS_SPEED] = "DDR50 ",
|
||||
};
|
||||
|
||||
|
||||
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
|
||||
|
||||
switch (card->type) {
|
||||
case MMC_TYPE_MMC:
|
||||
type = "MMC";
|
||||
break;
|
||||
case MMC_TYPE_SD:
|
||||
type = "SD";
|
||||
if (mmc_card_blockaddr(card)) {
|
||||
if (mmc_card_ext_capacity(card))
|
||||
type = "SDXC";
|
||||
else
|
||||
type = "SDHC";
|
||||
}
|
||||
break;
|
||||
case MMC_TYPE_SDIO:
|
||||
type = "SDIO";
|
||||
break;
|
||||
case MMC_TYPE_SD_COMBO:
|
||||
type = "SD-combo";
|
||||
if (mmc_card_blockaddr(card))
|
||||
type = "SDHC-combo";
|
||||
break;
|
||||
default:
|
||||
type = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
if (mmc_card_uhs(card) &&
|
||||
(card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))
|
||||
uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
pr_info("%s: new %s%s%s card on SPI\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_hs(card) ? "high speed " : "",
|
||||
mmc_card_ddr52(card) ? "DDR " : "",
|
||||
type);
|
||||
} else {
|
||||
pr_info("%s: new %s%s%s%s%s card at address %04x\n",
|
||||
mmc_hostname(card->host),
|
||||
mmc_card_uhs(card) ? "ultra high speed " :
|
||||
(mmc_card_hs(card) ? "high speed " : ""),
|
||||
mmc_card_hs400(card) ? "HS400 " :
|
||||
(mmc_card_hs200(card) ? "HS200 " : ""),
|
||||
mmc_card_ddr52(card) ? "DDR " : "",
|
||||
uhs_bus_speed_mode, type, card->rca);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
mmc_add_card_debugfs(card);
|
||||
#endif
|
||||
mmc_init_context_info(card->host);
|
||||
|
||||
ret = device_add(&card->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mmc_card_set_present(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a new MMC card with the driver model, and
|
||||
* (eventually) free it.
|
||||
*/
|
||||
void mmc_remove_card(struct mmc_card *card)
|
||||
{
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
mmc_remove_card_debugfs(card);
|
||||
#endif
|
||||
|
||||
if (mmc_card_present(card)) {
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
pr_info("%s: SPI card removed\n",
|
||||
mmc_hostname(card->host));
|
||||
} else {
|
||||
pr_info("%s: card %04x removed\n",
|
||||
mmc_hostname(card->host), card->rca);
|
||||
}
|
||||
device_del(&card->dev);
|
||||
}
|
||||
|
||||
put_device(&card->dev);
|
||||
}
|
||||
|
31
drivers/mmc/core/bus.h
Normal file
31
drivers/mmc/core/bus.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/bus.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_BUS_H
|
||||
#define _MMC_CORE_BUS_H
|
||||
|
||||
#define MMC_DEV_ATTR(name, fmt, args...) \
|
||||
static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct mmc_card *card = mmc_dev_to_card(dev); \
|
||||
return sprintf(buf, fmt, args); \
|
||||
} \
|
||||
static DEVICE_ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
|
||||
|
||||
struct mmc_card *mmc_alloc_card(struct mmc_host *host,
|
||||
struct device_type *type);
|
||||
int mmc_add_card(struct mmc_card *card);
|
||||
void mmc_remove_card(struct mmc_card *card);
|
||||
|
||||
int mmc_register_bus(void);
|
||||
void mmc_unregister_bus(void);
|
||||
|
||||
#endif
|
||||
|
2813
drivers/mmc/core/core.c
Normal file
2813
drivers/mmc/core/core.c
Normal file
File diff suppressed because it is too large
Load diff
85
drivers/mmc/core/core.h
Normal file
85
drivers/mmc/core/core.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/core.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_CORE_H
|
||||
#define _MMC_CORE_CORE_H
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define MMC_CMD_RETRIES 3
|
||||
|
||||
struct mmc_bus_ops {
|
||||
void (*remove)(struct mmc_host *);
|
||||
void (*detect)(struct mmc_host *);
|
||||
int (*pre_suspend)(struct mmc_host *);
|
||||
int (*suspend)(struct mmc_host *);
|
||||
int (*resume)(struct mmc_host *);
|
||||
int (*runtime_suspend)(struct mmc_host *);
|
||||
int (*runtime_resume)(struct mmc_host *);
|
||||
int (*power_save)(struct mmc_host *);
|
||||
int (*power_restore)(struct mmc_host *);
|
||||
int (*alive)(struct mmc_host *);
|
||||
int (*shutdown)(struct mmc_host *);
|
||||
};
|
||||
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
|
||||
void mmc_detach_bus(struct mmc_host *host);
|
||||
|
||||
void mmc_init_erase(struct mmc_card *card);
|
||||
|
||||
void mmc_set_chip_select(struct mmc_host *host, int mode);
|
||||
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
|
||||
void mmc_gate_clock(struct mmc_host *host);
|
||||
void mmc_ungate_clock(struct mmc_host *host);
|
||||
void mmc_set_ungated(struct mmc_host *host);
|
||||
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
|
||||
void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
|
||||
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
|
||||
int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr);
|
||||
int __mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage);
|
||||
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
|
||||
void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
|
||||
void mmc_power_up(struct mmc_host *host, u32 ocr);
|
||||
void mmc_power_off(struct mmc_host *host);
|
||||
void mmc_power_cycle(struct mmc_host *host, u32 ocr);
|
||||
|
||||
static inline void mmc_delay(unsigned int ms)
|
||||
{
|
||||
if (ms < 1000 / HZ) {
|
||||
cond_resched();
|
||||
mdelay(ms);
|
||||
} else {
|
||||
msleep(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_rescan(struct work_struct *work);
|
||||
void mmc_start_host(struct mmc_host *host);
|
||||
void mmc_stop_host(struct mmc_host *host);
|
||||
|
||||
int _mmc_detect_card_removed(struct mmc_host *host);
|
||||
|
||||
int mmc_attach_mmc(struct mmc_host *host);
|
||||
int mmc_attach_sd(struct mmc_host *host);
|
||||
int mmc_attach_sdio(struct mmc_host *host);
|
||||
|
||||
/* Module parameters */
|
||||
extern bool use_spi_crc;
|
||||
|
||||
/* Debugfs information for hosts and cards */
|
||||
void mmc_add_host_debugfs(struct mmc_host *host);
|
||||
void mmc_remove_host_debugfs(struct mmc_host *host);
|
||||
|
||||
void mmc_add_card_debugfs(struct mmc_card *card);
|
||||
void mmc_remove_card_debugfs(struct mmc_card *card);
|
||||
|
||||
void mmc_init_context_info(struct mmc_host *host);
|
||||
#endif
|
||||
|
386
drivers/mmc/core/debugfs.c
Normal file
386
drivers/mmc/core/debugfs.c
Normal file
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* Debugfs support for hosts and cards
|
||||
*
|
||||
* Copyright (C) 2008 Atmel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/fault-inject.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
#ifdef CONFIG_FAIL_MMC_REQUEST
|
||||
|
||||
static DECLARE_FAULT_ATTR(fail_default_attr);
|
||||
static char *fail_request;
|
||||
module_param(fail_request, charp, 0);
|
||||
|
||||
#endif /* CONFIG_FAIL_MMC_REQUEST */
|
||||
|
||||
/* The debugfs functions are optimized away when CONFIG_DEBUG_FS isn't set. */
|
||||
static int mmc_ios_show(struct seq_file *s, void *data)
|
||||
{
|
||||
static const char *vdd_str[] = {
|
||||
[8] = "2.0",
|
||||
[9] = "2.1",
|
||||
[10] = "2.2",
|
||||
[11] = "2.3",
|
||||
[12] = "2.4",
|
||||
[13] = "2.5",
|
||||
[14] = "2.6",
|
||||
[15] = "2.7",
|
||||
[16] = "2.8",
|
||||
[17] = "2.9",
|
||||
[18] = "3.0",
|
||||
[19] = "3.1",
|
||||
[20] = "3.2",
|
||||
[21] = "3.3",
|
||||
[22] = "3.4",
|
||||
[23] = "3.5",
|
||||
[24] = "3.6",
|
||||
};
|
||||
struct mmc_host *host = s->private;
|
||||
struct mmc_ios *ios = &host->ios;
|
||||
const char *str;
|
||||
|
||||
seq_printf(s, "clock:\t\t%u Hz\n", ios->clock);
|
||||
if (host->actual_clock)
|
||||
seq_printf(s, "actual clock:\t%u Hz\n", host->actual_clock);
|
||||
seq_printf(s, "vdd:\t\t%u ", ios->vdd);
|
||||
if ((1 << ios->vdd) & MMC_VDD_165_195)
|
||||
seq_printf(s, "(1.65 - 1.95 V)\n");
|
||||
else if (ios->vdd < (ARRAY_SIZE(vdd_str) - 1)
|
||||
&& vdd_str[ios->vdd] && vdd_str[ios->vdd + 1])
|
||||
seq_printf(s, "(%s ~ %s V)\n", vdd_str[ios->vdd],
|
||||
vdd_str[ios->vdd + 1]);
|
||||
else
|
||||
seq_printf(s, "(invalid)\n");
|
||||
|
||||
switch (ios->bus_mode) {
|
||||
case MMC_BUSMODE_OPENDRAIN:
|
||||
str = "open drain";
|
||||
break;
|
||||
case MMC_BUSMODE_PUSHPULL:
|
||||
str = "push-pull";
|
||||
break;
|
||||
default:
|
||||
str = "invalid";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, "bus mode:\t%u (%s)\n", ios->bus_mode, str);
|
||||
|
||||
switch (ios->chip_select) {
|
||||
case MMC_CS_DONTCARE:
|
||||
str = "don't care";
|
||||
break;
|
||||
case MMC_CS_HIGH:
|
||||
str = "active high";
|
||||
break;
|
||||
case MMC_CS_LOW:
|
||||
str = "active low";
|
||||
break;
|
||||
default:
|
||||
str = "invalid";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, "chip select:\t%u (%s)\n", ios->chip_select, str);
|
||||
|
||||
switch (ios->power_mode) {
|
||||
case MMC_POWER_OFF:
|
||||
str = "off";
|
||||
break;
|
||||
case MMC_POWER_UP:
|
||||
str = "up";
|
||||
break;
|
||||
case MMC_POWER_ON:
|
||||
str = "on";
|
||||
break;
|
||||
default:
|
||||
str = "invalid";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, "power mode:\t%u (%s)\n", ios->power_mode, str);
|
||||
seq_printf(s, "bus width:\t%u (%u bits)\n",
|
||||
ios->bus_width, 1 << ios->bus_width);
|
||||
|
||||
switch (ios->timing) {
|
||||
case MMC_TIMING_LEGACY:
|
||||
str = "legacy";
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS:
|
||||
str = "mmc high-speed";
|
||||
break;
|
||||
case MMC_TIMING_SD_HS:
|
||||
str = "sd high-speed";
|
||||
break;
|
||||
case MMC_TIMING_UHS_SDR50:
|
||||
str = "sd uhs SDR50";
|
||||
break;
|
||||
case MMC_TIMING_UHS_SDR104:
|
||||
str = "sd uhs SDR104";
|
||||
break;
|
||||
case MMC_TIMING_UHS_DDR50:
|
||||
str = "sd uhs DDR50";
|
||||
break;
|
||||
case MMC_TIMING_MMC_DDR52:
|
||||
str = "mmc DDR52";
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS200:
|
||||
str = "mmc HS200";
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS400:
|
||||
str = "mmc HS400";
|
||||
break;
|
||||
default:
|
||||
str = "invalid";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, "timing spec:\t%u (%s)\n", ios->timing, str);
|
||||
|
||||
switch (ios->signal_voltage) {
|
||||
case MMC_SIGNAL_VOLTAGE_330:
|
||||
str = "3.30 V";
|
||||
break;
|
||||
case MMC_SIGNAL_VOLTAGE_180:
|
||||
str = "1.80 V";
|
||||
break;
|
||||
case MMC_SIGNAL_VOLTAGE_120:
|
||||
str = "1.20 V";
|
||||
break;
|
||||
default:
|
||||
str = "invalid";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, "signal voltage:\t%u (%s)\n", ios->chip_select, str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_ios_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mmc_ios_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations mmc_ios_fops = {
|
||||
.open = mmc_ios_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int mmc_clock_opt_get(void *data, u64 *val)
|
||||
{
|
||||
struct mmc_host *host = data;
|
||||
|
||||
*val = host->ios.clock;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_clock_opt_set(void *data, u64 val)
|
||||
{
|
||||
struct mmc_host *host = data;
|
||||
|
||||
/* We need this check due to input value is u64 */
|
||||
if (val > host->f_max)
|
||||
return -EINVAL;
|
||||
|
||||
mmc_claim_host(host);
|
||||
mmc_set_clock(host, (unsigned int) val);
|
||||
mmc_release_host(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set,
|
||||
"%llu\n");
|
||||
|
||||
void mmc_add_host_debugfs(struct mmc_host *host)
|
||||
{
|
||||
struct dentry *root;
|
||||
|
||||
root = debugfs_create_dir(mmc_hostname(host), NULL);
|
||||
if (IS_ERR(root))
|
||||
/* Don't complain -- debugfs just isn't enabled */
|
||||
return;
|
||||
if (!root)
|
||||
/* Complain -- debugfs is enabled, but it failed to
|
||||
* create the directory. */
|
||||
goto err_root;
|
||||
|
||||
host->debugfs_root = root;
|
||||
|
||||
if (!debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops))
|
||||
goto err_node;
|
||||
|
||||
if (!debugfs_create_file("clock", S_IRUSR | S_IWUSR, root, host,
|
||||
&mmc_clock_fops))
|
||||
goto err_node;
|
||||
|
||||
#ifdef CONFIG_MMC_CLKGATE
|
||||
if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR),
|
||||
root, &host->clk_delay))
|
||||
goto err_node;
|
||||
#endif
|
||||
#ifdef CONFIG_FAIL_MMC_REQUEST
|
||||
if (fail_request)
|
||||
setup_fault_attr(&fail_default_attr, fail_request);
|
||||
host->fail_mmc_request = fail_default_attr;
|
||||
if (IS_ERR(fault_create_debugfs_attr("fail_mmc_request",
|
||||
root,
|
||||
&host->fail_mmc_request)))
|
||||
goto err_node;
|
||||
#endif
|
||||
return;
|
||||
|
||||
err_node:
|
||||
debugfs_remove_recursive(root);
|
||||
host->debugfs_root = NULL;
|
||||
err_root:
|
||||
dev_err(&host->class_dev, "failed to initialize debugfs\n");
|
||||
}
|
||||
|
||||
void mmc_remove_host_debugfs(struct mmc_host *host)
|
||||
{
|
||||
debugfs_remove_recursive(host->debugfs_root);
|
||||
}
|
||||
|
||||
static int mmc_dbg_card_status_get(void *data, u64 *val)
|
||||
{
|
||||
struct mmc_card *card = data;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
mmc_get_card(card);
|
||||
|
||||
ret = mmc_send_status(data, &status);
|
||||
if (!ret)
|
||||
*val = status;
|
||||
|
||||
mmc_put_card(card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(mmc_dbg_card_status_fops, mmc_dbg_card_status_get,
|
||||
NULL, "%08llx\n");
|
||||
|
||||
#define EXT_CSD_STR_LEN 1025
|
||||
|
||||
static int mmc_ext_csd_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct mmc_card *card = inode->i_private;
|
||||
char *buf;
|
||||
ssize_t n = 0;
|
||||
u8 *ext_csd;
|
||||
int err, i;
|
||||
|
||||
buf = kmalloc(EXT_CSD_STR_LEN + 1, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ext_csd = kmalloc(512, GFP_KERNEL);
|
||||
if (!ext_csd) {
|
||||
err = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
mmc_get_card(card);
|
||||
err = mmc_send_ext_csd(card, ext_csd);
|
||||
mmc_put_card(card);
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
for (i = 0; i < 512; i++)
|
||||
n += sprintf(buf + n, "%02x", ext_csd[i]);
|
||||
n += sprintf(buf + n, "\n");
|
||||
BUG_ON(n != EXT_CSD_STR_LEN);
|
||||
|
||||
filp->private_data = buf;
|
||||
kfree(ext_csd);
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
kfree(buf);
|
||||
kfree(ext_csd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t mmc_ext_csd_read(struct file *filp, char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
char *buf = filp->private_data;
|
||||
|
||||
return simple_read_from_buffer(ubuf, cnt, ppos,
|
||||
buf, EXT_CSD_STR_LEN);
|
||||
}
|
||||
|
||||
static int mmc_ext_csd_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
kfree(file->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations mmc_dbg_ext_csd_fops = {
|
||||
.open = mmc_ext_csd_open,
|
||||
.read = mmc_ext_csd_read,
|
||||
.release = mmc_ext_csd_release,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
void mmc_add_card_debugfs(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
struct dentry *root;
|
||||
|
||||
if (!host->debugfs_root)
|
||||
return;
|
||||
|
||||
root = debugfs_create_dir(mmc_card_id(card), host->debugfs_root);
|
||||
if (IS_ERR(root))
|
||||
/* Don't complain -- debugfs just isn't enabled */
|
||||
return;
|
||||
if (!root)
|
||||
/* Complain -- debugfs is enabled, but it failed to
|
||||
* create the directory. */
|
||||
goto err;
|
||||
|
||||
card->debugfs_root = root;
|
||||
|
||||
if (!debugfs_create_x32("state", S_IRUSR, root, &card->state))
|
||||
goto err;
|
||||
|
||||
if (mmc_card_mmc(card) || mmc_card_sd(card))
|
||||
if (!debugfs_create_file("status", S_IRUSR, root, card,
|
||||
&mmc_dbg_card_status_fops))
|
||||
goto err;
|
||||
|
||||
if (mmc_card_mmc(card))
|
||||
if (!debugfs_create_file("ext_csd", S_IRUSR, root, card,
|
||||
&mmc_dbg_ext_csd_fops))
|
||||
goto err;
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
debugfs_remove_recursive(root);
|
||||
card->debugfs_root = NULL;
|
||||
dev_err(&card->dev, "failed to initialize debugfs\n");
|
||||
}
|
||||
|
||||
void mmc_remove_card_debugfs(struct mmc_card *card)
|
||||
{
|
||||
debugfs_remove_recursive(card->debugfs_root);
|
||||
}
|
628
drivers/mmc/core/host.c
Normal file
628
drivers/mmc/core/host.c
Normal file
|
@ -0,0 +1,628 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/host.c
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright (C) 2007-2008 Pierre Ossman
|
||||
* Copyright (C) 2010 Linus Walleij
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* MMC host class device management
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/slot-gpio.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "host.h"
|
||||
|
||||
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||||
|
||||
static void mmc_host_classdev_release(struct device *dev)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
mutex_destroy(&host->slot.lock);
|
||||
kfree(host);
|
||||
}
|
||||
|
||||
static struct class mmc_host_class = {
|
||||
.name = "mmc_host",
|
||||
.dev_release = mmc_host_classdev_release,
|
||||
};
|
||||
|
||||
int mmc_register_host_class(void)
|
||||
{
|
||||
return class_register(&mmc_host_class);
|
||||
}
|
||||
|
||||
void mmc_unregister_host_class(void)
|
||||
{
|
||||
class_unregister(&mmc_host_class);
|
||||
}
|
||||
|
||||
static DEFINE_IDR(mmc_host_idr);
|
||||
static DEFINE_SPINLOCK(mmc_host_lock);
|
||||
|
||||
#ifdef CONFIG_MMC_CLKGATE
|
||||
static ssize_t clkgate_delay_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
return snprintf(buf, PAGE_SIZE, "%lu\n", host->clkgate_delay);
|
||||
}
|
||||
|
||||
static ssize_t clkgate_delay_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct mmc_host *host = cls_dev_to_mmc_host(dev);
|
||||
unsigned long flags, value;
|
||||
|
||||
if (kstrtoul(buf, 0, &value))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
host->clkgate_delay = value;
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enabling clock gating will make the core call out to the host
|
||||
* once up and once down when it performs a request or card operation
|
||||
* intermingled in any fashion. The driver will see this through
|
||||
* set_ios() operations with ios.clock field set to 0 to gate (disable)
|
||||
* the block clock, and to the old frequency to enable it again.
|
||||
*/
|
||||
static void mmc_host_clk_gate_delayed(struct mmc_host *host)
|
||||
{
|
||||
unsigned long tick_ns;
|
||||
unsigned long freq = host->ios.clock;
|
||||
unsigned long flags;
|
||||
|
||||
if (!freq) {
|
||||
pr_debug("%s: frequency set to 0 in disable function, "
|
||||
"this means the clock is already disabled.\n",
|
||||
mmc_hostname(host));
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* New requests may have appeared while we were scheduling,
|
||||
* then there is no reason to delay the check before
|
||||
* clk_disable().
|
||||
*/
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
|
||||
/*
|
||||
* Delay n bus cycles (at least 8 from MMC spec) before attempting
|
||||
* to disable the MCI block clock. The reference count may have
|
||||
* gone up again after this delay due to rescheduling!
|
||||
*/
|
||||
if (!host->clk_requests) {
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
tick_ns = DIV_ROUND_UP(1000000000, freq);
|
||||
ndelay(host->clk_delay * tick_ns);
|
||||
} else {
|
||||
/* New users appeared while waiting for this work */
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
return;
|
||||
}
|
||||
mutex_lock(&host->clk_gate_mutex);
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
if (!host->clk_requests) {
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
/* This will set host->ios.clock to 0 */
|
||||
mmc_gate_clock(host);
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
pr_debug("%s: gated MCI clock\n", mmc_hostname(host));
|
||||
}
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
mutex_unlock(&host->clk_gate_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal work. Work to disable the clock at some later point.
|
||||
*/
|
||||
static void mmc_host_clk_gate_work(struct work_struct *work)
|
||||
{
|
||||
struct mmc_host *host = container_of(work, struct mmc_host,
|
||||
clk_gate_work.work);
|
||||
|
||||
mmc_host_clk_gate_delayed(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_clk_hold - ungate hardware MCI clocks
|
||||
* @host: host to ungate.
|
||||
*
|
||||
* Makes sure the host ios.clock is restored to a non-zero value
|
||||
* past this call. Increase clock reference count and ungate clock
|
||||
* if we're the first user.
|
||||
*/
|
||||
void mmc_host_clk_hold(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* cancel any clock gating work scheduled by mmc_host_clk_release() */
|
||||
cancel_delayed_work_sync(&host->clk_gate_work);
|
||||
mutex_lock(&host->clk_gate_mutex);
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
if (host->clk_gated) {
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
mmc_ungate_clock(host);
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
pr_debug("%s: ungated MCI clock\n", mmc_hostname(host));
|
||||
}
|
||||
host->clk_requests++;
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
mutex_unlock(&host->clk_gate_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_may_gate_card - check if this card may be gated
|
||||
* @card: card to check.
|
||||
*/
|
||||
static bool mmc_host_may_gate_card(struct mmc_card *card)
|
||||
{
|
||||
/* If there is no card we may gate it */
|
||||
if (!card)
|
||||
return true;
|
||||
/*
|
||||
* Don't gate SDIO cards! These need to be clocked at all times
|
||||
* since they may be independent systems generating interrupts
|
||||
* and other events. The clock requests counter from the core will
|
||||
* go down to zero since the core does not need it, but we will not
|
||||
* gate the clock, because there is somebody out there that may still
|
||||
* be using it.
|
||||
*/
|
||||
return !(card->quirks & MMC_QUIRK_BROKEN_CLK_GATING);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_clk_release - gate off hardware MCI clocks
|
||||
* @host: host to gate.
|
||||
*
|
||||
* Calls the host driver with ios.clock set to zero as often as possible
|
||||
* in order to gate off hardware MCI clocks. Decrease clock reference
|
||||
* count and schedule disabling of clock.
|
||||
*/
|
||||
void mmc_host_clk_release(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
host->clk_requests--;
|
||||
if (mmc_host_may_gate_card(host->card) &&
|
||||
!host->clk_requests)
|
||||
schedule_delayed_work(&host->clk_gate_work,
|
||||
msecs_to_jiffies(host->clkgate_delay));
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_clk_rate - get current clock frequency setting
|
||||
* @host: host to get the clock frequency for.
|
||||
*
|
||||
* Returns current clock frequency regardless of gating.
|
||||
*/
|
||||
unsigned int mmc_host_clk_rate(struct mmc_host *host)
|
||||
{
|
||||
unsigned long freq;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->clk_lock, flags);
|
||||
if (host->clk_gated)
|
||||
freq = host->clk_old;
|
||||
else
|
||||
freq = host->ios.clock;
|
||||
spin_unlock_irqrestore(&host->clk_lock, flags);
|
||||
return freq;
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_clk_init - set up clock gating code
|
||||
* @host: host with potential clock to control
|
||||
*/
|
||||
static inline void mmc_host_clk_init(struct mmc_host *host)
|
||||
{
|
||||
host->clk_requests = 0;
|
||||
/* Hold MCI clock for 8 cycles by default */
|
||||
host->clk_delay = 8;
|
||||
/*
|
||||
* Default clock gating delay is 0ms to avoid wasting power.
|
||||
* This value can be tuned by writing into sysfs entry.
|
||||
*/
|
||||
host->clkgate_delay = 0;
|
||||
host->clk_gated = false;
|
||||
INIT_DELAYED_WORK(&host->clk_gate_work, mmc_host_clk_gate_work);
|
||||
spin_lock_init(&host->clk_lock);
|
||||
mutex_init(&host->clk_gate_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_host_clk_exit - shut down clock gating code
|
||||
* @host: host with potential clock to control
|
||||
*/
|
||||
static inline void mmc_host_clk_exit(struct mmc_host *host)
|
||||
{
|
||||
/*
|
||||
* Wait for any outstanding gate and then make sure we're
|
||||
* ungated before exiting.
|
||||
*/
|
||||
if (cancel_delayed_work_sync(&host->clk_gate_work))
|
||||
mmc_host_clk_gate_delayed(host);
|
||||
if (host->clk_gated)
|
||||
mmc_host_clk_hold(host);
|
||||
/* There should be only one user now */
|
||||
WARN_ON(host->clk_requests > 1);
|
||||
}
|
||||
|
||||
static inline void mmc_host_clk_sysfs_init(struct mmc_host *host)
|
||||
{
|
||||
host->clkgate_delay_attr.show = clkgate_delay_show;
|
||||
host->clkgate_delay_attr.store = clkgate_delay_store;
|
||||
sysfs_attr_init(&host->clkgate_delay_attr.attr);
|
||||
host->clkgate_delay_attr.attr.name = "clkgate_delay";
|
||||
host->clkgate_delay_attr.attr.mode = S_IRUGO | S_IWUSR;
|
||||
if (device_create_file(&host->class_dev, &host->clkgate_delay_attr))
|
||||
pr_err("%s: Failed to create clkgate_delay sysfs entry\n",
|
||||
mmc_hostname(host));
|
||||
}
|
||||
#else
|
||||
|
||||
static inline void mmc_host_clk_init(struct mmc_host *host)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void mmc_host_clk_exit(struct mmc_host *host)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void mmc_host_clk_sysfs_init(struct mmc_host *host)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* mmc_of_parse() - parse host's device-tree node
|
||||
* @host: host whose node should be parsed.
|
||||
*
|
||||
* To keep the rest of the MMC subsystem unaware of whether DT has been
|
||||
* used to to instantiate and configure this host instance or not, we
|
||||
* parse the properties and set respective generic mmc-host flags and
|
||||
* parameters.
|
||||
*/
|
||||
int mmc_of_parse(struct mmc_host *host)
|
||||
{
|
||||
struct device_node *np;
|
||||
u32 bus_width;
|
||||
int len, ret;
|
||||
bool cd_cap_invert, cd_gpio_invert = false;
|
||||
bool ro_cap_invert, ro_gpio_invert = false;
|
||||
u32 device_strength;
|
||||
|
||||
if (!host->parent || !host->parent->of_node)
|
||||
return 0;
|
||||
|
||||
np = host->parent->of_node;
|
||||
|
||||
/* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
|
||||
if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
|
||||
dev_dbg(host->parent,
|
||||
"\"bus-width\" property is missing, assuming 1 bit.\n");
|
||||
bus_width = 1;
|
||||
}
|
||||
|
||||
switch (bus_width) {
|
||||
case 8:
|
||||
host->caps |= MMC_CAP_8_BIT_DATA;
|
||||
/* Hosts capable of 8-bit transfers can also do 4 bits */
|
||||
case 4:
|
||||
host->caps |= MMC_CAP_4_BIT_DATA;
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
dev_err(host->parent,
|
||||
"Invalid \"bus-width\" value %u!\n", bus_width);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* f_max is obtained from the optional "max-frequency" property */
|
||||
of_property_read_u32(np, "max-frequency", &host->f_max);
|
||||
|
||||
/*
|
||||
* Configure CD and WP pins. They are both by default active low to
|
||||
* match the SDHCI spec. If GPIOs are provided for CD and / or WP, the
|
||||
* mmc-gpio helpers are used to attach, configure and use them. If
|
||||
* polarity inversion is specified in DT, one of MMC_CAP2_CD_ACTIVE_HIGH
|
||||
* and MMC_CAP2_RO_ACTIVE_HIGH capability-2 flags is set. If the
|
||||
* "broken-cd" property is provided, the MMC_CAP_NEEDS_POLL capability
|
||||
* is set. If the "non-removable" property is found, the
|
||||
* MMC_CAP_NONREMOVABLE capability is set and no card-detection
|
||||
* configuration is performed.
|
||||
*/
|
||||
|
||||
/* Parse Card Detection */
|
||||
if (of_find_property(np, "non-removable", &len)) {
|
||||
host->caps |= MMC_CAP_NONREMOVABLE;
|
||||
} else {
|
||||
cd_cap_invert = of_property_read_bool(np, "cd-inverted");
|
||||
|
||||
if (of_find_property(np, "broken-cd", &len))
|
||||
host->caps |= MMC_CAP_NEEDS_POLL;
|
||||
|
||||
ret = mmc_gpiod_request_cd(host, "cd", 0, true,
|
||||
0, &cd_gpio_invert);
|
||||
if (ret) {
|
||||
if (ret == -EPROBE_DEFER)
|
||||
return ret;
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(host->parent,
|
||||
"Failed to request CD GPIO: %d\n",
|
||||
ret);
|
||||
}
|
||||
} else
|
||||
dev_info(host->parent, "Got CD GPIO\n");
|
||||
|
||||
/*
|
||||
* There are two ways to flag that the CD line is inverted:
|
||||
* through the cd-inverted flag and by the GPIO line itself
|
||||
* being inverted from the GPIO subsystem. This is a leftover
|
||||
* from the times when the GPIO subsystem did not make it
|
||||
* possible to flag a line as inverted.
|
||||
*
|
||||
* If the capability on the host AND the GPIO line are
|
||||
* both inverted, the end result is that the CD line is
|
||||
* not inverted.
|
||||
*/
|
||||
if (cd_cap_invert ^ cd_gpio_invert)
|
||||
host->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
|
||||
}
|
||||
|
||||
/* Parse Write Protection */
|
||||
ro_cap_invert = of_property_read_bool(np, "wp-inverted");
|
||||
|
||||
ret = mmc_gpiod_request_ro(host, "wp", 0, false, 0, &ro_gpio_invert);
|
||||
if (ret) {
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto out;
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(host->parent,
|
||||
"Failed to request WP GPIO: %d\n",
|
||||
ret);
|
||||
}
|
||||
} else
|
||||
dev_info(host->parent, "Got WP GPIO\n");
|
||||
|
||||
/* See the comment on CD inversion above */
|
||||
if (ro_cap_invert ^ ro_gpio_invert)
|
||||
host->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH;
|
||||
|
||||
if (of_find_property(np, "cap-sd-highspeed", &len))
|
||||
host->caps |= MMC_CAP_SD_HIGHSPEED;
|
||||
if (of_find_property(np, "cap-mmc-highspeed", &len))
|
||||
host->caps |= MMC_CAP_MMC_HIGHSPEED;
|
||||
if (of_find_property(np, "sd-uhs-sdr12", &len))
|
||||
host->caps |= MMC_CAP_UHS_SDR12;
|
||||
if (of_find_property(np, "sd-uhs-sdr25", &len))
|
||||
host->caps |= MMC_CAP_UHS_SDR25;
|
||||
if (of_find_property(np, "sd-uhs-sdr50", &len))
|
||||
host->caps |= MMC_CAP_UHS_SDR50;
|
||||
if (of_find_property(np, "sd-uhs-sdr104", &len))
|
||||
host->caps |= MMC_CAP_UHS_SDR104;
|
||||
if (of_find_property(np, "sd-uhs-ddr50", &len))
|
||||
host->caps |= MMC_CAP_UHS_DDR50;
|
||||
if (of_find_property(np, "cap-power-off-card", &len))
|
||||
host->caps |= MMC_CAP_POWER_OFF_CARD;
|
||||
if (of_find_property(np, "cap-sdio-irq", &len))
|
||||
host->caps |= MMC_CAP_SDIO_IRQ;
|
||||
if (of_find_property(np, "full-pwr-cycle", &len))
|
||||
host->caps2 |= MMC_CAP2_FULL_PWR_CYCLE;
|
||||
if (of_find_property(np, "keep-power-in-suspend", &len))
|
||||
host->pm_caps |= MMC_PM_KEEP_POWER;
|
||||
if (of_find_property(np, "enable-sdio-wakeup", &len))
|
||||
host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
||||
if (of_find_property(np, "mmc-ddr-1_8v", &len))
|
||||
host->caps |= MMC_CAP_1_8V_DDR;
|
||||
if (of_find_property(np, "mmc-ddr-1_2v", &len))
|
||||
host->caps |= MMC_CAP_1_2V_DDR;
|
||||
if (of_find_property(np, "mmc-hs200-1_8v", &len))
|
||||
host->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
|
||||
if (of_find_property(np, "mmc-hs200-1_2v", &len))
|
||||
host->caps2 |= MMC_CAP2_HS200_1_2V_SDR;
|
||||
if (of_find_property(np, "mmc-hs400-1_8v", &len))
|
||||
host->caps2 |= MMC_CAP2_HS400_1_8V | MMC_CAP2_HS200_1_8V_SDR;
|
||||
if (of_find_property(np, "mmc-hs400-1_2v", &len))
|
||||
host->caps2 |= MMC_CAP2_HS400_1_2V | MMC_CAP2_HS200_1_2V_SDR;
|
||||
if (of_find_property(np, "supports-hs400-enhanced-strobe", NULL))
|
||||
host->caps2 |= MMC_CAP2_STROBE_ENHANCED;
|
||||
if (of_find_property(np, "skip-init-mmc-scan", NULL))
|
||||
host->caps2 |= MMC_CAP2_SKIP_INIT_SCAN;
|
||||
|
||||
host->dsr_req = !of_property_read_u32(np, "dsr", &host->dsr);
|
||||
if (host->dsr_req && (host->dsr & ~0xffff)) {
|
||||
dev_err(host->parent,
|
||||
"device tree specified broken value for DSR: 0x%x, ignoring\n",
|
||||
host->dsr);
|
||||
host->dsr_req = 0;
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(np, "device_drv", &device_strength))
|
||||
host->device_drv = device_strength << 4;
|
||||
else
|
||||
host->device_drv = MMC_DRIVER_TYPE_0;
|
||||
return 0;
|
||||
|
||||
out:
|
||||
mmc_gpio_free_cd(host);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_of_parse);
|
||||
|
||||
/**
|
||||
* mmc_alloc_host - initialise the per-host structure.
|
||||
* @extra: sizeof private data structure
|
||||
* @dev: pointer to host device model structure
|
||||
*
|
||||
* Initialise the per-host structure.
|
||||
*/
|
||||
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
{
|
||||
int err;
|
||||
struct mmc_host *host;
|
||||
|
||||
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
|
||||
if (!host)
|
||||
return NULL;
|
||||
|
||||
/* scanning will be enabled when we're ready */
|
||||
host->rescan_disable = 1;
|
||||
idr_preload(GFP_KERNEL);
|
||||
spin_lock(&mmc_host_lock);
|
||||
err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
|
||||
if (err >= 0)
|
||||
host->index = err;
|
||||
spin_unlock(&mmc_host_lock);
|
||||
idr_preload_end();
|
||||
if (err < 0)
|
||||
goto free;
|
||||
|
||||
dev_set_name(&host->class_dev, "mmc%d", host->index);
|
||||
|
||||
host->parent = dev;
|
||||
host->class_dev.parent = dev;
|
||||
host->class_dev.class = &mmc_host_class;
|
||||
device_initialize(&host->class_dev);
|
||||
|
||||
mmc_host_clk_init(host);
|
||||
|
||||
mutex_init(&host->slot.lock);
|
||||
host->slot.cd_irq = -EINVAL;
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
host->wlock_name = kasprintf(GFP_KERNEL,
|
||||
"%s_detect", mmc_hostname(host));
|
||||
wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND,
|
||||
host->wlock_name);
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
#ifdef CONFIG_PM
|
||||
host->pm_notify.notifier_call = mmc_pm_notify;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
*/
|
||||
host->max_segs = 1;
|
||||
host->max_seg_size = PAGE_CACHE_SIZE;
|
||||
|
||||
host->max_req_size = PAGE_CACHE_SIZE;
|
||||
host->max_blk_size = 512;
|
||||
host->max_blk_count = PAGE_CACHE_SIZE / 512;
|
||||
|
||||
return host;
|
||||
|
||||
free:
|
||||
kfree(host);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_alloc_host);
|
||||
|
||||
/**
|
||||
* mmc_add_host - initialise host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Register the host with the driver model. The host must be
|
||||
* prepared to start servicing requests before this function
|
||||
* completes.
|
||||
*/
|
||||
int mmc_add_host(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
|
||||
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
|
||||
!host->ops->enable_sdio_irq);
|
||||
|
||||
err = device_add(&host->class_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
mmc_add_host_debugfs(host);
|
||||
#endif
|
||||
mmc_host_clk_sysfs_init(host);
|
||||
|
||||
mmc_start_host(host);
|
||||
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
|
||||
register_pm_notifier(&host->pm_notify);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_add_host);
|
||||
|
||||
/**
|
||||
* mmc_remove_host - remove host hardware
|
||||
* @host: mmc host
|
||||
*
|
||||
* Unregister and remove all cards associated with this host,
|
||||
* and power down the MMC bus. No new requests will be issued
|
||||
* after this function has returned.
|
||||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
{
|
||||
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
|
||||
unregister_pm_notifier(&host->pm_notify);
|
||||
|
||||
mmc_stop_host(host);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
mmc_remove_host_debugfs(host);
|
||||
#endif
|
||||
|
||||
device_del(&host->class_dev);
|
||||
|
||||
led_trigger_unregister_simple(host->led);
|
||||
|
||||
mmc_host_clk_exit(host);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_remove_host);
|
||||
|
||||
/**
|
||||
* mmc_free_host - free the host structure
|
||||
* @host: mmc host
|
||||
*
|
||||
* Free the host once all references to it have been dropped.
|
||||
*/
|
||||
void mmc_free_host(struct mmc_host *host)
|
||||
{
|
||||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
wake_lock_destroy(&host->detect_wake_lock);
|
||||
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_free_host);
|
19
drivers/mmc/core/host.h
Normal file
19
drivers/mmc/core/host.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/host.h
|
||||
*
|
||||
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _MMC_CORE_HOST_H
|
||||
#define _MMC_CORE_HOST_H
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
int mmc_register_host_class(void);
|
||||
void mmc_unregister_host_class(void);
|
||||
|
||||
#endif
|
||||
|
2108
drivers/mmc/core/mmc.c
Normal file
2108
drivers/mmc/core/mmc.c
Normal file
File diff suppressed because it is too large
Load diff
677
drivers/mmc/core/mmc_ops.c
Normal file
677
drivers/mmc/core/mmc_ops.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/mmc_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "mmc_ops.h"
|
||||
|
||||
#define MMC_OPS_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */
|
||||
|
||||
static inline int __mmc_send_status(struct mmc_card *card, u32 *status,
|
||||
bool ignore_crc)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
cmd.opcode = MMC_SEND_STATUS;
|
||||
if (!mmc_host_is_spi(card->host))
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
if (ignore_crc)
|
||||
cmd.flags &= ~MMC_RSP_CRC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* NOTE: callers are required to understand the difference
|
||||
* between "native" and SPI format status words!
|
||||
*/
|
||||
if (status)
|
||||
*status = cmd.resp[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status)
|
||||
{
|
||||
return __mmc_send_status(card, status, false);
|
||||
}
|
||||
|
||||
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
cmd.opcode = MMC_SELECT_CARD;
|
||||
|
||||
if (card) {
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
} else {
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_select_card(struct mmc_card *card)
|
||||
{
|
||||
BUG_ON(!card);
|
||||
|
||||
return _mmc_select_card(card->host, card);
|
||||
}
|
||||
|
||||
int mmc_deselect_cards(struct mmc_host *host)
|
||||
{
|
||||
return _mmc_select_card(host, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the value specified in the device tree or board code into the optional
|
||||
* 16 bit Driver Stage Register. This can be used to tune raise/fall times and
|
||||
* drive strength of the DAT and CMD outputs. The actual meaning of a given
|
||||
* value is hardware dependant.
|
||||
* The presence of the DSR register can be determined from the CSD register,
|
||||
* bit 76.
|
||||
*/
|
||||
int mmc_set_dsr(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
cmd.opcode = MMC_SET_DSR;
|
||||
|
||||
cmd.arg = (host->dsr << 16) | 0xffff;
|
||||
cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
|
||||
|
||||
return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
}
|
||||
|
||||
int mmc_go_idle(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
/*
|
||||
* Non-SPI hosts need to prevent chipselect going active during
|
||||
* GO_IDLE; that would put chips into SPI mode. Remind them of
|
||||
* that in case of hardware that won't pull up DAT3/nCS otherwise.
|
||||
*
|
||||
* SPI hosts ignore ios.chip_select; it's managed according to
|
||||
* rules that must accommodate non-MMC slaves which this layer
|
||||
* won't even know about.
|
||||
*/
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
mmc_set_chip_select(host, MMC_CS_HIGH);
|
||||
mmc_delay(1);
|
||||
}
|
||||
|
||||
cmd.opcode = MMC_GO_IDLE_STATE;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
|
||||
mmc_delay(1);
|
||||
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
mmc_set_chip_select(host, MMC_CS_DONTCARE);
|
||||
mmc_delay(1);
|
||||
}
|
||||
|
||||
host->use_spi_crc = 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
cmd.opcode = MMC_SEND_OP_COND;
|
||||
cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (!(cmd.resp[0] & R1_SPI_IDLE))
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (rocr && !mmc_host_is_spi(host))
|
||||
*rocr = cmd.resp[0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_all_send_cid(struct mmc_host *host, u32 *cid)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cid);
|
||||
|
||||
cmd.opcode = MMC_ALL_SEND_CID;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(cid, cmd.resp, sizeof(u32) * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_set_relative_addr(struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
cmd.opcode = MMC_SET_RELATIVE_ADDR;
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!cxd);
|
||||
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = arg;
|
||||
cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(cxd, cmd.resp, sizeof(u32) * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: void *buf, caller for the buf is required to use DMA-capable
|
||||
* buffer or on-stack buffer (with some overhead in callee).
|
||||
*/
|
||||
static int
|
||||
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
|
||||
u32 opcode, void *buf, unsigned len)
|
||||
{
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg;
|
||||
void *data_buf;
|
||||
int is_on_stack;
|
||||
|
||||
is_on_stack = object_is_on_stack(buf);
|
||||
if (is_on_stack) {
|
||||
/*
|
||||
* dma onto stack is unsafe/nonportable, but callers to this
|
||||
* routine normally provide temporary on-stack buffers ...
|
||||
*/
|
||||
data_buf = kmalloc(len, GFP_KERNEL);
|
||||
if (!data_buf)
|
||||
return -ENOMEM;
|
||||
} else
|
||||
data_buf = buf;
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = 0;
|
||||
|
||||
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
|
||||
* rely on callers to never use this with "native" calls for reading
|
||||
* CSD or CID. Native versions of those commands use the R2 type,
|
||||
* not R1 plus a data block.
|
||||
*/
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = len;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, data_buf, len);
|
||||
|
||||
if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
|
||||
/*
|
||||
* The spec states that CSR and CID accesses have a timeout
|
||||
* of 64 clock cycles.
|
||||
*/
|
||||
data.timeout_ns = 0;
|
||||
data.timeout_clks = 64;
|
||||
} else
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
if (is_on_stack) {
|
||||
memcpy(buf, data_buf, len);
|
||||
kfree(data_buf);
|
||||
}
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_csd(struct mmc_card *card, u32 *csd)
|
||||
{
|
||||
int ret, i;
|
||||
u32 *csd_tmp;
|
||||
|
||||
if (!mmc_host_is_spi(card->host))
|
||||
return mmc_send_cxd_native(card->host, card->rca << 16,
|
||||
csd, MMC_SEND_CSD);
|
||||
|
||||
csd_tmp = kmalloc(16, GFP_KERNEL);
|
||||
if (!csd_tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd_tmp, 16);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
csd[i] = be32_to_cpu(csd_tmp[i]);
|
||||
|
||||
err:
|
||||
kfree(csd_tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mmc_send_cid(struct mmc_host *host, u32 *cid)
|
||||
{
|
||||
int ret, i;
|
||||
u32 *cid_tmp;
|
||||
|
||||
if (!mmc_host_is_spi(host)) {
|
||||
if (!host->card)
|
||||
return -EINVAL;
|
||||
return mmc_send_cxd_native(host, host->card->rca << 16,
|
||||
cid, MMC_SEND_CID);
|
||||
}
|
||||
|
||||
cid_tmp = kmalloc(16, GFP_KERNEL);
|
||||
if (!cid_tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid_tmp, 16);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
for (i = 0;i < 4;i++)
|
||||
cid[i] = be32_to_cpu(cid_tmp[i]);
|
||||
|
||||
err:
|
||||
kfree(cid_tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
|
||||
{
|
||||
return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
|
||||
ext_csd, 512);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mmc_send_ext_csd);
|
||||
|
||||
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int err;
|
||||
|
||||
cmd.opcode = MMC_SPI_READ_OCR;
|
||||
cmd.arg = highcap ? (1 << 30) : 0;
|
||||
cmd.flags = MMC_RSP_SPI_R3;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
|
||||
*ocrp = cmd.resp[1];
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_spi_set_crc(struct mmc_host *host, int use_crc)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int err;
|
||||
|
||||
cmd.opcode = MMC_SPI_CRC_ON_OFF;
|
||||
cmd.flags = MMC_RSP_SPI_R1;
|
||||
cmd.arg = use_crc;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (!err)
|
||||
host->use_spi_crc = use_crc;
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* __mmc_switch - modify EXT_CSD register
|
||||
* @card: the MMC card associated with the data transfer
|
||||
* @set: cmd set values
|
||||
* @index: EXT_CSD register index
|
||||
* @value: value to program into EXT_CSD register
|
||||
* @timeout_ms: timeout (ms) for operation performed by register write,
|
||||
* timeout of zero implies maximum possible timeout
|
||||
* @use_busy_signal: use the busy signal as response type
|
||||
* @send_status: send status cmd to poll for busy
|
||||
* @ignore_crc: ignore CRC errors when sending status cmd to poll for busy
|
||||
*
|
||||
* Modifies the EXT_CSD register for selected card.
|
||||
*/
|
||||
int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
||||
unsigned int timeout_ms, bool use_busy_signal, bool send_status,
|
||||
bool ignore_crc)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
unsigned long timeout;
|
||||
u32 status = 0;
|
||||
bool use_r1b_resp = use_busy_signal;
|
||||
|
||||
/*
|
||||
* If the cmd timeout and the max_busy_timeout of the host are both
|
||||
* specified, let's validate them. A failure means we need to prevent
|
||||
* the host from doing hw busy detection, which is done by converting
|
||||
* to a R1 response instead of a R1B.
|
||||
*/
|
||||
if (timeout_ms && host->max_busy_timeout &&
|
||||
(timeout_ms > host->max_busy_timeout))
|
||||
use_r1b_resp = false;
|
||||
|
||||
cmd.opcode = MMC_SWITCH;
|
||||
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
||||
(index << 16) |
|
||||
(value << 8) |
|
||||
set;
|
||||
cmd.flags = MMC_CMD_AC;
|
||||
if (use_r1b_resp) {
|
||||
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
|
||||
/*
|
||||
* A busy_timeout of zero means the host can decide to use
|
||||
* whatever value it finds suitable.
|
||||
*/
|
||||
cmd.busy_timeout = timeout_ms;
|
||||
} else {
|
||||
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
|
||||
}
|
||||
|
||||
if (index == EXT_CSD_SANITIZE_START)
|
||||
cmd.sanitize_busy = true;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* No need to check card status in case of unblocking command */
|
||||
if (!use_busy_signal)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* CRC errors shall only be ignored in cases were CMD13 is used to poll
|
||||
* to detect busy completion.
|
||||
*/
|
||||
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
|
||||
ignore_crc = false;
|
||||
|
||||
/* We have an unspecified cmd timeout, use the fallback value. */
|
||||
if (!timeout_ms)
|
||||
timeout_ms = MMC_OPS_TIMEOUT_MS;
|
||||
|
||||
/* Must check status to be sure of no errors. */
|
||||
timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
||||
do {
|
||||
if (send_status) {
|
||||
err = __mmc_send_status(card, &status, ignore_crc);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
|
||||
break;
|
||||
if (mmc_host_is_spi(host))
|
||||
break;
|
||||
|
||||
/*
|
||||
* We are not allowed to issue a status command and the host
|
||||
* does'nt support MMC_CAP_WAIT_WHILE_BUSY, then we can only
|
||||
* rely on waiting for the stated timeout to be sufficient.
|
||||
*/
|
||||
if (!send_status) {
|
||||
mmc_delay(timeout_ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Timeout if the device never leaves the program state. */
|
||||
if (time_after(jiffies, timeout)) {
|
||||
pr_err("%s: Card stuck in programming state! %s\n",
|
||||
mmc_hostname(host), __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
|
||||
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (status & R1_SPI_ILLEGAL_COMMAND)
|
||||
return -EBADMSG;
|
||||
} else {
|
||||
if (status & 0xFDFFA000)
|
||||
pr_warn("%s: unexpected status %#x after switch\n",
|
||||
mmc_hostname(host), status);
|
||||
if (status & R1_SWITCH_ERROR)
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__mmc_switch);
|
||||
|
||||
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
||||
unsigned int timeout_ms)
|
||||
{
|
||||
return __mmc_switch(card, set, index, value, timeout_ms, true, true,
|
||||
false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mmc_switch);
|
||||
|
||||
static int
|
||||
mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
|
||||
u8 len)
|
||||
{
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg;
|
||||
u8 *data_buf;
|
||||
u8 *test_buf;
|
||||
int i, err;
|
||||
static u8 testdata_8bit[8] = { 0x55, 0xaa, 0, 0, 0, 0, 0, 0 };
|
||||
static u8 testdata_4bit[4] = { 0x5a, 0, 0, 0 };
|
||||
|
||||
/* dma onto stack is unsafe/nonportable, but callers to this
|
||||
* routine normally provide temporary on-stack buffers ...
|
||||
*/
|
||||
data_buf = kmalloc(len, GFP_KERNEL);
|
||||
if (!data_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (len == 8)
|
||||
test_buf = testdata_8bit;
|
||||
else if (len == 4)
|
||||
test_buf = testdata_4bit;
|
||||
else {
|
||||
pr_err("%s: Invalid bus_width %d\n",
|
||||
mmc_hostname(host), len);
|
||||
kfree(data_buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (opcode == MMC_BUS_TEST_W)
|
||||
memcpy(data_buf, test_buf, len);
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = 0;
|
||||
|
||||
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
|
||||
* rely on callers to never use this with "native" calls for reading
|
||||
* CSD or CID. Native versions of those commands use the R2 type,
|
||||
* not R1 plus a data block.
|
||||
*/
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = len;
|
||||
data.blocks = 1;
|
||||
if (opcode == MMC_BUS_TEST_R)
|
||||
data.flags = MMC_DATA_READ;
|
||||
else
|
||||
data.flags = MMC_DATA_WRITE;
|
||||
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
mmc_set_data_timeout(&data, card);
|
||||
sg_init_one(&sg, data_buf, len);
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
err = 0;
|
||||
if (opcode == MMC_BUS_TEST_R) {
|
||||
for (i = 0; i < len / 4; i++)
|
||||
if ((test_buf[i] ^ data_buf[i]) != 0xff) {
|
||||
err = -EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kfree(data_buf);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_bus_test(struct mmc_card *card, u8 bus_width)
|
||||
{
|
||||
int err, width;
|
||||
|
||||
if (bus_width == MMC_BUS_WIDTH_8)
|
||||
width = 8;
|
||||
else if (bus_width == MMC_BUS_WIDTH_4)
|
||||
width = 4;
|
||||
else if (bus_width == MMC_BUS_WIDTH_1)
|
||||
return 0; /* no need for test */
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Ignore errors from BUS_TEST_W. BUS_TEST_R will fail if there
|
||||
* is a problem. This improves chances that the test will work.
|
||||
*/
|
||||
mmc_send_bus_test(card, card->host, MMC_BUS_TEST_W, width);
|
||||
err = mmc_send_bus_test(card, card->host, MMC_BUS_TEST_R, width);
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
unsigned int opcode;
|
||||
int err;
|
||||
|
||||
if (!card->ext_csd.hpi) {
|
||||
pr_warn("%s: Card didn't support HPI command\n",
|
||||
mmc_hostname(card->host));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
opcode = card->ext_csd.hpi_cmd;
|
||||
if (opcode == MMC_STOP_TRANSMISSION)
|
||||
cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
|
||||
else if (opcode == MMC_SEND_STATUS)
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
cmd.opcode = opcode;
|
||||
cmd.arg = card->rca << 16 | 1;
|
||||
|
||||
err = mmc_wait_for_cmd(card->host, &cmd, 0);
|
||||
if (err) {
|
||||
pr_warn("%s: error %d interrupting operation. "
|
||||
"HPI command response %#x\n", mmc_hostname(card->host),
|
||||
err, cmd.resp[0]);
|
||||
return err;
|
||||
}
|
||||
if (status)
|
||||
*status = cmd.resp[0];
|
||||
|
||||
return 0;
|
||||
}
|
32
drivers/mmc/core/mmc_ops.h
Normal file
32
drivers/mmc/core/mmc_ops.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/mmc_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_MMC_OPS_H
|
||||
#define _MMC_MMC_OPS_H
|
||||
|
||||
int mmc_select_card(struct mmc_card *card);
|
||||
int mmc_deselect_cards(struct mmc_host *host);
|
||||
int mmc_set_dsr(struct mmc_host *host);
|
||||
int mmc_go_idle(struct mmc_host *host);
|
||||
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_all_send_cid(struct mmc_host *host, u32 *cid);
|
||||
int mmc_set_relative_addr(struct mmc_card *card);
|
||||
int mmc_send_csd(struct mmc_card *card, u32 *csd);
|
||||
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status);
|
||||
int mmc_send_cid(struct mmc_host *host, u32 *cid);
|
||||
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
|
||||
int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
|
||||
int mmc_bus_test(struct mmc_card *card, u8 bus_width);
|
||||
int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status);
|
||||
|
||||
#endif
|
||||
|
99
drivers/mmc/core/quirks.c
Normal file
99
drivers/mmc/core/quirks.c
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* This file contains work-arounds for many known SD/MMC
|
||||
* and SDIO hardware bugs.
|
||||
*
|
||||
* Copyright (c) 2011 Andrei Warkentin <andreiw@motorola.com>
|
||||
* Copyright (c) 2011 Pierre Tardy <tardyp@gmail.com>
|
||||
* Inspired from pci fixup code:
|
||||
* Copyright (c) 1999 Martin Mares <mj@ucw.cz>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio_ids.h>
|
||||
|
||||
#ifndef SDIO_VENDOR_ID_TI
|
||||
#define SDIO_VENDOR_ID_TI 0x0097
|
||||
#endif
|
||||
|
||||
#ifndef SDIO_DEVICE_ID_TI_WL1271
|
||||
#define SDIO_DEVICE_ID_TI_WL1271 0x4076
|
||||
#endif
|
||||
|
||||
#ifndef SDIO_VENDOR_ID_STE
|
||||
#define SDIO_VENDOR_ID_STE 0x0020
|
||||
#endif
|
||||
|
||||
#ifndef SDIO_DEVICE_ID_STE_CW1200
|
||||
#define SDIO_DEVICE_ID_STE_CW1200 0x2280
|
||||
#endif
|
||||
|
||||
#ifndef SDIO_DEVICE_ID_MARVELL_8797_F0
|
||||
#define SDIO_DEVICE_ID_MARVELL_8797_F0 0x9128
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This hook just adds a quirk for all sdio devices
|
||||
*/
|
||||
static void add_quirk_for_sdio_devices(struct mmc_card *card, int data)
|
||||
{
|
||||
if (mmc_card_sdio(card))
|
||||
card->quirks |= data;
|
||||
}
|
||||
|
||||
static const struct mmc_fixup mmc_fixup_methods[] = {
|
||||
/* by default sdio devices are considered CLK_GATING broken */
|
||||
/* good cards will be whitelisted as they are tested */
|
||||
SDIO_FIXUP(SDIO_ANY_ID, SDIO_ANY_ID,
|
||||
add_quirk_for_sdio_devices,
|
||||
MMC_QUIRK_BROKEN_CLK_GATING),
|
||||
|
||||
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
|
||||
remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING),
|
||||
|
||||
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
|
||||
add_quirk, MMC_QUIRK_NONSTD_FUNC_IF),
|
||||
|
||||
SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
|
||||
add_quirk, MMC_QUIRK_DISABLE_CD),
|
||||
|
||||
SDIO_FIXUP(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200,
|
||||
add_quirk, MMC_QUIRK_BROKEN_BYTE_MODE_512),
|
||||
|
||||
SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8797_F0,
|
||||
add_quirk, MMC_QUIRK_BROKEN_IRQ_POLLING),
|
||||
|
||||
END_FIXUP
|
||||
};
|
||||
|
||||
void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table)
|
||||
{
|
||||
const struct mmc_fixup *f;
|
||||
u64 rev = cid_rev_card(card);
|
||||
|
||||
/* Non-core specific workarounds. */
|
||||
if (!table)
|
||||
table = mmc_fixup_methods;
|
||||
|
||||
for (f = table; f->vendor_fixup; f++) {
|
||||
if ((f->manfid == CID_MANFID_ANY ||
|
||||
f->manfid == card->cid.manfid) &&
|
||||
(f->oemid == CID_OEMID_ANY ||
|
||||
f->oemid == card->cid.oemid) &&
|
||||
(f->name == CID_NAME_ANY ||
|
||||
!strncmp(f->name, card->cid.prod_name,
|
||||
sizeof(card->cid.prod_name))) &&
|
||||
(f->cis_vendor == card->cis.vendor ||
|
||||
f->cis_vendor == (u16) SDIO_ANY_ID) &&
|
||||
(f->cis_device == card->cis.device ||
|
||||
f->cis_device == (u16) SDIO_ANY_ID) &&
|
||||
rev >= f->rev_start && rev <= f->rev_end) {
|
||||
dev_dbg(&card->dev, "calling %pf\n", f->vendor_fixup);
|
||||
f->vendor_fixup(card, f->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_fixup_device);
|
1367
drivers/mmc/core/sd.c
Normal file
1367
drivers/mmc/core/sd.c
Normal file
File diff suppressed because it is too large
Load diff
16
drivers/mmc/core/sd.h
Normal file
16
drivers/mmc/core/sd.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef _MMC_CORE_SD_H
|
||||
#define _MMC_CORE_SD_H
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
|
||||
extern struct device_type sd_type;
|
||||
|
||||
int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr);
|
||||
int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card);
|
||||
void mmc_decode_cid(struct mmc_card *card);
|
||||
int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
|
||||
bool reinit);
|
||||
unsigned mmc_sd_get_max_clock(struct mmc_card *card);
|
||||
int mmc_sd_switch_hs(struct mmc_card *card);
|
||||
|
||||
#endif
|
395
drivers/mmc/core/sd_ops.c
Normal file
395
drivers/mmc/core/sd_ops.c
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sd_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sd.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sd_ops.h"
|
||||
|
||||
int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(card && (card->host != host));
|
||||
|
||||
cmd.opcode = MMC_APP_CMD;
|
||||
|
||||
if (card) {
|
||||
cmd.arg = card->rca << 16;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
} else {
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Check that card supported application commands */
|
||||
if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mmc_app_cmd);
|
||||
|
||||
/**
|
||||
* mmc_wait_for_app_cmd - start an application command and wait for
|
||||
completion
|
||||
* @host: MMC host to start command
|
||||
* @card: Card to send MMC_APP_CMD to
|
||||
* @cmd: MMC command to start
|
||||
* @retries: maximum number of retries
|
||||
*
|
||||
* Sends a MMC_APP_CMD, checks the card response, sends the command
|
||||
* in the parameter and waits for it to complete. Return any error
|
||||
* that occurred while the command was executing. Do not attempt to
|
||||
* parse the response.
|
||||
*/
|
||||
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
|
||||
struct mmc_command *cmd, int retries)
|
||||
{
|
||||
struct mmc_request mrq = {NULL};
|
||||
|
||||
int i, err;
|
||||
|
||||
BUG_ON(!cmd);
|
||||
BUG_ON(retries < 0);
|
||||
|
||||
err = -EIO;
|
||||
|
||||
/*
|
||||
* We have to resend MMC_APP_CMD for each attempt so
|
||||
* we cannot use the retries field in mmc_command.
|
||||
*/
|
||||
for (i = 0;i <= retries;i++) {
|
||||
err = mmc_app_cmd(host, card);
|
||||
if (err) {
|
||||
/* no point in retrying; no APP commands allowed */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&mrq, 0, sizeof(struct mmc_request));
|
||||
|
||||
memset(cmd->resp, 0, sizeof(cmd->resp));
|
||||
cmd->retries = 0;
|
||||
|
||||
mrq.cmd = cmd;
|
||||
cmd->data = NULL;
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
|
||||
err = cmd->error;
|
||||
if (!cmd->error)
|
||||
break;
|
||||
|
||||
/* no point in retrying illegal APP commands */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_wait_for_app_cmd);
|
||||
|
||||
int mmc_app_set_bus_width(struct mmc_card *card, int width)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
cmd.opcode = SD_APP_SET_BUS_WIDTH;
|
||||
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
|
||||
switch (width) {
|
||||
case MMC_BUS_WIDTH_1:
|
||||
cmd.arg = SD_BUS_WIDTH_1;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
cmd.arg = SD_BUS_WIDTH_4;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
cmd.opcode = SD_APP_OP_COND;
|
||||
if (mmc_host_is_spi(host))
|
||||
cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */
|
||||
else
|
||||
cmd.arg = ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
if (!(cmd.resp[0] & R1_SPI_IDLE))
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (!i)
|
||||
pr_err("%s: card never left busy state\n", mmc_hostname(host));
|
||||
|
||||
if (rocr && !mmc_host_is_spi(host))
|
||||
*rocr = cmd.resp[0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int mmc_send_if_cond(struct mmc_host *host, u32 ocr)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int err;
|
||||
static const u8 test_pattern = 0xAA;
|
||||
u8 result_pattern;
|
||||
|
||||
/*
|
||||
* To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND
|
||||
* before SD_APP_OP_COND. This command will harmlessly fail for
|
||||
* SD 1.0 cards.
|
||||
*/
|
||||
cmd.opcode = SD_SEND_IF_COND;
|
||||
cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;
|
||||
cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (mmc_host_is_spi(host))
|
||||
result_pattern = cmd.resp[1] & 0xFF;
|
||||
else
|
||||
result_pattern = cmd.resp[0] & 0xFF;
|
||||
|
||||
if (result_pattern != test_pattern)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca)
|
||||
{
|
||||
int err;
|
||||
struct mmc_command cmd = {0};
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!rca);
|
||||
|
||||
cmd.opcode = SD_SEND_RELATIVE_ADDR;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*rca = cmd.resp[0] >> 16;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_app_send_scr(struct mmc_card *card, u32 *scr)
|
||||
{
|
||||
int err;
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg;
|
||||
void *data_buf;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
BUG_ON(!scr);
|
||||
|
||||
/* NOTE: caller guarantees scr is heap-allocated */
|
||||
|
||||
err = mmc_app_cmd(card->host, card);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* dma onto stack is unsafe/nonportable, but callers to this
|
||||
* routine normally provide temporary on-stack buffers ...
|
||||
*/
|
||||
data_buf = kmalloc(sizeof(card->raw_scr), GFP_KERNEL);
|
||||
if (data_buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_APP_SEND_SCR;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = 8;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, data_buf, 8);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
memcpy(scr, data_buf, sizeof(card->raw_scr));
|
||||
kfree(data_buf);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
scr[0] = be32_to_cpu(scr[0]);
|
||||
scr[1] = be32_to_cpu(scr[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
|
||||
u8 value, u8 *resp)
|
||||
{
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
|
||||
/* NOTE: caller guarantees resp is heap-allocated */
|
||||
|
||||
mode = !!mode;
|
||||
value &= 0xF;
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_SWITCH;
|
||||
cmd.arg = mode << 31 | 0x00FFFFFF;
|
||||
cmd.arg &= ~(0xF << (group * 4));
|
||||
cmd.arg |= value << (group * 4);
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = 64;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, resp, 64);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_app_sd_status(struct mmc_card *card, void *ssr)
|
||||
{
|
||||
int err;
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(!card->host);
|
||||
BUG_ON(!ssr);
|
||||
|
||||
/* NOTE: caller guarantees ssr is heap-allocated */
|
||||
|
||||
err = mmc_app_cmd(card->host, card);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_APP_SD_STATUS;
|
||||
cmd.arg = 0;
|
||||
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = 64;
|
||||
data.blocks = 1;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, ssr, 64);
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
return 0;
|
||||
}
|
25
drivers/mmc/core/sd_ops.h
Normal file
25
drivers/mmc/core/sd_ops.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sd_ops.h
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_SD_OPS_H
|
||||
#define _MMC_SD_OPS_H
|
||||
|
||||
int mmc_app_set_bus_width(struct mmc_card *card, int width);
|
||||
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
|
||||
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
|
||||
int mmc_app_send_scr(struct mmc_card *card, u32 *scr);
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
|
||||
u8 value, u8 *resp);
|
||||
int mmc_app_sd_status(struct mmc_card *card, void *ssr);
|
||||
|
||||
#endif
|
||||
|
1280
drivers/mmc/core/sdio.c
Normal file
1280
drivers/mmc/core/sdio.c
Normal file
File diff suppressed because it is too large
Load diff
351
drivers/mmc/core/sdio_bus.c
Normal file
351
drivers/mmc/core/sdio_bus.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sdio_bus.c
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* SDIO function driver model
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_cis.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
#include <linux/mmc/host.h>
|
||||
#endif
|
||||
|
||||
/* show configuration fields */
|
||||
#define sdio_config_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct sdio_func *func; \
|
||||
\
|
||||
func = dev_to_sdio_func (dev); \
|
||||
return sprintf (buf, format_string, func->field); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(field)
|
||||
|
||||
sdio_config_attr(class, "0x%02x\n");
|
||||
sdio_config_attr(vendor, "0x%04x\n");
|
||||
sdio_config_attr(device, "0x%04x\n");
|
||||
|
||||
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func (dev);
|
||||
|
||||
return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n",
|
||||
func->class, func->vendor, func->device);
|
||||
}
|
||||
static DEVICE_ATTR_RO(modalias);
|
||||
|
||||
static struct attribute *sdio_dev_attrs[] = {
|
||||
&dev_attr_class.attr,
|
||||
&dev_attr_vendor.attr,
|
||||
&dev_attr_device.attr,
|
||||
&dev_attr_modalias.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(sdio_dev);
|
||||
|
||||
static const struct sdio_device_id *sdio_match_one(struct sdio_func *func,
|
||||
const struct sdio_device_id *id)
|
||||
{
|
||||
if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class)
|
||||
return NULL;
|
||||
if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor)
|
||||
return NULL;
|
||||
if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device)
|
||||
return NULL;
|
||||
return id;
|
||||
}
|
||||
|
||||
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
|
||||
struct sdio_driver *sdrv)
|
||||
{
|
||||
const struct sdio_device_id *ids;
|
||||
|
||||
ids = sdrv->id_table;
|
||||
|
||||
if (ids) {
|
||||
while (ids->class || ids->vendor || ids->device) {
|
||||
if (sdio_match_one(func, ids))
|
||||
return ids;
|
||||
ids++;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
struct sdio_driver *sdrv = to_sdio_driver(drv);
|
||||
|
||||
if (sdio_match_device(func, sdrv))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sdio_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
|
||||
if (add_uevent_var(env,
|
||||
"SDIO_CLASS=%02X", func->class))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(env,
|
||||
"SDIO_ID=%04X:%04X", func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
if (add_uevent_var(env,
|
||||
"MODALIAS=sdio:c%02Xv%04Xd%04X",
|
||||
func->class, func->vendor, func->device))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdio_bus_probe(struct device *dev)
|
||||
{
|
||||
struct sdio_driver *drv = to_sdio_driver(dev->driver);
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
const struct sdio_device_id *id;
|
||||
int ret;
|
||||
|
||||
id = sdio_match_device(func, drv);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
/* Unbound SDIO functions are always suspended.
|
||||
* During probe, the function is set active and the usage count
|
||||
* is incremented. If the driver supports runtime PM,
|
||||
* it should call pm_runtime_put_noidle() in its probe routine and
|
||||
* pm_runtime_get_noresume() in its remove routine.
|
||||
*/
|
||||
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0)
|
||||
goto disable_runtimepm;
|
||||
}
|
||||
|
||||
/* Set the default block size so the driver is sure it's something
|
||||
* sensible. */
|
||||
sdio_claim_host(func);
|
||||
ret = sdio_set_block_size(func, 0);
|
||||
sdio_release_host(func);
|
||||
if (ret)
|
||||
goto disable_runtimepm;
|
||||
|
||||
ret = drv->probe(func, id);
|
||||
if (ret)
|
||||
goto disable_runtimepm;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_runtimepm:
|
||||
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
|
||||
pm_runtime_put_noidle(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_bus_remove(struct device *dev)
|
||||
{
|
||||
struct sdio_driver *drv = to_sdio_driver(dev->driver);
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
int ret = 0;
|
||||
|
||||
/* Make sure card is powered before invoking ->remove() */
|
||||
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
drv->remove(func);
|
||||
|
||||
if (func->irq_handler) {
|
||||
pr_warn("WARNING: driver %s did not remove its interrupt handler!\n",
|
||||
drv->name);
|
||||
sdio_claim_host(func);
|
||||
sdio_release_irq(func);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
/* First, undo the increment made directly above */
|
||||
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
|
||||
pm_runtime_put_noidle(dev);
|
||||
|
||||
/* Then undo the runtime PM settings in sdio_bus_probe() */
|
||||
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
|
||||
pm_runtime_put_sync(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static const struct dev_pm_ops sdio_bus_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_generic_suspend, pm_generic_resume)
|
||||
SET_RUNTIME_PM_OPS(
|
||||
pm_generic_runtime_suspend,
|
||||
pm_generic_runtime_resume,
|
||||
NULL
|
||||
)
|
||||
};
|
||||
|
||||
#define SDIO_PM_OPS_PTR (&sdio_bus_pm_ops)
|
||||
|
||||
#else /* !CONFIG_PM */
|
||||
|
||||
#define SDIO_PM_OPS_PTR NULL
|
||||
|
||||
#endif /* !CONFIG_PM */
|
||||
|
||||
static struct bus_type sdio_bus_type = {
|
||||
.name = "sdio",
|
||||
.dev_groups = sdio_dev_groups,
|
||||
.match = sdio_bus_match,
|
||||
.uevent = sdio_bus_uevent,
|
||||
.probe = sdio_bus_probe,
|
||||
.remove = sdio_bus_remove,
|
||||
.pm = SDIO_PM_OPS_PTR,
|
||||
};
|
||||
|
||||
int sdio_register_bus(void)
|
||||
{
|
||||
return bus_register(&sdio_bus_type);
|
||||
}
|
||||
|
||||
void sdio_unregister_bus(void)
|
||||
{
|
||||
bus_unregister(&sdio_bus_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_register_driver - register a function driver
|
||||
* @drv: SDIO function driver
|
||||
*/
|
||||
int sdio_register_driver(struct sdio_driver *drv)
|
||||
{
|
||||
drv->drv.name = drv->name;
|
||||
drv->drv.bus = &sdio_bus_type;
|
||||
return driver_register(&drv->drv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_register_driver);
|
||||
|
||||
/**
|
||||
* sdio_unregister_driver - unregister a function driver
|
||||
* @drv: SDIO function driver
|
||||
*/
|
||||
void sdio_unregister_driver(struct sdio_driver *drv)
|
||||
{
|
||||
drv->drv.bus = &sdio_bus_type;
|
||||
driver_unregister(&drv->drv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_unregister_driver);
|
||||
|
||||
static void sdio_release_func(struct device *dev)
|
||||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
/*
|
||||
* If this device is embedded then we never allocated
|
||||
* cis tables for this func
|
||||
*/
|
||||
if (!func->card->host->embedded_sdio_data.funcs)
|
||||
#endif
|
||||
sdio_free_func_cis(func);
|
||||
|
||||
kfree(func->info);
|
||||
|
||||
kfree(func);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialise a new SDIO function structure.
|
||||
*/
|
||||
struct sdio_func *sdio_alloc_func(struct mmc_card *card)
|
||||
{
|
||||
struct sdio_func *func;
|
||||
|
||||
func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
|
||||
if (!func)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
func->card = card;
|
||||
|
||||
device_initialize(&func->dev);
|
||||
|
||||
func->dev.parent = &card->dev;
|
||||
func->dev.bus = &sdio_bus_type;
|
||||
func->dev.release = sdio_release_func;
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static void sdio_acpi_set_handle(struct sdio_func *func)
|
||||
{
|
||||
struct mmc_host *host = func->card->host;
|
||||
u64 addr = (host->slotno << 16) | func->num;
|
||||
|
||||
acpi_preset_companion(&func->dev, ACPI_COMPANION(host->parent), addr);
|
||||
}
|
||||
#else
|
||||
static inline void sdio_acpi_set_handle(struct sdio_func *func) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Register a new SDIO function with the driver model.
|
||||
*/
|
||||
int sdio_add_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);
|
||||
|
||||
sdio_acpi_set_handle(func);
|
||||
ret = device_add(&func->dev);
|
||||
if (ret == 0) {
|
||||
sdio_func_set_present(func);
|
||||
dev_pm_domain_attach(&func->dev, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister a SDIO function with the driver model, and
|
||||
* (eventually) free it.
|
||||
* This function can be called through error paths where sdio_add_func() was
|
||||
* never executed (because a failure occurred at an earlier point).
|
||||
*/
|
||||
void sdio_remove_func(struct sdio_func *func)
|
||||
{
|
||||
if (!sdio_func_present(func))
|
||||
return;
|
||||
|
||||
dev_pm_domain_detach(&func->dev, false);
|
||||
device_del(&func->dev);
|
||||
put_device(&func->dev);
|
||||
}
|
||||
|
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
|
||||
|
412
drivers/mmc/core/sdio_cis.c
Normal file
412
drivers/mmc/core/sdio_cis.c
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sdio_cis.c
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 11, 2007
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* Copyright 2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_cis.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned i, nr_strings;
|
||||
char **buffer, *string;
|
||||
|
||||
/* Find all null-terminated (including zero length) strings in
|
||||
the TPLLV1_INFO field. Trailing garbage is ignored. */
|
||||
buf += 2;
|
||||
size -= 2;
|
||||
|
||||
nr_strings = 0;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (buf[i] == 0xff)
|
||||
break;
|
||||
if (buf[i] == 0)
|
||||
nr_strings++;
|
||||
}
|
||||
if (nr_strings == 0)
|
||||
return 0;
|
||||
|
||||
size = i;
|
||||
|
||||
buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
string = (char*)(buffer + nr_strings);
|
||||
|
||||
for (i = 0; i < nr_strings; i++) {
|
||||
buffer[i] = string;
|
||||
strcpy(string, buf);
|
||||
string += strlen(string) + 1;
|
||||
buf += strlen(buf) + 1;
|
||||
}
|
||||
|
||||
if (func) {
|
||||
func->num_info = nr_strings;
|
||||
func->info = (const char**)buffer;
|
||||
} else {
|
||||
card->num_info = nr_strings;
|
||||
card->info = (const char**)buffer;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned int vendor, device;
|
||||
|
||||
/* TPLMID_MANF */
|
||||
vendor = buf[0] | (buf[1] << 8);
|
||||
|
||||
/* TPLMID_CARD */
|
||||
device = buf[2] | (buf[3] << 8);
|
||||
|
||||
if (func) {
|
||||
func->vendor = vendor;
|
||||
func->device = device;
|
||||
} else {
|
||||
card->cis.vendor = vendor;
|
||||
card->cis.device = device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const unsigned char speed_val[16] =
|
||||
{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
|
||||
static const unsigned int speed_unit[8] =
|
||||
{ 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
|
||||
|
||||
|
||||
typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
|
||||
const unsigned char *, unsigned);
|
||||
|
||||
struct cis_tpl {
|
||||
unsigned char code;
|
||||
unsigned char min_size;
|
||||
tpl_parse_t *parse;
|
||||
};
|
||||
|
||||
static int cis_tpl_parse(struct mmc_card *card, struct sdio_func *func,
|
||||
const char *tpl_descr,
|
||||
const struct cis_tpl *tpl, int tpl_count,
|
||||
unsigned char code,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
/* look for a matching code in the table */
|
||||
for (i = 0; i < tpl_count; i++, tpl++) {
|
||||
if (tpl->code == code)
|
||||
break;
|
||||
}
|
||||
if (i < tpl_count) {
|
||||
if (size >= tpl->min_size) {
|
||||
if (tpl->parse)
|
||||
ret = tpl->parse(card, func, buf, size);
|
||||
else
|
||||
ret = -EILSEQ; /* known tuple, not parsed */
|
||||
} else {
|
||||
/* invalid tuple */
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret && ret != -EILSEQ && ret != -ENOENT) {
|
||||
pr_err("%s: bad %s tuple 0x%02x (%u bytes)\n",
|
||||
mmc_hostname(card->host), tpl_descr, code, size);
|
||||
}
|
||||
} else {
|
||||
/* unknown tuple */
|
||||
ret = -ENOENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cistpl_funce_common(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
/* Only valid for the common CIS (function 0) */
|
||||
if (func)
|
||||
return -EINVAL;
|
||||
|
||||
/* TPLFE_FN0_BLK_SIZE */
|
||||
card->cis.blksize = buf[1] | (buf[2] << 8);
|
||||
|
||||
/* TPLFE_MAX_TRAN_SPEED */
|
||||
card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] *
|
||||
speed_unit[buf[3] & 7];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cistpl_funce_func(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
unsigned vsn;
|
||||
unsigned min_size;
|
||||
|
||||
/* Only valid for the individual function's CIS (1-7) */
|
||||
if (!func)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* This tuple has a different length depending on the SDIO spec
|
||||
* version.
|
||||
*/
|
||||
vsn = func->card->cccr.sdio_vsn;
|
||||
min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
|
||||
|
||||
if (size < min_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* TPLFE_MAX_BLK_SIZE */
|
||||
func->max_blksize = buf[12] | (buf[13] << 8);
|
||||
|
||||
/* TPLFE_ENABLE_TIMEOUT_VAL, present in ver 1.1 and above */
|
||||
if (vsn > SDIO_SDIO_REV_1_00)
|
||||
func->enable_timeout = (buf[28] | (buf[29] << 8)) * 10;
|
||||
else
|
||||
func->enable_timeout = jiffies_to_msecs(HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Known TPLFE_TYPEs table for CISTPL_FUNCE tuples.
|
||||
*
|
||||
* Note that, unlike PCMCIA, CISTPL_FUNCE tuples are not parsed depending
|
||||
* on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO
|
||||
* TPLFID_FUNCTION is always hardcoded to 0x0C.
|
||||
*/
|
||||
static const struct cis_tpl cis_tpl_funce_list[] = {
|
||||
{ 0x00, 4, cistpl_funce_common },
|
||||
{ 0x01, 0, cistpl_funce_func },
|
||||
{ 0x04, 1+1+6, /* CISTPL_FUNCE_LAN_NODE_ID */ },
|
||||
};
|
||||
|
||||
static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
|
||||
const unsigned char *buf, unsigned size)
|
||||
{
|
||||
if (size < 1)
|
||||
return -EINVAL;
|
||||
|
||||
return cis_tpl_parse(card, func, "CISTPL_FUNCE",
|
||||
cis_tpl_funce_list,
|
||||
ARRAY_SIZE(cis_tpl_funce_list),
|
||||
buf[0], buf, size);
|
||||
}
|
||||
|
||||
/* Known TPL_CODEs table for CIS tuples */
|
||||
static const struct cis_tpl cis_tpl_list[] = {
|
||||
{ 0x15, 3, cistpl_vers_1 },
|
||||
{ 0x20, 4, cistpl_manfid },
|
||||
{ 0x21, 2, /* cistpl_funcid */ },
|
||||
{ 0x22, 0, cistpl_funce },
|
||||
};
|
||||
|
||||
static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func_tuple *this, **prev;
|
||||
unsigned i, ptr = 0;
|
||||
|
||||
/*
|
||||
* Note that this works for the common CIS (function number 0) as
|
||||
* well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS
|
||||
* have the same offset.
|
||||
*/
|
||||
for (i = 0; i < 3; i++) {
|
||||
unsigned char x, fn;
|
||||
|
||||
if (func)
|
||||
fn = func->num;
|
||||
else
|
||||
fn = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0,
|
||||
SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x);
|
||||
if (ret)
|
||||
return ret;
|
||||
ptr |= x << (i * 8);
|
||||
}
|
||||
|
||||
if (func)
|
||||
prev = &func->tuples;
|
||||
else
|
||||
prev = &card->tuples;
|
||||
|
||||
BUG_ON(*prev);
|
||||
|
||||
do {
|
||||
unsigned char tpl_code, tpl_link;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* 0xff means we're done */
|
||||
if (tpl_code == 0xff)
|
||||
break;
|
||||
|
||||
/* null entries have no link field or data */
|
||||
if (tpl_code == 0x00)
|
||||
continue;
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* a size of 0xff also means we're done */
|
||||
if (tpl_link == 0xff)
|
||||
break;
|
||||
|
||||
this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL);
|
||||
if (!this)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < tpl_link; i++) {
|
||||
ret = mmc_io_rw_direct(card, 0, 0,
|
||||
ptr + i, 0, &this->data[i]);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
if (ret) {
|
||||
kfree(this);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Try to parse the CIS tuple */
|
||||
ret = cis_tpl_parse(card, func, "CIS",
|
||||
cis_tpl_list, ARRAY_SIZE(cis_tpl_list),
|
||||
tpl_code, this->data, tpl_link);
|
||||
if (ret == -EILSEQ || ret == -ENOENT) {
|
||||
/*
|
||||
* The tuple is unknown or known but not parsed.
|
||||
* Queue the tuple for the function driver.
|
||||
*/
|
||||
this->next = NULL;
|
||||
this->code = tpl_code;
|
||||
this->size = tpl_link;
|
||||
*prev = this;
|
||||
prev = &this->next;
|
||||
|
||||
if (ret == -ENOENT) {
|
||||
/* warn about unknown tuples */
|
||||
pr_warn_ratelimited("%s: queuing unknown"
|
||||
" CIS tuple 0x%02x (%u bytes)\n",
|
||||
mmc_hostname(card->host),
|
||||
tpl_code, tpl_link);
|
||||
}
|
||||
|
||||
/* keep on analyzing tuples */
|
||||
ret = 0;
|
||||
} else {
|
||||
/*
|
||||
* We don't need the tuple anymore if it was
|
||||
* successfully parsed by the SDIO core or if it is
|
||||
* not going to be queued for a driver.
|
||||
*/
|
||||
kfree(this);
|
||||
}
|
||||
|
||||
ptr += tpl_link;
|
||||
} while (!ret);
|
||||
|
||||
/*
|
||||
* Link in all unknown tuples found in the common CIS so that
|
||||
* drivers don't have to go digging in two places.
|
||||
*/
|
||||
if (func)
|
||||
*prev = card->tuples;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sdio_read_common_cis(struct mmc_card *card)
|
||||
{
|
||||
return sdio_read_cis(card, NULL);
|
||||
}
|
||||
|
||||
void sdio_free_common_cis(struct mmc_card *card)
|
||||
{
|
||||
struct sdio_func_tuple *tuple, *victim;
|
||||
|
||||
tuple = card->tuples;
|
||||
|
||||
while (tuple) {
|
||||
victim = tuple;
|
||||
tuple = tuple->next;
|
||||
kfree(victim);
|
||||
}
|
||||
|
||||
card->tuples = NULL;
|
||||
}
|
||||
|
||||
int sdio_read_func_cis(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdio_read_cis(func->card, func);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Since we've linked to tuples in the card structure,
|
||||
* we must make sure we have a reference to it.
|
||||
*/
|
||||
get_device(&func->card->dev);
|
||||
|
||||
/*
|
||||
* Vendor/device id is optional for function CIS, so
|
||||
* copy it from the card structure as needed.
|
||||
*/
|
||||
if (func->vendor == 0) {
|
||||
func->vendor = func->card->cis.vendor;
|
||||
func->device = func->card->cis.device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sdio_free_func_cis(struct sdio_func *func)
|
||||
{
|
||||
struct sdio_func_tuple *tuple, *victim;
|
||||
|
||||
tuple = func->tuples;
|
||||
|
||||
while (tuple && tuple != func->card->tuples) {
|
||||
victim = tuple;
|
||||
tuple = tuple->next;
|
||||
kfree(victim);
|
||||
}
|
||||
|
||||
func->tuples = NULL;
|
||||
|
||||
/*
|
||||
* We have now removed the link to the tuples in the
|
||||
* card structure, so remove the reference.
|
||||
*/
|
||||
put_device(&func->card->dev);
|
||||
}
|
||||
|
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
|
755
drivers/mmc/core/sdio_io.c
Executable file
755
drivers/mmc/core/sdio_io.c
Executable file
|
@ -0,0 +1,755 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sdio_io.c
|
||||
*
|
||||
* Copyright 2007-2008 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_ops.h"
|
||||
|
||||
/**
|
||||
* sdio_claim_host - exclusively claim a bus for a certain SDIO function
|
||||
* @func: SDIO function that will be accessed
|
||||
*
|
||||
* Claim a bus for a set of operations. The SDIO function given
|
||||
* is used to figure out which bus is relevant.
|
||||
*/
|
||||
void sdio_claim_host(struct sdio_func *func)
|
||||
{
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
mmc_claim_host(func->card->host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_claim_host);
|
||||
|
||||
/**
|
||||
* sdio_release_host - release a bus for a certain SDIO function
|
||||
* @func: SDIO function that was accessed
|
||||
*
|
||||
* Release a bus, allowing others to claim the bus for their
|
||||
* operations.
|
||||
*/
|
||||
void sdio_release_host(struct sdio_func *func)
|
||||
{
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
mmc_release_host(func->card->host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_release_host);
|
||||
|
||||
/**
|
||||
* sdio_enable_func - enables a SDIO function for usage
|
||||
* @func: SDIO function to enable
|
||||
*
|
||||
* Powers up and activates a SDIO function so that register
|
||||
* access is possible.
|
||||
*/
|
||||
int sdio_enable_func(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
unsigned long timeout;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func));
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
reg |= 1 << func->num;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(func->enable_timeout);
|
||||
|
||||
while (1) {
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®);
|
||||
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(func->max_blksize, func->card->host->max_blk_size);
|
||||
blksz = min(blksz, 512u);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
|
||||
blksz & 0xff, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0,
|
||||
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
|
||||
(blksz >> 8) & 0xff, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
func->cur_blksize = blksz;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_set_block_size);
|
||||
|
||||
/*
|
||||
* Calculate the maximum byte mode transfer size
|
||||
*/
|
||||
static inline unsigned int sdio_max_byte_size(struct sdio_func *func)
|
||||
{
|
||||
unsigned mval = func->card->host->max_blk_size;
|
||||
|
||||
if (mmc_blksz_for_byte_mode(func->card))
|
||||
mval = min(mval, func->cur_blksize);
|
||||
else
|
||||
mval = min(mval, func->max_blksize);
|
||||
|
||||
if (mmc_card_broken_byte_mode_512(func->card))
|
||||
return min(mval, 511u);
|
||||
|
||||
return min(mval, 512u); /* maximum size for byte mode */
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_align_size - pads a transfer size to a more optimal value
|
||||
* @func: SDIO function
|
||||
* @sz: original transfer size
|
||||
*
|
||||
* Pads the original data size with a number of extra bytes in
|
||||
* order to avoid controller bugs and/or performance hits
|
||||
* (e.g. some controllers revert to PIO for certain sizes).
|
||||
*
|
||||
* If possible, it will also adjust the size so that it can be
|
||||
* handled in just a single request.
|
||||
*
|
||||
* Returns the improved size, which might be unmodified.
|
||||
*/
|
||||
unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz)
|
||||
{
|
||||
unsigned int orig_sz;
|
||||
unsigned int blk_sz, byte_sz;
|
||||
unsigned chunk_sz;
|
||||
|
||||
orig_sz = sz;
|
||||
|
||||
/*
|
||||
* Do a first check with the controller, in case it
|
||||
* wants to increase the size up to a point where it
|
||||
* might need more than one block.
|
||||
*/
|
||||
sz = mmc_align_data_size(func->card, sz);
|
||||
|
||||
/*
|
||||
* If we can still do this with just a byte transfer, then
|
||||
* we're done.
|
||||
*/
|
||||
if (sz <= sdio_max_byte_size(func))
|
||||
return sz;
|
||||
|
||||
if (func->card->cccr.multi_block) {
|
||||
/*
|
||||
* Check if the transfer is already block aligned
|
||||
*/
|
||||
if ((sz % func->cur_blksize) == 0)
|
||||
return sz;
|
||||
|
||||
/*
|
||||
* Realign it so that it can be done with one request,
|
||||
* and recheck if the controller still likes it.
|
||||
*/
|
||||
blk_sz = ((sz + func->cur_blksize - 1) /
|
||||
func->cur_blksize) * func->cur_blksize;
|
||||
blk_sz = mmc_align_data_size(func->card, blk_sz);
|
||||
|
||||
/*
|
||||
* This value is only good if it is still just
|
||||
* one request.
|
||||
*/
|
||||
if ((blk_sz % func->cur_blksize) == 0)
|
||||
return blk_sz;
|
||||
|
||||
/*
|
||||
* We failed to do one request, but at least try to
|
||||
* pad the remainder properly.
|
||||
*/
|
||||
byte_sz = mmc_align_data_size(func->card,
|
||||
sz % func->cur_blksize);
|
||||
if (byte_sz <= sdio_max_byte_size(func)) {
|
||||
blk_sz = sz / func->cur_blksize;
|
||||
return blk_sz * func->cur_blksize + byte_sz;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We need multiple requests, so first check that the
|
||||
* controller can handle the chunk size;
|
||||
*/
|
||||
chunk_sz = mmc_align_data_size(func->card,
|
||||
sdio_max_byte_size(func));
|
||||
if (chunk_sz == sdio_max_byte_size(func)) {
|
||||
/*
|
||||
* Fix up the size of the remainder (if any)
|
||||
*/
|
||||
byte_sz = orig_sz % chunk_sz;
|
||||
if (byte_sz) {
|
||||
byte_sz = mmc_align_data_size(func->card,
|
||||
byte_sz);
|
||||
}
|
||||
|
||||
return (orig_sz / chunk_sz) * chunk_sz + byte_sz;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The controller is simply incapable of transferring the size
|
||||
* we want in decent manner, so just return the original size.
|
||||
*/
|
||||
return orig_sz;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_align_size);
|
||||
|
||||
/* Split an arbitrarily sized data transfer into several
|
||||
* IO_RW_EXTENDED commands. */
|
||||
static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned size)
|
||||
{
|
||||
unsigned remainder = size;
|
||||
unsigned max_blocks;
|
||||
int ret;
|
||||
|
||||
/* Do the bulk of the transfer using block mode (if supported). */
|
||||
if (func->card->cccr.multi_block && (size > sdio_max_byte_size(func))) {
|
||||
/* Blocks per command is limited by host count, host transfer
|
||||
* size and the maximum for IO_RW_EXTENDED of 511 blocks. */
|
||||
max_blocks = min(func->card->host->max_blk_count, 511u);
|
||||
|
||||
while (remainder >= func->cur_blksize) {
|
||||
unsigned blocks;
|
||||
|
||||
blocks = remainder / func->cur_blksize;
|
||||
if (blocks > max_blocks)
|
||||
blocks = max_blocks;
|
||||
size = blocks * func->cur_blksize;
|
||||
|
||||
ret = mmc_io_rw_extended(func->card, write,
|
||||
func->num, addr, incr_addr, buf,
|
||||
blocks, func->cur_blksize);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
remainder -= size;
|
||||
buf += size;
|
||||
if (incr_addr)
|
||||
addr += size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the remainder using byte mode. */
|
||||
while (remainder > 0) {
|
||||
size = min(remainder, sdio_max_byte_size(func));
|
||||
|
||||
/* Indicate byte mode by setting "blocks" = 0 */
|
||||
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
|
||||
incr_addr, buf, 0, size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
remainder -= size;
|
||||
buf += size;
|
||||
if (incr_addr)
|
||||
addr += size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_readb - read a single byte from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a single byte from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb);
|
||||
|
||||
/**
|
||||
* sdio_readb_ext - read a single byte from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
* @in: value to add to argument
|
||||
*
|
||||
* Reads a single byte from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned char sdio_readb_ext(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret, unsigned in)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, (u8)in, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb_ext);
|
||||
|
||||
/**
|
||||
* sdio_writeb - write a single byte to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: byte to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a single byte to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writeb);
|
||||
|
||||
/**
|
||||
* sdio_writeb_readb - write and read a byte from SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @write_byte: byte to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Performs a RAW (Read after Write) operation as defined by SDIO spec -
|
||||
* single byte is written to address space of a given SDIO function and
|
||||
* response is read back from the same address, both using single request.
|
||||
* If there is a problem with the operation, 0xff is returned and
|
||||
* @err_ret will contain the error code.
|
||||
*/
|
||||
u8 sdio_writeb_readb(struct sdio_func *func, u8 write_byte,
|
||||
unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, func->num, addr,
|
||||
write_byte, &val);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
if (ret)
|
||||
val = 0xff;
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writeb_readb);
|
||||
|
||||
/**
|
||||
* sdio_memcpy_fromio - read a chunk of memory from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @dst: buffer to store the data
|
||||
* @addr: address to begin reading from
|
||||
* @count: number of bytes to read
|
||||
*
|
||||
* Reads from the address space of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_memcpy_fromio(struct sdio_func *func, void *dst,
|
||||
unsigned int addr, int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_memcpy_fromio);
|
||||
|
||||
/**
|
||||
* sdio_memcpy_toio - write a chunk of memory to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to start writing to
|
||||
* @src: buffer that contains the data to write
|
||||
* @count: number of bytes to write
|
||||
*
|
||||
* Writes to the address space of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr,
|
||||
void *src, int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_memcpy_toio);
|
||||
|
||||
/**
|
||||
* sdio_readsb - read from a FIFO on a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @dst: buffer to store the data
|
||||
* @addr: address of (single byte) FIFO
|
||||
* @count: number of bytes to read
|
||||
*
|
||||
* Reads from the specified FIFO of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
|
||||
int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readsb);
|
||||
|
||||
/**
|
||||
* sdio_writesb - write to a FIFO of a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address of (single byte) FIFO
|
||||
* @src: buffer that contains the data to write
|
||||
* @count: number of bytes to write
|
||||
*
|
||||
* Writes to the specified FIFO of a given SDIO function. Return
|
||||
* value indicates if the transfer succeeded or not.
|
||||
*/
|
||||
int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src,
|
||||
int count)
|
||||
{
|
||||
return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writesb);
|
||||
|
||||
/**
|
||||
* sdio_readw - read a 16 bit integer from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a 16 bit integer from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xffff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
return le16_to_cpup((__le16 *)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readw);
|
||||
|
||||
/**
|
||||
* sdio_writew - write a 16 bit integer to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: integer to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a 16 bit integer to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writew(struct sdio_func *func, u16 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(__le16 *)func->tmpbuf = cpu_to_le16(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writew);
|
||||
|
||||
/**
|
||||
* sdio_readl - read a 32 bit integer from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a 32 bit integer from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address,
|
||||
* 0xffffffff is returned and @err_ret will contain the error
|
||||
* code.
|
||||
*/
|
||||
u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
return le32_to_cpup((__le32 *)func->tmpbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readl);
|
||||
|
||||
/**
|
||||
* sdio_writel - write a 32 bit integer to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @b: integer to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a 32 bit integer to the address space of a given SDIO
|
||||
* function. @err_ret will contain the status of the actual
|
||||
* transfer.
|
||||
*/
|
||||
void sdio_writel(struct sdio_func *func, u32 b, unsigned int addr, int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*(__le32 *)func->tmpbuf = cpu_to_le32(b);
|
||||
|
||||
ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_writel);
|
||||
|
||||
/**
|
||||
* sdio_f0_readb - read a single byte from SDIO function 0
|
||||
* @func: an SDIO function of the card
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Reads a single byte from the address space of SDIO function 0.
|
||||
* If there is a problem reading the address, 0xff is returned
|
||||
* and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_f0_readb);
|
||||
|
||||
/**
|
||||
* sdio_f0_writeb - write a single byte to SDIO function 0
|
||||
* @func: an SDIO function of the card
|
||||
* @b: byte to write
|
||||
* @addr: address to write to
|
||||
* @err_ret: optional status value from transfer
|
||||
*
|
||||
* Writes a single byte to the address space of SDIO function 0.
|
||||
* @err_ret will contain the status of the actual transfer.
|
||||
*
|
||||
* Only writes to the vendor specific CCCR registers (0xF0 -
|
||||
* 0xFF) are permiited; @err_ret will be set to -EINVAL for *
|
||||
* writes outside this range.
|
||||
*/
|
||||
void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
|
||||
int *err_ret)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if ((addr < 0xF0 || addr > 0xFF) && (!mmc_card_lenient_fn0(func->card))) {
|
||||
if (err_ret)
|
||||
*err_ret = -EINVAL;
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL);
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_f0_writeb);
|
||||
|
||||
/**
|
||||
* sdio_get_host_pm_caps - get host power management capabilities
|
||||
* @func: SDIO function attached to host
|
||||
*
|
||||
* Returns a capability bitmask corresponding to power management
|
||||
* features supported by the host controller that the card function
|
||||
* might rely upon during a system suspend. The host doesn't need
|
||||
* to be claimed, nor the function active, for this information to be
|
||||
* obtained.
|
||||
*/
|
||||
mmc_pm_flag_t sdio_get_host_pm_caps(struct sdio_func *func)
|
||||
{
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
return func->card->host->pm_caps;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_get_host_pm_caps);
|
||||
|
||||
/**
|
||||
* sdio_set_host_pm_flags - set wanted host power management capabilities
|
||||
* @func: SDIO function attached to host
|
||||
*
|
||||
* Set a capability bitmask corresponding to wanted host controller
|
||||
* power management features for the upcoming suspend state.
|
||||
* This must be called, if needed, each time the suspend method of
|
||||
* the function driver is called, and must contain only bits that
|
||||
* were returned by sdio_get_host_pm_caps().
|
||||
* The host doesn't need to be claimed, nor the function active,
|
||||
* for this information to be set.
|
||||
*/
|
||||
int sdio_set_host_pm_flags(struct sdio_func *func, mmc_pm_flag_t flags)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
host = func->card->host;
|
||||
|
||||
if (flags & ~host->pm_caps)
|
||||
return -EINVAL;
|
||||
|
||||
/* function suspend methods are serialized, hence no lock needed */
|
||||
host->pm_flags |= flags;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_set_host_pm_flags);
|
345
drivers/mmc/core/sdio_irq.c
Normal file
345
drivers/mmc/core/sdio_irq.c
Normal file
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* linux/drivers/mmc/core/sdio_irq.c
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: June 18, 2007
|
||||
* Copyright: MontaVista Software Inc.
|
||||
*
|
||||
* Copyright 2008 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/mmc/core.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include "sdio_ops.h"
|
||||
|
||||
static int process_sdio_pending_irqs(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card = host->card;
|
||||
int i, ret, count;
|
||||
unsigned char pending;
|
||||
struct sdio_func *func;
|
||||
|
||||
/*
|
||||
* Optimization, if there is only 1 function interrupt registered
|
||||
* and we know an IRQ was signaled then call irq handler directly.
|
||||
* Otherwise do the full probe.
|
||||
*/
|
||||
func = card->sdio_single_irq;
|
||||
if (func && host->sdio_irq_pending) {
|
||||
func->irq_handler(func);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
|
||||
if (ret) {
|
||||
pr_debug("%s: error %d reading SDIO_CCCR_INTx\n",
|
||||
mmc_card_id(card), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pending && mmc_card_broken_irq_polling(card) &&
|
||||
!(host->caps & MMC_CAP_SDIO_IRQ)) {
|
||||
unsigned char dummy;
|
||||
|
||||
/* A fake interrupt could be created when we poll SDIO_CCCR_INTx
|
||||
* register with a Marvell SD8797 card. A dummy CMD52 read to
|
||||
* function 0 register 0xff can avoid this.
|
||||
*/
|
||||
mmc_io_rw_direct(card, 0, 0, 0xff, 0, &dummy);
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (i = 1; i <= 7; i++) {
|
||||
if (pending & (1 << i)) {
|
||||
func = card->sdio_func[i - 1];
|
||||
if (!func) {
|
||||
pr_warn("%s: pending IRQ for non-existent function\n",
|
||||
mmc_card_id(card));
|
||||
ret = -EINVAL;
|
||||
} else if (func->irq_handler) {
|
||||
func->irq_handler(func);
|
||||
count++;
|
||||
} else {
|
||||
pr_warn("%s: pending IRQ with no handler\n",
|
||||
sdio_func_id(func));
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count)
|
||||
return count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void sdio_run_irqs(struct mmc_host *host)
|
||||
{
|
||||
mmc_claim_host(host);
|
||||
host->sdio_irq_pending = true;
|
||||
process_sdio_pending_irqs(host);
|
||||
mmc_release_host(host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_run_irqs);
|
||||
|
||||
static int sdio_irq_thread(void *_host)
|
||||
{
|
||||
struct mmc_host *host = _host;
|
||||
struct sched_param param = { .sched_priority = 1 };
|
||||
unsigned long period, idle_period;
|
||||
int ret;
|
||||
|
||||
sched_setscheduler(current, SCHED_FIFO, ¶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);
|
||||
host->sdio_irq_pending = false;
|
||||
mmc_release_host(host);
|
||||
|
||||
/*
|
||||
* Give other threads a chance to run in the presence of
|
||||
* errors.
|
||||
*/
|
||||
if (ret < 0) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (!kthread_should_stop())
|
||||
schedule_timeout(HZ);
|
||||
set_current_state(TASK_RUNNING);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adaptive polling frequency based on the assumption
|
||||
* that an interrupt will be closely followed by more.
|
||||
* This has a substantial benefit for network devices.
|
||||
*/
|
||||
if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
|
||||
if (ret > 0)
|
||||
period /= 2;
|
||||
else {
|
||||
period++;
|
||||
if (period > idle_period)
|
||||
period = idle_period;
|
||||
}
|
||||
}
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ) {
|
||||
mmc_host_clk_hold(host);
|
||||
host->ops->enable_sdio_irq(host, 1);
|
||||
mmc_host_clk_release(host);
|
||||
}
|
||||
if (!kthread_should_stop())
|
||||
schedule_timeout(period);
|
||||
set_current_state(TASK_RUNNING);
|
||||
} while (!kthread_should_stop());
|
||||
|
||||
if (host->caps & MMC_CAP_SDIO_IRQ) {
|
||||
mmc_host_clk_hold(host);
|
||||
host->ops->enable_sdio_irq(host, 0);
|
||||
mmc_host_clk_release(host);
|
||||
}
|
||||
|
||||
pr_debug("%s: IRQ thread exiting with code %d\n",
|
||||
mmc_hostname(host), ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdio_card_irq_get(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
|
||||
if (!host->sdio_irqs++) {
|
||||
if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 0);
|
||||
host->sdio_irq_thread =
|
||||
kthread_run(sdio_irq_thread, host,
|
||||
"ksdioirqd/%s", mmc_hostname(host));
|
||||
if (IS_ERR(host->sdio_irq_thread)) {
|
||||
int err = PTR_ERR(host->sdio_irq_thread);
|
||||
host->sdio_irqs--;
|
||||
return err;
|
||||
}
|
||||
} else if (host->caps & MMC_CAP_SDIO_IRQ) {
|
||||
mmc_host_clk_hold(host);
|
||||
host->ops->enable_sdio_irq(host, 1);
|
||||
mmc_host_clk_release(host);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdio_card_irq_put(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
WARN_ON(!host->claimed);
|
||||
BUG_ON(host->sdio_irqs < 1);
|
||||
|
||||
if (!--host->sdio_irqs) {
|
||||
if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
|
||||
atomic_set(&host->sdio_irq_thread_abort, 1);
|
||||
kthread_stop(host->sdio_irq_thread);
|
||||
} else if (host->caps & MMC_CAP_SDIO_IRQ) {
|
||||
mmc_host_clk_hold(host);
|
||||
host->ops->enable_sdio_irq(host, 0);
|
||||
mmc_host_clk_release(host);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If there is only 1 function registered set sdio_single_irq */
|
||||
static void sdio_single_irq_set(struct mmc_card *card)
|
||||
{
|
||||
struct sdio_func *func;
|
||||
int i;
|
||||
|
||||
card->sdio_single_irq = NULL;
|
||||
if ((card->host->caps & MMC_CAP_SDIO_IRQ) &&
|
||||
card->host->sdio_irqs == 1)
|
||||
for (i = 0; i < card->sdio_funcs; i++) {
|
||||
func = card->sdio_func[i];
|
||||
if (func && func->irq_handler) {
|
||||
card->sdio_single_irq = func;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sdio_claim_irq - claim the IRQ for a SDIO function
|
||||
* @func: SDIO function
|
||||
* @handler: IRQ handler callback
|
||||
*
|
||||
* Claim and activate the IRQ for the given SDIO function. The provided
|
||||
* handler will be called when that IRQ is asserted. The host is always
|
||||
* claimed already when the handler is called so the handler must not
|
||||
* call sdio_claim_host() nor sdio_release_host().
|
||||
*/
|
||||
int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
|
||||
|
||||
if (func->irq_handler) {
|
||||
pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg |= 1 << func->num;
|
||||
|
||||
reg |= 1; /* Master interrupt enable */
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
func->irq_handler = handler;
|
||||
ret = sdio_card_irq_get(func->card);
|
||||
if (ret)
|
||||
func->irq_handler = NULL;
|
||||
sdio_single_irq_set(func->card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_claim_irq);
|
||||
|
||||
/**
|
||||
* sdio_release_irq - release the IRQ for a SDIO function
|
||||
* @func: SDIO function
|
||||
*
|
||||
* Disable and release the IRQ for the given SDIO function.
|
||||
*/
|
||||
int sdio_release_irq(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
unsigned char reg;
|
||||
|
||||
BUG_ON(!func);
|
||||
BUG_ON(!func->card);
|
||||
|
||||
pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
|
||||
|
||||
if (func->irq_handler) {
|
||||
func->irq_handler = NULL;
|
||||
sdio_card_irq_put(func->card);
|
||||
sdio_single_irq_set(func->card);
|
||||
}
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg &= ~(1 << func->num);
|
||||
|
||||
/* Disable master interrupt with the last function interrupt */
|
||||
if (!(reg & 0xFE))
|
||||
reg = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_release_irq);
|
||||
|
223
drivers/mmc/core/sdio_ops.c
Normal file
223
drivers/mmc/core/sdio_ops.c
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* linux/drivers/mmc/sdio_ops.c
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int i, err = 0;
|
||||
|
||||
BUG_ON(!host);
|
||||
|
||||
cmd.opcode = SD_IO_SEND_OP_COND;
|
||||
cmd.arg = ocr;
|
||||
cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR;
|
||||
|
||||
for (i = 100; i; i--) {
|
||||
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
/* if we're just probing, do a single pass */
|
||||
if (ocr == 0)
|
||||
break;
|
||||
|
||||
/* otherwise wait until reset completes */
|
||||
if (mmc_host_is_spi(host)) {
|
||||
/*
|
||||
* Both R1_SPI_IDLE and MMC_CARD_BUSY indicate
|
||||
* an initialized card under SPI, but some cards
|
||||
* (Marvell's) only behave when looking at this
|
||||
* one.
|
||||
*/
|
||||
if (cmd.resp[1] & MMC_CARD_BUSY)
|
||||
break;
|
||||
} else {
|
||||
if (cmd.resp[0] & MMC_CARD_BUSY)
|
||||
break;
|
||||
}
|
||||
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
mmc_delay(10);
|
||||
}
|
||||
|
||||
if (rocr)
|
||||
*rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8 *out)
|
||||
{
|
||||
struct mmc_command cmd = {0};
|
||||
int err;
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(fn > 7);
|
||||
|
||||
/* sanity check */
|
||||
if (addr & ~0x1FFFF)
|
||||
return -EINVAL;
|
||||
|
||||
cmd.opcode = SD_IO_RW_DIRECT;
|
||||
cmd.arg = write ? 0x80000000 : 0x00000000;
|
||||
cmd.arg |= fn << 28;
|
||||
cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
|
||||
cmd.arg |= addr << 9;
|
||||
cmd.arg |= in;
|
||||
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
|
||||
|
||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (mmc_host_is_spi(host)) {
|
||||
/* host driver already reported errors */
|
||||
} else {
|
||||
if (cmd.resp[0] & R5_ERROR)
|
||||
return -EIO;
|
||||
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
|
||||
return -EINVAL;
|
||||
if (cmd.resp[0] & R5_OUT_OF_RANGE)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
if (mmc_host_is_spi(host))
|
||||
*out = (cmd.resp[0] >> 8) & 0xFF;
|
||||
else
|
||||
*out = cmd.resp[0] & 0xFF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8 *out)
|
||||
{
|
||||
BUG_ON(!card);
|
||||
return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out);
|
||||
}
|
||||
|
||||
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
|
||||
{
|
||||
struct mmc_request mrq = {NULL};
|
||||
struct mmc_command cmd = {0};
|
||||
struct mmc_data data = {0};
|
||||
struct scatterlist sg, *sg_ptr;
|
||||
struct sg_table sgtable;
|
||||
unsigned int nents, left_size, i;
|
||||
unsigned int seg_size = card->host->max_seg_size;
|
||||
|
||||
BUG_ON(!card);
|
||||
BUG_ON(fn > 7);
|
||||
WARN_ON(blksz == 0);
|
||||
|
||||
/* sanity check */
|
||||
if (addr & ~0x1FFFF)
|
||||
return -EINVAL;
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
mrq.data = &data;
|
||||
|
||||
cmd.opcode = SD_IO_RW_EXTENDED;
|
||||
cmd.arg = write ? 0x80000000 : 0x00000000;
|
||||
cmd.arg |= fn << 28;
|
||||
cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
|
||||
cmd.arg |= addr << 9;
|
||||
if (blocks == 0)
|
||||
cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
|
||||
else
|
||||
cmd.arg |= 0x08000000 | blocks; /* block mode */
|
||||
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
|
||||
|
||||
data.blksz = blksz;
|
||||
/* Code in host drivers/fwk assumes that "blocks" always is >=1 */
|
||||
data.blocks = blocks ? blocks : 1;
|
||||
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
|
||||
|
||||
left_size = data.blksz * data.blocks;
|
||||
nents = (left_size - 1) / seg_size + 1;
|
||||
if (nents > 1) {
|
||||
if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
|
||||
data.sg = sgtable.sgl;
|
||||
data.sg_len = nents;
|
||||
|
||||
for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
|
||||
sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)),
|
||||
min(seg_size, left_size),
|
||||
offset_in_page(buf + (i * seg_size)));
|
||||
left_size = left_size - seg_size;
|
||||
}
|
||||
} else {
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
|
||||
sg_init_one(&sg, buf, left_size);
|
||||
}
|
||||
|
||||
mmc_set_data_timeout(&data, card);
|
||||
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
|
||||
if (nents > 1)
|
||||
sg_free_table(&sgtable);
|
||||
|
||||
if (cmd.error)
|
||||
return cmd.error;
|
||||
if (data.error)
|
||||
return data.error;
|
||||
|
||||
if (mmc_host_is_spi(card->host)) {
|
||||
/* host driver already reported errors */
|
||||
} else {
|
||||
if (cmd.resp[0] & R5_ERROR)
|
||||
return -EIO;
|
||||
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
|
||||
return -EINVAL;
|
||||
if (cmd.resp[0] & R5_OUT_OF_RANGE)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdio_reset(struct mmc_host *host)
|
||||
{
|
||||
int ret;
|
||||
u8 abort;
|
||||
|
||||
/* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */
|
||||
|
||||
ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort);
|
||||
if (ret)
|
||||
abort = 0x08;
|
||||
else
|
||||
abort |= 0x08;
|
||||
|
||||
ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
23
drivers/mmc/core/sdio_ops.h
Normal file
23
drivers/mmc/core/sdio_ops.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* linux/drivers/mmc/sdio_ops.c
|
||||
*
|
||||
* Copyright 2006-2007 Pierre Ossman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _MMC_SDIO_OPS_H
|
||||
#define _MMC_SDIO_OPS_H
|
||||
|
||||
int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
||||
int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, u8 in, u8* out);
|
||||
int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
|
||||
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
|
||||
int sdio_reset(struct mmc_host *host);
|
||||
|
||||
#endif
|
||||
|
407
drivers/mmc/core/slot-gpio.c
Normal file
407
drivers/mmc/core/slot-gpio.c
Normal file
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* Generic GPIO card-detect helper
|
||||
*
|
||||
* Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/slot-gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct mmc_gpio {
|
||||
struct gpio_desc *ro_gpio;
|
||||
struct gpio_desc *cd_gpio;
|
||||
bool override_ro_active_level;
|
||||
bool override_cd_active_level;
|
||||
char *ro_label;
|
||||
char cd_label[0];
|
||||
};
|
||||
|
||||
static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
|
||||
{
|
||||
/* Schedule a card detection after a debounce timeout */
|
||||
struct mmc_host *host = dev_id;
|
||||
|
||||
host->trigger_card_event = true;
|
||||
mmc_detect_change(host, msecs_to_jiffies(200));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mmc_gpio_alloc(struct mmc_host *host)
|
||||
{
|
||||
size_t len = strlen(dev_name(host->parent)) + 4;
|
||||
struct mmc_gpio *ctx;
|
||||
|
||||
mutex_lock(&host->slot.lock);
|
||||
|
||||
ctx = host->slot.handler_priv;
|
||||
if (!ctx) {
|
||||
/*
|
||||
* devm_kzalloc() can be called after device_initialize(), even
|
||||
* before device_add(), i.e., between mmc_alloc_host() and
|
||||
* mmc_add_host()
|
||||
*/
|
||||
ctx = devm_kzalloc(&host->class_dev, sizeof(*ctx) + 2 * len,
|
||||
GFP_KERNEL);
|
||||
if (ctx) {
|
||||
ctx->ro_label = ctx->cd_label + len;
|
||||
snprintf(ctx->cd_label, len, "%s cd", dev_name(host->parent));
|
||||
snprintf(ctx->ro_label, len, "%s ro", dev_name(host->parent));
|
||||
host->slot.handler_priv = ctx;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&host->slot.lock);
|
||||
|
||||
return ctx ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
int mmc_gpio_get_ro(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
|
||||
if (!ctx || !ctx->ro_gpio)
|
||||
return -ENOSYS;
|
||||
|
||||
if (ctx->override_ro_active_level)
|
||||
return !gpiod_get_raw_value_cansleep(ctx->ro_gpio) ^
|
||||
!!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH);
|
||||
|
||||
return gpiod_get_value_cansleep(ctx->ro_gpio);
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_get_ro);
|
||||
|
||||
int mmc_gpio_get_cd(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
|
||||
if (!ctx || !ctx->cd_gpio)
|
||||
return -ENOSYS;
|
||||
|
||||
if (ctx->override_cd_active_level)
|
||||
return !gpiod_get_raw_value_cansleep(ctx->cd_gpio) ^
|
||||
!!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
|
||||
|
||||
return gpiod_get_value_cansleep(ctx->cd_gpio);
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_get_cd);
|
||||
|
||||
/**
|
||||
* mmc_gpio_request_ro - request a gpio for write-protection
|
||||
* @host: mmc host
|
||||
* @gpio: gpio number requested
|
||||
*
|
||||
* As devm_* managed functions are used in mmc_gpio_request_ro(), client
|
||||
* drivers do not need to explicitly call mmc_gpio_free_ro() for freeing up,
|
||||
* if the requesting and freeing are only needed at probing and unbinding time
|
||||
* for once. However, if client drivers do something special like runtime
|
||||
* switching for write-protection, they are responsible for calling
|
||||
* mmc_gpio_request_ro() and mmc_gpio_free_ro() as a pair on their own.
|
||||
*
|
||||
* Returns zero on success, else an error.
|
||||
*/
|
||||
int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio)
|
||||
{
|
||||
struct mmc_gpio *ctx;
|
||||
int ret;
|
||||
|
||||
if (!gpio_is_valid(gpio))
|
||||
return -EINVAL;
|
||||
|
||||
ret = mmc_gpio_alloc(host);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx = host->slot.handler_priv;
|
||||
|
||||
ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN,
|
||||
ctx->ro_label);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->override_ro_active_level = true;
|
||||
ctx->ro_gpio = gpio_to_desc(gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_request_ro);
|
||||
|
||||
void mmc_gpiod_request_cd_irq(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
int ret, irq;
|
||||
|
||||
if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
|
||||
return;
|
||||
|
||||
irq = gpiod_to_irq(ctx->cd_gpio);
|
||||
|
||||
/*
|
||||
* Even if gpiod_to_irq() returns a valid IRQ number, the platform might
|
||||
* still prefer to poll, e.g., because that IRQ number is already used
|
||||
* by another unit and cannot be shared.
|
||||
*/
|
||||
if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL)
|
||||
irq = -EINVAL;
|
||||
|
||||
if (irq >= 0) {
|
||||
ret = devm_request_threaded_irq(&host->class_dev, irq,
|
||||
NULL, mmc_gpio_cd_irqt,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
ctx->cd_label, host);
|
||||
if (ret < 0)
|
||||
irq = ret;
|
||||
}
|
||||
|
||||
host->slot.cd_irq = irq;
|
||||
|
||||
if (irq < 0)
|
||||
host->caps |= MMC_CAP_NEEDS_POLL;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpiod_request_cd_irq);
|
||||
|
||||
/**
|
||||
* mmc_gpio_request_cd - request a gpio for card-detection
|
||||
* @host: mmc host
|
||||
* @gpio: gpio number requested
|
||||
* @debounce: debounce time in microseconds
|
||||
*
|
||||
* As devm_* managed functions are used in mmc_gpio_request_cd(), client
|
||||
* drivers do not need to explicitly call mmc_gpio_free_cd() for freeing up,
|
||||
* if the requesting and freeing are only needed at probing and unbinding time
|
||||
* for once. However, if client drivers do something special like runtime
|
||||
* switching for card-detection, they are responsible for calling
|
||||
* mmc_gpio_request_cd() and mmc_gpio_free_cd() as a pair on their own.
|
||||
*
|
||||
* If GPIO debouncing is desired, set the debounce parameter to a non-zero
|
||||
* value. The caller is responsible for ensuring that the GPIO driver associated
|
||||
* with the GPIO supports debouncing, otherwise an error will be returned.
|
||||
*
|
||||
* Returns zero on success, else an error.
|
||||
*/
|
||||
int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio,
|
||||
unsigned int debounce)
|
||||
{
|
||||
struct mmc_gpio *ctx;
|
||||
int ret;
|
||||
|
||||
ret = mmc_gpio_alloc(host);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx = host->slot.handler_priv;
|
||||
|
||||
ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN,
|
||||
ctx->cd_label);
|
||||
if (ret < 0)
|
||||
/*
|
||||
* don't bother freeing memory. It might still get used by other
|
||||
* slot functions, in any case it will be freed, when the device
|
||||
* is destroyed.
|
||||
*/
|
||||
return ret;
|
||||
|
||||
if (debounce) {
|
||||
ret = gpio_set_debounce(gpio, debounce);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->override_cd_active_level = true;
|
||||
ctx->cd_gpio = gpio_to_desc(gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_request_cd);
|
||||
|
||||
/**
|
||||
* mmc_gpio_free_ro - free the write-protection gpio
|
||||
* @host: mmc host
|
||||
*
|
||||
* It's provided only for cases that client drivers need to manually free
|
||||
* up the write-protection gpio requested by mmc_gpio_request_ro().
|
||||
*/
|
||||
void mmc_gpio_free_ro(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
int gpio;
|
||||
|
||||
if (!ctx || !ctx->ro_gpio)
|
||||
return;
|
||||
|
||||
gpio = desc_to_gpio(ctx->ro_gpio);
|
||||
ctx->ro_gpio = NULL;
|
||||
|
||||
devm_gpio_free(&host->class_dev, gpio);
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_free_ro);
|
||||
|
||||
/**
|
||||
* mmc_gpio_free_cd - free the card-detection gpio
|
||||
* @host: mmc host
|
||||
*
|
||||
* It's provided only for cases that client drivers need to manually free
|
||||
* up the card-detection gpio requested by mmc_gpio_request_cd().
|
||||
*/
|
||||
void mmc_gpio_free_cd(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
int gpio;
|
||||
|
||||
if (!ctx || !ctx->cd_gpio)
|
||||
return;
|
||||
|
||||
if (host->slot.cd_irq >= 0) {
|
||||
devm_free_irq(&host->class_dev, host->slot.cd_irq, host);
|
||||
host->slot.cd_irq = -EINVAL;
|
||||
}
|
||||
|
||||
gpio = desc_to_gpio(ctx->cd_gpio);
|
||||
ctx->cd_gpio = NULL;
|
||||
|
||||
devm_gpio_free(&host->class_dev, gpio);
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpio_free_cd);
|
||||
|
||||
/**
|
||||
* mmc_gpiod_request_cd - request a gpio descriptor for card-detection
|
||||
* @host: mmc host
|
||||
* @con_id: function within the GPIO consumer
|
||||
* @idx: index of the GPIO to obtain in the consumer
|
||||
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
|
||||
* @debounce: debounce time in microseconds
|
||||
* @gpio_invert: will return whether the GPIO line is inverted or not, set
|
||||
* to NULL to ignore
|
||||
*
|
||||
* Use this function in place of mmc_gpio_request_cd() to use the GPIO
|
||||
* descriptor API. Note that it is paired with mmc_gpiod_free_cd() not
|
||||
* mmc_gpio_free_cd(). Note also that it must be called prior to mmc_add_host()
|
||||
* otherwise the caller must also call mmc_gpiod_request_cd_irq().
|
||||
*
|
||||
* Returns zero on success, else an error.
|
||||
*/
|
||||
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
|
||||
unsigned int idx, bool override_active_level,
|
||||
unsigned int debounce, bool *gpio_invert)
|
||||
{
|
||||
struct mmc_gpio *ctx;
|
||||
struct gpio_desc *desc;
|
||||
int ret;
|
||||
|
||||
ret = mmc_gpio_alloc(host);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx = host->slot.handler_priv;
|
||||
|
||||
if (!con_id)
|
||||
con_id = ctx->cd_label;
|
||||
|
||||
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
if (debounce) {
|
||||
ret = gpiod_set_debounce(desc, debounce);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (gpio_invert)
|
||||
*gpio_invert = !gpiod_is_active_low(desc);
|
||||
|
||||
ctx->override_cd_active_level = override_active_level;
|
||||
ctx->cd_gpio = desc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpiod_request_cd);
|
||||
|
||||
/**
|
||||
* mmc_gpiod_request_ro - request a gpio descriptor for write protection
|
||||
* @host: mmc host
|
||||
* @con_id: function within the GPIO consumer
|
||||
* @idx: index of the GPIO to obtain in the consumer
|
||||
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
|
||||
* @debounce: debounce time in microseconds
|
||||
* @gpio_invert: will return whether the GPIO line is inverted or not,
|
||||
* set to NULL to ignore
|
||||
*
|
||||
* Use this function in place of mmc_gpio_request_ro() to use the GPIO
|
||||
* descriptor API. Note that it is paired with mmc_gpiod_free_ro() not
|
||||
* mmc_gpio_free_ro().
|
||||
*
|
||||
* Returns zero on success, else an error.
|
||||
*/
|
||||
int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
|
||||
unsigned int idx, bool override_active_level,
|
||||
unsigned int debounce, bool *gpio_invert)
|
||||
{
|
||||
struct mmc_gpio *ctx;
|
||||
struct gpio_desc *desc;
|
||||
int ret;
|
||||
|
||||
ret = mmc_gpio_alloc(host);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx = host->slot.handler_priv;
|
||||
|
||||
if (!con_id)
|
||||
con_id = ctx->ro_label;
|
||||
|
||||
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
if (debounce) {
|
||||
ret = gpiod_set_debounce(desc, debounce);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (gpio_invert)
|
||||
*gpio_invert = !gpiod_is_active_low(desc);
|
||||
|
||||
ctx->override_ro_active_level = override_active_level;
|
||||
ctx->ro_gpio = desc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpiod_request_ro);
|
||||
|
||||
/**
|
||||
* mmc_gpiod_free_cd - free the card-detection gpio descriptor
|
||||
* @host: mmc host
|
||||
*
|
||||
* It's provided only for cases that client drivers need to manually free
|
||||
* up the card-detection gpio requested by mmc_gpiod_request_cd().
|
||||
*/
|
||||
void mmc_gpiod_free_cd(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||
|
||||
if (!ctx || !ctx->cd_gpio)
|
||||
return;
|
||||
|
||||
if (host->slot.cd_irq >= 0) {
|
||||
devm_free_irq(&host->class_dev, host->slot.cd_irq, host);
|
||||
host->slot.cd_irq = -EINVAL;
|
||||
}
|
||||
|
||||
devm_gpiod_put(host->parent, ctx->cd_gpio);
|
||||
|
||||
ctx->cd_gpio = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_gpiod_free_cd);
|
Loading…
Add table
Add a link
Reference in a new issue