#!/usr/bin/env python
# -*- coding: utf-8 -*-
# COPYRIGHT: Openmoko Inc. 2009
# LICENSE: GPL Version 3 or later
# DESCRIPTION: Simple FLASH programmer
# AUTHOR: Christopher Hall <hsw@openmoko.com>

try:
    from serial.serialposix import *
except:
    print 'error: Missing python module: python-serial'
    print '       sudo apt-get install python-serial'
    exit(1)

import time
import re
import getopt
import os.path
import sys

import MapFile
import Dump
import SPI
import FLASHROM


VALID_CPU_IDS = [0x060e0721]
JACKKNIFE_ID = 0x3f3f3f3f

ROM_LIST = {
'A1': FLASHROM.PM25LV512,
'A2': FLASHROM.PM25LV512,
'A3': FLASHROM.EN25F05,
'A4': FLASHROM.EN25F05,
'A5': FLASHROM.EN25F05,
'A6': FLASHROM.EN25F05,
'A7': FLASHROM.EN25F05,
'A8': FLASHROM.EN25F05,
'A9': FLASHROM.EN25F05,
'A0': FLASHROM.EN25F05,
'V1': FLASHROM.EN25F05,
'V2': FLASHROM.EN25F05,
}


class SerialPort():

#    def __init__(self, port = '/dev/ttyUSB1', bps = 57600, timeout = 0.2):
    def __init__(self, port = '/dev/ttyUSB1', bps = 115200, timeout = 0.2):
        self.s = Serial(port = port, stopbits = STOPBITS_TWO)
        self.s.xonxoff = False
        self.s.rtscts = False
        self.s.baudrate = bps
        self.s.timeout = timeout
        self.s.open()
        self.spi_channel = SPI.SPI(self.s)

    def __del__(self):
        self.s.close()

    def flush(self):
        print 'flush:', self.s.inWaiting()
        if 0 < self.s.inWaiting():
            self.s.read(self.s.inWaiting())

    def reset(self):
        self.s.setDTR(1)
        time.sleep(0.1)
        self.s.setDTR(0)

    def getID(self):
        self.flush()
        self.s.write('\x80\x80\x80\x80')
        time.sleep(0.1)
        self.id = 0
        for b in self.s.read(4):
            self.id = (self.id << 8) | ord(b)
        return self.id

    def send(self, boot):
        for v in boot:
            self.s.write(v)
        readback = self.s.read(512)
        if len(readback) == 512 and readback == boot:
            return True
        l = len(readback)
        print 'Boot block readback mismatches'
        for i in range(512):
            if i >= l:
                rb = '--'
            else:
                rb = '%02x' % (ord(readback[i]))
            v = '%02x' % (ord(boot[i]))
            if rb != v:
                print 'BOOT[%3d] = %s -> %s' % (i, v, rb)
        return False

    def spi(self):
        return self.spi_channel


class AuxSerialPort():

#    def __init__(self, port = '/dev/ttyUSB1', bps = 57600, timeout = 0.2):
    def __init__(self, port = '/dev/ttyUSB1', bps = 115200, timeout = 0.2):
        self.s = Serial(port = port, stopbits = STOPBITS_TWO)
        self.s.xonxoff = False
        self.s.rtscts = False
        self.s.baudrate = bps
        self.s.timeout = timeout
        self.s.open()
        self.spi_channel = SPI.SPI(self.s)

    def program(self):
        self.s.setDTR(1)

    def normal(self):
        self.s.setDTR(0)

    def __del__(self):
        self.s.close()


def usage(message):
    if None != message:
        print 'error:', message
    print 'usage: %s [--verbose] --boot=<boot> --map-file=<map>' % os.path.basename(__file__)
    exit(1)

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hvb:m:p:a:', ['help', 'verbose', 'boot=', 'map-file=', 'port=', 'aux-port='])
    except getopt.GetoptError, err:
        usage(err)

    verbose = False
    bootfile = 'jackknife'
    mapfile = ''
    aux_port = ''
    port = '/dev/ttyUSB1'

    for opt, arg in opts:
        if opt in ('-v', '--verbose'):
            verbose = True
        elif opt in ('-h', '--help'):
            usage(None)
        elif opt in ('-b', '--boot'):
            bootfile = arg
        elif opt in ('-m', '--map-file'):
            mapfile = arg
        elif opt in ('-p', '--port'):
            port = arg
        elif opt in ('-a', '--aux-port'):
            aux_port = arg
        else:
            usage('unhandled option: ' + opt)

    if not os.path.isfile(bootfile):
        usage('bootfile: "%s" does not exist' % bootfile)
    if not os.path.isfile(mapfile):
        usage('mapfile: "%s" does not exist' % mapfile)

    f = open(bootfile, 'rb')
    boot = f.read()
    f.close
    l = len(boot)
    if l < 512:
        boot += '\x00' * (512 - l)

    map = MapFile.MapFile(mapfile)
    if not map.ok():
        usage('Invalid mapfile')

    if verbose:
        for r in map.items():
            print '0x%04x %s (%d bytes)' % (r[0], r[1], r[2])

    s = SerialPort(port = port)

    aux = None
    if '' != aux_port:
        aux = AuxSerialPort(port = aux_port)
        aux.program()

    id = s.getID()
    while not id in VALID_CPU_IDS:
        print 'Press Reset'
        s.reset()
        time.sleep(1)
        id = s.getID()

    print 'CPU Identifier = 0x%08x' % id

    if not s.send(boot):
        usage('Transmission of Boot Block failed')

    board = s.spi().version()
    if None == board or len(board) == 0:
        usage('cannot determine board type');

    if board in ROM_LIST:
        print 'board = %s  rom = %s' % (board, ROM_LIST[board])
    else:
        usage('unknown board: %s' % board);

    rom = ROM_LIST[board](s.spi())
    for r in map.items():
        offset, name, length, data = r[0], r[1], r[2], r[3]
        print '0x%04x %s (%d bytes)' % (offset, name, length)
        rom.program(data, offset)
        rom.verify(data, offset)

    if None != aux:
        aux.normal()

    s = None
    aux = None

if __name__ == '__main__':
    main()
