2015-05-27 16:07:18 +10:00
/*
* Copyright 2014 IBM Corp .
*
* 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 .
*/
# include <linux/pci.h>
# include <misc/cxl.h>
# include "cxl.h"
static int cxl_dma_set_mask ( struct pci_dev * pdev , u64 dma_mask )
{
if ( dma_mask < DMA_BIT_MASK ( 64 ) ) {
pr_info ( " %s only 64bit DMA supported on CXL " , __func__ ) ;
return - EIO ;
}
* ( pdev - > dev . dma_mask ) = dma_mask ;
return 0 ;
}
static int cxl_pci_probe_mode ( struct pci_bus * bus )
{
return PCI_PROBE_NORMAL ;
}
static int cxl_setup_msi_irqs ( struct pci_dev * pdev , int nvec , int type )
{
return - ENODEV ;
}
static void cxl_teardown_msi_irqs ( struct pci_dev * pdev )
{
/*
* MSI should never be set but need still need to provide this call
* back .
*/
}
static bool cxl_pci_enable_device_hook ( struct pci_dev * dev )
{
struct pci_controller * phb ;
struct cxl_afu * afu ;
struct cxl_context * ctx ;
phb = pci_bus_to_host ( dev - > bus ) ;
afu = ( struct cxl_afu * ) phb - > private_data ;
2015-09-07 10:52:58 +10:00
if ( ! cxl_adapter_link_ok ( afu - > adapter ) ) {
dev_warn ( & dev - > dev , " %s: Device link is down, refusing to enable AFU \n " , __func__ ) ;
return false ;
}
2015-05-27 16:07:18 +10:00
set_dma_ops ( & dev - > dev , & dma_direct_ops ) ;
set_dma_offset ( & dev - > dev , PAGE_OFFSET ) ;
/*
* Allocate a context to do cxl things too . If we eventually do real
* DMA ops , we ' ll need a default context to attach them to
*/
ctx = cxl_dev_context_init ( dev ) ;
if ( ! ctx )
return false ;
dev - > dev . archdata . cxl_ctx = ctx ;
return ( cxl_afu_check_and_enable ( afu ) = = 0 ) ;
}
static void cxl_pci_disable_device ( struct pci_dev * dev )
{
struct cxl_context * ctx = cxl_get_context ( dev ) ;
if ( ctx ) {
if ( ctx - > status = = STARTED ) {
dev_err ( & dev - > dev , " Default context started \n " ) ;
return ;
}
2015-06-05 14:38:26 +10:00
dev - > dev . archdata . cxl_ctx = NULL ;
2015-05-27 16:07:18 +10:00
cxl_release_context ( ctx ) ;
}
}
static resource_size_t cxl_pci_window_alignment ( struct pci_bus * bus ,
unsigned long type )
{
return 1 ;
}
static void cxl_pci_reset_secondary_bus ( struct pci_dev * dev )
{
/* Should we do an AFU reset here ? */
}
static int cxl_pcie_cfg_record ( u8 bus , u8 devfn )
{
return ( bus < < 8 ) + devfn ;
}
static unsigned long cxl_pcie_cfg_addr ( struct pci_controller * phb ,
u8 bus , u8 devfn , int offset )
{
int record = cxl_pcie_cfg_record ( bus , devfn ) ;
return ( unsigned long ) phb - > cfg_addr + ( ( unsigned long ) phb - > cfg_data * record ) + offset ;
}
static int cxl_pcie_config_info ( struct pci_bus * bus , unsigned int devfn ,
int offset , int len ,
volatile void __iomem * * ioaddr ,
u32 * mask , int * shift )
{
struct pci_controller * phb ;
struct cxl_afu * afu ;
unsigned long addr ;
phb = pci_bus_to_host ( bus ) ;
if ( phb = = NULL )
return PCIBIOS_DEVICE_NOT_FOUND ;
2015-06-29 16:05:11 +05:30
afu = ( struct cxl_afu * ) phb - > private_data ;
2015-05-27 16:07:18 +10:00
if ( cxl_pcie_cfg_record ( bus - > number , devfn ) > afu - > crs_num )
return PCIBIOS_DEVICE_NOT_FOUND ;
if ( offset > = ( unsigned long ) phb - > cfg_data )
return PCIBIOS_BAD_REGISTER_NUMBER ;
addr = cxl_pcie_cfg_addr ( phb , bus - > number , devfn , offset ) ;
* ioaddr = ( void * ) ( addr & ~ 0x3ULL ) ;
* shift = ( ( addr & 0x3 ) * 8 ) ;
switch ( len ) {
case 1 :
* mask = 0xff ;
break ;
case 2 :
* mask = 0xffff ;
break ;
default :
* mask = 0xffffffff ;
break ;
}
return 0 ;
}
2015-08-14 17:41:18 +10:00
static inline bool cxl_config_link_ok ( struct pci_bus * bus )
{
struct pci_controller * phb ;
struct cxl_afu * afu ;
/* Config space IO is based on phb->cfg_addr, which is based on
* afu_desc_mmio . This isn ' t safe to read / write when the link
* goes down , as EEH tears down MMIO space .
*
* Check if the link is OK before proceeding .
*/
phb = pci_bus_to_host ( bus ) ;
if ( phb = = NULL )
return false ;
afu = ( struct cxl_afu * ) phb - > private_data ;
return cxl_adapter_link_ok ( afu - > adapter ) ;
}
2015-05-27 16:07:18 +10:00
static int cxl_pcie_read_config ( struct pci_bus * bus , unsigned int devfn ,
int offset , int len , u32 * val )
{
volatile void __iomem * ioaddr ;
int shift , rc ;
u32 mask ;
rc = cxl_pcie_config_info ( bus , devfn , offset , len , & ioaddr ,
& mask , & shift ) ;
if ( rc )
return rc ;
2015-08-14 17:41:18 +10:00
if ( ! cxl_config_link_ok ( bus ) )
return PCIBIOS_DEVICE_NOT_FOUND ;
2015-05-27 16:07:18 +10:00
/* Can only read 32 bits */
* val = ( in_le32 ( ioaddr ) > > shift ) & mask ;
return PCIBIOS_SUCCESSFUL ;
}
static int cxl_pcie_write_config ( struct pci_bus * bus , unsigned int devfn ,
int offset , int len , u32 val )
{
volatile void __iomem * ioaddr ;
u32 v , mask ;
int shift , rc ;
rc = cxl_pcie_config_info ( bus , devfn , offset , len , & ioaddr ,
& mask , & shift ) ;
if ( rc )
return rc ;
2015-08-14 17:41:18 +10:00
if ( ! cxl_config_link_ok ( bus ) )
return PCIBIOS_DEVICE_NOT_FOUND ;
2015-05-27 16:07:18 +10:00
/* Can only write 32 bits so do read-modify-write */
mask < < = shift ;
val < < = shift ;
2015-11-04 13:24:09 +11:00
v = ( in_le32 ( ioaddr ) & ~ mask ) | ( val & mask ) ;
2015-05-27 16:07:18 +10:00
out_le32 ( ioaddr , v ) ;
return PCIBIOS_SUCCESSFUL ;
}
static struct pci_ops cxl_pcie_pci_ops =
{
. read = cxl_pcie_read_config ,
. write = cxl_pcie_write_config ,
} ;
static struct pci_controller_ops cxl_pci_controller_ops =
{
. probe_mode = cxl_pci_probe_mode ,
. enable_device_hook = cxl_pci_enable_device_hook ,
. disable_device = cxl_pci_disable_device ,
. release_device = cxl_pci_disable_device ,
. window_alignment = cxl_pci_window_alignment ,
. reset_secondary_bus = cxl_pci_reset_secondary_bus ,
. setup_msi_irqs = cxl_setup_msi_irqs ,
. teardown_msi_irqs = cxl_teardown_msi_irqs ,
. dma_set_mask = cxl_dma_set_mask ,
} ;
int cxl_pci_vphb_add ( struct cxl_afu * afu )
{
struct pci_dev * phys_dev ;
struct pci_controller * phb , * phys_phb ;
phys_dev = to_pci_dev ( afu - > adapter - > dev . parent ) ;
phys_phb = pci_bus_to_host ( phys_dev - > bus ) ;
/* Alloc and setup PHB data structure */
phb = pcibios_alloc_controller ( phys_phb - > dn ) ;
if ( ! phb )
return - ENODEV ;
/* Setup parent in sysfs */
phb - > parent = & phys_dev - > dev ;
/* Setup the PHB using arch provided callback */
phb - > ops = & cxl_pcie_pci_ops ;
phb - > cfg_addr = afu - > afu_desc_mmio + afu - > crs_offset ;
phb - > cfg_data = ( void * ) ( u64 ) afu - > crs_len ;
phb - > private_data = afu ;
phb - > controller_ops = cxl_pci_controller_ops ;
/* Scan the bus */
pcibios_scan_phb ( phb ) ;
if ( phb - > bus = = NULL )
return - ENXIO ;
/* Claim resources. This might need some rework as well depending
* whether we are doing probe - only or not , like assigning unassigned
* resources etc . . .
*/
pcibios_claim_one_bus ( phb - > bus ) ;
/* Add probed PCI devices to the device model */
pci_bus_add_devices ( phb - > bus ) ;
afu - > phb = phb ;
return 0 ;
}
2015-08-14 17:41:26 +10:00
void cxl_pci_vphb_reconfigure ( struct cxl_afu * afu )
{
/* When we are reconfigured, the AFU's MMIO space is unmapped
* and remapped . We need to reflect this in the PHB ' s view of
* the world .
*/
afu - > phb - > cfg_addr = afu - > afu_desc_mmio + afu - > crs_offset ;
}
2015-05-27 16:07:18 +10:00
void cxl_pci_vphb_remove ( struct cxl_afu * afu )
{
struct pci_controller * phb ;
/* If there is no configuration record we won't have one of these */
if ( ! afu | | ! afu - > phb )
return ;
phb = afu - > phb ;
2015-10-13 15:09:44 +11:00
afu - > phb = NULL ;
2015-05-27 16:07:18 +10:00
pci_remove_root_bus ( phb - > bus ) ;
2015-10-13 15:09:44 +11:00
pcibios_free_controller ( phb ) ;
2015-05-27 16:07:18 +10:00
}
struct cxl_afu * cxl_pci_to_afu ( struct pci_dev * dev )
{
struct pci_controller * phb ;
phb = pci_bus_to_host ( dev - > bus ) ;
return ( struct cxl_afu * ) phb - > private_data ;
}
EXPORT_SYMBOL_GPL ( cxl_pci_to_afu ) ;
unsigned int cxl_pci_to_cfg_record ( struct pci_dev * dev )
{
return cxl_pcie_cfg_record ( dev - > bus - > number , dev - > devfn ) ;
}
EXPORT_SYMBOL_GPL ( cxl_pci_to_cfg_record ) ;