/*
Simple proof-of-concept userspace tool to demonstrate the control of
WS2811-based LED-strips through the framebuffer of an iMX233.
(C) 2013 Jeroen Domburg (jeroen AT spritesmods.com)

This program is free software: you can redistribute it and/or modify
t under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
	    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
			    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
Compile like this:
arm-linux-gnueabi-gcc -static ledtst.c -lm -o ledtst
*/


#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <math.h>

#define NOSTRIPS 16 //Number of strips connected. For 16bpp, max is 16.
#define STRIPLEN 600 //in WS2811 pixels

//This will encode a framebuffer with per-led RGB values into the weird framebuffer
//data values we need to form the correct WS2811 driving waveform.
void encodeToFb(uint8_t *in, int striplen, uint16_t *fbmem) {
	int pix, col, bit, bitmask, strip, n;
	int p=0;
	for (pix=0; pix<STRIPLEN; pix++) { //STRIPLEN points...
		for (col=0; col<3; col++) { //...of 3 bytes (r, g. b)
			bitmask=0x80;
			for (bit=0; bit<8; bit++) { //...of 8 bits each.
				//At 4MHz, a bit has 5 cycles. For an 1, it's high 4, low one. For an 0, it's high one, low 4.
				fbmem[p++]=0xffff; //1st cycle always is one
				n=0;
				//Iterate through every LED-strip to fetch the bit it needs to send out. Combine those
				//in n.
				for (strip=0; strip<16; strip++) { 
					n>>=1;
					if (in[(striplen*strip*3)+(pix*3)+col]&bitmask) n|=0x8000;
				}
				fbmem[p++]=n;
				fbmem[p++]=n;	//Middle 3 are dependent on bit value
				fbmem[p++]=n;
				fbmem[p++]=0;	//Final always is 0
				bitmask>>=1; //next bit in byte
			}
		}
	}
}


//Helper function to set the value of a single pixel in the 'framebuffer'
//of LED pixel values.
void setPixel(char* rgbFb, int strip, int pixel, int r, int g, int b) {
	int pos=(strip*STRIPLEN*3)+pixel*3;
	if (strip<0 || strip>=NOSTRIPS) return;
	if (pixel<0 || pixel>=STRIPLEN) return;
	rgbFb[pos++]=g; //My strips have R and G switched.
	rgbFb[pos++]=r;
	rgbFb[pos++]=b;
}

//Generate fancy sinodial animation. This routine is the only one which assumes
//there's 4 strips of 15 pixels connected to the Olinuxino; the rest of the routines
//act like 16 strips of 600 pixels each are connected.
void doFancyAnimation(char *fb, int t) {
	int x, y, i;
	//Clear background
	int inv=(t&16);
	for (x=0; x<15; x++) {
		for (y=0; y<4; y++) {
			setPixel(fb, y, x, 0, 0, 0);
		}
	}
	//Make a nicely coloured sinus movement animation thingamajig
	for (x=0; x<16; x++) {
		int r=sin(x*.2+t*-.2)*127+128;
		int g=sin(x*.2+t*-.2+2)*127+128;
		int b=sin(x*.2+t*-.2+4)*127+128;

		float p=sin(x*.2+t*.1)*1.5+1.5;
		int pn=(int)p;
		//Ooooh, anti-aliasing :)
		float err=p-(float)pn;
		setPixel(fb, pn, x, r*(1.0-err), g*(1.0-err), b*(1.0-err));
		setPixel(fb, pn+1, x, r*err, g*err, b*err);
	}

}


int main() {
	uint8_t rgbFb[NOSTRIPS*STRIPLEN*3]={0}; //The 'framebuffer' of r,g,b values for each LED in the strips.
	int t=0, frames=0;
	int arg=0;
	int fb;
	time_t timer;
	uint16_t *fbmem;
	fb=open("/dev/fb0", O_RDWR);
	if (fb<0) {
		perror("opening fb");
		exit(1);
	}
	fbmem=mmap(NULL, STRIPLEN*5*24*2, PROT_READ|PROT_WRITE, MAP_SHARED, fb, 0);
	if (fbmem==NULL) {
		perror("mmap'ing fb");
		exit(1);
	}

	timer=time(NULL);
	while(1) {
		t++;
		doFancyAnimation(rgbFb, t);

		encodeToFb(rgbFb, STRIPLEN, fbmem);
		//ioctl(fb, FBIO_WAITFORVSYNC, &arg); //this doesn't seem to work, unfortunately...
		//usleep(1000000/25); //soo, if you want your cpu to be able to do other things too, limit the framerate by e.g. something like this.
		frames++;
		if (timer!=time(NULL)) {
			timer=time(NULL);
			printf("%d fps\n", frames);
			frames=0;
		}
	}
	exit(0);
}
