blob: 34fff2f04ec805ee58c0ce2dd932e138b3f989f8 [file] [log] [blame]
/**
* Copyright (c) 2012-2012 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.
**/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/version.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <asm/byteorder.h>
#include <linux/pci.h>
#include <linux/moduleparam.h>
#include <asm-generic/pci-dma-compat.h>
#include "qdpc_config.h"
#include "qdpc_debug.h"
#include "qdpc_init.h"
#include "qdpc_regs.h"
#include <qdpc_platform.h>
static int use_msi = 1;
module_param(use_msi, int, 0644);
MODULE_PARM_DESC(use_msi, "Set 0 to use Legacy interrupt");
static int qdpc_pcie_init_intr(struct vmac_priv *priv);
static int qdpc_pcie_init_mem(struct vmac_priv *priv);
static int g_msi = 1;
int32_t qdpc_pcie_init_intr_and_mem(struct vmac_priv *priv)
{
struct pci_dev *pdev = priv->pdev;
int result = 0;
/* Initialize interrupts */
if (( result = qdpc_pcie_init_intr(priv)) < 0) {
PRINT_ERROR("PCIe Interrupt Initialization failed \n");
return result;
}
/* Memory Initialization */
if (( result = qdpc_pcie_init_mem(priv)) < 0) {
PRINT_ERROR("PCIe Memory Initialization failed \n");
qdpc_free_interrupt(pdev);
}
return result;
}
static int32_t qdpc_pcie_init_intr(struct vmac_priv *priv)
{
struct pci_dev *pdev = priv->pdev;
priv->msi_enabled = 0; /* Set default to use Legacy INTx interrupt */
/* Check if the device has MSI capability */
if (use_msi) {
if (!pci_enable_msi(pdev)) {
PRINT_INFO("PCIe MSI Interrupt Enabled\n");
priv->msi_enabled = 1;
} else {
PRINT_ERROR("PCIe MSI Interrupt enabling failed. Fall back to Legacy IRQ\n");
}
}
if(!priv->msi_enabled) {
PRINT_INFO("PCIe Legacy Interrupt Enabled\n");
pci_intx(pdev, 1);
}
return 0;
}
static bool qdpc_bar_check(struct vmac_priv *priv, qdpc_bar_t *bar)
{
uint32_t offset = bar->b_offset;
size_t len = bar->b_len;
dma_addr_t busaddr = bar->b_busaddr;
uint8_t index = bar->b_index;
if (index > 5) {
printk("Invalid BAR index:%u. Must be between 0 and 5\n", index);
return 0;
}
if (!len) {
/* NOTE:
* Do not use an implicit length such as the BAR length
* if the map length is too large say > 16Mb this leaves
* the implementation vulnerable to
* Linux and the attack of the Silent "S" (one between the n and u)
*/
printk("Zero length BAR\n");
return 0;
}
if (busaddr) { /*initialized BAR */
unsigned long bar_start = pci_resource_start(priv->pdev , index);
unsigned long bar_end = pci_resource_end(priv->pdev , index);
if (!bar_start) {
printk("Invalid BAR address: 0x%p.\n", (void *)busaddr);
return 0;
}
if ((busaddr - offset) != bar_start) {
printk("Invalid BAR offset:0x%p. BAR starts at 0x%p\n",
(void *)(busaddr -offset), (void *)bar_start);
return 0;
}
/* Check the span of the BAR including the offset + length, bar_end points to the last byte of BAR */
if ((busaddr + len - 1) > bar_end) {
printk("Invalid BAR end address:0x%p. BAR ends at 0x%p\n",
(void *)(busaddr + len), (void *)bar_end);
return 0;
}
} else { /* Unitialized bar */
unsigned long bar_end = pci_resource_end(priv->pdev , index);
busaddr = pci_resource_start(priv->pdev , index);
if (!busaddr) {
printk("Invalid BAR address: 0x%p.\n", (void *)busaddr);
return 0;
}
/* Checks that offset area is within bar */
if ( (busaddr + offset) > bar_end) {
printk("Invalid BAR offset 0x%p, extends beyond end of BAR(0x%p).\n",
(void *)(busaddr + offset), (void *)bar_end);
return 0;
}
/* Checks that mapped area is within bar */
if ((busaddr + len + offset - 1) > bar_end) {
printk("Mapped area 0x%p, extends beyond end of BAR(0x%p).\n",
(void *)(busaddr + len + offset - 1), (void *)bar_end);
return 0;
}
}
return 1;
}
static qdpc_bar_t *qdpc_map_bar(struct vmac_priv *priv, qdpc_bar_t *bar,
uint8_t index, size_t len, uint32_t offset)
{
void *vaddr = NULL;
dma_addr_t busaddr = 0;
qdpc_bar_t temp_bar;
memset(&temp_bar, 0 ,sizeof(qdpc_bar_t));
temp_bar.b_len = len;
temp_bar.b_offset = offset;
temp_bar.b_index = index;
if (!qdpc_bar_check(priv, &temp_bar)) {
printk("Failed bar mapping sanity check in %s\n", __FUNCTION__);
return NULL;
}
/* Reserve PCIe memory region*/
busaddr = pci_resource_start(priv->pdev , index) + offset;
if (!request_mem_region(busaddr, len , QDPC_DEV_NAME)) {
printk("Failed to reserve %u bytes of PCIe memory "
"region starting at 0x%p\n", (uint32_t)len, (void *)busaddr);
return NULL;
}
qdpc_update_hw_bar(priv->pdev, index);
vaddr = ioremap_nocache(busaddr, len);
if (!vaddr) {
printk("Failed to map %u bytes at BAR%u at bus address 0x%p.\n",
(uint32_t)len, index, (void *)busaddr);
release_mem_region(busaddr, len);
return NULL;
}
memset(&temp_bar, 0 ,sizeof(qdpc_bar_t));
bar->b_vaddr = vaddr;
bar->b_busaddr = busaddr;
bar->b_len = len;
bar->b_index = index;
bar->b_offset = offset;
printk("BAR:%u vaddr=0x%p busaddr=%p offset=%u len=%u\n",
bar->b_index, bar->b_vaddr, (void *)bar->b_busaddr,
bar->b_offset, (uint32_t)bar->b_len);
return bar;
}
static bool qdpc_unmap_bar(struct vmac_priv *priv, qdpc_bar_t *bar)
{
if (!qdpc_bar_check(priv, bar)) {
PRINT_ERROR("Failed bar mapping sanity check in %s\n", __FUNCTION__);
return 0;
}
iounmap(bar->b_vaddr);
release_mem_region(bar->b_busaddr - bar->b_offset, bar->b_len);
memset(bar, 0 , sizeof(qdpc_bar_t));
return 1;
}
static void qdpc_map_epmem(struct vmac_priv *priv)
{
printk("%s() Mapping epmem\n", __FUNCTION__);
qdpc_map_bar(priv, &priv->epmem_bar, QDPC_SHMEM_BAR,
pci_resource_len(priv->pdev, QDPC_SHMEM_BAR) , 0);
priv->bda =(qdpc_pcie_bda_t *)QDPC_BAR_VADDR(priv->epmem_bar, 0);
priv->bda->bda_rc_msi_enabled = g_msi;
}
static void qdpc_map_sysctl_regs(struct vmac_priv *priv)
{
printk("%s() Mapping sysctl\n", __FUNCTION__);
qdpc_map_bar(priv, &priv->sysctl_bar, QDPC_SYSCTL_BAR, pci_resource_len(priv->pdev, QDPC_SYSCTL_BAR) , 0);
}
static void qdpc_map_dma_regs(struct vmac_priv *priv)
{
printk("%s() Mapping dma registers\n", __FUNCTION__);
qdpc_map_bar(priv, &priv->dmareg_bar, QDPC_DMA_BAR, pci_resource_len(priv->pdev, QDPC_DMA_BAR), 0);
}
static void qdpc_unmap_epmem(struct vmac_priv *priv)
{
printk("%s() Unmapping sysctl\n", __FUNCTION__);
priv->bda = NULL;
qdpc_unmap_bar(priv, &priv->epmem_bar);
}
static void qdpc_unmap_sysctl_regs(struct vmac_priv *priv)
{
printk("%s() Unmapping sysctl\n", __FUNCTION__);
qdpc_unmap_bar(priv, &priv->sysctl_bar);
}
static void qdpc_unmap_dma_regs(struct vmac_priv *priv)
{
printk("%s() Unmapping dma regs\n", __FUNCTION__);
qdpc_unmap_bar(priv, &priv->dmareg_bar);
}
int32_t qdpc_set_dma_mask(struct vmac_priv *priv) {
int result = 0;
uint64_t dma_mask = qdpc_pci_readl(&priv->bda->bda_dma_mask);
printk("Requested DMA mask:0x%llx\n", dma_mask);
result = pci_set_dma_mask(priv->pdev, dma_mask);
if (!result) {
result = pci_set_consistent_dma_mask(priv->pdev, dma_mask);
if (result) {
printk(" pci_set_consistent_dma_mask() error %d. Mask:0x%llx\n", result, dma_mask);
return 1;
}
} else {
printk(" pci_set_dma_mask() error %d. Mask:0x%llx\n", result, dma_mask);
return 1;
}
return 0;
}
static int32_t qdpc_pcie_init_mem(struct vmac_priv *priv)
{
int ret = 0;
/* Map SynControl registers and Host to Endpoint interrupt registers to BAR-2 */
qdpc_map_sysctl_regs(priv);
qdpc_map_epmem(priv);
qdpc_map_dma_regs(priv);
return ret;
}
int qdpc_unmap_iomem(struct vmac_priv *priv)
{
qdpc_unmap_dma_regs(priv);
qdpc_unmap_epmem(priv);
qdpc_unmap_sysctl_regs(priv);
return SUCCESS;
}
void qdpc_free_interrupt(struct pci_dev *pdev)
{
struct net_device *ndev = pci_get_drvdata(pdev);
struct vmac_priv *priv;
if (ndev == NULL)
return;
priv = netdev_priv(ndev);
if(priv->msi_enabled)
pci_disable_msi(pdev);
else
pci_intx(pdev, 0);
}
void qdpc_pcie_free_mem(struct pci_dev *pdev)
{
return;
}
void *qdpc_map_pciemem(unsigned long busaddr, size_t len)
{
/* Reserve PCIe memory region*/
if (!request_mem_region(busaddr, len, QDPC_DEV_NAME)) {
PRINT_ERROR(KERN_ERR "Failed to reserve %u bytes of "
"PCIe memory region starting at 0x%lx\n", (uint32_t)len, busaddr);
return NULL;
}
return ioremap_nocache(busaddr, len);
}
void qdpc_unmap_pciemem(unsigned long busaddr, void *vaddr, size_t len)
{
if (!vaddr || !busaddr)
return;
iounmap(vaddr);
release_mem_region(busaddr, len);
}
void qdpc_deassert_intx(struct vmac_priv *priv)
{
void *basereg = QDPC_BAR_VADDR(priv->sysctl_bar, TOPAZ_PCIE_CFG0_OFFSET);
qdpc_pcie_posted_write(priv->ep_pciecfg0_val & ~TOPAZ_ASSERT_INTX, basereg);
}