root/drivers/ata/ahci_tegra.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * drivers/ata/ahci_tegra.c
 *
 * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
 *
 * Author:
 *      Mikko Perttunen <mperttunen@nvidia.com>
 */

#include <linux/ahci_platform.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>

#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>

#include "ahci.h"

#define DRV_NAME "tegra-ahci"

#define SATA_CONFIGURATION_0                            0x180
#define SATA_CONFIGURATION_0_EN_FPCI                    BIT(0)
#define SATA_CONFIGURATION_0_CLK_OVERRIDE                       BIT(31)

#define SCFG_OFFSET                                     0x1000

#define T_SATA0_CFG_1                                   0x04
#define T_SATA0_CFG_1_IO_SPACE                          BIT(0)
#define T_SATA0_CFG_1_MEMORY_SPACE                      BIT(1)
#define T_SATA0_CFG_1_BUS_MASTER                        BIT(2)
#define T_SATA0_CFG_1_SERR                              BIT(8)

#define T_SATA0_CFG_9                                   0x24
#define T_SATA0_CFG_9_BASE_ADDRESS                      0x40020000

#define SATA_FPCI_BAR5                                  0x94
#define SATA_FPCI_BAR5_START_MASK                       (0xfffffff << 4)
#define SATA_FPCI_BAR5_START                            (0x0040020 << 4)
#define SATA_FPCI_BAR5_ACCESS_TYPE                      (0x1)

#define SATA_INTR_MASK                                  0x188
#define SATA_INTR_MASK_IP_INT_MASK                      BIT(16)

#define T_SATA0_CFG_35                                  0x94
#define T_SATA0_CFG_35_IDP_INDEX_MASK                   (0x7ff << 2)
#define T_SATA0_CFG_35_IDP_INDEX                        (0x2a << 2)

#define T_SATA0_AHCI_IDP1                               0x98
#define T_SATA0_AHCI_IDP1_DATA                          (0x400040)

#define T_SATA0_CFG_PHY_1                               0x12c
#define T_SATA0_CFG_PHY_1_PADS_IDDQ_EN                  BIT(23)
#define T_SATA0_CFG_PHY_1_PAD_PLL_IDDQ_EN               BIT(22)

#define T_SATA0_NVOOB                                   0x114
#define T_SATA0_NVOOB_SQUELCH_FILTER_MODE_MASK          (0x3 << 24)
#define T_SATA0_NVOOB_SQUELCH_FILTER_MODE               (0x1 << 24)
#define T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH_MASK        (0x3 << 26)
#define T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH             (0x3 << 26)

#define T_SATA_CFG_PHY_0                                0x120
#define T_SATA_CFG_PHY_0_USE_7BIT_ALIGN_DET_FOR_SPD     BIT(11)
#define T_SATA_CFG_PHY_0_MASK_SQUELCH                   BIT(24)

#define T_SATA0_CFG2NVOOB_2                             0x134
#define T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW_MASK   (0x1ff << 18)
#define T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW        (0xc << 18)

#define T_SATA0_AHCI_HBA_CAP_BKDR                       0x300
#define T_SATA0_AHCI_HBA_CAP_BKDR_PARTIAL_ST_CAP        BIT(13)
#define T_SATA0_AHCI_HBA_CAP_BKDR_SLUMBER_ST_CAP        BIT(14)
#define T_SATA0_AHCI_HBA_CAP_BKDR_SALP                  BIT(26)
#define T_SATA0_AHCI_HBA_CAP_BKDR_SUPP_PM               BIT(17)
#define T_SATA0_AHCI_HBA_CAP_BKDR_SNCQ                  BIT(30)

#define T_SATA0_BKDOOR_CC                               0x4a4
#define T_SATA0_BKDOOR_CC_CLASS_CODE_MASK               (0xffff << 16)
#define T_SATA0_BKDOOR_CC_CLASS_CODE                    (0x0106 << 16)
#define T_SATA0_BKDOOR_CC_PROG_IF_MASK                  (0xff << 8)
#define T_SATA0_BKDOOR_CC_PROG_IF                       (0x01 << 8)

#define T_SATA0_CFG_SATA                                0x54c
#define T_SATA0_CFG_SATA_BACKDOOR_PROG_IF_EN            BIT(12)

