#!/usr/bin/env python3

# Script to create enums from datasheet register tables
#
# Usage:
#
# First, create a text file from the datasheet:
#    pdftotext -layout /path/to/rockchip-3288-trm.pdf /tmp/asc
#
# Then use this script to output the #defines for a particular register:
#    ./tools/rkmux.py GRF_GPIO4C_IOMUX
#
# It will create output suitable for putting in a header file, with SHIFT and
# MASK values for each bitfield in the register.
#
# Note: this tool is not perfect and you may need to edit the resulting code.
# But it should speed up the process.

import csv
import re
import sys

tab_to_col = 3

class RegField:
    def __init__(self, cols=None):
        if cols:
            self.bits, self.attr, self.reset_val, self.desc = (
                [x.strip() for x in cols])
            self.desc = [self.desc]
        else:
            self.bits = ''
            self.attr = ''
            self.reset_val = ''
            self.desc = []

    def Setup(self, cols):
        self.bits, self.attr, self.reset_val = cols[0:3]
        if len(cols) > 3:
            self.desc.append(cols[3])

    def AddDesc(self, desc):
        self.desc.append(desc)

    def Show(self):
        print(self)
        print()
        self.__init__()

    def __str__(self):
        return '%s,%s,%s,%s' % (self.bits, self.attr, self.reset_val,
                                '\n'.join(self.desc))

class Printer:
    def __init__(self, name):
        self.first = True
        self.name = name
        self.re_sel = re.compile("[1-9]'b([01]+): (.*)")

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        if not self.first:
            self.output_footer()

    def output_header(self):
        print('/* %s */' % self.name)
        print('enum {')

    def output_footer(self):
        print('};');

    def output_regfield(self, regfield):
        lines = regfield.desc
        field = lines[0]
        #print 'field:', field
        if field in ['reserved', 'reserve', 'write_enable', 'write_mask']:
            return
        if field.endswith('_sel') or field.endswith('_con'):
            field = field[:-4]
        elif field.endswith(' iomux'):
            field = field[:-6]
        elif field.endswith('_mode') or field.endswith('_mask'):
            field = field[:-5]
        #else:
            #print 'bad field %s' % field
            #return
        field = field.upper()
        if ':' in regfield.bits:
            bit_high, bit_low = [int(x) for x in regfield.bits.split(':')]
        else:
            bit_high = bit_low = int(regfield.bits)
        bit_width = bit_high - bit_low + 1
        mask = (1 << bit_width) - 1
        if self.first:
            self.first = False
            self.output_header()
        else:
            print()
        out_enum(field, 'shift', bit_low)
        out_enum(field, 'mask', mask)
        next_val = -1
        #print 'lines: %s', lines
        for line in lines:
            m = self.re_sel.match(line)
            if m:
                val, enum = int(m.group(1), 2), m.group(2)
                if enum not in ['reserved', 'reserve']:
                    out_enum(field, enum, val, val == next_val)
                    next_val = val + 1


def process_file(name, fd):
    field = RegField()
    reg = ''

    fields = []

    def add_it(field):
        if field.bits:
            if reg == name:
                fields.append(field)
            field = RegField()
        return field

    def is_field_start(line):
       if '=' in line or '+' in line:
           return False
       if (line.startswith('gpio') or line.startswith('peri_') or
                line.endswith('_sel') or line.endswith('_con')):
           return True
       if not ' ' in line: # and '_' in line:
           return True
       return False

    for line in fd:
        line = line.rstrip()
        if line[:4] in ['GRF_', 'PMU_', 'CRU_']:
            field = add_it(field)
            reg = line
            do_this = name == reg
        elif not line or not line.startswith(' '):
            continue
        line = line.replace('\xe2\x80\x99', "'")
        leading = len(line) - len(line.lstrip())
        line = line.lstrip()
        cols = re.split(' *', line, 3)
        if leading > 15 or (len(cols) > 3 and is_field_start(cols[3])):
            if is_field_start(line):
                field = add_it(field)
            field.AddDesc(line)
        else:
            if cols[0] == 'Bit' or len(cols) < 3:
                continue
            #print
            #print field
            field = add_it(field)
            field.Setup(cols)
    field = add_it(field)

    with Printer(name) as printer:
        for field in fields:
            #print field
            printer.output_regfield(field)
            #print

def out_enum(field, suffix, value, skip_val=False):
    str = '%s_%s' % (field.upper(), suffix.upper())
    if not skip_val:
        tabs = tab_to_col - len(str) / 8
        if value > 9:
            val_str = '%#x' % value
        else:
            val_str = '%d' % value

        str += '%s= %s' % ('\t' * tabs, val_str)
    print('\t%s,' % str)

# Process a CSV file, e.g. from tabula
def process_csv(name, fd):
    reader = csv.reader(fd)

    rows = []

    field = RegField()
    for row in reader:
        #print field.desc
        if not row[0]:
            field.desc.append(row[3])
            continue
        if field.bits:
            if field.bits != 'Bit':
                rows.append(field)
        #print row
        field = RegField(row)

    with Printer(name) as printer:
        for row in rows:
            #print field
            printer.output_regfield(row)
            #print

fname = sys.argv[1]
name = sys.argv[2]

# Read output from pdftotext -layout
if 1:
    with open(fname, 'r') as fd:
        process_file(name, fd)

# Use tabula
# It seems to be better at outputting text for an entire cell in one cell.
# But it does not always work. E.g. GRF_GPIO7CH_IOMUX.
# So there is no point in using it.
if 0:
    with open(fname, 'r') as fd:
        process_csv(name, fd)
