Message ID | 1516725385-24535-7-git-send-email-john.garry@huawei.com |
---|---|
State | New |
Headers | show |
Series | LPC: legacy ISA I/O support | expand |
On Wed, Jan 24, 2018 at 12:36:22AM +0800, John Garry wrote: > From: Zhichang Yuan <yuanzhichang@hisilicon.com> > > The low-pin-count(LPC) interface of Hip06/Hip07 accesses the peripherals in > I/O port addresses. This patch implements the LPC host controller driver > which perform the I/O operations on the underlying hardware. > We don't want to touch those existing peripherals' driver, such as ipmi-bt. > So this driver applies the indirect-IO introduced in the previous patch > after registering an indirect-IO node to the indirect-IO devices list which > will be searched in the I/O accessors to retrieve the host-local I/O port. > > The driver config is set as a bool instead of a trisate. The reason > here is that, by the very nature of the driver providing a logical > PIO range, it does not make sense to have this driver as a loadable > module. Another more specific reason is that the Huawei D03 board > which includes hip06 SoC requires the LPC bus for UART console, so > should be built in. > > Signed-off-by: Zou Rongrong <zourongrong@huawei.com> > Signed-off-by: Zhichang Yuan <yuanzhichang@hisilicon.com> > Signed-off-by: Gabriele Paoloni <gabriele.paoloni@huawei.com> > Acked-by: Rob Herring <robh@kernel.org> #dts part > --- > .../arm/hisilicon/hisilicon-low-pin-count.txt | 33 ++ > drivers/bus/Kconfig | 8 + > drivers/bus/Makefile | 1 + > drivers/bus/hisi_lpc.c | 526 +++++++++++++++++++++ > 4 files changed, 568 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..213181f > --- /dev/null > +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt > @@ -0,0 +1,33 @@ > +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>; > + }; > +}; > diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig > index 57e011d..91debe4 100644 > --- a/drivers/bus/Kconfig > +++ b/drivers/bus/Kconfig > @@ -65,6 +65,14 @@ config BRCMSTB_GISB_ARB > arbiter. This driver provides timeout and target abort error handling > 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 needed for some legacy ISA devices attached to Low-Pin-Count > + on Hisilicon hip06/7 SoC. > + > config IMX_WEIM > bool "Freescale EIM DRIVER" > depends on ARCH_MXC > diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile > index 9bcd0bf..abc7a42 100644 > --- a/drivers/bus/Makefile > +++ b/drivers/bus/Makefile > @@ -8,6 +8,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..4ce089a > --- /dev/null > +++ b/drivers/bus/hisi_lpc.c > @@ -0,0 +1,526 @@ > +/* > + * Copyright (C) 2017 Hisilicon Limited, All Rights Reserved. > + * Author: Zhichang Yuan <yuanzhichang@hisilicon.com> > + * Author: Zou Rongrong <zourongrong@huawei.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 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/logic_pio.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 LPC_MIN_BUS_RANGE 0x0 > + > +/* > + * The default maximal IO size for Hip06/Hip07 LPC bus. > + * Defining the I/O range size as 0x4000 here should be sufficient for > + * all peripherals under the bus. > + */ > +#define LPC_BUS_IO_SIZE 0x4000 > + > +/* > + * Setting this bit means each IO operation will target to a > + * different port address: > + * 0 means repeatedly IO operations will stick 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 logic_pio_hwaddr *io_host; > +}; > + > +/* The maximum continuous cycles per burst */ > +#define LPC_MAX_BURST 16 > +/* The IO cycle counts supported is four per operation at maximum */ > +#define LPC_MAX_DULEN 4 > +#if LPC_MAX_DULEN > LPC_MAX_BURST > +#error "LPC.. MAX_DULEN must be not bigger than MAX_OPCNT!" > +#endif > + > +#if LPC_MAX_BURST % LPC_MAX_DULEN > +#error "LPC.. LPC_MAX_BURST must be multiple of LPC_MAX_DULEN!" > +#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 LPC_START_WORK 0x01 > + > +/* 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 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 peripheral. > + * @lpcdev: pointer to hisi lpc device > + * @para: some parameters 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 *lpcdev, struct lpc_cycle_para *para, > + unsigned long ptaddr, unsigned char *buf, > + unsigned long opcnt) > +{ > + unsigned long cnt_per_trans; > + unsigned int cmd_word; > + unsigned int waitcnt; > + int ret; > + > + if (!buf || !opcnt || !para || !para->csize || !lpcdev) > + 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; > + } > + > + ret = 0; > + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize; > + for (; opcnt && !ret; cnt_per_trans = para->csize) { > + unsigned long flags; > + > + /* whole operation must be atomic */ > + spin_lock_irqsave(&lpcdev->cycle_lock, flags); > + > + writel_relaxed(cnt_per_trans, lpcdev->membase + LPC_REG_OP_LEN); > + > + writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD); > + > + writel_relaxed(ptaddr, lpcdev->membase + LPC_REG_ADDR); > + > + writel(LPC_START_WORK, lpcdev->membase + LPC_REG_START); > + > + /* whether the operation is finished */ > + ret = wait_lpc_idle(lpcdev->membase, waitcnt); > + if (!ret) { > + opcnt -= cnt_per_trans; Do we need to check for an underflow here? > + for (cnt_per_trans--; cnt_per_trans--; buf++) > + *buf = readb_relaxed(lpcdev->membase + > + LPC_REG_RDATA); > + *buf = readb(lpcdev->membase + LPC_REG_RDATA); > + } > + > + spin_unlock_irqrestore(&lpcdev->cycle_lock, flags); > + } > + > + return ret; > +} > + > +/* > + * hisilpc_target_out - trigger a series of lpc cycles to write required > + * data to target peripheral. > + * @lpcdev: pointer to hisi lpc device > + * @para: some parameters 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. s/read/written/ ? > + * Returns 0 on success, non-zero on fail. > + * > + */ > +static int > +hisilpc_target_out(struct hisilpc_dev *lpcdev, struct lpc_cycle_para *para, > + unsigned long ptaddr, const unsigned char *buf, > + unsigned long opcnt) > +{ > + unsigned long cnt_per_trans; > + unsigned int cmd_word; > + unsigned int waitcnt; > + int ret; > + > + if (!buf || !opcnt || !para || !lpcdev) > + 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; > + } > + > + ret = 0; > + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize; > + for (; opcnt && !ret; cnt_per_trans = para->csize) { > + unsigned long flags; > + > + spin_lock_irqsave(&lpcdev->cycle_lock, flags); > + > + writel_relaxed(cnt_per_trans, lpcdev->membase + LPC_REG_OP_LEN); > + writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD); > + writel_relaxed(ptaddr, lpcdev->membase + LPC_REG_ADDR); > + > + opcnt -= cnt_per_trans; Underflow check needed? -dann > + for (; cnt_per_trans--; buf++) > + writeb_relaxed(*buf, lpcdev->membase + LPC_REG_WDATA); > + > + writel(LPC_START_WORK, lpcdev->membase + LPC_REG_START); > + > + /* whether the operation is finished */ > + ret = wait_lpc_idle(lpcdev->membase, waitcnt); > + > + spin_unlock_irqrestore(&lpcdev->cycle_lock, flags); > + } > + > + return ret; > +} > + > +static inline unsigned long > +hisi_lpc_pio_to_addr(struct hisilpc_dev *lpcdev, unsigned long pio) > +{ > + return pio - lpcdev->io_host->io_start + > + lpcdev->io_host->hw_start; > +} > + > + > +/** > + * hisilpc_comm_in - read/input the data from the I/O peripheral > + * through LPC. > + * @devobj: pointer to the device information relevant to LPC controller. > + * @pio: 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 u32 hisilpc_comm_in(void *devobj, unsigned long pio, size_t dlen) > +{ > + int ret = 0; > + unsigned char rd_data = 0; > + unsigned long ptaddr; > + struct lpc_cycle_para iopara; > + struct hisilpc_dev *lpcdev = devobj; > + > + if (!lpcdev || !dlen || dlen > LPC_MAX_DULEN) > + return -1; > + > + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); > + > + iopara.opflags = FG_INCRADDR_LPC; > + iopara.csize = dlen; > + > + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, &rd_data, dlen); > + if (ret) > + return -1; > + > + return le32_to_cpu((u32)rd_data); > +} > + > +/** > + * hisilpc_comm_out - output the data whose maximum length is four bytes > + to the I/O peripheral through the LPC host. > + * @devobj: pointer to the device information relevant to LPC controller. > + * @pio: the target I/O port address. > + * @outval: a value to be outputted from caller, maximum is four bytes. > + * @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 pio, > + u32 outval, size_t dlen) > +{ > + unsigned long ptaddr; > + struct hisilpc_dev *lpcdev = devobj; > + struct lpc_cycle_para iopara; > + const unsigned char *newbuf; > + > + if (!lpcdev || !dlen || dlen > LPC_MAX_DULEN) > + return; > + > + outval = cpu_to_le32(outval); > + > + newbuf = (const unsigned char *)&outval; > + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); > + > + 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. > + * @pio: 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. > + * > + * when succeed, the data read back is stored in buffer pointed by inbuf. > + * Returns 0 on success, -errno otherwise > + * > + */ > +static u32 > +hisilpc_comm_ins(void *devobj, unsigned long pio, void *inbuf, > + size_t dlen, unsigned int count) > +{ > + struct hisilpc_dev *lpcdev = devobj; > + struct lpc_cycle_para iopara; > + unsigned char *newbuf; > + unsigned int loopcnt, cntleft; > + unsigned long ptaddr; > + > + if (!lpcdev || !inbuf || !count || !dlen || dlen > LPC_MAX_DULEN || > + count % dlen) > + return -EINVAL; > + > + iopara.opflags = 0; > + if (dlen > 1) > + iopara.opflags |= FG_INCRADDR_LPC; > + iopara.csize = dlen; > + > + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); > + newbuf = inbuf; > + /* > + * ensure data stream whose length is multiple of dlen to be processed > + * each IO input > + */ > + cntleft = count * dlen; > + do { > + int ret; > + > + loopcnt = min_t(unsigned int, LPC_MAX_BURST, cntleft); > + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, > + newbuf, loopcnt); > + if (ret) > + return ret; > + newbuf += loopcnt; > + cntleft -= loopcnt; > + } while (cntleft); > + > + return 0; > +} > + > +/* > + * 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. > + * @pio: 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 pio, const void *outbuf, > + size_t dlen, unsigned int count) > +{ > + struct hisilpc_dev *lpcdev = devobj; > + struct lpc_cycle_para iopara; > + const unsigned char *newbuf; > + unsigned int loopcnt, cntleft; > + unsigned long ptaddr; > + > + if (!lpcdev || !outbuf || !count || !dlen || dlen > LPC_MAX_DULEN || > + count % dlen) > + return; > + > + iopara.opflags = 0; > + if (dlen > 1) > + iopara.opflags |= FG_INCRADDR_LPC; > + iopara.csize = dlen; > + > + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); > + newbuf = outbuf; > + /* > + * ensure data stream whose length is multiple of dlen to be processed > + * each IO input > + */ > + cntleft = count * dlen; > + do { > + loopcnt = min_t(unsigned int, LPC_MAX_BURST, cntleft); > + if (hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf, > + loopcnt)) > + break; > + newbuf += loopcnt; > + cntleft -= loopcnt; > + } while (cntleft); > +} > + > +static struct hostio_ops hisi_lpc_ops = { > + .pfin = hisilpc_comm_in, > + .pfout = hisilpc_comm_out, > + .pfins = hisilpc_comm_ins, > + .pfouts = hisilpc_comm_outs, > +}; > + > +/** > + * hisilpc_probe - the probe callback function for hisi lpc device, > + * will finish all the initialization. > + * @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 device *dev = &pdev->dev; > + struct resource *res; > + struct hisilpc_dev *lpcdev; > + int ret = 0; > + > + lpcdev = devm_kzalloc(dev, sizeof(struct hisilpc_dev), GFP_KERNEL); > + if (!lpcdev) > + return -ENOMEM; > + > + spin_lock_init(&lpcdev->cycle_lock); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) > + return -ENODEV; > + > + lpcdev->membase = devm_ioremap_resource(dev, res); > + if (IS_ERR(lpcdev->membase)) { > + dev_err(dev, "remap failed\n"); > + return PTR_ERR(lpcdev->membase); > + } > + > + /* register the LPC host PIO resources */ > + { > + struct logic_pio_hwaddr *range; > + > + range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL); > + if (!range) > + return -ENOMEM; > + range->fwnode = dev->fwnode; > + range->flags = PIO_INDIRECT; > + range->size = LPC_BUS_IO_SIZE; > + range->hw_start = LPC_MIN_BUS_RANGE; > + > + ret = logic_pio_register_range(range); > + if (ret) { > + kfree(range); > + dev_err(dev, "register IO range failed (%d)!\n", ret); > + return -ret; > + } > + lpcdev->io_host = range; > + > + /* > + * It is time to start the children scanning: > + * For ACPI children, the corresponding devices had been > + * created during the ACPI enumeration. > + * The OF scanning must be performed after initialization of > + * 'lpcdev' to avoid some children which complete the scanning > + * trigger the MMIO accesses which will probably cause a panic. > + */ > + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); > + if (ret) { > + /* > + * When LPC probing is not completely successful, set > + * 'devpara' as NULL. This will make all the LPC I/O > + * return failure directly without any hardware > + * operations. It will prevent some peripherals which > + * had not finished the initialization to manipulate > + * I/O for safety. > + */ > + lpcdev->io_host->devpara = NULL; > + dev_err(dev, "populate children failed(%d)\n", ret); > + return ret; > + } > + } > + lpcdev->io_host->devpara = lpcdev; > + lpcdev->io_host->ops = &hisi_lpc_ops; > + > + dev_info(dev, "registered range[%pa - sz:%pa]\n", > + &lpcdev->io_host->io_start, > + &lpcdev->io_host->size); > + > + return ret; > +} > + > +static const struct of_device_id hisilpc_of_match[] = { > + { .compatible = "hisilicon,hip06-lpc", }, > + { .compatible = "hisilicon,hip07-lpc", }, > + {}, > +}; > + > +static struct platform_driver hisilpc_driver = { > + .driver = { > + .name = "hisi_lpc", > + .of_match_table = hisilpc_of_match, > + }, > + .probe = hisilpc_probe, > +}; > + > +builtin_platform_driver(hisilpc_driver);
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..213181f --- /dev/null +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon-low-pin-count.txt @@ -0,0 +1,33 @@ +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>; + }; +}; diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 57e011d..91debe4 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -65,6 +65,14 @@ config BRCMSTB_GISB_ARB arbiter. This driver provides timeout and target abort error handling 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 needed for some legacy ISA devices attached to Low-Pin-Count + on Hisilicon hip06/7 SoC. + config IMX_WEIM bool "Freescale EIM DRIVER" depends on ARCH_MXC diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 9bcd0bf..abc7a42 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -8,6 +8,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..4ce089a --- /dev/null +++ b/drivers/bus/hisi_lpc.c @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2017 Hisilicon Limited, All Rights Reserved. + * Author: Zhichang Yuan <yuanzhichang@hisilicon.com> + * Author: Zou Rongrong <zourongrong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 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/logic_pio.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 LPC_MIN_BUS_RANGE 0x0 + +/* + * The default maximal IO size for Hip06/Hip07 LPC bus. + * Defining the I/O range size as 0x4000 here should be sufficient for + * all peripherals under the bus. + */ +#define LPC_BUS_IO_SIZE 0x4000 + +/* + * Setting this bit means each IO operation will target to a + * different port address: + * 0 means repeatedly IO operations will stick 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 logic_pio_hwaddr *io_host; +}; + +/* The maximum continuous cycles per burst */ +#define LPC_MAX_BURST 16 +/* The IO cycle counts supported is four per operation at maximum */ +#define LPC_MAX_DULEN 4 +#if LPC_MAX_DULEN > LPC_MAX_BURST +#error "LPC.. MAX_DULEN must be not bigger than MAX_OPCNT!" +#endif + +#if LPC_MAX_BURST % LPC_MAX_DULEN +#error "LPC.. LPC_MAX_BURST must be multiple of LPC_MAX_DULEN!" +#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 LPC_START_WORK 0x01 + +/* 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 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 peripheral. + * @lpcdev: pointer to hisi lpc device + * @para: some parameters 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 *lpcdev, struct lpc_cycle_para *para, + unsigned long ptaddr, unsigned char *buf, + unsigned long opcnt) +{ + unsigned long cnt_per_trans; + unsigned int cmd_word; + unsigned int waitcnt; + int ret; + + if (!buf || !opcnt || !para || !para->csize || !lpcdev) + 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; + } + + ret = 0; + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize; + for (; opcnt && !ret; cnt_per_trans = para->csize) { + unsigned long flags; + + /* whole operation must be atomic */ + spin_lock_irqsave(&lpcdev->cycle_lock, flags); + + writel_relaxed(cnt_per_trans, lpcdev->membase + LPC_REG_OP_LEN); + + writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD); + + writel_relaxed(ptaddr, lpcdev->membase + LPC_REG_ADDR); + + writel(LPC_START_WORK, lpcdev->membase + LPC_REG_START); + + /* whether the operation is finished */ + ret = wait_lpc_idle(lpcdev->membase, waitcnt); + if (!ret) { + opcnt -= cnt_per_trans; + for (cnt_per_trans--; cnt_per_trans--; buf++) + *buf = readb_relaxed(lpcdev->membase + + LPC_REG_RDATA); + *buf = readb(lpcdev->membase + LPC_REG_RDATA); + } + + spin_unlock_irqrestore(&lpcdev->cycle_lock, flags); + } + + return ret; +} + +/* + * hisilpc_target_out - trigger a series of lpc cycles to write required + * data to target peripheral. + * @lpcdev: pointer to hisi lpc device + * @para: some parameters 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 *lpcdev, struct lpc_cycle_para *para, + unsigned long ptaddr, const unsigned char *buf, + unsigned long opcnt) +{ + unsigned long cnt_per_trans; + unsigned int cmd_word; + unsigned int waitcnt; + int ret; + + if (!buf || !opcnt || !para || !lpcdev) + 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; + } + + ret = 0; + cnt_per_trans = (para->csize == 1) ? opcnt : para->csize; + for (; opcnt && !ret; cnt_per_trans = para->csize) { + unsigned long flags; + + spin_lock_irqsave(&lpcdev->cycle_lock, flags); + + writel_relaxed(cnt_per_trans, lpcdev->membase + LPC_REG_OP_LEN); + writel_relaxed(cmd_word, lpcdev->membase + LPC_REG_CMD); + writel_relaxed(ptaddr, lpcdev->membase + LPC_REG_ADDR); + + opcnt -= cnt_per_trans; + for (; cnt_per_trans--; buf++) + writeb_relaxed(*buf, lpcdev->membase + LPC_REG_WDATA); + + writel(LPC_START_WORK, lpcdev->membase + LPC_REG_START); + + /* whether the operation is finished */ + ret = wait_lpc_idle(lpcdev->membase, waitcnt); + + spin_unlock_irqrestore(&lpcdev->cycle_lock, flags); + } + + return ret; +} + +static inline unsigned long +hisi_lpc_pio_to_addr(struct hisilpc_dev *lpcdev, unsigned long pio) +{ + return pio - lpcdev->io_host->io_start + + lpcdev->io_host->hw_start; +} + + +/** + * hisilpc_comm_in - read/input the data from the I/O peripheral + * through LPC. + * @devobj: pointer to the device information relevant to LPC controller. + * @pio: 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 u32 hisilpc_comm_in(void *devobj, unsigned long pio, size_t dlen) +{ + int ret = 0; + unsigned char rd_data = 0; + unsigned long ptaddr; + struct lpc_cycle_para iopara; + struct hisilpc_dev *lpcdev = devobj; + + if (!lpcdev || !dlen || dlen > LPC_MAX_DULEN) + return -1; + + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); + + iopara.opflags = FG_INCRADDR_LPC; + iopara.csize = dlen; + + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, &rd_data, dlen); + if (ret) + return -1; + + return le32_to_cpu((u32)rd_data); +} + +/** + * hisilpc_comm_out - output the data whose maximum length is four bytes + to the I/O peripheral through the LPC host. + * @devobj: pointer to the device information relevant to LPC controller. + * @pio: the target I/O port address. + * @outval: a value to be outputted from caller, maximum is four bytes. + * @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 pio, + u32 outval, size_t dlen) +{ + unsigned long ptaddr; + struct hisilpc_dev *lpcdev = devobj; + struct lpc_cycle_para iopara; + const unsigned char *newbuf; + + if (!lpcdev || !dlen || dlen > LPC_MAX_DULEN) + return; + + outval = cpu_to_le32(outval); + + newbuf = (const unsigned char *)&outval; + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); + + 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. + * @pio: 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. + * + * when succeed, the data read back is stored in buffer pointed by inbuf. + * Returns 0 on success, -errno otherwise + * + */ +static u32 +hisilpc_comm_ins(void *devobj, unsigned long pio, void *inbuf, + size_t dlen, unsigned int count) +{ + struct hisilpc_dev *lpcdev = devobj; + struct lpc_cycle_para iopara; + unsigned char *newbuf; + unsigned int loopcnt, cntleft; + unsigned long ptaddr; + + if (!lpcdev || !inbuf || !count || !dlen || dlen > LPC_MAX_DULEN || + count % dlen) + return -EINVAL; + + iopara.opflags = 0; + if (dlen > 1) + iopara.opflags |= FG_INCRADDR_LPC; + iopara.csize = dlen; + + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); + newbuf = inbuf; + /* + * ensure data stream whose length is multiple of dlen to be processed + * each IO input + */ + cntleft = count * dlen; + do { + int ret; + + loopcnt = min_t(unsigned int, LPC_MAX_BURST, cntleft); + ret = hisilpc_target_in(lpcdev, &iopara, ptaddr, + newbuf, loopcnt); + if (ret) + return ret; + newbuf += loopcnt; + cntleft -= loopcnt; + } while (cntleft); + + return 0; +} + +/* + * 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. + * @pio: 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 pio, const void *outbuf, + size_t dlen, unsigned int count) +{ + struct hisilpc_dev *lpcdev = devobj; + struct lpc_cycle_para iopara; + const unsigned char *newbuf; + unsigned int loopcnt, cntleft; + unsigned long ptaddr; + + if (!lpcdev || !outbuf || !count || !dlen || dlen > LPC_MAX_DULEN || + count % dlen) + return; + + iopara.opflags = 0; + if (dlen > 1) + iopara.opflags |= FG_INCRADDR_LPC; + iopara.csize = dlen; + + ptaddr = hisi_lpc_pio_to_addr(lpcdev, pio); + newbuf = outbuf; + /* + * ensure data stream whose length is multiple of dlen to be processed + * each IO input + */ + cntleft = count * dlen; + do { + loopcnt = min_t(unsigned int, LPC_MAX_BURST, cntleft); + if (hisilpc_target_out(lpcdev, &iopara, ptaddr, newbuf, + loopcnt)) + break; + newbuf += loopcnt; + cntleft -= loopcnt; + } while (cntleft); +} + +static struct hostio_ops hisi_lpc_ops = { + .pfin = hisilpc_comm_in, + .pfout = hisilpc_comm_out, + .pfins = hisilpc_comm_ins, + .pfouts = hisilpc_comm_outs, +}; + +/** + * hisilpc_probe - the probe callback function for hisi lpc device, + * will finish all the initialization. + * @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 device *dev = &pdev->dev; + struct resource *res; + struct hisilpc_dev *lpcdev; + int ret = 0; + + lpcdev = devm_kzalloc(dev, sizeof(struct hisilpc_dev), GFP_KERNEL); + if (!lpcdev) + return -ENOMEM; + + spin_lock_init(&lpcdev->cycle_lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + lpcdev->membase = devm_ioremap_resource(dev, res); + if (IS_ERR(lpcdev->membase)) { + dev_err(dev, "remap failed\n"); + return PTR_ERR(lpcdev->membase); + } + + /* register the LPC host PIO resources */ + { + struct logic_pio_hwaddr *range; + + range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL); + if (!range) + return -ENOMEM; + range->fwnode = dev->fwnode; + range->flags = PIO_INDIRECT; + range->size = LPC_BUS_IO_SIZE; + range->hw_start = LPC_MIN_BUS_RANGE; + + ret = logic_pio_register_range(range); + if (ret) { + kfree(range); + dev_err(dev, "register IO range failed (%d)!\n", ret); + return -ret; + } + lpcdev->io_host = range; + + /* + * It is time to start the children scanning: + * For ACPI children, the corresponding devices had been + * created during the ACPI enumeration. + * The OF scanning must be performed after initialization of + * 'lpcdev' to avoid some children which complete the scanning + * trigger the MMIO accesses which will probably cause a panic. + */ + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) { + /* + * When LPC probing is not completely successful, set + * 'devpara' as NULL. This will make all the LPC I/O + * return failure directly without any hardware + * operations. It will prevent some peripherals which + * had not finished the initialization to manipulate + * I/O for safety. + */ + lpcdev->io_host->devpara = NULL; + dev_err(dev, "populate children failed(%d)\n", ret); + return ret; + } + } + lpcdev->io_host->devpara = lpcdev; + lpcdev->io_host->ops = &hisi_lpc_ops; + + dev_info(dev, "registered range[%pa - sz:%pa]\n", + &lpcdev->io_host->io_start, + &lpcdev->io_host->size); + + return ret; +} + +static const struct of_device_id hisilpc_of_match[] = { + { .compatible = "hisilicon,hip06-lpc", }, + { .compatible = "hisilicon,hip07-lpc", }, + {}, +}; + +static struct platform_driver hisilpc_driver = { + .driver = { + .name = "hisi_lpc", + .of_match_table = hisilpc_of_match, + }, + .probe = hisilpc_probe, +}; + +builtin_platform_driver(hisilpc_driver);