File Coverage

blib/lib/Device/AVR/UPDI.pm
Criterion Covered Total %
statement 462 494 93.5
branch 49 70 70.0
condition 1 5 20.0
subroutine 79 85 92.9
pod 16 27 59.2
total 607 681 89.1


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   3637252 use v5.26;
  13         156  
7 13     13   82 use warnings;
  13         29  
  13         477  
8 13     13   8144 use Object::Pad 0.800 ':experimental(adjust_params)';
  13         147669  
  13         1169  
9              
10             package Device::AVR::UPDI 0.14;
11             class Device::AVR::UPDI :strict(params);
12              
13 13     13   5962 use Carp;
  13         32  
  13         756  
14              
15 13     13   7208 use Future::AsyncAwait;
  13         33068  
  13         72  
16 13     13   1415 use Future::IO 0.03; # ->sysread_exactly
  13         13546  
  13         479  
17              
18 13     13   7291 use File::ShareDir qw( module_dir );
  13         372116  
  13         903  
19 13     13   6174 use YAML ();
  13         101186  
  13         824  
20              
21             my $SHAREDIR = module_dir( __PACKAGE__ );
22              
23 13     13   7027 use Object::Pad::ClassAttr::Struct 0.04;
  13         15766  
  13         69  
24              
25 13   50 13   876 use constant DEBUG => $ENV{UPDI_DEBUG} // 0;
  13         35  
  13         1756  
26              
27             class Device::AVR::UPDI::_PartInfo :Struct {
28 1     1   4 field $name;
  1         7  
29              
30 1     1   4 field $signature;
  1         5  
31 27     27   56 field $baseaddr_nvmctrl;
  27         140  
32 2     2   5 field $baseaddr_fuse;
  2         12  
33 1     1   3 field $baseaddr_sigrow;
  1         5  
34              
35 4     4   13 field $baseaddr_flash;
  4         34  
36 0     0   0 field $pagesize_flash;
  0         0  
37 1     1   5 field $size_flash;
  1         5  
38              
39 4     4   11 field $baseaddr_eeprom;
  4         23  
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         11  
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 20 field $_partinfo :reader;
  7         76  
143              
144 9     9   63 field $_nvm_version :writer(_set_nvm_version);
  9         205  
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 0         0 method fuseinfo ()
  0         0  
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         107582 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   34601 };
  13         38  
293              
294 1         3 async method _break ()
  1         3  
295 1         4 {
296 1         11 my $was_baud = $_fh->getobaud;
297              
298             # Writing a 0 at 300baud is sufficient to look like a BREAK
299 1         1100 $_fh->setbaud( 300 );
300 1         1114 $_fh->print( "\x00" );
301              
302 1         1145 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         3055 $_fh->setbaud( $was_baud );
309 1     1   3 }
310              
311 110         160 async method _op_writeread ( $write, $readlen )
  110         188  
  110         151  
  110         148  
312 110         242 {
313 110         145 printf STDERR "WR: => %v02X\n", $write if DEBUG > 1;
314 110         444 await Future::IO->syswrite_exactly( $_fh, $write );
315              
316 110         277204 my $buf = "";
317 110         379 my $len = length( $write ) + $readlen;
318              
319 110         305 while( length $buf < $len ) {
320 110 50       307 my $what = ( length $buf >= length $write ) ?
321             "chip response - is the chip present?" :
322             "echo of command - is this a UPDI programmer?";
323              
324 110         447 $buf .= await Future->wait_any(
325             Future::IO->sysread( $_fh, $len - length $buf ),
326             Future::IO->sleep( 0.1 )
327             ->then_fail( "Timed out waiting for $what\n" )
328             );
329              
330 110         335851 my $got = substr( $buf, 0, length $write );
331 110         283 my $exp = substr( $write, 0, length $buf );
332              
333 110         166 printf STDERR "RD: <= %v02X\n", $buf if DEBUG > 1;
334              
335 110 50       461 die "Received different bytes while waiting to receive echo of command - is this a UPDI programmer?\n"
336             if $got ne $exp;
337             }
338              
339 110         576 return substr( $buf, length( $write ) );
340 110     110   192 }
341              
342 29         41 method _pack_op_addr ( $op, $addr )
  29         43  
  29         51  
  29         45  
