soc/tegra: pmc: Add wake event support
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>
This commit is contained in:
parent
eac9c48aac
commit
19906e6b16
@ -30,9 +30,12 @@
|
|||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/iopoll.h>
|
#include <linux/iopoll.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_address.h>
|
#include <linux/of_address.h>
|
||||||
#include <linux/of_clk.h>
|
#include <linux/of_clk.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
#include <linux/pinctrl/pinctrl.h>
|
#include <linux/pinctrl/pinctrl.h>
|
||||||
#include <linux/pinctrl/pinconf.h>
|
#include <linux/pinctrl/pinconf.h>
|
||||||
@ -49,6 +52,7 @@
|
|||||||
#include <soc/tegra/fuse.h>
|
#include <soc/tegra/fuse.h>
|
||||||
#include <soc/tegra/pmc.h>
|
#include <soc/tegra/pmc.h>
|
||||||
|
|
||||||
|
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||||
#include <dt-bindings/pinctrl/pinctrl-tegra-io-pad.h>
|
#include <dt-bindings/pinctrl/pinctrl-tegra-io-pad.h>
|
||||||
|
|
||||||
#define PMC_CNTRL 0x0
|
#define PMC_CNTRL 0x0
|
||||||
@ -126,6 +130,16 @@
|
|||||||
#define GPU_RG_CNTRL 0x2d4
|
#define GPU_RG_CNTRL 0x2d4
|
||||||
|
|
||||||
/* Tegra186 and later */
|
/* 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 0x4f4
|
||||||
#define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0)
|
#define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0)
|
||||||
|
|
||||||
@ -158,6 +172,38 @@ struct tegra_pmc_regs {
|
|||||||
unsigned int rst_level_mask;
|
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 {
|
struct tegra_pmc_soc {
|
||||||
unsigned int num_powergates;
|
unsigned int num_powergates;
|
||||||
const char *const *powergates;
|
const char *const *powergates;
|
||||||
@ -185,6 +231,9 @@ struct tegra_pmc_soc {
|
|||||||
unsigned int num_reset_sources;
|
unsigned int num_reset_sources;
|
||||||
const char * const *reset_levels;
|
const char * const *reset_levels;
|
||||||
unsigned int num_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[] = {
|
static const char * const tegra186_reset_sources[] = {
|
||||||
@ -271,6 +320,9 @@ struct tegra_pmc {
|
|||||||
struct mutex powergates_lock;
|
struct mutex powergates_lock;
|
||||||
|
|
||||||
struct pinctrl_dev *pctl_dev;
|
struct pinctrl_dev *pctl_dev;
|
||||||
|
|
||||||
|
struct irq_domain *domain;
|
||||||
|
struct irq_chip irq;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
|
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
|
||||||
@ -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)
|
static int tegra_pmc_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
@ -1690,6 +1911,10 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
|||||||
if (err)
|
if (err)
|
||||||
goto cleanup_restart_handler;
|
goto cleanup_restart_handler;
|
||||||
|
|
||||||
|
err = tegra_pmc_irq_init(pmc);
|
||||||
|
if (err < 0)
|
||||||
|
goto cleanup_restart_handler;
|
||||||
|
|
||||||
mutex_lock(&pmc->powergates_lock);
|
mutex_lock(&pmc->powergates_lock);
|
||||||
iounmap(pmc->base);
|
iounmap(pmc->base);
|
||||||
pmc->base = base;
|
pmc->base = base;
|
||||||
|
Loading…
Reference in New Issue
Block a user