/*
 * Copyright (c) 1998-2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <sys/systm.h>    // snprintf
#include <IOKit/assert.h>
#include <IOKit/IOMessage.h>
#include "AppleIntelPIIXPATA.h"

#define super IOPCIATA
OSDefineMetaClassAndStructors( AppleIntelPIIXPATA, IOPCIATA )

#ifdef  PIIX_DEBUG
#define DLOG(fmt, args...)  kprintf(fmt, ## args)
#else
#define DLOG(fmt, args...)
#endif

// Controller supported modes.
//
#define PIOModes   \
    (_provider->getPIOModeMask() & ((1 << piixPIOTimingCount) - 1))

#define DMAModes   \
    (_provider->getDMAModeMask() & ((1 << piixDMATimingCount) - 1))

#define UDMAModes  \
    (_provider->getUltraDMAModeMask() & ((1 << piixUDMATimingCount) - 1))

//---------------------------------------------------------------------------
//
// Start the single-channel PIIX ATA controller driver.
//

bool AppleIntelPIIXPATA::start( IOService * provider )
{
    DLOG("%s::%s( %p )\n", getName(), __FUNCTION__, provider);

    // Our provider is a 'nub' that represents a single channel
    // PIIX ATA controller. Note that it is not an IOPCIDevice.

    _provider = OSDynamicCast( AppleIntelPIIXATAChannel, provider );
    if ( _provider == 0 )
        goto fail;

    // Retain and open our provider. The IOPCIDevice object will be
    // returned by the provider if the open was successful.

    _provider->retain();

    if ( ( _provider->open( this, 0, &_pciDevice ) != true ) ||
         ( _pciDevice == 0 ) )
    {
        IOLog("%s: provider open failed\n", getName());
        goto fail;
    }

    // Cache controller properties, and validate them.

    _cmdBlock = _provider->getCommandBlockAddress();
    _ctrBlock = _provider->getControlBlockAddress();
    _channel  = _provider->getChannelNumber();

    if ( _channel > kPIIX_CHANNEL_SECONDARY )
    {
        IOLog("%s: invalid ATA channel number %ld\n", getName(), _channel);
        goto fail;
    }

    // Configure the PIIX device.

    if ( configurePCIDevice( _pciDevice, _channel ) != true )
    {
        // IOLog("%s: PIIX PCI configuration failed\n", getName());
        goto fail;
    }

    // Get the base address for the bus master registers in I/O space.

    if ( getBMBaseAddress( _pciDevice, _channel, &_ioBMOffset ) != true )
    {
        IOLog("%s: get bus-master base address failed\n", getName());
        goto fail;
    }

    // Must setup these variables inherited from IOPCIATA before
    // calling its start().

    _bmCommandReg   = IOATAIOReg8::withAddress( _ioBMOffset + kPIIX_IO_BMICX );
    _bmStatusReg    = IOATAIOReg8::withAddress( _ioBMOffset + kPIIX_IO_BMISX );
    _bmPRDAddresReg = IOATAIOReg32::withAddress( _ioBMOffset + kPIIX_IO_BMIDTPX );

    // Reset controller timings for both drives.

    resetTimingsForDevice( kATADevice0DeviceID );
    resetTimingsForDevice( kATADevice1DeviceID );

    // Call super after resolving _cmdBlock and _ctrBlock. This is because our
    // configureTFPointers() function will be called by super.

    if ( super::start(_provider) == false )
    {
        goto fail;
    }

    // This driver will handle interrupts using a work loop.
    // Create interrupt event source that will signal the
    // work loop (thread) when a device interrupt occurs.

    if ( _provider->getInterruptVector() == 14 ||
         _provider->getInterruptVector() == 15 )
    {
        // Legacy IRQ are never shared, no need for an interrupt filter.

        _intSrc = IOInterruptEventSource::interruptEventSource(
                      this, &interruptOccurred,
                      _provider, 0 );
    }
    else
    {
        _intSrc = IOFilterInterruptEventSource::filterInterruptEventSource(
                      this, &interruptOccurred, &interruptFilter,
                      _provider, 0 );
    }

    if ( !_intSrc || !_workLoop ||
         (_workLoop->addEventSource(_intSrc) != kIOReturnSuccess) )
    {
        IOLog("%s: interrupt registration error\n", getName());
        goto fail;
    }
    _intSrc->enable();

    // For each device discovered on the ATA bus (by super),
    // create a nub for that device and call registerService() to
    // trigger matching against that device.

    for ( UInt32 i = 0; i < kMaxDrives; i++ )
    {
        if ( _devInfo[i].type != kUnknownATADeviceType )
        {
            ATADeviceNub * nub;

            nub = ATADeviceNub::ataDeviceNub( (IOATAController*) this,
                                              (ataUnitID) i,
                                              _devInfo[i].type );

            if ( nub )
            {
                if ( nub->attach( this ) )
                {
                    _nub[i] = (IOATADevice *) nub;
                    _nub[i]->retain();
                    _nub[i]->registerService();
                }
                nub->release();
            }
        }
    }

    // Successful start, announce our vital properties.

    IOLog("%s: %s (CMD 0x%x, CTR 0x%x, IRQ %d, BM 0x%x)\n", getName(),
          _provider->getControllerName(), _cmdBlock, _ctrBlock,
          _provider->getInterruptVector(), _ioBMOffset);

    return true;

fail:
    
    if ( _provider )
    {
        _provider->close( this );
    }
    
    return false;
}

/*---------------------------------------------------------------------------
 *
 * Release resources before this object is destroyed.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::free( void )
{
#define RELEASE(x) do { if(x) { (x)->release(); (x) = 0; } } while(0)

    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    // Release resources created by start().

    RELEASE( _intSrc         );
    RELEASE( _nub[0]         );
    RELEASE( _nub[1]         );
    RELEASE( _provider       );
    RELEASE( _bmCommandReg   );
    RELEASE( _bmStatusReg    );
    RELEASE( _bmPRDAddresReg );

    // Release registers created by configureTFPointers().

    RELEASE( _tfDataReg      );
    RELEASE( _tfFeatureReg   );
    RELEASE( _tfSCountReg    );
    RELEASE( _tfSectorNReg   );
    RELEASE( _tfCylLoReg     );
    RELEASE( _tfCylHiReg     );
    RELEASE( _tfSDHReg       );
    RELEASE( _tfStatusCmdReg );
    RELEASE( _tfAltSDevCReg  );

    // IOATAController should release this.

    if ( _doubleBuffer.logicalBuffer )
    {
        IOFree( (void *) _doubleBuffer.logicalBuffer,
                         _doubleBuffer.bufferSize );
        _doubleBuffer.bufferSize     = 0;
        _doubleBuffer.logicalBuffer  = 0;
        _doubleBuffer.physicalBuffer = 0;
    }

    // What about _workloop, _cmdGate, and _timer in the superclass?

    super::free();
}

/*---------------------------------------------------------------------------
 *
 * Return a new work loop object, or the one (we) previously created.
 *
 ---------------------------------------------------------------------------*/