343 29     29   62 {
344 29 100       263 return $_nvm_version >= 2
345             ? pack( "C S> 16 )
346             : pack( "C S<", $op|OP_ADDR16<<2, $addr );
347             }
348              
349 45         66 async method _op_write_expecting_ack ( $op, $write )
  45         115  
  45         70  
  45         63  
350 45         120 {
351 45 100       109 if( $_reg_ctrla & CTRLA_RSD ) {
352             # No ACK expected
353 6         21 await $self->_op_writeread( $write, 0 );
354             }
355             else {
356 39         134 my $ack = await $self->_op_writeread( $write, 1 );
357 39 50       3315 $ack eq "\x40" or croak "Expected ACK to $op";
358             }
359 45     45   79 }
360              
361 10         18 async method stptr ( $addr )
  10         19  
  10         30  
362 10         23 {
363 10 100       98 my $cmd = $_nvm_version >= 2
364             ? pack( "C S> 16 )
365             : pack( "C S<", OP_ST|OP_PTRREG|OP_ADDR16, $addr );
366              
367 10         53 await $self->_op_write_expecting_ack( "ST PTR" => SYNC . $cmd );
368 10     10 0 21 }
369              
370 13         25 async method lds8 ( $addr )
  13         24  
  13         23  
371 13         33 {
372 13         45 my $ret = unpack "C", await
373             $self->_op_writeread( SYNC . $self->_pack_op_addr( OP_LDS, $addr ), 1 );
374              
375 13         1069 printf STDERR ">> LDS8[%04X] -> %02X\n", $addr, $ret if DEBUG;
376 13         52 return $ret;
377 13     13 0 28 }
378              
379 16         35 async method sts8 ( $addr, $val )
  16         30  
  16         31  
  16         26  
380 16         48 {
381 16         25 printf STDERR ">> STS8[%04X] = %02X\n", $addr, $val if DEBUG;
382              
383 16         72 await $self->_op_write_expecting_ack( STS8 =>
384             SYNC . $self->_pack_op_addr( OP_STS, $addr ) );
385              
386 16         1276 await $self->_op_write_expecting_ack( "STS8 DATA" =>
387             pack( "C", $val ) );
388 16     16 0 28 }
389              
390 5         11 async method ld ( $addr, $len )
  5         9  
  5         11  
  5         9  
391 5         12 {
392 5         28 await $self->stptr( $addr );
393              
394 5         842 my $ret = "";
395              
396 5         27 while( $len ) {
397 5         11 my $chunklen = $len;
398             # REPEAT can only do at most 255 repeats
399 5 50       25 $chunklen = 256 if $chunklen > 256;
400              
401             await
402 5 50       56 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $chunklen - 1 ), 0 ) if $chunklen > 1;
403 5         395 $ret .= await
404             $self->_op_writeread( SYNC . pack( "C", OP_LD|OP_PTRINC|OP_DATA8 ), $chunklen );
405              
406 5         403 $len -= $chunklen;
407             }
408              
409 5         13 printf STDERR ">> LD[%04X] -> %v02X\n", $addr, $ret if DEBUG;
410 5         20 return $ret;
411 5     5 0 12 }
412              
413 3         6 async method st8 ( $addr, $data )
  3         8  
  3         7  
  3         5  
414 3         11 {
415 3         8 printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG;
416              
417 3         8 my $len = length( $data );
418              
419 3         15 await $self->stptr( $addr );
420              
421             await
422 3 100       632 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1;
423              
424             await
425 3         170 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 );
426              
427             # If we're in RSD mode we might as well just write all the data in one go
428 3 100       239 if( $_reg_ctrla & CTRLA_RSD ) {
429 2         10 await $self->_op_writeread( $data, 0 )
430             }
431             else {
432 1         13 foreach my $byte ( split //, $data ) {
433 1         4 await $self->_op_write_expecting_ack( "STR data" => $byte );
434             }
435             }
436 3     3 0 6 }
437              
438 2         6 async method st16 ( $addr, $data )
  2         4  
  2         4  
  2         4  
