2016-11-01 13:06:32

by zhichang.yuan

[permalink] [raw]
Subject: [PATCH/RESEND V4 0/3] ARM64 LPC: legacy ISA I/O support

This patch supports the IPMI-bt device attached to the Low-Pin-Count interface
implemented on Hisilicon Hip06 SoC.
-----------
| LPC host|
| |
-----------
|
_____________V_______________LPC
| |
V V
------------
| BT(ipmi)|
------------

When master accesses those periperals beneath the Hip06 LPC, a specific LPC
driver is needed to make LPC host generate the standard LPC I/O cycles with
the target periperals'I/O port addresses. But on curent arm64 world, there is
no real I/O accesses. All the I/O operations through in/out pair are based on
MMIO which is not satisfied the I/O mechanism on Hip06 LPC.
To solve this issue and keep the relevant existing peripherals' driver
unchanged, this patch set redefines the in/out pair to support both the IO
operations for Hip06 LPC and the original MMIO. The way specific to Hip06 is
named as indirect-IO in this patchset.

Changes from V3:
- UART support deferred to a separate patchset; This patchset only support
ipmi device under LPC;
- LPC bus I/O range is fixed to 0 ~ (PCIBIOS_MIN_IO - 1), which is separeted
from PCI/PCIE PIO space;
- Based on Arnd's remarks, removed the ranges property from Hip06 lpc dts and
added a new fixup function, of_isa_indirect_io(), to get the I/O address
directly from LPC dts configurations;
- Support in(w,l)/out(w,l) for Hip06 lpc I/O;
- Decouple the header file dependency on the gerenic io.h by defining in/out
as normal functions in c file;
- removed unused macro definitions in the LPC driver;

Changes from V2:
- Support the PIO retrieval from the linux PIO generated by
pci_address_to_pio. This method replace the 4K PIO reservation in V2;
- Support the flat-tree earlycon;
- Some revises based on Arnd's remarks;
- Make sure the linux PIO range allocated to Hip06 LPC peripherals starts
from non-ZERO;

Changes from V1:
- Support the ACPI LPC device;
- Optimize the dts LPC driver in ISA compatible mode;
- Reserve the IO range below 4K in avoid the possible conflict with PCI host
IO ranges;
- Support the LPC uart and relevant earlycon;

Signed-off-by: Zhichang Yuan <[email protected]>

zhichang.yuan (3):
ARM64 LPC: Indirect ISA port IO introduced
ARM64 LPC: Add missing range exception for special ISA
ARM64 LPC: LPC driver implementation on Hip06

.../arm/hisilicon/hisilicon-low-pin-count.txt | 31 ++
MAINTAINERS | 8 +
arch/arm64/Kconfig | 6 +
arch/arm64/include/asm/extio.h | 94 ++++
arch/arm64/include/asm/io.h | 36 ++
arch/arm64/kernel/Makefile | 1 +
arch/arm64/kernel/extio.c | 53 +++
drivers/bus/Kconfig | 8 +
drivers/bus/Makefile | 1 +
drivers/bus/hisi_lpc.c | 501 +++++++++++++++++++++
drivers/of/address.c | 47 +-
drivers/pci/pci.c | 6 +-
include/linux/of_address.h | 17 +
13 files changed, 804 insertions(+), 5 deletions(-)
create mode 100644 Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
create mode 100644 arch/arm64/include/asm/extio.h
create mode 100644 arch/arm64/kernel/extio.c
create mode 100644 drivers/bus/hisi_lpc.c

--
1.9.1


2016-11-01 13:02:55

by zhichang.yuan

[permalink] [raw]
Subject: [PATCH/RESEND V4 3/3] ARM64 LPC: LPC driver implementation on Hip06

On Hip06, the accesses to LPC peripherals work in an indirect way. A
corresponding LPC driver configure some registers in LPC master at first, then
the real accesses on LPC slave devices are finished by the LPC master, which
is transparent to LPC driver.
This patch implement the relevant driver for Hip06 LPC. Cooperating with
indirect-IO, ipmi messages is in service without any changes on ipmi driver.

Cc: Mark Rutland <[email protected]>
Signed-off-by: zhichang.yuan <[email protected]>
Signed-off-by: Gabriele Paoloni <[email protected]>
---
.../arm/hisilicon/hisilicon-low-pin-count.txt | 31 ++
MAINTAINERS | 8 +
drivers/bus/Kconfig | 8 +
drivers/bus/Makefile | 1 +
drivers/bus/hisi_lpc.c | 501 +++++++++++++++++++++
5 files changed, 549 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
create mode 100644 drivers/bus/hisi_lpc.c

diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
new file mode 100644
index 0000000..e681419
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
@@ -0,0 +1,31 @@
+Hisilicon Hip06 low-pin-count device
+ Usually LPC controller is part of PCI host bridge, so the legacy ISA ports
+ locate on LPC bus can be accessed direclty. But some SoCs have independent
+ LPC controller, and access the legacy ports by triggering LPC I/O cycles.
+ Hisilicon Hip06 implements this LPC device.
+
+Required properties:
+- compatible: should be "hisilicon,low-pin-count"
+- #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 register set for this device 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 = "hisilicom,low-pin-count";
+ #address-cells = <2>;
+ #size-cells = <1>;
+ reg = <0x0 0xa01b0000 0x0 0x1000>;
+
+ ipmi0: bt@e4 {
+ compatible = "ipmi-bt";
+ device_type = "ipmi";
+ reg = <0x01 0xe4 0x04>;
+ status = "disabled";
+ };
+};
diff --git a/MAINTAINERS b/MAINTAINERS
index 1cd38a7..7c69410 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5716,6 +5716,14 @@ F: include/uapi/linux/if_hippi.h
F: net/802/hippi.c
F: drivers/net/hippi/

+HISILICON LPC BUS DRIVER
+M: Zhichang Yuan <[email protected]>
+L: [email protected]
+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
M: Yisen Zhuang <[email protected]>
M: Salil Mehta <[email protected]>
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index 7010dca..a108abc 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -64,6 +64,14 @@ config BRCMSTB_GISB_ARB
arbiter. This driver provides timeout and target abort error handling
and internal bus master decoding.

+config HISILICON_LPC
+ bool "Workaround for nonstandard ISA I/O space on Hisilicon Hip0X"
+ depends on (ARCH_HISI || COMPILE_TEST) && ARM64
+ select ARM64_INDIRECT_PIO
+ help
+ Driver needed for some legacy ISA devices attached to Low-Pin-Count
+ on Hisilicon Hip0X SoC.
+
config IMX_WEIM
bool "Freescale EIM DRIVER"
depends on ARCH_MXC
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
index c6cfa6b..10b4983 100644
--- a/drivers/bus/Makefile
+++ b/drivers/bus/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_ARM_CCI) += arm-cci.o
obj-$(CONFIG_ARM_CCN) += arm-ccn.o

obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o
+obj-$(CONFIG_HISILICON_LPC) += hisi_lpc.o
obj-$(CONFIG_IMX_WEIM) += imx-weim.o
obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o
obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o
diff --git a/drivers/bus/hisi_lpc.c b/drivers/bus/hisi_lpc.c
new file mode 100644
index 0000000..9f48a1a
--- /dev/null
+++ b/drivers/bus/hisi_lpc.c
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2016 Hisilicon Limited, All Rights Reserved.
+ * Author: Zhichang Yuan <[email protected]>
+ * Author: Zou Rongrong <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 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/acpi.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.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/serial_8250.h>
+#include <linux/slab.h>
+
+/*
+ * this bit set means each IO operation will target to different port address;
+ * 0 means repeatly IO operations will be sticked on the same port, such as BT;
+ */
+#define FG_INCRADDR_LPC 0x02
+
+struct lpc_cycle_para {
+ unsigned int opflags;
+ unsigned int csize;/* the data length of each operation */
+};
+
+struct hisilpc_dev {
+ spinlock_t cycle_lock;
+ void __iomem *membase;
+ struct extio_ops io_ops;
+};
+
+
+/* The maximum continous operations*/
+#define LPC_MAX_OPCNT 16
+/* only support IO data unit length is four at maximum */
+#define LPC_MAX_DULEN 4
+#if LPC_MAX_DULEN > LPC_MAX_OPCNT
+#error "LPC.. MAX_DULEN must be not bigger than MAX_OPCNT!"
+#endif
+
+#define LPC_REG_START 0x00/* start a new LPC cycle */
+#define LPC_REG_OP_STATUS 0x04/* the current LPC status */
+#define LPC_REG_IRQ_ST 0x08/* interrupt enable&status */
+#define LPC_REG_OP_LEN 0x10/* how many LPC cycles each start */
+#define LPC_REG_CMD 0x14/* command for the required LPC cycle */
+#define LPC_REG_ADDR 0x20/* LPC target address */
+#define LPC_REG_WDATA 0x24/* data to be written */
+#define LPC_REG_RDATA 0x28/* data coming from peer */
+
+
+/* The command register fields*/
+#define LPC_CMD_SAMEADDR 0x08
+#define LPC_CMD_TYPE_IO 0x00
+#define LPC_CMD_WRITE 0x01
+#define LPC_CMD_READ 0x00
+/* the bit attribute is W1C. 1 represents OK. */
+#define LPC_STAT_BYIRQ 0x02
+
+#define LPC_STATUS_IDLE 0x01
+#define LPC_OP_FINISHED 0x02
+
+#define START_WORK 0x01
+
+/*
+ * The minimal waiting interval... Suggest it is not less than 10.
+ * Bigger value probably will lower the performance.
+ */
+#define LPC_NSEC_PERWAIT 100
+/*
+ * The maximum waiting time is about 128us.
+ * 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 specfic for single IO operation, such as inb. */
+#define LPC_PEROP_WAITCNT 100
+
+
+static inline int wait_lpc_idle(unsigned char *mbase,
+ unsigned int waitcnt) {
+ u32 opstatus;
+
+ while (waitcnt--) {
+ ndelay(LPC_NSEC_PERWAIT);
+ opstatus = readl(mbase + LPC_REG_OP_STATUS);
+ if (opstatus & LPC_STATUS_IDLE)
+ return (opstatus & LPC_OP_FINISHED) ? 0 : (-EIO);
+ }
+ return -ETIME;
+}
+
+
+/**
+ * hisilpc_target_in - trigger a series of lpc cycles to read required data
+ * from target periperal.
+ * @pdev: pointer to hisi lpc device
+ * @para: some paramerters used to control the lpc I/O operations
+ * @ptaddr: the lpc I/O target port address
+ * @buf: where the read back data is stored
+ * @opcnt: how many I/O operations required in this calling
+ *
+ * only one byte data is read each I/O operation.
+ *
+ * Returns 0 on success, non-zero on fail.
+ *
+ */
+static int hisilpc_target_in(struct hisilpc_dev *pdev,
+ struct lpc_cycle_para *para,
+ unsigned long ptaddr, unsigned char *buf,
+ unsigned long opcnt)
+{
+ unsigned int cmd_word;
+ unsigned int waitcnt;
+ int retval;
+ unsigned long flags;
+ unsigned long cnt_per_trans;
+
+ if (!buf || !opcnt || !para || !para->csize || !pdev)
+ return -EINVAL;
+
+ if (opcnt > LPC_MAX_OPCNT)
+ return -EINVAL;
+
+ cmd_word = LPC_CMD_TYPE_IO | LPC_CMD_READ;
+ waitcnt = (LPC_PEROP_WAITCNT);
+ if (!(para->opflags & FG_INCRADDR_LPC)) {
+ cmd_word |= LPC_CMD_SAMEADDR;
+ waitcnt = LPC_MAX_WAITCNT;
+ }
+
+ retval = 0;
+ cnt_per_trans = (para->csize == 1) ? opcnt : para->csize;
+ for (; opcnt && !retval; cnt_per_trans = para->csize) {
+ /* whole operation must be atomic */
+ spin_lock_irqsave(&pdev->cycle_lock, flags);
+
+ writel(cnt_per_trans, pdev->membase + LPC_REG_OP_LEN);
+
+ writel(cmd_word, pdev->membase + LPC_REG_CMD);
+
+ writel(ptaddr, pdev->membase + LPC_REG_ADDR);
+
+ writel(START_WORK, pdev->membase + LPC_REG_START);
+
+ /* whether the operation is finished */
+ retval = wait_lpc_idle(pdev->membase, waitcnt);
+ if (!retval) {
+ opcnt -= cnt_per_trans;
+ for (; cnt_per_trans--; buf++)
+ *buf = readl(pdev->membase + LPC_REG_RDATA);
+ }
+
+ spin_unlock_irqrestore(&pdev->cycle_lock, flags);
+ }
+
+ return retval;
+}
+
+/**
+ * hisilpc_target_out - trigger a series of lpc cycles to write required data
+ * to target periperal.
+ * @pdev: pointer to hisi lpc device
+ * @para: some paramerters used to control the lpc I/O operations
+ * @ptaddr: the lpc I/O target port address
+ * @buf: where the data to be written is stored
+ * @opcnt: how many I/O operations required
+ *
+ * only one byte data is read each I/O operation.
+ *
+ * Returns 0 on success, non-zero on fail.
+ *
+ */
+static int hisilpc_target_out(struct hisilpc_dev *pdev,
+ struct lpc_cycle_para *para,
+ unsigned long ptaddr,
+ const unsigned char *buf,
+ unsigned long opcnt)
+{
+ unsigned int cmd_word;
+ unsigned int waitcnt;
+ int retval;
+ unsigned long flags;
+ unsigned long cnt_per_trans;
+
+ if (!buf || !opcnt || !para || !pdev)
+ return -EINVAL;
+
+ if (opcnt > LPC_MAX_OPCNT)
+ return -EINVAL;
+ /* default is increasing address */
+ cmd_word = LPC_CMD_TYPE_IO | LPC_CMD_WRITE;
+ waitcnt = (LPC_PEROP_WAITCNT);
+ if (!(para->opflags & FG_INCRADDR_LPC)) {
+ cmd_word |= LPC_CMD_SAMEADDR;
+ waitcnt = LPC_MAX_WAITCNT;
+ }
+
+ retval = 0;
+ cnt_per_trans = (para->csize == 1) ? opcnt : para->csize;
+ for (; opcnt && !retval; cnt_per_trans = para->csize) {
+ spin_lock_irqsave(&pdev->cycle_lock, flags);
+
+ writel(cnt_per_trans, pdev->membase + LPC_REG_OP_LEN);
+ opcnt -= cnt_per_trans;
+ for (; cnt_per_trans--; buf++)
+ writel(*buf, pdev->membase + LPC_REG_WDATA);
+
+ writel(cmd_word, pdev->membase + LPC_REG_CMD);
+
+ writel(ptaddr, pdev->membase + LPC_REG_ADDR);
+
+ writel(START_WORK, pdev->membase + LPC_REG_START);
+
+ /* whether the operation is finished */
+ retval = wait_lpc_idle(pdev->membase, waitcnt);
+
+ spin_unlock_irqrestore(&pdev->cycle_lock, flags);
+ }
+
+ return retval;
+}
+
+
+/**
+ * hisilpc_comm_in - read/input the data from the I/O peripheral through LPC.
+ * @devobj: pointer to the device information relevant to LPC controller.
+ * @ptaddr: the target I/O port address.
+ * @dlen: the data length required to read from the target I/O port.
+ *
+ * when succeed, the data read back is stored in buffer pointed by inbuf.
+ * For inb, return the data read from I/O or -1 when error occur.
+ */
+static u64 hisilpc_comm_in(void *devobj, unsigned long ptaddr, size_t dlen)
+{
+ struct hisilpc_dev *lpcdev;
+ struct lpc_cycle_para iopara;
+ u32 rd_data;
+ unsigned char *newbuf;
+ int ret = 0;
+
+ if (!devobj || !dlen || dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
+ return -1;
+
+ /* the local buffer must be enough for one data unit */
+ if (sizeof(rd_data) < dlen)
+ return -1;
+
+ newbuf = (unsigned char *)&rd_data;
+
+ lpcdev = (struct hisilpc_dev *)devobj;
+
+ iopara.opflags = FG_INCRADDR_LPC;
+ iopara.csize = dlen;
+
+ ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, newbuf, dlen);
+ if (ret)
+ return -1;
+
+ return le32_to_cpu(rd_data);
+}
+
+/**
+ * hisilpc_comm_out - write/output the data whose maximal length is four bytes to
+ * the I/O peripheral through LPC.
+ * @devobj: pointer to the device information relevant to LPC controller.
+ * @outval: a value to be outputed from caller, maximum is four bytes.
+ * @ptaddr: the target I/O port address.
+ * @dlen: the data length required writing to the target I/O port .
+ *
+ * This function is corresponding to out(b,w,l) only
+ *
+ */
+static void hisilpc_comm_out(void *devobj, unsigned long ptaddr,
+ u32 outval, size_t dlen)
+{
+ struct hisilpc_dev *lpcdev;
+ struct lpc_cycle_para iopara;
+ const unsigned char *newbuf;
+
+ if (!devobj || !dlen || dlen > LPC_MAX_DULEN)
+ return;
+
+ if (sizeof(outval) < dlen)
+ return;
+
+ outval = cpu_to_le32(outval);
+
+ newbuf = (const unsigned char *)&outval;
+ lpcdev = (struct hisilpc_dev *)devobj;
+
+ iopara.opflags = FG_INCRADDR_LPC;
+ iopara.csize = dlen;
+
+ hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf, dlen);
+}
+
+
+/**
+ * hisilpc_comm_ins - read/input the data in buffer to the I/O peripheral
+ * through LPC, it corresponds to ins(b,w,l)
+ * @devobj: pointer to the device information relevant to LPC controller.
+ * @ptaddr: the target I/O port address.
+ * @inbuf: a buffer where read/input data bytes are stored.
+ * @dlen: the data length required writing to the target I/O port .
+ * @count: how many data units whose length is dlen will be read.
+ *
+ */
+static u64 hisilpc_comm_ins(void *devobj, unsigned long ptaddr,
+ void *inbuf, size_t dlen, unsigned int count)
+{
+ struct hisilpc_dev *lpcdev;
+ struct lpc_cycle_para iopara;
+ unsigned char *newbuf;
+ unsigned int loopcnt, cntleft;
+ unsigned int max_perburst;
+ int ret = 0;
+
+ if (!devobj || !inbuf || !count || !dlen ||
+ dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
+ return -1;
+
+ iopara.opflags = 0;
+ if (dlen > 1)
+ iopara.opflags |= FG_INCRADDR_LPC;
+ iopara.csize = dlen;
+
+ lpcdev = (struct hisilpc_dev *)devobj;
+ newbuf = (unsigned char *)inbuf;
+ /*
+ * ensure data stream whose lenght is multiple of dlen to be processed
+ * each IO input
+ */
+ max_perburst = LPC_MAX_OPCNT & (~(dlen - 1));
+ cntleft = count * dlen;
+ do {
+ loopcnt = (cntleft >= max_perburst) ? max_perburst : cntleft;
+ ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, newbuf,
+ loopcnt);
+ if (ret)
+ break;
+ newbuf += loopcnt;
+ cntleft -= loopcnt;
+ } while (cntleft);
+
+ return ret;
+}
+
+/**
+ * hisilpc_comm_outs - write/output the data in buffer to the I/O peripheral
+ * through LPC, it corresponds to outs(b,w,l)
+ * @devobj: pointer to the device information relevant to LPC controller.
+ * @ptaddr: the target I/O port address.
+ * @outbuf: a buffer where write/output data bytes are stored.
+ * @dlen: the data length required writing to the target I/O port .
+ * @count: how many data units whose length is dlen will be written.
+ *
+ */
+static void hisilpc_comm_outs(void *devobj, unsigned long ptaddr,
+ const void *outbuf, size_t dlen, unsigned int count)
+{
+ struct hisilpc_dev *lpcdev;
+ struct lpc_cycle_para iopara;
+ const unsigned char *newbuf;
+ unsigned int loopcnt, cntleft;
+ unsigned int max_perburst;
+ int ret = 0;
+
+ if (!devobj || !outbuf || !count || !dlen ||
+ dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
+ return;
+
+ iopara.opflags = 0;
+ if (dlen > 1)
+ iopara.opflags |= FG_INCRADDR_LPC;
+ iopara.csize = dlen;
+
+ lpcdev = (struct hisilpc_dev *)devobj;
+ newbuf = (unsigned char *)outbuf;
+ /*
+ * ensure data stream whose lenght is multiple of dlen to be processed
+ * each IO input
+ */
+ max_perburst = LPC_MAX_OPCNT & (~(dlen - 1));
+ cntleft = count * dlen;
+ do {
+ loopcnt = (cntleft >= max_perburst) ? max_perburst : cntleft;
+ ret = hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf,
+ loopcnt);
+ if (ret)
+ break;
+ newbuf += loopcnt;
+ cntleft -= loopcnt;
+ } while (cntleft);
+}
+
+
+/**
+ * hisilpc_probe - the probe callback function for hisi lpc device,
+ * will finish all the intialization.
+ * @pdev: the platform device corresponding to hisi lpc
+ *
+ * Returns 0 on success, non-zero on fail.
+ *
+ */
+static int hisilpc_probe(struct platform_device *pdev)
+{
+ struct resource *iores;
+ struct hisilpc_dev *lpcdev;
+ int ret;
+
+ dev_info(&pdev->dev, "hslpc start probing...\n");
+
+ lpcdev = devm_kzalloc(&pdev->dev,
+ sizeof(struct hisilpc_dev), GFP_KERNEL);
+ if (!lpcdev)
+ return -ENOMEM;
+
+ spin_lock_init(&lpcdev->cycle_lock);
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lpcdev->membase = devm_ioremap_resource(&pdev->dev, iores);
+ if (IS_ERR(lpcdev->membase)) {
+ dev_err(&pdev->dev, "No mem resource for memory mapping!\n");
+ return PTR_ERR(lpcdev->membase);
+ }
+ /*
+ * The first PCIBIOS_MIN_IO is reserved specific for indirectIO.
+ * It will separate indirectIO range from pci host bridge to
+ * avoid the possible PIO conflict.
+ * Set the indirectIO range directly here.
+ */
+ lpcdev->io_ops.start = 0;
+ lpcdev->io_ops.end = PCIBIOS_MIN_IO - 1;
+ lpcdev->io_ops.devpara = lpcdev;
+ lpcdev->io_ops.pfin = hisilpc_comm_in;
+ lpcdev->io_ops.pfout = hisilpc_comm_out;
+ lpcdev->io_ops.pfins = hisilpc_comm_ins;
+ lpcdev->io_ops.pfouts = hisilpc_comm_outs;
+
+ platform_set_drvdata(pdev, lpcdev);
+
+ arm64_set_extops(&lpcdev->io_ops);
+
+ /*
+ * The children scanning is only for dts mode. For ACPI children,
+ * the corresponding devices had be created during acpi scanning.
+ */
+ ret = 0;
+ if (!has_acpi_companion(&pdev->dev))
+ ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+
+ if (!ret)
+ dev_info(&pdev->dev, "hslpc end probing. range[0x%lx - %lx]\n",
+ arm64_extio_ops->start, arm64_extio_ops->end);
+ else
+ dev_info(&pdev->dev, "hslpc probing is fail(%d)\n", ret);
+
+ return ret;
+}
+
+static const struct of_device_id hisilpc_of_match[] = {
+ {
+ .compatible = "hisilicon,low-pin-count",
+ },
+ {},
+};
+
+static const struct acpi_device_id hisilpc_acpi_match[] = {
+ {"HISI0191", },
+ {},
+};
+
+static struct platform_driver hisilpc_driver = {
+ .driver = {
+ .name = "hisi_lpc",
+ .of_match_table = hisilpc_of_match,
+ .acpi_match_table = hisilpc_acpi_match,
+ },
+ .probe = hisilpc_probe,
+};
+
+
+builtin_platform_driver(hisilpc_driver);
--
1.9.1