IOWorkLoop * AppleIntelPIIXPATA::getWorkLoop( void ) const
{
    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    return ( _workLoop ) ? _workLoop :
                           IOWorkLoop::workLoop();
}

/*---------------------------------------------------------------------------
 *
 * Override IOATAController::synchronousIO()
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::synchronousIO( void )
{
    IOReturn ret;
    
    // IOATAController::synchronousIO() asserts nIEN bit in order to disable
    // drive interrupts during polled mode command execution. The problem is
    // that this will float the INTRQ line and put it in high impedance state,
    // which on certain systems has the undesirable effect of latching a false
    // interrupt on the interrupt controller. Perhaps those systems lack a
    // strong pull down resistor on the INTRQ line. Experiment shows that the
    // interrupt event source is signalled, and its producerCount incremented
    // after every synchronousIO() call. This false interrupt can become
    // catastrophic after reverting to async operations since software can
    // issue a command, handle the false interrupt, and issue another command
    // to the drive before the actual completion of the first command, leading
    // to a irrecoverable bus hang. This function is called after an ATA bus
    // reset. Waking from system sleep will exercise this path.
    // The workaround is to mask the interrupt line while the INTRQ line is
    // floating (or bouncing).

    if (_intSrc) _intSrc->disable();
    ret = super::synchronousIO();
    if (_intSrc) _intSrc->enable();

    return ret;
}

/*---------------------------------------------------------------------------
 *
 * Configure the PIIX PCI device.
 *
 ---------------------------------------------------------------------------*/

bool AppleIntelPIIXPATA::configurePCIDevice( IOPCIDevice * device,
                                             UInt32        channel )
{
    UInt32 reg;

    DLOG("%s::%s( %p, %ld )\n", getName(), __FUNCTION__,
         device, channel);

    // Fetch the corresponding primary/secondary IDETIM register and
    // check the individual channel enable bit. We assume that the
    // master IOSE bit was already checked by our provider.

    reg = device->configRead32( kPIIX_PCI_IDETIM );

    if ( channel == kPIIX_CHANNEL_SECONDARY )
        reg >>= 16;  // kPIIX_PCI_IDETIM + 2 for secondary channel

    if ( (reg & kPIIX_PCI_IDETIM_IDE) == 0 )
    {
        IOLog("%s: %s PCI IDE channel is disabled\n", getName(),
              (channel == kPIIX_CHANNEL_PRIMARY) ? "Primary" : "Secondary");
        return false;
    }

    // Enable bus-master. The previous state of the bit is returned
    // but ignored.

    device->setBusMasterEnable( true );

    // Read the IDE config register containing the Ultra DMA clock control,
    // and 80-conductor cable reporting bits.

    _ideConfig = device->configRead16( kPIIX_PCI_IDECONFIG );
    DLOG("%s: IDE_CONFIG = %04x\n", getName(), _ideConfig);

    return true;
}

/*---------------------------------------------------------------------------
 *
 * Determine the start of the I/O mapped Bus-Master registers.
 * This range is defined by PCI config space register kPIIX_PCI_BMIBA.
 *
 ---------------------------------------------------------------------------*/

bool AppleIntelPIIXPATA::getBMBaseAddress( IOPCIDevice * provider,
                                           UInt32        channel,
                                           UInt16 *      addrOut )
{
    UInt32 bmiba;

    DLOG("%s::%s( %p, %ld, %p )\n", getName(), __FUNCTION__,
         provider, channel, addrOut);

    bmiba = provider->configRead32( kPIIX_PCI_BMIBA );

    if ( (bmiba & kPIIX_PCI_BMIBA_RTE) == 0 )
    {
        IOLog("%s: PCI memory range 0x%02x (0x%08lx) is not an I/O range\n",
              getName(), kPIIX_PCI_BMIBA, bmiba);
        return false;
    }

    bmiba &= kPIIX_PCI_BMIBA_MASK;  // get the address portion

    // If bmiba is zero, it is likely that the user has elected to
    // turn off PCI IDE support in the BIOS.

    if ( bmiba == 0 )
        return false;

    if ( channel == kPIIX_CHANNEL_SECONDARY )
        bmiba += kPIIX_IO_BM_OFFSET;

    *addrOut = (UInt16) bmiba;

    DLOG("%s::%s ioBMOffset = %04x\n", getName(), __FUNCTION__, *addrOut);

    return true;
}

/*---------------------------------------------------------------------------
 *
 * Reset all timing registers to the slowest (most compatible) timing.
 * UDMA modes are disabled.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::resetTimingsForDevice( ataUnitID unit )
{
    _pioTiming[unit]  = &piixPIOTiming[ 0 ];  // PIO Mode 0
    _dmaTiming[unit]  = 0;
    _udmaTiming[unit] = 0;

    // Compute the timing register values.

    computeTimingRegisters( unit );
    computeUDMATimingRegisters( unit );

    // Write the timing values to hardware.

    writeTimingRegisters();
}

/*---------------------------------------------------------------------------
 *
 * Setup the location of the task file registers.
 *
 ---------------------------------------------------------------------------*/

bool AppleIntelPIIXPATA::configureTFPointers( void )
{
    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    _tfDataReg      = IOATAIOReg16::withAddress( _cmdBlock + 0 );
    _tfFeatureReg   = IOATAIOReg8::withAddress(  _cmdBlock + 1 );
    _tfSCountReg    = IOATAIOReg8::withAddress(  _cmdBlock + 2 );
    _tfSectorNReg   = IOATAIOReg8::withAddress(  _cmdBlock + 3 );
    _tfCylLoReg     = IOATAIOReg8::withAddress(  _cmdBlock + 4 );
    _tfCylHiReg     = IOATAIOReg8::withAddress(  _cmdBlock + 5 );
    _tfSDHReg       = IOATAIOReg8::withAddress(  _cmdBlock + 6 );
    _tfStatusCmdReg = IOATAIOReg8::withAddress(  _cmdBlock + 7 );
    _tfAltSDevCReg  = IOATAIOReg8::withAddress(  _ctrBlock + 2 );

    if ( !_tfDataReg || !_tfFeatureReg || !_tfSCountReg ||
         !_tfSectorNReg || !_tfCylLoReg || !_tfCylHiReg ||
         !_tfSDHReg || !_tfStatusCmdReg || !_tfAltSDevCReg )
    {
        return false;
    }

    return true;
}

