/* -*- c++ -*- This is Picprog, Microchip PIC programmer software for the serial port device. Copyright © 1997,2002,2003,2004 Jaakko Hyvätti This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The author may be contacted at: Email: Jaakko.Hyvatti@iki.fi URL: http://www.iki.fi/hyvatti/ Phone: +358 40 5011222 Please send any suggestions, bug reports, success stories etc. to the Email address above. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "picport.h" using namespace std; #ifndef O_NONBLOCK #define O_NONBLOCK O_NDELAY #endif unsigned int picport::tsc_1000ns = 0; // Set this to -1 if you want to use nanosleep when running as root. // This only works with 2.4 kernels, 2.6 series kernels no more work. int picport::use_nanosleep = 0; // Extra delays for long cables, in us int picport::t_on = 0; int picport::t_off = 0; int picport::t_edge = 0; void picport::set_clock_data (int rts, int dtr) { // Before first call to set_clock_data, read the modem status like this: // ioctl (fd, TIOCMGET, &modembits); if (rts) modembits |= TIOCM_RTS; else modembits &= ~TIOCM_RTS; if (dtr) modembits |= TIOCM_DTR; else modembits &= ~TIOCM_DTR; if (0 > ioctl (fd, TIOCMSET, &modembits)) { int e = errno; tcsetattr (fd, TCSANOW, &saved); cerr << "Unable to set RTS/DTR on tty " << portname << ":" << strerror (e) << endl; exit (EX_IOERR); } } picport::picport (const char *tty) : addr (0), debug_on (0) { memset (&W, 0, sizeof (W)); portname = new char [strlen (tty) + 1]; strcpy (portname, tty); if (0 > (fd = open (tty, O_RDWR|O_NOCTTY|O_NONBLOCK))) { int e = errno; cerr << "Unable to open tty " << tty << ":" << strerror (e) << endl; exit (EX_IOERR); } tcgetattr (fd, &saved); termstate = saved; termstate.c_iflag = IGNBRK | IGNPAR; termstate.c_oflag = 0; termstate.c_cflag = B9600 | CS8 | CREAD | CLOCAL; termstate.c_lflag = 0; tcsetattr (fd, TCSANOW, &termstate); // Initialize lines for programming // RTS/DTR low, sleep and then TxD high (program voltage) ioctl (fd, TIOCCBRK, 0); // Before first call to set_clock_data, read the modem status. ioctl (fd, TIOCMGET, &modembits); set_clock_data (0, 0); usleep (50+t_edge); // Check the CTS. If it is up, even when we just lowered DTR, // we probably are not talking to a JDM type programmer. int i; ioctl (fd, TIOCMGET, &i); if (i & TIOCM_CTS) { tcsetattr (fd, TCSANOW, &saved); cerr << tty << ":CTS is high, probably we are not connected to a programmer but a modem or terminal." << endl; exit (EX_IOERR); } if (use_nanosleep < 0) { struct sched_param scp; scp.sched_priority = 50; if (sched_setscheduler (0, SCHED_FIFO, &scp)) // Not root. Cannot use realtime scheduling. use_nanosleep = 0; else use_nanosleep = 1; } #ifdef RDTSC_WORKS if (!use_nanosleep && !tsc_1000ns) { // Read /proc/cpuinfo for clock speed, or if that fails, try our // own test. ifstream cpui ("/proc/cpuinfo"); string w; unsigned int mhz = 1; // 1MHz is just a flag that tsc was found while (cpui) { cpui >> w; if ("MHz" == w) { unsigned int tmp; // Read just the integer part. No need for the rest. cpui >> w >> tmp; // Update this when we have 100 GHz CPUs. if (tmp >= 4 && tmp <= 100000) mhz = tmp; } if ("tsc" == w && mhz) { // tsc capability found. Use it. tsc_1000ns = mhz; break; } } // If the /proc fs did not contain clock speed but indicated // tsc capability, test the approximate clock speed. if (1 == tsc_1000ns) { // Loop the test 20 times, and select the smallest count. for (int recount = 0; recount < 20; ++recount) { struct timeval tv1, tv2; unsigned long a1, d1, a2, d2; // Wait for when a microsecond changes gettimeofday (&tv2, 0); do { gettimeofday (&tv1, 0); } while (tv2.tv_usec == tv1.tv_usec); asm volatile("rdtsc":"=a" (a1), "=d" (d1)); tv1.tv_usec += 1000; if (tv1.tv_usec >= 1000000) { tv1.tv_usec -= 1000000; tv1.tv_sec ++; } do { gettimeofday (&tv2, 0); } while (tv2.tv_sec < tv1.tv_sec || tv2.tv_sec == tv1.tv_sec && tv2.tv_usec < tv1.tv_usec); asm volatile("rdtsc":"=a" (a2), "=d" (d2)); if (a2 < a1) d2 --; a2 -= a1; a2 &= 0xffffffff; // for x86_64 d2 -= d1; if (d2) continue; a2 /= 1000; if (tsc_1000ns <= 1 || tsc_1000ns > a2) tsc_1000ns = a2; } if (tsc_1000ns <= 1) { tcsetattr (fd, TCSANOW, &saved); cerr << "Unable to determine CPU clock speed for delay loops" << endl; exit (EX_IOERR); } } if (tsc_1000ns >= 1) cout << "CPU clock speed: " << tsc_1000ns << " MHz" << endl; } #endif if (0 > ioctl (fd, TIOCSBRK, 0)) { int e = errno; ioctl (fd, TIOCCBRK, 0); tcsetattr (fd, TCSANOW, &saved); cerr << "Unable to start break on tty " << tty << ":" << strerror (e) << endl; exit (EX_IOERR); } usleep (10+t_off); } picport::~picport () { ioctl (fd, TIOCCBRK, 0); usleep (1+t_off); tcsetattr (fd, TCSANOW, &saved); close (fd); delete [] portname; } void picport::reset () { set_clock_data (0, 0); ioctl (fd, TIOCCBRK, 0); usleep (50+t_off); ioctl (fd, TIOCSBRK, 0); usleep (10+t_on); addr = 0; } void picport::delay (long ns) { if (1 == use_nanosleep && !t_edge) { timespec ts = {ns / 1000000000, ns % 1000000000}, ts2; while (nanosleep (&ts, &ts2) && EINTR == errno) ts = ts2; return; } #ifdef RDTSC_WORKS if (tsc_1000ns > 1 && !t_edge) { unsigned long a1, d1, a2, d2; asm volatile("rdtsc":"=a" (a1), "=d" (d1)); d2 = d1; if (ns > 10000) // This is not as accurate but does not overflow a2 = 0xffffffff & (a1 + 1 + (ns+999)/1000*tsc_1000ns); else a2 = 0xffffffff & (a1 + 1 + (ns*tsc_1000ns + 999) / 1000); if (a2 < a1) d2++; do { asm volatile("rdtsc":"=a" (a1), "=d" (d1)); } while (d1 < d2 || d1 == d2 && a1 < a2); return; } // Fall back to gettimeofday() if tsc is not available. #endif // RDTSC_WORKS // Delay loop that should take more than a microsecond to execute. // Check the real time clock also and break out if at least one // microsecond has gone. struct timeval tv1, tv2; volatile int i; gettimeofday (&tv1, 0); tv2.tv_sec = tv1.tv_sec; tv2.tv_usec = 0xffffffff & (tv1.tv_usec + 1 + (ns + 999)/1000+t_edge); if (tv2.tv_usec < tv1.tv_usec) tv2.tv_sec++; for (i = 0; i < 10000 || t_edge; i++) { gettimeofday (&tv1, 0); if (tv1.tv_sec > tv2.tv_sec || tv1.tv_sec == tv2.tv_sec && tv1.tv_usec >= tv2.tv_usec) break; } } void picport::p_out (int b) { set_clock_data (1, b); // set data, clock up delay (1000); set_clock_data (0, b); // clock down delay (1000); } int picport::p_in () { set_clock_data (1, 1); // clock up delay (1000); set_clock_data (0, 1); // set data up, clock down delay (1000); // 100ns works with all other chips but 16f88 needs 1000ns. int i; ioctl (fd, TIOCMGET, &i); return 0 != (i & TIOCM_CTS); } int picport::command18 (enum commands18 comm, int data) { int i, shift = comm; if (nop_prog == comm) { // A programming command must leave the last bit clock pulse up p_out (0); p_out (0); p_out (0); set_clock_data (1, 0); // clock up delay (1000*1000); // P9 >1 ms programming time set_clock_data (0, 0); // clock down // P10 >5 µs high voltage discharge time // Later models listed as > 100 µs delay (100*1000); } else { for (i = 0; i < 4; i++) p_out ((shift >> i) & 1); set_clock_data (0, 0); // set data down delay (1000); } shift = 0; // default return value switch (comm) { case nop_erase: // Erase cycle has delay between command and data usleep (10000); // P11 5 ms + P10 5 µs erase time // FALLTHROUGH case instr: case nop_prog: if (0x0e00 == (data & 0xff00)) W[0] = data & 0x00ff; else if (0x6ef8 == data) addr = (addr & 0x00ffff) | (W[0] << 16); else if (0x6ef7 == data) addr = (addr & 0xff00ff) | (W[0] << 8); else if (0x6ef6 == data) addr = (addr & 0xffff00) | W[0]; goto sw; case twrite_dec2: addr -= 2; goto sw; case twrite_inc2: addr += 2; // FALLTHROUGH case twrite: case twrite_prog: sw: for (i = 0; i < 16; i++) p_out ((data >> i) & 1); set_clock_data (0, 0); // set data down break; case tread_dec: --addr; goto sr; case tread_inc: case inc_tread: ++addr; // FALLTHROUGH case shift_out: case tread: sr: for (i = 0; i < 8; i++) p_out(0); delay (1000); for (i = 0; i < 8; i++) shift |= p_in () << i; set_clock_data (0, 0); // set data down } delay (1000); return shift; } int picport::command30 (enum commands30 comm, int data) { int i, shift = comm; for (i = 0; i < 4; i++) p_out ((shift >> i) & 1); shift = 0; // default return value switch (comm) { case SIX: if (0x200000 == (data & 0xff0000)) W[data & 15] = (data & 0x0ffff0) >> 4; else if (0xEB0000 == (data & 0xfff87f)) W[(data >> 7) & 15] = 0; else if (0xBA1830 == (data & 0xfff870)) { // TBLRDL ++W[(data >> 7) & 15]; ++W[data & 15]; } else if (0xBA0830 == (data & 0xfff870)) { // TBLRDL ++W[(data >> 7) & 15]; ++W[data & 15]; } else if (0x880190 == (data & 0xfffff0)) addr = (W[data & 15] << 16) & 0xff0000; addr = (addr & 0xff0000) | W[6]; for (i = 0; i < 24; i++) p_out ((data >> i) & 1); break; case REGOUT: for (i = 0; i < 8; i++) p_out(0); for (i = 0; i < 16; i++) shift |= p_in () << i; } set_clock_data (0, 0); // set data down return shift; } void picport::setaddress (unsigned long a) { if (0 != a && addr == a) return; command18 (instr, 0x0e00 | ((a & 0xff0000) >> 16)); command18 (instr, 0x6ef8); command18 (instr, 0x0e00 | ((a & 0x00ff00) >> 8)); command18 (instr, 0x6ef7); command18 (instr, 0x0e00 | (a & 0x0000ff)); command18 (instr, 0x6ef6); } void picport::setaddress30 (unsigned long a) { if (0 != a && addr == a) return; command30 (SIX, 0x200000 | ((a & 0xff0000) >> 12)); // MOV #, W0 command30 (SIX, 0x880190); // MOV W0, TBLPAG command30 (SIX, 0x200006 | ((a & 0x00ffff) << 4)); // MOV #, W6 } // -1 == error, no programmer present int picport::command (enum commands comm, int data) { int tmp1, tmp2; // first, send out the command, 6 bits int i, shift = comm; for (i = 0; i < 6; i++) p_out ((shift >> i) & 1); set_clock_data (0, 0); // set data down shift = 0; // default return value switch (comm) { case inc_addr: if (++addr >= 0x4000) addr = 0x2000; break; case data_from_prog: case data_from_data: delay (1000); tmp1 = p_in (); for (i = 0; i < 14; i++) shift |= p_in () << i; tmp2 = p_in (); set_clock_data (0, 0); // set data down // Start and stop bits must be 1. if (!tmp1 || !tmp2) { cerr << portname << ":PIC programmer missing or chip fault" << endl; return -1; } if (data_from_data == comm) { // Check that the leftover bits were valid, all 1's. // This detects if the programmer is not connected to the port. // Unfortunately later chips clear these bits, so we must // accept both all 1's and all 0's. if ((shift & 0x3f00) != 0x3f00 && (shift & 0x3f00) != 0x0000) { cerr << portname << ": read value " << hex << setfill('0') << setw(4) << shift << dec << ": PIC programmer or chip fault\n" "Is code protection enabled? " "Use --erase option to disable code protection." << endl; return -1; } shift &= 0xff; } break; case load_conf: addr = 0x2000; // FALLTHROUGH case data_for_prog: case data_for_data: delay (1000); p_out (0); for (i = 0; i < 14; i++) p_out ((data >> i) & 1); p_out (0); set_clock_data (0, 0); // set data down break; default: ; } delay (1000); return shift; }