Re: [PATCH v2] [RESEND] mmc: add Toshiba PCI SD controller driver
From: Ulf Hansson
Date: Tue Nov 04 2014 - 06:09:56 EST
On 2 November 2014 22:51, Ondrej Zary <linux@xxxxxxxxxxxxxxxxxxxx> wrote:
> This patch resurrects an old never-finished driver for Toshiba PCI SD
> controllers found in some older Toshiba laptops (such as Portege R100):
>
> 02:0d.0 System peripheral [0880]: Toshiba America Info Systems SD TypA Controller [1179:0805] (rev 05)
>
> The code is fixed, cleaned up and successfully tested with SD, SDHC, SDXC and
> MMC cards on Portege R100. (MMC cards don't even work in Windows!)
> SDIO probably does not work (don't have any SDIO card).
>
> The hardware is slow (around 2 MB/s - same in Windows) because it does not
> support bus mastering (busmaster enable bit cannot be set in PCI control reg).
> Also the card clock is limited to 16MHz (33MHz PCI clock divided by 2).
>
> Signed-off-by: Ondrej Zary <linux@xxxxxxxxxxxxxxxxxxxx>
Hi Ondrej,
Sorry for a very very late reply.
> ---
> drivers/mmc/host/Kconfig | 5 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/toshsd.c | 751 +++++++++++++++++++++++++++++++++++++++++++++
> drivers/mmc/host/toshsd.h | 178 +++++++++++
> 4 files changed, 935 insertions(+)
> create mode 100644 drivers/mmc/host/toshsd.c
> create mode 100644 drivers/mmc/host/toshsd.h
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 4511358..8cf31f5 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -727,3 +727,8 @@ config MMC_SUNXI
> help
> This selects support for the SD/MMC Host Controller on
> Allwinner sunxi SoCs.
> +
> +config MMC_TOSHIBA_PCI
> + tristate "Toshiba Type A SD/MMC Card Interface Driver"
> + depends on PCI
> + help
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index f211eed..39114be 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o
> obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o
> obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o
> obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o
> +obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o
>
> obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o
> obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
> diff --git a/drivers/mmc/host/toshsd.c b/drivers/mmc/host/toshsd.c
> new file mode 100644
> index 0000000..2ea1d76
> --- /dev/null
> +++ b/drivers/mmc/host/toshsd.c
> @@ -0,0 +1,751 @@
> +/*
> + * 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
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or (at
> + * your option) any later version.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/scatterlist.h>
> +#include <linux/interrupt.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);
> +static void toshsd_set_ios_unlocked(struct mmc_host *mmc, struct mmc_ios *ios);
I would implement these functions at the proper place instead of
having them defined here.
Moreover I think toshsd_set_ios_unlocked() could be renamed to
"__toshsd_set_ios()".
> +
> +static inline u16 toshsd_readw(struct toshsd_host *host, u16 reg)
> +{
> + return ioread16(host->ioaddr + reg);
> +}
> +
> +static inline u32 toshsd_readl(struct toshsd_host *host, u16 reg)
> +{
> + return ioread32(host->ioaddr + reg);
> +}
> +
> +static inline void toshsd_writew(struct toshsd_host *host, u16 reg, u16 val)
> +{
> + iowrite16(val, host->ioaddr + reg);
> +}
> +
> +static inline void toshsd_writel(struct toshsd_host *host, u16 reg, u32 val)
> +{
> + iowrite32(val, host->ioaddr + reg);
> +}
> +
> +static inline void toshsd_readl_rep(struct toshsd_host *host, u16 reg,
> + void *dst, unsigned long count)
> +{
> + ioread32_rep(host->ioaddr + reg, dst, count);
> +}
> +
> +static inline void toshsd_writel_rep(struct toshsd_host *host, u16 reg,
> + const void *src, unsigned long count)
> +{
> + iowrite32_rep(host->ioaddr + reg, src, count);
> +}
To me, all these wrapper functions seems a bit ugly. How about
invoking io* functions directly instead?
> +
> +static void toshsd_set_led(struct toshsd_host *host, unsigned char state)
> +{
> + toshsd_writew(host, SDIO_BASE + SDIO_LEDCTRL, state);
> +}
> +
> +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 void toshsd_data_transfer(unsigned long h)
> +{
> + struct toshsd_host *host = (struct toshsd_host *) h;
> + 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;
> + }
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (!sg_miter_next(sg_miter))
> + return;
> + 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)
> + toshsd_readl_rep(host, SD_DATAPORT, buf, count >> 2);
> + else
> + toshsd_writel_rep(host, SD_DATAPORT, buf, count >> 2);
> +
> + sg_miter->consumed = count;
> + sg_miter_stop(sg_miter);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +static void toshsd_cmd_irq(struct toshsd_host *host)
> +{
> + struct mmc_command *cmd = host->cmd;
> + u8 *buf = (u8 *) cmd->resp;
> + u16 data;
> +
> + if (!host->cmd) {
> + dev_warn(&host->pdev->dev, "Spurious CMD irq\n");
> + return;
> + }
> +
> + host->cmd = NULL;
> +
> + if (cmd->flags & MMC_RSP_PRESENT && cmd->flags & MMC_RSP_136) {
> + /* R2 */
> + buf[12] = 0xff;
> + data = toshsd_readw(host, SD_RESPONSE0);
> + buf[13] = data & 0xff;
> + buf[14] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE1);
> + buf[15] = data & 0xff;
> + buf[8] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE2);
> + buf[9] = data & 0xff;
> + buf[10] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE3);
> + buf[11] = data & 0xff;
> + buf[4] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE4);
> + buf[5] = data & 0xff;
> + buf[6] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE5);
> + buf[7] = data & 0xff;
> + buf[0] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE6);
> + buf[1] = data & 0xff;
> + buf[2] = data >> 8;
> + data = toshsd_readw(host, SD_RESPONSE7);
> + buf[3] = data & 0xff;
> + } else if (cmd->flags & MMC_RSP_PRESENT) {
> + /* R1, R1B, R3, R6, R7 */
> + data = toshsd_readw(host, SD_RESPONSE0);
> + buf[0] = data & 0xff;
> + buf[1] = data >> 8;
> + data = toshsd_readw(host, 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);
> +
> + toshsd_writew(host, SD_STOPINTERNAL, 0);
> +
> + 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;
> +
> + spin_lock(&host->lock);
> + int_status = toshsd_readl(host, SD_CARDSTATUS);
> + int_mask = toshsd_readl(host, 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) {
> + spin_unlock(&host->lock);
> + return IRQ_NONE;
> + }
> +
> + 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 = toshsd_readl(host, 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) {
> + toshsd_writel(host, SD_CARDSTATUS, int_status &
> + ~(SD_BUF_CMD_TIMEOUT | SD_CARD_RESP_END));
> + } else {
> + toshsd_init(host);
> + toshsd_set_ios_unlocked(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)) {
> + toshsd_writel(host, SD_CARDSTATUS, int_status &
> + ~(SD_CARD_CARD_REMOVED_0 |
> + SD_CARD_CARD_INSERTED_0));
> +
> + 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)) {
> + toshsd_writel(host, SD_CARDSTATUS, int_status &
> + ~(SD_BUF_WRITE_ENABLE | SD_BUF_READ_ENABLE));
> +
> + tasklet_schedule(&host->data_read_tasklet);
Instead of using a tasklet, I would advise to use a threaded IRQ.
> + }
> +
> + /* Command completion */
> + if (int_reg & SD_CARD_RESP_END) {
> + toshsd_writel(host, SD_CARDSTATUS, int_status &
> + ~(SD_CARD_RESP_END));
> + toshsd_cmd_irq(host);
> + }
> +
> + /* Data transfer completion */
> + if (int_reg & SD_CARD_RW_END) {
> + toshsd_writel(host, SD_CARDSTATUS, int_status &
> + ~(SD_CARD_RW_END));
> + toshsd_data_end_irq(host);
> + }
> +irq_end:
> + spin_unlock(&host->lock);
> + return IRQ_HANDLED;
> +}
> +
> +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) {
> + toshsd_writew(host, SD_STOPINTERNAL,
> + SD_STOPINT_ISSSUE_CMD12);
> +
> + 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_NORMAL;
> + 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) {
> + toshsd_writew(host, SD_STOPINTERNAL,
> + SD_STOPINT_AUTO_ISSUE_CMD12);
> + 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 */
> + toshsd_writel(host, SD_ARG0, cmd->arg);
> + toshsd_writew(host, SD_CMD, c);
> +}
> +
> +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 */
> + toshsd_writew(host, SD_BLOCKCOUNT, data->blocks);
> + toshsd_writew(host, SD_CARDXFERDATALEN, data->blksz);
> +}
> +
> +/* 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 (!(toshsd_readw(host, 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);
> +}
> +
> +/* 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_unlocked(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;
> + toshsd_writew(host, SD_CARDCLOCKCTRL, clk);
> +
> + mdelay(10);
> + } else
> + toshsd_writew(host, SD_CARDCLOCKCTRL, 0);
> +
> + 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:
> + toshsd_writew(host, SD_CARDOPTIONSETUP, SD_CARDOPT_REQUIRED
> + | SD_CARDOPT_DATA_RESP_TIMEOUT(14)
> + | SD_CARDOPT_C2_MODULE_ABSENT
> + | SD_CARDOPT_DATA_XFR_WIDTH_1);
> + break;
> + case MMC_BUS_WIDTH_4:
> + toshsd_writew(host, SD_CARDOPTIONSETUP, SD_CARDOPT_REQUIRED
> + | SD_CARDOPT_DATA_RESP_TIMEOUT(14)
> + | SD_CARDOPT_C2_MODULE_ABSENT
> + | SD_CARDOPT_DATA_XFR_WIDTH_4);
> + break;
> + }
> +}
> +
> +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_unlocked(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 !(toshsd_readw(host, SD_CARDSTATUS) & SD_CARD_WRITE_PROTECT);
> +}
> +
> +static int toshsd_get_cd(struct mmc_host *mmc)
> +{
> + struct toshsd_host *host = mmc_priv(mmc);
> +
> + return !!(toshsd_readw(host, SD_CARDSTATUS) & SD_CARD_PRESENT_0);
> +}
> +
> +static 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_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 */
> + toshsd_writew(host, SD_SOFTWARERESET, 0); /* assert */
> + mdelay(2);
> + toshsd_writew(host, SD_SOFTWARERESET, 1); /* deassert */
> + mdelay(2);
> +
> + /* Clear card registers */
> + toshsd_writew(host, SD_CARDCLOCKCTRL, 0);
> + toshsd_writel(host, SD_CARDSTATUS, 0);
> + toshsd_writel(host, SD_ERRORSTATUS0, 0);
> + toshsd_writew(host, SD_STOPINTERNAL, 0);
> +
> + /* SDIO clock? */
> + toshsd_writew(host, SDIO_BASE + SDIO_CLOCKNWAITCTRL, 0x100);
> +
> + /* 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 */
> + toshsd_writel(host, SD_INTMASKCARD, ~(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));
> +
> + toshsd_writew(host, SD_TRANSACTIONCTRL, 0x1000);
> +}
> +
> +static void toshsd_powerdown(struct toshsd_host *host)
> +{
> + /* mask all interrupts */
> + toshsd_writel(host, SD_INTMASKCARD, 0xffffffff);
> + /* disable card clock */
> + toshsd_writew(host, SDIO_BASE + SDIO_CLOCKNWAITCTRL, 0x000);
> + toshsd_writew(host, SD_CARDCLOCKCTRL, 0);
> + /* 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
This should be CONFIG_PM_SLEEP.
> +
> +static int toshsd_suspend(struct pci_dev *pdev, pm_message_t state)
This is the legacy version of system PM callbacks. You need to convert
to the modern ones instead.
> +{
> + struct toshsd_host *host = pci_get_drvdata(pdev);
> +
> + toshsd_powerdown(host);
> +
> + pci_save_state(pdev);
> + pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
> + pci_disable_device(pdev);
> + pci_set_power_state(pdev, pci_choose_state(pdev, state));
> +
> + return 0;
> +}
> +
> +static int toshsd_resume(struct pci_dev *pdev)
This is the legacy version of system PM callbacks. You need to convert
to the modern ones instead.
> +{
> + 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;
> +}
> +
> +#else /* CONFIG_PM */
> +
> +#define toshsd_suspend NULL
> +#define toshsd_resume NULL
You don't need these, instead you should use:
static SET_SYSTEM_SLEEP_PM_OPS(toshsd_suspend, toshsd_resume);
> +
> +#endif /* CONFIG_PM */
> +
> +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);
> +
> + tasklet_init(&host->data_read_tasklet, toshsd_data_transfer,
> + (unsigned long) host);
Again, use threaded IRQ instead.
> +
> + toshsd_init(host);
> +
> + ret = request_irq(pdev->irq, toshsd_irq, IRQF_SHARED, DRIVER_NAME,
> + host);
> + if (ret)
> + goto untasklet;
> +
> + mmc_add_host(mmc);
> +
> + base = pci_resource_start(pdev, 0);
> + dev_dbg(&pdev->dev, "MMIO %pa, IRQ %d\n", &base, pdev->irq);
> +
> + return 0;
> +
> +untasklet:
> + tasklet_kill(&host->data_read_tasklet);
> + 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);
> + tasklet_kill(&host->data_read_tasklet);
> + 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 struct pci_driver toshsd_driver = {
> + .name = DRIVER_NAME,
> + .id_table = pci_ids,
> + .probe = toshsd_probe,
> + .remove = toshsd_remove,
> + .suspend = toshsd_suspend,
> + .resume = toshsd_resume,
This is the legacy version of system PM callbacks. You need to convert
to the modern ones instead.
> +};
> +
> +static int __init toshsd_drv_init(void)
> +{
> + return pci_register_driver(&toshsd_driver);
> +}
> +
> +static void __exit toshsd_drv_exit(void)
> +{
> + pci_unregister_driver(&toshsd_driver);
> +}
> +
> +module_init(toshsd_drv_init);
> +module_exit(toshsd_drv_exit);
> +
> +MODULE_AUTHOR("Ondrej Zary, Richard Betts");
> +MODULE_DESCRIPTION("Toshiba PCI Secure Digital Host Controller Interface driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/mmc/host/toshsd.h b/drivers/mmc/host/toshsd.h
> new file mode 100644
> index 0000000..0b05aee
> --- /dev/null
> +++ b/drivers/mmc/host/toshsd.h
> @@ -0,0 +1,178 @@
> +/*
> + * 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
> + *
> + * 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.
> + */
> +
> +#define HCLK 33000000 /* 33 MHz (PCI clock) */
> +
> +#define SD_PCICFG_CLKSTOP 0x40 /* 0x1f = clock controller, 0 = stop */
> +#define SD_PCICFG_GATEDCLK 0x41 /* Gated clock */
> +#define SD_PCICFG_CLKMODE 0x42 /* Control clock of SD controller */
> +#define SD_PCICFG_PINSTATUS 0x44 /* R/O: read status of SD pins */
> +#define SD_PCICFG_POWER1 0x48
> +#define SD_PCICFG_POWER2 0x49
> +#define SD_PCICFG_POWER3 0x4a
> +#define SD_PCICFG_CARDDETECT 0x4c
> +#define SD_PCICFG_SLOTS 0x50 /* R/O: define support slot number */
> +#define SD_PCICFG_EXTGATECLK1 0xf0 /* Could be used for gated clock */
> +#define SD_PCICFG_EXTGATECLK2 0xf1 /* Could be used for gated clock */
> +#define SD_PCICFG_EXTGATECLK3 0xf9 /* Bit 1: double buffer/single buffer */
> +#define SD_PCICFG_SDLED_ENABLE1 0xfa
> +#define SD_PCICFG_SDLED_ENABLE2 0xfe
> +
> +#define SD_PCICFG_CLKMODE_DIV_DISABLE (1 << 0)
> +#define SD_PCICFG_CLKSTOP_ENABLE_ALL 0x1f
> +#define SD_PCICFG_LED_ENABLE1_START 0x12
> +#define SD_PCICFG_LED_ENABLE2_START 0x80
> +
> +#define SD_PCICFG_PWR1_33V 0x08 /* Set for 3.3 volts */
> +#define SD_PCICFG_PWR1_OFF 0x00 /* Turn off power */
> +#define SD_PCICFG_PWR2_AUTO 0x02
> +
> +#define SD_CMD 0x00 /* also for SDIO */
> +#define SD_ARG0 0x04 /* also for SDIO */
> +#define SD_ARG1 0x06 /* also for SDIO */
> +#define SD_STOPINTERNAL 0x08
> +#define SD_BLOCKCOUNT 0x0a /* also for SDIO */
> +#define SD_RESPONSE0 0x0c /* also for SDIO */
> +#define SD_RESPONSE1 0x0e /* also for SDIO */
> +#define SD_RESPONSE2 0x10 /* also for SDIO */
> +#define SD_RESPONSE3 0x12 /* also for SDIO */
> +#define SD_RESPONSE4 0x14 /* also for SDIO */
> +#define SD_RESPONSE5 0x16 /* also for SDIO */
> +#define SD_RESPONSE6 0x18 /* also for SDIO */
> +#define SD_RESPONSE7 0x1a /* also for SDIO */
> +#define SD_CARDSTATUS 0x1c /* also for SDIO */
> +#define SD_BUFFERCTRL 0x1e /* also for SDIO */
> +#define SD_INTMASKCARD 0x20 /* also for SDIO */
> +#define SD_INTMASKBUFFER 0x22 /* also for SDIO */
> +#define SD_CARDCLOCKCTRL 0x24
> +#define SD_CARDXFERDATALEN 0x26 /* also for SDIO */
> +#define SD_CARDOPTIONSETUP 0x28 /* also for SDIO */ toshsd_suspend
> +#define SD_ERRORSTATUS0 0x2c /* also for SDIO */ toshsd_suspend
> +#define SD_ERRORSTATUS1 0x2e /* also for SDIO */
> +#define SD_DATAPORT 0x30 /* also for SDIO */
> +#define SD_TRANSACTIONCTRL 0x34 /* also for SDIO */
> +#define SD_SOFTWARERESET 0xe0 /* also for SDIO */
> +
> +/* registers above marked "also for SDIO" and all SDIO registers below can be
> + * accessed at SDIO_BASE + reg address */
> +#define SDIO_BASE 0x100
> +
> +#define SDIO_CARDPORTSEL 0x02
> +#define SDIO_CARDINTCTRL 0x36
> +#define SDIO_CLOCKNWAITCTRL 0x38
> +#define SDIO_HOSTINFORMATION 0x3a
> +#define SDIO_ERRORCTRL 0x3c
> +#define SDIO_LEDCTRL 0x3e
> +
> +#define SD_TRANSCTL_SET (1 << 8)
> +
> +#define SD_CARDCLK_DIV_DISABLE (1 << 15)
> +#define SD_CARDCLK_ENABLE_CLOCK (1 << 8)
> +#define SD_CARDCLK_CLK_DIV_512 (1 << 7)
> +#define SD_CARDCLK_CLK_DIV_256 (1 << 6)
> +#define SD_CARDCLK_CLK_DIV_128 (1 << 5)
> +#define SD_CARDCLK_CLK_DIV_64 (1 << 4)
> +#define SD_CARDCLK_CLK_DIV_32 (1 << 3)
> +#define SD_CARDCLK_CLK_DIV_16 (1 << 2)
> +#define SD_CARDCLK_CLK_DIV_8 (1 << 1)
> +#define SD_CARDCLK_CLK_DIV_4 (1 << 0)
> +#define SD_CARDCLK_CLK_DIV_2 (0 << 0)
> +
> +#define SD_CARDOPT_REQUIRED 0x000e
> +#define SD_CARDOPT_DATA_RESP_TIMEOUT(x) (((x) & 0x0f) << 4) /* 4 bits */
> +#define SD_CARDOPT_C2_MODULE_ABSENT (1 << 14)
> +#define SD_CARDOPT_DATA_XFR_WIDTH_1 (1 << 15)
> +#define SD_CARDOPT_DATA_XFR_WIDTH_4 (0 << 15)
> +
> +#define SD_CMD_TYPE_CMD (0 << 6)
> +#define SD_CMD_TYPE_ACMD (1 << 6)
> +#define SD_CMD_TYPE_AUTHEN (2 << 6)
> +#define SD_CMD_RESP_TYPE_NORMAL (0 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R1 (4 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R1B (5 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R2 (6 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R3 (7 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R6 (4 << 8)
> +#define SD_CMD_RESP_TYPE_EXT_R7 (4 << 8)
> +#define SD_CMD_DATA_PRESENT (1 << 11)
> +#define SD_CMD_TRANSFER_READ (1 << 12)
> +#define SD_CMD_MULTI_BLOCK (1 << 13)
> +#define SD_CMD_SECURITY_CMD (1 << 14)
> +
> +#define SD_STOPINT_ISSSUE_CMD12 (1 << 0)
> +#define SD_STOPINT_AUTO_ISSUE_CMD12 (1 << 8)
> +
> +#define SD_CARD_RESP_END (1 << 0)
> +#define SD_CARD_RW_END (1 << 2)
> +#define SD_CARD_CARD_REMOVED_0 (1 << 3)
> +#define SD_CARD_CARD_INSERTED_0 (1 << 4)
> +#define SD_CARD_PRESENT_0 (1 << 5)
> +#define SD_CARD_UNK6 (1 << 6)
> +#define SD_CARD_WRITE_PROTECT (1 << 7)
> +#define SD_CARD_CARD_REMOVED_3 (1 << 8)
> +#define SD_CARD_CARD_INSERTED_3 (1 << 9)
> +#define SD_CARD_PRESENT_3 (1 << 10)
> +
> +#define SD_BUF_CMD_INDEX_ERR (1 << 16)
> +#define SD_BUF_CRC_ERR (1 << 17)
> +#define SD_BUF_STOP_BIT_END_ERR (1 << 18)
> +#define SD_BUF_DATA_TIMEOUT (1 << 19)
> +#define SD_BUF_OVERFLOW (1 << 20)
> +#define SD_BUF_UNDERFLOW (1 << 21)
> +#define SD_BUF_CMD_TIMEOUT (1 << 22)
> +#define SD_BUF_UNK7 (1 << 23)
> +#define SD_BUF_READ_ENABLE (1 << 24)
> +#define SD_BUF_WRITE_ENABLE (1 << 25)
> +#define SD_BUF_ILLEGAL_FUNCTION (1 << 29)
> +#define SD_BUF_CMD_BUSY (1 << 30)
> +#define SD_BUF_ILLEGAL_ACCESS (1 << 31)
> +
> +#define SD_ERR0_RESP_CMD_ERR (1 << 0)
> +#define SD_ERR0_RESP_NON_CMD12_END_BIT_ERR (1 << 2)
> +#define SD_ERR0_RESP_CMD12_END_BIT_ERR (1 << 3)
> +#define SD_ERR0_READ_DATA_END_BIT_ERR (1 << 4)
> +#define SD_ERR0_WRITE_CRC_STATUS_END_BIT_ERR (1 << 5)
> +#define SD_ERR0_RESP_NON_CMD12_CRC_ERR (1 << 8)
> +#define SD_ERR0_RESP_CMD12_CRC_ERR (1 << 9)
> +#define SD_ERR0_READ_DATA_CRC_ERR (1 << 10)
> +#define SD_ERR0_WRITE_CMD_CRC_ERR (1 << 11)
> +
> +#define SD_ERR1_NO_CMD_RESP (1 << 16)
> +#define SD_ERR1_TIMEOUT_READ_DATA (1 << 20)
> +#define SD_ERR1_TIMEOUT_CRS_STATUS (1 << 21)
> +#define SD_ERR1_TIMEOUT_CRC_BUSY (1 << 22)
> +
> +#define IRQ_DONT_CARE_BITS (SD_CARD_PRESENT_3 \
> + | SD_CARD_WRITE_PROTECT \
> + | SD_CARD_UNK6 \
> + | SD_CARD_PRESENT_0 \
> + | SD_BUF_UNK7 \
> + | SD_BUF_CMD_BUSY)
> +
> +struct toshsd_host {
> + struct pci_dev *pdev;
> + struct mmc_host *mmc;
> +
> + spinlock_t lock;
> +
> + struct mmc_request *mrq;/* Current request */
> + struct mmc_command *cmd;/* Current command */
> + struct mmc_data *data; /* Current data request */
> +
> + struct sg_mapping_iter sg_miter; /* for PIO */
> +
> + void __iomem *ioaddr; /* mapped address */
> +
> + struct tasklet_struct data_read_tasklet;
> +};
> --
> Ondrej Zary
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
Kind regards
Uffe
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/