File Coverage

blib/lib/Device/AVR/UPDI.pm
Criterion Covered Total %
statement 349 375 93.0
branch 49 70 70.0
condition 1 5 20.0
subroutine 79 85 92.9
pod 16 27 59.2
total 494 562 87.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-2023 -- leonerd@leonerd.org.uk
5              
6 13     13   1852931 use v5.20;
  13         91  
7 13     13   101 use warnings;
  13         26  
  13         450  
8 13     13   8142 use Object::Pad 0.76 ':experimental(adjust_params)';
  13         145878  
  13         1011  
9              
10             package Device::AVR::UPDI 0.13;
11             class Device::AVR::UPDI :strict(params);
12              
13 13     13   5800 use Carp;
  13         31  
  13         869  
14              
15 13     13   7287 use Future::AsyncAwait;
  13         30948  
  13         75  
16 13     13   1227 use Future::IO 0.03; # ->sysread_exactly
  13         13932  
  13         482  
17              
18 13     13   7192 use File::ShareDir qw( module_dir );
  13         372282  
  13         800  
19 13     13   6279 use YAML ();
  13         97692  
  13         802  
20              
21             my $SHAREDIR = module_dir( __PACKAGE__ );
22              
23 13     13   6874 use Object::Pad::ClassAttr::Struct 0.04;
  13         14920  
  13         71  
24              
25 13   50 13   828 use constant DEBUG => $ENV{UPDI_DEBUG} // 0;
  13         37  
  13         1770  
26              
27             class Device::AVR::UPDI::_PartInfo :Struct {
28 1     1   5 field $name;
  1         6  
29              
30 1     1   4 field $signature;
  1         5  
31 27     27   53 field $baseaddr_nvmctrl;
  27         118  
32 2     2   6 field $baseaddr_fuse;
  2         10  
33 1     1   3 field $baseaddr_sigrow;
  1         6  
34              
35 4     4   10 field $baseaddr_flash;
  4         20  
36 0     0   0 field $pagesize_flash;
  0         0  
37 1     1   4 field $size_flash;
  1         7  
38              
39 4     4   10 field $baseaddr_eeprom;
  4         21  
40 0     0   0 field $pagesize_eeprom;
  0         0  
41 0     0   0 field $size_eeprom;
  0         0  
42              
43 1     1   4 field $fusenames;
  1         9  
44             }
45              
46             my %partinfos;
47             {
48             while( readline DATA ) {
49             m/^#/ and next;
50             chomp;
51             my ( $name, $signature, @fields ) = split m/\|/, $_;
52             $signature = pack "H*", $signature;
53             my $fuses = [ map { length $_ ? $_ : undef } split m/,/, pop @fields ];
54             m/^0x/ and $_ = hex $_ for @fields;
55              
56             my $partinfo = Device::AVR::UPDI::_PartInfo->new_values( $name, $signature, @fields, $fuses );
57              
58             $partinfos{lc $name} = $partinfo;
59             $partinfos{"m$1"} = $partinfo if $name =~ m/^ATmega(.*)$/;
60             $partinfos{"t$1"} = $partinfo if $name =~ m/^ATtiny(.*)$/;
61             }
62              
63             close DATA;
64             }
65              
66             =head1 NAME
67              
68             C - interact with an F microcontroller over F
69              
70             =head1 DESCRIPTION
71              
72             This module provides a class for interacting with an F microcontroller
73             over the F programming and debug interface. This is used by chips in the
74             newer F 0-series, or F 0-series or 1-series, or F or
75             F families.
76              
77             =head2 Hardware Interface
78              
79             This code expects to find a serial port connected to the UPDI pin of the
80             microcontroller as a shared single-wire interface. Suitable hardware to
81             provide this can be created using a USB-UART adapter, connecting the C
82             line directly to the MCU's C pin, and connecting C via a
83             current-limiting resistor of 1kohm.
84              
85             +------------+ +-------------------+
86             | RX-|-------------+ | |
87             | USB-UART | +------|-UPDI |
88             | TX-|---[ 1k ]---+ | ATmega or ATtiny |
89             +------------+ +-------------------|
90              
91             =cut
92              
93             =head1 CONSTRUCTORS
94              
95             =cut
96              
97             =head2 new
98              
99             $updi = Device::AVR::UPDI->new( ... );
100              
101             Constructs and returns a new C instance.
102              
103             Takes the following named arguments:
104              
105             =over 4
106              
107             =item dev => STRING
108              
109             Path to the device node representing the serial port connection.
110              
111             =item fh => IO
112              
113             Alternative to C, provides an IO handle directly. This should be an
114             instance of L, or at least, provide the same interface.
115              
116             =item part => STRING
117              
118             Name of the AVR chip to interact with. This is used to define parameters like
119             memory size and location of internal peripherals.
120              
121             Any of the following forms are accepted
122              
123             part => "ATtiny814" | "attiny814" | "t814"
124             part => "ATmega4809" | "atmega4809" | "m4809"
125             part => "AVR64DA48" | "avr64da48"
126              
127             =item baud => INT
128              
129             Optional. Overrides the baud rate for communications. Defaults to 115200.
130              
131             Lower numbers may be useful if communication is unreliable, for example over a
132             long cable or with high capacitance or noise.
133              
134             =back
135              
136             After construction, the link must be initialised by calling L
137             before any of the command methods are used.
138              
139             =cut
140              
141             field $_fh;
142 7     7 1 36 field $_partinfo :reader;
  7         90  
