/*
 * Velleman K8048 Programmer for FreeBSD and others.
 *
 * Copyright (c) 2005-2007 Darron Broad
 * All rights reserved.
 *
 * Licensed under the terms of the BSD license, see file LICENSE
 * for details.
 *
 * $Id: pic16f.c,v 1.103 2008/09/19 04:07:02 darron Exp $
 *
 * PIC12F/16F 14 bit word architecture command set
 *
 * These commands have been verified to work with the following devices:
 *
 * PIC16F627    http://ww1.microchip.com/downloads/en/DeviceDoc/30034d.pdf
 * PIC12F675    http://ww1.microchip.com/downloads/en/DeviceDoc/41190c.pdf
 *
 * Sample data:
 *
 * PIC16F627  DEVICEID: 07a0
 * PIC12F675  DEVICEID: 0fd0 : CONFIG : 11ff : OSCCAL   : 343c BG: 01
 * PIC16F676  DEVICEID:                        OSCCAL   : 3430 BG: 01
 * PIC16F873A DEVICEID: 0e47 : DEVICE :  e40 : REVISION :    7 (diodesign)
 * PIC16F877A DEVICEID: 0e27 : DEVICE :  e20 : REVISION :    7
 * PIC16F88   DEVICEID: 0765 : DEVICE :  760 : REVISION :    5 (tobin)
 *
 * PIC12F683:
 *  [2000] [USERID0]  ff .
 *  [2001] [USERID1]  ff .
 *  [2002] [USERID2]  ff .
 *  [2003] [USERID3]  ff .
 *  [2006] [DEVICEID] 0463 DEV:460 (23) REV:3 PIC12F683
 *  [2007] [CONFIG1] 3fff
 *  [2008] [CONFIG2] 22be
 *  [2009] [CONFIG3] 3fff
 * PIC16F688:
 *  [2000] [USERID0]  ff .
 *  [2001] [USERID1]  ff .
 *  [2002] [USERID2]  ff .
 *  [2003] [USERID3]  ff .
 *  [2006] [DEVICEID] 1186 DEV:1180 (8c) REV:6 PIC16F688
 *  [2007] [CONFIG1] 3fff
 *  [2008] [CONFIG2] 24f8
 *  [2009] [CONFIG3] 3fff
 *
 * Contrib:
 *
 *  PIC16F88   support and diagnostics by Tobin Richard  
 *  PIC16F873A support by Chris Williams (diodesign)
 *
 * Compatible ICSP devices:
 *
 * DS30262E
 *  PIC16F84A   T
 *
 * DS30277D
 *  PIC16F627   T
 *  PIC16F628   U
 *
 * DS39025F
 *  PIC16F870   U
 *  PIC16F871   U
 *  PIC16F872   U
 *  PIC16F873   U
 *  PIC16F874   U
 *  PIC16F876   U
 *  PIC16F877   U
 *
 * DS39589B
 *  PIC16F873A  T
 *  PIC16F874A  U
 *  PIC16F876A  U
 *  PIC16F877A  T
 *
 * DS39607B
 *  PIC16F87    U
 *  PIC16F88    T
 *
 * DS41173C/DS41191C
 *  PIC12F629   U
 *  PIC12F675   T
 *  PIC16F630   U
 *  PIC16F676   T
 *
 * DS41196F
 *  PICF627A    U
 *  PICF628A    U
 *  PICF648A    T
 *
 * DS39603C
 *  PIC16F818   U
 *  PIC16F819   T
 *
 * DS40245B
 *  PIC16F716   T (see note 1)
 *
 * DS41204G       (see note 2)
 *  PIC12F635   U
 *  PIC12F683   T
 *  PIC16F631   U
 *  PIC16F636   U
 *  PIC16F639   U
 *  PIC16F677   U
 *  PIC16F684   U
 *  PIC16F685   U
 *  PIC16F688   T
 *  PIC16F689   U
 *  PIC16F690   U
 *
 * T=TESTED, U=UNTESTED
 *
 * 1. The PIC16F716 must be power cycled before verification.
 *
 *    The k8048 cannot control the 5V rail so follow these steps:
 *
 *     a. program device
 *     b. switch to standby
 *     c. switch to prog
 *     d. verify device
 *
 * 2. Disabling protection is currently unimplemented.
 */

#include "k8048.h"

/*****************************************************************************
 *
 * Hardware configuration
 *
 *****************************************************************************/
struct pic16f_config pic16f_conf;

/*****************************************************************************
 *
 * Hardware alogithm map
 *
 *****************************************************************************/

struct pic16f_dsmap pic16f_map[]=
{
    /*  devicename      deviceid    datasheet   osccal address  config mask */
    {   "PIC16F84A",    PIC16F84A,  DS30262E,   0,              0x3fff},
    {   "PIC16F84",     PIC16F84,   DS30262E,   0,              0x3fff},
    {   "PIC16C84",     PIC16C84,   DS30262E,   0,              0x3fff},

    {   "PIC16F627",    PIC16F627,  DS30277D,   0,              0x3fff},
    {   "PIC16F628",    PIC16F628,  DS30277D,   0,              0x3fff},

    {   "PIC16F870",    PIC16F870,  DS39025F,   0,              0x3fff},
    {   "PIC16F871",    PIC16F871,  DS39025F,   0,              0x3fff},
    {   "PIC16F872",    PIC16F872,  DS39025F,   0,              0x3fff},
    {   "PIC16F873",    PIC16F873,  DS39025F,   0,              0x3fff},
    {   "PIC16F874",    PIC16F874,  DS39025F,   0,              0x3fff},
    {   "PIC16F876",    PIC16F876,  DS39025F,   0,              0x3fff},
    {   "PIC16F877",    PIC16F877,  DS39025F,   0,              0x3fff},
    
    {   "PIC16F873A",   PIC16F873A, DS39589B,   0,              0x3fff},
    {   "PIC16F874A",   PIC16F874A, DS39589B,   0,              0x3fff},
    {   "PIC16F876A",   PIC16F876A, DS39589B,   0,              0x3fff},
    {   "PIC16F877A",   PIC16F877A, DS39589B,   0,              0x3fff},
    
