blob: 83a9c1462d11a7793de2c2c74d4246e4ca7d9c7e [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
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
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/reboot.h>
#include <asm/hardware.h>
#include "qdrv_features.h"
#include "qdrv_debug.h"
#include "qdrv_mac.h"
#include "qdrv_uc_print.h"
#include "qdrv_soc.h"
#include "qdrv_control.h"
#include <qtn/registers.h>
#include <qtn/mproc_sync_base.h>
#include <qtn/txbf_mbox.h>
#ifdef TOPAZ_AMBER_IP
#include <qtn/amber.h>
#endif
/* Default irq handler for unclaimed interrupts */
static void no_irq_handler(void *arg1, void *arg2)
{
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return;
}
int qdrv_mac_set_handler(struct qdrv_mac *mac, int irq, struct int_handler *handler)
{
if(irq < 0 || irq >= HOST_INTERRUPTS)
{
return(-1);
}
/* Copy the handler structure */
mac->int_handlers[irq] = *handler;
return(0);
}
int qdrv_mac_set_host_dsp_handler(struct qdrv_mac *mac, int irq, struct int_handler *handler)
{
if(irq < 0 || irq >= HOST_INTERRUPTS)
{
return(-1);
}
/* Copy the handler structure */
mac->mac_host_dsp_int_handlers[irq] = *handler;
return(0);
}
int qdrv_mac_clear_handler(struct qdrv_mac *mac, int irq)
{
struct int_handler int_handler;
/* Install an empty handler so we can avoid checking */
/* if a handler is installed every time we take an interrupt */
int_handler.handler = no_irq_handler;
int_handler.arg1 = NULL;
int_handler.arg2 = NULL;
return(qdrv_mac_set_handler(mac, irq, &int_handler));
}
/* Called by the high priority input to kill off the host and
* prevent it locking up.
*/
static irqreturn_t qdrv_mac_die(int irq, void *dev_id)
{
struct qdrv_mac *mac = (struct qdrv_mac *)dev_id;
u_int32_t status = qtn_mproc_sync_irq_ack_nolock(
TOPAZ_SYS_CTL_M2L_HI_INT,
0xFFFF << RUBY_M2L_IPC_HI_IRQ(0) /* high IPC*/);
if (status & (1 << RUBY_M2L_IRQ_HI_DIE)) {
DBGPRINTF_E( "IRQ from MAC: dead\n");
qdrv_mac_die_action(mac);
}
if (status & (1 << RUBY_M2L_IRQ_HI_REBOOT)) {
DBGPRINTF_E( "IRQ from MAC: reboot\n");
/* MuC ask to restart system.
*/
#ifdef TOPAZ_AMPER_IP
amber_set_shutdown_code(AMBER_SD_CODE_EMERGENCY);
#endif
kernel_restart("MUC restart");
}
/* touch uc print buffer work queue to flush shared message buf */
uc_print_schedule_work();
return(IRQ_HANDLED);
}
static irqreturn_t __sram_text qdrv_mac_interrupt(int irq, void *dev_id)
{
int i;
struct qdrv_mac *mac = (struct qdrv_mac *)dev_id;
u_int32_t status = qtn_mproc_sync_irq_ack_nolock(
(u_int32_t)mac->mac_host_int_status,
0xFFFF /* low IPC*/);
/* Call the handlers */
for(i = 0; i < HOST_INTERRUPTS; i++)
{
if ((status & (1 << i)) && mac->int_handlers[i].handler)
{
(*mac->int_handlers[i].handler)(mac->int_handlers[i].arg1,
mac->int_handlers[i].arg2);
}
}
/* We are done - See you next time */
return(IRQ_HANDLED);
}
static irqreturn_t __sram_text qdrv_dsp_interrupt(int irq, void *dev_id)
{
int i;
struct qdrv_mac *mac = (struct qdrv_mac *)dev_id;
u_int32_t status = qtn_txbf_lhost_irq_ack(mac);
/* Call the handlers */
for (i = 0; i < HOST_DSP_INTERRUPTS; i++) {
if ((status & (1 << i)) && mac->mac_host_dsp_int_handlers[i].handler)
{
// printk("Interrupt for irq %d dev %p\n", i, dev_id);
(*mac->mac_host_dsp_int_handlers[i].handler)(
mac->mac_host_dsp_int_handlers[i].arg1,
mac->mac_host_dsp_int_handlers[i].arg2);
}
}
/* We are done - See you next time */
return(IRQ_HANDLED);
}
void __sram_text qdrv_mac_interrupt_muc(struct qdrv_mac *mac)
{
qtn_mproc_sync_irq_trigger((u_int32_t)mac->mac_uc_intgen, RUBY_L2M_IRQ_HLINK);
}
void __sram_text qdrv_mac_interrupt_muc_high(struct qdrv_mac *mac)
{
qtn_mproc_sync_irq_trigger((u_int32_t)mac->mac_uc_intgen, RUBY_L2M_IRQ_HIGH);
}
int qdrv_mac_init(struct qdrv_mac *mac, u8 *mac_addr, int unit, int irq, struct qdrv_mac_params *params)
{
int i;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
/* Reset the MAC data structure */
memset(mac, 0, sizeof(struct qdrv_mac));
/* Initialize */
mac->unit = unit;
mac->irq = irq;
mac->reg = (struct muc_ctrl_reg *)
IO_ADDRESS(MUC_BASE_ADDR + MUC_OFFSET_CTRL_REG);
mac->ruby_sysctrl = (struct ruby_sys_ctrl_reg *)IO_ADDRESS(RUBY_SYS_CTL_BASE_ADDR);
memcpy(mac->mac_addr, mac_addr, IEEE80211_ADDR_LEN);
memcpy(&mac->params, params, sizeof(mac->params));
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Copied MAC addr etc.\n");
/* Clear all the interrupt handlers */
for (i = 0; i < HOST_INTERRUPTS; i++) {
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Cleared handler %d\n", i);
qdrv_mac_clear_handler(mac, i);
}
/* Set interrupts to low (IRQ) priority */
mac->reg->mac0_host_int_pri = 0;
/* Set up pointer to int status register for generic int handler */
mac->mac_host_int_mask = &mac->ruby_sysctrl->m2l_int_mask;
mac->mac_host_int_status = &mac->ruby_sysctrl->m2l_int;
mac->mac_host_sem = &mac->ruby_sysctrl->l2m_sem;
mac->mac_uc_intgen = &mac->ruby_sysctrl->l2m_int;
/* Set up pointers for LHost to DSP communication */
mac->mac_host_dsp_int_mask = &mac->ruby_sysctrl->d2l_int_mask;
mac->mac_host_dsp_int_status = &mac->ruby_sysctrl->d2l_int;
mac->mac_host_dsp_sem = &mac->ruby_sysctrl->l2d_sem;
mac->mac_host_dsp_intgen = &mac->ruby_sysctrl->l2d_int;
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Requesting IRQs\n");
/* Register handler with linux for MuC interrupt line. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
if (request_irq(mac->irq, qdrv_mac_interrupt, 0, "QMAC0low", mac) != 0) {
#else
if (request_irq(mac->irq, qdrv_mac_interrupt, IRQF_SAMPLE_RANDOM, "QMAC0low", mac) != 0) {
#endif
DBGPRINTF_E("Can't get MAC0 irq %d\n", mac->irq);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(-ENODEV);
}
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Requested IRQ QMAC0low %p\n", mac);
if (request_irq(RUBY_IRQ_IPC_HI, qdrv_mac_die, 0, "QMACDie", mac) != 0) {
DBGPRINTF_E("Can't get MACDIE irq %d\n", RUBY_IRQ_IPC_HI);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(-ENODEV);
}
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Requested IRQ DIE %p\n", mac);
if (request_irq(QTN_TXBF_D2L_IRQ, qdrv_dsp_interrupt, 0, QTN_TXBF_D2L_IRQ_NAME, mac) != 0) {
DBGPRINTF_E("Can't get DSP irq %d\n", QTN_TXBF_D2L_IRQ);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(-ENODEV);
}
DBGPRINTF(DBG_LL_CRIT, QDRV_LF_QCTRL | QDRV_LF_TRACE, "Requested IRQ DSP %p\n", mac);
/* Enable the high priority interrupts */
for (i = 16; i < 32; i++) {
qdrv_mac_enable_irq(mac, i);
}
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(0);
}
int qdrv_mac_exit(struct qdrv_mac *mac)
{
int i;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
if (!mac->enabled) {
DBGPRINTF_E("MAC unit %d is not enabled\n", mac->unit);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(-1);
}
free_irq(mac->irq, mac);
free_irq(RUBY_IRQ_IPC_HI, mac);
free_irq(QTN_TXBF_D2L_IRQ, mac);
for (i = 0; i < HOST_INTERRUPTS; i++) {
qdrv_mac_disable_irq(mac, i);
qdrv_mac_clear_handler(mac, i);
}
mac->enabled = 0;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(0);
}