ata: add Broadcom AHCI SATA3 driver for STB chips
authorBrian Norris <computersforpeace@gmail.com>
Tue, 12 May 2015 23:28:21 +0000 (16:28 -0700)
committerTejun Heo <tj@kernel.org>
Mon, 25 May 2015 00:11:25 +0000 (20:11 -0400)
Pretty straightforward driver, using the nice library-ization of the
generic ahci_platform driver.

Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
drivers/ata/Kconfig
drivers/ata/Makefile
drivers/ata/ahci_brcmstb.c [new file with mode: 0644]

index ee5209fb82b2ff70bce254f7ab88bfb5e9753259..e881503fd4c6187be73c27b9748df65d61782e59 100644 (file)
@@ -98,6 +98,15 @@ config SATA_AHCI_PLATFORM
 
          If unsure, say N.
 
+config AHCI_BRCMSTB
+       tristate "Broadcom STB AHCI SATA support"
+       depends on ARCH_BRCMSTB
+       help
+         This option enables support for the AHCI SATA3 controller found on
+         STB SoC's.
+
+         If unsure, say N.
+
 config AHCI_DA850
        tristate "DaVinci DA850 AHCI SATA support"
        depends on ARCH_DAVINCI_DA850
index 40f7865f20a1dbf62123da6998fcef81fa9a2690..5154753ade634f7928347ebd0fce12ec27ceff13 100644 (file)
@@ -10,6 +10,7 @@ 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 libahci.o
+obj-$(CONFIG_AHCI_BRCMSTB)     += ahci_brcmstb.o libahci.o libahci_platform.o
 obj-$(CONFIG_AHCI_DA850)       += ahci_da850.o libahci.o libahci_platform.o
 obj-$(CONFIG_AHCI_IMX)         += ahci_imx.o libahci.o libahci_platform.o
 obj-$(CONFIG_AHCI_MVEBU)       += ahci_mvebu.o libahci.o libahci_platform.o
