| 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__ |