#ifndef _SCSI_H_
#define _SCSI_H_

struct scsi_bus_t;
struct atapi_device_t;
struct podule_t;

typedef struct scsi_device_t
{
	void *(*init)(struct scsi_bus_t *bus, int id, struct podule_t *podule, const struct podule_callbacks_t *callbacks);
	void *(*atapi_init)(struct scsi_bus_t *bus, int id, struct atapi_device_t *atapi_dev);
	void (*close)(void *p);
	void (*reset)(void *p);

	void (*start_command)(void *p);
	int (*command)(uint8_t *cdb, void *p);

	uint8_t (*get_status)(void *p);
	uint8_t (*get_sense_key)(void *p);
	int (*get_bytes_required)(void *p);

	void (*atapi_identify)(uint16_t *buffer, void *p);
	int (*atapi_set_feature)(uint8_t feature, uint8_t val, void *p);

	uint8_t (*read)(void *p);
	void (*write)(uint8_t val, void *p);
	int (*read_complete)(void *p);
	int (*write_complete)(void *p);

	void (*timer_callback)(void *p);
} scsi_device_t;

#define CDB_MAX_LEN 20

typedef struct scsi_bus_t
{
	int state;
	int new_state;
	int clear_req;
	uint32_t bus_in, bus_out;
	int dev_id;

	int command_pos;
	uint8_t command[CDB_MAX_LEN];

	scsi_device_t *devices[8];
	void *device_data[8];
	int device_timer_us[8];

	int change_state_delay;
	int new_req_delay;

	int is_atapi;
} scsi_bus_t;

#define SCSI_TEST_UNIT_READY              0x00
#define SCSI_REZERO_UNIT                  0x01
#define SCSI_REQUEST_SENSE                0x03
#define SCSI_FORMAT                       0x04
#define SCSI_READ_6                       0x08
#define SCSI_WRITE_6                      0x0a
#define SCSI_SEEK_6                       0x0b
#define SCSI_INQUIRY                      0x12
#define SCSI_MODE_SELECT_6                0x15
#define SCSI_MODE_SENSE_6                 0x1a
#define SCSI_START_STOP_UNIT              0x1b
#define SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e
#define SCSI_READ_CAPACITY_10             0x25
#define SCSI_READ_10                      0x28
#define SCSI_WRITE_10                     0x2a
#define SCSI_SEEK_10                      0x2b
#define SCSI_WRITE_AND_VERIFY             0x2e
#define SCSI_VERIFY_10                    0x2f
#define SCSI_READ_BUFFER                  0x3c
#define SCSI_MODE_SENSE_10                0x5a

#define SCSI_RESERVE                      0x16
#define SCSI_RELEASE                      0x17
#define SEND_DIAGNOSTIC                   0x1d

#define STATUS_GOOD            0x00
#define STATUS_CHECK_CONDITION 0x02

#define MSG_COMMAND_COMPLETE 0x00

#define BUS_DBP 0x01
#define BUS_SEL 0x02
#define BUS_IO  0x04
#define BUS_CD  0x08
#define BUS_MSG 0x10
#define BUS_REQ 0x20
#define BUS_BSY 0x40
#define BUS_RST 0x80
#define BUS_ACK 0x200
#define BUS_ATN 0x200
#define BUS_ARB 0x8000
#define BUS_SETDATA(val) ((uint32_t)val << 16)
#define BUS_GETDATA(val) ((val >> 16) & 0xff)
#define BUS_DATAMASK 0xff0000

#define BUS_IDLE (1 << 31)

int scsi_bus_update(scsi_bus_t *bus, int bus_assert);
int scsi_bus_read(scsi_bus_t *bus);
int scsi_bus_match(scsi_bus_t *bus, int bus_assert);
void scsi_bus_kick(scsi_bus_t *bus);
void scsi_bus_init(scsi_bus_t *bus, struct podule_t *podule, const struct podule_callbacks_t *podule_callbacks);
void scsi_bus_close(scsi_bus_t *bus);
void scsi_bus_reset(scsi_bus_t *bus);
void scsi_bus_timer_run(scsi_bus_t *bus, int time_us);

void scsi_bus_device_set_timer(scsi_bus_t *bus, int id, int timer_us);
void scsi_bus_atapi_init(scsi_bus_t *bus, scsi_device_t *device, int id, struct atapi_device_t *atapi_dev);

#define KEY_NONE			0
#define KEY_NOT_READY			2
#define KEY_ILLEGAL_REQ                 5
#define KEY_UNIT_ATTENTION		6
#define KEY_DATA_PROTECT                7

#define ASC_AUDIO_PLAY_OPERATION	0x00
#define ASC_ILLEGAL_OPCODE		0x20
#define	ASC_LBA_OUT_OF_RANGE            0x21
#define	ASC_INV_FIELD_IN_CMD_PACKET	0x24
#define ASC_INVALID_LUN                 0x25
#define ASC_WRITE_PROTECT               0x27
#define ASC_MEDIUM_MAY_HAVE_CHANGED	0x28
#define ASC_MEDIUM_NOT_PRESENT		0x3a
#define ASC_DATA_PHASE_ERROR		0x4b
#define ASC_ILLEGAL_MODE_FOR_THIS_TRACK	0x64


#define SCSI_PHASE_DATA_OUT    0
#define SCSI_PHASE_DATA_IN     BUS_IO
#define SCSI_PHASE_COMMAND     BUS_CD
#define SCSI_PHASE_STATUS      (BUS_CD | BUS_IO)
#define SCSI_PHASE_MESSAGE_OUT (BUS_MSG | BUS_CD)
#define SCSI_PHASE_MESSAGE_IN  (BUS_MSG | BUS_CD | BUS_IO)

#define START_STOP_START 0x01
#define START_STOP_LOEJ  0x02

typedef enum
{
	SCSI_STATE_IDLE,
	SCSI_STATE_SELECT,
	SCSI_STATE_SELECT_FAILED,
	SCSI_STATE_SEND_COMMAND,
	SCSI_STATE_NEXT_PHASE,
	SCSI_STATE_END_PHASE,
	SCSI_STATE_READ_DATA,
	SCSI_STATE_WRITE_DATA,
	SCSI_STATE_READ_STATUS,
	SCSI_STATE_READ_MESSAGE,
	SCSI_STATE_READ_DATA_NULL,
	SCSI_STATE_WRITE_DATA_NULL
} scsi_state_t;

extern void scsi_fatal(const char *format, ...);
extern void scsi_log(const char *format, ...);

#endif /*_SCSI_H_*/
