File Coverage

blib/lib/Device/Chip/Adapter/BusPirate.pm
Criterion Covered Total %
statement 129 222 58.1
branch 11 36 30.5
condition 6 10 60.0
subroutine 36 62 58.0
pod 3 7 42.8
total 185 337 54.9


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