| /** |
| * 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/pci.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/kthread.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <net/sock.h> |
| #include <linux/netlink.h> |
| #include <linux/firmware.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/delay.h> |
| |
| #include "qdpc_config.h" |
| #include "qdpc_debug.h" |
| #include "qdpc_init.h" |
| #include "qdpc_regs.h" |
| #include "qdpc_platform.h" |
| #include "topaz_vnet.h" |
| #define QDPC_TOPAZ_IMG "topaz-linux.lzma.img" |
| #define QDPC_TOPAZ_UBOOT "u-boot.bin" |
| #define MAX_IMG_NUM 2 |
| |
| #define EP_BOOT_FROM_FLASH 0 |
| |
| #ifndef MEMORY_START_ADDRESS |
| #define MEMORY_START_ADDRESS virt_to_bus((void *)PAGE_OFFSET) |
| #endif |
| |
| static unsigned int tlp_mps = 256; |
| module_param(tlp_mps, uint, 0644); |
| MODULE_PARM_DESC(tlp_mps, "Default PCIe Max_Payload_Size"); |
| |
| /* |
| * Define EP state during host suspend |
| * 0 = EP does not power off |
| * 1 = EP power off |
| */ |
| #define EP_SUSPEND_MODE_RUNNING 0 |
| #define EP_SUSPEND_MODE_PWR_OFF 1 |
| static unsigned int suspend_mode = EP_SUSPEND_MODE_RUNNING; |
| module_param(suspend_mode, uint, 0644); |
| MODULE_PARM_DESC(suspend_mode, "Default suspend behavior"); |
| static unsigned int suspend_flag = 0; |
| |
| /* Quantenna PCIE vendor and device identifiers */ |
| static struct pci_device_id qdpc_pcie_ids[] = { |
| {PCI_DEVICE(QDPC_VENDOR_ID, QDPC_DEVICE_ID),}, |
| {0,} |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, qdpc_pcie_ids); |
| |
| static int qdpc_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id); |
| static void qdpc_pcie_remove(struct pci_dev *pdev); |
| static int qdpc_boot_thread(void *data); |
| static void qdpc_nl_recv_msg(struct sk_buff *skb); |
| int qdpc_init_netdev(struct net_device **net_dev, struct pci_dev *pdev); |
| pci_ers_result_t qdpc_pcie_slot_reset(struct pci_dev *dev); |
| static void qdpc_pcie_shutdown(struct pci_dev *pdev); |
| |
| |
| char qdpc_pcie_driver_name[] = "qdpc_host"; |
| |
| static struct pci_error_handlers qdpc_err_hdl = { |
| .slot_reset = qdpc_pcie_slot_reset, |
| }; |
| |
| static struct pci_driver qdpc_pcie_driver = { |
| .name = qdpc_pcie_driver_name, |
| .id_table = qdpc_pcie_ids, |
| .probe = qdpc_pcie_probe, |
| .remove = qdpc_pcie_remove, |
| #ifdef CONFIG_QTN_PM |
| .suspend = qdpc_pcie_suspend, |
| .resume = qdpc_pcie_resume, |
| #endif |
| .err_handler = &qdpc_err_hdl, |
| .shutdown = qdpc_pcie_shutdown, |
| }; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0) |
| struct netlink_kernel_cfg qdpc_netlink_cfg = { |
| .groups = 0, |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0) |
| .flags = 0, |
| #endif |
| .input = qdpc_nl_recv_msg, |
| .cb_mutex = NULL, |
| .bind = NULL, |
| }; |
| #endif |
| |
| struct sock *qdpc_nl_sk = NULL; |
| int qdpc_clntPid = 0; |
| |
| unsigned int (*qdpc_pci_readl)(void *addr) = qdpc_readl; |
| void (*qdpc_pci_writel)(unsigned int val, void *addr) = qdpc_writel; |
| |
| static int qdpc_bootpoll(struct vmac_priv *p, uint32_t state) |
| { |
| while (!kthread_should_stop() && (qdpc_isbootstate(p,state) == 0)) { |
| if (qdpc_booterror(p)) |
| return -1; |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(QDPC_SCHED_TIMEOUT); |
| } |
| return 0; |
| } |
| static void booterror(qdpc_pcie_bda_t *bda) |
| { |
| if (PCIE_BDA_TARGET_FWLOAD_ERR & qdpc_pci_readl(&bda->bda_flags)) |
| printk("EP boot from download firmware failed!\n"); |
| else if (PCIE_BDA_TARGET_FBOOT_ERR & qdpc_pci_readl(&bda->bda_flags)) |
| printk("EP boot from flash failed! Please check if there is usable image in Target flash.\n"); |
| else |
| printk("EP boot get in error, dba flag: 0x%x\n", qdpc_pci_readl(&bda->bda_flags)); |
| } |
| |
| static void qdpc_pci_endian_detect(struct vmac_priv *priv) |
| { |
| __iomem qdpc_pcie_bda_t *bda = priv->bda; |
| volatile uint32_t pci_endian; |
| |
| writel(QDPC_PCI_ENDIAN_DETECT_DATA, &bda->bda_pci_endian); |
| mmiowb(); |
| writel(QDPC_PCI_ENDIAN_VALID_STATUS, &bda->bda_pci_pre_status); |
| |
| while (readl(&bda->bda_pci_post_status) != QDPC_PCI_ENDIAN_VALID_STATUS) { |
| if (kthread_should_stop()) |
| break; |
| |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(QDPC_SCHED_TIMEOUT); |
| } |
| |
| pci_endian = readl(&bda->bda_pci_endian); |
| if (pci_endian == QDPC_PCI_LITTLE_ENDIAN) { |
| qdpc_pci_readl = qdpc_readl; |
| qdpc_pci_writel = qdpc_writel; |
| printk("PCI memory is little endian\n"); |
| } else if (pci_endian == QDPC_PCI_BIG_ENDIAN) { |
| qdpc_pci_readl = qdpc_le32_readl; |
| qdpc_pci_writel = qdpc_le32_writel; |
| printk("PCI memory is big endian\n"); |
| } else { |
| qdpc_pci_readl = qdpc_readl; |
| qdpc_pci_writel = qdpc_writel; |
| printk("PCI memory endian value:%08x is invalid - using little endian\n", pci_endian); |
| } |
| |
| /* Clear endian flags */ |
| writel(0, &bda->bda_pci_pre_status); |
| writel(0, &bda->bda_pci_post_status); |
| writel(0, &bda->bda_pci_endian); |
| } |
| |
| static void qdpc_pci_dma_offset_reset(struct vmac_priv *priv) |
| { |
| __iomem qdpc_pcie_bda_t *bda = priv->bda; |
| uint32_t dma_offset; |
| |
| /* Get EP Mapping address */ |
| dma_offset = readl(&bda->bda_dma_offset); |
| if ((dma_offset & PCIE_DMA_OFFSET_ERROR_MASK) != PCIE_DMA_OFFSET_ERROR) { |
| printk("DMA offset : 0x%08x, no need to reset the value.\n", dma_offset); |
| return; |
| } |
| dma_offset &= ~PCIE_DMA_OFFSET_ERROR_MASK; |
| |
| printk("EP map start addr : 0x%08x, Host memory start : 0x%08x\n", |
| dma_offset, (unsigned int)MEMORY_START_ADDRESS); |
| |
| /* Reset DMA offset in bda */ |
| dma_offset -= MEMORY_START_ADDRESS; |
| writel(dma_offset, &bda->bda_dma_offset); |
| } |
| |
| static int qdpc_firmware_load(struct pci_dev *pdev, struct vmac_priv *priv, const char *name) |
| { |
| #define DMABLOCKSIZE (1 * 1024 * 1024) |
| #define NBLOCKS(size) ((size)/(DMABLOCKSIZE) + (((size)%(DMABLOCKSIZE) > 0) ? 1 : 0)) |
| |
| int result = SUCCESS; |
| const struct firmware *fw; |
| __iomem qdpc_pcie_bda_t *bda = priv->bda; |
| |
| /* Request compressed firmware from user space */ |
| if ((result = request_firmware(&fw, name, &pdev->dev)) == -ENOENT) { |
| /* |
| * No firmware found in the firmware directory, skip firmware downloading process |
| * boot from flash directly on target |
| */ |
| printk( "no firmware found skip fw downloading\n"); |
| qdpc_pcie_posted_write((PCIE_BDA_HOST_NOFW_ERR | |
| qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags); |
| return FAILURE; |
| } else if (result == SUCCESS) { |
| uint32_t nblocks = NBLOCKS(fw->size); |
| uint32_t remaining = fw->size; |
| uint32_t count; |
| uint32_t dma_offset = qdpc_pci_readl(&bda->bda_dma_offset); |
| void *data =(void *) __get_free_pages(GFP_KERNEL | GFP_DMA, |
| get_order(DMABLOCKSIZE)); |
| const uint8_t *curdata = fw->data; |
| dma_addr_t handle = 0; |
| |
| if (!data) { |
| printk(KERN_ERR "Allocation failed for memory size[%u] Download firmware failed!\n", DMABLOCKSIZE); |
| release_firmware(fw); |
| qdpc_pcie_posted_write((PCIE_BDA_HOST_MEMALLOC_ERR | |
| qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags); |
| return FAILURE; |
| } |
| |
| handle = pci_map_single(priv->pdev, data ,DMABLOCKSIZE, PCI_DMA_TODEVICE); |
| if (!handle) { |
| printk("Pci map for memory data block 0x%p error, Download firmware failed!\n", data); |
| free_pages((unsigned long)data, get_order(DMABLOCKSIZE)); |
| release_firmware(fw); |
| qdpc_pcie_posted_write((PCIE_BDA_HOST_MEMMAP_ERR | |
| qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags); |
| return FAILURE; |
| } |
| |
| qdpc_setbootstate(priv, QDPC_BDA_FW_HOST_LOAD); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_EP_RDY); |
| |
| /* Start loading firmware */ |
| for (count = 0 ; count < nblocks; count++) |
| { |
| uint32_t size = (remaining > DMABLOCKSIZE) ? DMABLOCKSIZE : remaining; |
| |
| memcpy(data, curdata, size); |
| /* flush dcache */ |
| pci_dma_sync_single_for_device(priv->pdev, handle ,size, PCI_DMA_TODEVICE); |
| |
| qdpc_pcie_posted_write(handle + dma_offset, &bda->bda_img); |
| qdpc_pcie_posted_write(size, &bda->bda_img_size); |
| printk("FW Data[%u]: VA:0x%p PA:0x%p Sz=%u..\n", count, (void *)curdata, (void *)handle, size); |
| |
| qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_RDY); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_BLOCK_DONE); |
| |
| remaining = (remaining < size) ? remaining : (remaining - size); |
| curdata += size; |
| printk("done!\n"); |
| } |
| |
| pci_unmap_single(priv->pdev,handle, DMABLOCKSIZE, PCI_DMA_TODEVICE); |
| /* Mark end of block */ |
| qdpc_pcie_posted_write(0, &bda->bda_img); |
| qdpc_pcie_posted_write(0, &bda->bda_img_size); |
| qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_RDY); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_BLOCK_DONE); |
| |
| qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_END); |
| |
| PRINT_INFO("Image. Sz:%u State:0x%x\n", (uint32_t)fw->size, qdpc_pci_readl(&bda->bda_bootstate)); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_LOAD_DONE); |
| |
| free_pages((unsigned long)data, get_order(DMABLOCKSIZE)); |
| release_firmware(fw); |
| PRINT_INFO("Image downloaded....!\n"); |
| } else { |
| PRINT_ERROR("Failed to load firmware:%d\n", result); |
| return result; |
| } |
| return result; |
| } |
| |
| static void qdpc_pcie_dev_init(struct vmac_priv *priv, struct pci_dev *pdev, struct net_device *ndev) |
| { |
| SET_NETDEV_DEV(ndev, &pdev->dev); |
| |
| priv->pdev = pdev; |
| priv->ndev = ndev; |
| pci_set_drvdata(pdev, ndev); |
| } |
| |
| static void qdpc_tune_pcie_mps(struct pci_dev *pdev, int pos) |
| { |
| struct pci_dev *parent = NULL; |
| int ppos = 0; |
| uint32_t dev_cap, pcap; |
| uint16_t dev_ctl, pctl; |
| unsigned int mps = tlp_mps; |
| #define BIT_TO_MPS(m) (1 << ((m) + 7)) |
| |
| if (pdev->bus && pdev->bus->self) { |
| parent = pdev->bus->self; |
| if (likely(parent)) { |
| ppos = pci_find_capability(parent, PCI_CAP_ID_EXP); |
| if (ppos) { |
| pci_read_config_dword(parent, ppos + PCI_EXP_DEVCAP, &pcap); |
| pci_read_config_dword(pdev, pos + PCI_EXP_DEVCAP, &dev_cap); |
| printk(KERN_INFO "parent cap:%u, dev cap:%u\n",\ |
| BIT_TO_MPS(pcap & PCI_EXP_DEVCAP_PAYLOAD), BIT_TO_MPS(dev_cap & PCI_EXP_DEVCAP_PAYLOAD)); |
| mps = min(BIT_TO_MPS(dev_cap & PCI_EXP_DEVCAP_PAYLOAD), BIT_TO_MPS(pcap & PCI_EXP_DEVCAP_PAYLOAD)); |
| } |
| } |
| } |
| mps = 128; |
| printk(KERN_INFO"Setting MPS to %u\n", mps); |
| |
| /* |
| * Set Max_Payload_Size |
| * Max_Payload_Size_in_effect = 1 << ( ( (dev_ctl >> 5) & 0x07) + 7); |
| */ |
| mps = (((mps >> 7) - 1) << 5); |
| pci_read_config_word(pdev, pos + PCI_EXP_DEVCTL, &dev_ctl); |
| dev_ctl = ((dev_ctl & ~PCI_EXP_DEVCTL_PAYLOAD) | mps); |
| pci_write_config_word(pdev, pos + PCI_EXP_DEVCTL, dev_ctl); |
| |
| if (parent && ppos) { |
| pci_read_config_word(parent, pos + PCI_EXP_DEVCTL, &pctl); |
| pctl = ((pctl & ~PCI_EXP_DEVCTL_PAYLOAD) | mps); |
| pci_write_config_word(parent, pos + PCI_EXP_DEVCTL, pctl); |
| } |
| } |
| |
| static struct net_device *g_ndev = NULL; |
| static int qdpc_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
| { |
| struct vmac_priv *priv = NULL; |
| struct net_device *ndev = NULL; |
| int result = SUCCESS; |
| int pos; |
| |
| /* When system boots up, the state of pdev is not saved on the entry of this function. |
| * Data structure pci_dev will keep in use when module is removed and re-installed. |
| * So we can call pci_restore_sate() to recover its previous configuration space. |
| */ |
| if (pdev->state_saved == true) { |
| printk("Recovery: restore saved state\n"); |
| pci_restore_state(pdev); |
| } |
| |
| /* Save "fresh poweron" state including BAR address, etc. So the state can be |
| * used for recovery next time. |
| */ |
| pci_save_state(pdev); |
| |
| /* Allocate device structure */ |
| if (!(ndev = vmac_alloc_ndev())) |
| return -ENOMEM; |
| |
| g_ndev = ndev; |
| priv = netdev_priv(ndev); |
| qdpc_pcie_dev_init(priv, pdev, ndev); |
| |
| /* allocate netlink data buffer */ |
| priv->nl_buf = kmalloc(VMAC_NL_BUF_SIZE, GFP_KERNEL); |
| if (!priv->nl_buf) { |
| result = -ENOMEM; |
| goto out; |
| } |
| |
| /* Check if the device has PCI express capability */ |
| pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); |
| if (!pos) { |
| PRINT_ERROR(KERN_ERR "The device %x does not have PCI Express capability\n", |
| pdev->device); |
| result = -ENOSYS; |
| goto out; |
| } else { |
| PRINT_DBG(KERN_INFO "The device %x has PCI Express capability\n", pdev->device); |
| } |
| |
| #ifdef CPTCFG_QUANTENNA_PCIE_HOST_MPS_FIX |
| qdpc_tune_pcie_mps(pdev, pos); |
| #endif |
| |
| /* Wake up the device if it is in suspended state and allocate IO, |
| * memory regions and IRQ if not |
| */ |
| if (pci_enable_device(pdev)) { |
| PRINT_ERROR(KERN_ERR "Failed to initialize PCI device with device ID %x\n", |
| pdev->device); |
| |
| result = -EIO; |
| goto out; |
| } else { |
| PRINT_DBG(KERN_INFO "Initialized PCI device with device ID %x\n", pdev->device); |
| } |
| |
| /* |
| * Check if the PCI device can support DMA addressing properly. |
| * The mask gives the bits that the device can address |
| */ |
| pci_set_master(pdev); |
| |
| /* Initialize PCIE layer */ |
| if (( result = qdpc_pcie_init_intr_and_mem(priv)) < 0) { |
| PRINT_DBG("Interrupt & Memory Initialization failed \n"); |
| goto release_memory; |
| } |
| |
| if (!!(result = vmac_net_init(pdev))) { |
| PRINT_DBG("Vmac netdev init fail\n"); |
| goto free_mem_interrupt; |
| } |
| |
| /* Create and start the thread to initiate the INIT Handshake*/ |
| priv->init_thread = kthread_run(qdpc_boot_thread, priv, "qdpc_init_thread"); |
| if (priv->init_thread == NULL) { |
| PRINT_ERROR("Init thread creation failed \n"); |
| goto free_mem_interrupt; |
| } |
| |
| |
| /* Create netlink & register with kernel */ |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0) |
| priv->nl_socket = netlink_kernel_create(&init_net, |
| QDPC_NETLINK_RPC_PCI_CLNT, &qdpc_netlink_cfg); |
| #elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0) |
| priv->nl_socket = netlink_kernel_create(&init_net, |
| QDPC_NETLINK_RPC_PCI_CLNT, THIS_MODULE, &qdpc_netlink_cfg); |
| #else |
| priv->nl_socket = netlink_kernel_create(&init_net, |
| QDPC_NETLINK_RPC_PCI_CLNT, 0, qdpc_nl_recv_msg, |
| NULL, THIS_MODULE); |
| #endif |
| if (priv->nl_socket) { |
| return SUCCESS; |
| } |
| |
| PRINT_ERROR(KERN_ALERT "Error creating netlink socket.\n"); |
| result = FAILURE; |
| |
| free_mem_interrupt: |
| qdpc_pcie_free_mem(pdev); |
| qdpc_free_interrupt(pdev); |
| |
| release_memory: |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) |
| /* Releasing the memory region if any error occured */ |
| pci_clear_master(pdev); |
| #endif |
| |
| pci_disable_device(pdev); |
| |
| out: |
| kfree(priv->nl_buf); |
| free_netdev(ndev); |
| /* Any failure in probe, so it can directly return in remove */ |
| pci_set_drvdata(pdev, NULL); |
| |
| return result; |
| } |
| |
| static void qdpc_pcie_remove(struct pci_dev *pdev) |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| struct vmac_priv *vmp; |
| |
| if (ndev == NULL) |
| return; |
| |
| vmp = netdev_priv(ndev); |
| |
| vmp->ep_ready = 0; |
| if (vmp->init_thread) |
| kthread_stop(vmp->init_thread); |
| if (vmp->nl_socket) |
| netlink_kernel_release(vmp->nl_socket); |
| |
| kfree(vmp->nl_buf); |
| |
| vmac_clean(ndev); |
| |
| qdpc_free_interrupt(pdev); |
| qdpc_pcie_free_mem(pdev); |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) |
| pci_clear_master(pdev); |
| #endif |
| pci_disable_device(pdev); |
| |
| writel(TOPAZ_SET_INT(IPC_RESET_EP), (volatile void *)(vmp->ep_ipc_reg)); |
| qdpc_unmap_iomem(vmp); |
| |
| free_netdev(ndev); |
| g_ndev = NULL; |
| |
| return; |
| } |
| |
| static inline int qdpc_pcie_set_power_state(struct pci_dev *pdev, pci_power_t state) |
| { |
| uint16_t pmcsr; |
| |
| pci_read_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET, &pmcsr); |
| |
| switch (state) { |
| case PCI_D0: |
| pci_write_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET,(pmcsr & ~PCI_PM_CTRL_STATE_MASK) | PCI_D0); |
| break; |
| |
| case PCI_D3hot: |
| pci_write_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET,(pmcsr & ~PCI_PM_CTRL_STATE_MASK) | (PCI_D3hot | PCI_PM_CTRL_PME_ENABLE)); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int qdpc_pcie_suspend(struct pci_dev *pdev, pm_message_t state) |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| struct vmac_priv *priv; |
| |
| if (ndev == NULL) |
| return -EINVAL; |
| |
| priv = netdev_priv(ndev); |
| if (le32_to_cpu(*priv->ep_pmstate) == PCI_D3hot) { |
| return 0; |
| } |
| |
| printk("%s start power management suspend\n", qdpc_pcie_driver_name); |
| |
| /* Set ep not ready to drop packets in low power mode */ |
| priv->ep_ready = 0; |
| |
| ndev->flags &= ~IFF_RUNNING; |
| *priv->ep_pmstate = cpu_to_le32(PCI_D3hot); |
| barrier(); |
| writel(TOPAZ_SET_INT(IPC_EP_PM_CTRL), (volatile void *)(priv->ep_ipc_reg)); |
| |
| msleep(100); |
| pci_save_state(pdev); |
| pci_disable_device(pdev); |
| qdpc_pcie_set_power_state(pdev, PCI_D3hot); |
| |
| if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) |
| suspend_flag = 1; |
| |
| return 0; |
| } |
| |
| int qdpc_pcie_resume(struct pci_dev *pdev) |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| struct vmac_priv *priv; |
| int ret; |
| |
| if (ndev == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| priv = netdev_priv(ndev); |
| if (le32_to_cpu(*priv->ep_pmstate) == PCI_D0) { |
| ret = 0; |
| goto out; |
| } |
| |
| printk("%s start power management resume\n", qdpc_pcie_driver_name); |
| |
| ret = pci_enable_device(pdev); |
| if (ret) { |
| PRINT_ERROR("%s: pci_enable_device failed on resume\n", __func__); |
| goto out; |
| } |
| |
| pci_restore_state(pdev); |
| pdev->state_saved = true; |
| qdpc_pcie_set_power_state(pdev, PCI_D0); |
| |
| { |
| *priv->ep_pmstate = cpu_to_le32(PCI_D0); |
| barrier(); |
| writel(TOPAZ_SET_INT(IPC_EP_PM_CTRL), (volatile void *)(priv->ep_ipc_reg)); |
| |
| msleep(5000); |
| } |
| |
| if ( (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) && |
| (pdev->driver && pdev->driver->err_handler && pdev->driver->err_handler->slot_reset) ) { |
| printk("slot_reset in %s(), Device name: %s\n", __FUNCTION__, dev_name(&pdev->dev)); |
| if(pdev->driver->err_handler->slot_reset(pdev) == PCI_ERS_RESULT_RECOVERED) |
| printk("Recovery OK\n"); |
| else { |
| printk("Recovery Error"); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| /* Set ep_ready to resume tx traffic */ |
| priv->ep_ready = 1; |
| ndev->flags |= IFF_RUNNING; |
| |
| out: |
| if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) |
| suspend_flag = 0; |
| return ret; |
| } |
| |
| static int __init qdpc_init_module(void) |
| { |
| int ret; |
| |
| PRINT_DBG(KERN_INFO "Quantenna pcie driver initialization\n"); |
| |
| if (qdpc_platform_init()) { |
| PRINT_ERROR("Platform initilization failed \n"); |
| ret = FAILURE; |
| return ret; |
| } |
| |
| /* Register the pci driver with device*/ |
| if ((ret = pci_register_driver(&qdpc_pcie_driver)) < 0 ) { |
| PRINT_ERROR("Could not register the driver to pci : %d\n", ret); |
| ret = -ENODEV; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static void __exit qdpc_exit_module(void) |
| { |
| /* Release netlink */ |
| qdpc_platform_exit(); |
| |
| /* Unregister the pci driver with the device */ |
| pci_unregister_driver(&qdpc_pcie_driver); |
| |
| |
| return; |
| } |
| |
| void qdpc_recovery_clean(struct pci_dev *pdev, struct net_device *ndev) |
| { |
| struct vmac_priv *vmp; |
| |
| vmp = netdev_priv(ndev); |
| vmp->ep_ready = 0; |
| |
| if (vmp->init_thread) { |
| kthread_stop(vmp->init_thread); |
| vmp->init_thread = NULL; |
| } |
| |
| vmac_recovery_clean(ndev); |
| |
| pci_disable_device(pdev); |
| |
| return; |
| } |
| |
| int qdpc_recovery_reinit(struct pci_dev *pdev, struct net_device *ndev) |
| { |
| struct vmac_priv *priv = NULL; |
| |
| if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF && suspend_flag) |
| suspend_flag = 0; |
| else { |
| if (pdev->state_saved == true) { |
| pci_restore_state(pdev); |
| pdev->state_saved = true; |
| } else { |
| printk("Recovery Error: No saved state\n"); |
| goto out; |
| } |
| } |
| |
| if (pci_enable_device(pdev)) { |
| printk("Recovery Error: Failed to enable PCI device\n"); |
| goto out; |
| } |
| |
| priv = netdev_priv(ndev); |
| if (vmac_recovery_init(priv, ndev) == ENOMEM) { |
| printk("Recovery Error: Not enough memory\n"); |
| goto qdpc_recovery_err_0; |
| } |
| |
| priv->init_thread = kthread_run(qdpc_boot_thread, priv, "qdpc_init_thread"); |
| if (priv->init_thread == NULL) { |
| printk("Recovery Error: Thread creation failed \n"); |
| goto qdpc_recovery_err_0; |
| } |
| |
| return SUCCESS; |
| |
| qdpc_recovery_err_0: |
| pci_disable_device(pdev); |
| out: |
| return -1; |
| } |
| |
| static int qdpc_recovery_access_check(struct pci_dev *pdev) |
| { |
| uint32_t val = 0; |
| |
| pci_read_config_dword(pdev, QDPC_VENDOR_ID_OFFSET, &val); |
| |
| if (val == ((QDPC_DEVICE_ID << 16) | QDPC_VENDOR_ID)) { |
| printk("%s: PCIe read access check: Pass\n", __func__); |
| return 0; |
| } else { |
| printk("%s: PCIe read access check: Fail: VENDOR_ID read error: 0x%08x\n", __func__, val); |
| return -1; |
| } |
| } |
| |
| int qdpc_pcie_recovery(struct pci_dev *pdev) |
| { |
| struct net_device *ndev = pci_get_drvdata(pdev); |
| |
| qdpc_recovery_clean(pdev, ndev); |
| |
| /* Wait EP link up. If this function is called at hardirq context where 10s |
| * delay is not allowed, please replace with link up check at Root Complex's |
| * status register. |
| */ |
| mdelay(10000); |
| |
| if (qdpc_recovery_access_check(pdev) != 0) |
| return -1; |
| |
| /* Re-allocate and initialize data structure */ |
| qdpc_recovery_reinit(pdev, ndev); |
| |
| return 0; |
| } |
| |
| pci_ers_result_t qdpc_pcie_slot_reset(struct pci_dev *dev) |
| { |
| if (qdpc_pcie_recovery(dev) == 0) |
| return PCI_ERS_RESULT_RECOVERED; |
| else |
| return PCI_ERS_RESULT_DISCONNECT; |
| } |
| |
| static void qdpc_pcie_shutdown(struct pci_dev *pdev) |
| { |
| qdpc_pcie_remove(pdev); |
| |
| return; |
| } |
| |
| static int qdpc_bringup_fw(struct vmac_priv *priv) |
| { |
| __iomem qdpc_pcie_bda_t *bda = priv->bda; |
| uint32_t bdaflg; |
| char *fwname; |
| |
| qdpc_pci_endian_detect(priv); |
| qdpc_pci_dma_offset_reset(priv); |
| |
| printk("Setting HOST ready...\n"); |
| qdpc_setbootstate(priv, QDPC_BDA_FW_HOST_RDY); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_TARGET_RDY); |
| |
| bdaflg = qdpc_pci_readl(&bda->bda_flags); |
| if ((PCIE_BDA_FLASH_PRESENT & bdaflg) && EP_BOOT_FROM_FLASH) { |
| printk("EP have fw in flash, boot from flash\n"); |
| qdpc_pcie_posted_write((PCIE_BDA_FLASH_BOOT | |
| qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags); |
| qdpc_setbootstate(priv, QDPC_BDA_FW_TARGET_BOOT); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_FLASH_BOOT); |
| goto fw_start; |
| } |
| bdaflg &= PCIE_BDA_XMIT_UBOOT; |
| fwname = bdaflg ? QDPC_TOPAZ_UBOOT : QDPC_TOPAZ_IMG; |
| |
| qdpc_setbootstate(priv, QDPC_BDA_FW_TARGET_BOOT); |
| printk("EP FW load request...\n"); |
| qdpc_bootpoll(priv, QDPC_BDA_FW_LOAD_RDY); |
| |
| printk("Start download Firmware %s...\n", fwname); |
| if (qdpc_firmware_load(priv->pdev, priv, fwname)){ |
| printk("Failed to download firmware.\n"); |
| priv->init_thread = NULL; |
| do_exit(-1); |
| } |
| |
| fw_start: |
| qdpc_setbootstate(priv, QDPC_BDA_FW_START); |
| printk("Start booting EP...\n"); |
| if (bdaflg != PCIE_BDA_XMIT_UBOOT) { |
| if (qdpc_bootpoll(priv,QDPC_BDA_FW_CONFIG)) { |
| booterror(bda); |
| priv->init_thread = NULL; |
| do_exit(-1); |
| } |
| printk("EP boot successful, starting config...\n"); |
| |
| /* Save target-side MSI address for later enable/disable irq*/ |
| priv->dma_msi_imwr = readl(QDPC_BAR_VADDR(priv->dmareg_bar, TOPAZ_IMWR_DONE_ADDRLO_OFFSET)); |
| priv->dma_msi_dummy = virt_to_bus(&priv->dma_msi_data) + qdpc_pci_readl(&bda->bda_dma_offset); |
| priv->ep_pciecfg0_val = readl(QDPC_BAR_VADDR(priv->sysctl_bar, TOPAZ_PCIE_CFG0_OFFSET)); |
| |
| qdpc_setbootstate(priv, QDPC_BDA_FW_RUN); |
| qdpc_bootpoll(priv,QDPC_BDA_FW_RUNNING); |
| priv->ep_ready = 1; |
| } |
| |
| return (int)bdaflg; |
| } |
| |
| static int qdpc_boot_done(struct vmac_priv *priv) |
| { |
| struct net_device *ndev; |
| ndev = priv->ndev; |
| |
| PRINT_INFO("Connection established with Target BBIC4 board\n"); |
| |
| priv->init_thread = NULL; |
| do_exit(0); |
| } |
| |
| static int qdpc_boot_thread(void *data) |
| { |
| struct vmac_priv *priv = (struct vmac_priv *)data; |
| int i; |
| |
| for (i = 0; i < MAX_IMG_NUM; i++) { |
| if (qdpc_bringup_fw(priv) <= 0) |
| break; |
| } |
| |
| qdpc_boot_done(priv); |
| |
| return 0; |
| } |
| |
| static void qdpc_nl_recv_msg(struct sk_buff *skb) |
| { |
| struct vmac_priv *priv = netdev_priv(g_ndev); |
| struct nlmsghdr *nlh = (struct nlmsghdr*)skb->data; |
| struct sk_buff *skb2; |
| unsigned int data_len; |
| unsigned int offset; |
| qdpc_cmd_hdr_t *cmd_hdr; |
| uint16_t rpc_type; |
| |
| /* Parsing the netlink message */ |
| |
| PRINT_DBG(KERN_INFO "%s line %d Netlink received pid:%d, size:%d, type:%d\n", |
| __FUNCTION__, __LINE__, nlh->nlmsg_pid, nlh->nlmsg_len, nlh->nlmsg_type); |
| |
| switch (nlh->nlmsg_type) { |
| case QDPC_NL_TYPE_CLNT_STR_REG: |
| case QDPC_NL_TYPE_CLNT_LIB_REG: |
| if (nlh->nlmsg_type == QDPC_NL_TYPE_CLNT_STR_REG) |
| priv->str_call_nl_pid = nlh->nlmsg_pid; |
| else |
| priv->lib_call_nl_pid = nlh->nlmsg_pid; |
| return; |
| case QDPC_NL_TYPE_CLNT_STR_REQ: |
| case QDPC_NL_TYPE_CLNT_LIB_REQ: |
| break; |
| default: |
| PRINT_DBG(KERN_INFO "%s line %d Netlink Invalid type %d\n", |
| __FUNCTION__, __LINE__, nlh->nlmsg_type); |
| return; |
| } |
| |
| /* |
| * make new skbs; Fragment if necessary. |
| * The original skb will be freed in netlink_unicast_kernel, |
| * we hold the new skbs until DMA transfer is done |
| */ |
| offset = sizeof(struct nlmsghdr); |
| data_len = nlh->nlmsg_len; |
| |
| while (data_len > 0) { |
| unsigned int len = min_t(unsigned int, data_len, priv->ndev->mtu); |
| unsigned int skb2_len = len + sizeof(qdpc_cmd_hdr_t); |
| |
| skb2 = alloc_skb(skb2_len, GFP_ATOMIC); |
| if (!skb2) { |
| printk(KERN_INFO "%s: skb alloc failed\n", __func__); |
| return; |
| } |
| |
| data_len -= len; |
| |
| rpc_type = nlh->nlmsg_type & QDPC_RPC_TYPE_MASK; |
| rpc_type |= (data_len > 0 ? QDPC_RPC_TYPE_FRAG : 0); |
| |
| cmd_hdr = (qdpc_cmd_hdr_t *)skb2->data; |
| memcpy(cmd_hdr->dst_magic, QDPC_NETLINK_DST_MAGIC, ETH_ALEN); |
| memcpy(cmd_hdr->src_magic, QDPC_NETLINK_SRC_MAGIC, ETH_ALEN); |
| cmd_hdr->type = __constant_htons(QDPC_APP_NETLINK_TYPE); |
| cmd_hdr->len = htons((uint16_t)len); |
| cmd_hdr->rpc_type = htons(rpc_type); |
| cmd_hdr->total_len = htons((uint16_t)(nlh->nlmsg_len)); |
| |
| memcpy((uint8_t *)(cmd_hdr + 1), skb->data + offset, len); |
| |
| offset += len; |
| |
| skb_put(skb2, skb2_len); |
| skb_reset_mac_header(skb2); |
| skb_reset_network_header(skb2); |
| skb2->protocol = __constant_htons(QDPC_APP_NETLINK_TYPE); |
| skb2->dev = priv->ndev; |
| |
| dev_queue_xmit(skb2); |
| } |
| } |
| |
| module_init(qdpc_init_module); |
| module_exit(qdpc_exit_module); |