[PATCH 2.6.15.4 rel.2 1/1] libata: add hotswap to sata_svw
From: Martin Devera
Date: Thu Feb 16 2006 - 10:08:17 EST
From: Martin Devera <devik@xxxxxx>
Add hotswap capability to Serverworks/BroadCom SATA controlers. The
controler has SIM register and it selects which bits in SATA_ERROR
register fires interrupt.
The solution hooks on COMWAKE (plug), PHYRDY change and 10B8B decode
error (unplug) and calls into Lukasz's hotswap framework.
The code got one day testing on dual core Athlon64 H8SSL Supermicro
MoBo with HT-1000 SATA, SMP kernel and two CaviarRE SATA HDDs in
hotswap bays.
Signed-off-by: Martin Devera <devik@xxxxxx>
---
This patch depends on Lukasz Kosewski's:
[PATCH 2.6.15-rc7-git3 2/3] libata: framework API for hotswapping drives on libata
and its intent is mainly wider testing and review
--- a/drivers/scsi/sata_svw.c Wed Feb 15 11:20:22 2006
+++ b/drivers/scsi/sata_svw.c Thu Feb 16 15:49:31 2006
@@ -9,6 +9,7 @@
* Copyright 2003 Benjamin Herrenschmidt <benh@xxxxxxxxxxxxxxxxxxx>
*
* Bits from Jeff Garzik, Copyright RedHat, Inc.
+ * Hotplug code by Martin Devera <devik@xxxxxx>
*
* This driver probably works with non-Apple versions of the
* Broadcom chipset...
@@ -54,7 +55,7 @@
#endif /* CONFIG_PPC_OF */
#define DRV_NAME "sata_svw"
-#define DRV_VERSION "1.07"
+#define DRV_VERSION "1.08"
/* Taskfile registers offsets */
#define K2_SATA_TF_CMD_OFFSET 0x00
@@ -81,9 +82,18 @@
#define K2_SATA_SICR2_OFFSET 0x84
#define K2_SATA_SIM_OFFSET 0x88
+/* Hotplug interrupt masks */
+#define K2_SIM_REMOVE_MASK 0x90000
+#define K2_SIM_INSERT_MASK 0x40000
+
/* Port stride */
#define K2_SATA_PORT_OFFSET 0x100
+struct svw_host
+{
+ unsigned long not_in_reset_bitmap; /* see k2_hotplug_irq for explanation */
+};
+
static u8 k2_stat_check_status(struct ata_port *ap);
@@ -282,6 +292,118 @@ static int k2_sata_proc_info(struct Scsi
}
#endif /* CONFIG_PPC_OF */
+/* This shall run every time for all ports when interrupt fires. It
+ returns 1 if hotplug related event was handled. */
+static int k2_hotplug_irq(struct ata_port *ap,struct svw_host *host)
+{
+ u32 error = scr_read(ap, SCR_ERROR);
+ error &= K2_SIM_REMOVE_MASK | K2_SIM_INSERT_MASK;
+ if (!error)
+ return 0;
+
+ /* clear error bits, this must be done in any case or we get
+ unhandled IRQ BUG */
+ scr_write(ap, SCR_ERROR, error);
+
+ DPRINTK("K2 Got (un)plug event %X (status %X) on dev %d\n",
+ error,scr_read(ap, SCR_STATUS),ap->port_no+1);
+
+ /* not_in_reset_bitmap has a bit set for each port when the port is
+ outside of reset routine. Because reset generates spurious events
+ we ignore them here. Because not_in_reset_bitmap is zero at init
+ time we also ignore these events until the port is first reset.
+ It is not possible simply to set SIM register to zero because
+ interrupt comming from other port still causes this routine run
+ for all ports and the event will be discovered. */
+ if (!test_bit(ap->port_no,&host->not_in_reset_bitmap)) {
+ DPRINTK("K2 ignoring the event\n");
+ return 1;
+ }
+
+ if (error & K2_SIM_INSERT_MASK)
+ sata_hot_plug(ap);
+ else /* make sure that the drive is really gone */
+ if (!sata_dev_present(ap))
+ sata_hot_unplug(ap);
+ return 1;
+}
+
+/* Essencialy the same as in libata, we only needed to hook hotplug events. */
+static irqreturn_t k2_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
+{
+ struct ata_host_set *host_set = dev_instance;
+ struct svw_host *host = (struct svw_host *)host_set->private_data;
+ unsigned int i;
+ unsigned int handled = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host_set->lock, flags);
+
+ for (i = 0; i < host_set->n_ports; i++) {
+ struct ata_port *ap;
+
+ ap = host_set->ports[i];
+ if (!ap)
+ continue;
+
+ if (!(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) {
+ struct ata_queued_cmd *qc;
+
+ qc = ata_qc_from_tag(ap, ap->active_tag);
+ if (qc && (!(qc->tf.ctl & ATA_NIEN)) &&
+ (qc->flags & ATA_QCFLAG_ACTIVE))
+ handled |= ata_host_intr(ap, qc);
+ }
+ handled |= k2_hotplug_irq(ap,host);
+ }
+
+ spin_unlock_irqrestore(&host_set->lock, flags);
+
+ return IRQ_RETVAL(handled);
+}
+
+/* Override phy_reset to setup SIM register. It must be done here because it
+ can trigger IRQ and we must be sure that a handler is installed. And it is
+ at the time when reset is called. */
+static void k2_phy_reset(struct ata_port *ap)
+{
+ /* HACK: there is no standard place in port structure to record
+ base pointer - incidentaly cmd_addr is at offset zero ... */
+ void *base = (void*)ap->ioaddr.cmd_addr;
+
+ struct svw_host *host = (struct svw_host *)ap->host_set->private_data;
+
+ DPRINTK("K2 reset %d start\n",ap->port_no+1);
+
+ clear_bit(ap->port_no,&host->not_in_reset_bitmap);
+
+ /* Not strictly neccessary but why to fire spurious interrupts
+ and then ignore them anyway */
+ writel(0, base + K2_SATA_SIM_OFFSET);
+ sata_phy_reset(ap);
+
+ /* Clear a magic bit in SCR1 according to Darwin, those help
+ * some funky seagate drives (though so far, those were already
+ * set by the firmware on the machines I had access to)
+ */
+ writel(readl(base + K2_SATA_SICR1_OFFSET) & ~0x00040000,
+ base + K2_SATA_SICR1_OFFSET);
+
+ /* Make sure that interrupt is triggered when these XXX_MASK bits
+ in SCR_ERROR are set */
+ scr_write(ap, SCR_ERROR, K2_SIM_INSERT_MASK | K2_SIM_REMOVE_MASK);
+ writel(K2_SIM_INSERT_MASK | K2_SIM_REMOVE_MASK, base + K2_SATA_SIM_OFFSET);
+
+ set_bit(ap->port_no,&host->not_in_reset_bitmap);
+
+ DPRINTK("K2 reset done\n");
+}
+
+static void k2_host_stop (struct ata_host_set *host_set)
+{
+ kfree(host_set->private_data);
+ ata_pci_host_stop(host_set);
+}
static struct scsi_host_template k2_sata_sht = {
.module = THIS_MODULE,
@@ -314,7 +436,7 @@ static const struct ata_port_operations
.check_status = k2_stat_check_status,
.exec_command = ata_exec_command,
.dev_select = ata_std_dev_select,
- .phy_reset = sata_phy_reset,
+ .phy_reset = k2_phy_reset,
.bmdma_setup = k2_bmdma_setup_mmio,
.bmdma_start = k2_bmdma_start_mmio,
.bmdma_stop = ata_bmdma_stop,
@@ -322,13 +444,13 @@ static const struct ata_port_operations
.qc_prep = ata_qc_prep,
.qc_issue = ata_qc_issue_prot,
.eng_timeout = ata_eng_timeout,
- .irq_handler = ata_interrupt,
+ .irq_handler = k2_interrupt,
.irq_clear = ata_bmdma_irq_clear,
.scr_read = k2_sata_scr_read,
.scr_write = k2_sata_scr_write,
.port_start = ata_port_start,
.port_stop = ata_port_stop,
- .host_stop = ata_pci_host_stop,
+ .host_stop = k2_host_stop,
};
static void k2_sata_setup_port(struct ata_ioports *port, unsigned long base)
@@ -354,6 +476,7 @@ static void k2_sata_setup_port(struct at
static int k2_sata_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
static int printed_version;
+ struct svw_host *host;
struct ata_probe_ent *probe_ent = NULL;
unsigned long base;
void __iomem *mmio_base;
@@ -408,17 +531,13 @@ static int k2_sata_init_one (struct pci_
goto err_out_free_ent;
}
base = (unsigned long) mmio_base;
-
- /* Clear a magic bit in SCR1 according to Darwin, those help
- * some funky seagate drives (though so far, those were already
- * set by the firmware on the machines I had access to)
- */
- writel(readl(mmio_base + K2_SATA_SICR1_OFFSET) & ~0x00040000,
- mmio_base + K2_SATA_SICR1_OFFSET);
-
- /* Clear SATA error & interrupts we don't use */
- writel(0xffffffff, mmio_base + K2_SATA_SCR_ERROR_OFFSET);
- writel(0x0, mmio_base + K2_SATA_SIM_OFFSET);
+
+ host = kmalloc(sizeof(struct svw_host), GFP_KERNEL);
+ if (!host)
+ goto err_out_free_ent;
+
+ memset(host, 0, sizeof(struct svw_host));
+ probe_ent->private_data = host;
probe_ent->sht = &k2_sata_sht;
probe_ent->host_flags = ATA_FLAG_SATA | ATA_FLAG_SATA_RESET |
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/