diff --git a/drivers/ata/ahci_brcmstb.c b/drivers/ata/ahci_brcmstb.c
new file mode 100644 (file)
index 0000000..ce1e3a8
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * Broadcom SATA3 AHCI Controller Driver
+ *
+ * Copyright © 2009-2015 Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <linux/ahci_platform.h>
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/libata.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+
+#include "ahci.h"
+
+#define DRV_NAME                                       "brcm-ahci"
+
+#define SATA_TOP_CTRL_VERSION                          0x0
+#define SATA_TOP_CTRL_BUS_CTRL                         0x4
+ #define MMIO_ENDIAN_SHIFT                             0 /* CPU->AHCI */
+ #define DMADESC_ENDIAN_SHIFT                          2 /* AHCI->DDR */
+ #define DMADATA_ENDIAN_SHIFT                          4 /* AHCI->DDR */
+ #define PIODATA_ENDIAN_SHIFT                          6
+  #define ENDIAN_SWAP_NONE                             0
+  #define ENDIAN_SWAP_FULL                             2
+ #define OVERRIDE_HWINIT                               BIT(16)
+#define SATA_TOP_CTRL_TP_CTRL                          0x8
+#define SATA_TOP_CTRL_PHY_CTRL                         0xc
+ #define SATA_TOP_CTRL_PHY_CTRL_1                      0x0
+  #define SATA_TOP_CTRL_1_PHY_DEFAULT_POWER_STATE      BIT(14)
+ #define SATA_TOP_CTRL_PHY_CTRL_2                      0x4
+  #define SATA_TOP_CTRL_2_SW_RST_MDIOREG               BIT(0)
+  #define SATA_TOP_CTRL_2_SW_RST_OOB                   BIT(1)
+  #define SATA_TOP_CTRL_2_SW_RST_RX                    BIT(2)
+  #define SATA_TOP_CTRL_2_SW_RST_TX                    BIT(3)
+  #define SATA_TOP_CTRL_2_PHY_GLOBAL_RESET             BIT(14)
+ #define SATA_TOP_CTRL_PHY_OFFS                                0x8
+ #define SATA_TOP_MAX_PHYS                             2
+#define SATA_TOP_CTRL_SATA_TP_OUT                      0x1c
+#define SATA_TOP_CTRL_CLIENT_INIT_CTRL                 0x20
+
+/* On big-endian MIPS, buses are reversed to big endian, so switch them back */
+#if defined(CONFIG_MIPS) && defined(__BIG_ENDIAN)
+#define DATA_ENDIAN                     2 /* AHCI->DDR inbound accesses */
+#define MMIO_ENDIAN                     2 /* CPU->AHCI outbound accesses */
+#else
+#define DATA_ENDIAN                     0
+#define MMIO_ENDIAN                     0
+#endif
+
+#define BUS_CTRL_ENDIAN_CONF                           \
+       ((DATA_ENDIAN << DMADATA_ENDIAN_SHIFT) |        \
+       (DATA_ENDIAN << DMADESC_ENDIAN_SHIFT) |         \
+       (MMIO_ENDIAN << MMIO_ENDIAN_SHIFT))
+
+struct brcm_ahci_priv {
+       struct device *dev;
+       void __iomem *top_ctrl;
+       u32 port_mask;
+};
+
+static const struct ata_port_info ahci_brcm_port_info = {
+       .flags          = AHCI_FLAG_COMMON,
+       .pio_mask       = ATA_PIO4,
+       .udma_mask      = ATA_UDMA6,
+       .port_ops       = &ahci_platform_ops,
+};
+
+static inline u32 brcm_sata_readreg(void __iomem *addr)
+{
+       /*
+        * MIPS endianness is configured by boot strap, which also reverses all
+        * bus endianness (i.e., big-endian CPU + big endian bus ==> native
+        * endian I/O).
+        *
+        * Other architectures (e.g., ARM) either do not support big endian, or
+        * else leave I/O in little endian mode.
+        */
+       if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(__BIG_ENDIAN))
+               return __raw_readl(addr);
+       else
+               return readl_relaxed(addr);
+}
+
+static inline void brcm_sata_writereg(u32 val, void __iomem *addr)
+{
+       /* See brcm_sata_readreg() comments */
+       if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(__BIG_ENDIAN))
+               __raw_writel(val, addr);
+       else
+               writel_relaxed(val, addr);
+}
+
+static void brcm_sata_phy_enable(struct brcm_ahci_priv *priv, int port)
+{
+       void __iomem *phyctrl = priv->top_ctrl + SATA_TOP_CTRL_PHY_CTRL +
+                               (port * SATA_TOP_CTRL_PHY_OFFS);
+       void __iomem *p;
+       u32 reg;
+
+       /* clear PHY_DEFAULT_POWER_STATE */
+       p = phyctrl + SATA_TOP_CTRL_PHY_CTRL_1;
+       reg = brcm_sata_readreg(p);
+       reg &= ~SATA_TOP_CTRL_1_PHY_DEFAULT_POWER_STATE;
+       brcm_sata_writereg(reg, p);
+
+       /* reset the PHY digital logic */
+       p = phyctrl + SATA_TOP_CTRL_PHY_CTRL_2;
+       reg = brcm_sata_readreg(p);
+       reg &= ~(SATA_TOP_CTRL_2_SW_RST_MDIOREG | SATA_TOP_CTRL_2_SW_RST_OOB |
+                SATA_TOP_CTRL_2_SW_RST_RX);
+       reg |= SATA_TOP_CTRL_2_SW_RST_TX;
+       brcm_sata_writereg(reg, p);
+       reg = brcm_sata_readreg(p);
+       reg |= SATA_TOP_CTRL_2_PHY_GLOBAL_RESET;
+       brcm_sata_writereg(reg, p);
+       reg = brcm_sata_readreg(p);
+       reg &= ~SATA_TOP_CTRL_2_PHY_GLOBAL_RESET;
+       brcm_sata_writereg(reg, p);
+       (void)brcm_sata_readreg(p);
+}
+
+static void brcm_sata_phy_disable(struct brcm_ahci_priv *priv, int port)
+{
+       void __iomem *phyctrl = priv->top_ctrl + SATA_TOP_CTRL_PHY_CTRL +
+                               (port * SATA_TOP_CTRL_PHY_OFFS);
+       void __iomem *p;
+       u32 reg;
+
+       /* power-off the PHY digital logic */
+       p = phyctrl + SATA_TOP_CTRL_PHY_CTRL_2;
+       reg = brcm_sata_readreg(p);
+       reg |= (SATA_TOP_CTRL_2_SW_RST_MDIOREG | SATA_TOP_CTRL_2_SW_RST_OOB |
+               SATA_TOP_CTRL_2_SW_RST_RX | SATA_TOP_CTRL_2_SW_RST_TX |
+               SATA_TOP_CTRL_2_PHY_GLOBAL_RESET);
+       brcm_sata_writereg(reg, p);
+
+       /* set PHY_DEFAULT_POWER_STATE */
+       p = phyctrl + SATA_TOP_CTRL_PHY_CTRL_1;
+       reg = brcm_sata_readreg(p);
+       reg |= SATA_TOP_CTRL_1_PHY_DEFAULT_POWER_STATE;
+       brcm_sata_writereg(reg, p);
+}
+
+static void brcm_sata_phys_enable(struct brcm_ahci_priv *priv)
+{
+       int i;
+
+       for (i = 0; i < SATA_TOP_MAX_PHYS; i++)
+               if (priv->port_mask & BIT(i))
+                       brcm_sata_phy_enable(priv, i);
+}
+
+static void brcm_sata_phys_disable(struct brcm_ahci_priv *priv)
+{
+       int i;
+
+       for (i = 0; i < SATA_TOP_MAX_PHYS; i++)
+               if (priv->port_mask & BIT(i))
+                       brcm_sata_phy_disable(priv, i);
+}
+
+static u32 brcm_ahci_get_portmask(struct platform_device *pdev,
+                                 struct brcm_ahci_priv *priv)
+{
+       void __iomem *ahci;
+       struct resource *res;
+       u32 impl;
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ahci");
+       ahci = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(ahci))
+               return 0;
+
+       impl = readl(ahci + HOST_PORTS_IMPL);
+
+       if (fls(impl) > SATA_TOP_MAX_PHYS)
+               dev_warn(priv->dev, "warning: more ports than PHYs (%#x)\n",
+                        impl);
+       else if (!impl)
+               dev_info(priv->dev, "no ports found\n");
+
+       devm_iounmap(&pdev->dev, ahci);
+       devm_release_mem_region(&pdev->dev, res->start, resource_size(res));
+
+       return impl;
+}
+
+static void brcm_sata_init(struct brcm_ahci_priv *priv)
+{
+       /* Configure endianness */
+       brcm_sata_writereg(BUS_CTRL_ENDIAN_CONF,
+                          priv->top_ctrl + SATA_TOP_CTRL_BUS_CTRL);
+}
+
+static int brcm_ahci_suspend(struct device *dev)
+{
+       struct ata_host *host = dev_get_drvdata(dev);
+       struct ahci_host_priv *hpriv = host->private_data;
+       struct brcm_ahci_priv *priv = hpriv->plat_data;
+       int ret;
+
+       ret = ahci_platform_suspend(dev);
+       brcm_sata_phys_disable(priv);
+       return ret;
+}
+
+static int brcm_ahci_resume(struct device *dev)
+{
+       struct ata_host *host = dev_get_drvdata(dev);
+       struct ahci_host_priv *hpriv = host->private_data;
+       struct brcm_ahci_priv *priv = hpriv->plat_data;
+
+       brcm_sata_init(priv);
+       brcm_sata_phys_enable(priv);
+       return ahci_platform_resume(dev);
+}
+
+static struct scsi_host_template ahci_platform_sht = {
+       AHCI_SHT(DRV_NAME),
+};
+
+static int brcm_ahci_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct brcm_ahci_priv *priv;
+       struct ahci_host_priv *hpriv;
+       struct resource *res;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       priv->dev = dev;
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "top-ctrl");
+       priv->top_ctrl = devm_ioremap_resource(dev, res);
+       if (IS_ERR(priv->top_ctrl))
+               return PTR_ERR(priv->top_ctrl);
+
+       brcm_sata_init(priv);
+
+       priv->port_mask = brcm_ahci_get_portmask(pdev, priv);
+       if (!priv->port_mask)
+               return -ENODEV;
+
+       brcm_sata_phys_enable(priv);
+
+       hpriv = ahci_platform_get_resources(pdev);
+       if (IS_ERR(hpriv))
+               return PTR_ERR(hpriv);
+       hpriv->plat_data = priv;
+
+       ret = ahci_platform_enable_resources(hpriv);
+       if (ret)
+               return ret;
+
+       ret = ahci_platform_init_host(pdev, hpriv, &ahci_brcm_port_info,
+                                     &ahci_platform_sht);
+       if (ret)
+               return ret;
+
+       dev_info(dev, "Broadcom AHCI SATA3 registered\n");
+
+       return 0;
+}
+
+static int brcm_ahci_remove(struct platform_device *pdev)
+{
+       struct ata_host *host = dev_get_drvdata(&pdev->dev);
+       struct ahci_host_priv *hpriv = host->private_data;
+       struct brcm_ahci_priv *priv = hpriv->plat_data;
+       int ret;
+
+       ret = ata_platform_remove_one(pdev);
+       if (ret)
+               return ret;
+
+       brcm_sata_phys_disable(priv);
+
+       return 0;
+}
+
+static const struct of_device_id ahci_of_match[] = {
+       {.compatible = "brcm,bcm7445-ahci"},
+       {},
+};
+MODULE_DEVICE_TABLE(of, ahci_of_match);
+
+static SIMPLE_DEV_PM_OPS(ahci_brcm_pm_ops, brcm_ahci_suspend, brcm_ahci_resume);
+
+static struct platform_driver brcm_ahci_driver = {
+       .probe = brcm_ahci_probe,
+       .remove = brcm_ahci_remove,
+       .driver = {
+               .name = DRV_NAME,
+               .of_match_table = ahci_of_match,
+               .pm = &ahci_brcm_pm_ops,
+       },
+};
+module_platform_driver(brcm_ahci_driver);
+
+MODULE_DESCRIPTION("Broadcom SATA3 AHCI Controller Driver");
+MODULE_AUTHOR("Brian Norris");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sata-brcmstb");