From mboxrd@z Thu Jan 1 00:00:00 1970 From: Stefan Roese Date: Tue, 13 Nov 2007 15:45:40 +0100 Subject: [U-Boot-Users] PATCH: support JEDEC flash roms in CFI-flash framework In-Reply-To: <20071112202311.GB29521@discworld.dascon.de> References: <20071112202311.GB29521@discworld.dascon.de> Message-ID: <200711131545.40500.sr@denx.de> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de Hi Michael, Thanks. Looks very promising. If nobody objects, I would like to add this into the official repo after 1.3.0 is released. Please find some more comments below. On Monday 12 November 2007, Michael Schwingen wrote: > the following patch adds support for non-CFI flash ROMS, by hooking into > the CFI flash code and using most of its code, as recently discussed here > in the thread "Mixing CFI and non-CFI flashs". A reference to a mail thread is not good in a commit description. Please try to add all necessary infos directly in this text. > Signed-off-by: Michael Schwingen > > > diff --git a/drivers/Makefile b/drivers/Makefile > index d19588f..4a2b11e 100755 > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -29,7 +29,7 @@ LIB = $(obj)libdrivers.a > > COBJS = 3c589.o 5701rls.o ali512x.o at45.o ata_piix.o \ > ati_radeon_fb.o atmel_usart.o \ > - bcm570x.o bcm570x_autoneg.o cfb_console.o cfi_flash.o \ > + bcm570x.o bcm570x_autoneg.o cfb_console.o cfi_flash.o jedec_flash.o \ > cs8900.o ct69000.o dataflash.o dc2114x.o dm9000x.o \ > ds1722.o e1000.o eepro100.o enc28j60.o \ > fsl_i2c.o fsl_pci_init.o \ > diff --git a/drivers/cfi_flash.c b/drivers/cfi_flash.c > index 5579a1e..a437193 100644 > --- a/drivers/cfi_flash.c > +++ b/drivers/cfi_flash.c > @@ -98,10 +98,6 @@ > #define AMD_STATUS_TOGGLE 0x40 > #define AMD_STATUS_ERROR 0x20 > > -#define AMD_ADDR_ERASE_START ((info->portwidth == FLASH_CFI_8BIT) ? 0xAAA > : 0x555) -#define AMD_ADDR_START ((info->portwidth == FLASH_CFI_8BIT) ? > 0xAAA : 0x555) -#define AMD_ADDR_ACK ((info->portwidth == FLASH_CFI_8BIT) > ? 0x555 : 0x2AA) - > #define FLASH_OFFSET_MANUFACTURER_ID 0x00 > #define FLASH_OFFSET_DEVICE_ID 0x01 > #define FLASH_OFFSET_DEVICE_ID2 0x0E > @@ -331,6 +327,62 @@ ulong flash_read_long (flash_info_t * info, > flash_sect_t sect, uint offset) } > > > +#ifdef CFG_FLASH_CFI_LEGACY > +/*----------------------------------------------------------------------- > + * Call board code to request info about non-CFI flash. > + * board_flash_get_legacy needs to fill in at least: > + * info->portwidth, info->chipwidth and info->interface for Jedec probing. > + */ > +int flash_detect_legacy(ulong base, int banknum) > +{ > + flash_info_t *info = &flash_info[banknum]; > + if (board_flash_get_legacy(base, banknum, info)) { > + /* board code may have filled info completely. If not, we > + use JEDEC ID probing. */ > + if (!info->vendor) { > + int modes[] = { CFI_CMDSET_AMD_STANDARD, CFI_CMDSET_INTEL_STANDARD }; > + int i; > + > + for(i=0; i + info->vendor = modes[i]; > + info->start[0] = base; > + if (info->portwidth == FLASH_CFI_8BIT && info->interface == > FLASH_CFI_X8X16) { + info->unlock_addr1 = 0x2AAA; > + info->unlock_addr2 = 0x5555; > + } else { > + info->unlock_addr1 = 0x5555; > + info->unlock_addr2 = 0x2AAA; > + } > + flash_read_jedec_ids(info); > + debug("JEDEC PROBE: ID %x %x %x\n", info->manufacturer_id, > info->device_id, info->device_id2); + if (jedec_flash_match(info, base)) > + break; > + } > + } > + switch(info->vendor) { > + case CFI_CMDSET_INTEL_STANDARD: > + case CFI_CMDSET_INTEL_EXTENDED: > + info->cmd_reset = FLASH_CMD_RESET; > + break; > + case CFI_CMDSET_AMD_STANDARD: > + case CFI_CMDSET_AMD_EXTENDED: > + case CFI_CMDSET_AMD_LEGACY: > + info->cmd_reset = AMD_CMD_RESET; > + break; > + } > + info->flash_id = FLASH_MAN_CFI; > + return 1; > + } > + return 0; /* use CFI */ > +} > +#else > +int inline flash_detect_legacy(ulong base, int banknum) > +{ > + return 0; /* use CFI */ > +} > +#endif > + > + > /*----------------------------------------------------------------------- > */ > unsigned long flash_init (void) > @@ -345,7 +397,10 @@ unsigned long flash_init (void) > /* Init: no FLASHes known */ > for (i = 0; i < CFG_MAX_FLASH_BANKS; ++i) { > flash_info[i].flash_id = FLASH_UNKNOWN; > - size += flash_info[i].size = flash_get_size (bank_base[i], i); > + > + if (!flash_detect_legacy (bank_base[i], i)) > + flash_get_size (bank_base[i], i); > + size += flash_info[i].size; > if (flash_info[i].flash_id == FLASH_UNKNOWN) { > #ifndef CFG_FLASH_QUIET_TEST > printf ("## Unknown FLASH on Bank %d - Size = 0x%08lx = %ld MB\n", > @@ -483,11 +538,18 @@ int flash_erase (flash_info_t * info, int s_first, > int s_last) case CFI_CMDSET_AMD_STANDARD: > case CFI_CMDSET_AMD_EXTENDED: > flash_unlock_seq (info, sect); > - flash_write_cmd (info, sect, AMD_ADDR_ERASE_START, > - AMD_CMD_ERASE_START); > + flash_write_cmd (info, sect, info->unlock_addr1, AMD_CMD_ERASE_START); > flash_unlock_seq (info, sect); > flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR); > break; > +#ifdef CFG_FLASH_CFI_LEGACY > + case CFI_CMDSET_AMD_LEGACY: > + flash_unlock_seq (info, 0); > + flash_write_cmd (info, 0, info->unlock_addr1, AMD_CMD_ERASE_START); > + flash_unlock_seq (info, 0); > + flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR); > + break; > +#endif > default: > debug ("Unkown flash vendor %d\n", > info->vendor); > @@ -516,10 +578,15 @@ void flash_print_info (flash_info_t * info) > return; > } > > - printf ("CFI conformant FLASH (%d x %d)", > + printf ("%s FLASH (%d x %d)", > + info->name, > (info->portwidth << 3), (info->chipwidth << 3)); > - printf (" Size: %ld MB in %d Sectors\n", > - info->size >> 20, info->sector_count); > + if (info->size < 1024*1024) > + printf (" Size: %ld kB in %d Sectors\n", > + info->size >> 10, info->sector_count); > + else > + printf (" Size: %ld MB in %d Sectors\n", > + info->size >> 20, info->sector_count); > printf (" "); > switch (info->vendor) { > case CFI_CMDSET_INTEL_STANDARD: > @@ -534,6 +601,11 @@ void flash_print_info (flash_info_t * info) > case CFI_CMDSET_AMD_EXTENDED: > printf ("AMD Extended"); > break; > +#ifdef CFG_FLASH_CFI_LEGACY > + case CFI_CMDSET_AMD_LEGACY: > + printf ("AMD Legacy"); > + break; > +#endif > default: > printf ("Unknown (%d)", info->vendor); > break; > @@ -777,6 +849,9 @@ static int flash_is_busy (flash_info_t * info, > flash_sect_t sect) break; > case CFI_CMDSET_AMD_STANDARD: > case CFI_CMDSET_AMD_EXTENDED: > +#ifdef CFG_FLASH_CFI_LEGACY > + case CFI_CMDSET_AMD_LEGACY: > +#endif > retval = flash_toggle (info, sect, 0, AMD_STATUS_TOGGLE); > break; > default: > @@ -967,8 +1042,8 @@ static void flash_write_cmd (flash_info_t * info, > flash_sect_t sect, uint offset > > static void flash_unlock_seq (flash_info_t * info, flash_sect_t sect) > { > - flash_write_cmd (info, sect, AMD_ADDR_START, AMD_CMD_UNLOCK_START); > - flash_write_cmd (info, sect, AMD_ADDR_ACK, AMD_CMD_UNLOCK_ACK); > + flash_write_cmd (info, sect, info->unlock_addr1, AMD_CMD_UNLOCK_START); > + flash_write_cmd (info, sect, info->unlock_addr2, AMD_CMD_UNLOCK_ACK); > } > > /*----------------------------------------------------------------------- > @@ -1105,7 +1180,7 @@ static void flash_read_jedec_ids (flash_info_t * > info) case CFI_CMDSET_AMD_EXTENDED: > flash_write_cmd(info, 0, 0, AMD_CMD_RESET); > flash_unlock_seq(info, 0); > - flash_write_cmd(info, 0, AMD_ADDR_START, FLASH_CMD_READ_ID); > + flash_write_cmd(info, 0, info->unlock_addr1, FLASH_CMD_READ_ID); > udelay(1000); /* some flash are slow to respond */ > info->manufacturer_id = flash_read_uchar (info, > FLASH_OFFSET_MANUFACTURER_ID); > @@ -1156,6 +1231,10 @@ static int flash_detect_cfi (flash_info_t * info) > debug ("port %d bits chip %d bits\n", > info->portwidth << CFI_FLASH_SHIFT_WIDTH, > info->chipwidth << CFI_FLASH_SHIFT_WIDTH); > + /* this probably only works if info->interface == FLASH_CFI_X8X16 */ > + info->unlock_addr1 = (info->portwidth == FLASH_CFI_8BIT) ? 0xAAA : > 0x555; + info->unlock_addr2 = (info->portwidth == FLASH_CFI_8BIT) ? > 0x555 : 0x2AA; + info->name = "CFI conformant"; > return 1; > } > } > @@ -1282,6 +1361,10 @@ ulong flash_get_size (ulong base, int banknum) > debug ("erase_region_count = %d erase_region_size = %d\n", > erase_region_count, erase_region_size); > for (j = 0; j < erase_region_count; j++) { > + if (sect_cnt >= CFG_MAX_FLASH_SECT) { > + printf("ERROR: too many flash sectors\n"); > + break; > + } > info->start[sect_cnt] = sector; > sector += (erase_region_size * size_ratio); > > @@ -1384,8 +1467,11 @@ static int flash_write_cfiword (flash_info_t * info, > ulong dest, break; > case CFI_CMDSET_AMD_EXTENDED: > case CFI_CMDSET_AMD_STANDARD: > +#ifdef CFG_FLASH_CFI_LEGACY > + case CFI_CMDSET_AMD_LEGACY: > +#endif > flash_unlock_seq (info, 0); > - flash_write_cmd (info, 0, AMD_ADDR_START, AMD_CMD_WRITE); > + flash_write_cmd (info, 0, info->unlock_addr1, AMD_CMD_WRITE); > break; > } > > diff --git a/drivers/jedec_flash.c b/drivers/jedec_flash.c > new file mode 100644 > index 0000000..10ffb9e > --- /dev/null > +++ b/drivers/jedec_flash.c > @@ -0,0 +1,315 @@ > +/* > + * (C) Copyright 2007 > + * Michael Schwingen, > + * > + * based in great part on jedec_probe.c from linux kernel: > + * (C) 2000 Red Hat. GPL'd. > + * Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot 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 > + * > + */ > + > +/* The DEBUG define must be before common to enable debugging */ > +/*#define DEBUG*/ > + > +#include > +#include > +#include > +#include > +#include > + > +#if defined CFG_FLASH_CFI_DRIVER && defined CFG_FLASH_CFI_LEGACY > + > +#define P_ID_AMD_STD CFI_CMDSET_AMD_LEGACY > + > +/* Manufacturers */ > +#define MANUFACTURER_AMD 0x0001 > +#define MANUFACTURER_SST 0x00BF > + > +/* AMD */ > +#define AM29DL800BB 0x22C8 > +#define AM29DL800BT 0x224A > + > +#define AM29F800BB 0x2258 > +#define AM29F800BT 0x22D6 > +#define AM29LV400BB 0x22BA > +#define AM29LV400BT 0x22B9 > +#define AM29LV800BB 0x225B > +#define AM29LV800BT 0x22DA > +#define AM29LV160DT 0x22C4 > +#define AM29LV160DB 0x2249 > +#define AM29F017D 0x003D > +#define AM29F016D 0x00AD > +#define AM29F080 0x00D5 > +#define AM29F040 0x00A4 > +#define AM29LV040B 0x004F > +#define AM29F032B 0x0041 > +#define AM29F002T 0x00B0 > + > +/* SST */ > +#define SST39LF800 0x2781 > +#define SST39LF160 0x2782 > +#define SST39VF1601 0x234b > +#define SST39LF512 0x00D4 > +#define SST39LF010 0x00D5 > +#define SST39LF020 0x00D6 > +#define SST39LF040 0x00D7 > +#define SST39SF010A 0x00B5 > +#define SST39SF020A 0x00B6 > + > + > +/* > + * Unlock address sets for AMD command sets. > + * Intel command sets use the MTD_UADDR_UNNECESSARY. > + * Each identifier, except MTD_UADDR_UNNECESSARY, and > + * MTD_UADDR_NO_SUPPORT must be defined below in unlock_addrs[]. > + * MTD_UADDR_NOT_SUPPORTED must be 0 so that structure > + * initialization need not require initializing all of the > + * unlock addresses for all bit widths. > + */ > +enum uaddr { > + MTD_UADDR_NOT_SUPPORTED = 0, /* data width not supported */ > + MTD_UADDR_0x0555_0x02AA, > + MTD_UADDR_0x0555_0x0AAA, > + MTD_UADDR_0x5555_0x2AAA, > + MTD_UADDR_0x0AAA_0x0555, > + MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */ > + MTD_UADDR_UNNECESSARY, /* Does not require any address */ > +}; > + > + > +struct unlock_addr { > + u32 addr1; > + u32 addr2; > +}; > + > + > +/* > + * I don't like the fact that the first entry in unlock_addrs[] > + * exists, but is for MTD_UADDR_NOT_SUPPORTED - and, therefore, > + * should not be used. The problem is that structures with > + * initializers have extra fields initialized to 0. It is _very_ > + * desireable to have the unlock address entries for unsupported > + * data widths automatically initialized - that means that > + * MTD_UADDR_NOT_SUPPORTED must be 0 and the first entry here > + * must go unused. > + */ > +static const struct unlock_addr unlock_addrs[] = { > + [MTD_UADDR_NOT_SUPPORTED] = { > + .addr1 = 0xffff, > + .addr2 = 0xffff > + }, > + > + [MTD_UADDR_0x0555_0x02AA] = { > + .addr1 = 0x0555, > + .addr2 = 0x02aa > + }, > + > + [MTD_UADDR_0x0555_0x0AAA] = { > + .addr1 = 0x0555, > + .addr2 = 0x0aaa > + }, > + > + [MTD_UADDR_0x5555_0x2AAA] = { > + .addr1 = 0x5555, > + .addr2 = 0x2aaa > + }, > + > + [MTD_UADDR_0x0AAA_0x0555] = { > + .addr1 = 0x0AAA, > + .addr2 = 0x0555 > + }, > + > + [MTD_UADDR_DONT_CARE] = { > + .addr1 = 0x0000, /* Doesn't matter which address */ > + .addr2 = 0x0000 /* is used - must be last entry */ > + }, > + > + [MTD_UADDR_UNNECESSARY] = { > + .addr1 = 0x0000, > + .addr2 = 0x0000 > + } > +}; > + > + > +struct amd_flash_info { > + const __u16 mfr_id; > + const __u16 dev_id; > + const char *name; > + const int DevSize; > + const int NumEraseRegions; > + const int CmdSet; > + const __u8 uaddr[4]; /* unlock addrs for 8, 16, 32, 64 */ > + const ulong regions[6]; > +}; > + > +#define ERASEINFO(size,blocks) (size<<8)|(blocks-1) > + > +#define SIZE_64KiB 16 > +#define SIZE_128KiB 17 > +#define SIZE_256KiB 18 > +#define SIZE_512KiB 19 > +#define SIZE_1MiB 20 > +#define SIZE_2MiB 21 > +#define SIZE_4MiB 22 > +#define SIZE_8MiB 23 > + > +static const struct amd_flash_info jedec_table[] = { > +#ifdef CFG_FLASH_LEGACY_256Kx8 > + { > + .mfr_id = MANUFACTURER_SST, > + .dev_id = SST39LF020, > + .name = "SST 39LF020", > + .uaddr = { > + [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ > + }, > + .DevSize = SIZE_256KiB, > + .CmdSet = P_ID_AMD_STD, > + .NumEraseRegions= 1, > + .regions = { > + ERASEINFO(0x01000,64), > + } > + }, > +#endif > +#ifdef CFG_FLASH_LEGACY_512Kx8 > + { > + .mfr_id = MANUFACTURER_AMD, > + .dev_id = AM29LV040B, > + .name = "AMD AM29LV040B", > + .uaddr = { > + [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ > + }, > + .DevSize = SIZE_512KiB, > + .CmdSet = P_ID_AMD_STD, > + .NumEraseRegions= 1, > + .regions = { > + ERASEINFO(0x10000,8), > + } > + }, > + { > + .mfr_id = MANUFACTURER_SST, > + .dev_id = SST39LF040, > + .name = "SST 39LF040", > + .uaddr = { > + [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ > + }, > + .DevSize = SIZE_512KiB, > + .CmdSet = P_ID_AMD_STD, > + .NumEraseRegions= 1, > + .regions = { > + ERASEINFO(0x01000,128), > + } > + }, > +#endif > +}; > + > + > +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) > + > + > +static inline void fill_info(flash_info_t *info, const struct > amd_flash_info *jedec_entry, ulong base) +{ > + int i,j; > + int sect_cnt; > + int size_ratio; > + int total_size; > + enum uaddr uaddr_idx; > + > + size_ratio = info->portwidth / info->chipwidth; > + > + debug("Found JEDEC Flash: %s\n", jedec_entry->name); > + info->vendor = jedec_entry->CmdSet; > + /* Todo: do we need device-specific timeouts? */ > + info->erase_blk_tout = 30000; > + info->buffer_write_tout = 1000; > + info->write_tout = 100; > + info->name = jedec_entry->name; How does the Linux driver handle this timeout? Is it device specific there? > + /* copy unlock addresses from device table to CFI info struct. This > + is just here because the addresses are in the table anyway - if > + the flash is not detected due to wrong unlock addresses, > + flash_detect_legacy would have to try all of them before we even > + get here. */ > + switch(info->chipwidth) { > + case FLASH_CFI_8BIT: > + uaddr_idx = jedec_entry->uaddr[0]; > + break; > + case FLASH_CFI_16BIT: > + uaddr_idx = jedec_entry->uaddr[1]; > + break; > + case FLASH_CFI_32BIT: > + uaddr_idx = jedec_entry->uaddr[2]; > + break; > + default: > + uaddr_idx = MTD_UADDR_NOT_SUPPORTED; > + break; > + } > + > + debug("unlock address index %d\n", uaddr_idx); > + info->unlock_addr1 = unlock_addrs[uaddr_idx].addr1; > + info->unlock_addr2 = unlock_addrs[uaddr_idx].addr2; > + debug("unlock addresses are 0x%x/0x%x\n", info->unlock_addr1, > info->unlock_addr2); + > + sect_cnt = 0; > + total_size = 0; > + for (i = 0; i < jedec_entry->NumEraseRegions; i++) { > + ulong erase_region_size = jedec_entry->regions[i] >> 8; > + ulong erase_region_count = (jedec_entry->regions[i] & 0xff) + 1; > + > + total_size += erase_region_size * erase_region_count; > + debug ("erase_region_count = %d erase_region_size = %d\n", > + erase_region_count, erase_region_size); > + for (j = 0; j < erase_region_count; j++) { > + if (sect_cnt >= CFG_MAX_FLASH_SECT) { > + printf("ERROR: too many flash sectors\n"); > + break; > + } > + info->start[sect_cnt] = base; > + base += (erase_region_size * size_ratio); > + sect_cnt++; > + } > + } > + info->sector_count = sect_cnt; > + info->size = total_size * size_ratio; > +} > + > +/*----------------------------------------------------------------------- > + * match jedec ids against table. If a match is found, fill flash_info > entry + */ > +int jedec_flash_match(flash_info_t *info, ulong base) > +{ > + int ret = 0; > + int i; > + ulong mask = 0xFFFF; > + if (info->chipwidth == 1) > + mask = 0xFF; > + > + for (i = 0; i < ARRAY_SIZE(jedec_table); i++) { > + if ( (jedec_table[i].mfr_id & mask) == (info->manufacturer_id & mask) && Please remove the space between the two "(": if ((jedec_table[i].mfr_id & mask) == (info->manufacturer_id & mask) && > + (jedec_table[i].dev_id & mask) == (info->device_id & mask)) { > + fill_info(info, &jedec_table[i], base); > + ret = 1; > + break; > + } > + } > + return ret; > +} > + > +#endif /* defined CFG_FLASH_CFI_DRIVER && defined CFG_FLASH_CFI_LEGACY */ > diff --git a/include/flash.h b/include/flash.h > index b0bf733..bbd3b98 100644 > --- a/include/flash.h > +++ b/include/flash.h > @@ -52,6 +52,9 @@ typedef struct { > ushort ext_addr; /* extended query table address */ > ushort cfi_version; /* cfi version */ > ushort cfi_offset; /* offset for cfi query */ > + ulong unlock_addr1; /* unlock address 1 for AMD flash roms */ > + ulong unlock_addr2; /* unlock address 2 for AMD flash roms */ > + const char *name; /* human-readable name */ > #endif > } flash_info_t; > > @@ -101,6 +104,12 @@ extern void flash_read_user_serial(flash_info_t * > info, void * buffer, int offse extern void > flash_read_factory_serial(flash_info_t * info, void * buffer, int offset, > int len); #endif /* CFG_FLASH_PROTECTION */ > > +#ifdef CFG_FLASH_CFI_LEGACY > +extern ulong board_flash_get_legacy(ulong base, int banknum, flash_info_t > *info); +extern int jedec_flash_match(flash_info_t *info, ulong base); > +#define CFI_CMDSET_AMD_LEGACY 0xFFF0 > +#endif > + > /*----------------------------------------------------------------------- > * return codes from flash_write(): > */ > > ------------------------------------------------------------------------- > This SF.net email is sponsored by: Splunk Inc. > Still grepping through log files to find problems? Stop. > Now Search log events and configuration files using AJAX and a browser. > Download your FREE copy of Splunk now >> http://get.splunk.com/ > _______________________________________________ > U-Boot-Users mailing list > U-Boot-Users at lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/u-boot-users Thanks for all your effort. Best regards, Stefan ===================================================================== DENX Software Engineering GmbH, MD: Wolfgang Denk & Detlev Zundel HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany Phone: +49-8142-66989-0 Fax: +49-8142-66989-80 Email: office at denx.de =====================================================================