/* Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.

This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").

Contact information
-------------------

Circuits At Home, LTD
Web      :  http://www.circuitsathome.com
e-mail   :  support@circuitsathome.com
*/

#if !defined(_usb_h_) || defined(ADDRESS_H_INCLUDED)
#error "Never include address.h directly; include Usb.h instead"
#else
#define ADDRESS_H_INCLUDED

#include <stddef.h>
#include <stdint.h>

/* NAK powers. To save space in endpoint data structure, amount of retries before giving up and returning 0x4 is stored in */
/* bmNakPower as a power of 2. The actual nak_limit is then calculated as nak_limit = ( 2^bmNakPower - 1) */
#define USB_NAK_MAX_POWER		15		//NAK binary order maximum value
#define USB_NAK_DEFAULT			14		//default 32K-1 NAKs before giving up
#define USB_NAK_NOWAIT			1		//Single NAK stops transfer
#define USB_NAK_NONAK			0		//Do not count NAKs, stop retrying after USB Timeout

struct EpInfo {
	uint32_t epAddr; // Endpoint address
	uint32_t maxPktSize; // Maximum packet size
	union {
		uint8_t		epAttribs;

		struct {
			uint8_t		bmSndToggle	:	1;		// Send toggle, when zero bmSNDTOG0, bmSNDTOG1 otherwise
			uint8_t		bmRcvToggle	:	1;		// Send toggle, when zero bmRCVTOG0, bmRCVTOG1 otherwise
			uint8_t		bmNakPower	:	6;		// Binary order for NAK_LIMIT value
                };
	};
};

//	  7   6   5   4   3   2   1   0
//  ---------------------------------
//  |   | H | P | P | P | A | A | A |
//  ---------------------------------
//
// H - if 1 the address is a hub address
// P - parent hub address
// A - device address / port number in case of hub
//

struct UsbDeviceAddress {
	union {
		struct {
			uint32_t		bmAddress	: 3;	// device address/port number
			uint32_t		bmParent	: 3;	// parent hub address
			uint32_t		bmHub		: 1;	// hub flag
			uint32_t		bmReserved	: 25;	// reserved, must be zerro
		};
		uint32_t devAddress;
	};
};

#define bmUSB_DEV_ADDR_ADDRESS		0x07
#define bmUSB_DEV_ADDR_PARENT		0x38
#define bmUSB_DEV_ADDR_HUB		0x40

struct UsbDeviceDefinition {
	EpInfo			*epinfo;		// endpoint info pointer
	UsbDeviceAddress	address;
	uint32_t		epcount;		// number of endpoints
	uint32_t		lowspeed;		// indicates if a device is the low speed one
	//	uint8_t			devclass;		// device class
};

class AddressPool {
public:
	virtual UsbDeviceDefinition* GetUsbDevicePtr(uint32_t addr) = 0;
	virtual uint32_t AllocAddress(uint32_t parent, uint32_t is_hub = 0, uint32_t port = 0) = 0;
	virtual void FreeAddress(uint32_t addr) = 0;
};

typedef void (*UsbDeviceHandleFunc)(UsbDeviceDefinition *pdev);

#define ADDR_ERROR_INVALID_INDEX		0xFF
#define ADDR_ERROR_INVALID_ADDRESS		0xFF

template <const uint32_t MAX_DEVICES_ALLOWED>
class AddressPoolImpl : public AddressPool {
	EpInfo dev0ep; //Endpoint data structure used during enumeration for uninitialized device

	uint32_t hubCounter; // hub counter is kept
	// in order to avoid hub address duplication

	UsbDeviceDefinition thePool[MAX_DEVICES_ALLOWED];

	// Initializes address pool entry

	void InitEntry(uint32_t index) {
		thePool[index].address.devAddress = 0;
		thePool[index].epcount = 1;
		thePool[index].lowspeed = 0;
		thePool[index].epinfo = &dev0ep;
	};

	// Returns thePool index for a given address

	uint32_t FindAddressIndex(uint32_t address = 0) {
		for (uint8_t i = 1; i < MAX_DEVICES_ALLOWED; i++) {
			if(thePool[i].address.devAddress == address)
				return i;
		}
		return 0;
	};

	// Returns thePool child index for a given parent

	uint32_t FindChildIndex(UsbDeviceAddress addr, uint32_t start = 1) {
		for (uint32_t i = (start < 1 || start >= MAX_DEVICES_ALLOWED) ? 1 : start; i < MAX_DEVICES_ALLOWED; i++) {
			if(thePool[i].address.bmParent == addr.bmAddress)
				return i;
	}
	return 0;
	};