#define T_SATA0_CFG_MISC                                0x550

#define T_SATA0_INDEX                                   0x680

#define T_SATA0_CHX_PHY_CTRL1_GEN1                      0x690
#define T_SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_MASK          0xff
#define T_SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_SHIFT         0
#define T_SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_MASK         (0xff << 8)
#define T_SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_SHIFT        8

#define T_SATA0_CHX_PHY_CTRL1_GEN2                      0x694
#define T_SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_MASK          0xff
#define T_SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_SHIFT         0
#define T_SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_MASK         (0xff << 12)
#define T_SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_SHIFT        12

#define T_SATA0_CHX_PHY_CTRL2                           0x69c
#define T_SATA0_CHX_PHY_CTRL2_CDR_CNTL_GEN1             0x23

#define T_SATA0_CHX_PHY_CTRL11                          0x6d0
#define T_SATA0_CHX_PHY_CTRL11_GEN2_RX_EQ               (0x2800 << 16)

#define T_SATA0_CHX_PHY_CTRL17_0                        0x6e8
#define T_SATA0_CHX_PHY_CTRL17_0_RX_EQ_CTRL_L_GEN1      0x55010000
#define T_SATA0_CHX_PHY_CTRL18_0                        0x6ec
#define T_SATA0_CHX_PHY_CTRL18_0_RX_EQ_CTRL_L_GEN2      0x55010000
#define T_SATA0_CHX_PHY_CTRL20_0                        0x6f4
#define T_SATA0_CHX_PHY_CTRL20_0_RX_EQ_CTRL_H_GEN1      0x1
#define T_SATA0_CHX_PHY_CTRL21_0                        0x6f8
#define T_SATA0_CHX_PHY_CTRL21_0_RX_EQ_CTRL_H_GEN2      0x1

/* AUX Registers */
#define SATA_AUX_MISC_CNTL_1_0                          0x8
#define SATA_AUX_MISC_CNTL_1_0_DEVSLP_OVERRIDE          BIT(17)
#define SATA_AUX_MISC_CNTL_1_0_SDS_SUPPORT              BIT(13)
#define SATA_AUX_MISC_CNTL_1_0_DESO_SUPPORT             BIT(15)

#define SATA_AUX_RX_STAT_INT_0                          0xc
#define SATA_AUX_RX_STAT_INT_0_SATA_DEVSLP              BIT(7)

#define SATA_AUX_SPARE_CFG0_0                           0x18
#define SATA_AUX_SPARE_CFG0_0_MDAT_TIMER_AFTER_PG_VALID BIT(14)

#define FUSE_SATA_CALIB                                 0x124
#define FUSE_SATA_CALIB_MASK                            0x3

struct sata_pad_calibration {
        u8 gen1_tx_amp;
        u8 gen1_tx_peak;
        u8 gen2_tx_amp;
        u8 gen2_tx_peak;
};

static const struct sata_pad_calibration tegra124_pad_calibration[] = {
        {0x18, 0x04, 0x18, 0x0a},
        {0x0e, 0x04, 0x14, 0x0a},
        {0x0e, 0x07, 0x1a, 0x0e},
        {0x14, 0x0e, 0x1a, 0x0e},
};

struct tegra_ahci_ops {
        int (*init)(struct ahci_host_priv *hpriv);
};

struct tegra_ahci_regs {
        unsigned int nvoob_comma_cnt_mask;
        unsigned int nvoob_comma_cnt_val;
};

struct tegra_ahci_soc {
        const char *const               *supply_names;
        u32                             num_supplies;
        bool                            supports_devslp;
        bool                            has_sata_oob_rst;
        const struct tegra_ahci_ops     *ops;
        const struct tegra_ahci_regs    *regs;
};

struct tegra_ahci_priv {
        struct platform_device     *pdev;
        void __iomem               *sata_regs;
        void __iomem               *sata_aux_regs;
        struct reset_control       *sata_rst;
        struct reset_control       *sata_oob_rst;
        struct reset_control       *sata_cold_rst;
        /* Needs special handling, cannot use ahci_platform */
        struct clk                 *sata_clk;
        struct tegra_pmc           *pmc;
        struct regulator_bulk_data *supplies;
        const struct tegra_ahci_soc *soc;
};

