Skip to content

Commit

Permalink
soc/tegra: pmc: Add wake event support
Browse files Browse the repository at this point in the history
The power management controller has top-level controls that allow
certain interrupts (such as from the RTC or a subset of GPIOs) to
wake the system from sleep. Implement infrastructure to support
these wake events.

Signed-off-by: Thierry Reding <treding@nvidia.com>
  • Loading branch information
Thierry Reding committed Nov 28, 2018
1 parent eac9c48 commit 19906e6
Showing 1 changed file with 225 additions and 0 deletions.
225 changes: 225 additions & 0 deletions drivers/soc/tegra/pmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_clk.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
Expand All @@ -49,6 +52,7 @@
#include <soc/tegra/fuse.h>
#include <soc/tegra/pmc.h>

#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/pinctrl/pinctrl-tegra-io-pad.h>

#define PMC_CNTRL 0x0
Expand Down Expand Up @@ -126,6 +130,16 @@
#define GPU_RG_CNTRL 0x2d4

/* Tegra186 and later */
#define WAKE_AOWAKE_CNTRL(x) (0x000 + ((x) << 2))
#define WAKE_AOWAKE_CNTRL_LEVEL (1 << 3)
#define WAKE_AOWAKE_MASK_W(x) (0x180 + ((x) << 2))
#define WAKE_AOWAKE_MASK_R(x) (0x300 + ((x) << 2))
#define WAKE_AOWAKE_STATUS_W(x) (0x30c + ((x) << 2))
#define WAKE_AOWAKE_STATUS_R(x) (0x48c + ((x) << 2))
#define WAKE_AOWAKE_TIER0_ROUTING(x) (0x4b4 + ((x) << 2))
#define WAKE_AOWAKE_TIER1_ROUTING(x) (0x4c0 + ((x) << 2))
#define WAKE_AOWAKE_TIER2_ROUTING(x) (0x4cc + ((x) << 2))

#define WAKE_AOWAKE_CTRL 0x4f4
#define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0)

Expand Down Expand Up @@ -158,6 +172,38 @@ struct tegra_pmc_regs {
unsigned int rst_level_mask;
};

struct tegra_wake_event {
const char *name;
unsigned int id;
unsigned int irq;
struct {
unsigned int instance;
unsigned int pin;
} gpio;
};

#define TEGRA_WAKE_IRQ(_name, _id, _irq) \
{ \
.name = _name, \
.id = _id, \
.irq = _irq, \
.gpio = { \
.instance = UINT_MAX, \
.pin = UINT_MAX, \
}, \
}

#define TEGRA_WAKE_GPIO(_name, _id, _instance, _pin) \
{ \
.name = _name, \
.id = _id, \
.irq = 0, \
.gpio = { \
.instance = _instance, \
.pin = _pin, \
}, \
}

struct tegra_pmc_soc {
unsigned int num_powergates;
const char *const *powergates;
Expand Down Expand Up @@ -185,6 +231,9 @@ struct tegra_pmc_soc {
unsigned int num_reset_sources;
const char * const *reset_levels;
unsigned int num_reset_levels;

const struct tegra_wake_event *wake_events;
unsigned int num_wake_events;
};

static const char * const tegra186_reset_sources[] = {
Expand Down Expand Up @@ -271,6 +320,9 @@ struct tegra_pmc {
struct mutex powergates_lock;

struct pinctrl_dev *pctl_dev;

struct irq_domain *domain;
struct irq_chip irq;
};

static struct tegra_pmc *pmc = &(struct tegra_pmc) {
Expand Down Expand Up @@ -1602,6 +1654,175 @@ static void tegra_pmc_reset_sysfs_init(struct tegra_pmc *pmc)
}
}

static int tegra_pmc_irq_translate(struct irq_domain *domain,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (WARN_ON(fwspec->param_count < 2))
return -EINVAL;

*hwirq = fwspec->param[0];
*type = fwspec->param[1];

return 0;
}

static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int num_irqs, void *data)
{
struct tegra_pmc *pmc = domain->host_data;
const struct tegra_pmc_soc *soc = pmc->soc;
struct irq_fwspec *fwspec = data;
unsigned int i;
int err = 0;

for (i = 0; i < soc->num_wake_events; i++) {
const struct tegra_wake_event *event = &soc->wake_events[i];

if (fwspec->param_count == 2) {
struct irq_fwspec spec;

if (event->id != fwspec->param[0])
continue;

err = irq_domain_set_hwirq_and_chip(domain, virq,
event->id,
&pmc->irq, pmc);
if (err < 0)
break;

spec.fwnode = &pmc->dev->of_node->fwnode;
spec.param_count = 3;
spec.param[0] = GIC_SPI;
spec.param[1] = event->irq;
spec.param[2] = fwspec->param[1];

err = irq_domain_alloc_irqs_parent(domain, virq,
num_irqs, &spec);

break;
}

if (fwspec->param_count == 3) {
if (event->gpio.instance != fwspec->param[0] ||
event->gpio.pin != fwspec->param[1])
continue;

err = irq_domain_set_hwirq_and_chip(domain, virq,
event->id,
&pmc->irq, pmc);

break;
}
}

if (i == soc->num_wake_events)
err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX,
&pmc->irq, pmc);

