/* mchip control functions for picturebook 

   Tridge, July 2000

   based on earlier work by 
   Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
*/
/* 
   Copyright (C) Andrew Tridgell 2000
   
   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.
   
   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, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "capture.h"

#define NUM_PAGES 1024

void *mchip_base;
static int subsample=0;

static u8 *framebuf;

u32 readl(volatile void *addr)
{
	return *(volatile u32 *)(addr);
}

void writel(u32 v, void *addr)
{
	*(volatile u32 *)(addr) = v;
}

int mchip_hsize(void)
{
	return subsample ? 320 : 640;
}

int mchip_vsize(void)
{
	return subsample ? 240 : 480;
}

static void mchip_sync(int reg) /* wait until register is accessible */
{
	int i;
        int status;
	u32 mask = MCHIP_HIC_STATUS_MCC_RDY;

	if (reg <= 0x80) return;
	if (reg >= 0x100) mask = MCHIP_HIC_STATUS_VRJ_RDY;
	for (i = MCHIP_REG_TIMEOUT; 
	     i && !((status=readl(mchip_base+MCHIP_HIC_STATUS)) & mask);
	     i--) sdelay(1);
	if (!i) printf ("mchip_sync() TIMEOUT on reg 0x%x  status=0x%x\n", 
			reg, status);
}



static void mchip_set(int reg, u32 v)
{
	mchip_sync(reg);
	writel(v, mchip_base + reg);
}


static u32 mchip_read(int reg)
{
	mchip_sync(reg);
	return readl(mchip_base + reg);
}

/* wait for a register to become zero */
static int delay1(u32 reg)
{
	int n = 10;
	while (--n && mchip_read(reg)) sdelay(1);
	return n;
}

/* wait for a register to become a particular value */
static int delay2(u32 reg, u32 v)
{
	int n = 10;
	while (--n && mchip_read(reg) != v) sdelay(1);
	return n;
}



static int frame_num;

/* setup for DMA transfers - also zeros the framebuffer */
static void mchip_dma_setup(void)
{
	static u32 pt_addr;
	int i;
	if (!framebuf) {
		printf("dma setup starting ...\n");
		framebuf = ptable_init(NUM_PAGES, &pt_addr);
		printf("dma setup done\n");
	}

	mchip_set(MCHIP_MM_PT_ADDR, pt_addr);
	for (i=0;i<4;i++) {
		mchip_set(MCHIP_MM_FIR(i), 0);
	}
	memset(framebuf, 0, NUM_PAGES*PAGE_SIZE);
	frame_num = 0;
}

/* stop any existing HIC action and wait for any dma to complete then
   reset the dma engine */
void mchip_hic_stop(void)
{
	if (!(mchip_read(MCHIP_HIC_STATUS) & MCHIP_HIC_STATUS_BUSY)) return;
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_STOP);
	delay1(MCHIP_HIC_CMD);
	while (!delay2(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) {
		printf("resetting HIC\n");
		mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_STOP);
		delay1(MCHIP_HIC_CMD);
		mchip_set(MCHIP_HIC_CTL, MCHIP_HIC_CTL_SOFT_RESET);
		sdelay(250000);
	}
	sdelay(100000); 
	mchip_subsample(subsample);
	mchip_dma_setup();
}

/* set the framerate into the mchip */
void mchip_set_framerate(double framerate)
{
	int n;

	/* these are very approximate */
	if (subsample) {
		n = 30/framerate - 1;
	} else {
		n = 15/framerate - 1;
	}
	if (n <= 1) n = 0;
	mchip_set(MCHIP_HIC_S_RATE, n);
}


/* wait for the next frame to be ready from the dma engine */
static u32 mchip_wait_frame(void)
{
	int n = 20;
	while (--n) {
		int i;
		for (i=0;i<4;i++) {
			u32 v = mchip_read(MCHIP_MM_FIR((frame_num+i)%4));
			if (v & MCHIP_MM_FIR_RDY) {
				frame_num = (frame_num+i)%4;
				mchip_set(MCHIP_MM_FIR(frame_num), 0);
				frame_num = (frame_num+1)%4;
				return v;
			}
		}
		sdelay(1);
	}
	if (debug) {
		printf(__FUNCTION__ " timeout\n");
	}
	return 0;
}

/* read one frame from the framebuffer assuming it was captured using
   a uncompressed transfer */
static int mchip_cont_read_frame(u8 *buf, int size)
{
	u32 v;
	int pt_id;
	int avail;

	v = mchip_wait_frame();

	if (v == 0) return 0;

	pt_id = (v >> 17) & 0x3FF;
	avail = NUM_PAGES-pt_id;

	if (size > avail*PAGE_SIZE) {
		memcpy(buf, framebuf+pt_id*PAGE_SIZE, avail*PAGE_SIZE);
		memcpy(buf+avail*PAGE_SIZE,
		       framebuf,
		       size-avail*PAGE_SIZE);
	} else {
		memcpy(buf, framebuf+pt_id*PAGE_SIZE, size);
	}

	return size;
}

