File Coverage

blib/lib/Device/Chip/Adapter/BusPirate.pm
Criterion Covered Total %
statement 118 212 55.6
branch 14 42 33.3
condition 3 8 37.5
subroutine 32 59 54.2
pod 3 7 42.8
total 170 328 51.8


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2015-2021 -- leonerd@leonerd.org.uk
5              
6             package Device::Chip::Adapter::BusPirate 0.21;
7              
8 2     2   3295 use v5.14;
  2         8  
9 2     2   17 use warnings;
  2         4  
  2         72  
10 2     2   12 use base qw( Device::Chip::Adapter );
  2         3  
  2         1333  
11              
12 2     2   25663 use Carp;
  2         4  
  2         103  
13              
14 2     2   13 use Future::AsyncAwait;
  2         3  
  2         10  
15              
16 2     2   71 use Device::BusPirate;
  2         5  
  2         1741  
17              
18             =head1 NAME
19              
20             C - a C implementation
21              
22             =head1 DESCRIPTION
23              
24             This class implements the L interface for the
25             I, allowing an instance of a L driver to communicate
26             with the actual chip hardware by using the I as a hardware
27             adapter.
28              
29             =cut
30              
31             =head1 CONSTRUCTOR
32              
33             =cut
34              
35             =head2 new
36              
37             $adapter = Device::Chip::Adapter::BusPirate->new( %args )
38              
39             Returns a new instance of a C. Takes the
40             same named arguments as L.
41              
42             =cut
43              
44             sub new
45             {
46 1     1 1 97 my $class = shift;
47              
48 1         9 my $bp = Device::BusPirate->new( @_ );
49              
50 1         6 bless {
51             bp => $bp,
52             mode => undef,
53             }, $class;
54             }
55              
56             sub new_from_description
57             {
58 0     0 1 0 my $class = shift;
59 0         0 my %args = @_;
60             # Whitelist known-OK constructor args
61 0         0 $class->new( map { $_ => $args{$_} } qw( serial baud ) );
  0         0  
62             }
63              
64             =head1 METHODS
65              
66             This module provides no new methods beyond the basic API documented in
67             L at version 0.01.
68              
69             Since version I this module now supports multiple instances of the I2C
70             protocol, allowing multiple chips to be shared on the same bus.
71              
72             =cut
73              
74 0     0   0 sub _modename { return ( ref($_[0]) =~ m/.*::(.*?)$/ )[0] }
75              
76             async sub make_protocol_GPIO
77 0     0 0 0 {
78 0         0 my $self = shift;
79              
80             $self->{mode} and
81 0 0       0 croak "Cannot enter GPIO protocol when " . _modename( $self->{mode} ) . " already active";
82              
83 0         0 my $mode = await $self->{bp}->enter_mode( "BB" );
84 0         0 $self->{mode} = $mode;
85              
86 0         0 await $mode->configure( open_drain => 0 );
87              
88 0         0 return Device::Chip::Adapter::BusPirate::_GPIO->new( $mode );
89             }
90              
91             async sub make_protocol_SPI
92 0     0 0 0 {
93 0         0 my $self = shift;
94              
95             $self->{mode} and
96 0 0       0 croak "Cannot enter SPI protocol when " . _modename( $self->{mode} ) . " already active";
97              
98 0         0 my $mode = await $self->{bp}->enter_mode( "SPI" );
99 0         0 $self->{mode} = $mode;
100              
101 0         0 await $mode->configure( open_drain => 0 );
102              
103 0         0 return Device::Chip::Adapter::BusPirate::_SPI->new( $mode );
104             }
105              
106             async sub _enter_mode_I2C
107 1     1   3 {
108 1         2 my $self = shift;
109              
110             return $self->{mode} if
111 1 50 33     8 $self->{mode} and _modename( $self->{mode} ) eq "I2C";
112              
113             $self->{mode} and
114 1 50       5 croak "Cannot enter I2C protocol when " . _modename( $self->{mode} ) . " already active";
115              
116 1         5 my $mode = await $self->{bp}->enter_mode( "I2C" );
117 1         135 $self->{mode} = $mode;
118              
119 1         6 await $mode->configure( open_drain => 1 );
120              
121 1         33 return $mode;
122             }
123              
124             async sub make_protocol_I2C
125 1     1 0 56 {
126 1         3 my $self = shift;
127              
128 1         4 my $mode = await $self->_enter_mode_I2C;
129              
130 1         91 return Device::Chip::Adapter::BusPirate::_I2C->new( $mode );
131             }
132              
133             async sub make_protocol_UART
134 0     0 0 0 {
135 0         0 my $self = shift;
136              
137             $self->{mode} and
138 0 0       0 croak "Cannot enter UART protocol when " . _modename( $self->{mode} ) . " already active";
139              
140 0         0 my $mode = await $self->{bp}->enter_mode( "UART" );
141 0         0 $self->{mode} = $mode;
142              
143 0         0 await $mode->configure( open_drain => 0 );
144              
145 0         0 return Device::Chip::Adapter::BusPirate::_UART->new( $mode );
146             }
147              
148             sub shutdown
149             {
150 0     0 1 0 my $self = shift;
151 0         0 $self->{mode}->power( 0 )->get;
152 0         0 $self->{bp}->stop;
153             }
154              
155             package
156             Device::Chip::Adapter::BusPirate::_base;
157              
158 2     2   16 use Carp;
  2         6  
  2         130  