/*---------------------------------------------------------------------------
 *
 * Filter interrupts that are not originated by our hardware. This will help
 * to prevent waking up our work loop thread when a shared interrupt line is
 * asserted by another device.
 *
 ---------------------------------------------------------------------------*/

bool AppleIntelPIIXPATA::interruptFilter( OSObject * owner,
                                          IOFilterInterruptEventSource * src )
{
    AppleIntelPIIXPATA * self = (AppleIntelPIIXPATA *) owner;

    if ( *(self->_bmStatusReg) & kPIIX_IO_BMISX_IDEINTS )
        return true;   // wakeup the work loop
    else
        return false;  // ignore this interrupt
}

/*---------------------------------------------------------------------------
 *
 * The work loop based interrupt handler called by our interrupt event
 * source.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::interruptOccurred( OSObject *               owner,
                                            IOInterruptEventSource * src,
                                            int                      count )
{
    AppleIntelPIIXPATA * self = (AppleIntelPIIXPATA *) owner;

    // Clear interrupt latch

    *(self->_bmStatusReg) = kPIIX_IO_BMISX_IDEINTS;

    // Let our superclass handle the interrupt to advance to the next state
    // in its internal state machine.
    
    self->handleDeviceInterrupt();
}

/*---------------------------------------------------------------------------
 *
 * Extend the implementation of scanForDrives() from IOATAController
 * to issue a soft reset before scanning for ATA/ATAPI drive signatures.
 *
 ---------------------------------------------------------------------------*/

UInt32 AppleIntelPIIXPATA::scanForDrives( void )
{
    UInt32 unitsFound;

    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    *_tfAltSDevCReg = mATADCRReset;

    IODelay( 100 );

    *_tfAltSDevCReg = 0x0;

    IOSleep( 10 );

    unitsFound = super::scanForDrives();

#if ENABLE_VPC4_DRIVESCAN_WORKAROUND
    // FIXME: Hack for Darwin/x86 on VPC compatibility.
    // VPC 4.0 will set the error bit (bit 0) in the status register
    // following an assertion of SRST. The scanForDrives() code in
    // IOATAController will see that this bit is set, and will not
    // recognize the ATA device. The following code will re-scan the
    // bus and ignore the error bit.

    for ( int unit = 0; ((unit < 2) && (unitsFound < 2)); unit++ )
    {
        if ( _devInfo[unit].type == kUnknownATADeviceType )
        {
            UInt32 milsSpent;

            // Select unit and wait for a not busy bus.

            for ( milsSpent = 0; milsSpent < 10000; )
            {
                *_tfSDHReg = ( unit << 4 );
                IODelay( 10 );

                if ( (*_tfStatusCmdReg & mATABusy) == 0x00 ) break;

                IOSleep( 10 );
                milsSpent += 10;
            }
            if ( milsSpent >= 10000 ) break;

            // Ignore the error bit in the status register, and check
            // for a ATA device signature.

            if ( (*_tfCylLoReg == 0x00) && (*_tfCylHiReg == 0x00) &&
                 (*_tfSCountReg == 0x01) && (*_tfSectorNReg == 0x01) &&
                 ( (*_tfAltSDevCReg & 0x50) == 0x50) )
            {
                _devInfo[unit].type = kATADeviceType;
                _devInfo[unit].packetSend = kATAPIUnknown;
                unitsFound++;
            }
        }
    }
#endif

    *_tfSDHReg = 0x00;  // Initialize device selection to device 0.

    return unitsFound;
}

/*---------------------------------------------------------------------------
 *
 * Provide information on the bus capability.
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::provideBusInfo( IOATABusInfo * infoOut )
{
    DLOG("%s::%s( %p )\n", getName(), __FUNCTION__, infoOut);

    if ( infoOut == 0 )
    {
        IOLog("%s::%s bad argument\n", getName(), __FUNCTION__);
        return -1;
    }

    infoOut->zeroData();
    infoOut->setSocketType( kInternalATASocket );

    infoOut->setPIOModes( PIOModes );
    infoOut->setDMAModes( DMAModes );
    infoOut->setUltraModes( UDMAModes );
    infoOut->setExtendedLBA( true );

    UInt8 units = 0;
    if ( _devInfo[0].type != kUnknownATADeviceType ) units++;
    if ( _devInfo[1].type != kUnknownATADeviceType ) units++;

    infoOut->setUnits( units );

    return kATANoErr;
}

/*---------------------------------------------------------------------------
 *
 * Return the currently configured timing for the drive unit.
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::getConfig( IOATADevConfig * configOut,
                                        UInt32           unit )
{
    DLOG("%s::%s( %p, %ld )\n", getName(), __FUNCTION__,
         configOut, unit);

    if ( (configOut == 0) || (unit > kATADevice1DeviceID) )
    {
        IOLog("%s::%s bad argument\n", getName(), __FUNCTION__);
        return -1;
    }

    configOut->setPIOMode( 0 );
    configOut->setDMAMode( 0 );
    configOut->setUltraMode( 0 );

    if ( _pioTiming[unit] )
    {
        configOut->setPIOMode( 1 << _pioTiming[unit]->mode );
        configOut->setPIOCycleTime( _pioTiming[unit]->cycleTime );
    }

    if ( _dmaTiming[unit] )
    {
        configOut->setDMAMode( 1 << _dmaTiming[unit]->mode );
        configOut->setDMACycleTime( _dmaTiming[unit]->cycleTime );
    }

    if ( _udmaTiming[unit] )
    {
        configOut->setUltraMode( 1 << _udmaTiming[unit]->mode );
    }

    configOut->setPacketConfig( _devInfo[unit].packetSend );

    return kATANoErr;
}

/*---------------------------------------------------------------------------
 *
 * Select the bus timings for a given drive.
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::selectConfig( IOATADevConfig * config,
                                           UInt32           unit )
{
    const PIIXTiming *     pioTiming  = 0;
    const PIIXTiming *     dmaTiming  = 0;
    const PIIXUDMATiming * udmaTiming = 0;

    DLOG("%s::%s( %p, %ld )\n", getName(), __FUNCTION__,
         config, unit);

    if ( (config == 0) || (unit > kATADevice1DeviceID) )
    {
        IOLog("%s::%s bad argument\n", getName(), __FUNCTION__);
        return -1;
    }

    removeProperty( kSelectedPIOModeKey );
    removeProperty( kSelectedDMAModeKey );
    removeProperty( kSelectedUltraDMAModeKey );

    // Verify that the selected PIO mode is supported.

    if ( ( config->getPIOMode() & PIOModes ) == 0 )
    {
        DLOG("%s: Unsupported PIO mode %x\n", getName(), config->getPIOMode());
        pioTiming = &piixPIOTiming[0];
    }
    else
    {
        UInt8  pioModeNumber;

        // Convert a bit-significant indicators to a numeric values.

        pioModeNumber = bitSigToNumeric( config->getPIOMode() );
        DLOG("%s: pioModeNumber = %d\n", getName(), pioModeNumber);

        // Even though the drive supports the given PIO mode, it is
        // necessary to check the cycle time reported by the drive,
        // and downgrade the controller timings if the drive cannot
        // support the cycle time for the target PIO mode.

        for ( ; pioModeNumber > 0; pioModeNumber-- )
        {
            // If the mode is supported by the controller, and the
            // drive supported cycle time is less than or equal to
            // the mode's cycle time, then select the mode.

            if ( ( ( (1 << pioModeNumber) & PIOModes ) ) &&
                 ( config->getPIOCycleTime() <=
                   piixPIOTiming[ pioModeNumber ].cycleTime ) )
            {
                break;
            }

            DLOG("%s: pioModeNumber = %d\n", getName(), pioModeNumber - 1);
        }

        setDriveProperty( unit, kSelectedPIOModeKey, pioModeNumber, 8 );
        pioTiming = &piixPIOTiming[ pioModeNumber ];
    }

    // Look at the selected Multi-Word DMA mode.

    if ( config->getDMAMode() )
    {
        UInt8  dmaModeNumber;

        // Is the selected DMA mode supported?

        if ( ( config->getDMAMode() & DMAModes ) == 0 )
        {
            DLOG("%s: Unsupported DMA mode\n", getName());
            return kATAModeNotSupported;
        }

        dmaModeNumber = bitSigToNumeric( config->getDMAMode() );
        DLOG("%s: dmaModeNumber = %d\n", getName(), dmaModeNumber);

        // Even though the drive supports the given DMA mode, it is
        // necessary to check the cycle time reported by the drive,
        // and downgrade the controller timings if the drive cannot
        // support the cycle time for the target DMA mode.

        for ( ; dmaModeNumber > 0; dmaModeNumber-- )
        {
            // If the mode is supported by the controller, and the
            // drive supported cycle time is less than or equal to
            // the mode's cycle time, then select the mode.

            if ( ( ( (1 << dmaModeNumber) & DMAModes ) ) &&
                 ( config->getDMACycleTime() <=
                   piixDMATiming[ dmaModeNumber ].cycleTime ) )
            {
                break;
            }

            DLOG("%s: dmaModeNumber = %d\n", getName(), dmaModeNumber - 1);
        }

        if ( (1 << dmaModeNumber) & DMAModes )
        {
            setDriveProperty( unit, kSelectedDMAModeKey, dmaModeNumber, 8 );
            dmaTiming = &piixDMATiming[ dmaModeNumber ];
        }
        else
            dmaTiming = 0;  // No Multi-Word DMA mode selected.
    }

    // Look at the selected U-DMA mode.

    if ( config->getUltraMode() )
    {
        UInt8 udmaModeNumber;
    
        // Is the selected Ultra-DMA mode supported?

        if ( ( config->getUltraMode() & UDMAModes ) == 0 )
        {
            DLOG("%s: Unsupported U-DMA mode\n", getName());
            return kATAModeNotSupported;
        }

        udmaModeNumber = bitSigToNumeric( config->getUltraMode() );

        // For Ultra DMA mode 3 or higher, a 80-conductor cable must
        // be present. Otherwise, the drive will be limited to mode 2.

        if ( udmaModeNumber > 2 )
        {
            UInt16 cableMask = kPIIX_PCI_IDECONFIG_PCR0;
            if ( unit == kATADevice1DeviceID )         cableMask <<= 1;
            if ( _channel == kPIIX_CHANNEL_SECONDARY ) cableMask <<= 2;

            if ( ( cableMask & _ideConfig ) == 0 )
            {
                DLOG("%s: 80-conductor cable not detected\n", getName());
                udmaModeNumber = 2;   // limited to mode 2
            }
        }

        setDriveProperty( unit, kSelectedUltraDMAModeKey, udmaModeNumber, 8 );
        DLOG("%s: udmaModeNumber = %d\n", getName(), udmaModeNumber);
        udmaTiming = &piixUDMATiming[ udmaModeNumber ];
    }

    // If DMA and PIO require different timings, we use fast
    // timing for DMA only, and revert to compatible timing
    // for PIO data register access.

    if ( dmaTiming && (pioTiming->cycleTime != dmaTiming->cycleTime) )
    {
        DLOG("%s: forcing PIO compatible timing\n", getName());
        pioTiming = &piixPIOTiming[0];
    }

    // Cache the selected timings.

    _pioTiming[unit]  = pioTiming;
    _dmaTiming[unit]  = dmaTiming;
    _udmaTiming[unit] = udmaTiming;

    // Compute the timing register values.

    computeTimingRegisters( (ataUnitID) unit );
    computeUDMATimingRegisters( (ataUnitID) unit );

    // Write the timing values to hardware.

    writeTimingRegisters();

    _devInfo[unit].packetSend = config->getPacketConfig();

    return getConfig( config, unit );
}

/*---------------------------------------------------------------------------
 *
 * Write the timing values to the PIIX timing registers.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::writeTimingRegisters( ataUnitID unit )
{
    UInt8  idetimOffset;
    UInt8  sidetimMask;
    UInt8  udmactlMask;
    UInt16 udmatimMask;
    UInt16 ideConfigMask;

    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    if ( _channel == kPIIX_CHANNEL_PRIMARY )
    {
        idetimOffset = kPIIX_PCI_IDETIM;
    
        sidetimMask  = kPIIX_PCI_SIDETIM_PRTC1_MASK |
                       kPIIX_PCI_SIDETIM_PISP1_MASK;
        
        udmactlMask  = kPIIX_PCI_UDMACTL_PSDE0 |
                       kPIIX_PCI_UDMACTL_PSDE1;
        
        udmatimMask  = kPIIX_PCI_UDMATIM_PCT0_MASK |
                       kPIIX_PCI_UDMATIM_PCT1_MASK;
    
        ideConfigMask = kPIIX_PCI_IDECONFIG_PCB0      |
                        kPIIX_PCI_IDECONFIG_PCB1      |
                        kPIIX_PCI_IDECONFIG_FAST_PCB0 |
                        kPIIX_PCI_IDECONFIG_FAST_PCB1 |
                        kPIIX_PCI_IDECONFIG_WR_PP_EN;
    }
    else
    {
        idetimOffset = kPIIX_PCI_IDETIM_S;

        sidetimMask  = kPIIX_PCI_SIDETIM_SRTC1_MASK |
                       kPIIX_PCI_SIDETIM_SISP1_MASK;

        udmactlMask  = kPIIX_PCI_UDMACTL_SSDE0 |
                       kPIIX_PCI_UDMACTL_SSDE1;

        udmatimMask  = kPIIX_PCI_UDMATIM_SCT0_MASK |
                       kPIIX_PCI_UDMATIM_SCT1_MASK;

        ideConfigMask = kPIIX_PCI_IDECONFIG_SCB0      |
                        kPIIX_PCI_IDECONFIG_SCB1      |
                        kPIIX_PCI_IDECONFIG_FAST_SCB0 |
                        kPIIX_PCI_IDECONFIG_FAST_SCB1 |
                        kPIIX_PCI_IDECONFIG_WR_PP_EN;
    }

    // Timing registers are shared between primary and secondary ATA
    // channels. Call the PCI config space write functions in our
    // provider to serialize changes in those timing registers.

    if ( _provider->hasSharedDriveTimings() == true )
    {
        _provider->pciConfigWrite16( idetimOffset, _idetim[unit] );
        
        DLOG("%s: IDETIM[%d] = %04x\n", getName(), unit, _idetim[unit]);
    }
    else
    {
        _provider->pciConfigWrite16( idetimOffset, _idetim[0] | _idetim[1]);

        _provider->pciConfigWrite8( kPIIX_PCI_SIDETIM, _sidetim, sidetimMask );

        _provider->pciConfigWrite8( kPIIX_PCI_UDMACTL, _udmactl, udmactlMask );

        _provider->pciConfigWrite16( kPIIX_PCI_UDMATIM, _udmatim, udmatimMask );

        _provider->pciConfigWrite16( kPIIX_PCI_IDECONFIG,
                                     _ideConfig, ideConfigMask );

        DLOG("%s: IDETIM = %04x SIDETIM = %02x UDMACTL = %02x UDMATIM = %04x\n",
             getName(),
             _idetim[0] | _idetim[1], _sidetim, _udmactl, _udmatim);
    }
}

/*---------------------------------------------------------------------------
 *
 * Compute the U-DMA timing register values.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::computeUDMATimingRegisters( ataUnitID unit )
{
    UInt8  udmaEnableBit   = kPIIX_PCI_UDMACTL_PSDE0;
    UInt8  udmatimShifts   = kPIIX_PCI_UDMATIM_PCT0_SHIFT;
    UInt8  udmaClockShifts = 0;

    DLOG("%s::%s( %d )\n", getName(), __FUNCTION__, unit);

    if ( _channel == kPIIX_CHANNEL_SECONDARY )
    {
        udmaEnableBit  <<= 2;
        udmatimShifts   += 8;
        udmaClockShifts += 2;
    }
    if ( unit == kATADevice1DeviceID )
    {
        udmaEnableBit  <<= 1;
        udmatimShifts   += 4;
        udmaClockShifts += 1;
    }

    // Disable U-DMA for this drive unit.

    _udmactl &= ~udmaEnableBit;

    // If U-DMA is enabled for this unit, update timing register
    // and enable U-DMA.

    if ( _udmaTiming[unit] )
    {
        _udmatim &= ~( kPIIX_PCI_UDMATIM_PCT0_MASK << udmatimShifts );
        _udmatim |= (( _udmaTiming[unit]->udmatim << udmatimShifts ) &
                     ( kPIIX_PCI_UDMATIM_PCT0_MASK << udmatimShifts ));

        _ideConfig &= ~(( kPIIX_PCI_IDECONFIG_PCB0 |
                          kPIIX_PCI_IDECONFIG_FAST_PCB0 ) << udmaClockShifts);
        _ideConfig |= ( _udmaTiming[unit]->udmaClock << udmaClockShifts );

        _udmactl |= udmaEnableBit;
    }
}

/*---------------------------------------------------------------------------
 *
 * Compute the PIO/DMA timing register values.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::computeTimingRegisters( ataUnitID unit )
{
    const PIIXTiming * timing;
    UInt8              index;
    bool               slowPIO = false;

    DLOG("%s::%s( %d )\n", getName(), __FUNCTION__, unit);

    assert( _pioTiming[unit] != 0 );

    timing = _dmaTiming[unit] ? _dmaTiming[unit] : _pioTiming[unit];

    if ( timing->cycleTime != _pioTiming[unit]->cycleTime )
    {
        DLOG("%s: using PIO compatible timing\n", getName());
        slowPIO = true;
    }

    // Get register programming index.

    index = timing->registerIndex;

    if ( _provider->hasSharedDriveTimings() == true )
    {
        _idetim[unit] = piixIDETIM[index][unit];
    }
    else /* PIIX3 or better */
    {
        _idetim[unit] = piix3IDETIM[index][unit];

        /* Update SIDETIM register for Drive 1 */

        if ( unit == kATADevice1DeviceID )
        {
            _sidetim &= ~piix3SIDETIM[0][_channel];
            _sidetim |=  piix3SIDETIM[index][_channel];
        }

        // Always enable the PIO performance feature on
        // ICH controllers.
    
        _ideConfig |= kPIIX_PCI_IDECONFIG_WR_PP_EN;
    }

    if ( slowPIO )
    {
        if ( unit == kATADevice1DeviceID )
            _idetim[unit] &= ~kPIIX_PCI_IDETIM_DTE1;
        else
            _idetim[unit] &= ~kPIIX_PCI_IDETIM_DTE0;
    }
}