    {   "PIC16F87",     PIC16F87,   DS39607B,   0,              0x3fff},
    {   "PIC16F88",     PIC16F88,   DS39607B,   0,              0x3fff},
    
    {   "PIC12F629",    PIC12F629,  DS41191C,   0x03ff,         0x01ff},
    {   "PIC12F675",    PIC12F675,  DS41191C,   0x03ff,         0x01ff},
    {   "PIC16F630",    PIC16F630,  DS41191C,   0x03ff,         0x01ff},
    {   "PIC16F676",    PIC16F676,  DS41191C,   0x03ff,         0x01ff},
    
    {   "PIC16F627A",   PIC16F627A, DS41196F,   0,              0x3fff},
    {   "PIC16F628A",   PIC16F628A, DS41196F,   0,              0x3fff},
    {   "PIC16F648A",   PIC16F648A, DS41196F,   0,              0x3fff},

    {   "PIC16F818",    PIC16F818,  DS39603C,   0,              0x3fff},
    {   "PIC16F819",    PIC16F819,  DS39603C,   0,              0x3fff},
    
    {   "PIC16F716",    PIC16F716,  DS40245B,   0,              0x3fff},

    /* These devices have one or two calibration words at 2008/2009 */
    {   "PIC12F635",    PIC12F635,  DS41204G,   0,              0x3fff},
    {   "PIC12F683",    PIC12F683,  DS41204G,   0,              0x3fff},
    {   "PIC16F631",    PIC16F631,  DS41204G,   0,              0x3fff},
    {   "PIC16F636",    PIC16F636,  DS41204G,   0,              0x3fff},
    {   "PIC16F639",    PIC16F639,  DS41204G,   0,              0x3fff},
    {   "PIC16F677",    PIC16F677,  DS41204G,   0,              0x3fff},
    {   "PIC16F684",    PIC16F684,  DS41204G,   0,              0x3fff},
    {   "PIC16F685",    PIC16F685,  DS41204G,   0,              0x3fff},
    {   "PIC16F688",    PIC16F688,  DS41204G,   0,              0x3fff},
    {   "PIC16F689",    PIC16F689,  DS41204G,   0,              0x3fff},
    {   "PIC16F690",    PIC16F690,  DS41204G,   0,              0x3fff},

    {   "(null)",       0,          0,          0,              0x3fff}
};

int pic16f_index= (sizeof(pic16f_map) / sizeof(struct pic16f_dsmap)) - 1;

/*****************************************************************************
 *
 * Hardware functions
 *
 *****************************************************************************/

/*I/O*************************************************************************/

/*
 * LOAD CONFIGURATION
 *  PC <= CONFIG_LOW
 *
 * X00000
 */
void
pic16f_load_configuration(struct k8048 *k, unsigned short word)
{
    io_command_out(k, "000000");
    io_word_out14(k, word);
}

/*
 * LOAD DATA FOR PROGRAM MEMORY
 *  PC <= CODE_LOW
 *  WR <= word
 *
 * X00010
 */
void
pic16f_load_data_for_program_memory(struct k8048 *k, unsigned short word)
{
    io_command_out(k, "000010");
    io_word_out14(k, word);
}

/*
 * LOAD DATA FOR DATA MEMORY
 *  PC <= DATA_LOW
 *  WR <= word
 *
 * X00011
 */
void
pic16f_load_data_for_data_memory(struct k8048 *k, unsigned short word)
{
    io_command_out(k, "000011");
    io_word_out14(k, word);
}

/*
 * INCREMENT ADDRESS
 *  PC <= 1 + PC
 *
 * X00110
 */
void
pic16f_increment_address(struct k8048 *k)
{
    io_command_out(k, "000110");
}

/*
 * READ DATA FROM PROGRAM MEMORY
 *  RETURN (PC)
 *
 * X00100
 */
unsigned short
pic16f_read_data_from_program_memory(struct k8048 *k)
{
    io_command_out(k, "000100");
    return io_word_in14(k);
}

/*
 * READ DATA FROM DATA MEMORY
 *  RETURN (PC)
 *
 * X00101
 */
unsigned char
pic16f_read_data_from_data_memory(struct k8048 *k)
{
    io_command_out(k, "000101");
    return (unsigned char) (io_word_in14(k) & 0xff);
}

/*PROG************************************************************************/

/*
 * BEGIN ERASE PROGRAMMING
 *  (PC) <= WR
 *
 * 001000
 */
void
pic16f_begin_erase_programming(struct k8048 *k)
{
    io_command_out(k, "001000");
    usleep (PIC16F_TPROG_DEFAULT +  PIC16F_TERASE_DEFAULT);
}

/*
 * BEGIN PROGRAMMING
 *  (PC) <= WR
 *
 * 001000 / 011000
 */
void
pic16f_begin_programming_001000(struct k8048 *k)
{
    io_command_out(k, "001000");
    usleep (PIC16F_TPROG_DEFAULT);
}
void
pic16f_begin_programming_011000(struct k8048 *k)
{
    io_command_out(k, "011000");
    usleep (PIC16F_TPROG_DEFAULT);
}

/*
 * END PROGRAMMING
 *
 * 001110 / 010111
 */
void
pic16f_end_programming_010111(struct k8048 *k)
{
    io_command_out(k, "010111");
}
void
pic16f_end_programming_001110(struct k8048 *k)
{
    io_command_out(k, "001110");
    usleep (PIC16F_TDISCHARGE_DEFAULT);
}

/*ERASE***********************************************************************/

/*
 * BULK ERASE PROGRAM MEMORY (PRELOAD 0x3FFF)
 *
 * X01001
 */
void
pic16f_bulk_erase_program_memory(struct k8048 *k)
{
    io_command_out(k, "001001");
    usleep (PIC16F_TERASE_DEFAULT);
}

