| /* |
| * Copyright (C) 2010 Juergen Beisert, Pengutronix <jbe@pengutronix.de> |
| * |
| * This code is based on: |
| * |
| * Copyright (C) 2007 SigmaTel, Inc., Ioannis Kappas <ikappas@sigmatel.com> |
| * |
| * Portions copyright (C) 2003 Russell King, PXA MMCI Driver |
| * Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver |
| * |
| * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. |
| * Copyright 2008 Embedded Alley Solutions, Inc 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 as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| /** |
| * @file |
| * @brief MCI card host interface for i.MX23 CPU |
| */ |
| |
| /* #define DEBUG */ |
| |
| #include <common.h> |
| #include <init.h> |
| #include <mci.h> |
| #include <errno.h> |
| #include <clock.h> |
| #include <asm/io.h> |
| #include <asm/bitops.h> |
| #include <mach/imx-regs.h> |
| #include <mach/mci.h> |
| #include <mach/clock.h> |
| |
| #define CLOCKRATE_MIN (1 * 1000 * 1000) |
| #define CLOCKRATE_MAX (480 * 1000 * 1000) |
| |
| #define HW_SSP_CTRL0 0x000 |
| # define SSP_CTRL0_SFTRST (1 << 31) |
| # define SSP_CTRL0_CLKGATE (1 << 30) |
| # define SSP_CTRL0_RUN (1 << 29) |
| # define SSP_CTRL0_LOCK_CS (1 << 27) |
| # define SSP_CTRL0_READ (1 << 25) |
| # define SSP_CTRL0_IGNORE_CRC (1 << 26) |
| # define SSP_CTRL0_DATA_XFER (1 << 24) |
| # define SSP_CTRL0_BUS_WIDTH(x) (((x) & 0x3) << 22) |
| # define SSP_CTRL0_WAIT_FOR_IRQ (1 << 21) |
| # define SSP_CTRL0_LONG_RESP (1 << 19) |
| # define SSP_CTRL0_GET_RESP (1 << 17) |
| # define SSP_CTRL0_ENABLE (1 << 16) |
| #ifdef CONFIG_ARCH_IMX23 |
| # define SSP_CTRL0_XFER_COUNT(x) ((x) & 0xffff) |
| #endif |
| |
| #define HW_SSP_CMD0 0x010 |
| # define SSP_CMD0_SLOW_CLK (1 << 22) |
| # define SSP_CMD0_CONT_CLK (1 << 21) |
| # define SSP_CMD0_APPEND_8CYC (1 << 20) |
| #ifdef CONFIG_ARCH_IMX23 |
| # define SSP_CMD0_BLOCK_SIZE(x) (((x) & 0xf) << 16) |
| # define SSP_CMD0_BLOCK_COUNT(x) (((x) & 0xff) << 8) |
| #endif |
| # define SSP_CMD0_CMD(x) ((x) & 0xff) |
| |
| #define HW_SSP_CMD1 0x020 |
| |
| #ifdef CONFIG_ARCH_IMX23 |
| # define HW_SSP_COMPREF 0x030 |
| # define HW_SSP_COMPMASK 0x040 |
| # define HW_SSP_TIMING 0x050 |
| # define HW_SSP_CTRL1 0x060 |
| # define HW_SSP_DATA 0x070 |
| #endif |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_XFER_COUNT 0x30 |
| # define HW_SSP_BLOCK_SIZE 0x40 |
| # define SSP_BLOCK_SIZE(x) ((x) & 0xf) |
| # define SSP_BLOCK_COUNT(x) (((x) & 0xffffff) << 4) |
| # define HW_SSP_COMPREF 0x050 |
| # define HW_SSP_COMPMASK 0x060 |
| # define HW_SSP_TIMING 0x070 |
| # define HW_SSP_CTRL1 0x080 |
| # define HW_SSP_DATA 0x090 |
| #endif |
| /* bit definition for register HW_SSP_TIMING */ |
| # define SSP_TIMING_TIMEOUT_MASK (0xffff0000) |
| # define SSP_TIMING_TIMEOUT(x) ((x) << 16) |
| # define SSP_TIMING_CLOCK_DIVIDE(x) (((x) & 0xff) << 8) |
| # define SSP_TIMING_CLOCK_RATE(x) ((x) & 0xff) |
| |
| /* bit definition for register HW_SSP_CTRL1 */ |
| # define SSP_CTRL1_POLARITY (1 << 9) |
| # define SSP_CTRL1_WORD_LENGTH(x) (((x) & 0xf) << 4) |
| # define SSP_CTRL1_SSP_MODE(x) ((x) & 0xf) |
| |
| #ifdef CONFIG_ARCH_IMX23 |
| # define HW_SSP_SDRESP0 0x080 |
| # define HW_SSP_SDRESP1 0x090 |
| # define HW_SSP_SDRESP2 0x0A0 |
| # define HW_SSP_SDRESP3 0x0B0 |
| #endif |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_SDRESP0 0x0A0 |
| # define HW_SSP_SDRESP1 0x0B0 |
| # define HW_SSP_SDRESP2 0x0C0 |
| # define HW_SSP_SDRESP3 0x0D0 |
| #endif |
| |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_DDR_CTRL 0x0E0 |
| # define HW_SSP_DLL_CTRL 0x0F0 |
| #endif |
| |
| #ifdef CONFIG_ARCH_IMX23 |
| # define HW_SSP_STATUS 0x0C0 |
| #endif |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_STATUS 0x100 |
| #endif |
| |
| /* bit definition for register HW_SSP_STATUS */ |
| # define SSP_STATUS_PRESENT (1 << 31) |
| # define SSP_STATUS_SD_PRESENT (1 << 29) |
| # define SSP_STATUS_CARD_DETECT (1 << 28) |
| # define SSP_STATUS_RESP_CRC_ERR (1 << 16) |
| # define SSP_STATUS_RESP_ERR (1 << 15) |
| # define SSP_STATUS_RESP_TIMEOUT (1 << 14) |
| # define SSP_STATUS_DATA_CRC_ERR (1 << 13) |
| # define SSP_STATUS_TIMEOUT (1 << 12) |
| # define SSP_STATUS_FIFO_OVRFLW (1 << 9) |
| # define SSP_STATUS_FIFO_FULL (1 << 8) |
| # define SSP_STATUS_FIFO_EMPTY (1 << 5) |
| # define SSP_STATUS_FIFO_UNDRFLW (1 << 4) |
| # define SSP_STATUS_CMD_BUSY (1 << 3) |
| # define SSP_STATUS_DATA_BUSY (1 << 2) |
| # define SSP_STATUS_BUSY (1 << 0) |
| # define SSP_STATUS_ERROR (SSP_STATUS_FIFO_OVRFLW | SSP_STATUS_FIFO_UNDRFLW | \ |
| SSP_STATUS_RESP_CRC_ERR | SSP_STATUS_RESP_ERR | \ |
| SSP_STATUS_RESP_TIMEOUT | SSP_STATUS_DATA_CRC_ERR | SSP_STATUS_TIMEOUT) |
| |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_DLL_STS 0x110 |
| #endif |
| |
| #ifdef CONFIG_ARCH_IMX23 |
| # define HW_SSP_DEBUG 0x100 |
| # define HW_SSP_VERSION 0x110 |
| #endif |
| |
| #ifdef CONFIG_ARCH_IMX28 |
| # define HW_SSP_DEBUG 0x120 |
| # define HW_SSP_VERSION 0x130 |
| #endif |
| |
| struct mxs_mci_host { |
| struct mci_host host; |
| void __iomem *regs; |
| unsigned clock; /* current clock speed in Hz ("0" if disabled) */ |
| unsigned index; |
| #ifdef CONFIG_MCI_INFO |
| unsigned f_min; |
| unsigned f_max; |
| #endif |
| int bus_width:2; /* 0 = 1 bit, 1 = 4 bit, 2 = 8 bit */ |
| }; |
| |
| #define to_mxs_mci(mxs) container_of(mxs, struct mxs_mci_host, host) |
| |
| /** |
| * Get the SSP clock rate |
| * @param hw_dev Host interface device instance |
| * @return Unit's clock in [Hz] |
| */ |
| static unsigned mxs_mci_get_unit_clock(struct mxs_mci_host *mxs_mci) |
| { |
| return imx_get_sspclk(mxs_mci->index); |
| } |
| |
| /** |
| * Get MCI cards response if defined for the type of command |
| * @param hw_dev Host interface device instance |
| * @param cmd Command description |
| * @return Response bytes count, -EINVAL for unsupported response types |
| */ |
| static int mxs_mci_get_cards_response(struct mxs_mci_host *mxs_mci, struct mci_cmd *cmd) |
| { |
| switch (cmd->resp_type) { |
| case MMC_RSP_NONE: |
| return 0; |
| |
| case MMC_RSP_R1: |
| case MMC_RSP_R1b: |
| case MMC_RSP_R3: |
| cmd->response[0] = readl(mxs_mci->regs + HW_SSP_SDRESP0); |
| return 1; |
| |
| case MMC_RSP_R2: |
| cmd->response[3] = readl(mxs_mci->regs + HW_SSP_SDRESP0); |
| cmd->response[2] = readl(mxs_mci->regs + HW_SSP_SDRESP1); |
| cmd->response[1] = readl(mxs_mci->regs + HW_SSP_SDRESP2); |
| cmd->response[0] = readl(mxs_mci->regs + HW_SSP_SDRESP3); |
| return 4; |
| } |
| |
| return -EINVAL; |
| } |
| |
| /** |
| * Finish a request to the MCI card |
| * @param hw_dev Host interface device instance |
| * |
| * Can also stop the clock to save power |
| */ |
| static void mxs_mci_finish_request(struct mxs_mci_host *mxs_mci) |
| { |
| /* stop the engines (normaly already done) */ |
| writel(SSP_CTRL0_RUN, mxs_mci->regs + HW_SSP_CTRL0 + 8); |
| } |
| |
| /** |
| * Check if the last command failed and if, why it failed |
| * @param status HW_SSP_STATUS's content |
| * @return 0 if no error, negative values else |
| */ |
| static int mxs_mci_get_cmd_error(unsigned status) |
| { |
| if (status & SSP_STATUS_ERROR) |
| pr_debug("Status Reg reports %08X\n", status); |
| |
| if (status & SSP_STATUS_TIMEOUT) { |
| pr_debug("CMD timeout\n"); |
| return -ETIMEDOUT; |
| } else if (status & SSP_STATUS_RESP_TIMEOUT) { |
| pr_debug("RESP timeout\n"); |
| return -ETIMEDOUT; |
| } else if (status & SSP_STATUS_RESP_CRC_ERR) { |
| pr_debug("CMD crc error\n"); |
| return -EILSEQ; |
| } else if (status & SSP_STATUS_RESP_ERR) { |
| pr_debug("RESP error\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Define the timout for the next command |
| * @param hw_dev Host interface device instance |
| * @param to Timeout value in MCI card's bus clocks |
| */ |
| static void mxs_mci_setup_timeout(struct mxs_mci_host *mxs_mci, unsigned to) |
| { |
| uint32_t reg; |
| |
| reg = readl(mxs_mci->regs + HW_SSP_TIMING) & ~SSP_TIMING_TIMEOUT_MASK; |
| reg |= SSP_TIMING_TIMEOUT(to); |
| writel(reg, mxs_mci->regs + HW_SSP_TIMING); |
| } |
| |
| /** |
| * Read data from the MCI card |
| * @param hw_dev Host interface device instance |
| * @param buffer To write data into |
| * @param length Count of bytes to read (must be multiples of 4) |
| * @return 0 on success, negative values else |
| * |
| * @note This routine uses PIO to read in the data bytes from the FIFO. This |
| * may fail whith high clock speeds. If you receive -EIO errors you can try |
| * again with reduced clock speeds. |
| */ |
| static int mxs_mci_read_data(struct mxs_mci_host *mxs_mci, void *buffer, unsigned length) |
| { |
| uint32_t *p = buffer; |
| |
| if (length & 0x3) { |
| pr_debug("Cannot read data sizes not multiple of 4 (request for %u detected)\n", |
| length); |
| return -EINVAL; |
| } |
| |
| while ((length != 0) && |
| ((readl(mxs_mci->regs + HW_SSP_STATUS) & SSP_STATUS_ERROR) == 0)) { |
| /* TODO sort out FIFO overflows and emit -EOI for this case */ |
| if ((readl(mxs_mci->regs + HW_SSP_STATUS) & SSP_STATUS_FIFO_EMPTY) == 0) { |
| *p = readl(mxs_mci->regs + HW_SSP_DATA); |
| p++; |
| length -= 4; |
| } |
| } |
| |
| if (length == 0) |
| return 0; |
| |
| return -EINVAL; |
| } |
| |
| |
| /** |
| * Write data into the MCI card |
| * @param hw_dev Host interface device instance |
| * @param buffer To read the data from |
| * @param length Count of bytes to write (must be multiples of 4) |
| * @return 0 on success, negative values else |
| * |
| * @note This routine uses PIO to write the data bytes into the FIFO. This |
| * may fail with high clock speeds. If you receive -EIO errors you can try |
| * again with reduced clock speeds. |
| */ |
| static int mxs_mci_write_data(struct mxs_mci_host *mxs_mci, const void *buffer, unsigned length) |
| { |
| const uint32_t *p = buffer; |
| |
| if (length & 0x3) { |
| pr_debug("Cannot write data sizes not multiple of 4 (request for %u detected)\n", |
| length); |
| return -EINVAL; |
| } |
| |
| while ((length != 0) && |
| ((readl(mxs_mci->regs + HW_SSP_STATUS) & SSP_STATUS_ERROR) == 0)) { |
| /* TODO sort out FIFO overflows and emit -EOI for this case */ |
| if ((readl(mxs_mci->regs + HW_SSP_STATUS) & SSP_STATUS_FIFO_FULL) == 0) { |
| writel(*p, mxs_mci->regs + HW_SSP_DATA); |
| p++; |
| length -= 4; |
| } |
| } |
| if (length == 0) |
| return 0; |
| |
| return -EINVAL; |
| } |
| |
| /** |
| * Start the transaction with or without data |
| * @param hw_dev Host interface device instance |
| * @param data Data transfer description (might be NULL) |
| * @return 0 on success |
| */ |
| static int mxs_mci_transfer_data(struct mxs_mci_host *mxs_mci, struct mci_data *data) |
| { |
| /* |
| * Everything is ready for the transaction now: |
| * - transfer configuration |
| * - command and its parameters |
| * |
| * Start the transaction right now |
| */ |
| writel(SSP_CTRL0_RUN, mxs_mci->regs + HW_SSP_CTRL0 + 4); |
| |
| if (data != NULL) { |
| unsigned length = data->blocks * data->blocksize; |
| |
| if (data->flags & MMC_DATA_READ) |
| return mxs_mci_read_data(mxs_mci, data->dest, length); |
| else |
| return mxs_mci_write_data(mxs_mci, data->src, length); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Configure the MCI hardware for the next transaction |
| * @param cmd_flags Command information |
| * @param data_flags Data information (may be 0) |
| * @return Corresponding setting for the SSP_CTRL0 register |
| */ |
| static uint32_t mxs_mci_prepare_transfer_setup(unsigned cmd_flags, unsigned data_flags) |
| { |
| uint32_t reg = 0; |
| |
| if (cmd_flags & MMC_RSP_PRESENT) |
| reg |= SSP_CTRL0_GET_RESP; |
| if ((cmd_flags & MMC_RSP_CRC) == 0) |
| reg |= SSP_CTRL0_IGNORE_CRC; |
| if (cmd_flags & MMC_RSP_136) |
| reg |= SSP_CTRL0_LONG_RESP; |
| if (cmd_flags & MMC_RSP_BUSY) |
| reg |= SSP_CTRL0_WAIT_FOR_IRQ; /* FIXME correct? */ |
| #if 0 |
| if (cmd_flags & MMC_RSP_OPCODE) |
| /* TODO */ |
| #endif |
| if (data_flags & MMC_DATA_READ) |
| reg |= SSP_CTRL0_READ; |
| |
| return reg; |
| } |
| |
| /** |
| * Handle MCI commands without data |
| * @param hw_dev Host interface device instance |
| * @param cmd The command to handle |
| * @return 0 on success |
| * |
| * This functions handles the following MCI commands: |
| * - "broadcast command (BC)" without a response |
| * - "broadcast commands with response (BCR)" |
| * - "addressed command (AC)" with response, but without data |
| */ |
| static int mxs_mci_std_cmds(struct mxs_mci_host *mxs_mci, struct mci_cmd *cmd) |
| { |
| /* setup command and transfer parameters */ |
| writel(mxs_mci_prepare_transfer_setup(cmd->resp_type, 0) | |
| SSP_CTRL0_ENABLE, mxs_mci->regs + HW_SSP_CTRL0); |
| |
| /* prepare the command, when no response is expected add a few trailing clocks */ |
| writel(SSP_CMD0_CMD(cmd->cmdidx) | |
| (cmd->resp_type & MMC_RSP_PRESENT ? 0 : SSP_CMD0_APPEND_8CYC), |
| mxs_mci->regs + HW_SSP_CMD0); |
| |
| /* prepare command's arguments */ |
| writel(cmd->cmdarg, mxs_mci->regs + HW_SSP_CMD1); |
| |
| mxs_mci_setup_timeout(mxs_mci, 0xffff); |
| |
| /* start the transfer */ |
| writel(SSP_CTRL0_RUN, mxs_mci->regs + HW_SSP_CTRL0 + 4); |
| |
| /* wait until finished */ |
| while (readl(mxs_mci->regs + HW_SSP_CTRL0) & SSP_CTRL0_RUN) |
| ; |
| |
| if (cmd->resp_type & MMC_RSP_PRESENT) |
| mxs_mci_get_cards_response(mxs_mci, cmd); |
| |
| return mxs_mci_get_cmd_error(readl(mxs_mci->regs + HW_SSP_STATUS)); |
| } |
| |
| /** |
| * Handle an "addressed data transfer command " with or without data |
| * @param hw_dev Host interface device instance |
| * @param cmd The command to handle |
| * @param data The data information (buffer, direction aso.) May be NULL |
| * @return 0 on success |
| */ |
| static int mxs_mci_adtc(struct mxs_mci_host *mxs_mci, struct mci_cmd *cmd, |
| struct mci_data *data) |
| { |
| uint32_t xfer_cnt, log2blocksize, block_cnt; |
| int err; |
| |
| /* Note: 'data' can be NULL! */ |
| if (data != NULL) { |
| xfer_cnt = data->blocks * data->blocksize; |
| block_cnt = data->blocks - 1; /* can be 0 */ |
| log2blocksize = find_first_bit((const unsigned long*)&data->blocksize, |
| 32); |
| } else |
| xfer_cnt = log2blocksize = block_cnt = 0; |
| |
| /* setup command and transfer parameters */ |
| #ifdef CONFIG_ARCH_IMX23 |
| writel(mxs_mci_prepare_transfer_setup(cmd->resp_type, data != NULL ? data->flags : 0) | |
| SSP_CTRL0_BUS_WIDTH(mxs_mci->bus_width) | |
| (xfer_cnt != 0 ? SSP_CTRL0_DATA_XFER : 0) | /* command plus data */ |
| SSP_CTRL0_ENABLE | |
| SSP_CTRL0_XFER_COUNT(xfer_cnt), /* byte count to be transfered */ |
| mxs_mci->regs + HW_SSP_CTRL0); |
| |
| /* prepare the command and the transfered data count */ |
| writel(SSP_CMD0_CMD(cmd->cmdidx) | |
| SSP_CMD0_BLOCK_SIZE(log2blocksize) | |
| SSP_CMD0_BLOCK_COUNT(block_cnt) | |
| (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION ? SSP_CMD0_APPEND_8CYC : 0), |
| mxs_mci->regs + HW_SSP_CMD0); |
| #endif |
| #ifdef CONFIG_ARCH_IMX28 |
| writel(mxs_mci_prepare_transfer_setup(cmd->resp_type, data != NULL ? data->flags : 0) | |
| SSP_CTRL0_BUS_WIDTH(mxs_mci->bus_width) | |
| (xfer_cnt != 0 ? SSP_CTRL0_DATA_XFER : 0) | /* command plus data */ |
| SSP_CTRL0_ENABLE, |
| mxs_mci->regs + HW_SSP_CTRL0); |
| writel(xfer_cnt, mxs_mci->regs + HW_SSP_XFER_COUNT); |
| |
| /* prepare the command and the transfered data count */ |
| writel(SSP_CMD0_CMD(cmd->cmdidx) | |
| (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION ? SSP_CMD0_APPEND_8CYC : 0), |
| mxs_mci->regs + HW_SSP_CMD0); |
| writel(SSP_BLOCK_SIZE(log2blocksize) | |
| SSP_BLOCK_COUNT(block_cnt), |
| mxs_mci->regs + HW_SSP_BLOCK_SIZE); |
| #endif |
| |
| /* prepare command's arguments */ |
| writel(cmd->cmdarg, mxs_mci->regs + HW_SSP_CMD1); |
| |
| mxs_mci_setup_timeout(mxs_mci, 0xffff); |
| |
| err = mxs_mci_transfer_data(mxs_mci, data); |
| if (err != 0) { |
| pr_debug(" Transfering data failed\n"); |
| return err; |
| } |
| |
| /* wait until finished */ |
| while (readl(mxs_mci->regs + HW_SSP_CTRL0) & SSP_CTRL0_RUN) |
| ; |
| |
| mxs_mci_get_cards_response(mxs_mci, cmd); |
| |
| return 0; |
| } |
| |
| |
| /** |
| * @param hw_dev Host interface device instance |
| * @param nc New Clock in [Hz] (may be 0 to disable the clock) |
| * @return The real clock frequency |
| * |
| * The SSP unit clock can base on the external 24 MHz or the internal 480 MHz |
| * Its unit clock value is derived from the io clock, from the SSP divider |
| * and at least the SSP bus clock itself is derived from the SSP unit's divider |
| * |
| * @code |
| * |------------------- generic -------------|-peripheral specific-|-----all SSPs-----|-per SSP unit-| |
| * 24 MHz ---------------------------- |
| * \ \ |
| * \ |----| FRAC |----IO CLK----| SSP unit DIV |---| SSP DIV |--- SSP output clock |
| * \- | PLL |--- 480 MHz ---/ |
| * @endcode |
| * |
| * @note Up to "SSP unit DIV" the outer world must care. This routine only |
| * handles the "SSP DIV". |
| */ |
| static unsigned mxs_mci_setup_clock_speed(struct mxs_mci_host *mxs_mci, unsigned nc) |
| { |
| unsigned ssp, div, rate, reg; |
| |
| if (nc == 0U) { |
| /* TODO stop the clock */ |
| return 0; |
| } |
| |
| ssp = mxs_mci_get_unit_clock(mxs_mci); |
| |
| for (div = 2; div < 255; div += 2) { |
| rate = DIV_ROUND_CLOSEST(DIV_ROUND_CLOSEST(ssp, nc), div); |
| if (rate <= 0x100) |
| break; |
| } |
| if (div >= 255) { |
| pr_warning("Cannot set clock to %d Hz\n", nc); |
| return 0; |
| } |
| |
| reg = readl(mxs_mci->regs + HW_SSP_TIMING) & SSP_TIMING_TIMEOUT_MASK; |
| reg |= SSP_TIMING_CLOCK_DIVIDE(div) | SSP_TIMING_CLOCK_RATE(rate - 1); |
| writel(reg, mxs_mci->regs + HW_SSP_TIMING); |
| |
| return ssp / div / rate; |
| } |
| |
| /** |
| * Reset the MCI engine (the hard way) |
| * @param hw_dev Host interface instance |
| * |
| * This will reset everything in all registers of this unit! (FIXME) |
| */ |
| static void mxs_mci_reset(struct mxs_mci_host *mxs_mci) |
| { |
| writel(SSP_CTRL0_SFTRST, mxs_mci->regs + HW_SSP_CTRL0 + 8); |
| while (readl(mxs_mci->regs + HW_SSP_CTRL0) & SSP_CTRL0_SFTRST) |
| ; |
| } |
| |
| /* ------------------------- MCI API -------------------------------------- */ |
| |
| /** |
| * Keep the attached MMC/SD unit in a well know state |
| * @param mci_pdata MCI platform data |
| * @param mci_dev MCI device instance |
| * @return 0 on success, negative value else |
| */ |
| static int mxs_mci_initialize(struct mci_host *host, struct device_d *mci_dev) |
| { |
| struct mxs_mci_host *mxs_mci = to_mxs_mci(host); |
| |
| /* enable the clock to this unit to be able to reset it */ |
| writel(SSP_CTRL0_CLKGATE, mxs_mci->regs + HW_SSP_CTRL0 + 8); |
| |
| /* reset the unit */ |
| mxs_mci_reset(mxs_mci); |
| |
| /* restore the last settings */ |
| mxs_mci_setup_timeout(mxs_mci, 0xffff); |
| writel(SSP_CTRL0_IGNORE_CRC | |
| SSP_CTRL0_BUS_WIDTH(mxs_mci->bus_width), |
| mxs_mci->regs + HW_SSP_CTRL0); |
| writel(SSP_CTRL1_POLARITY | |
| SSP_CTRL1_SSP_MODE(3) | |
| SSP_CTRL1_WORD_LENGTH(7), mxs_mci->regs + HW_SSP_CTRL1); |
| |
| return 0; |
| } |
| |
| /** |
| * Process one command to the MCI card |
| * @param mci_pdata MCI platform data |
| * @param cmd The command to process |
| * @param data The data to handle in the command (can be NULL) |
| * @return 0 on success, negative value else |
| */ |
| static int mxs_mci_request(struct mci_host *host, struct mci_cmd *cmd, |
| struct mci_data *data) |
| { |
| struct mxs_mci_host *mxs_mci = to_mxs_mci(host); |
| int rc; |
| |
| if ((cmd->resp_type == 0) || (data == NULL)) |
| rc = mxs_mci_std_cmds(mxs_mci, cmd); |
| else |
| rc = mxs_mci_adtc(mxs_mci, cmd, data); /* with response and data */ |
| |
| mxs_mci_finish_request(mxs_mci); /* TODO */ |
| return rc; |
| } |
| |
| /** |
| * Setup the bus width and IO speed |
| * @param mci_pdata MCI platform data |
| * @param mci_dev MCI device instance |
| * @param bus_width New bus width value (1, 4 or 8) |
| * @param clock New clock in Hz (can be '0' to disable the clock) |
| * |
| * Drivers currently realized values are stored in MCI's platformdata |
| */ |
| static void mxs_mci_set_ios(struct mci_host *host, struct device_d *mci_dev, |
| unsigned bus_width, unsigned clock) |
| { |
| struct mxs_mci_host *mxs_mci = to_mxs_mci(host); |
| |
| switch (bus_width) { |
| case 8: |
| mxs_mci->bus_width = 2; |
| host->bus_width = 8; /* 8 bit is possible */ |
| break; |
| case 4: |
| mxs_mci->bus_width = 1; |
| host->bus_width = 4; /* 4 bit is possible */ |
| break; |
| default: |
| mxs_mci->bus_width = 0; |
| host->bus_width = 1; /* 1 bit is possible */ |
| break; |
| } |
| |
| mxs_mci->clock = mxs_mci_setup_clock_speed(mxs_mci, clock); |
| pr_debug("IO settings: bus width=%d, frequency=%u Hz\n", host->bus_width, |
| mxs_mci->clock); |
| } |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| #ifdef CONFIG_MCI_INFO |
| const unsigned char bus_width[3] = { 1, 4, 8 }; |
| |
| static void mxs_mci_info(struct device_d *hw_dev) |
| { |
| struct mxs_mci_host *mxs_mci = GET_HOST_DATA(hw_dev); |
| |
| printf(" Interface\n"); |
| printf(" Min. bus clock: %u Hz\n", mxs_mci->f_min); |
| printf(" Max. bus clock: %u Hz\n", mxs_mci->f_max); |
| printf(" Current bus clock: %u Hz\n", mxs_mci->clock); |
| printf(" Bus width: %u bit\n", bus_width[mxs_mci->bus_width]); |
| printf("\n"); |
| } |
| #endif |
| |
| static int mxs_mci_probe(struct device_d *hw_dev) |
| { |
| struct mxs_mci_platform_data *pd = hw_dev->platform_data; |
| struct mxs_mci_host *mxs_mci; |
| struct mci_host *host; |
| |
| if (hw_dev->platform_data == NULL) { |
| pr_err("Missing platform data\n"); |
| return -EINVAL; |
| } |
| |
| mxs_mci = xzalloc(sizeof(*mxs_mci)); |
| host = &mxs_mci->host; |
| |
| hw_dev->priv = mxs_mci; |
| host->hw_dev = hw_dev; |
| host->send_cmd = mxs_mci_request, |
| host->set_ios = mxs_mci_set_ios, |
| host->init = mxs_mci_initialize, |
| mxs_mci->regs = (void *)hw_dev->map_base; |
| |
| /* feed forward the platform specific values */ |
| host->voltages = pd->voltages; |
| host->host_caps = pd->caps; |
| |
| #ifdef CONFIG_ARCH_IMX23 |
| mxs_mci->index = 0; /* there is only one clock for all */ |
| #endif |
| #ifdef CONFIG_ARCH_IMX28 |
| /* one dedicated clock per unit */ |
| switch (hw_dev->map_base) { |
| case IMX_SSP0_BASE: |
| mxs_mci->index = 0; |
| break; |
| case IMX_SSP1_BASE: |
| mxs_mci->index = 1; |
| break; |
| case IMX_SSP2_BASE: |
| mxs_mci->index = 2; |
| break; |
| case IMX_SSP3_BASE: |
| mxs_mci->index = 3; |
| break; |
| default: |
| pr_debug("Unknown SSP unit at address 0x%08x\n", mxs_mci->regs); |
| return 0; |
| } |
| #endif |
| if (pd->f_min == 0) { |
| host->f_min = mxs_mci_get_unit_clock(mxs_mci) / 254 / 256; |
| pr_debug("Min. frequency is %u Hz\n", host->f_min); |
| } else { |
| host->f_min = pd->f_min; |
| pr_debug("Min. frequency is %u Hz, could be %u Hz\n", |
| host->f_min, mxs_mci_get_unit_clock(mxs_mci) / 254 / 256); |
| } |
| if (pd->f_max == 0) { |
| host->f_max = mxs_mci_get_unit_clock(mxs_mci) / 2 / 1; |
| pr_debug("Max. frequency is %u Hz\n", host->f_max); |
| } else { |
| host->f_max = pd->f_max; |
| pr_debug("Max. frequency is %u Hz, could be %u Hz\n", |
| host->f_max, mxs_mci_get_unit_clock(mxs_mci) / 2 / 1); |
| } |
| |
| #ifdef CONFIG_MCI_INFO |
| mxs_mci->f_min = host->f_min; |
| mxs_mci->f_max = host->f_max; |
| #endif |
| |
| return mci_register(host); |
| } |
| |
| static struct driver_d mxs_mci_driver = { |
| .name = "mxs_mci", |
| .probe = mxs_mci_probe, |
| #ifdef CONFIG_MCI_INFO |
| .info = mxs_mci_info, |
| #endif |
| }; |
| |
| static int mxs_mci_init_driver(void) |
| { |
| register_driver(&mxs_mci_driver); |
| return 0; |
| } |
| |
| device_initcall(mxs_mci_init_driver); |