diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/dma/Makefile | 1 | ||||
-rw-r--r-- | drivers/dma/apbh_dma.c | 691 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/mxs_gpio.c | 136 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/mxs_i2c.c | 246 | ||||
-rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/mxsmmc.c | 351 | ||||
-rw-r--r-- | drivers/mmc/omap_hsmmc.c | 26 | ||||
-rw-r--r-- | drivers/mtd/nand/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/nand/mxs_nand.c | 1118 | ||||
-rw-r--r-- | drivers/mtd/onenand/Makefile | 4 | ||||
-rw-r--r-- | drivers/mtd/onenand/onenand_base.c | 18 | ||||
-rw-r--r-- | drivers/mtd/onenand/onenand_spl.c | 146 | ||||
-rw-r--r-- | drivers/net/davinci_emac.c | 24 | ||||
-rw-r--r-- | drivers/net/fec_mxc.c | 44 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/mxsrtc.c | 86 | ||||
-rw-r--r-- | drivers/serial/serial_pxa.c | 464 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/mxs_spi.c | 186 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/host/ehci-mxs.c | 154 |
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) >> 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_set); + else + writel(1 << PAD_PIN(gp), ®->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_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_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; +} |