/*
 * spiio.c - Very simple SPI I/O example
 *
 * Written 2011 by Werner Almesberger
 * Copyright 2011 Werner Almesberger
 *
 * This program 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
 * (at your option) any later version.
 */


#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>


enum {
	VDD_OFF		= 1 << 2,	/* VDD disable, PD02 */
	MMC_CMD		= 1 << 8,	/* CMD, PD08 */
	MMC_CLK		= 1 << 9,	/* CLK, PD09 */
	MMC_DAT0	= 1 << 10,	/* DAT0, PD10 */
	MMC_DAT1	= 1 << 11,	/* DAT1, PD11 */
	MMC_DAT2	= 1 << 12,	/* DAT2, PD12 */
	MMC_DAT3	= 1 << 13,	/* DAT3/CD, PD13 */
};

#define	MOSI	MMC_CMD
#define	MISO	MMC_DAT0
#define	SCLK	MMC_DAT2
#define	nSEL	MMC_DAT3


#define SOC_BASE	0x10000000

#define	REG(n)	(*(volatile uint32_t *) (mem+(n)))

#define GPIO(n)	REG(0x10000+(n))

#define	PDPIN	GPIO(0x300)	/* port D pin level */
#define PDDATS	GPIO(0x314)	/* port D data set */
#define PDDATC	GPIO(0x318)	/* port D data clear */
#define PDFUNS	GPIO(0x344)	/* port D function set */
#define PDFUNC	GPIO(0x348)	/* port D function clear */
#define PDDIRS	GPIO(0x364)	/* port D direction set */
#define PDDIRC	GPIO(0x368)	/* port D direction clear */

#define	PAGE_SIZE	4096


static void *mem;


/* ----- Low-level SPI operations ------------------------------------------ */


static void spi_begin(void)
{
	PDDATC = nSEL;
}


static void spi_end(void)
{
	PDDATS = nSEL;
}


static void spi_send(uint8_t v)
{
	uint8_t mask;

	for (mask = 0x80; mask; mask >>= 1) {
		if (v & mask)
			PDDATS = MOSI;
		else
			PDDATC = MOSI;
		PDDATS = SCLK;
		PDDATC = SCLK;
	}
}


static uint8_t spi_recv(void)
{
	uint8_t res = 0;
        uint8_t mask;

	for (mask = 0x80; mask; mask >>= 1) {
		if (PDPIN & MISO)
			res |= mask;
		PDDATS = SCLK;
		PDDATC = SCLK;
	}
        return res;
}


static uint8_t spi_bidir(uint8_t v)
{
	uint8_t res = 0;
	uint8_t mask;

	for (mask = 0x80; mask; mask >>= 1) {
		if (PDPIN & MISO)
			res |= mask;
		if (v & mask)
			PDDATS = MOSI;
		else
			PDDATC = MOSI;
		PDDATS = SCLK;
		PDDATC = SCLK;
	}
        return res;
}


/* ----  High-level -------------------------------------------------------- */


static void spi_io(const char *s)
{
	const char *p;
	uint8_t ch;

	spi_begin();
	for (p = s; *p; p++) {
		ch = spi_bidir(*s);
		if (!ch)
			continue;
		if (strchr("\t\n\r", ch) || (ch >= ' ' && ch <= '~'))
			putchar(ch);
		else
			putchar('?');
	}
	spi_end();
	putchar('\n');
}


int main(int argc, char **argv)
{
	int fd, i;

	fd = open("/dev/mem", O_RDWR | O_SYNC);
	if (fd < 0) {
		perror("/dev/mem");
		exit(1);
	}
	mem = mmap(NULL, PAGE_SIZE*3*16, PROT_READ | PROT_WRITE,
	    MAP_SHARED, fd, SOC_BASE);
        if (mem == MAP_FAILED) {
                perror("mmap");
                exit(1);
        }

	/* set the output levels */
	PDDATS = nSEL | VDD_OFF;
	PDDATC = SCLK;

	PDFUNC = MOSI | MISO | SCLK | nSEL;

	/* set the pin directions */
	PDDIRC = MISO;
	PDDIRS = MOSI | SCLK | nSEL;

	for (i = 1; i != argc; i++)
		spi_io(argv[i]);

	/* make all MMC pins inputs */
	PDDIRC = MOSI | MISO | SCLK | nSEL;

	return 0;
}