File Coverage

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