remoteproc: pru: Add support for PRU specific interrupt configuration
[linux-2.6-microblaze.git] / drivers / remoteproc / qcom_wcnss.c
index e2573f7..f958542 100644 (file)
@@ -17,6 +17,8 @@
 #include <linux/of_address.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
 #include <linux/qcom_scm.h>
 #include <linux/regulator/consumer.h>
 #include <linux/remoteproc.h>
 #define WCNSS_PMU_XO_MODE_19p2         0
 #define WCNSS_PMU_XO_MODE_48           3
 
+#define WCNSS_MAX_PDS                  2
+
 struct wcnss_data {
        size_t pmu_offset;
        size_t spare_offset;
 
+       const char *pd_names[WCNSS_MAX_PDS];
        const struct wcnss_vreg_info *vregs;
-       size_t num_vregs;
+       size_t num_vregs, num_pd_vregs;
 };
 
 struct qcom_wcnss {
@@ -80,6 +85,8 @@ struct qcom_wcnss {
        struct mutex iris_lock;
        struct qcom_iris *iris;
 
+       struct device *pds[WCNSS_MAX_PDS];
+       size_t num_pds;
        struct regulator_bulk_data *vregs;
        size_t num_vregs;
 
@@ -111,24 +118,28 @@ static const struct wcnss_data pronto_v1_data = {
        .pmu_offset = 0x1004,
        .spare_offset = 0x1088,
 
+       .pd_names = { "mx", "cx" },
        .vregs = (struct wcnss_vreg_info[]) {
                { "vddmx", 950000, 1150000, 0 },
                { "vddcx", .super_turbo = true},
                { "vddpx", 1800000, 1800000, 0 },
        },
-       .num_vregs = 3,
+       .num_pd_vregs = 2,
+       .num_vregs = 1,
 };
 
 static const struct wcnss_data pronto_v2_data = {
        .pmu_offset = 0x1004,
        .spare_offset = 0x1088,
 
+       .pd_names = { "mx", "cx" },
        .vregs = (struct wcnss_vreg_info[]) {
                { "vddmx", 1287500, 1287500, 0 },
                { "vddcx", .super_turbo = true },
                { "vddpx", 1800000, 1800000, 0 },
        },
-       .num_vregs = 3,
+       .num_pd_vregs = 2,
+       .num_vregs = 1,
 };
 
 void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss,
@@ -219,7 +230,7 @@ static void wcnss_configure_iris(struct qcom_wcnss *wcnss)
 static int wcnss_start(struct rproc *rproc)
 {
        struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
-       int ret;
+       int ret, i;
 
        mutex_lock(&wcnss->iris_lock);
        if (!wcnss->iris) {
@@ -228,9 +239,18 @@ static int wcnss_start(struct rproc *rproc)
                goto release_iris_lock;
        }
 
+       for (i = 0; i < wcnss->num_pds; i++) {
+               dev_pm_genpd_set_performance_state(wcnss->pds[i], INT_MAX);
+               ret = pm_runtime_get_sync(wcnss->pds[i]);
+               if (ret < 0) {
+                       pm_runtime_put_noidle(wcnss->pds[i]);
+                       goto disable_pds;
+               }
+       }
+
        ret = regulator_bulk_enable(wcnss->num_vregs, wcnss->vregs);
        if (ret)
-               goto release_iris_lock;
+               goto disable_pds;
 
        ret = qcom_iris_enable(wcnss->iris);
        if (ret)
@@ -262,6 +282,11 @@ disable_iris:
        qcom_iris_disable(wcnss->iris);
 disable_regulators:
        regulator_bulk_disable(wcnss->num_vregs, wcnss->vregs);
+disable_pds:
+       for (i--; i >= 0; i--) {
+               pm_runtime_put(wcnss->pds[i]);
+               dev_pm_genpd_set_performance_state(wcnss->pds[i], 0);
+       }
 release_iris_lock:
        mutex_unlock(&wcnss->iris_lock);
 
@@ -371,14 +396,54 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev)
        return IRQ_HANDLED;
 }
 
+static int wcnss_init_pds(struct qcom_wcnss *wcnss,
+                         const char * const pd_names[WCNSS_MAX_PDS])
+{
+       int i, ret;
+
+       for (i = 0; i < WCNSS_MAX_PDS; i++) {
+               if (!pd_names[i])
+                       break;
+
+               wcnss->pds[i] = dev_pm_domain_attach_by_name(wcnss->dev, pd_names[i]);
+               if (IS_ERR_OR_NULL(wcnss->pds[i])) {
+                       ret = PTR_ERR(wcnss->pds[i]) ? : -ENODATA;
+                       for (i--; i >= 0; i--)
+                               dev_pm_domain_detach(wcnss->pds[i], false);
+                       return ret;
+               }
+       }
+       wcnss->num_pds = i;
+
+       return 0;
+}
+
+static void wcnss_release_pds(struct qcom_wcnss *wcnss)
+{
+       int i;
+
+       for (i = 0; i < wcnss->num_pds; i++)
+               dev_pm_domain_detach(wcnss->pds[i], false);
+}
+
 static int wcnss_init_regulators(struct qcom_wcnss *wcnss,
                                 const struct wcnss_vreg_info *info,
-                                int num_vregs)
+                                int num_vregs, int num_pd_vregs)
 {
        struct regulator_bulk_data *bulk;
        int ret;
        int i;
 
+       /*
+        * If attaching the power domains suceeded we can skip requesting
+        * the regulators for the power domains. For old device trees we need to
+        * reserve extra space to manage them through the regulator interface.
+        */
+       if (wcnss->num_pds)
+               info += num_pd_vregs;
+       else
+               num_vregs += num_pd_vregs;
+
        bulk = devm_kcalloc(wcnss->dev,
                            num_vregs, sizeof(struct regulator_bulk_data),
                            GFP_KERNEL);
@@ -514,33 +579,42 @@ static int wcnss_probe(struct platform_device *pdev)
        wcnss->pmu_cfg = mmio + data->pmu_offset;
        wcnss->spare_out = mmio + data->spare_offset;
 
-       ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs);
-       if (ret)
+       /*
+        * We might need to fallback to regulators instead of power domains
+        * for old device trees. Don't report an error in that case.
+        */
+       ret = wcnss_init_pds(wcnss, data->pd_names);
+       if (ret && (ret != -ENODATA || !data->num_pd_vregs))
                goto free_rproc;
 
