/*
 *  Copyright (C) 2002-2007  The DOSBox Team
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <string.h>
#include "dosbox.h"
#include "mem.h"
#include "inout.h"
#include "dma.h"
#include "pic.h"
#include "paging.h"
#include "setup.h"

DmaController *DmaControllers[2];

#define EMM_PAGEFRAME4K	((0xE000*16)/4096)
Bit32u ems_board_mapping[LINK_START];

static void UpdateEMSMapping(void) {
	/* if EMS is not present, this will result in a 1:1 mapping */
	Bitu i;
	for (i=0;i<0x10;i++) {
		ems_board_mapping[EMM_PAGEFRAME4K+i]=paging.firstmb[EMM_PAGEFRAME4K+i];
	}
}

/* read a block from physical memory */
static void DMA_BlockRead(PhysPt pt,void * data,Bitu size) {
	Bit8u * write=(Bit8u *) data;
	for ( ; size ; size--, pt++) {
		Bitu page = pt >> 12;
		/* care for EMS pageframe etc. */
		if (page < EMM_PAGEFRAME4K) page = paging.firstmb[page];
		else if (page < EMM_PAGEFRAME4K+0x10) page = ems_board_mapping[page];
		else if (page < LINK_START) page = paging.firstmb[page];
		*write++=phys_readb(page*4096 + (pt & 4095));
	}
}

/* write a block into physical memory */
static void DMA_BlockWrite(PhysPt pt,void * data,Bitu size) {
	Bit8u * read=(Bit8u *) data;
	for ( ; size ; size--, pt++) {
		Bitu page = pt >> 12;
		/* care for EMS pageframe etc. */
		if (page < EMM_PAGEFRAME4K) page = paging.firstmb[page];
		else if (page < EMM_PAGEFRAME4K+0x10) page = ems_board_mapping[page];
		else if (page < LINK_START) page = paging.firstmb[page];
		phys_writeb(page*4096 + (pt & 4095), *read++);
	}
}

DmaChannel * GetDMAChannel(Bit8u chan) {
	if (chan<4) {
		/* channel on first DMA controller */
		if (DmaControllers[0]) return DmaControllers[0]->GetChannel(chan);
	} else if (chan<8) {
		/* channel on second DMA controller */
		if (DmaControllers[1]) return DmaControllers[1]->GetChannel(chan-4);
	}
	return NULL;
}

/* remove the second DMA controller (ports are removed automatically) */
void CloseSecondDMAController(void) {
	if (DmaControllers[1]) {
		delete DmaControllers[1];
		DmaControllers[1]=NULL;
	}
}

/* check availability of second DMA controller, needed for SB16 */
bool SecondDMAControllerAvailable(void) {
	if (DmaControllers[1]) return true;
	else return false;
}

static void DMA_Write_Port(Bitu port,Bitu val,Bitu iolen) {
	if (port<0x10) {
		/* write to the first DMA controller (channels 0-3) */
		DmaControllers[0]->WriteControllerReg(port,val,1);
	} else if (port>=0xc0 && port <=0xdf) {
		/* write to the second DMA controller (channels 4-7) */
		DmaControllers[1]->WriteControllerReg((port-0xc0) >> 1,val,1);
	} else {
		UpdateEMSMapping();
		switch (port) {
			/* write DMA page register */
			case 0x81:GetDMAChannel(2)->SetPage(val);break;
			case 0x82:GetDMAChannel(3)->SetPage(val);break;
			case 0x83:GetDMAChannel(1)->SetPage(val);break;
			case 0x89:GetDMAChannel(6)->SetPage(val);break;
			case 0x8a:GetDMAChannel(7)->SetPage(val);break;
			case 0x8b:GetDMAChannel(5)->SetPage(val);break;
		}
	}
}

static Bitu DMA_Read_Port(Bitu port,Bitu iolen) {
	if (port<0x10) {
		/* read from the first DMA controller (channels 0-3) */
		return DmaControllers[0]->ReadControllerReg(port,iolen);
	} else if (port>=0xc0 && port <=0xdf) {
		/* read from the second DMA controller (channels 4-7) */
		return DmaControllers[1]->ReadControllerReg((port-0xc0) >> 1,iolen);
	} else switch (port) {
		/* read DMA page register */
		case 0x81:return GetDMAChannel(2)->pagenum;
		case 0x82:return GetDMAChannel(3)->pagenum;
		case 0x83:return GetDMAChannel(1)->pagenum;
		case 0x89:return GetDMAChannel(6)->pagenum;
		case 0x8a:return GetDMAChannel(7)->pagenum;
		case 0x8b:return GetDMAChannel(5)->pagenum;
	}
	return 0;
}

