linux/drivers/mmc/host/sdhci_am654.c
Faiz Abbas 4d627c8854 mmc: sdhci_am654: Remove Inverted Write Protect flag
The MMC/SD controllers on am65x and j721e don't in fact detect the write
protect line as inverted. No issues were detected because of this
because the sdwp line is not connected on any of the evms. Fix this by
removing the flag.

Fixes: 1accbced1c ("mmc: sdhci_am654: Add Support for 4 bit IP on J721E")
Cc: stable@vger.kernel.org
Signed-off-by: Faiz Abbas <faiz_abbas@ti.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Link: https://lore.kernel.org/r/20200108143301.1929-2-faiz_abbas@ti.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
2020-01-16 12:29:16 +01:00

594 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* sdhci_am654.c - SDHCI driver for TI's AM654 SOCs
*
* Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com
*
*/
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include "cqhci.h"
#include "sdhci-pltfm.h"
/* CTL_CFG Registers */
#define CTL_CFG_2 0x14
#define SLOTTYPE_MASK GENMASK(31, 30)
#define SLOTTYPE_EMBEDDED BIT(30)
/* PHY Registers */
#define PHY_CTRL1 0x100
#define PHY_CTRL2 0x104
#define PHY_CTRL3 0x108
#define PHY_CTRL4 0x10C
#define PHY_CTRL5 0x110
#define PHY_CTRL6 0x114
#define PHY_STAT1 0x130
#define PHY_STAT2 0x134
#define IOMUX_ENABLE_SHIFT 31
#define IOMUX_ENABLE_MASK BIT(IOMUX_ENABLE_SHIFT)
#define OTAPDLYENA_SHIFT 20
#define OTAPDLYENA_MASK BIT(OTAPDLYENA_SHIFT)
#define OTAPDLYSEL_SHIFT 12
#define OTAPDLYSEL_MASK GENMASK(15, 12)
#define STRBSEL_SHIFT 24
#define STRBSEL_4BIT_MASK GENMASK(27, 24)
#define STRBSEL_8BIT_MASK GENMASK(31, 24)
#define SEL50_SHIFT 8
#define SEL50_MASK BIT(SEL50_SHIFT)
#define SEL100_SHIFT 9
#define SEL100_MASK BIT(SEL100_SHIFT)
#define FREQSEL_SHIFT 8
#define FREQSEL_MASK GENMASK(10, 8)
#define DLL_TRIM_ICP_SHIFT 4
#define DLL_TRIM_ICP_MASK GENMASK(7, 4)
#define DR_TY_SHIFT 20
#define DR_TY_MASK GENMASK(22, 20)
#define ENDLL_SHIFT 1
#define ENDLL_MASK BIT(ENDLL_SHIFT)
#define DLLRDY_SHIFT 0
#define DLLRDY_MASK BIT(DLLRDY_SHIFT)
#define PDB_SHIFT 0
#define PDB_MASK BIT(PDB_SHIFT)
#define CALDONE_SHIFT 1
#define CALDONE_MASK BIT(CALDONE_SHIFT)
#define RETRIM_SHIFT 17
#define RETRIM_MASK BIT(RETRIM_SHIFT)
#define DRIVER_STRENGTH_50_OHM 0x0
#define DRIVER_STRENGTH_33_OHM 0x1
#define DRIVER_STRENGTH_66_OHM 0x2
#define DRIVER_STRENGTH_100_OHM 0x3
#define DRIVER_STRENGTH_40_OHM 0x4
#define CLOCK_TOO_SLOW_HZ 400000
/* Command Queue Host Controller Interface Base address */
#define SDHCI_AM654_CQE_BASE_ADDR 0x200
static struct regmap_config sdhci_am654_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.fast_io = true,
};
struct sdhci_am654_data {
struct regmap *base;
int otap_del_sel;
int trm_icp;
int drv_strength;
bool dll_on;
int strb_sel;
u32 flags;
};
struct sdhci_am654_driver_data {
const struct sdhci_pltfm_data *pdata;
u32 flags;
#define IOMUX_PRESENT (1 << 0)
#define FREQSEL_2_BIT (1 << 1)
#define STRBSEL_4_BIT (1 << 2)
#define DLL_PRESENT (1 << 3)
};
static void sdhci_am654_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
int sel50, sel100, freqsel;
u32 mask, val;
int ret;
if (sdhci_am654->dll_on) {
regmap_update_bits(sdhci_am654->base, PHY_CTRL1, ENDLL_MASK, 0);
sdhci_am654->dll_on = false;
}
sdhci_set_clock(host, clock);
if (clock > CLOCK_TOO_SLOW_HZ) {
/* Setup DLL Output TAP delay */
mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
val = (1 << OTAPDLYENA_SHIFT) |
(sdhci_am654->otap_del_sel << OTAPDLYSEL_SHIFT);
regmap_update_bits(sdhci_am654->base, PHY_CTRL4, mask, val);
/* Write to STRBSEL for HS400 speed mode */
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
if (sdhci_am654->flags & STRBSEL_4_BIT)
mask = STRBSEL_4BIT_MASK;
else
mask = STRBSEL_8BIT_MASK;
regmap_update_bits(sdhci_am654->base, PHY_CTRL4, mask,
sdhci_am654->strb_sel <<
STRBSEL_SHIFT);
}
if (sdhci_am654->flags & FREQSEL_2_BIT) {
switch (clock) {
case 200000000:
sel50 = 0;
sel100 = 0;
break;
case 100000000:
sel50 = 0;
sel100 = 1;
break;
default:
sel50 = 1;
sel100 = 0;
}
/* Configure PHY DLL frequency */
mask = SEL50_MASK | SEL100_MASK;
val = (sel50 << SEL50_SHIFT) | (sel100 << SEL100_SHIFT);
regmap_update_bits(sdhci_am654->base, PHY_CTRL5, mask,
val);
} else {
switch (clock) {
case 200000000:
freqsel = 0x0;
break;
default:
freqsel = 0x4;
}
regmap_update_bits(sdhci_am654->base, PHY_CTRL5,
FREQSEL_MASK,
freqsel << FREQSEL_SHIFT);
}
/* Configure DLL TRIM */
mask = DLL_TRIM_ICP_MASK;
val = sdhci_am654->trm_icp << DLL_TRIM_ICP_SHIFT;
/* Configure DLL driver strength */
mask |= DR_TY_MASK;
val |= sdhci_am654->drv_strength << DR_TY_SHIFT;
regmap_update_bits(sdhci_am654->base, PHY_CTRL1, mask, val);
/* Enable DLL */
regmap_update_bits(sdhci_am654->base, PHY_CTRL1, ENDLL_MASK,
0x1 << ENDLL_SHIFT);
/*
* Poll for DLL ready. Use a one second timeout.
* Works in all experiments done so far
*/
ret = regmap_read_poll_timeout(sdhci_am654->base, PHY_STAT1,
val, val & DLLRDY_MASK, 1000,
1000000);
if (ret) {
dev_err(mmc_dev(host->mmc), "DLL failed to relock\n");
return;
}
sdhci_am654->dll_on = true;
}
}
static void sdhci_j721e_4bit_set_clock(struct sdhci_host *host,
unsigned int clock)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
int val, mask;
mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
val = (1 << OTAPDLYENA_SHIFT) |
(sdhci_am654->otap_del_sel << OTAPDLYSEL_SHIFT);
regmap_update_bits(sdhci_am654->base, PHY_CTRL4, mask, val);
sdhci_set_clock(host, clock);
}
static void sdhci_am654_set_power(struct sdhci_host *host, unsigned char mode,
unsigned short vdd)
{
if (!IS_ERR(host->mmc->supply.vmmc)) {
struct mmc_host *mmc = host->mmc;
mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
}
sdhci_set_power_noreg(host, mode, vdd);
}
static void sdhci_am654_write_b(struct sdhci_host *host, u8 val, int reg)
{
unsigned char timing = host->mmc->ios.timing;
if (reg == SDHCI_HOST_CONTROL) {
switch (timing) {
/*
* According to the data manual, HISPD bit
* should not be set in these speed modes.
*/
case MMC_TIMING_SD_HS:
case MMC_TIMING_MMC_HS:
case MMC_TIMING_UHS_SDR12:
case MMC_TIMING_UHS_SDR25:
val &= ~SDHCI_CTRL_HISPD;
}
}
writeb(val, host->ioaddr + reg);
}
static struct sdhci_ops sdhci_am654_ops = {
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.set_bus_width = sdhci_set_bus_width,
.set_power = sdhci_am654_set_power,
.set_clock = sdhci_am654_set_clock,
.write_b = sdhci_am654_write_b,
.reset = sdhci_reset,
};
static const struct sdhci_pltfm_data sdhci_am654_pdata = {
.ops = &sdhci_am654_ops,
.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
};
static const struct sdhci_am654_driver_data sdhci_am654_drvdata = {
.pdata = &sdhci_am654_pdata,
.flags = IOMUX_PRESENT | FREQSEL_2_BIT | STRBSEL_4_BIT | DLL_PRESENT,
};
static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask)
{
int cmd_error = 0;
int data_error = 0;
if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error))
return intmask;
cqhci_irq(host->mmc, intmask, cmd_error, data_error);
return 0;
}
static struct sdhci_ops sdhci_j721e_8bit_ops = {
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.set_bus_width = sdhci_set_bus_width,
.set_power = sdhci_am654_set_power,
.set_clock = sdhci_am654_set_clock,
.write_b = sdhci_am654_write_b,
.irq = sdhci_am654_cqhci_irq,
.reset = sdhci_reset,
};
static const struct sdhci_pltfm_data sdhci_j721e_8bit_pdata = {
.ops = &sdhci_j721e_8bit_ops,
.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
};
static const struct sdhci_am654_driver_data sdhci_j721e_8bit_drvdata = {
.pdata = &sdhci_j721e_8bit_pdata,
.flags = DLL_PRESENT,
};
static struct sdhci_ops sdhci_j721e_4bit_ops = {
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.set_bus_width = sdhci_set_bus_width,
.set_power = sdhci_am654_set_power,
.set_clock = sdhci_j721e_4bit_set_clock,
.write_b = sdhci_am654_write_b,
.irq = sdhci_am654_cqhci_irq,
.reset = sdhci_reset,
};
static const struct sdhci_pltfm_data sdhci_j721e_4bit_pdata = {
.ops = &sdhci_j721e_4bit_ops,
.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
};
static const struct sdhci_am654_driver_data sdhci_j721e_4bit_drvdata = {
.pdata = &sdhci_j721e_4bit_pdata,
.flags = IOMUX_PRESENT,
};
static void sdhci_am654_dumpregs(struct mmc_host *mmc)
{
sdhci_dumpregs(mmc_priv(mmc));
}
static const struct cqhci_host_ops sdhci_am654_cqhci_ops = {
.enable = sdhci_cqe_enable,
.disable = sdhci_cqe_disable,
.dumpregs = sdhci_am654_dumpregs,
};
static int sdhci_am654_cqe_add_host(struct sdhci_host *host)
{
struct cqhci_host *cq_host;
int ret;
cq_host = devm_kzalloc(host->mmc->parent, sizeof(struct cqhci_host),
GFP_KERNEL);
if (!cq_host)
return -ENOMEM;
cq_host->mmio = host->ioaddr + SDHCI_AM654_CQE_BASE_ADDR;
cq_host->quirks |= CQHCI_QUIRK_SHORT_TXFR_DESC_SZ;
cq_host->caps |= CQHCI_TASK_DESC_SZ_128;
cq_host->ops = &sdhci_am654_cqhci_ops;
host->mmc->caps2 |= MMC_CAP2_CQE;
ret = cqhci_init(cq_host, host->mmc, 1);
return ret;
}
static int sdhci_am654_init(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
u32 ctl_cfg_2 = 0;
u32 mask;
u32 val;
int ret;
/* Reset OTAP to default value */
mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
regmap_update_bits(sdhci_am654->base, PHY_CTRL4, mask, 0x0);
if (sdhci_am654->flags & DLL_PRESENT) {
regmap_read(sdhci_am654->base, PHY_STAT1, &val);
if (~val & CALDONE_MASK) {
/* Calibrate IO lines */
regmap_update_bits(sdhci_am654->base, PHY_CTRL1,
PDB_MASK, PDB_MASK);
ret = regmap_read_poll_timeout(sdhci_am654->base,
PHY_STAT1, val,
val & CALDONE_MASK,
1, 20);
if (ret)
return ret;
}
}
/* Enable pins by setting IO mux to 0 */
if (sdhci_am654->flags & IOMUX_PRESENT)
regmap_update_bits(sdhci_am654->base, PHY_CTRL1,
IOMUX_ENABLE_MASK, 0);
/* Set slot type based on SD or eMMC */
if (host->mmc->caps & MMC_CAP_NONREMOVABLE)
ctl_cfg_2 = SLOTTYPE_EMBEDDED;
regmap_update_bits(sdhci_am654->base, CTL_CFG_2, SLOTTYPE_MASK,
ctl_cfg_2);
ret = sdhci_setup_host(host);
if (ret)
return ret;
ret = sdhci_am654_cqe_add_host(host);
if (ret)
goto err_cleanup_host;
ret = __sdhci_add_host(host);
if (ret)
goto err_cleanup_host;
return 0;
err_cleanup_host:
sdhci_cleanup_host(host);
return ret;
}
static int sdhci_am654_get_of_property(struct platform_device *pdev,
struct sdhci_am654_data *sdhci_am654)
{
struct device *dev = &pdev->dev;
int drv_strength;
int ret;
ret = device_property_read_u32(dev, "ti,otap-del-sel",
&sdhci_am654->otap_del_sel);
if (ret)
return ret;
if (sdhci_am654->flags & DLL_PRESENT) {
ret = device_property_read_u32(dev, "ti,trm-icp",
&sdhci_am654->trm_icp);
if (ret)
return ret;
ret = device_property_read_u32(dev, "ti,driver-strength-ohm",
&drv_strength);
if (ret)
return ret;
switch (drv_strength) {
case 50:
sdhci_am654->drv_strength = DRIVER_STRENGTH_50_OHM;
break;
case 33:
sdhci_am654->drv_strength = DRIVER_STRENGTH_33_OHM;
break;
case 66:
sdhci_am654->drv_strength = DRIVER_STRENGTH_66_OHM;
break;
case 100:
sdhci_am654->drv_strength = DRIVER_STRENGTH_100_OHM;
break;
case 40:
sdhci_am654->drv_strength = DRIVER_STRENGTH_40_OHM;
break;
default:
dev_err(dev, "Invalid driver strength\n");
return -EINVAL;
}
}
device_property_read_u32(dev, "ti,strobe-sel", &sdhci_am654->strb_sel);
sdhci_get_of_property(pdev);
return 0;
}
static const struct of_device_id sdhci_am654_of_match[] = {
{
.compatible = "ti,am654-sdhci-5.1",
.data = &sdhci_am654_drvdata,
},
{
.compatible = "ti,j721e-sdhci-8bit",
.data = &sdhci_j721e_8bit_drvdata,
},
{
.compatible = "ti,j721e-sdhci-4bit",
.data = &sdhci_j721e_4bit_drvdata,
},
{ /* sentinel */ }
};
static int sdhci_am654_probe(struct platform_device *pdev)
{
const struct sdhci_am654_driver_data *drvdata;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_am654_data *sdhci_am654;
const struct of_device_id *match;
struct sdhci_host *host;
struct resource *res;
struct clk *clk_xin;
struct device *dev = &pdev->dev;
void __iomem *base;
int ret;
match = of_match_node(sdhci_am654_of_match, pdev->dev.of_node);
drvdata = match->data;
host = sdhci_pltfm_init(pdev, drvdata->pdata, sizeof(*sdhci_am654));
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
sdhci_am654->flags = drvdata->flags;
clk_xin = devm_clk_get(dev, "clk_xin");
if (IS_ERR(clk_xin)) {
dev_err(dev, "clk_xin clock not found.\n");
ret = PTR_ERR(clk_xin);
goto err_pltfm_free;
}
pltfm_host->clk = clk_xin;
/* Clocks are enabled using pm_runtime */
pm_runtime_enable(dev);
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
pm_runtime_put_noidle(dev);
goto pm_runtime_disable;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base)) {
ret = PTR_ERR(base);
goto pm_runtime_put;
}
sdhci_am654->base = devm_regmap_init_mmio(dev, base,
&sdhci_am654_regmap_config);
if (IS_ERR(sdhci_am654->base)) {
dev_err(dev, "Failed to initialize regmap\n");
ret = PTR_ERR(sdhci_am654->base);
goto pm_runtime_put;
}
ret = sdhci_am654_get_of_property(pdev, sdhci_am654);
if (ret)
goto pm_runtime_put;
ret = mmc_of_parse(host->mmc);
if (ret) {
dev_err(dev, "parsing dt failed (%d)\n", ret);
goto pm_runtime_put;
}
ret = sdhci_am654_init(host);
if (ret)
goto pm_runtime_put;
return 0;
pm_runtime_put:
pm_runtime_put_sync(dev);
pm_runtime_disable:
pm_runtime_disable(dev);
err_pltfm_free:
sdhci_pltfm_free(pdev);
return ret;
}
static int sdhci_am654_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
int ret;
sdhci_remove_host(host, true);
ret = pm_runtime_put_sync(&pdev->dev);
if (ret < 0)
return ret;
pm_runtime_disable(&pdev->dev);
sdhci_pltfm_free(pdev);
return 0;
}
static struct platform_driver sdhci_am654_driver = {
.driver = {
.name = "sdhci-am654",
.of_match_table = sdhci_am654_of_match,
},
.probe = sdhci_am654_probe,
.remove = sdhci_am654_remove,
};
module_platform_driver(sdhci_am654_driver);
MODULE_DESCRIPTION("Driver for SDHCI Controller on TI's AM654 devices");
MODULE_AUTHOR("Faiz Abbas <faiz_abbas@ti.com>");
MODULE_LICENSE("GPL");