+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@xxxxxxxxxxx> > Signed-off-by: Rob Herring <rob.herring@xxxxxxxxxxx> > --- > .../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); > -- To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html