Re: [RFC PATCH v3 1/3] mmc: sprd: Add MMC host driver for Spreadtrum SoC
From: Ulf Hansson
Date: Thu Sep 10 2015 - 09:28:39 EST
On 14 August 2015 at 18:55, Hongtao Wu <wuht06@xxxxxxxxx> wrote:
> the Spreadtrum MMC host driver is used to supply EMMC, SD, and
> SDIO types of memory cards
Perhaps some more information about the controller. Are there any
specific features it support or doesn't support!?
Moreover it would be nice to know a bit more what this patch contains.
For example, you have some debugfs support, what's that?
>
> Signed-off-by: Billows Wu(WuHongtao) <wuht06@xxxxxxxxx>
> ---
> drivers/mmc/host/Kconfig | 6 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/sprd_sdhost.c | 1202 ++++++++++++++++++++++++++++++++
> drivers/mmc/host/sprd_sdhost.h | 615 ++++++++++++++++
Just by looking at number of lines here, it tells me that the size of
header file is just way too big. It's half the size of the c-file,
that just can't be right. :-)
Just to make it clear. I don't like "one-liner" wrapper functions nor
macros. If you remove these kind of stuff, the total size would shrink
significantly I belive.
> drivers/mmc/host/sprd_sdhost_debugfs.c | 212 ++++++
> drivers/mmc/host/sprd_sdhost_debugfs.h | 27 +
I had a brief look at the debugfs support, let me suggest that we
handle that in a separate patch.
Perhaps, some of that code can also be made more generic and used by
the mmc core instead.
> 6 files changed, 2063 insertions(+)
> create mode 100644 drivers/mmc/host/sprd_sdhost.c
> create mode 100644 drivers/mmc/host/sprd_sdhost.h
> create mode 100644 drivers/mmc/host/sprd_sdhost_debugfs.c
> create mode 100644 drivers/mmc/host/sprd_sdhost_debugfs.h
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index fd9a58e..c43d938 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -264,6 +264,12 @@ config MMC_SDHCI_SPEAR
>
> If you have a controller with this interface, say Y or M here.
>
> +config SPRD_MMC_SDHOST
> + tristate "Spreadtrum SDIO host Controller support"
> + help
> + This selects the SDIO Host Controller in spreadtrum platform
> +
> + If you have a controller with this interface, say Y or M here.
> If unsure, say N.
>
> config MMC_SDHCI_S3C_DMA
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index e928d61..e00227f 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -74,6 +74,7 @@ obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o
> obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o
> obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
> obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
> +obj-$(CONFIG_SPRD_MMC_SDHOST) += sprd_sdhost.o sprd_sdhost_debugfs.o
>
> ifeq ($(CONFIG_CB710_DEBUG),y)
> CFLAGS-cb710-mmc += -DDEBUG
> diff --git a/drivers/mmc/host/sprd_sdhost.c b/drivers/mmc/host/sprd_sdhost.c
> new file mode 100644
> index 0000000..95639a3
> --- /dev/null
> +++ b/drivers/mmc/host/sprd_sdhost.c
> @@ -0,0 +1,1202 @@
> +/*
> + * linux/drivers/mmc/host/sprd_sdhost.c - Secure Digital Host Controller
> + * Interface driver
> + *
> + * Copyright (C) 2015 Spreadtrum corporation.
> + *
> + * 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/dma-mapping.h>
> +#include <linux/highmem.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/scatterlist.h>
> +
> +#include "sprd_sdhost.h"
> +#include "sprd_sdhost_debugfs.h"
> +
> +#define DRIVER_NAME "sdhost"
> +#define SDHOST_CAPS \
Please remove this define it makes it harder to read the code.
> + (MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | \
> + MMC_CAP_ERASE | MMC_CAP_UHS_SDR50 | \
> + MMC_CAP_CMD23 | MMC_CAP_HW_RESET)
> +
> +struct sdhost_caps_data {
> + char *name;
> + u32 ocr_avail;
> + u32 caps;
> + u32 caps2;
> + u32 pm_caps;
> + u32 base_clk;
> + u32 signal_default_voltage;
> +};
I don't think you need this struct, as most of this data is already
present it struct mmc_host. And for that small pieces that isn't you
might as well just add into your struct sdhost_host.
> +
> +struct sdhost_caps_data caps_info_map[] = {
This is the wrong way of how to assign capabilties. Instead
mmc_of_parse() parses your DT configuration and assign the
corresponding caps in the struct mmc_host.
> + {
> + .name = "sd",
> + .ocr_avail = MMC_VDD_29_30 | MMC_VDD_30_31,
ocr_avail should be fetched from external regulators via
mmc_regulator_get_supply() API. I don't think you need this at all
then, right?
> + .caps = SDHOST_CAPS,
> + .caps2 = MMC_CAP2_HC_ERASE_SZ,
> + .signal_default_voltage = 3000000,
Again, if this is needed you shall use the regulator API to find out
this information.
> + },
> + {
> + .name = "wifi",
> + .ocr_avail = MMC_VDD_165_195 | MMC_VDD_29_30 |
> + MMC_VDD_30_31 | MMC_VDD_32_33 | MMC_VDD_33_34,
> + .caps = SDHOST_CAPS | MMC_CAP_POWER_OFF_CARD |
> + MMC_CAP_UHS_SDR12,
> + },
> + {
> + .name = "emmc",
> + .ocr_avail = MMC_VDD_29_30 | MMC_VDD_30_31,
> + .caps = SDHOST_CAPS |
> + MMC_CAP_8_BIT_DATA | MMC_CAP_UHS_SDR12 |
> + MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_DDR50 |
> + MMC_CAP_MMC_HIGHSPEED,
> + .signal_default_voltage = 1800000,
> + }
> +};
> +
> +#ifdef CONFIG_PM_RUNTIME
Regarding the system PM and runtime PM support, please remove that
code from $subject patch and let's add that as a separate patch
instead.
The reason is simply that I see way too many strange things going on
in that part of code, so I decided that it's easier for me to comment
on them later and separately.
> +static void _pm_runtime_setting(struct platform_device *pdev,
> + struct sdhost_host *host);
> +#else
> +static void _pm_runtime_setting(struct platform_device *pdev,
> + struct sdhost_host *host)
> +{
> +}
> +#endif
> +
> +static void _reset_ios(struct sdhost_host *host)
> +{
> + _sdhost_disable_all_int(host);
> +
> + host->ios.clock = 0;
> + host->ios.vdd = 0;
> + host->ios.power_mode = MMC_POWER_OFF;
> + host->ios.bus_width = MMC_BUS_WIDTH_1;
> + host->ios.timing = MMC_TIMING_LEGACY;
> + host->ios.signal_voltage = MMC_SIGNAL_VOLTAGE_330;
> +
> + _sdhost_reset(host, _RST_ALL);
> + _sdhost_set_delay(host, host->write_delay,
> + host->read_pos_delay, host->read_neg_delay);
> +}
> +
> +static int __local_pm_suspend(struct sdhost_host *host)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&host->lock, flags);
> + _sdhost_disable_all_int(host);
> + _sdhost_all_clk_off(host);
> + /* wake lock */
> + spin_unlock_irqrestore(&host->lock, flags);
> + clk_disable(host->clk);
> + clk_unprepare(host->clk);
> + synchronize_irq(host->irq);
> +
> + return 0;
> +}
> +
> +static int __local_pm_resume(struct sdhost_host *host)
> +{
> + unsigned long flags;
> +
> + clk_prepare(host->clk);
> + clk_enable(host->clk);
> + spin_lock_irqsave(&host->lock, flags);
> + if (host->ios.clock) {
> + _sdhost_sd_clk_off(host);
> + _sdhost_clk_set_and_on(host,
> + _sdhost_calc_div(host->base_clk,
> + host->ios.clock));
> + _sdhost_sd_clk_on(host);
> + }
> + _sdhost_set_delay(host, host->write_delay,
> + host->read_pos_delay, host->read_neg_delay);
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_RUNTIME
> +static void _pm_runtime_setting(struct platform_device *pdev,
> + struct sdhost_host *host)
> +{
> + pm_runtime_set_active(&pdev->dev);
> + pm_suspend_ignore_children(&pdev->dev, true);
> + pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
> + pm_runtime_use_autosuspend(&pdev->dev);
> + pm_runtime_enable(&pdev->dev);
> +}
> +#endif
> +
> +static int _runtime_get(struct sdhost_host *host)
> +{
> + return pm_runtime_get_sync(host->mmc->parent);
> +}
> +
> +static int _runtime_put(struct sdhost_host *host)
> +{
> + pm_runtime_mark_last_busy(host->mmc->parent);
> + return pm_runtime_put_autosuspend(host->mmc->parent);
> +}
> +
> +#ifdef CONFIG_PM_RUNTIME
> +static int _runtime_suspend(struct device *dev)
> +{
> + struct platform_device *pdev =
> + container_of(dev, struct platform_device, dev);
> + struct sdhost_host *host = platform_get_drvdata(pdev);
> +
> + return __local_pm_suspend(host);
> +}
> +
> +static int _runtime_resume(struct device *dev)
> +{
> + struct platform_device *pdev =
> + container_of(dev, struct platform_device, dev);
> + struct sdhost_host *host = platform_get_drvdata(pdev);
> +
> + return __local_pm_resume(host);
> +}
> +
> +static int _runtime_idle(struct device *dev)
> +{
> + return 0;
> +}
> +#endif
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int _pm_suspend(struct device *dev)
> +{
> + struct platform_device *pdev =
> + container_of(dev, struct platform_device, dev);
> + struct sdhost_host *host = platform_get_drvdata(pdev);
> +
> + _runtime_get(host);
> + host->mmc->pm_flags = host->mmc->pm_caps;
> +
> + return __local_pm_suspend(host);
> +}
> +
> +static int _pm_resume(struct device *dev)
> +{
> + struct platform_device *pdev =
> + container_of(dev, struct platform_device, dev);
> + struct sdhost_host *host = platform_get_drvdata(pdev);
> + struct mmc_ios ios;
> +
> + __local_pm_resume(host);
> +
> + ios = host->mmc->ios;
> + _reset_ios(host);
> + host->mmc->ops->set_ios(host->mmc, &ios);
> + _runtime_put(host);
> +
> + return 0;
> +}
> +#endif
> +
> +static void __get_rsp(struct sdhost_host *host)
> +{
> + u32 i, offset;
> + unsigned int flags = host->cmd->flags;
> + u32 *resp = host->cmd->resp;
> +
> + if (!(flags & MMC_RSP_PRESENT))
> + return;
> +
> + if (flags & MMC_RSP_136) {
> + /* CRC is stripped so we need to do some shifting. */
> + for (i = 0, offset = 12; i < 3; i++, offset -= 4) {
> + resp[i] =
> + _sdhost_readl(host,
Just to make it clear around what I mean with "one-liner" wrapprer
functions, _sdhost_readl() is a perfect example.
_sdhost_readl() calls __local_readl() which calls readl_relaxed().
This is crazy and completely unnecessary complicated. Please just call
readl_relaxed() here, since it helps me review and understand what's
going on in the code.
As there are many examples of similar cases, please simplify the code
according to my suggestions.
> + SDHOST_32_RESPONSE + offset) << 8;
> + resp[i] |=
> + _sdhost_readb(host,
> + SDHOST_32_RESPONSE + offset - 1);
> + }
> + resp[3] = _sdhost_readl(host, SDHOST_32_RESPONSE) << 8;
> + } else {
> + resp[0] = _sdhost_readl(host, SDHOST_32_RESPONSE);
> + }
> +}
> +
> +static void _send_cmd(struct sdhost_host *host, struct mmc_command *cmd)
> +{
> + struct mmc_data *data = cmd->data;
> + int sg_cnt;
> + u32 flag = 0;
> + u16 rsp_type = 0;
> + int if_has_data = 0;
> + int if_mult = 0;
> + int if_read = 0;
> + int if_dma = 0;
> + u16 auto_cmd = __ACMD_DIS;
> +
> + pr_debug("%s(%s) CMD%d, arg 0x%x, flag 0x%x\n", __func__,
> + host->device_name, cmd->opcode, cmd->arg, cmd->flags);
> + if (cmd->data)
> + pr_debug("%s(%s) block size %d, cnt %d\n", __func__,
> + host->device_name, cmd->data->blksz, cmd->data->blocks);
> +
> + _sdhost_disable_all_int(host);
Do you really need to turn the IRQs on/off between each an every
command? If not, it would save you from reading/writing to the
registers a few times per each commands.
> +
> + if (MMC_ERASE == cmd->opcode) {
I guess you might already know that the mmc core suffers from a few
bugs around how to deal with erase/trim/discard. And it's related to
the max_busy_timeout value.
Now, this fix you have here is okay for now, but we should really fix
the problem in the core. Host drivers shouldn't have to care about
this as a specific command.
> + /* if it is erase command , it's busy time will long,
> + * so we set long timeout value here.
> + */
> + mod_timer(&host->timer, jiffies +
> + msecs_to_jiffies(host->mmc->max_busy_timeout + 1000));
> + _sdhost_writeb(host->ioaddr,
> + __DATA_TIMEOUT_MAX_VAL, SDHOST_8_TIMEOUT);
> + } else {
> + mod_timer(&host->timer,
> + jiffies + (SDHOST_MAX_TIMEOUT + 1) * HZ);
> + _sdhost_writeb(host, host->data_timeout_val, SDHOST_8_TIMEOUT);
> + }
> +
> + host->cmd = cmd;
> + if (data) {
> + /* set data param */
> + WARN_ON((data->blksz * data->blocks > 524288) ||
> + (data->blksz > host->mmc->max_blk_size) ||
> + (data->blocks > 65535));
> +
> + data->bytes_xfered = 0;
> +
> + if_has_data = 1;
> + if_read = (data->flags & MMC_DATA_READ);
> + if_mult = (mmc_op_multi(cmd->opcode) || data->blocks > 1);
> + if (if_read && !if_mult)
> + flag = _DATA_FILTER_RD_SIGLE;
> + else if (if_read && if_mult)
> + flag = _DATA_FILTER_RD_MULTI;
> + else if (!if_read && !if_mult)
> + flag = _DATA_FILTER_WR_SIGLE;
> + else
> + flag = _DATA_FILTER_WR_MULT;
> +
> + if (!host->auto_cmd_mode)
> + flag |= _INT_ERR_ACMD;
> +
> + if_dma = 1;
> + auto_cmd = host->auto_cmd_mode;
> + _sdhost_set_blk_size(host, data->blksz);
> +
> + sg_cnt = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
> + (data->flags & MMC_DATA_READ) ?
> + DMA_FROM_DEVICE : DMA_TO_DEVICE);
> + if (1 == sg_cnt) {
> + _sdhost_set_dma(host, __SDMA_MOD);
> + _sdhost_set_16_blk_cnt(host, data->blocks);
> + _sdhost_writel(host, sg_dma_address(data->sg),
> + SDHOST_32_SYS_ADDR);
> + } else {
> + flag |= _INT_ERR_ADMA;
> + _sdhost_set_dma(host, __32ADMA_MOD);
> + _sdhost_set_32_blk_cnt(host, data->blocks);
> + _sdhost_writel(host, sg_dma_address(data->sg),
> + SDHOST_32_SYS_ADDR);
> + }
> + }
> +
> + _sdhost_writel(host, cmd->arg, SDHOST_32_ARG);
> + switch (mmc_resp_type(cmd)) {
> + case MMC_RSP_R1B:
> + rsp_type = _RSP1B_5B;
> + flag |= _CMD_FILTER_R1B;
> + break;
> + case MMC_RSP_NONE:
> + rsp_type = _RSP0;
> + flag |= _CMD_FILTER_R0;
> + break;
> + case MMC_RSP_R2:
> + rsp_type = _RSP2;
> + flag |= _CMD_FILTER_R2;
> + break;
> + case MMC_RSP_R4:
> + rsp_type = _RSP3_4;
> + flag |= _CMD_FILTER_R1_R4_R5_R6_R7;
> + break;
> + case MMC_RSP_R1:
> + case MMC_RSP_R1 & ~MMC_RSP_CRC:
> + rsp_type = _RSP1_5_6_7;
> + flag |= _CMD_FILTER_R1_R4_R5_R6_R7;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> +
> + host->int_filter = flag;
> + _sdhost_enable_int(host, flag);
> + pr_debug("sdhost %s CMD%d rsp:0x%x intflag:0x%x\n"
> + "if_mult:0x%x if_read:0x%x auto_cmd:0x%x if_dma:0x%x\n",
> + host->device_name, cmd->opcode, mmc_resp_type(cmd),
> + flag, if_mult, if_read, auto_cmd, if_dma);
> +
> + _sdhost_set_trans_and_cmd(host, if_mult, if_read, auto_cmd, if_mult,
> + if_dma, cmd->opcode, if_has_data, rsp_type);
I don't like think this kind of wrapper function either. It's just way
too hard to review and understand.
Instead I would prefer if _send_cmd() builds the register value in a
u32 variable itself. Then it writes that value by invoking
writel_relaxed().
Please follow my suggestion for other similar cases as well.
> +}
> +
> +static irqreturn_t _irq(int irq, void *param)
> +{
Please consider to split up this function in smaller sub-functions, it
would help me to review.
> + u32 intmask;
> + struct sdhost_host *host = (struct sdhost_host *)param;
> + struct mmc_request *mrq = host->mrq;
> + struct mmc_command *cmd = host->cmd;
> + struct mmc_data *data;
> +
> + spin_lock(&host->lock);
> + /* maybe _timeout() run in one core and _irq() run in
> + * another core, this will panic if access cmd->data
> + */
> + if ((!mrq) || (!cmd)) {
> + spin_unlock(&host->lock);
> + return IRQ_NONE;
> + }
> + data = cmd->data;
> +
> + intmask = _sdhost_readl(host, SDHOST_32_INT_ST);
> + if (!intmask) {
> + spin_unlock(&host->lock);
> + return IRQ_NONE;
> + }
> + pr_debug("%s(%s) CMD%d, intmask 0x%x, filter = 0x%x\n", __func__,
> + host->device_name, cmd->opcode, intmask, host->int_filter);
> +
> + /* sometimes an undesired interrupt will happen, so we must clear
> + * this unused interrupt.
> + */
> + _sdhost_clear_int(host, intmask);
> + /* just care about the interrupt that we want */
> + intmask &= host->int_filter;
> +
> + while (intmask) {
> + if (_INT_FILTER_ERR & intmask) {
> + /* some error happened in command */
> + if (_INT_FILTER_ERR_CMD & intmask) {
> + if (_INT_ERR_CMD_TIMEOUT & intmask)
> + cmd->error = -ETIMEDOUT;
> + else
> + cmd->error = -EILSEQ;
> + }
> + /* some error happened in data token or command
> + * with R1B
> + */
> + if (_INT_FILTER_ERR_DATA & intmask) {
> + if (data) {
> + /* current error is happened in data
> + * token
> + */
> + if (_INT_ERR_DATA_TIMEOUT & intmask)
> + data->error = -ETIMEDOUT;
> + else
> + data->error = -EILSEQ;
> + } else {
> + /* current error is happend in response
> + * with busy
> + */
> + if (_INT_ERR_DATA_TIMEOUT & intmask)
> + cmd->error = -ETIMEDOUT;
> + else
> + cmd->error = -EILSEQ;
> + }
> + }
> + if (_INT_ERR_ACMD & intmask) {
> + /* Auto cmd12 and cmd23 error is belong to data
> + * token error
> + */
> + data->error = -EILSEQ;
> + }
> + if (_INT_ERR_ADMA & intmask)
> + data->error = -EIO;
> +
> + pr_debug("sdhost %s int 0x%x\n", host->device_name,
> + intmask);
> + dump_sdio_reg(host);
> + _sdhost_disable_all_int(host);
> + /* if current error happened in data token,
> + * we send cmd12 to stop it
> + */
> + if ((mrq->cmd == cmd) && (mrq->stop)) {
> + _sdhost_reset(host, _RST_CMD | _RST_DATA);
> + _send_cmd(host, mrq->stop);
> + } else {
> + /* request finish with error, so reset it and
> + * stop the request
> + */
> + _sdhost_reset(host, _RST_CMD | _RST_DATA);
> + tasklet_schedule(&host->finish_tasklet);
Instead of using tasklets, please convert to threaded IRQ handlers.
> + }
> + goto out;
> + } else {
> + /* delete irq that wanted in filter */
> + host->int_filter &= ~(_INT_FILTER_NORMAL & intmask);
> + if (_INT_DMA_END & intmask) {
> + _sdhost_writel(host,
> + _sdhost_readl(host, SDHOST_32_SYS_ADDR),
> + SDHOST_32_SYS_ADDR);
> + }
> + if (_INT_CMD_END & intmask) {
> + cmd->error = 0;
> + __get_rsp(host);
> + }
> + if (_INT_TRAN_END & intmask) {
> + if (data) {
> + dma_unmap_sg(mmc_dev(host->mmc),
> + data->sg, data->sg_len,
> + (data->flags & MMC_DATA_READ) ?
> + DMA_FROM_DEVICE :
> + DMA_TO_DEVICE);
> + data->error = 0;
> + data->bytes_xfered =
> + data->blksz * data->blocks;
> + } else {
> + /* R1B also can produce transferComplete
> + * interrupt
> + */
> + cmd->error = 0;
> + }
> + }
> + if (!(_INT_FILTER_NORMAL & host->int_filter)) {
> + /* current cmd finished */
> + _sdhost_disable_all_int(host);
> + if (mrq->sbc == cmd) {
> + _send_cmd(host, mrq->cmd);
> + } else if ((mrq->cmd == host->cmd)
> + && (mrq->stop)) {
> + _send_cmd(host, mrq->stop);
> + } else {
> + /* finish with success and stop the
> + * request
> + */
> + tasklet_schedule(&host->finish_tasklet);
> + goto out;
> + }
> + }
> + }
> +
> + intmask = _sdhost_readl(host, SDHOST_32_INT_ST);
> + _sdhost_clear_int(host, intmask);
> + intmask &= host->int_filter;
> + };
> +
> +out:
> + spin_unlock(&host->lock);
> + return IRQ_HANDLED;
> +}
> +
> +static void _tasklet(unsigned long param)
As already stated, please convert to threaded IRQ handlers in favor of tasklets.
> +{
> + struct sdhost_host *host = (struct sdhost_host *)param;
> + unsigned long flags;
> + struct mmc_request *mrq;
> +
> + del_timer(&host->timer);
> +
> + spin_lock_irqsave(&host->lock, flags);
> + if (!host->mrq) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + return;
> + }
> + mrq = host->mrq;
> + host->mrq = NULL;
> + host->cmd = NULL;
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + pr_debug("sdhost %s cmd %d data %d\n",
> + host->device_name, mrq->cmd->error,
> + ((!!mrq->cmd->data) ? mrq->cmd->data->error : 0));
> + mmc_request_done(host->mmc, mrq);
> + _runtime_put(host);
> +}
> +
> +static void _timeout(unsigned long data)
> +{
> + struct sdhost_host *host = (struct sdhost_host *)data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&host->lock, flags);
> + if (host->mrq) {
> + pr_info("sdhost %s Timeout waiting for hardware interrupt!\n",
> + host->device_name);
> + dump_sdio_reg(host);
> + if (host->cmd->data)
> + host->cmd->data->error = -ETIMEDOUT;
> + else if (host->cmd)
> + host->cmd->error = -ETIMEDOUT;
> + else
> + host->mrq->cmd->error = -ETIMEDOUT;
> +
> + _sdhost_disable_all_int(host);
> + _sdhost_reset(host, _RST_CMD | _RST_DATA);
> + tasklet_schedule(&host->finish_tasklet);
> + }
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +static void sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> +
> + host->mrq = mrq;
> + /* 1 find whether card is still in slot */
> + if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE)) {
> + if (!mmc_gpio_get_cd(host->mmc)) {
This means for every new request you will be reading a gpio line to
see if the card is still there. That's going to be highly inefficient
and affecting performance.
I would instead leave this to be entirely controlled by the mmc core.
Typically what's needed from your controller would be that it provides
an IRQ for a CMD timeout reasonably fast, in the case when card has
been removed. Then you will be okay.
> + mrq->cmd->error = -ENOMEDIUM;
> + tasklet_schedule(&host->finish_tasklet);
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + return;
> + }
> + /* else asume sdcard is present */
> + }
> +
> + /*
> + * in our control we can not use auto cmd12 and auto cmd23 together
> + * so in following program we use auto cmd23 prior to auto cmd12
> + */
I don't really follow what's the issue is here. Can you perhaps elaborate a bit?
I noticed that you have set MMC_CAP_CMD23, which indicates to the mmc
core that it shall use mrq->sbc when creating reqeusts.
Now, are you saying that you controller internally handles CMD12 when
using the CMD23 way of reading/writing data?
I don't like that you need to change the values of the mrq->data. If
special treatment is needed, it's better to indicate that through a
new mmc cap to the mmc core, somehow...
> + pr_debug("%s(%s) CMD%d request %d %d %d\n",
> + __func__, host->device_name, mrq->cmd->opcode,
> + !!mrq->sbc, !!mrq->cmd, !!mrq->stop);
> + host->auto_cmd_mode = __ACMD_DIS;
> + if (!mrq->sbc && mrq->stop && SDHOST_FLAG_ENABLE_ACMD12) {
> + host->auto_cmd_mode = __ACMD12;
> + mrq->data->stop = NULL;
> + mrq->stop = NULL;
> + }
> +
> + /* 3 send cmd list */
> + if ((mrq->sbc) && SDHOST_FLAG_ENABLE_ACMD23) {
> + host->auto_cmd_mode = __ACMD23;
> + mrq->data->stop = NULL;
> + mrq->stop = NULL;
> + _send_cmd(host, mrq->cmd);
> + } else if (mrq->sbc) {
> + mrq->data->stop = NULL;
> + mrq->stop = NULL;
> + _send_cmd(host, mrq->sbc);
> + } else {
> + _send_cmd(host, mrq->cmd);
> + }
> +
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +static void _signal_voltage_on_off(struct sdhost_host *host, u32 on_off)
> +{
> + if (!host->mmc->supply.vqmmc) {
> + pr_debug("%s(%s) there is no signal voltage!\n",
> + __func__, host->device_name);
> + return;
> + }
> +
> + if (on_off && (!host->sdio_1_8v_signal_enabled)) {
> + if (!regulator_enable(host->mmc->supply.vqmmc) &&
> + regulator_is_enabled(host->mmc->supply.vqmmc)) {
> + host->sdio_1_8v_signal_enabled = true;
> + pr_debug("%s(%s) signal voltage enable success!\n",
> + __func__, host->device_name);
> + } else
> + pr_debug("%s(%s) signal voltage enable fail!\n",
> + __func__, host->device_name);
> +
> + } else if (!on_off && host->sdio_1_8v_signal_enabled) {
> + if (!regulator_disable(host->mmc->supply.vqmmc) &&
> + !regulator_is_enabled(host->mmc->supply.vqmmc)) {
> + host->sdio_1_8v_signal_enabled = false;
> + pr_debug("%s(%s) signal voltage disable success!\n",
> + __func__, host->device_name);
> + } else
> + pr_debug("%s(%s) signal voltage disable fail\n",
> + __func__, host->device_name);
> + }
> +}
> +
> +/*
> + * 1 This votage is always poweron
> + * 2 initial votage is 2.7v~3.6v
> + * 3 It can be reconfig to 1.7v~1.95v
> + */
> +static int sdhost_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> + int err;
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (!mmc->supply.vqmmc) {
> + /* there are no 1.8v signal votage. */
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> + err = 0;
> + pr_debug("sdhost %s There is no signalling voltage\n",
> + host->device_name);
> + return err;
> + }
> +
> + /* I/O power supply */
> + if (ios->signal_voltage == host->ios.signal_voltage) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> + return 0;
> + }
> +
> + switch (ios->signal_voltage) {
> + case MMC_SIGNAL_VOLTAGE_330:
> + err = regulator_set_voltage(mmc->supply.vqmmc,
> + 3000000, 3000000);
> + break;
> + case MMC_SIGNAL_VOLTAGE_180:
> + err = regulator_set_voltage(mmc->supply.vqmmc,
> + 1800000, 1800000);
> + break;
> + case MMC_SIGNAL_VOLTAGE_120:
> + err = regulator_set_voltage(mmc->supply.vqmmc,
> + 1100000, 1300000);
> + break;
> + default:
> + err = -EIO;
> + break;
> + }
> + if (likely(!err))
> + host->ios.signal_voltage = ios->signal_voltage;
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> +
> + if (err)
> + WARN(err, "Switching to signalling voltage failed\n");
> +
> + return err;
> +}
> +
> +static void sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> +
> + pr_debug("%s(%s) ios:\n"
> + "sdhost clock = %d-->%d\n"
> + "sdhost vdd = %d-->%d\n"
> + "sdhost bus_mode = %d-->%d\n"
> + "sdhost chip_select = %d-->%d\n"
> + "sdhost power_mode = %d-->%d\n"
> + "sdhost bus_width = %d-->%d\n"
> + "sdhost timing = %d-->%d\n"
> + "sdhost signal_voltage = %d-->%d\n"
> + "sdhost drv_type = %d-->%d\n",
> + __func__, host->device_name,
> + host->ios.clock, ios->clock,
> + host->ios.vdd, ios->vdd,
> + host->ios.bus_mode, ios->bus_mode,
> + host->ios.chip_select, ios->chip_select,
> + host->ios.power_mode, ios->power_mode,
> + host->ios.bus_width, ios->bus_width,
> + host->ios.timing, ios->timing,
> + host->ios.signal_voltage, ios->signal_voltage,
> + host->ios.drv_type, ios->drv_type);
Huh, this seems like a leftover from an ongoing development. Please remove.
Potentially we might want this information through a TRACE buffer
instead, but then it should be handled by the mmc core and thus
helping debugging for all mmc hosts.
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (0 == ios->clock) {
> + _sdhost_all_clk_off(host);
> + host->ios.clock = 0;
> + } else if (ios->clock != host->ios.clock) {
> + u32 div;
> +
> + div = _sdhost_calc_div(host->base_clk, ios->clock);
> + _sdhost_sd_clk_off(host);
> + _sdhost_clk_set_and_on(host, div);
> + _sdhost_sd_clk_on(host);
> + host->ios.clock = ios->clock;
> + host->data_timeout_val =
> + _sdhost_calc_timeout(host->base_clk, SDHOST_MAX_TIMEOUT);
> + mmc->max_busy_timeout = (1 << 30) / (ios->clock / 1000);
> + }
> +
> + if (ios->power_mode != host->ios.power_mode) {
> + switch (ios->power_mode) {
> + case MMC_POWER_OFF:
> + spin_unlock_irqrestore(&host->lock, flags);
> + _signal_voltage_on_off(host, 0);
> + if (mmc->supply.vmmc)
> + mmc_regulator_set_ocr(host->mmc,
> + mmc->supply.vmmc, 0);
> + spin_lock_irqsave(&host->lock, flags);
> + _reset_ios(host);
> + host->ios.power_mode = ios->power_mode;
> + break;
> + case MMC_POWER_ON:
> + case MMC_POWER_UP:
> + spin_unlock_irqrestore(&host->lock, flags);
> + if (mmc->supply.vmmc)
> + mmc_regulator_set_ocr(host->mmc,
> + mmc->supply.vmmc,
> + ios->vdd);
> + _signal_voltage_on_off(host, 1);
> + spin_lock_irqsave(&host->lock, flags);
> + host->ios.power_mode = ios->power_mode;
> + host->ios.vdd = ios->vdd;
> + break;
> + default:
> + break;
> + }
> + }
> +
> + /* flash power voltage select */
> + if (ios->vdd != host->ios.vdd) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + if (mmc->supply.vmmc) {
> + pr_info("sdhost %s 3.0 %d!\n",
> + host->device_name, ios->vdd);
> + mmc_regulator_set_ocr(host->mmc,
> + mmc->supply.vmmc, ios->vdd);
> + }
> + spin_lock_irqsave(&host->lock, flags);
> + host->ios.vdd = ios->vdd;
> + }
> +
> + if (ios->bus_width != host->ios.bus_width) {
> + _sdhost_set_buswidth(host, ios->bus_width);
> + host->ios.bus_width = ios->bus_width;
> + }
> +
> + if (ios->timing != host->ios.timing) {
> + /* 1 first close SD clock */
> + _sdhost_sd_clk_off(host);
> + /* 2 set timing mode */
> + switch (ios->timing) { /* timing specification used */
> + case MMC_TIMING_LEGACY:
> + /* basic clock mode */
> + _sdhost_set_uhs_mode(host, __TIMING_MODE_SDR12);
> + break;
> + case MMC_TIMING_MMC_HS:
> + case MMC_TIMING_SD_HS:
> + _sdhost_set_uhs_mode(host, __TIMING_MODE_SDR12);
> + break;
> + case MMC_TIMING_UHS_SDR12:
> + case MMC_TIMING_UHS_SDR25:
> + case MMC_TIMING_UHS_SDR50:
> + case MMC_TIMING_UHS_SDR104:
> + case MMC_TIMING_UHS_DDR50:
> + case MMC_TIMING_MMC_HS200:
> + _sdhost_set_uhs_mode(host, ios->timing -
> + MMC_TIMING_UHS_SDR12 +
> + __TIMING_MODE_SDR12);
> + break;
> + default:
> + break;
> + }
> + /* 3 open SD clock */
> + _sdhost_sd_clk_on(host);
> + host->ios.timing = ios->timing;
> + }
> +
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> +}
> +
> +static int sdhost_get_ro(struct mmc_host *mmc)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> + /* read & write */
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> + return 0;
> +}
> +
> +static int sdhost_get_cd(struct mmc_host *mmc)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> + int gpio_cd;
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (host->mmc->caps & MMC_CAP_NONREMOVABLE) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> + return 1;
> + }
> +
> + gpio_cd = mmc_gpio_get_cd(host->mmc);
> + if (IS_ERR_VALUE(gpio_cd))
> + gpio_cd = 1;
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> + return !!gpio_cd;
I think you should be able to assign your ->get_cd() callback directly
to mmc_gpio_get_cd() as I think the other stuff is already managed by
the mmc core.
Moreover, I don't understand the runtime*() calls here, but let's
leave that out of this review...
> +}
> +
> +static int sdhost_card_busy(struct mmc_host *mmc)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> + unsigned long flags;
> + u32 present_state;
> +
> + _runtime_get(host);
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /* Check whether DAT[3:0] is 0000 */
> + present_state = _sdhost_readl(host, SDHOST_32_PRES_STATE);
> +
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> + _runtime_put(host);
> +
> + return !(present_state & _DATA_LVL_MASK);
> +}
> +
> +static void sdhost_hw_reset(struct mmc_host *mmc)
> +{
> + struct sdhost_host *host = mmc_priv(mmc);
> +
> + _runtime_get(host);
> +
> + /* close LDO and open LDO again. */
> + _signal_voltage_on_off(host, 0);
> + if (mmc->supply.vmmc)
> + mmc_regulator_set_ocr(host->mmc, mmc->supply.vmmc, 0);
> + if (mmc->supply.vmmc)
> + mmc_regulator_set_ocr(host->mmc, mmc->supply.vmmc,
> + host->ios.vdd);
> +
> + _signal_voltage_on_off(host, 1);
> + mmiowb();
> + _runtime_put(host);
> +}
> +
> +static const struct mmc_host_ops sdhost_ops = {
> + .request = sdhost_request,
> + .set_ios = sdhost_set_ios,
> + .get_ro = sdhost_get_ro,
> + .get_cd = sdhost_get_cd,
> + .start_signal_voltage_switch = sdhost_set_vqmmc,
> + .card_busy = sdhost_card_busy,
> + .hw_reset = sdhost_hw_reset,
> +};
> +
> +static int get_caps_info(struct sdhost_host *host)
> +{
> + struct sdhost_caps_data *pdata = NULL;
> + int index;
> + int ret;
> +
> + for (index = 0; index < sizeof(caps_info_map) /
> + sizeof(struct sdhost_caps_data); index++) {
> + if (strcmp(host->device_name, caps_info_map[index].name) == 0) {
> + pdata = &caps_info_map[index];
> + break;
> + }
> + }
> +
As stated in the beginning, the above way isn't what you should be
doing. Instead...
> + host->ocr_avail = pdata->ocr_avail;
> + host->caps = pdata->caps;
> + host->caps2 = pdata->caps2;
> + host->pm_caps = pdata->pm_caps;
> + host->signal_default_voltage = pdata->signal_default_voltage;
> +
> + ret = mmc_of_parse(host->mmc);
... let mmc_of_parse() take care of this.
> + if (ret)
> + pr_err("parse sprd %s controller fail\n", host->device_name);
> +
> + return ret;
> +}
> +
> +static int _get_dt_resource(struct platform_device *pdev,
> + struct sdhost_host *host)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + u32 sdhost_delay[3];
> + int ret = 0;
> +
> + host->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!host->res)
> + return -ENOENT;
> +
> + host->ioaddr = devm_ioremap_resource(&pdev->dev, host->res);
> + if (IS_ERR(host->ioaddr)) {
> + ret = PTR_ERR(host->ioaddr);
> + dev_err(&pdev->dev, "can not map iomem: %d\n", ret);
> + goto err;
> + }
> +
> + host->mapbase = host->res->start;
> + host->irq = platform_get_irq(pdev, 0);
> + if (host->irq < 0) {
> + ret = host->irq;
> + goto err;
> + }
> +
> + host->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR_OR_NULL(host->clk)) {
> + ret = PTR_ERR(host->clk);
> + dev_err(&pdev->dev, "can not get clock: %d\n", ret);
Remove, printed by the clock framework.
> + goto err;
> + }
> +
> + host->base_clk = clk_get_rate(host->clk);
> +
> + ret = of_property_read_string(np, "sprd,name", &host->device_name);
> + if (ret) {
> + dev_err(&pdev->dev,
> + "can not read the property of sprd name\n");
> + goto err;
> + }
> +
> + ret = get_caps_info(host);
> + if (ret)
> + goto err;
> +
> + host->detect_gpio = of_get_named_gpio(np, "cd-gpios", 0);
Already covered by mmc_of_parse().
> + if (!gpio_is_valid(host->detect_gpio))
> + host->detect_gpio = -1;
> +
> + ret = of_property_read_u32_array(np, "sprd,delay", sdhost_delay, 3);
> + if (!ret) {
> + host->write_delay = sdhost_delay[0];
> + host->read_pos_delay = sdhost_delay[1];
> + host->read_neg_delay = sdhost_delay[2];
> + } else
> + dev_err(&pdev->dev,
> + "can not read the property of sprd delay\n");
> +
> + return 0;
> +
> +err:
> + dev_err(&pdev->dev, "sprd_sdhost get basic resource fail\n");
> + return ret;
> +}
> +
> +static int _get_ext_resource(struct sdhost_host *host)
> +{
> + int err;
> + struct mmc_host *mmc = host->mmc;
> +
> + host->dma_mask = DMA_BIT_MASK(64);
> + host->data_timeout_val = 0;
> +
> + /* 1 LDO */
> + mmc_regulator_get_supply(mmc);
> + if (IS_ERR_OR_NULL(mmc->supply.vmmc)) {
> + pr_err("%s(%s): no vmmc regulator found\n",
> + __func__, host->device_name);
> + mmc->supply.vmmc = NULL;
> + }
> + if (IS_ERR_OR_NULL(mmc->supply.vqmmc)) {
> + pr_err("%s(%s): no vqmmc regulator found\n",
> + __func__, host->device_name);
> + mmc->supply.vqmmc = NULL;
First; both regulators are made optional when fetching them via
mmc_regulator_get_supply().
Second; the prints are already managed by the mmc core, so I don't
think you need it here as well.
> + } else {
> + regulator_is_supported_voltage(mmc->supply.vqmmc,
> + host->signal_default_voltage,
> + host->signal_default_voltage);
> + regulator_set_voltage(mmc->supply.vqmmc,
> + host->signal_default_voltage,
> + host->signal_default_voltage);
> + }
> + host->mmc = mmc;
> +
> + /* 2 clock */
> + clk_prepare_enable(host->clk);
> +
> + /* 3 reset sdio */
> + _reset_ios(host);
> + err = devm_request_irq(&host->pdev->dev, host->irq, _irq,
> + IRQF_SHARED, mmc_hostname(host->mmc), host);
> + if (err) {
> + pr_err("%s: can not request irq\n", host->device_name);
> + goto err_clk_disable;
> + }
> +
> + tasklet_init(&host->finish_tasklet, _tasklet, (unsigned long)host);
> + /* 4 init timer */
> + setup_timer(&host->timer, _timeout, (unsigned long)host);
> +
> + return 0;
> +
> +err_clk_disable:
> + clk_disable_unprepare(host->clk);
> + return err;
> +}
> +
> +static void _set_mmc_struct(struct sdhost_host *host, struct mmc_host *mmc)
> +{
> + mmc = host->mmc;
> + mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
> + mmc->ops = &sdhost_ops;
> + mmc->f_max = host->base_clk;
> + mmc->f_min = (unsigned int)(host->base_clk / __CLK_MAX_DIV);
> +
> + mmc->caps = host->caps;
> + mmc->caps2 = host->caps2;
> + mmc->pm_caps = host->pm_caps;
> + mmc->pm_flags = host->pm_caps;
> + mmc->ocr_avail = host->ocr_avail;
> + mmc->ocr_avail_sdio = host->ocr_avail;
> + mmc->ocr_avail_sd = host->ocr_avail;
> + mmc->ocr_avail_mmc = host->ocr_avail;
> + mmc->max_current_330 = SDHOST_MAX_CUR;
> + mmc->max_current_300 = SDHOST_MAX_CUR;
> + mmc->max_current_180 = SDHOST_MAX_CUR;
> +
> + mmc->max_segs = 1;
> + mmc->max_req_size = 524288; /* 512k */
> + mmc->max_seg_size = mmc->max_req_size;
> +
> + mmc->max_blk_size = 512;
> + mmc->max_blk_count = 65535;
> +
> + pr_info("%s(%s): ocr avail = 0x%x\n"
> + "base clock = %u, pm_caps = 0x%x\n"
> + "caps: 0x%x, caps2: 0x%x\n",
> + __func__, host->device_name, mmc->ocr_avail,
> + host->base_clk, host->pm_caps, mmc->caps, mmc->caps2);
> +}
> +
> +static int sdhost_probe(struct platform_device *pdev)
> +{
> + struct mmc_host *mmc;
> + struct sdhost_host *host;
> + int ret;
> +
> + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
> + if (!host)
> + return -ENOMEM;
> +
> + /* globe resource */
> + mmc = mmc_alloc_host(sizeof(struct sdhost_host), &pdev->dev);
> + if (!mmc) {
> + dev_err(&pdev->dev, "no memory for mmc host\n");
> + return -ENOMEM;
> + }
> +
> + host = mmc_priv(mmc);
> + host->mmc = mmc;
> + host->pdev = pdev;
> + spin_lock_init(&host->lock);
> + platform_set_drvdata(pdev, host);
> +
> + /* get basic resource from device tree */
> + ret = _get_dt_resource(pdev, host);
> + if (ret) {
> + dev_err(&pdev->dev, "fail to get basic resource: %d\n", ret);
> + goto err_free_host;
> + }
> +
> + ret = _get_ext_resource(host);
> + if (ret) {
> + dev_err(&pdev->dev, "fail to get external resource: %d\n", ret);
> + goto err_free_host;
> + }
> +
> + _set_mmc_struct(host, mmc);
> + _pm_runtime_setting(pdev, host);
> +
> + /* add host */
> + mmiowb();
> + ret = mmc_add_host(mmc);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to add mmc host: %d\n", ret);
> + goto err_free_host;
> + }
> +
> + if (-1 != host->detect_gpio) {
> + mmc->caps &= ~MMC_CAP_NONREMOVABLE;
> + mmc_gpio_request_cd(mmc, host->detect_gpio, 0);
> + }
> +
> + sdhost_add_debugfs(host);
> +
> + dev_info(&pdev->dev,
> + "Spreadtrum %s[%s] host controller at 0x%08lx irq %d\n",
> + host->device_name, mmc_hostname(mmc),
> + host->mapbase, host->irq);
> +
> + return 0;
> +
> +err_free_host:
> + mmc_free_host(mmc);
> + return ret;
> +}
> +
> +static int sdhost_remove(struct platform_device *pdev)
> +{
> + struct sdhost_host *host = platform_get_drvdata(pdev);
> + struct mmc_host *mmc = host->mmc;
> +
> + if (-1 != host->detect_gpio)
> + mmc_gpio_free_cd(mmc);
> +
> + mmc_remove_host(mmc);
> + clk_disable_unprepare(host->clk);
> + free_irq(host->irq, host);
> + release_mem_region(host->res->start, resource_size(host->res));
Both free_irq() and release_mem_region() isn't need since I think you
used the devm_* functions, right!?
> + mmc_free_host(mmc);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops sdhost_dev_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(_pm_suspend, _pm_resume)
> + SET_RUNTIME_PM_OPS(_runtime_suspend,
> + _runtime_resume, _runtime_idle)
> +};
> +
> +static const struct of_device_id sdhost_of_match[] = {
> + {.compatible = "sprd,sdhost-3.0"},
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, sdhost_of_match);
> +
> +static struct platform_driver sdhost_driver = {
> + .probe = sdhost_probe,
> + .remove = sdhost_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .pm = &sdhost_dev_pm_ops,
> + .name = DRIVER_NAME,
> + .of_match_table = of_match_ptr(sdhost_of_match),
> + },
> +};
> +
> +module_platform_driver(sdhost_driver);
> +
> +MODULE_DESCRIPTION("Spreadtrum sdio host controller driver");
> +MODULE_LICENSE("GPL");
So I will stop here for now and let you address my comments. Hopefully
I should be able to give a quicker response once you post the next
version.
Kind regards
Uffe
> diff --git a/drivers/mmc/host/sprd_sdhost.h b/drivers/mmc/host/sprd_sdhost.h
> new file mode 100644
> index 0000000..d5cc438
> --- /dev/null
> +++ b/drivers/mmc/host/sprd_sdhost.h
> @@ -0,0 +1,615 @@
> +/*
> + * linux/drivers/mmc/host/sprd_sdhost.h - Secure Digital Host Controller
> + * Interface driver
> + *
> + * Copyright (C) 2015 Spreadtrum corporation.
> + *
> + * 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 __SDHOST_H_
> +#define __SDHOST_H_
> +
> +#include <linux/clk.h>
> +#include <linux/compiler.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/slot-gpio.h>
> +#include <linux/scatterlist.h>
> +#include <linux/types.h>
> +
> +/**********************************************************\
> + *
> + * Controller block structure
> + *
> +\**********************************************************/
> +struct sdhost_host {
> + /* --globe resource--- */
> + spinlock_t lock;
> + struct mmc_host *mmc;
> +
> + /*--basic resource-- */
> + struct resource *res;
> + void __iomem *ioaddr;
> + int irq;
> + const char *device_name;
> + struct platform_device *pdev;
> + unsigned long mapbase;
> +
> + int detect_gpio;
> + u32 ocr_avail;
> + u32 base_clk;
> + u32 caps;
> + u32 caps2;
> + u32 pm_caps;
> + u32 write_delay;
> + u32 read_pos_delay;
> + u32 read_neg_delay;
> +
> + /* --extern resource getted by base resource-- */
> + uint64_t dma_mask;
> + u8 data_timeout_val;
> + u32 signal_default_voltage;
> + bool sdio_1_8v_signal_enabled;
> + struct clk *clk;
> + struct tasklet_struct finish_tasklet;
> + struct timer_list timer;
> +
> + /* --runtime param-- */
> + u32 int_filter;
> + struct mmc_ios ios;
> + struct mmc_request *mrq; /* Current request */
> + struct mmc_command *cmd; /* Current command */
> + u16 auto_cmd_mode;
> +
> + /*--debugfs-- */
> + struct dentry *debugfs_root;
> +};
> +
> +/* Controller flag */
> +#define SDHOST_FLAG_ENABLE_ACMD12 0
> +#define SDHOST_FLAG_ENABLE_ACMD23 0
> +#define SDHOST_FLAG_USE_ADMA 1
> +#define SDHOST_MAX_TIMEOUT 3
> +
> +/* Controller registers */
> +#ifdef SPRD_SDHOST_4_BYTE_ALIGNE
> +static inline void __local_writeb(u8 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + u32 addr;
> + u32 value;
> + u32 ofst;
> +
> + ofst = (reg & 0x3) << 3;
> + addr = reg & (~((u32) (0x3)));
> + value = readl_relaxed((host->ioaddr + addr));
> + value &= (~(((u32) ((u8) (-1))) << ofst));
> + value |= (((u32) val) << ofst);
> + writel_relaxed(value, (host->ioaddr + addr));
> +}
> +
> +static inline void __local_writew(u16 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + u32 addr;
> + u32 value;
> + u32 ofst;
> +
> + ofst = (reg & 0x3) << 3;
> + addr = reg & (~((u32) (0x3)));
> + value = readl_relaxed(host->ioaddr + addr);
> + value &= (~(((u32) ((u16) (-1))) << ofst));
> + value |= (((u32) val) << ofst);
> + writel_relaxed(value, (host->ioaddr + addr));
> +}
> +
> +static inline void __local_writel(u32 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + writel_relaxed(val, (host->ioaddr + reg));
> +}
> +
> +static inline u8 __local_readb(struct sdhost_host *host, u32 reg)
> +{
> + u32 addr;
> + u32 value;
> + u32 ofst;
> +
> + ofst = (reg & 0x3) << 3;
> + addr = reg & (~((u32) (0x3)));
> + value = readl_relaxed(host->ioaddr + addr);
> + return ((u8) (value >> ofst));
> +
> +}
> +
> +static inline u16 __local_readw(struct sdhost_host *host, u32 reg)
> +{
> + u32 addr;
> + u32 value;
> + u32 ofst;
> +
> + ofst = (reg & 0x3) << 3;
> + addr = reg & (~((u32) (0x3)));
> + value = readl_relaxed(host->ioaddr + addr);
> +
> + return ((u16) (value >> ofst));
> +
> +}
> +
> +static inline u32 __local_readl(struct sdhsot_host *host, u32 reg)
> +{
> + return readl_relaxed(host->ioaddr + reg);
> +}
> +
> +#else
> +static inline void __local_writeb(u8 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + writeb_relaxed(val, host->ioaddr + reg);
> +}
> +
> +static inline void __local_writew(u16 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + writew_relaxed(val, host->ioaddr + reg);
> +}
> +
> +static inline void __local_writel(u32 val, struct sdhost_host *host,
> + u32 reg)
> +{
> + writel_relaxed(val, host->ioaddr + reg);
> +}
> +
> +static inline u8 __local_readb(struct sdhost_host *host, u32 reg)
> +{
> + return readb_relaxed(host->ioaddr + reg);
> +}
> +
> +static inline u16 __local_readw(struct sdhost_host *host, u32 reg)
> +{
> + return readw_relaxed(host->ioaddr + reg);
> +}
> +
> +static inline u32 __local_readl(struct sdhost_host *host, u32 reg)
> +{
> + return readl_relaxed(host->ioaddr + reg);
> +}
> +#endif
> +
> +static inline void _sdhost_writeb(struct sdhost_host *host, u8 val,
> + int reg)
> +{
> + __local_writeb(val, host, reg);
> +}
> +
> +static inline void _sdhost_writew(struct sdhost_host *host, u16 val,
> + int reg)
> +{
> + __local_writew(val, host, reg);
> +}
> +
> +static inline void _sdhost_writel(struct sdhost_host *host, u32 val,
> + int reg)
> +{
> + __local_writel(val, host, reg);
> +}
> +
> +static inline u8 _sdhost_readb(struct sdhost_host *host, int reg)
> +{
> + return __local_readb(host, reg);
> +}
> +
> +static inline u16 _sdhost_readw(struct sdhost_host *host, int reg)
> +{
> + return __local_readw(host, reg);
> +}
> +
> +static inline u32 _sdhost_readl(struct sdhost_host *host, int reg)
> +{
> + return __local_readl(host, reg);
> +}
> +
> +#define SDHOST_32_SYS_ADDR 0x00
> +/* used in cmd23 with ADMA in sdio 3.0 */
> +#define SDHOST_32_BLK_CNT 0x00
> +#define SDHOST_16_BLK_CNT 0x06
> +
> +static inline void _sdhost_set_16_blk_cnt(struct sdhost_host *host,
> + u32 blk_cnt)
> +{
> + __local_writew((blk_cnt & 0xFFFF), host, SDHOST_16_BLK_CNT);
> +}
> +
> +static inline void _sdhost_set_32_blk_cnt(struct sdhost_host *host,
> + u32 blk_cnt)
> +{
> + __local_writel((blk_cnt & 0xFFFFFFFF), host, SDHOST_32_BLK_CNT);
> +}
> +
> +#define SDHOST_16_BLK_SIZE 0x04
> +
> +static inline void _sdhost_set_blk_size(struct sdhost_host *host,
> + u32 blk_size)
> +{
> + __local_writew((blk_size & 0xFFF) | 0x7000, host, SDHOST_16_BLK_SIZE);
> +}
> +
> +#define SDHOST_32_ARG 0x08
> +#define SDHOST_16_TR_MODE 0x0C
> +#define __ACMD_DIS 0x00
> +#define __ACMD12 0x01
> +#define __ACMD23 0x02
> +
> +static inline void _sdhost_set_trans_mode(struct sdhost_host *host,
> + u16 if_mult, u16 if_read,
> + u16 auto_cmd,
> + u16 if_blk_cnt, u16 if_dma)
> +{
> + __local_writew((((if_mult ? 1 : 0) << 5) |
> + ((if_read ? 1 : 0) << 4) |
> + (auto_cmd << 2) |
> + ((if_blk_cnt ? 1 : 0) << 1) |
> + ((if_dma ? 1 : 0) << 0)), host, SDHOST_16_TR_MODE);
> +}
> +
> +#define SDHOST_16_CMD 0x0E
> +#define _CMD_INDEX_CHK 0x0010
> +#define _CMD_CRC_CHK 0x0008
> +#define _CMD_RSP_NONE 0x0000
> +#define _CMD_RSP_136 0x0001
> +#define _CMD_RSP_48 0x0002
> +#define _CMD_RSP_48_BUSY 0x0003
> +#define _RSP0 0
> +#define _RSP1_5_6_7 \
> + (_CMD_INDEX_CHK | _CMD_CRC_CHK | _CMD_RSP_48)
> +#define _RSP2 \
> + (_CMD_CRC_CHK | _CMD_RSP_136)
> +#define _RSP3_4 \
> + _CMD_RSP_48
> +#define _RSP1B_5B \
> + (_CMD_INDEX_CHK | _CMD_CRC_CHK | _CMD_RSP_48_BUSY)
> +
> +static inline void _sdhost_set_cmd(struct sdhost_host *host, u16 cmd,
> + int if_has_data, u16 rsp_type)
> +{
> + __local_writew(((cmd << 8) |
> + ((if_has_data ? 1 : 0) << 5) |
> + (rsp_type)), host, SDHOST_16_CMD);
> +}
> +
> +#define SDHOST_32_TR_MODE_AND_CMD 0x0C
> +
> +static inline void _sdhost_set_trans_and_cmd(struct sdhost_host *host,
> + int if_mult, int if_read,
> + u16 auto_cmd, int if_blk_cnt,
> + int if_dma, u32 cmd,
> + int if_has_data, u32 rsp_type)
> +{
> + __local_writel((((if_mult ? 1 : 0) << 5) |
> + ((if_read ? 1 : 0) << 4) |
> + (((u32) auto_cmd) << 2) |
> + ((if_blk_cnt ? 1 : 0) << 1) |
> + ((if_dma ? 1 : 0) << 0) |
> + (((u32) cmd) << 24) |
> + ((if_has_data ? 1 : 0) << 21) |
> + (rsp_type << 16)),
> + host, SDHOST_32_TR_MODE_AND_CMD);
> +}
> +
> +#define SDHOST_32_RESPONSE 0x10
> +#define SDHOST_32_PRES_STATE 0x24
> +#define _DATA_LVL_MASK 0x00F00000
> +
> +#define SDHOST_8_HOST_CTRL 0x28
> +#define __8_BIT_MOD 0x20
> +#define __4_BIT_MOD 0x02
> +#define __1_BIT_MOD 0x00
> +#define __SDMA_MOD 0x00
> +#define __32ADMA_MOD 0x10
> +#define __64ADMA_MOD 0x18
> +#define __HISPD_MOD 0x04
> +
> +static inline void _sdhost_set_buswidth(struct sdhost_host *host,
> + u32 buswidth)
> +{
> + u8 ctrl = 0;
> +
> + ctrl = __local_readb(host, SDHOST_8_HOST_CTRL);
> + ctrl &= (~(__8_BIT_MOD | __4_BIT_MOD | __1_BIT_MOD));
> + switch (buswidth) {
> + case MMC_BUS_WIDTH_1:
> + ctrl |= __1_BIT_MOD;
> + break;
> + case MMC_BUS_WIDTH_4:
> + ctrl |= __4_BIT_MOD;
> + break;
> + case MMC_BUS_WIDTH_8:
> + ctrl |= __8_BIT_MOD;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> + __local_writeb(ctrl, host, SDHOST_8_HOST_CTRL);
> +}
> +
> +static inline void _sdhost_set_dma(struct sdhost_host *host, u8 dma_mode)
> +{
> + u8 ctrl = 0;
> +
> + ctrl = __local_readb(host, SDHOST_8_HOST_CTRL);
> + ctrl &= (~(__SDMA_MOD | __32ADMA_MOD | __64ADMA_MOD));
> + ctrl |= dma_mode;
> + __local_writeb(ctrl, host, SDHOST_8_HOST_CTRL);
> +}
> +
> +static inline void _sdhost_enable_hispd(struct sdhost_host *host)
> +{
> + u8 ctrl = 0;
> +
> + ctrl = __local_readb(host, SDHOST_8_HOST_CTRL);
> + ctrl |= __HISPD_MOD;
> + __local_writeb(ctrl, host, SDHOST_8_HOST_CTRL);
> +}
> +
> +#define SDHOST_8_PWR_CTRL 0x29 /* not used */
> +#define SDHOST_8_BLK_GAP 0x2A /* not used */
> +#define SDHOST_8_WACKUP_CTRL 0x2B /* not used */
> +
> +#define SDHOST_16_CLK_CTRL 0x2C
> +#define __CLK_IN_EN 0x0001
> +#define __CLK_IN_STABLE 0x0002
> +#define __CLK_SD 0x0004
> +#define __CLK_MAX_DIV 2046
> +
> +static inline void _sdhost_all_clk_off(struct sdhost_host *host)
> +{
> + __local_writew(0, host, SDHOST_16_CLK_CTRL);
> +}
> +
> +static inline void _sdhost_sd_clk_off(struct sdhost_host *host)
> +{
> + u16 ctrl = 0;
> +
> + ctrl = __local_readw(host, SDHOST_16_CLK_CTRL);
> + ctrl &= (~__CLK_SD);
> + __local_writew(ctrl, host, SDHOST_16_CLK_CTRL);
> +}
> +
> +static inline void _sdhost_sd_clk_on(struct sdhost_host *host)
> +{
> + u16 ctrl = 0;
> +
> + ctrl = __local_readw(host, SDHOST_16_CLK_CTRL);
> + ctrl |= __CLK_SD;
> + __local_writew(ctrl, host, SDHOST_16_CLK_CTRL);
> +}
> +
> +static inline u32 _sdhost_calc_div(u32 base_clk, u32 clk)
> +{
> + u32 div;
> +
> + if (base_clk <= clk)
> + return 0;
> +
> + div = (u32) (base_clk / clk);
> + div = (div >> 1);
> + if (div)
> + div--;
> + if ((base_clk / ((div + 1) << 1)) > clk)
> + div++;
> + if (__CLK_MAX_DIV < div)
> + div = __CLK_MAX_DIV;
> +
> + return div;
> +}
> +
> +static inline void _sdhost_clk_set_and_on(struct sdhost_host *host,
> + u32 div)
> +{
> + u16 ctrl = 0;
> + unsigned long timeout;
> +
> + __local_writew(0, host, SDHOST_16_CLK_CTRL);
> + ctrl |= (u16) (((div & 0x300) >> 2) | ((div & 0xFF) << 8));
> + ctrl |= __CLK_IN_EN;
> + __local_writew(ctrl, host, SDHOST_16_CLK_CTRL);
> +
> + /* wait max 20 ms*/
> + timeout = 100;
> + while (!(__CLK_IN_STABLE & __local_readw(host, SDHOST_16_CLK_CTRL))) {
> + if (timeout == 0) {
> + pr_err("must check! %s clock set and on fail\n",
> + host->device_name);
> + return;
> + }
> +
> + timeout--;
> + mdelay(1);
> + }
> +}
> +
> +#define SDHOST_8_TIMEOUT 0x2E
> +#define __DATA_TIMEOUT_MAX_VAL 0xe
> +
> +static inline u8 _sdhost_calc_timeout(unsigned int clock,
> + u8 timeout_value)
> +{
> + unsigned target_timeout, current_timeout;
> + u8 count;
> +
> + count = 0;
> + current_timeout = 1 << 16;
> + target_timeout = timeout_value * clock;
> +
> + while (target_timeout > current_timeout) {
> + count++;
> + current_timeout <<= 1;
> + }
> + count--;
> + if (count >= 0xF)
> + count = 0xE;
> + return count;
> +}
> +
> +#define SDHOST_8_RST 0x2F
> +#define _RST_ALL 0x01
> +#define _RST_CMD 0x02
> +#define _RST_DATA 0x04
> +#define _RST_EMMC 0x08 /* spredtrum define it byself */
> +
> +static inline void _sdhost_reset(struct sdhost_host *host, u8 mask)
> +{
> + unsigned long timeout;
> +
> + __local_writeb((_RST_EMMC | mask), host, SDHOST_8_RST);
> +
> + /* wait max 100 ms*/
> + timeout = 100;
> + while (__local_readb(host, SDHOST_8_RST) & mask) {
> + if (timeout == 0) {
> + pr_err("must check! reset %s fail\n",
> + host->device_name);
> + return;
> + }
> +
> + timeout--;
> + mdelay(1);
> + }
> +}
> +
> +/* spredtrum define it byself */
> +static inline void _sdhost_reset_emmc(struct sdhost_host *host)
> +{
> + __local_writeb(0, host, SDHOST_8_RST);
> + mdelay(2);
> + __local_writeb(_RST_EMMC, host, SDHOST_8_RST);
> +}
> +
> +#define SDHOST_32_INT_ST 0x30
> +#define SDHOST_32_INT_ST_EN 0x34
> +#define SDHOST_32_INT_SIG_EN 0x38
> +#define _INT_CMD_END 0x00000001
> +#define _INT_TRAN_END 0x00000002
> +#define _INT_DMA_END 0x00000008
> +#define _INT_WR_RDY 0x00000010 /* not used */
> +#define _INT_RD_RDY 0x00000020 /* not used */
> +#define _INT_ERR 0x00008000
> +#define _INT_ERR_CMD_TIMEOUT 0x00010000
> +#define _INT_ERR_CMD_CRC 0x00020000
> +#define _INT_ERR_CMD_END 0x00040000
> +#define _INT_ERR_CMD_INDEX 0x00080000
> +#define _INT_ERR_DATA_TIMEOUT 0x00100000
> +#define _INT_ERR_DATA_CRC 0x00200000
> +#define _INT_ERR_DATA_END 0x00400000
> +#define _INT_ERR_CUR_LIMIT 0x00800000
> +#define _INT_ERR_ACMD 0x01000000
> +#define _INT_ERR_ADMA 0x02000000
> +
> +/* used in irq */
> +#define _INT_FILTER_ERR_CMD \
> + (_INT_ERR_CMD_TIMEOUT | _INT_ERR_CMD_CRC | \
> + _INT_ERR_CMD_END | _INT_ERR_CMD_INDEX)
> +#define _INT_FILTER_ERR_DATA \
> + (_INT_ERR_DATA_TIMEOUT | _INT_ERR_DATA_CRC | \
> + _INT_ERR_DATA_END)
> +#define _INT_FILTER_ERR \
> + (_INT_ERR | _INT_FILTER_ERR_CMD | \
> + _INT_FILTER_ERR_DATA | _INT_ERR_ACMD | \
> + _INT_ERR_ADMA)
> +#define _INT_FILTER_NORMAL \
> + (_INT_CMD_END | _INT_TRAN_END)
> +
> +/* used for setting */
> +#define _DATA_FILTER_RD_SIGLE \
> + (_INT_TRAN_END | _INT_DMA_END | \
> + _INT_ERR | _INT_ERR_DATA_TIMEOUT | \
> + _INT_ERR_DATA_CRC | _INT_ERR_DATA_END)
> +#define _DATA_FILTER_RD_MULTI \
> + (_INT_TRAN_END | _INT_DMA_END | _INT_ERR | \
> + _INT_ERR_DATA_TIMEOUT | _INT_ERR_DATA_CRC | \
> + _INT_ERR_DATA_END)
> +#define _DATA_FILTER_WR_SIGLE \
> + (_INT_TRAN_END | _INT_DMA_END | \
> + _INT_ERR | _INT_ERR_DATA_TIMEOUT | \
> + _INT_ERR_DATA_CRC)
> +#define _DATA_FILTER_WR_MULT \
> + (_INT_TRAN_END | _INT_DMA_END | \
> + _INT_ERR | _INT_ERR_DATA_TIMEOUT | \
> + _INT_ERR_DATA_CRC)
> +#define _CMD_FILTER_R0 \
> + _INT_CMD_END
> +#define _CMD_FILTER_R2 \
> + (_INT_CMD_END | _INT_ERR | \
> + _INT_ERR_CMD_TIMEOUT | _INT_ERR_CMD_CRC | \
> + _INT_ERR_CMD_END)
> +#define _CMD_FILTER_R3 \
> + (_INT_CMD_END | _INT_ERR | \
> + _INT_ERR_CMD_TIMEOUT | _INT_ERR_CMD_END)
> +#define _CMD_FILTER_R1_R4_R5_R6_R7 \
> + (_INT_CMD_END | _INT_ERR | \
> + _INT_ERR_CMD_TIMEOUT | _INT_ERR_CMD_CRC | \
> + _INT_ERR_CMD_END | _INT_ERR_CMD_INDEX)
> +#define _CMD_FILTER_R1B \
> + (_INT_CMD_END | _INT_ERR | \
> + _INT_ERR_CMD_TIMEOUT | _INT_ERR_CMD_CRC | \
> + _INT_ERR_CMD_END | _INT_ERR_CMD_INDEX | \
> + _INT_TRAN_END | _INT_ERR_DATA_TIMEOUT)
> +
> +static inline void _sdhost_disable_all_int(struct sdhost_host *host)
> +{
> + __local_writel(0x0, host, SDHOST_32_INT_SIG_EN);
> + __local_writel(0x0, host, SDHOST_32_INT_ST_EN);
> + __local_writel(0xFFFFFFFF, host, SDHOST_32_INT_ST);
> +}
> +
> +static inline void _sdhost_enable_int(struct sdhost_host *host, u32 mask)
> +{
> + __local_writel(mask, host, SDHOST_32_INT_ST_EN);
> + __local_writel(mask, host, SDHOST_32_INT_SIG_EN);
> +}
> +
> +static inline void _sdhost_clear_int(struct sdhost_host *host, u32 mask)
> +{
> + __local_writel(mask, host, SDHOST_32_INT_ST);
> +}
> +
> +#define SDHOST_16_ACMD_ERR 0x3C
> +
> +#define SDHOST_16_HOST_CTRL_2 0x3E
> +#define __TIMING_MODE_SDR12 0x0000
> +#define __TIMING_MODE_SDR25 0x0001
> +#define __TIMING_MODE_SDR50 0x0002
> +#define __TIMING_MODE_SDR104 0x0003
> +#define __TIMING_MODE_DDR50 0x0004
> +#define __TIMING_MODE_SDR200 0x0005
> +
> +static inline void _sdhost_set_uhs_mode(struct sdhost_host *host, u16 mode)
> +{
> + __local_writew(mode, host, SDHOST_16_HOST_CTRL_2);
> +}
> +
> +#define SDHOST_MAX_CUR 1020
> +
> +/* the following register is defined by spreadtrum self.
> + * It is not standard register of SDIO
> + * */
> +static inline void _sdhost_set_delay(struct sdhost_host *host,
> + u32 write_delay,
> + u32 read_pos_delay,
> + u32 read_neg_delay)
> +{
> + __local_writel(write_delay, host, 0x80);
> + __local_writel(read_pos_delay, host, 0x84);
> + __local_writel(read_neg_delay, host, 0x88);
> +}
> +
> +#endif /* __SDHOST_H_ */
> diff --git a/drivers/mmc/host/sprd_sdhost_debugfs.c b/drivers/mmc/host/sprd_sdhost_debugfs.c
> new file mode 100644
> index 0000000..29b7f58
> --- /dev/null
> +++ b/drivers/mmc/host/sprd_sdhost_debugfs.c
> @@ -0,0 +1,212 @@
> +/*
> + * linux/drivers/mmc/host/sprd_sdhost_debugfs.c - Secure Digital Host
> + * Controller Interface driver
> + *
> + * Copyright (C) 2015 Spreadtrum corporation.
> + *
> + * 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/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/mmc/host.h>
> +
> +#include "sprd_sdhost_debugfs.h"
> +
> +#define ELEMENT(v) {v, #v}
> +#define ELEMENT_NUM 26
> +struct {
> + uint32_t bit;
> + char *caps_name;
> +} caps_info[3][ELEMENT_NUM] = {
> + {
> + ELEMENT(MMC_CAP_4_BIT_DATA),
> + ELEMENT(MMC_CAP_MMC_HIGHSPEED),
> + ELEMENT(MMC_CAP_SD_HIGHSPEED),
> + ELEMENT(MMC_CAP_SDIO_IRQ),
> + ELEMENT(MMC_CAP_SPI),
> + ELEMENT(MMC_CAP_NEEDS_POLL),
> + ELEMENT(MMC_CAP_8_BIT_DATA),
> + ELEMENT(MMC_CAP_AGGRESSIVE_PM),
> + ELEMENT(MMC_CAP_NONREMOVABLE),
> + ELEMENT(MMC_CAP_WAIT_WHILE_BUSY),
> + ELEMENT(MMC_CAP_ERASE),
> + ELEMENT(MMC_CAP_1_8V_DDR),
> + ELEMENT(MMC_CAP_1_2V_DDR),
> + ELEMENT(MMC_CAP_POWER_OFF_CARD),
> + ELEMENT(MMC_CAP_BUS_WIDTH_TEST),
> + ELEMENT(MMC_CAP_UHS_SDR12),
> + ELEMENT(MMC_CAP_UHS_SDR25),
> + ELEMENT(MMC_CAP_UHS_SDR50),
> + ELEMENT(MMC_CAP_UHS_SDR104),
> + ELEMENT(MMC_CAP_UHS_DDR50),
> + ELEMENT(MMC_CAP_RUNTIME_RESUME),
> + ELEMENT(MMC_CAP_DRIVER_TYPE_A),
> + ELEMENT(MMC_CAP_DRIVER_TYPE_C),
> + ELEMENT(MMC_CAP_DRIVER_TYPE_D),
> + ELEMENT(MMC_CAP_CMD23),
> + ELEMENT(MMC_CAP_HW_RESET)
> + }, {
> + ELEMENT(MMC_CAP2_BOOTPART_NOACC),
> + ELEMENT(MMC_CAP2_FULL_PWR_CYCLE),
> + ELEMENT(MMC_CAP2_HS200_1_8V_SDR),
> + ELEMENT(MMC_CAP2_HS200_1_2V_SDR),
> + ELEMENT(MMC_CAP2_HS200),
> + ELEMENT(MMC_CAP2_HC_ERASE_SZ),
> + ELEMENT(MMC_CAP2_CD_ACTIVE_HIGH),
> + ELEMENT(MMC_CAP2_RO_ACTIVE_HIGH),
> + ELEMENT(MMC_CAP2_PACKED_RD),
> + ELEMENT(MMC_CAP2_PACKED_WR),
> + ELEMENT(MMC_CAP2_PACKED_CMD),
> + ELEMENT(MMC_CAP2_NO_PRESCAN_POWERUP),
> + ELEMENT(MMC_CAP2_HS400_1_8V),
> + ELEMENT(MMC_CAP2_HS400_1_2V),
> + ELEMENT(MMC_CAP2_HS400),
> + ELEMENT(MMC_CAP2_SDIO_IRQ_NOTHREAD)
> + }, {
> + ELEMENT(MMC_PM_KEEP_POWER),
> + ELEMENT(MMC_PM_WAKE_SDIO_IRQ),
> + ELEMENT(MMC_PM_IGNORE_PM_NOTIFY)
> + }
> +
> +};
> +
> +static int sdhost_param_show(struct seq_file *s, void *data)
> +{
> + struct sdhost_host *host = s->private;
> + uint32_t i;
> +
> + seq_printf(s, "\n"
> + "ioaddr\t= 0x%p\n"
> + "irq\t= %d\n"
> + "device_name\t= %s\n"
> + "detect_gpio\t= %d\n"
> + "base_clk\t= %d\n"
> + "write_delay\t= %d\n"
> + "read_pos_delay\t= %d\n"
> + "read_neg_delay\t= %d\n",
> + host->ioaddr, host->irq, host->device_name,
> + host->detect_gpio, host->base_clk,
> + host->write_delay, host->read_pos_delay,
> + host->read_neg_delay);
> + seq_printf(s, "OCR 0x%x\n", host->ocr_avail);
> +
> + for (i = 0; i < ELEMENT_NUM; i++) {
> + if ((caps_info[0][i].bit ==
> + (host->caps & caps_info[0][i].bit))
> + && caps_info[0][i].bit)
> + seq_printf(s, "caps:%s\n", caps_info[0][i].caps_name);
> + }
> + for (i = 0; i < ELEMENT_NUM; i++) {
> + if ((caps_info[1][i].bit ==
> + (host->caps2 & caps_info[1][i].bit))
> + && caps_info[1][i].bit)
> + seq_printf(s, "caps2:%s\n", caps_info[1][i].caps_name);
> + }
> + for (i = 0; i < ELEMENT_NUM; i++) {
> + if ((caps_info[2][i].bit ==
> + (host->pm_caps & caps_info[2][i].bit))
> + && caps_info[2][i].bit)
> + seq_printf(s, "pm_caps:%s\n",
> + caps_info[2][i].caps_name);
> + }
> +
> + return 0;
> +}
> +
> +static int sdhost_param_open(struct inode *inode, struct file *file)
> +{
> + return single_open(file, sdhost_param_show, inode->i_private);
> +}
> +
> +static const struct file_operations sdhost_param_fops = {
> + .open = sdhost_param_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .release = single_release,
> +};
> +
> +#define SDHOST_ATTR(PARAM_NAME) \
> + static int sdhost_##PARAM_NAME##_get(void *data, u64 *val)\
> + { \
> + struct sdhost_host *host = data;\
> + *val = (u64)host->PARAM_NAME;\
> + return 0;\
> + } \
> + static int sdhost_##PARAM_NAME##_set(void *data, u64 val)\
> + { \
> + struct sdhost_host *host = data;\
> + if (0x7F >= (uint32_t)val) { \
> + host->PARAM_NAME = (uint32_t)val;\
> + _sdhost_set_delay(host, \
> + host->write_delay, \
> + host->read_pos_delay, \
> + host->read_neg_delay);\
> + } \
> + return 0;\
> + } \
> + DEFINE_SIMPLE_ATTRIBUTE(sdhost_##PARAM_NAME##_fops,\
> + sdhost_##PARAM_NAME##_get,\
> + sdhost_##PARAM_NAME##_set,\
> + "%llu\n")
> +
> +SDHOST_ATTR(write_delay);
> +SDHOST_ATTR(read_pos_delay);
> +SDHOST_ATTR(read_neg_delay);
> +
> +void sdhost_add_debugfs(struct sdhost_host *host)
> +{
> + struct dentry *root;
> +
> + root = debugfs_create_dir(host->device_name, NULL);
> + if (IS_ERR(root))
> + /* Don't complain -- debugfs just isn't enabled */
> + return;
> + if (!root)
> + return;
> +
> + host->debugfs_root = root;
> +
> + if (!debugfs_create_file("basic_resource", S_IRUSR, root,
> + (void *)host, &sdhost_param_fops))
> + goto err;
> + if (!debugfs_create_file("write_delay", S_IRUSR | S_IWUSR, root,
> + (void *)host, &sdhost_write_delay_fops))
> + goto err;
> + if (!debugfs_create_file("read_pos_delay", S_IRUSR | S_IWUSR, root,
> + (void *)host, &sdhost_read_pos_delay_fops))
> + goto err;
> + if (!debugfs_create_file("read_neg_delay", S_IRUSR | S_IWUSR, root,
> + (void *)host, &sdhost_read_neg_delay_fops))
> + goto err;
> + return;
> +
> +err:
> + debugfs_remove_recursive(root);
> + host->debugfs_root = 0;
> +}
> +
> +void dump_sdio_reg(struct sdhost_host *host)
> +{
> + unsigned int i;
> +
> + if (!host->mmc->card)
> + return;
> +
> + pr_info("+++++++++++ REGISTER DUMP (%s) ++++++++++\n",
> + host->device_name);
> +
> + for (i = 0; i < 0x09; i++) {
> + pr_info("0x%08x | 0x%08x | 0x%08x | 0x%08x\n\r",
> + _sdhost_readl(host, 0 + (i << 4)),
> + _sdhost_readl(host, 4 + (i << 4)),
> + _sdhost_readl(host, 8 + (i << 4)),
> + _sdhost_readl(host, 12 + (i << 4))
> + );
> + }
> +
> + pr_info("----------------------------------------\n");
> +}
> diff --git a/drivers/mmc/host/sprd_sdhost_debugfs.h b/drivers/mmc/host/sprd_sdhost_debugfs.h
> new file mode 100644
> index 0000000..0063e1b
> --- /dev/null
> +++ b/drivers/mmc/host/sprd_sdhost_debugfs.h
> @@ -0,0 +1,27 @@
> +/*
> + * linux/drivers/mmc/host/sprd_sdhost_debugfs.h - Secure Digital Host Controller
> + * Interface driver
> + *
> + * Copyright (C) 2015 Spreadtrum corporation.
> + *
> + * 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 _SDHOST_DEBUGFS_H_
> +#define _SDHOST_DEBUGFS_H_
> +
> +#include "sprd_sdhost.h"
> +
> +#ifdef CONFIG_DEBUG_FS
> +void sdhost_add_debugfs(struct sdhost_host *host);
> +void dump_sdio_reg(struct sdhost_host *host);
> +#else
> +static inline void sdhost_add_debugfs(struct sdhost_host *host) {}
> +static inline void dump_sdio_reg(struct sdhost_host *host) {}
> +#endif
> +
> +#endif
> --
> 1.7.9.5
>
--
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/