/*
 * BULK ERASE DATA MEMORY (PRELOAD 0x3FFF)
 *
 * X01011
 */
void
pic16f_bulk_erase_data_memory(struct k8048 *k)
{
    io_command_out(k, "001011");
    usleep (PIC16F_TERASE_DEFAULT);
}

/*
 * CHIP ERASE
 *
 * X11111
 */
void
pic16f_chip_erase(struct k8048 *k)
{
    io_command_out(k, "011111");
    usleep (PIC16F_TERASE_DEFAULT);
}

/*
 * BULK ERASE SETUP 1 / 2
 *
 * 000001 / 000111
 */
void
pic16f_bulk_erase_setup1(struct k8048 *k)
{
    io_command_out(k, "000001");
}
void
pic16f_bulk_erase_setup2(struct k8048 *k)
{
    io_command_out(k, "000111");
}

/*****************************************************************************
 *
 * Compound functions
 *
 *****************************************************************************/
    
/*
 * READ PROGRAM MEMORY
 *
 *  RETURN CODE WORD, INCREMENT PC
 */
unsigned short
pic16f_read_program_memory_increment(struct k8048 *k)
{
    unsigned short data= (pic16f_read_data_from_program_memory(k) & 0x3fff);

    pic16f_increment_address(k);
    
    return data;
}

/*
 * READ DATA MEMORY
 *
 *  RETURN DATA BYTE, INCREMENT PC
 */
unsigned char
pic16f_read_data_memory_increment(struct k8048 *k)
{
    unsigned char data= pic16f_read_data_from_data_memory(k);

    pic16f_increment_address(k);
    
    return data;
}

/*
 * DISABLE PROTECTION
 *
 *  SOME DEVICES HAVE SPECIFIC PROTECTION DISABLE OPERATIONS
 *  OTHERS USE THE SAME PROCESS AS PER BLANKING THE DEVICE
 *
 *  WHEN OSCCAL OR CONFIG != 0 THEN THESE VALUES WILL BE
 *  WRITTEN TO THE DEVICE AFTER PROTECTION IS DISABLED
 *  IN PREFERENCE TO THE HARDWARE DEFAULT
 */
void
pic16f_disableprotection(struct k8048 *k, unsigned short osccal, unsigned short config)
{
    int i;

    switch(pic16f_map[pic16f_index].datasheet)      /* disable protection + erase */
    {
        case DS30262E:  /* PIC16F84A  */
        case DS30277D:  /* PIC16F62X  */
        case DS39025F:  /* PIC16F87X  */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        for(i=0; i<7; i++)
                            pic16f_increment_address(k);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        io_standby(k);
                        break;

        case DS39589B:  /* PIC16F87XA */
        case DS39603C:  /* PIC16F818/819 */
        case DS39607B:  /* PIC16F87/88   */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_chip_erase(k);
                        io_standby(k);
                        break;
        
        case DS40245B:  /* PIC16F716  */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        io_standby(k);
                        break;

        case DS41191C:  /* PIC12F629/675
                           PIC16F630/676 */
                        if(osccal == 0)
                            osccal= pic16f_read_osccal(k);
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_bulk_erase_data_memory(k);
                        io_standby(k);
                        if(config == 0)
                            config= (pic16f_conf.index[PIC16F_CONFIG_WORD1] & 0x3000) | 
                                pic16f_map[pic16f_index].configmask;
                        pic16f_write_osccal(k, osccal);
                        pic16f_write_config(k, config);
                        break;
        
        case DS41196F:  /* PIC16F627A/628A/648A */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_bulk_erase_data_memory(k);
                        io_standby(k);
                        break;
        
        default:        printf("%s: information: unimplemented\n", __FUNCTION__);
                        break;
    }
}

/*
 * BULK ERASE
 *
 *  UNLESS THE BLANKING FUNCTION DISABLES CODE PROTECTION THEN
 *  THIS OPERATION MAY FAIL. IN THAT CASE PRECEDE THIS FUNCTION
 *  WITH DISABLEPROTECTION ABOVE.
 */
void
pic16f_bulkerase(struct k8048 *k)
{
    unsigned short osccal;

    switch(pic16f_map[pic16f_index].datasheet)      /* erase */
    {
        case DS30262E:  /* PIC16F84A  */
        case DS30277D:  /* PIC16F62X  */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_begin_programming_011000(k);
                        pic16f_load_data_for_data_memory(k, 0x3fff);
                        pic16f_bulk_erase_data_memory(k);
                        pic16f_begin_programming_011000(k);
                        io_standby(k);
                        break;

        case DS39025F:  /* PIC16F87X  */
                        io_init_program_verify(k);
                        pic16f_load_data_for_program_memory(k, 0x3fff);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        pic16f_load_data_for_data_memory(k, 0x3fff);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_bulk_erase_setup1(k);
                        pic16f_bulk_erase_setup2(k);
                        io_standby(k);
                        break;
        
        case DS39589B:  /* PIC16F87XA */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_bulk_erase_data_memory(k);
                        pic16f_begin_erase_programming(k);
                        io_standby(k);
                        break;

        case DS39603C:  /* PIC16F818/819 */
        case DS39607B:  /* PIC16F87/88   */
                        io_init_program_verify(k);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_end_programming_010111(k);
                        pic16f_bulk_erase_data_memory(k);
                        pic16f_begin_erase_programming(k);
                        pic16f_end_programming_010111(k);
                        io_standby(k);
                        break;
        
        case DS40245B:  /* PIC16F716 */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        io_standby(k);
                        break;

        case DS41191C:  /* PIC12F629/675
                           PIC16F630/676 */
                        osccal= pic16f_read_osccal(k);
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_bulk_erase_data_memory(k);
                        io_standby(k);
                        pic16f_write_osccal(k, osccal);
                        break;

        case DS41196F:  /* PIC16F627A/628A/648A */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_bulk_erase_data_memory(k);
                        io_standby(k);
                        break;
        
        case DS41204G:  /* PIC12F6XX/16F6XX */
                        io_init_program_verify(k);
                        pic16f_load_configuration(k, 0x3fff);
                        pic16f_bulk_erase_program_memory(k);
                        pic16f_bulk_erase_data_memory(k);
                        io_standby(k);
                        break;

        default:        printf("%s: information: unimplemented\n", __FUNCTION__);
                        break;
    }
}