	// Frees address entry specified by index parameter

	void FreeAddressByIndex(uint32_t index) {
		// Zero field is reserved and should not be affected
		if(index == 0)
			return;

		UsbDeviceAddress uda = thePool[index].address;
		// If a hub was switched off all port addresses should be freed
		if(uda.bmHub == 1) {
			for (uint32_t i = 1; (i = FindChildIndex(uda, i));)
				FreeAddressByIndex(i);

			// If the hub had the last allocated address, hubCounter should be decremented
			if(hubCounter == uda.bmAddress)
				hubCounter--;
		}
		InitEntry(index);
	}

	// Initializes the whole address pool at once

	void InitAllAddresses() {
		for (uint32_t i = 1; i < MAX_DEVICES_ALLOWED; i++)
			InitEntry(i);

		hubCounter = 0;
	};

public:

	AddressPoolImpl() : hubCounter(0) {
		// Zero address is reserved
		InitEntry(0);

		thePool[0].address.devAddress = 0;
		thePool[0].epinfo = &dev0ep;
		dev0ep.epAddr = 0;
		dev0ep.maxPktSize = 8;
		dev0ep.epAttribs = 0; //set DATA0/1 toggles to 0
		dev0ep.bmNakPower = USB_NAK_MAX_POWER;

		InitAllAddresses();
	};

	// Returns a pointer to a specified address entry

	virtual UsbDeviceDefinition* GetUsbDevicePtr(uint32_t addr) {
		if(!addr)
			return thePool;

		uint32_t index = FindAddressIndex(addr);

		return (!index) ? NULL : thePool + index;
	};

        // Performs an operation specified by pfunc for each addressed device

	void ForEachUsbDevice(UsbDeviceHandleFunc pfunc) {
		if(!pfunc)
			return;

		for (uint32_t i = 1; i < MAX_DEVICES_ALLOWED; i++)
			if(thePool[i].address.devAddress)
				pfunc(thePool + i);
	};

	// Allocates new address

	virtual uint32_t AllocAddress(uint32_t parent, uint32_t is_hub = 0, uint32_t port = 0) {
		/* if (parent != 0 && port == 0)
				USB_HOST_SERIAL.println("PRT:0"); */
		UsbDeviceAddress _parent;
		_parent.devAddress = parent;
		if(_parent.bmReserved || port > 7)
			//if(parent > 127 || port > 7)
			return 0;

		if(is_hub && hubCounter == 7)
			return 0;

		// finds first empty address entry starting from one
		uint32_t index = FindAddressIndex(0);

		if(!index) // if empty entry is not found
			return 0;

		if(_parent.devAddress == 0) {
			if(is_hub) {
				thePool[index].address.devAddress = 0x41;
				hubCounter++;
			} else
				thePool[index].address.devAddress = 1;

			return thePool[index].address.devAddress;
		}

		UsbDeviceAddress addr;
		addr.devAddress = 0; // Ensure all bits are zero
		addr.bmParent = _parent.bmAddress;
		if(is_hub) {
			addr.bmHub = 1;
			addr.bmAddress = ++hubCounter;
		} else {
			addr.bmHub = 0;
			addr.bmAddress = port;
		}
		thePool[index].address = addr;
		/*
		USB_HOST_SERIAL.print("Addr:");
		USB_HOST_SERIAL.print(addr.bmHub, HEX);
		USB_HOST_SERIAL.print(".");
		USB_HOST_SERIAL.print(addr.bmParent, HEX);
		USB_HOST_SERIAL.print(".");
		USB_HOST_SERIAL.println(addr.bmAddress, HEX);
		*/
		return thePool[index].address.devAddress;
	};

    // Empties pool entry

	virtual void FreeAddress(uint32_t addr) {
		// if the root hub is disconnected all the addresses should be initialized
		if(addr == 0x41) {
			InitAllAddresses();
			return;
		}
		uint32_t index = FindAddressIndex(addr);
		FreeAddressByIndex(index);
	};

	// Returns number of hubs attached
	// It can be rather helpfull to find out if there are hubs attached than getting the exact number of hubs.
	//uint32_t GetNumHubs()
	//{
	//	return hubCounter;
	//};
	//uint32_t GetNumDevices()
	//{
	//	uint32_t counter = 0;

	//	for (uint32_t i=1; i<MAX_DEVICES_ALLOWED; i++)
	//		if (thePool[i].address != 0);
	//			counter++;

	//	return counter;
	//};
};

#endif /* ADDRESS_H_INCLUDED */
