diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2011-02-25 10:22:03 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2011-03-08 12:28:46 +0100 |
commit | 246dbb30012842f686b1489d241e8ca297a538ef (patch) | |
tree | df52a92f83ce8909a4256e275079001a3e42bb8b /drivers/mci/mxs.c | |
parent | fdd07ccfd46955fe3e7320d4d5c43127f964aeea (diff) | |
download | barebox-246dbb30012842f686b1489d241e8ca297a538ef.tar.gz barebox-246dbb30012842f686b1489d241e8ca297a538ef.tar.xz |
mci i.MX23/28: rename driver to mxs.c
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mci/mxs.c')
-rw-r--r-- | drivers/mci/mxs.c | 787 |
1 files changed, 787 insertions, 0 deletions
diff --git a/drivers/mci/mxs.c b/drivers/mci/mxs.c new file mode 100644 index 0000000000..c1c5ff13c9 --- /dev/null +++ b/drivers/mci/mxs.c @@ -0,0 +1,787 @@ +/* + * 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 stm_mci_host { + 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 */ +}; + +/** + * Get the SSP clock rate + * @param hw_dev Host interface device instance + * @return Unit's clock in [Hz] + */ +static unsigned get_unit_clock(struct device_d *hw_dev) +{ + struct stm_mci_host *host_data = GET_HOST_DATA(hw_dev); + + return imx_get_sspclk(host_data->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 get_cards_response(struct device_d *hw_dev, 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(hw_dev->map_base + HW_SSP_SDRESP0); + return 1; + + case MMC_RSP_R2: + cmd->response[3] = readl(hw_dev->map_base + HW_SSP_SDRESP0); + cmd->response[2] = readl(hw_dev->map_base + HW_SSP_SDRESP1); + cmd->response[1] = readl(hw_dev->map_base + HW_SSP_SDRESP2); + cmd->response[0] = readl(hw_dev->map_base + 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 finish_request(struct device_d *hw_dev) +{ + /* stop the engines (normaly already done) */ + writel(SSP_CTRL0_RUN, hw_dev->map_base + 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 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 stm_setup_timout(struct device_d *hw_dev, unsigned to) +{ + uint32_t reg; + + reg = readl(hw_dev->map_base + HW_SSP_TIMING) & ~SSP_TIMING_TIMEOUT_MASK; + reg |= SSP_TIMING_TIMEOUT(to); + writel(reg, hw_dev->map_base + 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 read_data(struct device_d *hw_dev, 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(hw_dev->map_base + HW_SSP_STATUS) & SSP_STATUS_ERROR) == 0)) { + /* TODO sort out FIFO overflows and emit -EOI for this case */ + if ((readl(hw_dev->map_base + HW_SSP_STATUS) & SSP_STATUS_FIFO_EMPTY) == 0) { + *p = readl(hw_dev->map_base + 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 write_data(struct device_d *hw_dev, 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(hw_dev->map_base + HW_SSP_STATUS) & SSP_STATUS_ERROR) == 0)) { + /* TODO sort out FIFO overflows and emit -EOI for this case */ + if ((readl(hw_dev->map_base + HW_SSP_STATUS) & SSP_STATUS_FIFO_FULL) == 0) { + writel(*p, hw_dev->map_base + 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 transfer_data(struct device_d *hw_dev, 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, hw_dev->map_base + HW_SSP_CTRL0 + 4); + + if (data != NULL) { + unsigned length = data->blocks * data->blocksize; + + if (data->flags & MMC_DATA_READ) + return read_data(hw_dev, data->dest, length); + else + return write_data(hw_dev, 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 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 stm_mci_std_cmds(struct device_d *hw_dev, struct mci_cmd *cmd) +{ + /* setup command and transfer parameters */ + writel(prepare_transfer_setup(cmd->resp_type, 0) | + SSP_CTRL0_ENABLE, hw_dev->map_base + 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), + hw_dev->map_base + HW_SSP_CMD0); + + /* prepare command's arguments */ + writel(cmd->cmdarg, hw_dev->map_base + HW_SSP_CMD1); + + stm_setup_timout(hw_dev, 0xffff); + + /* start the transfer */ + writel(SSP_CTRL0_RUN, hw_dev->map_base + HW_SSP_CTRL0 + 4); + + /* wait until finished */ + while (readl(hw_dev->map_base + HW_SSP_CTRL0) & SSP_CTRL0_RUN) + ; + + if (cmd->resp_type & MMC_RSP_PRESENT) + get_cards_response(hw_dev, cmd); + + return get_cmd_error(readl(hw_dev->map_base + 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 stm_mci_adtc(struct device_d *hw_dev, struct mci_cmd *cmd, + struct mci_data *data) +{ + struct stm_mci_host *host_data = (struct stm_mci_host*)GET_HOST_DATA(hw_dev); + 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(prepare_transfer_setup(cmd->resp_type, data != NULL ? data->flags : 0) | + SSP_CTRL0_BUS_WIDTH(host_data->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 */ + hw_dev->map_base + 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), + hw_dev->map_base + HW_SSP_CMD0); +#endif +#ifdef CONFIG_ARCH_IMX28 + writel(prepare_transfer_setup(cmd->resp_type, data != NULL ? data->flags : 0) | + SSP_CTRL0_BUS_WIDTH(host_data->bus_width) | + (xfer_cnt != 0 ? SSP_CTRL0_DATA_XFER : 0) | /* command plus data */ + SSP_CTRL0_ENABLE, + hw_dev->map_base + HW_SSP_CTRL0); + writel(xfer_cnt, hw_dev->map_base + 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), + hw_dev->map_base + HW_SSP_CMD0); + writel(SSP_BLOCK_SIZE(log2blocksize) | + SSP_BLOCK_COUNT(block_cnt), + hw_dev->map_base + HW_SSP_BLOCK_SIZE); +#endif + + /* prepare command's arguments */ + writel(cmd->cmdarg, hw_dev->map_base + HW_SSP_CMD1); + + stm_setup_timout(hw_dev, 0xffff); + + err = transfer_data(hw_dev, data); + if (err != 0) { + pr_debug(" Transfering data failed\n"); + return err; + } + + /* wait until finished */ + while (readl(hw_dev->map_base + HW_SSP_CTRL0) & SSP_CTRL0_RUN) + ; + + get_cards_response(hw_dev, 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 setup_clock_speed(struct device_d *hw_dev, unsigned nc) +{ + unsigned ssp, div, rate, reg; + + if (nc == 0U) { + /* TODO stop the clock */ + return 0; + } + + ssp = get_unit_clock(hw_dev); + + 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(hw_dev->map_base + HW_SSP_TIMING) & SSP_TIMING_TIMEOUT_MASK; + reg |= SSP_TIMING_CLOCK_DIVIDE(div) | SSP_TIMING_CLOCK_RATE(rate - 1); + writel(reg, hw_dev->map_base + 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 stm_mci_reset(struct device_d *hw_dev) +{ + writel(SSP_CTRL0_SFTRST, hw_dev->map_base + HW_SSP_CTRL0 + 8); + while (readl(hw_dev->map_base + HW_SSP_CTRL0) & SSP_CTRL0_SFTRST) + ; +} + +/** + * Initialize the engine + * @param hw_dev Host interface instance + * @param mci_dev MCI device instance + */ +static int stm_mci_initialize(struct device_d *hw_dev, struct device_d *mci_dev) +{ + struct mci_host *host = GET_MCI_PDATA(mci_dev); + struct stm_mci_host *host_data = (struct stm_mci_host*)GET_HOST_DATA(hw_dev); + + /* enable the clock to this unit to be able to reset it */ + writel(SSP_CTRL0_CLKGATE, hw_dev->map_base + HW_SSP_CTRL0 + 8); + + /* reset the unit */ + stm_mci_reset(hw_dev); + + /* restore the last settings */ + host->clock = host_data->clock = setup_clock_speed(hw_dev, host->clock); + stm_setup_timout(hw_dev, 0xffff); + writel(SSP_CTRL0_IGNORE_CRC | + SSP_CTRL0_BUS_WIDTH(host_data->bus_width), + hw_dev->map_base + HW_SSP_CTRL0); + writel(SSP_CTRL1_POLARITY | + SSP_CTRL1_SSP_MODE(3) | + SSP_CTRL1_WORD_LENGTH(7), hw_dev->map_base + HW_SSP_CTRL1); + + return 0; +} + +/* ------------------------- 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 mci_reset(struct mci_host *mci_pdata, struct device_d *mci_dev) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + + return stm_mci_initialize(hw_dev, mci_dev); +} + +/** + * 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 mci_request(struct mci_host *mci_pdata, struct mci_cmd *cmd, + struct mci_data *data) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + int rc; + + if ((cmd->resp_type == 0) || (data == NULL)) + rc = stm_mci_std_cmds(hw_dev, cmd); + else + rc = stm_mci_adtc(hw_dev, cmd, data); /* with response and data */ + + finish_request(hw_dev); /* 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 mci_set_ios(struct mci_host *mci_pdata, struct device_d *mci_dev, + unsigned bus_width, unsigned clock) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + struct stm_mci_host *host_data = (struct stm_mci_host*)GET_HOST_DATA(hw_dev); + struct mci_host *host = GET_MCI_PDATA(mci_dev); + + switch (bus_width) { + case 8: + host_data->bus_width = 2; + host->bus_width = 8; /* 8 bit is possible */ + break; + case 4: + host_data->bus_width = 1; + host->bus_width = 4; /* 4 bit is possible */ + break; + default: + host_data->bus_width = 0; + host->bus_width = 1; /* 1 bit is possible */ + break; + } + + host->clock = host_data->clock = setup_clock_speed(hw_dev, clock); + pr_debug("IO settings: bus width=%d, frequency=%u Hz\n", host->bus_width, + host->clock); +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_MCI_INFO +const unsigned char bus_width[3] = { 1, 4, 8 }; + +static void stm_info(struct device_d *hw_dev) +{ + struct stm_mci_host *host_data = GET_HOST_DATA(hw_dev); + + printf(" Interface\n"); + printf(" Min. bus clock: %u Hz\n", host_data->f_min); + printf(" Max. bus clock: %u Hz\n", host_data->f_max); + printf(" Current bus clock: %u Hz\n", host_data->clock); + printf(" Bus width: %u bit\n", bus_width[host_data->bus_width]); + printf("\n"); +} +#endif + +static int stm_mci_probe(struct device_d *hw_dev) +{ + struct stm_mci_platform_data *pd = hw_dev->platform_data; + struct stm_mci_host *host_data; + struct mci_host *host; + + if (hw_dev->platform_data == NULL) { + pr_err("Missing platform data\n"); + return -EINVAL; + } + + host = xzalloc(sizeof(struct stm_mci_host) + sizeof(struct mci_host)); + host_data = (struct stm_mci_host*)&host[1]; + + hw_dev->priv = host_data; + host->hw_dev = hw_dev; + host->send_cmd = mci_request, + host->set_ios = mci_set_ios, + host->init = mci_reset, + + /* feed forward the platform specific values */ + host->voltages = pd->voltages; + host->host_caps = pd->caps; + +#ifdef CONFIG_ARCH_IMX23 + host_data->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: + host_data->index = 0; + break; + case IMX_SSP1_BASE: + host_data->index = 1; + break; + case IMX_SSP2_BASE: + host_data->index = 2; + break; + case IMX_SSP3_BASE: + host_data->index = 3; + break; + default: + pr_debug("Unknown SSP unit at address 0x%08x\n", hw_dev->map_base); + return 0; + } +#endif + if (pd->f_min == 0) { + host->f_min = get_unit_clock(hw_dev) / 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, get_unit_clock(hw_dev) / 254 / 256); + } + if (pd->f_max == 0) { + host->f_max = get_unit_clock(hw_dev) / 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, get_unit_clock(hw_dev) / 2 / 1); + } + +#ifdef CONFIG_MCI_INFO + host_data->f_min = host->f_min; + host_data->f_max = host->f_max; +#endif + + return mci_register(host); +} + +static struct driver_d stm_mci_driver = { + .name = "stm_mci", + .probe = stm_mci_probe, +#ifdef CONFIG_MCI_INFO + .info = stm_info, +#endif +}; + +static int stm_mci_init_driver(void) +{ + register_driver(&stm_mci_driver); + return 0; +} + +device_initcall(stm_mci_init_driver); |