GIT 5ababae36e3b7f26351487928f60e17c0edfac97 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/jgarzik/misc-2.6.git#ALL commit 5ababae36e3b7f26351487928f60e17c0edfac97 Author: Andrew Morton Date: Thu Jul 3 23:36:20 2008 -0700 drivers/scsi/broadsas.c: fix uninitialised var warning drivers/scsi/broadsas.c: In function 'bs_phy_control': drivers/scsi/broadsas.c:891: warning: 'rc' may be used uninitialized in this function Cc: Jeff Garzik Signed-off-by: Andrew Morton Signed-off-by: Jeff Garzik commit e10e8b60550d2e5df076f48dc16a3771b9a476bf Author: Jeff Garzik Date: Tue Mar 25 21:15:39 2008 -0400 [SCSI #sas] broadsas: fix build failures commit 400977ad43e82c2cd853e92a6747e5502f7eca44 Author: Jeff Garzik Date: Mon Mar 17 07:28:32 2008 -0400 [SCSI] Add Broadcom SAS/SATA driver (rough draft, not working) Signed-off-by: Jeff Garzik drivers/scsi/Kconfig | 11 + drivers/scsi/Makefile | 1 + drivers/scsi/broadsas.c | 1308 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1320 insertions(+), 0 deletions(-) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index c7f0629..43ea40d 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -505,6 +505,17 @@ config SCSI_AIC7XXX_OLD source "drivers/scsi/aic7xxx/Kconfig.aic79xx" source "drivers/scsi/aic94xx/Kconfig" +config SCSI_BROADSAS + tristate "Broadcom 8603 SAS/SATA support" + depends on PCI + select SCSI_SAS_LIBSAS + help + This driver supports Broadcom SAS/SATA PCI devices. + + To compile this driver as a module, choose M here: the + module will be called broadsas. + +# All the I2O code and drivers do not seem to be 64bit safe. config SCSI_DPT_I2O tristate "Adaptec I2O RAID support " depends on SCSI && PCI && VIRT_TO_BUS diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 72fd504..7d3a12f 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_SCSI_AIC79XX) += aic7xxx/ obj-$(CONFIG_SCSI_AACRAID) += aacraid/ obj-$(CONFIG_SCSI_AIC7XXX_OLD) += aic7xxx_old.o obj-$(CONFIG_SCSI_AIC94XX) += aic94xx/ +obj-$(CONFIG_SCSI_BROADSAS) += broadsas.o obj-$(CONFIG_SCSI_IPS) += ips.o obj-$(CONFIG_SCSI_FD_MCS) += fd_mcs.o obj-$(CONFIG_SCSI_FUTURE_DOMAIN)+= fdomain.o diff --git a/drivers/scsi/broadsas.c b/drivers/scsi/broadsas.c new file mode 100644 index 0000000..bad2908 --- /dev/null +++ b/drivers/scsi/broadsas.c @@ -0,0 +1,1308 @@ +/* + broadsas.c - Broadcom 8603 SAS/SATA support + + Copyright 2007 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty + of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with this program; see the file COPYING. If not, + write to the Free Software Foundation, 675 Mass Ave, Cambridge, + MA 02139, USA. + + --------------------------------------------------------------- + + Technical notes: + * SMP and STP protocols are not supported, despite stuff like + SAS_CTL_SMP, which was left over from an earlier chip. + + Random notes: + 1) investigate whether + bw32(SAS_RX_CONS_IDX, br32(SAS_RX_PROD_IDX)); + will kick the DMA engine into action, even if DMA engine is + disabled. + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "broadsas" +#define DRV_VERSION "0.1" + +struct bs_info; +struct bs_phy; + +#define br32(reg) readl(regs + BS_##reg) +#define bw32(reg,val) writel((val), regs + BS_##reg) +#define bw32_f(reg,val) do { \ + writel((val), regs + BS_##reg); \ + readl(regs + BS_##reg); \ + } while (0) + +/* driver compile-time configuration */ +enum driver_configuration { + BS_TX_RING_SZ = 128, /* OK: 2, 4, 8, 16, 32, 64, 128, 256 */ + BS_RX_RING_SZ = 128, /* OK: 2, 4, 8, 16, 32, 64, 128, 256 */ + BS_TX_FRAME_SZ = 1024, /* max: 24 + 1024 */ + BS_MAX_PRD = 256, /* S/G per slot; my arbitrary guess */ + BS_RX_BUF_SZ = 512, /* Response buffer size; OK: 0-64k */ +}; + +/* unchangeable hardware details */ +enum hardware_details { + BS_PHYS = 8, /* number of storage ports / phys */ + BS_TX_SZ = 32, /* TX descriptor size */ + BS_RX_SZ = 16, /* RX descriptor size */ +}; + +/* global registers */ +enum global_registers { + GBL_CTL = 0x1000, /* global control */ + GBL_STAT = 0x1004, /* global status */ + GBL_INT_TIMING = 0x1014, /* int coalescing control */ + GBL_INT_MASK = 0x1018, /* global int mask */ +}; + +enum bs_port_registers { + /* per-port registers common to SAS and SATA (though some bits differ)*/ + BS_TX_ADDR_LO = 0xA0, /* tx addr lo, hi */ + BS_TX_ADDR_HI = 0xA4, + BS_TX_PROD_IDX = 0xA8, /* tx producer index */ + BS_TX_CONS_IDX = 0xAC, /* tx consumer index (RO) */ + + BS_QDMA_CTL = 0xB0, /* QDMA control */ + BS_QDMA_Q_DEPTH = 0xB4, /* QDMA ring sizing */ + BS_QDMA_STAT = 0xB8, /* QDMA status */ + BS_QDMA_INT_MASK = 0xBC, /* QDMA interrupt mask */ + + /* SAS per-port registers (replaces SATA register space, in SAS mode) */ + BS_SAS_STAT0 = 0x40, /* SAS status 0..1 */ + BS_SAS_STAT1 = 0x44, + + BS_SAS_CTL0 = 0x48, /* SAS control 0..5 */ + BS_SAS_CTL1 = 0x4C, + BS_SAS_CTL2 = 0x50, + BS_SAS_CTL3 = 0x54, + BS_SAS_CTL4 = 0x58, + BS_SAS_CTL5 = 0x5C, + + BS_SAS_ERR_TAGS = 0x70, /* SAS error tags */ + BS_SAS_ERR_STAT = 0x74, /* SAS error status */ + BS_SAS_SACT_TAG_CLR = 0x78, /* SACTIVE tag to clear */ + + BS_SAS_RX_ADDR_LO = 0xC0, /* rx addr lo, hi */ + BS_SAS_RX_ADDR_HI = 0xC4, + BS_SAS_RX_PROD_IDX = 0xC8, /* rx producer index (RO) */ + BS_SAS_RX_CONS_IDX = 0xCC, /* rx consumer index */ + + BS_SAS_RX_IU_MAX_SZ = 0xD0, /* response IU max size */ + + BS_SAS_IDX_ADDR = 0xE0, /* index address */ + BS_SAS_IDX_DATA = 0xE4, /* index data */ + + BS_SAS_DBG_TXDMA = 0xE8, /* TXDMA debug */ + BS_SAS_DBG_RXDMA = 0xEC, /* RXDMA debug */ + BS_SAS_DBG_IUDMA = 0xF0, /* IUDMA debug */ + BS_SAS_DBG_TXFRAME = 0xF4, /* TXFRAME debug */ + BS_SAS_DBG_RXFRAME = 0xF8, /* RXFRAME debug */ + + /* SATA per-port registers (replaces SAS register space, in SAS mode) */ + BS_SATA_STAT = 0x40, /* standard SATA phy SStatus */ + BS_SATA_ERR = 0x44, /* standard SATA phy SError */ + BS_SATA_CTL = 0x48, /* standard SATA phy SControl */ +}; + +enum sas_port_register_bits { + /* BS_SAS_ERR_STAT */ + BS_SAS_ERR_XPRIM_OVER = (1U << 4), /* SSP xmit primitive overflw */ + BS_SAS_ERR_NAK = (1U << 3), /* NAK detected */ + BS_SAS_ERR_ACKNAK_TIMO = (1U << 2), /* ACK/NAK timeout */ + BS_SAS_ERR_PHYRDY = (1U << 1), /* PhyRdy changed */ + BS_SAS_ERR_SATA = (1U << 0), /* SATA device detected */ + + /* BS_SAS_DBG_IUDMA */ + BS_SAS_SAS_REGS = 0, /* Select SAS registers */ + BS_SAS_SATA_REGS = (1U << 16), /* Select SATA registers */ + BS_SAS_SATA_FORCE = (1U << 8), /* Force SATA mode */ + BS_SAS_SATA_ACTIVE = (1U << 0), /* SATA activated (read-only) */ + + QDEPTH_TX_SZ_SHIFT = 0, /* tx ring size */ + QDEPTH_RX_SZ_SHIFT = 8, /* rx ring size */ + QDEPTH_CMD_ISSUE_WINDOW_SHIFT = 16, /* max cmds dev can queue */ + + QINT_ALL_SATA = 0x3f, + QINT_ALL_SAS = 0x1ffff, + + /* SAS version of BS_QDMA_CTL */ + QCTL_XFER_RDY_SKIP_CHK = (1U << 23), /* skip XR cmd desc err chk */ + QCTL_TX_DESC_DATA_END = (1U << 22), /* mark data-end in TX desc */ + QCTL_RXDMA_RST = (1U << 19), /* RXDMA soft reset */ + QCTL_RXDMA_ERR_REP = (1U << 18), /* RXDMA report error */ + QCTL_RXDMA_NO_HANG = (1U << 17), /* 1==continue rx on error */ + QCTL_RXDMA_EN = (1U << 16), /* RXDMA enable */ + QCTL_TXDMA_RST = (1U << 8), /* TXDMA soft reset */ + QCTL_PORT_RST = (1U << 3), /* port reset (in SAS?) */ + QCTL_PAUSE = (1U << 1), /* QDMA pause */ + QCTL_EN = (1U << 0), /* QDMA enable */ + + /* SAS version of BS_QDMA_STAT */ + QSTAT_RSP_INACT = (1U << 31), /* inactive RSP frame tag */ + QSTAT_XR_INACT = (1U << 30), /* XFER_RDY: inact. frame tag */ + QSTAT_XR_SIZE = (1U << 29), /* XFER_RDY: wrong size */ + QSTAT_XR_NON_DATA = (1U << 28), /* XFER_RDY: non-data cmd */ + QSTAT_XR_TAG_MISMATCH = (1U << 27), /* XFER_RDY: tag mismatch */ + QSTAT_DATA_INACT = (1U << 26), /* inactive DATA frame */ + QSTAT_DATA_TX_DESC = (1U << 25), /* DATA: bad cmd TX type */ + QSTAT_DATA_NON_DATA = (1U << 24), /* DATA: non-data cmd */ + QSTAT_DATA_TAG_MISMATCH = (1U << 23), /* DATA: tag mismatch */ + QSTAT_DATA_RUNT = (1U << 22), /* DATA: frame larger than desc */ + QSTAT_OPEN = (1U << 21), /* rx'd OPEN */ + QSTAT_IDENTIFY = (1U << 20), /* rx'd IDENTIFY */ + QSTAT_UNKNOWN = (1U << 19), /* rx'd unknown frame */ + QSTAT_RSP_SPECIAL = (1U << 18), /* rx'd RSP w/ special tag match*/ + QSTAT_RSP_GOOD = (1U << 17), /* rx'd good RSP */ + QSTAT_IUDMA_PARITY = (1U << 16), /* IUDMA bus parity err */ + QSTAT_IUDMA_ABORT = (1U << 15), /* IUDMA bus master abort */ + QSTAT_PHY = (1U << 14), /* PHY change detected */ + QSTAT_SATA = (1U << 13), /* SATA device detected */ + QSTAT_PROTOCOL = (1U << 12), /* SAS proto specific err */ + QSTAT_RXDMA_ERR = (1U << 11), /* RXDMA err int */ + QSTAT_RXDMA_PARITY = (1U << 10), /* RXDMA bus parity err */ + QSTAT_RXDMA_ABORT = (1U << 9), /* RXDMA bus master abort */ + QSTAT_CQDMA_ABORT_PARITY= (1U << 8), /* cqdma? master abrt / par err */ + QSTAT_TXDMA_UNDER = (1U << 7), /* TXDMA underrun */ + QSTAT_TXDMA_PARITY = (1U << 6), /* TXDMA bus parity err */ + QSTAT_TXDMA_ABORT = (1U << 5), /* TXDMA bus master abort */ + QSTAT_TX_KILLED = (1U << 4), /* internal/software TX abort */ + QSTAT_RXDMA_OK = (1U << 3), /* RXDMA non-error int */ + QSTAT_DMA_ABORTED = (1U << 2), /* QDMA abort acknowledge */ + QSTAT_DMA_PAUSED = (1U << 1), /* QDMA pause acknowledge */ + QSTAT_DONE = (1U << 0), /* cmd/prd done, CLOSE, etc. */ + + SAS_CTL_NO_DATA = (1U << 8), /* Command with no data */ + SAS_CTL_SATA = 0, /* Connection type: SATA */ + SAS_CTL_SSP = (1U << 5), /* Connection type: SSP */ + SAS_CTL_STP = (1U << 6), /* Connection type: STP */ + SAS_CTL_SMP = (3U << 5), /* Connection type: SMP */ + SAS_CTL_SKIP = (1U << 4), /* Skip this entry */ + SAS_CTL_FIRST_BURST = (1U << 2), /* Frst brst active 4 cur wr cmd */ + SAS_CTL_DIR_READ = (1U << 1),/* Data direction (1==dev to mem) */ + SAS_CTL_EIN = (1U << 0), /* INT upon response frame */ + + /* BS_SAS_CTL0 */ + CTL0_INIT_LOWER = (1U << 16), /* initiator SAS addr lower + than target SAS addr */ + CTL0_CXN_MGMT = (1U << 14), /* open/close cxn based on + outstanding frames */ + CTL0_SPINUP = (1U << 13), /* send notify spinup prim */ + CTL0_SSP = (1U << 9), /* accept SSP */ + CTL0_HARD_RST = (1U << 6), /* hard reset */ + CTL0_EN = (1U << 0), /* phy enable */ + + PRD_EOT = (1U << 31), /* end of PRD table */ + + BSF_MSI = (1U << 0), /* PCI MSI active */ +}; + +enum bs_tx_bits { + TX_DESC_SAS = 0x10, +}; + +enum bs_rx_flag_bits { + RSPD_DA = (1U << 7), /* data available */ + + RSP_TYPE_MASK = 0x7, + RSTYPE_NORMAL = 0, /* normal RSP frame */ + RSTYPE_IDENTIFY = 1, /* IDENTIFY frame */ + RSTYPE_OPEN = 2, /* OPEN frame */ + RSTYPE_UNKNOWN = 3, /* unknown frame */ + RSTYPE_XFER_RDY_ERR = 4, /* XFER_RDY with error */ + RSTYPE_DATA_ERR = 5, /* DATA with error */ + RSTYPE_INACT = 6, /* RSP frame w/ inactive tag */ + RSTYPE_SP_TAG = 7, /* RSP frame w/ special tag */ +}; + +struct bs_tx_raw { + __le32 word[8]; +}; + +struct bs_tx_sas { + __le32 flag_type; /* RDE; ctl flag; desc type */ + __le32 tag_len; /* tag; IU length */ + __le64 frame; /* frame/IU addr */ + __le64 next_sg; /* Next SG addr */ + __le32 data_lo; /* First data buf addr lo */ + __le32 data_hi; /* First data buf addr hi, len*/ +}; + +struct bs_prd { + __le32 addr_lo; /* buffer address (low) */ + __le32 addr_hi_flags; /* addr (hi), len, flags */ +}; + +struct bs_rx_desc { + __le32 tag_flags; + __le32 reserved; + __le64 buf_addr; +}; + +struct bs_tx_slot { + struct bs_prd *prd; + dma_addr_t prd_dma; + + void *frame_buf; + dma_addr_t frame_dma; +}; + +struct bs_rx_slot { + void *rbuf; + dma_addr_t rbuf_dma; +}; + +struct bs_tag_info { + struct bs_phy *phy; + struct sas_task *task; + unsigned int n_elem; +}; + +struct bs_port { + struct asd_sas_port sas_port; +}; + +struct bs_phy { + struct bs_info *bsi; + struct bs_port *port; + + void __iomem *regs; /* this phy's registers */ + + struct bs_tx_raw *tx; /* TX (command) queue */ + dma_addr_t tx_dma; + u32 tx_prod; /* cached producer idx */ + + struct bs_rx_desc *rx; /* RX (response) queue (SAS) */ + dma_addr_t rx_dma; + + struct asd_sas_phy sas_phy; + + unsigned long tags[(BS_TX_RING_SZ / sizeof(unsigned long))+1]; + struct bs_tag_info tag_info[BS_TX_RING_SZ]; + + struct bs_tx_slot tx_slot[BS_TX_RING_SZ]; /* per-tx-slot info */ + struct bs_rx_slot rx_slot[BS_RX_RING_SZ]; /* per-rx-slot info */ + + u8 frame_rcvd[24 + 1024]; +}; + +struct bs_info { + unsigned long flags; + struct pci_dev *pdev; + void __iomem *regs; + + spinlock_t lock; + + u8 sas_addr[SAS_ADDR_SIZE]; + struct sas_ha_struct sas; + + struct Scsi_Host *shost; + struct bs_phy phy[BS_PHYS]; + struct bs_port port[BS_PHYS]; +}; + +static struct scsi_transport_template *bs_stt; + +static struct scsi_host_template bs_sht = { + .module = THIS_MODULE, + .name = DRV_NAME, + .queuecommand = sas_queuecommand, + .target_alloc = sas_target_alloc, + .slave_configure = sas_slave_configure, + .slave_destroy = sas_slave_destroy, + .change_queue_depth = sas_change_queue_depth, + .change_queue_type = sas_change_queue_type, + .bios_param = sas_bios_param, + .can_queue = 1, + .cmd_per_lun = 1, + .this_id = -1, + .sg_tablesize = SG_ALL, + .max_sectors = SCSI_DEFAULT_MAX_SECTORS, + .use_clustering = ENABLE_CLUSTERING, + .eh_device_reset_handler= sas_eh_device_reset_handler, + .eh_bus_reset_handler = sas_eh_bus_reset_handler, + .slave_alloc = sas_slave_alloc, + .target_destroy = sas_target_destroy, + .ioctl = sas_ioctl, +}; + +/* move to PCI layer or libata core? */ +static int pci_go_64(struct pci_dev *pdev) +{ + int rc; + + if (!pci_set_dma_mask(pdev, DMA_47BIT_MASK)) { + rc = pci_set_consistent_dma_mask(pdev, DMA_47BIT_MASK); + if (rc) { + rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "64-bit DMA enable failed\n"); + return rc; + } + } + } else { + rc = pci_set_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "32-bit DMA enable failed\n"); + return rc; + } + rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "32-bit consistent DMA enable failed\n"); + return rc; + } + } + + return rc; +} + +static void __iomem *bs_phy_regs(struct bs_info *bsi, int phy_no) +{ + return bsi->regs + (0x100 * phy_no); +} + +static bool bs_sata_active(void __iomem *regs) +{ + return br32(SAS_DBG_IUDMA) & BS_SAS_SATA_ACTIVE; +} + +static void bs_tag_clear(struct bs_phy *phy, unsigned int tag) +{ + phy->tags[tag / sizeof(unsigned long)] &= + ~(1UL << (tag % sizeof(unsigned long))); +} + +static void bs_tag_set(struct bs_phy *phy, unsigned int tag) +{ + phy->tags[tag / sizeof(unsigned long)] |= + (1UL << (tag % sizeof(unsigned long))); +} + +static bool bs_tag_test(struct bs_phy *phy, unsigned int tag) +{ + return phy->tags[tag / sizeof(unsigned long)] & + (1UL << (tag % sizeof(unsigned long))); +} + +static int bs_tag_alloc(struct bs_phy *phy, unsigned int *tag_out) +{ + unsigned int i; + + for (i = 0; i < BS_TX_RING_SZ; i++) + if (!bs_tag_test(phy, i)) { + bs_tag_set(phy, i); + *tag_out = i; + return 0; + } + + return -EBUSY; +} + +static void bs_phy_int_sata(struct bs_phy *phy) +{ + void __iomem *regs = phy->regs; + u32 dma_stat; + + /* FIXME / TODO */ + + dev_printk(KERN_ERR, &phy->bsi->pdev->dev, "unexpected SATA int\n"); + + dma_stat = br32(QDMA_STAT); + if (dma_stat) + bw32(QDMA_STAT, dma_stat); +} + +static void bs_slot_free(struct bs_phy *phy, struct bs_tag_info *bti, + struct sas_task *task, unsigned int tag) +{ + if (bti->n_elem) + pci_unmap_sg(phy->bsi->pdev, task->scatter, + bti->n_elem, task->data_dir); + + bs_tag_clear(phy, tag); +} + +static void bs_slot_complete(struct bs_phy *phy, unsigned int tag, + unsigned int rx_idx, u32 flags) +{ + struct bs_tag_info *bti = &phy->tag_info[tag]; + struct bs_rx_slot *slot = &phy->rx_slot[tag]; + struct sas_task *task = bti->task; + struct task_status_struct *tstat = &task->task_status; + bool aborted; + + spin_lock(&task->task_state_lock); + aborted = task->task_state_flags & SAS_TASK_STATE_ABORTED; + if (!aborted) { + task->task_state_flags &= + ~(SAS_TASK_STATE_PENDING | SAS_TASK_AT_INITIATOR); + task->task_state_flags |= SAS_TASK_STATE_DONE; + } + spin_unlock(&task->task_state_lock); + + if (aborted) + return; + + memset(tstat, 0, sizeof(*tstat)); + + /* defaults */ + tstat->resp = SAS_TASK_COMPLETE; + tstat->stat = SAM_CHECK_COND; + + /* process response based on protocol */ + switch (task->task_proto) { + case SAS_PROTOCOL_SSP: + /* response frame returned in our storage buffer */ + if (flags & RSPD_DA) { + struct ssp_response_iu *iu = slot->rbuf; + sas_ssp_task_response(&phy->bsi->pdev->dev, task, iu); + } + + /* response frame summarized in RX descriptor */ + else { + struct ssp_response_iu iu = { + .status = (flags >> 8) & 0xff, + }; + sas_ssp_task_response(&phy->bsi->pdev->dev, task, &iu); + } + break; + + default: /* do nothing, fail via tstat defaults above */ + break; + } + + /* re-init RX slot */ + phy->rx[rx_idx].tag_flags = cpu_to_le32(RSPD_DA); + + bs_slot_free(phy, bti, task, tag); + task->task_done(task); +} + +static void bs_sas_rx(struct bs_phy *phy) +{ + void __iomem *regs = phy->regs; + u32 prod_idx, cons_idx, tag, rdtype, flags; + struct bs_rx_desc *rx; + bool work_done = false; + + prod_idx = br32(SAS_RX_PROD_IDX); + cons_idx = br32(SAS_RX_CONS_IDX); + + while (prod_idx != cons_idx) { + rx = &phy->rx[cons_idx]; + + flags = le32_to_cpu(rx->tag_flags); + tag = flags >> 16; + rdtype = flags & RSP_TYPE_MASK; + + switch (rdtype) { + case RSTYPE_NORMAL: /* normal RSP frame */ + WARN_ON(!bs_tag_test(phy, tag)); + bs_slot_complete(phy, tag, cons_idx, flags); + break; + + default: + dev_printk(KERN_WARNING, &phy->bsi->pdev->dev, + "unsupported frame type %d " + "(flags %x, prod %d, cons %d)\n", + rdtype, flags, prod_idx, cons_idx); + break; + } + + work_done = true; + cons_idx = (cons_idx + 1) & (BS_RX_RING_SZ - 1); + } + + if (work_done) + bw32(SAS_RX_CONS_IDX, cons_idx); +} + +static void bs_phy_int_sas(struct bs_phy *phy) +{ + void __iomem *regs = phy->regs; + u32 sas_err, dma_stat; + + sas_err = br32(SAS_ERR_STAT); + if (sas_err) { + bw32(SAS_ERR_STAT, sas_err); + dev_printk(KERN_ERR, &phy->bsi->pdev->dev, + "SAS_ERR_STAT 0x%x\n", sas_err); + } + + dma_stat = br32(QDMA_STAT); + if (dma_stat) { + bw32(QDMA_STAT, dma_stat); + + if (dma_stat & QSTAT_DONE) + bs_sas_rx(phy); + } +} + +static void bs_phy_int(struct bs_phy *phy) +{ + if (bs_sata_active(phy->regs)) + bs_phy_int_sata(phy); + else + bs_phy_int_sas(phy); +} + +static irqreturn_t bs_interrupt(int irq, void *opaque) +{ + struct bs_info *bsi = opaque; + int i; + u32 stat; + + stat = readl(bsi->regs + GBL_STAT); + if (stat == 0 || stat == 0xffffffff) + return IRQ_NONE; + + spin_lock(&bsi->lock); + + for (i = 0; i < BS_PHYS; i++) + if (stat & (1U << i)) + bs_phy_int(&bsi->phy[i]); + + spin_unlock(&bsi->lock); + + return IRQ_HANDLED; +} + +static void bs_fill_sg(struct bs_tx_sas *cmd, struct bs_tx_slot *tx_slot, + struct scatterlist *sg, unsigned int n_elem) +{ + int i; + u32 eot; + + for (i = 0; i < n_elem; i++) { + if (i == (n_elem - 1)) + eot = PRD_EOT; + else + eot = 0; + if (i == 0) { + if (eot) + eot = (1 << 0); + cmd->data_lo = cpu_to_le32(sg_dma_address(sg) | eot); + cmd->data_hi = cpu_to_le32( + ((((u64)sg_dma_address(sg)) >> 32) & 0xffff) | + (sg_dma_len(sg) << 16)); + } else { + struct bs_prd *prd; + + prd = &tx_slot->prd[i - 1]; + + prd->addr_lo = cpu_to_le32(sg_dma_address(sg)); + prd->addr_hi_flags = cpu_to_le32(eot | + ((((u64)sg_dma_address(sg)) >> 16) & 0x7fff0000) | + (sg_dma_len(sg) & 0xffff)); + } + + sg++; + } +} + +static int bs_task_exec_sata(struct bs_phy *phy, + struct sas_task *task, unsigned int tag, + const int num, gfp_t gfp_flags) +{ + return -EINVAL; +} + +static void bs_task_prep_ssp(struct bs_info *bsi, struct sas_task *task, + struct bs_tx_slot *tx_slot, unsigned int tag, + unsigned int *iu_len) +{ + struct ssp_frame_hdr *ssp_hdr; + u8 *buf_cmd; + u8 fburst = 0; + + if (task->ssp_task.enable_first_burst) + fburst = (1 << 7); + + memset(tx_slot->frame_buf, 0, BS_TX_FRAME_SZ); + ssp_hdr = tx_slot->frame_buf; + ssp_hdr->frame_type = SSP_COMMAND; + memcpy(ssp_hdr->hashed_dest_addr, task->dev->hashed_sas_addr, + HASHED_SAS_ADDR_SIZE); + memcpy(ssp_hdr->hashed_src_addr, + task->dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); + ssp_hdr->tag = cpu_to_be16(tag); + + /* fill in command frame IU */ + buf_cmd = tx_slot->frame_buf + sizeof(*ssp_hdr); + memcpy(buf_cmd, &task->ssp_task.LUN, 8); + buf_cmd[9] = fburst | + task->ssp_task.task_attr | + (task->ssp_task.task_prio << 3); + memcpy(buf_cmd + 12, &task->ssp_task.cdb, 16); + + *iu_len = 28; /* size of COMMAND frame IU */ +} + +static int bs_task_exec_sas(struct bs_phy *phy, + struct sas_task *task, unsigned int tag, + const int num, gfp_t gfp_flags) +{ + u32 slot_idx = phy->tx_prod; + struct bs_tx_sas *cmd = (struct bs_tx_sas *) &phy->tx[slot_idx]; + struct bs_tx_slot *tx_slot = &phy->tx_slot[slot_idx]; + void __iomem *regs = phy->regs; + u32 ctl_flags = SAS_CTL_EIN; + unsigned int n_elem = 0, iu_len = 0; + int rc; + + if (task->num_scatter) { + n_elem = pci_map_sg(phy->bsi->pdev, task->scatter, + task->num_scatter, task->data_dir); + if (!n_elem) + return -ENOMEM; + } + + if (task->data_dir == DMA_NONE) + ctl_flags |= SAS_CTL_NO_DATA; + else if (task->data_dir == DMA_FROM_DEVICE) + ctl_flags |= SAS_CTL_DIR_READ; + + switch (task->task_proto) { + case SAS_PROTOCOL_SSP: + ctl_flags |= SAS_CTL_SSP; + bs_task_prep_ssp(phy->bsi, task, tx_slot, tag, &iu_len); + break; + + default: + WARN_ON(1); + /* fall through */ + + case SAS_PROTOCOL_STP: + case SAS_PROTOCOL_SMP: + rc = -EINVAL; + goto err_out; + } + + cmd->flag_type = cpu_to_le32(TX_DESC_SAS | + (ctl_flags << 8)); + cmd->tag_len = cpu_to_le32((tag << 16) | iu_len); + cmd->frame = cpu_to_le64(tx_slot->frame_dma); + + if (n_elem) { + cmd->next_sg = cpu_to_le64(tx_slot->prd_dma); + bs_fill_sg(cmd, tx_slot, task->scatter, n_elem); + } else + cmd->next_sg = 0; + + spin_lock(&task->task_state_lock); + task->task_state_flags |= SAS_TASK_AT_INITIATOR; + spin_unlock(&task->task_state_lock); + + phy->tx_prod = + slot_idx = (slot_idx + 1) & (BS_TX_RING_SZ - 1); + bw32(TX_PROD_IDX, slot_idx); + + phy->tag_info[tag].n_elem = n_elem; + return 0; + +err_out: + if (n_elem) + pci_unmap_sg(phy->bsi->pdev, task->scatter, n_elem, + task->data_dir); + return rc; +} + +static int bs_task_exec(struct sas_task *task, const int num, gfp_t gfp_flags) +{ + struct bs_info *bsi = task->dev->port->ha->lldd_ha; + struct bs_phy *phy = &bsi->phy[0]; /* FIXME FIXME FIXME */ + int rc; + unsigned int tag = 0xdeadbeef; + unsigned long flags; + bool sata; + + sata = bs_sata_active(phy->regs); + if (sata) { + if (task->task_proto != SAS_PROTOCOL_SATA) + return -EINVAL; + } else { + if (task->task_proto == SAS_PROTOCOL_SATA) + return -EINVAL; + } + + spin_lock_irqsave(&bsi->lock, flags); + + rc = bs_tag_alloc(phy, &tag); + if (rc) + goto out; + + if (sata) + rc = bs_task_exec_sata(phy, task, tag, num, gfp_flags); + else + rc = bs_task_exec_sas(phy, task, tag, num, gfp_flags); + + if (rc) + goto err_out; + + phy->tag_info[tag].phy = phy; + phy->tag_info[tag].task = task; + +out: + spin_unlock_irqrestore(&bsi->lock, flags); + return rc; + +err_out: + bs_tag_clear(phy, tag); + goto out; +} + +static void bs_phy_free(struct bs_phy *phy) +{ + struct bs_info *bsi = phy->bsi; + int i; + + if (phy->tx) + dma_free_coherent(&bsi->pdev->dev, BS_TX_SZ * BS_TX_RING_SZ, + phy->tx, phy->tx_dma); + if (phy->rx) + dma_free_coherent(&bsi->pdev->dev, BS_RX_SZ * BS_RX_RING_SZ, + phy->rx, phy->rx_dma); + + for (i = 0; i < BS_TX_RING_SZ; i++) { + struct bs_tx_slot *tx_slot = &phy->tx_slot[i]; + + if (tx_slot->prd) { + dma_free_coherent(&bsi->pdev->dev, + BS_MAX_PRD * sizeof(struct bs_prd), + tx_slot->prd, tx_slot->prd_dma); + } + if (tx_slot->frame_buf) { + dma_free_coherent(&bsi->pdev->dev, BS_TX_FRAME_SZ, + tx_slot->frame_buf, + tx_slot->frame_dma); + } + } + + for (i = 0; i < BS_RX_RING_SZ; i++) { + struct bs_rx_slot *rx_slot = &phy->rx_slot[i]; + + if (rx_slot->rbuf) { + dma_free_coherent(&bsi->pdev->dev, BS_RX_BUF_SZ, + rx_slot->rbuf, rx_slot->rbuf_dma); + } + } +} + +static void bs_free(struct bs_info *bsi) +{ + int i; + + if (!bsi) + return; + + for (i = 0; i < BS_PHYS; i++) + bs_phy_free(&bsi->phy[i]); + + if (bsi->regs) + iounmap(bsi->regs); + if (bsi->shost) + scsi_host_put(bsi->shost); + kfree(bsi->sas.sas_port); + kfree(bsi->sas.sas_phy); + kfree(bsi); +} + +static int __devinit bs_phy_alloc(struct bs_info *bsi, int phy_no) +{ + struct bs_phy *phy = &bsi->phy[phy_no]; + int i; + + phy->bsi = bsi; + phy->regs = bs_phy_regs(bsi, phy_no); + bs_tag_set(phy, BS_TX_RING_SZ - 1); + + phy->tx = dma_alloc_coherent(&bsi->pdev->dev, BS_TX_SZ * BS_TX_RING_SZ, + &phy->tx_dma, GFP_KERNEL); + if (!phy->tx) + return -ENOMEM; + + phy->rx = dma_alloc_coherent(&bsi->pdev->dev, BS_RX_SZ * BS_RX_RING_SZ, + &phy->rx_dma, GFP_KERNEL); + if (!phy->rx) + return -ENOMEM; + + for (i = 0; i < BS_TX_RING_SZ; i++) { + struct bs_tx_slot *tx_slot = &phy->tx_slot[i]; + + tx_slot->prd = dma_alloc_coherent(&bsi->pdev->dev, + BS_MAX_PRD * sizeof(struct bs_prd), + &tx_slot->prd_dma, GFP_KERNEL); + if (!tx_slot->prd) + return -ENOMEM; + tx_slot->frame_buf = dma_alloc_coherent(&bsi->pdev->dev, + BS_TX_FRAME_SZ, &tx_slot->frame_dma, + GFP_KERNEL); + if (!tx_slot->frame_buf) + return -ENOMEM; + } + + for (i = 0; i < BS_RX_RING_SZ; i++) { + struct bs_rx_slot *rx_slot = &phy->rx_slot[i]; + + rx_slot->rbuf = dma_alloc_coherent(&bsi->pdev->dev,BS_RX_BUF_SZ, + &rx_slot->rbuf_dma, GFP_KERNEL); + if (!rx_slot->rbuf) + return -ENOMEM; + + phy->rx[i].tag_flags = cpu_to_le32(RSPD_DA); + phy->rx[i].buf_addr = cpu_to_le64(rx_slot->rbuf_dma); + } + + return 0; +} + +/* FIXME: locking? */ +static int bs_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func, + void *funcdata) +{ + struct bs_phy *phy = sas_phy->lldd_phy; + void __iomem *regs = phy->regs; + int rc = -EOPNOTSUPP; + u32 tmp; + + switch (func) { + case PHY_FUNC_LINK_RESET: + /* FIXME: correct? */ + tmp = br32(SAS_CTL0); + if (!(tmp & CTL0_EN)) { + bw32_f(SAS_CTL0, tmp | CTL0_EN); + break; + } + + /* fall through */ + + case PHY_FUNC_HARD_RESET: + tmp = br32(SAS_CTL0); + if (tmp & CTL0_HARD_RST) + tmp &= ~CTL0_HARD_RST; + else + bw32_f(SAS_CTL0, tmp | CTL0_HARD_RST); + udelay(500); + bw32_f(SAS_CTL0, tmp); + break; + + case PHY_FUNC_DISABLE: + tmp = br32(SAS_CTL0); + if (tmp & CTL0_EN) + bw32_f(SAS_CTL0, tmp & ~CTL0_EN); + break; + + case PHY_FUNC_RELEASE_SPINUP_HOLD: + + case PHY_FUNC_SET_LINK_RATE: + default: + break; + } + + return rc; +} + +static void __devinit bs_phy_init(struct bs_info *bsi, int phy_id) +{ + struct bs_phy *phy = &bsi->phy[phy_id]; + struct asd_sas_phy *sas_phy = &phy->sas_phy; + + sas_phy->enabled = 1; + sas_phy->class = SAS; + sas_phy->iproto = SAS_PROTOCOL_ALL; + sas_phy->tproto = 0; + sas_phy->type = PHY_TYPE_PHYSICAL; + sas_phy->role = PHY_ROLE_INITIATOR; + sas_phy->oob_mode = OOB_NOT_CONNECTED; + sas_phy->linkrate = SAS_LINK_RATE_UNKNOWN; + + sas_phy->id = phy_id; + sas_phy->sas_addr = &bsi->sas_addr[0]; + sas_phy->frame_rcvd = &phy->frame_rcvd[0]; + sas_phy->ha = &bsi->sas; + sas_phy->lldd_phy = phy; +} + +static struct bs_info * __devinit bs_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct bs_info *bsi; + unsigned long res_start, res_len; + struct asd_sas_phy **arr_phy; + struct asd_sas_port **arr_port; + int i; + + bsi = kzalloc(sizeof(*bsi), GFP_KERNEL); + if (!bsi) + return NULL; + + spin_lock_init(&bsi->lock); + bsi->pdev = pdev; + + bsi->shost = scsi_host_alloc(&bs_sht, sizeof(void *)); + if (!bsi->shost) + goto err_out; + + arr_phy = kcalloc(BS_PHYS, sizeof(void *), GFP_KERNEL); + arr_port = kcalloc(BS_PHYS, sizeof(void *), GFP_KERNEL); + if (!arr_phy || !arr_port) + goto err_out; + + for (i = 0; i < BS_PHYS; i++) { + bs_phy_init(bsi, i); + arr_phy[i] = &bsi->phy[i].sas_phy; + arr_port[i] = &bsi->port[i].sas_port; + } + + SHOST_TO_SAS_HA(bsi->shost) = &bsi->sas; + bsi->shost->transportt = bs_stt; + bsi->shost->max_id = ~0; + bsi->shost->max_lun = ~0; + bsi->shost->max_cmd_len = ~0; + + bsi->sas.sas_ha_name = DRV_NAME; + bsi->sas.dev = &pdev->dev; + bsi->sas.lldd_module = THIS_MODULE; + bsi->sas.sas_addr = &bsi->sas_addr[0]; + bsi->sas.sas_phy = arr_phy; + bsi->sas.sas_port = arr_port; + bsi->sas.num_phys = BS_PHYS; + bsi->sas.lldd_max_execute_num = BS_TX_RING_SZ - 1; /* FIXME: correct? */ + bsi->sas.lldd_queue_size = BS_TX_RING_SZ - 1; /* FIXME: correct? */ + bsi->sas.lldd_ha = bsi; + bsi->sas.core.shost = bsi->shost; + + res_start = pci_resource_start(pdev, 2); + res_len = pci_resource_len(pdev, 2); + if (!res_start || !res_len) + goto err_out; + + bsi->regs = ioremap_nocache(res_start, res_len); + if (!bsi->regs) + goto err_out; + + for (i = 0; i < BS_PHYS; i++) + if (bs_phy_alloc(bsi, i)) + goto err_out; + + /* FIXME: obtain SAS address from somewhere */ + + return bsi; + +err_out: + bs_free(bsi); + return NULL; +} + +static void __devinit bs_phy_clear(struct bs_phy *phy, bool sata) +{ + void __iomem *regs = phy->regs; + u32 tmp; + + tmp = br32(QDMA_STAT); + if (tmp) + bw32(QDMA_STAT, tmp); + + if (!sata) { + tmp = br32(SAS_ERR_STAT); + if (tmp) + bw32(SAS_ERR_STAT, tmp); + } +} + +static void __devinit bs_phy_hw_init(struct bs_phy *phy) +{ + void __iomem *regs = phy->regs; + u32 tmp; + bool sata; + + /* + * first, make sure things are quiet + */ + + sata = bs_sata_active(regs); + if (sata) /* quiet phy */ + bw32(SATA_CTL, 0x3); + else + bw32(SAS_CTL0, br32(SAS_CTL0) & ~CTL0_EN); + bw32_f(QDMA_CTL, 0); /* quiet DMA engines */ + + /* + * reset port machinery + */ + + if (sata) + tmp = QCTL_PORT_RST; + else + tmp = QCTL_RXDMA_RST | QCTL_TXDMA_RST; + bw32_f(QDMA_CTL, tmp); + + udelay(250); + + bw32_f(QDMA_CTL, 0); + bw32(SAS_RX_CONS_IDX, 0); + + /* paranoia: did/would SATA mode change? */ + sata = bs_sata_active(regs); + bs_phy_clear(phy, sata); + + /* + * reset phy + */ + + if (sata) { + bw32(SATA_CTL, 0x301); /* initiate phy reset */ + } else { + bw32_f(SAS_CTL0, br32(SAS_CTL0) | CTL0_HARD_RST | CTL0_EN); + udelay(500); + tmp = br32(SAS_CTL0); + if (tmp & CTL0_HARD_RST) { + tmp &= ~CTL0_HARD_RST; + bw32_f(SAS_CTL0, tmp); + } + } + + /* + * configure SAS/SATA TX ring + */ + + bw32(TX_ADDR_LO, phy->tx_dma); + bw32(TX_ADDR_HI, (phy->tx_dma >> 16) >> 16); + phy->tx_prod = br32(TX_CONS_IDX); + bw32(TX_PROD_IDX, phy->tx_prod); /* == empty */ + + tmp = BS_TX_RING_SZ - 1; + if (!sata) { + tmp |= (BS_RX_RING_SZ << QDEPTH_RX_SZ_SHIFT); + tmp |= (BS_TX_RING_SZ << QDEPTH_CMD_ISSUE_WINDOW_SHIFT); + } + bw32(QDMA_Q_DEPTH, tmp); + + /* + * configure SAS RX ring + */ + if (!sata) { + bw32(SAS_RX_ADDR_LO, phy->rx_dma); + bw32(SAS_RX_ADDR_HI, (phy->rx_dma >> 16) >> 16); + bw32(SAS_RX_CONS_IDX, br32(SAS_RX_PROD_IDX)); /* == empty */ + bw32(SAS_RX_IU_MAX_SZ, BS_RX_BUF_SZ); + } + + /* all interrupt sources are palatable; turn them all on */ + if (sata) + bw32(QDMA_INT_MASK, QINT_ALL_SATA); + else + bw32(QDMA_INT_MASK, QINT_ALL_SAS); +} + +static void __devinit bs_phy_enable(struct bs_phy *phy) +{ + void __iomem *regs = phy->regs; + bool sata = bs_sata_active(regs); + u32 tmp; + + tmp = QCTL_EN; + if (!sata) + tmp |= QCTL_RXDMA_EN; + bw32_f(QDMA_CTL, tmp); + + bs_phy_clear(phy, sata); +} + +static void __devinit bs_hw_init(struct bs_info *bsi) +{ + int i; + + /* first, make sure interrupts are masked */ + writel(0, bsi->regs + GBL_INT_MASK); + readl(bsi->regs + GBL_INT_MASK); /* flush */ + for (i = 0; i < BS_PHYS; i++) { + void __iomem *regs = bsi->phy[i].regs; + bw32(QDMA_INT_MASK, 0); + } + + /* reset and init phys */ + for (i = 0; i < BS_PHYS; i++) + bs_phy_hw_init(&bsi->phy[i]); + + /* enable phy DMA engines */ + for (i = 0; i < BS_PHYS; i++) + bs_phy_enable(&bsi->phy[i]); + + /* enable ints for phys 0-7 */ + writel(0xff, bsi->regs + GBL_INT_MASK); +} + +static void __devinit bs_print_info(struct bs_info *bsi) +{ + struct pci_dev *pdev = bsi->pdev; + static int printed_version; + + if (!printed_version++) + dev_printk(KERN_INFO, &pdev->dev, "version " DRV_VERSION "\n"); + + dev_printk(KERN_INFO, &pdev->dev, "%u phys, addr %llx\n", + BS_PHYS, SAS_ADDR(bsi->sas_addr)); +} + +static int __devinit bs_pci_init(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int rc; + struct bs_info *bsi; + + rc = pci_enable_device(pdev); + if (rc) + return rc; + + pci_set_master(pdev); + + rc = pci_request_regions(pdev, DRV_NAME); + if (rc) + goto err_out_disable; + + rc = pci_go_64(pdev); + if (rc) + goto err_out_regions; + + bsi = bs_alloc(pdev, ent); + if (!bsi) { + rc = -ENOMEM; + goto err_out_regions; + } + + bs_hw_init(bsi); + + if (!pci_enable_msi(pdev)) + bsi->flags |= BSF_MSI; + + rc = request_irq(pdev->irq, bs_interrupt, IRQF_SHARED, DRV_NAME, bsi); + if (rc) + goto err_out_msi; + + rc = scsi_add_host(bsi->shost, &pdev->dev); + if (rc) + goto err_out_irq; + + rc = sas_register_ha(&bsi->sas); + if (rc) + goto err_out_shost; + + pci_set_drvdata(pdev, bsi); + + bs_print_info(bsi); + + scsi_scan_host(bsi->shost); + return 0; + +err_out_shost: + scsi_remove_host(bsi->shost); +err_out_irq: + free_irq(pdev->irq, bsi); +err_out_msi: + if (bsi->flags & BSF_MSI) + pci_disable_msi(pdev); + bs_free(bsi); +err_out_regions: + pci_release_regions(pdev); +err_out_disable: + pci_disable_device(pdev); + return rc; +} + +static void __devexit bs_pci_remove(struct pci_dev *pdev) +{ + struct bs_info *bsi = pci_get_drvdata(pdev); + + pci_set_drvdata(pdev, NULL); + + sas_unregister_ha(&bsi->sas); + sas_remove_host(bsi->shost); + scsi_remove_host(bsi->shost); + + free_irq(pdev->irq, bsi); + if (bsi->flags & BSF_MSI) + pci_disable_msi(pdev); + bs_free(bsi); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static struct sas_domain_function_template bs_transport_ops = { + .lldd_execute_task = bs_task_exec, + .lldd_control_phy = bs_phy_control, +}; + +static struct pci_device_id __devinitdata bs_pci_table[] = { + { PCI_VDEVICE(BROADCOM, 0x0252) }, + + { } /* terminate list */ +}; + +static struct pci_driver bs_pci_driver = { + .name = DRV_NAME, + .id_table = bs_pci_table, + .probe = bs_pci_init, + .remove = __devexit_p(bs_pci_remove), +}; + +static int __init bs_init(void) +{ + int rc; + + bs_stt = sas_domain_attach_transport(&bs_transport_ops); + if (!bs_stt) + return -ENOMEM; + + rc = pci_register_driver(&bs_pci_driver); + if (rc) + goto err_out; + + return 0; + +err_out: + sas_release_transport(bs_stt); + return rc; +} + +static void __exit bs_exit(void) +{ + pci_unregister_driver(&bs_pci_driver); + sas_release_transport(bs_stt); +} + +module_init(bs_init); +module_exit(bs_exit); + +MODULE_AUTHOR("Jeff Garzik "); +MODULE_DESCRIPTION("Broadcom 8603 SAS/SATA controller driver"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, bs_pci_table); +