]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
HID: intel-ish-hid: ipc layer
authorSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Sun, 7 Aug 2016 09:25:35 +0000 (02:25 -0700)
committerJiri Kosina <jkosina@suse.cz>
Wed, 17 Aug 2016 09:13:07 +0000 (11:13 +0200)
This layer is responsible for
- Enumerating over PCI bus
- Inform FW about host readiness
- Provide HW interface to transport layer for control and messages
- Interrupt handling and routing

Original-author: Daniel Drubin <daniel.drubin@intel.com>
Reviewed-and-tested-by: Ooi, Joyce <joyce.ooi@intel.com>
Tested-by: Grant Likely <grant.likely@secretlab.ca>
Tested-by: Rann Bar-On <rb6@duke.edu>
Tested-by: Atri Bhattacharya <badshah400@aim.com>
Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/intel-ish-hid/Makefile
drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/hw-ish.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/ipc.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/pci-ish.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/utils.h [new file with mode: 0644]
include/trace/events/intel_ish.h [new file with mode: 0644]

index 7b32d49624ae5201d22288e6f1fbfff135a7ff25..ab626d8f7bb86afe7e681f37bac01780599df8d7 100644 (file)
@@ -10,3 +10,9 @@ intel-ishtp-objs += ishtp/client.o
 intel-ishtp-objs += ishtp/bus.o
 intel-ishtp-objs += ishtp/dma-if.o
 intel-ishtp-objs += ishtp/client-buffers.o
