/* 
 * avivotool v0.1
 * by Daniel Stone <daniel@fooishbar.org>
 *
 * based on:
 * Radeontool   v1.4
 * by Frederick Dean <software@fdd.com>
 *
 * Copyright 2002-2004 Frederick Dean
 * Use hereby granted under the zlib license.
 *
 * Warning: I do not have the Radeon documents, so this was engineered from 
 * the radeon_reg.h header file.  
 *
 * USE RADEONTOOL AT YOUR OWN RISK
 *
 * Thanks to Deepak Chawla, Erno Kuusela, Rolf Offermanns, and Soos Peter
 * for patches.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <fnmatch.h>
#include <errno.h>
#include <pciaccess.h>

#include "radeon.h"
#include "radeon_reg.h"

int skip;

/* *ctrl_mem is mapped to the actual device's memory mapped control area. */
/* Not the address but what it points to is volatile. */
RADEONCardInfo *card_info = NULL;
unsigned char * volatile ctrl_mem;
unsigned char * volatile fb_mem;


static void die(const char *why)
{
    fprintf(stderr, "fatal error: %s\n", why);
    pci_system_cleanup();
    exit(-1);
}

static void die_error(int err, const char *why)
{
    fprintf(stderr, "fatal error: %s: %s\n", why, strerror(err));
    pci_system_cleanup();
    exit(-1);
}

static void radeon_set(unsigned long offset, const char *name, unsigned int value)
{

    if (ctrl_mem == NULL)
        die("internal error");

#ifdef __powerpc__
    __asm__ __volatile__ ("stwbrx %1,%2,%3\n\t"
                          "eieio"
                          : "=m" (*((volatile unsigned int *)ctrl_mem+offset))
                          : "r"(value), "b"(ctrl_mem), "r"(offset));
#else
    *(unsigned int * volatile)(ctrl_mem + offset) = value;  
#endif
}

static void radeon_set_indexed(unsigned long index_offset,
                               unsigned long data_offset, unsigned long offset,
                               const char *name, unsigned int value)
{
    radeon_set(index_offset, "index", offset);
    radeon_set(data_offset, name, value);
}

static unsigned int radeon_get(unsigned long offset, const char *name)
{
    unsigned int value;

    if (ctrl_mem == NULL)
        die("internal error");

#ifdef __powerpc__
    __asm__ __volatile__ ("lwbrx %0,%1,%2\n\t"
                          "eieio"
                          : "=r" (value)
                          : "b" (ctrl_mem), "r"(offset),
                          "m" (*((volatile unsigned int *)ctrl_mem+offset)));
#else
    value = *(unsigned int * volatile)(ctrl_mem + offset);
#endif

    return value;
}

static unsigned int radeon_get_indexed(unsigned long index_offset,
                                       unsigned long data_offset,
                                       unsigned long offset, const char *name)
{
    radeon_set(index_offset, "index", offset);
    return radeon_get(data_offset, name);
}

static unsigned int radeon_get_clk(unsigned long offset, const char *name)
{
    return radeon_get_indexed(RADEON_CLOCK_CNTL_INDEX,
			      RADEON_CLOCK_CNTL_DATA,
                              offset, name);
}

static void radeon_set_clk(unsigned long offset, const char *name,
                          unsigned int value)
{
    return radeon_set_indexed(RADEON_CLOCK_CNTL_INDEX, RADEON_CLOCK_CNTL_DATA,
                              offset | RADEON_PLL_WR_EN, name, value);
}

static unsigned int radeon_get_mc(unsigned long offset, const char *name)
{
    return radeon_get_indexed(AVIVO_MC_INDEX, AVIVO_MC_DATA,
                              offset | 0x007f0000, name);
}

static void radeon_set_mc(unsigned long offset, const char *name,
                          unsigned int value)
{
    return radeon_set_indexed(AVIVO_MC_INDEX, AVIVO_MC_DATA,
                              offset | 0x00ff0000, name, value);
}

static unsigned int radeon_get_pcie(unsigned long offset, const char *name)
{
    return radeon_get_indexed(0x30, 0x34, offset, name);
}

static void radeon_set_pcie(unsigned long offset, const char *name, unsigned int value)
{
    return radeon_set_indexed(0x30, 0x34, offset, name, value);
}