439 2         9 {
440 2         5 printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG;
441              
442             # Count in 16bit words
443 2         10 my $len = int( length( $data ) / 2 );
444              
445 2         10 await $self->stptr( $addr );
446              
447             await
448 2 50       477 $self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1;
449              
450             await
451 2         174 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA16 ), 0 );
452              
453             # If we're in RSD mode we might as well just write all the data in one go
454 2 50       177 if( $_reg_ctrla & CTRLA_RSD ) {
455 2         13 await $self->_op_writeread( substr( $data, 0, $len*2 ), 0 );
456             }
457             else {
458 0         0 foreach my $word ( $data =~ m/.{2}/sg ) {
459 0         0 await $self->_op_write_expecting_ack( "STR data" => $word );
460             }
461             }
462              
463 2 50       179 if( length( $data ) % 2 ) {
464             # Final byte
465 2         10 my $byte = substr $data, 2 * $len, 1;
466             await
467 2         9 $self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 );
468              
469 2         496 await $self->_op_write_expecting_ack( "STR data" => $byte );
470             }
471 2     2 0 6 }
472              
473 7         12 async method ldcs ( $reg )
  7         14  
  7         9  
474 7         24 {
475 7         43 my $ret = unpack "C", await
476             $self->_op_writeread( SYNC . pack( "C", OP_LDCS | $reg ), 1 );
477              
478 7         574 printf STDERR ">> LDCS[%02X] -> %02X\n", $reg, $ret if DEBUG;
479 7         28 return $ret;
480 7     7 0 14 }
481              
482 16         36 async method stcs ( $reg, $value )
  16         27  
  16         25  
  16         26  
483 16         35 {
484 16         29 printf STDERR ">> STCS[%02X] = %02X\n", $reg, $value if DEBUG;
485              
486             await
487 16         153 $self->_op_writeread( SYNC . pack( "CC", OP_STCS | $reg, $value ), 0 );
488 16     16 0 33 }
489              
490 2         3 async method key ( $key )
  2         5  
  2         2  
491 2         7 {
492 2 50       10 length $key == 8 or
493             die "Expected 8 byte key\n";
494              
495 2         4 printf STDERR ">> KEY %v02X\n", $key if DEBUG;
496              
497             await
498 2         25 $self->_op_writeread( SYNC . pack( "C a*", OP_KEY, $key ), 0 );
499 2     2 0 4 }
500              
501 9         17 async method set_rsd ( $on )
  9         17  
  9         15  
502 9         48 {
503 9 100       39 my $val = $on ? $_reg_ctrla | CTRLA_RSD : $_reg_ctrla & ~CTRLA_RSD;
504              
505 9 100       68 await $self->stcs( REG_CTRLA, $_reg_ctrla = $val ) if $_reg_ctrla != $val;
506 9     9 0 27 }
507              
508             =head2 init_link
509              
510             await $updi->init_link;
511              
512             Initialise the UPDI link for proper communication.
513              
514             This method must be invoked after the object is constructed, before using any
515             of the other commands.
516              
517             =cut
518              
519 1         3 async method init_link ()
  1         1  
520 1         18 {
521             # Sleep 100msec before sending BREAK in case of attached UPDI 12V pulse hardware
522 1         8 await Future::IO->sleep( 0.1 );
523              
524 1         1564 await $self->_break;
525              
526             # We have to disable collision detection or else the chip won't respond
527             # properly
528 1         1306 await $self->stcs( REG_CTRLB, CTRLB_CCDETDIS );
529              
530             # Set CTRLA to known state also
531 1         158 await $self->stcs( REG_CTRLA, $_reg_ctrla = 0 );
532              
533             # Read the SIB so we can determine what kind of NVM controller is required
534 1         148 my $sib = await $self->read_sib;
535 1 50       87 $sib->{nvm_version} =~ m/^P:(\d)/ or croak "Unrecognised NVM_VERSION string: $sib->{nvm_version}";
536 1         7 $_nvm_version = $1;
537 1     1 1 9786 }
538              
539             =head2 read_updirev
540              
541             $rev = await $updi->read_updirev;
542              
543             Reads the C field of the C register.
544              
545             =cut
546              
547 1         2 async method read_updirev ()
  1         2  
548 1         4 {
549 1         5 return ( await $self->ldcs( REG_STATUSA ) ) >> 4;
550 1     1 1 2001 }
551              
552             =head2 read_asi_sys_status
553              
554             Reads the C register.
555              
556             =cut
557              
558 0         0 async method read_asi_sys_status ()
  0         0  
