2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-11-25 01:24:41 +03:00
/*
* Driver for Western Digital WD7193 , WD7197 and WD7296 SCSI cards
* Copyright 2013 Ondrej Zary
*
* Original driver by
* Aaron Dewell < dewell @ woods . net >
* Gaerti < Juergen . Gaertner @ mbox . si . uni - hannover . de >
*
* HW documentation available in book :
*
* SPIDER Command Protocol
* by Chandru M . Sippy
* SCSI Storage Products ( MCP )
* Western Digital Corporation
* 09 - 15 - 95
*
* http : //web.archive.org/web/20070717175254/http://sun1.rrzn.uni-hannover.de/gaertner.juergen/wd719x/Linux/Docu/Spider/
*/
/*
* Driver workflow :
* 1. SCSI command is transformed to SCB ( Spider Control Block ) by the
* queuecommand function .
* 2. The address of the SCB is stored in a list to be able to access it , if
* something goes wrong .
* 3. The address of the SCB is written to the Controller , which loads the SCB
* via BM - DMA and processes it .
* 4. After it has finished , it generates an interrupt , and sets registers .
*
* flaws :
* - abort / reset functions
*
* ToDo :
* - tagged queueing
*/
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/pci.h>
# include <linux/firmware.h>
# include <linux/eeprom_93cx6.h>
# include <scsi/scsi_cmnd.h>
# include <scsi/scsi_device.h>
# include <scsi/scsi_host.h>
# include "wd719x.h"
/* low-level register access */
static inline u8 wd719x_readb ( struct wd719x * wd , u8 reg )
{
return ioread8 ( wd - > base + reg ) ;
}
static inline u32 wd719x_readl ( struct wd719x * wd , u8 reg )
{
return ioread32 ( wd - > base + reg ) ;
}
static inline void wd719x_writeb ( struct wd719x * wd , u8 reg , u8 val )
{
iowrite8 ( val , wd - > base + reg ) ;
}
static inline void wd719x_writew ( struct wd719x * wd , u8 reg , u16 val )
{
iowrite16 ( val , wd - > base + reg ) ;
}
static inline void wd719x_writel ( struct wd719x * wd , u8 reg , u32 val )
{
iowrite32 ( val , wd - > base + reg ) ;
}
/* wait until the command register is ready */
static inline int wd719x_wait_ready ( struct wd719x * wd )
{
int i = 0 ;
do {
if ( wd719x_readb ( wd , WD719X_AMR_COMMAND ) = = WD719X_CMD_READY )
return 0 ;
udelay ( 1 ) ;
} while ( i + + < WD719X_WAIT_FOR_CMD_READY ) ;
dev_err ( & wd - > pdev - > dev , " command register is not ready: 0x%02x \n " ,
wd719x_readb ( wd , WD719X_AMR_COMMAND ) ) ;
return - ETIMEDOUT ;
}
/* poll interrupt status register until command finishes */
static inline int wd719x_wait_done ( struct wd719x * wd , int timeout )
{
u8 status ;
while ( timeout > 0 ) {
status = wd719x_readb ( wd , WD719X_AMR_INT_STATUS ) ;
if ( status )
break ;
timeout - - ;
udelay ( 1 ) ;
}
if ( timeout < = 0 ) {
dev_err ( & wd - > pdev - > dev , " direct command timed out \n " ) ;
return - ETIMEDOUT ;
}
if ( status ! = WD719X_INT_NOERRORS ) {
2019-06-17 20:50:12 +03:00
u8 sue = wd719x_readb ( wd , WD719X_AMR_SCB_ERROR ) ;
/* we get this after wd719x_dev_reset, it's not an error */
if ( sue = = WD719X_SUE_TERM )
return 0 ;
/* we get this after wd719x_bus_reset, it's not an error */
if ( sue = = WD719X_SUE_RESET )
return 0 ;
2014-11-25 01:24:41 +03:00
dev_err ( & wd - > pdev - > dev , " direct command failed, status 0x%02x, SUE 0x%02x \n " ,
2019-06-17 20:50:12 +03:00
status , sue ) ;
2014-11-25 01:24:41 +03:00
return - EIO ;
}
return 0 ;
}
static int wd719x_direct_cmd ( struct wd719x * wd , u8 opcode , u8 dev , u8 lun ,
u8 tag , dma_addr_t data , int timeout )
{
int ret = 0 ;
/* clear interrupt status register (allow command register to clear) */
wd719x_writeb ( wd , WD719X_AMR_INT_STATUS , WD719X_INT_NONE ) ;
/* Wait for the Command register to become free */
if ( wd719x_wait_ready ( wd ) )
return - ETIMEDOUT ;
2019-06-17 20:50:12 +03:00
/* disable interrupts except for RESET/ABORT (it breaks them) */
if ( opcode ! = WD719X_CMD_BUSRESET & & opcode ! = WD719X_CMD_ABORT & &
opcode ! = WD719X_CMD_ABORT_TAG & & opcode ! = WD719X_CMD_RESET )
dev | = WD719X_DISABLE_INT ;
2014-11-25 01:24:41 +03:00
wd719x_writeb ( wd , WD719X_AMR_CMD_PARAM , dev ) ;
wd719x_writeb ( wd , WD719X_AMR_CMD_PARAM_2 , lun ) ;
wd719x_writeb ( wd , WD719X_AMR_CMD_PARAM_3 , tag ) ;
if ( data )
wd719x_writel ( wd , WD719X_AMR_SCB_IN , data ) ;
/* clear interrupt status register again */
wd719x_writeb ( wd , WD719X_AMR_INT_STATUS , WD719X_INT_NONE ) ;
/* Now, write the command */
wd719x_writeb ( wd , WD719X_AMR_COMMAND , opcode ) ;
if ( timeout ) /* wait for the command to complete */
ret = wd719x_wait_done ( wd , timeout ) ;
/* clear interrupt status register (clean up) */
if ( opcode ! = WD719X_CMD_READ_FIRMVER )
wd719x_writeb ( wd , WD719X_AMR_INT_STATUS , WD719X_INT_NONE ) ;
return ret ;
}
static void wd719x_destroy ( struct wd719x * wd )
{
/* stop the RISC */
if ( wd719x_direct_cmd ( wd , WD719X_CMD_SLEEP , 0 , 0 , 0 , 0 ,
WD719X_WAIT_FOR_RISC ) )
dev_warn ( & wd - > pdev - > dev , " RISC sleep command failed \n " ) ;
/* disable RISC */
wd719x_writeb ( wd , WD719X_PCI_MODE_SELECT , 0 ) ;
2018-11-09 18:54:04 +03:00
WARN_ON_ONCE ( ! list_empty ( & wd - > active_scbs ) ) ;
2014-11-25 01:24:41 +03:00
/* free internal buffers */
2018-11-09 18:54:06 +03:00
dma_free_coherent ( & wd - > pdev - > dev , wd - > fw_size , wd - > fw_virt ,
wd - > fw_phys ) ;
2014-11-25 01:24:41 +03:00
wd - > fw_virt = NULL ;
2018-11-09 18:54:06 +03:00
dma_free_coherent ( & wd - > pdev - > dev , WD719X_HASH_TABLE_SIZE , wd - > hash_virt ,
wd - > hash_phys ) ;
2014-11-25 01:24:41 +03:00
wd - > hash_virt = NULL ;
2018-11-09 18:54:06 +03:00
dma_free_coherent ( & wd - > pdev - > dev , sizeof ( struct wd719x_host_param ) ,
wd - > params , wd - > params_phys ) ;
2014-11-25 01:24:41 +03:00
wd - > params = NULL ;
free_irq ( wd - > pdev - > irq , wd ) ;
}
2018-11-09 18:54:05 +03:00
/* finish a SCSI command, unmap buffers */
static void wd719x_finish_cmd ( struct wd719x_scb * scb , int result )
2014-11-25 01:24:41 +03:00
{
2018-11-09 18:54:05 +03:00
struct scsi_cmnd * cmd = scb - > cmd ;
2014-11-25 01:24:41 +03:00
struct wd719x * wd = shost_priv ( cmd - > device - > host ) ;
2018-11-09 18:54:05 +03:00
list_del ( & scb - > list ) ;
dma_unmap_single ( & wd - > pdev - > dev , scb - > phys ,
sizeof ( struct wd719x_scb ) , DMA_BIDIRECTIONAL ) ;
scsi_dma_unmap ( cmd ) ;
2022-02-18 22:51:14 +03:00
dma_unmap_single ( & wd - > pdev - > dev , scb - > dma_handle ,
2018-11-09 18:54:05 +03:00
SCSI_SENSE_BUFFERSIZE , DMA_FROM_DEVICE ) ;
2014-11-25 01:24:41 +03:00
cmd - > result = result < < 16 ;
2021-10-07 23:46:05 +03:00
scsi_done ( cmd ) ;
2014-11-25 01:24:41 +03:00
}
/* Build a SCB and send it to the card */
static int wd719x_queuecommand ( struct Scsi_Host * sh , struct scsi_cmnd * cmd )
{
int i , count_sg ;
unsigned long flags ;
2018-11-09 18:54:05 +03:00
struct wd719x_scb * scb = scsi_cmd_priv ( cmd ) ;
2014-11-25 01:24:41 +03:00
struct wd719x * wd = shost_priv ( sh ) ;
scb - > cmd = cmd ;
scb - > CDB_tag = 0 ; /* Tagged queueing not supported yet */
scb - > devid = cmd - > device - > id ;
scb - > lun = cmd - > device - > lun ;
/* copy the command */
memcpy ( scb - > CDB , cmd - > cmnd , cmd - > cmd_len ) ;
2018-11-09 18:54:05 +03:00
/* map SCB */
scb - > phys = dma_map_single ( & wd - > pdev - > dev , scb , sizeof ( * scb ) ,
DMA_BIDIRECTIONAL ) ;
if ( dma_mapping_error ( & wd - > pdev - > dev , scb - > phys ) )
goto out_error ;
2014-11-25 01:24:41 +03:00
/* map sense buffer */
scb - > sense_buf_length = SCSI_SENSE_BUFFERSIZE ;
2022-02-18 22:51:14 +03:00
scb - > dma_handle = dma_map_single ( & wd - > pdev - > dev , cmd - > sense_buffer ,
SCSI_SENSE_BUFFERSIZE , DMA_FROM_DEVICE ) ;
if ( dma_mapping_error ( & wd - > pdev - > dev , scb - > dma_handle ) )
2018-11-09 18:54:05 +03:00
goto out_unmap_scb ;
2022-02-18 22:51:14 +03:00
scb - > sense_buf = cpu_to_le32 ( scb - > dma_handle ) ;
2014-11-25 01:24:41 +03:00
/* request autosense */
scb - > SCB_options | = WD719X_SCB_FLAGS_AUTO_REQUEST_SENSE ;
/* check direction */
if ( cmd - > sc_data_direction = = DMA_TO_DEVICE )
scb - > SCB_options | = WD719X_SCB_FLAGS_CHECK_DIRECTION
| WD719X_SCB_FLAGS_PCI_TO_SCSI ;
else if ( cmd - > sc_data_direction = = DMA_FROM_DEVICE )
scb - > SCB_options | = WD719X_SCB_FLAGS_CHECK_DIRECTION ;
/* Scather/gather */
count_sg = scsi_dma_map ( cmd ) ;
2018-11-09 18:54:05 +03:00
if ( count_sg < 0 )
goto out_unmap_sense ;
2014-11-25 01:24:41 +03:00
BUG_ON ( count_sg > WD719X_SG ) ;
if ( count_sg ) {
struct scatterlist * sg ;
scb - > data_length = cpu_to_le32 ( count_sg *
sizeof ( struct wd719x_sglist ) ) ;
scb - > data_p = cpu_to_le32 ( scb - > phys +
offsetof ( struct wd719x_scb , sg_list ) ) ;
scsi_for_each_sg ( cmd , sg , count_sg , i ) {
scb - > sg_list [ i ] . ptr = cpu_to_le32 ( sg_dma_address ( sg ) ) ;
scb - > sg_list [ i ] . length = cpu_to_le32 ( sg_dma_len ( sg ) ) ;
}
scb - > SCB_options | = WD719X_SCB_FLAGS_DO_SCATTER_GATHER ;
} else { /* zero length */
scb - > data_length = 0 ;
scb - > data_p = 0 ;
}
2018-11-09 18:54:05 +03:00
spin_lock_irqsave ( wd - > sh - > host_lock , flags ) ;
2014-11-25 01:24:41 +03:00
/* check if the Command register is free */
if ( wd719x_readb ( wd , WD719X_AMR_COMMAND ) ! = WD719X_CMD_READY ) {
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
return SCSI_MLQUEUE_HOST_BUSY ;
}
2018-11-09 18:54:05 +03:00
list_add ( & scb - > list , & wd - > active_scbs ) ;
2014-11-25 01:24:41 +03:00
/* write pointer to the AMR */
wd719x_writel ( wd , WD719X_AMR_SCB_IN , scb - > phys ) ;
/* send SCB opcode */
wd719x_writeb ( wd , WD719X_AMR_COMMAND , WD719X_CMD_PROCESS_SCB ) ;
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
2018-11-09 18:54:05 +03:00
return 0 ;
2014-11-25 01:24:41 +03:00
2018-11-09 18:54:05 +03:00
out_unmap_sense :
2022-02-18 22:51:14 +03:00
dma_unmap_single ( & wd - > pdev - > dev , scb - > dma_handle ,
2018-11-09 18:54:05 +03:00
SCSI_SENSE_BUFFERSIZE , DMA_FROM_DEVICE ) ;
out_unmap_scb :
dma_unmap_single ( & wd - > pdev - > dev , scb - > phys , sizeof ( * scb ) ,
DMA_BIDIRECTIONAL ) ;
out_error :
cmd - > result = DID_ERROR < < 16 ;
2021-10-07 23:46:05 +03:00
scsi_done ( cmd ) ;
2014-11-25 01:24:41 +03:00
return 0 ;
}
static int wd719x_chip_init ( struct wd719x * wd )
{
int i , ret ;
u32 risc_init [ 3 ] ;
const struct firmware * fw_wcs , * fw_risc ;
const char fwname_wcs [ ] = " wd719x-wcs.bin " ;
const char fwname_risc [ ] = " wd719x-risc.bin " ;
memset ( wd - > hash_virt , 0 , WD719X_HASH_TABLE_SIZE ) ;
/* WCS (sequencer) firmware */
ret = request_firmware ( & fw_wcs , fwname_wcs , & wd - > pdev - > dev ) ;
if ( ret ) {
dev_err ( & wd - > pdev - > dev , " Unable to load firmware %s: %d \n " ,
fwname_wcs , ret ) ;
return ret ;
}
/* RISC firmware */
ret = request_firmware ( & fw_risc , fwname_risc , & wd - > pdev - > dev ) ;
if ( ret ) {
dev_err ( & wd - > pdev - > dev , " Unable to load firmware %s: %d \n " ,
fwname_risc , ret ) ;
release_firmware ( fw_wcs ) ;
return ret ;
}
wd - > fw_size = ALIGN ( fw_wcs - > size , 4 ) + fw_risc - > size ;
if ( ! wd - > fw_virt )
2018-11-09 18:54:06 +03:00
wd - > fw_virt = dma_alloc_coherent ( & wd - > pdev - > dev , wd - > fw_size ,
& wd - > fw_phys , GFP_KERNEL ) ;
2014-11-25 01:24:41 +03:00
if ( ! wd - > fw_virt ) {
ret = - ENOMEM ;
goto wd719x_init_end ;
}
/* make a fresh copy of WCS and RISC code */
memcpy ( wd - > fw_virt , fw_wcs - > data , fw_wcs - > size ) ;
memcpy ( wd - > fw_virt + ALIGN ( fw_wcs - > size , 4 ) , fw_risc - > data ,
fw_risc - > size ) ;
/* Reset the Spider Chip and adapter itself */
wd719x_writeb ( wd , WD719X_PCI_PORT_RESET , WD719X_PCI_RESET ) ;
udelay ( WD719X_WAIT_FOR_RISC ) ;
/* Clear PIO mode bits set by BIOS */
wd719x_writeb ( wd , WD719X_AMR_CMD_PARAM , 0 ) ;
/* ensure RISC is not running */
wd719x_writeb ( wd , WD719X_PCI_MODE_SELECT , 0 ) ;
/* ensure command port is ready */
wd719x_writeb ( wd , WD719X_AMR_COMMAND , 0 ) ;
if ( wd719x_wait_ready ( wd ) ) {
ret = - ETIMEDOUT ;
goto wd719x_init_end ;
}
/* Transfer the first 2K words of RISC code to kick start the uP */
risc_init [ 0 ] = wd - > fw_phys ; /* WCS FW */
risc_init [ 1 ] = wd - > fw_phys + ALIGN ( fw_wcs - > size , 4 ) ; /* RISC FW */
risc_init [ 2 ] = wd - > hash_phys ; /* hash table */
/* clear DMA status */
wd719x_writeb ( wd , WD719X_PCI_CHANNEL2_3STATUS , 0 ) ;
/* address to read firmware from */
wd719x_writel ( wd , WD719X_PCI_EXTERNAL_ADDR , risc_init [ 1 ] ) ;
/* base address to write firmware to (on card) */
wd719x_writew ( wd , WD719X_PCI_INTERNAL_ADDR , WD719X_PRAM_BASE_ADDR ) ;
/* size: first 2K words */
wd719x_writew ( wd , WD719X_PCI_DMA_TRANSFER_SIZE , 2048 * 2 ) ;
/* start DMA */
wd719x_writeb ( wd , WD719X_PCI_CHANNEL2_3CMD , WD719X_START_CHANNEL2_3DMA ) ;
/* wait for DMA to complete */
i = WD719X_WAIT_FOR_RISC ;
while ( i - - > 0 ) {
u8 status = wd719x_readb ( wd , WD719X_PCI_CHANNEL2_3STATUS ) ;
if ( status = = WD719X_START_CHANNEL2_3DONE )
break ;
if ( status = = WD719X_START_CHANNEL2_3ABORT ) {
dev_warn ( & wd - > pdev - > dev , " RISC bootstrap failed: DMA aborted \n " ) ;
ret = - EIO ;
goto wd719x_init_end ;
}
udelay ( 1 ) ;
}
if ( i < 1 ) {
dev_warn ( & wd - > pdev - > dev , " RISC bootstrap failed: DMA timeout \n " ) ;
ret = - ETIMEDOUT ;
goto wd719x_init_end ;
}
/* firmware is loaded, now initialize and wake up the RISC */
/* write RISC initialization long words to Spider */
wd719x_writel ( wd , WD719X_AMR_SCB_IN , risc_init [ 0 ] ) ;
wd719x_writel ( wd , WD719X_AMR_SCB_IN + 4 , risc_init [ 1 ] ) ;
wd719x_writel ( wd , WD719X_AMR_SCB_IN + 8 , risc_init [ 2 ] ) ;
/* disable interrupts during initialization of RISC */
wd719x_writeb ( wd , WD719X_AMR_CMD_PARAM , WD719X_DISABLE_INT ) ;
/* issue INITIALIZE RISC comand */
wd719x_writeb ( wd , WD719X_AMR_COMMAND , WD719X_CMD_INIT_RISC ) ;
/* enable advanced mode (wake up RISC) */
wd719x_writeb ( wd , WD719X_PCI_MODE_SELECT , WD719X_ENABLE_ADVANCE_MODE ) ;
udelay ( WD719X_WAIT_FOR_RISC ) ;
ret = wd719x_wait_done ( wd , WD719X_WAIT_FOR_RISC ) ;
/* clear interrupt status register */
wd719x_writeb ( wd , WD719X_AMR_INT_STATUS , WD719X_INT_NONE ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " Unable to initialize RISC \n " ) ;
goto wd719x_init_end ;
}
/* RISC is up and running */
/* Read FW version from RISC */
ret = wd719x_direct_cmd ( wd , WD719X_CMD_READ_FIRMVER , 0 , 0 , 0 , 0 ,
WD719X_WAIT_FOR_RISC ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " Unable to read firmware version \n " ) ;
goto wd719x_init_end ;
}
dev_info ( & wd - > pdev - > dev , " RISC initialized with firmware version %.2x.%.2x \n " ,
wd719x_readb ( wd , WD719X_AMR_SCB_OUT + 1 ) ,
wd719x_readb ( wd , WD719X_AMR_SCB_OUT ) ) ;
/* RESET SCSI bus */
ret = wd719x_direct_cmd ( wd , WD719X_CMD_BUSRESET , 0 , 0 , 0 , 0 ,
WD719X_WAIT_FOR_SCSI_RESET ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " SCSI bus reset failed \n " ) ;
goto wd719x_init_end ;
}
/* use HostParameter structure to set Spider's Host Parameter Block */
ret = wd719x_direct_cmd ( wd , WD719X_CMD_SET_PARAM , 0 ,
sizeof ( struct wd719x_host_param ) , 0 ,
wd - > params_phys , WD719X_WAIT_FOR_RISC ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " Failed to set HOST PARAMETERS \n " ) ;
goto wd719x_init_end ;
}
/* initiate SCAM (does nothing if disabled in BIOS) */
/* bug?: we should pass a mask of static IDs which we don't have */
ret = wd719x_direct_cmd ( wd , WD719X_CMD_INIT_SCAM , 0 , 0 , 0 , 0 ,
WD719X_WAIT_FOR_SCSI_RESET ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " SCAM initialization failed \n " ) ;
goto wd719x_init_end ;
}
/* clear AMR_BIOS_SHARE_INT register */
wd719x_writeb ( wd , WD719X_AMR_BIOS_SHARE_INT , 0 ) ;
wd719x_init_end :
release_firmware ( fw_wcs ) ;
release_firmware ( fw_risc ) ;
return ret ;
}
static int wd719x_abort ( struct scsi_cmnd * cmd )
{
int action , result ;
unsigned long flags ;
2018-11-09 18:54:05 +03:00
struct wd719x_scb * scb = scsi_cmd_priv ( cmd ) ;
2014-11-25 01:24:41 +03:00
struct wd719x * wd = shost_priv ( cmd - > device - > host ) ;
2021-08-13 16:49:11 +03:00
struct device * dev = & wd - > pdev - > dev ;
2014-11-25 01:24:41 +03:00
2021-08-13 16:49:11 +03:00
dev_info ( dev , " abort command, tag: %x \n " , scsi_cmd_to_rq ( cmd ) - > tag ) ;
2014-11-25 01:24:41 +03:00
2021-08-13 16:49:11 +03:00
action = WD719X_CMD_ABORT ;
2014-11-25 01:24:41 +03:00
spin_lock_irqsave ( wd - > sh - > host_lock , flags ) ;
result = wd719x_direct_cmd ( wd , action , cmd - > device - > id ,
2021-08-13 16:49:11 +03:00
cmd - > device - > lun , scsi_cmd_to_rq ( cmd ) - > tag ,
scb - > phys , 0 ) ;
2019-06-17 20:50:12 +03:00
wd719x_finish_cmd ( scb , DID_ABORT ) ;
2014-11-25 01:24:41 +03:00
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
if ( result )
return FAILED ;
return SUCCESS ;
}
static int wd719x_reset ( struct scsi_cmnd * cmd , u8 opcode , u8 device )
{
int result ;
unsigned long flags ;
struct wd719x * wd = shost_priv ( cmd - > device - > host ) ;
2019-06-17 20:50:12 +03:00
struct wd719x_scb * scb , * tmp ;
2014-11-25 01:24:41 +03:00
dev_info ( & wd - > pdev - > dev , " %s reset requested \n " ,
( opcode = = WD719X_CMD_BUSRESET ) ? " bus " : " device " ) ;
spin_lock_irqsave ( wd - > sh - > host_lock , flags ) ;
result = wd719x_direct_cmd ( wd , opcode , device , 0 , 0 , 0 ,
WD719X_WAIT_FOR_SCSI_RESET ) ;
2019-06-17 20:50:12 +03:00
/* flush all SCBs (or all for a device if dev_reset) */
list_for_each_entry_safe ( scb , tmp , & wd - > active_scbs , list ) {
if ( opcode = = WD719X_CMD_BUSRESET | |
scb - > cmd - > device - > id = = device )
wd719x_finish_cmd ( scb , DID_RESET ) ;
}
2014-11-25 01:24:41 +03:00
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
if ( result )
return FAILED ;
return SUCCESS ;
}
static int wd719x_dev_reset ( struct scsi_cmnd * cmd )
{
return wd719x_reset ( cmd , WD719X_CMD_RESET , cmd - > device - > id ) ;
}
static int wd719x_bus_reset ( struct scsi_cmnd * cmd )
{
return wd719x_reset ( cmd , WD719X_CMD_BUSRESET , 0 ) ;
}
static int wd719x_host_reset ( struct scsi_cmnd * cmd )
{
struct wd719x * wd = shost_priv ( cmd - > device - > host ) ;
struct wd719x_scb * scb , * tmp ;
unsigned long flags ;
dev_info ( & wd - > pdev - > dev , " host reset requested \n " ) ;
spin_lock_irqsave ( wd - > sh - > host_lock , flags ) ;
2019-06-17 20:50:12 +03:00
/* stop the RISC */
if ( wd719x_direct_cmd ( wd , WD719X_CMD_SLEEP , 0 , 0 , 0 , 0 ,
WD719X_WAIT_FOR_RISC ) )
dev_warn ( & wd - > pdev - > dev , " RISC sleep command failed \n " ) ;
/* disable RISC */
wd719x_writeb ( wd , WD719X_PCI_MODE_SELECT , 0 ) ;
2014-11-25 01:24:41 +03:00
/* flush all SCBs */
2018-11-09 18:54:05 +03:00
list_for_each_entry_safe ( scb , tmp , & wd - > active_scbs , list )
2019-06-17 20:50:12 +03:00
wd719x_finish_cmd ( scb , DID_RESET ) ;
2014-11-25 01:24:41 +03:00
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
2019-06-17 20:50:12 +03:00
/* Try to reinit the RISC */
return wd719x_chip_init ( wd ) = = 0 ? SUCCESS : FAILED ;
2014-11-25 01:24:41 +03:00
}
static int wd719x_biosparam ( struct scsi_device * sdev , struct block_device * bdev ,
sector_t capacity , int geom [ ] )
{
if ( capacity > = 0x200000 ) {
geom [ 0 ] = 255 ; /* heads */
geom [ 1 ] = 63 ; /* sectors */
} else {
geom [ 0 ] = 64 ; /* heads */
geom [ 1 ] = 32 ; /* sectors */
}
geom [ 2 ] = sector_div ( capacity , geom [ 0 ] * geom [ 1 ] ) ; /* cylinders */
return 0 ;
}
/* process a SCB-completion interrupt */
static inline void wd719x_interrupt_SCB ( struct wd719x * wd ,
union wd719x_regs regs ,
struct wd719x_scb * scb )
{
int result ;
/* now have to find result from card */
switch ( regs . bytes . SUE ) {
case WD719X_SUE_NOERRORS :
result = DID_OK ;
break ;
case WD719X_SUE_REJECTED :
dev_err ( & wd - > pdev - > dev , " command rejected \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_SCBQFULL :
dev_err ( & wd - > pdev - > dev , " SCB queue is full \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_TERM :
dev_dbg ( & wd - > pdev - > dev , " SCB terminated by direct command \n " ) ;
result = DID_ABORT ; /* or DID_RESET? */
break ;
case WD719X_SUE_CHAN1ABORT :
case WD719X_SUE_CHAN23ABORT :
result = DID_ABORT ;
dev_err ( & wd - > pdev - > dev , " DMA abort \n " ) ;
break ;
case WD719X_SUE_CHAN1PAR :
case WD719X_SUE_CHAN23PAR :
result = DID_PARITY ;
dev_err ( & wd - > pdev - > dev , " DMA parity error \n " ) ;
break ;
case WD719X_SUE_TIMEOUT :
result = DID_TIME_OUT ;
dev_dbg ( & wd - > pdev - > dev , " selection timeout \n " ) ;
break ;
case WD719X_SUE_RESET :
2015-05-07 17:21:27 +03:00
dev_dbg ( & wd - > pdev - > dev , " bus reset occurred \n " ) ;
2014-11-25 01:24:41 +03:00
result = DID_RESET ;
break ;
case WD719X_SUE_BUSERROR :
dev_dbg ( & wd - > pdev - > dev , " SCSI bus error \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_WRONGWAY :
dev_err ( & wd - > pdev - > dev , " wrong data transfer direction \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_BADPHASE :
dev_err ( & wd - > pdev - > dev , " invalid SCSI phase \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_TOOLONG :
dev_err ( & wd - > pdev - > dev , " record too long \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_BUSFREE :
dev_err ( & wd - > pdev - > dev , " unexpected bus free \n " ) ;
result = DID_NO_CONNECT ; /* or DID_ERROR ???*/
break ;
case WD719X_SUE_ARSDONE :
dev_dbg ( & wd - > pdev - > dev , " auto request sense \n " ) ;
if ( regs . bytes . SCSI = = 0 )
result = DID_OK ;
else
result = DID_PARITY ;
break ;
case WD719X_SUE_IGNORED :
dev_err ( & wd - > pdev - > dev , " target id %d ignored command \n " ,
scb - > cmd - > device - > id ) ;
result = DID_NO_CONNECT ;
break ;
case WD719X_SUE_WRONGTAGS :
dev_err ( & wd - > pdev - > dev , " reversed tags \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_BADTAGS :
dev_err ( & wd - > pdev - > dev , " tag type not supported by target \n " ) ;
result = DID_ERROR ;
break ;
case WD719X_SUE_NOSCAMID :
dev_err ( & wd - > pdev - > dev , " no SCAM soft ID available \n " ) ;
result = DID_ERROR ;
break ;
default :
dev_warn ( & wd - > pdev - > dev , " unknown SUE error code: 0x%x \n " ,
regs . bytes . SUE ) ;
result = DID_ERROR ;
break ;
}
2018-11-09 18:54:05 +03:00
wd719x_finish_cmd ( scb , result ) ;
2014-11-25 01:24:41 +03:00
}
static irqreturn_t wd719x_interrupt ( int irq , void * dev_id )
{
struct wd719x * wd = dev_id ;
union wd719x_regs regs ;
unsigned long flags ;
u32 SCB_out ;
spin_lock_irqsave ( wd - > sh - > host_lock , flags ) ;
/* read SCB pointer back from card */
SCB_out = wd719x_readl ( wd , WD719X_AMR_SCB_OUT ) ;
/* read all status info at once */
regs . all = cpu_to_le32 ( wd719x_readl ( wd , WD719X_AMR_OP_CODE ) ) ;
switch ( regs . bytes . INT ) {
case WD719X_INT_NONE :
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
return IRQ_NONE ;
case WD719X_INT_LINKNOSTATUS :
dev_err ( & wd - > pdev - > dev , " linked command completed with no status \n " ) ;
break ;
case WD719X_INT_BADINT :
dev_err ( & wd - > pdev - > dev , " unsolicited interrupt \n " ) ;
break ;
case WD719X_INT_NOERRORS :
case WD719X_INT_LINKNOERRORS :
case WD719X_INT_ERRORSLOGGED :
case WD719X_INT_SPIDERFAILED :
/* was the cmd completed a direct or SCB command? */
if ( regs . bytes . OPC = = WD719X_CMD_PROCESS_SCB ) {
struct wd719x_scb * scb ;
list_for_each_entry ( scb , & wd - > active_scbs , list )
if ( SCB_out = = scb - > phys )
break ;
if ( SCB_out = = scb - > phys )
wd719x_interrupt_SCB ( wd , regs , scb ) ;
else
dev_err ( & wd - > pdev - > dev , " card returned invalid SCB pointer \n " ) ;
} else
2019-06-17 20:50:12 +03:00
dev_dbg ( & wd - > pdev - > dev , " direct command 0x%x completed \n " ,
2014-11-25 01:24:41 +03:00
regs . bytes . OPC ) ;
break ;
case WD719X_INT_PIOREADY :
dev_err ( & wd - > pdev - > dev , " card indicates PIO data ready but we never use PIO \n " ) ;
/* interrupt will not be cleared until all data is read */
break ;
default :
dev_err ( & wd - > pdev - > dev , " unknown interrupt reason: %d \n " ,
regs . bytes . INT ) ;
}
/* clear interrupt so another can happen */
wd719x_writeb ( wd , WD719X_AMR_INT_STATUS , WD719X_INT_NONE ) ;
spin_unlock_irqrestore ( wd - > sh - > host_lock , flags ) ;
return IRQ_HANDLED ;
}
static void wd719x_eeprom_reg_read ( struct eeprom_93cx6 * eeprom )
{
struct wd719x * wd = eeprom - > data ;
u8 reg = wd719x_readb ( wd , WD719X_PCI_GPIO_DATA ) ;
eeprom - > reg_data_out = reg & WD719X_EE_DO ;
}
static void wd719x_eeprom_reg_write ( struct eeprom_93cx6 * eeprom )
{
struct wd719x * wd = eeprom - > data ;
u8 reg = 0 ;
if ( eeprom - > reg_data_in )
reg | = WD719X_EE_DI ;
if ( eeprom - > reg_data_clock )
reg | = WD719X_EE_CLK ;
if ( eeprom - > reg_chip_select )
reg | = WD719X_EE_CS ;
wd719x_writeb ( wd , WD719X_PCI_GPIO_DATA , reg ) ;
}
/* read config from EEPROM so it can be downloaded by the RISC on (re-)init */
static void wd719x_read_eeprom ( struct wd719x * wd )
{
struct eeprom_93cx6 eeprom ;
u8 gpio ;
struct wd719x_eeprom_header header ;
eeprom . data = wd ;
eeprom . register_read = wd719x_eeprom_reg_read ;
eeprom . register_write = wd719x_eeprom_reg_write ;
eeprom . width = PCI_EEPROM_WIDTH_93C46 ;
/* set all outputs to low */
wd719x_writeb ( wd , WD719X_PCI_GPIO_DATA , 0 ) ;
/* configure GPIO pins */
gpio = wd719x_readb ( wd , WD719X_PCI_GPIO_CONTROL ) ;
/* GPIO outputs */
gpio & = ( ~ ( WD719X_EE_CLK | WD719X_EE_DI | WD719X_EE_CS ) ) ;
/* GPIO input */
gpio | = WD719X_EE_DO ;
wd719x_writeb ( wd , WD719X_PCI_GPIO_CONTROL , gpio ) ;
/* read EEPROM header */
eeprom_93cx6_multireadb ( & eeprom , 0 , ( u8 * ) & header , sizeof ( header ) ) ;
if ( header . sig1 = = ' W ' & & header . sig2 = = ' D ' )
eeprom_93cx6_multireadb ( & eeprom , header . cfg_offset ,
( u8 * ) wd - > params ,
sizeof ( struct wd719x_host_param ) ) ;
else { /* default EEPROM values */
dev_warn ( & wd - > pdev - > dev , " EEPROM signature is invalid (0x%02x 0x%02x), using default values \n " ,
header . sig1 , header . sig2 ) ;
wd - > params - > ch_1_th = 0x10 ; /* 16 DWs = 64 B */
wd - > params - > scsi_conf = 0x4c ; /* 48ma, spue, parity check */
wd - > params - > own_scsi_id = 0x07 ; /* ID 7, SCAM disabled */
wd - > params - > sel_timeout = 0x4d ; /* 250 ms */
wd - > params - > sleep_timer = 0x01 ;
wd - > params - > cdb_size = cpu_to_le16 ( 0x5555 ) ; /* all 6 B */
wd - > params - > scsi_pad = 0x1b ;
if ( wd - > type = = WD719X_TYPE_7193 ) /* narrow card - disable */
wd - > params - > wide = cpu_to_le32 ( 0x00000000 ) ;
else /* initiate & respond to WIDE messages */
wd - > params - > wide = cpu_to_le32 ( 0xffffffff ) ;
wd - > params - > sync = cpu_to_le32 ( 0xffffffff ) ;
wd - > params - > soft_mask = 0x00 ; /* all disabled */
wd - > params - > unsol_mask = 0x00 ; /* all disabled */
}
/* disable TAGGED messages */
wd - > params - > tag_en = cpu_to_le16 ( 0x0000 ) ;
}
/* Read card type from GPIO bits 1 and 3 */
static enum wd719x_card_type wd719x_detect_type ( struct wd719x * wd )
{
u8 card = wd719x_readb ( wd , WD719X_PCI_GPIO_CONTROL ) ;
card | = WD719X_GPIO_ID_BITS ;
wd719x_writeb ( wd , WD719X_PCI_GPIO_CONTROL , card ) ;
card = wd719x_readb ( wd , WD719X_PCI_GPIO_DATA ) & WD719X_GPIO_ID_BITS ;
switch ( card ) {
case 0x08 :
return WD719X_TYPE_7193 ;
case 0x02 :
return WD719X_TYPE_7197 ;
case 0x00 :
return WD719X_TYPE_7296 ;
default :
dev_warn ( & wd - > pdev - > dev , " unknown card type 0x%x \n " , card ) ;
return WD719X_TYPE_UNKNOWN ;
}
}
static int wd719x_board_found ( struct Scsi_Host * sh )
{
struct wd719x * wd = shost_priv ( sh ) ;
2017-11-28 21:12:34 +03:00
static const char * const card_types [ ] = {
" Unknown card " , " WD7193 " , " WD7197 " , " WD7296 "
} ;
2014-11-25 01:24:41 +03:00
int ret ;
INIT_LIST_HEAD ( & wd - > active_scbs ) ;
sh - > base = pci_resource_start ( wd - > pdev , 0 ) ;
wd - > type = wd719x_detect_type ( wd ) ;
wd - > sh = sh ;
sh - > irq = wd - > pdev - > irq ;
wd - > fw_virt = NULL ;
/* memory area for host (EEPROM) parameters */
2018-11-09 18:54:06 +03:00
wd - > params = dma_alloc_coherent ( & wd - > pdev - > dev ,
sizeof ( struct wd719x_host_param ) ,
& wd - > params_phys , GFP_KERNEL ) ;
2014-11-25 01:24:41 +03:00
if ( ! wd - > params ) {
dev_warn ( & wd - > pdev - > dev , " unable to allocate parameter buffer \n " ) ;
return - ENOMEM ;
}
/* memory area for the RISC for hash table of outstanding requests */
2018-11-09 18:54:06 +03:00
wd - > hash_virt = dma_alloc_coherent ( & wd - > pdev - > dev ,
WD719X_HASH_TABLE_SIZE ,
& wd - > hash_phys , GFP_KERNEL ) ;
2014-11-25 01:24:41 +03:00
if ( ! wd - > hash_virt ) {
dev_warn ( & wd - > pdev - > dev , " unable to allocate hash buffer \n " ) ;
ret = - ENOMEM ;
goto fail_free_params ;
}
ret = request_irq ( wd - > pdev - > irq , wd719x_interrupt , IRQF_SHARED ,
" wd719x " , wd ) ;
if ( ret ) {
dev_warn ( & wd - > pdev - > dev , " unable to assign IRQ %d \n " ,
wd - > pdev - > irq ) ;
goto fail_free_hash ;
}
/* read parameters from EEPROM */
wd719x_read_eeprom ( wd ) ;
ret = wd719x_chip_init ( wd ) ;
if ( ret )
goto fail_free_irq ;
sh - > this_id = wd - > params - > own_scsi_id & WD719X_EE_SCSI_ID_MASK ;
dev_info ( & wd - > pdev - > dev , " %s at I/O 0x%lx, IRQ %u, SCSI ID %d \n " ,
card_types [ wd - > type ] , sh - > base , sh - > irq , sh - > this_id ) ;
return 0 ;
fail_free_irq :
free_irq ( wd - > pdev - > irq , wd ) ;
fail_free_hash :
2018-11-09 18:54:06 +03:00
dma_free_coherent ( & wd - > pdev - > dev , WD719X_HASH_TABLE_SIZE , wd - > hash_virt ,
2014-11-25 01:24:41 +03:00
wd - > hash_phys ) ;
fail_free_params :
2018-11-09 18:54:06 +03:00
dma_free_coherent ( & wd - > pdev - > dev , sizeof ( struct wd719x_host_param ) ,
2014-11-25 01:24:41 +03:00
wd - > params , wd - > params_phys ) ;
return ret ;
}
2023-03-22 22:55:10 +03:00
static const struct scsi_host_template wd719x_template = {
2015-02-09 15:38:21 +03:00
. module = THIS_MODULE ,
2014-11-25 01:24:41 +03:00
. name = " Western Digital 719x " ,
2018-11-09 18:54:05 +03:00
. cmd_size = sizeof ( struct wd719x_scb ) ,
2014-11-25 01:24:41 +03:00
. queuecommand = wd719x_queuecommand ,
. eh_abort_handler = wd719x_abort ,
. eh_device_reset_handler = wd719x_dev_reset ,
. eh_bus_reset_handler = wd719x_bus_reset ,
. eh_host_reset_handler = wd719x_host_reset ,
. bios_param = wd719x_biosparam ,
. proc_name = " wd719x " ,
. can_queue = 255 ,
. this_id = 7 ,
. sg_tablesize = WD719X_SG ,
} ;
static int wd719x_pci_probe ( struct pci_dev * pdev , const struct pci_device_id * d )
{
int err ;
struct Scsi_Host * sh ;
struct wd719x * wd ;
err = pci_enable_device ( pdev ) ;
if ( err )
goto fail ;
2022-02-28 17:54:15 +03:00
err = dma_set_mask ( & pdev - > dev , DMA_BIT_MASK ( 32 ) ) ;
if ( err ) {
2014-11-25 01:24:41 +03:00
dev_warn ( & pdev - > dev , " Unable to set 32-bit DMA mask \n " ) ;
goto disable_device ;
}
err = pci_request_regions ( pdev , " wd719x " ) ;
if ( err )
goto disable_device ;
pci_set_master ( pdev ) ;
err = - ENODEV ;
if ( pci_resource_len ( pdev , 0 ) = = 0 )
goto release_region ;
err = - ENOMEM ;
sh = scsi_host_alloc ( & wd719x_template , sizeof ( struct wd719x ) ) ;
if ( ! sh )
goto release_region ;
wd = shost_priv ( sh ) ;
wd - > base = pci_iomap ( pdev , 0 , 0 ) ;
if ( ! wd - > base )
goto free_host ;
wd - > pdev = pdev ;
err = wd719x_board_found ( sh ) ;
if ( err )
goto unmap ;
err = scsi_add_host ( sh , & wd - > pdev - > dev ) ;
if ( err )
goto destroy ;
scsi_scan_host ( sh ) ;
pci_set_drvdata ( pdev , sh ) ;
return 0 ;
destroy :
wd719x_destroy ( wd ) ;
unmap :
pci_iounmap ( pdev , wd - > base ) ;
free_host :
scsi_host_put ( sh ) ;
release_region :
pci_release_regions ( pdev ) ;
disable_device :
pci_disable_device ( pdev ) ;
fail :
return err ;
}
static void wd719x_pci_remove ( struct pci_dev * pdev )
{
struct Scsi_Host * sh = pci_get_drvdata ( pdev ) ;
struct wd719x * wd = shost_priv ( sh ) ;
scsi_remove_host ( sh ) ;
wd719x_destroy ( wd ) ;
pci_iounmap ( pdev , wd - > base ) ;
pci_release_regions ( pdev ) ;
pci_disable_device ( pdev ) ;
scsi_host_put ( sh ) ;
}
2016-09-02 02:14:58 +03:00
static const struct pci_device_id wd719x_pci_table [ ] = {
2014-11-25 01:24:41 +03:00
{ PCI_DEVICE ( PCI_VENDOR_ID_WD , 0x3296 ) } ,
{ }
} ;
MODULE_DEVICE_TABLE ( pci , wd719x_pci_table ) ;
static struct pci_driver wd719x_pci_driver = {
. name = " wd719x " ,
. id_table = wd719x_pci_table ,
. probe = wd719x_pci_probe ,
. remove = wd719x_pci_remove ,
} ;
2018-04-21 13:58:34 +03:00
module_pci_driver ( wd719x_pci_driver ) ;
2014-11-25 01:24:41 +03:00
MODULE_DESCRIPTION ( " Western Digital WD7193/7197/7296 SCSI driver " ) ;
MODULE_AUTHOR ( " Ondrej Zary, Aaron Dewell, Juergen Gaertner " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_FIRMWARE ( " wd719x-wcs.bin " ) ;
MODULE_FIRMWARE ( " wd719x-risc.bin " ) ;