Linux-ide Archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] ahci: custom reset for Calxeda ahci
@ 2012-08-17 14:51 Mark Langsdorf
  2012-08-17 14:51 ` [PATCH 1/2] ahci: un-staticize ahci_dev_classify Mark Langsdorf
  2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
  0 siblings, 2 replies; 8+ messages in thread
From: Mark Langsdorf @ 2012-08-17 14:51 UTC (permalink / raw
  To: jgarzik; +Cc: linux-ide, probinson, jonathon, dmarlin

The phy interface on Calxeda's EnergyCore SoC does not always connect to
SATA drives correctly, especially when spread spectrum drives are used.
These patches provide a software work-around to the problem.

--Mark Langsdorf
Calxeda, Inc.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] ahci: un-staticize ahci_dev_classify
  2012-08-17 14:51 [PATCH 0/2] ahci: custom reset for Calxeda ahci Mark Langsdorf
@ 2012-08-17 14:51 ` Mark Langsdorf
  2012-08-17 17:27   ` Jeff Garzik
  2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
  1 sibling, 1 reply; 8+ messages in thread
From: Mark Langsdorf @ 2012-08-17 14:51 UTC (permalink / raw
  To: jgarzik; +Cc: linux-ide, probinson, jonathon, dmarlin, Rob Herring

From: Rob Herring <rob.herring@calxeda.com>

Make ahci_dev_classify available to the ahci platform driver for custom
hard reset function.

Signed-off-by: Rob Herring <rob.herring@calxeda.com>
---
 drivers/ata/ahci.h    |    1 +
 drivers/ata/libahci.c |    3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index c2594dd..57eb1c2 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -320,6 +320,7 @@ extern struct device_attribute *ahci_sdev_attrs[];
 extern struct ata_port_operations ahci_ops;
 extern struct ata_port_operations ahci_pmp_retry_srst_ops;
 
+unsigned int ahci_dev_classify(struct ata_port *ap);
 void ahci_fill_cmd_slot(struct ahci_port_priv *pp, unsigned int tag,
 			u32 opts);
 void ahci_save_initial_config(struct device *dev,
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index f9eaa82..555c07a 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -1139,7 +1139,7 @@ static void ahci_dev_config(struct ata_device *dev)
 	}
 }
 
-static unsigned int ahci_dev_classify(struct ata_port *ap)
+unsigned int ahci_dev_classify(struct ata_port *ap)
 {
 	void __iomem *port_mmio = ahci_port_base(ap);
 	struct ata_taskfile tf;
@@ -1153,6 +1153,7 @@ static unsigned int ahci_dev_classify(struct ata_port *ap)
 
 	return ata_dev_classify(&tf);
 }
+EXPORT_SYMBOL_GPL(ahci_dev_classify);
 
 void ahci_fill_cmd_slot(struct ahci_port_priv *pp, unsigned int tag,
 			u32 opts)
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr
  2012-08-17 14:51 [PATCH 0/2] ahci: custom reset for Calxeda ahci Mark Langsdorf
  2012-08-17 14:51 ` [PATCH 1/2] ahci: un-staticize ahci_dev_classify Mark Langsdorf
