Merge branch 'pci/host-designware' into next
* pci/host-designware: ARM: DRA7: clockdomain: Change the CLKTRCTRL of CM_PCIE_CLKSTCTRL to SW_WKUP MAINTAINERS: Add PCI Endpoint maintainer Documentation: PCI: Add userguide for PCI endpoint test function tools: PCI: Add sample test script to invoke pcitest tools: PCI: Add a userspace tool to test PCI endpoint Documentation: misc-devices: Add Documentation for pci-endpoint-test driver misc: Add host side PCI driver for PCI test function device PCI: Add device IDs for DRA74x and DRA72x dt-bindings: PCI: dra7xx: Add DT bindings to enable unaligned access PCI: dwc: dra7xx: Workaround for errata id i870 dt-bindings: PCI: dra7xx: Add DT bindings for PCI dra7xx EP mode PCI: dwc: dra7xx: Add EP mode support PCI: dwc: dra7xx: Facilitate wrapper and MSI interrupts to be enabled independently dt-bindings: PCI: Add DT bindings for PCI designware EP mode PCI: dwc: designware: Add EP mode support Documentation: PCI: Add binding documentation for pci-test endpoint function PCI: endpoint: functions: Add an EP function to test PCI Documentation: PCI: Add specification for the *PCI test* function device PCI: endpoint: Create configfs entry for EPC device and EPF driver Documentation: PCI: Guide to use PCI endpoint configfs PCI: endpoint: Introduce configfs entry for configuring EP functions Documentation: PCI: Guide to use PCI Endpoint Core Layer PCI: endpoint: Add EP core layer to enable EP controller and EP functions PCI: dwc: dra7xx: Push request_irq() call to the bottom of probe PCI: dwc: designware: Move _unroll configurations to a separate function PCI: dwc: all: Modify dbi accessors to access data of 4/2/1 bytes PCI: dwc: all: Modify dbi accessors to take dbi_base as argument PCI: dwc: artpec6: Populate cpu_addr_fixup ops PCI: dwc: dra7xx: Populate cpu_addr_fixup ops PCI: dwc: designware: Add new *ops* for CPU addr fixup PCI: dwc: Fix uninitialized variable in dw_handle_msi_irq() PCI: dwc: Unindent dw_handle_msi_irq() loop PCI: dwc: Fix dw_pcie_ops NULL pointer dereference PCI: dwc: Select PCI_HOST_COMMON for hisi PCI: thunder-pem: Fix legacy firmware PEM-specific resources PCI: thunder-pem: Add legacy firmware support for Cavium ThunderX host controller PCI: thunder-pem: Use Cavium assigned hardware ID for ThunderX host controller PCI: iproc: Save host bridge window resource in struct iproc_pcie PCI/ASPM: Always set link->downstream to avoid NULL dereference on remove PCI: Prevent VPD access for QLogic ISP2722 PCI: exynos: Initialize elbi_base even when using PHY framework
This commit is contained in:
commit
9b2707ca56
@ -12,3 +12,13 @@ pci.txt
|
||||
- info on the PCI subsystem for device driver authors
|
||||
pcieaer-howto.txt
|
||||
- the PCI Express Advanced Error Reporting Driver Guide HOWTO
|
||||
endpoint/pci-endpoint.txt
|
||||
- guide to add endpoint controller driver and endpoint function driver.
|
||||
endpoint/pci-endpoint-cfs.txt
|
||||
- guide to use configfs to configure the PCI endpoint function.
|
||||
endpoint/pci-test-function.txt
|
||||
- specification of *PCI test* function device.
|
||||
endpoint/pci-test-howto.txt
|
||||
- userguide for PCI endpoint test function.
|
||||
endpoint/function/binding/
|
||||
- binding documentation for PCI endpoint function
|
||||
|
17
Documentation/PCI/endpoint/function/binding/pci-test.txt
Normal file
17
Documentation/PCI/endpoint/function/binding/pci-test.txt
Normal file
@ -0,0 +1,17 @@
|
||||
PCI TEST ENDPOINT FUNCTION
|
||||
|
||||
name: Should be "pci_epf_test" to bind to the pci_epf_test driver.
|
||||
|
||||
Configurable Fields:
|
||||
vendorid : should be 0x104c
|
||||
deviceid : should be 0xb500 for DRA74x and 0xb501 for DRA72x
|
||||
revid : don't care
|
||||
progif_code : don't care
|
||||
subclass_code : don't care
|
||||
baseclass_code : should be 0xff
|
||||
cache_line_size : don't care
|
||||
subsys_vendor_id : don't care
|
||||
subsys_id : don't care
|
||||
interrupt_pin : Should be 1 - INTA, 2 - INTB, 3 - INTC, 4 -INTD
|
||||
msi_interrupts : Should be 1 to 32 depending on the number of MSI interrupts
|
||||
to test
|
105
Documentation/PCI/endpoint/pci-endpoint-cfs.txt
Normal file
105
Documentation/PCI/endpoint/pci-endpoint-cfs.txt
Normal file
@ -0,0 +1,105 @@
|
||||
CONFIGURING PCI ENDPOINT USING CONFIGFS
|
||||
Kishon Vijay Abraham I <kishon@ti.com>
|
||||
|
||||
The PCI Endpoint Core exposes configfs entry (pci_ep) to configure the
|
||||
PCI endpoint function and to bind the endpoint function
|
||||
with the endpoint controller. (For introducing other mechanisms to
|
||||
configure the PCI Endpoint Function refer to [1]).
|
||||
|
||||
*) Mounting configfs
|
||||
|
||||
The PCI Endpoint Core layer creates pci_ep directory in the mounted configfs
|
||||
directory. configfs can be mounted using the following command.
|
||||
|
||||
mount -t configfs none /sys/kernel/config
|
||||
|
||||
*) Directory Structure
|
||||
|
||||
The pci_ep configfs has two directories at its root: controllers and
|
||||
functions. Every EPC device present in the system will have an entry in
|
||||
the *controllers* directory and and every EPF driver present in the system
|
||||
will have an entry in the *functions* directory.
|
||||
|
||||
/sys/kernel/config/pci_ep/
|
||||
.. controllers/
|
||||
.. functions/
|
||||
|
||||
*) Creating EPF Device
|
||||
|
||||
Every registered EPF driver will be listed in controllers directory. The
|
||||
entries corresponding to EPF driver will be created by the EPF core.
|
||||
|
||||
/sys/kernel/config/pci_ep/functions/
|
||||
.. <EPF Driver1>/
|
||||
... <EPF Device 11>/
|
||||
... <EPF Device 21>/
|
||||
.. <EPF Driver2>/
|
||||
... <EPF Device 12>/
|
||||
... <EPF Device 22>/
|
||||
|
||||
In order to create a <EPF device> of the type probed by <EPF Driver>, the
|
||||
user has to create a directory inside <EPF DriverN>.
|
||||
|
||||
Every <EPF device> directory consists of the following entries that can be
|
||||
used to configure the standard configuration header of the endpoint function.
|
||||
(These entries are created by the framework when any new <EPF Device> is
|
||||
created)
|
||||
|
||||
.. <EPF Driver1>/
|
||||
... <EPF Device 11>/
|
||||
... vendorid
|
||||
... deviceid
|
||||
... revid
|
||||
... progif_code
|
||||
... subclass_code
|
||||
... baseclass_code
|
||||
... cache_line_size
|
||||
... subsys_vendor_id
|
||||
... subsys_id
|
||||
... interrupt_pin
|
||||
|
||||
*) EPC Device
|
||||
|
||||
Every registered EPC device will be listed in controllers directory. The
|
||||
entries corresponding to EPC device will be created by the EPC core.
|
||||
|
||||
/sys/kernel/config/pci_ep/controllers/
|
||||
.. <EPC Device1>/
|
||||
... <Symlink EPF Device11>/
|
||||
... <Symlink EPF Device12>/
|
||||
... start
|
||||
.. <EPC Device2>/
|
||||
... <Symlink EPF Device21>/
|
||||
... <Symlink EPF Device22>/
|
||||
... start
|
||||
|
||||
The <EPC Device> directory will have a list of symbolic links to
|
||||
<EPF Device>. These symbolic links should be created by the user to
|
||||
represent the functions present in the endpoint device.
|
||||
|
||||
The <EPC Device> directory will also have a *start* field. Once
|
||||
"1" is written to this field, the endpoint device will be ready to
|
||||
establish the link with the host. This is usually done after
|
||||
all the EPF devices are created and linked with the EPC device.
|
||||
|
||||
|
||||
| controllers/
|
||||
| <Directory: EPC name>/
|
||||
| <Symbolic Link: Function>
|
||||
| start
|
||||
| functions/
|
||||
| <Directory: EPF driver>/
|
||||
| <Directory: EPF device>/
|
||||
| vendorid
|
||||
| deviceid
|
||||
| revid
|
||||
| progif_code
|
||||
| subclass_code
|
||||
| baseclass_code
|
||||
| cache_line_size
|
||||
| subsys_vendor_id
|
||||
| subsys_id
|
||||
| interrupt_pin
|
||||
| function
|
||||
|
||||
[1] -> Documentation/PCI/endpoint/pci-endpoint.txt
|
215
Documentation/PCI/endpoint/pci-endpoint.txt
Normal file
215
Documentation/PCI/endpoint/pci-endpoint.txt
Normal file
@ -0,0 +1,215 @@
|
||||
PCI ENDPOINT FRAMEWORK
|
||||
Kishon Vijay Abraham I <kishon@ti.com>
|
||||
|
||||
This document is a guide to use the PCI Endpoint Framework in order to create
|
||||
endpoint controller driver, endpoint function driver, and using configfs
|
||||
interface to bind the function driver to the controller driver.
|
||||
|
||||
1. Introduction
|
||||
|
||||
Linux has a comprehensive PCI subsystem to support PCI controllers that
|
||||
operates in Root Complex mode. The subsystem has capability to scan PCI bus,
|
||||
assign memory resources and IRQ resources, load PCI driver (based on
|
||||
vendor ID, device ID), support other services like hot-plug, power management,
|
||||
advanced error reporting and virtual channels.
|
||||
|
||||
However the PCI controller IP integrated in some SoCs is capable of operating
|
||||
either in Root Complex mode or Endpoint mode. PCI Endpoint Framework will
|
||||
add endpoint mode support in Linux. This will help to run Linux in an
|
||||
EP system which can have a wide variety of use cases from testing or
|
||||
validation, co-processor accelerator, etc.
|
||||
|
||||
2. PCI Endpoint Core
|
||||
|
||||
The PCI Endpoint Core layer comprises 3 components: the Endpoint Controller
|
||||
library, the Endpoint Function library, and the configfs layer to bind the
|
||||
endpoint function with the endpoint controller.
|
||||
|
||||
2.1 PCI Endpoint Controller(EPC) Library
|
||||
|
||||
The EPC library provides APIs to be used by the controller that can operate
|
||||
in endpoint mode. It also provides APIs to be used by function driver/library
|
||||
in order to implement a particular endpoint function.
|
||||
|
||||
2.1.1 APIs for the PCI controller Driver
|
||||
|
||||
This section lists the APIs that the PCI Endpoint core provides to be used
|
||||
by the PCI controller driver.
|
||||
|
||||
*) devm_pci_epc_create()/pci_epc_create()
|
||||
|
||||
The PCI controller driver should implement the following ops:
|
||||
* write_header: ops to populate configuration space header
|
||||
* set_bar: ops to configure the BAR
|
||||
* clear_bar: ops to reset the BAR
|
||||
* alloc_addr_space: ops to allocate in PCI controller address space
|
||||
* free_addr_space: ops to free the allocated address space
|
||||
* raise_irq: ops to raise a legacy or MSI interrupt
|
||||
* start: ops to start the PCI link
|
||||
* stop: ops to stop the PCI link
|
||||
|
||||
The PCI controller driver can then create a new EPC device by invoking
|
||||
devm_pci_epc_create()/pci_epc_create().
|
||||
|
||||
*) devm_pci_epc_destroy()/pci_epc_destroy()
|
||||
|
||||
The PCI controller driver can destroy the EPC device created by either
|
||||
devm_pci_epc_create() or pci_epc_create() using devm_pci_epc_destroy() or
|
||||
pci_epc_destroy().
|
||||
|
||||
*) pci_epc_linkup()
|
||||
|
||||
In order to notify all the function devices that the EPC device to which
|
||||
they are linked has established a link with the host, the PCI controller
|
||||
driver should invoke pci_epc_linkup().
|
||||
|
||||
*) pci_epc_mem_init()
|
||||
|
||||
Initialize the pci_epc_mem structure used for allocating EPC addr space.
|
||||
|
||||
*) pci_epc_mem_exit()
|
||||
|
||||
Cleanup the pci_epc_mem structure allocated during pci_epc_mem_init().
|
||||
|
||||
2.1.2 APIs for the PCI Endpoint Function Driver
|
||||
|
||||
This section lists the APIs that the PCI Endpoint core provides to be used
|
||||
by the PCI endpoint function driver.
|
||||
|
||||
*) pci_epc_write_header()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_write_header() to
|
||||
write the standard configuration header to the endpoint controller.
|
||||
|
||||
*) pci_epc_set_bar()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_set_bar() to configure
|
||||
the Base Address Register in order for the host to assign PCI addr space.
|
||||
Register space of the function driver is usually configured
|
||||
using this API.
|
||||
|
||||
*) pci_epc_clear_bar()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_clear_bar() to reset
|
||||
the BAR.
|
||||
|
||||
*) pci_epc_raise_irq()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_raise_irq() to raise
|
||||
Legacy Interrupt or MSI Interrupt.
|
||||
|
||||
*) pci_epc_mem_alloc_addr()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_mem_alloc_addr(), to
|
||||
allocate memory address from EPC addr space which is required to access
|
||||
RC's buffer
|
||||
|
||||
*) pci_epc_mem_free_addr()
|
||||
|
||||
The PCI endpoint function driver should use pci_epc_mem_free_addr() to
|
||||
free the memory space allocated using pci_epc_mem_alloc_addr().
|
||||
|
||||
2.1.3 Other APIs
|
||||
|
||||
There are other APIs provided by the EPC library. These are used for binding
|
||||
the EPF device with EPC device. pci-ep-cfs.c can be used as reference for
|
||||
using these APIs.
|
||||
|
||||
*) pci_epc_get()
|
||||
|
||||
Get a reference to the PCI endpoint controller based on the device name of
|
||||
the controller.
|
||||
|
||||
*) pci_epc_put()
|
||||
|
||||
Release the reference to the PCI endpoint controller obtained using
|
||||
pci_epc_get()
|
||||
|
||||
*) pci_epc_add_epf()
|
||||
|
||||
Add a PCI endpoint function to a PCI endpoint controller. A PCIe device
|
||||
can have up to 8 functions according to the specification.
|
||||
|
||||
*) pci_epc_remove_epf()
|
||||
|
||||
Remove the PCI endpoint function from PCI endpoint controller.
|
||||
|
||||
*) pci_epc_start()
|
||||
|
||||
The PCI endpoint function driver should invoke pci_epc_start() once it
|
||||
has configured the endpoint function and wants to start the PCI link.
|
||||
|
||||
*) pci_epc_stop()
|
||||
|
||||
The PCI endpoint function driver should invoke pci_epc_stop() to stop
|
||||
the PCI LINK.
|
||||
|
||||
2.2 PCI Endpoint Function(EPF) Library
|
||||
|
||||
The EPF library provides APIs to be used by the function driver and the EPC
|
||||
library to provide endpoint mode functionality.
|
||||
|
||||
2.2.1 APIs for the PCI Endpoint Function Driver
|
||||
|
||||
This section lists the APIs that the PCI Endpoint core provides to be used
|
||||
by the PCI endpoint function driver.
|
||||
|
||||
*) pci_epf_register_driver()
|
||||
|
||||
The PCI Endpoint Function driver should implement the following ops:
|
||||
* bind: ops to perform when a EPC device has been bound to EPF device
|
||||
* unbind: ops to perform when a binding has been lost between a EPC
|
||||
device and EPF device
|
||||
* linkup: ops to perform when the EPC device has established a
|
||||
connection with a host system
|
||||
|
||||
The PCI Function driver can then register the PCI EPF driver by using
|
||||
pci_epf_register_driver().
|
||||
|
||||
*) pci_epf_unregister_driver()
|
||||
|
||||
The PCI Function driver can unregister the PCI EPF driver by using
|
||||
pci_epf_unregister_driver().
|
||||
|
||||
*) pci_epf_alloc_space()
|
||||
|
||||
The PCI Function driver can allocate space for a particular BAR using
|
||||
pci_epf_alloc_space().
|
||||
|
||||
*) pci_epf_free_space()
|
||||
|
||||
The PCI Function driver can free the allocated space
|
||||
(using pci_epf_alloc_space) by invoking pci_epf_free_space().
|
||||
|
||||
2.2.2 APIs for the PCI Endpoint Controller Library
|
||||
This section lists the APIs that the PCI Endpoint core provides to be used
|
||||
by the PCI endpoint controller library.
|
||||
|
||||
*) pci_epf_linkup()
|
||||
|
||||
The PCI endpoint controller library invokes pci_epf_linkup() when the
|
||||
EPC device has established the connection to the host.
|
||||
|
||||
2.2.2 Other APIs
|
||||
There are other APIs provided by the EPF library. These are used to notify
|
||||
the function driver when the EPF device is bound to the EPC device.
|
||||
pci-ep-cfs.c can be used as reference for using these APIs.
|
||||
|
||||
*) pci_epf_create()
|
||||
|
||||
Create a new PCI EPF device by passing the name of the PCI EPF device.
|
||||
This name will be used to bind the the EPF device to a EPF driver.
|
||||
|
||||
*) pci_epf_destroy()
|
||||
|
||||
Destroy the created PCI EPF device.
|
||||
|
||||
*) pci_epf_bind()
|
||||
|
||||
pci_epf_bind() should be invoked when the EPF device has been bound to
|
||||
a EPC device.
|
||||
|
||||
*) pci_epf_unbind()
|
||||
|
||||
pci_epf_unbind() should be invoked when the binding between EPC device
|
||||
and EPF device is lost.
|
66
Documentation/PCI/endpoint/pci-test-function.txt
Normal file
66
Documentation/PCI/endpoint/pci-test-function.txt
Normal file
@ -0,0 +1,66 @@
|
||||
PCI TEST
|
||||
Kishon Vijay Abraham I <kishon@ti.com>
|
||||
|
||||
Traditionally PCI RC has always been validated by using standard
|
||||
PCI cards like ethernet PCI cards or USB PCI cards or SATA PCI cards.
|
||||
However with the addition of EP-core in linux kernel, it is possible
|
||||
to configure a PCI controller that can operate in EP mode to work as
|
||||
a test device.
|
||||
|
||||
The PCI endpoint test device is a virtual device (defined in software)
|
||||
used to test the endpoint functionality and serve as a sample driver
|
||||
for other PCI endpoint devices (to use the EP framework).
|
||||
|
||||
The PCI endpoint test device has the following registers:
|
||||
|
||||
1) PCI_ENDPOINT_TEST_MAGIC
|
||||
2) PCI_ENDPOINT_TEST_COMMAND
|
||||
3) PCI_ENDPOINT_TEST_STATUS
|
||||
4) PCI_ENDPOINT_TEST_SRC_ADDR
|
||||
5) PCI_ENDPOINT_TEST_DST_ADDR
|
||||
6) PCI_ENDPOINT_TEST_SIZE
|
||||
7) PCI_ENDPOINT_TEST_CHECKSUM
|
||||
|
||||
*) PCI_ENDPOINT_TEST_MAGIC
|
||||
|
||||
This register will be used to test BAR0. A known pattern will be written
|
||||
and read back from MAGIC register to verify BAR0.
|
||||
|
||||
*) PCI_ENDPOINT_TEST_COMMAND:
|
||||
|
||||
This register will be used by the host driver to indicate the function
|
||||
that the endpoint device must perform.
|
||||
|
||||
Bitfield Description:
|
||||
Bit 0 : raise legacy IRQ
|
||||
Bit 1 : raise MSI IRQ
|
||||
Bit 2 - 7 : MSI interrupt number
|
||||
Bit 8 : read command (read data from RC buffer)
|
||||
Bit 9 : write command (write data to RC buffer)
|
||||
Bit 10 : copy command (copy data from one RC buffer to another
|
||||
RC buffer)
|
||||
|
||||
*) PCI_ENDPOINT_TEST_STATUS
|
||||
|
||||
This register reflects the status of the PCI endpoint device.
|
||||
|
||||
Bitfield Description:
|
||||
Bit 0 : read success
|
||||
Bit 1 : read fail
|
||||
Bit 2 : write success
|
||||
Bit 3 : write fail
|
||||
Bit 4 : copy success
|
||||
Bit 5 : copy fail
|
||||
Bit 6 : IRQ raised
|
||||
Bit 7 : source address is invalid
|
||||
Bit 8 : destination address is invalid
|
||||
|
||||
*) PCI_ENDPOINT_TEST_SRC_ADDR
|
||||
|
||||
This register contains the source address (RC buffer address) for the
|
||||
COPY/READ command.
|
||||
|
||||
*) PCI_ENDPOINT_TEST_DST_ADDR
|
||||
|
||||
This register contains the destination address (RC buffer address) for
|
||||
the COPY/WRITE command.
|
179
Documentation/PCI/endpoint/pci-test-howto.txt
Normal file
179
Documentation/PCI/endpoint/pci-test-howto.txt
Normal file
@ -0,0 +1,179 @@
|
||||
PCI TEST USERGUIDE
|
||||
Kishon Vijay Abraham I <kishon@ti.com>
|
||||
|
||||
This document is a guide to help users use pci-epf-test function driver
|
||||
and pci_endpoint_test host driver for testing PCI. The list of steps to
|
||||
be followed in the host side and EP side is given below.
|
||||
|
||||
1. Endpoint Device
|
||||
|
||||
1.1 Endpoint Controller Devices
|
||||
|
||||
To find the list of endpoint controller devices in the system:
|
||||
|
||||
# ls /sys/class/pci_epc/
|
||||
51000000.pcie_ep
|
||||
|
||||
If PCI_ENDPOINT_CONFIGFS is enabled
|
||||
# ls /sys/kernel/config/pci_ep/controllers
|
||||
51000000.pcie_ep
|
||||
|
||||
1.2 Endpoint Function Drivers
|
||||
|
||||
To find the list of endpoint function drivers in the system:
|
||||
|
||||
# ls /sys/bus/pci-epf/drivers
|
||||
pci_epf_test
|
||||
|
||||
If PCI_ENDPOINT_CONFIGFS is enabled
|
||||
# ls /sys/kernel/config/pci_ep/functions
|
||||
pci_epf_test
|
||||
|
||||
1.3 Creating pci-epf-test Device
|
||||
|
||||
PCI endpoint function device can be created using the configfs. To create
|
||||
pci-epf-test device, the following commands can be used
|
||||
|
||||
# mount -t configfs none /sys/kernel/config
|
||||
# cd /sys/kernel/config/pci_ep/
|
||||
# mkdir functions/pci_epf_test/func1
|
||||
|
||||
The "mkdir func1" above creates the pci-epf-test function device that will
|
||||
be probed by pci_epf_test driver.
|
||||
|
||||
The PCI endpoint framework populates the directory with the following
|
||||
configurable fields.
|
||||
|
||||
# ls functions/pci_epf_test/func1
|
||||
baseclass_code interrupt_pin revid subsys_vendor_id
|
||||
cache_line_size msi_interrupts subclass_code vendorid
|
||||
deviceid progif_code subsys_id
|
||||
|
||||
The PCI endpoint function driver populates these entries with default values
|
||||
when the device is bound to the driver. The pci-epf-test driver populates
|
||||
vendorid with 0xffff and interrupt_pin with 0x0001
|
||||
|
||||
# cat functions/pci_epf_test/func1/vendorid
|
||||
0xffff
|
||||
# cat functions/pci_epf_test/func1/interrupt_pin
|
||||
0x0001
|
||||
|
||||
1.4 Configuring pci-epf-test Device
|
||||
|
||||
The user can configure the pci-epf-test device using configfs entry. In order
|
||||
to change the vendorid and the number of MSI interrupts used by the function
|
||||
device, the following commands can be used.
|
||||
|
||||
# echo 0x104c > functions/pci_epf_test/func1/vendorid
|
||||
# echo 0xb500 > functions/pci_epf_test/func1/deviceid
|
||||
# echo 16 > functions/pci_epf_test/func1/msi_interrupts
|
||||
|
||||
1.5 Binding pci-epf-test Device to EP Controller
|
||||
|
||||
In order for the endpoint function device to be useful, it has to be bound to
|
||||
a PCI endpoint controller driver. Use the configfs to bind the function
|
||||
device to one of the controller driver present in the system.
|
||||
|
||||
# ln -s functions/pci_epf_test/func1 controllers/51000000.pcie_ep/
|
||||
|
||||
Once the above step is completed, the PCI endpoint is ready to establish a link
|
||||
with the host.
|
||||
|
||||
1.6 Start the Link
|
||||
|
||||
In order for the endpoint device to establish a link with the host, the _start_
|
||||
field should be populated with '1'.
|
||||
|
||||
# echo 1 > controllers/51000000.pcie_ep/start
|
||||
|
||||
2. RootComplex Device
|
||||
|
||||
2.1 lspci Output
|
||||
|
||||
Note that the devices listed here correspond to the value populated in 1.4 above
|
||||
|
||||
00:00.0 PCI bridge: Texas Instruments Device 8888 (rev 01)
|
||||
01:00.0 Unassigned class [ff00]: Texas Instruments Device b500
|
||||
|
||||
2.2 Using Endpoint Test function Device
|
||||
|
||||
pcitest.sh added in tools/pci/ can be used to run all the default PCI endpoint
|
||||
tests. Before pcitest.sh can be used pcitest.c should be compiled using the
|
||||
following commands.
|
||||
|
||||
cd <kernel-dir>
|
||||
make headers_install ARCH=arm
|
||||
arm-linux-gnueabihf-gcc -Iusr/include tools/pci/pcitest.c -o pcitest
|
||||
cp pcitest <rootfs>/usr/sbin/
|
||||
cp tools/pci/pcitest.sh <rootfs>
|
||||
|
||||
2.2.1 pcitest.sh Output
|
||||
# ./pcitest.sh
|
||||
BAR tests
|
||||
|
||||
BAR0: OKAY
|
||||
BAR1: OKAY
|
||||
BAR2: OKAY
|
||||
BAR3: OKAY
|
||||
BAR4: NOT OKAY
|
||||
BAR5: NOT OKAY
|
||||
|
||||
Interrupt tests
|
||||
|
||||
LEGACY IRQ: NOT OKAY
|
||||
MSI1: OKAY
|
||||
MSI2: OKAY
|
||||
MSI3: OKAY
|
||||
MSI4: OKAY
|
||||
MSI5: OKAY
|
||||
MSI6: OKAY
|
||||
MSI7: OKAY
|
||||
MSI8: OKAY
|
||||
MSI9: OKAY
|
||||
MSI10: OKAY
|
||||
MSI11: OKAY
|
||||
MSI12: OKAY
|
||||
MSI13: OKAY
|
||||
MSI14: OKAY
|
||||
MSI15: OKAY
|
||||
MSI16: OKAY
|
||||
MSI17: NOT OKAY
|
||||
MSI18: NOT OKAY
|
||||
MSI19: NOT OKAY
|
||||
MSI20: NOT OKAY
|
||||
MSI21: NOT OKAY
|
||||
MSI22: NOT OKAY
|
||||
MSI23: NOT OKAY
|
||||
MSI24: NOT OKAY
|
||||
MSI25: NOT OKAY
|
||||
MSI26: NOT OKAY
|
||||
MSI27: NOT OKAY
|
||||
MSI28: NOT OKAY
|
||||
MSI29: NOT OKAY
|
||||
MSI30: NOT OKAY
|
||||
MSI31: NOT OKAY
|
||||
MSI32: NOT OKAY
|
||||
|
||||
Read Tests
|
||||
|
||||
READ ( 1 bytes): OKAY
|
||||
READ ( 1024 bytes): OKAY
|
||||
READ ( 1025 bytes): OKAY
|
||||
READ (1024000 bytes): OKAY
|
||||
READ (1024001 bytes): OKAY
|
||||
|
||||
Write Tests
|
||||
|
||||
WRITE ( 1 bytes): OKAY
|
||||
WRITE ( 1024 bytes): OKAY
|
||||
WRITE ( 1025 bytes): OKAY
|
||||
WRITE (1024000 bytes): OKAY
|
||||
WRITE (1024001 bytes): OKAY
|
||||
|
||||
Copy Tests
|
||||
|
||||
COPY ( 1 bytes): OKAY
|
||||
COPY ( 1024 bytes): OKAY
|
||||
COPY ( 1025 bytes): OKAY
|
||||
COPY (1024000 bytes): OKAY
|
||||
COPY (1024001 bytes): OKAY
|
@ -6,30 +6,40 @@ Required properties:
|
||||
- reg-names: Must be "config" for the PCIe configuration space.
|
||||
(The old way of getting the configuration address space from "ranges"
|
||||
is deprecated and should be avoided.)
|
||||
- num-lanes: number of lanes to use
|
||||
RC mode:
|
||||
- #address-cells: set to <3>
|
||||
- #size-cells: set to <2>
|
||||
- device_type: set to "pci"
|
||||
- ranges: ranges for the PCI memory and I/O regions
|
||||
- #interrupt-cells: set to <1>
|
||||
- interrupt-map-mask and interrupt-map: standard PCI properties
|
||||
to define the mapping of the PCIe interface to interrupt
|
||||
- interrupt-map-mask and interrupt-map: standard PCI
|
||||
properties to define the mapping of the PCIe interface to interrupt
|
||||
numbers.
|
||||
- num-lanes: number of lanes to use
|
||||
EP mode:
|
||||
- num-ib-windows: number of inbound address translation
|
||||
windows
|
||||
- num-ob-windows: number of outbound address translation
|
||||
windows
|
||||
|
||||
Optional properties:
|
||||
- num-viewport: number of view ports configured in hardware. If a platform
|
||||
does not specify it, the driver assumes 2.
|
||||
- num-lanes: number of lanes to use (this property should be specified unless
|
||||
the link is brought already up in BIOS)
|
||||
- reset-gpio: gpio pin number of power good signal
|
||||
- bus-range: PCI bus numbers covered (it is recommended for new devicetrees to
|
||||
specify this property, to keep backwards compatibility a range of 0x00-0xff
|
||||
is assumed if not present)
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
See ../clocks/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries:
|
||||
- "pcie"
|
||||
- "pcie_bus"
|
||||
RC mode:
|
||||
- num-viewport: number of view ports configured in
|
||||
hardware. If a platform does not specify it, the driver assumes 2.
|
||||
- bus-range: PCI bus numbers covered (it is recommended
|
||||
for new devicetrees to specify this property, to keep backwards
|
||||
compatibility a range of 0x00-0xff is assumed if not present)
|
||||
EP mode:
|
||||
- max-functions: maximum number of functions that can be
|
||||
configured
|
||||
|
||||
Example configuration:
|
||||
|
||||
|
@ -1,17 +1,22 @@
|
||||
TI PCI Controllers
|
||||
|
||||
PCIe Designware Controller
|
||||
- compatible: Should be "ti,dra7-pcie""
|
||||
- reg : Two register ranges as listed in the reg-names property
|
||||
- reg-names : The first entry must be "ti-conf" for the TI specific registers
|
||||
The second entry must be "rc-dbics" for the designware pcie
|
||||
registers
|
||||
The third entry must be "config" for the PCIe configuration space
|
||||
- compatible: Should be "ti,dra7-pcie" for RC
|
||||
Should be "ti,dra7-pcie-ep" for EP
|
||||
- phys : list of PHY specifiers (used by generic PHY framework)
|
||||
- phy-names : must be "pcie-phy0", "pcie-phy1", "pcie-phyN".. based on the
|
||||
number of PHYs as specified in *phys* property.
|
||||
- ti,hwmods : Name of the hwmod associated to the pcie, "pcie<X>",
|
||||
where <X> is the instance number of the pcie from the HW spec.
|
||||
- num-lanes as specified in ../designware-pcie.txt
|
||||
|
||||
HOST MODE
|
||||
=========
|
||||
- reg : Two register ranges as listed in the reg-names property
|
||||
- reg-names : The first entry must be "ti-conf" for the TI specific registers
|
||||
The second entry must be "rc-dbics" for the DesignWare PCIe
|
||||
registers
|
||||
The third entry must be "config" for the PCIe configuration space
|
||||
- interrupts : Two interrupt entries must be specified. The first one is for
|
||||
main interrupt line and the second for MSI interrupt line.
|
||||
- #address-cells,
|
||||
@ -19,13 +24,36 @@ PCIe Designware Controller
|
||||
#interrupt-cells,
|
||||
device_type,
|
||||
ranges,
|
||||
num-lanes,
|
||||
interrupt-map-mask,
|
||||
interrupt-map : as specified in ../designware-pcie.txt
|
||||
|
||||
DEVICE MODE
|
||||
===========
|
||||
- reg : Four register ranges as listed in the reg-names property
|
||||
- reg-names : "ti-conf" for the TI specific registers
|
||||
"ep_dbics" for the standard configuration registers as
|
||||
they are locally accessed within the DIF CS space
|
||||
"ep_dbics2" for the standard configuration registers as
|
||||
they are locally accessed within the DIF CS2 space
|
||||
"addr_space" used to map remote RC address space
|
||||
- interrupts : one interrupt entries must be specified for main interrupt.
|
||||
- num-ib-windows : number of inbound address translation windows
|
||||
- num-ob-windows : number of outbound address translation windows
|
||||
- ti,syscon-unaligned-access: phandle to the syscon DT node. The 1st argument
|
||||
should contain the register offset within syscon
|
||||
and the 2nd argument should contain the bit field
|
||||
for setting the bit to enable unaligned
|
||||
access.
|
||||
|
||||
Optional Property:
|
||||
- gpios : Should be added if a gpio line is required to drive PERST# line
|
||||
|
||||
NOTE: Two DT nodes may be added for each PCI controller; one for host
|
||||
mode and another for device mode. So in order for PCI to
|
||||
work in host mode, EP mode DT node should be disabled and in order to PCI to
|
||||
work in EP mode, host mode DT node should be disabled. Host mode and EP
|
||||
mode are mutually exclusive.
|
||||
|
||||
Example:
|
||||
axi {
|
||||
compatible = "simple-bus";
|
||||
|
35
Documentation/misc-devices/pci-endpoint-test.txt
Normal file
35
Documentation/misc-devices/pci-endpoint-test.txt
Normal file
@ -0,0 +1,35 @@
|
||||
Driver for PCI Endpoint Test Function
|
||||
|
||||
This driver should be used as a host side driver if the root complex is
|
||||
connected to a configurable PCI endpoint running *pci_epf_test* function
|
||||
driver configured according to [1].
|
||||
|
||||
The "pci_endpoint_test" driver can be used to perform the following tests.
|
||||
|
||||
The PCI driver for the test device performs the following tests
|
||||
*) verifying addresses programmed in BAR
|
||||
*) raise legacy IRQ
|
||||
*) raise MSI IRQ
|
||||
*) read data
|
||||
*) write data
|
||||
*) copy data
|
||||
|
||||
This misc driver creates /dev/pci-endpoint-test.<num> for every
|
||||
*pci_epf_test* function connected to the root complex and "ioctls"
|
||||
should be used to perform the above tests.
|
||||
|
||||
ioctl
|
||||
-----
|
||||
PCITEST_BAR: Tests the BAR. The number of the BAR to be tested
|
||||
should be passed as argument.
|
||||
PCITEST_LEGACY_IRQ: Tests legacy IRQ
|
||||
PCITEST_MSI: Tests message signalled interrupts. The MSI number
|
||||
to be tested should be passed as argument.
|
||||
PCITEST_WRITE: Perform write tests. The size of the buffer should be passed
|
||||
as argument.
|
||||
PCITEST_READ: Perform read tests. The size of the buffer should be passed
|
||||
as argument.
|
||||
PCITEST_COPY: Perform read tests. The size of the buffer should be passed
|
||||
as argument.
|
||||
|
||||
[1] -> Documentation/PCI/endpoint/function/binding/pci-test.txt
|
@ -9581,6 +9581,15 @@ F: include/linux/pci*
|
||||
F: arch/x86/pci/
|
||||
F: arch/x86/kernel/quirks.c
|
||||
|
||||
PCI ENDPOINT SUBSYSTEM
|
||||
M: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kishon/pci-endpoint.git
|
||||
S: Supported
|
||||
F: drivers/pci/endpoint/
|
||||
F: drivers/misc/pci_endpoint_test.c
|
||||
F: tools/pci/
|
||||
|
||||
PCI DRIVER FOR ALTERA PCIE IP
|
||||
M: Ley Foon Tan <lftan@altera.com>
|
||||
L: rfi@lists.rocketboards.org (moderated for non-subscribers)
|
||||
|
@ -524,7 +524,7 @@ static struct clockdomain pcie_7xx_clkdm = {
|
||||
.dep_bit = DRA7XX_PCIE_STATDEP_SHIFT,
|
||||
.wkdep_srcs = pcie_wkup_sleep_deps,
|
||||
.sleepdep_srcs = pcie_wkup_sleep_deps,
|
||||
.flags = CLKDM_CAN_HWSUP_SWSUP,
|
||||
.flags = CLKDM_CAN_SWSUP,
|
||||
};
|
||||
|
||||
static struct clockdomain atl_7xx_clkdm = {
|
||||
|
@ -14,7 +14,9 @@ obj-$(CONFIG_GENERIC_PHY) += phy/
|
||||
obj-$(CONFIG_PINCTRL) += pinctrl/
|
||||
obj-$(CONFIG_GPIOLIB) += gpio/
|
||||
obj-y += pwm/
|
||||
|
||||
obj-$(CONFIG_PCI) += pci/
|
||||
obj-$(CONFIG_PCI_ENDPOINT) += pci/endpoint/
|
||||
# PCI dwc controller drivers
|
||||
obj-y += pci/dwc/
|
||||
|
||||
|
@ -771,6 +771,13 @@ config PANEL_BOOT_MESSAGE
|
||||
|
||||
endif # PANEL
|
||||
|
||||
config PCI_ENDPOINT_TEST
|
||||
depends on PCI
|
||||
tristate "PCI Endpoint Test driver"
|
||||
---help---
|
||||
Enable this configuration option to enable the host side test driver
|
||||
for PCI Endpoint.
|
||||
|
||||
source "drivers/misc/c2port/Kconfig"
|
||||
source "drivers/misc/eeprom/Kconfig"
|
||||
source "drivers/misc/cb710/Kconfig"
|
||||
|
@ -54,6 +54,7 @@ obj-$(CONFIG_ECHO) += echo/
|
||||
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
|
||||
obj-$(CONFIG_CXL_BASE) += cxl/
|
||||
obj-$(CONFIG_PANEL) += panel.o
|
||||
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
|
||||
|
||||
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
|
||||
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
|
||||
|
534
drivers/misc/pci_endpoint_test.c
Normal file
534
drivers/misc/pci_endpoint_test.c
Normal file
@ -0,0 +1,534 @@
|
||||
/**
|
||||
* Host side test driver to test endpoint functionality
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_ids.h>
|
||||
|
||||
#include <linux/pci_regs.h>
|
||||
|
||||
#include <uapi/linux/pcitest.h>
|
||||
|
||||
#define DRV_MODULE_NAME "pci-endpoint-test"
|
||||
|
||||
#define PCI_ENDPOINT_TEST_MAGIC 0x0
|
||||
|
||||
#define PCI_ENDPOINT_TEST_COMMAND 0x4
|
||||
#define COMMAND_RAISE_LEGACY_IRQ BIT(0)
|
||||
#define COMMAND_RAISE_MSI_IRQ BIT(1)
|
||||
#define MSI_NUMBER_SHIFT 2
|
||||
/* 6 bits for MSI number */
|
||||
#define COMMAND_READ BIT(8)
|
||||
#define COMMAND_WRITE BIT(9)
|
||||
#define COMMAND_COPY BIT(10)
|
||||
|
||||
#define PCI_ENDPOINT_TEST_STATUS 0x8
|
||||
#define STATUS_READ_SUCCESS BIT(0)
|
||||
#define STATUS_READ_FAIL BIT(1)
|
||||
#define STATUS_WRITE_SUCCESS BIT(2)
|
||||
#define STATUS_WRITE_FAIL BIT(3)
|
||||
#define STATUS_COPY_SUCCESS BIT(4)
|
||||
#define STATUS_COPY_FAIL BIT(5)
|
||||
#define STATUS_IRQ_RAISED BIT(6)
|
||||
#define STATUS_SRC_ADDR_INVALID BIT(7)
|
||||
#define STATUS_DST_ADDR_INVALID BIT(8)
|
||||
|
||||
#define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR 0xc
|
||||
#define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR 0x10
|
||||
|
||||
#define PCI_ENDPOINT_TEST_LOWER_DST_ADDR 0x14
|
||||
#define PCI_ENDPOINT_TEST_UPPER_DST_ADDR 0x18
|
||||
|
||||
#define PCI_ENDPOINT_TEST_SIZE 0x1c
|
||||
#define PCI_ENDPOINT_TEST_CHECKSUM 0x20
|
||||
|
||||
static DEFINE_IDA(pci_endpoint_test_ida);
|
||||
|
||||
#define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \
|
||||
miscdev)
|
||||
enum pci_barno {
|
||||
BAR_0,
|
||||
BAR_1,
|
||||
BAR_2,
|
||||
BAR_3,
|
||||
BAR_4,
|
||||
BAR_5,
|
||||
};
|
||||
|
||||
struct pci_endpoint_test {
|
||||
struct pci_dev *pdev;
|
||||
void __iomem *base;
|
||||
void __iomem *bar[6];
|
||||
struct completion irq_raised;
|
||||
int last_irq;
|
||||
/* mutex to protect the ioctls */
|
||||
struct mutex mutex;
|
||||
struct miscdevice miscdev;
|
||||
};
|
||||
|
||||
static int bar_size[] = { 4, 512, 1024, 16384, 131072, 1048576 };
|
||||
|
||||
static inline u32 pci_endpoint_test_readl(struct pci_endpoint_test *test,
|
||||
u32 offset)
|
||||
{
|
||||
return readl(test->base + offset);
|
||||
}
|
||||
|
||||
static inline void pci_endpoint_test_writel(struct pci_endpoint_test *test,
|
||||
u32 offset, u32 value)
|
||||
{
|
||||
writel(value, test->base + offset);
|
||||
}
|
||||
|
||||
static inline u32 pci_endpoint_test_bar_readl(struct pci_endpoint_test *test,
|
||||
int bar, int offset)
|
||||
{
|
||||
return readl(test->bar[bar] + offset);
|
||||
}
|
||||
|
||||
static inline void pci_endpoint_test_bar_writel(struct pci_endpoint_test *test,
|
||||
int bar, u32 offset, u32 value)
|
||||
{
|
||||
writel(value, test->bar[bar] + offset);
|
||||
}
|
||||
|
||||
static irqreturn_t pci_endpoint_test_irqhandler(int irq, void *dev_id)
|
||||
{
|
||||
struct pci_endpoint_test *test = dev_id;
|
||||
u32 reg;
|
||||
|
||||
reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
|
||||
if (reg & STATUS_IRQ_RAISED) {
|
||||
test->last_irq = irq;
|
||||
complete(&test->irq_raised);
|
||||
reg &= ~STATUS_IRQ_RAISED;
|
||||
}
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS,
|
||||
reg);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_bar(struct pci_endpoint_test *test,
|
||||
enum pci_barno barno)
|
||||
{
|
||||
int j;
|
||||
u32 val;
|
||||
int size;
|
||||
|
||||
if (!test->bar[barno])
|
||||
return false;
|
||||
|
||||
size = bar_size[barno];
|
||||
|
||||
for (j = 0; j < size; j += 4)
|
||||
pci_endpoint_test_bar_writel(test, barno, j, 0xA0A0A0A0);
|
||||
|
||||
for (j = 0; j < size; j += 4) {
|
||||
val = pci_endpoint_test_bar_readl(test, barno, j);
|
||||
if (val != 0xA0A0A0A0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_legacy_irq(struct pci_endpoint_test *test)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
||||
COMMAND_RAISE_LEGACY_IRQ);
|
||||
val = wait_for_completion_timeout(&test->irq_raised,
|
||||
msecs_to_jiffies(1000));
|
||||
if (!val)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_msi_irq(struct pci_endpoint_test *test,
|
||||
u8 msi_num)
|
||||
{
|
||||
u32 val;
|
||||
struct pci_dev *pdev = test->pdev;
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
||||
msi_num << MSI_NUMBER_SHIFT |
|
||||
COMMAND_RAISE_MSI_IRQ);
|
||||
val = wait_for_completion_timeout(&test->irq_raised,
|
||||
msecs_to_jiffies(1000));
|
||||
if (!val)
|
||||
return false;
|
||||
|
||||
if (test->last_irq - pdev->irq == msi_num - 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_copy(struct pci_endpoint_test *test, size_t size)
|
||||
{
|
||||
bool ret = false;
|
||||
void *src_addr;
|
||||
void *dst_addr;
|
||||
dma_addr_t src_phys_addr;
|
||||
dma_addr_t dst_phys_addr;
|
||||
struct pci_dev *pdev = test->pdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
u32 src_crc32;
|
||||
u32 dst_crc32;
|
||||
|
||||
src_addr = dma_alloc_coherent(dev, size, &src_phys_addr, GFP_KERNEL);
|
||||
if (!src_addr) {
|
||||
dev_err(dev, "failed to allocate source buffer\n");
|
||||
ret = false;
|
||||
goto err;
|
||||
}
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_SRC_ADDR,
|
||||
lower_32_bits(src_phys_addr));
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_SRC_ADDR,
|
||||
upper_32_bits(src_phys_addr));
|
||||
|
||||
get_random_bytes(src_addr, size);
|
||||
src_crc32 = crc32_le(~0, src_addr, size);
|
||||
|
||||
dst_addr = dma_alloc_coherent(dev, size, &dst_phys_addr, GFP_KERNEL);
|
||||
if (!dst_addr) {
|
||||
dev_err(dev, "failed to allocate destination address\n");
|
||||
ret = false;
|
||||
goto err_src_addr;
|
||||
}
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_DST_ADDR,
|
||||
lower_32_bits(dst_phys_addr));
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_DST_ADDR,
|
||||
upper_32_bits(dst_phys_addr));
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE,
|
||||
size);
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
||||
1 << MSI_NUMBER_SHIFT | COMMAND_COPY);
|
||||
|
||||
wait_for_completion(&test->irq_raised);
|
||||
|
||||
dst_crc32 = crc32_le(~0, dst_addr, size);
|
||||
if (dst_crc32 == src_crc32)
|
||||
ret = true;
|
||||
|
||||
dma_free_coherent(dev, size, dst_addr, dst_phys_addr);
|
||||
|
||||
err_src_addr:
|
||||
dma_free_coherent(dev, size, src_addr, src_phys_addr);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_write(struct pci_endpoint_test *test, size_t size)
|
||||
{
|
||||
bool ret = false;
|
||||
u32 reg;
|
||||
void *addr;
|
||||
dma_addr_t phys_addr;
|
||||
struct pci_dev *pdev = test->pdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
u32 crc32;
|
||||
|
||||
addr = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
|
||||
if (!addr) {
|
||||
dev_err(dev, "failed to allocate address\n");
|
||||
ret = false;
|
||||
goto err;
|
||||
}
|
||||
|
||||
get_random_bytes(addr, size);
|
||||
|
||||
crc32 = crc32_le(~0, addr, size);
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_CHECKSUM,
|
||||
crc32);
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_SRC_ADDR,
|
||||
lower_32_bits(phys_addr));
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_SRC_ADDR,
|
||||
upper_32_bits(phys_addr));
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
||||
1 << MSI_NUMBER_SHIFT | COMMAND_READ);
|
||||
|
||||
wait_for_completion(&test->irq_raised);
|
||||
|
||||
reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
|
||||
if (reg & STATUS_READ_SUCCESS)
|
||||
ret = true;
|
||||
|
||||
dma_free_coherent(dev, size, addr, phys_addr);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool pci_endpoint_test_read(struct pci_endpoint_test *test, size_t size)
|
||||
{
|
||||
bool ret = false;
|
||||
void *addr;
|
||||
dma_addr_t phys_addr;
|
||||
struct pci_dev *pdev = test->pdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
u32 crc32;
|
||||
|
||||
addr = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
|
||||
if (!addr) {
|
||||
dev_err(dev, "failed to allocate destination address\n");
|
||||
ret = false;
|
||||
goto err;
|
||||
}
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_LOWER_DST_ADDR,
|
||||
lower_32_bits(phys_addr));
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_UPPER_DST_ADDR,
|
||||
upper_32_bits(phys_addr));
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
|
||||
1 << MSI_NUMBER_SHIFT | COMMAND_WRITE);
|
||||
|
||||
wait_for_completion(&test->irq_raised);
|
||||
|
||||
crc32 = crc32_le(~0, addr, size);
|
||||
if (crc32 == pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_CHECKSUM))
|
||||
ret = true;
|
||||
|
||||
dma_free_coherent(dev, size, addr, phys_addr);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
enum pci_barno bar;
|
||||
struct pci_endpoint_test *test = to_endpoint_test(file->private_data);
|
||||
|
||||
mutex_lock(&test->mutex);
|
||||
switch (cmd) {
|
||||
case PCITEST_BAR:
|
||||
bar = arg;
|
||||
if (bar < 0 || bar > 5)
|
||||
goto ret;
|
||||
ret = pci_endpoint_test_bar(test, bar);
|
||||
break;
|
||||
case PCITEST_LEGACY_IRQ:
|
||||
ret = pci_endpoint_test_legacy_irq(test);
|
||||
break;
|
||||
case PCITEST_MSI:
|
||||
ret = pci_endpoint_test_msi_irq(test, arg);
|
||||
break;
|
||||
case PCITEST_WRITE:
|
||||
ret = pci_endpoint_test_write(test, arg);
|
||||
break;
|
||||
case PCITEST_READ:
|
||||
ret = pci_endpoint_test_read(test, arg);
|
||||
break;
|
||||
case PCITEST_COPY:
|
||||
ret = pci_endpoint_test_copy(test, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
ret:
|
||||
mutex_unlock(&test->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations pci_endpoint_test_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = pci_endpoint_test_ioctl,
|
||||
};
|
||||
|
||||
static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
int irq;
|
||||
int id;
|
||||
char name[20];
|
||||
enum pci_barno bar;
|
||||
void __iomem *base;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pci_endpoint_test *test;
|
||||
struct miscdevice *misc_device;
|
||||
|
||||
if (pci_is_bridge(pdev))
|
||||
return -ENODEV;
|
||||
|
||||
test = devm_kzalloc(dev, sizeof(*test), GFP_KERNEL);
|
||||
if (!test)
|
||||
return -ENOMEM;
|
||||
|
||||
test->pdev = pdev;
|
||||
init_completion(&test->irq_raised);
|
||||
mutex_init(&test->mutex);
|
||||
|
||||
err = pci_enable_device(pdev);
|
||||
if (err) {
|
||||
dev_err(dev, "Cannot enable PCI device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = pci_request_regions(pdev, DRV_MODULE_NAME);
|
||||
if (err) {
|
||||
dev_err(dev, "Cannot obtain PCI resources\n");
|
||||
goto err_disable_pdev;
|
||||
}
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI);
|
||||
if (irq < 0)
|
||||
dev_err(dev, "failed to get MSI interrupts\n");
|
||||
|
||||
err = devm_request_irq(dev, pdev->irq, pci_endpoint_test_irqhandler,
|
||||
IRQF_SHARED, DRV_MODULE_NAME, test);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to request IRQ %d\n", pdev->irq);
|
||||
goto err_disable_msi;
|
||||
}
|
||||
|
||||
for (i = 1; i < irq; i++) {
|
||||
err = devm_request_irq(dev, pdev->irq + i,
|
||||
pci_endpoint_test_irqhandler,
|
||||
IRQF_SHARED, DRV_MODULE_NAME, test);
|
||||
if (err)
|
||||
dev_err(dev, "failed to request IRQ %d for MSI %d\n",
|
||||
pdev->irq + i, i + 1);
|
||||
}
|
||||
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
base = pci_ioremap_bar(pdev, bar);
|
||||
if (!base) {
|
||||
dev_err(dev, "failed to read BAR%d\n", bar);
|
||||
WARN_ON(bar == BAR_0);
|
||||
}
|
||||
test->bar[bar] = base;
|
||||
}
|
||||
|
||||
test->base = test->bar[0];
|
||||
if (!test->base) {
|
||||
dev_err(dev, "Cannot perform PCI test without BAR0\n");
|
||||
goto err_iounmap;
|
||||
}
|
||||
|
||||
pci_set_drvdata(pdev, test);
|
||||
|
||||
id = ida_simple_get(&pci_endpoint_test_ida, 0, 0, GFP_KERNEL);
|
||||
if (id < 0) {
|
||||
dev_err(dev, "unable to get id\n");
|
||||
goto err_iounmap;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
|
||||
misc_device = &test->miscdev;
|
||||
misc_device->minor = MISC_DYNAMIC_MINOR;
|
||||
misc_device->name = name;
|
||||
misc_device->fops = &pci_endpoint_test_fops,
|
||||
|
||||
err = misc_register(misc_device);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to register device\n");
|
||||
goto err_ida_remove;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_ida_remove:
|
||||
ida_simple_remove(&pci_endpoint_test_ida, id);
|
||||
|
||||
err_iounmap:
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
if (test->bar[bar])
|
||||
pci_iounmap(pdev, test->bar[bar]);
|
||||
}
|
||||
|
||||
err_disable_msi:
|
||||
pci_disable_msi(pdev);
|
||||
pci_release_regions(pdev);
|
||||
|
||||
err_disable_pdev:
|
||||
pci_disable_device(pdev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void pci_endpoint_test_remove(struct pci_dev *pdev)
|
||||
{
|
||||
int id;
|
||||
enum pci_barno bar;
|
||||
struct pci_endpoint_test *test = pci_get_drvdata(pdev);
|
||||
struct miscdevice *misc_device = &test->miscdev;
|
||||
|
||||
if (sscanf(misc_device->name, DRV_MODULE_NAME ".%d", &id) != 1)
|
||||
return;
|
||||
|
||||
misc_deregister(&test->miscdev);
|
||||
ida_simple_remove(&pci_endpoint_test_ida, id);
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
if (test->bar[bar])
|
||||
pci_iounmap(pdev, test->bar[bar]);
|
||||
}
|
||||
pci_disable_msi(pdev);
|
||||
pci_release_regions(pdev);
|
||||
pci_disable_device(pdev);
|
||||
}
|
||||
|
||||
static const struct pci_device_id pci_endpoint_test_tbl[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA74x) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA72x) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
|
||||
|
||||
static struct pci_driver pci_endpoint_test_driver = {
|
||||
.name = DRV_MODULE_NAME,
|
||||
.id_table = pci_endpoint_test_tbl,
|
||||
.probe = pci_endpoint_test_probe,
|
||||
.remove = pci_endpoint_test_remove,
|
||||
};
|
||||
module_pci_driver(pci_endpoint_test_driver);
|
||||
|
||||
MODULE_DESCRIPTION("PCI ENDPOINT TEST HOST DRIVER");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -134,3 +134,4 @@ config PCI_HYPERV
|
||||
source "drivers/pci/hotplug/Kconfig"
|
||||
source "drivers/pci/dwc/Kconfig"
|
||||
source "drivers/pci/host/Kconfig"
|
||||
source "drivers/pci/endpoint/Kconfig"
|
||||
|
@ -9,16 +9,44 @@ config PCIE_DW_HOST
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIE_DW
|
||||
|
||||
config PCIE_DW_EP
|
||||
bool
|
||||
depends on PCI_ENDPOINT
|
||||
select PCIE_DW
|
||||
|
||||
config PCI_DRA7XX
|
||||
bool "TI DRA7xx PCIe controller"
|
||||
depends on PCI
|
||||
depends on (PCI && PCI_MSI_IRQ_DOMAIN) || PCI_ENDPOINT
|
||||
depends on OF && HAS_IOMEM && TI_PIPE3
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIE_DW_HOST
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC. There
|
||||
are two instances of PCIe controller in DRA7xx. This controller can
|
||||
act both as EP and RC. This reuses the Designware core.
|
||||
work either as EP or RC. In order to enable host-specific features
|
||||
PCI_DRA7XX_HOST must be selected and in order to enable device-
|
||||
specific features PCI_DRA7XX_EP must be selected. This uses
|
||||
the Designware core.
|
||||
|
||||
if PCI_DRA7XX
|
||||
|
||||
config PCI_DRA7XX_HOST
|
||||
bool "PCI DRA7xx Host Mode"
|
||||
depends on PCI
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIE_DW_HOST
|
||||
default y
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC to work in
|
||||
host mode.
|
||||
|
||||
config PCI_DRA7XX_EP
|
||||
bool "PCI DRA7xx Endpoint Mode"
|
||||
depends on PCI_ENDPOINT
|
||||
select PCIE_DW_EP
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC to work in
|
||||
endpoint mode.
|
||||
|
||||
endif
|
||||
|
||||
config PCIE_DW_PLAT
|
||||
bool "Platform bus based DesignWare PCIe Controller"
|
||||
@ -89,6 +117,7 @@ config PCI_HISI
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIEPORTBUS
|
||||
select PCIE_DW_HOST
|
||||
select PCI_HOST_COMMON
|
||||
help
|
||||
Say Y here if you want PCIe controller support on HiSilicon
|
||||
Hip05 and Hip06 SoCs
|
||||
|
@ -1,7 +1,10 @@
|
||||
obj-$(CONFIG_PCIE_DW) += pcie-designware.o
|
||||
obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
|
||||
obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
|
||||
obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
|
||||
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
|
||||
ifneq ($(filter y,$(CONFIG_PCI_DRA7XX_HOST) $(CONFIG_PCI_DRA7XX_EP)),)
|
||||
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
|
||||
endif
|
||||
obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
|
||||
obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
|
||||
obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
|
||||
|
@ -10,12 +10,14 @@
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
@ -24,6 +26,8 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/resource.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
@ -57,6 +61,11 @@
|
||||
#define MSI BIT(4)
|
||||
#define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD)
|
||||
|
||||
#define PCIECTRL_TI_CONF_DEVICE_TYPE 0x0100
|
||||
#define DEVICE_TYPE_EP 0x0
|
||||
#define DEVICE_TYPE_LEG_EP 0x1
|
||||
#define DEVICE_TYPE_RC 0x4
|
||||
|
||||
#define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104
|
||||
#define LTSSM_EN 0x1
|
||||
|
||||
@ -66,6 +75,13 @@
|
||||
|
||||
#define EXP_CAP_ID_OFFSET 0x70
|
||||
|
||||
#define PCIECTRL_TI_CONF_INTX_ASSERT 0x0124
|
||||
#define PCIECTRL_TI_CONF_INTX_DEASSERT 0x0128
|
||||
|
||||
#define PCIECTRL_TI_CONF_MSI_XMT 0x012c
|
||||
#define MSI_REQ_GRANT BIT(0)
|
||||
#define MSI_VECTOR_SHIFT 7
|
||||
|
||||
struct dra7xx_pcie {
|
||||
struct dw_pcie *pci;
|
||||
void __iomem *base; /* DT ti_conf */
|
||||
@ -73,6 +89,11 @@ struct dra7xx_pcie {
|
||||
struct phy **phy;
|
||||
int link_gen;
|
||||
struct irq_domain *irq_domain;
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
struct dra7xx_pcie_of_data {
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
#define to_dra7xx_pcie(x) dev_get_drvdata((x)->dev)
|
||||
@ -88,6 +109,11 @@ static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset,
|
||||
writel(value, pcie->base + offset);
|
||||
}
|
||||
|
||||
static u64 dra7xx_pcie_cpu_addr_fixup(u64 pci_addr)
|
||||
{
|
||||
return pci_addr & DRA7XX_CPU_TO_BUS_ADDR;
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_link_up(struct dw_pcie *pci)
|
||||
{
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
@ -96,9 +122,19 @@ static int dra7xx_pcie_link_up(struct dw_pcie *pci)
|
||||
return !!(reg & LINK_UP);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx)
|
||||
static void dra7xx_pcie_stop_link(struct dw_pcie *pci)
|
||||
{
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
u32 reg;
|
||||
|
||||
reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD);
|
||||
reg &= ~LTSSM_EN;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_establish_link(struct dw_pcie *pci)
|
||||
{
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
struct device *dev = pci->dev;
|
||||
u32 reg;
|
||||
u32 exp_cap_off = EXP_CAP_ID_OFFSET;
|
||||
@ -132,19 +168,31 @@ static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx)
|
||||
reg |= LTSSM_EN;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg);
|
||||
|
||||
return dw_pcie_wait_for_link(pci);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_msi_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI,
|
||||
~LEG_EP_INTERRUPTS & ~MSI);
|
||||
|
||||
dra7xx_pcie_writel(dra7xx,
|
||||
PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI,
|
||||
MSI | LEG_EP_INTERRUPTS);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_wrapper_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN,
|
||||
~INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN,
|
||||
INTERRUPTS);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN,
|
||||
~INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx,
|
||||
PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN, INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI,
|
||||
~LEG_EP_INTERRUPTS & ~MSI);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI,
|
||||
MSI | LEG_EP_INTERRUPTS);
|
||||
dra7xx_pcie_enable_wrapper_interrupts(dra7xx);
|
||||
dra7xx_pcie_enable_msi_interrupts(dra7xx);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_host_init(struct pcie_port *pp)
|
||||
@ -152,14 +200,10 @@ static void dra7xx_pcie_host_init(struct pcie_port *pp)
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
pp->io_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->mem_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->cfg0_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->cfg1_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
|
||||
dw_pcie_setup_rc(pp);
|
||||
|
||||
dra7xx_pcie_establish_link(dra7xx);
|
||||
dra7xx_pcie_establish_link(pci);
|
||||
dw_pcie_wait_for_link(pci);
|
||||
dw_pcie_msi_init(pp);
|
||||
dra7xx_pcie_enable_interrupts(dra7xx);
|
||||
}
|
||||
@ -237,6 +281,7 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
struct dra7xx_pcie *dra7xx = arg;
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
struct device *dev = pci->dev;
|
||||
struct dw_pcie_ep *ep = &pci->ep;
|
||||
u32 reg;
|
||||
|
||||
reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN);
|
||||
@ -273,8 +318,11 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
if (reg & LINK_REQ_RST)
|
||||
dev_dbg(dev, "Link Request Reset\n");
|
||||
|
||||
if (reg & LINK_UP_EVT)
|
||||
if (reg & LINK_UP_EVT) {
|
||||
if (dra7xx->mode == DW_PCIE_EP_TYPE)
|
||||
dw_pcie_ep_linkup(ep);
|
||||
dev_dbg(dev, "Link-up state change\n");
|
||||
}
|
||||
|
||||
if (reg & CFG_BME_EVT)
|
||||
dev_dbg(dev, "CFG 'Bus Master Enable' change\n");
|
||||
@ -287,6 +335,94 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
dra7xx_pcie_enable_wrapper_interrupts(dra7xx);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_raise_legacy_irq(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_ASSERT, 0x1);
|
||||
mdelay(1);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_DEASSERT, 0x1);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_raise_msi_irq(struct dra7xx_pcie *dra7xx,
|
||||
u8 interrupt_num)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = (interrupt_num - 1) << MSI_VECTOR_SHIFT;
|
||||
reg |= MSI_REQ_GRANT;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_MSI_XMT, reg);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_raise_irq(struct dw_pcie_ep *ep,
|
||||
enum pci_epc_irq_type type, u8 interrupt_num)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
switch (type) {
|
||||
case PCI_EPC_IRQ_LEGACY:
|
||||
dra7xx_pcie_raise_legacy_irq(dra7xx);
|
||||
break;
|
||||
case PCI_EPC_IRQ_MSI:
|
||||
dra7xx_pcie_raise_msi_irq(dra7xx, interrupt_num);
|
||||
break;
|
||||
default:
|
||||
dev_err(pci->dev, "UNKNOWN IRQ type\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dw_pcie_ep_ops pcie_ep_ops = {
|
||||
.ep_init = dra7xx_pcie_ep_init,
|
||||
.raise_irq = dra7xx_pcie_raise_irq,
|
||||
};
|
||||
|
||||
static int __init dra7xx_add_pcie_ep(struct dra7xx_pcie *dra7xx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
|
||||
ep = &pci->ep;
|
||||
ep->ops = &pcie_ep_ops;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics");
|
||||
pci->dbi_base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!pci->dbi_base)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics2");
|
||||
pci->dbi_base2 = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!pci->dbi_base2)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
ep->phys_base = res->start;
|
||||
ep->addr_size = resource_size(res);
|
||||
|
||||
ret = dw_pcie_ep_init(ep);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to initialize endpoint\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
@ -329,6 +465,9 @@ static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx,
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.cpu_addr_fixup = dra7xx_pcie_cpu_addr_fixup,
|
||||
.start_link = dra7xx_pcie_establish_link,
|
||||
.stop_link = dra7xx_pcie_stop_link,
|
||||
.link_up = dra7xx_pcie_link_up,
|
||||
};
|
||||
|
||||
@ -371,6 +510,68 @@ err_phy:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dra7xx_pcie_of_data dra7xx_pcie_rc_of_data = {
|
||||
.mode = DW_PCIE_RC_TYPE,
|
||||
};
|
||||
|
||||
static const struct dra7xx_pcie_of_data dra7xx_pcie_ep_of_data = {
|
||||
.mode = DW_PCIE_EP_TYPE,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_dra7xx_pcie_match[] = {
|
||||
{
|
||||
.compatible = "ti,dra7-pcie",
|
||||
.data = &dra7xx_pcie_rc_of_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,dra7-pcie-ep",
|
||||
.data = &dra7xx_pcie_ep_of_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
/*
|
||||
* dra7xx_pcie_ep_unaligned_memaccess: workaround for AM572x/AM571x Errata i870
|
||||
* @dra7xx: the dra7xx device where the workaround should be applied
|
||||
*
|
||||
* Access to the PCIe slave port that are not 32-bit aligned will result
|
||||
* in incorrect mapping to TLP Address and Byte enable fields. Therefore,
|
||||
* byte and half-word accesses are not possible to byte offset 0x1, 0x2, or
|
||||
* 0x3.
|
||||
*
|
||||
* To avoid this issue set PCIE_SS1_AXI2OCP_LEGACY_MODE_ENABLE to 1.
|
||||
*/
|
||||
static int dra7xx_pcie_ep_unaligned_memaccess(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct of_phandle_args args;
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = syscon_regmap_lookup_by_phandle(np,
|
||||
"ti,syscon-unaligned-access");
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_dbg(dev, "can't get ti,syscon-unaligned-access\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_parse_phandle_with_fixed_args(np, "ti,syscon-unaligned-access",
|
||||
2, 0, &args);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to parse ti,syscon-unaligned-access\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(regmap, args.args[0], args.args[1],
|
||||
args.args[1]);
|
||||
if (ret)
|
||||
dev_err(dev, "failed to enable unaligned access\n");
|
||||
|
||||
of_node_put(args.np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
u32 reg;
|
||||
@ -388,6 +589,16 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
struct device_node *np = dev->of_node;
|
||||
char name[10];
|
||||
struct gpio_desc *reset;
|
||||
const struct of_device_id *match;
|
||||
const struct dra7xx_pcie_of_data *data;
|
||||
enum dw_pcie_device_mode mode;
|
||||
|
||||
match = of_match_device(of_match_ptr(of_dra7xx_pcie_match), dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
data = (struct dra7xx_pcie_of_data *)match->data;
|
||||
mode = (enum dw_pcie_device_mode)data->mode;
|
||||
|
||||
dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL);
|
||||
if (!dra7xx)
|
||||
@ -409,13 +620,6 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler,
|
||||
IRQF_SHARED, "dra7xx-pcie-main", dra7xx);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ti_conf");
|
||||
base = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
if (!base)
|
||||
@ -473,9 +677,37 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
if (dra7xx->link_gen < 0 || dra7xx->link_gen > 2)
|
||||
dra7xx->link_gen = 2;
|
||||
|
||||
switch (mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE,
|
||||
DEVICE_TYPE_RC);
|
||||
ret = dra7xx_add_pcie_port(dra7xx, pdev);
|
||||
if (ret < 0)
|
||||
goto err_gpio;
|
||||
break;
|
||||
case DW_PCIE_EP_TYPE:
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE,
|
||||
DEVICE_TYPE_EP);
|
||||
|
||||
ret = dra7xx_pcie_ep_unaligned_memaccess(dev);
|
||||
if (ret)
|
||||
goto err_gpio;
|
||||
|
||||
ret = dra7xx_add_pcie_ep(dra7xx, pdev);
|
||||
if (ret < 0)
|
||||
goto err_gpio;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "INVALID device type %d\n", mode);
|
||||
}
|
||||
dra7xx->mode = mode;
|
||||
|
||||
ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler,
|
||||
IRQF_SHARED, "dra7xx-pcie-main", dra7xx);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
goto err_gpio;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@ -496,6 +728,9 @@ static int dra7xx_pcie_suspend(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* clear MSE */
|
||||
val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
|
||||
val &= ~PCI_COMMAND_MEMORY;
|
||||
@ -510,6 +745,9 @@ static int dra7xx_pcie_resume(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* set MSE */
|
||||
val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
|
||||
val |= PCI_COMMAND_MEMORY;
|
||||
@ -548,11 +786,6 @@ static const struct dev_pm_ops dra7xx_pcie_pm_ops = {
|
||||
dra7xx_pcie_resume_noirq)
|
||||
};
|
||||
|
||||
static const struct of_device_id of_dra7xx_pcie_match[] = {
|
||||
{ .compatible = "ti,dra7-pcie", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver dra7xx_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "dra7-pcie",
|
||||
|
@ -132,10 +132,6 @@ static int exynos5440_pcie_get_mem_resources(struct platform_device *pdev,
|
||||
struct device *dev = pci->dev;
|
||||
struct resource *res;
|
||||
|
||||
/* If using the PHY framework, doesn't need to get other resource */
|
||||
if (ep->using_phy)
|
||||
return 0;
|
||||
|
||||
ep->mem_res = devm_kzalloc(dev, sizeof(*ep->mem_res), GFP_KERNEL);
|
||||
if (!ep->mem_res)
|
||||
return -ENOMEM;
|
||||
@ -145,6 +141,10 @@ static int exynos5440_pcie_get_mem_resources(struct platform_device *pdev,
|
||||
if (IS_ERR(ep->mem_res->elbi_base))
|
||||
return PTR_ERR(ep->mem_res->elbi_base);
|
||||
|
||||
/* If using the PHY framework, doesn't need to get other resource */
|
||||
if (ep->using_phy)
|
||||
return 0;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
ep->mem_res->phy_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(ep->mem_res->phy_base))
|
||||
@ -521,23 +521,25 @@ static void exynos_pcie_enable_interrupts(struct exynos_pcie *ep)
|
||||
exynos_pcie_msi_init(ep);
|
||||
}
|
||||
|
||||
static u32 exynos_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
static u32 exynos_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base,
|
||||
u32 reg, size_t size)
|
||||
{
|
||||
struct exynos_pcie *ep = to_exynos_pcie(pci);
|
||||
u32 val;
|
||||
|
||||
exynos_pcie_sideband_dbi_r_mode(ep, true);
|
||||
val = readl(pci->dbi_base + reg);
|
||||
dw_pcie_read(base + reg, size, &val);
|
||||
exynos_pcie_sideband_dbi_r_mode(ep, false);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void exynos_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
static void exynos_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base,
|
||||
u32 reg, size_t size, u32 val)
|
||||
{
|
||||
struct exynos_pcie *ep = to_exynos_pcie(pci);
|
||||
|
||||
exynos_pcie_sideband_dbi_w_mode(ep, true);
|
||||
writel(val, pci->dbi_base + reg);
|
||||
dw_pcie_write(base + reg, size, val);
|
||||
exynos_pcie_sideband_dbi_w_mode(ep, false);
|
||||
}
|
||||
|
||||
@ -644,8 +646,8 @@ static int __init exynos_add_pcie_port(struct exynos_pcie *ep,
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.readl_dbi = exynos_pcie_readl_dbi,
|
||||
.writel_dbi = exynos_pcie_writel_dbi,
|
||||
.read_dbi = exynos_pcie_read_dbi,
|
||||
.write_dbi = exynos_pcie_write_dbi,
|
||||
.link_up = exynos_pcie_link_up,
|
||||
};
|
||||
|
||||
|
@ -78,6 +78,11 @@ static void artpec6_pcie_writel(struct artpec6_pcie *artpec6_pcie, u32 offset, u
|
||||
regmap_write(artpec6_pcie->regmap, offset, val);
|
||||
}
|
||||
|
||||
static u64 artpec6_pcie_cpu_addr_fixup(u64 pci_addr)
|
||||
{
|
||||
return pci_addr & ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
}
|
||||
|
||||
static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie)
|
||||
{
|
||||
struct dw_pcie *pci = artpec6_pcie->pci;
|
||||
@ -142,11 +147,6 @@ static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie)
|
||||
*/
|
||||
dw_pcie_writel_dbi(pci, MISC_CONTROL_1_OFF, DBI_RO_WR_EN);
|
||||
|
||||
pp->io_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->mem_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->cfg0_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->cfg1_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
|
||||
/* setup root complex */
|
||||
dw_pcie_setup_rc(pp);
|
||||
|
||||
@ -234,6 +234,10 @@ static int artpec6_add_pcie_port(struct artpec6_pcie *artpec6_pcie,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.cpu_addr_fixup = artpec6_pcie_cpu_addr_fixup,
|
||||
};
|
||||
|
||||
static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@ -252,6 +256,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
pci->dev = dev;
|
||||
pci->ops = &dw_pcie_ops;
|
||||
|
||||
artpec6_pcie->pci = pci;
|
||||
|
||||
|
342
drivers/pci/dwc/pcie-designware-ep.c
Normal file
342
drivers/pci/dwc/pcie-designware-ep.c
Normal file
@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Synopsys Designware PCIe Endpoint controller driver
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
|
||||
void dw_pcie_ep_linkup(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct pci_epc *epc = ep->epc;
|
||||
|
||||
pci_epc_linkup(epc);
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = PCI_BASE_ADDRESS_0 + (4 * bar);
|
||||
dw_pcie_writel_dbi2(pci, reg, 0x0);
|
||||
dw_pcie_writel_dbi(pci, reg, 0x0);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_write_header(struct pci_epc *epc,
|
||||
struct pci_epf_header *hdr)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, hdr->vendorid);
|
||||
dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, hdr->deviceid);
|
||||
dw_pcie_writeb_dbi(pci, PCI_REVISION_ID, hdr->revid);
|
||||
dw_pcie_writeb_dbi(pci, PCI_CLASS_PROG, hdr->progif_code);
|
||||
dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE,
|
||||
hdr->subclass_code | hdr->baseclass_code << 8);
|
||||
dw_pcie_writeb_dbi(pci, PCI_CACHE_LINE_SIZE,
|
||||
hdr->cache_line_size);
|
||||
dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_VENDOR_ID,
|
||||
hdr->subsys_vendor_id);
|
||||
dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_ID, hdr->subsys_id);
|
||||
dw_pcie_writeb_dbi(pci, PCI_INTERRUPT_PIN,
|
||||
hdr->interrupt_pin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, enum pci_barno bar,
|
||||
dma_addr_t cpu_addr,
|
||||
enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int ret;
|
||||
u32 free_win;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
free_win = find_first_zero_bit(&ep->ib_window_map,
|
||||
sizeof(ep->ib_window_map));
|
||||
if (free_win >= ep->num_ib_windows) {
|
||||
dev_err(pci->dev, "no free inbound window\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = dw_pcie_prog_inbound_atu(pci, free_win, bar, cpu_addr,
|
||||
as_type);
|
||||
if (ret < 0) {
|
||||
dev_err(pci->dev, "Failed to program IB window\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ep->bar_to_atu[bar] = free_win;
|
||||
set_bit(free_win, &ep->ib_window_map);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, phys_addr_t phys_addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
u32 free_win;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
free_win = find_first_zero_bit(&ep->ob_window_map,
|
||||
sizeof(ep->ob_window_map));
|
||||
if (free_win >= ep->num_ob_windows) {
|
||||
dev_err(pci->dev, "no free outbound window\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_prog_outbound_atu(pci, free_win, PCIE_ATU_TYPE_MEM,
|
||||
phys_addr, pci_addr, size);
|
||||
|
||||
set_bit(free_win, &ep->ob_window_map);
|
||||
ep->outbound_addr[free_win] = phys_addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_clear_bar(struct pci_epc *epc, enum pci_barno bar)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
u32 atu_index = ep->bar_to_atu[bar];
|
||||
|
||||
dw_pcie_ep_reset_bar(pci, bar);
|
||||
|
||||
dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_INBOUND);
|
||||
clear_bit(atu_index, &ep->ib_window_map);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_set_bar(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
enum dw_pcie_as_type as_type;
|
||||
u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar);
|
||||
|
||||
if (!(flags & PCI_BASE_ADDRESS_SPACE))
|
||||
as_type = DW_PCIE_AS_MEM;
|
||||
else
|
||||
as_type = DW_PCIE_AS_IO;
|
||||
|
||||
ret = dw_pcie_ep_inbound_atu(ep, bar, bar_phys, as_type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dw_pcie_writel_dbi2(pci, reg, size - 1);
|
||||
dw_pcie_writel_dbi(pci, reg, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_find_index(struct dw_pcie_ep *ep, phys_addr_t addr,
|
||||
u32 *atu_index)
|
||||
{
|
||||
u32 index;
|
||||
|
||||
for (index = 0; index < ep->num_ob_windows; index++) {
|
||||
if (ep->outbound_addr[index] != addr)
|
||||
continue;
|
||||
*atu_index = index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_unmap_addr(struct pci_epc *epc, phys_addr_t addr)
|
||||
{
|
||||
int ret;
|
||||
u32 atu_index;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
ret = dw_pcie_find_index(ep, addr, &atu_index);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_OUTBOUND);
|
||||
clear_bit(atu_index, &ep->ob_window_map);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_map_addr(struct pci_epc *epc, phys_addr_t addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
ret = dw_pcie_ep_outbound_atu(ep, addr, pci_addr, size);
|
||||
if (ret) {
|
||||
dev_err(pci->dev, "failed to enable address\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_get_msi(struct pci_epc *epc)
|
||||
{
|
||||
int val;
|
||||
u32 lower_addr;
|
||||
u32 upper_addr;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
val = dw_pcie_readb_dbi(pci, MSI_MESSAGE_CONTROL);
|
||||
val = (val & MSI_CAP_MME_MASK) >> MSI_CAP_MME_SHIFT;
|
||||
|
||||
lower_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_L32);
|
||||
upper_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_U32);
|
||||
|
||||
if (!(lower_addr || upper_addr))
|
||||
return -EINVAL;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 encode_int)
|
||||
{
|
||||
int val;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
val = (encode_int << MSI_CAP_MMC_SHIFT);
|
||||
dw_pcie_writew_dbi(pci, MSI_MESSAGE_CONTROL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_raise_irq(struct pci_epc *epc,
|
||||
enum pci_epc_irq_type type, u8 interrupt_num)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
|
||||
if (!ep->ops->raise_irq)
|
||||
return -EINVAL;
|
||||
|
||||
return ep->ops->raise_irq(ep, type, interrupt_num);
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_stop(struct pci_epc *epc)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
if (!pci->ops->stop_link)
|
||||
return;
|
||||
|
||||
pci->ops->stop_link(pci);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_start(struct pci_epc *epc)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
if (!pci->ops->start_link)
|
||||
return -EINVAL;
|
||||
|
||||
return pci->ops->start_link(pci);
|
||||
}
|
||||
|
||||
static const struct pci_epc_ops epc_ops = {
|
||||
.write_header = dw_pcie_ep_write_header,
|
||||
.set_bar = dw_pcie_ep_set_bar,
|
||||
.clear_bar = dw_pcie_ep_clear_bar,
|
||||
.map_addr = dw_pcie_ep_map_addr,
|
||||
.unmap_addr = dw_pcie_ep_unmap_addr,
|
||||
.set_msi = dw_pcie_ep_set_msi,
|
||||
.get_msi = dw_pcie_ep_get_msi,
|
||||
.raise_irq = dw_pcie_ep_raise_irq,
|
||||
.start = dw_pcie_ep_start,
|
||||
.stop = dw_pcie_ep_stop,
|
||||
};
|
||||
|
||||
void dw_pcie_ep_exit(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct pci_epc *epc = ep->epc;
|
||||
|
||||
pci_epc_mem_exit(epc);
|
||||
}
|
||||
|
||||
int dw_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
int ret;
|
||||
void *addr;
|
||||
enum pci_barno bar;
|
||||
struct pci_epc *epc;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct device *dev = pci->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
if (!pci->dbi_base || !pci->dbi_base2) {
|
||||
dev_err(dev, "dbi_base/deb_base2 is not populated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-ib-windows", &ep->num_ib_windows);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to read *num-ib-windows* property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-ob-windows", &ep->num_ob_windows);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to read *num-ob-windows* property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
addr = devm_kzalloc(dev, sizeof(phys_addr_t) * ep->num_ob_windows,
|
||||
GFP_KERNEL);
|
||||
if (!addr)
|
||||
return -ENOMEM;
|
||||
ep->outbound_addr = addr;
|
||||
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++)
|
||||
dw_pcie_ep_reset_bar(pci, bar);
|
||||
|
||||
if (ep->ops->ep_init)
|
||||
ep->ops->ep_init(ep);
|
||||
|
||||
epc = devm_pci_epc_create(dev, &epc_ops);
|
||||
if (IS_ERR(epc)) {
|
||||
dev_err(dev, "failed to create epc device\n");
|
||||
return PTR_ERR(epc);
|
||||
}
|
||||
|
||||
ret = of_property_read_u8(np, "max-functions", &epc->max_functions);
|
||||
if (ret < 0)
|
||||
epc->max_functions = 1;
|
||||
|
||||
ret = pci_epc_mem_init(epc, ep->phys_base, ep->addr_size);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to initialize address space\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ep->epc = epc;
|
||||
epc_set_drvdata(epc, ep);
|
||||
dw_pcie_setup(pci);
|
||||
|
||||
return 0;
|
||||
}
|
@ -56,26 +56,27 @@ static struct irq_chip dw_msi_irq_chip = {
|
||||
/* MSI int handler */
|
||||
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp)
|
||||
{
|
||||
unsigned long val;
|
||||
u32 val;
|
||||
int i, pos, irq;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
for (i = 0; i < MAX_MSI_CTRLS; i++) {
|
||||
dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4,
|
||||
(u32 *)&val);
|
||||
if (val) {
|
||||
&val);
|
||||
if (!val)
|
||||
continue;
|
||||
|
||||
ret = IRQ_HANDLED;
|
||||
pos = 0;
|
||||
while ((pos = find_next_bit(&val, 32, pos)) != 32) {
|
||||
irq = irq_find_mapping(pp->irq_domain,
|
||||
i * 32 + pos);
|
||||
dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS +
|
||||
i * 12, 4, 1 << pos);
|
||||
while ((pos = find_next_bit((unsigned long *) &val, 32,
|
||||
pos)) != 32) {
|
||||
irq = irq_find_mapping(pp->irq_domain, i * 32 + pos);
|
||||
dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12,
|
||||
4, 1 << pos);
|
||||
generic_handle_irq(irq);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -86,6 +86,9 @@ static int dw_plat_add_pcie_port(struct pcie_port *pp,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
};
|
||||
|
||||
static int dw_plat_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@ -103,6 +106,7 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
pci->dev = dev;
|
||||
pci->ops = &dw_pcie_ops;
|
||||
|
||||
dw_plat_pcie->pci = pci;
|
||||
|
||||
|
@ -61,30 +61,45 @@ int dw_pcie_write(void __iomem *addr, int size, u32 val)
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size)
|
||||
{
|
||||
if (pci->ops->readl_dbi)
|
||||
return pci->ops->readl_dbi(pci, reg);
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
return readl(pci->dbi_base + reg);
|
||||
if (pci->ops->read_dbi)
|
||||
return pci->ops->read_dbi(pci, base, reg, size);
|
||||
|
||||
ret = dw_pcie_read(base + reg, size, &val);
|
||||
if (ret)
|
||||
dev_err(pci->dev, "read DBI address failed\n");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val)
|
||||
{
|
||||
if (pci->ops->writel_dbi)
|
||||
pci->ops->writel_dbi(pci, reg, val);
|
||||
else
|
||||
writel(val, pci->dbi_base + reg);
|
||||
int ret;
|
||||
|
||||
if (pci->ops->write_dbi) {
|
||||
pci->ops->write_dbi(pci, base, reg, size, val);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = dw_pcie_write(base + reg, size, val);
|
||||
if (ret)
|
||||
dev_err(pci->dev, "write DBI address failed\n");
|
||||
}
|
||||
|
||||
static u32 dw_pcie_readl_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
|
||||
|
||||
return dw_pcie_readl_dbi(pci, offset + reg);
|
||||
}
|
||||
|
||||
static void dw_pcie_writel_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
u32 val)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
|
||||
@ -92,27 +107,55 @@ static void dw_pcie_writel_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
dw_pcie_writel_dbi(pci, offset + reg, val);
|
||||
}
|
||||
|
||||
void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, int index, int type,
|
||||
u64 cpu_addr, u64 pci_addr, u32 size)
|
||||
{
|
||||
u32 retries, val;
|
||||
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1,
|
||||
type);
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE);
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_ob_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "outbound iATU is not being enabled\n");
|
||||
}
|
||||
|
||||
void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type,
|
||||
u64 cpu_addr, u64 pci_addr, u32 size)
|
||||
{
|
||||
u32 retries, val;
|
||||
|
||||
if (pci->ops->cpu_addr_fixup)
|
||||
cpu_addr = pci->ops->cpu_addr_fixup(cpu_addr);
|
||||
|
||||
if (pci->iatu_unroll_enabled) {
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1,
|
||||
type);
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE);
|
||||
} else {
|
||||
dw_pcie_prog_outbound_atu_unroll(pci, index, type, cpu_addr,
|
||||
pci_addr, size);
|
||||
return;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
|
||||
PCIE_ATU_REGION_OUTBOUND | index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
|
||||
@ -127,25 +170,144 @@ void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
if (pci->iatu_unroll_enabled)
|
||||
val = dw_pcie_readl_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
else
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
|
||||
|
||||
if (val == PCIE_ATU_ENABLE)
|
||||
return;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "iATU is not being enabled\n");
|
||||
dev_err(pci->dev, "outbound iATU is not being enabled\n");
|
||||
}
|
||||
|
||||
static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
|
||||
|
||||
return dw_pcie_readl_dbi(pci, offset + reg);
|
||||
}
|
||||
|
||||
static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
u32 val)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
|
||||
|
||||
dw_pcie_writel_dbi(pci, offset + reg, val);
|
||||
}
|
||||
|
||||
int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int type;
|
||||
u32 retries, val;
|
||||
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(cpu_addr));
|
||||
|
||||
switch (as_type) {
|
||||
case DW_PCIE_AS_MEM:
|
||||
type = PCIE_ATU_TYPE_MEM;
|
||||
break;
|
||||
case DW_PCIE_AS_IO:
|
||||
type = PCIE_ATU_TYPE_IO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type);
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE |
|
||||
PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_ib_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return 0;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "inbound iATU is not being enabled\n");
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int type;
|
||||
u32 retries, val;
|
||||
|
||||
if (pci->iatu_unroll_enabled)
|
||||
return dw_pcie_prog_inbound_atu_unroll(pci, index, bar,
|
||||
cpu_addr, as_type);
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
|
||||
index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));
|
||||
|
||||
switch (as_type) {
|
||||
case DW_PCIE_AS_MEM:
|
||||
type = PCIE_ATU_TYPE_MEM;
|
||||
break;
|
||||
case DW_PCIE_AS_IO:
|
||||
type = PCIE_ATU_TYPE_IO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE
|
||||
| PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return 0;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "inbound iATU is not being enabled\n");
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
|
||||
enum dw_pcie_region_type type)
|
||||
{
|
||||
int region;
|
||||
|
||||
switch (type) {
|
||||
case DW_PCIE_REGION_INBOUND:
|
||||
region = PCIE_ATU_REGION_INBOUND;
|
||||
break;
|
||||
case DW_PCIE_REGION_OUTBOUND:
|
||||
region = PCIE_ATU_REGION_OUTBOUND;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~PCIE_ATU_ENABLE);
|
||||
}
|
||||
|
||||
int dw_pcie_wait_for_link(struct dw_pcie *pci)
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include <linux/msi.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
|
||||
/* Parameters for the waiting for link up routine */
|
||||
#define LINK_WAIT_MAX_RETRIES 10
|
||||
#define LINK_WAIT_USLEEP_MIN 90000
|
||||
@ -89,6 +92,16 @@
|
||||
#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \
|
||||
((0x3 << 20) | ((region) << 9))
|
||||
|
||||
#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \
|
||||
((0x3 << 20) | ((region) << 9) | (0x1 << 8))
|
||||
|
||||
#define MSI_MESSAGE_CONTROL 0x52
|
||||
#define MSI_CAP_MMC_SHIFT 1
|
||||
#define MSI_CAP_MME_SHIFT 4
|
||||
#define MSI_CAP_MME_MASK (7 << MSI_CAP_MME_SHIFT)
|
||||
#define MSI_MESSAGE_ADDR_L32 0x54
|
||||
#define MSI_MESSAGE_ADDR_U32 0x58
|
||||
|
||||
/*
|
||||
* Maximum number of MSI IRQs can be 256 per controller. But keep
|
||||
* it 32 as of now. Probably we will never need more than 32. If needed,
|
||||
@ -99,6 +112,20 @@
|
||||
|
||||
struct pcie_port;
|
||||
struct dw_pcie;
|
||||
struct dw_pcie_ep;
|
||||
|
||||
enum dw_pcie_region_type {
|
||||
DW_PCIE_REGION_UNKNOWN,
|
||||
DW_PCIE_REGION_INBOUND,
|
||||
DW_PCIE_REGION_OUTBOUND,
|
||||
};
|
||||
|
||||
enum dw_pcie_device_mode {
|
||||
DW_PCIE_UNKNOWN_TYPE,
|
||||
DW_PCIE_EP_TYPE,
|
||||
DW_PCIE_LEG_EP_TYPE,
|
||||
DW_PCIE_RC_TYPE,
|
||||
};
|
||||
|
||||
struct dw_pcie_host_ops {
|
||||
int (*rd_own_conf)(struct pcie_port *pp, int where, int size, u32 *val);
|
||||
@ -142,35 +169,116 @@ struct pcie_port {
|
||||
DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
|
||||
};
|
||||
|
||||
enum dw_pcie_as_type {
|
||||
DW_PCIE_AS_UNKNOWN,
|
||||
DW_PCIE_AS_MEM,
|
||||
DW_PCIE_AS_IO,
|
||||
};
|
||||
|
||||
struct dw_pcie_ep_ops {
|
||||
void (*ep_init)(struct dw_pcie_ep *ep);
|
||||
int (*raise_irq)(struct dw_pcie_ep *ep, enum pci_epc_irq_type type,
|
||||
u8 interrupt_num);
|
||||
};
|
||||
|
||||
struct dw_pcie_ep {
|
||||
struct pci_epc *epc;
|
||||
struct dw_pcie_ep_ops *ops;
|
||||
phys_addr_t phys_base;
|
||||
size_t addr_size;
|
||||
u8 bar_to_atu[6];
|
||||
phys_addr_t *outbound_addr;
|
||||
unsigned long ib_window_map;
|
||||
unsigned long ob_window_map;
|
||||
u32 num_ib_windows;
|
||||
u32 num_ob_windows;
|
||||
};
|
||||
|
||||
struct dw_pcie_ops {
|
||||
u32 (*readl_dbi)(struct dw_pcie *pcie, u32 reg);
|
||||
void (*writel_dbi)(struct dw_pcie *pcie, u32 reg, u32 val);
|
||||
u64 (*cpu_addr_fixup)(u64 cpu_addr);
|
||||
u32 (*read_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg,
|
||||
size_t size);
|
||||
void (*write_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val);
|
||||
int (*link_up)(struct dw_pcie *pcie);
|
||||
int (*start_link)(struct dw_pcie *pcie);
|
||||
void (*stop_link)(struct dw_pcie *pcie);
|
||||
};
|
||||
|
||||
struct dw_pcie {
|
||||
struct device *dev;
|
||||
void __iomem *dbi_base;
|
||||
void __iomem *dbi_base2;
|
||||
u32 num_viewport;
|
||||
u8 iatu_unroll_enabled;
|
||||
struct pcie_port pp;
|
||||
struct dw_pcie_ep ep;
|
||||
const struct dw_pcie_ops *ops;
|
||||
};
|
||||
|
||||
#define to_dw_pcie_from_pp(port) container_of((port), struct dw_pcie, pp)
|
||||
|
||||
#define to_dw_pcie_from_ep(endpoint) \
|
||||
container_of((endpoint), struct dw_pcie, ep)
|
||||
|
||||
int dw_pcie_read(void __iomem *addr, int size, u32 *val);
|
||||
int dw_pcie_write(void __iomem *addr, int size, u32 val);
|
||||
|
||||
u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg);
|
||||
void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val);
|
||||
u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size);
|
||||
void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val);
|
||||
int dw_pcie_link_up(struct dw_pcie *pci);
|
||||
int dw_pcie_wait_for_link(struct dw_pcie *pci);
|
||||
void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
|
||||
int type, u64 cpu_addr, u64 pci_addr,
|
||||
u32 size);
|
||||
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type);
|
||||
void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
|
||||
enum dw_pcie_region_type type);
|
||||
void dw_pcie_setup(struct dw_pcie *pci);
|
||||
|
||||
static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x4, val);
|
||||
}
|
||||
|
||||
static inline u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x4);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writew_dbi(struct dw_pcie *pci, u32 reg, u16 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x2, val);
|
||||
}
|
||||
|
||||
static inline u16 dw_pcie_readw_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x2);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writeb_dbi(struct dw_pcie *pci, u32 reg, u8 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x1, val);
|
||||
}
|
||||
|
||||
static inline u8 dw_pcie_readb_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x1);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writel_dbi2(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base2, reg, 0x4, val);
|
||||
}
|
||||
|
||||
static inline u32 dw_pcie_readl_dbi2(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base2, reg, 0x4);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCIE_DW_HOST
|
||||
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp);
|
||||
void dw_pcie_msi_init(struct pcie_port *pp);
|
||||
@ -195,4 +303,23 @@ static inline int dw_pcie_host_init(struct pcie_port *pp)
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIE_DW_EP
|
||||
void dw_pcie_ep_linkup(struct dw_pcie_ep *ep);
|
||||
int dw_pcie_ep_init(struct dw_pcie_ep *ep);
|
||||
void dw_pcie_ep_exit(struct dw_pcie_ep *ep);
|
||||
#else
|
||||
static inline void dw_pcie_ep_linkup(struct dw_pcie_ep *ep)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int dw_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dw_pcie_ep_exit(struct dw_pcie_ep *ep)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#endif /* _PCIE_DESIGNWARE_H */
|
||||
|
31
drivers/pci/endpoint/Kconfig
Normal file
31
drivers/pci/endpoint/Kconfig
Normal file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# PCI Endpoint Support
|
||||
#
|
||||
|
||||
menu "PCI Endpoint"
|
||||
|
||||
config PCI_ENDPOINT
|
||||
bool "PCI Endpoint Support"
|
||||
help
|
||||
Enable this configuration option to support configurable PCI
|
||||
endpoint. This should be enabled if the platform has a PCI
|
||||
controller that can operate in endpoint mode.
|
||||
|
||||
Enabling this option will build the endpoint library, which
|
||||
includes endpoint controller library and endpoint function
|
||||
library.
|
||||
|
||||
If in doubt, say "N" to disable Endpoint support.
|
||||
|
||||
config PCI_ENDPOINT_CONFIGFS
|
||||
bool "PCI Endpoint Configfs Support"
|
||||
depends on PCI_ENDPOINT
|
||||
select CONFIGFS_FS
|
||||
help
|
||||
This will enable the configfs entry that can be used to
|
||||
configure the endpoint function and used to bind the
|
||||
function with a endpoint controller.
|
||||
|
||||
source "drivers/pci/endpoint/functions/Kconfig"
|
||||
|
||||
endmenu
|
7
drivers/pci/endpoint/Makefile
Normal file
7
drivers/pci/endpoint/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
# Makefile for PCI Endpoint Support
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o
|
||||
obj-$(CONFIG_PCI_ENDPOINT) += pci-epc-core.o pci-epf-core.o\
|
||||
pci-epc-mem.o functions/
|
12
drivers/pci/endpoint/functions/Kconfig
Normal file
12
drivers/pci/endpoint/functions/Kconfig
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# PCI Endpoint Functions
|
||||
#
|
||||
|
||||
config PCI_EPF_TEST
|
||||
tristate "PCI Endpoint Test driver"
|
||||
depends on PCI_ENDPOINT
|
||||
help
|
||||
Enable this configuration option to enable the test driver
|
||||
for PCI Endpoint.
|
||||
|
||||
If in doubt, say "N" to disable Endpoint test driver.
|
5
drivers/pci/endpoint/functions/Makefile
Normal file
5
drivers/pci/endpoint/functions/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for PCI Endpoint Functions
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o
|
510
drivers/pci/endpoint/functions/pci-epf-test.c
Normal file
510
drivers/pci/endpoint/functions/pci-epf-test.c
Normal file
@ -0,0 +1,510 @@
|
||||
/**
|
||||
* Test driver to test endpoint functionality
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci_regs.h>
|
||||
|
||||
#define COMMAND_RAISE_LEGACY_IRQ BIT(0)
|
||||
#define COMMAND_RAISE_MSI_IRQ BIT(1)
|
||||
#define MSI_NUMBER_SHIFT 2
|
||||
#define MSI_NUMBER_MASK (0x3f << MSI_NUMBER_SHIFT)
|
||||
#define COMMAND_READ BIT(8)
|
||||
#define COMMAND_WRITE BIT(9)
|
||||
#define COMMAND_COPY BIT(10)
|
||||
|
||||
#define STATUS_READ_SUCCESS BIT(0)
|
||||
#define STATUS_READ_FAIL BIT(1)
|
||||
#define STATUS_WRITE_SUCCESS BIT(2)
|
||||
#define STATUS_WRITE_FAIL BIT(3)
|
||||
#define STATUS_COPY_SUCCESS BIT(4)
|
||||
#define STATUS_COPY_FAIL BIT(5)
|
||||
#define STATUS_IRQ_RAISED BIT(6)
|
||||
#define STATUS_SRC_ADDR_INVALID BIT(7)
|
||||
#define STATUS_DST_ADDR_INVALID BIT(8)
|
||||
|
||||
#define TIMER_RESOLUTION 1
|
||||
|
||||
static struct workqueue_struct *kpcitest_workqueue;
|
||||
|
||||
struct pci_epf_test {
|
||||
void *reg[6];
|
||||
struct pci_epf *epf;
|
||||
struct delayed_work cmd_handler;
|
||||
};
|
||||
|
||||
struct pci_epf_test_reg {
|
||||
u32 magic;
|
||||
u32 command;
|
||||
u32 status;
|
||||
u64 src_addr;
|
||||
u64 dst_addr;
|
||||
u32 size;
|
||||
u32 checksum;
|
||||
} __packed;
|
||||
|
||||
static struct pci_epf_header test_header = {
|
||||
.vendorid = PCI_ANY_ID,
|
||||
.deviceid = PCI_ANY_ID,
|
||||
.baseclass_code = PCI_CLASS_OTHERS,
|
||||
.interrupt_pin = PCI_INTERRUPT_INTA,
|
||||
};
|
||||
|
||||
static int bar_size[] = { 512, 1024, 16384, 131072, 1048576 };
|
||||
|
||||
static int pci_epf_test_copy(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *src_addr;
|
||||
void __iomem *dst_addr;
|
||||
phys_addr_t src_phys_addr;
|
||||
phys_addr_t dst_phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size);
|
||||
if (!src_addr) {
|
||||
dev_err(dev, "failed to allocate source address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, src_phys_addr, reg->src_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map source address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
goto err_src_addr;
|
||||
}
|
||||
|
||||
dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size);
|
||||
if (!dst_addr) {
|
||||
dev_err(dev, "failed to allocate destination address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err_src_map_addr;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, dst_phys_addr, reg->dst_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map destination address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
goto err_dst_addr;
|
||||
}
|
||||
|
||||
memcpy(dst_addr, src_addr, reg->size);
|
||||
|
||||
pci_epc_unmap_addr(epc, dst_phys_addr);
|
||||
|
||||
err_dst_addr:
|
||||
pci_epc_mem_free_addr(epc, dst_phys_addr, dst_addr, reg->size);
|
||||
|
||||
err_src_map_addr:
|
||||
pci_epc_unmap_addr(epc, src_phys_addr);
|
||||
|
||||
err_src_addr:
|
||||
pci_epc_mem_free_addr(epc, src_phys_addr, src_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_test_read(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *src_addr;
|
||||
void *buf;
|
||||
u32 crc32;
|
||||
phys_addr_t phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||
if (!src_addr) {
|
||||
dev_err(dev, "failed to allocate address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, phys_addr, reg->src_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
goto err_addr;
|
||||
}
|
||||
|
||||
buf = kzalloc(reg->size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_map_addr;
|
||||
}
|
||||
|
||||
memcpy(buf, src_addr, reg->size);
|
||||
|
||||
crc32 = crc32_le(~0, buf, reg->size);
|
||||
if (crc32 != reg->checksum)
|
||||
ret = -EIO;
|
||||
|
||||
kfree(buf);
|
||||
|
||||
err_map_addr:
|
||||
pci_epc_unmap_addr(epc, phys_addr);
|
||||
|
||||
err_addr:
|
||||
pci_epc_mem_free_addr(epc, phys_addr, src_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_test_write(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *dst_addr;
|
||||
void *buf;
|
||||
phys_addr_t phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||
if (!dst_addr) {
|
||||
dev_err(dev, "failed to allocate address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, phys_addr, reg->dst_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
goto err_addr;
|
||||
}
|
||||
|
||||
buf = kzalloc(reg->size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_map_addr;
|
||||
}
|
||||
|
||||
get_random_bytes(buf, reg->size);
|
||||
reg->checksum = crc32_le(~0, buf, reg->size);
|
||||
|
||||
memcpy(dst_addr, buf, reg->size);
|
||||
|
||||
/*
|
||||
* wait 1ms inorder for the write to complete. Without this delay L3
|
||||
* error in observed in the host system.
|
||||
*/
|
||||
mdelay(1);
|
||||
|
||||
kfree(buf);
|
||||
|
||||
err_map_addr:
|
||||
pci_epc_unmap_addr(epc, phys_addr);
|
||||
|
||||
err_addr:
|
||||
pci_epc_mem_free_addr(epc, phys_addr, dst_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test)
|
||||
{
|
||||
u8 irq;
|
||||
u8 msi_count;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
reg->status |= STATUS_IRQ_RAISED;
|
||||
msi_count = pci_epc_get_msi(epc);
|
||||
irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
|
||||
if (irq > msi_count || msi_count <= 0)
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
|
||||
else
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
|
||||
}
|
||||
|
||||
static void pci_epf_test_cmd_handler(struct work_struct *work)
|
||||
{
|
||||
int ret;
|
||||
u8 irq;
|
||||
u8 msi_count;
|
||||
struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test,
|
||||
cmd_handler.work);
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
if (!reg->command)
|
||||
goto reset_handler;
|
||||
|
||||
if (reg->command & COMMAND_RAISE_LEGACY_IRQ) {
|
||||
reg->status = STATUS_IRQ_RAISED;
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_WRITE) {
|
||||
ret = pci_epf_test_write(epf_test);
|
||||
if (ret)
|
||||
reg->status |= STATUS_WRITE_FAIL;
|
||||
else
|
||||
reg->status |= STATUS_WRITE_SUCCESS;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_READ) {
|
||||
ret = pci_epf_test_read(epf_test);
|
||||
if (!ret)
|
||||
reg->status |= STATUS_READ_SUCCESS;
|
||||
else
|
||||
reg->status |= STATUS_READ_FAIL;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_COPY) {
|
||||
ret = pci_epf_test_copy(epf_test);
|
||||
if (!ret)
|
||||
reg->status |= STATUS_COPY_SUCCESS;
|
||||
else
|
||||
reg->status |= STATUS_COPY_FAIL;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_RAISE_MSI_IRQ) {
|
||||
msi_count = pci_epc_get_msi(epc);
|
||||
irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
|
||||
if (irq > msi_count || msi_count <= 0)
|
||||
goto reset_handler;
|
||||
reg->status = STATUS_IRQ_RAISED;
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
reset_handler:
|
||||
reg->command = 0;
|
||||
|
||||
queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
|
||||
msecs_to_jiffies(1));
|
||||
}
|
||||
|
||||
static void pci_epf_test_linkup(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
|
||||
msecs_to_jiffies(1));
|
||||
}
|
||||
|
||||
static void pci_epf_test_unbind(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
struct pci_epc *epc = epf->epc;
|
||||
int bar;
|
||||
|
||||
cancel_delayed_work(&epf_test->cmd_handler);
|
||||
pci_epc_stop(epc);
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
if (epf_test->reg[bar]) {
|
||||
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
||||
pci_epc_clear_bar(epc, bar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pci_epf_test_set_bar(struct pci_epf *epf)
|
||||
{
|
||||
int flags;
|
||||
int bar;
|
||||
int ret;
|
||||
struct pci_epf_bar *epf_bar;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
flags = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_32;
|
||||
if (sizeof(dma_addr_t) == 0x8)
|
||||
flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
|
||||
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
epf_bar = &epf->bar[bar];
|
||||
ret = pci_epc_set_bar(epc, bar, epf_bar->phys_addr,
|
||||
epf_bar->size, flags);
|
||||
if (ret) {
|
||||
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
||||
dev_err(dev, "failed to set BAR%d\n", bar);
|
||||
if (bar == BAR_0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_alloc_space(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
struct device *dev = &epf->dev;
|
||||
void *base;
|
||||
int bar;
|
||||
|
||||
base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg),
|
||||
BAR_0);
|
||||
if (!base) {
|
||||
dev_err(dev, "failed to allocated register space\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
epf_test->reg[0] = base;
|
||||
|
||||
for (bar = BAR_1; bar <= BAR_5; bar++) {
|
||||
base = pci_epf_alloc_space(epf, bar_size[bar - 1], bar);
|
||||
if (!base)
|
||||
dev_err(dev, "failed to allocate space for BAR%d\n",
|
||||
bar);
|
||||
epf_test->reg[bar] = base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_bind(struct pci_epf *epf)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf_header *header = epf->header;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
if (WARN_ON_ONCE(!epc))
|
||||
return -EINVAL;
|
||||
|
||||
ret = pci_epc_write_header(epc, header);
|
||||
if (ret) {
|
||||
dev_err(dev, "configuration header write failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pci_epf_test_alloc_space(epf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_epf_test_set_bar(epf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_epc_set_msi(epc, epf->msi_interrupts);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_probe(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test;
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
epf_test = devm_kzalloc(dev, sizeof(*epf_test), GFP_KERNEL);
|
||||
if (!epf_test)
|
||||
return -ENOMEM;
|
||||
|
||||
epf->header = &test_header;
|
||||
epf_test->epf = epf;
|
||||
|
||||
INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler);
|
||||
|
||||
epf_set_drvdata(epf, epf_test);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_remove(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
kfree(epf_test);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_epf_ops ops = {
|
||||
.unbind = pci_epf_test_unbind,
|
||||
.bind = pci_epf_test_bind,
|
||||
.linkup = pci_epf_test_linkup,
|
||||
};
|
||||
|
||||
static const struct pci_epf_device_id pci_epf_test_ids[] = {
|
||||
{
|
||||
.name = "pci_epf_test",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct pci_epf_driver test_driver = {
|
||||
.driver.name = "pci_epf_test",
|
||||
.probe = pci_epf_test_probe,
|
||||
.remove = pci_epf_test_remove,
|
||||
.id_table = pci_epf_test_ids,
|
||||
.ops = &ops,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init pci_epf_test_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
kpcitest_workqueue = alloc_workqueue("kpcitest",
|
||||
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
|
||||
ret = pci_epf_register_driver(&test_driver);
|
||||
if (ret) {
|
||||
pr_err("failed to register pci epf test driver --> %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epf_test_init);
|
||||
|
||||
static void __exit pci_epf_test_exit(void)
|
||||
{
|
||||
pci_epf_unregister_driver(&test_driver);
|
||||
}
|
||||
module_exit(pci_epf_test_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPF TEST DRIVER");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
509
drivers/pci/endpoint/pci-ep-cfs.c
Normal file
509
drivers/pci/endpoint/pci-ep-cfs.c
Normal file
@ -0,0 +1,509 @@
|
||||
/**
|
||||
* configfs to configure the PCI endpoint
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct config_group *functions_group;
|
||||
static struct config_group *controllers_group;
|
||||
|
||||
struct pci_epf_group {
|
||||
struct config_group group;
|
||||
struct pci_epf *epf;
|
||||
};
|
||||
|
||||
struct pci_epc_group {
|
||||
struct config_group group;
|
||||
struct pci_epc *epc;
|
||||
bool start;
|
||||
unsigned long function_num_map;
|
||||
};
|
||||
|
||||
static inline struct pci_epf_group *to_pci_epf_group(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct pci_epf_group, group);
|
||||
}
|
||||
|
||||
static inline struct pci_epc_group *to_pci_epc_group(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct pci_epc_group, group);
|
||||
}
|
||||
|
||||
static ssize_t pci_epc_start_store(struct config_item *item, const char *page,
|
||||
size_t len)
|
||||
{
|
||||
int ret;
|
||||
bool start;
|
||||
struct pci_epc *epc;
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(item);
|
||||
|
||||
epc = epc_group->epc;
|
||||
|
||||
ret = kstrtobool(page, &start);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!start) {
|
||||
pci_epc_stop(epc);
|
||||
return len;
|
||||
}
|
||||
|
||||
ret = pci_epc_start(epc);
|
||||
if (ret) {
|
||||
dev_err(&epc->dev, "failed to start endpoint controller\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
epc_group->start = start;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t pci_epc_start_show(struct config_item *item, char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n",
|
||||
to_pci_epc_group(item)->start);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(pci_epc_, start);
|
||||
|
||||
static struct configfs_attribute *pci_epc_attrs[] = {
|
||||
&pci_epc_attr_start,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int pci_epc_epf_link(struct config_item *epc_item,
|
||||
struct config_item *epf_item)
|
||||
{
|
||||
int ret;
|
||||
u32 func_no = 0;
|
||||
struct pci_epc *epc;
|
||||
struct pci_epf *epf;
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
|
||||
|
||||
epc = epc_group->epc;
|
||||
epf = epf_group->epf;
|
||||
ret = pci_epc_add_epf(epc, epf);
|
||||
if (ret)
|
||||
goto err_add_epf;
|
||||
|
||||
func_no = find_first_zero_bit(&epc_group->function_num_map,
|
||||
sizeof(epc_group->function_num_map));
|
||||
set_bit(func_no, &epc_group->function_num_map);
|
||||
epf->func_no = func_no;
|
||||
|
||||
ret = pci_epf_bind(epf);
|
||||
if (ret)
|
||||
goto err_epf_bind;
|
||||
|
||||
return 0;
|
||||
|
||||
err_epf_bind:
|
||||
pci_epc_remove_epf(epc, epf);
|
||||
|
||||
err_add_epf:
|
||||
clear_bit(func_no, &epc_group->function_num_map);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pci_epc_epf_unlink(struct config_item *epc_item,
|
||||
struct config_item *epf_item)
|
||||
{
|
||||
struct pci_epc *epc;
|
||||
struct pci_epf *epf;
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
|
||||
|
||||
WARN_ON_ONCE(epc_group->start);
|
||||
|
||||
epc = epc_group->epc;
|
||||
epf = epf_group->epf;
|
||||
clear_bit(epf->func_no, &epc_group->function_num_map);
|
||||
pci_epf_unbind(epf);
|
||||
pci_epc_remove_epf(epc, epf);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations pci_epc_item_ops = {
|
||||
.allow_link = pci_epc_epf_link,
|
||||
.drop_link = pci_epc_epf_unlink,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epc_type = {
|
||||
.ct_item_ops = &pci_epc_item_ops,
|
||||
.ct_attrs = pci_epc_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct config_group *pci_ep_cfs_add_epc_group(const char *name)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc *epc;
|
||||
struct config_group *group;
|
||||
struct pci_epc_group *epc_group;
|
||||
|
||||
epc_group = kzalloc(sizeof(*epc_group), GFP_KERNEL);
|
||||
if (!epc_group) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
group = &epc_group->group;
|
||||
|
||||
config_group_init_type_name(group, name, &pci_epc_type);
|
||||
ret = configfs_register_group(controllers_group, group);
|
||||
if (ret) {
|
||||
pr_err("failed to register configfs group for %s\n", name);
|
||||
goto err_register_group;
|
||||
}
|
||||
|
||||
epc = pci_epc_get(name);
|
||||
if (IS_ERR(epc)) {
|
||||
ret = PTR_ERR(epc);
|
||||
goto err_epc_get;
|
||||
}
|
||||
|
||||
epc_group->epc = epc;
|
||||
|
||||
return group;
|
||||
|
||||
err_epc_get:
|
||||
configfs_unregister_group(group);
|
||||
|
||||
err_register_group:
|
||||
kfree(epc_group);
|
||||
|
||||
err:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_add_epc_group);
|
||||
|
||||
void pci_ep_cfs_remove_epc_group(struct config_group *group)
|
||||
{
|
||||
struct pci_epc_group *epc_group;
|
||||
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
epc_group = container_of(group, struct pci_epc_group, group);
|
||||
pci_epc_put(epc_group->epc);
|
||||
configfs_unregister_group(&epc_group->group);
|
||||
kfree(epc_group);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_remove_epc_group);
|
||||
|
||||
#define PCI_EPF_HEADER_R(_name) \
|
||||
static ssize_t pci_epf_##_name##_show(struct config_item *item, char *page) \
|
||||
{ \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
return sprintf(page, "0x%04x\n", epf->header->_name); \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u32(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u32 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou32(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u16(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u16 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou16(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u8(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u8 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou8(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
static ssize_t pci_epf_msi_interrupts_store(struct config_item *item,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou8(page, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
to_pci_epf_group(item)->epf->msi_interrupts = val;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t pci_epf_msi_interrupts_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n",
|
||||
to_pci_epf_group(item)->epf->msi_interrupts);
|
||||
}
|
||||
|
||||
PCI_EPF_HEADER_R(vendorid)
|
||||
PCI_EPF_HEADER_W_u16(vendorid)
|
||||
|
||||
PCI_EPF_HEADER_R(deviceid)
|
||||
PCI_EPF_HEADER_W_u16(deviceid)
|
||||
|
||||
PCI_EPF_HEADER_R(revid)
|
||||
PCI_EPF_HEADER_W_u8(revid)
|
||||
|
||||
PCI_EPF_HEADER_R(progif_code)
|
||||
PCI_EPF_HEADER_W_u8(progif_code)
|
||||
|
||||
PCI_EPF_HEADER_R(subclass_code)
|
||||
PCI_EPF_HEADER_W_u8(subclass_code)
|
||||
|
||||
PCI_EPF_HEADER_R(baseclass_code)
|
||||
PCI_EPF_HEADER_W_u8(baseclass_code)
|
||||
|
||||
PCI_EPF_HEADER_R(cache_line_size)
|
||||
PCI_EPF_HEADER_W_u8(cache_line_size)
|
||||
|
||||
PCI_EPF_HEADER_R(subsys_vendor_id)
|
||||
PCI_EPF_HEADER_W_u16(subsys_vendor_id)
|
||||
|
||||
PCI_EPF_HEADER_R(subsys_id)
|
||||
PCI_EPF_HEADER_W_u16(subsys_id)
|
||||
|
||||
PCI_EPF_HEADER_R(interrupt_pin)
|
||||
PCI_EPF_HEADER_W_u8(interrupt_pin)
|
||||
|
||||
CONFIGFS_ATTR(pci_epf_, vendorid);
|
||||
CONFIGFS_ATTR(pci_epf_, deviceid);
|
||||
CONFIGFS_ATTR(pci_epf_, revid);
|
||||
CONFIGFS_ATTR(pci_epf_, progif_code);
|
||||
CONFIGFS_ATTR(pci_epf_, subclass_code);
|
||||
CONFIGFS_ATTR(pci_epf_, baseclass_code);
|
||||
CONFIGFS_ATTR(pci_epf_, cache_line_size);
|
||||
CONFIGFS_ATTR(pci_epf_, subsys_vendor_id);
|
||||
CONFIGFS_ATTR(pci_epf_, subsys_id);
|
||||
CONFIGFS_ATTR(pci_epf_, interrupt_pin);
|
||||
CONFIGFS_ATTR(pci_epf_, msi_interrupts);
|
||||
|
||||
static struct configfs_attribute *pci_epf_attrs[] = {
|
||||
&pci_epf_attr_vendorid,
|
||||
&pci_epf_attr_deviceid,
|
||||
&pci_epf_attr_revid,
|
||||
&pci_epf_attr_progif_code,
|
||||
&pci_epf_attr_subclass_code,
|
||||
&pci_epf_attr_baseclass_code,
|
||||
&pci_epf_attr_cache_line_size,
|
||||
&pci_epf_attr_subsys_vendor_id,
|
||||
&pci_epf_attr_subsys_id,
|
||||
&pci_epf_attr_interrupt_pin,
|
||||
&pci_epf_attr_msi_interrupts,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void pci_epf_release(struct config_item *item)
|
||||
{
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(item);
|
||||
|
||||
pci_epf_destroy(epf_group->epf);
|
||||
kfree(epf_group);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations pci_epf_ops = {
|
||||
.release = pci_epf_release,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epf_type = {
|
||||
.ct_item_ops = &pci_epf_ops,
|
||||
.ct_attrs = pci_epf_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_group *pci_epf_make(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
struct pci_epf_group *epf_group;
|
||||
struct pci_epf *epf;
|
||||
|
||||
epf_group = kzalloc(sizeof(*epf_group), GFP_KERNEL);
|
||||
if (!epf_group)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
config_group_init_type_name(&epf_group->group, name, &pci_epf_type);
|
||||
|
||||
epf = pci_epf_create(group->cg_item.ci_name);
|
||||
if (IS_ERR(epf)) {
|
||||
pr_err("failed to create endpoint function device\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
epf_group->epf = epf;
|
||||
|
||||
return &epf_group->group;
|
||||
}
|
||||
|
||||
static void pci_epf_drop(struct config_group *group, struct config_item *item)
|
||||
{
|
||||
config_item_put(item);
|
||||
}
|
||||
|
||||
static struct configfs_group_operations pci_epf_group_ops = {
|
||||
.make_group = &pci_epf_make,
|
||||
.drop_item = &pci_epf_drop,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epf_group_type = {
|
||||
.ct_group_ops = &pci_epf_group_ops,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct config_group *pci_ep_cfs_add_epf_group(const char *name)
|
||||
{
|
||||
struct config_group *group;
|
||||
|
||||
group = configfs_register_default_group(functions_group, name,
|
||||
&pci_epf_group_type);
|
||||
if (IS_ERR(group))
|
||||
pr_err("failed to register configfs group for %s function\n",
|
||||
name);
|
||||
|
||||
return group;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_add_epf_group);
|
||||
|
||||
void pci_ep_cfs_remove_epf_group(struct config_group *group)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(group))
|
||||
return;
|
||||
|
||||
configfs_unregister_default_group(group);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_remove_epf_group);
|
||||
|
||||
static struct config_item_type pci_functions_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_controllers_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_ep_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct configfs_subsystem pci_ep_cfs_subsys = {
|
||||
.su_group = {
|
||||
.cg_item = {
|
||||
.ci_namebuf = "pci_ep",
|
||||
.ci_type = &pci_ep_type,
|
||||
},
|
||||
},
|
||||
.su_mutex = __MUTEX_INITIALIZER(pci_ep_cfs_subsys.su_mutex),
|
||||
};
|
||||
|
||||
static int __init pci_ep_cfs_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct config_group *root = &pci_ep_cfs_subsys.su_group;
|
||||
|
||||
config_group_init(root);
|
||||
|
||||
ret = configfs_register_subsystem(&pci_ep_cfs_subsys);
|
||||
if (ret) {
|
||||
pr_err("Error %d while registering subsystem %s\n",
|
||||
ret, root->cg_item.ci_namebuf);
|
||||
goto err;
|
||||
}
|
||||
|
||||
functions_group = configfs_register_default_group(root, "functions",
|
||||
&pci_functions_type);
|
||||
if (IS_ERR(functions_group)) {
|
||||
ret = PTR_ERR(functions_group);
|
||||
pr_err("Error %d while registering functions group\n",
|
||||
ret);
|
||||
goto err_functions_group;
|
||||
}
|
||||
|
||||
controllers_group =
|
||||
configfs_register_default_group(root, "controllers",
|
||||
&pci_controllers_type);
|
||||
if (IS_ERR(controllers_group)) {
|
||||
ret = PTR_ERR(controllers_group);
|
||||
pr_err("Error %d while registering controllers group\n",
|
||||
ret);
|
||||
goto err_controllers_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_controllers_group:
|
||||
configfs_unregister_default_group(functions_group);
|
||||
|
||||
err_functions_group:
|
||||
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
module_init(pci_ep_cfs_init);
|
||||
|
||||
static void __exit pci_ep_cfs_exit(void)
|
||||
{
|
||||
configfs_unregister_default_group(controllers_group);
|
||||
configfs_unregister_default_group(functions_group);
|
||||
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
|
||||
}
|
||||
module_exit(pci_ep_cfs_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EP CONFIGFS");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
580
drivers/pci/endpoint/pci-epc-core.c
Normal file
580
drivers/pci/endpoint/pci-epc-core.c
Normal file
@ -0,0 +1,580 @@
|
||||
/**
|
||||
* PCI Endpoint *Controller* (EPC) library
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct class *pci_epc_class;
|
||||
|
||||
static void devm_pci_epc_release(struct device *dev, void *res)
|
||||
{
|
||||
struct pci_epc *epc = *(struct pci_epc **)res;
|
||||
|
||||
pci_epc_destroy(epc);
|
||||
}
|
||||
|
||||
static int devm_pci_epc_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
struct pci_epc **epc = res;
|
||||
|
||||
return *epc == match_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_epc_put() - release the PCI endpoint controller
|
||||
* @epc: epc returned by pci_epc_get()
|
||||
*
|
||||
* release the refcount the caller obtained by invoking pci_epc_get()
|
||||
*/
|
||||
void pci_epc_put(struct pci_epc *epc)
|
||||
{
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
module_put(epc->ops->owner);
|
||||
put_device(&epc->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_put);
|
||||
|
||||
/**
|
||||
* pci_epc_get() - get the PCI endpoint controller
|
||||
* @epc_name: device name of the endpoint controller
|
||||
*
|
||||
* Invoke to get struct pci_epc * corresponding to the device name of the
|
||||
* endpoint controller
|
||||
*/
|
||||
struct pci_epc *pci_epc_get(const char *epc_name)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct pci_epc *epc;
|
||||
struct device *dev;
|
||||
struct class_dev_iter iter;
|
||||
|
||||
class_dev_iter_init(&iter, pci_epc_class, NULL, NULL);
|
||||
while ((dev = class_dev_iter_next(&iter))) {
|
||||
if (strcmp(epc_name, dev_name(dev)))
|
||||
continue;
|
||||
|
||||
epc = to_pci_epc(dev);
|
||||
if (!try_module_get(epc->ops->owner)) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
class_dev_iter_exit(&iter);
|
||||
get_device(&epc->dev);
|
||||
return epc;
|
||||
}
|
||||
|
||||
err:
|
||||
class_dev_iter_exit(&iter);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get);
|
||||
|
||||
/**
|
||||
* pci_epc_stop() - stop the PCI link
|
||||
* @epc: the link of the EPC device that has to be stopped
|
||||
*
|
||||
* Invoke to stop the PCI link
|
||||
*/
|
||||
void pci_epc_stop(struct pci_epc *epc)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc) || !epc->ops->stop)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->stop(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_stop);
|
||||
|
||||
/**
|
||||
* pci_epc_start() - start the PCI link
|
||||
* @epc: the link of *this* EPC device has to be started
|
||||
*
|
||||
* Invoke to start the PCI link
|
||||
*/
|
||||
int pci_epc_start(struct pci_epc *epc)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->start)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->start(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_start);
|
||||
|
||||
/**
|
||||
* pci_epc_raise_irq() - interrupt the host system
|
||||
* @epc: the EPC device which has to interrupt the host
|
||||
* @type: specify the type of interrupt; legacy or MSI
|
||||
* @interrupt_num: the MSI interrupt number
|
||||
*
|
||||
* Invoke to raise an MSI or legacy interrupt
|
||||
*/
|
||||
int pci_epc_raise_irq(struct pci_epc *epc, enum pci_epc_irq_type type,
|
||||
u8 interrupt_num)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->raise_irq)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->raise_irq(epc, type, interrupt_num);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_raise_irq);
|
||||
|
||||
/**
|
||||
* pci_epc_get_msi() - get the number of MSI interrupt numbers allocated
|
||||
* @epc: the EPC device to which MSI interrupts was requested
|
||||
*
|
||||
* Invoke to get the number of MSI interrupts allocated by the RC
|
||||
*/
|
||||
int pci_epc_get_msi(struct pci_epc *epc)
|
||||
{
|
||||
int interrupt;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return 0;
|
||||
|
||||
if (!epc->ops->get_msi)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
interrupt = epc->ops->get_msi(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
if (interrupt < 0)
|
||||
return 0;
|
||||
|
||||
interrupt = 1 << interrupt;
|
||||
|
||||
return interrupt;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get_msi);
|
||||
|
||||
/**
|
||||
* pci_epc_set_msi() - set the number of MSI interrupt numbers required
|
||||
* @epc: the EPC device on which MSI has to be configured
|
||||
* @interrupts: number of MSI interrupts required by the EPF
|
||||
*
|
||||
* Invoke to set the required number of MSI interrupts.
|
||||
*/
|
||||
int pci_epc_set_msi(struct pci_epc *epc, u8 interrupts)
|
||||
{
|
||||
int ret;
|
||||
u8 encode_int;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->set_msi)
|
||||
return 0;
|
||||
|
||||
encode_int = order_base_2(interrupts);
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->set_msi(epc, encode_int);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_set_msi);
|
||||
|
||||
/**
|
||||
* pci_epc_unmap_addr() - unmap CPU address from PCI address
|
||||
* @epc: the EPC device on which address is allocated
|
||||
* @phys_addr: physical address of the local system
|
||||
*
|
||||
* Invoke to unmap the CPU address from PCI address.
|
||||
*/
|
||||
void pci_epc_unmap_addr(struct pci_epc *epc, phys_addr_t phys_addr)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return;
|
||||
|
||||
if (!epc->ops->unmap_addr)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->unmap_addr(epc, phys_addr);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_unmap_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_map_addr() - map CPU address to PCI address
|
||||
* @epc: the EPC device on which address is allocated
|
||||
* @phys_addr: physical address of the local system
|
||||
* @pci_addr: PCI address to which the physical address should be mapped
|
||||
* @size: the size of the allocation
|
||||
*
|
||||
* Invoke to map CPU address with PCI address.
|
||||
*/
|
||||
int pci_epc_map_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->map_addr)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->map_addr(epc, phys_addr, pci_addr, size);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_map_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_clear_bar() - reset the BAR
|
||||
* @epc: the EPC device for which the BAR has to be cleared
|
||||
* @bar: the BAR number that has to be reset
|
||||
*
|
||||
* Invoke to reset the BAR of the endpoint device.
|
||||
*/
|
||||
void pci_epc_clear_bar(struct pci_epc *epc, int bar)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return;
|
||||
|
||||
if (!epc->ops->clear_bar)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->clear_bar(epc, bar);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_clear_bar);
|
||||
|
||||
/**
|
||||
* pci_epc_set_bar() - configure BAR in order for host to assign PCI addr space
|
||||
* @epc: the EPC device on which BAR has to be configured
|
||||
* @bar: the BAR number that has to be configured
|
||||
* @size: the size of the addr space
|
||||
* @flags: specify memory allocation/io allocation/32bit address/64 bit address
|
||||
*
|
||||
* Invoke to configure the BAR of the endpoint device.
|
||||
*/
|
||||
int pci_epc_set_bar(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags)
|
||||
{
|
||||
int ret;
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->set_bar)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, irq_flags);
|
||||
ret = epc->ops->set_bar(epc, bar, bar_phys, size, flags);
|
||||
spin_unlock_irqrestore(&epc->lock, irq_flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_set_bar);
|
||||
|
||||
/**
|
||||
* pci_epc_write_header() - write standard configuration header
|
||||
* @epc: the EPC device to which the configuration header should be written
|
||||
* @header: standard configuration header fields
|
||||
*
|
||||
* Invoke to write the configuration header to the endpoint controller. Every
|
||||
* endpoint controller will have a dedicated location to which the standard
|
||||
* configuration header would be written. The callback function should write
|
||||
* the header fields to this dedicated location.
|
||||
*/
|
||||
int pci_epc_write_header(struct pci_epc *epc, struct pci_epf_header *header)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->write_header)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->write_header(epc, header);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_write_header);
|
||||
|
||||
/**
|
||||
* pci_epc_add_epf() - bind PCI endpoint function to an endpoint controller
|
||||
* @epc: the EPC device to which the endpoint function should be added
|
||||
* @epf: the endpoint function to be added
|
||||
*
|
||||
* A PCI endpoint device can have one or more functions. In the case of PCIe,
|
||||
* the specification allows up to 8 PCIe endpoint functions. Invoke
|
||||
* pci_epc_add_epf() to add a PCI endpoint function to an endpoint controller.
|
||||
*/
|
||||
int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (epf->epc)
|
||||
return -EBUSY;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (epf->func_no > epc->max_functions - 1)
|
||||
return -EINVAL;
|
||||
|
||||
epf->epc = epc;
|
||||
dma_set_coherent_mask(&epf->dev, epc->dev.coherent_dma_mask);
|
||||
epf->dev.dma_mask = epc->dev.dma_mask;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_add_tail(&epf->list, &epc->pci_epf);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_add_epf);
|
||||
|
||||
/**
|
||||
* pci_epc_remove_epf() - remove PCI endpoint function from endpoint controller
|
||||
* @epc: the EPC device from which the endpoint function should be removed
|
||||
* @epf: the endpoint function to be removed
|
||||
*
|
||||
* Invoke to remove PCI endpoint function from the endpoint controller.
|
||||
*/
|
||||
void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_del(&epf->list);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_remove_epf);
|
||||
|
||||
/**
|
||||
* pci_epc_linkup() - Notify the EPF device that EPC device has established a
|
||||
* connection with the Root Complex.
|
||||
* @epc: the EPC device which has established link with the host
|
||||
*
|
||||
* Invoke to Notify the EPF device that the EPC device has established a
|
||||
* connection with the Root Complex.
|
||||
*/
|
||||
void pci_epc_linkup(struct pci_epc *epc)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct pci_epf *epf;
|
||||
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_for_each_entry(epf, &epc->pci_epf, list)
|
||||
pci_epf_linkup(epf);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_linkup);
|
||||
|
||||
/**
|
||||
* pci_epc_destroy() - destroy the EPC device
|
||||
* @epc: the EPC device that has to be destroyed
|
||||
*
|
||||
* Invoke to destroy the PCI EPC device
|
||||
*/
|
||||
void pci_epc_destroy(struct pci_epc *epc)
|
||||
{
|
||||
pci_ep_cfs_remove_epc_group(epc->group);
|
||||
device_unregister(&epc->dev);
|
||||
kfree(epc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_destroy);
|
||||
|
||||
/**
|
||||
* devm_pci_epc_destroy() - destroy the EPC device
|
||||
* @dev: device that wants to destroy the EPC
|
||||
* @epc: the EPC device that has to be destroyed
|
||||
*
|
||||
* Invoke to destroy the devres associated with this
|
||||
* pci_epc and destroy the EPC device.
|
||||
*/
|
||||
void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = devres_destroy(dev, devm_pci_epc_release, devm_pci_epc_match,
|
||||
epc);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PCI EPC resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_pci_epc_destroy);
|
||||
|
||||
/**
|
||||
* __pci_epc_create() - create a new endpoint controller (EPC) device
|
||||
* @dev: device that is creating the new EPC
|
||||
* @ops: function pointers for performing EPC operations
|
||||
* @owner: the owner of the module that creates the EPC device
|
||||
*
|
||||
* Invoke to create a new EPC device and add it to pci_epc class.
|
||||
*/
|
||||
struct pci_epc *
|
||||
__pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc *epc;
|
||||
|
||||
if (WARN_ON(!dev)) {
|
||||
ret = -EINVAL;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
epc = kzalloc(sizeof(*epc), GFP_KERNEL);
|
||||
if (!epc) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
spin_lock_init(&epc->lock);
|
||||
INIT_LIST_HEAD(&epc->pci_epf);
|
||||
|
||||
device_initialize(&epc->dev);
|
||||
dma_set_coherent_mask(&epc->dev, dev->coherent_dma_mask);
|
||||
epc->dev.class = pci_epc_class;
|
||||
epc->dev.dma_mask = dev->dma_mask;
|
||||
epc->ops = ops;
|
||||
|
||||
ret = dev_set_name(&epc->dev, "%s", dev_name(dev));
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
ret = device_add(&epc->dev);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
epc->group = pci_ep_cfs_add_epc_group(dev_name(dev));
|
||||
|
||||
return epc;
|
||||
|
||||
put_dev:
|
||||
put_device(&epc->dev);
|
||||
kfree(epc);
|
||||
|
||||
err_ret:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pci_epc_create);
|
||||
|
||||
/**
|
||||
* __devm_pci_epc_create() - create a new endpoint controller (EPC) device
|
||||
* @dev: device that is creating the new EPC
|
||||
* @ops: function pointers for performing EPC operations
|
||||
* @owner: the owner of the module that creates the EPC device
|
||||
*
|
||||
* Invoke to create a new EPC device and add it to pci_epc class.
|
||||
* While at that, it also associates the device with the pci_epc using devres.
|
||||
* On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*/
|
||||
struct pci_epc *
|
||||
__devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
struct pci_epc **ptr, *epc;
|
||||
|
||||
ptr = devres_alloc(devm_pci_epc_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
epc = __pci_epc_create(dev, ops, owner);
|
||||
if (!IS_ERR(epc)) {
|
||||
*ptr = epc;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return epc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_pci_epc_create);
|
||||
|
||||
static int __init pci_epc_init(void)
|
||||
{
|
||||
pci_epc_class = class_create(THIS_MODULE, "pci_epc");
|
||||
if (IS_ERR(pci_epc_class)) {
|
||||
pr_err("failed to create pci epc class --> %ld\n",
|
||||
PTR_ERR(pci_epc_class));
|
||||
return PTR_ERR(pci_epc_class);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epc_init);
|
||||
|
||||
static void __exit pci_epc_exit(void)
|
||||
{
|
||||
class_destroy(pci_epc_class);
|
||||
}
|
||||
module_exit(pci_epc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPC Library");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
143
drivers/pci/endpoint/pci-epc-mem.c
Normal file
143
drivers/pci/endpoint/pci-epc-mem.c
Normal file
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* PCI Endpoint *Controller* Address Space Management
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
|
||||
/**
|
||||
* pci_epc_mem_init() - initialize the pci_epc_mem structure
|
||||
* @epc: the EPC device that invoked pci_epc_mem_init
|
||||
* @phys_base: the physical address of the base
|
||||
* @size: the size of the address space
|
||||
*
|
||||
* Invoke to initialize the pci_epc_mem structure used by the
|
||||
* endpoint functions to allocate mapped PCI address.
|
||||
*/
|
||||
int pci_epc_mem_init(struct pci_epc *epc, phys_addr_t phys_base, size_t size)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc_mem *mem;
|
||||
unsigned long *bitmap;
|
||||
int pages = size >> PAGE_SHIFT;
|
||||
int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long);
|
||||
|
||||
mem = kzalloc(sizeof(*mem), GFP_KERNEL);
|
||||
if (!mem) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
bitmap = kzalloc(bitmap_size, GFP_KERNEL);
|
||||
if (!bitmap) {
|
||||
ret = -ENOMEM;
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
mem->bitmap = bitmap;
|
||||
mem->phys_base = phys_base;
|
||||
mem->pages = pages;
|
||||
mem->size = size;
|
||||
|
||||
epc->mem = mem;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mem:
|
||||
kfree(mem);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_init);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_exit() - cleanup the pci_epc_mem structure
|
||||
* @epc: the EPC device that invoked pci_epc_mem_exit
|
||||
*
|
||||
* Invoke to cleanup the pci_epc_mem structure allocated in
|
||||
* pci_epc_mem_init().
|
||||
*/
|
||||
void pci_epc_mem_exit(struct pci_epc *epc)
|
||||
{
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
|
||||
epc->mem = NULL;
|
||||
kfree(mem->bitmap);
|
||||
kfree(mem);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_exit);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_alloc_addr() - allocate memory address from EPC addr space
|
||||
* @epc: the EPC device on which memory has to be allocated
|
||||
* @phys_addr: populate the allocated physical address here
|
||||
* @size: the size of the address space that has to be allocated
|
||||
*
|
||||
* Invoke to allocate memory address from the EPC address space. This
|
||||
* is usually done to map the remote RC address into the local system.
|
||||
*/
|
||||
void __iomem *pci_epc_mem_alloc_addr(struct pci_epc *epc,
|
||||
phys_addr_t *phys_addr, size_t size)
|
||||
{
|
||||
int pageno;
|
||||
void __iomem *virt_addr;
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
int order = get_order(size);
|
||||
|
||||
pageno = bitmap_find_free_region(mem->bitmap, mem->pages, order);
|
||||
if (pageno < 0)
|
||||
return NULL;
|
||||
|
||||
*phys_addr = mem->phys_base + (pageno << PAGE_SHIFT);
|
||||
virt_addr = ioremap(*phys_addr, size);
|
||||
if (!virt_addr)
|
||||
bitmap_release_region(mem->bitmap, pageno, order);
|
||||
|
||||
return virt_addr;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_alloc_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_free_addr() - free the allocated memory address
|
||||
* @epc: the EPC device on which memory was allocated
|
||||
* @phys_addr: the allocated physical address
|
||||
* @virt_addr: virtual address of the allocated mem space
|
||||
* @size: the size of the allocated address space
|
||||
*
|
||||
* Invoke to free the memory allocated using pci_epc_mem_alloc_addr.
|
||||
*/
|
||||
void pci_epc_mem_free_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
void __iomem *virt_addr, size_t size)
|
||||
{
|
||||
int pageno;
|
||||
int order = get_order(size);
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
|
||||
iounmap(virt_addr);
|
||||
pageno = (phys_addr - mem->phys_base) >> PAGE_SHIFT;
|
||||
bitmap_release_region(mem->bitmap, pageno, order);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_free_addr);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPC Address Space Management");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
359
drivers/pci/endpoint/pci-epf-core.c
Normal file
359
drivers/pci/endpoint/pci-epf-core.c
Normal file
@ -0,0 +1,359 @@
|
||||
/**
|
||||
* PCI Endpoint *Function* (EPF) library
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct bus_type pci_epf_bus_type;
|
||||
static struct device_type pci_epf_type;
|
||||
|
||||
/**
|
||||
* pci_epf_linkup() - Notify the function driver that EPC device has
|
||||
* established a connection with the Root Complex.
|
||||
* @epf: the EPF device bound to the EPC device which has established
|
||||
* the connection with the host
|
||||
*
|
||||
* Invoke to notify the function driver that EPC device has established
|
||||
* a connection with the Root Complex.
|
||||
*/
|
||||
void pci_epf_linkup(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return;
|
||||
}
|
||||
|
||||
epf->driver->ops->linkup(epf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_linkup);
|
||||
|
||||
/**
|
||||
* pci_epf_unbind() - Notify the function driver that the binding between the
|
||||
* EPF device and EPC device has been lost
|
||||
* @epf: the EPF device which has lost the binding with the EPC device
|
||||
*
|
||||
* Invoke to notify the function driver that the binding between the EPF device
|
||||
* and EPC device has been lost.
|
||||
*/
|
||||
void pci_epf_unbind(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return;
|
||||
}
|
||||
|
||||
epf->driver->ops->unbind(epf);
|
||||
module_put(epf->driver->owner);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_unbind);
|
||||
|
||||
/**
|
||||
* pci_epf_bind() - Notify the function driver that the EPF device has been
|
||||
* bound to a EPC device
|
||||
* @epf: the EPF device which has been bound to the EPC device
|
||||
*
|
||||
* Invoke to notify the function driver that it has been bound to a EPC device
|
||||
*/
|
||||
int pci_epf_bind(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!try_module_get(epf->driver->owner))
|
||||
return -EAGAIN;
|
||||
|
||||
return epf->driver->ops->bind(epf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_bind);
|
||||
|
||||
/**
|
||||
* pci_epf_free_space() - free the allocated PCI EPF register space
|
||||
* @addr: the virtual address of the PCI EPF register space
|
||||
* @bar: the BAR number corresponding to the register space
|
||||
*
|
||||
* Invoke to free the allocated PCI EPF register space.
|
||||
*/
|
||||
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar)
|
||||
{
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
if (!addr)
|
||||
return;
|
||||
|
||||
dma_free_coherent(dev, epf->bar[bar].size, addr,
|
||||
epf->bar[bar].phys_addr);
|
||||
|
||||
epf->bar[bar].phys_addr = 0;
|
||||
epf->bar[bar].size = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_free_space);
|
||||
|
||||
/**
|
||||
* pci_epf_alloc_space() - allocate memory for the PCI EPF register space
|
||||
* @size: the size of the memory that has to be allocated
|
||||
* @bar: the BAR number corresponding to the allocated register space
|
||||
*
|
||||
* Invoke to allocate memory for the PCI EPF register space.
|
||||
*/
|
||||
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar)
|
||||
{
|
||||
void *space;
|
||||
struct device *dev = &epf->dev;
|
||||
dma_addr_t phys_addr;
|
||||
|
||||
if (size < 128)
|
||||
size = 128;
|
||||
size = roundup_pow_of_two(size);
|
||||
|
||||
space = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
|
||||
if (!space) {
|
||||
dev_err(dev, "failed to allocate mem space\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epf->bar[bar].phys_addr = phys_addr;
|
||||
epf->bar[bar].size = size;
|
||||
|
||||
return space;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_alloc_space);
|
||||
|
||||
/**
|
||||
* pci_epf_unregister_driver() - unregister the PCI EPF driver
|
||||
* @driver: the PCI EPF driver that has to be unregistered
|
||||
*
|
||||
* Invoke to unregister the PCI EPF driver.
|
||||
*/
|
||||
void pci_epf_unregister_driver(struct pci_epf_driver *driver)
|
||||
{
|
||||
pci_ep_cfs_remove_epf_group(driver->group);
|
||||
driver_unregister(&driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_unregister_driver);
|
||||
|
||||
/**
|
||||
* __pci_epf_register_driver() - register a new PCI EPF driver
|
||||
* @driver: structure representing PCI EPF driver
|
||||
* @owner: the owner of the module that registers the PCI EPF driver
|
||||
*
|
||||
* Invoke to register a new PCI EPF driver.
|
||||
*/
|
||||
int __pci_epf_register_driver(struct pci_epf_driver *driver,
|
||||
struct module *owner)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!driver->ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (!driver->ops->bind || !driver->ops->unbind || !driver->ops->linkup)
|
||||
return -EINVAL;
|
||||
|
||||
driver->driver.bus = &pci_epf_bus_type;
|
||||
driver->driver.owner = owner;
|
||||
|
||||
ret = driver_register(&driver->driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
driver->group = pci_ep_cfs_add_epf_group(driver->driver.name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pci_epf_register_driver);
|
||||
|
||||
/**
|
||||
* pci_epf_destroy() - destroy the created PCI EPF device
|
||||
* @epf: the PCI EPF device that has to be destroyed.
|
||||
*
|
||||
* Invoke to destroy the PCI EPF device created by invoking pci_epf_create().
|
||||
*/
|
||||
void pci_epf_destroy(struct pci_epf *epf)
|
||||
{
|
||||
device_unregister(&epf->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_destroy);
|
||||
|
||||
/**
|
||||
* pci_epf_create() - create a new PCI EPF device
|
||||
* @name: the name of the PCI EPF device. This name will be used to bind the
|
||||
* the EPF device to a EPF driver
|
||||
*
|
||||
* Invoke to create a new PCI EPF device by providing the name of the function
|
||||
* device.
|
||||
*/
|
||||
struct pci_epf *pci_epf_create(const char *name)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf *epf;
|
||||
struct device *dev;
|
||||
char *func_name;
|
||||
char *buf;
|
||||
|
||||
epf = kzalloc(sizeof(*epf), GFP_KERNEL);
|
||||
if (!epf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
buf = kstrdup(name, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto free_epf;
|
||||
}
|
||||
|
||||
func_name = buf;
|
||||
buf = strchrnul(buf, '.');
|
||||
*buf = '\0';
|
||||
|
||||
epf->name = kstrdup(func_name, GFP_KERNEL);
|
||||
if (!epf->name) {
|
||||
ret = -ENOMEM;
|
||||
goto free_func_name;
|
||||
}
|
||||
|
||||
dev = &epf->dev;
|
||||
device_initialize(dev);
|
||||
dev->bus = &pci_epf_bus_type;
|
||||
dev->type = &pci_epf_type;
|
||||
|
||||
ret = dev_set_name(dev, "%s", name);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
ret = device_add(dev);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
kfree(func_name);
|
||||
return epf;
|
||||
|
||||
put_dev:
|
||||
put_device(dev);
|
||||
kfree(epf->name);
|
||||
|
||||
free_func_name:
|
||||
kfree(func_name);
|
||||
|
||||
free_epf:
|
||||
kfree(epf);
|
||||
|
||||
err_ret:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_create);
|
||||
|
||||
static void pci_epf_dev_release(struct device *dev)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
|
||||
kfree(epf->name);
|
||||
kfree(epf);
|
||||
}
|
||||
|
||||
static struct device_type pci_epf_type = {
|
||||
.release = pci_epf_dev_release,
|
||||
};
|
||||
|
||||
static int
|
||||
pci_epf_match_id(const struct pci_epf_device_id *id, const struct pci_epf *epf)
|
||||
{
|
||||
while (id->name[0]) {
|
||||
if (strcmp(epf->name, id->name) == 0)
|
||||
return true;
|
||||
id++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int pci_epf_device_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(drv);
|
||||
|
||||
if (driver->id_table)
|
||||
return pci_epf_match_id(driver->id_table, epf);
|
||||
|
||||
return !strcmp(epf->name, drv->name);
|
||||
}
|
||||
|
||||
static int pci_epf_device_probe(struct device *dev)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
|
||||
|
||||
if (!driver->probe)
|
||||
return -ENODEV;
|
||||
|
||||
epf->driver = driver;
|
||||
|
||||
return driver->probe(epf);
|
||||
}
|
||||
|
||||
static int pci_epf_device_remove(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
|
||||
|
||||
ret = driver->remove(epf);
|
||||
epf->driver = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bus_type pci_epf_bus_type = {
|
||||
.name = "pci-epf",
|
||||
.match = pci_epf_device_match,
|
||||
.probe = pci_epf_device_probe,
|
||||
.remove = pci_epf_device_remove,
|
||||
};
|
||||
|
||||
static int __init pci_epf_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bus_register(&pci_epf_bus_type);
|
||||
if (ret) {
|
||||
pr_err("failed to register pci epf bus --> %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epf_init);
|
||||
|
||||
static void __exit pci_epf_exit(void)
|
||||
{
|
||||
bus_unregister(&pci_epf_bus_type);
|
||||
}
|
||||
module_exit(pci_epf_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPF Library");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -14,6 +14,7 @@
|
||||
* Copyright (C) 2015 - 2016 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/of_address.h>
|
||||
@ -334,6 +335,49 @@ static int thunder_pem_init(struct device *dev, struct pci_config_window *cfg,
|
||||
|
||||
#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
|
||||
|
||||
#define PEM_RES_BASE 0x87e0c0000000UL
|
||||
#define PEM_NODE_MASK GENMASK(45, 44)
|
||||
#define PEM_INDX_MASK GENMASK(26, 24)
|
||||
#define PEM_MIN_DOM_IN_NODE 4
|
||||
#define PEM_MAX_DOM_IN_NODE 10
|
||||
|
||||
static void thunder_pem_reserve_range(struct device *dev, int seg,
|
||||
struct resource *r)
|
||||
{
|
||||
resource_size_t start = r->start, end = r->end;
|
||||
struct resource *res;
|
||||
const char *regionid;
|
||||
|
||||
regionid = kasprintf(GFP_KERNEL, "PEM RC:%d", seg);
|
||||
if (!regionid)
|
||||
return;
|
||||
|
||||
res = request_mem_region(start, end - start + 1, regionid);
|
||||
if (res)
|
||||
res->flags &= ~IORESOURCE_BUSY;
|
||||
else
|
||||
kfree(regionid);
|
||||
|
||||
dev_info(dev, "%pR %s reserved\n", r,
|
||||
res ? "has been" : "could not be");
|
||||
}
|
||||
|
||||
static void thunder_pem_legacy_fw(struct acpi_pci_root *root,
|
||||
struct resource *res_pem)
|
||||
{
|
||||
int node = acpi_get_node(root->device->handle);
|
||||
int index;
|
||||
|
||||
if (node == NUMA_NO_NODE)
|
||||
node = 0;
|
||||
|
||||
index = root->segment - PEM_MIN_DOM_IN_NODE;
|
||||
index -= node * PEM_MAX_DOM_IN_NODE;
|
||||
res_pem->start = PEM_RES_BASE | FIELD_PREP(PEM_NODE_MASK, node) |
|
||||
FIELD_PREP(PEM_INDX_MASK, index);
|
||||
res_pem->flags = IORESOURCE_MEM;
|
||||
}
|
||||
|
||||
static int thunder_pem_acpi_init(struct pci_config_window *cfg)
|
||||
{
|
||||
struct device *dev = cfg->parent;
|
||||
@ -346,10 +390,24 @@ static int thunder_pem_acpi_init(struct pci_config_window *cfg)
|
||||
if (!res_pem)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = acpi_get_rc_resources(dev, "THRX0002", root->segment, res_pem);
|
||||
ret = acpi_get_rc_resources(dev, "CAVA02B", root->segment, res_pem);
|
||||
|
||||
/*
|
||||
* If we fail to gather resources it means that we run with old
|
||||
* FW where we need to calculate PEM-specific resources manually.
|
||||
*/
|
||||
if (ret) {
|
||||
dev_err(dev, "can't get rc base address\n");
|
||||
return ret;
|
||||
thunder_pem_legacy_fw(root, res_pem);
|
||||
/*
|
||||
* Reserve 64K size PEM specific resources. The full 16M range
|
||||
* size is required for thunder_pem_init() call.
|
||||
*/
|
||||
res_pem->end = res_pem->start + SZ_64K - 1;
|
||||
thunder_pem_reserve_range(dev, root->segment, res_pem);
|
||||
res_pem->end = res_pem->start + SZ_16M - 1;
|
||||
|
||||
/* Reserve PCI configuration space as well. */
|
||||
thunder_pem_reserve_range(dev, root->segment, &cfg->res);
|
||||
}
|
||||
|
||||
return thunder_pem_init(dev, cfg, res_pem);
|
||||
|
@ -44,8 +44,7 @@ static int iproc_pcie_bcma_probe(struct bcma_device *bdev)
|
||||
{
|
||||
struct device *dev = &bdev->dev;
|
||||
struct iproc_pcie *pcie;
|
||||
LIST_HEAD(res);
|
||||
struct resource res_mem;
|
||||
LIST_HEAD(resources);
|
||||
int ret;
|
||||
|
||||
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
|
||||
@ -63,22 +62,23 @@ static int iproc_pcie_bcma_probe(struct bcma_device *bdev)
|
||||
|
||||
pcie->base_addr = bdev->addr;
|
||||
|
||||
res_mem.start = bdev->addr_s[0];
|
||||
res_mem.end = bdev->addr_s[0] + SZ_128M - 1;
|
||||
res_mem.name = "PCIe MEM space";
|
||||
res_mem.flags = IORESOURCE_MEM;
|
||||
pci_add_resource(&res, &res_mem);
|
||||
pcie->mem.start = bdev->addr_s[0];
|
||||
pcie->mem.end = bdev->addr_s[0] + SZ_128M - 1;
|
||||
pcie->mem.name = "PCIe MEM space";
|
||||
pcie->mem.flags = IORESOURCE_MEM;
|
||||
pci_add_resource(&resources, &pcie->mem);
|
||||
|
||||
pcie->map_irq = iproc_pcie_bcma_map_irq;
|
||||
|
||||
ret = iproc_pcie_setup(pcie, &res);
|
||||
if (ret)
|
||||
ret = iproc_pcie_setup(pcie, &resources);
|
||||
if (ret) {
|
||||
dev_err(dev, "PCIe controller setup failed\n");
|
||||
|
||||
pci_free_resource_list(&res);
|
||||
pci_free_resource_list(&resources);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bcma_set_drvdata(bdev, pcie);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iproc_pcie_bcma_remove(struct bcma_device *bdev)
|
||||
|
@ -51,7 +51,7 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
struct device_node *np = dev->of_node;
|
||||
struct resource reg;
|
||||
resource_size_t iobase = 0;
|
||||
LIST_HEAD(res);
|
||||
LIST_HEAD(resources);
|
||||
int ret;
|
||||
|
||||
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
|
||||
@ -96,10 +96,10 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
pcie->phy = NULL;
|
||||
}
|
||||
|
||||
ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &res, &iobase);
|
||||
ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &resources,
|
||||
&iobase);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"unable to get PCI host bridge resources\n");
|
||||
dev_err(dev, "unable to get PCI host bridge resources\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -112,14 +112,15 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
pcie->map_irq = of_irq_parse_and_map_pci;
|
||||
}
|
||||
|
||||
ret = iproc_pcie_setup(pcie, &res);
|
||||
if (ret)
|
||||
ret = iproc_pcie_setup(pcie, &resources);
|
||||
if (ret) {
|
||||
dev_err(dev, "PCIe controller setup failed\n");
|
||||
|
||||
pci_free_resource_list(&res);
|
||||
pci_free_resource_list(&resources);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iproc_pcie_pltfm_remove(struct platform_device *pdev)
|
||||
|
@ -90,6 +90,7 @@ struct iproc_pcie {
|
||||
#ifdef CONFIG_ARM
|
||||
struct pci_sys_data sysdata;
|
||||
#endif
|
||||
struct resource mem;
|
||||
struct pci_bus *root_bus;
|
||||
struct phy *phy;
|
||||
int (*map_irq)(const struct pci_dev *, u8, u8);
|
||||
|
@ -478,7 +478,7 @@ static void aspm_calc_l1ss_info(struct pcie_link_state *link,
|
||||
|
||||
static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
|
||||
{
|
||||
struct pci_dev *child, *parent = link->pdev;
|
||||
struct pci_dev *child = link->downstream, *parent = link->pdev;
|
||||
struct pci_bus *linkbus = parent->subordinate;
|
||||
struct aspm_register_info upreg, dwreg;
|
||||
|
||||
@ -491,9 +491,7 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
|
||||
|
||||
/* Get upstream/downstream components' register state */
|
||||
pcie_get_aspm_reg(parent, &upreg);
|
||||
child = pci_function_0(linkbus);
|
||||
pcie_get_aspm_reg(child, &dwreg);
|
||||
link->downstream = child;
|
||||
|
||||
/*
|
||||
* If ASPM not supported, don't mess with the clocks and link,
|
||||
@ -800,6 +798,7 @@ static struct pcie_link_state *alloc_pcie_link_state(struct pci_dev *pdev)
|
||||
INIT_LIST_HEAD(&link->children);
|
||||
INIT_LIST_HEAD(&link->link);
|
||||
link->pdev = pdev;
|
||||
link->downstream = pci_function_0(pdev->subordinate);
|
||||
|
||||
/*
|
||||
* Root Ports and PCI/PCI-X to PCIe Bridges are roots of PCIe
|
||||
|
@ -2174,6 +2174,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005d, quirk_blacklist_vpd);
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005f, quirk_blacklist_vpd);
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATTANSIC, PCI_ANY_ID,
|
||||
quirk_blacklist_vpd);
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_QLOGIC, 0x2261, quirk_blacklist_vpd);
|
||||
|
||||
/*
|
||||
* For Broadcom 5706, 5708, 5709 rev. A nics, any read beyond the
|
||||
|
@ -428,6 +428,16 @@ struct i2c_device_id {
|
||||
kernel_ulong_t driver_data; /* Data private to the driver */
|
||||
};
|
||||
|
||||
/* pci_epf */
|
||||
|
||||
#define PCI_EPF_NAME_SIZE 20
|
||||
#define PCI_EPF_MODULE_PREFIX "pci_epf:"
|
||||
|
||||
struct pci_epf_device_id {
|
||||
char name[PCI_EPF_NAME_SIZE];
|
||||
kernel_ulong_t driver_data;
|
||||
};
|
||||
|
||||
/* spi */
|
||||
|
||||
#define SPI_NAME_SIZE 32
|
||||
|
41
include/linux/pci-ep-cfs.h
Normal file
41
include/linux/pci-ep-cfs.h
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* PCI Endpoint ConfigFS header file
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_PCI_EP_CFS_H
|
||||
#define __LINUX_PCI_EP_CFS_H
|
||||
|
||||
#include <linux/configfs.h>
|
||||
|
||||
#ifdef CONFIG_PCI_ENDPOINT_CONFIGFS
|
||||
struct config_group *pci_ep_cfs_add_epc_group(const char *name);
|
||||
void pci_ep_cfs_remove_epc_group(struct config_group *group);
|
||||
struct config_group *pci_ep_cfs_add_epf_group(const char *name);
|
||||
void pci_ep_cfs_remove_epf_group(struct config_group *group);
|
||||
#else
|
||||
static inline struct config_group *pci_ep_cfs_add_epc_group(const char *name)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void pci_ep_cfs_remove_epc_group(struct config_group *group)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct config_group *pci_ep_cfs_add_epf_group(const char *name)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void pci_ep_cfs_remove_epf_group(struct config_group *group)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#endif /* __LINUX_PCI_EP_CFS_H */
|
144
include/linux/pci-epc.h
Normal file
144
include/linux/pci-epc.h
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* PCI Endpoint *Controller* (EPC) header file
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_PCI_EPC_H
|
||||
#define __LINUX_PCI_EPC_H
|
||||
|
||||
#include <linux/pci-epf.h>
|
||||
|
||||
struct pci_epc;
|
||||
|
||||
enum pci_epc_irq_type {
|
||||
PCI_EPC_IRQ_UNKNOWN,
|
||||
PCI_EPC_IRQ_LEGACY,
|
||||
PCI_EPC_IRQ_MSI,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epc_ops - set of function pointers for performing EPC operations
|
||||
* @write_header: ops to populate configuration space header
|
||||
* @set_bar: ops to configure the BAR
|
||||
* @clear_bar: ops to reset the BAR
|
||||
* @map_addr: ops to map CPU address to PCI address
|
||||
* @unmap_addr: ops to unmap CPU address and PCI address
|
||||
* @set_msi: ops to set the requested number of MSI interrupts in the MSI
|
||||
* capability register
|
||||
* @get_msi: ops to get the number of MSI interrupts allocated by the RC from
|
||||
* the MSI capability register
|
||||
* @raise_irq: ops to raise a legacy or MSI interrupt
|
||||
* @start: ops to start the PCI link
|
||||
* @stop: ops to stop the PCI link
|
||||
* @owner: the module owner containing the ops
|
||||
*/
|
||||
struct pci_epc_ops {
|
||||
int (*write_header)(struct pci_epc *pci_epc,
|
||||
struct pci_epf_header *hdr);
|
||||
int (*set_bar)(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags);
|
||||
void (*clear_bar)(struct pci_epc *epc, enum pci_barno bar);
|
||||
int (*map_addr)(struct pci_epc *epc, phys_addr_t addr,
|
||||
u64 pci_addr, size_t size);
|
||||
void (*unmap_addr)(struct pci_epc *epc, phys_addr_t addr);
|
||||
int (*set_msi)(struct pci_epc *epc, u8 interrupts);
|
||||
int (*get_msi)(struct pci_epc *epc);
|
||||
int (*raise_irq)(struct pci_epc *pci_epc,
|
||||
enum pci_epc_irq_type type, u8 interrupt_num);
|
||||
int (*start)(struct pci_epc *epc);
|
||||
void (*stop)(struct pci_epc *epc);
|
||||
struct module *owner;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epc_mem - address space of the endpoint controller
|
||||
* @phys_base: physical base address of the PCI address space
|
||||
* @size: the size of the PCI address space
|
||||
* @bitmap: bitmap to manage the PCI address space
|
||||
* @pages: number of bits representing the address region
|
||||
*/
|
||||
struct pci_epc_mem {
|
||||
phys_addr_t phys_base;
|
||||
size_t size;
|
||||
unsigned long *bitmap;
|
||||
int pages;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epc - represents the PCI EPC device
|
||||
* @dev: PCI EPC device
|
||||
* @pci_epf: list of endpoint functions present in this EPC device
|
||||
* @ops: function pointers for performing endpoint operations
|
||||
* @mem: address space of the endpoint controller
|
||||
* @max_functions: max number of functions that can be configured in this EPC
|
||||
* @group: configfs group representing the PCI EPC device
|
||||
* @lock: spinlock to protect pci_epc ops
|
||||
*/
|
||||
struct pci_epc {
|
||||
struct device dev;
|
||||
struct list_head pci_epf;
|
||||
const struct pci_epc_ops *ops;
|
||||
struct pci_epc_mem *mem;
|
||||
u8 max_functions;
|
||||
struct config_group *group;
|
||||
/* spinlock to protect against concurrent access of EP controller */
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
#define to_pci_epc(device) container_of((device), struct pci_epc, dev)
|
||||
|
||||
#define pci_epc_create(dev, ops) \
|
||||
__pci_epc_create((dev), (ops), THIS_MODULE)
|
||||
#define devm_pci_epc_create(dev, ops) \
|
||||
__devm_pci_epc_create((dev), (ops), THIS_MODULE)
|
||||
|
||||
static inline void epc_set_drvdata(struct pci_epc *epc, void *data)
|
||||
{
|
||||
dev_set_drvdata(&epc->dev, data);
|
||||
}
|
||||
|
||||
static inline void *epc_get_drvdata(struct pci_epc *epc)
|
||||
{
|
||||
return dev_get_drvdata(&epc->dev);
|
||||
}
|
||||
|
||||
struct pci_epc *
|
||||
__devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner);
|
||||
struct pci_epc *
|
||||
__pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner);
|
||||
void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc);
|
||||
void pci_epc_destroy(struct pci_epc *epc);
|
||||
int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf);
|
||||
void pci_epc_linkup(struct pci_epc *epc);
|
||||
void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf);
|
||||
int pci_epc_write_header(struct pci_epc *epc, struct pci_epf_header *hdr);
|
||||
int pci_epc_set_bar(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags);
|
||||
void pci_epc_clear_bar(struct pci_epc *epc, int bar);
|
||||
int pci_epc_map_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
u64 pci_addr, size_t size);
|
||||
void pci_epc_unmap_addr(struct pci_epc *epc, phys_addr_t phys_addr);
|
||||
int pci_epc_set_msi(struct pci_epc *epc, u8 interrupts);
|
||||
int pci_epc_get_msi(struct pci_epc *epc);
|
||||
int pci_epc_raise_irq(struct pci_epc *epc, enum pci_epc_irq_type type,
|
||||
u8 interrupt_num);
|
||||
int pci_epc_start(struct pci_epc *epc);
|
||||
void pci_epc_stop(struct pci_epc *epc);
|
||||
struct pci_epc *pci_epc_get(const char *epc_name);
|
||||
void pci_epc_put(struct pci_epc *epc);
|
||||
|
||||
int pci_epc_mem_init(struct pci_epc *epc, phys_addr_t phys_addr, size_t size);
|
||||
void pci_epc_mem_exit(struct pci_epc *epc);
|
||||
void __iomem *pci_epc_mem_alloc_addr(struct pci_epc *epc,
|
||||
phys_addr_t *phys_addr, size_t size);
|
||||
void pci_epc_mem_free_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
void __iomem *virt_addr, size_t size);
|
||||
#endif /* __LINUX_PCI_EPC_H */
|
162
include/linux/pci-epf.h
Normal file
162
include/linux/pci-epf.h
Normal file
@ -0,0 +1,162 @@
|
||||
/**
|
||||
* PCI Endpoint *Function* (EPF) header file
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_PCI_EPF_H
|
||||
#define __LINUX_PCI_EPF_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
|
||||
struct pci_epf;
|
||||
|
||||
enum pci_interrupt_pin {
|
||||
PCI_INTERRUPT_UNKNOWN,
|
||||
PCI_INTERRUPT_INTA,
|
||||
PCI_INTERRUPT_INTB,
|
||||
PCI_INTERRUPT_INTC,
|
||||
PCI_INTERRUPT_INTD,
|
||||
};
|
||||
|
||||
enum pci_barno {
|
||||
BAR_0,
|
||||
BAR_1,
|
||||
BAR_2,
|
||||
BAR_3,
|
||||
BAR_4,
|
||||
BAR_5,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epf_header - represents standard configuration header
|
||||
* @vendorid: identifies device manufacturer
|
||||
* @deviceid: identifies a particular device
|
||||
* @revid: specifies a device-specific revision identifier
|
||||
* @progif_code: identifies a specific register-level programming interface
|
||||
* @subclass_code: identifies more specifically the function of the device
|
||||
* @baseclass_code: broadly classifies the type of function the device performs
|
||||
* @cache_line_size: specifies the system cacheline size in units of DWORDs
|
||||
* @subsys_vendor_id: vendor of the add-in card or subsystem
|
||||
* @subsys_id: id specific to vendor
|
||||
* @interrupt_pin: interrupt pin the device (or device function) uses
|
||||
*/
|
||||
struct pci_epf_header {
|
||||
u16 vendorid;
|
||||
u16 deviceid;
|
||||
u8 revid;
|
||||
u8 progif_code;
|
||||
u8 subclass_code;
|
||||
u8 baseclass_code;
|
||||
u8 cache_line_size;
|
||||
u16 subsys_vendor_id;
|
||||
u16 subsys_id;
|
||||
enum pci_interrupt_pin interrupt_pin;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epf_ops - set of function pointers for performing EPF operations
|
||||
* @bind: ops to perform when a EPC device has been bound to EPF device
|
||||
* @unbind: ops to perform when a binding has been lost between a EPC device
|
||||
* and EPF device
|
||||
* @linkup: ops to perform when the EPC device has established a connection with
|
||||
* a host system
|
||||
*/
|
||||
struct pci_epf_ops {
|
||||
int (*bind)(struct pci_epf *epf);
|
||||
void (*unbind)(struct pci_epf *epf);
|
||||
void (*linkup)(struct pci_epf *epf);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epf_driver - represents the PCI EPF driver
|
||||
* @probe: ops to perform when a new EPF device has been bound to the EPF driver
|
||||
* @remove: ops to perform when the binding between the EPF device and EPF
|
||||
* driver is broken
|
||||
* @driver: PCI EPF driver
|
||||
* @ops: set of function pointers for performing EPF operations
|
||||
* @owner: the owner of the module that registers the PCI EPF driver
|
||||
* @group: configfs group corresponding to the PCI EPF driver
|
||||
* @id_table: identifies EPF devices for probing
|
||||
*/
|
||||
struct pci_epf_driver {
|
||||
int (*probe)(struct pci_epf *epf);
|
||||
int (*remove)(struct pci_epf *epf);
|
||||
|
||||
struct device_driver driver;
|
||||
struct pci_epf_ops *ops;
|
||||
struct module *owner;
|
||||
struct config_group *group;
|
||||
const struct pci_epf_device_id *id_table;
|
||||
};
|
||||
|
||||
#define to_pci_epf_driver(drv) (container_of((drv), struct pci_epf_driver, \
|
||||
driver))
|
||||
|
||||
/**
|
||||
* struct pci_epf_bar - represents the BAR of EPF device
|
||||
* @phys_addr: physical address that should be mapped to the BAR
|
||||
* @size: the size of the address space present in BAR
|
||||
*/
|
||||
struct pci_epf_bar {
|
||||
dma_addr_t phys_addr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_epf - represents the PCI EPF device
|
||||
* @dev: the PCI EPF device
|
||||
* @name: the name of the PCI EPF device
|
||||
* @header: represents standard configuration header
|
||||
* @bar: represents the BAR of EPF device
|
||||
* @msi_interrupts: number of MSI interrupts required by this function
|
||||
* @func_no: unique function number within this endpoint device
|
||||
* @epc: the EPC device to which this EPF device is bound
|
||||
* @driver: the EPF driver to which this EPF device is bound
|
||||
* @list: to add pci_epf as a list of PCI endpoint functions to pci_epc
|
||||
*/
|
||||
struct pci_epf {
|
||||
struct device dev;
|
||||
const char *name;
|
||||
struct pci_epf_header *header;
|
||||
struct pci_epf_bar bar[6];
|
||||
u8 msi_interrupts;
|
||||
u8 func_no;
|
||||
|
||||
struct pci_epc *epc;
|
||||
struct pci_epf_driver *driver;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
#define to_pci_epf(epf_dev) container_of((epf_dev), struct pci_epf, dev)
|
||||
|
||||
#define pci_epf_register_driver(driver) \
|
||||
__pci_epf_register_driver((driver), THIS_MODULE)
|
||||
|
||||
static inline void epf_set_drvdata(struct pci_epf *epf, void *data)
|
||||
{
|
||||
dev_set_drvdata(&epf->dev, data);
|
||||
}
|
||||
|
||||
static inline void *epf_get_drvdata(struct pci_epf *epf)
|
||||
{
|
||||
return dev_get_drvdata(&epf->dev);
|
||||
}
|
||||
|
||||
struct pci_epf *pci_epf_create(const char *name);
|
||||
void pci_epf_destroy(struct pci_epf *epf);
|
||||
int __pci_epf_register_driver(struct pci_epf_driver *driver,
|
||||
struct module *owner);
|
||||
void pci_epf_unregister_driver(struct pci_epf_driver *driver);
|
||||
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar);
|
||||
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar);
|
||||
int pci_epf_bind(struct pci_epf *epf);
|
||||
void pci_epf_unbind(struct pci_epf *epf);
|
||||
void pci_epf_linkup(struct pci_epf *epf);
|
||||
#endif /* __LINUX_PCI_EPF_H */
|
@ -862,6 +862,8 @@
|
||||
#define PCI_DEVICE_ID_TI_X620 0xac8d
|
||||
#define PCI_DEVICE_ID_TI_X420 0xac8e
|
||||
#define PCI_DEVICE_ID_TI_XX20_FM 0xac8f
|
||||
#define PCI_DEVICE_ID_TI_DRA74x 0xb500
|
||||
#define PCI_DEVICE_ID_TI_DRA72x 0xb501
|
||||
|
||||
#define PCI_VENDOR_ID_SONY 0x104d
|
||||
|
||||
|
@ -333,6 +333,7 @@ header-y += parport.h
|
||||
header-y += patchkey.h
|
||||
header-y += pci.h
|
||||
header-y += pci_regs.h
|
||||
header-y += pcitest.h
|
||||
header-y += perf_event.h
|
||||
header-y += personality.h
|
||||
header-y += pfkeyv2.h
|
||||
|
19
include/uapi/linux/pcitest.h
Normal file
19
include/uapi/linux/pcitest.h
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* pcitest.h - PCI test uapi defines
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UAPI_LINUX_PCITEST_H
|
||||
#define __UAPI_LINUX_PCITEST_H
|
||||
|
||||
#define PCITEST_BAR _IO('P', 0x1)
|
||||
#define PCITEST_LEGACY_IRQ _IO('P', 0x2)
|
||||
#define PCITEST_MSI _IOW('P', 0x3, int)
|
||||
#define PCITEST_WRITE _IOW('P', 0x4, unsigned long)
|
||||
#define PCITEST_READ _IOW('P', 0x5, unsigned long)
|
||||
#define PCITEST_COPY _IOW('P', 0x6, unsigned long)
|
||||
|
||||
#endif /* __UAPI_LINUX_PCITEST_H */
|
186
tools/pci/pcitest.c
Normal file
186
tools/pci/pcitest.c
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Userspace PCI Endpoint Test Module
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/pcitest.h>
|
||||
|
||||
#define BILLION 1E9
|
||||
|
||||
static char *result[] = { "NOT OKAY", "OKAY" };
|
||||
|
||||
struct pci_test {
|
||||
char *device;
|
||||
char barnum;
|
||||
bool legacyirq;
|
||||
unsigned int msinum;
|
||||
bool read;
|
||||
bool write;
|
||||
bool copy;
|
||||
unsigned long size;
|
||||
};
|
||||
|
||||
static int run_test(struct pci_test *test)
|
||||
{
|
||||
long ret;
|
||||
int fd;
|
||||
struct timespec start, end;
|
||||
double time;
|
||||
|
||||
fd = open(test->device, O_RDWR);
|
||||
if (fd < 0) {
|
||||
perror("can't open PCI Endpoint Test device");
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (test->barnum >= 0 && test->barnum <= 5) {
|
||||
ret = ioctl(fd, PCITEST_BAR, test->barnum);
|
||||
fprintf(stdout, "BAR%d:\t\t", test->barnum);
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
if (test->legacyirq) {
|
||||
ret = ioctl(fd, PCITEST_LEGACY_IRQ, 0);
|
||||
fprintf(stdout, "LEGACY IRQ:\t");
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
if (test->msinum > 0 && test->msinum <= 32) {
|
||||
ret = ioctl(fd, PCITEST_MSI, test->msinum);
|
||||
fprintf(stdout, "MSI%d:\t\t", test->msinum);
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
if (test->write) {
|
||||
ret = ioctl(fd, PCITEST_WRITE, test->size);
|
||||
fprintf(stdout, "WRITE (%7ld bytes):\t\t", test->size);
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
if (test->read) {
|
||||
ret = ioctl(fd, PCITEST_READ, test->size);
|
||||
fprintf(stdout, "READ (%7ld bytes):\t\t", test->size);
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
if (test->copy) {
|
||||
ret = ioctl(fd, PCITEST_COPY, test->size);
|
||||
fprintf(stdout, "COPY (%7ld bytes):\t\t", test->size);
|
||||
if (ret < 0)
|
||||
fprintf(stdout, "TEST FAILED\n");
|
||||
else
|
||||
fprintf(stdout, "%s\n", result[ret]);
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
struct pci_test *test;
|
||||
|
||||
test = calloc(1, sizeof(*test));
|
||||
if (!test) {
|
||||
perror("Fail to allocate memory for pci_test\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* since '0' is a valid BAR number, initialize it to -1 */
|
||||
test->barnum = -1;
|
||||
|
||||
/* set default size as 100KB */
|
||||
test->size = 0x19000;
|
||||
|
||||
/* set default endpoint device */
|
||||
test->device = "/dev/pci-endpoint-test.0";
|
||||
|
||||
while ((c = getopt(argc, argv, "D:b:m:lrwcs:")) != EOF)
|
||||
switch (c) {
|
||||
case 'D':
|
||||
test->device = optarg;
|
||||
continue;
|
||||
case 'b':
|
||||
test->barnum = atoi(optarg);
|
||||
if (test->barnum < 0 || test->barnum > 5)
|
||||
goto usage;
|
||||
continue;
|
||||
case 'l':
|
||||
test->legacyirq = true;
|
||||
continue;
|
||||
case 'm':
|
||||
test->msinum = atoi(optarg);
|
||||
if (test->msinum < 1 || test->msinum > 32)
|
||||
goto usage;
|
||||
continue;
|
||||
case 'r':
|
||||
test->read = true;
|
||||
continue;
|
||||
case 'w':
|
||||
test->write = true;
|
||||
continue;
|
||||
case 'c':
|
||||
test->copy = true;
|
||||
continue;
|
||||
case 's':
|
||||
test->size = strtoul(optarg, NULL, 0);
|
||||
continue;
|
||||
case '?':
|
||||
case 'h':
|
||||
default:
|
||||
usage:
|
||||
fprintf(stderr,
|
||||
"usage: %s [options]\n"
|
||||
"Options:\n"
|
||||
"\t-D <dev> PCI endpoint test device {default: /dev/pci-endpoint-test.0}\n"
|
||||
"\t-b <bar num> BAR test (bar number between 0..5)\n"
|
||||
"\t-m <msi num> MSI test (msi number between 1..32)\n"
|
||||
"\t-r Read buffer test\n"
|
||||
"\t-w Write buffer test\n"
|
||||
"\t-c Copy buffer test\n"
|
||||
"\t-s <size> Size of buffer {default: 100KB}\n",
|
||||
argv[0]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
run_test(test);
|
||||
return 0;
|
||||
}
|
56
tools/pci/pcitest.sh
Normal file
56
tools/pci/pcitest.sh
Normal file
@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "BAR tests"
|
||||
echo
|
||||
|
||||
bar=0
|
||||
|
||||
while [ $bar -lt 6 ]
|
||||
do
|
||||
pcitest -b $bar
|
||||
bar=`expr $bar + 1`
|
||||
done
|
||||
echo
|
||||
|
||||
echo "Interrupt tests"
|
||||
echo
|
||||
|
||||
pcitest -l
|
||||
msi=1
|
||||
|
||||
while [ $msi -lt 33 ]
|
||||
do
|
||||
pcitest -m $msi
|
||||
msi=`expr $msi + 1`
|
||||
done
|
||||
echo
|
||||
|
||||
echo "Read Tests"
|
||||
echo
|
||||
|
||||
pcitest -r -s 1
|
||||
pcitest -r -s 1024
|
||||
pcitest -r -s 1025
|
||||
pcitest -r -s 1024000
|
||||
pcitest -r -s 1024001
|
||||
echo
|
||||
|
||||
echo "Write Tests"
|
||||
echo
|
||||
|
||||
pcitest -w -s 1
|
||||
pcitest -w -s 1024
|
||||
pcitest -w -s 1025
|
||||
pcitest -w -s 1024000
|
||||
pcitest -w -s 1024001
|
||||
echo
|
||||
|
||||
echo "Copy Tests"
|
||||
echo
|
||||
|
||||
pcitest -c -s 1
|
||||
pcitest -c -s 1024
|
||||
pcitest -c -s 1025
|
||||
pcitest -c -s 1024000
|
||||
pcitest -c -s 1024001
|
||||
echo
|
Loading…
Reference in New Issue
Block a user