Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,18 @@
config VIDEO_INTEL_IPU6
tristate "Intel IPU6 driver"
depends on ACPI || COMPILE_TEST
depends on VIDEO_DEV
depends on X86 && HAS_DMA
depends on IPU_BRIDGE || !IPU_BRIDGE
select AUXILIARY_BUS
select IOMMU_IOVA
select VIDEO_V4L2_SUBDEV_API
select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_SG
select V4L2_FWNODE
help
This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs
and used for capturing images and video from camera sensors.
To compile this driver, say Y here! It contains 2 modules -
intel_ipu6 and intel_ipu6_isys.
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
intel-ipu6-y := ipu6.o \
ipu6-bus.o \
ipu6-dma.o \
ipu6-mmu.o \
ipu6-buttress.o \
ipu6-cpd.o \
ipu6-fw-com.o
obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o
intel-ipu6-isys-y := ipu6-isys.o \
ipu6-isys-csi2.o \
ipu6-fw-isys.o \
ipu6-isys-video.o \
ipu6-isys-queue.o \
ipu6-isys-subdev.o \
ipu6-isys-mcd-phy.o \
ipu6-isys-jsl-phy.o \
ipu6-isys-dwc-phy.o
obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 - 2024 Intel Corporation
*/
#include <linux/auxiliary_bus.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-buttress.h"
#include "ipu6-dma.h"
static int bus_pm_runtime_suspend(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
int ret;
ret = pm_generic_runtime_suspend(dev);
if (ret)
return ret;
ret = ipu6_buttress_power(dev, adev->ctrl, false);
if (!ret)
return 0;
dev_err(dev, "power down failed!\n");
/* Powering down failed, attempt to resume device now */
ret = pm_generic_runtime_resume(dev);
if (!ret)
return -EBUSY;
return -EIO;
}
static int bus_pm_runtime_resume(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
int ret;
ret = ipu6_buttress_power(dev, adev->ctrl, true);
if (ret)
return ret;
ret = pm_generic_runtime_resume(dev);
if (ret)
goto out_err;
return 0;
out_err:
ipu6_buttress_power(dev, adev->ctrl, false);
return -EBUSY;
}
static struct dev_pm_domain ipu6_bus_pm_domain = {
.ops = {
.runtime_suspend = bus_pm_runtime_suspend,
.runtime_resume = bus_pm_runtime_resume,
},
};
static DEFINE_MUTEX(ipu6_bus_mutex);
static void ipu6_bus_release(struct device *dev)
{
struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
kfree(adev->pdata);
kfree(adev);
}
struct ipu6_bus_device *
ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
void *pdata, const struct ipu6_buttress_ctrl *ctrl,
char *name)
{
struct auxiliary_device *auxdev;
struct ipu6_bus_device *adev;
struct ipu6_device *isp = pci_get_drvdata(pdev);
int ret;
adev = kzalloc_obj(*adev);
if (!adev)
return ERR_PTR(-ENOMEM);
adev->isp = isp;
adev->ctrl = ctrl;
adev->pdata = pdata;
auxdev = &adev->auxdev;
auxdev->name = name;
auxdev->id = (pci_domain_nr(pdev->bus) << 16) |
PCI_DEVID(pdev->bus->number, pdev->devfn);
auxdev->dev.parent = parent;
auxdev->dev.release = ipu6_bus_release;
ret = auxiliary_device_init(auxdev);
if (ret < 0) {
dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n",
ret);
kfree(adev);
return ERR_PTR(ret);
}
dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain);
pm_runtime_forbid(&adev->auxdev.dev);
pm_runtime_enable(&adev->auxdev.dev);
return adev;
}
int ipu6_bus_add_device(struct ipu6_bus_device *adev)
{
struct auxiliary_device *auxdev = &adev->auxdev;
int ret;
ret = auxiliary_device_add(auxdev);
if (ret) {
auxiliary_device_uninit(auxdev);
return ret;
}
mutex_lock(&ipu6_bus_mutex);
list_add(&adev->list, &adev->isp->devices);
mutex_unlock(&ipu6_bus_mutex);
pm_runtime_allow(&auxdev->dev);
return 0;
}
void ipu6_bus_del_devices(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_bus_device *adev, *save;
mutex_lock(&ipu6_bus_mutex);
list_for_each_entry_safe(adev, save, &isp->devices, list) {
pm_runtime_disable(&adev->auxdev.dev);
list_del(&adev->list);
auxiliary_device_delete(&adev->auxdev);
auxiliary_device_uninit(&adev->auxdev);
}
mutex_unlock(&ipu6_bus_mutex);
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 - 2024 Intel Corporation */
#ifndef IPU6_BUS_H
#define IPU6_BUS_H
#include <linux/auxiliary_bus.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/scatterlist.h>
#include <linux/types.h>
struct firmware;
struct pci_dev;
struct ipu6_buttress_ctrl;
struct ipu6_bus_device {
struct auxiliary_device auxdev;
const struct auxiliary_driver *auxdrv;
const struct ipu6_auxdrv_data *auxdrv_data;
struct list_head list;
void *pdata;
struct ipu6_mmu *mmu;
struct ipu6_device *isp;
const struct ipu6_buttress_ctrl *ctrl;
const struct firmware *fw;
struct sg_table fw_sgt;
u64 *pkg_dir;
dma_addr_t pkg_dir_dma_addr;
unsigned int pkg_dir_size;
};
struct ipu6_auxdrv_data {
irqreturn_t (*isr)(struct ipu6_bus_device *adev);
irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev);
bool wake_isr_thread;
};
#define to_ipu6_bus_device(_dev) \
container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev)
#define auxdev_to_adev(_auxdev) \
container_of(_auxdev, struct ipu6_bus_device, auxdev)
#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev)
struct ipu6_bus_device *
ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
void *pdata, const struct ipu6_buttress_ctrl *ctrl,
char *name);
int ipu6_bus_add_device(struct ipu6_bus_device *adev);
void ipu6_bus_del_devices(struct pci_dev *pdev);
#endif
@@ -0,0 +1,910 @@
// 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-dma.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_dbg_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,
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 = &b->cse;
u32 val;
int ret;
int tout;
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,
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, &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;
int active;
active = pm_runtime_get_if_active(&isp->pdev->dev);
if (!active)
return IRQ_NONE;
irq_status = readl(isp->base + reg_irq_sts);
if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) {
if (active > 0)
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_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_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);
if (active > 0)
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,
const 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);
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 pci_dev *pdev = sys->isp->pdev;
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 = PFN_UP(fw->size);
pages = kmalloc_objs(*pages, n_pages);
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(&pdev->dev, sgt, DMA_TO_DEVICE, 0);
if (ret) {
sg_free_table(sgt);
goto out;
}
ret = ipu6_dma_map_sgtable(sys, sgt, DMA_TO_DEVICE, 0);
if (ret) {
dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0);
sg_free_table(sgt);
goto out;
}
ipu6_dma_sync_sgtable(sys, sgt);
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)
{
struct pci_dev *pdev = sys->isp->pdev;
ipu6_dma_unmap_sgtable(sys, sgt, DMA_TO_DEVICE, 0);
dma_unmap_sgtable(&pdev->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,
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,
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->cse.send_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;
INIT_LIST_HEAD(&b->constraints);
isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
dev_dbg(&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);
}
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_BUTTRESS_H
#define IPU6_BUTTRESS_H
#include <linux/completion.h>
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/mutex.h>
struct device;
struct firmware;
struct ipu6_device;
struct ipu6_bus_device;
#define BUTTRESS_PS_FREQ_STEP 25U
#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8)
#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32)
#define BUTTRESS_IS_FREQ_STEP 25U
#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8)
#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 22)
struct ipu6_buttress_ctrl {
u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off;
unsigned int ratio;
unsigned int qos_floor;
};
struct ipu6_buttress_ipc {
struct completion send_complete;
struct completion recv_complete;
u32 nack;
u32 nack_mask;
u32 recv_data;
u32 csr_out;
u32 csr_in;
u32 db0_in;
u32 db0_out;
u32 data0_out;
u32 data0_in;
};
struct ipu6_buttress {
struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex;
struct ipu6_buttress_ipc cse;
struct list_head constraints;
u32 wdt_cached_value;
bool force_suspend;
u32 ref_clk;
};
struct ipu6_ipc_buttress_bulk_msg {
u32 cmd;
u32 expected_resp;
bool require_resp;
u8 cmd_size;
};
int ipu6_buttress_ipc_reset(struct ipu6_device *isp,
struct ipu6_buttress_ipc *ipc);
int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys,
const struct firmware *fw,
struct sg_table *sgt);
void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys,
struct sg_table *sgt);
int ipu6_buttress_power(struct device *dev,
const struct ipu6_buttress_ctrl *ctrl, bool on);
bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp);
int ipu6_buttress_authenticate(struct ipu6_device *isp);
int ipu6_buttress_reset_authentication(struct ipu6_device *isp);
bool ipu6_buttress_auth_done(struct ipu6_device *isp);
int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp);
void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val);
u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp);
irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr);
irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr);
int ipu6_buttress_init(struct ipu6_device *isp);
void ipu6_buttress_exit(struct ipu6_device *isp);
void ipu6_buttress_csi_port_config(struct ipu6_device *isp,
u32 legacy, u32 combo);
void ipu6_buttress_restore(struct ipu6_device *isp);
#endif /* IPU6_BUTTRESS_H */
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/dma-mapping.h>
#include <linux/gfp_types.h>
#include <linux/math64.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-cpd.h"
#include "ipu6-dma.h"
/* 15 entries + header*/
#define MAX_PKG_DIR_ENT_CNT 16
/* 2 qword per entry/header */
#define PKG_DIR_ENT_LEN 2
/* PKG_DIR size in bytes */
#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \
(PKG_DIR_ENT_LEN) * sizeof(u64))
/* _IUPKDR_ */
#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL
/* $CPD */
#define CPD_HDR_MARK 0x44504324
#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32))
#define MAX_METADATA_SIZE SZ_64K
#define MAX_COMPONENT_ID 127
#define MAX_COMPONENT_VERSION 0xffff
#define MANIFEST_IDX 0
#define METADATA_IDX 1
#define MODULEDATA_IDX 2
/*
* PKG_DIR Entry (type == id)
* 63:56 55 54:48 47:32 31:24 23:0
* Rsvd Rsvd Type Version Rsvd Size
*/
#define PKG_DIR_SIZE_MASK GENMASK_ULL(23, 0)
#define PKG_DIR_VERSION_MASK GENMASK_ULL(47, 32)
#define PKG_DIR_TYPE_MASK GENMASK_ULL(54, 48)
static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd,
u8 idx)
{
const struct ipu6_cpd_hdr *cpd_hdr = cpd;
const struct ipu6_cpd_ent *ent;
ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len);
return ent + idx;
}
#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX)
#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX)
#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX)
static const struct ipu6_cpd_metadata_cmpnt_hdr *
ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata,
unsigned int metadata_size, u8 idx)
{
size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn);
size_t cmpnt_count = metadata_size - extn_size;
cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size);
if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) {
dev_err(&isp->pdev->dev, "Component index out of range (%d)\n",
idx);
return ERR_PTR(-EINVAL);
}
return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size;
}
static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp,
const void *metadata,
unsigned int metadata_size, u8 idx)
{
const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
if (IS_ERR(cmpnt))
return PTR_ERR(cmpnt);
return cmpnt->ver;
}
static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp,
const void *metadata,
unsigned int metadata_size, u8 idx)
{
const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
if (IS_ERR(cmpnt))
return PTR_ERR(cmpnt);
return cmpnt->id;
}
static int ipu6_cpd_parse_module_data(struct ipu6_device *isp,
const void *module_data,
unsigned int module_data_size,
dma_addr_t dma_addr_module_data,
u64 *pkg_dir, const void *metadata,
unsigned int metadata_size)
{
const struct ipu6_cpd_module_data_hdr *module_data_hdr;
const struct ipu6_cpd_hdr *dir_hdr;
const struct ipu6_cpd_ent *dir_ent;
unsigned int i;
u8 len;
if (!module_data)
return -EINVAL;
module_data_hdr = module_data;
dir_hdr = module_data + module_data_hdr->hdr_len;
len = dir_hdr->hdr_len;
dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len);
pkg_dir[0] = PKG_DIR_HDR_MARK;
/* pkg_dir entry count = component count + pkg_dir header */
pkg_dir[1] = dir_hdr->ent_cnt + 1;
for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) {
u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)];
int ver, id;
*p++ = dma_addr_module_data + dir_ent->offset;
id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata,
metadata_size, i);
if (id < 0 || id > MAX_COMPONENT_ID) {
dev_err(&isp->pdev->dev, "Invalid CPD component id\n");
return -EINVAL;
}
ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata,
metadata_size, i);
if (ver < 0 || ver > MAX_COMPONENT_VERSION) {
dev_err(&isp->pdev->dev,
"Invalid CPD component version\n");
return -EINVAL;
}
*p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) |
FIELD_PREP(PKG_DIR_TYPE_MASK, id) |
FIELD_PREP(PKG_DIR_VERSION_MASK, ver);
}
return 0;
}
int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src)
{
dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl);
const struct ipu6_cpd_ent *ent, *man_ent, *met_ent;
struct ipu6_device *isp = adev->isp;
unsigned int man_sz, met_sz;
void *pkg_dir_pos;
int ret;
man_ent = ipu6_cpd_get_manifest(src);
man_sz = man_ent->len;
met_ent = ipu6_cpd_get_metadata(src);
met_sz = met_ent->len;
adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz;
adev->pkg_dir = ipu6_dma_alloc(adev, adev->pkg_dir_size,
&adev->pkg_dir_dma_addr, GFP_KERNEL, 0);
if (!adev->pkg_dir)
return -ENOMEM;
/*
* pkg_dir entry/header:
* qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0
* N Address/Offset/"_IUPKDR_"
* N + 1 | rsvd | rsvd | type | ver | rsvd | size
*
* We can ignore other fields that size in N + 1 qword as they
* are 0 anyway. Just setting size for now.
*/
ent = ipu6_cpd_get_moduledata(src);
ret = ipu6_cpd_parse_module_data(isp, src + ent->offset,
ent->len, dma_addr_src + ent->offset,
adev->pkg_dir, src + met_ent->offset,
met_ent->len);
if (ret) {
dev_err(&isp->pdev->dev, "Failed to parse module data\n");
ipu6_dma_free(adev, adev->pkg_dir_size,
adev->pkg_dir, adev->pkg_dir_dma_addr, 0);
return ret;
}
/* Copy manifest after pkg_dir */
pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT;
memcpy(pkg_dir_pos, src + man_ent->offset, man_sz);
/* Copy metadata after manifest */
pkg_dir_pos += man_sz;
memcpy(pkg_dir_pos, src + met_ent->offset, met_sz);
ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr,
adev->pkg_dir_size);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6");
void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
{
ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir,
adev->pkg_dir_dma_addr, 0);
}
EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6");
static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd,
unsigned long cpd_size,
unsigned long data_size)
{
const struct ipu6_cpd_hdr *cpd_hdr = cpd;
const struct ipu6_cpd_ent *ent;
unsigned int i;
u8 len;
len = cpd_hdr->hdr_len;
/* Ensure cpd hdr is within moduledata */
if (cpd_size < len) {
dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
return -EINVAL;
}
/* Sanity check for CPD header */
if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
dev_err(&isp->pdev->dev, "Invalid CPD header\n");
return -EINVAL;
}
/* Ensure that all entries are within moduledata */
ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len);
for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) {
if (data_size < ent->offset ||
data_size - ent->offset < ent->len) {
dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i);
return -EINVAL;
}
}
return 0;
}
static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp,
const void *moduledata,
u32 moduledata_size)
{
const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata;
int ret;
/* Ensure moduledata hdr is within moduledata */
if (moduledata_size < sizeof(*mod_hdr) ||
moduledata_size < mod_hdr->hdr_len) {
dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
return -EINVAL;
}
dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date);
ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len,
moduledata_size - mod_hdr->hdr_len,
moduledata_size);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n");
return ret;
}
return 0;
}
static int ipu6_cpd_validate_metadata(struct ipu6_device *isp,
const void *metadata, u32 meta_size)
{
const struct ipu6_cpd_metadata_extn *extn = metadata;
/* Sanity check for metadata size */
if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
return -EINVAL;
}
/* Validate extension and image types */
if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT ||
extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) {
dev_err(&isp->pdev->dev,
"Invalid CPD metadata descriptor img_type (%d)\n",
extn->img_type);
return -EINVAL;
}
/* Validate metadata size multiple of metadata components */
if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n");
return -EINVAL;
}
return 0;
}
int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
unsigned long cpd_file_size)
{
const struct ipu6_cpd_hdr *hdr = cpd_file;
const struct ipu6_cpd_ent *ent;
int ret;
ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size,
cpd_file_size);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD in file\n");
return ret;
}
/* Check for CPD file marker */
if (hdr->hdr_mark != CPD_HDR_MARK) {
dev_err(&isp->pdev->dev, "Invalid CPD header\n");
return -EINVAL;
}
/* Sanity check for manifest size */
ent = ipu6_cpd_get_manifest(cpd_file);
if (ent->len > MAX_MANIFEST_SIZE) {
dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n");
return -EINVAL;
}
/* Validate metadata */
ent = ipu6_cpd_get_metadata(cpd_file);
ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len);
if (ret) {
dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
return ret;
}
/* Validate moduledata */
ent = ipu6_cpd_get_moduledata(cpd_file);
ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset,
ent->len);
if (ret)
dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n");
return ret;
}
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2015--2024 Intel Corporation */
#ifndef IPU6_CPD_H
#define IPU6_CPD_H
struct ipu6_device;
struct ipu6_bus_device;
#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION 7
#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION 11
#define IPU6_CPD_SIZE_OF_COMPONENT_NAME 12
#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT 0x10
#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED 0
#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER 1
#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE 2
#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX 0
#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX 1
#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE 3
#define IPU6_CPD_METADATA_HASH_KEY_SIZE 48
#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE 32
struct ipu6_cpd_module_data_hdr {
u32 hdr_len;
u32 endian;
u32 fw_pkg_date;
u32 hive_sdk_date;
u32 compiler_date;
u32 target_platform_type;
u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION];
u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION];
u8 rsvd[2];
} __packed;
/*
* ipu6_cpd_hdr structure updated as the chksum and
* sub_partition_name is unused on host side
* CSE layout version 1.6 for IPU6SE (hdr_len = 0x10)
* CSE layout version 1.7 for IPU6 (hdr_len = 0x14)
*/
struct ipu6_cpd_hdr {
u32 hdr_mark;
u32 ent_cnt;
u8 hdr_ver;
u8 ent_ver;
u8 hdr_len;
} __packed;
struct ipu6_cpd_ent {
u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME];
u32 offset;
u32 len;
u8 rsvd[4];
} __packed;
struct ipu6_cpd_metadata_cmpnt_hdr {
u32 id;
u32 size;
u32 ver;
} __packed;
struct ipu6_cpd_metadata_cmpnt {
struct ipu6_cpd_metadata_cmpnt_hdr hdr;
u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE];
u32 entry_point;
u32 icache_base_offs;
u8 attrs[16];
} __packed;
struct ipu6se_cpd_metadata_cmpnt {
struct ipu6_cpd_metadata_cmpnt_hdr hdr;
u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE];
u32 entry_point;
u32 icache_base_offs;
u8 attrs[16];
} __packed;
struct ipu6_cpd_metadata_extn {
u32 extn_type;
u32 len;
u32 img_type;
u8 rsvd[16];
} __packed;
struct ipu6_cpd_client_pkg_hdr {
u32 prog_list_offs;
u32 prog_list_size;
u32 prog_desc_offs;
u32 prog_desc_size;
u32 pg_manifest_offs;
u32 pg_manifest_size;
u32 prog_bin_offs;
u32 prog_bin_size;
} __packed;
int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src);
void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev);
int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
unsigned long cpd_file_size);
#endif /* IPU6_CPD_H */
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/iova.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-mmu.h"
struct vm_info {
struct list_head list;
struct page **pages;
dma_addr_t ipu6_iova;
void *vaddr;
unsigned long size;
};
static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova)
{
struct vm_info *info, *save;
list_for_each_entry_safe(info, save, &mmu->vma_list, list) {
if (iova >= info->ipu6_iova &&
iova < (info->ipu6_iova + info->size))
return info;
}
return NULL;
}
static void __clear_buffer(struct page *page, size_t size, unsigned long attrs)
{
void *ptr;
if (!page)
return;
/*
* Ensure that the allocated pages are zeroed, and that any data
* lurking in the kernel direct-mapped region is invalidated.
*/
ptr = page_address(page);
memset(ptr, 0, size);
if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
clflush_cache_range(ptr, size);
}
static struct page **__alloc_buffer(size_t size, gfp_t gfp, unsigned long attrs)
{
int count = PHYS_PFN(size);
int array_size = count * sizeof(struct page *);
struct page **pages;
int i = 0;
pages = kvzalloc(array_size, GFP_KERNEL);
if (!pages)
return NULL;
gfp |= __GFP_NOWARN;
while (count) {
int j, order = __fls(count);
pages[i] = alloc_pages(gfp, order);
while (!pages[i] && order)
pages[i] = alloc_pages(gfp, --order);
if (!pages[i])
goto error;
if (order) {
split_page(pages[i], order);
j = 1 << order;
while (j--)
pages[i + j] = pages[i] + j;
}
__clear_buffer(pages[i], PAGE_SIZE << order, attrs);
i += 1 << order;
count -= 1 << order;
}
return pages;
error:
while (i--)
if (pages[i])
__free_pages(pages[i], 0);
kvfree(pages);
return NULL;
}
static void __free_buffer(struct page **pages, size_t size, unsigned long attrs)
{
int count = PHYS_PFN(size);
unsigned int i;
for (i = 0; i < count && pages[i]; i++) {
__clear_buffer(pages[i], PAGE_SIZE, attrs);
__free_pages(pages[i], 0);
}
kvfree(pages);
}
void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle,
size_t size)
{
void *vaddr;
u32 offset;
struct vm_info *info;
struct ipu6_mmu *mmu = sys->mmu;
info = get_vm_info(mmu, dma_handle);
if (WARN_ON(!info))
return;
offset = dma_handle - info->ipu6_iova;
if (WARN_ON(size > (info->size - offset)))
return;
vaddr = info->vaddr + offset;
clflush_cache_range(vaddr, size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, "INTEL_IPU6");
void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents)
{
struct scatterlist *sg;
int i;
for_each_sg(sglist, sg, nents, i)
clflush_cache_range(sg_virt(sg), sg->length);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, "INTEL_IPU6");
void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt)
{
ipu6_dma_sync_sg(sys, sgt->sgl, sgt->orig_nents);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, "INTEL_IPU6");
void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct pci_dev *pdev = sys->isp->pdev;
dma_addr_t pci_dma_addr, ipu6_iova;
struct ipu6_mmu *mmu = sys->mmu;
struct vm_info *info;
unsigned long count;
struct page **pages;
struct iova *iova;
unsigned int i;
int ret;
info = kzalloc_obj(*info);
if (!info)
return NULL;
size = PAGE_ALIGN(size);
count = PHYS_PFN(size);
iova = alloc_iova(&mmu->dmap->iovad, count,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova)
goto out_kfree;
pages = __alloc_buffer(size, gfp, attrs);
if (!pages)
goto out_free_iova;
dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n",
size, iova->pfn_lo, iova->pfn_hi);
for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) {
pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0,
PAGE_SIZE, DMA_BIDIRECTIONAL,
attrs);
dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n",
&pci_dma_addr);
if (dma_mapping_error(&pdev->dev, pci_dma_addr)) {
dev_err(dev, "pci_dma_mapping for page[%d] failed", i);
goto out_unmap;
}
ret = ipu6_mmu_map(mmu->dmap->mmu_info,
PFN_PHYS(iova->pfn_lo + i), pci_dma_addr,
PAGE_SIZE);
if (ret) {
dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed",
i, &pci_dma_addr);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr,
PAGE_SIZE, DMA_BIDIRECTIONAL,
attrs);
goto out_unmap;
}
}
info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL);
if (!info->vaddr)
goto out_unmap;
*dma_handle = PFN_PHYS(iova->pfn_lo);
info->pages = pages;
info->ipu6_iova = *dma_handle;
info->size = size;
list_add(&info->list, &mmu->vma_list);
return info->vaddr;
out_unmap:
while (i--) {
ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
ipu6_iova);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL, attrs);
ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE);
}
__free_buffer(pages, size, attrs);
out_free_iova:
__free_iova(&mmu->dmap->iovad, iova);
out_kfree:
kfree(info);
return NULL;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, "INTEL_IPU6");
void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr,
dma_addr_t dma_handle, unsigned long attrs)
{
struct ipu6_mmu *mmu = sys->mmu;
struct pci_dev *pdev = sys->isp->pdev;
struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle));
dma_addr_t pci_dma_addr, ipu6_iova;
struct vm_info *info;
struct page **pages;
unsigned int i;
if (WARN_ON(!iova))
return;
info = get_vm_info(mmu, dma_handle);
if (WARN_ON(!info))
return;
if (WARN_ON(!info->vaddr))
return;
if (WARN_ON(!info->pages))
return;
list_del(&info->list);
size = PAGE_ALIGN(size);
pages = info->pages;
vunmap(vaddr);
for (i = 0; i < PHYS_PFN(size); i++) {
ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
ipu6_iova);
dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL, attrs);
}
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
__free_buffer(pages, size, attrs);
mmu->tlb_invalidate(mmu);
__free_iova(&mmu->dmap->iovad, iova);
kfree(info);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, "INTEL_IPU6");
int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma,
void *addr, dma_addr_t iova, size_t size,
unsigned long attrs)
{
struct ipu6_mmu *mmu = sys->mmu;
size_t count = PFN_UP(size);
struct vm_info *info;
size_t i;
int ret;
info = get_vm_info(mmu, iova);
if (!info)
return -EFAULT;
if (!info->vaddr)
return -EFAULT;
if (vma->vm_start & ~PAGE_MASK)
return -EINVAL;
if (size > info->size)
return -EFAULT;
for (i = 0; i < count; i++) {
ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i),
info->pages[i]);
if (ret < 0)
return ret;
}
return 0;
}
void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct ipu6_mmu *mmu = sys->mmu;
struct iova *iova = find_iova(&mmu->dmap->iovad,
PHYS_PFN(sg_dma_address(sglist)));
struct scatterlist *sg;
dma_addr_t pci_dma_addr;
unsigned int i;
if (!nents)
return;
if (WARN_ON(!iova))
return;
/*
* Before IPU6 mmu unmap, return the pci dma address back to sg
* assume the nents is less than orig_nents as the least granule
* is 1 SZ_4K page
*/
dev_dbg(dev, "trying to unmap concatenated %u ents\n", nents);
for_each_sg(sglist, sg, nents, i) {
dev_dbg(dev, "unmap sg[%d] %pad size %u\n", i,
&sg_dma_address(sg), sg_dma_len(sg));
pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
sg_dma_address(sg));
dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n",
&pci_dma_addr, i);
sg_dma_address(sg) = pci_dma_addr;
}
dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n",
iova->pfn_lo, iova->pfn_hi);
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
mmu->tlb_invalidate(mmu);
__free_iova(&mmu->dmap->iovad, iova);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6");
int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs)
{
struct device *dev = &sys->auxdev.dev;
struct ipu6_mmu *mmu = sys->mmu;
struct scatterlist *sg;
struct iova *iova;
size_t npages = 0;
unsigned long iova_addr;
int i;
for_each_sg(sglist, sg, nents, i) {
if (sg->offset) {
dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n",
i, sg->offset);
return -EFAULT;
}
}
for_each_sg(sglist, sg, nents, i)
npages += PFN_UP(sg_dma_len(sg));
dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n",
nents, npages);
iova = alloc_iova(&mmu->dmap->iovad, npages,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova)
return 0;
dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo,
iova->pfn_hi);
iova_addr = iova->pfn_lo;
for_each_sg(sglist, sg, nents, i) {
phys_addr_t iova_pa;
int ret;
iova_pa = PFN_PHYS(iova_addr);
dev_dbg(dev, "mapping entry %d: iova %pap phy %pap size %d\n",
i, &iova_pa, &sg_dma_address(sg), sg_dma_len(sg));
ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
sg_dma_address(sg),
PAGE_ALIGN(sg_dma_len(sg)));
if (ret)
goto out_fail;
sg_dma_address(sg) = PFN_PHYS(iova_addr);
iova_addr += PFN_UP(sg_dma_len(sg));
}
dev_dbg(dev, "dmamap %d ents %zu pages mapped\n", nents, npages);
return nents;
out_fail:
ipu6_dma_unmap_sg(sys, sglist, i, dir, attrs);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, "INTEL_IPU6");
int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs)
{
int nents;
nents = ipu6_dma_map_sg(sys, sgt->sgl, sgt->nents, dir, attrs);
if (nents < 0)
return nents;
sgt->nents = nents;
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, "INTEL_IPU6");
void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs)
{
ipu6_dma_unmap_sg(sys, sgt->sgl, sgt->nents, dir, attrs);
}
EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, "INTEL_IPU6");
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_DMA_H
#define IPU6_DMA_H
#include <linux/iova.h>
#include <linux/scatterlist.h>
#include <linux/types.h>
#include "ipu6-bus.h"
struct ipu6_mmu_info;
struct ipu6_dma_mapping {
struct ipu6_mmu_info *mmu_info;
struct iova_domain iovad;
};
void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle,
size_t size);
void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents);
void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt);
void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
unsigned long attrs);
void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr,
dma_addr_t dma_handle, unsigned long attrs);
int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma,
void *addr, dma_addr_t iova, size_t size,
unsigned long attrs);
int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs);
void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist,
int nents, enum dma_data_direction dir,
unsigned long attrs);
int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs);
void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt,
enum dma_data_direction dir, unsigned long attrs);
#endif /* IPU6_DMA_H */
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/math.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-fw-com.h"
/*
* FWCOM layer is a shared resource between FW and driver. It consist
* of token queues to both send and receive directions. Queue is simply
* an array of structures with read and write indexes to the queue.
* There are 1...n queues to both directions. Queues locates in
* system RAM and are mapped to ISP MMU so that both CPU and ISP can
* see the same buffer. Indexes are located in ISP DMEM so that FW code
* can poll those with very low latency and cost. CPU access to indexes is
* more costly but that happens only at message sending time and
* interrupt triggered message handling. CPU doesn't need to poll indexes.
* wr_reg / rd_reg are offsets to those dmem location. They are not
* the indexes itself.
*/
/* Shared structure between driver and FW - do not modify */
struct ipu6_fw_sys_queue {
u64 host_address;
u32 vied_address;
u32 size;
u32 token_size;
u32 wr_reg; /* reg number in subsystem's regmem */
u32 rd_reg;
u32 _align;
} __packed;
struct ipu6_fw_sys_queue_res {
u64 host_address;
u32 vied_address;
u32 reg;
} __packed;
enum syscom_state {
/* Program load or explicit host setting should init to this */
SYSCOM_STATE_UNINIT = 0x57a7e000,
/* SP Syscom sets this when it is ready for use */
SYSCOM_STATE_READY = 0x57a7e001,
/* SP Syscom sets this when no more syscom accesses will happen */
SYSCOM_STATE_INACTIVE = 0x57a7e002,
};
enum syscom_cmd {
/* Program load or explicit host setting should init to this */
SYSCOM_COMMAND_UNINIT = 0x57a7f000,
/* Host Syscom requests syscom to become inactive */
SYSCOM_COMMAND_INACTIVE = 0x57a7f001,
};
/* firmware config: data that sent from the host to SP via DDR */
/* Cell copies data into a context */
struct ipu6_fw_syscom_config {
u32 firmware_address;
u32 num_input_queues;
u32 num_output_queues;
/* ISP pointers to an array of ipu6_fw_sys_queue structures */
u32 input_queue;
u32 output_queue;
/* ISYS / PSYS private data */
u32 specific_addr;
u32 specific_size;
};
struct ipu6_fw_com_context {
struct ipu6_bus_device *adev;
void __iomem *dmem_addr;
int (*cell_ready)(struct ipu6_bus_device *adev);
void (*cell_start)(struct ipu6_bus_device *adev);
void *dma_buffer;
dma_addr_t dma_addr;
unsigned int dma_size;
struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */
struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */
u32 config_vied_addr;
unsigned int buttress_boot_offset;
void __iomem *base_addr;
};
#define FW_COM_WR_REG 0
#define FW_COM_RD_REG 4
#define REGMEM_OFFSET 0
#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a
enum regmem_id {
/* pass pkg_dir address to SPC in non-secure mode */
PKG_DIR_ADDR_REG = 0,
/* Tunit CFG blob for secure - provided by host.*/
TUNIT_CFG_DWR_REG = 1,
/* syscom commands - modified by the host */
SYSCOM_COMMAND_REG = 2,
/* Store interrupt status - updated by SP */
SYSCOM_IRQ_REG = 3,
/* first syscom queue pointer register */
SYSCOM_QPR_BASE_REG = 4
};
#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000
#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \
((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4)
enum buttress_syscom_id {
/* pass syscom configuration to SPC */
SYSCOM_CONFIG_ID = 0,
/* syscom state - modified by SP */
SYSCOM_STATE_ID = 1,
/* syscom vtl0 addr mask */
SYSCOM_VTL0_ADDR_MASK_ID = 2,
SYSCOM_ID_MAX
};
static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size,
unsigned int token_size,
struct ipu6_fw_sys_queue_res *res)
{
unsigned int buf_size = (size + 1) * token_size;
q->size = size + 1;
q->token_size = token_size;
/* acquire the shared buffer space */
q->host_address = res->host_address;
res->host_address += buf_size;
q->vied_address = res->vied_address;
res->vied_address += buf_size;
/* acquire the shared read and writer pointers */
q->wr_reg = res->reg;
res->reg++;
q->rd_reg = res->reg;
res->reg++;
}
void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
struct ipu6_bus_device *adev, void __iomem *base)
{
size_t conf_size, inq_size, outq_size, specific_size;
struct ipu6_fw_syscom_config *config_host_addr;
unsigned int sizeinput = 0, sizeoutput = 0;
struct ipu6_fw_sys_queue_res res;
struct ipu6_fw_com_context *ctx;
struct device *dev = &adev->auxdev.dev;
size_t sizeall, offset;
void *specific_host_addr;
unsigned int i;
if (!cfg || !cfg->cell_start || !cfg->cell_ready)
return NULL;
ctx = kzalloc_obj(*ctx);
if (!ctx)
return NULL;
ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET;
ctx->adev = adev;
ctx->cell_start = cfg->cell_start;
ctx->cell_ready = cfg->cell_ready;
ctx->buttress_boot_offset = cfg->buttress_boot_offset;
ctx->base_addr = base;
/*
* Allocate DMA mapped memory. Allocate one big chunk.
*/
/* Base cfg for FW */
conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8);
/* Descriptions of the queues */
inq_size = size_mul(cfg->num_input_queues,
sizeof(struct ipu6_fw_sys_queue));
outq_size = size_mul(cfg->num_output_queues,
sizeof(struct ipu6_fw_sys_queue));
/* FW specific information structure */
specific_size = roundup(cfg->specific_size, 8);
sizeall = conf_size + inq_size + outq_size + specific_size;
for (i = 0; i < cfg->num_input_queues; i++)
sizeinput += size_mul(cfg->input[i].queue_size + 1,
cfg->input[i].token_size);
for (i = 0; i < cfg->num_output_queues; i++)
sizeoutput += size_mul(cfg->output[i].queue_size + 1,
cfg->output[i].token_size);
sizeall += sizeinput + sizeoutput;
ctx->dma_buffer = ipu6_dma_alloc(adev, sizeall, &ctx->dma_addr,
GFP_KERNEL, 0);
if (!ctx->dma_buffer) {
dev_err(dev, "failed to allocate dma memory\n");
kfree(ctx);
return NULL;
}
ctx->dma_size = sizeall;
config_host_addr = ctx->dma_buffer;
ctx->config_vied_addr = ctx->dma_addr;
offset = conf_size;
ctx->input_queue = ctx->dma_buffer + offset;
config_host_addr->input_queue = ctx->dma_addr + offset;
config_host_addr->num_input_queues = cfg->num_input_queues;
offset += inq_size;
ctx->output_queue = ctx->dma_buffer + offset;
config_host_addr->output_queue = ctx->dma_addr + offset;
config_host_addr->num_output_queues = cfg->num_output_queues;
/* copy firmware specific data */
offset += outq_size;
specific_host_addr = ctx->dma_buffer + offset;
config_host_addr->specific_addr = ctx->dma_addr + offset;
config_host_addr->specific_size = cfg->specific_size;
if (cfg->specific_addr && cfg->specific_size)
memcpy(specific_host_addr, cfg->specific_addr,
cfg->specific_size);
ipu6_dma_sync_single(adev, ctx->config_vied_addr, sizeall);
/* initialize input queues */
offset += specific_size;
res.reg = SYSCOM_QPR_BASE_REG;
res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
res.vied_address = ctx->dma_addr + offset;
for (i = 0; i < cfg->num_input_queues; i++)
ipu6_sys_queue_init(ctx->input_queue + i,
cfg->input[i].queue_size,
cfg->input[i].token_size, &res);
/* initialize output queues */
offset += sizeinput;
res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
res.vied_address = ctx->dma_addr + offset;
for (i = 0; i < cfg->num_output_queues; i++) {
ipu6_sys_queue_init(ctx->output_queue + i,
cfg->output[i].queue_size,
cfg->output[i].token_size, &res);
}
return ctx;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, "INTEL_IPU6");
int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx)
{
/* write magic pattern to disable the tunit trace */
writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4);
/* Check if SP is in valid state */
if (!ctx->cell_ready(ctx->adev))
return -EIO;
/* store syscom uninitialized command */
writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4);
/* store syscom uninitialized state */
writel(SYSCOM_STATE_UNINIT,
BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
/* store firmware configuration address */
writel(ctx->config_vied_addr,
BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_CONFIG_ID));
ctx->cell_start(ctx->adev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, "INTEL_IPU6");
int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx)
{
int state;
state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
if (state != SYSCOM_STATE_READY)
return -EBUSY;
/* set close request flag */
writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr +
SYSCOM_COMMAND_REG * 4);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, "INTEL_IPU6");
int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force)
{
/* check if release is forced, an verify cell state if it is not */
if (!force && !ctx->cell_ready(ctx->adev))
return -EBUSY;
ipu6_dma_free(ctx->adev, ctx->dma_size,
ctx->dma_buffer, ctx->dma_addr, 0);
kfree(ctx);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, "INTEL_IPU6");
bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx)
{
int state;
state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
ctx->buttress_boot_offset,
SYSCOM_STATE_ID));
return state == SYSCOM_STATE_READY;
}
EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, "INTEL_IPU6");
void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr, rd;
unsigned int packets;
unsigned int index;
wr = readl(q_dmem + FW_COM_WR_REG);
rd = readl(q_dmem + FW_COM_RD_REG);
if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
return NULL;
if (wr < rd)
packets = rd - wr - 1;
else
packets = q->size - (wr - rd + 1);
if (!packets)
return NULL;
index = readl(q_dmem + FW_COM_WR_REG);
return (void *)((uintptr_t)q->host_address + index * q->token_size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, "INTEL_IPU6");
void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1;
if (wr >= q->size)
wr = 0;
writel(wr, q_dmem + FW_COM_WR_REG);
}
EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, "INTEL_IPU6");
void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int wr, rd;
unsigned int packets;
wr = readl(q_dmem + FW_COM_WR_REG);
rd = readl(q_dmem + FW_COM_RD_REG);
if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
return NULL;
if (wr < rd)
wr += q->size;
packets = wr - rd;
if (!packets)
return NULL;
return (void *)((uintptr_t)q->host_address + rd * q->token_size);
}
EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, "INTEL_IPU6");
void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
{
struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1;
if (rd >= q->size)
rd = 0;
writel(rd, q_dmem + FW_COM_RD_REG);
}
EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, "INTEL_IPU6");
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_FW_COM_H
#define IPU6_FW_COM_H
struct ipu6_fw_com_context;
struct ipu6_bus_device;
struct ipu6_fw_syscom_queue_config {
unsigned int queue_size; /* tokens per queue */
unsigned int token_size; /* bytes per token */
};
#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0
struct ipu6_fw_com_cfg {
unsigned int num_input_queues;
unsigned int num_output_queues;
struct ipu6_fw_syscom_queue_config *input;
struct ipu6_fw_syscom_queue_config *output;
unsigned int dmem_addr;
/* firmware-specific configuration data */
void *specific_addr;
unsigned int specific_size;
int (*cell_ready)(struct ipu6_bus_device *adev);
void (*cell_start)(struct ipu6_bus_device *adev);
unsigned int buttress_boot_offset;
};
void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
struct ipu6_bus_device *adev, void __iomem *base);
int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx);
bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx);
int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx);
int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force);
void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
#endif
@@ -0,0 +1,487 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/cacheflush.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include "ipu6-bus.h"
#include "ipu6-fw-com.h"
#include "ipu6-isys.h"
#include "ipu6-platform-isys-csi2-reg.h"
#include "ipu6-platform-regs.h"
static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = {
"STREAM_OPEN",
"STREAM_START",
"STREAM_START_AND_CAPTURE",
"STREAM_CAPTURE",
"STREAM_STOP",
"STREAM_FLUSH",
"STREAM_CLOSE"
};
static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id)
{
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_isys_proxy_resp_info_abi *resp;
int ret;
resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
if (!resp)
return 1;
dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n",
resp->request_id, resp->error_info.error,
resp->error_info.error_details);
ret = req_id == resp->request_id ? 0 : -EIO;
ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
return ret;
}
int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
unsigned int req_id,
unsigned int index,
unsigned int offset, u32 value)
{
struct ipu6_fw_com_context *ctx = isys->fwcom;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_proxy_send_queue_token *token;
unsigned int timeout = 1000;
int ret;
dev_dbg(dev,
"proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n",
req_id, index, offset, value);
token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
if (!token)
return -EBUSY;
token->request_id = req_id;
token->region_index = index;
token->offset = offset;
token->value = value;
ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
do {
usleep_range(100, 110);
ret = handle_proxy_response(isys, req_id);
if (!ret)
break;
if (ret == -EIO) {
dev_err(dev, "Proxy respond with unexpected id\n");
break;
}
timeout--;
} while (ret && timeout);
if (!timeout)
dev_err(dev, "Proxy response timed out\n");
return ret;
}
int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle,
void *cpu_mapped_buf,
dma_addr_t dma_mapped_buf,
size_t size, u16 send_type)
{
struct ipu6_fw_com_context *ctx = isys->fwcom;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_fw_send_queue_token *token;
if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE)
return -EINVAL;
dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]);
/*
* Time to flush cache in case we have some payload. Not all messages
* have that
*/
if (cpu_mapped_buf)
clflush_cache_range(cpu_mapped_buf, size);
token = ipu6_send_get_token(ctx,
stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
if (!token)
return -EBUSY;
token->payload = dma_mapped_buf;
token->buf_handle = (unsigned long)cpu_mapped_buf;
token->send_type = send_type;
ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
return 0;
}
int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle, u16 send_type)
{
return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0,
send_type);
}
int ipu6_fw_isys_close(struct ipu6_isys *isys)
{
struct device *dev = &isys->adev->auxdev.dev;
int retry = IPU6_ISYS_CLOSE_RETRY;
unsigned long flags;
void *fwcom;
int ret;
/*
* Stop the isys fw. Actual close takes
* some time as the FW must stop its actions including code fetch
* to SP icache.
* spinlock to wait the interrupt handler to be finished
*/
spin_lock_irqsave(&isys->power_lock, flags);
ret = ipu6_fw_com_close(isys->fwcom);
fwcom = isys->fwcom;
isys->fwcom = NULL;
spin_unlock_irqrestore(&isys->power_lock, flags);
if (ret)
dev_err(dev, "Device close failure: %d\n", ret);
/* release probably fails if the close failed. Let's try still */
do {
usleep_range(400, 500);
ret = ipu6_fw_com_release(fwcom, 0);
retry--;
} while (ret && retry);
if (ret) {
dev_err(dev, "Device release time out %d\n", ret);
spin_lock_irqsave(&isys->power_lock, flags);
isys->fwcom = fwcom;
spin_unlock_irqrestore(&isys->power_lock, flags);
}
return ret;
}
void ipu6_fw_isys_cleanup(struct ipu6_isys *isys)
{
int ret;
ret = ipu6_fw_com_release(isys->fwcom, 1);
if (ret < 0)
dev_warn(&isys->adev->auxdev.dev,
"Device busy, fw_com release failed.");
isys->fwcom = NULL;
}
static void start_sp(struct ipu6_bus_device *adev)
{
struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
void __iomem *spc_regs_base = isys->pdata->base +
isys->pdata->ipdata->hw_variant.spc_offset;
u32 val = IPU6_ISYS_SPC_STATUS_START |
IPU6_ISYS_SPC_STATUS_RUN |
IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0;
writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
}
static int query_sp(struct ipu6_bus_device *adev)
{
struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
void __iomem *spc_regs_base = isys->pdata->base +
isys->pdata->ipdata->hw_variant.spc_offset;
u32 val;
val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
/* return true when READY == 1, START == 0 */
val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START;
return val == IPU6_ISYS_SPC_STATUS_READY;
}
static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys,
struct ipu6_fw_com_cfg *fwcom,
unsigned int num_streams)
{
unsigned int max_send_queues, max_sram_blocks, max_devq_size;
struct ipu6_fw_syscom_queue_config *input_queue_cfg;
struct ipu6_fw_syscom_queue_config *output_queue_cfg;
struct device *dev = &isys->adev->auxdev.dev;
int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY;
int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV;
int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG;
int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES;
int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES;
int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES;
struct ipu6_fw_isys_fw_config *isys_fw_cfg;
u32 num_in_message_queues;
unsigned int max_streams;
unsigned int size;
unsigned int i;
max_streams = isys->pdata->ipdata->max_streams;
max_send_queues = isys->pdata->ipdata->max_send_queues;
max_sram_blocks = isys->pdata->ipdata->max_sram_blocks;
max_devq_size = isys->pdata->ipdata->max_devq_size;
num_in_message_queues = clamp(num_streams, 1U, max_streams);
isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL);
if (!isys_fw_cfg)
return -ENOMEM;
isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES;
isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES;
isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues;
isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES;
/* Common msg/dev return queue */
isys_fw_cfg->num_recv_queues[type_dev] = 0;
isys_fw_cfg->num_recv_queues[type_msg] = 1;
size = sizeof(*input_queue_cfg) * max_send_queues;
input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
if (!input_queue_cfg)
return -ENOMEM;
size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES;
output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
if (!output_queue_cfg)
return -ENOMEM;
fwcom->input = input_queue_cfg;
fwcom->output = output_queue_cfg;
fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] +
isys_fw_cfg->num_send_queues[type_dev] +
isys_fw_cfg->num_send_queues[type_msg];
fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] +
isys_fw_cfg->num_recv_queues[type_dev] +
isys_fw_cfg->num_recv_queues[type_msg];
/* SRAM partitioning. Equal partitioning is set. */
for (i = 0; i < max_sram_blocks; i++) {
if (i < num_in_message_queues)
isys_fw_cfg->buffer_partition.num_gda_pages[i] =
(IPU6_DEVICE_GDA_NR_PAGES *
IPU6_DEVICE_GDA_VIRT_FACTOR) /
num_in_message_queues;
else
isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0;
}
/* FW assumes proxy interface at fwcom queue 0 */
for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) {
input_queue_cfg[i].token_size =
sizeof(struct ipu6_fw_proxy_send_queue_token);
input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE;
}
for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) {
input_queue_cfg[base_dev_send + i].token_size =
sizeof(struct ipu6_fw_send_queue_token);
input_queue_cfg[base_dev_send + i].queue_size = max_devq_size;
}
for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) {
input_queue_cfg[base_msg_send + i].token_size =
sizeof(struct ipu6_fw_send_queue_token);
input_queue_cfg[base_msg_send + i].queue_size =
IPU6_ISYS_SIZE_SEND_QUEUE;
}
for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) {
output_queue_cfg[i].token_size =
sizeof(struct ipu6_fw_proxy_resp_queue_token);
output_queue_cfg[i].queue_size =
IPU6_ISYS_SIZE_PROXY_RECV_QUEUE;
}
/* There is no recv DEV queue */
for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) {
output_queue_cfg[base_msg_recv + i].token_size =
sizeof(struct ipu6_fw_resp_queue_token);
output_queue_cfg[base_msg_recv + i].queue_size =
IPU6_ISYS_SIZE_RECV_QUEUE;
}
fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset;
fwcom->specific_addr = isys_fw_cfg;
fwcom->specific_size = sizeof(*isys_fw_cfg);
return 0;
}
int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams)
{
struct device *dev = &isys->adev->auxdev.dev;
int retry = IPU6_ISYS_OPEN_RETRY;
struct ipu6_fw_com_cfg fwcom = {
.cell_start = start_sp,
.cell_ready = query_sp,
.buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET,
};
int ret;
ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams);
isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev,
isys->pdata->base);
if (!isys->fwcom) {
dev_err(dev, "isys fw com prepare failed\n");
return -EIO;
}
ret = ipu6_fw_com_open(isys->fwcom);
if (ret) {
dev_err(dev, "isys fw com open failed %d\n", ret);
return ret;
}
do {
usleep_range(400, 500);
if (ipu6_fw_com_ready(isys->fwcom))
break;
retry--;
} while (retry > 0);
if (!retry) {
dev_err(dev, "isys port open ready failed %d\n", ret);
ipu6_fw_isys_close(isys);
ret = -EIO;
}
return ret;
}
struct ipu6_fw_isys_resp_info_abi *
ipu6_fw_isys_get_resp(void *context, unsigned int queue)
{
return ipu6_recv_get_token(context, queue);
}
void ipu6_fw_isys_put_resp(void *context, unsigned int queue)
{
ipu6_recv_put_token(context, queue);
}
void ipu6_fw_isys_dump_stream_cfg(struct device *dev,
struct ipu6_fw_isys_stream_cfg_data_abi *cfg)
{
unsigned int i;
dev_dbg(dev, "-----------------------------------------------------\n");
dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n");
dev_dbg(dev, "compfmt = %d\n", cfg->vc);
dev_dbg(dev, "src = %d\n", cfg->src);
dev_dbg(dev, "vc = %d\n", cfg->vc);
dev_dbg(dev, "isl_use = %d\n", cfg->isl_use);
dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type);
dev_dbg(dev, "send_irq_sof_discarded = %d\n",
cfg->send_irq_sof_discarded);
dev_dbg(dev, "send_irq_eof_discarded = %d\n",
cfg->send_irq_eof_discarded);
dev_dbg(dev, "send_resp_sof_discarded = %d\n",
cfg->send_resp_sof_discarded);
dev_dbg(dev, "send_resp_eof_discarded = %d\n",
cfg->send_resp_eof_discarded);
dev_dbg(dev, "crop:\n");
dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset,
cfg->crop.top_offset);
dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset,
cfg->crop.bottom_offset);
dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins);
for (i = 0; i < cfg->nof_input_pins; i++) {
dev_dbg(dev, "input pin[%d]:\n", i);
dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt);
dev_dbg(dev, "\t.mipi_store_mode = %d\n",
cfg->input_pins[i].mipi_store_mode);
dev_dbg(dev, "\t.bits_per_pix = %d\n",
cfg->input_pins[i].bits_per_pix);
dev_dbg(dev, "\t.mapped_dt = 0x%0x\n",
cfg->input_pins[i].mapped_dt);
dev_dbg(dev, "\t.input_res = %dx%d\n",
cfg->input_pins[i].input_res.width,
cfg->input_pins[i].input_res.height);
dev_dbg(dev, "\t.mipi_decompression = %d\n",
cfg->input_pins[i].mipi_decompression);
dev_dbg(dev, "\t.capture_mode = %d\n",
cfg->input_pins[i].capture_mode);
}
dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins);
for (i = 0; i < cfg->nof_output_pins; i++) {
dev_dbg(dev, "output_pin[%d]:\n", i);
dev_dbg(dev, "\t.input_pin_id = %d\n",
cfg->output_pins[i].input_pin_id);
dev_dbg(dev, "\t.output_res = %dx%d\n",
cfg->output_pins[i].output_res.width,
cfg->output_pins[i].output_res.height);
dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride);
dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt);
dev_dbg(dev, "\t.payload_buf_size = %d\n",
cfg->output_pins[i].payload_buf_size);
dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft);
dev_dbg(dev, "\t.watermark_in_lines = %d\n",
cfg->output_pins[i].watermark_in_lines);
dev_dbg(dev, "\t.send_irq = %d\n",
cfg->output_pins[i].send_irq);
dev_dbg(dev, "\t.reserve_compression = %d\n",
cfg->output_pins[i].reserve_compression);
dev_dbg(dev, "\t.snoopable = %d\n",
cfg->output_pins[i].snoopable);
dev_dbg(dev, "\t.error_handling_enable = %d\n",
cfg->output_pins[i].error_handling_enable);
dev_dbg(dev, "\t.sensor_type = %d\n",
cfg->output_pins[i].sensor_type);
}
dev_dbg(dev, "-----------------------------------------------------\n");
}
void
ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
struct ipu6_fw_isys_frame_buff_set_abi *buf,
unsigned int outputs)
{
unsigned int i;
dev_dbg(dev, "-----------------------------------------------------\n");
dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n");
for (i = 0; i < outputs; i++) {
dev_dbg(dev, "output_pin[%d]:\n", i);
dev_dbg(dev, "\t.out_buf_id = %llu\n",
buf->output_pins[i].out_buf_id);
dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr);
dev_dbg(dev, "\t.compress = %d\n",
buf->output_pins[i].compress);
}
dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof);
dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof);
dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof);
dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof);
dev_dbg(dev, "send_irq_capture_ack = 0x%x\n",
buf->send_irq_capture_ack);
dev_dbg(dev, "send_irq_capture_done = 0x%x\n",
buf->send_irq_capture_done);
dev_dbg(dev, "send_resp_capture_ack = 0x%x\n",
buf->send_resp_capture_ack);
dev_dbg(dev, "send_resp_capture_done = 0x%x\n",
buf->send_resp_capture_done);
dev_dbg(dev, "-----------------------------------------------------\n");
}
@@ -0,0 +1,596 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_FW_ISYS_H
#define IPU6_FW_ISYS_H
#include <linux/types.h>
struct device;
struct ipu6_isys;
/* Max number of Input/Output Pins */
#define IPU6_MAX_IPINS 4
#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1)
#define IPU6_STREAM_ID_MAX 16
#define IPU6_NONSECURE_STREAM_ID_MAX 12
#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX)
#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX)
#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX)
#define IPU6SE_STREAM_ID_MAX 8
#define IPU6SE_NONSECURE_STREAM_ID_MAX 4
#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX)
#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX)
#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX)
/* Single return queue for all streams/commands type */
#define IPU6_N_MAX_MSG_RECV_QUEUES 1
/* Single device queue for high priority commands (bypass in-order queue) */
#define IPU6_N_MAX_DEV_SEND_QUEUES 1
/* Single dedicated send queue for proxy interface */
#define IPU6_N_MAX_PROXY_SEND_QUEUES 1
/* Single dedicated recv queue for proxy interface */
#define IPU6_N_MAX_PROXY_RECV_QUEUES 1
/* Send queues layout */
#define IPU6_BASE_PROXY_SEND_QUEUES 0
#define IPU6_BASE_DEV_SEND_QUEUES \
(IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES)
#define IPU6_BASE_MSG_SEND_QUEUES \
(IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES)
/* Recv queues layout */
#define IPU6_BASE_PROXY_RECV_QUEUES 0
#define IPU6_BASE_MSG_RECV_QUEUES \
(IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES)
#define IPU6_N_MAX_RECV_QUEUES \
(IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES)
#define IPU6_N_MAX_SEND_QUEUES \
(IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES)
#define IPU6SE_N_MAX_SEND_QUEUES \
(IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES)
/* Max number of planes for frame formats supported by the FW */
#define IPU6_PIN_PLANES_MAX 4
#define IPU6_FW_ISYS_SENSOR_TYPE_START 14
#define IPU6_FW_ISYS_SENSOR_TYPE_END 19
#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6
#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11
/*
* Device close takes some time from last ack message to actual stopping
* of the SP processor. As long as the SP processor runs we can't proceed with
* clean up of resources.
*/
#define IPU6_ISYS_OPEN_RETRY 2000
#define IPU6_ISYS_CLOSE_RETRY 2000
#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000)
enum ipu6_fw_isys_resp_type {
IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK,
IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF,
IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF,
IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE,
IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED,
IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED,
IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED,
IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED,
IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY,
N_IPU6_FW_ISYS_RESP_TYPE
};
enum ipu6_fw_isys_send_type {
IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0,
IPU6_FW_ISYS_SEND_TYPE_STREAM_START,
IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE,
IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE,
IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP,
IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH,
IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE,
N_IPU6_FW_ISYS_SEND_TYPE
};
enum ipu6_fw_isys_queue_type {
IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0,
IPU6_FW_ISYS_QUEUE_TYPE_DEV,
IPU6_FW_ISYS_QUEUE_TYPE_MSG,
N_IPU6_FW_ISYS_QUEUE_TYPE
};
enum ipu6_fw_isys_stream_source {
IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0,
IPU6_FW_ISYS_STREAM_SRC_PORT_1,
IPU6_FW_ISYS_STREAM_SRC_PORT_2,
IPU6_FW_ISYS_STREAM_SRC_PORT_3,
IPU6_FW_ISYS_STREAM_SRC_PORT_4,
IPU6_FW_ISYS_STREAM_SRC_PORT_5,
IPU6_FW_ISYS_STREAM_SRC_PORT_6,
IPU6_FW_ISYS_STREAM_SRC_PORT_7,
IPU6_FW_ISYS_STREAM_SRC_PORT_8,
IPU6_FW_ISYS_STREAM_SRC_PORT_9,
IPU6_FW_ISYS_STREAM_SRC_PORT_10,
IPU6_FW_ISYS_STREAM_SRC_PORT_11,
IPU6_FW_ISYS_STREAM_SRC_PORT_12,
IPU6_FW_ISYS_STREAM_SRC_PORT_13,
IPU6_FW_ISYS_STREAM_SRC_PORT_14,
IPU6_FW_ISYS_STREAM_SRC_PORT_15,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8,
IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9,
N_IPU6_FW_ISYS_STREAM_SRC
};
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \
IPU6_FW_ISYS_STREAM_SRC_PORT_6
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \
IPU6_FW_ISYS_STREAM_SRC_PORT_7
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \
IPU6_FW_ISYS_STREAM_SRC_PORT_8
#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \
IPU6_FW_ISYS_STREAM_SRC_PORT_9
#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0
#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1
/*
* enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec
* supports up to 4 virtual per physical channel
*/
enum ipu6_fw_isys_mipi_vc {
IPU6_FW_ISYS_MIPI_VC_0 = 0,
IPU6_FW_ISYS_MIPI_VC_1,
IPU6_FW_ISYS_MIPI_VC_2,
IPU6_FW_ISYS_MIPI_VC_3,
N_IPU6_FW_ISYS_MIPI_VC
};
enum ipu6_fw_isys_frame_format_type {
IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */
/* 12 bit YUV 420, Intel proprietary tiled format */
IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY,
IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */
IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */
IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */
IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */
IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */
IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */
IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */
IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */
/* Internal format, 2 y lines followed by a uvinterleaved line */
IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE,
IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */
IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */
/**
* 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit
* value, 5 bits for R, 6 bits for G and 5 bits for B.
*/
IPU6_FW_ISYS_FRAME_FORMAT_RGB565,
IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */
IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */
IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */
IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */
N_IPU6_FW_ISYS_FRAME_FORMAT
};
enum ipu6_fw_isys_pin_type {
/* captured as MIPI packets */
IPU6_FW_ISYS_PIN_TYPE_MIPI = 0,
/* captured through the SoC path */
IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3,
};
/*
* enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach
* MIPI SRAM with the long packet header or
* if not, then only option is to capture it with pin type MIPI.
*/
enum ipu6_fw_isys_mipi_store_mode {
IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0,
IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER,
N_IPU6_FW_ISYS_MIPI_STORE_MODE
};
enum ipu6_fw_isys_capture_mode {
IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0,
IPU6_FW_ISYS_CAPTURE_MODE_BURST,
N_IPU6_FW_ISYS_CAPTURE_MODE,
};
enum ipu6_fw_isys_sensor_mode {
IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0,
IPU6_FW_ISYS_SENSOR_MODE_TOBII,
N_IPU6_FW_ISYS_SENSOR_MODE,
};
enum ipu6_fw_isys_error {
IPU6_FW_ISYS_ERROR_NONE = 0,
IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY,
IPU6_FW_ISYS_ERROR_HW_CONSISTENCY,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION,
IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION,
IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES,
IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO,
IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO,
IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC,
IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION,
IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL,
N_IPU6_FW_ISYS_ERROR
};
enum ipu6_fw_proxy_error {
IPU6_FW_PROXY_ERROR_NONE = 0,
IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION,
IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET,
N_IPU6_FW_PROXY_ERROR
};
/* firmware ABI structure below are aligned in firmware, no need pack */
struct ipu6_fw_isys_buffer_partition_abi {
u32 num_gda_pages[IPU6_STREAM_ID_MAX];
};
struct ipu6_fw_isys_fw_config {
struct ipu6_fw_isys_buffer_partition_abi buffer_partition;
u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
};
/*
* struct ipu6_fw_isys_resolution_abi: Generic resolution structure.
*/
struct ipu6_fw_isys_resolution_abi {
u32 width;
u32 height;
};
/**
* struct ipu6_fw_isys_output_pin_payload_abi - ISYS output pin buffer
* @out_buf_id: Points to output pin buffer - buffer identifier
* @addr: Points to output pin buffer - CSS Virtual Address
* @compress: Request frame compression (1), or not (0)
*/
struct ipu6_fw_isys_output_pin_payload_abi {
u64 out_buf_id;
u32 addr;
u32 compress;
};
/**
* struct ipu6_fw_isys_output_pin_info_abi - ISYS output pin info
* @output_res: output pin resolution
* @stride: output stride in Bytes (not valid for statistics)
* @watermark_in_lines: pin watermark level in lines
* @payload_buf_size: minimum size in Bytes of all buffers that will be
* supplied for capture on this pin
* @ts_offsets: ts_offsets
* @s2m_pixel_soc_pixel_remapping: pixel soc remapping (see the definition of
* S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING)
* @csi_be_soc_pixel_remapping: see s2m_pixel_soc_pixel_remapping
* @send_irq: assert if pin event should trigger irq
* @input_pin_id: related input pin id
* @pt: pin type -real format "enum ipu6_fw_isys_pin_type"
* @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type"
* @reserved: a reserved field
* @reserve_compression: reserve compression resources for pin
* @snoopable: snoopable
* @error_handling_enable: enable error handling
* @sensor_type: sensor_type
*/
struct ipu6_fw_isys_output_pin_info_abi {
struct ipu6_fw_isys_resolution_abi output_res;
u32 stride;
u32 watermark_in_lines;
u32 payload_buf_size;
u32 ts_offsets[IPU6_PIN_PLANES_MAX];
u32 s2m_pixel_soc_pixel_remapping;
u32 csi_be_soc_pixel_remapping;
u8 send_irq;
u8 input_pin_id;
u8 pt;
u8 ft;
u8 reserved;
u8 reserve_compression;
u8 snoopable;
u8 error_handling_enable;
u32 sensor_type;
};
/**
* struct ipu6_fw_isys_input_pin_info_abi - ISYS input pin info
* @input_res: input resolution
* @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type)
* @mipi_store_mode: defines if legacy long packet header will be stored or
* discarded if discarded, output pin type for this
* input pin can only be MIPI
* (enum ipu6_fw_isys_mipi_store_mode)
* @bits_per_pix: native bits per pixel
* @mapped_dt: actual data type from sensor
* @mipi_decompression: defines which compression will be in mipi backend
* @crop_first_and_last_lines: Control whether to crop the first and last line
* of the input image. Crop done by HW device.
* @capture_mode: mode of capture, regular or burst, default value is regular
* @reserved: a reserved field
*/
struct ipu6_fw_isys_input_pin_info_abi {
struct ipu6_fw_isys_resolution_abi input_res;
u8 dt;
u8 mipi_store_mode;
u8 bits_per_pix;
u8 mapped_dt;
u8 mipi_decompression;
u8 crop_first_and_last_lines;
u8 capture_mode;
u8 reserved;
};
/**
* struct ipu6_fw_isys_cropping_abi - ISYS cropping coordinates
* @top_offset: Top offset
* @left_offset: Left offset
* @bottom_offset: Bottom offset
* @right_offset: Right offset
*/
struct ipu6_fw_isys_cropping_abi {
s32 top_offset;
s32 left_offset;
s32 bottom_offset;
s32 right_offset;
};
/**
* struct ipu6_fw_isys_stream_cfg_data_abi - ISYS stream configuration data
* ISYS stream configuration data structure
* @crop: for extended use and is not used in FW currently
* @input_pins: input pin descriptors
* @output_pins: output pin descriptors
* @compfmt: de-compression setting for User Defined Data
* @nof_input_pins: number of input pins
* @nof_output_pins: number of output pins
* @send_irq_sof_discarded: send irq on discarded frame sof response
* - if '1' it will override the send_resp_sof_discarded
* and send the response
* - if '0' the send_resp_sof_discarded will determine
* whether to send the response
* @send_irq_eof_discarded: send irq on discarded frame eof response
* - if '1' it will override the send_resp_eof_discarded
* and send the response
* - if '0' the send_resp_eof_discarded will determine
* whether to send the response
* @send_resp_sof_discarded: send response for discarded frame sof detected,
* used only when send_irq_sof_discarded is '0'
* @send_resp_eof_discarded: send response for discarded frame eof detected,
* used only when send_irq_eof_discarded is '0'
* @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1
* @vc: MIPI Virtual Channel (up to 4 virtual per physical channel)
* @isl_use: indicates whether stream requires ISL and how
* @sensor_type: type of connected sensor, tobii or others, default is 0
* @reserved: a reserved field
* @reserved2: a reserved field
*/
struct ipu6_fw_isys_stream_cfg_data_abi {
struct ipu6_fw_isys_cropping_abi crop;
struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS];
struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS];
u32 compfmt;
u8 nof_input_pins;
u8 nof_output_pins;
u8 send_irq_sof_discarded;
u8 send_irq_eof_discarded;
u8 send_resp_sof_discarded;
u8 send_resp_eof_discarded;
u8 src;
u8 vc;
u8 isl_use;
u8 sensor_type;
u8 reserved;
u8 reserved2;
};
/**
* struct ipu6_fw_isys_frame_buff_set_abi - ISYS frame buffer set (request)
* @output_pins: output pin addresses
* @send_irq_sof: send irq on frame sof response
* - if '1' it will override the send_resp_sof and
* send the response
* - if '0' the send_resp_sof will determine whether to
* send the response
* @send_irq_eof: send irq on frame eof response
* - if '1' it will override the send_resp_eof and
* send the response
* - if '0' the send_resp_eof will determine whether to
* send the response
* @send_irq_capture_ack: send irq on capture ack
* @send_irq_capture_done: send irq on capture done
* @send_resp_sof: send response for frame sof detected,
* used only when send_irq_sof is '0'
* @send_resp_eof: send response for frame eof detected,
* used only when send_irq_eof is '0'
* @send_resp_capture_ack: send response for capture ack event
* @send_resp_capture_done: send response for capture done event
* @reserved: a reserved field
*/
struct ipu6_fw_isys_frame_buff_set_abi {
struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS];
u8 send_irq_sof;
u8 send_irq_eof;
u8 send_irq_capture_ack;
u8 send_irq_capture_done;
u8 send_resp_sof;
u8 send_resp_eof;
u8 send_resp_capture_ack;
u8 send_resp_capture_done;
u8 reserved[8];
};
/**
* struct ipu6_fw_isys_error_info_abi - ISYS error information
* @error: error code if something went wrong
* @error_details: depending on error code, it may contain additional error info
*/
struct ipu6_fw_isys_error_info_abi {
u32 error;
u32 error_details;
};
/**
* struct ipu6_fw_isys_resp_info_abi - ISYS firmware response
* @buf_id: buffer ID
* @pin: this var is only valid for pin event related responses,
* contains pin addresses
* @error_info: error information from the FW
* @timestamp: Time information for event if available
* @stream_handle: stream id the response corresponds to
* @type: response type (enum ipu6_fw_isys_resp_type)
* @pin_id: pin id that the pin payload corresponds to
* @reserved: a reserved field
* @reserved2: a reserved field
*/
struct ipu6_fw_isys_resp_info_abi {
u64 buf_id;
struct ipu6_fw_isys_output_pin_payload_abi pin;
struct ipu6_fw_isys_error_info_abi error_info;
u32 timestamp[2];
u8 stream_handle;
u8 type;
u8 pin_id;
u8 reserved;
u32 reserved2;
};
/**
* struct ipu6_fw_isys_proxy_error_info_abi - ISYS proxy error
* @error: error code if something went wrong
* @error_details: depending on error code, it may contain additional error info
*/
struct ipu6_fw_isys_proxy_error_info_abi {
u32 error;
u32 error_details;
};
struct ipu6_fw_isys_proxy_resp_info_abi {
u32 request_id;
struct ipu6_fw_isys_proxy_error_info_abi error_info;
};
/**
* struct ipu6_fw_proxy_write_queue_token - ISYS proxy write queue token
* @request_id: update id for the specific proxy write request
* @region_index: Region id for the proxy write request
* @offset: Offset of the write request according to the base address
* of the region
* @value: Value that is requested to be written with the proxy write request
*/
struct ipu6_fw_proxy_write_queue_token {
u32 request_id;
u32 region_index;
u32 offset;
u32 value;
};
/**
* struct ipu6_fw_resp_queue_token - ISYS response queue token
* @resp_info: response info
*/
struct ipu6_fw_resp_queue_token {
struct ipu6_fw_isys_resp_info_abi resp_info;
};
/**
* struct ipu6_fw_send_queue_token - ISYS send queue token
* @buf_handle: buffer handle
* @payload: payload
* @send_type: send_type
* @stream_id: stream_id
*/
struct ipu6_fw_send_queue_token {
u64 buf_handle;
u32 payload;
u16 send_type;
u16 stream_id;
};
/**
* struct ipu6_fw_proxy_resp_queue_token - ISYS proxy response queue token
* @proxy_resp_info: proxy response info
*/
struct ipu6_fw_proxy_resp_queue_token {
struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info;
};
/**
* struct ipu6_fw_proxy_send_queue_token - SYS proxy send queue token
* @request_id: request_id
* @region_index: region_index
* @offset: offset
* @value: value
*/
struct ipu6_fw_proxy_send_queue_token {
u32 request_id;
u32 region_index;
u32 offset;
u32 value;
};
void
ipu6_fw_isys_dump_stream_cfg(struct device *dev,
struct ipu6_fw_isys_stream_cfg_data_abi *cfg);
void
ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
struct ipu6_fw_isys_frame_buff_set_abi *buf,
unsigned int outputs);
int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams);
int ipu6_fw_isys_close(struct ipu6_isys *isys);
int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle, u16 send_type);
int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
const unsigned int stream_handle,
void *cpu_mapped_buf, dma_addr_t dma_mapped_buf,
size_t size, u16 send_type);
int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
unsigned int req_id,
unsigned int index,
unsigned int offset, u32 value);
void ipu6_fw_isys_cleanup(struct ipu6_isys *isys);
struct ipu6_fw_isys_resp_info_abi *
ipu6_fw_isys_get_resp(void *context, unsigned int queue);
void ipu6_fw_isys_put_resp(void *context, unsigned int queue);
#endif
@@ -0,0 +1,647 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/minmax.h>
#include <linux/sprintf.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-isys-subdev.h"
#include "ipu6-platform-isys-csi2-reg.h"
static const u32 csi2_supported_codes[] = {
MEDIA_BUS_FMT_RGB565_1X16,
MEDIA_BUS_FMT_RGB888_1X24,
MEDIA_BUS_FMT_UYVY8_1X16,
MEDIA_BUS_FMT_YUYV8_1X16,
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SBGGR12_1X12,
MEDIA_BUS_FMT_SGBRG12_1X12,
MEDIA_BUS_FMT_SGRBG12_1X12,
MEDIA_BUS_FMT_SRGGB12_1X12,
MEDIA_BUS_FMT_SBGGR8_1X8,
MEDIA_BUS_FMT_SGBRG8_1X8,
MEDIA_BUS_FMT_SGRBG8_1X8,
MEDIA_BUS_FMT_SRGGB8_1X8,
MEDIA_BUS_FMT_Y8_1X8,
MEDIA_BUS_FMT_Y10_1X10,
MEDIA_BUS_FMT_Y12_1X12,
MEDIA_BUS_FMT_Y16_1X16,
MEDIA_BUS_FMT_META_8,
MEDIA_BUS_FMT_META_10,
MEDIA_BUS_FMT_META_12,
MEDIA_BUS_FMT_META_16,
MEDIA_BUS_FMT_META_24,
0
};
/*
* Strings corresponding to CSI-2 receiver errors are here.
* Corresponding macros are defined in the header file.
*/
static const struct ipu6_csi2_error dphy_rx_errors[] = {
{ "Single packet header error corrected", true },
{ "Multiple packet header errors detected", true },
{ "Payload checksum (CRC) error", true },
{ "Transfer FIFO overflow", false },
{ "Reserved short packet data type detected", true },
{ "Reserved long packet data type detected", true },
{ "Incomplete long packet detected", false },
{ "Frame sync error", false },
{ "Line sync error", false },
{ "DPHY recoverable synchronization error", true },
{ "DPHY fatal error", false },
{ "DPHY elastic FIFO overflow", false },
{ "Inter-frame short packet discarded", true },
{ "Inter-frame long packet discarded", true },
{ "MIPI pktgen overflow", false },
{ "MIPI pktgen data loss", false },
{ "FIFO overflow", false },
{ "Lane deskew", false },
{ "SOT sync error", false },
{ "HSIDLE detected", false }
};
s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2)
{
struct media_pad *src_pad;
if (!csi2)
return -EINVAL;
src_pad = media_pad_remote_pad_unique(&csi2->asd.sd.entity.pads[CSI2_PAD_SINK]);
if (IS_ERR(src_pad)) {
dev_err(&csi2->isys->adev->auxdev.dev,
"can't get source pad of %s (%pe)\n",
csi2->asd.sd.name, src_pad);
return PTR_ERR(src_pad);
}
return v4l2_get_link_freq(src_pad, 0, 0);
}
static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct device *dev = &csi2->isys->adev->auxdev.dev;
dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n",
sub->type, sub->id);
switch (sub->type) {
case V4L2_EVENT_FRAME_SYNC:
return v4l2_event_subscribe(fh, sub, 10, NULL);
case V4L2_EVENT_CTRL:
return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
default:
return -EINVAL;
}
}
static const struct v4l2_subdev_core_ops csi2_sd_core_ops = {
.subscribe_event = csi2_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};
/*
* The input system CSI2+ receiver has several
* parameters affecting the receiver timings. These depend
* on the MIPI bus frequency F in Hz (sensor transmitter rate)
* as follows:
* register value = (A/1e9 + B * UI) / COUNT_ACC
* where
* UI = 1 / (2 * F) in seconds
* COUNT_ACC = counter accuracy in seconds
* COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8.
*
* A and B are coefficients from the table below,
* depending whether the register minimum or maximum value is
* calculated.
* Minimum Maximum
* Clock lane A B A B
* reg_rx_csi_dly_cnt_termen_clane 0 0 38 0
* reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16
* Data lanes
* reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6
* reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4
* reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6
*
* We use the minimum values of both A and B.
*/
#define DIV_SHIFT 8
#define CSI2_ACCINV 8
static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv)
{
return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT)
/ (s32)(link_freq >> DIV_SHIFT));
}
static int
ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2,
struct ipu6_isys_csi2_timing *timing, s32 accinv)
{
struct device *dev = &csi2->isys->adev->auxdev.dev;
s64 link_freq;
link_freq = ipu6_isys_csi2_get_link_freq(csi2);
if (link_freq < 0)
return link_freq;
timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A,
CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B,
link_freq, accinv);
timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A,
CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B,
link_freq, accinv);
timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A,
CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B,
link_freq, accinv);
timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A,
CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B,
link_freq, accinv);
dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n",
timing->ctermen, timing->csettle,
timing->dtermen, timing->dsettle);
return 0;
}
void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2)
{
u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
struct ipu6_isys *isys = csi2->isys;
u32 mask;
mask = isys->pdata->ipdata->csi2.irq_mask;
writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
csi2->receiver_errors |= irq & mask;
}
void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2)
{
struct device *dev = &csi2->isys->adev->auxdev.dev;
const struct ipu6_csi2_error *errors;
u32 status;
u32 i;
/* register errors once more in case of interrupts are disabled */
ipu6_isys_register_errors(csi2);
status = csi2->receiver_errors;
csi2->receiver_errors = 0;
errors = dphy_rx_errors;
for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) {
if (status & BIT(i))
dev_err_ratelimited(dev, "csi2-%i error: %s\n",
csi2->port, errors[i].error_string);
}
}
static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd,
const struct ipu6_isys_csi2_timing *timing,
unsigned int nlanes, int enable)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct ipu6_isys *isys = csi2->isys;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_isys_csi2_config cfg;
unsigned int nports;
int ret = 0;
u32 mask = 0;
u32 i;
dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off",
csi2->port, nlanes);
cfg.port = csi2->port;
cfg.nlanes = nlanes;
mask = isys->pdata->ipdata->csi2.irq_mask;
nports = isys->pdata->ipdata->csi2.nports;
if (!enable) {
writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE);
writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE);
writel(0,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(0,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
writel(0xffffffff,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
isys->phy_set_power(isys, &cfg, timing, false);
writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
(isys->pdata->ipdata->csi2.fw_access_port_ofs,
csi2->port));
writel(0, isys->pdata->base +
CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port));
return ret;
}
/* reset port reset */
writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST);
usleep_range(100, 200);
writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST);
/* enable port clock */
for (i = 0; i < nports; i++) {
writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i));
writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
(isys->pdata->ipdata->csi2.fw_access_port_ofs, i));
}
/* enable all error related irq */
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
writel(mask,
csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
/*
* Using event from firmware instead of irq to handle CSI2 sync event
* which can reduce system wakeups. If CSI2 sync irq enabled, we need
* disable the firmware CSI2 sync event to avoid duplicate handling.
*/
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
/* configure to enable FE and PPI2CSI */
writel(0, csi2->base + CSI_REG_CSI_FE_MODE);
writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL);
writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID,
csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL);
writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1),
csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF);
writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE);
writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE);
ret = isys->phy_set_power(isys, &cfg, timing, true);
if (ret)
dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port,
ret);
return ret;
}
static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
struct ipu6_isys_csi2_timing timing = { };
struct v4l2_subdev *remote_sd;
struct media_pad *remote_pad;
u64 sink_streams;
int ret;
remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]);
remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
sink_streams =
v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK,
&streams_mask);
ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV);
if (ret)
return ret;
ret = ipu6_isys_csi2_set_stream(sd, &timing, csi2->nlanes, true);
if (ret)
return ret;
ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index,
sink_streams);
if (ret) {
ipu6_isys_csi2_set_stream(sd, NULL, 0, false);
return ret;
}
return 0;
}
static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct v4l2_subdev *remote_sd;
struct media_pad *remote_pad;
u64 sink_streams;
sink_streams =
v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK,
&streams_mask);
remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]);
remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
ipu6_isys_csi2_set_stream(sd, NULL, 0, false);
v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams);
return 0;
}
static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct device *dev = &asd->isys->adev->auxdev.dev;
struct v4l2_mbus_framefmt *sink_ffmt;
struct v4l2_mbus_framefmt *src_ffmt;
struct v4l2_rect *crop;
if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
return -EINVAL;
sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
sel->pad,
sel->stream);
if (!sink_ffmt)
return -EINVAL;
src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
if (!src_ffmt)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
if (!crop)
return -EINVAL;
/* Only vertical cropping is supported */
sel->r.left = 0;
sel->r.width = sink_ffmt->width;
/* Non-bayer formats can't be single line cropped */
if (!ipu6_isys_is_bayer_format(sink_ffmt->code))
sel->r.top &= ~1;
sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT,
sink_ffmt->height - sel->r.top);
*crop = sel->r;
/* update source pad format */
src_ffmt->width = sel->r.width;
src_ffmt->height = sel->r.height;
if (ipu6_isys_is_bayer_format(sink_ffmt->code))
src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code,
sel->r.left,
sel->r.top);
dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n",
sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height,
src_ffmt->code);
return 0;
}
static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_mbus_framefmt *sink_ffmt;
struct v4l2_rect *crop;
int ret = 0;
if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK)
return -EINVAL;
sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
sel->pad,
sel->stream);
if (!sink_ffmt)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
if (!crop)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP_DEFAULT:
case V4L2_SEL_TGT_CROP_BOUNDS:
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = sink_ffmt->width;
sel->r.height = sink_ffmt->height;
break;
case V4L2_SEL_TGT_CROP:
sel->r = *crop;
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = ipu6_isys_subdev_set_fmt,
.get_selection = ipu6_isys_csi2_get_sel,
.set_selection = ipu6_isys_csi2_set_sel,
.enum_mbus_code = ipu6_isys_subdev_enum_mbus_code,
.set_routing = ipu6_isys_subdev_set_routing,
.enable_streams = ipu6_isys_csi2_enable_streams,
.disable_streams = ipu6_isys_csi2_disable_streams,
};
static const struct v4l2_subdev_ops csi2_sd_ops = {
.core = &csi2_sd_core_ops,
.pad = &csi2_sd_pad_ops,
};
static const struct media_entity_operations csi2_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
.has_pad_interdep = v4l2_subdev_has_pad_interdep,
};
void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2)
{
if (!csi2->isys)
return;
v4l2_device_unregister_subdev(&csi2->asd.sd);
v4l2_subdev_cleanup(&csi2->asd.sd);
ipu6_isys_subdev_cleanup(&csi2->asd);
csi2->isys = NULL;
}
int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2,
struct ipu6_isys *isys,
void __iomem *base, unsigned int index)
{
struct device *dev = &isys->adev->auxdev.dev;
int ret;
csi2->isys = isys;
csi2->base = base;
csi2->port = index;
csi2->asd.sd.entity.ops = &csi2_entity_ops;
csi2->asd.isys = isys;
ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS);
if (ret)
goto fail;
csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index;
csi2->asd.supported_codes = csi2_supported_codes;
snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index);
v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd);
ret = v4l2_subdev_init_finalize(&csi2->asd.sd);
if (ret) {
dev_err(dev, "failed to init v4l2 subdev\n");
goto fail;
}
ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
if (ret) {
dev_err(dev, "failed to register v4l2 subdev\n");
goto fail;
}
return 0;
fail:
ipu6_isys_csi2_cleanup(csi2);
return ret;
}
void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream)
{
struct video_device *vdev = stream->asd->sd.devnode;
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
struct v4l2_event ev = {
.type = V4L2_EVENT_FRAME_SYNC,
};
ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence);
v4l2_event_queue(vdev, &ev);
dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n",
csi2->port, ev.u.frame_sync.frame_sequence, stream->vc);
}
void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
u32 frame_sequence = atomic_read(&stream->sequence);
dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n",
csi2->port, frame_sequence);
}
int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
struct ipu6_isys_csi2 *csi2,
struct media_entity *source_entity,
struct v4l2_mbus_frame_desc_entry *entry)
{
struct v4l2_mbus_frame_desc_entry *desc_entry = NULL;
struct device *dev = &csi2->isys->adev->auxdev.dev;
struct v4l2_mbus_frame_desc desc;
struct v4l2_subdev *source;
struct media_pad *pad;
unsigned int i;
int ret;
source = media_entity_to_v4l2_subdev(source_entity);
if (!source)
return -EPIPE;
pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]);
if (!pad)
return -EPIPE;
ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc);
if (ret)
return ret;
if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
dev_err(dev, "Unsupported frame descriptor type\n");
return -EINVAL;
}
for (i = 0; i < desc.num_entries; i++) {
if (source_stream == desc.entry[i].stream) {
desc_entry = &desc.entry[i];
break;
}
}
if (!desc_entry) {
dev_err(dev, "Failed to find stream %u from remote subdev\n",
source_stream);
return -EINVAL;
}
if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) {
dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc);
return -EINVAL;
}
*entry = *desc_entry;
return 0;
}
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_CSI2_H
#define IPU6_ISYS_CSI2_H
#include <linux/container_of.h>
#include "ipu6-isys-subdev.h"
#include "ipu6-isys-video.h"
struct media_entity;
struct v4l2_mbus_frame_desc_entry;
struct ipu6_isys_video;
struct ipu6_isys;
struct ipu6_isys_stream;
#define NR_OF_CSI2_VC 16
#define INVALID_VC_ID -1
#define NR_OF_CSI2_SINK_PADS 1
#define CSI2_PAD_SINK 0
#define NR_OF_CSI2_SRC_PADS 8
#define CSI2_PAD_SRC 1
#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS)
#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0
#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0
#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95
#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8
#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0
#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0
#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85
#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2
struct ipu6_isys_csi2 {
struct ipu6_isys_subdev asd;
struct ipu6_isys *isys;
struct ipu6_isys_video av[NR_OF_CSI2_SRC_PADS];
void __iomem *base;
u32 receiver_errors;
unsigned int nlanes;
unsigned int port;
};
struct ipu6_isys_csi2_timing {
u32 ctermen;
u32 csettle;
u32 dtermen;
u32 dsettle;
};
struct ipu6_csi2_error {
const char *error_string;
bool is_info_only;
};
#define ipu6_isys_subdev_to_csi2(__sd) \
container_of(__sd, struct ipu6_isys_csi2, asd)
#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd)
s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2);
int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys,
void __iomem *base, unsigned int index);
void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2);
void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream);
void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream);
void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2);
void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2);
int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
struct ipu6_isys_csi2 *csi2,
struct media_entity *source_entity,
struct v4l2_mbus_frame_desc_entry *entry);
#endif /* IPU6_ISYS_CSI2_H */
@@ -0,0 +1,548 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-platform-isys-csi2-reg.h"
#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i))
#define IPU6_DWC_DPHY_RSTZ 0x00
#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04
#define IPU6_DWC_DPHY_HSFREQRANGE 0x08
#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c
#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10
#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14
#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18
#define IPU6_DWC_DPHY_DFT_CTRL0 0x28
#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c
#define IPU6_DWC_DPHY_DFT_CTRL2 0x30
/*
* test IFC request definition:
* - req: 0 for read, 1 for write
* - 12 bits address
* - 8bits data (will ignore for read)
* --24----16------4-----0
* --|-data-|-addr-|-req-|
*/
#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \
FIELD_PREP(GENMASK(15, 4), addr) | \
FIELD_PREP(GENMASK(1, 0), req))
#define TEST_IFC_REQ_READ 0
#define TEST_IFC_REQ_WRITE 1
#define TEST_IFC_REQ_RESET 2
#define TEST_IFC_ACCESS_MODE_FSM 0
#define TEST_IFC_ACCESS_MODE_IFC_CTL 1
enum phy_fsm_state {
PHY_FSM_STATE_POWERON = 0,
PHY_FSM_STATE_BGPON = 1,
PHY_FSM_STATE_CAL_TYPE = 2,
PHY_FSM_STATE_BURNIN_CAL = 3,
PHY_FSM_STATE_TERMCAL = 4,
PHY_FSM_STATE_OFFSETCAL = 5,
PHY_FSM_STATE_OFFSET_LANE = 6,
PHY_FSM_STATE_IDLE = 7,
PHY_FSM_STATE_ULP = 8,
PHY_FSM_STATE_DDLTUNNING = 9,
PHY_FSM_STATE_SKEW_BACKWARD = 10,
PHY_FSM_STATE_INVALID,
};
static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
dev_dbg(dev, "write: reg 0x%zx = data 0x%x", base + addr - isys_base,
data);
writel(data, base + addr);
}
static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
u32 data;
data = readl(base + addr);
dev_dbg(dev, "read: reg 0x%zx = data 0x%x", base + addr - isys_base,
data);
return data;
}
static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data, u8 shift, u8 width)
{
u32 temp;
u32 mask;
mask = (1 << width) - 1;
temp = dwc_dphy_read(isys, phy_id, addr);
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_write(isys, phy_id, addr, temp);
}
static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u8 shift, u8 width)
{
u32 val;
val = dwc_dphy_read(isys, phy_id, addr) >> shift;
return val & ((1 << width) - 1);
}
#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC)
static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 *val)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_READ, addr, 0));
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
10, DWC_DPHY_TIMEOUT);
if (ret) {
dev_err(dev, "DWC ifc request read timeout\n");
return ret;
}
*val = completion >> 8 & 0xff;
*val = FIELD_GET(GENMASK(15, 8), completion);
dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val);
return 0;
}
static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_WRITE, addr, data));
completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION);
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
10, DWC_DPHY_TIMEOUT);
if (ret)
dev_err(dev, "DWC ifc request write timeout\n");
return ret;
}
static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u32 data, u8 shift, u8 width)
{
u32 temp, mask;
int ret;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp);
if (ret)
return;
mask = (1 << width) - 1;
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_ifc_write(isys, phy_id, addr, temp);
}
static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u8 shift, u8 width)
{
int ret;
u32 val;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val);
if (ret)
return 0;
return ((val >> shift) & ((1 << width) - 1));
}
static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 fsm_state;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1);
usleep_range(10, 20);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1);
ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state,
(fsm_state == PHY_FSM_STATE_IDLE ||
fsm_state == PHY_FSM_STATE_ULP),
100, DWC_DPHY_TIMEOUT, false, isys,
phy_id, 0x1e, 0, 4);
if (ret)
dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id,
fsm_state);
return ret;
}
struct dwc_dphy_freq_range {
u8 hsfreq;
u16 min;
u16 max;
u16 default_mbps;
u16 osc_freq_target;
};
#define DPHY_FREQ_RANGE_NUM (63)
#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff)
static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = {
{0x00, 80, 97, 80, 335},
{0x10, 80, 107, 90, 335},
{0x20, 84, 118, 100, 335},
{0x30, 93, 128, 110, 335},
{0x01, 103, 139, 120, 335},
{0x11, 112, 149, 130, 335},
{0x21, 122, 160, 140, 335},
{0x31, 131, 170, 150, 335},
{0x02, 141, 181, 160, 335},
{0x12, 150, 191, 170, 335},
{0x22, 160, 202, 180, 335},
{0x32, 169, 212, 190, 335},
{0x03, 183, 228, 205, 335},
{0x13, 198, 244, 220, 335},
{0x23, 212, 259, 235, 335},
{0x33, 226, 275, 250, 335},
{0x04, 250, 301, 275, 335},
{0x14, 274, 328, 300, 335},
{0x25, 297, 354, 325, 335},
{0x35, 321, 380, 350, 335},
{0x05, 369, 433, 400, 335},
{0x16, 416, 485, 450, 335},
{0x26, 464, 538, 500, 335},
{0x37, 511, 590, 550, 335},
{0x07, 559, 643, 600, 335},
{0x18, 606, 695, 650, 335},
{0x28, 654, 748, 700, 335},
{0x39, 701, 800, 750, 335},
{0x09, 749, 853, 800, 335},
{0x19, 796, 905, 850, 335},
{0x29, 844, 958, 900, 335},
{0x3a, 891, 1010, 950, 335},
{0x0a, 939, 1063, 1000, 335},
{0x1a, 986, 1115, 1050, 335},
{0x2a, 1034, 1168, 1100, 335},
{0x3b, 1081, 1220, 1150, 335},
{0x0b, 1129, 1273, 1200, 335},
{0x1b, 1176, 1325, 1250, 335},
{0x2b, 1224, 1378, 1300, 335},
{0x3c, 1271, 1430, 1350, 335},
{0x0c, 1319, 1483, 1400, 335},
{0x1c, 1366, 1535, 1450, 335},
{0x2c, 1414, 1588, 1500, 335},
{0x3d, 1461, 1640, 1550, 208},
{0x0d, 1509, 1693, 1600, 214},
{0x1d, 1556, 1745, 1650, 221},
{0x2e, 1604, 1798, 1700, 228},
{0x3e, 1651, 1850, 1750, 234},
{0x0e, 1699, 1903, 1800, 241},
{0x1e, 1746, 1955, 1850, 248},
{0x2f, 1794, 2008, 1900, 255},
{0x3f, 1841, 2060, 1950, 261},
{0x0f, 1889, 2113, 2000, 268},
{0x40, 1936, 2165, 2050, 275},
{0x41, 1984, 2218, 2100, 281},
{0x42, 2031, 2270, 2150, 288},
{0x43, 2079, 2323, 2200, 294},
{0x44, 2126, 2375, 2250, 302},
{0x45, 2174, 2428, 2300, 308},
{0x46, 2221, 2480, 2350, 315},
{0x47, 2269, 2500, 2400, 321},
{0x48, 2316, 2500, 2450, 328},
{0x49, 2364, 2500, 2500, 335}
};
static u16 get_hsfreq_by_mbps(u32 mbps)
{
u16 best = DPHY_FREQ_RANGE_INVALID_INDEX;
unsigned int i;
for (i = 0; i < DPHY_FREQ_RANGE_NUM; i++) {
if (mbps > freqranges[i].max)
continue;
if (mbps < freqranges[i].min)
break;
if (best == DPHY_FREQ_RANGE_INVALID_INDEX ||
freqranges[i].osc_freq_target >
freqranges[best].osc_freq_target ||
(freqranges[i].osc_freq_target ==
freqranges[best].osc_freq_target &&
abs((int)mbps - (int)freqranges[i].default_mbps) <
abs((int)mbps - (int)freqranges[best].default_mbps)))
best = i;
}
return best;
}
static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys,
u32 phy_id, u32 mbps)
{
struct ipu6_bus_device *adev = isys->adev;
struct device *dev = &adev->auxdev.dev;
struct ipu6_device *isp = adev->isp;
u32 cfg_clk_freqrange;
u32 osc_freq_target;
u32 index;
dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps);
index = get_hsfreq_by_mbps(mbps);
if (index == DPHY_FREQ_RANGE_INVALID_INDEX) {
dev_err(dev, "link freq not found for mbps %u", mbps);
return -EINVAL;
}
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE,
freqranges[index].hsfreq, 0, 7);
/* Force termination Calibration */
if (isys->phy_termcal_val) {
dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1);
dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2);
dwc_dphy_ifc_write_mask(isys, phy_id, 0x209,
isys->phy_termcal_val, 4, 4);
}
/*
* Enable override to configure the DDL target oscillation
* frequency on bit 0 of register 0xe4
*/
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1);
/*
* configure registers 0xe2, 0xe3 with the
* appropriate DDL target oscillation frequency
* 0x1cc(460)
*/
osc_freq_target = freqranges[index].osc_freq_target;
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2,
osc_freq_target & 0xff, 0, 8);
dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3,
(osc_freq_target >> 8) & 0xf, 0, 4);
if (mbps < 1500) {
/* deskew_polarity_rw, for < 1.5Gbps */
dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1);
}
/*
* Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4]
* (38.4 - 17) * 4 = ~85 (0x55)
*/
cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10;
dev_dbg(dev, "ref_clk = %u clk_freqrange = %u",
isp->buttress.ref_clk, cfg_clk_freqrange);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE,
cfg_clk_freqrange, 0, 8);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1);
return 0;
}
static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master,
u32 slave, u32 mbps)
{
/* Config mastermacro */
dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1);
/* Config master PHY clk lane to drive long channel clk */
dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1);
/* Config both PHYs data lanes to get clk from long channel */
dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1);
/* Config slave PHY clk lane to bypass long channel clk to DDR clk */
dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1);
/* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */
dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2);
/* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */
dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1);
/* Turn off slave PHY LP-RX clk lane */
dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1);
dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5);
}
#define PHY_E 4
static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 rescal_done;
int ret;
ret = dwc_dphy_pwr_up(isys, phy_id);
if (ret != 0) {
dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret);
return ret;
}
/* reset forcerxmode */
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1);
dev_dbg(dev, "Dphy %u is ready!", phy_id);
if (phy_id != PHY_E || isys->phy_termcal_val)
return 0;
usleep_range(100, 200);
rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1);
if (rescal_done) {
isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id,
0x220, 2, 4);
dev_dbg(dev, "termcal done with value = %u",
isys->phy_termcal_val);
}
return 0;
}
static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id)
{
dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE,
TEST_IFC_ACCESS_MODE_FSM);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
TEST_IFC_REQ_RESET);
}
int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 phy_id, primary, secondary;
u32 nlanes, port, mbps;
s64 link_freq;
int ret;
port = cfg->port;
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
nlanes = cfg->nlanes;
/* only port 0, 2 and 4 support 4 lanes */
if (nlanes == 4 && port % 2) {
dev_err(dev, "invalid csi-port %u with %u lanes\n", port,
nlanes);
return -EINVAL;
}
link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]);
if (link_freq < 0) {
dev_err(dev, "get link freq failed(%lld).\n", link_freq);
return link_freq;
}
mbps = div_u64(link_freq, 500000);
phy_id = port;
primary = port & ~1;
secondary = primary + 1;
if (on) {
if (nlanes == 4) {
dev_dbg(dev, "config phy %u and %u in aggr mode\n",
primary, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_aggr_setup(isys, primary,
secondary, mbps);
ret = ipu6_isys_dwc_phy_config(isys, primary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary);
return ret;
}
dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n",
phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id);
return ret;
}
if (nlanes == 4) {
dev_dbg(dev, "Power down phy %u and phy %u for port %u\n",
primary, secondary, port);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
return 0;
}
dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
return 0;
}
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/io.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-platform-isys-csi2-reg.h"
/* only use BB0, BB2, BB4, and BB6 on PHY0 */
#define IPU6SE_ISYS_PHY_BB_NUM 4
#define IPU6SE_ISYS_PHY_0_BASE 0x10000
#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x))
#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x))
#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x))
#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x))
/*
* use port_cfg to configure that which data lanes used
* +---------+ +------+ +-----+
* | port0 x4<-----| | | |
* | | | port | | |
* | port1 x2<-----| | | |
* | | | <-| PHY |
* | port2 x4<-----| | | |
* | | |config| | |
* | port3 x2<-----| | | |
* +---------+ +------+ +-----+
*/
static const unsigned int csi2_port_cfg[][3] = {
{0, 0, 0x1f}, /* no link */
{4, 0, 0x10}, /* x4 + x4 config */
{2, 0, 0x12}, /* x2 + x2 config */
{1, 0, 0x13}, /* x1 + x1 config */
{2, 1, 0x15}, /* x2x1 + x2x1 config */
{1, 1, 0x16}, /* x1x1 + x1x1 config */
{2, 2, 0x18}, /* x2x2 + x2x2 config */
{1, 2, 0x19} /* x1x2 + x1x2 config */
};
/* port, nlanes, bbindex, portcfg */
static const unsigned int phy_port_cfg[][4] = {
/* sip0 */
{0, 1, 0, 0x15},
{0, 2, 0, 0x15},
{0, 4, 0, 0x15},
{0, 4, 2, 0x22},
/* sip1 */
{2, 1, 4, 0x15},
{2, 2, 4, 0x15},
{2, 4, 4, 0x15},
{2, 4, 6, 0x22}
};
static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys,
unsigned int port,
unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *base = isys->adev->isp->base;
unsigned int bbnum;
u32 val, reg, i;
dev_dbg(dev, "port %u with %u lanes", port, nlanes);
/* only support <1.5Gbps */
for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) {
/* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
val = readl(base + reg);
val |= FIELD_PREP(GENMASK(6, 1), 13);
writel(val, base + reg);
/* cphy_rx_control1.en_crc1 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i);
val = readl(base + reg);
val |= BIT(31);
writel(val, base + reg);
/* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i);
val = readl(base + reg);
val |= BIT(25) | BIT(26);
writel(val, base + reg);
/* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
val = readl(base + reg);
val |= BIT(0);
writel(val, base + reg);
}
/* Front end config, use minimal channel loss */
for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) {
if (phy_port_cfg[i][0] == port &&
phy_port_cfg[i][1] == nlanes) {
bbnum = phy_port_cfg[i][2] / 2;
reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum);
val = readl(base + reg);
val |= phy_port_cfg[i][3];
writel(val, base + reg);
}
}
}
static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys)
{
void __iomem *base = isys->adev->isp->base;
u32 val, reg;
reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL);
reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL);
reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL);
reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL;
val = readl(base + reg);
val |= BIT(0);
writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL);
}
static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys,
unsigned int port, unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
unsigned int sip = port / 2;
unsigned int index;
switch (nlanes) {
case 1:
index = 5;
break;
case 2:
index = 6;
break;
case 4:
index = 1;
break;
default:
dev_err(dev, "lanes nr %u is unsupported\n", nlanes);
return -EINVAL;
}
dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes);
writel(csi2_port_cfg[index][2],
isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip));
return 0;
}
static void
ipu6_isys_csi2_set_timing(struct ipu6_isys *isys,
const struct ipu6_isys_csi2_timing *timing,
unsigned int port, unsigned int nlanes)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *reg;
u32 port_base;
u32 i;
port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) :
CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port);
dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE;
writel(timing->ctermen, reg);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE;
writel(timing->csettle, reg);
for (i = 0; i < nlanes; i++) {
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i);
writel(timing->dtermen, reg);
reg = isys->pdata->base + port_base;
reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i);
writel(timing->dsettle, reg);
}
}
#define DPHY_TIMER_INCR 0x28
int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
int ret = 0;
u32 nlanes;
u32 port;
if (!on)
return 0;
port = cfg->port;
nlanes = cfg->nlanes;
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes);
writel(DPHY_TIMER_INCR,
isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR);
/* set port cfg and rx timing */
ipu6_isys_csi2_set_timing(isys, timing, port, nlanes);
ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes);
if (ret)
return ret;
ipu6_isys_csi2_rx_control(isys);
return 0;
}
@@ -0,0 +1,720 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/time64.h>
#include <media/v4l2-async.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-platform-isys-csi2-reg.h"
#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8)
#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4)
#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0)
#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8)
#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0)
#define CSI_REG_HUB_GPREG_PHY_READY BIT(4)
#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC)
/*
* bridge to phy in buttress reg map, each phy has 16 kbytes
* only 2 phys for TGL U and Y
*/
#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000)
/*
* There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL.
* Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes.
* CSI port 1, 3 (5, 7) can support max 2 data lanes.
* CSI port 0, 2 (4, 6) can support max 4 data lanes.
* PHY configurations are PPI based instead of port.
* Left:
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x4 | unused | D3 | D2 | C0 | D0 | D1 |
* |---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 |
* ----------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x1 | C1 | D0 | unused | C0 | D0 | D1 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x1 | C1 | D0 | unused | C0 | D0 | unused |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x2 | C1 | D0 | D1 | C0 | D0 | unused |
* +---------+---------+---------+---------+--------+---------+----------+
*
* Right:
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x4 | D1 | D0 | C2 | D2 | D3 | unused |
* |---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 |
* ----------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x2x1 | D1 | D0 | C2 | unused | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x1 | unused | D0 | C2 | unused | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
* | | | | | | | |
* | x1x2 | unused | D0 | C2 | D1 | D0 | C3 |
* +---------+---------+---------+---------+--------+---------+----------+
*
* ppi mapping per phy :
*
* x4 + x4:
* Left : port0 - PPI range {0, 1, 2, 3, 4}
* Right: port2 - PPI range {6, 7, 8, 9, 10}
*
* x4 + x2x2:
* Left: port0 - PPI range {0, 1, 2, 3, 4}
* Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
*
* x2x2 + x4:
* Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
* Right: port2 - PPI range {6, 7, 8, 9, 10}
*
* x2x2 + x2x2:
* Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
* Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
*/
struct phy_reg {
u32 reg;
u32 val;
};
static const struct phy_reg common_init_regs[] = {
/* for TGL-U, use 0x80000000 */
{0x00000040, 0x80000000},
{0x00000044, 0x00a80880},
{0x00000044, 0x00b80880},
{0x00000010, 0x0000078c},
{0x00000344, 0x2f4401e2},
{0x00000544, 0x924401e2},
{0x00000744, 0x594401e2},
{0x00000944, 0x624401e2},
{0x00000b44, 0xfc4401e2},
{0x00000d44, 0xc54401e2},
{0x00000f44, 0x034401e2},
{0x00001144, 0x8f4401e2},
{0x00001344, 0x754401e2},
{0x00001544, 0xe94401e2},
{0x00001744, 0xcb4401e2},
{0x00001944, 0xfa4401e2}
};
static const struct phy_reg x1_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78ea},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xfe6030fa},
{0x00000480, 0x29ef5ed0},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port1_config_regs[] = {
{0x00000c94, 0xc80060fa},
{0x00000c80, 0xcf47abea},
{0x00000c90, 0x10a0840b},
{0x00000ca8, 0xdf04010a},
{0x00000d00, 0x57050060},
{0x00000d10, 0x0030001c},
{0x00000d38, 0x5f004444},
{0x00000d3c, 0x78464204},
{0x00000d48, 0x7821f940},
{0x00000d4c, 0xb2000433},
{0x00000a94, 0xc91030fa},
{0x00000a80, 0x5a166ed0},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x5d060100},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port2_config_regs[] = {
{0x00001294, 0x28f000fa},
{0x00001280, 0x08130cea},
{0x00001290, 0x10a0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0x2d20b0fa},
{0x00001080, 0xade75dd0},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x1_port3_config_regs[] = {
{0x00001894, 0xc80060fa},
{0x00001880, 0x0f90fd6a},
{0x00001890, 0x10a0840b},
{0x000018a8, 0xdf04010a},
{0x00001900, 0x57050060},
{0x00001910, 0x0030001c},
{0x00001938, 0x5f004444},
{0x0000193c, 0x78464204},
{0x00001948, 0x7821f940},
{0x0000194c, 0xb2000433},
{0x00001694, 0x3050d0fa},
{0x00001680, 0x0ef6d050},
{0x00001690, 0x10a0540b},
{0x000016a8, 0xe301010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78ea},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xc80060fa},
{0x00000480, 0x29ef5ed8},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000000},
{0x00000294, 0xc80060fa},
{0x00000280, 0xcb45b950},
{0x00000290, 0x10a0540b},
{0x000002a8, 0x8c01010a},
{0x00000300, 0xef053460},
{0x00000310, 0x8030101c},
{0x00000338, 0x41808444},
{0x0000033c, 0x32422204},
{0x00000340, 0x0180088c},
{0x00000374, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port1_config_regs[] = {
{0x00000c94, 0xc80060fa},
{0x00000c80, 0xcf47abea},
{0x00000c90, 0x10a0840b},
{0x00000ca8, 0xdf04010a},
{0x00000d00, 0x57050060},
{0x00000d10, 0x0030001c},
{0x00000d38, 0x5f004444},
{0x00000d3c, 0x78464204},
{0x00000d48, 0x7821f940},
{0x00000d4c, 0xb2000433},
{0x00000a94, 0xc80060fa},
{0x00000a80, 0x5a166ed8},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x7a01010a},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000000},
{0x00000894, 0xc80060fa},
{0x00000880, 0x4d4f21d0},
{0x00000890, 0x10a0540b},
{0x000008a8, 0x5601010a},
{0x00000900, 0xef053460},
{0x00000910, 0x8030101c},
{0x00000938, 0xdf808444},
{0x0000093c, 0xc8422204},
{0x00000940, 0x0180088c},
{0x00000974, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port2_config_regs[] = {
{0x00001294, 0xc80060fa},
{0x00001280, 0x08130cea},
{0x00001290, 0x10a0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0xc80060fa},
{0x00001080, 0xade75dd8},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000000},
{0x00000e94, 0xc80060fa},
{0x00000e80, 0x0fbf16d0},
{0x00000e90, 0x10a0540b},
{0x00000ea8, 0x7a01010a},
{0x00000f00, 0xf5053460},
{0x00000f10, 0xc030101c},
{0x00000f38, 0xdf808444},
{0x00000f3c, 0xc8422204},
{0x00000f40, 0x8180088c},
{0x00000000, 0x00000000}
};
static const struct phy_reg x2_port3_config_regs[] = {
{0x00001894, 0xc80060fa},
{0x00001880, 0x0f90fd6a},
{0x00001890, 0x10a0840b},
{0x000018a8, 0xdf04010a},
{0x00001900, 0x57050060},
{0x00001910, 0x0030001c},
{0x00001938, 0x5f004444},
{0x0000193c, 0x78464204},
{0x00001948, 0x7821f940},
{0x0000194c, 0xb2000433},
{0x00001694, 0xc80060fa},
{0x00001680, 0x0ef6d058},
{0x00001690, 0x10a0540b},
{0x000016a8, 0x7a01010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000000},
{0x00001494, 0xc80060fa},
{0x00001480, 0xf9d34bd0},
{0x00001490, 0x10a0540b},
{0x000014a8, 0x7a01010a},
{0x00001500, 0x1b053460},
{0x00001510, 0x0030101c},
{0x00001538, 0xdf808444},
{0x0000153c, 0xc8422204},
{0x00001540, 0x8180088c},
{0x00001574, 0x00000000},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port0_config_regs[] = {
{0x00000694, 0xc80060fa},
{0x00000680, 0x3d4f78fa},
{0x00000690, 0x10a0140b},
{0x000006a8, 0xdf04010a},
{0x00000700, 0x57050060},
{0x00000710, 0x0030001c},
{0x00000738, 0x5f004444},
{0x0000073c, 0x78464204},
{0x00000748, 0x7821f940},
{0x0000074c, 0xb2000433},
{0x00000494, 0xfe6030fa},
{0x00000480, 0x29ef5ed8},
{0x00000490, 0x10a0540b},
{0x000004a8, 0x7a01010a},
{0x00000500, 0xef053460},
{0x00000510, 0xe030101c},
{0x00000538, 0xdf808444},
{0x0000053c, 0xc8422204},
{0x00000540, 0x0180088c},
{0x00000574, 0x00000004},
{0x00000294, 0x23e030fa},
{0x00000280, 0xcb45b950},
{0x00000290, 0x10a0540b},
{0x000002a8, 0x8c01010a},
{0x00000300, 0xef053460},
{0x00000310, 0x8030101c},
{0x00000338, 0x41808444},
{0x0000033c, 0x32422204},
{0x00000340, 0x0180088c},
{0x00000374, 0x00000004},
{0x00000894, 0x5620b0fa},
{0x00000880, 0x4d4f21dc},
{0x00000890, 0x10a0540b},
{0x000008a8, 0x5601010a},
{0x00000900, 0xef053460},
{0x00000910, 0x8030101c},
{0x00000938, 0xdf808444},
{0x0000093c, 0xc8422204},
{0x00000940, 0x0180088c},
{0x00000974, 0x00000004},
{0x00000a94, 0xc91030fa},
{0x00000a80, 0x5a166ecc},
{0x00000a90, 0x10a0540b},
{0x00000aa8, 0x5d01010a},
{0x00000b00, 0xef053460},
{0x00000b10, 0xa030101c},
{0x00000b38, 0xdf808444},
{0x00000b3c, 0xc8422204},
{0x00000b40, 0x0180088c},
{0x00000b74, 0x00000004},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port1_config_regs[] = {
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port2_config_regs[] = {
{0x00001294, 0x28f000fa},
{0x00001280, 0x08130cfa},
{0x00001290, 0x10c0140b},
{0x000012a8, 0xd704010a},
{0x00001300, 0x8d050060},
{0x00001310, 0x0030001c},
{0x00001338, 0xdf008444},
{0x0000133c, 0x78422204},
{0x00001348, 0x7821f940},
{0x0000134c, 0x5a000433},
{0x00001094, 0x2d20b0fa},
{0x00001080, 0xade75dd8},
{0x00001090, 0x10a0540b},
{0x000010a8, 0xb101010a},
{0x00001100, 0x33053460},
{0x00001110, 0x0030101c},
{0x00001138, 0xdf808444},
{0x0000113c, 0xc8422204},
{0x00001140, 0x8180088c},
{0x00001174, 0x00000004},
{0x00000e94, 0xd308d0fa},
{0x00000e80, 0x0fbf16d0},
{0x00000e90, 0x10a0540b},
{0x00000ea8, 0x2c01010a},
{0x00000f00, 0xf5053460},
{0x00000f10, 0xc030101c},
{0x00000f38, 0xdf808444},
{0x00000f3c, 0xc8422204},
{0x00000f40, 0x8180088c},
{0x00000f74, 0x00000004},
{0x00001494, 0x136850fa},
{0x00001480, 0xf9d34bdc},
{0x00001490, 0x10a0540b},
{0x000014a8, 0x5a01010a},
{0x00001500, 0x1b053460},
{0x00001510, 0x0030101c},
{0x00001538, 0xdf808444},
{0x0000153c, 0xc8422204},
{0x00001540, 0x8180088c},
{0x00001574, 0x00000004},
{0x00001694, 0x3050d0fa},
{0x00001680, 0x0ef6d04c},
{0x00001690, 0x10a0540b},
{0x000016a8, 0xe301010a},
{0x00001700, 0x69053460},
{0x00001710, 0xa030101c},
{0x00001738, 0xdf808444},
{0x0000173c, 0xc8422204},
{0x00001740, 0x0180088c},
{0x00001774, 0x00000004},
{0x00000000, 0x00000000}
};
static const struct phy_reg x4_port3_config_regs[] = {
{0x00000000, 0x00000000}
};
static const struct phy_reg *x1_config_regs[4] = {
x1_port0_config_regs,
x1_port1_config_regs,
x1_port2_config_regs,
x1_port3_config_regs
};
static const struct phy_reg *x2_config_regs[4] = {
x2_port0_config_regs,
x2_port1_config_regs,
x2_port2_config_regs,
x2_port3_config_regs
};
static const struct phy_reg *x4_config_regs[4] = {
x4_port0_config_regs,
x4_port1_config_regs,
x4_port2_config_regs,
x4_port3_config_regs
};
static const struct phy_reg **config_regs[3] = {
x1_config_regs,
x2_config_regs,
x4_config_regs
};
static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN;
writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK,
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d powerup ack timeout", id);
return ret;
}
static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK),
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d powerdown ack timeout", id);
return ret;
}
static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert)
{
void __iomem *isys_base = isys->pdata->base;
u32 val;
val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
if (assert)
val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET;
else
val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET);
writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
}
static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 val;
int ret;
ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
val, val & CSI_REG_HUB_GPREG_PHY_READY,
200, MCD_PHY_POWER_STATUS_TIMEOUT);
if (ret)
dev_err(dev, "PHY%d ready ack timeout", id);
return ret;
}
static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys)
{
struct ipu6_bus_device *adev = isys->adev;
struct ipu6_device *isp = adev->isp;
void __iomem *isp_base = isp->base;
struct sensor_async_sd *s_asd;
struct v4l2_async_connection *asc;
void __iomem *phy_base;
unsigned int i;
u8 phy_id;
list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
s_asd = container_of(asc, struct sensor_async_sd, asc);
phy_id = s_asd->csi2.port / 4;
phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
for (i = 0; i < ARRAY_SIZE(common_init_regs); i++)
writel(common_init_regs[i].val,
phy_base + common_init_regs[i].reg);
}
}
static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg)
{
int phy_port;
int ret;
if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1))
return -EINVAL;
/* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */
/* normalize driver port number */
phy_port = cfg->port % 4;
/* swap port number only for A and B */
if (phy_port == 0)
phy_port = 1;
else if (phy_port == 1)
phy_port = 0;
ret = phy_port;
/* check validity per lane configuration */
if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2))
ret = -EINVAL;
else if ((cfg->nlanes == 2 || cfg->nlanes == 1) &&
!(phy_port >= 0 && phy_port <= 3))
ret = -EINVAL;
return ret;
}
static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys)
{
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_bus_device *adev = isys->adev;
const struct phy_reg **phy_config_regs;
struct ipu6_device *isp = adev->isp;
void __iomem *isp_base = isp->base;
struct sensor_async_sd *s_asd;
struct ipu6_isys_csi2_config cfg;
struct v4l2_async_connection *asc;
int phy_port, phy_id;
unsigned int i;
void __iomem *phy_base;
list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
s_asd = container_of(asc, struct sensor_async_sd, asc);
cfg.port = s_asd->csi2.port;
cfg.nlanes = s_asd->csi2.nlanes;
phy_port = ipu6_isys_driver_port_to_phy_port(&cfg);
if (phy_port < 0) {
dev_err(dev, "invalid port %d for lane %d", cfg.port,
cfg.nlanes);
return -ENXIO;
}
phy_id = cfg.port / 4;
phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id,
cfg.nlanes);
phy_config_regs = config_regs[cfg.nlanes / 2];
cfg.port = phy_port;
for (i = 0; phy_config_regs[cfg.port][i].reg; i++)
writel(phy_config_regs[cfg.port][i].val,
phy_base + phy_config_regs[cfg.port][i].reg);
}
return 0;
}
#define CSI_MCD_PHY_NUM 2
static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM];
int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u8 port, phy_id;
refcount_t *ref;
int ret;
port = cfg->port;
phy_id = port / 4;
ref = &phy_power_ref_count[phy_id];
dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port,
cfg->nlanes);
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n", port);
return -EINVAL;
}
if (on) {
if (refcount_read(ref)) {
dev_dbg(dev, "for phy %d is already UP", phy_id);
refcount_inc(ref);
return 0;
}
ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id);
if (ret)
return ret;
ipu6_isys_mcd_phy_reset(isys, phy_id, 0);
ipu6_isys_mcd_phy_common_init(isys);
ret = ipu6_isys_mcd_phy_config(isys);
if (ret)
return ret;
ipu6_isys_mcd_phy_reset(isys, phy_id, 1);
ret = ipu6_isys_mcd_phy_ready(isys, phy_id);
if (ret)
return ret;
refcount_set(ref, 1);
return 0;
}
if (!refcount_dec_and_test(ref))
return 0;
return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id);
}
@@ -0,0 +1,857 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/atomic.h>
#include <linux/cleanup.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-v4l2.h>
#include "ipu6-bus.h"
#include "ipu6-dma.h"
#include "ipu6-fw-isys.h"
#include "ipu6-isys.h"
#include "ipu6-isys-video.h"
static int ipu6_isys_buf_init(struct vb2_buffer *vb)
{
struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue);
struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
int ret;
ret = ipu6_dma_map_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0);
if (ret)
return ret;
ivb->dma_addr = sg_dma_address(sg->sgl);
return 0;
}
static void ipu6_isys_buf_cleanup(struct vb2_buffer *vb)
{
struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue);
struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
ivb->dma_addr = 0;
ipu6_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0);
}
static int ipu6_isys_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
unsigned int *num_planes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
u32 size = ipu6_isys_get_data_size(av);
/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
if (!*num_planes) {
sizes[0] = size;
} else if (sizes[0] < size) {
dev_dbg(dev, "%s: queue setup: size %u < %u\n",
av->vdev.name, sizes[0], size);
return -EINVAL;
}
*num_planes = 1;
return 0;
}
static int ipu6_isys_buf_prepare(struct vb2_buffer *vb)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
u32 bytesperline = ipu6_isys_get_bytes_per_line(av);
u32 height = ipu6_isys_get_frame_height(av);
u32 size = ipu6_isys_get_data_size(av);
dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
av->vdev.name, size, vb2_plane_size(vb, 0));
if (size > vb2_plane_size(vb, 0))
return -EINVAL;
vb2_set_plane_payload(vb, 0, bytesperline * height);
return 0;
}
/*
* Queue a buffer list back to incoming or active queues. The buffers
* are removed from the buffer list.
*/
void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
unsigned long op_flags,
enum vb2_buffer_state state)
{
struct ipu6_isys_buffer *ib, *ib_safe;
unsigned long flags;
bool first = true;
if (!bl)
return;
WARN_ON_ONCE(!bl->nbufs);
WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE &&
op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING);
list_for_each_entry_safe(ib, ib_safe, &bl->head, head) {
struct ipu6_isys_video *av;
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
struct ipu6_isys_queue *aq =
vb2_queue_to_isys_queue(vb->vb2_queue);
struct device *dev;
av = ipu6_isys_queue_to_video(aq);
dev = &av->isys->adev->auxdev.dev;
spin_lock_irqsave(&aq->lock, flags);
list_del(&ib->head);
if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE)
list_add(&ib->head, &aq->active);
else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING)
list_add_tail(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (first) {
dev_dbg(dev,
"queue buf list %p flags %lx, s %d, %d bufs\n",
bl, op_flags, state, bl->nbufs);
first = false;
}
bl->nbufs--;
}
WARN_ON(bl->nbufs);
}
/*
* flush_firmware_streamon_fail() - Flush in cases where requests may
* have been queued to firmware and the *firmware streamon fails for a
* reason or another.
*/
static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_queue *aq;
unsigned long flags;
lockdep_assert_held(&stream->mutex);
list_for_each_entry(aq, &stream->queues, node) {
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_buffer *ib, *ib_safe;
spin_lock_irqsave(&aq->lock, flags);
list_for_each_entry_safe(ib, ib_safe, &aq->active, head) {
struct vb2_buffer *vb =
ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
if (av->streaming) {
dev_dbg(dev,
"%s: queue buffer %u back to incoming\n",
av->vdev.name, vb->index);
/* Queue already streaming, return to driver. */
list_add(&ib->head, &aq->incoming);
continue;
}
/* Queue not yet streaming, return to user. */
dev_dbg(dev, "%s: return %u back to videobuf2\n",
av->vdev.name, vb->index);
vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib),
VB2_BUF_STATE_QUEUED);
}
spin_unlock_irqrestore(&aq->lock, flags);
}
}
/*
* Attempt obtaining a buffer list from the incoming queues, a list of buffers
* that contains one entry from each video buffer queue. If a buffer can't be
* obtained from every queue, the buffers are returned back to the queue.
*/
static int buffer_list_get(struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl)
{
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_queue *aq;
unsigned long flags;
unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING;
lockdep_assert_held(&stream->mutex);
bl->nbufs = 0;
INIT_LIST_HEAD(&bl->head);
list_for_each_entry(aq, &stream->queues, node) {
struct ipu6_isys_buffer *ib;
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->incoming)) {
spin_unlock_irqrestore(&aq->lock, flags);
if (!list_empty(&bl->head))
ipu6_isys_buffer_list_queue(bl, buf_flag, 0);
return -ENODATA;
}
ib = list_last_entry(&aq->incoming,
struct ipu6_isys_buffer, head);
dev_dbg(dev, "buffer: %s: buffer %u\n",
ipu6_isys_queue_to_video(aq)->vdev.name,
ipu6_isys_buffer_to_vb2_buffer(ib)->index);
list_del(&ib->head);
list_add(&ib->head, &bl->head);
spin_unlock_irqrestore(&aq->lock, flags);
bl->nbufs++;
}
dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs);
return 0;
}
static void
ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb,
struct ipu6_fw_isys_frame_buff_set_abi *set)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
set->output_pins[aq->fw_output].addr = ivb->dma_addr;
set->output_pins[aq->fw_output].out_buf_id = vb->index + 1;
}
/*
* Convert a buffer list to a isys fw ABI framebuffer set. The
* buffer list is not modified.
*/
#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30)
void
ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl)
{
struct ipu6_isys_buffer *ib;
WARN_ON(!bl->nbufs);
set->send_irq_sof = 1;
set->send_resp_sof = 1;
set->send_irq_eof = 0;
set->send_resp_eof = 0;
if (stream->streaming)
set->send_irq_capture_ack = 0;
else
set->send_irq_capture_ack = 1;
set->send_irq_capture_done = 0;
set->send_resp_capture_ack = 1;
set->send_resp_capture_done = 1;
if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) {
set->send_resp_capture_ack = 0;
set->send_resp_capture_done = 0;
}
list_for_each_entry(ib, &bl->head, head) {
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
ipu6_isys_buf_to_fw_frame_buf_pin(vb, set);
}
}
/* Start streaming for real. The buffer list must be available. */
static int ipu6_isys_stream_start(struct ipu6_isys_video *av,
struct ipu6_isys_buffer_list *bl)
{
struct ipu6_isys_stream *stream = av->stream;
struct device *dev = &stream->isys->adev->auxdev.dev;
struct ipu6_isys_buffer_list __bl;
int ret;
guard(mutex)(&stream->isys->stream_mutex);
ret = ipu6_isys_video_set_streaming(av, 1, bl);
if (ret)
goto out_requeue;
stream->streaming = 1;
bl = &__bl;
do {
struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
struct isys_fw_msgs *msg;
u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE;
ret = buffer_list_get(stream, bl);
if (ret < 0)
break;
msg = ipu6_get_fw_msg_buf(stream);
if (!msg)
return -ENOMEM;
buf = &msg->fw_msg.frame;
ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl);
ipu6_fw_isys_dump_frame_buff_set(dev, buf,
stream->nr_output_pins);
ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE,
0);
ret = ipu6_fw_isys_complex_cmd(stream->isys,
stream->stream_handle, buf,
msg->dma_addr, sizeof(*buf),
send_type);
} while (!WARN_ON(ret));
return 0;
out_requeue:
if (bl && bl->nbufs)
ipu6_isys_buffer_list_queue(bl,
IPU6_ISYS_BUFFER_LIST_FL_INCOMING,
VB2_BUF_STATE_QUEUED);
flush_firmware_streamon_fail(stream);
return ret;
}
static void buf_queue(struct vb2_buffer *vb)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_video_buffer *ivb =
vb2_buffer_to_ipu6_isys_video_buffer(vvb);
struct ipu6_isys_buffer *ib = &ivb->ib;
struct device *dev = &av->isys->adev->auxdev.dev;
struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
struct ipu6_isys_stream *stream = av->stream;
struct ipu6_isys_buffer_list bl;
struct isys_fw_msgs *msg;
unsigned long flags;
dma_addr_t dma;
int ret;
dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name);
dma = ivb->dma_addr;
dev_dbg(dev, "iova: iova %pad\n", &dma);
spin_lock_irqsave(&aq->lock, flags);
list_add(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (!vb2_start_streaming_called(vb->vb2_queue)) {
dev_dbg(dev, "start_streaming hasn't been called yet on %s\n",
av->vdev.name);
return;
}
mutex_lock(&stream->mutex);
if (stream->nr_streaming != stream->nr_queues) {
dev_dbg(dev, "not streaming yet, adding to incoming\n");
goto out;
}
/*
* We just put one buffer to the incoming list of this queue
* (above). Let's see whether all queues in the pipeline would
* have a buffer.
*/
ret = buffer_list_get(stream, &bl);
if (ret < 0) {
dev_dbg(dev, "No buffers available\n");
goto out;
}
msg = ipu6_get_fw_msg_buf(stream);
if (!msg) {
ret = -ENOMEM;
goto out;
}
buf = &msg->fw_msg.frame;
ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl);
ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins);
/*
* We must queue the buffers in the buffer list to the
* appropriate video buffer queues BEFORE passing them to the
* firmware since we could get a buffer event back before we
* have queued them ourselves to the active queue.
*/
ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle,
buf, msg->dma_addr, sizeof(*buf),
IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE);
if (ret < 0)
dev_err(dev, "send stream capture failed\n");
out:
mutex_unlock(&stream->mutex);
}
static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq)
{
struct v4l2_mbus_framefmt format, *__format;
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
struct media_pad *remote_pad =
media_pad_remote_pad_first(av->vdev.entity.pads);
struct v4l2_subdev *sd;
u32 r_stream, code;
if (!remote_pad)
return -ENOTCONN;
sd = media_entity_to_v4l2_subdev(remote_pad->entity);
r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index);
struct v4l2_subdev_state *state =
v4l2_subdev_lock_and_get_active_state(sd);
__format = v4l2_subdev_state_get_format(state, remote_pad->index,
r_stream);
if (__format)
format = *__format;
v4l2_subdev_unlock_state(state);
if (!__format) {
dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n",
sd->entity.name, remote_pad->index, r_stream);
return -EPIPE;
}
if (format.width != ipu6_isys_get_frame_width(av) ||
format.height != ipu6_isys_get_frame_height(av)) {
dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
ipu6_isys_get_frame_width(av),
ipu6_isys_get_frame_height(av), format.width,
format.height);
return -EINVAL;
}
code = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0)->code;
if (format.code != code) {
dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n",
code, format.code);
return -EINVAL;
}
return 0;
}
static void return_buffers(struct ipu6_isys_queue *aq,
enum vb2_buffer_state state)
{
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_buffer *ib;
bool need_reset = false;
unsigned long flags;
spin_lock_irqsave(&aq->lock, flags);
while (!list_empty(&aq->incoming)) {
struct vb2_buffer *vb;
ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer,
head);
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
spin_lock_irqsave(&aq->lock, flags);
}
/*
* Something went wrong (FW crash / HW hang / not all buffers
* returned from isys) if there are still buffers queued in active
* queue. We have to clean up places a bit.
*/
while (!list_empty(&aq->active)) {
struct vb2_buffer *vb;
ib = list_first_entry(&aq->active, struct ipu6_isys_buffer,
head);
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
spin_lock_irqsave(&aq->lock, flags);
need_reset = true;
}
spin_unlock_irqrestore(&aq->lock, flags);
if (need_reset) {
mutex_lock(&av->isys->mutex);
av->isys->need_reset = true;
mutex_unlock(&av->isys->mutex);
}
}
static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av)
{
video_device_pipeline_stop(&av->vdev);
ipu6_isys_put_stream(av->stream);
av->stream = NULL;
}
static int start_streaming(struct vb2_queue *q, unsigned int count)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
const struct ipu6_isys_pixelformat *pfmt =
ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0);
struct ipu6_isys_buffer_list __bl, *bl = NULL;
struct ipu6_isys_stream *stream;
struct media_pad *source_pad, *remote_pad;
int nr_queues, ret;
dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n",
av->vdev.name, ipu6_isys_get_frame_width(av),
ipu6_isys_get_frame_height(av), pfmt->css_pixelformat);
remote_pad = media_pad_remote_pad_unique(&av->pad);
if (IS_ERR(remote_pad)) {
dev_dbg(dev, "failed to get remote pad\n");
ret = PTR_ERR(remote_pad);
goto out_return_buffers;
}
source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]);
if (IS_ERR(source_pad)) {
dev_dbg(dev, "No external source entity\n");
ret = PTR_ERR(source_pad);
goto out_return_buffers;
}
ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &nr_queues);
if (ret < 0) {
dev_dbg(dev, "failed to setup video\n");
goto out_return_buffers;
}
ret = ipu6_isys_link_fmt_validate(aq);
if (ret) {
dev_dbg(dev,
"%s: link format validation failed (%d)\n",
av->vdev.name, ret);
goto out_pipeline_stop;
}
ret = ipu6_isys_fw_open(av->isys);
if (ret)
goto out_pipeline_stop;
stream = av->stream;
mutex_lock(&stream->mutex);
if (!stream->nr_streaming) {
ret = ipu6_isys_video_prepare_stream(av, source_pad->entity,
nr_queues);
if (ret)
goto out_fw_close;
}
stream->nr_streaming++;
dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming,
stream->nr_queues);
list_add(&aq->node, &stream->queues);
ipu6_isys_configure_stream_watermark(av, source_pad->entity);
ipu6_isys_update_stream_watermark(av, true);
if (stream->nr_streaming != stream->nr_queues)
goto out;
bl = &__bl;
ret = buffer_list_get(stream, bl);
if (ret < 0) {
dev_warn(dev, "no buffer available, DRIVER BUG?\n");
goto out;
}
ret = ipu6_isys_stream_start(av, bl);
if (ret)
goto out_stream_start;
out:
mutex_unlock(&stream->mutex);
return 0;
out_stream_start:
ipu6_isys_update_stream_watermark(av, false);
list_del(&aq->node);
stream->nr_streaming--;
out_fw_close:
mutex_unlock(&stream->mutex);
ipu6_isys_fw_close(av->isys);
out_pipeline_stop:
ipu6_isys_stream_cleanup(av);
out_return_buffers:
return_buffers(aq, VB2_BUF_STATE_QUEUED);
return ret;
}
static void stop_streaming(struct vb2_queue *q)
{
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_isys_stream *stream = av->stream;
mutex_lock(&stream->mutex);
ipu6_isys_update_stream_watermark(av, false);
mutex_lock(&av->isys->stream_mutex);
if (stream->nr_streaming == stream->nr_queues && stream->streaming)
ipu6_isys_video_set_streaming(av, 0, NULL);
list_del(&aq->node);
mutex_unlock(&av->isys->stream_mutex);
stream->nr_streaming--;
stream->streaming = 0;
mutex_unlock(&stream->mutex);
ipu6_isys_stream_cleanup(av);
return_buffers(aq, VB2_BUF_STATE_ERROR);
ipu6_isys_fw_close(av->isys);
}
static unsigned int
get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, u64 time)
{
struct ipu6_isys *isys = stream->isys;
struct device *dev = &isys->adev->auxdev.dev;
unsigned int i;
/*
* The timestamp is invalid as no TSC in some FPGA platform,
* so get the sequence from pipeline directly in this case.
*/
if (time == 0)
return atomic_read(&stream->sequence) - 1;
for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
if (time == stream->seq[i].timestamp) {
dev_dbg(dev, "sof: using seq nr %u for ts %llu\n",
stream->seq[i].sequence, time);
return stream->seq[i].sequence;
}
for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n",
stream->seq[i].sequence, stream->seq[i].timestamp);
return 0;
}
static u64 get_sof_ns_delta(struct ipu6_isys_video *av, u64 timestamp)
{
struct ipu6_bus_device *adev = av->isys->adev;
struct ipu6_device *isp = adev->isp;
u64 delta, tsc_now;
ipu6_buttress_tsc_read(isp, &tsc_now);
if (!tsc_now)
return 0;
delta = tsc_now - timestamp;
return ipu6_buttress_tsc_ticks_to_ns(delta, isp);
}
static void
ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, u64 time)
{
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->auxdev.dev;
struct ipu6_isys_stream *stream = av->stream;
u64 ns;
u32 sequence;
ns = ktime_get_ns() - get_sof_ns_delta(av, time);
sequence = get_sof_sequence_by_timestamp(stream, time);
vbuf->vb2_buf.timestamp = ns;
vbuf->sequence = sequence;
dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n",
av->vdev.name, ktime_get_ns(), sequence);
dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index,
vbuf->vb2_buf.timestamp);
}
static void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib)
{
struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
if (atomic_read(&ib->str2mmio_flag)) {
vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
/*
* Operation on buffer is ended with error and will be reported
* to the userspace when it is de-queued
*/
atomic_set(&ib->str2mmio_flag, 0);
} else {
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
}
}
static void
ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr,
u64 time, bool error_check)
{
struct ipu6_isys_queue *aq = stream->output_pins_queue[pin_id];
struct ipu6_isys *isys = stream->isys;
struct device *dev = &isys->adev->auxdev.dev;
struct ipu6_isys_buffer *ib;
struct vb2_buffer *vb;
unsigned long flags;
bool first = true;
struct vb2_v4l2_buffer *buf;
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->active)) {
spin_unlock_irqrestore(&aq->lock, flags);
dev_err(dev, "active queue empty\n");
return;
}
list_for_each_entry_reverse(ib, &aq->active, head) {
struct ipu6_isys_video_buffer *ivb;
struct vb2_v4l2_buffer *vvb;
dma_addr_t addr;
vb = ipu6_isys_buffer_to_vb2_buffer(ib);
vvb = to_vb2_v4l2_buffer(vb);
ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb);
addr = ivb->dma_addr;
if (pin_addr != addr) {
if (first)
dev_err(dev, "Unexpected buffer address %pad\n",
&addr);
first = false;
continue;
}
if (error_check) {
/*
* Check for error message:
* 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO'
*/
atomic_set(&ib->str2mmio_flag, 1);
}
dev_dbg(dev, "buffer: found buffer %pad\n", &addr);
buf = to_vb2_v4l2_buffer(vb);
buf->field = V4L2_FIELD_NONE;
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
ipu6_isys_buf_calc_sequence_time(ib, time);
ipu6_isys_queue_buf_done(ib);
return;
}
dev_err(dev, "Failed to find a matching video buffer\n");
spin_unlock_irqrestore(&aq->lock, flags);
}
void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
struct ipu6_fw_isys_resp_info_abi *info)
{
u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0];
bool err = info->error_info.error == IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO;
ipu6_stream_buf_ready(stream, info->pin_id, info->pin.addr, time, err);
}
static const struct vb2_ops ipu6_isys_queue_ops = {
.queue_setup = ipu6_isys_queue_setup,
.buf_init = ipu6_isys_buf_init,
.buf_prepare = ipu6_isys_buf_prepare,
.buf_cleanup = ipu6_isys_buf_cleanup,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.buf_queue = buf_queue,
};
int ipu6_isys_queue_init(struct ipu6_isys_queue *aq)
{
struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys;
struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
struct ipu6_bus_device *adev = isys->adev;
int ret;
/* no support for userptr */
if (!aq->vbq.io_modes)
aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF;
aq->vbq.drv_priv = isys;
aq->vbq.ops = &ipu6_isys_queue_ops;
aq->vbq.lock = &av->mutex;
aq->vbq.mem_ops = &vb2_dma_sg_memops;
aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
aq->vbq.min_queued_buffers = 1;
aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(&aq->vbq);
if (ret)
return ret;
aq->vbq.dev = &adev->isp->pdev->dev;
spin_lock_init(&aq->lock);
INIT_LIST_HEAD(&aq->active);
INIT_LIST_HEAD(&aq->incoming);
return 0;
}
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_QUEUE_H
#define IPU6_ISYS_QUEUE_H
#include <linux/container_of.h>
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/spinlock_types.h>
#include <media/videobuf2-v4l2.h>
#include "ipu6-fw-isys.h"
#include "ipu6-isys-video.h"
struct ipu6_isys_stream;
struct ipu6_isys_queue {
struct vb2_queue vbq;
struct list_head node;
spinlock_t lock; /* Protects active and incoming lists */
struct list_head active;
struct list_head incoming;
unsigned int fw_output;
};
struct ipu6_isys_buffer {
struct list_head head;
atomic_t str2mmio_flag;
};
struct ipu6_isys_video_buffer {
struct vb2_v4l2_buffer vb_v4l2;
struct ipu6_isys_buffer ib;
dma_addr_t dma_addr;
};
#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0)
#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1)
struct ipu6_isys_buffer_list {
struct list_head head;
unsigned int nbufs;
};
#define vb2_queue_to_isys_queue(__vb2) \
container_of(__vb2, struct ipu6_isys_queue, vbq)
#define ipu6_isys_to_isys_video_buffer(__ib) \
container_of(__ib, struct ipu6_isys_video_buffer, ib)
#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \
container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2)
#define ipu6_isys_buffer_to_vb2_buffer(__ib) \
(&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf)
void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
unsigned long op_flags,
enum vb2_buffer_state state);
void
ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
struct ipu6_isys_stream *stream,
struct ipu6_isys_buffer_list *bl);
void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
struct ipu6_fw_isys_resp_info_abi *info);
int ipu6_isys_queue_init(struct ipu6_isys_queue *aq);
#endif /* IPU6_ISYS_QUEUE_H */
@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/minmax.h>
#include <media/media-entity.h>
#include <media/mipi-csi2.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-isys-subdev.h"
unsigned int ipu6_isys_mbus_code_to_bpp(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_META_24:
return 24;
case MEDIA_BUS_FMT_RGB565_1X16:
case MEDIA_BUS_FMT_UYVY8_1X16:
case MEDIA_BUS_FMT_YUYV8_1X16:
case MEDIA_BUS_FMT_Y16_1X16:
case MEDIA_BUS_FMT_META_16:
return 16;
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_Y12_1X12:
case MEDIA_BUS_FMT_META_12:
return 12;
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_Y10_1X10:
case MEDIA_BUS_FMT_META_10:
return 10;
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_Y8_1X8:
case MEDIA_BUS_FMT_META_8:
return 8;
default:
WARN_ON(1);
return 8;
}
}
unsigned int ipu6_isys_mbus_code_to_mipi(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_RGB565_1X16:
return MIPI_CSI2_DT_RGB565;
case MEDIA_BUS_FMT_RGB888_1X24:
return MIPI_CSI2_DT_RGB888;
case MEDIA_BUS_FMT_UYVY8_1X16:
case MEDIA_BUS_FMT_YUYV8_1X16:
return MIPI_CSI2_DT_YUV422_8B;
case MEDIA_BUS_FMT_SBGGR16_1X16:
case MEDIA_BUS_FMT_SGBRG16_1X16:
case MEDIA_BUS_FMT_SGRBG16_1X16:
case MEDIA_BUS_FMT_SRGGB16_1X16:
case MEDIA_BUS_FMT_Y16_1X16:
return MIPI_CSI2_DT_RAW16;
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_Y12_1X12:
return MIPI_CSI2_DT_RAW12;
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_Y10_1X10:
return MIPI_CSI2_DT_RAW10;
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_Y8_1X8:
return MIPI_CSI2_DT_RAW8;
case MEDIA_BUS_FMT_META_8:
case MEDIA_BUS_FMT_META_10:
case MEDIA_BUS_FMT_META_12:
case MEDIA_BUS_FMT_META_16:
case MEDIA_BUS_FMT_META_24:
return MIPI_CSI2_DT_EMBEDDED_8B;
default:
/* return unavailable MIPI data type - 0x3f */
WARN_ON(1);
return 0x3f;
}
}
bool ipu6_isys_is_bayer_format(u32 code)
{
switch (code) {
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_SRGGB16_1X16:
case MEDIA_BUS_FMT_SGRBG16_1X16:
case MEDIA_BUS_FMT_SGBRG16_1X16:
case MEDIA_BUS_FMT_SBGGR16_1X16:
return true;
default:
return false;
}
}
u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y)
{
static const u32 code_map[] = {
MEDIA_BUS_FMT_SRGGB8_1X8,
MEDIA_BUS_FMT_SGRBG8_1X8,
MEDIA_BUS_FMT_SGBRG8_1X8,
MEDIA_BUS_FMT_SBGGR8_1X8,
MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SRGGB12_1X12,
MEDIA_BUS_FMT_SGRBG12_1X12,
MEDIA_BUS_FMT_SGBRG12_1X12,
MEDIA_BUS_FMT_SBGGR12_1X12,
MEDIA_BUS_FMT_SRGGB16_1X16,
MEDIA_BUS_FMT_SGRBG16_1X16,
MEDIA_BUS_FMT_SGBRG16_1X16,
MEDIA_BUS_FMT_SBGGR16_1X16,
};
u32 i;
for (i = 0; i < ARRAY_SIZE(code_map); i++)
if (code_map[i] == code)
break;
if (WARN_ON(i == ARRAY_SIZE(code_map)))
return code;
return code_map[i ^ (((y & 1) << 1) | (x & 1))];
}
int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
struct v4l2_mbus_framefmt *fmt;
struct v4l2_rect *crop;
u32 code = asd->supported_codes[0];
u32 other_pad, other_stream;
unsigned int i;
int ret;
/* No transcoding, source and sink formats must match. */
if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) &&
sd->entity.num_pads > 1)
return v4l2_subdev_get_fmt(sd, state, format);
format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH,
IPU6_ISYS_MAX_WIDTH);
format->format.height = clamp(format->format.height,
IPU6_ISYS_MIN_HEIGHT,
IPU6_ISYS_MAX_HEIGHT);
for (i = 0; asd->supported_codes[i]; i++) {
if (asd->supported_codes[i] == format->format.code) {
code = asd->supported_codes[i];
break;
}
}
format->format.code = code;
format->format.field = V4L2_FIELD_NONE;
/* Store the format and propagate it to the source pad. */
fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
if (!fmt)
return -EINVAL;
*fmt = format->format;
if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK))
return 0;
/* propagate format to following source pad */
fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
format->stream);
if (!fmt)
return -EINVAL;
*fmt = format->format;
ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
format->pad,
format->stream,
&other_pad,
&other_stream);
if (ret)
return -EINVAL;
crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream);
/* reset crop */
crop->left = 0;
crop->top = 0;
crop->width = fmt->width;
crop->height = fmt->height;
return 0;
}
int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
const u32 *supported_codes = asd->supported_codes;
u32 index;
for (index = 0; supported_codes[index]; index++) {
if (index == code->index) {
code->code = supported_codes[index];
return 0;
}
}
return -EINVAL;
}
static int subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_krouting *routing)
{
static const struct v4l2_mbus_framefmt format = {
.width = 4096,
.height = 3072,
.code = MEDIA_BUS_FMT_SGRBG10_1X10,
.field = V4L2_FIELD_NONE,
};
int ret;
ret = v4l2_subdev_routing_validate(sd, routing,
V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
if (ret)
return ret;
return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
}
u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad)
{
struct v4l2_subdev_state *state;
struct v4l2_subdev_route *routes;
unsigned int i;
u32 source_stream = 0;
state = v4l2_subdev_lock_and_get_active_state(sd);
if (!state)
return 0;
routes = state->routing.routes;
for (i = 0; i < state->routing.num_routes; i++) {
if (routes[i].source_pad == pad) {
source_stream = routes[i].source_stream;
break;
}
}
v4l2_subdev_unlock_state(state);
return source_stream;
}
static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_subdev_route route = {
.sink_pad = 0,
.sink_stream = 0,
.source_pad = 1,
.source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
};
struct v4l2_subdev_krouting routing = {
.num_routes = 1,
.routes = &route,
};
return subdev_set_routing(sd, state, &routing);
}
int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
enum v4l2_subdev_format_whence which,
struct v4l2_subdev_krouting *routing)
{
return subdev_set_routing(sd, state, routing);
}
static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = {
.init_state = ipu6_isys_subdev_init_state,
};
int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
const struct v4l2_subdev_ops *ops,
unsigned int nr_ctrls,
unsigned int num_sink_pads,
unsigned int num_source_pads)
{
unsigned int num_pads = num_sink_pads + num_source_pads;
unsigned int i;
int ret;
v4l2_subdev_init(&asd->sd, ops);
asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS |
V4L2_SUBDEV_FL_STREAMS;
asd->sd.owner = THIS_MODULE;
asd->sd.dev = &asd->isys->adev->auxdev.dev;
asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops;
asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads,
sizeof(*asd->pad), GFP_KERNEL);
if (!asd->pad)
return -ENOMEM;
for (i = 0; i < num_sink_pads; i++)
asd->pad[i].flags = MEDIA_PAD_FL_SINK |
MEDIA_PAD_FL_MUST_CONNECT;
for (i = num_sink_pads; i < num_pads; i++)
asd->pad[i].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad);
if (ret)
return ret;
if (asd->ctrl_init) {
ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls);
if (ret)
goto out_media_entity_cleanup;
asd->ctrl_init(&asd->sd);
if (asd->ctrl_handler.error) {
ret = asd->ctrl_handler.error;
goto out_v4l2_ctrl_handler_free;
}
asd->sd.ctrl_handler = &asd->ctrl_handler;
}
asd->source = -1;
return 0;
out_v4l2_ctrl_handler_free:
v4l2_ctrl_handler_free(&asd->ctrl_handler);
out_media_entity_cleanup:
media_entity_cleanup(&asd->sd.entity);
return ret;
}
void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd)
{
media_entity_cleanup(&asd->sd.entity);
v4l2_ctrl_handler_free(&asd->ctrl_handler);
}
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_SUBDEV_H
#define IPU6_ISYS_SUBDEV_H
#include <linux/container_of.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
struct ipu6_isys;
struct ipu6_isys_subdev {
struct v4l2_subdev sd;
struct ipu6_isys *isys;
u32 const *supported_codes;
struct media_pad *pad;
struct v4l2_ctrl_handler ctrl_handler;
void (*ctrl_init)(struct v4l2_subdev *sd);
int source; /* SSI stream source; -1 if unset */
};
#define to_ipu6_isys_subdev(__sd) \
container_of(__sd, struct ipu6_isys_subdev, sd)
unsigned int ipu6_isys_mbus_code_to_bpp(u32 code);
unsigned int ipu6_isys_mbus_code_to_mipi(u32 code);
bool ipu6_isys_is_bayer_format(u32 code);
u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y);
int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt);
int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum
*code);
u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad);
int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
enum v4l2_subdev_format_whence which,
struct v4l2_subdev_krouting *routing);
int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
const struct v4l2_subdev_ops *ops,
unsigned int nr_ctrls,
unsigned int num_sink_pads,
unsigned int num_source_pads);
void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd);
#endif /* IPU6_ISYS_SUBDEV_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_VIDEO_H
#define IPU6_ISYS_VIDEO_H
#include <linux/atomic.h>
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <media/media-entity.h>
#include <media/v4l2-dev.h>
#include "ipu6-isys-queue.h"
#define IPU6_ISYS_OUTPUT_PINS 11
#define IPU6_ISYS_MAX_PARALLEL_SOF 2
struct file;
struct ipu6_isys;
struct ipu6_isys_csi2;
struct ipu6_isys_subdev;
struct ipu6_isys_pixelformat {
u32 pixelformat;
u32 bpp;
u32 bpp_packed;
u32 code;
u32 css_pixelformat;
bool is_meta;
};
struct sequence_info {
unsigned int sequence;
u64 timestamp;
};
/*
* Align with firmware stream. Each stream represents a CSI virtual channel.
* May map to multiple video devices
*/
struct ipu6_isys_stream {
struct mutex mutex;
atomic_t sequence;
unsigned int seq_index;
struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF];
int stream_source;
int stream_handle;
unsigned int nr_output_pins;
struct ipu6_isys_subdev *asd;
int nr_queues; /* Number of capture queues */
int nr_streaming;
int streaming; /* Has streaming been really started? */
struct list_head queues;
struct completion stream_open_completion;
struct completion stream_close_completion;
struct completion stream_start_completion;
struct completion stream_stop_completion;
struct ipu6_isys *isys;
struct ipu6_isys_queue *output_pins_queue[IPU6_ISYS_OUTPUT_PINS];
int error;
u8 vc;
};
struct video_stream_watermark {
u32 width;
u32 height;
u32 hblank;
u32 frame_rate;
u64 pixel_rate;
u64 stream_data_rate;
u16 sram_gran_shift;
u16 sram_gran_size;
struct list_head stream_node;
};
struct ipu6_isys_video {
struct ipu6_isys_queue aq;
/* Serialise access to other fields in the struct. */
struct mutex mutex;
struct media_pad pad;
struct video_device vdev;
struct v4l2_pix_format pix_fmt;
struct v4l2_meta_format meta_fmt;
struct ipu6_isys *isys;
struct ipu6_isys_csi2 *csi2;
struct ipu6_isys_stream *stream;
unsigned int streaming;
struct video_stream_watermark watermark;
u32 source_stream;
u8 vc;
u8 dt;
};
#define ipu6_isys_queue_to_video(__aq) \
container_of(__aq, struct ipu6_isys_video, aq)
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[];
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[];
const struct ipu6_isys_pixelformat *
ipu6_isys_get_isys_format(u32 pixelformat, u32 code);
int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av,
struct media_entity *source_entity,
int nr_queues);
int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state,
struct ipu6_isys_buffer_list *bl);
int ipu6_isys_fw_open(struct ipu6_isys *isys);
void ipu6_isys_fw_close(struct ipu6_isys *isys);
int ipu6_isys_setup_video(struct ipu6_isys_video *av,
struct media_pad *remote_pad,
struct media_pad *source_pad, int *nr_queues);
int ipu6_isys_video_init(struct ipu6_isys_video *av);
void ipu6_isys_video_cleanup(struct ipu6_isys_video *av);
void ipu6_isys_put_stream(struct ipu6_isys_stream *stream);
struct ipu6_isys_stream *
ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle);
struct ipu6_isys_stream *
ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc);
void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
struct media_entity *source);
void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state);
u32 ipu6_isys_get_format(struct ipu6_isys_video *av);
u32 ipu6_isys_get_data_size(struct ipu6_isys_video *av);
u32 ipu6_isys_get_bytes_per_line(struct ipu6_isys_video *av);
u32 ipu6_isys_get_frame_width(struct ipu6_isys_video *av);
u32 ipu6_isys_get_frame_height(struct ipu6_isys_video *av);
#endif /* IPU6_ISYS_VIDEO_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,200 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_ISYS_H
#define IPU6_ISYS_H
#include <linux/irqreturn.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pm_qos.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <media/media-device.h>
#include <media/v4l2-async.h>
#include <media/v4l2-device.h>
#include "ipu6.h"
#include "ipu6-fw-isys.h"
#include "ipu6-isys-csi2.h"
#include "ipu6-isys-video.h"
struct ipu6_bus_device;
#define IPU6_ISYS_ENTITY_PREFIX "Intel IPU6"
/* FW support max 16 streams */
#define IPU6_ISYS_MAX_STREAMS 16
#define ISYS_UNISPART_IRQS (IPU6_ISYS_UNISPART_IRQ_SW | \
IPU6_ISYS_UNISPART_IRQ_CSI0 | \
IPU6_ISYS_UNISPART_IRQ_CSI1)
/*
* Current message queue configuration. These must be big enough
* so that they never gets full. Queues are located in system memory
*/
#define IPU6_ISYS_SIZE_RECV_QUEUE 40
#define IPU6_ISYS_SIZE_SEND_QUEUE 40
#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5
#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5
#define IPU6_ISYS_NUM_RECV_QUEUE 1
#define IPU6_ISYS_MIN_WIDTH 2U
#define IPU6_ISYS_MIN_HEIGHT 1U
#define IPU6_ISYS_MAX_WIDTH 4672U
#define IPU6_ISYS_MAX_HEIGHT 3416U
/* the threshold granularity is 2KB on IPU6 */
#define IPU6_SRAM_GRANULARITY_SHIFT 11
#define IPU6_SRAM_GRANULARITY_SIZE 2048
/* the threshold granularity is 1KB on IPU6SE */
#define IPU6SE_SRAM_GRANULARITY_SHIFT 10
#define IPU6SE_SRAM_GRANULARITY_SIZE 1024
/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */
#define IPU6_MAX_SRAM_SIZE (200 << 10)
/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */
#define IPU6SE_MAX_SRAM_SIZE (96 << 10)
#define IPU6EP_LTR_VALUE 200
#define IPU6EP_MIN_MEMOPEN_TH 0x4
#define IPU6EP_MTL_LTR_VALUE 1023
#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc
struct ltr_did {
union {
u32 value;
struct {
u8 val0;
u8 val1;
u8 val2;
u8 val3;
} bits;
} lut_ltr;
union {
u32 value;
struct {
u8 th0;
u8 th1;
u8 th2;
u8 th3;
} bits;
} lut_fill_time;
};
struct isys_iwake_watermark {
bool iwake_enabled;
bool force_iwake_disable;
u32 iwake_threshold;
u64 isys_pixelbuffer_datarate;
struct ltr_did ltrdid;
struct mutex mutex; /* protect whole struct */
struct ipu6_isys *isys;
struct list_head video_list;
};
struct ipu6_isys_csi2_config {
u32 nlanes;
u32 port;
};
struct sensor_async_sd {
struct v4l2_async_connection asc;
struct ipu6_isys_csi2_config csi2;
};
/*
* struct ipu6_isys
*
* @media_dev: Media device
* @v4l2_dev: V4L2 device
* @adev: ISYS bus device
* @power: Is ISYS powered on or not?
* @isr_bits: Which bits does the ISR handle?
* @power_lock: Serialise access to power (power state in general)
* @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers
* @streams_lock: serialise access to streams
* @streams: streams per firmware stream ID
* @fwcom: fw communication layer private pointer
* or optional external library private pointer
* @phy_termcal_val: the termination calibration value, only used for DWC PHY
* @need_reset: Isys requires d0i0->i3 transition
* @ref_count: total number of callers fw open
* @mutex: serialise access isys video open/release related operations
* @stream_mutex: serialise stream start and stop, queueing requests
* @pdata: platform data pointer
* @csi2: CSI-2 receivers
*/
struct ipu6_isys {
struct media_device media_dev;
struct v4l2_device v4l2_dev;
struct ipu6_bus_device *adev;
int power;
spinlock_t power_lock;
u32 isr_csi2_bits;
u32 csi2_rx_ctrl_cached;
spinlock_t streams_lock;
struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS];
int streams_ref_count[IPU6_ISYS_MAX_STREAMS];
void *fwcom;
u32 phy_termcal_val;
bool need_reset;
bool icache_prefetch;
bool csi2_cse_ipc_not_supported;
unsigned int ref_count;
unsigned int stream_opened;
unsigned int sensor_type;
struct mutex mutex;
struct mutex stream_mutex;
struct ipu6_isys_pdata *pdata;
int (*phy_set_power)(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
struct ipu6_isys_csi2 *csi2;
struct pm_qos_request pm_qos;
spinlock_t listlock; /* Protect framebuflist */
struct list_head framebuflist;
struct list_head framebuflist_fw;
struct v4l2_async_notifier notifier;
struct isys_iwake_watermark iwake_watermark;
};
struct isys_fw_msgs {
union {
u64 dummy;
struct ipu6_fw_isys_frame_buff_set_abi frame;
struct ipu6_fw_isys_stream_cfg_data_abi stream;
} fw_msg;
struct list_head head;
dma_addr_t dma_addr;
};
struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream);
void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data);
void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys);
extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops;
void update_watermark_setting(struct ipu6_isys *isys);
int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on);
#endif /* IPU6_ISYS_H */
@@ -0,0 +1,806 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <asm/barrier.h>
#include <linux/align.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/iova.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/pfn.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include "ipu6.h"
#include "ipu6-dma.h"
#include "ipu6-mmu.h"
#include "ipu6-platform-regs.h"
#define ISP_PAGE_SHIFT 12
#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT)
#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1))
#define ISP_L1PT_SHIFT 22
#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1))
#define ISP_L2PT_SHIFT 12
#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK))))
#define ISP_L1PT_PTES 1024
#define ISP_L2PT_PTES 1024
#define ISP_PADDR_SHIFT 12
#define REG_TLB_INVALIDATE 0x0000
#define REG_L1_PHYS 0x0004 /* 27-bit pfn */
#define REG_INFO 0x0008
#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT)
static void tlb_invalidate(struct ipu6_mmu *mmu)
{
unsigned long flags;
unsigned int i;
spin_lock_irqsave(&mmu->ready_lock, flags);
if (!mmu->ready) {
spin_unlock_irqrestore(&mmu->ready_lock, flags);
return;
}
for (i = 0; i < mmu->nr_mmus; i++) {
/*
* To avoid the HW bug induced dead lock in some of the IPU6
* MMUs on successive invalidate calls, we need to first do a
* read to the page table base before writing the invalidate
* register. MMUs which need to implement this WA, will have
* the insert_read_before_invalidate flags set as true.
* Disregard the return value of the read.
*/
if (mmu->mmu_hw[i].insert_read_before_invalidate)
readl(mmu->mmu_hw[i].base + REG_L1_PHYS);
writel(0xffffffff, mmu->mmu_hw[i].base +
REG_TLB_INVALIDATE);
/*
* The TLB invalidation is a "single cycle" (IOMMU clock cycles)
* When the actual MMIO write reaches the IPU6 TLB Invalidate
* register, wmb() will force the TLB invalidate out if the CPU
* attempts to update the IOMMU page table (or sooner).
*/
wmb();
}
spin_unlock_irqrestore(&mmu->ready_lock, flags);
}
#ifdef DEBUG
static void page_table_dump(struct ipu6_mmu_info *mmu_info)
{
u32 l1_idx;
dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n");
for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
u32 l2_idx;
u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT;
phys_addr_t l2_phys;
if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval)
continue;
l2_phys = TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]);
dev_dbg(mmu_info->dev,
"l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pap\n",
l1_idx, iova, iova + ISP_PAGE_SIZE, &l2_phys);
for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) {
u32 *l2_pt = mmu_info->l2_pts[l1_idx];
u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT);
if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval)
continue;
dev_dbg(mmu_info->dev,
"\tl2 entry %u; iova 0x%8.8x, phys %pa\n",
l2_idx, iova2,
TBL_PHYS_ADDR(l2_pt[l2_idx]));
}
}
dev_dbg(mmu_info->dev, "end IOMMU page table dump\n");
}
#endif /* DEBUG */
static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr)
{
dma_addr_t dma;
dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL);
if (dma_mapping_error(mmu_info->dev, dma))
return 0;
return dma;
}
static int get_dummy_page(struct ipu6_mmu_info *mmu_info)
{
void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
if (!pt)
return -ENOMEM;
dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt);
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map dummy page\n");
goto err_free_page;
}
mmu_info->dummy_page = pt;
mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT;
return 0;
err_free_page:
free_page((unsigned long)pt);
return -ENOMEM;
}
static void free_dummy_page(struct ipu6_mmu_info *mmu_info)
{
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->dummy_page_pteval),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_page);
}
static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
unsigned int i;
if (!pt)
return -ENOMEM;
dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt);
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map l2pt page\n");
goto err_free_page;
}
for (i = 0; i < ISP_L2PT_PTES; i++)
pt[i] = mmu_info->dummy_page_pteval;
mmu_info->dummy_l2_pt = pt;
mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT;
return 0;
err_free_page:
free_page((unsigned long)pt);
return -ENOMEM;
}
static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
{
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_l2_pt);
}
static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
dma_addr_t dma;
unsigned int i;
if (!pt)
return NULL;
dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt);
for (i = 0; i < ISP_L1PT_PTES; i++)
pt[i] = mmu_info->dummy_l2_pteval;
dma = map_single(mmu_info, pt);
if (!dma) {
dev_err(mmu_info->dev, "Failed to map l1pt page\n");
goto err_free_page;
}
mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT;
dev_dbg(mmu_info->dev, "l1 pt %p mapped at %pad\n", pt, &dma);
return pt;
err_free_page:
free_page((unsigned long)pt);
return NULL;
}
static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info)
{
u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
unsigned int i;
if (!pt)
return NULL;
dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt);
for (i = 0; i < ISP_L2PT_PTES; i++)
pt[i] = mmu_info->dummy_page_pteval;
return pt;
}
static void l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t dummy, size_t size)
{
unsigned int l2_entries;
unsigned int l2_idx;
unsigned long flags;
u32 l1_idx;
u32 *l2_pt;
spin_lock_irqsave(&mmu_info->lock, flags);
for (l1_idx = iova >> ISP_L1PT_SHIFT;
size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
dev_dbg(mmu_info->dev,
"unmapping l2 pgtable (l1 index %u (iova 0x%8.8lx))\n",
l1_idx, iova);
if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) {
dev_err(mmu_info->dev,
"unmap not mapped iova 0x%8.8lx l1 index %u\n",
iova, l1_idx);
continue;
}
l2_pt = mmu_info->l2_pts[l1_idx];
l2_entries = 0;
for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
phys_addr_t pteval = TBL_PHYS_ADDR(l2_pt[l2_idx]);
dev_dbg(mmu_info->dev,
"unmap l2 index %u with pteval 0x%p\n",
l2_idx, &pteval);
l2_pt[l2_idx] = mmu_info->dummy_page_pteval;
iova += ISP_PAGE_SIZE;
size -= ISP_PAGE_SIZE;
l2_entries++;
}
WARN_ON_ONCE(!l2_entries);
clflush_cache_range(&l2_pt[l2_idx - l2_entries],
sizeof(l2_pt[0]) * l2_entries);
}
WARN_ON_ONCE(size);
spin_unlock_irqrestore(&mmu_info->lock, flags);
}
static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
struct device *dev = mmu_info->dev;
unsigned int l2_entries;
u32 *l2_pt, *l2_virt;
unsigned int l2_idx;
unsigned long flags;
size_t mapped = 0;
dma_addr_t dma;
u32 l1_entry;
u32 l1_idx;
int err = 0;
spin_lock_irqsave(&mmu_info->lock, flags);
paddr = ALIGN(paddr, ISP_PAGE_SIZE);
for (l1_idx = iova >> ISP_L1PT_SHIFT;
size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
dev_dbg(dev,
"mapping l2 page table for l1 index %u (iova %8.8x)\n",
l1_idx, (u32)iova);
l1_entry = mmu_info->l1_pt[l1_idx];
if (l1_entry == mmu_info->dummy_l2_pteval) {
l2_virt = mmu_info->l2_pts[l1_idx];
if (likely(!l2_virt)) {
l2_virt = alloc_l2_pt(mmu_info);
if (!l2_virt) {
err = -ENOMEM;
goto error;
}
}
dma = map_single(mmu_info, l2_virt);
if (!dma) {
dev_err(dev, "Failed to map l2pt page\n");
free_page((unsigned long)l2_virt);
err = -EINVAL;
goto error;
}
l1_entry = dma >> ISP_PADDR_SHIFT;
dev_dbg(dev, "page for l1_idx %u %p allocated\n",
l1_idx, l2_virt);
mmu_info->l1_pt[l1_idx] = l1_entry;
mmu_info->l2_pts[l1_idx] = l2_virt;
clflush_cache_range(&mmu_info->l1_pt[l1_idx],
sizeof(mmu_info->l1_pt[l1_idx]));
}
l2_pt = mmu_info->l2_pts[l1_idx];
l2_entries = 0;
for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
l2_pt[l2_idx]);
iova += ISP_PAGE_SIZE;
paddr += ISP_PAGE_SIZE;
mapped += ISP_PAGE_SIZE;
size -= ISP_PAGE_SIZE;
l2_entries++;
}
WARN_ON_ONCE(!l2_entries);
clflush_cache_range(&l2_pt[l2_idx - l2_entries],
sizeof(l2_pt[0]) * l2_entries);
}
spin_unlock_irqrestore(&mmu_info->lock, flags);
return 0;
error:
spin_unlock_irqrestore(&mmu_info->lock, flags);
/* unroll mapping in case something went wrong */
if (mapped)
l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped);
return err;
}
static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
u32 iova_start = round_down(iova, ISP_PAGE_SIZE);
u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE);
dev_dbg(mmu_info->dev,
"mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr %pap\n",
iova_start, iova_end, size, &paddr);
return l2_map(mmu_info, iova_start, paddr, size);
}
static void __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info,
unsigned long iova, size_t size)
{
l2_unmap(mmu_info, iova, 0, size);
}
static int allocate_trash_buffer(struct ipu6_mmu *mmu)
{
unsigned int n_pages = PFN_UP(IPU6_MMUV2_TRASH_RANGE);
struct iova *iova;
unsigned int i;
dma_addr_t dma;
unsigned long iova_addr;
int ret;
/* Allocate 8MB in iova range */
iova = alloc_iova(&mmu->dmap->iovad, n_pages,
PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
if (!iova) {
dev_err(mmu->dev, "cannot allocate iova range for trash\n");
return -ENOMEM;
}
dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0,
PAGE_SIZE, DMA_BIDIRECTIONAL);
if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) {
dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n");
ret = -ENOMEM;
goto out_free_iova;
}
mmu->pci_trash_page = dma;
/*
* Map the 8MB iova address range to the same physical trash page
* mmu->trash_page which is already reserved at the probe
*/
iova_addr = iova->pfn_lo;
for (i = 0; i < n_pages; i++) {
ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
mmu->pci_trash_page, PAGE_SIZE);
if (ret) {
dev_err(mmu->dev,
"mapping trash buffer range failed\n");
goto out_unmap;
}
iova_addr++;
}
mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo);
dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n",
mmu->mmid, (unsigned int)mmu->iova_trash_page);
return 0;
out_unmap:
ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page,
PAGE_SIZE, DMA_BIDIRECTIONAL);
out_free_iova:
__free_iova(&mmu->dmap->iovad, iova);
return ret;
}
int ipu6_mmu_hw_init(struct ipu6_mmu *mmu)
{
struct ipu6_mmu_info *mmu_info;
unsigned long flags;
unsigned int i;
mmu_info = mmu->dmap->mmu_info;
/* Initialise the each MMU HW block */
for (i = 0; i < mmu->nr_mmus; i++) {
struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i];
unsigned int j;
u16 block_addr;
/* Write page table address per MMU */
writel((phys_addr_t)mmu_info->l1_pt_dma,
mmu->mmu_hw[i].base + REG_L1_PHYS);
/* Set info bits per MMU */
writel(mmu->mmu_hw[i].info_bits,
mmu->mmu_hw[i].base + REG_INFO);
/* Configure MMU TLB stream configuration for L1 */
for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams;
block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) {
if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) {
dev_err(mmu->dev, "invalid L1 configuration\n");
return -EINVAL;
}
/* Write block start address for each streams */
writel(block_addr, mmu_hw->base +
mmu_hw->l1_stream_id_reg_offset + 4 * j);
}
/* Configure MMU TLB stream configuration for L2 */
for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams;
block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) {
if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) {
dev_err(mmu->dev, "invalid L2 configuration\n");
return -EINVAL;
}
writel(block_addr, mmu_hw->base +
mmu_hw->l2_stream_id_reg_offset + 4 * j);
}
}
if (!mmu->trash_page) {
int ret;
mmu->trash_page = alloc_page(GFP_KERNEL);
if (!mmu->trash_page) {
dev_err(mmu->dev, "insufficient memory for trash buffer\n");
return -ENOMEM;
}
ret = allocate_trash_buffer(mmu);
if (ret) {
__free_page(mmu->trash_page);
mmu->trash_page = NULL;
dev_err(mmu->dev, "trash buffer allocation failed\n");
return ret;
}
}
spin_lock_irqsave(&mmu->ready_lock, flags);
mmu->ready = true;
spin_unlock_irqrestore(&mmu->ready_lock, flags);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, "INTEL_IPU6");
static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp)
{
struct ipu6_mmu_info *mmu_info;
int ret;
mmu_info = kzalloc_obj(*mmu_info);
if (!mmu_info)
return NULL;
mmu_info->aperture_start = 0;
mmu_info->aperture_end =
(dma_addr_t)DMA_BIT_MASK(isp->secure_mode ?
IPU6_MMU_ADDR_BITS :
IPU6_MMU_ADDR_BITS_NON_SECURE);
mmu_info->pgsize_bitmap = SZ_4K;
mmu_info->dev = &isp->pdev->dev;
ret = get_dummy_page(mmu_info);
if (ret)
goto err_free_info;
ret = alloc_dummy_l2_pt(mmu_info);
if (ret)
goto err_free_dummy_page;
mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts));
if (!mmu_info->l2_pts)
goto err_free_dummy_l2_pt;
/*
* We always map the L1 page table (a single page as well as
* the L2 page tables).
*/
mmu_info->l1_pt = alloc_l1_pt(mmu_info);
if (!mmu_info->l1_pt)
goto err_free_l2_pts;
spin_lock_init(&mmu_info->lock);
dev_dbg(mmu_info->dev, "domain initialised\n");
return mmu_info;
err_free_l2_pts:
vfree(mmu_info->l2_pts);
err_free_dummy_l2_pt:
free_dummy_l2_pt(mmu_info);
err_free_dummy_page:
free_dummy_page(mmu_info);
err_free_info:
kfree(mmu_info);
return NULL;
}
void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu)
{
unsigned long flags;
spin_lock_irqsave(&mmu->ready_lock, flags);
mmu->ready = false;
spin_unlock_irqrestore(&mmu->ready_lock, flags);
}
EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6");
static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp)
{
struct ipu6_dma_mapping *dmap;
dmap = kzalloc_obj(*dmap);
if (!dmap)
return NULL;
dmap->mmu_info = ipu6_mmu_alloc(isp);
if (!dmap->mmu_info) {
kfree(dmap);
return NULL;
}
init_iova_domain(&dmap->iovad, SZ_4K, 1);
dmap->mmu_info->dmap = dmap;
dev_dbg(&isp->pdev->dev, "alloc mapping\n");
iova_cache_get();
return dmap;
}
phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
dma_addr_t iova)
{
phys_addr_t phy_addr;
unsigned long flags;
u32 *l2_pt;
spin_lock_irqsave(&mmu_info->lock, flags);
l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT];
phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT];
phy_addr <<= ISP_PAGE_SHIFT;
spin_unlock_irqrestore(&mmu_info->lock, flags);
return phy_addr;
}
void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
size_t size)
{
unsigned int min_pagesz;
dev_dbg(mmu_info->dev, "unmapping iova 0x%lx size 0x%zx\n", iova, size);
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
/*
* The virtual address and the size of the mapping must be
* aligned (at least) to the size of the smallest page supported
* by the hardware
*/
if (!IS_ALIGNED(iova | size, min_pagesz)) {
dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n",
iova, size, min_pagesz);
return;
}
__ipu6_mmu_unmap(mmu_info, iova, size);
}
int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size)
{
unsigned int min_pagesz;
if (mmu_info->pgsize_bitmap == 0UL)
return -ENODEV;
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
/*
* both the virtual address and the physical one, as well as
* the size of the mapping, must be aligned (at least) to the
* size of the smallest page supported by the hardware
*/
if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
dev_err(mmu_info->dev,
"unaligned: iova %lx pa %pa size %zx min_pagesz %x\n",
iova, &paddr, size, min_pagesz);
return -EINVAL;
}
dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
iova, &paddr, size);
return __ipu6_mmu_map(mmu_info, iova, paddr, size);
}
static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)
{
struct ipu6_dma_mapping *dmap = mmu->dmap;
struct ipu6_mmu_info *mmu_info = dmap->mmu_info;
struct iova *iova;
u32 l1_idx;
if (mmu->iova_trash_page) {
iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page));
if (iova) {
/* unmap and free the trash buffer iova */
ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo),
PFN_PHYS(iova_size(iova)));
__free_iova(&dmap->iovad, iova);
} else {
dev_err(mmu->dev, "trash buffer iova not found.\n");
}
mmu->iova_trash_page = 0;
dma_unmap_page(mmu_info->dev, mmu->pci_trash_page,
PAGE_SIZE, DMA_BIDIRECTIONAL);
mmu->pci_trash_page = 0;
__free_page(mmu->trash_page);
}
for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) {
dma_unmap_single(mmu_info->dev,
TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->l2_pts[l1_idx]);
}
}
vfree(mmu_info->l2_pts);
free_dummy_page(mmu_info);
dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma),
PAGE_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)mmu_info->dummy_l2_pt);
free_page((unsigned long)mmu_info->l1_pt);
kfree(mmu_info);
}
struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
void __iomem *base, int mmid,
const struct ipu6_hw_variants *hw)
{
struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev));
struct ipu6_mmu_pdata *pdata;
struct ipu6_mmu *mmu;
unsigned int i;
if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES)
return ERR_PTR(-EINVAL);
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
for (i = 0; i < hw->nr_mmus; i++) {
struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i];
const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i];
if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS ||
src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS)
return ERR_PTR(-EINVAL);
*pdata_mmu = *src_mmu;
pdata_mmu->base = base + src_mmu->offset;
}
mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL);
if (!mmu)
return ERR_PTR(-ENOMEM);
mmu->mmid = mmid;
mmu->mmu_hw = pdata->mmu_hw;
mmu->nr_mmus = hw->nr_mmus;
mmu->tlb_invalidate = tlb_invalidate;
mmu->ready = false;
INIT_LIST_HEAD(&mmu->vma_list);
spin_lock_init(&mmu->ready_lock);
mmu->dmap = alloc_dma_mapping(isp);
if (!mmu->dmap) {
dev_err(dev, "can't alloc dma mapping\n");
return ERR_PTR(-ENOMEM);
}
return mmu;
}
void ipu6_mmu_cleanup(struct ipu6_mmu *mmu)
{
struct ipu6_dma_mapping *dmap = mmu->dmap;
ipu6_mmu_destroy(mmu);
mmu->dmap = NULL;
iova_cache_put();
put_iova_domain(&dmap->iovad);
kfree(dmap);
}
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 Intel Corporation */
#ifndef IPU6_MMU_H
#define IPU6_MMU_H
#define ISYS_MMID 1
#define PSYS_MMID 0
#include <linux/list.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
struct device;
struct page;
struct ipu6_hw_variants;
struct ipu6_mmu_info {
struct device *dev;
u32 *l1_pt;
u32 l1_pt_dma;
u32 **l2_pts;
u32 *dummy_l2_pt;
u32 dummy_l2_pteval;
void *dummy_page;
u32 dummy_page_pteval;
dma_addr_t aperture_start;
dma_addr_t aperture_end;
unsigned long pgsize_bitmap;
spinlock_t lock; /* Serialize access to users */
struct ipu6_dma_mapping *dmap;
};
struct ipu6_mmu {
struct list_head node;
struct ipu6_mmu_hw *mmu_hw;
unsigned int nr_mmus;
unsigned int mmid;
phys_addr_t pgtbl;
struct device *dev;
struct ipu6_dma_mapping *dmap;
struct list_head vma_list;
struct page *trash_page;
dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */
dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */
bool ready;
spinlock_t ready_lock; /* Serialize access to bool ready */
void (*tlb_invalidate)(struct ipu6_mmu *mmu);
};
struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
void __iomem *base, int mmid,
const struct ipu6_hw_variants *hw);
void ipu6_mmu_cleanup(struct ipu6_mmu *mmu);
int ipu6_mmu_hw_init(struct ipu6_mmu *mmu);
void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu);
int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
phys_addr_t paddr, size_t size);
void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
size_t size);
phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
dma_addr_t iova);
#endif
@@ -0,0 +1,224 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2023--2024 Intel Corporation */
#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H
#define IPU6_PLATFORM_BUTTRESS_REGS_H
#include <linux/bits.h>
/* IS_WORKPOINT_REQ */
#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34
/* PS_WORKPOINT_REQ */
#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38
/* should be tuned for real silicon */
#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08
#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a
#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d
#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10
#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708
#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3
#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3)
#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6
#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6)
#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0
#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1
#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2
#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288
#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c
#define BUTTRESS_REG_WDT 0x8
#define BUTTRESS_REG_BTRS_CTRL 0xc
#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0)
#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1)
#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8)
#define BUTTRESS_REG_FW_RESET_CTL 0x30
#define BUTTRESS_FW_RESET_CTL_START BIT(0)
#define BUTTRESS_FW_RESET_CTL_DONE BIT(1)
#define BUTTRESS_REG_IS_FREQ_CTL 0x34
#define BUTTRESS_REG_PS_FREQ_CTL 0x38
#define BUTTRESS_FREQ_CTL_START BIT(31)
#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16)
#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8)
#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0)
#define BUTTRESS_REG_PWR_STATE 0x5c
#define BUTTRESS_PWR_STATE_RESET 0x0
#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1
#define BUTTRESS_PWR_STATE_PWR_RDY 0x3
#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4
#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11)
enum {
BUTTRESS_PWR_STATE_HH_STATE_IDLE,
BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS,
BUTTRESS_PWR_STATE_HH_STATE_DONE,
BUTTRESS_PWR_STATE_HH_STATE_ERR,
};
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19)
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe
#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24)
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15
#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16
#define BUTTRESS_REG_SECURITY_CTL 0x300
#define BUTTRESS_REG_SKU 0x314
#define BUTTRESS_REG_SECURITY_TOUCH 0x318
#define BUTTRESS_REG_CAMERA_MASK 0x84
#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16)
#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0)
#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0)
#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1)
#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3)
#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78
#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C
#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80
#define BUTTRESS_REG_ISR_STATUS 0x90
#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94
#define BUTTRESS_REG_ISR_ENABLE 0x98
#define BUTTRESS_REG_ISR_CLEAR 0x9C
#define BUTTRESS_ISR_IS_IRQ BIT(0)
#define BUTTRESS_ISR_PS_IRQ BIT(1)
#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2)
#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3)
#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4)
#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5)
#define BUTTRESS_ISR_CSE_CSR_SET BIT(6)
#define BUTTRESS_ISR_ISH_CSR_SET BIT(7)
#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8)
#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9)
#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10)
#define BUTTRESS_ISR_SAI_VIOLATION BIT(11)
#define BUTTRESS_ISR_HW_ASSERTION BIT(12)
#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13)
#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14)
#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15)
#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16)
#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17)
#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18)
#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19)
#define BUTTRESS_ISR_UFI_ERROR BIT(20)
#define BUTTRESS_REG_IU2CSEDB0 0x100
#define BUTTRESS_IU2CSEDB0_BUSY BIT(31)
#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2
#define BUTTRESS_REG_IU2CSEDATA0 0x104
#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1
#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2
#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3
#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16
#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0)
#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1)
#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2)
#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4)
#define BUTTRESS_REG_IU2CSECSR 0x108
#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0)
#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1)
#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2)
#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3)
#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4)
#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5)
#define BUTTRESS_REG_CSE2IUDB0 0x304
#define BUTTRESS_REG_CSE2IUCSR 0x30C
#define BUTTRESS_REG_CSE2IUDATA0 0x308
/* 0x20 == NACK, 0xf == unknown command */
#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20
#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0)
#define BUTTRESS_REG_ISH2IUCSR 0x50
#define BUTTRESS_REG_ISH2IUDB0 0x54
#define BUTTRESS_REG_ISH2IUDATA0 0x58
#define BUTTRESS_REG_IU2ISHDB0 0x10C
#define BUTTRESS_REG_IU2ISHDATA0 0x110
#define BUTTRESS_REG_IU2ISHDATA1 0x114
#define BUTTRESS_REG_IU2ISHCSR 0x118
#define BUTTRESS_REG_FABRIC_CMD 0x88
#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0)
#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4)
#define BUTTRESS_REG_TSW_CTL 0x120
#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8)
#define BUTTRESS_REG_TSC_LO 0x164
#define BUTTRESS_REG_TSC_HI 0x168
#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \
BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ)
#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \
BUTTRESS_ISR_SAI_VIOLATION)
#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */
@@ -0,0 +1,172 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2023--2024 Intel Corporation */
#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H
#define IPU6_PLATFORM_ISYS_CSI2_REG_H
#include <linux/bits.h>
#define CSI_REG_BASE 0x220000
#define CSI_REG_PORT_BASE(id) (CSI_REG_BASE + (id) * 0x1000)
/* CSI Port Genral Purpose Registers */
#define CSI_REG_PORT_GPREG_SRST 0x0
#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4
#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8
/*
* Port IRQs mapping events:
* IRQ0 - CSI_FE event
* IRQ1 - CSI_SYNC
* IRQ2 - S2M_SIDS0TO7
* IRQ3 - S2M_SIDS8TO15
*/
#define CSI_PORT_REG_BASE_IRQ_CSI 0x80
#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0
#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0
#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0
#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0
#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4
#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8
#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc
#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10
#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14
#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0)
#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0)
#define CSI_RX_NUM_ERRORS_IN_IRQ 20
#define CSI_RX_NUM_IRQ 32
#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2))
#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2))
/* PPI2CSI */
#define CSI_REG_PPI2CSI_ENABLE 0x200
#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204
#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3)
#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208
enum CSI_PPI2CSI_CTRL {
CSI_PPI2CSI_DISABLE = 0,
CSI_PPI2CSI_ENABLE = 1,
};
/* CSI_FE */
#define CSI_REG_CSI_FE_ENABLE 0x280
#define CSI_REG_CSI_FE_MODE 0x284
#define CSI_REG_CSI_FE_MUX_CTRL 0x288
#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290
enum CSI_FE_ENABLE_TYPE {
CSI_FE_DISABLE = 0,
CSI_FE_ENABLE = 1,
};
enum CSI_FE_MODE_TYPE {
CSI_FE_DPHY_MODE = 0,
CSI_FE_CPHY_MODE = 1,
};
enum CSI_FE_INPUT_SELECTOR {
CSI_SENSOR_INPUT = 0,
CSI_MIPIGEN_INPUT = 1,
};
enum CSI_FE_SYNC_CNTR_SEL_TYPE {
CSI_CNTR_SENSOR_LINE_ID = BIT(0),
CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID,
CSI_CNTR_SENSOR_FRAME_ID = BIT(1),
CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID,
};
/* CSI HUB General Purpose Registers */
#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000)
#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004)
#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4)
#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000
#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000
#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) \
(CSI_REG_BASE + (ofs) + (id) * 4)
enum CSI_PORT_CLK_GATING_SWITCH {
CSI_PORT_CLK_GATING_OFF = 0,
CSI_PORT_CLK_GATING_ON = 1,
};
#define CSI_REG_BASE_HUB_IRQ 0x18200
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210
#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230
#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234
/* MTL IPU6V6 irq ctrl0 & ctrl1 */
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730
#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734
/*
* 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits)
* [0] CSI_PORT.IRQ_CTRL0_csi
* [1] CSI_PORT.IRQ_CTRL1_csi_sync
* [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7
* [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15
*/
#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \
(0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE))
/*
* ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3
* sip0 - 0, 1
* sip1 - 2, 3
* 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes
* all offset are base from isys base address
*/
#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4)
#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4)
#define CSI2_HUB_GPREG_DPHY_TIMER_INCR 0x238040
#define CSI2_HUB_GPREG_HPLL_FREQ 0x238044
#define CSI2_HUB_GPREG_IS_CLK_RATIO 0x238048
#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE 0x23804c
#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE 0x238058
#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL 0x23805c
#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL 0x238088
#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL 0x2380a4
#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL 0x2380d0
#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48)
#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48)
#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48)
/* offset from port base */
#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL 0x0
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE 0x4
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE 0x8
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8)
#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8)
#endif /* IPU6_ISYS_CSI2_REG_H */
@@ -0,0 +1,179 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2018 - 2024 Intel Corporation */
#ifndef IPU6_PLATFORM_REGS_H
#define IPU6_PLATFORM_REGS_H
#include <linux/bits.h>
/*
* IPU6 uses uniform address within IPU6, therefore all subsystem registers
* locates in one single space starts from 0 but in different sctions with
* different addresses, the subsystem offsets are defined to 0 as the
* register definition will have the address offset to 0.
*/
#define IPU6_UNIFIED_OFFSET 0
#define IPU6_ISYS_IOMMU0_OFFSET 0x2e0000
#define IPU6_ISYS_IOMMU1_OFFSET 0x2e0500
#define IPU6_ISYS_IOMMUI_OFFSET 0x2e0a00
#define IPU6_PSYS_IOMMU0_OFFSET 0x1b0000
#define IPU6_PSYS_IOMMU1_OFFSET 0x1b0700
#define IPU6_PSYS_IOMMU1R_OFFSET 0x1b0e00
#define IPU6_PSYS_IOMMUI_OFFSET 0x1b1500
/* the offset from IOMMU base register */
#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET 0x0c
#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET 0x4c
#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET 0x8c
#define IPU6_MMU_INFO_OFFSET 0x8
#define IPU6_ISYS_SPC_OFFSET 0x210000
#define IPU6SE_PSYS_SPC_OFFSET 0x110000
#define IPU6_PSYS_SPC_OFFSET 0x118000
#define IPU6_ISYS_DMEM_OFFSET 0x200000
#define IPU6_PSYS_DMEM_OFFSET 0x100000
#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE 0x27c000
#define IPU6_REG_ISYS_UNISPART_IRQ_MASK 0x27c004
#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS 0x27c008
#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR 0x27c00c
#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE 0x27c010
#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE 0x27c014
#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG 0x27c414
#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG 0x27c418
#define IPU6_ISYS_UNISPART_IRQ_CSI0 BIT(2)
#define IPU6_ISYS_UNISPART_IRQ_CSI1 BIT(3)
#define IPU6_ISYS_UNISPART_IRQ_SW BIT(22)
#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE 0x2b0200
#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK 0x2b0204
#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS 0x2b0208
#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR 0x2b020c
#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE 0x2b0210
#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE 0x2b0214
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE 0x2d2100
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK 0x2d2104
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS 0x2d2108
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR 0x2d210c
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE 0x2d2110
#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE 0x2d2114
/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */
#define IPU6_REG_ISYS_CDC_THRESHOLD(i) (0x27c400 + ((i) * 4))
#define IPU6_CSI_IRQ_NUM_PER_PIPE 4
#define IPU6SE_ISYS_CSI_PORT_NUM 4
#define IPU6_ISYS_CSI_PORT_NUM 8
#define IPU6_ISYS_CSI_PORT_IRQ(irq_num) BIT(irq_num)
/* PKG DIR OFFSET in IMR in secure mode */
#define IPU6_PKG_DIR_IMR_OFFSET 0x40
#define IPU6_ISYS_REG_SPC_STATUS_CTRL 0x0
#define IPU6_ISYS_SPC_STATUS_START BIT(1)
#define IPU6_ISYS_SPC_STATUS_RUN BIT(3)
#define IPU6_ISYS_SPC_STATUS_READY BIT(5)
#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12)
#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH BIT(13)
#define IPU6_PSYS_REG_SPC_STATUS_CTRL 0x0
#define IPU6_PSYS_REG_SPC_START_PC 0x4
#define IPU6_PSYS_REG_SPC_ICACHE_BASE 0x10
#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14
#define IPU6_PSYS_SPC_STATUS_START BIT(1)
#define IPU6_PSYS_SPC_STATUS_RUN BIT(3)
#define IPU6_PSYS_SPC_STATUS_READY BIT(5)
#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12)
#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13)
#define IPU6_PSYS_REG_SPP0_STATUS_CTRL 0x20000
#define IPU6_INFO_ENABLE_SNOOP BIT(0)
#define IPU6_INFO_DEC_FORCE_FLUSH BIT(1)
#define IPU6_INFO_DEC_PASS_THROUGH BIT(2)
#define IPU6_INFO_ZLW BIT(3)
#define IPU6_INFO_REQUEST_DESTINATION_IOSF BIT(9)
#define IPU6_INFO_IMR_BASE BIT(10)
#define IPU6_INFO_IMR_DESTINED BIT(11)
#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF
/*
* s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the
* pixel s2m remp ability.Remap here means that s2m rearange the order
* of the pixels in each 4 pixels group.
* For examle, mirroring remping means that if input's 4 first pixels
* are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels.
* 0xE4 is from s2m MAS document. It means no remapping.
*/
#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4
/*
* csi_be_soc_pixel_remapping is for the enabling of the pixel remapping.
* This remapping is exactly like the stream2mmio remapping.
*/
#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4
#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000
#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000
#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc)
#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc)
#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc)
enum ipu6_device_ab_group1_target_id {
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER,
IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB,
};
enum nci_ab_access_mode {
NCI_AB_ACCESS_MODE_RW, /* read & write */
NCI_AB_ACCESS_MODE_RO, /* read only */
NCI_AB_ACCESS_MODE_WO, /* write only */
NCI_AB_ACCESS_MODE_NA, /* No access at all */
};
/* IRQ-related registers in PSYS */
#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200
#define IPU6_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204
#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208
#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c
#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210
#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214
/* There are 8 FW interrupts, n = 0..7 */
#define IPU6_PSYS_GPDEV_FWIRQ0 5
#define IPU6_PSYS_GPDEV_FWIRQ1 6
#define IPU6_PSYS_GPDEV_FWIRQ2 7
#define IPU6_PSYS_GPDEV_FWIRQ3 8
#define IPU6_PSYS_GPDEV_FWIRQ4 9
#define IPU6_PSYS_GPDEV_FWIRQ5 10
#define IPU6_PSYS_GPDEV_FWIRQ6 11
#define IPU6_PSYS_GPDEV_FWIRQ7 12
#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n) BIT(n)
#define IPU6_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100)
#endif /* IPU6_PLATFORM_REGS_H */
@@ -0,0 +1,848 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/pci-ats.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <media/ipu-bridge.h>
#include <media/ipu6-pci-table.h>
#include "ipu6.h"
#include "ipu6-bus.h"
#include "ipu6-buttress.h"
#include "ipu6-cpd.h"
#include "ipu6-isys.h"
#include "ipu6-mmu.h"
#include "ipu6-platform-buttress-regs.h"
#include "ipu6-platform-isys-csi2-reg.h"
#include "ipu6-platform-regs.h"
#define IPU6_PCI_BAR 0
struct ipu6_cell_program {
u32 magic_number;
u32 blob_offset;
u32 blob_size;
u32 start[3];
u32 icache_source;
u32 icache_target;
u32 icache_size;
u32 pmem_source;
u32 pmem_target;
u32 pmem_size;
u32 data_source;
u32 data_target;
u32 data_size;
u32 bss_target;
u32 bss_size;
u32 cell_id;
u32 regs_addr;
u32 cell_pmem_data_bus_address;
u32 cell_dmem_data_bus_address;
u32 cell_pmem_control_bus_address;
u32 cell_dmem_control_bus_address;
u32 next;
u32 dummy[2];
};
static struct ipu6_isys_internal_pdata isys_ipdata = {
.hw_variant = {
.offset = IPU6_UNIFIED_OFFSET,
.nr_mmus = 3,
.mmu_hw = {
{
.offset = IPU6_ISYS_IOMMU0_OFFSET,
.info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF,
.nr_l1streams = 16,
.l1_block_sz = {
3, 8, 2, 2, 2, 2, 2, 2, 1, 1,
1, 1, 1, 1, 1, 1
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_ISYS_IOMMU1_OFFSET,
.info_bits = 0,
.nr_l1streams = 16,
.l1_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 1, 1, 4
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_ISYS_IOMMUI_OFFSET,
.info_bits = 0,
.nr_l1streams = 0,
.nr_l2streams = 0,
.insert_read_before_invalidate = false,
},
},
.cdc_fifos = 3,
.cdc_fifo_threshold = {6, 8, 2},
.dmem_offset = IPU6_ISYS_DMEM_OFFSET,
.spc_offset = IPU6_ISYS_SPC_OFFSET,
},
.isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN,
};
static struct ipu6_psys_internal_pdata psys_ipdata = {
.hw_variant = {
.offset = IPU6_UNIFIED_OFFSET,
.nr_mmus = 4,
.mmu_hw = {
{
.offset = IPU6_PSYS_IOMMU0_OFFSET,
.info_bits =
IPU6_INFO_REQUEST_DESTINATION_IOSF,
.nr_l1streams = 16,
.l1_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMU1_OFFSET,
.info_bits = 0,
.nr_l1streams = 32,
.l1_block_sz = {
1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 10,
5, 4, 14, 6, 4, 14, 6, 4, 8,
4, 2, 1, 1, 1, 1, 14
},
.nr_l2streams = 32,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMU1R_OFFSET,
.info_bits = 0,
.nr_l1streams = 16,
.l1_block_sz = {
1, 4, 4, 4, 4, 16, 8, 4, 32,
16, 16, 2, 2, 2, 1, 12
},
.nr_l2streams = 16,
.l2_block_sz = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2
},
.insert_read_before_invalidate = false,
.l1_stream_id_reg_offset =
IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
.l2_stream_id_reg_offset =
IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
},
{
.offset = IPU6_PSYS_IOMMUI_OFFSET,
.info_bits = 0,
.nr_l1streams = 0,
.nr_l2streams = 0,
.insert_read_before_invalidate = false,
},
},
.dmem_offset = IPU6_PSYS_DMEM_OFFSET,
},
};
static const struct ipu6_buttress_ctrl isys_buttress_ctrl = {
.ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO,
.qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
.freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL,
.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT,
.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK,
.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
};
static const struct ipu6_buttress_ctrl psys_buttress_ctrl = {
.ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO,
.qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
.freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL,
.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT,
.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK,
.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
};
static void
ipu6_pkg_dir_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base,
u64 *pkg_dir, dma_addr_t pkg_dir_vied_address)
{
struct ipu6_cell_program *prog;
void __iomem *spc_base;
u32 server_fw_addr;
dma_addr_t dma_addr;
u32 pg_offset;
server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2));
if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX)
dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl);
else
dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl);
pg_offset = server_fw_addr - dma_addr;
prog = (struct ipu6_cell_program *)((uintptr_t)isp->cpd_fw->data +
pg_offset);
spc_base = base + prog->regs_addr;
if (spc_base != (base + hw_variant->spc_offset))
dev_warn(&isp->pdev->dev,
"SPC reg addr %p not matching value from CPD %p\n",
base + hw_variant->spc_offset, spc_base);
writel(server_fw_addr + prog->blob_offset +
prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE);
writel(IPU6_INFO_REQUEST_DESTINATION_IOSF,
spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER);
writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC);
writel(pkg_dir_vied_address, base + hw_variant->dmem_offset);
}
void ipu6_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
dma_addr_t pkg_dir_dma_addr)
{
void __iomem *dmem_base = base + hw_variant->dmem_offset;
void __iomem *spc_regs_base = base + hw_variant->spc_offset;
u32 val;
val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
if (isp->secure_mode)
writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base);
else
ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base,
pkg_dir, pkg_dir_dma_addr);
}
EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6");
#define IPU6_ISYS_CSI2_NPORTS 4
#define IPU6SE_ISYS_CSI2_NPORTS 4
#define IPU6_TGL_ISYS_CSI2_NPORTS 8
#define IPU6EP_MTL_ISYS_CSI2_NPORTS 6
static void ipu6_internal_pdata_init(struct ipu6_device *isp)
{
u8 hw_ver = isp->hw_ver;
isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS;
isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT;
isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE;
isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE;
isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START;
isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END;
isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS;
isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES;
isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX;
isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE;
isys_ipdata.csi2.nports = IPU6_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
isys_ipdata.csi2.ctrl0_irq_clear =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_enable =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
isys_ipdata.csi2.ctrl0_irq_status =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
isys_ipdata.csi2.ctrl0_irq_lnp =
IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver);
psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET;
isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS;
if (is_ipu6ep(hw_ver)) {
isys_ipdata.ltr = IPU6EP_LTR_VALUE;
isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH;
}
if (is_ipu6_tgl(hw_ver))
isys_ipdata.csi2.nports = IPU6_TGL_ISYS_CSI2_NPORTS;
if (is_ipu6ep_mtl(hw_ver)) {
isys_ipdata.csi2.nports = IPU6EP_MTL_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.ctrl0_irq_edge =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
isys_ipdata.csi2.ctrl0_irq_clear =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
isys_ipdata.csi2.ctrl0_irq_mask =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
isys_ipdata.csi2.ctrl0_irq_enable =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
isys_ipdata.csi2.ctrl0_irq_lnp =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
isys_ipdata.csi2.ctrl0_irq_status =
IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
isys_ipdata.csi2.fw_access_port_ofs =
CSI_REG_HUB_FW_ACCESS_PORT_V6OFS;
isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE;
isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH;
}
if (is_ipu6se(hw_ver)) {
isys_ipdata.csi2.nports = IPU6SE_ISYS_CSI2_NPORTS;
isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK;
isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS;
isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT;
isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE;
isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE;
isys_ipdata.sensor_type_start =
IPU6SE_FW_ISYS_SENSOR_TYPE_START;
isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END;
isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS;
isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES;
isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX;
isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE;
psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET;
}
}
static struct ipu6_bus_device *
ipu6_isys_init(struct pci_dev *pdev, struct device *parent,
struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
const struct ipu6_isys_internal_pdata *ipdata)
{
struct device *dev = &pdev->dev;
struct ipu6_bus_device *isys_adev;
struct ipu6_isys_pdata *pdata;
int ret;
ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb);
if (ret) {
dev_err_probe(dev, ret, "IPU6 bridge init failed\n");
return ERR_PTR(ret);
}
pdata = kzalloc_obj(*pdata);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->base = base;
pdata->ipdata = ipdata;
isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
IPU6_ISYS_NAME);
if (IS_ERR(isys_adev)) {
kfree(pdata);
return dev_err_cast_probe(dev, isys_adev,
"ipu6_bus_initialize_device isys failed\n");
}
isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID,
&ipdata->hw_variant);
if (IS_ERR(isys_adev->mmu)) {
put_device(&isys_adev->auxdev.dev);
kfree(pdata);
return dev_err_cast_probe(dev, isys_adev->mmu,
"ipu6_mmu_init(isys_adev->mmu) failed\n");
}
isys_adev->mmu->dev = &isys_adev->auxdev.dev;
ret = ipu6_bus_add_device(isys_adev);
if (ret) {
kfree(pdata);
return ERR_PTR(ret);
}
return isys_adev;
}
static struct ipu6_bus_device *
ipu6_psys_init(struct pci_dev *pdev, struct device *parent,
struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
const struct ipu6_psys_internal_pdata *ipdata)
{
struct ipu6_bus_device *psys_adev;
struct ipu6_psys_pdata *pdata;
int ret;
pdata = kzalloc_obj(*pdata);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->base = base;
pdata->ipdata = ipdata;
psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
IPU6_PSYS_NAME);
if (IS_ERR(psys_adev)) {
kfree(pdata);
return dev_err_cast_probe(&pdev->dev, psys_adev,
"ipu6_bus_initialize_device psys failed\n");
}
psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID,
&ipdata->hw_variant);
if (IS_ERR(psys_adev->mmu)) {
put_device(&psys_adev->auxdev.dev);
kfree(pdata);
return dev_err_cast_probe(&pdev->dev, psys_adev->mmu,
"ipu6_mmu_init(psys_adev->mmu) failed\n");
}
psys_adev->mmu->dev = &psys_adev->auxdev.dev;
ret = ipu6_bus_add_device(psys_adev);
if (ret) {
kfree(pdata);
return ERR_PTR(ret);
}
return psys_adev;
}
static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver)
{
int ret;
/* No PCI msi capability for IPU6EP */
if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) {
/* likely do nothing as msi not enabled by default */
pci_disable_msi(dev);
return 0;
}
ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return dev_err_probe(&dev->dev, ret, "Request msi failed");
return 0;
}
static void ipu6_configure_vc_mechanism(struct ipu6_device *isp)
{
u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL);
if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
else
val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
else
val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL);
}
static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL;
struct device *dev = &pdev->dev;
void __iomem *isys_base = NULL;
void __iomem *psys_base = NULL;
struct ipu6_device *isp;
phys_addr_t phys;
u32 val, version, sku_id;
int ret;
isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
if (!isp)
return -ENOMEM;
isp->pdev = pdev;
INIT_LIST_HEAD(&isp->devices);
ret = pcim_enable_device(pdev);
if (ret)
return dev_err_probe(dev, ret, "Enable PCI device failed\n");
phys = pci_resource_start(pdev, IPU6_PCI_BAR);
dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys);
isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME);
if (IS_ERR(isp->base))
return dev_err_probe(dev, PTR_ERR(isp->base),
"Failed to I/O mem remapping\n");
pci_set_drvdata(pdev, isp);
pci_set_master(pdev);
isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt);
switch (id->device) {
case PCI_DEVICE_ID_INTEL_IPU6:
isp->hw_ver = IPU6_VER_6;
isp->cpd_fw_name = IPU6_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6SE:
isp->hw_ver = IPU6_VER_6SE;
isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME;
isp->cpd_metadata_cmpnt_size =
sizeof(struct ipu6se_cpd_metadata_cmpnt);
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP:
case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP:
isp->hw_ver = IPU6_VER_6EP;
isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN:
isp->hw_ver = IPU6_VER_6EP;
isp->cpd_fw_name = IPU6EPADLN_FIRMWARE_NAME;
break;
case PCI_DEVICE_ID_INTEL_IPU6EP_MTL:
isp->hw_ver = IPU6_VER_6EP_MTL;
isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME;
break;
default:
return dev_err_probe(dev, -ENODEV,
"Unsupported IPU6 device %x\n",
id->device);
}
ipu6_internal_pdata_init(isp);
isys_base = isp->base + isys_ipdata.hw_variant.offset;
psys_base = isp->base + psys_ipdata.hw_variant.offset;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
if (ret)
return dev_err_probe(dev, ret, "Failed to set DMA mask\n");
dma_set_max_seg_size(dev, UINT_MAX);
ret = ipu6_pci_config_setup(pdev, isp->hw_ver);
if (ret)
return ret;
ret = ipu6_buttress_init(isp);
if (ret)
return ret;
ret = request_firmware(&isp->cpd_fw, isp->cpd_fw_name, dev);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Requesting signed firmware %s failed\n",
isp->cpd_fw_name);
goto buttress_exit;
}
ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data,
isp->cpd_fw->size);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Failed to validate cpd\n");
goto out_ipu6_bus_del_devices;
}
isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl,
sizeof(isys_buttress_ctrl), GFP_KERNEL);
if (!isys_ctrl) {
ret = -ENOMEM;
goto out_ipu6_bus_del_devices;
}
isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base,
&isys_ipdata);
if (IS_ERR(isp->isys)) {
ret = PTR_ERR(isp->isys);
goto out_ipu6_bus_del_devices;
}
psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl,
sizeof(psys_buttress_ctrl), GFP_KERNEL);
if (!psys_ctrl) {
ret = -ENOMEM;
goto out_ipu6_bus_del_devices;
}
isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl,
psys_base, &psys_ipdata);
if (IS_ERR(isp->psys)) {
ret = PTR_ERR(isp->psys);
goto out_ipu6_bus_del_devices;
}
ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
if (ret < 0)
goto out_ipu6_bus_del_devices;
ret = ipu6_mmu_hw_init(isp->psys->mmu);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"Failed to set MMU hardware\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw,
&isp->psys->fw_sgt);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"failed to create pkg dir\n");
goto out_ipu6_rpm_put;
}
ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr,
ipu6_buttress_isr_threaded,
IRQF_SHARED, IPU6_NAME, isp);
if (ret) {
dev_err_probe(dev, ret, "Requesting irq failed\n");
goto out_ipu6_rpm_put;
}
ret = ipu6_buttress_authenticate(isp);
if (ret) {
dev_err_probe(&isp->pdev->dev, ret,
"FW authentication failed\n");
goto out_free_irq;
}
ipu6_mmu_hw_cleanup(isp->psys->mmu);
pm_runtime_put(&isp->psys->auxdev.dev);
/* Configure the arbitration mechanisms for VC requests */
ipu6_configure_vc_mechanism(isp);
val = readl(isp->base + BUTTRESS_REG_SKU);
sku_id = FIELD_GET(GENMASK(6, 4), val);
version = FIELD_GET(GENMASK(3, 0), val);
dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id,
pdev->device, isp->hw_ver);
pm_runtime_put_noidle(dev);
pm_runtime_allow(dev);
isp->bus_ready_to_probe = true;
return 0;
out_free_irq:
devm_free_irq(dev, pdev->irq, isp);
out_ipu6_rpm_put:
pm_runtime_put_sync(&isp->psys->auxdev.dev);
out_ipu6_bus_del_devices:
if (!IS_ERR_OR_NULL(isp->psys)) {
ipu6_cpd_free_pkg_dir(isp->psys);
ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
}
if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu))
ipu6_mmu_cleanup(isp->psys->mmu);
if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu))
ipu6_mmu_cleanup(isp->isys->mmu);
ipu6_bus_del_devices(pdev);
release_firmware(isp->cpd_fw);
buttress_exit:
ipu6_buttress_exit(isp);
return ret;
}
static void ipu6_pci_remove(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_mmu *isys_mmu = isp->isys->mmu;
struct ipu6_mmu *psys_mmu = isp->psys->mmu;
devm_free_irq(&pdev->dev, pdev->irq, isp);
ipu6_cpd_free_pkg_dir(isp->psys);
ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
ipu6_buttress_exit(isp);
ipu6_bus_del_devices(pdev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
release_firmware(isp->cpd_fw);
ipu6_mmu_cleanup(psys_mmu);
ipu6_mmu_cleanup(isys_mmu);
}
static void ipu6_pci_reset_prepare(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
pm_runtime_forbid(&isp->pdev->dev);
}
static void ipu6_pci_reset_done(struct pci_dev *pdev)
{
struct ipu6_device *isp = pci_get_drvdata(pdev);
ipu6_buttress_restore(isp);
if (isp->secure_mode)
ipu6_buttress_reset_authentication(isp);
isp->need_ipc_reset = true;
pm_runtime_allow(&isp->pdev->dev);
}
/*
* PCI base driver code requires driver to provide these to enable
* PCI device level PM state transitions (D0<->D3)
*/
static int ipu6_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
synchronize_irq(pdev->irq);
return 0;
}
static int ipu6_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct ipu6_device *isp = pci_get_drvdata(pdev);
struct ipu6_buttress *b = &isp->buttress;
int ret;
/* Configure the arbitration mechanisms for VC requests */
ipu6_configure_vc_mechanism(isp);
isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
dev_info(dev, "IPU6 in %s mode\n",
isp->secure_mode ? "secure" : "non-secure");
ipu6_buttress_restore(isp);
ret = ipu6_buttress_ipc_reset(isp, &b->cse);
if (ret)
dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n");
ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
if (ret < 0) {
dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n");
return 0;
}
ret = ipu6_buttress_authenticate(isp);
if (ret)
dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret);
pm_runtime_put(&isp->psys->auxdev.dev);
return 0;
}
static int ipu6_runtime_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct ipu6_device *isp = pci_get_drvdata(pdev);
int ret;
ipu6_configure_vc_mechanism(isp);
ipu6_buttress_restore(isp);
if (isp->need_ipc_reset) {
struct ipu6_buttress *b = &isp->buttress;
isp->need_ipc_reset = false;
ret = ipu6_buttress_ipc_reset(isp, &b->cse);
if (ret)
dev_err(&isp->pdev->dev, "IPC reset protocol failed\n");
}
return 0;
}
static const struct dev_pm_ops ipu6_pm_ops = {
SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume)
RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL)
};
MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl);
static const struct pci_error_handlers pci_err_handlers = {
.reset_prepare = ipu6_pci_reset_prepare,
.reset_done = ipu6_pci_reset_done,
};
static struct pci_driver ipu6_pci_driver = {
.name = IPU6_NAME,
.id_table = ipu6_pci_tbl,
.probe = ipu6_pci_probe,
.remove = ipu6_pci_remove,
.driver = {
.pm = pm_ptr(&ipu6_pm_ops),
},
.err_handler = &pci_err_handlers,
};
module_pci_driver(ipu6_pci_driver);
MODULE_IMPORT_NS("INTEL_IPU_BRIDGE");
MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
MODULE_AUTHOR("Qingwu Zhang <qingwu.zhang@intel.com>");
MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>");
MODULE_AUTHOR("Hongju Wang");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel IPU6 PCI driver");
@@ -0,0 +1,342 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 - 2024 Intel Corporation */
#ifndef IPU6_H
#define IPU6_H
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/types.h>
#include "ipu6-buttress.h"
struct firmware;
struct pci_dev;
struct ipu6_bus_device;
#define IPU6_NAME "intel-ipu6"
#define IPU6_MEDIA_DEV_MODEL_NAME "ipu6"
#define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin"
#define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin"
#define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin"
#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin"
#define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin"
enum ipu6_version {
IPU6_VER_INVALID = 0,
IPU6_VER_6 = 1,
IPU6_VER_6SE = 3,
IPU6_VER_6EP = 5,
IPU6_VER_6EP_MTL = 6,
};
/*
* IPU6 - TGL
* IPU6SE - JSL
* IPU6EP - ADL/RPL
* IPU6EP_MTL - MTL
*/
static inline bool is_ipu6se(u8 hw_ver)
{
return hw_ver == IPU6_VER_6SE;
}
static inline bool is_ipu6ep(u8 hw_ver)
{
return hw_ver == IPU6_VER_6EP;
}
static inline bool is_ipu6ep_mtl(u8 hw_ver)
{
return hw_ver == IPU6_VER_6EP_MTL;
}
static inline bool is_ipu6_tgl(u8 hw_ver)
{
return hw_ver == IPU6_VER_6;
}
/*
* ISYS DMA can overshoot. For higher resolutions over allocation is one line
* but it must be at minimum 1024 bytes. Value could be different in
* different versions / generations thus provide it via platform data.
*/
#define IPU6_ISYS_OVERALLOC_MIN 1024
/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */
#define IPU6_DEVICE_GDA_NR_PAGES 128
/* Virtualization factor to calculate the available virtual pages */
#define IPU6_DEVICE_GDA_VIRT_FACTOR 32
struct ipu6_device {
struct pci_dev *pdev;
struct list_head devices;
struct ipu6_bus_device *isys;
struct ipu6_bus_device *psys;
struct ipu6_buttress buttress;
const struct firmware *cpd_fw;
const char *cpd_fw_name;
u32 cpd_metadata_cmpnt_size;
void __iomem *base;
bool need_ipc_reset;
bool secure_mode;
u8 hw_ver;
bool bus_ready_to_probe;
};
#define IPU6_ISYS_NAME "isys"
#define IPU6_PSYS_NAME "psys"
#define IPU6_MMU_MAX_DEVICES 4
#define IPU6_MMU_ADDR_BITS 32
/* The firmware is accessible within the first 2 GiB only in non-secure mode. */
#define IPU6_MMU_ADDR_BITS_NON_SECURE 31
#define IPU6_MMU_MAX_TLB_L1_STREAMS 32
#define IPU6_MMU_MAX_TLB_L2_STREAMS 32
#define IPU6_MAX_LI_BLOCK_ADDR 128
#define IPU6_MAX_L2_BLOCK_ADDR 64
#define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX
#define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX
/*
* To maximize the IOSF utlization, IPU6 need to send requests in bursts.
* At the DMA interface with the buttress, there are CDC FIFOs with burst
* collection capability. CDC FIFO burst collectors have a configurable
* threshold and is configured based on the outcome of performance measurements.
*
* isys has 3 ports with IOSF interface for VC0, VC1 and VC2
* psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2
*
* Threshold values are pre-defined and are arrived at after performance
* evaluations on a type of IPU6
*/
#define IPU6_MAX_VC_IOSF_PORTS 4
/*
* IPU6 must configure correct arbitration mechanism related to the IOSF VC
* requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on
* stall and 1 means stall until the request is completed.
*/
#define IPU6_BTRS_ARB_MODE_TYPE_REARB 0
#define IPU6_BTRS_ARB_MODE_TYPE_STALL 1
/* Currently chosen arbitration mechanism for VC0 */
#define IPU6_BTRS_ARB_STALL_MODE_VC0 \
IPU6_BTRS_ARB_MODE_TYPE_REARB
/* Currently chosen arbitration mechanism for VC1 */
#define IPU6_BTRS_ARB_STALL_MODE_VC1 \
IPU6_BTRS_ARB_MODE_TYPE_REARB
/*
* MMU Invalidation HW bug workaround by ZLW mechanism
*
* Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in
* wrong translation or replication of the translation. This will cause data
* corruption. So we cannot directly use the MMU V2 invalidation registers
* to invalidate the MMU. Instead, whenever an invalidate is called, we need to
* clear the TLB by evicting all the valid translations by filling it with trash
* buffer (which is guaranteed not to be used by any other processes). ZLW is
* used to fill the L1 and L2 caches with the trash buffer translations. ZLW
* or Zero length write, is pre-fetch mechanism to pre-fetch the pages in
* advance to the L1 and L2 caches without triggering any memory operations.
*
* In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream
* One L1 block has 16 entries, hence points to 16 * 4K pages
* L2 -> 16 streams and 32 blocks. 2 blocks per streams
* One L2 block maps to 1024 L1 entries, hence points to 4MB address range
* 2 blocks per L2 stream means, 1 stream points to 8MB range
*
* As we need to clear the caches and 8MB being the biggest cache size, we need
* to have trash buffer which points to 8MB address range. As these trash
* buffers are not used for any memory transactions, we need only the least
* amount of physical memory. So we reserve 8MB IOVA address range but only
* one page is reserved from physical memory. Each of this 8MB IOVA address
* range is then mapped to the same physical memory page.
*/
/* One L2 entry maps 1024 L1 entries and one L1 entry per page */
#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE)
/* Max L2 blocks per stream */
#define IPU6_MMUV2_MAX_L2_BLOCKS 2
/* Max L1 blocks per stream */
#define IPU6_MMUV2_MAX_L1_BLOCKS 16
#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS)
/* Entries per L1 block */
#define MMUV2_ENTRIES_PER_L1_BLOCK 16
#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE)
#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE
/*
* In some of the IPU6 MMUs, there is provision to configure L1 and L2 page
* table caches. Both these L1 and L2 caches are divided into multiple sections
* called streams. There is maximum 16 streams for both caches. Each of these
* sections are subdivided into multiple blocks. When nr_l1streams = 0 and
* nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support
* L1/L2 page table caches.
*
* L1 stream per block sizes are configurable and varies per usecase.
* L2 has constant block sizes - 2 blocks per stream.
*
* MMU1 support pre-fetching of the pages to have less cache lookup misses. To
* enable the pre-fetching, MMU1 AT (Address Translator) device registers
* need to be configured.
*
* There are four types of memory accesses which requires ZLW configuration.
* ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU.
*
* 1. Sequential Access or 1D mode
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 1
* Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where
* N is pre-defined and hardcoded in the platform data
* Set ZLW_2D -> 0
*
* 2. ZLW 2D mode
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 1,
* Set ZLW_N -> 0
* Set ZLW_2D -> 1
*
* 3. ZLW Enable (no 1D or 2D mode)
* Set ZLW_EN -> 1
* set ZLW_PAGE_CROSS_1D -> 0,
* Set ZLW_N -> 0
* Set ZLW_2D -> 0
*
* 4. ZLW disable
* Set ZLW_EN -> 0
* set ZLW_PAGE_CROSS_1D -> 0,
* Set ZLW_N -> 0
* Set ZLW_2D -> 0
*
* To configure the ZLW for the above memory access, four registers are
* available. Hence to track these four settings, we have the following entries
* in the struct ipu6_mmu_hw. Each of these entries are per stream and
* available only for the L1 streams.
*
* a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN)
* b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary
* c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted
* Insert ZLW request N pages ahead address.
* d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D)
*
*
* Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined
* as per the usecase specific calculations. Any change to this pre-defined
* table has to happen in sync with IPU6 FW.
*/
struct ipu6_mmu_hw {
union {
unsigned long offset;
void __iomem *base;
};
u32 info_bits;
u8 nr_l1streams;
/*
* L1 has variable blocks per stream - total of 64 blocks and maximum of
* 16 blocks per stream. Configurable by using the block start address
* per stream. Block start address is calculated from the block size
*/
u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS];
/* Is ZLW is enabled in each stream */
bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS];
bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS];
bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
u32 l1_stream_id_reg_offset;
u32 l2_stream_id_reg_offset;
u8 nr_l2streams;
/*
* L2 has fixed 2 blocks per stream. Block address is calculated
* from the block size
*/
u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS];
/* flag to track if WA is needed for successive invalidate HW bug */
bool insert_read_before_invalidate;
};
struct ipu6_mmu_pdata {
u32 nr_mmus;
struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
int mmid;
};
struct ipu6_isys_csi2_pdata {
void __iomem *base;
};
struct ipu6_isys_internal_csi2_pdata {
u32 nports;
u32 irq_mask;
u32 ctrl0_irq_edge;
u32 ctrl0_irq_clear;
u32 ctrl0_irq_mask;
u32 ctrl0_irq_enable;
u32 ctrl0_irq_lnp;
u32 ctrl0_irq_status;
u32 fw_access_port_ofs;
};
struct ipu6_isys_internal_tpg_pdata {
u32 ntpgs;
u32 *offsets;
u32 *sels;
};
struct ipu6_hw_variants {
unsigned long offset;
u32 nr_mmus;
struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
u8 cdc_fifos;
u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS];
u32 dmem_offset;
u32 spc_offset;
};
struct ipu6_isys_internal_pdata {
struct ipu6_isys_internal_csi2_pdata csi2;
struct ipu6_hw_variants hw_variant;
u32 num_parallel_streams;
u32 isys_dma_overshoot;
u32 sram_gran_shift;
u32 sram_gran_size;
u32 max_sram_size;
u32 max_streams;
u32 max_send_queues;
u32 max_sram_blocks;
u32 max_devq_size;
u32 sensor_type_start;
u32 sensor_type_end;
u32 ltr;
u32 memopen_threshold;
bool enhanced_iwake;
};
struct ipu6_isys_pdata {
void __iomem *base;
const struct ipu6_isys_internal_pdata *ipdata;
};
struct ipu6_psys_internal_pdata {
struct ipu6_hw_variants hw_variant;
};
struct ipu6_psys_pdata {
void __iomem *base;
const struct ipu6_psys_internal_pdata *ipdata;
};
int ipu6_fw_authenticate(void *data, u64 val);
void ipu6_configure_spc(struct ipu6_device *isp,
const struct ipu6_hw_variants *hw_variant,
int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
dma_addr_t pkg_dir_dma_addr);
#endif /* IPU6_H */