18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * H/W layer of ISHTP provider device (ISH)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2014-2016, Intel Corporation.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/sched.h>
98c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
128c2ecf20Sopenharmony_ci#include "client.h"
138c2ecf20Sopenharmony_ci#include "hw-ish.h"
148c2ecf20Sopenharmony_ci#include "hbm.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci/* For FW reset flow */
178c2ecf20Sopenharmony_cistatic struct work_struct fw_reset_work;
188c2ecf20Sopenharmony_cistatic struct ishtp_device *ishtp_dev;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/**
218c2ecf20Sopenharmony_ci * ish_reg_read() - Read register
228c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
238c2ecf20Sopenharmony_ci * @offset: Register offset
248c2ecf20Sopenharmony_ci *
258c2ecf20Sopenharmony_ci * Read 32 bit register at a given offset
268c2ecf20Sopenharmony_ci *
278c2ecf20Sopenharmony_ci * Return: Read register value
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_cistatic inline uint32_t ish_reg_read(const struct ishtp_device *dev,
308c2ecf20Sopenharmony_ci	unsigned long offset)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	struct ish_hw *hw = to_ish_hw(dev);
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	return readl(hw->mem_addr + offset);
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/**
388c2ecf20Sopenharmony_ci * ish_reg_write() - Write register
398c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
408c2ecf20Sopenharmony_ci * @offset: Register offset
418c2ecf20Sopenharmony_ci * @value: Value to write
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * Writes 32 bit register at a give offset
448c2ecf20Sopenharmony_ci */
458c2ecf20Sopenharmony_cistatic inline void ish_reg_write(struct ishtp_device *dev,
468c2ecf20Sopenharmony_ci				 unsigned long offset,
478c2ecf20Sopenharmony_ci				 uint32_t value)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	struct ish_hw *hw = to_ish_hw(dev);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	writel(value, hw->mem_addr + offset);
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/**
558c2ecf20Sopenharmony_ci * _ish_read_fw_sts_reg() - Read FW status register
568c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
578c2ecf20Sopenharmony_ci *
588c2ecf20Sopenharmony_ci * Read FW status register
598c2ecf20Sopenharmony_ci *
608c2ecf20Sopenharmony_ci * Return: Read register value
618c2ecf20Sopenharmony_ci */
628c2ecf20Sopenharmony_cistatic inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/**
688c2ecf20Sopenharmony_ci * check_generated_interrupt() - Check if ISH interrupt
698c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
708c2ecf20Sopenharmony_ci *
718c2ecf20Sopenharmony_ci * Check if an interrupt was generated for ISH
728c2ecf20Sopenharmony_ci *
738c2ecf20Sopenharmony_ci * Return: Read true or false
748c2ecf20Sopenharmony_ci */
758c2ecf20Sopenharmony_cistatic bool check_generated_interrupt(struct ishtp_device *dev)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	bool interrupt_generated = true;
788c2ecf20Sopenharmony_ci	uint32_t pisr_val = 0;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (dev->pdev->device == CHV_DEVICE_ID) {
818c2ecf20Sopenharmony_ci		pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
828c2ecf20Sopenharmony_ci		interrupt_generated =
838c2ecf20Sopenharmony_ci			IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
848c2ecf20Sopenharmony_ci	} else {
858c2ecf20Sopenharmony_ci		pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT);
868c2ecf20Sopenharmony_ci		interrupt_generated = !!pisr_val;
878c2ecf20Sopenharmony_ci		/* only busy-clear bit is RW, others are RO */
888c2ecf20Sopenharmony_ci		if (pisr_val)
898c2ecf20Sopenharmony_ci			ish_reg_write(dev, IPC_REG_PISR_BXT, pisr_val);
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return interrupt_generated;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci/**
968c2ecf20Sopenharmony_ci * ish_is_input_ready() - Check if FW ready for RX
978c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
988c2ecf20Sopenharmony_ci *
998c2ecf20Sopenharmony_ci * Check if ISH FW is ready for receiving data
1008c2ecf20Sopenharmony_ci *
1018c2ecf20Sopenharmony_ci * Return: Read true or false
1028c2ecf20Sopenharmony_ci */
1038c2ecf20Sopenharmony_cistatic bool ish_is_input_ready(struct ishtp_device *dev)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	uint32_t doorbell_val;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL);
1088c2ecf20Sopenharmony_ci	return !IPC_IS_BUSY(doorbell_val);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci/**
1128c2ecf20Sopenharmony_ci * set_host_ready() - Indicate host ready
1138c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
1148c2ecf20Sopenharmony_ci *
1158c2ecf20Sopenharmony_ci * Set host ready indication to FW
1168c2ecf20Sopenharmony_ci */
1178c2ecf20Sopenharmony_cistatic void set_host_ready(struct ishtp_device *dev)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	if (dev->pdev->device == CHV_DEVICE_ID) {
1208c2ecf20Sopenharmony_ci		if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
1218c2ecf20Sopenharmony_ci				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
1228c2ecf20Sopenharmony_ci				REVISION_ID_CHT_Ax_SI)
1238c2ecf20Sopenharmony_ci			ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81);
1248c2ecf20Sopenharmony_ci		else if (dev->pdev->revision == REVISION_ID_CHT_B0 ||
1258c2ecf20Sopenharmony_ci				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
1268c2ecf20Sopenharmony_ci				REVISION_ID_CHT_Bx_SI ||
1278c2ecf20Sopenharmony_ci				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
1288c2ecf20Sopenharmony_ci				REVISION_ID_CHT_Kx_SI ||
1298c2ecf20Sopenharmony_ci				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
1308c2ecf20Sopenharmony_ci				REVISION_ID_CHT_Dx_SI) {
1318c2ecf20Sopenharmony_ci			uint32_t host_comm_val;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci			host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM);
1348c2ecf20Sopenharmony_ci			host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81;
1358c2ecf20Sopenharmony_ci			ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val);
1368c2ecf20Sopenharmony_ci		}
1378c2ecf20Sopenharmony_ci	} else {
1388c2ecf20Sopenharmony_ci			uint32_t host_pimr_val;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci			host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT);
1418c2ecf20Sopenharmony_ci			host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT;
1428c2ecf20Sopenharmony_ci			/*
1438c2ecf20Sopenharmony_ci			 * disable interrupt generated instead of
1448c2ecf20Sopenharmony_ci			 * RX_complete_msg
1458c2ecf20Sopenharmony_ci			 */
1468c2ecf20Sopenharmony_ci			host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci			ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val);
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci/**
1538c2ecf20Sopenharmony_ci * ishtp_fw_is_ready() - Check if FW ready
1548c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
1558c2ecf20Sopenharmony_ci *
1568c2ecf20Sopenharmony_ci * Check if ISH FW is ready
1578c2ecf20Sopenharmony_ci *
1588c2ecf20Sopenharmony_ci * Return: Read true or false
1598c2ecf20Sopenharmony_ci */
1608c2ecf20Sopenharmony_cistatic bool ishtp_fw_is_ready(struct ishtp_device *dev)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	uint32_t ish_status = _ish_read_fw_sts_reg(dev);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	return IPC_IS_ISH_ILUP(ish_status) &&
1658c2ecf20Sopenharmony_ci		IPC_IS_ISH_ISHTP_READY(ish_status);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci/**
1698c2ecf20Sopenharmony_ci * ish_set_host_rdy() - Indicate host ready
1708c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
1718c2ecf20Sopenharmony_ci *
1728c2ecf20Sopenharmony_ci * Set host ready indication to FW
1738c2ecf20Sopenharmony_ci */
1748c2ecf20Sopenharmony_cistatic void ish_set_host_rdy(struct ishtp_device *dev)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	IPC_SET_HOST_READY(host_status);
1798c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci/**
1838c2ecf20Sopenharmony_ci * ish_clr_host_rdy() - Indicate host not ready
1848c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
1858c2ecf20Sopenharmony_ci *
1868c2ecf20Sopenharmony_ci * Send host not ready indication to FW
1878c2ecf20Sopenharmony_ci */
1888c2ecf20Sopenharmony_cistatic void ish_clr_host_rdy(struct ishtp_device *dev)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	IPC_CLEAR_HOST_READY(host_status);
1938c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci/**
1978c2ecf20Sopenharmony_ci * _ishtp_read_hdr() - Read message header
1988c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
1998c2ecf20Sopenharmony_ci *
2008c2ecf20Sopenharmony_ci * Read header of 32bit length
2018c2ecf20Sopenharmony_ci *
2028c2ecf20Sopenharmony_ci * Return: Read register value
2038c2ecf20Sopenharmony_ci */
2048c2ecf20Sopenharmony_cistatic uint32_t _ishtp_read_hdr(const struct ishtp_device *dev)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG);
2078c2ecf20Sopenharmony_ci}
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci/**
2108c2ecf20Sopenharmony_ci * _ishtp_read - Read message
2118c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
2128c2ecf20Sopenharmony_ci * @buffer: message buffer
2138c2ecf20Sopenharmony_ci * @buffer_length: length of message buffer
2148c2ecf20Sopenharmony_ci *
2158c2ecf20Sopenharmony_ci * Read message from FW
2168c2ecf20Sopenharmony_ci *
2178c2ecf20Sopenharmony_ci * Return: Always 0
2188c2ecf20Sopenharmony_ci */
2198c2ecf20Sopenharmony_cistatic int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer,
2208c2ecf20Sopenharmony_ci	unsigned long buffer_length)
2218c2ecf20Sopenharmony_ci{
2228c2ecf20Sopenharmony_ci	uint32_t	i;
2238c2ecf20Sopenharmony_ci	uint32_t	*r_buf = (uint32_t *)buffer;
2248c2ecf20Sopenharmony_ci	uint32_t	msg_offs;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr);
2278c2ecf20Sopenharmony_ci	for (i = 0; i < buffer_length; i += sizeof(uint32_t))
2288c2ecf20Sopenharmony_ci		*r_buf++ = ish_reg_read(dev, msg_offs + i);
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	return 0;
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci/**
2348c2ecf20Sopenharmony_ci * write_ipc_from_queue() - try to write ipc msg from Tx queue to device
2358c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
2368c2ecf20Sopenharmony_ci *
2378c2ecf20Sopenharmony_ci * Check if DRBL is cleared. if it is - write the first IPC msg,  then call
2388c2ecf20Sopenharmony_ci * the callback function (unless it's NULL)
2398c2ecf20Sopenharmony_ci *
2408c2ecf20Sopenharmony_ci * Return: 0 for success else failure code
2418c2ecf20Sopenharmony_ci */
2428c2ecf20Sopenharmony_cistatic int write_ipc_from_queue(struct ishtp_device *dev)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	struct wr_msg_ctl_info	*ipc_link;
2458c2ecf20Sopenharmony_ci	unsigned long	length;
2468c2ecf20Sopenharmony_ci	unsigned long	rem;
2478c2ecf20Sopenharmony_ci	unsigned long	flags;
2488c2ecf20Sopenharmony_ci	uint32_t	doorbell_val;
2498c2ecf20Sopenharmony_ci	uint32_t	*r_buf;
2508c2ecf20Sopenharmony_ci	uint32_t	reg_addr;
2518c2ecf20Sopenharmony_ci	int	i;
2528c2ecf20Sopenharmony_ci	void	(*ipc_send_compl)(void *);
2538c2ecf20Sopenharmony_ci	void	*ipc_send_compl_prm;
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	if (dev->dev_state == ISHTP_DEV_DISABLED)
2568c2ecf20Sopenharmony_ci		return -EINVAL;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
2598c2ecf20Sopenharmony_ci	if (!ish_is_input_ready(dev)) {
2608c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
2618c2ecf20Sopenharmony_ci		return -EBUSY;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	/*
2658c2ecf20Sopenharmony_ci	 * if tx send list is empty - return 0;
2668c2ecf20Sopenharmony_ci	 * may happen, as RX_COMPLETE handler doesn't check list emptiness.
2678c2ecf20Sopenharmony_ci	 */
2688c2ecf20Sopenharmony_ci	if (list_empty(&dev->wr_processing_list)) {
2698c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
2708c2ecf20Sopenharmony_ci		return	0;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	ipc_link = list_first_entry(&dev->wr_processing_list,
2748c2ecf20Sopenharmony_ci				    struct wr_msg_ctl_info, link);
2758c2ecf20Sopenharmony_ci	/* first 4 bytes of the data is the doorbell value (IPC header) */
2768c2ecf20Sopenharmony_ci	length = ipc_link->length - sizeof(uint32_t);
2778c2ecf20Sopenharmony_ci	doorbell_val = *(uint32_t *)ipc_link->inline_data;
2788c2ecf20Sopenharmony_ci	r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t));
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	/* If sending MNG_SYNC_FW_CLOCK, update clock again */
2818c2ecf20Sopenharmony_ci	if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG &&
2828c2ecf20Sopenharmony_ci		IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) {
2838c2ecf20Sopenharmony_ci		uint64_t usec_system, usec_utc;
2848c2ecf20Sopenharmony_ci		struct ipc_time_update_msg time_update;
2858c2ecf20Sopenharmony_ci		struct time_sync_format ts_format;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci		usec_system = ktime_to_us(ktime_get_boottime());
2888c2ecf20Sopenharmony_ci		usec_utc = ktime_to_us(ktime_get_real());
2898c2ecf20Sopenharmony_ci		ts_format.ts1_source = HOST_SYSTEM_TIME_USEC;
2908c2ecf20Sopenharmony_ci		ts_format.ts2_source = HOST_UTC_TIME_USEC;
2918c2ecf20Sopenharmony_ci		ts_format.reserved = 0;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci		time_update.primary_host_time = usec_system;
2948c2ecf20Sopenharmony_ci		time_update.secondary_host_time = usec_utc;
2958c2ecf20Sopenharmony_ci		time_update.sync_info = ts_format;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci		memcpy(r_buf, &time_update,
2988c2ecf20Sopenharmony_ci		       sizeof(struct ipc_time_update_msg));
2998c2ecf20Sopenharmony_ci	}
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++,
3028c2ecf20Sopenharmony_ci			reg_addr += 4)
3038c2ecf20Sopenharmony_ci		ish_reg_write(dev, reg_addr, r_buf[i]);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	rem = length & 0x3;
3068c2ecf20Sopenharmony_ci	if (rem > 0) {
3078c2ecf20Sopenharmony_ci		uint32_t reg = 0;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci		memcpy(&reg, &r_buf[length >> 2], rem);
3108c2ecf20Sopenharmony_ci		ish_reg_write(dev, reg_addr, reg);
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val);
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	/* Flush writes to msg registers and doorbell */
3158c2ecf20Sopenharmony_ci	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	/* Update IPC counters */
3188c2ecf20Sopenharmony_ci	++dev->ipc_tx_cnt;
3198c2ecf20Sopenharmony_ci	dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	ipc_send_compl = ipc_link->ipc_send_compl;
3228c2ecf20Sopenharmony_ci	ipc_send_compl_prm = ipc_link->ipc_send_compl_prm;
3238c2ecf20Sopenharmony_ci	list_del_init(&ipc_link->link);
3248c2ecf20Sopenharmony_ci	list_add(&ipc_link->link, &dev->wr_free_list);
3258c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	/*
3288c2ecf20Sopenharmony_ci	 * callback will be called out of spinlock,
3298c2ecf20Sopenharmony_ci	 * after ipc_link returned to free list
3308c2ecf20Sopenharmony_ci	 */
3318c2ecf20Sopenharmony_ci	if (ipc_send_compl)
3328c2ecf20Sopenharmony_ci		ipc_send_compl(ipc_send_compl_prm);
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	return 0;
3358c2ecf20Sopenharmony_ci}
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci/**
3388c2ecf20Sopenharmony_ci * write_ipc_to_queue() - write ipc msg to Tx queue
3398c2ecf20Sopenharmony_ci * @dev: ishtp device instance
3408c2ecf20Sopenharmony_ci * @ipc_send_compl: Send complete callback
3418c2ecf20Sopenharmony_ci * @ipc_send_compl_prm:	Parameter to send in complete callback
3428c2ecf20Sopenharmony_ci * @msg: Pointer to message
3438c2ecf20Sopenharmony_ci * @length: Length of message
3448c2ecf20Sopenharmony_ci *
3458c2ecf20Sopenharmony_ci * Recived msg with IPC (and upper protocol) header  and add it to the device
3468c2ecf20Sopenharmony_ci *  Tx-to-write list then try to send the first IPC waiting msg
3478c2ecf20Sopenharmony_ci *  (if DRBL is cleared)
3488c2ecf20Sopenharmony_ci * This function returns negative value for failure (means free list
3498c2ecf20Sopenharmony_ci *  is empty, or msg too long) and 0 for success.
3508c2ecf20Sopenharmony_ci *
3518c2ecf20Sopenharmony_ci * Return: 0 for success else failure code
3528c2ecf20Sopenharmony_ci */
3538c2ecf20Sopenharmony_cistatic int write_ipc_to_queue(struct ishtp_device *dev,
3548c2ecf20Sopenharmony_ci	void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
3558c2ecf20Sopenharmony_ci	unsigned char *msg, int length)
3568c2ecf20Sopenharmony_ci{
3578c2ecf20Sopenharmony_ci	struct wr_msg_ctl_info *ipc_link;
3588c2ecf20Sopenharmony_ci	unsigned long flags;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	if (length > IPC_FULL_MSG_SIZE)
3618c2ecf20Sopenharmony_ci		return -EMSGSIZE;
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
3648c2ecf20Sopenharmony_ci	if (list_empty(&dev->wr_free_list)) {
3658c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
3668c2ecf20Sopenharmony_ci		return -ENOMEM;
3678c2ecf20Sopenharmony_ci	}
3688c2ecf20Sopenharmony_ci	ipc_link = list_first_entry(&dev->wr_free_list,
3698c2ecf20Sopenharmony_ci				    struct wr_msg_ctl_info, link);
3708c2ecf20Sopenharmony_ci	list_del_init(&ipc_link->link);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	ipc_link->ipc_send_compl = ipc_send_compl;
3738c2ecf20Sopenharmony_ci	ipc_link->ipc_send_compl_prm = ipc_send_compl_prm;
3748c2ecf20Sopenharmony_ci	ipc_link->length = length;
3758c2ecf20Sopenharmony_ci	memcpy(ipc_link->inline_data, msg, length);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	list_add_tail(&ipc_link->link, &dev->wr_processing_list);
3788c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	write_ipc_from_queue(dev);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	return 0;
3838c2ecf20Sopenharmony_ci}
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci/**
3868c2ecf20Sopenharmony_ci * ipc_send_mng_msg() - Send management message
3878c2ecf20Sopenharmony_ci * @dev: ishtp device instance
3888c2ecf20Sopenharmony_ci * @msg_code: Message code
3898c2ecf20Sopenharmony_ci * @msg: Pointer to message
3908c2ecf20Sopenharmony_ci * @size: Length of message
3918c2ecf20Sopenharmony_ci *
3928c2ecf20Sopenharmony_ci * Send management message to FW
3938c2ecf20Sopenharmony_ci *
3948c2ecf20Sopenharmony_ci * Return: 0 for success else failure code
3958c2ecf20Sopenharmony_ci */
3968c2ecf20Sopenharmony_cistatic int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code,
3978c2ecf20Sopenharmony_ci	void *msg, size_t size)
3988c2ecf20Sopenharmony_ci{
3998c2ecf20Sopenharmony_ci	unsigned char	ipc_msg[IPC_FULL_MSG_SIZE];
4008c2ecf20Sopenharmony_ci	uint32_t	drbl_val = IPC_BUILD_MNG_MSG(msg_code, size);
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
4038c2ecf20Sopenharmony_ci	memcpy(ipc_msg + sizeof(uint32_t), msg, size);
4048c2ecf20Sopenharmony_ci	return	write_ipc_to_queue(dev, NULL, NULL, ipc_msg,
4058c2ecf20Sopenharmony_ci		sizeof(uint32_t) + size);
4068c2ecf20Sopenharmony_ci}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci#define WAIT_FOR_FW_RDY			0x1
4098c2ecf20Sopenharmony_ci#define WAIT_FOR_INPUT_RDY		0x2
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci/**
4128c2ecf20Sopenharmony_ci * timed_wait_for_timeout() - wait special event with timeout
4138c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
4148c2ecf20Sopenharmony_ci * @condition: indicate the condition for waiting
4158c2ecf20Sopenharmony_ci * @timeinc: time slice for every wait cycle, in ms
4168c2ecf20Sopenharmony_ci * @timeout: time in ms for timeout
4178c2ecf20Sopenharmony_ci *
4188c2ecf20Sopenharmony_ci * This function will check special event to be ready in a loop, the loop
4198c2ecf20Sopenharmony_ci * period is specificd in timeinc. Wait timeout will causes failure.
4208c2ecf20Sopenharmony_ci *
4218c2ecf20Sopenharmony_ci * Return: 0 for success else failure code
4228c2ecf20Sopenharmony_ci */
4238c2ecf20Sopenharmony_cistatic int timed_wait_for_timeout(struct ishtp_device *dev, int condition,
4248c2ecf20Sopenharmony_ci				unsigned int timeinc, unsigned int timeout)
4258c2ecf20Sopenharmony_ci{
4268c2ecf20Sopenharmony_ci	bool complete = false;
4278c2ecf20Sopenharmony_ci	int ret;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	do {
4308c2ecf20Sopenharmony_ci		if (condition == WAIT_FOR_FW_RDY) {
4318c2ecf20Sopenharmony_ci			complete = ishtp_fw_is_ready(dev);
4328c2ecf20Sopenharmony_ci		} else if (condition == WAIT_FOR_INPUT_RDY) {
4338c2ecf20Sopenharmony_ci			complete = ish_is_input_ready(dev);
4348c2ecf20Sopenharmony_ci		} else {
4358c2ecf20Sopenharmony_ci			ret = -EINVAL;
4368c2ecf20Sopenharmony_ci			goto out;
4378c2ecf20Sopenharmony_ci		}
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci		if (!complete) {
4408c2ecf20Sopenharmony_ci			unsigned long left_time;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci			left_time = msleep_interruptible(timeinc);
4438c2ecf20Sopenharmony_ci			timeout -= (timeinc - left_time);
4448c2ecf20Sopenharmony_ci		}
4458c2ecf20Sopenharmony_ci	} while (!complete && timeout > 0);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	if (complete)
4488c2ecf20Sopenharmony_ci		ret = 0;
4498c2ecf20Sopenharmony_ci	else
4508c2ecf20Sopenharmony_ci		ret = -EBUSY;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ciout:
4538c2ecf20Sopenharmony_ci	return ret;
4548c2ecf20Sopenharmony_ci}
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci#define TIME_SLICE_FOR_FW_RDY_MS		100
4578c2ecf20Sopenharmony_ci#define TIME_SLICE_FOR_INPUT_RDY_MS		100
4588c2ecf20Sopenharmony_ci#define TIMEOUT_FOR_FW_RDY_MS			2000
4598c2ecf20Sopenharmony_ci#define TIMEOUT_FOR_INPUT_RDY_MS		2000
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci/**
4628c2ecf20Sopenharmony_ci * ish_fw_reset_handler() - FW reset handler
4638c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
4648c2ecf20Sopenharmony_ci *
4658c2ecf20Sopenharmony_ci * Handle FW reset
4668c2ecf20Sopenharmony_ci *
4678c2ecf20Sopenharmony_ci * Return: 0 for success else failure code
4688c2ecf20Sopenharmony_ci */
4698c2ecf20Sopenharmony_cistatic int ish_fw_reset_handler(struct ishtp_device *dev)
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci	uint32_t	reset_id;
4728c2ecf20Sopenharmony_ci	unsigned long	flags;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	/* Read reset ID */
4758c2ecf20Sopenharmony_ci	reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_ci	/* Clear IPC output queue */
4788c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
4798c2ecf20Sopenharmony_ci	list_splice_init(&dev->wr_processing_list, &dev->wr_free_list);
4808c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	/* ISHTP notification in IPC_RESET */
4838c2ecf20Sopenharmony_ci	ishtp_reset_handler(dev);
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	if (!ish_is_input_ready(dev))
4868c2ecf20Sopenharmony_ci		timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
4878c2ecf20Sopenharmony_ci			TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	/* ISH FW is dead */
4908c2ecf20Sopenharmony_ci	if (!ish_is_input_ready(dev))
4918c2ecf20Sopenharmony_ci		return	-EPIPE;
4928c2ecf20Sopenharmony_ci	/*
4938c2ecf20Sopenharmony_ci	 * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
4948c2ecf20Sopenharmony_ci	 * RESET_NOTIFY_ACK - FW will be checking for it
4958c2ecf20Sopenharmony_ci	 */
4968c2ecf20Sopenharmony_ci	ish_set_host_rdy(dev);
4978c2ecf20Sopenharmony_ci	/* Send RESET_NOTIFY_ACK (with reset_id) */
4988c2ecf20Sopenharmony_ci	ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id,
4998c2ecf20Sopenharmony_ci			 sizeof(uint32_t));
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_ci	/* Wait for ISH FW'es ILUP and ISHTP_READY */
5028c2ecf20Sopenharmony_ci	timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
5038c2ecf20Sopenharmony_ci			TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
5048c2ecf20Sopenharmony_ci	if (!ishtp_fw_is_ready(dev)) {
5058c2ecf20Sopenharmony_ci		/* ISH FW is dead */
5068c2ecf20Sopenharmony_ci		uint32_t	ish_status;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci		ish_status = _ish_read_fw_sts_reg(dev);
5098c2ecf20Sopenharmony_ci		dev_err(dev->devc,
5108c2ecf20Sopenharmony_ci			"[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n",
5118c2ecf20Sopenharmony_ci			ish_status);
5128c2ecf20Sopenharmony_ci		return -ENODEV;
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci	return	0;
5158c2ecf20Sopenharmony_ci}
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci#define TIMEOUT_FOR_HW_RDY_MS			300
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci/**
5208c2ecf20Sopenharmony_ci * ish_fw_reset_work_fn() - FW reset worker function
5218c2ecf20Sopenharmony_ci * @unused: not used
5228c2ecf20Sopenharmony_ci *
5238c2ecf20Sopenharmony_ci * Call ish_fw_reset_handler to complete FW reset
5248c2ecf20Sopenharmony_ci */
5258c2ecf20Sopenharmony_cistatic void fw_reset_work_fn(struct work_struct *unused)
5268c2ecf20Sopenharmony_ci{
5278c2ecf20Sopenharmony_ci	int	rv;
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	rv = ish_fw_reset_handler(ishtp_dev);
5308c2ecf20Sopenharmony_ci	if (!rv) {
5318c2ecf20Sopenharmony_ci		/* ISH is ILUP & ISHTP-ready. Restart ISHTP */
5328c2ecf20Sopenharmony_ci		msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS);
5338c2ecf20Sopenharmony_ci		ishtp_dev->recvd_hw_ready = 1;
5348c2ecf20Sopenharmony_ci		wake_up_interruptible(&ishtp_dev->wait_hw_ready);
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci		/* ISHTP notification in IPC_RESET sequence completion */
5378c2ecf20Sopenharmony_ci		ishtp_reset_compl_handler(ishtp_dev);
5388c2ecf20Sopenharmony_ci	} else
5398c2ecf20Sopenharmony_ci		dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
5408c2ecf20Sopenharmony_ci			rv);
5418c2ecf20Sopenharmony_ci}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci/**
5448c2ecf20Sopenharmony_ci * _ish_sync_fw_clock() -Sync FW clock with the OS clock
5458c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
5468c2ecf20Sopenharmony_ci *
5478c2ecf20Sopenharmony_ci * Sync FW and OS time
5488c2ecf20Sopenharmony_ci */
5498c2ecf20Sopenharmony_cistatic void _ish_sync_fw_clock(struct ishtp_device *dev)
5508c2ecf20Sopenharmony_ci{
5518c2ecf20Sopenharmony_ci	static unsigned long	prev_sync;
5528c2ecf20Sopenharmony_ci	uint64_t	usec;
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	if (prev_sync && jiffies - prev_sync < 20 * HZ)
5558c2ecf20Sopenharmony_ci		return;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	prev_sync = jiffies;
5588c2ecf20Sopenharmony_ci	usec = ktime_to_us(ktime_get_boottime());
5598c2ecf20Sopenharmony_ci	ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t));
5608c2ecf20Sopenharmony_ci}
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_ci/**
5638c2ecf20Sopenharmony_ci * recv_ipc() - Receive and process IPC management messages
5648c2ecf20Sopenharmony_ci * @dev: ishtp device instance
5658c2ecf20Sopenharmony_ci * @doorbell_val: doorbell value
5668c2ecf20Sopenharmony_ci *
5678c2ecf20Sopenharmony_ci * This function runs in ISR context.
5688c2ecf20Sopenharmony_ci * NOTE: Any other mng command than reset_notify and reset_notify_ack
5698c2ecf20Sopenharmony_ci * won't wake BH handler
5708c2ecf20Sopenharmony_ci */
5718c2ecf20Sopenharmony_cistatic void	recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val)
5728c2ecf20Sopenharmony_ci{
5738c2ecf20Sopenharmony_ci	uint32_t	mng_cmd;
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci	mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val);
5768c2ecf20Sopenharmony_ci
5778c2ecf20Sopenharmony_ci	switch (mng_cmd) {
5788c2ecf20Sopenharmony_ci	default:
5798c2ecf20Sopenharmony_ci		break;
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	case MNG_RX_CMPL_INDICATION:
5828c2ecf20Sopenharmony_ci		if (dev->suspend_flag) {
5838c2ecf20Sopenharmony_ci			dev->suspend_flag = 0;
5848c2ecf20Sopenharmony_ci			wake_up_interruptible(&dev->suspend_wait);
5858c2ecf20Sopenharmony_ci		}
5868c2ecf20Sopenharmony_ci		if (dev->resume_flag) {
5878c2ecf20Sopenharmony_ci			dev->resume_flag = 0;
5888c2ecf20Sopenharmony_ci			wake_up_interruptible(&dev->resume_wait);
5898c2ecf20Sopenharmony_ci		}
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci		write_ipc_from_queue(dev);
5928c2ecf20Sopenharmony_ci		break;
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	case MNG_RESET_NOTIFY:
5958c2ecf20Sopenharmony_ci		if (!ishtp_dev) {
5968c2ecf20Sopenharmony_ci			ishtp_dev = dev;
5978c2ecf20Sopenharmony_ci			INIT_WORK(&fw_reset_work, fw_reset_work_fn);
5988c2ecf20Sopenharmony_ci		}
5998c2ecf20Sopenharmony_ci		schedule_work(&fw_reset_work);
6008c2ecf20Sopenharmony_ci		break;
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_ci	case MNG_RESET_NOTIFY_ACK:
6038c2ecf20Sopenharmony_ci		dev->recvd_hw_ready = 1;
6048c2ecf20Sopenharmony_ci		wake_up_interruptible(&dev->wait_hw_ready);
6058c2ecf20Sopenharmony_ci		break;
6068c2ecf20Sopenharmony_ci	}
6078c2ecf20Sopenharmony_ci}
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci/**
6108c2ecf20Sopenharmony_ci * ish_irq_handler() - ISH IRQ handler
6118c2ecf20Sopenharmony_ci * @irq: irq number
6128c2ecf20Sopenharmony_ci * @dev_id: ishtp device pointer
6138c2ecf20Sopenharmony_ci *
6148c2ecf20Sopenharmony_ci * ISH IRQ handler. If interrupt is generated and is for ISH it will process
6158c2ecf20Sopenharmony_ci * the interrupt.
6168c2ecf20Sopenharmony_ci */
6178c2ecf20Sopenharmony_ciirqreturn_t ish_irq_handler(int irq, void *dev_id)
6188c2ecf20Sopenharmony_ci{
6198c2ecf20Sopenharmony_ci	struct ishtp_device	*dev = dev_id;
6208c2ecf20Sopenharmony_ci	uint32_t	doorbell_val;
6218c2ecf20Sopenharmony_ci	bool	interrupt_generated;
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci	/* Check that it's interrupt from ISH (may be shared) */
6248c2ecf20Sopenharmony_ci	interrupt_generated = check_generated_interrupt(dev);
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	if (!interrupt_generated)
6278c2ecf20Sopenharmony_ci		return IRQ_NONE;
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_ci	doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL);
6308c2ecf20Sopenharmony_ci	if (!IPC_IS_BUSY(doorbell_val))
6318c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	if (dev->dev_state == ISHTP_DEV_DISABLED)
6348c2ecf20Sopenharmony_ci		return	IRQ_HANDLED;
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci	/* Sanity check: IPC dgram length in header */
6378c2ecf20Sopenharmony_ci	if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) {
6388c2ecf20Sopenharmony_ci		dev_err(dev->devc,
6398c2ecf20Sopenharmony_ci			"IPC hdr - bad length: %u; dropped\n",
6408c2ecf20Sopenharmony_ci			(unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val));
6418c2ecf20Sopenharmony_ci		goto	eoi;
6428c2ecf20Sopenharmony_ci	}
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) {
6458c2ecf20Sopenharmony_ci	default:
6468c2ecf20Sopenharmony_ci		break;
6478c2ecf20Sopenharmony_ci	case IPC_PROTOCOL_MNG:
6488c2ecf20Sopenharmony_ci		recv_ipc(dev, doorbell_val);
6498c2ecf20Sopenharmony_ci		break;
6508c2ecf20Sopenharmony_ci	case IPC_PROTOCOL_ISHTP:
6518c2ecf20Sopenharmony_ci		ishtp_recv(dev);
6528c2ecf20Sopenharmony_ci		break;
6538c2ecf20Sopenharmony_ci	}
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_cieoi:
6568c2ecf20Sopenharmony_ci	/* Update IPC counters */
6578c2ecf20Sopenharmony_ci	++dev->ipc_rx_cnt;
6588c2ecf20Sopenharmony_ci	dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
6598c2ecf20Sopenharmony_ci
6608c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
6618c2ecf20Sopenharmony_ci	/* Flush write to doorbell */
6628c2ecf20Sopenharmony_ci	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
6638c2ecf20Sopenharmony_ci
6648c2ecf20Sopenharmony_ci	return	IRQ_HANDLED;
6658c2ecf20Sopenharmony_ci}
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ci/**
6688c2ecf20Sopenharmony_ci * ish_disable_dma() - disable dma communication between host and ISHFW
6698c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
6708c2ecf20Sopenharmony_ci *
6718c2ecf20Sopenharmony_ci * Clear the dma enable bit and wait for dma inactive.
6728c2ecf20Sopenharmony_ci *
6738c2ecf20Sopenharmony_ci * Return: 0 for success else error code.
6748c2ecf20Sopenharmony_ci */
6758c2ecf20Sopenharmony_ciint ish_disable_dma(struct ishtp_device *dev)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	unsigned int	dma_delay;
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	/* Clear the dma enable bit */
6808c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci	/* wait for dma inactive */
6838c2ecf20Sopenharmony_ci	for (dma_delay = 0; dma_delay < MAX_DMA_DELAY &&
6848c2ecf20Sopenharmony_ci		_ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA);
6858c2ecf20Sopenharmony_ci		dma_delay += 5)
6868c2ecf20Sopenharmony_ci		mdelay(5);
6878c2ecf20Sopenharmony_ci
6888c2ecf20Sopenharmony_ci	if (dma_delay >= MAX_DMA_DELAY) {
6898c2ecf20Sopenharmony_ci		dev_err(dev->devc,
6908c2ecf20Sopenharmony_ci			"Wait for DMA inactive timeout\n");
6918c2ecf20Sopenharmony_ci		return	-EBUSY;
6928c2ecf20Sopenharmony_ci	}
6938c2ecf20Sopenharmony_ci
6948c2ecf20Sopenharmony_ci	return 0;
6958c2ecf20Sopenharmony_ci}
6968c2ecf20Sopenharmony_ci
6978c2ecf20Sopenharmony_ci/**
6988c2ecf20Sopenharmony_ci * ish_wakeup() - wakeup ishfw from waiting-for-host state
6998c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
7008c2ecf20Sopenharmony_ci *
7018c2ecf20Sopenharmony_ci * Set the dma enable bit and send a void message to FW,
7028c2ecf20Sopenharmony_ci * it wil wakeup FW from waiting-for-host state.
7038c2ecf20Sopenharmony_ci */
7048c2ecf20Sopenharmony_cistatic void ish_wakeup(struct ishtp_device *dev)
7058c2ecf20Sopenharmony_ci{
7068c2ecf20Sopenharmony_ci	/* Set dma enable bit */
7078c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	/*
7108c2ecf20Sopenharmony_ci	 * Send 0 IPC message so that ISH FW wakes up if it was already
7118c2ecf20Sopenharmony_ci	 * asleep.
7128c2ecf20Sopenharmony_ci	 */
7138c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	/* Flush writes to doorbell and REMAP2 */
7168c2ecf20Sopenharmony_ci	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
7178c2ecf20Sopenharmony_ci}
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci/**
7208c2ecf20Sopenharmony_ci * _ish_hw_reset() - HW reset
7218c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
7228c2ecf20Sopenharmony_ci *
7238c2ecf20Sopenharmony_ci * Reset ISH HW to recover if any error
7248c2ecf20Sopenharmony_ci *
7258c2ecf20Sopenharmony_ci * Return: 0 for success else error fault code
7268c2ecf20Sopenharmony_ci */
7278c2ecf20Sopenharmony_cistatic int _ish_hw_reset(struct ishtp_device *dev)
7288c2ecf20Sopenharmony_ci{
7298c2ecf20Sopenharmony_ci	struct pci_dev *pdev = dev->pdev;
7308c2ecf20Sopenharmony_ci	int	rv;
7318c2ecf20Sopenharmony_ci	uint16_t csr;
7328c2ecf20Sopenharmony_ci
7338c2ecf20Sopenharmony_ci	if (!pdev)
7348c2ecf20Sopenharmony_ci		return	-ENODEV;
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	rv = pci_reset_function(pdev);
7378c2ecf20Sopenharmony_ci	if (!rv)
7388c2ecf20Sopenharmony_ci		dev->dev_state = ISHTP_DEV_RESETTING;
7398c2ecf20Sopenharmony_ci
7408c2ecf20Sopenharmony_ci	if (!pdev->pm_cap) {
7418c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Can't reset - no PM caps\n");
7428c2ecf20Sopenharmony_ci		return	-EINVAL;
7438c2ecf20Sopenharmony_ci	}
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_ci	/* Disable dma communication between FW and host */
7468c2ecf20Sopenharmony_ci	if (ish_disable_dma(dev)) {
7478c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
7488c2ecf20Sopenharmony_ci			"Can't reset - stuck with DMA in-progress\n");
7498c2ecf20Sopenharmony_ci		return	-EBUSY;
7508c2ecf20Sopenharmony_ci	}
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr);
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	csr &= ~PCI_PM_CTRL_STATE_MASK;
7558c2ecf20Sopenharmony_ci	csr |= PCI_D3hot;
7568c2ecf20Sopenharmony_ci	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
7578c2ecf20Sopenharmony_ci
7588c2ecf20Sopenharmony_ci	mdelay(pdev->d3hot_delay);
7598c2ecf20Sopenharmony_ci
7608c2ecf20Sopenharmony_ci	csr &= ~PCI_PM_CTRL_STATE_MASK;
7618c2ecf20Sopenharmony_ci	csr |= PCI_D0;
7628c2ecf20Sopenharmony_ci	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
7638c2ecf20Sopenharmony_ci
7648c2ecf20Sopenharmony_ci	/* Now we can enable ISH DMA operation and wakeup ISHFW */
7658c2ecf20Sopenharmony_ci	ish_wakeup(dev);
7668c2ecf20Sopenharmony_ci
7678c2ecf20Sopenharmony_ci	return	0;
7688c2ecf20Sopenharmony_ci}
7698c2ecf20Sopenharmony_ci
7708c2ecf20Sopenharmony_ci/**
7718c2ecf20Sopenharmony_ci * _ish_ipc_reset() - IPC reset
7728c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
7738c2ecf20Sopenharmony_ci *
7748c2ecf20Sopenharmony_ci * Resets host and fw IPC and upper layers
7758c2ecf20Sopenharmony_ci *
7768c2ecf20Sopenharmony_ci * Return: 0 for success else error fault code
7778c2ecf20Sopenharmony_ci */
7788c2ecf20Sopenharmony_cistatic int _ish_ipc_reset(struct ishtp_device *dev)
7798c2ecf20Sopenharmony_ci{
7808c2ecf20Sopenharmony_ci	struct ipc_rst_payload_type ipc_mng_msg;
7818c2ecf20Sopenharmony_ci	int	rv = 0;
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci	ipc_mng_msg.reset_id = 1;
7848c2ecf20Sopenharmony_ci	ipc_mng_msg.reserved = 0;
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci	set_host_ready(dev);
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	/* Clear the incoming doorbell */
7898c2ecf20Sopenharmony_ci	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
7908c2ecf20Sopenharmony_ci	/* Flush write to doorbell */
7918c2ecf20Sopenharmony_ci	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci	dev->recvd_hw_ready = 0;
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci	/* send message */
7968c2ecf20Sopenharmony_ci	rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg,
7978c2ecf20Sopenharmony_ci		sizeof(struct ipc_rst_payload_type));
7988c2ecf20Sopenharmony_ci	if (rv) {
7998c2ecf20Sopenharmony_ci		dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n");
8008c2ecf20Sopenharmony_ci		return	rv;
8018c2ecf20Sopenharmony_ci	}
8028c2ecf20Sopenharmony_ci
8038c2ecf20Sopenharmony_ci	wait_event_interruptible_timeout(dev->wait_hw_ready,
8048c2ecf20Sopenharmony_ci					 dev->recvd_hw_ready, 2 * HZ);
8058c2ecf20Sopenharmony_ci	if (!dev->recvd_hw_ready) {
8068c2ecf20Sopenharmony_ci		dev_err(dev->devc, "Timed out waiting for HW ready\n");
8078c2ecf20Sopenharmony_ci		rv = -ENODEV;
8088c2ecf20Sopenharmony_ci	}
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci	return rv;
8118c2ecf20Sopenharmony_ci}
8128c2ecf20Sopenharmony_ci
8138c2ecf20Sopenharmony_ci/**
8148c2ecf20Sopenharmony_ci * ish_hw_start() -Start ISH HW
8158c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
8168c2ecf20Sopenharmony_ci *
8178c2ecf20Sopenharmony_ci * Set host to ready state and wait for FW reset
8188c2ecf20Sopenharmony_ci *
8198c2ecf20Sopenharmony_ci * Return: 0 for success else error fault code
8208c2ecf20Sopenharmony_ci */
8218c2ecf20Sopenharmony_ciint ish_hw_start(struct ishtp_device *dev)
8228c2ecf20Sopenharmony_ci{
8238c2ecf20Sopenharmony_ci	ish_set_host_rdy(dev);
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	set_host_ready(dev);
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_ci	/* After that we can enable ISH DMA operation and wakeup ISHFW */
8288c2ecf20Sopenharmony_ci	ish_wakeup(dev);
8298c2ecf20Sopenharmony_ci
8308c2ecf20Sopenharmony_ci	/* wait for FW-initiated reset flow */
8318c2ecf20Sopenharmony_ci	if (!dev->recvd_hw_ready)
8328c2ecf20Sopenharmony_ci		wait_event_interruptible_timeout(dev->wait_hw_ready,
8338c2ecf20Sopenharmony_ci						 dev->recvd_hw_ready,
8348c2ecf20Sopenharmony_ci						 10 * HZ);
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci	if (!dev->recvd_hw_ready) {
8378c2ecf20Sopenharmony_ci		dev_err(dev->devc,
8388c2ecf20Sopenharmony_ci			"[ishtp-ish]: Timed out waiting for FW-initiated reset\n");
8398c2ecf20Sopenharmony_ci		return	-ENODEV;
8408c2ecf20Sopenharmony_ci	}
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	return 0;
8438c2ecf20Sopenharmony_ci}
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci/**
8468c2ecf20Sopenharmony_ci * ish_ipc_get_header() -Get doorbell value
8478c2ecf20Sopenharmony_ci * @dev: ishtp device pointer
8488c2ecf20Sopenharmony_ci * @length: length of message
8498c2ecf20Sopenharmony_ci * @busy: busy status
8508c2ecf20Sopenharmony_ci *
8518c2ecf20Sopenharmony_ci * Get door bell value from message header
8528c2ecf20Sopenharmony_ci *
8538c2ecf20Sopenharmony_ci * Return: door bell value
8548c2ecf20Sopenharmony_ci */
8558c2ecf20Sopenharmony_cistatic uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
8568c2ecf20Sopenharmony_ci				   int busy)
8578c2ecf20Sopenharmony_ci{
8588c2ecf20Sopenharmony_ci	uint32_t drbl_val;
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci	drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy);
8618c2ecf20Sopenharmony_ci
8628c2ecf20Sopenharmony_ci	return drbl_val;
8638c2ecf20Sopenharmony_ci}
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_cistatic const struct ishtp_hw_ops ish_hw_ops = {
8668c2ecf20Sopenharmony_ci	.hw_reset = _ish_hw_reset,
8678c2ecf20Sopenharmony_ci	.ipc_reset = _ish_ipc_reset,
8688c2ecf20Sopenharmony_ci	.ipc_get_header = ish_ipc_get_header,
8698c2ecf20Sopenharmony_ci	.ishtp_read = _ishtp_read,
8708c2ecf20Sopenharmony_ci	.write = write_ipc_to_queue,
8718c2ecf20Sopenharmony_ci	.get_fw_status = _ish_read_fw_sts_reg,
8728c2ecf20Sopenharmony_ci	.sync_fw_clock = _ish_sync_fw_clock,
8738c2ecf20Sopenharmony_ci	.ishtp_read_hdr = _ishtp_read_hdr
8748c2ecf20Sopenharmony_ci};
8758c2ecf20Sopenharmony_ci
8768c2ecf20Sopenharmony_ci/**
8778c2ecf20Sopenharmony_ci * ish_dev_init() -Initialize ISH devoce
8788c2ecf20Sopenharmony_ci * @pdev: PCI device
8798c2ecf20Sopenharmony_ci *
8808c2ecf20Sopenharmony_ci * Allocate ISHTP device and initialize IPC processing
8818c2ecf20Sopenharmony_ci *
8828c2ecf20Sopenharmony_ci * Return: ISHTP device instance on success else NULL
8838c2ecf20Sopenharmony_ci */
8848c2ecf20Sopenharmony_cistruct ishtp_device *ish_dev_init(struct pci_dev *pdev)
8858c2ecf20Sopenharmony_ci{
8868c2ecf20Sopenharmony_ci	struct ishtp_device *dev;
8878c2ecf20Sopenharmony_ci	int	i;
8888c2ecf20Sopenharmony_ci
8898c2ecf20Sopenharmony_ci	dev = devm_kzalloc(&pdev->dev,
8908c2ecf20Sopenharmony_ci			   sizeof(struct ishtp_device) + sizeof(struct ish_hw),
8918c2ecf20Sopenharmony_ci			   GFP_KERNEL);
8928c2ecf20Sopenharmony_ci	if (!dev)
8938c2ecf20Sopenharmony_ci		return NULL;
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	ishtp_device_init(dev);
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_ci	init_waitqueue_head(&dev->wait_hw_ready);
8988c2ecf20Sopenharmony_ci
8998c2ecf20Sopenharmony_ci	spin_lock_init(&dev->wr_processing_spinlock);
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_ci	/* Init IPC processing and free lists */
9028c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&dev->wr_processing_list);
9038c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&dev->wr_free_list);
9048c2ecf20Sopenharmony_ci	for (i = 0; i < IPC_TX_FIFO_SIZE; i++) {
9058c2ecf20Sopenharmony_ci		struct wr_msg_ctl_info	*tx_buf;
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci		tx_buf = devm_kzalloc(&pdev->dev,
9088c2ecf20Sopenharmony_ci				      sizeof(struct wr_msg_ctl_info),
9098c2ecf20Sopenharmony_ci				      GFP_KERNEL);
9108c2ecf20Sopenharmony_ci		if (!tx_buf) {
9118c2ecf20Sopenharmony_ci			/*
9128c2ecf20Sopenharmony_ci			 * IPC buffers may be limited or not available
9138c2ecf20Sopenharmony_ci			 * at all - although this shouldn't happen
9148c2ecf20Sopenharmony_ci			 */
9158c2ecf20Sopenharmony_ci			dev_err(dev->devc,
9168c2ecf20Sopenharmony_ci				"[ishtp-ish]: failure in Tx FIFO allocations (%d)\n",
9178c2ecf20Sopenharmony_ci				i);
9188c2ecf20Sopenharmony_ci			break;
9198c2ecf20Sopenharmony_ci		}
9208c2ecf20Sopenharmony_ci		list_add_tail(&tx_buf->link, &dev->wr_free_list);
9218c2ecf20Sopenharmony_ci	}
9228c2ecf20Sopenharmony_ci
9238c2ecf20Sopenharmony_ci	dev->ops = &ish_hw_ops;
9248c2ecf20Sopenharmony_ci	dev->devc = &pdev->dev;
9258c2ecf20Sopenharmony_ci	dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr);
9268c2ecf20Sopenharmony_ci	return dev;
9278c2ecf20Sopenharmony_ci}
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_ci/**
9308c2ecf20Sopenharmony_ci * ish_device_disable() - Disable ISH device
9318c2ecf20Sopenharmony_ci * @dev: ISHTP device pointer
9328c2ecf20Sopenharmony_ci *
9338c2ecf20Sopenharmony_ci * Disable ISH by clearing host ready to inform firmware.
9348c2ecf20Sopenharmony_ci */
9358c2ecf20Sopenharmony_civoid	ish_device_disable(struct ishtp_device *dev)
9368c2ecf20Sopenharmony_ci{
9378c2ecf20Sopenharmony_ci	struct pci_dev *pdev = dev->pdev;
9388c2ecf20Sopenharmony_ci
9398c2ecf20Sopenharmony_ci	if (!pdev)
9408c2ecf20Sopenharmony_ci		return;
9418c2ecf20Sopenharmony_ci
9428c2ecf20Sopenharmony_ci	/* Disable dma communication between FW and host */
9438c2ecf20Sopenharmony_ci	if (ish_disable_dma(dev)) {
9448c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
9458c2ecf20Sopenharmony_ci			"Can't reset - stuck with DMA in-progress\n");
9468c2ecf20Sopenharmony_ci		return;
9478c2ecf20Sopenharmony_ci	}
9488c2ecf20Sopenharmony_ci
9498c2ecf20Sopenharmony_ci	/* Put ISH to D3hot state for power saving */
9508c2ecf20Sopenharmony_ci	pci_set_power_state(pdev, PCI_D3hot);
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_ci	dev->dev_state = ISHTP_DEV_DISABLED;
9538c2ecf20Sopenharmony_ci	ish_clr_host_rdy(dev);
9548c2ecf20Sopenharmony_ci}
955