#!/usr/bin/perl

#   Copyright (C) 2008 Mauro Carvalho Chehab <mchehab@redhat.com>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, version 2 of the License.
#
#   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.
#
# This small script parses register dumps generated by em28xx driver
# with debug options enabled, generating a source code with the results
# of the dump.
#
# To use it, you may modprobe em28xx with reg_debug=1, and do:
# dmesg | ./parse_em28xx.pl
#
# Also, there are other utilities that produce similar outputs, and it
# is not hard to parse some USB analyzers log into the expected format.
#
# It will parse anything (including this file) with a format similar to:
#
# 40 00 00 00 48 00 01 00 >>> 00
# 40 00 00 00 12 00 01 00 >>> 37
# 40 00 00 00 08 00 01 00 >>> 3d
# 40 00 00 00 08 00 01 00 >>> 2d
# 40 00 00 00 08 00 01 00 >>> 3d
# 40 00 00 00 48 00 01 00 >>> 00
# 40 00 00 00 12 00 01 00 >>> 37
# 40 00 00 00 08 00 01 00 >>> 3d
# 40 00 00 00 08 00 01 00 >>> 2d
# 40 00 00 00 08 00 01 00 >>> 3d
# c0 00 00 00 43 00 01 00 <<< 00
# 40 00 00 00 42 00 01 00 >>> fc
# c0 00 00 00 40 00 02 00 <<< ff ff
# c0 00 00 00 43 00 01 00 <<< 00
# 40 00 00 00 42 00 01 00 >>> fe
# c0 00 00 00 40 00 02 00 <<< ff ff
# c0 00 00 00 43 00 01 00 <<< 00
# 40 00 00 00 42 00 01 00 >>> 80
# c0 00 00 00 40 00 02 00 <<< 90 6a
#
# Producing a much easier to understand series of C function calls:
#
# em28xx_write_reg(dev, 0x48, 0x00);
# em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x3d);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x2d);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x3d);
# em28xx_write_reg(dev, 0x48, 0x00);
# em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x3d);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x2d);
# em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x3d);
# em28xx_read_ac97(dev, AC97_VENDOR_ID1); /* read 0x0xffff */
# em28xx_read_ac97(dev, AC97_VENDOR_ID2); /* read 0x0xffff */
# em28xx_read_ac97(dev, AC97_RESET);      /* read 0x0x6a90 */
#
# This way, it is easier to understand what the em28xx driver is doing.
#
# Known limitations:
#   - Currently, the tool only parses em28xx, i2c, ac97 and em202 registers.
#   - It is limited to read/write operations with 1 or 2 bytes of
#     arguments;
#   - Not all registers are documented;
#   - It doesn't parse em2800-only registers;
#   - em28xx currently doesn't implement em28xx_read_reg16() or
#     em28xx_write_reg16(). However, this tool uses those two "functions"
#     meaning to read or write 2 consecutive bytes, ordering arguments
#     in big-endian notation.
#

use strict;

my %reg_map = (
	"0x00" => "EM28XX_R00_CHIPCFG",
	"0x04" => "EM2880_R04_GPO",
	"0x08" => "EM28XX_R08_GPIO",
	"0x06" => "EM28XX_R06_I2C_CLK",
	"0x0a" => "EM28XX_R0A_CHIPID",
	"0x0c" => "EM28XX_R0C_USBSUSP",
	"0x0e" => "EM28XX_R0E_AUDIOSRC",
	"0x0f" => "EM28XX_R0F_XCLK",
	"0x20" => "EM28XX_XCLK_IR_RC5_MODE",
	"0x10" => "EM28XX_R10_VINMODE",
	"0x11" => "EM28XX_R11_VINCTRL",
	"0x12" => "EM28XX_R12_VINENABLE",
	"0x14" => "EM28XX_R14_GAMMA",
	"0x15" => "EM28XX_R15_RGAIN",
	"0x16" => "EM28XX_R16_GGAIN",
	"0x17" => "EM28XX_R17_BGAIN",
	"0x18" => "EM28XX_R18_ROFFSET",
	"0x19" => "EM28XX_R19_GOFFSET",
	"0x1a" => "EM28XX_R1A_BOFFSET",
	"0x1b" => "EM28XX_R1B_OFLOW",
	"0x1c" => "EM28XX_R1C_HSTART",
	"0x1d" => "EM28XX_R1D_VSTART",
	"0x1e" => "EM28XX_R1E_CWIDTH",
	"0x1f" => "EM28XX_R1F_CHEIGHT",
	"0x20" => "EM28XX_R20_YGAIN",
	"0x21" => "EM28XX_R21_YOFFSET",
	"0x22" => "EM28XX_R22_UVGAIN",
	"0x23" => "EM28XX_R23_UOFFSET",
	"0x24" => "EM28XX_R24_VOFFSET",
	"0x25" => "EM28XX_R25_SHARPNESS",
	"0x26" => "EM28XX_R26_COMPR",
	"0x27" => "EM28XX_R27_OUTFMT",
	"0x28" => "EM28XX_R28_XMIN",
	"0x29" => "EM28XX_R29_XMAX",
	"0x2a" => "EM28XX_R2A_YMIN",
	"0x2b" => "EM28XX_R2B_YMAX",
	"0x30" => "EM28XX_R30_HSCALELOW",
	"0x31" => "EM28XX_R31_HSCALEHIGH",
	"0x32" => "EM28XX_R32_VSCALELOW",
	"0x33" => "EM28XX_R33_VSCALEHIGH",
	"0x40" => "EM28XX_R40_AC97LSB",
	"0x41" => "EM28XX_R41_AC97MSB",
	"0x42" => "EM28XX_R42_AC97ADDR",
	"0x43" => "EM28XX_R43_AC97BUSY",
	"0x45" => "EM28XX_R45_IR",
	"0x50" => "EM2874_R50_IR_CONFIG",
	"0x51" => "EM2874_R51_IR",
	"0x5f" => "EM2874_R5F_TS_ENABLE",
	"0x80" => "EM2874_R80_GPIO",
);

my %ac97_map = (
	"0x00" => "AC97_RESET",
	"0x02" => "AC97_MASTER_VOL",
	"0x04" => "AC97_LINE_LEVEL_VOL",
	"0x06" => "AC97_MASTER_MONO_VOL",
	"0x0a" => "AC97_PC_BEEP_VOL",
	"0x0c" => "AC97_PHONE_VOL",
	"0x0e" => "AC97_MIC_VOL",
	"0x10" => "AC97_LINEIN_VOL",
	"0x12" => "AC97_CD_VOL",
	"0x14" => "AC97_VIDEO_VOL",
	"0x16" => "AC97_AUX_VOL",
	"0x18" => "AC97_PCM_OUT_VOL",
	"0x1a" => "AC97_RECORD_SELECT",
	"0x1c" => "AC97_RECORD_GAIN",
	"0x20" => "AC97_GENERAL_PURPOSE",
	"0x22" => "AC97_3D_CTRL",
	"0x24" => "AC97_AUD_INT_AND_PAG",
	"0x26" => "AC97_POWER_DOWN_CTRL",
	"0x28" => "AC97_EXT_AUD_ID",
	"0x2a" => "AC97_EXT_AUD_CTRL",
	"0x2c" => "AC97_PCM_OUT_FRONT_SRATE",
	"0x2e" => "AC97_PCM_OUT_SURR_SRATE",
	"0x30" => "AC97_PCM_OUT_LFE_SRATE",
	"0x32" => "AC97_PCM_IN_SRATE",
	"0x36" => "AC97_LFE_MASTER_VOL",
	"0x38" => "AC97_SURR_MASTER_VOL",
	"0x3a" => "AC97_SPDIF_OUT_CTRL",
	"0x7c" => "AC97_VENDOR_ID1",
	"0x7e" => "AC97_VENDOR_ID2",

	# em202 specific AC97 registers

	"0x3e" => "EM202_EXT_MODEM_CTRL",
	"0x4c" => "EM202_GPIO_CONF",
	"0x4e" => "EM202_GPIO_POLARITY",
	"0x50" => "EM202_GPIO_STICKY",
	"0x52" => "EM202_GPIO_MASK",
	"0x54" => "EM202_GPIO_STATUS",
	"0x6a" => "EM202_SPDIF_OUT_SEL",
	"0x72" => "EM202_ANTIPOP",
	"0x74" => "EM202_EAPD_GPIO_ACCESS",
);

