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