#!/usr/bin/perl
use strict;

#   Copyright (C) 2014 Mauro Carvalho Chehab <mchehab@kernel.org>
#   Copyright (c) 2014 Samsung Electronics Co., Ltd.
#
#   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.
#

my %speed_map = (
	0x07 => "250KHz",
	0x14 => "100KHz",
	0x40 => "30KHz",
	0x60 => "20KHz",
);

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_speed = "unknown";
my $i2c_addr = "unknown";
my @i2c_sbuf;
my @i2c_rbuf;
my $i2c_hold;

while (<>) {
	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 $timestamp = $1;
		my $reqtype = hex($2);
		my $req = hex($3);
		my $val = hex("$5$4");
		my $reg = hex("$7$6");
		my $wlen = hex("$9$8");
		my $payload = $10;

		$timestamp =~ s/^\s+//;
		$timestamp =~ s/\s+$//;

		if (($reqtype == 0x40) && ($reg == 0x02ff)) {
			# Start of an I2C tranfer
			@i2c_sbuf = ();
			@i2c_rbuf = ();
			next;
		}
		if (($reqtype == 0x40) && ($reg == 0x0202)) {
			# I2C speed
			$i2c_speed = sprintf "0x%02x", $val;
			$i2c_speed = $speed_map{$val} if defined($speed_map{$val});
			next;
		}

		if (($reqtype == 0x40) && ($reg == 0x0203)) {
			# I2C addr
			$i2c_addr = $val >> 1;
			next;
		}

		if (($reqtype == 0x40) && ($reg == 0x0200)) {
			# I2C trigger
			$i2c_hold = ($val & 0x40) == 0x40;

			if (!$i2c_hold && ($val & 0x01)) {
				printf "$timestamp au0828 I2C write addr = 0x%02x (speed = %s) ", $i2c_addr, $i2c_speed;
				printf "0x%02x ", $_ foreach (@i2c_sbuf);
				print "\n";

				@i2c_sbuf = ();
			}

			next;
		}

		if (($reqtype == 0xc0) && ($reg == 0x0201)) {
			# Wait to be ready
			next;
		}


		if (($reqtype == 0x40) && ($reg == 0x0205)) {
			# I2C write data
			push @i2c_sbuf, $val;

			next;
		}

		if (($reqtype == 0xc0) && ($reg == 0x0209)) {
			# I2C read data
			my @bytes = split(/ /, $payload);
			push @i2c_rbuf, hex($_) foreach(@bytes);

			if (!$i2c_hold) {
				printf "$timestamp au0828 I2C read addr = 0x%02x (speed = %s, len=%d) ", $i2c_addr, $i2c_speed, scalar(@i2c_rbuf);
				printf "0x%02x ", $_ foreach (@i2c_rbuf);
				print "\n";

				@i2c_rbuf = ();
			}
			next;
		}

		printf("%s %s(0x%02x), Req 0x%02x, register 0x%04x, value 0x%04x, wlen %d: %s\n",
			$timestamp, type_req($reqtype), $reqtype, $req, $reg, $val, $wlen, $payload);
	}
}
