/****************************************************************************** ** ** FILE NAME : ifxmips_pcie_msi.c ** PROJECT : IFX UEIP for VRX200 ** MODULES : PCI MSI sub module ** ** DATE : 02 Mar 2009 ** AUTHOR : Lei Chuanhua ** DESCRIPTION : PCIe MSI Driver ** COPYRIGHT : Copyright (c) 2009 ** Infineon Technologies AG ** Am Campeon 1-12, 85579 Neubiberg, Germany ** ** 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. ** HISTORY ** $Date $Author $Comment ** 02 Mar,2009 Lei Chuanhua Initial version *******************************************************************************/ /*! \defgroup IFX_PCIE_MSI MSI OS APIs \ingroup IFX_PCIE \brief PCIe bus driver OS interface functions */ /*! \file ifxmips_pcie_msi.c \ingroup IFX_PCIE \brief PCIe MSI OS interface file */ #include #include #include #include #include #include #include #include #include #include #include #include "pcie-lantiq.h" #define IFX_MSI_IRQ_NUM 16 #define SM(_v, _f) (((_v) << _f##_S) & (_f)) #define IFX_MSI_PIC_REG_BASE (KSEG1 | 0x1F700000) #define IFX_PCIE_MSI_IR0 (INT_NUM_IM4_IRL0 + 27) #define IFX_PCIE_MSI_IR1 (INT_NUM_IM4_IRL0 + 28) #define IFX_PCIE_MSI_IR2 (INT_NUM_IM4_IRL0 + 29) #define IFX_PCIE_MSI_IR3 (INT_NUM_IM0_IRL0 + 30) #define IFX_MSI_PCI_INT_DISABLE 0x80000000 #define IFX_MSI_PIC_INT_LINE 0x30000000 #define IFX_MSI_PIC_MSG_ADDR 0x0FFF0000 #define IFX_MSI_PIC_MSG_DATA 0x0000FFFF #define IFX_MSI_PIC_BIG_ENDIAN 1 #define IFX_MSI_PIC_INT_LINE_S 28 #define IFX_MSI_PIC_MSG_ADDR_S 16 #define IFX_MSI_PIC_MSG_DATA_S 0x0 enum { IFX_PCIE_MSI_IDX0 = 0, IFX_PCIE_MSI_IDX1, IFX_PCIE_MSI_IDX2, IFX_PCIE_MSI_IDX3, }; typedef struct ifx_msi_irq_idx { const int irq; const int idx; }ifx_msi_irq_idx_t; struct ifx_msi_pic { volatile u32 pic_table[IFX_MSI_IRQ_NUM]; volatile u32 pic_endian; /* 0x40 */ }; typedef struct ifx_msi_pic *ifx_msi_pic_t; typedef struct ifx_msi_irq { const volatile ifx_msi_pic_t msi_pic_p; const u32 msi_phy_base; const ifx_msi_irq_idx_t msi_irq_idx[IFX_MSI_IRQ_NUM]; /* * Each bit in msi_free_irq_bitmask represents a MSI interrupt that is * in use. */ u16 msi_free_irq_bitmask; /* * Each bit in msi_multiple_irq_bitmask tells that the device using * this bit in msi_free_irq_bitmask is also using the next bit. This * is used so we can disable all of the MSI interrupts when a device * uses multiple. */ u16 msi_multiple_irq_bitmask; }ifx_msi_irq_t; static ifx_msi_irq_t msi_irqs[IFX_PCIE_CORE_NR] = { { .msi_pic_p = (const volatile ifx_msi_pic_t)IFX_MSI_PIC_REG_BASE, .msi_phy_base = PCIE_MSI_PHY_BASE, .msi_irq_idx = { {IFX_PCIE_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE_MSI_IR3, IFX_PCIE_MSI_IDX3}, }, .msi_free_irq_bitmask = 0, .msi_multiple_irq_bitmask= 0, }, #ifdef CONFIG_IFX_PCIE_2ND_CORE { .msi_pic_p = (const volatile ifx_msi_pic_t)IFX_MSI1_PIC_REG_BASE, .msi_phy_base = PCIE1_MSI_PHY_BASE, .msi_irq_idx = { {IFX_PCIE1_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE1_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE1_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE1_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE1_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE1_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE1_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE1_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE1_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE1_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE1_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE1_MSI_IR3, IFX_PCIE_MSI_IDX3}, {IFX_PCIE1_MSI_IR0, IFX_PCIE_MSI_IDX0}, {IFX_PCIE1_MSI_IR1, IFX_PCIE_MSI_IDX1}, {IFX_PCIE1_MSI_IR2, IFX_PCIE_MSI_IDX2}, {IFX_PCIE1_MSI_IR3, IFX_PCIE_MSI_IDX3}, }, .msi_free_irq_bitmask = 0, .msi_multiple_irq_bitmask= 0, }, #endif /* CONFIG_IFX_PCIE_2ND_CORE */ }; /* * This lock controls updates to msi_free_irq_bitmask, * msi_multiple_irq_bitmask and pic register settting */ static DEFINE_SPINLOCK(ifx_pcie_msi_lock); void pcie_msi_pic_init(int pcie_port) { spin_lock(&ifx_pcie_msi_lock); msi_irqs[pcie_port].msi_pic_p->pic_endian = IFX_MSI_PIC_BIG_ENDIAN; spin_unlock(&ifx_pcie_msi_lock); } /** * \fn int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) * \brief Called when a driver request MSI interrupts instead of the * legacy INT A-D. This routine will allocate multiple interrupts * for MSI devices that support them. A device can override this by * programming the MSI control bits [6:4] before calling * pci_enable_msi(). * * \param[in] pdev Device requesting MSI interrupts * \param[in] desc MSI descriptor * * \return -EINVAL Invalid pcie root port or invalid msi bit * \return 0 OK * \ingroup IFX_PCIE_MSI */ int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) { int irq, pos; u16 control; int irq_idx; int irq_step; int configured_private_bits; int request_private_bits; struct msi_msg msg; u16 search_mask; struct ifx_pci_controller *ctrl = pdev->bus->sysdata; int pcie_port = ctrl->port; IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s %s enter\n", __func__, pci_name(pdev)); /* XXX, skip RC MSI itself */ if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT) { IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s RC itself doesn't use MSI interrupt\n", __func__); return -EINVAL; } /* * Read the MSI config to figure out how many IRQs this device * wants. Most devices only want 1, which will give * configured_private_bits and request_private_bits equal 0. */ pci_read_config_word(pdev, desc->msi_attrib.pos + PCI_MSI_FLAGS, &control); /* * If the number of private bits has been configured then use * that value instead of the requested number. This gives the * driver the chance to override the number of interrupts * before calling pci_enable_msi(). */ configured_private_bits = (control & PCI_MSI_FLAGS_QSIZE) >> 4; if (configured_private_bits == 0) { /* Nothing is configured, so use the hardware requested size */ request_private_bits = (control & PCI_MSI_FLAGS_QMASK) >> 1; } else { /* * Use the number of configured bits, assuming the * driver wanted to override the hardware request * value. */ request_private_bits = configured_private_bits; } /* * The PCI 2.3 spec mandates that there are at most 32 * interrupts. If this device asks for more, only give it one. */ if (request_private_bits > 5) { request_private_bits = 0; } again: /* * The IRQs have to be aligned on a power of two based on the * number being requested. */ irq_step = (1 << request_private_bits); /* Mask with one bit for each IRQ */ search_mask = (1 << irq_step) - 1; /* * We're going to search msi_free_irq_bitmask_lock for zero * bits. This represents an MSI interrupt number that isn't in * use. */ spin_lock(&ifx_pcie_msi_lock); for (pos = 0; pos < IFX_MSI_IRQ_NUM; pos += irq_step) { if ((msi_irqs[pcie_port].msi_free_irq_bitmask & (search_mask << pos)) == 0) { msi_irqs[pcie_port].msi_free_irq_bitmask |= search_mask << pos; msi_irqs[pcie_port].msi_multiple_irq_bitmask |= (search_mask >> 1) << pos; break; } } spin_unlock(&ifx_pcie_msi_lock); /* Make sure the search for available interrupts didn't fail */ if (pos >= IFX_MSI_IRQ_NUM) { if (request_private_bits) { IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s: Unable to find %d free " "interrupts, trying just one", __func__, 1 << request_private_bits); request_private_bits = 0; goto again; } else { printk(KERN_ERR "%s: Unable to find a free MSI interrupt\n", __func__); return -EINVAL; } } irq = msi_irqs[pcie_port].msi_irq_idx[pos].irq; irq_idx = msi_irqs[pcie_port].msi_irq_idx[pos].idx; IFX_PCIE_PRINT(PCIE_MSG_MSI, "pos %d, irq %d irq_idx %d\n", pos, irq, irq_idx); /* * Initialize MSI. This has to match the memory-write endianess from the device * Address bits [23:12] */ spin_lock(&ifx_pcie_msi_lock); msi_irqs[pcie_port].msi_pic_p->pic_table[pos] = SM(irq_idx, IFX_MSI_PIC_INT_LINE) | SM((msi_irqs[pcie_port].msi_phy_base >> 12), IFX_MSI_PIC_MSG_ADDR) | SM((1 << pos), IFX_MSI_PIC_MSG_DATA); /* Enable this entry */ msi_irqs[pcie_port].msi_pic_p->pic_table[pos] &= ~IFX_MSI_PCI_INT_DISABLE; spin_unlock(&ifx_pcie_msi_lock); IFX_PCIE_PRINT(PCIE_MSG_MSI, "pic_table[%d]: 0x%08x\n", pos, msi_irqs[pcie_port].msi_pic_p->pic_table[pos]); /* Update the number of IRQs the device has available to it */ control &= ~PCI_MSI_FLAGS_QSIZE; control |= (request_private_bits << 4); pci_write_config_word(pdev, desc->msi_attrib.pos + PCI_MSI_FLAGS, control); irq_set_msi_desc(irq, desc); msg.address_hi = 0x0; msg.address_lo = msi_irqs[pcie_port].msi_phy_base; msg.data = SM((1 << pos), IFX_MSI_PIC_MSG_DATA); IFX_PCIE_PRINT(PCIE_MSG_MSI, "msi_data: pos %d 0x%08x\n", pos, msg.data); write_msi_msg(irq, &msg); IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s exit\n", __func__); return 0; } static int pcie_msi_irq_to_port(unsigned int irq, int *port) { int ret = 0; if (irq == IFX_PCIE_MSI_IR0 || irq == IFX_PCIE_MSI_IR1 || irq == IFX_PCIE_MSI_IR2 || irq == IFX_PCIE_MSI_IR3) { *port = IFX_PCIE_PORT0; } #ifdef CONFIG_IFX_PCIE_2ND_CORE else if (irq == IFX_PCIE1_MSI_IR0 || irq == IFX_PCIE1_MSI_IR1 || irq == IFX_PCIE1_MSI_IR2 || irq == IFX_PCIE1_MSI_IR3) { *port = IFX_PCIE_PORT1; } #endif /* CONFIG_IFX_PCIE_2ND_CORE */ else { printk(KERN_ERR "%s: Attempted to teardown illegal " "MSI interrupt (%d)\n", __func__, irq); ret = -EINVAL; } return ret; } /** * \fn void arch_teardown_msi_irq(unsigned int irq) * \brief Called when a device no longer needs its MSI interrupts. All * MSI interrupts for the device are freed. * * \param irq The devices first irq number. There may be multple in sequence. * \return none * \ingroup IFX_PCIE_MSI */ void arch_teardown_msi_irq(unsigned int irq) { int pos; int number_irqs; u16 bitmask; int pcie_port; IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s enter\n", __func__); BUG_ON(irq > (INT_NUM_IM4_IRL0 + 31)); if (pcie_msi_irq_to_port(irq, &pcie_port) != 0) { return; } /* Shift the mask to the correct bit location, not always correct * Probally, the first match will be chosen. */ for (pos = 0; pos < IFX_MSI_IRQ_NUM; pos++) { if ((msi_irqs[pcie_port].msi_irq_idx[pos].irq == irq) && (msi_irqs[pcie_port].msi_free_irq_bitmask & ( 1 << pos))) { break; } } if (pos >= IFX_MSI_IRQ_NUM) { printk(KERN_ERR "%s: Unable to find a matched MSI interrupt\n", __func__); return; } spin_lock(&ifx_pcie_msi_lock); /* Disable this entry */ msi_irqs[pcie_port].msi_pic_p->pic_table[pos] |= IFX_MSI_PCI_INT_DISABLE; msi_irqs[pcie_port].msi_pic_p->pic_table[pos] &= ~(IFX_MSI_PIC_INT_LINE | IFX_MSI_PIC_MSG_ADDR | IFX_MSI_PIC_MSG_DATA); spin_unlock(&ifx_pcie_msi_lock); /* * Count the number of IRQs we need to free by looking at the * msi_multiple_irq_bitmask. Each bit set means that the next * IRQ is also owned by this device. */ number_irqs = 0; while (((pos + number_irqs) < IFX_MSI_IRQ_NUM) && (msi_irqs[pcie_port].msi_multiple_irq_bitmask & (1 << (pos + number_irqs)))) { number_irqs++; } number_irqs++; /* Mask with one bit for each IRQ */ bitmask = (1 << number_irqs) - 1; bitmask <<= pos; if ((msi_irqs[pcie_port].msi_free_irq_bitmask & bitmask) != bitmask) { printk(KERN_ERR "%s: Attempted to teardown MSI " "interrupt (%d) not in use\n", __func__, irq); return; } /* Checks are done, update the in use bitmask */ spin_lock(&ifx_pcie_msi_lock); msi_irqs[pcie_port].msi_free_irq_bitmask &= ~bitmask; msi_irqs[pcie_port].msi_multiple_irq_bitmask &= ~(bitmask >> 1); spin_unlock(&ifx_pcie_msi_lock); IFX_PCIE_PRINT(PCIE_MSG_MSI, "%s exit\n", __func__); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Chuanhua.Lei@infineon.com"); MODULE_SUPPORTED_DEVICE("Infineon PCIe IP builtin MSI PIC module"); MODULE_DESCRIPTION("Infineon PCIe IP builtin MSI PIC driver");