2016-01-28 20:09:51 +00:00
|
|
|
/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
|
2012-01-05 22:32:59 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
|
|
* only version 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_address.h>
|
|
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <linux/irqdomain.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/spmi.h>
|
|
|
|
#include <linux/radix-tree.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/printk.h>
|
2013-02-26 23:29:22 +00:00
|
|
|
#include <linux/ratelimit.h>
|
2013-12-12 00:11:54 +00:00
|
|
|
#include <linux/irqchip/qpnp-int.h>
|
2017-04-18 01:29:57 +00:00
|
|
|
#include <linux/wakeup_reason.h>
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
#include <asm/irq.h>
|
|
|
|
|
|
|
|
/* 16 slave_ids, 256 per_ids per slave, and 8 ints per per_id */
|
2013-02-26 23:29:22 +00:00
|
|
|
#define QPNPINT_NR_IRQS (16 * 256 * 8)
|
|
|
|
/* This value is guaranteed not to be valid for private data */
|
|
|
|
#define QPNPINT_INVALID_DATA 0x80000000
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2017-04-18 01:29:57 +00:00
|
|
|
#ifdef CONFIG_SEC_PM_DEBUG
|
|
|
|
enum {
|
|
|
|
MSM_QPNP_INT_DBG_DISABLED = 0,
|
|
|
|
MSM_QPNP_INT_DBG_SHOW_IRQ = BIT(0),
|
|
|
|
};
|
|
|
|
|
|
|
|
int msm_qpnp_int_debug_mask = MSM_QPNP_INT_DBG_DISABLED;
|
|
|
|
|
|
|
|
module_param_named(
|
|
|
|
debug_mask, msm_qpnp_int_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
|
|
|
|
);
|
|
|
|
#endif
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
enum qpnpint_regs {
|
|
|
|
QPNPINT_REG_RT_STS = 0x10,
|
|
|
|
QPNPINT_REG_SET_TYPE = 0x11,
|
|
|
|
QPNPINT_REG_POLARITY_HIGH = 0x12,
|
|
|
|
QPNPINT_REG_POLARITY_LOW = 0x13,
|
|
|
|
QPNPINT_REG_LATCHED_CLR = 0x14,
|
|
|
|
QPNPINT_REG_EN_SET = 0x15,
|
|
|
|
QPNPINT_REG_EN_CLR = 0x16,
|
|
|
|
QPNPINT_REG_LATCHED_STS = 0x18,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct q_perip_data {
|
|
|
|
uint8_t type; /* bitmap */
|
|
|
|
uint8_t pol_high; /* bitmap */
|
|
|
|
uint8_t pol_low; /* bitmap */
|
|
|
|
uint8_t int_en; /* bitmap */
|
|
|
|
uint8_t use_count;
|
2016-01-28 20:09:51 +00:00
|
|
|
spinlock_t lock;
|
2012-01-05 22:32:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct q_irq_data {
|
|
|
|
uint32_t priv_d; /* data to optimize arbiter interactions */
|
|
|
|
struct q_chip_data *chip_d;
|
|
|
|
struct q_perip_data *per_d;
|
|
|
|
uint8_t mask_shift;
|
|
|
|
uint8_t spmi_slave;
|
|
|
|
uint16_t spmi_offset;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct q_chip_data {
|
|
|
|
int bus_nr;
|
2012-06-01 20:33:51 +00:00
|
|
|
struct irq_domain *domain;
|
2013-02-26 23:29:22 +00:00
|
|
|
struct qpnp_local_int *cb;
|
2012-01-05 22:32:59 +00:00
|
|
|
struct spmi_controller *spmi_ctrl;
|
|
|
|
struct radix_tree_root per_tree;
|
2012-06-01 20:33:51 +00:00
|
|
|
struct list_head list;
|
2012-01-05 22:32:59 +00:00
|
|
|
};
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
static LIST_HEAD(qpnpint_chips);
|
|
|
|
static DEFINE_MUTEX(qpnpint_chips_mutex);
|
|
|
|
|
|
|
|
#define QPNPINT_MAX_BUSSES 4
|
|
|
|
struct q_chip_data *chip_lookup[QPNPINT_MAX_BUSSES];
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* qpnpint_encode_hwirq - translate between qpnp_irq_spec and
|
|
|
|
* hwirq representation.
|
|
|
|
*
|
|
|
|
* slave_offset = (addr->slave * 256 * 8);
|
|
|
|
* perip_offset = slave_offset + (addr->perip * 8);
|
|
|
|
* return perip_offset + addr->irq;
|
|
|
|
*/
|
|
|
|
static inline int qpnpint_encode_hwirq(struct qpnp_irq_spec *spec)
|
|
|
|
{
|
|
|
|
uint32_t hwirq;
|
|
|
|
|
|
|
|
if (spec->slave > 15 || spec->irq > 7)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
hwirq = (spec->slave << 11);
|
|
|
|
hwirq |= (spec->per << 3);
|
|
|
|
hwirq |= spec->irq;
|
|
|
|
|
|
|
|
return hwirq;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* qpnpint_decode_hwirq - translate between hwirq and
|
|
|
|
* qpnp_irq_spec representation.
|
|
|
|
*/
|
|
|
|
static inline int qpnpint_decode_hwirq(unsigned long hwirq,
|
|
|
|
struct qpnp_irq_spec *spec)
|
|
|
|
{
|
|
|
|
if (hwirq > 65535)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spec->slave = (hwirq >> 11) & 0xF;
|
|
|
|
spec->per = (hwirq >> 3) & 0xFF;
|
|
|
|
spec->irq = hwirq & 0x7;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-28 22:21:14 +00:00
|
|
|
static int qpnpint_spmi_read(struct q_irq_data *irq_d, uint8_t reg,
|
|
|
|
void *buf, uint32_t len)
|
|
|
|
{
|
|
|
|
struct q_chip_data *chip_d = irq_d->chip_d;
|
|
|
|
|
|
|
|
if (!chip_d->spmi_ctrl)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
return spmi_ext_register_readl(chip_d->spmi_ctrl, irq_d->spmi_slave,
|
|
|
|
irq_d->spmi_offset + reg, buf, len);
|
|
|
|
}
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
static int qpnpint_spmi_write(struct q_irq_data *irq_d, uint8_t reg,
|
|
|
|
void *buf, uint32_t len)
|
|
|
|
{
|
|
|
|
struct q_chip_data *chip_d = irq_d->chip_d;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!chip_d->spmi_ctrl)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
rc = spmi_ext_register_writel(chip_d->spmi_ctrl, irq_d->spmi_slave,
|
|
|
|
irq_d->spmi_offset + reg, buf, len);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
static int qpnpint_arbiter_op(struct irq_data *d,
|
|
|
|
struct q_irq_data *irq_d,
|
|
|
|
int (*arb_op)(struct spmi_controller *,
|
|
|
|
struct qpnp_irq_spec *,
|
|
|
|
uint32_t))
|
|
|
|
|
|
|
|
{
|
|
|
|
struct q_chip_data *chip_d = irq_d->chip_d;
|
|
|
|
struct qpnp_irq_spec q_spec;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!arb_op)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!chip_d->cb->register_priv_data) {
|
|
|
|
pr_warn_ratelimited("No ability to register arbiter registration data\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = qpnpint_decode_hwirq(d->hwirq, &q_spec);
|
|
|
|
if (rc) {
|
|
|
|
pr_err_ratelimited("%s: decode failed on hwirq %lu\n",
|
|
|
|
__func__, d->hwirq);
|
|
|
|
return rc;
|
2013-07-25 03:15:36 +00:00
|
|
|
}
|
2013-02-26 23:29:22 +00:00
|
|
|
|
2013-07-25 03:15:36 +00:00
|
|
|
if (irq_d->priv_d == QPNPINT_INVALID_DATA) {
|
|
|
|
rc = chip_d->cb->register_priv_data(chip_d->spmi_ctrl,
|
|
|
|
&q_spec, &irq_d->priv_d);
|
|
|
|
if (rc) {
|
|
|
|
pr_err_ratelimited(
|
|
|
|
"%s: decode failed on hwirq %lu rc = %d\n",
|
|
|
|
__func__, d->hwirq, rc);
|
|
|
|
return rc;
|
2013-02-26 23:29:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-07-25 03:15:36 +00:00
|
|
|
arb_op(chip_d->spmi_ctrl, &q_spec, irq_d->priv_d);
|
2013-02-26 23:29:22 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-11-26 22:28:27 +00:00
|
|
|
static void qpnpint_irq_ack(struct irq_data *d)
|
2012-01-05 22:32:59 +00:00
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
|
|
|
|
2013-11-26 22:28:27 +00:00
|
|
|
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_LATCHED_CLR,
|
|
|
|
&irq_d->mask_shift, 1);
|
2013-02-26 23:29:22 +00:00
|
|
|
if (rc) {
|
2013-11-26 22:28:27 +00:00
|
|
|
pr_err_ratelimited("spmi write failure on irq %d, rc=%d\n",
|
|
|
|
d->irq, rc);
|
2013-02-26 23:29:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
2013-11-26 22:28:27 +00:00
|
|
|
static void qpnpint_irq_mask(struct irq_data *d)
|
2012-01-05 22:32:59 +00:00
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
|
|
|
|
struct q_chip_data *chip_d = irq_d->chip_d;
|
|
|
|
struct q_perip_data *per_d = irq_d->per_d;
|
|
|
|
int rc;
|
2016-01-28 20:09:51 +00:00
|
|
|
uint8_t prev_int_en;
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
if (!chip_d->cb) {
|
|
|
|
pr_warn_ratelimited("No arbiter on bus=%u slave=%u offset=%u\n",
|
|
|
|
chip_d->bus_nr, irq_d->spmi_slave,
|
|
|
|
irq_d->spmi_offset);
|
|
|
|
return;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
2016-01-28 20:09:51 +00:00
|
|
|
spin_lock(&per_d->lock);
|
|
|
|
prev_int_en = per_d->int_en;
|
2012-01-05 22:32:59 +00:00
|
|
|
per_d->int_en &= ~irq_d->mask_shift;
|
|
|
|
|
2013-07-25 03:15:36 +00:00
|
|
|
if (prev_int_en && !(per_d->int_en)) {
|
|
|
|
/*
|
|
|
|
* no interrupt on this peripheral is enabled
|
|
|
|
* ask the arbiter to ignore this peripheral
|
|
|
|
*/
|
|
|
|
qpnpint_arbiter_op(d, irq_d, chip_d->cb->mask);
|
|
|
|
}
|
2016-01-28 20:09:51 +00:00
|
|
|
spin_unlock(&per_d->lock);
|
2013-07-25 03:15:36 +00:00
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_CLR,
|
2013-11-26 22:28:27 +00:00
|
|
|
(u8 *)&irq_d->mask_shift, 1);
|
2013-02-26 23:29:22 +00:00
|
|
|
if (rc) {
|
2013-11-26 22:28:27 +00:00
|
|
|
pr_err_ratelimited("spmi failure on irq %d\n", d->irq);
|
2013-02-26 23:29:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2013-11-26 22:28:27 +00:00
|
|
|
pr_debug("done hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qpnpint_irq_mask_ack(struct irq_data *d)
|
|
|
|
{
|
|
|
|
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
|
|
|
|
|
|
|
qpnpint_irq_mask(d);
|
|
|
|
qpnpint_irq_ack(d);
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qpnpint_irq_unmask(struct irq_data *d)
|
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
|
|
|
|
struct q_chip_data *chip_d = irq_d->chip_d;
|
|
|
|
struct q_perip_data *per_d = irq_d->per_d;
|
|
|
|
int rc;
|
spmi: qpnp-int: clear the latched status when unmasking an interrupt
PMIC interrupts each have an internal latched status bit which is
not visible from any register. This status bit is set as soon as
the conditions specified in the interrupt type and polarity
registers are met even if the interrupt is not enabled. When it
is set, nothing else changes within the PMIC and no interrupt
notification packets are sent. If the internal latched status
bit is set when an interrupt is enabled, then the value is
immediately propagated into the interrupt latched status register
and an interrupt notification packet is sent out from the PMIC
over SPMI.
This PMIC hardware behavior can lead to a situation where the
handler for a level triggered interrupt is called immediately
after enable_irq() is called even though the interrupt physically
triggered while it was disabled within the genirq framework.
This situation takes place if the the interrupt fires twice after
calling disable_irq(). The first time it fires, the level flow
handler will mask and disregard it. Unfortunately, the second
time it fires, the internal latched status bit is set within the
PMIC and no further notification is received. When enable_irq()
is called later, the interrupt is unmasked (enabled in the PMIC)
which results in the PMIC immediately sending an interrupt
notification packet out over SPMI. This breaks the semantics
of level triggered interrupts within the genirq framework since
they should be completely ignored while disabled.
The PMIC internal latched status behavior also affects how
interrupts are treated during suspend. While entering suspend,
all interrupts not specified as wakeup mode are masked. Upon
resume, these interrupts are unmasked. Thus if any of the
non-wakeup PMIC interrupts fired while the system was suspended,
then the PMIC will send interrupt notification packets out via
SPMI as soon as they are unmasked during resume. This behavior
violates genirq semantics as well since non-wakeup interrupts
should be completely ignored during suspend.
Modify the qpnpint_irq_unmask() function so that the interrupt
latched status clear register is written immediately before the
interrupt enable register. This clears the internal latched
status bit of the interrupt so that it cannot trigger spuriously
immediately upon being enabled.
Also, before writing these registers, check if the interrupt is
already enabled within the PMIC. If it is enabled, then no
further register writes are required. This condition check
ensures that a valid latched status register bit is not cleared
until it is properly handled.
Change-Id: Ib99a384bfeba440a4ad91cd0e16e8f62e4352f28
Signed-off-by: David Collins <collinsd@codeaurora.org>
2013-11-05 17:31:16 +00:00
|
|
|
uint8_t buf[2];
|
2016-01-28 20:09:51 +00:00
|
|
|
uint8_t prev_int_en;
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
if (!chip_d->cb) {
|
|
|
|
pr_warn_ratelimited("No arbiter on bus=%u slave=%u offset=%u\n",
|
|
|
|
chip_d->bus_nr, irq_d->spmi_slave,
|
|
|
|
irq_d->spmi_offset);
|
|
|
|
return;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
2016-01-28 20:09:51 +00:00
|
|
|
spin_lock(&per_d->lock);
|
|
|
|
prev_int_en = per_d->int_en;
|
2012-01-05 22:32:59 +00:00
|
|
|
per_d->int_en |= irq_d->mask_shift;
|
2013-07-25 03:15:36 +00:00
|
|
|
if (!prev_int_en && per_d->int_en) {
|
|
|
|
/*
|
|
|
|
* no interrupt prior to this call was enabled for the
|
|
|
|
* peripheral. Ask the arbiter to enable interrupts for
|
|
|
|
* this peripheral
|
|
|
|
*/
|
|
|
|
qpnpint_arbiter_op(d, irq_d, chip_d->cb->unmask);
|
|
|
|
}
|
2016-01-28 20:09:51 +00:00
|
|
|
spin_unlock(&per_d->lock);
|
spmi: qpnp-int: clear the latched status when unmasking an interrupt
PMIC interrupts each have an internal latched status bit which is
not visible from any register. This status bit is set as soon as
the conditions specified in the interrupt type and polarity
registers are met even if the interrupt is not enabled. When it
is set, nothing else changes within the PMIC and no interrupt
notification packets are sent. If the internal latched status
bit is set when an interrupt is enabled, then the value is
immediately propagated into the interrupt latched status register
and an interrupt notification packet is sent out from the PMIC
over SPMI.
This PMIC hardware behavior can lead to a situation where the
handler for a level triggered interrupt is called immediately
after enable_irq() is called even though the interrupt physically
triggered while it was disabled within the genirq framework.
This situation takes place if the the interrupt fires twice after
calling disable_irq(). The first time it fires, the level flow
handler will mask and disregard it. Unfortunately, the second
time it fires, the internal latched status bit is set within the
PMIC and no further notification is received. When enable_irq()
is called later, the interrupt is unmasked (enabled in the PMIC)
which results in the PMIC immediately sending an interrupt
notification packet out over SPMI. This breaks the semantics
of level triggered interrupts within the genirq framework since
they should be completely ignored while disabled.
The PMIC internal latched status behavior also affects how
interrupts are treated during suspend. While entering suspend,
all interrupts not specified as wakeup mode are masked. Upon
resume, these interrupts are unmasked. Thus if any of the
non-wakeup PMIC interrupts fired while the system was suspended,
then the PMIC will send interrupt notification packets out via
SPMI as soon as they are unmasked during resume. This behavior
violates genirq semantics as well since non-wakeup interrupts
should be completely ignored during suspend.
Modify the qpnpint_irq_unmask() function so that the interrupt
latched status clear register is written immediately before the
interrupt enable register. This clears the internal latched
status bit of the interrupt so that it cannot trigger spuriously
immediately upon being enabled.
Also, before writing these registers, check if the interrupt is
already enabled within the PMIC. If it is enabled, then no
further register writes are required. This condition check
ensures that a valid latched status register bit is not cleared
until it is properly handled.
Change-Id: Ib99a384bfeba440a4ad91cd0e16e8f62e4352f28
Signed-off-by: David Collins <collinsd@codeaurora.org>
2013-11-05 17:31:16 +00:00
|
|
|
|
|
|
|
/* Check the current state of the interrupt enable bit. */
|
|
|
|
rc = qpnpint_spmi_read(irq_d, QPNPINT_REG_EN_SET, buf, 1);
|
2013-02-26 23:29:22 +00:00
|
|
|
if (rc) {
|
spmi: qpnp-int: clear the latched status when unmasking an interrupt
PMIC interrupts each have an internal latched status bit which is
not visible from any register. This status bit is set as soon as
the conditions specified in the interrupt type and polarity
registers are met even if the interrupt is not enabled. When it
is set, nothing else changes within the PMIC and no interrupt
notification packets are sent. If the internal latched status
bit is set when an interrupt is enabled, then the value is
immediately propagated into the interrupt latched status register
and an interrupt notification packet is sent out from the PMIC
over SPMI.
This PMIC hardware behavior can lead to a situation where the
handler for a level triggered interrupt is called immediately
after enable_irq() is called even though the interrupt physically
triggered while it was disabled within the genirq framework.
This situation takes place if the the interrupt fires twice after
calling disable_irq(). The first time it fires, the level flow
handler will mask and disregard it. Unfortunately, the second
time it fires, the internal latched status bit is set within the
PMIC and no further notification is received. When enable_irq()
is called later, the interrupt is unmasked (enabled in the PMIC)
which results in the PMIC immediately sending an interrupt
notification packet out over SPMI. This breaks the semantics
of level triggered interrupts within the genirq framework since
they should be completely ignored while disabled.
The PMIC internal latched status behavior also affects how
interrupts are treated during suspend. While entering suspend,
all interrupts not specified as wakeup mode are masked. Upon
resume, these interrupts are unmasked. Thus if any of the
non-wakeup PMIC interrupts fired while the system was suspended,
then the PMIC will send interrupt notification packets out via
SPMI as soon as they are unmasked during resume. This behavior
violates genirq semantics as well since non-wakeup interrupts
should be completely ignored during suspend.
Modify the qpnpint_irq_unmask() function so that the interrupt
latched status clear register is written immediately before the
interrupt enable register. This clears the internal latched
status bit of the interrupt so that it cannot trigger spuriously
immediately upon being enabled.
Also, before writing these registers, check if the interrupt is
already enabled within the PMIC. If it is enabled, then no
further register writes are required. This condition check
ensures that a valid latched status register bit is not cleared
until it is properly handled.
Change-Id: Ib99a384bfeba440a4ad91cd0e16e8f62e4352f28
Signed-off-by: David Collins <collinsd@codeaurora.org>
2013-11-05 17:31:16 +00:00
|
|
|
pr_err("SPMI read failure for IRQ %d, rc=%d\n", d->irq, rc);
|
2013-02-26 23:29:22 +00:00
|
|
|
return;
|
|
|
|
}
|
spmi: qpnp-int: clear the latched status when unmasking an interrupt
PMIC interrupts each have an internal latched status bit which is
not visible from any register. This status bit is set as soon as
the conditions specified in the interrupt type and polarity
registers are met even if the interrupt is not enabled. When it
is set, nothing else changes within the PMIC and no interrupt
notification packets are sent. If the internal latched status
bit is set when an interrupt is enabled, then the value is
immediately propagated into the interrupt latched status register
and an interrupt notification packet is sent out from the PMIC
over SPMI.
This PMIC hardware behavior can lead to a situation where the
handler for a level triggered interrupt is called immediately
after enable_irq() is called even though the interrupt physically
triggered while it was disabled within the genirq framework.
This situation takes place if the the interrupt fires twice after
calling disable_irq(). The first time it fires, the level flow
handler will mask and disregard it. Unfortunately, the second
time it fires, the internal latched status bit is set within the
PMIC and no further notification is received. When enable_irq()
is called later, the interrupt is unmasked (enabled in the PMIC)
which results in the PMIC immediately sending an interrupt
notification packet out over SPMI. This breaks the semantics
of level triggered interrupts within the genirq framework since
they should be completely ignored while disabled.
The PMIC internal latched status behavior also affects how
interrupts are treated during suspend. While entering suspend,
all interrupts not specified as wakeup mode are masked. Upon
resume, these interrupts are unmasked. Thus if any of the
non-wakeup PMIC interrupts fired while the system was suspended,
then the PMIC will send interrupt notification packets out via
SPMI as soon as they are unmasked during resume. This behavior
violates genirq semantics as well since non-wakeup interrupts
should be completely ignored during suspend.
Modify the qpnpint_irq_unmask() function so that the interrupt
latched status clear register is written immediately before the
interrupt enable register. This clears the internal latched
status bit of the interrupt so that it cannot trigger spuriously
immediately upon being enabled.
Also, before writing these registers, check if the interrupt is
already enabled within the PMIC. If it is enabled, then no
further register writes are required. This condition check
ensures that a valid latched status register bit is not cleared
until it is properly handled.
Change-Id: Ib99a384bfeba440a4ad91cd0e16e8f62e4352f28
Signed-off-by: David Collins <collinsd@codeaurora.org>
2013-11-05 17:31:16 +00:00
|
|
|
|
|
|
|
if (!(buf[0] & irq_d->mask_shift)) {
|
|
|
|
/*
|
|
|
|
* Since the interrupt is currently disabled, write to both the
|
|
|
|
* LATCHED_CLR and EN_SET registers so that a spurious interrupt
|
|
|
|
* cannot be triggered when the interrupt is enabled.
|
|
|
|
*/
|
|
|
|
buf[0] = irq_d->mask_shift;
|
|
|
|
buf[1] = irq_d->mask_shift;
|
|
|
|
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_LATCHED_CLR, buf, 2);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("SPMI write failure for IRQ %d, rc=%d\n", d->irq,
|
|
|
|
rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type)
|
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
|
|
|
|
struct q_perip_data *per_d = irq_d->per_d;
|
|
|
|
int rc;
|
|
|
|
u8 buf[3];
|
|
|
|
|
|
|
|
pr_debug("hwirq %lu irq: %d flow: 0x%x\n", d->hwirq,
|
|
|
|
d->irq, flow_type);
|
|
|
|
|
|
|
|
per_d->pol_high &= ~irq_d->mask_shift;
|
|
|
|
per_d->pol_low &= ~irq_d->mask_shift;
|
|
|
|
if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
|
|
|
|
per_d->type |= irq_d->mask_shift; /* edge trig */
|
|
|
|
if (flow_type & IRQF_TRIGGER_RISING)
|
|
|
|
per_d->pol_high |= irq_d->mask_shift;
|
|
|
|
if (flow_type & IRQF_TRIGGER_FALLING)
|
|
|
|
per_d->pol_low |= irq_d->mask_shift;
|
|
|
|
} else {
|
|
|
|
if ((flow_type & IRQF_TRIGGER_HIGH) &&
|
|
|
|
(flow_type & IRQF_TRIGGER_LOW))
|
|
|
|
return -EINVAL;
|
|
|
|
per_d->type &= ~irq_d->mask_shift; /* level trig */
|
|
|
|
if (flow_type & IRQF_TRIGGER_HIGH)
|
|
|
|
per_d->pol_high |= irq_d->mask_shift;
|
|
|
|
else
|
2012-06-08 00:05:41 +00:00
|
|
|
per_d->pol_low |= irq_d->mask_shift;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
buf[0] = per_d->type;
|
|
|
|
buf[1] = per_d->pol_high;
|
|
|
|
buf[2] = per_d->pol_low;
|
|
|
|
|
|
|
|
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_SET_TYPE, &buf, 3);
|
2013-02-26 23:29:22 +00:00
|
|
|
if (rc) {
|
2012-06-01 20:33:51 +00:00
|
|
|
pr_err("spmi failure on irq %d\n", d->irq);
|
2013-02-26 23:29:22 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2013-11-25 19:34:10 +00:00
|
|
|
if (flow_type & IRQ_TYPE_EDGE_BOTH)
|
|
|
|
__irq_set_handler_locked(d->irq, handle_edge_irq);
|
|
|
|
else
|
|
|
|
__irq_set_handler_locked(d->irq, handle_level_irq);
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
return 0;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
2013-02-28 22:21:14 +00:00
|
|
|
static int qpnpint_irq_read_line(struct irq_data *d)
|
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
|
|
|
|
int rc;
|
|
|
|
u8 buf;
|
|
|
|
|
|
|
|
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
|
|
|
|
|
|
|
|
rc = qpnpint_spmi_read(irq_d, QPNPINT_REG_RT_STS, &buf, 1);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("spmi failure on irq %d\n", d->irq);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (buf & irq_d->mask_shift) ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
2012-05-29 23:57:52 +00:00
|
|
|
static int qpnpint_irq_set_wake(struct irq_data *d, unsigned int on)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
static struct irq_chip qpnpint_chip = {
|
|
|
|
.name = "qpnp-int",
|
2013-11-26 22:28:27 +00:00
|
|
|
.irq_ack = qpnpint_irq_ack,
|
2012-01-05 22:32:59 +00:00
|
|
|
.irq_mask = qpnpint_irq_mask,
|
|
|
|
.irq_mask_ack = qpnpint_irq_mask_ack,
|
|
|
|
.irq_unmask = qpnpint_irq_unmask,
|
|
|
|
.irq_set_type = qpnpint_irq_set_type,
|
2013-02-28 22:21:14 +00:00
|
|
|
.irq_read_line = qpnpint_irq_read_line,
|
2012-05-29 23:57:52 +00:00
|
|
|
.irq_set_wake = qpnpint_irq_set_wake,
|
|
|
|
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
2012-01-05 22:32:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int qpnpint_init_irq_data(struct q_chip_data *chip_d,
|
|
|
|
struct q_irq_data *irq_d,
|
|
|
|
unsigned long hwirq)
|
|
|
|
{
|
|
|
|
struct qpnp_irq_spec q_spec;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
irq_d->mask_shift = 1 << (hwirq & 0x7);
|
|
|
|
rc = qpnpint_decode_hwirq(hwirq, &q_spec);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
irq_d->spmi_slave = q_spec.slave;
|
|
|
|
irq_d->spmi_offset = q_spec.per << 8;
|
|
|
|
irq_d->chip_d = chip_d;
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
irq_d->priv_d = QPNPINT_INVALID_DATA;
|
|
|
|
|
|
|
|
if (chip_d->cb && chip_d->cb->register_priv_data) {
|
|
|
|
rc = chip_d->cb->register_priv_data(chip_d->spmi_ctrl, &q_spec,
|
2012-01-05 22:32:59 +00:00
|
|
|
&irq_d->priv_d);
|
2012-06-01 20:33:51 +00:00
|
|
|
if (rc)
|
|
|
|
return rc;
|
2013-02-26 23:29:22 +00:00
|
|
|
}
|
2012-06-01 20:33:51 +00:00
|
|
|
|
|
|
|
irq_d->per_d->use_count++;
|
|
|
|
return 0;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct q_irq_data *qpnpint_alloc_irq_data(
|
|
|
|
struct q_chip_data *chip_d,
|
|
|
|
unsigned long hwirq)
|
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d;
|
|
|
|
struct q_perip_data *per_d;
|
2013-01-25 22:29:41 +00:00
|
|
|
int rc;
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
irq_d = kzalloc(sizeof(struct q_irq_data), GFP_KERNEL);
|
|
|
|
if (!irq_d)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Peripheral Tree is keyed from the slave + per_id. We're
|
|
|
|
* ignoring the irq bits here since this peripheral structure
|
|
|
|
* should be common for all irqs on the same peripheral.
|
|
|
|
*/
|
|
|
|
per_d = radix_tree_lookup(&chip_d->per_tree, (hwirq & ~0x7));
|
|
|
|
if (!per_d) {
|
|
|
|
per_d = kzalloc(sizeof(struct q_perip_data), GFP_KERNEL);
|
2012-06-01 20:33:51 +00:00
|
|
|
if (!per_d) {
|
2013-01-25 22:29:41 +00:00
|
|
|
rc = -ENOMEM;
|
|
|
|
goto alloc_fail;
|
2012-06-01 20:33:51 +00:00
|
|
|
}
|
2016-01-28 20:09:51 +00:00
|
|
|
spin_lock_init(&per_d->lock);
|
2013-01-25 22:29:41 +00:00
|
|
|
rc = radix_tree_preload(GFP_KERNEL);
|
|
|
|
if (rc)
|
|
|
|
goto alloc_fail;
|
|
|
|
rc = radix_tree_insert(&chip_d->per_tree,
|
2012-01-05 22:32:59 +00:00
|
|
|
(hwirq & ~0x7), per_d);
|
2013-01-25 22:29:41 +00:00
|
|
|
if (rc)
|
|
|
|
goto alloc_fail;
|
|
|
|
radix_tree_preload_end();
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
irq_d->per_d = per_d;
|
|
|
|
|
|
|
|
return irq_d;
|
2013-01-25 22:29:41 +00:00
|
|
|
|
|
|
|
alloc_fail:
|
|
|
|
kfree(per_d);
|
|
|
|
kfree(irq_d);
|
|
|
|
return ERR_PTR(rc);
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnpint_irq_domain_dt_translate(struct irq_domain *d,
|
|
|
|
struct device_node *controller,
|
|
|
|
const u32 *intspec, unsigned int intsize,
|
|
|
|
unsigned long *out_hwirq,
|
|
|
|
unsigned int *out_type)
|
|
|
|
{
|
|
|
|
struct qpnp_irq_spec addr;
|
|
|
|
int ret;
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
pr_debug("intspec[0] 0x%x intspec[1] 0x%x intspec[2] 0x%x\n",
|
|
|
|
intspec[0], intspec[1], intspec[2]);
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
if (d->of_node != controller)
|
|
|
|
return -EINVAL;
|
|
|
|
if (intsize != 3)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
addr.irq = intspec[2] & 0x7;
|
|
|
|
addr.per = intspec[1] & 0xFF;
|
|
|
|
addr.slave = intspec[0] & 0xF;
|
|
|
|
|
|
|
|
ret = qpnpint_encode_hwirq(&addr);
|
|
|
|
if (ret < 0) {
|
2012-06-01 20:33:51 +00:00
|
|
|
pr_err("invalid intspec\n");
|
2012-01-05 22:32:59 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
*out_hwirq = ret;
|
|
|
|
*out_type = IRQ_TYPE_NONE;
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
pr_debug("out_hwirq = %lu\n", *out_hwirq);
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qpnpint_free_irq_data(struct q_irq_data *irq_d)
|
|
|
|
{
|
|
|
|
if (irq_d->per_d->use_count == 1)
|
|
|
|
kfree(irq_d->per_d);
|
|
|
|
else
|
|
|
|
irq_d->per_d->use_count--;
|
|
|
|
kfree(irq_d);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnpint_irq_domain_map(struct irq_domain *d,
|
|
|
|
unsigned int virq, irq_hw_number_t hwirq)
|
|
|
|
{
|
|
|
|
struct q_chip_data *chip_d = d->host_data;
|
|
|
|
struct q_irq_data *irq_d;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
pr_debug("hwirq = %lu\n", hwirq);
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
if (hwirq < 0 || hwirq >= QPNPINT_NR_IRQS) {
|
2012-06-01 20:33:51 +00:00
|
|
|
pr_err("hwirq %lu out of bounds\n", hwirq);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
irq_d = qpnpint_alloc_irq_data(chip_d, hwirq);
|
|
|
|
if (IS_ERR(irq_d)) {
|
|
|
|
pr_err("failed to alloc irq data for hwirq %lu\n", hwirq);
|
|
|
|
return PTR_ERR(irq_d);
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
rc = qpnpint_init_irq_data(chip_d, irq_d, hwirq);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("failed to init irq data for hwirq %lu\n", hwirq);
|
|
|
|
goto map_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
irq_set_chip_and_handler(virq,
|
|
|
|
&qpnpint_chip,
|
|
|
|
handle_level_irq);
|
|
|
|
irq_set_chip_data(virq, irq_d);
|
|
|
|
#ifdef CONFIG_ARM
|
|
|
|
set_irq_flags(virq, IRQF_VALID);
|
|
|
|
#else
|
|
|
|
irq_set_noprobe(virq);
|
|
|
|
#endif
|
2012-01-05 22:32:59 +00:00
|
|
|
return 0;
|
2012-06-01 20:33:51 +00:00
|
|
|
|
|
|
|
map_err:
|
|
|
|
qpnpint_free_irq_data(irq_d);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void qpnpint_irq_domain_unmap(struct irq_domain *d, unsigned int virq)
|
|
|
|
{
|
|
|
|
struct q_irq_data *irq_d = irq_get_chip_data(virq);
|
|
|
|
|
|
|
|
if (WARN_ON(!irq_d))
|
|
|
|
return;
|
|
|
|
|
|
|
|
qpnpint_free_irq_data(irq_d);
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const struct irq_domain_ops qpnpint_irq_domain_ops = {
|
2012-06-01 20:33:51 +00:00
|
|
|
.map = qpnpint_irq_domain_map,
|
|
|
|
.unmap = qpnpint_irq_domain_unmap,
|
|
|
|
.xlate = qpnpint_irq_domain_dt_translate,
|
2012-01-05 22:32:59 +00:00
|
|
|
};
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
int qpnpint_register_controller(struct device_node *node,
|
|
|
|
struct spmi_controller *ctrl,
|
2012-01-05 22:32:59 +00:00
|
|
|
struct qpnp_local_int *li_cb)
|
|
|
|
{
|
2012-06-01 20:33:51 +00:00
|
|
|
struct q_chip_data *chip_d;
|
|
|
|
|
|
|
|
if (!node || !ctrl || ctrl->nr >= QPNPINT_MAX_BUSSES)
|
2012-01-05 22:32:59 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
list_for_each_entry(chip_d, &qpnpint_chips, list)
|
|
|
|
if (node == chip_d->domain->of_node) {
|
2013-02-26 23:29:22 +00:00
|
|
|
chip_d->cb = kmemdup(li_cb,
|
|
|
|
sizeof(*li_cb), GFP_ATOMIC);
|
|
|
|
if (!chip_d->cb)
|
|
|
|
return -ENOMEM;
|
2012-06-01 20:33:51 +00:00
|
|
|
chip_d->spmi_ctrl = ctrl;
|
|
|
|
chip_lookup[ctrl->nr] = chip_d;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENOENT;
|
2012-01-05 22:32:59 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(qpnpint_register_controller);
|
|
|
|
|
2013-02-26 23:29:22 +00:00
|
|
|
int qpnpint_unregister_controller(struct device_node *node)
|
|
|
|
{
|
|
|
|
struct q_chip_data *chip_d;
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
list_for_each_entry(chip_d, &qpnpint_chips, list)
|
|
|
|
if (node == chip_d->domain->of_node) {
|
|
|
|
kfree(chip_d->cb);
|
|
|
|
chip_d->cb = NULL;
|
|
|
|
if (chip_d->spmi_ctrl)
|
|
|
|
chip_lookup[chip_d->spmi_ctrl->nr] = NULL;
|
|
|
|
chip_d->spmi_ctrl = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(qpnpint_unregister_controller);
|
|
|
|
|
2017-04-18 01:29:57 +00:00
|
|
|
#ifdef CONFIG_SEC_PM
|
|
|
|
extern char last_resume_kernel_reason[];
|
|
|
|
extern int last_resume_kernel_reason_len;
|
|
|
|
#endif
|
|
|
|
|
2013-08-10 01:44:13 +00:00
|
|
|
static int __qpnpint_handle_irq(struct spmi_controller *spmi_ctrl,
|
|
|
|
struct qpnp_irq_spec *spec,
|
|
|
|
bool show)
|
2012-01-05 22:32:59 +00:00
|
|
|
{
|
|
|
|
struct irq_domain *domain;
|
|
|
|
unsigned long hwirq, busno;
|
|
|
|
int irq;
|
|
|
|
|
2013-07-10 17:52:36 +00:00
|
|
|
if (!spec || !spmi_ctrl)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
pr_debug("spec slave = %u per = %u irq = %u\n",
|
|
|
|
spec->slave, spec->per, spec->irq);
|
|
|
|
|
|
|
|
busno = spmi_ctrl->nr;
|
2013-07-10 17:52:36 +00:00
|
|
|
if (busno >= QPNPINT_MAX_BUSSES)
|
2012-01-05 22:32:59 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
hwirq = qpnpint_encode_hwirq(spec);
|
|
|
|
if (hwirq < 0) {
|
2012-06-01 20:33:51 +00:00
|
|
|
pr_err("invalid irq spec passed\n");
|
2012-01-05 22:32:59 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
domain = chip_lookup[busno]->domain;
|
|
|
|
irq = irq_find_mapping(domain, hwirq);
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2013-08-10 01:44:13 +00:00
|
|
|
if (show) {
|
|
|
|
struct irq_desc *desc;
|
|
|
|
const char *name = "null";
|
|
|
|
|
|
|
|
desc = irq_to_desc(irq);
|
|
|
|
if (desc == NULL)
|
|
|
|
name = "stray irq";
|
|
|
|
else if (desc->action && desc->action->name)
|
|
|
|
name = desc->action->name;
|
|
|
|
|
2017-04-18 01:29:57 +00:00
|
|
|
#ifdef CONFIG_SEC_PM
|
|
|
|
printk("Resume caused by IRQ %d(PIN %lu) %s [0x%01x, 0x%02x,0x%01x]\n",
|
|
|
|
irq, hwirq, name, spec->slave, spec->per, spec->irq);
|
|
|
|
last_resume_kernel_reason_len +=
|
|
|
|
sprintf(last_resume_kernel_reason + last_resume_kernel_reason_len,
|
|
|
|
"%d,%lu,%s|", irq, hwirq, name);
|
|
|
|
#else
|
2013-08-10 01:44:13 +00:00
|
|
|
pr_warn("%d triggered [0x%01x, 0x%02x,0x%01x] %s\n",
|
|
|
|
irq, spec->slave, spec->per, spec->irq, name);
|
2017-04-18 01:29:57 +00:00
|
|
|
#endif
|
2013-08-10 01:44:13 +00:00
|
|
|
} else {
|
|
|
|
generic_handle_irq(irq);
|
|
|
|
}
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2013-08-10 01:44:13 +00:00
|
|
|
|
|
|
|
int qpnpint_handle_irq(struct spmi_controller *spmi_ctrl,
|
|
|
|
struct qpnp_irq_spec *spec)
|
|
|
|
{
|
|
|
|
return __qpnpint_handle_irq(spmi_ctrl, spec, false);
|
|
|
|
}
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
EXPORT_SYMBOL(qpnpint_handle_irq);
|
|
|
|
|
2013-08-10 01:44:13 +00:00
|
|
|
int qpnpint_show_irq(struct spmi_controller *spmi_ctrl,
|
|
|
|
struct qpnp_irq_spec *spec)
|
|
|
|
{
|
|
|
|
return __qpnpint_handle_irq(spmi_ctrl, spec, true);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(qpnpint_show_irq);
|
|
|
|
|
2012-01-05 22:32:59 +00:00
|
|
|
int __init qpnpint_of_init(struct device_node *node, struct device_node *parent)
|
|
|
|
{
|
2012-06-01 20:33:51 +00:00
|
|
|
struct q_chip_data *chip_d;
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
chip_d = kzalloc(sizeof(struct q_chip_data), GFP_KERNEL);
|
|
|
|
if (!chip_d)
|
|
|
|
return -ENOMEM;
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
chip_d->domain = irq_domain_add_tree(node,
|
|
|
|
&qpnpint_irq_domain_ops, chip_d);
|
|
|
|
if (!chip_d->domain) {
|
|
|
|
pr_err("Unable to allocate irq_domain\n");
|
|
|
|
kfree(chip_d);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2012-01-05 22:32:59 +00:00
|
|
|
|
2012-06-01 20:33:51 +00:00
|
|
|
INIT_RADIX_TREE(&chip_d->per_tree, GFP_ATOMIC);
|
|
|
|
list_add(&chip_d->list, &qpnpint_chips);
|
2012-01-05 22:32:59 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(qpnpint_of_init);
|