/*---------------------------------------------------------------------------
 *
 * Select the bus timing configuration for a particular device.
 *
 ---------------------------------------------------------------------------*/

void AppleIntelPIIXPATA::selectIOTiming( ataUnitID unit )
{
    if ( _provider->hasSharedDriveTimings() == true )
    {
        DLOG("%s::%s( %d )\n", getName(), __FUNCTION__, unit);
        writeTimingRegisters( unit );
    }
}

/*---------------------------------------------------------------------------
 *
 * Flush the outstanding commands in the command queue.
 * Implementation borrowed from MacIOATA in IOATAFamily.
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::handleQueueFlush( void )
{
    UInt32 savedQstate = _queueState;

    DLOG("%s::%s()\n", getName(), __FUNCTION__);

    _queueState = IOATAController::kQueueLocked;

    IOATABusCommand * cmdPtr = 0;

    while ( cmdPtr = dequeueFirstCommand() )
    {
        cmdPtr->setResult( kIOReturnError );
        cmdPtr->executeCallback();
    }

    _queueState = savedQstate;

    return kATANoErr;
}

/*---------------------------------------------------------------------------
 *
 * Handle termination notification from the provider.
 *
 ---------------------------------------------------------------------------*/

IOReturn AppleIntelPIIXPATA::message( UInt32      type,
                                      IOService * provider,
                                      void *      argument )
{
    if ( ( provider == _provider ) &&
         ( type == kIOMessageServiceIsTerminated ) )
    {
        _provider->close( this );
        return kIOReturnSuccess;
    }

    return super::message( type, provider, argument );
}

/*---------------------------------------------------------------------------
 *
 * Publish a numeric property pertaining to a drive to the registry.
 *
 ---------------------------------------------------------------------------*/

bool AppleIntelPIIXPATA::setDriveProperty( UInt32       driveUnit,
                                           const char * key,
                                           UInt32       value,
                                           UInt32       numberOfBits)
{
    char keyString[40];
    
    snprintf(keyString, 40, "Drive %ld %s", driveUnit, key);
    
    return super::setProperty( keyString, value, numberOfBits );
}