2016-11-01 13:03:01

by zhichang.yuan

[permalink] [raw]
Subject: [PATCH/RESEND V4 1/3] ARM64 LPC: Indirect ISA port IO introduced

For arm64, there is no I/O space as other architectural platforms, such as
X86. Most I/O accesses are achieved based on MMIO. But for some arm64 SoCs,
such as Hip06, when accessing some legacy ISA devices connected to LPC, those
known port addresses are used to control the corresponding target devices, for
example, 0x2f8 is for UART, 0xe4 is for ipmi-bt. It is different from the
normal MMIO mode in using.

To drive these devices, this patch introduces a method named indirect-IO.
In this method the in/out pair in arch/arm64/include/asm/io.h will be
redefined. When upper layer drivers call in/out with those known legacy port
addresses to access the peripherals, the hooking functions corrresponding to
those target peripherals will be called. Through this way, those upper layer
drivers which depend on in/out can run on Hip06 without any changes.

Cc: Catalin Marinas <[email protected]>
Cc: Will Deacon <[email protected]>
Signed-off-by: zhichang.yuan <[email protected]>
Signed-off-by: Gabriele Paoloni <[email protected]>
---
arch/arm64/Kconfig | 6 +++
arch/arm64/include/asm/extio.h | 94 ++++++++++++++++++++++++++++++++++++++++++
arch/arm64/include/asm/io.h | 29 +++++++++++++
arch/arm64/kernel/Makefile | 1 +
arch/arm64/kernel/extio.c | 29 +++++++++++++
5 files changed, 159 insertions(+)
create mode 100644 arch/arm64/include/asm/extio.h
create mode 100644 arch/arm64/kernel/extio.c

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 30398db..103dbea 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -163,6 +163,12 @@ config ARCH_MMAP_RND_COMPAT_BITS_MIN
config ARCH_MMAP_RND_COMPAT_BITS_MAX
default 16

+config ARM64_INDIRECT_PIO
+ bool "access peripherals with legacy I/O port"
+ help
+ Support special accessors for ISA I/O devices. This is needed for
+ SoCs that do not support standard read/write for the ISA range.
+
config NO_IOPORT_MAP
def_bool y if !PCI

diff --git a/arch/arm64/include/asm/extio.h b/arch/arm64/include/asm/extio.h
new file mode 100644
index 0000000..6ae0787
--- /dev/null
+++ b/arch/arm64/include/asm/extio.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 Hisilicon Limited, All Rights Reserved.
+ * Author: Zhichang Yuan <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 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/>.
+ */
+
+#ifndef __LINUX_EXTIO_H
+#define __LINUX_EXTIO_H
+
+struct extio_ops {
+ unsigned long start;/* inclusive, sys io addr */
+ unsigned long end;/* inclusive, sys io addr */
+
+ u64 (*pfin)(void *devobj, unsigned long ptaddr, size_t dlen);
+ void (*pfout)(void *devobj, unsigned long ptaddr, u32 outval,
+ size_t dlen);
+ u64 (*pfins)(void *devobj, unsigned long ptaddr, void *inbuf,
+ size_t dlen, unsigned int count);
+ void (*pfouts)(void *devobj, unsigned long ptaddr,
+ const void *outbuf, size_t dlen,
+ unsigned int count);
+ void *devpara;
+};
+
+extern struct extio_ops *arm64_extio_ops;
+
+#define DECLARE_EXTIO(bw, type) \
+extern type in##bw(unsigned long addr); \
+extern void out##bw(type value, unsigned long addr); \
+extern void ins##bw(unsigned long addr, void *buffer, unsigned int count);\
+extern void outs##bw(unsigned long addr, const void *buffer, unsigned int count);
+
+#define BUILD_EXTIO(bw, type) \
+type in##bw(unsigned long addr) \
+{ \
+ if (!arm64_extio_ops || arm64_extio_ops->start > addr || \
+ arm64_extio_ops->end < addr) \
+ return read##bw(PCI_IOBASE + addr); \
+ return arm64_extio_ops->pfin ? \
+ arm64_extio_ops->pfin(arm64_extio_ops->devpara, \
+ addr, sizeof(type)) : -1; \
+} \
+ \
+void out##bw(type value, unsigned long addr) \
+{ \
+ if (!arm64_extio_ops || arm64_extio_ops->start > addr || \
+ arm64_extio_ops->end < addr) \
+ write##bw(value, PCI_IOBASE + addr); \
+ else \
+ if (arm64_extio_ops->pfout) \
+ arm64_extio_ops->pfout(arm64_extio_ops->devpara,\
+ addr, value, sizeof(type)); \
+} \
+ \
+void ins##bw(unsigned long addr, void *buffer, unsigned int count) \
+{ \
+ if (!arm64_extio_ops || arm64_extio_ops->start > addr || \
+ arm64_extio_ops->end < addr) \
+ reads##bw(PCI_IOBASE + addr, buffer, count); \
+ else \
+ if (arm64_extio_ops->pfins) \
+ arm64_extio_ops->pfins(arm64_extio_ops->devpara,\
+ addr, buffer, sizeof(type), count); \
+} \
+ \
+void outs##bw(unsigned long addr, const void *buffer, unsigned int count) \
+{ \
+ if (!arm64_extio_ops || arm64_extio_ops->start > addr || \
+ arm64_extio_ops->end < addr) \
+ writes##bw(PCI_IOBASE + addr, buffer, count); \
+ else \
+ if (arm64_extio_ops->pfouts) \
+ arm64_extio_ops->pfouts(arm64_extio_ops->devpara,\
+ addr, buffer, sizeof(type), count); \
+}
+
+static inline void arm64_set_extops(struct extio_ops *ops)
+{
+ if (ops)
+ WRITE_ONCE(arm64_extio_ops, ops);
+}
+
+#endif /* __LINUX_EXTIO_H*/
diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
index 0bba427..136735d 100644
--- a/arch/arm64/include/asm/io.h
+++ b/arch/arm64/include/asm/io.h
@@ -31,6 +31,7 @@
#include <asm/early_ioremap.h>
#include <asm/alternative.h>
#include <asm/cpufeature.h>
+#include <asm/extio.h>

#include <xen/xen.h>

@@ -149,6 +150,34 @@ static inline u64 __raw_readq(const volatile void __iomem *addr)
#define IO_SPACE_LIMIT (PCI_IO_SIZE - 1)
#define PCI_IOBASE ((void __iomem *)PCI_IO_START)

+
+/*
+ * redefine the in(s)b/out(s)b for indirect-IO.
+ */
+#ifdef CONFIG_ARM64_INDIRECT_PIO
+#define inb inb
+#define outb outb
+#define insb insb
+#define outsb outsb
+/* external declaration */
+DECLARE_EXTIO(b, u8)
+
+#define inw inw
+#define outw outw
+#define insw insw
+#define outsw outsw
+
+DECLARE_EXTIO(w, u16)
+
+#define inl inl
+#define outl outl
+#define insl insl
+#define outsl outsl
+
+DECLARE_EXTIO(l, u32)
+#endif
+
+
/*
* String version of I/O memory access operations.
*/
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 7d66bba..60e0482 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -31,6 +31,7 @@ arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \
sys_compat.o entry32.o
arm64-obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o entry-ftrace.o
arm64-obj-$(CONFIG_MODULES) += arm64ksyms.o module.o
+arm64-obj-$(CONFIG_ARM64_INDIRECT_PIO) += extio.o
arm64-obj-$(CONFIG_ARM64_MODULE_PLTS) += module-plts.o
arm64-obj-$(CONFIG_PERF_EVENTS) += perf_regs.o perf_callchain.o
arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o
diff --git a/arch/arm64/kernel/extio.c b/arch/arm64/kernel/extio.c
new file mode 100644
index 0000000..80cafd5
--- /dev/null
+++ b/arch/arm64/kernel/extio.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 Hisilicon Limited, All Rights Reserved.
+ * Author: Zhichang Yuan <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 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>
+
+struct extio_ops *arm64_extio_ops;
+
+
+BUILD_EXTIO(b, u8)
+
+BUILD_EXTIO(w, u16)
+
+BUILD_EXTIO(l, u32)
+
+#endif
--
1.9.1

2016-11-01 13:06:40

by zhichang.yuan

[permalink] [raw]
Subject: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range exception for special ISA

Currently if the range property is not specified of_translate_one
returns an error. There are some special devices that work on a
range of I/O ports where it's is not correct to specify a range
property as the cpu addresses are used by special accessors.
Here we add a new exception in of_translate_one to return
the cpu address if the range property is not there. The exception
checks if the parent bus is ISA and if the special accessors are
defined.

Cc: Bjorn Helgaas <[email protected]>
Cc: Rob Herring <[email protected]>
Signed-off-by: zhichang.yuan <[email protected]>
Signed-off-by: Gabriele Paoloni <[email protected]>
---
arch/arm64/include/asm/io.h | 7 +++++++
arch/arm64/kernel/extio.c | 24 +++++++++++++++++++++++
drivers/of/address.c | 47 +++++++++++++++++++++++++++++++++++++++++++--
drivers/pci/pci.c | 6 +++---
include/linux/of_address.h | 17 ++++++++++++++++
5 files changed, 96 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
index 136735d..e480199 100644
--- a/arch/arm64/include/asm/io.h
+++ b/arch/arm64/include/asm/io.h
@@ -175,6 +175,13 @@ static inline u64 __raw_readq(const volatile void __iomem *addr)
#define outsl outsl

DECLARE_EXTIO(l, u32)
+
+
+#define indirect_io_ison indirect_io_ison
+extern int indirect_io_ison(void);
+
+#define chk_indirect_range chk_indirect_range
+extern int chk_indirect_range(u64 taddr);
#endif


diff --git a/arch/arm64/kernel/extio.c b/arch/arm64/kernel/extio.c
index 80cafd5..55df8dc 100644
--- a/arch/arm64/kernel/extio.c
+++ b/arch/arm64/kernel/extio.c
@@ -19,6 +19,30 @@

struct extio_ops *arm64_extio_ops;

+/**
+ * indirect_io_ison - check whether indirectIO can work well. This function only call
+ * before the target I/O address was obtained.
+ *
+ * Returns 1 when indirectIO can work.
+ */
+int indirect_io_ison()
+{
+ return arm64_extio_ops ? 1 : 0;
+}
+
+/**
+ * check_indirect_io - check whether the input taddr is for indirectIO.
+ * @taddr: the io address to be checked.
+ *
+ * Returns 1 when taddr is in the range; otherwise return 0.
+ */
+int chk_indirect_range(u64 taddr)
+{
+ if (arm64_extio_ops->start > taddr || arm64_extio_ops->end < taddr)
+ return 0;
+
+ return 1;
+}

BUILD_EXTIO(b, u8)

diff --git a/drivers/of/address.c b/drivers/of/address.c
index 02b2903..0bee822 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -479,6 +479,39 @@ static int of_empty_ranges_quirk(struct device_node *np)
return false;
}

+
+/*
+ * Check whether the current device being translating use indirectIO.
+ *
+ * return 1 if the check is past, or 0 represents fail checking.
+ */
+static int of_isa_indirect_io(struct device_node *parent,
+ struct of_bus *bus, __be32 *addr,
+ int na, u64 *presult)
+{
+ unsigned int flags;
+ unsigned int rlen;
+
+ /* whether support indirectIO */
+ if (!indirect_io_ison())
+ return 0;
+
+ if (!of_bus_isa_match(parent))
+ return 0;
+
+ flags = bus->get_flags(addr);
+ if (!(flags & IORESOURCE_IO))
+ return 0;
+
+ /* there is ranges property, apply the normal translation directly. */
+ if (of_get_property(parent, "ranges", &rlen))
+ return 0;
+
+ *presult = of_read_number(addr + 1, na - 1);
+
+ return chk_indirect_range(*presult);
+}
+
static int of_translate_one(struct device_node *parent, struct of_bus *bus,
struct of_bus *pbus, __be32 *addr,
int na, int ns, int pna, const char *rprop)
@@ -532,7 +565,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
}
memcpy(addr, ranges + na, 4 * pna);

- finish:
+finish:
of_dump_addr("parent translation for:", addr, pna);
pr_debug("with offset: %llx\n", (unsigned long long)offset);

@@ -595,6 +628,15 @@ static u64 __of_translate_address(struct device_node *dev,
result = of_read_number(addr, na);
break;
}
+ /*
+ * For indirectIO device which has no ranges property, get
+ * the address from reg directly.
+ */
+ if (of_isa_indirect_io(dev, bus, addr, na, &result)) {
+ pr_info("isa indirectIO matched(%s)..addr = 0x%llx\n",
+ of_node_full_name(dev), result);
+ break;
+ }

/* Get new parent bus and counts */
pbus = of_match_bus(parent);
@@ -688,8 +730,9 @@ static int __of_address_to_resource(struct device_node *dev,
if (taddr == OF_BAD_ADDR)
return -EINVAL;
memset(r, 0, sizeof(struct resource));
- if (flags & IORESOURCE_IO) {
+ if (flags & IORESOURCE_IO && taddr >= PCIBIOS_MIN_IO) {
unsigned long port;
+
port = pci_address_to_pio(taddr);
if (port == (unsigned long)-1)
return -EINVAL;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index ba34907..1a08511 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3263,7 +3263,7 @@ int __weak pci_register_io_range(phys_addr_t addr, resource_size_t size)

#ifdef PCI_IOBASE
struct io_range *range;
- resource_size_t allocated_size = 0;
+ resource_size_t allocated_size = PCIBIOS_MIN_IO;

/* check if the range hasn't been previously recorded */
spin_lock(&io_range_lock);
@@ -3312,7 +3312,7 @@ phys_addr_t pci_pio_to_address(unsigned long pio)

#ifdef PCI_IOBASE
struct io_range *range;
- resource_size_t allocated_size = 0;
+ resource_size_t allocated_size = PCIBIOS_MIN_IO;

if (pio > IO_SPACE_LIMIT)
return address;
@@ -3335,7 +3335,7 @@ unsigned long __weak pci_address_to_pio(phys_addr_t address)
{
#ifdef PCI_IOBASE
struct io_range *res;
- resource_size_t offset = 0;
+ resource_size_t offset = PCIBIOS_MIN_IO;
unsigned long addr = -1;

spin_lock(&io_range_lock);
diff --git a/include/linux/of_address.h b/include/linux/of_address.h
index 3786473..0ba7e21 100644
--- a/include/linux/of_address.h
+++ b/include/linux/of_address.h
@@ -24,6 +24,23 @@ struct of_pci_range {
#define for_each_of_pci_range(parser, range) \
for (; of_pci_range_parser_one(parser, range);)

+
+#ifndef indirect_io_ison
+#define indirect_io_ison indirect_io_ison
+static inline int indirect_io_ison(void)
+{
+ return 0;
+}
+#endif
+
+#ifndef chk_indirect_range
+#define chk_indirect_range chk_indirect_range
+static inline int chk_indirect_range(u64 taddr)
+{
+ return 0;
+}
+#endif
+
/* Translate a DMA address from device space to CPU space */
extern u64 of_translate_dma_address(struct device_node *dev,
const __be32 *in_addr);
--
1.9.1

2016-11-01 15:05:53

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range exception for special ISA

Hi zhichang.yuan,

[auto build test ERROR on arm64/for-next/core]
[also build test ERROR on v4.9-rc3 next-20161028]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
[Suggest to use git(>=2.9.0) format-patch --base=<commit> (or --base=auto for convenience) to record what (public, well-known) commit your patch series was built on]
[Check https://git-scm.com/docs/git-format-patch for more information]

url: https://github.com/0day-ci/linux/commits/zhichang-yuan/ARM64-LPC-legacy-ISA-I-O-support/20161101-211425
base: https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-next/core
config: openrisc-or1ksim_defconfig (attached as .config)
compiler: or32-linux-gcc (GCC) 4.5.1-or32-1.0rc1
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=openrisc

All errors (new ones prefixed by >>):

drivers/of/address.c: In function '__of_address_to_resource':
>> drivers/of/address.c:733:40: error: 'PCIBIOS_MIN_IO' undeclared (first use in this function)
drivers/of/address.c:733:40: note: each undeclared identifier is reported only once for each function it appears in

vim +/PCIBIOS_MIN_IO +733 drivers/of/address.c

727 if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
728 return -EINVAL;
729 taddr = of_translate_address(dev, addrp);
730 if (taddr == OF_BAD_ADDR)
731 return -EINVAL;
732 memset(r, 0, sizeof(struct resource));
> 733 if (flags & IORESOURCE_IO && taddr >= PCIBIOS_MIN_IO) {
734 unsigned long port;
735
736 port = pci_address_to_pio(taddr);

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation


Attachments:
(No filename) (1.88 kB)
.config.gz (7.04 kB)
Download all attachments

2016-11-01 15:40:34

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range exception for special ISA

Hi zhichang.yuan,

[auto build test ERROR on arm64/for-next/core]
[also build test ERROR on v4.9-rc3 next-20161028]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
[Suggest to use git(>=2.9.0) format-patch --base=<commit> (or --base=auto for convenience) to record what (public, well-known) commit your patch series was built on]
[Check https://git-scm.com/docs/git-format-patch for more information]

url: https://github.com/0day-ci/linux/commits/zhichang-yuan/ARM64-LPC-legacy-ISA-I-O-support/20161101-211425
base: https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-next/core
config: arm-efm32_defconfig (attached as .config)
compiler: arm-linux-gnueabi-gcc (Debian 6.1.1-9) 6.1.1 20160705
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=arm

All errors (new ones prefixed by >>):

drivers/built-in.o: In function `__of_address_to_resource':
>> drivers/of/address.c:748: undefined reference to `pcibios_min_io'

vim +748 drivers/of/address.c

1f5bef30 Grant Likely 2010-06-08 742 r->start = taddr;
1f5bef30 Grant Likely 2010-06-08 743 r->end = taddr + size - 1;
1f5bef30 Grant Likely 2010-06-08 744 }
1f5bef30 Grant Likely 2010-06-08 745 r->flags = flags;
35f3da32 Benoit Cousson 2011-12-05 746 r->name = name ? name : dev->full_name;
35f3da32 Benoit Cousson 2011-12-05 747
1f5bef30 Grant Likely 2010-06-08 @748 return 0;
1f5bef30 Grant Likely 2010-06-08 749 }
1f5bef30 Grant Likely 2010-06-08 750
1f5bef30 Grant Likely 2010-06-08 751 /**

:::::: The code at line 748 was first introduced by commit
:::::: 1f5bef30cf6c66f097ea5dfc580a41924df888d1 of/address: merge of_address_to_resource()

:::::: TO: Grant Likely <[email protected]>
:::::: CC: Grant Likely <[email protected]>

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation


Attachments:
(No filename) (2.12 kB)
.config.gz (10.63 kB)
Download all attachments

2016-11-01 16:59:42

by Bjorn Helgaas

[permalink] [raw]
Subject: Re: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range exception for special ISA

On Tue, Nov 01, 2016 at 09:28:45PM +0800, zhichang.yuan wrote:
> Currently if the range property is not specified of_translate_one
> returns an error. There are some special devices that work on a
> range of I/O ports where it's is not correct to specify a range
> property as the cpu addresses are used by special accessors.
> Here we add a new exception in of_translate_one to return
> the cpu address if the range property is not there. The exception
> checks if the parent bus is ISA and if the special accessors are
> defined.

Using "()" after function names helps distinguish them from text.

s/it's is/it's/

I haven't been paying attention, so I missed the context. But "as the
cpu addresses are used by special accessors" doesn't really make sense
to me. In general, *most* acccessors use CPU addresses, i.e.,
resource addresses. Accessors don't use bus addresses because we may
have multiple instances of a bus, and we may reuse bus address ranges
on the different instances.

In the patch, I see a check for "parent bus is ISA"
("of_bus_isa_match(parent)"), but I don't see the check for whether
the special accessors are defined, so I can't quite connect the dots.

> Cc: Bjorn Helgaas <[email protected]>
> Cc: Rob Herring <[email protected]>
> Signed-off-by: zhichang.yuan <[email protected]>
> Signed-off-by: Gabriele Paoloni <[email protected]>
> ---
> arch/arm64/include/asm/io.h | 7 +++++++
> arch/arm64/kernel/extio.c | 24 +++++++++++++++++++++++
> drivers/of/address.c | 47 +++++++++++++++++++++++++++++++++++++++++++--
> drivers/pci/pci.c | 6 +++---
> include/linux/of_address.h | 17 ++++++++++++++++
> 5 files changed, 96 insertions(+), 5 deletions(-)
>
> diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
> index 136735d..e480199 100644
> --- a/arch/arm64/include/asm/io.h
> +++ b/arch/arm64/include/asm/io.h
> @@ -175,6 +175,13 @@ static inline u64 __raw_readq(const volatile void __iomem *addr)
> #define outsl outsl
>
> DECLARE_EXTIO(l, u32)
> +
> +
> +#define indirect_io_ison indirect_io_ison
> +extern int indirect_io_ison(void);

This makes it look like "ison" is some new word I'm not familiar with.
"indirect_io_is_on()" or even "indirect_io_enabled()" would be more
readable.

> +
> +#define chk_indirect_range chk_indirect_range
> +extern int chk_indirect_range(u64 taddr);
> #endif
>
>
> diff --git a/arch/arm64/kernel/extio.c b/arch/arm64/kernel/extio.c
> index 80cafd5..55df8dc 100644
> --- a/arch/arm64/kernel/extio.c
> +++ b/arch/arm64/kernel/extio.c
> @@ -19,6 +19,30 @@
>
> struct extio_ops *arm64_extio_ops;
>
> +/**
> + * indirect_io_ison - check whether indirectIO can work well. This function only call
> + * before the target I/O address was obtained.
> + *
> + * Returns 1 when indirectIO can work.
> + */
> +int indirect_io_ison()
> +{
> + return arm64_extio_ops ? 1 : 0;
> +}
> +
> +/**
> + * check_indirect_io - check whether the input taddr is for indirectIO.

Comment name ("check_indirect_io") doesn't match actual function name
("chk_indirect_range").

One of my pet peeves: "check" is completely worthless as part of a
function name because it doesn't help the reader figure out the sense
of the result. What does a "true" result mean? Name it something
like "address_is_indirect()" so it reads naturally when the caller
does something like "if (address_is_indirect())"

> + * @taddr: the io address to be checked.
> + *
> + * Returns 1 when taddr is in the range; otherwise return 0.
> + */
> +int chk_indirect_range(u64 taddr)
> +{
> + if (arm64_extio_ops->start > taddr || arm64_extio_ops->end < taddr)
> + return 0;
> +
> + return 1;
> +}
>
> BUILD_EXTIO(b, u8)
>
> diff --git a/drivers/of/address.c b/drivers/of/address.c
> index 02b2903..0bee822 100644
> --- a/drivers/of/address.c
> +++ b/drivers/of/address.c
> @@ -479,6 +479,39 @@ static int of_empty_ranges_quirk(struct device_node *np)
> return false;
> }
>
> +
> +/*
> + * Check whether the current device being translating use indirectIO.

What does "the current device" mean? I assume you're talking about
"any device on 'bus'"? And apparently the caller is inquiring about a
particular address, too?

> + * return 1 if the check is past, or 0 represents fail checking.

This doesn't really make sense. I assume you mean something like
"return 1 if 'address' uses indirectIO; 0 otherwise"?

> + */
> +static int of_isa_indirect_io(struct device_node *parent,
> + struct of_bus *bus, __be32 *addr,
> + int na, u64 *presult)
> +{
> + unsigned int flags;
> + unsigned int rlen;
> +
> + /* whether support indirectIO */
> + if (!indirect_io_ison())
> + return 0;
> +
> + if (!of_bus_isa_match(parent))
> + return 0;
> +
> + flags = bus->get_flags(addr);
> + if (!(flags & IORESOURCE_IO))
> + return 0;
> +
> + /* there is ranges property, apply the normal translation directly. */
> + if (of_get_property(parent, "ranges", &rlen))
> + return 0;
> +
> + *presult = of_read_number(addr + 1, na - 1);
> +
> + return chk_indirect_range(*presult);
> +}
> +
> static int of_translate_one(struct device_node *parent, struct of_bus *bus,
> struct of_bus *pbus, __be32 *addr,
> int na, int ns, int pna, const char *rprop)
> @@ -532,7 +565,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
> }
> memcpy(addr, ranges + na, 4 * pna);
>
> - finish:
> +finish:
> of_dump_addr("parent translation for:", addr, pna);
> pr_debug("with offset: %llx\n", (unsigned long long)offset);
>
> @@ -595,6 +628,15 @@ static u64 __of_translate_address(struct device_node *dev,
> result = of_read_number(addr, na);
> break;
> }
> + /*
> + * For indirectIO device which has no ranges property, get
> + * the address from reg directly.
> + */
> + if (of_isa_indirect_io(dev, bus, addr, na, &result)) {
> + pr_info("isa indirectIO matched(%s)..addr = 0x%llx\n",
> + of_node_full_name(dev), result);
> + break;
> + }
>
> /* Get new parent bus and counts */
> pbus = of_match_bus(parent);
> @@ -688,8 +730,9 @@ static int __of_address_to_resource(struct device_node *dev,
> if (taddr == OF_BAD_ADDR)
> return -EINVAL;
> memset(r, 0, sizeof(struct resource));
> - if (flags & IORESOURCE_IO) {
> + if (flags & IORESOURCE_IO && taddr >= PCIBIOS_MIN_IO) {
> unsigned long port;
> +
> port = pci_address_to_pio(taddr);
> if (port == (unsigned long)-1)
> return -EINVAL;
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index ba34907..1a08511 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -3263,7 +3263,7 @@ int __weak pci_register_io_range(phys_addr_t addr, resource_size_t size)
>
> #ifdef PCI_IOBASE
> struct io_range *range;
> - resource_size_t allocated_size = 0;
> + resource_size_t allocated_size = PCIBIOS_MIN_IO;

I don't understand what's going on here. PCIBIOS_MIN_IO is an
*address*, so you're setting a *size* to an address. Maybe this just
needs an explanation. The connection to the rest of this patch isn't
obvious. If it could be split to a separate patch, so much the
better; then you'd have a nice place to describe it.

> /* check if the range hasn't been previously recorded */
> spin_lock(&io_range_lock);
> @@ -3312,7 +3312,7 @@ phys_addr_t pci_pio_to_address(unsigned long pio)
>
> #ifdef PCI_IOBASE
> struct io_range *range;
> - resource_size_t allocated_size = 0;
> + resource_size_t allocated_size = PCIBIOS_MIN_IO;
>
> if (pio > IO_SPACE_LIMIT)
> return address;
> @@ -3335,7 +3335,7 @@ unsigned long __weak pci_address_to_pio(phys_addr_t address)
> {
> #ifdef PCI_IOBASE
> struct io_range *res;
> - resource_size_t offset = 0;
> + resource_size_t offset = PCIBIOS_MIN_IO;
> unsigned long addr = -1;
>
> spin_lock(&io_range_lock);
> diff --git a/include/linux/of_address.h b/include/linux/of_address.h
> index 3786473..0ba7e21 100644
> --- a/include/linux/of_address.h
> +++ b/include/linux/of_address.h
> @@ -24,6 +24,23 @@ struct of_pci_range {
> #define for_each_of_pci_range(parser, range) \
> for (; of_pci_range_parser_one(parser, range);)
>
> +
> +#ifndef indirect_io_ison
> +#define indirect_io_ison indirect_io_ison
> +static inline int indirect_io_ison(void)
> +{
> + return 0;
> +}
> +#endif
> +
> +#ifndef chk_indirect_range
> +#define chk_indirect_range chk_indirect_range
> +static inline int chk_indirect_range(u64 taddr)
> +{
> + return 0;
> +}
> +#endif
> +
> /* Translate a DMA address from device space to CPU space */
> extern u64 of_translate_dma_address(struct device_node *dev,
> const __be32 *in_addr);
> --
> 1.9.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-pci" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2016-11-02 21:52:21

by Gabriele Paoloni

[permalink] [raw]
Subject: RE: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range exception for special ISA

Hi Bjorn

Many Thanks for reviewing this

> -----Original Message-----
> From: Bjorn Helgaas [mailto:[email protected]]
> Sent: 01 November 2016 17:00
> To: Yuanzhichang
> Cc: [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected]; linux-arm-
> [email protected]; [email protected]; linux-
> [email protected]; Linuxarm; [email protected]; linux-
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> John Garry; Gabriele Paoloni; [email protected];
> [email protected]; xuwei (O)
> Subject: Re: [PATCH/RESEND V4 2/3] ARM64 LPC: Add missing range
> exception for special ISA
>
> On Tue, Nov 01, 2016 at 09:28:45PM +0800, zhichang.yuan wrote:
> > Currently if the range property is not specified of_translate_one
> > returns an error. There are some special devices that work on a
> > range of I/O ports where it's is not correct to specify a range
> > property as the cpu addresses are used by special accessors.
> > Here we add a new exception in of_translate_one to return
> > the cpu address if the range property is not there. The exception
> > checks if the parent bus is ISA and if the special accessors are
> > defined.
>
> Using "()" after function names helps distinguish them from text.
>
> s/it's is/it's/

Sure we'll fix above nits in the next version

>
> I haven't been paying attention, so I missed the context. But "as the
> cpu addresses are used by special accessors" doesn't really make sense
> to me. In general, *most* acccessors use CPU addresses, i.e.,
> resource addresses. Accessors don't use bus addresses because we may
> have multiple instances of a bus, and we may reuse bus address ranges
> on the different instances.

Basically our LPC device use a CPU address range (not a bus range as
a bus range is supposed to be remapped into a cpu range using the range
property).

This CPIU address range is [0, PCIBIOS_MIN_IO-1].
This patch effectively does 2 things:
1) reserve the cpu physical range [0, PCIBIOS_MIN_IO-1] so that it cannot
be used by PCI I/O space (see drivers/pci/pci.c)

2) Avoid to translate the cpu addresses that belongs to LPC (in fact
for this device we do not specify a range property in the DT): see
changes in __of_translate_address()

3) Do not retrieve an I/O token for I/O addresses that belong to LPC
(in fact LPC accessors work directly on the CPU physical addresses):
see change in __of_address_to_resource()

Probably 1) and 3) can be put in a separate patch with respect to 2)...

What do you think?

>
> In the patch, I see a check for "parent bus is ISA"
> ("of_bus_isa_match(parent)"), but I don't see the check for whether
> the special accessors are defined, so I can't quite connect the dots.

See of_isa_indirect_io() called just before of_bus_isa_match(parent)...

>
> > Cc: Bjorn Helgaas <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Signed-off-by: zhichang.yuan <[email protected]>
> > Signed-off-by: Gabriele Paoloni <[email protected]>
> > ---
> > arch/arm64/include/asm/io.h | 7 +++++++
> > arch/arm64/kernel/extio.c | 24 +++++++++++++++++++++++
> > drivers/of/address.c | 47
> +++++++++++++++++++++++++++++++++++++++++++--
> > drivers/pci/pci.c | 6 +++---
> > include/linux/of_address.h | 17 ++++++++++++++++
> > 5 files changed, 96 insertions(+), 5 deletions(-)
> >
> > diff --git a/arch/arm64/include/asm/io.h
> b/arch/arm64/include/asm/io.h
> > index 136735d..e480199 100644
> > --- a/arch/arm64/include/asm/io.h
> > +++ b/arch/arm64/include/asm/io.h
> > @@ -175,6 +175,13 @@ static inline u64 __raw_readq(const volatile
> void __iomem *addr)
> > #define outsl outsl
> >
> > DECLARE_EXTIO(l, u32)
> > +
> > +
> > +#define indirect_io_ison indirect_io_ison
> > +extern int indirect_io_ison(void);
>
> This makes it look like "ison" is some new word I'm not familiar with.
> "indirect_io_is_on()" or even "indirect_io_enabled()" would be more
> readable.

Sure agreed

>
> > +
> > +#define chk_indirect_range chk_indirect_range
> > +extern int chk_indirect_range(u64 taddr);
> > #endif
> >
> >
> > diff --git a/arch/arm64/kernel/extio.c b/arch/arm64/kernel/extio.c
> > index 80cafd5..55df8dc 100644
> > --- a/arch/arm64/kernel/extio.c
> > +++ b/arch/arm64/kernel/extio.c
> > @@ -19,6 +19,30 @@
> >
> > struct extio_ops *arm64_extio_ops;
> >
> > +/**
> > + * indirect_io_ison - check whether indirectIO can work well. This
> function only call
> > + * before the target I/O address was obtained.
> > + *
> > + * Returns 1 when indirectIO can work.
> > + */
> > +int indirect_io_ison()
> > +{
> > + return arm64_extio_ops ? 1 : 0;
> > +}
> > +
> > +/**
> > + * check_indirect_io - check whether the input taddr is for
> indirectIO.
>
> Comment name ("check_indirect_io") doesn't match actual function name
> ("chk_indirect_range").
>
> One of my pet peeves: "check" is completely worthless as part of a
> function name because it doesn't help the reader figure out the sense
> of the result. What does a "true" result mean? Name it something
> like "address_is_indirect()" so it reads naturally when the caller
> does something like "if (address_is_indirect())"

Yes it makes sense, we can change to " address_is_indirect"

>
> > + * @taddr: the io address to be checked.
> > + *
> > + * Returns 1 when taddr is in the range; otherwise return 0.
> > + */
> > +int chk_indirect_range(u64 taddr)
> > +{
> > + if (arm64_extio_ops->start > taddr || arm64_extio_ops->end <
> taddr)
> > + return 0;
> > +
> > + return 1;
> > +}
> >
> > BUILD_EXTIO(b, u8)
> >
> > diff --git a/drivers/of/address.c b/drivers/of/address.c
> > index 02b2903..0bee822 100644
> > --- a/drivers/of/address.c
> > +++ b/drivers/of/address.c
> > @@ -479,6 +479,39 @@ static int of_empty_ranges_quirk(struct
> device_node *np)
> > return false;
> > }
> >
> > +
> > +/*
> > + * Check whether the current device being translating use
> indirectIO.
>
> What does "the current device" mean? I assume you're talking about
> "any device on 'bus'"? And apparently the caller is inquiring about a
> particular address, too?

Effectively we check if the device is lying on a ISA bus, if the accessors
struct pointer is set, if the parent device is an ISA bus and if the device
physical addresses are within the range reserved for LPC...

So we check all the conditions that allow us to say that this device
is lying on a special ISA bus device

>
> > + * return 1 if the check is past, or 0 represents fail checking.
>
> This doesn't really make sense. I assume you mean something like
> "return 1 if 'address' uses indirectIO; 0 otherwise"?

Yes the comment is quite bad; we'll change this in the next version

>
> > + */
> > +static int of_isa_indirect_io(struct device_node *parent,
> > + struct of_bus *bus, __be32 *addr,
> > + int na, u64 *presult)
> > +{
> > + unsigned int flags;
> > + unsigned int rlen;
> > +
> > + /* whether support indirectIO */
> > + if (!indirect_io_ison())
> > + return 0;
> > +
> > + if (!of_bus_isa_match(parent))
> > + return 0;
> > +
> > + flags = bus->get_flags(addr);
> > + if (!(flags & IORESOURCE_IO))
> > + return 0;
> > +
> > + /* there is ranges property, apply the normal translation
> directly. */
> > + if (of_get_property(parent, "ranges", &rlen))
> > + return 0;
> > +
> > + *presult = of_read_number(addr + 1, na - 1);
> > +
> > + return chk_indirect_range(*presult);
> > +}
> > +
> > static int of_translate_one(struct device_node *parent, struct
> of_bus *bus,
> > struct of_bus *pbus, __be32 *addr,
> > int na, int ns, int pna, const char *rprop)
> > @@ -532,7 +565,7 @@ static int of_translate_one(struct device_node
> *parent, struct of_bus *bus,
> > }
> > memcpy(addr, ranges + na, 4 * pna);
> >
> > - finish:
> > +finish:
> > of_dump_addr("parent translation for:", addr, pna);
> > pr_debug("with offset: %llx\n", (unsigned long long)offset);
> >
> > @@ -595,6 +628,15 @@ static u64 __of_translate_address(struct
> device_node *dev,
> > result = of_read_number(addr, na);
> > break;
> > }
> > + /*
> > + * For indirectIO device which has no ranges property, get
> > + * the address from reg directly.
> > + */
> > + if (of_isa_indirect_io(dev, bus, addr, na, &result)) {
> > + pr_info("isa indirectIO matched(%s)..addr =
> 0x%llx\n",
> > + of_node_full_name(dev), result);
> > + break;
> > + }
> >
> > /* Get new parent bus and counts */
> > pbus = of_match_bus(parent);
> > @@ -688,8 +730,9 @@ static int __of_address_to_resource(struct
> device_node *dev,
> > if (taddr == OF_BAD_ADDR)
> > return -EINVAL;
> > memset(r, 0, sizeof(struct resource));
> > - if (flags & IORESOURCE_IO) {
> > + if (flags & IORESOURCE_IO && taddr >= PCIBIOS_MIN_IO) {
> > unsigned long port;
> > +
> > port = pci_address_to_pio(taddr);
> > if (port == (unsigned long)-1)
> > return -EINVAL;
> > diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> > index ba34907..1a08511 100644
> > --- a/drivers/pci/pci.c
> > +++ b/drivers/pci/pci.c
> > @@ -3263,7 +3263,7 @@ int __weak pci_register_io_range(phys_addr_t
> addr, resource_size_t size)
> >
> > #ifdef PCI_IOBASE
> > struct io_range *range;
> > - resource_size_t allocated_size = 0;
> > + resource_size_t allocated_size = PCIBIOS_MIN_IO;
>
> I don't understand what's going on here. PCIBIOS_MIN_IO is an
> *address*, so you're setting a *size* to an address. Maybe this just
> needs an explanation. The connection to the rest of this patch isn't
> obvious. If it could be split to a separate patch, so much the
> better; then you'd have a nice place to describe it.

Please see my first comment above...this is needed to reserve the
cpu address range for the LPC...

>
> > /* check if the range hasn't been previously recorded */
> > spin_lock(&io_range_lock);
> > @@ -3312,7 +3312,7 @@ phys_addr_t pci_pio_to_address(unsigned long
> pio)
> >
> > #ifdef PCI_IOBASE
> > struct io_range *range;
> > - resource_size_t allocated_size = 0;
> > + resource_size_t allocated_size = PCIBIOS_MIN_IO;
> >
> > if (pio > IO_SPACE_LIMIT)
> > return address;
> > @@ -3335,7 +3335,7 @@ unsigned long __weak
> pci_address_to_pio(phys_addr_t address)
> > {
> > #ifdef PCI_IOBASE
> > struct io_range *res;
> > - resource_size_t offset = 0;
> > + resource_size_t offset = PCIBIOS_MIN_IO;
> > unsigned long addr = -1;
> >
> > spin_lock(&io_range_lock);
> > diff --git a/include/linux/of_address.h b/include/linux/of_address.h
> > index 3786473..0ba7e21 100644
> > --- a/include/linux/of_address.h
> > +++ b/include/linux/of_address.h
> > @@ -24,6 +24,23 @@ struct of_pci_range {
> > #define for_each_of_pci_range(parser, range) \
> > for (; of_pci_range_parser_one(parser, range);)
> >
> > +
> > +#ifndef indirect_io_ison
> > +#define indirect_io_ison indirect_io_ison
> > +static inline int indirect_io_ison(void)
> > +{
> > + return 0;
> > +}
> > +#endif
> > +
> > +#ifndef chk_indirect_range
> > +#define chk_indirect_range chk_indirect_range
> > +static inline int chk_indirect_range(u64 taddr)
> > +{
> > + return 0;
> > +}
> > +#endif
> > +
> > /* Translate a DMA address from device space to CPU space */
> > extern u64 of_translate_dma_address(struct device_node *dev,
> > const __be32 *in_addr);
> > --
> > 1.9.1
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-pci"
> in
> > the body of a message to [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html

2016-11-07 11:34:52

by Mark Rutland

[permalink] [raw]
Subject: Re: [PATCH/RESEND V4 3/3] ARM64 LPC: LPC driver implementation on Hip06

Hi,

I received an off-list v5 of this for some reason.

I have several concerns with this, but given there's evidently a v5, I
intend to wait until that is posted before I reply with my comments.

Thanks,
Mark.

On Tue, Nov 01, 2016 at 09:28:46PM +0800, zhichang.yuan wrote:
> On Hip06, the accesses to LPC peripherals work in an indirect way. A
> corresponding LPC driver configure some registers in LPC master at first, then
> the real accesses on LPC slave devices are finished by the LPC master, which
> is transparent to LPC driver.
> This patch implement the relevant driver for Hip06 LPC. Cooperating with
> indirect-IO, ipmi messages is in service without any changes on ipmi driver.
>
> Cc: Mark Rutland <[email protected]>
> Signed-off-by: zhichang.yuan <[email protected]>
> Signed-off-by: Gabriele Paoloni <[email protected]>
> ---
> .../arm/hisilicon/hisilicon-low-pin-count.txt | 31 ++
> MAINTAINERS | 8 +
> drivers/bus/Kconfig | 8 +
> drivers/bus/Makefile | 1 +
> drivers/bus/hisi_lpc.c | 501 +++++++++++++++++++++
> 5 files changed, 549 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
> create mode 100644 drivers/bus/hisi_lpc.c
>
> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
> new file mode 100644
> index 0000000..e681419
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt
> @@ -0,0 +1,31 @@
> +Hisilicon Hip06 low-pin-count device
> + Usually LPC controller is part of PCI host bridge, so the legacy ISA ports
> + locate on LPC bus can be accessed direclty. But some SoCs have independent
> + LPC controller, and access the legacy ports by triggering LPC I/O cycles.
> + Hisilicon Hip06 implements this LPC device.
> +
> +Required properties:
> +- compatible: should be "hisilicon,low-pin-count"
> +- #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 register set for this device 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 = "hisilicom,low-pin-count";
> + #address-cells = <2>;
> + #size-cells = <1>;
> + reg = <0x0 0xa01b0000 0x0 0x1000>;
> +
> + ipmi0: bt@e4 {
> + compatible = "ipmi-bt";
> + device_type = "ipmi";
> + reg = <0x01 0xe4 0x04>;
> + status = "disabled";
> + };
> +};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1cd38a7..7c69410 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5716,6 +5716,14 @@ F: include/uapi/linux/if_hippi.h
> F: net/802/hippi.c
> F: drivers/net/hippi/
>
> +HISILICON LPC BUS DRIVER
> +M: Zhichang Yuan <[email protected]>
> +L: [email protected]
> +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
> M: Yisen Zhuang <[email protected]>
> M: Salil Mehta <[email protected]>
> diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
> index 7010dca..a108abc 100644
> --- a/drivers/bus/Kconfig
> +++ b/drivers/bus/Kconfig
> @@ -64,6 +64,14 @@ config BRCMSTB_GISB_ARB
> arbiter. This driver provides timeout and target abort error handling
> and internal bus master decoding.
>
> +config HISILICON_LPC
> + bool "Workaround for nonstandard ISA I/O space on Hisilicon Hip0X"
> + depends on (ARCH_HISI || COMPILE_TEST) && ARM64
> + select ARM64_INDIRECT_PIO
> + help
> + Driver needed for some legacy ISA devices attached to Low-Pin-Count
> + on Hisilicon Hip0X SoC.
> +
> config IMX_WEIM
> bool "Freescale EIM DRIVER"
> depends on ARCH_MXC
> diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
> index c6cfa6b..10b4983 100644
> --- a/drivers/bus/Makefile
> +++ b/drivers/bus/Makefile
> @@ -7,6 +7,7 @@ obj-$(CONFIG_ARM_CCI) += arm-cci.o
> obj-$(CONFIG_ARM_CCN) += arm-ccn.o
>
> obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o
> +obj-$(CONFIG_HISILICON_LPC) += hisi_lpc.o
> obj-$(CONFIG_IMX_WEIM) += imx-weim.o
> obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o
> obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o
> diff --git a/drivers/bus/hisi_lpc.c b/drivers/bus/hisi_lpc.c
> new file mode 100644
> index 0000000..9f48a1a
> --- /dev/null
> +++ b/drivers/bus/hisi_lpc.c
> @@ -0,0 +1,501 @@
> +/*
> + * Copyright (C) 2016 Hisilicon Limited, All Rights Reserved.
> + * Author: Zhichang Yuan <[email protected]>
> + * Author: Zou Rongrong <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 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/acpi.h>
> +#include <linux/console.h>
> +#include <linux/delay.h>
> +#include <linux/io.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/serial_8250.h>
> +#include <linux/slab.h>
> +
> +/*
> + * this bit set means each IO operation will target to different port address;
> + * 0 means repeatly IO operations will be sticked on the same port, such as BT;
> + */
> +#define FG_INCRADDR_LPC 0x02
> +
> +struct lpc_cycle_para {
> + unsigned int opflags;
> + unsigned int csize;/* the data length of each operation */
> +};
> +
> +struct hisilpc_dev {
> + spinlock_t cycle_lock;
> + void __iomem *membase;
> + struct extio_ops io_ops;
> +};
> +
> +
> +/* The maximum continous operations*/
> +#define LPC_MAX_OPCNT 16
> +/* only support IO data unit length is four at maximum */
> +#define LPC_MAX_DULEN 4
> +#if LPC_MAX_DULEN > LPC_MAX_OPCNT
> +#error "LPC.. MAX_DULEN must be not bigger than MAX_OPCNT!"
> +#endif
> +
> +#define LPC_REG_START 0x00/* start a new LPC cycle */
> +#define LPC_REG_OP_STATUS 0x04/* the current LPC status */
> +#define LPC_REG_IRQ_ST 0x08/* interrupt enable&status */
> +#define LPC_REG_OP_LEN 0x10/* how many LPC cycles each start */
> +#define LPC_REG_CMD 0x14/* command for the required LPC cycle */
> +#define LPC_REG_ADDR 0x20/* LPC target address */
> +#define LPC_REG_WDATA 0x24/* data to be written */
> +#define LPC_REG_RDATA 0x28/* data coming from peer */
> +
> +
> +/* The command register fields*/
> +#define LPC_CMD_SAMEADDR 0x08
> +#define LPC_CMD_TYPE_IO 0x00
> +#define LPC_CMD_WRITE 0x01
> +#define LPC_CMD_READ 0x00
> +/* the bit attribute is W1C. 1 represents OK. */
> +#define LPC_STAT_BYIRQ 0x02
> +
> +#define LPC_STATUS_IDLE 0x01
> +#define LPC_OP_FINISHED 0x02
> +
> +#define START_WORK 0x01
> +
> +/*
> + * The minimal waiting interval... Suggest it is not less than 10.
> + * Bigger value probably will lower the performance.
> + */
> +#define LPC_NSEC_PERWAIT 100
> +/*
> + * The maximum waiting time is about 128us.
> + * 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 specfic for single IO operation, such as inb. */
> +#define LPC_PEROP_WAITCNT 100
> +
> +
> +static inline int wait_lpc_idle(unsigned char *mbase,
> + unsigned int waitcnt) {
> + u32 opstatus;
> +
> + while (waitcnt--) {
> + ndelay(LPC_NSEC_PERWAIT);
> + opstatus = readl(mbase + LPC_REG_OP_STATUS);
> + if (opstatus & LPC_STATUS_IDLE)
> + return (opstatus & LPC_OP_FINISHED) ? 0 : (-EIO);
> + }
> + return -ETIME;
> +}
> +
> +
> +/**
> + * hisilpc_target_in - trigger a series of lpc cycles to read required data
> + * from target periperal.
> + * @pdev: pointer to hisi lpc device
> + * @para: some paramerters used to control the lpc I/O operations
> + * @ptaddr: the lpc I/O target port address
> + * @buf: where the read back data is stored
> + * @opcnt: how many I/O operations required in this calling
> + *
> + * only one byte data is read each I/O operation.
> + *
> + * Returns 0 on success, non-zero on fail.
> + *
> + */
> +static int hisilpc_target_in(struct hisilpc_dev *pdev,
> + struct lpc_cycle_para *para,
> + unsigned long ptaddr, unsigned char *buf,
> + unsigned long opcnt)
> +{
> + unsigned int cmd_word;
> + unsigned int waitcnt;
> + int retval;
> + unsigned long flags;
> + unsigned long cnt_per_trans;
> +
> + if (!buf || !opcnt || !para || !para->csize || !pdev)
> + return -EINVAL;
> +
> + if (opcnt > LPC_MAX_OPCNT)
> + return -EINVAL;
> +
> + cmd_word = LPC_CMD_TYPE_IO | LPC_CMD_READ;
> + waitcnt = (LPC_PEROP_WAITCNT);
> + if (!(para->opflags & FG_INCRADDR_LPC)) {
> + cmd_word |= LPC_CMD_SAMEADDR;
> + waitcnt = LPC_MAX_WAITCNT;
> + }
> +
> + retval = 0;
> + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize;
> + for (; opcnt && !retval; cnt_per_trans = para->csize) {
> + /* whole operation must be atomic */
> + spin_lock_irqsave(&pdev->cycle_lock, flags);
> +
> + writel(cnt_per_trans, pdev->membase + LPC_REG_OP_LEN);
> +
> + writel(cmd_word, pdev->membase + LPC_REG_CMD);
> +
> + writel(ptaddr, pdev->membase + LPC_REG_ADDR);
> +
> + writel(START_WORK, pdev->membase + LPC_REG_START);
> +
> + /* whether the operation is finished */
> + retval = wait_lpc_idle(pdev->membase, waitcnt);
> + if (!retval) {
> + opcnt -= cnt_per_trans;
> + for (; cnt_per_trans--; buf++)
> + *buf = readl(pdev->membase + LPC_REG_RDATA);
> + }
> +
> + spin_unlock_irqrestore(&pdev->cycle_lock, flags);
> + }
> +
> + return retval;
> +}
> +
> +/**
> + * hisilpc_target_out - trigger a series of lpc cycles to write required data
> + * to target periperal.
> + * @pdev: pointer to hisi lpc device
> + * @para: some paramerters used to control the lpc I/O operations
> + * @ptaddr: the lpc I/O target port address
> + * @buf: where the data to be written is stored
> + * @opcnt: how many I/O operations required
> + *
> + * only one byte data is read each I/O operation.
> + *
> + * Returns 0 on success, non-zero on fail.
> + *
> + */
> +static int hisilpc_target_out(struct hisilpc_dev *pdev,
> + struct lpc_cycle_para *para,
> + unsigned long ptaddr,
> + const unsigned char *buf,
> + unsigned long opcnt)
> +{
> + unsigned int cmd_word;
> + unsigned int waitcnt;
> + int retval;
> + unsigned long flags;
> + unsigned long cnt_per_trans;
> +
> + if (!buf || !opcnt || !para || !pdev)
> + return -EINVAL;
> +
> + if (opcnt > LPC_MAX_OPCNT)
> + return -EINVAL;
> + /* default is increasing address */
> + cmd_word = LPC_CMD_TYPE_IO | LPC_CMD_WRITE;
> + waitcnt = (LPC_PEROP_WAITCNT);
> + if (!(para->opflags & FG_INCRADDR_LPC)) {
> + cmd_word |= LPC_CMD_SAMEADDR;
> + waitcnt = LPC_MAX_WAITCNT;
> + }
> +
> + retval = 0;
> + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize;
> + for (; opcnt && !retval; cnt_per_trans = para->csize) {
> + spin_lock_irqsave(&pdev->cycle_lock, flags);
> +
> + writel(cnt_per_trans, pdev->membase + LPC_REG_OP_LEN);
> + opcnt -= cnt_per_trans;
> + for (; cnt_per_trans--; buf++)
> + writel(*buf, pdev->membase + LPC_REG_WDATA);
> +
> + writel(cmd_word, pdev->membase + LPC_REG_CMD);
> +
> + writel(ptaddr, pdev->membase + LPC_REG_ADDR);
> +
> + writel(START_WORK, pdev->membase + LPC_REG_START);
> +
> + /* whether the operation is finished */
> + retval = wait_lpc_idle(pdev->membase, waitcnt);
> +
> + spin_unlock_irqrestore(&pdev->cycle_lock, flags);
> + }
> +
> + return retval;
> +}
> +
> +
> +/**
> + * hisilpc_comm_in - read/input the data from the I/O peripheral through LPC.
> + * @devobj: pointer to the device information relevant to LPC controller.
> + * @ptaddr: the target I/O port address.
> + * @dlen: the data length required to read from the target I/O port.
> + *
> + * when succeed, the data read back is stored in buffer pointed by inbuf.
> + * For inb, return the data read from I/O or -1 when error occur.
> + */
> +static u64 hisilpc_comm_in(void *devobj, unsigned long ptaddr, size_t dlen)
> +{
> + struct hisilpc_dev *lpcdev;
> + struct lpc_cycle_para iopara;
> + u32 rd_data;
> + unsigned char *newbuf;
> + int ret = 0;
> +
> + if (!devobj || !dlen || dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
> + return -1;
> +
> + /* the local buffer must be enough for one data unit */
> + if (sizeof(rd_data) < dlen)
> + return -1;
> +
> + newbuf = (unsigned char *)&rd_data;
> +
> + lpcdev = (struct hisilpc_dev *)devobj;
> +
> + iopara.opflags = FG_INCRADDR_LPC;
> + iopara.csize = dlen;
> +
> + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, newbuf, dlen);
> + if (ret)
> + return -1;
> +
> + return le32_to_cpu(rd_data);
> +}
> +
> +/**
> + * hisilpc_comm_out - write/output the data whose maximal length is four bytes to
> + * the I/O peripheral through LPC.
> + * @devobj: pointer to the device information relevant to LPC controller.
> + * @outval: a value to be outputed from caller, maximum is four bytes.
> + * @ptaddr: the target I/O port address.
> + * @dlen: the data length required writing to the target I/O port .
> + *
> + * This function is corresponding to out(b,w,l) only
> + *
> + */
> +static void hisilpc_comm_out(void *devobj, unsigned long ptaddr,
> + u32 outval, size_t dlen)
> +{
> + struct hisilpc_dev *lpcdev;
> + struct lpc_cycle_para iopara;
> + const unsigned char *newbuf;
> +
> + if (!devobj || !dlen || dlen > LPC_MAX_DULEN)
> + return;
> +
> + if (sizeof(outval) < dlen)
> + return;
> +
> + outval = cpu_to_le32(outval);
> +
> + newbuf = (const unsigned char *)&outval;
> + lpcdev = (struct hisilpc_dev *)devobj;
> +
> + iopara.opflags = FG_INCRADDR_LPC;
> + iopara.csize = dlen;
> +
> + hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf, dlen);
> +}
> +
> +
> +/**
> + * hisilpc_comm_ins - read/input the data in buffer to the I/O peripheral
> + * through LPC, it corresponds to ins(b,w,l)
> + * @devobj: pointer to the device information relevant to LPC controller.
> + * @ptaddr: the target I/O port address.
> + * @inbuf: a buffer where read/input data bytes are stored.
> + * @dlen: the data length required writing to the target I/O port .
> + * @count: how many data units whose length is dlen will be read.
> + *
> + */
> +static u64 hisilpc_comm_ins(void *devobj, unsigned long ptaddr,
> + void *inbuf, size_t dlen, unsigned int count)
> +{
> + struct hisilpc_dev *lpcdev;
> + struct lpc_cycle_para iopara;
> + unsigned char *newbuf;
> + unsigned int loopcnt, cntleft;
> + unsigned int max_perburst;
> + int ret = 0;
> +
> + if (!devobj || !inbuf || !count || !dlen ||
> + dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
> + return -1;
> +
> + iopara.opflags = 0;
> + if (dlen > 1)
> + iopara.opflags |= FG_INCRADDR_LPC;
> + iopara.csize = dlen;
> +
> + lpcdev = (struct hisilpc_dev *)devobj;
> + newbuf = (unsigned char *)inbuf;
> + /*
> + * ensure data stream whose lenght is multiple of dlen to be processed
> + * each IO input
> + */
> + max_perburst = LPC_MAX_OPCNT & (~(dlen - 1));
> + cntleft = count * dlen;
> + do {
> + loopcnt = (cntleft >= max_perburst) ? max_perburst : cntleft;
> + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, newbuf,
> + loopcnt);
> + if (ret)
> + break;
> + newbuf += loopcnt;
> + cntleft -= loopcnt;
> + } while (cntleft);
> +
> + return ret;
> +}
> +
> +/**
> + * hisilpc_comm_outs - write/output the data in buffer to the I/O peripheral
> + * through LPC, it corresponds to outs(b,w,l)
> + * @devobj: pointer to the device information relevant to LPC controller.
> + * @ptaddr: the target I/O port address.
> + * @outbuf: a buffer where write/output data bytes are stored.
> + * @dlen: the data length required writing to the target I/O port .
> + * @count: how many data units whose length is dlen will be written.
> + *
> + */
> +static void hisilpc_comm_outs(void *devobj, unsigned long ptaddr,
> + const void *outbuf, size_t dlen, unsigned int count)
> +{
> + struct hisilpc_dev *lpcdev;
> + struct lpc_cycle_para iopara;
> + const unsigned char *newbuf;
> + unsigned int loopcnt, cntleft;
> + unsigned int max_perburst;
> + int ret = 0;
> +
> + if (!devobj || !outbuf || !count || !dlen ||
> + dlen > LPC_MAX_DULEN || (dlen & (dlen - 1)))
> + return;
> +
> + iopara.opflags = 0;
> + if (dlen > 1)
> + iopara.opflags |= FG_INCRADDR_LPC;
> + iopara.csize = dlen;
> +
> + lpcdev = (struct hisilpc_dev *)devobj;
> + newbuf = (unsigned char *)outbuf;
> + /*
> + * ensure data stream whose lenght is multiple of dlen to be processed
> + * each IO input
> + */
> + max_perburst = LPC_MAX_OPCNT & (~(dlen - 1));
> + cntleft = count * dlen;
> + do {
> + loopcnt = (cntleft >= max_perburst) ? max_perburst : cntleft;
> + ret = hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf,
> + loopcnt);
> + if (ret)
> + break;
> + newbuf += loopcnt;
> + cntleft -= loopcnt;
> + } while (cntleft);
> +}
> +
> +
> +/**
> + * hisilpc_probe - the probe callback function for hisi lpc device,
> + * will finish all the intialization.
> + * @pdev: the platform device corresponding to hisi lpc
> + *
> + * Returns 0 on success, non-zero on fail.
> + *
> + */
> +static int hisilpc_probe(struct platform_device *pdev)
> +{
> + struct resource *iores;
> + struct hisilpc_dev *lpcdev;
> + int ret;
> +
> + dev_info(&pdev->dev, "hslpc start probing...\n");
> +
> + lpcdev = devm_kzalloc(&pdev->dev,
> + sizeof(struct hisilpc_dev), GFP_KERNEL);
> + if (!lpcdev)
> + return -ENOMEM;
> +
> + spin_lock_init(&lpcdev->cycle_lock);
> + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + lpcdev->membase = devm_ioremap_resource(&pdev->dev, iores);
> + if (IS_ERR(lpcdev->membase)) {
> + dev_err(&pdev->dev, "No mem resource for memory mapping!\n");
> + return PTR_ERR(lpcdev->membase);
> + }
> + /*
> + * The first PCIBIOS_MIN_IO is reserved specific for indirectIO.
> + * It will separate indirectIO range from pci host bridge to
> + * avoid the possible PIO conflict.
> + * Set the indirectIO range directly here.
> + */
> + lpcdev->io_ops.start = 0;
> + lpcdev->io_ops.end = PCIBIOS_MIN_IO - 1;
> + lpcdev->io_ops.devpara = lpcdev;
> + lpcdev->io_ops.pfin = hisilpc_comm_in;
> + lpcdev->io_ops.pfout = hisilpc_comm_out;
> + lpcdev->io_ops.pfins = hisilpc_comm_ins;
> + lpcdev->io_ops.pfouts = hisilpc_comm_outs;
> +
> + platform_set_drvdata(pdev, lpcdev);
> +
> + arm64_set_extops(&lpcdev->io_ops);
> +
> + /*
> + * The children scanning is only for dts mode. For ACPI children,
> + * the corresponding devices had be created during acpi scanning.
> + */
> + ret = 0;
> + if (!has_acpi_companion(&pdev->dev))
> + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
> +
> + if (!ret)
> + dev_info(&pdev->dev, "hslpc end probing. range[0x%lx - %lx]\n",
> + arm64_extio_ops->start, arm64_extio_ops->end);
> + else
> + dev_info(&pdev->dev, "hslpc probing is fail(%d)\n", ret);
> +
> + return ret;
> +}
> +
> +static const struct of_device_id hisilpc_of_match[] = {
> + {
> + .compatible = "hisilicon,low-pin-count",
> + },
> + {},
> +};
> +
> +static const struct acpi_device_id hisilpc_acpi_match[] = {
> + {"HISI0191", },
> + {},
> +};
> +
> +static struct platform_driver hisilpc_driver = {
> + .driver = {
> + .name = "hisi_lpc",
> + .of_match_table = hisilpc_of_match,
> + .acpi_match_table = hisilpc_acpi_match,
> + },
> + .probe = hisilpc_probe,
> +};
> +
> +
> +builtin_platform_driver(hisilpc_driver);
> --
> 1.9.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html