mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-28 17:03:20 +02:00
1048c7b452
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@34060 3c298f89-4303-0410-b956-a3cf2f4a3e73
400 lines
14 KiB
C
400 lines
14 KiB
C
/******************************************************************************
|
|
**
|
|
** 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");
|
|
|