559 0         0 {
560 0         0 return await $self->ldcs( REG_ASI_SYS_STATUS );
561 0     0 1 0 }
562              
563             =head2 read_sib
564              
565             $sib = await $updi->read_sib;
566              
567             Reads the System Information Block.
568              
569             This is returned in a HASH reference, containing four keys:
570              
571             {
572             family => "tinyAVR",
573             nvm_version => "P:0",
574             ocd_version => "D:0",
575             dbg_osc_freq => 3,
576             }
577              
578             =cut
579              
580 2         5 async method read_sib ()
  2         3  
581 2         10 {
582 2         9 my $bytes = await
583             $self->_op_writeread( SYNC . pack( "C", OP_KEY_READSIB ), 16 );
584 2         151 printf STDERR ">> READSIB -> %v02X\n", $bytes if DEBUG;
585              
586 2         18 my ( $family, $nvm, $ocd, $dbgosc ) = unpack "A7 x A3 A3 x A1", $bytes;
587             return {
588 2         18 family => $family,
589             nvm_version => $nvm,
590             ocd_version => $ocd,
591             dbg_osc_freq => $dbgosc,
592             };
593 2     2 1 5900 }
594              
595             =head2 read_signature
596              
597             $signature = await $updi->read_signature;
598              
599             Reads the three signature bytes from the Signature Row of the device. This is
600             returned as a plain byte string of length 3.
601              
602             =cut
603              
604 1         3 async method read_signature ()
  1         3  
605 1         5 {
606             # The ATtiny814 datasheet says
607             # All Atmel microcontrollers have a three-byte signature code which
608             # identifies the device. This code can be read in both serial and parallel
609             # mode, also when the device is locked. The three bytes reside in a
610             # separate address space.
611             # So far no attempt at reading signature over UPDI from a locked device has
612             # been successful. :(
613              
614 1         4 return await $self->ld( $_partinfo->baseaddr_sigrow, 3 );
615 1     1 1 5512 }
616              
617             =head2 request_reset
618              
619             await $updi->request_reset( $reset );
620              
621             Sets or clears the system reset request. Typically used to issue a system
622             reset by momentarilly toggling the request on and off again:
623              
624             await $updi->request_reset( 1 );
625             await $updi->request_reset( 0 );
626              
627             =cut
628              
629 6         13 async method request_reset ( $reset )
  6         13  
  6         9  
630 6         32 {
631 6         32 await $self->stcs( REG_ASI_RESET_REQ, $reset ? ASI_RESET_REQ_SIGNATURE : 0 );
632 6     6 1 7599 }
633              
634             =head2 erase_chip
635              
636             await $updi->erase_chip;
637              
638             Requests a full chip erase, waiting until the erase is complete.
639              
640             After this, the chip will be unlocked.
641              
642             Takes an optional named argument:
643              
644             =over 4
645              
646             =item no_reset => BOOL
647              
648             If true, does not issue a system reset request after loading the key. This
649             allows you to load multiple keys at once before sending the reset, which
650             may be required e.g. to recover from a bad C fuse setting.
651              
652             await $updi->erase_chip( no_reset => 1 );
653             await $updi->enable_nvmprog;
654              
655             =back
656              
657             =cut
658              
659 1         2 async method erase_chip ( %opts )
  1         2  
  1         2  
660 1         5 {
661 1         4 await $self->key( KEY_CHIPERASE );
662              
663 1 50       182 die "Failed to set CHIPERASE key\n" unless ASI_KEY_CHIPERASE & await $self->ldcs( REG_ASI_KEY_STATUS );
664              
665 1 50       75 return if $opts{no_reset};
666              
667 1         7 await $self->request_reset( 1 );
668 1         220 await $self->request_reset( 0 );
669              
670 1         213 my $timeout = 50;
671 1         6 while( --$timeout ) {
672 2 100       1407 last if not ASI_SYS_STATUS_LOCKSTATUS & await $self->ldcs( REG_ASI_SYS_STATUS );
673              
674 1         75 await Future::IO->sleep( 0.05 );
675             }
676 1 50       77 die "Failed to unlock chip\n" if !$timeout;
677 1     1 1 2695 }
678              
679             =head2 enable_nvmprog
680              
681             await $updi->enable_nvmprog;
682              
683             Requests the chip to enter NVM programming mode.
684              
685             =cut
686              
687 1         4 async method enable_nvmprog ()
  1         2  
