|
@@ -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");
|