[brcm-2.4] fix serial flash support (#6442)
authorjow <jow@3c298f89-4303-0410-b956-a3cf2f4a3e73>
Sat, 16 Jan 2010 15:11:52 +0000 (15:11 +0000)
committerjow <jow@3c298f89-4303-0410-b956-a3cf2f4a3e73>
Sat, 16 Jan 2010 15:11:52 +0000 (15:11 +0000)
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@19171 3c298f89-4303-0410-b956-a3cf2f4a3e73

target/linux/brcm-2.4/files/arch/mips/bcm947xx/include/bcmutils.h [new file with mode: 0644]
target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c [new file with mode: 0644]
target/linux/brcm-2.4/files/drivers/mtd/devices/sflash.c
target/linux/brcm-2.4/patches/004-flash.patch

diff --git a/target/linux/brcm-2.4/files/arch/mips/bcm947xx/include/bcmutils.h b/target/linux/brcm-2.4/files/arch/mips/bcm947xx/include/bcmutils.h
new file mode 100644 (file)
index 0000000..4c4986c
--- /dev/null
@@ -0,0 +1,589 @@
+/*
+ * Misc useful os-independent macros and functions.
+ *
+ * Copyright 2007, Broadcom Corporation
+ * All Rights Reserved.
+ * 
+ * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
+ * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
+ * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
+ * $Id$
+ */
+
+#ifndef        _bcmutils_h_
+#define        _bcmutils_h_
+
+/* ctype replacement */
+#define _BCM_U 0x01    /* upper */
+#define _BCM_L 0x02    /* lower */
+#define _BCM_D 0x04    /* digit */
+#define _BCM_C 0x08    /* cntrl */
+#define _BCM_P 0x10    /* punct */
+#define _BCM_S 0x20    /* white space (space/lf/tab) */
+#define _BCM_X 0x40    /* hex digit */
+#define _BCM_SP        0x80    /* hard space (0x20) */
+
+extern const unsigned char bcm_ctype[];
+#define bcm_ismask(x)  (bcm_ctype[(int)(unsigned char)(x)])
+
+#define bcm_isalnum(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L|_BCM_D)) != 0)
+#define bcm_isalpha(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L)) != 0)
+#define bcm_iscntrl(c) ((bcm_ismask(c)&(_BCM_C)) != 0)
+#define bcm_isdigit(c) ((bcm_ismask(c)&(_BCM_D)) != 0)
+#define bcm_isgraph(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D)) != 0)
+#define bcm_islower(c) ((bcm_ismask(c)&(_BCM_L)) != 0)
+#define bcm_isprint(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D|_BCM_SP)) != 0)
+#define bcm_ispunct(c) ((bcm_ismask(c)&(_BCM_P)) != 0)
+#define bcm_isspace(c) ((bcm_ismask(c)&(_BCM_S)) != 0)
+#define bcm_isupper(c) ((bcm_ismask(c)&(_BCM_U)) != 0)
+#define bcm_isxdigit(c)        ((bcm_ismask(c)&(_BCM_D|_BCM_X)) != 0)
+#define bcm_tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c))
+#define bcm_toupper(c) (bcm_islower((c)) ? ((c) + 'A' - 'a') : (c))
+
+/* Buffer structure for collecting string-formatted data 
+* using bcm_bprintf() API.
+* Use bcm_binit() to initialize before use
+*/
+
+struct bcmstrbuf {
+       char *buf;      /* pointer to current position in origbuf */
+       unsigned int size;      /* current (residual) size in bytes */
+       char *origbuf;  /* unmodified pointer to orignal buffer */
+       unsigned int origsize;  /* unmodified orignal buffer size in bytes */
+};
+
+/* ** driver-only section ** */
+#ifdef BCMDRIVER
+#include <osl.h>
+
+#define GPIO_PIN_NOTDEFINED    0x20    /* Pin not defined */
+
+/*
+ * Spin at most 'us' microseconds while 'exp' is true.
+ * Caller should explicitly test 'exp' when this completes
+ * and take appropriate error action if 'exp' is still true.
+ */
+#define SPINWAIT(exp, us) { \
+       uint countdown = (us) + 9; \
+       while ((exp) && (countdown >= 10)) {\
+               OSL_DELAY(10); \
+               countdown -= 10; \
+       } \
+}
+
+
+/* osl multi-precedence packet queue */
+#ifndef PKTQ_LEN_DEFAULT
+#define PKTQ_LEN_DEFAULT        128    /* Max 128 packets */
+#endif
+#ifndef PKTQ_MAX_PREC
+#define PKTQ_MAX_PREC           16     /* Maximum precedence levels */
+#endif
+
+typedef struct pktq_prec {
+       void *head;     /* first packet to dequeue */
+       void *tail;     /* last packet to dequeue */
+       uint16 len;     /* number of queued packets */
+       uint16 max;     /* maximum number of queued packets */
+} pktq_prec_t;
+
+
+/* multi-priority pkt queue */
+struct pktq {
+       uint16 num_prec;        /* number of precedences in use */
+       uint16 hi_prec;         /* rapid dequeue hint (>= highest non-empty prec) */
+       uint16 max;             /* total max packets */
+       uint16 len;             /* total number of packets */
+       /* q array must be last since # of elements can be either PKTQ_MAX_PREC or 1 */
+       struct pktq_prec q[PKTQ_MAX_PREC];
+};
+
+/* simple, non-priority pkt queue */
+struct spktq {
+       uint16 num_prec;        /* number of precedences in use (always 1) */
+       uint16 hi_prec;         /* rapid dequeue hint (>= highest non-empty prec) */
+       uint16 max;             /* total max packets */
+       uint16 len;             /* total number of packets */
+       /* q array must be last since # of elements can be either PKTQ_MAX_PREC or 1 */
+       struct pktq_prec q[1];
+};
+
+#define PKTQ_PREC_ITER(pq, prec)        for (prec = (pq)->num_prec - 1; prec >= 0; prec--)
+
+/* forward definition of ether_addr structure used by some function prototypes */
+
+struct ether_addr;
+
+/* operations on a specific precedence in packet queue */
+
+#define pktq_psetmax(pq, prec, _max)    ((pq)->q[prec].max = (_max))
+#define pktq_plen(pq, prec)             ((pq)->q[prec].len)
+#define pktq_pavail(pq, prec)           ((pq)->q[prec].max - (pq)->q[prec].len)
+#define pktq_pfull(pq, prec)            ((pq)->q[prec].len >= (pq)->q[prec].max)
+#define pktq_pempty(pq, prec)           ((pq)->q[prec].len == 0)
+
+#define pktq_ppeek(pq, prec)            ((pq)->q[prec].head)
+#define pktq_ppeek_tail(pq, prec)       ((pq)->q[prec].tail)
+
+extern void *pktq_penq(struct pktq *pq, int prec, void *p);
+extern void *pktq_penq_head(struct pktq *pq, int prec, void *p);
+extern void *pktq_pdeq(struct pktq *pq, int prec);
+extern void *pktq_pdeq_tail(struct pktq *pq, int prec);
+/* Empty the queue at particular precedence level */
+extern void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, bool dir);
+/* Remove a specified packet from its queue */
+extern bool pktq_pdel(struct pktq *pq, void *p, int prec);
+
+/* operations on a set of precedences in packet queue */
+
+extern int pktq_mlen(struct pktq *pq, uint prec_bmp);
+extern void *pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out);
+
+/* operations on packet queue as a whole */
+
+#define pktq_len(pq)                    ((int)(pq)->len)
+#define pktq_max(pq)                    ((int)(pq)->max)
+#define pktq_avail(pq)                  ((int)((pq)->max - (pq)->len))
+#define pktq_full(pq)                   ((pq)->len >= (pq)->max)
+#define pktq_empty(pq)                  ((pq)->len == 0)
+
+/* operations for single precedence queues */
+#define pktenq(pq, p)          pktq_penq(((struct pktq *)pq), 0, (p))
+#define pktenq_head(pq, p)     pktq_penq_head(((struct pktq *)pq), 0, (p))
+#define pktdeq(pq)             pktq_pdeq(((struct pktq *)pq), 0)
+#define pktdeq_tail(pq)                pktq_pdeq_tail(((struct pktq *)pq), 0)
+#define pktqinit(pq, len) pktq_init(((struct pktq *)pq), 1, len)
+
+extern void pktq_init(struct pktq *pq, int num_prec, int max_len);
+/* prec_out may be NULL if caller is not interested in return value */
+extern void *pktq_deq(struct pktq *pq, int *prec_out);
+extern void *pktq_deq_tail(struct pktq *pq, int *prec_out);
+extern void *pktq_peek(struct pktq *pq, int *prec_out);
+extern void *pktq_peek_tail(struct pktq *pq, int *prec_out);
+extern void pktq_flush(osl_t *osh, struct pktq *pq, bool dir); /* Empty the entire queue */
+extern int pktq_setmax(struct pktq *pq, int max_len);
+
+/* externs */
+/* packet */
+extern uint pktcopy(osl_t *osh, void *p, uint offset, int len, uchar *buf);
+extern uint pkttotlen(osl_t *osh, void *p);
+extern void *pktlast(osl_t *osh, void *p);
+
+/* Get priority from a packet and pass it back in scb (or equiv) */
+extern uint pktsetprio(void *pkt, bool update_vtag);
+#define        PKTPRIO_VDSCP   0x100           /* DSCP prio found after VLAN tag */
+#define        PKTPRIO_VLAN    0x200           /* VLAN prio found */
+#define        PKTPRIO_UPD     0x400           /* DSCP used to update VLAN prio */
+#define        PKTPRIO_DSCP    0x800           /* DSCP prio found */
+
+/* string */
+extern int BCMROMFN(bcm_atoi)(char *s);
+extern ulong BCMROMFN(bcm_strtoul)(char *cp, char **endp, uint base);
+extern char *BCMROMFN(bcmstrstr)(char *haystack, char *needle);
+extern char *BCMROMFN(bcmstrcat)(char *dest, const char *src);
+extern char *BCMROMFN(bcmstrncat)(char *dest, const char *src, uint size);
+extern ulong wchar2ascii(char *abuf, ushort *wbuf, ushort wbuflen, ulong abuflen);
+/* ethernet address */
+extern char *bcm_ether_ntoa(struct ether_addr *ea, char *buf);
+extern int BCMROMFN(bcm_ether_atoe)(char *p, struct ether_addr *ea);
+
+/* ip address */
+struct ipv4_addr;
+extern char *bcm_ip_ntoa(struct ipv4_addr *ia, char *buf);
+
+/* delay */
+extern void bcm_mdelay(uint ms);
+/* variable access */
+extern char *getvar(char *vars, const char *name);
+extern int getintvar(char *vars, const char *name);
+extern uint getgpiopin(char *vars, char *pin_name, uint def_pin);
+#ifdef BCMPERFSTATS
+extern void bcm_perf_enable(void);
+extern void bcmstats(char *fmt);
+extern void bcmlog(char *fmt, uint a1, uint a2);
+extern void bcmdumplog(char *buf, int size);
+extern int bcmdumplogent(char *buf, uint idx);
+#else
+#define bcm_perf_enable()
+#define bcmstats(fmt)
+#define        bcmlog(fmt, a1, a2)
+#define        bcmdumplog(buf, size)   *buf = '\0'
+#define        bcmdumplogent(buf, idx) -1
+#endif /* BCMPERFSTATS */
+extern char *bcm_nvram_vars(uint *length);
+extern int bcm_nvram_cache(void *sbh);
+
+/* Support for sharing code across in-driver iovar implementations.
+ * The intent is that a driver use this structure to map iovar names
+ * to its (private) iovar identifiers, and the lookup function to
+ * find the entry.  Macros are provided to map ids and get/set actions
+ * into a single number space for a switch statement.
+ */
+
+/* iovar structure */
+typedef struct bcm_iovar {
+       const char *name;       /* name for lookup and display */
+       uint16 varid;           /* id for switch */
+       uint16 flags;           /* driver-specific flag bits */
+       uint16 type;            /* base type of argument */
+       uint16 minlen;          /* min length for buffer vars */
+} bcm_iovar_t;
+
+/* varid definitions are per-driver, may use these get/set bits */
+
+/* IOVar action bits for id mapping */
+#define IOV_GET 0 /* Get an iovar */
+#define IOV_SET 1 /* Set an iovar */
+
+/* Varid to actionid mapping */
+#define IOV_GVAL(id)           ((id)*2)
+#define IOV_SVAL(id)           (((id)*2)+IOV_SET)
+#define IOV_ISSET(actionid)    ((actionid & IOV_SET) == IOV_SET)
+
+/* flags are per-driver based on driver attributes */
+
+extern const bcm_iovar_t *bcm_iovar_lookup(const bcm_iovar_t *table, const char *name);
+extern int bcm_iovar_lencheck(const bcm_iovar_t *table, void *arg, int len, bool set);
+
+#endif /* BCMDRIVER */
+
+/* Base type definitions */
+#define IOVT_VOID      0       /* no value (implictly set only) */
+#define IOVT_BOOL      1       /* any value ok (zero/nonzero) */
+#define IOVT_INT8      2       /* integer values are range-checked */
+#define IOVT_UINT8     3       /* unsigned int 8 bits */
+#define IOVT_INT16     4       /* int 16 bits */
+#define IOVT_UINT16    5       /* unsigned int 16 bits */
+#define IOVT_INT32     6       /* int 32 bits */
+#define IOVT_UINT32    7       /* unsigned int 32 bits */
+#define IOVT_BUFFER    8       /* buffer is size-checked as per minlen */
+#define BCM_IOVT_VALID(type) (((unsigned int)(type)) <= IOVT_BUFFER)
+
+/* Initializer for IOV type strings */
+#define BCM_IOV_TYPE_INIT { \
+       "void", \
+       "bool", \
+       "int8", \
+       "uint8", \
+       "int16", \
+       "uint16", \
+       "int32", \
+       "uint32", \
+       "buffer", \
+       "" }
+
+#define BCM_IOVT_IS_INT(type) (\
+       (type == IOVT_BOOL) || \
+       (type == IOVT_INT8) || \
+       (type == IOVT_UINT8) || \
+       (type == IOVT_INT16) || \
+       (type == IOVT_UINT16) || \
+       (type == IOVT_INT32) || \
+       (type == IOVT_UINT32))
+
+/* ** driver/apps-shared section ** */
+
+#define BCME_STRLEN            64      /* Max string length for BCM errors */
+#define VALID_BCMERROR(e)  ((e <= 0) && (e >= BCME_LAST))
+
+
+/*
+ * error codes could be added but the defined ones shouldn't be changed/deleted
+ * these error codes are exposed to the user code
+ * when ever a new error code is added to this list
+ * please update errorstring table with the related error string and
+ * update osl files with os specific errorcode map
+*/
+
+#define BCME_OK                                0       /* Success */
+#define BCME_ERROR                     -1      /* Error generic */
+#define BCME_BADARG                    -2      /* Bad Argument */
+#define BCME_BADOPTION                 -3      /* Bad option */
+#define BCME_NOTUP                     -4      /* Not up */
+#define BCME_NOTDOWN                   -5      /* Not down */
+#define BCME_NOTAP                     -6      /* Not AP */
+#define BCME_NOTSTA                    -7      /* Not STA  */
+#define BCME_BADKEYIDX                 -8      /* BAD Key Index */
+#define BCME_RADIOOFF                  -9      /* Radio Off */
+#define BCME_NOTBANDLOCKED             -10     /* Not  band locked */
+#define BCME_NOCLK                     -11     /* No Clock */
+#define BCME_BADRATESET                        -12     /* BAD Rate valueset */
+#define BCME_BADBAND                   -13     /* BAD Band */
+#define BCME_BUFTOOSHORT               -14     /* Buffer too short */
+#define BCME_BUFTOOLONG                        -15     /* Buffer too long */
+#define BCME_BUSY                      -16     /* Busy */
+#define BCME_NOTASSOCIATED             -17     /* Not Associated */
+#define BCME_BADSSIDLEN                        -18     /* Bad SSID len */
+#define BCME_OUTOFRANGECHAN            -19     /* Out of Range Channel */
+#define BCME_BADCHAN                   -20     /* Bad Channel */
+#define BCME_BADADDR                   -21     /* Bad Address */
+#define BCME_NORESOURCE                        -22     /* Not Enough Resources */
+#define BCME_UNSUPPORTED               -23     /* Unsupported */
+#define BCME_BADLEN                    -24     /* Bad length */
+#define BCME_NOTREADY                  -25     /* Not Ready */
+#define BCME_EPERM                     -26     /* Not Permitted */
+#define BCME_NOMEM                     -27     /* No Memory */
+#define BCME_ASSOCIATED                        -28     /* Associated */
+#define BCME_RANGE                     -29     /* Not In Range */
+#define BCME_NOTFOUND                  -30     /* Not Found */
+#define BCME_WME_NOT_ENABLED           -31     /* WME Not Enabled */
+#define BCME_TSPEC_NOTFOUND            -32     /* TSPEC Not Found */
+#define BCME_ACM_NOTSUPPORTED          -33     /* ACM Not Supported */
+#define BCME_NOT_WME_ASSOCIATION       -34     /* Not WME Association */
+#define BCME_SDIO_ERROR                        -35     /* SDIO Bus Error */
+#define BCME_DONGLE_DOWN               -36     /* Dongle Not Accessible */
+#define BCME_VERSION                   -37 /* Incorrect version */
+#define BCME_LAST                      BCME_VERSION
+
+/* These are collection of BCME Error strings */
+#define BCMERRSTRINGTABLE {            \
+       "OK",                           \
+       "Undefined error",              \
+       "Bad Argument",                 \
+       "Bad Option",                   \
+       "Not up",                       \
+       "Not down",                     \
+       "Not AP",                       \
+       "Not STA",                      \
+       "Bad Key Index",                \
+       "Radio Off",                    \
+       "Not band locked",              \
+       "No clock",                     \
+       "Bad Rate valueset",            \
+       "Bad Band",                     \
+       "Buffer too short",             \
+       "Buffer too long",              \
+       "Busy",                         \
+       "Not Associated",               \
+       "Bad SSID len",                 \
+       "Out of Range Channel",         \
+       "Bad Channel",                  \
+       "Bad Address",                  \
+       "Not Enough Resources",         \
+       "Unsupported",                  \
+       "Bad length",                   \
+       "Not Ready",                    \
+       "Not Permitted",                \
+       "No Memory",                    \
+       "Associated",                   \
+       "Not In Range",                 \
+       "Not Found",                    \
+       "WME Not Enabled",              \
+       "TSPEC Not Found",              \
+       "ACM Not Supported",            \
+       "Not WME Association",          \
+       "SDIO Bus Error",               \
+       "Dongle Not Accessible",        \
+       "Incorrect version"     \
+}
+
+#ifndef ABS
+#define        ABS(a)                  (((a) < 0)?-(a):(a))
+#endif /* ABS */
+
+#ifndef MIN
+#define        MIN(a, b)               (((a) < (b))?(a):(b))
+#endif /* MIN */
+
+#ifndef MAX
+#define        MAX(a, b)               (((a) > (b))?(a):(b))
+#endif /* MAX */
+
+#define CEIL(x, y)             (((x) + ((y)-1)) / (y))
+#define        ROUNDUP(x, y)           ((((x)+((y)-1))/(y))*(y))
+#define        ISALIGNED(a, x)         (((a) & ((x)-1)) == 0)
+#define        ISPOWEROF2(x)           ((((x)-1)&(x)) == 0)
+#define VALID_MASK(mask)       !((mask) & ((mask) + 1))
+#ifndef OFFSETOF
+#define        OFFSETOF(type, member)  ((uint)(uintptr)&((type *)0)->member)
+#endif /* OFFSETOF */
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(a)           (sizeof(a)/sizeof(a[0]))
+#endif
+
+/* bit map related macros */
+#ifndef setbit
+#ifndef NBBY               /* the BSD family defines NBBY */
+#define        NBBY    8       /* 8 bits per byte */
+#endif /* #ifndef NBBY */
+#define        setbit(a, i)    (((uint8 *)a)[(i)/NBBY] |= 1<<((i)%NBBY))
+#define        clrbit(a, i)    (((uint8 *)a)[(i)/NBBY] &= ~(1<<((i)%NBBY)))
+#define        isset(a, i)     (((const uint8 *)a)[(i)/NBBY] & (1<<((i)%NBBY)))
+#define        isclr(a, i)     ((((const uint8 *)a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0)
+#endif /* setbit */
+
+#define        NBITS(type)     (sizeof(type) * 8)
+#define NBITVAL(nbits) (1 << (nbits))
+#define MAXBITVAL(nbits)       ((1 << (nbits)) - 1)
+#define        NBITMASK(nbits) MAXBITVAL(nbits)
+#define MAXNBVAL(nbyte)        MAXBITVAL((nbyte) * 8)
+
+/* basic mux operation - can be optimized on several architectures */
+#define MUX(pred, true, false) ((pred) ? (true) : (false))
+
+/* modulo inc/dec - assumes x E [0, bound - 1] */
+#define MODDEC(x, bound) MUX((x) == 0, (bound) - 1, (x) - 1)
+#define MODINC(x, bound) MUX((x) == (bound) - 1, 0, (x) + 1)
+
+/* modulo inc/dec, bound = 2^k */
+#define MODDEC_POW2(x, bound) (((x) - 1) & ((bound) - 1))
+#define MODINC_POW2(x, bound) (((x) + 1) & ((bound) - 1))
+
+/* modulo add/sub - assumes x, y E [0, bound - 1] */
+#define MODADD(x, y, bound) \
+    MUX((x) + (y) >= (bound), (x) + (y) - (bound), (x) + (y))
+#define MODSUB(x, y, bound) \
+    MUX(((int)(x)) - ((int)(y)) < 0, (x) - (y) + (bound), (x) - (y))
+
+/* module add/sub, bound = 2^k */
+#define MODADD_POW2(x, y, bound) (((x) + (y)) & ((bound) - 1))
+#define MODSUB_POW2(x, y, bound) (((x) - (y)) & ((bound) - 1))
+
+/* crc defines */
+#define CRC8_INIT_VALUE  0xff          /* Initial CRC8 checksum value */
+#define CRC8_GOOD_VALUE  0x9f          /* Good final CRC8 checksum value */
+#define CRC16_INIT_VALUE 0xffff                /* Initial CRC16 checksum value */
+#define CRC16_GOOD_VALUE 0xf0b8                /* Good final CRC16 checksum value */
+#define CRC32_INIT_VALUE 0xffffffff    /* Initial CRC32 checksum value */
+#define CRC32_GOOD_VALUE 0xdebb20e3    /* Good final CRC32 checksum value */
+
+/* bcm_format_flags() bit description structure */
+typedef struct bcm_bit_desc {
+       uint32  bit;
+       const char* name;
+} bcm_bit_desc_t;
+
+/* tag_ID/length/value_buffer tuple */
+typedef struct bcm_tlv {
+       uint8   id;
+       uint8   len;
+       uint8   data[1];
+} bcm_tlv_t;
+
+/* Check that bcm_tlv_t fits into the given buflen */
+#define bcm_valid_tlv(elt, buflen) ((buflen) >= 2 && (int)(buflen) >= (int)(2 + (elt)->len))
+
+/* buffer length for ethernet address from bcm_ether_ntoa() */
+#define ETHER_ADDR_STR_LEN     18      /* 18-bytes of Ethernet address buffer length */
+
+/* unaligned load and store macros */
+#ifdef IL_BIGENDIAN
+static INLINE uint32
+load32_ua(uint8 *a)
+{
+       return ((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3]);
+}
+
+static INLINE void
+store32_ua(uint8 *a, uint32 v)
+{
+       a[0] = (v >> 24) & 0xff;
+       a[1] = (v >> 16) & 0xff;
+       a[2] = (v >> 8) & 0xff;
+       a[3] = v & 0xff;
+}
+
+static INLINE uint16
+load16_ua(uint8 *a)
+{
+       return ((a[0] << 8) | a[1]);
+}
+
+static INLINE void
+store16_ua(uint8 *a, uint16 v)
+{
+       a[0] = (v >> 8) & 0xff;
+       a[1] = v & 0xff;
+}
+
+#else /* IL_BIGENDIAN */
+
+static INLINE uint32
+load32_ua(uint8 *a)
+{
+       return ((a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0]);
+}
+
+static INLINE void
+store32_ua(uint8 *a, uint32 v)
+{
+       a[3] = (v >> 24) & 0xff;
+       a[2] = (v >> 16) & 0xff;
+       a[1] = (v >> 8) & 0xff;
+       a[0] = v & 0xff;
+}
+
+static INLINE uint16
+load16_ua(uint8 *a)
+{
+       return ((a[1] << 8) | a[0]);
+}
+
+static INLINE void
+store16_ua(uint8 *a, uint16 v)
+{
+       a[1] = (v >> 8) & 0xff;
+       a[0] = v & 0xff;
+}
+
+#endif /* IL_BIGENDIAN */
+
+/* externs */
+/* crc */
+extern uint8 BCMROMFN(hndcrc8)(uint8 *p, uint nbytes, uint8 crc);
+extern uint16 BCMROMFN(hndcrc16)(uint8 *p, uint nbytes, uint16 crc);
+extern uint32 BCMROMFN(hndcrc32)(uint8 *p, uint nbytes, uint32 crc);
+/* format/print */
+extern char *bcm_brev_str(uint16 brev, char *buf);
+extern void printfbig(char *buf);
+
+/* IE parsing */
+extern bcm_tlv_t *BCMROMFN(bcm_next_tlv)(bcm_tlv_t *elt, int *buflen);
+extern bcm_tlv_t *BCMROMFN(bcm_parse_tlvs)(void *buf, int buflen, uint key);
+extern bcm_tlv_t *BCMROMFN(bcm_parse_ordered_tlvs)(void *buf, int buflen, uint key);
+
+/* bcmerror */
+extern const char *bcmerrorstr(int bcmerror);
+
+/* multi-bool data type: set of bools, mbool is true if any is set */
+typedef uint32 mbool;
+#define mboolset(mb, bit)              ((mb) |= (bit))         /* set one bool */
+#define mboolclr(mb, bit)              ((mb) &= ~(bit))        /* clear one bool */
+#define mboolisset(mb, bit)            (((mb) & (bit)) != 0)   /* TRUE if one bool is set */
+#define        mboolmaskset(mb, mask, val)     ((mb) = (((mb) & ~(mask)) | (val)))
+
+/* power conversion */
+extern uint16 BCMROMFN(bcm_qdbm_to_mw)(uint8 qdbm);
+extern uint8 BCMROMFN(bcm_mw_to_qdbm)(uint16 mw);
+
+/* generic datastruct to help dump routines */
+struct fielddesc {
+       const char *nameandfmt;
+       uint32  offset;
+       uint32  len;
+};
+
+extern void bcm_binit(struct bcmstrbuf *b, char *buf, uint size);
+extern int bcm_bprintf(struct bcmstrbuf *b, const char *fmt, ...);
+
+typedef  uint32 (*readreg_rtn)(void *arg0, void *arg1, uint32 offset);
+extern uint bcmdumpfields(readreg_rtn func_ptr, void *arg0, void *arg1, struct fielddesc *str,
+                          char *buf, uint32 bufsize);
+
+extern uint bcm_mkiovar(char *name, char *data, uint datalen, char *buf, uint len);
+extern uint BCMROMFN(bcm_bitcount)(uint8 *bitmap, uint bytelength);
+
+#ifdef BCMDBG_PKT      /* pkt logging for debugging */
+#define PKTLIST_SIZE 1000
+typedef struct {
+       void *list[PKTLIST_SIZE]; /* List of pointers to packets */
+       uint count; /* Total count of the packets */
+} pktlist_info_t;
+
+extern void pktlist_add(pktlist_info_t *pktlist, void *p);
+extern void pktlist_remove(pktlist_info_t *pktlist, void *p);
+extern char* pktlist_dump(pktlist_info_t *pktlist, char *buf);
+#endif  /* BCMDBG_PKT */
+
+#endif /* _bcmutils_h_ */
diff --git a/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c b/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c
new file mode 100644 (file)
index 0000000..9bfb8b4
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * Broadcom SiliconBackplane chipcommon serial flash interface
+ *
+ * Copyright 2007, Broadcom Corporation
+ * All Rights Reserved.
+ * 
+ * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
+ * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
+ * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
+ *
+ * $Id$
+ */
+
+#include <typedefs.h>
+#include <osl.h>
+#include "include/bcmutils.h"
+#include <sbutils.h>
+#include <sbconfig.h>
+#include <sbchipc.h>
+#include <bcmdevs.h>
+#include <sflash.h>
+
+/* Private global state */
+static struct sflash sflash;
+
+/* Issue a serial flash command */
+static INLINE void
+sflash_cmd(osl_t *osh, chipcregs_t *cc, uint opcode)
+{
+       W_REG(osh, &cc->flashcontrol, SFLASH_START | opcode);
+       while (R_REG(osh, &cc->flashcontrol) & SFLASH_BUSY);
+}
+
+/* Initialize serial flash access */
+struct sflash *
+sflash_init(sb_t *sbh, chipcregs_t *cc)
+{
+       uint32 id, id2;
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       osh = sb_osh(sbh);
+
+       bzero(&sflash, sizeof(sflash));
+
+       sflash.type = sbh->cccaps & CC_CAP_FLASH_MASK;
+
+       switch (sflash.type) {
+       case SFLASH_ST:
+               /* Probe for ST chips */
+               sflash_cmd(osh, cc, SFLASH_ST_DP);
+               sflash_cmd(osh, cc, SFLASH_ST_RES);
+               id = R_REG(osh, &cc->flashdata);
+               switch (id) {
+               case 0x11:
+                       /* ST M25P20 2 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 4;
+                       break;
+               case 0x12:
+                       /* ST M25P40 4 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 8;
+                       break;
+               case 0x13:
+                       /* ST M25P80 8 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 16;
+                       break;
+               case 0x14:
+                       /* ST M25P16 16 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 32;
+                       break;
+               case 0x15:
+                       /* ST M25P32 32 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 64;
+                       break;
+               case 0x16:
+                       /* ST M25P64 64 Mbit Serial Flash */
+                       sflash.blocksize = 64 * 1024;
+                       sflash.numblocks = 128;
+                       break;
+               case 0xbf:
+                       W_REG(osh, &cc->flashaddress, 1);
+                       sflash_cmd(osh, cc, SFLASH_ST_RES);
+                       id2 = R_REG(osh, &cc->flashdata);
+                       if (id2 == 0x44) {
+                               /* SST M25VF80 4 Mbit Serial Flash */
+                               sflash.blocksize = 64 * 1024;
+                               sflash.numblocks = 8;
+                       }
+                       break;
+               }
+               break;
+
+       case SFLASH_AT:
+               /* Probe for Atmel chips */
+               sflash_cmd(osh, cc, SFLASH_AT_STATUS);
+               id = R_REG(osh, &cc->flashdata) & 0x3c;
+               switch (id) {
+               case 0xc:
+                       /* Atmel AT45DB011 1Mbit Serial Flash */
+                       sflash.blocksize = 256;
+                       sflash.numblocks = 512;
+                       break;
+               case 0x14:
+                       /* Atmel AT45DB021 2Mbit Serial Flash */
+                       sflash.blocksize = 256;
+                       sflash.numblocks = 1024;
+                       break;
+               case 0x1c:
+                       /* Atmel AT45DB041 4Mbit Serial Flash */
+                       sflash.blocksize = 256;
+                       sflash.numblocks = 2048;
+                       break;
+               case 0x24:
+                       /* Atmel AT45DB081 8Mbit Serial Flash */
+                       sflash.blocksize = 256;
+                       sflash.numblocks = 4096;
+                       break;
+               case 0x2c:
+                       /* Atmel AT45DB161 16Mbit Serial Flash */
+                       sflash.blocksize = 512;
+                       sflash.numblocks = 4096;
+                       break;
+               case 0x34:
+                       /* Atmel AT45DB321 32Mbit Serial Flash */
+                       sflash.blocksize = 512;
+                       sflash.numblocks = 8192;
+                       break;
+               case 0x3c:
+                       /* Atmel AT45DB642 64Mbit Serial Flash */
+                       sflash.blocksize = 1024;
+                       sflash.numblocks = 8192;
+                       break;
+               }
+               break;
+       }
+
+       sflash.size = sflash.blocksize * sflash.numblocks;
+       return sflash.size ? &sflash : NULL;
+}
+
+/* Read len bytes starting at offset into buf. Returns number of bytes read. */
+int
+sflash_read(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, uchar *buf)
+{
+       uint8 *from, *to;
+       int cnt, i;
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       if (!len)
+               return 0;
+
+       if ((offset + len) > sflash.size)
+               return -22;
+
+       if ((len >= 4) && (offset & 3))
+               cnt = 4 - (offset & 3);
+       else if ((len >= 4) && ((uintptr)buf & 3))
+               cnt = 4 - ((uintptr)buf & 3);
+       else
+               cnt = len;
+
+       osh = sb_osh(sbh);
+
+       from = (uint8 *)(uintptr)OSL_UNCACHED(SB_FLASH2 + offset);
+       to = (uint8 *)buf;
+
+       if (cnt < 4) {
+               for (i = 0; i < cnt; i ++) {
+                       *to = R_REG(osh, from);
+                       from ++;
+                       to ++;
+               }
+               return cnt;
+       }
+
+       while (cnt >= 4) {
+               *(uint32 *)to = R_REG(osh, (uint32 *)from);
+               from += 4;
+               to += 4;
+               cnt -= 4;
+       }
+
+       return (len - cnt);
+}
+
+/* Poll for command completion. Returns zero when complete. */
+int
+sflash_poll(sb_t *sbh, chipcregs_t *cc, uint offset)
+{
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       osh = sb_osh(sbh);
+
+       if (offset >= sflash.size)
+               return -22;
+
+       switch (sflash.type) {
+       case SFLASH_ST:
+               /* Check for ST Write In Progress bit */
+               sflash_cmd(osh, cc, SFLASH_ST_RDSR);
+               return R_REG(osh, &cc->flashdata) & SFLASH_ST_WIP;
+       case SFLASH_AT:
+               /* Check for Atmel Ready bit */
+               sflash_cmd(osh, cc, SFLASH_AT_STATUS);
+               return !(R_REG(osh, &cc->flashdata) & SFLASH_AT_READY);
+       }
+
+       return 0;
+}
+
+/* Write len bytes starting at offset into buf. Returns number of bytes
+ * written. Caller should poll for completion.
+ */
+int
+sflash_write(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, const uchar *buf)
+{
+       struct sflash *sfl;
+       int ret = 0;
+       bool is4712b0;
+       uint32 page, byte, mask;
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       osh = sb_osh(sbh);
+
+       if (!len)
+               return 0;
+
+       if ((offset + len) > sflash.size)
+               return -22;
+
+       sfl = &sflash;
+       switch (sfl->type) {
+       case SFLASH_ST:
+               is4712b0 = (sbh->chip == BCM4712_CHIP_ID) && (sbh->chiprev == 3);
+               /* Enable writes */
+               sflash_cmd(osh, cc, SFLASH_ST_WREN);
+               if (is4712b0) {
+                       mask = 1 << 14;
+                       W_REG(osh, &cc->flashaddress, offset);
+                       W_REG(osh, &cc->flashdata, *buf++);
+                       /* Set chip select */
+                       OR_REG(osh, &cc->gpioout, mask);
+                       /* Issue a page program with the first byte */
+                       sflash_cmd(osh, cc, SFLASH_ST_PP);
+                       ret = 1;
+                       offset++;
+                       len--;
+                       while (len > 0) {
+                               if ((offset & 255) == 0) {
+                                       /* Page boundary, drop cs and return */
+                                       AND_REG(osh, &cc->gpioout, ~mask);
+                                       if (!sflash_poll(sbh, cc, offset)) {
+                                               /* Flash rejected command */
+                                               return -11;
+                                       }
+                                       return ret;
+                               } else {
+                                       /* Write single byte */
+                                       sflash_cmd(osh, cc, *buf++);
+                               }
+                               ret++;
+                               offset++;
+                               len--;
+                       }
+                       /* All done, drop cs if needed */
+                       if ((offset & 255) != 1) {
+                               /* Drop cs */
+                               AND_REG(osh, &cc->gpioout, ~mask);
+                               if (!sflash_poll(sbh, cc, offset)) {
+                                       /* Flash rejected command */
+                                       return -12;
+                               }
+                       }
+               } else if ( (sbh->ccrev >= 20) && (len != 1) ) {
+               //} else if ( sbh->ccrev >= 20 ) {              /* foxconn modified by EricHuang, 05/24/2007 */
+                       W_REG(NULL, &cc->flashaddress, offset);
+                       W_REG(NULL, &cc->flashdata, *buf++);
+                       /* Issue a page program with CSA bit set */
+                       sflash_cmd(osh, cc, SFLASH_ST_CSA | SFLASH_ST_PP);
+                       ret = 1;
+                       offset++;
+                       len--;
+                       while (len > 0) {
+                               if ((offset & 255) == 0) {
+                                       /* Page boundary, poll droping cs and return */
+                                       W_REG(NULL, &cc->flashcontrol, 0);
+                                       /* wklin added start, 06/08/2007 */
+                                       W_REG(NULL, &cc->flashcontrol, 0);
+                                       OSL_DELAY(1);
+                                       /* wklin added end, 06/08/2007 */
+                                       /* wklin rmeoved start, 06/08/2007 */
+#if 0
+                                       if (!sflash_poll(sbh, cc, offset)) {
+                                               /* Flash rejected command */
+                                               return -11;
+                                       }
+#endif                                 
+                                        /* wklin removed end, 06/08/2007 */
+                                       return ret;
+                               } else {
+                                       /* Write single byte */
+                                       sflash_cmd(osh, cc, SFLASH_ST_CSA | *buf++);
+                               }
+                               ret++;
+                               offset++;
+                               len--;
+                       }
+                       /* All done, drop cs if needed */
+                       if ((offset & 255) != 1) {
+                               /* Drop cs, poll */
+                               W_REG(NULL, &cc->flashcontrol, 0);
+                               /* wklin added start, 06/08/2007 */
+                               W_REG(NULL, &cc->flashcontrol, 0);
+                               OSL_DELAY(1);
+                               /* wklin added end, 06/08/2007 */
+                               /* wklin removed start, 06/08/2007 */
+#if 0                          
+                               if (!sflash_poll(sbh, cc, offset)) {
+                                       /* Flash rejected command */
+                                       return -12;
+                               }
+#endif
+                                /* wklin removed end, 06/08/2007 */
+                       }
+               } else {
+                       ret = 1;
+                       W_REG(osh, &cc->flashaddress, offset);
+                       W_REG(osh, &cc->flashdata, *buf);
+                       /* Page program */
+                       sflash_cmd(osh, cc, SFLASH_ST_PP);
+               }
+               break;
+       case SFLASH_AT:
+               mask = sfl->blocksize - 1;
+               page = (offset & ~mask) << 1;
+               byte = offset & mask;
+               /* Read main memory page into buffer 1 */
+               if (byte || (len < sfl->blocksize)) {
+                       W_REG(osh, &cc->flashaddress, page);
+                       sflash_cmd(osh, cc, SFLASH_AT_BUF1_LOAD);
+                       /* 250 us for AT45DB321B */
+                       SPINWAIT(sflash_poll(sbh, cc, offset), 1000);
+                       ASSERT(!sflash_poll(sbh, cc, offset));
+               }
+               /* Write into buffer 1 */
+               for (ret = 0; (ret < (int)len) && (byte < sfl->blocksize); ret++) {
+                       W_REG(osh, &cc->flashaddress, byte++);
+                       W_REG(osh, &cc->flashdata, *buf++);
+                       sflash_cmd(osh, cc, SFLASH_AT_BUF1_WRITE);
+               }
+               /* Write buffer 1 into main memory page */
+               W_REG(osh, &cc->flashaddress, page);
+               sflash_cmd(osh, cc, SFLASH_AT_BUF1_PROGRAM);
+               break;
+       }
+
+       return ret;
+}
+
+/* Erase a region. Returns number of bytes scheduled for erasure.
+ * Caller should poll for completion.
+ */
+int
+sflash_erase(sb_t *sbh, chipcregs_t *cc, uint offset)
+{
+       struct sflash *sfl;
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       osh = sb_osh(sbh);
+
+       if (offset >= sflash.size)
+               return -22;
+
+       sfl = &sflash;
+       switch (sfl->type) {
+       case SFLASH_ST:
+               sflash_cmd(osh, cc, SFLASH_ST_WREN);
+               W_REG(osh, &cc->flashaddress, offset);
+               sflash_cmd(osh, cc, SFLASH_ST_SE);
+               return sfl->blocksize;
+       case SFLASH_AT:
+               W_REG(osh, &cc->flashaddress, offset << 1);
+               sflash_cmd(osh, cc, SFLASH_AT_PAGE_ERASE);
+               return sfl->blocksize;
+       }
+
+       return 0;
+}
+
+/*
+ * writes the appropriate range of flash, a NULL buf simply erases
+ * the region of flash
+ */
+int
+sflash_commit(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, const uchar *buf)
+{
+       struct sflash *sfl;
+       uchar *block = NULL, *cur_ptr, *blk_ptr;
+       uint blocksize = 0, mask, cur_offset, cur_length, cur_retlen, remainder;
+       uint blk_offset, blk_len, copied;
+       int bytes, ret = 0;
+       osl_t *osh;
+
+       ASSERT(sbh);
+
+       osh = sb_osh(sbh);
+
+       /* Check address range */
+       if (len <= 0)
+               return 0;
+
+       sfl = &sflash;
+       if ((offset + len) > sfl->size)
+               return -1;
+
+       blocksize = sfl->blocksize;
+       mask = blocksize - 1;
+
+       /* Allocate a block of mem */
+       if (!(block = MALLOC(osh, blocksize)))
+               return -1;
+
+       while (len) {
+               /* Align offset */
+               cur_offset = offset & ~mask;
+               cur_length = blocksize;
+               cur_ptr = block;
+
+               remainder = blocksize - (offset & mask);
+               if (len < remainder)
+                       cur_retlen = len;
+               else
+                       cur_retlen = remainder;
+
+               /* buf == NULL means erase only */
+               if (buf) {
+                       /* Copy existing data into holding block if necessary */
+                       if ((offset & mask) || (len < blocksize)) {
+                               blk_offset = cur_offset;
+                               blk_len = cur_length;
+                               blk_ptr = cur_ptr;
+
+                               /* Copy entire block */
+                               while (blk_len) {
+                                       copied = sflash_read(sbh, cc, blk_offset, blk_len, blk_ptr);
+                                       blk_offset += copied;
+                                       blk_len -= copied;
+                                       blk_ptr += copied;
+                               }
+                       }
+
+                       /* Copy input data into holding block */
+                       memcpy(cur_ptr + (offset & mask), buf, cur_retlen);
+               }
+
+               /* Erase block */
+               if ((ret = sflash_erase(sbh, cc, (uint) cur_offset)) < 0)
+                       goto done;
+               while (sflash_poll(sbh, cc, (uint) cur_offset));
+
+               /* buf == NULL means erase only */
+               if (!buf) {
+                       offset += cur_retlen;
+                       len -= cur_retlen;
+                       continue;
+               }
+
+               /* Write holding block */
+               while (cur_length > 0) {
+                       if ((bytes = sflash_write(sbh, cc,
+                                                 (uint) cur_offset,
+                                                 (uint) cur_length,
+                                                 (uchar *) cur_ptr)) < 0) {
+                               ret = bytes;
+                               goto done;
+                       }
+                       while (sflash_poll(sbh, cc, (uint) cur_offset));
+                       cur_offset += bytes;
+                       cur_length -= bytes;
+                       cur_ptr += bytes;
+               }
+
+               offset += cur_retlen;
+               len -= cur_retlen;
+               buf += cur_retlen;
+       }
+
+       ret = len;
+done:
+       if (block)
+               MFREE(osh, block, blocksize);
+       return ret;
+}
index 7ffb004..a8aab7b 100644 (file)
 /*
  * Broadcom SiliconBackplane chipcommon serial flash interface
  *
 /*
  * Broadcom SiliconBackplane chipcommon serial flash interface
  *
- * Copyright 2007, Broadcom Corporation
- * All Rights Reserved.
- * 
- * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
- * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
- * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
+ * Copyright 2006, Broadcom Corporation      
+ * All Rights Reserved.      
+ *       
+ * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY      
+ * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM      
+ * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS      
+ * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.      
  *
  *
+ * $Id$
  */
 
  */
 
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
 #include <typedefs.h>
 #include <osl.h>
 #include <typedefs.h>
 #include <osl.h>
+// #include <bcmutils.h>
+#include <bcmdevs.h>
+#include <bcmnvram.h>
 #include <sbutils.h>
 #include <sbconfig.h>
 #include <sbchipc.h>
 #include <sbutils.h>
 #include <sbconfig.h>
 #include <sbchipc.h>
-#include <bcmdevs.h>
 #include <sflash.h>
 
 #include <sflash.h>
 
+#ifdef CONFIG_MTD_PARTITIONS
+extern struct mtd_partition * init_mtd_partitions(struct mtd_info *mtd, size_t size);
+#endif
+
+struct sflash_mtd {
+       sb_t *sbh;
+       chipcregs_t *cc;
+       struct semaphore lock;
+       struct mtd_info mtd;
+       struct mtd_erase_region_info region;
+};
+
 /* Private global state */
 /* Private global state */
-static struct sflash sflash;
+static struct sflash_mtd sflash;
 
 
-/* Issue a serial flash command */
-static INLINE void
-sflash_cmd (osl_t * osh, chipcregs_t * cc, uint opcode)
+static int
+sflash_mtd_poll(struct sflash_mtd *sflash, unsigned int offset, int timeout)
 {
 {
-  W_REG (osh, &cc->flashcontrol, SFLASH_START | opcode);
-  while (R_REG (osh, &cc->flashcontrol) & SFLASH_BUSY);
-}
+       int now = jiffies;
+       int ret = 0;
 
 
-/* Initialize serial flash access */
-struct sflash *
-sflash_init (sb_t * sbh, chipcregs_t * cc)
-{
-  uint32 id, id2;
-  osl_t *osh;
-
-  ASSERT (sbh);
-
-  osh = sb_osh (sbh);
-
-  bzero (&sflash, sizeof (sflash));
-
-  sflash.type = sbh->cccaps & CC_CAP_FLASH_MASK;
-
-  switch (sflash.type)
-    {
-    case SFLASH_ST:
-      /* Probe for ST chips */
-      sflash_cmd (osh, cc, SFLASH_ST_DP);
-      sflash_cmd (osh, cc, SFLASH_ST_RES);
-      id = R_REG (osh, &cc->flashdata);
-      switch (id)
-       {
-       case 0x11:
-         /* ST M25P20 2 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 4;
-         break;
-       case 0x12:
-         /* ST M25P40 4 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 8;
-         break;
-       case 0x13:
-         /* ST M25P80 8 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 16;
-         break;
-       case 0x14:
-         /* ST M25P16 16 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 32;
-         break;
-       case 0x15:
-         /* ST M25P32 32 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 64;
-         break;
-       case 0x16:
-         /* ST M25P64 64 Mbit Serial Flash */
-         sflash.blocksize = 64 * 1024;
-         sflash.numblocks = 128;
-         break;
-       case 0xbf:
-         W_REG (osh, &cc->flashaddress, 1);
-         sflash_cmd (osh, cc, SFLASH_ST_RES);
-         id2 = R_REG (osh, &cc->flashdata);
-         if (id2 == 0x44)
-           {
-             /* SST M25VF80 4 Mbit Serial Flash */
-             sflash.blocksize = 64 * 1024;
-             sflash.numblocks = 8;
-           }
-         break;
-       }
-      break;
-
-    case SFLASH_AT:
-      /* Probe for Atmel chips */
-      sflash_cmd (osh, cc, SFLASH_AT_STATUS);
-      id = R_REG (osh, &cc->flashdata) & 0x3c;
-      switch (id)
-       {
-       case 0xc:
-         /* Atmel AT45DB011 1Mbit Serial Flash */
-         sflash.blocksize = 256;
-         sflash.numblocks = 512;
-         break;
-       case 0x14:
-         /* Atmel AT45DB021 2Mbit Serial Flash */
-         sflash.blocksize = 256;
-         sflash.numblocks = 1024;
-         break;
-       case 0x1c:
-         /* Atmel AT45DB041 4Mbit Serial Flash */
-         sflash.blocksize = 256;
-         sflash.numblocks = 2048;
-         break;
-       case 0x24:
-         /* Atmel AT45DB081 8Mbit Serial Flash */
-         sflash.blocksize = 256;
-         sflash.numblocks = 4096;
-         break;
-       case 0x2c:
-         /* Atmel AT45DB161 16Mbit Serial Flash */
-         sflash.blocksize = 512;
-         sflash.numblocks = 4096;
-         break;
-       case 0x34:
-         /* Atmel AT45DB321 32Mbit Serial Flash */
-         sflash.blocksize = 512;
-         sflash.numblocks = 8192;
-         break;
-       case 0x3c:
-         /* Atmel AT45DB642 64Mbit Serial Flash */
-         sflash.blocksize = 1024;
-         sflash.numblocks = 8192;
-         break;
+       for (;;) {
+               if (!sflash_poll(sflash->sbh, sflash->cc, offset)) {
+                       ret = 0;
+                       break;
+               }
+               if (time_after(jiffies, now + timeout)) {
+                       printk(KERN_ERR "sflash: timeout\n");
+                       ret = -ETIMEDOUT;
+                       break;
+               }
+               if (current->need_resched) {
+                       set_current_state(TASK_UNINTERRUPTIBLE);
+                       schedule_timeout(timeout / 10);
+               } else
+                       udelay(1);
        }
        }
-      break;
-    }
 
 
-  sflash.size = sflash.blocksize * sflash.numblocks;
-  return sflash.size ? &sflash : NULL;
+       return ret;
 }
 
 }
 