+
+obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
+intel-ish-ipc-objs := ipc/ipc.o
+intel-ish-ipc-objs += ipc/pci-ish.o
+
+ccflags-y += -Idrivers/hid/intel-ish-hid/ishtp
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h b/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h
new file mode 100644 (file)
index 0000000..ab68afc
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * ISH registers definitions
+ *
+ * Copyright (c) 2012-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#ifndef _ISHTP_ISH_REGS_H_
+#define _ISHTP_ISH_REGS_H_
+
+
+/*** IPC PCI Offsets and sizes ***/
+/* ISH IPC Base Address */
+#define IPC_REG_BASE           0x0000
+/* Peripheral Interrupt Status Register */
+#define IPC_REG_PISR_CHV_AB      (IPC_REG_BASE + 0x00)
+/* Peripheral Interrupt Mask Register */
+#define IPC_REG_PIMR_CHV_AB      (IPC_REG_BASE + 0x04)
+/*BXT, CHV_K0*/
+/*Peripheral Interrupt Status Register */
+#define IPC_REG_PISR_BXT        (IPC_REG_BASE + 0x0C)
+/*Peripheral Interrupt Mask Register */
+#define IPC_REG_PIMR_BXT        (IPC_REG_BASE + 0x08)
+/***********************************/
+/* ISH Host Firmware status Register */
+#define IPC_REG_ISH_HOST_FWSTS (IPC_REG_BASE + 0x34)
+/* Host Communication Register */
+#define IPC_REG_HOST_COMM      (IPC_REG_BASE + 0x38)
+/* Reset register */
+#define IPC_REG_ISH_RST                (IPC_REG_BASE + 0x44)
+
+/* Inbound doorbell register Host to ISH */
+#define IPC_REG_HOST2ISH_DRBL  (IPC_REG_BASE + 0x48)
+/* Outbound doorbell register ISH to Host */
+#define IPC_REG_ISH2HOST_DRBL  (IPC_REG_BASE + 0x54)
+/* ISH to HOST message registers */
+#define IPC_REG_ISH2HOST_MSG   (IPC_REG_BASE + 0x60)
+/* HOST to ISH message registers */
+#define IPC_REG_HOST2ISH_MSG   (IPC_REG_BASE + 0xE0)
+/* REMAP2 to enable DMA (D3 RCR) */
+#define        IPC_REG_ISH_RMP2        (IPC_REG_BASE + 0x368)
+
+#define        IPC_REG_MAX             (IPC_REG_BASE + 0x400)
+
+/*** register bits - HISR ***/
+/* bit corresponds HOST2ISH interrupt in PISR and PIMR registers */
+#define IPC_INT_HOST2ISH_BIT            (1<<0)
+/***********************************/
+/*CHV_A0, CHV_B0*/
+/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
+#define IPC_INT_ISH2HOST_BIT_CHV_AB    (1<<3)
+/*BXT, CHV_K0*/
+/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
+#define IPC_INT_ISH2HOST_BIT_BXT       (1<<0)
+/***********************************/
+
+/* bit corresponds ISH2HOST busy clear interrupt in PIMR register */
+#define IPC_INT_ISH2HOST_CLR_MASK_BIT  (1<<11)
+
+/* offset of ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
+#define IPC_INT_ISH2HOST_CLR_OFFS      (0)
+
+/* bit corresponds ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
+#define IPC_INT_ISH2HOST_CLR_BIT       (1<<IPC_INT_ISH2HOST_CLR_OFFS)
+
+/* bit corresponds busy bit in doorbell registers */
+#define IPC_DRBL_BUSY_OFFS             (31)
+#define IPC_DRBL_BUSY_BIT              (1<<IPC_DRBL_BUSY_OFFS)
+
+#define        IPC_HOST_OWNS_MSG_OFFS          (30)
+
+/*
+ * A0: bit means that host owns MSGnn registers and is reading them.
+ * ISH FW may not write to them
+ */
+#define        IPC_HOST_OWNS_MSG_BIT           (1<<IPC_HOST_OWNS_MSG_OFFS)
+
+/*
+ * Host status bits (HOSTCOMM)
+ */
+/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
+#define IPC_HOSTCOMM_READY_OFFS                (7)
+#define IPC_HOSTCOMM_READY_BIT         (1<<IPC_HOSTCOMM_READY_OFFS)
+
+/***********************************/
+/*CHV_A0, CHV_B0*/
+#define        IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB (31)
+#define        IPC_HOSTCOMM_INT_EN_BIT_CHV_AB          \
+       (1<<IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB)
+/*BXT, CHV_K0*/
+#define IPC_PIMR_INT_EN_OFFS_BXT       (0)
+#define IPC_PIMR_INT_EN_BIT_BXT                (1<<IPC_PIMR_INT_EN_OFFS_BXT)
+
+#define IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT   (8)
+#define IPC_HOST2ISH_BUSYCLEAR_MASK_BIT                \
+       (1<<IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT)
+/***********************************/
+/*
+ * both Host and ISH have ILUP at bit 0
+ * bit corresponds host ready bit in both status registers
+ */
+#define IPC_ILUP_OFFS                  (0)
+#define IPC_ILUP_BIT                   (1<<IPC_ILUP_OFFS)
+
+/*
+ * FW status bits (relevant)
+ */
+#define        IPC_FWSTS_ILUP          0x1
+#define        IPC_FWSTS_ISHTP_UP      (1<<1)
+#define        IPC_FWSTS_DMA0          (1<<16)
+#define        IPC_FWSTS_DMA1          (1<<17)
+#define        IPC_FWSTS_DMA2          (1<<18)
+#define        IPC_FWSTS_DMA3          (1<<19)
+
+#define        IPC_ISH_IN_DMA          \
+       (IPC_FWSTS_DMA0 | IPC_FWSTS_DMA1 | IPC_FWSTS_DMA2 | IPC_FWSTS_DMA3)
+
+/* bit corresponds host ready bit in ISH FW Status Register */
+#define IPC_ISH_ISHTP_READY_OFFS               (1)
+#define IPC_ISH_ISHTP_READY_BIT                (1<<IPC_ISH_ISHTP_READY_OFFS)
+
+#define        IPC_RMP2_DMA_ENABLED    0x1     /* Value to enable DMA, per D3 RCR */
+
+#define IPC_MSG_MAX_SIZE       0x80
+
+
+#define IPC_HEADER_LENGTH_MASK         0x03FF
+#define IPC_HEADER_PROTOCOL_MASK       0x0F
+#define IPC_HEADER_MNG_CMD_MASK                0x0F
+
+#define IPC_HEADER_LENGTH_OFFSET       0
+#define IPC_HEADER_PROTOCOL_OFFSET     10
+#define IPC_HEADER_MNG_CMD_OFFSET      16
+
+#define IPC_HEADER_GET_LENGTH(drbl_reg)                \
+       (((drbl_reg) >> IPC_HEADER_LENGTH_OFFSET)&IPC_HEADER_LENGTH_MASK)
+#define IPC_HEADER_GET_PROTOCOL(drbl_reg)      \
+       (((drbl_reg) >> IPC_HEADER_PROTOCOL_OFFSET)&IPC_HEADER_PROTOCOL_MASK)
+#define IPC_HEADER_GET_MNG_CMD(drbl_reg)       \
+       (((drbl_reg) >> IPC_HEADER_MNG_CMD_OFFSET)&IPC_HEADER_MNG_CMD_MASK)
+
+#define IPC_IS_BUSY(drbl_reg)                  \
+       (((drbl_reg)&IPC_DRBL_BUSY_BIT) == ((uint32_t)IPC_DRBL_BUSY_BIT))
+
+/***********************************/
+/*CHV_A0, CHV_B0*/
+#define IPC_INT_FROM_ISH_TO_HOST_CHV_AB(drbl_reg) \
+       (((drbl_reg)&IPC_INT_ISH2HOST_BIT_CHV_AB) == \
+       ((u32)IPC_INT_ISH2HOST_BIT_CHV_AB))
+/*BXT, CHV_K0*/
+#define IPC_INT_FROM_ISH_TO_HOST_BXT(drbl_reg) \
+       (((drbl_reg)&IPC_INT_ISH2HOST_BIT_BXT) == \
+       ((u32)IPC_INT_ISH2HOST_BIT_BXT))
+/***********************************/
+
+#define IPC_BUILD_HEADER(length, protocol, busy)               \
+       (((busy)<<IPC_DRBL_BUSY_OFFS) |                         \
+       ((protocol) << IPC_HEADER_PROTOCOL_OFFSET) |            \
+       ((length)<<IPC_HEADER_LENGTH_OFFSET))
+
+#define IPC_BUILD_MNG_MSG(cmd, length)                         \
+       (((1)<<IPC_DRBL_BUSY_OFFS)|                             \
+       ((IPC_PROTOCOL_MNG)<<IPC_HEADER_PROTOCOL_OFFSET)|       \
+       ((cmd)<<IPC_HEADER_MNG_CMD_OFFSET)|                     \
+        ((length)<<IPC_HEADER_LENGTH_OFFSET))
+
+
+#define IPC_SET_HOST_READY(host_status)                \
+                               ((host_status) |= (IPC_HOSTCOMM_READY_BIT))
+
+#define IPC_SET_HOST_ILUP(host_status)         \
+                               ((host_status) |= (IPC_ILUP_BIT))
+
+#define IPC_CLEAR_HOST_READY(host_status)      \
+                               ((host_status) ^= (IPC_HOSTCOMM_READY_BIT))
+
+#define IPC_CLEAR_HOST_ILUP(host_status)       \
+                               ((host_status) ^= (IPC_ILUP_BIT))
+
+/* todo - temp until PIMR HW ready */
+#define IPC_HOST_BUSY_READING_OFFS     6
+
+/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
+#define IPC_HOST_BUSY_READING_BIT      (1<<IPC_HOST_BUSY_READING_OFFS)
+
+#define IPC_SET_HOST_BUSY_READING(host_status) \
+                               ((host_status) |= (IPC_HOST_BUSY_READING_BIT))
+
+#define IPC_CLEAR_HOST_BUSY_READING(host_status)\
+                               ((host_status) ^= (IPC_HOST_BUSY_READING_BIT))
+
+
+#define IPC_IS_ISH_ISHTP_READY(ish_status)     \
+               (((ish_status) & IPC_ISH_ISHTP_READY_BIT) ==    \
+                       ((uint32_t)IPC_ISH_ISHTP_READY_BIT))
+
+#define IPC_IS_ISH_ILUP(ish_status)            \
+               (((ish_status) & IPC_ILUP_BIT) == ((uint32_t)IPC_ILUP_BIT))
+
+
+#define IPC_PROTOCOL_ISHTP             1
+#define IPC_PROTOCOL_MNG               3
+
+#define MNG_RX_CMPL_ENABLE             0
+#define MNG_RX_CMPL_DISABLE            1
+#define MNG_RX_CMPL_INDICATION         2
+#define MNG_RESET_NOTIFY               3
+#define MNG_RESET_NOTIFY_ACK           4
+#define MNG_SYNC_FW_CLOCK              5
+#define MNG_ILLEGAL_CMD                        0xFF
+
+#endif /* _ISHTP_ISH_REGS_H_ */
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
new file mode 100644 (file)
index 0000000..46615a0
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * H/W layer of ISHTP provider device (ISH)
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#ifndef _ISHTP_HW_ISH_H_
+#define _ISHTP_HW_ISH_H_
+
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include "hw-ish-regs.h"
+#include "ishtp-dev.h"
+
+#define CHV_DEVICE_ID          0x22D8
+#define BXT_Ax_DEVICE_ID       0x0AA2
+#define BXT_Bx_DEVICE_ID       0x1AA2
+#define APL_Ax_DEVICE_ID       0x5AA2
+#define SPT_Ax_DEVICE_ID       0x9D35
+
+#define        REVISION_ID_CHT_A0      0x6
+#define        REVISION_ID_CHT_Ax_SI   0x0
+#define        REVISION_ID_CHT_Bx_SI   0x10
+#define        REVISION_ID_CHT_Kx_SI   0x20
+#define        REVISION_ID_CHT_Dx_SI   0x30
+#define        REVISION_ID_CHT_B0      0xB0
+#define        REVISION_ID_SI_MASK     0x70
+
+struct ipc_rst_payload_type {
+       uint16_t        reset_id;
+       uint16_t        reserved;
+};
+
+struct time_sync_format {
+       uint8_t ts1_source;
+       uint8_t ts2_source;
+       uint16_t reserved;
+} __packed;
+
+struct ipc_time_update_msg {
+       uint64_t primary_host_time;
+       struct time_sync_format sync_info;
+       uint64_t secondary_host_time;
+} __packed;
+
+enum {
+       HOST_UTC_TIME_USEC = 0,
+       HOST_SYSTEM_TIME_USEC = 1
+};
+
+struct ish_hw {
+       void __iomem *mem_addr;
+};
+
+#define to_ish_hw(dev) (struct ish_hw *)((dev)->hw)
+
+irqreturn_t ish_irq_handler(int irq, void *dev_id);
+struct ishtp_device *ish_dev_init(struct pci_dev *pdev);
+int ish_hw_start(struct ishtp_device *dev);
+void ish_device_disable(struct ishtp_device *dev);
+
+#endif /* _ISHTP_HW_ISH_H_ */
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
new file mode 100644 (file)
index 0000000..851029b
--- /dev/null
@@ -0,0 +1,882 @@
+/*
+ * H/W layer of ISHTP provider device (ISH)
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include "client.h"
+#include "hw-ish.h"
+#include "utils.h"
+#include "hbm.h"
+
+/* For FW reset flow */
+static struct work_struct fw_reset_work;
+static struct ishtp_device *ishtp_dev;
+
+/**
+ * ish_reg_read() - Read register
+ * @dev: ISHTP device pointer
+ * @offset: Register offset
+ *
+ * Read 32 bit register at a given offset
+ *
+ * Return: Read register value
+ */
+static inline uint32_t ish_reg_read(const struct ishtp_device *dev,
+       unsigned long offset)
+{
+       struct ish_hw *hw = to_ish_hw(dev);
+
+       return readl(hw->mem_addr + offset);
+}
+
+/**
+ * ish_reg_write() - Write register
+ * @dev: ISHTP device pointer
+ * @offset: Register offset
+ * @value: Value to write
+ *
+ * Writes 32 bit register at a give offset
+ */
+static inline void ish_reg_write(struct ishtp_device *dev,
+                                unsigned long offset,
+                                uint32_t value)
+{
+       struct ish_hw *hw = to_ish_hw(dev);
+
+       writel(value, hw->mem_addr + offset);
+}
+
+/**
+ * _ish_read_fw_sts_reg() - Read FW status register
+ * @dev: ISHTP device pointer
+ *
+ * Read FW status register
+ *
+ * Return: Read register value
+ */
+static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev)
+{
+       return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+}
+
+/**
+ * check_generated_interrupt() - Check if ISH interrupt
+ * @dev: ISHTP device pointer
+ *
+ * Check if an interrupt was generated for ISH
+ *
+ * Return: Read true or false
+ */
+static bool check_generated_interrupt(struct ishtp_device *dev)
+{
+       bool interrupt_generated = true;
+       uint32_t pisr_val = 0;
+
+       if (dev->pdev->device == CHV_DEVICE_ID) {
+               pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
+               interrupt_generated =
+                       IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
+       } else {
+               pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT);
+               interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val);
+       }
+
+       return interrupt_generated;
+}
+
+/**
+ * ish_is_input_ready() - Check if FW ready for RX
+ * @dev: ISHTP device pointer
+ *
+ * Check if ISH FW is ready for receiving data
+ *
+ * Return: Read true or false
+ */
+static bool ish_is_input_ready(struct ishtp_device *dev)
+{
+       uint32_t doorbell_val;
+
+       doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL);
+       return !IPC_IS_BUSY(doorbell_val);
+}
+
+/**
+ * set_host_ready() - Indicate host ready
+ * @dev: ISHTP device pointer
+ *
+ * Set host ready indication to FW
+ */
+static void set_host_ready(struct ishtp_device *dev)
+{
+       if (dev->pdev->device == CHV_DEVICE_ID) {
+               if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
+                               (dev->pdev->revision & REVISION_ID_SI_MASK) ==
+                               REVISION_ID_CHT_Ax_SI)
+                       ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81);
+               else if (dev->pdev->revision == REVISION_ID_CHT_B0 ||
+                               (dev->pdev->revision & REVISION_ID_SI_MASK) ==
+                               REVISION_ID_CHT_Bx_SI ||
+                               (dev->pdev->revision & REVISION_ID_SI_MASK) ==
+                               REVISION_ID_CHT_Kx_SI ||
+                               (dev->pdev->revision & REVISION_ID_SI_MASK) ==
+                               REVISION_ID_CHT_Dx_SI) {
+                       uint32_t host_comm_val;
+
+                       host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM);
+                       host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81;
+                       ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val);
+               }
+       } else {
+                       uint32_t host_pimr_val;
+
+                       host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT);
+                       host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT;
+                       /*
+                        * disable interrupt generated instead of
+                        * RX_complete_msg
+                        */
+                       host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT;
+
+                       ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val);
+       }
+}
+
+/**
+ * ishtp_fw_is_ready() - Check if FW ready
+ * @dev: ISHTP device pointer
+ *
+ * Check if ISH FW is ready
+ *
+ * Return: Read true or false
+ */
+static bool ishtp_fw_is_ready(struct ishtp_device *dev)
+{
+       uint32_t ish_status = _ish_read_fw_sts_reg(dev);
+
+       return IPC_IS_ISH_ILUP(ish_status) &&
+               IPC_IS_ISH_ISHTP_READY(ish_status);
+}
+
+/**
+ * ish_set_host_rdy() - Indicate host ready
+ * @dev: ISHTP device pointer
+ *
+ * Set host ready indication to FW
+ */
+static void ish_set_host_rdy(struct ishtp_device *dev)
+{
+       uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
+
+       IPC_SET_HOST_READY(host_status);
+       ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
+}
+
+/**
+ * ish_clr_host_rdy() - Indicate host not ready
+ * @dev: ISHTP device pointer
+ *
+ * Send host not ready indication to FW
+ */
+static void ish_clr_host_rdy(struct ishtp_device *dev)
+{
+       uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
+
+       IPC_CLEAR_HOST_READY(host_status);
+       ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
+}
+
+/**
+ * _ishtp_read_hdr() - Read message header
+ * @dev: ISHTP device pointer
+ *
+ * Read header of 32bit length
+ *
+ * Return: Read register value
+ */
+static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev)
+{
+       return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG);
+}
+
+/**
+ * _ishtp_read - Read message
+ * @dev: ISHTP device pointer
+ * @buffer: message buffer
+ * @buffer_length: length of message buffer
+ *
+ * Read message from FW
+ *
+ * Return: Always 0
+ */
+static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer,
+       unsigned long buffer_length)
+{
+       uint32_t        i;
+       uint32_t        *r_buf = (uint32_t *)buffer;
+       uint32_t        msg_offs;
+
+       msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr);
+       for (i = 0; i < buffer_length; i += sizeof(uint32_t))
+               *r_buf++ = ish_reg_read(dev, msg_offs + i);
+
+       return 0;
+}
+
+/**
+ * write_ipc_from_queue() - try to write ipc msg from Tx queue to device
+ * @dev: ishtp device pointer
+ *
+ * Check if DRBL is cleared. if it is - write the first IPC msg,  then call
+ * the callback function (unless it's NULL)
+ *
+ * Return: 0 for success else failure code
+ */
+static int write_ipc_from_queue(struct ishtp_device *dev)
+{
+       struct wr_msg_ctl_info  *ipc_link;
+       unsigned long   length;
+       unsigned long   rem;
+       unsigned long   flags;
+       uint32_t        doorbell_val;
+       uint32_t        *r_buf;
+       uint32_t        reg_addr;
+       int     i;
+       void    (*ipc_send_compl)(void *);
+       void    *ipc_send_compl_prm;
+       static int      out_ipc_locked;
+       unsigned long   out_ipc_flags;
+
+       if (dev->dev_state == ISHTP_DEV_DISABLED)
+               return  -EINVAL;
+
+       spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags);
+       if (out_ipc_locked) {
+               spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+               return -EBUSY;
+       }
+       out_ipc_locked = 1;
+       if (!ish_is_input_ready(dev)) {
+               out_ipc_locked = 0;
+               spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+               return -EBUSY;
+       }
+       spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+
+       spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+       /*
+        * if tx send list is empty - return 0;
+        * may happen, as RX_COMPLETE handler doesn't check list emptiness.
+        */
+       if (list_empty(&dev->wr_processing_list_head.link)) {
+               spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+               out_ipc_locked = 0;
+               return  0;
+       }
+
+       ipc_link = list_entry(dev->wr_processing_list_head.link.next,
+                             struct wr_msg_ctl_info, link);
+       /* first 4 bytes of the data is the doorbell value (IPC header) */
+       length = ipc_link->length - sizeof(uint32_t);
+       doorbell_val = *(uint32_t *)ipc_link->inline_data;
+       r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t));
+
+       /* If sending MNG_SYNC_FW_CLOCK, update clock again */
+       if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG &&
+               IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) {
+               struct timespec ts_system;
+               struct timeval tv_utc;
+               uint64_t        usec_system, usec_utc;
+               struct ipc_time_update_msg time_update;
+               struct time_sync_format ts_format;
+
+               get_monotonic_boottime(&ts_system);
+               do_gettimeofday(&tv_utc);
+               usec_system = (timespec_to_ns(&ts_system)) / NSEC_PER_USEC;
+               usec_utc = (uint64_t)tv_utc.tv_sec * 1000000 +
+                                               ((uint32_t)tv_utc.tv_usec);
+               ts_format.ts1_source = HOST_SYSTEM_TIME_USEC;
+               ts_format.ts2_source = HOST_UTC_TIME_USEC;
+
+               time_update.primary_host_time = usec_system;
+               time_update.secondary_host_time = usec_utc;
+               time_update.sync_info = ts_format;
+
+               memcpy(r_buf, &time_update,
+                      sizeof(struct ipc_time_update_msg));
+       }
+
+       for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++,
+                       reg_addr += 4)
+               ish_reg_write(dev, reg_addr, r_buf[i]);
+
+       rem = length & 0x3;
+       if (rem > 0) {
+               uint32_t reg = 0;
+
+               memcpy(&reg, &r_buf[length >> 2], rem);
+               ish_reg_write(dev, reg_addr, reg);
+       }
+       /* Flush writes to msg registers and doorbell */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+       /* Update IPC counters */
+       ++dev->ipc_tx_cnt;
+       dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
+
+       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val);
+       out_ipc_locked = 0;
+
+       ipc_send_compl = ipc_link->ipc_send_compl;
+       ipc_send_compl_prm = ipc_link->ipc_send_compl_prm;
+       list_del_init(&ipc_link->link);
+       list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link);
+       spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+       /*
+        * callback will be called out of spinlock,
+        * after ipc_link returned to free list
+        */
+       if (ipc_send_compl)
+               ipc_send_compl(ipc_send_compl_prm);
+
+       return 0;
+}
+
+/**
+ * write_ipc_to_queue() - write ipc msg to Tx queue
+ * @dev: ishtp device instance
+ * @ipc_send_compl: Send complete callback
+ * @ipc_send_compl_prm:        Parameter to send in complete callback
+ * @msg: Pointer to message
+ * @length: Length of message
+ *
+ * Recived msg with IPC (and upper protocol) header  and add it to the device
+ *  Tx-to-write list then try to send the first IPC waiting msg
+ *  (if DRBL is cleared)
+ * This function returns negative value for failure (means free list
+ *  is empty, or msg too long) and 0 for success.
+ *
+ * Return: 0 for success else failure code
+ */
+static int write_ipc_to_queue(struct ishtp_device *dev,
+       void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
+       unsigned char *msg, int length)
+{
+       struct wr_msg_ctl_info *ipc_link;
+       unsigned long   flags;
+
+       if (length > IPC_FULL_MSG_SIZE)
+               return -EMSGSIZE;
+
+       spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+       if (list_empty(&dev->wr_free_list_head.link)) {
+               spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+               return -ENOMEM;
+       }
+       ipc_link = list_entry(dev->wr_free_list_head.link.next,
+               struct wr_msg_ctl_info, link);
+       list_del_init(&ipc_link->link);
+
+       ipc_link->ipc_send_compl = ipc_send_compl;
+       ipc_link->ipc_send_compl_prm = ipc_send_compl_prm;
+       ipc_link->length = length;
+       memcpy(ipc_link->inline_data, msg, length);
+
+       list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link);
+       spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+       write_ipc_from_queue(dev);
+
+       return 0;
+}
+
+/**
+ * ipc_send_mng_msg() - Send management message
+ * @dev: ishtp device instance
+ * @msg_code: Message code
+ * @msg: Pointer to message
+ * @size: Length of message
+ *
+ * Send management message to FW
+ *
+ * Return: 0 for success else failure code
+ */
+static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code,
+       void *msg, size_t size)
+{
+       unsigned char   ipc_msg[IPC_FULL_MSG_SIZE];
+       uint32_t        drbl_val = IPC_BUILD_MNG_MSG(msg_code, size);
+
+       memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
+       memcpy(ipc_msg + sizeof(uint32_t), msg, size);
+       return  write_ipc_to_queue(dev, NULL, NULL, ipc_msg,
+               sizeof(uint32_t) + size);
+}
+
+/**
+ * ish_fw_reset_handler() - FW reset handler
+ * @dev: ishtp device pointer
+ *
+ * Handle FW reset
+ *
+ * Return: 0 for success else failure code
+ */
+static int ish_fw_reset_handler(struct ishtp_device *dev)
+{
+       uint32_t        reset_id;
+       unsigned long   flags;
+       struct wr_msg_ctl_info *processing, *next;
+
+       /* Read reset ID */
+       reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
+
+       /* Clear IPC output queue */
+       spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+       list_for_each_entry_safe(processing, next,
+                       &dev->wr_processing_list_head.link, link) {
+               list_del(&processing->link);
+               list_add_tail(&processing->link, &dev->wr_free_list_head.link);
+       }
+       spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+       /* ISHTP notification in IPC_RESET */
+       ishtp_reset_handler(dev);
+
+       if (!ish_is_input_ready(dev))
+               timed_wait_for_timeout(WAIT_FOR_SEND_SLICE,
+                       ish_is_input_ready(dev), (2 * HZ));
+
+       /* ISH FW is dead */
+       if (!ish_is_input_ready(dev))
+               return  -EPIPE;
+       /*
+        * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
+        * RESET_NOTIFY_ACK - FW will be checking for it
+        */
+       ish_set_host_rdy(dev);
+       /* Send RESET_NOTIFY_ACK (with reset_id) */
+       ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id,
+                        sizeof(uint32_t));
+
+       /* Wait for ISH FW'es ILUP and ISHTP_READY */
+       timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, ishtp_fw_is_ready(dev),
+               (2 * HZ));
+       if (!ishtp_fw_is_ready(dev)) {
+               /* ISH FW is dead */
+               uint32_t        ish_status;
+
+               ish_status = _ish_read_fw_sts_reg(dev);
+               dev_err(dev->devc,
+                       "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n",
+                       ish_status);
+               return -ENODEV;
+       }
+       return  0;
+}
+
+/**
+ * ish_fw_reset_work_fn() - FW reset worker function
+ * @unused: not used
+ *
+ * Call ish_fw_reset_handler to complete FW reset
+ */
+static void fw_reset_work_fn(struct work_struct *unused)
+{
+       int     rv;
+
+       rv = ish_fw_reset_handler(ishtp_dev);
+       if (!rv) {
+               /* ISH is ILUP & ISHTP-ready. Restart ISHTP */
+               schedule_timeout(HZ / 3);
+               ishtp_dev->recvd_hw_ready = 1;
+               wake_up_interruptible(&ishtp_dev->wait_hw_ready);
+
+               /* ISHTP notification in IPC_RESET sequence completion */
+               ishtp_reset_compl_handler(ishtp_dev);
+       } else
+               dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
+                       rv);
+}
+
+/**
+ * _ish_sync_fw_clock() -Sync FW clock with the OS clock
+ * @dev: ishtp device pointer
+ *
+ * Sync FW and OS time
+ */
+static void _ish_sync_fw_clock(struct ishtp_device *dev)
+{
+       static unsigned long    prev_sync;
+       struct timespec ts;
+       uint64_t        usec;
+
+       if (prev_sync && jiffies - prev_sync < 20 * HZ)
+               return;
+
+       prev_sync = jiffies;
+       get_monotonic_boottime(&ts);
+       usec = (timespec_to_ns(&ts)) / NSEC_PER_USEC;
+       ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t));
+}
+
+/**
+ * recv_ipc() - Receive and process IPC management messages
+ * @dev: ishtp device instance
+ * @doorbell_val: doorbell value
+ *
+ * This function runs in ISR context.
+ * NOTE: Any other mng command than reset_notify and reset_notify_ack
+ * won't wake BH handler
+ */
+static void    recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val)
+{
+       uint32_t        mng_cmd;
+
+       mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val);
+
+       switch (mng_cmd) {
+       default:
+               break;
+
+       case MNG_RX_CMPL_INDICATION:
+               if (dev->suspend_flag) {
+                       dev->suspend_flag = 0;
+                       wake_up_interruptible(&dev->suspend_wait);
+               }
+               if (dev->resume_flag) {
+                       dev->resume_flag = 0;
+                       wake_up_interruptible(&dev->resume_wait);
+               }
+
+               write_ipc_from_queue(dev);
+               break;
+
+       case MNG_RESET_NOTIFY:
+               if (!ishtp_dev) {
+                       ishtp_dev = dev;
+                       INIT_WORK(&fw_reset_work, fw_reset_work_fn);
+               }
+               schedule_work(&fw_reset_work);
+               break;
+
+       case MNG_RESET_NOTIFY_ACK:
+               dev->recvd_hw_ready = 1;
+               wake_up_interruptible(&dev->wait_hw_ready);
+               break;
+       }
+}
+
+/**
+ * ish_irq_handler() - ISH IRQ handler
+ * @irq: irq number
+ * @dev_id: ishtp device pointer
+ *
+ * ISH IRQ handler. If interrupt is generated and is for ISH it will process
+ * the interrupt.
+ */
+irqreturn_t ish_irq_handler(int irq, void *dev_id)
+{
+       struct ishtp_device     *dev = dev_id;
+       uint32_t        doorbell_val;
+       bool    interrupt_generated;
+
+       /* Check that it's interrupt from ISH (may be shared) */
+       interrupt_generated = check_generated_interrupt(dev);
+
+       if (!interrupt_generated)
+               return IRQ_NONE;
+
+       doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL);
+       if (!IPC_IS_BUSY(doorbell_val))
+               return IRQ_HANDLED;
+
+       if (dev->dev_state == ISHTP_DEV_DISABLED)
+               return  IRQ_HANDLED;
+
+       /* Sanity check: IPC dgram length in header */
+       if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) {
+               dev_err(dev->devc,
+                       "IPC hdr - bad length: %u; dropped\n",
+                       (unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val));
+               goto    eoi;
+       }
+
+       switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) {
+       default:
+               break;
+       case IPC_PROTOCOL_MNG:
+               recv_ipc(dev, doorbell_val);
+               break;
+       case IPC_PROTOCOL_ISHTP:
+               ishtp_recv(dev);
+               break;
+       }
+
+eoi:
+       /* Update IPC counters */
+       ++dev->ipc_rx_cnt;
+       dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
+
+       ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
+       /* Flush write to doorbell */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+       return  IRQ_HANDLED;
+}
+
+/**
+ * _ish_hw_reset() - HW reset
+ * @dev: ishtp device pointer
+ *
+ * Reset ISH HW to recover if any error
+ *
+ * Return: 0 for success else error fault code
+ */
+static int _ish_hw_reset(struct ishtp_device *dev)
+{
+       struct pci_dev *pdev = dev->pdev;
+       int     rv;
+       unsigned int    dma_delay;
+       uint16_t csr;
+
+       if (!pdev)
+               return  -ENODEV;
+
+       rv = pci_reset_function(pdev);
+       if (!rv)
+               dev->dev_state = ISHTP_DEV_RESETTING;
+
+       if (!pdev->pm_cap) {
+               dev_err(&pdev->dev, "Can't reset - no PM caps\n");
+               return  -EINVAL;
+       }
+
+       /* Now trigger reset to FW */
+       ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
+
+       for (dma_delay = 0; dma_delay < MAX_DMA_DELAY &&
+               _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA);
+               dma_delay += 5)
+               mdelay(5);
+
+       if (dma_delay >= MAX_DMA_DELAY) {
+               dev_err(&pdev->dev,
+                       "Can't reset - stuck with DMA in-progress\n");
+               return  -EBUSY;
+       }
+
+       pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr);
+
+       csr &= ~PCI_PM_CTRL_STATE_MASK;
+       csr |= PCI_D3hot;
+       pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
+
+       mdelay(pdev->d3_delay);
+
+       csr &= ~PCI_PM_CTRL_STATE_MASK;
+       csr |= PCI_D0;
+       pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
+
+       ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
+
+       /*
+        * Send 0 IPC message so that ISH FW wakes up if it was already
+        * asleep
+        */
+       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
+
+       /* Flush writes to doorbell and REMAP2 */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+       return  0;
+}
+
+/**
+ * _ish_ipc_reset() - IPC reset
+ * @dev: ishtp device pointer
+ *
+ * Resets host and fw IPC and upper layers
+ *
+ * Return: 0 for success else error fault code
+ */
+static int _ish_ipc_reset(struct ishtp_device *dev)
+{
+       struct ipc_rst_payload_type ipc_mng_msg;
+       int     rv = 0;
+
+       ipc_mng_msg.reset_id = 1;
+       ipc_mng_msg.reserved = 0;
+
+       set_host_ready(dev);
+
+       /* Clear the incoming doorbell */
+       ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
+       /* Flush write to doorbell */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+       dev->recvd_hw_ready = 0;
+
+       /* send message */
+       rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg,
+               sizeof(struct ipc_rst_payload_type));
+       if (rv) {
+               dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n");
+               return  rv;
+       }
+
+       wait_event_interruptible_timeout(dev->wait_hw_ready,
+                                        dev->recvd_hw_ready, 2 * HZ);
+       if (!dev->recvd_hw_ready) {
+               dev_err(dev->devc, "Timed out waiting for HW ready\n");
+               rv = -ENODEV;
+       }
+
+       return rv;
+}
+
+/**
+ * ish_hw_start() -Start ISH HW
+ * @dev: ishtp device pointer
+ *
+ * Set host to ready state and wait for FW reset
+ *
+ * Return: 0 for success else error fault code
+ */
+int ish_hw_start(struct ishtp_device *dev)
+{
+       ish_set_host_rdy(dev);
+       /* After that we can enable ISH DMA operation */
+       ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
+
+       /*
+        * Send 0 IPC message so that ISH FW wakes up if it was already
+        * asleep
+        */
+       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
+       /* Flush write to doorbell */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+       set_host_ready(dev);
+
+       /* wait for FW-initiated reset flow */
+       if (!dev->recvd_hw_ready)
+               wait_event_interruptible_timeout(dev->wait_hw_ready,
+                                                dev->recvd_hw_ready,
+                                                10 * HZ);
+
+       if (!dev->recvd_hw_ready) {
+               dev_err(dev->devc,
+                       "[ishtp-ish]: Timed out waiting for FW-initiated reset\n");
+               return  -ENODEV;
+       }
+
+       return 0;
+}
+
+/**
+ * ish_ipc_get_header() -Get doorbell value
+ * @dev: ishtp device pointer
+ * @length: length of message
+ * @busy: busy status
+ *
+ * Get door bell value from message header
+ *
+ * Return: door bell value
+ */
+static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
+                                  int busy)
+{
+       uint32_t drbl_val;
+
+       drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy);
+
+       return drbl_val;
+}
+
+static const struct ishtp_hw_ops ish_hw_ops = {
+       .hw_reset = _ish_hw_reset,
+       .ipc_reset = _ish_ipc_reset,
+       .ipc_get_header = ish_ipc_get_header,
+       .ishtp_read = _ishtp_read,
+       .write = write_ipc_to_queue,
+       .get_fw_status = _ish_read_fw_sts_reg,
+       .sync_fw_clock = _ish_sync_fw_clock,
+       .ishtp_read_hdr = _ishtp_read_hdr
+};
+
+/**
+ * ish_dev_init() -Initialize ISH devoce
+ * @pdev: PCI device
+ *
+ * Allocate ISHTP device and initialize IPC processing
+ *
+ * Return: ISHTP device instance on success else NULL
+ */
+struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
+{
+       struct ishtp_device *dev;
+       int     i;
+
+       dev = kzalloc(sizeof(struct ishtp_device) + sizeof(struct ish_hw),
+               GFP_KERNEL);
+       if (!dev)
+               return NULL;
+
+       ishtp_device_init(dev);
+
+       init_waitqueue_head(&dev->wait_hw_ready);
+
+       spin_lock_init(&dev->wr_processing_spinlock);
+       spin_lock_init(&dev->out_ipc_spinlock);
+
+       /* Init IPC processing and free lists */
+       INIT_LIST_HEAD(&dev->wr_processing_list_head.link);
+       INIT_LIST_HEAD(&dev->wr_free_list_head.link);
+       for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) {
+               struct wr_msg_ctl_info  *tx_buf;
+
+               tx_buf = kzalloc(sizeof(struct wr_msg_ctl_info), GFP_KERNEL);
+               if (!tx_buf) {
+                       /*
+                        * IPC buffers may be limited or not available
+                        * at all - although this shouldn't happen
+                        */
+                       dev_err(dev->devc,
+                               "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n",
+                               i);
+                       break;
+               }
+               list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link);
+       }
+
+       dev->ops = &ish_hw_ops;
+       dev->devc = &pdev->dev;
+       dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr);
+       return dev;
+}
+
+/**
+ * ish_device_disable() - Disable ISH device
+ * @dev: ISHTP device pointer
+ *
+ * Disable ISH by clearing host ready to inform firmware.
+ */
+void   ish_device_disable(struct ishtp_device *dev)
+{
+       dev->dev_state = ISHTP_DEV_DISABLED;
+       ish_clr_host_rdy(dev);
+}
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
new file mode 100644 (file)
index 0000000..b14d6f4
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * PCI glue for ISHTP provider device (ISH) driver
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/miscdevice.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/intel_ish.h>
+#include "ishtp-dev.h"
+#include "hw-ish.h"
+
+static const struct pci_device_id ish_pci_tbl[] = {
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
+       {0, }
+};
+MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
+
+/**
+ * ish_event_tracer() - Callback function to dump trace messages
+ * @dev:       ishtp device
+ * @format:    printf style format
+ *
+ * Callback to direct log messages to Linux trace buffers
+ */
+static void ish_event_tracer(struct ishtp_device *dev, char *format, ...)
+{
+       if (trace_ishtp_dump_enabled()) {
+               va_list args;
+               char tmp_buf[100];
+
+               va_start(args, format);
+               vsnprintf(tmp_buf, sizeof(tmp_buf), format, args);
+               va_end(args);
+
+               trace_ishtp_dump(tmp_buf);
+       }
+}
+
+/**
+ * ish_init() - Init function
+ * @dev:       ishtp device
+ *
+ * This function initialize wait queues for suspend/resume and call
+ * calls hadware initialization function. This will initiate
+ * startup sequence
+ *
+ * Return: 0 for success or error code for failure
+ */
+static int ish_init(struct ishtp_device *dev)
+{
+       int ret;
+
+       /* Set the state of ISH HW to start */
+       ret = ish_hw_start(dev);
+       if (ret) {
+               dev_err(dev->devc, "ISH: hw start failed.\n");
+               return ret;
+       }
+
+       /* Start the inter process communication to ISH processor */
+       ret = ishtp_start(dev);
+       if (ret) {
+               dev_err(dev->devc, "ISHTP: Protocol init failed.\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * ish_probe() - PCI driver probe callback
+ * @pdev:      pci device
+ * @ent:       pci device id
+ *
+ * Initialize PCI function, setup interrupt and call for ISH initialization
+ *
+ * Return: 0 for success or error code for failure
+ */
+static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+       struct ishtp_device *dev;
+       struct ish_hw *hw;
+       int     ret;
+
+       /* enable pci dev */
+       ret = pci_enable_device(pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n");
+               return ret;
+       }
+
+       /* set PCI host mastering */
+       pci_set_master(pdev);
+
+       /* pci request regions for ISH driver */
+       ret = pci_request_regions(pdev, KBUILD_MODNAME);
+       if (ret) {
+               dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n");
+               goto disable_device;
+       }
+
+       /* allocates and initializes the ISH dev structure */
+       dev = ish_dev_init(pdev);
+       if (!dev) {
+               ret = -ENOMEM;
+               goto release_regions;
+       }
+       hw = to_ish_hw(dev);
+       dev->print_log = ish_event_tracer;
+
+       /* mapping IO device memory */
+       hw->mem_addr = pci_iomap(pdev, 0, 0);
+       if (!hw->mem_addr) {
+               dev_err(&pdev->dev, "ISH: mapping I/O range failure\n");
+               ret = -ENOMEM;
+               goto free_device;
+       }
+
+       dev->pdev = pdev;
+
+       pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3;
+
+       /* request and enable interrupt */
+       ret = request_irq(pdev->irq, ish_irq_handler, IRQF_NO_SUSPEND,
+                         KBUILD_MODNAME, dev);
+       if (ret) {
+               dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n",
+                       pdev->irq);
+               goto free_device;
+       }
+
+       dev_set_drvdata(dev->devc, dev);
+
+       init_waitqueue_head(&dev->suspend_wait);
+       init_waitqueue_head(&dev->resume_wait);
+
+       ret = ish_init(dev);
+       if (ret)
+               goto free_irq;
+
+       return 0;
+
+free_irq:
+       free_irq(pdev->irq, dev);
+free_device:
+       pci_iounmap(pdev, hw->mem_addr);
+       kfree(dev);
+release_regions:
+       pci_release_regions(pdev);
+disable_device:
+       pci_clear_master(pdev);
+       pci_disable_device(pdev);
+       dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n");
+
+       return ret;
+}
+
+/**
+ * ish_remove() - PCI driver remove callback
+ * @pdev:      pci device
+ *
+ * This function does cleanup of ISH on pci remove callback
+ */
+static void ish_remove(struct pci_dev *pdev)
+{
+       struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev);
+       struct ish_hw *hw = to_ish_hw(ishtp_dev);
+
+       ishtp_bus_remove_all_clients(ishtp_dev, false);
+       ish_device_disable(ishtp_dev);
+
+       free_irq(pdev->irq, ishtp_dev);
+       pci_iounmap(pdev, hw->mem_addr);
+       pci_release_regions(pdev);
+       pci_clear_master(pdev);
+       pci_disable_device(pdev);
+       kfree(ishtp_dev);
+}
+
+static struct device *ish_resume_device;
+
+/**
+ * ish_resume_handler() - Work function to complete resume
+ * @work:      work struct
+ *
+ * The resume work function to complete resume function asynchronously.
+ * There are two types of platforms, one where ISH is not powered off,
+ * in that case a simple resume message is enough, others we need
+ * a reset sequence.
+ */
+static void ish_resume_handler(struct work_struct *work)
+{
+       struct pci_dev *pdev = to_pci_dev(ish_resume_device);
+       struct ishtp_device *dev = pci_get_drvdata(pdev);
+       int ret;
+
+       ishtp_send_resume(dev);
+
+       /* 50 ms to get resume response */
+       if (dev->resume_flag)
+               ret = wait_event_interruptible_timeout(dev->resume_wait,
+                                                      !dev->resume_flag,
+                                                      msecs_to_jiffies(50));
+
+       /*
+        * If no resume response. This platform  is not S0ix compatible
+        * So on resume full reboot of ISH processor will happen, so
+        * need to go through init sequence again
+        */
+       if (dev->resume_flag)
+               ish_init(dev);
+}
+
+/**
+ * ish_suspend() - ISH suspend callback
+ * @device:    device pointer
+ *
+ * ISH suspend callback
+ *
+ * Return: 0 to the pm core
+ */
+static int ish_suspend(struct device *device)
+{
+       struct pci_dev *pdev = to_pci_dev(device);
+       struct ishtp_device *dev = pci_get_drvdata(pdev);
+
+       enable_irq_wake(pdev->irq);
+       /*
+        * If previous suspend hasn't been asnwered then ISH is likely dead,
+        * don't attempt nested notification
+        */
+       if (dev->suspend_flag)
+               return  0;
+
+       dev->resume_flag = 0;
+       dev->suspend_flag = 1;
+       ishtp_send_suspend(dev);
+
+       /* 25 ms should be enough for live ISH to flush all IPC buf */
+       if (dev->suspend_flag)
+               wait_event_interruptible_timeout(dev->suspend_wait,
+                                                !dev->suspend_flag,
+                                                 msecs_to_jiffies(25));
+
+       return 0;
+}
+
+static DECLARE_WORK(resume_work, ish_resume_handler);
+/**
+ * ish_resume() - ISH resume callback
+ * @device:    device pointer
+ *
+ * ISH resume callback
+ *
+ * Return: 0 to the pm core
+ */
+static int ish_resume(struct device *device)
+{
+       struct pci_dev *pdev = to_pci_dev(device);
+       struct ishtp_device *dev = pci_get_drvdata(pdev);
+
+       ish_resume_device = device;
+       dev->resume_flag = 1;
+
+       disable_irq_wake(pdev->irq);
+       schedule_work(&resume_work);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops ish_pm_ops = {
+       .suspend = ish_suspend,
+       .resume = ish_resume,
+};
+#define ISHTP_ISH_PM_OPS       (&ish_pm_ops)
+#else
+#define ISHTP_ISH_PM_OPS       NULL
+#endif
+
+static struct pci_driver ish_driver = {
+       .name = KBUILD_MODNAME,
+       .id_table = ish_pci_tbl,
+       .probe = ish_probe,
+       .remove = ish_remove,
+       .driver.pm = ISHTP_ISH_PM_OPS,
+};
+
+static int __init ish_driver_init(void)
+{
+       return pci_register_driver(&ish_driver);
+}
+
+static void __exit ish_driver_exit(void)
+{
+       pci_unregister_driver(&ish_driver);
+}
+
+module_init(ish_driver_init);
+module_exit(ish_driver_exit);
+
+/* Original author */
+MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
+/* Adoption to upstream Linux kernel */
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+
+MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-ish-hid/ipc/utils.h b/drivers/hid/intel-ish-hid/ipc/utils.h
new file mode 100644 (file)
index 0000000..5a82123
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Utility macros of ISH
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+#ifndef UTILS__H
+#define UTILS__H
+
+#define        WAIT_FOR_SEND_SLICE     (HZ / 10)
+#define        WAIT_FOR_CONNECT_SLICE  (HZ / 10)
+
+/*
+ * Waits for specified event when a thread that triggers event can't signal
+ * Also, waits *at_least* `timeinc` after condition is satisfied
+ */
+#define        timed_wait_for(timeinc, condition)                      \
+       do {                                                    \
+               int completed = 0;                              \
+               do {                                            \
+                       unsigned long   j;                      \
+                       int     done = 0;                       \
+                                                               \
+                       completed = (condition);                \
+                       for (j = jiffies, done = 0; !done; ) {  \
+                               schedule_timeout(timeinc);      \
+                               if (time_is_before_eq_jiffies(j + timeinc)) \
+                                       done = 1;               \
+                       }                                       \
+               } while (!(completed));                         \
+       } while (0)
+
+
+/*
+ * Waits for specified event when a thread that triggers event
+ * can't signal with timeout (use whenever we may hang)
+ */
+#define        timed_wait_for_timeout(timeinc, condition, timeout)     \
+       do {                                                    \
+               int     t = timeout;                            \
+               do {                                            \
+                       unsigned long   j;                      \
+                       int     done = 0;                       \
+                                                               \
+                       for (j = jiffies, done = 0; !done; ) {  \
+                               schedule_timeout(timeinc);      \
+                               if (time_is_before_eq_jiffies(j + timeinc)) \
+                                       done = 1;               \
+                       } \
+                       t -= timeinc;                           \
+                       if (t <= 0)                             \
+                               break;                          \
+               } while (!(condition));                         \
+       } while (0)
+
+#endif /* UTILS__H */
diff --git a/include/trace/events/intel_ish.h b/include/trace/events/intel_ish.h
new file mode 100644 (file)
index 0000000..92f7d5b
--- /dev/null
@@ -0,0 +1,30 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM intel_ish
+
+#if !defined(_TRACE_INTEL_ISH_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_INTEL_ISH_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(ishtp_dump,
+
+       TP_PROTO(const char *message),
+
+       TP_ARGS(message),
+
+       TP_STRUCT__entry(
+               __string(message, message)
+       ),
+
+       TP_fast_assign(
+               __assign_str(message, message);
+       ),
+
+       TP_printk("%s", __get_str(message))
+);
+
+
+#endif /* _TRACE_INTEL_ISH_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>