159 2     2   14 use List::Util qw( first );
  2         4  
  2         1607  
160              
161             sub new
162             {
163 1     1   3 my $class = shift;
164 1         2 my ( $mode ) = @_;
165              
166 1         7 bless { mode => $mode }, $class;
167             }
168              
169             sub sleep
170             {
171 0     0   0 my $self = shift;
172 0         0 $self->{mode}->pirate->sleep( @_ );
173             }
174              
175             sub power
176             {
177 0     0   0 my $self = shift;
178 0         0 $self->{mode}->power( @_ );
179             }
180              
181             sub _find_speed
182             {
183 1     1   2 shift;
184 1         5 my ( $max_bitrate, @speeds ) = @_;
185              
186             return first {
187 2     2   4 my $rate = $_;
188 2 50       18 $rate =~ m/(.*)k$/ and $rate = 1E3 * $1;
189 2 50       10 $rate =~ m/(.*)M$/ and $rate = 1E6 * $1;
190              
191 2         9 $rate <= $max_bitrate
192 1         11 } @speeds;
193             }
194              
195             # Most modes only have access to the AUX GPIO pin
196 0     0   0 sub list_gpios { return qw( AUX ) }
197              
198             sub meta_gpios
199             {
200 0     0   0 my $self = shift;
201              
202 0         0 return map { Device::Chip::Adapter::GPIODefinition( $_, "rw", 0 ) }
  0         0  
203             $self->list_gpios;
204             }
205              
206             sub write_gpios
207             {
208 0     0   0 my $self = shift;
209 0         0 my ( $gpios ) = @_;
210              
211 0         0 my $mode = $self->{mode};
212              
213 0         0 foreach my $pin ( keys %$gpios ) {
214 0 0       0 $pin eq "AUX" or
215             croak "Unrecognised GPIO pin name $pin";
216              
217 0         0 return $mode->aux( $gpios->{$pin} );
218             }
219              
220 0         0 Future->done;
221             }
222              
223             sub read_gpios
224             {
225 0     0   0 my $self = shift;
226 0         0 my ( $gpios ) = @_;
227              
228 0         0 my $mode = $self->{mode};
229              
230 0         0 my @f;
231 0         0 foreach my $pin ( @$gpios ) {
232 0 0       0 $pin eq "AUX" or
233             croak "Unrecognised GPIO pin name $pin";
234              
235             return $mode->read_aux
236 0     0   0 ->transform( done => sub { { AUX => $_[0] } } );
  0         0  
237             }
238              
239 0         0 Future->done( {} );
240             }
241              
242             # there's no more efficient way to tris_gpios than just read and ignore the result
243             async sub tris_gpios
244 0     0   0 {
245 0         0 my $self = shift;
246 0         0 await $self->read_gpios;
247 0         0 return;
248             }
249              
250             package
251             Device::Chip::Adapter::BusPirate::_GPIO;
252 2     2   17 use base qw( Device::Chip::Adapter::BusPirate::_base );
  2         5  
  2         889  