-/* Read len bytes starting at offset into buf. Returns number of bytes read. */
-int
-sflash_read (sb_t * sbh, chipcregs_t * cc, uint offset, uint len, uchar * buf)
+static int
+sflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
 {
 {
-  uint8 *from, *to;
-  int cnt, i;
-  osl_t *osh;
-
-  ASSERT (sbh);
-
-  if (!len)
-    return 0;
+       struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
+       int bytes, ret = 0;
+
+       /* Check address range */
+       if (len == 0){
+        *retlen = 0;
+               return 0;
+ }
+       if (!len)
+               return 0;
+       if ((from + len) > mtd->size)
+               return -EINVAL;
+       
+       down(&sflash->lock);
+
+       *retlen = 0;
+       while (len) {
+               if ((bytes = sflash_read(sflash->sbh, sflash->cc, (uint) from, len, buf)) < 0) {
+                       ret = bytes;
+                       break;
+               }
+               from += (loff_t) bytes;
+               len -= bytes;
+               buf += bytes;
+               *retlen += bytes;
+       }
 
 
-  if ((offset + len) > sflash.size)
-    return -22;
+       up(&sflash->lock);
 
 
-  if ((len >= 4) && (offset & 3))
-    cnt = 4 - (offset & 3);
-  else if ((len >= 4) && ((uintptr) buf & 3))
-    cnt = 4 - ((uintptr) buf & 3);
-  else
-    cnt = len;
+       return ret;
+}
 
 
-  osh = sb_osh (sbh);
+static int
+sflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+       struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
+       int bytes, ret = 0;
+
+       /* Check address range */
+       if (len == 0){
+        *retlen = 0;
+               return 0;
+ }
+       if (!len)
+               return 0;
+       if ((to + len) > mtd->size)
+               return -EINVAL;
+
+       down(&sflash->lock);
+
+       *retlen = 0;
+       while (len) {
+               if ((bytes = sflash_write(sflash->sbh, sflash->cc, (uint)to, (uint)len, buf)) < 0) {
+                       ret = bytes;
+                       break;
+               }
+               if ((ret = sflash_mtd_poll(sflash, (unsigned int) to, HZ / 10)))
+                       break;
+               to += (loff_t) bytes;
+               len -= bytes;
+               buf += bytes;
+               *retlen += bytes;
+       }
 
 
-  from = (uint8 *) (uintptr) OSL_UNCACHED (SB_FLASH2 + offset);
-  to = (uint8 *) buf;
+       up(&sflash->lock);
 
 
-  if (cnt < 4)
-    {
-      for (i = 0; i < cnt; i++)
-       {
-         *to = R_REG (osh, from);
-         from++;
-         to++;
-       }
-      return cnt;
-    }
-
-  while (cnt >= 4)
-    {
-      *(uint32 *) to = R_REG (osh, (uint32 *) from);
-      from += 4;
-      to += 4;
-      cnt -= 4;
-    }
-
-  return (len - cnt);
+       return ret;
 }
 
 }
 
