2020-05-26 12:21:13 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Loongson PCI Host Controller Driver
*
* Copyright ( C ) 2020 Jiaxun Yang < jiaxun . yang @ flygoat . com >
*/
# include <linux/of_device.h>
# include <linux/of_pci.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
2022-07-14 15:42:12 +03:00
# include <linux/pci-acpi.h>
# include <linux/pci-ecam.h>
2020-05-26 12:21:13 +03:00
# include "../pci.h"
/* Device IDs */
# define DEV_PCIE_PORT_0 0x7a09
# define DEV_PCIE_PORT_1 0x7a19
# define DEV_PCIE_PORT_2 0x7a29
# define DEV_LS2K_APB 0x7a02
# define DEV_LS7A_CONF 0x7a10
# define DEV_LS7A_LPC 0x7a0c
# define FLAG_CFG0 BIT(0)
# define FLAG_CFG1 BIT(1)
# define FLAG_DEV_FIX BIT(2)
2022-07-14 15:42:13 +03:00
# define FLAG_DEV_HIDDEN BIT(3)
2020-05-26 12:21:13 +03:00
2022-07-14 15:42:11 +03:00
struct loongson_pci_data {
u32 flags ;
struct pci_ops * ops ;
} ;
2020-05-26 12:21:13 +03:00
struct loongson_pci {
void __iomem * cfg0_base ;
void __iomem * cfg1_base ;
struct platform_device * pdev ;
2022-07-14 15:42:11 +03:00
const struct loongson_pci_data * data ;
2020-05-26 12:21:13 +03:00
} ;
/* Fixup wrong class code in PCIe bridges */
static void bridge_class_quirk ( struct pci_dev * dev )
{
2022-02-14 14:41:08 +03:00
dev - > class = PCI_CLASS_BRIDGE_PCI_NORMAL ;
2020-05-26 12:21:13 +03:00
}
2020-07-18 12:39:36 +03:00
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
2020-05-26 12:21:13 +03:00
DEV_PCIE_PORT_0 , bridge_class_quirk ) ;
2020-07-18 12:39:36 +03:00
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
2020-05-26 12:21:13 +03:00
DEV_PCIE_PORT_1 , bridge_class_quirk ) ;
2020-07-18 12:39:36 +03:00
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
2020-05-26 12:21:13 +03:00
DEV_PCIE_PORT_2 , bridge_class_quirk ) ;
static void system_bus_quirk ( struct pci_dev * pdev )
{
/*
* The address space consumed by these devices is outside the
* resources of the host bridge .
*/
pdev - > mmio_always_on = 1 ;
pdev - > non_compliant_bars = 1 ;
}
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
DEV_LS2K_APB , system_bus_quirk ) ;
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
DEV_LS7A_CONF , system_bus_quirk ) ;
DECLARE_PCI_FIXUP_EARLY ( PCI_VENDOR_ID_LOONGSON ,
DEV_LS7A_LPC , system_bus_quirk ) ;
static void loongson_mrrs_quirk ( struct pci_dev * dev )
{
struct pci_bus * bus = dev - > bus ;
struct pci_dev * bridge ;
static const struct pci_device_id bridge_devids [ ] = {
{ PCI_VDEVICE ( LOONGSON , DEV_PCIE_PORT_0 ) } ,
{ PCI_VDEVICE ( LOONGSON , DEV_PCIE_PORT_1 ) } ,
{ PCI_VDEVICE ( LOONGSON , DEV_PCIE_PORT_2 ) } ,
{ 0 , } ,
} ;
/* look for the matching bridge */
while ( ! pci_is_root_bus ( bus ) ) {
bridge = bus - > self ;
bus = bus - > parent ;
/*
* Some Loongson PCIe ports have a h / w limitation of
* 256 bytes maximum read request size . They can ' t handle
* anything larger than this . So force this limit on
* any devices attached under these ports .
*/
if ( pci_match_id ( bridge_devids , bridge ) ) {
if ( pcie_get_readrq ( dev ) > 256 ) {
pci_info ( dev , " limiting MRRS to 256 \n " ) ;
pcie_set_readrq ( dev , 256 ) ;
}
break ;
}
}
}
DECLARE_PCI_FIXUP_ENABLE ( PCI_ANY_ID , PCI_ANY_ID , loongson_mrrs_quirk ) ;
2022-07-14 15:42:12 +03:00
static struct loongson_pci * pci_bus_to_loongson_pci ( struct pci_bus * bus )
2020-05-26 12:21:13 +03:00
{
2022-07-14 15:42:12 +03:00
struct pci_config_window * cfg ;
2020-05-26 12:21:13 +03:00
2022-07-14 15:42:12 +03:00
if ( acpi_disabled )
return ( struct loongson_pci * ) ( bus - > sysdata ) ;
cfg = bus - > sysdata ;
return ( struct loongson_pci * ) ( cfg - > priv ) ;
2020-05-26 12:21:13 +03:00
}
2022-07-14 15:42:12 +03:00
static void __iomem * cfg0_map ( struct loongson_pci * priv , struct pci_bus * bus ,
unsigned int devfn , int where )
2020-05-26 12:21:13 +03:00
{
unsigned long addroff = 0x0 ;
2022-07-14 15:42:12 +03:00
unsigned char busnum = bus - > number ;
2020-05-26 12:21:13 +03:00
2022-07-14 15:42:12 +03:00
if ( ! pci_is_root_bus ( bus ) ) {
2020-05-26 12:21:13 +03:00
addroff | = BIT ( 24 ) ; /* Type 1 Access */
2022-07-14 15:42:12 +03:00
addroff | = ( busnum < < 16 ) ;
}
addroff | = ( devfn < < 8 ) | where ;
2020-05-26 12:21:13 +03:00
return priv - > cfg0_base + addroff ;
}
2022-07-14 15:42:12 +03:00
static void __iomem * cfg1_map ( struct loongson_pci * priv , struct pci_bus * bus ,
unsigned int devfn , int where )
2020-05-26 12:21:13 +03:00
{
2022-07-14 15:42:12 +03:00
unsigned long addroff = 0x0 ;
2020-05-26 12:21:13 +03:00
unsigned char busnum = bus - > number ;
2022-07-14 15:42:12 +03:00
if ( ! pci_is_root_bus ( bus ) ) {
addroff | = BIT ( 28 ) ; /* Type 1 Access */
addroff | = ( busnum < < 16 ) ;
}
addroff | = ( devfn < < 8 ) | ( where & 0xff ) | ( ( where & 0xf00 ) < < 16 ) ;
return priv - > cfg1_base + addroff ;
}
2022-07-14 15:42:13 +03:00
static bool pdev_may_exist ( struct pci_bus * bus , unsigned int device ,
unsigned int function )
{
return ! ( pci_is_root_bus ( bus ) & &
( device > = 9 & & device < = 20 ) & & ( function > 0 ) ) ;
}
2022-07-14 15:42:12 +03:00
static void __iomem * pci_loongson_map_bus ( struct pci_bus * bus ,
unsigned int devfn , int where )
{
2022-07-14 15:42:13 +03:00
unsigned int device = PCI_SLOT ( devfn ) ;
unsigned int function = PCI_FUNC ( devfn ) ;
2022-07-14 15:42:12 +03:00
struct loongson_pci * priv = pci_bus_to_loongson_pci ( bus ) ;
2020-05-26 12:21:13 +03:00
/*
* Do not read more than one device on the bus other than
2022-07-14 15:42:12 +03:00
* the host bus .
2020-05-26 12:21:13 +03:00
*/
2022-07-14 15:42:13 +03:00
if ( ( priv - > data - > flags & FLAG_DEV_FIX ) & & bus - > self ) {
if ( ! pci_is_root_bus ( bus ) & & ( device > 0 ) )
return NULL ;
}
/* Don't access non-existent devices */
if ( priv - > data - > flags & FLAG_DEV_HIDDEN ) {
if ( ! pdev_may_exist ( bus , device , function ) )
return NULL ;
}
2020-05-26 12:21:13 +03:00
/* CFG0 can only access standard space */
if ( where < PCI_CFG_SPACE_SIZE & & priv - > cfg0_base )
2022-07-14 15:42:12 +03:00
return cfg0_map ( priv , bus , devfn , where ) ;
2020-05-26 12:21:13 +03:00
/* CFG1 can access extended space */
if ( where < PCI_CFG_SPACE_EXP_SIZE & & priv - > cfg1_base )
2022-07-14 15:42:12 +03:00
return cfg1_map ( priv , bus , devfn , where ) ;
2020-05-26 12:21:13 +03:00
return NULL ;
}
2022-07-14 15:42:12 +03:00
# ifdef CONFIG_OF
2020-05-26 12:21:13 +03:00
static int loongson_map_irq ( const struct pci_dev * dev , u8 slot , u8 pin )
{
int irq ;
u8 val ;
irq = of_irq_parse_and_map_pci ( dev , slot , pin ) ;
if ( irq > 0 )
return irq ;
/* Care i8259 legacy systems */
pci_read_config_byte ( dev , PCI_INTERRUPT_LINE , & val ) ;
/* i8259 only have 15 IRQs */
if ( val > 15 )
return 0 ;
return val ;
}
2022-07-14 15:42:11 +03:00
/* LS2K/LS7A accept 8/16/32-bit PCI config operations */
2020-05-26 12:21:13 +03:00
static struct pci_ops loongson_pci_ops = {
2022-07-14 15:42:11 +03:00
. map_bus = pci_loongson_map_bus ,
. read = pci_generic_config_read ,
. write = pci_generic_config_write ,
} ;
/* RS780/SR5690 only accept 32-bit PCI config operations */
static struct pci_ops loongson_pci_ops32 = {
2020-05-26 12:21:13 +03:00
. map_bus = pci_loongson_map_bus ,
. read = pci_generic_config_read32 ,
. write = pci_generic_config_write32 ,
} ;
2022-07-14 15:42:11 +03:00
static const struct loongson_pci_data ls2k_pci_data = {
2022-07-14 15:42:13 +03:00
. flags = FLAG_CFG1 | FLAG_DEV_FIX | FLAG_DEV_HIDDEN ,
2022-07-14 15:42:11 +03:00
. ops = & loongson_pci_ops ,
} ;
static const struct loongson_pci_data ls7a_pci_data = {
2022-07-14 15:42:13 +03:00
. flags = FLAG_CFG1 | FLAG_DEV_FIX | FLAG_DEV_HIDDEN ,
2022-07-14 15:42:11 +03:00
. ops = & loongson_pci_ops ,
} ;
static const struct loongson_pci_data rs780e_pci_data = {
. flags = FLAG_CFG0 ,
. ops = & loongson_pci_ops32 ,
} ;
2020-05-26 12:21:13 +03:00
static const struct of_device_id loongson_pci_of_match [ ] = {
{ . compatible = " loongson,ls2k-pci " ,
2022-07-14 15:42:11 +03:00
. data = & ls2k_pci_data , } ,
2020-05-26 12:21:13 +03:00
{ . compatible = " loongson,ls7a-pci " ,
2022-07-14 15:42:11 +03:00
. data = & ls7a_pci_data , } ,
2020-05-26 12:21:13 +03:00
{ . compatible = " loongson,rs780e-pci " ,
2022-07-14 15:42:11 +03:00
. data = & rs780e_pci_data , } ,
2020-05-26 12:21:13 +03:00
{ }
} ;
static int loongson_pci_probe ( struct platform_device * pdev )
{
struct loongson_pci * priv ;
struct device * dev = & pdev - > dev ;
struct device_node * node = dev - > of_node ;
struct pci_host_bridge * bridge ;
struct resource * regs ;
if ( ! node )
return - ENODEV ;
bridge = devm_pci_alloc_host_bridge ( dev , sizeof ( * priv ) ) ;
if ( ! bridge )
return - ENODEV ;
priv = pci_host_bridge_priv ( bridge ) ;
priv - > pdev = pdev ;
2022-07-14 15:42:11 +03:00
priv - > data = of_device_get_match_data ( dev ) ;
2020-05-26 12:21:13 +03:00
2022-07-14 15:42:11 +03:00
if ( priv - > data - > flags & FLAG_CFG0 ) {
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! regs )
dev_err ( dev , " missing mem resources for cfg0 \n " ) ;
else {
priv - > cfg0_base = devm_pci_remap_cfg_resource ( dev , regs ) ;
if ( IS_ERR ( priv - > cfg0_base ) )
return PTR_ERR ( priv - > cfg0_base ) ;
}
2020-05-26 12:21:13 +03:00
}
2022-07-14 15:42:11 +03:00
if ( priv - > data - > flags & FLAG_CFG1 ) {
2020-05-26 12:21:13 +03:00
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 1 ) ;
if ( ! regs )
dev_info ( dev , " missing mem resource for cfg1 \n " ) ;
else {
priv - > cfg1_base = devm_pci_remap_cfg_resource ( dev , regs ) ;
if ( IS_ERR ( priv - > cfg1_base ) )
priv - > cfg1_base = NULL ;
}
}
bridge - > sysdata = priv ;
2022-07-14 15:42:11 +03:00
bridge - > ops = priv - > data - > ops ;
2020-05-26 12:21:13 +03:00
bridge - > map_irq = loongson_map_irq ;
2020-09-21 16:10:54 +03:00
return pci_host_probe ( bridge ) ;
2020-05-26 12:21:13 +03:00
}
static struct platform_driver loongson_pci_driver = {
. driver = {
. name = " loongson-pci " ,
. of_match_table = loongson_pci_of_match ,
} ,
. probe = loongson_pci_probe ,
} ;
builtin_platform_driver ( loongson_pci_driver ) ;
2022-07-14 15:42:12 +03:00
# endif
# ifdef CONFIG_ACPI
static int loongson_pci_ecam_init ( struct pci_config_window * cfg )
{
struct device * dev = cfg - > parent ;
struct loongson_pci * priv ;
struct loongson_pci_data * data ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
cfg - > priv = priv ;
2022-07-14 15:42:13 +03:00
data - > flags = FLAG_CFG1 | FLAG_DEV_HIDDEN ;
2022-07-14 15:42:12 +03:00
priv - > data = data ;
priv - > cfg1_base = cfg - > win - ( cfg - > busr . start < < 16 ) ;
return 0 ;
}
const struct pci_ecam_ops loongson_pci_ecam_ops = {
. bus_shift = 16 ,
. init = loongson_pci_ecam_init ,
. pci_ops = {
. map_bus = pci_loongson_map_bus ,
. read = pci_generic_config_read ,
. write = pci_generic_config_write ,
}
} ;
# endif