/**
 * This file contains the headers and definitions for the AHCI driver.
 */

#ifndef AHCI_H
#define AHCI_H

#include <drv/pci.h>

/* The size of the PRDT in every AHCI command list */
#define AHCI_PRDT_SIZE	8

/* Max number of commands per port */
#define AHCI_COMMAND_LIST_SIZE 32

/* Max number of ports per controller */
#define AHCI_NUM_PORTS 32

/* Size of AHCI data structures in memory */
#define AHCI_SIZE \
	((sizeof(AHCI_COMMAND_HEADER) * AHCI_COMMAND_LIST_SIZE + \
	sizeof(AHCI_FIS) + \
	sizeof(AHCI_COMMAND_TABLE) * AHCI_COMMAND_LIST_SIZE + \
	sizeof(AHCI_PRDT_ENTRY) * AHCI_PRDT_SIZE) * \
	AHCI_NUM_PORTS)

/* The start of AHCI data structures in memory */
#define AHCI_START (PAGE_ALIGN_ADDR(CACHE_CACHE_START - AHCI_SIZE))

/* Interrupt status register */
#define PxIS_DHRS	0x1
#define PxIS_PSS	0x2
#define PxIS_DSS	0x4
#define PxIS_SDBS	0x8
#define PxIS_UFS	0x10
#define PxIS_DPS	0x20
#define PxIS_PCS	0x40
#define PxIS_DMPS	0x80
#define PxIS_PRCS	0x400000
#define PxIS_IPMS	0x800000
#define PxIS_OFS	0x1000000
#define PxIS_INFS	0x4000000
#define PxIS_IFS	0x8000000
#define PxIS_HBDS	0x10000000
#define PxIS_HBFS	0x20000000
#define PxIS_TFES	0x40000000
#define PxIS_CPDS	0x80000000

/* Interrupt Enable Register */
#define PxIE_DHRE	0x1
#define PxIE_PSE	0x2
#define PxIE_DSE	0x4
#define PxIE_SDBE	0x8
#define PxIE_UFE	0x10
#define PxIE_DPE	0x20
#define PxIE_PCE	0x40
#define PxIE_DMPE	0x80
#define PxIE_PRCE	0x400000
#define PxIE_IPME	0x800000
#define PxIE_OFE	0x1000000
#define PxIE_INFE	0x4000000
#define PxIE_IFE	0x8000000
#define PxIE_HBDE	0x10000000
#define PxIE_HBFE	0x20000000
#define PxIE_TFEE	0x40000000
#define PxIE_CPDE	0x80000000

/* HBA port command register */
#define PxCMD_ST 	0x1
#define PxCMD_SUD	0x2
#define PxCMD_POD	0x4
#define PxCMD_CLO	0x8
#define PxCMD_FRE	0x10
#define PxCMD_CCS	0x1F00
#define PxCMD_MPSS	0x2000
#define PxCMD_FR	0x4000
#define PxCMD_CR	0x8000
#define PxCMD_CPS	0x10000
#define PxCMD_PMA	0x20000
#define PxCMD_HPCP	0x40000
#define PxCMD_MPSP	0x80000
#define PxCMD_CPD	0x100000
#define PxCMD_ESP	0x200000
#define PxCMD_FBSCP	0x400000
#define PxCMD_APSTE	0x800000
#define PxCMD_ATAPI	0x1000000
#define	PxCMD_DLAE	0x2000000
#define PxCMD_ALPE	0x4000000
#define PxCMD_ASP	0x8000000
#define PxCMD_ICC	0xF000000