+       ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs,
+                                   data->num_pd_vregs);
+       if (ret)
+               goto detach_pds;
+
        ret = wcnss_request_irq(wcnss, pdev, "wdog", false, wcnss_wdog_interrupt);
        if (ret < 0)
-               goto free_rproc;
+               goto detach_pds;
        wcnss->wdog_irq = ret;
 
        ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt);
        if (ret < 0)
-               goto free_rproc;
+               goto detach_pds;
        wcnss->fatal_irq = ret;
 
        ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt);
        if (ret < 0)
-               goto free_rproc;
+               goto detach_pds;
        wcnss->ready_irq = ret;
 
        ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt);
        if (ret < 0)
-               goto free_rproc;
+               goto detach_pds;
        wcnss->handover_irq = ret;
 
        ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt);
        if (ret < 0)
-               goto free_rproc;
+               goto detach_pds;
        wcnss->stop_ack_irq = ret;
 
        if (wcnss->stop_ack_irq) {
@@ -548,7 +622,7 @@ static int wcnss_probe(struct platform_device *pdev)
                                                   &wcnss->stop_bit);
                if (IS_ERR(wcnss->state)) {
                        ret = PTR_ERR(wcnss->state);
-                       goto free_rproc;
+                       goto detach_pds;
                }
        }
 
@@ -556,15 +630,17 @@ static int wcnss_probe(struct platform_device *pdev)
        wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID);
        if (IS_ERR(wcnss->sysmon)) {
                ret = PTR_ERR(wcnss->sysmon);
-               goto free_rproc;
+               goto detach_pds;
        }
 
        ret = rproc_add(rproc);
        if (ret)
-               goto free_rproc;
+               goto detach_pds;
 
        return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
 
+detach_pds:
+       wcnss_release_pds(wcnss);
 free_rproc:
        rproc_free(rproc);
 
@@ -582,6 +658,7 @@ static int wcnss_remove(struct platform_device *pdev)
 
        qcom_remove_sysmon_subdev(wcnss->sysmon);
        qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
+       wcnss_release_pds(wcnss);
        rproc_free(wcnss->rproc);
 
        return 0;