void DmaController::WriteControllerReg(Bitu reg,Bitu val,Bitu len) {
	DmaChannel * chan;Bitu i;
	switch (reg) {
	/* set base address of DMA transfer (1st byte low part, 2nd byte high part) */
	case 0x0:case 0x2:case 0x4:case 0x6:
		UpdateEMSMapping();
		chan=GetChannel(reg >> 1);
		flipflop=!flipflop;
		if (flipflop) {
			chan->baseaddr=(chan->baseaddr&0xff00)|val;
			chan->curraddr=(chan->curraddr&0xff00)|val;
		} else {
			chan->baseaddr=(chan->baseaddr&0x00ff)|(val << 8);
			chan->curraddr=(chan->curraddr&0x00ff)|(val << 8);
		}
		break;
	/* set DMA transfer count (1st byte low part, 2nd byte high part) */
	case 0x1:case 0x3:case 0x5:case 0x7:
		UpdateEMSMapping();
		chan=GetChannel(reg >> 1);
		flipflop=!flipflop;
		if (flipflop) {
			chan->basecnt=(chan->basecnt&0xff00)|val;
			chan->currcnt=(chan->currcnt&0xff00)|val;
		} else {
			chan->basecnt=(chan->basecnt&0x00ff)|(val << 8);
			chan->currcnt=(chan->currcnt&0x00ff)|(val << 8);
		}
		break;
	case 0x8:		/* Comand reg not used */
		break;
	case 0x9:		/* Request registers, memory to memory */
		//TODO Warning?
		break;
	case 0xa:		/* Mask Register */
		if ((val & 0x4)==0) UpdateEMSMapping();
		chan=GetChannel(val & 3);
		chan->SetMask((val & 0x4)>0);
		break;
	case 0xb:		/* Mode Register */
		UpdateEMSMapping();
		chan=GetChannel(val & 3);
		chan->autoinit=(val & 0x10) > 0;
		chan->increment=(val & 0x20) > 0;
		//TODO Maybe other bits?
		break;
	case 0xc:		/* Clear Flip/Flip */
		flipflop=false;
		break;
	case 0xd:		/* Master Clear/Reset */
		for (i=0;i<4;i++) {
			chan=GetChannel(i);
			chan->SetMask(true);
			chan->tcount=false;
		}
		flipflop=false;
		break;
	case 0xe:		/* Clear Mask register */		
		UpdateEMSMapping();
		for (i=0;i<4;i++) {
			chan=GetChannel(i);
			chan->SetMask(false);
		}
		break;
	case 0xf:		/* Multiple Mask register */
		UpdateEMSMapping();
		for (i=0;i<4;i++) {
			chan=GetChannel(i);
			chan->SetMask(val & 1);
			val>>=1;
		}
		break;
	}
}

Bitu DmaController::ReadControllerReg(Bitu reg,Bitu len) {
	DmaChannel * chan;Bitu i,ret;
	switch (reg) {
	/* read base address of DMA transfer (1st byte low part, 2nd byte high part) */
	case 0x0:case 0x2:case 0x4:case 0x6:
		chan=GetChannel(reg >> 1);
		flipflop=!flipflop;
		if (flipflop) {
			return chan->curraddr & 0xff;
		} else {
			return (chan->curraddr >> 8) & 0xff;
		}
	/* read DMA transfer count (1st byte low part, 2nd byte high part) */
	case 0x1:case 0x3:case 0x5:case 0x7:
		chan=GetChannel(reg >> 1);
		flipflop=!flipflop;
		if (flipflop) {
			return chan->currcnt & 0xff;
		} else {
			return (chan->currcnt >> 8) & 0xff;
		}
	case 0x8:		/* Status Register */
		ret=0;
		for (i=0;i<4;i++) {
			chan=GetChannel(i);
			if (chan->tcount) ret|=1 << i;
			chan->tcount=false;
			if (chan->callback) ret|=1 << (i+4);
		}
		return ret;
	default:
		LOG(LOG_DMA,LOG_NORMAL)("Trying to read undefined DMA port %x",reg);
		break;
	}
	return 0xffffffff;
}

