new file mode 100644
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include "reg/reg_access.h"
+#include "reg/reg_ipc.h"
+#include "bus/pci/ipc.h"
+#include "fw/msg_rx.h"
+#include "bus/pci/irq.h"
+#include "chip.h"
+#include "hw.h"
+#include "tx/tx.h"
+#include "dfs/radar.h"
+#include "recovery.h"
+#include "radio.h"
+#include "bus/pci/rx_pci.h"
+
+static void cl_irq_status_rxdesc(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /* Handle the reception of a Rx Descriptor */
+
+ /*
+ * Disable the RX interrupt until rxelement/skb handled
+ * this would avoid redundant context switch + redundant tasklet scheduling
+ */
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.rxdesc);
+
+ /* Acknowledge the interrupt BEFORE handling the packet */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.rxdesc);
+
+ /*
+ * If more than 50% of buffer are populated handle them in the interrupt,
+ * otherwise schedule a tasklet to handle the buffers.
+ */
+ if (cl_rx_process_in_irq(cl_hw))
+ cl_rx_pci_desc_handler(cl_hw);
+ else
+ tasklet_schedule(&ipc_env->rxdesc_tasklet);
+}
+
+static void cl_irq_status_txcfm(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /*
+ * Disable the TXCFM interrupt bit - will be enabled
+ * at the end of cl_tx_pci_single_cfm_tasklet()
+ */
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.txcfm);
+
+ /* Acknowledge the TXCFM interrupt */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.txcfm);
+
+ /* Schedule tasklet to handle the TXCFM */
+ tasklet_schedule(&ipc_env->tx_single_cfm_tasklet);
+}
+
+static void cl_irq_status_tbtt(struct cl_hw *cl_hw)
+{
+ unsigned long tbtt_diff_msec = jiffies_to_msecs(jiffies - cl_hw->last_tbtt_irq);
+
+ /* Acknowledge the interrupt BEFORE handling the request */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.tbtt);
+
+ cl_hw->last_tbtt_irq = jiffies;
+ cl_hw->tbtt_cnt++;
+
+ /*
+ * Send beacon only if radio is on, there is at least one AP interface
+ * up, we aren't in the middle of recovery, and user didn't disable them.
+ */
+ if (cl_radio_is_off(cl_hw) ||
+ cl_hw->vif_db.num_iface_bcn == 0 ||
+ cl_recovery_in_progress(cl_hw) ||
+ cl_hw->tx_disable_flags ||
+ !test_bit(CL_DEV_STARTED, &cl_hw->drv_flags) ||
+ test_bit(CL_DEV_FW_ERROR, &cl_hw->drv_flags))
+ return;
+
+ if (cl_hw->iface_conf == CL_IFCONF_MESH_ONLY ||
+ (cl_hw->mesh_tbtt_div > 1 &&
+ ((cl_hw->tbtt_cnt % cl_hw->mesh_tbtt_div) == 0))) {
+ tasklet_hi_schedule(&cl_hw->tx_mesh_bcn_task);
+ } else {
+ /*
+ * More than 2 times the beacon interval passed between beacons - WARNING
+ * More than 3 times the beacon interval passed between beacons - ERROR
+ */
+ if (tbtt_diff_msec > (cl_hw->conf->ha_beacon_int * 3))
+ cl_dbg_err(cl_hw, "last_tbtt_irq=%lu, curr_time=%lu, diff=%lu\n",
+ cl_hw->last_tbtt_irq, jiffies, tbtt_diff_msec);
+ else if (tbtt_diff_msec > (cl_hw->conf->ha_beacon_int * 2))
+ cl_dbg_warn(cl_hw, "last_tbtt_irq=%lu, curr_time=%lu, diff=%lu\n",
+ cl_hw->last_tbtt_irq, jiffies, tbtt_diff_msec);
+ }
+
+ cl_tx_bcns(cl_hw);
+}
+
+static void cl_irq_status_msg(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /* Acknowledge the interrupt BEFORE handling the request */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.msg);
+
+ /* Schedule tasklet to handle the messages */
+ tasklet_schedule(&ipc_env->msg_tasklet);
+}
+
+static u8 cl_radar_handler(struct cl_hw *cl_hw, ptrdiff_t hostid)
+{
+ struct cl_radar_elem *radar_elem = (struct cl_radar_elem *)hostid;
+ u8 ret = 0;
+ struct cl_radar_pulse_array *pulses;
+
+ /* Retrieve the radar pulses structure */
+ pulses = (struct cl_radar_pulse_array *)radar_elem->radarbuf_ptr;
+
+ /* Look for pulse count meaning that this hostbuf contains RADAR pulses */
+ if (pulses->cnt == 0) {
+ ret = -1;
+ goto radar_no_push;
+ }
+
+ /* Push pulse information to queue and schedule a tasklet to handle it */
+ cl_radar_push(cl_hw, radar_elem);
+
+ /* Reset the radar element and re-use it */
+ pulses->cnt = 0;
+
+ /* Make sure memory is written before push to HW */
+ wmb();
+
+ /* Push back the buffer to the firmware */
+ cl_ipc_radarbuf_push(cl_hw->ipc_env, (ptrdiff_t)radar_elem, radar_elem->dma_addr);
+
+radar_no_push:
+ return ret;
+}
+
+static void cl_irq_status_radar(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /*
+ * Firmware has triggered an IT saying that a radar event has been sent to upper layer.
+ * Then we first need to check the validity of the current msg buf, and the validity
+ * of the next buffers too, because it is likely that several buffers have been
+ * filled within the time needed for this irq handling
+ */
+
+ /* Disable the RADAR interrupt bit - will be enabled at the end of cl_radar_tasklet() */
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.radar);
+
+ /* Acknowledge the RADAR interrupt */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.radar);
+
+ /* Push all new radar pulses to queue */
+ while (cl_radar_handler(cl_hw,
+ ipc_env->radar_hostbuf_array[ipc_env->radar_host_idx].hostid) == 0)
+ ;
+
+ /* Schedule tasklet to handle the radar pulses */
+ cl_radar_tasklet_schedule(cl_hw);
+}
+
+static void cl_irq_status_dbg(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /* Disable the DBG interrupt bit - will be enabled at the end of cl_dbgfile_tasklet() */
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.dbg);
+
+ /* Acknowledge the DBG interrupt */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.dbg);
+
+ /* Schedule tasklet to handle the debug */
+ tasklet_schedule(&ipc_env->dbg_tasklet);
+}
+
+static void cl_irq_status_txdesc_ind(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /*
+ * Disable the TXDESC_IND interrupt bit -
+ * will be enabled at the end of cl_tx_pci_agg_cfm_tasklet()
+ */
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.txdesc_ind);
+
+ /* Acknowledge the TXDESC_IND interrupt */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.txdesc_ind);
+
+ tasklet_schedule(&ipc_env->tx_agg_cfm_tasklet);
+ tasklet_schedule(&cl_hw->tx_task);
+}
+
+static void cl_irq_status_sync(struct cl_hw *cl_hw, struct cl_ipc_host_env *ipc_env)
+{
+ /* Acknowledge the interrupt BEFORE handling the request */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.sync);
+
+ set_bit(CL_DEV_FW_SYNC, &cl_hw->drv_flags);
+ wake_up_interruptible(&cl_hw->fw_sync_wq);
+}
+
+void cl_irq_status(struct cl_hw *cl_hw, u32 status)
+{
+ /* Handle all IPC interrupts on the host side */
+ struct cl_ipc_host_env *ipc_env = cl_hw->ipc_env;
+
+ if (status & cl_hw->ipc_e2a_irq.rxdesc)
+ cl_irq_status_rxdesc(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.txcfm)
+ cl_irq_status_txcfm(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.tbtt)
+ cl_irq_status_tbtt(cl_hw);
+
+ if (status & cl_hw->ipc_e2a_irq.msg)
+ cl_irq_status_msg(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.radar)
+ cl_irq_status_radar(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.dbg)
+ cl_irq_status_dbg(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.txdesc_ind)
+ cl_irq_status_txdesc_ind(cl_hw, ipc_env);
+
+ if (status & cl_hw->ipc_e2a_irq.sync)
+ cl_irq_status_sync(cl_hw, ipc_env);
+}
+
+#ifdef CONFIG_CL_PCIE
+static void cl_irq_handler(struct cl_chip *chip)
+{
+ /* Interrupt handler */
+ u32 status, statuses = 0;
+ unsigned long now = jiffies;
+ struct cl_irq_stats *irq_stats = &chip->irq_stats;
+
+ while ((status = ipc_xmac_2_host_status_get(chip))) {
+ statuses |= status;
+
+ if (status & IPC_IRQ_L2H_ALL)
+ cl_irq_status(chip->cl_hw_tcv0, status);
+
+ if (status & IPC_IRQ_S2H_ALL)
+ cl_irq_status(chip->cl_hw_tcv1, status);
+ }
+
+ if (statuses & (IPC_IRQ_L2H_RXDESC | IPC_IRQ_S2H_RXDESC))
+ irq_stats->last_rx = now;
+
+ if (statuses & (IPC_IRQ_L2H_TXCFM | IPC_IRQ_S2H_TXCFM))
+ irq_stats->last_tx = now;
+
+ irq_stats->last_isr = now;
+ irq_stats->last_isr_statuses = statuses;
+}
+
+static irqreturn_t cl_irq_request_handler(int irq, void *dev_id)
+{
+ struct cl_chip *chip = (struct cl_chip *)dev_id;
+
+ cl_irq_handler(chip);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_SMP
+static void cl_irq_set_affinity(struct cl_chip *chip, struct pci_dev *pci_dev)
+{
+ s32 irq_smp_affinity = chip->conf->ce_irq_smp_affinity;
+
+ if (irq_smp_affinity != -1) {
+ struct irq_data *data = irq_get_irq_data(pci_dev->irq);
+
+ if (data) {
+ static struct cpumask mask;
+
+ cpumask_clear(&mask);
+ cpumask_set_cpu(irq_smp_affinity, &mask);
+
+ if (data->chip->irq_set_affinity) {
+ data->chip->irq_set_affinity(data, &mask, false);
+ pr_debug("irq=%d, affinity=%d\n", pci_dev->irq, irq_smp_affinity);
+ }
+ }
+ }
+}
+#endif
+
+int cl_irq_request(struct cl_chip *chip)
+{
+ /*
+ * Allocate host irq line.
+ * Enable PCIe device interrupts
+ */
+ int ret;
+ /* Request exclusive PCI interrupt in firmware test mode */
+ struct pci_dev *pci_dev = chip->pci_dev;
+
+ ret = request_irq(pci_dev->irq, cl_irq_request_handler, IRQF_SHARED, "cl", chip);
+
+ if (ret) {
+ pr_err("ERROR: could not assign interrupt %d, err=%d\n", pci_dev->irq, ret);
+ return ret;
+ }
+
+#ifdef CONFIG_SMP
+ cl_irq_set_affinity(chip, pci_dev);
+#endif
+
+ return ret;
+}
+
+void cl_irq_free(struct cl_chip *chip)
+{
+ struct pci_dev *pci_dev = chip->pci_dev;
+ /* Disable PCI device interrupt and release irq line */
+ free_irq(pci_dev->irq, chip);
+}
+#endif /* CONFIG_CL_PCIE */
+
+void cl_irq_enable(struct cl_hw *cl_hw, u32 value)
+{
+ /* Enable IPC interrupts */
+ ipc_xmac_2_host_enable_set_set(cl_hw->chip, value);
+}
+
+void cl_irq_disable(struct cl_hw *cl_hw, u32 value)
+{
+ /* Disable IPC interrupts */
+ ipc_xmac_2_host_enable_clear_set(cl_hw->chip, value);
+}