Messages in this thread Patch in this message | | | From | Rongrong Zou <> | Subject | [PATCH] Hisilicon LPC driver | Date | Mon, 30 Nov 2015 21:07:17 +0800 |
| |
This is the Low Pin Count driver for Hisilicon Hi1610 SoC. It is used for LPC master accessing LPC slave device.
We only implement I/O read and I/O write here, and the 2 interfaces are exported for uart driver and ipmi_si driver.
Signed-off-by: Rongrong Zou <zourongrong@gmail.com> Signed-off-by: lijianhua <Jueying0518@gmail.com> --- .../bindings/misc/hisilicon,low-pin-count.txt | 11 + MAINTAINERS | 5 + drivers/misc/Kconfig | 7 + drivers/misc/Makefile | 1 + drivers/misc/hisi_lpc.c | 292 +++++++++++++++++++++ include/linux/hisi_lpc.h | 83 ++++++ 6 files changed, 399 insertions(+) create mode 100644 Documentation/devicetree/bindings/misc/hisilicon,low-pin-count.txt create mode 100644 drivers/misc/hisi_lpc.c create mode 100644 include/linux/hisi_lpc.h
diff --git a/Documentation/devicetree/bindings/misc/hisilicon,low-pin-count.txt b/Documentation/devicetree/bindings/misc/hisilicon,low-pin-count.txt new file mode 100644 index 0000000..05c1e19 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/hisilicon,low-pin-count.txt @@ -0,0 +1,11 @@ +Hisilicon Low Pin Count bus + +Required properties +- compatible: "hisilicon,low-pin-count" +- reg specifies low pin count address range + +Example: + lpc_0: lpc@a01b0000 { + compatible = "hisilicon,low-pin-count"; + ret = <0x0 0xa01b0000, 0x0, 0x10000>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 3f92804..352a4e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5037,6 +5037,11 @@ F: include/uapi/linux/if_hippi.h F: net/802/hippi.c F: drivers/net/hippi/ +HISILICON LOW PIN COUNT DRIVER +M: Rongrong Zou <zourongrong@huawei.com> +S: Maintained +F: drivers/misc/hisi-lpc/ + HOST AP DRIVER M: Jouni Malinen <j@w1.fi> L: hostap@shmoo.com (subscribers-only) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 22892c7..67f286f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -525,6 +525,13 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config HISI_LPC + bool "hisilicon Low Pin Count driver" + help + The hisilicon Low Pin Count controller can communicate with + device which have Low Pin Count slave controller. This driver + Support IO cycle only. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3..a8903f0dc 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_HISI_LPC) += hisi_lpc.o diff --git a/drivers/misc/hisi_lpc.c b/drivers/misc/hisi_lpc.c new file mode 100644 index 0000000..aae9ff3 --- /dev/null +++ b/drivers/misc/hisi_lpc.c @@ -0,0 +1,292 @@ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/hisi_lpc.h> + +#define LPC_REG_READ(reg, result) ((result) = readl(reg)) + +#define LPC_REG_WRITE(reg, data) writel((data), (reg)) + + +struct hs_lpc_dev *lpc_dev; + +int lpc_master_write(unsigned int slv_access_mode, unsigned int cycle_type, + unsigned int addr, unsigned char *buf, unsigned int len) +{ + unsigned int i; + unsigned int lpc_cmd_value; + unsigned int lpc_irq_st_value; + unsigned int lpc_op_state_value; + unsigned int retry = 0; + + /* para check */ + if (!buf) + return -EFAULT; + + if (slv_access_mode != HS_LPC_CMD_SAMEADDR_SING && + slv_access_mode != HS_LPC_CMD_SAMEADDR_INC) { + dev_err(lpc_dev->dev, "Slv access mode error\n"); + return -EINVAL; + } + + if ((cycle_type != HS_LPC_CMD_TYPE_IO) && + (cycle_type != HS_LPC_CMD_TYPE_MEM) && + (cycle_type != HS_LPC_CMD_TYPE_FWH)) { + dev_err(lpc_dev->dev, "Cycle type error\n"); + return -EINVAL; + } + + if (len == 0) { + dev_err(lpc_dev->dev, "Write length zero\n"); + return -EINVAL; + } + + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_IRQ_ST, HS_LPC_IRQ_CLEAR); + retry = 0; + while (0 == (LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_OP_STATUS, + lpc_op_state_value) & HS_LPC_STATUS_DILE)) { + udelay(1); + retry++; + if (retry >= 10000) { + dev_err(lpc_dev->dev, "lpc W, wait idle time out\n"); + return -ETIME; + } + } + + /* set lpc master write cycle type and slv access mode */ + lpc_cmd_value = HS_LPC_CMD_WRITE | cycle_type | slv_access_mode; + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_CMD, lpc_cmd_value); + + /* set lpc op len */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_OP_LEN, len); + + /* Set write data */ + for (i = 0; i < len; i++) + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_WDATA, buf[i]); + + /* set lpc addr config */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_ADDR, addr); + + /* set lpc start work */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_START, START_WORK); + + retry = 0; + while ((LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_IRQ_ST, + lpc_irq_st_value) & HS_LPC_IRQ_OCCURRED) == 0) { + udelay(1); + retry++; + if (retry >= 10000) { + dev_err(lpc_dev->dev, "lpc write, time out\n"); + return -ETIME; + } + } + + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_IRQ_ST, HS_LPC_IRQ_CLEAR); + + if ((LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_OP_STATUS, + lpc_op_state_value) & HS_LPC_OP_FINISHED) == HS_LPC_OP_FINISHED) { + return 0; + } + + dev_err(lpc_dev->dev, "Lpc master write failed, op_result : 0x%x\n", + lpc_op_state_value); + return -1; +} + +void lpc_io_write_byte(u8 value, unsigned long addr) +{ + unsigned long flags; + int ret; + + if (!lpc_dev) { + pr_err("device is not register\n!"); + return; + } + spin_lock_irqsave(&lpc_dev->lock, flags); + ret = lpc_master_write(HS_LPC_CMD_SAMEADDR_SING, HS_LPC_CMD_TYPE_IO, + addr, &value, 1); + spin_unlock_irqrestore(&lpc_dev->lock, flags); +} +EXPORT_SYMBOL(lpc_io_write_byte); + +int lpc_master_read(unsigned int slv_access_mode, unsigned int cycle_type, + unsigned int addr, unsigned char *buf, unsigned int len) +{ + unsigned int i; + unsigned int lpc_cmd_value; + unsigned int lpc_irq_st_value; + unsigned int lpc_rdata_value; + unsigned int lpc_op_state_value; + unsigned int retry = 0; + + /* para check */ + if (!buf) + return -EFAULT; + + if (slv_access_mode != HS_LPC_CMD_SAMEADDR_SING && + slv_access_mode != HS_LPC_CMD_SAMEADDR_INC) { + dev_err(lpc_dev->dev, "Slv access mode error\n"); + return -EINVAL; + } + + if (cycle_type != HS_LPC_CMD_TYPE_IO && + cycle_type != HS_LPC_CMD_TYPE_MEM && + cycle_type != HS_LPC_CMD_TYPE_FWH) { + dev_err(lpc_dev->dev, "Cycle type error\n"); + return -EINVAL; + } + + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_IRQ_ST, HS_LPC_IRQ_CLEAR); + + retry = 0; + while (0 == (LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_OP_STATUS, + lpc_op_state_value) & HS_LPC_STATUS_DILE)) { + udelay(1); + retry++; + if (retry >= 10000) { + dev_err(lpc_dev->dev, "lpc read,wait idl timeout\n"); + return -ETIME; + } + } + + /* set lpc master read cycle type and slv access mode */ + lpc_cmd_value = HS_LPC_CMD_READ | cycle_type | slv_access_mode; + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_CMD, lpc_cmd_value); + + /* set lpc op len */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_OP_LEN, len); + + /* set lpc addr config */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_ADDR, addr); + + /* set lpc start work */ + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_START, START_WORK); + + while ((LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_IRQ_ST, + lpc_irq_st_value) & HS_LPC_IRQ_OCCURRED) == 0) { + udelay(1); + retry++; + if (retry >= 10000) { + dev_err(lpc_dev->dev, "lpc read, time out\n"); + return -ETIME; + } + } + + LPC_REG_WRITE(lpc_dev->regs + HS_LPC_REG_IRQ_ST, HS_LPC_IRQ_CLEAR); + + /* Get read data */ + if ((LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_OP_STATUS, + lpc_op_state_value) & HS_LPC_OP_FINISHED) == HS_LPC_OP_FINISHED) { + for (i = 0; i < len; i++) { + LPC_REG_READ(lpc_dev->regs + HS_LPC_REG_RDATA, + lpc_rdata_value); + buf[i] = lpc_rdata_value; + } + return 0; + } + + dev_err(lpc_dev->dev, "Lpc master read failed, op_result : 0x%x\n", + lpc_op_state_value); + + return -1; +} + +u8 lpc_io_read_byte(unsigned long addr) +{ + unsigned char value; + unsigned long flags; + + if (!lpc_dev) { + pr_err("device is not register!\n"); + return 0; + } + + spin_lock_irqsave(&lpc_dev->lock, flags); + lpc_master_read(HS_LPC_CMD_SAMEADDR_SING, + HS_LPC_CMD_TYPE_IO, addr, &value, 1); + spin_unlock_irqrestore(&lpc_dev->lock, flags); + return value; +} +EXPORT_SYMBOL(lpc_io_read_byte); + +static int hs_lpc_probe(struct platform_device *pdev) +{ + struct resource *regs = NULL; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "Device OF-Node is NULL\n"); + return -EFAULT; + } + + lpc_dev = devm_kzalloc(&pdev->dev, + sizeof(struct hs_lpc_dev), GFP_KERNEL); + if (!lpc_dev) + return -ENOMEM; + + spin_lock_init(&lpc_dev->lock); + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lpc_dev->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(lpc_dev->regs)) + return PTR_ERR(lpc_dev->regs); + + dev_info(&pdev->dev, "hisi low pin count initialized successfully\n"); + + lpc_dev->dev = &pdev->dev; + platform_set_drvdata(pdev, lpc_dev); + + return 0; +} + +static int hs_lpc_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id hs_lpc_pltfm_match[] = { + { + .compatible = "hisilicon,low-pin-count", + }, + {}, +}; + +static struct platform_driver hs_lpc_driver = { + .driver = { + .name = "hs-lpc", + .owner = THIS_MODULE, + .of_match_table = hs_lpc_pltfm_match, + }, + .probe = hs_lpc_probe, + .remove = hs_lpc_remove, +}; + +static int __init hs_lpc_init_driver(void) +{ + return platform_driver_register(&hs_lpc_driver); +} + +static void __exit hs_lpc_init_exit(void) +{ + platform_driver_unregister(&hs_lpc_driver); +} + +arch_initcall(hs_lpc_init_driver); +module_exit(hs_lpc_init_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Huawei Tech. Co., Ltd."); +MODULE_DESCRIPTION("LPC driver for linux"); +MODULE_VERSION("v1.0"); diff --git a/include/linux/hisi_lpc.h b/include/linux/hisi_lpc.h new file mode 100644 index 0000000..4cf93ee --- /dev/null +++ b/include/linux/hisi_lpc.h @@ -0,0 +1,83 @@ +#ifndef __HISI_LPC_H__ +#define __HISI_LPC_H__ + +#define HS_LPC_REG_START (0x00) +#define HS_LPC_REG_OP_STATUS (0x04) +#define HS_LPC_REG_IRQ_ST (0x08) +#define HS_LPC_REG_OP_LEN (0x10) +#define HS_LPC_REG_CMD (0x14) +#define HS_LPC_REG_FWH_ID_MSIZE (0x18) +#define HS_LPC_REG_ADDR (0x20) +#define HS_LPC_REG_WDATA (0x24) +#define HS_LPC_REG_RDATA (0x28) +#define HS_LPC_REG_LONG_CNT (0x30) +#define HS_LPC_REG_TX_FIFO_ST (0x50) +#define HS_LPC_REG_RX_FIFO_ST (0x54) +#define HS_LPC_REG_TIME_OUT (0x58) +#define HS_LPC_REG_STRQ_CTRL0 (0x80) +#define HS_LPC_REG_STRQ_CTRL1 (0x84) +#define HS_LPC_REG_STRQ_INT (0x90) +#define HS_LPC_REG_STRQ_INT_MASK (0x94) +#define HS_LPC_REG_STRQ_STAT (0xa0) + +#define HS_LPC_CMD_SAMEADDR_SING (0x00000008) +#define HS_LPC_CMD_SAMEADDR_INC (0x00000000) +#define HS_LPC_CMD_TYPE_IO (0x00000000) +#define HS_LPC_CMD_TYPE_MEM (0x00000002) +#define HS_LPC_CMD_TYPE_FWH (0x00000004) +#define HS_LPC_CMD_WRITE (0x00000001) +#define HS_LPC_CMD_READ (0x00000000) + +#define HS_LPC_IRQ_CLEAR (0x02) +#define HS_LPC_IRQ_OCCURRED (0x02) +#define HS_LPC_STATUS_DILE (0x01) +#define HS_LPC_OP_FINISHED (0x02) +#define START_WORK (0x01) + +#define REG_OFFSET_IOMG033 0X84 +#define REG_OFFSET_IOMG035 0x8C +#define REG_OFFSET_IOMG036 0x90 +#define REG_OFFSET_IOMG050 0xC8 +#define REG_OFFSET_IOMG049 0xC4 +#define REG_OFFSET_IOMG048 0xC0 +#define REG_OFFSET_IOMG047 0xBC +#define REG_OFFSET_IOMG046 0xB8 +#define REG_OFFSET_IOMG045 0xB4 + +#define REG_OFFSET_LPC_RESET 0xad8 +#define REG_OFFSET_LPC_BUS_RESET 0xae0 +#define REG_OFFSET_LPC_CLK_DIS 0x3a4 +#define REG_OFFSET_LPC_BUS_CLK_DIS 0x3ac +#define REG_OFFSET_LPC_DE_RESET 0xadc +#define REG_OFFSET_LPC_BUS_DE_RESET 0xae4 +#define REG_OFFSET_LPC_CLK_EN 0x3a0 +#define REG_OFFSET_LPC_BUS_CLK_EN 0x3a8 + +#define LPC_FRAME_LEN (0x10) + +struct hs_lpc_dev { + spinlock_t lock; + void __iomem *regs; + struct device *dev; +}; + +#define LPC_CURR_STATUS_IDLE 0 +#define LPC_CURR_STATUS_START 1 +#define LPC_CURR_STATUS_TYPE_DIR 2 +#define LPC_CURR_STATUS_ADDR 3 +#define LPC_CURR_STATUS_MSIZE 4 +#define LPC_CURR_STATUS_WDATA 5 +#define LPC_CURR_STATUS_TARHOST 6 +#define LPC_CURR_STATUS_SYNC 7 +#define LPC_CURR_STATUS_RDATA 8 +#define LPC_CURR_STATUS_TARSLAVE 9 +#define LPC_CURR_STATUS_ABORT 10 + +#define ALG_HCCS_SUBCTRL_BASE_ADDR (0xD0000000) +#define REG_OFFSET_RESET_REQ 0x0AD0 +#define REG_OFFSET_RESET_DREQ 0x0AD4 + +extern u8 lpc_io_read_byte(unsigned long addr); +extern void lpc_io_write_byte(u8 value, unsigned long addr); +#endif + -- 1.9.1
| |