* [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).