MTD support on Motorola Hawk ASIC-based boards
We have the somewhat dubious honor of migrating a couple of customer's applications environments from VxWorks to TimeSys Linux on obsolete Motorola MVME2400 and MCP750 boards (the wheels of progress in certain defense and telecomm hardware venues turn *very* slowly). Everything else is working fine except we've been unable to get the MTD subsystem to successfully probe and recognize the 8 MB user flash on these boards.
The basic configuration is nothing out of the ordinary: 4 AMD 29LV160BT (2 MB) devices in x16 mode, arranged in two 32-bit wide even/odd address banks. The one major quirk in the design is that the Hawk memory controller ASIC only permits 32-bit wide aligned writes to this array (any other type of write -> /dev/null, reads are unconstrained). We're very confident that we have the registers in the Hawk set up correctly and we've spent many hours trying all the chip type/bus width/interleave permutations available in the existing MTD implementation with no success. Therefore, before we embark on any inappropriate or unnecessary major surgery we wanted to ask the community for any suggestions, ideas, or other pearls of wisdom.
Thanks for any assistance...
Signature · 3 lines
Tom Nelson Sr. Consulting Engineer, Dot4, Inc. [email protected] <mailto:[email protected]>
Quoted 5 lines · Thomas W. Nelson
The basic configuration is nothing out of the ordinary: 4 AMD 29LV160BT (2 MB) devices in x16 mode, arranged in two 32-bit wide even/odd address banks. The one major quirk in the design is that the Hawk memory controller ASIC only permits 32-bit wide aligned writes to this array (any other type of write ->
/dev/null, reads are unconstrained).
Our code doesn't currently handle the case where you have more chips than you can address in a single bus cycle, and there are different sets of chips at different bus addresses.
I've just encountered a board slightly more pathological than this -- the Dy-4 SVME/DMV-182 seems to have eight 32-bit chips, arranged interleaved so that the word at 0x0000 is the first chip, as is the word at 0x0020... You then find your CFI 'Q' at 0x200, 'R' at 0x220, etc...
There's a hack in the TimeSys kernel which was never discussed or shown here, which makes the code handle the concept that the repetition interval (the address at which you start talking to chip 0 again) may be a _multiple_ of the buswidth. They call this multiple 'extra_interleave'.
When I first looked at it I thought it was a horrifically ugly hack, but having looked for alternative approaches I don't think it's their fault -- _all_ the CFI code gives me the same kind of skullache.
Alternative suggestions for handling this are most definitely welcome.
A simple alternative in your case might be to say your buswidth is 64, and hack your write64 to do it in 2 32-bit accesses. But we need to address this properly anyway.
The patch is against an old snapshot of MTD code but should serve to illustrate the concept. The CVS Ids of the files it's based on are present in the diff.
Index: include/linux/mtd/cfi.h
===================================================================
RCS file: /home/cvs/mtd/include/linux/mtd/cfi.h,v
retrieving revision 1.38
linux/mtd/cfi.h +61 -35
@@ -104,58 +104,32 @@ typedef __u32 cfi_word;* out of range. */ -#ifdef CFIDEV_INTERLEAVE_1 -# ifdef CFIDEV_INTERLEAVE -# undef CFIDEV_INTERLEAVE# define CFIDEV_INTERLEAVE (cfi->interleave) -# else -# define CFIDEV_INTERLEAVE CFIDEV_INTERLEAVE_1 -# endif -# define cfi_interleave_is_1 () (CFIDEV_INTERLEAVE == CFIDEV_INTERLEAVE_1) + +#ifdef CFIDEV_INTERLEAVE_1 +# define cfi_interleave_is_1 () (cfi->subword_interleave == CFIDEV_INTERLEAVE_1)#else # define cfi_interleave_is_1 () (0)#endif #ifdef CFIDEV_INTERLEAVE_2 -# ifdef CFIDEV_INTERLEAVE -# undef CFIDEV_INTERLEAVE -# define CFIDEV_INTERLEAVE (cfi->interleave) -# else -# define CFIDEV_INTERLEAVE CFIDEV_INTERLEAVE_2 -# endif -# define cfi_interleave_is_2 () (CFIDEV_INTERLEAVE == CFIDEV_INTERLEAVE_2) +# define cfi_interleave_is_2 () (cfi->subword_interleave == CFIDEV_INTERLEAVE_2)#else # define cfi_interleave_is_2 () (0)#endif #ifdef CFIDEV_INTERLEAVE_4 -# ifdef CFIDEV_INTERLEAVE -# undef CFIDEV_INTERLEAVE -# define CFIDEV_INTERLEAVE (cfi->interleave) -# else -# define CFIDEV_INTERLEAVE CFIDEV_INTERLEAVE_4 -# endif -# define cfi_interleave_is_4 () (CFIDEV_INTERLEAVE == CFIDEV_INTERLEAVE_4) +# define cfi_interleave_is_4 () (cfi->subword_interleave == CFIDEV_INTERLEAVE_4)#else # define cfi_interleave_is_4 () (0)#endif #ifdef CFIDEV_INTERLEAVE_8 -# ifdef CFIDEV_INTERLEAVE -# undef CFIDEV_INTERLEAVE -# define CFIDEV_INTERLEAVE (cfi->interleave) -# else -# define CFIDEV_INTERLEAVE CFIDEV_INTERLEAVE_8 -# endif -# define cfi_interleave_is_8 () (CFIDEV_INTERLEAVE == CFIDEV_INTERLEAVE_8) +# define cfi_interleave_is_8 () (cfi->subword_interleave == CFIDEV_INTERLEAVE_8)#else # define cfi_interleave_is_8 () (0)#endif -#ifndef CFIDEV_INTERLEAVE -#error You must define at least one interleave to support! -#endif -#ifdef CFIDEV_BUSWIDTH_1 # ifdef CFIDEV_BUSWIDTH # undef CFIDEV_BUSWIDTH
@@ -320,6 +294,8 @@ struct cfi_private {__u16 cmdset;void *cmdset_priv;int interleave; +int subword_interleave;/* interleave before extra interleave is applied */ +int extra_interleave_step;/* log2(subword_interleave * device_type) */ int device_type;int cfi_mode;/* Are we a JEDEC device pretending to be CFI? */ int addr_unlock1;
@@ -436,18 +412,23 @@ static inline void cfi_write(struct map_ }/* -* Sends a CFI command to a bank of flash for the given geometry. +* Sends a CFI command to a specific chip of a bank of flash for the +* given geometry. Note that this only refers to chips specified +* using extra_interleave; sub-word and tightly connected interleaves +* always act as if cfi_send_gen_cmd_all was called. * * Returns the offset in flash where the command was written. * If prev_val is non-null, it will be set to the value at the command address, * before the command was written. */ -static inline __u32 cfi_send_gen_cmd (u_char cmd,__u32 cmd_addr,__u32 base, -struct map_info *map,struct cfi_private *cfi, +static inline __u32 cfi_send_gen_cmd_chip (u_char cmd,__u32 cmd_addr,int chip, +__u32 base,struct map_info *map, +struct cfi_private *cfi,int type,cfi_word *prev_val) {cfi_word val;__u32 addr = base +cfi_build_cmd_addr (cmd_addr,CFIDEV_INTERLEAVE , type); + addr += chip << cfi->extra_interleave_step; val =cfi_build_cmd (cmd, map, cfi);
@@ -459,6 +440,51 @@ static inline __u32 cfi_send_gen_cmd(u_creturn addr - base; } +/* +* Sends a CFI command to a bank of flash for the given geometry. +* If extra_interleave is 1, this is the same as cfi_send_gen_cmd_all. +* Otherwise, it sends a command only to the first chip (or group +* of subword-interleaved chips). This is generally only desired +* for things like queries, when one may want to use prev_val. +* +* Returns the offset in flash where the command was written. +* If prev_val is non-null, it will be set to the value at the command address, +* before the command was written. +*/ +static inline __u32 cfi_send_gen_cmd (u_char cmd,__u32 cmd_addr,__u32 base, +struct map_info *map, +struct cfi_private *cfi, +int type,cfi_word *prev_val) +{ +return cfi_send_gen_cmd_chip (cmd, cmd_addr,0 , base, + map, cfi, type, prev_val); +} + +/* +* Sends a CFI command to all chips of a bank of flash for the given geometry. +* +* Returns the offset in flash where the command was written. +* If prev_val is non-null, it will be set to the value at the command address, +* before the command was written. +*/ +static inline __u32 cfi_send_gen_cmd_all (u_char cmd,__u32 cmd_addr, +__u32 base, +struct map_info *map, +struct cfi_private *cfi, +int type) +{ +cfi_word val; +__u32 addr = base +cfi_build_cmd_addr (cmd_addr,CFIDEV_INTERLEAVE , type); +int i; + + val =cfi_build_cmd (cmd, map, cfi); + +for (i =0 ; i < map->extra_interleave; i++) +cfi_write (map, val, addr + (i << cfi->extra_interleave_step)); + +return addr - base; +} +static inline __u8 cfi_read_query (struct map_info *map,__u32 addr) {if (cfi_buswidth_is_1 ()) {
Index: include/linux/mtd/map.h
===================================================================
RCS file: /home/cvs/mtd/include/linux/mtd/map.h,v
retrieving revision 1.34
linux/mtd/map.h +13 -2
@@ -38,7 +38,6 @@ struct map_info {int buswidth;/* in octets */ -#ifdef CONFIG_MTD_COMPLEX_MAPPINGS u8 (*read8)(struct map_info *,unsigned long );u16 (*read16)(struct map_info *,unsigned long );u32 (*read32)(struct map_info *,unsigned long );
@@ -57,7 +56,7 @@ struct map_info {/* We can perhaps put in 'point' and 'unpoint' methods, if we really want to enable XIP for non-linear mappings. Not yet though. */ -#endif +/* set_vpp() must handle being reentered -- enable, enable, disable must leave it enabled. */ void (*set_vpp)(struct map_info *,int );
@@ -66,6 +65,18 @@ struct map_info {unsigned long map_priv_2;void *fldrv_priv;struct mtd_chip_driver *fldrv; + +/* Interleave between loosely connected chips -- only use this +when a command sent to one chip will not be received by all +chips in the interleave. Do not count sub-word interleaves, as +in that case all chips can be simultaneously programmed even if +the chips are loosely connected. A value of zero will be +converted into a default of one. ++ This is currently supported only for CFI cmdset 0002 +(AMD/Fujitsu). */ + +unsigned int extra_interleave; };struct mtd_chip_driver {
Index: drivers/mtd/chips/cfi_cmdset_0002.c
===================================================================
RCS file: /home/cvs/mtd/drivers/mtd/chips/cfi_cmdset_0002.c,v retrieving revision 1.94
mtd/chips/cfi_cmdset_0002.c +187 -95
554 lines not on this page · raw message
Index: drivers/mtd/chips/cfi_probe.c
===================================================================
RCS file: /home/cvs/mtd/drivers/mtd/chips/cfi_probe.c,v retrieving revision 1.73
mtd/chips/cfi_probe.c +10 -1
@@ -14,12 +14,13 @@#include <linux/errno.h> #include <linux/slab.h> #include <linux/interrupt.h> +#include <linux/bitops.h> #include <linux/mtd/map.h> #include <linux/mtd/cfi.h> #include <linux/mtd/gen_probe.h> -//#define DEBUG_CFI +#undef DEBUG_CFI#ifdef DEBUG_CFI static void print_cfi_ident (struct cfi_ident *);
@@ -53,6 +54,14 @@ static int cfi_probe_chip(struct map_inf {int i; +if (map->extra_interleave ==0 ) + map->extra_interleave =1 ; + + cfi->subword_interleave = cfi->interleave; + cfi->extra_interleave_step = +find_last_set_bit (cfi->device_type * cfi->interleave); + cfi->interleave *= map->extra_interleave; +if ((base +0 ) >= map->size) { printk(KERN_NOTICE "Probe at base[0x00](0x%08lx) past the end of the map(0x%08lx)\n",
Signature · 1 line
dwmw2
Quoted 21 lines · David Woodhouse
Our code doesn't currently handle the case where you have more chips than you can address in a single bus cycle, and there are different sets of chips at different bus addresses.
I've just encountered a board slightly more pathological than this -- the Dy-4 SVME/DMV-182 seems to have eight 32-bit chips, arranged interleaved so that the word at
0x0000is the first chip, as is the word at0x0020... You then find your CFI 'Q' at0x200, 'R' at0x220, etc...There's a hack in the TimeSys kernel which was never discussed or shown here, which makes the code handle the concept that the repetition interval (the address at which you start talking to chip 0 again) may be a _multiple_ of the buswidth. They call this multiple '
extra_interleave'.When I first looked at it I thought it was a horrifically ugly hack, but having looked for alternative approaches I don't think it's their fault -- _all_ the CFI code gives me the same kind of skullache.
Alternative suggestions for handling this are most definitely welcome.
Quoted 9 lines
A simple alternative in your case might be to say your buswidth is 64, and hack your write64 to do it in 2 32-bit accesses. But we need to address this properly anyway.
The patch is against an old snapshot of MTD code but should serve to illustrate the concept. The CVS Ids of the files it's based on are present in the diff.
Thanks for the info and insight on this, Dave. Your assistance is sincerely appreciated.
We did some preliminary work for TimeSys on the SVME182 (since we had already ported their 4.1 release to the '181), and I was rather surprised when our initial shot at the MTD code didn't work on it. Now I know why.
Just as a sanity check (since I have so little left :-), will these modifications also correctly deal with the 32-bit only write limitation during probes? Getting the chips into query mode appears to be the real challenge here; I haven't been able to perform the correct incantation to get them to respond in any way, CFI or JEDEC. BTW, despite the apparent chip labeling, there's a possibility that the devices on the Mot boards may actually be a pre-CFI version released by AMD for them. Sigh...
I'd also like to inject some personal observations about the more complex flash geometries that are appearing. On higher-end boards, such as those from Dy4, Pentek, etc., these multibank interleaved configurations are becoming very common as their customers want high performance flash access for their XIP or flash filesystem-based application environments. We are seeing an increasing number of our clients in the avionics, machine vision, and other demanding high-performance environments using this class of hardware migrating from proprietary to embedded Linux solutions for all the well-publicized reasons I won't repeat here. Therefore, it will become increasingly important for the credibility of Linux in these higher-end embedded systems to provide adequate support for these configurations.
That said, I'll certainly assist with this effort whenever I can slice off some spare cycles and we have access to this type of hardware for testing. First, I have to finish wrapping my brain around the current MTD design/implementation.
Best regards...
Signature · 3 lines
Thomas W. Nelson Sr. Consulting Engineer, Dot4, Inc. [email protected] <mailto:[email protected]>
Quoted 5 lines · Thomas W. Nelson
Just as a sanity check (since I have so little left :-), will these modifications also correctly deal with the 32-bit only write limitation during probes? Getting the chips into query mode appears to be the real challenge here; I haven't been able to perform the correct incantation to get them to respond in any way, CFI or JEDEC.
You ought to be able to fake 64-bit buswidth for the case you described. Play with it in GDB for a while, poking commands at various addresses and looking for the idents.
Quoted 6 lines
I'd also like to inject some personal observations about the more complex flash geometries that are appearing. On higher-end boards, such as those from Dy4, Pentek, etc., these multibank interleaved configurations are becoming very common as their customers want high performance flash access for their XIP or flash filesystem-based application environments.
Whereas others just use Intel chips configured for burst reads...
I suspect that the best way forward might be to turn the cfi_word typedef into a structure with an array of unsigned longs. In the common case there'll be only one of them, but for pathological cases there can be many.
Signature · 1 line
dwmw2
Call for testing: Wide flash bank support.
In reply to Thomas W. NelsonQuoted 3 lines · Thomas W. Nelson
The basic configuration is nothing out of the ordinary: 4 AMD 29LV160BT (2 MB) devices in x16 mode, arranged in two 32-bit wide even/odd address banks.
I've rewritten the way that bus accesses work, and updated the command set drivers for cmdset 0001 and 0002 (Intel and AMD resp.). The AMD one seems to work on my 32-byte 'bankwidth' test machine, with eight 32-bit chips. I haven't tested the Intel driver at all.
I'd appreciate it if as many people as possible could test the new code, even on configurations with 'normal' buswidth (in fact, _especially_ there since that was working before and mustn't break).
Signature · 1 line
dwmw2