/*****************************************************************************
 *
 * Read block data
 *
 *****************************************************************************/

/*
 * READ CONFIGURATION MEMORY 2000 .. 200X AND STORE DEVICE MAP INDEX
 */
void
pic16f_read_config_memory(struct k8048 *k)
{
    int i;
    unsigned short deviceid;

    io_init_program_verify(k);

    pic16f_load_configuration(k, 0);
    for(i=0; i<PIC16F_CONFIG_SIZE; i++)
        pic16f_conf.index[i]= pic16f_read_program_memory_increment(k);

    io_standby(k);

    if(k->devicename[0])    /* Override device probe */
    {
        for(i=0; pic16f_map[i].deviceid; i++)
        {
            if(strcasecmp(pic16f_map[i].devicename, k->devicename)==0)
                break;
        }
        if(pic16f_map[i].deviceid == 0)
        {
            printf("%s: fatal error: unknown device: [%s]\n", __FUNCTION__, k->devicename);
            exit(-1); /* panic */
        }
        pic16f_conf.index[PIC16F_CONFIG_DEVICEID]= pic16f_map[i].deviceid;
    }
    else                    /* Device probe */
    {
        deviceid= pic16f_conf.index[PIC16F_CONFIG_DEVICEID] & PIC16F_DEVICEID_MASK;

        i=0;
        while( (pic16f_map[i].deviceid != deviceid) && (pic16f_map[i].deviceid != 0))
            i++;

        if(pic16f_map[i].deviceid == 0)
        {
            /*
             * k8048 NO POWER               3fe0 [3fff]
             * k8048 SWITCH IN STANDBY      0000 [0000]
             * k0848 SWITCH IN RUN          3fe0 [3fff]
             * k0848 SWITCH IN PROG         0460 [0463]
             */
            switch(pic16f_conf.index[PIC16F_CONFIG_DEVICEID])
            {
                case 0x0000:    printf("%s: fatal error: switch is on standby, or device is not a PIC12F/PIC16F.\n", __FUNCTION__);
                                break;
                case 0x3fff:    printf("%s: fatal error: switch is on run, or no power is supplied, or no device is installed.\n", __FUNCTION__);
                                break;
                default:        printf("%s: fatal error: unknown device: %04x [%04x]\n", __FUNCTION__, deviceid,
                                    pic16f_conf.index[PIC16F_CONFIG_DEVICEID]);
            }
            exit(-1); /* panic */
        }
    }
    pic16f_index= i;
}

/*
 * READ PROGRAM MEMORY BLOCK 0000 .. 0000 + SIZE
 *  
 *  RETURN BASE ADDRESS
 */
unsigned short
pic16f_read_program_memory_block(struct k8048 *k, unsigned short *block, int size)
{
    int i;

    io_init_program_verify(k);
        
    pic16f_load_data_for_program_memory(k, 0);
    for(i=0; i < size; i++)
        block[i]= pic16f_read_program_memory_increment(k);

    io_standby(k);

    return PIC16F_CODE_LOW;
}

/*
 * READ DATA MEMORY BLOCK 2100 .. 2100 + SIZE
 *
 *  RETURN BASE ADDRESS
 */
unsigned short
pic16f_read_data_memory_block(struct k8048 *k, unsigned char *block, int size)
{
    int i;

    io_init_program_verify(k);
        
    pic16f_load_data_for_data_memory(k, 0);
    for(i=0; i < size; i++)
        block[i]= pic16f_read_data_memory_increment(k);

    io_standby(k);

    return PIC16F_DATA_LOW;
}

/*****************************************************************************
 *
 * Read/Write Oscillator Calibration 
 *
 * DS41191C
 *
 *****************************************************************************/

/*
 * READ OSCILLATOR CALIBRATION WORD
 *
 *  RETURN DATA WORD
 */
unsigned short
pic16f_read_osccal(struct k8048 *k)
{
    int i;
    unsigned short data;
    
    if(pic16f_map[pic16f_index].osccaladdr == 0)
    {
        printf("%s: error: osccal not supported on this device\n", __FUNCTION__);
        return 0;
    }

    io_init_program_verify(k);

    pic16f_load_data_for_program_memory(k, 0);

    for(i=0; i < (pic16f_map[pic16f_index].osccaladdr + 1); i++)
        data= pic16f_read_program_memory_increment(k);

    io_standby(k);

    return data;
}

/*
 * WRITE OSCILLATOR CALIBRATION WORD
 */
int
pic16f_write_osccal(struct k8048 *k, unsigned short osccal)
{
    int i;
    unsigned short vdata;

    if(pic16f_map[pic16f_index].osccaladdr == 0)
    {
        printf("%s: error: osccal not supported on this device\n", __FUNCTION__);
        return FAIL;
    }

    io_init_program_verify(k);

    pic16f_load_data_for_program_memory(k, osccal);

    for(i=0; i < pic16f_map[pic16f_index].osccaladdr; i++)
        pic16f_increment_address(k);

    pic16f_begin_programming_001000(k);

    vdata= pic16f_read_data_from_program_memory(k);

    io_standby(k);

    if(vdata!=osccal)
    {
        printf("%s: error: osccal write failed: read [%04x] expected [%04x]\n", __FUNCTION__, vdata, osccal);
        return FAIL;
    }
    return PASS;
}

/*****************************************************************************
 *
 * Program Config
 *
 *****************************************************************************/

/*
 * WRITE CONFIG
 */
