/*- * Copyright (c) 1998 - 2008 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "opt_ata.h" #include #include #include #include #include #include #include /* for {get,rel}_mplock() */ #include "ata-all.h" #include "ata-pci.h" #include "ata_if.h" /* * SATA support functions */ void ata_sata_phy_event(void *context, int dummy) { struct ata_connect_task *tp = (struct ata_connect_task *)context; struct ata_channel *ch = device_get_softc(tp->dev); device_t *children; int nchildren, i; get_mplock(); if (tp->action == ATA_C_ATTACH) { if (bootverbose) device_printf(tp->dev, "CONNECTED\n"); ATA_RESET(tp->dev); ata_identify(tp->dev); } if (tp->action == ATA_C_DETACH) { if (!device_get_children(tp->dev, &children, &nchildren)) { for (i = 0; i < nchildren; i++) if (children[i]) device_delete_child(tp->dev, children[i]); kfree(children, M_TEMP); } lockmgr(&ch->state_mtx, LK_EXCLUSIVE); ch->state = ATA_IDLE; lockmgr(&ch->state_mtx, LK_RELEASE); if (bootverbose) device_printf(tp->dev, "DISCONNECTED\n"); } rel_mplock(); kfree(tp, M_ATA); } void ata_sata_phy_check_events(device_t dev) { struct ata_channel *ch = device_get_softc(dev); u_int32_t error = ATA_IDX_INL(ch, ATA_SERROR); /* clear error bits/interrupt */ ATA_IDX_OUTL(ch, ATA_SERROR, error); /* do we have any events flagged ? */ if (error) { struct ata_connect_task *tp; u_int32_t status = ATA_IDX_INL(ch, ATA_SSTATUS); /* if we have a connection event deal with it */ if ((error & ATA_SE_PHY_CHANGED) && (tp = (struct ata_connect_task *) kmalloc(sizeof(struct ata_connect_task), M_ATA, M_INTWAIT | M_ZERO))) { if (((status & ATA_SS_CONWELL_MASK) == ATA_SS_CONWELL_GEN1) || ((status & ATA_SS_CONWELL_MASK) == ATA_SS_CONWELL_GEN2)) { if (bootverbose) device_printf(ch->dev, "CONNECT requested\n"); tp->action = ATA_C_ATTACH; } else { if (bootverbose) device_printf(ch->dev, "DISCONNECT requested\n"); tp->action = ATA_C_DETACH; } tp->dev = ch->dev; TASK_INIT(&tp->task, 0, ata_sata_phy_event, tp); taskqueue_enqueue(taskqueue_thread[mycpuid], &tp->task); } } } static int ata_sata_connect(struct ata_channel *ch) { u_int32_t status; int timeout; /* wait up to 1 second for "connect well" */ for (timeout = 0; timeout < 100 ; timeout++) { status = ATA_IDX_INL(ch, ATA_SSTATUS); if ((status & ATA_SS_CONWELL_MASK) == ATA_SS_CONWELL_GEN1 || (status & ATA_SS_CONWELL_MASK) == ATA_SS_CONWELL_GEN2) break; ata_udelay(10000); } if (timeout >= 100) { if (bootverbose) device_printf(ch->dev, "SATA connect status=%08x\n", status); return 0; } if (bootverbose) device_printf(ch->dev, "SATA connect time=%dms\n", timeout * 10); /* clear SATA error register */ ATA_IDX_OUTL(ch, ATA_SERROR, ATA_IDX_INL(ch, ATA_SERROR)); return 1; } int ata_sata_phy_reset(device_t dev) { struct ata_channel *ch = device_get_softc(dev); int loop, retry; if ((ATA_IDX_INL(ch, ATA_SCONTROL) & ATA_SC_DET_MASK) == ATA_SC_DET_IDLE) return ata_sata_connect(ch); for (retry = 0; retry < 10; retry++) { for (loop = 0; loop < 10; loop++) { ATA_IDX_OUTL(ch, ATA_SCONTROL, ATA_SC_DET_RESET); ata_udelay(100); if ((ATA_IDX_INL(ch, ATA_SCONTROL) & ATA_SC_DET_MASK) == ATA_SC_DET_RESET) break; } ata_udelay(5000); for (loop = 0; loop < 10; loop++) { ATA_IDX_OUTL(ch, ATA_SCONTROL, ATA_SC_DET_IDLE | ATA_SC_IPM_DIS_PARTIAL | ATA_SC_IPM_DIS_SLUMBER); ata_udelay(100); if ((ATA_IDX_INL(ch, ATA_SCONTROL) & ATA_SC_DET_MASK) == 0) return ata_sata_connect(ch); } } return 0; } void ata_sata_setmode(device_t dev, int mode) { struct ata_device *atadev = device_get_softc(dev); /* * if we detect that the device isn't a real SATA device we limit * the transfer mode to UDMA5/ATA100. * this works around the problems some devices has with the * Marvell 88SX8030 SATA->PATA converters and UDMA6/ATA133. */ if (atadev->param.satacapabilities != 0x0000 && atadev->param.satacapabilities != 0xffff) { struct ata_channel *ch = device_get_softc(device_get_parent(dev)); /* on some drives we need to set the transfer mode */ ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_SETXFER, 0, ata_limit_mode(dev, mode, ATA_UDMA6)); /* query SATA STATUS for the speed */ if (ch->r_io[ATA_SSTATUS].res && ((ATA_IDX_INL(ch, ATA_SSTATUS) & ATA_SS_CONWELL_MASK) == ATA_SS_CONWELL_GEN2)) atadev->mode = ATA_SA300; else atadev->mode = ATA_SA150; } else { mode = ata_limit_mode(dev, mode, ATA_UDMA5); if (!ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_SETXFER, 0, mode)) atadev->mode = mode; } } int ata_request2fis_h2d(struct ata_request *request, u_int8_t *fis) { struct ata_device *atadev = device_get_softc(request->dev); if (request->flags & ATA_R_ATAPI) { fis[0] = 0x27; /* host to device */ fis[1] = 0x80 | (atadev->unit & 0x0f); fis[2] = ATA_PACKET_CMD; if (request->flags & (ATA_R_READ | ATA_R_WRITE)) fis[3] = ATA_F_DMA; else { fis[5] = request->transfersize; fis[6] = request->transfersize >> 8; } fis[7] = ATA_D_LBA; fis[15] = ATA_A_4BIT; return 20; } else { ata_modify_if_48bit(request); fis[0] = 0x27; /* host to device */ fis[1] = 0x80 | (atadev->unit & 0x0f); fis[2] = request->u.ata.command; fis[3] = request->u.ata.feature; fis[4] = request->u.ata.lba; fis[5] = request->u.ata.lba >> 8; fis[6] = request->u.ata.lba >> 16; fis[7] = ATA_D_LBA; if (!(atadev->flags & ATA_D_48BIT_ACTIVE)) fis[7] |= (ATA_D_IBM | (request->u.ata.lba >> 24 & 0x0f)); fis[8] = request->u.ata.lba >> 24; fis[9] = request->u.ata.lba >> 32; fis[10] = request->u.ata.lba >> 40; fis[11] = request->u.ata.feature >> 8; fis[12] = request->u.ata.count; fis[13] = request->u.ata.count >> 8; fis[15] = ATA_A_4BIT; return 20; } return 0; }