688 1         3 {
689 1         5 await $self->key( KEY_NVMPROG );
690              
691 1 50       183 die "Failed to set NVMPROG key\n" unless ASI_KEY_NVMPROG & await $self->ldcs( REG_ASI_KEY_STATUS );
692              
693 1         76 await $self->request_reset( 1 );
694 1         223 await $self->request_reset( 0 );
695              
696 1         216 my $timeout = 50;
697 1         6 while( --$timeout ) {
698 2 100       1367 last if ASI_SYS_STATUS_NVMPROG & await $self->ldcs( REG_ASI_SYS_STATUS );
699              
700 1         93 await Future::IO->sleep( 0.05 );
701             }
702 1 50       83 die "Timed out waiting for NVMPROG key to be accepted\n" if !$timeout;
703 1     1 1 2843 }
704              
705             field $_nvmctrl;
706              
707 10         17 method nvmctrl ()
  10         19  
708 10     10 0 34 {
709 10 100       53 return $_nvmctrl if defined $_nvmctrl;
710              
711 6 50       24 defined $_nvm_version or
712             croak "Must ->init_link before calling ->nvmctrl";
713              
714             # ATtiny and ATmega chips claim "P:0"
715 6 100       68 return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv0->new( updi => $self )
716             if $_nvm_version == 0;
717              
718             # AVR Dx chips claim "P:2"
719 3 50       50 return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv2->new( updi => $self )
720             if $_nvm_version == 2;
721              
722 0         0 croak "Unrecognised NVM version $_nvm_version";
723             }
724              
725             =head2 read_flash_page
726              
727             $data = await $updi->read_flash_page( $addr, $len );
728              
729             Reads a single flash page and returns the data. C<$addr> is within the flash
730             address space.
731              
732             =cut
733              
734 2         5 async method read_flash_page ( $addr, $len )
  2         6  
  2         3  
  2         5  
735 2         8 {
736 2         11 return await $self->nvmctrl->read_flash_page( $addr, $len );
737 2     2 1 5154 }
738              
739             =head2 write_flash_page
740              
741             await $updi->write_flash_page( $addr, $data );
742              
743             Writes a single flash page into the NVM controller in 16-bit word transfers.
744             C<$addr> is within the flash address space.
745              
746             =cut
747              
748 2         7 async method write_flash_page ( $addr, $data )
  2         6  
  2         5  
  2         4  
749 2         11 {
750 2         26 await $self->nvmctrl->write_flash_page( $addr, $data );
751 2     2 1 25606 }
752              
753             =head2 read_eeprom_page
754              
755             $data = await $updi->read_eeprom_page( $addr, $len );
756              
757             Reads a single EEPROM page and returns the data. C<$addr> is within the EEPROM
758             address space.
759              
760             =cut
761              
762 2         4 async method read_eeprom_page ( $addr, $len )
  2         5  
  2         6  
  2         3  
763 2         9 {
764 2         10 return await $self->nvmctrl->read_eeprom_page( $addr, $len );
765 2     2 1 4843 }
766              
767             =head2 write_eeprom_page
768              
769             Similar to L but issues a combined erase-and-write
770             command and C<$addr> is within the EEPROM address space.
771              
772             =cut
773              
774 2         4 async method write_eeprom_page ( $addr, $data )
  2         4  
  2         4  
  2         7  
775 2         10 {
776 2         31 await $self->nvmctrl->write_eeprom_page( $addr, $data );
777 2     2 1 22190 }
778              
779             =head2 write_fuse
780              
781             await $updi->write_fuse( $idx, $value );
782              
783             Writes a fuse value. C<$idx> is the index of the fuse within the FUSES memory
784             segment, from 0 onwards.
785              
786             =cut
787              
788 2         4 async method write_fuse ( $idx, $value )
  2         6  
  2         3  
  2         3  
789 2         8 {
790 2         11 await $self->nvmctrl->write_fuse( $idx, $value );
791 2     2 1 6588 }
792              
793             =head2 read_fuse
794              
795             $value = await $updi->read_fuse( $idx );
796              
797             Reads a fuse value. C<$idx> is the index of the fuse within the FUSES memory
798             segment, from 0 onwards.
799              
800             =cut
801              
802 0         0 async method read_fuse ( $idx )
  0         0  
  0         0  
803 0         0 {
804 0         0 my $addr = $_partinfo->baseaddr_fuse + $idx;
805              
806 0         0 return await $self->lds8( $addr );
807 0     0 1 0 }
808              
809             role # hide from indexer
810             Device::AVR::UPDI::_NVMCtrl {
811              
812             use constant {
813 13         14013 NVMCTRL_CTRLA => 0,
814             NVMCTRL_STATUS => 2,
815             NVMCTRL_STATUS_FBUSY => (1<<0),
816 13     13   1916 };
  13         37  
817              
818 36     36   114 field $_updi :reader :param;
  36     36   135  
        36      
819 37     37   70 field $_partinfo :reader;
  37     37   148  
        37      
820              
821             ADJUST
822             {
823             $_partinfo = $_updi->partinfo;
824             }
825              
826 11         23 async method nvmctrl_command ( $cmd )
  11         22  
  11         17  
827 11         32 {
828 11         35 await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLA, $cmd );
829 11     11   27 }
        11      
        11      
830              
831 11         21 async method await_nvm_not_busy ()
  11         19  
832 11         43 {
833 11         23 my $timeout = 50;
834 11         58 while( --$timeout ) {
835 13 100       3021 last if not( NVMCTRL_STATUS_FBUSY & await $self->updi->lds8(
836             $self->partinfo->baseaddr_nvmctrl + NVMCTRL_STATUS ) );
837              
838 2         508 await Future::IO->sleep( 0.01 );
839             }
840 11     11   38 }
        11      
        11      
841             }
842              
843             class # hide from indexer
844             Device::AVR::UPDI::_NVMCtrlv0 :does(Device::AVR::UPDI::_NVMCtrl) {
845              
846 13     13   1643 use Carp;
  13         37  
  13         1129  
847              
848             use constant {
849             # Command values
850 13         21881 NVMCTRL_CMD_WP => 1,
851             NVMCTRL_CMD_ER => 2,
852             NVMCTRL_CMD_ERWP => 3,
853             NVMCTRL_CMD_PBC => 4,
854             NVMCTRL_CMD_CHER => 5,
855             NVMCTRL_CMD_EEER => 6,
856             NVMCTRL_CMD_WFU => 7,
857             NVMCTRL_CTRLB => 1,
858             NVMCTRL_DATA => 6,
859             NVMCTRL_ADDR => 8,
860 13     13   95 };
  13         64  
861              
862 1         2 async method read_flash_page ( $addr, $len )
  1         2  
  1         1  
  1         3  
863 1         3 {
864 1         5 return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len );
865 1     1   2 }
866              
867 1         2 async method read_eeprom_page ( $addr, $len )
  1         2  
  1         1  
  1         2  
868 1         3 {
869 1         5 return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len );
870 1     1   2 }
871              
872 2         6 async method _write_page ( $addr, $data, $wordsize, $cmd )
  2         4  
  2         4  
  2         4  
  2         5  
  2         6  
873 2         6 {
874 2         9 my $updi = $self->updi;
875              
876             # clear page buffer
877 2         10 await $self->nvmctrl_command( NVMCTRL_CMD_PBC );
878 2         452 await $self->await_nvm_not_busy;
879              
880             # Disable response sig for speed
881 2         351 await $updi->set_rsd( 1 );
882              
883 2 100       445 if( $wordsize == 8 ) {
    50          
884 1         6 await $updi->st8( $addr, $data );
885             }
886             elsif( $wordsize == 16 ) {
887 1         7 await $updi->st16( $addr, $data );
888             }
889             else {
890 0         0 croak "Invalid word size";
891             }
892              
893             # Re-enable response sig again
894 2         409 await $updi->set_rsd( 0 );
895              
896 2         437 await $self->nvmctrl_command( $cmd );
897 2         433 await $self->await_nvm_not_busy;
898 2     2   5 }
899              
900 1         2 async method write_flash_page ( $addr, $data )
  1         3  
  1         2  
  1         2  
901 1         3 {
902 1         7 await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_WP );
903 1     1   3 }
904              
905 1         3 async method write_eeprom_page ( $addr, $data )
  1         3  
  1         2  
  1         2  
906 1         2 {
907 1         4 await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_ERWP );
908 1     1   2 }
909              
910 1         2 async method write_fuse ( $idx, $value )
  1         2  
  1         1  
  1         2  