int
pic16f_write_config(struct k8048 *k, unsigned short config)
{
    int i;
    unsigned short vdata;

    io_init_program_verify(k);

    pic16f_load_configuration(k, config);

    for(i=0; i<7; i++)
        pic16f_increment_address(k);

    switch(pic16f_map[pic16f_index].datasheet)  /* write one word */
    {
        case DS30262E:  /* PIC16F84A  */
        case DS30277D:  /* PIC16F62X  */
        case DS39025F:  /* PIC16F87X  */
                        pic16f_begin_programming_011000(k);
                        break;

        case DS39589B:  /* PIC16F87XA */
                        pic16f_begin_erase_programming(k);
                        break;

        case DS39603C:  /* PIC16F818/819 */
        case DS39607B:  /* PIC16F87/88   */
                        pic16f_begin_programming_011000(k);
                        pic16f_end_programming_010111(k);
                        break;

        case DS40245B:  /* PIC16F716  */
                        pic16f_begin_programming_011000(k);
                        pic16f_end_programming_001110(k);
                        break;

        case DS41191C:  /* PIC12F629/675
                           PIC16F630/676 */
                        pic16f_begin_programming_001000(k);
                        break;

        case DS41196F:  /* PIC16F627A/628A/648A */
                        pic16f_load_data_for_program_memory(k, config);
                        pic16f_begin_programming_001000(k);
                        break;
        
        case DS41204G:  /* PIC12F6XX/16F6XX */
                        pic16f_begin_programming_001000(k);
                        break;

        default:        printf("%s: information: unimplemented\n", __FUNCTION__);
                        break;
    }

    vdata= pic16f_read_data_from_program_memory(k);

    io_standby(k);

    if(pic16f_map[pic16f_index].datasheet == DS41191C)
    {
        vdata  &= pic16f_map[pic16f_index].configmask;
        config &= pic16f_map[pic16f_index].configmask;
    }

    if(vdata != config)
    {
        printf("%s: error: config word write failed: read [%04x] expected [%04x]\n", __FUNCTION__, vdata, config);
        return FAIL;
    }
    return PASS;
}

/*****************************************************************************
 *
 * Region (program/config/data) management
 *
 *****************************************************************************/

/*
 * DETERMINE MEMORY REGION: CODE, CONFIG or DATA
 */
int
pic16f_getregion(unsigned int address)
{
    if(address>=PIC16F_CODE_LOW && address<=PIC16F_CODE_HIGH)
        return PIC16F_REGIONCODE;

    if(address>=PIC16F_CONFIG_LOW && address<=PIC16F_CONFIG_HIGH)
        return PIC16F_REGIONCONFIG;

    if(address>=PIC16F_DATA_LOW && address<=PIC16F_DATA_HIGH)
        return PIC16F_REGIONDATA;

    printf("%s: warning: region unknown\n", __FUNCTION__);
    return PIC16F_REGIONUNKNOWN;
}

/*
 * INITIALISE FOR REGION
 */
unsigned int
pic16f_initregion(struct k8048 *k, int region)
{
    switch(region)
    {
        case PIC16F_REGIONCODE:     pic16f_load_data_for_program_memory(k, 0);
                                    return PIC16F_CODE_LOW;
        case PIC16F_REGIONCONFIG:   pic16f_load_configuration(k, 0);
                                    return PIC16F_CONFIG_LOW;
        case PIC16F_REGIONDATA:     pic16f_load_data_for_data_memory(k, 0);
                                    return PIC16F_DATA_LOW;
    }
    printf("%s: warning: region unknown\n", __FUNCTION__);
    return 0;
}

/*
 * LOAD DATA FOR REGION (STORE WORD IN WORKING REGISTER)
 */
void
pic16f_loadregion(struct k8048 *k, int region, unsigned short word)
{
    switch(region)
    {
        case PIC16F_REGIONCODE:
        case PIC16F_REGIONCONFIG:   pic16f_load_data_for_program_memory(k, word);
                                    break;
        case PIC16F_REGIONDATA:     pic16f_load_data_for_data_memory(k, word);
                                    break;
        default:                    printf("%s: warning: region unknown\n", __FUNCTION__);
                                    break;
    }
}

/*
 * VERIFY DATA FOR REGION
 */
int
pic16f_verifyregion(struct k8048 *k, unsigned short address, unsigned short data, int region)
{
    unsigned short vdata;

    if(k->debug_level > 0)
        printf("%s: address=0x%04x data=0x%04x region=%d\n", __func__, address, data, region);

    switch(region)
    {
        case PIC16F_REGIONCODE:
        case PIC16F_REGIONCONFIG:   vdata= pic16f_read_data_from_program_memory(k);
                                    break;
        case PIC16F_REGIONDATA:     vdata= pic16f_read_data_from_data_memory(k);
                                    break;
        default:                    printf("%s: warning: region unknown\n", __FUNCTION__);
                                    return 0;
    }

    if( (address == 0x2007) && (pic16f_map[pic16f_index].datasheet == DS41191C))
    {
        vdata &= pic16f_map[pic16f_index].configmask;
        data  &= pic16f_map[pic16f_index].configmask;
    }

    if( vdata != data)
    {
        printf("%s: error: read [%04x] expected [%04x] at [%04x]\n", __FUNCTION__, vdata, data, address);
        return FAIL;
    }
    return PASS;
}

/*
 * PROGRAM AND VERIFY DATA FOR REGION (CACHE CONFIG WORD FOR LATER)
 *
 * XXX INVESTIGATE MULTI-WORD WRITES ON SUPPORTED DEVICES
 * NB. SOME DEVICES USE ALTERNATE PROGRAMMING COMMANDS FOR
 * MULTI-WORD, WHILST OTHERS WILL COMMIT WHATEVER IS
 * PENDING WITH JUST THE ONE COMMAND.
 *
 * HERE WE ALWAYS COMMIT ONE WORD TO THE DEVICE.
 */
