162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * H/W layer of ISHTP provider device (ISH) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2014-2016, Intel Corporation. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/devm-helpers.h> 962306a36Sopenharmony_ci#include <linux/sched.h> 1062306a36Sopenharmony_ci#include <linux/spinlock.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/jiffies.h> 1362306a36Sopenharmony_ci#include "client.h" 1462306a36Sopenharmony_ci#include "hw-ish.h" 1562306a36Sopenharmony_ci#include "hbm.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* For FW reset flow */ 1862306a36Sopenharmony_cistatic struct work_struct fw_reset_work; 1962306a36Sopenharmony_cistatic struct ishtp_device *ishtp_dev; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/** 2262306a36Sopenharmony_ci * ish_reg_read() - Read register 2362306a36Sopenharmony_ci * @dev: ISHTP device pointer 2462306a36Sopenharmony_ci * @offset: Register offset 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * Read 32 bit register at a given offset 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * Return: Read register value 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_cistatic inline uint32_t ish_reg_read(const struct ishtp_device *dev, 3162306a36Sopenharmony_ci unsigned long offset) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct ish_hw *hw = to_ish_hw(dev); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci return readl(hw->mem_addr + offset); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/** 3962306a36Sopenharmony_ci * ish_reg_write() - Write register 4062306a36Sopenharmony_ci * @dev: ISHTP device pointer 4162306a36Sopenharmony_ci * @offset: Register offset 4262306a36Sopenharmony_ci * @value: Value to write 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * Writes 32 bit register at a give offset 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_cistatic inline void ish_reg_write(struct ishtp_device *dev, 4762306a36Sopenharmony_ci unsigned long offset, 4862306a36Sopenharmony_ci uint32_t value) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct ish_hw *hw = to_ish_hw(dev); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci writel(value, hw->mem_addr + offset); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/** 5662306a36Sopenharmony_ci * _ish_read_fw_sts_reg() - Read FW status register 5762306a36Sopenharmony_ci * @dev: ISHTP device pointer 5862306a36Sopenharmony_ci * 5962306a36Sopenharmony_ci * Read FW status register 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * Return: Read register value 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_cistatic inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/** 6962306a36Sopenharmony_ci * check_generated_interrupt() - Check if ISH interrupt 7062306a36Sopenharmony_ci * @dev: ISHTP device pointer 7162306a36Sopenharmony_ci * 7262306a36Sopenharmony_ci * Check if an interrupt was generated for ISH 7362306a36Sopenharmony_ci * 7462306a36Sopenharmony_ci * Return: Read true or false 7562306a36Sopenharmony_ci */ 7662306a36Sopenharmony_cistatic bool check_generated_interrupt(struct ishtp_device *dev) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci bool interrupt_generated = true; 7962306a36Sopenharmony_ci uint32_t pisr_val = 0; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (dev->pdev->device == CHV_DEVICE_ID) { 8262306a36Sopenharmony_ci pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); 8362306a36Sopenharmony_ci interrupt_generated = 8462306a36Sopenharmony_ci IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); 8562306a36Sopenharmony_ci } else { 8662306a36Sopenharmony_ci pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT); 8762306a36Sopenharmony_ci interrupt_generated = !!pisr_val; 8862306a36Sopenharmony_ci /* only busy-clear bit is RW, others are RO */ 8962306a36Sopenharmony_ci if (pisr_val) 9062306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_PISR_BXT, pisr_val); 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return interrupt_generated; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/** 9762306a36Sopenharmony_ci * ish_is_input_ready() - Check if FW ready for RX 9862306a36Sopenharmony_ci * @dev: ISHTP device pointer 9962306a36Sopenharmony_ci * 10062306a36Sopenharmony_ci * Check if ISH FW is ready for receiving data 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * Return: Read true or false 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistatic bool ish_is_input_ready(struct ishtp_device *dev) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci uint32_t doorbell_val; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL); 10962306a36Sopenharmony_ci return !IPC_IS_BUSY(doorbell_val); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci/** 11362306a36Sopenharmony_ci * set_host_ready() - Indicate host ready 11462306a36Sopenharmony_ci * @dev: ISHTP device pointer 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Set host ready indication to FW 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_cistatic void set_host_ready(struct ishtp_device *dev) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci if (dev->pdev->device == CHV_DEVICE_ID) { 12162306a36Sopenharmony_ci if (dev->pdev->revision == REVISION_ID_CHT_A0 || 12262306a36Sopenharmony_ci (dev->pdev->revision & REVISION_ID_SI_MASK) == 12362306a36Sopenharmony_ci REVISION_ID_CHT_Ax_SI) 12462306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81); 12562306a36Sopenharmony_ci else if (dev->pdev->revision == REVISION_ID_CHT_B0 || 12662306a36Sopenharmony_ci (dev->pdev->revision & REVISION_ID_SI_MASK) == 12762306a36Sopenharmony_ci REVISION_ID_CHT_Bx_SI || 12862306a36Sopenharmony_ci (dev->pdev->revision & REVISION_ID_SI_MASK) == 12962306a36Sopenharmony_ci REVISION_ID_CHT_Kx_SI || 13062306a36Sopenharmony_ci (dev->pdev->revision & REVISION_ID_SI_MASK) == 13162306a36Sopenharmony_ci REVISION_ID_CHT_Dx_SI) { 13262306a36Sopenharmony_ci uint32_t host_comm_val; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM); 13562306a36Sopenharmony_ci host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81; 13662306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci } else { 13962306a36Sopenharmony_ci uint32_t host_pimr_val; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT); 14262306a36Sopenharmony_ci host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT; 14362306a36Sopenharmony_ci /* 14462306a36Sopenharmony_ci * disable interrupt generated instead of 14562306a36Sopenharmony_ci * RX_complete_msg 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val); 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci/** 15462306a36Sopenharmony_ci * ishtp_fw_is_ready() - Check if FW ready 15562306a36Sopenharmony_ci * @dev: ISHTP device pointer 15662306a36Sopenharmony_ci * 15762306a36Sopenharmony_ci * Check if ISH FW is ready 15862306a36Sopenharmony_ci * 15962306a36Sopenharmony_ci * Return: Read true or false 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_cistatic bool ishtp_fw_is_ready(struct ishtp_device *dev) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci uint32_t ish_status = _ish_read_fw_sts_reg(dev); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return IPC_IS_ISH_ILUP(ish_status) && 16662306a36Sopenharmony_ci IPC_IS_ISH_ISHTP_READY(ish_status); 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci/** 17062306a36Sopenharmony_ci * ish_set_host_rdy() - Indicate host ready 17162306a36Sopenharmony_ci * @dev: ISHTP device pointer 17262306a36Sopenharmony_ci * 17362306a36Sopenharmony_ci * Set host ready indication to FW 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_cistatic void ish_set_host_rdy(struct ishtp_device *dev) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci IPC_SET_HOST_READY(host_status); 18062306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci/** 18462306a36Sopenharmony_ci * ish_clr_host_rdy() - Indicate host not ready 18562306a36Sopenharmony_ci * @dev: ISHTP device pointer 18662306a36Sopenharmony_ci * 18762306a36Sopenharmony_ci * Send host not ready indication to FW 18862306a36Sopenharmony_ci */ 18962306a36Sopenharmony_cistatic void ish_clr_host_rdy(struct ishtp_device *dev) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci IPC_CLEAR_HOST_READY(host_status); 19462306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic bool ish_chk_host_rdy(struct ishtp_device *dev) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return (host_status & IPC_HOSTCOMM_READY_BIT); 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci/** 20562306a36Sopenharmony_ci * ish_set_host_ready() - reconfig ipc host registers 20662306a36Sopenharmony_ci * @dev: ishtp device pointer 20762306a36Sopenharmony_ci * 20862306a36Sopenharmony_ci * Set host to ready state 20962306a36Sopenharmony_ci * This API is called in some case: 21062306a36Sopenharmony_ci * fw is still on, but ipc is powered down. 21162306a36Sopenharmony_ci * such as OOB case. 21262306a36Sopenharmony_ci * 21362306a36Sopenharmony_ci * Return: 0 for success else error fault code 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_civoid ish_set_host_ready(struct ishtp_device *dev) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci if (ish_chk_host_rdy(dev)) 21862306a36Sopenharmony_ci return; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci ish_set_host_rdy(dev); 22162306a36Sopenharmony_ci set_host_ready(dev); 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci/** 22562306a36Sopenharmony_ci * _ishtp_read_hdr() - Read message header 22662306a36Sopenharmony_ci * @dev: ISHTP device pointer 22762306a36Sopenharmony_ci * 22862306a36Sopenharmony_ci * Read header of 32bit length 22962306a36Sopenharmony_ci * 23062306a36Sopenharmony_ci * Return: Read register value 23162306a36Sopenharmony_ci */ 23262306a36Sopenharmony_cistatic uint32_t _ishtp_read_hdr(const struct ishtp_device *dev) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci/** 23862306a36Sopenharmony_ci * _ishtp_read - Read message 23962306a36Sopenharmony_ci * @dev: ISHTP device pointer 24062306a36Sopenharmony_ci * @buffer: message buffer 24162306a36Sopenharmony_ci * @buffer_length: length of message buffer 24262306a36Sopenharmony_ci * 24362306a36Sopenharmony_ci * Read message from FW 24462306a36Sopenharmony_ci * 24562306a36Sopenharmony_ci * Return: Always 0 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_cistatic int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer, 24862306a36Sopenharmony_ci unsigned long buffer_length) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci uint32_t i; 25162306a36Sopenharmony_ci uint32_t *r_buf = (uint32_t *)buffer; 25262306a36Sopenharmony_ci uint32_t msg_offs; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); 25562306a36Sopenharmony_ci for (i = 0; i < buffer_length; i += sizeof(uint32_t)) 25662306a36Sopenharmony_ci *r_buf++ = ish_reg_read(dev, msg_offs + i); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return 0; 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci/** 26262306a36Sopenharmony_ci * write_ipc_from_queue() - try to write ipc msg from Tx queue to device 26362306a36Sopenharmony_ci * @dev: ishtp device pointer 26462306a36Sopenharmony_ci * 26562306a36Sopenharmony_ci * Check if DRBL is cleared. if it is - write the first IPC msg, then call 26662306a36Sopenharmony_ci * the callback function (unless it's NULL) 26762306a36Sopenharmony_ci * 26862306a36Sopenharmony_ci * Return: 0 for success else failure code 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_cistatic int write_ipc_from_queue(struct ishtp_device *dev) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct wr_msg_ctl_info *ipc_link; 27362306a36Sopenharmony_ci unsigned long length; 27462306a36Sopenharmony_ci unsigned long rem; 27562306a36Sopenharmony_ci unsigned long flags; 27662306a36Sopenharmony_ci uint32_t doorbell_val; 27762306a36Sopenharmony_ci uint32_t *r_buf; 27862306a36Sopenharmony_ci uint32_t reg_addr; 27962306a36Sopenharmony_ci int i; 28062306a36Sopenharmony_ci void (*ipc_send_compl)(void *); 28162306a36Sopenharmony_ci void *ipc_send_compl_prm; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci if (dev->dev_state == ISHTP_DEV_DISABLED) 28462306a36Sopenharmony_ci return -EINVAL; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci spin_lock_irqsave(&dev->wr_processing_spinlock, flags); 28762306a36Sopenharmony_ci if (!ish_is_input_ready(dev)) { 28862306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 28962306a36Sopenharmony_ci return -EBUSY; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci /* 29362306a36Sopenharmony_ci * if tx send list is empty - return 0; 29462306a36Sopenharmony_ci * may happen, as RX_COMPLETE handler doesn't check list emptiness. 29562306a36Sopenharmony_ci */ 29662306a36Sopenharmony_ci if (list_empty(&dev->wr_processing_list)) { 29762306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 29862306a36Sopenharmony_ci return 0; 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci ipc_link = list_first_entry(&dev->wr_processing_list, 30262306a36Sopenharmony_ci struct wr_msg_ctl_info, link); 30362306a36Sopenharmony_ci /* first 4 bytes of the data is the doorbell value (IPC header) */ 30462306a36Sopenharmony_ci length = ipc_link->length - sizeof(uint32_t); 30562306a36Sopenharmony_ci doorbell_val = *(uint32_t *)ipc_link->inline_data; 30662306a36Sopenharmony_ci r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t)); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci /* If sending MNG_SYNC_FW_CLOCK, update clock again */ 30962306a36Sopenharmony_ci if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG && 31062306a36Sopenharmony_ci IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) { 31162306a36Sopenharmony_ci uint64_t usec_system, usec_utc; 31262306a36Sopenharmony_ci struct ipc_time_update_msg time_update; 31362306a36Sopenharmony_ci struct time_sync_format ts_format; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci usec_system = ktime_to_us(ktime_get_boottime()); 31662306a36Sopenharmony_ci usec_utc = ktime_to_us(ktime_get_real()); 31762306a36Sopenharmony_ci ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; 31862306a36Sopenharmony_ci ts_format.ts2_source = HOST_UTC_TIME_USEC; 31962306a36Sopenharmony_ci ts_format.reserved = 0; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci time_update.primary_host_time = usec_system; 32262306a36Sopenharmony_ci time_update.secondary_host_time = usec_utc; 32362306a36Sopenharmony_ci time_update.sync_info = ts_format; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci memcpy(r_buf, &time_update, 32662306a36Sopenharmony_ci sizeof(struct ipc_time_update_msg)); 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++, 33062306a36Sopenharmony_ci reg_addr += 4) 33162306a36Sopenharmony_ci ish_reg_write(dev, reg_addr, r_buf[i]); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci rem = length & 0x3; 33462306a36Sopenharmony_ci if (rem > 0) { 33562306a36Sopenharmony_ci uint32_t reg = 0; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci memcpy(®, &r_buf[length >> 2], rem); 33862306a36Sopenharmony_ci ish_reg_write(dev, reg_addr, reg); 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci /* Flush writes to msg registers and doorbell */ 34362306a36Sopenharmony_ci ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci /* Update IPC counters */ 34662306a36Sopenharmony_ci ++dev->ipc_tx_cnt; 34762306a36Sopenharmony_ci dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci ipc_send_compl = ipc_link->ipc_send_compl; 35062306a36Sopenharmony_ci ipc_send_compl_prm = ipc_link->ipc_send_compl_prm; 35162306a36Sopenharmony_ci list_del_init(&ipc_link->link); 35262306a36Sopenharmony_ci list_add(&ipc_link->link, &dev->wr_free_list); 35362306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci /* 35662306a36Sopenharmony_ci * callback will be called out of spinlock, 35762306a36Sopenharmony_ci * after ipc_link returned to free list 35862306a36Sopenharmony_ci */ 35962306a36Sopenharmony_ci if (ipc_send_compl) 36062306a36Sopenharmony_ci ipc_send_compl(ipc_send_compl_prm); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return 0; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci/** 36662306a36Sopenharmony_ci * write_ipc_to_queue() - write ipc msg to Tx queue 36762306a36Sopenharmony_ci * @dev: ishtp device instance 36862306a36Sopenharmony_ci * @ipc_send_compl: Send complete callback 36962306a36Sopenharmony_ci * @ipc_send_compl_prm: Parameter to send in complete callback 37062306a36Sopenharmony_ci * @msg: Pointer to message 37162306a36Sopenharmony_ci * @length: Length of message 37262306a36Sopenharmony_ci * 37362306a36Sopenharmony_ci * Recived msg with IPC (and upper protocol) header and add it to the device 37462306a36Sopenharmony_ci * Tx-to-write list then try to send the first IPC waiting msg 37562306a36Sopenharmony_ci * (if DRBL is cleared) 37662306a36Sopenharmony_ci * This function returns negative value for failure (means free list 37762306a36Sopenharmony_ci * is empty, or msg too long) and 0 for success. 37862306a36Sopenharmony_ci * 37962306a36Sopenharmony_ci * Return: 0 for success else failure code 38062306a36Sopenharmony_ci */ 38162306a36Sopenharmony_cistatic int write_ipc_to_queue(struct ishtp_device *dev, 38262306a36Sopenharmony_ci void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, 38362306a36Sopenharmony_ci unsigned char *msg, int length) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct wr_msg_ctl_info *ipc_link; 38662306a36Sopenharmony_ci unsigned long flags; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci if (length > IPC_FULL_MSG_SIZE) 38962306a36Sopenharmony_ci return -EMSGSIZE; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci spin_lock_irqsave(&dev->wr_processing_spinlock, flags); 39262306a36Sopenharmony_ci if (list_empty(&dev->wr_free_list)) { 39362306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 39462306a36Sopenharmony_ci return -ENOMEM; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci ipc_link = list_first_entry(&dev->wr_free_list, 39762306a36Sopenharmony_ci struct wr_msg_ctl_info, link); 39862306a36Sopenharmony_ci list_del_init(&ipc_link->link); 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci ipc_link->ipc_send_compl = ipc_send_compl; 40162306a36Sopenharmony_ci ipc_link->ipc_send_compl_prm = ipc_send_compl_prm; 40262306a36Sopenharmony_ci ipc_link->length = length; 40362306a36Sopenharmony_ci memcpy(ipc_link->inline_data, msg, length); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci list_add_tail(&ipc_link->link, &dev->wr_processing_list); 40662306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci write_ipc_from_queue(dev); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci} 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci/** 41462306a36Sopenharmony_ci * ipc_send_mng_msg() - Send management message 41562306a36Sopenharmony_ci * @dev: ishtp device instance 41662306a36Sopenharmony_ci * @msg_code: Message code 41762306a36Sopenharmony_ci * @msg: Pointer to message 41862306a36Sopenharmony_ci * @size: Length of message 41962306a36Sopenharmony_ci * 42062306a36Sopenharmony_ci * Send management message to FW 42162306a36Sopenharmony_ci * 42262306a36Sopenharmony_ci * Return: 0 for success else failure code 42362306a36Sopenharmony_ci */ 42462306a36Sopenharmony_cistatic int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code, 42562306a36Sopenharmony_ci void *msg, size_t size) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci unsigned char ipc_msg[IPC_FULL_MSG_SIZE]; 42862306a36Sopenharmony_ci uint32_t drbl_val = IPC_BUILD_MNG_MSG(msg_code, size); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci memcpy(ipc_msg, &drbl_val, sizeof(uint32_t)); 43162306a36Sopenharmony_ci memcpy(ipc_msg + sizeof(uint32_t), msg, size); 43262306a36Sopenharmony_ci return write_ipc_to_queue(dev, NULL, NULL, ipc_msg, 43362306a36Sopenharmony_ci sizeof(uint32_t) + size); 43462306a36Sopenharmony_ci} 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci#define WAIT_FOR_FW_RDY 0x1 43762306a36Sopenharmony_ci#define WAIT_FOR_INPUT_RDY 0x2 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci/** 44062306a36Sopenharmony_ci * timed_wait_for_timeout() - wait special event with timeout 44162306a36Sopenharmony_ci * @dev: ISHTP device pointer 44262306a36Sopenharmony_ci * @condition: indicate the condition for waiting 44362306a36Sopenharmony_ci * @timeinc: time slice for every wait cycle, in ms 44462306a36Sopenharmony_ci * @timeout: time in ms for timeout 44562306a36Sopenharmony_ci * 44662306a36Sopenharmony_ci * This function will check special event to be ready in a loop, the loop 44762306a36Sopenharmony_ci * period is specificd in timeinc. Wait timeout will causes failure. 44862306a36Sopenharmony_ci * 44962306a36Sopenharmony_ci * Return: 0 for success else failure code 45062306a36Sopenharmony_ci */ 45162306a36Sopenharmony_cistatic int timed_wait_for_timeout(struct ishtp_device *dev, int condition, 45262306a36Sopenharmony_ci unsigned int timeinc, unsigned int timeout) 45362306a36Sopenharmony_ci{ 45462306a36Sopenharmony_ci bool complete = false; 45562306a36Sopenharmony_ci int ret; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci do { 45862306a36Sopenharmony_ci if (condition == WAIT_FOR_FW_RDY) { 45962306a36Sopenharmony_ci complete = ishtp_fw_is_ready(dev); 46062306a36Sopenharmony_ci } else if (condition == WAIT_FOR_INPUT_RDY) { 46162306a36Sopenharmony_ci complete = ish_is_input_ready(dev); 46262306a36Sopenharmony_ci } else { 46362306a36Sopenharmony_ci ret = -EINVAL; 46462306a36Sopenharmony_ci goto out; 46562306a36Sopenharmony_ci } 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (!complete) { 46862306a36Sopenharmony_ci unsigned long left_time; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci left_time = msleep_interruptible(timeinc); 47162306a36Sopenharmony_ci timeout -= (timeinc - left_time); 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci } while (!complete && timeout > 0); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci if (complete) 47662306a36Sopenharmony_ci ret = 0; 47762306a36Sopenharmony_ci else 47862306a36Sopenharmony_ci ret = -EBUSY; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ciout: 48162306a36Sopenharmony_ci return ret; 48262306a36Sopenharmony_ci} 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci#define TIME_SLICE_FOR_FW_RDY_MS 100 48562306a36Sopenharmony_ci#define TIME_SLICE_FOR_INPUT_RDY_MS 100 48662306a36Sopenharmony_ci#define TIMEOUT_FOR_FW_RDY_MS 2000 48762306a36Sopenharmony_ci#define TIMEOUT_FOR_INPUT_RDY_MS 2000 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci/** 49062306a36Sopenharmony_ci * ish_fw_reset_handler() - FW reset handler 49162306a36Sopenharmony_ci * @dev: ishtp device pointer 49262306a36Sopenharmony_ci * 49362306a36Sopenharmony_ci * Handle FW reset 49462306a36Sopenharmony_ci * 49562306a36Sopenharmony_ci * Return: 0 for success else failure code 49662306a36Sopenharmony_ci */ 49762306a36Sopenharmony_cistatic int ish_fw_reset_handler(struct ishtp_device *dev) 49862306a36Sopenharmony_ci{ 49962306a36Sopenharmony_ci uint32_t reset_id; 50062306a36Sopenharmony_ci unsigned long flags; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci /* Read reset ID */ 50362306a36Sopenharmony_ci reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci /* Clear IPC output queue */ 50662306a36Sopenharmony_ci spin_lock_irqsave(&dev->wr_processing_spinlock, flags); 50762306a36Sopenharmony_ci list_splice_init(&dev->wr_processing_list, &dev->wr_free_list); 50862306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* ISHTP notification in IPC_RESET */ 51162306a36Sopenharmony_ci ishtp_reset_handler(dev); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (!ish_is_input_ready(dev)) 51462306a36Sopenharmony_ci timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY, 51562306a36Sopenharmony_ci TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* ISH FW is dead */ 51862306a36Sopenharmony_ci if (!ish_is_input_ready(dev)) 51962306a36Sopenharmony_ci return -EPIPE; 52062306a36Sopenharmony_ci /* 52162306a36Sopenharmony_ci * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending 52262306a36Sopenharmony_ci * RESET_NOTIFY_ACK - FW will be checking for it 52362306a36Sopenharmony_ci */ 52462306a36Sopenharmony_ci ish_set_host_rdy(dev); 52562306a36Sopenharmony_ci /* Send RESET_NOTIFY_ACK (with reset_id) */ 52662306a36Sopenharmony_ci ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, 52762306a36Sopenharmony_ci sizeof(uint32_t)); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci /* Wait for ISH FW'es ILUP and ISHTP_READY */ 53062306a36Sopenharmony_ci timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY, 53162306a36Sopenharmony_ci TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS); 53262306a36Sopenharmony_ci if (!ishtp_fw_is_ready(dev)) { 53362306a36Sopenharmony_ci /* ISH FW is dead */ 53462306a36Sopenharmony_ci uint32_t ish_status; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci ish_status = _ish_read_fw_sts_reg(dev); 53762306a36Sopenharmony_ci dev_err(dev->devc, 53862306a36Sopenharmony_ci "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n", 53962306a36Sopenharmony_ci ish_status); 54062306a36Sopenharmony_ci return -ENODEV; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci return 0; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci#define TIMEOUT_FOR_HW_RDY_MS 300 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci/** 54862306a36Sopenharmony_ci * fw_reset_work_fn() - FW reset worker function 54962306a36Sopenharmony_ci * @unused: not used 55062306a36Sopenharmony_ci * 55162306a36Sopenharmony_ci * Call ish_fw_reset_handler to complete FW reset 55262306a36Sopenharmony_ci */ 55362306a36Sopenharmony_cistatic void fw_reset_work_fn(struct work_struct *unused) 55462306a36Sopenharmony_ci{ 55562306a36Sopenharmony_ci int rv; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci rv = ish_fw_reset_handler(ishtp_dev); 55862306a36Sopenharmony_ci if (!rv) { 55962306a36Sopenharmony_ci /* ISH is ILUP & ISHTP-ready. Restart ISHTP */ 56062306a36Sopenharmony_ci msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS); 56162306a36Sopenharmony_ci ishtp_dev->recvd_hw_ready = 1; 56262306a36Sopenharmony_ci wake_up_interruptible(&ishtp_dev->wait_hw_ready); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci /* ISHTP notification in IPC_RESET sequence completion */ 56562306a36Sopenharmony_ci ishtp_reset_compl_handler(ishtp_dev); 56662306a36Sopenharmony_ci } else 56762306a36Sopenharmony_ci dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", 56862306a36Sopenharmony_ci rv); 56962306a36Sopenharmony_ci} 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci/** 57262306a36Sopenharmony_ci * _ish_sync_fw_clock() -Sync FW clock with the OS clock 57362306a36Sopenharmony_ci * @dev: ishtp device pointer 57462306a36Sopenharmony_ci * 57562306a36Sopenharmony_ci * Sync FW and OS time 57662306a36Sopenharmony_ci */ 57762306a36Sopenharmony_cistatic void _ish_sync_fw_clock(struct ishtp_device *dev) 57862306a36Sopenharmony_ci{ 57962306a36Sopenharmony_ci static unsigned long prev_sync; 58062306a36Sopenharmony_ci uint64_t usec; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if (prev_sync && time_before(jiffies, prev_sync + 20 * HZ)) 58362306a36Sopenharmony_ci return; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci prev_sync = jiffies; 58662306a36Sopenharmony_ci usec = ktime_to_us(ktime_get_boottime()); 58762306a36Sopenharmony_ci ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); 58862306a36Sopenharmony_ci} 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci/** 59162306a36Sopenharmony_ci * recv_ipc() - Receive and process IPC management messages 59262306a36Sopenharmony_ci * @dev: ishtp device instance 59362306a36Sopenharmony_ci * @doorbell_val: doorbell value 59462306a36Sopenharmony_ci * 59562306a36Sopenharmony_ci * This function runs in ISR context. 59662306a36Sopenharmony_ci * NOTE: Any other mng command than reset_notify and reset_notify_ack 59762306a36Sopenharmony_ci * won't wake BH handler 59862306a36Sopenharmony_ci */ 59962306a36Sopenharmony_cistatic void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci uint32_t mng_cmd; 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val); 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci switch (mng_cmd) { 60662306a36Sopenharmony_ci default: 60762306a36Sopenharmony_ci break; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci case MNG_RX_CMPL_INDICATION: 61062306a36Sopenharmony_ci if (dev->suspend_flag) { 61162306a36Sopenharmony_ci dev->suspend_flag = 0; 61262306a36Sopenharmony_ci wake_up_interruptible(&dev->suspend_wait); 61362306a36Sopenharmony_ci } 61462306a36Sopenharmony_ci if (dev->resume_flag) { 61562306a36Sopenharmony_ci dev->resume_flag = 0; 61662306a36Sopenharmony_ci wake_up_interruptible(&dev->resume_wait); 61762306a36Sopenharmony_ci } 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci write_ipc_from_queue(dev); 62062306a36Sopenharmony_ci break; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci case MNG_RESET_NOTIFY: 62362306a36Sopenharmony_ci if (!ishtp_dev) { 62462306a36Sopenharmony_ci ishtp_dev = dev; 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci schedule_work(&fw_reset_work); 62762306a36Sopenharmony_ci break; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci case MNG_RESET_NOTIFY_ACK: 63062306a36Sopenharmony_ci dev->recvd_hw_ready = 1; 63162306a36Sopenharmony_ci wake_up_interruptible(&dev->wait_hw_ready); 63262306a36Sopenharmony_ci break; 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci} 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci/** 63762306a36Sopenharmony_ci * ish_irq_handler() - ISH IRQ handler 63862306a36Sopenharmony_ci * @irq: irq number 63962306a36Sopenharmony_ci * @dev_id: ishtp device pointer 64062306a36Sopenharmony_ci * 64162306a36Sopenharmony_ci * ISH IRQ handler. If interrupt is generated and is for ISH it will process 64262306a36Sopenharmony_ci * the interrupt. 64362306a36Sopenharmony_ci */ 64462306a36Sopenharmony_ciirqreturn_t ish_irq_handler(int irq, void *dev_id) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct ishtp_device *dev = dev_id; 64762306a36Sopenharmony_ci uint32_t doorbell_val; 64862306a36Sopenharmony_ci bool interrupt_generated; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci /* Check that it's interrupt from ISH (may be shared) */ 65162306a36Sopenharmony_ci interrupt_generated = check_generated_interrupt(dev); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci if (!interrupt_generated) 65462306a36Sopenharmony_ci return IRQ_NONE; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); 65762306a36Sopenharmony_ci if (!IPC_IS_BUSY(doorbell_val)) 65862306a36Sopenharmony_ci return IRQ_HANDLED; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci if (dev->dev_state == ISHTP_DEV_DISABLED) 66162306a36Sopenharmony_ci return IRQ_HANDLED; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci /* Sanity check: IPC dgram length in header */ 66462306a36Sopenharmony_ci if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) { 66562306a36Sopenharmony_ci dev_err(dev->devc, 66662306a36Sopenharmony_ci "IPC hdr - bad length: %u; dropped\n", 66762306a36Sopenharmony_ci (unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val)); 66862306a36Sopenharmony_ci goto eoi; 66962306a36Sopenharmony_ci } 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { 67262306a36Sopenharmony_ci default: 67362306a36Sopenharmony_ci break; 67462306a36Sopenharmony_ci case IPC_PROTOCOL_MNG: 67562306a36Sopenharmony_ci recv_ipc(dev, doorbell_val); 67662306a36Sopenharmony_ci break; 67762306a36Sopenharmony_ci case IPC_PROTOCOL_ISHTP: 67862306a36Sopenharmony_ci ishtp_recv(dev); 67962306a36Sopenharmony_ci break; 68062306a36Sopenharmony_ci } 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_cieoi: 68362306a36Sopenharmony_ci /* Update IPC counters */ 68462306a36Sopenharmony_ci ++dev->ipc_rx_cnt; 68562306a36Sopenharmony_ci dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); 68862306a36Sopenharmony_ci /* Flush write to doorbell */ 68962306a36Sopenharmony_ci ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci return IRQ_HANDLED; 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci/** 69562306a36Sopenharmony_ci * ish_disable_dma() - disable dma communication between host and ISHFW 69662306a36Sopenharmony_ci * @dev: ishtp device pointer 69762306a36Sopenharmony_ci * 69862306a36Sopenharmony_ci * Clear the dma enable bit and wait for dma inactive. 69962306a36Sopenharmony_ci * 70062306a36Sopenharmony_ci * Return: 0 for success else error code. 70162306a36Sopenharmony_ci */ 70262306a36Sopenharmony_ciint ish_disable_dma(struct ishtp_device *dev) 70362306a36Sopenharmony_ci{ 70462306a36Sopenharmony_ci unsigned int dma_delay; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci /* Clear the dma enable bit */ 70762306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci /* wait for dma inactive */ 71062306a36Sopenharmony_ci for (dma_delay = 0; dma_delay < MAX_DMA_DELAY && 71162306a36Sopenharmony_ci _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA); 71262306a36Sopenharmony_ci dma_delay += 5) 71362306a36Sopenharmony_ci mdelay(5); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci if (dma_delay >= MAX_DMA_DELAY) { 71662306a36Sopenharmony_ci dev_err(dev->devc, 71762306a36Sopenharmony_ci "Wait for DMA inactive timeout\n"); 71862306a36Sopenharmony_ci return -EBUSY; 71962306a36Sopenharmony_ci } 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci return 0; 72262306a36Sopenharmony_ci} 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci/** 72562306a36Sopenharmony_ci * ish_wakeup() - wakeup ishfw from waiting-for-host state 72662306a36Sopenharmony_ci * @dev: ishtp device pointer 72762306a36Sopenharmony_ci * 72862306a36Sopenharmony_ci * Set the dma enable bit and send a void message to FW, 72962306a36Sopenharmony_ci * it wil wakeup FW from waiting-for-host state. 73062306a36Sopenharmony_ci */ 73162306a36Sopenharmony_cistatic void ish_wakeup(struct ishtp_device *dev) 73262306a36Sopenharmony_ci{ 73362306a36Sopenharmony_ci /* Set dma enable bit */ 73462306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci /* 73762306a36Sopenharmony_ci * Send 0 IPC message so that ISH FW wakes up if it was already 73862306a36Sopenharmony_ci * asleep. 73962306a36Sopenharmony_ci */ 74062306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci /* Flush writes to doorbell and REMAP2 */ 74362306a36Sopenharmony_ci ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); 74462306a36Sopenharmony_ci} 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci/** 74762306a36Sopenharmony_ci * _ish_hw_reset() - HW reset 74862306a36Sopenharmony_ci * @dev: ishtp device pointer 74962306a36Sopenharmony_ci * 75062306a36Sopenharmony_ci * Reset ISH HW to recover if any error 75162306a36Sopenharmony_ci * 75262306a36Sopenharmony_ci * Return: 0 for success else error fault code 75362306a36Sopenharmony_ci */ 75462306a36Sopenharmony_cistatic int _ish_hw_reset(struct ishtp_device *dev) 75562306a36Sopenharmony_ci{ 75662306a36Sopenharmony_ci struct pci_dev *pdev = dev->pdev; 75762306a36Sopenharmony_ci int rv; 75862306a36Sopenharmony_ci uint16_t csr; 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci if (!pdev) 76162306a36Sopenharmony_ci return -ENODEV; 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci rv = pci_reset_function(pdev); 76462306a36Sopenharmony_ci if (!rv) 76562306a36Sopenharmony_ci dev->dev_state = ISHTP_DEV_RESETTING; 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci if (!pdev->pm_cap) { 76862306a36Sopenharmony_ci dev_err(&pdev->dev, "Can't reset - no PM caps\n"); 76962306a36Sopenharmony_ci return -EINVAL; 77062306a36Sopenharmony_ci } 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* Disable dma communication between FW and host */ 77362306a36Sopenharmony_ci if (ish_disable_dma(dev)) { 77462306a36Sopenharmony_ci dev_err(&pdev->dev, 77562306a36Sopenharmony_ci "Can't reset - stuck with DMA in-progress\n"); 77662306a36Sopenharmony_ci return -EBUSY; 77762306a36Sopenharmony_ci } 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr); 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci csr &= ~PCI_PM_CTRL_STATE_MASK; 78262306a36Sopenharmony_ci csr |= PCI_D3hot; 78362306a36Sopenharmony_ci pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci mdelay(pdev->d3hot_delay); 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci csr &= ~PCI_PM_CTRL_STATE_MASK; 78862306a36Sopenharmony_ci csr |= PCI_D0; 78962306a36Sopenharmony_ci pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci /* Now we can enable ISH DMA operation and wakeup ISHFW */ 79262306a36Sopenharmony_ci ish_wakeup(dev); 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci return 0; 79562306a36Sopenharmony_ci} 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci/** 79862306a36Sopenharmony_ci * _ish_ipc_reset() - IPC reset 79962306a36Sopenharmony_ci * @dev: ishtp device pointer 80062306a36Sopenharmony_ci * 80162306a36Sopenharmony_ci * Resets host and fw IPC and upper layers 80262306a36Sopenharmony_ci * 80362306a36Sopenharmony_ci * Return: 0 for success else error fault code 80462306a36Sopenharmony_ci */ 80562306a36Sopenharmony_cistatic int _ish_ipc_reset(struct ishtp_device *dev) 80662306a36Sopenharmony_ci{ 80762306a36Sopenharmony_ci struct ipc_rst_payload_type ipc_mng_msg; 80862306a36Sopenharmony_ci int rv = 0; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci ipc_mng_msg.reset_id = 1; 81162306a36Sopenharmony_ci ipc_mng_msg.reserved = 0; 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci set_host_ready(dev); 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci /* Clear the incoming doorbell */ 81662306a36Sopenharmony_ci ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); 81762306a36Sopenharmony_ci /* Flush write to doorbell */ 81862306a36Sopenharmony_ci ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci dev->recvd_hw_ready = 0; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci /* send message */ 82362306a36Sopenharmony_ci rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg, 82462306a36Sopenharmony_ci sizeof(struct ipc_rst_payload_type)); 82562306a36Sopenharmony_ci if (rv) { 82662306a36Sopenharmony_ci dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n"); 82762306a36Sopenharmony_ci return rv; 82862306a36Sopenharmony_ci } 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci wait_event_interruptible_timeout(dev->wait_hw_ready, 83162306a36Sopenharmony_ci dev->recvd_hw_ready, 2 * HZ); 83262306a36Sopenharmony_ci if (!dev->recvd_hw_ready) { 83362306a36Sopenharmony_ci dev_err(dev->devc, "Timed out waiting for HW ready\n"); 83462306a36Sopenharmony_ci rv = -ENODEV; 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci return rv; 83862306a36Sopenharmony_ci} 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci/** 84162306a36Sopenharmony_ci * ish_hw_start() -Start ISH HW 84262306a36Sopenharmony_ci * @dev: ishtp device pointer 84362306a36Sopenharmony_ci * 84462306a36Sopenharmony_ci * Set host to ready state and wait for FW reset 84562306a36Sopenharmony_ci * 84662306a36Sopenharmony_ci * Return: 0 for success else error fault code 84762306a36Sopenharmony_ci */ 84862306a36Sopenharmony_ciint ish_hw_start(struct ishtp_device *dev) 84962306a36Sopenharmony_ci{ 85062306a36Sopenharmony_ci ish_set_host_rdy(dev); 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci set_host_ready(dev); 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci /* After that we can enable ISH DMA operation and wakeup ISHFW */ 85562306a36Sopenharmony_ci ish_wakeup(dev); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci /* wait for FW-initiated reset flow */ 85862306a36Sopenharmony_ci if (!dev->recvd_hw_ready) 85962306a36Sopenharmony_ci wait_event_interruptible_timeout(dev->wait_hw_ready, 86062306a36Sopenharmony_ci dev->recvd_hw_ready, 86162306a36Sopenharmony_ci 10 * HZ); 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci if (!dev->recvd_hw_ready) { 86462306a36Sopenharmony_ci dev_err(dev->devc, 86562306a36Sopenharmony_ci "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); 86662306a36Sopenharmony_ci return -ENODEV; 86762306a36Sopenharmony_ci } 86862306a36Sopenharmony_ci 86962306a36Sopenharmony_ci return 0; 87062306a36Sopenharmony_ci} 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci/** 87362306a36Sopenharmony_ci * ish_ipc_get_header() -Get doorbell value 87462306a36Sopenharmony_ci * @dev: ishtp device pointer 87562306a36Sopenharmony_ci * @length: length of message 87662306a36Sopenharmony_ci * @busy: busy status 87762306a36Sopenharmony_ci * 87862306a36Sopenharmony_ci * Get door bell value from message header 87962306a36Sopenharmony_ci * 88062306a36Sopenharmony_ci * Return: door bell value 88162306a36Sopenharmony_ci */ 88262306a36Sopenharmony_cistatic uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, 88362306a36Sopenharmony_ci int busy) 88462306a36Sopenharmony_ci{ 88562306a36Sopenharmony_ci uint32_t drbl_val; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci return drbl_val; 89062306a36Sopenharmony_ci} 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci/** 89362306a36Sopenharmony_ci * _dma_no_cache_snooping() 89462306a36Sopenharmony_ci * 89562306a36Sopenharmony_ci * Check on current platform, DMA supports cache snooping or not. 89662306a36Sopenharmony_ci * This callback is used to notify uplayer driver if manully cache 89762306a36Sopenharmony_ci * flush is needed when do DMA operation. 89862306a36Sopenharmony_ci * 89962306a36Sopenharmony_ci * Please pay attention to this callback implementation, if declare 90062306a36Sopenharmony_ci * having cache snooping on a cache snooping not supported platform 90162306a36Sopenharmony_ci * will cause uplayer driver receiving mismatched data; and if 90262306a36Sopenharmony_ci * declare no cache snooping on a cache snooping supported platform 90362306a36Sopenharmony_ci * will cause cache be flushed twice and performance hit. 90462306a36Sopenharmony_ci * 90562306a36Sopenharmony_ci * @dev: ishtp device pointer 90662306a36Sopenharmony_ci * 90762306a36Sopenharmony_ci * Return: false - has cache snooping capability 90862306a36Sopenharmony_ci * true - no cache snooping, need manually cache flush 90962306a36Sopenharmony_ci */ 91062306a36Sopenharmony_cistatic bool _dma_no_cache_snooping(struct ishtp_device *dev) 91162306a36Sopenharmony_ci{ 91262306a36Sopenharmony_ci return (dev->pdev->device == EHL_Ax_DEVICE_ID || 91362306a36Sopenharmony_ci dev->pdev->device == TGL_LP_DEVICE_ID || 91462306a36Sopenharmony_ci dev->pdev->device == TGL_H_DEVICE_ID || 91562306a36Sopenharmony_ci dev->pdev->device == ADL_S_DEVICE_ID || 91662306a36Sopenharmony_ci dev->pdev->device == ADL_P_DEVICE_ID); 91762306a36Sopenharmony_ci} 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_cistatic const struct ishtp_hw_ops ish_hw_ops = { 92062306a36Sopenharmony_ci .hw_reset = _ish_hw_reset, 92162306a36Sopenharmony_ci .ipc_reset = _ish_ipc_reset, 92262306a36Sopenharmony_ci .ipc_get_header = ish_ipc_get_header, 92362306a36Sopenharmony_ci .ishtp_read = _ishtp_read, 92462306a36Sopenharmony_ci .write = write_ipc_to_queue, 92562306a36Sopenharmony_ci .get_fw_status = _ish_read_fw_sts_reg, 92662306a36Sopenharmony_ci .sync_fw_clock = _ish_sync_fw_clock, 92762306a36Sopenharmony_ci .ishtp_read_hdr = _ishtp_read_hdr, 92862306a36Sopenharmony_ci .dma_no_cache_snooping = _dma_no_cache_snooping 92962306a36Sopenharmony_ci}; 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci/** 93262306a36Sopenharmony_ci * ish_dev_init() -Initialize ISH devoce 93362306a36Sopenharmony_ci * @pdev: PCI device 93462306a36Sopenharmony_ci * 93562306a36Sopenharmony_ci * Allocate ISHTP device and initialize IPC processing 93662306a36Sopenharmony_ci * 93762306a36Sopenharmony_ci * Return: ISHTP device instance on success else NULL 93862306a36Sopenharmony_ci */ 93962306a36Sopenharmony_cistruct ishtp_device *ish_dev_init(struct pci_dev *pdev) 94062306a36Sopenharmony_ci{ 94162306a36Sopenharmony_ci struct ishtp_device *dev; 94262306a36Sopenharmony_ci int i; 94362306a36Sopenharmony_ci int ret; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci dev = devm_kzalloc(&pdev->dev, 94662306a36Sopenharmony_ci sizeof(struct ishtp_device) + sizeof(struct ish_hw), 94762306a36Sopenharmony_ci GFP_KERNEL); 94862306a36Sopenharmony_ci if (!dev) 94962306a36Sopenharmony_ci return NULL; 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci ishtp_device_init(dev); 95262306a36Sopenharmony_ci 95362306a36Sopenharmony_ci init_waitqueue_head(&dev->wait_hw_ready); 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci spin_lock_init(&dev->wr_processing_spinlock); 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci /* Init IPC processing and free lists */ 95862306a36Sopenharmony_ci INIT_LIST_HEAD(&dev->wr_processing_list); 95962306a36Sopenharmony_ci INIT_LIST_HEAD(&dev->wr_free_list); 96062306a36Sopenharmony_ci for (i = 0; i < IPC_TX_FIFO_SIZE; i++) { 96162306a36Sopenharmony_ci struct wr_msg_ctl_info *tx_buf; 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci tx_buf = devm_kzalloc(&pdev->dev, 96462306a36Sopenharmony_ci sizeof(struct wr_msg_ctl_info), 96562306a36Sopenharmony_ci GFP_KERNEL); 96662306a36Sopenharmony_ci if (!tx_buf) { 96762306a36Sopenharmony_ci /* 96862306a36Sopenharmony_ci * IPC buffers may be limited or not available 96962306a36Sopenharmony_ci * at all - although this shouldn't happen 97062306a36Sopenharmony_ci */ 97162306a36Sopenharmony_ci dev_err(dev->devc, 97262306a36Sopenharmony_ci "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n", 97362306a36Sopenharmony_ci i); 97462306a36Sopenharmony_ci break; 97562306a36Sopenharmony_ci } 97662306a36Sopenharmony_ci list_add_tail(&tx_buf->link, &dev->wr_free_list); 97762306a36Sopenharmony_ci } 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci ret = devm_work_autocancel(&pdev->dev, &fw_reset_work, fw_reset_work_fn); 98062306a36Sopenharmony_ci if (ret) { 98162306a36Sopenharmony_ci dev_err(dev->devc, "Failed to initialise FW reset work\n"); 98262306a36Sopenharmony_ci return NULL; 98362306a36Sopenharmony_ci } 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci dev->ops = &ish_hw_ops; 98662306a36Sopenharmony_ci dev->devc = &pdev->dev; 98762306a36Sopenharmony_ci dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); 98862306a36Sopenharmony_ci return dev; 98962306a36Sopenharmony_ci} 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci/** 99262306a36Sopenharmony_ci * ish_device_disable() - Disable ISH device 99362306a36Sopenharmony_ci * @dev: ISHTP device pointer 99462306a36Sopenharmony_ci * 99562306a36Sopenharmony_ci * Disable ISH by clearing host ready to inform firmware. 99662306a36Sopenharmony_ci */ 99762306a36Sopenharmony_civoid ish_device_disable(struct ishtp_device *dev) 99862306a36Sopenharmony_ci{ 99962306a36Sopenharmony_ci struct pci_dev *pdev = dev->pdev; 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci if (!pdev) 100262306a36Sopenharmony_ci return; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci /* Disable dma communication between FW and host */ 100562306a36Sopenharmony_ci if (ish_disable_dma(dev)) { 100662306a36Sopenharmony_ci dev_err(&pdev->dev, 100762306a36Sopenharmony_ci "Can't reset - stuck with DMA in-progress\n"); 100862306a36Sopenharmony_ci return; 100962306a36Sopenharmony_ci } 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ci /* Put ISH to D3hot state for power saving */ 101262306a36Sopenharmony_ci pci_set_power_state(pdev, PCI_D3hot); 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_ci dev->dev_state = ISHTP_DEV_DISABLED; 101562306a36Sopenharmony_ci ish_clr_host_rdy(dev); 101662306a36Sopenharmony_ci} 1017