Merge branch 'master' of git://git.denx.de/u-boot-arm

This commit is contained in:
Wolfgang Denk
2009-02-01 21:38:07 +01:00
113 changed files with 12041 additions and 28 deletions

View File

@@ -30,6 +30,7 @@ COBJS-$(CONFIG_FSL_I2C) += fsl_i2c.o
COBJS-$(CONFIG_I2C_MXC) += mxc_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
COBJS-$(CONFIG_SOFT_I2C) += soft_i2c.o
COBJS-$(CONFIG_TSI108_I2C) += tsi108_i2c.o

View File

@@ -109,7 +109,11 @@ static int i2c_read_byte (u8 devaddr, u8 regoffset, u8 * value)
status = wait_for_pin ();
if (status & I2C_STAT_RRDY) {
#if defined(CONFIG_OMAP243X) || defined(CONFIG_OMAP34XX)
*value = readb (I2C_DATA);
#else
*value = readw (I2C_DATA);
#endif
udelay (20000);
} else {
i2c_error = 1;
@@ -150,8 +154,23 @@ static int i2c_write_byte (u8 devaddr, u8 regoffset, u8 value)
status = wait_for_pin ();
if (status & I2C_STAT_XRDY) {
#if defined(CONFIG_OMAP243X) || defined(CONFIG_OMAP34XX)
/* send out 1 byte */
writeb (regoffset, I2C_DATA);
writew (I2C_STAT_XRDY, I2C_STAT);
status = wait_for_pin ();
if ((status & I2C_STAT_XRDY)) {
/* send out next 1 byte */
writeb (value, I2C_DATA);
writew (I2C_STAT_XRDY, I2C_STAT);
} else {
i2c_error = 1;
}
#else
/* send out two bytes */
writew ((value << 8) + regoffset, I2C_DATA);
#endif
/* must have enough delay to allow BB bit to go low */
udelay (50000);
if (readw (I2C_STAT) & I2C_STAT_NACK) {
@@ -188,7 +207,11 @@ static void flush_fifo(void)
while(1){
stat = readw(I2C_STAT);
if(stat == I2C_STAT_RRDY){
#if defined(CONFIG_OMAP243X) || defined(CONFIG_OMAP34XX)
readb(I2C_DATA);
#else
readw(I2C_DATA);
#endif
writew(I2C_STAT_RRDY,I2C_STAT);
udelay(1000);
}else

View File

@@ -26,6 +26,7 @@ include $(TOPDIR)/config.mk
LIB := $(obj)libmmc.a
COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o
COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o
COBJS := $(COBJS-y)
SRCS := $(COBJS:.o=.c)

558
drivers/mmc/omap3_mmc.c Normal file
View File

@@ -0,0 +1,558 @@
/*
* (C) Copyright 2008
* Texas Instruments, <www.ti.com>
* Syed Mohammed Khasim <khasim@ti.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's version 2 of
* the License.
*
* 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 <config.h>
#include <common.h>
#include <fat.h>
#include <mmc.h>
#include <part.h>
#include <i2c.h>
const unsigned short mmc_transspeed_val[15][4] = {
{CLKD(10, 1), CLKD(10, 10), CLKD(10, 100), CLKD(10, 1000)},
{CLKD(12, 1), CLKD(12, 10), CLKD(12, 100), CLKD(12, 1000)},
{CLKD(13, 1), CLKD(13, 10), CLKD(13, 100), CLKD(13, 1000)},
{CLKD(15, 1), CLKD(15, 10), CLKD(15, 100), CLKD(15, 1000)},
{CLKD(20, 1), CLKD(20, 10), CLKD(20, 100), CLKD(20, 1000)},
{CLKD(26, 1), CLKD(26, 10), CLKD(26, 100), CLKD(26, 1000)},
{CLKD(30, 1), CLKD(30, 10), CLKD(30, 100), CLKD(30, 1000)},
{CLKD(35, 1), CLKD(35, 10), CLKD(35, 100), CLKD(35, 1000)},
{CLKD(40, 1), CLKD(40, 10), CLKD(40, 100), CLKD(40, 1000)},
{CLKD(45, 1), CLKD(45, 10), CLKD(45, 100), CLKD(45, 1000)},
{CLKD(52, 1), CLKD(52, 10), CLKD(52, 100), CLKD(52, 1000)},
{CLKD(55, 1), CLKD(55, 10), CLKD(55, 100), CLKD(55, 1000)},
{CLKD(60, 1), CLKD(60, 10), CLKD(60, 100), CLKD(60, 1000)},
{CLKD(70, 1), CLKD(70, 10), CLKD(70, 100), CLKD(70, 1000)},
{CLKD(80, 1), CLKD(80, 10), CLKD(80, 100), CLKD(80, 1000)}
};
mmc_card_data cur_card_data;
static block_dev_desc_t mmc_blk_dev;
block_dev_desc_t *mmc_get_dev(int dev)
{
return (block_dev_desc_t *) &mmc_blk_dev;
}
void twl4030_mmc_config(void)
{
unsigned char data;
data = 0x20;
i2c_write(0x4B, 0x82, 1, &data, 1);
data = 0x2;
i2c_write(0x4B, 0x85, 1, &data, 1);
}
unsigned char mmc_board_init(void)
{
unsigned int value = 0;
twl4030_mmc_config();
value = CONTROL_PBIAS_LITE;
CONTROL_PBIAS_LITE = value | (1 << 2) | (1 << 1) | (1 << 9);
value = CONTROL_DEV_CONF0;
CONTROL_DEV_CONF0 = value | (1 << 24);
return 1;
}
void mmc_init_stream(void)
{
volatile unsigned int mmc_stat;
OMAP_HSMMC_CON |= INIT_INITSTREAM;
OMAP_HSMMC_CMD = MMC_CMD0;
do {
mmc_stat = OMAP_HSMMC_STAT;
} while (!(mmc_stat & CC_MASK));
OMAP_HSMMC_STAT = CC_MASK;
OMAP_HSMMC_CMD = MMC_CMD0;
do {
mmc_stat = OMAP_HSMMC_STAT;
} while (!(mmc_stat & CC_MASK));
OMAP_HSMMC_STAT = OMAP_HSMMC_STAT;
OMAP_HSMMC_CON &= ~INIT_INITSTREAM;
}
unsigned char mmc_clock_config(unsigned int iclk, unsigned short clk_div)
{
unsigned int val;
mmc_reg_out(OMAP_HSMMC_SYSCTL, (ICE_MASK | DTO_MASK | CEN_MASK),
(ICE_STOP | DTO_15THDTO | CEN_DISABLE));
switch (iclk) {
case CLK_INITSEQ:
val = MMC_INIT_SEQ_CLK / 2;
break;
case CLK_400KHZ:
val = MMC_400kHz_CLK;
break;
case CLK_MISC:
val = clk_div;
break;
default:
return 0;
}
mmc_reg_out(OMAP_HSMMC_SYSCTL,
ICE_MASK | CLKD_MASK, (val << CLKD_OFFSET) | ICE_OSCILLATE);
while ((OMAP_HSMMC_SYSCTL & ICS_MASK) == ICS_NOTREADY) ;
OMAP_HSMMC_SYSCTL |= CEN_ENABLE;
return 1;
}
unsigned char mmc_init_setup(void)
{
unsigned int reg_val;
mmc_board_init();
OMAP_HSMMC_SYSCONFIG |= MMC_SOFTRESET;
while ((OMAP_HSMMC_SYSSTATUS & RESETDONE) == 0) ;
OMAP_HSMMC_SYSCTL |= SOFTRESETALL;
while ((OMAP_HSMMC_SYSCTL & SOFTRESETALL) != 0x0) ;
OMAP_HSMMC_HCTL = DTW_1_BITMODE | SDBP_PWROFF | SDVS_3V0;
OMAP_HSMMC_CAPA |= VS30_3V0SUP | VS18_1V8SUP;
reg_val = OMAP_HSMMC_CON & RESERVED_MASK;
OMAP_HSMMC_CON = CTPL_MMC_SD | reg_val | WPP_ACTIVEHIGH |
CDP_ACTIVEHIGH | MIT_CTO | DW8_1_4BITMODE | MODE_FUNC |
STR_BLOCK | HR_NOHOSTRESP | INIT_NOINIT | NOOPENDRAIN;
mmc_clock_config(CLK_INITSEQ, 0);
OMAP_HSMMC_HCTL |= SDBP_PWRON;
OMAP_HSMMC_IE = 0x307f0033;
mmc_init_stream();
return 1;
}
unsigned char mmc_send_cmd(unsigned int cmd, unsigned int arg,
unsigned int *response)
{
volatile unsigned int mmc_stat;
while ((OMAP_HSMMC_PSTATE & DATI_MASK) == DATI_CMDDIS) ;
OMAP_HSMMC_BLK = BLEN_512BYTESLEN | NBLK_STPCNT;
OMAP_HSMMC_STAT = 0xFFFFFFFF;
OMAP_HSMMC_ARG = arg;
OMAP_HSMMC_CMD = cmd | CMD_TYPE_NORMAL | CICE_NOCHECK |
CCCE_NOCHECK | MSBS_SGLEBLK | ACEN_DISABLE | BCE_DISABLE |
DE_DISABLE;
while (1) {
do {
mmc_stat = OMAP_HSMMC_STAT;
} while (mmc_stat == 0);
if ((mmc_stat & ERRI_MASK) != 0)
return (unsigned char) mmc_stat;
if (mmc_stat & CC_MASK) {
OMAP_HSMMC_STAT = CC_MASK;
response[0] = OMAP_HSMMC_RSP10;
if ((cmd & RSP_TYPE_MASK) == RSP_TYPE_LGHT136) {
response[1] = OMAP_HSMMC_RSP32;
response[2] = OMAP_HSMMC_RSP54;
response[3] = OMAP_HSMMC_RSP76;
}
break;
}
}
return 1;
}
unsigned char mmc_read_data(unsigned int *output_buf)
{
volatile unsigned int mmc_stat;
unsigned int read_count = 0;
/*
* Start Polled Read
*/
while (1) {
do {
mmc_stat = OMAP_HSMMC_STAT;
} while (mmc_stat == 0);
if ((mmc_stat & ERRI_MASK) != 0)
return (unsigned char) mmc_stat;
if (mmc_stat & BRR_MASK) {
unsigned int k;
OMAP_HSMMC_STAT |= BRR_MASK;
for (k = 0; k < MMCSD_SECTOR_SIZE / 4; k++) {
*output_buf = OMAP_HSMMC_DATA;
output_buf++;
read_count += 4;
}
}
if (mmc_stat & BWR_MASK)
OMAP_HSMMC_STAT |= BWR_MASK;
if (mmc_stat & TC_MASK) {
OMAP_HSMMC_STAT |= TC_MASK;
break;
}
}
return 1;
}
unsigned char mmc_detect_card(mmc_card_data *mmc_card_cur)
{
unsigned char err;
unsigned int argument = 0;
unsigned int ocr_value, ocr_recvd, ret_cmd41, hcs_val;
unsigned int resp[4];
unsigned short retry_cnt = 2000;
/* Set to Initialization Clock */
err = mmc_clock_config(CLK_400KHZ, 0);
if (err != 1)
return err;
mmc_card_cur->RCA = MMC_RELATIVE_CARD_ADDRESS;
argument = 0x00000000;
ocr_value = (0x1FF << 15);
err = mmc_send_cmd(MMC_CMD0, argument, resp);
if (err != 1)
return err;
argument = SD_CMD8_CHECK_PATTERN | SD_CMD8_2_7_3_6_V_RANGE;
err = mmc_send_cmd(MMC_SDCMD8, argument, resp);
hcs_val = (err == 1) ?
MMC_OCR_REG_HOST_CAPACITY_SUPPORT_SECTOR :
MMC_OCR_REG_HOST_CAPACITY_SUPPORT_BYTE;
argument = 0x0000 << 16;
err = mmc_send_cmd(MMC_CMD55, argument, resp);
if (err == 1) {
mmc_card_cur->card_type = SD_CARD;
ocr_value |= hcs_val;
ret_cmd41 = MMC_ACMD41;
} else {
mmc_card_cur->card_type = MMC_CARD;
ocr_value |= MMC_OCR_REG_ACCESS_MODE_SECTOR;
ret_cmd41 = MMC_CMD1;
OMAP_HSMMC_CON &= ~OD;
OMAP_HSMMC_CON |= OPENDRAIN;
}
argument = ocr_value;
err = mmc_send_cmd(ret_cmd41, argument, resp);
if (err != 1)
return err;
ocr_recvd = ((mmc_resp_r3 *) resp)->ocr;
while (!(ocr_recvd & (0x1 << 31)) && (retry_cnt > 0)) {
retry_cnt--;
if (mmc_card_cur->card_type == SD_CARD) {
argument = 0x0000 << 16;
err = mmc_send_cmd(MMC_CMD55, argument, resp);
}
argument = ocr_value;
err = mmc_send_cmd(ret_cmd41, argument, resp);
if (err != 1)
return err;
ocr_recvd = ((mmc_resp_r3 *) resp)->ocr;
}
if (!(ocr_recvd & (0x1 << 31)))
return 0;
if (mmc_card_cur->card_type == MMC_CARD) {
if ((ocr_recvd & MMC_OCR_REG_ACCESS_MODE_MASK) ==
MMC_OCR_REG_ACCESS_MODE_SECTOR) {
mmc_card_cur->mode = SECTOR_MODE;
} else {
mmc_card_cur->mode = BYTE_MODE;
}
ocr_recvd &= ~MMC_OCR_REG_ACCESS_MODE_MASK;
} else {
if ((ocr_recvd & MMC_OCR_REG_HOST_CAPACITY_SUPPORT_MASK)
== MMC_OCR_REG_HOST_CAPACITY_SUPPORT_SECTOR) {
mmc_card_cur->mode = SECTOR_MODE;
} else {
mmc_card_cur->mode = BYTE_MODE;
}
ocr_recvd &= ~MMC_OCR_REG_HOST_CAPACITY_SUPPORT_MASK;
}
ocr_recvd &= ~(0x1 << 31);
if (!(ocr_recvd & ocr_value))
return 0;
err = mmc_send_cmd(MMC_CMD2, argument, resp);
if (err != 1)
return err;
if (mmc_card_cur->card_type == MMC_CARD) {
argument = mmc_card_cur->RCA << 16;
err = mmc_send_cmd(MMC_CMD3, argument, resp);
if (err != 1)
return err;
} else {
argument = 0x00000000;
err = mmc_send_cmd(MMC_SDCMD3, argument, resp);
if (err != 1)
return err;
mmc_card_cur->RCA = ((mmc_resp_r6 *) resp)->newpublishedrca;
}
OMAP_HSMMC_CON &= ~OD;
OMAP_HSMMC_CON |= NOOPENDRAIN;
return 1;
}
unsigned char mmc_read_cardsize(mmc_card_data *mmc_dev_data,
mmc_csd_reg_t *cur_csd)
{
mmc_extended_csd_reg_t ext_csd;
unsigned int size, count, blk_len, blk_no, card_size, argument;
unsigned char err;
unsigned int resp[4];
if (mmc_dev_data->mode == SECTOR_MODE) {
if (mmc_dev_data->card_type == SD_CARD) {
card_size =
(((mmc_sd2_csd_reg_t *) cur_csd)->
c_size_lsb & MMC_SD2_CSD_C_SIZE_LSB_MASK) |
((((mmc_sd2_csd_reg_t *) cur_csd)->
c_size_msb & MMC_SD2_CSD_C_SIZE_MSB_MASK)
<< MMC_SD2_CSD_C_SIZE_MSB_OFFSET);
mmc_dev_data->size = card_size * 1024;
if (mmc_dev_data->size == 0)
return 0;
} else {
argument = 0x00000000;
err = mmc_send_cmd(MMC_CMD8, argument, resp);
if (err != 1)
return err;
err = mmc_read_data((unsigned int *) &ext_csd);
if (err != 1)
return err;
mmc_dev_data->size = ext_csd.sectorcount;
if (mmc_dev_data->size == 0)
mmc_dev_data->size = 8388608;
}
} else {
if (cur_csd->c_size_mult >= 8)
return 0;
if (cur_csd->read_bl_len >= 12)
return 0;
/* Compute size */
count = 1 << (cur_csd->c_size_mult + 2);
card_size = (cur_csd->c_size_lsb & MMC_CSD_C_SIZE_LSB_MASK) |
((cur_csd->c_size_msb & MMC_CSD_C_SIZE_MSB_MASK)
<< MMC_CSD_C_SIZE_MSB_OFFSET);
blk_no = (card_size + 1) * count;
blk_len = 1 << cur_csd->read_bl_len;
size = blk_no * blk_len;
mmc_dev_data->size = size / MMCSD_SECTOR_SIZE;
if (mmc_dev_data->size == 0)
return 0;
}
return 1;
}
unsigned char omap_mmc_read_sect(unsigned int start_sec, unsigned int num_bytes,
mmc_card_data *mmc_c,
unsigned long *output_buf)
{
unsigned char err;
unsigned int argument;
unsigned int resp[4];
unsigned int num_sec_val =
(num_bytes + (MMCSD_SECTOR_SIZE - 1)) / MMCSD_SECTOR_SIZE;
unsigned int sec_inc_val;
if (num_sec_val == 0)
return 1;
if (mmc_c->mode == SECTOR_MODE) {
argument = start_sec;
sec_inc_val = 1;
} else {
argument = start_sec * MMCSD_SECTOR_SIZE;
sec_inc_val = MMCSD_SECTOR_SIZE;
}
while (num_sec_val) {
err = mmc_send_cmd(MMC_CMD17, argument, resp);
if (err != 1)
return err;
err = mmc_read_data((unsigned int *) output_buf);
if (err != 1)
return err;
output_buf += (MMCSD_SECTOR_SIZE / 4);
argument += sec_inc_val;
num_sec_val--;
}
return 1;
}
unsigned char configure_mmc(mmc_card_data *mmc_card_cur)
{
unsigned char ret_val;
unsigned int argument;
unsigned int resp[4];
unsigned int trans_clk, trans_fact, trans_unit, retries = 2;
mmc_csd_reg_t Card_CSD;
unsigned char trans_speed;
ret_val = mmc_init_setup();
if (ret_val != 1)
return ret_val;
do {
ret_val = mmc_detect_card(mmc_card_cur);
retries--;
} while ((retries > 0) && (ret_val != 1));
argument = mmc_card_cur->RCA << 16;
ret_val = mmc_send_cmd(MMC_CMD9, argument, resp);
if (ret_val != 1)
return ret_val;
((unsigned int *) &Card_CSD)[3] = resp[3];
((unsigned int *) &Card_CSD)[2] = resp[2];
((unsigned int *) &Card_CSD)[1] = resp[1];
((unsigned int *) &Card_CSD)[0] = resp[0];
if (mmc_card_cur->card_type == MMC_CARD)
mmc_card_cur->version = Card_CSD.spec_vers;
trans_speed = Card_CSD.tran_speed;
ret_val = mmc_send_cmd(MMC_CMD4, MMC_DSR_DEFAULT << 16, resp);
if (ret_val != 1)
return ret_val;
trans_unit = trans_speed & MMC_CSD_TRAN_SPEED_UNIT_MASK;
trans_fact = trans_speed & MMC_CSD_TRAN_SPEED_FACTOR_MASK;
if (trans_unit > MMC_CSD_TRAN_SPEED_UNIT_100MHZ)
return 0;
if ((trans_fact < MMC_CSD_TRAN_SPEED_FACTOR_1_0) ||
(trans_fact > MMC_CSD_TRAN_SPEED_FACTOR_8_0))
return 0;
trans_unit >>= 0;
trans_fact >>= 3;
trans_clk = mmc_transspeed_val[trans_fact - 1][trans_unit] * 2;
ret_val = mmc_clock_config(CLK_MISC, trans_clk);
if (ret_val != 1)
return ret_val;
argument = mmc_card_cur->RCA << 16;
ret_val = mmc_send_cmd(MMC_CMD7_SELECT, argument, resp);
if (ret_val != 1)
return ret_val;
/* Configure the block length to 512 bytes */
argument = MMCSD_SECTOR_SIZE;
ret_val = mmc_send_cmd(MMC_CMD16, argument, resp);
if (ret_val != 1)
return ret_val;
/* get the card size in sectors */
ret_val = mmc_read_cardsize(mmc_card_cur, &Card_CSD);
if (ret_val != 1)
return ret_val;
return 1;
}
unsigned long mmc_bread(int dev_num, unsigned long blknr, lbaint_t blkcnt,
void *dst)
{
omap_mmc_read_sect(blknr, (blkcnt * MMCSD_SECTOR_SIZE), &cur_card_data,
(unsigned long *) dst);
return 1;
}
int mmc_init(int verbose)
{
if (configure_mmc(&cur_card_data) != 1)
return 1;
mmc_blk_dev.if_type = IF_TYPE_MMC;
mmc_blk_dev.part_type = PART_TYPE_DOS;
mmc_blk_dev.dev = 0;
mmc_blk_dev.lun = 0;
mmc_blk_dev.type = 0;
/* FIXME fill in the correct size (is set to 32MByte) */
mmc_blk_dev.blksz = MMCSD_SECTOR_SIZE;
mmc_blk_dev.lba = 0x10000;
mmc_blk_dev.removable = 0;
mmc_blk_dev.block_read = mmc_bread;
fat_register_device(&mmc_blk_dev, 1);
return 0;
}
int mmc_read(ulong src, uchar *dst, int size)
{
return 0;
}
int mmc_write(uchar *src, ulong dst, int size)
{
return 0;
}
int mmc2info(ulong addr)
{
return 0;
}

View File

@@ -38,6 +38,7 @@ endif
COBJS-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o
COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o
COBJS-$(CONFIG_NAND_S3C64XX) += s3c64xx.o
COBJS-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o
endif
COBJS := $(COBJS-y)

View File

@@ -0,0 +1,353 @@
/*
* (C) Copyright 2004-2008 Texas Instruments, <www.ti.com>
* Rohit Choraria <rohitkc@ti.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 <asm/errno.h>
#include <asm/arch/mem.h>
#include <asm/arch/omap_gpmc.h>
#include <linux/mtd/nand_ecc.h>
#include <nand.h>
static uint8_t cs;
static gpmc_t *gpmc_base = (gpmc_t *)GPMC_BASE;
static gpmc_csx_t *gpmc_cs_base;
static struct nand_ecclayout hw_nand_oob = GPMC_NAND_HW_ECC_LAYOUT;
/*
* omap_nand_hwcontrol - Set the address pointers corretly for the
* following address/data/command operation
*/
static void omap_nand_hwcontrol(struct mtd_info *mtd, int32_t cmd,
uint32_t ctrl)
{
register struct nand_chip *this = mtd->priv;
/*
* Point the IO_ADDR to DATA and ADDRESS registers instead
* of chip address
*/
switch (ctrl) {
case NAND_CTRL_CHANGE | NAND_CTRL_CLE:
this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_cmd;
break;
case NAND_CTRL_CHANGE | NAND_CTRL_ALE:
this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_adr;
break;
case NAND_CTRL_CHANGE | NAND_NCE:
this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_dat;
break;
}
if (cmd != NAND_CMD_NONE)
writeb(cmd, this->IO_ADDR_W);
}
/*
* omap_hwecc_init - Initialize the Hardware ECC for NAND flash in
* GPMC controller
* @mtd: MTD device structure
*
*/
static void omap_hwecc_init(struct nand_chip *chip)
{
/*
* Init ECC Control Register
* Clear all ECC | Enable Reg1
*/
writel(ECCCLEAR | ECCRESULTREG1, &gpmc_base->ecc_control);
writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL, &gpmc_base->ecc_size_config);
}
/*
* gen_true_ecc - This function will generate true ECC value, which
* can be used when correcting data read from NAND flash memory core
*
* @ecc_buf: buffer to store ecc code
*
* @return: re-formatted ECC value
*/
static uint32_t gen_true_ecc(uint8_t *ecc_buf)
{
return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) |
((ecc_buf[2] & 0x0F) << 8);
}
/*
* omap_correct_data - Compares the ecc read from nand spare area with ECC
* registers values and corrects one bit error if it has occured
* Further details can be had from OMAP TRM and the following selected links:
* http://en.wikipedia.org/wiki/Hamming_code
* http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf
*
* @mtd: MTD device structure
* @dat: page data
* @read_ecc: ecc read from nand flash
* @calc_ecc: ecc read from ECC registers
*
* @return 0 if data is OK or corrected, else returns -1
*/
static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat,
uint8_t *read_ecc, uint8_t *calc_ecc)
{
uint32_t orig_ecc, new_ecc, res, hm;
uint16_t parity_bits, byte;
uint8_t bit;
/* Regenerate the orginal ECC */
orig_ecc = gen_true_ecc(read_ecc);
new_ecc = gen_true_ecc(calc_ecc);
/* Get the XOR of real ecc */
res = orig_ecc ^ new_ecc;
if (res) {
/* Get the hamming width */
hm = hweight32(res);
/* Single bit errors can be corrected! */
if (hm == 12) {
/* Correctable data! */
parity_bits = res >> 16;
bit = (parity_bits & 0x7);
byte = (parity_bits >> 3) & 0x1FF;
/* Flip the bit to correct */
dat[byte] ^= (0x1 << bit);
} else if (hm == 1) {
printf("Error: Ecc is wrong\n");
/* ECC itself is corrupted */
return 2;
} else {
/*
* hm distance != parity pairs OR one, could mean 2 bit
* error OR potentially be on a blank page..
* orig_ecc: contains spare area data from nand flash.
* new_ecc: generated ecc while reading data area.
* Note: if the ecc = 0, all data bits from which it was
* generated are 0xFF.
* The 3 byte(24 bits) ecc is generated per 512byte
* chunk of a page. If orig_ecc(from spare area)
* is 0xFF && new_ecc(computed now from data area)=0x0,
* this means that data area is 0xFF and spare area is
* 0xFF. A sure sign of a erased page!
*/
if ((orig_ecc == 0x0FFF0FFF) && (new_ecc == 0x00000000))
return 0;
printf("Error: Bad compare! failed\n");
/* detected 2 bit error */
return -1;
}
}
return 0;
}
/*
* omap_calculate_ecc - Generate non-inverted ECC bytes.
*
* Using noninverted ECC can be considered ugly since writing a blank
* page ie. padding will clear the ECC bytes. This is no problem as
* long nobody is trying to write data on the seemingly unused page.
* Reading an erased page will produce an ECC mismatch between
* generated and read ECC bytes that has to be dealt with separately.
* E.g. if page is 0xFF (fresh erased), and if HW ECC engine within GPMC
* is used, the result of read will be 0x0 while the ECC offsets of the
* spare area will be 0xFF which will result in an ECC mismatch.
* @mtd: MTD structure
* @dat: unused
* @ecc_code: ecc_code buffer
*/
static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
uint8_t *ecc_code)
{
u_int32_t val;
/* Start Reading from HW ECC1_Result = 0x200 */
val = readl(&gpmc_base->ecc1_result);
ecc_code[0] = val & 0xFF;
ecc_code[1] = (val >> 16) & 0xFF;
ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0);
/*
* Stop reading anymore ECC vals and clear old results
* enable will be called if more reads are required
*/
writel(0x000, &gpmc_base->ecc_config);
return 0;
}
/*
* omap_enable_ecc - This function enables the hardware ecc functionality
* @mtd: MTD device structure
* @mode: Read/Write mode
*/
static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode)
{
struct nand_chip *chip = mtd->priv;
uint32_t val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1;
switch (mode) {
case NAND_ECC_READ:
case NAND_ECC_WRITE:
/* Clear the ecc result registers, select ecc reg as 1 */
writel(ECCCLEAR | ECCRESULTREG1, &gpmc_base->ecc_control);
/*
* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes
* tell all regs to generate size0 sized regs
* we just have a single ECC engine for all CS
*/
writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL,
&gpmc_base->ecc_size_config);
val = (dev_width << 7) | (cs << 1) | (0x1);
writel(val, &gpmc_base->ecc_config);
break;
default:
printf("Error: Unrecognized Mode[%d]!\n", mode);
break;
}
}
/*
* omap_nand_switch_ecc - switch the ECC operation b/w h/w ecc and s/w ecc.
* The default is to come up on s/w ecc
*
* @hardware - 1 -switch to h/w ecc, 0 - s/w ecc
*
*/
void omap_nand_switch_ecc(int32_t hardware)
{
struct nand_chip *nand;
struct mtd_info *mtd;
if (nand_curr_device < 0 ||
nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
!nand_info[nand_curr_device].name) {
printf("Error: Can't switch ecc, no devices available\n");
return;
}
mtd = &nand_info[nand_curr_device];
nand = mtd->priv;
nand->options |= NAND_OWN_BUFFERS;
/* Reset ecc interface */
nand->ecc.read_page = NULL;
nand->ecc.write_page = NULL;
nand->ecc.read_oob = NULL;
nand->ecc.write_oob = NULL;
nand->ecc.hwctl = NULL;
nand->ecc.correct = NULL;
nand->ecc.calculate = NULL;
/* Setup the ecc configurations again */
if (hardware) {
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.layout = &hw_nand_oob;
nand->ecc.size = 512;
nand->ecc.bytes = 3;
nand->ecc.hwctl = omap_enable_hwecc;
nand->ecc.correct = omap_correct_data;
nand->ecc.calculate = omap_calculate_ecc;
omap_hwecc_init(nand);
printf("HW ECC selected\n");
} else {
nand->ecc.mode = NAND_ECC_SOFT;
/* Use mtd default settings */
nand->ecc.layout = NULL;
printf("SW ECC selected\n");
}
/* Update NAND handling after ECC mode switch */
nand_scan_tail(mtd);
nand->options &= ~NAND_OWN_BUFFERS;
}
/*
* Board-specific NAND initialization. The following members of the
* argument are board-specific:
* - IO_ADDR_R: address to read the 8 I/O lines of the flash device
* - IO_ADDR_W: address to write the 8 I/O lines of the flash device
* - cmd_ctrl: hardwarespecific function for accesing control-lines
* - waitfunc: hardwarespecific function for accesing device ready/busy line
* - ecc.hwctl: function to enable (reset) hardware ecc generator
* - ecc.mode: mode of ecc, see defines
* - chip_delay: chip dependent delay for transfering data from array to
* read regs (tR)
* - options: various chip options. They can partly be set to inform
* nand_scan about special functionality. See the defines for further
* explanation
*/
int board_nand_init(struct nand_chip *nand)
{
int32_t gpmc_config = 0;
cs = 0;
/*
* xloader/Uboot's gpmc configuration would have configured GPMC for
* nand type of memory. The following logic scans and latches on to the
* first CS with NAND type memory.
* TBD: need to make this logic generic to handle multiple CS NAND
* devices.
*/
while (cs < GPMC_MAX_CS) {
/*
* Each GPMC set for a single CS is at offset 0x30
* - already remapped for us
*/
gpmc_cs_base = (gpmc_csx_t *)(GPMC_CONFIG_CS0_BASE +
(cs * GPMC_CONFIG_WIDTH));
/* Check if NAND type is set */
if ((readl(&gpmc_cs_base->config1) & 0xC00) ==
0x800) {
/* Found it!! */
break;
}
cs++;
}
if (cs >= GPMC_MAX_CS) {
printf("NAND: Unable to find NAND settings in "
"GPMC Configuration - quitting\n");
return -ENODEV;
}
gpmc_config = readl(&gpmc_base->config);
/* Disable Write protect */
gpmc_config |= 0x10;
writel(gpmc_config, &gpmc_base->config);
nand->IO_ADDR_R = (void __iomem *)&gpmc_cs_base->nand_dat;
nand->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_cmd;
nand->cmd_ctrl = omap_nand_hwcontrol;
nand->options = NAND_NO_PADDING | NAND_CACHEPRG | NAND_NO_AUTOINCR;
/* If we are 16 bit dev, our gpmc config tells us that */
if ((readl(gpmc_cs_base) & 0x3000) == 0x1000)
nand->options |= NAND_BUSWIDTH_16;
nand->chip_delay = 100;
/* Default ECC mode */
nand->ecc.mode = NAND_ECC_SOFT;
return 0;
}