Merge branch 'for-next/tegra'
This commit is contained in:
commit
6d8a85d6c3
|
@ -25,3 +25,5 @@ CONFIG_CMD_TIMEOUT=y
|
|||
CONFIG_CMD_GPIO=y
|
||||
CONFIG_CMD_CLK=y
|
||||
CONFIG_DRIVER_SERIAL_NS16550=y
|
||||
CONFIG_MCI=y
|
||||
CONFIG_MCI_TEGRA=y
|
||||
|
|
|
@ -29,4 +29,9 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
sdhci@c8000600 {
|
||||
status = "okay";
|
||||
bus-width = <4>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -187,4 +187,8 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
sdhci@c8000600 {
|
||||
cd-gpios = <&gpio 23 0>; /* gpio PC7 */
|
||||
};
|
||||
};
|
||||
|
|
|
@ -46,4 +46,36 @@
|
|||
compatible = "nvidia,tegra20-pmc";
|
||||
reg = <0x7000e400 0x400>;
|
||||
};
|
||||
|
||||
sdhci@c8000000 {
|
||||
compatible = "nvidia,tegra20-sdhci";
|
||||
reg = <0xc8000000 0x200>;
|
||||
interrupts = <0 14 0x04>;
|
||||
clocks = <&tegra_car 14>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
sdhci@c8000200 {
|
||||
compatible = "nvidia,tegra20-sdhci";
|
||||
reg = <0xc8000200 0x200>;
|
||||
interrupts = <0 15 0x04>;
|
||||
clocks = <&tegra_car 9>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
sdhci@c8000400 {
|
||||
compatible = "nvidia,tegra20-sdhci";
|
||||
reg = <0xc8000400 0x200>;
|
||||
interrupts = <0 19 0x04>;
|
||||
clocks = <&tegra_car 69>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
sdhci@c8000600 {
|
||||
compatible = "nvidia,tegra20-sdhci";
|
||||
reg = <0xc8000600 0x200>;
|
||||
interrupts = <0 31 0x04>;
|
||||
clocks = <&tegra_car 15>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -46,6 +46,38 @@
|
|||
#define CRC_CLK_OUT_ENB_L_AC97 (1 << 3)
|
||||
#define CRC_CLK_OUT_ENB_L_CPU (1 << 0)
|
||||
|
||||
#define CRC_CCLK_BURST_POLICY 0x020
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_SHIFT 28
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_FIQ 8
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_IRQ 4
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_RUN 2
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_IDLE 1
|
||||
#define CRC_CCLK_BURST_POLICY_SYS_STATE_STDBY 0
|
||||
#define CRC_CCLK_BURST_POLICY_FIQ_SRC_SHIFT 12
|
||||
#define CRC_CCLK_BURST_POLICY_IRQ_SRC_SHIFT 8
|
||||
#define CRC_CCLK_BURST_POLICY_RUN_SRC_SHIFT 4
|
||||
#define CRC_CCLK_BURST_POLICY_IDLE_SRC_SHIFT 0
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_CLKM 0
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLC_OUT0 1
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_CLKS 2
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLM_OUT0 3
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLP_OUT0 4
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLP_OUT4 5
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLP_OUT3 6
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_CLKD 7
|
||||
#define CRC_CCLK_BURST_POLICY_SRC_PLLX_OUT0 8
|
||||
|
||||
#define CRC_SUPER_CCLK_DIV 0x024
|
||||
#define CRC_SUPER_CDIV_ENB (1 << 31)
|
||||
#define CRC_SUPER_CDIV_DIS_FROM_COP_FIQ (1 << 27)
|
||||
#define CRC_SUPER_CDIV_DIS_FROM_CPU_FIQ (1 << 26)
|
||||
#define CRC_SUPER_CDIV_DIS_FROM_COP_IRQ (1 << 25)
|
||||
#define CRC_SUPER_CDIV_DIS_FROM_CPU_IRQ (1 << 24)
|
||||
#define CRC_SUPER_CDIV_DIVIDEND_SHIFT 8
|
||||
#define CRC_SUPER_CDIV_DIVIDEND_MASK (0xff << CRC_SUPER_CDIV_DIVIDEND_SHIFT)
|
||||
#define CRC_SUPER_CDIV_DIVISOR_SHIFT 0
|
||||
#define CRC_SUPER_CDIV_DIVISOR_MASK (0xff << CRC_SUPER_CDIV_DIVISOR_SHIFT)
|
||||
|
||||
#define CRC_SCLK_BURST_POLICY 0x028
|
||||
#define CRC_SCLK_BURST_POLICY_SYS_STATE_SHIFT 28
|
||||
#define CRC_SCLK_BURST_POLICY_SYS_STATE_FIQ 8
|
||||
|
@ -53,6 +85,18 @@
|
|||
#define CRC_SCLK_BURST_POLICY_SYS_STATE_RUN 2
|
||||
#define CRC_SCLK_BURST_POLICY_SYS_STATE_IDLE 1
|
||||
#define CRC_SCLK_BURST_POLICY_SYS_STATE_STDBY 0
|
||||
#define CRC_SCLK_BURST_POLICY_FIQ_SRC_SHIFT 12
|
||||
#define CRC_SCLK_BURST_POLICY_IRQ_SRC_SHIFT 8
|
||||
#define CRC_SCLK_BURST_POLICY_RUN_SRC_SHIFT 4
|
||||
#define CRC_SCLK_BURST_POLICY_IDLE_SRC_SHIFT 0
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_CLKM 0
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_PLLC_OUT1 1
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_PLLP_OUT4 2
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_PLLP_OUT3 3
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_PLLP_OUT2 4
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_CLKD 5
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_CLKS 6
|
||||
#define CRC_SCLK_BURST_POLICY_SRC_PLLM_OUT1 7
|
||||
|
||||
#define CRC_SUPER_SCLK_DIV 0x02c
|
||||
#define CRC_SUPER_SDIV_ENB (1 << 31)
|
||||
|
|
|
@ -103,6 +103,8 @@ static void init_pllx(void)
|
|||
CRC_OSC_CTRL_OSC_FREQ_MASK) >> CRC_OSC_CTRL_OSC_FREQ_SHIFT;
|
||||
|
||||
conf = &pllx_config_table[chiptype][osc_freq];
|
||||
/* we are not relocated yet - globals are a bit more tricky here */
|
||||
conf = (struct pll_config *)((char *)conf - get_runtime_offset());
|
||||
|
||||
/* set PLL bypass and frequency parameters */
|
||||
reg = CRC_PLLX_BASE_BYPASS;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <asm/barebox-arm.h>
|
||||
#include <mach/lowlevel.h>
|
||||
#include <mach/tegra20-pmc.h>
|
||||
#include <mach/tegra20-car.h>
|
||||
|
||||
void tegra_maincomplex_entry(void)
|
||||
{
|
||||
|
@ -27,6 +28,14 @@ void tegra_maincomplex_entry(void)
|
|||
|
||||
arm_cpu_lowlevel_init();
|
||||
|
||||
/* switch to PLLX */
|
||||
writel(CRC_CCLK_BURST_POLICY_SYS_STATE_RUN <<
|
||||
CRC_CCLK_BURST_POLICY_SYS_STATE_SHIFT |
|
||||
CRC_CCLK_BURST_POLICY_SRC_PLLX_OUT0 <<
|
||||
CRC_CCLK_BURST_POLICY_RUN_SRC_SHIFT,
|
||||
TEGRA_CLK_RESET_BASE + CRC_CCLK_BURST_POLICY);
|
||||
writel(CRC_SUPER_CDIV_ENB, TEGRA_CLK_RESET_BASE + CRC_SUPER_CCLK_DIV);
|
||||
|
||||
switch (tegra_get_chiptype()) {
|
||||
case TEGRA20:
|
||||
rambase = 0x0;
|
||||
|
|
|
@ -295,6 +295,20 @@ static void tegra20_periph_init(void)
|
|||
clks[uarte] = tegra_clk_register_periph_nodiv("uarte", mux_pllpcm_clkm,
|
||||
ARRAY_SIZE(mux_pllpcm_clkm), car_base,
|
||||
CRC_CLK_SOURCE_UARTE, uarte, TEGRA_PERIPH_ON_APB);
|
||||
|
||||
/* peripheral clocks with a divider */
|
||||
clks[sdmmc1] = tegra_clk_register_periph("sdmmc1", mux_pllpcm_clkm,
|
||||
ARRAY_SIZE(mux_pllpcm_clkm), car_base,
|
||||
CRC_CLK_SOURCE_SDMMC1, sdmmc1, 1);
|
||||
clks[sdmmc2] = tegra_clk_register_periph("sdmmc2", mux_pllpcm_clkm,
|
||||
ARRAY_SIZE(mux_pllpcm_clkm), car_base,
|
||||
CRC_CLK_SOURCE_SDMMC2, sdmmc2, 1);
|
||||
clks[sdmmc3] = tegra_clk_register_periph("sdmmc3", mux_pllpcm_clkm,
|
||||
ARRAY_SIZE(mux_pllpcm_clkm), car_base,
|
||||
CRC_CLK_SOURCE_SDMMC3, sdmmc3, 1);
|
||||
clks[sdmmc4] = tegra_clk_register_periph("sdmmc4", mux_pllpcm_clkm,
|
||||
ARRAY_SIZE(mux_pllpcm_clkm), car_base,
|
||||
CRC_CLK_SOURCE_SDMMC4, sdmmc4, 1);
|
||||
}
|
||||
|
||||
static struct tegra_clk_init_table init_table[] = {
|
||||
|
@ -310,6 +324,10 @@ static struct tegra_clk_init_table init_table[] = {
|
|||
{uartc, pll_p, 0, 1},
|
||||
{uartd, pll_p, 0, 1},
|
||||
{uarte, pll_p, 0, 1},
|
||||
{sdmmc1, pll_p, 48000000, 0},
|
||||
{sdmmc2, pll_p, 48000000, 0},
|
||||
{sdmmc3, pll_p, 48000000, 0},
|
||||
{sdmmc4, pll_p, 48000000, 0},
|
||||
{clk_max, clk_max, 0, 0}, /* sentinel */
|
||||
};
|
||||
|
||||
|
@ -325,6 +343,13 @@ static int tegra20_car_probe(struct device_d *dev)
|
|||
|
||||
tegra_init_from_table(init_table, clks, clk_max);
|
||||
|
||||
/* speed up system bus */
|
||||
writel(CRC_SCLK_BURST_POLICY_SYS_STATE_RUN <<
|
||||
CRC_SCLK_BURST_POLICY_SYS_STATE_SHIFT |
|
||||
CRC_SCLK_BURST_POLICY_SRC_PLLC_OUT1 <<
|
||||
CRC_SCLK_BURST_POLICY_RUN_SRC_SHIFT,
|
||||
car_base + CRC_SCLK_BURST_POLICY);
|
||||
|
||||
clk_data.clks = clks;
|
||||
clk_data.clk_num = ARRAY_SIZE(clks);
|
||||
of_clk_add_provider(dev->device_node, of_clk_src_onecell_get,
|
||||
|
|
|
@ -106,12 +106,10 @@ static int tegra_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|||
{
|
||||
/* If gpio is in output mode then read from the out value */
|
||||
if ((tegra_gpio_readl(GPIO_OE(offset)) >> GPIO_BIT(offset)) & 1) {
|
||||
printf("GPIO output mode\n");
|
||||
return (tegra_gpio_readl(GPIO_OUT(offset)) >>
|
||||
GPIO_BIT(offset)) & 0x1;
|
||||
}
|
||||
|
||||
printf("GPIO input mode\n");
|
||||
return (tegra_gpio_readl(GPIO_IN(offset)) >> GPIO_BIT(offset)) & 0x1;
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,13 @@ config MCI_MMCI
|
|||
Enable this entry to add support to read and write SD cards on a
|
||||
ARM AMBA PL180.
|
||||
|
||||
config MCI_TEGRA
|
||||
bool "Tegra SD/MMC"
|
||||
depends on ARCH_TEGRA
|
||||
help
|
||||
Enable this to support SD and MMC card read/write on a Tegra based
|
||||
systems.
|
||||
|
||||
config MCI_SPI
|
||||
bool "MMC/SD over SPI"
|
||||
select CRC7
|
||||
|
|
|
@ -7,6 +7,7 @@ obj-$(CONFIG_MCI_MXS) += mxs.o
|
|||
obj-$(CONFIG_MCI_OMAP_HSMMC) += omap_hsmmc.o
|
||||
obj-$(CONFIG_MCI_PXA) += pxamci.o
|
||||
obj-$(CONFIG_MCI_S3C) += s3c.o
|
||||
obj-$(CONFIG_MCI_TEGRA) += tegra-sdmmc.o
|
||||
obj-$(CONFIG_MCI_SPI) += mci_spi.o
|
||||
obj-$(CONFIG_MCI_DW) += dw_mmc.o
|
||||
obj-$(CONFIG_MCI_MMCI) += mmci.o
|
||||
|
|
|
@ -0,0 +1,464 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Lucas Stach <l.stach@pengutronix.de>
|
||||
*
|
||||
* Partly based on code (C) Copyright 2010-2013
|
||||
* NVIDIA Corporation <www.nvidia.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <asm/mmu.h>
|
||||
#include <common.h>
|
||||
#include <clock.h>
|
||||
#include <driver.h>
|
||||
#include <gpio.h>
|
||||
#include <init.h>
|
||||
#include <io.h>
|
||||
#include <malloc.h>
|
||||
#include <mci.h>
|
||||
#include <of_gpio.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include "sdhci.h"
|
||||
|
||||
#define TEGRA_SDMMC_PRESENT_STATE 0x024
|
||||
#define TEGRA_SDMMC_PRESENT_STATE_CMD_INHIBIT_CMD (1 << 0)
|
||||
#define TEGRA_SDMMC_PRESENT_STATE_CMD_INHIBIT_DAT (1 << 1)
|
||||
|
||||
#define TEGRA_SDMMC_PWR_CNTL 0x028
|
||||
#define TEGRA_SDMMC_PWR_CNTL_SD_BUS (1 << 8)
|
||||
#define TEGRA_SDMMC_PWR_CNTL_33_V (7 << 9)
|
||||
|
||||
#define TEGRA_SDMMC_CLK_CNTL 0x02c
|
||||
#define TEGRA_SDMMC_CLK_CNTL_SW_RESET_FOR_ALL (1 << 24)
|
||||
#define TEGRA_SDMMC_CLK_CNTL_SD_CLOCK_EN (1 << 2)
|
||||
#define TEGRA_SDMMC_CLK_INTERNAL_CLOCK_STABLE (1 << 1)
|
||||
#define TEGRA_SDMMC_CLK_CNTL_INTERNAL_CLOCK_EN (1 << 0)
|
||||
|
||||
#define TEGRA_SDMMC_INTERRUPT_STATUS_ERR_INTERRUPT (1 << 15)
|
||||
#define TEGRA_SDMMC_INTERRUPT_STATUS_CMD_TIMEOUT (1 << 16)
|
||||
|
||||
#define TEGRA_SDMMC_INT_STAT_EN 0x034
|
||||
#define TEGRA_SDMMC_INT_STAT_EN_CMD_COMPLETE (1 << 0)
|
||||
#define TEGRA_SDMMC_INT_STAT_EN_XFER_COMPLETE (1 << 1)
|
||||
#define TEGRA_SDMMC_INT_STAT_EN_DMA_INTERRUPT (1 << 3)
|
||||
#define TEGRA_SDMMC_INT_STAT_EN_BUFFER_WRITE_READY (1 << 4)
|
||||
#define TEGRA_SDMMC_INT_STAT_EN_BUFFER_READ_READY (1 << 5)
|
||||
|
||||
#define TEGRA_SDMMC_INT_SIG_EN 0x038
|
||||
#define TEGRA_SDMMC_INT_SIG_EN_XFER_COMPLETE (1 << 1)
|
||||
|
||||
struct tegra_sdmmc_host {
|
||||
struct mci_host mci;
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
int gpio_cd, gpio_pwr;
|
||||
};
|
||||
#define to_tegra_sdmmc_host(mci) container_of(mci, struct tegra_sdmmc_host, mci)
|
||||
|
||||
static int tegra_sdmmc_wait_inhibit(struct tegra_sdmmc_host *host,
|
||||
struct mci_cmd *cmd, struct mci_data *data,
|
||||
unsigned int timeout)
|
||||
{
|
||||
u32 val = TEGRA_SDMMC_PRESENT_STATE_CMD_INHIBIT_CMD;
|
||||
|
||||
/*
|
||||
* We shouldn't wait for data inhibit for stop commands, even
|
||||
* though they might use busy signaling
|
||||
*/
|
||||
if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY))
|
||||
val |= TEGRA_SDMMC_PRESENT_STATE_CMD_INHIBIT_DAT;
|
||||
|
||||
wait_on_timeout(timeout * MSECOND,
|
||||
!(readl(host->regs + TEGRA_SDMMC_PRESENT_STATE) & val));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sdmmc_send_cmd(struct mci_host *mci, struct mci_cmd *cmd,
|
||||
struct mci_data *data)
|
||||
{
|
||||
struct tegra_sdmmc_host *host = to_tegra_sdmmc_host(mci);
|
||||
u32 val = 0;
|
||||
int ret;
|
||||
|
||||
ret = tegra_sdmmc_wait_inhibit(host, cmd, data, 10);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set up for a data transfer if we have one */
|
||||
if (data) {
|
||||
if (data->flags & MMC_DATA_WRITE) {
|
||||
dma_flush_range((unsigned long)data->src,
|
||||
(unsigned long)(data->src +
|
||||
data->blocks * 512));
|
||||
writel((u32)data->src, host->regs + SDHCI_DMA_ADDRESS);
|
||||
} else {
|
||||
dma_clean_range((unsigned long)data->src,
|
||||
(unsigned long)(data->src +
|
||||
data->blocks * 512));
|
||||
writel((u32)data->dest, host->regs + SDHCI_DMA_ADDRESS);
|
||||
}
|
||||
|
||||
writel((7 << 12) | data->blocks << 16 | data->blocksize,
|
||||
host->regs + SDHCI_BLOCK_SIZE__BLOCK_COUNT);
|
||||
}
|
||||
|
||||
writel(cmd->cmdarg, host->regs + SDHCI_ARGUMENT);
|
||||
|
||||
if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY))
|
||||
return -1;
|
||||
|
||||
if (data) {
|
||||
if (data->blocks > 1)
|
||||
val |= TRANSFER_MODE_MSBSEL;
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
val |= TRANSFER_MODE_DTDSEL;
|
||||
|
||||
val |= TRANSFER_MODE_DMAEN | TRANSFER_MODE_BCEN;
|
||||
}
|
||||
|
||||
if (!(cmd->resp_type & MMC_RSP_PRESENT))
|
||||
val |= COMMAND_RSPTYP_NONE;
|
||||
else if (cmd->resp_type & MMC_RSP_136)
|
||||
val |= COMMAND_RSPTYP_136;
|
||||
else if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
val |= COMMAND_RSPTYP_48_BUSY;
|
||||
else
|
||||
val |= COMMAND_RSPTYP_48;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
val |= COMMAND_CCCEN;
|
||||
if (cmd->resp_type & MMC_RSP_OPCODE)
|
||||
val |= COMMAND_CICEN;
|
||||
|
||||
if (data)
|
||||
val |= COMMAND_DPSEL;
|
||||
|
||||
writel(COMMAND_CMD(cmd->cmdidx) | val,
|
||||
host->regs + SDHCI_TRANSFER_MODE__COMMAND);
|
||||
|
||||
ret = wait_on_timeout(100 * MSECOND,
|
||||
(val = readl(host->regs + SDHCI_INT_STATUS))
|
||||
& IRQSTAT_CC);
|
||||
|
||||
if (ret) {
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((val & IRQSTAT_CC) && !data)
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
|
||||
if (val & TEGRA_SDMMC_INTERRUPT_STATUS_CMD_TIMEOUT) {
|
||||
/* Timeout Error */
|
||||
dev_dbg(mci->hw_dev, "timeout: %08x cmd %d\n", val, cmd->cmdidx);
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
return -ETIMEDOUT;
|
||||
} else if (val & TEGRA_SDMMC_INTERRUPT_STATUS_ERR_INTERRUPT) {
|
||||
/* Error Interrupt */
|
||||
dev_dbg(mci->hw_dev, "error: %08x cmd %d\n", val, cmd->cmdidx);
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
u32 cmdrsp[4];
|
||||
|
||||
cmdrsp[3] = readl(host->regs + SDHCI_RESPONSE_3);
|
||||
cmdrsp[2] = readl(host->regs + SDHCI_RESPONSE_2);
|
||||
cmdrsp[1] = readl(host->regs + SDHCI_RESPONSE_1);
|
||||
cmdrsp[0] = readl(host->regs + SDHCI_RESPONSE_0);
|
||||
cmd->response[0] = (cmdrsp[3] << 8) | (cmdrsp[2] >> 24);
|
||||
cmd->response[1] = (cmdrsp[2] << 8) | (cmdrsp[1] >> 24);
|
||||
cmd->response[2] = (cmdrsp[1] << 8) | (cmdrsp[0] >> 24);
|
||||
cmd->response[3] = (cmdrsp[0] << 8);
|
||||
} else if (cmd->resp_type & MMC_RSP_BUSY) {
|
||||
ret = wait_on_timeout(100 * MSECOND,
|
||||
readl(host->regs + TEGRA_SDMMC_PRESENT_STATE)
|
||||
& (1 << 20));
|
||||
|
||||
if (ret) {
|
||||
dev_err(mci->hw_dev, "card is still busy\n");
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cmd->response[0] = readl(host->regs + SDHCI_RESPONSE_0);
|
||||
} else {
|
||||
cmd->response[0] = readl(host->regs + SDHCI_RESPONSE_0);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
uint64_t start = get_time_ns();
|
||||
|
||||
while (1) {
|
||||
val = readl(host->regs + SDHCI_INT_STATUS);
|
||||
|
||||
if (val & TEGRA_SDMMC_INTERRUPT_STATUS_ERR_INTERRUPT) {
|
||||
/* Error Interrupt */
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
dev_err(mci->hw_dev,
|
||||
"error during transfer: 0x%08x\n", val);
|
||||
return -EIO;
|
||||
} else if (val & IRQSTAT_DINT) {
|
||||
/*
|
||||
* DMA Interrupt, restart the transfer where
|
||||
* it was interrupted.
|
||||
*/
|
||||
u32 address = readl(host->regs +
|
||||
SDHCI_DMA_ADDRESS);
|
||||
|
||||
writel(IRQSTAT_DINT,
|
||||
host->regs + SDHCI_INT_STATUS);
|
||||
writel(address, host->regs + SDHCI_DMA_ADDRESS);
|
||||
} else if (val & IRQSTAT_TC) {
|
||||
/* Transfer Complete */;
|
||||
break;
|
||||
} else if (is_timeout(start, 2 * SECOND)) {
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
dev_err(mci->hw_dev, "MMC Timeout\n"
|
||||
" Interrupt status 0x%08x\n"
|
||||
" Interrupt status enable 0x%08x\n"
|
||||
" Interrupt signal enable 0x%08x\n"
|
||||
" Present status 0x%08x\n",
|
||||
val,
|
||||
readl(host->regs + SDHCI_INT_ENABLE),
|
||||
readl(host->regs + SDHCI_SIGNAL_ENABLE),
|
||||
readl(host->regs + SDHCI_PRESENT_STATE));
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
writel(val, host->regs + SDHCI_INT_STATUS);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
dma_inv_range((unsigned long)data->dest,
|
||||
(unsigned long)(data->dest +
|
||||
data->blocks * 512));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_sdmmc_set_clock(struct tegra_sdmmc_host *host, u32 clock)
|
||||
{
|
||||
u32 prediv = 1, adjusted_clock = clock, val;
|
||||
|
||||
while (adjusted_clock < 3200000) {
|
||||
prediv *= 2;
|
||||
adjusted_clock = clock * prediv * 2;
|
||||
}
|
||||
|
||||
/* clear clock related bits */
|
||||
val = readl(host->regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
val &= 0xffff0000;
|
||||
writel(val, host->regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
|
||||
/* set new frequency */
|
||||
val |= prediv << 8;
|
||||
val |= TEGRA_SDMMC_CLK_CNTL_INTERNAL_CLOCK_EN;
|
||||
writel(val, host->regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
|
||||
clk_set_rate(host->clk, adjusted_clock);
|
||||
|
||||
/* wait for controller to settle */
|
||||
wait_on_timeout(10 * MSECOND,
|
||||
!(readl(host->regs + TEGRA_SDMMC_CLK_CNTL) &
|
||||
TEGRA_SDMMC_CLK_INTERNAL_CLOCK_STABLE));
|
||||
|
||||
/* enable card clock */
|
||||
val |= TEGRA_SDMMC_CLK_CNTL_SD_CLOCK_EN;
|
||||
writel(val, host->regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
}
|
||||
|
||||
static void tegra_sdmmc_set_ios(struct mci_host *mci, struct mci_ios *ios)
|
||||
{
|
||||
struct tegra_sdmmc_host *host = to_tegra_sdmmc_host(mci);
|
||||
u32 val;
|
||||
|
||||
/* set clock */
|
||||
if (ios->clock)
|
||||
tegra_sdmmc_set_clock(host, ios->clock);
|
||||
|
||||
/* set bus width */
|
||||
val = readl(host->regs + TEGRA_SDMMC_PWR_CNTL);
|
||||
val &= ~(0x21);
|
||||
|
||||
if (ios->bus_width == MMC_BUS_WIDTH_8)
|
||||
val |= (1 << 5);
|
||||
else if (ios->bus_width == MMC_BUS_WIDTH_4)
|
||||
val |= (1 << 1);
|
||||
|
||||
writel(val, host->regs + TEGRA_SDMMC_PWR_CNTL);
|
||||
}
|
||||
|
||||
static int tegra_sdmmc_init(struct mci_host *mci, struct device_d *dev)
|
||||
{
|
||||
struct tegra_sdmmc_host *host = to_tegra_sdmmc_host(mci);
|
||||
void __iomem *regs = host->regs;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* reset controller */
|
||||
writel(TEGRA_SDMMC_CLK_CNTL_SW_RESET_FOR_ALL,
|
||||
regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
|
||||
ret = wait_on_timeout(100 * MSECOND,
|
||||
!(readl(regs + TEGRA_SDMMC_CLK_CNTL) &
|
||||
TEGRA_SDMMC_CLK_CNTL_SW_RESET_FOR_ALL));
|
||||
if (ret) {
|
||||
dev_err(dev, "timeout while reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set power */
|
||||
val = readl(regs + TEGRA_SDMMC_PWR_CNTL);
|
||||
val &= ~(0xff << 8);
|
||||
val |= TEGRA_SDMMC_PWR_CNTL_33_V | TEGRA_SDMMC_PWR_CNTL_SD_BUS;
|
||||
writel(val, regs + TEGRA_SDMMC_PWR_CNTL);
|
||||
|
||||
/* setup signaling */
|
||||
writel(0xffffffff, regs + TEGRA_SDMMC_INT_STAT_EN);
|
||||
writel(0xffffffff, regs + TEGRA_SDMMC_INT_SIG_EN);
|
||||
|
||||
writel(0xe << 16, regs + TEGRA_SDMMC_CLK_CNTL);
|
||||
|
||||
val = readl(regs + TEGRA_SDMMC_INT_STAT_EN);
|
||||
val &= ~(0xffff);
|
||||
val = (TEGRA_SDMMC_INT_STAT_EN_CMD_COMPLETE |
|
||||
TEGRA_SDMMC_INT_STAT_EN_XFER_COMPLETE |
|
||||
TEGRA_SDMMC_INT_STAT_EN_DMA_INTERRUPT |
|
||||
TEGRA_SDMMC_INT_STAT_EN_BUFFER_WRITE_READY |
|
||||
TEGRA_SDMMC_INT_STAT_EN_BUFFER_READ_READY);
|
||||
writel(val, regs + TEGRA_SDMMC_INT_STAT_EN);
|
||||
|
||||
val = readl(regs + TEGRA_SDMMC_INT_SIG_EN);
|
||||
val &= ~(0xffff);
|
||||
val = TEGRA_SDMMC_INT_SIG_EN_XFER_COMPLETE;
|
||||
writel(val, regs + TEGRA_SDMMC_INT_SIG_EN);
|
||||
|
||||
tegra_sdmmc_set_clock(host, 400000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sdmmc_card_present(struct mci_host *mci)
|
||||
{
|
||||
struct tegra_sdmmc_host *host = to_tegra_sdmmc_host(mci);
|
||||
int ret;
|
||||
|
||||
if (gpio_is_valid(host->gpio_cd)) {
|
||||
ret = gpio_direction_input(host->gpio_cd);
|
||||
if (ret)
|
||||
return 0;
|
||||
return gpio_get_value(host->gpio_cd) ? 0 : 1;
|
||||
}
|
||||
|
||||
return !(readl(host->regs + SDHCI_PRESENT_STATE) & PRSSTAT_WPSPL);
|
||||
}
|
||||
|
||||
static int tegra_sdmmc_detect(struct device_d *dev)
|
||||
{
|
||||
struct tegra_sdmmc_host *host = dev->priv;
|
||||
|
||||
return mci_detect_card(&host->mci);
|
||||
}
|
||||
|
||||
static void tegra_sdmmc_parse_dt(struct tegra_sdmmc_host *host)
|
||||
{
|
||||
struct device_node *np = host->mci.hw_dev->device_node;
|
||||
|
||||
host->gpio_cd = of_get_named_gpio(np, "cd-gpios", 0);
|
||||
host->gpio_pwr = of_get_named_gpio(np, "power-gpios", 0);
|
||||
mci_of_parse(&host->mci);
|
||||
}
|
||||
|
||||
static int tegra_sdmmc_probe(struct device_d *dev)
|
||||
{
|
||||
struct tegra_sdmmc_host *host;
|
||||
struct mci_host *mci;
|
||||
int ret;
|
||||
|
||||
host = xzalloc(sizeof(*host));
|
||||
mci = &host->mci;
|
||||
|
||||
host->clk = clk_get(dev, NULL);
|
||||
if (IS_ERR(host->clk))
|
||||
return PTR_ERR(host->clk);
|
||||
|
||||
host->regs = dev_request_mem_region(dev, 0);
|
||||
if (!host->regs) {
|
||||
dev_err(dev, "could not get iomem region\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mci->hw_dev = dev;
|
||||
mci->f_max = 48000000;
|
||||
mci->f_min = 375000;
|
||||
tegra_sdmmc_parse_dt(host);
|
||||
|
||||
if (gpio_is_valid(host->gpio_pwr)) {
|
||||
ret = gpio_request(host->gpio_pwr, "tegra_sdmmc_power");
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to allocate power gpio\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
gpio_direction_output(host->gpio_pwr, 1);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(host->gpio_cd)) {
|
||||
ret = gpio_request(host->gpio_cd, "tegra_sdmmc_cd");
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to allocate cd gpio\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
gpio_direction_input(host->gpio_cd);
|
||||
}
|
||||
|
||||
clk_enable(host->clk);
|
||||
|
||||
mci->init = tegra_sdmmc_init;
|
||||
mci->card_present = tegra_sdmmc_card_present;
|
||||
mci->set_ios = tegra_sdmmc_set_ios;
|
||||
mci->send_cmd = tegra_sdmmc_send_cmd;
|
||||
mci->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
mci->host_caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA |
|
||||
MMC_CAP_MMC_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED_52MHZ |
|
||||
MMC_CAP_SD_HIGHSPEED;
|
||||
|
||||
dev->priv = host;
|
||||
dev->detect = tegra_sdmmc_detect;
|
||||
|
||||
return mci_register(&host->mci);
|
||||
}
|
||||
|
||||
static __maybe_unused struct of_device_id tegra_sdmmc_compatible[] = {
|
||||
{
|
||||
.compatible = "nvidia,tegra30-sdhci",
|
||||
}, {
|
||||
.compatible = "nvidia,tegra20-sdhci",
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
|
||||
static struct driver_d tegra_sdmmc_driver = {
|
||||
.name = "tegra-sdmmc",
|
||||
.probe = tegra_sdmmc_probe,
|
||||
.of_compatible = DRV_OF_COMPAT(tegra_sdmmc_compatible),
|
||||
};
|
||||
device_platform_driver(tegra_sdmmc_driver);
|
|
@ -25,7 +25,7 @@ int of_get_named_gpio_flags(struct device_node *np, const char *propname,
|
|||
ret = of_parse_phandle_with_args(np, propname, "#gpio-cells",
|
||||
index, &out_args);
|
||||
if (ret) {
|
||||
pr_err("%s: cannot parse %s property: %d\n",
|
||||
pr_debug("%s: cannot parse %s property: %d\n",
|
||||
__func__, propname, ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue