summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/ufs/ufshcd-pltfrm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/ufs/ufshcd-pltfrm.c')
-rw-r--r--drivers/scsi/ufs/ufshcd-pltfrm.c291
1 files changed, 243 insertions, 48 deletions
diff --git a/drivers/scsi/ufs/ufshcd-pltfrm.c b/drivers/scsi/ufs/ufshcd-pltfrm.c
index 5e4623225422..8adf067ff019 100644
--- a/drivers/scsi/ufs/ufshcd-pltfrm.c
+++ b/drivers/scsi/ufs/ufshcd-pltfrm.c
@@ -35,53 +35,236 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/of.h>
#include "ufshcd.h"
+static const struct of_device_id ufs_of_match[];
+static struct ufs_hba_variant_ops *get_variant_ops(struct device *dev)
+{
+ if (dev->of_node) {
+ const struct of_device_id *match;
+
+ match = of_match_node(ufs_of_match, dev->of_node);
+ if (match)
+ return (struct ufs_hba_variant_ops *)match->data;
+ }
+
+ return NULL;
+}
+
+static int ufshcd_parse_clock_info(struct ufs_hba *hba)
+{
+ int ret = 0;
+ int cnt;
+ int i;
+ struct device *dev = hba->dev;
+ struct device_node *np = dev->of_node;
+ char *name;
+ u32 *clkfreq = NULL;
+ struct ufs_clk_info *clki;
+ int len = 0;
+ size_t sz = 0;
+
+ if (!np)
+ goto out;
+
+ INIT_LIST_HEAD(&hba->clk_list_head);
+
+ cnt = of_property_count_strings(np, "clock-names");
+ if (!cnt || (cnt == -EINVAL)) {
+ dev_info(dev, "%s: Unable to find clocks, assuming enabled\n",
+ __func__);
+ } else if (cnt < 0) {
+ dev_err(dev, "%s: count clock strings failed, err %d\n",
+ __func__, cnt);
+ ret = cnt;
+ }
+
+ if (cnt <= 0)
+ goto out;
+
+ if (!of_get_property(np, "freq-table-hz", &len)) {
+ dev_info(dev, "freq-table-hz property not specified\n");
+ goto out;
+ }
+
+ if (len <= 0)
+ goto out;
+
+ sz = len / sizeof(*clkfreq);
+ if (sz != 2 * cnt) {
+ dev_err(dev, "%s len mismatch\n", "freq-table-hz");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ clkfreq = devm_kzalloc(dev, sz * sizeof(*clkfreq),
+ GFP_KERNEL);
+ if (!clkfreq) {
+ dev_err(dev, "%s: no memory\n", "freq-table-hz");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = of_property_read_u32_array(np, "freq-table-hz",
+ clkfreq, sz);
+ if (ret && (ret != -EINVAL)) {
+ dev_err(dev, "%s: error reading array %d\n",
+ "freq-table-hz", ret);
+ goto free_clkfreq;
+ }
+
+ for (i = 0; i < sz; i += 2) {
+ ret = of_property_read_string_index(np,
+ "clock-names", i/2, (const char **)&name);
+ if (ret)
+ goto free_clkfreq;
+
+ clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
+ if (!clki) {
+ ret = -ENOMEM;
+ goto free_clkfreq;
+ }
+
+ clki->min_freq = clkfreq[i];
+ clki->max_freq = clkfreq[i+1];
+ clki->name = kstrdup(name, GFP_KERNEL);
+ dev_dbg(dev, "%s: min %u max %u name %s\n", "freq-table-hz",
+ clki->min_freq, clki->max_freq, clki->name);
+ list_add_tail(&clki->list, &hba->clk_list_head);
+ }
+free_clkfreq:
+ kfree(clkfreq);
+out:
+ return ret;
+}
+
+#define MAX_PROP_SIZE 32
+static int ufshcd_populate_vreg(struct device *dev, const char *name,
+ struct ufs_vreg **out_vreg)
+{
+ int ret = 0;
+ char prop_name[MAX_PROP_SIZE];
+ struct ufs_vreg *vreg = NULL;
+ struct device_node *np = dev->of_node;
+
+ if (!np) {
+ dev_err(dev, "%s: non DT initialization\n", __func__);
+ goto out;
+ }
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", name);
+ if (!of_parse_phandle(np, prop_name, 0)) {
+ dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n",
+ __func__, prop_name);
+ goto out;
+ }
+
+ vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
+ if (!vreg) {
+ dev_err(dev, "No memory for %s regulator\n", name);
+ goto out;
+ }
+
+ vreg->name = kstrdup(name, GFP_KERNEL);
+
+ /* if fixed regulator no need further initialization */
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-fixed-regulator", name);
+ if (of_property_read_bool(np, prop_name))
+ goto out;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-max-microamp", name);
+ ret = of_property_read_u32(np, prop_name, &vreg->max_uA);
+ if (ret) {
+ dev_err(dev, "%s: unable to find %s err %d\n",
+ __func__, prop_name, ret);
+ goto out_free;
+ }
+
+ vreg->min_uA = 0;
+ if (!strcmp(name, "vcc")) {
+ if (of_property_read_bool(np, "vcc-supply-1p8")) {
+ vreg->min_uV = UFS_VREG_VCC_1P8_MIN_UV;
+ vreg->max_uV = UFS_VREG_VCC_1P8_MAX_UV;
+ } else {
+ vreg->min_uV = UFS_VREG_VCC_MIN_UV;
+ vreg->max_uV = UFS_VREG_VCC_MAX_UV;
+ }
+ } else if (!strcmp(name, "vccq")) {
+ vreg->min_uV = UFS_VREG_VCCQ_MIN_UV;
+ vreg->max_uV = UFS_VREG_VCCQ_MAX_UV;
+ } else if (!strcmp(name, "vccq2")) {
+ vreg->min_uV = UFS_VREG_VCCQ2_MIN_UV;
+ vreg->max_uV = UFS_VREG_VCCQ2_MAX_UV;
+ }
+
+ goto out;
+
+out_free:
+ devm_kfree(dev, vreg);
+ vreg = NULL;
+out:
+ if (!ret)
+ *out_vreg = vreg;
+ return ret;
+}
+
+/**
+ * ufshcd_parse_regulator_info - get regulator info from device tree
+ * @hba: per adapter instance
+ *
+ * Get regulator info from device tree for vcc, vccq, vccq2 power supplies.
+ * If any of the supplies are not defined it is assumed that they are always-on
+ * and hence return zero. If the property is defined but parsing is failed
+ * then return corresponding error.
+ */
+static int ufshcd_parse_regulator_info(struct ufs_hba *hba)
+{
+ int err;
+ struct device *dev = hba->dev;
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ err = ufshcd_populate_vreg(dev, "vdd-hba", &info->vdd_hba);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vcc", &info->vcc);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vccq", &info->vccq);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vccq2", &info->vccq2);
+out:
+ return err;
+}
+
#ifdef CONFIG_PM
/**
* ufshcd_pltfrm_suspend - suspend power management function
* @dev: pointer to device handle
*
- *
- * Returns 0
+ * Returns 0 if successful
+ * Returns non-zero otherwise
*/
static int ufshcd_pltfrm_suspend(struct device *dev)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct ufs_hba *hba = platform_get_drvdata(pdev);
-
- /*
- * TODO:
- * 1. Call ufshcd_suspend
- * 2. Do bus specific power management
- */
-
- disable_irq(hba->irq);
-
- return 0;
+ return ufshcd_system_suspend(dev_get_drvdata(dev));
}
/**
* ufshcd_pltfrm_resume - resume power management function
* @dev: pointer to device handle
*
- * Returns 0
+ * Returns 0 if successful
+ * Returns non-zero otherwise
*/
static int ufshcd_pltfrm_resume(struct device *dev)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct ufs_hba *hba = platform_get_drvdata(pdev);
-
- /*
- * TODO:
- * 1. Call ufshcd_resume.
- * 2. Do bus specific wake up
- */
-
- enable_irq(hba->irq);
-
- return 0;
+ return ufshcd_system_resume(dev_get_drvdata(dev));
}
#else
#define ufshcd_pltfrm_suspend NULL
@@ -91,30 +274,15 @@ static int ufshcd_pltfrm_resume(struct device *dev)
#ifdef CONFIG_PM_RUNTIME
static int ufshcd_pltfrm_runtime_suspend(struct device *dev)
{
- struct ufs_hba *hba = dev_get_drvdata(dev);
-
- if (!hba)
- return 0;
-
- return ufshcd_runtime_suspend(hba);
+ return ufshcd_runtime_suspend(dev_get_drvdata(dev));
}
static int ufshcd_pltfrm_runtime_resume(struct device *dev)
{
- struct ufs_hba *hba = dev_get_drvdata(dev);
-
- if (!hba)
- return 0;
-
- return ufshcd_runtime_resume(hba);
+ return ufshcd_runtime_resume(dev_get_drvdata(dev));
}
static int ufshcd_pltfrm_runtime_idle(struct device *dev)
{
- struct ufs_hba *hba = dev_get_drvdata(dev);
-
- if (!hba)
- return 0;
-
- return ufshcd_runtime_idle(hba);
+ return ufshcd_runtime_idle(dev_get_drvdata(dev));
}
#else /* !CONFIG_PM_RUNTIME */
#define ufshcd_pltfrm_runtime_suspend NULL
@@ -122,6 +290,11 @@ static int ufshcd_pltfrm_runtime_idle(struct device *dev)
#define ufshcd_pltfrm_runtime_idle NULL
#endif /* CONFIG_PM_RUNTIME */
+static void ufshcd_pltfrm_shutdown(struct platform_device *pdev)
+{
+ ufshcd_shutdown((struct ufs_hba *)platform_get_drvdata(pdev));
+}
+
/**
* ufshcd_pltfrm_probe - probe routine of the driver
* @pdev: pointer to Platform device handle
@@ -138,8 +311,8 @@ static int ufshcd_pltfrm_probe(struct platform_device *pdev)
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mmio_base = devm_ioremap_resource(dev, mem_res);
- if (IS_ERR(mmio_base)) {
- err = PTR_ERR(mmio_base);
+ if (IS_ERR(*(void **)&mmio_base)) {
+ err = PTR_ERR(*(void **)&mmio_base);
goto out;
}
@@ -150,10 +323,31 @@ static int ufshcd_pltfrm_probe(struct platform_device *pdev)
goto out;
}
+ err = ufshcd_alloc_host(dev, &hba);
+ if (err) {
+ dev_err(&pdev->dev, "Allocation failed\n");
+ goto out;
+ }
+
+ hba->vops = get_variant_ops(&pdev->dev);
+
+ err = ufshcd_parse_clock_info(hba);
+ if (err) {
+ dev_err(&pdev->dev, "%s: clock parse failed %d\n",
+ __func__, err);
+ goto out;
+ }
+ err = ufshcd_parse_regulator_info(hba);
+ if (err) {
+ dev_err(&pdev->dev, "%s: regulator init failed %d\n",
+ __func__, err);
+ goto out;
+ }
+
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
- err = ufshcd_init(dev, &hba, mmio_base, irq);
+ err = ufshcd_init(hba, mmio_base, irq);
if (err) {
dev_err(dev, "Intialization failed\n");
goto out_disable_rpm;
@@ -201,6 +395,7 @@ static const struct dev_pm_ops ufshcd_dev_pm_ops = {
static struct platform_driver ufshcd_pltfrm_driver = {
.probe = ufshcd_pltfrm_probe,
.remove = ufshcd_pltfrm_remove,
+ .shutdown = ufshcd_pltfrm_shutdown,
.driver = {
.name = "ufshcd",
.owner = THIS_MODULE,
OpenPOWER on IntegriCloud