| /* |
| * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver |
| * |
| * Copyright (C) 2012, Samsung Electronics Co., Ltd. |
| * |
| * 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/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/mmc/host.h> |
| #include <linux/mmc/dw_mmc.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/slab.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/smc.h> |
| #include <linux/sec_sysfs.h> |
| |
| #include "dw_mmc.h" |
| #include "dw_mmc-pltfm.h" |
| #include "dw_mmc-exynos.h" |
| |
| #include "dw_mmc-exynos-smu.h" |
| #include "dw_mmc-exynos-fmp.h" |
| |
| static struct workqueue_struct *hwacg_workqueue; |
| |
| static void dw_mci_hwacg_work(struct work_struct *work) |
| { |
| struct dw_mci *host = container_of(work, struct dw_mci, hwacg_work.work); |
| u32 reg; |
| |
| reg = mci_readl(host, FORCE_CLK_STOP); |
| reg |= MMC_HWACG_CONTROL; |
| host->qactive_check = HWACG_Q_ACTIVE_EN; |
| mci_writel(host, FORCE_CLK_STOP, reg); |
| } |
| |
| static void dw_mci_exynos_register_dump(struct dw_mci *host) |
| { |
| dev_err(host->dev, ": EMMCP_BASE: 0x%08x\n", |
| host->sfr_dump->fmp_emmcp_base = mci_readl(host, EMMCP_BASE)); |
| dev_err(host->dev, ": MPSECURITY: 0x%08x\n", |
| host->sfr_dump->mpsecurity = mci_readl(host, MPSECURITY)); |
| dev_err(host->dev, ": MPSTAT: 0x%08x\n", |
| host->sfr_dump->mpstat = mci_readl(host, MPSTAT)); |
| dev_err(host->dev, ": MPSBEGIN: 0x%08x\n", |
| host->sfr_dump->mpsbegin = mci_readl(host, MPSBEGIN0)); |
| dev_err(host->dev, ": MPSEND: 0x%08x\n", |
| host->sfr_dump->mpsend = mci_readl(host, MPSEND0)); |
| dev_err(host->dev, ": MPSCTRL: 0x%08x\n", |
| host->sfr_dump->mpsctrl = mci_readl(host, MPSCTRL0)); |
| dev_err(host->dev, ": HS400_DQS_EN: 0x%08x\n", |
| host->sfr_dump->hs400_rdqs_en = mci_readl(host, HS400_DQS_EN)); |
| dev_err(host->dev, ": HS400_ASYNC_FIFO_CTRL: 0x%08x\n", |
| host->sfr_dump->hs400_acync_fifo_ctrl = |
| mci_readl(host, HS400_ASYNC_FIFO_CTRL)); |
| dev_err(host->dev, ": HS400_DLINE_CTRL: 0x%08x\n", |
| host->sfr_dump->hs400_dline_ctrl = |
| mci_readl(host, HS400_DLINE_CTRL)); |
| } |
| |
| void dw_mci_reg_dump(struct dw_mci *host) |
| { |
| |
| u32 reg; |
| |
| dev_err(host->dev, ": ============== CMU DEBUG ===================\n"); |
| dev_err(host->dev, ": Q-channel control : 0x%08x\n", |
| readl(host->cmu_debug_reg + QCH_CON_MMC_EMBD_QCH)); |
| dev_err(host->dev, ": CMU CLK STATUS : 0x%08x\n", |
| readl(host->cmu_debug_reg + MMC_CLK_STATUS)); |
| dev_err(host->dev, ": FSYS_CMU_CONTROL : 0x%08x\n", |
| readl(host->cmu_debug_reg + FSYS_CMU_CONTROL_OPTION)); |
| dev_err(host->dev, ": ============== FIRST STATUS DUMP ===========\n"); |
| dev_err(host->dev, ": cmd_status: 0x%08x\n", host->cmd_status); |
| dev_err(host->dev, ": data_status: 0x%08x\n", host->data_status); |
| dev_err(host->dev, ": pending_events: 0x%08lx\n", host->pending_events); |
| dev_err(host->dev, ": completed_events:0x%08lx\n", host->completed_events); |
| dev_err(host->dev, ": state: %d\n", host->state); |
| |
| dev_err(host->dev, ": ============== REGISTER DUMP ==============\n"); |
| dev_err(host->dev, ": CTRL: 0x%08x\n", |
| host->sfr_dump->contrl = mci_readl(host, CTRL)); |
| dev_err(host->dev, ": PWREN: 0x%08x\n", |
| host->sfr_dump->pwren = mci_readl(host, PWREN)); |
| dev_err(host->dev, ": CLKDIV: 0x%08x\n", |
| host->sfr_dump->clkdiv = mci_readl(host, CLKDIV)); |
| dev_err(host->dev, ": CLKSRC: 0x%08x\n", |
| host->sfr_dump->clksrc = mci_readl(host, CLKSRC)); |
| dev_err(host->dev, ": CLKENA: 0x%08x\n", |
| host->sfr_dump->clkena = mci_readl(host, CLKENA)); |
| dev_err(host->dev, ": TMOUT: 0x%08x\n", |
| host->sfr_dump->tmout = mci_readl(host, TMOUT)); |
| dev_err(host->dev, ": CTYPE: 0x%08x\n", |
| host->sfr_dump->ctype = mci_readl(host, CTYPE)); |
| dev_err(host->dev, ": BLKSIZ: 0x%08x\n", |
| host->sfr_dump->blksiz = mci_readl(host, BLKSIZ)); |
| dev_err(host->dev, ": BYTCNT: 0x%08x\n", |
| host->sfr_dump->bytcnt = mci_readl(host, BYTCNT)); |
| dev_err(host->dev, ": INTMSK: 0x%08x\n", |
| host->sfr_dump->intmask = mci_readl(host, INTMASK)); |
| dev_err(host->dev, ": CMDARG: 0x%08x\n", |
| host->sfr_dump->cmdarg = mci_readl(host, CMDARG)); |
| dev_err(host->dev, ": CMD: 0x%08x\n", |
| host->sfr_dump->cmd = mci_readl(host, CMD)); |
| dev_err(host->dev, ": RESP0: 0x%08x\n", mci_readl(host, RESP0)); |
| dev_err(host->dev, ": RESP1: 0x%08x\n", mci_readl(host, RESP1)); |
| dev_err(host->dev, ": RESP2: 0x%08x\n", mci_readl(host, RESP2)); |
| dev_err(host->dev, ": RESP3: 0x%08x\n", mci_readl(host, RESP3)); |
| dev_err(host->dev, ": MINTSTS: 0x%08x\n", |
| host->sfr_dump->mintsts = mci_readl(host, MINTSTS)); |
| dev_err(host->dev, ": RINTSTS: 0x%08x\n", |
| host->sfr_dump->rintsts = mci_readl(host, RINTSTS)); |
| dev_err(host->dev, ": STATUS: 0x%08x\n", |
| host->sfr_dump->status = mci_readl(host, STATUS)); |
| dev_err(host->dev, ": FIFOTH: 0x%08x\n", |
| host->sfr_dump->fifoth = mci_readl(host, FIFOTH)); |
| dev_err(host->dev, ": CDETECT: 0x%08x\n", mci_readl(host, CDETECT)); |
| dev_err(host->dev, ": WRTPRT: 0x%08x\n", mci_readl(host, WRTPRT)); |
| dev_err(host->dev, ": GPIO: 0x%08x\n", mci_readl(host, GPIO)); |
| dev_err(host->dev, ": TCBCNT: 0x%08x\n", |
| host->sfr_dump->tcbcnt = mci_readl(host, TCBCNT)); |
| dev_err(host->dev, ": TBBCNT: 0x%08x\n", |
| host->sfr_dump->tbbcnt = mci_readl(host, TBBCNT)); |
| dev_err(host->dev, ": DEBNCE: 0x%08x\n", mci_readl(host, DEBNCE)); |
| dev_err(host->dev, ": USRID: 0x%08x\n", mci_readl(host, USRID)); |
| dev_err(host->dev, ": VERID: 0x%08x\n", mci_readl(host, VERID)); |
| dev_err(host->dev, ": HCON: 0x%08x\n", mci_readl(host, HCON)); |
| dev_err(host->dev, ": UHS_REG: 0x%08x\n", |
| host->sfr_dump->uhs_reg = mci_readl(host, UHS_REG)); |
| dev_err(host->dev, ": BMOD: 0x%08x\n", |
| host->sfr_dump->bmod = mci_readl(host, BMOD)); |
| dev_err(host->dev, ": PLDMND: 0x%08x\n", mci_readl(host, PLDMND)); |
| if(host->dma_64bit_address == 1) { |
| dev_err(host->dev, ": DBADDRL: 0x%08x\n", |
| host->sfr_dump->dbaddrl = mci_readl(host, DBADDRL)); |
| dev_err(host->dev, ": DBADDRU: 0x%08x\n", |
| host->sfr_dump->dbaddru = mci_readl(host, DBADDRU)); |
| dev_err(host->dev, ": DSCADDRL: 0x%08x\n", |
| host->sfr_dump->dscaddrl = mci_readl(host, DSCADDRL)); |
| dev_err(host->dev, ": DSCADDRU: 0x%08x\n", |
| host->sfr_dump->dscaddru = mci_readl(host, DSCADDRU)); |
| dev_err(host->dev, ": BUFADDRL: 0x%08x\n", |
| host->sfr_dump->bufaddr = mci_readl(host, BUFADDRL)); |
| dev_err(host->dev, ": BUFADDRU: 0x%08x\n", |
| host->sfr_dump->bufaddru = mci_readl(host, BUFADDRU)); |
| dev_err(host->dev, ": IDSTS64: 0x%08x\n", |
| host->sfr_dump->idsts64 = mci_readl(host, IDSTS64)); |
| dev_err(host->dev, ": IDINTEN64: 0x%08x\n", |
| host->sfr_dump->idinten64 = mci_readl(host, IDINTEN64)); |
| } else { |
| dev_err(host->dev, ": DBADDR: 0x%08x\n", |
| host->sfr_dump->dbaddr = mci_readl(host, DBADDR)); |
| dev_err(host->dev, ": DSCADDR: 0x%08x\n", |
| host->sfr_dump->dscaddr = mci_readl(host, DSCADDR)); |
| dev_err(host->dev, ": BUFADDR: 0x%08x\n", |
| host->sfr_dump->bufaddr = mci_readl(host, BUFADDR)); |
| dev_err(host->dev, ": IDSTS: 0x%08x\n", mci_readl(host, IDSTS)); |
| dev_err(host->dev, ": IDINTEN: 0x%08x\n", mci_readl(host, IDINTEN)); |
| } |
| dev_err(host->dev, ": CLKSEL: 0x%08x\n", |
| host->sfr_dump->clksel = mci_readl(host, CLKSEL)); |
| dev_err(host->dev, ": RESP_TAT: 0x%08x\n", mci_readl(host, RESP_TAT)); |
| dev_err(host->dev, ": FORCE_CLK_STOP: 0x%08x\n", |
| host->sfr_dump->force_clk_stop = mci_readl(host, FORCE_CLK_STOP)); |
| dev_err(host->dev, ": SHA_CMD_IE : 0x%08x\n", |
| host->sfr_dump->sha_cmd_ie = mci_readl(host, SHA_CMD_IE)); |
| dev_err(host->dev, ": SHA_CMD_IS : 0x%08x\n", |
| host->sfr_dump->sha_cmd_is = mci_readl(host, SHA_CMD_IS)); |
| dev_err(host->dev, ": CDTHRCTL: 0x%08x\n", mci_readl(host, CDTHRCTL)); |
| dw_mci_exynos_register_dump(host); |
| dev_err(host->dev, ": ============== SECOND STATUS DUMP ================\n"); |
| dev_err(host->dev, ": cmd_status: 0x%08x\n", |
| host->sfr_dump->cmd_status = host->cmd_status); |
| dev_err(host->dev, ": data_status: 0x%08x\n", |
| host->sfr_dump->force_clk_stop = host->data_status); |
| dev_err(host->dev, ": pending_events: 0x%08lx\n", |
| host->sfr_dump->pending_events = host->pending_events); |
| dev_err(host->dev, ": completed_events:0x%08lx\n", |
| host->sfr_dump->completed_events = host->completed_events); |
| dev_err(host->dev, ": state: %d\n", |
| host->sfr_dump->host_state = host->state); |
| dev_err(host->dev, ": gate-clk: %s\n", |
| atomic_read(&host->ciu_clk_cnt) ? |
| "enable" : "disable"); |
| dev_err(host->dev, ": ciu_en_win: %d\n", |
| atomic_read(&host->ciu_en_win)); |
| reg = mci_readl(host, CMD); |
| dev_err(host->dev, ": ================= CMD REG =================\n"); |
| if((reg >> 9) & 0x1) { |
| dev_err(host->dev, ": read/write : %s\n", |
| (reg & (0x1 << 10)) ? "write" : "read"); |
| dev_err(host->dev, ": data expected : %d\n", (reg >> 9) & 0x1); |
| } |
| dev_err(host->dev, ": cmd index : %d\n", |
| host->sfr_dump->cmd_index =((reg >> 0) & 0x3f)); |
| reg = mci_readl(host, STATUS); |
| dev_err(host->dev, ": ================ STATUS REG ===============\n"); |
| dev_err(host->dev, ": fifocount : %d\n", |
| host->sfr_dump->fifo_count = ((reg >> 17) & 0x1fff)); |
| dev_err(host->dev, ": response index : %d\n", (reg >> 11) & 0x3f); |
| dev_err(host->dev, ": data state mc busy: %d\n", (reg >> 10) & 0x1); |
| dev_err(host->dev, ": data busy : %d\n", |
| host->sfr_dump->data_busy = ((reg >> 9) & 0x1)); |
| dev_err(host->dev, ": data 3 state : %d\n", |
| host->sfr_dump->data_3_state = ((reg >> 8) & 0x1)); |
| dev_err(host->dev, ": command fsm state : %d\n", (reg >> 4) & 0xf); |
| dev_err(host->dev, ": fifo full : %d\n", (reg >> 3) & 0x1); |
| dev_err(host->dev, ": fifo empty : %d\n", (reg >> 2) & 0x1); |
| dev_err(host->dev, ": fifo tx watermark : %d\n", |
| host->sfr_dump->fifo_tx_watermark = ((reg >> 1) & 0x1)); |
| dev_err(host->dev, ": fifo rx watermark : %d\n", |
| host->sfr_dump->fifo_rx_watermark = ((reg >> 0) & 0x1)); |
| dev_err(host->dev, ": ===========================================\n"); |
| } |
| |
| /* Variations in Exynos specific dw-mshc controller */ |
| enum dw_mci_exynos_type { |
| DW_MCI_TYPE_EXYNOS, |
| }; |
| |
| static struct dw_mci_exynos_compatible { |
| char *compatible; |
| enum dw_mci_exynos_type ctrl_type; |
| } exynos_compat[] = { |
| { |
| .compatible = "samsung,exynos-dw-mshc", |
| .ctrl_type = DW_MCI_TYPE_EXYNOS, |
| }, |
| }; |
| |
| /* |
| * This is a last resort for recovery. |
| */ |
| #ifdef CONFIG_MMC_CQ_HCI |
| void exynos_cqe_sw_reset(struct mmc_host *mmc) |
| { |
| struct dw_mci_slot *slot = mmc_priv(mmc); |
| struct dw_mci *host = slot->host; |
| u32 reg; |
| |
| reg = mci_readl(host, AXI_BURST_LEN); |
| reg |= (1 << 22); |
| mci_writel(host, AXI_BURST_LEN, reg); |
| } |
| #else |
| void exynos_cqe_sw_reset(struct mmc_host *mmc) |
| { |
| } |
| #endif |
| |
| static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host) |
| { |
| return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1; |
| } |
| |
| static int dw_mci_exynos_priv_init(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL); |
| priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN); |
| priv->saved_dqs_en |= AXI_NON_BLOCKING_WR; |
| mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en); |
| if (!priv->dqs_delay) |
| priv->dqs_delay = |
| DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl); |
| #if defined(CONFIG_MMC_DW_64BIT_DESC) |
| if (priv->voltage_int_extra != 0) { |
| u32 reg = 0; |
| reg = mci_readl(host, AXI_BURST_LEN); |
| reg &= ~(0x7 << 24); |
| reg |= (priv->voltage_int_extra << 24); |
| mci_writel(host, AXI_BURST_LEN, reg); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int dw_mci_exynos_setup_clock(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| unsigned long rate = clk_get_rate(host->ciu_clk); |
| |
| host->bus_hz = rate / (priv->ciu_div + 1); |
| return 0; |
| } |
| |
| static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing) |
| { |
| u32 clksel; |
| |
| clksel = mci_readl(host, CLKSEL); |
| clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing; |
| |
| if (!((host->pdata->io_mode == MMC_TIMING_MMC_HS400) || |
| (host->pdata->io_mode == MMC_TIMING_MMC_HS400_ES))) |
| clksel &= ~(BIT(30) | BIT(19)); |
| |
| mci_writel(host, CLKSEL, clksel); |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int dw_mci_exynos_suspend(struct device *dev) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| |
| return dw_mci_suspend(host); |
| } |
| |
| static int dw_mci_exynos_resume(struct device *dev) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| |
| dw_mci_exynos_priv_init(host); |
| return dw_mci_resume(host); |
| } |
| |
| /** |
| * dw_mci_exynos_resume_noirq - Exynos-specific resume code |
| * |
| * On exynos5420 there is a silicon errata that will sometimes leave the |
| * WAKEUP_INT bit in the CLKSEL register asserted. This bit is 1 to indicate |
| * that it fired and we can clear it by writing a 1 back. Clear it to prevent |
| * interrupts from going off constantly. |
| * |
| * We run this code on all exynos variants because it doesn't hurt. |
| */ |
| |
| static int dw_mci_exynos_resume_noirq(struct device *dev) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| u32 clksel; |
| |
| clksel = mci_readl(host, CLKSEL); |
| |
| if (clksel & SDMMC_CLKSEL_WAKEUP_INT) |
| mci_writel(host, CLKSEL, clksel); |
| |
| return 0; |
| } |
| #else |
| #define dw_mci_exynos_suspend NULL |
| #define dw_mci_exynos_resume NULL |
| #define dw_mci_exynos_resume_noirq NULL |
| #endif /* CONFIG_PM_SLEEP */ |
| |
| static void dw_mci_card_int_hwacg_ctrl(struct dw_mci *host, u32 flag, int mode) |
| { |
| u32 reg; |
| |
| reg = mci_readl(host, FORCE_CLK_STOP); |
| if (mode == HWACG_WORK_INIT) { |
| if (flag == W_INIT) { |
| hwacg_workqueue = alloc_ordered_workqueue("kmmcd", 0); |
| if (!hwacg_workqueue) |
| dev_err(host->dev, "hwacg workqueue alloc fail!\n"); |
| |
| INIT_DELAYED_WORK(&host->hwacg_work, dw_mci_hwacg_work); |
| } else if (flag == W_FREE) |
| destroy_workqueue(hwacg_workqueue); |
| } else if (flag == HWACG_Q_ACTIVE_EN) { |
| if (mode == CMDQ_MODE) { |
| queue_delayed_work(hwacg_workqueue, &host->hwacg_work, |
| msecs_to_jiffies(10)); |
| } else { |
| if (host->prv_hwacg_state != true) { |
| reg |= MMC_HWACG_CONTROL; |
| host->qactive_check = HWACG_Q_ACTIVE_EN; |
| mci_writel(host, FORCE_CLK_STOP, reg); |
| } |
| } |
| } else if (flag == HWACG_Q_ACTIVE_DIS) { |
| if (mode == CMDQ_MODE) { |
| if (delayed_work_pending(&host->hwacg_work)) |
| cancel_delayed_work_sync(&host->hwacg_work); |
| else |
| flush_delayed_work(&host->hwacg_work); |
| } |
| reg &= ~(MMC_HWACG_CONTROL); |
| host->qactive_check = HWACG_Q_ACTIVE_DIS; |
| mci_writel(host, FORCE_CLK_STOP, reg); |
| } |
| } |
| |
| static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr) |
| { |
| /* |
| * Exynos4412 and Exynos5250 extends the use of CMD register with the |
| * use of bit 29 (which is reserved on standard MSHC controllers) for |
| * optionally bypassing the HOLD register for command and data. The |
| * HOLD register should be bypassed in case there is no phase shift |
| * applied on CMD/DATA that is sent to the card. |
| */ |
| if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL))) |
| *cmdr |= SDMMC_CMD_USE_HOLD_REG; |
| } |
| |
| static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 dqs, strobe; |
| /* |
| * Not supported to configure register |
| * related to HS400 |
| */ |
| |
| dqs = priv->saved_dqs_en; |
| strobe = priv->saved_strobe_ctrl; |
| |
| if (timing == MMC_TIMING_MMC_HS400 || |
| timing == MMC_TIMING_MMC_HS400_ES) { |
| dqs &= ~(DWMCI_TXDT_CRC_TIMER_SET(0xFF, 0xFF)); |
| dqs |= (DWMCI_TXDT_CRC_TIMER_SET(priv->hs400_tx_t_fastlimit, |
| priv->hs400_tx_t_initval) | DWMCI_RDDQS_EN | |
| DWMCI_AXI_NON_BLOCKING_WRITE); |
| if(host->pdata->quirks & DW_MCI_QUIRK_ENABLE_ULP) { |
| if (priv->delay_line || priv->tx_delay_line) |
| strobe = DWMCI_WD_DQS_DELAY_CTRL(priv->tx_delay_line) | |
| DWMCI_FIFO_CLK_DELAY_CTRL(0x2) | |
| DWMCI_RD_DQS_DELAY_CTRL(priv->delay_line); |
| else |
| strobe = DWMCI_FIFO_CLK_DELAY_CTRL(0x2) | |
| DWMCI_RD_DQS_DELAY_CTRL(90); |
| } else { |
| if (priv->delay_line) |
| strobe = DWMCI_FIFO_CLK_DELAY_CTRL(0x2) | |
| DWMCI_RD_DQS_DELAY_CTRL(priv->delay_line); |
| else |
| strobe = DWMCI_FIFO_CLK_DELAY_CTRL(0x2) | |
| DWMCI_RD_DQS_DELAY_CTRL(90); |
| } |
| dqs |= (DATA_STROBE_EN | DWMCI_AXI_NON_BLOCKING_WRITE); |
| if (timing == MMC_TIMING_MMC_HS400_ES) |
| dqs |= DWMCI_RESP_RCLK_MODE; |
| } else { |
| dqs &= ~DATA_STROBE_EN; |
| } |
| |
| mci_writel(host, HS400_DQS_EN, dqs); |
| mci_writel(host, HS400_DLINE_CTRL, strobe); |
| } |
| |
| static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 actual; |
| u8 div; |
| int ret; |
| /* |
| * Don't care if wanted clock is zero or |
| * ciu clock is unavailable |
| */ |
| if (!wanted || IS_ERR(host->ciu_clk)) |
| return; |
| |
| /* Guaranteed minimum frequency for cclkin */ |
| if (wanted < EXYNOS_CCLKIN_MIN) |
| wanted = EXYNOS_CCLKIN_MIN; |
| |
| if (wanted == priv->cur_speed) |
| return; |
| |
| div = dw_mci_exynos_get_ciu_div(host); |
| ret = clk_set_rate(host->ciu_clk, wanted * div); |
| if (ret) |
| dev_warn(host->dev, |
| "failed to set clk-rate %u error: %d\n", |
| wanted * div, ret); |
| actual = clk_get_rate(host->ciu_clk); |
| host->bus_hz = actual / div; |
| priv->cur_speed = wanted; |
| host->current_speed = 0; |
| } |
| |
| static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| unsigned int wanted = ios->clock; |
| u32 *clk_tbl = priv->ref_clk; |
| u32 timing = ios->timing, clksel; |
| u32 cclkin; |
| |
| cclkin = clk_tbl[timing]; |
| host->pdata->io_mode = timing; |
| if(host->bus_hz != cclkin) |
| wanted = cclkin; |
| |
| switch (timing) { |
| case MMC_TIMING_MMC_HS400: |
| case MMC_TIMING_MMC_HS400_ES: |
| /* Update tuned sample timing */ |
| if(host->pdata->quirks & DW_MCI_QUIRK_ENABLE_ULP){ |
| clksel = SDMMC_CLKSEL_UP_SAMPLE( |
| priv->hs400_ulp_timing, |
| priv->tuned_sample); |
| clksel |= (BIT(30) | BIT(19)); /* ultra low powermode on */ |
| } else { |
| clksel = SDMMC_CLKSEL_UP_SAMPLE( |
| priv->hs400_timing, |
| priv->tuned_sample); |
| clksel &= ~(BIT(30) | BIT(19)); /* ultra low powermode on */ |
| wanted <<= 1; |
| } |
| if (host->pdata->is_fine_tuned) |
| clksel |= BIT(6); |
| break; |
| case MMC_TIMING_MMC_DDR52: |
| case MMC_TIMING_UHS_DDR50: |
| clksel = priv->ddr_timing; |
| /* Should be double rate for DDR mode */ |
| if (ios->bus_width == MMC_BUS_WIDTH_8) |
| wanted <<= 1; |
| break; |
| case MMC_TIMING_MMC_HS200: |
| clksel = SDMMC_CLKSEL_UP_SAMPLE(priv->hs200_timing, priv->tuned_sample); |
| break; |
| case MMC_TIMING_UHS_SDR104: |
| if(priv->sdr104_timing) |
| clksel = SDMMC_CLKSEL_UP_SAMPLE(priv->sdr104_timing, priv->tuned_sample); |
| else { |
| dev_info(host->dev,"Setting of SDR104 timing in not been!!\n"); |
| clksel = SDMMC_CLKSEL_UP_SAMPLE(priv->sdr_timing, |
| priv->tuned_sample); |
| } |
| break; |
| case MMC_TIMING_UHS_SDR50: |
| if(priv->sdr50_timing) |
| clksel = SDMMC_CLKSEL_UP_SAMPLE(priv->sdr50_timing, priv->tuned_sample); |
| else { |
| dev_info(host->dev,"Setting of SDR50 timing is not been!!\n"); |
| clksel = SDMMC_CLKSEL_UP_SAMPLE(priv->sdr_timing, |
| priv->tuned_sample); |
| } |
| break; |
| default: |
| clksel = priv->sdr_timing; |
| } |
| |
| if (host->pdata->quirks & DW_MCI_QUIRK_HWACG_CTRL) { |
| if (host->current_speed > 400*1000) |
| dw_mci_card_int_hwacg_ctrl(host, HWACG_Q_ACTIVE_EN, LEGACY_MODE); |
| else |
| dw_mci_card_int_hwacg_ctrl(host, HWACG_Q_ACTIVE_DIS, LEGACY_MODE); |
| } |
| |
| host->cclk_in = wanted; |
| |
| /* Set clock timing for the requested speed mode*/ |
| dw_mci_exynos_set_clksel_timing(host, clksel); |
| |
| /* Configure setting for HS400 */ |
| dw_mci_exynos_config_hs400(host, timing); |
| |
| /* Configure clock rate */ |
| dw_mci_exynos_adjust_clock(host, wanted); |
| } |
| |
| #ifndef MHZ |
| #define MHZ (1000 * 1000) |
| #endif |
| static int dw_mci_exynos_parse_dt(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv; |
| struct device_node *np = host->dev->of_node; |
| u32 timing[4]; |
| u32 div = 0, voltage_int_extra = 0; |
| int idx; |
| int ref_clk_size; |
| u32 *ref_clk; |
| u32 *ciu_clkin_values = NULL; |
| int idx_ref; |
| int ret = 0; |
| int id = 0, i; |
| |
| priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) { |
| dev_err(host->dev, "mem alloc failed for private data\n"); |
| return -ENOMEM; |
| } |
| |
| for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) { |
| if (of_device_is_compatible(np, exynos_compat[idx].compatible)) |
| priv->ctrl_type = exynos_compat[idx].ctrl_type; |
| } |
| |
| if (of_property_read_u32(np, "num-ref-clks", &ref_clk_size)) { |
| dev_err(host->dev, "Getting a number of referece clock failed\n"); |
| ret = -ENODEV; |
| goto err_ref_clk; |
| } |
| |
| ref_clk = devm_kzalloc(host->dev, ref_clk_size * sizeof(*ref_clk), |
| GFP_KERNEL); |
| if (!ref_clk) { |
| dev_err(host->dev, "Mem alloc failed for reference clock table\n"); |
| ret = -ENOMEM; |
| goto err_ref_clk; |
| } |
| |
| ciu_clkin_values = devm_kzalloc(host->dev, |
| ref_clk_size * sizeof(*ciu_clkin_values), GFP_KERNEL); |
| |
| if (!ciu_clkin_values) { |
| dev_err(host->dev, "Mem alloc failed for temporary clock values\n"); |
| ret = -ENOMEM; |
| goto err_ref_clk; |
| } |
| if (of_property_read_u32_array(np, "ciu_clkin", ciu_clkin_values, ref_clk_size)) { |
| dev_err(host->dev, "Getting ciu_clkin values faild\n"); |
| ret = -ENOMEM; |
| goto err_ref_clk; |
| } |
| |
| for (idx_ref = 0; idx_ref < ref_clk_size; idx_ref++, ref_clk++, ciu_clkin_values++) { |
| if (*ciu_clkin_values > MHZ) |
| *(ref_clk) = (*ciu_clkin_values); |
| else |
| *(ref_clk) = (*ciu_clkin_values) * MHZ; |
| } |
| |
| ref_clk -= ref_clk_size; |
| ciu_clkin_values -= ref_clk_size; |
| priv->ref_clk = ref_clk; |
| |
| if (of_get_property(np, "card-detect", NULL)) |
| priv->cd_gpio = of_get_named_gpio(np, "card-detect", 0); |
| else |
| priv->cd_gpio = -1; |
| |
| if (of_get_property(np, "sec-sd-slot-type", NULL)) |
| of_property_read_u32(np, |
| "sec-sd-slot-type", &priv->sec_sd_slot_type); |
| else { |
| if (priv->cd_gpio != -1) /* treat default SD slot if cd_gpio is defined */ |
| priv->sec_sd_slot_type = SEC_HOTPLUG_SD_SLOT; |
| else |
| priv->sec_sd_slot_type = -1; |
| } |
| |
| /* Swapping clock drive strength */ |
| of_property_read_u32(np, "clk-drive-number", &priv->clk_drive_number); |
| |
| priv->pinctrl = devm_pinctrl_get(host->dev); |
| |
| if (IS_ERR(priv->pinctrl)) { |
| priv->pinctrl = NULL; |
| } else { |
| priv->clk_drive_base = pinctrl_lookup_state(priv->pinctrl, "default"); |
| priv->clk_drive_str[0] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-1x"); |
| priv->clk_drive_str[1] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-2x"); |
| priv->clk_drive_str[2] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-3x"); |
| priv->clk_drive_str[3] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-4x"); |
| priv->clk_drive_str[4] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-5x"); |
| priv->clk_drive_str[5] = pinctrl_lookup_state(priv->pinctrl, "fast-slew-rate-6x"); |
| |
| for (i = 0; i < 6; i++) { |
| if (IS_ERR(priv->clk_drive_str[i])) |
| priv->clk_drive_str[i] = NULL; |
| } |
| } |
| |
| of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div); |
| priv->ciu_div = div; |
| |
| if (of_property_read_u32(np, "samsung,voltage-int-extra", &voltage_int_extra)) |
| priv->voltage_int_extra = voltage_int_extra; |
| |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-sdr-timing", timing, 4); |
| if (ret) |
| return ret; |
| |
| priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-ddr-timing", timing, 4); |
| if (ret) |
| return ret; |
| |
| priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| |
| of_property_read_u32(np, "ignore-phase", &priv->ignore_phase); |
| if (of_find_property(np, "bypass-for-allpass", NULL)) |
| priv->ctrl_flag |= DW_MMC_EXYNOS_BYPASS_FOR_ALL_PASS; |
| if (of_find_property(np, "use-enable-shift", NULL)) |
| priv->ctrl_flag |= DW_MMC_EXYNOS_ENABLE_SHIFT; |
| |
| id = of_alias_get_id(host->dev->of_node, "mshc"); |
| switch (id) { |
| /* dwmmc0 : eMMC */ |
| case 0: |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-hs200-timing", timing, 4); |
| if (ret) |
| goto err_ref_clk; |
| priv->hs200_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-hs400-timing", timing, 4); |
| if (ret) |
| goto err_ref_clk; |
| |
| priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-hs400-ulp-timing", timing, 4); |
| if (!ret) |
| priv->hs400_ulp_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| else |
| ret = 0; |
| |
| /* Rx Delay Line */ |
| of_property_read_u32(np, |
| "samsung,dw-mshc-hs400-delay-line", &priv->delay_line); |
| |
| /* Tx Delay Line */ |
| of_property_read_u32(np, |
| "samsung,dw-mshc-hs400-tx-delay-line", &priv->tx_delay_line); |
| |
| /* The fast RXCRC packet arrival time */ |
| of_property_read_u32(np, |
| "samsung,dw-mshc-txdt-crc-timer-fastlimit", &priv->hs400_tx_t_fastlimit); |
| |
| /* Initial value of the timeout down counter for RXCRC packet */ |
| of_property_read_u32(np, |
| "samsung,dw-mshc-txdt-crc-timer-initval", &priv->hs400_tx_t_initval); |
| break; |
| /* dwmmc1 : SDIO */ |
| case 1: |
| /* dwmmc2 : SD Card */ |
| case 2: |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-sdr50-timing", timing, 4); /* SDR50 100Mhz */ |
| if (!ret) |
| priv->sdr50_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| else { |
| priv->sdr50_timing = priv->sdr_timing; |
| ret = 0; |
| } |
| |
| ret = of_property_read_u32_array(np, |
| "samsung,dw-mshc-sdr104-timing", timing, 4); /* SDR104 200mhz */ |
| if (!ret) |
| priv->sdr104_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], timing[2], timing[3]); |
| else { |
| priv->sdr104_timing = priv->sdr_timing; |
| ret = 0; |
| } |
| break; |
| default: |
| ret = -ENODEV; |
| } |
| |
| host->priv = priv; |
| err_ref_clk: |
| return ret; |
| } |
| |
| static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host) |
| { |
| return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL)); |
| } |
| |
| static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample) |
| { |
| u32 clksel; |
| clksel = mci_readl(host, CLKSEL); |
| clksel = (clksel & ~0x7) | SDMMC_CLKSEL_CCLK_SAMPLE(sample); |
| mci_writel(host, CLKSEL, clksel); |
| } |
| |
| static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host) |
| { |
| u32 clksel; |
| u8 sample; |
| |
| clksel = mci_readl(host, CLKSEL); |
| sample = (clksel + 1) & 0x7; |
| clksel = (clksel & ~0x7) | sample; |
| mci_writel(host, CLKSEL, clksel); |
| return sample; |
| } |
| |
| static void dw_mci_set_quirk_endbit(struct dw_mci *host, s8 mid) |
| { |
| u32 clksel, phase; |
| u32 shift; |
| |
| clksel = mci_readl(host, CLKSEL); |
| phase = (((clksel >> 24) & 0x7) + 1) << 1; |
| shift = 360 / phase; |
| |
| if (host->verid < DW_MMC_260A && (shift * mid) % 360 >= 225) |
| host->quirks |= DW_MCI_QUIRK_NO_DETECT_EBIT; |
| else |
| host->quirks &= ~DW_MCI_QUIRK_NO_DETECT_EBIT; |
| } |
| |
| static void dw_mci_exynos_set_enable_shift(struct dw_mci *host, u32 sample, bool fine_tune) |
| { |
| u32 i, j, en_shift, en_shift_phase[3][4] = {{0, 0, 1, 0}, |
| {1, 2, 3, 3}, |
| {2, 4, 5, 5}}; |
| |
| en_shift = mci_readl(host, HS400_ENABLE_SHIFT) |
| & ~(DWMCI_ENABLE_SHIFT_MASK); |
| |
| for (i = 0; i < 3; i++) { |
| for (j = 1; j < 4; j++) { |
| if (sample == en_shift_phase[i][j]) { |
| en_shift |= DWMCI_ENABLE_SHIFT(en_shift_phase[i][0]); |
| break; |
| } |
| } |
| } |
| if ((en_shift < 2) && fine_tune) |
| en_shift += 1; |
| mci_writel(host, HS400_ENABLE_SHIFT, en_shift); |
| } |
| static u8 dw_mci_tuning_sampling(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 clksel, i; |
| u8 sample; |
| |
| clksel = mci_readl(host, CLKSEL); |
| sample = (clksel + 1) & 0x7; |
| |
| if (priv->ignore_phase) { |
| for (i = 0; i < 8; i++) { |
| if (priv->ignore_phase & (0x1 << sample)) |
| sample = (sample + 1) & 0x7; |
| else |
| break; |
| } |
| } |
| clksel = (clksel & 0xfffffff8) | sample; |
| mci_writel(host, CLKSEL, clksel); |
| |
| if (!(priv->ignore_phase & phase7_en)) { |
| if (phase7_en & (0x1 << sample)) |
| mci_phase7_mux_en(host, AXI_BURST_LEN); |
| else |
| mci_phase7_mux_dis(host, AXI_BURST_LEN); |
| } |
| |
| if (priv->ctrl_flag & DW_MMC_EXYNOS_ENABLE_SHIFT) |
| dw_mci_exynos_set_enable_shift(host, sample, false); |
| |
| return sample; |
| } |
| |
| /* initialize the clock sample to given value */ |
| static void dw_mci_exynos_set_sample(struct dw_mci *host, u32 sample, bool tuning) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 clksel; |
| |
| clksel = mci_readl(host, CLKSEL); |
| clksel = (clksel & ~0x7) | SDMMC_CLKSEL_CCLK_SAMPLE(sample); |
| mci_writel(host, CLKSEL, clksel); |
| |
| if (sample == 7) |
| mci_phase7_mux_en(host, AXI_BURST_LEN); |
| else |
| mci_phase7_mux_dis(host, AXI_BURST_LEN); |
| |
| if (priv->ctrl_flag & DW_MMC_EXYNOS_ENABLE_SHIFT) |
| dw_mci_exynos_set_enable_shift(host, sample, false); |
| if (!tuning) |
| dw_mci_set_quirk_endbit(host, clksel); |
| } |
| |
| static void dw_mci_set_fine_tuning_bit(struct dw_mci *host, |
| bool is_fine_tuning) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 clksel, sample; |
| |
| clksel = mci_readl(host, CLKSEL); |
| clksel &= ~BIT(6); |
| sample = (clksel & 0x7); |
| |
| if (is_fine_tuning) { |
| host->pdata->is_fine_tuned = true; |
| clksel |= BIT(6); |
| } else |
| host->pdata->is_fine_tuned = false; |
| mci_writel(host, CLKSEL, clksel); |
| if (priv->ctrl_flag & DW_MMC_EXYNOS_ENABLE_SHIFT) { |
| if (((sample % 2) == 1) && is_fine_tuning && sample != 0x7) |
| dw_mci_exynos_set_enable_shift(host, sample, true); |
| else |
| dw_mci_exynos_set_enable_shift(host, sample, false); |
| } |
| } |
| |
| /* read current clock sample offset */ |
| static u32 dw_mci_exynos_get_sample(struct dw_mci *host) |
| { |
| u32 clksel = mci_readl(host, CLKSEL); |
| return SDMMC_CLKSEL_CCLK_SAMPLE(clksel); |
| } |
| |
| static int __find_median_of_16bits(u32 orig_bits, u16 mask, u8 startbit) |
| { |
| u32 i, testbits; |
| |
| testbits = orig_bits; |
| for (i = startbit; i < (16 + startbit); i++, testbits >>= 1) |
| if ((testbits & mask) == mask) |
| return SDMMC_CLKSEL_CCLK_FINE_SAMPLE(i); |
| return -1; |
| } |
| |
| #define NUM_OF_MASK 7 |
| static int find_median_of_16bits(struct dw_mci *host, unsigned int map, bool force) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| u32 orig_bits; |
| u8 i, divratio; |
| int sel = -1; |
| u16 mask[NUM_OF_MASK] = {0x1fff, 0x7ff, 0x1ff, 0x7f, 0x1f, 0xf, 0x7}; |
| /* Tuning during the center value is set to 3/2 */ |
| int optimum[NUM_OF_MASK] = {9, 7, 6, 5, 3, 2, 1}; |
| |
| /* replicate the map so "arithimetic shift right" shifts in |
| * the same bits "again". e.g. portable "Rotate Right" bit operation. |
| */ |
| if (map == 0xFFFF && force == false) |
| return sel; |
| |
| divratio = (mci_readl(host, CLKSEL) >> 24) & 0x7; |
| dev_info(host->dev, "divratio: %d map: 0x %08x\n", divratio, map); |
| |
| orig_bits = map | (map << 16); |
| |
| if (divratio == 1) { |
| if (!(priv->ctrl_flag & DW_MMC_EXYNOS_ENABLE_SHIFT)) |
| orig_bits = orig_bits & (orig_bits >> 8); |
| } |
| |
| for (i = 0; i < NUM_OF_MASK; i++) { |
| sel = __find_median_of_16bits(orig_bits, mask[i], optimum[i]); |
| if (-1 != sel) |
| break; |
| } |
| |
| return sel; |
| } |
| |
| static void exynos_dwmci_tuning_drv_st(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| dev_info(host->dev, "Clock GPIO Drive Strength Value: x%d\n", |
| (priv->clk_drive_tuning)); |
| |
| if (priv->pinctrl && priv->clk_drive_str[priv->clk_drive_tuning - 1]) |
| pinctrl_select_state(priv->pinctrl, priv->clk_drive_str[priv->clk_drive_tuning - 1]); |
| } |
| |
| /* |
| * Test all 8 possible "Clock in" Sample timings. |
| * Create a bitmap of which CLock sample values work and find the "median" |
| * value. Apply it and remember that we found the best value. |
| */ |
| static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode, |
| struct dw_mci_tuning_data *tuning_data) |
| { |
| struct dw_mci *host = slot->host; |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| struct mmc_host *mmc = slot->mmc; |
| unsigned int tuning_loop = MAX_TUNING_LOOP; |
| unsigned int drv_str_retries; |
| bool tuned = 0; |
| int ret = 0; |
| u8 *tuning_blk; /* data read from device */ |
| |
| unsigned int sample_good = 0; /* bit map of clock sample (0-7) */ |
| u32 test_sample = -1; |
| u32 orig_sample; |
| int best_sample = 0, best_sample_ori = 0; |
| u8 pass_index; |
| bool is_fine_tuning = false; |
| unsigned int abnormal_result = 0xFFFF; |
| unsigned int temp_ignore_phase = priv->ignore_phase; |
| int ffs_ignore_phase = 0; |
| u8 all_pass_count = 0; |
| bool bypass = false; |
| |
| while (temp_ignore_phase) { |
| ffs_ignore_phase = ffs(temp_ignore_phase) - 1; |
| abnormal_result &= ~(0x3 << (2 * ffs_ignore_phase)); |
| temp_ignore_phase &= ~(0x1 << ffs_ignore_phase); |
| } |
| |
| /* Short circuit: don't tune again if we already did. */ |
| if (host->pdata->tuned) { |
| host->drv_data->misc_control(host, CTRL_RESTORE_CLKSEL, NULL); |
| mci_writel(host, CDTHRCTL, host->cd_rd_thr << 16 | 1); |
| dev_info(host->dev, "EN_SHIFT 0x %08x CLKSEL 0x %08x\n", |
| mci_readl(host, HS400_ENABLE_SHIFT), |
| mci_readl(host, CLKSEL)); |
| return 0; |
| } |
| |
| tuning_blk = kmalloc(2 * tuning_data->blksz, GFP_KERNEL); |
| if (!tuning_blk) |
| return -ENOMEM; |
| |
| test_sample = orig_sample = dw_mci_exynos_get_sample(host); |
| host->cd_rd_thr = 512; |
| mci_writel(host, CDTHRCTL, host->cd_rd_thr << 16 | 1); |
| |
| /* |
| * eMMC 4.5 spec section 6.6.7.1 says the device is guaranteed to |
| * complete 40 iteration of CMD21 in 150ms. So this shouldn't take |
| * longer than about 30ms or so....at least assuming most values |
| * work and don't time out. |
| */ |
| |
| if (host->pdata->io_mode == MMC_TIMING_MMC_HS400) |
| host->quirks |= DW_MCI_QUIRK_NO_DETECT_EBIT; |
| |
| dev_info(host->dev, "Tuning Abnormal_result 0x%08x.\n", abnormal_result); |
| |
| priv->clk_drive_tuning = priv->clk_drive_number; |
| drv_str_retries = priv->clk_drive_number; |
| |
| do { |
| struct mmc_request mrq; |
| struct mmc_command cmd; |
| struct mmc_command stop; |
| struct mmc_data data; |
| struct scatterlist sg; |
| |
| if (!tuning_loop) |
| break; |
| |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.opcode = opcode; |
| cmd.arg = 0; |
| cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; |
| cmd.error = 0; |
| cmd.busy_timeout = 10; /* 2x * (150ms/40 + setup overhead) */ |
| |
| memset(&stop, 0, sizeof(stop)); |
| stop.opcode = MMC_STOP_TRANSMISSION; |
| stop.arg = 0; |
| stop.flags = MMC_RSP_R1B | MMC_CMD_AC; |
| stop.error = 0; |
| |
| memset(&data, 0, sizeof(data)); |
| data.blksz = tuning_data->blksz; |
| data.blocks = 1; |
| data.flags = MMC_DATA_READ; |
| data.sg = &sg; |
| data.sg_len = 1; |
| data.error = 0; |
| |
| memset(tuning_blk, ~0U, tuning_data->blksz); |
| sg_init_one(&sg, tuning_blk, tuning_data->blksz); |
| |
| memset(&mrq, 0, sizeof(mrq)); |
| mrq.cmd = &cmd; |
| mrq.stop = &stop; |
| mrq.data = &data; |
| host->mrq = &mrq; |
| |
| /* |
| * DDR200 tuning Sequence with fine tuning setup |
| * |
| * 0. phase 0 (0 degree) + no fine tuning setup |
| * - pass_index = 0 |
| * 1. phase 0 + fine tuning setup |
| * - pass_index = 1 |
| * 2. phase 1 (90 degree) + no fine tuning setup |
| * - pass_index = 2 |
| * .. |
| * 15. phase 7 + fine tuning setup |
| * - pass_index = 15 |
| * |
| */ |
| dw_mci_set_fine_tuning_bit(host, is_fine_tuning); |
| |
| dw_mci_set_timeout(host, dw_mci_calc_timeout(host)); |
| mmc_wait_for_req(mmc, &mrq); |
| |
| pass_index = (u8)test_sample * 2; |
| |
| if (is_fine_tuning) |
| pass_index++; |
| |
| if (!cmd.error && !data.error) { |
| /* |
| * Verify the "tuning block" arrived (to host) intact. |
| * If yes, remember this sample value works. |
| */ |
| if (host->use_dma == 1) { |
| sample_good |= (1 << pass_index); |
| } else { |
| if (!memcmp(tuning_data->blk_pattern, tuning_blk, tuning_data->blksz)) |
| sample_good |= (1 << pass_index); |
| } |
| } else { |
| dev_info(&mmc->class_dev, |
| "Tuning error: cmd.error:%d, data.error:%d CLKSEL = 0x%08x, EN_SHIFT = 0x%08x\n", |
| cmd.error, data.error, |
| mci_readl(host, CLKSEL), |
| mci_readl(host, HS400_ENABLE_SHIFT)); |
| } |
| |
| if (is_fine_tuning) |
| test_sample = dw_mci_tuning_sampling(host); |
| |
| is_fine_tuning = !is_fine_tuning; |
| |
| if (orig_sample == test_sample && !is_fine_tuning) { |
| |
| /* |
| * Get at middle clock sample values. |
| */ |
| if (sample_good == abnormal_result) |
| all_pass_count++; |
| |
| if (priv->ctrl_flag & DW_MMC_EXYNOS_BYPASS_FOR_ALL_PASS) |
| bypass = (all_pass_count > priv->clk_drive_number) ? true : false; |
| |
| if (bypass) { |
| dev_info(host->dev, "Bypassed for all pass at %d times\n", priv->clk_drive_number); |
| sample_good = abnormal_result & 0xFFFF; |
| tuned = true; |
| } |
| |
| best_sample = find_median_of_16bits(host, sample_good, bypass); |
| |
| if (best_sample >= 0) { |
| dev_info(host->dev, "sample_good: 0x%02x best_sample: 0x%02x\n", |
| sample_good, best_sample); |
| |
| if (sample_good != abnormal_result || bypass) { |
| tuned = true; |
| break; |
| } |
| } else |
| dev_info(host->dev, |
| "Failed to find median Value in sample_good (0x%02x)\n", sample_good); |
| |
| if (drv_str_retries) { |
| drv_str_retries--; |
| if (priv->clk_drive_str[0]) { |
| exynos_dwmci_tuning_drv_st(host); |
| if (priv->clk_drive_tuning > 0) |
| priv->clk_drive_tuning--; |
| } |
| sample_good = 0; |
| } else |
| break; |
| } |
| tuning_loop--; |
| } while (!tuned); |
| |
| /* |
| * To set sample value with mid, the value should be divided by 2, |
| * because mid represents index in pass map extended.(8 -> 16 bits) |
| * And that mid is odd number, means the selected case includes |
| * using fine tuning. |
| */ |
| |
| best_sample_ori = best_sample; |
| best_sample /= 2; |
| |
| if (host->pdata->io_mode == MMC_TIMING_MMC_HS400) |
| host->quirks &= ~DW_MCI_QUIRK_NO_DETECT_EBIT; |
| |
| if (tuned) { |
| host->pdata->clk_smpl = priv->tuned_sample = best_sample; |
| if (host->pdata->only_once_tune) |
| host->pdata->tuned = true; |
| |
| if (best_sample_ori % 2) |
| best_sample += 1; |
| |
| dw_mci_exynos_set_sample(host, best_sample, false); |
| dw_mci_set_fine_tuning_bit(host, false); |
| } else { |
| /* Failed. Just restore and return error */ |
| dev_err(host->dev, "tuning err\n"); |
| mci_writel(host, CDTHRCTL, 0 << 16 | 0); |
| dw_mci_exynos_set_sample(host, orig_sample, false); |
| ret = -EIO; |
| } |
| |
| /* Rollback Clock drive strength */ |
| if (priv->pinctrl && priv->clk_drive_base) |
| pinctrl_select_state(priv->pinctrl, priv->clk_drive_base); |
| |
| dev_info(host->dev, "CLKSEL = 0x%08x, EN_SHIFT = 0x%08x\n", |
| mci_readl(host, CLKSEL), |
| mci_readl(host, HS400_ENABLE_SHIFT)); |
| |
| kfree(tuning_blk); |
| return ret; |
| } |
| |
| static struct device *sd_detection_cmd_dev; |
| |
| static ssize_t sd_detection_cmd_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) { |
| if (priv->sec_sd_slot_type > 0 && !gpio_is_valid(priv->cd_gpio)) |
| goto gpio_error; |
| |
| dev_info(host->dev, "SD card inserted.\n"); |
| return sprintf(buf, "Insert\n"); |
| } else { |
| if (priv->sec_sd_slot_type > 0 && !gpio_is_valid(priv->cd_gpio)) |
| goto gpio_error; |
| |
| if (gpio_get_value(priv->cd_gpio) ^ (host->pdata->use_gpio_invert) |
| && priv->sec_sd_slot_type == SEC_HYBRID_SD_SLOT) { |
| dev_info(host->dev, "SD slot tray Removed.\n"); |
| return sprintf(buf, "Notray\n"); |
| } |
| |
| dev_info(host->dev, "SD card removed.\n"); |
| return sprintf(buf, "Remove\n"); |
| } |
| |
| gpio_error: |
| dev_info(host->dev, "%s : External SD detect pin Error\n", __func__); |
| return sprintf(buf, "Error\n"); |
| } |
| |
| static ssize_t sd_detection_cnt_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| |
| dev_info(host->dev, "%s : CD count is = %u\n", __func__, host->card_detect_cnt); |
| return sprintf(buf, "%u", host->card_detect_cnt); |
| } |
| |
| static ssize_t sd_detection_maxmode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| const char *uhs_bus_speed_mode = ""; |
| struct device_node *np = host->dev->of_node; |
| |
| if (of_find_property(np, "sd-uhs-sdr104", NULL)) |
| uhs_bus_speed_mode = "SDR104"; |
| else if (of_find_property(np, "sd-uhs-ddr50", NULL)) |
| uhs_bus_speed_mode = "DDR50"; |
| else if (of_find_property(np, "sd-uhs-sdr50", NULL)) |
| uhs_bus_speed_mode = "SDR50"; |
| else if (of_find_property(np, "sd-uhs-sdr25", NULL)) |
| uhs_bus_speed_mode = "SDR25"; |
| else if (of_find_property(np, "sd-uhs-sdr12", NULL)) |
| uhs_bus_speed_mode = "SDR12"; |
| else |
| uhs_bus_speed_mode = "HS"; |
| |
| dev_info(host->dev, "%s : Max supported Host Speed Mode = %s\n", __func__, uhs_bus_speed_mode); |
| return sprintf(buf, "%s\n", uhs_bus_speed_mode); |
| } |
| static ssize_t sd_detection_curmode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| |
| const char *uhs_bus_speed_mode = ""; |
| static const char *const uhs_speeds[] = { |
| [UHS_SDR12_BUS_SPEED] = "SDR12", |
| [UHS_SDR25_BUS_SPEED] = "SDR25", |
| [UHS_SDR50_BUS_SPEED] = "SDR50", |
| [UHS_SDR104_BUS_SPEED] = "SDR104", |
| [UHS_DDR50_BUS_SPEED] = "DDR50", |
| }; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) { |
| if (mmc_card_uhs(host->cur_slot->mmc->card)) |
| uhs_bus_speed_mode = uhs_speeds[host->cur_slot->mmc->card->sd_bus_speed]; |
| else |
| uhs_bus_speed_mode = "HS"; |
| dev_info(host->dev, "%s : Current SD Card Speed = %s\n", __func__, uhs_bus_speed_mode); |
| } else |
| uhs_bus_speed_mode = "No Card"; |
| return sprintf(buf, "%s\n", uhs_bus_speed_mode); |
| } |
| |
| static ssize_t sdcard_summary_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *card; |
| const char *uhs_bus_speed_mode = ""; |
| static const char *const uhs_speeds[] = { |
| [UHS_SDR12_BUS_SPEED] = "SDR12", |
| [UHS_SDR25_BUS_SPEED] = "SDR25", |
| [UHS_SDR50_BUS_SPEED] = "SDR50", |
| [UHS_SDR104_BUS_SPEED] = "SDR104", |
| [UHS_DDR50_BUS_SPEED] = "DDR50", |
| }; |
| static const char *const unit[] = {"KB", "MB", "GB", "TB"}; |
| unsigned int size, serial; |
| int digit = 1; |
| char ret_size[6]; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) { |
| card = host->cur_slot->mmc->card; |
| |
| /* MANID */ |
| /* SERIAL */ |
| serial = card->cid.serial & (0x0000FFFF); |
| |
| /*SIZE*/ |
| if (card->csd.read_blkbits == 9) /* 1 Sector = 512 Bytes */ |
| size = (card->csd.capacity) >> 1; |
| else if (card->csd.read_blkbits == 11) /* 1 Sector = 2048 Bytes */ |
| size = (card->csd.capacity) << 1; |
| else /* 1 Sector = 1024 Bytes */ |
| size = card->csd.capacity; |
| |
| if (size >= 380000000 && size <= 410000000) { /* QUIRK 400GB SD Card */ |
| sprintf(ret_size, "400GB"); |
| } else if (size >= 190000000 && size <= 210000000) { /* QUIRK 200GB SD Card */ |
| sprintf(ret_size, "200GB"); |
| } else { |
| while ((size >> 1) > 0) { |
| size = size >> 1; |
| digit++; |
| } |
| sprintf(ret_size, "%d%s", 1 << (digit%10), unit[digit/10]); |
| } |
| |
| /* SPEEDMODE */ |
| if (mmc_card_uhs(card)) |
| uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed]; |
| else if (mmc_card_hs(card)) |
| uhs_bus_speed_mode = "HS"; |
| else |
| uhs_bus_speed_mode = "DS"; |
| |
| /* SUMMARY */ |
| dev_info(host->dev, "MANID : 0x%02X, SERIAL : %04X, SIZE : %s, SPEEDMODE : %s\n", |
| card->cid.manfid, serial, ret_size, uhs_bus_speed_mode); |
| return sprintf(buf, "\"MANID\":\"0x%02X\",\"SERIAL\":\"%04X\""\ |
| ",\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\",\"NOTI\":\"%d\"\n", |
| card->cid.manfid, serial, ret_size, uhs_bus_speed_mode, |
| card->err_log[0].noti_cnt); |
| } else { |
| /* SUMMARY : No SD Card Case */ |
| dev_info(host->dev, "%s : No SD Card\n", __func__); |
| return sprintf(buf, "\"MANID\":\"NoCard\",\"SERIAL\":\"NoCard\""\ |
| ",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\",\"NOTI\":\"NoCard\"\n"); |
| } |
| } |
| |
| static struct device *sd_info_cmd_dev; |
| static ssize_t sd_count_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *cur_card = NULL; |
| struct mmc_card_error_log *err_log; |
| u64 total_cnt = 0; |
| int len = 0; |
| int i = 0; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) |
| cur_card = host->cur_slot->mmc->card; |
| else { |
| len = snprintf(buf, PAGE_SIZE, "No Card\n"); |
| goto out; |
| } |
| |
| err_log = cur_card->err_log; |
| |
| for (i = 0; i < 6; i++) { |
| if(total_cnt < MAX_CNT_U64) |
| total_cnt += err_log[i].count; |
| } |
| len = snprintf(buf, PAGE_SIZE, "%lld\n", total_cnt); |
| |
| out: |
| return len; |
| } |
| |
| static struct device *sd_data_cmd_dev; |
| static ssize_t sd_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *cur_card = NULL; |
| struct mmc_card_error_log *err_log; |
| u64 total_c_cnt = 0; |
| u64 total_t_cnt = 0; |
| int len = 0; |
| int i = 0; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) |
| cur_card = host->cur_slot->mmc->card; |
| else { |
| len = snprintf(buf, PAGE_SIZE, |
| "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\""\ |
| ",\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\"\n"); |
| goto out; |
| } |
| |
| err_log = cur_card->err_log; |
| |
| for (i = 0; i < 6; i++) { |
| if(err_log[i].err_type == -EILSEQ && total_c_cnt < MAX_CNT_U64) |
| total_c_cnt += err_log[i].count; |
| if(err_log[i].err_type == -ETIMEDOUT && total_t_cnt < MAX_CNT_U64) |
| total_t_cnt += err_log[i].count; |
| } |
| |
| len = snprintf(buf, PAGE_SIZE, |
| "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\""\ |
| ",\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\"\n", |
| err_log[0].ge_cnt, err_log[0].cc_cnt, err_log[0].ecc_cnt, err_log[0].wp_cnt, |
| err_log[0].oor_cnt, total_c_cnt, total_t_cnt); |
| out: |
| return len; |
| } |
| |
| static struct device *mmc_card_dev; |
| static ssize_t mmc_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *card = host->cur_slot->mmc->card; |
| struct mmc_card_error_log *err_log; |
| u64 total_c_cnt = 0; |
| u64 total_t_cnt = 0; |
| int len = 0; |
| int i = 0; |
| |
| if (!card) { |
| len = snprintf(buf, PAGE_SIZE, |
| "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\","\ |
| "\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\","\ |
| "\"HALT\":\"0\",\"CQED\":\"0\",\"RPMB\":\"0\"\n"); |
| goto out; |
| } |
| |
| err_log = card->err_log; |
| |
| for (i = 0; i < 6; i++) { |
| if (err_log[i].err_type == -EILSEQ && total_c_cnt < MAX_CNT_U64) |
| total_c_cnt += err_log[i].count; |
| if (err_log[i].err_type == -ETIMEDOUT && total_t_cnt < MAX_CNT_U64) |
| total_t_cnt += err_log[i].count; |
| } |
| |
| len = snprintf(buf, PAGE_SIZE, |
| "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\","\ |
| "\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\","\ |
| "\"HALT\":\"%d\",\"CQED\":\"%d\",\"RPMB\":\"%d\"\n", |
| err_log[0].ge_cnt, err_log[0].cc_cnt, err_log[0].ecc_cnt, |
| err_log[0].wp_cnt, err_log[0].oor_cnt, total_c_cnt, total_t_cnt, |
| err_log[0].halt_cnt, err_log[0].cq_cnt, err_log[0].rpmb_cnt); |
| out: |
| return len; |
| } |
| |
| static ssize_t mmc_summary_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *card = host->cur_slot->mmc->card; |
| char *bus_speed_mode = ""; |
| static const char *const unit[] = {"B", "KB", "MB", "GB", "TB"}; |
| uint64_t size; |
| int digit = 0, pre_size = 1; |
| char ret_size[6]; |
| |
| if (card) { |
| /* SIZE */ |
| size = (uint64_t)card->ext_csd.sectors * card->ext_csd.data_sector_size; |
| |
| /* SIZE - unit */ |
| while(size > 1024) |
| { |
| size /= 1024; |
| digit++; |
| if(digit == 4) |
| break; |
| } |
| |
| /* SIZE - capacity */ |
| while(size > pre_size) |
| { |
| if(pre_size > 1024) |
| break; |
| pre_size = pre_size << 1; |
| } |
| |
| sprintf(ret_size, "%d%s", pre_size, unit[digit]); |
| |
| /* SPEED MODE */ |
| if(mmc_card_hs400(card)) |
| bus_speed_mode = "HS400"; |
| else if(mmc_card_hs200(card)) |
| bus_speed_mode = "HS200"; |
| else if(mmc_card_ddr52(card)) |
| bus_speed_mode = "DDR50"; |
| else if(mmc_card_hs(card)) |
| bus_speed_mode = "HS"; |
| else |
| bus_speed_mode = "LEGACY"; |
| |
| /* SUMMARY */ |
| sprintf(buf, "\"MANID\":\"0x%02X\",\"PNM\":\"%s\","\ |
| "\"REV\":\"%#x%x%x%x\",\"CQ\":\"%d\","\ |
| "\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\","\ |
| "\"LIFE\":\"%u\"\n", |
| card->cid.manfid, card->cid.prod_name, |
| (char)card->ext_csd.fwrev[4], |
| (char)card->ext_csd.fwrev[5], |
| (char)card->ext_csd.fwrev[6], |
| (char)card->ext_csd.fwrev[7], |
| (mmc_card_cmdq(card) ? true : false), |
| ret_size, bus_speed_mode, |
| (card->ext_csd.device_life_time_est_typ_a > |
| card->ext_csd.device_life_time_est_typ_b ? |
| card->ext_csd.device_life_time_est_typ_a : |
| card->ext_csd.device_life_time_est_typ_b) |
| ); |
| dev_info(dev, "%s", buf); |
| return sprintf(buf, "%s", buf); |
| } else { |
| /* SUMMARY : No MMC Case */ |
| dev_info(dev, "%s : No eMMC Card\n", __func__); |
| return sprintf(buf, "\"MANID\":\"NoCard\",\"PNM\":\"NoCard\",\"REV\":\"NoCard\""\ |
| ",\"CQ\":\"NoCard\",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\""\ |
| "\"LIFE\":\"NoCard\"\n"); |
| } |
| } |
| |
| #ifdef CONFIG_SEC_FACTORY |
| static ssize_t mmc_hwrst_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *card = host->cur_slot->mmc->card; |
| |
| if (card) |
| return sprintf(buf, "%d\n", card->ext_csd.rst_n_function); |
| else |
| return sprintf(buf, "no card\n"); |
| |
| } |
| static DEVICE_ATTR(hwrst, 0444, mmc_hwrst_show, NULL); |
| #endif |
| |
| static ssize_t sd_cid_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *cur_card = NULL; |
| int len = 0; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) |
| cur_card = host->cur_slot->mmc->card; |
| else { |
| len = snprintf(buf, PAGE_SIZE, "No Card\n"); |
| goto out; |
| } |
| |
| len = snprintf(buf, PAGE_SIZE, |
| "%08x%08x%08x%08x\n", |
| cur_card->raw_cid[0], cur_card->raw_cid[1], |
| cur_card->raw_cid[2], cur_card->raw_cid[3]); |
| out: |
| return len; |
| } |
| |
| static ssize_t sd_health_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *cur_card = NULL; |
| struct mmc_card_error_log *err_log; |
| u64 total_c_cnt = 0; |
| u64 total_t_cnt = 0; |
| int len = 0; |
| int i = 0; |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) |
| cur_card = host->cur_slot->mmc->card; |
| |
| if (!cur_card) { |
| //There should be no spaces in 'No Card'(Vold Team). |
| len = snprintf(buf, PAGE_SIZE, "NOCARD\n"); |
| goto out; |
| } |
| |
| err_log = cur_card->err_log; |
| |
| for (i = 0; i < 6; i++) { |
| if (err_log[i].err_type == -EILSEQ && total_c_cnt < MAX_CNT_U64) |
| total_c_cnt += err_log[i].count; |
| if (err_log[i].err_type == -ETIMEDOUT && total_t_cnt < MAX_CNT_U64) |
| total_t_cnt += err_log[i].count; |
| } |
| |
| if(err_log[0].ge_cnt > 100 || err_log[0].ecc_cnt > 0 || err_log[0].wp_cnt > 0 || |
| err_log[0].oor_cnt > 10 || total_t_cnt > 100 || total_c_cnt > 100) |
| len = snprintf(buf, PAGE_SIZE, "BAD\n"); |
| else |
| len = snprintf(buf, PAGE_SIZE, "GOOD\n"); |
| |
| out: |
| return len; |
| } |
| |
| static DEVICE_ATTR(status, 0444, sd_detection_cmd_show, NULL); |
| static DEVICE_ATTR(cd_cnt, 0444, sd_detection_cnt_show, NULL); |
| static DEVICE_ATTR(max_mode, 0444, sd_detection_maxmode_show, NULL); |
| static DEVICE_ATTR(current_mode, 0444, sd_detection_curmode_show, NULL); |
| static DEVICE_ATTR(sdcard_summary, 0444, sdcard_summary_show, NULL); |
| static DEVICE_ATTR(sd_count, 0444, sd_count_show, NULL); |
| static DEVICE_ATTR(sd_data, 0444, sd_data_show, NULL); |
| static DEVICE_ATTR(mmc_data, S_IRUGO, mmc_data_show, NULL); |
| static DEVICE_ATTR(mmc_summary, S_IRUGO, mmc_summary_show, NULL); |
| static DEVICE_ATTR(data, 0444, sd_cid_show, NULL); |
| static DEVICE_ATTR(fc, 0444, sd_health_show, NULL); |
| |
| /* Callback function for SD Card IO Error */ |
| static int sdcard_uevent(struct mmc_card *card) |
| { |
| pr_info("%s: Send Notification about SD Card IO Error\n", mmc_hostname(card->host)); |
| return kobject_uevent(&sd_detection_cmd_dev->kobj, KOBJ_CHANGE); |
| } |
| |
| static int dw_mci_sdcard_uevent(struct device *dev, struct kobj_uevent_env *env) |
| { |
| struct dw_mci *host = dev_get_drvdata(dev); |
| struct mmc_card *card; |
| int retval = 0; |
| bool card_exist; |
| |
| add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); |
| |
| if (host->cur_slot && host->cur_slot->mmc && host->cur_slot->mmc->card) { |
| card_exist = true; |
| card = host->cur_slot->mmc->card; |
| } else |
| card_exist = false; |
| #if 0 /* Disable this feature for MASS Project. It's possible to enable after review */ |
| retval = add_uevent_var(env, "IOERROR=%s", card_exist ? ( |
| ((card->err_log[0].ge_cnt && !(card->err_log[0].ge_cnt % 1000)) || |
| (card->err_log[0].ecc_cnt && !(card->err_log[0].ecc_cnt % 1000)) || |
| (card->err_log[0].wp_cnt && !(card->err_log[0].wp_cnt % 100)) || |
| (card->err_log[0].oor_cnt && !(card->err_log[0].oor_cnt % 100))) |
| ? "YES" : "NO") : "NoCard"); |
| #endif |
| return retval; |
| } |
| |
| static struct device_type sdcard_type = { |
| .uevent = dw_mci_sdcard_uevent, |
| }; |
| |
| static int dw_mci_exynos_request_ext_irq(struct dw_mci *host, |
| irq_handler_t func) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| int ext_cd_irq = 0; |
| if (gpio_is_valid(priv->cd_gpio) && |
| !gpio_request(priv->cd_gpio, "DWMCI_EXT_CD")) { |
| ext_cd_irq = gpio_to_irq(priv->cd_gpio); |
| if (ext_cd_irq && |
| devm_request_irq(host->dev, ext_cd_irq, func, |
| IRQF_TRIGGER_RISING | |
| IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, |
| "tflash_det", host) == 0) { |
| dev_warn(host->dev, "success to request irq for card detect.\n"); |
| enable_irq_wake(ext_cd_irq); |
| } else |
| dev_warn(host->dev, "cannot request irq for card detect.\n"); |
| } |
| return 0; |
| } |
| |
| static int dw_mci_exynos_check_cd(struct dw_mci *host) |
| { |
| int ret = -1; |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| if (gpio_is_valid(priv->cd_gpio)) { |
| if(host->pdata->use_gpio_invert) |
| ret = gpio_get_value(priv->cd_gpio) ? 1 : 0; |
| else |
| ret = gpio_get_value(priv->cd_gpio) ? 0 : 1; |
| } |
| return ret; |
| } |
| |
| static void dw_mci_exynos_add_sysfs(struct dw_mci *host) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| if ((priv->sec_sd_slot_type) >= 0) { |
| if (!sd_detection_cmd_dev) { |
| sd_detection_cmd_dev = sec_device_create(host, "sdcard"); |
| if (IS_ERR(sd_detection_cmd_dev)) |
| pr_err("Fail to create sysfs dev\n"); |
| |
| sd_detection_cmd_dev->type = &sdcard_type; |
| host->slot[0]->mmc->sdcard_uevent = sdcard_uevent; |
| |
| if (device_create_file(sd_detection_cmd_dev, |
| &dev_attr_status) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| |
| if (device_create_file(sd_detection_cmd_dev, |
| &dev_attr_cd_cnt) < 0) |
| pr_err("Fail to create cd_cnt sysfs file\n"); |
| |
| if (device_create_file(sd_detection_cmd_dev, |
| &dev_attr_max_mode) < 0) |
| pr_err("Fail to create max_mode sysfs file\n"); |
| |
| if (device_create_file(sd_detection_cmd_dev, |
| &dev_attr_current_mode) < 0) |
| pr_err("Fail to create current_mode sysfs file\n"); |
| |
| if (device_create_file(sd_detection_cmd_dev, |
| &dev_attr_sdcard_summary) < 0) |
| pr_err("Fail to create sdcard_summary sysfs file\n"); |
| } |
| |
| if (!sd_info_cmd_dev) { |
| sd_info_cmd_dev = sec_device_create(host, "sdinfo"); |
| if (IS_ERR(sd_info_cmd_dev)) |
| pr_err("Fail to create sysfs dev\n"); |
| |
| if (device_create_file(sd_info_cmd_dev, |
| &dev_attr_sd_count) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| |
| if (device_create_file(sd_info_cmd_dev, |
| &dev_attr_data) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| |
| if (device_create_file(sd_info_cmd_dev, |
| &dev_attr_fc) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| } |
| |
| if (!sd_data_cmd_dev) { |
| sd_data_cmd_dev = sec_device_create(host, "sddata"); |
| if (IS_ERR(sd_data_cmd_dev)) |
| pr_err("Fail to create sysfs dev\n"); |
| if (device_create_file(sd_data_cmd_dev, |
| &dev_attr_sd_data) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| } |
| } |
| |
| /* For eMMC(dwmmc0) Case */ |
| if (of_alias_get_id(host->dev->of_node, "mshc") == 0) { |
| if (!mmc_card_dev) { |
| mmc_card_dev = sec_device_create(host, "mmc"); |
| if (IS_ERR(mmc_card_dev)) |
| pr_err("Fail to create sysfs dev\n"); |
| |
| if (device_create_file(mmc_card_dev, |
| &dev_attr_mmc_data) < 0) |
| pr_err("%s : Failed to create device file(%s)!\n", |
| __func__, dev_attr_mmc_data.attr.name); |
| |
| if (device_create_file(mmc_card_dev, |
| &dev_attr_mmc_summary) < 0) |
| pr_err("%s : Failed to create device file(%s)!\n", |
| __func__, dev_attr_mmc_summary.attr.name); |
| |
| #ifdef CONFIG_SEC_FACTORY |
| if (device_create_file(mmc_card_dev, |
| &dev_attr_hwrst) < 0) |
| pr_err("Fail to create status sysfs file\n"); |
| #endif |
| } |
| } |
| } |
| |
| #if 0 |
| static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates) |
| { |
| const u8 iter = 8; |
| u8 __c; |
| s8 i, loc = -1; |
| |
| for (i = 0; i < iter; i++) { |
| __c = ror8(candiates, i); |
| if ((__c & 0xc7) == 0xc7) { |
| loc = i; |
| goto out; |
| } |
| } |
| |
| for (i = 0; i < iter; i++) { |
| __c = ror8(candiates, i); |
| if ((__c & 0x83) == 0x83) { |
| loc = i; |
| goto out; |
| } |
| } |
| |
| out: |
| return loc; |
| } |
| |
| static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode) |
| { |
| struct dw_mci *host = slot->host; |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| struct mmc_host *mmc = slot->mmc; |
| u8 start_smpl, smpl, candiates = 0; |
| s8 found = -1; |
| int ret = 0; |
| |
| start_smpl = dw_mci_exynos_get_clksmpl(host); |
| |
| do { |
| mci_writel(host, TMOUT, ~0); |
| smpl = dw_mci_exynos_move_next_clksmpl(host); |
| |
| if (!mmc_send_tuning(mmc, opcode, NULL)) |
| candiates |= (1 << smpl); |
| |
| } while (start_smpl != smpl); |
| |
| found = dw_mci_exynos_get_best_clksmpl(candiates); |
| if (found >= 0) { |
| dw_mci_exynos_set_clksmpl(host, found); |
| priv->tuned_sample = found; |
| } else { |
| ret = -EIO; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| #if 0 |
| static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host, |
| struct mmc_ios *ios) |
| { |
| struct dw_mci_exynos_priv_data *priv = host->priv; |
| |
| dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing); |
| dw_mci_exynos_adjust_clock(host, (ios->clock) << 1); |
| |
| return 0; |
| } |
| #endif |
| |
| /* Common capabilities of Exynos4/Exynos5 SoC */ |
| static unsigned long exynos_dwmmc_caps[4] = { |
| MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23, |
| MMC_CAP_CMD23, |
| MMC_CAP_CMD23, |
| MMC_CAP_CMD23, |
| }; |
| |
| static int dw_mci_exynos_misc_control(struct dw_mci *host, |
| enum dw_mci_misc_control control, void *priv) |
| { |
| int ret = 0; |
| |
| switch (control) { |
| case CTRL_RESTORE_CLKSEL: |
| dw_mci_exynos_set_sample(host, host->pdata->clk_smpl, false); |
| dw_mci_set_fine_tuning_bit(host, host->pdata->is_fine_tuned); |
| break; |
| case CTRL_REQUEST_EXT_IRQ: |
| ret = dw_mci_exynos_request_ext_irq(host, (irq_handler_t)priv); |
| break; |
| case CTRL_CHECK_CD: |
| ret = dw_mci_exynos_check_cd(host); |
| break; |
| case CTRL_ADD_SYSFS: |
| dw_mci_exynos_add_sysfs(host); |
| break; |
| default: |
| dev_err(host->dev, "dw_mmc exynos: wrong case\n"); |
| ret = -ENODEV; |
| } |
| return ret; |
| } |
| |
| static int dw_mci_exynos_crypto_engine_cfg(struct dw_mci *host, |
| void *desc, |
| struct mmc_data *data, |
| struct page *page, |
| int sector_offset, |
| bool cmdq_enabled) |
| { |
| return exynos_mmc_fmp_cfg(host, desc, data, page, sector_offset, cmdq_enabled); |
| } |
| |
| static int dw_mci_exynos_crypto_engine_clear(struct dw_mci *host, void *desc, |
| bool cmdq_enabled) |
| { |
| return exynos_mmc_fmp_clear(host, desc, cmdq_enabled); |
| } |
| |
| static int dw_mci_exynos_access_control_get_dev(struct dw_mci *host) |
| { |
| return exynos_mmc_smu_get_dev(host); |
| } |
| |
| static int dw_mci_exynos_access_control_sec_cfg(struct dw_mci *host) |
| { |
| return exynos_mmc_smu_sec_cfg(host); |
| } |
| |
| static int dw_mci_exynos_access_control_init(struct dw_mci *host) |
| { |
| return exynos_mmc_smu_init(host); |
| } |
| |
| static int dw_mci_exynos_access_control_abort(struct dw_mci *host) |
| { |
| return exynos_mmc_smu_abort(host); |
| } |
| |
| static int dw_mci_exynos_access_control_resume(struct dw_mci *host) |
| { |
| return exynos_mmc_smu_resume(host); |
| } |
| |
| static const struct dw_mci_drv_data exynos_drv_data = { |
| .caps = exynos_dwmmc_caps, |
| .init = dw_mci_exynos_priv_init, |
| .setup_clock = dw_mci_exynos_setup_clock, |
| .prepare_command = dw_mci_exynos_prepare_command, |
| .set_ios = dw_mci_exynos_set_ios, |
| .parse_dt = dw_mci_exynos_parse_dt, |
| .execute_tuning = dw_mci_exynos_execute_tuning, |
| #if 0 |
| .prepare_hs400_tuning = dw_mci_exynos_prepare_hs400_tuning, |
| #endif |
| .hwacg_control = dw_mci_card_int_hwacg_ctrl, |
| .misc_control = dw_mci_exynos_misc_control, |
| .crypto_engine_cfg = dw_mci_exynos_crypto_engine_cfg, |
| .crypto_engine_clear = dw_mci_exynos_crypto_engine_clear, |
| .access_control_get_dev = dw_mci_exynos_access_control_get_dev, |
| .access_control_sec_cfg = dw_mci_exynos_access_control_sec_cfg, |
| .access_control_init = dw_mci_exynos_access_control_init, |
| .access_control_abort = dw_mci_exynos_access_control_abort, |
| .access_control_resume = dw_mci_exynos_access_control_resume, |
| }; |
| |
| static const struct of_device_id dw_mci_exynos_match[] = { |
| { .compatible = "samsung,exynos-dw-mshc", |
| .data = &exynos_drv_data, }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, dw_mci_exynos_match); |
| |
| static int dw_mci_exynos_probe(struct platform_device *pdev) |
| { |
| const struct dw_mci_drv_data *drv_data; |
| const struct of_device_id *match; |
| |
| match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node); |
| drv_data = match->data; |
| return dw_mci_pltfm_register(pdev, drv_data); |
| } |
| |
| static const struct dev_pm_ops dw_mci_exynos_pmops = { |
| SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume) |
| .resume_noirq = dw_mci_exynos_resume_noirq, |
| .thaw_noirq = dw_mci_exynos_resume_noirq, |
| .restore_noirq = dw_mci_exynos_resume_noirq, |
| }; |
| |
| static struct platform_driver dw_mci_exynos_pltfm_driver = { |
| .probe = dw_mci_exynos_probe, |
| .remove = dw_mci_pltfm_remove, |
| .driver = { |
| .name = "dwmmc_exynos", |
| .of_match_table = dw_mci_exynos_match, |
| .pm = &dw_mci_exynos_pmops, |
| .suppress_bind_attrs = true, |
| }, |
| }; |
| |
| module_platform_driver(dw_mci_exynos_pltfm_driver); |
| |
| MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension"); |
| MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:dwmmc_exynos"); |