// SPDX-License-Identifier: GPL-2.0-only
/*
* Aic94xx SAS/SATA driver SCB management.
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*/
#include <linux/gfp.h>
#include <scsi/scsi_host.h>
#include "aic94xx.h"
#include "aic94xx_reg.h"
#include "aic94xx_hwi.h"
#include "aic94xx_seq.h"
#include "aic94xx_dump.h"
/* ---------- EMPTY SCB ---------- */
#define DL_PHY_MASK 7
#define BYTES_DMAED 0
#define PRIMITIVE_RECVD 0x08
#define PHY_EVENT 0x10
#define LINK_RESET_ERROR 0x18
#define TIMER_EVENT 0x20
#define REQ_TASK_ABORT 0xF0
#define REQ_DEVICE_RESET 0xF1
#define SIGNAL_NCQ_ERROR 0xF2
#define CLEAR_NCQ_ERROR 0xF3
#define PHY_EVENTS_STATUS (CURRENT_LOSS_OF_SIGNAL | CURRENT_OOB_DONE \
| CURRENT_SPINUP_HOLD | CURRENT_GTO_TIMEOUT \
| CURRENT_OOB_ERROR)
static void get_lrate_mode(struct asd_phy *phy, u8 oob_mode)
{
struct sas_phy *sas_phy = phy->sas_phy.phy;
switch (oob_mode & 7) {
case PHY_SPEED_60:
/* FIXME: sas transport class doesn't have this */
phy->sas_phy.linkrate = SAS_LINK_RATE_6_0_GBPS;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS;
break;
case PHY_SPEED_30:
phy->sas_phy.linkrate = SAS_LINK_RATE_3_0_GBPS;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS;
break;
case PHY_SPEED_15:
phy->sas_phy.linkrate = SAS_LINK_RATE_1_5_GBPS;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_1_5_GBPS;
break;
}
sas_phy->negotiated_linkrate = phy->sas_phy.linkrate;
sas_phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS;
sas_phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS;
sas_phy->maximum_linkrate = phy->phy_desc->max_sas_lrate;
sas_phy->minimum_linkrate = phy->phy_desc->min_sas_lrate;
if (oob_mode & SAS_MODE)
phy->sas_phy.oob_mode = SAS_OOB_MODE;
else if (oob_mode & SATA_MODE)
phy->sas_phy.oob_mode = SATA_OOB_MODE;
}
static void asd_phy_event_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_ha_struct *sas_ha = &asd_ha->sas_ha;
int phy_id = dl->status_block[0] & DL_PHY_MASK;
struct asd_phy *phy = &asd_ha->phys[phy_id];
u8 oob_status = dl->status_block[1] & PHY_EVENTS_STATUS;
u8 oob_mode = dl->status_block[2];
switch (oob_status) {
case CURRENT_LOSS_OF_SIGNAL:
/* directly attached device was removed */
ASD_DPRINTK("phy%d: device unplugged\n", phy_id);
asd_turn_led(asd_ha, phy_id, 0);
sas_phy_disconnected(&phy->sas_phy);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_LOSS_OF_SIGNAL);
break;
case CURRENT_OOB_DONE:
/* hot plugged device */
asd_turn_led(asd_ha, phy_id, 1);
get_lrate_mode(phy, oob_mode);
ASD_DPRINTK("phy%d device plugged: lrate:0x%x, proto:0x%x\n",
phy_id, phy->sas_phy.linkrate, phy->sas_phy.iproto);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_OOB_DONE);
break;
case CURRENT_SPINUP_HOLD:
/* hot plug SATA, no COMWAKE sent */
asd_turn_led(asd_ha, phy_id, 1);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_SPINUP_HOLD);
break;
case CURRENT_GTO_TIMEOUT:
case CURRENT_OOB_ERROR:
ASD_DPRINTK("phy%d error while OOB: oob status:0x%x\n", phy_id,
dl->status_block[1]);
asd_turn_led(asd_ha, phy_id, 0);
sas_phy_disconnected(&phy->sas_phy);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_OOB_ERROR);
break;
}
}
/* If phys are enabled sparsely, this will do the right thing. */
static unsigned ord_phy(struct asd_ha_struct *asd_ha, struct asd_phy *phy)
{
u8 enabled_mask = asd_ha->hw_prof.enabled_phys;
int i, k = 0;
for_each_phy(enabled_mask, enabled_mask, i) {
if (&asd_ha->phys[i] == phy)
return k;
k++;
}
return 0;
}
/**
* asd_get_attached_sas_addr -- extract/generate attached SAS address
* phy: pointer to asd_phy
* sas_addr: pointer to buffer where the SAS address is to be written
*
* This function extracts the SAS address from an IDENTIFY frame
* received. If OOB is SATA, then a SAS address is generated from the
* HA tables.
*
* LOCKING: the frame_rcvd_lock needs to be held since this parses the frame
* buffer.
*/
static void asd_get_attached_sas_addr(struct asd_phy *phy, u8 *sas_addr)
{
if (phy->sas_phy.frame_rcvd[0] == 0x34
&& phy->sas_phy.oob_mode == SATA_OOB_MODE) {
struct asd_ha_struct *asd_ha = phy->sas_phy.ha->lldd_ha;
/* FIS device-to-host */
u64 addr = be64_to_cpu(*(__be64 *)phy->phy_desc->sas_addr);
addr += asd_ha->hw_prof.sata_name_base + ord_phy(asd_ha, phy);
*(__be64 *)sas_addr = cpu_to_be64(addr);
} else {
struct sas_identify_frame *idframe =
(void *)<