tegrakernel/kernel/nvidia/drivers/platform/tegra/pm-irq-t18x.c

564 lines
13 KiB
C

/*
* Copyright (c) 2015-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kobject.h>
#include <linux/kernel.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
#include <linux/seq_file.h>
#include <linux/syscore_ops.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/irqchip/tegra.h>
#include <soc/tegra/pmc.h>
#include <soc/tegra/chip-id.h>
#include <soc/tegra/fuse.h>
#include "tegra186-aowake.h"
#define INT_OFFSET 32
#ifdef CONFIG_PM_SLEEP
/* Per wake registers */
#define WAKE_AOWAKE_CNTRL_0 0x0 /* ~0x17f */
#define WAKE_AOWAKE_MASK_W_0 0x180 /* ~0x2ff */
#define WAKE_AOWAKE_STATUS_W_0 0x30c /* ~0x48b */
/* Aggregated wake registers */
#define WAKE_AOWAKE_MASK_R_31_0_0 0x300
#define WAKE_AOWAKE_MASK_R_63_32_0 0x304
#define WAKE_AOWAKE_MASK_R_95_64_0 0x308
#define WAKE_AOWAKE_STATUS_R_31_0_0 0x48c
#define WAKE_AOWAKE_SW_STATUS_31_0_0 0x4a0
#define WAKE_AOWAKE_TIER2_ROUTING_31_0_0 0x4cc
#define WAKE_AOWAKE_SW_STATUS_W_0 0x49c
/* Regular registers */
#define WAKE_LATCH_SW 0x498
#define WAKE_NR_EVENTS 96
#define WAKE_NR_VECTORS (WAKE_NR_EVENTS / 32)
/* wake level/polarity constants */
enum {
WAKE_LEVEL_LO = 0,
WAKE_LEVEL_HI,
WAKE_LEVEL_ANY
};
static u32 wke_wake_enb[WAKE_NR_VECTORS];
static u32 wke_wake_level[WAKE_NR_VECTORS];
static u32 wke_wake_level_any[WAKE_NR_VECTORS];
static u32 wke_wake_irq_count[WAKE_NR_EVENTS];
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
static struct irq_domain *tegra_pm_irq_domain;
#endif
static inline void wk_set_bit(int nr, u32 *addr)
{
u32 mask = BIT(nr % 32);
addr[nr / 32] |= mask;
}
static inline void wk_clr_bit(int nr, u32 *addr)
{
u32 mask = BIT(nr % 32);
addr[nr / 32] &= ~mask;
}
static inline int wk_test_bit(int nr, u32 *addr)
{
u32 mask = BIT(nr % 32);
return !!(addr[nr / 32] & mask);
}
/* ensures that sufficient time is passed for a register write to
* serialize into the 32KHz domain */
static void wke_32kwritel(u32 val, u32 reg)
{
tegra_aowake_write(val, reg);
udelay(130);
}
static void print_vals(char *name, u32 *vals)
{
int i;
for (i = 0; i < WAKE_NR_VECTORS; i++)
pr_info("Wake[%d-%d] %s=%#x\n",
(i + 1) * 32 - 1, i * 32, name, vals[i]);
}
static void wke_write_wake_masks(u32 *enb)
{
u32 reg = WAKE_AOWAKE_MASK_W_0;
u32 val;
int i;
for (i = 0; i < WAKE_NR_EVENTS; i++, reg += 4) {
val = wk_test_bit(i, enb);
tegra_aowake_write(val, reg);
}
print_vals("enable", enb);
}
static void wke_write_tier2_routing(u32 *enb)
{
int i;
u32 reg = WAKE_AOWAKE_TIER2_ROUTING_31_0_0;
for (i = 0; i < WAKE_NR_VECTORS; i++, reg += 4)
tegra_aowake_write(enb[i], reg);
print_vals("route", enb);
}
static void wke_write_wake_level(int wake, int level)
{
u32 val;
u32 reg = WAKE_AOWAKE_CNTRL_0 + wake*4;
val = tegra_aowake_read(reg);
if (level)
val |= (1 << 3);
else
val &= ~(1 << 3);
tegra_aowake_write(val, reg);
}
static void wke_write_wake_levels(u32 *lvl)
{
int i;
for (i = 0; i < WAKE_NR_EVENTS; i++) {
wke_write_wake_level(i, wk_test_bit(i, lvl));
}
print_vals("level", lvl);
}
int tegra18x_read_wake_status(u32 *status)
{
int i;
u32 reg = WAKE_AOWAKE_STATUS_R_31_0_0;
u32 mask = WAKE_AOWAKE_TIER2_ROUTING_31_0_0;
for (i = 0; i < WAKE_NR_VECTORS; i++, reg += 4, mask += 4) {
status[i] = tegra_aowake_read(reg);
status[i] = status[i] & tegra_aowake_read(mask);
}
return WAKE_NR_VECTORS;
}
static void wke_clear_sw_wake_status(void)
{
wke_32kwritel(1, WAKE_AOWAKE_SW_STATUS_W_0);
}
static void wke_read_sw_wake_status(u32 *status)
{
int i;
u32 reg = WAKE_AOWAKE_SW_STATUS_31_0_0;
for (i = 0; i < WAKE_NR_EVENTS; i++)
wke_write_wake_level(i, 0);
wke_clear_sw_wake_status();
wke_32kwritel(1, WAKE_LATCH_SW);
/*
* WAKE_AOWAKE_SW_STATUS is edge triggered, so in order to
* obtain the current status of the wake signals, change the polarity
* of the wake level from 0->1 while latching to force a positive edge
* if the sampled signal is '1'.
*/
for (i = 0; i < WAKE_NR_EVENTS; i++)
wke_write_wake_level(i, 1);
/*
* Wait for the update to be synced into the 32kHz domain,
* and let enough time lapse, so that the wake signals have time to
* be sampled.
*/
udelay(300);
wke_32kwritel(0, WAKE_LATCH_SW);
for (i = 0; i < WAKE_NR_VECTORS; i++, reg += 4)
status[i] = tegra_aowake_read(reg);
}
static void wke_clear_wake_status(void)
{
u32 regw;
u32 status;
int i, wake;
u32 reg = WAKE_AOWAKE_STATUS_R_31_0_0;
u32 mask = WAKE_AOWAKE_TIER2_ROUTING_31_0_0;
unsigned long ulong_status;
for (i = 0; i < WAKE_NR_VECTORS; i++, reg += 4, mask += 4) {
status = tegra_aowake_read(reg);
status = status & tegra_aowake_read(mask);
ulong_status = (unsigned long)status;
regw = WAKE_AOWAKE_STATUS_W_0 + i * 32 * 4;
for_each_set_bit(wake, &ulong_status, 32)
wke_32kwritel(1, regw + wake * 4);
}
}
static int wke_irq_set_wake(int wake, int enable)
{
if (wake < 0 || wake >= WAKE_NR_EVENTS)
return -EINVAL;
if (enable) {
wk_set_bit(wake, wke_wake_enb);
pr_info("Enabling wake%d\n", wake);
} else {
wk_clr_bit(wake, wke_wake_enb);
pr_info("Disabling wake%d\n", wake);
}
return 0;
}
static int wke_irq_set_wake_level(int wake, int flow_type)
{
if (wake < 0 || wake >= WAKE_NR_EVENTS)
return -EINVAL;
switch (flow_type) {
case IRQF_TRIGGER_FALLING:
case IRQF_TRIGGER_LOW:
wk_clr_bit(wake, wke_wake_level);
wk_clr_bit(wake, wke_wake_level_any);
break;
case IRQF_TRIGGER_HIGH:
case IRQF_TRIGGER_RISING:
wk_set_bit(wake, wke_wake_level);
wk_set_bit(wake, wke_wake_level_any);
break;
case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING:
wk_set_bit(wake, wke_wake_level_any);
break;
default:
return -EINVAL;
}
return 0;
}
/* translate sc7 wake sources back into irqs to catch edge triggered wakeups */
static void process_wake_event(int index, u32 status)
{
int irq;
irq_hw_number_t hwirq;
int wake;
struct irq_desc *desc;
unsigned long ulong_status = (unsigned long)status;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
int gpio;
#endif
pr_info("Wake[%d:%d] status=0x%x\n",
(index + 1) * 32, index * 32, status);
for_each_set_bit(wake, &ulong_status, 32) {
hwirq = tegra_wake_to_irq(wake + 32 * index);
if (hwirq == -EINVAL) {
pr_info("Resume caused by WAKE%d\n",
(wake + 32 * index));
continue;
}
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
gpio = tegra_wake_to_gpio(wake + 32 * index);
if (gpio != -EINVAL)
irq = gpio_to_irq(gpio);
else
irq = irq_find_mapping(tegra_pm_irq_domain, hwirq);
#else
irq = hwirq;
#endif
desc = irq_to_desc(irq);
if (!desc || !desc->action || !desc->action->name) {
pr_info("Resume caused by WAKE%d, irq %d\n",
(wake + 32 * index), irq);
continue;
}
pr_info("Resume caused by WAKE%d, %s\n", (wake + 32 * index),
desc->action->name);
wke_wake_irq_count[wake + 32 * index]++;
generic_handle_irq(irq);
}
}
static void tegra_pm_irq_resume(void)
{
int i;
u32 status;
u32 reg = WAKE_AOWAKE_STATUS_R_31_0_0;
u32 mask = WAKE_AOWAKE_TIER2_ROUTING_31_0_0;
for (i = 0; i < WAKE_NR_VECTORS; i++, reg += 4, mask += 4) {
status = tegra_aowake_read(reg);
status = status & tegra_aowake_read(mask);
process_wake_event(i, status);
}
}
/* set up sc7 wake sources */
static int tegra_pm_irq_suspend(void)
{
u32 status[WAKE_NR_VECTORS];
u32 lvl[WAKE_NR_VECTORS];
u32 wake_level[WAKE_NR_VECTORS];
u32 wake_enb[WAKE_NR_VECTORS];
enum tegra_revision revision;
int i;
wke_read_sw_wake_status(status);
/* flip the wakeup trigger for any-edge triggered pads
* which are currently asserting as wakeups */
for (i = 0; i < WAKE_NR_VECTORS; i++) {
lvl[i] = ~status[i] & wke_wake_level_any[i];
wake_level[i] = lvl[i] | wke_wake_level[i];
wake_enb[i] = wke_wake_enb[i];
}
/* Clear PMC Wake Status registers while going to suspend */
wke_clear_wake_status();
revision = tegra_chip_get_revision();
if (revision < TEGRA186_REVISION_A02p)
wake_enb[2] &= ~(7 << 12);
wke_write_wake_levels(wake_level);
wke_write_wake_masks(wake_enb);
wke_write_tier2_routing(wake_enb);
return 0;
}
static int pm_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
int i;
int ret;
int wake_size;
int wake_list[WAKE_NR_EVENTS];
int err = 0;
tegra_irq_to_wake(d->hwirq, wake_list, &wake_size);
for (i = 0; i < wake_size; i++) {
ret = wke_irq_set_wake_level(wake_list[i], flow_type);
if (ret < 0) {
pr_err("Set lp0 wake type=%d fail for irq=%d, wake%d ret=%d\n",
flow_type, d->irq, wake_list[i], ret);
if (!err)
err = ret;
}
}
return err;
}
static int pm_irq_set_wake(struct irq_data *d, unsigned int enable)
{
int i;
int ret;
int wake_size;
int wake_list[WAKE_NR_EVENTS];
int err = 0;
tegra_irq_to_wake(d->hwirq, wake_list, &wake_size);
for (i = 0; i < wake_size; i++) {
/* pmc lp0 wake enable for non-gpio wake sources */
ret = wke_irq_set_wake(wake_list[i], enable);
if (ret < 0) {
pr_err("Failed lp0 wake %s for irq=%d, wake%d ret=%d\n",
(enable ? "enable" : "disable"), d->irq,
wake_list[i], ret);
if (!err)
err = ret;
}
}
return err;
}
int tegra_pm_irq_set_wake_type(int wake, int flow_type)
{
return wke_irq_set_wake_level(wake, flow_type);
}
int tegra_pm_irq_set_wake(int wake, int enable)
{
return wke_irq_set_wake(wake, enable);
}
static struct syscore_ops pm_irq_ops = {
.suspend = tegra_pm_irq_suspend,
.resume = tegra_pm_irq_resume,
.save = tegra_pm_irq_suspend,
.restore = tegra_pm_irq_resume,
};
#ifndef CONFIG_IRQ_DOMAIN_HIERARCHY
static int tegra_pm_irq_init(void)
{
register_syscore_ops(&pm_irq_ops);
return 0;
}
subsys_initcall(tegra_pm_irq_init);
#endif
int __init pm_irq_init(void)
{
tegra_wakeup_table_init();
#ifndef CONFIG_IRQ_DOMAIN_HIERARCHY
/* Hook into GIC ops */
gic_arch_extn.irq_set_type = pm_irq_set_type;
gic_arch_extn.irq_set_wake = pm_irq_set_wake;
#endif
return 0;
}
#else /* CONFIG_PM_SLEEP */
int tegra18x_read_wake_status(u32 *status)
{
return 0;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
static struct irq_chip tegra_pm_chip = {
.name = "PM",
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_retrigger = irq_chip_retrigger_hierarchy,
#ifdef CONFIG_PM_SLEEP
.irq_set_wake = pm_irq_set_wake,
.irq_set_type = pm_irq_set_type,
#endif
#ifdef CONFIG_SMP
.irq_set_affinity = irq_chip_set_affinity_parent,
#endif
};
static int tegra_pm_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (is_of_node(fwspec->fwnode)) {
if (fwspec->param_count != 3)
return -EINVAL;
/* No PPI should point to this domain */
if (fwspec->param[0] != 0)
return -EINVAL;
*hwirq = fwspec->param[1] + INT_OFFSET;
*type = fwspec->param[2];
return 0;
}
return -EINVAL;
}
static int tegra_pm_domain_alloc(struct irq_domain *domain,
unsigned int virq,
unsigned int nr_irqs, void *data)
{
struct irq_fwspec *fwspec = data;
struct irq_fwspec parent_fwspec;
irq_hw_number_t hwirq;
int i;
if (fwspec->param_count != 3)
return -EINVAL; /* Not GIC compliant */
if (fwspec->param[0] != 0)
return -EINVAL; /* No PPI should point to this domain */
hwirq = fwspec->param[1] + INT_OFFSET;
for (i = 0; i < nr_irqs; i++)
irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
&tegra_pm_chip, NULL);
parent_fwspec = *fwspec;
parent_fwspec.fwnode = domain->parent->fwnode;
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
&parent_fwspec);
}
static const struct irq_domain_ops tegra_pm_domain_ops = {
.translate = tegra_pm_domain_translate,
.alloc = tegra_pm_domain_alloc,
.free = irq_domain_free_irqs_common,
};
static int __init tegra_pm_irq_init(struct device_node *node,
struct device_node *parent)
{
struct irq_domain *domain, *parent_domain;
if (!parent) {
pr_err("%s: no parent, giving up\n", node->full_name);
return -ENODEV;
}
parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("%s: unable to obtain parent domain\n", node->full_name);
return -ENXIO;
}
domain = irq_domain_add_hierarchy(parent_domain, 0, 0, node,
&tegra_pm_domain_ops, NULL);
if (!domain)
return -ENOMEM;
#ifdef CONFIG_PM_SLEEP
tegra_pm_irq_domain = domain;
register_syscore_ops(&pm_irq_ops);
#endif
return 0;
}
IRQCHIP_DECLARE(tegra_pm_irq, "nvidia,tegra186-pm-irq", tegra_pm_irq_init);
IRQCHIP_DECLARE(tegra19x_pm_irq, "nvidia,tegra194-pm-irq", tegra_pm_irq_init);
#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */