File Coverage

blib/lib/Device/AVR/UPDI.pm
Criterion Covered Total %
statement 461 493 93.5
branch 50 72 69.4
condition 1 5 20.0
subroutine 70 76 92.1
pod 16 27 59.2
total 598 673 88.8


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