tegrakernel/kernel/nvidia/drivers/ata/tegra/ahci_tegra.c

1942 lines
52 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* drivers/ata/ahci_tegra.c
*
* Copyright (c) 2016-2019, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 "ahci_tegra.h"
#include "linux/pinctrl/consumer.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#define TEGRA_AHCI_DUMP_REGS(s, fmt, args...) { if (s != NULL)\
seq_printf(s, fmt, ##args); else printk(fmt, ##args); }
#define TEGRA_AHCI_DUMP_STRING(s, str) { if (s != NULL)\
seq_puts(s, str); else printk(str); }
#endif
static int tegra_ahci_power_on(struct ahci_host_priv *hpriv);
static int tegra_ahci_enable_clks(struct ahci_host_priv *hpriv);
static void tegra_ahci_disable_clks(struct ahci_host_priv *hpriv);
static void tegra_ahci_power_off(struct ahci_host_priv *hpriv);
static int tegra_ahci_controller_init(struct ahci_host_priv *hpriv);
static void tegra_ahci_controller_deinit(struct ahci_host_priv *hpriv);
static int tegra_ahci_quirks(struct ahci_host_priv *hpriv);
static int tegra_ahci_disable_features(struct ahci_host_priv *hpriv);
static struct ahci_host_priv *
tegra_ahci_platform_get_resources(struct tegra_ahci_priv *);
static int tegra194_ahci_platform_get_resources(struct tegra_ahci_priv *tegra);
static int tegra186_ahci_platform_get_resources(struct tegra_ahci_priv *tegra);
static int tegra210_ahci_platform_get_resources(struct tegra_ahci_priv *tegra);
#ifdef CONFIG_PM
static int tegra_ahci_runtime_suspend(struct device *dev);
static int tegra_ahci_runtime_resume(struct device *dev);
#endif
#ifdef CONFIG_PM_SLEEP
static int tegra_ahci_suspend(struct device *dev);
static int tegra_ahci_resume(struct device *dev);
#endif
static void tegra_ahci_shutdown(struct platform_device *pdev);
static char * const tegra_rail_names[] = {};
static const struct tegra_ahci_soc_data tegra194_ahci_data = {
.sata_regulator_names = tegra_rail_names,
.num_sata_regulators = ARRAY_SIZE(tegra_rail_names),
.ops = {
.tegra_ahci_power_on = tegra_ahci_power_on,
.tegra_ahci_power_off = tegra_ahci_power_off,
.tegra_ahci_quirks = tegra_ahci_quirks,
.tegra_ahci_platform_get_resources =
tegra194_ahci_platform_get_resources,
},
.reg = {
.t_satao_nvoob_comma_cnt_mask = (0XFF << 16),
.t_satao_nvoob_comma_cnt = (0X07 << 16),
},
.enable_pose_edge = false,
};
static const struct tegra_ahci_soc_data tegra186_ahci_data = {
.sata_regulator_names = tegra_rail_names,
.num_sata_regulators = ARRAY_SIZE(tegra_rail_names),
.ops = {
.tegra_ahci_power_on = tegra_ahci_power_on,
.tegra_ahci_power_off = tegra_ahci_power_off,
.tegra_ahci_quirks = tegra_ahci_quirks,
.tegra_ahci_platform_get_resources =
tegra186_ahci_platform_get_resources,
},
.reg = {
.t_satao_nvoob_comma_cnt_mask = (0XFF << 16),
.t_satao_nvoob_comma_cnt = (0X07 << 16),
},
.enable_pose_edge = true,
};
static const struct tegra_ahci_soc_data tegra210_ahci_data = {
.sata_regulator_names = tegra_rail_names,
.num_sata_regulators = ARRAY_SIZE(tegra_rail_names),
.ops = {
.tegra_ahci_power_on = tegra_ahci_power_on,
.tegra_ahci_power_off = tegra_ahci_power_off,
.tegra_ahci_quirks = tegra_ahci_quirks,
.tegra_ahci_platform_get_resources =
tegra210_ahci_platform_get_resources,
},
.reg = {
.t_satao_nvoob_comma_cnt_mask = (0X7 << 28),
.t_satao_nvoob_comma_cnt = (0X7 << 28),
},
.enable_pose_edge = true,
};
static const struct of_device_id tegra_ahci_of_match[] = {
{ .compatible = "nvidia,tegra194-ahci-sata",
.data = &tegra194_ahci_data,
},
{ .compatible = "nvidia,tegra186-ahci-sata",
.data = &tegra186_ahci_data,
},
{ .compatible = "nvidia,tegra210-ahci-sata",
.data = &tegra210_ahci_data,
},
{}
};
MODULE_DEVICE_TABLE(of, tegra_ahci_of_match);
static void tegra_ahci_host_stop(struct ata_host *host)
{
struct ahci_host_priv *hpriv = host->private_data;
tegra_ahci_controller_deinit(hpriv);
}
static int tegra_ahci_port_suspend(struct ata_port *ap, pm_message_t mesg)
{
struct ata_host *host = ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct ata_link *link;
struct ata_device *dev;
int ret = 0;
u32 port_status = 0;
enum tegra_ahci_port_runtime_status lpm_state;
int i;
lpm_state = TEGRA_AHCI_PORT_RUNTIME_ACTIVE;
tegra->skip_rtpm = false;
if (!ata_dev_enabled(ap->link.device))
goto skip;
port_status = tegra_ahci_bar5_readl(hpriv, T_AHCI_PORT_PXSSTS);
port_status = (port_status & T_AHCI_PORT_PXSSTS_IPM_MASK) >>
T_AHCI_PORT_PXSSTS_IPM_SHIFT;
ata_for_each_link(link, ap, PMP_FIRST) {
if (link->flags & ATA_LFLAG_NO_LPM) {
ata_link_info(link, "No LPM on this link\n");
continue;
}
ata_for_each_dev(dev, link, ENABLED) {
bool hipm = ata_id_has_hipm(dev->id);
bool dipm = ata_id_has_dipm(dev->id) &&
(!(link->ap->flags & ATA_FLAG_NO_DIPM));
if (ap->target_lpm_policy == ATA_LPM_MIN_POWER) {
if ((hpriv->cap2 & HOST_CAP2_SDS) &&
(hpriv->cap2 & HOST_CAP2_SADM) &&
(link->device->flags & ATA_DFLAG_DEVSLP))
lpm_state =
TEGRA_AHCI_PORT_RUNTIME_DEVSLP;
else
lpm_state =
TEGRA_AHCI_PORT_RUNTIME_SLUMBER;
} else if (ap->target_lpm_policy == ATA_LPM_MED_POWER) {
lpm_state = TEGRA_AHCI_PORT_RUNTIME_PARTIAL;
}
if (hipm || dipm) {
for (i = 0; i < TEGRA_AHCI_LPM_TIMEOUT; i++) {
port_status = tegra_ahci_bar5_readl(
hpriv, T_AHCI_PORT_PXSSTS);
port_status =
(port_status &
T_AHCI_PORT_PXSSTS_IPM_MASK)
>> T_AHCI_PORT_PXSSTS_IPM_SHIFT;
if (port_status < lpm_state)
mdelay(10);
else
break;
}
if (port_status < lpm_state) {
ata_link_err(link,
"Link didn't enter LPM\n");
if (ap->pm_mesg.event & PM_EVENT_AUTO)
ret = -EBUSY;
} else {
if (!(port_status ==
TEGRA_AHCI_PORT_RUNTIME_ACTIVE))
ata_link_info(link,
"Link entered LPM\n");
}
} else {
ata_dev_info(dev,
"does not support HIPM/DIPM\n");
}
}
}
if (!ret && (lpm_state == TEGRA_AHCI_PORT_RUNTIME_ACTIVE ||
port_status == TEGRA_AHCI_PORT_RUNTIME_ACTIVE)) {
if (ap->pm_mesg.event & PM_EVENT_AUTO) {
tegra->skip_rtpm = true;
dev_info(&tegra->pdev->dev,
"Skip powergating SATA Controller\n");
return 0;
}
}
skip:
if (!ret && !(ap->pflags & ATA_PFLAG_SUSPENDED)) {
ret = ahci_ops.port_suspend(ap, mesg);
}
return ret;
}
static int tegra_ahci_port_resume(struct ata_port *ap)
{
struct ata_host *host = ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct ata_link *link = NULL;
#ifdef CONFIG_PM
struct scsi_device *sdev = NULL;
#endif
int ret = 0;
if (tegra->skip_rtpm) {
tegra->skip_rtpm = false;
if (ap->pm_mesg.event & PM_EVENT_AUTO) {
ata_for_each_link(link, ap, HOST_FIRST) {
link->eh_info.action &= ~ATA_EH_RESET;
}
ata_eh_thaw_port(ap);
return 0;
}
}
if (ap->pm_mesg.event & PM_EVENT_RESUME) {
if (ap->pm_mesg.event & PM_EVENT_AUTO)
ata_for_each_link(link, ap, HOST_FIRST) {
link->eh_info.action &= ~ATA_EH_RESET;
}
#ifdef CONFIG_PM
else
shost_for_each_device(sdev, ap->scsi_host) {
if (sdev->request_queue->rpm_status ==
RPM_SUSPENDED)
sdev->request_queue->rpm_status =
RPM_ACTIVE;
}
#endif
}
ret = ahci_ops.port_resume(ap);
if ((ap->pm_mesg.event & PM_EVENT_AUTO) &&
(ap->pm_mesg.event & PM_EVENT_RESUME))
ata_eh_thaw_port(ap);
return ret;
}
static int tegra_ahci_compliance_mode_testing;
static ssize_t tegra_ahci_compliance_mode_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ata_host *host = dev_get_drvdata(dev);
struct ahci_host_priv *hpriv = host->private_data;
u32 val;
u32 mask;
switch (buf[0]) {
case '1':
dev_info(dev, "Starting compliance mode."
" unregistering from SATA subsystem\n");
tegra_ahci_compliance_mode_testing = 1;
ata_host_detach(host);
dev_info(dev, "compliance Testing can be started\n");
break;
case '2':
dev_info(dev, "Starting Rx compliance Test\n");
tegra_ahci_compliance_mode_testing = 2;
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA1,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA2,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
val = tegra_ahci_scfg_readl(hpriv, T_SATA_CFG_PHY_0);
dev_info(dev,
"SATA0_CFG_PHY_0_0 DONT_INSERT_ALIGNS_IN_BIST_L= %x\n",
val);
val = tegra_ahci_scfg_readl(hpriv, T_SATA0_SPARE_2);
dev_info(dev,
"SATA0_SPARE_2_0 REM_TWO_ALIGNS_IN_BIST_L = %x\n", val);
val = tegra_ahci_scfg_readl(hpriv, T_SATA0_CHXCFG4_CHX);
dev_info(dev,
" SATA0_CHXCFG4_CHX_0 PHY_ALIGN_DWORD_CNT = %x\n", val);
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_CH1, T_SATA0_INDEX);
mask = T_SATA0_CFG_PHY_0_DONT_INSERT_ALIGNS_IN_BIST_L;
val = (u32) ~mask;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA_CFG_PHY_0);
tegra_ahci_scfg_update(hpriv,
T_SATA0_SPARE_2_REM_TWO_ALIGNS_IN_BIST_L,
T_SATA0_SPARE_2_REM_TWO_ALIGNS_IN_BIST_L,
T_SATA0_SPARE_2);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_CHXCFG4_CHX_DATA,
T_SATA0_CHXCFG4_CHX);
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_NONE_SELECTED,
T_SATA0_INDEX);
val = tegra_ahci_scfg_readl(hpriv, T_SATA_CFG_PHY_0);
dev_info(dev,
"SATA0_CFG_PHY_0_0 DONT_INSERT_ALIGNS_IN_BIST_L= %x\n",
val);
val = tegra_ahci_scfg_readl(hpriv, T_SATA0_SPARE_2);
dev_info(dev,
"SATA0_SPARE_2_0 REM_TWO_ALIGNS_IN_BIST_L = %x\n", val);
val = tegra_ahci_scfg_readl(hpriv, T_SATA0_CHXCFG4_CHX);
dev_info(dev,
" SATA0_CHXCFG4_CHX_0 PHY_ALIGN_DWORD_CNT = %x\n", val);
break;
case '3':
dev_info(dev, "Starting Tx compliance Test: HFTP\n");
tegra_ahci_compliance_mode_testing = 3;
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA1,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA3,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
break;
case '4':
dev_info(dev, "Starting Tx compliance Test: MFTP\n");
tegra_ahci_compliance_mode_testing = 4;
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_MFTP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA1,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_MFTP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA3,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
break;
case '5':
dev_info(dev, "Starting Tx compliance Test: LFTP\n");
tegra_ahci_compliance_mode_testing = 5;
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_LFTP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA1,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_LFTP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA3,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
break;
case '6':
dev_info(dev, "Starting Tx compliance Test: LBP\n");
tegra_ahci_compliance_mode_testing = 4;
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_LBP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA1,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_DWORD_DATA_LBP,
T_SATA0_AHCI_HBA_BIST_DWORD);
tegra_ahci_scfg_writel(hpriv,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL_DATA3,
T_SATA0_AHCI_HBA_BIST_OVERRIDE_CTL);
break;
default:
dev_info(dev, "unknown options...\n");
}
return count;
}
static ssize_t tegra_ahci_compliance_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", tegra_ahci_compliance_mode_testing);
}
static DEVICE_ATTR(tegra_ahci_compliance_mode_testing, S_IWUSR | S_IRUGO,
tegra_ahci_compliance_mode_show, tegra_ahci_compliance_mode_set);
static unsigned int tegra_ahci_qc_issue(struct ata_queued_cmd *qc)
{
if (qc->tf.command == ATA_CMD_SET_FEATURES &&
qc->tf.feature == SATA_FPDMA_OFFSET) {
WARN(1, "SATA_FPDMA_OFFSET Feature is not supported");
return AC_ERR_INVALID;
} else if (qc->tf.command == ATA_CMD_READ_LOG_EXT &&
qc->tf.lbal == ATA_LOG_SATA_NCQ) {
u8 *buf =
(u8 *) page_address((const struct page *)qc->sg->page_link);
/*
* Since our SATA Controller does not support this command
* don't send this command to the drive instead complete
* the function here and indicate to the upper layer
* that there is no entries in the buffer.
*/
buf += qc->sg->offset;
buf[0] = TEGRA_AHCI_READ_LOG_EXT_NOENTRY;
qc->complete_fn(qc);
return 0;
}
return ahci_ops.qc_issue(qc);
}
static int tegra_ahci_hardreset(struct ata_link *link, unsigned int *class,
unsigned long deadline)
{
struct ata_host *host = link->ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret;
u32 val;
u32 mask;
ret = ahci_ops.hardreset(link, class, deadline);
if (ret < 0 && tegra->soc_data->enable_pose_edge) {
mask = val = T_SATA0_CFG_LINK_0_USE_POSEDGE_SCTL_DET;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_CFG_LINK_0);
}
return ret;
}
static int tegra_ahci_softreset(struct ata_link *link, unsigned int *class,
unsigned long deadline)
{
struct ata_host *host = link->ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret;
u32 val;
u32 mask;
ret = ahci_ops.softreset(link, class, deadline);
if (ret < 0 && tegra->soc_data->enable_pose_edge) {
mask = val = T_SATA0_CFG_LINK_0_USE_POSEDGE_SCTL_DET;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_CFG_LINK_0);
}
return ret;
}
static int tegra_ahci_umh(unsigned long long block, char *block_dev)
{
char *envp[] = {
"HOME=/",
"PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
NULL };
char *argv[TEGRA_BADBLK_MAX_ARGUMENTS + 1];
char buf1[TEGRA_BADBLK_STRING_LENGTH] = { };
char buf2[TEGRA_BADBLK_STRING_LENGTH] = { };
int ret = 0;
snprintf(buf1, sizeof(buf1), "%llu", block);
snprintf(buf2, sizeof(buf2), "%s", block_dev);
argv[TEGRA_BADBLK_COMMAND] = "/system/bin/badblk.sh";
argv[TEGRA_BADBLK_COMMAND_PARAM1] = buf1;
argv[TEGRA_BADBLK_COMMAND_PARAM2] = buf2;
argv[TEGRA_BADBLK_MAX_ARGUMENTS] = NULL;
ret = call_usermodehelper(argv[TEGRA_BADBLK_COMMAND],
argv, envp, UMH_WAIT_PROC);
if (ret == -ENOENT) {
argv[TEGRA_BADBLK_COMMAND] = "/home/ubuntu/badblk.sh";
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
ret = call_usermodehelper(argv[TEGRA_BADBLK_COMMAND],
argv, envp, UMH_WAIT_PROC);
}
return ret;
}
static void tegra_ahci_badblk_manage(struct work_struct *work)
{
struct tegra_ahci_badblk_priv *badblk =
container_of(work, struct tegra_ahci_badblk_priv, badblk_work);
struct tegra_ahci_priv *tegra =
container_of(badblk, struct tegra_ahci_priv, badblk);
struct tegra_ahci_badblk_info *temp = NULL;
struct platform_device *pdev = tegra->pdev;
int ret = 0;
spin_lock(&tegra->badblk.badblk_lock);
temp = tegra->badblk.head;
if (!temp) {
spin_unlock(&tegra->badblk.badblk_lock);
return;
}
tegra->badblk.head = temp->next;
spin_unlock(&tegra->badblk.badblk_lock);
ret = tegra_ahci_umh(temp->block, temp->block_dev);
devm_kfree(&pdev->dev, temp);
}
static void tegra_ahci_unbind(struct work_struct *work)
{
struct tegra_ahci_priv *tegra =
container_of(to_delayed_work(work),
struct tegra_ahci_priv, work);
struct platform_device *pdev = tegra->pdev;
struct ata_host *host = platform_get_drvdata(pdev);
int i = 0;
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
unsigned long flags;
spin_lock_irqsave(ap->lock, flags);
if (ap && ((ap->pflags & ATA_PFLAG_LOADING) ||
(ap->pflags & ATA_PFLAG_INITIALIZING))) {
INIT_DELAYED_WORK(&tegra->work, tegra_ahci_unbind);
schedule_delayed_work(&tegra->work, msecs_to_jiffies(1000));
spin_unlock_irqrestore(ap->lock, flags);
return;
}
spin_unlock_irqrestore(ap->lock, flags);
}
tegra_ahci_shutdown(pdev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pdev->dev.driver->pm = NULL;
pdev->dev.driver->shutdown = NULL;
}
static char *tegra_ahci_get_disk_name(struct scsi_cmnd *scsicmd)
{
if (scsicmd && scsicmd->request && scsicmd->request->rq_disk)
return scsicmd->request->rq_disk->disk_name;
else
return NULL;
}
/**
* tegra_ata_tf_read_block - Read block address from ATA taskfile
* @tf: ATA taskfile of interest
* @dev: ATA device @tf belongs to
*
* LOCKING:
* None.
*
* Read block address from @tf. This function can handle all
* three address formats - LBA, LBA48 and CHS. tf->protocol and
* flags select the address format to use.
*
* RETURNS:
* Block address read from @tf.
*/
u64 tegra_ata_tf_read_block(struct ata_taskfile *tf, struct ata_device *dev)
{
u64 block = 0;
if (tf->flags & ATA_TFLAG_LBA) {
if (tf->flags & ATA_TFLAG_LBA48) {
block |= (u64)tf->hob_lbah << 40;
block |= (u64)tf->hob_lbam << 32;
block |= (u64)tf->hob_lbal << 24;
} else
block |= (tf->device & 0xf) << 24;
block |= tf->lbah << 16;
block |= tf->lbam << 8;
block |= tf->lbal;
} else {
u32 cyl, head, sect;
cyl = tf->lbam | (tf->lbah << 8);
head = tf->device & 0xf;
sect = tf->lbal;
if (!sect) {
ata_dev_warn(dev,
"device reported invalid CHS sector 0\n");
sect = 1; /* oh well */
}
block = ((u64)cyl * dev->heads + head) *
dev->sectors + sect - 1;
}
return block;
}
static void tegra_ahci_schedule_badblk_work(struct ata_queued_cmd *qc,
struct ata_device *dev)
{
unsigned long long sector;
unsigned long long block;
char *disk_name = NULL;
struct tegra_ahci_badblk_info *temp = NULL;
struct ata_host *host = qc->ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
disk_name = tegra_ahci_get_disk_name(qc->scsicmd);
sector = tegra_ata_tf_read_block(&qc->tf, dev);
block = (sector * 512) / 4096;
if (spin_trylock(&tegra->badblk.badblk_lock)) {
temp = devm_kzalloc(&pdev->dev,
sizeof(struct tegra_ahci_badblk_info),
GFP_ATOMIC);
if (temp) {
temp->block = block;
if (disk_name)
strncpy(temp->block_dev, disk_name, 99);
temp->next = tegra->badblk.head;
tegra->badblk.head = temp;
schedule_work(&tegra->badblk.badblk_work);
}
spin_unlock(&tegra->badblk.badblk_lock);
}
}
static void tegra_ahci_error_handler(struct ata_port *ap)
{
int tag;
for (tag = 0; tag < ATA_MAX_QUEUE; tag++) {
struct ata_queued_cmd *qc = __ata_qc_from_tag(ap, tag);
if ((qc->flags & ATA_QCFLAG_FAILED)
&& (qc->result_tf.feature & (ATA_UNC | ATA_AMNF))) {
struct ata_link *link;
struct ata_device *dev;
ata_for_each_link(link, qc->ap, PMP_FIRST)
ata_for_each_dev(dev, link, ENABLED)
tegra_ahci_schedule_badblk_work(qc,
dev);
}
}
ahci_ops.error_handler(ap);
if (!ata_dev_enabled(ap->link.device)) {
if (!(ap->pflags & ATA_PFLAG_SUSPENDED)) {
struct ata_host *host = ap->host;
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
INIT_DELAYED_WORK(&tegra->work, tegra_ahci_unbind);
schedule_delayed_work(&tegra->work, msecs_to_jiffies(1000));
}
}
}
static struct ata_port_operations ahci_tegra_port_ops = {
.inherits = &ahci_ops,
.qc_issue = tegra_ahci_qc_issue,
.host_stop = tegra_ahci_host_stop,
.port_suspend = tegra_ahci_port_suspend,
.port_resume = tegra_ahci_port_resume,
.hardreset = tegra_ahci_hardreset,
.softreset = tegra_ahci_softreset,
.error_handler = tegra_ahci_error_handler,
};
static struct ata_port_info ahci_tegra_port_info = {
.flags = AHCI_FLAG_COMMON,
.pio_mask = ATA_PIO4,
.udma_mask = ATA_UDMA6,
.port_ops = &ahci_tegra_port_ops,
};
static struct scsi_host_template ahci_platform_sht = {
AHCI_SHT(DRV_NAME),
};
#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM)
static void tegra_ahci_pg_save_registers(struct ata_host *host)
{
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
u32 *pg_save;
u32 regs;
int i;
pg_save = tegra->pg_save;
/* save BAR5 registers */
regs = ARRAY_SIZE(pg_save_bar5_registers);
tegra_ahci_save_regs(&pg_save, tegra->base_list[TEGRA_SATA_AHCI],
pg_save_bar5_registers, regs);
/* save BAR5 port_registers */
regs = ARRAY_SIZE(pg_save_bar5_port_registers);
for (i = 0; i < hpriv->nports; ++i)
tegra_ahci_save_regs(&pg_save,
tegra->base_list[TEGRA_SATA_AHCI] + (0x80*i),
pg_save_bar5_port_registers, regs);
/* save bkdr registers */
regs = ARRAY_SIZE(pg_save_bar5_bkdr_registers);
tegra_ahci_save_regs(&pg_save, tegra->base_list[TEGRA_SATA_AHCI],
pg_save_bar5_bkdr_registers, regs);
/* and save bkdr per_port registers */
for (i = 0; i < hpriv->nports; ++i) {
tegra_ahci_scfg_writel(hpriv, (1 << i), T_SATA0_INDEX);
regs = ARRAY_SIZE(pg_save_bar5_bkdr_port_registers);
tegra_ahci_save_regs(&pg_save,
tegra->base_list[TEGRA_SATA_AHCI] + (0x80*i),
pg_save_bar5_bkdr_port_registers,
regs);
}
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_NONE_SELECTED,
T_SATA0_INDEX);
}
static void tegra_ahci_pg_restore_registers(struct ata_host *host)
{
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
void *pg_save;
u32 regs;
int i;
pg_save = tegra->pg_save;
tegra_ahci_controller_init(hpriv);
/* restore BAR5 registers */
regs = ARRAY_SIZE(pg_save_bar5_registers);
tegra_ahci_restore_regs(&pg_save, tegra->base_list[TEGRA_SATA_AHCI],
pg_save_bar5_registers, regs);
/* restore BAR5 port_registers */
regs = ARRAY_SIZE(pg_save_bar5_port_registers);
for (i = 0; i < hpriv->nports; ++i)
tegra_ahci_restore_regs(&pg_save,
tegra->base_list[TEGRA_SATA_AHCI]+(0x80*i),
pg_save_bar5_port_registers, regs);
/* restore bkdr registers */
regs = ARRAY_SIZE(pg_restore_bar5_bkdr_registers);
tegra_ahci_restore_regs(&pg_save, tegra->base_list[TEGRA_SATA_CONFIG],
pg_restore_bar5_bkdr_registers, regs);
/* and restore BAR5 bkdr per_port registers */
for (i = 0; i < hpriv->nports; ++i) {
tegra_ahci_scfg_writel(hpriv, (1 << i), T_SATA0_INDEX);
regs = ARRAY_SIZE(pg_restore_bar5_bkdr_port_registers);
tegra_ahci_restore_regs(&pg_save,
tegra->base_list[TEGRA_SATA_CONFIG],
pg_restore_bar5_bkdr_port_registers,
regs);
}
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_NONE_SELECTED,
T_SATA0_INDEX);
}
static int tegra_ahci_pg_save_restore_init(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
u32 save_size;
int ret = 0;
/* Setup PG save/restore area: */
/* calculate the size */
save_size = ARRAY_SIZE(pg_save_bar5_registers) +
ARRAY_SIZE(pg_save_bar5_bkdr_registers);
/* and add save port_registers for all the ports */
save_size += ARRAY_SIZE(pg_save_bar5_port_registers) +
ARRAY_SIZE(pg_save_bar5_bkdr_port_registers);
/*
* save_size is number of registers times number of bytes per
* register to get total save size.
*/
save_size *= sizeof(u32);
tegra->pg_save = devm_kzalloc(dev, save_size, GFP_KERNEL);
if (!tegra->pg_save)
ret = -ENOMEM;
return 0;
}
static bool tegra_ahci_is_link_in_devslp(struct ahci_host_priv *hpriv)
{
u32 rval = 0;
rval = tegra_ahci_aux_readl(hpriv, SATA_AUX_RX_STAT_INT_0);
if (rval & SATA_DEVSLP)
return true;
return false;
}
static void tegra_ahci_override_devslp(struct ahci_host_priv *hpriv,
bool override)
{
u32 val = 0;
u32 mask = DEVSLP_OVERRIDE;
if (override)
val = DEVSLP_OVERRIDE;
else
val = 0xFFFFFF & ~DEVSLP_OVERRIDE;
tegra_ahci_aux_update(hpriv, val, mask, SATA_AUX_MISC_CNTL_1_0);
}
static int tegra_ahci_elpg_enter(struct ata_host *host)
{
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret = 0;
int i;
/*
* 1. Program the UPHY_LANE registers to put UPHY to IDDQ
*/
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->phys[i])
continue;
phy_power_off(hpriv->phys[i]);
}
/* 2. Program a register in the PMC to indicate to SATA that it is
* entering power gating. This shall drive the pmc2sata_pg_info
* signal
*/
tegra_pmc_sata_pwrgt_update(PMC_IMPL_SATA_PWRGT_0_PG_INFO,
PMC_IMPL_SATA_PWRGT_0_PG_INFO);
/*
* 3. Do the context save procedure for SATA
*/
tegra_ahci_pg_save_registers(host);
/*
* 4. Check the assertion status of DEVSLP and
* set the DEVSLP override with the following SATA AUX
* registers accordingly.
*/
tegra->devslp_override = tegra_ahci_is_link_in_devslp(hpriv);
if (tegra->devslp_override)
tegra_ahci_override_devslp(hpriv, true);
/* 5. Powergate */
tegra_ahci_disable_clks(hpriv);
return ret;
}
static int tegra_ahci_elpg_exit(struct ata_host *host)
{
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret = 0;
u32 val;
u32 mask;
int i;
/* 1. unpowergate */
ret = tegra_ahci_enable_clks(hpriv);
if (ret)
return ret;
/* 2. Restore SATA Registers */
tegra_ahci_pg_restore_registers(host);
/*
* During the restoration of the registers, the driver would now need to
* restore the register T_SATA0_CFG_POWER_GATE_SSTS_RESTORED after the
* ssts_det, ssts_spd are restored. This register is used to tell the
* controller whether a drive existed earlier or not and move the PHY
* state machines into either HR_slumber or not.
*/
tegra_ahci_scfg_update(hpriv, T_SATA0_CFG_POWER_GATE_SSTS_RESTORED,
T_SATA0_CFG_POWER_GATE_SSTS_RESTORED,
T_SATA0_CFG_POWER_GATE);
/* 3. If devslp asserted, de-assert devslp */
if (tegra->devslp_override) {
tegra_ahci_override_devslp(hpriv, false);
val = mask = MDAT_TIMER_AFTER_PG_VALID;
tegra_ahci_aux_update(hpriv, val, mask, SATA_AUX_SPARE_CFG0_0);
}
/* 4. Program a register in the PMC to indicate to SATA that it is
* entering power gating. This shall drive the pmc2sata_pg_info
* signal
*/
tegra_pmc_sata_pwrgt_update(PMC_IMPL_SATA_PWRGT_0_PG_INFO,
~PMC_IMPL_SATA_PWRGT_0_PG_INFO);
if (tegra->devslp_override) {
tegra->devslp_override = false;
val = mask = T_SATA0_CFG_POWER_GATE_POWER_UNGATE_COMP;
tegra_ahci_scfg_update(hpriv, val, mask,
T_SATA0_CFG_POWER_GATE);
}
/*
* 5. Program the UPHY_LANE registers to bring up UPHY from IDDQ
*/
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->phys[i])
continue;
ret = phy_power_on(hpriv->phys[i]);
if (ret) {
phy_exit(hpriv->phys[i]);
goto disable_phys;
}
}
return 0;
disable_phys:
while (--i >= 0) {
phy_power_off(hpriv->phys[i]);
phy_exit(hpriv->phys[i]);
}
return ret;
}
#endif
#ifdef CONFIG_PM
static int tegra_ahci_runtime_suspend(struct device *dev)
{
struct ata_host *host = dev_get_drvdata(dev);
int ret = 0;
ret = tegra_ahci_elpg_enter(host);
return ret;
}
static int tegra_ahci_runtime_resume(struct device *dev)
{
struct ata_host *host = dev_get_drvdata(dev);
int ret = 0;
ret = tegra_ahci_elpg_exit(host);
return ret;
}
#endif
#ifdef CONFIG_PM_SLEEP
static int tegra_ahci_suspend(struct device *dev)
{
struct ata_host *host = dev_get_drvdata(dev);
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret;
int i;
if (!pm_runtime_suspended(dev)) {
ret = ahci_platform_suspend_host(dev);
if (ret)
return ret;
ret = tegra_ahci_elpg_enter(host);
if (ret)
return ret;
}
/*
* If DEVSLP is asserted, PAD driver should enable the pull-up
* of the GPIO pin by programming the following register before
* entering LP0.
*/
if (tegra->devslp_override) {
/* Yet to add the code - http://nvbugs/200132422 */
tegra->devslp_pinmux_override = true;
}
/* Place uphy to reset */
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->phys[i])
continue;
phy_exit(hpriv->phys[i]);
}
return 0;
}
static int tegra_ahci_resume(struct device *dev)
{
struct ata_host *host = dev_get_drvdata(dev);
struct ahci_host_priv *hpriv = host->private_data;
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret;
int i;
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->phys[i])
continue;
ret = phy_init(hpriv->phys[i]);
if (ret)
goto disable_phys;
}
ret = tegra_ahci_elpg_exit(host);
if (ret)
goto disable_phys;
/*
* If DEVSLP is supported and GPIO pin is assigned to SATA,
* PAD driver should disable the pull-up of the GPIO pin
* by programming the following register after exiting LP0
* and the DEVSLP override in SATA AUX register has been set.
*/
if (tegra->devslp_pinmux_override) {
/* Yet to add the code - http://nvbugs/200132422 */
tegra->devslp_pinmux_override = false;
}
ret = ahci_platform_resume_host(dev);
if (ret)
goto elpg_entry;
/* We resumed so update PM runtime state */
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
return 0;
elpg_entry:
tegra_ahci_elpg_enter(host);
disable_phys:
while (--i >= 0)
phy_exit(hpriv->phys[i]);
return ret;
}
#endif
static const struct dev_pm_ops tegra_ahci_dev_rt_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tegra_ahci_suspend, tegra_ahci_resume)
SET_RUNTIME_PM_OPS(tegra_ahci_runtime_suspend,
tegra_ahci_runtime_resume, NULL)
};
static int tegra_ahci_enable_clks(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
int ret;
ret = clk_prepare_enable(tegra->sata_clk);
if (ret)
return ret;
ret = clk_prepare_enable(tegra->sata_oob_clk);
if (ret)
clk_disable_unprepare(tegra->sata_clk);
return ret;
}
static void tegra_ahci_disable_clks(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
clk_disable_unprepare(tegra->sata_oob_clk);
clk_disable_unprepare(tegra->sata_clk);
}
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_data->num_sata_regulators,
tegra->supplies);
if (ret)
return ret;
reset_control_assert(tegra->sata_rst);
if (tegra->sata_oob_rst)
reset_control_assert(tegra->sata_oob_rst);
reset_control_assert(tegra->sata_cold_rst);
/* set SATA clk and SATA_OOB clk source */
ret = clk_set_parent(tegra->sata_clk, tegra->pllp_uphy_clk);
if (ret)
goto disable_regulators;
ret = clk_set_parent(tegra->sata_oob_clk, tegra->pllp_clk);
if (ret)
goto disable_regulators;
ret = clk_set_rate(tegra->sata_clk, TEGRA_SATA_CORE_CLOCK_FREQ_HZ);
if (ret)
goto disable_regulators;
ret = clk_set_rate(tegra->sata_oob_clk, TEGRA_SATA_OOB_CLOCK_FREQ_HZ);
if (ret)
goto disable_regulators;
ret = ahci_platform_enable_resources(hpriv);
if (ret)
goto disable_regulators;
reset_control_deassert(tegra->sata_rst);
if (tegra->sata_oob_rst)
reset_control_deassert(tegra->sata_oob_rst);
reset_control_deassert(tegra->sata_cold_rst);
ret = clk_prepare_enable(tegra->sata_clk);
if (ret)
goto disable_regulators;
ret = clk_prepare_enable(tegra->sata_oob_clk);
if (ret)
goto disable_sata_clk;
return 0;
disable_sata_clk:
clk_disable_unprepare(tegra->sata_clk);
disable_regulators:
regulator_bulk_disable(tegra->soc_data->num_sata_regulators,
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);
if (tegra->sata_oob_rst)
reset_control_assert(tegra->sata_oob_rst);
reset_control_assert(tegra->sata_cold_rst);
clk_disable_unprepare(tegra->sata_clk);
clk_disable_unprepare(tegra->sata_oob_clk);
regulator_bulk_disable(tegra->soc_data->num_sata_regulators,
tegra->supplies);
}
static int tegra_ahci_controller_init(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
unsigned int val;
unsigned int mask;
int ret = 0;
/* Program the following SATA IPFS registers
* to allow SW accesses to SATA.s MMIO Register */
mask = FPCI_BAR5_START_MASK | FPCI_BAR5_ACCESS_TYPE;
val = FPCI_BAR5_START | FPCI_BAR5_ACCESS_TYPE;
tegra_ahci_sata_update(hpriv, val, mask, SATA_FPCI_BAR5_0);
/* Program the following SATA IPFS register to enable the SATA */
val = SATA_CONFIGURATION_0_EN_FPCI;
tegra_ahci_sata_update(hpriv, val, val, SATA_CONFIGURATION_0);
/* Electrical settings for better link stability */
tegra_ahci_scfg_writel(hpriv, T_SATA_CHX_PHY_CTRL17_RX_EQ_CTRL_L_GEN1,
T_SATA_CHX_PHY_CTRL17);
tegra_ahci_scfg_writel(hpriv, T_SATA_CHX_PHY_CTRL18_RX_EQ_CTRL_L_GEN2,
T_SATA_CHX_PHY_CTRL18);
tegra_ahci_scfg_writel(hpriv, T_SATA_CHX_PHY_CTRL20_RX_EQ_CTRL_H_GEN1,
T_SATA_CHX_PHY_CTRL20);
tegra_ahci_scfg_writel(hpriv, T_SATA_CHX_PHY_CTRL21_RX_EQ_CTRL_H_GEN2,
T_SATA_CHX_PHY_CTRL21);
/* tegra ahci quirks */
ret = tegra->soc_data->ops.tegra_ahci_quirks(hpriv);
if (ret)
return ret;
/* Configure SATA Configuration registers*/
/* Program the following SATA configuration registers
* to initialize SATA */
val = (T_SATA_CFG_1_IO_SPACE | T_SATA_CFG_1_MEMORY_SPACE |
T_SATA_CFG_1_BUS_MASTER | T_SATA_CFG_1_SERR);
tegra_ahci_scfg_update(hpriv, val, val, T_SATA_CFG_1);
tegra_ahci_scfg_writel(hpriv, T_SATA_CFG_9_BASE_ADDRESS, T_SATA_CFG_9);
/* Program Class Code and Programming interface for SATA */
val = T_SATA_CFG_SATA_BACKDOOR_PROG_IF_EN;
tegra_ahci_scfg_update(hpriv, val, val, T_SATA_CFG_SATA);
mask = T_SATA_BKDOOR_CC_CLASS_CODE_MASK | T_SATA_BKDOOR_CC_PROG_IF_MASK;
val = T_SATA_BKDOOR_CC_CLASS_CODE | T_SATA_BKDOOR_CC_PROG_IF;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA_BKDOOR_CC);
mask = T_SATA_CFG_SATA_BACKDOOR_PROG_IF_EN;
val = (u32) ~T_SATA_CFG_SATA_BACKDOOR_PROG_IF_EN;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA_CFG_SATA);
/* Enabling LPM capabilities through Backdoor Programming */
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);
tegra_ahci_scfg_update(hpriv, val, val, 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 */
mask = T_SATA0_CFG_35_IDP_INDEX_MASK;
val = T_SATA0_CFG_35_IDP_INDEX;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_CFG_35);
tegra_ahci_scfg_writel(hpriv, T_SATA0_AHCI_IDP1_DATA,
T_SATA0_AHCI_IDP1);
val = (T_SATA0_CFG_PHY_1_PADS_IDDQ_EN |
T_SATA0_CFG_PHY_1_PAD_PLL_IDDQ_EN);
tegra_ahci_scfg_update(hpriv, val, val, T_SATA0_CFG_PHY_1);
/*
* Indicate Sata only has the capability to enter DevSleep
* from slumber link.
*/
tegra_ahci_aux_update(hpriv, DESO_SUPPORT, DESO_SUPPORT,
SATA_AUX_MISC_CNTL_1_0);
/* Enabling IPFS Clock Gating */
mask = SATA_CONFIGURATION_CLK_OVERRIDE;
val = (u32) ~SATA_CONFIGURATION_CLK_OVERRIDE;
tegra_ahci_sata_update(hpriv, val, mask, SATA_CONFIGURATION_0);
val = IP_INT_MASK;
tegra_ahci_sata_update(hpriv, val, val, SATA_INTR_MASK_0);
ret = tegra_ahci_disable_features(hpriv);
return ret;
}
static void tegra_ahci_controller_deinit(struct ahci_host_priv *hpriv)
{
#ifdef CONFIG_PM
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
if (tegra_platform_is_silicon())
tegra_ahci_runtime_suspend(dev);
pm_runtime_disable(dev);
#endif
}
static void tegra_ahci_disable_devslp(struct ahci_host_priv *hpriv)
{
u32 val = 0;
u32 mask = SDS_SUPPORT;
val = 0xFFFFFFFF & ~SDS_SUPPORT;
tegra_ahci_aux_update(hpriv, val, mask, SATA_AUX_MISC_CNTL_1_0);
}
static void tegra_ahci_disable_hipm(struct ahci_host_priv *hpriv)
{
u32 val = 0;
u32 mask = T_SATA0_AHCI_HBA_CAP_BKDR_SALP;
val = 0xFFFFFFFF & ~T_SATA0_AHCI_HBA_CAP_BKDR_SALP;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_AHCI_HBA_CAP_BKDR);
}
static void tegra_ahci_disable_partial(struct ahci_host_priv *hpriv)
{
u32 val = 0;
u32 mask = T_SATA0_AHCI_HBA_CAP_BKDR_PARTIAL_ST_CAP;
val = 0xFFFFFFFF & ~T_SATA0_AHCI_HBA_CAP_BKDR_PARTIAL_ST_CAP;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_AHCI_HBA_CAP_BKDR);
}
static void tegra_ahci_disable_slumber(struct ahci_host_priv *hpriv)
{
u32 val = 0;
u32 mask = T_SATA0_AHCI_HBA_CAP_BKDR_SLUMBER_ST_CAP;
val = 0xFFFFFFFF & ~T_SATA0_AHCI_HBA_CAP_BKDR_SLUMBER_ST_CAP;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_AHCI_HBA_CAP_BKDR);
}
static void tegra_ahci_disable_ncq(struct ahci_host_priv *hpriv)
{
u32 val = 0;
u32 mask = T_SATA0_AHCI_HBA_CAP_BKDR_SNCQ;
val = 0xFFFFFFFF & ~T_SATA0_AHCI_HBA_CAP_BKDR_SNCQ;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_AHCI_HBA_CAP_BKDR);
}
static int tegra_ahci_disable_features(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct property *prop;
const char *feature;
bool devslp_enabled = true;
int ret = 0;
if (of_property_count_strings(np, "nvidia,disable-features") <= 0)
return 0;
of_property_for_each_string(np, "nvidia,disable-features", prop,
feature) {
if (!strcmp(feature, "devslp")) {
tegra_ahci_disable_devslp(hpriv);
devslp_enabled = false;
} else if (!strcmp(feature, "hipm")) {
tegra_ahci_disable_hipm(hpriv);
} else if (!strcmp(feature, "ncq")) {
tegra_ahci_disable_ncq(hpriv);
} else if (!strcmp(feature, "dipm")) {
ahci_tegra_port_info.flags |= ATA_FLAG_NO_DIPM;
} else if (!strcmp(feature, "partial")) {
tegra_ahci_disable_partial(hpriv);
} else if (!strcmp(feature, "slumber")) {
tegra_ahci_disable_slumber(hpriv);
}
}
if (devslp_enabled) {
if (gpio_is_valid(tegra->devslp_gpio)) {
ret = gpio_request(tegra->devslp_gpio,
"ahci-tegra");
if (ret != 0) {
dev_err(&pdev->dev,
"gpio request failed\n");
return ret;
}
gpio_direction_output(tegra->devslp_gpio, 1);
}
if (tegra->devslp_pin) {
ret = pinctrl_select_state(tegra->devslp_pin,
tegra->devslp_active);
if (ret < 0) {
dev_err(&pdev->dev,
"setting devslp pin state failed\n");
return ret;
}
}
}
return ret;
}
static int tegra_ahci_quirks(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
u32 t_satao_nvoob_comma_cnt_mask =
tegra->soc_data->reg.t_satao_nvoob_comma_cnt_mask;
u32 t_satao_nvoob_comma_cnt =
tegra->soc_data->reg.t_satao_nvoob_comma_cnt;
unsigned int val;
unsigned int mask;
int ret = 0;
/* SATA WARS */
/* For SQUELCH Filter & Gen3 drive getting detected as Gen1 drive */
mask = T_SATA_CFG_PHY_0_MASK_SQUELCH |
T_SATA_CFG_PHY_0_USE_7BIT_ALIGN_DET_FOR_SPD;
val = T_SATA_CFG_PHY_0_MASK_SQUELCH;
val &= ~T_SATA_CFG_PHY_0_USE_7BIT_ALIGN_DET_FOR_SPD;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA_CFG_PHY_0);
mask = (t_satao_nvoob_comma_cnt_mask |
T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH_MASK |
T_SATA0_NVOOB_SQUELCH_FILTER_MODE_MASK);
val = (t_satao_nvoob_comma_cnt | T_SATA0_NVOOB_SQUELCH_FILTER_LENGTH |
T_SATA0_NVOOB_SQUELCH_FILTER_MODE);
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_NVOOB);
/* Change CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW from 83.3 ns to 58.8ns */
mask = T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW_MASK;
val = T_SATA0_CFG2NVOOB_2_COMWAKE_IDLE_CNT_LOW;
tegra_ahci_scfg_update(hpriv, val, mask, T_SATA0_CFG2NVOOB_2);
if (tegra && tegra->prod_list) {
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_CH1, T_SATA0_INDEX);
ret = tegra_prod_set_by_name(tegra->base_list, "prod",
tegra->prod_list);
if (ret)
dev_err(dev, "Prod setting from DT failed\n");
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_NONE_SELECTED,
T_SATA0_INDEX);
}
return ret;
}
static int
tegra_ahci_platform_get_clks_resets(struct tegra_ahci_priv *tegra)
{
struct platform_device *pdev = tegra->pdev;
int ret = 0;
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");
ret = PTR_ERR(tegra->sata_clk);
goto err_out;
}
tegra->sata_oob_clk = devm_clk_get(&pdev->dev, "sata-oob");
if (IS_ERR(tegra->sata_oob_clk)) {
dev_err(&pdev->dev, "Failed to get sata_oob clock\n");
ret = PTR_ERR(tegra->sata_oob_clk);
goto err_out;
}
tegra->pllp_clk = devm_clk_get(&pdev->dev, "pllp");
if (IS_ERR(tegra->pllp_clk)) {
dev_err(&pdev->dev, "Failed to get pllp clock\n");
ret = PTR_ERR(tegra->pllp_clk);
goto err_out;
}
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");
ret = PTR_ERR(tegra->sata_rst);
goto err_out;
}
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");
ret = PTR_ERR(tegra->sata_cold_rst);
goto err_out;
}
return 0;
err_out:
return ret;
}
static int
tegra_ahci_platform_get_memory_resources(struct tegra_ahci_priv *tegra)
{
struct platform_device *pdev = tegra->pdev;
struct resource *res;
int ret = 0;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sata-ahci");
tegra->base_list[TEGRA_SATA_AHCI] = devm_ioremap_resource(&pdev->dev,
res);
if (IS_ERR(tegra->base_list[TEGRA_SATA_AHCI])) {
ret = PTR_ERR(tegra->base_list[TEGRA_SATA_AHCI]);
goto err_out;
}
tegra->res[TEGRA_SATA_AHCI] = res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sata-config");
tegra->base_list[TEGRA_SATA_CONFIG] =
devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tegra->base_list[TEGRA_SATA_CONFIG])) {
ret = PTR_ERR(tegra->base_list[TEGRA_SATA_CONFIG]);
goto err_out;
}
tegra->res[TEGRA_SATA_CONFIG] = res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sata-ipfs");
tegra->base_list[TEGRA_SATA_IPFS] = devm_ioremap_resource(&pdev->dev,
res);
if (IS_ERR(tegra->base_list[TEGRA_SATA_IPFS])) {
ret = PTR_ERR(tegra->base_list[TEGRA_SATA_IPFS]);
goto err_out;
}
tegra->res[TEGRA_SATA_IPFS] = res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sata-aux");
tegra->base_list[TEGRA_SATA_AUX] = devm_ioremap_resource(&pdev->dev,
res);
if (IS_ERR(tegra->base_list[TEGRA_SATA_AUX])) {
ret = PTR_ERR(tegra->base_list[TEGRA_SATA_AUX]);
goto err_out;
}
tegra->res[TEGRA_SATA_AUX] = res;
return 0;
err_out:
return ret;
}
static int tegra_ahci_set_lpm(struct ahci_host_priv *hpriv)
{
struct tegra_ahci_priv *tegra = hpriv->plat_data;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
struct ata_host *host = dev_get_drvdata(dev);
struct device_node *np = dev->of_node;
struct property *prop;
const char *feature;
enum ata_lpm_policy policy = ATA_LPM_MAX_POWER;
int ret = 0;
int i;
if (of_property_count_strings(np, "nvidia,link-flags") <= 0)
return 0;
of_property_for_each_string(np, "nvidia,link-flags", prop,
feature) {
if (!strcmp(feature, "min_power"))
policy = ATA_LPM_MIN_POWER;
else if (!strcmp(feature, "max_power"))
policy = ATA_LPM_MAX_POWER;
else if (!strcmp(feature, "med_power"))
policy = ATA_LPM_MED_POWER;
}
if (policy > ATA_LPM_MAX_POWER) {
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
ap->target_lpm_policy = policy;
ata_port_schedule_eh(ap);
}
}
return ret;
}
static int tegra194_ahci_platform_get_resources(struct tegra_ahci_priv *tegra)
{
tegra->pllp_uphy_clk = tegra->pllp_clk;
tegra->sata_oob_rst = NULL;
return 0;
}
static int tegra186_ahci_platform_get_resources(struct tegra_ahci_priv *tegra)
{
struct platform_device *pdev = tegra->pdev;
tegra->pllp_uphy_clk = devm_clk_get(&pdev->dev, "pllp-uphy");
if (IS_ERR(tegra->pllp_uphy_clk)) {
dev_err(&pdev->dev, "Failed to get pllp_uphy clock\n");
return PTR_ERR(tegra->pllp_uphy_clk);
}
tegra->sata_oob_rst = NULL;
return 0;
}
static int tegra210_ahci_platform_get_resources(struct tegra_ahci_priv *tegra)
{
struct platform_device *pdev = tegra->pdev;
tegra->pllp_uphy_clk = tegra->pllp_clk;
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);
}
return 0;
}
static struct ahci_host_priv *
tegra_ahci_platform_get_resources(struct tegra_ahci_priv *tegra)
{
struct ahci_host_priv *hpriv;
struct platform_device *pdev = tegra->pdev;
struct device *dev = &pdev->dev;
struct phy *phy = NULL;
int ret = -ENOMEM;
int i;
int sz;
hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL);
if (!hpriv) {
ret = PTR_ERR(hpriv);
goto err_out;
}
hpriv->plat_data = tegra;
tegra->devslp_pin = devm_pinctrl_get(dev);
if (IS_ERR(tegra->devslp_pin)) {
ret = PTR_ERR(tegra->devslp_pin);
dev_err(dev, "failed to get pinctrl: %d\n", ret);
goto err_out;
}
tegra->devslp_active = pinctrl_lookup_state(tegra->devslp_pin,
"devslp_active");
if (IS_ERR(tegra->devslp_active)) {
dev_info(dev, "Missing devslp-active state\n");
tegra->devslp_pin = NULL;
} else {
tegra->devslp_pullup = pinctrl_lookup_state(tegra->devslp_pin,
"devslp_pullup");
if (IS_ERR(tegra->devslp_pullup)) {
dev_info(dev, "Missing devslp-pullup state\n");
tegra->devslp_pin = NULL;
}
}
tegra->devslp_gpio =
of_get_named_gpio(dev->of_node, "gpios", 0);
ret = tegra_ahci_platform_get_memory_resources(tegra);
if (ret)
goto err_out;
else
hpriv->mmio = tegra->base_list[TEGRA_SATA_AHCI];
sz = sizeof(*hpriv->target_pwrs);
hpriv->target_pwrs = devm_kzalloc(dev, sz, GFP_KERNEL);
if (!hpriv->target_pwrs) {
ret = -ENOMEM;
goto err_out;
}
hpriv->target_pwrs[0] = devm_regulator_get_optional(dev, "target-3v3");
if (IS_ERR(hpriv->target_pwrs[0])) {
ret = PTR_ERR(hpriv->target_pwrs[0]);
if (ret == -EPROBE_DEFER)
goto err_out;
hpriv->target_pwrs[0] = NULL;
}
ret = tegra_ahci_platform_get_clks_resets(tegra);
if (ret)
goto err_out;
if (tegra_platform_is_silicon()) {
phy = devm_phy_optional_get(dev, "sata-phy");
if (!IS_ERR(phy)) {
hpriv->phys = devm_kzalloc(dev, sizeof(*hpriv->phys),
GFP_KERNEL);
if (!hpriv->phys) {
ret = -ENOMEM;
goto err_out;
}
hpriv->phys[0] = phy;
hpriv->nports = 1;
} else {
ret = PTR_ERR(phy);
goto err_out;
}
} else {
hpriv->nports = 1;
}
tegra->supplies = devm_kzalloc(dev,
sizeof(*tegra->supplies) *
tegra->soc_data->num_sata_regulators,
GFP_KERNEL);
if (IS_ERR(tegra->supplies)) {
dev_err(dev, "memory allocation failed for tegra supplies\n");
ret = PTR_ERR(tegra->supplies);
goto err_out;
}
for (i = 0; i < tegra->soc_data->num_sata_regulators; i++)
tegra->supplies[i].supply =
tegra->soc_data->sata_regulator_names[i];
ret = devm_regulator_bulk_get(dev, tegra->soc_data->num_sata_regulators,
tegra->supplies);
if (ret) {
dev_err(&pdev->dev, "Failed to get regulators\n");
goto err_out;
}
ret = tegra->soc_data->ops.tegra_ahci_platform_get_resources(tegra);
if (ret)
goto err_out;
tegra->prod_list = devm_tegra_prod_get(dev);
if (IS_ERR(tegra->prod_list)) {
dev_err(dev, "Prod Init failed\n");
tegra->prod_list = NULL;
}
#ifdef CONFIG_PM
ret = tegra_ahci_pg_save_restore_init(hpriv);
if (ret) {
dev_err(&pdev->dev,
"Failed to allocate memory for save and restore\n");
goto err_out;
}
#endif
return hpriv;
err_out:
return ERR_PTR(ret);
}
static void tegra_ahci_shutdown(struct platform_device *pdev)
{
if (!pm_runtime_suspended(&pdev->dev))
ata_platform_remove_one(pdev);
}
static int tegra_ahci_probe(struct platform_device *pdev)
{
struct ahci_host_priv *hpriv;
struct tegra_ahci_priv *tegra;
const struct of_device_id *match = NULL;
int ret;
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
tegra->pdev = pdev;
match = of_match_device(of_match_ptr(tegra_ahci_of_match),
&pdev->dev);
if (!match)
return -ENODEV;
tegra->soc_data =
(struct tegra_ahci_soc_data *)match->data;
hpriv = tegra_ahci_platform_get_resources(tegra);
if (IS_ERR(hpriv))
return PTR_ERR(hpriv);
if (tegra_platform_is_silicon()) {
ret = tegra->soc_data->ops.tegra_ahci_power_on(hpriv);
if (ret)
return ret;
}
ret = tegra_ahci_controller_init(hpriv);
if (ret)
goto poweroff_controller;
INIT_WORK(&tegra->badblk.badblk_work, tegra_ahci_badblk_manage);
tegra->badblk.head = NULL;
spin_lock_init(&tegra->badblk.badblk_lock);
ret = ahci_platform_init_host(pdev, hpriv, &ahci_tegra_port_info,
&ahci_platform_sht);
if (ret)
goto poweroff_controller;
ret = tegra_ahci_set_lpm(hpriv);
if (ret) {
dev_err(&pdev->dev,
"Failed to set lpm\n");
goto poweroff_controller;
}
ret = pm_runtime_set_active(&pdev->dev);
if (ret)
dev_dbg(&pdev->dev, "unable to set runtime pm active err=%d\n",
ret);
else
pm_runtime_enable(&pdev->dev);
ret = device_create_file(&pdev->dev,
&dev_attr_tegra_ahci_compliance_mode_testing);
if (ret)
dev_warn(&pdev->dev,
"Failed to create compliance mode attricute err=%d\n", ret);
#ifdef CONFIG_DEBUG_FS
tegra_ahci_dump_debuginit(hpriv);
#endif
return 0;
poweroff_controller:
if (tegra_platform_is_silicon())
tegra->soc_data->ops.tegra_ahci_power_off(hpriv);
return ret;
};
static struct platform_driver tegra_ahci_driver = {
.probe = tegra_ahci_probe,
.remove = ata_platform_remove_one,
.shutdown = tegra_ahci_shutdown,
.driver = {
.name = "tegra-ahci",
.of_match_table = tegra_ahci_of_match,
#ifdef CONFIG_PM
.pm = &tegra_ahci_dev_rt_ops,
#endif
},
};
module_platform_driver(tegra_ahci_driver);
#ifdef CONFIG_DEBUG_FS
static void tegra_ahci_dbg_print_regs(struct seq_file *s, u32 *ptr,
u32 base, u32 regs)
{
#define REGS_PER_LINE 4
u32 i, j;
u32 lines = regs / REGS_PER_LINE;
for (i = 0; i < lines; i++) {
TEGRA_AHCI_DUMP_REGS(s, "0x%08x: ", base+(i*16));
for (j = 0; j < REGS_PER_LINE; ++j) {
TEGRA_AHCI_DUMP_REGS(s, "0x%08x ", readl(ptr));
++ptr;
}
TEGRA_AHCI_DUMP_STRING(s, "\n");
}
#undef REGS_PER_LINE
}
int tegra_ahci_dbg_dump_show(struct seq_file *s, void *data)
{
struct ahci_host_priv *hpriv = NULL;
struct tegra_ahci_priv *tegra = NULL;
u32 base;
u32 *ptr;
u32 i;
if (s)
hpriv = s->private;
else
hpriv = data;
tegra = hpriv->plat_data;
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_CH1, T_SATA0_INDEX);
base = tegra->res[TEGRA_SATA_CONFIG]->start;
ptr = tegra->base_list[TEGRA_SATA_CONFIG];
TEGRA_AHCI_DUMP_STRING(s, "SATA CONFIG Registers:\n");
TEGRA_AHCI_DUMP_STRING(s, "----------------------\n");
tegra_ahci_dbg_print_regs(s, ptr, base, 0x400);
base = tegra->res[TEGRA_SATA_AHCI]->start;
ptr = hpriv->mmio;
TEGRA_AHCI_DUMP_STRING(s, "\nAHCI HBA Registers:\n");
TEGRA_AHCI_DUMP_STRING(s, "-------------------\n");
tegra_ahci_dbg_print_regs(s, ptr, base, 64);
for (i = 0; i < hpriv->nports; ++i) {
base = (tegra->res[TEGRA_SATA_AHCI]->start) + 0x100 + (0x80*i);
ptr = hpriv->mmio + 0x100;
TEGRA_AHCI_DUMP_REGS(s, "\nPort %u Registers:\n", i);
TEGRA_AHCI_DUMP_STRING(s, "---------------\n");
tegra_ahci_dbg_print_regs(s, ptr, base, 20);
}
tegra_ahci_scfg_writel(hpriv, T_SATA0_INDEX_NONE_SELECTED,
T_SATA0_INDEX);
return 0;
}
static int tegra_ahci_dbg_dump_open(struct inode *inode, struct file *file)
{
return single_open(file, tegra_ahci_dbg_dump_show, inode->i_private);
}
static const struct file_operations debug_fops = {
.open = tegra_ahci_dbg_dump_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
int tegra_ahci_dump_debuginit(void *data)
{
(void) debugfs_create_file("tegra_ahci", S_IRUGO,
NULL, data, &debug_fops);
return 0;
}
EXPORT_SYMBOL(tegra_ahci_dump_debuginit);
#endif
MODULE_DESCRIPTION("Tegra AHCI SATA driver");
MODULE_LICENSE("GPL v2");