/* * (C) Copyright 2004, 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_SYS_NIOS_EPCSBASE) #include #include #include #include /*-----------------------------------------------------------------------*/ #define SHORT_HELP\ "epcs - read/write Cyclone EPCS configuration device.\n" #define LONG_HELP\ "\n"\ "epcs erase start [end]\n"\ " - erase sector start or sectors start through end.\n"\ "epcs info\n"\ " - display EPCS device information.\n"\ "epcs protect on | off\n"\ " - turn device protection on or off.\n"\ "epcs read addr offset count\n"\ " - read count bytes from offset to addr.\n"\ "epcs write addr offset count\n"\ " - write count bytes to offset from addr.\n"\ "epcs verify addr offset count\n"\ " - verify count bytes at offset from addr." /*-----------------------------------------------------------------------*/ /* Operation codes for serial configuration devices */ #define EPCS_WRITE_ENA 0x06 /* Write enable */ #define EPCS_WRITE_DIS 0x04 /* Write disable */ #define EPCS_READ_STAT 0x05 /* Read status */ #define EPCS_READ_BYTES 0x03 /* Read bytes */ #define EPCS_READ_ID 0xab /* Read silicon id */ #define EPCS_WRITE_STAT 0x01 /* Write status */ #define EPCS_WRITE_BYTES 0x02 /* Write bytes */ #define EPCS_ERASE_BULK 0xc7 /* Erase entire device */ #define EPCS_ERASE_SECT 0xd8 /* Erase sector */ /* Device status register bits */ #define EPCS_STATUS_WIP (1<<0) /* Write in progress */ #define EPCS_STATUS_WEL (1<<1) /* Write enable latch */ /* Misc */ #define EPCS_TIMEOUT 100 /* 100 msec timeout */ static nios_spi_t *epcs = (nios_spi_t *)CONFIG_SYS_NIOS_EPCSBASE; /*********************************************************************** * Device access ***********************************************************************/ static int epcs_cs (int assert) { ulong start; unsigned tmp; if (assert) { tmp = readl (&epcs->control); writel (tmp | NIOS_SPI_SSO, &epcs->control); } else { /* Let all bits shift out */ start = get_timer (0); while ((readl (&epcs->status) & NIOS_SPI_TMT) == 0) if (get_timer (start) > EPCS_TIMEOUT) return (-1); tmp = readl (&epcs->control); writel (tmp & ~NIOS_SPI_SSO, &epcs->control); } return (0); } static int epcs_tx (unsigned char c) { ulong start; start = get_timer (0); while ((readl (&epcs->status) & NIOS_SPI_TRDY) == 0) if (get_timer (start) > EPCS_TIMEOUT) return (-1); writel (c, &epcs->txdata); return (0); } static int epcs_rx (void) { ulong start; start = get_timer (0); while ((readl (&epcs->status) & NIOS_SPI_RRDY) == 0) if (get_timer (start) > EPCS_TIMEOUT) return (-1); return (readl (&epcs->rxdata)); } static unsigned char bitrev[] = { 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f }; static unsigned char epcs_bitrev (unsigned char c) { unsigned char val; val = bitrev[c>>4]; val |= bitrev[c & 0x0f]<<4; return (val); } static void epcs_rcv (unsigned char *dst, int len) { while (len--) { epcs_tx (0); *dst++ = epcs_rx (); } } static void epcs_rrcv (unsigned char *dst, int len) { while (len--) { epcs_tx (0); *dst++ = epcs_bitrev (epcs_rx ()); } } static void epcs_snd (unsigned char *src, int len) { while (len--) { epcs_tx (*src++); epcs_rx (); } } static void epcs_rsnd (unsigned char *src, int len) { while (len--) { epcs_tx (epcs_bitrev (*src++)); epcs_rx (); } } static void epcs_wr_enable (void) { epcs_cs (1); epcs_tx (EPCS_WRITE_ENA); epcs_rx (); epcs_cs (0); } static unsigned char epcs_status_rd (void) { unsigned char status; epcs_cs (1); epcs_tx (EPCS_READ_STAT); epcs_rx (); epcs_tx (0); status = epcs_rx (); epcs_cs (0); return (status); } static void epcs_status_wr (unsigned char status) { epcs_wr_enable (); epcs_cs (1); epcs_tx (EPCS_WRITE_STAT); epcs_rx (); epcs_tx (status); epcs_rx (); epcs_cs (0); return; } /*********************************************************************** * Device information ***********************************************************************/ static struct epcs_devinfo_t devinfo[] = { { "EPCS1 ", 0x10, 17, 4, 15, 8, 0x0c }, { "EPCS4 ", 0x12, 19, 8, 16, 8, 0x1c }, { "EPCS16", 0x14, 21, 32, 16, 8, 0x1c }, { "EPCS64", 0x16, 23,128, 16, 8, 0x1c }, { 0, 0, 0, 0, 0, 0 } }; int epcs_reset (void) { /* When booting from an epcs controller, the epcs bootrom * code may leave the slave select in an asserted state. * This causes two problems: (1) The initial epcs access * will fail -- not a big deal, and (2) a software reset * will cause the bootrom code to hang since it does not * ensure the select is negated prior to first access -- a * big deal. Here we just negate chip select and everything * gets better :-) */ epcs_cs (0); /* Negate chip select */ return (0); } epcs_devinfo_t *epcs_dev_find (void) { unsigned char buf[4]; unsigned char id; int i; struct epcs_devinfo_t *dev = NULL; /* Read silicon id requires 3 "dummy bytes" before it's put * on the wire. */ buf[0] = EPCS_READ_ID; buf[1] = 0; buf[2] = 0; buf[3] = 0; epcs_cs (1); epcs_snd (buf,4); epcs_rcv (buf,1); if (epcs_cs (0) == -1) return (NULL); 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 ***********************************************************************/ int epcs_cfgsz (void) { int sz = 0; unsigned char buf[128]; unsigned char *p; struct epcs_devinfo_t *dev = epcs_dev_find (); if (!dev) return (-1); /* Read in the first 128 bytes of the device */ buf[0] = EPCS_READ_BYTES; buf[1] = 0; buf[2] = 0; buf[3] = 0; epcs_cs (1); epcs_snd (buf,4); epcs_rrcv (buf, sizeof(buf)); epcs_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); } int epcs_erase (unsigned start, unsigned end) { unsigned off, sectsz; unsigned char buf[4]; struct epcs_devinfo_t *dev = epcs_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 ("epcs 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] = EPCS_ERASE_SECT; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; epcs_wr_enable (); epcs_cs (1); epcs_snd (buf,4); epcs_cs (0); printf ("."); /* Some user feedback */ /* Wait for erase to complete */ while (epcs_status_rd() & EPCS_STATUS_WIP) ; } printf (" done.\n"); return (0); } int epcs_read (ulong addr, ulong off, ulong cnt) { unsigned char buf[4]; struct epcs_devinfo_t *dev = epcs_dev_find (); if (!dev) return (-1); buf[0] = EPCS_READ_BYTES; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; epcs_cs (1); epcs_snd (buf,4); epcs_rrcv ((unsigned char *)addr, cnt); epcs_cs (0); return (0); } int epcs_write (ulong addr, ulong off, ulong cnt) { ulong wrcnt; unsigned pgsz; unsigned char buf[4]; struct epcs_devinfo_t *dev = epcs_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] = EPCS_WRITE_BYTES; buf[1] = off >> 16; buf[2] = off >> 8; buf[3] = off; epcs_wr_enable (); epcs_cs (1); epcs_snd (buf,4); epcs_rsnd ((unsigned char *)addr, wrcnt); epcs_cs (0); /* Wait for write to complete */ while (epcs_status_rd() & EPCS_STATUS_WIP) ; cnt -= wrcnt; off += wrcnt; addr += wrcnt; } return (0); } int epcs_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; epcs_read ((ulong)buf, off, rdcnt); for (i=0; isz_sect); off = sectsz * sect; end = off + sectsz; while (off < end) { epcs_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_epcs_info (struct epcs_devinfo_t *dev, int argc, char * const 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 = epcs_status_rd(); printf ("status: 0x%02x (WIP:%d, WEL:%d, PROT:%s)\n", stat, (stat & EPCS_STATUS_WIP) ? 1 : 0, (stat & EPCS_STATUS_WEL) ? 1 : 0, (stat & dev->prot_mask) ? "on" : "off" ); /* Configuration */ tmp = epcs_cfgsz (); if (tmp) { printf ("config: 0x%06x (%d) bytes\n", tmp, tmp ); } else { printf ("config: unknown\n" ); } /* Sector info */ for (i=0; (i < dev->num_sects) && (argc > 1); i++) { erased = epcs_sect_erased (i, &tmp, dev); if ((i & 0x03) == 0) printf ("\n"); printf ("%4d: %07x ", i, i*(1<sz_sect) ); if (erased) printf ("E "); else printf (" "); } printf ("\n"); return; } static void do_epcs_erase (struct epcs_devinfo_t *dev, int argc, char * const argv[]) { unsigned start,end; if ((argc < 3) || (argc > 4)) { printf ("USAGE: epcs erase sect [end]\n"); return; } if ((epcs_status_rd() & dev->prot_mask) != 0) { printf ( "epcs: 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 ("epcs: invalid sector range: [%d:%d]\n", start, end ); return; } epcs_erase (start, end); return; } static void do_epcs_protect (struct epcs_devinfo_t *dev, int argc, char * const 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: epcs 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 = epcs_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 ("epcs: unknown protection: %s\n", argv[2]); return; } epcs_status_wr (stat); return; } static void do_epcs_read (struct epcs_devinfo_t *dev, int argc, char * const argv[]) { ulong addr,off,cnt; ulong sz; if (argc < 5) { printf ("USAGE: epcs 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 ("epcs: read %08lx <- %06lx (0x%lx bytes)\n", addr, off, cnt); epcs_read (addr, off, cnt); return; } static void do_epcs_write (struct epcs_devinfo_t *dev, int argc, char * const argv[]) { ulong addr,off,cnt; ulong sz; ulong err; if (argc < 5) { printf ("USAGE: epcs write addr offset count\n"); return; } if ((epcs_status_rd() & dev->prot_mask) != 0) { printf ( "epcs: 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 ("epcs: write %08lx -> %06lx (0x%lx bytes)\n", addr, off, cnt); epcs_write (addr, off, cnt); if (epcs_verify (addr, off, cnt, &err) != 0) printf ("epcs: write error at offset %06lx\n", err); return; } static void do_epcs_verify (struct epcs_devinfo_t *dev, int argc, char * const argv[]) { ulong addr,off,cnt; ulong sz; ulong err; if (argc < 5) { printf ("USAGE: epcs 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 ("epcs: verify %08lx -> %06lx (0x%lx bytes)\n", addr, off, cnt); if (epcs_verify (addr, off, cnt, &err) != 0) printf ("epcs: verify error at offset %06lx\n", err); return; } /*-----------------------------------------------------------------------*/ int do_epcs (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { int len; struct epcs_devinfo_t *dev = epcs_dev_find (); if (!dev) { printf ("epcs: device not found.\n"); return (-1); } if (argc < 2) { do_epcs_info (dev, argc, argv); return (0); } len = strlen (argv[1]); if (strncmp ("info", argv[1], len) == 0) { do_epcs_info (dev, argc, argv); } else if (strncmp ("erase", argv[1], len) == 0) { do_epcs_erase (dev, argc, argv); } else if (strncmp ("protect", argv[1], len) == 0) { do_epcs_protect (dev, argc, argv); } else if (strncmp ("read", argv[1], len) == 0) { do_epcs_read (dev, argc, argv); } else if (strncmp ("write", argv[1], len) == 0) { do_epcs_write (dev, argc, argv); } else if (strncmp ("verify", argv[1], len) == 0) { do_epcs_verify (dev, argc, argv); } else { printf ("epcs: unknown operation: %s\n", argv[1]); } return (0); } /*-----------------------------------------------------------------------*/ U_BOOT_CMD( epcs, 5, 0, do_epcs, SHORT_HELP, LONG_HELP ); #endif /* CONFIG_NIOS_EPCS */