143              
144 9     9   200 field $_nvm_version :writer(_set_nvm_version);
  9         191  
145              
146             ADJUST :params (
147             :$fh = undef,
148             :$dev = undef,
149             :$baud //= 115200,
150             :$part,
151             ) {
152             $_fh = $fh // do {
153             require IO::Termios;
154             IO::Termios->open( $dev ) or
155             die "Unable to open $dev - $!\n";
156             };
157              
158             $_fh->cfmakeraw();
159              
160             # 8bits, Even parity, 2 stop
161             $_fh->set_mode( "$baud,8,e,2" );
162             $_fh->setflag_clocal( 1 );
163              
164             $_fh->autoflush;
165              
166             $_partinfo = $partinfos{lc $part} //
167             croak "Unrecognised part name $part";
168             }
169              
170             field $_reg_ctrla = 0;
171              
172             =head1 ACCESSORS
173              
174             =cut
175              
176             =head2 partinfo
177              
178             $partinfo = $updi->partinfo;
179              
180             Returns the Part Info structure containing base addresses and other parameters
181             which may be useful for interacting with the chip.
182              
183             The returned structure provides the following fields
184              
185             $name = $partinfo->name;
186              
187             $sig = $partinfo->signature;
188              
189             $addr = $partinfo->baseaddr_nvmctrl;
190             $addr = $partinfo->baseaddr_fuse;
191             $addr = $partinfo->baseaddr_flash;
192             $addr = $partinfo->baseaddr_eeprom;
193             $addr = $partinfo->baseaddr_sigrow;
194              
195             $bytes = $partinfo->pagesize_flash;
196             $bytes = $partinfo->pagesize_eeprom;
197             $bytes = $partinfo->size_flash;
198             $bytes = $partinfo->size_eeprom;
199              
200             $fusenames = $partinfo->fusenames;
201              
202             =cut
203              
204             # generated accessor
205              
206             =head2 fuseinfo
207              
208             Returns a data structure containing information about the individual fuse
209             fields defined by this device.
210              
211             This is parsed directly from a shipped YAML file; see the files in the
212             F directory for more details.
213              
214             =cut
215              
216             field $_fuseinfo;
217              
218             method fuseinfo
219 0     0 1 0 {
220 0   0     0 return $_fuseinfo //= do {
221 0         0 my $yamlpath = "$SHAREDIR/${\ $self->partinfo->name }.yaml";
  0         0  
222 0 0       0 unless( -f $yamlpath ) {
223 0         0 die "No YAML file found at $yamlpath\n";
224             }
225 0         0 YAML::LoadFile( $yamlpath );
226             };
227             }
228              
229             =head1 METHODS
230              
231             The following methods documented in an C expression return L
232             instances.
233              
234             =cut
235              
236             use constant {
237             # SYNC byte
238 13         103731 SYNC => "\x55",
239              
240             # Instruction opcodes
241             OP_LDS => 0x00,
242             OP_STS => 0x40,
243             OP_DATA8 => 0x00,
244             OP_DATA16 => 0x01,
245             OP_ADDR8 => 0x00,
246             OP_ADDR16 => 0x01,
247             OP_ADDR24 => 0x02,
248             OP_LD => 0x20,
249             OP_ST => 0x60,
250             OP_PTR => 0x00,
251             OP_PTRINC => 0x04,
252             OP_PTRREG => 0x08,
253             OP_LDCS => 0x80,
254             OP_STCS => 0xC0,
255             OP_REPEAT => 0xA0,
256             OP_KEY => 0xE0,
257             OP_KEY_READSIB => 0xE5,
258              
259             # UPDI registers
260             REG_STATUSA => 0x00,
261             REG_STATUSB => 0x01,
262             REG_CTRLA => 0x02,
263             CTRLA_IBDLY => (1<<7),
264             CTRLA_PARD => (1<<5),
265             CTRLA_DTD => (1<<4),
266             CTRLA_RSD => (1<<3),
267             CTRLA_GTVAL_MASK => 0x07,
268             REG_CTRLB => 0x03,
269             CTRLB_NACKDIS => (1<<4),
270             CTRLB_CCDETDIS => (1<<3),
271             CTRLB_UPDIDIS => (1<<2),
272              
273             REG_ASI_KEY_STATUS => 0x07,
274             ASI_KEY_UROWWRITE => (1<<5),
275             ASI_KEY_NVMPROG => (1<<4),
276             ASI_KEY_CHIPERASE => (1<<3),
277             REG_ASI_RESET_REQ => 0x08,
278             ASI_RESET_REQ_SIGNATURE => 0x59,
279             REG_ASI_CTRLA => 0x09,
280             REG_ASI_SYS_CTRLA => 0x0A,
281             REG_ASI_SYS_STATUS => 0x0B,
282             ASI_SYS_STATUS_RSTSYS => (1<<5),
283             ASI_SYS_STATUS_INSLEEP => (1<<4),
284             ASI_SYS_STATUS_NVMPROG => (1<<3),
285             ASI_SYS_STATUS_UROWPROG => (1<<2),
286             ASI_SYS_STATUS_LOCKSTATUS => (1<<0),
287             REG_ASI_CRC_STATUS => 0x0C,
288              
289             # Keys
290             KEY_CHIPERASE => "\x65\x73\x61\x72\x45\x4D\x56\x4E",
291             KEY_NVMPROG => "\x20\x67\x6F\x72\x50\x4D\x56\x4E",
292 13     13   33743 };
  13         53  
