diff options
Diffstat (limited to 'drivers/misc/mei/hw-txe.c')
| -rw-r--r-- | drivers/misc/mei/hw-txe.c | 1190 | 
1 files changed, 1190 insertions, 0 deletions
diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c new file mode 100644 index 00000000000..93273783dec --- /dev/null +++ b/drivers/misc/mei/hw-txe.c @@ -0,0 +1,1190 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2013-2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/pci.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/irqreturn.h> + +#include <linux/mei.h> + +#include "mei_dev.h" +#include "hw-txe.h" +#include "client.h" +#include "hbm.h" + +/** + * mei_txe_reg_read - Reads 32bit data from the device + * + * @base_addr: registers base address + * @offset: register offset + * + */ +static inline u32 mei_txe_reg_read(void __iomem *base_addr, +					unsigned long offset) +{ +	return ioread32(base_addr + offset); +} + +/** + * mei_txe_reg_write - Writes 32bit data to the device + * + * @base_addr: registers base address + * @offset: register offset + * @value: the value to write + */ +static inline void mei_txe_reg_write(void __iomem *base_addr, +				unsigned long offset, u32 value) +{ +	iowrite32(value, base_addr + offset); +} + +/** + * mei_txe_sec_reg_read_silent - Reads 32bit data from the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * + * Doesn't check for aliveness while Reads 32bit data from the SeC BAR + */ +static inline u32 mei_txe_sec_reg_read_silent(struct mei_txe_hw *hw, +				unsigned long offset) +{ +	return mei_txe_reg_read(hw->mem_addr[SEC_BAR], offset); +} + +/** + * mei_txe_sec_reg_read - Reads 32bit data from the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * + * Reads 32bit data from the SeC BAR and shout loud if aliveness is not set + */ +static inline u32 mei_txe_sec_reg_read(struct mei_txe_hw *hw, +				unsigned long offset) +{ +	WARN(!hw->aliveness, "sec read: aliveness not asserted\n"); +	return mei_txe_sec_reg_read_silent(hw, offset); +} +/** + * mei_txe_sec_reg_write_silent - Writes 32bit data to the SeC BAR + *   doesn't check for aliveness + * + * @dev: the device structure + * @offset: register offset + * @value: value to write + * + * Doesn't check for aliveness while writes 32bit data from to the SeC BAR + */ +static inline void mei_txe_sec_reg_write_silent(struct mei_txe_hw *hw, +				unsigned long offset, u32 value) +{ +	mei_txe_reg_write(hw->mem_addr[SEC_BAR], offset, value); +} + +/** + * mei_txe_sec_reg_write - Writes 32bit data to the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * @value: value to write + * + * Writes 32bit data from the SeC BAR and shout loud if aliveness is not set + */ +static inline void mei_txe_sec_reg_write(struct mei_txe_hw *hw, +				unsigned long offset, u32 value) +{ +	WARN(!hw->aliveness, "sec write: aliveness not asserted\n"); +	mei_txe_sec_reg_write_silent(hw, offset, value); +} +/** + * mei_txe_br_reg_read - Reads 32bit data from the Bridge BAR + * + * @hw: the device structure + * @offset: offset from which to read the data + * + */ +static inline u32 mei_txe_br_reg_read(struct mei_txe_hw *hw, +				unsigned long offset) +{ +	return mei_txe_reg_read(hw->mem_addr[BRIDGE_BAR], offset); +} + +/** + * mei_txe_br_reg_write - Writes 32bit data to the Bridge BAR + * + * @hw: the device structure + * @offset: offset from which to write the data + * @value: the byte to write + */ +static inline void mei_txe_br_reg_write(struct mei_txe_hw *hw, +				unsigned long offset, u32 value) +{ +	mei_txe_reg_write(hw->mem_addr[BRIDGE_BAR], offset, value); +} + +/** + * mei_txe_aliveness_set - request for aliveness change + * + * @dev: the device structure + * @req: requested aliveness value + * + * Request for aliveness change and returns true if the change is + *   really needed and false if aliveness is already + *   in the requested state + * Requires device lock to be held + */ +static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) +{ + +	struct mei_txe_hw *hw = to_txe_hw(dev); +	bool do_req = hw->aliveness != req; + +	dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n", +				hw->aliveness, req); +	if (do_req) { +		dev->pg_event = MEI_PG_EVENT_WAIT; +		mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req); +	} +	return do_req; +} + + +/** + * mei_txe_aliveness_req_get - get aliveness requested register value + * + * @dev: the device structure + * + * Extract HICR_HOST_ALIVENESS_RESP_ACK bit from + * from HICR_HOST_ALIVENESS_REQ register value + */ +static u32 mei_txe_aliveness_req_get(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 reg; +	reg = mei_txe_br_reg_read(hw, SICR_HOST_ALIVENESS_REQ_REG); +	return reg & SICR_HOST_ALIVENESS_REQ_REQUESTED; +} + +/** + * mei_txe_aliveness_get - get aliveness response register value + * @dev: the device structure + * + * Extract HICR_HOST_ALIVENESS_RESP_ACK bit + * from HICR_HOST_ALIVENESS_RESP register value + */ +static u32 mei_txe_aliveness_get(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 reg; +	reg = mei_txe_br_reg_read(hw, HICR_HOST_ALIVENESS_RESP_REG); +	return reg & HICR_HOST_ALIVENESS_RESP_ACK; +} + +/** + * mei_txe_aliveness_poll - waits for aliveness to settle + * + * @dev: the device structure + * @expected: expected aliveness value + * + * Polls for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set + * returns > 0 if the expected value was received, -ETIME otherwise + */ +static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	int t = 0; + +	do { +		hw->aliveness = mei_txe_aliveness_get(dev); +		if (hw->aliveness == expected) { +			dev->pg_event = MEI_PG_EVENT_IDLE; +			dev_dbg(&dev->pdev->dev, +				"aliveness settled after %d msecs\n", t); +			return t; +		} +		mutex_unlock(&dev->device_lock); +		msleep(MSEC_PER_SEC / 5); +		mutex_lock(&dev->device_lock); +		t += MSEC_PER_SEC / 5; +	} while (t < SEC_ALIVENESS_WAIT_TIMEOUT); + +	dev->pg_event = MEI_PG_EVENT_IDLE; +	dev_err(&dev->pdev->dev, "aliveness timed out\n"); +	return -ETIME; +} + +/** + * mei_txe_aliveness_wait - waits for aliveness to settle + * + * @dev: the device structure + * @expected: expected aliveness value + * + * Waits for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set + * returns returns 0 on success and < 0 otherwise + */ +static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	const unsigned long timeout = +			msecs_to_jiffies(SEC_ALIVENESS_WAIT_TIMEOUT); +	long err; +	int ret; + +	hw->aliveness = mei_txe_aliveness_get(dev); +	if (hw->aliveness == expected) +		return 0; + +	mutex_unlock(&dev->device_lock); +	err = wait_event_timeout(hw->wait_aliveness_resp, +			dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); +	mutex_lock(&dev->device_lock); + +	hw->aliveness = mei_txe_aliveness_get(dev); +	ret = hw->aliveness == expected ? 0 : -ETIME; + +	if (ret) +		dev_warn(&dev->pdev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", +			err, hw->aliveness, dev->pg_event); +	else +		dev_dbg(&dev->pdev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", +			jiffies_to_msecs(timeout - err), +			hw->aliveness, dev->pg_event); + +	dev->pg_event = MEI_PG_EVENT_IDLE; +	return ret; +} + +/** + * mei_txe_aliveness_set_sync - sets an wait for aliveness to complete + * + * @dev: the device structure + * + * returns returns 0 on success and < 0 otherwise + */ +int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) +{ +	if (mei_txe_aliveness_set(dev, req)) +		return mei_txe_aliveness_wait(dev, req); +	return 0; +} + +/** + * mei_txe_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_txe_pg_is_enabled(struct mei_device *dev) +{ +	return true; +} + +/** + * mei_txe_pg_state  - translate aliveness register value + *   to the mei power gating state + * + * @dev: the device structure + * + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_txe_pg_state(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	return hw->aliveness ? MEI_PG_OFF : MEI_PG_ON; +} + +/** + * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt + * + * @dev: the device structure + */ +static void mei_txe_input_ready_interrupt_enable(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 hintmsk; +	/* Enable the SEC_IPC_HOST_INT_MASK_IN_RDY interrupt */ +	hintmsk = mei_txe_sec_reg_read(hw, SEC_IPC_HOST_INT_MASK_REG); +	hintmsk |= SEC_IPC_HOST_INT_MASK_IN_RDY; +	mei_txe_sec_reg_write(hw, SEC_IPC_HOST_INT_MASK_REG, hintmsk); +} + +/** + * mei_txe_input_doorbell_set + *   - Sets bit 0 in SEC_IPC_INPUT_DOORBELL.IPC_INPUT_DOORBELL. + * @dev: the device structure + */ +static void mei_txe_input_doorbell_set(struct mei_txe_hw *hw) +{ +	/* Clear the interrupt cause */ +	clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause); +	mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_DOORBELL_REG, 1); +} + +/** + * mei_txe_output_ready_set - Sets the SICR_SEC_IPC_OUTPUT_STATUS bit to 1 + * + * @dev: the device structure + */ +static void mei_txe_output_ready_set(struct mei_txe_hw *hw) +{ +	mei_txe_br_reg_write(hw, +			SICR_SEC_IPC_OUTPUT_STATUS_REG, +			SEC_IPC_OUTPUT_STATUS_RDY); +} + +/** + * mei_txe_is_input_ready - check if TXE is ready for receiving data + * + * @dev: the device structure + */ +static bool mei_txe_is_input_ready(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 status; +	status = mei_txe_sec_reg_read(hw, SEC_IPC_INPUT_STATUS_REG); +	return !!(SEC_IPC_INPUT_STATUS_RDY & status); +} + +/** + * mei_txe_intr_clear - clear all interrupts + * + * @dev: the device structure + */ +static inline void mei_txe_intr_clear(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_sec_reg_write_silent(hw, SEC_IPC_HOST_INT_STATUS_REG, +		SEC_IPC_HOST_INT_STATUS_PENDING); +	mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_STS_MSK); +	mei_txe_br_reg_write(hw, HHISR_REG, IPC_HHIER_MSK); +} + +/** + * mei_txe_intr_disable - disable all interrupts + * + * @dev: the device structure + */ +static void mei_txe_intr_disable(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_br_reg_write(hw, HHIER_REG, 0); +	mei_txe_br_reg_write(hw, HIER_REG, 0); +} +/** + * mei_txe_intr_disable - enable all interrupts + * + * @dev: the device structure + */ +static void mei_txe_intr_enable(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_br_reg_write(hw, HHIER_REG, IPC_HHIER_MSK); +	mei_txe_br_reg_write(hw, HIER_REG, HIER_INT_EN_MSK); +} + +/** + * mei_txe_pending_interrupts - check if there are pending interrupts + *	only Aliveness, Input ready, and output doorbell are of relevance + * + * @dev: the device structure + * + * Checks if there are pending interrupts + * only Aliveness, Readiness, Input ready, and Output doorbell are relevant + */ +static bool mei_txe_pending_interrupts(struct mei_device *dev) +{ + +	struct mei_txe_hw *hw = to_txe_hw(dev); +	bool ret = (hw->intr_cause & (TXE_INTR_READINESS | +				      TXE_INTR_ALIVENESS | +				      TXE_INTR_IN_READY  | +				      TXE_INTR_OUT_DB)); + +	if (ret) { +		dev_dbg(&dev->pdev->dev, +			"Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n", +			!!(hw->intr_cause & TXE_INTR_IN_READY), +			!!(hw->intr_cause & TXE_INTR_READINESS), +			!!(hw->intr_cause & TXE_INTR_ALIVENESS), +			!!(hw->intr_cause & TXE_INTR_OUT_DB)); +	} +	return ret; +} + +/** + * mei_txe_input_payload_write - write a dword to the host buffer + *	at offset idx + * + * @dev: the device structure + * @idx: index in the host buffer + * @value: value + */ +static void mei_txe_input_payload_write(struct mei_device *dev, +			unsigned long idx, u32 value) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_PAYLOAD_REG + +			(idx * sizeof(u32)), value); +} + +/** + * mei_txe_out_data_read - read dword from the device buffer + *	at offset idx + * + * @dev: the device structure + * @idx: index in the device buffer + * + * returns register value at index + */ +static u32 mei_txe_out_data_read(const struct mei_device *dev, +					unsigned long idx) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	return mei_txe_br_reg_read(hw, +		BRIDGE_IPC_OUTPUT_PAYLOAD_REG + (idx * sizeof(u32))); +} + +/* Readiness */ + +/** + * mei_txe_readiness_set_host_rdy + * + * @dev: the device structure + */ +static void mei_txe_readiness_set_host_rdy(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_br_reg_write(hw, +		SICR_HOST_IPC_READINESS_REQ_REG, +		SICR_HOST_IPC_READINESS_HOST_RDY); +} + +/** + * mei_txe_readiness_clear + * + * @dev: the device structure + */ +static void mei_txe_readiness_clear(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	mei_txe_br_reg_write(hw, SICR_HOST_IPC_READINESS_REQ_REG, +				SICR_HOST_IPC_READINESS_RDY_CLR); +} +/** + * mei_txe_readiness_get - Reads and returns + *	the HICR_SEC_IPC_READINESS register value + * + * @dev: the device structure + */ +static u32 mei_txe_readiness_get(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	return mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG); +} + + +/** + * mei_txe_readiness_is_sec_rdy - check readiness + *  for HICR_SEC_IPC_READINESS_SEC_RDY + * + * @readiness - cached readiness state + */ +static inline bool mei_txe_readiness_is_sec_rdy(u32 readiness) +{ +	return !!(readiness & HICR_SEC_IPC_READINESS_SEC_RDY); +} + +/** + * mei_txe_hw_is_ready - check if the hw is ready + * + * @dev: the device structure + */ +static bool mei_txe_hw_is_ready(struct mei_device *dev) +{ +	u32 readiness =  mei_txe_readiness_get(dev); +	return mei_txe_readiness_is_sec_rdy(readiness); +} + +/** + * mei_txe_host_is_ready - check if the host is ready + * + * @dev: the device structure + */ +static inline bool mei_txe_host_is_ready(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 reg = mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG); +	return !!(reg & HICR_SEC_IPC_READINESS_HOST_RDY); +} + +/** + * mei_txe_readiness_wait - wait till readiness settles + * + * @dev: the device structure + * + * returns 0 on success and -ETIME on timeout + */ +static int mei_txe_readiness_wait(struct mei_device *dev) +{ +	if (mei_txe_hw_is_ready(dev)) +		return 0; + +	mutex_unlock(&dev->device_lock); +	wait_event_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, +			msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT)); +	mutex_lock(&dev->device_lock); +	if (!dev->recvd_hw_ready) { +		dev_err(&dev->pdev->dev, "wait for readiness failed\n"); +		return -ETIME; +	} + +	dev->recvd_hw_ready = false; +	return 0; +} + +/** + *  mei_txe_hw_config - configure hardware at the start of the devices + * + * @dev: the device structure + * + * Configure hardware at the start of the device should be done only + *   once at the device probe time + */ +static void mei_txe_hw_config(struct mei_device *dev) +{ + +	struct mei_txe_hw *hw = to_txe_hw(dev); +	/* Doesn't change in runtime */ +	dev->hbuf_depth = PAYLOAD_SIZE / 4; + +	hw->aliveness = mei_txe_aliveness_get(dev); +	hw->readiness = mei_txe_readiness_get(dev); + +	dev_dbg(&dev->pdev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", +		hw->aliveness, hw->readiness); +} + + +/** + * mei_txe_write - writes a message to device. + * + * @dev: the device structure + * @header: header of message + * @buf: message buffer will be written + * returns 1 if success, 0 - otherwise. + */ + +static int mei_txe_write(struct mei_device *dev, +		struct mei_msg_hdr *header, unsigned char *buf) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	unsigned long rem; +	unsigned long length; +	int slots = dev->hbuf_depth; +	u32 *reg_buf = (u32 *)buf; +	u32 dw_cnt; +	int i; + +	if (WARN_ON(!header || !buf)) +		return -EINVAL; + +	length = header->length; + +	dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + +	dw_cnt = mei_data2slots(length); +	if (dw_cnt > slots) +		return -EMSGSIZE; + +	if (WARN(!hw->aliveness, "txe write: aliveness not asserted\n")) +		return -EAGAIN; + +	/* Enable Input Ready Interrupt. */ +	mei_txe_input_ready_interrupt_enable(dev); + +	if (!mei_txe_is_input_ready(dev)) { +		struct mei_fw_status fw_status; +		mei_fw_status(dev, &fw_status); +		dev_err(&dev->pdev->dev, "Input is not ready " FW_STS_FMT "\n", +			FW_STS_PRM(fw_status)); +		return -EAGAIN; +	} + +	mei_txe_input_payload_write(dev, 0, *((u32 *)header)); + +	for (i = 0; i < length / 4; i++) +		mei_txe_input_payload_write(dev, i + 1, reg_buf[i]); + +	rem = length & 0x3; +	if (rem > 0) { +		u32 reg = 0; +		memcpy(®, &buf[length - rem], rem); +		mei_txe_input_payload_write(dev, i + 1, reg); +	} + +	/* after each write the whole buffer is consumed */ +	hw->slots = 0; + +	/* Set Input-Doorbell */ +	mei_txe_input_doorbell_set(hw); + +	return 0; +} + +/** + * mei_txe_hbuf_max_len - mimics the me hbuf circular buffer + * + * @dev: the device structure + * + * returns the PAYLOAD_SIZE - 4 + */ +static size_t mei_txe_hbuf_max_len(const struct mei_device *dev) +{ +	return PAYLOAD_SIZE - sizeof(struct mei_msg_hdr); +} + +/** + * mei_txe_hbuf_empty_slots - mimics the me hbuf circular buffer + * + * @dev: the device structure + * + * returns always hbuf_depth + */ +static int mei_txe_hbuf_empty_slots(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	return hw->slots; +} + +/** + * mei_txe_count_full_read_slots - mimics the me device circular buffer + * + * @dev: the device structure + * + * returns always buffer size in dwords count + */ +static int mei_txe_count_full_read_slots(struct mei_device *dev) +{ +	/* read buffers has static size */ +	return  PAYLOAD_SIZE / 4; +} + +/** + * mei_txe_read_hdr - read message header which is always in 4 first bytes + * + * @dev: the device structure + * + * returns mei message header + */ + +static u32 mei_txe_read_hdr(const struct mei_device *dev) +{ +	return mei_txe_out_data_read(dev, 0); +} +/** + * mei_txe_read - reads a message from the txe device. + * + * @dev: the device structure + * @buf: message buffer will be written + * @len: message size will be read + * + * returns -EINVAL on error wrong argument and 0 on success + */ +static int mei_txe_read(struct mei_device *dev, +		unsigned char *buf, unsigned long len) +{ + +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 i; +	u32 *reg_buf = (u32 *)buf; +	u32 rem = len & 0x3; + +	if (WARN_ON(!buf || !len)) +		return -EINVAL; + +	dev_dbg(&dev->pdev->dev, +		"buffer-length = %lu buf[0]0x%08X\n", +		len, mei_txe_out_data_read(dev, 0)); + +	for (i = 0; i < len / 4; i++) { +		/* skip header: index starts from 1 */ +		u32 reg = mei_txe_out_data_read(dev, i + 1); +		dev_dbg(&dev->pdev->dev, "buf[%d] = 0x%08X\n", i, reg); +		*reg_buf++ = reg; +	} + +	if (rem) { +		u32 reg = mei_txe_out_data_read(dev, i + 1); +		memcpy(reg_buf, ®, rem); +	} + +	mei_txe_output_ready_set(hw); +	return 0; +} + +/** + * mei_txe_hw_reset - resets host and fw. + * + * @dev: the device structure + * @intr_enable: if interrupt should be enabled after reset. + * + * returns 0 on success and < 0 in case of error + */ +static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); + +	u32 aliveness_req; +	/* +	 * read input doorbell to ensure consistency between  Bridge and SeC +	 * return value might be garbage return +	 */ +	(void)mei_txe_sec_reg_read_silent(hw, SEC_IPC_INPUT_DOORBELL_REG); + +	aliveness_req = mei_txe_aliveness_req_get(dev); +	hw->aliveness = mei_txe_aliveness_get(dev); + +	/* Disable interrupts in this stage we will poll */ +	mei_txe_intr_disable(dev); + +	/* +	 * If Aliveness Request and Aliveness Response are not equal then +	 * wait for them to be equal +	 * Since we might have interrupts disabled - poll for it +	 */ +	if (aliveness_req != hw->aliveness) +		if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) { +			dev_err(&dev->pdev->dev, +				"wait for aliveness settle failed ... bailing out\n"); +			return -EIO; +		} + +	/* +	 * If Aliveness Request and Aliveness Response are set then clear them +	 */ +	if (aliveness_req) { +		mei_txe_aliveness_set(dev, 0); +		if (mei_txe_aliveness_poll(dev, 0) < 0) { +			dev_err(&dev->pdev->dev, +				"wait for aliveness failed ... bailing out\n"); +			return -EIO; +		} +	} + +	/* +	 * Set rediness RDY_CLR bit +	 */ +	mei_txe_readiness_clear(dev); + +	return 0; +} + +/** + * mei_txe_hw_start - start the hardware after reset + * + * @dev: the device structure + * + * returns 0 on success and < 0 in case of error + */ +static int mei_txe_hw_start(struct mei_device *dev) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	int ret; + +	u32 hisr; + +	/* bring back interrupts */ +	mei_txe_intr_enable(dev); + +	ret = mei_txe_readiness_wait(dev); +	if (ret < 0) { +		dev_err(&dev->pdev->dev, "wating for readiness failed\n"); +		return ret; +	} + +	/* +	 * If HISR.INT2_STS interrupt status bit is set then clear it. +	 */ +	hisr = mei_txe_br_reg_read(hw, HISR_REG); +	if (hisr & HISR_INT_2_STS) +		mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_2_STS); + +	/* Clear the interrupt cause of OutputDoorbell */ +	clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause); + +	ret = mei_txe_aliveness_set_sync(dev, 1); +	if (ret < 0) { +		dev_err(&dev->pdev->dev, "wait for aliveness failed ... bailing out\n"); +		return ret; +	} + +	/* enable input ready interrupts: +	 * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK +	 */ +	mei_txe_input_ready_interrupt_enable(dev); + + +	/*  Set the SICR_SEC_IPC_OUTPUT_STATUS.IPC_OUTPUT_READY bit */ +	mei_txe_output_ready_set(hw); + +	/* Set bit SICR_HOST_IPC_READINESS.HOST_RDY +	 */ +	mei_txe_readiness_set_host_rdy(dev); + +	return 0; +} + +/** + * mei_txe_check_and_ack_intrs - translate multi BAR interrupt into + *  single bit mask and acknowledge the interrupts + * + * @dev: the device structure + * @do_ack: acknowledge interrupts + */ +static bool mei_txe_check_and_ack_intrs(struct mei_device *dev, bool do_ack) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); +	u32 hisr; +	u32 hhisr; +	u32 ipc_isr; +	u32 aliveness; +	bool generated; + +	/* read interrupt registers */ +	hhisr = mei_txe_br_reg_read(hw, HHISR_REG); +	generated = (hhisr & IPC_HHIER_MSK); +	if (!generated) +		goto out; + +	hisr = mei_txe_br_reg_read(hw, HISR_REG); + +	aliveness = mei_txe_aliveness_get(dev); +	if (hhisr & IPC_HHIER_SEC && aliveness) +		ipc_isr = mei_txe_sec_reg_read_silent(hw, +				SEC_IPC_HOST_INT_STATUS_REG); +	else +		ipc_isr = 0; + +	generated = generated || +		(hisr & HISR_INT_STS_MSK) || +		(ipc_isr & SEC_IPC_HOST_INT_STATUS_PENDING); + +	if (generated && do_ack) { +		/* Save the interrupt causes */ +		hw->intr_cause |= hisr & HISR_INT_STS_MSK; +		if (ipc_isr & SEC_IPC_HOST_INT_STATUS_IN_RDY) +			hw->intr_cause |= TXE_INTR_IN_READY; + + +		mei_txe_intr_disable(dev); +		/* Clear the interrupts in hierarchy: +		 * IPC and Bridge, than the High Level */ +		mei_txe_sec_reg_write_silent(hw, +			SEC_IPC_HOST_INT_STATUS_REG, ipc_isr); +		mei_txe_br_reg_write(hw, HISR_REG, hisr); +		mei_txe_br_reg_write(hw, HHISR_REG, hhisr); +	} + +out: +	return generated; +} + +/** + * mei_txe_irq_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id) +{ +	struct mei_device *dev = dev_id; + +	if (mei_txe_check_and_ack_intrs(dev, true)) +		return IRQ_WAKE_THREAD; +	return IRQ_NONE; +} + + +/** + * mei_txe_irq_thread_handler - txe interrupt thread + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) +{ +	struct mei_device *dev = (struct mei_device *) dev_id; +	struct mei_txe_hw *hw = to_txe_hw(dev); +	struct mei_cl_cb complete_list; +	s32 slots; +	int rets = 0; + +	dev_dbg(&dev->pdev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", +		mei_txe_br_reg_read(hw, HHISR_REG), +		mei_txe_br_reg_read(hw, HISR_REG), +		mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG)); + + +	/* initialize our complete list */ +	mutex_lock(&dev->device_lock); +	mei_io_list_init(&complete_list); + +	if (pci_dev_msi_enabled(dev->pdev)) +		mei_txe_check_and_ack_intrs(dev, true); + +	/* show irq events */ +	mei_txe_pending_interrupts(dev); + +	hw->aliveness = mei_txe_aliveness_get(dev); +	hw->readiness = mei_txe_readiness_get(dev); + +	/* Readiness: +	 * Detection of TXE driver going through reset +	 * or TXE driver resetting the HECI interface. +	 */ +	if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) { +		dev_dbg(&dev->pdev->dev, "Readiness Interrupt was received...\n"); + +		/* Check if SeC is going through reset */ +		if (mei_txe_readiness_is_sec_rdy(hw->readiness)) { +			dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); +			dev->recvd_hw_ready = true; +		} else { +			dev->recvd_hw_ready = false; +			if (dev->dev_state != MEI_DEV_RESETTING) { + +				dev_warn(&dev->pdev->dev, "FW not ready: resetting.\n"); +				schedule_work(&dev->reset_work); +				goto end; + +			} +		} +		wake_up(&dev->wait_hw_ready); +	} + +	/************************************************************/ +	/* Check interrupt cause: +	 * Aliveness: Detection of SeC acknowledge of host request that +	 * it remain alive or host cancellation of that request. +	 */ + +	if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) { +		/* Clear the interrupt cause */ +		dev_dbg(&dev->pdev->dev, +			"Aliveness Interrupt: Status: %d\n", hw->aliveness); +		dev->pg_event = MEI_PG_EVENT_RECEIVED; +		if (waitqueue_active(&hw->wait_aliveness_resp)) +			wake_up(&hw->wait_aliveness_resp); +	} + + +	/* Output Doorbell: +	 * Detection of SeC having sent output to host +	 */ +	slots = mei_count_full_read_slots(dev); +	if (test_and_clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause)) { +		/* Read from TXE */ +		rets = mei_irq_read_handler(dev, &complete_list, &slots); +		if (rets && dev->dev_state != MEI_DEV_RESETTING) { +			dev_err(&dev->pdev->dev, +				"mei_irq_read_handler ret = %d.\n", rets); + +			schedule_work(&dev->reset_work); +			goto end; +		} +	} +	/* Input Ready: Detection if host can write to SeC */ +	if (test_and_clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause)) { +		dev->hbuf_is_ready = true; +		hw->slots = dev->hbuf_depth; +	} + +	if (hw->aliveness && dev->hbuf_is_ready) { +		/* get the real register value */ +		dev->hbuf_is_ready = mei_hbuf_is_ready(dev); +		rets = mei_irq_write_handler(dev, &complete_list); +		if (rets && rets != -EMSGSIZE) +			dev_err(&dev->pdev->dev, "mei_irq_write_handler ret = %d.\n", +				rets); +		dev->hbuf_is_ready = mei_hbuf_is_ready(dev); +	} + +	mei_irq_compl_handler(dev, &complete_list); + +end: +	dev_dbg(&dev->pdev->dev, "interrupt thread end ret = %d\n", rets); + +	mutex_unlock(&dev->device_lock); + +	mei_enable_interrupts(dev); +	return IRQ_HANDLED; +} + + +/** + * mei_txe_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns: 0 on success an error code otherwise + */ +static int mei_txe_fw_status(struct mei_device *dev, +			     struct mei_fw_status *fw_status) +{ +	const u32 pci_cfg_reg[] = {PCI_CFG_TXE_FW_STS0, PCI_CFG_TXE_FW_STS1}; +	int i; + +	if (!fw_status) +		return -EINVAL; + +	fw_status->count = 2; + +	for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { +		int ret; +		ret = pci_read_config_dword(dev->pdev, +				pci_cfg_reg[i], &fw_status->status[i]); +		if (ret) +			return ret; +	} + +	return 0; +} + +static const struct mei_hw_ops mei_txe_hw_ops = { + +	.fw_status = mei_txe_fw_status, +	.host_is_ready = mei_txe_host_is_ready, + +	.pg_state = mei_txe_pg_state, + +	.hw_is_ready = mei_txe_hw_is_ready, +	.hw_reset = mei_txe_hw_reset, +	.hw_config = mei_txe_hw_config, +	.hw_start = mei_txe_hw_start, + +	.pg_is_enabled = mei_txe_pg_is_enabled, + +	.intr_clear = mei_txe_intr_clear, +	.intr_enable = mei_txe_intr_enable, +	.intr_disable = mei_txe_intr_disable, + +	.hbuf_free_slots = mei_txe_hbuf_empty_slots, +	.hbuf_is_ready = mei_txe_is_input_ready, +	.hbuf_max_len = mei_txe_hbuf_max_len, + +	.write = mei_txe_write, + +	.rdbuf_full_slots = mei_txe_count_full_read_slots, +	.read_hdr = mei_txe_read_hdr, + +	.read = mei_txe_read, + +}; + +#define MEI_CFG_TXE_FW_STS                            \ +	.fw_status.count = 2,                         \ +	.fw_status.status[0] = PCI_CFG_TXE_FW_STS0,   \ +	.fw_status.status[1] = PCI_CFG_TXE_FW_STS1 + +const struct mei_cfg mei_txe_cfg = { +	MEI_CFG_TXE_FW_STS, +}; + + +/** + * mei_txe_dev_init - allocates and initializes txe hardware specific structure + * + * @pdev - pci device + * @cfg - per device generation config + * + * returns struct mei_device * on success or NULL; + * + */ +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, +				    const struct mei_cfg *cfg) +{ +	struct mei_device *dev; +	struct mei_txe_hw *hw; + +	dev = kzalloc(sizeof(struct mei_device) + +			 sizeof(struct mei_txe_hw), GFP_KERNEL); +	if (!dev) +		return NULL; + +	mei_device_init(dev, cfg); + +	hw = to_txe_hw(dev); + +	init_waitqueue_head(&hw->wait_aliveness_resp); + +	dev->ops = &mei_txe_hw_ops; + +	dev->pdev = pdev; +	return dev; +} + +/** + * mei_txe_setup_satt2 - SATT2 configuration for DMA support. + * + * @dev:   the device structure + * @addr:  physical address start of the range + * @range: physical range size + */ +int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range) +{ +	struct mei_txe_hw *hw = to_txe_hw(dev); + +	u32 lo32 = lower_32_bits(addr); +	u32 hi32 = upper_32_bits(addr); +	u32 ctrl; + +	/* SATT is limited to 36 Bits */ +	if (hi32 & ~0xF) +		return -EINVAL; + +	/* SATT has to be 16Byte aligned */ +	if (lo32 & 0xF) +		return -EINVAL; + +	/* SATT range has to be 4Bytes aligned */ +	if (range & 0x4) +		return -EINVAL; + +	/* SATT is limited to 32 MB range*/ +	if (range > SATT_RANGE_MAX) +		return -EINVAL; + +	ctrl = SATT2_CTRL_VALID_MSK; +	ctrl |= hi32  << SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT; + +	mei_txe_br_reg_write(hw, SATT2_SAP_SIZE_REG, range); +	mei_txe_br_reg_write(hw, SATT2_BRG_BA_LSB_REG, lo32); +	mei_txe_br_reg_write(hw, SATT2_CTRL_REG, ctrl); +	dev_dbg(&dev->pdev->dev, "SATT2: SAP_SIZE_OFFSET=0x%08X, BRG_BA_LSB_OFFSET=0x%08X, CTRL_OFFSET=0x%08X\n", +		range, lo32, ctrl); + +	return 0; +}  | 