static void rs690_set_mc(unsigned long offset, const char *name, unsigned int value)
{
    return radeon_set_indexed(RS690_NB_MC_INDEX, RS690_NB_MC_DATA,
			      (offset & RS690_MC_INDEX_MASK) | RS690_MC_INDEX_WR_EN, name, value);
}

static unsigned int rs690_get_mc(unsigned long offset, const char *name)
{
    return radeon_get_indexed(RS690_NB_MC_INDEX, RS690_NB_MC_DATA, offset, name);
}

static void usage(void)
{
    printf("usage: radeonreg [options] [command]\n");
    printf("         --skip=1           - use the second radeon card\n");
    printf("         regs <set>         - show a listing of some random registers\n");
    printf("         regmatch <pattern> - show registers matching wildcard pattern\n");
    printf("         regset <pattern> <value> - set registers matching wildcard pattern\n");
    exit(-1);
}

#define GET_REG(r) radeon_get(r, #r)
#define SET_REG(r, v) radeon_set(r, #r, v)

#define GET_REG(r) radeon_get(r, #r)
#define SHOW_REG(r) printf("%s\t%08x\n", #r, radeon_get(r, #r))
#define SHOW_UNKNOWN_REG(r) { tmp = radeon_get(r, "byhand"); printf("%08lx\t%08x (%d)\n", r, tmp, tmp); }
#define REG_TYPE_NAME(r) ((r == REG_TYPE_STATIC) ? "static" : ((r == REG_TYPE_SEMI_STATIC) ? "semi-static" : "random"))
#define SHOW_STATIC_REG(r) { tmp = get_reg_type(r); printf("%s (%08lx)\t%s\n", get_reg_name(r, ""), r, REG_TYPE_NAME(tmp)); }
#define SHOW_REG_DECIMAL(r) printf("%s\t%d (decimal)\n", #r, radeon_get(r, #r))
#define SHOW_REG_BITS(r, ...) radeon_show_reg_bits(NULL, #r, 0, 0, r, __VA_ARGS__)
#define SHOW_MC_REG(r) printf("%s\t%08x\n", #r, radeon_get_indexed(AVIVO_MC_INDEX, AVIVO_MC_DATA, (r | 0x007f0000), #r))
#define SHOW_MC_REG_BITS(r, ...) radeon_show_reg_bits(NULL, AVIVO_MC_INDEX, AVIVO_MC_DATA, #r, (r | 0x007f0000), __VA_ARGS__)
#define SHOW_PCIE_REG(r) printf("%s\t%08x\n", #r, radeon_get_indexed(0x30, 0x34, (r), #r))

#define SHOW_UNK_MC_REG(r) printf("%02x\t%08x\n", r, radeon_get_indexed(AVIVO_MC_INDEX, AVIVO_MC_DATA, (r | 0x007f0000), #r))

#define SHOW_UNK_CLK_REG(r) printf("%02x\t%08x\n", r, radeon_get_indexed(RADEON_CLOCK_CNTL_INDEX, RADEON_CLOCK_CNTL_DATA, (r), #r))

static void radeon_show_radeon_display_regs(const char *type)
{
    int tmp; /* may be stomped at any moment. */
    unsigned long i;

    for (i = 0x0000; i < 0x00fc; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x0200; i < 0x060c; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x0800; i < 0x0904; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x0d00; i < 0x0d78; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0; i < 0x2f; i++)
	    SHOW_UNK_CLK_REG(i);
}

static void radeon_show_avivo_display_regs(const char *type)
{
    int tmp; /* may be stomped at any moment. */
    unsigned long i;

    for (i = 0x0000; i < 0x5b4; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x5dfc; i < 0x7ff0; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0; i < 0x3f; i++)
	    SHOW_UNK_CLK_REG(i);
}

static void radeon_show_dce3_display_regs(const char *type)
{
    int tmp; /* may be stomped at any moment. */
    unsigned long i;

    for (i = 0x0000; i < 0x5bc; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x5dfc; i < 0x8000; i += 4)
            SHOW_UNKNOWN_REG(i);
}

