///////////////////////////////////////////////////////////////////////////////////////
// File          bcm2835/BCM2385.xs
// Description:  XS module for HiPi::Device::BCM2385
// Created       Fri Nov 23 12:13:43 2012
// SVN Id        $Id:$
// Copyright:    Copyright (c) 2012 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 2 of the License, or any later 
//               version.
///////////////////////////////////////////////////////////////////////////////////////

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "mylib/include/ppport.h"
#define BCM2835_NO_DELAY_COMPATIBILITY
#include "BCM2835/src/src/bcm2835.h"

#define XSRPI_INT_FALL    0x01
#define XSRPI_INT_RISE    0x02
#define XSRPI_INT_AFALL   0x04
#define XSRPI_INT_ARISE   0x08
#define XSRPI_INT_HIGH    0x10
#define XSRPI_INT_LOW     0x20

/*----------------------------------------------------------------
 * C CODE SECTION
 *
 * why replace base bcm2835 sections?
 * because I wanted to be able to specify
 * different BSC peripherals for I2C
 *----------------------------------------------------------------*/

void hipi_i2c_setSlaveAddress(volatile uint32_t* baseaddress, uint8_t addr );
void hipi_i2c_setClockDivider( volatile uint32_t* baseaddress, uint16_t divider );
uint8_t hipi_i2c_write( volatile uint32_t* baseaddress, const char * buf, uint32_t len );
uint8_t hipi_i2c_read( volatile uint32_t* baseaddress, char* buf, uint32_t len );
uint8_t hipi_i2c_read_register_rs( volatile uint32_t* baseaddress, char* regaddr, char* readbuf, uint32_t len );


void hipi_i2c_setSlaveAddress(volatile uint32_t* baseaddress, uint8_t addr )
{
   	volatile uint32_t* paddr = baseaddress + BCM2835_BSC_A/4;
	bcm2835_peri_write(paddr, addr);
}

void hipi_i2c_setClockDivider( volatile uint32_t* baseaddress, uint16_t divider )
{
    volatile uint32_t* paddr = baseaddress + BCM2835_BSC_DIV/4;
    bcm2835_peri_write(paddr, divider);
}

uint8_t hipi_i2c_write( volatile uint32_t* baseaddress, const char* buf, uint32_t len )
{
    volatile uint32_t* dlen    = baseaddress + BCM2835_BSC_DLEN/4;
    volatile uint32_t* fifo    = baseaddress + BCM2835_BSC_FIFO/4;
    volatile uint32_t* status  = baseaddress + BCM2835_BSC_S/4;
    volatile uint32_t* control = baseaddress + BCM2835_BSC_C/4;
    volatile uint32_t* divaddr = baseaddress + BCM2835_BSC_DIV/4;
    
    uint16_t divider = bcm2835_peri_read(divaddr);
    int i2c_byte_wait_us = ((float)divider / BCM2835_CORE_CLK_HZ) * 1000000 * 9;

    uint32_t remaining = len;
    uint32_t i = 0;
    uint8_t reason = BCM2835_I2C_REASON_OK;

    // Clear FIFO
    bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 );
    // Clear Status
	bcm2835_peri_write_nb(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE);
	// Set Data Length
    bcm2835_peri_write_nb(dlen, len);
    // pre populate FIFO with up to 10 bytes ( an arbitrary num less than 16 )
    while( remaining && ( i < 10 ) )
    {
        bcm2835_peri_write_nb(fifo, buf[i]);
        i++;
        remaining--;
    }
    
    // Enable device and start transfer
    bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST);
    
    // wait for transaction to start
    while ( !( bcm2835_peri_read(status) & BCM2835_BSC_S_TA ) )
    {
        bcm2835_delayMicroseconds(10);
    }
    
    // write remaining data
    while (bcm2835_peri_read(status) & BCM2835_BSC_S_TA )
    {
    	while ((bcm2835_peri_read(status) & BCM2835_BSC_S_TXD) && remaining)
    	{
        	// Write to FIFO, no barrier
        	bcm2835_peri_write_nb(fifo, buf[i]);
        	i++;
        	remaining--;
    	}
    	// wait
    	bcm2835_delayMicroseconds(i2c_byte_wait_us);
    }

    // Received a NACK
    if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR)
    {
		reason = BCM2835_I2C_REASON_ERROR_NACK;
    }

    // Received Clock Stretch Timeout
    else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT)
    {
		reason = BCM2835_I2C_REASON_ERROR_CLKT;
    }

    // Not all data is sent
    else if (remaining)
    {
		reason = BCM2835_I2C_REASON_ERROR_DATA;
    }

    bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE);

    return reason;
}


