diff options
Diffstat (limited to 'drivers/media/pci/intel/ipu6/ipu6-buttress.c')
-rw-r--r-- | drivers/media/pci/intel/ipu6/ipu6-buttress.c | 917 |
1 files changed, 917 insertions, 0 deletions
diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c new file mode 100644 index 0000000000..23c537e7ce --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/math64.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/pfn.h> +#include <linux/pm_runtime.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/time64.h> + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-platform-buttress-regs.h" + +#define BOOTLOADER_STATUS_OFFSET 0x15c + +#define BOOTLOADER_MAGIC_KEY 0xb00710ad + +#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 +#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 +#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE + +#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10 + +#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) + +#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) +#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) +#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) +#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_RESET_RETRY 2000 +#define BUTTRESS_CSE_IPC_RESET_RETRY 4 +#define BUTTRESS_IPC_CMD_SEND_RETRY 1 + +#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 + +static const u32 ipu6_adev_irq_mask[2] = { + BUTTRESS_ISR_IS_IRQ, + BUTTRESS_ISR_PS_IRQ +}; + +int ipu6_buttress_ipc_reset(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int retries = BUTTRESS_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val = 0, csr_in_clr; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode"); + return 0; + } + + mutex_lock(&b->ipc_mutex); + + /* Clear-by-1 CSR (all bits), corresponding internal states. */ + val = readl(isp->base + ipc->csr_in); + writel(val, isp->base + ipc->csr_in); + + /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ + writel(ENTRY, isp->base + ipc->csr_out); + /* + * Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + */ + csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | + BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | + BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; + + do { + usleep_range(400, 500); + val = readl(isp->base + ipc->csr_in); + switch (val) { + case ENTRY | EXIT: + case ENTRY | EXIT | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_COMP_ACTIONS_RST_PHASE2). + * 2) Set peer CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + */ + writel(ENTRY | EXIT, isp->base + ipc->csr_in); + writel(QUERY, isp->base + ipc->csr_out); + break; + case ENTRY: + case ENTRY | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1. + */ + writel(ENTRY | QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + case EXIT: + case EXIT | QUERY: + /* + * Clear-by-1 CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * 1) Clear incoming doorbell. + * 2) Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + * 3) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + */ + writel(EXIT, isp->base + ipc->csr_in); + writel(0, isp->base + ipc->db0_in); + writel(csr_in_clr, isp->base + ipc->csr_in); + writel(EXIT, isp->base + ipc->csr_out); + + /* + * Read csr_in again to make sure if RST_PHASE2 is done. + * If csr_in is QUERY, it should be handled again. + */ + usleep_range(200, 300); + val = readl(isp->base + ipc->csr_in); + if (val & QUERY) { + dev_dbg(&isp->pdev->dev, + "RST_PHASE2 retry csr_in = %x\n", val); + break; + } + mutex_unlock(&b->ipc_mutex); + return 0; + case QUERY: + /* + * 1) Clear-by-1 CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1 + */ + writel(QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + default: + dev_warn_ratelimited(&isp->pdev->dev, + "Unexpected CSR 0x%x\n", val); + break; + } + } while (retries--); + + mutex_unlock(&b->ipc_mutex); + dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n"); + + return -ETIMEDOUT; +} + +static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); +} + +static int +ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + void __iomem *addr; + int ret; + u32 val; + + writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); + + addr = isp->base + ipc->csr_in; + ret = readl_poll_timeout(addr, val, val & mask, 200, + BUTTRESS_IPC_VALIDITY_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); + ipu6_buttress_ipc_validity_close(isp, ipc); + } + + return ret; +} + +static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) +{ + if (ipc_msg) + *ipc_msg = readl(isp->base + ipc->data0_in); + writel(0, isp->base + ipc->db0_in); +} + +static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, + enum ipu6_buttress_ipc_domain ipc_domain, + struct ipu6_ipc_buttress_bulk_msg *msgs, + u32 size) +{ + unsigned long tx_timeout_jiffies, rx_timeout_jiffies; + unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_buttress_ipc *ipc; + u32 val; + int ret; + int tout; + + ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish; + + mutex_lock(&b->ipc_mutex); + + ret = ipu6_buttress_ipc_validity_open(isp, ipc); + if (ret) { + dev_err(&isp->pdev->dev, "IPC validity open failed\n"); + goto out; + } + + tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); + rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); + + for (i = 0; i < size; i++) { + reinit_completion(&ipc->send_complete); + if (msgs[i].require_resp) + reinit_completion(&ipc->recv_complete); + + dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", + msgs[i].cmd); + writel(msgs[i].cmd, isp->base + ipc->data0_out); + val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; + writel(val, isp->base + ipc->db0_out); + + tout = wait_for_completion_timeout(&ipc->send_complete, + tx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "send IPC response timeout\n"); + if (!retry--) { + ret = -ETIMEDOUT; + goto out; + } + + /* Try again if CSE is not responding on first try */ + writel(0, isp->base + ipc->db0_out); + i--; + continue; + } + + retry = BUTTRESS_IPC_CMD_SEND_RETRY; + + if (!msgs[i].require_resp) + continue; + + tout = wait_for_completion_timeout(&ipc->recv_complete, + rx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "recv IPC response timeout\n"); + ret = -ETIMEDOUT; + goto out; + } + + if (ipc->nack_mask && + (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + dev_err(&isp->pdev->dev, + "IPC NACK for cmd 0x%x\n", msgs[i].cmd); + ret = -EIO; + goto out; + } + + if (ipc->recv_data != msgs[i].expected_resp) { + dev_err(&isp->pdev->dev, + "expected resp: 0x%x, IPC response: 0x%x ", + msgs[i].expected_resp, ipc->recv_data); + ret = -EIO; + goto out; + } + } + + dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); + +out: + ipu6_buttress_ipc_validity_close(isp, ipc); + mutex_unlock(&b->ipc_mutex); + return ret; +} + +static int +ipu6_buttress_ipc_send(struct ipu6_device *isp, + enum ipu6_buttress_ipc_domain ipc_domain, + u32 ipc_msg, u32 size, bool require_resp, + u32 expected_resp) +{ + struct ipu6_ipc_buttress_bulk_msg msg = { + .cmd = ipc_msg, + .cmd_size = size, + .require_resp = require_resp, + .expected_resp = expected_resp, + }; + + return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1); +} + +static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) +{ + irqreturn_t ret = IRQ_WAKE_THREAD; + + if (!adev || !adev->auxdrv || !adev->auxdrv_data) + return IRQ_NONE; + + if (adev->auxdrv_data->isr) + ret = adev->auxdrv_data->isr(adev); + + if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) + ret = IRQ_NONE; + + return ret; +} + +irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + struct ipu6_buttress *b = &isp->buttress; + u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; + irqreturn_t ret = IRQ_NONE; + u32 disable_irqs = 0; + u32 irq_status; + u32 i, count = 0; + + pm_runtime_get_noresume(&isp->pdev->dev); + + irq_status = readl(isp->base + reg_irq_sts); + if (!irq_status) { + pm_runtime_put_noidle(&isp->pdev->dev); + return IRQ_NONE; + } + + do { + writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { + irqreturn_t r = ipu6_buttress_call_isr(adev[i]); + + if (!(irq_status & ipu6_adev_irq_mask[i])) + continue; + + if (r == IRQ_WAKE_THREAD) { + ret = IRQ_WAKE_THREAD; + disable_irqs |= ipu6_adev_irq_mask[i]; + } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { + ret = IRQ_HANDLED; + } + } + + if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) + ret = IRQ_HANDLED; + + if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); + ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); + complete(&b->cse.recv_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n"); + ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data); + complete(&b->ish.recv_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->cse.send_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->ish.send_complete); + } + + if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + ipu6_buttress_get_secure_mode(isp)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_SAI_VIOLATION\n"); + + if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR | + BUTTRESS_ISR_PS_FATAL_MEM_ERR)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_FATAL_MEM_ERR\n"); + + if (irq_status & BUTTRESS_ISR_UFI_ERROR) + dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n"); + + if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) { + dev_err(&isp->pdev->dev, "too many consecutive IRQs\n"); + ret = IRQ_NONE; + break; + } + + irq_status = readl(isp->base + reg_irq_sts); + } while (irq_status); + + if (disable_irqs) + writel(BUTTRESS_IRQS & ~disable_irqs, + isp->base + BUTTRESS_REG_ISR_ENABLE); + + pm_runtime_put(&isp->pdev->dev); + + return ret; +} + +irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + const struct ipu6_auxdrv_data *drv_data = NULL; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { + drv_data = adev[i]->auxdrv_data; + if (!drv_data) + continue; + + if (drv_data->wake_isr_thread && + drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) + ret = IRQ_HANDLED; + } + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + return ret; +} + +int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, + bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + u32 pwr_sts, val; + int ret; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + if (!on) { + val = 0; + pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + } else { + val = BUTTRESS_FREQ_CTL_START | + FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, + ctrl->ratio) | + FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, + ctrl->qos_floor) | + BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; + + pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + } + + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + + ctrl->started = !ret && on; + + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + + return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; +} + +bool ipu6_buttress_auth_done(struct ipu6_device *isp) +{ + u32 val; + + if (!isp->secure_mode) + return true; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); + + return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); + +int ipu6_buttress_reset_authentication(struct ipu6_device *isp) +{ + int ret; + u32 val; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + writel(BUTTRESS_FW_RESET_CTL_START, isp->base + + BUTTRESS_REG_FW_RESET_CTL); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, + val & BUTTRESS_FW_RESET_CTL_DONE, 500, + BUTTRESS_CSE_FWRESET_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Time out while resetting authentication state\n"); + return ret; + } + + dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); + writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + /* leave some time for HW restore */ + usleep_range(800, 1000); + + return 0; +} + +int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, + const struct firmware *fw, struct sg_table *sgt) +{ + bool is_vmalloc = is_vmalloc_addr(fw->data); + struct page **pages; + const void *addr; + unsigned long n_pages; + unsigned int i; + int ret; + + if (!is_vmalloc && !virt_addr_valid(fw->data)) + return -EDOM; + + n_pages = PHYS_PFN(PAGE_ALIGN(fw->size)); + + pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + addr = fw->data; + for (i = 0; i < n_pages; i++) { + struct page *p = is_vmalloc ? + vmalloc_to_page(addr) : virt_to_page(addr); + + if (!p) { + ret = -ENOMEM; + goto out; + } + pages[i] = p; + addr += PAGE_SIZE; + } + + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, + GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto out; + } + + ret = dma_map_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); + if (ret < 0) { + ret = -ENOMEM; + sg_free_table(sgt); + goto out; + } + + dma_sync_sgtable_for_device(&sys->auxdev.dev, sgt, DMA_TO_DEVICE); + +out: + kfree(pages); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); + +void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, + struct sg_table *sgt) +{ + dma_unmap_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); + sg_free_table(sgt); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); + +int ipu6_buttress_authenticate(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_psys_pdata *psys_pdata; + u32 data, mask, done, fail; + int ret; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + psys_pdata = isp->psys->pdata; + + mutex_lock(&b->auth_mutex); + + if (ipu6_buttress_auth_done(isp)) { + ret = 0; + goto out_unlock; + } + + /* + * Write address of FIT table to FW_SOURCE register + * Let's use fw address. I.e. not using FIT table yet + */ + data = lower_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); + + data = upper_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + + /* + * Write boot_load into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); + + ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, + BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + goto out_unlock; + } + + mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; + done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; + fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE auth failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, + data, data == BOOTLOADER_MAGIC_KEY, 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", + data); + goto out_unlock; + } + + /* + * Write authenticate_run into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); + ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, + BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n"); + goto out_unlock; + } + + done = BUTTRESS_SECURITY_CTL_AUTH_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + dev_info(&isp->pdev->dev, "CSE authenticate_run done\n"); + +out_unlock: + mutex_unlock(&b->auth_mutex); + + return ret; +} + +static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) +{ + u32 val, mask, done; + int ret; + + mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; + + writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, + isp->base + BUTTRESS_REG_FABRIC_CMD); + + val = readl(isp->base + BUTTRESS_REG_PWR_STATE); + val = FIELD_GET(mask, val); + if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { + dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); + return -EINVAL; + } + + done = BUTTRESS_PWR_STATE_HH_STATE_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, + FIELD_GET(mask, val) == done, 500, + BUTTRESS_TSC_SYNC_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); + + return ret; +} + +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +{ + unsigned int i; + + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + u32 val; + int ret; + + ret = ipu6_buttress_send_tsc_request(isp); + if (ret != -ETIMEDOUT) + return ret; + + val = readl(isp->base + BUTTRESS_REG_TSW_CTL); + val = val | BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); + +void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) +{ + u32 tsc_hi_1, tsc_hi_2, tsc_lo; + unsigned long flags; + + local_irq_save(flags); + tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); + tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); + tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); + if (tsc_hi_1 == tsc_hi_2) { + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + } else { + /* Check if TSC has rolled over */ + if (tsc_lo & BIT(31)) + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + else + *val = (u64)tsc_hi_2 << 32 | tsc_lo; + } + local_irq_restore(flags); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); + +u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) +{ + u64 ns = ticks * 10000; + + /* + * converting TSC tick count to ns is calculated by: + * Example (TSC clock frequency is 19.2MHz): + * ns = ticks * 1000 000 000 / 19.2Mhz + * = ticks * 1000 000 000 / 19200000Hz + * = ticks * 10000 / 192 ns + */ + return div_u64(ns, isp->buttress.ref_clk); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); + +void ipu6_buttress_restore(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); +} + +int ipu6_buttress_init(struct ipu6_device *isp) +{ + int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val; + + mutex_init(&b->power_mutex); + mutex_init(&b->auth_mutex); + mutex_init(&b->cons_mutex); + mutex_init(&b->ipc_mutex); + init_completion(&b->ish.send_complete); + init_completion(&b->cse.send_complete); + init_completion(&b->ish.recv_complete); + init_completion(&b->cse.recv_complete); + + b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; + b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; + b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; + b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; + b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; + b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; + b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + + /* no ISH on IPU6 */ + memset(&b->ish, 0, sizeof(b->ish)); + INIT_LIST_HEAD(&b->constraints); + + isp->secure_mode = ipu6_buttress_get_secure_mode(isp); + dev_info(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", + isp->secure_mode ? "secure" : "non-secure", + readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + + b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + /* get ref_clk frequency by reading the indication in btrs control */ + val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); + + switch (val) { + case 0x0: + b->ref_clk = 240; + break; + case 0x1: + b->ref_clk = 192; + break; + case 0x2: + b->ref_clk = 384; + break; + default: + dev_warn(&isp->pdev->dev, + "Unsupported ref clock, use 19.2Mhz by default.\n"); + b->ref_clk = 192; + break; + } + + /* Retry couple of times in case of CSE initialization is delayed */ + do { + ret = ipu6_buttress_ipc_reset(isp, &b->cse); + if (ret) { + dev_warn(&isp->pdev->dev, + "IPC reset protocol failed, retrying\n"); + } else { + dev_dbg(&isp->pdev->dev, "IPC reset done\n"); + return 0; + } + } while (ipc_reset_retry--); + + dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); + + return ret; +} + +void ipu6_buttress_exit(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); +} |