@ 2012-08-17 14:51 ` Mark Langsdorf
  2012-08-17 15:33   ` Rob Herring
                     ` (2 more replies)
  1 sibling, 3 replies; 8+ messages in thread
From: Mark Langsdorf @ 2012-08-17 14:51 UTC (permalink / raw
  To: jgarzik
  Cc: linux-ide, probinson, jonathon, dmarlin, Mark Langsdorf,
	Rob Herring

Calxeda highbank SATA phy has intermittent problems bringing up a link
with Gen3 drives. Retrying the phy hard reset can work-around this issue,
but each reset also disables spread spectrum support. The reset function
also needs to reprogram the phy to enable spread spectrum support.

Signed-off-by: Mark Langsdorf <mark.langsdorf@calxeda.com>
Signed-off-by: Rob Herring <rob.herring@calxeda.com>
---
 .../devicetree/bindings/arm/calxeda/combophy.txt   |   17 ++
 .../devicetree/bindings/ata/ahci-platform.txt      |    8 +
 arch/arm/boot/dts/highbank.dts                     |   17 ++
 drivers/ata/Makefile                               |    1 +
 drivers/ata/ahci.h                                 |   17 ++
 drivers/ata/ahci_platform.c                        |   88 ++++++++-
 drivers/ata/sata_highbank.c                        |  199 ++++++++++++++++++++
 7 files changed, 339 insertions(+), 8 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/arm/calxeda/combophy.txt
 create mode 100644 drivers/ata/sata_highbank.c

diff --git a/Documentation/devicetree/bindings/arm/calxeda/combophy.txt b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
new file mode 100644
index 0000000..6622bdb
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
@@ -0,0 +1,17 @@
+Calxeda Highbank Combination Phys for SATA
+
+Properties:
+- compatible : Should be "calxeda,hb-combophy"
+- #phy-cells: Should be 1.
+- reg : Address and size for Combination Phy registers.
+- phydev: device ID for programming the combophy.
+
+Example:
+
+	combophy5: combo-phy@fff5d000 {
+		compatible = "calxeda,hb-combophy";
+		#phy-cells = <1>;
+		reg = <0xfff5d000 0x1000>;
+		phydev = <31>;
+	};
+
diff --git a/Documentation/devicetree/bindings/ata/ahci-platform.txt b/Documentation/devicetree/bindings/ata/ahci-platform.txt
index 8bb8a76..147c1f6 100644
--- a/Documentation/devicetree/bindings/ata/ahci-platform.txt
+++ b/Documentation/devicetree/bindings/ata/ahci-platform.txt
@@ -8,9 +8,17 @@ Required properties:
 - interrupts        : <interrupt mapping for SATA IRQ>
 - reg               : <registers mapping>
 
+Optional properties:
+- calxeda,port-phys: phandle-combophy and lane assignment, which maps each
+			SATA port to a combophy and a lane within that
+			combophy
+
 Example:
         sata@ffe08000 {
 		compatible = "calxeda,hb-ahci";
                 reg = <0xffe08000 0x1000>;
                 interrupts = <115>;
+		calxeda,port-phys = <&combophy5 0 &combophy0 0 &combophy0 1
+					&combophy0 2 &combophy0 3>;
+
         };
diff --git a/arch/arm/boot/dts/highbank.dts b/arch/arm/boot/dts/highbank.dts
index 9fecf1a..5204cf7 100644
--- a/arch/arm/boot/dts/highbank.dts
+++ b/arch/arm/boot/dts/highbank.dts
@@ -121,6 +121,9 @@
 			compatible = "calxeda,hb-ahci";
 			reg = <0xffe08000 0x10000>;
 			interrupts = <0 83 4>;
+			calxeda,port-phys = <&combophy5 0 &combophy0 0
+					     &combophy0 1 &combophy0 2
+					     &combophy0 3>;
 		};
 
 		sdhci@ffe0e000 {
@@ -306,5 +309,19 @@
 			reg = <0xfff51000 0x1000>;
 			interrupts = <0 80 4  0 81 4  0 82 4>;
 		};
+
+		combophy0: combo-phy@fff58000 {
+			compatible = "calxeda,hb-combophy";
+			#phy-cells = <1>;
+			reg = <0xfff58000 0x1000>;
+			phydev = <5>;
+		};
+
+		combophy5: combo-phy@fff5d000 {
+			compatible = "calxeda,hb-combophy";
+			#phy-cells = <1>;
+			reg = <0xfff5d000 0x1000>;
+			phydev = <31>;
+		};
 	};
 };
diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
index a454a13..1e57f10 100644
--- a/drivers/ata/Makefile
+++ b/drivers/ata/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_SATA_FSL)		+= sata_fsl.o
 obj-$(CONFIG_SATA_INIC162X)	+= sata_inic162x.o
 obj-$(CONFIG_SATA_SIL24)	+= sata_sil24.o
 obj-$(CONFIG_SATA_DWC)		+= sata_dwc_460ex.o
+obj-$(CONFIG_ARCH_HIGHBANK)	+= sata_highbank.o
 
 # SFF w/ custom DMA
 obj-$(CONFIG_PDC_ADMA)		+= pdc_adma.o
diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index 57eb1c2..8a9a702 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -364,4 +364,21 @@ static inline int ahci_nr_ports(u32 cap)
 	return (cap & 0x1f) + 1;
 }
 
+#ifdef CONFIG_ARCH_HIGHBANK
+void calxeda_cphy_override_lane(u8 sata_port);
+void calxeda_cphy_disable_overrides(u8 sata_port);
+int calxeda_initialize_phys(struct device *dev, void __iomem *addr);
+#else
+void calxeda_cphy_override_lane(u8 sata_port)
+{
+}
+void calxeda_cphy_disable_overrides(u8 sata_port)
+{
+}
+int calxeda_initialize_phys(struct device *dev, void __iomem *addr)
+{
+	return 0;
+}
+#endif
+
 #endif /* _AHCI_H */
diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c
index 09728e0..25abb08 100644
--- a/drivers/ata/ahci_platform.c
+++ b/drivers/ata/ahci_platform.c
@@ -19,6 +19,7 @@
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/device.h>
+#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/libata.h>
 #include <linux/ahci_platform.h>
@@ -27,6 +28,7 @@
 enum ahci_type {
 	AHCI,		/* standard platform ahci */
 	IMX53_AHCI,	/* ahci on i.mx53 */
+	CALXEDA_AHCI,
 	STRICT_AHCI,	/* delayed DMA engine start */
 };
 
@@ -38,6 +40,9 @@ static struct platform_device_id ahci_devtype[] = {
 		.name = "imx53-ahci",
 		.driver_data = IMX53_AHCI,
 	}, {
+		.name = "hb-ahci",
+		.driver_data = CALXEDA_AHCI,
+	}, {
 		.name = "strict-ahci",
 		.driver_data = STRICT_AHCI,
 	}, {
@@ -46,6 +51,52 @@ static struct platform_device_id ahci_devtype[] = {
 };
 MODULE_DEVICE_TABLE(platform, ahci_devtype);
 
+static int ahci_calxeda_hardreset(struct ata_link *link, unsigned int *class,
+				unsigned long deadline)
+{
+	const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
+	struct ata_port *ap = link->ap;
+	struct ahci_port_priv *pp = ap->private_data;
+	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
+	struct ata_taskfile tf;
+	bool online;
+	u32 sstatus;
+	int rc;
+	int retry = 10;
+
+	ahci_stop_engine(ap);
+
+	/* clear D2H reception area to properly wait for D2H FIS */
+	ata_tf_init(link->device, &tf);
+	tf.command = 0x80;
+	ata_tf_to_fis(&tf, 0, 0, d2h_fis);
+
+	do {
+		calxeda_cphy_disable_overrides(link->ap->port_no);
+		rc = sata_link_hardreset(link, timing, deadline, &online, NULL);
+		calxeda_cphy_override_lane(link->ap->port_no);
+
+		/* If the status is 1, we are connected, but the link did not
+		 * come up. So retry resetting the link again.
+		 */
+		if (sata_scr_read(link, SCR_STATUS, &sstatus))
+			break;
+		if (!(sstatus & 0x3))
+			break;
+	} while (!online && retry--);
+
+	ahci_start_engine(ap);
+
+	if (online)
+		*class = ahci_dev_classify(ap);
+
+	return rc;
+}
+
+static struct ata_port_operations ahci_calxeda_ops = {
+	.inherits		= &ahci_ops,
+	.hardreset		= ahci_calxeda_hardreset,
+};
 
 static const struct ata_port_info ahci_port_info[] = {
 	/* by features */
@@ -61,6 +112,12 @@ static const struct ata_port_info ahci_port_info[] = {
 		.udma_mask	= ATA_UDMA6,
 		.port_ops	= &ahci_pmp_retry_srst_ops,
 	},
+	[CALXEDA_AHCI] = {
+		.flags          = AHCI_FLAG_COMMON,
+		.pio_mask       = ATA_PIO4,
+		.udma_mask      = ATA_UDMA6,
+		.port_ops       = &ahci_calxeda_ops,
+	},
 	[STRICT_AHCI] = {
 		AHCI_HFLAGS	(AHCI_HFLAG_DELAY_ENGINE),
 		.flags		= AHCI_FLAG_COMMON,
@@ -70,25 +127,47 @@ static const struct ata_port_info ahci_port_info[] = {
 	},
 };
 
+static struct ahci_platform_data calxeda_platdata = {
+	.init = calxeda_initialize_phys,
+	.ata_port_info = &ahci_port_info[CALXEDA_AHCI],
+};
+
+
 static struct scsi_host_template ahci_platform_sht = {
 	AHCI_SHT("ahci_platform"),
 };
 
+static const struct of_device_id ahci_of_match[] = {
+	{ .compatible = "calxeda,hb-ahci",
+	  .data = &calxeda_platdata, },
+	{ .compatible = "snps,spear-ahci", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ahci_of_match);
+
 static int __init ahci_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 	const struct platform_device_id *id = platform_get_device_id(pdev);
-	struct ata_port_info pi = ahci_port_info[id ? id->driver_data : 0];
+	struct ata_port_info pi;
 	const struct ata_port_info *ppi[] = { &pi, NULL };
 	struct ahci_host_priv *hpriv;
 	struct ata_host *host;
 	struct resource *mem;
+	const struct of_device_id *match;
 	int irq;
 	int n_ports;
 	int i;
 	int rc;
 
+	match = of_match_device(ahci_of_match, dev);
+	if (match && match->data) {
+			pdata = (struct ahci_platform_data *) match->data;
+			pi = *((struct ata_port_info *) pdata->ata_port_info);
+	} else
+		pi = ahci_port_info[id ? id->driver_data : 0];
+
 	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!mem) {
 		dev_err(dev, "no mmio space\n");
@@ -276,13 +355,6 @@ static int ahci_resume(struct device *dev)
 
 SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_suspend, ahci_resume);
 
-static const struct of_device_id ahci_of_match[] = {
-	{ .compatible = "calxeda,hb-ahci", },
-	{ .compatible = "snps,spear-ahci", },
-	{},
-};
-MODULE_DEVICE_TABLE(of, ahci_of_match);
-
 static struct platform_driver ahci_driver = {
 	.remove = __devexit_p(ahci_remove),
 	.driver = {
diff --git a/drivers/ata/sata_highbank.c b/drivers/ata/sata_highbank.c
new file mode 100644
index 0000000..8266e4b
--- /dev/null
+++ b/drivers/ata/sata_highbank.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2012 Calxeda, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/ahci_platform.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include "ahci.h"
+
+#define CPHY_MAP(dev, addr) ((((dev) & 0x1f) << 7) | (((addr) >> 9) & 0x7f))
+#define CPHY_ADDR(addr) (((addr) & 0x1ff) << 2)
+#define SERDES_CR_CTL			0x80a0
+#define SERDES_CR_ADDR			0x80a1
+#define SERDES_CR_DATA			0x80a2
+#define CR_BUSY				0x0001
+#define CR_START			0x0001
+#define CR_WR_RDN			0x0002
+#define CPHY_RX_INPUT_STS		0x2002
+#define CPHY_SATA_OVERRIDE	 	0x4000
+#define CPHY_OVERRIDE			0x2005
+#define SPHY_LANE			0x100
+#define SPHY_HALF_RATE			0x0001
+#define CPHY_SATA_DPLL_MODE		0x0700
+#define CPHY_SATA_DPLL_SHIFT		8
+#define CPHY_SATA_DPLL_RESET		(1 << 11)
+#define CPHY_PHY_COUNT			6
+#define CPHY_LANE_COUNT			4
+#define CPHY_PORT_COUNT			(CPHY_PHY_COUNT * CPHY_LANE_COUNT)
+
+static DEFINE_SPINLOCK(cphy_lock);
+/* Each of the 6 phys can have up to 4 sata ports attached to i. Map 0-based
+ * sata ports to their phys and then to their lanes within the phys
+ */
+struct phy_lane_info {
+	void __iomem *phy_base;
+	u8 lane_mapping;
+	u8 phy_devs;
+};
+static struct phy_lane_info port_data[CPHY_PORT_COUNT];
+
+static u32 __combo_phy_reg_read(u8 sata_port, u32 addr)
+{
+	u32 data;
+	u8 dev = port_data[sata_port].phy_devs;
+	spin_lock(&cphy_lock);
+	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
+	data = readl(port_data[sata_port].phy_base + CPHY_ADDR(addr));
+	spin_unlock(&cphy_lock);
+	return data;
+}
+
+static void __combo_phy_reg_write(u8 sata_port, u32 addr, u32 data)
+{
+	u8 dev = port_data[sata_port].phy_devs;
+	spin_lock(&cphy_lock);
+	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
+	writel(data, port_data[sata_port].phy_base + CPHY_ADDR(addr));
+	spin_unlock(&cphy_lock);
+}
+
+static void combo_phy_wait_for_ready(u8 sata_port) 
+{
+	while (__combo_phy_reg_read(sata_port, SERDES_CR_CTL) & CR_BUSY)
+		udelay(5);
+}
+
+static u32 combo_phy_read(u8 sata_port, u32 addr)
+{
+	combo_phy_wait_for_ready(sata_port);
+	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
+	udelay(25);
+	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_START);
+	udelay(25);
+	combo_phy_wait_for_ready(sata_port);
+	return __combo_phy_reg_read(sata_port, SERDES_CR_DATA);
+}
+
+static void combo_phy_write(u8 sata_port, u32 addr, u32 data)
+{
+	combo_phy_wait_for_ready(sata_port);
+	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
+	udelay(25);
+	__combo_phy_reg_write(sata_port, SERDES_CR_DATA, data);
+	udelay(25);
+	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_WR_RDN | CR_START);
+}
+
+void calxeda_cphy_disable_overrides(u8 sata_port)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp;
+	if (unlikely(port_data[sata_port].phy_base == NULL))
+		return;
+	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
+	tmp &= ~CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+}
+EXPORT_SYMBOL(calxeda_cphy_disable_overrides);
+
+static void cphy_override_rx_mode(u8 sata_port, u32 val)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp;
+	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
+	tmp &= ~CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp |= CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp &= ~CPHY_SATA_DPLL_MODE;
+	tmp |= val << CPHY_SATA_DPLL_SHIFT;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp |= CPHY_SATA_DPLL_RESET;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp &= ~CPHY_SATA_DPLL_RESET;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	msleep(15);
+}
+
+void calxeda_cphy_override_lane(u8 sata_port)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp, k = 0;
+	if (unlikely(port_data[sata_port].phy_base == NULL))
+		return;
+	do {
+		tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS +
+						lane * SPHY_LANE);
+	} while ((tmp & SPHY_HALF_RATE) && (k++ < 1000));
+	cphy_override_rx_mode(sata_port, 3);
+}
+EXPORT_SYMBOL(calxeda_cphy_override_lane);
+
+int calxeda_initialize_phys(struct device *dev, void __iomem *addr)
+{
+	struct device_node *sata_node;
+	int phy_count = 0, phy, port = 0;
+	void __iomem *cphy_base[CPHY_PHY_COUNT];
+	struct device_node *phy_nodes[CPHY_PHY_COUNT];
+	memset(port_data, 0, sizeof(struct phy_lane_info) * CPHY_PORT_COUNT);
+	memset(phy_nodes, 0, sizeof(struct device_node*) * CPHY_PHY_COUNT);
+
+	for_each_compatible_node(sata_node, NULL, "calxeda,hb-ahci") {
+		do {
+			u32 tmp;
+			struct of_phandle_args phy_data;
+			if (of_parse_phandle_with_args(sata_node,
+					"calxeda,port-phys", "#phy-cells",
+					port, &phy_data))
+				break;
+			for (phy = 0; phy < phy_count; phy++) {
+				if (phy_nodes[phy] == phy_data.np)
+					break;
+			}
+			if (phy_nodes[phy] == NULL) {
+				phy_nodes[phy] = phy_data.np;
+				cphy_base[phy] = of_iomap(phy_nodes[phy], 0);
+				if (cphy_base[phy] == NULL)
+					return 0;
+				phy_count += 1;
+			}
+			port_data[port].lane_mapping = phy_data.args[0];
+			of_property_read_u32(phy_nodes[phy], "phydev", &tmp);
+			port_data[port].phy_devs = tmp;
+			port_data[port].phy_base = cphy_base[phy];
+			of_node_put(phy_data.np);
+			port += 1;
+		} while (port < CPHY_PORT_COUNT);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(calxeda_initialize_phys);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr
  2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
@ 2012-08-17 15:33   ` Rob Herring
  2012-08-17 17:25   ` Jeff Garzik
  2012-09-06 21:03   ` [PATCH v2] ata: add platform driver for Calxeda AHCI controller Mark Langsdorf
  2 siblings, 0 replies; 8+ messages in thread