int
pic16f_programregion(struct k8048 *k, unsigned short address, int region, unsigned short data)
{
    if(address==0x2007)                                     /* config word */
    {
        if(pic16f_map[pic16f_index].datasheet == DS41191C)  /* preserve bandgap bits */
            data= (pic16f_conf.index[PIC16F_CONFIG_WORD1] & 0x3000) | (data & pic16f_map[pic16f_index].configmask);

        pic16f_conf.index[PIC16F_CONFIG_WORD1]= data;        /* cache config word */
        return 0;
    }
    
    pic16f_loadregion(k, region, data);                     /* store data in working register */

    switch(pic16f_map[pic16f_index].datasheet)              /* write word */
    {
        case DS30262E:  /* PIC16F84A  */
        case DS30277D:  /* PIC16F62X  */
        case DS39025F:  /* PIC16F87X  */
                        /* program one word */
                        pic16f_begin_programming_011000(k);
                        break;

        case DS39589B:  /* PIC16F87XA */
                        /* program one word (XXX device allows up to 8) */
                        pic16f_begin_programming_011000(k);
                        pic16f_end_programming_010111(k);
                        break;

        case DS39603C:  /* PIC16F818/819 */
        case DS39607B:  /* PIC16F87/88   */
                        /* program one word (XXX device allows up to 4) */
                        pic16f_begin_programming_011000(k);
                        pic16f_end_programming_010111(k);
                        break;

        case DS40245B:  /* PIC16F716  */
                        /* program one word (XXX device allows up to 4) */
                        pic16f_begin_programming_011000(k);
                        pic16f_end_programming_001110(k);
                        break;

        case DS41191C:  /* PIC12F629/675
                           PIC16F630/676 */
                        /* program one word */
                        pic16f_begin_programming_001000(k);
                        break;

        case DS41196F:  /* PIC16F627A/628A/648A */
                        /* program one word (XXX device allows up to 4) */
                        pic16f_begin_programming_001000(k);
                        break;
        
        case DS41204G:  /* PIC12F6XX/16F6XX */
                        /* program one word (XXX device allows up to 4) */
                        pic16f_begin_programming_001000(k);
                        break;
        
        default:        printf("%s: information: unimplemented\n", __FUNCTION__);
			return 0;
                        break;
    }
    return 1;
}

/*****************************************************************************
 *
 * Region programming
 *
 *****************************************************************************/
    
/*
 * PROGRAM DEVICE WITH/AGAINST GLOBAL INHX32 DATA
 */
void
pic16f_program(struct k8048 *k)
{
    int i, j;
    unsigned short hex_address, PC_address, wdata;
    int new_region, current_region=PIC16F_REGIONUNKNOWN;
    int total=0;

    /* initialise device for programming */
    pic16f_bulkerase(k);

    /* for each line */
    for(i=0; i<inhx32_count; i++)
    {
        hex_address= (inhx32_pdata[i]->address >> 1);
        new_region = pic16f_getregion(hex_address);
        if(new_region==PIC16F_REGIONUNKNOWN)
            continue;

        if(new_region != current_region)
        {
            io_init_program_verify(k);  /* RESET PC */

            PC_address= pic16f_initregion(k, new_region);
            current_region= new_region;
        }

        /* skip over unused PC locations */
        while(hex_address > PC_address)
        {
            PC_address++;
            pic16f_increment_address(k);
        }

        /* for each word in line */
        for(j=0; j<inhx32_pdata[i]->nbytes; j+=2)
        {
            wdata= inhx32_pdata[i]->bytes[j] | (inhx32_pdata[i]->bytes[j+1] << 8);
            total += pic16f_programregion(k, PC_address++, current_region, wdata);
            pic16f_increment_address(k);
        }
    }
    io_standby(k);
    
    /* finalise device programming (write config word) */
    pic16f_write_config(k, pic16f_conf.index[PIC16F_CONFIG_WORD1]);
    total++;

    printf("Total: %d\n", total);

    if(pic16f_map[pic16f_index].datasheet == DS40245B)
    {
        printf("%s: information: The %s must be power cycled before verification.\n",
            __FUNCTION__, pic16f_map[pic16f_index].devicename);
    }
}

/*
 * VERIFY DEVICE WITH/AGAINST GLOBAL INHX32 DATA
 */
int
pic16f_verify(struct k8048 *k)
{
    int i, j;
    unsigned short hex_address, PC_address, wdata;
    int new_region, current_region=PIC16F_REGIONUNKNOWN;
    int fail=0, total=0;

    /* for each line */
    for(i=0; i<inhx32_count; i++)
    {
        hex_address= (inhx32_pdata[i]->address >> 1);
        new_region = pic16f_getregion(hex_address);
        if(new_region==PIC16F_REGIONUNKNOWN)
            continue;

        if(new_region != current_region)
        {
            io_init_program_verify(k);  /* RESET PC */
            
            PC_address= pic16f_initregion(k, new_region);
            current_region= new_region;
        }

        /* skip over unused PC locations */
        while(hex_address > PC_address)
        {
            PC_address++;
            pic16f_increment_address(k);
        }

        /* for each word in line */
        for(j=0; j<inhx32_pdata[i]->nbytes; j+=2)
        {
            wdata= inhx32_pdata[i]->bytes[j] | (inhx32_pdata[i]->bytes[j+1] << 8);
            fail+= pic16f_verifyregion(k, PC_address++, wdata, current_region);
            pic16f_increment_address(k);
            total++;
        }
    }
    io_standby(k);

    printf("Total: %d Pass: %d Fail: %d\n", total, total-fail, fail);
    return fail;
}

/*****************************************************************************
 *
 * Diagnostic functions
 *
 *****************************************************************************/

/*
 * DUMP DEVICE ID DETAILS
 */
void
pic16f_dumpdeviceid(struct k8048 *k)
{
    int i;
    unsigned char u;
    unsigned short deviceid= pic16f_conf.index[PIC16F_CONFIG_DEVICEID] & PIC16F_DEVICEID_MASK;
    
    for(i=0; i<4; i++)
    {
        u= pic16f_conf.index[i] & 0xff;
        printf("[%04x] [USERID%d]  %02x %c\n", 0x2000+i, i, u, (u>=32 && u<127) ? u : '.');
    }
    printf("[2006] [DEVICEID] %04x DEV:%x (%x) REV:%x",
        pic16f_conf.index[PIC16F_CONFIG_DEVICEID], deviceid, deviceid >> 5,
            pic16f_conf.index[PIC16F_CONFIG_DEVICEID] & PIC16F_DEVICEREV_MASK);

    printf(" %s\n", pic16f_map[pic16f_index].devicename);
}

