blob: 75711c0f72648c48e522855cbb79c12e4103dcb8 [file] [log] [blame]
/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* 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.
*/
#include <linux/interrupt.h>
#include "ipa_i.h"
#define INTERRUPT_WORKQUEUE_NAME "ipa_interrupt_wq"
#define DIS_SUSPEND_INTERRUPT_TIMEOUT 5
#define IPA_IRQ_NUM_MAX 32
struct ipa3_interrupt_info {
ipa_irq_handler_t handler;
enum ipa_irq_type interrupt;
void *private_data;
bool deferred_flag;
};
struct ipa3_interrupt_work_wrap {
struct work_struct interrupt_work;
ipa_irq_handler_t handler;
enum ipa_irq_type interrupt;
void *private_data;
void *interrupt_data;
};
static struct ipa3_interrupt_info ipa_interrupt_to_cb[IPA_IRQ_NUM_MAX];
static struct workqueue_struct *ipa_interrupt_wq;
static u32 ipa_ee;
static void ipa3_tx_suspend_interrupt_wa(void);
static void ipa3_enable_tx_suspend_wa(struct work_struct *work);
static DECLARE_DELAYED_WORK(dwork_en_suspend_int,
ipa3_enable_tx_suspend_wa);
static spinlock_t suspend_wa_lock;
static void ipa3_process_interrupts(bool isr_context);
static int ipa3_irq_mapping[IPA_IRQ_MAX] = {
[IPA_UC_TX_CMD_Q_NOT_FULL_IRQ] = -1,
[IPA_UC_TO_PROC_ACK_Q_NOT_FULL_IRQ] = -1,
[IPA_BAD_SNOC_ACCESS_IRQ] = 0,
[IPA_EOT_COAL_IRQ] = -1,
[IPA_UC_IRQ_0] = 2,
[IPA_UC_IRQ_1] = 3,
[IPA_UC_IRQ_2] = 4,
[IPA_UC_IRQ_3] = 5,
[IPA_UC_IN_Q_NOT_EMPTY_IRQ] = 6,
[IPA_UC_RX_CMD_Q_NOT_FULL_IRQ] = 7,
[IPA_PROC_TO_UC_ACK_Q_NOT_EMPTY_IRQ] = 8,
[IPA_RX_ERR_IRQ] = 9,
[IPA_DEAGGR_ERR_IRQ] = 10,
[IPA_TX_ERR_IRQ] = 11,
[IPA_STEP_MODE_IRQ] = 12,
[IPA_PROC_ERR_IRQ] = 13,
[IPA_TX_SUSPEND_IRQ] = 14,
[IPA_TX_HOLB_DROP_IRQ] = 15,
[IPA_BAM_GSI_IDLE_IRQ] = 16,
};
static void ipa3_interrupt_defer(struct work_struct *work);
static DECLARE_WORK(ipa3_interrupt_defer_work, ipa3_interrupt_defer);
static void ipa3_deferred_interrupt_work(struct work_struct *work)
{
struct ipa3_interrupt_work_wrap *work_data =
container_of(work,
struct ipa3_interrupt_work_wrap,
interrupt_work);
IPADBG("call handler from workq...\n");
work_data->handler(work_data->interrupt, work_data->private_data,
work_data->interrupt_data);
kfree(work_data->interrupt_data);
kfree(work_data);
}
static bool ipa3_is_valid_ep(u32 ep_suspend_data)
{
u32 bmsk = 1;
u32 i = 0;
for (i = 0; i < ipa3_ctx->ipa_num_pipes; i++) {
if ((ep_suspend_data & bmsk) && (ipa3_ctx->ep[i].valid))
return true;
bmsk = bmsk << 1;
}
return false;
}
static int ipa3_handle_interrupt(int irq_num, bool isr_context)
{
struct ipa3_interrupt_info interrupt_info;
struct ipa3_interrupt_work_wrap *work_data;
u32 suspend_data;
void *interrupt_data = NULL;
struct ipa_tx_suspend_irq_data *suspend_interrupt_data = NULL;
int res;
interrupt_info = ipa_interrupt_to_cb[irq_num];
if (interrupt_info.handler == NULL) {
IPAERR("A callback function wasn't set for interrupt num %d\n",
irq_num);
return -EINVAL;
}
switch (interrupt_info.interrupt) {
case IPA_TX_SUSPEND_IRQ:
IPADBG_LOW("processing TX_SUSPEND interrupt work-around\n");
ipa3_tx_suspend_interrupt_wa();
suspend_data = ipahal_read_reg_n(IPA_IRQ_SUSPEND_INFO_EE_n,
ipa_ee);
IPADBG_LOW("get interrupt %d\n", suspend_data);
if (ipa3_ctx->ipa_hw_type >= IPA_HW_v3_1) {
/* Clearing L2 interrupts status */
ipahal_write_reg_n(IPA_SUSPEND_IRQ_CLR_EE_n,
ipa_ee, suspend_data);
}
if (!ipa3_is_valid_ep(suspend_data))
return 0;
suspend_interrupt_data =
kzalloc(sizeof(*suspend_interrupt_data), GFP_ATOMIC);
if (!suspend_interrupt_data) {
IPAERR("failed allocating suspend_interrupt_data\n");
return -ENOMEM;
}
suspend_interrupt_data->endpoints = suspend_data;
interrupt_data = suspend_interrupt_data;
break;
case IPA_UC_IRQ_0:
if (ipa3_ctx->apply_rg10_wa) {
/*
* Early detect of uC crash. If RG10 workaround is
* enable uC crash will not be detected as before
* processing uC event the interrupt is cleared using
* uC register write which times out as it crashed
* already.
*/
if (ipa3_ctx->uc_ctx.uc_sram_mmio->eventOp ==
IPA_HW_2_CPU_EVENT_ERROR)
ipa3_ctx->uc_ctx.uc_failed = true;
}
break;
default:
break;
}
/* Force defer processing if in ISR context. */
if (interrupt_info.deferred_flag || isr_context) {
work_data = kzalloc(sizeof(struct ipa3_interrupt_work_wrap),
GFP_ATOMIC);
if (!work_data) {
IPAERR("failed allocating ipa3_interrupt_work_wrap\n");
res = -ENOMEM;
goto fail_alloc_work;
}
INIT_WORK(&work_data->interrupt_work,
ipa3_deferred_interrupt_work);
work_data->handler = interrupt_info.handler;
work_data->interrupt = interrupt_info.interrupt;
work_data->private_data = interrupt_info.private_data;
work_data->interrupt_data = interrupt_data;
queue_work(ipa_interrupt_wq, &work_data->interrupt_work);
} else {
interrupt_info.handler(interrupt_info.interrupt,
interrupt_info.private_data,
interrupt_data);
kfree(interrupt_data);
}
return 0;
fail_alloc_work:
kfree(interrupt_data);
return res;
}
static void ipa3_enable_tx_suspend_wa(struct work_struct *work)
{
u32 en;
u32 suspend_bmask;
int irq_num;
IPADBG_LOW("Enter\n");
irq_num = ipa3_irq_mapping[IPA_TX_SUSPEND_IRQ];
BUG_ON(irq_num == -1);
/* make sure ipa hw is clocked on*/
IPA_ACTIVE_CLIENTS_INC_SIMPLE();
en = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
suspend_bmask = 1 << irq_num;
/*enable TX_SUSPEND_IRQ*/
en |= suspend_bmask;
IPADBG("enable TX_SUSPEND_IRQ, IPA_IRQ_EN_EE reg, write val = %u\n"
, en);
ipa3_uc_rg10_write_reg(IPA_IRQ_EN_EE_n, ipa_ee, en);
ipa3_process_interrupts(false);
IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
IPADBG_LOW("Exit\n");
}
static void ipa3_tx_suspend_interrupt_wa(void)
{
u32 val;
u32 suspend_bmask;
int irq_num;
IPADBG_LOW("Enter\n");
irq_num = ipa3_irq_mapping[IPA_TX_SUSPEND_IRQ];
BUG_ON(irq_num == -1);
/*disable TX_SUSPEND_IRQ*/
val = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
suspend_bmask = 1 << irq_num;
val &= ~suspend_bmask;
IPADBG("Disabling TX_SUSPEND_IRQ, write val: %u to IPA_IRQ_EN_EE reg\n",
val);
ipa3_uc_rg10_write_reg(IPA_IRQ_EN_EE_n, ipa_ee, val);
IPADBG_LOW(" processing suspend interrupt work-around, delayed work\n");
queue_delayed_work(ipa_interrupt_wq, &dwork_en_suspend_int,
msecs_to_jiffies(DIS_SUSPEND_INTERRUPT_TIMEOUT));
IPADBG_LOW("Exit\n");
}
static inline bool is_uc_irq(int irq_num)
{
if (ipa_interrupt_to_cb[irq_num].interrupt >= IPA_UC_IRQ_0 &&
ipa_interrupt_to_cb[irq_num].interrupt <= IPA_UC_IRQ_3)
return true;
else
return false;
}
static void ipa3_process_interrupts(bool isr_context)
{
u32 reg;
u32 bmsk;
u32 i = 0;
u32 en;
unsigned long flags;
bool uc_irq;
IPADBG_LOW("Enter\n");
spin_lock_irqsave(&suspend_wa_lock, flags);
en = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
reg = ipahal_read_reg_n(IPA_IRQ_STTS_EE_n, ipa_ee);
while (en & reg) {
bmsk = 1;
for (i = 0; i < IPA_IRQ_NUM_MAX; i++) {
if (en & reg & bmsk) {
uc_irq = is_uc_irq(i);
/*
* Clear uC interrupt before processing to avoid
* clearing unhandled interrupts
*/
if (uc_irq)
ipa3_uc_rg10_write_reg(IPA_IRQ_CLR_EE_n,
ipa_ee, bmsk);
/*
* handle the interrupt with spin_lock
* unlocked to avoid calling client in atomic
* context. mutual exclusion still preserved
* as the read/clr is done with spin_lock
* locked.
*/
spin_unlock_irqrestore(&suspend_wa_lock, flags);
ipa3_handle_interrupt(i, isr_context);
spin_lock_irqsave(&suspend_wa_lock, flags);
/*
* Clear non uC interrupt after processing
* to avoid clearing interrupt data
*/
if (!uc_irq)
ipa3_uc_rg10_write_reg(IPA_IRQ_CLR_EE_n,
ipa_ee, bmsk);
}
bmsk = bmsk << 1;
}
/*
* In case uC failed interrupt cannot be cleared.
* Device will crash as part of handling uC event handler.
*/
if (ipa3_ctx->apply_rg10_wa && ipa3_ctx->uc_ctx.uc_failed)
break;
reg = ipahal_read_reg_n(IPA_IRQ_STTS_EE_n, ipa_ee);
/* since the suspend interrupt HW bug we must
* read again the EN register, otherwise the while is endless
*/
en = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
}
spin_unlock_irqrestore(&suspend_wa_lock, flags);
IPADBG_LOW("Exit\n");
}
static void ipa3_interrupt_defer(struct work_struct *work)
{
IPADBG("processing interrupts in wq\n");
IPA_ACTIVE_CLIENTS_INC_SIMPLE();
ipa3_process_interrupts(false);
IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
IPADBG("Done\n");
}
static irqreturn_t ipa3_isr(int irq, void *ctxt)
{
unsigned long flags;
IPADBG_LOW("Enter\n");
/* defer interrupt handling in case IPA is not clocked on */
if (ipa3_active_clients_trylock(&flags) == 0) {
IPADBG("defer interrupt processing\n");
queue_work(ipa3_ctx->power_mgmt_wq, &ipa3_interrupt_defer_work);
return IRQ_HANDLED;
}
if (ipa3_ctx->ipa3_active_clients.cnt == 0) {
IPADBG("defer interrupt processing\n");
queue_work(ipa3_ctx->power_mgmt_wq, &ipa3_interrupt_defer_work);
goto bail;
}
ipa3_process_interrupts(true);
IPADBG_LOW("Exit\n");
bail:
ipa3_active_clients_trylock_unlock(&flags);
return IRQ_HANDLED;
}
/**
* ipa3_add_interrupt_handler() - Adds handler to an interrupt type
* @interrupt: Interrupt type
* @handler: The handler to be added
* @deferred_flag: whether the handler processing should be deferred in
* a workqueue
* @private_data: the client's private data
*
* Adds handler to an interrupt type and enable the specific bit
* in IRQ_EN register, associated interrupt in IRQ_STTS register will be enabled
*/
int ipa3_add_interrupt_handler(enum ipa_irq_type interrupt,
ipa_irq_handler_t handler,
bool deferred_flag,
void *private_data)
{
u32 val;
u32 bmsk;
int irq_num;
int client_idx, ep_idx;
IPADBG("in ipa3_add_interrupt_handler interrupt_enum(%d)\n", interrupt);
if (interrupt < IPA_BAD_SNOC_ACCESS_IRQ ||
interrupt >= IPA_IRQ_MAX) {
IPAERR("invalid interrupt number %d\n", interrupt);
return -EINVAL;
}
irq_num = ipa3_irq_mapping[interrupt];
if (irq_num < 0 || irq_num >= IPA_IRQ_NUM_MAX) {
IPAERR("interrupt %d not supported\n", interrupt);
WARN_ON(1);
return -EFAULT;
}
IPADBG("ipa_interrupt_to_cb irq_num(%d)\n", irq_num);
ipa_interrupt_to_cb[irq_num].deferred_flag = deferred_flag;
ipa_interrupt_to_cb[irq_num].handler = handler;
ipa_interrupt_to_cb[irq_num].private_data = private_data;
ipa_interrupt_to_cb[irq_num].interrupt = interrupt;
val = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
IPADBG("read IPA_IRQ_EN_EE_n register. reg = %d\n", val);
bmsk = 1 << irq_num;
val |= bmsk;
ipa3_uc_rg10_write_reg(IPA_IRQ_EN_EE_n, ipa_ee, val);
IPADBG("wrote IPA_IRQ_EN_EE_n register. reg = %d\n", val);
/* register SUSPEND_IRQ_EN_EE_n_ADDR for L2 interrupt*/
if ((interrupt == IPA_TX_SUSPEND_IRQ) &&
(ipa3_ctx->ipa_hw_type >= IPA_HW_v3_1)) {
val = ~0;
for (client_idx = 0; client_idx < IPA_CLIENT_MAX; client_idx++)
if (IPA_CLIENT_IS_Q6_CONS(client_idx) ||
IPA_CLIENT_IS_Q6_PROD(client_idx)) {
ep_idx = ipa3_get_ep_mapping(client_idx);
IPADBG("modem ep_idx(%d) client_idx = %d\n",
ep_idx, client_idx);
if (ep_idx == -1)
IPADBG("Invalid IPA client\n");
else
val &= ~(1 << ep_idx);
}
ipahal_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_n, ipa_ee, val);
IPADBG("wrote IPA_SUSPEND_IRQ_EN_EE_n reg = %d\n", val);
}
return 0;
}
/**
* ipa3_remove_interrupt_handler() - Removes handler to an interrupt type
* @interrupt: Interrupt type
*
* Removes the handler and disable the specific bit in IRQ_EN register
*/
int ipa3_remove_interrupt_handler(enum ipa_irq_type interrupt)
{
u32 val;
u32 bmsk;
int irq_num;
if (interrupt < IPA_BAD_SNOC_ACCESS_IRQ ||
interrupt >= IPA_IRQ_MAX) {
IPAERR("invalid interrupt number %d\n", interrupt);
return -EINVAL;
}
irq_num = ipa3_irq_mapping[interrupt];
if (irq_num < 0 || irq_num >= IPA_IRQ_NUM_MAX) {
IPAERR("interrupt %d not supported\n", interrupt);
WARN_ON(1);
return -EFAULT;
}
kfree(ipa_interrupt_to_cb[irq_num].private_data);
ipa_interrupt_to_cb[irq_num].deferred_flag = false;
ipa_interrupt_to_cb[irq_num].handler = NULL;
ipa_interrupt_to_cb[irq_num].private_data = NULL;
ipa_interrupt_to_cb[irq_num].interrupt = -1;
/* clean SUSPEND_IRQ_EN_EE_n_ADDR for L2 interrupt */
if ((interrupt == IPA_TX_SUSPEND_IRQ) &&
(ipa3_ctx->ipa_hw_type >= IPA_HW_v3_1)) {
ipahal_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_n, ipa_ee, 0);
IPADBG("wrote IPA_SUSPEND_IRQ_EN_EE_n reg = %d\n", 0);
}
val = ipahal_read_reg_n(IPA_IRQ_EN_EE_n, ipa_ee);
bmsk = 1 << irq_num;
val &= ~bmsk;
ipa3_uc_rg10_write_reg(IPA_IRQ_EN_EE_n, ipa_ee, val);
return 0;
}
/**
* ipa3_interrupts_init() - Initialize the IPA interrupts framework
* @ipa_irq: The interrupt number to allocate
* @ee: Execution environment
* @ipa_dev: The basic device structure representing the IPA driver
*
* - Initialize the ipa_interrupt_to_cb array
* - Clear interrupts status
* - Register the ipa interrupt handler - ipa3_isr
* - Enable apps processor wakeup by IPA interrupts
*/
int ipa3_interrupts_init(u32 ipa_irq, u32 ee, struct device *ipa_dev)
{
int idx;
int res = 0;
ipa_ee = ee;
for (idx = 0; idx < IPA_IRQ_NUM_MAX; idx++) {
ipa_interrupt_to_cb[idx].deferred_flag = false;
ipa_interrupt_to_cb[idx].handler = NULL;
ipa_interrupt_to_cb[idx].private_data = NULL;
ipa_interrupt_to_cb[idx].interrupt = -1;
}
ipa_interrupt_wq = create_singlethread_workqueue(
INTERRUPT_WORKQUEUE_NAME);
if (!ipa_interrupt_wq) {
IPAERR("workqueue creation failed\n");
return -ENOMEM;
}
res = request_irq(ipa_irq, (irq_handler_t) ipa3_isr,
IRQF_TRIGGER_RISING, "ipa", ipa_dev);
if (res) {
IPAERR("fail to register IPA IRQ handler irq=%d\n", ipa_irq);
return -ENODEV;
}
IPADBG("IPA IRQ handler irq=%d registered\n", ipa_irq);
res = enable_irq_wake(ipa_irq);
if (res)
IPAERR("fail to enable IPA IRQ wakeup irq=%d res=%d\n",
ipa_irq, res);
else
IPADBG("IPA IRQ wakeup enabled irq=%d\n", ipa_irq);
spin_lock_init(&suspend_wa_lock);
return 0;
}
/**
* ipa3_suspend_active_aggr_wa() - Emulate suspend IRQ
* @clnt_hndl: suspended client handle, IRQ is emulated for this pipe
*
* Emulate suspend IRQ to unsuspend client which was suspended with an open
* aggregation frame in order to bypass HW bug of IRQ not generated when
* endpoint is suspended during an open aggregation.
*/
void ipa3_suspend_active_aggr_wa(u32 clnt_hdl)
{
struct ipa3_interrupt_info interrupt_info;
struct ipa3_interrupt_work_wrap *work_data;
struct ipa_tx_suspend_irq_data *suspend_interrupt_data;
int irq_num;
int aggr_active_bitmap = ipahal_read_reg(IPA_STATE_AGGR_ACTIVE);
if (aggr_active_bitmap & (1 << clnt_hdl)) {
/* force close aggregation */
ipahal_write_reg(IPA_AGGR_FORCE_CLOSE, (1 << clnt_hdl));
/* simulate suspend IRQ */
irq_num = ipa3_irq_mapping[IPA_TX_SUSPEND_IRQ];
interrupt_info = ipa_interrupt_to_cb[irq_num];
if (interrupt_info.handler == NULL) {
IPAERR("no CB function for IPA_TX_SUSPEND_IRQ!\n");
return;
}
suspend_interrupt_data = kzalloc(
sizeof(*suspend_interrupt_data),
GFP_ATOMIC);
if (!suspend_interrupt_data) {
IPAERR("failed allocating suspend_interrupt_data\n");
return;
}
suspend_interrupt_data->endpoints = 1 << clnt_hdl;
work_data = kzalloc(sizeof(struct ipa3_interrupt_work_wrap),
GFP_ATOMIC);
if (!work_data) {
IPAERR("failed allocating ipa3_interrupt_work_wrap\n");
goto fail_alloc_work;
}
INIT_WORK(&work_data->interrupt_work,
ipa3_deferred_interrupt_work);
work_data->handler = interrupt_info.handler;
work_data->interrupt = IPA_TX_SUSPEND_IRQ;
work_data->private_data = interrupt_info.private_data;
work_data->interrupt_data = (void *)suspend_interrupt_data;
queue_work(ipa_interrupt_wq, &work_data->interrupt_work);
return;
fail_alloc_work:
kfree(suspend_interrupt_data);
}
}