/* Capability Register Layout */
#define CAP_NP		0x1F
#define CAP_SXS		0x20
#define CAP_EMS		0x40
#define CAP_CCCS	0x80
#define CAP_NCS		0x1F00
#define CAP_PSC		0x2000
#define CAP_SSC		0x4000
#define CAP_PMD		0x8000
#define CAP_FBSS	0x10000
#define CAP_SPM		0x20000
#define CAP_SAM		0x40000
#define CAP_ISS		0xF00000
#define CAP_SCLO	0x1000000
#define CAP_SAL		0x2000000
#define CAP_SALP	0x4000000
#define CAP_SSS		0x8000000
#define CAP_SMPS	0x10000000
#define CAP_SSNTF	0x20000000
#define CAP_SNCQ	0x40000000
#define CAP_S64A	0x80000000

/* Capability register 2 layout */
#define CAP2_BOH	0x1
#define CAP2_NVMP	0x2
#define CAP2_APST	0x4
#define CAP2_SDS	0x8
#define CAP2_SADM	0x10
#define CAP2_DESO	0x20

/* BOHC register layout */
#define BOHC_BOS	0x1
#define BOHC_OOS	0x2
#define BOHC_SOOE	0x4
#define BOHC_OOC	0x8
#define BOHC_BB		0x10

/* Global host control */
#define GHC_HR		0x1
#define GHC_IE		0x2
#define GHC_MRSM	0x4
#define GHC_AE		0x80000000

/* SATA status register */
#define PxSSTS_DET	0xF
	#define PxSSTS_DET_NODEV_NOPHY 0x0
	#define PxSSTS_DET_DEV_NOPHY 0x1
	#define PxSSTS_DET_DEV_PHY 0x3
	#define PxSSTS_DET_PHY_OFF 0x4

#define PxSSTS_SPD	0xF0
	#define PxSSTS_SP_GEN1 0x1
	#define PxSSTS_SP_GEN2 0x2
	#define PxSSTS_SP_GEN3 0x3

#define PxSSTS_IPM	0xF00
	#define PxSSTS_PM_ACTIVE 0x1
	#define PxSSTS_PM_PARTIAL 0x2
	#define PxSSTS_PM_SLUMBER 0x6
	#define PxSSTS_PM_DEVSLEEP 0x8

/* SATA control */
#define PxSCTL_DET	0xF
#define PxSCTL_SDP	0xF0
#define PxSCTL_IPM	0xF00

/* SATA device signatures */
#define SATA_SIG_ATA	0x00000101
#define SATA_SIG_ATAPI	0xEB140101
#define SATA_SIG_SEMB	0xC33C0101
#define SATA_SIG_PM		0x96690101

#define ATA_CMD_READ_DMA		0xC8
#define ATA_CMD_READ_DMA_EX 	0x25
#define ATA_CMD_WRITE_DMA		0xCA
#define ATA_CMD_WRITE_DMA_EX 	0x35
#define ATA_CMD_IDENTIFY_PIO	0xEC
#define ATA_CMD_IDENTIFY_DMA	0xEE

#define PxTFD_STS_BSY	0x80
#define PxTFD_STS_DRQ	0x08

/**
 * Frame information structures from ATA
 */
typedef enum ahci_fis_type
{
	HOST_TO_DEVICE = 0x27,
	DEVICE_TO_HOST = 0x34,
	DMA_ACTIVATE = 0x39,
	DMA_SETUP = 0x41,
	DATA = 0x46,
	BIST = 0x58,
	PIO_SETUP = 0x5F,
	DEVICE_BITS = 0xA1
} AHCI_FIS_TYPE;

/**
 * This struct describes a host-to-device FIS. Used by the host to send commands to the AHCI controller.
 */