/*
 * DUMP OSCCAL DETAILS
 */
void
pic16f_dumposccal(struct k8048 *k)
{
    if(pic16f_map[pic16f_index].osccaladdr != 0)
        printf("[%04x] [OSCCAL] %04x\n", pic16f_map[pic16f_index].osccaladdr, pic16f_read_osccal(k));
    else
        printf("%s: information: osccal is not supported on this device\n", __FUNCTION__);
}

/*
 * DUMP CONFIG WORD DETAILS FOR DEVICE (IF KNOWN)
 */
void
pic16f_dumpconfig(struct k8048 *k)
{
    switch(pic16f_map[pic16f_index].datasheet)
    {
        case DS39607B:  printf("[2007] [CONFIG1] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        printf("[2008] [CONFIG2] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD2]);
                        break;
        case DS41204G:  printf("[2007] [CONFIG1] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        printf("[2008] [CONFIG2] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD2]);
                        printf("[2009] [CONFIG3] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD3]);
                        break;
        default:        printf("[2007] [CONFIG] %04x\n", pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        break;
    }
    switch(pic16f_map[pic16f_index].datasheet)
    {
        case DS30277D:  pic16f_dumpconfig_16f627(pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        break;
        case DS39589B:  pic16f_dumpconfig_16f877a(pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        break;
        case DS41191C:  pic16f_dumpconfig_12f675(pic16f_conf.index[PIC16F_CONFIG_WORD1]);
                        break;
        case DS39607B:  pic16f_dumpconfig_16f88(pic16f_conf.index[PIC16F_CONFIG_WORD1],
                            pic16f_conf.index[PIC16F_CONFIG_WORD2]);
                        break;
    }
}

/*
 * DUMP CONFIG WORD DETAILS FOR PIC16F627
 */
void
pic16f_dumpconfig_16f627(unsigned int config)
{
    int cp, dp, lvp, boden, mclre, pwrten, wdten, osc;

    cp= (config & 0x3000) >> 12;
    printf("\nCode protection for 2k program memory:\n");
    switch(cp)
    {
        case 0: printf("\t[CP] 0000h -> 07ffh code protected\n"); break;
        case 1: printf("\t[CP] 0200h -> 07ffh code protected\n"); break;
        case 2: printf("\t[CP] 0400h -> 07ffh code protected\n"); break;
        case 3: printf("\t[CP] Program memory code protection off\n"); break;
    }

    cp= (config & 0x0c00) >> 10;
    printf("\nCode protection for 1k program memory:\n");
    switch(cp)
    {
        case 0: printf("\t[CP] 0000h -> 03ffh code protected\n"); break;
        case 1: printf("\t[CP] 0200h -> 03ffh code protected\n"); break;
        case 2: printf("\t[CP] Program memory code protection off\n"); break;
        case 3: printf("\t[CP] Program memory code protection off\n"); break;
    }

    dp=config & 0x100;
    if(dp)      printf("\n[DATA_CP] Data memory code protection off\n");
    else        printf("\n[DATA_CP] Data memory code protection on\n");

    lvp=config & 0x80;
    if(lvp)     printf("\n[LVP] Low voltage programming enabled\n");
    else        printf("\n[LVP] Low voltage programming disabled\n");

    boden=config & 0x40;
    if(boden)   printf("\n[BODEN] Brown-out detect reset enabled\n");
    else        printf("\n[BODEN] Brown-out detect reset disabled\n");

    mclre=config & 0x20;
    if(mclre)   printf("\n[MCLRE] MCLR enabled\n");
    else        printf("\n[MCLRE] MCLR disabled\n");

    pwrten=config & 0x08;
    if(pwrten)  printf("\n[PWRTE] Power-up timer disabled\n");
    else        printf("\n[PWRTE] Power-up timer enabled\n");

    wdten=config & 0x04;
    if(wdten)   printf("\n[WDT] Watchdog timer enabled\n");
    else        printf("\n[WDT] Watchdog timer disabled\n");

    osc=((config & 0x10) >> 2) | (config & 0x03);
    printf("\nOscillator selection:\n");
    switch(osc)
    {
        case 7: printf("\t[ER] 111\n"); break;
        case 6: printf("\t[ER] 110\n"); break;
        case 5: printf("\t[INTRC] 101\n"); break;
        case 4: printf("\t[INTRC] 100\n"); break;
        case 3: printf("\t[EC] 011\n"); break;
        case 2: printf("\t[HS] 010\n"); break;
        case 1: printf("\t[XT] 001\n"); break;
        case 0: printf("\t[LP] 000\n"); break;
    }
}

/*
 * DUMP CONFIG WORD DETAILS FOR PIC12F675
 */
void
pic16f_dumpconfig_12f675(unsigned int config)
{
    int bg, dp, cp, boden, mclre, pwrten, wdten, osc;

    bg= (config & 0x3000) >> 12;
    printf("\nBandgap calibration bits:\n");
    switch(bg)
    {
        case 0: printf("\t[BG1:BG0] 00\n"); break;
        case 1: printf("\t[BG1:BG0] 01\n"); break;
        case 2: printf("\t[BG1:BG0] 10\n"); break;
        case 3: printf("\t[BG1:BG0] 11\n"); break;
    }
    
    dp=config & 0x100;
    if(dp)      printf("\n[DATA_CP] Data memory code protection off\n");
    else        printf("\n[DATA_CP] Data memory code protection on\n");
    
    cp=config & 0x80;
    if(cp)      printf("\n[CP] Program memory code protection off\n");
    else        printf("\n[CP] Program memory code protection on\n");
    
    boden=config & 0x40;
    if(boden)   printf("\n[BODEN] Brown-out detect reset enabled\n");
    else        printf("\n[BODEN] Brown-out detect reset disabled\n");

    mclre=config & 0x20;
    if(mclre)   printf("\n[MCLRE] MCLR enabled\n");
    else        printf("\n[MCLRE] MCLR disabled\n");

    pwrten=config & 0x10;
    if(pwrten)  printf("\n[PWRTE] Power-up timer disabled\n");
    else        printf("\n[PWRTE] Power-up timer enabled\n");

    wdten=config & 0x08;
    if(wdten)   printf("\n[WDT] Watchdog timer enabled\n");
    else        printf("\n[WDT] Watchdog timer disabled\n");

    osc=config & 0x07;
    printf("\nOscillator selection:\n");
    switch(osc)
    {
        case 7: printf("\t[RC] 111\n"); break;
        case 6: printf("\t[RC] 110\n"); break;
        case 5: printf("\t[INTOSC] CLKOUT\n"); break;
        case 4: printf("\t[INTOSC] NO CLKOUT\n"); break;
        case 3: printf("\t[EC] 011\n"); break;
        case 2: printf("\t[HS] 010\n"); break;
        case 1: printf("\t[XT] 001\n"); break;
        case 0: printf("\t[LP] 000\n"); break;
    }
}

/*
 * DUMP CONFIG WORD DETAILS FOR PIC16F877A
 */
void
pic16f_dumpconfig_16f877a(unsigned int config)
{
    unsigned short w;

    w= (config & 0x2000);
    if(w) printf("\n[CP] Code memory code protection off\n");
    else  printf("\n[CP] Code memory code protection on\n");

    w= (config & 0x0800);
    if(w) printf("\n[DEBUG] In circuit debugger off\n");
    else  printf("\n[DEBUG] In circuit debugger on\n");
    
    w= (config & 0x0600) >> 9;
    printf("\nWrite protection:\n");
    switch(w)
    {
        case 3: printf("\t[WRT1:WRT0] 11 off\n"); break;
        case 2: printf("\t[WRT1:WRT0] 10 0000 => 00ff Protected\n"); break;
        case 1: printf("\t[WRT1:WRT0] 01 0000 => 07ff Protected\n"); break;
        case 0: printf("\t[WRT1:WRT0] 00 0000 => 0fff Protected\n"); break;
    }

    w=config & 0x0100;
    if(w) printf("\n[CPD] Data memory code protection off\n");
    else  printf("\n[CPD] Data memory code protection on\n");

    w=config & 0x80;
    if(w) printf("\n[LVP] Low voltage programming enabled\n");
    else  printf("\n[LVP] Low voltage programming disabled\n");

    w=config & 0x40;
    if(w) printf("\n[BOREN] Brown-out reset enabled\n");
    else  printf("\n[BOREN] Brown-out reset disabled\n");

    w=config & 0x20;
    if(w) printf("\n[MCLRE] MCLR enabled\n");
    else  printf("\n[MCLRE] MCLR disabled\n");

    w=config & 0x08;
    if(w) printf("\n[PWRTE] Power-up timer disabled\n");
    else  printf("\n[PWRTE] Power-up timer enabled\n");

    w=config & 0x04;
    if(w) printf("\n[WDT] Watchdog timer enabled\n");
    else  printf("\n[WDT] Watchdog timer disabled\n");

    w=(config & 0x03);
    printf("\nOscillator selection:\n");
    switch(w)
    {
        case 3: printf("\t[RC] 11\n"); break;
        case 2: printf("\t[HS] 10\n"); break;
        case 1: printf("\t[XT] 01\n"); break;
        case 0: printf("\t[LP] 00\n"); break;
    }
}

/*
 * DUMP CONFIG WORD DETAILS FOR PIC16F87/8
 */
void
pic16f_dumpconfig_16f88(unsigned int config, unsigned int config2)
{
    unsigned short w;
    w= (config & 0x2000);
    if(w) printf("\n[CP] Code memory code protection off\n");
    else  printf("\n[CP] Code memory code protection on\n");

    w= (config & 0x0800);
    if(w) printf("\n[DEBUG] In circuit debugger off\n");
    else  printf("\n[DEBUG] In circuit debugger on\n");

    w= (config & 0x0600) >> 9;
    printf("\n[WRT1:WRT0] Flash write enable 0x%02x\n", w);

    w=config & 0x0100;
    if(w) printf("\n[CPD] Data memory code protection off\n");
    else  printf("\n[CPD] Data memory code protection on\n");

    w=config & 0x80;
    if(w) printf("\n[LVP] Low voltage programming enabled\n");
    else  printf("\n[LVP] Low voltage programming disabled\n");

    w=config & 0x40;
    if(w) printf("\n[BOREN] Brown-out reset enabled\n");
    else  printf("\n[BOREN] Brown-out reset disabled\n");

    w=config & 0x20;
    if(w) printf("\n[MCLRE] MCLR enabled\n");
    else  printf("\n[MCLRE] MCLR disabled\n");

    w=config & 0x08;
    if(w) printf("\n[PWRTE] Power-up timer disabled\n");
    else  printf("\n[PWRTE] Power-up timer enabled\n");

    w=config & 0x04;
    if(w) printf("\n[WDT] Watchdog timer enabled\n");
    else  printf("\n[WDT] Watchdog timer disabled\n");

    w=(config & 0x03);
    printf("\nOscillator selection:\n");
    switch(w)
    {
        case 3: printf("\t[RC] 11\n"); break;
        case 2: printf("\t[HS] 10\n"); break;
        case 1: printf("\t[XT] 01\n"); break;
        case 0: printf("\t[LP] 00\n"); break;
    }

    w=(config2 & 0x01);
    if(w) printf("\n[FCMEM] Fail-safe clock monitor enabled\n");
    else  printf("\n[PWRTE] Fail-safe clock monitor disabled\n");

    w=(config2 & 0x02);
    if(w) printf("\n[IESO] Internal/external switch over mode enabled\n");
    else  printf("\n[IESO] Internal/external switch over mode disabled\n");
}
