forked from Minki/linux
2874c5fd28
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
706 lines
17 KiB
C
706 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Toshiba PCI Secure Digital Host Controller Interface driver
|
|
*
|
|
* Copyright (C) 2014 Ondrej Zary
|
|
* Copyright (C) 2007 Richard Betts, All Rights Reserved.
|
|
*
|
|
* Based on asic3_mmc.c, copyright (c) 2005 SDG Systems, LLC and,
|
|
* sdhci.c, copyright (C) 2005-2006 Pierre Ossman
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/mmc.h>
|
|
|
|
#include "toshsd.h"
|
|
|
|
#define DRIVER_NAME "toshsd"
|
|
|
|
static const struct pci_device_id pci_ids[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA, 0x0805) },
|
|
{ /* end: all zeroes */ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, pci_ids);
|
|
|
|
static void toshsd_init(struct toshsd_host *host)
|
|
{
|
|
/* enable clock */
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_CLKSTOP,
|
|
SD_PCICFG_CLKSTOP_ENABLE_ALL);
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_CARDDETECT, 2);
|
|
|
|
/* reset */
|
|
iowrite16(0, host->ioaddr + SD_SOFTWARERESET); /* assert */
|
|
mdelay(2);
|
|
iowrite16(1, host->ioaddr + SD_SOFTWARERESET); /* deassert */
|
|
mdelay(2);
|
|
|
|
/* Clear card registers */
|
|
iowrite16(0, host->ioaddr + SD_CARDCLOCKCTRL);
|
|
iowrite32(0, host->ioaddr + SD_CARDSTATUS);
|
|
iowrite32(0, host->ioaddr + SD_ERRORSTATUS0);
|
|
iowrite16(0, host->ioaddr + SD_STOPINTERNAL);
|
|
|
|
/* SDIO clock? */
|
|
iowrite16(0x100, host->ioaddr + SDIO_BASE + SDIO_CLOCKNWAITCTRL);
|
|
|
|
/* enable LED */
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_SDLED_ENABLE1,
|
|
SD_PCICFG_LED_ENABLE1_START);
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_SDLED_ENABLE2,
|
|
SD_PCICFG_LED_ENABLE2_START);
|
|
|
|
/* set interrupt masks */
|
|
iowrite32(~(u32)(SD_CARD_RESP_END | SD_CARD_RW_END
|
|
| SD_CARD_CARD_REMOVED_0 | SD_CARD_CARD_INSERTED_0
|
|
| SD_BUF_READ_ENABLE | SD_BUF_WRITE_ENABLE
|
|
| SD_BUF_CMD_TIMEOUT),
|
|
host->ioaddr + SD_INTMASKCARD);
|
|
|
|
iowrite16(0x1000, host->ioaddr + SD_TRANSACTIONCTRL);
|
|
}
|
|
|
|
/* Set MMC clock / power.
|
|
* Note: This controller uses a simple divider scheme therefore it cannot run
|
|
* SD/MMC cards at full speed (24/20MHz). HCLK (=33MHz PCI clock?) is too high
|
|
* and the next slowest is 16MHz (div=2).
|
|
*/
|
|
static void __toshsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct toshsd_host *host = mmc_priv(mmc);
|
|
|
|
if (ios->clock) {
|
|
u16 clk;
|
|
int div = 1;
|
|
|
|
while (ios->clock < HCLK / div)
|
|
div *= 2;
|
|
|
|
clk = div >> 2;
|
|
|
|
if (div == 1) { /* disable the divider */
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_CLKMODE,
|
|
SD_PCICFG_CLKMODE_DIV_DISABLE);
|
|
clk |= SD_CARDCLK_DIV_DISABLE;
|
|
} else
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_CLKMODE, 0);
|
|
|
|
clk |= SD_CARDCLK_ENABLE_CLOCK;
|
|
iowrite16(clk, host->ioaddr + SD_CARDCLOCKCTRL);
|
|
|
|
mdelay(10);
|
|
} else
|
|
iowrite16(0, host->ioaddr + SD_CARDCLOCKCTRL);
|
|
|
|
switch (ios->power_mode) {
|
|
case MMC_POWER_OFF:
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_POWER1,
|
|
SD_PCICFG_PWR1_OFF);
|
|
mdelay(1);
|
|
break;
|
|
case MMC_POWER_UP:
|
|
break;
|
|
case MMC_POWER_ON:
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_POWER1,
|
|
SD_PCICFG_PWR1_33V);
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_POWER2,
|
|
SD_PCICFG_PWR2_AUTO);
|
|
mdelay(20);
|
|
break;
|
|
}
|
|
|
|
switch (ios->bus_width) {
|
|
case MMC_BUS_WIDTH_1:
|
|
iowrite16(SD_CARDOPT_REQUIRED | SD_CARDOPT_DATA_RESP_TIMEOUT(14)
|
|
| SD_CARDOPT_C2_MODULE_ABSENT
|
|
| SD_CARDOPT_DATA_XFR_WIDTH_1,
|
|
host->ioaddr + SD_CARDOPTIONSETUP);
|
|
break;
|
|
case MMC_BUS_WIDTH_4:
|
|
iowrite16(SD_CARDOPT_REQUIRED | SD_CARDOPT_DATA_RESP_TIMEOUT(14)
|
|
| SD_CARDOPT_C2_MODULE_ABSENT
|
|
| SD_CARDOPT_DATA_XFR_WIDTH_4,
|
|
host->ioaddr + SD_CARDOPTIONSETUP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void toshsd_set_led(struct toshsd_host *host, unsigned char state)
|
|
{
|
|
iowrite16(state, host->ioaddr + SDIO_BASE + SDIO_LEDCTRL);
|
|
}
|
|
|
|
static void toshsd_finish_request(struct toshsd_host *host)
|
|
{
|
|
struct mmc_request *mrq = host->mrq;
|
|
|
|
/* Write something to end the command */
|
|
host->mrq = NULL;
|
|
host->cmd = NULL;
|
|
host->data = NULL;
|
|
|
|
toshsd_set_led(host, 0);
|
|
mmc_request_done(host->mmc, mrq);
|
|
}
|
|
|
|
static irqreturn_t toshsd_thread_irq(int irq, void *dev_id)
|
|
{
|
|
struct toshsd_host *host = dev_id;
|
|
struct mmc_data *data = host->data;
|
|
struct sg_mapping_iter *sg_miter = &host->sg_miter;
|
|
unsigned short *buf;
|
|
int count;
|
|
unsigned long flags;
|
|
|
|
if (!data) {
|
|
dev_warn(&host->pdev->dev, "Spurious Data IRQ\n");
|
|
if (host->cmd) {
|
|
host->cmd->error = -EIO;
|
|
toshsd_finish_request(host);
|
|
}
|
|
return IRQ_NONE;
|
|
}
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
|
|
if (!sg_miter_next(sg_miter))
|
|
goto done;
|
|
|
|
buf = sg_miter->addr;
|
|
|
|
/* Ensure we dont read more than one block. The chip will interrupt us
|
|
* When the next block is available.
|
|
*/
|
|
count = sg_miter->length;
|
|
if (count > data->blksz)
|
|
count = data->blksz;
|
|
|
|
dev_dbg(&host->pdev->dev, "count: %08x, flags %08x\n", count,
|
|
data->flags);
|
|
|
|
/* Transfer the data */
|
|
if (data->flags & MMC_DATA_READ)
|
|
ioread32_rep(host->ioaddr + SD_DATAPORT, buf, count >> 2);
|
|
else
|
|
iowrite32_rep(host->ioaddr + SD_DATAPORT, buf, count >> 2);
|
|
|
|
sg_miter->consumed = count;
|
|
sg_miter_stop(sg_miter);
|
|
|
|
done:
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void toshsd_cmd_irq(struct toshsd_host *host)
|
|
{
|
|
struct mmc_command *cmd = host->cmd;
|
|
u8 *buf;
|
|
u16 data;
|
|
|
|
if (!host->cmd) {
|
|
dev_warn(&host->pdev->dev, "Spurious CMD irq\n");
|
|
return;
|
|
}
|
|
buf = (u8 *)cmd->resp;
|
|
host->cmd = NULL;
|
|
|
|
if (cmd->flags & MMC_RSP_PRESENT && cmd->flags & MMC_RSP_136) {
|
|
/* R2 */
|
|
buf[12] = 0xff;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE0);
|
|
buf[13] = data & 0xff;
|
|
buf[14] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE1);
|
|
buf[15] = data & 0xff;
|
|
buf[8] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE2);
|
|
buf[9] = data & 0xff;
|
|
buf[10] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE3);
|
|
buf[11] = data & 0xff;
|
|
buf[4] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE4);
|
|
buf[5] = data & 0xff;
|
|
buf[6] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE5);
|
|
buf[7] = data & 0xff;
|
|
buf[0] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE6);
|
|
buf[1] = data & 0xff;
|
|
buf[2] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE7);
|
|
buf[3] = data & 0xff;
|
|
} else if (cmd->flags & MMC_RSP_PRESENT) {
|
|
/* R1, R1B, R3, R6, R7 */
|
|
data = ioread16(host->ioaddr + SD_RESPONSE0);
|
|
buf[0] = data & 0xff;
|
|
buf[1] = data >> 8;
|
|
data = ioread16(host->ioaddr + SD_RESPONSE1);
|
|
buf[2] = data & 0xff;
|
|
buf[3] = data >> 8;
|
|
}
|
|
|
|
dev_dbg(&host->pdev->dev, "Command IRQ complete %d %d %x\n",
|
|
cmd->opcode, cmd->error, cmd->flags);
|
|
|
|
/* If there is data to handle we will
|
|
* finish the request in the mmc_data_end_irq handler.*/
|
|
if (host->data)
|
|
return;
|
|
|
|
toshsd_finish_request(host);
|
|
}
|
|
|
|
static void toshsd_data_end_irq(struct toshsd_host *host)
|
|
{
|
|
struct mmc_data *data = host->data;
|
|
|
|
host->data = NULL;
|
|
|
|
if (!data) {
|
|
dev_warn(&host->pdev->dev, "Spurious data end IRQ\n");
|
|
return;
|
|
}
|
|
|
|
if (data->error == 0)
|
|
data->bytes_xfered = data->blocks * data->blksz;
|
|
else
|
|
data->bytes_xfered = 0;
|
|
|
|
dev_dbg(&host->pdev->dev, "Completed data request xfr=%d\n",
|
|
data->bytes_xfered);
|
|
|
|
iowrite16(0, host->ioaddr + SD_STOPINTERNAL);
|
|
|
|
toshsd_finish_request(host);
|
|
}
|
|
|
|
static irqreturn_t toshsd_irq(int irq, void *dev_id)
|
|
{
|
|
struct toshsd_host *host = dev_id;
|
|
u32 int_reg, int_mask, int_status, detail;
|
|
int error = 0, ret = IRQ_HANDLED;
|
|
|
|
spin_lock(&host->lock);
|
|
int_status = ioread32(host->ioaddr + SD_CARDSTATUS);
|
|
int_mask = ioread32(host->ioaddr + SD_INTMASKCARD);
|
|
int_reg = int_status & ~int_mask & ~IRQ_DONT_CARE_BITS;
|
|
|
|
dev_dbg(&host->pdev->dev, "IRQ status:%x mask:%x\n",
|
|
int_status, int_mask);
|
|
|
|
/* nothing to do: it's not our IRQ */
|
|
if (!int_reg) {
|
|
ret = IRQ_NONE;
|
|
goto irq_end;
|
|
}
|
|
|
|
if (int_reg & SD_BUF_CMD_TIMEOUT) {
|
|
error = -ETIMEDOUT;
|
|
dev_dbg(&host->pdev->dev, "Timeout\n");
|
|
} else if (int_reg & SD_BUF_CRC_ERR) {
|
|
error = -EILSEQ;
|
|
dev_err(&host->pdev->dev, "BadCRC\n");
|
|
} else if (int_reg & (SD_BUF_ILLEGAL_ACCESS
|
|
| SD_BUF_CMD_INDEX_ERR
|
|
| SD_BUF_STOP_BIT_END_ERR
|
|
| SD_BUF_OVERFLOW
|
|
| SD_BUF_UNDERFLOW
|
|
| SD_BUF_DATA_TIMEOUT)) {
|
|
dev_err(&host->pdev->dev, "Buffer status error: { %s%s%s%s%s%s}\n",
|
|
int_reg & SD_BUF_ILLEGAL_ACCESS ? "ILLEGAL_ACC " : "",
|
|
int_reg & SD_BUF_CMD_INDEX_ERR ? "CMD_INDEX " : "",
|
|
int_reg & SD_BUF_STOP_BIT_END_ERR ? "STOPBIT_END " : "",
|
|
int_reg & SD_BUF_OVERFLOW ? "OVERFLOW " : "",
|
|
int_reg & SD_BUF_UNDERFLOW ? "UNDERFLOW " : "",
|
|
int_reg & SD_BUF_DATA_TIMEOUT ? "DATA_TIMEOUT " : "");
|
|
|
|
detail = ioread32(host->ioaddr + SD_ERRORSTATUS0);
|
|
dev_err(&host->pdev->dev, "detail error status { %s%s%s%s%s%s%s%s%s%s%s%s%s}\n",
|
|
detail & SD_ERR0_RESP_CMD_ERR ? "RESP_CMD " : "",
|
|
detail & SD_ERR0_RESP_NON_CMD12_END_BIT_ERR ? "RESP_END_BIT " : "",
|
|
detail & SD_ERR0_RESP_CMD12_END_BIT_ERR ? "RESP_END_BIT " : "",
|
|
detail & SD_ERR0_READ_DATA_END_BIT_ERR ? "READ_DATA_END_BIT " : "",
|
|
detail & SD_ERR0_WRITE_CRC_STATUS_END_BIT_ERR ? "WRITE_CMD_END_BIT " : "",
|
|
detail & SD_ERR0_RESP_NON_CMD12_CRC_ERR ? "RESP_CRC " : "",
|
|
detail & SD_ERR0_RESP_CMD12_CRC_ERR ? "RESP_CRC " : "",
|
|
detail & SD_ERR0_READ_DATA_CRC_ERR ? "READ_DATA_CRC " : "",
|
|
detail & SD_ERR0_WRITE_CMD_CRC_ERR ? "WRITE_CMD_CRC " : "",
|
|
detail & SD_ERR1_NO_CMD_RESP ? "NO_CMD_RESP " : "",
|
|
detail & SD_ERR1_TIMEOUT_READ_DATA ? "READ_DATA_TIMEOUT " : "",
|
|
detail & SD_ERR1_TIMEOUT_CRS_STATUS ? "CRS_STATUS_TIMEOUT " : "",
|
|
detail & SD_ERR1_TIMEOUT_CRC_BUSY ? "CRC_BUSY_TIMEOUT " : "");
|
|
error = -EIO;
|
|
}
|
|
|
|
if (error) {
|
|
if (host->cmd)
|
|
host->cmd->error = error;
|
|
|
|
if (error == -ETIMEDOUT) {
|
|
iowrite32(int_status &
|
|
~(SD_BUF_CMD_TIMEOUT | SD_CARD_RESP_END),
|
|
host->ioaddr + SD_CARDSTATUS);
|
|
} else {
|
|
toshsd_init(host);
|
|
__toshsd_set_ios(host->mmc, &host->mmc->ios);
|
|
goto irq_end;
|
|
}
|
|
}
|
|
|
|
/* Card insert/remove. The mmc controlling code is stateless. */
|
|
if (int_reg & (SD_CARD_CARD_INSERTED_0 | SD_CARD_CARD_REMOVED_0)) {
|
|
iowrite32(int_status &
|
|
~(SD_CARD_CARD_REMOVED_0 | SD_CARD_CARD_INSERTED_0),
|
|
host->ioaddr + SD_CARDSTATUS);
|
|
|
|
if (int_reg & SD_CARD_CARD_INSERTED_0)
|
|
toshsd_init(host);
|
|
|
|
mmc_detect_change(host->mmc, 1);
|
|
}
|
|
|
|
/* Data transfer */
|
|
if (int_reg & (SD_BUF_READ_ENABLE | SD_BUF_WRITE_ENABLE)) {
|
|
iowrite32(int_status &
|
|
~(SD_BUF_WRITE_ENABLE | SD_BUF_READ_ENABLE),
|
|
host->ioaddr + SD_CARDSTATUS);
|
|
|
|
ret = IRQ_WAKE_THREAD;
|
|
goto irq_end;
|
|
}
|
|
|
|
/* Command completion */
|
|
if (int_reg & SD_CARD_RESP_END) {
|
|
iowrite32(int_status & ~(SD_CARD_RESP_END),
|
|
host->ioaddr + SD_CARDSTATUS);
|
|
toshsd_cmd_irq(host);
|
|
}
|
|
|
|
/* Data transfer completion */
|
|
if (int_reg & SD_CARD_RW_END) {
|
|
iowrite32(int_status & ~(SD_CARD_RW_END),
|
|
host->ioaddr + SD_CARDSTATUS);
|
|
toshsd_data_end_irq(host);
|
|
}
|
|
irq_end:
|
|
spin_unlock(&host->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void toshsd_start_cmd(struct toshsd_host *host, struct mmc_command *cmd)
|
|
{
|
|
struct mmc_data *data = host->data;
|
|
int c = cmd->opcode;
|
|
|
|
dev_dbg(&host->pdev->dev, "Command opcode: %d\n", cmd->opcode);
|
|
|
|
if (cmd->opcode == MMC_STOP_TRANSMISSION) {
|
|
iowrite16(SD_STOPINT_ISSUE_CMD12,
|
|
host->ioaddr + SD_STOPINTERNAL);
|
|
|
|
cmd->resp[0] = cmd->opcode;
|
|
cmd->resp[1] = 0;
|
|
cmd->resp[2] = 0;
|
|
cmd->resp[3] = 0;
|
|
|
|
toshsd_finish_request(host);
|
|
return;
|
|
}
|
|
|
|
switch (mmc_resp_type(cmd)) {
|
|
case MMC_RSP_NONE:
|
|
c |= SD_CMD_RESP_TYPE_NONE;
|
|
break;
|
|
|
|
case MMC_RSP_R1:
|
|
c |= SD_CMD_RESP_TYPE_EXT_R1;
|
|
break;
|
|
case MMC_RSP_R1B:
|
|
c |= SD_CMD_RESP_TYPE_EXT_R1B;
|
|
break;
|
|
case MMC_RSP_R2:
|
|
c |= SD_CMD_RESP_TYPE_EXT_R2;
|
|
break;
|
|
case MMC_RSP_R3:
|
|
c |= SD_CMD_RESP_TYPE_EXT_R3;
|
|
break;
|
|
|
|
default:
|
|
dev_err(&host->pdev->dev, "Unknown response type %d\n",
|
|
mmc_resp_type(cmd));
|
|
break;
|
|
}
|
|
|
|
host->cmd = cmd;
|
|
|
|
if (cmd->opcode == MMC_APP_CMD)
|
|
c |= SD_CMD_TYPE_ACMD;
|
|
|
|
if (cmd->opcode == MMC_GO_IDLE_STATE)
|
|
c |= (3 << 8); /* removed from ipaq-asic3.h for some reason */
|
|
|
|
if (data) {
|
|
c |= SD_CMD_DATA_PRESENT;
|
|
|
|
if (data->blocks > 1) {
|
|
iowrite16(SD_STOPINT_AUTO_ISSUE_CMD12,
|
|
host->ioaddr + SD_STOPINTERNAL);
|
|
c |= SD_CMD_MULTI_BLOCK;
|
|
}
|
|
|
|
if (data->flags & MMC_DATA_READ)
|
|
c |= SD_CMD_TRANSFER_READ;
|
|
|
|
/* MMC_DATA_WRITE does not require a bit to be set */
|
|
}
|
|
|
|
/* Send the command */
|
|
iowrite32(cmd->arg, host->ioaddr + SD_ARG0);
|
|
iowrite16(c, host->ioaddr + SD_CMD);
|
|
}
|
|
|
|
static void toshsd_start_data(struct toshsd_host *host, struct mmc_data *data)
|
|
{
|
|
unsigned int flags = SG_MITER_ATOMIC;
|
|
|
|
dev_dbg(&host->pdev->dev, "setup data transfer: blocksize %08x nr_blocks %d, offset: %08x\n",
|
|
data->blksz, data->blocks, data->sg->offset);
|
|
|
|
host->data = data;
|
|
|
|
if (data->flags & MMC_DATA_READ)
|
|
flags |= SG_MITER_TO_SG;
|
|
else
|
|
flags |= SG_MITER_FROM_SG;
|
|
|
|
sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
|
|
|
|
/* Set transfer length and blocksize */
|
|
iowrite16(data->blocks, host->ioaddr + SD_BLOCKCOUNT);
|
|
iowrite16(data->blksz, host->ioaddr + SD_CARDXFERDATALEN);
|
|
}
|
|
|
|
/* Process requests from the MMC layer */
|
|
static void toshsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
struct toshsd_host *host = mmc_priv(mmc);
|
|
unsigned long flags;
|
|
|
|
/* abort if card not present */
|
|
if (!(ioread16(host->ioaddr + SD_CARDSTATUS) & SD_CARD_PRESENT_0)) {
|
|
mrq->cmd->error = -ENOMEDIUM;
|
|
mmc_request_done(mmc, mrq);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
|
|
WARN_ON(host->mrq != NULL);
|
|
|
|
host->mrq = mrq;
|
|
|
|
if (mrq->data)
|
|
toshsd_start_data(host, mrq->data);
|
|
|
|
toshsd_set_led(host, 1);
|
|
|
|
toshsd_start_cmd(host, mrq->cmd);
|
|
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
static void toshsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct toshsd_host *host = mmc_priv(mmc);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
__toshsd_set_ios(mmc, ios);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
static int toshsd_get_ro(struct mmc_host *mmc)
|
|
{
|
|
struct toshsd_host *host = mmc_priv(mmc);
|
|
|
|
/* active low */
|
|
return !(ioread16(host->ioaddr + SD_CARDSTATUS) & SD_CARD_WRITE_PROTECT);
|
|
}
|
|
|
|
static int toshsd_get_cd(struct mmc_host *mmc)
|
|
{
|
|
struct toshsd_host *host = mmc_priv(mmc);
|
|
|
|
return !!(ioread16(host->ioaddr + SD_CARDSTATUS) & SD_CARD_PRESENT_0);
|
|
}
|
|
|
|
static const struct mmc_host_ops toshsd_ops = {
|
|
.request = toshsd_request,
|
|
.set_ios = toshsd_set_ios,
|
|
.get_ro = toshsd_get_ro,
|
|
.get_cd = toshsd_get_cd,
|
|
};
|
|
|
|
|
|
static void toshsd_powerdown(struct toshsd_host *host)
|
|
{
|
|
/* mask all interrupts */
|
|
iowrite32(0xffffffff, host->ioaddr + SD_INTMASKCARD);
|
|
/* disable card clock */
|
|
iowrite16(0x000, host->ioaddr + SDIO_BASE + SDIO_CLOCKNWAITCTRL);
|
|
iowrite16(0, host->ioaddr + SD_CARDCLOCKCTRL);
|
|
/* power down card */
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_POWER1, SD_PCICFG_PWR1_OFF);
|
|
/* disable clock */
|
|
pci_write_config_byte(host->pdev, SD_PCICFG_CLKSTOP, 0);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int toshsd_pm_suspend(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct toshsd_host *host = pci_get_drvdata(pdev);
|
|
|
|
toshsd_powerdown(host);
|
|
|
|
pci_save_state(pdev);
|
|
pci_enable_wake(pdev, PCI_D3hot, 0);
|
|
pci_disable_device(pdev);
|
|
pci_set_power_state(pdev, PCI_D3hot);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int toshsd_pm_resume(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct toshsd_host *host = pci_get_drvdata(pdev);
|
|
int ret;
|
|
|
|
pci_set_power_state(pdev, PCI_D0);
|
|
pci_restore_state(pdev);
|
|
ret = pci_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
toshsd_init(host);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static int toshsd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
int ret;
|
|
struct toshsd_host *host;
|
|
struct mmc_host *mmc;
|
|
resource_size_t base;
|
|
|
|
ret = pci_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mmc = mmc_alloc_host(sizeof(struct toshsd_host), &pdev->dev);
|
|
if (!mmc) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
host = mmc_priv(mmc);
|
|
host->mmc = mmc;
|
|
|
|
host->pdev = pdev;
|
|
pci_set_drvdata(pdev, host);
|
|
|
|
ret = pci_request_regions(pdev, DRIVER_NAME);
|
|
if (ret)
|
|
goto free;
|
|
|
|
host->ioaddr = pci_iomap(pdev, 0, 0);
|
|
if (!host->ioaddr) {
|
|
ret = -ENOMEM;
|
|
goto release;
|
|
}
|
|
|
|
/* Set MMC host parameters */
|
|
mmc->ops = &toshsd_ops;
|
|
mmc->caps = MMC_CAP_4_BIT_DATA;
|
|
mmc->ocr_avail = MMC_VDD_32_33;
|
|
|
|
mmc->f_min = HCLK / 512;
|
|
mmc->f_max = HCLK;
|
|
|
|
spin_lock_init(&host->lock);
|
|
|
|
toshsd_init(host);
|
|
|
|
ret = request_threaded_irq(pdev->irq, toshsd_irq, toshsd_thread_irq,
|
|
IRQF_SHARED, DRIVER_NAME, host);
|
|
if (ret)
|
|
goto unmap;
|
|
|
|
mmc_add_host(mmc);
|
|
|
|
base = pci_resource_start(pdev, 0);
|
|
dev_dbg(&pdev->dev, "MMIO %pa, IRQ %d\n", &base, pdev->irq);
|
|
|
|
pm_suspend_ignore_children(&pdev->dev, 1);
|
|
|
|
return 0;
|
|
|
|
unmap:
|
|
pci_iounmap(pdev, host->ioaddr);
|
|
release:
|
|
pci_release_regions(pdev);
|
|
free:
|
|
mmc_free_host(mmc);
|
|
pci_set_drvdata(pdev, NULL);
|
|
err:
|
|
pci_disable_device(pdev);
|
|
return ret;
|
|
}
|
|
|
|
static void toshsd_remove(struct pci_dev *pdev)
|
|
{
|
|
struct toshsd_host *host = pci_get_drvdata(pdev);
|
|
|
|
mmc_remove_host(host->mmc);
|
|
toshsd_powerdown(host);
|
|
free_irq(pdev->irq, host);
|
|
pci_iounmap(pdev, host->ioaddr);
|
|
pci_release_regions(pdev);
|
|
mmc_free_host(host->mmc);
|
|
pci_set_drvdata(pdev, NULL);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static const struct dev_pm_ops toshsd_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(toshsd_pm_suspend, toshsd_pm_resume)
|
|
};
|
|
|
|
static struct pci_driver toshsd_driver = {
|
|
.name = DRIVER_NAME,
|
|
.id_table = pci_ids,
|
|
.probe = toshsd_probe,
|
|
.remove = toshsd_remove,
|
|
.driver.pm = &toshsd_pm_ops,
|
|
};
|
|
|
|
module_pci_driver(toshsd_driver);
|
|
|
|
MODULE_AUTHOR("Ondrej Zary, Richard Betts");
|
|
MODULE_DESCRIPTION("Toshiba PCI Secure Digital Host Controller Interface driver");
|
|
MODULE_LICENSE("GPL");
|