#define TIMERSTREAM				FALSE

#include "AppleUSBTrinityAudioDevice.h"

#include <IOKit/usb/USB.h>
#include <IOKit/usb/IOUSBDevice.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOKitKeys.h>

#include <libkern/OSByteOrder.h>

#include <kern/thread_call.h>

static SInt32	power4AEQSettings[] = {
    228,	-129968,	130513,
    -279,	-125942,	128415,
    -1689,	-123355,	126686,
    -5136,	-95891,		109553,
    -18995,	-993,		6924,
    -45000
};

static SInt32    power3AEQSettings[] = {
    228,	-129968,	130513,
    -279,	-125942,	128415,
    -1689,	-123355,	126686,
    -5137,	-95891,		109553,
    -18995,	-993,		6924,
    -42000};

static SInt32 power1500mAEQSettings[] = {
	228,	-129968,	130513, 
	-279,	-125942,	128415, 
	-1689,	-123355,	126686, 
	-5137,	-95891,		109553, 
	-18995,	-993,		6924, 
	-20000};

SInt32    power500mAEQSettings[] = {
    228,	-129968,	130513,
    -279,	-125942,	128415,
    -1689,	-123355,	126686,
    -5137,	-95891,		109553,
    -18995,	-993,		6924,
    -8000};

static UInt8 pluginBinary[] = {
    0xBF, 0x35, 0x81, 0xBA, 0x85, 0xEA, 0x7B, 0x80, 0xE1, 0x13, 0xBF, 0xDE, 0x0B, 0x8D, 0xB9, 0x85,
    0xBF, 0x1A, 0x0C, 0x8D, 0xB9, 0xE9, 0xF3, 0x81, 0xE8, 0x80, 0x80, 0x79, 0x90, 0x03, 0xBC, 0xEC,
    0x81, 0xEA, 0xA2, 0xB0, 0xE4, 0x00, 0xE5, 0x02, 0x44, 0x99, 0x01, 0x45, 0x15, 0x71, 0x14, 0x19,
    0x90, 0xF6, 0xE0, 0xF0, 0xC8, 0x87, 0x80, 0xC8, 0x51, 0xB0, 0x12, 0x63, 0x90, 0x03, 0x28, 0x98,
    0x02, 0xE0, 0x40, 0xC8, 0x89, 0x80, 0xC8, 0xA0, 0xB0, 0xE1, 0xFB, 0x12, 0x21, 0xC8, 0x88, 0x80,
    0xE8, 0x80, 0x80, 0x90, 0x09, 0xE8, 0x01, 0xA0, 0xC8, 0x80, 0x80, 0xBF, 0x2F, 0x81, 0xE8, 0x80,
    0x80, 0xC8, 0xF3, 0x81, 0xE1, 0x0C, 0x12, 0x21, 0x90, 0x25, 0xE9, 0xED, 0x81, 0xE8, 0x7B, 0x80,
    0x59, 0x49, 0x74, 0xE9, 0xF0, 0x81, 0xE2, 0x80, 0x2A, 0x73, 0x11, 0x2A, 0x7B, 0x99, 0x0A, 0xE8,
    0xF1, 0x81, 0x00, 0xC8, 0xF1, 0x81, 0xCC, 0x7B, 0x80, 0xE8, 0xEE, 0x81, 0xBC, 0xDA, 0x81, 0xE8,
    0xF2, 0x81, 0x90, 0x29, 0xE9, 0x7B, 0x80, 0xE8, 0xED, 0x81, 0x51, 0x72, 0xE8, 0xF1, 0x81, 0x98,
    0x16, 0x40, 0xC8, 0xF1, 0x81, 0x12, 0xE1, 0x80, 0x29, 0xE1, 0xB0, 0x79, 0x99, 0x04, 0x12, 0xBC,
    0xD4, 0x81, 0xE0, 0x30, 0xC8, 0x7B, 0x80, 0xE8, 0xEF, 0x81, 0xC8, 0xF2, 0x81, 0xE8, 0xF2, 0x81,
    0x90, 0x03, 0xBC, 0x24, 0x81, 0x40, 0xC8, 0xF2, 0x81, 0xBC, 0x24, 0x81, 0xB9, 0x01, 0x08, 0x0F,
    0xD0, 0x01, 0x01, 0x01
};

UInt8 disableplugin = 0xba;

#define super AppleUSBAudioDevice
OSDefineMetaClassAndStructors (AppleUSBTrinityAudioDevice, AppleUSBAudioDevice)

IOReturn AppleUSBTrinityAudioDevice::xdfpSetMem (UInt8 * buf, UInt16 length, UInt16 xdfpAddr) {
    IOUSBDevRequest 	devReq;
    IOReturn			result;
    
    result = kIOReturnError;
    FailWithAction (!controlInterface, debugIOLog ("AppleUSBTrinityAudioDevice::xdfpSetMem () - Error - no USB interface\n"), Exit);

    devReq.bmRequestType = USBmakebmRequestType (kUSBOut, kUSBVendor, kUSBDevice);
    devReq.bRequest = kMicronasSetMemReq;
    devReq.wValue = 0;
    devReq.wIndex = xdfpAddr;
    devReq.wLength = length;
    devReq.pData = buf;

    result = controlInterface->DeviceRequest (&devReq);

Exit:
    return result;
}

IOReturn AppleUSBTrinityAudioDevice::xdfpGetMem (UInt8 * buf, UInt16 length, UInt16 xdfpAddr) {
    IOUSBDevRequest 	devReq;
    IOReturn			result;
    
    result = kIOReturnError;
    FailWithAction (!controlInterface, debugIOLog ("AppleUSBTrinityAudioDevice::xdfpGetMem () - Error - no USB interface\n"), Exit);

    devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBVendor, kUSBDevice);
    devReq.bRequest = kMicronasGetMemReq;
    devReq.wValue = 0;
    devReq.wIndex = xdfpAddr;
    devReq.wLength = length;
    devReq.pData = buf;

    result = controlInterface->DeviceRequest (&devReq);

Exit:
    return result;
}

IOReturn AppleUSBTrinityAudioDevice::xdfpWrite (UInt16 xdfpAddr, SInt32 value) {
    static UInt8 xdfpData[5];

    if (value < 0) value += 0x40000;
    xdfpData[0] = (value >> 10) & 0xff;
    xdfpData[1] = (value >> 2) & 0xff;
    xdfpData[2] = value & 0x03;
    xdfpData[3] = (xdfpAddr >> 8) & 0x03;
    xdfpData[4] = xdfpAddr & 0xff;

    return xdfpSetMem (xdfpData, 5, V8_WRITE_START_ADDR);
}

IOReturn AppleUSBTrinityAudioDevice::disablePlugin () {
    return xdfpSetMem (&disableplugin, 1, V8_PLUGIN_START_ADDR);
}

SInt32 *AppleUSBTrinityAudioDevice::getProperEQSettings () {
    IOUSBDevice *			device;
    IORegistryEntry *		parent;
	const IORegistryPlane *	plane;
    OSObject *				powerProperty;
    UInt32					availablePower;
    OSNumber *				powerNum;
    OSData *				powerData;
    SInt32 *				settings;

    settings = NULL;
    availablePower = 500;
    FailIf (!controlInterface, Exit);

    device = controlInterface->GetDevice ();
    FailIf (!device, Exit);

	plane = IORegistryEntry::getPlane (kIOUSBPlane);		// As per 2679853
    FailIf (!plane, Exit);

	parent = device->getParentEntry (plane);
	FailIf (!parent, Exit);

    powerProperty = parent->getProperty ("AAPL,current-available");
    FailIf (!powerProperty, Exit);

    if (powerNum = OSDynamicCast (OSNumber, powerProperty)) {
        availablePower = powerNum->unsigned32BitValue ();
    } else if (powerData = OSDynamicCast (OSData, powerProperty)) {
        availablePower = *(UInt32 *)powerData->getBytesNoCopy ();
    }

    debug3IOLog ("AppleUSBTrinityAudioDevice[0x%x]: Hub (%d) found.\n", this, availablePower);

    if (availablePower >= 4000) {
		debugIOLog ("Usinging 4 amp EQ\n");
        settings = power4AEQSettings;
    } else if (availablePower >= 3000) {
		debugIOLog ("Usinging 3 amp EQ\n");
        settings = power3AEQSettings;
	} else if (availablePower >= 1500) {
		debugIOLog ("Usinging 1.5 amp EQ\n");
		settings = power1500mAEQSettings;
    } else if (availablePower >= 500) {
		debugIOLog ("Usinging 0.5 amp EQ\n");
        settings = power500mAEQSettings;
    }

Exit:
    return settings;
}

IOReturn AppleUSBTrinityAudioDevice::downloadEQ () {
    return downloadEQ (getProperEQSettings ());
}

IOReturn AppleUSBTrinityAudioDevice::downloadEQ (SInt32 * eqSettings) {
    UInt32				eqIndex;
    UInt16				xdfpAddr;
    IOReturn			result;

	debug2IOLog ("+AppleUSBTrinityAudioDevice::downloadEQ (%p)\n", eqSettings)
    result = kIOReturnBadArgument;
    FailWithAction (!eqSettings, debugIOLog("AppleUSBTrinityAudioDevice::downloadEQ() - error no EQ settings available.\n"), Exit);

    for (eqIndex = 0, xdfpAddr = XDFP_STARTING_EQ_ADDR; eqIndex < EQ_TABLE_SIZE; eqIndex++, xdfpAddr++) {
        result = xdfpWrite (xdfpAddr, eqSettings[eqIndex]);
        if (result != kIOReturnSuccess) {
            debug3IOLog ("AppleUSBTrinityAudioDevice::downloadEQ () - error writing settings %d: 0x%x\n", eqIndex, result);
            break;
        }
        IOSleep (3);
    }

Exit:
	debug3IOLog ("-AppleUSBTrinityAudioDevice::downloadEQ (%p), result = %d\n", eqSettings, result)
    return result;
}

IOReturn AppleUSBTrinityAudioDevice::downloadPlugin () {
    return xdfpSetMem (&pluginBinary[1], sizeof (pluginBinary), V8_PLUGIN_START_ADDR + 1);
}

IOReturn AppleUSBTrinityAudioDevice::enablePlugin () {
    return xdfpSetMem (pluginBinary, 1, V8_PLUGIN_START_ADDR);
}

bool AppleUSBTrinityAudioDevice::start (IOService * provider) {
	IOService *		device;
	bool			result;

	debug2IOLog ("AppleUSBTrinityAudioDevice[0x%x]: G4 Cube speakers detected.\n", this);

	result = FALSE;
	FailIf (FALSE == super::start (provider), Exit);

	disablePlugin ();
	downloadEQ ();
	downloadPlugin ();
	enablePlugin ();

	device = controlInterface->GetDevice ();
	FailIf (NULL == device, Exit);

	result = TRUE;

Exit:
	return result;
}

IOReturn AppleUSBTrinityAudioDevice::performPowerStateChange (IOAudioDevicePowerState oldPowerState, IOAudioDevicePowerState newPowerState, UInt32 *microSecsUntilComplete) {
	IOReturn						result;

	debug4IOLog ("+AppleUSBTrinityAudioDevice::performPowerStateChange (%d, %d, %p)\n", oldPowerState, newPowerState, microSecsUntilComplete);

	result = super::performPowerStateChange (oldPowerState, newPowerState, microSecsUntilComplete);

	if (oldPowerState == kIOAudioDeviceSleep) {
		debugIOLog ("G4 Cube speakers: Waking from sleep - downloading power settings to speakers.\n");
		eqRetryCount = 0;
		attemptEQDownload ();
	}

	return result;
}

void AppleUSBTrinityAudioDevice::scheduleEQDownloadRetry (void) {
    AbsoluteTime		fireTime;
    UInt64				nanos;

	retain ();	// Don't let our kext be unloaded because we are about to set up a timer task to be called back. (fixes 2649020)
    clock_get_uptime (&fireTime);
    absolutetime_to_nanoseconds (fireTime, &nanos);
    nanos += 10000000;	// Schedule 10ms in the future for retry
    nanoseconds_to_absolutetime (nanos, &fireTime);

    thread_call_func_delayed ((thread_call_func_t)retryEQDownload, this, fireTime);
}

void AppleUSBTrinityAudioDevice::attemptEQDownload (void) {
    debug2IOLog ("AppleUSBTrinityAudioDevice::attemptEQDownload () - eqRetryCount = %d\n", eqRetryCount);

	FailIf (NULL == controlInterface, Exit);
	if (downloadEQ () != kIOReturnSuccess) {
		if (++eqRetryCount < MAX_EQ_DOWNLOAD_RETRIES) {
			scheduleEQDownloadRetry ();
		} else {
			debugIOLog ("G4 Cube Speakers: too many retries of downloadEQ () - aborting.\n");
		}
	}

Exit:
	return;
}

void AppleUSBTrinityAudioDevice::retryEQDownload (void * arg) {
    AppleUSBTrinityAudioDevice * device = (AppleUSBTrinityAudioDevice *)arg;
    if (device) {
        device->attemptEQDownload ();
		device->release ();	// it's now safe for our driver to be unloaded if needed.
    }
}