typedef struct ahci_fis_h2d
{
	// DWORD 0
	uint8_t  fis_type;	// FIS_TYPE_REG_H2D

	uint8_t  pmport:4;	// Port multiplier
	uint8_t  rsv0:3;	// Reserved
	uint8_t  c:1;		// 1: Command, 0: Control

	uint8_t  command;	// Command register
	uint8_t  featurel;	// Feature register, 7:0
	
	// DWORD 1
	uint8_t  lba0;		// LBA low register, 7:0
	uint8_t  lba1;		// LBA mid register, 15:8
	uint8_t  lba2;		// LBA high register, 23:16
	uint8_t  device;	// Device register

	// DWORD 2
	uint8_t  lba3;		// LBA register, 31:24
	uint8_t  lba4;		// LBA register, 39:32
	uint8_t  lba5;		// LBA register, 47:40
	uint8_t  featureh;	// Feature register, 15:8

	// DWORD 3
	uint8_t  countl;	// Count register, 7:0
	uint8_t  counth;	// Count register, 15:8
	uint8_t  icc;		// Isochronous command completion
	uint8_t  control;	// Control register

	// DWORD 4
	uint8_t  rsv1[4];	// Reserved
} AHCI_FIS_H2D;

/**
 * This struct describes device-to-host FIS. Used by the AHCI controller to send info to the host.
 */
typedef struct ahci_fis_d2h
{
	// DWORD 0
	uint8_t  fis_type;    // FIS_TYPE_REG_D2H

	uint8_t  pmport:4;    // Port multiplier
	uint8_t  rsv0:2;      // Reserved
	uint8_t  i:1;         // Interrupt bit
	uint8_t  rsv1:1;      // Reserved

	uint8_t  status;      // Status register
	uint8_t  error;       // Error register
	
	// DWORD 1
	uint8_t  lba0;        // LBA low register, 7:0
	uint8_t  lba1;        // LBA mid register, 15:8
	uint8_t  lba2;        // LBA high register, 23:16
	uint8_t  device;      // Device register

	// DWORD 2
	uint8_t  lba3;        // LBA register, 31:24
	uint8_t  lba4;        // LBA register, 39:32
	uint8_t  lba5;        // LBA register, 47:40
	uint8_t  rsv2;        // Reserved

	// DWORD 3
	uint8_t  countl;      // Count register, 7:0
	uint8_t  counth;      // Count register, 15:8
	uint8_t  rsv3[2];     // Reserved

	// DWORD 4
	uint8_t  rsv4[4];     // Reserved
} AHCI_FIS_D2H;

/**
 * This struct describes a data FIS.
 */
typedef struct ahci_fis_data
{
	// DWORD 0
	uint8_t  fis_type;	// FIS_TYPE_DATA

	uint8_t  pmport:4;	// Port multiplier
	uint8_t  rsv0:4;		// Reserved

	uint8_t  rsv1[2];	// Reserved

	// DWORD 1 ~ N
	uint32_t data[1];	// Payload
} AHCI_FIS_DATA;

/**
 * Describes the "set device bits" FIS
 * TODO: When you figure out what this does, fill it in.
 */
typedef struct ahci_fis_dev_bits
{
	uint16_t poopoo_doodoo;
} AHCI_FIS_DEV_BITS;

/**
 * This struct describes a PIO setup FIS. Used by the AHCI controller to notify the host it's about to send or ready to receive a PIO payload.
 */
typedef struct ahci_fis_pio_setup
{
	// DWORD 0
	uint8_t  fis_type;	// FIS_TYPE_PIO_SETUP

	uint8_t  pmport:4;	// Port multiplier
	uint8_t  rsv0:1;	// Reserved
	uint8_t  d:1;		// Data transfer direction, 1 - device to host
	uint8_t  i:1;		// Interrupt bit
	uint8_t  rsv1:1;

	uint8_t  status;	// Status register
	uint8_t  error;		// Error register

	// DWORD 1
	uint8_t  lba0;		// LBA low register, 7:0
	uint8_t  lba1;		// LBA mid register, 15:8
	uint8_t  lba2;		// LBA high register, 23:16
	uint8_t  device;	// Device register

	// DWORD 2
	uint8_t  lba3;		// LBA register, 31:24
	uint8_t  lba4;		// LBA register, 39:32
	uint8_t  lba5;		// LBA register, 47:40
	uint8_t  rsv2;		// Reserved

	// DWORD 3
	uint8_t  countl;	// Count register, 7:0
	uint8_t  counth;	// Count register, 15:8
	uint8_t  rsv3;		// Reserved
	uint8_t  e_status;	// New value of status register

	// DWORD 4
	uint16_t tc;		// Transfer count
	uint8_t  rsv4[2];	// Reserved
} AHCI_FIS_PIO_SETUP;

