summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/dma/Makefile1
-rw-r--r--drivers/dma/apbh_dma.c691
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/mxs_gpio.c136
-rw-r--r--drivers/i2c/Makefile1
-rw-r--r--drivers/i2c/mxs_i2c.c246
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/mxsmmc.c351
-rw-r--r--drivers/mmc/omap_hsmmc.c26
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/mxs_nand.c1118
-rw-r--r--drivers/mtd/onenand/Makefile4
-rw-r--r--drivers/mtd/onenand/onenand_base.c18
-rw-r--r--drivers/mtd/onenand/onenand_spl.c146
-rw-r--r--drivers/net/davinci_emac.c24
-rw-r--r--drivers/net/fec_mxc.c44
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/mxsrtc.c86
-rw-r--r--drivers/serial/serial_pxa.c464
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/mxs_spi.c186
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/ehci-mxs.c154
23 files changed, 3393 insertions, 309 deletions
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 3d9c9f1..5d864b5 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -26,6 +26,7 @@ include $(TOPDIR)/config.mk
LIB := $(obj)libdma.o
COBJS-$(CONFIG_FSLDMAFEC) += MCD_tasksInit.o MCD_dmaApi.o MCD_tasks.o
+COBJS-$(CONFIG_APBH_DMA) += apbh_dma.o
COBJS-$(CONFIG_FSL_DMA) += fsl_dma.o
COBJS-$(CONFIG_OMAP3_DMA) += omap3_dma.o
diff --git a/drivers/dma/apbh_dma.c b/drivers/dma/apbh_dma.c
new file mode 100644
index 0000000..69a1042
--- /dev/null
+++ b/drivers/dma/apbh_dma.c
@@ -0,0 +1,691 @@
+/*
+ * Freescale i.MX28 APBH DMA driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * Copyright (C) 2010 Freescale Semiconductor, 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/list.h>
+
+#include <common.h>
+#include <malloc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/arch/dma.h>
+
+static struct mxs_dma_chan mxs_dma_channels[MXS_MAX_DMA_CHANNELS];
+
+/*
+ * Test is the DMA channel is valid channel
+ */
+int mxs_dma_validate_chan(int channel)
+{
+ struct mxs_dma_chan *pchan;
+
+ if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS))
+ return -EINVAL;
+
+ pchan = mxs_dma_channels + channel;
+ if (!(pchan->flags & MXS_DMA_FLAGS_ALLOCATED))
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Enable a DMA channel.
+ *
+ * If the given channel has any DMA descriptors on its active list, this
+ * function causes the DMA hardware to begin processing them.
+ *
+ * This function marks the DMA channel as "busy," whether or not there are any
+ * descriptors to process.
+ */
+int mxs_dma_enable(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ unsigned int sem;
+ struct mxs_dma_chan *pchan;
+ struct mxs_dma_desc *pdesc;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = mxs_dma_channels + channel;
+
+ if (pchan->pending_num == 0) {
+ pchan->flags |= MXS_DMA_FLAGS_BUSY;
+ return 0;
+ }
+
+ pdesc = list_first_entry(&pchan->active, struct mxs_dma_desc, node);
+ if (pdesc == NULL)
+ return -EFAULT;
+
+ if (pchan->flags & MXS_DMA_FLAGS_BUSY) {
+ if (!(pdesc->cmd.data & MXS_DMA_DESC_CHAIN))
+ return 0;
+
+ sem = mxs_dma_read_semaphore(channel);
+ if (sem == 0)
+ return 0;
+
+ if (sem == 1) {
+ pdesc = list_entry(pdesc->node.next,
+ struct mxs_dma_desc, node);
+ writel(mxs_dma_cmd_address(pdesc),
+ &apbh_regs->ch[channel].hw_apbh_ch_nxtcmdar);
+ }
+ writel(pchan->pending_num,
+ &apbh_regs->ch[channel].hw_apbh_ch_sema);
+ pchan->active_num += pchan->pending_num;
+ pchan->pending_num = 0;
+ } else {
+ pchan->active_num += pchan->pending_num;
+ pchan->pending_num = 0;
+ writel(mxs_dma_cmd_address(pdesc),
+ &apbh_regs->ch[channel].hw_apbh_ch_nxtcmdar);
+ writel(pchan->active_num,
+ &apbh_regs->ch[channel].hw_apbh_ch_sema);
+ writel(1 << (channel + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_ctrl0_clr);
+ }
+
+ pchan->flags |= MXS_DMA_FLAGS_BUSY;
+ return 0;
+}
+
+/*
+ * Disable a DMA channel.
+ *
+ * This function shuts down a DMA channel and marks it as "not busy." Any
+ * descriptors on the active list are immediately moved to the head of the
+ * "done" list, whether or not they have actually been processed by the
+ * hardware. The "ready" flags of these descriptors are NOT cleared, so they
+ * still appear to be active.
+ *
+ * This function immediately shuts down a DMA channel's hardware, aborting any
+ * I/O that may be in progress, potentially leaving I/O hardware in an undefined
+ * state. It is unwise to call this function if there is ANY chance the hardware
+ * is still processing a command.
+ */
+int mxs_dma_disable(int channel)
+{
+ struct mxs_dma_chan *pchan;
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = mxs_dma_channels + channel;
+
+ if (!(pchan->flags & MXS_DMA_FLAGS_BUSY))
+ return -EINVAL;
+
+ writel(1 << (channel + APBH_CTRL0_CLKGATE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_ctrl0_set);
+
+ pchan->flags &= ~MXS_DMA_FLAGS_BUSY;
+ pchan->active_num = 0;
+ pchan->pending_num = 0;
+ list_splice_init(&pchan->active, &pchan->done);
+
+ return 0;
+}
+
+/*
+ * Resets the DMA channel hardware.
+ */
+int mxs_dma_reset(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ writel(1 << (channel + APBH_CHANNEL_CTRL_RESET_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_channel_ctrl_set);
+
+ return 0;
+}
+
+/*
+ * Freeze a DMA channel.
+ *
+ * This function causes the channel to continuously fail arbitration for bus
+ * access, which halts all forward progress without losing any state. A call to
+ * mxs_dma_unfreeze() will cause the channel to continue its current operation
+ * with no ill effect.
+ */
+int mxs_dma_freeze(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ writel(1 << (channel + APBH_CHANNEL_CTRL_FREEZE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_channel_ctrl_set);
+
+ return 0;
+}
+
+/*
+ * Unfreeze a DMA channel.
+ *
+ * This function reverses the effect of mxs_dma_freeze(), enabling the DMA
+ * channel to continue from where it was frozen.
+ */
+int mxs_dma_unfreeze(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ writel(1 << (channel + APBH_CHANNEL_CTRL_FREEZE_CHANNEL_OFFSET),
+ &apbh_regs->hw_apbh_channel_ctrl_clr);
+
+ return 0;
+}
+
+/*
+ * Read a DMA channel's hardware semaphore.
+ *
+ * As used by the MXS platform's DMA software, the DMA channel's hardware
+ * semaphore reflects the number of DMA commands the hardware will process, but
+ * has not yet finished. This is a volatile value read directly from hardware,
+ * so it must be be viewed as immediately stale.
+ *
+ * If the channel is not marked busy, or has finished processing all its
+ * commands, this value should be zero.
+ *
+ * See mxs_dma_append() for details on how DMA command blocks must be configured
+ * to maintain the expected behavior of the semaphore's value.
+ */
+int mxs_dma_read_semaphore(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ uint32_t tmp;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ tmp = readl(&apbh_regs->ch[channel].hw_apbh_ch_sema);
+
+ tmp &= APBH_CHn_SEMA_PHORE_MASK;
+ tmp >>= APBH_CHn_SEMA_PHORE_OFFSET;
+
+ return tmp;
+}
+
+/*
+ * Enable or disable DMA interrupt.
+ *
+ * This function enables the given DMA channel to interrupt the CPU.
+ */
+int mxs_dma_enable_irq(int channel, int enable)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ if (enable)
+ writel(1 << (channel + APBH_CTRL1_CH_CMDCMPLT_IRQ_EN_OFFSET),
+ &apbh_regs->hw_apbh_ctrl1_set);
+ else
+ writel(1 << (channel + APBH_CTRL1_CH_CMDCMPLT_IRQ_EN_OFFSET),
+ &apbh_regs->hw_apbh_ctrl1_clr);
+
+ return 0;
+}
+
+/*
+ * Check if a DMA interrupt is pending.
+ */
+int mxs_dma_irq_is_pending(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ uint32_t tmp;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ tmp = readl(&apbh_regs->hw_apbh_ctrl1);
+ tmp |= readl(&apbh_regs->hw_apbh_ctrl2);
+
+ return (tmp >> channel) & 1;
+}
+
+/*
+ * Clear DMA interrupt.
+ *
+ * The software that is using the DMA channel must register to receive its
+ * interrupts and, when they arrive, must call this function to clear them.
+ */
+int mxs_dma_ack_irq(int channel)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ writel(1 << channel, &apbh_regs->hw_apbh_ctrl1_clr);
+ writel(1 << channel, &apbh_regs->hw_apbh_ctrl2_clr);
+
+ return 0;
+}
+
+/*
+ * Request to reserve a DMA channel
+ */
+int mxs_dma_request(int channel)
+{
+ struct mxs_dma_chan *pchan;
+
+ if ((channel < 0) || (channel >= MXS_MAX_DMA_CHANNELS))
+ return -EINVAL;
+
+ pchan = mxs_dma_channels + channel;
+ if ((pchan->flags & MXS_DMA_FLAGS_VALID) != MXS_DMA_FLAGS_VALID)
+ return -ENODEV;
+
+ if (pchan->flags & MXS_DMA_FLAGS_ALLOCATED)
+ return -EBUSY;
+
+ pchan->flags |= MXS_DMA_FLAGS_ALLOCATED;
+ pchan->active_num = 0;
+ pchan->pending_num = 0;
+
+ INIT_LIST_HEAD(&pchan->active);
+ INIT_LIST_HEAD(&pchan->done);
+
+ return 0;
+}
+
+/*
+ * Release a DMA channel.
+ *
+ * This function releases a DMA channel from its current owner.
+ *
+ * The channel will NOT be released if it's marked "busy" (see
+ * mxs_dma_enable()).
+ */
+int mxs_dma_release(int channel)
+{
+ struct mxs_dma_chan *pchan;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = mxs_dma_channels + channel;
+
+ if (pchan->flags & MXS_DMA_FLAGS_BUSY)
+ return -EBUSY;
+
+ pchan->dev = 0;
+ pchan->active_num = 0;
+ pchan->pending_num = 0;
+ pchan->flags &= ~MXS_DMA_FLAGS_ALLOCATED;
+
+ return 0;
+}
+
+/*
+ * Allocate DMA descriptor
+ */
+struct mxs_dma_desc *mxs_dma_desc_alloc(void)
+{
+ struct mxs_dma_desc *pdesc;
+
+ pdesc = memalign(MXS_DMA_ALIGNMENT, sizeof(struct mxs_dma_desc));
+
+ if (pdesc == NULL)
+ return NULL;
+
+ memset(pdesc, 0, sizeof(*pdesc));
+ pdesc->address = (dma_addr_t)pdesc;
+
+ return pdesc;
+};
+
+/*
+ * Free DMA descriptor
+ */
+void mxs_dma_desc_free(struct mxs_dma_desc *pdesc)
+{
+ if (pdesc == NULL)
+ return;
+
+ free(pdesc);
+}
+
+/*
+ * Return the address of the command within a descriptor.
+ */
+unsigned int mxs_dma_cmd_address(struct mxs_dma_desc *desc)
+{
+ return desc->address + offsetof(struct mxs_dma_desc, cmd);
+}
+
+/*
+ * Check if descriptor is on a channel's active list.
+ *
+ * This function returns the state of a descriptor's "ready" flag. This flag is
+ * usually set only if the descriptor appears on a channel's active list. The
+ * descriptor may or may not have already been processed by the hardware.
+ *
+ * The "ready" flag is set when the descriptor is submitted to a channel by a
+ * call to mxs_dma_append() or mxs_dma_append_list(). The "ready" flag is
+ * cleared when a processed descriptor is moved off the active list by a call
+ * to mxs_dma_finish(). The "ready" flag is NOT cleared if the descriptor is
+ * aborted by a call to mxs_dma_disable().
+ */
+int mxs_dma_desc_pending(struct mxs_dma_desc *pdesc)
+{
+ return pdesc->flags & MXS_DMA_DESC_READY;
+}
+
+/*
+ * Add a DMA descriptor to a channel.
+ *
+ * If the descriptor list for this channel is not empty, this function sets the
+ * CHAIN bit and the NEXTCMD_ADDR fields in the last descriptor's DMA command so
+ * it will chain to the new descriptor's command.
+ *
+ * Then, this function marks the new descriptor as "ready," adds it to the end
+ * of the active descriptor list, and increments the count of pending
+ * descriptors.
+ *
+ * The MXS platform DMA software imposes some rules on DMA commands to maintain
+ * important invariants. These rules are NOT checked, but they must be carefully
+ * applied by software that uses MXS DMA channels.
+ *
+ * Invariant:
+ * The DMA channel's hardware semaphore must reflect the number of DMA
+ * commands the hardware will process, but has not yet finished.
+ *
+ * Explanation:
+ * A DMA channel begins processing commands when its hardware semaphore is
+ * written with a value greater than zero, and it stops processing commands
+ * when the semaphore returns to zero.
+ *
+ * When a channel finishes a DMA command, it will decrement its semaphore if
+ * the DECREMENT_SEMAPHORE bit is set in that command's flags bits.
+ *
+ * In principle, it's not necessary for the DECREMENT_SEMAPHORE to be set,
+ * unless it suits the purposes of the software. For example, one could
+ * construct a series of five DMA commands, with the DECREMENT_SEMAPHORE
+ * bit set only in the last one. Then, setting the DMA channel's hardware
+ * semaphore to one would cause the entire series of five commands to be
+ * processed. However, this example would violate the invariant given above.
+ *
+ * Rule:
+ * ALL DMA commands MUST have the DECREMENT_SEMAPHORE bit set so that the DMA
+ * channel's hardware semaphore will be decremented EVERY time a command is
+ * processed.
+ */
+int mxs_dma_desc_append(int channel, struct mxs_dma_desc *pdesc)
+{
+ struct mxs_dma_chan *pchan;
+ struct mxs_dma_desc *last;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = mxs_dma_channels + channel;
+
+ pdesc->cmd.next = mxs_dma_cmd_address(pdesc);
+ pdesc->flags |= MXS_DMA_DESC_FIRST | MXS_DMA_DESC_LAST;
+
+ if (!list_empty(&pchan->active)) {
+ last = list_entry(pchan->active.prev, struct mxs_dma_desc,
+ node);
+
+ pdesc->flags &= ~MXS_DMA_DESC_FIRST;
+ last->flags &= ~MXS_DMA_DESC_LAST;
+
+ last->cmd.next = mxs_dma_cmd_address(pdesc);
+ last->cmd.data |= MXS_DMA_DESC_CHAIN;
+ }
+ pdesc->flags |= MXS_DMA_DESC_READY;
+ if (pdesc->flags & MXS_DMA_DESC_FIRST)
+ pchan->pending_num++;
+ list_add_tail(&pdesc->node, &pchan->active);
+
+ return ret;
+}
+
+/*
+ * Retrieve processed DMA descriptors.
+ *
+ * This function moves all the descriptors from the DMA channel's "done" list to
+ * the head of the given list.
+ */
+int mxs_dma_get_finished(int channel, struct list_head *head)
+{
+ struct mxs_dma_chan *pchan;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ if (head == NULL)
+ return 0;
+
+ pchan = mxs_dma_channels + channel;
+
+ list_splice(&pchan->done, head);
+
+ return 0;
+}
+
+/*
+ * Clean up processed DMA descriptors.
+ *
+ * This function removes processed DMA descriptors from the "active" list. Pass
+ * in a non-NULL list head to get the descriptors moved to your list. Pass NULL
+ * to get the descriptors moved to the channel's "done" list. Descriptors on
+ * the "done" list can be retrieved with mxs_dma_get_finished().
+ *
+ * This function marks the DMA channel as "not busy" if no unprocessed
+ * descriptors remain on the "active" list.
+ */
+int mxs_dma_finish(int channel, struct list_head *head)
+{
+ int sem;
+ struct mxs_dma_chan *pchan;
+ struct list_head *p, *q;
+ struct mxs_dma_desc *pdesc;
+ int ret;
+
+ ret = mxs_dma_validate_chan(channel);
+ if (ret)
+ return ret;
+
+ pchan = mxs_dma_channels + channel;
+
+ sem = mxs_dma_read_semaphore(channel);
+ if (sem < 0)
+ return sem;
+
+ if (sem == pchan->active_num)
+ return 0;
+
+ list_for_each_safe(p, q, &pchan->active) {
+ if ((pchan->active_num) <= sem)
+ break;
+
+ pdesc = list_entry(p, struct mxs_dma_desc, node);
+ pdesc->flags &= ~MXS_DMA_DESC_READY;
+
+ if (head)
+ list_move_tail(p, head);
+ else
+ list_move_tail(p, &pchan->done);
+
+ if (pdesc->flags & MXS_DMA_DESC_LAST)
+ pchan->active_num--;
+ }
+
+ if (sem == 0)
+ pchan->flags &= ~MXS_DMA_FLAGS_BUSY;
+
+ return 0;
+}
+
+/*
+ * Wait for DMA channel to complete
+ */
+int mxs_dma_wait_complete(uint32_t timeout, unsigned int chan)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ int ret;
+
+ ret = mxs_dma_validate_chan(chan);
+ if (ret)
+ return ret;
+
+ if (mx28_wait_mask_set(&apbh_regs->hw_apbh_ctrl1_reg,
+ 1 << chan, timeout)) {
+ ret = -ETIMEDOUT;
+ mxs_dma_reset(chan);
+ }
+
+ return 0;
+}
+
+/*
+ * Execute the DMA channel
+ */
+int mxs_dma_go(int chan)
+{
+ uint32_t timeout = 10000;
+ int ret;
+
+ LIST_HEAD(tmp_desc_list);
+
+ mxs_dma_enable_irq(chan, 1);
+ mxs_dma_enable(chan);
+
+ /* Wait for DMA to finish. */
+ ret = mxs_dma_wait_complete(timeout, chan);
+
+ /* Clear out the descriptors we just ran. */
+ mxs_dma_finish(chan, &tmp_desc_list);
+
+ /* Shut the DMA channel down. */
+ mxs_dma_ack_irq(chan);
+ mxs_dma_reset(chan);
+ mxs_dma_enable_irq(chan, 0);
+ mxs_dma_disable(chan);
+
+ return ret;
+}
+
+/*
+ * Initialize the DMA hardware
+ */
+int mxs_dma_init(void)
+{
+ struct mx28_apbh_regs *apbh_regs =
+ (struct mx28_apbh_regs *)MXS_APBH_BASE;
+ struct mxs_dma_chan *pchan;
+ int ret, channel;
+
+ mx28_reset_block(&apbh_regs->hw_apbh_ctrl0_reg);
+
+#ifdef CONFIG_APBH_DMA_BURST8
+ writel(APBH_CTRL0_AHB_BURST8_EN,
+ &apbh_regs->hw_apbh_ctrl0_set);
+#else
+ writel(APBH_CTRL0_AHB_BURST8_EN,
+ &apbh_regs->hw_apbh_ctrl0_clr);
+#endif
+
+#ifdef CONFIG_APBH_DMA_BURST
+ writel(APBH_CTRL0_APB_BURST_EN,
+ &apbh_regs->hw_apbh_ctrl0_set);
+#else
+ writel(APBH_CTRL0_APB_BURST_EN,
+ &apbh_regs->hw_apbh_ctrl0_clr);
+#endif
+
+ for (channel = 0; channel < MXS_MAX_DMA_CHANNELS; channel++) {
+ pchan = mxs_dma_channels + channel;
+ pchan->flags = MXS_DMA_FLAGS_VALID;
+
+ ret = mxs_dma_request(channel);
+
+ if (ret) {
+ printf("MXS DMA: Can't acquire DMA channel %i\n",
+ channel);
+
+ goto err;
+ }
+
+ mxs_dma_reset(channel);
+ mxs_dma_ack_irq(channel);
+ }
+
+ return 0;
+
+err:
+ while (--channel >= 0)
+ mxs_dma_release(channel);
+ return ret;
+}
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index f505813..e1142d1 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -30,6 +30,7 @@ COBJS-$(CONFIG_KIRKWOOD_GPIO) += kw_gpio.o
COBJS-$(CONFIG_MARVELL_GPIO) += mvgpio.o
COBJS-$(CONFIG_MARVELL_MFP) += mvmfp.o
COBJS-$(CONFIG_MXC_GPIO) += mxc_gpio.o
+COBJS-$(CONFIG_MXS_GPIO) += mxs_gpio.o
COBJS-$(CONFIG_PCA953X) += pca953x.o
COBJS-$(CONFIG_PCA9698) += pca9698.o
COBJS-$(CONFIG_S5P) += s5p_gpio.o
diff --git a/drivers/gpio/mxs_gpio.c b/drivers/gpio/mxs_gpio.c
new file mode 100644
index 0000000..b7e9591
--- /dev/null
+++ b/drivers/gpio/mxs_gpio.c
@@ -0,0 +1,136 @@
+/*
+ * Freescale i.MX28 GPIO control code
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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
+ */
+
+#include <common.h>
+#include <netdev.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/iomux.h>
+#include <asm/arch/imx-regs.h>
+
+#if defined(CONFIG_MX23)
+#define PINCTRL_BANKS 3
+#define PINCTRL_DOUT(n) (0x0500 + ((n) * 0x10))
+#define PINCTRL_DIN(n) (0x0600 + ((n) * 0x10))
+#define PINCTRL_DOE(n) (0x0700 + ((n) * 0x10))
+#define PINCTRL_PIN2IRQ(n) (0x0800 + ((n) * 0x10))
+#define PINCTRL_IRQEN(n) (0x0900 + ((n) * 0x10))
+#define PINCTRL_IRQSTAT(n) (0x0c00 + ((n) * 0x10))
+#elif defined(CONFIG_MX28)
+#define PINCTRL_BANKS 5
+#define PINCTRL_DOUT(n) (0x0700 + ((n) * 0x10))
+#define PINCTRL_DIN(n) (0x0900 + ((n) * 0x10))
+#define PINCTRL_DOE(n) (0x0b00 + ((n) * 0x10))
+#define PINCTRL_PIN2IRQ(n) (0x1000 + ((n) * 0x10))
+#define PINCTRL_IRQEN(n) (0x1100 + ((n) * 0x10))
+#define PINCTRL_IRQSTAT(n) (0x1400 + ((n) * 0x10))
+#else
+#error "Please select CONFIG_MX23 or CONFIG_MX28"
+#endif
+
+#define GPIO_INT_FALL_EDGE 0x0
+#define GPIO_INT_LOW_LEV 0x1
+#define GPIO_INT_RISE_EDGE 0x2
+#define GPIO_INT_HIGH_LEV 0x3
+#define GPIO_INT_LEV_MASK (1 << 0)
+#define GPIO_INT_POL_MASK (1 << 1)
+
+void mxs_gpio_init(void)
+{
+ int i;
+
+ for (i = 0; i < PINCTRL_BANKS; i++) {
+ writel(0, MXS_PINCTRL_BASE + PINCTRL_PIN2IRQ(i));
+ writel(0, MXS_PINCTRL_BASE + PINCTRL_IRQEN(i));
+ /* Use SCT address here to clear the IRQSTAT bits */
+ writel(0xffffffff, MXS_PINCTRL_BASE + PINCTRL_IRQSTAT(i) + 8);
+ }
+}
+
+int gpio_get_value(int gp)
+{
+ uint32_t bank = PAD_BANK(gp);
+ uint32_t offset = PINCTRL_DIN(bank);
+ struct mx28_register *reg =
+ (struct mx28_register *)(MXS_PINCTRL_BASE + offset);
+
+ return (readl(&reg->reg) >> PAD_PIN(gp)) & 1;
+}
+
+void gpio_set_value(int gp, int value)
+{
+ uint32_t bank = PAD_BANK(gp);
+ uint32_t offset = PINCTRL_DOUT(bank);
+ struct mx28_register *reg =
+ (struct mx28_register *)(MXS_PINCTRL_BASE + offset);
+
+ if (value)
+ writel(1 << PAD_PIN(gp), &reg->reg_set);
+ else
+ writel(1 << PAD_PIN(gp), &reg->reg_clr);
+}
+
+int gpio_direction_input(int gp)
+{
+ uint32_t bank = PAD_BANK(gp);
+ uint32_t offset = PINCTRL_DOE(bank);
+ struct mx28_register *reg =
+ (struct mx28_register *)(MXS_PINCTRL_BASE + offset);
+
+ writel(1 << PAD_PIN(gp), &reg->reg_clr);
+
+ return 0;
+}
+
+int gpio_direction_output(int gp, int value)
+{
+ uint32_t bank = PAD_BANK(gp);
+ uint32_t offset = PINCTRL_DOE(bank);
+ struct mx28_register *reg =
+ (struct mx28_register *)(MXS_PINCTRL_BASE + offset);
+
+ writel(1 << PAD_PIN(gp), &reg->reg_set);
+
+ gpio_set_value(gp, value);
+
+ return 0;
+}
+
+int gpio_request(int gp, const char *label)
+{
+ if (PAD_BANK(gp) > PINCTRL_BANKS)
+ return -EINVAL;
+
+ return 0;
+}
+
+void gpio_free(int gp)
+{
+}
+
+void gpio_toggle_value(int gp)
+{
+ gpio_set_value(gp, !gpio_get_value(gp));
+}
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index a48047a..2fb521e 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -31,6 +31,7 @@ COBJS-$(CONFIG_FSL_I2C) += fsl_i2c.o
COBJS-$(CONFIG_I2C_MVTWSI) += mvtwsi.o
COBJS-$(CONFIG_I2C_MV) += mv_i2c.o
COBJS-$(CONFIG_I2C_MXC) += mxc_i2c.o
+COBJS-$(CONFIG_I2C_MXS) += mxs_i2c.o
COBJS-$(CONFIG_DRIVER_OMAP1510_I2C) += omap1510_i2c.o
COBJS-$(CONFIG_DRIVER_OMAP24XX_I2C) += omap24xx_i2c.o
COBJS-$(CONFIG_DRIVER_OMAP34XX_I2C) += omap24xx_i2c.o
diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c
new file mode 100644
index 0000000..c8fea32
--- /dev/null
+++ b/drivers/i2c/mxs_i2c.c
@@ -0,0 +1,246 @@
+/*
+ * Freescale i.MX28 I2C Driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Partly based on Linux kernel i2c-mxs.c driver:
+ * Copyright (C) 2011 Wolfram Sang, Pengutronix e.K.
+ *
+ * Which was based on a (non-working) driver which was:
+ * Copyright (C) 2009-2010 Freescale Semiconductor, 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
+ *
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+#define MXS_I2C_MAX_TIMEOUT 1000000
+
+void mxs_i2c_reset(void)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+ int ret;
+
+ ret = mx28_reset_block(&i2c_regs->hw_i2c_ctrl0_reg);
+ if (ret) {
+ debug("MXS I2C: Block reset timeout\n");
+ return;
+ }
+
+ writel(I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ | I2C_CTRL1_NO_SLAVE_ACK_IRQ |
+ I2C_CTRL1_EARLY_TERM_IRQ | I2C_CTRL1_MASTER_LOSS_IRQ |
+ I2C_CTRL1_SLAVE_STOP_IRQ | I2C_CTRL1_SLAVE_IRQ,
+ &i2c_regs->hw_i2c_ctrl1_clr);
+
+ writel(I2C_QUEUECTRL_PIO_QUEUE_MODE, &i2c_regs->hw_i2c_queuectrl_set);
+}
+
+void mxs_i2c_setup_read(uint8_t chip, int len)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+
+ writel(I2C_QUEUECMD_RETAIN_CLOCK | I2C_QUEUECMD_PRE_SEND_START |
+ I2C_QUEUECMD_MASTER_MODE | I2C_QUEUECMD_DIRECTION |
+ (1 << I2C_QUEUECMD_XFER_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_queuecmd);
+
+ writel((chip << 1) | 1, &i2c_regs->hw_i2c_data);
+
+ writel(I2C_QUEUECMD_SEND_NAK_ON_LAST | I2C_QUEUECMD_MASTER_MODE |
+ (len << I2C_QUEUECMD_XFER_COUNT_OFFSET) |
+ I2C_QUEUECMD_POST_SEND_STOP, &i2c_regs->hw_i2c_queuecmd);
+
+ writel(I2C_QUEUECTRL_QUEUE_RUN, &i2c_regs->hw_i2c_queuectrl_set);
+}
+
+void mxs_i2c_write(uchar chip, uint addr, int alen,
+ uchar *buf, int blen, int stop)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+ uint32_t data;
+ int i, remain, off;
+
+ if ((alen > 4) || (alen == 0)) {
+ debug("MXS I2C: Invalid address length\n");
+ return;
+ }
+
+ if (stop)
+ stop = I2C_QUEUECMD_POST_SEND_STOP;
+
+ writel(I2C_QUEUECMD_PRE_SEND_START |
+ I2C_QUEUECMD_MASTER_MODE | I2C_QUEUECMD_DIRECTION |
+ ((blen + alen + 1) << I2C_QUEUECMD_XFER_COUNT_OFFSET) | stop,
+ &i2c_regs->hw_i2c_queuecmd);
+
+ data = (chip << 1) << 24;
+
+ for (i = 0; i < alen; i++) {
+ data >>= 8;
+ data |= ((char *)&addr)[i] << 24;
+ if ((i & 3) == 2)
+ writel(data, &i2c_regs->hw_i2c_data);
+ }
+
+ off = i;
+ for (; i < off + blen; i++) {
+ data >>= 8;
+ data |= buf[i - off] << 24;
+ if ((i & 3) == 2)
+ writel(data, &i2c_regs->hw_i2c_data);
+ }
+
+ remain = 24 - ((i & 3) * 8);
+ if (remain)
+ writel(data >> remain, &i2c_regs->hw_i2c_data);
+
+ writel(I2C_QUEUECTRL_QUEUE_RUN, &i2c_regs->hw_i2c_queuectrl_set);
+}
+
+int mxs_i2c_wait_for_ack(void)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+ uint32_t tmp;
+ int timeout = MXS_I2C_MAX_TIMEOUT;
+
+ for (;;) {
+ tmp = readl(&i2c_regs->hw_i2c_ctrl1);
+ if (tmp & I2C_CTRL1_NO_SLAVE_ACK_IRQ) {
+ debug("MXS I2C: No slave ACK\n");
+ goto err;
+ }
+
+ if (tmp & (
+ I2C_CTRL1_EARLY_TERM_IRQ | I2C_CTRL1_MASTER_LOSS_IRQ |
+ I2C_CTRL1_SLAVE_STOP_IRQ | I2C_CTRL1_SLAVE_IRQ)) {
+ debug("MXS I2C: Error (CTRL1 = %08x)\n", tmp);
+ goto err;
+ }
+
+ if (tmp & I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ)
+ break;
+
+ if (!timeout--) {
+ debug("MXS I2C: Operation timed out\n");
+ goto err;
+ }
+
+ udelay(1);
+ }
+
+ return 0;
+
+err:
+ mxs_i2c_reset();
+ return 1;
+}
+
+int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+ uint32_t tmp = 0;
+ int ret;
+ int i;
+
+ mxs_i2c_write(chip, addr, alen, NULL, 0, 0);
+ ret = mxs_i2c_wait_for_ack();
+ if (ret) {
+ debug("MXS I2C: Failed writing address\n");
+ return ret;
+ }
+
+ mxs_i2c_setup_read(chip, len);
+ ret = mxs_i2c_wait_for_ack();
+ if (ret) {
+ debug("MXS I2C: Failed reading address\n");
+ return ret;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (!(i & 3)) {
+ while (readl(&i2c_regs->hw_i2c_queuestat) &
+ I2C_QUEUESTAT_RD_QUEUE_EMPTY)
+ ;
+ tmp = readl(&i2c_regs->hw_i2c_queuedata);
+ }
+ buffer[i] = tmp & 0xff;
+ tmp >>= 8;
+ }
+
+ return 0;
+}
+
+int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len)
+{
+ int ret;
+ mxs_i2c_write(chip, addr, alen, buffer, len, 1);
+ ret = mxs_i2c_wait_for_ack();
+ if (ret)
+ debug("MXS I2C: Failed writing address\n");
+
+ return ret;
+}
+
+int i2c_probe(uchar chip)
+{
+ int ret;
+ mxs_i2c_write(chip, 0, 1, NULL, 0, 1);
+ ret = mxs_i2c_wait_for_ack();
+ mxs_i2c_reset();
+ return ret;
+}
+
+void i2c_init(int speed, int slaveadd)
+{
+ struct mx28_i2c_regs *i2c_regs = (struct mx28_i2c_regs *)MXS_I2C0_BASE;
+
+ mxs_i2c_reset();
+
+ switch (speed) {
+ case 100000:
+ writel((0x0078 << I2C_TIMING0_HIGH_COUNT_OFFSET) |
+ (0x0030 << I2C_TIMING0_RCV_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_timing0);
+ writel((0x0080 << I2C_TIMING1_LOW_COUNT_OFFSET) |
+ (0x0030 << I2C_TIMING1_XMIT_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_timing1);
+ break;
+ case 400000:
+ writel((0x000f << I2C_TIMING0_HIGH_COUNT_OFFSET) |
+ (0x0007 << I2C_TIMING0_RCV_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_timing0);
+ writel((0x001f << I2C_TIMING1_LOW_COUNT_OFFSET) |
+ (0x000f << I2C_TIMING1_XMIT_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_timing1);
+ break;
+ default:
+ printf("MXS I2C: Invalid speed selected (%d Hz)\n", speed);
+ return;
+ }
+
+ writel((0x0015 << I2C_TIMING2_BUS_FREE_OFFSET) |
+ (0x000d << I2C_TIMING2_LEADIN_COUNT_OFFSET),
+ &i2c_regs->hw_i2c_timing2);
+
+ return;
+}
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 9f9db75..506f1d6 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -36,6 +36,7 @@ COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o
COBJS-$(CONFIG_ARM_PL180_MMCI) += arm_pl180_mmci.o
COBJS-$(CONFIG_MV_SDHCI) += mv_sdhci.o
COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o
+COBJS-$(CONFIG_MXS_MMC) += mxsmmc.o
COBJS-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o
COBJS-$(CONFIG_PXA_MMC) += pxa_mmc.o
COBJS-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o
diff --git a/drivers/mmc/mxsmmc.c b/drivers/mmc/mxsmmc.c
new file mode 100644
index 0000000..2a9949e
--- /dev/null
+++ b/drivers/mmc/mxsmmc.c
@@ -0,0 +1,351 @@
+/*
+ * Freescale i.MX28 SSP MMC driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * (C) Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Terry Lv
+ *
+ * Copyright 2007, Freescale Semiconductor, Inc
+ * Andy Fleming
+ *
+ * Based vaguely on the pxa mmc code:
+ * (C) Copyright 2003
+ * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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
+ */
+#include <common.h>
+#include <malloc.h>
+#include <mmc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+struct mxsmmc_priv {
+ int id;
+ struct mx28_ssp_regs *regs;
+ uint32_t clkseq_bypass;
+ uint32_t *clkctrl_ssp;
+ uint32_t buswidth;
+ int (*mmc_is_wp)(int);
+};
+
+#define MXSMMC_MAX_TIMEOUT 10000
+
+/*
+ * Sends a command out on the bus. Takes the mmc pointer,
+ * a command pointer, and an optional data pointer.
+ */
+static int
+mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
+{
+ struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv;
+ struct mx28_ssp_regs *ssp_regs = priv->regs;
+ uint32_t reg;
+ int timeout;
+ uint32_t data_count;
+ uint32_t *data_ptr;
+ uint32_t ctrl0;
+
+ debug("MMC%d: CMD%d\n", mmc->block_dev.dev, cmd->cmdidx);
+
+ /* Check bus busy */
+ timeout = MXSMMC_MAX_TIMEOUT;
+ while (--timeout) {
+ udelay(1000);
+ reg = readl(&ssp_regs->hw_ssp_status);
+ if (!(reg &
+ (SSP_STATUS_BUSY | SSP_STATUS_DATA_BUSY |
+ SSP_STATUS_CMD_BUSY))) {
+ break;
+ }
+ }
+
+ if (!timeout) {
+ printf("MMC%d: Bus busy timeout!\n", mmc->block_dev.dev);
+ return TIMEOUT;
+ }
+
+ /* See if card is present */
+ if (readl(&ssp_regs->hw_ssp_status) & SSP_STATUS_CARD_DETECT) {
+ printf("MMC%d: No card detected!\n", mmc->block_dev.dev);
+ return NO_CARD_ERR;
+ }
+
+ /* Start building CTRL0 contents */
+ ctrl0 = priv->buswidth;
+
+ /* Set up command */
+ if (!(cmd->resp_type & MMC_RSP_CRC))
+ ctrl0 |= SSP_CTRL0_IGNORE_CRC;
+ if (cmd->resp_type & MMC_RSP_PRESENT) /* Need to get response */
+ ctrl0 |= SSP_CTRL0_GET_RESP;
+ if (cmd->resp_type & MMC_RSP_136) /* It's a 136 bits response */
+ ctrl0 |= SSP_CTRL0_LONG_RESP;
+
+ /* Command index */
+ reg = readl(&ssp_regs->hw_ssp_cmd0);
+ reg &= ~(SSP_CMD0_CMD_MASK | SSP_CMD0_APPEND_8CYC);
+ reg |= cmd->cmdidx << SSP_CMD0_CMD_OFFSET;
+ if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)
+ reg |= SSP_CMD0_APPEND_8CYC;
+ writel(reg, &ssp_regs->hw_ssp_cmd0);
+
+ /* Command argument */
+ writel(cmd->cmdarg, &ssp_regs->hw_ssp_cmd1);
+
+ /* Set up data */
+ if (data) {
+ /* READ or WRITE */
+ if (data->flags & MMC_DATA_READ) {
+ ctrl0 |= SSP_CTRL0_READ;
+ } else if (priv->mmc_is_wp(mmc->block_dev.dev)) {
+ printf("MMC%d: Can not write a locked card!\n",
+ mmc->block_dev.dev);
+ return UNUSABLE_ERR;
+ }
+
+ ctrl0 |= SSP_CTRL0_DATA_XFER;
+ reg = ((data->blocks - 1) <<
+ SSP_BLOCK_SIZE_BLOCK_COUNT_OFFSET) |
+ ((ffs(data->blocksize) - 1) <<
+ SSP_BLOCK_SIZE_BLOCK_SIZE_OFFSET);
+ writel(reg, &ssp_regs->hw_ssp_block_size);
+
+ reg = data->blocksize * data->blocks;
+ writel(reg, &ssp_regs->hw_ssp_xfer_size);
+ }
+
+ /* Kick off the command */
+ ctrl0 |= SSP_CTRL0_WAIT_FOR_IRQ | SSP_CTRL0_ENABLE | SSP_CTRL0_RUN;
+ writel(ctrl0, &ssp_regs->hw_ssp_ctrl0);
+
+ /* Wait for the command to complete */
+ timeout = MXSMMC_MAX_TIMEOUT;
+ while (--timeout) {
+ udelay(1000);
+ reg = readl(&ssp_regs->hw_ssp_status);
+ if (!(reg & SSP_STATUS_CMD_BUSY))
+ break;
+ }
+
+ if (!timeout) {
+ printf("MMC%d: Command %d busy\n",
+ mmc->block_dev.dev, cmd->cmdidx);
+ return TIMEOUT;
+ }
+
+ /* Check command timeout */
+ if (reg & SSP_STATUS_RESP_TIMEOUT) {
+ printf("MMC%d: Command %d timeout (status 0x%08x)\n",
+ mmc->block_dev.dev, cmd->cmdidx, reg);
+ return TIMEOUT;
+ }
+
+ /* Check command errors */
+ if (reg & (SSP_STATUS_RESP_CRC_ERR | SSP_STATUS_RESP_ERR)) {
+ printf("MMC%d: Command %d error (status 0x%08x)!\n",
+ mmc->block_dev.dev, cmd->cmdidx, reg);
+ return COMM_ERR;
+ }
+
+ /* Copy response to response buffer */
+ if (cmd->resp_type & MMC_RSP_136) {
+ cmd->response[3] = readl(&ssp_regs->hw_ssp_sdresp0);
+ cmd->response[2] = readl(&ssp_regs->hw_ssp_sdresp1);
+ cmd->response[1] = readl(&ssp_regs->hw_ssp_sdresp2);
+ cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp3);
+ } else
+ cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp0);
+
+ /* Return if no data to process */
+ if (!data)
+ return 0;
+
+ /* Process the data */
+ data_count = data->blocksize * data->blocks;
+ timeout = MXSMMC_MAX_TIMEOUT;
+ if (data->flags & MMC_DATA_READ) {
+ data_ptr = (uint32_t *)data->dest;
+ while (data_count && --timeout) {
+ reg = readl(&ssp_regs->hw_ssp_status);
+ if (!(reg & SSP_STATUS_FIFO_EMPTY)) {
+ *data_ptr++ = readl(&ssp_regs->hw_ssp_data);
+ data_count -= 4;
+ timeout = MXSMMC_MAX_TIMEOUT;
+ } else
+ udelay(1000);
+ }
+ } else {
+ data_ptr = (uint32_t *)data->src;
+ timeout *= 100;
+ while (data_count && --timeout) {
+ reg = readl(&ssp_regs->hw_ssp_status);
+ if (!(reg & SSP_STATUS_FIFO_FULL)) {
+ writel(*data_ptr++, &ssp_regs->hw_ssp_data);
+ data_count -= 4;
+ timeout = MXSMMC_MAX_TIMEOUT;
+ } else
+ udelay(1000);
+ }
+ }
+
+ if (!timeout) {
+ printf("MMC%d: Data timeout with command %d (status 0x%08x)!\n",
+ mmc->block_dev.dev, cmd->cmdidx, reg);
+ return COMM_ERR;
+ }
+
+ /* Check data errors */
+ reg = readl(&ssp_regs->hw_ssp_status);
+ if (reg &
+ (SSP_STATUS_TIMEOUT | SSP_STATUS_DATA_CRC_ERR |
+ SSP_STATUS_FIFO_OVRFLW | SSP_STATUS_FIFO_UNDRFLW)) {
+ printf("MMC%d: Data error with command %d (status 0x%08x)!\n",
+ mmc->block_dev.dev, cmd->cmdidx, reg);
+ return COMM_ERR;
+ }
+
+ return 0;
+}
+
+static void mxsmmc_set_ios(struct mmc *mmc)
+{
+ struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv;
+ struct mx28_ssp_regs *ssp_regs = priv->regs;
+
+ /* Set the clock speed */
+ if (mmc->clock)
+ mx28_set_ssp_busclock(priv->id, mmc->clock / 1000);
+
+ switch (mmc->bus_width) {
+ case 1:
+ priv->buswidth = SSP_CTRL0_BUS_WIDTH_ONE_BIT;
+ break;
+ case 4:
+ priv->buswidth = SSP_CTRL0_BUS_WIDTH_FOUR_BIT;
+ break;
+ case 8:
+ priv->buswidth = SSP_CTRL0_BUS_WIDTH_EIGHT_BIT;
+ break;
+ }
+
+ /* Set the bus width */
+ clrsetbits_le32(&ssp_regs->hw_ssp_ctrl0,
+ SSP_CTRL0_BUS_WIDTH_MASK, priv->buswidth);
+
+ debug("MMC%d: Set %d bits bus width\n",
+ mmc->block_dev.dev, mmc->bus_width);
+}
+
+static int mxsmmc_init(struct mmc *mmc)
+{
+ struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv;
+ struct mx28_ssp_regs *ssp_regs = priv->regs;
+
+ /* Reset SSP */
+ mx28_reset_block(&ssp_regs->hw_ssp_ctrl0_reg);
+
+ /* 8 bits word length in MMC mode */
+ clrsetbits_le32(&ssp_regs->hw_ssp_ctrl1,
+ SSP_CTRL1_SSP_MODE_MASK | SSP_CTRL1_WORD_LENGTH_MASK,
+ SSP_CTRL1_SSP_MODE_SD_MMC | SSP_CTRL1_WORD_LENGTH_EIGHT_BITS);
+
+ /* Set initial bit clock 400 KHz */
+ mx28_set_ssp_busclock(priv->id, 400);
+
+ /* Send initial 74 clock cycles (185 us @ 400 KHz)*/
+ writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_set);
+ udelay(200);
+ writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_clr);
+
+ return 0;
+}
+
+int mxsmmc_initialize(bd_t *bis, int id, int (*wp)(int))
+{
+ struct mx28_clkctrl_regs *clkctrl_regs =
+ (struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+ struct mmc *mmc = NULL;
+ struct mxsmmc_priv *priv = NULL;
+
+ mmc = malloc(sizeof(struct mmc));
+ if (!mmc)
+ return -ENOMEM;
+
+ priv = malloc(sizeof(struct mxsmmc_priv));
+ if (!priv) {
+ free(mmc);
+ return -ENOMEM;
+ }
+
+ priv->mmc_is_wp = wp;
+ priv->id = id;
+ switch (id) {
+ case 0:
+ priv->regs = (struct mx28_ssp_regs *)MXS_SSP0_BASE;
+ priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP0;
+ priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp0;
+ break;
+ case 1:
+ priv->regs = (struct mx28_ssp_regs *)MXS_SSP1_BASE;
+ priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP1;
+ priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp1;
+ break;
+ case 2:
+ priv->regs = (struct mx28_ssp_regs *)MXS_SSP2_BASE;
+ priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP2;
+ priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp2;
+ break;
+ case 3:
+ priv->regs = (struct mx28_ssp_regs *)MXS_SSP3_BASE;
+ priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP3;
+ priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp3;
+ break;
+ }
+
+ sprintf(mmc->name, "MXS MMC");
+ mmc->send_cmd = mxsmmc_send_cmd;
+ mmc->set_ios = mxsmmc_set_ios;
+ mmc->init = mxsmmc_init;
+ mmc->priv = priv;
+
+ mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT |
+ MMC_MODE_HS_52MHz | MMC_MODE_HS;
+
+ /*
+ * SSPCLK = 480 * 18 / 29 / 1 = 297.731 MHz
+ * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)),
+ * CLOCK_DIVIDE has to be an even value from 2 to 254, and
+ * CLOCK_RATE could be any integer from 0 to 255.
+ */
+ mmc->f_min = 400000;
+ mmc->f_max = mxc_get_clock(MXC_SSP0_CLK + id) * 1000 / 2;
+ mmc->b_max = 0;
+
+ mmc_register(mmc);
+ return 0;
+}
diff --git a/drivers/mmc/omap_hsmmc.c b/drivers/mmc/omap_hsmmc.c
index ebda980..c38b9e6 100644
--- a/drivers/mmc/omap_hsmmc.c
+++ b/drivers/mmc/omap_hsmmc.c
@@ -36,8 +36,9 @@
/* If we fail after 1 second wait, something is really bad */
#define MAX_RETRY_MS 1000
-static int mmc_read_data(hsmmc_t *mmc_base, char *buf, unsigned int size);
-static int mmc_write_data(hsmmc_t *mmc_base, const char *buf, unsigned int siz);
+static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size);
+static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
+ unsigned int siz);
static struct mmc hsmmc_dev[2];
#if defined(CONFIG_OMAP44XX) && defined(CONFIG_TWL6030_POWER)
@@ -97,7 +98,7 @@ unsigned char mmc_board_init(struct mmc *mmc)
return 0;
}
-void mmc_init_stream(hsmmc_t *mmc_base)
+void mmc_init_stream(struct hsmmc *mmc_base)
{
ulong start;
@@ -128,7 +129,7 @@ void mmc_init_stream(hsmmc_t *mmc_base)
static int mmc_init_setup(struct mmc *mmc)
{
- hsmmc_t *mmc_base = (hsmmc_t *)mmc->priv;
+ struct hsmmc *mmc_base = (struct hsmmc *)mmc->priv;
unsigned int reg_val;
unsigned int dsor;
ulong start;
@@ -192,7 +193,7 @@ static int mmc_init_setup(struct mmc *mmc)
static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
struct mmc_data *data)
{
- hsmmc_t *mmc_base = (hsmmc_t *)mmc->priv;
+ struct hsmmc *mmc_base = (struct hsmmc *)mmc->priv;
unsigned int flags, mmc_stat;
ulong start;
@@ -305,7 +306,7 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
return 0;
}
-static int mmc_read_data(hsmmc_t *mmc_base, char *buf, unsigned int size)
+static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size)
{
unsigned int *output_buf = (unsigned int *)buf;
unsigned int mmc_stat;
@@ -356,7 +357,8 @@ static int mmc_read_data(hsmmc_t *mmc_base, char *buf, unsigned int size)
return 0;
}
-static int mmc_write_data(hsmmc_t *mmc_base, const char *buf, unsigned int size)
+static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
+ unsigned int size)
{
unsigned int *input_buf = (unsigned int *)buf;
unsigned int mmc_stat;
@@ -409,7 +411,7 @@ static int mmc_write_data(hsmmc_t *mmc_base, const char *buf, unsigned int size)
static void mmc_set_ios(struct mmc *mmc)
{
- hsmmc_t *mmc_base = (hsmmc_t *)mmc->priv;
+ struct hsmmc *mmc_base = (struct hsmmc *)mmc->priv;
unsigned int dsor = 0;
ulong start;
@@ -473,20 +475,20 @@ int omap_mmc_init(int dev_index)
switch (dev_index) {
case 0:
- mmc->priv = (hsmmc_t *)OMAP_HSMMC1_BASE;
+ mmc->priv = (struct hsmmc *)OMAP_HSMMC1_BASE;
break;
#ifdef OMAP_HSMMC2_BASE
case 1:
- mmc->priv = (hsmmc_t *)OMAP_HSMMC2_BASE;
+ mmc->priv = (struct hsmmc *)OMAP_HSMMC2_BASE;
break;
#endif
#ifdef OMAP_HSMMC3_BASE
case 2:
- mmc->priv = (hsmmc_t *)OMAP_HSMMC3_BASE;
+ mmc->priv = (struct hsmmc *)OMAP_HSMMC3_BASE;
break;
#endif
default:
- mmc->priv = (hsmmc_t *)OMAP_HSMMC1_BASE;
+ mmc->priv = (struct hsmmc *)OMAP_HSMMC1_BASE;
return 1;
}
mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 28bd350..36ee454 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -54,6 +54,7 @@ COBJS-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o
COBJS-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o
COBJS-$(CONFIG_NAND_MPC5121_NFC) += mpc5121_nfc.o
COBJS-$(CONFIG_NAND_MXC) += mxc_nand.o
+COBJS-$(CONFIG_NAND_MXS) += mxs_nand.o
COBJS-$(CONFIG_NAND_NDFC) += ndfc.o
COBJS-$(CONFIG_NAND_NOMADIK) += nomadik.o
COBJS-$(CONFIG_NAND_S3C2410) += s3c2410_nand.o
diff --git a/drivers/mtd/nand/mxs_nand.c b/drivers/mtd/nand/mxs_nand.c
new file mode 100644
index 0000000..ce2a326
--- /dev/null
+++ b/drivers/mtd/nand/mxs_nand.c
@@ -0,0 +1,1118 @@
+/*
+ * Freescale i.MX28 NAND flash driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * Freescale GPMI NFC NAND Flash Driver
+ *
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/types.h>
+#include <common.h>
+#include <malloc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/arch/dma.h>
+
+#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4
+
+#define MXS_NAND_CHUNK_DATA_CHUNK_SIZE 512
+#define MXS_NAND_METADATA_SIZE 10
+
+#define MXS_NAND_COMMAND_BUFFER_SIZE 32
+
+#define MXS_NAND_BCH_TIMEOUT 10000
+
+struct mxs_nand_info {
+ int cur_chip;
+
+ uint32_t cmd_queue_len;
+
+ uint8_t *cmd_buf;
+ uint8_t *data_buf;
+ uint8_t *oob_buf;
+
+ uint8_t marking_block_bad;
+ uint8_t raw_oob_mode;
+
+ /* Functions with altered behaviour */
+ int (*hooked_read_oob)(struct mtd_info *mtd,
+ loff_t from, struct mtd_oob_ops *ops);
+ int (*hooked_write_oob)(struct mtd_info *mtd,
+ loff_t to, struct mtd_oob_ops *ops);
+ int (*hooked_block_markbad)(struct mtd_info *mtd,
+ loff_t ofs);
+
+ /* DMA descriptors */
+ struct mxs_dma_desc **desc;
+ uint32_t desc_index;
+};
+
+struct nand_ecclayout fake_ecc_layout;
+
+static struct mxs_dma_desc *mxs_nand_get_dma_desc(struct mxs_nand_info *info)
+{
+ struct mxs_dma_desc *desc;
+
+ if (info->desc_index >= MXS_NAND_DMA_DESCRIPTOR_COUNT) {
+ printf("MXS NAND: Too many DMA descriptors requested\n");
+ return NULL;
+ }
+
+ desc = info->desc[info->desc_index];
+ info->desc_index++;
+
+ return desc;
+}
+
+static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
+{
+ int i;
+ struct mxs_dma_desc *desc;
+
+ for (i = 0; i < info->desc_index; i++) {
+ desc = info->desc[i];
+ memset(desc, 0, sizeof(struct mxs_dma_desc));
+ desc->address = (dma_addr_t)desc;
+ }
+
+ info->desc_index = 0;
+}
+
+static uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
+{
+ return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+}
+
+static uint32_t mxs_nand_ecc_size_in_bits(uint32_t ecc_strength)
+{
+ return ecc_strength * 13;
+}
+
+static uint32_t mxs_nand_aux_status_offset(void)
+{
+ return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3;
+}
+
+static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
+ uint32_t page_oob_size)
+{
+ if (page_data_size == 2048)
+ return 8;
+
+ if (page_data_size == 4096) {
+ if (page_oob_size == 128)
+ return 8;
+
+ if (page_oob_size == 218)
+ return 16;
+ }
+
+ return 0;
+}
+
+static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size,
+ uint32_t ecc_strength)
+{
+ uint32_t chunk_data_size_in_bits;
+ uint32_t chunk_ecc_size_in_bits;
+ uint32_t chunk_total_size_in_bits;
+ uint32_t block_mark_chunk_number;
+ uint32_t block_mark_chunk_bit_offset;
+ uint32_t block_mark_bit_offset;
+
+ chunk_data_size_in_bits = MXS_NAND_CHUNK_DATA_CHUNK_SIZE * 8;
+ chunk_ecc_size_in_bits = mxs_nand_ecc_size_in_bits(ecc_strength);
+
+ chunk_total_size_in_bits =
+ chunk_data_size_in_bits + chunk_ecc_size_in_bits;
+
+ /* Compute the bit offset of the block mark within the physical page. */
+ block_mark_bit_offset = page_data_size * 8;
+
+ /* Subtract the metadata bits. */
+ block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8;
+
+ /*
+ * Compute the chunk number (starting at zero) in which the block mark
+ * appears.
+ */
+ block_mark_chunk_number =
+ block_mark_bit_offset / chunk_total_size_in_bits;
+
+ /*
+ * Compute the bit offset of the block mark within its chunk, and
+ * validate it.
+ */
+ block_mark_chunk_bit_offset = block_mark_bit_offset -
+ (block_mark_chunk_number * chunk_total_size_in_bits);
+
+ if (block_mark_chunk_bit_offset > chunk_data_size_in_bits)
+ return 1;
+
+ /*
+ * Now that we know the chunk number in which the block mark appears,
+ * we can subtract all the ECC bits that appear before it.
+ */
+ block_mark_bit_offset -=
+ block_mark_chunk_number * chunk_ecc_size_in_bits;
+
+ return block_mark_bit_offset;
+}
+
+static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd)
+{
+ uint32_t ecc_strength;
+ ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
+ return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3;
+}
+
+static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd)
+{
+ uint32_t ecc_strength;
+ ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
+ return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) & 0x7;
+}
+
+/*
+ * Wait for BCH complete IRQ and clear the IRQ
+ */
+static int mxs_nand_wait_for_bch_complete(void)
+{
+ struct mx28_bch_regs *bch_regs = (struct mx28_bch_regs *)MXS_BCH_BASE;
+ int timeout = MXS_NAND_BCH_TIMEOUT;
+ int ret;
+
+ ret = mx28_wait_mask_set(&bch_regs->hw_bch_ctrl_reg,
+ BCH_CTRL_COMPLETE_IRQ, timeout);
+
+ writel(BCH_CTRL_COMPLETE_IRQ, &bch_regs->hw_bch_ctrl_clr);
+
+ return ret;
+}
+
+/*
+ * This is the function that we install in the cmd_ctrl function pointer of the
+ * owning struct nand_chip. The only functions in the reference implementation
+ * that use these functions pointers are cmdfunc and select_chip.
+ *
+ * In this driver, we implement our own select_chip, so this function will only
+ * be called by the reference implementation's cmdfunc. For this reason, we can
+ * ignore the chip enable bit and concentrate only on sending bytes to the NAND
+ * Flash.
+ */
+static void mxs_nand_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl)
+{
+ struct nand_chip *nand = mtd->priv;
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mxs_dma_desc *d;
+ uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+ int ret;
+
+ /*
+ * If this condition is true, something is _VERY_ wrong in MTD
+ * subsystem!
+ */
+ if (nand_info->cmd_queue_len == MXS_NAND_COMMAND_BUFFER_SIZE) {
+ printf("MXS NAND: Command queue too long\n");
+ return;
+ }
+
+ /*
+ * Every operation begins with a command byte and a series of zero or
+ * more address bytes. These are distinguished by either the Address
+ * Latch Enable (ALE) or Command Latch Enable (CLE) signals being
+ * asserted. When MTD is ready to execute the command, it will
+ * deasert both latch enables.
+ *
+ * Rather than run a separate DMA operation for every single byte, we
+ * queue them up and run a single DMA operation for the entire series
+ * of command and data bytes.
+ */
+ if (ctrl & (NAND_ALE | NAND_CLE)) {
+ if (data != NAND_CMD_NONE)
+ nand_info->cmd_buf[nand_info->cmd_queue_len++] = data;
+ return;
+ }
+
+ /*
+ * If control arrives here, MTD has deasserted both the ALE and CLE,
+ * which means it's ready to run an operation. Check if we have any
+ * bytes to send.
+ */
+ if (nand_info->cmd_queue_len == 0)
+ return;
+
+ /* Compile the DMA descriptor -- a descriptor that sends command. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_DEC_SEM |
+ MXS_DMA_DESC_WAIT4END | (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+ (nand_info->cmd_queue_len << MXS_DMA_DESC_BYTES_OFFSET);
+
+ d->cmd.address = (dma_addr_t)nand_info->cmd_buf;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_CLE |
+ GPMI_CTRL0_ADDRESS_INCREMENT |
+ nand_info->cmd_queue_len;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_go(channel);
+ if (ret)
+ printf("MXS NAND: Error sending command\n");
+
+ mxs_nand_return_dma_descs(nand_info);
+
+ /* Reset the command queue. */
+ nand_info->cmd_queue_len = 0;
+}
+
+/*
+ * Test if the NAND flash is ready.
+ */
+static int mxs_nand_device_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct mxs_nand_info *nand_info = chip->priv;
+ struct mx28_gpmi_regs *gpmi_regs =
+ (struct mx28_gpmi_regs *)MXS_GPMI_BASE;
+ uint32_t tmp;
+
+ tmp = readl(&gpmi_regs->hw_gpmi_stat);
+ tmp >>= (GPMI_STAT_READY_BUSY_OFFSET + nand_info->cur_chip);
+
+ return tmp & 1;
+}
+
+/*
+ * Select the NAND chip.
+ */
+static void mxs_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *nand = mtd->priv;
+ struct mxs_nand_info *nand_info = nand->priv;
+
+ nand_info->cur_chip = chip;
+}
+
+/*
+ * Handle block mark swapping.
+ *
+ * Note that, when this function is called, it doesn't know whether it's
+ * swapping the block mark, or swapping it *back* -- but it doesn't matter
+ * because the the operation is the same.
+ */
+static void mxs_nand_swap_block_mark(struct mtd_info *mtd,
+ uint8_t *data_buf, uint8_t *oob_buf)
+{
+ uint32_t bit_offset;
+ uint32_t buf_offset;
+
+ uint32_t src;
+ uint32_t dst;
+
+ bit_offset = mxs_nand_mark_bit_offset(mtd);
+ buf_offset = mxs_nand_mark_byte_offset(mtd);
+
+ /*
+ * Get the byte from the data area that overlays the block mark. Since
+ * the ECC engine applies its own view to the bits in the page, the
+ * physical block mark won't (in general) appear on a byte boundary in
+ * the data.
+ */
+ src = data_buf[buf_offset] >> bit_offset;
+ src |= data_buf[buf_offset + 1] << (8 - bit_offset);
+
+ dst = oob_buf[0];
+
+ oob_buf[0] = src;
+
+ data_buf[buf_offset] &= ~(0xff << bit_offset);
+ data_buf[buf_offset + 1] &= 0xff << bit_offset;
+
+ data_buf[buf_offset] |= dst << bit_offset;
+ data_buf[buf_offset + 1] |= dst >> (8 - bit_offset);
+}
+
+/*
+ * Read data from NAND.
+ */
+static void mxs_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int length)
+{
+ struct nand_chip *nand = mtd->priv;
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mxs_dma_desc *d;
+ uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+ int ret;
+
+ if (length > NAND_MAX_PAGESIZE) {
+ printf("MXS NAND: DMA buffer too big\n");
+ return;
+ }
+
+ if (!buf) {
+ printf("MXS NAND: DMA buffer is NULL\n");
+ return;
+ }
+
+ /* Compile the DMA descriptor - a descriptor that reads data. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_DMA_WRITE | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+ (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+ (length << MXS_DMA_DESC_BYTES_OFFSET);
+
+ d->cmd.address = (dma_addr_t)nand_info->data_buf;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ length;
+
+ mxs_dma_desc_append(channel, d);
+
+ /*
+ * A DMA descriptor that waits for the command to end and the chip to
+ * become ready.
+ *
+ * I think we actually should *not* be waiting for the chip to become
+ * ready because, after all, we don't care. I think the original code
+ * did that and no one has re-thought it yet.
+ */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_DEC_SEM |
+ MXS_DMA_DESC_WAIT4END | (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+ d->cmd.address = 0;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_go(channel);
+ if (ret) {
+ printf("MXS NAND: DMA read error\n");
+ goto rtn;
+ }
+
+ memcpy(buf, nand_info->data_buf, length);
+
+rtn:
+ mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Write data to NAND.
+ */
+static void mxs_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+ int length)
+{
+ struct nand_chip *nand = mtd->priv;
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mxs_dma_desc *d;
+ uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+ int ret;
+
+ if (length > NAND_MAX_PAGESIZE) {
+ printf("MXS NAND: DMA buffer too big\n");
+ return;
+ }
+
+ if (!buf) {
+ printf("MXS NAND: DMA buffer is NULL\n");
+ return;
+ }
+
+ memcpy(nand_info->data_buf, buf, length);
+
+ /* Compile the DMA descriptor - a descriptor that writes data. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+ (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+ (length << MXS_DMA_DESC_BYTES_OFFSET);
+
+ d->cmd.address = (dma_addr_t)nand_info->data_buf;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ length;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_go(channel);
+ if (ret)
+ printf("MXS NAND: DMA write error\n");
+
+ mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Read a single byte from NAND.
+ */
+static uint8_t mxs_nand_read_byte(struct mtd_info *mtd)
+{
+ uint8_t buf;
+ mxs_nand_read_buf(mtd, &buf, 1);
+ return buf;
+}
+
+/*
+ * Read a page from NAND.
+ */
+static int mxs_nand_ecc_read_page(struct mtd_info *mtd, struct nand_chip *nand,
+ uint8_t *buf, int page)
+{
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mxs_dma_desc *d;
+ uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+ uint32_t corrected = 0, failed = 0;
+ uint8_t *status;
+ int i, ret;
+
+ /* Compile the DMA descriptor - wait for ready. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+ MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END |
+ (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+ d->cmd.address = 0;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Compile the DMA descriptor - enable the BCH block and read. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+ MXS_DMA_DESC_WAIT4END | (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+ d->cmd.address = 0;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_READ |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (mtd->writesize + mtd->oobsize);
+ d->cmd.pio_words[1] = 0;
+ d->cmd.pio_words[2] =
+ GPMI_ECCCTRL_ENABLE_ECC |
+ GPMI_ECCCTRL_ECC_CMD_DECODE |
+ GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE;
+ d->cmd.pio_words[3] = mtd->writesize + mtd->oobsize;
+ d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf;
+ d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Compile the DMA descriptor - disable the BCH block. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+ MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END |
+ (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+ d->cmd.address = 0;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA |
+ (mtd->writesize + mtd->oobsize);
+ d->cmd.pio_words[1] = 0;
+ d->cmd.pio_words[2] = 0;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Compile the DMA descriptor - deassert the NAND lock and interrupt. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_DEC_SEM;
+
+ d->cmd.address = 0;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_go(channel);
+ if (ret) {
+ printf("MXS NAND: DMA read error\n");
+ goto rtn;
+ }
+
+ ret = mxs_nand_wait_for_bch_complete();
+ if (ret) {
+ printf("MXS NAND: BCH read timeout\n");
+ goto rtn;
+ }
+
+ /* Read DMA completed, now do the mark swapping. */
+ mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf);
+
+ /* Loop over status bytes, accumulating ECC status. */
+ status = nand_info->oob_buf + mxs_nand_aux_status_offset();
+ for (i = 0; i < mxs_nand_ecc_chunk_cnt(mtd->writesize); i++) {
+ if (status[i] == 0x00)
+ continue;
+
+ if (status[i] == 0xff)
+ continue;
+
+ if (status[i] == 0xfe) {
+ failed++;
+ continue;
+ }
+
+ corrected += status[i];
+ }
+
+ /* Propagate ECC status to the owning MTD. */
+ mtd->ecc_stats.failed += failed;
+ mtd->ecc_stats.corrected += corrected;
+
+ /*
+ * It's time to deliver the OOB bytes. See mxs_nand_ecc_read_oob() for
+ * details about our policy for delivering the OOB.
+ *
+ * We fill the caller's buffer with set bits, and then copy the block
+ * mark to the caller's buffer. Note that, if block mark swapping was
+ * necessary, it has already been done, so we can rely on the first
+ * byte of the auxiliary buffer to contain the block mark.
+ */
+ memset(nand->oob_poi, 0xff, mtd->oobsize);
+
+ nand->oob_poi[0] = nand_info->oob_buf[0];
+
+ memcpy(buf, nand_info->data_buf, mtd->writesize);
+
+rtn:
+ mxs_nand_return_dma_descs(nand_info);
+
+ return ret;
+}
+
+/*
+ * Write a page to NAND.
+ */
+static void mxs_nand_ecc_write_page(struct mtd_info *mtd,
+ struct nand_chip *nand, const uint8_t *buf)
+{
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mxs_dma_desc *d;
+ uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+ int ret;
+
+ memcpy(nand_info->data_buf, buf, mtd->writesize);
+ memcpy(nand_info->oob_buf, nand->oob_poi, mtd->oobsize);
+
+ /* Handle block mark swapping. */
+ mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf);
+
+ /* Compile the DMA descriptor - write data. */
+ d = mxs_nand_get_dma_desc(nand_info);
+ d->cmd.data =
+ MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+ MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+ (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+ d->cmd.address = 0;
+
+ d->cmd.pio_words[0] =
+ GPMI_CTRL0_COMMAND_MODE_WRITE |
+ GPMI_CTRL0_WORD_LENGTH |
+ (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+ GPMI_CTRL0_ADDRESS_NAND_DATA;
+ d->cmd.pio_words[1] = 0;
+ d->cmd.pio_words[2] =
+ GPMI_ECCCTRL_ENABLE_ECC |
+ GPMI_ECCCTRL_ECC_CMD_ENCODE |
+ GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE;
+ d->cmd.pio_words[3] = (mtd->writesize + mtd->oobsize);
+ d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf;
+ d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf;
+
+ mxs_dma_desc_append(channel, d);
+
+ /* Execute the DMA chain. */
+ ret = mxs_dma_go(channel);
+ if (ret) {
+ printf("MXS NAND: DMA write error\n");
+ goto rtn;
+ }
+
+ ret = mxs_nand_wait_for_bch_complete();
+ if (ret) {
+ printf("MXS NAND: BCH write timeout\n");
+ goto rtn;
+ }
+
+rtn:
+ mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Read OOB from NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct mxs_nand_info *nand_info = chip->priv;
+ int ret;
+
+ if (ops->mode == MTD_OOB_RAW)
+ nand_info->raw_oob_mode = 1;
+ else
+ nand_info->raw_oob_mode = 0;
+
+ ret = nand_info->hooked_read_oob(mtd, from, ops);
+
+ nand_info->raw_oob_mode = 0;
+
+ return ret;
+}
+
+/*
+ * Write OOB to NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct mxs_nand_info *nand_info = chip->priv;
+ int ret;
+
+ if (ops->mode == MTD_OOB_RAW)
+ nand_info->raw_oob_mode = 1;
+ else
+ nand_info->raw_oob_mode = 0;
+
+ ret = nand_info->hooked_write_oob(mtd, to, ops);
+
+ nand_info->raw_oob_mode = 0;
+
+ return ret;
+}
+
+/*
+ * Mark a block bad in NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct nand_chip *chip = mtd->priv;
+ struct mxs_nand_info *nand_info = chip->priv;
+ int ret;
+
+ nand_info->marking_block_bad = 1;
+
+ ret = nand_info->hooked_block_markbad(mtd, ofs);
+
+ nand_info->marking_block_bad = 0;
+
+ return ret;
+}
+
+/*
+ * There are several places in this driver where we have to handle the OOB and
+ * block marks. This is the function where things are the most complicated, so
+ * this is where we try to explain it all. All the other places refer back to
+ * here.
+ *
+ * These are the rules, in order of decreasing importance:
+ *
+ * 1) Nothing the caller does can be allowed to imperil the block mark, so all
+ * write operations take measures to protect it.
+ *
+ * 2) In read operations, the first byte of the OOB we return must reflect the
+ * true state of the block mark, no matter where that block mark appears in
+ * the physical page.
+ *
+ * 3) ECC-based read operations return an OOB full of set bits (since we never
+ * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads
+ * return).
+ *
+ * 4) "Raw" read operations return a direct view of the physical bytes in the
+ * page, using the conventional definition of which bytes are data and which
+ * are OOB. This gives the caller a way to see the actual, physical bytes
+ * in the page, without the distortions applied by our ECC engine.
+ *
+ * What we do for this specific read operation depends on whether we're doing
+ * "raw" read, or an ECC-based read.
+ *
+ * It turns out that knowing whether we want an "ECC-based" or "raw" read is not
+ * easy. When reading a page, for example, the NAND Flash MTD code calls our
+ * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an
+ * ECC-based or raw view of the page is implicit in which function it calls
+ * (there is a similar pair of ECC-based/raw functions for writing).
+ *
+ * Since MTD assumes the OOB is not covered by ECC, there is no pair of
+ * ECC-based/raw functions for reading or or writing the OOB. The fact that the
+ * caller wants an ECC-based or raw view of the page is not propagated down to
+ * this driver.
+ *
+ * Since our OOB *is* covered by ECC, we need this information. So, we hook the
+ * ecc.read_oob and ecc.write_oob function pointers in the owning
+ * struct mtd_info with our own functions. These hook functions set the
+ * raw_oob_mode field so that, when control finally arrives here, we'll know
+ * what to do.
+ */
+static int mxs_nand_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand,
+ int page, int cmd)
+{
+ struct mxs_nand_info *nand_info = nand->priv;
+
+ /*
+ * First, fill in the OOB buffer. If we're doing a raw read, we need to
+ * get the bytes from the physical page. If we're not doing a raw read,
+ * we need to fill the buffer with set bits.
+ */
+ if (nand_info->raw_oob_mode) {
+ /*
+ * If control arrives here, we're doing a "raw" read. Send the
+ * command to read the conventional OOB and read it.
+ */
+ nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+ nand->read_buf(mtd, nand->oob_poi, mtd->oobsize);
+ } else {
+ /*
+ * If control arrives here, we're not doing a "raw" read. Fill
+ * the OOB buffer with set bits and correct the block mark.
+ */
+ memset(nand->oob_poi, 0xff, mtd->oobsize);
+
+ nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+ mxs_nand_read_buf(mtd, nand->oob_poi, 1);
+ }
+
+ return 0;
+
+}
+
+/*
+ * Write OOB data to NAND.
+ */
+static int mxs_nand_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
+ int page)
+{
+ struct mxs_nand_info *nand_info = nand->priv;
+ uint8_t block_mark = 0;
+
+ /*
+ * There are fundamental incompatibilities between the i.MX GPMI NFC and
+ * the NAND Flash MTD model that make it essentially impossible to write
+ * the out-of-band bytes.
+ *
+ * We permit *ONE* exception. If the *intent* of writing the OOB is to
+ * mark a block bad, we can do that.
+ */
+
+ if (!nand_info->marking_block_bad) {
+ printf("NXS NAND: Writing OOB isn't supported\n");
+ return -EIO;
+ }
+
+ /* Write the block mark. */
+ nand->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
+ nand->write_buf(mtd, &block_mark, 1);
+ nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+ /* Check if it worked. */
+ if (nand->waitfunc(mtd, nand) & NAND_STATUS_FAIL)
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * Claims all blocks are good.
+ *
+ * In principle, this function is *only* called when the NAND Flash MTD system
+ * isn't allowed to keep an in-memory bad block table, so it is forced to ask
+ * the driver for bad block information.
+ *
+ * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so
+ * this function is *only* called when we take it away.
+ *
+ * Thus, this function is only called when we want *all* blocks to look good,
+ * so it *always* return success.
+ */
+static int mxs_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+ return 0;
+}
+
+/*
+ * Nominally, the purpose of this function is to look for or create the bad
+ * block table. In fact, since the we call this function at the very end of
+ * the initialization process started by nand_scan(), and we doesn't have a
+ * more formal mechanism, we "hook" this function to continue init process.
+ *
+ * At this point, the physical NAND Flash chips have been identified and
+ * counted, so we know the physical geometry. This enables us to make some
+ * important configuration decisions.
+ *
+ * The return value of this function propogates directly back to this driver's
+ * call to nand_scan(). Anything other than zero will cause this driver to
+ * tear everything down and declare failure.
+ */
+static int mxs_nand_scan_bbt(struct mtd_info *mtd)
+{
+ struct nand_chip *nand = mtd->priv;
+ struct mxs_nand_info *nand_info = nand->priv;
+ struct mx28_bch_regs *bch_regs = (struct mx28_bch_regs *)MXS_BCH_BASE;
+ uint32_t tmp;
+
+ /* Configure BCH and set NFC geometry */
+ mx28_reset_block(&bch_regs->hw_bch_ctrl_reg);
+
+ /* Configure layout 0 */
+ tmp = (mxs_nand_ecc_chunk_cnt(mtd->writesize) - 1)
+ << BCH_FLASHLAYOUT0_NBLOCKS_OFFSET;
+ tmp |= MXS_NAND_METADATA_SIZE << BCH_FLASHLAYOUT0_META_SIZE_OFFSET;
+ tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+ << BCH_FLASHLAYOUT0_ECC0_OFFSET;
+ tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+ writel(tmp, &bch_regs->hw_bch_flash0layout0);
+
+ tmp = (mtd->writesize + mtd->oobsize)
+ << BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET;
+ tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+ << BCH_FLASHLAYOUT1_ECCN_OFFSET;
+ tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+ writel(tmp, &bch_regs->hw_bch_flash0layout1);
+
+ /* Set *all* chip selects to use layout 0 */
+ writel(0, &bch_regs->hw_bch_layoutselect);
+
+ /* Enable BCH complete interrupt */
+ writel(BCH_CTRL_COMPLETE_IRQ_EN, &bch_regs->hw_bch_ctrl_set);
+
+ /* Hook some operations at the MTD level. */
+ if (mtd->read_oob != mxs_nand_hook_read_oob) {
+ nand_info->hooked_read_oob = mtd->read_oob;
+ mtd->read_oob = mxs_nand_hook_read_oob;
+ }
+
+ if (mtd->write_oob != mxs_nand_hook_write_oob) {
+ nand_info->hooked_write_oob = mtd->write_oob;
+ mtd->write_oob = mxs_nand_hook_write_oob;
+ }
+
+ if (mtd->block_markbad != mxs_nand_hook_block_markbad) {
+ nand_info->hooked_block_markbad = mtd->block_markbad;
+ mtd->block_markbad = mxs_nand_hook_block_markbad;
+ }
+
+ /* We use the reference implementation for bad block management. */
+ return nand_default_bbt(mtd);
+}
+
+/*
+ * Allocate DMA buffers
+ */
+int mxs_nand_alloc_buffers(struct mxs_nand_info *nand_info)
+{
+ uint8_t *buf;
+ const int size = NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE;
+
+ /* DMA buffers */
+ buf = memalign(MXS_DMA_ALIGNMENT, size);
+ if (!buf) {
+ printf("MXS NAND: Error allocating DMA buffers\n");
+ return -ENOMEM;
+ }
+
+ memset(buf, 0, size);
+
+ nand_info->data_buf = buf;
+ nand_info->oob_buf = buf + NAND_MAX_PAGESIZE;
+
+ /* Command buffers */
+ nand_info->cmd_buf = memalign(MXS_DMA_ALIGNMENT,
+ MXS_NAND_COMMAND_BUFFER_SIZE);
+ if (!nand_info->cmd_buf) {
+ free(buf);
+ printf("MXS NAND: Error allocating command buffers\n");
+ return -ENOMEM;
+ }
+ memset(nand_info->cmd_buf, 0, MXS_NAND_COMMAND_BUFFER_SIZE);
+ nand_info->cmd_queue_len = 0;
+
+ return 0;
+}
+
+/*
+ * Initializes the NFC hardware.
+ */
+int mxs_nand_init(struct mxs_nand_info *info)
+{
+ struct mx28_gpmi_regs *gpmi_regs =
+ (struct mx28_gpmi_regs *)MXS_GPMI_BASE;
+ int i = 0;
+
+ info->desc = malloc(sizeof(struct mxs_dma_desc *) *
+ MXS_NAND_DMA_DESCRIPTOR_COUNT);
+ if (!info->desc)
+ goto err1;
+
+ /* Allocate the DMA descriptors. */
+ for (i = 0; i < MXS_NAND_DMA_DESCRIPTOR_COUNT; i++) {
+ info->desc[i] = mxs_dma_desc_alloc();
+ if (!info->desc[i])
+ goto err2;
+ }
+
+ /* Init the DMA controller. */
+ mxs_dma_init();
+
+ /* Reset the GPMI block. */
+ mx28_reset_block(&gpmi_regs->hw_gpmi_ctrl0_reg);
+
+ /*
+ * Choose NAND mode, set IRQ polarity, disable write protection and
+ * select BCH ECC.
+ */
+ clrsetbits_le32(&gpmi_regs->hw_gpmi_ctrl1,
+ GPMI_CTRL1_GPMI_MODE,
+ GPMI_CTRL1_ATA_IRQRDY_POLARITY | GPMI_CTRL1_DEV_RESET |
+ GPMI_CTRL1_BCH_MODE);
+
+ return 0;
+
+err2:
+ free(info->desc);
+err1:
+ for (--i; i >= 0; i--)
+ mxs_dma_desc_free(info->desc[i]);
+ printf("MXS NAND: Unable to allocate DMA descriptors\n");
+ return -ENOMEM;
+}
+
+/*!
+ * This function is called during the driver binding process.
+ *
+ * @param pdev the device structure used to store device specific
+ * information that is used by the suspend, resume and
+ * remove functions
+ *
+ * @return The function always returns 0.
+ */
+int board_nand_init(struct nand_chip *nand)
+{
+ struct mxs_nand_info *nand_info;
+ int err;
+
+ nand_info = malloc(sizeof(struct mxs_nand_info));
+ if (!nand_info) {
+ printf("MXS NAND: Failed to allocate private data\n");
+ return -ENOMEM;
+ }
+ memset(nand_info, 0, sizeof(struct mxs_nand_info));
+
+ err = mxs_nand_alloc_buffers(nand_info);
+ if (err)
+ goto err1;
+
+ err = mxs_nand_init(nand_info);
+ if (err)
+ goto err2;
+
+ memset(&fake_ecc_layout, 0, sizeof(fake_ecc_layout));
+
+ nand->priv = nand_info;
+ nand->options |= NAND_NO_SUBPAGE_WRITE;
+
+ nand->cmd_ctrl = mxs_nand_cmd_ctrl;
+
+ nand->dev_ready = mxs_nand_device_ready;
+ nand->select_chip = mxs_nand_select_chip;
+ nand->block_bad = mxs_nand_block_bad;
+ nand->scan_bbt = mxs_nand_scan_bbt;
+
+ nand->read_byte = mxs_nand_read_byte;
+
+ nand->read_buf = mxs_nand_read_buf;
+ nand->write_buf = mxs_nand_write_buf;
+
+ nand->ecc.read_page = mxs_nand_ecc_read_page;
+ nand->ecc.write_page = mxs_nand_ecc_write_page;
+ nand->ecc.read_oob = mxs_nand_ecc_read_oob;
+ nand->ecc.write_oob = mxs_nand_ecc_write_oob;
+
+ nand->ecc.layout = &fake_ecc_layout;
+ nand->ecc.mode = NAND_ECC_HW;
+ nand->ecc.bytes = 9;
+ nand->ecc.size = 512;
+
+ return 0;
+
+err2:
+ free(nand_info->data_buf);
+ free(nand_info->cmd_buf);
+err1:
+ free(nand_info);
+ return err;
+}
diff --git a/drivers/mtd/onenand/Makefile b/drivers/mtd/onenand/Makefile
index b984bd4..b090d40 100644
--- a/drivers/mtd/onenand/Makefile
+++ b/drivers/mtd/onenand/Makefile
@@ -25,8 +25,12 @@ include $(TOPDIR)/config.mk
LIB := $(obj)libonenand.o
+ifndef CONFIG_SPL_BUILD
COBJS-$(CONFIG_CMD_ONENAND) := onenand_uboot.o onenand_base.o onenand_bbt.o
COBJS-$(CONFIG_SAMSUNG_ONENAND) += samsung.o
+else
+COBJS-y := onenand_spl.o
+endif
COBJS := $(COBJS-y)
SRCS := $(COBJS:.o=.c)
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c
index 24e02c2..06f187f 100644
--- a/drivers/mtd/onenand/onenand_base.c
+++ b/drivers/mtd/onenand/onenand_base.c
@@ -1943,16 +1943,10 @@ static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int
{
struct onenand_chip *this = mtd->priv;
int start, end, block, value, status;
- int wp_status_mask;
start = onenand_block(this, ofs);
end = onenand_block(this, ofs + len);
- if (cmd == ONENAND_CMD_LOCK)
- wp_status_mask = ONENAND_WP_LS;
- else
- wp_status_mask = ONENAND_WP_US;
-
/* Continuous lock scheme */
if (this->options & ONENAND_HAS_CONT_LOCK) {
/* Set start block address */
@@ -2226,19 +2220,21 @@ static const struct onenand_manufacturers onenand_manuf_ids[] = {
static int onenand_check_maf(int manuf)
{
int size = ARRAY_SIZE(onenand_manuf_ids);
- char *name;
int i;
+#ifdef ONENAND_DEBUG
+ char *name;
+#endif
for (i = 0; i < size; i++)
if (manuf == onenand_manuf_ids[i].id)
break;
+#ifdef ONENAND_DEBUG
if (i < size)
name = onenand_manuf_ids[i].name;
else
name = "Unknown";
-#ifdef ONENAND_DEBUG
printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf);
#endif
@@ -2255,7 +2251,7 @@ static int flexonenand_get_boundary(struct mtd_info *mtd)
{
struct onenand_chip *this = mtd->priv;
unsigned int die, bdry;
- int ret, syscfg, locked;
+ int syscfg, locked;
/* Disable ECC */
syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1);
@@ -2266,7 +2262,7 @@ static int flexonenand_get_boundary(struct mtd_info *mtd)
this->wait(mtd, FL_SYNCING);
this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0);
- ret = this->wait(mtd, FL_READING);
+ this->wait(mtd, FL_READING);
bdry = this->read_word(this->base + ONENAND_DATARAM);
if ((bdry >> FLEXONENAND_PI_UNLOCK_SHIFT) == 3)
@@ -2276,7 +2272,7 @@ static int flexonenand_get_boundary(struct mtd_info *mtd)
this->boundary[die] = bdry & FLEXONENAND_PI_MASK;
this->command(mtd, ONENAND_CMD_RESET, 0, 0);
- ret = this->wait(mtd, FL_RESETING);
+ this->wait(mtd, FL_RESETING);
printk(KERN_INFO "Die %d boundary: %d%s\n", die,
this->boundary[die], locked ? "(Locked)" : "(Unlocked)");
diff --git a/drivers/mtd/onenand/onenand_spl.c b/drivers/mtd/onenand/onenand_spl.c
new file mode 100644
index 0000000..50eaa71
--- /dev/null
+++ b/drivers/mtd/onenand/onenand_spl.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * Based on code:
+ * Copyright (C) 2005-2009 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <linux/mtd/onenand_regs.h>
+#include <onenand_uboot.h>
+
+/*
+ * Device geometry:
+ * - 2048b page, 128k erase block.
+ * - 4096b page, 256k erase block.
+ */
+enum onenand_spl_pagesize {
+ PAGE_2K = 2048,
+ PAGE_4K = 4096,
+};
+
+#define ONENAND_PAGES_PER_BLOCK 64
+#define onenand_block_address(block) (block)
+#define onenand_sector_address(page) (page << 2)
+#define onenand_buffer_address() ((1 << 3) << 8)
+#define onenand_bufferram_address(block) (0)
+
+static inline uint16_t onenand_readw(uint32_t addr)
+{
+ return readw(CONFIG_SYS_ONENAND_BASE + addr);
+}
+
+static inline void onenand_writew(uint16_t value, uint32_t addr)
+{
+ writew(value, CONFIG_SYS_ONENAND_BASE + addr);
+}
+
+static enum onenand_spl_pagesize onenand_spl_get_geometry(void)
+{
+ uint32_t dev_id, density;
+
+ if (!onenand_readw(ONENAND_REG_TECHNOLOGY)) {
+ dev_id = onenand_readw(ONENAND_REG_DEVICE_ID);
+ density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT;
+ density &= ONENAND_DEVICE_DENSITY_MASK;
+
+ if (density < ONENAND_DEVICE_DENSITY_4Gb)
+ return PAGE_2K;
+
+ if (dev_id & ONENAND_DEVICE_IS_DDP)
+ return PAGE_2K;
+ }
+
+ return PAGE_4K;
+}
+
+static int onenand_spl_read_page(uint32_t block, uint32_t page, uint32_t *buf,
+ enum onenand_spl_pagesize pagesize)
+{
+ const uint32_t addr = CONFIG_SYS_ONENAND_BASE + ONENAND_DATARAM;
+ uint32_t offset;
+
+ onenand_writew(onenand_block_address(block),
+ ONENAND_REG_START_ADDRESS1);
+
+ onenand_writew(onenand_bufferram_address(block),
+ ONENAND_REG_START_ADDRESS2);
+
+ onenand_writew(onenand_sector_address(page),
+ ONENAND_REG_START_ADDRESS8);
+
+ onenand_writew(onenand_buffer_address(),
+ ONENAND_REG_START_BUFFER);
+
+ onenand_writew(ONENAND_INT_CLEAR, ONENAND_REG_INTERRUPT);
+
+ onenand_writew(ONENAND_CMD_READ, ONENAND_REG_COMMAND);
+
+ while (!(onenand_readw(ONENAND_REG_INTERRUPT) & ONENAND_INT_READ))
+ continue;
+
+ /* Check for invalid block mark */
+ if (page < 2 && (onenand_readw(ONENAND_SPARERAM) != 0xffff))
+ return 1;
+
+ for (offset = 0; offset < pagesize; offset += 4)
+ buf[offset / 4] = readl(addr + offset);
+
+ return 0;
+}
+
+void onenand_spl_load_image(uint32_t offs, uint32_t size, void *dst)
+{
+ uint32_t *addr = (uint32_t *)dst;
+ uint32_t total_pages;
+ uint32_t block;
+ uint32_t page, rpage;
+ enum onenand_spl_pagesize pagesize;
+ int ret;
+
+ pagesize = onenand_spl_get_geometry();
+
+ /*
+ * The page can be either 2k or 4k, avoid using DIV_ROUND_UP to avoid
+ * pulling further unwanted functions into the SPL.
+ */
+ if (pagesize == 2048) {
+ total_pages = DIV_ROUND_UP(size, 2048);
+ page = offs / 2048;
+ } else {
+ total_pages = DIV_ROUND_UP(size, 4096);
+ page = offs / 4096;
+ }
+
+ for (; page <= total_pages; page++) {
+ block = page / ONENAND_PAGES_PER_BLOCK;
+ rpage = page & (ONENAND_PAGES_PER_BLOCK - 1);
+ ret = onenand_spl_read_page(block, rpage, addr, pagesize);
+ if (ret) {
+ total_pages += ONENAND_PAGES_PER_BLOCK;
+ page += ONENAND_PAGES_PER_BLOCK - 1;
+ } else {
+ addr += pagesize / 4;
+ }
+ }
+}
diff --git a/drivers/net/davinci_emac.c b/drivers/net/davinci_emac.c
index fa31159..36c33af 100644
--- a/drivers/net/davinci_emac.c
+++ b/drivers/net/davinci_emac.c
@@ -85,15 +85,17 @@ static int emac_rx_queue_active = 0;
/* Receive packet buffers */
static unsigned char emac_rx_buffers[EMAC_MAX_RX_BUFFERS * (EMAC_MAX_ETHERNET_PKT_SIZE + EMAC_PKT_ALIGN)];
-#define MAX_PHY 3
+#ifndef CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT
+#define CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT 3
+#endif
/* PHY address for a discovered PHY (0xff - not found) */
-static u_int8_t active_phy_addr[MAX_PHY] = { 0xff, 0xff, 0xff };
+static u_int8_t active_phy_addr[CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT];
/* number of PHY found active */
static u_int8_t num_phy;
-phy_t phy[MAX_PHY];
+phy_t phy[CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT];
static int davinci_eth_set_mac_addr(struct eth_device *dev)
{
@@ -160,9 +162,8 @@ static int davinci_eth_phy_detect(void)
int j;
unsigned int count = 0;
- active_phy_addr[0] = 0xff;
- active_phy_addr[1] = 0xff;
- active_phy_addr[2] = 0xff;
+ for (i = 0; i < CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT; i++)
+ active_phy_addr[i] = 0xff;
udelay(1000);
phy_act_state = readl(&adap_mdio->ALIVE);
@@ -175,7 +176,14 @@ static int davinci_eth_phy_detect(void)
for (i = 0, j = 0; i < 32; i++)
if (phy_act_state & (1 << i)) {
count++;
- active_phy_addr[j++] = i;
+ if (count < CONFIG_SYS_DAVINCI_EMAC_PHY_COUNT) {
+ active_phy_addr[j++] = i;
+ } else {
+ printf("%s: to many PHYs detected.\n",
+ __func__);
+ count = 0;
+ break;
+ }
}
num_phy = count;
@@ -752,7 +760,7 @@ int davinci_emac_initialize(void)
if (!ret)
return(0);
else
- printf(" %d ETH PHY detected\n", ret);
+ debug_emac(" %d ETH PHY detected\n", ret);
/* Get PHY ID and initialize phy_ops for a detected PHY */
for (i = 0; i < num_phy; i++) {
diff --git a/drivers/net/fec_mxc.c b/drivers/net/fec_mxc.c
index 0c0c7cd..b05a4c0 100644
--- a/drivers/net/fec_mxc.c
+++ b/drivers/net/fec_mxc.c
@@ -42,6 +42,14 @@ DECLARE_GLOBAL_DATA_PTR;
#define CONFIG_FEC_XCV_TYPE MII100
#endif
+/*
+ * The i.MX28 operates with packets in big endian. We need to swap them before
+ * sending and after receiving.
+ */
+#ifdef CONFIG_MX28
+#define CONFIG_FEC_MXC_SWAP_PACKET
+#endif
+
#undef DEBUG
struct nbuf {
@@ -51,6 +59,32 @@ struct nbuf {
uint8_t head[16]; /**< MAC header(6 + 6 + 2) + 2(aligned) */
};
+#ifdef CONFIG_FEC_MXC_SWAP_PACKET
+static void swap_packet(uint32_t *packet, int length)
+{
+ int i;
+
+ for (i = 0; i < DIV_ROUND_UP(length, 4); i++)
+ packet[i] = __swab32(packet[i]);
+}
+#endif
+
+/*
+ * The i.MX28 has two ethernet interfaces, but they are not equal.
+ * Only the first one can access the MDIO bus.
+ */
+#ifdef CONFIG_MX28
+static inline struct ethernet_regs *fec_miiphy_fec_to_eth(struct fec_priv *fec)
+{
+ return (struct ethernet_regs *)MXS_ENET0_BASE;
+}
+#else
+static inline struct ethernet_regs *fec_miiphy_fec_to_eth(struct fec_priv *fec)
+{
+ return fec->eth;
+}
+#endif
+
/*
* MII-interface related functions
*/
@@ -59,7 +93,7 @@ static int fec_miiphy_read(const char *dev, uint8_t phyAddr, uint8_t regAddr,
{
struct eth_device *edev = eth_get_dev_by_name(dev);
struct fec_priv *fec = (struct fec_priv *)edev->priv;
- struct ethernet_regs *eth = fec->eth;
+ struct ethernet_regs *eth = fec_miiphy_fec_to_eth(fec);
uint32_t reg; /* convenient holder for the PHY register */
uint32_t phy; /* convenient holder for the PHY */
@@ -117,7 +151,7 @@ static int fec_miiphy_write(const char *dev, uint8_t phyAddr, uint8_t regAddr,
{
struct eth_device *edev = eth_get_dev_by_name(dev);
struct fec_priv *fec = (struct fec_priv *)edev->priv;
- struct ethernet_regs *eth = fec->eth;
+ struct ethernet_regs *eth = fec_miiphy_fec_to_eth(fec);
uint32_t reg; /* convenient holder for the PHY register */
uint32_t phy; /* convenient holder for the PHY */
@@ -572,6 +606,9 @@ static int fec_send(struct eth_device *dev, volatile void* packet, int length)
* Note: We are always using the first buffer for transmission,
* the second will be empty and only used to stop the DMA engine
*/
+#ifdef CONFIG_FEC_MXC_SWAP_PACKET
+ swap_packet((uint32_t *)packet, length);
+#endif
writew(length, &fec->tbd_base[fec->tbd_index].data_length);
writel((uint32_t)packet, &fec->tbd_base[fec->tbd_index].data_pointer);
/*
@@ -668,6 +705,9 @@ static int fec_recv(struct eth_device *dev)
/*
* Fill the buffer and pass it to upper layers
*/
+#ifdef CONFIG_FEC_MXC_SWAP_PACKET
+ swap_packet((uint32_t *)frame->data, frame_length);
+#endif
memcpy(buff, frame->data, frame_length);
NetReceive(buff, frame_length);
len = frame_length;
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index a16f590..faf4fcd 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -57,6 +57,7 @@ COBJS-$(CONFIG_RTC_MK48T59) += mk48t59.o
COBJS-$(CONFIG_RTC_MPC5200) += mpc5xxx.o
COBJS-$(CONFIG_RTC_MPC8xx) += mpc8xx.o
COBJS-$(CONFIG_RTC_MV) += mvrtc.o
+COBJS-$(CONFIG_RTC_MXS) += mxsrtc.o
COBJS-$(CONFIG_RTC_PCF8563) += pcf8563.o
COBJS-$(CONFIG_RTC_PL031) += pl031.o
COBJS-$(CONFIG_RTC_PT7C4338) += pt7c4338.o
diff --git a/drivers/rtc/mxsrtc.c b/drivers/rtc/mxsrtc.c
new file mode 100644
index 0000000..5beb1a0
--- /dev/null
+++ b/drivers/rtc/mxsrtc.c
@@ -0,0 +1,86 @@
+/*
+ * Freescale i.MX28 RTC Driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * 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
+ *
+ */
+
+#include <common.h>
+#include <rtc.h>
+#include <asm/io.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+#define MXS_RTC_MAX_TIMEOUT 1000000
+
+/* Set time in seconds since 1970-01-01 */
+int mxs_rtc_set_time(uint32_t secs)
+{
+ struct mx28_rtc_regs *rtc_regs = (struct mx28_rtc_regs *)MXS_RTC_BASE;
+ int ret;
+
+ writel(secs, &rtc_regs->hw_rtc_seconds);
+
+ /*
+ * The 0x80 here means seconds were copied to analog. This information
+ * is taken from the linux kernel driver for the STMP37xx RTC since
+ * documentation doesn't mention it.
+ */
+ ret = mx28_wait_mask_clr(&rtc_regs->hw_rtc_stat_reg,
+ 0x80 << RTC_STAT_STALE_REGS_OFFSET, MXS_RTC_MAX_TIMEOUT);
+
+ if (ret)
+ printf("MXS RTC: Timeout waiting for update\n");
+
+ return ret;
+}
+
+int rtc_get(struct rtc_time *time)
+{
+ struct mx28_rtc_regs *rtc_regs = (struct mx28_rtc_regs *)MXS_RTC_BASE;
+ uint32_t secs;
+
+ secs = readl(&rtc_regs->hw_rtc_seconds);
+ to_tm(secs, time);
+
+ return 0;
+}
+
+int rtc_set(struct rtc_time *time)
+{
+ uint32_t secs;
+
+ secs = mktime(time->tm_year, time->tm_mon, time->tm_mday,
+ time->tm_hour, time->tm_min, time->tm_sec);
+
+ return mxs_rtc_set_time(secs);
+}
+
+void rtc_reset(void)
+{
+ struct mx28_rtc_regs *rtc_regs = (struct mx28_rtc_regs *)MXS_RTC_BASE;
+ int ret;
+
+ /* Set time to 1970-01-01 */
+ mxs_rtc_set_time(0);
+
+ /* Reset the RTC block */
+ ret = mx28_reset_block(&rtc_regs->hw_rtc_ctrl_reg);
+ if (ret)
+ printf("MXS RTC: Block reset timeout\n");
+}
diff --git a/drivers/serial/serial_pxa.c b/drivers/serial/serial_pxa.c
index 68469a4..84bb17c 100644
--- a/drivers/serial/serial_pxa.c
+++ b/drivers/serial/serial_pxa.c
@@ -1,4 +1,6 @@
/*
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ *
* (C) Copyright 2002
* Wolfgang Denk, DENX Software Engineering, <wd@denx.de>
*
@@ -32,148 +34,161 @@
#include <watchdog.h>
#include <serial.h>
#include <asm/arch/pxa-regs.h>
+#include <asm/arch/regs-uart.h>
#include <asm/io.h>
DECLARE_GLOBAL_DATA_PTR;
-#define FFUART_INDEX 0
-#define BTUART_INDEX 1
-#define STUART_INDEX 2
+/*
+ * The numbering scheme differs here for PXA25x, PXA27x and PXA3xx so we can
+ * easily handle enabling of clock.
+ */
+#ifdef CONFIG_CPU_MONAHANS
+#define UART_CLK_BASE CKENA_21_BTUART
+#define UART_CLK_REG CKENA
+#define BTUART_INDEX 0
+#define FFUART_INDEX 1
+#define STUART_INDEX 2
+#elif CONFIG_PXA250
+#define UART_CLK_BASE (1 << 4) /* HWUART */
+#define UART_CLK_REG CKEN
+#define HWUART_INDEX 0
+#define STUART_INDEX 1
+#define FFUART_INDEX 2
+#define BTUART_INDEX 3
+#else /* PXA27x */
+#define UART_CLK_BASE CKEN5_STUART
+#define UART_CLK_REG CKEN
+#define STUART_INDEX 0
+#define FFUART_INDEX 1
+#define BTUART_INDEX 2
+#endif
+
+/*
+ * Only PXA250 has HWUART, to avoid poluting the code with more macros,
+ * artificially introduce this.
+ */
+#ifndef CONFIG_PXA250
+#define HWUART_INDEX 0xff
+#endif
#ifndef CONFIG_SERIAL_MULTI
-#if defined (CONFIG_FFUART)
+#if defined(CONFIG_FFUART)
#define UART_INDEX FFUART_INDEX
-#elif defined (CONFIG_BTUART)
+#elif defined(CONFIG_BTUART)
#define UART_INDEX BTUART_INDEX
-#elif defined (CONFIG_STUART)
+#elif defined(CONFIG_STUART)
#define UART_INDEX STUART_INDEX
+#elif defined(CONFIG_HWUART)
+#define UART_INDEX HWUART_INDEX
#else
-#error "Bad: you didn't configure serial ..."
+#error "Please select CONFIG_(FF|BT|ST|HW)UART in board config file."
#endif
#endif
-void pxa_setbrg_dev (unsigned int uart_index)
+uint32_t pxa_uart_get_baud_divider(void)
{
- unsigned int quot = 0;
-
if (gd->baudrate == 1200)
- quot = 768;
+ return 768;
else if (gd->baudrate == 9600)
- quot = 96;
+ return 96;
else if (gd->baudrate == 19200)
- quot = 48;
+ return 48;
else if (gd->baudrate == 38400)
- quot = 24;
+ return 24;
else if (gd->baudrate == 57600)
- quot = 16;
+ return 16;
else if (gd->baudrate == 115200)
- quot = 8;
- else
- hang ();
+ return 8;
+ else /* Unsupported baudrate */
+ return 0;
+}
+struct pxa_uart_regs *pxa_uart_index_to_regs(uint32_t uart_index)
+{
switch (uart_index) {
- case FFUART_INDEX:
-#ifdef CONFIG_CPU_MONAHANS
- writel(readl(CKENA) | CKENA_22_FFUART, CKENA);
-#else
- writel(readl(CKEN) | CKEN6_FFUART, CKEN);
-#endif /* CONFIG_CPU_MONAHANS */
-
- writel(0, FFIER); /* Disable for now */
- writel(0, FFFCR); /* No fifos enabled */
+ case FFUART_INDEX: return (struct pxa_uart_regs *)FFUART_BASE;
+ case BTUART_INDEX: return (struct pxa_uart_regs *)BTUART_BASE;
+ case STUART_INDEX: return (struct pxa_uart_regs *)STUART_BASE;
+ case HWUART_INDEX: return (struct pxa_uart_regs *)HWUART_BASE;
+ default:
+ return NULL;
+ }
+}
- /* set baud rate */
- writel(LCR_WLS0 | LCR_WLS1 | LCR_DLAB, FFLCR);
- writel(quot & 0xff, FFDLL);
- writel(quot >> 8, FFDLH);
- writel(LCR_WLS0 | LCR_WLS1, FFLCR);
+void pxa_uart_toggle_clock(uint32_t uart_index, int enable)
+{
+ uint32_t clk_reg, clk_offset, reg;
- writel(IER_UUE, FFIER); /* Enable FFUART */
- break;
+ clk_reg = UART_CLK_REG;
+ clk_offset = UART_CLK_BASE << uart_index;
- case BTUART_INDEX:
-#ifdef CONFIG_CPU_MONAHANS
- writel(readl(CKENA) | CKENA_21_BTUART, CKENA);
-#else
- writel(readl(CKEN) | CKEN7_BTUART, CKEN);
-#endif /* CONFIG_CPU_MONAHANS */
+ reg = readl(clk_reg);
- writel(0, BTIER);
- writel(0, BTFCR);
+ if (enable)
+ reg |= clk_offset;
+ else
+ reg &= ~clk_offset;
- /* set baud rate */
- writel(LCR_DLAB, BTLCR);
- writel(quot & 0xff, BTDLL);
- writel(quot >> 8, BTDLH);
- writel(LCR_WLS0 | LCR_WLS1, BTLCR);
+ writel(reg, clk_reg);
+}
- writel(IER_UUE, BTIER); /* Enable BFUART */
+/*
+ * Enable clock and set baud rate, parity etc.
+ */
+void pxa_setbrg_dev(uint32_t uart_index)
+{
+ uint32_t divider = 0;
+ struct pxa_uart_regs *uart_regs;
- break;
+ divider = pxa_uart_get_baud_divider();
+ if (!divider)
+ hang();
- case STUART_INDEX:
-#ifdef CONFIG_CPU_MONAHANS
- writel(readl(CKENA) | CKENA_23_STUART, CKENA);
-#else
- writel(readl(CKEN) | CKEN5_STUART, CKEN);
-#endif /* CONFIG_CPU_MONAHANS */
+ uart_regs = pxa_uart_index_to_regs(uart_index);
+ if (!uart_regs)
+ hang();
- writel(0, STIER);
- writel(0, STFCR);
+ pxa_uart_toggle_clock(uart_index, 1);
- /* set baud rate */
- writel(LCR_DLAB, STLCR);
- writel(quot & 0xff, STDLL);
- writel(quot >> 8, STDLH);
- writel(LCR_WLS0 | LCR_WLS1, STLCR);
+ /* Disable interrupts and FIFOs */
+ writel(0, &uart_regs->ier);
+ writel(0, &uart_regs->fcr);
- writel(IER_UUE, STIER); /* Enable STUART */
- break;
+ /* Set baud rate */
+ writel(LCR_WLS0 | LCR_WLS1 | LCR_DLAB, &uart_regs->lcr);
+ writel(divider & 0xff, &uart_regs->dll);
+ writel(divider >> 8, &uart_regs->dlh);
+ writel(LCR_WLS0 | LCR_WLS1, &uart_regs->lcr);
- default:
- hang();
- }
+ /* Enable UART */
+ writel(IER_UUE, &uart_regs->ier);
}
-
/*
* Initialise the serial port with the given baudrate. The settings
* are always 8 data bits, no parity, 1 stop bit, no start bits.
- *
*/
-int pxa_init_dev (unsigned int uart_index)
+int pxa_init_dev(unsigned int uart_index)
{
pxa_setbrg_dev (uart_index);
-
- return (0);
+ return 0;
}
-
/*
* Output a single byte to the serial port.
*/
-void pxa_putc_dev (unsigned int uart_index,const char c)
+void pxa_putc_dev(unsigned int uart_index, const char c)
{
- switch (uart_index) {
- case FFUART_INDEX:
- /* wait for room in the tx FIFO on FFUART */
- while ((readl(FFLSR) & LSR_TEMT) == 0)
- WATCHDOG_RESET (); /* Reset HW Watchdog, if needed */
- writel(c, FFTHR);
- break;
-
- case BTUART_INDEX:
- while ((readl(BTLSR) & LSR_TEMT) == 0)
- WATCHDOG_RESET (); /* Reset HW Watchdog, if needed */
- writel(c, BTTHR);
- break;
-
- case STUART_INDEX:
- while ((readl(STLSR) & LSR_TEMT) == 0)
- WATCHDOG_RESET (); /* Reset HW Watchdog, if needed */
- writel(c, STTHR);
- break;
- }
+ struct pxa_uart_regs *uart_regs;
+
+ uart_regs = pxa_uart_index_to_regs(uart_index);
+ if (!uart_regs)
+ hang();
+
+ while (!(readl(&uart_regs->lsr) & LSR_TEMT))
+ WATCHDOG_RESET();
+ writel(c, &uart_regs->thr);
/* If \n, also do \r */
if (c == '\n')
@@ -185,17 +200,15 @@ void pxa_putc_dev (unsigned int uart_index,const char c)
* otherwise. When the function is succesfull, the character read is
* written into its argument c.
*/
-int pxa_tstc_dev (unsigned int uart_index)
+int pxa_tstc_dev(unsigned int uart_index)
{
- switch (uart_index) {
- case FFUART_INDEX:
- return readl(FFLSR) & LSR_DR;
- case BTUART_INDEX:
- return readl(BTLSR) & LSR_DR;
- case STUART_INDEX:
- return readl(STLSR) & LSR_DR;
- }
- return -1;
+ struct pxa_uart_regs *uart_regs;
+
+ uart_regs = pxa_uart_index_to_regs(uart_index);
+ if (!uart_regs)
+ return -1;
+
+ return readl(&uart_regs->lsr) & LSR_DR;
}
/*
@@ -203,187 +216,86 @@ int pxa_tstc_dev (unsigned int uart_index)
* otherwise. When the function is succesfull, the character read is
* written into its argument c.
*/
-int pxa_getc_dev (unsigned int uart_index)
+int pxa_getc_dev(unsigned int uart_index)
{
- switch (uart_index) {
- case FFUART_INDEX:
- while (!(readl(FFLSR) & LSR_DR))
- /* Reset HW Watchdog, if needed */
- WATCHDOG_RESET();
- return (char) readl(FFRBR) & 0xff;
-
- case BTUART_INDEX:
- while (!(readl(BTLSR) & LSR_DR))
- /* Reset HW Watchdog, if needed */
- WATCHDOG_RESET();
- return (char) readl(BTRBR) & 0xff;
- case STUART_INDEX:
- while (!(readl(STLSR) & LSR_DR))
- /* Reset HW Watchdog, if needed */
- WATCHDOG_RESET();
- return (char) readl(STRBR) & 0xff;
- }
- return -1;
-}
+ struct pxa_uart_regs *uart_regs;
-void
-pxa_puts_dev (unsigned int uart_index,const char *s)
-{
- while (*s) {
- pxa_putc_dev (uart_index,*s++);
- }
-}
+ uart_regs = pxa_uart_index_to_regs(uart_index);
+ if (!uart_regs)
+ return -1;
-#if defined (CONFIG_FFUART)
-static int ffuart_init(void)
-{
- return pxa_init_dev(FFUART_INDEX);
+ while (!(readl(&uart_regs->lsr) & LSR_DR))
+ WATCHDOG_RESET();
+ return readl(&uart_regs->rbr) & 0xff;
}
-static void ffuart_setbrg(void)
+void pxa_puts_dev(unsigned int uart_index, const char *s)
{
- return pxa_setbrg_dev(FFUART_INDEX);
+ while (*s)
+ pxa_putc_dev(uart_index, *s++);
}
-static void ffuart_putc(const char c)
-{
- return pxa_putc_dev(FFUART_INDEX,c);
-}
-
-static void ffuart_puts(const char *s)
-{
- return pxa_puts_dev(FFUART_INDEX,s);
-}
-
-static int ffuart_getc(void)
-{
- return pxa_getc_dev(FFUART_INDEX);
-}
-
-static int ffuart_tstc(void)
-{
- return pxa_tstc_dev(FFUART_INDEX);
-}
-
-struct serial_device serial_ffuart_device =
-{
- "serial_ffuart",
- ffuart_init,
- NULL,
- ffuart_setbrg,
- ffuart_getc,
- ffuart_tstc,
- ffuart_putc,
- ffuart_puts,
-};
+#define pxa_uart(uart, UART) \
+ int uart##_init(void) \
+ { \
+ return pxa_init_dev(UART##_INDEX); \
+ } \
+ \
+ void uart##_setbrg(void) \
+ { \
+ return pxa_setbrg_dev(UART##_INDEX); \
+ } \
+ \
+ void uart##_putc(const char c) \
+ { \
+ return pxa_putc_dev(UART##_INDEX, c); \
+ } \
+ \
+ void uart##_puts(const char *s) \
+ { \
+ return pxa_puts_dev(UART##_INDEX, s); \
+ } \
+ \
+ int uart##_getc(void) \
+ { \
+ return pxa_getc_dev(UART##_INDEX); \
+ } \
+ \
+ int uart##_tstc(void) \
+ { \
+ return pxa_tstc_dev(UART##_INDEX); \
+ } \
+
+#define pxa_uart_desc(uart) \
+ struct serial_device serial_##uart##_device = \
+ { \
+ "serial_"#uart, \
+ uart##_init, \
+ NULL, \
+ uart##_setbrg, \
+ uart##_getc, \
+ uart##_tstc, \
+ uart##_putc, \
+ uart##_puts, \
+ };
+
+#define pxa_uart_multi(uart, UART) \
+ pxa_uart(uart, UART) \
+ pxa_uart_desc(uart)
+
+#if defined(CONFIG_HWUART)
+ pxa_uart_multi(hwuart, HWUART)
#endif
-
-#if defined (CONFIG_BTUART)
-static int btuart_init(void)
-{
- return pxa_init_dev(BTUART_INDEX);
-}
-
-static void btuart_setbrg(void)
-{
- return pxa_setbrg_dev(BTUART_INDEX);
-}
-
-static void btuart_putc(const char c)
-{
- return pxa_putc_dev(BTUART_INDEX,c);
-}
-
-static void btuart_puts(const char *s)
-{
- return pxa_puts_dev(BTUART_INDEX,s);
-}
-
-static int btuart_getc(void)
-{
- return pxa_getc_dev(BTUART_INDEX);
-}
-
-static int btuart_tstc(void)
-{
- return pxa_tstc_dev(BTUART_INDEX);
-}
-
-struct serial_device serial_btuart_device =
-{
- "serial_btuart",
- btuart_init,
- NULL,
- btuart_setbrg,
- btuart_getc,
- btuart_tstc,
- btuart_putc,
- btuart_puts,
-};
+#if defined(CONFIG_STUART)
+ pxa_uart_multi(stuart, STUART)
#endif
-
-#if defined (CONFIG_STUART)
-static int stuart_init(void)
-{
- return pxa_init_dev(STUART_INDEX);
-}
-
-static void stuart_setbrg(void)
-{
- return pxa_setbrg_dev(STUART_INDEX);
-}
-
-static void stuart_putc(const char c)
-{
- return pxa_putc_dev(STUART_INDEX,c);
-}
-
-static void stuart_puts(const char *s)
-{
- return pxa_puts_dev(STUART_INDEX,s);
-}
-
-static int stuart_getc(void)
-{
- return pxa_getc_dev(STUART_INDEX);
-}
-
-static int stuart_tstc(void)
-{
- return pxa_tstc_dev(STUART_INDEX);
-}
-
-struct serial_device serial_stuart_device =
-{
- "serial_stuart",
- stuart_init,
- NULL,
- stuart_setbrg,
- stuart_getc,
- stuart_tstc,
- stuart_putc,
- stuart_puts,
-};
+#if defined(CONFIG_FFUART)
+ pxa_uart_multi(ffuart, FFUART)
+#endif
+#if defined(CONFIG_BTUART)
+ pxa_uart_multi(btuart, BTUART)
#endif
-
-#ifndef CONFIG_SERIAL_MULTI
-inline int serial_init(void) {
- return (pxa_init_dev(UART_INDEX));
-}
-void serial_setbrg(void) {
- pxa_setbrg_dev(UART_INDEX);
-}
-int serial_getc(void) {
- return(pxa_getc_dev(UART_INDEX));
-}
-int serial_tstc(void) {
- return(pxa_tstc_dev(UART_INDEX));
-}
-void serial_putc(const char c) {
- pxa_putc_dev(UART_INDEX,c);
-}
-void serial_puts(const char *s) {
- pxa_puts_dev(UART_INDEX,s);
-}
-#endif /* CONFIG_SERIAL_MULTI */
+#ifndef CONFIG_SERIAL_MULTI
+ pxa_uart(serial, UART)
+#endif
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 84ad6fa..6f389f0 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -37,6 +37,7 @@ COBJS-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o
COBJS-$(CONFIG_MPC52XX_SPI) += mpc52xx_spi.o
COBJS-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o
COBJS-$(CONFIG_MXC_SPI) += mxc_spi.o
+COBJS-$(CONFIG_MXS_SPI) += mxs_spi.o
COBJS-$(CONFIG_OC_TINY_SPI) += oc_tiny_spi.o
COBJS-$(CONFIG_OMAP3_SPI) += omap3_spi.o
COBJS-$(CONFIG_SOFT_SPI) += soft_spi.o
diff --git a/drivers/spi/mxs_spi.c b/drivers/spi/mxs_spi.c
new file mode 100644
index 0000000..4c27fef
--- /dev/null
+++ b/drivers/spi/mxs_spi.c
@@ -0,0 +1,186 @@
+/*
+ * Freescale i.MX28 SPI driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * 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
+ *
+ * NOTE: This driver only supports the SPI-controller chipselects,
+ * GPIO driven chipselects are not supported.
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <spi.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+#define MXS_SPI_MAX_TIMEOUT 1000000
+#define MXS_SPI_PORT_OFFSET 0x2000
+
+struct mxs_spi_slave {
+ struct spi_slave slave;
+ uint32_t max_khz;
+ uint32_t mode;
+ struct mx28_ssp_regs *regs;
+};
+
+static inline struct mxs_spi_slave *to_mxs_slave(struct spi_slave *slave)
+{
+ return container_of(slave, struct mxs_spi_slave, slave);
+}
+
+void spi_init(void)
+{
+}
+
+struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
+ unsigned int max_hz, unsigned int mode)
+{
+ struct mxs_spi_slave *mxs_slave;
+ uint32_t addr;
+
+ if (bus > 3) {
+ printf("MXS SPI: Max bus number is 3\n");
+ return NULL;
+ }
+
+ mxs_slave = malloc(sizeof(struct mxs_spi_slave));
+ if (!mxs_slave)
+ return NULL;
+
+ addr = MXS_SSP0_BASE + (bus * MXS_SPI_PORT_OFFSET);
+
+ mxs_slave->slave.bus = bus;
+ mxs_slave->slave.cs = cs;
+ mxs_slave->max_khz = max_hz / 1000;
+ mxs_slave->mode = mode;
+ mxs_slave->regs = (struct mx28_ssp_regs *)addr;
+
+ return &mxs_slave->slave;
+}
+
+void spi_free_slave(struct spi_slave *slave)
+{
+ struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave);
+ free(mxs_slave);
+}
+
+int spi_claim_bus(struct spi_slave *slave)
+{
+ struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave);
+ struct mx28_ssp_regs *ssp_regs = mxs_slave->regs;
+ uint32_t reg = 0;
+
+ mx28_reset_block(&ssp_regs->hw_ssp_ctrl0_reg);
+
+ writel(SSP_CTRL0_BUS_WIDTH_ONE_BIT, &ssp_regs->hw_ssp_ctrl0);
+
+ reg = SSP_CTRL1_SSP_MODE_SPI | SSP_CTRL1_WORD_LENGTH_EIGHT_BITS;
+ reg |= (mxs_slave->mode & SPI_CPOL) ? SSP_CTRL1_POLARITY : 0;
+ reg |= (mxs_slave->mode & SPI_CPHA) ? SSP_CTRL1_PHASE : 0;
+ writel(reg, &ssp_regs->hw_ssp_ctrl1);
+
+ writel(0, &ssp_regs->hw_ssp_cmd0);
+
+ mx28_set_ssp_busclock(slave->bus, mxs_slave->max_khz);
+
+ return 0;
+}
+
+void spi_release_bus(struct spi_slave *slave)
+{
+}
+
+static void mxs_spi_start_xfer(struct mx28_ssp_regs *ssp_regs)
+{
+ writel(SSP_CTRL0_LOCK_CS, &ssp_regs->hw_ssp_ctrl0_set);
+ writel(SSP_CTRL0_IGNORE_CRC, &ssp_regs->hw_ssp_ctrl0_clr);
+}
+
+static void mxs_spi_end_xfer(struct mx28_ssp_regs *ssp_regs)
+{
+ writel(SSP_CTRL0_LOCK_CS, &ssp_regs->hw_ssp_ctrl0_clr);
+ writel(SSP_CTRL0_IGNORE_CRC, &ssp_regs->hw_ssp_ctrl0_set);
+}
+
+int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
+ const void *dout, void *din, unsigned long flags)
+{
+ struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave);
+ struct mx28_ssp_regs *ssp_regs = mxs_slave->regs;
+ int len = bitlen / 8;
+ const char *tx = dout;
+ char *rx = din;
+
+ if (bitlen == 0)
+ return 0;
+
+ if (!rx && !tx)
+ return 0;
+
+ if (flags & SPI_XFER_BEGIN)
+ mxs_spi_start_xfer(ssp_regs);
+
+ while (len--) {
+ /* We transfer 1 byte */
+ writel(1, &ssp_regs->hw_ssp_xfer_size);
+
+ if ((flags & SPI_XFER_END) && !len)
+ mxs_spi_end_xfer(ssp_regs);
+
+ if (tx)
+ writel(SSP_CTRL0_READ, &ssp_regs->hw_ssp_ctrl0_clr);
+ else
+ writel(SSP_CTRL0_READ, &ssp_regs->hw_ssp_ctrl0_set);
+
+ writel(SSP_CTRL0_RUN, &ssp_regs->hw_ssp_ctrl0_set);
+
+ if (mx28_wait_mask_set(&ssp_regs->hw_ssp_ctrl0_reg,
+ SSP_CTRL0_RUN, MXS_SPI_MAX_TIMEOUT)) {
+ printf("MXS SPI: Timeout waiting for start\n");
+ return -1;
+ }
+
+ if (tx)
+ writel(*tx++, &ssp_regs->hw_ssp_data);
+
+ writel(SSP_CTRL0_DATA_XFER, &ssp_regs->hw_ssp_ctrl0_set);
+
+ if (rx) {
+ if (mx28_wait_mask_clr(&ssp_regs->hw_ssp_status_reg,
+ SSP_STATUS_FIFO_EMPTY, MXS_SPI_MAX_TIMEOUT)) {
+ printf("MXS SPI: Timeout waiting for data\n");
+ return -1;
+ }
+
+ *rx = readl(&ssp_regs->hw_ssp_data);
+ rx++;
+ }
+
+ if (mx28_wait_mask_clr(&ssp_regs->hw_ssp_ctrl0_reg,
+ SSP_CTRL0_RUN, MXS_SPI_MAX_TIMEOUT)) {
+ printf("MXS SPI: Timeout waiting for finish\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 51b2494..09abb75 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -41,6 +41,7 @@ else
COBJS-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o
endif
COBJS-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o
+COBJS-$(CONFIG_USB_EHCI_MXS) += ehci-mxs.o
COBJS-$(CONFIG_USB_EHCI_PPC4XX) += ehci-ppc4xx.o
COBJS-$(CONFIG_USB_EHCI_IXP4XX) += ehci-ixp.o
COBJS-$(CONFIG_USB_EHCI_KIRKWOOD) += ehci-kirkwood.o
diff --git a/drivers/usb/host/ehci-mxs.c b/drivers/usb/host/ehci-mxs.c
new file mode 100644
index 0000000..c795f23
--- /dev/null
+++ b/drivers/usb/host/ehci-mxs.c
@@ -0,0 +1,154 @@
+/*
+ * Freescale i.MX28 USB Host driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * 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
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch/regs-common.h>
+#include <asm/arch/regs-base.h>
+#include <asm/arch/regs-clkctrl.h>
+#include <asm/arch/regs-usb.h>
+#include <asm/arch/regs-usbphy.h>
+
+#include "ehci-core.h"
+#include "ehci.h"
+
+#if (CONFIG_EHCI_MXS_PORT != 0) && (CONFIG_EHCI_MXS_PORT != 1)
+#error "MXS EHCI: Invalid port selected!"
+#endif
+
+#ifndef CONFIG_EHCI_MXS_PORT
+#error "MXS EHCI: Please define correct port using CONFIG_EHCI_MXS_PORT!"
+#endif
+
+static struct ehci_mxs {
+ struct mx28_usb_regs *usb_regs;
+ struct mx28_usbphy_regs *phy_regs;
+} ehci_mxs;
+
+int mxs_ehci_get_port(struct ehci_mxs *mxs_usb, int port)
+{
+ uint32_t usb_base, phy_base;
+ switch (port) {
+ case 0:
+ usb_base = MXS_USBCTRL0_BASE;
+ phy_base = MXS_USBPHY0_BASE;
+ break;
+ case 1:
+ usb_base = MXS_USBCTRL1_BASE;
+ phy_base = MXS_USBPHY1_BASE;
+ break;
+ default:
+ printf("CONFIG_EHCI_MXS_PORT (port = %d)\n", port);
+ return -1;
+ }
+
+ mxs_usb->usb_regs = (struct mx28_usb_regs *)usb_base;
+ mxs_usb->phy_regs = (struct mx28_usbphy_regs *)phy_base;
+ return 0;
+}
+
+/* This DIGCTL register ungates clock to USB */
+#define HW_DIGCTL_CTRL 0x8001c000
+#define HW_DIGCTL_CTRL_USB0_CLKGATE (1 << 2)
+#define HW_DIGCTL_CTRL_USB1_CLKGATE (1 << 16)
+
+int ehci_hcd_init(void)
+{
+
+ int ret;
+ uint32_t usb_base, cap_base;
+ struct mx28_register *digctl_ctrl =
+ (struct mx28_register *)HW_DIGCTL_CTRL;
+ struct mx28_clkctrl_regs *clkctrl_regs =
+ (struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+ ret = mxs_ehci_get_port(&ehci_mxs, CONFIG_EHCI_MXS_PORT);
+ if (ret)
+ return ret;
+
+ /* Reset the PHY block */
+ writel(USBPHY_CTRL_SFTRST, &ehci_mxs.phy_regs->hw_usbphy_ctrl_set);
+ udelay(10);
+ writel(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE,
+ &ehci_mxs.phy_regs->hw_usbphy_ctrl_clr);
+
+ /* Enable USB clock */
+ writel(CLKCTRL_PLL0CTRL0_EN_USB_CLKS | CLKCTRL_PLL0CTRL0_POWER,
+ &clkctrl_regs->hw_clkctrl_pll0ctrl0_set);
+ writel(CLKCTRL_PLL1CTRL0_EN_USB_CLKS | CLKCTRL_PLL1CTRL0_POWER,
+ &clkctrl_regs->hw_clkctrl_pll1ctrl0_set);
+
+ writel(HW_DIGCTL_CTRL_USB0_CLKGATE | HW_DIGCTL_CTRL_USB1_CLKGATE,
+ &digctl_ctrl->reg_clr);
+
+ /* Start USB PHY */
+ writel(0, &ehci_mxs.phy_regs->hw_usbphy_pwd);
+
+ /* Enable UTMI+ Level 2 and Level 3 compatibility */
+ writel(USBPHY_CTRL_ENUTMILEVEL3 | USBPHY_CTRL_ENUTMILEVEL2 | 1,
+ &ehci_mxs.phy_regs->hw_usbphy_ctrl_set);
+
+ usb_base = ((uint32_t)ehci_mxs.usb_regs) + 0x100;
+ hccr = (struct ehci_hccr *)usb_base;
+
+ cap_base = ehci_readl(&hccr->cr_capbase);
+ hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base));
+
+ return 0;
+}
+
+int ehci_hcd_stop(void)
+{
+ int ret;
+ uint32_t tmp;
+ struct mx28_register *digctl_ctrl =
+ (struct mx28_register *)HW_DIGCTL_CTRL;
+ struct mx28_clkctrl_regs *clkctrl_regs =
+ (struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+ ret = mxs_ehci_get_port(&ehci_mxs, CONFIG_EHCI_MXS_PORT);
+ if (ret)
+ return ret;
+
+ /* Stop the USB port */
+ tmp = ehci_readl(&hcor->or_usbcmd);
+ tmp &= ~CMD_RUN;
+ ehci_writel(tmp, &hcor->or_usbcmd);
+
+ /* Disable the PHY */
+ tmp = USBPHY_PWD_RXPWDRX | USBPHY_PWD_RXPWDDIFF |
+ USBPHY_PWD_RXPWD1PT1 | USBPHY_PWD_RXPWDENV |
+ USBPHY_PWD_TXPWDV2I | USBPHY_PWD_TXPWDIBIAS |
+ USBPHY_PWD_TXPWDFS;
+ writel(tmp, &ehci_mxs.phy_regs->hw_usbphy_pwd);
+
+ /* Disable USB clock */
+ writel(CLKCTRL_PLL0CTRL0_EN_USB_CLKS,
+ &clkctrl_regs->hw_clkctrl_pll0ctrl0_clr);
+ writel(CLKCTRL_PLL1CTRL0_EN_USB_CLKS,
+ &clkctrl_regs->hw_clkctrl_pll1ctrl0_clr);
+
+ /* Gate off the USB clock */
+ writel(HW_DIGCTL_CTRL_USB0_CLKGATE | HW_DIGCTL_CTRL_USB1_CLKGATE,
+ &digctl_ctrl->reg_set);
+
+ return 0;
+}