File Coverage

blib/lib/Device/Chip/SDCard.pm
Criterion Covered Total %
statement 128 143 89.5
branch 13 22 59.0
condition n/a
subroutine 21 23 91.3
pod 5 7 71.4
total 167 195 85.6


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, 2016-2020 -- leonerd@leonerd.org.uk
5              
6 5     5   448062 use v5.26;
  5         54  
7 5     5   3051 use Object::Pad 0.19;
  5         35142  
  5         26  
8              
9             package Device::Chip::SDCard 0.03;
10             class Device::Chip::SDCard
11 1     1   594 extends Device::Chip;
  1         14989  
  1         67  
12              
13 5     5   2203 use Future::AsyncAwait;
  5         1008  
  5         31  
14              
15 5     5   3076 use Data::Bitfield qw( bitfield boolfield );
  5         12844  
  5         406  
16              
17 5     5   43 use constant PROTOCOL => "SPI";
  5         11  
  5         4380  
18              
19             =head1 NAME
20              
21             C - chip driver for F and F cards
22              
23             =head1 SYNOPSIS
24              
25             use Device::Chip::SDCard;
26             use Future::AsyncAwait;
27              
28             my $card = Device::Chip::SDCard->new;
29              
30             await $card->mount( Device::Chip::Adapter::...->new );
31              
32             await $card->initialise;
33              
34             my $bytes = await $card->read_block( 0 );
35              
36             print "Read block zero:\n";
37             printf "%v02X\n", $bytes;
38              
39             =head1 DESCRIPTION
40              
41             This L subclass provides specific communication to an F or
42             F storage card attached via an SPI adapter.
43              
44             At present it only supports MMC and SDSC ("standard capacity") cards, not SDHC
45             or SDXC.
46              
47             =cut
48              
49             method SPI_options
50 4     4 0 2773 {
51             return (
52 4         79 mode => 0,
53             max_bitrate => 1E6,
54             );
55             }
56              
57             =head1 METHODS
58              
59             The following methods documented in an C expression return L
60             instances.
61              
62             =cut
63              
64 5         11 async method send_command ( $cmd, $arg = 0, $readlen = 0 )
  5         8  
  5         11  
  5         7  
  5         6  
65 5         18 {
66 5         10 my $crcstop = 0x95;
67              
68             # TODO: until we can perform dynamic transactions with D:C:A we'll have to
69             # do this by presuming the maximum amount of time for the card to respond
70             # (8 words) and look for the response in what's returned
71              
72 5         15 my ( $resp ) = await $self->protocol->readwrite(
73             pack "C N C a*", 0x40 | $cmd, $arg, $crcstop, "\xFF" x ( 8 + $readlen ),
74             );
75              
76             # Trim to the start of the expected result
77 5         14007 substr $resp, 0, 7, "";
78              
79             # Look for a byte with top bit clear
80 5         21 while( length $resp ) {
81 9         22 my $ret = unpack( "C", $resp );
82 9 100       67 return ( $ret, unpack "x a$readlen", $resp ) if !( $ret & 0x80 );
83              
84 4         12 substr $resp, 0, 1, "";
85             }
86              
87 0         0 die sprintf "Timed out waiting for response to command %02X", $cmd;
88 5     5 0 395 }
89              
90 4         9 async method _recv_data_block ( $buf, $len )
  4         10  
  4         7  
  4         8  
91 4         19 {
92             # Wait for a token
93 4         8 while(1) {
94 7         3328 $buf =~ s/^\xFF+//;
95              
96 7 100       31 last if $buf =~ s/^\xFE//;
97              
98 3         12 $buf .= await $self->protocol->readwrite_no_ss( "\xFF" x 16 );
99             }
100              
101             # Now want the data + CRC
102 4 50       18 if( length $buf < $len + 2 ) {
103 4         26 $buf .= await $self->protocol->readwrite_no_ss( "\xFF" x ( $len + 2 - length $buf ) );
104             }
105              
106             # TODO: might want to verify the CRC?
107              
108 4         4510 return substr $buf, 0, $len;
109 4     4   10 }
110              
111             # Commands
112             use constant {
113 5         688 CMD_GO_IDLE_STATE => 0,
114             CMD_SEND_OP_COND => 1,
115             CMD_SEND_CSD => 9,
116             CMD_SET_BLOCKLEN => 16,
117             CMD_READ_SINGLE_BLOCK => 17,
118             CMD_READ_OCR => 58,
119 5     5   45 };
  5         11  
