#########################################################################################
# Package       HiPi::RaspberryPi
# Description:  Data from, inter alia, /proc/cpuinfo
# Created       Sat Nov 24 00:30:35 2012
# SVN Id        $Id: RaspberryPi.pm 78 2016-03-29 13:53:19Z Mark Dootson $
# Copyright:    Copyright (c) 2012-2015 Mark Dootson
# Licence:      This work is free software; you can redistribute it and/or modify it 
#               under the terms of the GNU General Public License as published by the 
#               Free Software Foundation; either version 3 of the License, or any later 
#               version.
#
#########################################################################################

package HiPi::RaspberryPi;

#########################################################################################

use strict;
use warnings;
use threads;
use threads::shared;
require Exporter;
use base qw( Exporter );
use Carp;

our $VERSION ='0.45';

our @EXPORT_OK = qw(
    system_is_windows
    system_is_unix
    system_is_raspberry
    system_is_mac
    system_is_raspberry_2
    system_is_raspberry_3
    system_uses_device_tree
    system_home_directory
    system_systype
);
                    
our %EXPORT_TAGS = ( all => \@EXPORT_OK );

our ($_israspberry, $_isunix, $_iswindows, $_ismac, $_israspberry_2,
     $_israspberry_3, $_uses_device_tree, $_homedir, $_sysconfig, $_systype ) = (0,0,0,0,0,0, '', undef, undef);

sub system_is_windows { $_iswindows; }
sub system_is_unix { $_isunix; }
sub system_is_raspberry { $_israspberry; }
sub system_is_mac { $_ismac; }
sub system_is_raspberry_2 { $_israspberry_2; }
sub system_is_raspberry_3 { $_israspberry_3; }
sub system_uses_device_tree { $_uses_device_tree; }
sub system_home_directory { $_homedir; }
sub system_systype { $_systype; }

{
    my $perlversion = $^V;
	$perlversion =~ s/^v//;
	my($major, $minor, $release) = split(/\./, $perlversion);
	if ($minor <= 14 ) {
        $_systype = 'wheezy';
    } else {
		$_systype = 'current';
	}
}


our %_revstash = (
    'beta'      => { release => 'Q1 2012', model => 'Raspberry Pi Model B Revision beta', revision => 1, pinplan => 1, memory => 256, manufacturer => 'Generic' },
    '0002'      => { release => 'Q1 2012', model => 'Raspberry Pi Model B Revision 1.0', revision => 1, pinplan => 1, memory => 256, manufacturer => 'Generic' },
    '0003'      => { release => 'Q3 2012', model => 'Raspberry Pi Model B Revision 1.0', revision => 1, pinplan => 1, memory => 256, manufacturer => 'Generic' },
    '0004'      => { release => 'Q3 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Sony' },
    '0005'      => { release => 'Q4 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, memory => 256, manufacturer => 'Qisda' },
    '0006'      => { release => 'Q4 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Egoman' },
    '0007'      => { release => 'Q1 2013', model => 'Raspberry Pi Model A', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Egoman' },
    '0008'      => { release => 'Q1 2013', model => 'Raspberry Pi Model A', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Sony' },
    '0009'      => { release => 'Q1 2013', model => 'Raspberry Pi Model A', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Qisda' },
    '0010'      => { release => 'Q3 2014', model => 'Raspberry Pi Model B +', revision => 2, pinplan => 3, memory => 512, manufacturer => 'Sony' },
    '0011'      => { release => 'Q2 2013', model => 'Compute Module', revision => 2, pinplan => 2, memory => 512, manufacturer => 'Sony' },
    '0012'      => { release => 'Q4 2014', model => 'Raspberry Pi Model A +', revision => 2, pinplan => 3, memory => 256, manufacturer => 'Sony' },
    # '0013'      => { release => 'Q1 2013', model => 'Raspberry Pi Model A', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Qisda' },
    '0014'      => { release => 'Q2 2015', model => 'Compute Module', revision => 2, pinplan => 2, memory => 512, manufacturer => 'Sony' },
    '000d'      => { release => 'Q4 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, pinplan => 2, memory => 512, manufacturer => 'Egoman' },
    '000e'      => { release => 'Q4 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, pinplan => 2, memory => 512, manufacturer => 'Sony' },
    '000f'      => { release => 'Q4 2012', model => 'Raspberry Pi Model B Revision 2.0', revision => 2, pinplan => 2, memory => 512, manufacturer => 'Qisda' },
    'unknown'   => { release => 'Q1 2013', model => 'Virtual or Non-Raspberry Model A', revision => 2, pinplan => 2, memory => 256, manufacturer => 'Virtual' },
    'unknown2'   => { release => 'Q1 2015', model => 'Virtual or Unknown Raspberry Model 2 B', revision => 1, pinplan => 3, memory => 1024, manufacturer => 'Virtual' },
);

# MAP 24 bits of Revision  NEW:1, MEMSIZE:3, MANUFACTURER:4, PROCESSOR:4, MODEL:8, BOARD REVISION:4

our %_revinfostash = (
    memsize => {
        '0' => 256,
        '1' => 512,
        '2' => 1024,
    },
    manufacturer => {
        '0' => 'Sony',
        '1' => 'Egoman',
        '2' => 'Embest',
        '3' => 'Other',
    },
    processor => {
        '0' => 'BCM2835',
        '1' => 'BCM2836',
        '2' => 'BCM2837',
    },
    type => {
        '0' => 'Raspberry Pi Model A',
        '1' => 'Raspberry Pi Model B',
        '2' => 'Raspberry Pi Model A +',
        '3' => 'Raspberry Pi Model B +',
        '4' => 'Raspberry Pi 2 Model B',
        '5' => 'Raspberry Pi Alpha',
        '6' => 'Raspberry Pi Compute Module',
        '7' => 'Raspberry Pi Compute Module',
        '8' => 'Raspberry Pi 3',
        '9' => 'Raspberry Pi Zero',
        
    },
    typepinplan => {
        '0' => 2,
        '1' => 2,
        '2' => 3,
        '3' => 3,
        '4' => 3,
        '5' => 1,
        '6' => 2,
        '7' => 3,
        '8' => 3,
        '9' => 3,
    },
    release => {
        '0' => 'Q1 2013',
        '1' => 'Q3 2012',
        '2' => 'Q4 2014',
        '3' => 'Q3 2014',
        '4' => 'Q1 2015',
        '5' => 'Q1 2012',
        '6' => 'Q2 2013',
        '7' => 'Q2 2015',
        '8' => 'Q1 2016',
        '9' => 'Q4 2015',
    },
);

our %_boardinfo = %{ $_revstash{unknown} };

our %_cpuinfostash: shared;
    
{
    lock %_cpuinfostash;
    %_cpuinfostash = ( 'GPIO Revision' => 2 );
    
    # Platform
    if( $^O =~ /^mswin/i ) {
        $_iswindows = 1;
    } elsif( $^O =~ /^darwin/i ) {
        $_ismac = 1;
    } else {
        $_isunix = 1;
        # clean our path for safety
        local $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
        my $output = qx(cat /proc/cpuinfo);
        
        if( $output ) {
            for ( split(/\n/, $output) ) {
                if( $_ =~ /^([^\s]+)\s*:\s(.+)$/ ) {
                    $_cpuinfostash{$1} = $2;
                }
            }
        }
        
        # uses device tree
        $_uses_device_tree = ( -e '/proc/device-tree/soc/ranges' ) ? 1 : 0;
    }
     
    my $hardware = ($_cpuinfostash{Hardware}) ?  $_cpuinfostash{Hardware} : 'BCM2708';
    my $serial = ($_cpuinfostash{Serial}) ?  $_cpuinfostash{Serial} : 'UNKNOWN';
    my $basepinplan = ( $hardware eq 'BCM2709' ) ? 3 : 2;
    my $defaultkey = ( $basepinplan == 3  ) ? 'unknown2' : 'unknown';
    my $rev = ($_cpuinfostash{Revision}) ?  lc( $_cpuinfostash{Revision} ) : $defaultkey;
    $rev =~ s/^\s+//;
    $rev =~ s/\s+$//;
    
    $_israspberry = $_cpuinfostash{Hardware} && $_cpuinfostash{Hardware} =~ /^BCM2708|BCM2709$/;
    $_israspberry_2 = ( $_israspberry && $hardware eq 'BCM2709' ) ? 1 : 0;
    $_israspberry_3 = 0;
    
    if ( $rev =~ /(beta|unknown|unknown2)$/) {
        my $infokey = exists($_revstash{$rev}) ? $rev : $defaultkey;
        %_boardinfo = %{ $_revstash{$infokey} };
        $_boardinfo{processor} = 'BCM2835';
    } else {
        # is this a scheme 0 or 1 number
        
        my $revnum = oct( '0x' . $rev );
        
        my $schemenewt = 0b100000000000000000000000 & $revnum;
        $schemenewt = $schemenewt >> 23;
        
        if ( $schemenewt ) {
            my $schemerev = 0b1111 & $revnum;
            my $schemetype = 0b111111110000 & $revnum;
            $schemetype = $schemetype >> 4;
            my $schemeproc = 0b1111000000000000 & $revnum;
            $schemeproc = $schemeproc >> 12;
            my $schememanu = 0b11110000000000000000 & $revnum;
            $schememanu = $schememanu >> 16;
            my $schemesize = 0b11100000000000000000000 & $revnum;
            $schemesize = $schemesize >> 20;
            
            # base type
            my $binfo = $_revstash{$defaultkey};
                        
            $binfo->{release}  = $_revinfostash{release}->{$schemetype} || 'Q1 2015';
            $binfo->{model}    = $_revinfostash{type}->{$schemetype} || qq(Unknown Raspberry Pi Type : $schemetype);
            $binfo->{revision} = $schemerev;
            $binfo->{memory}   = $_revinfostash{memsize}->{$schemesize} || 256;
            $binfo->{manufacturer} = $_revinfostash{manufacturer}->{$schememanu} || 'Sony';
            $binfo->{pinplan} =  $_revinfostash{typepinplan}->{$schemetype} || $basepinplan;
            $binfo->{processor} = $_revinfostash{processor}->{$schemeproc} || 'BCM2835';
            
            $_israspberry_3 = ( $schemetype >= 8  ) ? 1 : 0;
            
            %_boardinfo = %$binfo;
        } else {
            my $infokey = exists($_revstash{$rev}) ? $rev : $defaultkey;
            %_boardinfo = %{ $_revstash{$infokey} };
            $_boardinfo{processor} = 'BCM2835';
        }
        
    }    
   
    # Home Dir
    if( $_iswindows) {
        require Win32;
        $_homedir = Win32::GetFolderPath( 0x001C, 1);
        $_homedir = Win32::GetShortPathName( $_homedir );
        $_homedir =~ s/\\/\//g;
    } else {
        $_homedir = (getpwuid($<))[7];
    }
    
    unless( $_iswindows ) {
        $_boardinfo{hardware} = $hardware;
        $_boardinfo{serial} = $serial;
    }
    
    unless( -d $_homedir && -w $_homedir ) {
        croak qq(Unable to access home directory $_homedir);
    }
}

sub get_cpuinfo {
    # return a ref to the hash of values from /proc/cpuinfo.
    # %_cpuinfo is shared (threads::shared) data
    my %cpuinfo;
    {
        lock %_cpuinfostash;
        %cpuinfo = %_cpuinfostash;
    }
    return \%cpuinfo;
}

sub get_piboard_info {
    my %rval = %_boardinfo;
    return \%rval;
}

sub get_piboard_rev {
    return $_boardinfo{pinplan};
}

sub get_validpins {
    
    my $pirev = get_piboard_rev();
    
    if ( $pirev == 1 ) {
        return ( 0, 1, 4, 7, 8, 9, 10, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25 );
    } elsif ( $pirev == 2 ) {    
        return ( 2, 3, 4, 7, 8, 9, 10, 11, 14, 15, 17, 18, 22, 23, 24, 25, 27, 28, 29, 30, 31 );
    } else {
        # return current latest known pinset
        return ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27);
    }
}

1;

__END__
