/*******************************************************************************
Copyright (C) Marvell International Ltd. and its affiliates

This software file (the "File") is owned and distributed by Marvell 
International Ltd. and/or its affiliates ("Marvell") under the following
alternative licensing terms.  Once you have made an election to distribute the
File under one of the following license alternatives, please (i) delete this
introductory statement regarding license alternatives, (ii) delete the two
license alternatives that you have not elected to use and (iii) preserve the
Marvell copyright notice above.


********************************************************************************
Marvell GPL License Option

If you received this File from Marvell, you may opt to use, redistribute and/or 
modify this File in accordance with the terms and conditions of the General 
Public License Version 2, June 1991 (the "GPL License"), a copy of which is 
available along with the File in the license.txt file or by writing to the Free 
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 or 
on the worldwide web at http://www.gnu.org/licenses/gpl.txt. 

THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED 
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY 
DISCLAIMED.  The GPL License provides additional details about this warranty 
disclaimer.
*******************************************************************************/
/*******************************************************************************
* file_name - mvLinuxIALSmart.c
*
* DESCRIPTION: C file for S.M.A.R.T. features - smartmontools app
*
* DEPENDENCIES:
*   None.
*
*
*******************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/kdev_t.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <asm/dma.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_tcq.h>
#else
#include <linux/blk.h>
#include "scsi.h"
#include "hosts.h"
#endif
#include "mvScsiAtaLayer.h"
#include "mvLinuxIalHt.h"
#include "mvRegs.h"
#include "mvIALCommon.h"
#include "mvLinuxIalSmart.h"


extern MV_VOID handleNoneUdmaError(MV_SATA_SCSI_CMD_BLOCK  *pScb,
                                   MV_STORAGE_DEVICE_REGISTERS *registerStruct);

extern MV_VOID handleUdmaError(MV_SATA_SCSI_CMD_BLOCK  *pScb,
                               MV_U32 responseFlags,
                               MV_STORAGE_DEVICE_REGISTERS *registerStruct);

extern MV_VOID  checkQueueCommandResult(MV_SATA_SCSI_CMD_BLOCK *pScb,
                                        MV_QUEUE_COMMAND_RESULT result);

extern MV_VOID setSenseData(IN MV_SATA_SCSI_CMD_BLOCK *pScb, IN MV_U8 SenseKey,
                            IN MV_U8 AdditionalSenseCode);

static MV_BOOLEAN
SmartCommandCompletionCB(MV_SATA_ADAPTER *pSataAdapter,
                         MV_U8 channelNum,
                         MV_COMPLETION_TYPE comp_type,
                         MV_VOID_PTR commandId,
                         MV_U16 responseFlags,
                         MV_U32 timeStamp,
                         MV_STORAGE_DEVICE_REGISTERS *registerStruct);

static void swap_buf_le16(u16 *buf, unsigned int buf_words)
{
#ifdef __BIG_ENDIAN
     unsigned int i;

     for (i = 0; i < buf_words; i++)
	  buf[i] = le16_to_cpu(buf[i]);
#endif /* __BIG_ENDIAN */
}