120              
121             # Response first byte bitflags
122             use constant {
123 5         16008 RESP_PARAM_ERROR => 1<<6,
124             RESP_ADDR_ERROR => 1<<5,
125             RESP_ERASESEQ_ERROR => 1<<4,
126             RESP_CRC_ERROR => 1<<3,
127             RESP_ILLEGAL_CMD => 1<<2,
128             RESP_ERASE_RESET => 1<<1,
129             RESP_IDLE => 1<<0,
130 5     5   35 };
  5         10  
131              
132             =head2 initialise
133              
134             await $card->initialise;
135              
136             Checks that an SD card is present, switches it into SPI mode and waits for its
137             initialisation process to complete.
138              
139             =cut
140              
141 1         2 async method initialise ()
  1         2  
142 1         4 {
143             # Initialise first by switching the card into SPI mode
144 1         3 await $self->protocol->write( "\xFF" x 10 );
145              
146 1         9095 my $resp = await $self->send_command( CMD_GO_IDLE_STATE );
147 1 50       37 $resp == 1 or die "Expected 01 response; got $resp";
148              
149 1         4 foreach my $attempt ( 1 .. 200 ) {
150             # TODO: Consider using SEND_IF_COND and doing SDHC initialisation
151 2         6 $resp = await $self->send_command( CMD_SEND_OP_COND );
152 2 100       65 last unless $resp & RESP_IDLE;
153             }
154              
155 1 50       4 $resp & RESP_IDLE and die "Timed out waiting for card to leave IDLE mode";
156              
157 1         5 $resp = await $self->send_command( CMD_SET_BLOCKLEN, 512 );
158 1 50       34 $resp == 0 or die "Expected 00 response; got $resp";
159              
160 1         4 return;
161 1     1 1 476 }
162              
163             =head2 size
164              
165             $n_bytes = await $card->size;
166              
167             Returns the size of the media card in bytes.
168              
169             =cut
170              
171 0         0 async method size ()
  0         0  
172 0         0 {
173 0         0 my $csd = await $self->read_csd;
174              
175 0         0 return $csd->{bytes};
176 0     0 1 0 }
177              
178 4         7 method _spi_txn ( $code )
  4         8  
  4         6  
