#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2016-2020 -- leonerd@leonerd.org.uk

use Object::Pad 0.19;

package Device::Chip::MCP4725 0.11;
class Device::Chip::MCP4725
   extends Device::Chip;

use Carp;
use Future::AsyncAwait;

use constant PROTOCOL => "I2C";

=encoding UTF-8

=head1 NAME

C<Device::Chip::MCP4725> - chip driver for F<MCP4725>

=head1 SYNOPSIS

   use Device::Chip::MCP4725;
   use Future::AsyncAwait;

   my $chip = Device::Chip::MCP4725->new;
   await $chip->mount( Device::Chip::Adapter::...->new );

   # Presuming Vcc = 5V
   await $chip->write_dac_ratio( 1.23 / 5 );
   print "Output is now set to 1.23V\n";

=head1 DESCRIPTION

This L<Device::Chip> subclass provides specific communication to a
F<Microchip> F<MCP4725> attached to a computer via an I²C adapter.

The reader is presumed to be familiar with the general operation of this chip;
the documentation here will not attempt to explain or define chip-specific
concepts or features, only the use of this module to access them.

=cut

=head1 MOUNT PARAMETERS

=head2 addr

The I²C address of the device. Can be specified in decimal, octal or hex with
leading C<0> or C<0x> prefixes.

=cut

sub I2C_options ( $, %params )
{
   my $addr = delete $params{addr} // 0x60;
   $addr = oct $addr if $addr =~ m/^0/;

   return (
      addr        => $addr,
      max_bitrate => 400E3,
   );
}

=head1 ACCESSORS

The following methods documented in an C<await> expression return L<Future>
instances.

=cut

my @POWERDOWN_TO_NAME = qw( normal 1k 100k 500k );
my %NAME_TO_POWERDOWN = map { $POWERDOWN_TO_NAME[$_] => $_ } 0 .. $#POWERDOWN_TO_NAME;

=head2 read_config

   $config = await $chip->read_config;

Returns a C<HASH> reference containing the chip's current configuration

   RDY => 0 | 1
   POR => 0 | 1

   PD  => "normal" | "1k" | "100k" | "500k"
   DAC => 0 .. 4095

   EEPROM_PD  => "normal" | "1k" | "100k" | "500k"
   EEPROM_DAC => 0 .. 4095

=cut

async method read_config ()
{
   my $bytes = await $self->protocol->read( 5 );
   my ( $status, $dac, $eeprom ) = unpack( "C S> S>", $bytes );

   return {
      RDY => !!( $status & 0x80 ),
      POR => !!( $status & 0x40 ),
      PD  => $POWERDOWN_TO_NAME[ ( $status & 0x06 ) >> 1 ],

      DAC => $dac >> 4,

      EEPROM_PD  => $POWERDOWN_TO_NAME[ ( $eeprom & 0x6000 ) >> 13 ],
      EEPROM_DAC => ( $eeprom & 0x0FFF ),
   };
}

=head1 METHODS

=cut

=head2 write_dac

   await $chip->write_dac( $dac, $powerdown );

Writes a new value for the DAC output and powerdown state in "fast" mode.

C<$powerdown> is optional and will default to 0 if not provided.

=cut

async method write_dac ( $dac, $powerdown = undef )
{
   $dac &= 0x0FFF;

   my $pd = 0;
   $pd = $NAME_TO_POWERDOWN{$powerdown} // croak "Unrecognised powerdown state '$powerdown'"
      if defined $powerdown;

   await $self->protocol->write( pack "S>", $pd << 12 | $dac );
}

=head2 write_dac_ratio

   await $chip->write_dac_ratio( $ratio );

Writes a new value for the DAC output, setting it to normal output for a given
ratio between 0 and 1.

=cut

async method write_dac_ratio ( $ratio )
{
   await $self->write_dac_ratio( $ratio * 2**12 );
}

=head2 write_dac_and_eeprom

   $chip->write_dac_and_eeprom( $dac, $powerdown )

As L</write_dac> but also updates the EEPROM with the same values.

=cut

async method write_dac_and_eeprom ( $dac, $powerdown = undef )
{
   $dac &= 0x0FFF;

   my $pd = 0;
   $pd = $NAME_TO_POWERDOWN{$powerdown} // croak "Unrecognised powerdown state '$powerdown'"
      if defined $powerdown;

   await $self->protocol->write( pack "C S>", 0x60 | $pd << 1, $dac << 4 );
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;