/* read a compressed frame from the framebuffer */
static int mchip_comp_read_frame(u8 *buf, int size)
{
	u32 v;
	int pt_start, pt_end, trailer;
	int fsize, fsize2;

 again:
	v = mchip_wait_frame();

	pt_start = (v >> 19) & 0xFF;
	pt_end = (v >> 11) & 0xFF;
	trailer = (v>>1) & 0x3FF;

	if (pt_end < pt_start) {
		fsize = (256-pt_start)*PAGE_SIZE;
		fsize2 = pt_end*PAGE_SIZE+trailer*4;
		if (fsize+fsize2 > size) {
			printf("oversized compressed frame %d %d\n", 
			       fsize, fsize2);
			goto again;
		} else {
			memcpy(buf, framebuf+pt_start*PAGE_SIZE, fsize);
			memcpy(buf+fsize, framebuf, fsize2); 
			fsize += fsize2;
		}
	} else {
		fsize = (pt_end-pt_start)*PAGE_SIZE+trailer*4;
		if (fsize > size) {
			printf("oversized compressed frame %d\n", fsize);
			goto again;
		} else {
			memcpy(buf, framebuf+pt_start*PAGE_SIZE, fsize);
		}
	}

	return fsize;
}

/* take a picture into SDRAM */
void mchip_take_picture(void)
{
	mchip_hic_stop();
	mchip_set (MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_CAP);
	mchip_set (MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);

	delay1(MCHIP_HIC_CMD);
	delay2(MCHIP_HIC_CMD, MCHIP_HIC_STATUS_IDLE);
}


/* dma a previously taken picture into a buffer */
void mchip_get_picture(u8 *buf, int bufsize)
{
	mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_OUT);
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);

	delay1(MCHIP_HIC_CMD);
	mchip_cont_read_frame(buf, bufsize);
	delay2(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE);
}


/* start continuous dma capture */
void mchip_continuous_start(void)
{
	mchip_hic_stop();

	mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_OUT);
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);

	delay1(MCHIP_HIC_CMD);
}

/* read one frame during a continuous capture */
int mchip_continuous_read(u8 *buf, int bufsize)
{
	return mchip_cont_read_frame(buf, bufsize);
}


/* load some huffman and quantisation tables into the VRJ chip ready
   for JPEG compression */
static void mchip_load_tables(void)
{
	int i;
	int size;
	u16 *tables;

	tables = jpeg_huffman_tables(&size);
	for (i=0;i<size/2;i++) {
		writel(tables[i], mchip_base + MCHIP_VRJ_TABLE_DATA);
	}
	tables = jpeg_quantisation_tables(&size, image_quality);
	for (i=0;i<size/2;i++) {
		writel(tables[i], mchip_base + MCHIP_VRJ_TABLE_DATA);
	}
}

/* setup the VRJ parameters in the chip */
static void mchip_vrj_setup(u8 mode)
{
	mchip_set(MCHIP_VRJ_BUS_MODE, 5);
	mchip_set(MCHIP_VRJ_SIGNAL_ACTIVE_LEVEL, 0x1f);
	mchip_set(MCHIP_VRJ_PDAT_USE, 1);
	mchip_set(MCHIP_VRJ_IRQ_FLAG, 0x20);
	mchip_set(MCHIP_VRJ_MODE_SPECIFY, mode);
	mchip_set(MCHIP_VRJ_NUM_LINES, mchip_vsize());
	mchip_set(MCHIP_VRJ_NUM_PIXELS, mchip_hsize());
	mchip_set(MCHIP_VRJ_NUM_COMPONENTS, 0x1b);
	mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_LO, 0xFFFF);
	mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_HI, 0xFFFF);
	mchip_set(MCHIP_VRJ_COMP_DATA_FORMAT, 0xC);
	mchip_set(MCHIP_VRJ_RESTART_INTERVAL, 0);
	mchip_set(MCHIP_VRJ_SOF1, 0x601);
	mchip_set(MCHIP_VRJ_SOF2, 0x1502);
	mchip_set(MCHIP_VRJ_SOF3, 0x1503);
	mchip_set(MCHIP_VRJ_SOF4, 0x1596);
	mchip_set(MCHIP_VRJ_SOS,  0x0ed0);

	mchip_load_tables();
}



/* compress one frame into a buffer */
int mchip_compress_frame(u8 *buf, int bufsize)
{
	int ret;

	mchip_vrj_setup(0x3f);
	sdelay(50);

	mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_COMP);
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);
	
	delay1(MCHIP_HIC_CMD);

	ret = mchip_comp_read_frame(buf, bufsize);

	delay2(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE);

	return ret;
}


/* uncompress one image into a buffer */
int mchip_uncompress_frame(u8 *img, int imgsize, u8 *buf, int bufsize)
{
	mchip_vrj_setup(0x3f);
	sdelay(50);

	mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_DECOMP);
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);
	
	delay1(MCHIP_HIC_CMD);

	return mchip_comp_read_frame(buf, bufsize);
}


