/* * (C) Copyright 2003, Psyent Corporation * Scott McNutt * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #if defined(CONFIG_NIOS_ASMI) #include #include #if !defined(CFG_NIOS_ASMIBASE) #error "*** CFG_NIOS_ASMIBASE not defined ***" #endif /*-----------------------------------------------------------------------*/ #define SHORT_HELP\ "asmi - read/write Cyclone ASMI configuration device.\n" #define LONG_HELP\ "\n"\ "asmi erase start [end]\n"\ " - erase sector start or sectors start through end.\n"\ "asmi info\n"\ " - display ASMI device information.\n"\ "asmi protect on | off\n"\ " - turn device protection on or off.\n"\ "asmi read addr offset count\n"\ " - read count bytes from offset to addr.\n"\ "asmi write addr offset count\n"\ " - write count bytes to offset from addr.\n"\ "asmi verify addr offset count\n"\ " - verify count bytes at offset from addr.\n" /*-----------------------------------------------------------------------*/ /* Operation codes for serial configuration devices */ #define ASMI_WRITE_ENA 0x06 /* Write enable */ #define ASMI_WRITE_DIS 0x04 /* Write disable */ #define ASMI_READ_STAT 0x05 /* Read status */ #define ASMI_READ_BYTES 0x03 /* Read bytes */ #define ASMI_READ_ID 0xab /* Read silicon id */ #define ASMI_WRITE_STAT 0x01 /* Write status */ #define ASMI_WRITE_BYTES 0x02 /* Write bytes */ #define ASMI_ERASE_BULK 0xc7 /* Erase entire device */ #define ASMI_ERASE_SECT 0xd8 /* Erase sector */ /* Device status register bits */ #define ASMI_STATUS_WIP (1<<0) /* Write in progress */ #define ASMI_STATUS_WEL (1<<1) /* Write enable latch */ static nios_asmi_t *asmi = (nios_asmi_t *)CFG_NIOS_ASMIBASE; /*********************************************************************** * Device access ***********************************************************************/ static void asmi_cs (int assert) { if (assert) { asmi->control |= NIOS_ASMI_SSO; } else { /* Let all bits shift out */ while ((asmi->status & NIOS_ASMI_TMT) == 0) ; asmi->control &= ~NIOS_ASMI_SSO; } } static void asmi_tx (unsigned char c) { while ((asmi->status & NIOS_ASMI_TRDY) == 0) ; asmi->txdata = c; } static int asmi_rx (void) { while ((asmi->status & NIOS_ASMI_RRDY) == 0) ; return (asmi->rxdata); } static unsigned char bitrev[] = { 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f }; static unsigned char asmi_bitrev( unsigned char c ) { unsigned char val; val = bitrev[c>>4]; val |= bitrev[c & 0x0f]<<4; return (val); } static void asmi_rcv (unsigned char *dst, int len) { while (len--) { asmi_tx (0); *dst++ = asmi_rx (); } } static void asmi_rrcv (unsigned char *dst, int len) { while (len--) { asmi_tx (0); *dst++ = asmi_bitrev (asmi_rx ()); } } static void asmi_snd (unsigned char *src, int len) { while (len--) { asmi_tx (*src++); asmi_rx (); } } static void asmi_rsnd (unsigned char *src, int len) { while (len--) { asmi_tx (asmi_bitrev (*src++)); asmi_rx (); } } static void asmi_wr_enable (void) { asmi_cs (1); asmi_tx (ASMI_WRITE_ENA); asmi_rx (); asmi_cs (0); } static unsigned char asmi_status_rd (void) { unsigned char status; asmi_cs (1); asmi_tx (ASMI_READ_STAT); asmi_rx (); asmi_tx (0); status = asmi_rx (); asmi_cs (0); return (status); } static void asmi_status_wr (unsigned char status) { asmi_wr_enable (); asmi_cs (1); asmi_tx (ASMI_WRITE_STAT); asmi_rx (); asmi_tx (status); asmi_rx (); asmi_cs (0); return; } /*********************************************************************** * Device information ***********************************************************************/ typedef struct asmi_devinfo_t { const char *name; /* Device name */ unsigned char id; /* Device silicon id */ unsigned char size; /* Total size log2(bytes)*/ unsigned char num_sects; /* Number of sectors */ unsigned char sz_sect; /* Sector size log2(bytes) */ unsigned char sz_page; /* Page size log2(bytes) */ unsigned char prot_mask; /* Protection mask */ }asmi_devinfo_t; static struct asmi_devinfo_t devinfo[] = { { "EPCS1 ", 0x10, 17, 4, 15, 8, 0x0c }, { "EPCS4 ", 0x12, 19, 8, 16, 8, 0x1c }, { 0, 0, 0, 0, 0, 0 } }; static asmi_devinfo_t *asmi_dev_find (void) { unsigned char buf[4]; unsigned char id; int i; struct asmi_devinfo_t *dev = NULL; /* Read silicon id requires 3 "dummy bytes" before it's put * on the wire. */ buf[0] = ASMI_READ_ID; buf[1] = 0; buf[2] = 0; buf[3] = 0; asmi_cs (1); asmi_snd (buf,4); asmi_rcv (buf,1); asmi_cs (0); id = buf[0]; /* Find the info struct */ i = 0; while (devinfo[i].name) { if (id == devinfo[i].id) { dev = &devinfo[i]; break; } i++; } return (dev); } /*********************************************************************** * Misc Utilities ***********************************************************************/ static unsigned asmi_cfgsz (void) { unsigned sz = 0; unsigned char buf[128]; unsigned char *p; /* Read in the first 128 bytes of the device */ buf[0] = ASMI_READ_BYTES; buf[1] = 0; buf[2] = 0; buf[3] = 0; asmi_cs (1); asmi_snd (buf,4); asmi_rrcv (buf, sizeof(buf)); asmi_cs (0); /* Search for the starting 0x6a which is followed by the * 4-byte 'register' and 4-byte bit-count. */ p = buf; while (p < buf + sizeof(buf)-8) { if ( *p == 0x6a ) { /* Point to bit count and extract */ p += 5; sz = *p++; sz |= *p++ << 8; sz |= *p++ << 16; sz |= *p++ << 24; /* Convert to byte count */ sz += 7; sz >>= 3; } else if (*p == 0xff) { /* 0xff is ok ... just skip */ p++; continue; } else { /* Not 0xff or 0x6a ... something's not * right ... report 'unknown' (sz=0). */ break; } } return (sz); } static int asmi_erase (unsigned start, unsigned end) { unsigned off, sectsz; unsigned char buf[4]; struct asmi_devinfo_t *dev = asmi_dev_find (); if (!dev || (start>end)) return (-1); /* Erase the requested sectors. An address is required * that lies within the requested sector -- we'll just * use the first address in the sector. */ printf ("asmi erasing sector %d ", start); if (start != end) printf ("to %d ", end); sectsz = (1 << dev->sz_sect); while (start <= end) { off = start * sectsz; start++; buf[0] = ASMI_ERASE_SECT; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; asmi_wr_enable (); asmi_cs (1); asmi_snd (buf,4); asmi_cs (0); printf ("."); /* Some user feedback */ /* Wait for erase to complete */ while (asmi_status_rd() & ASMI_STATUS_WIP) ; } printf (" done.\n"); return (0); } static int asmi_read (ulong addr, ulong off, ulong cnt) { unsigned char buf[4]; buf[0] = ASMI_READ_BYTES; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; asmi_cs (1); asmi_snd (buf,4); asmi_rrcv ((unsigned char *)addr, cnt); asmi_cs (0); return (0); } static int asmi_write (ulong addr, ulong off, ulong cnt) { ulong wrcnt; unsigned pgsz; unsigned char buf[4]; struct asmi_devinfo_t *dev = asmi_dev_find (); if (!dev) return (-1); pgsz = (1<sz_page); while (cnt) { if (off % pgsz) wrcnt = pgsz - (off % pgsz); else wrcnt = pgsz; wrcnt = (wrcnt > cnt) ? cnt : wrcnt; buf[0] = ASMI_WRITE_BYTES; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; asmi_wr_enable (); asmi_cs (1); asmi_snd (buf,4); asmi_rsnd ((unsigned char *)addr, wrcnt); asmi_cs (0); /* Wait for write to complete */ while (asmi_status_rd() & ASMI_STATUS_WIP) ; cnt -= wrcnt; off += wrcnt; addr += wrcnt; } return (0); } static int asmi_verify (ulong addr, ulong off, ulong cnt, ulong *err) { ulong rdcnt; unsigned char buf[256]; unsigned char *start,*end; int i; start = end = (unsigned char *)addr; while (cnt) { rdcnt = (cnt>sizeof(buf)) ? sizeof(buf) : cnt; asmi_read ((ulong)buf, off, rdcnt); for (i=0; isz_sect); off = sectsz * sect; end = off + sectsz; while (off < end) { asmi_read ((ulong)buf, off, sizeof(buf)); for (i=0; i < sizeof(buf); i++) { if (buf[i] != 0xff) { *offset = off + i; return (0); } } off += sizeof(buf); } return (1); } /*********************************************************************** * Commands ***********************************************************************/ static void do_asmi_info (struct asmi_devinfo_t *dev, int argc, char *argv[]) { int i; unsigned char stat; unsigned tmp; int erased; /* Basic device info */ printf ("%s: %d kbytes (%d sectors x %d kbytes," " %d bytes/page)\n", dev->name, 1 << (dev->size-10), dev->num_sects, 1 << (dev->sz_sect-10), 1 << dev->sz_page ); /* Status -- for now protection is all-or-nothing */ stat = asmi_status_rd(); printf ("status: 0x%02x (WIP:%d, WEL:%d, PROT:%s)\n", stat, (stat & ASMI_STATUS_WIP) ? 1 : 0, (stat & ASMI_STATUS_WEL) ? 1 : 0, (stat & dev->prot_mask) ? "on" : "off" ); /* Configuration */ tmp = asmi_cfgsz (); if (tmp) { printf ("config: 0x%06x (%d) bytes\n", tmp, tmp ); } else { printf ("config: unknown\n" ); } /* Sector info */ for (i=0; inum_sects; i++) { erased = asmi_sect_erased (i, &tmp, dev); printf (" %d: %06x ", i, i*(1<sz_sect) ); if (erased) printf ("erased\n"); else printf ("data @ 0x%06x\n", tmp); } return; } static void do_asmi_erase (struct asmi_devinfo_t *dev, int argc, char *argv[]) { unsigned start,end; if ((argc < 3) || (argc > 4)) { printf ("USAGE: asmi erase sect [end]\n"); return; } if ((asmi_status_rd() & dev->prot_mask) != 0) { printf ( "asmi: device protected.\n"); return; } start = simple_strtoul (argv[2], NULL, 10); if (argc > 3) end = simple_strtoul (argv[3], NULL, 10); else end = start; if ((start >= dev->num_sects) || (start > end)) { printf ("asmi: invalid sector range: [%d:%d]\n", start, end ); return; } asmi_erase (start, end); return; } static void do_asmi_protect (struct asmi_devinfo_t *dev, int argc, char *argv[]) { unsigned char stat; /* For now protection is all-or-nothing to keep things * simple. The protection bits don't map in a linear * fashion ... and we would rather protect the bottom * of the device since it contains the config data and * leave the top unprotected for app use. But unfortunately * protection works from top-to-bottom so it does * really help very much from a software app point-of-view. */ if (argc < 3) { printf ("USAGE: asmi protect on | off\n"); return; } if (!dev) return; /* Protection on/off is just a matter of setting/clearing * all protection bits in the status register. */ stat = asmi_status_rd (); if (strcmp ("on", argv[2]) == 0) { stat |= dev->prot_mask; } else if (strcmp ("off", argv[2]) == 0 ) { stat &= ~dev->prot_mask; } else { printf ("asmi: unknown protection: %s\n", argv[2]); return; } asmi_status_wr (stat); return; } static void do_asmi_read (struct asmi_devinfo_t *dev, int argc, char *argv[]) { ulong addr,off,cnt; ulong sz; if (argc < 5) { printf ("USAGE: asmi read addr offset count\n"); return; } sz = 1 << dev->size; addr = simple_strtoul (argv[2], NULL, 16); off = simple_strtoul (argv[3], NULL, 16); cnt = simple_strtoul (argv[4], NULL, 16); if (off > sz) { printf ("offset is greater than device size" "... aborting.\n"); return; } if ((off + cnt) > sz) { printf ("request exceeds device size" "... truncating.\n"); cnt = sz - off; } printf ("asmi: read %08lx <- %06lx (0x%lx bytes)\n", addr, off, cnt); asmi_read (addr, off, cnt); return; } static void do_asmi_write (struct asmi_devinfo_t *dev, int argc, char *argv[]) { ulong addr,off,cnt; ulong sz; ulong err; if (argc < 5) { printf ("USAGE: asmi write addr offset count\n"); return; } if ((asmi_status_rd() & dev->prot_mask) != 0) { printf ( "asmi: device protected.\n"); return; } sz = 1 << dev->size; addr = simple_strtoul (argv[2], NULL, 16); off = simple_strtoul (argv[3], NULL, 16); cnt = simple_strtoul (argv[4], NULL, 16); if (off > sz) { printf ("offset is greater than device size" "... aborting.\n"); return; } if ((off + cnt) > sz) { printf ("request exceeds device size" "... truncating.\n"); cnt = sz - off; } printf ("asmi: write %08lx -> %06lx (0x%lx bytes)\n", addr, off, cnt); asmi_write (addr, off, cnt); if (asmi_verify (addr, off, cnt, &err) != 0) printf ("asmi: write error at offset %06lx\n", err); return; } static void do_asmi_verify (struct asmi_devinfo_t *dev, int argc, char *argv[]) { ulong addr,off,cnt; ulong sz; ulong err; if (argc < 5) { printf ("USAGE: asmi verify addr offset count\n"); return; } sz = 1 << dev->size; addr = simple_strtoul (argv[2], NULL, 16); off = simple_strtoul (argv[3], NULL, 16); cnt = simple_strtoul (argv[4], NULL, 16); if (off > sz) { printf ("offset is greater than device size" "... aborting.\n"); return; } if ((off + cnt) > sz) { printf ("request exceeds device size" "... truncating.\n"); cnt = sz - off; } printf ("asmi: verify %08lx -> %06lx (0x%lx bytes)\n", addr, off, cnt); if (asmi_verify (addr, off, cnt, &err) != 0) printf ("asmi: verify error at offset %06lx\n", err); return; } /*-----------------------------------------------------------------------*/ int do_asmi (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { int len; struct asmi_devinfo_t *dev = asmi_dev_find (); if (argc < 2) { printf ("Usage:%s", LONG_HELP); return (0); } if (!dev) { printf ("asmi: device not found.\n"); return (0); } len = strlen (argv[1]); if (strncmp ("info", argv[1], len) == 0) { do_asmi_info ( dev, argc, argv); } else if (strncmp ("erase", argv[1], len) == 0) { do_asmi_erase (dev, argc, argv); } else if (strncmp ("protect", argv[1], len) == 0) { do_asmi_protect (dev, argc, argv); } else if (strncmp ("read", argv[1], len) == 0) { do_asmi_read (dev, argc, argv); } else if (strncmp ("write", argv[1], len) == 0) { do_asmi_write (dev, argc, argv); } else if (strncmp ("verify", argv[1], len) == 0) { do_asmi_verify (dev, argc, argv); } else { printf ("asmi: unknown operation: %s\n", argv[1]); } return (0); } /*-----------------------------------------------------------------------*/ U_BOOT_CMD( asmi, 5, 0, do_asmi, SHORT_HELP, LONG_HELP ); #endif /* CONFIG_NIOS_ASMI */