179 4     4   11 {
180             $self->protocol->assert_ss->then(
181             $code
182             )->followed_by( sub {
183 4     4   451 my ( $f ) = @_;
184 4         14 $self->protocol->release_ss->then( sub { $f } );
  4         4450  
185 4         14 });
186             }
187              
188             =head2 read_csd
189              
190             $data = await $card->read_csd;
191              
192             Returns a C reference containing decoded fields from the SD card's CSD
193             ("card-specific data") register.
194              
195             This hash will contain the following fields:
196              
197             TAAC
198             NSAC
199             TRAN_SPEED
200             CCC
201             READ_BL_LEN
202             READ_BL_LEN_PARTIAL
203             WRITE_BLK_MISALIGN
204             READ_BLK_MISALIGN
205             DSR_IMP
206             C_SIZE
207             VDD_R_CURR_MIN
208             VDD_R_CURR_MAX
209             VDD_W_CURR_MIN
210             VDD_W_CURR_MAX
211             C_SIZE_MULT
212             ERASE_BLK_EN
213             SECTOR_SIZE
214             WP_GRP_SIZE
215             WP_GRP_ENABLE
216             R2W_FACTOR
217             WRITE_BL_LEN
218             WRITE_BL_PARTIAL
219             FILE_FORMAT_GRP
220             COPY
221             PERM_WRITE_PROTECT
222             TEMP_WRITE_PROTECT
223             FILE_FORMAT
224              
225             The hash will also contain the following calculated fields, derived from the
226             decoded fields above for convenience of calling code.
227              
228             blocks # number of blocks implied by C_SIZE / C_SIZE_MULT
229             bytes # number of bytes of storage, implied by blocks and READ_BL_LEN
230              
231             =cut
232              
233             # This code is most annoying to write as it involves lots of bitwise unpacking
234             # at non-byte boundaries. It's easier (though inefficient) to perform this on
235             # an array of 128 1-bit values
236             sub _bits_to_uint ( @vals )
237 17     17   28 {
  17         34  
  17         23  
238 17         20 my $n = 0;
239 17         47 ( $n <<= 1 ) |= $_ for reverse @vals;
240 17         111 return $n;
241             }
242              
243             my %_DECSCALE = (
244             1 => 1.0, 2 => 1.2, 3 => 1.3, 4 => 1.5, 5 => 2.0, 6 => 2.5,
245             7 => 3.0, 8 => 3.5, 9 => 4.0, 0xA => 4.5, 0xB => 5.0,
246             0xC => 5.5, 0xD => 6.0, 0xE => 7.0, 0xF => 8.0
247             );
248              
249 2         4 sub _convert_decimal ( $unit, $val )
250 2     2   4 {
  2         3  
  2         2  
251 2         5 my $mult = $unit % 3;
252 2         4 $unit -= $mult;
253 2         3 $unit /= 3;
254              
255 2         8 $val = $_DECSCALE{$val} * ( 10 ** $mult );
256              
257 2         21 return $val . substr( "num kMG", $unit + 3, 1 );
258             }
259              
260             my %_CURRMIN = (
261             0 => 0.5, 1 => 1, 2 => 5, 3 => 10,
262             4 => 25, 5 => 35, 6 => 60, 7 => 100,
263             );
264             my %_CURRMAX = (
265             0 => 1, 1 => 5, 2 => 10, 3 => 25,
266             4 => 35, 5 => 45, 6 => 80, 7 => 200,
267             );
268              
269             sub _unpack_csd_v0 ( $bytes )
270 1     1   2 {
  1         2  
  1         2  
271 1         39 my @bits = reverse split //, unpack "B128", $bytes;
272              
273             my %csd = (
274             TAAC => _convert_decimal( _bits_to_uint( @bits[112 .. 114] ) - 9, _bits_to_uint( @bits[115 .. 118] ) ) . "s",
275             NSAC => 100*_bits_to_uint( @bits[104 .. 111] ) . "ck",
276             TRAN_SPEED => _convert_decimal( _bits_to_uint( @bits[ 96 .. 98] ) + 5, _bits_to_uint( @bits[ 99 .. 102] ) ) . "bit/s",
277 12         23 CCC => [ grep { $bits[84+$_] } 0 .. 11 ],
278             READ_BL_LEN => 2**_bits_to_uint( @bits[ 80 .. 83] ),
279             READ_BL_LEN_PARTIAL => $bits[79],
280             WRITE_BLK_MISALIGN => $bits[78],
281             READ_BLK_MISALIGN => $bits[77],
282             DSR_IMP => $bits[76],
283             C_SIZE => _bits_to_uint( @bits[ 62 .. 73] ),
284             VDD_R_CURR_MIN => $_CURRMIN{ _bits_to_uint( @bits[ 59 .. 61] ) } . "mA",
285             VDD_R_CURR_MAX => $_CURRMAX{ _bits_to_uint( @bits[ 56 .. 58] ) } . "mA",
286             VDD_W_CURR_MIN => $_CURRMIN{ _bits_to_uint( @bits[ 53 .. 55] ) } . "mA",
287 1         7 VDD_W_CURR_MAX => $_CURRMAX{ _bits_to_uint( @bits[ 50 .. 52] ) } . "mA",
288             C_SIZE_MULT => _bits_to_uint( @bits[ 47 .. 49] ),
289             ERASE_BLK_EN => $bits[46],
290             SECTOR_SIZE => 1+_bits_to_uint( @bits[ 39 .. 45] ),
291             WP_GRP_SIZE => 1+_bits_to_uint( @bits[ 32 .. 38] ),
292             WP_GRP_ENABLE => $bits[31],
293             R2W_FACTOR => 2**_bits_to_uint( @bits[ 26 .. 28] ),
294             WRITE_BL_LEN => 2**_bits_to_uint( @bits[ 22 .. 25] ),
295             WRITE_BL_PARTIAL => $bits[21],
296             FILE_FORMAT_GRP => $bits[15],
297             COPY => $bits[14],
298             PERM_WRITE_PROTECT => $bits[13],
299             TEMP_WRITE_PROTECT => $bits[12],
300             FILE_FORMAT => _bits_to_uint( @bits[ 10 .. 11] ),
301             # Final bits are the CRC, which we ignore
302             );
303              
304 1         8 $csd{blocks} = ( 1 + $csd{C_SIZE} ) * ( 2 ** ( $csd{C_SIZE_MULT} + 2 ) );
305 1         3 $csd{bytes} = $csd{blocks} * $csd{READ_BL_LEN};
306              
307 1         18 return \%csd;
308             }
309              
310 1         2 async method read_csd ()
  1         1  
