// intellectual property is bullshit. bgdc
//
// AVL's avR stuff
// Rev 2022-08

#ifndef AVLR_H
#define AVLR_H

#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <avr/io.h>

// --- PIN ACCESS MACROS -------------------------------------------------------
// These provide quick access to port pins. Port pins should be defined in a
// pin table header as 8-bit numbers, with the port offset in the high nibble
// and the pin number in the low nibble. ("Port offset" here is the number of
// (sizeof PORT_t) between &PORTA and &PORTn)
//
// Bear in mind that pins are not currently bounds checked. For best results,
// use PINDEF() instead of hard-coding.

#ifdef PORTA

/// Compute the pin value given a port and pin number (for example,
/// PINDEF(PORTB, 5)).
#define PINDEF(port, pin) (((&(port) - &PORTA) << 4) | ((pin) & 0xF))

/// Retrieve the PORT for a given pin.
#define PORT_FOR(npin) ((&PORTA)[((npin) & 0xF0) >> 4])

/// Retrieve the VPORT for a given pin.
#define VPORT_FOR(npin) ((&VPORTA)[((npin) & 0xF0) >> 4])

/// Retrieve the PINCTRL register for a given pin.
#define PINCTRL_FOR(npin) ( \
	(&PORT_FOR(npin).PIN0CTRL)[(npin) & 0x07] )

/// Return the bit for a given pin (1 << pinnumber)
#define BIT_FOR(npin) (1u << ((npin) & 0x07))

/// Return the inverted bitmask for a given pin
#define NBIT_FOR(npin) (uint8_t)(~(1u << ((npin) & 0x07)))

/// Set a pin low. Does not change drive.
#define PIN_CLR(npin) (VPORT_FOR(npin).OUT &= NBIT_FOR(npin))

/// Set a pin high. Does not change drive.
#define PIN_SET(npin) (VPORT_FOR(npin).OUT |= BIT_FOR(npin))

/// Set a pin's value
#define PIN_WRITE(npin, val) do { if (val) PIN_SET(npin); else PIN_CLR(npin); } while (0)

/// Return whether a pin is low.
#define PIN_IS_LOW(npin) (!(VPORT_FOR(npin).IN & BIT_FOR(npin)))

/// Return whether a pin is high.
#define PIN_IS_HIGH(npin) (!!(VPORT_FOR(npin).IN & BIT_FOR(npin)))

/// Set a pin driven.
#define PIN_DRIVE(npin) (VPORT_FOR(npin).DIR |= BIT_FOR(npin))

/// Set a pin un-driven.
#define PIN_RELEASE(npin) (VPORT_FOR(npin).DIR &= NBIT_FOR(npin))

/// Set a pin driven and give it a value
#define PIN_DRIVE_TO(npin, val) do { \
	PIN_WRITE(npin, val); \
	VPORT_FOR(npin).DIR |= BIT_FOR(npin); } while (0)

#endif // defined(PORTA)

#endif // !defined(AVLR_H)
