mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
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/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>
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
@ -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[] = {
|
||||
@ -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) {
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user