/*
 * This file is part of the libpayload project.
 *
 * Copyright (C) 2010 Patrick Georgi
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef __OHCI_PRIVATE_H
#define __OHCI_PRIVATE_H

#include <usb/usb.h>

#define MASK(startbit, lenbit) (((1<<(lenbit))-1)<<(startbit))

	// FIXME: fake
	typedef enum { CMD} reg;

	enum {
		NumberDownstreamPorts = 1<<0,
		PowerSwitchingMode = 1<<8,
		NoPowerSwitching = 1<<9,
		DeviceType = 1<<10,
		OverCurrentProtectionMode = 1<<11,
		NoOverCurrentProtection = 1<<12,
		PowerOnToPowerGoodTime = 1<<24
	} HcRhDescriptorAReg;

	enum {
		NumberDownstreamPortsMask = MASK(0, 8),
		PowerOnToPowerGoodTimeMask = MASK(24, 8)
	} HcRhDescriptorAMask;

	enum {
		DeviceRemovable = 1<<0,
		PortPowerControlMask = 1<<16
	} HcRhDescriptorBReg;

	enum {
		CurrentConnectStatus		= 1<<0,
		PortEnableStatus		= 1<<1,
		PortSuspendStatus		= 1<<2,
		PortOverCurrentIndicator	= 1<<3,
		PortResetStatus			= 1<<4,
		PortPowerStatus			= 1<<8,
		LowSpeedDeviceAttached		= 1<<9,
		ConnectStatusChange		= 1<<16,
		PortEnableStatusChange		= 1<<17,
		PortSuspendStatusChange		= 1<<18,
		PortOverCurrentIndicatorChange	= 1<<19,
		PortResetStatusChange		= 1<<20
	} HcRhPortStatusRead;
	enum {
		ClearPortEnable			= 1<<0,
		SetPortEnable			= 1<<1,
		SetPortSuspend			= 1<<2,
		ClearSuspendStatus		= 1<<3,
		SetPortReset			= 1<<4,
		SetPortPower			= 1<<8,
		ClearPortPower			= 1<<9,
	} HcRhPortStatusSet;

	enum {
		LocalPowerStatus = 1<<0,
		OverCurrentIndicator = 1<<1,
		DeviceRemoteWakeupEnable = 1<<15,
		LocalPowerStatusChange = 1<<16,
		OverCurrentIndicatorChange = 1<<17,
		ClearRemoteWakeupEnable = 1<<31
	} HcRhStatusReg;

	enum {
		FrameInterval = 1<<0,
		FSLargestDataPacket = 1<<16,
		FrameIntervalToggle = 1<<31
	} HcFmIntervalOffset;
	enum {
		FrameIntervalMask = MASK(0, 14),
		FSLargestDataPacketMask = MASK(16, 15),
		FrameIntervalToggleMask = MASK(31, 1)
	} HcFmIntervalMask;

	enum {
		ControlBulkServiceRatio = 1<<0,
		PeriodicListEnable = 1<<2,
		IsochronousEnable = 1<<3,
		ControlListEnable = 1<<4,
		BulkListEnable = 1<<5,
		HostControllerFunctionalState = 1<<6,
		InterruptRouting = 1<<8,
		RemoteWakeupConnected = 1<<9,
		RemoteWakeupEnable = 1<<10
	} HcControlReg;

	enum {
		ControlBulkServiceRatioMask = MASK(0, 2),
		HostControllerFunctionalStateMask = MASK(6, 2)
	} HcControlMask;

	enum {
		USBReset = 0*HostControllerFunctionalState,
		USBResume = 1*HostControllerFunctionalState,
		USBOperational = 2*HostControllerFunctionalState,
		USBSuspend = 3*HostControllerFunctionalState
	};

	enum {
		HostControllerReset = 1<<0,
		ControlListFilled = 1<<1,
		BulkListFilled = 1<<2,
		OwnershipChangeRequest = 1<<3,
		SchedulingOverrunCount = 1<<16
	} HcCommandStatusReg;

	enum {
		SchedulingOverrunCountMask = MASK(16, 2)
	} HcCommandStatusMask;

	enum {
		FrameRemaining = 1<<0,
		FrameRemainingToggle = 1<<31
	} HcFmRemainingReg;

	enum {
		SchedulingOverrung = 1<<0,
		WritebackDoneHead = 1<<1,
		StartofFrame = 1<<2,
		ResumeDetected = 1<<3,
		UnrecoverableError = 1<<4,
		FrameNumberOverflow = 1<<5,
		RootHubStatusChange = 1<<6,
		OwnershipChange = 1<<30
	} HcInterruptStatusReg;

     typedef struct {
	// Control and Status Partition
	volatile u32 HcRevision;
	volatile u32 HcControl;
	volatile u32 HcCommandStatus;
	volatile u32 HcInterruptStatus;
	volatile u32 HcInterruptEnable;
	volatile u32 HcInterruptDisable;

	// Memory Pointer Partition
	volatile u32 HcHCCA;
	volatile u32 HcPeriodCurrentED;
	volatile u32 HcControlHeadED;
	volatile u32 HcControlCurrentED;
	volatile u32 HcBulkHeadED;
	volatile u32 HcBulkCurrentED;
	volatile u32 HcDoneHead;

	// Frame Counter Partition
	volatile u32 HcFmInterval;
	volatile u32 HcFmRemaining;
	volatile u32 HcFmNumber;
	volatile u32 HcPeriodicStart;
	volatile u32 HcLSThreshold;

	// Root Hub Partition
	volatile u32 HcRhDescriptorA;
	volatile u32 HcRhDescriptorB;
	volatile u32 HcRhStatus;
	/* all bits in HcRhPortStatus registers are R/WC, so
	   _DO NOT_ use |= to set the bits,
	   this clears the entire state */
	volatile u32 HcRhPortStatus[];
     } __attribute__ ((packed)) opreg_t;

	typedef struct { /* should be 256 bytes according to spec */
		u32 HccaInterruptTable[32];
		volatile u16 HccaFrameNumber;
		volatile u16 HccaPad1;
		volatile u32 HccaDoneHead;
		u8 reserved[116]; /* pad according to spec */
		u8 what[4]; /* really pad to 256 as spec only covers 252 */
	} __attribute__ ((packed)) hcca_t;

	typedef volatile struct {
		u32 config;
		u32 tail_pointer;
		u32 head_pointer;
		u32 next_ed;
	} __attribute__ ((packed)) ed_t;
