#!/usr/bin/perl # Copyright (C) 2008-2011 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 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", "0x34" => "EM28XX_R34_VBI_START_H", "0x35" => "EM28XX_R35_VBI_START_V", "0x36" => "EM28XX_R36_VBI_WIDTH", "0x37" => "EM28XX_R37_VBI_HEIGHT", "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 0[23] 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"; next; } if (m/c0 0[23] 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"; next; } if (m/c0 0[23] 00 00 ([0-9a-f].) 00 ([0-9a-f].) 00\s+[\<]+/) { printf "i2c_master_recv(0x$1>>1, &buf, 0x$2); /* nothing returned */\n"; next; } }