From: Rob Herring @ 2012-08-17 15:33 UTC (permalink / raw
  To: Mark Langsdorf
  Cc: jgarzik, linux-ide, probinson, jonathon, dmarlin,
	devicetree-discuss@lists.ozlabs.org, linux-kernel@vger.kernel.org

+devicetree-discuss and lkml

On 08/17/2012 09:51 AM, Mark Langsdorf wrote:
> Calxeda highbank SATA phy has intermittent problems bringing up a link
> with Gen3 drives. Retrying the phy hard reset can work-around this issue,
> but each reset also disables spread spectrum support. The reset function
> also needs to reprogram the phy to enable spread spectrum support.
> 
> Signed-off-by: Mark Langsdorf <mark.langsdorf@calxeda.com>
> Signed-off-by: Rob Herring <rob.herring@calxeda.com>
> ---
>  .../devicetree/bindings/arm/calxeda/combophy.txt   |   17 ++
>  .../devicetree/bindings/ata/ahci-platform.txt      |    8 +
>  arch/arm/boot/dts/highbank.dts                     |   17 ++

New bindings need to go to devicetree-discuss/lkml...

One comment below

>  drivers/ata/Makefile                               |    1 +
>  drivers/ata/ahci.h                                 |   17 ++
>  drivers/ata/ahci_platform.c                        |   88 ++++++++-
>  drivers/ata/sata_highbank.c                        |  199 ++++++++++++++++++++
>  7 files changed, 339 insertions(+), 8 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/arm/calxeda/combophy.txt
>  create mode 100644 drivers/ata/sata_highbank.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/calxeda/combophy.txt b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
> new file mode 100644
> index 0000000..6622bdb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
> @@ -0,0 +1,17 @@
> +Calxeda Highbank Combination Phys for SATA
> +
> +Properties:
> +- compatible : Should be "calxeda,hb-combophy"
> +- #phy-cells: Should be 1.
> +- reg : Address and size for Combination Phy registers.
> +- phydev: device ID for programming the combophy.
> +
> +Example:
> +
> +	combophy5: combo-phy@fff5d000 {
> +		compatible = "calxeda,hb-combophy";
> +		#phy-cells = <1>;
> +		reg = <0xfff5d000 0x1000>;
> +		phydev = <31>;
> +	};
> +
> diff --git a/Documentation/devicetree/bindings/ata/ahci-platform.txt b/Documentation/devicetree/bindings/ata/ahci-platform.txt
> index 8bb8a76..147c1f6 100644
> --- a/Documentation/devicetree/bindings/ata/ahci-platform.txt
> +++ b/Documentation/devicetree/bindings/ata/ahci-platform.txt
> @@ -8,9 +8,17 @@ Required properties:
>  - interrupts        : <interrupt mapping for SATA IRQ>
>  - reg               : <registers mapping>
>  
> +Optional properties:
> +- calxeda,port-phys: phandle-combophy and lane assignment, which maps each
> +			SATA port to a combophy and a lane within that
> +			combophy
> +
>  Example:
>          sata@ffe08000 {
>  		compatible = "calxeda,hb-ahci";
>                  reg = <0xffe08000 0x1000>;
>                  interrupts = <115>;
> +		calxeda,port-phys = <&combophy5 0 &combophy0 0 &combophy0 1
> +					&combophy0 2 &combophy0 3>;
> +
>          };
> diff --git a/arch/arm/boot/dts/highbank.dts b/arch/arm/boot/dts/highbank.dts
> index 9fecf1a..5204cf7 100644
> --- a/arch/arm/boot/dts/highbank.dts
> +++ b/arch/arm/boot/dts/highbank.dts
> @@ -121,6 +121,9 @@
>  			compatible = "calxeda,hb-ahci";
>  			reg = <0xffe08000 0x10000>;
>  			interrupts = <0 83 4>;
> +			calxeda,port-phys = <&combophy5 0 &combophy0 0
> +					     &combophy0 1 &combophy0 2
> +					     &combophy0 3>;
>  		};
>  
>  		sdhci@ffe0e000 {
> @@ -306,5 +309,19 @@
>  			reg = <0xfff51000 0x1000>;
>  			interrupts = <0 80 4  0 81 4  0 82 4>;
>  		};
> +
> +		combophy0: combo-phy@fff58000 {
> +			compatible = "calxeda,hb-combophy";
> +			#phy-cells = <1>;
> +			reg = <0xfff58000 0x1000>;
> +			phydev = <5>;
> +		};
> +
> +		combophy5: combo-phy@fff5d000 {
> +			compatible = "calxeda,hb-combophy";
> +			#phy-cells = <1>;
> +			reg = <0xfff5d000 0x1000>;
> +			phydev = <31>;
> +		};
>  	};
>  };
> diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
> index a454a13..1e57f10 100644
> --- a/drivers/ata/Makefile
> +++ b/drivers/ata/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_SATA_FSL)		+= sata_fsl.o
>  obj-$(CONFIG_SATA_INIC162X)	+= sata_inic162x.o
>  obj-$(CONFIG_SATA_SIL24)	+= sata_sil24.o
>  obj-$(CONFIG_SATA_DWC)		+= sata_dwc_460ex.o
> +obj-$(CONFIG_ARCH_HIGHBANK)	+= sata_highbank.o
>  
>  # SFF w/ custom DMA
>  obj-$(CONFIG_PDC_ADMA)		+= pdc_adma.o
> diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
> index 57eb1c2..8a9a702 100644
> --- a/drivers/ata/ahci.h
> +++ b/drivers/ata/ahci.h
> @@ -364,4 +364,21 @@ static inline int ahci_nr_ports(u32 cap)
>  	return (cap & 0x1f) + 1;
>  }
>  
> +#ifdef CONFIG_ARCH_HIGHBANK
> +void calxeda_cphy_override_lane(u8 sata_port);
> +void calxeda_cphy_disable_overrides(u8 sata_port);
> +int calxeda_initialize_phys(struct device *dev, void __iomem *addr);
> +#else
> +void calxeda_cphy_override_lane(u8 sata_port)
> +{
> +}
> +void calxeda_cphy_disable_overrides(u8 sata_port)
> +{
> +}
> +int calxeda_initialize_phys(struct device *dev, void __iomem *addr)
> +{
> +	return 0;
> +}
> +#endif
> +
>  #endif /* _AHCI_H */
> diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c
> index 09728e0..25abb08 100644
> --- a/drivers/ata/ahci_platform.c
> +++ b/drivers/ata/ahci_platform.c
> @@ -19,6 +19,7 @@
>  #include <linux/init.h>
>  #include <linux/interrupt.h>
>  #include <linux/device.h>
> +#include <linux/of_device.h>
>  #include <linux/platform_device.h>
>  #include <linux/libata.h>
>  #include <linux/ahci_platform.h>
> @@ -27,6 +28,7 @@
>  enum ahci_type {
>  	AHCI,		/* standard platform ahci */
>  	IMX53_AHCI,	/* ahci on i.mx53 */
> +	CALXEDA_AHCI,
>  	STRICT_AHCI,	/* delayed DMA engine start */
>  };
>  
> @@ -38,6 +40,9 @@ static struct platform_device_id ahci_devtype[] = {
>  		.name = "imx53-ahci",
>  		.driver_data = IMX53_AHCI,
>  	}, {
> +		.name = "hb-ahci",
> +		.driver_data = CALXEDA_AHCI,
> +	}, {
>  		.name = "strict-ahci",
>  		.driver_data = STRICT_AHCI,
>  	}, {
> @@ -46,6 +51,52 @@ static struct platform_device_id ahci_devtype[] = {
>  };
>  MODULE_DEVICE_TABLE(platform, ahci_devtype);
>  
> +static int ahci_calxeda_hardreset(struct ata_link *link, unsigned int *class,
> +				unsigned long deadline)
> +{
> +	const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
> +	struct ata_port *ap = link->ap;
> +	struct ahci_port_priv *pp = ap->private_data;
> +	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
> +	struct ata_taskfile tf;
> +	bool online;
> +	u32 sstatus;
> +	int rc;
> +	int retry = 10;
> +
> +	ahci_stop_engine(ap);
> +
> +	/* clear D2H reception area to properly wait for D2H FIS */
> +	ata_tf_init(link->device, &tf);
> +	tf.command = 0x80;
> +	ata_tf_to_fis(&tf, 0, 0, d2h_fis);
> +
> +	do {
> +		calxeda_cphy_disable_overrides(link->ap->port_no);
> +		rc = sata_link_hardreset(link, timing, deadline, &online, NULL);
> +		calxeda_cphy_override_lane(link->ap->port_no);
> +
> +		/* If the status is 1, we are connected, but the link did not
> +		 * come up. So retry resetting the link again.
> +		 */
> +		if (sata_scr_read(link, SCR_STATUS, &sstatus))
> +			break;
> +		if (!(sstatus & 0x3))
> +			break;
> +	} while (!online && retry--);
> +
> +	ahci_start_engine(ap);
> +
> +	if (online)
> +		*class = ahci_dev_classify(ap);
> +
> +	return rc;
> +}
> +
> +static struct ata_port_operations ahci_calxeda_ops = {
> +	.inherits		= &ahci_ops,
> +	.hardreset		= ahci_calxeda_hardreset,
> +};
>  
>  static const struct ata_port_info ahci_port_info[] = {
>  	/* by features */
> @@ -61,6 +112,12 @@ static const struct ata_port_info ahci_port_info[] = {
>  		.udma_mask	= ATA_UDMA6,
>  		.port_ops	= &ahci_pmp_retry_srst_ops,
>  	},
> +	[CALXEDA_AHCI] = {
> +		.flags          = AHCI_FLAG_COMMON,
> +		.pio_mask       = ATA_PIO4,
> +		.udma_mask      = ATA_UDMA6,
> +		.port_ops       = &ahci_calxeda_ops,
> +	},
>  	[STRICT_AHCI] = {
>  		AHCI_HFLAGS	(AHCI_HFLAG_DELAY_ENGINE),
>  		.flags		= AHCI_FLAG_COMMON,
> @@ -70,25 +127,47 @@ static const struct ata_port_info ahci_port_info[] = {
>  	},
>  };
>  
> +static struct ahci_platform_data calxeda_platdata = {
> +	.init = calxeda_initialize_phys,
> +	.ata_port_info = &ahci_port_info[CALXEDA_AHCI],
> +};
> +
> +
>  static struct scsi_host_template ahci_platform_sht = {
>  	AHCI_SHT("ahci_platform"),
>  };
>  
> +static const struct of_device_id ahci_of_match[] = {
> +	{ .compatible = "calxeda,hb-ahci",
> +	  .data = &calxeda_platdata, },
> +	{ .compatible = "snps,spear-ahci", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ahci_of_match);
> +
>  static int __init ahci_probe(struct platform_device *pdev)
>  {
>  	struct device *dev = &pdev->dev;
>  	struct ahci_platform_data *pdata = dev_get_platdata(dev);
>  	const struct platform_device_id *id = platform_get_device_id(pdev);
> -	struct ata_port_info pi = ahci_port_info[id ? id->driver_data : 0];
> +	struct ata_port_info pi;
>  	const struct ata_port_info *ppi[] = { &pi, NULL };
>  	struct ahci_host_priv *hpriv;
>  	struct ata_host *host;
>  	struct resource *mem;
> +	const struct of_device_id *match;
>  	int irq;
>  	int n_ports;
>  	int i;
>  	int rc;
>  
> +	match = of_match_device(ahci_of_match, dev);
> +	if (match && match->data) {
> +			pdata = (struct ahci_platform_data *) match->data;
> +			pi = *((struct ata_port_info *) pdata->ata_port_info);
> +	} else
> +		pi = ahci_port_info[id ? id->driver_data : 0];
> +
>  	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	if (!mem) {
>  		dev_err(dev, "no mmio space\n");
> @@ -276,13 +355,6 @@ static int ahci_resume(struct device *dev)
>  
>  SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_suspend, ahci_resume);
>  
> -static const struct of_device_id ahci_of_match[] = {
> -	{ .compatible = "calxeda,hb-ahci", },
> -	{ .compatible = "snps,spear-ahci", },
> -	{},
> -};
> -MODULE_DEVICE_TABLE(of, ahci_of_match);
> -
>  static struct platform_driver ahci_driver = {
>  	.remove = __devexit_p(ahci_remove),
>  	.driver = {
> diff --git a/drivers/ata/sata_highbank.c b/drivers/ata/sata_highbank.c
> new file mode 100644
> index 0000000..8266e4b
> --- /dev/null
> +++ b/drivers/ata/sata_highbank.c
> @@ -0,0 +1,199 @@
> +/*
> + * Copyright 2012 Calxeda, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
> + */
> +#include <linux/kernel.h>
> +#include <linux/gfp.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/spinlock.h>
> +#include <linux/device.h>
> +#include <linux/of_device.h>
> +#include <linux/of_address.h>
> +#include <linux/ahci_platform.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/export.h>
> +#include "ahci.h"
> +
> +#define CPHY_MAP(dev, addr) ((((dev) & 0x1f) << 7) | (((addr) >> 9) & 0x7f))
> +#define CPHY_ADDR(addr) (((addr) & 0x1ff) << 2)
> +#define SERDES_CR_CTL			0x80a0
> +#define SERDES_CR_ADDR			0x80a1
> +#define SERDES_CR_DATA			0x80a2
> +#define CR_BUSY				0x0001
> +#define CR_START			0x0001
> +#define CR_WR_RDN			0x0002
> +#define CPHY_RX_INPUT_STS		0x2002
> +#define CPHY_SATA_OVERRIDE	 	0x4000
> +#define CPHY_OVERRIDE			0x2005
> +#define SPHY_LANE			0x100
> +#define SPHY_HALF_RATE			0x0001
> +#define CPHY_SATA_DPLL_MODE		0x0700
> +#define CPHY_SATA_DPLL_SHIFT		8
> +#define CPHY_SATA_DPLL_RESET		(1 << 11)
> +#define CPHY_PHY_COUNT			6
> +#define CPHY_LANE_COUNT			4
> +#define CPHY_PORT_COUNT			(CPHY_PHY_COUNT * CPHY_LANE_COUNT)
> +
> +static DEFINE_SPINLOCK(cphy_lock);
> +/* Each of the 6 phys can have up to 4 sata ports attached to i. Map 0-based
> + * sata ports to their phys and then to their lanes within the phys
> + */
> +struct phy_lane_info {
> +	void __iomem *phy_base;
> +	u8 lane_mapping;
> +	u8 phy_devs;
> +};
> +static struct phy_lane_info port_data[CPHY_PORT_COUNT];
> +
> +static u32 __combo_phy_reg_read(u8 sata_port, u32 addr)
> +{
> +	u32 data;
> +	u8 dev = port_data[sata_port].phy_devs;
> +	spin_lock(&cphy_lock);
> +	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
> +	data = readl(port_data[sata_port].phy_base + CPHY_ADDR(addr));
> +	spin_unlock(&cphy_lock);
> +	return data;
> +}
> +
> +static void __combo_phy_reg_write(u8 sata_port, u32 addr, u32 data)
> +{
> +	u8 dev = port_data[sata_port].phy_devs;
> +	spin_lock(&cphy_lock);
> +	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
> +	writel(data, port_data[sata_port].phy_base + CPHY_ADDR(addr));
> +	spin_unlock(&cphy_lock);
> +}
> +
> +static void combo_phy_wait_for_ready(u8 sata_port) 
> +{
> +	while (__combo_phy_reg_read(sata_port, SERDES_CR_CTL) & CR_BUSY)
> +		udelay(5);
> +}
> +
> +static u32 combo_phy_read(u8 sata_port, u32 addr)
> +{
> +	combo_phy_wait_for_ready(sata_port);
> +	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
> +	udelay(25);
> +	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_START);
> +	udelay(25);
> +	combo_phy_wait_for_ready(sata_port);
> +	return __combo_phy_reg_read(sata_port, SERDES_CR_DATA);
> +}
> +
> +static void combo_phy_write(u8 sata_port, u32 addr, u32 data)
> +{
> +	combo_phy_wait_for_ready(sata_port);
> +	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
> +	udelay(25);
> +	__combo_phy_reg_write(sata_port, SERDES_CR_DATA, data);
> +	udelay(25);
> +	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_WR_RDN | CR_START);
> +}
> +
> +void calxeda_cphy_disable_overrides(u8 sata_port)
> +{
> +	u8 lane = port_data[sata_port].lane_mapping;
> +	u32 tmp;
> +	if (unlikely(port_data[sata_port].phy_base == NULL))
> +		return;
> +	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
> +	tmp &= ~CPHY_SATA_OVERRIDE;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +}
> +EXPORT_SYMBOL(calxeda_cphy_disable_overrides);
> +
> +static void cphy_override_rx_mode(u8 sata_port, u32 val)
> +{
> +	u8 lane = port_data[sata_port].lane_mapping;
> +	u32 tmp;
> +	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
> +	tmp &= ~CPHY_SATA_OVERRIDE;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +
> +	tmp |= CPHY_SATA_OVERRIDE;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +
> +	tmp &= ~CPHY_SATA_DPLL_MODE;
> +	tmp |= val << CPHY_SATA_DPLL_SHIFT;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +
> +	tmp |= CPHY_SATA_DPLL_RESET;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +
> +	tmp &= ~CPHY_SATA_DPLL_RESET;
> +	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
> +
> +	msleep(15);
> +}
> +
> +void calxeda_cphy_override_lane(u8 sata_port)
> +{
> +	u8 lane = port_data[sata_port].lane_mapping;
> +	u32 tmp, k = 0;
> +	if (unlikely(port_data[sata_port].phy_base == NULL))
> +		return;
> +	do {
> +		tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS +
> +						lane * SPHY_LANE);
> +	} while ((tmp & SPHY_HALF_RATE) && (k++ < 1000));
> +	cphy_override_rx_mode(sata_port, 3);
> +}
> +EXPORT_SYMBOL(calxeda_cphy_override_lane);
> +
> +int calxeda_initialize_phys(struct device *dev, void __iomem *addr)
> +{
> +	struct device_node *sata_node;
> +	int phy_count = 0, phy, port = 0;
> +	void __iomem *cphy_base[CPHY_PHY_COUNT];
> +	struct device_node *phy_nodes[CPHY_PHY_COUNT];
> +	memset(port_data, 0, sizeof(struct phy_lane_info) * CPHY_PORT_COUNT);
> +	memset(phy_nodes, 0, sizeof(struct device_node*) * CPHY_PHY_COUNT);
> +
> +	for_each_compatible_node(sata_node, NULL, "calxeda,hb-ahci") {

This is not needed. This function is called for each controller already.
You should already have dev->of_node set.

Rob

> +		do {
> +			u32 tmp;
> +			struct of_phandle_args phy_data;
> +			if (of_parse_phandle_with_args(sata_node,
> +					"calxeda,port-phys", "#phy-cells",
> +					port, &phy_data))
> +				break;
> +			for (phy = 0; phy < phy_count; phy++) {
> +				if (phy_nodes[phy] == phy_data.np)
> +					break;
> +			}
> +			if (phy_nodes[phy] == NULL) {
> +				phy_nodes[phy] = phy_data.np;
> +				cphy_base[phy] = of_iomap(phy_nodes[phy], 0);
> +				if (cphy_base[phy] == NULL)
> +					return 0;
> +				phy_count += 1;
> +			}
> +			port_data[port].lane_mapping = phy_data.args[0];
> +			of_property_read_u32(phy_nodes[phy], "phydev", &tmp);
> +			port_data[port].phy_devs = tmp;
> +			port_data[port].phy_base = cphy_base[phy];
> +			of_node_put(phy_data.np);
> +			port += 1;
> +		} while (port < CPHY_PORT_COUNT);
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL(calxeda_initialize_phys);
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr
  2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
  2012-08-17 15:33   ` Rob Herring
@ 2012-08-17 17:25   ` Jeff Garzik
  2012-08-17 22:25     ` Mark Langsdorf
  2012-09-06 21:03   ` [PATCH v2] ata: add platform driver for Calxeda AHCI controller Mark Langsdorf
  2 siblings, 1 reply; 8+ messages in thread
From: Jeff Garzik @ 2012-08-17 17:25 UTC (permalink / raw
  To: Mark Langsdorf; +Cc: linux-ide, probinson, jonathon, dmarlin, Rob Herring

On 08/17/2012 10:51 AM, Mark Langsdorf wrote:
> Calxeda highbank SATA phy has intermittent problems bringing up a link
> with Gen3 drives. Retrying the phy hard reset can work-around this issue,
> but each reset also disables spread spectrum support. The reset function
> also needs to reprogram the phy to enable spread spectrum support.
>
> Signed-off-by: Mark Langsdorf <mark.langsdorf@calxeda.com>
> Signed-off-by: Rob Herring <rob.herring@calxeda.com>
> ---
>   .../devicetree/bindings/arm/calxeda/combophy.txt   |   17 ++
>   .../devicetree/bindings/ata/ahci-platform.txt      |    8 +
>   arch/arm/boot/dts/highbank.dts                     |   17 ++
>   drivers/ata/Makefile                               |    1 +
>   drivers/ata/ahci.h                                 |   17 ++
>   drivers/ata/ahci_platform.c                        |   88 ++++++++-
>   drivers/ata/sata_highbank.c                        |  199 ++++++++++++++++++++
>   7 files changed, 339 insertions(+), 8 deletions(-)
>   create mode 100644 Documentation/devicetree/bindings/arm/calxeda/combophy.txt
>   create mode 100644 drivers/ata/sata_highbank.c

This is an odd arrangement:

1) creates a sata_foo.c, which is not a driver but a library.

2) unconditionally burdens all ahci_platform users with Calxeda-specific 
code.

3) #ifndef CONFIG_ARCH_HIGHBANK code wrappers should be 'static inline'

And the biggest issue...

4) if you need all this code for workarounds, just create your own 
libahci driver at that point.  make sata_highbank a real driver.



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] ahci: un-staticize ahci_dev_classify
  2012-08-17 14:51 ` [PATCH 1/2] ahci: un-staticize ahci_dev_classify Mark Langsdorf
@ 2012-08-17 17:27   ` Jeff Garzik
  0 siblings, 0 replies; 8+ messages in thread
From: Jeff Garzik @ 2012-08-17 17:27 UTC (permalink / raw
  To: Mark Langsdorf; +Cc: linux-ide, probinson, jonathon, dmarlin, Rob Herring

On 08/17/2012 10:51 AM, Mark Langsdorf wrote:
> From: Rob Herring <rob.herring@calxeda.com>
>
> Make ahci_dev_classify available to the ahci platform driver for custom
> hard reset function.
>
> Signed-off-by: Rob Herring <rob.herring@calxeda.com>
> ---
>   drivers/ata/ahci.h    |    1 +
>   drivers/ata/libahci.c |    3 ++-
>   2 files changed, 3 insertions(+), 1 deletion(-)

applied




^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr
  2012-08-17 17:25   ` Jeff Garzik
@ 2012-08-17 22:25     ` Mark Langsdorf
  0 siblings, 0 replies; 8+ messages in thread
From: Mark Langsdorf @ 2012-08-17 22:25 UTC (permalink / raw
  To: Jeff Garzik
  Cc: linux-ide@vger.kernel.org, probinson@gmail.com,
	jonathon@jonmasters.org, dmarlin@redhat.com, Rob Herring

On 08/17/2012 12:25 PM, Jeff Garzik wrote:
> On 08/17/2012 10:51 AM, Mark Langsdorf wrote:
>> Calxeda highbank SATA phy has intermittent problems bringing up a link
>> with Gen3 drives. Retrying the phy hard reset can work-around this issue,
>> but each reset also disables spread spectrum support. The reset function
>> also needs to reprogram the phy to enable spread spectrum support.
>>
>> Signed-off-by: Mark Langsdorf <mark.langsdorf@calxeda.com>
>> Signed-off-by: Rob Herring <rob.herring@calxeda.com>
>> ---
>>   .../devicetree/bindings/arm/calxeda/combophy.txt   |   17 ++
>>   .../devicetree/bindings/ata/ahci-platform.txt      |    8 +
>>   arch/arm/boot/dts/highbank.dts                     |   17 ++
>>   drivers/ata/Makefile                               |    1 +
>>   drivers/ata/ahci.h                                 |   17 ++
>>   drivers/ata/ahci_platform.c                        |   88 ++++++++-
>>   drivers/ata/sata_highbank.c                        |  199 ++++++++++++++++++++
>>   7 files changed, 339 insertions(+), 8 deletions(-)
>>   create mode 100644 Documentation/devicetree/bindings/arm/calxeda/combophy.txt
>>   create mode 100644 drivers/ata/sata_highbank.c
>
> And the biggest issue...
> 
> 4) if you need all this code for workarounds, just create your own 
> libahci driver at that point.  make sata_highbank a real driver.

The frustrating bit is that we're nominally an AHCI compatible
controller except for the reset function and some kind of initialization
for the combophy iomem regions. The ata_port_operations already lets me
override just the reset function, and the ahci_probe() function has a
hook for the iomem initialization.

Is there any way I can inherit most of the existing ahci_platform
functions for suspend, resume, etc.? Because otherwise I'm just going to
duplicate the code and that feels unsatisfactory and seems unlikely to
get accepted anyway.

Would it be permissible to remove the statics from ahci_suspend,
ahci_resume, and ahci_remove so I could use them in my libahci driver?

--Mark Langsdorf
Calxeda, Inc.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2] ata: add platform driver for Calxeda AHCI controller
  2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
  2012-08-17 15:33   ` Rob Herring
  2012-08-17 17:25   ` Jeff Garzik
@ 2012-09-06 21:03   ` Mark Langsdorf
  2 siblings, 0 replies; 8+ messages in thread
From: Mark Langsdorf @ 2012-09-06 21:03 UTC (permalink / raw
  To: jgarzik
  Cc: linux-ide, jcm, dmarlin, devicetree-discuss, linux-kernel,
	Mark Langsdorf, Rob Herring

Calxeda highbank SATA phy has intermittent problems bringing up a link
with Gen3 drives. Retrying the phy hard reset can work-around this issue,
but each reset also disables spread spectrum support. The reset function
also needs to reprogram the phy to enable spread spectrum support.

Create a new driver based on ahci_platform to support the Calxeda Highbank
SATA controller.

Signed-off-by: Mark Langsdorf <mark.langsdorf@calxeda.com>
Signed-off-by: Rob Herring <rob.herring@calxeda.com>
---
Changes from v1
used to be the second patch in a 2 part series; first patch was accepted
        and this patch's name was changed from "ahci_platform: add custom
        hard reset for Calxeda ahci ctrlr"
Refactored all of sata_highbank so it is a stand-alone driver based on
        ahci_platform
Driver can now be loaded as a module
        Driver is no longer auto-selected as part of ARCH_HIGHBANK

 .../devicetree/bindings/arm/calxeda/combophy.txt   |  17 +
 .../devicetree/bindings/ata/ahci-platform.txt      |   8 +
 arch/arm/boot/dts/highbank.dts                     |  17 +
 drivers/ata/Kconfig                                |   8 +
 drivers/ata/Makefile                               |   1 +
 drivers/ata/ahci_platform.c                        |   1 -
 drivers/ata/sata_highbank.c                        | 450 +++++++++++++++++++++
 7 files changed, 501 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/arm/calxeda/combophy.txt
 create mode 100644 drivers/ata/sata_highbank.c

diff --git a/Documentation/devicetree/bindings/arm/calxeda/combophy.txt b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
new file mode 100644
index 0000000..6622bdb
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/calxeda/combophy.txt
@@ -0,0 +1,17 @@
+Calxeda Highbank Combination Phys for SATA
+
+Properties:
+- compatible : Should be "calxeda,hb-combophy"
+- #phy-cells: Should be 1.
+- reg : Address and size for Combination Phy registers.
+- phydev: device ID for programming the combophy.
+
+Example:
+
+	combophy5: combo-phy@fff5d000 {
+		compatible = "calxeda,hb-combophy";
+		#phy-cells = <1>;
+		reg = <0xfff5d000 0x1000>;
+		phydev = <31>;
+	};
+
diff --git a/Documentation/devicetree/bindings/ata/ahci-platform.txt b/Documentation/devicetree/bindings/ata/ahci-platform.txt
index 8bb8a76..147c1f6 100644
--- a/Documentation/devicetree/bindings/ata/ahci-platform.txt
+++ b/Documentation/devicetree/bindings/ata/ahci-platform.txt
@@ -8,9 +8,17 @@ Required properties:
 - interrupts        : <interrupt mapping for SATA IRQ>
 - reg               : <registers mapping>
 
+Optional properties:
+- calxeda,port-phys: phandle-combophy and lane assignment, which maps each
+			SATA port to a combophy and a lane within that
+			combophy
+
 Example:
         sata@ffe08000 {
 		compatible = "calxeda,hb-ahci";
                 reg = <0xffe08000 0x1000>;
                 interrupts = <115>;
+		calxeda,port-phys = <&combophy5 0 &combophy0 0 &combophy0 1
+					&combophy0 2 &combophy0 3>;
+
         };
diff --git a/arch/arm/boot/dts/highbank.dts b/arch/arm/boot/dts/highbank.dts
index 9fecf1a..5204cf7 100644
--- a/arch/arm/boot/dts/highbank.dts
+++ b/arch/arm/boot/dts/highbank.dts
@@ -121,6 +121,9 @@
 			compatible = "calxeda,hb-ahci";
 			reg = <0xffe08000 0x10000>;
 			interrupts = <0 83 4>;
+			calxeda,port-phys = <&combophy5 0 &combophy0 0
+					     &combophy0 1 &combophy0 2
+					     &combophy0 3>;
 		};
 
 		sdhci@ffe0e000 {
@@ -306,5 +309,19 @@
 			reg = <0xfff51000 0x1000>;
 			interrupts = <0 80 4  0 81 4  0 82 4>;
 		};
+
+		combophy0: combo-phy@fff58000 {
+			compatible = "calxeda,hb-combophy";
+			#phy-cells = <1>;
+			reg = <0xfff58000 0x1000>;
+			phydev = <5>;
+		};
+
+		combophy5: combo-phy@fff5d000 {
+			compatible = "calxeda,hb-combophy";
+			#phy-cells = <1>;
+			reg = <0xfff5d000 0x1000>;
+			phydev = <31>;
+		};
 	};
 };
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig
index 27cecd3..e08d322 100644
--- a/drivers/ata/Kconfig
+++ b/drivers/ata/Kconfig
@@ -214,6 +214,14 @@ config SATA_DWC_VDEBUG
 	help
 	  This option enables the taskfile dumping and NCQ debugging.
 
+config SATA_HIGHBANK
+	tristate "Calxeda Highbank SATA support"
+	help
+	  This option enables support for the Calxeda Highbank SoC's
+	  onboard SATA.
+
+	  If unsure, say N.
+
 config SATA_MV
 	tristate "Marvell SATA support"
 	help
diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
index a454a13..8b384f1 100644
--- a/drivers/ata/Makefile
+++ b/drivers/ata/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_SATA_FSL)		+= sata_fsl.o
 obj-$(CONFIG_SATA_INIC162X)	+= sata_inic162x.o
 obj-$(CONFIG_SATA_SIL24)	+= sata_sil24.o
 obj-$(CONFIG_SATA_DWC)		+= sata_dwc_460ex.o
+obj-$(CONFIG_SATA_HIGHBANK)	+= sata_highbank.o
 
 # SFF w/ custom DMA
 obj-$(CONFIG_PDC_ADMA)		+= pdc_adma.o
diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c
index 09728e0..dc187c7 100644
--- a/drivers/ata/ahci_platform.c
+++ b/drivers/ata/ahci_platform.c
@@ -277,7 +277,6 @@ static int ahci_resume(struct device *dev)
 SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_suspend, ahci_resume);
 
 static const struct of_device_id ahci_of_match[] = {
-	{ .compatible = "calxeda,hb-ahci", },
 	{ .compatible = "snps,spear-ahci", },
 	{},
 };
diff --git a/drivers/ata/sata_highbank.c b/drivers/ata/sata_highbank.c
new file mode 100644
index 0000000..0d7c4c2
--- /dev/null
+++ b/drivers/ata/sata_highbank.c
@@ -0,0 +1,450 @@
+/*
+ * Calxeda Highbank AHCI SATA platform driver
+ * Copyright 2012 Calxeda, Inc.
+ *
+ * based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/libata.h>
+#include <linux/ahci_platform.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include "ahci.h"
+
+#define CPHY_MAP(dev, addr) ((((dev) & 0x1f) << 7) | (((addr) >> 9) & 0x7f))
+#define CPHY_ADDR(addr) (((addr) & 0x1ff) << 2)
+#define SERDES_CR_CTL			0x80a0
+#define SERDES_CR_ADDR			0x80a1
+#define SERDES_CR_DATA			0x80a2
+#define CR_BUSY				0x0001
+#define CR_START			0x0001
+#define CR_WR_RDN			0x0002
+#define CPHY_RX_INPUT_STS		0x2002
+#define CPHY_SATA_OVERRIDE	 	0x4000
+#define CPHY_OVERRIDE			0x2005
+#define SPHY_LANE			0x100
+#define SPHY_HALF_RATE			0x0001
+#define CPHY_SATA_DPLL_MODE		0x0700
+#define CPHY_SATA_DPLL_SHIFT		8
+#define CPHY_SATA_DPLL_RESET		(1 << 11)
+#define CPHY_PHY_COUNT			6
+#define CPHY_LANE_COUNT			4
+#define CPHY_PORT_COUNT			(CPHY_PHY_COUNT * CPHY_LANE_COUNT)
+
+static DEFINE_SPINLOCK(cphy_lock);
+/* Each of the 6 phys can have up to 4 sata ports attached to i. Map 0-based
+ * sata ports to their phys and then to their lanes within the phys
+ */
+struct phy_lane_info {
+	void __iomem *phy_base;
+	u8 lane_mapping;
+	u8 phy_devs;
+};
+static struct phy_lane_info port_data[CPHY_PORT_COUNT];
+
+static u32 __combo_phy_reg_read(u8 sata_port, u32 addr)
+{
+	u32 data;
+	u8 dev = port_data[sata_port].phy_devs;
+	spin_lock(&cphy_lock);
+	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
+	data = readl(port_data[sata_port].phy_base + CPHY_ADDR(addr));
+	spin_unlock(&cphy_lock);
+	return data;
+}
+
+static void __combo_phy_reg_write(u8 sata_port, u32 addr, u32 data)
+{
+	u8 dev = port_data[sata_port].phy_devs;
+	spin_lock(&cphy_lock);
+	writel(CPHY_MAP(dev, addr), port_data[sata_port].phy_base + 0x800);
+	writel(data, port_data[sata_port].phy_base + CPHY_ADDR(addr));
+	spin_unlock(&cphy_lock);
+}
+
+static void combo_phy_wait_for_ready(u8 sata_port)
+{
+	while (__combo_phy_reg_read(sata_port, SERDES_CR_CTL) & CR_BUSY)
+		udelay(5);
+}
+
+static u32 combo_phy_read(u8 sata_port, u32 addr)
+{
+	combo_phy_wait_for_ready(sata_port);
+	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
+	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_START);
+	combo_phy_wait_for_ready(sata_port);
+	return __combo_phy_reg_read(sata_port, SERDES_CR_DATA);
+}
+
+static void combo_phy_write(u8 sata_port, u32 addr, u32 data)
+{
+	combo_phy_wait_for_ready(sata_port);
+	__combo_phy_reg_write(sata_port, SERDES_CR_ADDR, addr);
+	__combo_phy_reg_write(sata_port, SERDES_CR_DATA, data);
+	__combo_phy_reg_write(sata_port, SERDES_CR_CTL, CR_WR_RDN | CR_START);
+}
+
+static void highbank_cphy_disable_overrides(u8 sata_port)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp;
+	if (unlikely(port_data[sata_port].phy_base == NULL))
+		return;
+	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
+	tmp &= ~CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+}
+
+static void cphy_override_rx_mode(u8 sata_port, u32 val)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp;
+	tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
+	tmp &= ~CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp |= CPHY_SATA_OVERRIDE;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp &= ~CPHY_SATA_DPLL_MODE;
+	tmp |= val << CPHY_SATA_DPLL_SHIFT;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp |= CPHY_SATA_DPLL_RESET;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	tmp &= ~CPHY_SATA_DPLL_RESET;
+	combo_phy_write(sata_port, CPHY_OVERRIDE + lane * SPHY_LANE, tmp);
+
+	msleep(15);
+}
+
+static void highbank_cphy_override_lane(u8 sata_port)
+{
+	u8 lane = port_data[sata_port].lane_mapping;
+	u32 tmp, k = 0;
+
+	if (unlikely(port_data[sata_port].phy_base == NULL))
+		return;
+	do {
+		tmp = combo_phy_read(sata_port, CPHY_RX_INPUT_STS +
+						lane * SPHY_LANE);
+	} while ((tmp & SPHY_HALF_RATE) && (k++ < 1000));
+	cphy_override_rx_mode(sata_port, 3);
+}
+
+static int highbank_initialize_phys(struct device *dev, void __iomem *addr)
+{
+	struct device_node *sata_node = dev->of_node;
+	int phy_count = 0, phy, port = 0;
+	void __iomem *cphy_base[CPHY_PHY_COUNT];
+	struct device_node *phy_nodes[CPHY_PHY_COUNT];
+	memset(port_data, 0, sizeof(struct phy_lane_info) * CPHY_PORT_COUNT);
+	memset(phy_nodes, 0, sizeof(struct device_node*) * CPHY_PHY_COUNT);
+
+	do {
+		u32 tmp;
+		struct of_phandle_args phy_data;
+		if (of_parse_phandle_with_args(sata_node,
+				"calxeda,port-phys", "#phy-cells",
+				port, &phy_data))
+			break;
+		for (phy = 0; phy < phy_count; phy++) {
+			if (phy_nodes[phy] == phy_data.np)
+				break;
+		}
+		if (phy_nodes[phy] == NULL) {
+			phy_nodes[phy] = phy_data.np;
+			cphy_base[phy] = of_iomap(phy_nodes[phy], 0);
+			if (cphy_base[phy] == NULL) {
+				return 0;
+			}
+			phy_count += 1;
+		}
+		port_data[port].lane_mapping = phy_data.args[0];
+		of_property_read_u32(phy_nodes[phy], "phydev", &tmp);
+		port_data[port].phy_devs = tmp;
+		port_data[port].phy_base = cphy_base[phy];
+		of_node_put(phy_data.np);
+		port += 1;
+	} while (port < CPHY_PORT_COUNT);
+	return 0;
+}
+
+static int ahci_highbank_hardreset(struct ata_link *link, unsigned int *class,
+				unsigned long deadline)
+{
+	const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
+	struct ata_port *ap = link->ap;
+	struct ahci_port_priv *pp = ap->private_data;
+	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
+	struct ata_taskfile tf;
+	bool online;
+	u32 sstatus;
+	int rc;
+	int retry = 10;
+
+	ahci_stop_engine(ap);
+
+	/* clear D2H reception area to properly wait for D2H FIS */
+	ata_tf_init(link->device, &tf);
+	tf.command = 0x80;
+	ata_tf_to_fis(&tf, 0, 0, d2h_fis);
+
+	do {
+		highbank_cphy_disable_overrides(link->ap->port_no);
+		rc = sata_link_hardreset(link, timing, deadline, &online, NULL);
+		highbank_cphy_override_lane(link->ap->port_no);
+
+		/* If the status is 1, we are connected, but the link did not
+		 * come up. So retry resetting the link again.
+		 */
+		if (sata_scr_read(link, SCR_STATUS, &sstatus))
+			break;
+		if (!(sstatus & 0x3))
+			break;
+	} while (!online && retry--);
+
+	ahci_start_engine(ap);
+
+	if (online)
+		*class = ahci_dev_classify(ap);
+
+	return rc;
+}
+
+static struct ata_port_operations ahci_highbank_ops = {
+	.inherits		= &ahci_ops,
+	.hardreset		= ahci_highbank_hardreset,
+};
+
+static const struct ata_port_info ahci_highbank_port_info = {
+	.flags          = AHCI_FLAG_COMMON,
+	.pio_mask       = ATA_PIO4,
+	.udma_mask      = ATA_UDMA6,
+	.port_ops       = &ahci_highbank_ops,
+};
+
+static struct scsi_host_template ahci_highbank_platform_sht = {
+	AHCI_SHT("highbank-ahci"),
+};
+
+static const struct of_device_id ahci_of_match[] = {
+	{ .compatible = "calxeda,hb-ahci" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ahci_of_match);
+
+static int __init ahci_highbank_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ahci_host_priv *hpriv;
+	struct ata_host *host;
+	struct resource *mem;
+	int irq;
+	int n_ports;
+	int i;
+	int rc;
+	struct ata_port_info pi = ahci_highbank_port_info;
+	const struct ata_port_info *ppi[] = { &pi, NULL };
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(dev, "no mmio space\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0) {
+		dev_err(dev, "no irq\n");
+		return -EINVAL;
+	}
+
+	hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL);
+	if (!hpriv) {
+		dev_err(dev, "can't alloc ahci_host_priv\n");
+		return -ENOMEM;
+	}
+
+	hpriv->flags |= (unsigned long)pi.private_data;
+
+	hpriv->mmio = devm_ioremap(dev, mem->start, resource_size(mem));
+	if (!hpriv->mmio) {
+		dev_err(dev, "can't map %pR\n", mem);
+		return -ENOMEM;
+	}
+
+	rc = highbank_initialize_phys(dev, hpriv->mmio);
+	if (rc)
+		return rc;
+
+
+	ahci_save_initial_config(dev, hpriv, 0, 0);
+
+	/* prepare host */
+	if (hpriv->cap & HOST_CAP_NCQ)
+		pi.flags |= ATA_FLAG_NCQ;
+
+	if (hpriv->cap & HOST_CAP_PMP)
+		pi.flags |= ATA_FLAG_PMP;
+
+	ahci_set_em_messages(hpriv, &pi);
+
+	/* CAP.NP sometimes indicate the index of the last enabled
+	 * port, at other times, that of the last possible port, so
+	 * determining the maximum port number requires looking at
+	 * both CAP.NP and port_map.
+	 */
+	n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map));
+
+	host = ata_host_alloc_pinfo(dev, ppi, n_ports);
+	if (!host) {
+		rc = -ENOMEM;
+		goto err0;
+	}
+
+	host->private_data = hpriv;
+
+	if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
+		host->flags |= ATA_HOST_PARALLEL_SCAN;
+
+	if (pi.flags & ATA_FLAG_EM)
+		ahci_reset_em(host);
+
+	for (i = 0; i < host->n_ports; i++) {
+		struct ata_port *ap = host->ports[i];
+
+		ata_port_desc(ap, "mmio %pR", mem);
+		ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80);
+
+		/* set enclosure management message type */
+		if (ap->flags & ATA_FLAG_EM)
+			ap->em_message_type = hpriv->em_msg_type;
+
+		/* disabled/not-implemented port */
+		if (!(hpriv->port_map & (1 << i)))
+			ap->ops = &ata_dummy_port_ops;
+	}
+
+	rc = ahci_reset_controller(host);
+	if (rc)
+		goto err0;
+
+	ahci_init_controller(host);
+	ahci_print_info(host, "platform");
+
+	rc = ata_host_activate(host, irq, ahci_interrupt, 0,
+					&ahci_highbank_platform_sht);
+	if (rc)
+		goto err0;
+
+	return 0;
+err0:
+	return rc;
+}
+
+static int __devexit ahci_highbank_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ata_host *host = dev_get_drvdata(dev);
+
+	ata_host_detach(host);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ahci_highbank_suspend(struct device *dev)
+{
+	struct ata_host *host = dev_get_drvdata(dev);
+	struct ahci_host_priv *hpriv = host->private_data;
+	void __iomem *mmio = hpriv->mmio;
+	u32 ctl;
+	int rc;
+
+	if (hpriv->flags & AHCI_HFLAG_NO_SUSPEND) {
+		dev_err(dev, "firmware update required for suspend/resume\n");
+		return -EIO;
+	}
+
+	/*
+	 * AHCI spec rev1.1 section 8.3.3:
+	 * Software must disable interrupts prior to requesting a
+	 * transition of the HBA to D3 state.
+	 */
+	ctl = readl(mmio + HOST_CTL);
+	ctl &= ~HOST_IRQ_EN;
+	writel(ctl, mmio + HOST_CTL);
+	readl(mmio + HOST_CTL); /* flush */
+
+	rc = ata_host_suspend(host, PMSG_SUSPEND);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static int ahci_highbank_resume(struct device *dev)
+{
+	struct ata_host *host = dev_get_drvdata(dev);
+	int rc;
+
+	if (dev->power.power_state.event == PM_EVENT_SUSPEND) {
+		rc = ahci_reset_controller(host);
+		if (rc)
+			return rc;
+
+		ahci_init_controller(host);
+	}
+
+	ata_host_resume(host);
+
+	return 0;
+}
+#endif
+
+SIMPLE_DEV_PM_OPS(ahci_highbank_pm_ops,
+		  ahci_highbank_suspend, ahci_highbank_resume);
+
+static struct platform_driver ahci_highbank_driver = {
+        .remove = __devexit_p(ahci_highbank_remove),
+        .driver = {
+                .name = "highbank-ahci",
+                .owner = THIS_MODULE,
+                .of_match_table = ahci_of_match,
+                .pm = &ahci_highbank_pm_ops,
+        },
+	.probe = ahci_highbank_probe,
+};
+
+module_platform_driver(ahci_highbank_driver);
+
+MODULE_DESCRIPTION("Calxeda Highbank AHCI SATA platform driver");
+MODULE_AUTHOR("Mark Langsdorf <mark.langsdorf@calxeda.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("sata:highbank");
-- 
1.7.11.4


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2012-09-06 21:03 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-08-17 14:51 [PATCH 0/2] ahci: custom reset for Calxeda ahci Mark Langsdorf
2012-08-17 14:51 ` [PATCH 1/2] ahci: un-staticize ahci_dev_classify Mark Langsdorf
2012-08-17 17:27   ` Jeff Garzik
2012-08-17 14:51 ` [PATCH 2/2] ahci_platform: add custom hard reset for Calxeda ahci ctrlr Mark Langsdorf
2012-08-17 15:33   ` Rob Herring
2012-08-17 17:25   ` Jeff Garzik
2012-08-17 22:25     ` Mark Langsdorf
2012-09-06 21:03   ` [PATCH v2] ata: add platform driver for Calxeda AHCI controller Mark Langsdorf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).