Device-AVR-UPDI

 view release on metacpan or  search on metacpan

lib/Device/AVR/UPDI.pm  view on Meta::CPAN


=cut

async method write_eeprom_page ( $addr, $data )
{
   await $self->nvmctrl->write_eeprom_page( $addr, $data );
}

=head2 write_fuse

   await $updi->write_fuse( $idx, $value );

Writes a fuse value. C<$idx> is the index of the fuse within the FUSES memory
segment, from 0 onwards.

=cut

async method write_fuse ( $idx, $value )
{
   await $self->nvmctrl->write_fuse( $idx, $value );
}

=head2 read_fuse

   $value = await $updi->read_fuse( $idx );

Reads a fuse value. C<$idx> is the index of the fuse within the FUSES memory
segment, from 0 onwards.

=cut

async method read_fuse ( $idx )
{
   my $addr = $_partinfo->baseaddr_fuse + $idx;

   return await $self->lds8( $addr );
}

class # hide from indexer
   Device::AVR::UPDI::_NVMCtrl {

   use constant {
      NVMCTRL_CTRLA  => 0,
      NVMCTRL_STATUS => 2,
         NVMCTRL_STATUS_FBUSY => (1<<0),
   };

   field $_updi     :param :inheritable;
   field $_partinfo :inheritable;

   ADJUST
   {
      $_partinfo = $_updi->partinfo;
   }

   async method nvmctrl_command ( $cmd )
   {
      await $_updi->sts8( $_partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLA, $cmd );
   }

   async method await_nvm_not_busy ()
   {
      my $timeout = 50;
      while( --$timeout ) {
         last if not( NVMCTRL_STATUS_FBUSY & await $_updi->lds8(
            $_partinfo->baseaddr_nvmctrl + NVMCTRL_STATUS ) );

         await Future::IO->sleep( 0.01 );
      }
   }
}

class # hide from indexer
   Device::AVR::UPDI::_NVMCtrlv0 {

   inherit Device::AVR::UPDI::_NVMCtrl qw( $_updi $_partinfo );

   use Carp;

   use constant {
      # Command values
         NVMCTRL_CMD_WP   => 1,
         NVMCTRL_CMD_ER   => 2,
         NVMCTRL_CMD_ERWP => 3,
         NVMCTRL_CMD_PBC  => 4,
         NVMCTRL_CMD_CHER => 5,
         NVMCTRL_CMD_EEER => 6,
         NVMCTRL_CMD_WFU  => 7,
      NVMCTRL_CTRLB  => 1,
      NVMCTRL_DATA   => 6,
      NVMCTRL_ADDR   => 8,
   };

   async method read_flash_page ( $addr, $len )
   {
      return await $_updi->ld( $_partinfo->baseaddr_flash + $addr, $len );
   }

   async method read_eeprom_page ( $addr, $len )
   {
      return await $_updi->ld( $_partinfo->baseaddr_eeprom + $addr, $len );
   }

   async method _write_page ( $addr, $data, $wordsize, $cmd )
   {
      # clear page buffer
      await $self->nvmctrl_command( NVMCTRL_CMD_PBC );
      await $self->await_nvm_not_busy;

      # Disable response sig for speed
      await $_updi->set_rsd( 1 );

      if( $wordsize == 8 ) {
         await $_updi->st8( $addr, $data );
      }
      elsif( $wordsize == 16 ) {
         await $_updi->st16( $addr, $data );
      }
      else {
         croak "Invalid word size";
      }

      # Re-enable response sig again
      await $_updi->set_rsd( 0 );

      await $self->nvmctrl_command( $cmd );
      await $self->await_nvm_not_busy;
   }

   async method write_flash_page ( $addr, $data )
   {
      await $self->_write_page( $_partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_WP );
   }

   async method write_eeprom_page ( $addr, $data )
   {
      await $self->_write_page( $_partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_ERWP );
   }

   async method write_fuse ( $idx, $value )
   {
      my $addr = $_partinfo->baseaddr_fuse + $idx;

      my $baseaddr = $_partinfo->baseaddr_nvmctrl;

      # Oddly, this works but an attempt at STS16 does not. Unsure why
      await $_updi->sts8 ( $baseaddr + NVMCTRL_ADDR  , $addr & 0xFF );
      await $_updi->sts8 ( $baseaddr + NVMCTRL_ADDR+1, $addr >> 8 );

      await $_updi->sts8 ( $baseaddr + NVMCTRL_DATA, $value );

      await $self->nvmctrl_command( NVMCTRL_CMD_WFU );

      await $self->await_nvm_not_busy;
   }
}

class # hide from indexer
   Device::AVR::UPDI::_NVMCtrlv2 {

   inherit Device::AVR::UPDI::_NVMCtrl qw( $_updi $_partinfo );

   use Carp;

   use constant {
      # Command values
         NVMCTRL_CMD_NOCMD  => 0x00,
         NVMCTRL_CMD_FLWR   => 0x02,
         NVMCTRL_CMD_EEERWR => 0x13,

      NVMCTRL_CTRLB => 1,
   };

   async method _set_flmap ( $bank )
   {
      await $_updi->sts8( $_partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLB, $bank << 4 );
   }

   async method read_flash_page ( $addr, $len )
   {
      await $self->_set_flmap( $addr >> 15 );
      $addr &= 0x7FFF;

      return await $_updi->ld( $_partinfo->baseaddr_flash + $addr, $len );
   }

   async method read_eeprom_page ( $addr, $len )
   {
      return await $_updi->ld( $_partinfo->baseaddr_eeprom + $addr, $len );
   }

   async method _write_page ( $addr, $data, $wordsize, $cmd )
   {
      # set page write mode
      await $self->nvmctrl_command( $cmd );

      # Disable response sig for speed on long data
      #  (no point on single-byte fuses)
      await $_updi->set_rsd( 1 ) if length $data > 1;

      if( $wordsize == 8 ) {
         await $_updi->st8( $addr, $data );
      }
      elsif( $wordsize == 16 ) {
         await $_updi->st16( $addr, $data );
      }
      else {
         croak "Invalid word size";
      }

      # Re-enable response sig again
      await $_updi->set_rsd( 0 );

      await $self->await_nvm_not_busy;

      # clear command
      await $self->nvmctrl_command( NVMCTRL_CMD_NOCMD );
      await $self->await_nvm_not_busy;
   }

   async method write_flash_page ( $addr, $data )
   {
      await $self->_set_flmap( $addr >> 15 );
      $addr &= 0x7FFF;

      await $self->_write_page( $_partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_FLWR );
   }

   async method write_eeprom_page ( $addr, $data )
   {
      await $self->_write_page( $_partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_EEERWR );
   }

   async method write_fuse ( $idx, $value )
   {
      # Fuses are written by pretending it's EEPROM
      my $data = pack "C", $value;

      await $self->_write_page( $_partinfo->baseaddr_fuse + $idx, $data, 8, NVMCTRL_CMD_EEERWR );
   }
}

=head1 SEE ALSO

=over 2

=item *

"AVR UPDI Programming Cable"

An adapter cable to flash firmware onto an AVR microcontroller chip via UPDI,
compatible with this module.

L<https://www.tindie.com/products/16571/>

=back

=cut

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;

__DATA__
# These data are maintained by rebuild-partinfo.pl
# name|signature|baseaddr_nvmctl|baseaddr_fuse|baseaddr_sigrow|baseaddr_flash|pagesize_flash|size_flash|baseaddr_eeprom|pagesize_eeprom|size_eeprom|fuses
ATmega808|1e9326|0x1000|0x1280|0x1100|0x4000|64|8192|0x1400|32|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega809|1e932a|0x1000|0x1280|0x1100|0x4000|64|8192|0x1400|32|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega1608|1e9427|0x1000|0x1280|0x1100|0x4000|64|16384|0x1400|32|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega1609|1e9426|0x1000|0x1280|0x1100|0x4000|64|16384|0x1400|32|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega3208|1e9530|0x1000|0x1280|0x1100|0x4000|128|32768|0x1400|64|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega3209|1e9531|0x1000|0x1280|0x1100|0x4000|128|32768|0x1400|64|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega4808|1e9650|0x1000|0x1280|0x1100|0x4000|128|49152|0x1400|64|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND
ATmega4809|1e9651|0x1000|0x1280|0x1100|0x4000|128|49152|0x1400|64|256|WDTCFG,BODCFG,OSCCFG,,,SYSCFG0,SYSCFG1,APPEND,BOOTEND



( run in 1.736 second using v1.01-cache-2.11-cpan-39bf76dae61 )