/* -*- 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 <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sysexits.h>
#include <string>
#include <sched.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1