911 1         2 {
912 1         3 my $updi = $self->updi;
913              
914 1         4 my $addr = $self->partinfo->baseaddr_fuse + $idx;
915              
916 1         3 my $baseaddr = $self->partinfo->baseaddr_nvmctrl;
917              
918             # Oddly, this works but an attempt at STS16 does not. Unsure why
919 1         10 await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR , $addr & 0xFF );
920 1         150 await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR+1, $addr >> 8 );
921              
922 1         196 await $updi->sts8 ( $baseaddr + NVMCTRL_DATA, $value );
923              
924 1         160 await $self->nvmctrl_command( NVMCTRL_CMD_WFU );
925              
926 1         223 await $self->await_nvm_not_busy;
927 1     1   2 }
928             }
929              
930             class # hide from indexer
931             Device::AVR::UPDI::_NVMCtrlv2 :does(Device::AVR::UPDI::_NVMCtrl) {
932              
933 13     13   1680 use Carp;
  13         70  
  13         1295  
934              
935             use constant {
936             # Command values
937 13         24024 NVMCTRL_CMD_NOCMD => 0x00,
938             NVMCTRL_CMD_FLWR => 0x02,
939             NVMCTRL_CMD_EEERWR => 0x13,
940              
941             NVMCTRL_CTRLB => 1,
942 13     13   131 };
  13         49  
943              
944 2         3 async method _set_flmap ( $bank )
  2         4  
  2         4  
945 2         4 {
946 2         7 await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLB, $bank << 4 );
947 2     2   4 }
948              
949 1         2 async method read_flash_page ( $addr, $len )
  1         2  
  1         1  
  1         2  
950 1         2 {
951 1         4 await $self->_set_flmap( $addr >> 15 );
952 1         223 $addr &= 0x7FFF;
953              
954 1         9 return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len );
955 1     1   3 }
956              
957 1         2 async method read_eeprom_page ( $addr, $len )
  1         2  
  1         2  
  1         1  
958 1         3 {
959 1         4 return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len );
960 1     1   2 }
961              
962 3         8 async method _write_page ( $addr, $data, $wordsize, $cmd )
  3         7  
  3         8  
  3         8  
  3         5  
  3         6  
963 3         20 {
964 3         22 my $updi = $self->updi;
965              
966             # set page write mode
967 3         19 await $self->nvmctrl_command( $cmd );
968              
969             # Disable response sig for speed on long data
970             # (no point on single-byte fuses)
971 3 100       733 await $updi->set_rsd( 1 ) if length $data > 1;
972              
973 3 100       448 if( $wordsize == 8 ) {
    50          
974 2         15 await $updi->st8( $addr, $data );
975             }
976             elsif( $wordsize == 16 ) {
977 1         5 await $updi->st16( $addr, $data );
978             }
979             else {
980 0         0 croak "Invalid word size";
981             }
982              
983             # Re-enable response sig again
984 3         584 await $updi->set_rsd( 0 );
985              
986 3         493 await $self->await_nvm_not_busy;
987              
988             # clear command
989 3         446 await $self->nvmctrl_command( NVMCTRL_CMD_NOCMD );
990 3         656 await $self->await_nvm_not_busy;
991 3     3   9 }
992              
993 1         2 async method write_flash_page ( $addr, $data )
  1         3  
  1         2  
  1         2  
994 1         3 {
995 1         6 await $self->_set_flmap( $addr >> 15 );
996 1         210 $addr &= 0x7FFF;
997              
998 1         5 await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_FLWR );
999 1     1   3 }
1000              
1001 1         1 async method write_eeprom_page ( $addr, $data )
  1         2  
  1         2  
  1         2  
1002 1         3 {
1003 1         4 await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_EEERWR );
1004 1     1   2 }
1005              
1006 1         2 async method write_fuse ( $idx, $value )
  1         2  
  1         2  
  1         2  
1007 1         4 {
1008             # Fuses are written by pretending it's EEPROM
1009 1         7 my $data = pack "C", $value;
1010              
1011 1         5 await $self->_write_page( $self->partinfo->baseaddr_fuse + $idx, $data, 8, NVMCTRL_CMD_EEERWR );
1012 1     1   2 }
1013             }
1014              
1015             =head1 SEE ALSO
1016              
1017             =over 2
1018              
1019             =item *
1020              
1021             "AVR UPDI Programming Cable"
1022              
1023             An adapter cable to flash firmware onto an AVR microcontroller chip via UPDI,
1024             compatible with this module.
1025              
1026             L
1027              
1028             =back
1029              
1030             =cut
1031              
1032             =head1 AUTHOR
1033              
1034             Paul Evans
1035              
1036             =cut
1037              
1038             0x55AA;
1039              
1040             __DATA__