return err;
}

static const struct irq_domain_ops tegra_pmc_irq_domain_ops = {
.translate = tegra_pmc_irq_translate,
.alloc = tegra_pmc_irq_alloc,
};

static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
{
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
unsigned int offset, bit;
u32 value;

offset = data->hwirq / 32;
bit = data->hwirq % 32;

/* clear wake status */
writel(0x1, pmc->wake + WAKE_AOWAKE_STATUS_W(data->hwirq));

/* route wake to tier 2 */
value = readl(pmc->wake + WAKE_AOWAKE_TIER2_ROUTING(offset));

if (!on)
value &= ~(1 << bit);
else
value |= 1 << bit;

writel(value, pmc->wake + WAKE_AOWAKE_TIER2_ROUTING(offset));

/* enable wakeup event */
writel(!!on, pmc->wake + WAKE_AOWAKE_MASK_W(data->hwirq));

return 0;
}

static int tegra_pmc_irq_set_type(struct irq_data *data, unsigned int type)
{
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
u32 value;

if (data->hwirq == ULONG_MAX)
return 0;

value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq));

switch (type) {
case IRQ_TYPE_EDGE_RISING:
case IRQ_TYPE_LEVEL_HIGH:
value |= WAKE_AOWAKE_CNTRL_LEVEL;
break;

case IRQ_TYPE_EDGE_FALLING:
case IRQ_TYPE_LEVEL_LOW:
value &= ~WAKE_AOWAKE_CNTRL_LEVEL;
break;

case IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING:
value ^= WAKE_AOWAKE_CNTRL_LEVEL;
break;

default:
return -EINVAL;
}

writel(value, pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq));

return 0;
}

static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
{
struct irq_domain *parent = NULL;
struct device_node *np;

np = of_irq_find_parent(pmc->dev->of_node);
if (np) {
parent = irq_find_host(np);
of_node_put(np);
}

if (!parent)
return 0;

pmc->irq.name = dev_name(pmc->dev);
pmc->irq.irq_mask = irq_chip_mask_parent;
pmc->irq.irq_unmask = irq_chip_unmask_parent;
pmc->irq.irq_eoi = irq_chip_eoi_parent;
pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent;
pmc->irq.irq_set_type = tegra_pmc_irq_set_type;
pmc->irq.irq_set_wake = tegra_pmc_irq_set_wake;

pmc->domain = irq_domain_add_hierarchy(parent, 0, 96, pmc->dev->of_node,
&tegra_pmc_irq_domain_ops, pmc);
if (!pmc->domain) {
dev_err(pmc->dev, "failed to allocate domain\n");
return -ENOMEM;
}

return 0;
}

static int tegra_pmc_probe(struct platform_device *pdev)
{
void __iomem *base;
Expand Down Expand Up @@ -1690,6 +1911,10 @@ static int tegra_pmc_probe(struct platform_device *pdev)
if (err)
goto cleanup_restart_handler;

err = tegra_pmc_irq_init(pmc);
if (err < 0)
goto cleanup_restart_handler;

mutex_lock(&pmc->powergates_lock);
iounmap(pmc->base);
pmc->base = base;
Expand Down

0 comments on commit 19906e6

Please sign in to comment.