diff options
Diffstat (limited to 'target/linux/lantiq/files/arch/mips/pci/pcie-lantiq-msi.c')
-rw-r--r-- | target/linux/lantiq/files/arch/mips/pci/pcie-lantiq-msi.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/target/linux/lantiq/files/arch/mips/pci/pcie-lantiq-msi.c b/target/linux/lantiq/files/arch/mips/pci/pcie-lantiq-msi.c new file mode 100644 index 000000000..9cbf639bb --- /dev/null +++ b/target/linux/lantiq/files/arch/mips/pci/pcie-lantiq-msi.c @@ -0,0 +1,399 @@ +/****************************************************************************** +** +** 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 <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/pci.h> +#include <linux/msi.h> +#include <linux/module.h> +#include <asm/bootinfo.h> +#include <asm/irq.h> +#include <asm/traps.h> + +#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"); + |