293              
294             async method _break
295 1         5 {
296 1         8 my $was_baud = $_fh->getobaud;
297              
298             # Writing a 0 at 300baud is sufficient to look like a BREAK
299 1         1089 $_fh->setbaud( 300 );
300 1         1151 $_fh->print( "\x00" );
301              
302 1         1073 await Future->wait_any(
303             Future::IO->sysread( $_fh, 1 ),
304             Future::IO->sleep( 0.05 )
305             ->then_fail( "Timed out waiting for echo of BREAK - is this a UPDI programmer?\n" )
306             );
307              
308 1         3070 $_fh->setbaud( $was_baud );
309 1     1   2 }
310              
311             async method _op_writeread
312 110         230 {
313 110         237 my ( $write, $readlen ) = @_;
314              
315 110         151 printf STDERR "WR: => %v02X\n", $write if DEBUG > 1;
316 110         388 await Future::IO->syswrite_exactly( $_fh, $write );
317              
318 110         270481 my $buf = "";
319 110         263 my $len = length( $write ) + $readlen;
320              
321 110         298 while( length $buf < $len ) {
322 110 50       305 my $what = ( length $buf >= length $write ) ?
323             "chip response - is the chip present?" :
324             "echo of command - is this a UPDI programmer?";
325              
326 110         419 $buf .= await Future->wait_any(
327             Future::IO->sysread( $_fh, $len - length $buf ),
328             Future::IO->sleep( 0.1 )
329             ->then_fail( "Timed out waiting for $what\n" )
330             );
331              
332 110         330183 my $got = substr( $buf, 0, length $write );
333 110         267 my $exp = substr( $write, 0, length $buf );
334              
335 110         156 printf STDERR "RD: <= %v02X\n", $buf if DEBUG > 1;
336              
337 110 50       479 die "Received different bytes while waiting to receive echo of command - is this a UPDI programmer?\n"
338             if $got ne $exp;
339             }
340              
341 110         563 return substr( $buf, length( $write ) );
342 110     110   189 }
343              
344             method _pack_op_addr
345 29     29   54 {
346 29         73 my ( $op, $addr ) = @_;
347              
348 29 100       239 return $_nvm_version >= 2
349             ? pack( "C S> 16 )
350             : pack( "C S<", $op|OP_ADDR16<<2, $addr );
351             }
352              
353             async method _op_write_expecting_ack
354 45         111 {
355 45         110 my ( $op, $write ) = @_;
356              
357 45 100       114 if( $_reg_ctrla & CTRLA_RSD ) {
358             # No ACK expected
359 6         23 await $self->_op_writeread( $write, 0 );
360             }
361             else {
362 39         100 my $ack = await $self->_op_writeread( $write, 1 );
363 39 50       3129 $ack eq "\x40" or croak "Expected ACK to $op";
364             }
365 45     45   83 }
366              
367             async method stptr
368 10         24 {
369 10         24 my ( $addr ) = @_;
370              
371 10 100       83 my $cmd = $_nvm_version >= 2
372             ? pack( "C S> 16 )
373             : pack( "C S<", OP_ST|OP_PTRREG|OP_ADDR16, $addr );
374              
375 10         45 await $self->_op_write_expecting_ack( "ST PTR" => SYNC . $cmd );
376 10     10 0 28 }
377              
378             async method lds8
379 13         28 {
380 13         29 my ( $addr ) = @_;
381              
382 13         42 my $ret = unpack "C", await
383             $self->_op_writeread( SYNC . $self->_pack_op_addr( OP_LDS, $addr ), 1 );
384              
385 13         1032 printf STDERR ">> LDS8[%04X] -> %02X\n", $addr, $ret if DEBUG;
386 13         48 return $ret;
387 13     13 0 29 }
388              
389             async method sts8
390 16         34 {
391 16         38 my ( $addr, $val ) = @_;
392              
393 16         26 printf STDERR ">> STS8[%04X] = %02X\n", $addr, $val if DEBUG;
394              
395 16         50 await $self->_op_write_expecting_ack( STS8 =>
396             SYNC . $self->_pack_op_addr( OP_STS, $addr ) );
397              
398 16         1260 await $self->_op_write_expecting_ack( "STS8 DATA" =>
399             pack( "C", $val ) );
400 16     16 0 29 }
401              
402             async method ld
403 5         20 {
404 5         20 my ( $addr, $len ) = @_;
405              
406 5         21 await $self->stptr( $addr );
407              
408 5         806 my $ret = "";
409              
410 5         19 while( $len ) {
411 5         11 my $chunklen = $len;
412             # REPEAT can only do at most 255 repeats
413 5 50       19 $chunklen = 256 if $chunklen > 256;
414              
415             await
416 5 50       50 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $chunklen - 1 ), 0 ) if $chunklen > 1;
417 5         399 $ret .= await
418             $self->_op_writeread( SYNC . pack( "C", OP_LD|OP_PTRINC|OP_DATA8 ), $chunklen );
419              
420 5         393 $len -= $chunklen;
421             }
422              
423 5         12 printf STDERR ">> LD[%04X] -> %v02X\n", $addr, $ret if DEBUG;
424 5         22 return $ret;
425 5     5 0 12 }
426              
427             async method st8
428 3         13 {
429 3         13 my ( $addr, $data ) = @_;
430              
431 3         6 printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG;
432              
433 3         9 my $len = length( $data );
434              
435 3         13 await $self->stptr( $addr );
436              
437             await
438 3 100       634 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1;
439              
440             await
441 3         158 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 );
442              
443             # If we're in RSD mode we might as well just write all the data in one go
444 3 100       264 if( $_reg_ctrla & CTRLA_RSD ) {
445 2         10 await $self->_op_writeread( $data, 0 )
446             }
447             else {
448 1         7 foreach my $byte ( split //, $data ) {
449 1         4 await $self->_op_write_expecting_ack( "STR data" => $byte );
450             }
451             }
452 3     3 0 8 }
453              
454             async method st16
455 2         10 {
456 2         7 my ( $addr, $data ) = @_;
457              
458 2         4 printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG;
459              
460             # Count in 16bit words
461 2         10 my $len = int( length( $data ) / 2 );
462              
463 2         8 await $self->stptr( $addr );
464              
465             await
466 2 50       518 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1;
467              
468             await
469 2         153 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA16 ), 0 );
470              
471             # If we're in RSD mode we might as well just write all the data in one go
472 2 50       154 if( $_reg_ctrla & CTRLA_RSD ) {
473 2         25 await $self->_op_writeread( substr( $data, 0, $len*2 ), 0 );
474             }
475             else {
476 0         0 foreach my $word ( $data =~ m/.{2}/sg ) {
477 0         0 await $self->_op_write_expecting_ack( "STR data" => $word );
478             }
479             }
480              
481 2 50       162 if( length( $data ) % 2 ) {
482             # Final byte
483 2         10 my $byte = substr $data, 2 * $len, 1;
484             await
485 2         9 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 );
486              
487 2         171 await $self->_op_write_expecting_ack( "STR data" => $byte );
488             }
489 2     2 0 6 }
490              
491             async method ldcs
492 7         20 {
493 7         17 my ( $reg ) = @_;
494              
495 7         51 my $ret = unpack "C", await
496             $self->_op_writeread( SYNC . pack( "C", OP_LDCS | $reg ), 1 );
497              
498 7         533 printf STDERR ">> LDCS[%02X] -> %02X\n", $reg, $ret if DEBUG;
499 7         29 return $ret;
500 7     7 0 14 }
501              
502             async method stcs
503 16         38 {
504 16         42 my ( $reg, $value ) = @_;
505              
506 16         24 printf STDERR ">> STCS[%02X] = %02X\n", $reg, $value if DEBUG;
507              
508             await
509 16         110 $self->_op_writeread( SYNC . pack( "CC", OP_STCS | $reg, $value ), 0 );
510 16     16 0 33 }
511              
512             async method key
513 2         4 {
514 2         7 my ( $key ) = @_;
515              
516 2 50       9 length $key == 8 or
517             die "Expected 8 byte key\n";
518              
519 2         4 printf STDERR ">> KEY %v02X\n", $key if DEBUG;
520              
521             await
522 2         24 $self->_op_writeread( SYNC . pack( "C a*", OP_KEY, $key ), 0 );
523 2     2 0 5 }
524              
525             async method set_rsd
526 9         34 {
527 9         27 my ( $on ) = @_;
528              
529 9 100       33 my $val = $on ? $_reg_ctrla | CTRLA_RSD : $_reg_ctrla & ~CTRLA_RSD;
530              
531 9 100       56 await $self->stcs( REG_CTRLA, $_reg_ctrla = $val ) if $_reg_ctrla != $val;
532 9     9 0 22 }
533              
534             =head2 init_link
535              
536             await $updi->init_link;
537              
538             Initialise the UPDI link for proper communication.
539              
540             This method must be invoked after the object is constructed, before using any
541             of the other commands.
542              
543             =cut
544              
545             async method init_link
546 1         15 {
547             # Sleep 100msec before sending BREAK in case of attached UPDI 12V pulse hardware
548 1         11 await Future::IO->sleep( 0.1 );
549              
550 1         1510 await $self->_break;
551              
552             # We have to disable collision detection or else the chip won't respond
553             # properly
554 1         1244 await $self->stcs( REG_CTRLB, CTRLB_CCDETDIS );
555              
556             # Set CTRLA to known state also
557 1         165 await $self->stcs( REG_CTRLA, $_reg_ctrla = 0 );
558              
559             # Read the SIB so we can determine what kind of NVM controller is required
560 1         152 my $sib = await $self->read_sib;
561 1 50       81 $sib->{nvm_version} =~ m/^P:(\d)/ or croak "Unrecognised NVM_VERSION string: $sib->{nvm_version}";
562 1         8 $_nvm_version = $1;
563 1     1 1 9543 }
564              
565             =head2 read_updirev
566              
567             $rev = await $updi->read_updirev;
568              
569             Reads the C field of the C register.
570              
571             =cut
572              
573             async method read_updirev
574 1         3 {
575 1         6 return ( await $self->ldcs( REG_STATUSA ) ) >> 4;
576 1     1 1 2050 }
577              
578             =head2 read_asi_sys_status
579              
580             Reads the C register.
581              
582             =cut
583              
584             async method read_asi_sys_status
585 0         0 {
586 0         0 return await $self->ldcs( REG_ASI_SYS_STATUS );
587 0     0 1 0 }
588              
589             =head2 read_sib
590              
591             $sib = await $updi->read_sib;
592              
593             Reads the System Information Block.
594              
595             This is returned in a HASH reference, containing four keys:
596              
597             {
598             family => "tinyAVR",
599             nvm_version => "P:0",
600             ocd_version => "D:0",
601             dbg_osc_freq => 3,
602             }
603              
604             =cut
605              
606             async method read_sib
607 2         7 {
608 2         18 my $bytes = await
609             $self->_op_writeread( SYNC . pack( "C", OP_KEY_READSIB ), 16 );
610 2         146 printf STDERR ">> READSIB -> %v02X\n", $bytes if DEBUG;
611              
612 2         17 my ( $family, $nvm, $ocd, $dbgosc ) = unpack "A7 x A3 A3 x A1", $bytes;
613             return {
614 2         17 family => $family,
615             nvm_version => $nvm,
616             ocd_version => $ocd,
617             dbg_osc_freq => $dbgosc,
618             };
619 2     2 1 6204 }
620              
621             =head2 read_signature
622              
623             $signature = await $updi->read_signature;
624              
625             Reads the three signature bytes from the Signature Row of the device. This is
626             returned as a plain byte string of length 3.
627              
628             =cut
629              
630             async method read_signature
631 1         4 {
632             # The ATtiny814 datasheet says
633             # All Atmel microcontrollers have a three-byte signature code which
634             # identifies the device. This code can be read in both serial and parallel
635             # mode, also when the device is locked. The three bytes reside in a
636             # separate address space.
637             # So far no attempt at reading signature over UPDI from a locked device has
638             # been successful. :(
639              
640 1         7 return await $self->ld( $_partinfo->baseaddr_sigrow, 3 );
641 1     1 1 5057 }
642              
643             =head2 request_reset
644              
645             await $updi->request_reset( $reset );
646              
647             Sets or clears the system reset request. Typically used to issue a system
648             reset by momentarilly toggling the request on and off again:
649              
650             await $updi->request_reset( 1 );
651             await $updi->request_reset( 0 );
652              
653             =cut
654              
655             async method request_reset
656 6         31 {
657 6         15 my ( $reset ) = @_;
658              
659 6         28 await $self->stcs( REG_ASI_RESET_REQ, $reset ? ASI_RESET_REQ_SIGNATURE : 0 );
660 6     6 1 7519 }
661              
662             =head2 erase_chip
663              
664             await $updi->erase_chip;
665              
666             Requests a full chip erase, waiting until the erase is complete.
667              
668             After this, the chip will be unlocked.
669              
670             Takes an optional named argument:
671              
672             =over 4
673              
674             =item no_reset => BOOL
675              
676             If true, does not issue a system reset request after loading the key. This
677             allows you to load multiple keys at once before sending the reset, which
678             may be required e.g. to recover from a bad C fuse setting.
679              
680             await $updi->erase_chip( no_reset => 1 );
681             await $updi->enable_nvmprog;
682              
683             =back
684              
685             =cut
686              
687             async method erase_chip
688 1         4 {
689 1         4 my %opts = @_;
690              
691 1         4 await $self->key( KEY_CHIPERASE );
692              
693 1 50       185 die "Failed to set CHIPERASE key\n" unless ASI_KEY_CHIPERASE & await $self->ldcs( REG_ASI_KEY_STATUS );
694              
695 1 50       74 return if $opts{no_reset};
696              
697 1         4 await $self->request_reset( 1 );
698 1         222 await $self->request_reset( 0 );
699              
700 1         213 my $timeout = 50;
701 1         5 while( --$timeout ) {
702 2 100       1363 last if not ASI_SYS_STATUS_LOCKSTATUS & await $self->ldcs( REG_ASI_SYS_STATUS );
703              
704 1         76 await Future::IO->sleep( 0.05 );
705             }
706 1 50       75 die "Failed to unlock chip\n" if !$timeout;
707 1     1 1 2674 }
708              
709             =head2 enable_nvmprog
710              
711             await $updi->enable_nvmprog;
712              
713             Requests the chip to enter NVM programming mode.
714              
715             =cut
716              
717             async method enable_nvmprog
718 1         3 {
719 1         6 await $self->key( KEY_NVMPROG );
720              
721 1 50       181 die "Failed to set NVMPROG key\n" unless ASI_KEY_NVMPROG & await $self->ldcs( REG_ASI_KEY_STATUS );
722              
723 1         75 await $self->request_reset( 1 );
724 1         216 await $self->request_reset( 0 );
725              
726 1         208 my $timeout = 50;
727 1         5 while( --$timeout ) {
728 2 100       1354 last if ASI_SYS_STATUS_NVMPROG & await $self->ldcs( REG_ASI_SYS_STATUS );
729              
730 1         75 await Future::IO->sleep( 0.05 );
731             }
732 1 50       74 die "Timed out waiting for NVMPROG key to be accepted\n" if !$timeout;
733 1     1 1 2629 }
734              
735             field $_nvmctrl;
736              
737             method nvmctrl
738 10     10 0 26 {
739 10 100       54 return $_nvmctrl if defined $_nvmctrl;
740              
741 6 50       20 defined $_nvm_version or
742             croak "Must ->init_link before calling ->nvmctrl";
743              
744             # ATtiny and ATmega chips claim "P:0"
745 6 100       51 return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv0->new( updi => $self )
746             if $_nvm_version == 0;
747              
748             # AVR Dx chips claim "P:2"
749 3 50       45 return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv2->new( updi => $self )
750             if $_nvm_version == 2;
751              
752 0         0 croak "Unrecognised NVM version $_nvm_version";
753             }
754              
755             =head2 read_flash_page
756              
757             $data = await $updi->read_flash_page( $addr, $len );
758              
759             Reads a single flash page and returns the data. C<$addr> is within the flash
760             address space.
761              
762             =cut
763              
764             async method read_flash_page
765 2         7 {
766 2         6 my ( $addr, $len ) = @_;
767              
768 2         9 return await $self->nvmctrl->read_flash_page( $addr, $len );
769 2     2 1 4994 }
770              
771             =head2 write_flash_page
772              
773             await $updi->write_flash_page( $addr, $data );
774              
775             Writes a single flash page into the NVM controller in 16-bit word transfers.
776             C<$addr> is within the flash address space.
777              
778             =cut
779              
780             async method write_flash_page
781 2         8 {
782 2         10 my ( $addr, $data ) = @_;
783              
784 2         17 await $self->nvmctrl->write_flash_page( $addr, $data );
785 2     2 1 24571 }
786              
787             =head2 read_eeprom_page
788              
789             $data = await $updi->read_eeprom_page( $addr, $len );
790              
791             Reads a single EEPROM page and returns the data. C<$addr> is within the EEPROM
792             address space.
793              
794             =cut
795              
796             async method read_eeprom_page
797 2         8 {
798 2         6 my ( $addr, $len ) = @_;
799              
800 2         7 return await $self->nvmctrl->read_eeprom_page( $addr, $len );
801 2     2 1 4694 }
802              
803             =head2 write_eeprom_page
804              
805             Similar to L but issues a combined erase-and-write
806             command and C<$addr> is within the EEPROM address space.
807              
808             =cut
809              
810             async method write_eeprom_page
811 2         8 {
812 2         7 my ( $addr, $data ) = @_;
813              
814 2         31 await $self->nvmctrl->write_eeprom_page( $addr, $data );
815 2     2 1 24870 }
816              
817             =head2 write_fuse
818              
819             await $updi->write_fuse( $idx, $value );
820              
821             Writes a fuse value. C<$idx> is the index of the fuse within the FUSES memory
822             segment, from 0 onwards.
823              
824             =cut
825              
826             async method write_fuse
827 2         7 {
828 2         6 my ( $idx, $value ) = @_;
829              
830 2         9 await $self->nvmctrl->write_fuse( $idx, $value );
831 2     2 1 6172 }
832              
833             =head2 read_fuse
834              
835             $value = await $updi->read_fuse( $idx );
836              
837             Reads a fuse value. C<$idx> is the index of the fuse within the FUSES memory
838             segment, from 0 onwards.
839              
840             =cut
841              
842             async method read_fuse
843 0         0 {
844 0         0 my ( $idx ) = @_;
845              
846 0         0 my $addr = $_partinfo->baseaddr_fuse + $idx;
847              
848 0         0 return await $self->lds8( $addr );
849 0     0 1 0 }
850              
851             role # hide from indexer
852             Device::AVR::UPDI::_NVMCtrl {
853              
854             use constant {
855 13         12590 NVMCTRL_CTRLA => 0,
856             NVMCTRL_STATUS => 2,
857             NVMCTRL_STATUS_FBUSY => (1<<0),
858 13     13   1790 };
  13         31  
859              
860 36     36   75 field $_updi :reader :param;
  36     36   121  
        36      
861 37     37   73 field $_partinfo :reader;
  37     37   124  
        37      
862              
863             ADJUST
864             {
865             $_partinfo = $_updi->partinfo;
866             }
867              
868             async method nvmctrl_command
869 11         31 {
870 11         29 my ( $cmd ) = @_;
871              
872 11         29 await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLA, $cmd );
873 11     11   28 }
        11      
        11      