311 1         4 {
312 1         4 my $protocol = $self->protocol;
313              
314 1     1   8805 my $csd = await $self->_spi_txn( async sub {
315 1         5 await $protocol->write_no_ss(
316             pack "C N C a*", 0x40 | CMD_SEND_CSD, 0, 0xFF, "\xFF"
317             );
318              
319 1         1115 my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );
320              
321 1         1061 $buf =~ s/^\xFF*//;
322 1 50       6 $buf =~ s/^\0// or
323             return Future->fail( sprintf "Expected response 00; got %02X to SEND_CSD", ord $buf );
324              
325 1         5 return await $self->_recv_data_block( $buf, 16 );
326 1         11 });
327              
328             # Top two bits give the structure version
329 1         42 my $ver = vec( $csd, 0, 2 );
330 1 50       5 if( $ver == 0 ) {
    0          
331 1         3 return _unpack_csd_v0( $csd );
332             }
333             elsif( $ver == 1 ) {
334 0         0 return _unpack_csd_v1( $csd );
335             }
336             else {
337 0         0 die "Bad CSD structure version $ver";
338             }
339 1     1 1 450 }
340              
341             =head2 read_ocr
342              
343             $fields = await $card->read_ocr;
344              
345             Returns a C reference containing decoded fields from the card's OCR
346             ("operating conditions register").
347              
348             This hash will contain the following fields:
349              
350             BUSY
351             CCS
352             UHS_II
353             1V8_ACCEPTED
354             3V5, 3V4, 3V3, ..., 2V7
355              
356             =cut
357              
358             bitfield OCR =>
359             BUSY => boolfield( 31 ),
360             CCS => boolfield( 30 ),
361             UHS_II => boolfield( 29 ),
362             '1V8_ACCEPTED' => boolfield( 24 ),
363             '3V5' => boolfield( 23 ),
364             '3V4' => boolfield( 22 ),
365             '3V3' => boolfield( 21 ),
366             '3V2' => boolfield( 20 ),
367             '3V1' => boolfield( 19 ),
368             '3V0' => boolfield( 18 ),
369             '2V9' => boolfield( 17 ),
370             '2V8' => boolfield( 16 ),
371             '2V7' => boolfield( 15 );
372              
373 0         0 async method read_ocr ()
  0         0  
374 0         0 {
375 0         0 my ( $resp, $ocr ) = await $self->send_command( CMD_READ_OCR, undef, 4 );
376              
377 0         0 return { unpack_OCR( unpack "N", $ocr ) };
378 0     0 1 0 }
379              
380             =head2 read_block
381              
382             $bytes = await $card->read_block( $lba );
383              
384             Returns a 512-byte bytestring containing data read from the given sector of
385             the card.
386              
387             =cut
388              
389 3         8 async method read_block ( $lba )
  3         15  
  3         7  
390 3         15 {
391 3         8 my $byteaddr = $lba * 512;
392              
393 3         12 my $protocol = $self->protocol;
394              
395 3         21 my $buf;
396              
397 3     3   11428 return await $self->_spi_txn( async sub {
398 3         32 await $protocol->write_no_ss(
399             pack "C N C a*", 0x40 | CMD_READ_SINGLE_BLOCK, $byteaddr, 0xFF, "\xFF"
400             );
401              
402 3         3402 my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );
403              
404 3         3212 $buf =~ s/^\xFF*//;
405 3 50       18 $buf =~ s/^\0// or
406             die sprintf "Expected response 00; got %02X to READ_SINGLE_BLOCK", ord $buf;
407              
408 3         44 return await $self->_recv_data_block( $buf, 512 );
409 3         22 });
410 3     3 1 13937 }
411              
412             =head1 TODO
413              
414             =over 4
415              
416             =item *
417              
418             Support block writing.
419              
420             =item *
421              
422             Support the different initialisation sequence (and block size requirements) of
423             SDHC cards.
424              
425             =back
426              
427             =head1 AUTHOR
428              
429             Paul Evans
430              
431             =cut
432              
433             0x55AA;