/* start continuous compressed capture */
void mchip_cont_compression_start(void)
{
	mchip_hic_stop();
	mchip_vrj_setup(0x3f);
	mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_COMP);
	mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START);

	delay1(MCHIP_HIC_CMD);

	printf("continuous compressed capture started\n");
}

/* read one compressed frame from the framebuffer */
int mchip_cont_compression_read(u8 *buf, int bufsize)
{
	return mchip_comp_read_frame(buf, bufsize);
}

/* setup subsampling */
void mchip_subsample(int sub)
{
	if (debug) {
		printf(__FUNCTION__ " sub=%d\n", sub);
	}
	subsample = sub;
	mchip_set(MCHIP_MCC_R_SAMPLING, subsample);
	mchip_set(MCHIP_MCC_R_XRANGE, mchip_hsize());
	mchip_set(MCHIP_MCC_R_YRANGE, mchip_vsize());
	mchip_set(MCHIP_MCC_B_XRANGE, mchip_hsize());
	mchip_set(MCHIP_MCC_B_YRANGE, mchip_vsize());
	if (debug) {
		printf(__FUNCTION__ " done\n");
	}
	delay2(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE);
}

/* initialise the mchip */
void mchip_init(void)
{
      u32 mem;
      int error;
      u8 revision;
      u32 val;
      int fd;
      u8 irq;
      int mchip_dev;
      int mmap_size;

      mchip_dev = pci_find_device(PCI_VENDOR_ID_KAWASAKI, PCI_DEVICE_ID_KAWASAKI_MCHIP);
      if (mchip_dev == -1) {
	      printf("failed to find mchip\n");
	      exit(1);
      }

      pci_config_read_u32(mchip_dev, PCI_BASE_ADDRESS_0, &mem);

      if (mem == 0) {
	      mem = pci_read_base_address(PCI_VENDOR_ID_KAWASAKI, PCI_DEVICE_ID_KAWASAKI_MCHIP);
	      if (mem) printf("Got base address from devices\n");
      }

      if (mem == 0) {
	      printf("No device base address for mchip - exiting\n");
	      exit(1);
      }

      /* check if the chip is powered on */
      pci_config_read_u32(mchip_dev, MCHIP_PCI_POWER_CSR, &val);

      if ((val & 3) != 0) {
	      u32 intetc;
	      printf( "mchip_init: turning device on\n");
	      pci_config_read_u32(mchip_dev, PCI_INTERRUPT_LINE, &intetc);
	      pci_config_write_u32(mchip_dev, MCHIP_PCI_POWER_CSR, 0);
	      sdelay(100);
	      pci_config_write_u32(mchip_dev, PCI_BASE_ADDRESS_0, mem);
	      pci_config_write_u32(mchip_dev, PCI_INTERRUPT_LINE, intetc);
      }

      /* Read the revision byte. */
      error = pci_config_read_u8(mchip_dev, PCI_REVISION_ID, &revision);

      /* Enable response in PCI memory space and enable PCI bus mastering. */
      error = pci_config_write_u16(mchip_dev, PCI_COMMAND,
				   PCI_COMMAND_MEMORY|
				   PCI_COMMAND_MASTER|
				   PCI_COMMAND_INVALIDATE);

      pci_config_write_u8(mchip_dev, PCI_CACHE_LINE_SIZE, 8);
      pci_config_write_u8(mchip_dev, PCI_LATENCY_TIMER, 64);


      /* we don't want interrupts */
      pci_config_read_u8(mchip_dev, PCI_INTERRUPT_LINE, &irq);
      printf("mchip_init(): KL5A72002 rev. %d, base %p, irq %d\n", 
	      revision, (void *) mem, irq);

      fd = open("/dev/mem", O_RDWR|O_SYNC);
      if (fd == -1) {
	      perror("/dev/mem");
	      exit(1);
      }

      mmap_size = MCHIP_MM_REGS + (mem & (PAGE_SIZE-1));
      mchip_base = mmap(0, mmap_size,
			PROT_READ|PROT_WRITE, MAP_SHARED, fd, mem & ~(PAGE_SIZE-1));
      if (mem & (PAGE_SIZE-1)) {
	      mchip_base += (mem & (PAGE_SIZE-1));
      }
      close(fd);

      /* Ask the camera to perform a soft reset. */
      pci_config_write_u16(mchip_dev, MCHIP_PCI_SOFTRESET_SET, 1);

      delay1(MCHIP_HIC_CMD);
      delay2(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE);

      sdelay(10);
      mchip_set(MCHIP_VRJ_SOFT_RESET, 1);
      sdelay(10);
      mchip_set(MCHIP_MM_INTA, 0x0);

      sdelay(600); 

      mchip_set(MCHIP_MM_PCI_MODE, 5);

      mchip_dma_setup();

      close(mchip_dev);

      printf("mchip open\n");
}

void mchip_shutdown(void)
{
	mchip_hic_stop();
	mchip_set(MCHIP_MM_INTA, 0x0);
}

