Merge branch 'for-next/mxs-nand'
Conflicts: arch/arm/mach-mxs/Kconfig arch/arm/mach-mxs/Makefile drivers/Makefile
This commit is contained in:
commit
d04ce5dfe7
|
@ -92,6 +92,14 @@ config MXS_OCOTP_WRITABLE
|
|||
enable writing:
|
||||
ocotp0.permanent_write_enable=1
|
||||
|
||||
config MXS_CMD_BCB
|
||||
depends on NAND_MXS
|
||||
tristate "Nand bcb command"
|
||||
help
|
||||
To be able to boot from NAND the i.MX23/28 need a Boot Control Block
|
||||
in flash. This option enabled the 'bcb' command which can be used to
|
||||
generate this block during runtime.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Board specific settings "
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
obj-y += imx.o iomux-imx.o power.o
|
||||
obj-y += imx.o iomux-imx.o power.o common.o
|
||||
obj-$(CONFIG_DRIVER_VIDEO_STM) += imx_lcd_clk.o
|
||||
obj-$(CONFIG_ARCH_IMX23) += speed-imx23.o clocksource-imx23.o usb-imx23.o soc-imx23.o
|
||||
obj-$(CONFIG_ARCH_IMX28) += speed-imx28.o clocksource-imx28.o usb-imx28.o soc-imx28.o
|
||||
obj-$(CONFIG_MXS_OCOTP) += ocotp.o
|
||||
obj-$(CONFIG_MXS_CMD_BCB) += bcb.o
|
||||
|
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* (C) Copyright 2011 Wolfram Sang, Pengutronix e.K.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Based on a similar function in Karo Electronics TX28-U-Boot (flash.c).
|
||||
* Probably written by Lothar Waßmann (like tx28.c).
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <command.h>
|
||||
#include <environment.h>
|
||||
#include <malloc.h>
|
||||
#include <nand.h>
|
||||
#include <sizes.h>
|
||||
#include <errno.h>
|
||||
#include <io.h>
|
||||
|
||||
#include <mach/imx-regs.h>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
#define FCB_START_BLOCK 0
|
||||
#define NUM_FCB_BLOCKS 1
|
||||
#define MAX_FCB_BLOCKS 32768
|
||||
|
||||
#define GPMI_TIMING0 0x00000070
|
||||
#define GPMI_TIMING0_ADDRESS_SETUP_MASK (0xff << 16)
|
||||
#define GPMI_TIMING0_ADDRESS_SETUP_OFFSET 16
|
||||
#define GPMI_TIMING0_DATA_HOLD_MASK (0xff << 8)
|
||||
#define GPMI_TIMING0_DATA_HOLD_OFFSET 8
|
||||
#define GPMI_TIMING0_DATA_SETUP_MASK 0xff
|
||||
#define GPMI_TIMING0_DATA_SETUP_OFFSET 0
|
||||
|
||||
#define GPMI_TIMING1 0x00000080
|
||||
|
||||
#define BCH_MODE 0x00000020
|
||||
|
||||
#define BCH_FLASH0LAYOUT0 0x00000080
|
||||
#define BCH_FLASHLAYOUT0_NBLOCKS_MASK (0xff << 24)
|
||||
#define BCH_FLASHLAYOUT0_NBLOCKS_OFFSET 24
|
||||
#define BCH_FLASHLAYOUT0_META_SIZE_MASK (0xff << 16)
|
||||
#define BCH_FLASHLAYOUT0_META_SIZE_OFFSET 16
|
||||
#define BCH_FLASHLAYOUT0_ECC0_MASK (0xf << 12)
|
||||
#define BCH_FLASHLAYOUT0_ECC0_OFFSET 12
|
||||
#define BCH_FLASHLAYOUT0_DATA0_SIZE_MASK 0xfff
|
||||
#define BCH_FLASHLAYOUT0_DATA0_SIZE_OFFSET 0
|
||||
|
||||
#define BCH_FLASH0LAYOUT1 0x00000090
|
||||
#define BCH_FLASHLAYOUT1_PAGE_SIZE_MASK (0xffff << 16)
|
||||
#define BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET 16
|
||||
#define BCH_FLASHLAYOUT1_ECCN_MASK (0xf << 12)
|
||||
#define BCH_FLASHLAYOUT1_ECCN_OFFSET 12
|
||||
#define BCH_FLASHLAYOUT1_DATAN_SIZE_MASK 0xfff
|
||||
#define BCH_FLASHLAYOUT1_DATAN_SIZE_OFFSET 0
|
||||
|
||||
struct mx28_nand_timing {
|
||||
u8 data_setup;
|
||||
u8 data_hold;
|
||||
u8 address_setup;
|
||||
u8 dsample_time;
|
||||
u8 nand_timing_state;
|
||||
u8 tREA;
|
||||
u8 tRLOH;
|
||||
u8 tRHOH;
|
||||
};
|
||||
|
||||
struct mx28_fcb {
|
||||
u32 checksum;
|
||||
u32 fingerprint;
|
||||
u32 version;
|
||||
struct mx28_nand_timing timing;
|
||||
u32 page_data_size;
|
||||
u32 total_page_size;
|
||||
u32 sectors_per_block;
|
||||
u32 number_of_nands; /* not used by ROM code */
|
||||
u32 total_internal_die; /* not used by ROM code */
|
||||
u32 cell_type; /* not used by ROM code */
|
||||
u32 ecc_blockn_type;
|
||||
u32 ecc_block0_size;
|
||||
u32 ecc_blockn_size;
|
||||
u32 ecc_block0_type;
|
||||
u32 metadata_size;
|
||||
u32 ecc_blocks_per_page;
|
||||
u32 rsrvd[6]; /* not used by ROM code */
|
||||
u32 bch_mode;
|
||||
u32 boot_patch;
|
||||
u32 patch_sectors;
|
||||
u32 fw1_start_page;
|
||||
u32 fw2_start_page;
|
||||
u32 fw1_sectors;
|
||||
u32 fw2_sectors;
|
||||
u32 dbbt_search_area;
|
||||
u32 bb_mark_byte;
|
||||
u32 bb_mark_startbit;
|
||||
u32 bb_mark_phys_offset;
|
||||
};
|
||||
|
||||
struct mx28_dbbt_header {
|
||||
u32 checksum;
|
||||
u32 fingerprint;
|
||||
u32 version;
|
||||
u32 number_bb;
|
||||
u32 number_pages;
|
||||
u8 spare[492];
|
||||
};
|
||||
|
||||
struct mx28_dbbt {
|
||||
u32 nand_number;
|
||||
u32 number_bb;
|
||||
u32 bb_num[2040 / 4];
|
||||
};
|
||||
|
||||
#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET)
|
||||
#define GETBIT(v,n) (((v) >> (n)) & 0x1)
|
||||
|
||||
static u8 calculate_parity_13_8(u8 d)
|
||||
{
|
||||
u8 p = 0;
|
||||
|
||||
p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d, 2)) << 0;
|
||||
p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^ GETBIT(d, 1)) << 1;
|
||||
p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 2;
|
||||
p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 0)) << 3;
|
||||
p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 4;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void encode_hamming_13_8(void *_src, void *_ecc, size_t size)
|
||||
{
|
||||
int i;
|
||||
u8 *src = _src;
|
||||
u8 *ecc = _ecc;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
ecc[i] = calculate_parity_13_8(src[i]);
|
||||
}
|
||||
|
||||
static u32 calc_chksum(void *buf, size_t size)
|
||||
{
|
||||
u32 chksum = 0;
|
||||
u8 *bp = buf;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
chksum += bp[i];
|
||||
|
||||
return ~chksum;
|
||||
}
|
||||
|
||||
/*
|
||||
Physical organisation of data in NAND flash:
|
||||
metadata
|
||||
payload chunk 0 (may be empty)
|
||||
ecc for metadata + payload chunk 0
|
||||
payload chunk 1
|
||||
ecc for payload chunk 1
|
||||
...
|
||||
payload chunk n
|
||||
ecc for payload chunk n
|
||||
*/
|
||||
|
||||
static int calc_bb_offset(struct mtd_info *mtd, struct mx28_fcb *fcb)
|
||||
{
|
||||
int bb_mark_offset;
|
||||
int chunk_data_size = fcb->ecc_blockn_size * 8;
|
||||
int chunk_ecc_size = (fcb->ecc_blockn_type << 1) * 13;
|
||||
int chunk_total_size = chunk_data_size + chunk_ecc_size;
|
||||
int bb_mark_chunk, bb_mark_chunk_offs;
|
||||
|
||||
bb_mark_offset = (mtd->writesize - fcb->metadata_size) * 8;
|
||||
if (fcb->ecc_block0_size == 0)
|
||||
bb_mark_offset -= (fcb->ecc_block0_type << 1) * 13;
|
||||
|
||||
bb_mark_chunk = bb_mark_offset / chunk_total_size;
|
||||
bb_mark_chunk_offs = bb_mark_offset - (bb_mark_chunk * chunk_total_size);
|
||||
if (bb_mark_chunk_offs > chunk_data_size) {
|
||||
printf("Unsupported ECC layout; BB mark resides in ECC data: %u\n",
|
||||
bb_mark_chunk_offs);
|
||||
return -EINVAL;
|
||||
}
|
||||
bb_mark_offset -= bb_mark_chunk * chunk_ecc_size;
|
||||
return bb_mark_offset;
|
||||
}
|
||||
|
||||
static struct mx28_fcb *create_fcb(struct mtd_info *mtd, void *buf, unsigned fw1_start_block,
|
||||
size_t fw_size, unsigned fw2_start_block)
|
||||
{
|
||||
u32 fl0, fl1, t0;
|
||||
int metadata_size;
|
||||
int bb_mark_bit_offs;
|
||||
struct mx28_fcb *fcb;
|
||||
int fcb_offs;
|
||||
void __iomem *bch_regs = (void *)MXS_BCH_BASE;
|
||||
void __iomem *gpmi_regs = (void *)MXS_GPMI_BASE;
|
||||
|
||||
fl0 = readl(bch_regs + BCH_FLASH0LAYOUT0);
|
||||
fl1 = readl(bch_regs + BCH_FLASH0LAYOUT1);
|
||||
t0 = readl(gpmi_regs + GPMI_TIMING0);
|
||||
metadata_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_META_SIZE);
|
||||
|
||||
fcb = buf + ALIGN(metadata_size, 4);
|
||||
fcb_offs = (void *)fcb - buf;
|
||||
|
||||
memset(buf, 0x00, fcb_offs);
|
||||
memset(fcb, 0x00, sizeof(*fcb));
|
||||
memset(fcb + 1, 0xff, mtd->erasesize - fcb_offs - sizeof(*fcb));
|
||||
|
||||
strncpy((char *)&fcb->fingerprint, "FCB ", 4);
|
||||
fcb->version = cpu_to_be32(1);
|
||||
|
||||
fcb->timing.data_setup = BF_VAL(t0, GPMI_TIMING0_DATA_SETUP);
|
||||
fcb->timing.data_hold = BF_VAL(t0, GPMI_TIMING0_DATA_HOLD);
|
||||
fcb->timing.address_setup = BF_VAL(t0, GPMI_TIMING0_ADDRESS_SETUP);
|
||||
|
||||
fcb->page_data_size = mtd->writesize;
|
||||
fcb->total_page_size = mtd->writesize + mtd->oobsize;
|
||||
fcb->sectors_per_block = mtd->erasesize / mtd->writesize;
|
||||
|
||||
fcb->ecc_block0_type = BF_VAL(fl0, BCH_FLASHLAYOUT0_ECC0);
|
||||
fcb->ecc_block0_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_DATA0_SIZE);
|
||||
fcb->ecc_blockn_type = BF_VAL(fl1, BCH_FLASHLAYOUT1_ECCN);
|
||||
fcb->ecc_blockn_size = BF_VAL(fl1, BCH_FLASHLAYOUT1_DATAN_SIZE);
|
||||
|
||||
fcb->metadata_size = BF_VAL(fl0, BCH_FLASHLAYOUT0_META_SIZE);
|
||||
fcb->ecc_blocks_per_page = BF_VAL(fl0, BCH_FLASHLAYOUT0_NBLOCKS);
|
||||
fcb->bch_mode = readl(bch_regs + BCH_MODE);
|
||||
/*
|
||||
fcb->boot_patch = 0;
|
||||
fcb->patch_sectors = 0;
|
||||
*/
|
||||
fcb->fw1_start_page = fw1_start_block / mtd->writesize;
|
||||
fcb->fw1_sectors = DIV_ROUND_UP(fw_size, mtd->writesize);
|
||||
|
||||
if (fw2_start_block != 0 && fw2_start_block < mtd->size / mtd->erasesize) {
|
||||
fcb->fw2_start_page = fw2_start_block / mtd->writesize;
|
||||
fcb->fw2_sectors = fcb->fw1_sectors;
|
||||
}
|
||||
|
||||
fcb->dbbt_search_area = 1;
|
||||
|
||||
bb_mark_bit_offs = calc_bb_offset(mtd, fcb);
|
||||
if (bb_mark_bit_offs < 0)
|
||||
return ERR_PTR(bb_mark_bit_offs);
|
||||
fcb->bb_mark_byte = bb_mark_bit_offs / 8;
|
||||
fcb->bb_mark_startbit = bb_mark_bit_offs % 8;
|
||||
fcb->bb_mark_phys_offset = mtd->writesize;
|
||||
|
||||
fcb->checksum = calc_chksum(&fcb->fingerprint, 512 - 4);
|
||||
return fcb;
|
||||
}
|
||||
|
||||
static int find_fcb(struct mtd_info *mtd, void *ref, int page)
|
||||
{
|
||||
int ret = 0;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
void *buf = malloc(mtd->erasesize);
|
||||
|
||||
if (buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->select_chip(mtd, 0);
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
|
||||
ret = chip->ecc.read_page_raw(mtd, chip, buf);
|
||||
if (ret) {
|
||||
printf("Failed to read FCB from page %u: %d\n", page, ret);
|
||||
return ret;
|
||||
}
|
||||
chip->select_chip(mtd, -1);
|
||||
if (memcmp(buf, ref, mtd->writesize) == 0) {
|
||||
printf("%s: Found FCB in page %u (%08x)\n", __func__,
|
||||
page, page * mtd->writesize);
|
||||
ret = 1;
|
||||
}
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_fcb(struct mtd_info *mtd, void *buf, int block)
|
||||
{
|
||||
int ret;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int page = block / mtd->writesize;
|
||||
struct erase_info erase_opts = {
|
||||
.mtd = mtd,
|
||||
.addr = block,
|
||||
.len = mtd->erasesize,
|
||||
.callback = NULL,
|
||||
};
|
||||
|
||||
ret = find_fcb(mtd, buf, page);
|
||||
if (ret > 0) {
|
||||
printf("FCB at block %08x is up to date\n", block);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = mtd->erase(mtd, &erase_opts);
|
||||
if (ret) {
|
||||
printf("Failed to erase FCB block %08x\n", block);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("Writing FCB to block %08x\n", block);
|
||||
chip->select_chip(mtd, 0);
|
||||
ret = chip->write_page(mtd, chip, buf, page, 0, 1);
|
||||
if (ret) {
|
||||
printf("Failed to write FCB to block %08x: %d\n", block, ret);
|
||||
}
|
||||
chip->select_chip(mtd, -1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int update_bcb(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
int block;
|
||||
void *buf;
|
||||
struct mx28_fcb *fcb;
|
||||
struct cdev *tmp_cdev, *bcb_cdev, *firmware_cdev;
|
||||
unsigned long fw2_offset = 0;
|
||||
struct mtd_info *mtd;
|
||||
unsigned fcb_written = 0;
|
||||
|
||||
if (argc == 1)
|
||||
return COMMAND_ERROR_USAGE;
|
||||
|
||||
tmp_cdev = cdev_by_name("nand0");
|
||||
if (!tmp_cdev || !tmp_cdev->mtd) {
|
||||
pr_err("%s: No NAND device!\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mtd = tmp_cdev->mtd;
|
||||
|
||||
bcb_cdev = cdev_by_name("nand0.bcb");
|
||||
if (!bcb_cdev) {
|
||||
pr_err("%s: No FCB device!\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
firmware_cdev = cdev_by_name(argv[1]);
|
||||
if (!firmware_cdev) {
|
||||
pr_err("%s: Bootstream-Image not found!\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
tmp_cdev = cdev_by_name(argv[2]);
|
||||
if (!tmp_cdev) {
|
||||
pr_err("%s: Redundant Bootstream-Image not found!\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
fw2_offset = tmp_cdev->offset;
|
||||
}
|
||||
|
||||
buf = malloc(mtd->erasesize);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
fcb = create_fcb(mtd, buf, firmware_cdev->offset, firmware_cdev->size, fw2_offset);
|
||||
if (IS_ERR(fcb)) {
|
||||
printf("Failed to initialize FCB: %ld\n", PTR_ERR(fcb));
|
||||
return PTR_ERR(fcb);
|
||||
}
|
||||
encode_hamming_13_8(fcb, (void *)fcb + 512, 512);
|
||||
|
||||
for (block = bcb_cdev->offset; block < bcb_cdev->offset + bcb_cdev->size / 2;
|
||||
block += mtd->erasesize) {
|
||||
|
||||
if (nand_isbad_bbt(mtd, block, false))
|
||||
continue;
|
||||
|
||||
ret = write_fcb(mtd, buf, block);
|
||||
if (ret) {
|
||||
printf("Failed to write FCB to block %u\n", block);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fcb_written++;
|
||||
}
|
||||
|
||||
return fcb_written ? 0 : -ENOSPC;
|
||||
}
|
||||
|
||||
BAREBOX_CMD_HELP_START(bcb)
|
||||
BAREBOX_CMD_HELP_USAGE("bcb <first_bootstream> [second_bootstream]\n")
|
||||
BAREBOX_CMD_HELP_SHORT("Write a BCB to NAND flash which an MX23/28 needs to boot.\n")
|
||||
BAREBOX_CMD_HELP_TEXT ("Example: bcb nand0.bootstream\n")
|
||||
BAREBOX_CMD_HELP_END
|
||||
|
||||
BAREBOX_CMD_START(bcb)
|
||||
.cmd = update_bcb,
|
||||
.usage = "Writes a MX23/28 BCB data structure to flash",
|
||||
BAREBOX_CMD_HELP(cmd_bcb_help)
|
||||
BAREBOX_CMD_END
|
|
@ -0,0 +1,33 @@
|
|||
#include <common.h>
|
||||
#include <io.h>
|
||||
#include <mach/mxs.h>
|
||||
#include <mach/imx-regs.h>
|
||||
|
||||
#define MXS_BLOCK_SFTRST (1 << 31)
|
||||
#define MXS_BLOCK_CLKGATE (1 << 30)
|
||||
|
||||
int mxs_reset_block(void __iomem *reg, int just_enable)
|
||||
{
|
||||
/* Clear SFTRST */
|
||||
writel(MXS_BLOCK_SFTRST, reg + BIT_CLR);
|
||||
mdelay(1);
|
||||
|
||||
/* Clear CLKGATE */
|
||||
writel(MXS_BLOCK_CLKGATE, reg + BIT_CLR);
|
||||
|
||||
if (!just_enable) {
|
||||
/* Set SFTRST */
|
||||
writel(MXS_BLOCK_SFTRST, reg + BIT_SET);
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
/* Clear SFTRST */
|
||||
writel(MXS_BLOCK_SFTRST, reg + BIT_CLR);
|
||||
mdelay(1);
|
||||
|
||||
/* Clear CLKGATE */
|
||||
writel(MXS_BLOCK_CLKGATE, reg + BIT_CLR);
|
||||
mdelay(1);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -25,5 +25,6 @@ unsigned imx_set_sspclk(unsigned, unsigned, int);
|
|||
unsigned imx_set_ioclk(unsigned);
|
||||
unsigned imx_set_lcdifclk(unsigned);
|
||||
unsigned imx_get_lcdifclk(void);
|
||||
void imx_enable_nandclk(void);
|
||||
|
||||
#endif /* MACH_CLOCK_IMX23_H */
|
||||
|
|
|
@ -27,6 +27,7 @@ unsigned imx_set_lcdifclk(unsigned);
|
|||
unsigned imx_get_lcdifclk(void);
|
||||
unsigned imx_get_fecclk(void);
|
||||
void imx_enable_enetclk(void);
|
||||
void imx_enable_nandclk(void);
|
||||
|
||||
#endif /* MACH_CLOCK_IMX28_H */
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Freescale i.MX28 APBH DMA
|
||||
*
|
||||
* Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
|
||||
* on behalf of DENX Software Engineering GmbH
|
||||
*
|
||||
* Based on code from LTIB:
|
||||
* Copyright 2008-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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __DMA_H__
|
||||
#define __DMA_H__
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
#ifndef CONFIG_ARCH_DMA_PIO_WORDS
|
||||
#define DMA_PIO_WORDS 15
|
||||
#else
|
||||
#define DMA_PIO_WORDS CONFIG_ARCH_DMA_PIO_WORDS
|
||||
#endif
|
||||
|
||||
#define MXS_DMA_ALIGNMENT 32
|
||||
|
||||
/*
|
||||
* MXS DMA channels
|
||||
*/
|
||||
enum {
|
||||
MXS_DMA_CHANNEL_AHB_APBH_SSP0 = 0,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_SSP1,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_SSP2,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_SSP3,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI0,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI1,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI2,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI3,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI4,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI5,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI6,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_GPMI7,
|
||||
MXS_DMA_CHANNEL_AHB_APBH_SSP,
|
||||
MXS_MAX_DMA_CHANNELS,
|
||||
};
|
||||
|
||||
/*
|
||||
* MXS DMA hardware command.
|
||||
*
|
||||
* This structure describes the in-memory layout of an entire DMA command,
|
||||
* including space for the maximum number of PIO accesses. See the appropriate
|
||||
* reference manual for a detailed description of what these fields mean to the
|
||||
* DMA hardware.
|
||||
*/
|
||||
#define MXS_DMA_DESC_COMMAND_MASK 0x3
|
||||
#define MXS_DMA_DESC_COMMAND_OFFSET 0
|
||||
#define MXS_DMA_DESC_COMMAND_NO_DMAXFER 0x0
|
||||
#define MXS_DMA_DESC_COMMAND_DMA_WRITE 0x1
|
||||
#define MXS_DMA_DESC_COMMAND_DMA_READ 0x2
|
||||
#define MXS_DMA_DESC_COMMAND_DMA_SENSE 0x3
|
||||
#define MXS_DMA_DESC_CHAIN (1 << 2)
|
||||
#define MXS_DMA_DESC_IRQ (1 << 3)
|
||||
#define MXS_DMA_DESC_NAND_LOCK (1 << 4)
|
||||
#define MXS_DMA_DESC_NAND_WAIT_4_READY (1 << 5)
|
||||
#define MXS_DMA_DESC_DEC_SEM (1 << 6)
|
||||
#define MXS_DMA_DESC_WAIT4END (1 << 7)
|
||||
#define MXS_DMA_DESC_HALT_ON_TERMINATE (1 << 8)
|
||||
#define MXS_DMA_DESC_TERMINATE_FLUSH (1 << 9)
|
||||
#define MXS_DMA_DESC_PIO_WORDS_MASK (0xf << 12)
|
||||
#define MXS_DMA_DESC_PIO_WORDS_OFFSET 12
|
||||
#define MXS_DMA_DESC_BYTES_MASK (0xffff << 16)
|
||||
#define MXS_DMA_DESC_BYTES_OFFSET 16
|
||||
|
||||
struct mxs_dma_cmd {
|
||||
unsigned long next;
|
||||
unsigned long data;
|
||||
union {
|
||||
dma_addr_t address;
|
||||
unsigned long alternate;
|
||||
};
|
||||
unsigned long pio_words[DMA_PIO_WORDS];
|
||||
};
|
||||
|
||||
/*
|
||||
* MXS DMA command descriptor.
|
||||
*
|
||||
* This structure incorporates an MXS DMA hardware command structure, along
|
||||
* with metadata.
|
||||
*/
|
||||
#define MXS_DMA_DESC_FIRST (1 << 0)
|
||||
#define MXS_DMA_DESC_LAST (1 << 1)
|
||||
#define MXS_DMA_DESC_READY (1 << 31)
|
||||
|
||||
struct mxs_dma_desc {
|
||||
struct mxs_dma_cmd cmd;
|
||||
unsigned int flags;
|
||||
dma_addr_t address;
|
||||
void *buffer;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
/**
|
||||
* MXS DMA channel
|
||||
*
|
||||
* This structure represents a single DMA channel. The MXS platform code
|
||||
* maintains an array of these structures to represent every DMA channel in the
|
||||
* system (see mxs_dma_channels).
|
||||
*/
|
||||
#define MXS_DMA_FLAGS_IDLE 0
|
||||
#define MXS_DMA_FLAGS_BUSY (1 << 0)
|
||||
#define MXS_DMA_FLAGS_FREE 0
|
||||
#define MXS_DMA_FLAGS_ALLOCATED (1 << 16)
|
||||
#define MXS_DMA_FLAGS_VALID (1 << 31)
|
||||
|
||||
struct mxs_dma_chan {
|
||||
const char *name;
|
||||
unsigned long dev;
|
||||
struct mxs_dma_device *dma;
|
||||
unsigned int flags;
|
||||
unsigned int active_num;
|
||||
unsigned int pending_num;
|
||||
struct list_head active;
|
||||
struct list_head done;
|
||||
};
|
||||
|
||||
struct mxs_dma_desc *mxs_dma_desc_alloc(void);
|
||||
void mxs_dma_desc_free(struct mxs_dma_desc *);
|
||||
int mxs_dma_desc_append(int channel, struct mxs_dma_desc *pdesc);
|
||||
|
||||
int mxs_dma_go(int chan);
|
||||
int mxs_dma_init(void);
|
||||
|
||||
#endif /* __DMA_H__ */
|
|
@ -27,6 +27,9 @@
|
|||
#endif
|
||||
|
||||
#define IMX_MEMORY_BASE 0x40000000
|
||||
#define MXS_APBH_BASE 0x80004000
|
||||
#define MXS_BCH_BASE 0x8000a000
|
||||
#define MXS_GPMI_BASE 0x8000c000
|
||||
#define IMX_UART1_BASE 0x8006c000
|
||||
#define IMX_UART2_BASE 0x8006e000
|
||||
#define IMX_DBGUART_BASE 0x80070000
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
#define IMX_SRAM_BASE 0x00000000
|
||||
#define IMX_MEMORY_BASE 0x40000000
|
||||
|
||||
#define IMX_NFC_BASE 0x8000C000
|
||||
#define MXS_APBH_BASE 0x80004000
|
||||
#define MXS_BCH_BASE 0x8000a000
|
||||
#define MXS_GPMI_BASE 0x8000c000
|
||||
#define IMX_SSP0_BASE 0x80010000
|
||||
#define IMX_SSP1_BASE 0x80012000
|
||||
#define IMX_SSP2_BASE 0x80014000
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef __MACH_MXS_H
|
||||
#define __MACH_MXS_H
|
||||
|
||||
int mxs_reset_block(void __iomem *reg, int just_enable);
|
||||
|
||||
#endif /* __MACH_MXS_H */
|
|
@ -47,6 +47,8 @@
|
|||
# define GET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK)
|
||||
# define SET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK)
|
||||
#define HW_CLKCTRL_GPMI 0x080
|
||||
# define CLKCTRL_GPMI_CLKGATE (1 << 31)
|
||||
# define CLKCTRL_GPMI_DIV_MASK 0x3ff
|
||||
/* note: no set/clear register! */
|
||||
#define HW_CLKCTRL_SPDIF 0x090
|
||||
/* note: no set/clear register! */
|
||||
|
@ -288,6 +290,23 @@ unsigned imx_set_sspclk(unsigned index, unsigned nc, int high)
|
|||
return imx_get_sspclk(index);
|
||||
}
|
||||
|
||||
void imx_enable_nandclk(void)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
/* Clear bypass bit; refman says clear, but fsl-code does set. Hooray! */
|
||||
writel(CLKCTRL_CLKSEQ_BYPASS_GPMI,
|
||||
IMX_CCM_BASE + HW_CLKCTRL_CLKSEQ + BIT_SET);
|
||||
|
||||
reg = readl(IMX_CCM_BASE + HW_CLKCTRL_GPMI) & ~CLKCTRL_GPMI_CLKGATE;
|
||||
writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI);
|
||||
udelay(1000);
|
||||
/* Initialize DIV to 1 */
|
||||
reg &= ~CLKCTRL_GPMI_DIV_MASK;
|
||||
reg |= 1;
|
||||
writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI);
|
||||
}
|
||||
|
||||
void imx_dump_clocks(void)
|
||||
{
|
||||
printf("mpll: %10u kHz\n", imx_get_mpllclk() / 1000);
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
# define GET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK)
|
||||
# define SET_SSP_DIV(x) ((x) & CLKCTRL_SSP_DIV_MASK)
|
||||
#define HW_CLKCTRL_GPMI 0x0d0
|
||||
# define CLKCTRL_GPMI_CLKGATE (1 << 31)
|
||||
# define CLKCTRL_GPMI_DIV_MASK 0x3ff
|
||||
/* note: no set/clear register! */
|
||||
#define HW_CLKCTRL_SPDIF 0x0e0
|
||||
/* note: no set/clear register! */
|
||||
|
@ -398,6 +400,23 @@ void imx_enable_enetclk(void)
|
|||
IMX_CCM_BASE + HW_CLKCTRL_ENET);
|
||||
}
|
||||
|
||||
void imx_enable_nandclk(void)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
/* Clear bypass bit; refman says clear, but fsl-code does set. Hooray! */
|
||||
writel(CLKCTRL_CLKSEQ_BYPASS_GPMI,
|
||||
IMX_CCM_BASE + HW_CLKCTRL_CLKSEQ + BIT_SET);
|
||||
|
||||
reg = readl(IMX_CCM_BASE + HW_CLKCTRL_GPMI) & ~CLKCTRL_GPMI_CLKGATE;
|
||||
writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI);
|
||||
udelay(1000);
|
||||
/* Initialize DIV to 1 */
|
||||
reg &= ~CLKCTRL_GPMI_DIV_MASK;
|
||||
reg |= 1;
|
||||
writel(reg, IMX_CCM_BASE + HW_CLKCTRL_GPMI);
|
||||
}
|
||||
|
||||
void imx_dump_clocks(void)
|
||||
{
|
||||
printf("mpll: %10u kHz\n", imx_get_mpllclk() / 1000);
|
||||
|
|
|
@ -17,5 +17,6 @@ source "drivers/eeprom/Kconfig"
|
|||
source "drivers/input/Kconfig"
|
||||
source "drivers/watchdog/Kconfig"
|
||||
source "drivers/pwm/Kconfig"
|
||||
source "drivers/dma/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -15,4 +15,5 @@ obj-$(CONFIG_LED) += led/
|
|||
obj-y += eeprom/
|
||||
obj-$(CONFIG_PWM) += pwm/
|
||||
obj-y += input/
|
||||
obj-y += dma/
|
||||
obj-y += watchdog/
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
menu "DMA support"
|
||||
|
||||
config MXS_APBH_DMA
|
||||
tristate "MXS APBH DMA ENGINE"
|
||||
depends on ARCH_IMX23 || ARCH_IMX28
|
||||
help
|
||||
Experimental!
|
||||
endmenu
|
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_MXS_APBH_DMA) += apbh_dma.o
|
|
@ -0,0 +1,598 @@
|
|||
/*
|
||||
* Freescale i.MX28 APBH DMA driver
|
||||
*
|
||||
* Copyright (C) 2011 Wolfram Sang <w.sang@pengutronix.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <errno.h>
|
||||
#include <asm/mmu.h>
|
||||
#include <asm/io.h>
|
||||
#include <mach/clock.h>
|
||||
#include <mach/imx-regs.h>
|
||||
#include <mach/dma.h>
|
||||
#include <mach/mxs.h>
|
||||
|
||||
#define HW_APBHX_CTRL0 0x000
|
||||
#define BM_APBH_CTRL0_APB_BURST8_EN (1 << 29)
|
||||
#define BM_APBH_CTRL0_APB_BURST_EN (1 << 28)
|
||||
#define BP_APBH_CTRL0_CLKGATE_CHANNEL 8
|
||||
#define BP_APBH_CTRL0_RESET_CHANNEL 16
|
||||
#define HW_APBHX_CTRL1 0x010
|
||||
#define BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN 16
|
||||
#define HW_APBHX_CTRL2 0x020
|
||||
#define HW_APBHX_CHANNEL_CTRL 0x030
|
||||
#define BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL 16
|
||||
#define HW_APBH_VERSION (cpu_is_mx23() ? 0x3f0 : 0x800)
|
||||
#define HW_APBX_VERSION 0x800
|
||||
#define BP_APBHX_VERSION_MAJOR 24
|
||||
#define HW_APBHX_CHn_NXTCMDAR(n) \
|
||||
((apbh_is_old ? 0x050 : 0x110) + (n) * 0x70)
|
||||
#define HW_APBHX_CHn_SEMA(n) \
|
||||
((apbh_is_old ? 0x080 : 0x140) + (n) * 0x70)
|
||||
#define BM_APBHX_CHn_SEMA_PHORE (0xff << 16)
|
||||
#define BP_APBHX_CHn_SEMA_PHORE 16
|
||||
|
||||
static struct mxs_dma_chan mxs_dma_channels[MXS_MAX_DMA_CHANNELS];
|
||||
static bool apbh_is_old;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the address of the command within a descriptor.
|
||||
*/
|
||||
static unsigned int mxs_dma_cmd_address(struct mxs_dma_desc *desc)
|
||||
{
|
||||
return desc->address + offsetof(struct mxs_dma_desc, cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static int mxs_dma_read_semaphore(int channel)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
uint32_t tmp;
|
||||
int ret;
|
||||
|
||||
ret = mxs_dma_validate_chan(channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tmp = readl(apbh_regs + HW_APBHX_CHn_SEMA(channel));
|
||||
|
||||
tmp &= BM_APBHX_CHn_SEMA_PHORE;
|
||||
tmp >>= BP_APBHX_CHn_SEMA_PHORE;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static int mxs_dma_enable(int channel)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
unsigned int sem;
|
||||
struct mxs_dma_chan *pchan;
|
||||
struct mxs_dma_desc *pdesc;
|
||||
int channel_bit, 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 + HW_APBHX_CHn_NXTCMDAR(channel));
|
||||
}
|
||||
writel(pchan->pending_num,
|
||||
apbh_regs + HW_APBHX_CHn_SEMA(channel));
|
||||
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 + HW_APBHX_CHn_NXTCMDAR(channel));
|
||||
writel(pchan->active_num,
|
||||
apbh_regs + HW_APBHX_CHn_SEMA(channel));
|
||||
channel_bit = channel + (apbh_is_old ? BP_APBH_CTRL0_CLKGATE_CHANNEL : 0);
|
||||
writel(1 << channel_bit, apbh_regs + HW_APBHX_CTRL0 + BIT_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.
|
||||
*/
|
||||
static int mxs_dma_disable(int channel)
|
||||
{
|
||||
struct mxs_dma_chan *pchan;
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
int channel_bit, 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;
|
||||
|
||||
channel_bit = channel + (apbh_is_old ? BP_APBH_CTRL0_CLKGATE_CHANNEL : 0);
|
||||
writel(1 << channel_bit, apbh_regs + HW_APBHX_CTRL0 + BIT_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.
|
||||
*/
|
||||
static int mxs_dma_reset(int channel)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
int ret;
|
||||
|
||||
ret = mxs_dma_validate_chan(channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (apbh_is_old)
|
||||
writel(1 << (channel + BP_APBH_CTRL0_RESET_CHANNEL),
|
||||
apbh_regs + HW_APBHX_CTRL0 + BIT_SET);
|
||||
else
|
||||
writel(1 << (channel + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL),
|
||||
apbh_regs + HW_APBHX_CHANNEL_CTRL + BIT_SET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable DMA interrupt.
|
||||
*
|
||||
* This function enables the given DMA channel to interrupt the CPU.
|
||||
*/
|
||||
static int mxs_dma_enable_irq(int channel, int enable)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
int ret;
|
||||
|
||||
ret = mxs_dma_validate_chan(channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable)
|
||||
writel(1 << (channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN),
|
||||
apbh_regs + HW_APBHX_CTRL1 + BIT_SET);
|
||||
else
|
||||
writel(1 << (channel + BP_APBHX_CTRL1_CH_CMDCMPLT_IRQ_EN),
|
||||
apbh_regs + HW_APBHX_CTRL1 + BIT_CLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static int mxs_dma_ack_irq(int channel)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
int ret;
|
||||
|
||||
ret = mxs_dma_validate_chan(channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(1 << channel, apbh_regs + HW_APBHX_CTRL1 + BIT_CLR);
|
||||
writel(1 << channel, apbh_regs + HW_APBHX_CTRL2 + BIT_CLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Request to reserve a DMA channel
|
||||
*/
|
||||
static 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()).
|
||||
*/
|
||||
static 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 = dma_alloc_coherent(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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static 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
|
||||
*/
|
||||
static int mxs_dma_wait_complete(uint32_t timeout, unsigned int chan)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
int ret;
|
||||
|
||||
ret = mxs_dma_validate_chan(chan);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (--timeout) {
|
||||
if (readl(apbh_regs + HW_APBHX_CTRL1) & (1 << chan))
|
||||
break;
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
if (timeout == 0) {
|
||||
ret = -ETIMEDOUT;
|
||||
mxs_dma_reset(chan);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
void __iomem *apbh_regs = (void *)MXS_APBH_BASE;
|
||||
struct mxs_dma_chan *pchan;
|
||||
int ret, channel;
|
||||
u32 val, reg;
|
||||
|
||||
mxs_reset_block(apbh_regs, 0);
|
||||
|
||||
/* HACK: Get CPUID and determine APBH version */
|
||||
val = readl(0x8001c310) >> 16;
|
||||
if (val == 0x2800)
|
||||
reg = MXS_APBH_BASE + 0x0800;
|
||||
else
|
||||
reg = MXS_APBH_BASE + 0x03f0;
|
||||
|
||||
apbh_is_old = (readl((void *)reg) >> 24) < 3;
|
||||
|
||||
writel(BM_APBH_CTRL0_APB_BURST8_EN,
|
||||
apbh_regs + HW_APBHX_CTRL0 + BIT_SET);
|
||||
|
||||
writel(BM_APBH_CTRL0_APB_BURST_EN,
|
||||
apbh_regs + HW_APBHX_CTRL0 + BIT_SET);
|
||||
|
||||
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;
|
||||
}
|
|
@ -53,6 +53,11 @@ config NAND_IMX
|
|||
prompt "i.MX NAND driver"
|
||||
depends on ARCH_IMX
|
||||
|
||||
config NAND_MXS
|
||||
bool
|
||||
prompt "i.MX23/28 NAND driver"
|
||||
depends on MXS_APBH_DMA
|
||||
|
||||
config NAND_OMAP_GPMC
|
||||
tristate "NAND Flash Support for GPMC based OMAP platforms"
|
||||
depends on OMAP_GPMC
|
||||
|
|
|
@ -15,3 +15,5 @@ obj-$(CONFIG_NAND_IMX) += nand_imx.o
|
|||
obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o nand_omap_bch_decoder.o
|
||||
obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o
|
||||
obj-$(CONFIG_NAND_S3C24XX) += nand_s3c24xx.o
|
||||
obj-$(CONFIG_NAND_S3C24X0) += nand_s3c2410.o
|
||||
obj-$(CONFIG_NAND_MXS) += nand_mxs.o
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue