mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-20 00:11:22 +00:00
17d33e14f7
Currently, all SDIO cards are virtually removed upon a suspend, and completely reprobed upon a resume. This adds the suspend and resume methods to the SDIO bus driver so to be able to dispatch those events to the actual SDIO function drivers for real suspend/resume instead. All active functions on a card must have a driver with both a suspend and a resume method though. Failing that, we fall back to the current behavior of simply "removing" the card when suspending. When resuming, we make sure the same card is still inserted by comparing the vendor and product IDs. If there is a mismatch, or if there is simply no card anymore in the slot, then the previous card is "removed" and the new card is detected. This is further enhanced with the next patch. [akpm@linux-foundation.org: fix warnings] Signed-off-by: Nicolas Pitre <nico@marvell.com> Cc: <linux-mmc@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
572 lines
11 KiB
C
572 lines
11 KiB
C
/*
|
|
* linux/drivers/mmc/sdio.c
|
|
*
|
|
* Copyright 2006-2007 Pierre Ossman
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/mmc/sdio_func.h>
|
|
|
|
#include "core.h"
|
|
#include "bus.h"
|
|
#include "sdio_bus.h"
|
|
#include "mmc_ops.h"
|
|
#include "sd_ops.h"
|
|
#include "sdio_ops.h"
|
|
#include "sdio_cis.h"
|
|
|
|
static int sdio_read_fbr(struct sdio_func *func)
|
|
{
|
|
int ret;
|
|
unsigned char data;
|
|
|
|
ret = mmc_io_rw_direct(func->card, 0, 0,
|
|
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
data &= 0x0f;
|
|
|
|
if (data == 0x0f) {
|
|
ret = mmc_io_rw_direct(func->card, 0, 0,
|
|
SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
func->class = data;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int sdio_init_func(struct mmc_card *card, unsigned int fn)
|
|
{
|
|
int ret;
|
|
struct sdio_func *func;
|
|
|
|
BUG_ON(fn > SDIO_MAX_FUNCS);
|
|
|
|
func = sdio_alloc_func(card);
|
|
if (IS_ERR(func))
|
|
return PTR_ERR(func);
|
|
|
|
func->num = fn;
|
|
|
|
ret = sdio_read_fbr(func);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
ret = sdio_read_func_cis(func);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
card->sdio_func[fn - 1] = func;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
/*
|
|
* It is okay to remove the function here even though we hold
|
|
* the host lock as we haven't registered the device yet.
|
|
*/
|
|
sdio_remove_func(func);
|
|
return ret;
|
|
}
|
|
|
|
static int sdio_read_cccr(struct mmc_card *card)
|
|
{
|
|
int ret;
|
|
int cccr_vsn;
|
|
unsigned char data;
|
|
|
|
memset(&card->cccr, 0, sizeof(struct sdio_cccr));
|
|
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
cccr_vsn = data & 0x0f;
|
|
|
|
if (cccr_vsn > SDIO_CCCR_REV_1_20) {
|
|
printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n",
|
|
mmc_hostname(card->host), cccr_vsn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
card->cccr.sdio_vsn = (data & 0xf0) >> 4;
|
|
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (data & SDIO_CCCR_CAP_SMB)
|
|
card->cccr.multi_block = 1;
|
|
if (data & SDIO_CCCR_CAP_LSC)
|
|
card->cccr.low_speed = 1;
|
|
if (data & SDIO_CCCR_CAP_4BLS)
|
|
card->cccr.wide_bus = 1;
|
|
|
|
if (cccr_vsn >= SDIO_CCCR_REV_1_10) {
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (data & SDIO_POWER_SMPC)
|
|
card->cccr.high_power = 1;
|
|
}
|
|
|
|
if (cccr_vsn >= SDIO_CCCR_REV_1_20) {
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (data & SDIO_SPEED_SHS)
|
|
card->cccr.high_speed = 1;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int sdio_enable_wide(struct mmc_card *card)
|
|
{
|
|
int ret;
|
|
u8 ctrl;
|
|
|
|
if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
|
|
return 0;
|
|
|
|
if (card->cccr.low_speed && !card->cccr.wide_bus)
|
|
return 0;
|
|
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ctrl |= SDIO_BUS_WIDTH_4BIT;
|
|
|
|
ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If desired, disconnect the pull-up resistor on CD/DAT[3] (pin 1)
|
|
* of the card. This may be required on certain setups of boards,
|
|
* controllers and embedded sdio device which do not need the card's
|
|
* pull-up. As a result, card detection is disabled and power is saved.
|
|
*/
|
|
static int sdio_disable_cd(struct mmc_card *card)
|
|
{
|
|
int ret;
|
|
u8 ctrl;
|
|
|
|
if (!card->cccr.disable_cd)
|
|
return 0;
|
|
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ctrl |= SDIO_BUS_CD_DISABLE;
|
|
|
|
return mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
|
|
}
|
|
|
|
/*
|
|
* Test if the card supports high-speed mode and, if so, switch to it.
|
|
*/
|
|
static int sdio_enable_hs(struct mmc_card *card)
|
|
{
|
|
int ret;
|
|
u8 speed;
|
|
|
|
if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
|
|
return 0;
|
|
|
|
if (!card->cccr.high_speed)
|
|
return 0;
|
|
|
|
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
|
|
if (ret)
|
|
return ret;
|
|
|
|
speed |= SDIO_SPEED_EHS;
|
|
|
|
ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mmc_card_set_highspeed(card);
|
|
mmc_set_timing(card->host, MMC_TIMING_SD_HS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Handle the detection and initialisation of a card.
|
|
*
|
|
* In the case of a resume, "oldcard" will contain the card
|
|
* we're trying to reinitialise.
|
|
*/
|
|
static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
|
|
struct mmc_card *oldcard)
|
|
{
|
|
struct mmc_card *card;
|
|
int err;
|
|
|
|
BUG_ON(!host);
|
|
WARN_ON(!host->claimed);
|
|
|
|
/*
|
|
* Inform the card of the voltage
|
|
*/
|
|
err = mmc_send_io_op_cond(host, host->ocr, &ocr);
|
|
if (err)
|
|
goto err;
|
|
|
|
/*
|
|
* For SPI, enable CRC as appropriate.
|
|
*/
|
|
if (mmc_host_is_spi(host)) {
|
|
err = mmc_spi_set_crc(host, use_spi_crc);
|
|
if (err)
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Allocate card structure.
|
|
*/
|
|
card = mmc_alloc_card(host, NULL);
|
|
if (IS_ERR(card)) {
|
|
err = PTR_ERR(card);
|
|
goto err;
|
|
}
|
|
|
|
card->type = MMC_TYPE_SDIO;
|
|
|
|
/*
|
|
* For native busses: set card RCA and quit open drain mode.
|
|
*/
|
|
if (!mmc_host_is_spi(host)) {
|
|
err = mmc_send_relative_addr(host, &card->rca);
|
|
if (err)
|
|
goto remove;
|
|
|
|
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
|
|
}
|
|
|
|
/*
|
|
* Select card, as all following commands rely on that.
|
|
*/
|
|
if (!mmc_host_is_spi(host)) {
|
|
err = mmc_select_card(card);
|
|
if (err)
|
|
goto remove;
|
|
}
|
|
|
|
/*
|
|
* Read the common registers.
|
|
*/
|
|
err = sdio_read_cccr(card);
|
|
if (err)
|
|
goto remove;
|
|
|
|
/*
|
|
* Read the common CIS tuples.
|
|
*/
|
|
err = sdio_read_common_cis(card);
|
|
if (err)
|
|
goto remove;
|
|
|
|
if (oldcard) {
|
|
int same = (card->cis.vendor == oldcard->cis.vendor &&
|
|
card->cis.device == oldcard->cis.device);
|
|
mmc_remove_card(card);
|
|
if (!same) {
|
|
err = -ENOENT;
|
|
goto err;
|
|
}
|
|
card = oldcard;
|
|
}
|
|
|
|
/*
|
|
* Switch to high-speed (if supported).
|
|
*/
|
|
err = sdio_enable_hs(card);
|
|
if (err)
|
|
goto remove;
|
|
|
|
/*
|
|
* Change to the card's maximum speed.
|
|
*/
|
|
if (mmc_card_highspeed(card)) {
|
|
/*
|
|
* The SDIO specification doesn't mention how
|
|
* the CIS transfer speed register relates to
|
|
* high-speed, but it seems that 50 MHz is
|
|
* mandatory.
|
|
*/
|
|
mmc_set_clock(host, 50000000);
|
|
} else {
|
|
mmc_set_clock(host, card->cis.max_dtr);
|
|
}
|
|
|
|
/*
|
|
* Switch to wider bus (if supported).
|
|
*/
|
|
err = sdio_enable_wide(card);
|
|
if (err)
|
|
goto remove;
|
|
|
|
if (!oldcard)
|
|
host->card = card;
|
|
return 0;
|
|
|
|
remove:
|
|
if (!oldcard)
|
|
mmc_remove_card(card);
|
|
|
|
err:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Host is being removed. Free up the current card.
|
|
*/
|
|
static void mmc_sdio_remove(struct mmc_host *host)
|
|
{
|
|
int i;
|
|
|
|
BUG_ON(!host);
|
|
BUG_ON(!host->card);
|
|
|
|
for (i = 0;i < host->card->sdio_funcs;i++) {
|
|
if (host->card->sdio_func[i]) {
|
|
sdio_remove_func(host->card->sdio_func[i]);
|
|
host->card->sdio_func[i] = NULL;
|
|
}
|
|
}
|
|
|
|
mmc_remove_card(host->card);
|
|
host->card = NULL;
|
|
}
|
|
|
|
/*
|
|
* Card detection callback from host.
|
|
*/
|
|
static void mmc_sdio_detect(struct mmc_host *host)
|
|
{
|
|
int err;
|
|
|
|
BUG_ON(!host);
|
|
BUG_ON(!host->card);
|
|
|
|
mmc_claim_host(host);
|
|
|
|
/*
|
|
* Just check if our card has been removed.
|
|
*/
|
|
err = mmc_select_card(host->card);
|
|
|
|
mmc_release_host(host);
|
|
|
|
if (err) {
|
|
mmc_sdio_remove(host);
|
|
|
|
mmc_claim_host(host);
|
|
mmc_detach_bus(host);
|
|
mmc_release_host(host);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SDIO suspend. We need to suspend all functions separately.
|
|
* Therefore all registered functions must have drivers with suspend
|
|
* and resume methods. Failing that we simply remove the whole card.
|
|
*/
|
|
static void mmc_sdio_suspend(struct mmc_host *host)
|
|
{
|
|
int i;
|
|
|
|
/* make sure all registered functions can suspend/resume */
|
|
for (i = 0; i < host->card->sdio_funcs; i++) {
|
|
struct sdio_func *func = host->card->sdio_func[i];
|
|
if (func && sdio_func_present(func) && func->dev.driver) {
|
|
const struct dev_pm_ops *pmops = func->dev.driver->pm;
|
|
if (!pmops || !pmops->suspend || !pmops->resume) {
|
|
/* just remove the entire card in that case */
|
|
mmc_sdio_remove(host);
|
|
mmc_claim_host(host);
|
|
mmc_detach_bus(host);
|
|
mmc_release_host(host);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now suspend them */
|
|
for (i = 0; i < host->card->sdio_funcs; i++) {
|
|
struct sdio_func *func = host->card->sdio_func[i];
|
|
if (func && sdio_func_present(func) && func->dev.driver) {
|
|
const struct dev_pm_ops *pmops = func->dev.driver->pm;
|
|
pmops->suspend(&func->dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mmc_sdio_resume(struct mmc_host *host)
|
|
{
|
|
int i, err;
|
|
|
|
BUG_ON(!host);
|
|
BUG_ON(!host->card);
|
|
|
|
mmc_claim_host(host);
|
|
err = mmc_sdio_init_card(host, host->ocr, host->card);
|
|
mmc_release_host(host);
|
|
if (err) {
|
|
mmc_sdio_remove(host);
|
|
mmc_claim_host(host);
|
|
mmc_detach_bus(host);
|
|
mmc_release_host(host);
|
|
return;
|
|
}
|
|
|
|
/* resume all functions */
|
|
for (i = 0; i < host->card->sdio_funcs; i++) {
|
|
struct sdio_func *func = host->card->sdio_func[i];
|
|
if (func && sdio_func_present(func) && func->dev.driver) {
|
|
const struct dev_pm_ops *pmops = func->dev.driver->pm;
|
|
pmops->resume(&func->dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct mmc_bus_ops mmc_sdio_ops = {
|
|
.remove = mmc_sdio_remove,
|
|
.detect = mmc_sdio_detect,
|
|
.suspend = mmc_sdio_suspend,
|
|
.resume = mmc_sdio_resume,
|
|
};
|
|
|
|
|
|
/*
|
|
* Starting point for SDIO card init.
|
|
*/
|
|
int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
|
|
{
|
|
int err;
|
|
int i, funcs;
|
|
struct mmc_card *card;
|
|
|
|
BUG_ON(!host);
|
|
WARN_ON(!host->claimed);
|
|
|
|
mmc_attach_bus(host, &mmc_sdio_ops);
|
|
|
|
/*
|
|
* Sanity check the voltages that the card claims to
|
|
* support.
|
|
*/
|
|
if (ocr & 0x7F) {
|
|
printk(KERN_WARNING "%s: card claims to support voltages "
|
|
"below the defined range. These will be ignored.\n",
|
|
mmc_hostname(host));
|
|
ocr &= ~0x7F;
|
|
}
|
|
|
|
host->ocr = mmc_select_voltage(host, ocr);
|
|
|
|
/*
|
|
* Can we support the voltage(s) of the card(s)?
|
|
*/
|
|
if (!host->ocr) {
|
|
err = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Detect and init the card.
|
|
*/
|
|
err = mmc_sdio_init_card(host, host->ocr, NULL);
|
|
if (err)
|
|
goto err;
|
|
card = host->card;
|
|
|
|
/*
|
|
* The number of functions on the card is encoded inside
|
|
* the ocr.
|
|
*/
|
|
card->sdio_funcs = funcs = (ocr & 0x70000000) >> 28;
|
|
|
|
/*
|
|
* If needed, disconnect card detection pull-up resistor.
|
|
*/
|
|
err = sdio_disable_cd(card);
|
|
if (err)
|
|
goto remove;
|
|
|
|
/*
|
|
* Initialize (but don't add) all present functions.
|
|
*/
|
|
for (i = 0;i < funcs;i++) {
|
|
err = sdio_init_func(host->card, i + 1);
|
|
if (err)
|
|
goto remove;
|
|
}
|
|
|
|
mmc_release_host(host);
|
|
|
|
/*
|
|
* First add the card to the driver model...
|
|
*/
|
|
err = mmc_add_card(host->card);
|
|
if (err)
|
|
goto remove_added;
|
|
|
|
/*
|
|
* ...then the SDIO functions.
|
|
*/
|
|
for (i = 0;i < funcs;i++) {
|
|
err = sdio_add_func(host->card->sdio_func[i]);
|
|
if (err)
|
|
goto remove_added;
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
|
remove_added:
|
|
/* Remove without lock if the device has been added. */
|
|
mmc_sdio_remove(host);
|
|
mmc_claim_host(host);
|
|
remove:
|
|
/* And with lock if it hasn't been added. */
|
|
if (host->card)
|
|
mmc_sdio_remove(host);
|
|
err:
|
|
mmc_detach_bus(host);
|
|
mmc_release_host(host);
|
|
|
|
printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",
|
|
mmc_hostname(host), err);
|
|
|
|
return err;
|
|
}
|
|
|