/* * 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 #include #include #include #include "client.h" #include "hw-ish.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 = !!pisr_val; /* only busy-clear bit is RW, others are RO */ if (pisr_val) ish_reg_write(dev, IPC_REG_PISR_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)) { spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); out_ipc_locked = 0; return 0; } ipc_link = list_first_entry(&dev->wr_processing_list, 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) { uint64_t usec_system, usec_utc; struct ipc_time_update_msg time_update; struct time_sync_format ts_format; usec_system = ktime_to_us(ktime_get_boottime()); usec_utc = ktime_to_us(ktime_get_real()); ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; ts_format.ts2_source = HOST_UTC_TIME_USEC; ts_format.reserved = 0; 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(®, &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(&ipc_link->link, &dev->wr_free_list); 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)) { spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); return -ENOMEM; } ipc_link = list_first_entry(&dev->wr_free_list, 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); 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); } #define WAIT_FOR_FW_RDY 0x1 #define WAIT_FOR_INPUT_RDY 0x2 /** * timed_wait_for_timeout() - wait special event with timeout * @dev: ISHTP device pointer * @condition: indicate the condition for waiting * @timeinc: time slice for every wait cycle, in ms * @timeout: time in ms for timeout * * This function will check special event to be ready in a loop, the loop * period is specificd in timeinc. Wait timeout will causes failure. * * Return: 0 for success else failure code */ static int timed_wait_for_timeout(struct ishtp_device *dev, int condition, unsigned int timeinc, unsigned int timeout) { bool complete = false; int ret; do { if (condition == WAIT_FOR_FW_RDY) { complete = ishtp_fw_is_ready(dev); } else if (condition == WAIT_FOR_INPUT_RDY) { complete = ish_is_input_ready(dev); } else { ret = -EINVAL; goto out; } if (!complete) { unsigned long left_time; left_time = msleep_interruptible(timeinc); timeout -= (timeinc - left_time); } } while (!complete && timeout > 0); if (complete) ret = 0; else ret = -EBUSY; out: return ret; } #define TIME_SLICE_FOR_FW_RDY_MS 100 #define TIME_SLICE_FOR_INPUT_RDY_MS 100 #define TIMEOUT_FOR_FW_RDY_MS 2000 #define TIMEOUT_FOR_INPUT_RDY_MS 2000 /** * 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; /* 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_splice_init(&dev->wr_processing_list, &dev->wr_free_list); 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(dev, WAIT_FOR_INPUT_RDY, TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS); /* 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(dev, WAIT_FOR_FW_RDY, TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS); 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; } #define TIMEOUT_FOR_HW_RDY_MS 300 /** * 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 */ msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS); 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; uint64_t usec; if (prev_sync && jiffies - prev_sync < 20 * HZ) return; prev_sync = jiffies; usec = ktime_to_us(ktime_get_boottime()); 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_disable_dma() - disable dma communication between host and ISHFW * @dev: ishtp device pointer * * Clear the dma enable bit and wait for dma inactive. * * Return: 0 for success else error code. */ static int ish_disable_dma(struct ishtp_device *dev) { unsigned int dma_delay; /* Clear the dma enable bit */ ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); /* wait for dma inactive */ 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(dev->devc, "Wait for DMA inactive timeout\n"); return -EBUSY; } return 0; } /** * ish_wakeup() - wakeup ishfw from waiting-for-host state * @dev: ishtp device pointer * * Set the dma enable bit and send a void message to FW, * it wil wakeup FW from waiting-for-host state. */ static void ish_wakeup(struct ishtp_device *dev) { /* Set dma enable bit */ 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); } /** * _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; 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; } /* Disable dma communication between FW and host */ if (ish_disable_dma(dev)) { 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); /* Now we can enable ISH DMA operation and wakeup ISHFW */ ish_wakeup(dev); 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); set_host_ready(dev); /* After that we can enable ISH DMA operation and wakeup ISHFW */ ish_wakeup(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 = devm_kzalloc(&pdev->dev, 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); INIT_LIST_HEAD(&dev->wr_free_list); for (i = 0; i < IPC_TX_FIFO_SIZE; i++) { struct wr_msg_ctl_info *tx_buf; tx_buf = devm_kzalloc(&pdev->dev, 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); } 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) { struct pci_dev *pdev = dev->pdev; if (!pdev) return; /* Disable dma communication between FW and host */ if (ish_disable_dma(dev)) { dev_err(&pdev->dev, "Can't reset - stuck with DMA in-progress\n"); return; } /* Put ISH to D3hot state for power saving */ pci_set_power_state(pdev, PCI_D3hot); dev->dev_state = ISHTP_DEV_DISABLED; ish_clr_host_rdy(dev); }