//////////////////////////////////////////////////////////////////////////////////
//
// This file is part of the Next186 Soc PC project
// http://opencores.org/project,next186
//
// Filename: KB_8042_usb.v
// Description: Part of the Next186 SoC PC project, keyboard/mouse USB controller
//		Simplified 8042 implementation
// Version 1.0
// Creation date: Mar2019
//
// Author: Nicolae Dumitrache 
// e-mail: ndumitrache@opencores.org
//
/////////////////////////////////////////////////////////////////////////////////
// 
// Copyright (C) 2019 Nicolae Dumitrache
// 
// This source file may be used and distributed without 
// restriction provided that this copyright statement is not 
// removed from the file and that any derivative work contains 
// the original copyright notice and the associated disclaimer.
// 
// This source file is free software; you can redistribute it 
// and/or modify it under the terms of the GNU Lesser General 
// Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any 
// later version. 
// 
// This source 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 Lesser General Public License for more 
// details. 
// 
// You should have received a copy of the GNU Lesser General 
// Public License along with this source; if not, download it 
// from http://www.opencores.org/lgpl.shtml 
// 
///////////////////////////////////////////////////////////////////////////////////
// Additional Comments: 
//
// http://www.computer-engineering.org/ps2keyboard/
// http://wiki.osdev.org/%228042%22_PS/2_Controller
// http://wiki.osdev.org/Mouse_Input
//
//////////////////////////////////////////////////////////////////////////////////
// 12Mar2019 - Low speed USB interface - hot plug, hot sawp
//////////////////////////////////////////////////////////////////////////////////
`timescale 1ns / 1ps

module KB_Mouse_8042_usb(
    input CS,
	 input WR,
    input cmd,			// 0x60 = data, 0x64 = cmd
	 input [7:0]din,
	 output [7:0]dout, 
	 input clk,			// cpu CLK
	 input clk_usb,
	 output I_KB,		// interrupt keyboard
	 output I_MOUSE,  // interrupt mouse
	 output reg CPU_RST = 0,
	 inout USB0_DM, 
	 inout USB0_DP,
	 inout USB1_DM, 
	 inout USB1_DP
    );
	 
//	status bit5 = MOBF (mouse to host buffer full - with OBF), bit4=INH, bit2(1-initialized ok), bit1(IBF-input buffer full - host to kb/mouse), bit0(OBF-output buffer full - kb/mouse to host)
	reg [3:0]cmdbyte = 4'b1100; // EN2,EN,INT2,INT
	reg wcfg = 1'b0;	// write config byte
	reg next_mouse = 1'b0;
	reg ctl_outb = 1'b0;
	reg wr_mouse = 1'b0;
	reg wr_kb = 1'b0;
	reg rd_kb = 1'b0;
	reg rd_mouse = 1'b0;
	reg OBF = 1'b0;
	reg MOBF = 1'b0;
	reg [7:0]s_data;
	reg [7:0]wr_data;

	wire kb_data_out_ready;
	wire kb_data_in_ready;
	wire mouse_data_out_ready;
	wire mouse_data_in_ready;
	wire kb_data_ready;
	wire mouse_data_ready;
	wire IBF = ((wr_kb | ~kb_data_in_ready) & ~cmdbyte[2]) | ((wr_mouse  | ~mouse_data_in_ready) & ~cmdbyte[3]);
	
	wire [7:0]DO;
	wire [15:0]ADDR;
	wire WR8080;
	wire MREQ;
	wire IORQ;
	wire M1;
	reg [7:0]INP = 0;
	reg INT = 1'b0;
	reg [25:0]rstcount = 0;

	reg [7:0]InPort;
   	wire [7:0] DI;
	wire [7:0]usbdout0;
	wire [7:0]usbdout1;
	reg [15:0]mscount = 0;
	
	wire [7:0]mouse_data;
	wire [7:0]ps2_mouse_data;
	wire mouse_send_full;
	reg ps2_mouse_flush_data;

	wire [7:0]kb_data;
	wire [7:0]ps2_kb_data;
	wire kb_send_full;
	reg ps2_kb_flush_data;
	
	assign dout = cmd ? {2'b00, MOBF, 1'b1, wcfg, 1'b1, IBF, OBF | MOBF | ctl_outb} : ctl_outb ? {2'b00, cmdbyte[3:2], 2'b00, cmdbyte[1:0]} : s_data; //MOBF ? mouse_data : kb_data;
	assign I_KB = cmdbyte[0] & OBF; 			// INT & OBF
	assign I_MOUSE = cmdbyte[1] & MOBF; 	// INT2 & MOBF
	
	Next8080 N8080CPU (
		.DI(IORQ ? (M1 ? 8'hcf : InPort) : DI), // RST 8h
		.DO(DO), 
		.ADDR(ADDR), 
		.WR(WR8080), 
		.MREQ(MREQ),  
		.IORQ(IORQ), 
		.HALT(), 
		.CLK(clk_usb), 
		.RESET(!rstcount[25]), 
		.INT(INT), 
		.WAIT(1'b0),
		.M1(M1)
    );

	blk_mem_usb RAM2KB 
	(
		.Clock(!clk_usb),
		.ClockEn(MREQ),
		.Reset(1'b0),
		.WE(WR8080), 
		.Address(ADDR[10:0]), 
		.Data(DO), 
		.Q(DI)
	); 


	USB_phy USB0
	(
		.CLK48Mhz(clk_usb),
		.clk(clk_usb),
		.com(ADDR[0]),
		.wr(IORQ & WR8080 & ADDR[5]),
		.rd(IORQ & !WR8080 & !M1 & ADDR[5]),
		.din(DO),
		.dout(usbdout0),
		.DM(USB0_DM),
		.DP(USB0_DP)
	);


	USB_phy USB1
	(
		.CLK48Mhz(clk_usb),
		.clk(clk_usb),
		.com(ADDR[0]),
		.wr(IORQ & WR8080 & ADDR[6]),
		.rd(IORQ & !WR8080 & !M1 & ADDR[6]),
		.din(DO),
		.dout(usbdout1),
		.DM(USB1_DM),
		.DP(USB1_DP)
	);
	
	ps2_dev_proxy ps2_dev_mouse
	(
		.clk(clk_usb),
		.clk_ps2(clk),
		.din(DO),
		.wr(IORQ & WR8080 & ADDR[1]),
		.rd(IORQ & !WR8080 & !M1 & ADDR[1]),
		.flush(ps2_mouse_flush_data),
		.ps2din(wr_data),
		.ps2wr(wr_mouse),
		.ps2rd(rd_mouse),
		
		.ps2dout(ps2_mouse_data),
		.dout(mouse_data),
		.sfull(mouse_send_full),
		.doutready(mouse_data_ready),
		.ps2doutready(mouse_data_out_ready),
		.cmdready(mouse_data_in_ready)
    );	

	ps2_dev_proxy ps2_dev_kb
	(
		.clk(clk_usb),
		.clk_ps2(clk),
		.din(DO),
		.wr(IORQ & WR8080 & ADDR[3]),
		.rd(IORQ & !WR8080 & !M1 & ADDR[3]),
		.flush(ps2_kb_flush_data),
		.ps2din(wr_data),
		.ps2wr(wr_kb),
		.ps2rd(rd_kb),
		
		.ps2dout(ps2_kb_data),
		.dout(kb_data),
		.sfull(kb_send_full),
		.doutready(kb_data_ready),
		.ps2doutready(kb_data_out_ready),
		.cmdready(kb_data_in_ready)
    );
	
	always @(negedge clk_usb) InPort <= ADDR[6] ? usbdout1 : ADDR[5] ? usbdout0 : ADDR[3] ? kb_data : ADDR[1] ? mouse_data : ADDR[0] ? INP : mscount[15:8];
	
	always @(posedge clk_usb) begin
		rstcount <= rstcount + !rstcount[25];
			
		if(mscount == 16'hbb7f) begin
			mscount <= 16'h0000;
			INT <= 1'b1;
		end else mscount <= mscount + 1'b1;
		
		INP <= {4'b0000, kb_send_full, kb_data_ready, mouse_send_full, mouse_data_ready};
		if(IORQ & M1) INT <= 1'b0;
		if(IORQ & WR8080) begin
			if(ADDR[0] & !ADDR[5] & !ADDR[6]) {ps2_kb_flush_data, ps2_mouse_flush_data} <= DO[1:0];
		end
	end

	
	always @(posedge clk) begin
		CPU_RST <= 0;
		if(wr_kb & kb_data_in_ready) wr_kb <= 1'b0;
		if(rd_kb & kb_data_out_ready) rd_kb <= 1'b0;
		if(wr_mouse & mouse_data_in_ready) wr_mouse <= 1'b0;
		if(rd_mouse & mouse_data_out_ready) rd_mouse <= 1'b0;

		if(~OBF & ~MOBF)
			if(kb_data_out_ready & ~rd_kb & ~cmdbyte[2]) begin
				OBF <= 1'b1;
				s_data <= ps2_kb_data;
			end else if(mouse_data_out_ready & ~rd_mouse & ~cmdbyte[3]) begin
				MOBF <= 1'b1;
				s_data <= ps2_mouse_data;
			end
		
		if(CS) 
			if(WR)
				if(cmd)	// 0x64 write
					case(din)
						8'h20: ctl_outb <= 1'b1;	// read config byte
						8'h60: wcfg <= 1;			// write config byte
						8'ha7: cmdbyte[3] <= 1;	// disable mouse
						8'ha8: cmdbyte[3] <= 0;	// enable mouse
						8'had: cmdbyte[2] <= 1;	// disable kb
						8'hae: cmdbyte[2] <= 0;	// enable kb
						8'hd4: next_mouse <= 1;	//	write next byte to mouse
						/*8'hf0, 8'hf2, 8'hf4, 8'hf6, 8'hf8, 8'hfa, 8'hfc,*/ 8'hfe: CPU_RST <= 1; // CPU reset
					endcase 
				else begin	// 0x60 write
					if(wcfg) cmdbyte <= {din[5:4], din[1:0]};
					else begin
						next_mouse <= 0;
						wr_mouse <= next_mouse;
						wr_kb <= ~next_mouse;
						wr_data <= din;
					end
					wcfg <= 0;
				end
			else 	// read data
				if(~cmd) begin	
					ctl_outb <= 1'b0;
					if(!ctl_outb) begin
						OBF <= 1'b0;
						MOBF <= 1'b0;
						rd_kb <= OBF;
						rd_mouse <= MOBF;
					end
				end
	end
endmodule


module ps2_dev_proxy(
		input clk,	
		input clk_ps2,				
		input [7:0]din,	
		input wr,	 				// write <din> to send buffer
		input rd,					// read dout
		input flush,				// flush din, sfull will become 0
		input [7:0]ps2din,
		input ps2wr,
		input ps2rd,				
		
		output [7:0]ps2dout,
		output sfull,				// send buffer full
		output [7:0]dout,			// data out
		output doutready,			// <dout> ready
		output ps2doutready,		// ps2dout ready
		output cmdready
    );

	reg [1:0]rflush = 2'b00;
	wire cmdfull;	// PS2
	wire cmdempty;	// CPU
	wire sempty;	// PS2

	assign doutready = !cmdempty;
	assign ps2doutready = !sempty;
	assign cmdready = !cmdfull;

	always @(posedge clk_ps2)
		rflush <= {rflush[0], flush};


	q1 fifo_i // command queue (from host)
	(
		.wr_clk(clk_ps2),	// PS2 side
		.wr_en(ps2wr),
		.din(ps2din),
		.full(cmdfull),
		
		.rd_clk(clk),	// CPU side
		.rd_en(rd),
		.dout(dout),
		.empty(cmdempty)
	);
	
	q1 fifo_o  // data queue (to host)
	(
		.wr_clk(clk),	// CPU side
		.wr_en(wr),
		.din(din),
		.full(sfull),
		
		.rd_clk(clk_ps2),	// PS2 side
		.rd_en(ps2rd || rflush == 2'b01),
		.dout(ps2dout),
		.empty(sempty)
	);
	
endmodule