-/* Poll for command completion. Returns zero when complete. */
-int
-sflash_poll (sb_t * sbh, chipcregs_t * cc, uint offset)
+static int
+sflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase)
 {
 {
-  osl_t *osh;
-
-  ASSERT (sbh);
+       struct sflash_mtd *sflash = (struct sflash_mtd *) mtd->priv;
+       int i, j, ret = 0;
+       unsigned int addr, len;
+
+       /* Check address range */
+       if (!erase->len)
+               return 0;
+       if ((erase->addr + erase->len) > mtd->size)
+               return -EINVAL;
+
+       addr = erase->addr;
+       len = erase->len;
+
+       down(&sflash->lock);
+
+       /* Ensure that requested region is aligned */
+       for (i = 0; i < mtd->numeraseregions; i++) {
+               for (j = 0; j < mtd->eraseregions[i].numblocks; j++) {
+                       if (addr == mtd->eraseregions[i].offset + mtd->eraseregions[i].erasesize * j &&
+                           len >= mtd->eraseregions[i].erasesize) {
+                               if ((ret = sflash_erase(sflash->sbh, sflash->cc, addr)) < 0)
+                                       break;
+                               if ((ret = sflash_mtd_poll(sflash, addr, 10 * HZ)))
+                                       break;
+                               addr += mtd->eraseregions[i].erasesize;
+                               len -= mtd->eraseregions[i].erasesize;
+                       }
+               }
+               if (ret)
+                       break;
+       }
 
 
-  osh = sb_osh (sbh);
+       up(&sflash->lock);
 
 
-  if (offset >= sflash.size)
-    return -22;
+       /* Set erase status */
+       if (ret)
+               erase->state = MTD_ERASE_FAILED;
+       else 
+               erase->state = MTD_ERASE_DONE;
 
 
-  switch (sflash.type)
-    {
-    case SFLASH_ST:
-      /* Check for ST Write In Progress bit */
-      sflash_cmd (osh, cc, SFLASH_ST_RDSR);
-      return R_REG (osh, &cc->flashdata) & SFLASH_ST_WIP;
-    case SFLASH_AT:
-      /* Check for Atmel Ready bit */
-      sflash_cmd (osh, cc, SFLASH_AT_STATUS);
-      return !(R_REG (osh, &cc->flashdata) & SFLASH_AT_READY);
-    }
+       /* Call erase callback */
+       if (erase->callback)
+               erase->callback(erase);
 
 
-  return 0;
+       return ret;
 }
 
 }
 
