Re: [PATCH v3 10/17] media: intel/ipu6: add input system driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Bingbu,

On Thu, Jan 11, 2024 at 02:55:24PM +0800, bingbu.cao@xxxxxxxxx wrote:
> From: Bingbu Cao <bingbu.cao@xxxxxxxxx>
> 
> Input system driver do basic isys hardware setup and irq handling
> and work with fwnode and v4l2 to register the ISYS v4l2 devices.
> 
> Signed-off-by: Bingbu Cao <bingbu.cao@xxxxxxxxx>
> Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>
> ---
>  drivers/media/pci/intel/ipu6/ipu6-isys.c | 1353 ++++++++++++++++++++++
>  drivers/media/pci/intel/ipu6/ipu6-isys.h |  207 ++++
>  2 files changed, 1560 insertions(+)
>  create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.c
>  create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.h
> 
> diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c
> new file mode 100644
> index 000000000000..e8983363a0da
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c
> @@ -0,0 +1,1353 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2013 - 2023 Intel Corporation
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/firmware.h>
> +#include <linux/io.h>
> +#include <linux/irqreturn.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pm_qos.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/string.h>
> +
> +#include <media/ipu-bridge.h>
> +#include <media/media-device.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#include "ipu6-bus.h"
> +#include "ipu6-cpd.h"
> +#include "ipu6-isys.h"
> +#include "ipu6-isys-csi2.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_BUTTRESS_FABIC_CONTROL		0x68
> +#define GDA_ENABLE_IWAKE_INDEX			2
> +#define GDA_IWAKE_THRESHOLD_INDEX		1
> +#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX	0
> +#define GDA_MEMOPEN_THRESHOLD_INDEX		3
> +#define DEFAULT_DID_RATIO			90
> +#define DEFAULT_IWAKE_THRESHOLD			0x42
> +#define DEFAULT_MEM_OPEN_TIME			10
> +#define ONE_THOUSAND_MICROSECOND		1000
> +/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */
> +#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE	0x800
> +
> +/* LTR & DID value are 10 bit at most */
> +#define LTR_DID_VAL_MAX				1023
> +#define LTR_DEFAULT_VALUE			0x70503c19
> +#define FILL_TIME_DEFAULT_VALUE			0xfff0783c
> +#define LTR_DID_PKGC_2R				20
> +#define LTR_SCALE_DEFAULT			5
> +#define LTR_SCALE_1024NS			2
> +#define DID_SCALE_1US				2
> +#define DID_SCALE_32US				3
> +#define REG_PKGC_PMON_CFG			0xb00
> +
> +#define VAL_PKGC_PMON_CFG_RESET			0x38
> +#define VAL_PKGC_PMON_CFG_START			0x7
> +
> +#define IS_PIXEL_BUFFER_PAGES			0x80
> +/*
> + * when iwake mode is disabled, the critical threshold is statically set
> + * to 75% of the IS pixel buffer, criticalThreshold = (128 * 3) / 4
> + */
> +#define CRITICAL_THRESHOLD_IWAKE_DISABLE	(IS_PIXEL_BUFFER_PAGES * 3 / 4)
> +
> +union fabric_ctrl {
> +	struct {
> +		u16 ltr_val   : 10;
> +		u16 ltr_scale : 3;
> +		u16 reserved  : 3;
> +		u16 did_val   : 10;
> +		u16 did_scale : 3;
> +		u16 reserved2 : 1;
> +		u16 keep_power_in_D0   : 1;
> +		u16 keep_power_override : 1;
> +	} bits;
> +	u32 value;
> +};
> +
> +enum ltr_did_type {
> +	LTR_IWAKE_ON,
> +	LTR_IWAKE_OFF,
> +	LTR_ISYS_ON,
> +	LTR_ISYS_OFF,
> +	LTR_ENHANNCE_IWAKE,
> +	LTR_TYPE_MAX
> +};
> +
> +#define ISYS_PM_QOS_VALUE	300
> +
> +static int isys_isr_one(struct ipu6_bus_device *adev);

Could you move isys_isr_one() above isys_isr() so you wouldn't need this?

> +
> +static int
> +isys_complete_ext_device_registration(struct ipu6_isys *isys,
> +				      struct v4l2_subdev *sd,
> +				      struct ipu6_isys_csi2_config *csi2)
> +{
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < sd->entity.num_pads; i++) {
> +		if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE)
> +			break;
> +	}
> +
> +	if (i == sd->entity.num_pads) {
> +		dev_warn(dev, "no src pad in external entity\n");
> +		ret = -ENOENT;
> +		goto unregister_subdev;
> +	}
> +
> +	ret = media_create_pad_link(&sd->entity, i,
> +				    &isys->csi2[csi2->port].asd.sd.entity,
> +				    0, 0);

Could you use v4l2_create_fwnode_links_to_pad() instead?

> +	if (ret) {
> +		dev_warn(dev, "can't create link\n");
> +		goto unregister_subdev;
> +	}
> +
> +	isys->csi2[csi2->port].nlanes = csi2->nlanes;
> +
> +	return 0;
> +
> +unregister_subdev:
> +	v4l2_device_unregister_subdev(sd);
> +
> +	return ret;
> +}
> +
> +static void isys_stream_init(struct ipu6_isys *isys)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
> +		mutex_init(&isys->streams[i].mutex);
> +		init_completion(&isys->streams[i].stream_open_completion);
> +		init_completion(&isys->streams[i].stream_close_completion);
> +		init_completion(&isys->streams[i].stream_start_completion);
> +		init_completion(&isys->streams[i].stream_stop_completion);
> +		INIT_LIST_HEAD(&isys->streams[i].queues);
> +		isys->streams[i].isys = isys;
> +		isys->streams[i].stream_handle = i;
> +		isys->streams[i].vc = INVALID_VC_ID;
> +	}
> +}
> +
> +static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys)
> +{
> +	const struct ipu6_isys_internal_csi2_pdata *csi2 =
> +		&isys->pdata->ipdata->csi2;
> +	unsigned int i;
> +
> +	for (i = 0; i < csi2->nports; i++)
> +		ipu6_isys_csi2_cleanup(&isys->csi2[i]);
> +}
> +
> +static int isys_csi2_register_subdevices(struct ipu6_isys *isys)
> +{
> +	const struct ipu6_isys_internal_csi2_pdata *csi2_pdata =
> +		&isys->pdata->ipdata->csi2;
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	unsigned int i;
> +	int ret;
> +
> +	isys->csi2 = devm_kcalloc(dev, csi2_pdata->nports,
> +				  sizeof(*isys->csi2), GFP_KERNEL);
> +	if (!isys->csi2)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < csi2_pdata->nports; i++) {
> +		ret = ipu6_isys_csi2_init(&isys->csi2[i], isys,
> +					  isys->pdata->base +
> +					  csi2_pdata->offsets[i], i);
> +		if (ret)
> +			goto fail;
> +
> +		isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i);
> +	}
> +
> +	return 0;
> +
> +fail:
> +	while (i--)
> +		ipu6_isys_csi2_cleanup(&isys->csi2[i]);
> +
> +	return ret;
> +}
> +
> +static int isys_csi2_create_media_links(struct ipu6_isys *isys)
> +{
> +	const struct ipu6_isys_internal_csi2_pdata *csi2_pdata =
> +		&isys->pdata->ipdata->csi2;
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	unsigned int i, j, k;
> +	int ret;
> +
> +	for (i = 0; i < csi2_pdata->nports; i++) {
> +		struct media_entity *sd = &isys->csi2[i].asd.sd.entity;
> +
> +		for (j = 0; j < NR_OF_VIDEO_DEVICE; j++) {
> +			struct media_entity *v = &isys->av[j].vdev.entity;
> +			u32 flag = MEDIA_LNK_FL_DYNAMIC;
> +
> +			for (k = CSI2_PAD_SRC; k < NR_OF_CSI2_PADS; k++) {

As we discussed earlier, you should create exactly one link from a source
pad to a video node.

> +				ret = media_create_pad_link(sd, k, v, 0, flag);
> +				if (ret) {
> +					dev_err(dev, "CSI2 can't create link\n");
> +					return ret;
> +				}
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void isys_unregister_video_devices(struct ipu6_isys *isys)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < NR_OF_VIDEO_DEVICE; i++)
> +		ipu6_isys_video_cleanup(&isys->av[i]);
> +}
> +
> +static int isys_register_video_devices(struct ipu6_isys *isys)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) {
> +		snprintf(isys->av[i].vdev.name, sizeof(isys->av[i].vdev.name),
> +			 IPU6_ISYS_ENTITY_PREFIX " ISYS Capture %u", i);
> +		isys->av[i].isys = isys;
> +		isys->av[i].aq.vbq.buf_struct_size =
> +			sizeof(struct ipu6_isys_video_buffer);
> +		isys->av[i].pfmt = &ipu6_isys_pfmts[0];
> +
> +		ret = ipu6_isys_video_init(&isys->av[i]);
> +		if (ret)
> +			goto fail;
> +	}
> +
> +	return 0;
> +
> +fail:
> +	while (i--)
> +		ipu6_isys_video_cleanup(&isys->av[i]);
> +
> +	return ret;
> +}
> +
> +void isys_setup_hw(struct ipu6_isys *isys)
> +{
> +	void __iomem *base = isys->pdata->base;
> +	const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold;
> +	u32 irqs = 0;
> +	unsigned int i, nports;
> +
> +	nports = isys->pdata->ipdata->csi2.nports;
> +
> +	/* Enable irqs for all MIPI ports */
> +	for (i = 0; i < nports; i++)
> +		irqs |= IPU6_ISYS_UNISPART_IRQ_CSI2(i);
> +
> +	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_edge);
> +	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_lnp);
> +	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_mask);
> +	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_enable);
> +	writel(GENMASK(19, 0),
> +	       base + isys->pdata->ipdata->csi2.ctrl0_irq_clear);
> +
> +	irqs = ISYS_UNISPART_IRQS;
> +	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_EDGE);
> +	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE);
> +	writel(GENMASK(28, 0), base + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR);
> +	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
> +	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_ENABLE);
> +
> +	writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG);
> +	writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG);
> +
> +	/* Write CDC FIFO threshold values for isys */
> +	for (i = 0; i < isys->pdata->ipdata->hw_variant.cdc_fifos; i++)
> +		writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i));
> +}
> +
> +static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2)
> +{
> +	struct ipu6_isys_stream *stream;
> +	unsigned int i;
> +	u32 status;
> +	int source;
> +
> +	ipu6_isys_register_errors(csi2);
> +
> +	status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
> +		       CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
> +
> +	writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
> +	       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
> +
> +	source = csi2->asd.source;
> +	for (i = 0; i < NR_OF_CSI2_VC; i++) {
> +		if (status & IPU_CSI_RX_IRQ_FS_VC(i)) {
> +			stream = ipu6_isys_query_stream_by_source(csi2->isys,
> +								  source, i);
> +			if (stream) {
> +				ipu6_isys_csi2_sof_event_by_stream(stream);
> +				ipu6_isys_put_stream(stream);
> +			}
> +		}
> +
> +		if (status & IPU_CSI_RX_IRQ_FE_VC(i)) {
> +			stream = ipu6_isys_query_stream_by_source(csi2->isys,
> +								  source, i);
> +			if (stream) {
> +				ipu6_isys_csi2_eof_event_by_stream(stream);
> +				ipu6_isys_put_stream(stream);
> +			}
> +		}

How about this:

Add this to register definitions:

#define IPU_CSI_RX_IRQ_STATUS_VC_SHIFT	1

Do this at the end of a loop round:

	status &= ~(IPU_CSI_RX_IRQ_FS_VC(i) | IPU_CSI_RX_IRQ_FE_VC(i));

And then loop increment will be:

	int ffs_status = ffs(status);

	i = ffs_status ? (ffs_status - 1) >> IPU_CSI_RX_IRQ_STATUS_VC_SHIFT :
		NR_OF_CSI2_VC;

> +	}
> +}
> +
> +irqreturn_t isys_isr(struct ipu6_bus_device *adev)
> +{
> +	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
> +	void __iomem *base = isys->pdata->base;
> +	u32 status_sw, status_csi;
> +	u32 ctrl0_status, ctrl0_clear;
> +
> +	spin_lock(&isys->power_lock);
> +	if (!isys->power) {
> +		spin_unlock(&isys->power_lock);
> +		return IRQ_NONE;
> +	}
> +
> +	ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status;
> +	ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear;
> +
> +	status_csi = readl(isys->pdata->base + ctrl0_status);
> +	status_sw = readl(isys->pdata->base +
> +			  IPU6_REG_ISYS_UNISPART_IRQ_STATUS);
> +
> +	writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW,
> +	       base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
> +
> +	do {
> +		writel(status_csi, isys->pdata->base + ctrl0_clear);
> +
> +		writel(status_sw, isys->pdata->base +
> +		       IPU6_REG_ISYS_UNISPART_IRQ_CLEAR);
> +
> +		if (isys->isr_csi2_bits & status_csi) {
> +			unsigned int i;
> +
> +			for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) {
> +				/* irq from not enabled port */
> +				if (!isys->csi2[i].base)
> +					continue;
> +				if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i))
> +					ipu6_isys_csi2_isr(&isys->csi2[i]);
> +			}
> +		}
> +
> +		writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG);
> +
> +		if (!isys_isr_one(adev))
> +			status_sw = IPU6_ISYS_UNISPART_IRQ_SW;
> +		else
> +			status_sw = 0;
> +
> +		status_csi = readl(isys->pdata->base + ctrl0_status);
> +		status_sw |= readl(isys->pdata->base +
> +				   IPU6_REG_ISYS_UNISPART_IRQ_STATUS);
> +	} while ((status_csi & isys->isr_csi2_bits) ||
> +		 (status_sw & IPU6_ISYS_UNISPART_IRQ_SW));
> +
> +	writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
> +
> +	spin_unlock(&isys->power_lock);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did)
> +{
> +	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
> +	struct ltr_did ltrdid_default;
> +
> +	ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE;
> +	ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE;
> +
> +	if (iwake_watermark->ltrdid.lut_ltr.value)
> +		*pltr_did = iwake_watermark->ltrdid;
> +	else
> +		*pltr_did = ltrdid_default;
> +}
> +
> +static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value)
> +{
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	u32 req_id = index;
> +	u32 offset = 0;
> +	int ret;
> +
> +	ret = ipu6_fw_isys_send_proxy_token(isys, req_id, index, offset, value);
> +	if (ret)
> +		dev_err(dev, "write %d failed %d", index, ret);
> +
> +	return ret;
> +}
> +
> +/*
> + * When input system is powered up and before enabling any new sensor capture,
> + * or after disabling any sensor capture the following values need to be set:
> + * LTR_value = LTR(usec) from calculation;
> + * LTR_scale = 2;
> + * DID_value = DID(usec) from calculation;
> + * DID_scale = 2;
> + *
> + * When input system is powered down, the LTR and DID values
> + * must be returned to the default values:
> + * LTR_value = 1023;
> + * LTR_scale = 5;
> + * DID_value = 1023;
> + * DID_scale = 2;
> + */
> +static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did,
> +			     enum ltr_did_type use)
> +{
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	u16 ltr_val, ltr_scale = LTR_SCALE_1024NS;
> +	u16 did_val, did_scale = DID_SCALE_1US;
> +	struct ipu6_device *isp = isys->adev->isp;
> +	union fabric_ctrl fc;
> +
> +	switch (use) {
> +	case LTR_IWAKE_ON:
> +		ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX);
> +		did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX);
> +		ltr_scale = (ltr == LTR_DID_VAL_MAX &&
> +			     did == LTR_DID_VAL_MAX) ?
> +			LTR_SCALE_DEFAULT : LTR_SCALE_1024NS;
> +		break;
> +	case LTR_ISYS_ON:
> +	case LTR_IWAKE_OFF:
> +		ltr_val = LTR_DID_PKGC_2R;
> +		did_val = LTR_DID_PKGC_2R;
> +		break;
> +	case LTR_ISYS_OFF:
> +		ltr_val   = LTR_DID_VAL_MAX;
> +		did_val   = LTR_DID_VAL_MAX;
> +		ltr_scale = LTR_SCALE_DEFAULT;
> +		break;
> +	case LTR_ENHANNCE_IWAKE:
> +		if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) {
> +			ltr_val = LTR_DID_VAL_MAX;
> +			did_val = LTR_DID_VAL_MAX;
> +			ltr_scale = LTR_SCALE_DEFAULT;
> +		} else if (did < ONE_THOUSAND_MICROSECOND) {
> +			ltr_val = ltr;
> +			did_val = did;
> +		} else {
> +			ltr_val = ltr;
> +			/* div 90% value by 32 to account for scale change */
> +			did_val = did / 32;
> +			did_scale = DID_SCALE_32US;
> +		}
> +		break;
> +	default:
> +		ltr_val   = LTR_DID_VAL_MAX;
> +		did_val   = LTR_DID_VAL_MAX;
> +		ltr_scale = LTR_SCALE_DEFAULT;
> +		break;
> +	}
> +
> +	fc.value = readl(isp->base + IPU6_BUTTRESS_FABIC_CONTROL);
> +	fc.bits.ltr_val = ltr_val;
> +	fc.bits.ltr_scale = ltr_scale;
> +	fc.bits.did_val = did_val;
> +	fc.bits.did_scale = did_scale;
> +
> +	dev_dbg(dev, "ltr: value %u scale %u, did: value %u scale %u\n",
> +		ltr_val, ltr_scale, did_val, did_scale);
> +	writel(fc.value, isp->base + IPU6_BUTTRESS_FABIC_CONTROL);
> +}
> +
> +/*
> + * Driver may clear register GDA_ENABLE_IWAKE before FW configures the
> + * stream for debug purpose. Otherwise driver should not access this register.
> + */
> +static void enable_iwake(struct ipu6_isys *isys, bool enable)
> +{
> +	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
> +	int ret;
> +
> +	mutex_lock(&iwake_watermark->mutex);
> +
> +	if (iwake_watermark->iwake_enabled == enable) {
> +		mutex_unlock(&iwake_watermark->mutex);
> +		return;
> +	}
> +
> +	ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable);
> +	if (!ret)
> +		iwake_watermark->iwake_enabled = enable;
> +
> +	mutex_unlock(&iwake_watermark->mutex);
> +}
> +
> +void update_watermark_setting(struct ipu6_isys *isys)
> +{
> +	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
> +	u32 iwake_threshold, iwake_critical_threshold, page_num;
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	u32 calc_fill_time_us = 0, ltr = 0, did = 0;
> +	struct video_stream_watermark *p_watermark;
> +	enum ltr_did_type ltr_did_type;
> +	struct list_head *stream_node;
> +	u64 isys_pb_datarate_mbs = 0;
> +	u32 mem_open_threshold = 0;
> +	struct ltr_did ltrdid;
> +	u64 threshold_bytes;
> +	u32 max_sram_size;
> +	u32 shift;
> +
> +	shift = isys->pdata->ipdata->sram_gran_shift;
> +	max_sram_size = isys->pdata->ipdata->max_sram_size;
> +
> +	mutex_lock(&iwake_watermark->mutex);
> +	if (iwake_watermark->force_iwake_disable) {
> +		set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF);
> +		set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
> +				   CRITICAL_THRESHOLD_IWAKE_DISABLE);
> +		goto unlock_exit;
> +	}
> +
> +	if (list_empty(&iwake_watermark->video_list)) {
> +		isys_pb_datarate_mbs = 0;

isys_pb_datarate_mbs is already initialised as zero as it needs to be for
the loop below. This assignment and the above check are redundant, please
remove them.

> +	} else {
> +		list_for_each(stream_node, &iwake_watermark->video_list) {
> +			p_watermark = list_entry(stream_node,
> +						 struct video_stream_watermark,
> +						 stream_node);
> +			isys_pb_datarate_mbs += p_watermark->stream_data_rate;
> +		}
> +	}
> +	mutex_unlock(&iwake_watermark->mutex);
> +
> +	if (!isys_pb_datarate_mbs) {
> +		enable_iwake(isys, false);
> +		set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF);
> +		mutex_lock(&iwake_watermark->mutex);
> +		set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
> +				   CRITICAL_THRESHOLD_IWAKE_DISABLE);
> +		goto unlock_exit;
> +	}
> +
> +	enable_iwake(isys, true);
> +	calc_fill_time_us = max_sram_size / isys_pb_datarate_mbs;
> +
> +	if (isys->pdata->ipdata->enhanced_iwake) {
> +		ltr = isys->pdata->ipdata->ltr;
> +		did = calc_fill_time_us * DEFAULT_DID_RATIO / 100;
> +		ltr_did_type = LTR_ENHANNCE_IWAKE;
> +	} else {
> +		get_lut_ltrdid(isys, &ltrdid);
> +
> +		if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0)
> +			ltr = 0;
> +		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1)
> +			ltr = ltrdid.lut_ltr.bits.val0;
> +		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2)
> +			ltr = ltrdid.lut_ltr.bits.val1;
> +		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3)
> +			ltr = ltrdid.lut_ltr.bits.val2;
> +		else
> +			ltr = ltrdid.lut_ltr.bits.val3;
> +
> +		did = calc_fill_time_us - ltr;
> +		ltr_did_type = LTR_IWAKE_ON;
> +	}
> +
> +	set_iwake_ltrdid(isys, ltr, did, ltr_did_type);
> +
> +	/* calculate iwake threshold with 2KB granularity pages */
> +	threshold_bytes = did * isys_pb_datarate_mbs;
> +	iwake_threshold = max_t(u32, 1, threshold_bytes >> shift);
> +	iwake_threshold = min_t(u32, iwake_threshold, max_sram_size);
> +
> +	mutex_lock(&iwake_watermark->mutex);
> +	if (isys->pdata->ipdata->enhanced_iwake) {
> +		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
> +				   DEFAULT_IWAKE_THRESHOLD);
> +		/* calculate number of pages that will be filled in 10 usec */
> +		page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) /
> +			ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE;
> +		page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) %
> +			     ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0;
> +		mem_open_threshold = isys->pdata->ipdata->memopen_threshold;
> +		mem_open_threshold = max_t(u32, mem_open_threshold, page_num);
> +		dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold);
> +		set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX,
> +				   mem_open_threshold);
> +	} else {
> +		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
> +				   iwake_threshold);
> +	}
> +
> +	iwake_critical_threshold = iwake_threshold +
> +		(IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2;
> +
> +	dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold,
> +		iwake_critical_threshold);
> +
> +	set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
> +			   iwake_critical_threshold);
> +
> +	writel(VAL_PKGC_PMON_CFG_RESET,
> +	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
> +	writel(VAL_PKGC_PMON_CFG_START,
> +	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
> +unlock_exit:
> +	mutex_unlock(&iwake_watermark->mutex);
> +}
> +
> +static void isys_iwake_watermark_init(struct ipu6_isys *isys)
> +{
> +	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
> +
> +	INIT_LIST_HEAD(&iwake_watermark->video_list);
> +	mutex_init(&iwake_watermark->mutex);
> +
> +	iwake_watermark->ltrdid.lut_ltr.value = 0;
> +	iwake_watermark->isys = isys;
> +	iwake_watermark->iwake_enabled = false;
> +	iwake_watermark->force_iwake_disable = false;
> +}
> +
> +static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys)
> +{
> +	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
> +
> +	mutex_lock(&iwake_watermark->mutex);
> +	list_del(&iwake_watermark->video_list);
> +	mutex_unlock(&iwake_watermark->mutex);
> +
> +	mutex_destroy(&iwake_watermark->mutex);
> +}
> +
> +/* The .bound() notifier callback when a match is found */
> +static int isys_notifier_bound(struct v4l2_async_notifier *notifier,
> +			       struct v4l2_subdev *sd,
> +			       struct v4l2_async_connection *asc)
> +{
> +	struct ipu6_isys *isys =
> +		container_of(notifier, struct ipu6_isys, notifier);
> +	struct sensor_async_sd *s_asd =
> +		container_of(asc, struct sensor_async_sd, asc);
> +	int ret;
> +
> +	ret = ipu_bridge_instantiate_vcm(sd->dev);
> +	if (ret) {
> +		dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n");
> +		return ret;
> +	}
> +
> +	dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n",
> +		sd->name, s_asd->csi2.nlanes, s_asd->csi2.port);
> +	ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2);
> +	if (ret)
> +		return ret;
> +
> +	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
> +}
> +
> +static int isys_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct ipu6_isys *isys =
> +		container_of(notifier, struct ipu6_isys, notifier);
> +
> +	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations isys_async_ops = {
> +	.bound = isys_notifier_bound,
> +	.complete = isys_notifier_complete,
> +};
> +
> +#define ISYS_MAX_PORTS 8
> +static int isys_notifier_init(struct ipu6_isys *isys)
> +{
> +	struct ipu6_device *isp = isys->adev->isp;
> +	struct device *dev = &isp->pdev->dev;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev);
> +
> +	for (i = 0; i < ISYS_MAX_PORTS; i++) {
> +		struct v4l2_fwnode_endpoint vep = {
> +			.bus_type = V4L2_MBUS_CSI2_DPHY
> +		};
> +		struct sensor_async_sd *s_asd;
> +		struct fwnode_handle *ep;
> +
> +		ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0,
> +						FWNODE_GRAPH_ENDPOINT_NEXT);
> +		if (!ep)
> +			continue;
> +
> +		ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> +		if (ret) {
> +			dev_err(dev, "fwnode endpoint parse failed: %d\n", ret);
> +			goto err_parse;
> +		}
> +
> +		s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep,
> +							struct sensor_async_sd);
> +		if (IS_ERR(s_asd)) {
> +			ret = PTR_ERR(s_asd);
> +			dev_err(dev, "add remove fwnode failed: %d\n", ret);
> +			goto err_parse;
> +		}
> +
> +		s_asd->csi2.port = vep.base.port;
> +		s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes;
> +
> +		dev_dbg(dev, "remote endpoint port %d with %d lanes added\n",
> +			s_asd->csi2.port, s_asd->csi2.nlanes);
> +
> +		fwnode_handle_put(ep);
> +
> +		continue;
> +
> +err_parse:
> +		fwnode_handle_put(ep);
> +		return ret;
> +	}
> +
> +	isys->notifier.ops = &isys_async_ops;
> +	ret = v4l2_async_nf_register(&isys->notifier);
> +	if (ret) {
> +		dev_err(dev, "failed to register async notifier : %d\n", ret);
> +		v4l2_async_nf_cleanup(&isys->notifier);
> +	}
> +
> +	return ret;
> +}
> +
> +static void isys_notifier_cleanup(struct ipu6_isys *isys)
> +{
> +	v4l2_async_nf_unregister(&isys->notifier);
> +	v4l2_async_nf_cleanup(&isys->notifier);
> +}
> +
> +static int isys_register_devices(struct ipu6_isys *isys)
> +{
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	struct pci_dev *pdev = isys->adev->isp->pdev;
> +	int ret;
> +
> +	isys->media_dev.dev = dev;
> +	media_device_pci_init(&isys->media_dev,
> +			      pdev, IPU6_MEDIA_DEV_MODEL_NAME);
> +
> +	strscpy(isys->v4l2_dev.name, isys->media_dev.model,
> +		sizeof(isys->v4l2_dev.name));
> +
> +	ret = media_device_register(&isys->media_dev);
> +	if (ret < 0)
> +		goto out_media_device_unregister;
> +
> +	isys->v4l2_dev.mdev = &isys->media_dev;
> +	isys->v4l2_dev.ctrl_handler = NULL;
> +
> +	ret = v4l2_device_register(dev->parent, &isys->v4l2_dev);
> +	if (ret < 0)
> +		goto out_media_device_unregister;
> +
> +	ret = isys_register_video_devices(isys);
> +	if (ret)
> +		goto out_v4l2_device_unregister;
> +
> +	ret = isys_csi2_register_subdevices(isys);
> +	if (ret)
> +		goto out_isys_unregister_video_device;
> +
> +	ret = isys_csi2_create_media_links(isys);
> +	if (ret)
> +		goto out_isys_unregister_subdevices;
> +
> +	ret = isys_notifier_init(isys);
> +	if (ret)
> +		goto out_isys_unregister_subdevices;
> +
> +	return 0;
> +
> +out_isys_unregister_subdevices:
> +	isys_csi2_unregister_subdevices(isys);
> +
> +out_isys_unregister_video_device:
> +	isys_unregister_video_devices(isys);
> +
> +out_v4l2_device_unregister:
> +	v4l2_device_unregister(&isys->v4l2_dev);
> +
> +out_media_device_unregister:
> +	media_device_unregister(&isys->media_dev);
> +	media_device_cleanup(&isys->media_dev);
> +
> +	dev_err(dev, "failed to register isys devices\n");
> +
> +	return ret;
> +}
> +
> +static void isys_unregister_devices(struct ipu6_isys *isys)
> +{
> +	isys_unregister_video_devices(isys);
> +	isys_csi2_unregister_subdevices(isys);
> +	v4l2_device_unregister(&isys->v4l2_dev);
> +	media_device_unregister(&isys->media_dev);
> +	media_device_cleanup(&isys->media_dev);
> +}
> +
> +static int isys_runtime_pm_resume(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
> +	struct ipu6_device *isp = adev->isp;
> +	unsigned long flags;
> +	int ret;
> +
> +	if (!isys)
> +		return 0;
> +
> +	ret = ipu6_mmu_hw_init(adev->mmu);
> +	if (ret)
> +		return ret;
> +
> +	cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE);
> +
> +	ret = ipu6_buttress_start_tsc_sync(isp);
> +	if (ret)
> +		return ret;
> +
> +	spin_lock_irqsave(&isys->power_lock, flags);
> +	isys->power = 1;
> +	spin_unlock_irqrestore(&isys->power_lock, flags);
> +
> +	isys_setup_hw(isys);
> +
> +	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON);
> +
> +	return 0;
> +}
> +
> +static int isys_runtime_pm_suspend(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	struct ipu6_isys *isys;
> +	unsigned long flags;
> +
> +	isys = dev_get_drvdata(dev);
> +	if (!isys)
> +		return 0;
> +
> +	spin_lock_irqsave(&isys->power_lock, flags);
> +	isys->power = 0;
> +	spin_unlock_irqrestore(&isys->power_lock, flags);
> +
> +	mutex_lock(&isys->mutex);
> +	isys->need_reset = false;
> +	mutex_unlock(&isys->mutex);
> +
> +	isys->phy_termcal_val = 0;
> +	cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
> +
> +	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF);
> +
> +	ipu6_mmu_hw_cleanup(adev->mmu);
> +
> +	return 0;
> +}
> +
> +static int isys_suspend(struct device *dev)
> +{
> +	struct ipu6_isys *isys = dev_get_drvdata(dev);
> +
> +	/* If stream is open, refuse to suspend */
> +	if (isys->stream_opened)
> +		return -EBUSY;
> +
> +	return 0;
> +}
> +
> +static int isys_resume(struct device *dev)
> +{
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops isys_pm_ops = {
> +	.runtime_suspend = isys_runtime_pm_suspend,
> +	.runtime_resume = isys_runtime_pm_resume,
> +	.suspend = isys_suspend,
> +	.resume = isys_resume,
> +};
> +
> +static void isys_remove(struct auxiliary_device *auxdev)
> +{
> +	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
> +	struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev);
> +	struct ipu6_device *isp = adev->isp;
> +	struct isys_fw_msgs *fwmsg, *safe;
> +	unsigned int i;
> +
> +	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head)
> +		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
> +			       fwmsg, fwmsg->dma_addr, 0);
> +
> +	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head)
> +		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
> +			       fwmsg, fwmsg->dma_addr, 0);
> +
> +	isys_unregister_devices(isys);
> +	isys_notifier_cleanup(isys);
> +
> +	cpu_latency_qos_remove_request(&isys->pm_qos);
> +
> +	if (!isp->secure_mode) {
> +		ipu6_cpd_free_pkg_dir(adev);
> +		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
> +		release_firmware(adev->fw);
> +	}
> +
> +	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
> +		mutex_destroy(&isys->streams[i].mutex);
> +
> +	isys_iwake_watermark_cleanup(isys);
> +	mutex_destroy(&isys->stream_mutex);
> +	mutex_destroy(&isys->mutex);
> +}
> +
> +static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount)
> +{
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	struct isys_fw_msgs *addr;
> +	dma_addr_t dma_addr;
> +	unsigned long flags;
> +	unsigned int i;
> +
> +	for (i = 0; i < amount; i++) {
> +		addr = dma_alloc_attrs(dev, sizeof(struct isys_fw_msgs),
> +				       &dma_addr, GFP_KERNEL, 0);
> +		if (!addr)
> +			break;
> +		addr->dma_addr = dma_addr;
> +
> +		spin_lock_irqsave(&isys->listlock, flags);
> +		list_add(&addr->head, &isys->framebuflist);
> +		spin_unlock_irqrestore(&isys->listlock, flags);
> +	}
> +
> +	if (i == amount)
> +		return 0;
> +
> +	spin_lock_irqsave(&isys->listlock, flags);
> +	while (!list_empty(&isys->framebuflist)) {
> +		addr = list_first_entry(&isys->framebuflist,
> +					struct isys_fw_msgs, head);
> +		list_del(&addr->head);
> +		spin_unlock_irqrestore(&isys->listlock, flags);
> +		dma_free_attrs(dev, sizeof(struct isys_fw_msgs), addr,
> +			       addr->dma_addr, 0);
> +		spin_lock_irqsave(&isys->listlock, flags);
> +	}
> +	spin_unlock_irqrestore(&isys->listlock, flags);
> +
> +	return -ENOMEM;
> +}
> +
> +struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream)
> +{
> +	struct ipu6_isys *isys = stream->isys;
> +	struct device *dev = &isys->adev->auxdev.dev;
> +	struct isys_fw_msgs *msg;
> +	unsigned long flags;
> +	int ret;
> +
> +	spin_lock_irqsave(&isys->listlock, flags);
> +	if (list_empty(&isys->framebuflist)) {
> +		spin_unlock_irqrestore(&isys->listlock, flags);
> +		dev_dbg(dev, "Frame list empty\n");
> +
> +		ret = alloc_fw_msg_bufs(isys, 5);
> +		if (ret < 0)
> +			return NULL;
> +
> +		spin_lock_irqsave(&isys->listlock, flags);
> +		if (list_empty(&isys->framebuflist)) {
> +			spin_unlock_irqrestore(&isys->listlock, flags);
> +			dev_err(dev, "Frame list empty\n");
> +			return NULL;
> +		}
> +	}
> +	msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head);
> +	list_move(&msg->head, &isys->framebuflist_fw);
> +	spin_unlock_irqrestore(&isys->listlock, flags);
> +	memset(&msg->fw_msg, 0, sizeof(msg->fw_msg));
> +
> +	return msg;
> +}
> +
> +void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys)
> +{
> +	struct isys_fw_msgs *fwmsg, *fwmsg0;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&isys->listlock, flags);
> +	list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head)
> +		list_move(&fwmsg->head, &isys->framebuflist);
> +	spin_unlock_irqrestore(&isys->listlock, flags);
> +}
> +
> +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data)
> +{
> +	struct isys_fw_msgs *msg;
> +	unsigned long flags;
> +	u64 *ptr = (u64 *)data;
> +
> +	if (!ptr)
> +		return;
> +
> +	spin_lock_irqsave(&isys->listlock, flags);
> +	msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy);
> +	list_move(&msg->head, &isys->framebuflist);
> +	spin_unlock_irqrestore(&isys->listlock, flags);
> +}
> +
> +static int isys_probe(struct auxiliary_device *auxdev,
> +		      const struct auxiliary_device_id *auxdev_id)
> +{
> +	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
> +	struct ipu6_device *isp = adev->isp;
> +	const struct firmware *fw;
> +	struct ipu6_isys *isys;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!isp->bus_ready_to_probe)
> +		return -EPROBE_DEFER;
> +
> +	isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL);
> +	if (!isys)
> +		return -ENOMEM;
> +
> +	ret = ipu6_mmu_hw_init(adev->mmu);
> +	if (ret)
> +		return ret;
> +
> +	adev->auxdrv_data =
> +		(const struct ipu6_auxdrv_data *)auxdev_id->driver_data;
> +	adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver);
> +	isys->adev = adev;
> +	isys->pdata = adev->pdata;
> +
> +	/* initial sensor type */
> +	isys->sensor_type = isys->pdata->ipdata->sensor_type_start;
> +
> +	spin_lock_init(&isys->streams_lock);
> +	spin_lock_init(&isys->power_lock);
> +	isys->power = 0;
> +	isys->phy_termcal_val = 0;
> +
> +	mutex_init(&isys->mutex);
> +	mutex_init(&isys->stream_mutex);
> +
> +	spin_lock_init(&isys->listlock);
> +	INIT_LIST_HEAD(&isys->framebuflist);
> +	INIT_LIST_HEAD(&isys->framebuflist_fw);
> +
> +	isys->line_align = IPU6_ISYS_2600_MEM_LINE_ALIGN;
> +	isys->icache_prefetch = 0;
> +
> +	dev_set_drvdata(&auxdev->dev, isys);
> +
> +	isys_stream_init(isys);
> +
> +	if (!isp->secure_mode) {
> +		fw = isp->cpd_fw;
> +		ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt);
> +		if (ret)
> +			goto release_firmware;
> +
> +		ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data);
> +		if (ret)
> +			goto remove_shared_buffer;
> +	}
> +
> +	cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
> +
> +	ret = alloc_fw_msg_bufs(isys, 20);
> +	if (ret < 0)
> +		goto out_remove_pkg_dir_shared_buffer;
> +
> +	isys_iwake_watermark_init(isys);
> +
> +	if (is_ipu6se(adev->isp->hw_ver))
> +		isys->phy_set_power = ipu6_isys_jsl_phy_set_power;
> +	else if (is_ipu6ep_mtl(adev->isp->hw_ver))
> +		isys->phy_set_power = ipu6_isys_dwc_phy_set_power;
> +	else
> +		isys->phy_set_power = ipu6_isys_mcd_phy_set_power;
> +
> +	ret = isys_register_devices(isys);
> +	if (ret)
> +		goto out_remove_pkg_dir_shared_buffer;
> +
> +	ipu6_mmu_hw_cleanup(adev->mmu);
> +
> +	return 0;
> +
> +out_remove_pkg_dir_shared_buffer:
> +	if (!isp->secure_mode)
> +		ipu6_cpd_free_pkg_dir(adev);
> +remove_shared_buffer:
> +	if (!isp->secure_mode)
> +		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
> +release_firmware:
> +	if (!isp->secure_mode)
> +		release_firmware(adev->fw);
> +
> +	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
> +		mutex_destroy(&isys->streams[i].mutex);
> +
> +	mutex_destroy(&isys->mutex);
> +	mutex_destroy(&isys->stream_mutex);
> +
> +	ipu6_mmu_hw_cleanup(adev->mmu);
> +
> +	return ret;
> +}
> +
> +struct fwmsg {
> +	int type;
> +	char *msg;
> +	bool valid_ts;
> +};
> +
> +static const struct fwmsg fw_msg[] = {
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
> +	 "STREAM_START_AND_CAPTURE_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
> +	 "STREAM_START_AND_CAPTURE_DONE", 1},
> +	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1},
> +	{IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1},
> +	{IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1},
> +	{IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1},