uint8_t hipi_i2c_read( volatile uint32_t* baseaddress, char* buf, uint32_t len )
{
    volatile uint32_t* dlen    = baseaddress + BCM2835_BSC_DLEN/4;
    volatile uint32_t* fifo    = baseaddress + BCM2835_BSC_FIFO/4;
    volatile uint32_t* status  = baseaddress + BCM2835_BSC_S/4;
    volatile uint32_t* control = baseaddress + BCM2835_BSC_C/4;
    volatile uint32_t* divaddr = baseaddress + BCM2835_BSC_DIV/4;
    
    uint16_t divider = bcm2835_peri_read(divaddr);
    int i2c_byte_wait_us = ((float)divider / BCM2835_CORE_CLK_HZ) * 1000000 * 9;

    uint32_t remaining = len;
    uint32_t i = 0;
    uint8_t reason = BCM2835_I2C_REASON_OK;
    
    // Clear FIFO
    bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 );
    // Clear Status
	bcm2835_peri_write_nb(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE);
	// Set Data Length
    bcm2835_peri_write_nb(dlen, len);
    // Start read
    bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST | BCM2835_BSC_C_READ);
    
    // poll for transfer has started
    while ( !( bcm2835_peri_read(status) & BCM2835_BSC_S_TA ) )
    {
        bcm2835_delayMicroseconds(10);
    }    
    
    // empty FIFO as it is populated
    while (bcm2835_peri_read(status) & BCM2835_BSC_S_TA)
    {
    	while (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD)
    	{
    		// Read from FIFO, no barrier
    		buf[i] = bcm2835_peri_read_nb(fifo);
        	i++;
        	remaining--;
    	}
    	// When remaining data is to be received, then wait for a data in FIFO
        
    	bcm2835_delayMicroseconds(i2c_byte_wait_us);
    }
    
    // transfer has finished - grab any remaining stuff in FIFO
    while (remaining && (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD))
    {
        // Read from FIFO, no barrier
        buf[i] = bcm2835_peri_read_nb(fifo);
        i++;
        remaining--;
    }
    
    // Received a NACK
    if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR)
    {
		reason = BCM2835_I2C_REASON_ERROR_NACK;
    }

    // Received Clock Stretch Timeout
    else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT)
    {
		reason = BCM2835_I2C_REASON_ERROR_CLKT;
    }

    // Not all data is received
    else if (remaining)
    {
		reason = BCM2835_I2C_REASON_ERROR_DATA;
    }

    bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE);

    return reason;
}


uint8_t hipi_i2c_read_register_rs( volatile uint32_t* baseaddress, char* regaddr, char* readbuf, uint32_t len )
{   
    volatile uint32_t* dlen    = baseaddress + BCM2835_BSC_DLEN/4;
    volatile uint32_t* fifo    = baseaddress + BCM2835_BSC_FIFO/4;
    volatile uint32_t* status  = baseaddress + BCM2835_BSC_S/4;
    volatile uint32_t* control = baseaddress + BCM2835_BSC_C/4;
    volatile uint32_t* divaddr = baseaddress + BCM2835_BSC_DIV/4;
    
    uint16_t divider = bcm2835_peri_read(divaddr);
    int i2c_byte_wait_us = ((float)divider / BCM2835_CORE_CLK_HZ) * 1000000 * 9;
    int i = 0;
    
    uint32_t remaining = len;
    uint8_t reason = BCM2835_I2C_REASON_OK;
    
    // Clear FIFO
    bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 );
    // Clear Status
	bcm2835_peri_write_nb(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE);
	// Set Data Length
    bcm2835_peri_write_nb(dlen, 1);
    // Enable device and start transfer
    bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN);
    bcm2835_peri_write_nb(fifo, regaddr[0]);
    bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST);
    
    // poll for transfer has started
    while ( !( bcm2835_peri_read(status) & BCM2835_BSC_S_TA ) )
    {
        bcm2835_delayMicroseconds(10);
    }
    
    // Send a start-read
    bcm2835_peri_write_nb(dlen, len);
    bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST  | BCM2835_BSC_C_READ );
    
    // wait for write and first byte back
    
    bcm2835_delayMicroseconds(i2c_byte_wait_us * 3);
    
    // empty FIFO as it is populated
    while (bcm2835_peri_read(status) & BCM2835_BSC_S_TA)
    {
    	while (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD)
    	{
    		// Read from FIFO, no barrier
    		readbuf[i] = bcm2835_peri_read_nb(fifo);
        	i++;
        	remaining--;
    	}
    	// When remaining data is to be received, then wait for a data in FIFO
    	bcm2835_delayMicroseconds(i2c_byte_wait_us);
    }
    
    // transfer has finished - grab any remaining stuff in FIFO
    while (remaining && (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD))
    {
        // Read from FIFO, no barrier
        readbuf[i] = bcm2835_peri_read_nb(fifo);
        i++;
        remaining--;
    }
    
    // Received a NACK
    if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR)
    {
		reason = BCM2835_I2C_REASON_ERROR_NACK;
    }

    // Received Clock Stretch Timeout
    else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT)
    {
		reason = BCM2835_I2C_REASON_ERROR_CLKT;
    }

    // Not all data is sent
    else if (remaining)
    {
		reason = BCM2835_I2C_REASON_ERROR_DATA;
    }

    bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE);

    return reason;
}


/*----------------------------------------------
 * Perl Section
 *----------------------------------------------*/


MODULE = HiPi::BCM2835  PACKAGE = HiPi::BCM2835

#
# Address Pointers
#

uint32_t
bcm2835_gpio()
  CODE:
    RETVAL = (uint32_t)bcm2835_gpio;
  OUTPUT: RETVAL

uint32_t
bcm2835_pwm()
  CODE:
    RETVAL = (uint32_t)bcm2835_pwm;
  OUTPUT: RETVAL

uint32_t
bcm2835_clk()
  CODE:
    RETVAL = (uint32_t)bcm2835_clk;
  OUTPUT: RETVAL

uint32_t
bcm2835_pads()
  CODE:
    RETVAL = (uint32_t)bcm2835_pads;
  OUTPUT: RETVAL

uint32_t
bcm2835_spi0()
  CODE:
    RETVAL = (uint32_t)bcm2835_spi0;
  OUTPUT: RETVAL

uint32_t
bcm2835_bsc0()
  CODE:
    RETVAL = (uint32_t)bcm2835_bsc0;
  OUTPUT: RETVAL

uint32_t
bcm2835_bsc1()
  CODE:
    RETVAL = (uint32_t)bcm2835_bsc1;
  OUTPUT: RETVAL

uint32_t
bcm2835_st()
  CODE:
    RETVAL = (uint32_t)bcm2835_st;
  OUTPUT: RETVAL

#
# Custom Functions
#

uint8_t
hipi_gpio_fget( pin )
    uint8_t pin
  CODE:
    volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10);
    uint8_t  shift  = (pin % 10) * 3;
    uint32_t mask   = BCM2835_GPIO_FSEL_MASK << shift;
    uint32_t result = bcm2835_peri_read(paddr) & mask;
    RETVAL = result >> shift;
  OUTPUT: RETVAL


int
hipi_gpio_get_eds( pin )
    uint8_t pin
  CODE:
    volatile uint32_t* paddr;
    uint8_t shift;
    uint32_t output;
    uint32_t mask;
    RETVAL = 0;

    /* REN */
    paddr = bcm2835_gpio + BCM2835_GPREN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_RISE;
    }

    /* FEN */
    paddr = bcm2835_gpio + BCM2835_GPFEN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_FALL;
    }
    
    /* HEN */
    paddr = bcm2835_gpio + BCM2835_GPHEN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_HIGH;
    }
    
    /* LEN */
    paddr = bcm2835_gpio + BCM2835_GPLEN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_LOW;
    }
    
    /* AFEN */
    paddr = bcm2835_gpio + BCM2835_GPAFEN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_AFALL;
    }

    /* AREN */
    paddr = bcm2835_gpio + BCM2835_GPAREN0/4 + pin/32;
    shift = pin % 32;
    mask  = 1 << shift;
    output = bcm2835_peri_read(paddr) & mask;
    if( (output >> shift) == 1 ) {
        RETVAL += XSRPI_INT_ARISE;
    }
  
  OUTPUT: RETVAL


void
_hipi_i2c_setSlaveAddress( baseaddress, addr )
    volatile uint32_t* baseaddress
    uint8_t addr
  CODE:
	hipi_i2c_setSlaveAddress( baseaddress, addr );


void
_hipi_i2c_setClockDivider( baseaddress, divider )
    volatile uint32_t* baseaddress
    uint16_t divider
  CODE:
    hipi_i2c_setClockDivider( baseaddress, divider );


uint8_t
_hipi_i2c_write( baseaddress, buf, len )
    volatile uint32_t* baseaddress
    const char* buf
    uint32_t len
  CODE:
    RETVAL = hipi_i2c_write( baseaddress, buf, len );
  OUTPUT: RETVAL


uint8_t
_hipi_i2c_read( baseaddress, buf, len )
    volatile uint32_t* baseaddress
    char* buf
    uint32_t len
  CODE:
    RETVAL = hipi_i2c_read( baseaddress, buf, len );
  OUTPUT: RETVAL

uint8_t
_hipi_i2c_read_register( baseaddress, regaddr, readbuf, len )
    volatile uint32_t* baseaddress
    char* regaddr
    char* readbuf
    uint32_t len
  CODE:
    uint8_t reason = hipi_i2c_write( baseaddress, regaddr, 1 );
    if( reason )
    {
        RETVAL = reason;
    }
    else
    {
        RETVAL = hipi_i2c_read( baseaddress, readbuf, len );
    }
  OUTPUT: RETVAL

uint8_t
_hipi_i2c_read_register_rs( baseaddress, regaddr, readbuf, len )
    volatile uint32_t* baseaddress
    char* regaddr
    char* readbuf
    uint32_t len
  CODE:
    RETVAL = hipi_i2c_read_register_rs( baseaddress, regaddr, readbuf, len );
  OUTPUT: RETVAL

#
# Init
#

int
_hipi_bcm2835_init()
  CODE:
    RETVAL = bcm2835_init();
  OUTPUT: RETVAL

int
_hipi_bcm2835_close()
  CODE:
    RETVAL = bcm2835_close();
  OUTPUT: RETVAL

void
bcm2835_set_debug(uint8_t debug)

#
# Low level register access
#

uint32_t 
bcm2835_peri_read(volatile uint32_t* paddr)

uint32_t 
bcm2835_peri_read_nb(volatile uint32_t* paddr)

void 
bcm2835_peri_write(volatile uint32_t* paddr, uint32_t value)

void 
bcm2835_peri_write_nb(volatile uint32_t* paddr, uint32_t value)

void 
bcm2835_peri_set_bits(volatile uint32_t* paddr, uint32_t value, uint32_t mask)

#
# GPIO register access
#

void 
bcm2835_gpio_fsel(uint8_t pin, uint8_t mode)

void 
bcm2835_gpio_set(uint8_t pin);

void 
bcm2835_gpio_clr(uint8_t pin)

void
bcm2835_gpio_set_multi(uint32_t mask)

void
bcm2835_gpio_clr_multi(uint32_t mask)

uint8_t 
bcm2835_gpio_lev(uint8_t pin)

uint8_t 
bcm2835_gpio_eds(uint8_t pin)

void 
bcm2835_gpio_set_eds(uint8_t pin)

void
bcm2835_gpio_ren(uint8_t pin)

void
bcm2835_gpio_clr_ren(uint8_t pin)

void
bcm2835_gpio_fen(uint8_t pin)

void
bcm2835_gpio_clr_fen(uint8_t pin)

void 
bcm2835_gpio_hen(uint8_t pin)

void 
bcm2835_gpio_clr_hen(uint8_t pin)

void 
bcm2835_gpio_len(uint8_t pin)

void 
bcm2835_gpio_clr_len(uint8_t pin)

void 
bcm2835_gpio_aren(uint8_t pin)

void 
bcm2835_gpio_clr_aren(uint8_t pin)

void 
bcm2835_gpio_afen(uint8_t pin)

void 
bcm2835_gpio_clr_afen(uint8_t pin)

void 
bcm2835_gpio_pud(uint8_t pud)

void 
bcm2835_gpio_pudclk(uint8_t pin, uint8_t on)

uint32_t 
bcm2835_gpio_pad(uint8_t group)

void 
bcm2835_gpio_set_pad(uint8_t group, uint32_t control)

void 
bcm2835_delay(unsigned int millis)

void
bcm2835_delayMicroseconds(uint64_t micros)

void
bcm2835_gpio_write(uint8_t pin, uint8_t on)

void
bcm2835_gpio_write_multi(uint32_t mask, uint8_t on)

void
bcm2835_gpio_write_mask(uint32_t value, uint32_t mask)

void
bcm2835_gpio_set_pud(uint8_t pin, uint8_t pud)

void 
bcm2835_spi_begin()

void 
bcm2835_spi_end()

void
bcm2835_spi_setBitOrder(uint8_t order)

void 
bcm2835_spi_setClockDivider(uint16_t divider)

void 
bcm2835_spi_setDataMode(uint8_t mode)

void 
bcm2835_spi_chipSelect(uint8_t cs)

void 
bcm2835_spi_setChipSelectPolarity(uint8_t cs, uint8_t active)

uint8_t 
bcm2835_spi_transfer(uint8_t value)

void
hipi_spi_transfern( tbuf )
    SV* tbuf
  PPCODE:
    SV* rbuf = newSVsv(tbuf);
    bcm2835_spi_transfern( SvPVX(rbuf), (uint32_t)SvCUR(rbuf) );
    
    EXTEND(SP, 1);
    PUSHs(sv_2mortal(rbuf));


void
bcm2835_spi_transfern(char* buf, short length(buf))

void
hipi_spi_transfernb( tbuf )
    SV* tbuf
  PPCODE:
    SV* rbuf = newSVsv(tbuf);
    bcm2835_spi_transfernb( SvPVX(tbuf), SvPVX(rbuf), (uint32_t)SvCUR(tbuf) );
    
    EXTEND(SP, 1);
    PUSHs(sv_2mortal(rbuf));
    

void
bcm2835_spi_transfernb(char* tbuf, char* rbuf, short length(tbuf))

void
hipi_spi_writenb( buf )
    SV* buf
  PPCODE:
    SV* rbuf = sv_2mortal(newSVsv(buf));
    bcm2835_spi_writenb( SvPVX(rbuf), (uint32_t)SvCUR(rbuf) );
    
    EXTEND(SP, 1);
    PUSHs(rbuf);

void
bcm2835_spi_writenb(char* buf, short length(buf))

void
bcm2835_i2c_begin()

void
bcm2835_i2c_end()

void
bcm2835_i2c_setSlaveAddress(uint8_t addr)

void
bcm2835_i2c_setClockDivider(uint16_t divider)

uint8_t
bcm2835_i2c_write(const char * buf,  short length(buf));

uint8_t
bcm2835_i2c_read( char* buf, uint32_t len )

uint64_t
bcm2835_st_read()

void
bcm2835_st_delay(uint64_t offset_micros, uint64_t micros)

void
bcm2835_i2c_set_baudrate(uint32_t baudrate)

uint8_t
bcm2835_i2c_read_register_rs(char* regaddr, char* buf, uint32_t len)