DmaChannel::DmaChannel(Bit8u num, bool dma16) {
	masked = true;
	callback = NULL;
	if(num == 4) return;
	channum = num;
	DMA16 = dma16 ? 0x1 : 0x0;
	pagenum = 0;
	pagebase = 0;
	baseaddr = 0;
	curraddr = 0;
	basecnt = 0;
	currcnt = 0;
	increment = true;
	autoinit = false;
	tcount = false;
}

Bitu DmaChannel::Read(Bitu want, Bit8u * buffer) {
	Bitu done=0;
again:
	Bitu left=(currcnt+1);
	if (want<left) {
		DMA_BlockRead(pagebase+(curraddr << DMA16),buffer,want << DMA16);
		done+=want;
		curraddr+=want;
		currcnt-=want;
	} else {
		DMA_BlockRead(pagebase+(curraddr << DMA16),buffer,left << DMA16);
		buffer+=left << DMA16;
		want-=left;
		done+=left;
		ReachedTC();
		if (autoinit) {
			currcnt=basecnt;
			curraddr=baseaddr;
			if (want) goto again;
			UpdateEMSMapping();
		} else {
			curraddr+=left;
			currcnt=0xffff;
			masked=true;
			UpdateEMSMapping();
			DoCallBack(DMA_TRANSFEREND);
		}
	}
	return done;
}

Bitu DmaChannel::Write(Bitu want, Bit8u * buffer) {
	Bitu done=0;
again:
	Bitu left=(currcnt+1);
	if (want<left) {
		DMA_BlockWrite(pagebase+(curraddr << DMA16),buffer,want << DMA16);
		done+=want;
		curraddr+=want;
		currcnt-=want;
	} else {
		DMA_BlockWrite(pagebase+(curraddr << DMA16),buffer,left << DMA16);
		buffer+=left << DMA16;
		want-=left;
		done+=left;
		ReachedTC();
		if (autoinit) {
			currcnt=basecnt;
			curraddr=baseaddr;
			if (want) goto again;
			UpdateEMSMapping();
		} else {
			curraddr+=left;
			currcnt=0xffff;
			masked=true;
			UpdateEMSMapping();
			DoCallBack(DMA_TRANSFEREND);
		}
	}
	return done;
}

class DMA:public Module_base{
public:
	DMA(Section* configuration):Module_base(configuration){
		Bitu i;
		DmaControllers[0] = new DmaController(0);
		if (machine==MCH_VGA) DmaControllers[1] = new DmaController(1);
	
		for (i=0;i<0x10;i++) {
			Bitu mask=IO_MB;
			if (i<8) mask|=IO_MW;
			/* install handler for first DMA controller ports */
			DmaControllers[0]->DMA_WriteHandler[i].Install(i,DMA_Write_Port,mask);
			DmaControllers[0]->DMA_ReadHandler[i].Install(i,DMA_Read_Port,mask);
			if (machine==MCH_VGA) {
				/* install handler for second DMA controller ports */
				DmaControllers[1]->DMA_WriteHandler[i].Install(0xc0+i*2,DMA_Write_Port,mask);
				DmaControllers[1]->DMA_ReadHandler[i].Install(0xc0+i*2,DMA_Read_Port,mask);
			}
		}
		/* install handlers for ports 0x81-0x83 (on the first DMA controller) */
		DmaControllers[0]->DMA_WriteHandler[0x10].Install(0x81,DMA_Write_Port,IO_MB,3);
		DmaControllers[0]->DMA_ReadHandler[0x10].Install(0x81,DMA_Read_Port,IO_MB,3);

		if (machine==MCH_VGA) {
			/* install handlers for ports 0x81-0x83 (on the second DMA controller) */
			DmaControllers[1]->DMA_WriteHandler[0x10].Install(0x89,DMA_Write_Port,IO_MB,3);
			DmaControllers[1]->DMA_ReadHandler[0x10].Install(0x89,DMA_Read_Port,IO_MB,3);
		}
	}
	~DMA(){
		if (DmaControllers[0]) {
			delete DmaControllers[0];
			DmaControllers[0]=NULL;
		}
		if (DmaControllers[1]) {
			delete DmaControllers[1];
			DmaControllers[1]=NULL;
		}
	}
};

static DMA* test;

void DMA_Destroy(Section* sec){
	delete test;
}
void DMA_Init(Section* sec) {
	test = new DMA(sec);
	sec->AddDestroyFunction(&DMA_Destroy);
	Bitu i;
	for (i=0;i<LINK_START;i++) {
		ems_board_mapping[i]=i;
	}
}
