/* Copyright (C) 2015-2016 Andrew J. Kroll
   and
Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

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

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

#ifndef _UHS_host_h_
#error "Never include UHS_usbhost.h directly; include UHS_host.h instead"
#else
#ifndef _USBHOST_H_
#define _USBHOST_H_

// Very early prototypes
#ifdef UHS_LOAD_BT
void UHS_BT_SetUSBInterface(UHS_USB_HOST_BASE *host, ENUMERATION_INFO *ei);
void UHS_BT_ScanUninitialized(UHS_USB_HOST_BASE *host);
void UHS_BT_Poll(UHS_USB_HOST_BASE *host);
#endif
#ifdef UHS_LOAD_HID
void UHS_HID_SetUSBInterface(UHS_USB_HOST_BASE *host, ENUMERATION_INFO *ei);
void UHS_HID_ScanUninitialized(UHS_USB_HOST_BASE *host);
void UHS_HID_Poll(UHS_USB_HOST_BASE *host);
#endif

//#if defined(LOAD_UHS_CDC_ACM) || defined(LOAD_UHS_CDC_ACM_FTDI) || defined(LOAD_UHS_CDC_ACM_PROLIFIC) || defined(LOAD_UHS_CDC_ACM_XR21B1411)
//void UHS_CDC_ACM_SetUSBInterface(UHS_USB_HOST_BASE *host, ENUMERATION_INFO *ei);
//void UHS_CDC_ACM_ScanUninitialized(UHS_USB_HOST_BASE *host);
//void UHS_CDC_ACM_Poll(UHS_USB_HOST_BASE *host);
//#endif

class UHS_USBInterface; // forward class declaration

// enumerator to turn the VBUS on/off

typedef enum {
        vbus_on = 0,
        vbus_off = 1
} VBUS_t;

// All host SEI use this base class

class UHS_USB_HOST_BASE {
public:
        AddressPool addrPool;
        UHS_USBInterface* devConfig[UHS_HOST_MAX_INTERFACE_DRIVERS];
        volatile uint8_t usb_error;
        volatile uint8_t usb_task_state;
        volatile uint8_t usb_task_polling_disabled;
        volatile uint8_t usb_host_speed;
        volatile uint8_t hub_present;

        UHS_USB_HOST_BASE() {
                for(uint16_t i = 0; i < UHS_HOST_MAX_INTERFACE_DRIVERS; i++) {
                        devConfig[i] = NULL;
                }
                usb_task_polling_disabled = 0;
                usb_task_state = UHS_USB_HOST_STATE_INITIALIZE; //set up state machine
                usb_host_speed = 0;
                usb_error = 0;
        };

        /////////////////////////////////////////////
        //
        // Virtual methods that interface to the SIE
        // Overriding each is mandatory.
        //
        /////////////////////////////////////////////

        /**
         * Delay for x milliseconds
         * Override if your controller provides an SOF IRQ, which may involve
         * some sort of reentrant ISR or workaround with interrupts enabled.
         *
         * @param x how many milliseconds to delay
         * @return true if delay completed without a state change, false if delay aborted
         */
        virtual bool UHS_NI sof_delay(uint16_t x) {
                if(!(usb_task_state & UHS_USB_HOST_STATE_MASK)) return false;
                uint8_t current_state = usb_task_state;
                while(current_state == usb_task_state && x--) {
                        delay(1);
                }
                return (current_state == usb_task_state);
        };

        virtual UHS_EpInfo * UHS_NI ctrlReqOpen(NOTUSED(uint8_t addr), NOTUSED(uint64_t Request), NOTUSED(uint8_t *dataptr)) {
                return NULL;
        };

        virtual void UHS_NI vbusPower(NOTUSED(VBUS_t state)) {
        };

        virtual void UHS_NI Task() {
        };

        virtual uint8_t UHS_NI SetAddress(NOTUSED(uint8_t addr), NOTUSED(uint8_t ep), NOTUSED(UHS_EpInfo **ppep), NOTUSED(uint16_t &nak_limit)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI OutTransfer(NOTUSED(UHS_EpInfo *pep), NOTUSED(uint16_t nak_limit), NOTUSED(uint16_t nbytes), NOTUSED(uint8_t *data)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI InTransfer(NOTUSED(UHS_EpInfo *pep), NOTUSED(uint16_t nak_limit), NOTUSED(uint16_t *nbytesptr), NOTUSED(uint8_t *data)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI ctrlReqClose(NOTUSED(UHS_EpInfo *pep), NOTUSED(uint8_t bmReqType), NOTUSED(uint16_t left), NOTUSED(uint16_t nbytes), NOTUSED(uint8_t *dataptr)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI ctrlReqRead(NOTUSED(UHS_EpInfo *pep), NOTUSED(uint16_t *left), NOTUSED(uint16_t *read), NOTUSED(uint16_t nbytes), NOTUSED(uint8_t *dataptr)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI dispatchPkt(NOTUSED(uint8_t token), NOTUSED(uint8_t ep), NOTUSED(uint16_t nak_limit)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t UHS_NI init() {
                return 0;
        };

        virtual void UHS_NI doHostReset() {
        };

        virtual int16_t UHS_NI Init(NOTUSED(int16_t mseconds)) {
                return -1;
        };

        virtual int16_t UHS_NI Init() {
                return Init(INT16_MIN);
        };

        virtual uint8_t hwlPowerUp() {
                /* This is for machine specific support to enable/power up the USB HW to operate*/
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual uint8_t hwPowerDown() {
                /* This is for machine specific support to disable/powerdown the USB Hw */
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        virtual bool IsHub(uint8_t klass) {
                return (klass == UHS_USB_CLASS_HUB);
        };

        virtual void UHS_NI suspend_host() {
                // Used on MCU that lack control of IRQ priority (AVR).
                // Suspends ISRs, for critical code. IRQ will be serviced after it is resumed.
                // NOTE: you must track the state yourself!
        };

        virtual void UHS_NI resume_host() {
                // Used on MCU that lack control of IRQ priority (AVR).
                // Resumes ISRs.
                // NOTE: you must track the state yourself!
        };

        /////////////////////////////////////////////
        //
        // Built-ins, No need to override
        //
        /////////////////////////////////////////////
        // these two probably will go away, and won't be used, TBD
        inline void Poll_Others() {
#ifdef UHS_LOAD_BT
                UHS_BT_Poll(this);
#endif
#ifdef UHS_LOAD_HID
                UHS_HID_Poll(this);
#endif
        }

        inline void DisablePoll() {
                noInterrupts();
                usb_task_polling_disabled++;
                DDSB();
                interrupts();
        }

        inline void EnablePoll() {
                noInterrupts();
                usb_task_polling_disabled--;
                DDSB();
                interrupts();
        }

        uint8_t UHS_NI seekInterface(ENUMERATION_INFO *ei, uint16_t inf, USB_FD_CONFIGURATION_DESCRIPTOR *ucd);

        uint8_t UHS_NI setEpInfoEntry(uint8_t addr, uint8_t iface, uint8_t epcount, volatile UHS_EpInfo* eprecord_ptr);

        uint8_t UHS_NI EPClearHalt(uint8_t addr, uint8_t ep);

        uint8_t UHS_NI ctrlReq(uint8_t addr, uint64_t Request, uint16_t nbytes, uint8_t *dataptr);

        uint8_t UHS_NI getDevDescr(uint8_t addr, uint16_t nbytes, uint8_t *dataptr);

        uint8_t UHS_NI getConfDescr(uint8_t addr, uint16_t nbytes, uint8_t conf, uint8_t *dataptr);

        uint8_t UHS_NI setAddr(uint8_t oldaddr, uint8_t newaddr);

        uint8_t UHS_NI setConf(uint8_t addr, uint8_t conf_value);

        uint8_t UHS_NI getStrDescr(uint8_t addr, uint16_t nbytes, uint8_t index, uint16_t langid, uint8_t *dataptr);

        void UHS_NI ReleaseDevice(uint8_t addr);

        uint8_t UHS_NI Configuring(uint8_t parent, uint8_t port, uint8_t speed);

        void UHS_NI DeviceDefaults(uint8_t maxep, UHS_USBInterface *device);

        UHS_EpInfo* UHS_NI getEpInfoEntry(uint8_t addr, uint8_t ep);

        inline uint8_t getUsbTaskState() {
                return ( usb_task_state);
        };

        inline AddressPool* GetAddressPool() {
                return &addrPool;
        };

        int UHS_NI RegisterDeviceClass(UHS_USBInterface *pdev) {
                for(uint8_t i = 0; i < UHS_HOST_MAX_INTERFACE_DRIVERS; i++) {
                        if(!devConfig[i]) {
                                devConfig[i] = pdev;
                                return i;
                        }
                }
                //return UHS_HOST_ERROR_CANT_REGISTER_DEVICE_CLASS;
                return -1;
        };
#if 0

        inline void ForEachUsbDevice(UsbDeviceHandleFunc pfunc) {
                addrPool.ForEachUsbDevice(pfunc);
        };
#endif

        uint8_t TestInterface(ENUMERATION_INFO *ei);
        uint8_t enumerateInterface(ENUMERATION_INFO *ei);
        uint8_t getNextInterface(ENUMERATION_INFO *ei, UHS_EpInfo *pep, uint8_t data[], uint16_t *left, uint16_t *read, uint8_t *offset);
        uint8_t initDescrStream(ENUMERATION_INFO *ei, USB_FD_CONFIGURATION_DESCRIPTOR *ucd, UHS_EpInfo *pep, uint8_t *data, uint16_t *left, uint16_t *read, uint8_t *offset);
        uint8_t outTransfer(uint8_t addr, uint8_t ep, uint16_t nbytes, uint8_t *data);
        uint8_t inTransfer(uint8_t addr, uint8_t ep, uint16_t *nbytesptr, uint8_t *data);
        uint8_t doSoftReset(uint8_t parent, uint8_t port, uint8_t address);
        uint8_t getone(UHS_EpInfo *pep, uint16_t *left, uint16_t *read, uint8_t *dataptr, uint8_t *offset);
        uint8_t eat(UHS_EpInfo *pep, uint16_t *left, uint16_t *read, uint8_t *dataptr, uint8_t *offset, uint16_t *yum);

};

// All device interface drivers use this subclass

class UHS_USBInterface {
public:

        UHS_USB_HOST_BASE *pUsb; // Parent USB host
        volatile uint8_t bNumEP; // total number of EP in this interface
        volatile UHS_EpInfo epInfo[16]; // This is a stub, override in the driver.

        volatile uint8_t bAddress; // address of the device
        volatile uint8_t bConfNum; // configuration number
        volatile uint8_t bIface; // interface value
        volatile bool bPollEnable; // poll enable flag, operating status
        volatile uint32_t qNextPollTime; // next poll time

        /**
         * Resets interface driver to unused state. You should override this in
         * your driver if it requires extra class variable cleanup.
         */
        virtual void DriverDefaults() {
                printf("Default driver defaults.\r\n");
                pUsb->DeviceDefaults(bNumEP, this);
        };

        /**
         * Checks if this interface is supported.
         * Executed called when new devices are connected.
         *
         * @param ei
         * @return true if the interface is supported
         */
        virtual bool OKtoEnumerate(NOTUSED(ENUMERATION_INFO *ei)) {
                return false;
        };

        /**
         * Configures any needed endpoint information for an interface.
         * You must provide this in your driver.
         * Executed when new devices are connected and OKtoEnumerate()
         * returned true.
         *
         * @param ei
         * @return zero on success
         */
        virtual uint8_t SetInterface(NOTUSED(ENUMERATION_INFO *ei)) {
                return UHS_HOST_ERROR_NOT_IMPLEMENTED;
        };

        /**
         * Interface specific additional setup and enumeration that
         * can't occur when the descriptor stream is open.
         * Also used for collection of unclaimed interfaces, to link to the master.
         *
         * @return zero on success
         */
        virtual uint8_t Finalize() {
                return 0;
        };

        /**
         *  Executed after interface is finalized but, before polling has started.
         *
         * @return 0 on success
         */
        virtual uint8_t OnStart() {
                return 0;
        };

        /**
         * Start interface polling
         * @return
         */
        virtual uint8_t Start() {
                uint8_t rcode = OnStart();
                if(!rcode) bPollEnable = true;
                return rcode;
        };

        /**
         * Executed before anything else in Release().
         */
        virtual void OnRelease() {
                return;
        };

        /**
         * Release resources when device is disconnected.
         * Normally this does not need to be overridden.
         */
        virtual void Release() {
                OnRelease();
                DriverDefaults();
                return;
        };

        /**
         * Executed After driver polls.
         * Can be used when there is an important change detected during polling
         * and you want to handle it elsewhere.
         * Examples:
         * Media status change for bulk, e.g. ready, not-ready, media changed, door opened.
         * Button state/joystick position/etc changes on a HID device.
         * Flow control status change on a communication device, e.g. CTS on serial
         */
        virtual void OnPoll() {
                return;
        };

        /**
         * Poll interface driver. You should override this in your driver if you
         * require polling faster or slower than every 100 milliseconds, or your
         * driver requires special housekeeping.
         */
        virtual void Poll() {
                OnPoll();
                qNextPollTime = millis() + 100;
        };

        virtual bool UHS_NI Polling() {
                return bPollEnable;
        }

        /**
         * This is only for a hub.
         * @param port
         */
        virtual void ResetHubPort(NOTUSED(uint8_t port)) {
                return;
        };

#if 0
        /**
         * @return true if this interface is Vendor Specific.
         */
        virtual bool IsVSI() {
                return false;
        }
#endif
};

#if 0
/**
 * Vendor Specific interface class.
 * This is used by a partner interface.
 * It can also be used to force-enumerate an interface that
 * can use this interface directly.
 * You can also add an instance of this class within the interface constructor
 * if you expect the interface.
 *
 * If this is not needed, it may be removed. Nothing I have written needs this.
 * Let me know if it is not required, then IsVSI method can also be shit-canned.
 * -- AJK
 */

class UHS_VSI : public UHS_USBInterface {
public:
        volatile UHS_EpInfo epInfo[1];
        volatile ENUMERATION_INFO eInfo;
        UHS_VSI(UHS_USB_HOST_BASE *p);
        bool OKtoEnumerate(ENUMERATION_INFO *ei);
        uint8_t SetInterface(ENUMERATION_INFO *ei);
        virtual void DriverDefaults();
        virtual void Release();

        uint8_t GetAddress() {
                return bAddress;
        };

        virtual bool IsVSI() {
                return true;
        }

};
#endif

#endif //_USBHOST_H_
#endif