253              
254 2     2   17 use List::Util 1.29 qw( pairmap );
  2         34  
  2         754  
255              
256 0     0   0 sub list_gpios { return qw( MISO CS MOSI CLK AUX ) }
257              
258             sub write_gpios
259             {
260 0     0   0 my $self = shift;
261 0         0 my ( $gpios ) = @_;
262              
263 0         0 my $mode = $self->{mode};
264              
265             # TODO: validity checking
266             $mode->write(
267 0     0   0 pairmap { lc $a => $b } %$gpios
  0         0  
268             )
269             }
270              
271             async sub read_gpios
272 0     0   0 {
273 0         0 my $self = shift;
274 0         0 my ( $gpios ) = @_;
275              
276 0         0 my $mode = $self->{mode};
277              
278 0         0 my $vals = await $mode->read( map { lc $_ } @$gpios );
279              
280 0     0   0 return { pairmap { uc $a => $b } %$vals };
  0         0  
281             }
282              
283             package
284             Device::Chip::Adapter::BusPirate::_SPI;
285 2     2   16 use base qw( Device::Chip::Adapter::BusPirate::_base Device::Chip::ProtocolBase::SPI );
  2         5  
  2         1650  
286              
287 2     2   2848 use Carp;
  2         6  
  2         654  
288              
289             my @SPI_SPEEDS = (qw( 8M 4M 2.6M 2M 1M 250k 125k 30k ));
290              
291             sub configure
292             {
293 0     0   0 my $self = shift;
294 0         0 my %args = @_;
295              
296 0         0 my $mode = delete $args{mode};
297 0         0 my $max_bitrate = delete $args{max_bitrate};
298              
299             croak "Cannot support SPI wordsize other than 8"
300 0 0 0     0 if ( $args{wordsize} // 8 ) != 8;
301              
302 0 0       0 croak "Unrecognised configuration options: " . join( ", ", keys %args )
303             if %args;
304              
305             $self->{mode}->configure(
306 0 0       0 ( defined $mode ?
    0          
307             ( mode => $mode ) : () ),
308             ( defined $max_bitrate ?
309             ( speed => $self->_find_speed( $max_bitrate, @SPI_SPEEDS ) ) : () ),
310             );
311             }
312              
313             sub readwrite
314             {
315 0     0   0 my $self = shift;
316 0         0 my ( $data ) = @_;
317              
318 0         0 $self->{mode}->writeread_cs( $data );
319             }
320              
321             sub readwrite_no_ss
322             {
323 0     0   0 my $self = shift;
324 0         0 my ( $data ) = @_;
325              
326 0         0 $self->{mode}->writeread( $data );
327             }
328              
329             sub assert_ss
330             {
331 0     0   0 my $self = shift;
332 0         0 $self->{mode}->chip_select( 0 );
333             }
334              
335             sub release_ss
336             {
337 0     0   0 my $self = shift;
338 0         0 $self->{mode}->chip_select( 1 );
339             }
340              
341             package
342             Device::Chip::Adapter::BusPirate::_I2C;
343 2     2   16 use base qw( Device::Chip::Adapter::BusPirate::_base );
  2         5  
  2         650  
344              
345 2     2   16 use Carp;
  2         4  
  2         2023  
346              
347             my @I2C_SPEEDS = (qw( 400k 100k 50k 5k ));
348              
349             # TODO - addr ought to be a mount option somehow
350             sub configure
351             {
352 1     1   722 my $self = shift;
353 1         4 my %args = @_;
354              
355 1         3 my $addr = delete $args{addr};
356 1         3 my $max_bitrate = delete $args{max_bitrate};
357              
358 1 50       4 croak "Unrecognised configuration options: " . join( ", ", keys %args )
359             if %args;
360              
361 1 50       7 $self->{addr} = $addr if defined $addr;
362              
363 1         2 my @f;
364              
365             push @f, $self->{mode}->configure(
366 1 50       11 speed => $self->_find_speed( $max_bitrate, @I2C_SPEEDS )
367             ) if defined $max_bitrate;
368              
369             # It's highly likely the user will want the pullups enabled here
370 1         287 push @f, $self->{mode}->pullup( 1 );
371              
372 1         330 Future->needs_all( @f );
373             }
374              
375             sub DESTROY
376             {
377 1     1   1969 my $self = shift;
378 1 50       10 $self->{mode}->pullup( 0 )->get if $self->{mode};
379             }
380              
381             async sub write
382 5     5   638 {
383 5         9 my $self = shift;
384 5         11 my ( $bytes ) = @_;
385              
386 5 50       16 defined( my $addr = $self->{addr} ) or
387             croak "Cannot ->write without a defined addr";
388              
389 5 100   1   21 return await $self->txn(sub { $self->write( $bytes ) }) unless $self->{in_txn};
  1         5  
390              
391 4         13 await $self->{mode}->start_bit;
392 4         281 await $self->{mode}->write( chr( $addr << 1 | 0 ) . $bytes );
393             }
394              
395             async sub read
396 5     5   905 {
397 5         9 my $self = shift;
398 5         11 my ( $len ) = @_;
399              
400 5 50       16 defined( my $addr = $self->{addr} ) or
401             croak "Cannot ->read without a defined addr";
402              
403 5 100   1   16 return await $self->txn(sub { $self->read( $len ) }) unless $self->{in_txn};
  1         5  
404              
405 4         13 await $self->{mode}->start_bit;
406 4         330 await $self->{mode}->write( chr( $addr << 1 | 1 ) );
407 4         343 return await $self->{mode}->read( $len );
408             }
409              
410             sub txn
411             {
412 4     4   1246 my $self = shift;
413 4         11 my ( $code ) = @_;
414              
415 4   66     22 my $mutex = $self->{txn_mutex} //= Future::Mutex->new;
416              
417             return $mutex->enter(sub {
418 4     4   375 $self->{in_txn} = 1;
419             return $code->()->followed_by(sub {
420 4         663 my ( $f ) = @_;
421 4         9 $self->{in_txn} = 0;
422 4         13 return $self->{mode}->stop_bit->then( sub { $f } );
  4         228  
423 4         10 });
424 4         36 });
425             }
426              
427             async sub write_then_read
428 1     1   1292 {
429 1         2 my $self = shift;
430 1         4 my ( $write_bytes, $read_len ) = @_;
431              
432 1     1   3 return await $self->txn(async sub {
433 1         4 await $self->write( $write_bytes );
434 1         167 return await $self->read( $read_len );
435 1         7 });
436             }
437              
438             package
439             Device::Chip::Adapter::BusPirate::_UART;
440 2     2   16 use base qw( Device::Chip::Adapter::BusPirate::_base );
  2         5  
  2         617  
441              
442 2     2   17 use Carp;
  2         4  
  2         494  
443              
444             sub configure
445             {
446 0     0     my $self = shift;
447 0           my %args = @_;
448              
449             return $self->{mode}->configure(
450             baud => $args{baudrate},
451             bits => $args{bits},
452             parity => $args{parity},
453             stop => $args{stop},
454 0           );
455             }
456              
457             sub write
458             {
459 0     0     my $self = shift;
460 0           my ( $bytes ) = @_;
461              
462 0           return $self->{mode}->write( $bytes );
463             }
464              
465 0     0     sub read { croak "Device::BusPirate does not support read on UART" }
466              
467             =head1 AUTHOR
468              
469             Paul Evans
470              
471             =cut
472              
473             0x55AA;