static void radeon_show_dce4_display_regs(const char *type)
{
    int tmp; /* may be stomped at any moment. */
    unsigned long i;

    for (i = 0x0000; i < 0x5e8; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x5c6c; i < 0x7f64; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x10000; i < 0x12f64; i += 4)
            SHOW_UNKNOWN_REG(i);
}

static void radeon_show_dce5_display_regs(const char *type)
{
    int tmp; /* may be stomped at any moment. */
    unsigned long i;

    for (i = 0x0000; i < 0x5e8; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x5800; i < 0x7fa4; i += 4)
            SHOW_UNKNOWN_REG(i);
    for (i = 0x10000; i < 0x12fa4; i += 4)
            SHOW_UNKNOWN_REG(i);
}

void radeon_cmd_regs(const char *type)
{

    /* Dump all registers that we can read. */
    if (strcmp(type, "radeon") == 0) {
	radeon_show_radeon_display_regs(type);
        return;
    }
    if (strcmp(type, "avivo") == 0) {
	radeon_show_avivo_display_regs(type);
        return;
    }
    if (strcmp(type, "dce3") == 0) {
	radeon_show_dce3_display_regs(type);
        return;
    }
    if (strcmp(type, "dce4") == 0) {
	radeon_show_dce4_display_regs(type);
        return;
    }
    if (strcmp(type, "dce5") == 0) {
	radeon_show_dce5_display_regs(type);
        return;
    }
    if (strcmp(type, "all") == 0) {
	    if (IS_DISPLAY_RADEON(card_info))
		    radeon_show_radeon_display_regs(type);
	    else if (IS_DISPLAY_AVIVO(card_info))
		    radeon_show_avivo_display_regs(type);
	    else if (IS_DISPLAY_DCE3(card_info))
		    radeon_show_dce3_display_regs(type);
	    else if (IS_DISPLAY_DCE4(card_info))
		    radeon_show_dce4_display_regs(type);
	    else if (IS_DISPLAY_DCE5(card_info))
		    radeon_show_dce5_display_regs(type);
	    else
		    printf("unknown chipset, specify the regs to dump\n");
	    return;
    }
}

void radeon_reg_match(const char *pattern)
{
    int i;
    unsigned long address;
    unsigned int value;

    if (pattern[0] == '0' && pattern[1] == 'x') {
        address = strtol(&(pattern[2]), NULL, 16);
        value = radeon_get(address, pattern);
        printf("%s\t0x%08x (%d)\n", pattern, value, value);
    }
    else if (pattern[0] == 'M' && pattern[1] == 'C' && pattern[2] == ':') {
        address = strtol(&(pattern[3]), NULL, 16);
        value = radeon_get_mc(address, pattern);
        printf("%s\t0x%08x (%d)\n", pattern, value, value);
    }
    else if (pattern[0] == 'C' && pattern[1] == 'L' && pattern[2] == ':') {
        address = strtol(&(pattern[3]), NULL, 16);
        value = radeon_get_clk(address, pattern);
        printf("%s\t0x%08x (%d)\n", pattern, value, value);
    }
    else if (pattern[0] == 'P' && pattern[1] == 'C' && pattern[2] == ':') {
        address = strtol(&(pattern[3]), NULL, 16);
        value = radeon_get_pcie(address, pattern);
        printf("%s\t0x%08x (%d)\n", pattern, value, value);
    }
}

void set_reg(const char *name, const char *type, unsigned long address,
             unsigned int value,
             unsigned int (*get)(unsigned long, const char *),
             void (*set)(unsigned long, const char *, unsigned int))
{
    unsigned int readback;

    readback = get(address, name);
    printf("OLD: %s (%s%04lx)\t0x%08x (%d)\n", name, type, address,
           readback, readback);
    set(address, name, value);
    readback = get(address, name);
    printf("NEW: %s (%s%04lx)\t0x%08x (%d)\n", name, type, address,
           readback, readback);
}