/**
 * This struct describes a DMA setup FIS.
 */
typedef struct ahci_fis_dma_setup
{
	// DWORD 0
	uint8_t  fis_type;	// FIS_TYPE_DMA_SETUP

	uint8_t  pmport:4;	// Port multiplier
	uint8_t  rsv0:1;	// Reserved
	uint8_t  d:1;		// Data transfer direction, 1 - device to host
	uint8_t  i:1;		// Interrupt bit
	uint8_t  a:1;       // Auto-activate. Specifies if DMA Activate FIS is needed

    int8_t  rsved[2];       // Reserved

	//DWORD 1&2
	
	/** 
	 * DMA Buffer Identifier. Used to Identify DMA buffer in host memory.
	 * SATA Spec says host specific and not in Spec. Trying AHCI spec might work.
	 */
	uint64_t DMAbufferID;    

	//DWORD 3
	uint32_t rsvd;           //More reserved

	//DWORD 4
	uint32_t DMAbufOffset;   //Byte offset into buffer. First 2 bits must be 0

	//DWORD 5
	uint32_t TransferCount;  //Number of bytes to transfer. Bit 0 must be 0

	//DWORD 6
	uint32_t resvd;          //Reserved
	
} AHCI_FIS_DMA_SETUP;

typedef volatile struct ahci_hba_port
{
	uint32_t clb;		// 0x00, command list base address, 1K-byte aligned
	uint32_t clbu;		// 0x04, command list base address upper 32 bits
	uint32_t fb;		// 0x08, FIS base address, 256-byte aligned
	uint32_t fbu;		// 0x0C, FIS base address upper 32 bits
	uint32_t is;		// 0x10, interrupt status
	uint32_t ie;		// 0x14, interrupt enable
	uint32_t cmd;		// 0x18, command and status
	uint32_t rsv0;		// 0x1C, Reserved
	uint32_t tfd;		// 0x20, task file data
	uint32_t sig;		// 0x24, signature
	uint32_t ssts;		// 0x28, SATA status (SCR0:SStatus)
	uint32_t sctl;		// 0x2C, SATA control (SCR2:SControl)
	uint32_t serr;		// 0x30, SATA error (SCR1:SError)
	uint32_t sact;		// 0x34, SATA active (SCR3:SActive)
	uint32_t ci;		// 0x38, command issue
	uint32_t sntf;		// 0x3C, SATA notification (SCR4:SNotification)
	uint32_t fbs;		// 0x40, FIS-based switch control
	uint32_t rsv1[11];	// 0x44 ~ 0x6F, Reserved
	uint32_t vendor[4];	// 0x70 ~ 0x7F, vendor specific
} AHCI_HBA_PORT;

/**
 * This struct describes the host bus adapter memory registers.
 */
typedef volatile struct ahci_hba_memory
{
	// 0x00 - 0x2B, Generic Host Control
	uint32_t cap;		// 0x00, Host capability
	uint32_t ghc;		// 0x04, Global host control
	uint32_t is;		// 0x08, Interrupt status
	uint32_t pi;		// 0x0C, Port implemented
	uint32_t vs;		// 0x10, Version
	uint32_t ccc_ctl;	// 0x14, Command completion coalescing control
	uint32_t ccc_pts;	// 0x18, Command completion coalescing ports
	uint32_t em_loc;	// 0x1C, Enclosure management location
	uint32_t em_ctl;	// 0x20, Enclosure management control
	uint32_t cap2;		// 0x24, Host capabilities extended
	uint32_t bohc;		// 0x28, BIOS/OS handoff control and status

	// 0x2C - 0x9F, Reserved
	uint8_t  rsv[0xA0-0x2C];

	// 0xA0 - 0xFF, Vendor specific registers
	uint8_t  vendor[0x100-0xA0];

	// 0x100 - 0x10FF, Port control registers
	AHCI_HBA_PORT	ports[AHCI_NUM_PORTS];	// 1 ~ 32
} AHCI_HBA_MEMORY;