874              
875             async method await_nvm_not_busy
876 11         42 {
877 11         19 my $timeout = 50;
878 11         53 while( --$timeout ) {
879 13 100       2901 last if not( NVMCTRL_STATUS_FBUSY & await $self->updi->lds8(
880             $self->partinfo->baseaddr_nvmctrl + NVMCTRL_STATUS, 1 ) );
881              
882 2         144 await Future::IO->sleep( 0.01 );
883             }
884 11     11   28 }
        11      
        11      
885             }
886              
887             class # hide from indexer
888             Device::AVR::UPDI::_NVMCtrlv0 :does(Device::AVR::UPDI::_NVMCtrl) {
889              
890 13     13   1556 use Carp;
  13         34  
  13         1312  
891              
892             use constant {
893             # Command values
894 13         22190 NVMCTRL_CMD_WP => 1,
895             NVMCTRL_CMD_ER => 2,
896             NVMCTRL_CMD_ERWP => 3,
897             NVMCTRL_CMD_PBC => 4,
898             NVMCTRL_CMD_CHER => 5,
899             NVMCTRL_CMD_EEER => 6,
900             NVMCTRL_CMD_WFU => 7,
901             NVMCTRL_CTRLB => 1,
902             NVMCTRL_DATA => 6,
903             NVMCTRL_ADDR => 8,
904 13     13   96 };
  13         31  
905              
906             async method read_flash_page
907 1         2 {
908 1         3 my ( $addr, $len ) = @_;
909 1         4 return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len );
910 1     1   2 }
911              
912             async method read_eeprom_page
913 1         2 {
914 1         2 my ( $addr, $len ) = @_;
915 1         3 return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len );
916 1     1   3 }
917              
918             async method _write_page
919 2         5 {
920 2         7 my ( $addr, $data, $wordsize, $cmd ) = @_;
921              
922 2         6 my $updi = $self->updi;
923              
924             # clear page buffer
925 2         8 await $self->nvmctrl_command( NVMCTRL_CMD_PBC );
926 2         432 await $self->await_nvm_not_busy;
927              
928             # Disable response sig for speed
929 2         292 await $updi->set_rsd( 1 );
930              
931 2 100       442 if( $wordsize == 8 ) {
    50          
932 1         5 await $updi->st8( $addr, $data );
933             }
934             elsif( $wordsize == 16 ) {
935 1         5 await $updi->st16( $addr, $data );
936             }
937             else {
938 0         0 croak "Invalid word size";
939             }
940              
941             # Re-enable response sig again
942 2         363 await $updi->set_rsd( 0 );
943              
944 2         454 await $self->nvmctrl_command( $cmd );
945 2         421 await $self->await_nvm_not_busy;
946 2     2   8 }
947              
948             async method write_flash_page
949 1         2 {
950 1         3 my ( $addr, $data ) = @_;
951 1         3 await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_WP );
952 1     1   3 }
953              
954             async method write_eeprom_page
955 1         4 {
956 1         3 my ( $addr, $data ) = @_;
957 1         6 await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_ERWP );
958 1     1   3 }
959              
960             async method write_fuse
961 1         3 {
962 1         3 my ( $idx, $value ) = @_;
963              
964 1         4 my $updi = $self->updi;
965              
966 1         5 my $addr = $self->partinfo->baseaddr_fuse + $idx;
967              
968 1         3 my $baseaddr = $self->partinfo->baseaddr_nvmctrl;
969              
970             # Oddly, this works but an attempt at STS16 does not. Unsure why
971 1         6 await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR , $addr & 0xFF );
972 1         165 await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR+1, $addr >> 8 );
973              
974 1         153 await $updi->sts8 ( $baseaddr + NVMCTRL_DATA, $value );
975              
976 1         151 await $self->nvmctrl_command( NVMCTRL_CMD_WFU );
977              
978 1         218 await $self->await_nvm_not_busy;
979 1     1   2 }
980             }
981              
982             class # hide from indexer
983             Device::AVR::UPDI::_NVMCtrlv2 :does(Device::AVR::UPDI::_NVMCtrl) {
984              
985 13     13   1629 use Carp;
  13         35  
  13         930  
986              
987             use constant {
988             # Command values
989 13         23351 NVMCTRL_CMD_NOCMD => 0x00,
990             NVMCTRL_CMD_FLWR => 0x02,
991             NVMCTRL_CMD_EEERWR => 0x13,
992              
993             NVMCTRL_CTRLB => 1,
994 13     13   98 };
  13         43  
995              
996             async method _set_flmap
997 2         4 {
998 2         5 my ( $bank ) = @_;
999              
1000 2         5 await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLB, $bank << 4 );
1001 2     2   3 }
1002              
1003             async method read_flash_page
1004 1         2 {
1005 1         3 my ( $addr, $len ) = @_;
1006              
1007 1         4 await $self->_set_flmap( $addr >> 15 );
1008 1         224 $addr &= 0x7FFF;
1009              
1010 1         9 return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len );
1011 1     1   3 }
1012              
1013             async method read_eeprom_page
1014 1         3 {
1015 1         3 my ( $addr, $len ) = @_;
1016 1         4 return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len );
1017 1     1   2 }
1018              
1019             async method _write_page
1020 3         7 {
1021 3         11 my ( $addr, $data, $wordsize, $cmd ) = @_;
1022              
1023 3         9 my $updi = $self->updi;
1024              
1025             # set page write mode
1026 3         15 await $self->nvmctrl_command( $cmd );
1027              
1028             # Disable response sig for speed on long data
1029             # (no point on single-byte fuses)
1030 3 100       777 await $updi->set_rsd( 1 ) if length $data > 1;
1031              
1032 3 100       483 if( $wordsize == 8 ) {
    50          
1033 2         16 await $updi->st8( $addr, $data );
1034             }
1035             elsif( $wordsize == 16 ) {
1036 1         5 await $updi->st16( $addr, $data );
1037             }
1038             else {
1039 0         0 croak "Invalid word size";
1040             }
1041              
1042             # Re-enable response sig again
1043 3         553 await $updi->set_rsd( 0 );
1044              
1045 3         546 await $self->await_nvm_not_busy;
1046              
1047             # clear command
1048 3         490 await $self->nvmctrl_command( NVMCTRL_CMD_NOCMD );
1049 3         704 await $self->await_nvm_not_busy;
1050 3     3   8 }
1051              
1052             async method write_flash_page
1053 1         3 {
1054 1         3 my ( $addr, $data ) = @_;
1055              
1056 1         3 await $self->_set_flmap( $addr >> 15 );
1057 1         212 $addr &= 0x7FFF;
1058              
1059 1         4 await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_FLWR );
1060 1     1   2 }
1061              
1062             async method write_eeprom_page
1063 1         3 {
1064 1         4 my ( $addr, $data ) = @_;
1065              
1066 1         4 await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_EEERWR );
1067 1     1   2 }
1068              
1069             async method write_fuse
1070 1         3 {
1071 1         3 my ( $idx, $value ) = @_;
1072              
1073             # Fuses are written by pretending it's EEPROM
1074 1         9 my $data = pack "C", $value;
1075              
1076 1         4 await $self->_write_page( $self->partinfo->baseaddr_fuse + $idx, $data, 8, NVMCTRL_CMD_EEERWR );
1077 1     1   2 }
1078             }
1079              
1080             =head1 SEE ALSO
1081              
1082             =over 2
1083              
1084             =item *
1085              
1086             "AVR UPDI Programming Cable"
1087              
1088             An adapter cable to flash firmware onto an AVR microcontroller chip via UPDI,
1089             compatible with this module.
1090              
1091             L
1092              
1093             =back
1094              
1095             =cut
1096              
1097             =head1 AUTHOR
1098              
1099             Paul Evans
1100              
1101             =cut
1102              
1103             0x55AA;
1104              
1105             __DATA__