There are no unused indices in the response type numbers up to the largest
response type number. Could you instead index the array by response types?

> +	{-1, "UNKNOWN MESSAGE", 0}
> +};
> +
> +static u32 resp_type_to_index(int type)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(fw_msg); i++)
> +		if (fw_msg[i].type == type)
> +			return i;

Then you can remove this loop.

> +
> +	return  ARRAY_SIZE(fw_msg) - 1;

Extra space after return.

> +}
> +
> +static int isys_isr_one(struct ipu6_bus_device *adev)
> +{
> +	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
> +	struct ipu6_fw_isys_resp_info_abi *resp;
> +	struct ipu6_isys_stream *stream;
> +	struct ipu6_isys_csi2 *csi2 = NULL;
> +	u32 index;
> +	u64 ts;
> +
> +	if (!isys->fwcom)
> +		return 1;
> +
> +	resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
> +	if (!resp)
> +		return 1;
> +
> +	ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0];
> +
> +	index = resp_type_to_index(resp->type);
> +	dev_dbg(&adev->auxdev.dev,
> +		"FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n",
> +		resp->type, fw_msg[index].msg, resp->stream_handle,
> +		fw_msg[index].valid_ts ? ts : 0, resp->pin_id);
> +
> +	if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION)
> +		/* Suspension is kind of special case: not enough buffers */
> +		dev_dbg(&adev->auxdev.dev,
> +			"FW error resp SUSPENSION, details %d\n",
> +			resp->error_info.error_details);
> +	else if (resp->error_info.error)
> +		dev_dbg(&adev->auxdev.dev,
> +			"FW error resp error %d, details %d\n",
> +			resp->error_info.error, resp->error_info.error_details);
> +
> +	if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) {
> +		dev_err(&adev->auxdev.dev, "bad stream handle %u\n",
> +			resp->stream_handle);
> +		goto leave;
> +	}
> +
> +	stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle);
> +	if (!stream) {
> +		dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n",
> +			resp->stream_handle);
> +		goto leave;
> +	}
> +	stream->error = resp->error_info.error;
> +
> +	csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
> +
> +	switch (resp->type) {
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE:
> +		complete(&stream->stream_open_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK:
> +		complete(&stream->stream_close_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK:
> +		complete(&stream->stream_start_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK:
> +		complete(&stream->stream_start_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK:
> +		complete(&stream->stream_stop_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK:
> +		complete(&stream->stream_stop_completion);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY:
> +		/*
> +		 * firmware only release the capture msg until software
> +		 * get pin_data_ready event
> +		 */
> +		ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id);
> +		if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS &&
> +		    stream->output_pins[resp->pin_id].pin_ready)
> +			stream->output_pins[resp->pin_id].pin_ready(stream,
> +								    resp);
> +		else
> +			dev_warn(&adev->auxdev.dev,
> +				 "%d:No data pin ready handler for pin id %d\n",
> +				 resp->stream_handle, resp->pin_id);
> +		if (csi2)
> +			ipu6_isys_csi2_error(csi2);
> +
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK:
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE:
> +	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE:
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF:
> +
> +		ipu6_isys_csi2_sof_event_by_stream(stream);
> +		stream->seq[stream->seq_index].sequence =
> +			atomic_read(&stream->sequence) - 1;
> +		stream->seq[stream->seq_index].timestamp = ts;
> +		dev_dbg(&adev->auxdev.dev,
> +			"sof: handle %d: (index %u), timestamp 0x%16.16llx\n",
> +			resp->stream_handle,
> +			stream->seq[stream->seq_index].sequence, ts);
> +		stream->seq_index = (stream->seq_index + 1)
> +			% IPU6_ISYS_MAX_PARALLEL_SOF;
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF:
> +		ipu6_isys_csi2_eof_event_by_stream(stream);
> +		dev_dbg(&adev->auxdev.dev,
> +			"eof: handle %d: (index %u), timestamp 0x%16.16llx\n",
> +			resp->stream_handle,
> +			stream->seq[stream->seq_index].sequence, ts);
> +		break;
> +	case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY:
> +		break;
> +	default:
> +		dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n",
> +			resp->stream_handle, resp->type);
> +		break;
> +	}
> +
> +	ipu6_isys_put_stream(stream);
> +leave:
> +	ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
> +	return 0;
> +}
> +
> +static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = {
> +	.isr = isys_isr,
> +	.isr_threaded = NULL,
> +	.wake_isr_thread = false,
> +};
> +
> +static const struct auxiliary_device_id ipu6_isys_id_table[] = {
> +	{
> +		.name = "intel_ipu6.isys",
> +		.driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table);
> +
> +static struct auxiliary_driver isys_driver = {
> +	.name = IPU6_ISYS_NAME,
> +	.probe = isys_probe,
> +	.remove = isys_remove,
> +	.id_table = ipu6_isys_id_table,
> +	.driver = {
> +		.pm = &isys_pm_ops,
> +	},
> +};
> +
> +module_auxiliary_driver(isys_driver);
> +
> +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@xxxxxxxxxxxxxxx>");
> +MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@xxxxxxxxx>");
> +MODULE_AUTHOR("Bingbu Cao <bingbu.cao@xxxxxxxxx>");
> +MODULE_AUTHOR("Yunliang Ding <yunliang.ding@xxxxxxxxx>");
> +MODULE_AUTHOR("Hongju Wang <hongju.wang@xxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Intel IPU6 input system driver");
> +MODULE_IMPORT_NS(INTEL_IPU6);
> +MODULE_IMPORT_NS(INTEL_IPU_BRIDGE);
> diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h
> new file mode 100644
> index 000000000000..cf7a90bfedc9
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h
> @@ -0,0 +1,207 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (C) 2013 - 2023 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)
> +
> +#define IPU6_ISYS_2600_MEM_LINE_ALIGN	64
> +
> +/*
> + * 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		1U
> +#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
> + * @line_align: line alignment in memory
> + * @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;
> +	unsigned int line_align;
> +	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 ipu6_isys_video av[NR_OF_VIDEO_DEVICE];
> +
> +	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, u64 data);
> +void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys);
> +
> +extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops;
> +
> +void isys_setup_hw(struct ipu6_isys *isys);
> +irqreturn_t isys_isr(struct ipu6_bus_device *adev);
> +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 */

-- 
Regards,

Sakari Ailus




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux