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
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Hisilicon Limited, All Rights Reserved.
* Author: Zhichang Yuan <yuanzhichang@hisilicon.com>
* Author: Zou Rongrong <zourongrong@huawei.com>
* Author: John Garry <john.garry@huawei.com>
*/
#include <linux/acpi.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/logic_pio.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/slab.h>
#define DRV_NAME "hisi-lpc"
/*
* Setting this bit means each IO operation will target a different port
* address; 0 means repeated IO operations will use the same port,
* such as BT.
*/
#define FG_INCRADDR_LPC 0x02
struct lpc_cycle_para {
unsigned int opflags;
unsigned int csize; /* data length of each operation */
};
struct hisi_lpc_dev {
spinlock_t cycle_lock;
void __iomem *membase;
struct logic_pio_hwaddr *io_host;
};
/* The max IO cycle counts supported is four per operation at maximum */
#define LPC_MAX_DWIDTH 4
#define LPC_REG_STARTUP_SIGNAL 0x00
#define LPC_REG_STARTUP_SIGNAL_START BIT(0)
#define LPC_REG_OP_STATUS 0x04
#define LPC_REG_OP_STATUS_IDLE BIT(0)
#define LPC_REG_OP_STATUS_FINISHED BIT(1)
#define LPC_REG_OP_LEN 0x10 /* LPC cycles count per start */
#define LPC_REG_CMD 0x14
#define LPC_REG_CMD_OP BIT(0) /* 0: read, 1: write */
#define LPC_REG_CMD_SAMEADDR BIT(3)
#define LPC_REG_ADDR 0x20 /* target address */
#define LPC_REG_WDATA 0x24 /* write FIFO */
#define LPC_REG_RDATA 0x28 /* read FIFO */
/* The minimal nanosecond interval for each query on LPC cycle status */
#define LPC_NSEC_PERWAIT 100
/*
* The maximum waiting time is about 128us. It is specific for stream I/O,
* such as ins.
*
* The fastest IO cycle time is about 390ns, but the worst case will wait
* for extra 256 lpc clocks, so (256 + 13) * 30ns = 8 us. The maximum burst
* cycles is 16. So, the maximum waiting time is about 128us under worst
* case.
*
* Choose 1300 as the maximum.
*/
#define LPC_MAX_WAITCNT 1300
/* About 10us. This is specific for single IO operations, such as inb */
#define LPC_PEROP_WAITCNT 100
static int wait_lpc_idle(unsigned char *mbase, unsigned int waitcnt)
{
u32 status;
do {
status = readl(mbase + LPC_REG_OP_STATUS);
if (status & LPC_REG_OP_STATUS_IDLE)
return (status & LPC_REG_OP_STATUS_FINISHED) ? 0 : -EIO;
ndelay(LPC_NSEC_PERWAIT);
} while (--waitcnt);
return -ETIME;
}
/*
* hisi_lpc_target_in - trigger a series of LPC cycles for read operation
* @lpcdev: pointer to hisi lpc device
* @para: some parameters used to control the lpc I/O operations
* @addr: the lpc I/O target port address
* @buf: where the read back data is stored
* @opcnt: how many I/O operations required, i.e. data width
*
* Returns 0 on success, non-zero on fail.
*/
static int hisi_lpc_target_in(struct hisi_lpc_dev *lpcdev,
struct lpc_cycle_para *para, unsigned long addr,
unsigned char *buf, unsigned long opcnt)
{
unsigned int cmd_word;
unsigned int waitcnt;
unsigned long flags;
int ret;
if (!buf || !opcnt || !para || !para->csize || !lpcdev)
return -EINVAL;
cmd_word = 0; /* IO mode, Read */
waitcnt = LPC_PEROP_WAITCNT;
if (!(para->opflags & FG_INCRADDR_LPC)) {
cmd_word |= LPC_REG_CMD_SAMEADDR;
waitcnt = LPC_MAX_WAITCNT;
}
/* whole operation must be atomic */
spin_lock_irqsave(&lpcdev->cycle_lock, flags);
writel_relaxed(opcnt, lpcdev->membase + LPC_REG_OP_LEN);
writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD);
writel_relaxed(addr, lpcdev->membase + LPC_REG_ADDR);
writel(LPC_REG_STARTUP_SIGNAL_START,
lpcdev->membase + LPC_REG_STARTUP_SIGNAL);
/* whether the operation is finished */
ret = wait_lpc_idle(lpcdev->membase, waitcnt);
if (ret) {
spin_unlock_irqrestore(&lpcdev->cycle_lock, flags);
return ret;
}
readsb(lpcdev->membase + LPC_REG_RDATA, buf, opcnt);
spin_unlock_irqrestore(&lpcdev->cycle_lock, flags);
return 0;
}
/*
* hisi_lpc_target_out - trigger a series of LPC cycles for write operation
* @lpcdev: pointer to hisi lpc device
* @para: some parameters used to control the lpc I/O operations
* @addr: the lpc I/O target port address
* @buf: where the data to be written is stored
* @opcnt: how many I/O operations required, i.e. data width
*
* Returns 0 on success, non-zero on fail.
*/
static int hisi_lpc_target_out(struct hisi_lpc_dev *lpcdev,
struct lpc_cycle_para *para, unsigned long addr,
const unsigned char *buf, unsigned long opcnt)
{
unsigned int waitcnt;
unsigned long flags;
u32 cmd_word;
int ret;
if (!buf || !opcnt || !para || !lpcdev)
return -EINVAL;
/* default is increasing address */
cmd_word = LPC_REG_CMD_OP; /* IO mode, write */
waitcnt = LPC_PEROP_WAITCNT;
if (!(para->opflags & FG_INCRADDR_LPC)) {
cmd_word |= LPC_REG_CMD_SAMEADDR;
waitcnt = LPC_MAX_WAITCNT;
}
spin_lock_irqsave(&lpcdev->cycle_lock, flags);
writel_relaxed(opcnt, lpcdev->membase + LPC_REG_OP_LEN);
writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD);
writel_relaxed(addr, lpcdev->membase + LPC_REG_ADDR);
writesb(lpcdev->membase + LPC_REG_WDATA, buf, opcnt);
writel(LPC_REG_STARTUP_SIGNAL_START,
lpcdev->membase + LPC_REG_STARTUP_SIGNAL);
/* whether the operation is finished */
ret = wait_lpc_idle(lpcdev->membase, waitcnt);
spin_unlock_irqrestore(&lpcdev->cycle_lock, flags);
return ret;
}
static unsigned long hisi_lpc_pio_to_addr(struct hisi_lpc_dev *lpcdev,
unsigned long pio)
{
return pio - lpcdev->io_host->io_start + lpcdev->io_host->hw_start;
}
/*
* hisi_lpc_comm_in - input the data in a single operation
* @hostdata: pointer to the device information relevant to LPC controller
* @pio: the target I/O port address
* @dwidth: the data length required to read from the target I/O port
*
* When success, data is returned. Otherwise, ~0 is returned.
*/
static u32 hisi_lpc_comm_in(void *hostdata, unsigned long pio, size_t dwidth)
{
struct hisi_lpc_dev *lpcdev = hostdata;
struct lpc_cycle_para iopara;
unsigned long addr;
u32 rd_data = 0;
int ret;
if (!lpcdev || !dwidth || dwidth > LPC_MAX_DWIDTH)
return ~0;
addr = hisi_lpc_pio_to_addr(lpcdev, pio);
iopara.opflags = FG_INCRADDR_LPC;
iopara.csize = dwidth;
ret = hisi_lpc_target_in(lpcdev, &iopara, addr,
(unsigned char *)&rd_data, dwidth);
if (ret)
return ~0;
return le32_to_cpu(rd_data);
}
/*
* hisi_lpc_comm_out - output the data in a single operation
* @hostdata: pointer to the device information relevant to LPC controller
* @pio: the target I/O port address
* @val: a value to be output from caller, maximum is four bytes
* @dwidth: the data width required writing to the target I/O port
*
* This function corresponds to out(b,w,l) only.
*/
static void hisi_lpc_comm_out(void *hostdata, unsigned long pio,
u32 val, size_t dwidth)
{
struct hisi_lpc_dev *lpcdev = hostdata;
struct lpc_cycle_para iopara;
const unsigned char *buf;
unsigned long addr;
if (!lpcdev || !dwidth || dwidth > LPC_MAX_DWIDTH)
return;
val = cpu_to_le32(val);
buf = (const unsigned char *)&val;
addr = hisi_lpc_pio_to_addr(lpcdev, pio);
iopara.opflags = FG_INCRADDR_LPC;
iopara.csize = dwidth;
hisi_lpc_target_out(lpcdev, &iopara, addr, buf, dwidth);
}
/*
* hisi_lpc_comm_ins - input the data in the buffer in multiple operations
* @hostdata: pointer to the device information relevant to LPC controller
* @pio: the target I/O port address
* @buffer: a buffer where read/input data bytes are stored
* @dwidth: the data width required writing to the target I/O port
* @count: how many data units whose length is dwidth will be read
*
* When success, the data read back is stored in buffer pointed by buffer.
* Returns 0 on success, -errno otherwise.
*/
static u32 hisi_lpc_comm_ins(void *hostdata, unsigned long pio, void *buffer,
size_t dwidth, unsigned int count)
{
struct hisi_lpc_dev *lpcdev = hostdata;
unsigned char *buf = buffer;
struct lpc_cycle_para iopara;
unsigned long addr;
if (!lpcdev || !buf || !count || !dwidth || dwidth > LPC_MAX_DWIDTH)
return -EINVAL;
iopara.opflags = 0;
if (dwidth > 1)
iopara.opflags |= FG_INCRADDR_LPC;
iopara.csize = dwidth;
addr = hisi_lpc_pio_to_addr(lpcdev, pio);
do {
int ret;
ret = hisi_lpc_target_in(lpcdev, &iopara, addr, buf, dwidth);
if (ret)
return ret;
buf += dwidth;
} while (--count);
return 0;
}
/*
* hisi_lpc_comm_outs - output the data in the buffer in multiple operations
* @hostdata: pointer to the device information relevant to LPC controller
* @pio: the target I/O port address
* @buffer: a buffer where write/output data bytes are stored
* @dwidth: the data width required writing to the target I/O port
* @count: how many data units whose length is dwidth will be written
*/
static void hisi_lpc_comm_outs(void *hostdata, unsigned long pio,
const void *buffer, size_t dwidth,
unsigned int count)
{
struct hisi_lpc_dev *lpcdev = hostdata;
struct lpc_cycle_para iopara;
const unsigned char *buf = buffer;
unsigned long addr;
if (!lpcdev || !buf || !count || !dwidth || dwidth > LPC_MAX_DWIDTH)
return;
iopara.opflags = 0;
if (dwidth > 1)
iopara.opflags |= FG_INCRADDR_LPC;
iopara.csize = dwidth;
addr = hisi_lpc_pio_to_addr(lpcdev, pio);
do {
if (hisi_lpc_target_out(lpcdev, &iopara, addr, buf, dwidth))
break;
buf += dwidth;
} while (--count);
}
static const struct logic_pio_host_ops hisi_lpc_ops = {
.in = hisi_lpc_comm_in,
.out = hisi_lpc_comm_out,
.ins = hisi_lpc_comm_ins,
.outs = hisi_lpc_comm_outs,
};
#ifdef CONFIG_ACPI
#define MFD_CHILD_NAME_PREFIX DRV_NAME"-"
#define MFD_CHILD_NAME_LEN (ACPI_ID_LEN + sizeof(MFD_CHILD_NAME_PREFIX) - 1)
struct hisi_lpc_mfd_cell {
struct mfd_cell_acpi_match acpi_match;
char name[MFD_CHILD_NAME_LEN];
char pnpid[ACPI_ID_LEN];
};
static int hisi_lpc_acpi_xlat_io_res(struct acpi_device *adev,
struct acpi_device *host,
struct resource *res)
{
unsigned long sys_port;
resource_size_t len = resource_size(res);
sys_port = logic_pio_trans_hwaddr(&host->fwnode, res->start, len);
if (sys_port == ~0UL)
return -EFAULT;
res->start = sys_port;
res->end = sys_port + len;
return 0;
}
/*
* hisi_lpc_acpi_set_io_res - set the resources for a child's MFD
* @child: the device node to be updated the I/O resource
* @hostdev: the device node associated with host controller
* @res: double pointer to be set to the address of translated resources
* @num_res: pointer to variable to hold the number of translated resources
*
* Returns 0 when successful, and a negative value for failure.
*
* For a given host controller, each child device will have an associated
* host-relative address resource. This function will return the translated
* logical PIO addresses for each child devices resources.
*/
static int hisi_lpc_acpi_set_io_res(struct device *child,
struct device *hostdev,
const struct resource **res, int *num_res)
{
struct acpi_device *adev;
struct acpi_device *host;
struct resource_entry *rentry;
LIST_HEAD(resource_list);
struct resource *resources;
int count;
int i;
if (!child || !hostdev)
return -EINVAL;
host = to_acpi_device(hostdev);
adev = to_acpi_device(child);
if (!adev->status.present) {
dev_dbg(child, "device is not present\n");
return -EIO;
}
if (acpi_device_enumerated(adev)) {
dev_dbg(child, "has been enumerated\n");
return -EIO;
}
/*
* The following code segment to retrieve the resources is common to
* acpi_create_platform_device(), so consider a common helper function
* in future.
*/
count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
if (count <= 0) {
dev_dbg(child, "failed to get resources\n");
return count ? count : -EIO;
}
resources = devm_kcalloc(hostdev, count, sizeof(*resources),
GFP_KERNEL);
if (!resources) {
dev_warn(hostdev, "could not allocate memory for %d resources\n",
count);
acpi_dev_free_resource_list(&resource_list);
return -ENOMEM;
}
count = 0;
list_for_each_entry(rentry, &resource_list, node)
resources[count++] = *rentry->res;
acpi_dev_free_resource_list(&resource_list);
/* translate the I/O resources */
for (i = 0; i < count; i++) {
int ret;
if (!(resources[i].flags & IORESOURCE_IO))
continue;
ret = hisi_lpc_acpi_xlat_io_res(adev, host, &resources[i]);
if (ret) {
dev_err(child, "translate IO range %pR failed (%d)\n",
&resources[i], ret);
return ret;
}
}
*res = resources;
*num_res = count;
return 0;
}
/*
* hisi_lpc_acpi_probe - probe children for ACPI FW
* @hostdev: LPC host device pointer
*
* Returns 0 when successful, and a negative value for failure.
*
* Scan all child devices and create a per-device MFD with
* logical PIO translated IO resources.
*/
static int hisi_lpc_acpi_probe(struct device *hostdev)
{
struct acpi_device *adev = ACPI_COMPANION(hostdev);
struct hisi_lpc_mfd_cell *hisi_lpc_mfd_cells;
struct mfd_cell *mfd_cells;
struct acpi_device *child;
int size, ret, count = 0, cell_num = 0;
list_for_each_entry(child, &adev->children, node)
cell_num++;
/* allocate the mfd cell and companion ACPI info, one per child */
size = sizeof(*mfd_cells) + sizeof(*hisi_lpc_mfd_cells);
mfd_cells = devm_kcalloc(hostdev, cell_num, size, GFP_KERNEL);
if (!mfd_cells)
return -ENOMEM;
hisi_lpc_mfd_cells = (struct hisi_lpc_mfd_cell *)&mfd_cells[cell_num];
/* Only consider the children of the host */
list_for_each_entry(child, &adev->children, node) {
struct mfd_cell *mfd_cell = &mfd_cells[count];
struct hisi_lpc_mfd_cell *hisi_lpc_mfd_cell =
&hisi_lpc_mfd_cells[count];
struct mfd_cell_acpi_match *acpi_match =
&hisi_lpc_mfd_cell->acpi_match;
char *name = hisi_lpc_mfd_cell[count].name;
char *pnpid = hisi_lpc_mfd_cell[count].pnpid;
struct mfd_cell_acpi_match match = {
.pnpid = pnpid,
};
/*
* For any instances of this host controller (Hip06 and Hip07
* are the only chipsets), we would not have multiple slaves
* with the same HID. And in any system we would have just one
* controller active. So don't worrry about MFD name clashes.
*/
snprintf(name, MFD_CHILD_NAME_LEN, MFD_CHILD_NAME_PREFIX"%s",
acpi_device_hid(child));
snprintf(pnpid, ACPI_ID_LEN, "%s", acpi_device_hid(child));
memcpy(acpi_match, &match, sizeof(*acpi_match));
mfd_cell->name = name;
mfd_cell->acpi_match = acpi_match;
ret = hisi_lpc_acpi_set_io_res(&child->dev, &adev->dev,
&mfd_cell->resources,
&mfd_cell->num_resources);
if (ret) {
dev_warn(&child->dev, "set resource fail (%d)\n", ret);
return ret;
}
count++;
}
ret = mfd_add_devices(hostdev, PLATFORM_DEVID_NONE,
mfd_cells, cell_num, NULL, 0, NULL);
if (ret) {
dev_err(hostdev, "failed to add mfd cells (%d)\n", ret);
return ret;
}
return 0;
}
static const struct acpi_device_id hisi_lpc_acpi_match[] = {
{"HISI0191"},
{}
};
#else
static int hisi_lpc_acpi_probe(struct device *dev)
{
return -ENODEV;
}
#endif // CONFIG_ACPI
/*
* hisi_lpc_probe - the probe callback function for hisi lpc host,
* will finish all the initialization.
* @pdev: the platform device corresponding to hisi lpc host
*
* Returns 0 on success, non-zero on fail.
*/
static int hisi_lpc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct acpi_device *acpi_device = ACPI_COMPANION(dev);
struct logic_pio_hwaddr *range;
struct hisi_lpc_dev *lpcdev;
resource_size_t io_end;
struct resource *res;
int ret;
lpcdev = devm_kzalloc(dev, sizeof(*lpcdev), GFP_KERNEL);
if (!lpcdev)
return -ENOMEM;
spin_lock_init(&lpcdev->cycle_lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lpcdev->membase = devm_ioremap_resource(dev, res);
if (IS_ERR(lpcdev->membase))
return PTR_ERR(lpcdev->membase);
range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL);
if (!range)
return -ENOMEM;
range->fwnode = dev->fwnode;
range->flags = LOGIC_PIO_INDIRECT;
range->size = PIO_INDIRECT_SIZE;
ret = logic_pio_register_range(range);
if (ret) {
dev_err(dev, "register IO range failed (%d)!\n", ret);
return ret;
}
lpcdev->io_host = range;
/* register the LPC host PIO resources */
if (acpi_device)
ret = hisi_lpc_acpi_probe(dev);
else
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret)
return ret;
lpcdev->io_host->hostdata = lpcdev;
lpcdev->io_host->ops = &hisi_lpc_ops;
io_end = lpcdev->io_host->io_start + lpcdev->io_host->size;
dev_info(dev, "registered range [%pa - %pa]\n",
&lpcdev->io_host->io_start, &io_end);
return ret;
}
static const struct of_device_id hisi_lpc_of_match[] = {
{ .compatible = "hisilicon,hip06-lpc", },
{ .compatible = "hisilicon,hip07-lpc", },
{}
};
static struct platform_driver hisi_lpc_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = hisi_lpc_of_match,
.acpi_match_table = ACPI_PTR(hisi_lpc_acpi_match),
},
.probe = hisi_lpc_probe,
};
builtin_platform_driver(hisi_lpc_driver);
...@@ -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