MV_SCSI_COMMAND_STATUS_TYPE  mvScsiAtaSendSmartCommand(IN  MV_SATA_ADAPTER* pSataAdapter,
                                                       IN  MV_SATA_SCSI_CMD_BLOCK *pScb)
{
    MV_U8 *buff = (MV_U8*)pScb->pDataBuffer;
    MV_NON_UDMA_PROTOCOL protocolType = MV_NON_UDMA_PROTOCOL_NON_DATA;
    MV_QUEUE_COMMAND_RESULT result = MV_QUEUE_COMMAND_RESULT_OK;
    MV_QUEUE_COMMAND_INFO   qCommandInfo;
    MV_SATA_SCSI_DRIVE_DATA *pDriveData = &pScb->pSalAdapterExtension->ataDriveData[pScb->bus][pScb->target];

    mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART command: command received, bufflen = %d.\n",
             pScb->dataBufferLength);
    pScb->dataTransfered = 0;
    pScb->senseDataLength = 0;
    if (pScb->bus >= pSataAdapter->numberOfChannels)
    {
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_INVALID_BUS;
        pScb->dataTransfered = 0;
        pScb->completionCallBack(pSataAdapter, pScb);
        return MV_SCSI_COMMAND_STATUS_COMPLETED;
    }

    if ((pScb->target >= MV_SATA_PM_MAX_PORTS) ||
        (pScb->pSalAdapterExtension->ataDriveData[pScb->bus][pScb->target].driveReady == MV_FALSE))
    {
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_NO_DEVICE;
        pScb->dataTransfered = 0;
        pScb->completionCallBack(pSataAdapter, pScb);
        return MV_SCSI_COMMAND_STATUS_COMPLETED;
    }

    if (pScb->dataBufferLength <= 6 || /*six byte opcode*/
        (buff[SMART_BUF_COMMAND_OFFSET] != WIN_IDENTIFY &&
        buff[SMART_BUF_COMMAND_OFFSET] != MV_ATA_COMMAND_IDLE &&
        buff[SMART_BUF_COMMAND_OFFSET] != MV_ATA_COMMAND_IDLE_IMMEDIATE &&
        buff[SMART_BUF_COMMAND_OFFSET] != MV_ATA_COMMAND_STANDBY_IMMEDIATE &&
         buff[SMART_BUF_COMMAND_OFFSET] != WIN_SMART))
    {
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, "invalid SMART command received");
        setSenseData(pScb, SCSI_SENSE_ILLEGAL_REQUEST,
                     SCSI_ADSENSE_NO_SENSE);
        pScb->ScsiStatus = MV_SCSI_STATUS_CHECK_CONDITION;
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_BAD_SCSI_COMMAND;
        pScb->completionCallBack(pSataAdapter, pScb);
        return MV_SCSI_COMMAND_STATUS_COMPLETED;
    }

    if (buff[SMART_BUF_COMMAND_OFFSET] == WIN_IDENTIFY)
    {
        if (pScb->dataBufferLength < MV_ATA_IDENTIFY_DEV_DATA_LENGTH*2 + 6)
        {
            mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, "WIN_IDENTIFY: "
                     "invalid buffer length.%d\n", pScb->dataBufferLength);
            setSenseData(pScb, SCSI_SENSE_ILLEGAL_REQUEST,
                         SCSI_ADSENSE_NO_SENSE);
            pScb->ScsiStatus = MV_SCSI_STATUS_CHECK_CONDITION;
            pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_BAD_SCSI_COMMAND;
            pScb->completionCallBack(pSataAdapter, pScb);
            return MV_SCSI_COMMAND_STATUS_COMPLETED;
        }
        protocolType = MV_NON_UDMA_PROTOCOL_PIO_DATA_IN;
        buff[SMART_BUF_COMMAND_OFFSET] = MV_ATA_COMMAND_IDENTIFY;
        buff[SMART_BUF_SECTORCOUNT_OFFSET] = 0;
        buff[SMART_BUF_LBALOW_OFFSET] = 0;
        buff[SMART_BUF_LBAMID_OFFSET] = 0;
        buff[SMART_BUF_LBAHIGH_OFFSET] = 0;
        buff[SMART_BUF_FEATURES_OFFSET] = 0;
    }
    else  if (buff[SMART_BUF_COMMAND_OFFSET] == MV_ATA_COMMAND_IDLE)
    {
         mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART command: IDLE command received, sector count = %d.\n",
              buff[SMART_BUF_SECTORCOUNT_OFFSET]);

        protocolType = MV_NON_UDMA_PROTOCOL_NON_DATA;
        buff[SMART_BUF_COMMAND_OFFSET] = MV_ATA_COMMAND_IDLE;
        buff[SMART_BUF_LBALOW_OFFSET] = 0;
        buff[SMART_BUF_LBAMID_OFFSET] = 0;
        buff[SMART_BUF_LBAHIGH_OFFSET] = 0;
        buff[SMART_BUF_FEATURES_OFFSET] = 0;
    }
    else  if (buff[SMART_BUF_COMMAND_OFFSET] == MV_ATA_COMMAND_IDLE_IMMEDIATE)
    {
         mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART command: IDLE IMMEDIATE command received\n");
              
        protocolType = MV_NON_UDMA_PROTOCOL_NON_DATA;
        buff[SMART_BUF_COMMAND_OFFSET] = MV_ATA_COMMAND_IDLE_IMMEDIATE;
        buff[SMART_BUF_LBALOW_OFFSET] = 0;
        buff[SMART_BUF_LBAMID_OFFSET] = 0;
        buff[SMART_BUF_LBAHIGH_OFFSET] = 0;
        buff[SMART_BUF_FEATURES_OFFSET] = 0;
        buff[SMART_BUF_SECTORCOUNT_OFFSET] = 0;

    }
    else  if (buff[SMART_BUF_COMMAND_OFFSET] == MV_ATA_COMMAND_STANDBY_IMMEDIATE)
    {
         mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART command: STANDBY IMMEDIATE command received\n");
              
        protocolType = MV_NON_UDMA_PROTOCOL_NON_DATA;
        buff[SMART_BUF_COMMAND_OFFSET] = MV_ATA_COMMAND_STANDBY_IMMEDIATE;
        buff[SMART_BUF_LBALOW_OFFSET] = 0;
        buff[SMART_BUF_LBAMID_OFFSET] = 0;
        buff[SMART_BUF_LBAHIGH_OFFSET] = 0;
        buff[SMART_BUF_FEATURES_OFFSET] = 0;
        buff[SMART_BUF_SECTORCOUNT_OFFSET] = 0;

    }
 
    else
    {
        buff[SMART_BUF_LBAMID_OFFSET] = 0x4F;
        buff[SMART_BUF_LBAHIGH_OFFSET] = 0xC2;
        switch (buff[SMART_BUF_FEATURES_OFFSET])
        {
        case SMART_READ_VALUES:
        case SMART_READ_THRESHOLDS:
        case SMART_READ_LOG_SECTOR:
            if (pScb->dataBufferLength < ATA_SECTOR_SIZE + 6)
            {
                setSenseData(pScb, SCSI_SENSE_ILLEGAL_REQUEST,
                             SCSI_ADSENSE_NO_SENSE);
                pScb->ScsiStatus = MV_SCSI_STATUS_CHECK_CONDITION;
                pScb->ScsiCommandCompletion =
                MV_SCSI_COMPLETION_BAD_SCSI_COMMAND;
                pScb->completionCallBack(pSataAdapter, pScb);
                return MV_SCSI_COMMAND_STATUS_COMPLETED;
            }
            protocolType = MV_NON_UDMA_PROTOCOL_PIO_DATA_IN;
            break;
        case SMART_ENABLE:
        case SMART_DISABLE:
        case SMART_AUTO_OFFLINE:
        case SMART_AUTOSAVE:
        case SMART_IMMEDIATE_OFFLINE:
        case SMART_STATUS:
            protocolType = MV_NON_UDMA_PROTOCOL_NON_DATA;
            break;
        default:
            setSenseData(pScb, SCSI_SENSE_ILLEGAL_REQUEST,
                         SCSI_ADSENSE_NO_SENSE);
            pScb->ScsiStatus = MV_SCSI_STATUS_CHECK_CONDITION;
            pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_BAD_SCSI_COMMAND;
            pScb->completionCallBack(pSataAdapter, pScb);
            return MV_SCSI_COMMAND_STATUS_COMPLETED;
        }
    }
    qCommandInfo.type = MV_QUEUED_COMMAND_TYPE_NONE_UDMA;
    qCommandInfo.commandParams.NoneUdmaCommand.protocolType = protocolType;
    qCommandInfo.commandParams.NoneUdmaCommand.isEXT = MV_FALSE;
    qCommandInfo.PMPort = pScb->target;
    if (protocolType == MV_NON_UDMA_PROTOCOL_NON_DATA)
    {
        qCommandInfo.commandParams.NoneUdmaCommand.bufPtr = NULL;
        qCommandInfo.commandParams.NoneUdmaCommand.count = 0;
    }
    else
    {
        qCommandInfo.commandParams.NoneUdmaCommand.bufPtr =
        (MV_U16_PTR)&buff[6];
        qCommandInfo.commandParams.NoneUdmaCommand.count =
        (MV_U32)(ATA_SECTOR_SIZE/2);
        /*in words*/
    }
    qCommandInfo.commandParams.NoneUdmaCommand.features =
    (MV_U16)buff[SMART_BUF_FEATURES_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.sectorCount =
    (MV_U16)buff[SMART_BUF_SECTORCOUNT_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.lbaLow =
    (MV_U16)buff[SMART_BUF_LBALOW_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.lbaMid =
    (MV_U16)buff[SMART_BUF_LBAMID_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.lbaHigh =
    (MV_U16)buff[SMART_BUF_LBAHIGH_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.device = (MV_U8)(MV_BIT6);
    qCommandInfo.commandParams.NoneUdmaCommand.command =
    buff[SMART_BUF_COMMAND_OFFSET];
    qCommandInfo.commandParams.NoneUdmaCommand.callBack =
    SmartCommandCompletionCB;
    qCommandInfo.commandParams.NoneUdmaCommand.commandId = (MV_VOID_PTR) pScb;
    result = mvSataQueueCommand(pSataAdapter, pScb->bus, &qCommandInfo);
    if (result != MV_QUEUE_COMMAND_RESULT_OK)
    {
        checkQueueCommandResult(pScb, result);
        pScb->completionCallBack(pSataAdapter, pScb);
        return MV_SCSI_COMMAND_STATUS_COMPLETED;
    }
    pDriveData->stats.totalIOs++;

    mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART command: SMART command %02X, queued\n",
             buff[SMART_BUF_FEATURES_OFFSET]);
    return MV_SCSI_COMMAND_STATUS_QUEUED;
}

static void SmartFillReturnBuffer(IN MV_U8* buff,
                                  IN MV_STORAGE_DEVICE_REGISTERS *registerStruct)
{

    /*For PIO non-data return registers' values*/
    if (buff[SMART_BUF_COMMAND_OFFSET] == WIN_SMART &&
        buff[SMART_BUF_FEATURES_OFFSET] != SMART_READ_VALUES &&
        buff[SMART_BUF_FEATURES_OFFSET] != SMART_READ_THRESHOLDS &&
        buff[SMART_BUF_FEATURES_OFFSET] != SMART_READ_LOG_SECTOR)
    {
        buff[6+SMART_BUF_COMMAND_OFFSET] =
        buff[SMART_BUF_COMMAND_OFFSET];
        buff[6+SMART_BUF_FEATURES_OFFSET] =
        buff[SMART_BUF_FEATURES_OFFSET];
        buff[6+SMART_BUF_LBALOW_OFFSET] =
        registerStruct->lbaLowRegister & 0xFF;
        buff[6+SMART_BUF_SECTORCOUNT_OFFSET] =
        registerStruct->sectorCountRegister & 0xFF;
        buff[6+SMART_BUF_LBAMID_OFFSET] =
        registerStruct->lbaMidRegister & 0xFF;
        buff[6+SMART_BUF_LBAHIGH_OFFSET] =
        registerStruct->lbaHighRegister & 0xFF;
        buff[6+SMART_BUF_DEVICE_OFFSET] =
        registerStruct->deviceRegister & 0xFF;
        buff[6+SMART_BUF_ERROR_OFFSET] =
        registerStruct->errorRegister & 0xFF;
    }
    else
    {
	 swap_buf_le16((u16 *) (buff + 6), ATA_SECTOR_SIZE/2);
    }
}

static MV_BOOLEAN
SmartCommandCompletionCB(MV_SATA_ADAPTER *pSataAdapter,
                         MV_U8 channelNum,
                         MV_COMPLETION_TYPE comp_type,
                         MV_VOID_PTR commandId,
                         MV_U16 responseFlags,
                         MV_U32 timeStamp,
                         MV_STORAGE_DEVICE_REGISTERS *registerStruct)
{
    MV_SATA_SCSI_CMD_BLOCK  *pScb;
    struct scsi_cmnd   *SCpnt;
    struct mv_comp_info *pInfo;

    if (commandId == NULL)
    {
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, " commandId is NULL, can't hanlde this !!!,adapterId=%d,"
                 " channel=%d \n", pSataAdapter->adapterId, channelNum);
        return MV_FALSE;
    }

    pScb = commandId;
    SCpnt = (struct scsi_cmnd *)pScb->IALData;
    pInfo = ( struct mv_comp_info *) &(SCpnt->SCp);

    switch (comp_type)
    {
    case MV_COMPLETION_TYPE_NORMAL:
        if (pScb->ScsiCdb[0] == SCSI_OPCODE_MVSATA_SMART)
        {
            SmartFillReturnBuffer(pScb->pDataBuffer, registerStruct);
            mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART PIO command completed: "
                     "dev=%04X, Low=%04X, Mid=%04X, High=%04X, "
                     "SC=%04X, status = %04X,\n",
                     registerStruct->deviceRegister,
                     registerStruct->lbaLowRegister,
                     registerStruct->lbaMidRegister,
                     registerStruct->lbaHighRegister,
                     registerStruct->sectorCountRegister,
                     registerStruct->statusRegister);

	    if(pInfo->kmap_buffer)
	    {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
		    struct scatterlist *sg;
		    MV_U8*		pBuffer;
		    sg = (struct scatterlist *)SCpnt->request_buffer;
		    
		    mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "SMART PIO command needs kmap for Data-in buffer\n");
		    
		    pBuffer = kmap_atomic(sg->page, KM_IRQ0) + sg->offset;
		    memcpy(pBuffer, pInfo->pSALBlock->pDataBuffer, sg->length); 	
		    kunmap_atomic(pBuffer - sg->offset, KM_IRQ0);
#else
		    scsi_sg_copy_from_buffer(SCpnt, pInfo->pSALBlock->pDataBuffer,
					     scsi_bufflen(SCpnt));
#endif
	    }
		    
        }
        pScb->dataTransfered = MV_ATA_IDENTIFY_DEV_DATA_LENGTH*2;
        pScb->ScsiStatus = MV_SCSI_STATUS_GOOD;
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_SUCCESS;
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG, "command completed. pScb %p\n", pScb);
        break;
    case MV_COMPLETION_TYPE_ABORT:
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, " command Aborted. Cdb: %02x %02x %02x %02x %02x "
                 "%02x %02x %02x %02x %02x\n", pScb->ScsiCdb[0],
                 pScb->ScsiCdb[1], pScb->ScsiCdb[2], pScb->ScsiCdb[3],
                 pScb->ScsiCdb[4], pScb->ScsiCdb[5], pScb->ScsiCdb[6],
                 pScb->ScsiCdb[7], pScb->ScsiCdb[8], pScb->ScsiCdb[9]);
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_ABORTED;
        pScb->dataTransfered = 0;
        pScb->senseDataLength = 0;
        break;
    case MV_COMPLETION_TYPE_ERROR:
        pScb->dataTransfered = 0;
        pScb->senseDataLength = 0;
        pScb->ScsiCommandCompletion = MV_SCSI_COMPLETION_ATA_FAILED;

        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, " completion error, adapter =%d, channel=%d, flags=%x\n"
                 ,pSataAdapter->adapterId, channelNum, responseFlags);
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, "Failed command Cdb: %02x %02x %02x %02x %02x "
                 "%02x %02x %02x %02x %02x\n", pScb->ScsiCdb[0],
                 pScb->ScsiCdb[1], pScb->ScsiCdb[2], pScb->ScsiCdb[3],
                 pScb->ScsiCdb[4], pScb->ScsiCdb[5], pScb->ScsiCdb[6],
                 pScb->ScsiCdb[7], pScb->ScsiCdb[8], pScb->ScsiCdb[9]);
        /* here the  eDMA will be stopped, so we have to flush  */
        /* the pending commands                                 */
        handleNoneUdmaError(pScb, registerStruct);
        break;
    default:
        mvLogMsg(MV_IAL_LOG_ID, MV_DEBUG_ERROR, "Unknown completion type (%d)\n", comp_type);
        return MV_FALSE;
    }
    pScb->completionCallBack(pSataAdapter, pScb);
    return MV_TRUE;
}

