18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * AHCI SATA platform library
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2004-2005  Red Hat, Inc.
68c2ecf20Sopenharmony_ci *   Jeff Garzik <jgarzik@pobox.com>
78c2ecf20Sopenharmony_ci * Copyright 2010  MontaVista Software, LLC.
88c2ecf20Sopenharmony_ci *   Anton Vorontsov <avorontsov@ru.mvista.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/clk.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/gfp.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/pm.h>
168c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
178c2ecf20Sopenharmony_ci#include <linux/device.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci#include <linux/libata.h>
208c2ecf20Sopenharmony_ci#include <linux/ahci_platform.h>
218c2ecf20Sopenharmony_ci#include <linux/phy/phy.h>
228c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
238c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
248c2ecf20Sopenharmony_ci#include <linux/reset.h>
258c2ecf20Sopenharmony_ci#include "ahci.h"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic void ahci_host_stop(struct ata_host *host);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct ata_port_operations ahci_platform_ops = {
308c2ecf20Sopenharmony_ci	.inherits	= &ahci_ops,
318c2ecf20Sopenharmony_ci	.host_stop	= ahci_host_stop,
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_ops);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci/**
368c2ecf20Sopenharmony_ci * ahci_platform_enable_phys - Enable PHYs
378c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
388c2ecf20Sopenharmony_ci *
398c2ecf20Sopenharmony_ci * This function enables all the PHYs found in hpriv->phys, if any.
408c2ecf20Sopenharmony_ci * If a PHY fails to be enabled, it disables all the PHYs already
418c2ecf20Sopenharmony_ci * enabled in reverse order and returns an error.
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * RETURNS:
448c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
458c2ecf20Sopenharmony_ci */
468c2ecf20Sopenharmony_ciint ahci_platform_enable_phys(struct ahci_host_priv *hpriv)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	int rc, i;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	for (i = 0; i < hpriv->nports; i++) {
518c2ecf20Sopenharmony_ci		rc = phy_init(hpriv->phys[i]);
528c2ecf20Sopenharmony_ci		if (rc)
538c2ecf20Sopenharmony_ci			goto disable_phys;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci		rc = phy_set_mode(hpriv->phys[i], PHY_MODE_SATA);
568c2ecf20Sopenharmony_ci		if (rc) {
578c2ecf20Sopenharmony_ci			phy_exit(hpriv->phys[i]);
588c2ecf20Sopenharmony_ci			goto disable_phys;
598c2ecf20Sopenharmony_ci		}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci		rc = phy_power_on(hpriv->phys[i]);
628c2ecf20Sopenharmony_ci		if (rc && !(rc == -EOPNOTSUPP && (hpriv->flags & AHCI_HFLAG_IGN_NOTSUPP_POWER_ON))) {
638c2ecf20Sopenharmony_ci			phy_exit(hpriv->phys[i]);
648c2ecf20Sopenharmony_ci			goto disable_phys;
658c2ecf20Sopenharmony_ci		}
668c2ecf20Sopenharmony_ci	}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	return 0;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cidisable_phys:
718c2ecf20Sopenharmony_ci	while (--i >= 0) {
728c2ecf20Sopenharmony_ci		phy_power_off(hpriv->phys[i]);
738c2ecf20Sopenharmony_ci		phy_exit(hpriv->phys[i]);
748c2ecf20Sopenharmony_ci	}
758c2ecf20Sopenharmony_ci	return rc;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_enable_phys);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci/**
808c2ecf20Sopenharmony_ci * ahci_platform_disable_phys - Disable PHYs
818c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
828c2ecf20Sopenharmony_ci *
838c2ecf20Sopenharmony_ci * This function disables all PHYs found in hpriv->phys.
848c2ecf20Sopenharmony_ci */
858c2ecf20Sopenharmony_civoid ahci_platform_disable_phys(struct ahci_host_priv *hpriv)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	int i;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	for (i = 0; i < hpriv->nports; i++) {
908c2ecf20Sopenharmony_ci		phy_power_off(hpriv->phys[i]);
918c2ecf20Sopenharmony_ci		phy_exit(hpriv->phys[i]);
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_disable_phys);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci/**
978c2ecf20Sopenharmony_ci * ahci_platform_enable_clks - Enable platform clocks
988c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
998c2ecf20Sopenharmony_ci *
1008c2ecf20Sopenharmony_ci * This function enables all the clks found in hpriv->clks, starting at
1018c2ecf20Sopenharmony_ci * index 0. If any clk fails to enable it disables all the clks already
1028c2ecf20Sopenharmony_ci * enabled in reverse order, and then returns an error.
1038c2ecf20Sopenharmony_ci *
1048c2ecf20Sopenharmony_ci * RETURNS:
1058c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
1068c2ecf20Sopenharmony_ci */
1078c2ecf20Sopenharmony_ciint ahci_platform_enable_clks(struct ahci_host_priv *hpriv)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	int c, rc;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++) {
1128c2ecf20Sopenharmony_ci		rc = clk_prepare_enable(hpriv->clks[c]);
1138c2ecf20Sopenharmony_ci		if (rc)
1148c2ecf20Sopenharmony_ci			goto disable_unprepare_clk;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci	return 0;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cidisable_unprepare_clk:
1198c2ecf20Sopenharmony_ci	while (--c >= 0)
1208c2ecf20Sopenharmony_ci		clk_disable_unprepare(hpriv->clks[c]);
1218c2ecf20Sopenharmony_ci	return rc;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_enable_clks);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci/**
1268c2ecf20Sopenharmony_ci * ahci_platform_disable_clks - Disable platform clocks
1278c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
1288c2ecf20Sopenharmony_ci *
1298c2ecf20Sopenharmony_ci * This function disables all the clks found in hpriv->clks, in reverse
1308c2ecf20Sopenharmony_ci * order of ahci_platform_enable_clks (starting at the end of the array).
1318c2ecf20Sopenharmony_ci */
1328c2ecf20Sopenharmony_civoid ahci_platform_disable_clks(struct ahci_host_priv *hpriv)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	int c;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	for (c = AHCI_MAX_CLKS - 1; c >= 0; c--)
1378c2ecf20Sopenharmony_ci		if (hpriv->clks[c])
1388c2ecf20Sopenharmony_ci			clk_disable_unprepare(hpriv->clks[c]);
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_disable_clks);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/**
1438c2ecf20Sopenharmony_ci * ahci_platform_enable_regulators - Enable regulators
1448c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
1458c2ecf20Sopenharmony_ci *
1468c2ecf20Sopenharmony_ci * This function enables all the regulators found in controller and
1478c2ecf20Sopenharmony_ci * hpriv->target_pwrs, if any.  If a regulator fails to be enabled, it
1488c2ecf20Sopenharmony_ci * disables all the regulators already enabled in reverse order and
1498c2ecf20Sopenharmony_ci * returns an error.
1508c2ecf20Sopenharmony_ci *
1518c2ecf20Sopenharmony_ci * RETURNS:
1528c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
1538c2ecf20Sopenharmony_ci */
1548c2ecf20Sopenharmony_ciint ahci_platform_enable_regulators(struct ahci_host_priv *hpriv)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	int rc, i;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	rc = regulator_enable(hpriv->ahci_regulator);
1598c2ecf20Sopenharmony_ci	if (rc)
1608c2ecf20Sopenharmony_ci		return rc;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	rc = regulator_enable(hpriv->phy_regulator);
1638c2ecf20Sopenharmony_ci	if (rc)
1648c2ecf20Sopenharmony_ci		goto disable_ahci_pwrs;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	for (i = 0; i < hpriv->nports; i++) {
1678c2ecf20Sopenharmony_ci		if (!hpriv->target_pwrs[i])
1688c2ecf20Sopenharmony_ci			continue;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci		rc = regulator_enable(hpriv->target_pwrs[i]);
1718c2ecf20Sopenharmony_ci		if (rc)
1728c2ecf20Sopenharmony_ci			goto disable_target_pwrs;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	return 0;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cidisable_target_pwrs:
1788c2ecf20Sopenharmony_ci	while (--i >= 0)
1798c2ecf20Sopenharmony_ci		if (hpriv->target_pwrs[i])
1808c2ecf20Sopenharmony_ci			regulator_disable(hpriv->target_pwrs[i]);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	regulator_disable(hpriv->phy_regulator);
1838c2ecf20Sopenharmony_cidisable_ahci_pwrs:
1848c2ecf20Sopenharmony_ci	regulator_disable(hpriv->ahci_regulator);
1858c2ecf20Sopenharmony_ci	return rc;
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_enable_regulators);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci/**
1908c2ecf20Sopenharmony_ci * ahci_platform_disable_regulators - Disable regulators
1918c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
1928c2ecf20Sopenharmony_ci *
1938c2ecf20Sopenharmony_ci * This function disables all regulators found in hpriv->target_pwrs and
1948c2ecf20Sopenharmony_ci * AHCI controller.
1958c2ecf20Sopenharmony_ci */
1968c2ecf20Sopenharmony_civoid ahci_platform_disable_regulators(struct ahci_host_priv *hpriv)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	int i;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	for (i = 0; i < hpriv->nports; i++) {
2018c2ecf20Sopenharmony_ci		if (!hpriv->target_pwrs[i])
2028c2ecf20Sopenharmony_ci			continue;
2038c2ecf20Sopenharmony_ci		regulator_disable(hpriv->target_pwrs[i]);
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	regulator_disable(hpriv->ahci_regulator);
2078c2ecf20Sopenharmony_ci	regulator_disable(hpriv->phy_regulator);
2088c2ecf20Sopenharmony_ci}
2098c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_disable_regulators);
2108c2ecf20Sopenharmony_ci/**
2118c2ecf20Sopenharmony_ci * ahci_platform_enable_resources - Enable platform resources
2128c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
2138c2ecf20Sopenharmony_ci *
2148c2ecf20Sopenharmony_ci * This function enables all ahci_platform managed resources in the
2158c2ecf20Sopenharmony_ci * following order:
2168c2ecf20Sopenharmony_ci * 1) Regulator
2178c2ecf20Sopenharmony_ci * 2) Clocks (through ahci_platform_enable_clks)
2188c2ecf20Sopenharmony_ci * 3) Resets
2198c2ecf20Sopenharmony_ci * 4) Phys
2208c2ecf20Sopenharmony_ci *
2218c2ecf20Sopenharmony_ci * If resource enabling fails at any point the previous enabled resources
2228c2ecf20Sopenharmony_ci * are disabled in reverse order.
2238c2ecf20Sopenharmony_ci *
2248c2ecf20Sopenharmony_ci * RETURNS:
2258c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
2268c2ecf20Sopenharmony_ci */
2278c2ecf20Sopenharmony_ciint ahci_platform_enable_resources(struct ahci_host_priv *hpriv)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	int rc;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	rc = ahci_platform_enable_regulators(hpriv);
2328c2ecf20Sopenharmony_ci	if (rc)
2338c2ecf20Sopenharmony_ci		return rc;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	rc = ahci_platform_enable_clks(hpriv);
2368c2ecf20Sopenharmony_ci	if (rc)
2378c2ecf20Sopenharmony_ci		goto disable_regulator;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	rc = reset_control_deassert(hpriv->rsts);
2408c2ecf20Sopenharmony_ci	if (rc)
2418c2ecf20Sopenharmony_ci		goto disable_clks;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	rc = ahci_platform_enable_phys(hpriv);
2448c2ecf20Sopenharmony_ci	if (rc)
2458c2ecf20Sopenharmony_ci		goto disable_resets;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	return 0;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cidisable_resets:
2508c2ecf20Sopenharmony_ci	reset_control_assert(hpriv->rsts);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cidisable_clks:
2538c2ecf20Sopenharmony_ci	ahci_platform_disable_clks(hpriv);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cidisable_regulator:
2568c2ecf20Sopenharmony_ci	ahci_platform_disable_regulators(hpriv);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return rc;
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_enable_resources);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci/**
2638c2ecf20Sopenharmony_ci * ahci_platform_disable_resources - Disable platform resources
2648c2ecf20Sopenharmony_ci * @hpriv: host private area to store config values
2658c2ecf20Sopenharmony_ci *
2668c2ecf20Sopenharmony_ci * This function disables all ahci_platform managed resources in the
2678c2ecf20Sopenharmony_ci * following order:
2688c2ecf20Sopenharmony_ci * 1) Phys
2698c2ecf20Sopenharmony_ci * 2) Resets
2708c2ecf20Sopenharmony_ci * 3) Clocks (through ahci_platform_disable_clks)
2718c2ecf20Sopenharmony_ci * 4) Regulator
2728c2ecf20Sopenharmony_ci */
2738c2ecf20Sopenharmony_civoid ahci_platform_disable_resources(struct ahci_host_priv *hpriv)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	ahci_platform_disable_phys(hpriv);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	reset_control_assert(hpriv->rsts);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	ahci_platform_disable_clks(hpriv);
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	ahci_platform_disable_regulators(hpriv);
2828c2ecf20Sopenharmony_ci}
2838c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_disable_resources);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic void ahci_platform_put_resources(struct device *dev, void *res)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = res;
2888c2ecf20Sopenharmony_ci	int c;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	if (hpriv->got_runtime_pm) {
2918c2ecf20Sopenharmony_ci		pm_runtime_put_sync(dev);
2928c2ecf20Sopenharmony_ci		pm_runtime_disable(dev);
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++)
2968c2ecf20Sopenharmony_ci		clk_put(hpriv->clks[c]);
2978c2ecf20Sopenharmony_ci	/*
2988c2ecf20Sopenharmony_ci	 * The regulators are tied to child node device and not to the
2998c2ecf20Sopenharmony_ci	 * SATA device itself. So we can't use devm for automatically
3008c2ecf20Sopenharmony_ci	 * releasing them. We have to do it manually here.
3018c2ecf20Sopenharmony_ci	 */
3028c2ecf20Sopenharmony_ci	for (c = 0; c < hpriv->nports; c++)
3038c2ecf20Sopenharmony_ci		if (hpriv->target_pwrs && hpriv->target_pwrs[c])
3048c2ecf20Sopenharmony_ci			regulator_put(hpriv->target_pwrs[c]);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	kfree(hpriv->target_pwrs);
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic int ahci_platform_get_phy(struct ahci_host_priv *hpriv, u32 port,
3108c2ecf20Sopenharmony_ci				struct device *dev, struct device_node *node)
3118c2ecf20Sopenharmony_ci{
3128c2ecf20Sopenharmony_ci	int rc;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	hpriv->phys[port] = devm_of_phy_get(dev, node, NULL);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	if (!IS_ERR(hpriv->phys[port]))
3178c2ecf20Sopenharmony_ci		return 0;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	rc = PTR_ERR(hpriv->phys[port]);
3208c2ecf20Sopenharmony_ci	switch (rc) {
3218c2ecf20Sopenharmony_ci	case -ENOSYS:
3228c2ecf20Sopenharmony_ci		/* No PHY support. Check if PHY is required. */
3238c2ecf20Sopenharmony_ci		if (of_find_property(node, "phys", NULL)) {
3248c2ecf20Sopenharmony_ci			dev_err(dev,
3258c2ecf20Sopenharmony_ci				"couldn't get PHY in node %pOFn: ENOSYS\n",
3268c2ecf20Sopenharmony_ci				node);
3278c2ecf20Sopenharmony_ci			break;
3288c2ecf20Sopenharmony_ci		}
3298c2ecf20Sopenharmony_ci		fallthrough;
3308c2ecf20Sopenharmony_ci	case -ENODEV:
3318c2ecf20Sopenharmony_ci		/* continue normally */
3328c2ecf20Sopenharmony_ci		hpriv->phys[port] = NULL;
3338c2ecf20Sopenharmony_ci		rc = 0;
3348c2ecf20Sopenharmony_ci		break;
3358c2ecf20Sopenharmony_ci	case -EPROBE_DEFER:
3368c2ecf20Sopenharmony_ci		/* Do not complain yet */
3378c2ecf20Sopenharmony_ci		break;
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	default:
3408c2ecf20Sopenharmony_ci		dev_err(dev,
3418c2ecf20Sopenharmony_ci			"couldn't get PHY in node %pOFn: %d\n",
3428c2ecf20Sopenharmony_ci			node, rc);
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci		break;
3458c2ecf20Sopenharmony_ci	}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	return rc;
3488c2ecf20Sopenharmony_ci}
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_cistatic int ahci_platform_get_regulator(struct ahci_host_priv *hpriv, u32 port,
3518c2ecf20Sopenharmony_ci				struct device *dev)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	struct regulator *target_pwr;
3548c2ecf20Sopenharmony_ci	int rc = 0;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	target_pwr = regulator_get(dev, "target");
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	if (!IS_ERR(target_pwr))
3598c2ecf20Sopenharmony_ci		hpriv->target_pwrs[port] = target_pwr;
3608c2ecf20Sopenharmony_ci	else
3618c2ecf20Sopenharmony_ci		rc = PTR_ERR(target_pwr);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	return rc;
3648c2ecf20Sopenharmony_ci}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci/**
3678c2ecf20Sopenharmony_ci * ahci_platform_get_resources - Get platform resources
3688c2ecf20Sopenharmony_ci * @pdev: platform device to get resources for
3698c2ecf20Sopenharmony_ci * @flags: bitmap representing the resource to get
3708c2ecf20Sopenharmony_ci *
3718c2ecf20Sopenharmony_ci * This function allocates an ahci_host_priv struct, and gets the following
3728c2ecf20Sopenharmony_ci * resources, storing a reference to them inside the returned struct:
3738c2ecf20Sopenharmony_ci *
3748c2ecf20Sopenharmony_ci * 1) mmio registers (IORESOURCE_MEM 0, mandatory)
3758c2ecf20Sopenharmony_ci * 2) regulator for controlling the targets power (optional)
3768c2ecf20Sopenharmony_ci *    regulator for controlling the AHCI controller (optional)
3778c2ecf20Sopenharmony_ci * 3) 0 - AHCI_MAX_CLKS clocks, as specified in the devs devicetree node,
3788c2ecf20Sopenharmony_ci *    or for non devicetree enabled platforms a single clock
3798c2ecf20Sopenharmony_ci * 4) resets, if flags has AHCI_PLATFORM_GET_RESETS (optional)
3808c2ecf20Sopenharmony_ci * 5) phys (optional)
3818c2ecf20Sopenharmony_ci *
3828c2ecf20Sopenharmony_ci * RETURNS:
3838c2ecf20Sopenharmony_ci * The allocated ahci_host_priv on success, otherwise an ERR_PTR value
3848c2ecf20Sopenharmony_ci */
3858c2ecf20Sopenharmony_cistruct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev,
3868c2ecf20Sopenharmony_ci						   unsigned int flags)
3878c2ecf20Sopenharmony_ci{
3888c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
3898c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv;
3908c2ecf20Sopenharmony_ci	struct clk *clk;
3918c2ecf20Sopenharmony_ci	struct device_node *child;
3928c2ecf20Sopenharmony_ci	int i, enabled_ports = 0, rc = -ENOMEM, child_nodes;
3938c2ecf20Sopenharmony_ci	u32 mask_port_map = 0;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	if (!devres_open_group(dev, NULL, GFP_KERNEL))
3968c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	hpriv = devres_alloc(ahci_platform_put_resources, sizeof(*hpriv),
3998c2ecf20Sopenharmony_ci			     GFP_KERNEL);
4008c2ecf20Sopenharmony_ci	if (!hpriv)
4018c2ecf20Sopenharmony_ci		goto err_out;
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	devres_add(dev, hpriv);
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	hpriv->mmio = devm_ioremap_resource(dev,
4068c2ecf20Sopenharmony_ci			      platform_get_resource(pdev, IORESOURCE_MEM, 0));
4078c2ecf20Sopenharmony_ci	if (IS_ERR(hpriv->mmio)) {
4088c2ecf20Sopenharmony_ci		rc = PTR_ERR(hpriv->mmio);
4098c2ecf20Sopenharmony_ci		goto err_out;
4108c2ecf20Sopenharmony_ci	}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	for (i = 0; i < AHCI_MAX_CLKS; i++) {
4138c2ecf20Sopenharmony_ci		/*
4148c2ecf20Sopenharmony_ci		 * For now we must use clk_get(dev, NULL) for the first clock,
4158c2ecf20Sopenharmony_ci		 * because some platforms (da850, spear13xx) are not yet
4168c2ecf20Sopenharmony_ci		 * converted to use devicetree for clocks.  For new platforms
4178c2ecf20Sopenharmony_ci		 * this is equivalent to of_clk_get(dev->of_node, 0).
4188c2ecf20Sopenharmony_ci		 */
4198c2ecf20Sopenharmony_ci		if (i == 0)
4208c2ecf20Sopenharmony_ci			clk = clk_get(dev, NULL);
4218c2ecf20Sopenharmony_ci		else
4228c2ecf20Sopenharmony_ci			clk = of_clk_get(dev->of_node, i);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci		if (IS_ERR(clk)) {
4258c2ecf20Sopenharmony_ci			rc = PTR_ERR(clk);
4268c2ecf20Sopenharmony_ci			if (rc == -EPROBE_DEFER)
4278c2ecf20Sopenharmony_ci				goto err_out;
4288c2ecf20Sopenharmony_ci			break;
4298c2ecf20Sopenharmony_ci		}
4308c2ecf20Sopenharmony_ci		hpriv->clks[i] = clk;
4318c2ecf20Sopenharmony_ci	}
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	hpriv->ahci_regulator = devm_regulator_get(dev, "ahci");
4348c2ecf20Sopenharmony_ci	if (IS_ERR(hpriv->ahci_regulator)) {
4358c2ecf20Sopenharmony_ci		rc = PTR_ERR(hpriv->ahci_regulator);
4368c2ecf20Sopenharmony_ci		if (rc != 0)
4378c2ecf20Sopenharmony_ci			goto err_out;
4388c2ecf20Sopenharmony_ci	}
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	hpriv->phy_regulator = devm_regulator_get(dev, "phy");
4418c2ecf20Sopenharmony_ci	if (IS_ERR(hpriv->phy_regulator)) {
4428c2ecf20Sopenharmony_ci		rc = PTR_ERR(hpriv->phy_regulator);
4438c2ecf20Sopenharmony_ci		goto err_out;
4448c2ecf20Sopenharmony_ci	}
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	if (flags & AHCI_PLATFORM_GET_RESETS) {
4478c2ecf20Sopenharmony_ci		hpriv->rsts = devm_reset_control_array_get_optional_shared(dev);
4488c2ecf20Sopenharmony_ci		if (IS_ERR(hpriv->rsts)) {
4498c2ecf20Sopenharmony_ci			rc = PTR_ERR(hpriv->rsts);
4508c2ecf20Sopenharmony_ci			goto err_out;
4518c2ecf20Sopenharmony_ci		}
4528c2ecf20Sopenharmony_ci	}
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	/*
4558c2ecf20Sopenharmony_ci	 * Too many sub-nodes most likely means having something wrong with
4568c2ecf20Sopenharmony_ci	 * the firmware.
4578c2ecf20Sopenharmony_ci	 */
4588c2ecf20Sopenharmony_ci	child_nodes = of_get_child_count(dev->of_node);
4598c2ecf20Sopenharmony_ci	if (child_nodes > AHCI_MAX_PORTS) {
4608c2ecf20Sopenharmony_ci		rc = -EINVAL;
4618c2ecf20Sopenharmony_ci		goto err_out;
4628c2ecf20Sopenharmony_ci	}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	/*
4658c2ecf20Sopenharmony_ci	 * If no sub-node was found, we still need to set nports to
4668c2ecf20Sopenharmony_ci	 * one in order to be able to use the
4678c2ecf20Sopenharmony_ci	 * ahci_platform_[en|dis]able_[phys|regulators] functions.
4688c2ecf20Sopenharmony_ci	 */
4698c2ecf20Sopenharmony_ci	if (child_nodes)
4708c2ecf20Sopenharmony_ci		hpriv->nports = child_nodes;
4718c2ecf20Sopenharmony_ci	else
4728c2ecf20Sopenharmony_ci		hpriv->nports = 1;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	hpriv->phys = devm_kcalloc(dev, hpriv->nports, sizeof(*hpriv->phys), GFP_KERNEL);
4758c2ecf20Sopenharmony_ci	if (!hpriv->phys) {
4768c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4778c2ecf20Sopenharmony_ci		goto err_out;
4788c2ecf20Sopenharmony_ci	}
4798c2ecf20Sopenharmony_ci	/*
4808c2ecf20Sopenharmony_ci	 * We cannot use devm_ here, since ahci_platform_put_resources() uses
4818c2ecf20Sopenharmony_ci	 * target_pwrs after devm_ have freed memory
4828c2ecf20Sopenharmony_ci	 */
4838c2ecf20Sopenharmony_ci	hpriv->target_pwrs = kcalloc(hpriv->nports, sizeof(*hpriv->target_pwrs), GFP_KERNEL);
4848c2ecf20Sopenharmony_ci	if (!hpriv->target_pwrs) {
4858c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4868c2ecf20Sopenharmony_ci		goto err_out;
4878c2ecf20Sopenharmony_ci	}
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	if (child_nodes) {
4908c2ecf20Sopenharmony_ci		for_each_child_of_node(dev->of_node, child) {
4918c2ecf20Sopenharmony_ci			u32 port;
4928c2ecf20Sopenharmony_ci			struct platform_device *port_dev __maybe_unused;
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci			if (!of_device_is_available(child))
4958c2ecf20Sopenharmony_ci				continue;
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci			if (of_property_read_u32(child, "reg", &port)) {
4988c2ecf20Sopenharmony_ci				rc = -EINVAL;
4998c2ecf20Sopenharmony_ci				of_node_put(child);
5008c2ecf20Sopenharmony_ci				goto err_out;
5018c2ecf20Sopenharmony_ci			}
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci			if (port >= hpriv->nports) {
5048c2ecf20Sopenharmony_ci				dev_warn(dev, "invalid port number %d\n", port);
5058c2ecf20Sopenharmony_ci				continue;
5068c2ecf20Sopenharmony_ci			}
5078c2ecf20Sopenharmony_ci			mask_port_map |= BIT(port);
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci#ifdef CONFIG_OF_ADDRESS
5108c2ecf20Sopenharmony_ci			of_platform_device_create(child, NULL, NULL);
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci			port_dev = of_find_device_by_node(child);
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci			if (port_dev) {
5158c2ecf20Sopenharmony_ci				rc = ahci_platform_get_regulator(hpriv, port,
5168c2ecf20Sopenharmony_ci								&port_dev->dev);
5178c2ecf20Sopenharmony_ci				if (rc == -EPROBE_DEFER) {
5188c2ecf20Sopenharmony_ci					of_node_put(child);
5198c2ecf20Sopenharmony_ci					goto err_out;
5208c2ecf20Sopenharmony_ci				}
5218c2ecf20Sopenharmony_ci			}
5228c2ecf20Sopenharmony_ci#endif
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_ci			rc = ahci_platform_get_phy(hpriv, port, dev, child);
5258c2ecf20Sopenharmony_ci			if (rc) {
5268c2ecf20Sopenharmony_ci				of_node_put(child);
5278c2ecf20Sopenharmony_ci				goto err_out;
5288c2ecf20Sopenharmony_ci			}
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci			enabled_ports++;
5318c2ecf20Sopenharmony_ci		}
5328c2ecf20Sopenharmony_ci		if (!enabled_ports) {
5338c2ecf20Sopenharmony_ci			dev_warn(dev, "No port enabled\n");
5348c2ecf20Sopenharmony_ci			rc = -ENODEV;
5358c2ecf20Sopenharmony_ci			goto err_out;
5368c2ecf20Sopenharmony_ci		}
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci		if (!hpriv->mask_port_map)
5398c2ecf20Sopenharmony_ci			hpriv->mask_port_map = mask_port_map;
5408c2ecf20Sopenharmony_ci	} else {
5418c2ecf20Sopenharmony_ci		/*
5428c2ecf20Sopenharmony_ci		 * If no sub-node was found, keep this for device tree
5438c2ecf20Sopenharmony_ci		 * compatibility
5448c2ecf20Sopenharmony_ci		 */
5458c2ecf20Sopenharmony_ci		rc = ahci_platform_get_phy(hpriv, 0, dev, dev->of_node);
5468c2ecf20Sopenharmony_ci		if (rc)
5478c2ecf20Sopenharmony_ci			goto err_out;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci		rc = ahci_platform_get_regulator(hpriv, 0, dev);
5508c2ecf20Sopenharmony_ci		if (rc == -EPROBE_DEFER)
5518c2ecf20Sopenharmony_ci			goto err_out;
5528c2ecf20Sopenharmony_ci	}
5538c2ecf20Sopenharmony_ci	pm_runtime_enable(dev);
5548c2ecf20Sopenharmony_ci	pm_runtime_get_sync(dev);
5558c2ecf20Sopenharmony_ci	hpriv->got_runtime_pm = true;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	devres_remove_group(dev, NULL);
5588c2ecf20Sopenharmony_ci	return hpriv;
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_cierr_out:
5618c2ecf20Sopenharmony_ci	devres_release_group(dev, NULL);
5628c2ecf20Sopenharmony_ci	return ERR_PTR(rc);
5638c2ecf20Sopenharmony_ci}
5648c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_get_resources);
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci/**
5678c2ecf20Sopenharmony_ci * ahci_platform_init_host - Bring up an ahci-platform host
5688c2ecf20Sopenharmony_ci * @pdev: platform device pointer for the host
5698c2ecf20Sopenharmony_ci * @hpriv: ahci-host private data for the host
5708c2ecf20Sopenharmony_ci * @pi_template: template for the ata_port_info to use
5718c2ecf20Sopenharmony_ci * @sht: scsi_host_template to use when registering
5728c2ecf20Sopenharmony_ci *
5738c2ecf20Sopenharmony_ci * This function does all the usual steps needed to bring up an
5748c2ecf20Sopenharmony_ci * ahci-platform host, note any necessary resources (ie clks, phys, etc.)
5758c2ecf20Sopenharmony_ci * must be initialized / enabled before calling this.
5768c2ecf20Sopenharmony_ci *
5778c2ecf20Sopenharmony_ci * RETURNS:
5788c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
5798c2ecf20Sopenharmony_ci */
5808c2ecf20Sopenharmony_ciint ahci_platform_init_host(struct platform_device *pdev,
5818c2ecf20Sopenharmony_ci			    struct ahci_host_priv *hpriv,
5828c2ecf20Sopenharmony_ci			    const struct ata_port_info *pi_template,
5838c2ecf20Sopenharmony_ci			    struct scsi_host_template *sht)
5848c2ecf20Sopenharmony_ci{
5858c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
5868c2ecf20Sopenharmony_ci	struct ata_port_info pi = *pi_template;
5878c2ecf20Sopenharmony_ci	const struct ata_port_info *ppi[] = { &pi, NULL };
5888c2ecf20Sopenharmony_ci	struct ata_host *host;
5898c2ecf20Sopenharmony_ci	int i, irq, n_ports, rc;
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
5928c2ecf20Sopenharmony_ci	if (irq < 0) {
5938c2ecf20Sopenharmony_ci		if (irq != -EPROBE_DEFER)
5948c2ecf20Sopenharmony_ci			dev_err(dev, "no irq\n");
5958c2ecf20Sopenharmony_ci		return irq;
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci	if (!irq)
5988c2ecf20Sopenharmony_ci		return -EINVAL;
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_ci	hpriv->irq = irq;
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_ci	/* prepare host */
6038c2ecf20Sopenharmony_ci	pi.private_data = (void *)(unsigned long)hpriv->flags;
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci	ahci_save_initial_config(dev, hpriv);
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	if (hpriv->cap & HOST_CAP_NCQ)
6088c2ecf20Sopenharmony_ci		pi.flags |= ATA_FLAG_NCQ;
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci	if (hpriv->cap & HOST_CAP_PMP)
6118c2ecf20Sopenharmony_ci		pi.flags |= ATA_FLAG_PMP;
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_ci	ahci_set_em_messages(hpriv, &pi);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	/* CAP.NP sometimes indicate the index of the last enabled
6168c2ecf20Sopenharmony_ci	 * port, at other times, that of the last possible port, so
6178c2ecf20Sopenharmony_ci	 * determining the maximum port number requires looking at
6188c2ecf20Sopenharmony_ci	 * both CAP.NP and port_map.
6198c2ecf20Sopenharmony_ci	 */
6208c2ecf20Sopenharmony_ci	n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map));
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_ci	host = ata_host_alloc_pinfo(dev, ppi, n_ports);
6238c2ecf20Sopenharmony_ci	if (!host)
6248c2ecf20Sopenharmony_ci		return -ENOMEM;
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	host->private_data = hpriv;
6278c2ecf20Sopenharmony_ci
6288c2ecf20Sopenharmony_ci	if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
6298c2ecf20Sopenharmony_ci		host->flags |= ATA_HOST_PARALLEL_SCAN;
6308c2ecf20Sopenharmony_ci	else
6318c2ecf20Sopenharmony_ci		dev_info(dev, "SSS flag set, parallel bus scan disabled\n");
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	if (pi.flags & ATA_FLAG_EM)
6348c2ecf20Sopenharmony_ci		ahci_reset_em(host);
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci	for (i = 0; i < host->n_ports; i++) {
6378c2ecf20Sopenharmony_ci		struct ata_port *ap = host->ports[i];
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci		ata_port_desc(ap, "mmio %pR",
6408c2ecf20Sopenharmony_ci			      platform_get_resource(pdev, IORESOURCE_MEM, 0));
6418c2ecf20Sopenharmony_ci		ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80);
6428c2ecf20Sopenharmony_ci
6438c2ecf20Sopenharmony_ci		/* set enclosure management message type */
6448c2ecf20Sopenharmony_ci		if (ap->flags & ATA_FLAG_EM)
6458c2ecf20Sopenharmony_ci			ap->em_message_type = hpriv->em_msg_type;
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_ci		/* disabled/not-implemented port */
6488c2ecf20Sopenharmony_ci		if (!(hpriv->port_map & (1 << i)))
6498c2ecf20Sopenharmony_ci			ap->ops = &ata_dummy_port_ops;
6508c2ecf20Sopenharmony_ci	}
6518c2ecf20Sopenharmony_ci
6528c2ecf20Sopenharmony_ci	if (hpriv->cap & HOST_CAP_64) {
6538c2ecf20Sopenharmony_ci		rc = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
6548c2ecf20Sopenharmony_ci		if (rc) {
6558c2ecf20Sopenharmony_ci			rc = dma_coerce_mask_and_coherent(dev,
6568c2ecf20Sopenharmony_ci							  DMA_BIT_MASK(32));
6578c2ecf20Sopenharmony_ci			if (rc) {
6588c2ecf20Sopenharmony_ci				dev_err(dev, "Failed to enable 64-bit DMA.\n");
6598c2ecf20Sopenharmony_ci				return rc;
6608c2ecf20Sopenharmony_ci			}
6618c2ecf20Sopenharmony_ci			dev_warn(dev, "Enable 32-bit DMA instead of 64-bit.\n");
6628c2ecf20Sopenharmony_ci		}
6638c2ecf20Sopenharmony_ci	}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_ci	rc = ahci_reset_controller(host);
6668c2ecf20Sopenharmony_ci	if (rc)
6678c2ecf20Sopenharmony_ci		return rc;
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	ahci_init_controller(host);
6708c2ecf20Sopenharmony_ci	ahci_print_info(host, "platform");
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_ci	return ahci_host_activate(host, sht);
6738c2ecf20Sopenharmony_ci}
6748c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_init_host);
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_cistatic void ahci_host_stop(struct ata_host *host)
6778c2ecf20Sopenharmony_ci{
6788c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci	ahci_platform_disable_resources(hpriv);
6818c2ecf20Sopenharmony_ci}
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci/**
6848c2ecf20Sopenharmony_ci * ahci_platform_shutdown - Disable interrupts and stop DMA for host ports
6858c2ecf20Sopenharmony_ci * @pdev: platform device pointer for the host
6868c2ecf20Sopenharmony_ci *
6878c2ecf20Sopenharmony_ci * This function is called during system shutdown and performs the minimal
6888c2ecf20Sopenharmony_ci * deconfiguration required to ensure that an ahci_platform host cannot
6898c2ecf20Sopenharmony_ci * corrupt or otherwise interfere with a new kernel being started with kexec.
6908c2ecf20Sopenharmony_ci */
6918c2ecf20Sopenharmony_civoid ahci_platform_shutdown(struct platform_device *pdev)
6928c2ecf20Sopenharmony_ci{
6938c2ecf20Sopenharmony_ci	struct ata_host *host = platform_get_drvdata(pdev);
6948c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
6958c2ecf20Sopenharmony_ci	void __iomem *mmio = hpriv->mmio;
6968c2ecf20Sopenharmony_ci	int i;
6978c2ecf20Sopenharmony_ci
6988c2ecf20Sopenharmony_ci	for (i = 0; i < host->n_ports; i++) {
6998c2ecf20Sopenharmony_ci		struct ata_port *ap = host->ports[i];
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci		/* Disable port interrupts */
7028c2ecf20Sopenharmony_ci		if (ap->ops->freeze)
7038c2ecf20Sopenharmony_ci			ap->ops->freeze(ap);
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci		/* Stop the port DMA engines */
7068c2ecf20Sopenharmony_ci		if (ap->ops->port_stop)
7078c2ecf20Sopenharmony_ci			ap->ops->port_stop(ap);
7088c2ecf20Sopenharmony_ci	}
7098c2ecf20Sopenharmony_ci
7108c2ecf20Sopenharmony_ci	/* Disable and clear host interrupts */
7118c2ecf20Sopenharmony_ci	writel(readl(mmio + HOST_CTL) & ~HOST_IRQ_EN, mmio + HOST_CTL);
7128c2ecf20Sopenharmony_ci	readl(mmio + HOST_CTL); /* flush */
7138c2ecf20Sopenharmony_ci	writel(GENMASK(host->n_ports, 0), mmio + HOST_IRQ_STAT);
7148c2ecf20Sopenharmony_ci}
7158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_shutdown);
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
7188c2ecf20Sopenharmony_ci/**
7198c2ecf20Sopenharmony_ci * ahci_platform_suspend_host - Suspend an ahci-platform host
7208c2ecf20Sopenharmony_ci * @dev: device pointer for the host
7218c2ecf20Sopenharmony_ci *
7228c2ecf20Sopenharmony_ci * This function does all the usual steps needed to suspend an
7238c2ecf20Sopenharmony_ci * ahci-platform host, note any necessary resources (ie clks, phys, etc.)
7248c2ecf20Sopenharmony_ci * must be disabled after calling this.
7258c2ecf20Sopenharmony_ci *
7268c2ecf20Sopenharmony_ci * RETURNS:
7278c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
7288c2ecf20Sopenharmony_ci */
7298c2ecf20Sopenharmony_ciint ahci_platform_suspend_host(struct device *dev)
7308c2ecf20Sopenharmony_ci{
7318c2ecf20Sopenharmony_ci	struct ata_host *host = dev_get_drvdata(dev);
7328c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
7338c2ecf20Sopenharmony_ci	void __iomem *mmio = hpriv->mmio;
7348c2ecf20Sopenharmony_ci	u32 ctl;
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	if (hpriv->flags & AHCI_HFLAG_NO_SUSPEND) {
7378c2ecf20Sopenharmony_ci		dev_err(dev, "firmware update required for suspend/resume\n");
7388c2ecf20Sopenharmony_ci		return -EIO;
7398c2ecf20Sopenharmony_ci	}
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	/*
7428c2ecf20Sopenharmony_ci	 * AHCI spec rev1.1 section 8.3.3:
7438c2ecf20Sopenharmony_ci	 * Software must disable interrupts prior to requesting a
7448c2ecf20Sopenharmony_ci	 * transition of the HBA to D3 state.
7458c2ecf20Sopenharmony_ci	 */
7468c2ecf20Sopenharmony_ci	ctl = readl(mmio + HOST_CTL);
7478c2ecf20Sopenharmony_ci	ctl &= ~HOST_IRQ_EN;
7488c2ecf20Sopenharmony_ci	writel(ctl, mmio + HOST_CTL);
7498c2ecf20Sopenharmony_ci	readl(mmio + HOST_CTL); /* flush */
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_ci	if (hpriv->flags & AHCI_HFLAG_SUSPEND_PHYS)
7528c2ecf20Sopenharmony_ci		ahci_platform_disable_phys(hpriv);
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	return ata_host_suspend(host, PMSG_SUSPEND);
7558c2ecf20Sopenharmony_ci}
7568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_suspend_host);
7578c2ecf20Sopenharmony_ci
7588c2ecf20Sopenharmony_ci/**
7598c2ecf20Sopenharmony_ci * ahci_platform_resume_host - Resume an ahci-platform host
7608c2ecf20Sopenharmony_ci * @dev: device pointer for the host
7618c2ecf20Sopenharmony_ci *
7628c2ecf20Sopenharmony_ci * This function does all the usual steps needed to resume an ahci-platform
7638c2ecf20Sopenharmony_ci * host, note any necessary resources (ie clks, phys, etc.)  must be
7648c2ecf20Sopenharmony_ci * initialized / enabled before calling this.
7658c2ecf20Sopenharmony_ci *
7668c2ecf20Sopenharmony_ci * RETURNS:
7678c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
7688c2ecf20Sopenharmony_ci */
7698c2ecf20Sopenharmony_ciint ahci_platform_resume_host(struct device *dev)
7708c2ecf20Sopenharmony_ci{
7718c2ecf20Sopenharmony_ci	struct ata_host *host = dev_get_drvdata(dev);
7728c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
7738c2ecf20Sopenharmony_ci	int rc;
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci	if (dev->power.power_state.event == PM_EVENT_SUSPEND) {
7768c2ecf20Sopenharmony_ci		rc = ahci_reset_controller(host);
7778c2ecf20Sopenharmony_ci		if (rc)
7788c2ecf20Sopenharmony_ci			return rc;
7798c2ecf20Sopenharmony_ci
7808c2ecf20Sopenharmony_ci		ahci_init_controller(host);
7818c2ecf20Sopenharmony_ci	}
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci	if (hpriv->flags & AHCI_HFLAG_SUSPEND_PHYS)
7848c2ecf20Sopenharmony_ci		ahci_platform_enable_phys(hpriv);
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci	ata_host_resume(host);
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	return 0;
7898c2ecf20Sopenharmony_ci}
7908c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_resume_host);
7918c2ecf20Sopenharmony_ci
7928c2ecf20Sopenharmony_ci/**
7938c2ecf20Sopenharmony_ci * ahci_platform_suspend - Suspend an ahci-platform device
7948c2ecf20Sopenharmony_ci * @dev: the platform device to suspend
7958c2ecf20Sopenharmony_ci *
7968c2ecf20Sopenharmony_ci * This function suspends the host associated with the device, followed by
7978c2ecf20Sopenharmony_ci * disabling all the resources of the device.
7988c2ecf20Sopenharmony_ci *
7998c2ecf20Sopenharmony_ci * RETURNS:
8008c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
8018c2ecf20Sopenharmony_ci */
8028c2ecf20Sopenharmony_ciint ahci_platform_suspend(struct device *dev)
8038c2ecf20Sopenharmony_ci{
8048c2ecf20Sopenharmony_ci	struct ata_host *host = dev_get_drvdata(dev);
8058c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
8068c2ecf20Sopenharmony_ci	int rc;
8078c2ecf20Sopenharmony_ci
8088c2ecf20Sopenharmony_ci	rc = ahci_platform_suspend_host(dev);
8098c2ecf20Sopenharmony_ci	if (rc)
8108c2ecf20Sopenharmony_ci		return rc;
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci	ahci_platform_disable_resources(hpriv);
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_ci	return 0;
8158c2ecf20Sopenharmony_ci}
8168c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_suspend);
8178c2ecf20Sopenharmony_ci
8188c2ecf20Sopenharmony_ci/**
8198c2ecf20Sopenharmony_ci * ahci_platform_resume - Resume an ahci-platform device
8208c2ecf20Sopenharmony_ci * @dev: the platform device to resume
8218c2ecf20Sopenharmony_ci *
8228c2ecf20Sopenharmony_ci * This function enables all the resources of the device followed by
8238c2ecf20Sopenharmony_ci * resuming the host associated with the device.
8248c2ecf20Sopenharmony_ci *
8258c2ecf20Sopenharmony_ci * RETURNS:
8268c2ecf20Sopenharmony_ci * 0 on success otherwise a negative error code
8278c2ecf20Sopenharmony_ci */
8288c2ecf20Sopenharmony_ciint ahci_platform_resume(struct device *dev)
8298c2ecf20Sopenharmony_ci{
8308c2ecf20Sopenharmony_ci	struct ata_host *host = dev_get_drvdata(dev);
8318c2ecf20Sopenharmony_ci	struct ahci_host_priv *hpriv = host->private_data;
8328c2ecf20Sopenharmony_ci	int rc;
8338c2ecf20Sopenharmony_ci
8348c2ecf20Sopenharmony_ci	rc = ahci_platform_enable_resources(hpriv);
8358c2ecf20Sopenharmony_ci	if (rc)
8368c2ecf20Sopenharmony_ci		return rc;
8378c2ecf20Sopenharmony_ci
8388c2ecf20Sopenharmony_ci	rc = ahci_platform_resume_host(dev);
8398c2ecf20Sopenharmony_ci	if (rc)
8408c2ecf20Sopenharmony_ci		goto disable_resources;
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	/* We resumed so update PM runtime state */
8438c2ecf20Sopenharmony_ci	pm_runtime_disable(dev);
8448c2ecf20Sopenharmony_ci	pm_runtime_set_active(dev);
8458c2ecf20Sopenharmony_ci	pm_runtime_enable(dev);
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_ci	return 0;
8488c2ecf20Sopenharmony_ci
8498c2ecf20Sopenharmony_cidisable_resources:
8508c2ecf20Sopenharmony_ci	ahci_platform_disable_resources(hpriv);
8518c2ecf20Sopenharmony_ci
8528c2ecf20Sopenharmony_ci	return rc;
8538c2ecf20Sopenharmony_ci}
8548c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ahci_platform_resume);
8558c2ecf20Sopenharmony_ci#endif
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AHCI SATA platform library");
8588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
8598c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
860