| /******************************************************************************* |
| Copyright (C) Marvell International Ltd. and its affiliates |
| |
| ******************************************************************************** |
| 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. |
| *******************************************************************************/ |
| /* |
| * Copyright (C) Freescale Semiconductor, Inc. 2006. |
| * Author: Jason Jin<Jason.jin@freescale.com> |
| * Zhang Wei<wei.zhang@freescale.com> |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * 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 of |
| * the License, 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; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| * |
| * |
| * |
| */ |
| #include <common.h> |
| |
| #include <command.h> |
| #include <pci.h> |
| #include <asm/processor.h> |
| #include <asm/errno.h> |
| #include <asm/io.h> |
| #include <malloc.h> |
| #include <scsi.h> |
| #include <ata.h> |
| #include <linux/ctype.h> |
| #include <mv94xx_scsi.h> |
| |
| struct host_info *mv_host = NULL; |
| hd_driveid_t *ataid[MV_MAX_DEVICES]; |
| static unsigned long scsi_mem_addr; |
| #define msleep(a) udelay(a * 1000) |
| #define ssleep(a) msleep(a * 1000) |
| |
| #define bus_to_phys(a) pci_mem_to_phys(busdevfunc, (unsigned long) (a)) |
| #define phys_to_bus(a) pci_phys_to_mem(busdevfunc, (unsigned long) (a)) |
| |
| void scsi_write_dword(ulong offset,ulong val) |
| { |
| writel(val, scsi_mem_addr+offset); |
| } |
| |
| ulong scsi_read_dword(ulong offset) |
| { |
| return(readl(scsi_mem_addr+offset)); |
| } |
| |
| static int waiting_for_cmd_completed(u8 core_id, |
| int timeout_msec) |
| { |
| int i; |
| u32 status; |
| u32 offset1 = mv_host->mmio; /*main irq cause*/ |
| u32 offset2 = mv_host->mmio + mv_host->core[core_id].mmio_off; |
| u32 sign = (core_id == 0) ? (1<<18) : (1<<19); |
| |
| for (i = 0; (!((status = readl(offset1+0x10200)) & sign)) && (!((status = readl(offset2+0x0150)) & (1<<0))) && (i < timeout_msec); i++) |
| msleep(1); |
| |
| return (i < timeout_msec) ? 0 : -1; |
| } |
| |
| #define MV_MAX_DATA_BYTE_COUNT (128 *1024) |
| |
| static int issue_sata_cmd(struct dev_info *dev, u8 *fis, int fis_len, u8 *buf, |
| int buf_len) |
| { |
| struct mv_cmd_tbl *cmd_tbl = NULL; |
| struct mv_cmd_hdr *cmd_hdr = NULL; |
| u32 status_buf_off; |
| u32 prd_tbl_off; |
| u32 cmd_tbl_off; |
| struct mv_sg *sg = NULL; |
| int i; |
| ulong tmp; |
| struct port_info *dev_port = (struct port_info *)dev->port; |
| struct sas_core *core = (struct sas_core *)dev_port->core; |
| u8 phy_id; |
| struct dlvq_entry *dlvq_entry; |
| struct dlvq_entry *cur_entry = NULL; |
| struct cmplq_entry *cplq_entry; |
| u16 j; |
| u16 slot_nm; |
| u32 err_info0 = 0; |
| u32 err_info1 = 0; |
| |
| |
| cmd_tbl = (struct mv_cmd_tbl *)mv_host->cmd_table; |
| memset((unsigned char *)mv_host->cmd_table, 0, sizeof(struct mv_cmd_tbl)); |
| cmd_hdr = (struct mv_cmd_hdr *)mv_host->cmd_list; |
| memset((unsigned char *)cmd_hdr, 0, sizeof(struct mv_cmd_hdr)); |
| |
| status_buf_off = (unsigned char *)&cmd_tbl->status_buf - (unsigned char *)cmd_tbl; |
| cmd_hdr->sbf_addr = cpu_to_le32(mv_host->cmd_table) + status_buf_off; |
| cmd_hdr->sbf_addr_hi = 0; |
| |
| prd_tbl_off = (unsigned char *)&cmd_tbl->prd_entry - (unsigned char *)cmd_tbl; |
| cmd_hdr->prdt_addr = cpu_to_le32(mv_host->cmd_table) + prd_tbl_off; |
| |
| cmd_hdr->intf_select = 0; |
| cmd_hdr->ssp_retry = 1; |
| |
| memset((unsigned char *)&cmd_tbl->status_buf.err_info, 0, 8); |
| |
| if(dev->dev_type & DEVICE_TYPE_SATA) { |
| cmd_tbl_off = (unsigned char *)&cmd_tbl->stp_cmd_table - (unsigned char *)cmd_tbl; |
| cmd_hdr->tbl_addr = cpu_to_le32(mv_host->cmd_table) + cmd_tbl_off; |
| cmd_hdr->tbl_addr_hi = 0; |
| |
| } else { |
| printf("other device type %x shouldn't go here\n", dev->dev_type); |
| return -1; |
| } |
| |
| if (dev_port->id > mv_host->port_num) { |
| printf("Invaild port number %d\n", dev_port->id); |
| return -1; |
| } |
| |
| cmd_hdr->frm_len = 5; |
| cmd_hdr->atapi = 0; |
| cmd_hdr->reset =0; |
| cmd_hdr->tag = 0; |
| cmd_hdr->fst_dma = 0; |
| |
| if (dev_port->type & PORT_TYPE_PM) |
| cmd_hdr->pm_port = dev->pm_num; |
| else |
| cmd_hdr->pm_port = 0; |
| |
| memcpy((unsigned char *)cmd_tbl->stp_cmd_table.fis, fis, fis_len); |
| |
| cmd_hdr->prd_entry_cnt = 1; |
| if (buf_len) { |
| if (buf_len > MV_MAX_DATA_BYTE_COUNT) { |
| printf("buf len %x is larger then HW size:%x exit!\n", buf_len, MV_MAX_DATA_BYTE_COUNT); |
| return -1; |
| } |
| sg = &cmd_tbl->prd_entry; |
| sg->addr = cpu_to_le32((u32) buf); |
| sg->addr_hi = 0; |
| sg->flags_size = cpu_to_le32(0x3fffff & buf_len); |
| //printf("sg size:%x\n", sg->flags_size); |
| } |
| cmd_hdr->data_len = buf_len; |
| |
| /*assign register set*/ |
| if (dev->register_set != 0xff && core->used_register_set > 64) { |
| printf("no register set, exit!\n"); |
| return -1; |
| } |
| for( i=0; i<64; i++ ) |
| { |
| tmp = scsi_read_dword(core->mmio_off + (i<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1)); |
| if( !(tmp & (1 <<(i%32)) )) |
| { |
| dev->register_set = i; |
| tmp |= (1<<(i%32)); |
| scsi_write_dword(core->mmio_off + (i<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1), tmp); |
| break; |
| } |
| } |
| //printf("use register set:%d\n", dev->register_set); |
| core->used_register_set++; |
| |
| /*deliver queue entry*/ |
| dlvq_entry = (struct dlvq_entry *)mv_host->dlv_q; |
| core->lst_dlvq++; |
| if (core->lst_dlvq >= MAX_DELV_QUEUE_SIZE) |
| core->lst_dlvq = 0; |
| tmp = (u32)core->lst_dlvq; |
| cur_entry = &dlvq_entry[tmp]; |
| memset((unsigned char *)cur_entry, 0, sizeof(struct dlvq_entry)); |
| cur_entry->slot_nm = 0; |
| cur_entry->phy = dev_port->phy_map; |
| cur_entry->cmd = 0x03; |
| cur_entry->mode = 1; |
| cur_entry->prio = 0; |
| cur_entry->sata_reg_set = dev->register_set; |
| scsi_write_dword(core->mmio_off + COMMON_DELV_Q_WR_PTR, tmp); |
| |
| |
| if (waiting_for_cmd_completed(core->id, 10000)) { |
| //tmp = scsi_read_dword(0x10200L); |
| //printf("after clear main cause:%lx\n", tmp); |
| |
| //tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("after clear irq status:%lx\n", tmp); |
| |
| printf("issue_sata_cmd: cmd %x to phy_map:%x timeout exit!\n", fis[2], cur_entry->phy); |
| return -1; |
| } |
| |
| tmp = scsi_read_dword(0x10200L); |
| scsi_write_dword(0x10200L, tmp); |
| tmp = scsi_read_dword(0x10200L); |
| //printf("after clear main cause:%lx\n", tmp); |
| |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, tmp); |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("after clear irq status:%lx\n", tmp); |
| |
| /*clear port irq status*/ |
| phy_id = 0; |
| while (0 == (dev_port->phy_map & (1 <<phy_id))) |
| phy_id++; |
| tmp = scsi_read_dword(core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| scsi_write_dword((core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), tmp); |
| |
| /*if support pm, need enhance*/ |
| |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, INT_CMD_CMPL_MASK); /*clear cmpl int*/ |
| |
| /*handle cmpl queue*/ |
| cplq_entry = (struct cmplq_entry *)mv_host->cmpl_q; |
| j = core->lst_cmplq; |
| core->lst_cmplq = (u16)(*(u32 *)&cplq_entry[0])&0xfff; |
| /*wait cmplq update*/ |
| i = 1000; |
| while((j==core->lst_cmplq) && i) |
| { |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| if(i == 0) |
| { |
| i=300; |
| while((j==core->lst_cmplq) && i) |
| { |
| udelay(10); //sometimes main interrupt is abnormal |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| } |
| if(j==core->lst_cmplq){ |
| printf("complete queue isn't updated exit!\n"); |
| return -1; |
| } |
| |
| if (core->lst_cmplq!= 0xfff) { |
| while (j != core->lst_cmplq) { |
| j++; |
| if (j > MAX_CMPL_QUEUE_SIZE) |
| j = 0; |
| |
| if (cplq_entry[j + 1].attention) //RXQ_ATTN |
| { |
| printf("RXQ_ATTN\n"); |
| continue; |
| } |
| |
| slot_nm = (u16)cplq_entry[j+1].slot_num; |
| if (slot_nm != 0) { |
| printf("wrong slot num:%d, only use 0\n", slot_nm); |
| continue; |
| } |
| /*read error info, to be done*/ |
| err_info0 = cmd_tbl[slot_nm].status_buf.err_info.record1; |
| err_info1 = cmd_tbl[slot_nm].status_buf.err_info.record0; |
| //printf("error info 0x%08x 0x%08x\n", err_info0, err_info1); |
| |
| if (((err_info0 != 0x0L)||(err_info1 != 0x0L)) && (cplq_entry[j + 1].err_rcrd_xfrd)) |
| { |
| printf("has error info, error info 0x%08x 0x%08x, cplq_entry[%x].err_rcrd_xfrd %x\n", err_info0, err_info1, j+1, cplq_entry[j + 1].err_rcrd_xfrd);//cplq_entry %x, ((u32)(cplq_entry[j + 1])), |
| return -1; |
| } |
| else |
| { |
| memset((void *)&err_info0, 0, sizeof(u32)); |
| memset((void *)&err_info1, 0, sizeof(u32)); |
| } |
| /*check sata r-busy, to be done*/ |
| } |
| } |
| |
| /*free register set*/ |
| if (dev->register_set != 0xff && core->used_register_set > 64) { |
| printf("no register set exit!\n"); |
| return -1; |
| } |
| tmp = scsi_read_dword(core->mmio_off + (dev->register_set < 32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1)); |
| tmp &= ~(1 << (dev->register_set%32)); |
| scsi_write_dword(core->mmio_off + (dev->register_set<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1), tmp); |
| |
| tmp = scsi_read_dword(core->mmio_off + (dev->register_set < 32 ? COMMON_SRS_IRQ_STAT0 : COMMON_SRS_IRQ_STAT1)); |
| if(tmp & (1 <<(dev->register_set%32)) ) { |
| printf("register set %d is stopped !\n", dev->register_set); |
| scsi_write_dword(core->mmio_off + (dev->register_set<32 ? COMMON_SRS_IRQ_STAT0 : COMMON_SRS_IRQ_STAT1), tmp); |
| } |
| dev->register_set = 0xff; |
| core->used_register_set--; |
| |
| if ((err_info0 != 0) || (err_info1 != 0)) { |
| printf("has error info"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static char *ata_id_strcpy(u16 *target, u16 *src, int len) |
| { |
| int i; |
| for (i = 0; i < len / 2; i++) |
| target[i] = swab16(src[i]); |
| return (char *)target; |
| } |
| |
| |
| static void dump_ataid(u8 dev_id, u8 force) |
| { |
| hd_driveid_t *ata_id = ataid[dev_id]; |
| if (force) { |
| int i; |
| printf("dev:%d size of identify data:\n", dev_id); |
| for (i=0; i<256; i++) { |
| printf("word %d~%d: %4x %4x %4x %4x %4x %4x %4x %4x\n", i, i+7, ((u16 *)ata_id)[i], ((u16 *)ata_id)[i+1], ((u16 *)ata_id)[i+2], ((u16 *)ata_id)[i+3], ((u16 *)ata_id)[i+4], ((u16 *)ata_id)[i+5], ((u16 *)ata_id)[i+6], ((u16 *)ata_id)[i+7]); |
| i += 7; |
| } |
| } else { |
| debug("(49)ataid->capability = 0x%x\n", ata_id->capability); |
| debug("(53)ataid->field_valid =0x%x\n", ata_id->field_valid); |
| debug("(63)ataid->dma_mword = 0x%x\n", ata_id->dma_mword); |
| debug("(64)ataid->eide_pio_modes = 0x%x\n", ata_id->eide_pio_modes); |
| debug("(75)ataid->queue_depth = 0x%x\n", ata_id->queue_depth); |
| debug("(80)ataid->major_rev_num = 0x%x\n", ata_id->major_rev_num); |
| debug("(81)ataid->minor_rev_num = 0x%x\n", ata_id->minor_rev_num); |
| debug("(82)ataid->command_set_1 = 0x%x\n", ata_id->command_set_1); |
| debug("(83)ataid->command_set_2 = 0x%x\n", ata_id->command_set_2); |
| debug("(84)ataid->cfsse = 0x%x\n", ata_id->cfsse); |
| debug("(85)ataid->cfs_enable_1 = 0x%x\n", ata_id->cfs_enable_1); |
| debug("(86)ataid->cfs_enable_2 = 0x%x\n", ata_id->cfs_enable_2); |
| debug("(87)ataid->csf_default = 0x%x\n", ata_id->csf_default); |
| debug("(88)ataid->dma_ultra = 0x%x\n", ata_id->dma_ultra); |
| } |
| } |
| |
| /*need consider to share code with issue_sata_cmd()*/ |
| static int issue_sata_cmd_nodata(struct dev_info *dev, u8 *fis, int fis_len, u8 is_reset) |
| { |
| struct mv_cmd_tbl *cmd_tbl = NULL; |
| struct mv_cmd_hdr *cmd_hdr = NULL; |
| u32 status_buf_off; |
| u32 prd_tbl_off; |
| u32 cmd_tbl_off; |
| int i; |
| u32 tmp; |
| struct port_info *dev_port = (struct port_info *)dev->port; |
| struct sas_core *core = (struct sas_core *)dev_port->core; |
| u8 phy_id; |
| struct dlvq_entry *dlvq_entry; |
| struct dlvq_entry *cur_entry = NULL; |
| struct cmplq_entry *cplq_entry; |
| u16 j; |
| u16 slot_nm; |
| u32 err_info0 = 0; |
| u32 err_info1 = 0; |
| |
| cmd_tbl = (struct mv_cmd_tbl *)mv_host->cmd_table; |
| memset((unsigned char *)mv_host->cmd_table, 0, sizeof(struct mv_cmd_tbl)); |
| cmd_hdr = (struct mv_cmd_hdr *)mv_host->cmd_list; |
| memset((unsigned char *)cmd_hdr, 0, sizeof(struct mv_cmd_hdr)); |
| |
| status_buf_off = (unsigned char *)&cmd_tbl->status_buf - (unsigned char *)cmd_tbl; |
| cmd_hdr->sbf_addr = cpu_to_le32(mv_host->cmd_table) + status_buf_off; |
| cmd_hdr->sbf_addr_hi = 0; |
| |
| prd_tbl_off = (unsigned char *)&cmd_tbl->prd_entry - (unsigned char *)cmd_tbl; |
| cmd_hdr->prdt_addr = cpu_to_le32(mv_host->cmd_table) + prd_tbl_off; |
| |
| cmd_hdr->intf_select = 0; |
| cmd_hdr->ssp_retry = 1; |
| |
| memset((unsigned char *)&cmd_tbl->status_buf.err_info, 0, 8); |
| |
| cmd_tbl_off = (unsigned char *)&cmd_tbl->stp_cmd_table - (unsigned char *)cmd_tbl; |
| cmd_hdr->tbl_addr = cpu_to_le32(mv_host->cmd_table) + cmd_tbl_off; |
| cmd_hdr->tbl_addr_hi = 0; |
| |
| if (dev_port->id > mv_host->port_num) { |
| printf("Invaild port number %d\n", dev_port->id); |
| return -1; |
| } |
| |
| cmd_hdr->frm_len = 5; |
| cmd_hdr->atapi = 0; |
| cmd_hdr->reset = is_reset; |
| cmd_hdr->tag = 0; |
| cmd_hdr->fst_dma = 0; |
| cmd_hdr->pm_port = fis[1]; |
| |
| memcpy((unsigned char *)cmd_tbl->stp_cmd_table.fis, fis, fis_len); |
| |
| cmd_hdr->prd_entry_cnt = 1; |
| memset(&cmd_tbl->prd_entry, 0, sizeof(struct mv_sg)); |
| cmd_hdr->data_len = 0; |
| |
| /*assign register set*/ |
| if (dev->register_set != 0xff && core->used_register_set > 64) { |
| printf("no register set exit!\n"); |
| return -1; |
| } |
| for( i=0; i<64; i++ ) |
| { |
| tmp = scsi_read_dword(core->mmio_off + (i<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1)); |
| if( !(tmp & (1 <<(i%32)) )) |
| { |
| dev->register_set = i; |
| tmp |= (1<<(i%32)); |
| scsi_write_dword(core->mmio_off + (i<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1), tmp); |
| break; |
| } |
| } |
| core->used_register_set++; |
| |
| /*deliver queue entry*/ |
| dlvq_entry = (struct dlvq_entry *)mv_host->dlv_q; |
| core->lst_dlvq++; |
| if (core->lst_dlvq >= MAX_DELV_QUEUE_SIZE) |
| core->lst_dlvq = 0; |
| tmp = (u32)core->lst_dlvq; |
| cur_entry = &dlvq_entry[tmp]; |
| memset((unsigned char *)cur_entry, 0, sizeof(struct dlvq_entry)); |
| cur_entry->slot_nm = 0; |
| cur_entry->phy = dev_port->phy_map; |
| cur_entry->cmd = 0x03; /*CMD_STP*/ |
| cur_entry->mode = 1; |
| cur_entry->prio = 0; |
| cur_entry->sata_reg_set = dev->register_set; |
| scsi_write_dword(core->mmio_off + COMMON_DELV_Q_WR_PTR, tmp); |
| |
| if (waiting_for_cmd_completed(core->id, 10000)) { |
| //tmp = scsi_read_dword(0x10200L); |
| //printf("errrafter clear main cause:%lx\n", tmp); |
| |
| //tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("errrafter clear irq status:%lx\n", tmp); |
| |
| printf("issue_sata_cmd_nodata: cmd %x to phy_map:%x timeout exit!\n", fis[2], cur_entry->phy); |
| return -1; |
| } |
| |
| if (is_reset) |
| msleep(10); |
| |
| tmp = scsi_read_dword(0x10200L); |
| //printf("before clear main cause:%lx\n", tmp); |
| scsi_write_dword(0x10200L, tmp); |
| tmp = scsi_read_dword(0x10200L); |
| //printf("after clear main cause:%lx\n", tmp); |
| |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("before clear irq status:%lx\n", tmp); |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, tmp); |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("after clear irq status:%lx\n", tmp); |
| |
| /*clear port irq status*/ |
| phy_id = 0; |
| while (0 == (dev_port->phy_map & (1 <<phy_id))) |
| phy_id++; |
| tmp = scsi_read_dword(core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| //printf("before clear port irq status: %lx\n", tmp); |
| scsi_write_dword((core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), tmp); |
| tmp = scsi_read_dword(core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| //printf("After clear port irq status: %lx\n", tmp); |
| |
| /*if support pm, need enhance*/ |
| |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, INT_CMD_CMPL_MASK); /*clear cmpl int*/ |
| |
| /*handle cmpl queue*/ |
| cplq_entry = (struct cmplq_entry *)mv_host->cmpl_q; |
| j = core->lst_cmplq; |
| core->lst_cmplq = (u16)(*(u32 *)&cplq_entry[0])&0xfff; |
| //printf("j= %x, core->lst_cmplq = %x \n", j, core->lst_cmplq ); |
| /*wait cmplq update*/ |
| i = 3000; |
| while((j==core->lst_cmplq) && i) |
| { |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| if(i == 0) |
| { |
| i=300; |
| while((j==core->lst_cmplq) && i) |
| { |
| msleep(10); //sometimes main interrupt is abnormal |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| } |
| if(j==core->lst_cmplq){ |
| printf("complete queue isn't update exit!\n"); |
| return -1; |
| } |
| else{ |
| tmp = scsi_read_dword(core->mmio_off + 0x10200L); |
| if (0 != tmp) |
| printf("main irq == %x \n", tmp); |
| } |
| |
| if (core->lst_cmplq!= 0xfff) { |
| while (j != core->lst_cmplq) { |
| j++; |
| |
| if (j >= MAX_CMPL_QUEUE_SIZE) |
| j = 0; |
| |
| if (cplq_entry[j + 1].attention) //RXQ_ATTN |
| { |
| printf("RXQ_ATTN\n"); |
| continue; |
| } |
| |
| slot_nm = (u16)cplq_entry[j+1].slot_num; |
| |
| |
| if (slot_nm != 0) { |
| printf("wrong slot num:%d, only use 0\n", slot_nm); |
| continue; |
| } |
| /*read error info, to be done*/ |
| err_info0 = cmd_tbl[slot_nm].status_buf.err_info.record1; |
| err_info1 = cmd_tbl[slot_nm].status_buf.err_info.record0; |
| // printf("error info 0x%08x 0x%08x\n", err_info0, err_info1); |
| |
| if (((err_info0 != 0x0L)||(err_info1 != 0x0L)) && (cplq_entry[j + 1].err_rcrd_xfrd)) |
| { |
| printf("has error info, error info 0x%08x 0x%08x, cplq_entry[%x].err_rcrd_xfrd %x\n", err_info0, err_info1, j+1, cplq_entry[j + 1].err_rcrd_xfrd); |
| return -1; |
| } |
| else |
| { |
| memset((void *)&err_info0, 0, sizeof(u32)); |
| memset((void *)&err_info1, 0, sizeof(u32)); |
| } |
| |
| /*check sata r-busy, to be done*/ |
| } |
| } |
| |
| /*free register set*/ |
| if (dev->register_set != 0xff && core->used_register_set > 64) { |
| printf("no register set exit!\n"); |
| return -1; |
| } |
| tmp = scsi_read_dword(core->mmio_off + (dev->register_set < 32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1)); |
| tmp &= ~(1 << (dev->register_set%32)); |
| scsi_write_dword(core->mmio_off + (dev->register_set<32 ? COMMON_SATA_REG_SET0 : COMMON_SATA_REG_SET1), tmp); |
| |
| tmp = scsi_read_dword(core->mmio_off + (dev->register_set < 32 ? COMMON_SRS_IRQ_STAT0 : COMMON_SRS_IRQ_STAT1)); |
| if(tmp & (1 <<(dev->register_set%32)) ) { |
| printf("register set %d is stopped !\n", dev->register_set); |
| scsi_write_dword(core->mmio_off + (dev->register_set<32 ? COMMON_SRS_IRQ_STAT0 : COMMON_SRS_IRQ_STAT1), tmp); |
| } |
| dev->register_set = 0xff; |
| core->used_register_set--; |
| |
| if ((err_info0 != 0) || (err_info1 != 0)) { |
| printf("has error info\n"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * soft-reset. |
| */ |
| static int ata_issue_softreset(struct dev_info *dev, u8 srst, u8 port_reset) |
| { |
| u8 fis[20]; |
| |
| memset(fis, 0, 20); |
| /* Construct the FIS */ |
| fis[0] = 0x27; /* Host to device FIS. */ |
| fis[1] = port_reset ? 0x0f : dev->pm_num; /* Command FIS. */ |
| fis[2] = 0; /* Command byte. */ |
| |
| fis[15] = srst ? (1<<2) : 0; /*Control */ |
| |
| if (issue_sata_cmd_nodata(dev, (u8 *) &fis, 20, 1)) { |
| debug("softreset failure.\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * SCSI INQUIRY command operation. |
| */ |
| static int ata_scsiop_inquiry(ccb *pccb) |
| { |
| struct dev_info *dev = NULL; |
| struct port_info *port = NULL; |
| u8 hdr[] = { |
| 0, |
| 0, |
| 0x5, /* claim SPC-3 version compatibility */ |
| 2, |
| 95 - 4, |
| }; |
| u8 fis[20]; |
| u8 *tmpid; |
| //u8 port; |
| |
| /* Clean ccb data buffer */ |
| memset(pccb->pdata, 0, pccb->datalen); |
| |
| memcpy(pccb->pdata, hdr, sizeof(hdr)); |
| |
| if (pccb->datalen <= 35) |
| return 0; |
| |
| memset(fis, 0, 20); |
| /* Construct the FIS */ |
| fis[0] = 0x27; /* Host to device FIS. */ |
| fis[1] = 1 << 7; /* Command FIS. */ |
| fis[2] = ATA_CMD_IDENT; /* Command byte. */ |
| fis[3] = 1 << 0; |
| fis[7] = 0x40; /*device */ |
| |
| /* Read id from sata */ |
| dev = &mv_host->devices[pccb->target]; |
| port = dev->port; |
| |
| if (port->type & PORT_TYPE_PM) |
| fis[1] |= dev->pm_num; |
| |
| if (!(tmpid = malloc(sizeof(hd_driveid_t)))) |
| return -ENOMEM; |
| |
| memset(tmpid, 0, sizeof(hd_driveid_t)); |
| |
| if (issue_sata_cmd(dev, (u8 *) &fis, 20, |
| tmpid, sizeof(hd_driveid_t))) { |
| debug("scsi_mv94xx: SCSI inquiry command failure.\n"); |
| return -EIO; |
| } |
| |
| if (ataid[dev->id]) { |
| free(ataid[dev->id]); |
| ataid[dev->id] = NULL; |
| } |
| ataid[dev->id] = (hd_driveid_t *) tmpid; |
| |
| memcpy(&pccb->pdata[8], "ATA ", 8); |
| ata_id_strcpy((u16 *) &pccb->pdata[16], (u16 *)ataid[dev->id]->model, 16); |
| ata_id_strcpy((u16 *) &pccb->pdata[32], (u16 *)ataid[dev->id]->fw_rev, 4); |
| |
| dump_ataid(dev->id, 0); |
| return 0; |
| } |
| |
| |
| /* |
| * SCSI READ10 command operation. |
| */ |
| static int ata_scsiop_read10(ccb * pccb) |
| { |
| struct dev_info *dev = NULL; |
| struct port_info *port = NULL; |
| u32 len = 0; |
| u8 fis[20]; |
| u8 lba48 = 0; |
| |
| if (ataid[pccb->target]->command_set_2 & 0x0400) { |
| /*48bit lba*/ |
| lba48 = 1; |
| } |
| |
| len = (((u32) pccb->cmd[7]) << 8) | ((u32) pccb->cmd[8]); |
| |
| /* For 10-byte and 16-byte SCSI R/W commands, transfer |
| * length 0 means transfer 0 block of data. |
| * However, for ATA R/W commands, sector count 0 means |
| * 256 or 65536 sectors, not 0 sectors as in SCSI. |
| * |
| * WARNING: one or two older ATA drives treat 0 as 0... |
| */ |
| if (!len) |
| return 0; |
| memset(fis, 0, 20); |
| |
| /* Construct the FIS */ |
| fis[0] = 0x27; /* Host to device FIS. */ |
| fis[1] = 1 << 7; /* Command FIS. */ |
| if (lba48) |
| fis[2] = 0x25; /* Command byte. */ |
| else |
| fis[2] = 0xC8; /* Command byte. */ |
| fis[3] = 1<<0; |
| |
| /* LBA address, only support LBA28 in this driver */ |
| fis[4] = pccb->cmd[5]; |
| fis[5] = pccb->cmd[4]; |
| fis[6] = pccb->cmd[3]; |
| |
| if(lba48) { |
| fis[7] = 0x40; |
| fis[8] = pccb->cmd[2]; |
| |
| /* Sector Count */ |
| fis[12] = pccb->cmd[8]; |
| fis[13] = pccb->cmd[7]; |
| } |
| else { |
| fis[7] = (pccb->cmd[2] & 0x0f) | 0x40; //0xe0; |
| /* Sector Count */ |
| fis[12] = pccb->cmd[8]; |
| fis[13] = 0; // pccb->cmd[7]; |
| } |
| |
| /* Read */ |
| dev = &mv_host->devices[pccb->target]; |
| port = dev->port; |
| if (port->type & PORT_TYPE_PM) |
| fis[1] |= dev->pm_num; |
| |
| if (issue_sata_cmd(dev, (u8 *) &fis, 20, |
| pccb->pdata, pccb->datalen)) { |
| debug("mv94xx: SCSI READ10 command failure.\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * SCSI READ CAPACITY10 command operation. |
| */ |
| static int ata_scsiop_read_capacity10(ccb *pccb) |
| { |
| //ulong cap; |
| u32 cap_low, cap_high=0x0; |
| u32 *tmp32 = (u32 *)pccb->pdata; |
| u32 *tmp_cap = NULL; |
| |
| if (!ataid[pccb->target]) { |
| printf("scsi_mv94xx: SCSI READ CAPACITY10 command failure. " |
| "\tNo ATA info!\n" |
| "\tPlease run SCSI commmand INQUIRY firstly!\n"); |
| return -EPERM; |
| } |
| |
| cap_low = le32_to_cpu((u32)ataid[pccb->target]->lba_capacity); |
| cap_high = 0; |
| |
| #ifdef CONFIG_LBA48 |
| if (ataid[pccb->target]->command_set_2 & 0x0400) { /* LBA 48 support */ |
| tmp_cap = (u32 *)&ataid[pccb->target]->lba48_capacity[0]; |
| cap_low = le32_to_cpu(*tmp_cap); |
| tmp_cap = (u32 *)&ataid[pccb->target]->lba48_capacity[2]; |
| cap_high = le32_to_cpu(*tmp_cap); |
| } |
| //printf("ata_scsiop_read_capacity10:lba:%lx\n", cap); |
| #endif /* CONFIG_LBA48 */ |
| |
| |
| if (pccb->datalen >= 8) { |
| if (cap_high != 0) |
| tmp32[0] = cpu_to_be32(0xffffffff); |
| else |
| tmp32[0] = cpu_to_be32(cap_low); |
| tmp32[1] = cpu_to_be32(512); /*Sector Size default 512byte*/ |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * SCSI TEST UNIT READY command operation. |
| */ |
| static int ata_scsiop_test_unit_ready(ccb *pccb) |
| { |
| return (ataid[pccb->target]) ? 0 : -EPERM; |
| } |
| |
| static u8 ssp_normalize_sense(u8* sense_buffer, u32 sb_len, struct sas_sense_hdr* sshdr) |
| { |
| if (!sense_buffer || !sb_len) |
| return 0; |
| |
| memset(sshdr, 0, sizeof(*sshdr)); |
| sshdr->response_code = (sense_buffer[0] & 0x7f); |
| |
| if ((sshdr->response_code & 0x70)!= 0x70) |
| return 0; |
| |
| if (sshdr->response_code >= 0x72) { |
| if (sb_len > 1) |
| sshdr->sense_key = (sense_buffer[1] & 0xf); |
| if (sb_len > 2) |
| sshdr->asc = sense_buffer[2]; |
| if (sb_len > 3) |
| sshdr->ascq = sense_buffer[3]; |
| } else { |
| if (sb_len > 2) |
| sshdr->sense_key = (sense_buffer[2] & 0xf); |
| if (sb_len > 7) { |
| sb_len = (sb_len < (u32)(sense_buffer[7] + 8)) ? |
| sb_len : (u32)(sense_buffer[7] + 8); |
| if (sb_len > 12) |
| sshdr->asc = sense_buffer[12]; |
| if (sb_len > 13) |
| sshdr->ascq = sense_buffer[13]; |
| } |
| } |
| return 1; |
| } |
| |
| /*need consider to share complete queue entry check with issue_sata_cmd()*/ |
| static int issue_sas_cmd(struct dev_info *dev, u8 *cdb, u8 *buf, |
| int buf_len) |
| { |
| struct mv_cmd_tbl *cmd_tbl = NULL; |
| struct mv_cmd_hdr *cmd_hdr = NULL; |
| u32 status_buf_off; |
| u32 prd_tbl_off; |
| u32 tmp_tbl_off; |
| struct mv_sg *sg = NULL; |
| int i; |
| ulong tmp; |
| struct port_info *dev_port = (struct port_info *)dev->port; |
| struct sas_core *core = (struct sas_core *)dev_port->core; |
| u8 phy_id; |
| struct dlvq_entry *dlvq_entry; |
| struct dlvq_entry *cur_entry = NULL; |
| struct cmplq_entry *cplq_entry; |
| u16 j; |
| u16 slot_nm; |
| u16 *tmp16 = NULL; |
| u32 err_info0 = 0; |
| u32 err_info1 = 0; |
| u32 sense_len; |
| struct sas_sense_hdr sshdr; |
| |
| cmd_tbl = (struct mv_cmd_tbl *)mv_host->cmd_table; |
| memset((unsigned char *)mv_host->cmd_table, 0, sizeof(struct mv_cmd_tbl)); |
| cmd_hdr = (struct mv_cmd_hdr *)mv_host->cmd_list; |
| memset((unsigned char *)cmd_hdr, 0, sizeof(struct mv_cmd_hdr)); |
| |
| status_buf_off = (unsigned char *)&cmd_tbl->status_buf - (unsigned char *)cmd_tbl; |
| cmd_hdr->sbf_addr = cpu_to_le32(mv_host->cmd_table) + status_buf_off; |
| cmd_hdr->sbf_addr_hi = 0; |
| |
| prd_tbl_off = (unsigned char *)&cmd_tbl->prd_entry - (unsigned char *)cmd_tbl; |
| cmd_hdr->prdt_addr = cpu_to_le32(mv_host->cmd_table) + prd_tbl_off; |
| |
| cmd_hdr->intf_select = 0; |
| cmd_hdr->ssp_retry = 1; |
| |
| memset((unsigned char *)&cmd_tbl->status_buf.err_info, 0, 8); |
| |
| if(dev->dev_type & DEVICE_TYPE_SATA) { |
| tmp_tbl_off = (unsigned char *)&cmd_tbl->stp_cmd_table - (unsigned char *)cmd_tbl; |
| cmd_hdr->tbl_addr = cpu_to_le32(mv_host->cmd_table) + tmp_tbl_off; |
| cmd_hdr->tbl_addr_hi = 0; |
| |
| } else if(dev->dev_type & DEVICE_TYPE_SSP) { |
| tmp_tbl_off = (unsigned char *)&cmd_tbl->open_addr_frame - (unsigned char *)cmd_tbl; |
| cmd_hdr->opf_addr = cpu_to_le32(mv_host->cmd_table) + tmp_tbl_off; |
| cmd_hdr->opf_addr_hi = 0; |
| |
| tmp_tbl_off = (unsigned char *)&cmd_tbl->ssp_cmd_table - (unsigned char *)cmd_tbl; |
| cmd_hdr->tbl_addr = cpu_to_le32(mv_host->cmd_table) + tmp_tbl_off; |
| cmd_hdr->tbl_addr_hi = 0; |
| |
| } else { |
| printf("other device type %x shouldn't go here\n", dev->dev_type); |
| return -1; |
| } |
| |
| if (dev_port->id > mv_host->port_num) { |
| printf("Invaild port number %d\n", dev_port->id); |
| return -1; |
| } |
| |
| cmd_hdr->max_rsp_len = (sizeof(struct ssp_rsp_iu) > 0x200 ? sizeof(struct ssp_rsp_iu) : 0x200)/4; |
| cmd_hdr->frm_len = (sizeof(struct ssp_cmd_iu) + sizeof(struct ssp_frame_hdr))/4; |
| cmd_hdr->ssp_ft = 0x00; //frame_type_cmd |
| |
| cmd_hdr->tag |= 0xab << 9; //internal used tag |
| |
| memcpy(&cmd_tbl->ssp_cmd_table.cmd_iu.cdb[0], cdb, 16); |
| |
| /*assert only support single lun*/ |
| cmd_tbl->open_addr_frame.protocol = 0x1; //protocol SSP |
| cmd_tbl->open_addr_frame.frame_type = 0x1; //open frame |
| cmd_tbl->open_addr_frame.initiator = 1; |
| tmp16 = (u16 *)&(cmd_tbl->open_addr_frame.connect_tag[0]); |
| *tmp16 = cpu_to_be16(dev->id + 1); |
| cmd_tbl->open_addr_frame.connect_rate = dev->link_rate; |
| cmd_tbl->open_addr_frame.dst_sas_addr_low = be32_to_cpu(dev->sas_addr_low); |
| cmd_tbl->open_addr_frame.dst_sas_addr_high = be32_to_cpu(dev->sas_addr_high); |
| |
| cmd_hdr->prd_entry_cnt = 1; |
| if (buf_len) { |
| if (buf_len > MV_MAX_DATA_BYTE_COUNT) { |
| printf("buf len %x is larger then HW size:%x exit!\n", buf_len, MV_MAX_DATA_BYTE_COUNT); |
| return -1; |
| } |
| sg = &cmd_tbl->prd_entry; |
| sg->addr = cpu_to_le32((u32) buf); |
| sg->addr_hi = 0; |
| sg->flags_size = cpu_to_le32(0x3fffff & buf_len); |
| //printf("sg size:%x\n", sg->flags_size); |
| } |
| cmd_hdr->data_len = cpu_to_le32(buf_len); |
| |
| /*deliver queue entry*/ |
| dlvq_entry = (struct dlvq_entry *)mv_host->dlv_q; |
| core->lst_dlvq++; |
| if (core->lst_dlvq >= MAX_DELV_QUEUE_SIZE) |
| core->lst_dlvq = 0; |
| tmp = (u32)core->lst_dlvq; |
| cur_entry = &dlvq_entry[tmp]; |
| memset((unsigned char *)cur_entry, 0, sizeof(struct dlvq_entry)); |
| cur_entry->slot_nm = 0; |
| cur_entry->phy = dev_port->phy_map; |
| cur_entry->cmd = 0x01; //cmd_ssp |
| cur_entry->mode = 1; |
| cur_entry->prio = 0; |
| scsi_write_dword(core->mmio_off + COMMON_DELV_Q_WR_PTR, tmp); |
| |
| |
| if (waiting_for_cmd_completed(core->id, 10000)) { |
| //tmp = scsi_read_dword(0x10200L); |
| //printf("errrafter clear main cause:%lx\n", tmp); |
| |
| //tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("errrafter clear irq status:%lx\n", tmp); |
| |
| return -1; |
| } |
| |
| tmp = scsi_read_dword(0x10200L); |
| scsi_write_dword(0x10200L, tmp); |
| tmp = scsi_read_dword(0x10200L); |
| //printf("after clear main cause:%lx\n", tmp); |
| |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, tmp); |
| tmp = scsi_read_dword(core->mmio_off + COMMON_IRQ_STAT); |
| //printf("after clear irq status:%lx\n", tmp); |
| |
| /*clear port irq status*/ |
| phy_id = 0; |
| while (0 == (dev_port->phy_map & (1 <<phy_id))) |
| phy_id++; |
| tmp = scsi_read_dword(core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| scsi_write_dword((core->mmio_off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), tmp); |
| |
| /*if support pm, need enhance*/ |
| |
| scsi_write_dword(core->mmio_off + COMMON_IRQ_STAT, INT_CMD_CMPL_MASK); /*clear cmpl int*/ |
| |
| /*handle cmpl queue*/ |
| cplq_entry = (struct cmplq_entry *)mv_host->cmpl_q; |
| j = core->lst_cmplq; |
| core->lst_cmplq = (u16)(*(u32 *)&cplq_entry[0])&0xfff; |
| /*wait cmplq update*/ |
| i = 1000; |
| while((j==core->lst_cmplq) && i) |
| { |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| if(i == 0) |
| { |
| i=300; |
| while((j==core->lst_cmplq) && i) |
| { |
| udelay(10); //sometimes main interrupt is abnormal |
| core->lst_cmplq =(u16) le32_to_cpu(scsi_read_dword(core->mmio_off + COMMON_CMPL_Q_WR_PTR))&0xfff; |
| i--; |
| } |
| } |
| if(j==core->lst_cmplq){ |
| printf("complete queue isn't update exit!\n"); |
| return -1; |
| } |
| |
| if (core->lst_cmplq!= 0xfff) { |
| while (j != core->lst_cmplq) { |
| j++; |
| if (j > MAX_CMPL_QUEUE_SIZE) |
| j = 0; |
| slot_nm = (u16)cplq_entry[j+1].slot_num; |
| if (slot_nm != 0) { |
| printf("wrong slot num:%d, only use 0\n", slot_nm); |
| continue; |
| } |
| /*read error info, to be done*/ |
| err_info0 = cmd_tbl[slot_nm].status_buf.err_info.record1; |
| err_info1 = cmd_tbl[slot_nm].status_buf.err_info.record0; |
| //printf("error info 0x%08x 0x%08x\n", err_info0, err_info1); |
| if (cmd_tbl[slot_nm].status_buf.rsp_iu.status != 0x00) /*SCSI_STATUS_GOOD*/ { |
| printf("ssp response failed status :%x\n", cmd_tbl[slot_nm].status_buf.rsp_iu.status); |
| |
| sense_len = be32_to_cpu(cmd_tbl[slot_nm].status_buf.rsp_iu.sd_len); |
| |
| if (sense_len && (cmd_tbl[slot_nm].status_buf.rsp_iu.data_pres & 0x2) && |
| ssp_normalize_sense(cmd_tbl[slot_nm].status_buf.rsp_iu.sense_data, sense_len, &sshdr)) |
| { |
| printf("dev %d req 0x%x status 0x%x sense length %d sense(key 0x%x, asc 0x%x ascq 0x%x).\n", \ |
| dev->id, cdb[0], cmd_tbl[slot_nm].status_buf.rsp_iu.status, sense_len, \ |
| sshdr.sense_key, sshdr.asc, sshdr.ascq); |
| } |
| |
| return -1; |
| } |
| /*check sata r-busy, to be done*/ |
| } |
| } |
| |
| if ((err_info0 != 0) || (err_info1 != 0)) { |
| printf("has error info"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| static int sas_scsiop_inquiry(ccb *pccb) |
| { |
| struct dev_info *dev = NULL; |
| u8 cdb[16]; |
| u8 hdr[] = { |
| 0, |
| 0, |
| 0x5, /* claim SPC-3 version compatibility */ |
| 2, |
| 95 - 4, |
| }; |
| u8 *tmpid; |
| |
| /* Clean ccb data buffer */ |
| memset(pccb->pdata, 0, pccb->datalen); |
| memcpy(pccb->pdata, hdr, sizeof(hdr)); |
| |
| if (pccb->datalen <= 35) |
| return 0; |
| |
| /* Read id from sata */ |
| dev = &mv_host->devices[pccb->target]; |
| |
| if (!(tmpid = malloc(96))) |
| return -ENOMEM; |
| memset(tmpid, 0, 0x60); |
| |
| memset(cdb, 0, 16); |
| cdb[0] = pccb->cmd[0]; |
| cdb[4] = 0x60; //standard inquiry |
| //printf("receive SAS inquiry cmd!!\n"); |
| if (issue_sas_cmd(dev, (u8 *) &cdb, tmpid, 0x60)) { |
| debug("scsi_mv94xx: SCSI inquiry command failure.\n"); |
| return -EIO; |
| } |
| memcpy(&pccb->pdata[8], "SAS ", 8); |
| memcpy((u8 *) &pccb->pdata[16], (u8 *)&tmpid[16], 16); |
| memcpy((u8 *) &pccb->pdata[32], (u8 *)&tmpid[32], 4); |
| return 0; |
| } |
| |
| static int sas_scsiop_read10(ccb *pccb) |
| { |
| struct dev_info *dev = NULL; |
| u8 cdb[16]; |
| u32 len=0; |
| |
| len = (((u32) pccb->cmd[7]) << 8) | ((u32) pccb->cmd[8]); |
| if (!len) |
| return 0; |
| |
| memset(cdb, 0, 16); |
| cdb[0] = pccb->cmd[0]; |
| cdb[2] = pccb->cmd[2]; |
| cdb[3] = pccb->cmd[3]; |
| cdb[4] = pccb->cmd[4]; |
| cdb[5] = pccb->cmd[5]; |
| cdb[7] = pccb->cmd[7]; |
| cdb[8] = pccb->cmd[8]; |
| dev = &mv_host->devices[pccb->target]; |
| |
| if (issue_sas_cmd(dev, (u8 *) &cdb, pccb->pdata, pccb->datalen)) { |
| debug("scsi_mv94xx: SCSI read10 command failure.\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int sas_scsiop_common_cmds(ccb *pccb) |
| { |
| struct dev_info *dev = NULL; |
| u8 cdb[16]; |
| struct mv_cmd_tbl *cmd_tbl = NULL; |
| u8 sense_key; |
| u8 retry_cnt = 0; |
| |
| /* Read id from sata */ |
| dev = &mv_host->devices[pccb->target]; |
| |
| memset(cdb, 0, 16); |
| cdb[0] = pccb->cmd[0]; |
| |
| retry: |
| if (issue_sas_cmd(dev, (u8 *) &cdb, pccb->pdata, pccb->datalen)) { |
| cmd_tbl = (struct mv_cmd_tbl *)mv_host->cmd_table; |
| if (cmd_tbl[0].status_buf.rsp_iu.status == 0x02) /*Check condition*/ { |
| sense_key = cmd_tbl[0].status_buf.rsp_iu.sense_data[2] & 0x0f; |
| /*SCSI_SK_UNIT_ATTENTION & SCSI_SK_NOT_READY */ |
| if ((cdb[0] == 0x0) && ((sense_key == 0x06)|| (sense_key == 0x02))) { |
| msleep(100); |
| retry_cnt++; |
| |
| if (retry_cnt <= 10) |
| { |
| //printf("retry cmd %x , retry count is %x\n", cdb[0], retry_cnt); |
| goto retry; |
| } |
| else |
| return -EIO; |
| } |
| } |
| debug("scsi_mv94xx: SCSI command %x failure.\n", pccb->cmd[0]); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| int scsi_exec(ccb *pccb) |
| { |
| int ret; |
| |
| if (mv_host->devices[pccb->target].dev_type & DEVICE_TYPE_SATA) { |
| switch (pccb->cmd[0]) { |
| case SCSI_READ10: |
| ret = ata_scsiop_read10(pccb); |
| break; |
| case SCSI_RD_CAPAC: |
| ret = ata_scsiop_read_capacity10(pccb); |
| break; |
| case SCSI_TST_U_RDY: |
| ret = ata_scsiop_test_unit_ready(pccb); |
| break; |
| case SCSI_INQUIRY: |
| ret = ata_scsiop_inquiry(pccb); |
| break; |
| default: |
| printf("Unsupport SCSI command 0x%02x\n", pccb->cmd[0]); |
| return FALSE; |
| } |
| } else if (mv_host->devices[pccb->target].dev_type & DEVICE_TYPE_SSP) { |
| switch (pccb->cmd[0]) { |
| case SCSI_READ10: |
| ret = sas_scsiop_read10(pccb); |
| break; |
| case SCSI_RD_CAPAC: |
| case SCSI_TST_U_RDY: |
| ret = sas_scsiop_common_cmds(pccb); |
| break; |
| case SCSI_INQUIRY: |
| ret = sas_scsiop_inquiry(pccb); |
| break; |
| default: |
| //printf("temp not support SAS device command 0x%02x\n", pccb->cmd[0]); |
| return FALSE; |
| } |
| } else { |
| /*other type device*/ |
| return FALSE; |
| } |
| |
| if (ret) { |
| debug("SCSI command 0x%02x ret errno %d\n", pccb->cmd[0], ret); |
| return FALSE; |
| } |
| |
| return TRUE; |
| |
| } |
| |
| void scsi_write_phy_vsr_addr(ulong offset, u8 phy_id, ulong val) |
| { |
| writel(val, scsi_mem_addr+offset+0x0250+(phy_id*8)); |
| } |
| |
| void scsi_write_phy_vsr_data(ulong offset, u8 phy_id, ulong val) |
| { |
| writel(val, scsi_mem_addr+offset+0x0254+(phy_id*8)); |
| } |
| |
| ulong scsi_read_vsr_data(ulong offset, u8 phy_id) |
| { |
| return(readl(scsi_mem_addr+offset+0x0254+(phy_id*8))); |
| } |
| |
| static void mv_phy_config(struct sas_core *core, u8 phy_id) { |
| ulong tmp; |
| ulong off = core->mmio_off; |
| int loop=0; |
| |
| //printf("spin up phy:%d of core %d %p off:%lx\n", phy_id, core->id, core, off); |
| |
| /*set dev info*/ |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ID_FRAME5); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)); |
| tmp &= 0xffffff00; |
| tmp |= phy_id; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)), tmp); |
| |
| tmp = (1<<4) + (((1<<9)|(1<<10)|(1<<11))<<8); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ID_FRAME0); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)), tmp); |
| |
| /*set sas addr*/ |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ID_FRAME4); |
| tmp = 0x50050430; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)), tmp); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ID_FRAME3); |
| tmp = 0x11ab0000; |
| tmp |= phy_id; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)), tmp); |
| |
| /*enable phy*/ |
| scsi_write_dword((off+COMMON_PORT_VSR_ADDR0+(phy_id * 8)), VSR_PHY_CONFIG); |
| tmp = scsi_read_dword(off+COMMON_PORT_VSR_DATA0+(phy_id * 8)); |
| tmp |= (1<<0); |
| scsi_write_dword((off+COMMON_PORT_VSR_DATA0+(phy_id * 8)), tmp&0xfd7fffffL); |
| |
| #if 1 |
| scsi_write_dword((off+COMMON_PORT_VSR_ADDR0+(phy_id * 8)), 0x144); |
| scsi_write_dword((off+COMMON_PORT_VSR_DATA0+(phy_id * 8)), 0x08001006L); |
| scsi_write_dword((off+COMMON_PORT_VSR_ADDR0+(phy_id * 8)), 0x1b4); |
| scsi_write_dword((off+COMMON_PORT_VSR_DATA0+(phy_id * 8)), 0x0000705fL); |
| |
| #endif |
| /* Should clean the unassociated FIS interrupt before issuing |
| * reset phy. Saw a case with a special WD hard drive that |
| * sends two signature FIS's. For this case, if we do not clean |
| * the status, hardware could get stuck not responding to X_RDY |
| * with R_RDY |
| */ |
| tmp = scsi_read_dword(off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| if (tmp & IRQ_UNASSOC_FIS_RCVD_MASK) { |
| scsi_write_dword((off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), IRQ_UNASSOC_FIS_RCVD_MASK); |
| } |
| |
| tmp = scsi_read_dword(off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4)); |
| tmp |= (1<<0); |
| scsi_write_dword((off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4)), tmp); |
| |
| msleep(100); |
| tmp = scsi_read_dword(off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4)); |
| //printf("after write %lx\n", tmp); |
| while((tmp & SCTRL_STP_LINK_LAYER_RESET) && (loop++<100)){ |
| msleep(10); |
| tmp = scsi_read_dword(off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4)); |
| //printf("after write loop tmp %lx loop:%d\n", tmp, loop); |
| } |
| //printf("loop :%d\n", loop); |
| |
| /*reset irq*/ |
| tmp = scsi_read_dword(off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| scsi_write_dword((off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), tmp); |
| |
| tmp = scsi_read_dword(off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)); |
| tmp &= ~(1<<16); |
| scsi_write_dword((off+COMMON_PORT_IRQ_STAT0+(phy_id * 8)), tmp); |
| |
| /* enable phy change interrupt and broadcast change */ |
| tmp = IRQ_UNASSOC_FIS_RCVD_MASK | IRQ_ASYNC_NTFCN_RCVD_MASK; |
| scsi_write_dword((off+COMMON_PORT_IRQ_MASK0+(phy_id * 8)), tmp); |
| |
| |
| msleep(100); |
| |
| /*detect port type*/ |
| scsi_write_dword((off+COMMON_PORT_VSR_ADDR0+(phy_id * 8)), VSR_PHY_STATUS); |
| tmp = scsi_read_dword(off+COMMON_PORT_VSR_DATA0+(phy_id * 8)); |
| //printf("phy status R0Ch:%lx\n", tmp); |
| |
| //msleep(600); |
| |
| switch((u8)(((tmp&VSR_PHY_STATUS_MASK)>>16)&0xff)) |
| { |
| case VSR_PHY_STATUS_SAS_RDY: |
| core->phy[phy_id].type = PORT_TYPE_SAS; |
| break; |
| case VSR_PHY_STATUS_HR_RDY: |
| default: |
| core->phy[phy_id].type = PORT_TYPE_SATA; |
| break; |
| } |
| //printf("core %d phy %d type:%x\n", core->id, phy_id, core->phy[phy_id].type); |
| |
| /*read devinfo and sas address info*/ |
| |
| /*read phy status*/ |
| tmp = scsi_read_dword(off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4))+0x80000L; |
| //printf("phy status 201d0h:%lx\n", tmp); |
| core->phy[phy_id].status = tmp; |
| if (tmp & SCTRL_PHY_READY_MASK) { |
| if (core->phy[phy_id].type & PORT_TYPE_SAS) { |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ATT_ID_FRAME5); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)); |
| core->phy[phy_id].attach_info = (tmp&0xffL)<<24; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ATT_ID_FRAME0); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)); |
| core->phy[phy_id].attach_info |= ((tmp&0x70L)>>4) + |
| ((tmp&0x0f000000L)>>8) + |
| ((tmp&0x0f0000L)>>8); |
| |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ATT_ID_FRAME4); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)); |
| core->phy[phy_id].attach_sas_addr_high = cpu_to_be32(tmp); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_ATT_ID_FRAME3); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)); |
| core->phy[phy_id].attach_sas_addr_low = cpu_to_be32(tmp); |
| printf("SAS Core %d PHY %d detect SAS Type attach_info:%x!\n", core->id, phy_id, core->phy[phy_id].attach_info); |
| } else |
| printf("SAS Core %d PHY %d detect SATA Type!\n", core->id, phy_id); |
| |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_PHY_CONTROL); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)), 0x04); |
| } |
| } |
| |
| static void mv_port_config (struct sas_core *core) { |
| ulong off = core->mmio_off; |
| ulong tmp; |
| u8 i; |
| |
| for (i=0; i<core->phy_num; i++) { |
| struct port_info *port = NULL; |
| |
| if (0 == (core->phy[i].status & SCTRL_PHY_READY_MASK)) { |
| continue; |
| } |
| |
| core->phy[i].wp_phymap |= (1<<i); |
| |
| port = core->phy[i].port; |
| if (port == NULL) { |
| /*new port*/ |
| u8 port_id = mv_host->port_num; |
| if (port_id >= mv_host->max_port_num) |
| return; |
| mv_host->ports[port_id].id = port_id; |
| mv_host->port_num++; |
| port = &mv_host->ports[port_id]; |
| core->phy[i].port = port; |
| port->type = core->phy[i].type; |
| port->host = mv_host; |
| port->core = core; |
| port->dev_num = 0; |
| port->phy_map = core->phy[i].wp_phymap; |
| } |
| |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(i * 8)), CONFIG_WIDE_PORT); |
| tmp = (ulong)core->phy[i].wp_phymap; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_DATA0+(i * 8)), tmp); |
| |
| } |
| } |
| |
| static int pm_issue_read_reg(struct dev_info *dev, u8 is_control, u8 reg_num){ |
| u8 fis[20]; |
| //printf("pm issue read reg ,reg num %d\n", reg_num); |
| |
| memset(fis, 0, 20); |
| /* Construct the FIS */ |
| fis[0] = 0x27; /* Host to device FIS. */ |
| //fis[1] = dev->pm_num | 0x80; /* cmd pm */ |
| fis[1] = 0x0F | 0x80; |
| fis[2] = ATA_CMD_PM_READ; /* Command byte. */ |
| fis[3] = reg_num; /*features */ |
| fis[7] = is_control? 0x0f : dev->pm_num; /*device*/ |
| |
| if (issue_sata_cmd_nodata(dev, (u8 *) &fis, 20, 0)) { |
| debug("pm read reg failed.\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int pm_issue_write_reg(struct dev_info *dev, u8 is_control, |
| u8 reg_num, ulong reg_value){ |
| u8 fis[20]; |
| //printf("pm issue write reg, reg num %d, reg value %x.\n", reg_num, reg_value); |
| |
| memset(fis, 0, 20); |
| |
| /* Construct the FIS */ |
| fis[0] = 0x27; /* Host to device FIS. */ |
| fis[1] = 0x0F | 0x80; /* cmd pm */ |
| fis[2] = ATA_CMD_PM_WRITE; /* Command byte. */ |
| fis[3] = reg_num; /*features */ |
| fis[4] = (u8)((reg_value & 0xff00) >> 8);/*lba_low */ |
| fis[5] = (u8)((reg_value & 0xff0000) >> 16);/*lba_mid */ |
| fis[6] = (u8)((reg_value & 0xff000000) >> 24);/*lba_high */ |
| fis[7] = is_control? 0x0f : dev->pm_num; /*device*/ |
| fis[12] = (u8)(reg_value & 0xff);/*sector count */ |
| |
| if (issue_sata_cmd_nodata(dev, (u8 *) &fis, 20, 0)) { |
| debug("pm write reg failed.\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static u8 sata_init_pm(struct sas_core * core, struct dev_info *dev){ |
| ulong tmp, try_cnt = 20; |
| ulong rx_fis = cpu_to_le32(mv_host->rx_fis & 0xffffffff); |
| u32 unassoc_off = mv_host->unassociate_fis_off; |
| u8 tmp_set = 0; |
| ulong serror = 0, sstatus; |
| //printf("sata init pm \n"); |
| |
| memset((u32 *)rx_fis, 0, unassoc_off + 40 + 32); //only one register set, 0x800+D2H FIS off + 4 Dword. |
| |
| //memset(mv_host->rx_fis, 0, (0x100 * core->phy_num) + 40 + 32); |
| pm_issue_write_reg(dev, 0, PM_PSCR_SCONTROL, 0x01); |
| msleep(5); |
| pm_issue_write_reg(dev, 0, PM_PSCR_SCONTROL, 0x00); |
| msleep(5); |
| |
| pm_issue_read_reg(dev, 0, PM_PSCR_SERROR); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| serror = (u8)tmp; |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0x4); |
| serror |= tmp << 8; |
| pm_issue_write_reg(dev, 0, PM_PSCR_SERROR, serror); |
| |
| do{ |
| pm_issue_read_reg(dev, 0, PM_PSCR_SSTATUS); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| sstatus = (u8)tmp; |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0x4); |
| sstatus |= ((u8)tmp) <<8; |
| msleep(10); |
| try_cnt--; |
| }while(((sstatus & 0xFFF) != 0x113) && ((sstatus & 0xFFF) != 0x123) && ((sstatus & 0xFFF) != 0x133) && try_cnt > 0); |
| |
| // printf("Line %d: sstatus %x\n", __LINE__, sstatus); |
| pm_issue_read_reg(dev, 0, PM_PSCR_SERROR); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| |
| switch(sstatus & 0xfff) |
| { |
| case 0x113: |
| dev->link_rate = 0x08; |
| printf("Find SATA Dev %d Link Rate:1.5 Gbps!\n", dev->id); |
| break; |
| case 0x123: |
| dev->link_rate = 0x09; |
| printf("Find SATA Dev %d Link Rate:3.0 Gbps!\n", dev->id); |
| break; |
| case 0x133: |
| dev->link_rate = 0x0A; |
| printf("Find SATA Dev %d Link Rate:6.0 Gbps!\n", dev->id); |
| break; |
| default: |
| //printf("Find SATA Dev %d, sstatus %x \n", dev->id, sstatus); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static void sata_pm_device_detect(struct sas_core * core, struct dev_info *dev){ |
| ulong tmp, off; |
| struct port_info *port = dev->port; |
| ulong rx_fis = cpu_to_le32(mv_host->rx_fis & 0xffffffff); |
| u32 unassoc_off = mv_host->unassociate_fis_off; |
| u8 tmp_set = 0, pm_num_ports, pm_feature_enable; |
| u8 i, dev_id; |
| |
| //printf("sata_pm_device_detect.rx_fis %x, core->phy_num %x\n", rx_fis, core->phy_num); |
| |
| off = core->mmio_off; |
| tmp = scsi_read_dword(off + COMMON_CONTROL); |
| scsi_write_dword(off + COMMON_CONTROL, tmp | CONTROL_FIS_RCV_EN | CONTROL_EN_SATA_RETRY); |
| |
| pm_issue_read_reg(dev, 1, PM_GSCR_INFO); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| pm_num_ports = (u8)tmp; |
| //printf("line %d: rx_fis 0x%x, pm_num_ports %x \n", __LINE__, rx_fis, pm_num_ports); |
| |
| if (pm_num_ports > MV_MAX_DEV_PER_PM) |
| pm_num_ports = MV_MAX_DEV_PER_PM; |
| |
| /*Get PM device ID and vendor ID*/ |
| pm_issue_read_reg(dev, 1, PM_GSCR_ID); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| port->pm_vendor = (u8)tmp; |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0x4); |
| port->pm_id = (u16)(tmp >> 8); |
| port->pm_vendor |= ((u8)tmp) << 8; |
| //printf("port->pm_id = %x, port->pm_vendor = %x \n", port->pm_id, port->pm_vendor); |
| |
| if ((port->pm_vendor == 0x11ab) && (port->pm_id == 0x4140)) |
| pm_issue_write_reg(dev, 1, 0x9B, 0xF0); |
| |
| /*set PM feature*/ |
| pm_issue_read_reg(dev, 1, PM_GSCR_FEATURES_ENABLE); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| |
| pm_feature_enable = (u8)(tmp | 0x00000008); |
| pm_issue_write_reg(dev, 1, PM_GSCR_FEATURES_ENABLE, pm_feature_enable); |
| |
| pm_issue_read_reg(dev, 1, PM_GSCR_ERROR_ENABLE); |
| tmp = readl(rx_fis + SATA_RECEIVED_D2H_FIS(unassoc_off, tmp_set) + 0xC); |
| tmp = tmp | 0x00010000 | 0x04000000; |
| pm_issue_write_reg(dev, 1, PM_GSCR_ERROR_ENABLE, tmp); |
| |
| //memset(dev, 0, sizeof(struct dev_info)); |
| dev->dev_type &= ~DEVICE_TYPE_SATA; |
| dev->id = 0; |
| dev->pm_num = 0; |
| port->dev_num--; |
| mv_host->dev_num--; |
| |
| for (i = 0; i < pm_num_ports; i++) |
| { |
| dev_id = mv_host->dev_num; |
| dev = &mv_host->devices[dev_id]; |
| dev->id = dev_id; |
| dev->port = port; |
| dev->pm_num = i; |
| dev->dev_type |= DEVICE_TYPE_SATA; |
| dev->register_set = 0xff; |
| mv_host->dev_num++; |
| port->dev_num++; |
| |
| if (!sata_init_pm(core, dev)) |
| { |
| // if ((dev->link_rate != 0x08) |
| // && (dev->link_rate != 0x09) |
| // && (dev->link_rate != 0x0A)) |
| port->dev_num--; |
| |
| //memset(dev, 0, sizeof(struct dev_info)); |
| dev->dev_type &= ~DEVICE_TYPE_SATA; |
| dev->id = 0; |
| dev->pm_num = 0; |
| mv_host->dev_num--; |
| continue; |
| } |
| |
| printf("dev_id %x, Find Device on PM Port-> %x \n",dev_id, dev->pm_num); |
| } |
| } |
| |
| static void mv_sata_port_reset(struct port_info *port) { |
| ulong tmp, off; |
| struct sas_core *core; |
| struct dev_info *dev = NULL; |
| u8 phy_id; |
| u8 dev_id; |
| if (mv_host->dev_num >= MV_MAX_DEVICES) |
| return; |
| |
| //printf("enter mv_sata_port_reset!!\n"); |
| if (port == NULL) { |
| return; |
| } |
| |
| port->dev_num = 0; |
| |
| /*sata port detect*/ |
| /*only support narrow port, use port->phy_map as phy id*/ |
| core = (struct sas_core *)port->core; |
| off = core->mmio_off; |
| |
| phy_id = 0; |
| while (0 == (port->phy_map & (1 <<phy_id))) |
| phy_id++; |
| //printf("reset sata port, core:%p off:%lx phy id:%d\n", core, off, phy_id); |
| |
| tmp = scsi_read_dword(off+COMMON_PORT_PHY_CONTROL0+(phy_id * 4)); |
| if ((tmp & SCTRL_PHY_READY_MASK) != SCTRL_PHY_READY_MASK) |
| printf("detect sata port %d failed phy status(0x01d0h):%lx\n", phy_id, tmp); |
| |
| /*assign device*/ |
| { |
| dev_id = mv_host->dev_num; |
| dev = &mv_host->devices[dev_id]; |
| dev->id = dev_id; |
| dev->port = port; |
| dev->pm_num = 0; |
| dev->register_set = 0xff; |
| mv_host->dev_num++; |
| |
| port->dev_num++; |
| } |
| |
| /*sata port detect*/ |
| #if 1 |
| /*if support PM, need issue softreset first*/ |
| ata_issue_softreset(dev, 1, 1); |
| msleep(5); |
| ata_issue_softreset(dev, 0, 1); |
| msleep(50); |
| #endif |
| |
| /*check device ready*/ |
| { |
| u32 loop=1000; |
| //u8 phy_id = port->phy_map; |
| u32 sig = 0x0; |
| while (loop > 0) { |
| sig = 0; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_SATA_SIG3); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)) & 0xFF; |
| sig |= tmp; |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_SATA_SIG1); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)) & 0xFFFFFF; |
| sig |= (tmp << 8); |
| scsi_write_dword((off+COMMON_PORT_CONFIG_ADDR0+(phy_id * 8)), CONFIG_SATA_SIG0); |
| tmp = scsi_read_dword(off+COMMON_PORT_CONFIG_DATA0+(phy_id * 8)) & 0xFF; |
| if (!(((tmp>>16) & 0xFF) & 0x80)) { |
| switch(sig) |
| { |
| case 0x96690101: /*PM signature*/ |
| port->type = PORT_TYPE_SATA | PORT_TYPE_PM; |
| if ((u8)(tmp >> 16) == 0x50 ) |
| goto end; |
| break; |
| case 0x00000101: /*ATA signature*/ |
| //printf("find sata signature!!\n"); |
| dev->dev_type |= DEVICE_TYPE_SATA; |
| if((u8)((tmp >> 16)& 0xc0) == 0x40 ) |
| goto end; |
| break; |
| default: |
| break; |
| } |
| break; |
| |
| } else |
| printf("signature busy!!!\n"); |
| msleep(1); |
| loop--; |
| } |
| end: |
| if (loop == 0) { |
| printf("device not ready!!!\n"); |
| return; |
| } |
| |
| if (port->type & PORT_TYPE_PM) { |
| /*device detection behind PM*/ |
| dev->pm_num = 0xF; |
| sata_pm_device_detect(core, dev); |
| } else { |
| /*sata device ready*/ |
| |
| if ((core->phy[phy_id].status & SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK) >> |
| SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET < 0x0A) |
| dev->link_rate = (core->phy[phy_id].status & SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK) >> |
| SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET; |
| else |
| dev->link_rate = 0x0A; |
| |
| switch (dev->link_rate) { |
| case 0x08: |
| printf("Find SATA Dev %d Link Rate:1.5 Gbps!\n", dev->id); |
| break; |
| case 0x09: |
| printf("Find SATA Dev %d Link Rate:3.0 Gbps!\n", dev->id); |
| break; |
| case 0x0A: |
| default: |
| printf("Find SATA Dev %d Link Rate:6.0 Gbps!\n", dev->id); |
| break; |
| } |
| } |
| return; |
| } |
| } |
| |
| static void mv_sas_port_reset(struct port_info *port) { |
| //ulong off; |
| struct sas_core *core; |
| struct dev_info *dev = NULL; |
| u8 phy_id; |
| u8 dev_id; |
| if (mv_host->dev_num >= MV_MAX_DEVICES) |
| return; |
| |
| //printf("enter mv_sata_port_reset!!\n"); |
| if (port == NULL) { |
| return; |
| } |
| |
| port->dev_num = 0; |
| |
| /*sata port detect*/ |
| /*only support narrow port, use port->phy_map as phy id*/ |
| core = (struct sas_core *)port->core; |
| //off = core->mmio_off; |
| |
| phy_id = 0; |
| while (0 == (port->phy_map & (1 <<phy_id))) |
| phy_id++; |
| //printf("reset sata port, core:%p off:%lx phy id:%d\n", core, off, phy_id); |
| |
| if (core->phy[phy_id].attach_info & (1<<19)){ |
| /*assign device*/ |
| dev_id = mv_host->dev_num; |
| dev = &mv_host->devices[dev_id]; |
| dev->id = dev_id; |
| dev->port = port; |
| dev->pm_num = 0; |
| dev->register_set = 0xff; |
| |
| dev->dev_type = DEVICE_TYPE_SSP; |
| dev->sas_addr_low = core->phy[phy_id].attach_sas_addr_low; |
| dev->sas_addr_high = core->phy[phy_id].attach_sas_addr_high; |
| if ((core->phy[phy_id].status & SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK) >> |
| SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET < 0x0A) |
| dev->link_rate = (core->phy[phy_id].status & SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK) >> |
| SCTRL_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET; |
| else |
| dev->link_rate = 0x0A; |
| mv_host->dev_num++; |
| port->dev_num++; |
| |
| switch (dev->link_rate) { |
| case 0x08: |
| printf("Find SAS Dev %d Link Rate:1.5 Gbps!\n", dev->id); |
| break; |
| case 0x09: |
| printf("Find SAS Dev %d Link Rate:3.0 Gbps!\n", dev->id); |
| break; |
| case 0x0A: |
| default: |
| printf("Find SAS Dev %d Link Rate:6.0 Gbps!\n", dev->id); |
| break; |
| } |
| } else |
| printf("detect sas phy %d failed attach info:%x\n", phy_id, core->phy[phy_id].attach_info); |
| |
| return; |
| } |
| |
| static void mv_port_start (struct host_info *host) { |
| u8 i; |
| struct port_info *port; |
| |
| for (i=0; i<host->port_num; i++) { |
| port = &mv_host->ports[i]; |
| if (port->type == PORT_TYPE_SAS) { |
| /*reset sas port*/ |
| //to be done |
| mv_sas_port_reset(port); |
| } else |
| mv_sata_port_reset(port); |
| /*reset sata port*/ |
| } |
| } |
| |
| static void scsi_chip_init(void) |
| { |
| ulong tmp, off; |
| u8 i, j; |
| u8 k; |
| |
| for (i=0; i<mv_host->core_num; i++) { |
| off = mv_host->core[i].mmio_off; |
| tmp = scsi_read_dword(off+COMMON_CONFIG); |
| //printf("common config:%lx core %d off:%lx\n", tmp, i, off); |
| tmp |= CONFIG_SAS_SATA_RST; |
| scsi_write_dword(off, tmp); |
| msleep(100); |
| |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_ADDR), VSR_PHY_CONFIG); |
| tmp = scsi_read_dword(off+COMMON_PORT_ALL_VSR_DATA); |
| //printf("phy config:%lx\n", tmp); |
| |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_ADDR), VSR_PHY_CONFIG); |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_DATA), 0x0084fffeL); |
| |
| #if 0 |
| /*bios*/ |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_ADDR), 0x144); |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_DATA), 0x08001006L); |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_ADDR), 0x1b4); |
| scsi_write_dword((off+COMMON_PORT_ALL_VSR_DATA), 0x0000705fL); |
| #endif |
| |
| scsi_write_dword((off+COMMON_CONTROL), 0); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x128); |
| tmp = scsi_read_dword(off+COMMON_CMD_DATA); |
| tmp &=~ 0xffff; |
| tmp |= 0x7f7f; |
| scsi_write_dword((off+COMMON_CMD_DATA), tmp); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x124); |
| tmp = scsi_read_dword(off+COMMON_CMD_DATA); |
| tmp &= ~0xffffL; |
| tmp |= 0x3fffL; |
| scsi_write_dword((off+COMMON_CMD_DATA), tmp); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x134); |
| tmp = scsi_read_dword(off+COMMON_CMD_DATA); |
| tmp &=0xFFFF00FF; |
| tmp |=0x00028200; |
| scsi_write_dword((off+COMMON_CMD_DATA), tmp); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x138); |
| scsi_write_dword((off+COMMON_CMD_DATA), 0x003f003fL); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x13c); |
| scsi_write_dword((off+COMMON_CMD_DATA), 0x7a0000L); |
| |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x1a4); |
| scsi_write_dword((off+COMMON_CMD_DATA), 0xffefbf7dL); |
| |
| #if 0 |
| /*bios*/ |
| scsi_write_dword((off+COMMON_CMD_ADDR), 0x1b8); |
| tmp = scsi_read_dword(off+COMMON_CMD_DATA); |
| tmp &=0x0000ffffL; |
| tmp |=0x00fa0000L; |
| scsi_write_dword((off+COMMON_CMD_DATA), tmp); |
| |
| #endif |
| |
| #if 1 |
| /*set phy ffe*/ |
| for (j=0; j<MV_MAX_CORE_PHY; j++) { |
| scsi_write_phy_vsr_addr(off, j, 0x10c); |
| tmp = scsi_read_vsr_data(off, j); |
| tmp &= 0xFFFFFF80L; |
| tmp |= 0x7C; |
| scsi_write_phy_vsr_data(off, j, tmp); |
| } |
| |
| /*set phy tuning*/ |
| for (j=0; j<MV_MAX_CORE_PHY; j++) { |
| ulong value1, value2; |
| for (k=0; k<3; k++) { |
| switch (k) { |
| case 0: |
| value1 = 0x118; |
| value2 = 0x11C; |
| break; |
| case 1: |
| value1 = 0x11C; |
| value2 = 0x120; |
| break; |
| case 2: |
| value1 = 0x120; |
| value2 = 0x124; |
| break; |
| } |
| |
| scsi_write_phy_vsr_addr(off, j, value1); |
| tmp = scsi_read_vsr_data(off, j); |
| tmp &= ~(0xFBE << 16); |
| tmp |= (((0x1 << 11) | (0x1A << 7) | (0x6 << 1)) << 16); |
| scsi_write_phy_vsr_data(off, j, tmp); |
| |
| scsi_write_phy_vsr_addr(off, j, value2); |
| tmp = scsi_read_vsr_data(off, j); |
| tmp &= ~(0xC000); |
| tmp |= (0x3 << 14); |
| scsi_write_phy_vsr_data(off, j, tmp); |
| } |
| } |
| /*set phy rate*/ |
| for (j=0; j<MV_MAX_CORE_PHY; j++) { |
| scsi_write_phy_vsr_addr(off, j, VSR_PHY_CONFIG); |
| tmp = scsi_read_vsr_data(off, j); |
| //printf("phy rate:%lx\n", tmp); |
| tmp &= 0x80000FL; |
| tmp |= 0x0004fffeL; //6G //0x0004783eL; // 3G |
| scsi_write_phy_vsr_addr(off, j, VSR_PHY_CONFIG); |
| scsi_write_phy_vsr_data(off, j, tmp); |
| } |
| #endif |
| //msleep(500); /*wait phy ready*/ |
| |
| /*spin up device*/ |
| //To be done |
| for (j=0; j<MV_MAX_CORE_PHY; j++) { |
| mv_phy_config(&mv_host->core[i], j); |
| } |
| |
| /*reset CMD queue*/ |
| tmp = scsi_read_dword(off+COMMON_CONTROL); |
| tmp |= CONTROL_RESET_CMD_ISSUE; |
| scsi_write_dword((off+COMMON_CONTROL), tmp); |
| |
| tmp = scsi_read_dword(off+COMMON_CONFIG); |
| tmp |= CONFIG_CMD_TBL_BE; |
| tmp |= CONFIG_DATA_BE; |
| tmp &= ~CONFIG_OPEN_ADDR_BE; |
| tmp |= CONFIG_RSPNS_FRAME_BE; |
| scsi_write_dword((off+COMMON_CONFIG), tmp); |
| #if 0/*bios*/ |
| tmp = scsi_read_dword(off+COMMON_CONFIG); |
| tmp = scsi_read_dword(off+COMMON_CONFIG); |
| #endif |
| tmp = CONTROL_EN_CMD_ISSUE | CONTROL_RSPNS_RCV_EN; |
| scsi_write_dword((off+COMMON_CONTROL), tmp); |
| |
| tmp = cpu_to_le32(mv_host->cmd_list & 0xffffffff); |
| scsi_write_dword((off+COMMON_LST_ADDR), tmp); |
| scsi_write_dword((off+COMMON_LST_ADDR_HI), 0); |
| |
| tmp = cpu_to_le32(mv_host->rx_fis & 0xffffffff); |
| scsi_write_dword((off+COMMON_FIS_ADDR), tmp); |
| scsi_write_dword((off+COMMON_FIS_ADDR_HI), 0); |
| |
| tmp = 0; |
| scsi_write_dword((off+COMMON_DELV_Q_CONFIG), tmp); |
| tmp = DELV_QUEUE_SIZE_MASK & MAX_DELV_QUEUE_SIZE; |
| tmp |= DELV_QUEUE_ENABLE; |
| scsi_write_dword((off+COMMON_DELV_Q_CONFIG), tmp); |
| tmp = cpu_to_le32(mv_host->dlv_q & 0xffffffff); |
| scsi_write_dword((off+COMMON_DELV_Q_ADDR), tmp); |
| scsi_write_dword((off+COMMON_DELV_Q_ADDR_HI), 0); |
| |
| tmp = 0; |
| scsi_write_dword((off+COMMON_CMPL_Q_CONFIG), tmp); |
| tmp = CMPL_QUEUE_SIZE_MASK & MAX_CMPL_QUEUE_SIZE; |
| tmp |= CMPL_QUEUE_ENABLE; |
| scsi_write_dword((off+COMMON_CMPL_Q_CONFIG), tmp); |
| tmp = cpu_to_le32(mv_host->cmpl_q & 0xffffffff); |
| scsi_write_dword((off+COMMON_CMPL_Q_ADDR), tmp); |
| scsi_write_dword((off+COMMON_CMPL_Q_ADDR_HI), 0); |
| |
| tmp = 0; |
| tmp = INT_COAL_COUNT_MASK & MAX_DELV_QUEUE_SIZE; //one slot support |
| tmp |= INT_COAL_ENABLE; |
| scsi_write_dword((off+COMMON_COAL_CONFIG), tmp); |
| //tmp = 0x10400; //1ms |
| tmp = COAL_TIMER_MASK; |
| scsi_write_dword((off+COMMON_COAL_TIMEOUT), tmp); |
| |
| /* enable CMD/CMPL_Q/RESP mode */ |
| tmp = scsi_read_dword(off+COMMON_CONTROL); |
| tmp |= CONTROL_EN_CMD_ISSUE; /* for performance */ |
| tmp |= CONTROL_FIS_RCV_EN; |
| scsi_write_dword((off+COMMON_CONTROL), tmp); |
| |
| /* enable completion queue interrupt */ |
| tmp = (INT_PORT_MASK | INT_CMD_CMPL | INT_PHY_MASK); |
| scsi_write_dword((off+COMMON_IRQ_MASK), tmp); |
| |
| mv_port_config(&mv_host->core[i]); |
| } |
| |
| //msleep(9500); |
| |
| mv_port_start(mv_host); |
| |
| } |
| |
| static int mv_host_data_init(void) |
| { |
| u8 i; |
| u16 device; |
| struct sas_core *core = NULL; |
| struct port_info *port = NULL; |
| struct dev_info *dev = NULL; |
| |
| pci_read_config_word(mv_host->dev, PCI_DEVICE_ID, &device); |
| switch (device) { |
| case 0x9445: |
| mv_host->core_num = 1; |
| mv_host->max_port_num = 4; |
| break; |
| case 0x9485: |
| mv_host->core_num = 2; |
| mv_host->max_port_num = 8; |
| break; |
| default: |
| printf("unsupported device ID %x\n", device); |
| return -1; |
| } |
| |
| for (i=0; i<mv_host->core_num; i++) { |
| core = &mv_host->core[i]; |
| core->host = mv_host; |
| core->mmio_off = SAS_REG_BASE + 0x4000*i; |
| core->lst_dlvq = 0xfff; |
| core->lst_cmplq = 0xfff; |
| core->id = i; |
| core->start_phy_id = MV_MAX_CORE_PHY * i; |
| core->phy_num = MV_MAX_CORE_PHY; |
| printf("PCI Bar Address:0x%lx SAS Core %d Register Base:0x%lx!\n", mv_host->mmio, core->id, core->mmio_off); |
| } |
| |
| for (i=0; i<mv_host->max_port_num; i++) { |
| port = &mv_host->ports[i]; |
| port->id = i; |
| port->host = mv_host; |
| port->core = (i < MV_MAX_CORE_PHY) ? (&mv_host->core[0]) : (&mv_host->core[1]); |
| port->type = PORT_TYPE_SAS; |
| } |
| |
| for (i=0; i<MV_MAX_DEVICES; i++) { |
| dev = &mv_host->devices[i]; |
| dev->register_set = 0xff; |
| } |
| |
| mv_host->unassociate_fis_off = 0x800; |
| |
| return 0; |
| } |
| |
| static int mv_host_init(int busdevfunc) |
| { |
| u32 mem; |
| |
| /*init memory*/ |
| mv_host = malloc(sizeof(struct host_info)); |
| if (!mv_host) { |
| printf("No mem for host!\n"); |
| return -ENOMEM; |
| } |
| memset(mv_host, 0, sizeof(struct host_info)); |
| |
| mv_host->dev = busdevfunc; |
| mv_host->mmio = scsi_mem_addr; |
| |
| if (mv_host_data_init()) |
| return -1; |
| |
| mem = (u32) malloc(CORE_BUFFLEN); |
| if (!mem) { |
| free(mv_host); |
| printf("No mem for DMA!\n"); |
| return -ENOMEM; |
| } |
| memset((u8 *) mem, 0, CORE_BUFFLEN); |
| |
| mem = (mem + 0x40 - 1) & (~0x3f); /*align to 64bytes*/ |
| mv_host->cmd_list = mem; |
| mem += CMD_LIST_POOL_SIZE; |
| |
| mem = (mem + 0x100 - 1) & (~0xff); /*align to 256bytes*/ |
| mv_host->rx_fis = mem; |
| mem += RX_FIS_POOL_SIZE; |
| |
| mem = (mem + 0x80 - 1) & (~0x7f); /*align to 128bytes*/ |
| mv_host->cmd_table = mem; |
| //mv_host->cmd_table_dma = |
| mem += CMD_TABLE_POOL_SIZE; |
| |
| mem = (mem + 0x40 - 1) & (~0x3f); /*align to 64bytes*/ |
| mv_host->dlv_q = mem; |
| mem += DELV_Q_POOL_SIZE; |
| |
| mem = (mem + 0x40 - 1) & (~0x3f); /*align to 64bytes*/ |
| mv_host->cmpl_q = mem; |
| mem += CMPL_Q_POOL_SIZE; |
| |
| return 0; |
| } |
| |
| static int mv_host_reinit(void) |
| { |
| struct host_info tmp_host; |
| |
| memcpy(&tmp_host, mv_host, sizeof(struct host_info)); |
| memset(mv_host, 0, sizeof(struct host_info)); |
| |
| mv_host->dev = tmp_host.dev; |
| mv_host->mmio = scsi_mem_addr; |
| mv_host->cmd_list = tmp_host.cmd_list; |
| mv_host->rx_fis = tmp_host.rx_fis; |
| mv_host->cmd_table = tmp_host.cmd_table; |
| mv_host->dlv_q = tmp_host.dlv_q; |
| mv_host->cmpl_q = tmp_host.cmpl_q; |
| |
| if (mv_host_data_init()) |
| return -1; |
| |
| return 0; |
| } |
| |
| void scsi_low_level_init(int busdevfunc) |
| { |
| #if 1 |
| u32 cmd; |
| ulong addr; |
| |
| //pci_read_config_byte(busdevfunc, PCI_INTERRUPT_LINE, &vec); |
| addr = (ulong)pci_map_bar(busdevfunc, |
| PCI_BASE_ADDRESS_2, PCI_REGION_MEM); |
| scsi_mem_addr = addr; |
| |
| if (mv_host_init(busdevfunc)) { |
| printf("init scsi host failed!!!\n"); |
| return; |
| } |
| /* |
| * Enable bus mastering in case this has not been done, yet. |
| */ |
| pci_read_config_dword(busdevfunc, PCI_COMMAND, &cmd); |
| cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; |
| pci_write_config_dword(busdevfunc, PCI_COMMAND, cmd); |
| |
| pci_read_config_dword(busdevfunc, 0x78, &cmd); |
| //printf("PCI dev ctl 0x%x, it should be smaller than 0x2000\n", (cmd & 0x00007000)); |
| |
| scsi_chip_init(); |
| |
| #endif |
| } |
| |
| void scsi_bus_reset(void) |
| { |
| /*Not implement*/ |
| mv_host_reinit(); |
| scsi_chip_init(); |
| } |
| |
| void scsi_print_error(ccb * pccb) |
| { |
| //printf("scsi_print_error: command 0x%02x error\n", pccb->cmd[0]); |
| } |
| |