File Coverage

blib/lib/App/AVR/Fuses.pm
Criterion Covered Total %
statement 95 103 92.2
branch 26 40 65.0
condition 1 3 33.3
subroutine 18 20 90.0
pod 0 7 0.0
total 140 173 80.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2019 -- leonerd@leonerd.org.uk
5              
6             package App::AVR::Fuses;
7              
8 3     3   136850 use strict;
  3         22  
  3         84  
9 3     3   15 use warnings;
  3         5  
  3         82  
10 3     3   14 use feature qw( say );
  3         5  
  3         307  
11 3     3   71 use 5.010;
  3         10  
12              
13             our $VERSION = '0.01';
14              
15 3     3   1601 use File::ShareDir qw( module_dir );
  3         77748  
  3         177  
16 3     3   2179 use Getopt::Long qw( GetOptionsFromArray );
  3         31446  
  3         12  
17 3     3   556 use List::Util qw( first );
  3         7  
  3         290  
18              
19 3     3   1350 use YAML ();
  3         21303  
  3         4827  
20              
21             my $SHAREDIR = module_dir( __PACKAGE__ );
22              
23             =head1 NAME
24              
25             C - support module for F
26              
27             =head1 DESCRIPTION
28              
29             This module contains the support code for the F command.
30              
31             =cut
32              
33             sub usage
34             {
35 0     0 0 0 my ( $err ) = @_;
36              
37 0         0 my ( $basename ) = $0 =~ m{/([^/]+)$};
38 0 0       0 ( $err ? \*STDERR : \*STDOUT )->print( <<"EOF" );
39             Usage: $basename [YAML-FILE] FUSES...
40              
41             Options:
42             -h, -help - display this help
43              
44             -v, --verbose - print more verbose messages
45              
46             -p, --part NAME - specify AVR part name as an alternative to giving
47             the YAML-FILE path
48              
49             -f, --fuse FUSE=VALUE - preset the given fuse value
50             FUSE: lfuse | hfuse | efuse
51             VALUE: 123 | 0456 | 0x78
52             EOF
53              
54 0         0 exit $err;
55             }
56              
57             sub run
58             {
59 9     9 0 8243 my $class = shift;
60 9         44 my @argv = @_;
61              
62 9         19 my $yamlpath;
63              
64             my %current;
65              
66             GetOptionsFromArray(
67             \@argv,
68              
69 0     0   0 'h|help' => sub { usage(0) },
70              
71             'v|verbose' => \my $VERBOSE,
72              
73             'p|part=s' => sub {
74 9     9   4958 my $partname = $_[1];
75 9 100       114 $partname = "ATmega\U$1" if $partname =~ m/^(?:atmega|m)(.*)$/i;
76 9 100       56 $partname = "ATtiny\U$1" if $partname =~ m/^(?:attiny|t)(.*)$/i;
77              
78 9         49 $yamlpath = "$SHAREDIR/$partname.yaml";
79 9 50       405 unless( -f $yamlpath ) {
80 0         0 print STDERR "No YAML file found at $yamlpath\n";
81 0         0 exit 1;
82             }
83             },
84              
85             'f|fuse=s' => sub {
86 1 50   1   153 my ( $fuse, $val ) = $_[1] =~ m/^(.*?)=(.*)$/ or die "Unable to parse --fuse\n";
87 1 50       7 $val = oct $val if $val =~ m/^0/;
88 1         5 $current{lc substr $fuse, 0, 1} = $val;
89             },
90 9 50       123 ) or usage(1);
91              
92 9   33     743 $yamlpath //= shift @argv;
93              
94 9 50       48 defined $yamlpath
95             or usage(1);
96              
97 9         50 my $self = $class->new(
98             yamlpath => $yamlpath,
99             );
100              
101 9         42 $self->set_fusereg( $_ => $current{$_} ) for keys %current;
102              
103 9         32 foreach my $arg ( @argv ) {
104 3 50       34 my ( $name, $val ) = $arg =~ m/^(\w+)=(.*)$/ or
105             die "Unable to parse '$arg'\n";
106              
107 3 100       14 if( $val eq "?" ) {
108 1         8 say join "\n",
109             "Possible values for $name are:",
110             $self->list_values_for_fuse( $name );
111              
112 1         44 return 1;
113             }
114              
115 2         14 $self->set_fuse( uc $name, $val );
116             }
117              
118 8 100       51 if( $VERBOSE ) {
119             # Explain the current fuse values
120 2         14 foreach my $name ( $self->{fuses}->@* ) {
121 21         43 my $fuseinfo = $self->{fusemap}{$name};
122              
123 21         50 my $mask = $fuseinfo->{mask} + 0; # force number because YAML loading
124              
125 21         52 my $val = $self->{regs}{ $fuseinfo->{reg} } & $mask;
126              
127 21 100       37 if( $fuseinfo->{values} ) {
128 5     21   38 my $chosen = first { $_->{value} == $val } $fuseinfo->{values}->@*;
  21         72  
129 5         27 $val = "$chosen->{name} - $chosen->{caption}";
130             }
131             else {
132 16 100       30 $val = $val ? "1" : "0";
133             }
134              
135 21         79 say "using $name=$val";
136             }
137             }
138              
139 8         33 say join " ", $self->gen_fuses_avrdude;
140             }
141              
142             sub new
143             {
144 9     9 0 36 my $class = shift;
145 9         32 my %opts = @_;
146              
147 9         51 my $data = YAML::LoadFile( $opts{yamlpath} );
148              
149 9         1076547 my %regs;
150              
151             my $self = bless {
152 9         41 ( map { $_ => $data->{$_} } qw( reginfos fuses fusemap ) ),
  27         165  
153              
154             regs => \%regs,
155             }, $class;
156              
157             # Initialise defaults
158 9         125 $regs{ $_->{name} } = $_->{default} for $self->{reginfos}->@*;
159              
160 9         54 return $self;
161             }
162              
163             sub set_fusereg
164             {
165 1     1 0 3 my $self = shift;
166 1         3 my ( $name, $value ) = @_;
167              
168 1 50       5 exists $self->{regs}{$name} or
169             die "Unrecognised fuse register name $name\n";
170              
171 1         4 $self->{regs}{$name} = $value;
172             }
173              
174             sub set_fuse
175             {
176 2     2 0 5 my $self = shift;
177 2         5 my ( $name, $val ) = @_;
178              
179 2 50       9 my $fuseinfo = $self->{fusemap}{$name} or
180             die "No such fuse $name\n";
181              
182 2         6 my $reg = $fuseinfo->{reg};
183 2         7 my $mask = $fuseinfo->{mask} + 0; # force number because YAML loading
184              
185 2         6 my $regval = $self->{regs}{$reg};
186              
187 2         7 $regval &= ~$mask;
188              
189 2 100       7 if( $fuseinfo->{values} ) {
190 1     2   13 my $chosen = first { $_->{name} eq $val } $fuseinfo->{values}->@*;
  2         6  
191 1 50       6 defined $chosen or
192             die "Unrecognised value for $name\n";
193              
194 1         5 $regval |= $chosen->{value};
195             }
196             else {
197 1 50       5 $regval |= $mask if $val;
198             }
199              
200 2         8 $self->{regs}{$reg} = $regval;
201             }
202              
203             sub list_values_for_fuse
204             {
205 1     1 0 3 my $self = shift;
206 1         3 my ( $name ) = @_;
207              
208 1 50       5 my $fuseinfo = $self->{fusemap}{$name} or
209             die "No such fuse $name\n";
210              
211 1 50       5 if( $fuseinfo->{values} ) {
212             return map {
213 4         27 " $_->{name} - $_->{caption}"
214 1         4 } $fuseinfo->{values}->@*;
215             }
216             else {
217             return (
218 0         0 " 1",
219             " 0",
220             );
221             }
222             }
223              
224             sub gen_fuses_avrdude
225             {
226 8     8 0 19 my $self = shift;
227              
228 8         27 my @output;
229              
230 8         23 foreach my $reginfo ( $self->{reginfos}->@* ) {
231 24         49 my $regname = $reginfo->{name};
232              
233             # avrdude format -U lfuse:w:0x62:m
234 24         157 push @output, "-U", sprintf "%sfuse:w:0x%02X:m", $regname, $self->{regs}{$regname};
235             }
236              
237 8         421 return @output;
238             }
239              
240             =head1 AUTHOR
241              
242             Paul Evans
243              
244             =cut
245              
246             0x55AA;