static void tegra_ahci_handle_quirks(struct ahci_host_priv *hpriv)
{
        struct tegra_ahci_priv *tegra = hpriv->plat_data;
        u32 val;

        if (tegra->sata_aux_regs && !tegra->soc->supports_devslp) {
                val = readl(tegra->sata_aux_regs + SATA_AUX_MISC_CNTL_1_0);
                val &= ~SATA_AUX_MISC_CNTL_1_0_SDS_SUPPORT;
                writel(val, tegra->sata_aux_regs + SATA_AUX_MISC_CNTL_1_0);
        }
}

static int tegra124_ahci_init(struct ahci_host_priv *hpriv)
{
        struct tegra_ahci_priv *tegra = hpriv->plat_data;
        struct sata_pad_calibration calib;
        int ret;
        u32 val;

        /* Pad calibration */
        ret = tegra_fuse_readl(FUSE_SATA_CALIB, &val);
        if (ret)
                return ret;

        calib = tegra124_pad_calibration[val & FUSE_SATA_CALIB_MASK];

        writel(BIT(0), tegra->sata_regs + SCFG_OFFSET + T_SATA0_INDEX);

        val = readl(tegra->sata_regs +
                    SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL1_GEN1);
        val &= ~T_SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_MASK;
        val &= ~T_SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_MASK;
        val |= calib.gen1_tx_amp << T_SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_SHIFT;
        val |= calib.gen1_tx_peak << T_SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_SHIFT;
        writel(val, tegra->sata_regs + SCFG_OFFSET +
               T_SATA0_CHX_PHY_CTRL1_GEN1);

        val = readl(tegra->sata_regs +
                    SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL1_GEN2);
        val &= ~T_SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_MASK;
        val &= ~T_SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_MASK;
        val |= calib.gen2_tx_amp << T_SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_SHIFT;
        val |= calib.gen2_tx_peak << T_SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_SHIFT;
        writel(val, tegra->sata_regs + SCFG_OFFSET +
               T_SATA0_CHX_PHY_CTRL1_GEN2);

        writel(T_SATA0_CHX_PHY_CTRL11_GEN2_RX_EQ,
               tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL11);
        writel(T_SATA0_CHX_PHY_CTRL2_CDR_CNTL_GEN1,
               tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL2);

        writel(0, tegra->sata_regs + SCFG_OFFSET + T_SATA0_INDEX);

        return 0;
}

static int tegra_ahci_power_on(struct ahci_host_priv *hpriv)
{
        struct tegra_ahci_priv *tegra = hpriv->plat_data;
        int ret;

        ret = regulator_bulk_enable(tegra->soc->num_supplies,
                                    tegra->supplies);
        if (ret)
                return ret;

        if (!tegra->pdev->dev.pm_domain) {
                ret = tegra_pmc_powergate_sequence_power_up(tegra->pmc,
                                                            TEGRA_POWERGATE_SATA,
                                                            tegra->sata_clk,
                                                            tegra->sata_rst);
                if (ret)
                        goto disable_regulators;
        }

        reset_control_assert(tegra->sata_oob_rst);
        reset_control_assert(tegra->sata_cold_rst);

        ret = ahci_platform_enable_resources(hpriv);
        if (ret)
                goto disable_power;

        reset_control_deassert(tegra->sata_cold_rst);
        reset_control_deassert(tegra->sata_oob_rst);

        return 0;

disable_power:
        clk_disable_unprepare(tegra->sata_clk);

        if (!tegra->pdev->dev.pm_domain)
                tegra_pmc_powergate_power_off(tegra->pmc, TEGRA_POWERGATE_SATA);

disable_regulators:
        regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);

        return ret;
}

static void tegra_ahci_power_off(struct ahci_host_priv *hpriv)
{
        struct tegra_ahci_priv *tegra = hpriv->plat_data;

        ahci_platform_disable_resources(hpriv);

        reset_control_assert(tegra->sata_rst);
        reset_control_assert(tegra->sata_oob_rst);
        reset_control_assert(tegra->sata_cold_rst);

        clk_disable_unprepare(tegra->sata_clk);
        if (!tegra->pdev->dev.pm_domain)
                tegra_pmc_powergate_power_off(tegra->pmc, TEGRA_POWERGATE_SATA);

        regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
}

