summaryrefslogtreecommitdiff
path: root/drivers/soc/tegra/pmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/tegra/pmc.c')
-rw-r--r--drivers/soc/tegra/pmc.c149
1 files changed, 112 insertions, 37 deletions
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index bb173456bbff..71c834f3847e 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -51,6 +51,7 @@
#define PMC_CNTRL_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */
#define PMC_CNTRL_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */
#define PMC_CNTRL_INTR_POLARITY (1 << 17) /* inverts INTR polarity */
+#define PMC_CNTRL_MAIN_RST (1 << 4)
#define DPD_SAMPLE 0x020
#define DPD_SAMPLE_ENABLE (1 << 0)
@@ -80,6 +81,14 @@
#define PMC_SENSOR_CTRL_SCRATCH_WRITE (1 << 2)
#define PMC_SENSOR_CTRL_ENABLE_RST (1 << 1)
+#define PMC_RST_STATUS 0x1b4
+#define PMC_RST_STATUS_POR 0
+#define PMC_RST_STATUS_WATCHDOG 1
+#define PMC_RST_STATUS_SENSOR 2
+#define PMC_RST_STATUS_SW_MAIN 3
+#define PMC_RST_STATUS_LP0 4
+#define PMC_RST_STATUS_AOTAG 5
+
#define IO_DPD_REQ 0x1b8
#define IO_DPD_REQ_CODE_IDLE (0 << 30)
#define IO_DPD_REQ_CODE_OFF (1 << 30)
@@ -399,6 +408,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
disable_clks:
tegra_powergate_disable_clocks(pg);
usleep_range(10, 20);
+
powergate_off:
tegra_powergate_set(pg->id, false);
@@ -436,6 +446,7 @@ assert_resets:
usleep_range(10, 20);
tegra_powergate_reset_deassert(pg);
usleep_range(10, 20);
+
disable_clks:
tegra_powergate_disable_clocks(pg);
@@ -540,6 +551,9 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
struct tegra_powergate pg;
int err;
+ if (!tegra_powergate_is_available(id))
+ return -EINVAL;
+
pg.id = id;
pg.clks = &clk;
pg.num_clks = 1;
@@ -638,9 +652,10 @@ static int tegra_pmc_restart_notify(struct notifier_block *this,
tegra_pmc_writel(value, PMC_SCRATCH0);
- value = tegra_pmc_readl(0);
- value |= 0x10;
- tegra_pmc_writel(value, 0);
+ /* reset everything but PMC_SCRATCH0 and PMC_RST_STATUS */
+ value = tegra_pmc_readl(PMC_CNTRL);
+ value |= PMC_CNTRL_MAIN_RST;
+ tegra_pmc_writel(value, PMC_CNTRL);
return NOTIFY_DONE;
}
@@ -722,13 +737,14 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg,
err:
while (i--)
clk_put(pg->clks[i]);
+
kfree(pg->clks);
return err;
}
static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
- struct device_node *np)
+ struct device_node *np, bool off)
{
struct reset_control *rst;
unsigned int i, count;
@@ -748,6 +764,16 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
err = PTR_ERR(pg->resets[i]);
goto error;
}
+
+ if (off)
+ err = reset_control_assert(pg->resets[i]);
+ else
+ err = reset_control_deassert(pg->resets[i]);
+
+ if (err) {
+ reset_control_put(pg->resets[i]);
+ goto error;
+ }
}
pg->num_resets = count;
@@ -757,6 +783,7 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
error:
while (i--)
reset_control_put(pg->resets[i]);
+
kfree(pg->resets);
return err;
@@ -765,16 +792,19 @@ error:
static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
{
struct tegra_powergate *pg;
+ int id, err;
bool off;
- int id;
pg = kzalloc(sizeof(*pg), GFP_KERNEL);
if (!pg)
- goto error;
+ return;
id = tegra_powergate_lookup(pmc, np->name);
- if (id < 0)
+ if (id < 0) {
+ dev_err(pmc->dev, "powergate lookup failed for %s: %d\n",
+ np->name, id);
goto free_mem;
+ }
/*
* Clear the bit for this powergate so it cannot be managed
@@ -788,31 +818,64 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
pg->genpd.power_on = tegra_genpd_power_on;
pg->pmc = pmc;
- if (tegra_powergate_of_get_clks(pg, np))
+ off = !tegra_powergate_is_powered(pg->id);
+
+ err = tegra_powergate_of_get_clks(pg, np);
+ if (err < 0) {
+ dev_err(pmc->dev, "failed to get clocks for %s: %d\n",
+ np->name, err);
goto set_available;
+ }
- if (tegra_powergate_of_get_resets(pg, np))
+ err = tegra_powergate_of_get_resets(pg, np, off);
+ if (err < 0) {
+ dev_err(pmc->dev, "failed to get resets for %s: %d\n",
+ np->name, err);
goto remove_clks;
+ }
- off = !tegra_powergate_is_powered(pg->id);
+ if (!IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS))
+ goto power_on_cleanup;
+
+ /*
+ * FIXME: If XHCI is enabled for Tegra, then power-up the XUSB
+ * host and super-speed partitions. Once the XHCI driver
+ * manages the partitions itself this code can be removed. Note
+ * that we don't register these partitions with the genpd core
+ * to avoid it from powering down the partitions as they appear
+ * to be unused.
+ */
+ if (IS_ENABLED(CONFIG_USB_XHCI_TEGRA) &&
+ (id == TEGRA_POWERGATE_XUSBA || id == TEGRA_POWERGATE_XUSBC))
+ goto power_on_cleanup;
pm_genpd_init(&pg->genpd, NULL, off);
- if (of_genpd_add_provider_simple(np, &pg->genpd))
+ err = of_genpd_add_provider_simple(np, &pg->genpd);
+ if (err < 0) {
+ dev_err(pmc->dev, "failed to add genpd provider for %s: %d\n",
+ np->name, err);
goto remove_resets;
+ }
dev_dbg(pmc->dev, "added power domain %s\n", pg->genpd.name);
return;
+power_on_cleanup:
+ if (off)
+ WARN_ON(tegra_powergate_power_up(pg, true));
+
remove_resets:
while (pg->num_resets--)
reset_control_put(pg->resets[pg->num_resets]);
+
kfree(pg->resets);
remove_clks:
while (pg->num_clks--)
clk_put(pg->clks[pg->num_clks]);
+
kfree(pg->clks);
set_available:
@@ -820,16 +883,20 @@ set_available:
free_mem:
kfree(pg);
-
-error:
- dev_err(pmc->dev, "failed to create power domain for %s\n", np->name);
}
-static void tegra_powergate_init(struct tegra_pmc *pmc)
+static void tegra_powergate_init(struct tegra_pmc *pmc,
+ struct device_node *parent)
{
struct device_node *np, *child;
+ unsigned int i;
+
+ /* Create a bitmap of the available and valid partitions */
+ for (i = 0; i < pmc->soc->num_powergates; i++)
+ if (pmc->soc->powergates[i])
+ set_bit(i, pmc->powergates_available);
- np = of_get_child_by_name(pmc->dev->of_node, "powergates");
+ np = of_get_child_by_name(parent, "powergates");
if (!np)
return;
@@ -1205,6 +1272,14 @@ static int tegra_pmc_probe(struct platform_device *pdev)
struct resource *res;
int err;
+ /*
+ * Early initialisation should have configured an initial
+ * register mapping and setup the soc data pointer. If these
+ * are not valid then something went badly wrong!
+ */
+ if (WARN_ON(!pmc->base || !pmc->soc))
+ return -ENODEV;
+
err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node);
if (err < 0)
return err;
@@ -1242,8 +1317,6 @@ static int tegra_pmc_probe(struct platform_device *pdev)
return err;
}
- tegra_powergate_init(pmc);
-
mutex_lock(&pmc->powergates_lock);
iounmap(pmc->base);
pmc->base = base;
@@ -1477,10 +1550,11 @@ static int __init tegra_pmc_early_init(void)
const struct of_device_id *match;
struct device_node *np;
struct resource regs;
- unsigned int i;
bool invert;
u32 value;
+ mutex_init(&pmc->powergates_lock);
+
np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match);
if (!np) {
/*
@@ -1515,39 +1589,40 @@ static int __init tegra_pmc_early_init(void)
*/
if (of_address_to_resource(np, 0, &regs) < 0) {
pr_err("failed to get PMC registers\n");
+ of_node_put(np);
return -ENXIO;
}
-
- pmc->soc = match->data;
}
pmc->base = ioremap_nocache(regs.start, resource_size(&regs));
if (!pmc->base) {
pr_err("failed to map PMC registers\n");
+ of_node_put(np);
return -ENXIO;
}
- /* Create a bit-map of the available and valid partitions */
- for (i = 0; i < pmc->soc->num_powergates; i++)
- if (pmc->soc->powergates[i])
- set_bit(i, pmc->powergates_available);
+ if (np) {
+ pmc->soc = match->data;
- mutex_init(&pmc->powergates_lock);
+ tegra_powergate_init(pmc, np);
- /*
- * Invert the interrupt polarity if a PMC device tree node exists and
- * contains the nvidia,invert-interrupt property.
- */
- invert = of_property_read_bool(np, "nvidia,invert-interrupt");
+ /*
+ * Invert the interrupt polarity if a PMC device tree node
+ * exists and contains the nvidia,invert-interrupt property.
+ */
+ invert = of_property_read_bool(np, "nvidia,invert-interrupt");
- value = tegra_pmc_readl(PMC_CNTRL);
+ value = tegra_pmc_readl(PMC_CNTRL);
- if (invert)
- value |= PMC_CNTRL_INTR_POLARITY;
- else
- value &= ~PMC_CNTRL_INTR_POLARITY;
+ if (invert)
+ value |= PMC_CNTRL_INTR_POLARITY;
+ else
+ value &= ~PMC_CNTRL_INTR_POLARITY;
- tegra_pmc_writel(value, PMC_CNTRL);
+ tegra_pmc_writel(value, PMC_CNTRL);
+
+ of_node_put(np);
+ }
return 0;
}