package Device::BusPirate::Mode::BB;

use strict;
use warnings;
use base qw( Device::BusPirate::Mode );

our $VERSION = '0.02';

use Carp;

use Future;

use constant MODE => "BB";

use constant {
   MASK_CS   => 0x01,
   MASK_MISO => 0x02,
   MASK_CLK  => 0x04,
   MASK_MOSI => 0x08,
   MASK_AUX  => 0x10,

   CONF_PULLUP => 0x20,
   CONF_POWER  => 0x40,
};

=head1 NAME

C<Device::BusPirate::Mode::BB> - use C<Device::BusPirate> in bit-banging mode

=head1 DESCRIPTION

This object is returned by a L<Device::BusPirate> instance when switching it
into C<BB> mode. It provides methods to configure the hardware, and interact
with the five basic IO lines in bit-banging mode.

=cut

=head1 METHODS

=cut

sub start
{
   my $self = shift;

   $self->{dir_mask} = 0x1f; # all inputs

   $self->{out_mask} = 0; # all off

   Future->done( $self );
}

=head2 $bb->configure( %args )->get

Change configuration options. The following options exist; all of which are
simple true/false booleans.

=over 4

=item open_drain

If enabled, a "high" output pin will be set as an input; i.e. hi-Z. When
disabled (default), a "high" output pin will be driven by 3.3V. A "low" output
will be driven to GND in either case.

=back

=cut

sub configure
{
   my $self = shift;
   my %args = @_;

   defined $args{$_} and $self->{$_} = $args{$_}
      for (qw( open_drain ));

   return Future->done
}

=head2 $bb->cs( $state )->get

=head2 $bb->miso( $state )->get

=head2 $bb->clk( $state )->get

=head2 $bb->mosi( $state )->get

=head2 $bb->aux( $state )->get

Set an output pin to the given logical state. Uses the C<open_drain>
configuration setting to determine whether high should be hi-Z or 3.3V.

=cut

sub cs   { shift->_output( MASK_CS,   @_ ) }
sub miso { shift->_output( MASK_MISO, @_ ) }
sub clk  { shift->_output( MASK_CLK,  @_ ) }
sub mosi { shift->_output( MASK_MOSI, @_ ) }
sub aux  { shift->_output( MASK_AUX,  @_ ) }

sub _output
{
   my $self = shift;
   my ( $mask, $state ) = @_;

   if( $state and !$self->{open_drain} ) {
      $self->{dir_mask} &= ~$mask;
      $self->{out_mask} |=  $mask;
   }
   elsif( $state ) {
      $self->{dir_mask} |=  $mask;
   }
   else {
      $self->{dir_mask} &= ~$mask;
      $self->{out_mask} &= ~$mask;
   }

   $self->pirate->write( chr( 0x40 | $self->{dir_mask} ) );
   $self->pirate->write( chr( 0x80 | $self->{out_mask} ) );

   $self->pirate->read( 2 )->then_done(); # ignore input
}

sub read_cs   { shift->_input( MASK_CS,   @_ ) }
sub read_miso { shift->_input( MASK_MISO, @_ ) }
sub read_clk  { shift->_input( MASK_CLK,  @_ ) }
sub read_mosi { shift->_input( MASK_MOSI, @_ ) }
sub read_aux  { shift->_input( MASK_AUX,  @_ ) }

=head2 $state = $bb->read_cs->get

=head2 $state = $bb->read_miso->get

=head2 $state = $bb->read_clk->get

=head2 $state = $bb->read_mosi->get

=head2 $state = $bb->read_aux->get

Set a pin to input direction and read its current state.

=cut

sub _input
{
   my $self = shift;
   my ( $mask ) = @_;

   $self->{dir_mask} |= $mask;
   $self->pirate->write( chr( 0x40 | $self->{dir_mask} ) );
   $self->pirate->read( 1 )->then( sub {
      my ( $buf ) = @_;
      Future->done( ord( $buf ) & $mask );
   });
}

=head2 $bb->power( $power )->get

Enable or disable the C<VREG> 5V and 3.3V power outputs.

=cut

sub power
{
   my $self = shift;
   my ( $state ) = @_;

   $state ? ( $self->{out_mask} |=  CONF_POWER )
          : ( $self->{out_mask} &= ~CONF_POWER );
   $self->pirate->write( chr( 0x80 | $self->{out_mask} ) );
   $self->pirate->read( 1 )->then_done(); # ignore input
}

=head2 $bb->pullup( $pullup )->get

Enable or disable the IO pin pullup resistors from C<Vpu>. These are connected
to the C<MISO>, C<CLK>, C<MOSI> and C<CS> pins.

=cut

sub pullup
{
   my $self = shift;
   my ( $state ) = @_;

   $state ? ( $self->{out_mask} |=  CONF_PULLUP )
          : ( $self->{out_mask} &= ~CONF_PULLUP );
   $self->pirate->write( chr( 0x80 | $self->{out_mask} ) );
   $self->pirate->read( 1 )->then_done(); # ignore input
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