static int tegra_ahci_controller_init(struct ahci_host_priv *hpriv)
{
        struct tegra_ahci_priv *tegra = hpriv->plat_data;
        int ret;
        u32 val;

        ret = tegra_ahci_power_on(hpriv);
        if (ret) {
                dev_err(&tegra->pdev->dev,
                        "failed to power on AHCI controller: %d\n", ret);
                return ret;
        }

        /*
         * Program the following SATA IPFS registers to allow SW accesses to
         * SATA's MMIO register range.
         */
        val = readl(tegra->sata_regs + SATA_FPCI_BAR5);
        val &= ~(SATA_FPCI_BAR5_START_MASK | SATA_FPCI_BAR5_ACCESS_TYPE);
        val |= SATA_FPCI_BAR5_START | SATA_FPCI_BAR5_ACCESS_TYPE;
        writel(val, tegra->sata_regs + SATA_FPCI_BAR5);

        /* Program the following SATA IPFS register to enable the SATA */
        val = readl(tegra->sata_regs + SATA_CONFIGURATION_0);
        val |= SATA_CONFIGURATION_0_EN_FPCI;
        writel(val, tegra->sata_regs + SATA_CONFIGURATION_0);

        /* Electrical settings for better link stability */
        val = T_SATA0_CHX_PHY_CTRL17_0_RX_EQ_CTRL_L_GEN1;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL17_0);
        val = T_SATA0_CHX_PHY_CTRL18_0_RX_EQ_CTRL_L_GEN2;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL18_0);
        val = T_SATA0_CHX_PHY_CTRL20_0_RX_EQ_CTRL_H_GEN1;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL20_0);
        val = T_SATA0_CHX_PHY_CTRL21_0_RX_EQ_CTRL_H_GEN2;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CHX_PHY_CTRL21_0);

        /* For SQUELCH Filter & Gen3 drive getting detected as Gen1 drive */

        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA_CFG_PHY_0);
        val |= T_SATA_CFG_PHY_0_MASK_SQUELCH;
        val &= ~T_SATA_CFG_PHY_0_USE_7BIT_ALIGN_DET_FOR_SPD;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA_CFG_PHY_0);

        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_NVOOB);
        val &= ~(tegra->soc->regs->nvoob_comma_cnt_mask |
                 T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH_MASK |
                 T_SATA0_NVOOB_SQUELCH_FILTER_MODE_MASK);
        val |= (tegra->soc->regs->nvoob_comma_cnt_val |
                T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH |
                T_SATA0_NVOOB_SQUELCH_FILTER_MODE);
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_NVOOB);

        /*
         * Change CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW from 83.3 ns to 58.8ns
         */
        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG2NVOOB_2);
        val &= ~T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW_MASK;
        val |= T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG2NVOOB_2);

        if (tegra->soc->ops && tegra->soc->ops->init)
                tegra->soc->ops->init(hpriv);

        /*
         * Program the following SATA configuration registers to
         * initialize SATA
         */
        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_1);
        val |= (T_SATA0_CFG_1_IO_SPACE | T_SATA0_CFG_1_MEMORY_SPACE |
                T_SATA0_CFG_1_BUS_MASTER | T_SATA0_CFG_1_SERR);
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_1);
        val = T_SATA0_CFG_9_BASE_ADDRESS;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_9);

        /* Program Class Code and Programming interface for SATA */
        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_SATA);
        val |= T_SATA0_CFG_SATA_BACKDOOR_PROG_IF_EN;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_SATA);

        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_BKDOOR_CC);
        val &=
            ~(T_SATA0_BKDOOR_CC_CLASS_CODE_MASK |
              T_SATA0_BKDOOR_CC_PROG_IF_MASK);
        val |= T_SATA0_BKDOOR_CC_CLASS_CODE | T_SATA0_BKDOOR_CC_PROG_IF;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_BKDOOR_CC);

        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_SATA);
        val &= ~T_SATA0_CFG_SATA_BACKDOOR_PROG_IF_EN;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_SATA);

        /* Enabling LPM capabilities through Backdoor Programming */
        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_AHCI_HBA_CAP_BKDR);
        val |= (T_SATA0_AHCI_HBA_CAP_BKDR_PARTIAL_ST_CAP |
                T_SATA0_AHCI_HBA_CAP_BKDR_SLUMBER_ST_CAP |
                T_SATA0_AHCI_HBA_CAP_BKDR_SALP |
                T_SATA0_AHCI_HBA_CAP_BKDR_SUPP_PM);
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_AHCI_HBA_CAP_BKDR);

        /* SATA Second Level Clock Gating configuration
         * Enabling Gating of Tx/Rx clocks and driving Pad IDDQ and Lane
         * IDDQ Signals
         */
        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_35);
        val &= ~T_SATA0_CFG_35_IDP_INDEX_MASK;
        val |= T_SATA0_CFG_35_IDP_INDEX;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_35);

        val = T_SATA0_AHCI_IDP1_DATA;
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_AHCI_IDP1);

        val = readl(tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_PHY_1);
        val |= (T_SATA0_CFG_PHY_1_PADS_IDDQ_EN |
                T_SATA0_CFG_PHY_1_PAD_PLL_IDDQ_EN);
        writel(val, tegra->sata_regs + SCFG_OFFSET + T_SATA0_CFG_PHY_1);

        /* Enabling IPFS Clock Gating */
        val = readl(tegra->sata_regs + SATA_CONFIGURATION_0);
        val &= ~SATA_CONFIGURATION_0_CLK_OVERRIDE;
        writel(val, tegra->sata_regs + SATA_CONFIGURATION_0);

        tegra_ahci_handle_quirks(hpriv);

        /* Unmask SATA interrupts */

        val = readl(tegra->sata_regs + SATA_INTR_MASK);
        val |= SATA_INTR_MASK_IP_INT_MASK;
        writel(val, tegra->sata_regs + SATA_INTR_MASK);

        return 0;
}

