You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6218 lines
181 KiB
6218 lines
181 KiB
/*
|
|
* drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform
|
|
* driver source file
|
|
*
|
|
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/sdio_func.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/types.h>
|
|
#include <linux/input.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mmc/slot-gpio.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/msm-bus.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <trace/events/mmc.h>
|
|
|
|
#include "sdhci-msm.h"
|
|
#include "cmdq_hci.h"
|
|
#include "cmdq_hci-crypto-qti.h"
|
|
|
|
#ifdef CONFIG_DRV_SAMSUNG
|
|
#include <linux/sec_class.h>
|
|
#endif
|
|
|
|
#define QOS_REMOVE_DELAY_MS 10
|
|
#define CORE_POWER 0x0
|
|
#define CORE_SW_RST (1 << 7)
|
|
|
|
#define SDHCI_VER_100 0x2B
|
|
|
|
#define CORE_VERSION_STEP_MASK 0x0000FFFF
|
|
#define CORE_VERSION_MINOR_MASK 0x0FFF0000
|
|
#define CORE_VERSION_MINOR_SHIFT 16
|
|
#define CORE_VERSION_MAJOR_MASK 0xF0000000
|
|
#define CORE_VERSION_MAJOR_SHIFT 28
|
|
#define CORE_VERSION_TARGET_MASK 0x000000FF
|
|
#define SDHCI_MSM_VER_420 0x49
|
|
|
|
#define SWITCHABLE_SIGNALLING_VOL (1 << 29)
|
|
|
|
#define CORE_VERSION_MAJOR_MASK 0xF0000000
|
|
#define CORE_VERSION_MAJOR_SHIFT 28
|
|
|
|
#define CORE_HC_MODE 0x78
|
|
#define HC_MODE_EN 0x1
|
|
#define FF_CLK_SW_RST_DIS (1 << 13)
|
|
|
|
#define CORE_PWRCTL_BUS_OFF 0x01
|
|
#define CORE_PWRCTL_BUS_ON (1 << 1)
|
|
#define CORE_PWRCTL_IO_LOW (1 << 2)
|
|
#define CORE_PWRCTL_IO_HIGH (1 << 3)
|
|
|
|
#define CORE_PWRCTL_BUS_SUCCESS 0x01
|
|
#define CORE_PWRCTL_BUS_FAIL (1 << 1)
|
|
#define CORE_PWRCTL_IO_SUCCESS (1 << 2)
|
|
#define CORE_PWRCTL_IO_FAIL (1 << 3)
|
|
|
|
#define INT_MASK 0xF
|
|
#define MAX_PHASES 16
|
|
|
|
#define CORE_CMD_DAT_TRACK_SEL (1 << 0)
|
|
#define CORE_DLL_EN (1 << 16)
|
|
#define CORE_CDR_EN (1 << 17)
|
|
#define CORE_CK_OUT_EN (1 << 18)
|
|
#define CORE_CDR_EXT_EN (1 << 19)
|
|
#define CORE_DLL_PDN (1 << 29)
|
|
#define CORE_DLL_RST (1 << 30)
|
|
|
|
#define CORE_DLL_LOCK (1 << 7)
|
|
#define CORE_DDR_DLL_LOCK (1 << 11)
|
|
|
|
#define CORE_CLK_PWRSAVE (1 << 1)
|
|
#define CORE_VNDR_SPEC_ADMA_ERR_SIZE_EN (1 << 7)
|
|
#define CORE_HC_MCLK_SEL_DFLT (2 << 8)
|
|
#define CORE_HC_MCLK_SEL_HS400 (3 << 8)
|
|
#define CORE_HC_MCLK_SEL_MASK (3 << 8)
|
|
#define CORE_HC_AUTO_CMD21_EN (1 << 6)
|
|
#define CORE_IO_PAD_PWR_SWITCH_EN (1 << 15)
|
|
#define CORE_IO_PAD_PWR_SWITCH (1 << 16)
|
|
#define CORE_HC_SELECT_IN_EN (1 << 18)
|
|
#define CORE_HC_SELECT_IN_HS400 (6 << 19)
|
|
#define CORE_HC_SELECT_IN_MASK (7 << 19)
|
|
#define CORE_VENDOR_SPEC_POR_VAL 0xA1C
|
|
|
|
#define HC_SW_RST_WAIT_IDLE_DIS (1 << 20)
|
|
#define HC_SW_RST_REQ (1 << 21)
|
|
#define CORE_ONE_MID_EN (1 << 25)
|
|
|
|
#define CORE_8_BIT_SUPPORT (1 << 18)
|
|
#define CORE_3_3V_SUPPORT (1 << 24)
|
|
#define CORE_3_0V_SUPPORT (1 << 25)
|
|
#define CORE_1_8V_SUPPORT (1 << 26)
|
|
#define CORE_SYS_BUS_SUPPORT_64_BIT BIT(28)
|
|
|
|
#define CORE_CSR_CDC_CTLR_CFG0 0x130
|
|
#define CORE_SW_TRIG_FULL_CALIB (1 << 16)
|
|
#define CORE_HW_AUTOCAL_ENA (1 << 17)
|
|
|
|
#define CORE_CSR_CDC_CTLR_CFG1 0x134
|
|
#define CORE_CSR_CDC_CAL_TIMER_CFG0 0x138
|
|
#define CORE_TIMER_ENA (1 << 16)
|
|
|
|
#define CORE_CSR_CDC_CAL_TIMER_CFG1 0x13C
|
|
#define CORE_CSR_CDC_REFCOUNT_CFG 0x140
|
|
#define CORE_CSR_CDC_COARSE_CAL_CFG 0x144
|
|
#define CORE_CDC_OFFSET_CFG 0x14C
|
|
#define CORE_CSR_CDC_DELAY_CFG 0x150
|
|
#define CORE_CDC_SLAVE_DDA_CFG 0x160
|
|
#define CORE_CSR_CDC_STATUS0 0x164
|
|
#define CORE_CALIBRATION_DONE (1 << 0)
|
|
|
|
#define CORE_CDC_ERROR_CODE_MASK 0x7000000
|
|
|
|
#define CQ_CMD_DBG_RAM 0x110
|
|
#define CQ_CMD_DBG_RAM_WA 0x150
|
|
#define CQ_CMD_DBG_RAM_OL 0x154
|
|
|
|
#define CORE_CSR_CDC_GEN_CFG 0x178
|
|
#define CORE_CDC_SWITCH_BYPASS_OFF (1 << 0)
|
|
#define CORE_CDC_SWITCH_RC_EN (1 << 1)
|
|
|
|
#define CORE_CDC_T4_DLY_SEL (1 << 0)
|
|
#define CORE_CMDIN_RCLK_EN (1 << 1)
|
|
#define CORE_START_CDC_TRAFFIC (1 << 6)
|
|
|
|
#define CORE_PWRSAVE_DLL (1 << 3)
|
|
#define CORE_FIFO_ALT_EN (1 << 10)
|
|
#define CORE_CMDEN_HS400_INPUT_MASK_CNT (1 << 13)
|
|
|
|
#define CORE_DDR_CAL_EN (1 << 0)
|
|
#define CORE_FLL_CYCLE_CNT (1 << 18)
|
|
#define CORE_DLL_CLOCK_DISABLE (1 << 21)
|
|
|
|
#define DDR_CONFIG_POR_VAL 0x80040873
|
|
#define DLL_USR_CTL_POR_VAL 0x10800
|
|
#define ENABLE_DLL_LOCK_STATUS (1 << 26)
|
|
#define FINE_TUNE_MODE_EN (1 << 27)
|
|
#define BIAS_OK_SIGNAL (1 << 29)
|
|
#define DLL_CONFIG_3_POR_VAL 0x10
|
|
|
|
/* 512 descriptors */
|
|
#define SDHCI_MSM_MAX_SEGMENTS (1 << 9)
|
|
#define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */
|
|
|
|
#define CORE_FREQ_100MHZ (100 * 1000 * 1000)
|
|
#define TCXO_FREQ 19200000
|
|
|
|
#define INVALID_TUNING_PHASE -1
|
|
#define sdhci_is_valid_gpio_wakeup_int(_h) ((_h)->pdata->sdiowakeup_irq >= 0)
|
|
|
|
#define NUM_TUNING_PHASES 16
|
|
#define MAX_DRV_TYPES_SUPPORTED_HS200 4
|
|
#define MSM_AUTOSUSPEND_DELAY_MS 100
|
|
|
|
#define RCLK_TOGGLE 0x2
|
|
|
|
struct sdhci_msm_offset {
|
|
u32 CORE_MCI_DATA_CNT;
|
|
u32 CORE_MCI_STATUS;
|
|
u32 CORE_MCI_FIFO_CNT;
|
|
u32 CORE_MCI_VERSION;
|
|
u32 CORE_GENERICS;
|
|
u32 CORE_TESTBUS_CONFIG;
|
|
u32 CORE_TESTBUS_SEL2_BIT;
|
|
u32 CORE_TESTBUS_ENA;
|
|
u32 CORE_TESTBUS_SEL2;
|
|
u32 CORE_PWRCTL_STATUS;
|
|
u32 CORE_PWRCTL_MASK;
|
|
u32 CORE_PWRCTL_CLEAR;
|
|
u32 CORE_PWRCTL_CTL;
|
|
u32 CORE_SDCC_DEBUG_REG;
|
|
u32 CORE_DLL_CONFIG;
|
|
u32 CORE_DLL_STATUS;
|
|
u32 CORE_VENDOR_SPEC;
|
|
u32 CORE_VENDOR_SPEC_ADMA_ERR_ADDR0;
|
|
u32 CORE_VENDOR_SPEC_ADMA_ERR_ADDR1;
|
|
u32 CORE_VENDOR_SPEC_FUNC2;
|
|
u32 CORE_VENDOR_SPEC_CAPABILITIES0;
|
|
u32 CORE_DDR_200_CFG;
|
|
u32 CORE_VENDOR_SPEC3;
|
|
u32 CORE_DLL_CONFIG_2;
|
|
u32 CORE_DLL_CONFIG_3;
|
|
u32 CORE_DDR_CONFIG;
|
|
u32 CORE_DDR_CONFIG_OLD; /* Applcable to sddcc minor ver < 0x49 only */
|
|
u32 CORE_DLL_USR_CTL; /* Present on SDCC5.1 onwards */
|
|
};
|
|
|
|
struct sdhci_msm_offset sdhci_msm_offset_mci_removed = {
|
|
.CORE_MCI_DATA_CNT = 0x35C,
|
|
.CORE_MCI_STATUS = 0x324,
|
|
.CORE_MCI_FIFO_CNT = 0x308,
|
|
.CORE_MCI_VERSION = 0x318,
|
|
.CORE_GENERICS = 0x320,
|
|
.CORE_TESTBUS_CONFIG = 0x32C,
|
|
.CORE_TESTBUS_SEL2_BIT = 3,
|
|
.CORE_TESTBUS_ENA = (1 << 31),
|
|
.CORE_TESTBUS_SEL2 = (1 << 3),
|
|
.CORE_PWRCTL_STATUS = 0x240,
|
|
.CORE_PWRCTL_MASK = 0x244,
|
|
.CORE_PWRCTL_CLEAR = 0x248,
|
|
.CORE_PWRCTL_CTL = 0x24C,
|
|
.CORE_SDCC_DEBUG_REG = 0x358,
|
|
.CORE_DLL_CONFIG = 0x200,
|
|
.CORE_DLL_STATUS = 0x208,
|
|
.CORE_VENDOR_SPEC = 0x20C,
|
|
.CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 = 0x214,
|
|
.CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 = 0x218,
|
|
.CORE_VENDOR_SPEC_FUNC2 = 0x210,
|
|
.CORE_VENDOR_SPEC_CAPABILITIES0 = 0x21C,
|
|
.CORE_DDR_200_CFG = 0x224,
|
|
.CORE_VENDOR_SPEC3 = 0x250,
|
|
.CORE_DLL_CONFIG_2 = 0x254,
|
|
.CORE_DLL_CONFIG_3 = 0x258,
|
|
.CORE_DDR_CONFIG = 0x25C,
|
|
.CORE_DLL_USR_CTL = 0x388,
|
|
};
|
|
|
|
struct sdhci_msm_offset sdhci_msm_offset_mci_present = {
|
|
.CORE_MCI_DATA_CNT = 0x30,
|
|
.CORE_MCI_STATUS = 0x34,
|
|
.CORE_MCI_FIFO_CNT = 0x44,
|
|
.CORE_MCI_VERSION = 0x050,
|
|
.CORE_GENERICS = 0x70,
|
|
.CORE_TESTBUS_CONFIG = 0x0CC,
|
|
.CORE_TESTBUS_SEL2_BIT = 4,
|
|
.CORE_TESTBUS_ENA = (1 << 3),
|
|
.CORE_TESTBUS_SEL2 = (1 << 4),
|
|
.CORE_PWRCTL_STATUS = 0xDC,
|
|
.CORE_PWRCTL_MASK = 0xE0,
|
|
.CORE_PWRCTL_CLEAR = 0xE4,
|
|
.CORE_PWRCTL_CTL = 0xE8,
|
|
.CORE_SDCC_DEBUG_REG = 0x124,
|
|
.CORE_DLL_CONFIG = 0x100,
|
|
.CORE_DLL_STATUS = 0x108,
|
|
.CORE_VENDOR_SPEC = 0x10C,
|
|
.CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 = 0x114,
|
|
.CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 = 0x118,
|
|
.CORE_VENDOR_SPEC_FUNC2 = 0x110,
|
|
.CORE_VENDOR_SPEC_CAPABILITIES0 = 0x11C,
|
|
.CORE_DDR_200_CFG = 0x184,
|
|
.CORE_VENDOR_SPEC3 = 0x1B0,
|
|
.CORE_DLL_CONFIG_2 = 0x1B4,
|
|
.CORE_DLL_CONFIG_3 = 0x1B8,
|
|
.CORE_DDR_CONFIG_OLD = 0x1B8, /* Applicable to sdcc minor ver < 0x49 */
|
|
.CORE_DDR_CONFIG = 0x1BC,
|
|
};
|
|
|
|
u8 sdhci_msm_readb_relaxed(struct sdhci_host *host, u32 offset)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
void __iomem *base_addr;
|
|
|
|
if (msm_host->mci_removed)
|
|
base_addr = host->ioaddr;
|
|
else
|
|
base_addr = msm_host->core_mem;
|
|
|
|
return readb_relaxed(base_addr + offset);
|
|
}
|
|
|
|
u32 sdhci_msm_readl_relaxed(struct sdhci_host *host, u32 offset)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
void __iomem *base_addr;
|
|
|
|
if (msm_host->mci_removed)
|
|
base_addr = host->ioaddr;
|
|
else
|
|
base_addr = msm_host->core_mem;
|
|
|
|
return readl_relaxed(base_addr + offset);
|
|
}
|
|
|
|
void sdhci_msm_writeb_relaxed(u8 val, struct sdhci_host *host, u32 offset)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
void __iomem *base_addr;
|
|
|
|
if (msm_host->mci_removed)
|
|
base_addr = host->ioaddr;
|
|
else
|
|
base_addr = msm_host->core_mem;
|
|
|
|
writeb_relaxed(val, base_addr + offset);
|
|
}
|
|
|
|
void sdhci_msm_writel_relaxed(u32 val, struct sdhci_host *host, u32 offset)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
void __iomem *base_addr;
|
|
|
|
if (msm_host->mci_removed)
|
|
base_addr = host->ioaddr;
|
|
else
|
|
base_addr = msm_host->core_mem;
|
|
|
|
writel_relaxed(val, base_addr + offset);
|
|
}
|
|
|
|
/* Timeout value to avoid infinite waiting for pwr_irq */
|
|
#define MSM_PWR_IRQ_TIMEOUT_MS 5000
|
|
|
|
static const u32 tuning_block_64[] = {
|
|
0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE,
|
|
0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777,
|
|
0xF0FFF0FF, 0x3CCCFC0F, 0xCFCC33CC, 0xEEFFEFFF,
|
|
0xFDFFFDFF, 0xFFBFFFDF, 0xFFF7FFBB, 0xDE7B7FF7
|
|
};
|
|
|
|
static const u32 tuning_block_128[] = {
|
|
0xFF00FFFF, 0x0000FFFF, 0xCCCCFFFF, 0xCCCC33CC,
|
|
0xCC3333CC, 0xFFFFCCCC, 0xFFFFEEFF, 0xFFEEEEFF,
|
|
0xFFDDFFFF, 0xDDDDFFFF, 0xBBFFFFFF, 0xBBFFFFFF,
|
|
0xFFFFFFBB, 0xFFFFFF77, 0x77FF7777, 0xFFEEDDBB,
|
|
0x00FFFFFF, 0x00FFFFFF, 0xCCFFFF00, 0xCC33CCCC,
|
|
0x3333CCCC, 0xFFCCCCCC, 0xFFEEFFFF, 0xEEEEFFFF,
|
|
0xDDFFFFFF, 0xDDFFFFFF, 0xFFFFFFDD, 0xFFFFFFBB,
|
|
0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77
|
|
};
|
|
|
|
/* global to hold each slot instance for debug */
|
|
static struct sdhci_msm_host *sdhci_slot[2];
|
|
|
|
static int disable_slots;
|
|
/* root can write, others read */
|
|
module_param(disable_slots, int, 0644);
|
|
|
|
static bool nocmdq;
|
|
module_param(nocmdq, bool, 0644);
|
|
|
|
enum vdd_io_level {
|
|
/* set vdd_io_data->low_vol_level */
|
|
VDD_IO_LOW,
|
|
/* set vdd_io_data->high_vol_level */
|
|
VDD_IO_HIGH,
|
|
/*
|
|
* set whatever there in voltage_level (third argument) of
|
|
* sdhci_msm_set_vdd_io_vol() function.
|
|
*/
|
|
VDD_IO_SET_LEVEL,
|
|
};
|
|
|
|
enum dll_init_context {
|
|
DLL_INIT_NORMAL = 0,
|
|
DLL_INIT_FROM_CX_COLLAPSE_EXIT,
|
|
};
|
|
|
|
static unsigned int sdhci_msm_get_sup_clk_rate(struct sdhci_host *host,
|
|
u32 req_clk);
|
|
|
|
/* MSM platform specific tuning */
|
|
static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host,
|
|
u8 poll)
|
|
{
|
|
int rc = 0;
|
|
u32 wait_cnt = 50;
|
|
u8 ck_out_en = 0;
|
|
struct mmc_host *mmc = host->mmc;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
/* poll for CK_OUT_EN bit. max. poll time = 50us */
|
|
ck_out_en = !!(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) & CORE_CK_OUT_EN);
|
|
|
|
while (ck_out_en != poll) {
|
|
if (--wait_cnt == 0) {
|
|
pr_err("%s: %s: CK_OUT_EN bit is not %d\n",
|
|
mmc_hostname(mmc), __func__, poll);
|
|
rc = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
udelay(1);
|
|
|
|
ck_out_en = !!(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) & CORE_CK_OUT_EN);
|
|
}
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Enable CDR to track changes of DAT lines and adjust sampling
|
|
* point according to voltage/temperature variations
|
|
*/
|
|
static int msm_enable_cdr_cm_sdc4_dll(struct sdhci_host *host)
|
|
{
|
|
int rc = 0;
|
|
u32 config;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
config |= CORE_CDR_EN;
|
|
config &= ~(CORE_CDR_EXT_EN | CORE_CK_OUT_EN);
|
|
writel_relaxed(config, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
rc = msm_dll_poll_ck_out_en(host, 0);
|
|
if (rc)
|
|
goto err;
|
|
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) | CORE_CK_OUT_EN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
rc = msm_dll_poll_ck_out_en(host, 1);
|
|
if (rc)
|
|
goto err;
|
|
goto out;
|
|
err:
|
|
pr_err("%s: %s: failed\n", mmc_hostname(host->mmc), __func__);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t store_auto_cmd21(struct device *dev, struct device_attribute
|
|
*attr, const char *buf, size_t count)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
u32 tmp;
|
|
unsigned long flags;
|
|
|
|
if (!kstrtou32(buf, 0, &tmp)) {
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
msm_host->en_auto_cmd21 = !!tmp;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t show_auto_cmd21(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", msm_host->en_auto_cmd21);
|
|
}
|
|
|
|
/* MSM auto-tuning handler */
|
|
static int sdhci_msm_config_auto_tuning_cmd(struct sdhci_host *host,
|
|
bool enable,
|
|
u32 type)
|
|
{
|
|
int rc = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u32 val = 0;
|
|
|
|
if (!msm_host->en_auto_cmd21)
|
|
return 0;
|
|
|
|
if (type == MMC_SEND_TUNING_BLOCK_HS200)
|
|
val = CORE_HC_AUTO_CMD21_EN;
|
|
else
|
|
return 0;
|
|
|
|
if (enable) {
|
|
rc = msm_enable_cdr_cm_sdc4_dll(host);
|
|
writel_relaxed(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) | val,
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC);
|
|
} else {
|
|
writel_relaxed(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) & ~val,
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
|
|
{
|
|
int rc = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u8 grey_coded_phase_table[] = {0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
|
|
0xC, 0xD, 0xF, 0xE, 0xA, 0xB, 0x9,
|
|
0x8};
|
|
unsigned long flags;
|
|
u32 config;
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
|
|
config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN);
|
|
config |= (CORE_CDR_EXT_EN | CORE_DLL_EN);
|
|
writel_relaxed(config, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */
|
|
rc = msm_dll_poll_ck_out_en(host, 0);
|
|
if (rc)
|
|
goto err_out;
|
|
|
|
/*
|
|
* Write the selected DLL clock output phase (0 ... 15)
|
|
* to CDR_SELEXT bit field of DLL_CONFIG register.
|
|
*/
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
& ~(0xF << 20))
|
|
| (grey_coded_phase_table[phase] << 20)),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) | CORE_CK_OUT_EN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */
|
|
rc = msm_dll_poll_ck_out_en(host, 1);
|
|
if (rc)
|
|
goto err_out;
|
|
|
|
config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
config |= CORE_CDR_EN;
|
|
config &= ~CORE_CDR_EXT_EN;
|
|
writel_relaxed(config, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
goto out;
|
|
|
|
err_out:
|
|
pr_err("%s: %s: Failed to set DLL phase: %d\n",
|
|
mmc_hostname(mmc), __func__, phase);
|
|
out:
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Find out the greatest range of consecuitive selected
|
|
* DLL clock output phases that can be used as sampling
|
|
* setting for SD3.0 UHS-I card read operation (in SDR104
|
|
* timing mode) or for eMMC4.5 card read operation (in
|
|
* HS400/HS200 timing mode).
|
|
* Select the 3/4 of the range and configure the DLL with the
|
|
* selected DLL clock output phase.
|
|
*/
|
|
|
|
static int msm_find_most_appropriate_phase(struct sdhci_host *host,
|
|
u8 *phase_table, u8 total_phases)
|
|
{
|
|
int ret;
|
|
u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} };
|
|
u8 phases_per_row[MAX_PHASES] = {0};
|
|
int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0;
|
|
int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0;
|
|
bool phase_0_found = false, phase_15_found = false;
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
|
|
if (!total_phases || (total_phases > MAX_PHASES)) {
|
|
pr_err("%s: %s: invalid argument: total_phases=%d\n",
|
|
mmc_hostname(mmc), __func__, total_phases);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (cnt = 0; cnt < total_phases; cnt++) {
|
|
ranges[row_index][col_index] = phase_table[cnt];
|
|
phases_per_row[row_index] += 1;
|
|
col_index++;
|
|
|
|
if ((cnt + 1) == total_phases) {
|
|
continue;
|
|
/* check if next phase in phase_table is consecutive or not */
|
|
} else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) {
|
|
row_index++;
|
|
col_index = 0;
|
|
}
|
|
}
|
|
|
|
if (row_index >= MAX_PHASES)
|
|
return -EINVAL;
|
|
|
|
/* Check if phase-0 is present in first valid window? */
|
|
if (!ranges[0][0]) {
|
|
phase_0_found = true;
|
|
phase_0_raw_index = 0;
|
|
/* Check if cycle exist between 2 valid windows */
|
|
for (cnt = 1; cnt <= row_index; cnt++) {
|
|
if (phases_per_row[cnt]) {
|
|
for (i = 0; i < phases_per_row[cnt]; i++) {
|
|
if (ranges[cnt][i] == 15) {
|
|
phase_15_found = true;
|
|
phase_15_raw_index = cnt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If 2 valid windows form cycle then merge them as single window */
|
|
if (phase_0_found && phase_15_found) {
|
|
/* number of phases in raw where phase 0 is present */
|
|
u8 phases_0 = phases_per_row[phase_0_raw_index];
|
|
/* number of phases in raw where phase 15 is present */
|
|
u8 phases_15 = phases_per_row[phase_15_raw_index];
|
|
|
|
if (phases_0 + phases_15 >= MAX_PHASES)
|
|
/*
|
|
* If there are more than 1 phase windows then total
|
|
* number of phases in both the windows should not be
|
|
* more than or equal to MAX_PHASES.
|
|
*/
|
|
return -EINVAL;
|
|
|
|
/* Merge 2 cyclic windows */
|
|
i = phases_15;
|
|
for (cnt = 0; cnt < phases_0; cnt++) {
|
|
ranges[phase_15_raw_index][i] =
|
|
ranges[phase_0_raw_index][cnt];
|
|
if (++i >= MAX_PHASES)
|
|
break;
|
|
}
|
|
|
|
phases_per_row[phase_0_raw_index] = 0;
|
|
phases_per_row[phase_15_raw_index] = phases_15 + phases_0;
|
|
}
|
|
|
|
for (cnt = 0; cnt <= row_index; cnt++) {
|
|
if (phases_per_row[cnt] > curr_max) {
|
|
curr_max = phases_per_row[cnt];
|
|
selected_row_index = cnt;
|
|
}
|
|
}
|
|
|
|
i = ((curr_max * 3) / 4);
|
|
if (i)
|
|
i--;
|
|
|
|
ret = (int)ranges[selected_row_index][i];
|
|
|
|
if (ret >= MAX_PHASES) {
|
|
ret = -EINVAL;
|
|
pr_err("%s: %s: invalid phase selected=%d\n",
|
|
mmc_hostname(mmc), __func__, ret);
|
|
}
|
|
|
|
pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
|
|
return ret;
|
|
}
|
|
|
|
static inline void msm_cm_dll_set_freq(struct sdhci_host *host)
|
|
{
|
|
u32 mclk_freq = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
/* Program the MCLK value to MCLK_FREQ bit field */
|
|
if (host->clock <= 112000000)
|
|
mclk_freq = 0;
|
|
else if (host->clock <= 125000000)
|
|
mclk_freq = 1;
|
|
else if (host->clock <= 137000000)
|
|
mclk_freq = 2;
|
|
else if (host->clock <= 150000000)
|
|
mclk_freq = 3;
|
|
else if (host->clock <= 162000000)
|
|
mclk_freq = 4;
|
|
else if (host->clock <= 175000000)
|
|
mclk_freq = 5;
|
|
else if (host->clock <= 187000000)
|
|
mclk_freq = 6;
|
|
else if (host->clock <= 208000000)
|
|
mclk_freq = 7;
|
|
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
& ~(7 << 24)) | (mclk_freq << 24)),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
}
|
|
|
|
/* Initialize the DLL (Programmable Delay Line ) */
|
|
static int msm_init_cm_dll(struct sdhci_host *host,
|
|
enum dll_init_context init_context)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
struct mmc_host *mmc = host->mmc;
|
|
int rc = 0;
|
|
unsigned long flags;
|
|
u32 wait_cnt;
|
|
bool prev_pwrsave, curr_pwrsave;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
prev_pwrsave = !!(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) & CORE_CLK_PWRSAVE);
|
|
curr_pwrsave = prev_pwrsave;
|
|
/*
|
|
* Make sure that clock is always enabled when DLL
|
|
* tuning is in progress. Keeping PWRSAVE ON may
|
|
* turn off the clock. So let's disable the PWRSAVE
|
|
* here and re-enable it once tuning is completed.
|
|
*/
|
|
if (prev_pwrsave) {
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
& ~CORE_CLK_PWRSAVE), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
curr_pwrsave = false;
|
|
}
|
|
|
|
if (msm_host->use_updated_dll_reset) {
|
|
/* Disable the DLL clock */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
& ~CORE_CK_OUT_EN), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2)
|
|
| CORE_DLL_CLOCK_DISABLE), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2);
|
|
}
|
|
|
|
/* Write 1 to DLL_RST bit of DLL_CONFIG register */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) | CORE_DLL_RST),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Write 1 to DLL_PDN bit of DLL_CONFIG register */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) | CORE_DLL_PDN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
if (msm_host->use_updated_dll_reset) {
|
|
u32 mclk_freq = 0;
|
|
u32 actual_clk = sdhci_msm_get_sup_clk_rate(host, host->clock);
|
|
|
|
/*
|
|
* Only configure the mclk_freq in normal DLL init
|
|
* context. If the DLL init is coming from
|
|
* CX Collapse Exit context, the host->clock may be zero.
|
|
* The DLL_CONFIG_2 register has already been restored to
|
|
* proper value prior to getting here.
|
|
*/
|
|
if (init_context == DLL_INIT_NORMAL) {
|
|
switch (actual_clk) {
|
|
case 208000000:
|
|
case 202000000:
|
|
case 201500000:
|
|
case 200000000:
|
|
mclk_freq = 42;
|
|
break;
|
|
case 192000000:
|
|
mclk_freq = 40;
|
|
break;
|
|
default:
|
|
mclk_freq = (u32)((actual_clk / TCXO_FREQ) * 4);
|
|
pr_info_once("%s: %s: Non standard clk freq =%u\n",
|
|
mmc_hostname(mmc), __func__, actual_clk);
|
|
}
|
|
|
|
if ((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2)
|
|
& CORE_FLL_CYCLE_CNT))
|
|
mclk_freq *= 2;
|
|
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2)
|
|
& ~(0xFF << 10)) | (mclk_freq << 10)),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG_2);
|
|
}
|
|
/* wait for 5us before enabling DLL clock */
|
|
udelay(5);
|
|
}
|
|
|
|
/* Write 0 to DLL_RST bit of DLL_CONFIG register */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) & ~CORE_DLL_RST),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Write 0 to DLL_PDN bit of DLL_CONFIG register */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) & ~CORE_DLL_PDN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
if (msm_host->use_updated_dll_reset) {
|
|
/* Enable the DLL clock */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2)
|
|
& ~CORE_DLL_CLOCK_DISABLE), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2);
|
|
}
|
|
|
|
/* Configure Tassadar DLL (Only applicable for 7FF projects) */
|
|
if (msm_host->use_7nm_dll) {
|
|
if (msm_host->dll_hsr) {
|
|
writel_relaxed(msm_host->dll_hsr->dll_usr_ctl,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_DLL_USR_CTL);
|
|
writel_relaxed(msm_host->dll_hsr->dll_config_3,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3);
|
|
} else {
|
|
writel_relaxed(DLL_USR_CTL_POR_VAL | FINE_TUNE_MODE_EN |
|
|
ENABLE_DLL_LOCK_STATUS | BIAS_OK_SIGNAL,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_DLL_USR_CTL);
|
|
|
|
writel_relaxed(DLL_CONFIG_3_POR_VAL, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update the lower two bytes of DLL_CONFIG only with HSR values.
|
|
* Since these are the static settings.
|
|
*/
|
|
if (msm_host->dll_hsr) {
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) |
|
|
(msm_host->dll_hsr->dll_config & 0xffff)),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
}
|
|
|
|
/* Set DLL_EN bit to 1. */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG) | CORE_DLL_EN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Set CK_OUT_EN bit to 1. */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
| CORE_CK_OUT_EN), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
wait_cnt = 50;
|
|
/* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */
|
|
while (!(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_STATUS) & CORE_DLL_LOCK)) {
|
|
/* max. wait for 50us sec for LOCK bit to be set */
|
|
if (--wait_cnt == 0) {
|
|
pr_err("%s: %s: DLL failed to LOCK\n",
|
|
mmc_hostname(mmc), __func__);
|
|
rc = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
/* wait for 1us before polling again */
|
|
udelay(1);
|
|
}
|
|
|
|
out:
|
|
/* Restore the correct PWRSAVE state */
|
|
if (prev_pwrsave ^ curr_pwrsave) {
|
|
u32 reg = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
|
|
if (prev_pwrsave)
|
|
reg |= CORE_CLK_PWRSAVE;
|
|
else
|
|
reg &= ~CORE_CLK_PWRSAVE;
|
|
|
|
writel_relaxed(reg, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
|
|
return rc;
|
|
}
|
|
|
|
static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host)
|
|
{
|
|
u32 calib_done;
|
|
int ret = 0;
|
|
int cdc_err = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__);
|
|
|
|
/* Write 0 to CDC_T4_DLY_SEL field in VENDOR_SPEC_DDR200_CFG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DDR_200_CFG)
|
|
& ~CORE_CDC_T4_DLY_SEL),
|
|
host->ioaddr + msm_host_offset->CORE_DDR_200_CFG);
|
|
|
|
/* Write 0 to CDC_SWITCH_BYPASS_OFF field in CORE_CSR_CDC_GEN_CFG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG)
|
|
& ~CORE_CDC_SWITCH_BYPASS_OFF),
|
|
host->ioaddr + CORE_CSR_CDC_GEN_CFG);
|
|
|
|
/* Write 1 to CDC_SWITCH_RC_EN field in CORE_CSR_CDC_GEN_CFG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG)
|
|
| CORE_CDC_SWITCH_RC_EN),
|
|
host->ioaddr + CORE_CSR_CDC_GEN_CFG);
|
|
|
|
/* Write 0 to START_CDC_TRAFFIC field in CORE_DDR200_CFG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DDR_200_CFG)
|
|
& ~CORE_START_CDC_TRAFFIC),
|
|
host->ioaddr + msm_host_offset->CORE_DDR_200_CFG);
|
|
|
|
/*
|
|
* Perform CDC Register Initialization Sequence
|
|
*
|
|
* CORE_CSR_CDC_CTLR_CFG0 0x11800EC
|
|
* CORE_CSR_CDC_CTLR_CFG1 0x3011111
|
|
* CORE_CSR_CDC_CAL_TIMER_CFG0 0x1201000
|
|
* CORE_CSR_CDC_CAL_TIMER_CFG1 0x4
|
|
* CORE_CSR_CDC_REFCOUNT_CFG 0xCB732020
|
|
* CORE_CSR_CDC_COARSE_CAL_CFG 0xB19
|
|
* CORE_CSR_CDC_DELAY_CFG 0x3AC
|
|
* CORE_CDC_OFFSET_CFG 0x0
|
|
* CORE_CDC_SLAVE_DDA_CFG 0x16334
|
|
*/
|
|
|
|
writel_relaxed(0x11800EC, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
|
|
writel_relaxed(0x3011111, host->ioaddr + CORE_CSR_CDC_CTLR_CFG1);
|
|
writel_relaxed(0x1201000, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0);
|
|
writel_relaxed(0x4, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG1);
|
|
writel_relaxed(0xCB732020, host->ioaddr + CORE_CSR_CDC_REFCOUNT_CFG);
|
|
writel_relaxed(0xB19, host->ioaddr + CORE_CSR_CDC_COARSE_CAL_CFG);
|
|
writel_relaxed(0x4E2, host->ioaddr + CORE_CSR_CDC_DELAY_CFG);
|
|
writel_relaxed(0x0, host->ioaddr + CORE_CDC_OFFSET_CFG);
|
|
writel_relaxed(0x16334, host->ioaddr + CORE_CDC_SLAVE_DDA_CFG);
|
|
|
|
/* CDC HW Calibration */
|
|
|
|
/* Write 1 to SW_TRIG_FULL_CALIB field in CORE_CSR_CDC_CTLR_CFG0 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0)
|
|
| CORE_SW_TRIG_FULL_CALIB),
|
|
host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
|
|
|
|
/* Write 0 to SW_TRIG_FULL_CALIB field in CORE_CSR_CDC_CTLR_CFG0 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0)
|
|
& ~CORE_SW_TRIG_FULL_CALIB),
|
|
host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
|
|
|
|
/* Write 1 to HW_AUTOCAL_ENA field in CORE_CSR_CDC_CTLR_CFG0 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0)
|
|
| CORE_HW_AUTOCAL_ENA),
|
|
host->ioaddr + CORE_CSR_CDC_CTLR_CFG0);
|
|
|
|
/* Write 1 to TIMER_ENA field in CORE_CSR_CDC_CAL_TIMER_CFG0 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
CORE_CSR_CDC_CAL_TIMER_CFG0) | CORE_TIMER_ENA),
|
|
host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0);
|
|
|
|
mb();
|
|
|
|
/* Poll on CALIBRATION_DONE field in CORE_CSR_CDC_STATUS0 to be 1 */
|
|
ret = readl_poll_timeout(host->ioaddr + CORE_CSR_CDC_STATUS0,
|
|
calib_done, (calib_done & CORE_CALIBRATION_DONE), 1, 50);
|
|
|
|
if (ret == -ETIMEDOUT) {
|
|
pr_err("%s: %s: CDC Calibration was not completed\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* Verify CDC_ERROR_CODE field in CORE_CSR_CDC_STATUS0 is 0 */
|
|
cdc_err = readl_relaxed(host->ioaddr + CORE_CSR_CDC_STATUS0)
|
|
& CORE_CDC_ERROR_CODE_MASK;
|
|
if (cdc_err) {
|
|
pr_err("%s: %s: CDC Error Code %d\n",
|
|
mmc_hostname(host->mmc), __func__, cdc_err);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Write 1 to START_CDC_TRAFFIC field in CORE_DDR200_CFG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DDR_200_CFG)
|
|
| CORE_START_CDC_TRAFFIC),
|
|
host->ioaddr + msm_host_offset->CORE_DDR_200_CFG);
|
|
out:
|
|
pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc),
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u32 dll_status;
|
|
int ret = 0;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__);
|
|
|
|
/*
|
|
* Reprogramming the value in case it might have been modified by
|
|
* bootloaders.
|
|
*/
|
|
if (msm_host->dll_hsr && msm_host->dll_hsr->ddr_config) {
|
|
writel_relaxed(msm_host->dll_hsr->ddr_config, host->ioaddr +
|
|
msm_host_offset->CORE_DDR_CONFIG);
|
|
} else if (msm_host->rclk_delay_fix) {
|
|
writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr +
|
|
msm_host_offset->CORE_DDR_CONFIG);
|
|
} else {
|
|
writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr +
|
|
msm_host_offset->CORE_DDR_CONFIG_OLD);
|
|
}
|
|
|
|
if (msm_host->enhanced_strobe && mmc_card_strobe(msm_host->mmc->card))
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DDR_200_CFG)
|
|
| CORE_CMDIN_RCLK_EN), host->ioaddr +
|
|
msm_host_offset->CORE_DDR_200_CFG);
|
|
|
|
/* Write 1 to DDR_CAL_EN field in CORE_DLL_CONFIG_2 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2)
|
|
| CORE_DDR_CAL_EN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG_2);
|
|
|
|
/* Poll on DDR_DLL_LOCK bit in CORE_DLL_STATUS to be set */
|
|
ret = readl_poll_timeout(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_STATUS,
|
|
dll_status, (dll_status & CORE_DDR_DLL_LOCK), 10, 1000);
|
|
|
|
if (ret == -ETIMEDOUT) {
|
|
pr_err("%s: %s: CM_DLL_SDC4 Calibration was not completed\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3.
|
|
* when MCLK is gated OFF, it is not gated for less than 0.5us
|
|
* and MCLK must be switched on for at-least 1us before DATA
|
|
* starts coming. Controllers with 14lpp tech DLL cannot
|
|
* guarantee above requirement. So PWRSAVE_DLL should not be
|
|
* turned on for host controllers using this DLL.
|
|
*/
|
|
if (!msm_host->use_14lpp_dll)
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3)
|
|
| CORE_PWRSAVE_DLL), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3);
|
|
mb();
|
|
out:
|
|
pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc),
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_enhanced_strobe(struct sdhci_host *host)
|
|
{
|
|
int ret = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__);
|
|
|
|
if (!msm_host->enhanced_strobe || !mmc_card_strobe(mmc->card)) {
|
|
pr_debug("%s: host/card does not support hs400 enhanced strobe\n",
|
|
mmc_hostname(mmc));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (msm_host->calibration_done ||
|
|
!(mmc->ios.timing == MMC_TIMING_MMC_HS400)) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reset the tuning block.
|
|
*/
|
|
ret = msm_init_cm_dll(host, DLL_INIT_NORMAL);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = sdhci_msm_cm_dll_sdc4_calibration(host);
|
|
out:
|
|
if (!ret)
|
|
msm_host->calibration_done = true;
|
|
pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc),
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host)
|
|
{
|
|
int ret = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__);
|
|
|
|
/*
|
|
* Retuning in HS400 (DDR mode) will fail, just reset the
|
|
* tuning block and restore the saved tuning phase.
|
|
*/
|
|
ret = msm_init_cm_dll(host, DLL_INIT_NORMAL);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* Set the selected phase in delay line hw block */
|
|
ret = msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* Write 1 to CMD_DAT_TRACK_SEL field in DLL_CONFIG */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
| CORE_CMD_DAT_TRACK_SEL), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
if (msm_host->use_cdclp533)
|
|
/* Calibrate CDCLP533 DLL HW */
|
|
ret = sdhci_msm_cdclp533_calibration(host);
|
|
else
|
|
/* Calibrate CM_DLL_SDC4 HW */
|
|
ret = sdhci_msm_cm_dll_sdc4_calibration(host);
|
|
out:
|
|
pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc),
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void sdhci_msm_set_mmc_drv_type(struct sdhci_host *host, u32 opcode,
|
|
u8 drv_type)
|
|
{
|
|
struct mmc_command cmd = {0};
|
|
struct mmc_request mrq = {NULL};
|
|
struct mmc_host *mmc = host->mmc;
|
|
u8 val = ((drv_type << 4) | 2);
|
|
|
|
cmd.opcode = MMC_SWITCH;
|
|
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
|
(EXT_CSD_HS_TIMING << 16) |
|
|
(val << 8) |
|
|
EXT_CSD_CMD_SET_NORMAL;
|
|
cmd.flags = MMC_CMD_AC | MMC_RSP_R1B;
|
|
/* 1 sec */
|
|
cmd.busy_timeout = 1000 * 1000;
|
|
|
|
memset(cmd.resp, 0, sizeof(cmd.resp));
|
|
cmd.retries = 3;
|
|
|
|
mrq.cmd = &cmd;
|
|
cmd.data = NULL;
|
|
|
|
mmc_wait_for_req(mmc, &mrq);
|
|
pr_debug("%s: %s: set card drive type to %d\n",
|
|
mmc_hostname(mmc), __func__,
|
|
drv_type);
|
|
}
|
|
|
|
int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|
{
|
|
unsigned long flags;
|
|
int tuning_seq_cnt = 3;
|
|
u8 phase, *data_buf, tuned_phases[NUM_TUNING_PHASES], tuned_phase_cnt;
|
|
const u32 *tuning_block_pattern = tuning_block_64;
|
|
int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */
|
|
int rc;
|
|
struct mmc_host *mmc = host->mmc;
|
|
struct mmc_ios ios = host->mmc->ios;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
u8 drv_type = 0;
|
|
bool drv_type_changed = false;
|
|
struct mmc_card *card = host->mmc->card;
|
|
int sts_retry;
|
|
u8 last_good_phase = 0;
|
|
|
|
/*
|
|
* Tuning is required for SDR104, HS200 and HS400 cards and
|
|
* if clock frequency is greater than 100MHz in these modes.
|
|
*/
|
|
if (host->clock <= CORE_FREQ_100MHZ ||
|
|
!((ios.timing == MMC_TIMING_MMC_HS400) ||
|
|
(ios.timing == MMC_TIMING_MMC_HS200) ||
|
|
(ios.timing == MMC_TIMING_UHS_SDR104)))
|
|
return 0;
|
|
|
|
/*
|
|
* Clear tuning_done flag before tuning to ensure proper
|
|
* HS400 settings.
|
|
*/
|
|
msm_host->tuning_done = 0;
|
|
|
|
/*
|
|
* Don't allow re-tuning for CRC errors observed for any commands
|
|
* that are sent during tuning sequence itself.
|
|
*/
|
|
if (msm_host->tuning_in_progress)
|
|
return 0;
|
|
msm_host->tuning_in_progress = true;
|
|
pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
|
|
|
|
/* CDC/SDC4 DLL HW calibration is only required for HS400 mode*/
|
|
if (msm_host->tuning_done && !msm_host->calibration_done &&
|
|
(mmc->ios.timing == MMC_TIMING_MMC_HS400)) {
|
|
rc = sdhci_msm_hs400_dll_calibration(host);
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (!rc)
|
|
msm_host->calibration_done = true;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
goto out;
|
|
}
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
|
|
if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) &&
|
|
(mmc->ios.bus_width == MMC_BUS_WIDTH_8)) {
|
|
tuning_block_pattern = tuning_block_128;
|
|
size = sizeof(tuning_block_128);
|
|
}
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
data_buf = kmalloc(size, GFP_KERNEL);
|
|
if (!data_buf) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
retry:
|
|
tuned_phase_cnt = 0;
|
|
|
|
/* first of all reset the tuning block */
|
|
rc = msm_init_cm_dll(host, DLL_INIT_NORMAL);
|
|
if (rc)
|
|
goto kfree;
|
|
|
|
phase = 0;
|
|
do {
|
|
struct mmc_command cmd = {0};
|
|
struct mmc_data data = {0};
|
|
struct mmc_request mrq = {
|
|
.cmd = &cmd,
|
|
.data = &data
|
|
};
|
|
struct scatterlist sg;
|
|
struct mmc_command sts_cmd = {0};
|
|
|
|
msm_host->phase_on_tuning = phase;
|
|
/* set the phase in delay line hw block */
|
|
rc = msm_config_cm_dll_phase(host, phase);
|
|
if (rc)
|
|
goto kfree;
|
|
|
|
cmd.opcode = opcode;
|
|
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
|
|
|
data.blksz = size;
|
|
data.blocks = 1;
|
|
data.flags = MMC_DATA_READ;
|
|
data.timeout_ns = 1000 * 1000 * 1000; /* 1 sec */
|
|
|
|
data.sg = &sg;
|
|
data.sg_len = 1;
|
|
sg_init_one(&sg, data_buf, size);
|
|
memset(data_buf, 0, size);
|
|
mmc_wait_for_req(mmc, &mrq);
|
|
|
|
if (card && (cmd.error || data.error)) {
|
|
/*
|
|
* Set the dll to last known good phase while sending
|
|
* status command to ensure that status command won't
|
|
* fail due to bad phase.
|
|
*/
|
|
if (tuned_phase_cnt)
|
|
last_good_phase =
|
|
tuned_phases[tuned_phase_cnt-1];
|
|
else if (msm_host->saved_tuning_phase !=
|
|
INVALID_TUNING_PHASE)
|
|
last_good_phase = msm_host->saved_tuning_phase;
|
|
|
|
rc = msm_config_cm_dll_phase(host, last_good_phase);
|
|
if (rc)
|
|
goto kfree;
|
|
|
|
sts_cmd.opcode = MMC_SEND_STATUS;
|
|
sts_cmd.arg = card->rca << 16;
|
|
sts_cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
|
sts_retry = 5;
|
|
while (sts_retry) {
|
|
mmc_wait_for_cmd(mmc, &sts_cmd, 0);
|
|
|
|
if (sts_cmd.error ||
|
|
(R1_CURRENT_STATE(sts_cmd.resp[0])
|
|
!= R1_STATE_TRAN)) {
|
|
sts_retry--;
|
|
/*
|
|
* wait for at least 146 MCLK cycles for
|
|
* the card to move to TRANS state. As
|
|
* the MCLK would be min 200MHz for
|
|
* tuning, we need max 0.73us delay. To
|
|
* be on safer side 1ms delay is given.
|
|
*/
|
|
usleep_range(1000, 1200);
|
|
pr_debug("%s: phase %d sts cmd err %d resp 0x%x\n",
|
|
mmc_hostname(mmc), phase,
|
|
sts_cmd.error, sts_cmd.resp[0]);
|
|
continue;
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
if (!cmd.error && !data.error &&
|
|
!memcmp(data_buf, tuning_block_pattern, size)) {
|
|
/* tuning is successful at this tuning point */
|
|
tuned_phases[tuned_phase_cnt++] = phase;
|
|
pr_debug("%s: %s: found *** good *** phase = %d\n",
|
|
mmc_hostname(mmc), __func__, phase);
|
|
} else {
|
|
/* Ignore crc errors occurred during tuning */
|
|
if (cmd.error)
|
|
mmc->err_stats[MMC_ERR_CMD_CRC]--;
|
|
else if (data.error)
|
|
mmc->err_stats[MMC_ERR_DAT_CRC]--;
|
|
pr_debug("%s: %s: found ## bad ## phase = %d\n",
|
|
mmc_hostname(mmc), __func__, phase);
|
|
}
|
|
} while (++phase < 16);
|
|
|
|
if ((tuned_phase_cnt == NUM_TUNING_PHASES) &&
|
|
card && mmc_card_mmc(card)) {
|
|
/*
|
|
* If all phases pass then its a problem. So change the card's
|
|
* drive type to a different value, if supported and repeat
|
|
* tuning until at least one phase fails. Then set the original
|
|
* drive type back.
|
|
*
|
|
* If all the phases still pass after trying all possible
|
|
* drive types, then one of those 16 phases will be picked.
|
|
* This is no different from what was going on before the
|
|
* modification to change drive type and retune.
|
|
*/
|
|
pr_debug("%s: tuned phases count: %d\n", mmc_hostname(mmc),
|
|
tuned_phase_cnt);
|
|
|
|
/* set drive type to other value . default setting is 0x0 */
|
|
while (++drv_type <= MAX_DRV_TYPES_SUPPORTED_HS200) {
|
|
pr_debug("%s: trying different drive strength (%d)\n",
|
|
mmc_hostname(mmc), drv_type);
|
|
if (card->ext_csd.raw_driver_strength &
|
|
(1 << drv_type)) {
|
|
sdhci_msm_set_mmc_drv_type(host, opcode,
|
|
drv_type);
|
|
if (!drv_type_changed)
|
|
drv_type_changed = true;
|
|
goto retry;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* reset drive type to default (50 ohm) if changed */
|
|
if (drv_type_changed)
|
|
sdhci_msm_set_mmc_drv_type(host, opcode, 0);
|
|
|
|
if (tuned_phase_cnt) {
|
|
rc = msm_find_most_appropriate_phase(host, tuned_phases,
|
|
tuned_phase_cnt);
|
|
if (rc < 0)
|
|
goto kfree;
|
|
else
|
|
phase = (u8)rc;
|
|
|
|
/*
|
|
* Finally set the selected phase in delay
|
|
* line hw block.
|
|
*/
|
|
rc = msm_config_cm_dll_phase(host, phase);
|
|
if (rc)
|
|
goto kfree;
|
|
if (msm_host->saved_tuning_phase != phase) {
|
|
int i = 0;
|
|
char phase_result[17] = { 0, };
|
|
|
|
pr_err("%s: %s: finally setting the tuning phase from %d to %d\n",
|
|
mmc_hostname(mmc), __func__,
|
|
msm_host->saved_tuning_phase, phase);
|
|
for (i = 0; i < tuned_phase_cnt; i++)
|
|
snprintf(phase_result + i, sizeof(phase_result) - i, "%1x", tuned_phases[i]);
|
|
pr_err("%s\n", phase_result);
|
|
|
|
msm_host->saved_tuning_phase = phase;
|
|
}
|
|
} else {
|
|
if (--tuning_seq_cnt)
|
|
goto retry;
|
|
/* tuning failed */
|
|
pr_err("%s: %s: no tuning point found\n",
|
|
mmc_hostname(mmc), __func__);
|
|
rc = -EIO;
|
|
}
|
|
|
|
kfree:
|
|
kfree(data_buf);
|
|
out:
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (!rc)
|
|
msm_host->tuning_done = true;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
msm_host->tuning_in_progress = false;
|
|
pr_debug("%s: Exit %s, err(%d)\n", mmc_hostname(mmc), __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int sdhci_msm_setup_gpio(struct sdhci_msm_pltfm_data *pdata, bool enable)
|
|
{
|
|
struct sdhci_msm_gpio_data *curr;
|
|
int i, ret = 0;
|
|
|
|
curr = pdata->pin_data->gpio_data;
|
|
for (i = 0; i < curr->size; i++) {
|
|
if (!gpio_is_valid(curr->gpio[i].no)) {
|
|
ret = -EINVAL;
|
|
pr_err("%s: Invalid gpio = %d\n", __func__,
|
|
curr->gpio[i].no);
|
|
goto free_gpios;
|
|
}
|
|
if (enable) {
|
|
ret = gpio_request(curr->gpio[i].no,
|
|
curr->gpio[i].name);
|
|
if (ret) {
|
|
pr_err("%s: gpio_request(%d, %s) failed %d\n",
|
|
__func__, curr->gpio[i].no,
|
|
curr->gpio[i].name, ret);
|
|
goto free_gpios;
|
|
}
|
|
curr->gpio[i].is_enabled = true;
|
|
} else {
|
|
gpio_free(curr->gpio[i].no);
|
|
curr->gpio[i].is_enabled = false;
|
|
}
|
|
}
|
|
return ret;
|
|
|
|
free_gpios:
|
|
for (i--; i >= 0; i--) {
|
|
gpio_free(curr->gpio[i].no);
|
|
curr->gpio[i].is_enabled = false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_config_pinctrl_drv_type(struct sdhci_msm_pltfm_data *pdata,
|
|
unsigned int clock)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (clock > 150000000) {
|
|
if (pdata->pctrl_data->pins_drv_type_200MHz)
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_drv_type_200MHz);
|
|
} else if (clock > 75000000) {
|
|
if (pdata->pctrl_data->pins_drv_type_100MHz)
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_drv_type_100MHz);
|
|
} else if (clock > 400000) {
|
|
if (pdata->pctrl_data->pins_drv_type_50MHz)
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_drv_type_50MHz);
|
|
} else {
|
|
if (pdata->pctrl_data->pins_drv_type_400KHz)
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_drv_type_400KHz);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_setup_pinctrl(struct sdhci_msm_pltfm_data *pdata,
|
|
bool enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (enable)
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_active);
|
|
else
|
|
ret = pinctrl_select_state(pdata->pctrl_data->pctrl,
|
|
pdata->pctrl_data->pins_sleep);
|
|
|
|
if (ret < 0)
|
|
pr_err("%s state for pinctrl failed with %d\n",
|
|
enable ? "Enabling" : "Disabling", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (pdata->pin_cfg_sts == enable) {
|
|
return 0;
|
|
} else if (pdata->pctrl_data) {
|
|
ret = sdhci_msm_setup_pinctrl(pdata, enable);
|
|
goto out;
|
|
} else if (!pdata->pin_data) {
|
|
return 0;
|
|
}
|
|
|
|
if (pdata->pin_data->is_gpio)
|
|
ret = sdhci_msm_setup_gpio(pdata, enable);
|
|
out:
|
|
if (!ret)
|
|
pdata->pin_cfg_sts = enable;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name,
|
|
u32 **out, int *len, u32 size)
|
|
{
|
|
int ret = 0;
|
|
struct device_node *np = dev->of_node;
|
|
size_t sz;
|
|
u32 *arr = NULL;
|
|
|
|
if (!of_get_property(np, prop_name, len)) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
sz = *len = *len / sizeof(*arr);
|
|
if (sz <= 0 || (size > 0 && (sz > size))) {
|
|
dev_err(dev, "%s invalid size\n", prop_name);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
arr = devm_kzalloc(dev, sz * sizeof(*arr), GFP_KERNEL);
|
|
if (!arr) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(np, prop_name, arr, sz);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s failed reading array %d\n", prop_name, ret);
|
|
goto out;
|
|
}
|
|
*out = arr;
|
|
out:
|
|
if (ret)
|
|
*len = 0;
|
|
return ret;
|
|
}
|
|
|
|
#define MAX_PROP_SIZE 32
|
|
static int sdhci_msm_dt_parse_vreg_info(struct device *dev,
|
|
struct sdhci_msm_reg_data **vreg_data, const char *vreg_name)
|
|
{
|
|
int len, ret = 0;
|
|
const __be32 *prop;
|
|
char prop_name[MAX_PROP_SIZE];
|
|
struct sdhci_msm_reg_data *vreg;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name);
|
|
if (!of_parse_phandle(np, prop_name, 0)) {
|
|
dev_info(dev, "No vreg data found for %s\n", vreg_name);
|
|
return ret;
|
|
}
|
|
|
|
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
|
|
if (!vreg) {
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
|
|
vreg->name = vreg_name;
|
|
|
|
snprintf(prop_name, MAX_PROP_SIZE,
|
|
"qcom,%s-always-on", vreg_name);
|
|
if (of_get_property(np, prop_name, NULL))
|
|
vreg->is_always_on = true;
|
|
|
|
snprintf(prop_name, MAX_PROP_SIZE,
|
|
"qcom,%s-lpm-sup", vreg_name);
|
|
if (of_get_property(np, prop_name, NULL))
|
|
vreg->lpm_sup = true;
|
|
|
|
snprintf(prop_name, MAX_PROP_SIZE,
|
|
"qcom,%s-voltage-level", vreg_name);
|
|
prop = of_get_property(np, prop_name, &len);
|
|
if (!prop || (len != (2 * sizeof(__be32)))) {
|
|
dev_warn(dev, "%s %s property\n",
|
|
prop ? "invalid format" : "no", prop_name);
|
|
} else {
|
|
vreg->low_vol_level = be32_to_cpup(&prop[0]);
|
|
vreg->high_vol_level = be32_to_cpup(&prop[1]);
|
|
}
|
|
|
|
snprintf(prop_name, MAX_PROP_SIZE,
|
|
"qcom,%s-current-level", vreg_name);
|
|
prop = of_get_property(np, prop_name, &len);
|
|
if (!prop || (len != (2 * sizeof(__be32)))) {
|
|
dev_warn(dev, "%s %s property\n",
|
|
prop ? "invalid format" : "no", prop_name);
|
|
} else {
|
|
vreg->lpm_uA = be32_to_cpup(&prop[0]);
|
|
vreg->hpm_uA = be32_to_cpup(&prop[1]);
|
|
}
|
|
|
|
*vreg_data = vreg;
|
|
dev_dbg(dev, "%s: %s %s vol=[%d %d]uV, curr=[%d %d]uA\n",
|
|
vreg->name, vreg->is_always_on ? "always_on," : "",
|
|
vreg->lpm_sup ? "lpm_sup," : "", vreg->low_vol_level,
|
|
vreg->high_vol_level, vreg->lpm_uA, vreg->hpm_uA);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_parse_pinctrl_info(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
struct sdhci_pinctrl_data *pctrl_data;
|
|
struct pinctrl *pctrl;
|
|
int ret = 0;
|
|
|
|
/* Try to obtain pinctrl handle */
|
|
pctrl = devm_pinctrl_get(dev);
|
|
if (IS_ERR(pctrl)) {
|
|
ret = PTR_ERR(pctrl);
|
|
goto out;
|
|
}
|
|
pctrl_data = devm_kzalloc(dev, sizeof(*pctrl_data), GFP_KERNEL);
|
|
if (!pctrl_data) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
pctrl_data->pctrl = pctrl;
|
|
/* Look-up and keep the states handy to be used later */
|
|
pctrl_data->pins_active = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "active");
|
|
if (IS_ERR(pctrl_data->pins_active)) {
|
|
ret = PTR_ERR(pctrl_data->pins_active);
|
|
dev_err(dev, "Could not get active pinstates, err:%d\n", ret);
|
|
goto out;
|
|
}
|
|
pctrl_data->pins_sleep = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "sleep");
|
|
if (IS_ERR(pctrl_data->pins_sleep)) {
|
|
ret = PTR_ERR(pctrl_data->pins_sleep);
|
|
dev_err(dev, "Could not get sleep pinstates, err:%d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
pctrl_data->pins_drv_type_400KHz = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "ds_400KHz");
|
|
if (IS_ERR(pctrl_data->pins_drv_type_400KHz)) {
|
|
dev_dbg(dev, "Could not get 400K pinstates, err:%d\n", ret);
|
|
pctrl_data->pins_drv_type_400KHz = NULL;
|
|
}
|
|
|
|
pctrl_data->pins_drv_type_50MHz = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "ds_50MHz");
|
|
if (IS_ERR(pctrl_data->pins_drv_type_50MHz)) {
|
|
dev_dbg(dev, "Could not get 50M pinstates, err:%d\n", ret);
|
|
pctrl_data->pins_drv_type_50MHz = NULL;
|
|
}
|
|
|
|
pctrl_data->pins_drv_type_100MHz = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "ds_100MHz");
|
|
if (IS_ERR(pctrl_data->pins_drv_type_100MHz)) {
|
|
dev_dbg(dev, "Could not get 100M pinstates, err:%d\n", ret);
|
|
pctrl_data->pins_drv_type_100MHz = NULL;
|
|
}
|
|
|
|
pctrl_data->pins_drv_type_200MHz = pinctrl_lookup_state(
|
|
pctrl_data->pctrl, "ds_200MHz");
|
|
if (IS_ERR(pctrl_data->pins_drv_type_200MHz)) {
|
|
dev_dbg(dev, "Could not get 200M pinstates, err:%d\n", ret);
|
|
pctrl_data->pins_drv_type_200MHz = NULL;
|
|
}
|
|
|
|
pdata->pctrl_data = pctrl_data;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
#define GPIO_NAME_MAX_LEN 32
|
|
static int sdhci_msm_dt_parse_gpio_info(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
int ret = 0, cnt, i;
|
|
struct sdhci_msm_pin_data *pin_data;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
ret = sdhci_msm_parse_pinctrl_info(dev, pdata);
|
|
if (!ret) {
|
|
goto out;
|
|
} else if (ret == -EPROBE_DEFER) {
|
|
dev_err(dev, "Pinctrl framework not registered, err:%d\n", ret);
|
|
goto out;
|
|
} else {
|
|
dev_err(dev, "Parsing Pinctrl failed with %d, falling back on GPIO lib\n",
|
|
ret);
|
|
ret = 0;
|
|
}
|
|
pin_data = devm_kzalloc(dev, sizeof(*pin_data), GFP_KERNEL);
|
|
if (!pin_data) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
cnt = of_gpio_count(np);
|
|
if (cnt > 0) {
|
|
pin_data->gpio_data = devm_kzalloc(dev,
|
|
sizeof(struct sdhci_msm_gpio_data), GFP_KERNEL);
|
|
if (!pin_data->gpio_data) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
pin_data->gpio_data->size = cnt;
|
|
pin_data->gpio_data->gpio = devm_kzalloc(dev, cnt *
|
|
sizeof(struct sdhci_msm_gpio), GFP_KERNEL);
|
|
|
|
if (!pin_data->gpio_data->gpio) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
const char *name = NULL;
|
|
char result[GPIO_NAME_MAX_LEN];
|
|
|
|
pin_data->gpio_data->gpio[i].no = of_get_gpio(np, i);
|
|
of_property_read_string_index(np,
|
|
"qcom,gpio-names", i, &name);
|
|
|
|
snprintf(result, GPIO_NAME_MAX_LEN, "%s-%s",
|
|
dev_name(dev), name ? name : "?");
|
|
pin_data->gpio_data->gpio[i].name = result;
|
|
dev_dbg(dev, "%s: gpio[%s] = %d\n", __func__,
|
|
pin_data->gpio_data->gpio[i].name,
|
|
pin_data->gpio_data->gpio[i].no);
|
|
}
|
|
}
|
|
pdata->pin_data = pin_data;
|
|
out:
|
|
if (ret)
|
|
dev_err(dev, "%s failed with err %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static inline void parse_affine_irq(struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_IRQ;
|
|
}
|
|
#else
|
|
static inline void parse_affine_irq(struct sdhci_msm_pltfm_data *pdata) { }
|
|
#endif
|
|
|
|
static int sdhci_msm_pm_qos_parse_irq(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
const char *str;
|
|
u32 cpu;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
pdata->pm_qos_data.irq_valid = false;
|
|
pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_CORES;
|
|
if (!of_property_read_string(np, "qcom,pm-qos-irq-type", &str) &&
|
|
!strcmp(str, "affine_irq")) {
|
|
parse_affine_irq(pdata);
|
|
}
|
|
|
|
/* must specify cpu for "affine_cores" type */
|
|
if (pdata->pm_qos_data.irq_req_type == PM_QOS_REQ_AFFINE_CORES) {
|
|
pdata->pm_qos_data.irq_cpu = -1;
|
|
ret = of_property_read_u32(np, "qcom,pm-qos-irq-cpu", &cpu);
|
|
if (ret) {
|
|
dev_err(dev, "%s: error %d reading irq cpu\n", __func__,
|
|
ret);
|
|
goto out;
|
|
}
|
|
if (cpu < 0 || cpu >= num_possible_cpus()) {
|
|
dev_err(dev, "%s: invalid irq cpu %d (NR_CPUS=%d)\n",
|
|
__func__, cpu, num_possible_cpus());
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pdata->pm_qos_data.irq_cpu = cpu;
|
|
}
|
|
|
|
if (of_property_count_u32_elems(np, "qcom,pm-qos-irq-latency") !=
|
|
SDHCI_POWER_POLICY_NUM) {
|
|
dev_err(dev, "%s: could not read %d values for 'qcom,pm-qos-irq-latency'\n",
|
|
__func__, SDHCI_POWER_POLICY_NUM);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < SDHCI_POWER_POLICY_NUM; i++)
|
|
of_property_read_u32_index(np, "qcom,pm-qos-irq-latency", i,
|
|
&pdata->pm_qos_data.irq_latency.latency[i]);
|
|
|
|
pdata->pm_qos_data.irq_valid = true;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_pm_qos_parse_cpu_groups(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
u32 mask;
|
|
int nr_groups;
|
|
int ret;
|
|
int i;
|
|
|
|
/* Read cpu group mapping */
|
|
nr_groups = of_property_count_u32_elems(np, "qcom,pm-qos-cpu-groups");
|
|
if (nr_groups <= 0) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pdata->pm_qos_data.cpu_group_map.nr_groups = nr_groups;
|
|
pdata->pm_qos_data.cpu_group_map.mask =
|
|
kcalloc(nr_groups, sizeof(cpumask_t), GFP_KERNEL);
|
|
if (!pdata->pm_qos_data.cpu_group_map.mask) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < nr_groups; i++) {
|
|
of_property_read_u32_index(np, "qcom,pm-qos-cpu-groups",
|
|
i, &mask);
|
|
|
|
pdata->pm_qos_data.cpu_group_map.mask[i].bits[0] = mask;
|
|
if (!cpumask_subset(&pdata->pm_qos_data.cpu_group_map.mask[i],
|
|
cpu_possible_mask)) {
|
|
dev_err(dev, "%s: invalid mask 0x%x of cpu group #%d\n",
|
|
__func__, mask, i);
|
|
ret = -EINVAL;
|
|
goto free_res;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
free_res:
|
|
kfree(pdata->pm_qos_data.cpu_group_map.mask);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_pm_qos_parse_latency(struct device *dev, const char *name,
|
|
int nr_groups, struct sdhci_msm_pm_qos_latency **latency)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
struct sdhci_msm_pm_qos_latency *values;
|
|
int ret;
|
|
int i;
|
|
int group;
|
|
int cfg;
|
|
|
|
ret = of_property_count_u32_elems(np, name);
|
|
if (ret > 0 && ret != SDHCI_POWER_POLICY_NUM * nr_groups) {
|
|
dev_err(dev, "%s: invalid number of values for property %s: expected=%d actual=%d\n",
|
|
__func__, name, SDHCI_POWER_POLICY_NUM * nr_groups,
|
|
ret);
|
|
return -EINVAL;
|
|
} else if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
values = kcalloc(nr_groups, sizeof(struct sdhci_msm_pm_qos_latency),
|
|
GFP_KERNEL);
|
|
if (!values)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < SDHCI_POWER_POLICY_NUM * nr_groups; i++) {
|
|
group = i / SDHCI_POWER_POLICY_NUM;
|
|
cfg = i % SDHCI_POWER_POLICY_NUM;
|
|
of_property_read_u32_index(np, name, i,
|
|
&(values[group].latency[cfg]));
|
|
}
|
|
|
|
*latency = values;
|
|
return 0;
|
|
}
|
|
|
|
static void sdhci_msm_pm_qos_parse(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata)
|
|
{
|
|
if (sdhci_msm_pm_qos_parse_irq(dev, pdata))
|
|
dev_notice(dev, "%s: PM QoS voting for IRQ will be disabled\n",
|
|
__func__);
|
|
|
|
if (!sdhci_msm_pm_qos_parse_cpu_groups(dev, pdata)) {
|
|
pdata->pm_qos_data.cmdq_valid =
|
|
!sdhci_msm_pm_qos_parse_latency(dev,
|
|
"qcom,pm-qos-cmdq-latency-us",
|
|
pdata->pm_qos_data.cpu_group_map.nr_groups,
|
|
&pdata->pm_qos_data.cmdq_latency);
|
|
pdata->pm_qos_data.legacy_valid =
|
|
!sdhci_msm_pm_qos_parse_latency(dev,
|
|
"qcom,pm-qos-legacy-latency-us",
|
|
pdata->pm_qos_data.cpu_group_map.nr_groups,
|
|
&pdata->pm_qos_data.latency);
|
|
if (!pdata->pm_qos_data.cmdq_valid &&
|
|
!pdata->pm_qos_data.legacy_valid) {
|
|
/* clean-up previously allocated arrays */
|
|
kfree(pdata->pm_qos_data.latency);
|
|
kfree(pdata->pm_qos_data.cmdq_latency);
|
|
dev_err(dev, "%s: invalid PM QoS latency values. Voting for cpu group will be disabled\n",
|
|
__func__);
|
|
}
|
|
} else {
|
|
dev_notice(dev, "%s: PM QoS voting for cpu group will be disabled\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
static int sdhci_msm_dt_parse_hsr_info(struct device *dev,
|
|
struct sdhci_msm_host *msm_host)
|
|
|
|
{
|
|
u32 *dll_hsr_table = NULL;
|
|
int dll_hsr_table_len, dll_hsr_reg_count;
|
|
int ret = 0;
|
|
|
|
if (sdhci_msm_dt_get_array(dev, "qcom,dll-hsr-list",
|
|
&dll_hsr_table, &dll_hsr_table_len, 0))
|
|
goto skip_hsr;
|
|
|
|
dll_hsr_reg_count = sizeof(struct sdhci_msm_dll_hsr) / sizeof(u32);
|
|
if (dll_hsr_table_len != dll_hsr_reg_count) {
|
|
dev_err(dev, "Number of HSR entries are not matching\n");
|
|
ret = -EINVAL;
|
|
} else {
|
|
msm_host->dll_hsr = (struct sdhci_msm_dll_hsr *)dll_hsr_table;
|
|
}
|
|
|
|
skip_hsr:
|
|
if (!msm_host->dll_hsr)
|
|
dev_info(dev, "Failed to get dll hsr settings from dt\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Parse platform data */
|
|
static
|
|
struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev,
|
|
struct sdhci_msm_host *msm_host)
|
|
{
|
|
struct sdhci_msm_pltfm_data *pdata = NULL;
|
|
struct device_node *np = dev->of_node;
|
|
u32 bus_width = 0;
|
|
int len, i;
|
|
int clk_table_len;
|
|
u32 *clk_table = NULL;
|
|
int ice_clk_table_len;
|
|
u32 *ice_clk_table = NULL;
|
|
enum of_gpio_flags flags = OF_GPIO_ACTIVE_LOW;
|
|
const char *lower_bus_speed = NULL;
|
|
int bus_clk_table_len;
|
|
u32 *bus_clk_table = NULL;
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
goto out;
|
|
|
|
device_property_read_u32(dev, "post-power-on-delay-ms",
|
|
&msm_host->mmc->ios.power_delay_ms);
|
|
|
|
pdata->status_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &flags);
|
|
if (gpio_is_valid(pdata->status_gpio) && !(flags & OF_GPIO_ACTIVE_LOW))
|
|
pdata->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
|
|
|
|
pdata->tflash_en_gpio = of_get_named_gpio(np, "sec,tflash-en-gpio", 0);
|
|
|
|
of_property_read_u32(np, "qcom,bus-width", &bus_width);
|
|
if (bus_width == 8)
|
|
pdata->mmc_bus_width = MMC_CAP_8_BIT_DATA;
|
|
else if (bus_width == 4)
|
|
pdata->mmc_bus_width = MMC_CAP_4_BIT_DATA;
|
|
else {
|
|
dev_notice(dev, "invalid bus-width, default to 1-bit mode\n");
|
|
pdata->mmc_bus_width = 0;
|
|
}
|
|
|
|
if (sdhci_msm_dt_get_array(dev, "qcom,devfreq,freq-table",
|
|
&msm_host->mmc->clk_scaling.pltfm_freq_table,
|
|
&msm_host->mmc->clk_scaling.pltfm_freq_table_sz, 0))
|
|
pr_debug("%s: no clock scaling frequencies were supplied\n",
|
|
dev_name(dev));
|
|
else if (!msm_host->mmc->clk_scaling.pltfm_freq_table ||
|
|
!msm_host->mmc->clk_scaling.pltfm_freq_table_sz)
|
|
dev_err(dev, "bad dts clock scaling frequencies\n");
|
|
|
|
/*
|
|
* Few hosts can support DDR52 mode at the same lower
|
|
* system voltage corner as high-speed mode. In such cases,
|
|
* it is always better to put it in DDR mode which will
|
|
* improve the performance without any power impact.
|
|
*/
|
|
if (!of_property_read_string(np, "qcom,scaling-lower-bus-speed-mode",
|
|
&lower_bus_speed)) {
|
|
if (!strcmp(lower_bus_speed, "DDR52"))
|
|
msm_host->mmc->clk_scaling.lower_bus_speed_mode |=
|
|
MMC_SCALING_LOWER_DDR52_MODE;
|
|
}
|
|
|
|
if (sdhci_msm_dt_get_array(dev, "qcom,clk-rates",
|
|
&clk_table, &clk_table_len, 0)) {
|
|
dev_err(dev, "failed parsing supported clock rates\n");
|
|
goto out;
|
|
}
|
|
if (!clk_table || !clk_table_len) {
|
|
dev_err(dev, "Invalid clock table\n");
|
|
goto out;
|
|
}
|
|
pdata->sup_clk_table = clk_table;
|
|
pdata->sup_clk_cnt = clk_table_len;
|
|
|
|
if (!sdhci_msm_dt_get_array(dev, "qcom,bus-aggr-clk-rates",
|
|
&bus_clk_table, &bus_clk_table_len, 0)) {
|
|
if (bus_clk_table && bus_clk_table_len) {
|
|
pdata->bus_clk_table = bus_clk_table;
|
|
pdata->bus_clk_cnt = bus_clk_table_len;
|
|
}
|
|
}
|
|
|
|
if (!sdhci_msm_dt_get_array(dev, "qcom,ice-clk-rates",
|
|
&ice_clk_table, &ice_clk_table_len, 0)) {
|
|
if (ice_clk_table && ice_clk_table_len) {
|
|
if (ice_clk_table_len != 2) {
|
|
dev_err(dev, "Need max and min frequencies\n");
|
|
goto out;
|
|
}
|
|
pdata->sup_ice_clk_table = ice_clk_table;
|
|
pdata->sup_ice_clk_cnt = ice_clk_table_len;
|
|
pdata->ice_clk_max = pdata->sup_ice_clk_table[0];
|
|
pdata->ice_clk_min = pdata->sup_ice_clk_table[1];
|
|
dev_dbg(dev, "ICE clock rates (Hz): max: %u min: %u\n",
|
|
pdata->ice_clk_max, pdata->ice_clk_min);
|
|
}
|
|
}
|
|
|
|
pdata->vreg_data = devm_kzalloc(dev, sizeof(struct
|
|
sdhci_msm_slot_reg_data),
|
|
GFP_KERNEL);
|
|
if (!pdata->vreg_data) {
|
|
dev_err(dev, "failed to allocate memory for vreg data\n");
|
|
goto out;
|
|
}
|
|
|
|
if (sdhci_msm_dt_parse_vreg_info(dev, &pdata->vreg_data->vdd_data,
|
|
"vdd")) {
|
|
dev_err(dev, "failed parsing vdd data\n");
|
|
goto out;
|
|
}
|
|
if (sdhci_msm_dt_parse_vreg_info(dev,
|
|
&pdata->vreg_data->vdd_io_data,
|
|
"vdd-io")) {
|
|
dev_err(dev, "failed parsing vdd-io data\n");
|
|
goto out;
|
|
}
|
|
|
|
if (sdhci_msm_dt_parse_gpio_info(dev, pdata)) {
|
|
dev_err(dev, "failed parsing gpio data\n");
|
|
goto out;
|
|
}
|
|
|
|
len = of_property_count_strings(np, "qcom,bus-speed-mode");
|
|
|
|
for (i = 0; i < len; i++) {
|
|
const char *name = NULL;
|
|
|
|
of_property_read_string_index(np,
|
|
"qcom,bus-speed-mode", i, &name);
|
|
if (!name)
|
|
continue;
|
|
|
|
if (!strncmp(name, "HS400_1p8v", sizeof("HS400_1p8v")))
|
|
pdata->caps2 |= MMC_CAP2_HS400_1_8V;
|
|
else if (!strncmp(name, "HS400_1p2v", sizeof("HS400_1p2v")))
|
|
pdata->caps2 |= MMC_CAP2_HS400_1_2V;
|
|
else if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v")))
|
|
pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
|
|
else if (!strncmp(name, "HS200_1p2v", sizeof("HS200_1p2v")))
|
|
pdata->caps2 |= MMC_CAP2_HS200_1_2V_SDR;
|
|
else if (!strncmp(name, "DDR_1p8v", sizeof("DDR_1p8v")))
|
|
pdata->caps |= MMC_CAP_1_8V_DDR
|
|
| MMC_CAP_UHS_DDR50;
|
|
else if (!strncmp(name, "DDR_1p2v", sizeof("DDR_1p2v")))
|
|
pdata->caps |= MMC_CAP_1_2V_DDR
|
|
| MMC_CAP_UHS_DDR50;
|
|
}
|
|
|
|
if (of_get_property(np, "qcom,nonremovable", NULL))
|
|
pdata->nonremovable = true;
|
|
|
|
if (of_get_property(np, "qcom,nonhotplug", NULL))
|
|
pdata->nonhotplug = true;
|
|
|
|
pdata->largeaddressbus =
|
|
of_property_read_bool(np, "qcom,large-address-bus");
|
|
|
|
if (of_property_read_bool(np, "qcom,wakeup-on-idle"))
|
|
msm_host->mmc->wakeup_on_idle = true;
|
|
|
|
sdhci_msm_pm_qos_parse(dev, pdata);
|
|
|
|
if (of_get_property(np, "qcom,core_3_0v_support", NULL))
|
|
msm_host->core_3_0v_support = true;
|
|
|
|
pdata->sdr104_wa = of_property_read_bool(np, "qcom,sdr104-wa");
|
|
msm_host->regs_restore.is_supported =
|
|
of_property_read_bool(np, "qcom,restore-after-cx-collapse");
|
|
|
|
if (sdhci_msm_dt_parse_hsr_info(dev, msm_host))
|
|
goto out;
|
|
|
|
return pdata;
|
|
out:
|
|
return NULL;
|
|
}
|
|
|
|
/* Returns required bandwidth in Bytes per Sec */
|
|
static unsigned int sdhci_get_bw_required(struct sdhci_host *host,
|
|
struct mmc_ios *ios)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
unsigned int bw;
|
|
|
|
bw = msm_host->clk_rate;
|
|
/*
|
|
* For DDR mode, SDCC controller clock will be at
|
|
* the double rate than the actual clock that goes to card.
|
|
*/
|
|
if (ios->bus_width == MMC_BUS_WIDTH_4)
|
|
bw /= 2;
|
|
else if (ios->bus_width == MMC_BUS_WIDTH_1)
|
|
bw /= 8;
|
|
|
|
return bw;
|
|
}
|
|
|
|
static int sdhci_msm_bus_get_vote_for_bw(struct sdhci_msm_host *host,
|
|
unsigned int bw)
|
|
{
|
|
unsigned int *table = host->pdata->voting_data->bw_vecs;
|
|
unsigned int size = host->pdata->voting_data->bw_vecs_size;
|
|
int i;
|
|
|
|
if (host->msm_bus_vote.is_max_bw_needed && bw)
|
|
return host->msm_bus_vote.max_bw_vote;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (bw <= table[i])
|
|
break;
|
|
}
|
|
|
|
if (i && (i == size))
|
|
i--;
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* This function must be called with host lock acquired.
|
|
* Caller of this function should also ensure that msm bus client
|
|
* handle is not null.
|
|
*/
|
|
static inline int sdhci_msm_bus_set_vote(struct sdhci_msm_host *msm_host,
|
|
int vote,
|
|
unsigned long *flags)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(msm_host->pdev);
|
|
int rc = 0;
|
|
|
|
BUG_ON(!flags);
|
|
|
|
if (vote != msm_host->msm_bus_vote.curr_vote) {
|
|
spin_unlock_irqrestore(&host->lock, *flags);
|
|
rc = msm_bus_scale_client_update_request(
|
|
msm_host->msm_bus_vote.client_handle, vote);
|
|
spin_lock_irqsave(&host->lock, *flags);
|
|
if (rc) {
|
|
pr_err("%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
|
|
mmc_hostname(host->mmc),
|
|
msm_host->msm_bus_vote.client_handle, vote, rc);
|
|
goto out;
|
|
}
|
|
msm_host->msm_bus_vote.curr_vote = vote;
|
|
}
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Internal work. Work to set 0 bandwidth for msm bus.
|
|
*/
|
|
static void sdhci_msm_bus_work(struct work_struct *work)
|
|
{
|
|
struct sdhci_msm_host *msm_host;
|
|
struct sdhci_host *host;
|
|
unsigned long flags;
|
|
|
|
msm_host = container_of(work, struct sdhci_msm_host,
|
|
msm_bus_vote.vote_work.work);
|
|
host = platform_get_drvdata(msm_host->pdev);
|
|
|
|
if (!msm_host->msm_bus_vote.client_handle)
|
|
return;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
/* don't vote for 0 bandwidth if any request is in progress */
|
|
if (!host->mrq) {
|
|
sdhci_msm_bus_set_vote(msm_host,
|
|
msm_host->msm_bus_vote.min_bw_vote, &flags);
|
|
} else
|
|
pr_warn("%s: %s: Transfer in progress. skipping bus voting to 0 bandwidth\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
/*
|
|
* This function cancels any scheduled delayed work and sets the bus
|
|
* vote based on bw (bandwidth) argument.
|
|
*/
|
|
static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host,
|
|
unsigned int bw)
|
|
{
|
|
int vote;
|
|
unsigned long flags;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
cancel_delayed_work_sync(&msm_host->msm_bus_vote.vote_work);
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw);
|
|
sdhci_msm_bus_set_vote(msm_host, vote, &flags);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */
|
|
|
|
/* This function queues a work which will set the bandwidth requiement to 0 */
|
|
static void sdhci_msm_bus_queue_work(struct sdhci_host *host)
|
|
{
|
|
unsigned long flags;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (msm_host->msm_bus_vote.min_bw_vote !=
|
|
msm_host->msm_bus_vote.curr_vote)
|
|
queue_delayed_work(system_wq,
|
|
&msm_host->msm_bus_vote.vote_work,
|
|
msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY));
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
static int sdhci_msm_bus_register(struct sdhci_msm_host *host,
|
|
struct platform_device *pdev)
|
|
{
|
|
int rc = 0;
|
|
struct msm_bus_scale_pdata *bus_pdata;
|
|
|
|
struct sdhci_msm_bus_voting_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
data = devm_kzalloc(dev,
|
|
sizeof(struct sdhci_msm_bus_voting_data), GFP_KERNEL);
|
|
if (!data) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
data->bus_pdata = msm_bus_cl_get_pdata(pdev);
|
|
if (data->bus_pdata) {
|
|
rc = sdhci_msm_dt_get_array(dev, "qcom,bus-bw-vectors-bps",
|
|
&data->bw_vecs, &data->bw_vecs_size, 0);
|
|
if (rc) {
|
|
dev_err(&pdev->dev,
|
|
"%s: Failed to get bus-bw-vectors-bps\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
host->pdata->voting_data = data;
|
|
}
|
|
if (host->pdata->voting_data &&
|
|
host->pdata->voting_data->bus_pdata &&
|
|
host->pdata->voting_data->bw_vecs &&
|
|
host->pdata->voting_data->bw_vecs_size) {
|
|
|
|
bus_pdata = host->pdata->voting_data->bus_pdata;
|
|
host->msm_bus_vote.client_handle =
|
|
msm_bus_scale_register_client(bus_pdata);
|
|
if (!host->msm_bus_vote.client_handle) {
|
|
dev_err(&pdev->dev, "msm_bus_scale_register_client()\n");
|
|
rc = -EFAULT;
|
|
goto out;
|
|
}
|
|
/* cache the vote index for minimum and maximum bandwidth */
|
|
host->msm_bus_vote.min_bw_vote =
|
|
sdhci_msm_bus_get_vote_for_bw(host, 0);
|
|
host->msm_bus_vote.max_bw_vote =
|
|
sdhci_msm_bus_get_vote_for_bw(host, UINT_MAX);
|
|
} else {
|
|
devm_kfree(dev, data);
|
|
}
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static void sdhci_msm_bus_unregister(struct sdhci_msm_host *host)
|
|
{
|
|
if (host->msm_bus_vote.client_handle)
|
|
msm_bus_scale_unregister_client(
|
|
host->msm_bus_vote.client_handle);
|
|
}
|
|
|
|
static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct mmc_ios *ios = &host->mmc->ios;
|
|
unsigned int bw;
|
|
|
|
if (!msm_host->msm_bus_vote.client_handle)
|
|
return;
|
|
|
|
bw = sdhci_get_bw_required(host, ios);
|
|
if (enable) {
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, bw);
|
|
} else {
|
|
/*
|
|
* If clock gating is enabled, then remove the vote
|
|
* immediately because clocks will be disabled only
|
|
* after SDHCI_MSM_MMC_CLK_GATE_DELAY and thus no
|
|
* additional delay is required to remove the bus vote.
|
|
*/
|
|
#ifdef CONFIG_MMC_CLKGATE
|
|
if (host->mmc->clkgate_delay)
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
else
|
|
#endif
|
|
sdhci_msm_bus_queue_work(host);
|
|
}
|
|
}
|
|
|
|
/* Regulator utility functions */
|
|
static int sdhci_msm_vreg_init_reg(struct device *dev,
|
|
struct sdhci_msm_reg_data *vreg)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* check if regulator is already initialized? */
|
|
if (vreg->reg)
|
|
goto out;
|
|
|
|
/* Get the regulator handle */
|
|
vreg->reg = devm_regulator_get(dev, vreg->name);
|
|
if (IS_ERR(vreg->reg)) {
|
|
ret = PTR_ERR(vreg->reg);
|
|
pr_err("%s: devm_regulator_get(%s) failed. ret=%d\n",
|
|
__func__, vreg->name, ret);
|
|
goto out;
|
|
}
|
|
|
|
if (regulator_count_voltages(vreg->reg) > 0) {
|
|
vreg->set_voltage_sup = true;
|
|
/* sanity check */
|
|
if (!vreg->high_vol_level || !vreg->hpm_uA) {
|
|
pr_err("%s: %s invalid constraints specified\n",
|
|
__func__, vreg->name);
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void sdhci_msm_vreg_deinit_reg(struct sdhci_msm_reg_data *vreg)
|
|
{
|
|
if (vreg->reg)
|
|
devm_regulator_put(vreg->reg);
|
|
}
|
|
|
|
static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data
|
|
*vreg, int uA_load)
|
|
{
|
|
int ret = 0;
|
|
|
|
/*
|
|
* regulators that do not support regulator_set_voltage also
|
|
* do not support regulator_set_optimum_mode
|
|
*/
|
|
if (vreg->set_voltage_sup) {
|
|
ret = regulator_set_load(vreg->reg, uA_load);
|
|
if (ret < 0)
|
|
pr_err("%s: regulator_set_load(reg=%s,uA_load=%d) failed. ret=%d\n",
|
|
__func__, vreg->name, uA_load, ret);
|
|
else
|
|
/*
|
|
* regulator_set_load() can return non zero
|
|
* value even for success case.
|
|
*/
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_vreg_set_voltage(struct sdhci_msm_reg_data *vreg,
|
|
int min_uV, int max_uV)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (vreg->set_voltage_sup) {
|
|
ret = regulator_set_voltage(vreg->reg, min_uV, max_uV);
|
|
if (ret) {
|
|
pr_err("%s: regulator_set_voltage(%s)failed. min_uV=%d,max_uV=%d,ret=%d\n",
|
|
__func__, vreg->name, min_uV, max_uV, ret);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_vreg_enable(struct sdhci_msm_reg_data *vreg)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Put regulator in HPM (high power mode) */
|
|
ret = sdhci_msm_vreg_set_optimum_mode(vreg, vreg->hpm_uA);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!vreg->is_enabled) {
|
|
/* Set voltage level */
|
|
ret = sdhci_msm_vreg_set_voltage(vreg, vreg->high_vol_level,
|
|
vreg->high_vol_level);
|
|
if (ret) {
|
|
pr_err("%s: set volt(%s) to high is failed. ret=%d\n",
|
|
__func__, vreg->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_enable(vreg->reg);
|
|
if (ret) {
|
|
pr_err("%s: regulator_enable(%s) failed. ret=%d\n",
|
|
__func__, vreg->name, ret);
|
|
return ret;
|
|
}
|
|
vreg->is_enabled = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_vreg_disable(struct sdhci_msm_reg_data *vreg)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Never disable regulator marked as always_on */
|
|
if (vreg->is_enabled && !vreg->is_always_on) {
|
|
ret = regulator_disable(vreg->reg);
|
|
if (ret) {
|
|
pr_err("%s: regulator_disable(%s) failed. ret=%d\n",
|
|
__func__, vreg->name, ret);
|
|
goto out;
|
|
}
|
|
vreg->is_enabled = false;
|
|
|
|
ret = sdhci_msm_vreg_set_optimum_mode(vreg, 0);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* Set min. voltage level to 0 */
|
|
ret = sdhci_msm_vreg_set_voltage(vreg, 0, vreg->high_vol_level);
|
|
if (ret)
|
|
goto out;
|
|
} else if (vreg->is_enabled && vreg->is_always_on) {
|
|
if (vreg->lpm_sup) {
|
|
/* Put always_on regulator in LPM (low power mode) */
|
|
ret = sdhci_msm_vreg_set_optimum_mode(vreg,
|
|
vreg->lpm_uA);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_setup_vreg(struct sdhci_msm_pltfm_data *pdata,
|
|
bool enable, bool is_init)
|
|
{
|
|
int ret = 0, i;
|
|
struct sdhci_msm_slot_reg_data *curr_slot;
|
|
struct sdhci_msm_reg_data *vreg_table[2];
|
|
|
|
curr_slot = pdata->vreg_data;
|
|
if (!curr_slot) {
|
|
pr_debug("%s: vreg info unavailable,assuming the slot is powered by always on domain\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
|
|
vreg_table[0] = curr_slot->vdd_data;
|
|
vreg_table[1] = curr_slot->vdd_io_data;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vreg_table); i++) {
|
|
if (vreg_table[i]) {
|
|
if (enable){
|
|
if (gpio_is_valid(pdata->tflash_en_gpio)) {
|
|
if (!gpio_get_value(pdata->tflash_en_gpio)) {
|
|
gpio_direction_output(pdata->tflash_en_gpio, 1);
|
|
pr_err("tflash is %sabled(%d).\n",
|
|
gpio_get_value(pdata->tflash_en_gpio) ? "en" : "dis",
|
|
pdata->tflash_en_gpio);
|
|
}
|
|
}
|
|
ret = sdhci_msm_vreg_enable(vreg_table[i]);
|
|
}
|
|
else{
|
|
ret = sdhci_msm_vreg_disable(vreg_table[i]);
|
|
if (gpio_is_valid(pdata->tflash_en_gpio)) {
|
|
if (gpio_get_value(pdata->tflash_en_gpio)) {
|
|
mdelay(2);
|
|
gpio_direction_output(pdata->tflash_en_gpio, 0);
|
|
pr_err("tflash is %sabled(%d).\n",
|
|
gpio_get_value(pdata->tflash_en_gpio) ? "en" : "dis",
|
|
pdata->tflash_en_gpio);
|
|
}
|
|
}
|
|
}
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* This init function should be called only once for each SDHC slot */
|
|
static int sdhci_msm_vreg_init(struct device *dev,
|
|
struct sdhci_msm_pltfm_data *pdata,
|
|
bool is_init)
|
|
{
|
|
int ret = 0;
|
|
struct sdhci_msm_slot_reg_data *curr_slot;
|
|
struct sdhci_msm_reg_data *curr_vdd_reg, *curr_vdd_io_reg;
|
|
|
|
curr_slot = pdata->vreg_data;
|
|
if (!curr_slot)
|
|
goto out;
|
|
|
|
curr_vdd_reg = curr_slot->vdd_data;
|
|
curr_vdd_io_reg = curr_slot->vdd_io_data;
|
|
|
|
if (!is_init)
|
|
/* Deregister all regulators from regulator framework */
|
|
goto vdd_io_reg_deinit;
|
|
|
|
/*
|
|
* Get the regulator handle from voltage regulator framework
|
|
* and then try to set the voltage level for the regulator
|
|
*/
|
|
if (curr_vdd_reg) {
|
|
ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_reg);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
if (curr_vdd_io_reg) {
|
|
ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_io_reg);
|
|
if (ret)
|
|
goto vdd_reg_deinit;
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(dev, "vreg reset failed (%d)\n", ret);
|
|
goto out;
|
|
|
|
vdd_io_reg_deinit:
|
|
if (curr_vdd_io_reg)
|
|
sdhci_msm_vreg_deinit_reg(curr_vdd_io_reg);
|
|
vdd_reg_deinit:
|
|
if (curr_vdd_reg)
|
|
sdhci_msm_vreg_deinit_reg(curr_vdd_reg);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata,
|
|
enum vdd_io_level level,
|
|
unsigned int voltage_level)
|
|
{
|
|
int ret = 0;
|
|
int set_level;
|
|
struct sdhci_msm_reg_data *vdd_io_reg;
|
|
|
|
if (!pdata->vreg_data)
|
|
return ret;
|
|
|
|
vdd_io_reg = pdata->vreg_data->vdd_io_data;
|
|
if (vdd_io_reg && vdd_io_reg->is_enabled) {
|
|
switch (level) {
|
|
case VDD_IO_LOW:
|
|
set_level = vdd_io_reg->low_vol_level;
|
|
break;
|
|
case VDD_IO_HIGH:
|
|
set_level = vdd_io_reg->high_vol_level;
|
|
break;
|
|
case VDD_IO_SET_LEVEL:
|
|
set_level = voltage_level;
|
|
break;
|
|
default:
|
|
pr_err("%s: invalid argument level = %d",
|
|
__func__, level);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
ret = sdhci_msm_vreg_set_voltage(vdd_io_reg, set_level,
|
|
set_level);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Acquire spin-lock host->lock before calling this function
|
|
*/
|
|
static void sdhci_msm_cfg_sdiowakeup_gpio_irq(struct sdhci_host *host,
|
|
bool enable)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
if (enable && !msm_host->is_sdiowakeup_enabled)
|
|
enable_irq(msm_host->pdata->sdiowakeup_irq);
|
|
else if (!enable && msm_host->is_sdiowakeup_enabled)
|
|
disable_irq_nosync(msm_host->pdata->sdiowakeup_irq);
|
|
else
|
|
dev_warn(&msm_host->pdev->dev, "%s: wakeup to config: %d curr: %d\n",
|
|
__func__, enable, msm_host->is_sdiowakeup_enabled);
|
|
msm_host->is_sdiowakeup_enabled = enable;
|
|
}
|
|
|
|
static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data)
|
|
{
|
|
struct sdhci_host *host = (struct sdhci_host *)data;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
unsigned long flags;
|
|
|
|
pr_debug("%s: irq (%d) received\n", __func__, irq);
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
msm_host->sdio_pending_processing = true;
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void sdhci_msm_dump_pwr_ctrl_regs(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
unsigned int irq_flags = 0;
|
|
struct irq_desc *pwr_irq_desc = irq_to_desc(msm_host->pwr_irq);
|
|
|
|
if (pwr_irq_desc)
|
|
irq_flags = ACCESS_PRIVATE(pwr_irq_desc->irq_data.common,
|
|
state_use_accessors);
|
|
|
|
pr_err("%s: PWRCTL_STATUS: 0x%08x | PWRCTL_MASK: 0x%08x | PWRCTL_CTL: 0x%08x, pwr isr state=0x%x\n",
|
|
mmc_hostname(host->mmc),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_MASK),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_CTL), irq_flags);
|
|
|
|
MMC_TRACE(host->mmc,
|
|
"%s: Sts: 0x%08x | Mask: 0x%08x | Ctrl: 0x%08x, pwr isr state=0x%x\n",
|
|
__func__,
|
|
sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS),
|
|
sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_MASK),
|
|
sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_CTL), irq_flags);
|
|
}
|
|
|
|
static int sdhci_msm_clear_pwrctl_status(struct sdhci_host *host, u8 value)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset = msm_host->offset;
|
|
int ret = 0, retry = 10;
|
|
|
|
/*
|
|
* There is a rare HW scenario where the first clear pulse could be
|
|
* lost when actual reset and clear/read of status register is
|
|
* happening at a time. Hence, retry for at least 10 times to make
|
|
* sure status register is cleared. Otherwise, this will result in
|
|
* a spurious power IRQ resulting in system instability.
|
|
*/
|
|
do {
|
|
if (retry == 0) {
|
|
pr_err("%s: Timedout clearing (0x%x) pwrctl status register\n",
|
|
mmc_hostname(host->mmc), value);
|
|
sdhci_msm_dump_pwr_ctrl_regs(host);
|
|
WARN_ON(1);
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Clear the PWRCTL_STATUS interrupt bits by writing to the
|
|
* corresponding bits in the PWRCTL_CLEAR register.
|
|
*/
|
|
sdhci_msm_writeb_relaxed(value, host,
|
|
msm_host_offset->CORE_PWRCTL_CLEAR);
|
|
/*
|
|
* SDHC has core_mem and hc_mem device memory and these memory
|
|
* addresses do not fall within 1KB region. Hence, any update
|
|
* to core_mem address space would require an mb() to ensure
|
|
* this gets completed before its next update to registers
|
|
* within hc_mem.
|
|
*/
|
|
mb();
|
|
retry--;
|
|
udelay(10);
|
|
} while (value & sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data)
|
|
{
|
|
struct sdhci_host *host = (struct sdhci_host *)data;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u8 irq_status = 0;
|
|
u8 irq_ack = 0;
|
|
int ret = 0;
|
|
int pwr_state = 0, io_level = 0;
|
|
unsigned long flags;
|
|
|
|
irq_status = sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS);
|
|
|
|
pr_debug("%s: Received IRQ(%d), status=0x%x\n",
|
|
mmc_hostname(msm_host->mmc), irq, irq_status);
|
|
|
|
sdhci_msm_clear_pwrctl_status(host, irq_status);
|
|
|
|
/* Handle BUS ON/OFF*/
|
|
if (irq_status & CORE_PWRCTL_BUS_ON) {
|
|
ret = sdhci_msm_setup_vreg(msm_host->pdata, true, false);
|
|
if (!ret) {
|
|
ret = sdhci_msm_setup_pins(msm_host->pdata, true);
|
|
if (!(irq_status & CORE_PWRCTL_IO_LOW))
|
|
ret |= sdhci_msm_set_vdd_io_vol(msm_host->pdata,
|
|
VDD_IO_HIGH, 0);
|
|
}
|
|
if (ret)
|
|
irq_ack |= CORE_PWRCTL_BUS_FAIL;
|
|
else
|
|
irq_ack |= CORE_PWRCTL_BUS_SUCCESS;
|
|
|
|
pwr_state = REQ_BUS_ON;
|
|
io_level = REQ_IO_HIGH;
|
|
}
|
|
if (irq_status & CORE_PWRCTL_BUS_OFF) {
|
|
if (msm_host->pltfm_init_done)
|
|
ret = sdhci_msm_setup_vreg(msm_host->pdata,
|
|
false, false);
|
|
if (!ret) {
|
|
ret = sdhci_msm_setup_pins(msm_host->pdata, false);
|
|
ret |= sdhci_msm_set_vdd_io_vol(msm_host->pdata,
|
|
VDD_IO_LOW, 0);
|
|
}
|
|
if (ret)
|
|
irq_ack |= CORE_PWRCTL_BUS_FAIL;
|
|
else
|
|
irq_ack |= CORE_PWRCTL_BUS_SUCCESS;
|
|
|
|
pwr_state = REQ_BUS_OFF;
|
|
io_level = REQ_IO_LOW;
|
|
}
|
|
/* Handle IO LOW/HIGH */
|
|
if (irq_status & CORE_PWRCTL_IO_LOW) {
|
|
/* Switch voltage Low */
|
|
ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_LOW, 0);
|
|
if (ret)
|
|
irq_ack |= CORE_PWRCTL_IO_FAIL;
|
|
else
|
|
irq_ack |= CORE_PWRCTL_IO_SUCCESS;
|
|
|
|
io_level = REQ_IO_LOW;
|
|
}
|
|
if (irq_status & CORE_PWRCTL_IO_HIGH) {
|
|
/* Switch voltage High */
|
|
ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_HIGH, 0);
|
|
if (ret)
|
|
irq_ack |= CORE_PWRCTL_IO_FAIL;
|
|
else
|
|
irq_ack |= CORE_PWRCTL_IO_SUCCESS;
|
|
|
|
io_level = REQ_IO_HIGH;
|
|
}
|
|
|
|
/* ACK status to the core */
|
|
sdhci_msm_writeb_relaxed(irq_ack, host,
|
|
msm_host_offset->CORE_PWRCTL_CTL);
|
|
/*
|
|
* SDHC has core_mem and hc_mem device memory and these memory
|
|
* addresses do not fall within 1KB region. Hence, any update to
|
|
* core_mem address space would require an mb() to ensure this gets
|
|
* completed before its next update to registers within hc_mem.
|
|
*/
|
|
mb();
|
|
if ((io_level & REQ_IO_HIGH) &&
|
|
(msm_host->caps_0 & CORE_3_0V_SUPPORT) &&
|
|
!msm_host->core_3_0v_support)
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) &
|
|
~CORE_IO_PAD_PWR_SWITCH), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
else if ((io_level & REQ_IO_LOW) ||
|
|
(msm_host->caps_0 & CORE_1_8V_SUPPORT))
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) |
|
|
CORE_IO_PAD_PWR_SWITCH), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
mb();
|
|
|
|
pr_debug("%s: Handled IRQ(%d), ret=%d, ack=0x%x\n",
|
|
mmc_hostname(msm_host->mmc), irq, ret, irq_ack);
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (pwr_state)
|
|
msm_host->curr_pwr_state = pwr_state;
|
|
if (io_level)
|
|
msm_host->curr_io_level = io_level;
|
|
complete(&msm_host->pwr_irq_completion);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static ssize_t
|
|
show_polling(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
int poll;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
poll = !!(host->mmc->caps & MMC_CAP_NEEDS_POLL);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", poll);
|
|
}
|
|
|
|
static ssize_t
|
|
store_polling(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
int value;
|
|
unsigned long flags;
|
|
|
|
if (!kstrtou32(buf, 0, &value)) {
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (value) {
|
|
host->mmc->caps |= MMC_CAP_NEEDS_POLL;
|
|
mmc_detect_change(host->mmc, 0);
|
|
} else {
|
|
host->mmc->caps &= ~MMC_CAP_NEEDS_POLL;
|
|
}
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
show_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n",
|
|
msm_host->msm_bus_vote.is_max_bw_needed);
|
|
}
|
|
|
|
static ssize_t
|
|
store_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
uint32_t value;
|
|
unsigned long flags;
|
|
|
|
if (!kstrtou32(buf, 0, &value)) {
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
msm_host->msm_bus_vote.is_max_bw_needed = !!value;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void sdhci_msm_check_power_status(struct sdhci_host *host, u32 req_type)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
unsigned long flags;
|
|
bool done = false;
|
|
u32 io_sig_sts = SWITCHABLE_SIGNALLING_VOL;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
pr_debug("%s: %s: request %d curr_pwr_state %x curr_io_level %x\n",
|
|
mmc_hostname(host->mmc), __func__, req_type,
|
|
msm_host->curr_pwr_state, msm_host->curr_io_level);
|
|
if (!msm_host->mci_removed)
|
|
io_sig_sts = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_GENERICS);
|
|
|
|
/*
|
|
* The IRQ for request type IO High/Low will be generated when -
|
|
* 1. SWITCHABLE_SIGNALLING_VOL is enabled in HW.
|
|
* 2. If 1 is true and when there is a state change in 1.8V enable
|
|
* bit (bit 3) of SDHCI_HOST_CONTROL2 register. The reset state of
|
|
* that bit is 0 which indicates 3.3V IO voltage. So, when MMC core
|
|
* layer tries to set it to 3.3V before card detection happens, the
|
|
* IRQ doesn't get triggered as there is no state change in this bit.
|
|
* The driver already handles this case by changing the IO voltage
|
|
* level to high as part of controller power up sequence. Hence, check
|
|
* for host->pwr to handle a case where IO voltage high request is
|
|
* issued even before controller power up.
|
|
*/
|
|
if (req_type & (REQ_IO_HIGH | REQ_IO_LOW)) {
|
|
if (!(io_sig_sts & SWITCHABLE_SIGNALLING_VOL) ||
|
|
((req_type & REQ_IO_HIGH) && !host->pwr)) {
|
|
pr_debug("%s: do not wait for power IRQ that never comes\n",
|
|
mmc_hostname(host->mmc));
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((req_type & msm_host->curr_pwr_state) ||
|
|
(req_type & msm_host->curr_io_level))
|
|
done = true;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
/*
|
|
* This is needed here to hanlde a case where IRQ gets
|
|
* triggered even before this function is called so that
|
|
* x->done counter of completion gets reset. Otherwise,
|
|
* next call to wait_for_completion returns immediately
|
|
* without actually waiting for the IRQ to be handled.
|
|
*/
|
|
if (done)
|
|
init_completion(&msm_host->pwr_irq_completion);
|
|
else if (!wait_for_completion_timeout(&msm_host->pwr_irq_completion,
|
|
msecs_to_jiffies(MSM_PWR_IRQ_TIMEOUT_MS))) {
|
|
__WARN_printf("%s: request(%d) timed out waiting for pwr_irq\n",
|
|
mmc_hostname(host->mmc), req_type);
|
|
MMC_TRACE(host->mmc,
|
|
"%s: request(%d) timed out waiting for pwr_irq\n",
|
|
__func__, req_type);
|
|
sdhci_msm_dump_pwr_ctrl_regs(host);
|
|
}
|
|
pr_debug("%s: %s: request %d done\n", mmc_hostname(host->mmc),
|
|
__func__, req_type);
|
|
}
|
|
|
|
static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u32 config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
if (enable) {
|
|
config |= CORE_CDR_EN;
|
|
config &= ~CORE_CDR_EXT_EN;
|
|
writel_relaxed(config, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
} else {
|
|
config &= ~CORE_CDR_EN;
|
|
config |= CORE_CDR_EXT_EN;
|
|
writel_relaxed(config, host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
}
|
|
}
|
|
|
|
static unsigned int sdhci_msm_max_segs(void)
|
|
{
|
|
return SDHCI_MSM_MAX_SEGMENTS;
|
|
}
|
|
|
|
static unsigned int sdhci_msm_get_min_clock(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
return msm_host->pdata->sup_clk_table[0];
|
|
}
|
|
|
|
static unsigned int sdhci_msm_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int max_clk_index = msm_host->pdata->sup_clk_cnt;
|
|
|
|
return msm_host->pdata->sup_clk_table[max_clk_index - 1];
|
|
}
|
|
|
|
static unsigned int sdhci_msm_get_sup_clk_rate(struct sdhci_host *host,
|
|
u32 req_clk)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
unsigned int sel_clk = -1;
|
|
unsigned char cnt;
|
|
|
|
if (req_clk < sdhci_msm_get_min_clock(host)) {
|
|
sel_clk = sdhci_msm_get_min_clock(host);
|
|
return sel_clk;
|
|
}
|
|
|
|
for (cnt = 0; cnt < msm_host->pdata->sup_clk_cnt; cnt++) {
|
|
if (msm_host->pdata->sup_clk_table[cnt] > req_clk) {
|
|
break;
|
|
} else if (msm_host->pdata->sup_clk_table[cnt] == req_clk) {
|
|
sel_clk = msm_host->pdata->sup_clk_table[cnt];
|
|
break;
|
|
}
|
|
sel_clk = msm_host->pdata->sup_clk_table[cnt];
|
|
}
|
|
return sel_clk;
|
|
}
|
|
|
|
static long sdhci_msm_get_bus_aggr_clk_rate(struct sdhci_host *host,
|
|
u32 apps_clk)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
long sel_clk = -1;
|
|
unsigned char cnt;
|
|
|
|
if (msm_host->pdata->bus_clk_cnt != msm_host->pdata->sup_clk_cnt) {
|
|
pr_err("%s: %s: mismatch between bus_clk_cnt(%u) and apps_clk_cnt(%u)\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
(unsigned int)msm_host->pdata->bus_clk_cnt,
|
|
(unsigned int)msm_host->pdata->sup_clk_cnt);
|
|
return msm_host->pdata->bus_clk_table[0];
|
|
}
|
|
if (apps_clk == sdhci_msm_get_min_clock(host)) {
|
|
sel_clk = msm_host->pdata->bus_clk_table[0];
|
|
return sel_clk;
|
|
}
|
|
|
|
for (cnt = 0; cnt < msm_host->pdata->bus_clk_cnt; cnt++) {
|
|
if (msm_host->pdata->sup_clk_table[cnt] > apps_clk)
|
|
break;
|
|
sel_clk = msm_host->pdata->bus_clk_table[cnt];
|
|
}
|
|
return sel_clk;
|
|
}
|
|
|
|
static void sdhci_msm_registers_save(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
if (!msm_host->regs_restore.is_supported)
|
|
return;
|
|
|
|
msm_host->regs_restore.vendor_func = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
msm_host->regs_restore.vendor_pwrctl_mask =
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_PWRCTL_MASK);
|
|
msm_host->regs_restore.vendor_func2 =
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
msm_host->regs_restore.vendor_func3 =
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3);
|
|
msm_host->regs_restore.hc_2c_2e =
|
|
sdhci_readl(host, SDHCI_CLOCK_CONTROL);
|
|
msm_host->regs_restore.hc_3c_3e =
|
|
sdhci_readl(host, SDHCI_AUTO_CMD_STATUS);
|
|
msm_host->regs_restore.vendor_pwrctl_ctl =
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_PWRCTL_CTL);
|
|
msm_host->regs_restore.hc_38_3a =
|
|
sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
|
|
msm_host->regs_restore.hc_34_36 =
|
|
sdhci_readl(host, SDHCI_INT_ENABLE);
|
|
msm_host->regs_restore.hc_28_2a =
|
|
sdhci_readl(host, SDHCI_HOST_CONTROL);
|
|
msm_host->regs_restore.vendor_caps_0 =
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_CAPABILITIES0);
|
|
msm_host->regs_restore.hc_caps_1 =
|
|
sdhci_readl(host, SDHCI_CAPABILITIES_1);
|
|
msm_host->regs_restore.testbus_config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
msm_host->regs_restore.dll_config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
msm_host->regs_restore.dll_config2 = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2);
|
|
msm_host->regs_restore.dll_config = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
msm_host->regs_restore.dll_config2 = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_2);
|
|
msm_host->regs_restore.dll_config3 = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3);
|
|
msm_host->regs_restore.dll_usr_ctl = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_USR_CTL);
|
|
|
|
msm_host->regs_restore.is_valid = true;
|
|
|
|
pr_debug("%s: %s: registers saved. PWRCTL_MASK = 0x%x\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_PWRCTL_MASK));
|
|
}
|
|
|
|
static void sdhci_msm_registers_restore(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
u8 irq_status;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
struct mmc_ios ios = host->mmc->ios;
|
|
|
|
if (!msm_host->regs_restore.is_supported ||
|
|
!msm_host->regs_restore.is_valid)
|
|
return;
|
|
|
|
writel_relaxed(0, host->ioaddr + msm_host_offset->CORE_PWRCTL_MASK);
|
|
writel_relaxed(msm_host->regs_restore.vendor_func, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
writel_relaxed(msm_host->regs_restore.vendor_func2,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
writel_relaxed(msm_host->regs_restore.vendor_func3,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_2c_2e,
|
|
SDHCI_CLOCK_CONTROL);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_3c_3e,
|
|
SDHCI_AUTO_CMD_STATUS);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_38_3a,
|
|
SDHCI_SIGNAL_ENABLE);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_34_36,
|
|
SDHCI_INT_ENABLE);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_28_2a,
|
|
SDHCI_HOST_CONTROL);
|
|
writel_relaxed(msm_host->regs_restore.vendor_caps_0,
|
|
host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_CAPABILITIES0);
|
|
sdhci_writel(host, msm_host->regs_restore.hc_caps_1,
|
|
SDHCI_CAPABILITIES_1);
|
|
writel_relaxed(msm_host->regs_restore.testbus_config, host->ioaddr +
|
|
msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
msm_host->regs_restore.is_valid = false;
|
|
|
|
/*
|
|
* Clear the PWRCTL_STATUS register.
|
|
* There is a rare HW scenario where the first clear pulse could be
|
|
* lost when actual reset and clear/read of status register is
|
|
* happening at a time. Hence, retry for at least 10 times to make
|
|
* sure status register is cleared. Otherwise, this will result in
|
|
* a spurious power IRQ resulting in system instability.
|
|
*/
|
|
irq_status = sdhci_msm_readb_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS);
|
|
|
|
sdhci_msm_clear_pwrctl_status(host, irq_status);
|
|
|
|
writel_relaxed(msm_host->regs_restore.vendor_pwrctl_ctl,
|
|
host->ioaddr + msm_host_offset->CORE_PWRCTL_CTL);
|
|
writel_relaxed(msm_host->regs_restore.vendor_pwrctl_mask,
|
|
host->ioaddr + msm_host_offset->CORE_PWRCTL_MASK);
|
|
|
|
if (((ios.timing == MMC_TIMING_MMC_HS400) ||
|
|
(ios.timing == MMC_TIMING_MMC_HS200) ||
|
|
(ios.timing == MMC_TIMING_UHS_SDR104))
|
|
&& (ios.clock > CORE_FREQ_100MHZ)) {
|
|
writel_relaxed(msm_host->regs_restore.dll_config2,
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG_2);
|
|
writel_relaxed(msm_host->regs_restore.dll_config3,
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG_3);
|
|
writel_relaxed(msm_host->regs_restore.dll_usr_ctl,
|
|
host->ioaddr + msm_host_offset->CORE_DLL_USR_CTL);
|
|
writel_relaxed(msm_host->regs_restore.dll_config &
|
|
~(CORE_DLL_RST | CORE_DLL_PDN),
|
|
host->ioaddr + msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
msm_init_cm_dll(host, DLL_INIT_FROM_CX_COLLAPSE_EXIT);
|
|
msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase);
|
|
}
|
|
|
|
pr_debug("%s: %s: registers restored. PWRCTL_MASK = 0x%x\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_PWRCTL_MASK));
|
|
}
|
|
|
|
static int sdhci_msm_enable_controller_clock(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int rc = 0;
|
|
|
|
if (atomic_read(&msm_host->controller_clock))
|
|
return 0;
|
|
|
|
sdhci_msm_bus_voting(host, 1);
|
|
|
|
if (!IS_ERR(msm_host->pclk)) {
|
|
rc = clk_prepare_enable(msm_host->pclk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the pclk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto remove_vote;
|
|
}
|
|
}
|
|
|
|
if (!IS_ERR(msm_host->bus_aggr_clk)) {
|
|
rc = clk_prepare_enable(msm_host->bus_aggr_clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the bus aggr clk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_pclk;
|
|
}
|
|
}
|
|
|
|
rc = clk_prepare_enable(msm_host->clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the host-clk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_bus_aggr_clk;
|
|
}
|
|
|
|
if (!IS_ERR(msm_host->ice_clk)) {
|
|
rc = clk_prepare_enable(msm_host->ice_clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the ice-clk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_host_clk;
|
|
}
|
|
}
|
|
atomic_set(&msm_host->controller_clock, 1);
|
|
pr_debug("%s: %s: enabled controller clock\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
sdhci_msm_registers_restore(host);
|
|
goto out;
|
|
|
|
disable_host_clk:
|
|
if (!IS_ERR(msm_host->clk))
|
|
clk_disable_unprepare(msm_host->clk);
|
|
disable_bus_aggr_clk:
|
|
if (!IS_ERR(msm_host->bus_aggr_clk))
|
|
clk_disable_unprepare(msm_host->bus_aggr_clk);
|
|
disable_pclk:
|
|
if (!IS_ERR(msm_host->pclk))
|
|
clk_disable_unprepare(msm_host->pclk);
|
|
remove_vote:
|
|
if (msm_host->msm_bus_vote.client_handle)
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static void sdhci_msm_disable_controller_clock(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
if (atomic_read(&msm_host->controller_clock)) {
|
|
sdhci_msm_registers_save(host);
|
|
if (!IS_ERR(msm_host->clk))
|
|
clk_disable_unprepare(msm_host->clk);
|
|
if (!IS_ERR(msm_host->ice_clk))
|
|
clk_disable_unprepare(msm_host->ice_clk);
|
|
if (!IS_ERR(msm_host->bus_aggr_clk))
|
|
clk_disable_unprepare(msm_host->bus_aggr_clk);
|
|
if (!IS_ERR(msm_host->pclk))
|
|
clk_disable_unprepare(msm_host->pclk);
|
|
sdhci_msm_bus_voting(host, 0);
|
|
atomic_set(&msm_host->controller_clock, 0);
|
|
pr_debug("%s: %s: disabled controller clock\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
}
|
|
}
|
|
|
|
static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int rc = 0;
|
|
|
|
if (enable && !atomic_read(&msm_host->clks_on)) {
|
|
pr_debug("%s: request to enable clocks\n",
|
|
mmc_hostname(host->mmc));
|
|
|
|
/*
|
|
* The bus-width or the clock rate might have changed
|
|
* after controller clocks are enbaled, update bus vote
|
|
* in such case.
|
|
*/
|
|
if (atomic_read(&msm_host->controller_clock))
|
|
sdhci_msm_bus_voting(host, 1);
|
|
|
|
rc = sdhci_msm_enable_controller_clock(host);
|
|
if (rc)
|
|
goto remove_vote;
|
|
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_clk)) {
|
|
rc = clk_prepare_enable(msm_host->bus_clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the bus-clock with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_controller_clk;
|
|
}
|
|
}
|
|
if (!IS_ERR(msm_host->ff_clk)) {
|
|
rc = clk_prepare_enable(msm_host->ff_clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the ff_clk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_bus_clk;
|
|
}
|
|
}
|
|
if (!IS_ERR(msm_host->sleep_clk)) {
|
|
rc = clk_prepare_enable(msm_host->sleep_clk);
|
|
if (rc) {
|
|
pr_err("%s: %s: failed to enable the sleep_clk with error %d\n",
|
|
mmc_hostname(host->mmc), __func__, rc);
|
|
goto disable_ff_clk;
|
|
}
|
|
}
|
|
mb();
|
|
|
|
} else if (!enable && atomic_read(&msm_host->clks_on)) {
|
|
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
|
|
mb();
|
|
/*
|
|
* During 1.8V signal switching the clock source must
|
|
* still be ON as it requires accessing SDHC
|
|
* registers (SDHCi host control2 register bit 3 must
|
|
* be written and polled after stopping the SDCLK).
|
|
*/
|
|
if (host->mmc->card_clock_off)
|
|
return 0;
|
|
pr_debug("%s: request to disable clocks\n",
|
|
mmc_hostname(host->mmc));
|
|
if (!IS_ERR_OR_NULL(msm_host->sleep_clk))
|
|
clk_disable_unprepare(msm_host->sleep_clk);
|
|
if (!IS_ERR_OR_NULL(msm_host->ff_clk))
|
|
clk_disable_unprepare(msm_host->ff_clk);
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_clk))
|
|
clk_disable_unprepare(msm_host->bus_clk);
|
|
sdhci_msm_disable_controller_clock(host);
|
|
}
|
|
atomic_set(&msm_host->clks_on, enable);
|
|
goto out;
|
|
disable_ff_clk:
|
|
if (!IS_ERR_OR_NULL(msm_host->ff_clk))
|
|
clk_disable_unprepare(msm_host->ff_clk);
|
|
disable_bus_clk:
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_clk))
|
|
clk_disable_unprepare(msm_host->bus_clk);
|
|
disable_controller_clk:
|
|
if (!IS_ERR_OR_NULL(msm_host->clk))
|
|
clk_disable_unprepare(msm_host->clk);
|
|
if (!IS_ERR(msm_host->ice_clk))
|
|
clk_disable_unprepare(msm_host->ice_clk);
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_aggr_clk))
|
|
clk_disable_unprepare(msm_host->bus_aggr_clk);
|
|
if (!IS_ERR_OR_NULL(msm_host->pclk))
|
|
clk_disable_unprepare(msm_host->pclk);
|
|
atomic_set(&msm_host->controller_clock, 0);
|
|
remove_vote:
|
|
if (msm_host->msm_bus_vote.client_handle)
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
{
|
|
int rc;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
struct mmc_card *card = host->mmc->card;
|
|
struct mmc_ios curr_ios = host->mmc->ios;
|
|
u32 sup_clock, ddr_clock, dll_lock;
|
|
long bus_clk_rate;
|
|
bool curr_pwrsave;
|
|
|
|
if (!clock) {
|
|
/*
|
|
* disable pwrsave to ensure clock is not auto-gated until
|
|
* the rate is >400KHz (initialization complete).
|
|
*/
|
|
writel_relaxed(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) &
|
|
~CORE_CLK_PWRSAVE, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
sdhci_msm_prepare_clocks(host, false);
|
|
host->clock = clock;
|
|
goto out;
|
|
}
|
|
|
|
rc = sdhci_msm_prepare_clocks(host, true);
|
|
if (rc)
|
|
goto out;
|
|
|
|
curr_pwrsave = !!(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) & CORE_CLK_PWRSAVE);
|
|
if ((clock > 400000) &&
|
|
!curr_pwrsave && card && mmc_host_may_gate_card(card))
|
|
writel_relaxed(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
| CORE_CLK_PWRSAVE, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
/*
|
|
* Disable pwrsave for a newly added card if doesn't allow clock
|
|
* gating.
|
|
*/
|
|
else if (curr_pwrsave && card && !mmc_host_may_gate_card(card))
|
|
writel_relaxed(readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
& ~CORE_CLK_PWRSAVE, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
|
|
sup_clock = sdhci_msm_get_sup_clk_rate(host, clock);
|
|
if ((curr_ios.timing == MMC_TIMING_UHS_DDR50) ||
|
|
(curr_ios.timing == MMC_TIMING_MMC_DDR52) ||
|
|
(curr_ios.timing == MMC_TIMING_MMC_HS400)) {
|
|
/*
|
|
* The SDHC requires internal clock frequency to be double the
|
|
* actual clock that will be set for DDR mode. The controller
|
|
* uses the faster clock(100/400MHz) for some of its parts and
|
|
* send the actual required clock (50/200MHz) to the card.
|
|
*/
|
|
ddr_clock = clock * 2;
|
|
sup_clock = sdhci_msm_get_sup_clk_rate(host,
|
|
ddr_clock);
|
|
}
|
|
|
|
/*
|
|
* In general all timing modes are controlled via UHS mode select in
|
|
* Host Control2 register. eMMC specific HS200/HS400 doesn't have
|
|
* their respective modes defined here, hence we use these values.
|
|
*
|
|
* HS200 - SDR104 (Since they both are equivalent in functionality)
|
|
* HS400 - This involves multiple configurations
|
|
* Initially SDR104 - when tuning is required as HS200
|
|
* Then when switching to DDR @ 400MHz (HS400) we use
|
|
* the vendor specific HC_SELECT_IN to control the mode.
|
|
*
|
|
* In addition to controlling the modes we also need to select the
|
|
* correct input clock for DLL depending on the mode.
|
|
*
|
|
* HS400 - divided clock (free running MCLK/2)
|
|
* All other modes - default (free running MCLK)
|
|
*/
|
|
if (curr_ios.timing == MMC_TIMING_MMC_HS400) {
|
|
/* Select the divided clock (free running MCLK/2) */
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
& ~CORE_HC_MCLK_SEL_MASK)
|
|
| CORE_HC_MCLK_SEL_HS400), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
/*
|
|
* Select HS400 mode using the HC_SELECT_IN from VENDOR SPEC
|
|
* register
|
|
*/
|
|
if ((msm_host->tuning_done ||
|
|
(card && mmc_card_strobe(card) &&
|
|
msm_host->enhanced_strobe)) &&
|
|
!msm_host->calibration_done) {
|
|
/*
|
|
* Write 0x6 to HC_SELECT_IN and 1 to HC_SELECT_IN_EN
|
|
* field in VENDOR_SPEC_FUNC
|
|
*/
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
| CORE_HC_SELECT_IN_HS400
|
|
| CORE_HC_SELECT_IN_EN), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
}
|
|
/*
|
|
* After MCLK ugating, toggle the FIFO write clock to get
|
|
* the FIFO pointers and flags to valid state.
|
|
*/
|
|
if (msm_host->tuning_done ||
|
|
(card && mmc_card_strobe(card) &&
|
|
msm_host->enhanced_strobe)) {
|
|
/*
|
|
* set HC_REG_DLL_CONFIG_3[1] to select MCLK as
|
|
* DLL input clock
|
|
*/
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3))
|
|
| RCLK_TOGGLE), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3);
|
|
/* ensure above write as toggling same bit quickly */
|
|
wmb();
|
|
udelay(2);
|
|
/*
|
|
* clear HC_REG_DLL_CONFIG_3[1] to select RCLK as
|
|
* DLL input clock
|
|
*/
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3))
|
|
& ~RCLK_TOGGLE), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3);
|
|
}
|
|
if (!host->mmc->ios.old_rate && !msm_host->use_cdclp533) {
|
|
/*
|
|
* Poll on DLL_LOCK and DDR_DLL_LOCK bits in
|
|
* CORE_DLL_STATUS to be set. This should get set
|
|
* with in 15 us at 200 MHz.
|
|
*/
|
|
rc = readl_poll_timeout(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_STATUS,
|
|
dll_lock, (dll_lock & (CORE_DLL_LOCK |
|
|
CORE_DDR_DLL_LOCK)), 10, 1000);
|
|
if (rc == -ETIMEDOUT)
|
|
pr_err("%s: Unable to get DLL_LOCK/DDR_DLL_LOCK, dll_status: 0x%08x\n",
|
|
mmc_hostname(host->mmc),
|
|
dll_lock);
|
|
}
|
|
} else {
|
|
if (!msm_host->use_cdclp533)
|
|
/* set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3 */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3)
|
|
& ~CORE_PWRSAVE_DLL), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3);
|
|
|
|
/* Select the default clock (free running MCLK) */
|
|
writel_relaxed(((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
& ~CORE_HC_MCLK_SEL_MASK)
|
|
| CORE_HC_MCLK_SEL_DFLT), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
|
|
/*
|
|
* Disable HC_SELECT_IN to be able to use the UHS mode select
|
|
* configuration from Host Control2 register for all other
|
|
* modes.
|
|
*
|
|
* Write 0 to HC_SELECT_IN and HC_SELECT_IN_EN field
|
|
* in VENDOR_SPEC_FUNC
|
|
*/
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC)
|
|
& ~CORE_HC_SELECT_IN_EN
|
|
& ~CORE_HC_SELECT_IN_MASK), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
}
|
|
mb();
|
|
|
|
if (sup_clock != msm_host->clk_rate) {
|
|
pr_debug("%s: %s: setting clk rate to %u\n",
|
|
mmc_hostname(host->mmc), __func__, sup_clock);
|
|
rc = clk_set_rate(msm_host->clk, sup_clock);
|
|
if (rc) {
|
|
pr_err("%s: %s: Failed to set rate %u for host-clk : %d\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
sup_clock, rc);
|
|
goto out;
|
|
}
|
|
msm_host->clk_rate = sup_clock;
|
|
host->clock = clock;
|
|
|
|
if (!IS_ERR(msm_host->bus_aggr_clk) &&
|
|
msm_host->pdata->bus_clk_cnt) {
|
|
bus_clk_rate = sdhci_msm_get_bus_aggr_clk_rate(host,
|
|
sup_clock);
|
|
if (bus_clk_rate >= 0) {
|
|
rc = clk_set_rate(msm_host->bus_aggr_clk,
|
|
bus_clk_rate);
|
|
if (rc) {
|
|
pr_err("%s: %s: Failed to set rate %ld for bus-aggr-clk : %d\n",
|
|
mmc_hostname(host->mmc),
|
|
__func__, bus_clk_rate, rc);
|
|
goto out;
|
|
}
|
|
} else {
|
|
pr_err("%s: %s: Unsupported apps clk rate %u for bus-aggr-clk, err: %ld\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
sup_clock, bus_clk_rate);
|
|
}
|
|
}
|
|
|
|
/* Configure pinctrl drive type according to
|
|
* current clock rate
|
|
*/
|
|
rc = sdhci_msm_config_pinctrl_drv_type(msm_host->pdata, clock);
|
|
if (rc)
|
|
pr_err("%s: %s: Failed to set pinctrl drive type for clock rate %u (%d)\n",
|
|
mmc_hostname(host->mmc), __func__,
|
|
clock, rc);
|
|
|
|
/*
|
|
* Update the bus vote in case of frequency change due to
|
|
* clock scaling.
|
|
*/
|
|
sdhci_msm_bus_voting(host, 1);
|
|
}
|
|
out:
|
|
sdhci_set_clock(host, clock);
|
|
}
|
|
|
|
static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host,
|
|
unsigned int uhs)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
u16 ctrl_2;
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
/* Select Bus Speed Mode for host */
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
if ((uhs == MMC_TIMING_MMC_HS400) ||
|
|
(uhs == MMC_TIMING_MMC_HS200) ||
|
|
(uhs == MMC_TIMING_UHS_SDR104))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
|
else if (uhs == MMC_TIMING_UHS_SDR12)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
else if (uhs == MMC_TIMING_UHS_SDR25)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
else if (uhs == MMC_TIMING_UHS_SDR50)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
|
else if ((uhs == MMC_TIMING_UHS_DDR50) ||
|
|
(uhs == MMC_TIMING_MMC_DDR52))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
|
/*
|
|
* When clock frquency is less than 100MHz, the feedback clock must be
|
|
* provided and DLL must not be used so that tuning can be skipped. To
|
|
* provide feedback clock, the mode selection can be any value less
|
|
* than 3'b011 in bits [2:0] of HOST CONTROL2 register.
|
|
*/
|
|
if (host->clock <= CORE_FREQ_100MHZ) {
|
|
if ((uhs == MMC_TIMING_MMC_HS400) ||
|
|
(uhs == MMC_TIMING_MMC_HS200) ||
|
|
(uhs == MMC_TIMING_UHS_SDR104))
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
|
|
/*
|
|
* Make sure DLL is disabled when not required
|
|
*
|
|
* Write 1 to DLL_RST bit of DLL_CONFIG register
|
|
*/
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
| CORE_DLL_RST), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
|
|
/* Write 1 to DLL_PDN bit of DLL_CONFIG register */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG)
|
|
| CORE_DLL_PDN), host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG);
|
|
mb();
|
|
|
|
/*
|
|
* The DLL needs to be restored and CDCLP533 recalibrated
|
|
* when the clock frequency is set back to 400MHz.
|
|
*/
|
|
msm_host->calibration_done = false;
|
|
}
|
|
|
|
pr_debug("%s: %s-clock:%u uhs mode:%u ctrl_2:0x%x\n",
|
|
mmc_hostname(host->mmc), __func__, host->clock, uhs, ctrl_2);
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
|
|
}
|
|
|
|
#define MAX_TEST_BUS 60
|
|
#define DRV_NAME "cmdq-host"
|
|
static void sdhci_msm_cmdq_dump_debug_ram(struct sdhci_host *host)
|
|
{
|
|
int i = 0;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
struct cmdq_host *cq_host = host->cq_host;
|
|
|
|
u32 version = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_VERSION);
|
|
u16 minor = version & CORE_VERSION_TARGET_MASK;
|
|
/* registers offset changed starting from 4.2.0 */
|
|
int offset = minor >= SDHCI_MSM_VER_420 ? 0 : 0x48;
|
|
|
|
if (cq_host->offset_changed)
|
|
offset += CQ_V5_VENDOR_CFG;
|
|
pr_err("---- Debug RAM dump ----\n");
|
|
pr_err(DRV_NAME ": Debug RAM wrap-around: 0x%08x | Debug RAM overlap: 0x%08x\n",
|
|
cmdq_readl(cq_host, CQ_CMD_DBG_RAM_WA + offset),
|
|
cmdq_readl(cq_host, CQ_CMD_DBG_RAM_OL + offset));
|
|
|
|
while (i < 16) {
|
|
pr_err(DRV_NAME ": Debug RAM dump [%d]: 0x%08x\n", i,
|
|
cmdq_readl(cq_host, CQ_CMD_DBG_RAM + offset + (4 * i)));
|
|
i++;
|
|
}
|
|
pr_err("-------------------------\n");
|
|
}
|
|
|
|
static void sdhci_msm_cache_debug_data(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_debug_data *cached_data = &msm_host->cached_data;
|
|
|
|
memcpy(&cached_data->copy_mmc, msm_host->mmc,
|
|
sizeof(struct mmc_host));
|
|
if (msm_host->mmc->card)
|
|
memcpy(&cached_data->copy_card, msm_host->mmc->card,
|
|
sizeof(struct mmc_card));
|
|
memcpy(&cached_data->copy_host, host,
|
|
sizeof(struct sdhci_host));
|
|
}
|
|
|
|
void sdhci_msm_dump_vendor_regs(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
int tbsel, tbsel2;
|
|
int i, index = 0;
|
|
u32 test_bus_val = 0;
|
|
u32 debug_reg[MAX_TEST_BUS] = {0};
|
|
|
|
sdhci_msm_cache_debug_data(host);
|
|
pr_info("----------- VENDOR REGISTER DUMP -----------\n");
|
|
if (host->cq_host)
|
|
sdhci_msm_cmdq_dump_debug_ram(host);
|
|
|
|
MMC_TRACE(host->mmc, "Data cnt: 0x%08x | Fifo cnt: 0x%08x\n",
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_DATA_CNT),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_FIFO_CNT));
|
|
pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n",
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_DATA_CNT),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_FIFO_CNT),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_STATUS));
|
|
pr_info("DLL sts: 0x%08x | DLL cfg: 0x%08x | DLL cfg2: 0x%08x\n",
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_STATUS),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_DLL_CONFIG_2));
|
|
pr_info("DLL cfg3: 0x%08x | DLL usr ctl: 0x%08x | DDR cfg: 0x%08x\n",
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_CONFIG_3),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_DLL_USR_CTL),
|
|
sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_DDR_CONFIG));
|
|
pr_info("SDCC ver: 0x%08x | Vndr adma err : addr0: 0x%08x addr1: 0x%08x\n",
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_MCI_VERSION),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_ADMA_ERR_ADDR0),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_ADMA_ERR_ADDR1));
|
|
pr_info("Vndr func: 0x%08x | Vndr func2 : 0x%08x Vndr func3: 0x%08x\n",
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2),
|
|
readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3));
|
|
/*
|
|
* tbsel indicates [2:0] bits and tbsel2 indicates [7:4] bits
|
|
* of CORE_TESTBUS_CONFIG register.
|
|
*
|
|
* To select test bus 0 to 7 use tbsel and to select any test bus
|
|
* above 7 use (tbsel2 | tbsel) to get the test bus number. For eg,
|
|
* to select test bus 14, write 0x1E to CORE_TESTBUS_CONFIG register
|
|
* i.e., tbsel2[7:4] = 0001, tbsel[2:0] = 110.
|
|
*/
|
|
for (tbsel2 = 0; tbsel2 < 7; tbsel2++) {
|
|
for (tbsel = 0; tbsel < 8; tbsel++) {
|
|
if (index >= MAX_TEST_BUS)
|
|
break;
|
|
test_bus_val =
|
|
(tbsel2 << msm_host_offset->CORE_TESTBUS_SEL2_BIT) |
|
|
tbsel | msm_host_offset->CORE_TESTBUS_ENA;
|
|
sdhci_msm_writel_relaxed(test_bus_val, host,
|
|
msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
debug_reg[index++] = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_SDCC_DEBUG_REG);
|
|
}
|
|
}
|
|
for (i = 0; i < MAX_TEST_BUS; i = i + 4)
|
|
pr_info(" Test bus[%d to %d]: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
|
i, i + 3, debug_reg[i], debug_reg[i+1],
|
|
debug_reg[i+2], debug_reg[i+3]);
|
|
pr_info("Phase_on_tuning : 0x%x.\n", msm_host->phase_on_tuning);
|
|
}
|
|
|
|
static void sdhci_msm_reset(struct sdhci_host *host, u8 mask)
|
|
{
|
|
sdhci_reset(host, mask);
|
|
}
|
|
|
|
/*
|
|
* sdhci_msm_enhanced_strobe_mask :-
|
|
* Before running CMDQ transfers in HS400 Enhanced Strobe mode,
|
|
* SW should write 3 to
|
|
* HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register.
|
|
* The default reset value of this register is 2.
|
|
*/
|
|
static void sdhci_msm_enhanced_strobe_mask(struct sdhci_host *host, bool set)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
if (!msm_host->enhanced_strobe ||
|
|
!mmc_card_strobe(msm_host->mmc->card)) {
|
|
pr_debug("%s: host/card does not support hs400 enhanced strobe\n",
|
|
mmc_hostname(host->mmc));
|
|
return;
|
|
}
|
|
|
|
if (set) {
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3)
|
|
| CORE_CMDEN_HS400_INPUT_MASK_CNT),
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC3);
|
|
} else {
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3)
|
|
& ~CORE_CMDEN_HS400_INPUT_MASK_CNT),
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC3);
|
|
}
|
|
}
|
|
|
|
static void sdhci_msm_clear_set_dumpregs(struct sdhci_host *host, bool set)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
if (set) {
|
|
sdhci_msm_writel_relaxed(msm_host_offset->CORE_TESTBUS_ENA,
|
|
host, msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
} else {
|
|
u32 value;
|
|
|
|
value = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
value &= ~(msm_host_offset->CORE_TESTBUS_ENA);
|
|
sdhci_msm_writel_relaxed(value, host,
|
|
msm_host_offset->CORE_TESTBUS_CONFIG);
|
|
}
|
|
}
|
|
|
|
void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable)
|
|
{
|
|
u32 vendor_func2;
|
|
unsigned long timeout;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
vendor_func2 = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
|
|
if (enable) {
|
|
writel_relaxed(vendor_func2 | HC_SW_RST_REQ, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
timeout = 10000;
|
|
while (readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2) & HC_SW_RST_REQ) {
|
|
if (timeout == 0) {
|
|
pr_info("%s: Applying wait idle disable workaround\n",
|
|
mmc_hostname(host->mmc));
|
|
/*
|
|
* Apply the reset workaround to not wait for
|
|
* pending data transfers on AXI before
|
|
* resetting the controller. This could be
|
|
* risky if the transfers were stuck on the
|
|
* AXI bus.
|
|
*/
|
|
vendor_func2 = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
writel_relaxed(vendor_func2 |
|
|
HC_SW_RST_WAIT_IDLE_DIS, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
host->reset_wa_t = ktime_get();
|
|
return;
|
|
}
|
|
timeout--;
|
|
udelay(10);
|
|
}
|
|
pr_info("%s: waiting for SW_RST_REQ is successful\n",
|
|
mmc_hostname(host->mmc));
|
|
} else {
|
|
writel_relaxed(vendor_func2 & ~HC_SW_RST_WAIT_IDLE_DIS,
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
}
|
|
}
|
|
|
|
static void sdhci_msm_pm_qos_irq_unvote_work(struct work_struct *work)
|
|
{
|
|
struct sdhci_msm_pm_qos_irq *pm_qos_irq =
|
|
container_of(work, struct sdhci_msm_pm_qos_irq,
|
|
unvote_work.work);
|
|
|
|
if (atomic_read(&pm_qos_irq->counter))
|
|
return;
|
|
|
|
pm_qos_irq->latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&pm_qos_irq->req, pm_qos_irq->latency);
|
|
}
|
|
|
|
void sdhci_msm_pm_qos_irq_vote(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_pm_qos_latency *latency =
|
|
&msm_host->pdata->pm_qos_data.irq_latency;
|
|
int counter;
|
|
|
|
if (!msm_host->pm_qos_irq.enabled)
|
|
return;
|
|
|
|
counter = atomic_inc_return(&msm_host->pm_qos_irq.counter);
|
|
/* Make sure to update the voting in case power policy has changed */
|
|
if (msm_host->pm_qos_irq.latency == latency->latency[host->power_policy]
|
|
&& counter > 1)
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&msm_host->pm_qos_irq.unvote_work);
|
|
msm_host->pm_qos_irq.latency = latency->latency[host->power_policy];
|
|
pm_qos_update_request(&msm_host->pm_qos_irq.req,
|
|
msm_host->pm_qos_irq.latency);
|
|
}
|
|
|
|
void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int counter;
|
|
|
|
if (!msm_host->pm_qos_irq.enabled)
|
|
return;
|
|
|
|
if (atomic_read(&msm_host->pm_qos_irq.counter)) {
|
|
counter = atomic_dec_return(&msm_host->pm_qos_irq.counter);
|
|
} else {
|
|
WARN(1, "attempt to decrement pm_qos_irq.counter when it's 0");
|
|
return;
|
|
}
|
|
|
|
if (counter)
|
|
return;
|
|
|
|
if (async) {
|
|
queue_delayed_work(msm_host->pm_qos_wq,
|
|
&msm_host->pm_qos_irq.unvote_work,
|
|
msecs_to_jiffies(QOS_REMOVE_DELAY_MS));
|
|
return;
|
|
}
|
|
|
|
msm_host->pm_qos_irq.latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&msm_host->pm_qos_irq.req,
|
|
msm_host->pm_qos_irq.latency);
|
|
}
|
|
|
|
static ssize_t
|
|
sdhci_msm_pm_qos_irq_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_pm_qos_irq *irq = &msm_host->pm_qos_irq;
|
|
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"IRQ PM QoS: enabled=%d, counter=%d, latency=%d\n",
|
|
irq->enabled, atomic_read(&irq->counter), irq->latency);
|
|
}
|
|
|
|
static ssize_t
|
|
sdhci_msm_pm_qos_irq_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", msm_host->pm_qos_irq.enabled);
|
|
}
|
|
|
|
static ssize_t
|
|
sdhci_msm_pm_qos_irq_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
uint32_t value;
|
|
bool enable;
|
|
int ret;
|
|
|
|
ret = kstrtou32(buf, 0, &value);
|
|
if (ret)
|
|
goto out;
|
|
enable = !!value;
|
|
|
|
if (enable == msm_host->pm_qos_irq.enabled)
|
|
goto out;
|
|
|
|
msm_host->pm_qos_irq.enabled = enable;
|
|
if (!enable) {
|
|
cancel_delayed_work_sync(&msm_host->pm_qos_irq.unvote_work);
|
|
atomic_set(&msm_host->pm_qos_irq.counter, 0);
|
|
msm_host->pm_qos_irq.latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&msm_host->pm_qos_irq.req,
|
|
msm_host->pm_qos_irq.latency);
|
|
}
|
|
|
|
out:
|
|
return count;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static inline void set_affine_irq(struct sdhci_msm_host *msm_host,
|
|
struct sdhci_host *host)
|
|
{
|
|
msm_host->pm_qos_irq.req.irq = host->irq;
|
|
}
|
|
#else
|
|
static inline void set_affine_irq(struct sdhci_msm_host *msm_host,
|
|
struct sdhci_host *host) { }
|
|
#endif
|
|
|
|
static bool sdhci_msm_pm_qos_wq_init(struct sdhci_msm_host *msm_host)
|
|
{
|
|
char *wq = NULL;
|
|
bool ret = true;
|
|
|
|
wq = kasprintf(GFP_KERNEL, "sdhci_msm_pm_qos/%s",
|
|
dev_name(&msm_host->pdev->dev));
|
|
if (!wq)
|
|
return false;
|
|
/*
|
|
* Create a work queue with flag WQ_MEM_RECLAIM set for
|
|
* pm_qos_unvote work. Because mmc thread is created with
|
|
* flag PF_MEMALLOC set, kernel will check for work queue
|
|
* flag WQ_MEM_RECLAIM when flush the work queue. If work
|
|
* queue flag WQ_MEM_RECLAIM is not set, kernel warning
|
|
* will be triggered.
|
|
*/
|
|
msm_host->pm_qos_wq = create_workqueue(wq);
|
|
if (!msm_host->pm_qos_wq) {
|
|
ret = false;
|
|
dev_err(&msm_host->pdev->dev,
|
|
"failed to create pm qos unvote work queue\n");
|
|
}
|
|
kfree(wq);
|
|
return ret;
|
|
}
|
|
|
|
void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_pm_qos_latency *irq_latency;
|
|
int ret;
|
|
|
|
if (!msm_host->pdata->pm_qos_data.irq_valid)
|
|
return;
|
|
|
|
/* Initialize only once as this gets called per partition */
|
|
if (msm_host->pm_qos_irq.enabled)
|
|
return;
|
|
|
|
atomic_set(&msm_host->pm_qos_irq.counter, 0);
|
|
msm_host->pm_qos_irq.req.type =
|
|
msm_host->pdata->pm_qos_data.irq_req_type;
|
|
if ((msm_host->pm_qos_irq.req.type != PM_QOS_REQ_AFFINE_CORES) &&
|
|
(msm_host->pm_qos_irq.req.type != PM_QOS_REQ_ALL_CORES))
|
|
set_affine_irq(msm_host, host);
|
|
else
|
|
cpumask_copy(&msm_host->pm_qos_irq.req.cpus_affine,
|
|
cpumask_of(msm_host->pdata->pm_qos_data.irq_cpu));
|
|
|
|
sdhci_msm_pm_qos_wq_init(msm_host);
|
|
|
|
INIT_DELAYED_WORK(&msm_host->pm_qos_irq.unvote_work,
|
|
sdhci_msm_pm_qos_irq_unvote_work);
|
|
/* For initialization phase, set the performance latency */
|
|
irq_latency = &msm_host->pdata->pm_qos_data.irq_latency;
|
|
msm_host->pm_qos_irq.latency =
|
|
irq_latency->latency[SDHCI_PERFORMANCE_MODE];
|
|
pm_qos_add_request(&msm_host->pm_qos_irq.req, PM_QOS_CPU_DMA_LATENCY,
|
|
msm_host->pm_qos_irq.latency);
|
|
msm_host->pm_qos_irq.enabled = true;
|
|
|
|
/* sysfs */
|
|
msm_host->pm_qos_irq.enable_attr.show =
|
|
sdhci_msm_pm_qos_irq_enable_show;
|
|
msm_host->pm_qos_irq.enable_attr.store =
|
|
sdhci_msm_pm_qos_irq_enable_store;
|
|
sysfs_attr_init(&msm_host->pm_qos_irq.enable_attr.attr);
|
|
msm_host->pm_qos_irq.enable_attr.attr.name = "pm_qos_irq_enable";
|
|
msm_host->pm_qos_irq.enable_attr.attr.mode = 0644;
|
|
ret = device_create_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_irq.enable_attr);
|
|
if (ret)
|
|
pr_err("%s: fail to create pm_qos_irq_enable (%d)\n",
|
|
__func__, ret);
|
|
|
|
msm_host->pm_qos_irq.status_attr.show = sdhci_msm_pm_qos_irq_show;
|
|
msm_host->pm_qos_irq.status_attr.store = NULL;
|
|
sysfs_attr_init(&msm_host->pm_qos_irq.status_attr.attr);
|
|
msm_host->pm_qos_irq.status_attr.attr.name = "pm_qos_irq_status";
|
|
msm_host->pm_qos_irq.status_attr.attr.mode = 0444;
|
|
ret = device_create_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_irq.status_attr);
|
|
if (ret)
|
|
pr_err("%s: fail to create pm_qos_irq_status (%d)\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
static ssize_t sdhci_msm_pm_qos_group_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_pm_qos_group *group;
|
|
int i;
|
|
int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups;
|
|
int offset = 0;
|
|
|
|
for (i = 0; i < nr_groups; i++) {
|
|
group = &msm_host->pm_qos[i];
|
|
offset += snprintf(&buf[offset], PAGE_SIZE,
|
|
"Group #%d (mask=0x%lx) PM QoS: enabled=%d, counter=%d, latency=%d\n",
|
|
i, group->req.cpus_affine.bits[0],
|
|
msm_host->pm_qos_group_enable,
|
|
atomic_read(&group->counter),
|
|
group->latency);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static ssize_t sdhci_msm_pm_qos_group_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
msm_host->pm_qos_group_enable ? "enabled" : "disabled");
|
|
}
|
|
|
|
static ssize_t sdhci_msm_pm_qos_group_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups;
|
|
uint32_t value;
|
|
bool enable;
|
|
int ret;
|
|
int i;
|
|
|
|
ret = kstrtou32(buf, 0, &value);
|
|
if (ret)
|
|
goto out;
|
|
enable = !!value;
|
|
|
|
if (enable == msm_host->pm_qos_group_enable)
|
|
goto out;
|
|
|
|
msm_host->pm_qos_group_enable = enable;
|
|
if (!enable) {
|
|
for (i = 0; i < nr_groups; i++) {
|
|
cancel_delayed_work_sync(
|
|
&msm_host->pm_qos[i].unvote_work);
|
|
atomic_set(&msm_host->pm_qos[i].counter, 0);
|
|
msm_host->pm_qos[i].latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&msm_host->pm_qos[i].req,
|
|
msm_host->pm_qos[i].latency);
|
|
}
|
|
}
|
|
|
|
out:
|
|
return count;
|
|
}
|
|
|
|
static int sdhci_msm_get_cpu_group(struct sdhci_msm_host *msm_host, int cpu)
|
|
{
|
|
int i;
|
|
struct sdhci_msm_cpu_group_map *map =
|
|
&msm_host->pdata->pm_qos_data.cpu_group_map;
|
|
|
|
if (cpu < 0)
|
|
goto not_found;
|
|
|
|
for (i = 0; i < map->nr_groups; i++)
|
|
if (cpumask_test_cpu(cpu, &map->mask[i]))
|
|
return i;
|
|
|
|
not_found:
|
|
return -EINVAL;
|
|
}
|
|
|
|
void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host,
|
|
struct sdhci_msm_pm_qos_latency *latency, int cpu)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int group = sdhci_msm_get_cpu_group(msm_host, cpu);
|
|
struct sdhci_msm_pm_qos_group *pm_qos_group;
|
|
int counter;
|
|
|
|
if (!msm_host->pm_qos_group_enable || group < 0)
|
|
return;
|
|
|
|
pm_qos_group = &msm_host->pm_qos[group];
|
|
counter = atomic_inc_return(&pm_qos_group->counter);
|
|
|
|
/* Make sure to update the voting in case power policy has changed */
|
|
if (pm_qos_group->latency == latency->latency[host->power_policy]
|
|
&& counter > 1)
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&pm_qos_group->unvote_work);
|
|
|
|
pm_qos_group->latency = latency->latency[host->power_policy];
|
|
pm_qos_update_request(&pm_qos_group->req, pm_qos_group->latency);
|
|
}
|
|
|
|
static void sdhci_msm_pm_qos_cpu_unvote_work(struct work_struct *work)
|
|
{
|
|
struct sdhci_msm_pm_qos_group *group =
|
|
container_of(work, struct sdhci_msm_pm_qos_group,
|
|
unvote_work.work);
|
|
|
|
if (atomic_read(&group->counter))
|
|
return;
|
|
|
|
group->latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&group->req, group->latency);
|
|
}
|
|
|
|
bool sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int group = sdhci_msm_get_cpu_group(msm_host, cpu);
|
|
|
|
if (!msm_host->pm_qos_group_enable || group < 0 ||
|
|
atomic_dec_return(&msm_host->pm_qos[group].counter))
|
|
return false;
|
|
|
|
if (async) {
|
|
queue_delayed_work(msm_host->pm_qos_wq,
|
|
&msm_host->pm_qos[group].unvote_work,
|
|
msecs_to_jiffies(QOS_REMOVE_DELAY_MS));
|
|
return true;
|
|
}
|
|
|
|
msm_host->pm_qos[group].latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_update_request(&msm_host->pm_qos[group].req,
|
|
msm_host->pm_qos[group].latency);
|
|
return true;
|
|
}
|
|
|
|
void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host,
|
|
struct sdhci_msm_pm_qos_latency *latency)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups;
|
|
struct sdhci_msm_pm_qos_group *group;
|
|
int i;
|
|
int ret;
|
|
|
|
if (msm_host->pm_qos_group_enable)
|
|
return;
|
|
|
|
msm_host->pm_qos = kcalloc(nr_groups, sizeof(*msm_host->pm_qos),
|
|
GFP_KERNEL);
|
|
if (!msm_host->pm_qos)
|
|
return;
|
|
|
|
for (i = 0; i < nr_groups; i++) {
|
|
group = &msm_host->pm_qos[i];
|
|
INIT_DELAYED_WORK(&group->unvote_work,
|
|
sdhci_msm_pm_qos_cpu_unvote_work);
|
|
atomic_set(&group->counter, 0);
|
|
group->req.type = PM_QOS_REQ_AFFINE_CORES;
|
|
cpumask_copy(&group->req.cpus_affine,
|
|
&msm_host->pdata->pm_qos_data.cpu_group_map.mask[i]);
|
|
/* We set default latency here for all pm_qos cpu groups. */
|
|
group->latency = PM_QOS_DEFAULT_VALUE;
|
|
pm_qos_add_request(&group->req, PM_QOS_CPU_DMA_LATENCY,
|
|
group->latency);
|
|
pr_info("%s (): voted for group #%d (mask=0x%lx) latency=%d\n",
|
|
__func__, i,
|
|
group->req.cpus_affine.bits[0],
|
|
group->latency);
|
|
}
|
|
msm_host->pm_qos_prev_cpu = -1;
|
|
msm_host->pm_qos_group_enable = true;
|
|
|
|
/* sysfs */
|
|
msm_host->pm_qos_group_status_attr.show = sdhci_msm_pm_qos_group_show;
|
|
msm_host->pm_qos_group_status_attr.store = NULL;
|
|
sysfs_attr_init(&msm_host->pm_qos_group_status_attr.attr);
|
|
msm_host->pm_qos_group_status_attr.attr.name =
|
|
"pm_qos_cpu_groups_status";
|
|
msm_host->pm_qos_group_status_attr.attr.mode = 0444;
|
|
ret = device_create_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_group_status_attr);
|
|
if (ret)
|
|
dev_err(&msm_host->pdev->dev, "%s: fail to create pm_qos_group_status_attr (%d)\n",
|
|
__func__, ret);
|
|
msm_host->pm_qos_group_enable_attr.show =
|
|
sdhci_msm_pm_qos_group_enable_show;
|
|
msm_host->pm_qos_group_enable_attr.store =
|
|
sdhci_msm_pm_qos_group_enable_store;
|
|
sysfs_attr_init(&msm_host->pm_qos_group_enable_attr.attr);
|
|
msm_host->pm_qos_group_enable_attr.attr.name =
|
|
"pm_qos_cpu_groups_enable";
|
|
msm_host->pm_qos_group_enable_attr.attr.mode = 0444;
|
|
ret = device_create_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_group_enable_attr);
|
|
if (ret)
|
|
dev_err(&msm_host->pdev->dev, "%s: fail to create pm_qos_group_enable_attr (%d)\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
static void sdhci_msm_pre_req(struct sdhci_host *host,
|
|
struct mmc_request *mmc_req)
|
|
{
|
|
int cpu;
|
|
int group;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int prev_group = sdhci_msm_get_cpu_group(msm_host,
|
|
msm_host->pm_qos_prev_cpu);
|
|
|
|
sdhci_msm_pm_qos_irq_vote(host);
|
|
|
|
cpu = get_cpu();
|
|
put_cpu();
|
|
group = sdhci_msm_get_cpu_group(msm_host, cpu);
|
|
if (group < 0)
|
|
return;
|
|
|
|
if (group != prev_group && prev_group >= 0) {
|
|
sdhci_msm_pm_qos_cpu_unvote(host,
|
|
msm_host->pm_qos_prev_cpu, false);
|
|
prev_group = -1; /* make sure to vote for new group */
|
|
}
|
|
|
|
if (prev_group < 0) {
|
|
sdhci_msm_pm_qos_cpu_vote(host,
|
|
msm_host->pdata->pm_qos_data.latency, cpu);
|
|
msm_host->pm_qos_prev_cpu = cpu;
|
|
}
|
|
}
|
|
|
|
static void sdhci_msm_post_req(struct sdhci_host *host,
|
|
struct mmc_request *mmc_req)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
sdhci_msm_pm_qos_irq_unvote(host, false);
|
|
|
|
if (sdhci_msm_pm_qos_cpu_unvote(host, msm_host->pm_qos_prev_cpu, false))
|
|
msm_host->pm_qos_prev_cpu = -1;
|
|
}
|
|
|
|
static void sdhci_msm_init(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
sdhci_msm_pm_qos_irq_init(host);
|
|
|
|
if (msm_host->pdata->pm_qos_data.legacy_valid)
|
|
sdhci_msm_pm_qos_cpu_init(host,
|
|
msm_host->pdata->pm_qos_data.latency);
|
|
}
|
|
|
|
static unsigned int sdhci_msm_get_current_limit(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_slot_reg_data *curr_slot = msm_host->pdata->vreg_data;
|
|
u32 max_curr = 0;
|
|
|
|
if (curr_slot && curr_slot->vdd_data)
|
|
max_curr = curr_slot->vdd_data->hpm_uA;
|
|
|
|
return max_curr;
|
|
}
|
|
|
|
static void sdhci_msm_card_event(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
if (!mmc_gpio_get_cd(msm_host->mmc))
|
|
msm_host->saved_tuning_phase = INVALID_TUNING_PHASE;
|
|
}
|
|
|
|
static int sdhci_msm_notify_load(struct sdhci_host *host, enum mmc_load state)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int ret = 0;
|
|
u32 clk_rate = 0;
|
|
|
|
if (!IS_ERR(msm_host->ice_clk)) {
|
|
clk_rate = (state == MMC_LOAD_LOW) ?
|
|
msm_host->pdata->ice_clk_min :
|
|
msm_host->pdata->ice_clk_max;
|
|
if (msm_host->ice_clk_rate == clk_rate)
|
|
return 0;
|
|
pr_debug("%s: changing ICE clk rate to %u\n",
|
|
mmc_hostname(host->mmc), clk_rate);
|
|
ret = clk_set_rate(msm_host->ice_clk, clk_rate);
|
|
if (ret) {
|
|
pr_err("%s: ICE_CLK rate set failed (%d) for %u\n",
|
|
mmc_hostname(host->mmc), ret, clk_rate);
|
|
return ret;
|
|
}
|
|
msm_host->ice_clk_rate = clk_rate;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct sdhci_ops sdhci_msm_ops = {
|
|
.set_uhs_signaling = sdhci_msm_set_uhs_signaling,
|
|
.check_power_status = sdhci_msm_check_power_status,
|
|
.platform_execute_tuning = sdhci_msm_execute_tuning,
|
|
.enhanced_strobe = sdhci_msm_enhanced_strobe,
|
|
.toggle_cdr = sdhci_msm_toggle_cdr,
|
|
.get_max_segments = sdhci_msm_max_segs,
|
|
.set_clock = sdhci_msm_set_clock,
|
|
.get_min_clock = sdhci_msm_get_min_clock,
|
|
.get_max_clock = sdhci_msm_get_max_clock,
|
|
.dump_vendor_regs = sdhci_msm_dump_vendor_regs,
|
|
.config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd,
|
|
.enable_controller_clock = sdhci_msm_enable_controller_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.reset = sdhci_msm_reset,
|
|
.clear_set_dumpregs = sdhci_msm_clear_set_dumpregs,
|
|
.enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask,
|
|
.reset_workaround = sdhci_msm_reset_workaround,
|
|
.init = sdhci_msm_init,
|
|
.pre_req = sdhci_msm_pre_req,
|
|
.post_req = sdhci_msm_post_req,
|
|
.get_current_limit = sdhci_msm_get_current_limit,
|
|
.card_event = sdhci_msm_card_event,
|
|
.notify_load = sdhci_msm_notify_load,
|
|
};
|
|
|
|
static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host,
|
|
struct sdhci_host *host)
|
|
{
|
|
u32 version, caps = 0;
|
|
u16 minor;
|
|
u8 major;
|
|
u32 val;
|
|
const struct sdhci_msm_offset *msm_host_offset =
|
|
msm_host->offset;
|
|
|
|
version = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_MCI_VERSION);
|
|
major = (version & CORE_VERSION_MAJOR_MASK) >>
|
|
CORE_VERSION_MAJOR_SHIFT;
|
|
minor = version & CORE_VERSION_TARGET_MASK;
|
|
|
|
caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES);
|
|
|
|
/*
|
|
* Starting with SDCC 5 controller (core major version = 1)
|
|
* controller won't advertise 3.0v, 1.8v and 8-bit features
|
|
* except for some targets.
|
|
*/
|
|
if (major >= 1 && minor != 0x11 && minor != 0x12) {
|
|
struct sdhci_msm_reg_data *vdd_io_reg;
|
|
/*
|
|
* Enable 1.8V support capability on controllers that
|
|
* support dual voltage
|
|
*/
|
|
vdd_io_reg = msm_host->pdata->vreg_data->vdd_io_data;
|
|
if (vdd_io_reg && (vdd_io_reg->high_vol_level > 2700000))
|
|
caps |= CORE_3_0V_SUPPORT;
|
|
if (vdd_io_reg && (vdd_io_reg->low_vol_level < 1950000))
|
|
caps |= CORE_1_8V_SUPPORT;
|
|
if (msm_host->pdata->mmc_bus_width == MMC_CAP_8_BIT_DATA)
|
|
caps |= CORE_8_BIT_SUPPORT;
|
|
}
|
|
|
|
/*
|
|
* Enable one MID mode for SDCC5 (major 1) on 8916/8939 (minor 0x2e) and
|
|
* on 8992 (minor 0x3e) as a workaround to reset for data stuck issue.
|
|
*/
|
|
if (major == 1 && (minor == 0x2e || minor == 0x3e)) {
|
|
host->quirks2 |= SDHCI_QUIRK2_USE_RESET_WORKAROUND;
|
|
val = readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
writel_relaxed((val | CORE_ONE_MID_EN),
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC_FUNC2);
|
|
}
|
|
/*
|
|
* SDCC 5 controller with major version 1, minor version 0x34 and later
|
|
* with HS 400 mode support will use CM DLL instead of CDC LP 533 DLL.
|
|
*/
|
|
if ((major == 1) && (minor < 0x34))
|
|
msm_host->use_cdclp533 = true;
|
|
|
|
/*
|
|
* SDCC 5 controller with major version 1, minor version 0x42 and later
|
|
* will require additional steps when resetting DLL.
|
|
* It also supports HS400 enhanced strobe mode.
|
|
*/
|
|
if ((major == 1) && (minor >= 0x42)) {
|
|
msm_host->use_updated_dll_reset = true;
|
|
msm_host->enhanced_strobe = true;
|
|
}
|
|
|
|
/*
|
|
* SDCC 5 controller with major version 1 and minor version 0x42,
|
|
* 0x46 and 0x49 currently uses 14lpp tech DLL whose internal
|
|
* gating cannot guarantee MCLK timing requirement i.e.
|
|
* when MCLK is gated OFF, it is not gated for less than 0.5us
|
|
* and MCLK must be switched on for at-least 1us before DATA
|
|
* starts coming.
|
|
*/
|
|
if ((major == 1) && ((minor == 0x42) || (minor == 0x46) ||
|
|
(minor == 0x49) || (minor >= 0x6b)))
|
|
msm_host->use_14lpp_dll = true;
|
|
|
|
/* Fake 3.0V support for SDIO devices which requires such voltage */
|
|
if (msm_host->core_3_0v_support) {
|
|
caps |= CORE_3_0V_SUPPORT;
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
SDHCI_CAPABILITIES) | caps), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_CAPABILITIES0);
|
|
}
|
|
|
|
if ((major == 1) && (minor >= 0x49))
|
|
msm_host->rclk_delay_fix = true;
|
|
/*
|
|
* Mask 64-bit support for controller with 32-bit address bus so that
|
|
* smaller descriptor size will be used and improve memory consumption.
|
|
*/
|
|
if (!msm_host->pdata->largeaddressbus)
|
|
caps &= ~CORE_SYS_BUS_SUPPORT_64_BIT;
|
|
|
|
writel_relaxed(caps, host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC_CAPABILITIES0);
|
|
/* keep track of the value in SDHCI_CAPABILITIES */
|
|
msm_host->caps_0 = caps;
|
|
|
|
if ((major == 1) && (minor >= 0x6b)) {
|
|
host->cdr_support = true;
|
|
}
|
|
|
|
/* 7FF projects with 7nm DLL */
|
|
if ((major == 1) && ((minor == 0x6e) || (minor == 0x71) ||
|
|
(minor == 0x72)))
|
|
msm_host->use_7nm_dll = true;
|
|
}
|
|
|
|
#ifdef CONFIG_MMC_CQ_HCI
|
|
static void sdhci_msm_cmdq_init(struct sdhci_host *host,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
if (nocmdq) {
|
|
dev_dbg(&pdev->dev, "CMDQ disabled via cmdline\n");
|
|
return;
|
|
}
|
|
|
|
host->cq_host = cmdq_pltfm_init(pdev);
|
|
if (IS_ERR(host->cq_host)) {
|
|
dev_dbg(&pdev->dev, "cmdq-pltfm init: failed: %ld\n",
|
|
PTR_ERR(host->cq_host));
|
|
host->cq_host = NULL;
|
|
} else {
|
|
msm_host->mmc->caps2 |= MMC_CAP2_CMD_QUEUE;
|
|
}
|
|
/*
|
|
* Set the vendor specific ops needed for ICE.
|
|
* Default implementation if the ops are not set.
|
|
*/
|
|
#ifdef CONFIG_MMC_CQ_HCI_CRYPTO_QTI
|
|
cmdq_crypto_qti_set_vops(host->cq_host);
|
|
#endif
|
|
}
|
|
#else
|
|
static void sdhci_msm_cmdq_init(struct sdhci_host *host,
|
|
struct platform_device *pdev)
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
static bool sdhci_msm_is_bootdevice(struct device *dev)
|
|
{
|
|
if (strnstr(saved_command_line, "androidboot.bootdevice=",
|
|
strlen(saved_command_line))) {
|
|
char search_string[50];
|
|
|
|
snprintf(search_string, ARRAY_SIZE(search_string),
|
|
"androidboot.bootdevice=%s", dev_name(dev));
|
|
if (strnstr(saved_command_line, search_string,
|
|
strlen(saved_command_line)))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* "androidboot.bootdevice=" argument is not present then
|
|
* return true as we don't know the boot device anyways.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_DRV_SAMSUNG
|
|
/* SYSFS about SD Card Detection */
|
|
static struct device *t_flash_detect_dev;
|
|
|
|
static ssize_t t_flash_detect_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
#if defined(CONFIG_SEC_NO_SD_DETECT) || defined(CONFIG_SEC_HYBRID_TRAY)
|
|
if (!mmc_gpio_get_cd(msm_host->mmc)) {
|
|
pr_debug("SD slot tray Removed.\n");
|
|
return sprintf(buf, "Notray\n");
|
|
}
|
|
if (msm_host->mmc->card) {
|
|
pr_debug("External sd: card inserted.\n");
|
|
return sprintf(buf, "Insert\n");
|
|
}
|
|
pr_debug("External sd: card removed.\n");
|
|
return sprintf(buf, "Remove\n");
|
|
#else
|
|
unsigned int detect;
|
|
|
|
if (gpio_is_valid(msm_host->pdata->status_gpio))
|
|
detect = mmc_gpio_get_cd(msm_host->mmc);
|
|
else {
|
|
pr_info("%s : External SD detect pin Error\n", __func__);
|
|
return sprintf(buf, "Error\n");
|
|
}
|
|
|
|
pr_info("%s : detect = %d.\n", __func__, detect);
|
|
if (detect) {
|
|
pr_debug("External sd: card inserted.\n");
|
|
return sprintf(buf, "Insert\n");
|
|
}
|
|
pr_debug("External sd: card removed.\n");
|
|
return sprintf(buf, "Remove\n");
|
|
#endif
|
|
}
|
|
|
|
static ssize_t sd_detect_cnt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
|
|
dev_info(dev, "%s : CD count is = %u\n", __func__, msm_host->mmc->card_detect_cnt);
|
|
return sprintf(buf, "%u\n", msm_host->mmc->card_detect_cnt);
|
|
}
|
|
|
|
static ssize_t sd_detect_maxmode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
struct mmc_host *host = msm_host->mmc;
|
|
const char *uhs_bus_speed_mode = "";
|
|
|
|
if (host->caps & MMC_CAP_UHS_SDR104)
|
|
uhs_bus_speed_mode = "SDR104";
|
|
else if (host->caps & MMC_CAP_UHS_DDR50)
|
|
uhs_bus_speed_mode = "DDR50";
|
|
else if (host->caps & MMC_CAP_UHS_SDR50)
|
|
uhs_bus_speed_mode = "SDR50";
|
|
else if (host->caps & MMC_CAP_UHS_SDR25)
|
|
uhs_bus_speed_mode = "SDR25";
|
|
else if (host->caps & MMC_CAP_UHS_SDR12)
|
|
uhs_bus_speed_mode = "SDR12";
|
|
else
|
|
uhs_bus_speed_mode = "HS";
|
|
|
|
dev_info(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_detect_curmode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
struct mmc_host *host = msm_host->mmc;
|
|
const char *uhs_bus_speed_mode = "";
|
|
static const char *const uhs_speed[] = {
|
|
[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 && host->card) {
|
|
if (mmc_card_uhs(host->card))
|
|
uhs_bus_speed_mode =
|
|
uhs_speed[host->card->sd_bus_speed];
|
|
else
|
|
uhs_bus_speed_mode = "HS";
|
|
} else
|
|
uhs_bus_speed_mode = "No Card";
|
|
|
|
dev_info(dev, "%s: Current SD Card Speed = %s\n",
|
|
__func__, uhs_bus_speed_mode);
|
|
return sprintf(buf, "%s\n", uhs_bus_speed_mode);
|
|
}
|
|
|
|
static ssize_t sd_detect_curphase_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
struct mmc_host *host = msm_host->mmc;
|
|
|
|
if (host && host->card)
|
|
if (host->card->sd_bus_speed == UHS_SDR104_BUS_SPEED)
|
|
return sprintf(buf, "%d\n", (int)msm_host->saved_tuning_phase);
|
|
|
|
return sprintf(buf, "%d\n", -1);
|
|
}
|
|
|
|
static struct device *sd_info_dev;
|
|
static ssize_t sd_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->card;
|
|
struct mmc_card_error_log *err_log;
|
|
u64 total_cnt = 0;
|
|
int len = 0;
|
|
int i = 0;
|
|
|
|
if (!card) {
|
|
len = snprintf(buf, PAGE_SIZE, "no card\n");
|
|
goto out;
|
|
}
|
|
|
|
err_log = 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_dev;
|
|
static ssize_t sd_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->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\"\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\"\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 ssize_t sdcard_summary_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
struct mmc_host *host = msm_host->mmc;
|
|
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 && host->card) {
|
|
card = host->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(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 */
|
|
return sprintf(buf, "\"MANID\":\"NoCard\",\"SERIAL\":\"NoCard\"" \
|
|
",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\",\"NOTI\":\"NoCard\"\n");
|
|
}
|
|
}
|
|
|
|
static struct device *mmc_data_dev;
|
|
|
|
static ssize_t mmc_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->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 mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->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 mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->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, S_IRUGO, mmc_hwrst_show, NULL);
|
|
#endif
|
|
|
|
static ssize_t sd_cid_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->card;
|
|
int len = 0;
|
|
|
|
if (!card) {
|
|
len = snprintf(buf, PAGE_SIZE, "no card\n");
|
|
goto out;
|
|
}
|
|
|
|
len = snprintf(buf, PAGE_SIZE,
|
|
"%08x%08x%08x%08x\n",
|
|
card->raw_cid[0], card->raw_cid[1],
|
|
card->raw_cid[2], card->raw_cid[3]);
|
|
out:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t sd_health_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mmc_host *host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = host->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) {
|
|
//There should be no spaces in 'No Card'(Vold Team).
|
|
len = snprintf(buf, PAGE_SIZE, "NOCARD\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;
|
|
}
|
|
|
|
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, t_flash_detect_show, NULL);
|
|
static DEVICE_ATTR(cd_cnt, 0444, sd_detect_cnt_show, NULL);
|
|
static DEVICE_ATTR(max_mode, 0444, sd_detect_maxmode_show, NULL);
|
|
static DEVICE_ATTR(current_mode, 0444, sd_detect_curmode_show, NULL);
|
|
static DEVICE_ATTR(current_phase, 0444, sd_detect_curphase_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(sdcard_summary, 0444, sdcard_summary_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(&t_flash_detect_dev->kobj, KOBJ_CHANGE);
|
|
}
|
|
|
|
static int sdhci_sec_sdcard_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
struct sdhci_msm_host *msm_host = dev_get_drvdata(dev);
|
|
struct mmc_card *card = NULL;
|
|
int retval = 0;
|
|
bool card_exist = false;
|
|
|
|
add_uevent_var(env, "DEVNAME=%s", dev->kobj.name);
|
|
|
|
if (msm_host->mmc && msm_host->mmc->card) {
|
|
card_exist = true;
|
|
card = msm_host->mmc->card;
|
|
} else
|
|
card_exist = false;
|
|
|
|
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");
|
|
|
|
return retval;
|
|
}
|
|
|
|
static struct device_type sdcard_type = {
|
|
.uevent = sdhci_sec_sdcard_uevent,
|
|
};
|
|
#endif
|
|
|
|
static int sdhci_msm_probe(struct platform_device *pdev)
|
|
{
|
|
const struct sdhci_msm_offset *msm_host_offset;
|
|
struct sdhci_host *host;
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct sdhci_msm_host *msm_host;
|
|
struct resource *core_memres = NULL;
|
|
int ret = 0, dead = 0, tlmm_cfg = 0;
|
|
u16 host_version;
|
|
u32 irq_status, irq_ctl;
|
|
struct resource *tlmm_memres = NULL;
|
|
void __iomem *tlmm_mem;
|
|
unsigned long flags;
|
|
bool force_probe;
|
|
|
|
pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__);
|
|
msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host),
|
|
GFP_KERNEL);
|
|
if (!msm_host) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (of_find_compatible_node(NULL, NULL, "qcom,sdhci-msm-v5")) {
|
|
msm_host->mci_removed = true;
|
|
msm_host->offset = &sdhci_msm_offset_mci_removed;
|
|
} else {
|
|
msm_host->mci_removed = false;
|
|
msm_host->offset = &sdhci_msm_offset_mci_present;
|
|
}
|
|
msm_host_offset = msm_host->offset;
|
|
msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
|
|
host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0);
|
|
if (IS_ERR(host)) {
|
|
ret = PTR_ERR(host);
|
|
goto out_host_free;
|
|
}
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
pltfm_host->priv = msm_host;
|
|
msm_host->mmc = host->mmc;
|
|
msm_host->pdev = pdev;
|
|
|
|
/* Extract platform data */
|
|
if (pdev->dev.of_node) {
|
|
ret = of_alias_get_id(pdev->dev.of_node, "sdhc");
|
|
if (ret <= 0) {
|
|
dev_err(&pdev->dev, "Failed to get slot index %d\n",
|
|
ret);
|
|
goto pltfm_free;
|
|
}
|
|
|
|
/* Read property to determine if the probe is forced */
|
|
force_probe = of_find_property(pdev->dev.of_node,
|
|
"qcom,force-sdhc1-probe", NULL);
|
|
|
|
/* skip the probe if eMMC isn't a boot device */
|
|
if ((ret == 1) && !sdhci_msm_is_bootdevice(&pdev->dev)
|
|
&& !force_probe) {
|
|
ret = -ENODEV;
|
|
goto pltfm_free;
|
|
}
|
|
|
|
if (disable_slots & (1 << (ret - 1))) {
|
|
dev_info(&pdev->dev, "%s: Slot %d disabled\n", __func__,
|
|
ret);
|
|
ret = -ENODEV;
|
|
goto pltfm_free;
|
|
}
|
|
|
|
if (ret <= 2)
|
|
sdhci_slot[ret-1] = msm_host;
|
|
|
|
msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev,
|
|
msm_host);
|
|
if (!msm_host->pdata) {
|
|
dev_err(&pdev->dev, "DT parsing error\n");
|
|
goto pltfm_free;
|
|
}
|
|
} else {
|
|
dev_err(&pdev->dev, "No device tree node\n");
|
|
goto pltfm_free;
|
|
}
|
|
|
|
/* Setup Clocks */
|
|
|
|
/* Setup SDCC bus voter clock. */
|
|
msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_clk)) {
|
|
/* Vote for max. clk rate for max. performance */
|
|
ret = clk_set_rate(msm_host->bus_clk, INT_MAX);
|
|
if (ret)
|
|
goto pltfm_free;
|
|
ret = clk_prepare_enable(msm_host->bus_clk);
|
|
if (ret)
|
|
goto pltfm_free;
|
|
}
|
|
|
|
/* Setup main peripheral bus clock */
|
|
msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk");
|
|
if (!IS_ERR(msm_host->pclk)) {
|
|
ret = clk_prepare_enable(msm_host->pclk);
|
|
if (ret)
|
|
goto bus_clk_disable;
|
|
}
|
|
atomic_set(&msm_host->controller_clock, 1);
|
|
|
|
/* Setup SDC ufs bus aggr clock */
|
|
msm_host->bus_aggr_clk = devm_clk_get(&pdev->dev, "bus_aggr_clk");
|
|
if (!IS_ERR(msm_host->bus_aggr_clk)) {
|
|
ret = clk_prepare_enable(msm_host->bus_aggr_clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Bus aggregate clk not enabled\n");
|
|
goto pclk_disable;
|
|
}
|
|
}
|
|
|
|
/* Setup SDC ICE clock */
|
|
msm_host->ice_clk = devm_clk_get(&pdev->dev, "ice_core_clk");
|
|
if (!IS_ERR(msm_host->ice_clk)) {
|
|
/* ICE core has only one clock frequency for now */
|
|
ret = clk_set_rate(msm_host->ice_clk,
|
|
msm_host->pdata->ice_clk_max);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "ICE_CLK rate set failed (%d) for %u\n",
|
|
ret,
|
|
msm_host->pdata->ice_clk_max);
|
|
goto bus_aggr_clk_disable;
|
|
}
|
|
ret = clk_prepare_enable(msm_host->ice_clk);
|
|
if (ret)
|
|
goto bus_aggr_clk_disable;
|
|
|
|
msm_host->ice_clk_rate =
|
|
msm_host->pdata->ice_clk_max;
|
|
}
|
|
|
|
/* Setup SDC MMC clock */
|
|
msm_host->clk = devm_clk_get(&pdev->dev, "core_clk");
|
|
if (IS_ERR(msm_host->clk)) {
|
|
ret = PTR_ERR(msm_host->clk);
|
|
goto bus_aggr_clk_disable;
|
|
}
|
|
|
|
/* Set to the minimum supported clock frequency */
|
|
ret = clk_set_rate(msm_host->clk, sdhci_msm_get_min_clock(host));
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret);
|
|
goto bus_aggr_clk_disable;
|
|
}
|
|
ret = clk_prepare_enable(msm_host->clk);
|
|
if (ret)
|
|
goto bus_aggr_clk_disable;
|
|
|
|
msm_host->clk_rate = sdhci_msm_get_min_clock(host);
|
|
atomic_set(&msm_host->clks_on, 1);
|
|
|
|
/* Setup CDC calibration fixed feedback clock */
|
|
msm_host->ff_clk = devm_clk_get(&pdev->dev, "cal_clk");
|
|
if (!IS_ERR(msm_host->ff_clk)) {
|
|
ret = clk_prepare_enable(msm_host->ff_clk);
|
|
if (ret)
|
|
goto clk_disable;
|
|
}
|
|
|
|
/* Setup CDC calibration sleep clock */
|
|
msm_host->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk");
|
|
if (!IS_ERR(msm_host->sleep_clk)) {
|
|
ret = clk_prepare_enable(msm_host->sleep_clk);
|
|
if (ret)
|
|
goto ff_clk_disable;
|
|
}
|
|
|
|
msm_host->saved_tuning_phase = INVALID_TUNING_PHASE;
|
|
|
|
ret = sdhci_msm_bus_register(msm_host, pdev);
|
|
if (ret)
|
|
goto sleep_clk_disable;
|
|
|
|
if (msm_host->msm_bus_vote.client_handle)
|
|
INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work,
|
|
sdhci_msm_bus_work);
|
|
sdhci_msm_bus_voting(host, 1);
|
|
|
|
/* Setup regulators */
|
|
ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Regulator setup failed (%d)\n", ret);
|
|
goto bus_unregister;
|
|
}
|
|
|
|
/* Reset the core and Enable SDHC mode */
|
|
core_memres = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "core_mem");
|
|
if (!msm_host->mci_removed) {
|
|
if (!core_memres) {
|
|
dev_err(&pdev->dev, "Failed to get iomem resource\n");
|
|
goto vreg_deinit;
|
|
}
|
|
msm_host->core_mem = devm_ioremap(&pdev->dev,
|
|
core_memres->start, resource_size(core_memres));
|
|
|
|
if (!msm_host->core_mem) {
|
|
dev_err(&pdev->dev, "Failed to remap registers\n");
|
|
ret = -ENOMEM;
|
|
goto vreg_deinit;
|
|
}
|
|
}
|
|
|
|
tlmm_memres = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "tlmm_mem");
|
|
if (tlmm_memres) {
|
|
tlmm_mem = devm_ioremap(&pdev->dev, tlmm_memres->start,
|
|
resource_size(tlmm_memres));
|
|
|
|
if (!tlmm_mem) {
|
|
dev_err(&pdev->dev, "Failed to remap tlmm registers\n");
|
|
ret = -ENOMEM;
|
|
goto vreg_deinit;
|
|
}
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node,
|
|
"tlmm_cfg",
|
|
&tlmm_cfg);
|
|
if (ret)
|
|
writel_relaxed(readl_relaxed(tlmm_mem) | 0x2, tlmm_mem);
|
|
else
|
|
writel_relaxed(readl_relaxed(tlmm_mem) | tlmm_cfg,
|
|
tlmm_mem);
|
|
}
|
|
|
|
/*
|
|
* Reset the vendor spec register to power on reset state.
|
|
*/
|
|
writel_relaxed(CORE_VENDOR_SPEC_POR_VAL,
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC);
|
|
|
|
/* This enable ADMA error interrupt in case of length mismatch */
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) |
|
|
CORE_VNDR_SPEC_ADMA_ERR_SIZE_EN),
|
|
host->ioaddr + msm_host_offset->CORE_VENDOR_SPEC);
|
|
|
|
/*
|
|
* Ensure SDHCI FIFO is enabled by disabling alternative FIFO
|
|
*/
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3) &
|
|
~CORE_FIFO_ALT_EN), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC3);
|
|
|
|
if (!msm_host->mci_removed) {
|
|
/* Set HC_MODE_EN bit in HC_MODE register */
|
|
writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE));
|
|
|
|
/* Set FF_CLK_SW_RST_DIS bit in HC_MODE register */
|
|
writel_relaxed(readl_relaxed(msm_host->core_mem +
|
|
CORE_HC_MODE) | FF_CLK_SW_RST_DIS,
|
|
msm_host->core_mem + CORE_HC_MODE);
|
|
}
|
|
sdhci_set_default_hw_caps(msm_host, host);
|
|
|
|
/*
|
|
* Set the PAD_PWR_SWITCH_EN bit so that the PAD_PWR_SWITCH bit can
|
|
* be used as required later on.
|
|
*/
|
|
writel_relaxed((readl_relaxed(host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC) |
|
|
CORE_IO_PAD_PWR_SWITCH_EN), host->ioaddr +
|
|
msm_host_offset->CORE_VENDOR_SPEC);
|
|
/*
|
|
* CORE_SW_RST above may trigger power irq if previous status of PWRCTL
|
|
* was either BUS_ON or IO_HIGH_V. So before we enable the power irq
|
|
* interrupt in GIC (by registering the interrupt handler), we need to
|
|
* ensure that any pending power irq interrupt status is acknowledged
|
|
* otherwise power irq interrupt handler would be fired prematurely.
|
|
*/
|
|
irq_status = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_STATUS);
|
|
sdhci_msm_writel_relaxed(irq_status, host,
|
|
msm_host_offset->CORE_PWRCTL_CLEAR);
|
|
irq_ctl = sdhci_msm_readl_relaxed(host,
|
|
msm_host_offset->CORE_PWRCTL_CTL);
|
|
|
|
if (irq_status & (CORE_PWRCTL_BUS_ON | CORE_PWRCTL_BUS_OFF))
|
|
irq_ctl |= CORE_PWRCTL_BUS_SUCCESS;
|
|
if (irq_status & (CORE_PWRCTL_IO_HIGH | CORE_PWRCTL_IO_LOW))
|
|
irq_ctl |= CORE_PWRCTL_IO_SUCCESS;
|
|
sdhci_msm_writel_relaxed(irq_ctl, host,
|
|
msm_host_offset->CORE_PWRCTL_CTL);
|
|
|
|
/*
|
|
* Ensure that above writes are propogated before interrupt enablement
|
|
* in GIC.
|
|
*/
|
|
mb();
|
|
|
|
/*
|
|
* Following are the deviations from SDHC spec v3.0 -
|
|
* 1. Card detection is handled using separate GPIO.
|
|
* 2. Bus power control is handled by interacting with PMIC.
|
|
*/
|
|
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
|
host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE;
|
|
host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
|
|
host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
|
|
host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK;
|
|
host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD;
|
|
host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE;
|
|
host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT;
|
|
host->quirks2 |= SDHCI_QUIRK2_NON_STANDARD_TUNING;
|
|
host->quirks2 |= SDHCI_QUIRK2_USE_PIO_FOR_EMMC_TUNING;
|
|
|
|
if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK)
|
|
host->quirks2 |= SDHCI_QUIRK2_DIVIDE_TOUT_BY_4;
|
|
|
|
host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
|
|
dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
|
|
host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
|
|
SDHCI_VENDOR_VER_SHIFT));
|
|
if (((host_version & SDHCI_VENDOR_VER_MASK) >>
|
|
SDHCI_VENDOR_VER_SHIFT) == SDHCI_VER_100) {
|
|
/*
|
|
* Add 40us delay in interrupt handler when
|
|
* operating at initialization frequency(400KHz).
|
|
*/
|
|
host->quirks2 |= SDHCI_QUIRK2_SLOW_INT_CLR;
|
|
/*
|
|
* Set Software Reset for DAT line in Software
|
|
* Reset Register (Bit 2).
|
|
*/
|
|
host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT;
|
|
}
|
|
|
|
host->quirks2 |= SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR;
|
|
|
|
/* Setup PWRCTL irq */
|
|
msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
|
|
if (msm_host->pwr_irq < 0) {
|
|
dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n",
|
|
msm_host->pwr_irq);
|
|
goto vreg_deinit;
|
|
}
|
|
ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
|
|
sdhci_msm_pwr_irq, IRQF_ONESHOT,
|
|
dev_name(&pdev->dev), host);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n",
|
|
msm_host->pwr_irq, ret);
|
|
goto vreg_deinit;
|
|
}
|
|
|
|
/* Enable pwr irq interrupts */
|
|
sdhci_msm_writel_relaxed(INT_MASK, host,
|
|
msm_host_offset->CORE_PWRCTL_MASK);
|
|
|
|
#ifdef CONFIG_MMC_CLKGATE
|
|
/* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */
|
|
msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY;
|
|
#endif
|
|
|
|
/* Set host capabilities */
|
|
msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width;
|
|
msm_host->mmc->caps |= msm_host->pdata->caps;
|
|
msm_host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
|
|
msm_host->mmc->caps2 |= msm_host->pdata->caps2;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_NO_SDIO;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_MAX_DISCARD_SIZE;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_SLEEP_AWAKE;
|
|
msm_host->mmc->caps2 |= MMC_CAP2_DETECT_ON_ERR;
|
|
msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;
|
|
#if defined(CONFIG_SEC_HYBRID_TRAY)
|
|
msm_host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;
|
|
#endif
|
|
|
|
if (msm_host->pdata->nonremovable)
|
|
msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE;
|
|
|
|
if (msm_host->pdata->nonhotplug)
|
|
msm_host->mmc->caps2 |= MMC_CAP2_NONHOTPLUG;
|
|
|
|
msm_host->mmc->sdr104_wa = msm_host->pdata->sdr104_wa;
|
|
|
|
init_completion(&msm_host->pwr_irq_completion);
|
|
|
|
if (gpio_is_valid(msm_host->pdata->status_gpio)) {
|
|
/*
|
|
* Set up the card detect GPIO in active configuration before
|
|
* configuring it as an IRQ. Otherwise, it can be in some
|
|
* weird/inconsistent state resulting in flood of interrupts.
|
|
*/
|
|
sdhci_msm_setup_pins(msm_host->pdata, true);
|
|
|
|
/*
|
|
* This delay is needed for stabilizing the card detect GPIO
|
|
* line after changing the pull configs.
|
|
*/
|
|
usleep_range(10000, 10500);
|
|
ret = mmc_gpio_request_cd(msm_host->mmc,
|
|
msm_host->pdata->status_gpio, 0);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "%s: Failed to request card detection IRQ %d\n",
|
|
__func__, ret);
|
|
goto vreg_deinit;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_DRV_SAMSUNG
|
|
#if defined(CONFIG_SEC_NO_SD_DETECT)
|
|
if (t_flash_detect_dev == NULL && !strcmp(host->hw_name, "8804000.sdhci")) {
|
|
#else
|
|
if (t_flash_detect_dev == NULL && gpio_is_valid(msm_host->pdata->status_gpio)) {
|
|
#endif
|
|
pr_debug("%s : Change sysfs Card Detect\n", __func__);
|
|
|
|
t_flash_detect_dev = sec_device_create(0, NULL, "sdcard");
|
|
if (IS_ERR(t_flash_detect_dev)) {
|
|
pr_err("%s : Failed to create device!\n", __func__);
|
|
goto failed_sdcard;
|
|
}
|
|
|
|
t_flash_detect_dev->type = &sdcard_type;
|
|
msm_host->mmc->sdcard_uevent = sdcard_uevent;
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_status) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_status.attr.name);
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_cd_cnt) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_cd_cnt.attr.name);
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_max_mode) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_max_mode.attr.name);
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_current_mode) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_current_mode.attr.name);
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_current_phase) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_current_phase.attr.name);
|
|
|
|
if (device_create_file(t_flash_detect_dev,
|
|
&dev_attr_sdcard_summary) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_sdcard_summary.attr.name);
|
|
|
|
dev_set_drvdata(t_flash_detect_dev, msm_host);
|
|
}
|
|
failed_sdcard:
|
|
|
|
#if defined(CONFIG_NO_SD_DET_PIN)
|
|
if (sd_info_dev == NULL && !strcmp(host->hw_name, "8804000.sdhci")) {
|
|
#else
|
|
if (sd_info_dev == NULL && gpio_is_valid(msm_host->pdata->status_gpio)) {
|
|
#endif
|
|
sd_info_dev = sec_device_create(0, NULL, "sdinfo");
|
|
if (IS_ERR(sd_info_dev)) {
|
|
pr_err("%s : Failed to create device!\n", __func__);
|
|
goto failed_sdinfo;
|
|
}
|
|
|
|
if (device_create_file(sd_info_dev,
|
|
&dev_attr_sd_count) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_sd_count.attr.name);
|
|
|
|
if (device_create_file(sd_info_dev,
|
|
&dev_attr_data) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_data.attr.name);
|
|
|
|
if (device_create_file(sd_info_dev,
|
|
&dev_attr_fc) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_fc.attr.name);
|
|
|
|
dev_set_drvdata(sd_info_dev, msm_host->mmc);
|
|
}
|
|
failed_sdinfo:
|
|
|
|
#if defined(CONFIG_NO_SD_DET_PIN)
|
|
if (sd_data_dev == NULL && !strcmp(host->hw_name, "8804000.sdhci")) {
|
|
#else
|
|
if (sd_data_dev == NULL && gpio_is_valid(msm_host->pdata->status_gpio)) {
|
|
#endif
|
|
sd_data_dev = sec_device_create(0, NULL, "sddata");
|
|
if (IS_ERR(sd_data_dev)) {
|
|
pr_err("%s : Failed to create device!\n", __func__);
|
|
goto failed_sddata;
|
|
}
|
|
|
|
if (device_create_file(sd_data_dev,
|
|
&dev_attr_sd_data) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_sd_data.attr.name);
|
|
|
|
dev_set_drvdata(sd_data_dev, msm_host->mmc);
|
|
}
|
|
failed_sddata:
|
|
|
|
if (mmc_data_dev == NULL && !strcmp(host->hw_name, "7c4000.sdhci")) {
|
|
mmc_data_dev = sec_device_create(0, NULL, "mmc");
|
|
if (IS_ERR(mmc_data_dev)) {
|
|
pr_err("%s : Failed to create device!\n", __func__);
|
|
goto failed_mmc;
|
|
}
|
|
#ifdef CONFIG_SEC_FACTORY
|
|
if (device_create_file(mmc_data_dev,
|
|
&dev_attr_hwrst) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_hwrst.attr.name);
|
|
#endif
|
|
if (device_create_file(mmc_data_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_data_dev,
|
|
&dev_attr_mmc_summary) < 0)
|
|
pr_err("%s : Failed to create device file(%s)!\n",
|
|
__func__, dev_attr_mmc_summary.attr.name);
|
|
|
|
dev_set_drvdata(mmc_data_dev, msm_host->mmc);
|
|
}
|
|
failed_mmc:
|
|
#endif // CONFIG_DRV_SAMSUNG
|
|
|
|
if ((sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) &&
|
|
(dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(64)))) {
|
|
host->dma_mask = DMA_BIT_MASK(64);
|
|
mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
|
|
mmc_dev(host->mmc)->coherent_dma_mask = host->dma_mask;
|
|
} else if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) {
|
|
host->dma_mask = DMA_BIT_MASK(32);
|
|
mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
|
|
mmc_dev(host->mmc)->coherent_dma_mask = host->dma_mask;
|
|
} else {
|
|
dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__);
|
|
}
|
|
|
|
msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev,
|
|
"sdiowakeup_irq");
|
|
if (sdhci_is_valid_gpio_wakeup_int(msm_host)) {
|
|
dev_info(&pdev->dev, "%s: sdiowakeup_irq = %d\n", __func__,
|
|
msm_host->pdata->sdiowakeup_irq);
|
|
msm_host->is_sdiowakeup_enabled = true;
|
|
ret = request_irq(msm_host->pdata->sdiowakeup_irq,
|
|
sdhci_msm_sdiowakeup_irq,
|
|
IRQF_SHARED | IRQF_TRIGGER_HIGH,
|
|
"sdhci-msm sdiowakeup", host);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d\n",
|
|
__func__, msm_host->pdata->sdiowakeup_irq, ret);
|
|
msm_host->pdata->sdiowakeup_irq = -1;
|
|
msm_host->is_sdiowakeup_enabled = false;
|
|
goto vreg_deinit;
|
|
} else {
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
|
|
msm_host->sdio_pending_processing = false;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
}
|
|
|
|
sdhci_msm_cmdq_init(host, pdev);
|
|
ret = sdhci_add_host(host);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Add host failed (%d)\n", ret);
|
|
goto vreg_deinit;
|
|
}
|
|
|
|
/*
|
|
* To avoid polling and to avoid this R1b command conversion
|
|
* to R1 command if the requested busy timeout > host's max
|
|
* busy timeout in case of sanitize, erase or any R1b command
|
|
*/
|
|
host->mmc->max_busy_timeout = 0;
|
|
|
|
msm_host->pltfm_init_done = true;
|
|
|
|
pm_runtime_set_active(&pdev->dev);
|
|
pm_runtime_enable(&pdev->dev);
|
|
pm_runtime_set_autosuspend_delay(&pdev->dev, MSM_AUTOSUSPEND_DELAY_MS);
|
|
pm_runtime_use_autosuspend(&pdev->dev);
|
|
|
|
msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw;
|
|
msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw;
|
|
sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr);
|
|
msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw";
|
|
msm_host->msm_bus_vote.max_bus_bw.attr.mode = 0644;
|
|
ret = device_create_file(&pdev->dev,
|
|
&msm_host->msm_bus_vote.max_bus_bw);
|
|
if (ret)
|
|
goto remove_host;
|
|
|
|
if (!gpio_is_valid(msm_host->pdata->status_gpio)) {
|
|
msm_host->polling.show = show_polling;
|
|
msm_host->polling.store = store_polling;
|
|
sysfs_attr_init(&msm_host->polling.attr);
|
|
msm_host->polling.attr.name = "polling";
|
|
msm_host->polling.attr.mode = 0644;
|
|
ret = device_create_file(&pdev->dev, &msm_host->polling);
|
|
if (ret)
|
|
goto remove_max_bus_bw_file;
|
|
}
|
|
|
|
msm_host->auto_cmd21_attr.show = show_auto_cmd21;
|
|
msm_host->auto_cmd21_attr.store = store_auto_cmd21;
|
|
sysfs_attr_init(&msm_host->auto_cmd21_attr.attr);
|
|
msm_host->auto_cmd21_attr.attr.name = "enable_auto_cmd21";
|
|
msm_host->auto_cmd21_attr.attr.mode = 0644;
|
|
ret = device_create_file(&pdev->dev, &msm_host->auto_cmd21_attr);
|
|
if (ret) {
|
|
pr_err("%s: %s: failed creating auto-cmd21 attr: %d\n",
|
|
mmc_hostname(host->mmc), __func__, ret);
|
|
device_remove_file(&pdev->dev, &msm_host->auto_cmd21_attr);
|
|
}
|
|
if (sdhci_msm_is_bootdevice(&pdev->dev))
|
|
mmc_flush_detect_work(host->mmc);
|
|
|
|
/* Successful initialization */
|
|
goto out;
|
|
|
|
remove_max_bus_bw_file:
|
|
device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
|
|
remove_host:
|
|
dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
|
|
pm_runtime_disable(&pdev->dev);
|
|
sdhci_remove_host(host, dead);
|
|
vreg_deinit:
|
|
sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
|
|
bus_unregister:
|
|
if (msm_host->msm_bus_vote.client_handle)
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
sdhci_msm_bus_unregister(msm_host);
|
|
sleep_clk_disable:
|
|
if (!IS_ERR(msm_host->sleep_clk))
|
|
clk_disable_unprepare(msm_host->sleep_clk);
|
|
ff_clk_disable:
|
|
if (!IS_ERR(msm_host->ff_clk))
|
|
clk_disable_unprepare(msm_host->ff_clk);
|
|
clk_disable:
|
|
if (!IS_ERR(msm_host->clk))
|
|
clk_disable_unprepare(msm_host->clk);
|
|
bus_aggr_clk_disable:
|
|
if (!IS_ERR(msm_host->bus_aggr_clk))
|
|
clk_disable_unprepare(msm_host->bus_aggr_clk);
|
|
pclk_disable:
|
|
if (!IS_ERR(msm_host->pclk))
|
|
clk_disable_unprepare(msm_host->pclk);
|
|
bus_clk_disable:
|
|
if (!IS_ERR_OR_NULL(msm_host->bus_clk))
|
|
clk_disable_unprepare(msm_host->bus_clk);
|
|
pltfm_free:
|
|
sdhci_pltfm_free(pdev);
|
|
out_host_free:
|
|
devm_kfree(&pdev->dev, msm_host);
|
|
out:
|
|
pr_debug("%s: Exit %s\n", dev_name(&pdev->dev), __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_remove(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
struct sdhci_msm_pltfm_data *pdata = msm_host->pdata;
|
|
int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups;
|
|
int i;
|
|
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
|
|
0xffffffff);
|
|
|
|
pr_debug("%s: %s Enter\n", dev_name(&pdev->dev), __func__);
|
|
if (!gpio_is_valid(msm_host->pdata->status_gpio))
|
|
device_remove_file(&pdev->dev, &msm_host->polling);
|
|
|
|
device_remove_file(&pdev->dev, &msm_host->auto_cmd21_attr);
|
|
device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
if (msm_host->pm_qos_group_enable) {
|
|
struct sdhci_msm_pm_qos_group *group;
|
|
|
|
for (i = 0; i < nr_groups; i++)
|
|
cancel_delayed_work_sync(
|
|
&msm_host->pm_qos[i].unvote_work);
|
|
|
|
device_remove_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_group_enable_attr);
|
|
device_remove_file(&msm_host->pdev->dev,
|
|
&msm_host->pm_qos_group_status_attr);
|
|
|
|
for (i = 0; i < nr_groups; i++) {
|
|
group = &msm_host->pm_qos[i];
|
|
pm_qos_remove_request(&group->req);
|
|
}
|
|
}
|
|
|
|
if (msm_host->pm_qos_irq.enabled) {
|
|
cancel_delayed_work_sync(&msm_host->pm_qos_irq.unvote_work);
|
|
device_remove_file(&pdev->dev,
|
|
&msm_host->pm_qos_irq.enable_attr);
|
|
device_remove_file(&pdev->dev,
|
|
&msm_host->pm_qos_irq.status_attr);
|
|
pm_qos_remove_request(&msm_host->pm_qos_irq.req);
|
|
}
|
|
|
|
if (msm_host->pm_qos_wq)
|
|
destroy_workqueue(msm_host->pm_qos_wq);
|
|
|
|
sdhci_remove_host(host, dead);
|
|
|
|
sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
|
|
|
|
sdhci_msm_setup_pins(pdata, true);
|
|
sdhci_msm_setup_pins(pdata, false);
|
|
|
|
if (msm_host->msm_bus_vote.client_handle) {
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
sdhci_msm_bus_unregister(msm_host);
|
|
}
|
|
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (!(host->mmc->card && mmc_card_sdio(host->mmc->card) &&
|
|
sdhci_is_valid_gpio_wakeup_int(msm_host) &&
|
|
mmc_card_wake_sdio_irq(host->mmc))) {
|
|
msm_host->sdio_pending_processing = false;
|
|
return 1;
|
|
}
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
if (enable) {
|
|
/* configure DAT1 gpio if applicable */
|
|
if (sdhci_is_valid_gpio_wakeup_int(msm_host)) {
|
|
msm_host->sdio_pending_processing = false;
|
|
ret = enable_irq_wake(msm_host->pdata->sdiowakeup_irq);
|
|
if (!ret)
|
|
sdhci_msm_cfg_sdiowakeup_gpio_irq(host, true);
|
|
goto out;
|
|
} else {
|
|
pr_err("%s: sdiowakeup_irq(%d) invalid\n",
|
|
mmc_hostname(host->mmc), enable);
|
|
}
|
|
} else {
|
|
if (sdhci_is_valid_gpio_wakeup_int(msm_host)) {
|
|
ret = disable_irq_wake(msm_host->pdata->sdiowakeup_irq);
|
|
sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
|
|
msm_host->sdio_pending_processing = false;
|
|
} else {
|
|
pr_err("%s: sdiowakeup_irq(%d)invalid\n",
|
|
mmc_hostname(host->mmc), enable);
|
|
|
|
}
|
|
}
|
|
out:
|
|
if (ret)
|
|
pr_err("%s: %s: %sable wakeup: failed: %d gpio: %d\n",
|
|
mmc_hostname(host->mmc), __func__, enable ? "en" : "dis",
|
|
ret, msm_host->pdata->sdiowakeup_irq);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int sdhci_msm_runtime_suspend(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
ktime_t start = ktime_get();
|
|
|
|
if (host->mmc->card && mmc_card_sdio(host->mmc->card))
|
|
goto defer_disable_host_irq;
|
|
|
|
sdhci_cfg_irq(host, false, true);
|
|
|
|
defer_disable_host_irq:
|
|
disable_irq(msm_host->pwr_irq);
|
|
|
|
/*
|
|
* Remove the vote immediately only if clocks are off in which
|
|
* case we might have queued work to remove vote but it may not
|
|
* be completed before runtime suspend or system suspend.
|
|
*/
|
|
if (!atomic_read(&msm_host->clks_on)) {
|
|
if (msm_host->msm_bus_vote.client_handle)
|
|
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
|
|
}
|
|
|
|
trace_sdhci_msm_runtime_suspend(mmc_hostname(host->mmc), 0,
|
|
ktime_to_us(ktime_sub(ktime_get(), start)));
|
|
return 0;
|
|
}
|
|
|
|
static int sdhci_msm_runtime_resume(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
ktime_t start = ktime_get();
|
|
|
|
if (host->mmc->card && mmc_card_sdio(host->mmc->card))
|
|
goto defer_enable_host_irq;
|
|
|
|
sdhci_cfg_irq(host, true, true);
|
|
|
|
defer_enable_host_irq:
|
|
enable_irq(msm_host->pwr_irq);
|
|
|
|
trace_sdhci_msm_runtime_resume(mmc_hostname(host->mmc), 0,
|
|
ktime_to_us(ktime_sub(ktime_get(), start)));
|
|
return 0;
|
|
}
|
|
|
|
static int sdhci_msm_suspend(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int ret = 0;
|
|
int sdio_cfg = 0;
|
|
ktime_t start = ktime_get();
|
|
|
|
if (gpio_is_valid(msm_host->pdata->status_gpio) &&
|
|
(msm_host->mmc->slot.cd_irq >= 0))
|
|
disable_irq(msm_host->mmc->slot.cd_irq);
|
|
|
|
if (pm_runtime_suspended(dev)) {
|
|
pr_debug("%s: %s: already runtime suspended\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
goto out;
|
|
}
|
|
ret = sdhci_msm_runtime_suspend(dev);
|
|
out:
|
|
sdhci_msm_disable_controller_clock(host);
|
|
if (host->mmc->card && mmc_card_sdio(host->mmc->card)) {
|
|
sdio_cfg = sdhci_msm_cfg_sdio_wakeup(host, true);
|
|
if (sdio_cfg)
|
|
sdhci_cfg_irq(host, false, true);
|
|
}
|
|
|
|
trace_sdhci_msm_suspend(mmc_hostname(host->mmc), ret,
|
|
ktime_to_us(ktime_sub(ktime_get(), start)));
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_resume(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int ret = 0;
|
|
int sdio_cfg = 0;
|
|
ktime_t start = ktime_get();
|
|
|
|
if (gpio_is_valid(msm_host->pdata->status_gpio) &&
|
|
(msm_host->mmc->slot.cd_irq >= 0))
|
|
enable_irq(msm_host->mmc->slot.cd_irq);
|
|
|
|
if (pm_runtime_suspended(dev)) {
|
|
pr_debug("%s: %s: runtime suspended, defer system resume\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
goto out;
|
|
}
|
|
|
|
ret = sdhci_msm_runtime_resume(dev);
|
|
out:
|
|
if (host->mmc->card && mmc_card_sdio(host->mmc->card)) {
|
|
sdio_cfg = sdhci_msm_cfg_sdio_wakeup(host, false);
|
|
if (sdio_cfg)
|
|
sdhci_cfg_irq(host, true, true);
|
|
}
|
|
|
|
trace_sdhci_msm_resume(mmc_hostname(host->mmc), ret,
|
|
ktime_to_us(ktime_sub(ktime_get(), start)));
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_suspend_noirq(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* ksdioirqd may be running, hence retry
|
|
* suspend in case the clocks are ON
|
|
*/
|
|
if (atomic_read(&msm_host->clks_on)) {
|
|
pr_warn("%s: %s: clock ON after suspend, aborting suspend\n",
|
|
mmc_hostname(host->mmc), __func__);
|
|
ret = -EAGAIN;
|
|
}
|
|
|
|
if (host->mmc->card && mmc_card_sdio(host->mmc->card))
|
|
if (msm_host->sdio_pending_processing)
|
|
ret = -EBUSY;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_msm_freeze(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct mmc_host *mmc = host->mmc;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
|
|
|
if (gpio_is_valid(msm_host->pdata->status_gpio) &&
|
|
(msm_host->mmc->slot.cd_irq >= 0))
|
|
mmc_gpiod_free_cd_irq(mmc);
|
|
return 0;
|
|
}
|
|
|
|
static int sdhci_msm_restore(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
if (mmc->inlinecrypt_support)
|
|
mmc->inlinecrypt_reset_needed = true;
|
|
|
|
mmc_gpiod_restore_cd_irq(mmc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops sdhci_msm_pmops = {
|
|
.suspend_late = sdhci_msm_suspend,
|
|
.resume_early = sdhci_msm_resume,
|
|
.runtime_suspend = sdhci_msm_runtime_suspend,
|
|
.runtime_resume = sdhci_msm_runtime_resume,
|
|
.suspend_noirq = sdhci_msm_suspend_noirq,
|
|
.freeze = sdhci_msm_freeze,
|
|
.restore = sdhci_msm_restore,
|
|
.thaw = sdhci_msm_restore,
|
|
};
|
|
|
|
#define SDHCI_MSM_PMOPS (&sdhci_msm_pmops)
|
|
|
|
#else
|
|
#define SDHCI_MSM_PMOPS NULL
|
|
#endif
|
|
static const struct of_device_id sdhci_msm_dt_match[] = {
|
|
{.compatible = "qcom,sdhci-msm"},
|
|
{.compatible = "qcom,sdhci-msm-v5"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);
|
|
|
|
static struct platform_driver sdhci_msm_driver = {
|
|
.probe = sdhci_msm_probe,
|
|
.remove = sdhci_msm_remove,
|
|
.driver = {
|
|
.name = "sdhci_msm",
|
|
.owner = THIS_MODULE,
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
.of_match_table = sdhci_msm_dt_match,
|
|
.pm = SDHCI_MSM_PMOPS,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(sdhci_msm_driver);
|
|
|
|
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Secure Digital Host Controller Interface driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|