-/* Write len bytes starting at offset into buf. Returns number of bytes
- * written. Caller should poll for completion.
- */
-int
-sflash_write (sb_t * sbh, chipcregs_t * cc, uint offset, uint len,
-             const uchar * buf)
+#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
+#define sflash_mtd_init init_module
+#define sflash_mtd_exit cleanup_module
+#endif
+
+mod_init_t
+sflash_mtd_init(void)
 {
 {
-  struct sflash *sfl;
-  int ret = 0;
-  bool is4712b0;
-  uint32 page, byte, mask;
-  osl_t *osh;
-
-  ASSERT (sbh);
-
-  osh = sb_osh (sbh);
-
-  if (!len)
-    return 0;
-
-  if ((offset + len) > sflash.size)
-    return -22;
-
-  sfl = &sflash;
-  switch (sfl->type)
-    {
-    case SFLASH_ST:
-      is4712b0 = (sbh->chip == BCM4712_CHIP_ID) && (sbh->chiprev == 3);
-      /* Enable writes */
-      sflash_cmd (osh, cc, SFLASH_ST_WREN);
-      if (is4712b0)
-       {
-         mask = 1 << 14;
-         W_REG (osh, &cc->flashaddress, offset);
-         W_REG (osh, &cc->flashdata, *buf++);
-         /* Set chip select */
-         OR_REG (osh, &cc->gpioout, mask);
-         /* Issue a page program with the first byte */
-         sflash_cmd (osh, cc, SFLASH_ST_PP);
-         ret = 1;
-         offset++;
-         len--;
-         while (len > 0)
-           {
-             if ((offset & 255) == 0)
-               {
-                 /* Page boundary, drop cs and return */
-                 AND_REG (osh, &cc->gpioout, ~mask);
-                 if (!sflash_poll (sbh, cc, offset))
-                   {
-                     /* Flash rejected command */
-                     return -11;
-                   }
-                 return ret;
-               }
-             else
-               {
-                 /* Write single byte */
-                 sflash_cmd (osh, cc, *buf++);
-               }
-             ret++;
-             offset++;
-             len--;
-           }
-         /* All done, drop cs if needed */
-         if ((offset & 255) != 1)
-           {
-             /* Drop cs */
-             AND_REG (osh, &cc->gpioout, ~mask);
-             if (!sflash_poll (sbh, cc, offset))
-               {
-                 /* Flash rejected command */
-                 return -12;
-               }
-           }
-       }
-      else if (sbh->ccrev >= 20)
-       {
-         W_REG (NULL, &cc->flashaddress, offset);
-         W_REG (NULL, &cc->flashdata, *buf++);
-         /* Issue a page program with CSA bit set */
-         sflash_cmd (osh, cc, SFLASH_ST_CSA | SFLASH_ST_PP);
-         ret = 1;
-         offset++;
-         len--;
-         while (len > 0)
-           {
-             if ((offset & 255) == 0)
-               {
-                 /* Page boundary, poll droping cs and return */
-                 W_REG (NULL, &cc->flashcontrol, 0);
-                 if (!sflash_poll (sbh, cc, offset))
-                   {
-                     /* Flash rejected command */
-                     return -11;
-                   }
-                 return ret;
-               }
-             else
-               {
-                 /* Write single byte */
-                 sflash_cmd (osh, cc, SFLASH_ST_CSA | *buf++);
-               }
-             ret++;
-             offset++;
-             len--;
-           }
-         /* All done, drop cs if needed */
-         if ((offset & 255) != 1)
-           {
-             /* Drop cs, poll */
-             W_REG (NULL, &cc->flashcontrol, 0);
-             if (!sflash_poll (sbh, cc, offset))
-               {
-                 /* Flash rejected command */
-                 return -12;
-               }
-           }
-       }
-      else
-       {
-         ret = 1;
-         W_REG (osh, &cc->flashaddress, offset);
-         W_REG (osh, &cc->flashdata, *buf);
-         /* Page program */
-         sflash_cmd (osh, cc, SFLASH_ST_PP);
-       }
-      break;
-    case SFLASH_AT:
-      mask = sfl->blocksize - 1;
-      page = (offset & ~mask) << 1;
-      byte = offset & mask;
-      /* Read main memory page into buffer 1 */
-      if (byte || (len < sfl->blocksize))
-       {
-         W_REG (osh, &cc->flashaddress, page);
-         sflash_cmd (osh, cc, SFLASH_AT_BUF1_LOAD);
-         /* 250 us for AT45DB321B */
-         SPINWAIT (sflash_poll (sbh, cc, offset), 1000);
-         ASSERT (!sflash_poll (sbh, cc, offset));
+       struct pci_dev *pdev;
+       int ret = 0;
+       struct sflash *info;
+       uint i;
+#ifdef CONFIG_MTD_PARTITIONS
+       struct mtd_partition *parts;
+#endif
+
+       if (!(pdev = pci_find_device(VENDOR_BROADCOM, SB_CC, NULL))) {
+               printk(KERN_ERR "sflash: chipcommon not found\n");
+               return -ENODEV;
        }
        }
-      /* Write into buffer 1 */
-      for (ret = 0; (ret < (int) len) && (byte < sfl->blocksize); ret++)
-       {
-         W_REG (osh, &cc->flashaddress, byte++);
-         W_REG (osh, &cc->flashdata, *buf++);
-         sflash_cmd (osh, cc, SFLASH_AT_BUF1_WRITE);
-       }
-      /* Write buffer 1 into main memory page */
-      W_REG (osh, &cc->flashaddress, page);
-      sflash_cmd (osh, cc, SFLASH_AT_BUF1_PROGRAM);
-      break;
-    }
-
-  return ret;
-}
 
 
-/* Erase a region. Returns number of bytes scheduled for erasure.
- * Caller should poll for completion.
- */
-int
-sflash_erase (sb_t * sbh, chipcregs_t * cc, uint offset)
-{
-  struct sflash *sfl;
-  osl_t *osh;
-
-  ASSERT (sbh);
-
-  osh = sb_osh (sbh);
-
-  if (offset >= sflash.size)
-    return -22;
-
-  sfl = &sflash;
-  switch (sfl->type)
-    {
-    case SFLASH_ST:
-      sflash_cmd (osh, cc, SFLASH_ST_WREN);
-      W_REG (osh, &cc->flashaddress, offset);
-      sflash_cmd (osh, cc, SFLASH_ST_SE);
-      return sfl->blocksize;
-    case SFLASH_AT:
-      W_REG (osh, &cc->flashaddress, offset << 1);
-      sflash_cmd (osh, cc, SFLASH_AT_PAGE_ERASE);
-      return sfl->blocksize;
-    }
-
-  return 0;
-}
+       memset(&sflash, 0, sizeof(struct sflash_mtd));
+       init_MUTEX(&sflash.lock);
 
 
-/*
- * writes the appropriate range of flash, a NULL buf simply erases
- * the region of flash
- */
-int
-sflash_commit (sb_t * sbh, chipcregs_t * cc, uint offset, uint len,
-              const uchar * buf)
-{
-  struct sflash *sfl;
-  uchar *block = NULL, *cur_ptr, *blk_ptr;
-  uint blocksize = 0, mask, cur_offset, cur_length, cur_retlen, remainder;
-  uint blk_offset, blk_len, copied;
-  int bytes, ret = 0;
-  osl_t *osh;
-
-  ASSERT (sbh);
-
-  osh = sb_osh (sbh);
-
-  /* Check address range */
-  if (len <= 0)
-    return 0;
-
-  sfl = &sflash;
-  if ((offset + len) > sfl->size)
-    return -1;
-
-  blocksize = sfl->blocksize;
-  mask = blocksize - 1;
-
-  /* Allocate a block of mem */
-  if (!(block = MALLOC (osh, blocksize)))
-    return -1;
-
-  while (len)
-    {
-      /* Align offset */
-      cur_offset = offset & ~mask;
-      cur_length = blocksize;
-      cur_ptr = block;
-
-      remainder = blocksize - (offset & mask);
-      if (len < remainder)
-       cur_retlen = len;
-      else
-       cur_retlen = remainder;
-
-      /* buf == NULL means erase only */
-      if (buf)
-       {
-         /* Copy existing data into holding block if necessary */
-         if ((offset & mask) || (len < blocksize))
-           {
-             blk_offset = cur_offset;
-             blk_len = cur_length;
-             blk_ptr = cur_ptr;
-
-             /* Copy entire block */
-             while (blk_len)
-               {
-                 copied =
-                   sflash_read (sbh, cc, blk_offset, blk_len, blk_ptr);
-                 blk_offset += copied;
-                 blk_len -= copied;
-                 blk_ptr += copied;
-               }
-           }
+       /* attach to the backplane */
+       if (!(sflash.sbh = sb_kattach(SB_OSH))) {
+               printk(KERN_ERR "sflash: error attaching to backplane\n");
+               ret = -EIO;
+               goto fail;
+       }
 
 
-         /* Copy input data into holding block */
-         memcpy (cur_ptr + (offset & mask), buf, cur_retlen);
+       /* Map registers and flash base */
+       if (!(sflash.cc = ioremap_nocache(pci_resource_start(pdev, 0),
+                                         pci_resource_len(pdev, 0)))) {
+               printk(KERN_ERR "sflash: error mapping registers\n");
+               ret = -EIO;
+               goto fail;
        }
 
        }
 
-      /* Erase block */
-      if ((ret = sflash_erase (sbh, cc, (uint) cur_offset)) < 0)
-       goto done;
-      while (sflash_poll (sbh, cc, (uint) cur_offset));
-
-      /* buf == NULL means erase only */
-      if (!buf)
-       {
-         offset += cur_retlen;
-         len -= cur_retlen;
-         continue;
+       /* Initialize serial flash access */
+       if (!(info = sflash_init(sflash.sbh, sflash.cc))) {
+               printk(KERN_ERR "sflash: found no supported devices\n");
+               ret = -ENODEV;
+               goto fail;
        }
 
        }
 
-      /* Write holding block */
-      while (cur_length > 0)
-       {
-         if ((bytes = sflash_write (sbh, cc,
-                                    (uint) cur_offset,
-                                    (uint) cur_length,
-                                    (uchar *) cur_ptr)) < 0)
-           {
-             ret = bytes;
-             goto done;
-           }
-         while (sflash_poll (sbh, cc, (uint) cur_offset));
-         cur_offset += bytes;
-         cur_length -= bytes;
-         cur_ptr += bytes;
+       printk(KERN_INFO "sflash: found serial flash; blocksize=%dKB, numblocks=%d, size=%dKB\n",info->blocksize/1024,info->numblocks,info->size/1024);
+
+       /* Setup region info */
+       sflash.region.offset = 0;
+       sflash.region.erasesize = info->blocksize;
+       sflash.region.numblocks = info->numblocks;
+       if (sflash.region.erasesize > sflash.mtd.erasesize)
+               sflash.mtd.erasesize = sflash.region.erasesize;
+       sflash.mtd.size = info->size;
+       sflash.mtd.numeraseregions = 1;
+
+       /* Register with MTD */
+       sflash.mtd.name = "sflash";
+       sflash.mtd.type = MTD_NORFLASH;
+       sflash.mtd.flags = MTD_CAP_NORFLASH;
+       sflash.mtd.eraseregions = &sflash.region;
+       sflash.mtd.module = THIS_MODULE;
+       sflash.mtd.erase = sflash_mtd_erase;
+       sflash.mtd.read = sflash_mtd_read;
+       sflash.mtd.write = sflash_mtd_write;
+       sflash.mtd.priv = &sflash;
+
+#ifdef CONFIG_MTD_PARTITIONS
+       parts = init_mtd_partitions(&sflash.mtd, sflash.mtd.size);
+       for (i = 0; parts[i].name; i++);
+       ret = add_mtd_partitions(&sflash.mtd, parts, i);
+#else
+       ret = add_mtd_device(&sflash.mtd);
+#endif
+       if (ret) {
+               printk(KERN_ERR "sflash: add_mtd failed\n");
+               goto fail;
        }
 
        }
 
-      offset += cur_retlen;
-      len -= cur_retlen;
-      buf += cur_retlen;
-    }
+       return 0;
+
+ fail:
+       if (sflash.cc)
+               iounmap((void *) sflash.cc);
+       if (sflash.sbh)
+               sb_detach(sflash.sbh);
+       return ret;
+}
 
 
-  ret = len;
-done:
-  if (block)
-    MFREE (osh, block, blocksize);
-  return ret;
+mod_exit_t
+sflash_mtd_exit(void)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+       del_mtd_partitions(&sflash.mtd);
+#else
+       del_mtd_device(&sflash.mtd);
+#endif
+       iounmap((void *) sflash.cc);
+       sb_detach(sflash.sbh);
 }
 }
+
+module_init(sflash_mtd_init);
+module_exit(sflash_mtd_exit);
index f34efb0..7de8a82 100644 (file)
@@ -1,3 +1,13 @@
+--- a/arch/mips/bcm947xx/Makefile      
++++ b/arch/mips/bcm947xx/Makefile      
+@@ -11,6 +11,7 @@
+ obj-y         := prom.o setup.o time.o sbmips.o gpio.o
+ obj-y         += nvram.o nvram_linux.o cfe_env.o hndpmu.o
+ obj-y         += sbutils.o utils.o bcmsrom.o hndchipc.o
++obj-y         += sflash.o
+ obj-$(CONFIG_PCI) += sbpci.o pcibios.o
+ obj-y                 += export.o
 --- a/drivers/mtd/devices/Config.in
 +++ b/drivers/mtd/devices/Config.in
 @@ -5,6 +5,7 @@
 --- a/drivers/mtd/devices/Config.in
 +++ b/drivers/mtd/devices/Config.in
 @@ -5,6 +5,7 @@
This page took 0.066645 seconds and 4 git commands to generate.