static void tegra_ahci_controller_deinit(struct ahci_host_priv *hpriv)
{
        tegra_ahci_power_off(hpriv);
}

static void tegra_ahci_host_stop(struct ata_host *host)
{
        struct ahci_host_priv *hpriv = host->private_data;

        tegra_ahci_controller_deinit(hpriv);
}

static struct ata_port_operations ahci_tegra_port_ops = {
        .inherits       = &ahci_ops,
        .host_stop      = tegra_ahci_host_stop,
};

static const struct ata_port_info ahci_tegra_port_info = {
        .flags          = AHCI_FLAG_COMMON | ATA_FLAG_NO_DIPM,
        .pio_mask       = ATA_PIO4,
        .udma_mask      = ATA_UDMA6,
        .port_ops       = &ahci_tegra_port_ops,
};

static const char *const tegra124_supply_names[] = {
        "avdd", "hvdd", "vddio", "target-5v", "target-12v"
};

static const struct tegra_ahci_ops tegra124_ahci_ops = {
        .init = tegra124_ahci_init,
};

static const struct tegra_ahci_regs tegra124_ahci_regs = {
        .nvoob_comma_cnt_mask = GENMASK(30, 28),
        .nvoob_comma_cnt_val = (7 << 28),
};

static const struct tegra_ahci_soc tegra124_ahci_soc = {
        .supply_names = tegra124_supply_names,
        .num_supplies = ARRAY_SIZE(tegra124_supply_names),
        .supports_devslp = false,
        .has_sata_oob_rst = true,
        .ops = &tegra124_ahci_ops,
        .regs = &tegra124_ahci_regs,
};

static const struct tegra_ahci_soc tegra210_ahci_soc = {
        .supports_devslp = false,
        .has_sata_oob_rst = true,
        .regs = &tegra124_ahci_regs,
};

static const struct tegra_ahci_regs tegra186_ahci_regs = {
        .nvoob_comma_cnt_mask = GENMASK(23, 16),
        .nvoob_comma_cnt_val = (7 << 16),
};

static const struct tegra_ahci_soc tegra186_ahci_soc = {
        .supports_devslp = false,
        .has_sata_oob_rst = false,
        .regs = &tegra186_ahci_regs,
};

static const struct of_device_id tegra_ahci_of_match[] = {
        {
                .compatible = "nvidia,tegra124-ahci",
                .data = &tegra124_ahci_soc
        },
        {
                .compatible = "nvidia,tegra210-ahci",
                .data = &tegra210_ahci_soc
        },
        {
                .compatible = "nvidia,tegra186-ahci",
                .data = &tegra186_ahci_soc
        },
        {}
};
MODULE_DEVICE_TABLE(of, tegra_ahci_of_match);

