#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

#include <gpiod.h>

#define CLIENT_NAME "pin stimulation PoC"

#define MAKE128CONST(hi,lo) ((((__uint128_t)hi << 64) | lo))


// 128bit value, split into two 64bit values for portability
// should be transmitted LSB first
static uint64_t override_pattern_upper = 0b0001100110111100000011101010001011100011110111011010111111101001ULL;
static uint64_t override_pattern_lower = 0b1000011010000101001011011001010101100010000010011111001110010010ULL;

// mapping to sdcard pinout
// PS_CLK - DAT3
// PS_DATA - DAT2

struct gpiod_chip *chip;
struct gpiod_line *ps_clk;
struct gpiod_line *ps_data;

// note - all uses of this function should be right after each other in case the device makes some assumptions about the clock behaving like a clock
void send_bit(bool bit)
{
	gpiod_line_set_value(ps_clk, 0);
	usleep(10 / 2); // let's try 50kHz, so 20us period, so 10us between level changes
	gpiod_line_set_value(ps_data, bit & 0b1);
	usleep(10 / 2);
	gpiod_line_set_value(ps_clk, 1);
	usleep(10);
}

// hard to say from the spec how exactly this is supposed to go, but should be close enough
// note: allegedly gpiod_line_release returns the GPIOs to a "default" state, which on a sane system
// should be input; ideally you should make sure that this is the case for the particular GPIOs you're
// using
// note to note: it doesn't seem the pins are always returned to default state, so just in case
// re-claim them as inputs
void set_pins_to_highz(void)
{
	gpiod_line_release(ps_data);
	gpiod_line_request_input(ps_data, CLIENT_NAME);
	gpiod_line_release(ps_data);
	gpiod_line_set_value(ps_clk, 0);
	usleep(10);
	gpiod_line_set_value(ps_clk, 1);
	usleep(10);
	gpiod_line_set_value(ps_clk, 0);
	gpiod_line_release(ps_clk);
	gpiod_line_request_input(ps_clk, CLIENT_NAME);
	gpiod_line_release(ps_clk);
	usleep(10);
}

// activation codes - should be transmitted LSB first

// this is reserved in IEEE1149.7, and for the purposes of NIDnT
// (or NIDnT + something implementing IEEE1149.7) it basically means "NIDnT"
// (this is the only recognized activation code if you have just NIDnT)
const uint8_t ACTIVATION_CODE_EXTENDED = 0xe;

// activation codes defined by IEEE1149.7 (they are ignored by NIDnT):
// 0b0XXX - TAP.7 stuff
// 0b1000 - Slimbus
// 0b1001 - USB
// 0b1010 - ARM (do they know ARM is a company...?)

void send_ps_sequence(uint8_t extended_code)
{
	// stage 1 - send a minimum of 8 1s
	for(int i = 0; i < 8; i++)
		send_bit(1);

	// stage 2 - 128bit override pattern
	for(int i = 0; i < 64; i++) {
		send_bit((override_pattern_lower >> i) & 0b1);
	}
	for(int i = 0; i < 64; i++) {
		send_bit((override_pattern_upper >> i) & 0b1);
	}

	// stage 3 - four 0s for padding
	for(int i = 0; i < 8; i++)
		send_bit(0);

	// stage 4 - 4bit activation code
	for(int i = 0; i < 4; i++) {
		send_bit((ACTIVATION_CODE_EXTENDED >> i) & 0b1);
	}

	// stage 5 - 4bit extended code
	for(int i = 0; i < 4; i++) {
		send_bit((extended_code >> i) & 0b1);
	}

	// we're done here
	set_pins_to_highz();
}

int main(int argc, char **argv)
{
	if(argc < 3 + 1 || argc > 3 + 1) {
		printf("usage: %s [/dev/gpiochipX] [pin stimulation clock pin number] [pin stimulation data pin number]\n", argv[0]);
		return 1;
	}

	chip = gpiod_chip_open(argv[1]);
	if(!chip)
		return 2;

	ps_clk = gpiod_chip_get_line(chip, atoi(argv[2]));
	if(!ps_clk)
		return 3;

	ps_data = gpiod_chip_get_line(chip, atoi(argv[3]));
	if(!ps_data)
		return 4;

	gpiod_line_request_output(ps_clk, CLIENT_NAME, 0);
	gpiod_line_request_output(ps_data, CLIENT_NAME, 0);

	send_ps_sequence(3);

	return 0;
}