void radeon_reg_set(const char *inname, unsigned int value)
{
    int i;
    unsigned long address;

    if (inname[0] == '0' && inname[1] == 'x') {
        address = strtol(&(inname[2]), NULL, 16);
        set_reg(inname, "", address, value, radeon_get, radeon_set);
    }
    else if (inname[0] == 'M' && inname[1] == 'C' && inname[2] == ':') {
        address = strtol(&(inname[3]), NULL, 16);
        set_reg(inname, "MC: ", address, value, radeon_get_mc, radeon_set_mc);
    }
    else if (inname[0] == 'C' && inname[1] == 'L' && inname[2] == ':') {
        address = strtol(&(inname[3]), NULL, 16);
        set_reg(inname, "CL: ", address, value, radeon_get_clk, radeon_set_clk);
    }
    else if (inname[0] == 'P' && inname[1] == 'C' && inname[2] == ':') {
        address = strtol(&(inname[3]), NULL, 16);
        set_reg(inname, "PCIE: ", address, value, radeon_get_pcie, radeon_set_pcie);
    }
}

/* Find and map the buffers. */
static int map_radeon_mem(void)
{
#if 0
    struct pci_id_match match;
#else
    struct pci_slot_match match;
#endif
    struct pci_device_iterator *iter;
    struct pci_device *device;
    pciaddr_t fb_base, fb_size, ctrl_base, ctrl_size;
    int i = 0, ret;

    ret = pci_system_init();
    if (ret)
        die_error(ret, "failed to initialise libpciaccess");

#if 0
    match.vendor_id = 0x1002;
    match.device_id = PCI_MATCH_ANY;
    match.subvendor_id = PCI_MATCH_ANY;
    match.subdevice_id = PCI_MATCH_ANY;
    match.device_class = (0x03 << 16);
    match.device_class_mask = 0x00ff0000;
    match.match_data = 0;
    iter = pci_id_match_iterator_create(&match);
#else
    match.domain = PCI_MATCH_ANY;
    match.bus = PCI_MATCH_ANY;
    match.dev = PCI_MATCH_ANY;

    match.func = 0;
    match.match_data = 0;
    iter = pci_slot_match_iterator_create(&match);
#endif

    while ((device = pci_device_next(iter))) {
        pci_device_probe(device);
        if (device->vendor_id != 0x1002)
            continue;
        if ((device->device_class & 0x00ffff00) != 0x00030000 &&
            (device->device_class & 0x00ffff00) != 0x00038000)
            continue;
        if (skip--)
            continue;
        break;
    }

    if (!device) {
        printf("cannot find Radeon device\n");
        return -1;
    }

    for (i = 0; i < sizeof(RADEONCards) / sizeof(RADEONCardInfo); i++) {
        if (RADEONCards[i].pci_device_id == device->device_id)
            card_info = &RADEONCards[i];
    }

    ctrl_base = device->regions[2].base_addr;
    ctrl_size = device->regions[2].size;
    if (!ctrl_size)
        die("missing ctrl region");
    ret = pci_device_map_range(device, ctrl_base, ctrl_size,
			     PCI_DEV_MAP_FLAG_WRITABLE, (void **) &ctrl_mem);
    if (ret)
        die_error(ret, "cannot map ctrl region");

    fb_base = device->regions[0].base_addr;
    fb_size = device->regions[0].size;
    if (!fb_size || pci_device_map_range(device, fb_base, fb_size,
			     PCI_DEV_MAP_FLAG_WRITABLE, (void **) &fb_mem))
        fb_mem = NULL;

    pci_iterator_destroy(iter);

    return 0;
}

int main(int argc, char *argv[])
{
    int ret;
    if (argc == 1)
        usage();

    skip = 0;
    if (argc > 1 && strncmp(argv[1], "--skip=", 7) == 0) {
        skip = atoi(argv[1] + 7);
        argv++;
        argc--;
    }

    ret = map_radeon_mem();
    if (ret!=0)
	    die("Unable to see card");

    if (argc == 2) {
	    if (strcmp(argv[1], "regs") == 0) {
		    radeon_cmd_regs("default");
		    return 0;
	    }
    } else if (argc == 3) {
	    if (strcmp(argv[1], "regmatch") == 0) {
		    radeon_reg_match(argv[2]);
		    return 0;
	    } else if (strcmp(argv[1], "regs") == 0) {
		    radeon_cmd_regs(argv[2]);
		    return 0;
	    }

    } else if (argc == 4) {
        if (strcmp(argv[1], "regset") == 0) {
            radeon_reg_set(argv[2], strtoul(argv[3], NULL, 0));
            return 0;
        }
    }

    usage();

    pci_system_cleanup();

    return 1;
}