my ($r40, $r42, $r43, $dir);

sub output_ac97()
{
	if (hex($r42) < 0x80) {
		if ($dir < 0) {
			return;
		}
		$r42 = $ac97_map{$r42} if defined($ac97_map{$r42});
		printf "em28xx_write_ac97(dev, %s, %s);\n",$r42, $r40;
		$r43 = 0;

		return;
	}

	if ($dir > 0) {
		return;
	}
	$r42 = sprintf("0x%02x", hex($r42) - 0x80);
	$r42 = $ac97_map{$r42} if defined($ac97_map{$r42});
	printf "em28xx_read_ac97(dev, %s);\t/* read 0x%s */\n",$r42, $r40;
	$r43 = 0;
}

while (<>) {
	tr/A-F/a-f/;
	if (m/c0 00 00 00 ([0-9a-f].) 00 01 00\s+[\<]+\s+([0-9a-f].)/) {
		if ($1 eq "43" && $2 eq "00") {
			$r43 = 1;
			$r40 = -1;
			$r42 = -1;
			$dir = 0;
			next;
		}

		my $reg = "0x$1";
		$reg = $reg_map{$reg} if defined($reg_map{$reg});

		printf "em28xx_read_reg(dev, %s);\t\t/* read 0x%s */\n",
			$reg, $2;
		next;
	}
	if (m/40 00 00 00 ([0-9a-f].) 00 01 00\s+[\>]+\s+([0-9a-f].)/) {
		if ($r43 == 1) {
			if ($1 eq "42") {
				$r42 = "0x$2";
				if ($r40 >= 0) {
					output_ac97();
					next;
				}
				next;
			}
			$r43 = 0;
		}

		my $reg = "0x$1";
		$reg = $reg_map{$reg} if defined($reg_map{$reg});

		printf "em28xx_write_reg(dev, %s, 0x%s);\n",
			$reg, $2;
		next;
	}
	if (m/c0 00 00 00 ([0-9a-f].) 00 02 00\s+[\<]+\s+([0-9a-f].) ([0-9a-f].)/) {
		if ($r43 == 1) {
			if ($1 eq "40") {
				$r40 = "0x$3$2";
				$dir = -1;

				if ($r42 >= 0) {
					output_ac97();
					next;
				}
				next;
			}
			$r43 = 0;
		}
		my $reg = "0x$1";
		$reg = $reg_map{$reg} if defined($reg_map{$reg});

		printf "em28xx_read_reg16(dev, %s);\t\t/*read 0x%s%s */\n",
			$reg, $3, $2;
		next;
	}
	if (m/40 00 00 00 ([0-9a-f].) 00 02 00\s+[\>]+\s+([0-9a-f].) ([0-9a-f].)/) {
		if ($r43 == 1) {
			if ($1 eq "40") {
				$r40 = "0x$3$2";
				$dir = 1;

				if ($r42 >= 0) {
					output_ac97();
					next;
				}
				next;
			}
			$r43 = 0;
		}
		my $reg = "0x$1";
		$reg = $reg_map{$reg} if defined($reg_map{$reg});

		printf "em28xx_write_reg16(dev, %s,0x%s%s);\n",
			$reg, $3, $2;
		next;
	}

	if (m/40 02 00 00 ([0-9a-f].) 00 ([0-9a-f].) 00\s+[\>]+\s+([0-9a-f ]+)/) {
 		printf "i2c_master_send(0x$1>>1, { $3 }, 0x$2);\n";
	}
	if (m/c0 02 00 00 ([0-9a-f].) 00 ([0-9a-f].) 00\s+[\>]+\s+([0-9a-f ]+)/) {
 		printf "i2c_master_recv(0x$1>>1, &buf, 0x$2); /* $3 */\n";
	}
}