#define ED_HALTED 1
#define ED_TOGGLE 2

#define ED_FUNC_SHIFT 0
#define ED_FUNC_MASK MASK(0, 7)
#define ED_EP_SHIFT 7
#define ED_EP_MASK MASK(7, 4)
#define ED_DIR_SHIFT 11
#define ED_DIR_MASK MASK(11, 2)
#define ED_LOWSPEED (1 << 13)
#define ED_MPS_SHIFT 16

	typedef volatile struct {
		u32 config;
		u32 current_buffer_pointer;
		u32 next_td;
		u32 buffer_end;
	} __attribute__ ((packed)) td_t;
/*
 * Bits 0 through 17 of .config won't be interpreted by the host controller
 * (HC) and, after processing the TD, the HC has to ensure those bits have
 * the same state as before. So we are free to use those bits for our own
 * purpose.
 */
#define TD_QUEUETYPE_SHIFT	0
#define TD_QUEUETYPE_MASK	MASK(TD_QUEUETYPE_SHIFT, 2)
#define TD_QUEUETYPE_ASYNC	(0 << TD_QUEUETYPE_SHIFT)
#define TD_QUEUETYPE_INTR	(1 << TD_QUEUETYPE_SHIFT)

#define TD_DIRECTION_SHIFT 19
#define TD_DIRECTION_MASK MASK(TD_DIRECTION_SHIFT, 2)
#define TD_DIRECTION_SETUP OHCI_SETUP << TD_DIRECTION_SHIFT
#define TD_DIRECTION_IN OHCI_IN << TD_DIRECTION_SHIFT
#define TD_DIRECTION_OUT OHCI_OUT << TD_DIRECTION_SHIFT
#define TD_DELAY_INTERRUPT_SHIFT	21
#define TD_DELAY_INTERRUPT_MASK		MASK(TD_DELAY_INTERRUPT_SHIFT, 3)
#define TD_DELAY_INTERRUPT_ZERO		0
#define TD_DELAY_INTERRUPT_NOINTR	(7 << TD_DELAY_INTERRUPT_SHIFT)
#define TD_TOGGLE_DATA0 0
#define TD_TOGGLE_DATA1 (1 << 24)
#define TD_TOGGLE_FROM_ED 0
#define TD_TOGGLE_FROM_TD (1 << 25)
#define TD_CC_SHIFT 28
#define TD_CC_MASK MASK(TD_CC_SHIFT, 4)
#define TD_CC_NOERR 0
#define TD_CC_NOACCESS (14 << TD_CC_SHIFT) /* the lower of the two values, so "no access" can be tested with >= */

#define OHCI_INST(controller) ((ohci_t*)((controller)->instance))

	typedef struct ohci {
		opreg_t *opreg;
		hcca_t *hcca;
		usbdev_t *roothub;
		ed_t *periodic_ed;
#define DMA_SIZE (64 * 1024)
		void *dma_buffer;
	} ohci_t;

	typedef enum { OHCI_SETUP=0, OHCI_OUT=1, OHCI_IN=2, OHCI_FROM_TD=3 } ohci_pid_t;

#endif