#!/usr/bin/perl

#   Copyright (C) 2010 Mauro Carvalho Chehab
#
#   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 USB dumps generated by several drivers,
# decoding USB bits.
#
# To use it, do:
# dmesg | ./parse_usb.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.
#
use strict;
use Getopt::Long;

my $debug = 0;
GetOptions('d' => \$debug);

# FIXME: How to handle multiple registers being changed at the same time?

my %reg_map = (
	0x10e0 => "SN9C201_R10E0_IMG_FMT",
	0x1180 => "SN9C201_R1180_HSTART_LOW",
	0x1181 => "SN9C201_R1181_HSTART_HIGH",
	0x1182 => "SN9C201_R1182_VSTART_LOW",
	0x1183 => "SN9C201_R1183_VSTART_HIGH",
	0x1184 => "SN9C201_R1184_HSIZE",
	0x1185 => "SN9C201_R1185_VSIZE",
	0x1189 => "SN9C201_R1189_SCALE",
	0x11b8 => "SN9C201_R11B8_CLK_CTRL",
);


sub i2c_reg($)
{
	my %decode;
	my $reg = shift;

	$reg =~ s/\s+.*//;
	$reg = hex("$reg");

	if ($reg & 0x01) {
		$decode{"speed"} = "400kbps";
	} else {
		$decode{"speed"} = "100kbps";
	}
	if ($reg & 0x02) {
		$decode{"op"} = "RD";
	} else {
		$decode{"op"} = "WR";
	}
	$decode{"busy"} = "BUSY" if (($reg & 0x04) == 0);
	$decode{"err"} = "ERR" if ($reg & 0x08);
	$decode{"size"} = ($reg >> 4) & 7;
	$decode{"i2c"} = "3wire" if (($reg & 0x80) == 0);

	return %decode;
}

sub type_req($)
{
	my $reqtype = shift;
	my $s;

	if ($reqtype & 0x80) {
		$s = "RD ";
	} else {
		$s = "WR ";
	}
	if (($reqtype & 0x60) == 0x20) {
		$s .= "CLAS ";
	} elsif (($reqtype & 0x60) == 0x40) {
		$s .= "VEND ";
	} elsif (($reqtype & 0x60) == 0x60) {
		$s .= "RSVD ";
	}

	if (($reqtype & 0x1f) == 0x00) {
		$s .= "DEV ";
	} elsif (($reqtype & 0x1f) == 0x01) {
		$s .= "INT ";
	} elsif (($reqtype & 0x1f) == 0x02) {
		$s .= "EP ";
	} elsif (($reqtype & 0x1f) == 0x03) {
		$s .= "OTHER ";
	} elsif (($reqtype & 0x1f) == 0x04) {
		$s .= "PORT ";
	} elsif (($reqtype & 0x1f) == 0x05) {
		$s .= "RPIPE ";
	} else {
		$s .= sprintf "RECIP 0x%02x ", $reqtype & 0x1f;
	}

	$s =~ s/\s+$//;
	return $s;
}

my %i2c;
my $i2c_id;
while (<>) {
	tr/A-F/a-f/;
	if (m/([0-9a-f].) ([0-9a-f].) ([0-9a-f].) ([0-9a-f].) ([0-9a-f].) ([0-9a-f].) ([0-9a-f].) ([0-9a-f].)[\<\>\s]+(.*)/) {
		my $reqtype = hex($1);
		my $req = hex($2);
		my $wvalue = hex("$4$3");
		my $windex = hex("$6$5");
		my $wlen = hex("$8$7");
		my $payload = $9;
		my $fullpayload = $9;

		%i2c = i2c_reg($payload) if ($wvalue == 0x10c0);

		if (($wvalue >= 0x10c0) && ($wvalue <= 0x10c2)) {
			my $reg = $wvalue;
			if ($reg == 0x10c0) {
				$payload =~ s/^([0-9a-f].)//;
				$payload =~ s/^\s+//;
				$reg++;
			}
			if ($reg == 0x10c1 && $payload ne "") {
				$i2c_id = $payload;
				$i2c_id =~ s/\s+.*//;
				$i2c_id = "addr=0x$i2c_id, ";
				$payload =~ s/^([0-9a-f].)//;
				$payload =~ s/^\s+//;
				$reg++;
			}

			my $data;
			for (my $i = 0; ($i < $i2c{"size"}) && ($payload ne ""); $i++) {
				my $tmp = $payload;
				$tmp =~ s/\s+.*//;
				$payload =~ s/^([0-9a-f].)//;
				$payload =~ s/^\s+//;
				$data .= "0x$tmp, ";
			}
			$data =~ s/\,\s+$//;

			my $discard;
			for (my $i = 0; ($i < 5) && ($payload ne ""); $i++) {
				my $tmp = $payload;
				$tmp =~ s/\s+.*//;
				$payload =~ s/^([0-9a-f].)//;
				$payload =~ s/^\s+//;
				$discard .= "0x$tmp, ";
			}
			$discard =~ s/\,\s+$//;

			my $s = sprintf "%s %s %s %s %s size=%d",
				$i2c{"op"}, $i2c{"speed"}, $i2c{"busy"}, $i2c{"err"}, $i2c{"i2c"}, $i2c{"size"};
			$s =~ s/\s+/ /g;

			if ($debug) {
				if ($reqtype & 0x80) {
					printf "Read I2C: $s $i2c_id$data";
				} else {
					printf "I2C $s $i2c_id$data";
				}
				printf " ($discard)" if ($discard);
				printf "Extra: $payload" if ($payload);
				print "\n";

				printf("\t%s, Req %3d, wValue: 0x%04x, wIndex 0x%04x, wlen %d: %s\n",
					type_req($reqtype), $req, $wvalue, $windex, $wlen, $fullpayload);
			}
			next if ($reg < 0x10c2);
			if (($i2c{"size"} == 1) && ($reqtype & 0x80)) {
				printf "i2c_r1(gspca_dev, $data);\n";
			} elsif (($i2c{"size"} == 2) && (($reqtype & 0x80) == 0)) {
				printf "i2c_w1(gspca_dev, $data);\n";
			} else {
				if ($reqtype & 0x80) {
					printf "i2c_r(gspca_dev, { $data }, %d);\n", $i2c{"size"};
				} else {
					printf "i2c_w(gspca_dev, { $data }, %d);\n", $i2c{"size"};
				}
			}
		} else {
			printf("%s, Req %3d, wValue: 0x%04x, wIndex 0x%04x, wlen %d: %s\n",
				type_req($reqtype), $req, $wvalue, $windex, $wlen, $payload) if ($debug);

			my $reg;
			if (defined($reg_map{$wvalue})) {
				$reg = $reg_map{$wvalue};
			} else {
				$reg = sprintf "0x%04x", $wvalue;
			}

			if ($reqtype == 0xc1) {
				printf "reg_r(gspcadev, %s);\t/* read %s*/\n", $reg, $payload;
			} elsif ($reqtype == 0x41) {
				printf "reg_w(gspcadev, %s, { %s });*/\n", $reg, $payload;
			}
		}
	}
}