/**
 * This structure defines the "received FIS". When an AHCI controller sends an FIS to the host,
 * it does so by copying it into a location in memory. This is what that memory location looks like.
 */
typedef volatile struct ahci_fis
{
	// 0x00
	AHCI_FIS_DMA_SETUP	dsfis;		// DMA Setup FIS
	uint8_t         pad0[4];

	// 0x20
	AHCI_FIS_PIO_SETUP	psfis;		// PIO Setup FIS
	uint8_t         pad1[12];

	// 0x40
	AHCI_FIS_D2H		rfis;		// Register – Device to Host FIS
	uint8_t         pad2[4];

	// 0x58
	AHCI_FIS_DEV_BITS	sdbfis;		// Set Device Bit FIS
	
	// 0x60
	uint8_t         ufis[64];

	// 0xA0
	uint8_t   	rsv[0x100-0xA0];
} AHCI_FIS;

/**
 * an AHCI command header. This is used to describe the commands that will be sent.
 * the PRDTL is used to describe where in physical memory data should be copied to.3
 */
typedef struct ahci_command_header
{
	// DW0
	uint8_t  cfl:5;		// Command FIS length in DWORDS, 2 ~ 16
	uint8_t  a:1;		// ATAPI
	uint8_t  w:1;		// Write, 1: H2D, 0: D2H
	uint8_t  p:1;		// Prefetchable

	uint8_t  r:1;		// Reset
	uint8_t  b:1;		// BIST
	uint8_t  c:1;		// Clear busy upon R_OK
	uint8_t  rsv0:1;	// Reserved
	uint8_t  pmp:4;		// Port multiplier port

	uint16_t prdtl;		// Physical region descriptor table length in entries

	// DW1
	volatile
	uint32_t prdbc;		// Physical region descriptor byte count transferred

	// DW2, 3
	uint32_t ctba;		// Command table descriptor base address
	uint32_t ctbau;		// Command table descriptor base address upper 32 bits

	// DW4 - 7
	uint32_t rsv1[4];	// Reserved
} AHCI_COMMAND_HEADER;

/**
 * Used by AHCI to specify where in memory bytes from the device should be copied.
 */
typedef struct ahci_prdt_entry
{
	uint32_t dba;		// Data base address
	uint32_t dbau;		// Data base address upper 32 bits
	uint32_t rsv0;		// Reserved

	// DW3
	uint32_t dbc:22;		// Byte count, 4M max
	uint32_t rsv1:9;		// Reserved
	uint32_t i:1;		// Interrupt on completion
} AHCI_PRDT_ENTRY;

/**
 * Used by AHCI for queuing commands
 */
typedef struct ahci_command_table
{
	// 0x00
	uint8_t  cfis[64];	// Command FIS

	// 0x40
	uint8_t  acmd[16];	// ATAPI command, 12 or 16 bytes

	// 0x50
	uint8_t  rsv[48];	// Reserved

	// 0x80
	AHCI_PRDT_ENTRY	prdt_entry[AHCI_PRDT_SIZE];	// Physical region descriptor table entries, 0 ~ 65535
} AHCI_COMMAND_TABLE;

/**
 * Initialize AHCI structures.
 */
void ahci_init();

/**
 * Initialize the AHCI controller given by pdev.
 *
 * params:
 *		pdev - PCI device for the AHCI controller.
 */
void ahci_device_init(PCI_DEVICE *pdev);

#endif /* AHCI_H */