static const struct scsi_host_template ahci_platform_sht = {
        AHCI_SHT(DRV_NAME),
};

static int tegra_ahci_probe(struct platform_device *pdev)
{
        struct ahci_host_priv *hpriv;
        struct tegra_ahci_priv *tegra;
        struct resource *res;
        int ret;

        hpriv = ahci_platform_get_resources(pdev, 0);
        if (IS_ERR(hpriv))
                return PTR_ERR(hpriv);

        tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
        if (!tegra)
                return -ENOMEM;

        hpriv->plat_data = tegra;

        tegra->pdev = pdev;
        tegra->soc = of_device_get_match_data(&pdev->dev);

        tegra->sata_regs = devm_platform_ioremap_resource(pdev, 1);
        if (IS_ERR(tegra->sata_regs))
                return PTR_ERR(tegra->sata_regs);

        /*
         * AUX registers is optional.
         */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
        if (res) {
                tegra->sata_aux_regs = devm_ioremap_resource(&pdev->dev, res);
                if (IS_ERR(tegra->sata_aux_regs))
                        return PTR_ERR(tegra->sata_aux_regs);
        }

        tegra->sata_rst = devm_reset_control_get(&pdev->dev, "sata");
        if (IS_ERR(tegra->sata_rst)) {
                dev_err(&pdev->dev, "Failed to get sata reset\n");
                return PTR_ERR(tegra->sata_rst);
        }

        if (tegra->soc->has_sata_oob_rst) {
                tegra->sata_oob_rst = devm_reset_control_get(&pdev->dev,
                                                             "sata-oob");
                if (IS_ERR(tegra->sata_oob_rst)) {
                        dev_err(&pdev->dev, "Failed to get sata-oob reset\n");
                        return PTR_ERR(tegra->sata_oob_rst);
                }
        }

        tegra->sata_cold_rst = devm_reset_control_get(&pdev->dev, "sata-cold");
        if (IS_ERR(tegra->sata_cold_rst)) {
                dev_err(&pdev->dev, "Failed to get sata-cold reset\n");
                return PTR_ERR(tegra->sata_cold_rst);
        }

        tegra->sata_clk = devm_clk_get(&pdev->dev, "sata");
        if (IS_ERR(tegra->sata_clk)) {
                dev_err(&pdev->dev, "Failed to get sata clock\n");
                return PTR_ERR(tegra->sata_clk);
        }

        tegra->pmc = devm_tegra_pmc_get(&pdev->dev);
        if (IS_ERR(tegra->pmc))
                return dev_err_probe(&pdev->dev, PTR_ERR(tegra->pmc),
                                     "failed to get PMC\n");

        tegra->supplies = devm_kcalloc(&pdev->dev,
                                       tegra->soc->num_supplies,
                                       sizeof(*tegra->supplies), GFP_KERNEL);
        if (!tegra->supplies)
                return -ENOMEM;

        regulator_bulk_set_supply_names(tegra->supplies,
                                        tegra->soc->supply_names,
                                        tegra->soc->num_supplies);

        ret = devm_regulator_bulk_get(&pdev->dev,
                                      tegra->soc->num_supplies,
                                      tegra->supplies);
        if (ret) {
                dev_err(&pdev->dev, "Failed to get regulators\n");
                return ret;
        }

        ret = tegra_ahci_controller_init(hpriv);
        if (ret)
                return ret;

        ret = ahci_platform_init_host(pdev, hpriv, &ahci_tegra_port_info,
                                      &ahci_platform_sht);
        if (ret)
                goto deinit_controller;

        return 0;

deinit_controller:
        tegra_ahci_controller_deinit(hpriv);

        return ret;
};

static struct platform_driver tegra_ahci_driver = {
        .probe = tegra_ahci_probe,
        .remove = ata_platform_remove_one,
        .driver = {
                .name = DRV_NAME,
                .of_match_table = tegra_ahci_of_match,
        },
        /* LP0 suspend support not implemented */
};
module_platform_driver(tegra_ahci_driver);

MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
MODULE_DESCRIPTION("Tegra AHCI SATA driver");
MODULE_LICENSE("GPL v2");