| /* |
| * Copyright (c) 2014 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 <config.h> |
| #include <common.h> |
| #include <command.h> |
| #include <mmc.h> |
| #include <part.h> |
| #include <malloc.h> |
| #include <asm/io.h> |
| #include <asm-generic/errno.h> |
| #include <asm/arch-qca-common/qca_common.h> |
| #include "qca_mmc.h" |
| |
| static inline void qca_reg_wr_delay(qca_mmc *host) |
| { |
| while (readl(host->base + MCI_STATUS2) & MCI_MCLK_REG_WR_ACTIVE); |
| } |
| |
| static inline void |
| qca_start_command_exec(qca_mmc *host, struct mmc_cmd *cmd) |
| { |
| unsigned int c = 0; |
| unsigned int arg = cmd->cmdarg; |
| |
| writel(MCI_CLEAR_STATIC_MASK, host->base + MMCICLEAR); |
| |
| qca_reg_wr_delay(host); |
| |
| c |= (cmd->cmdidx | MCI_CPSM_ENABLE); |
| |
| if (cmd->resp_type & MMC_RSP_PRESENT) { |
| if (cmd->resp_type & MMC_RSP_136) |
| c |= MCI_CPSM_LONGRSP; |
| c |= MCI_CPSM_RESPONSE; |
| } |
| |
| if ((cmd->resp_type & MMC_RSP_R1b) == MMC_RSP_R1b) { |
| c |= MCI_CPSM_PROGENA; |
| } |
| |
| writel(arg, host->base + MMCIARGUMENT); |
| writel(c, host->base + MMCICOMMAND); |
| qca_reg_wr_delay(host); |
| } |
| |
| static int |
| qca_start_command(qca_mmc *host, struct mmc_cmd *cmd) |
| { |
| unsigned int status = 0; |
| int rc = 0; |
| |
| qca_start_command_exec(host, cmd); |
| |
| while (readl(host->base + MMCISTATUS) & MCI_CMDACTIVE); |
| |
| status = readl(host->base + MMCISTATUS); |
| |
| if ((cmd->resp_type != MMC_RSP_NONE)) { |
| |
| if (status & MCI_CMDTIMEOUT) { |
| rc = TIMEOUT; |
| } |
| /* MMC_CMD_SEND_OP_COND response doesn't have CRC. */ |
| if ((status & MCI_CMDCRCFAIL) && (cmd->cmdidx != MMC_CMD_SEND_OP_COND)) { |
| rc = UNUSABLE_ERR; |
| } |
| cmd->response[0] = readl(host->base + MMCIRESPONSE0); |
| /* |
| * Read rest of the response registers only if |
| * long response is expected for this command |
| */ |
| if (cmd->resp_type & MMC_RSP_136) { |
| cmd->response[1] = readl(host->base + MMCIRESPONSE1); |
| cmd->response[2] = readl(host->base + MMCIRESPONSE2); |
| cmd->response[3] = readl(host->base + MMCIRESPONSE3); |
| } |
| } |
| |
| writel(MCI_CLEAR_STATIC_MASK, host->base + MMCICLEAR); |
| qca_reg_wr_delay(host); |
| |
| return rc; |
| } |
| |
| |
| static int |
| qca_pio_read(qca_mmc *host, char *buffer, unsigned int remain) |
| { |
| unsigned int *ptr = (unsigned int *) buffer; |
| unsigned int status = 0; |
| unsigned int count = 0; |
| unsigned int error = MCI_DATACRCFAIL | MCI_DATATIMEOUT | MCI_RXOVERRUN; |
| unsigned int i; |
| |
| |
| do { |
| status = readl(host->base + MMCISTATUS); |
| udelay(1); |
| |
| if (status & error) { |
| break; |
| } |
| |
| if (status & MCI_RXDATAAVLBL ) { |
| unsigned rd_cnt = 1; |
| |
| if (status & MCI_RXFIFOHALFFULL) { |
| rd_cnt = MCI_HFIFO_COUNT; |
| } |
| |
| for (i = 0; i < rd_cnt; i++) { |
| *ptr = readl(host->base + MMCIFIFO + |
| (count % MCI_FIFOSIZE)); |
| ptr++; |
| count += sizeof(unsigned int); |
| } |
| |
| if (count == remain) |
| break; |
| |
| } else if (status & MCI_DATAEND) { |
| break; |
| } |
| } while (1); |
| |
| return count; |
| } |
| |
| static int |
| qca_pio_write(qca_mmc *host, const char *buffer, |
| unsigned int remain) |
| { |
| unsigned int *ptr = (unsigned int *) buffer; |
| unsigned int status = 0; |
| unsigned int count = 0; |
| unsigned int error = MCI_DATACRCFAIL | MCI_DATATIMEOUT | MCI_TXUNDERRUN; |
| unsigned int bcnt = 0; |
| unsigned int sz = 0; |
| int i; |
| |
| do { |
| status = readl(host->base + MMCISTATUS); |
| |
| if (status & error) |
| break; |
| |
| bcnt = remain - count; |
| |
| if (!bcnt) |
| break; |
| |
| if ((status & MCI_TXFIFOEMPTY) || |
| (status & MCI_TXFIFOHALFEMPTY)) { |
| |
| sz = ((bcnt >> 2) > MCI_HFIFO_COUNT) \ |
| ? MCI_HFIFO_COUNT : (bcnt >> 2); |
| |
| for (i = 0; i < sz; i++) { |
| writel(*ptr, host->base + MMCIFIFO); |
| ptr++; |
| count += sizeof(unsigned int); |
| } |
| } |
| } while (1); |
| |
| do { |
| status = readl(host->base + MMCISTATUS); |
| |
| if (status & error) { |
| printf("Error: %s, status=0x%x\n", __func__, status); |
| count = status; |
| break; |
| } |
| } while (!(status & MCI_DATAEND)); |
| |
| return count; |
| } |
| |
| static int |
| qca_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) |
| { |
| unsigned int datactrl = 0; |
| qca_mmc *host = mmc->priv; |
| unsigned int xfer_size ; |
| int status = 0; |
| |
| if (data) { |
| writel((readl(host->base + MMCICLOCK) | MCI_CLK_FLOWENA), |
| host->base + MMCICLOCK); |
| xfer_size = data->blocksize * data->blocks; |
| datactrl = MCI_DPSM_ENABLE | (data->blocksize << 4); |
| writel(0xffffffff, host->base + MMCIDATATIMER); |
| |
| if (data->flags & MMC_DATA_READ) |
| datactrl |= (MCI_DPSM_DIRECTION | MCI_RX_DATA_PEND); |
| |
| writel(xfer_size, host->base + MMCIDATALENGTH); |
| writel(datactrl, host->base + MMCIDATACTRL); |
| qca_reg_wr_delay(host); |
| |
| status = qca_start_command(host, cmd); |
| |
| if (data->flags & MMC_DATA_READ) { |
| qca_pio_read(host, data->dest, xfer_size); |
| } else { |
| if (qca_pio_write(host, data->src, xfer_size) != xfer_size) |
| status = -1; |
| } |
| writel(0, host->base + MMCIDATACTRL); |
| qca_reg_wr_delay(host); |
| } else if (cmd) { |
| status = qca_start_command(host, cmd); |
| } |
| |
| return status; |
| } |
| |
| |
| void qca_set_ios(struct mmc *mmc) |
| { |
| qca_mmc *host = mmc->priv; |
| u32 clk = 0, pwr = 0; |
| int mode; |
| |
| if (mmc->clock <= mmc->cfg->f_min) { |
| mode = MMC_IDENTIFY_MODE; |
| } else { |
| mode = MMC_DATA_TRANSFER_MODE; |
| } |
| |
| if (mode != host->clk_mode) { |
| host->clk_mode = mode; |
| emmc_clock_config(host->clk_mode); |
| } |
| |
| pwr = MCI_PWR_UP | MCI_PWR_ON; |
| |
| writel(pwr, host->base + MMCIPOWER); |
| qca_reg_wr_delay(host); |
| clk = readl(host->base + MMCICLOCK); |
| |
| clk |= MCI_CLK_ENABLE; |
| clk |= MCI_CLK_SELECTIN; |
| clk |= MCI_CLK_FLOWENA; |
| /* feedback clock */ |
| clk |= (2 << 14); |
| |
| if (mmc->bus_width == 1) { |
| clk |= MCI_CLK_WIDEBUS_1; |
| } else if (mmc->bus_width == 4) { |
| clk |= MCI_CLK_WIDEBUS_4; |
| } else if (mmc->bus_width == 8) { |
| clk |= MCI_CLK_WIDEBUS_8; |
| } |
| |
| /* Select free running MCLK as input clock of cm_dll_sdc4 */ |
| clk |= (2 << 23); |
| |
| /* Don't write into registers if clocks are disabled */ |
| writel(clk, host->base + MMCICLOCK); |
| qca_reg_wr_delay(host); |
| |
| writel((readl(host->base + MMCICLOCK) | MCI_CLK_PWRSAVE), host->base + MMCICLOCK); |
| qca_reg_wr_delay(host); |
| } |
| |
| int qca_mmc_start (struct mmc *mmc) |
| { |
| qca_mmc *host = mmc->priv; |
| |
| writel(readl(host->base + MMCIPOWER) | MCI_SW_RST, |
| host->base + MMCIPOWER); |
| qca_reg_wr_delay(host); |
| |
| while (readl(host->base + MMCIPOWER) & MCI_SW_RST) { |
| udelay(10); |
| } |
| |
| return 0; |
| } |
| static struct mmc_ops qca_mmc_ops = { |
| .send_cmd = qca_mmc_send_cmd, |
| .set_ios = qca_set_ios, |
| .init = qca_mmc_start |
| }; |
| |
| int qca_mmc_init(bd_t *bis, qca_mmc *host) |
| { |
| struct mmc_config *cfg; |
| struct mmc *mmc; |
| int ret = 0; |
| |
| cfg = malloc(sizeof(struct mmc_config)); |
| if (!cfg) { |
| return -ENOMEM; |
| } |
| |
| memset(cfg, 0, sizeof(struct mmc_config)); |
| |
| cfg->ops = &qca_mmc_ops; |
| cfg->f_min = 400000; |
| cfg->f_max = 52000000; |
| cfg->b_max = 512; |
| |
| /* voltage either 2.7-3.6V or 1.70 -1.95V */ |
| cfg->voltages = 0x40FF8080; |
| cfg->host_caps = MMC_MODE_8BIT; |
| cfg->host_caps |= MMC_MODE_HC; |
| cfg->part_type = PART_TYPE_EFI; |
| |
| mmc = mmc_create(cfg, host); |
| if (!mmc) { |
| puts("mmc_create failed\n"); |
| free(cfg); |
| ret = -ENODEV; |
| } else { |
| |
| host->mmc = mmc; |
| host->dev_num = mmc->block_dev.dev; |
| mmc->has_init = 0; |
| mmc->init_in_progress = 0; |
| } |
| |
| return ret; |
| } |