Commit 3da1b617 authored by Bjorn Helgaas's avatar Bjorn Helgaas Committed by Bjorn Helgaas

Merge branch 'pci/lpc'

  - add support for PCI I/O port space that's neither directly accessible
    via CPU in/out instructions nor directly mapped into CPU physical
    memory space (Zhichang Yuan)

  - add support for HiSilicon Hip06/Hip07 LPC I/O space (Zhichang Yuan,
    John Garry)

* pci/lpc:
  MAINTAINERS: Add John Garry as maintainer for HiSilicon LPC driver
  HISI LPC: Add ACPI support
  ACPI / scan: Do not enumerate Indirect IO host children
  ACPI / scan: Rename acpi_is_serial_bus_slave() for more general use
  HISI LPC: Support the LPC host on Hip06/Hip07 with DT bindings
  of: Add missing I/O range exception for indirect-IO devices
  PCI: Apply the new generic I/O management on PCI IO hosts
  PCI: Add fwnode handler as input param of pci_register_io_range()
  PCI: Remove __weak tag from pci_register_io_range()
  lib: Add generic PIO mapping method
parents a5c6ad78 6183d9b3
Hisilicon Hip06 Low Pin Count device
Hisilicon Hip06 SoCs implement a Low Pin Count (LPC) controller, which
provides I/O access to some legacy ISA devices.
Hip06 is based on arm64 architecture where there is no I/O space. So, the
I/O ports here are not CPU addresses, and there is no 'ranges' property in
LPC device node.
Required properties:
- compatible: value should be as follows:
(a) "hisilicon,hip06-lpc"
(b) "hisilicon,hip07-lpc"
- #address-cells: must be 2 which stick to the ISA/EISA binding doc.
- #size-cells: must be 1 which stick to the ISA/EISA binding doc.
- reg: base memory range where the LPC register set is mapped.
Note:
The node name before '@' must be "isa" to represent the binding stick to the
ISA/EISA binding specification.
Example:
isa@a01b0000 {
compatible = "hisilicon,hip06-lpc";
#address-cells = <2>;
#size-cells = <1>;
reg = <0x0 0xa01b0000 0x0 0x1000>;
ipmi0: bt@e4 {
compatible = "ipmi-bt";
device_type = "ipmi";
reg = <0x01 0xe4 0x04>;
};
};
...@@ -6386,6 +6386,13 @@ W: http://www.hisilicon.com ...@@ -6386,6 +6386,13 @@ W: http://www.hisilicon.com
S: Maintained S: Maintained
F: drivers/net/ethernet/hisilicon/hns3/ F: drivers/net/ethernet/hisilicon/hns3/
HISILICON LPC BUS DRIVER
M: john.garry@huawei.com
W: http://www.hisilicon.com
S: Maintained
F: drivers/bus/hisi_lpc.c
F: Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
HISILICON NETWORK SUBSYSTEM DRIVER HISILICON NETWORK SUBSYSTEM DRIVER
M: Yisen Zhuang <yisen.zhuang@huawei.com> M: Yisen Zhuang <yisen.zhuang@huawei.com>
M: Salil Mehta <salil.mehta@huawei.com> M: Salil Mehta <salil.mehta@huawei.com>
......
...@@ -729,7 +729,8 @@ static void acpi_pci_root_validate_resources(struct device *dev, ...@@ -729,7 +729,8 @@ static void acpi_pci_root_validate_resources(struct device *dev,
} }
} }
static void acpi_pci_root_remap_iospace(struct resource_entry *entry) static void acpi_pci_root_remap_iospace(struct fwnode_handle *fwnode,
struct resource_entry *entry)
{ {
#ifdef PCI_IOBASE #ifdef PCI_IOBASE
struct resource *res = entry->res; struct resource *res = entry->res;
...@@ -738,7 +739,7 @@ static void acpi_pci_root_remap_iospace(struct resource_entry *entry) ...@@ -738,7 +739,7 @@ static void acpi_pci_root_remap_iospace(struct resource_entry *entry)
resource_size_t length = resource_size(res); resource_size_t length = resource_size(res);
unsigned long port; unsigned long port;
if (pci_register_io_range(cpu_addr, length)) if (pci_register_io_range(fwnode, cpu_addr, length))
goto err; goto err;
port = pci_address_to_pio(cpu_addr); port = pci_address_to_pio(cpu_addr);
...@@ -780,7 +781,8 @@ int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info) ...@@ -780,7 +781,8 @@ int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info)
else { else {
resource_list_for_each_entry_safe(entry, tmp, list) { resource_list_for_each_entry_safe(entry, tmp, list) {
if (entry->res->flags & IORESOURCE_IO) if (entry->res->flags & IORESOURCE_IO)
acpi_pci_root_remap_iospace(entry); acpi_pci_root_remap_iospace(&device->fwnode,
entry);
if (entry->res->flags & IORESOURCE_DISABLED) if (entry->res->flags & IORESOURCE_DISABLED)
resource_list_destroy_entry(entry); resource_list_destroy_entry(entry);
......
...@@ -1524,11 +1524,25 @@ static int acpi_check_serial_bus_slave(struct acpi_resource *ares, void *data) ...@@ -1524,11 +1524,25 @@ static int acpi_check_serial_bus_slave(struct acpi_resource *ares, void *data)
return -1; return -1;
} }
static bool acpi_is_serial_bus_slave(struct acpi_device *device) static bool acpi_is_indirect_io_slave(struct acpi_device *device)
{
struct acpi_device *parent = device->parent;
const struct acpi_device_id indirect_io_hosts[] = {
{"HISI0191", 0},
{}
};
return parent && !acpi_match_device_ids(parent, indirect_io_hosts);
}
static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
{ {
struct list_head resource_list; struct list_head resource_list;
bool is_serial_bus_slave = false; bool is_serial_bus_slave = false;
if (acpi_is_indirect_io_slave(device))
return true;
/* Macs use device properties in lieu of _CRS resources */ /* Macs use device properties in lieu of _CRS resources */
if (x86_apple_machine && if (x86_apple_machine &&
(fwnode_property_present(&device->fwnode, "spiSclkPeriod") || (fwnode_property_present(&device->fwnode, "spiSclkPeriod") ||
...@@ -1560,7 +1574,8 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, ...@@ -1560,7 +1574,8 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,
acpi_bus_get_flags(device); acpi_bus_get_flags(device);
device->flags.match_driver = false; device->flags.match_driver = false;
device->flags.initialized = true; device->flags.initialized = true;
device->flags.serial_bus_slave = acpi_is_serial_bus_slave(device); device->flags.enumeration_by_parent =
acpi_device_enumeration_by_parent(device);
acpi_device_clear_enumerated(device); acpi_device_clear_enumerated(device);
device_initialize(&device->dev); device_initialize(&device->dev);
dev_set_uevent_suppress(&device->dev, true); dev_set_uevent_suppress(&device->dev, true);
...@@ -1858,10 +1873,10 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, ...@@ -1858,10 +1873,10 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used,
static void acpi_default_enumeration(struct acpi_device *device) static void acpi_default_enumeration(struct acpi_device *device)
{ {
/* /*
* Do not enumerate SPI/I2C/UART slaves as they will be enumerated by * Do not enumerate devices with enumeration_by_parent flag set as
* their respective parents. * they will be enumerated by their respective parents.
*/ */
if (!device->flags.serial_bus_slave) { if (!device->flags.enumeration_by_parent) {
acpi_create_platform_device(device, NULL); acpi_create_platform_device(device, NULL);
acpi_device_set_enumerated(device); acpi_device_set_enumerated(device);
} else { } else {
...@@ -1958,7 +1973,7 @@ static void acpi_bus_attach(struct acpi_device *device) ...@@ -1958,7 +1973,7 @@ static void acpi_bus_attach(struct acpi_device *device)
return; return;
device->flags.match_driver = true; device->flags.match_driver = true;
if (ret > 0 && !device->flags.serial_bus_slave) { if (ret > 0 && !device->flags.enumeration_by_parent) {
acpi_device_set_enumerated(device); acpi_device_set_enumerated(device);
goto ok; goto ok;
} }
...@@ -1967,10 +1982,10 @@ static void acpi_bus_attach(struct acpi_device *device) ...@@ -1967,10 +1982,10 @@ static void acpi_bus_attach(struct acpi_device *device)
if (ret < 0) if (ret < 0)
return; return;
if (!device->pnp.type.platform_id && !device->flags.serial_bus_slave) if (device->pnp.type.platform_id || device->flags.enumeration_by_parent)
acpi_device_set_enumerated(device);
else
acpi_default_enumeration(device); acpi_default_enumeration(device);
else
acpi_device_set_enumerated(device);
ok: ok:
list_for_each_entry(child, &device->children, node) list_for_each_entry(child, &device->children, node)
......
...@@ -65,6 +65,14 @@ config BRCMSTB_GISB_ARB ...@@ -65,6 +65,14 @@ config BRCMSTB_GISB_ARB
arbiter. This driver provides timeout and target abort error handling arbiter. This driver provides timeout and target abort error handling
and internal bus master decoding. and internal bus master decoding.
config HISILICON_LPC
bool "Support for ISA I/O space on HiSilicon Hip06/7"
depends on ARM64 && (ARCH_HISI || COMPILE_TEST)
select INDIRECT_PIO
help
Driver to enable I/O access to devices attached to the Low Pin
Count bus on the HiSilicon Hip06/7 SoC.
config IMX_WEIM config IMX_WEIM
bool "Freescale EIM DRIVER" bool "Freescale EIM DRIVER"
depends on ARCH_MXC depends on ARCH_MXC
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
obj-$(CONFIG_ARM_CCI) += arm-cci.o obj-$(CONFIG_ARM_CCI) += arm-cci.o
obj-$(CONFIG_ARM_CCN) += arm-ccn.o obj-$(CONFIG_ARM_CCN) += arm-ccn.o
obj-$(CONFIG_HISILICON_LPC) += hisi_lpc.o
obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o
obj-$(CONFIG_IMX_WEIM) += imx-weim.o obj-$(CONFIG_IMX_WEIM) += imx-weim.o
obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o
......
This diff is collapsed.
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
#define pr_fmt(fmt) "OF: " fmt #define pr_fmt(fmt) "OF: " fmt
#include <linux/device.h> #include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/logic_pio.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/pci.h> #include <linux/pci.h>
...@@ -333,7 +335,8 @@ int of_pci_range_to_resource(struct of_pci_range *range, ...@@ -333,7 +335,8 @@ int of_pci_range_to_resource(struct of_pci_range *range,
if (res->flags & IORESOURCE_IO) { if (res->flags & IORESOURCE_IO) {
unsigned long port; unsigned long port;
err = pci_register_io_range(range->cpu_addr, range->size); err = pci_register_io_range(&np->fwnode, range->cpu_addr,
range->size);
if (err) if (err)
goto invalid_range; goto invalid_range;
port = pci_address_to_pio(range->cpu_addr); port = pci_address_to_pio(range->cpu_addr);
...@@ -560,9 +563,14 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus, ...@@ -560,9 +563,14 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
* that translation is impossible (that is we are not dealing with a value * that translation is impossible (that is we are not dealing with a value
* that can be mapped to a cpu physical address). This is not really specified * that can be mapped to a cpu physical address). This is not really specified
* that way, but this is traditionally the way IBM at least do things * that way, but this is traditionally the way IBM at least do things
*
* Whenever the translation fails, the *host pointer will be set to the
* device that had registered logical PIO mapping, and the return code is
* relative to that node.
*/ */
static u64 __of_translate_address(struct device_node *dev, static u64 __of_translate_address(struct device_node *dev,
const __be32 *in_addr, const char *rprop) const __be32 *in_addr, const char *rprop,
struct device_node **host)
{ {
struct device_node *parent = NULL; struct device_node *parent = NULL;
struct of_bus *bus, *pbus; struct of_bus *bus, *pbus;
...@@ -575,6 +583,7 @@ static u64 __of_translate_address(struct device_node *dev, ...@@ -575,6 +583,7 @@ static u64 __of_translate_address(struct device_node *dev,
/* Increase refcount at current level */ /* Increase refcount at current level */
of_node_get(dev); of_node_get(dev);
*host = NULL;
/* Get parent & match bus type */ /* Get parent & match bus type */
parent = of_get_parent(dev); parent = of_get_parent(dev);
if (parent == NULL) if (parent == NULL)
...@@ -595,6 +604,8 @@ static u64 __of_translate_address(struct device_node *dev, ...@@ -595,6 +604,8 @@ static u64 __of_translate_address(struct device_node *dev,
/* Translate */ /* Translate */
for (;;) { for (;;) {
struct logic_pio_hwaddr *iorange;
/* Switch to parent bus */ /* Switch to parent bus */
of_node_put(dev); of_node_put(dev);
dev = parent; dev = parent;
...@@ -607,6 +618,19 @@ static u64 __of_translate_address(struct device_node *dev, ...@@ -607,6 +618,19 @@ static u64 __of_translate_address(struct device_node *dev,
break; break;
} }
/*
* For indirectIO device which has no ranges property, get
* the address from reg directly.
*/
iorange = find_io_range_by_fwnode(&dev->fwnode);
if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {
result = of_read_number(addr + 1, na - 1);
pr_debug("indirectIO matched(%pOF) 0x%llx\n",
dev, result);
*host = of_node_get(dev);
break;
}
/* Get new parent bus and counts */ /* Get new parent bus and counts */
pbus = of_match_bus(parent); pbus = of_match_bus(parent);
pbus->count_cells(dev, &pna, &pns); pbus->count_cells(dev, &pna, &pns);
...@@ -638,13 +662,32 @@ static u64 __of_translate_address(struct device_node *dev, ...@@ -638,13 +662,32 @@ static u64 __of_translate_address(struct device_node *dev,
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr) u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{ {
return __of_translate_address(dev, in_addr, "ranges"); struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}
return ret;
} }
EXPORT_SYMBOL(of_translate_address); EXPORT_SYMBOL(of_translate_address);
u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr) u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr)
{ {
return __of_translate_address(dev, in_addr, "dma-ranges"); struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, in_addr, "dma-ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}
return ret;
} }
EXPORT_SYMBOL(of_translate_dma_address); EXPORT_SYMBOL(of_translate_dma_address);
...@@ -686,29 +729,48 @@ const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, ...@@ -686,29 +729,48 @@ const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
} }
EXPORT_SYMBOL(of_get_address); EXPORT_SYMBOL(of_get_address);
static u64 of_translate_ioport(struct device_node *dev, const __be32 *in_addr,
u64 size)
{
u64 taddr;
unsigned long port;
struct device_node *host;
taddr = __of_translate_address(dev, in_addr, "ranges", &host);
if (host) {
/* host-specific port access */
port = logic_pio_trans_hwaddr(&host->fwnode, taddr, size);
of_node_put(host);
} else {
/* memory-mapped I/O range */
port = pci_address_to_pio(taddr);
}
if (port == (unsigned long)-1)
return OF_BAD_ADDR;
return port;
}
static int __of_address_to_resource(struct device_node *dev, static int __of_address_to_resource(struct device_node *dev,
const __be32 *addrp, u64 size, unsigned int flags, const __be32 *addrp, u64 size, unsigned int flags,
const char *name, struct resource *r) const char *name, struct resource *r)
{ {
u64 taddr; u64 taddr;
if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0) if (flags & IORESOURCE_MEM)
taddr = of_translate_address(dev, addrp);
else if (flags & IORESOURCE_IO)
taddr = of_translate_ioport(dev, addrp, size);
else
return -EINVAL; return -EINVAL;
taddr = of_translate_address(dev, addrp);
if (taddr == OF_BAD_ADDR) if (taddr == OF_BAD_ADDR)
return -EINVAL; return -EINVAL;
memset(r, 0, sizeof(struct resource)); memset(r, 0, sizeof(struct resource));
if (flags & IORESOURCE_IO) {
unsigned long port; r->start = taddr;
port = pci_address_to_pio(taddr); r->end = taddr + size - 1;
if (port == (unsigned long)-1)
return -EINVAL;
r->start = port;
r->end = port + size - 1;
} else {
r->start = taddr;
r->end = taddr + size - 1;
}
r->flags = flags; r->flags = flags;
r->name = name ? name : dev->full_name; r->name = name ? name : dev->full_name;
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/log2.h> #include <linux/log2.h>
#include <linux/logic_pio.h>
#include <linux/pci-aspm.h> #include <linux/pci-aspm.h>
#include <linux/pm_wakeup.h> #include <linux/pm_wakeup.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
...@@ -3440,68 +3441,35 @@ int pci_request_regions_exclusive(struct pci_dev *pdev, const char *res_name) ...@@ -3440,68 +3441,35 @@ int pci_request_regions_exclusive(struct pci_dev *pdev, const char *res_name)
} }
EXPORT_SYMBOL(pci_request_regions_exclusive); EXPORT_SYMBOL(pci_request_regions_exclusive);
#ifdef PCI_IOBASE
struct io_range {
struct list_head list;
phys_addr_t start;
resource_size_t size;
};
static LIST_HEAD(io_range_list);
static DEFINE_SPINLOCK(io_range_lock);
#endif
/* /*
* Record the PCI IO range (expressed as CPU physical address + size). * Record the PCI IO range (expressed as CPU physical address + size).
* Return a negative value if an error has occured, zero otherwise * Return a negative value if an error has occured, zero otherwise
*/ */
int __weak pci_register_io_range(phys_addr_t addr, resource_size_t size) int pci_register_io_range(struct fwnode_handle *fwnode, phys_addr_t addr,
resource_size_t size)
{ {
int err = 0; int ret = 0;
#ifdef PCI_IOBASE #ifdef PCI_IOBASE
struct io_range *range; struct logic_pio_hwaddr *range;
resource_size_t allocated_size = 0;
/* check if the range hasn't been previously recorded */
spin_lock(&io_range_lock);
list_for_each_entry(range, &io_range_list, list) {
if (addr >= range->start && addr + size <= range->start + size) {
/* range already registered, bail out */
goto end_register;
}
allocated_size += range->size;
}
/* range not registed yet, check for available space */ if (!size || addr + size < addr)
if (allocated_size + size - 1 > IO_SPACE_LIMIT) { return -EINVAL;
/* if it's too big check if 64K space can be reserved */
if (allocated_size + SZ_64K - 1 > IO_SPACE_LIMIT) {
err = -E2BIG;
goto end_register;
}
size = SZ_64K;
pr_warn("Requested IO range too big, new size set to 64K\n");
}
/* add the range to the list */
range = kzalloc(sizeof(*range), GFP_ATOMIC); range = kzalloc(sizeof(*range), GFP_ATOMIC);
if (!range) { if (!range)
err = -ENOMEM; return -ENOMEM;
goto end_register;
}
range->start = addr; range->fwnode = fwnode;
range->size = size; range->size = size;
range->hw_start = addr;
range->flags = LOGIC_PIO_CPU_MMIO;
list_add_tail(&range->list, &io_range_list); ret = logic_pio_register_range(range);
if (ret)
end_register: kfree(range);
spin_unlock(&io_range_lock);
#endif #endif
return err; return ret;
} }
phys_addr_t pci_pio_to_address(unsigned long pio) phys_addr_t pci_pio_to_address(unsigned long pio)
...@@ -3509,21 +3477,10 @@ phys_addr_t pci_pio_to_address(unsigned long pio) ...@@ -3509,21 +3477,10 @@ phys_addr_t pci_pio_to_address(unsigned long pio)
phys_addr_t address = (phys_addr_t)OF_BAD_ADDR; phys_addr_t address = (phys_addr_t)OF_BAD_ADDR;
#ifdef PCI_IOBASE #ifdef PCI_IOBASE
struct io_range *range; if (pio >= MMIO_UPPER_LIMIT)
resource_size_t allocated_size = 0;
if (pio > IO_SPACE_LIMIT)
return address; return address;
spin_lock(&io_range_lock); address = logic_pio_to_hwaddr(pio);
list_for_each_entry(range, &io_range_list, list) {
if (pio >= allocated_size && pio < allocated_size + range->size) {
address = range->start + pio - allocated_size;
break;
}
allocated_size += range->size;
}
spin_unlock(&io_range_lock);
#endif #endif
return address; return address;
...@@ -3532,21 +3489,7 @@ phys_addr_t pci_pio_to_address(unsigned long pio) ...@@ -3532,21 +3489,7 @@ phys_addr_t pci_pio_to_address(unsigned long pio)
unsigned long __weak pci_address_to_pio(phys_addr_t address) unsigned long __weak pci_address_to_pio(phys_addr_t address)
{ {
#ifdef PCI_IOBASE #ifdef PCI_IOBASE
struct io_range *res; return logic_pio_trans_cpuaddr(address);
resource_size_t offset = 0;
unsigned long addr = -1;
spin_lock(&io_range_lock);
list_for_each_entry(res, &io_range_list, list) {
if (address >= res->start && address < res->start + res->size) {
addr = address - res->start + offset;
break;
}
offset += res->size;
}
spin_unlock(&io_range_lock);
return addr;
#else #else
if (address > IO_SPACE_LIMIT) if (address > IO_SPACE_LIMIT)
return (unsigned long)-1; return (unsigned long)-1;
......
...@@ -215,7 +215,7 @@ struct acpi_device_flags { ...@@ -215,7 +215,7 @@ struct acpi_device_flags {
u32 of_compatible_ok:1; u32 of_compatible_ok:1;
u32 coherent_dma:1; u32 coherent_dma:1;
u32 cca_seen:1; u32 cca_seen:1;
u32 serial_bus_slave:1; u32 enumeration_by_parent:1;
u32 reserved:19; u32 reserved:19;
}; };
......
...@@ -351,6 +351,8 @@ static inline void writesq(volatile void __iomem *addr, const void *buffer, ...@@ -351,6 +351,8 @@ static inline void writesq(volatile void __iomem *addr, const void *buffer,
#define IO_SPACE_LIMIT 0xffff #define IO_SPACE_LIMIT 0xffff
#endif #endif
#include <linux/logic_pio.h>
/* /*
* {in,out}{b,w,l}() access little endian I/O. {in,out}{b,w,l}_p() can be * {in,out}{b,w,l}() access little endian I/O. {in,out}{b,w,l}_p() can be
* implemented on hardware that needs an additional delay for I/O accesses to * implemented on hardware that needs an additional delay for I/O accesses to
...@@ -899,7 +901,7 @@ static inline void iounmap(void __iomem *addr) ...@@ -899,7 +901,7 @@ static inline void iounmap(void __iomem *addr)
#define ioport_map ioport_map #define ioport_map ioport_map
static inline void __iomem *ioport_map(unsigned long port, unsigned int nr) static inline void __iomem *ioport_map(unsigned long port, unsigned int nr)
{ {
return PCI_IOBASE + (port & IO_SPACE_LIMIT); return PCI_IOBASE + (port & MMIO_UPPER_LIMIT);
} }
#endif #endif
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 HiSilicon Limited, All Rights Reserved.
* Author: Gabriele Paoloni <gabriele.paoloni@huawei.com>
* Author: Zhichang Yuan <yuanzhichang@hisilicon.com>
*/
#ifndef __LINUX_LOGIC_PIO_H
#define __LINUX_LOGIC_PIO_H
#include <linux/fwnode.h>
enum {
LOGIC_PIO_INDIRECT, /* Indirect IO flag */
LOGIC_PIO_CPU_MMIO, /* Memory-mapped IO flag */
};
struct logic_pio_hwaddr {
struct list_head list;
struct fwnode_handle *fwnode;
resource_size_t hw_start;
resource_size_t io_start;
resource_size_t size; /* range size populated */
unsigned long flags;
void *hostdata;
const struct logic_pio_host_ops *ops;
};
struct logic_pio_host_ops {
u32 (*in)(void *hostdata, unsigned long addr, size_t dwidth);
void (*out)(void *hostdata, unsigned long addr, u32 val,
size_t dwidth);
u32 (*ins)(void *hostdata, unsigned long addr, void *buffer,
size_t dwidth, unsigned int count);
void (*outs)(void *hostdata, unsigned long addr, const void *buffer,
size_t dwidth, unsigned int count);
};
#ifdef CONFIG_INDIRECT_PIO
u8 logic_inb(unsigned long addr);
void logic_outb(u8 value, unsigned long addr);
void logic_outw(u16 value, unsigned long addr);
void logic_outl(u32 value, unsigned long addr);
u16 logic_inw(unsigned long addr);
u32 logic_inl(unsigned long addr);
void logic_outb(u8 value, unsigned long addr);
void logic_outw(u16 value, unsigned long addr);
void logic_outl(u32 value, unsigned long addr);
void logic_insb(unsigned long addr, void *buffer, unsigned int count);
void logic_insl(unsigned long addr, void *buffer, unsigned int count);
void logic_insw(unsigned long addr, void *buffer, unsigned int count);
void logic_outsb(unsigned long addr, const void *buffer, unsigned int count);
void logic_outsw(unsigned long addr, const void *buffer, unsigned int count);
void logic_outsl(unsigned long addr, const void *buffer, unsigned int count);
#ifndef inb
#define inb logic_inb
#endif
#ifndef inw
#define inw logic_inw
#endif
#ifndef inl
#define inl logic_inl
#endif
#ifndef outb
#define outb logic_outb
#endif
#ifndef outw
#define outw logic_outw
#endif
#ifndef outl
#define outl logic_outl
#endif
#ifndef insb
#define insb logic_insb
#endif
#ifndef insw
#define insw logic_insw
#endif
#ifndef insl
#define insl logic_insl
#endif
#ifndef outsb
#define outsb logic_outsb
#endif
#ifndef outsw
#define outsw logic_outsw
#endif
#ifndef outsl
#define outsl logic_outsl
#endif
/*
* We reserve 0x4000 bytes for Indirect IO as so far this library is only
* used by the HiSilicon LPC Host. If needed, we can reserve a wider IO
* area by redefining the macro below.
*/
#define PIO_INDIRECT_SIZE 0x4000
#define MMIO_UPPER_LIMIT (IO_SPACE_LIMIT - PIO_INDIRECT_SIZE)
#else
#define MMIO_UPPER_LIMIT IO_SPACE_LIMIT
#endif /* CONFIG_INDIRECT_PIO */
struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode);
unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
resource_size_t hw_addr, resource_size_t size);
int logic_pio_register_range(struct logic_pio_hwaddr *newrange);
resource_size_t logic_pio_to_hwaddr(unsigned long pio);
unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr);
#endif /* __LINUX_LOGIC_PIO_H */
...@@ -1226,7 +1226,8 @@ int __must_check pci_bus_alloc_resource(struct pci_bus *bus, ...@@ -1226,7 +1226,8 @@ int __must_check pci_bus_alloc_resource(struct pci_bus *bus,
void *alignf_data); void *alignf_data);
int pci_register_io_range(phys_addr_t addr, resource_size_t size); int pci_register_io_range(struct fwnode_handle *fwnode, phys_addr_t addr,
resource_size_t size);
unsigned long pci_address_to_pio(phys_addr_t addr); unsigned long pci_address_to_pio(phys_addr_t addr);
phys_addr_t pci_pio_to_address(unsigned long pio); phys_addr_t pci_pio_to_address(unsigned long pio);
int pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr); int pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr);
......
...@@ -55,6 +55,22 @@ config ARCH_USE_CMPXCHG_LOCKREF ...@@ -55,6 +55,22 @@ config ARCH_USE_CMPXCHG_LOCKREF
config ARCH_HAS_FAST_MULTIPLIER config ARCH_HAS_FAST_MULTIPLIER
bool bool
config INDIRECT_PIO
bool "Access I/O in non-MMIO mode"
depends on ARM64
help
On some platforms where no separate I/O space exists, there are I/O
hosts which can not be accessed in MMIO mode. Using the logical PIO
mechanism, the host-local I/O resource can be mapped into system
logic PIO space shared with MMIO hosts, such as PCI/PCIe, then the
system can access the I/O devices with the mapped-logic PIO through
I/O accessors.
This way has relatively little I/O performance cost. Please make
sure your devices really need this configure item enabled.
When in doubt, say N.
config CRC_CCITT config CRC_CCITT
tristate "CRC-CCITT functions" tristate "CRC-CCITT functions"
help help
......
...@@ -81,6 +81,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomap_copy.o devres.o ...@@ -81,6 +81,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomap_copy.o devres.o
obj-$(CONFIG_CHECK_SIGNATURE) += check_signature.o obj-$(CONFIG_CHECK_SIGNATURE) += check_signature.o
obj-$(CONFIG_DEBUG_LOCKING_API_SELFTESTS) += locking-selftest.o obj-$(CONFIG_DEBUG_LOCKING_API_SELFTESTS) += locking-selftest.o
obj-y += logic_pio.o
obj-$(CONFIG_GENERIC_HWEIGHT) += hweight.o obj-$(CONFIG_GENERIC_HWEIGHT) += hweight.o
obj-$(CONFIG_BTREE) += btree.o obj-$(CONFIG_BTREE) += btree.o
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 HiSilicon Limited, All Rights Reserved.
* Author: Gabriele Paoloni <gabriele.paoloni@huawei.com>
* Author: Zhichang Yuan <yuanzhichang@hisilicon.com>
*/
#define pr_fmt(fmt) "LOGIC PIO: " fmt
#include <linux/of.h>
#include <linux/io.h>
#include <linux/logic_pio.h>
#include <linux/mm.h>
#include <linux/rculist.h>
#include <linux/sizes.h>
#include <linux/slab.h>
/* The unique hardware address list */
static LIST_HEAD(io_range_list);
static DEFINE_MUTEX(io_range_mutex);
/* Consider a kernel general helper for this */
#define in_range(b, first, len) ((b) >= (first) && (b) < (first) + (len))
/**
* logic_pio_register_range - register logical PIO range for a host
* @new_range: pointer to the IO range to be registered.
*
* Returns 0 on success, the error code in case of failure.
*
* Register a new IO range node in the IO range list.
*/
int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
{
struct logic_pio_hwaddr *range;
resource_size_t start;
resource_size_t end;
resource_size_t mmio_sz = 0;
resource_size_t iio_sz = MMIO_UPPER_LIMIT;
int ret = 0;
if (!new_range || !new_range->fwnode || !new_range->size)
return -EINVAL;
start = new_range->hw_start;
end = new_range->hw_start + new_range->size;
mutex_lock(&io_range_mutex);
list_for_each_entry_rcu(range, &io_range_list, list) {
if (range->fwnode == new_range->fwnode) {
/* range already there */
goto end_register;
}
if (range->flags == LOGIC_PIO_CPU_MMIO &&
new_range->flags == LOGIC_PIO_CPU_MMIO) {
/* for MMIO ranges we need to check for overlap */
if (start >= range->hw_start + range->size ||
end < range->hw_start) {
mmio_sz += range->size;
} else {
ret = -EFAULT;
goto end_register;
}
} else if (range->flags == LOGIC_PIO_INDIRECT &&
new_range->flags == LOGIC_PIO_INDIRECT) {
iio_sz += range->size;
}
}
/* range not registered yet, check for available space */
if (new_range->flags == LOGIC_PIO_CPU_MMIO) {
if (mmio_sz + new_range->size - 1 > MMIO_UPPER_LIMIT) {
/* if it's too big check if 64K space can be reserved */
if (mmio_sz + SZ_64K - 1 > MMIO_UPPER_LIMIT) {
ret = -E2BIG;
goto end_register;
}
new_range->size = SZ_64K;
pr_warn("Requested IO range too big, new size set to 64K\n");
}
new_range->io_start = mmio_sz;
} else if (new_range->flags == LOGIC_PIO_INDIRECT) {
if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) {
ret = -E2BIG;
goto end_register;
}
new_range->io_start = iio_sz;
} else {
/* invalid flag */
ret = -EINVAL;
goto end_register;
}
list_add_tail_rcu(&new_range->list, &io_range_list);
end_register:
mutex_unlock(&io_range_mutex);
return ret;
}
/**
* find_io_range_by_fwnode - find logical PIO range for given FW node
* @fwnode: FW node handle associated with logical PIO range
*
* Returns pointer to node on success, NULL otherwise.
*
* Traverse the io_range_list to find the registered node for @fwnode.
*/
struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode)
{
struct logic_pio_hwaddr *range;
list_for_each_entry_rcu(range, &io_range_list, list) {
if (range->fwnode == fwnode)
return range;
}
return NULL;
}
/* Return a registered range given an input PIO token */
static struct logic_pio_hwaddr *find_io_range(unsigned long pio)
{
struct logic_pio_hwaddr *range;
list_for_each_entry_rcu(range, &io_range_list, list) {
if (in_range(pio, range->io_start, range->size))
return range;
}
pr_err("PIO entry token %lx invalid\n", pio);
return NULL;
}
/**
* logic_pio_to_hwaddr - translate logical PIO to HW address
* @pio: logical PIO value
*
* Returns HW address if valid, ~0 otherwise.
*
* Translate the input logical PIO to the corresponding hardware address.
* The input PIO should be unique in the whole logical PIO space.
*/
resource_size_t logic_pio_to_hwaddr(unsigned long pio)
{
struct logic_pio_hwaddr *range;
range = find_io_range(pio);
if (range)
return range->hw_start + pio - range->io_start;
return (resource_size_t)~0;
}
/**
* logic_pio_trans_hwaddr - translate HW address to logical PIO
* @fwnode: FW node reference for the host
* @addr: Host-relative HW address
* @size: size to translate
*
* Returns Logical PIO value if successful, ~0UL otherwise
*/
unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
resource_size_t addr, resource_size_t size)
{
struct logic_pio_hwaddr *range;
range = find_io_range_by_fwnode(fwnode);
if (!range || range->flags == LOGIC_PIO_CPU_MMIO) {
pr_err("IO range not found or invalid\n");
return ~0UL;
}
if (range->size < size) {
pr_err("resource size %pa cannot fit in IO range size %pa\n",
&size, &range->size);
return ~0UL;
}
return addr - range->hw_start + range->io_start;
}
unsigned long logic_pio_trans_cpuaddr(resource_size_t addr)
{
struct logic_pio_hwaddr *range;
list_for_each_entry_rcu(range, &io_range_list, list) {
if (range->flags != LOGIC_PIO_CPU_MMIO)
continue;
if (in_range(addr, range->hw_start, range->size))
return addr - range->hw_start + range->io_start;
}
pr_err("addr %llx not registered in io_range_list\n",
(unsigned long long) addr);
return ~0UL;
}
#if defined(CONFIG_INDIRECT_PIO) && defined(PCI_IOBASE)
#define BUILD_LOGIC_IO(bw, type) \
type logic_in##bw(unsigned long addr) \
{ \
type ret = (type)~0; \
\
if (addr < MMIO_UPPER_LIMIT) { \
ret = read##bw(PCI_IOBASE + addr); \
} else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \
struct logic_pio_hwaddr *entry = find_io_range(addr); \
\
if (entry && entry->ops) \
ret = entry->ops->in(entry->hostdata, \
addr, sizeof(type)); \
else \
WARN_ON_ONCE(1); \
} \
return ret; \
} \
\
void logic_out##bw(type value, unsigned long addr) \
{ \
if (addr < MMIO_UPPER_LIMIT) { \
write##bw(value, PCI_IOBASE + addr); \
} else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \
struct logic_pio_hwaddr *entry = find_io_range(addr); \
\
if (entry && entry->ops) \
entry->ops->out(entry->hostdata, \
addr, value, sizeof(type)); \
else \
WARN_ON_ONCE(1); \
} \
} \
\
void logic_ins##bw(unsigned long addr, void *buffer, \
unsigned int count) \
{ \
if (addr < MMIO_UPPER_LIMIT) { \
reads##bw(PCI_IOBASE + addr, buffer, count); \
} else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \
struct logic_pio_hwaddr *entry = find_io_range(addr); \
\
if (entry && entry->ops) \
entry->ops->ins(entry->hostdata, \
addr, buffer, sizeof(type), count); \
else \
WARN_ON_ONCE(1); \
} \
\
} \
\
void logic_outs##bw(unsigned long addr, const void *buffer, \
unsigned int count) \
{ \
if (addr < MMIO_UPPER_LIMIT) { \
writes##bw(PCI_IOBASE + addr, buffer, count); \
} else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \
struct logic_pio_hwaddr *entry = find_io_range(addr); \
\
if (entry && entry->ops) \
entry->ops->outs(entry->hostdata, \
addr, buffer, sizeof(type), count); \
else \
WARN_ON_ONCE(1); \
} \
}
BUILD_LOGIC_IO(b, u8)
EXPORT_SYMBOL(logic_inb);
EXPORT_SYMBOL(logic_insb);
EXPORT_SYMBOL(logic_outb);
EXPORT_SYMBOL(logic_outsb);
BUILD_LOGIC_IO(w, u16)
EXPORT_SYMBOL(logic_inw);
EXPORT_SYMBOL(logic_insw);
EXPORT_SYMBOL(logic_outw);
EXPORT_SYMBOL(logic_outsw);
BUILD_LOGIC_IO(l, u32)
EXPORT_SYMBOL(logic_inl);
EXPORT_SYMBOL(logic_insl);
EXPORT_SYMBOL(logic_outl);
EXPORT_SYMBOL(logic_outsl);
#endif /* CONFIG_INDIRECT_PIO && PCI_IOBASE */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment