mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
media: staging/ipu7: add IPU7 input system device driver
The main input system driver mainly cover the basic hardware setup, v4l2 devices registration, firmware stream interfaces and interrupt handling. Input system CSI2 receiver is exposed as a v4l2 sub-device. Each CSI2 sub-device represent one single CSI2 hardware port which be linked with external sub-device such camera sensor by linked with ISYS CSI2's sink pad. The CSI2 source pad is linked to the sink pad of video capture device. Register V4L2 video device and setup the VB2 queues to support video capture. Video streaming callback will trigger the input system driver to construct a input system stream configuration for firmware based on data type and stream ID and then queue buffers to firmware to do capture. IPU7 CSI-2 D-PHY hardware is a Synopsys DWC MIPI CSI2 Rx IP, the driver program the DPHY to receive MIPI data from camera sensors. Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
This commit is contained in:
301
drivers/staging/media/ipu7/ipu7-fw-isys.c
Normal file
301
drivers/staging/media/ipu7/ipu7-fw-isys.c
Normal file
@@ -0,0 +1,301 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/cacheflush.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "abi/ipu7_fw_insys_config_abi.h"
|
||||
#include "abi/ipu7_fw_isys_abi.h"
|
||||
|
||||
#include "ipu7.h"
|
||||
#include "ipu7-boot.h"
|
||||
#include "ipu7-bus.h"
|
||||
#include "ipu7-dma.h"
|
||||
#include "ipu7-fw-isys.h"
|
||||
#include "ipu7-isys.h"
|
||||
#include "ipu7-platform-regs.h"
|
||||
#include "ipu7-syscom.h"
|
||||
|
||||
static const char * const send_msg_types[N_IPU_INSYS_SEND_TYPE] = {
|
||||
"STREAM_OPEN",
|
||||
"STREAM_START_AND_CAPTURE",
|
||||
"STREAM_CAPTURE",
|
||||
"STREAM_ABORT",
|
||||
"STREAM_FLUSH",
|
||||
"STREAM_CLOSE"
|
||||
};
|
||||
|
||||
int ipu7_fw_isys_complex_cmd(struct ipu7_isys *isys,
|
||||
const unsigned int stream_handle,
|
||||
void *cpu_mapped_buf,
|
||||
dma_addr_t dma_mapped_buf,
|
||||
size_t size, u16 send_type)
|
||||
{
|
||||
struct ipu7_syscom_context *ctx = isys->adev->syscom;
|
||||
struct device *dev = &isys->adev->auxdev.dev;
|
||||
struct ipu7_insys_send_queue_token *token;
|
||||
|
||||
if (send_type >= N_IPU_INSYS_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 = ipu7_syscom_get_token(ctx, stream_handle +
|
||||
IPU_INSYS_INPUT_MSG_QUEUE);
|
||||
if (!token)
|
||||
return -EBUSY;
|
||||
|
||||
token->addr = dma_mapped_buf;
|
||||
token->buf_handle = (unsigned long)cpu_mapped_buf;
|
||||
token->send_type = send_type;
|
||||
token->stream_id = stream_handle;
|
||||
token->flag = IPU_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE;
|
||||
|
||||
ipu7_syscom_put_token(ctx, stream_handle + IPU_INSYS_INPUT_MSG_QUEUE);
|
||||
/* now wakeup FW */
|
||||
ipu_buttress_wakeup_is_uc(isys->adev->isp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipu7_fw_isys_simple_cmd(struct ipu7_isys *isys,
|
||||
const unsigned int stream_handle, u16 send_type)
|
||||
{
|
||||
return ipu7_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0,
|
||||
send_type);
|
||||
}
|
||||
|
||||
int ipu7_fw_isys_init(struct ipu7_isys *isys)
|
||||
{
|
||||
struct syscom_queue_config *queue_configs;
|
||||
struct ipu7_bus_device *adev = isys->adev;
|
||||
struct device *dev = &adev->auxdev.dev;
|
||||
struct ipu7_insys_config *isys_config;
|
||||
struct ipu7_syscom_context *syscom;
|
||||
dma_addr_t isys_config_dma_addr;
|
||||
unsigned int i, num_queues;
|
||||
u32 freq;
|
||||
u8 major;
|
||||
int ret;
|
||||
|
||||
/* Allocate and init syscom context. */
|
||||
syscom = devm_kzalloc(dev, sizeof(struct ipu7_syscom_context),
|
||||
GFP_KERNEL);
|
||||
if (!syscom)
|
||||
return -ENOMEM;
|
||||
|
||||
adev->syscom = syscom;
|
||||
syscom->num_input_queues = IPU_INSYS_MAX_INPUT_QUEUES;
|
||||
syscom->num_output_queues = IPU_INSYS_MAX_OUTPUT_QUEUES;
|
||||
num_queues = syscom->num_input_queues + syscom->num_output_queues;
|
||||
queue_configs = devm_kzalloc(dev, FW_QUEUE_CONFIG_SIZE(num_queues),
|
||||
GFP_KERNEL);
|
||||
if (!queue_configs) {
|
||||
ipu7_fw_isys_release(isys);
|
||||
return -ENOMEM;
|
||||
}
|
||||
syscom->queue_configs = queue_configs;
|
||||
queue_configs[IPU_INSYS_OUTPUT_MSG_QUEUE].max_capacity =
|
||||
IPU_ISYS_SIZE_RECV_QUEUE;
|
||||
queue_configs[IPU_INSYS_OUTPUT_MSG_QUEUE].token_size_in_bytes =
|
||||
sizeof(struct ipu7_insys_resp);
|
||||
queue_configs[IPU_INSYS_OUTPUT_LOG_QUEUE].max_capacity =
|
||||
IPU_ISYS_SIZE_LOG_QUEUE;
|
||||
queue_configs[IPU_INSYS_OUTPUT_LOG_QUEUE].token_size_in_bytes =
|
||||
sizeof(struct ipu7_insys_resp);
|
||||
queue_configs[IPU_INSYS_OUTPUT_RESERVED_QUEUE].max_capacity = 0;
|
||||
queue_configs[IPU_INSYS_OUTPUT_RESERVED_QUEUE].token_size_in_bytes = 0;
|
||||
|
||||
queue_configs[IPU_INSYS_INPUT_DEV_QUEUE].max_capacity =
|
||||
IPU_ISYS_MAX_STREAMS;
|
||||
queue_configs[IPU_INSYS_INPUT_DEV_QUEUE].token_size_in_bytes =
|
||||
sizeof(struct ipu7_insys_send_queue_token);
|
||||
|
||||
for (i = IPU_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) {
|
||||
queue_configs[i].max_capacity = IPU_ISYS_SIZE_SEND_QUEUE;
|
||||
queue_configs[i].token_size_in_bytes =
|
||||
sizeof(struct ipu7_insys_send_queue_token);
|
||||
}
|
||||
|
||||
/* Allocate ISYS subsys config. */
|
||||
isys_config = ipu7_dma_alloc(adev, sizeof(struct ipu7_insys_config),
|
||||
&isys_config_dma_addr, GFP_KERNEL, 0);
|
||||
if (!isys_config) {
|
||||
dev_err(dev, "Failed to allocate isys subsys config.\n");
|
||||
ipu7_fw_isys_release(isys);
|
||||
return -ENOMEM;
|
||||
}
|
||||
isys->subsys_config = isys_config;
|
||||
isys->subsys_config_dma_addr = isys_config_dma_addr;
|
||||
memset(isys_config, 0, sizeof(struct ipu7_insys_config));
|
||||
isys_config->logger_config.use_source_severity = 0;
|
||||
isys_config->logger_config.use_channels_enable_bitmask = 1;
|
||||
isys_config->logger_config.channels_enable_bitmask =
|
||||
LOGGER_CONFIG_CHANNEL_ENABLE_SYSCOM_BITMASK;
|
||||
isys_config->logger_config.hw_printf_buffer_base_addr = 0U;
|
||||
isys_config->logger_config.hw_printf_buffer_size_bytes = 0U;
|
||||
isys_config->wdt_config.wdt_timer1_us = 0;
|
||||
isys_config->wdt_config.wdt_timer2_us = 0;
|
||||
ret = ipu_buttress_get_isys_freq(adev->isp, &freq);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get ISYS frequency.\n");
|
||||
ipu7_fw_isys_release(isys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ipu7_dma_sync_single(adev, isys_config_dma_addr,
|
||||
sizeof(struct ipu7_insys_config));
|
||||
|
||||
major = is_ipu8(adev->isp->hw_ver) ? 2U : 1U;
|
||||
ret = ipu7_boot_init_boot_config(adev, queue_configs, num_queues,
|
||||
freq, isys_config_dma_addr, major);
|
||||
if (ret)
|
||||
ipu7_fw_isys_release(isys);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ipu7_fw_isys_release(struct ipu7_isys *isys)
|
||||
{
|
||||
struct ipu7_bus_device *adev = isys->adev;
|
||||
|
||||
ipu7_boot_release_boot_config(adev);
|
||||
if (isys->subsys_config) {
|
||||
ipu7_dma_free(adev,
|
||||
sizeof(struct ipu7_insys_config),
|
||||
isys->subsys_config,
|
||||
isys->subsys_config_dma_addr, 0);
|
||||
isys->subsys_config = NULL;
|
||||
isys->subsys_config_dma_addr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ipu7_fw_isys_open(struct ipu7_isys *isys)
|
||||
{
|
||||
return ipu7_boot_start_fw(isys->adev);
|
||||
}
|
||||
|
||||
int ipu7_fw_isys_close(struct ipu7_isys *isys)
|
||||
{
|
||||
return ipu7_boot_stop_fw(isys->adev);
|
||||
}
|
||||
|
||||
struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu7_isys *isys)
|
||||
{
|
||||
return (struct ipu7_insys_resp *)
|
||||
ipu7_syscom_get_token(isys->adev->syscom,
|
||||
IPU_INSYS_OUTPUT_MSG_QUEUE);
|
||||
}
|
||||
|
||||
void ipu7_fw_isys_put_resp(struct ipu7_isys *isys)
|
||||
{
|
||||
ipu7_syscom_put_token(isys->adev->syscom, IPU_INSYS_OUTPUT_MSG_QUEUE);
|
||||
}
|
||||
|
||||
void ipu7_fw_isys_dump_stream_cfg(struct device *dev,
|
||||
struct ipu7_insys_stream_cfg *cfg)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
dev_dbg(dev, "---------------------------\n");
|
||||
dev_dbg(dev, "IPU_FW_ISYS_STREAM_CFG_DATA\n");
|
||||
|
||||
dev_dbg(dev, ".port id %d\n", cfg->port_id);
|
||||
dev_dbg(dev, ".vc %d\n", cfg->vc);
|
||||
dev_dbg(dev, ".nof_input_pins = %d\n", cfg->nof_input_pins);
|
||||
dev_dbg(dev, ".nof_output_pins = %d\n", cfg->nof_output_pins);
|
||||
dev_dbg(dev, ".stream_msg_map = 0x%x\n", cfg->stream_msg_map);
|
||||
|
||||
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.disable_mipi_unpacking = %d\n",
|
||||
cfg->input_pins[i].disable_mipi_unpacking);
|
||||
dev_dbg(dev, "\t.dt_rename_mode = %d\n",
|
||||
cfg->input_pins[i].dt_rename_mode);
|
||||
dev_dbg(dev, "\t.mapped_dt = 0x%0x\n",
|
||||
cfg->input_pins[i].mapped_dt);
|
||||
dev_dbg(dev, "\t.input_res = %d x %d\n",
|
||||
cfg->input_pins[i].input_res.width,
|
||||
cfg->input_pins[i].input_res.height);
|
||||
dev_dbg(dev, "\t.sync_msg_map = 0x%x\n",
|
||||
cfg->input_pins[i].sync_msg_map);
|
||||
}
|
||||
|
||||
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.stride = %d\n", cfg->output_pins[i].stride);
|
||||
dev_dbg(dev, "\t.send_irq = %d\n",
|
||||
cfg->output_pins[i].send_irq);
|
||||
dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft);
|
||||
|
||||
dev_dbg(dev, "\t.link.buffer_lines = %d\n",
|
||||
cfg->output_pins[i].link.buffer_lines);
|
||||
dev_dbg(dev, "\t.link.foreign_key = %d\n",
|
||||
cfg->output_pins[i].link.foreign_key);
|
||||
dev_dbg(dev, "\t.link.granularity_pointer_update = %d\n",
|
||||
cfg->output_pins[i].link.granularity_pointer_update);
|
||||
dev_dbg(dev, "\t.link.msg_link_streaming_mode = %d\n",
|
||||
cfg->output_pins[i].link.msg_link_streaming_mode);
|
||||
dev_dbg(dev, "\t.link.pbk_id = %d\n",
|
||||
cfg->output_pins[i].link.pbk_id);
|
||||
dev_dbg(dev, "\t.link.pbk_slot_id = %d\n",
|
||||
cfg->output_pins[i].link.pbk_slot_id);
|
||||
dev_dbg(dev, "\t.link.dest = %d\n",
|
||||
cfg->output_pins[i].link.dest);
|
||||
dev_dbg(dev, "\t.link.use_sw_managed = %d\n",
|
||||
cfg->output_pins[i].link.use_sw_managed);
|
||||
dev_dbg(dev, "\t.link.is_snoop = %d\n",
|
||||
cfg->output_pins[i].link.is_snoop);
|
||||
|
||||
dev_dbg(dev, "\t.crop.line_top = %d\n",
|
||||
cfg->output_pins[i].crop.line_top);
|
||||
dev_dbg(dev, "\t.crop.line_bottom = %d\n",
|
||||
cfg->output_pins[i].crop.line_bottom);
|
||||
|
||||
dev_dbg(dev, "\t.dpcm_enable = %d\n",
|
||||
cfg->output_pins[i].dpcm.enable);
|
||||
dev_dbg(dev, "\t.dpcm.type = %d\n",
|
||||
cfg->output_pins[i].dpcm.type);
|
||||
dev_dbg(dev, "\t.dpcm.predictor = %d\n",
|
||||
cfg->output_pins[i].dpcm.predictor);
|
||||
}
|
||||
dev_dbg(dev, "---------------------------\n");
|
||||
}
|
||||
|
||||
void ipu7_fw_isys_dump_frame_buff_set(struct device *dev,
|
||||
struct ipu7_insys_buffset *buf,
|
||||
unsigned int outputs)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
dev_dbg(dev, "--------------------------\n");
|
||||
dev_dbg(dev, "IPU_ISYS_BUFF_SET\n");
|
||||
dev_dbg(dev, ".capture_msg_map = %d\n", buf->capture_msg_map);
|
||||
dev_dbg(dev, ".frame_id = %d\n", buf->frame_id);
|
||||
dev_dbg(dev, ".skip_frame = %d\n", buf->skip_frame);
|
||||
|
||||
for (i = 0; i < outputs; i++) {
|
||||
dev_dbg(dev, ".output_pin[%d]:\n", i);
|
||||
dev_dbg(dev, "\t.user_token = %llx\n",
|
||||
buf->output_pins[i].user_token);
|
||||
dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr);
|
||||
}
|
||||
dev_dbg(dev, "---------------------------\n");
|
||||
}
|
||||
39
drivers/staging/media/ipu7/ipu7-fw-isys.h
Normal file
39
drivers/staging/media/ipu7/ipu7-fw-isys.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_FW_ISYS_H
|
||||
#define IPU7_FW_ISYS_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "abi/ipu7_fw_isys_abi.h"
|
||||
|
||||
struct device;
|
||||
struct ipu7_insys_buffset;
|
||||
struct ipu7_insys_stream_cfg;
|
||||
struct ipu7_isys;
|
||||
|
||||
/* From here on type defines not coming from the ISYSAPI interface */
|
||||
|
||||
int ipu7_fw_isys_init(struct ipu7_isys *isys);
|
||||
void ipu7_fw_isys_release(struct ipu7_isys *isys);
|
||||
int ipu7_fw_isys_open(struct ipu7_isys *isys);
|
||||
int ipu7_fw_isys_close(struct ipu7_isys *isys);
|
||||
|
||||
void ipu7_fw_isys_dump_stream_cfg(struct device *dev,
|
||||
struct ipu7_insys_stream_cfg *cfg);
|
||||
void ipu7_fw_isys_dump_frame_buff_set(struct device *dev,
|
||||
struct ipu7_insys_buffset *buf,
|
||||
unsigned int outputs);
|
||||
int ipu7_fw_isys_simple_cmd(struct ipu7_isys *isys,
|
||||
const unsigned int stream_handle, u16 send_type);
|
||||
int ipu7_fw_isys_complex_cmd(struct ipu7_isys *isys,
|
||||
const unsigned int stream_handle,
|
||||
void *cpu_mapped_buf,
|
||||
dma_addr_t dma_mapped_buf,
|
||||
size_t size, u16 send_type);
|
||||
struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu7_isys *isys);
|
||||
void ipu7_fw_isys_put_resp(struct ipu7_isys *isys);
|
||||
#endif
|
||||
1034
drivers/staging/media/ipu7/ipu7-isys-csi-phy.c
Normal file
1034
drivers/staging/media/ipu7/ipu7-isys-csi-phy.c
Normal file
File diff suppressed because it is too large
Load Diff
16
drivers/staging/media/ipu7/ipu7-isys-csi-phy.h
Normal file
16
drivers/staging/media/ipu7/ipu7-isys-csi-phy.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_CSI_PHY_H
|
||||
#define IPU7_ISYS_CSI_PHY_H
|
||||
|
||||
struct ipu7_isys;
|
||||
|
||||
#define PHY_MODE_DPHY 0U
|
||||
#define PHY_MODE_CPHY 1U
|
||||
|
||||
int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2);
|
||||
void ipu7_isys_csi_phy_powerdown(struct ipu7_isys_csi2 *csi2);
|
||||
#endif
|
||||
1197
drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h
Normal file
1197
drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h
Normal file
File diff suppressed because it is too large
Load Diff
543
drivers/staging/media/ipu7/ipu7-isys-csi2.c
Normal file
543
drivers/staging/media/ipu7/ipu7-isys-csi2.c
Normal file
@@ -0,0 +1,543 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.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 "ipu7.h"
|
||||
#include "ipu7-bus.h"
|
||||
#include "ipu7-isys.h"
|
||||
#include "ipu7-isys-csi2.h"
|
||||
#include "ipu7-isys-csi2-regs.h"
|
||||
#include "ipu7-isys-csi-phy.h"
|
||||
|
||||
static const u32 csi2_supported_codes[] = {
|
||||
MEDIA_BUS_FMT_Y10_1X10,
|
||||
MEDIA_BUS_FMT_RGB565_1X16,
|
||||
MEDIA_BUS_FMT_RGB888_1X24,
|
||||
MEDIA_BUS_FMT_UYVY8_1X16,
|
||||
MEDIA_BUS_FMT_YUYV8_1X16,
|
||||
MEDIA_BUS_FMT_YUYV10_1X20,
|
||||
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,
|
||||
0,
|
||||
};
|
||||
|
||||
s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
struct media_pad *src_pad;
|
||||
|
||||
src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity);
|
||||
if (IS_ERR(src_pad)) {
|
||||
dev_err(&csi2->isys->adev->auxdev.dev,
|
||||
"can't get source pad of %s (%ld)\n",
|
||||
csi2->asd.sd.name, PTR_ERR(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 ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
|
||||
struct ipu7_isys_csi2 *csi2 = to_ipu7_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_subscribe_event(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,
|
||||
};
|
||||
|
||||
static void csi2_irq_enable(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
struct ipu7_device *isp = csi2->isys->adev->isp;
|
||||
unsigned int offset, mask;
|
||||
|
||||
/* enable CSI2 legacy error irq */
|
||||
offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port);
|
||||
mask = IPU7_CSI_RX_ERROR_IRQ_MASK;
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_MASK);
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_ENABLE);
|
||||
|
||||
/* enable CSI2 legacy sync irq */
|
||||
offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port);
|
||||
mask = IPU7_CSI_RX_SYNC_IRQ_MASK;
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_MASK);
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_ENABLE);
|
||||
|
||||
mask = IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK;
|
||||
if (!is_ipu7(isp->hw_ver)) {
|
||||
writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR);
|
||||
writel(mask, csi2->base + offset + IRQ1_CTL_MASK);
|
||||
writel(mask, csi2->base + offset + IRQ1_CTL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static void csi2_irq_disable(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
struct ipu7_device *isp = csi2->isys->adev->isp;
|
||||
unsigned int offset, mask;
|
||||
|
||||
/* disable CSI2 legacy error irq */
|
||||
offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port);
|
||||
mask = IPU7_CSI_RX_ERROR_IRQ_MASK;
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
|
||||
writel(0, csi2->base + offset + IRQ_CTL_MASK);
|
||||
writel(0, csi2->base + offset + IRQ_CTL_ENABLE);
|
||||
|
||||
/* disable CSI2 legacy sync irq */
|
||||
offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port);
|
||||
mask = IPU7_CSI_RX_SYNC_IRQ_MASK;
|
||||
writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
|
||||
writel(0, csi2->base + offset + IRQ_CTL_MASK);
|
||||
writel(0, csi2->base + offset + IRQ_CTL_ENABLE);
|
||||
|
||||
if (!is_ipu7(isp->hw_ver)) {
|
||||
writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR);
|
||||
writel(0, csi2->base + offset + IRQ1_CTL_MASK);
|
||||
writel(0, csi2->base + offset + IRQ1_CTL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipu7_isys_csi2_disable_stream(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
struct ipu7_isys *isys = csi2->isys;
|
||||
void __iomem *isys_base = isys->pdata->base;
|
||||
|
||||
ipu7_isys_csi_phy_powerdown(csi2);
|
||||
|
||||
writel(0x4, isys_base + IS_IO_GPREGS_BASE + CLK_DIV_FACTOR_APB_CLK);
|
||||
csi2_irq_disable(csi2);
|
||||
}
|
||||
|
||||
static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
struct ipu7_isys *isys = csi2->isys;
|
||||
struct device *dev = &isys->adev->auxdev.dev;
|
||||
void __iomem *isys_base = isys->pdata->base;
|
||||
unsigned int port, nlanes, offset;
|
||||
int ret;
|
||||
|
||||
port = csi2->port;
|
||||
nlanes = csi2->nlanes;
|
||||
|
||||
offset = IS_IO_GPREGS_BASE;
|
||||
writel(0x2, isys_base + offset + CLK_DIV_FACTOR_APB_CLK);
|
||||
dev_dbg(dev, "port %u CLK_GATE = 0x%04x DIV_FACTOR_APB_CLK=0x%04x\n",
|
||||
port, readl(isys_base + offset + CSI_PORT_CLK_GATE),
|
||||
readl(isys_base + offset + CLK_DIV_FACTOR_APB_CLK));
|
||||
if (port == 0U && nlanes == 4U && !is_ipu7(isys->adev->isp->hw_ver)) {
|
||||
dev_dbg(dev, "CSI port %u in aggregation mode\n", port);
|
||||
writel(0x1, isys_base + offset + CSI_PORTAB_AGGREGATION);
|
||||
}
|
||||
|
||||
/* input is coming from CSI receiver (sensor) */
|
||||
offset = IS_IO_CSI2_ADPL_PORT_BASE(port);
|
||||
writel(CSI_SENSOR_INPUT, isys_base + offset + CSI2_ADPL_INPUT_MODE);
|
||||
writel(1, isys_base + offset + CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN);
|
||||
|
||||
ret = ipu7_isys_csi_phy_powerup(csi2);
|
||||
if (ret) {
|
||||
dev_err(dev, "CSI-%d PHY power up failed %d\n", port, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
csi2_irq_enable(csi2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_selection *sel)
|
||||
{
|
||||
struct ipu7_isys_subdev *asd = to_ipu7_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 == IPU7_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 (!ipu7_isys_is_bayer_format(sink_ffmt->code))
|
||||
sel->r.top &= ~1U;
|
||||
sel->r.height = clamp(sel->r.height & ~1U, IPU_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 (ipu7_isys_is_bayer_format(sink_ffmt->code))
|
||||
src_ffmt->code = ipu7_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 ipu7_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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set
|
||||
* of streams.
|
||||
*/
|
||||
#define CSI2_SUBDEV_MAX_STREAM_ID 63
|
||||
|
||||
static int ipu7_isys_csi2_enable_streams(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
u32 pad, u64 streams_mask)
|
||||
{
|
||||
struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
|
||||
struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd);
|
||||
struct v4l2_subdev *r_sd;
|
||||
struct media_pad *rp;
|
||||
u32 sink_pad, sink_stream;
|
||||
int ret, i;
|
||||
|
||||
if (!csi2->stream_count) {
|
||||
dev_dbg(&csi2->isys->adev->auxdev.dev,
|
||||
"stream on CSI2-%u with %u lanes\n", csi2->port,
|
||||
csi2->nlanes);
|
||||
ret = ipu7_isys_csi2_enable_stream(csi2);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) {
|
||||
if (streams_mask & BIT_ULL(i))
|
||||
break;
|
||||
}
|
||||
|
||||
ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i,
|
||||
&sink_pad, &sink_stream);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]);
|
||||
r_sd = media_entity_to_v4l2_subdev(rp->entity);
|
||||
|
||||
ret = v4l2_subdev_enable_streams(r_sd, rp->index,
|
||||
BIT_ULL(sink_stream));
|
||||
if (!ret) {
|
||||
csi2->stream_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!csi2->stream_count)
|
||||
ipu7_isys_csi2_disable_stream(csi2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ipu7_isys_csi2_disable_streams(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
u32 pad, u64 streams_mask)
|
||||
{
|
||||
struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
|
||||
struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd);
|
||||
struct v4l2_subdev *r_sd;
|
||||
struct media_pad *rp;
|
||||
u32 sink_pad, sink_stream;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) {
|
||||
if (streams_mask & BIT_ULL(i))
|
||||
break;
|
||||
}
|
||||
|
||||
ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i,
|
||||
&sink_pad, &sink_stream);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]);
|
||||
r_sd = media_entity_to_v4l2_subdev(rp->entity);
|
||||
|
||||
v4l2_subdev_disable_streams(r_sd, rp->index, BIT_ULL(sink_stream));
|
||||
|
||||
if (--csi2->stream_count)
|
||||
return 0;
|
||||
|
||||
dev_dbg(&csi2->isys->adev->auxdev.dev,
|
||||
"stream off CSI2-%u with %u lanes\n", csi2->port, csi2->nlanes);
|
||||
|
||||
ipu7_isys_csi2_disable_stream(csi2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
|
||||
.get_fmt = v4l2_subdev_get_fmt,
|
||||
.set_fmt = ipu7_isys_subdev_set_fmt,
|
||||
.get_selection = ipu7_isys_csi2_get_sel,
|
||||
.set_selection = ipu7_isys_csi2_set_sel,
|
||||
.enum_mbus_code = ipu7_isys_subdev_enum_mbus_code,
|
||||
.enable_streams = ipu7_isys_csi2_enable_streams,
|
||||
.disable_streams = ipu7_isys_csi2_disable_streams,
|
||||
.set_routing = ipu7_isys_subdev_set_routing,
|
||||
};
|
||||
|
||||
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 ipu7_isys_csi2_cleanup(struct ipu7_isys_csi2 *csi2)
|
||||
{
|
||||
if (!csi2->isys)
|
||||
return;
|
||||
|
||||
v4l2_device_unregister_subdev(&csi2->asd.sd);
|
||||
v4l2_subdev_cleanup(&csi2->asd.sd);
|
||||
ipu7_isys_subdev_cleanup(&csi2->asd);
|
||||
csi2->isys = NULL;
|
||||
}
|
||||
|
||||
int ipu7_isys_csi2_init(struct ipu7_isys_csi2 *csi2,
|
||||
struct ipu7_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;
|
||||
|
||||
if (!is_ipu7(isys->adev->isp->hw_ver))
|
||||
csi2->legacy_irq_mask = 0x7U << (index * 3U);
|
||||
else
|
||||
csi2->legacy_irq_mask = 0x3U << (index * 2U);
|
||||
|
||||
dev_dbg(dev, "csi-%d legacy irq mask = 0x%x\n", index,
|
||||
csi2->legacy_irq_mask);
|
||||
|
||||
csi2->asd.sd.entity.ops = &csi2_entity_ops;
|
||||
csi2->asd.isys = isys;
|
||||
|
||||
ret = ipu7_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
|
||||
IPU7_NR_OF_CSI2_SINK_PADS,
|
||||
IPU7_NR_OF_CSI2_SRC_PADS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
csi2->asd.source = (int)index;
|
||||
csi2->asd.supported_codes = csi2_supported_codes;
|
||||
snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
|
||||
IPU_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 (%d)\n", ret);
|
||||
goto isys_subdev_cleanup;
|
||||
}
|
||||
|
||||
ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register v4l2 subdev (%d)\n", ret);
|
||||
goto v4l2_subdev_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
v4l2_subdev_cleanup:
|
||||
v4l2_subdev_cleanup(&csi2->asd.sd);
|
||||
isys_subdev_cleanup:
|
||||
ipu7_isys_subdev_cleanup(&csi2->asd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ipu7_isys_csi2_sof_event_by_stream(struct ipu7_isys_stream *stream)
|
||||
{
|
||||
struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd);
|
||||
struct device *dev = &stream->isys->adev->auxdev.dev;
|
||||
struct video_device *vdev = csi2->asd.sd.devnode;
|
||||
struct v4l2_event ev = {
|
||||
.type = V4L2_EVENT_FRAME_SYNC,
|
||||
};
|
||||
|
||||
ev.id = stream->vc;
|
||||
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 ipu7_isys_csi2_eof_event_by_stream(struct ipu7_isys_stream *stream)
|
||||
{
|
||||
struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd);
|
||||
struct device *dev = &stream->isys->adev->auxdev.dev;
|
||||
u32 frame_sequence = atomic_read(&stream->sequence);
|
||||
|
||||
dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n",
|
||||
csi2->port, frame_sequence);
|
||||
}
|
||||
|
||||
int ipu7_isys_csi2_get_remote_desc(u32 source_stream,
|
||||
struct ipu7_isys_csi2 *csi2,
|
||||
struct media_entity *source_entity,
|
||||
struct v4l2_mbus_frame_desc_entry *entry,
|
||||
int *nr_queues)
|
||||
{
|
||||
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[IPU7_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 >= IPU7_NR_OF_CSI2_VC) {
|
||||
dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*entry = *desc_entry;
|
||||
|
||||
for (i = 0; i < desc.num_entries; i++) {
|
||||
if (desc_entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc)
|
||||
(*nr_queues)++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
64
drivers/staging/media/ipu7/ipu7-isys-csi2.h
Normal file
64
drivers/staging/media/ipu7/ipu7-isys-csi2.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_CSI2_H
|
||||
#define IPU7_ISYS_CSI2_H
|
||||
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "ipu7-isys-subdev.h"
|
||||
#include "ipu7-isys-video.h"
|
||||
|
||||
struct ipu7_isys;
|
||||
struct ipu7_isys_csi2_pdata;
|
||||
struct ipu7_isys_stream;
|
||||
|
||||
#define IPU7_NR_OF_CSI2_VC 16U
|
||||
#define INVALID_VC_ID -1
|
||||
#define IPU7_NR_OF_CSI2_SINK_PADS 1U
|
||||
#define IPU7_CSI2_PAD_SINK 0U
|
||||
#define IPU7_NR_OF_CSI2_SRC_PADS 8U
|
||||
#define IPU7_CSI2_PAD_SRC 1U
|
||||
#define IPU7_NR_OF_CSI2_PADS (IPU7_NR_OF_CSI2_SINK_PADS + \
|
||||
IPU7_NR_OF_CSI2_SRC_PADS)
|
||||
|
||||
/*
|
||||
* struct ipu7_isys_csi2
|
||||
*
|
||||
* @nlanes: number of lanes in the receiver
|
||||
*/
|
||||
struct ipu7_isys_csi2 {
|
||||
struct ipu7_isys_subdev asd;
|
||||
struct ipu7_isys_csi2_pdata *pdata;
|
||||
struct ipu7_isys *isys;
|
||||
struct ipu7_isys_video av[IPU7_NR_OF_CSI2_SRC_PADS];
|
||||
|
||||
void __iomem *base;
|
||||
u32 receiver_errors;
|
||||
u32 legacy_irq_mask;
|
||||
unsigned int nlanes;
|
||||
unsigned int port;
|
||||
unsigned int phy_mode;
|
||||
unsigned int stream_count;
|
||||
};
|
||||
|
||||
#define ipu7_isys_subdev_to_csi2(__sd) \
|
||||
container_of(__sd, struct ipu7_isys_csi2, asd)
|
||||
|
||||
#define to_ipu7_isys_csi2(__asd) container_of(__asd, struct ipu7_isys_csi2, asd)
|
||||
|
||||
s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2);
|
||||
int ipu7_isys_csi2_init(struct ipu7_isys_csi2 *csi2, struct ipu7_isys *isys,
|
||||
void __iomem *base, unsigned int index);
|
||||
void ipu7_isys_csi2_cleanup(struct ipu7_isys_csi2 *csi2);
|
||||
void ipu7_isys_csi2_sof_event_by_stream(struct ipu7_isys_stream *stream);
|
||||
void ipu7_isys_csi2_eof_event_by_stream(struct ipu7_isys_stream *stream);
|
||||
int ipu7_isys_csi2_get_remote_desc(u32 source_stream,
|
||||
struct ipu7_isys_csi2 *csi2,
|
||||
struct media_entity *source_entity,
|
||||
struct v4l2_mbus_frame_desc_entry *entry,
|
||||
int *nr_queues);
|
||||
#endif /* IPU7_ISYS_CSI2_H */
|
||||
829
drivers/staging/media/ipu7/ipu7-isys-queue.c
Normal file
829
drivers/staging/media/ipu7/ipu7-isys-queue.c
Normal file
@@ -0,0 +1,829 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/atomic.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 "abi/ipu7_fw_isys_abi.h"
|
||||
|
||||
#include "ipu7-bus.h"
|
||||
#include "ipu7-dma.h"
|
||||
#include "ipu7-fw-isys.h"
|
||||
#include "ipu7-isys.h"
|
||||
#include "ipu7-isys-csi2-regs.h"
|
||||
#include "ipu7-isys-video.h"
|
||||
#include "ipu7-platform-regs.h"
|
||||
|
||||
#define IPU_MAX_FRAME_COUNTER (U8_MAX + 1)
|
||||
|
||||
static int ipu7_isys_buf_init(struct vb2_buffer *vb)
|
||||
{
|
||||
struct ipu7_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 ipu7_isys_video_buffer *ivb =
|
||||
vb2_buffer_to_ipu7_isys_video_buffer(vvb);
|
||||
int ret;
|
||||
|
||||
ret = ipu7_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 ipu7_isys_buf_cleanup(struct vb2_buffer *vb)
|
||||
{
|
||||
struct ipu7_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 ipu7_isys_video_buffer *ivb =
|
||||
vb2_buffer_to_ipu7_isys_video_buffer(vvb);
|
||||
|
||||
ivb->dma_addr = 0;
|
||||
ipu7_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0);
|
||||
}
|
||||
|
||||
static int ipu7_isys_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
|
||||
unsigned int *num_planes, unsigned int sizes[],
|
||||
struct device *alloc_devs[])
|
||||
{
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
u32 size = av->pix_fmt.sizeimage;
|
||||
|
||||
/* 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 ipu7_isys_buf_prepare(struct vb2_buffer *vb)
|
||||
{
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
u32 bytesperline = av->pix_fmt.bytesperline;
|
||||
u32 height = av->pix_fmt.height;
|
||||
|
||||
dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
|
||||
av->vdev.name, av->pix_fmt.sizeimage, vb2_plane_size(vb, 0));
|
||||
|
||||
if (av->pix_fmt.sizeimage > vb2_plane_size(vb, 0))
|
||||
return -EINVAL;
|
||||
|
||||
dev_dbg(dev, "buffer: %s: bytesperline %u, height %u\n",
|
||||
av->vdev.name, bytesperline, height);
|
||||
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 ipu7_isys_buffer_list_queue(struct ipu7_isys_buffer_list *bl,
|
||||
unsigned long op_flags,
|
||||
enum vb2_buffer_state state)
|
||||
{
|
||||
struct ipu7_isys_buffer *ib, *ib_safe;
|
||||
unsigned long flags;
|
||||
bool first = true;
|
||||
|
||||
if (!bl)
|
||||
return;
|
||||
|
||||
WARN_ON_ONCE(!bl->nbufs);
|
||||
WARN_ON_ONCE(op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE &&
|
||||
op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING);
|
||||
|
||||
list_for_each_entry_safe(ib, ib_safe, &bl->head, head) {
|
||||
struct ipu7_isys_video *av;
|
||||
|
||||
struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib);
|
||||
struct ipu7_isys_queue *aq =
|
||||
vb2_queue_to_isys_queue(vb->vb2_queue);
|
||||
|
||||
av = ipu7_isys_queue_to_video(aq);
|
||||
spin_lock_irqsave(&aq->lock, flags);
|
||||
list_del(&ib->head);
|
||||
if (op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE)
|
||||
list_add(&ib->head, &aq->active);
|
||||
else if (op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING)
|
||||
list_add_tail(&ib->head, &aq->incoming);
|
||||
spin_unlock_irqrestore(&aq->lock, flags);
|
||||
|
||||
if (op_flags & IPU_ISYS_BUFFER_LIST_FL_SET_STATE)
|
||||
vb2_buffer_done(vb, state);
|
||||
|
||||
if (first) {
|
||||
dev_dbg(&av->isys->adev->auxdev.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 ipu7_isys_stream *stream)
|
||||
{
|
||||
struct ipu7_isys_queue *aq;
|
||||
unsigned long flags;
|
||||
|
||||
lockdep_assert_held(&stream->mutex);
|
||||
|
||||
list_for_each_entry(aq, &stream->queues, node) {
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
struct ipu7_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 =
|
||||
ipu7_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(ipu7_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 ipu7_isys_stream *stream,
|
||||
struct ipu7_isys_buffer_list *bl)
|
||||
{
|
||||
unsigned long buf_flag = IPU_ISYS_BUFFER_LIST_FL_INCOMING;
|
||||
struct device *dev = &stream->isys->adev->auxdev.dev;
|
||||
struct ipu7_isys_queue *aq;
|
||||
unsigned long flags;
|
||||
|
||||
bl->nbufs = 0;
|
||||
INIT_LIST_HEAD(&bl->head);
|
||||
|
||||
list_for_each_entry(aq, &stream->queues, node) {
|
||||
struct ipu7_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))
|
||||
ipu7_isys_buffer_list_queue(bl, buf_flag, 0);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
ib = list_last_entry(&aq->incoming,
|
||||
struct ipu7_isys_buffer, head);
|
||||
|
||||
dev_dbg(dev, "buffer: %s: buffer %u\n",
|
||||
ipu7_isys_queue_to_video(aq)->vdev.name,
|
||||
ipu7_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 ipu7_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb,
|
||||
struct ipu7_insys_buffset *set)
|
||||
{
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
|
||||
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
|
||||
struct ipu7_isys_video_buffer *ivb =
|
||||
vb2_buffer_to_ipu7_isys_video_buffer(vvb);
|
||||
|
||||
set->output_pins[aq->fw_output].addr = ivb->dma_addr;
|
||||
set->output_pins[aq->fw_output].user_token = (uintptr_t)set;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a buffer list to a isys fw ABI framebuffer set. The
|
||||
* buffer list is not modified.
|
||||
*/
|
||||
#define IPU_ISYS_FRAME_NUM_THRESHOLD (30)
|
||||
void ipu7_isys_buffer_to_fw_frame_buff(struct ipu7_insys_buffset *set,
|
||||
struct ipu7_isys_stream *stream,
|
||||
struct ipu7_isys_buffer_list *bl)
|
||||
{
|
||||
struct ipu7_isys_buffer *ib;
|
||||
u32 buf_id;
|
||||
|
||||
WARN_ON(!bl->nbufs);
|
||||
|
||||
set->skip_frame = 0;
|
||||
set->capture_msg_map = IPU_INSYS_FRAME_ENABLE_MSG_SEND_RESP |
|
||||
IPU_INSYS_FRAME_ENABLE_MSG_SEND_IRQ;
|
||||
|
||||
buf_id = atomic_fetch_inc(&stream->buf_id);
|
||||
set->frame_id = buf_id % IPU_MAX_FRAME_COUNTER;
|
||||
|
||||
list_for_each_entry(ib, &bl->head, head) {
|
||||
struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib);
|
||||
|
||||
ipu7_isys_buf_to_fw_frame_buf_pin(vb, set);
|
||||
}
|
||||
}
|
||||
|
||||
/* Start streaming for real. The buffer list must be available. */
|
||||
static int ipu7_isys_stream_start(struct ipu7_isys_video *av,
|
||||
struct ipu7_isys_buffer_list *bl, bool error)
|
||||
{
|
||||
struct ipu7_isys_stream *stream = av->stream;
|
||||
struct device *dev = &stream->isys->adev->auxdev.dev;
|
||||
struct ipu7_isys_buffer_list __bl;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&stream->isys->stream_mutex);
|
||||
|
||||
ret = ipu7_isys_video_set_streaming(av, 1, bl);
|
||||
mutex_unlock(&stream->isys->stream_mutex);
|
||||
if (ret)
|
||||
goto out_requeue;
|
||||
|
||||
stream->streaming = 1;
|
||||
|
||||
bl = &__bl;
|
||||
|
||||
do {
|
||||
struct ipu7_insys_buffset *buf = NULL;
|
||||
struct isys_fw_msgs *msg;
|
||||
enum ipu7_insys_send_type send_type =
|
||||
IPU_INSYS_SEND_TYPE_STREAM_CAPTURE;
|
||||
|
||||
ret = buffer_list_get(stream, bl);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
msg = ipu7_get_fw_msg_buf(stream);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
buf = &msg->fw_msg.frame;
|
||||
|
||||
ipu7_isys_buffer_to_fw_frame_buff(buf, stream, bl);
|
||||
|
||||
ipu7_fw_isys_dump_frame_buff_set(dev, buf,
|
||||
stream->nr_output_pins);
|
||||
|
||||
ipu7_isys_buffer_list_queue(bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE,
|
||||
0);
|
||||
|
||||
ret = ipu7_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)
|
||||
ipu7_isys_buffer_list_queue(bl,
|
||||
IPU_ISYS_BUFFER_LIST_FL_INCOMING |
|
||||
(error ?
|
||||
IPU_ISYS_BUFFER_LIST_FL_SET_STATE :
|
||||
0), error ? VB2_BUF_STATE_ERROR :
|
||||
VB2_BUF_STATE_QUEUED);
|
||||
flush_firmware_streamon_fail(stream);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void buf_queue(struct vb2_buffer *vb)
|
||||
{
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
|
||||
struct ipu7_isys_video_buffer *ivb =
|
||||
vb2_buffer_to_ipu7_isys_video_buffer(vvb);
|
||||
struct media_pipeline *media_pipe =
|
||||
media_entity_pipeline(&av->vdev.entity);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
struct ipu7_isys_stream *stream = av->stream;
|
||||
struct ipu7_isys_buffer *ib = &ivb->ib;
|
||||
struct ipu7_insys_buffset *buf = NULL;
|
||||
struct ipu7_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 (!media_pipe || !vb->vb2_queue->start_streaming_called) {
|
||||
dev_dbg(dev, "media pipeline is not ready for %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 = ipu7_get_fw_msg_buf(stream);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
buf = &msg->fw_msg.frame;
|
||||
|
||||
ipu7_isys_buffer_to_fw_frame_buff(buf, stream, &bl);
|
||||
|
||||
ipu7_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins);
|
||||
|
||||
if (!stream->streaming) {
|
||||
ret = ipu7_isys_stream_start(av, &bl, true);
|
||||
if (ret)
|
||||
dev_err(dev, "stream start failed.\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
ipu7_isys_buffer_list_queue(&bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
|
||||
|
||||
ret = ipu7_fw_isys_complex_cmd(stream->isys, stream->stream_handle,
|
||||
buf, msg->dma_addr, sizeof(*buf),
|
||||
IPU_INSYS_SEND_TYPE_STREAM_CAPTURE);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "send stream capture failed\n");
|
||||
|
||||
out:
|
||||
mutex_unlock(&stream->mutex);
|
||||
}
|
||||
|
||||
static int ipu7_isys_link_fmt_validate(struct ipu7_isys_queue *aq)
|
||||
{
|
||||
struct ipu7_isys_video *av = ipu7_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_mbus_framefmt format;
|
||||
struct v4l2_subdev *sd;
|
||||
u32 r_stream, code;
|
||||
int ret;
|
||||
|
||||
if (!remote_pad)
|
||||
return -ENOTCONN;
|
||||
|
||||
sd = media_entity_to_v4l2_subdev(remote_pad->entity);
|
||||
r_stream = ipu7_isys_get_src_stream_by_src_pad(sd, remote_pad->index);
|
||||
|
||||
ret = ipu7_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream,
|
||||
&format);
|
||||
if (ret) {
|
||||
dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n",
|
||||
sd->entity.name, remote_pad->index, r_stream);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (format.width != av->pix_fmt.width ||
|
||||
format.height != av->pix_fmt.height) {
|
||||
dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
|
||||
av->pix_fmt.width, av->pix_fmt.height, format.width,
|
||||
format.height);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
code = ipu7_isys_get_isys_format(av->pix_fmt.pixelformat)->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 ipu7_isys_queue *aq,
|
||||
enum vb2_buffer_state state)
|
||||
{
|
||||
struct ipu7_isys_buffer *ib;
|
||||
struct vb2_buffer *vb;
|
||||
unsigned long flags;
|
||||
|
||||
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)) {
|
||||
ib = list_last_entry(&aq->active, struct ipu7_isys_buffer,
|
||||
head);
|
||||
vb = ipu7_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);
|
||||
}
|
||||
|
||||
while (!list_empty(&aq->incoming)) {
|
||||
ib = list_last_entry(&aq->incoming, struct ipu7_isys_buffer,
|
||||
head);
|
||||
vb = ipu7_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);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&aq->lock, flags);
|
||||
}
|
||||
|
||||
static void ipu7_isys_stream_cleanup(struct ipu7_isys_video *av)
|
||||
{
|
||||
video_device_pipeline_stop(&av->vdev);
|
||||
ipu7_isys_put_stream(av->stream);
|
||||
av->stream = NULL;
|
||||
}
|
||||
|
||||
static int start_streaming(struct vb2_queue *q, unsigned int count)
|
||||
{
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
const struct ipu7_isys_pixelformat *pfmt =
|
||||
ipu7_isys_get_isys_format(av->pix_fmt.pixelformat);
|
||||
struct ipu7_isys_buffer_list __bl, *bl = NULL;
|
||||
struct ipu7_isys_stream *stream;
|
||||
struct media_entity *source_entity = NULL;
|
||||
int nr_queues, ret;
|
||||
|
||||
dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n",
|
||||
av->vdev.name, av->pix_fmt.width, av->pix_fmt.height,
|
||||
pfmt->css_pixelformat);
|
||||
|
||||
ret = ipu7_isys_setup_video(av, &source_entity, &nr_queues);
|
||||
if (ret < 0) {
|
||||
dev_dbg(dev, "failed to setup video\n");
|
||||
goto out_return_buffers;
|
||||
}
|
||||
|
||||
ret = ipu7_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;
|
||||
}
|
||||
|
||||
stream = av->stream;
|
||||
mutex_lock(&stream->mutex);
|
||||
if (!stream->nr_streaming) {
|
||||
ret = ipu7_isys_video_prepare_stream(av, source_entity,
|
||||
nr_queues);
|
||||
if (ret) {
|
||||
mutex_unlock(&stream->mutex);
|
||||
goto out_pipeline_stop;
|
||||
}
|
||||
}
|
||||
|
||||
stream->nr_streaming++;
|
||||
dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming,
|
||||
stream->nr_queues);
|
||||
|
||||
list_add(&aq->node, &stream->queues);
|
||||
|
||||
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 = ipu7_isys_fw_open(av->isys);
|
||||
if (ret)
|
||||
goto out_stream_start;
|
||||
|
||||
ipu7_isys_setup_hw(av->isys);
|
||||
|
||||
ret = ipu7_isys_stream_start(av, bl, false);
|
||||
if (ret)
|
||||
goto out_isys_fw_close;
|
||||
|
||||
out:
|
||||
mutex_unlock(&stream->mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
out_isys_fw_close:
|
||||
ipu7_isys_fw_close(av->isys);
|
||||
|
||||
out_stream_start:
|
||||
list_del(&aq->node);
|
||||
stream->nr_streaming--;
|
||||
mutex_unlock(&stream->mutex);
|
||||
|
||||
out_pipeline_stop:
|
||||
ipu7_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 ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct ipu7_isys_stream *stream = av->stream;
|
||||
|
||||
mutex_lock(&stream->mutex);
|
||||
mutex_lock(&av->isys->stream_mutex);
|
||||
if (stream->nr_streaming == stream->nr_queues && stream->streaming)
|
||||
ipu7_isys_video_set_streaming(av, 0, NULL);
|
||||
mutex_unlock(&av->isys->stream_mutex);
|
||||
|
||||
stream->nr_streaming--;
|
||||
list_del(&aq->node);
|
||||
stream->streaming = 0;
|
||||
|
||||
mutex_unlock(&stream->mutex);
|
||||
|
||||
ipu7_isys_stream_cleanup(av);
|
||||
|
||||
return_buffers(aq, VB2_BUF_STATE_ERROR);
|
||||
|
||||
ipu7_isys_fw_close(av->isys);
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
get_sof_sequence_by_timestamp(struct ipu7_isys_stream *stream, u64 time)
|
||||
{
|
||||
struct ipu7_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 < IPU_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;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "SOF: looking for %llu\n", time);
|
||||
for (i = 0; i < IPU_ISYS_MAX_PARALLEL_SOF; i++)
|
||||
dev_dbg(dev, "SOF: sequence %u, timestamp value %llu\n",
|
||||
stream->seq[i].sequence, stream->seq[i].timestamp);
|
||||
dev_dbg(dev, "SOF sequence number not found\n");
|
||||
|
||||
return atomic_read(&stream->sequence) - 1;
|
||||
}
|
||||
|
||||
static u64 get_sof_ns_delta(struct ipu7_isys_video *av, u64 time)
|
||||
{
|
||||
struct ipu7_bus_device *adev = av->isys->adev;
|
||||
struct ipu7_device *isp = adev->isp;
|
||||
u64 delta, tsc_now;
|
||||
|
||||
ipu_buttress_tsc_read(isp, &tsc_now);
|
||||
if (!tsc_now)
|
||||
return 0;
|
||||
|
||||
delta = tsc_now - time;
|
||||
|
||||
return ipu_buttress_tsc_ticks_to_ns(delta, isp);
|
||||
}
|
||||
|
||||
static void ipu7_isys_buf_calc_sequence_time(struct ipu7_isys_buffer *ib,
|
||||
u64 time)
|
||||
{
|
||||
struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib);
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||
struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct device *dev = &av->isys->adev->auxdev.dev;
|
||||
struct ipu7_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 ipu7_isys_queue_buf_done(struct ipu7_isys_buffer *ib)
|
||||
{
|
||||
struct vb2_buffer *vb = ipu7_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);
|
||||
}
|
||||
}
|
||||
|
||||
void ipu7_isys_queue_buf_ready(struct ipu7_isys_stream *stream,
|
||||
struct ipu7_insys_resp *info)
|
||||
{
|
||||
struct ipu7_isys_queue *aq = stream->output_pins[info->pin_id].aq;
|
||||
u64 time = ((u64)info->timestamp[1] << 32 | info->timestamp[0]);
|
||||
struct ipu7_isys *isys = stream->isys;
|
||||
struct device *dev = &isys->adev->auxdev.dev;
|
||||
struct ipu7_isys_buffer *ib;
|
||||
struct vb2_buffer *vb;
|
||||
unsigned long flags;
|
||||
bool first = true;
|
||||
struct vb2_v4l2_buffer *buf;
|
||||
|
||||
dev_dbg(dev, "buffer: %s: received buffer %8.8x %d\n",
|
||||
ipu7_isys_queue_to_video(aq)->vdev.name, info->pin.addr,
|
||||
info->frame_id);
|
||||
|
||||
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 ipu7_isys_video_buffer *ivb;
|
||||
struct vb2_v4l2_buffer *vvb;
|
||||
dma_addr_t addr;
|
||||
|
||||
vb = ipu7_isys_buffer_to_vb2_buffer(ib);
|
||||
vvb = to_vb2_v4l2_buffer(vb);
|
||||
ivb = vb2_buffer_to_ipu7_isys_video_buffer(vvb);
|
||||
addr = ivb->dma_addr;
|
||||
|
||||
if (info->pin.addr != addr) {
|
||||
if (first)
|
||||
dev_err(dev, "Unexpected buffer address %pad\n",
|
||||
&addr);
|
||||
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ipu7_isys_buf_calc_sequence_time(ib, time);
|
||||
|
||||
ipu7_isys_queue_buf_done(ib);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dev_err(dev, "Failed to find a matching video buffer\n");
|
||||
|
||||
spin_unlock_irqrestore(&aq->lock, flags);
|
||||
}
|
||||
|
||||
static const struct vb2_ops ipu7_isys_queue_ops = {
|
||||
.queue_setup = ipu7_isys_queue_setup,
|
||||
.buf_init = ipu7_isys_buf_init,
|
||||
.buf_prepare = ipu7_isys_buf_prepare,
|
||||
.buf_cleanup = ipu7_isys_buf_cleanup,
|
||||
.start_streaming = start_streaming,
|
||||
.stop_streaming = stop_streaming,
|
||||
.buf_queue = buf_queue,
|
||||
};
|
||||
|
||||
int ipu7_isys_queue_init(struct ipu7_isys_queue *aq)
|
||||
{
|
||||
struct ipu7_isys *isys = ipu7_isys_queue_to_video(aq)->isys;
|
||||
struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq);
|
||||
struct ipu7_bus_device *adev = isys->adev;
|
||||
int ret;
|
||||
|
||||
if (!aq->vbq.io_modes)
|
||||
aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF;
|
||||
|
||||
aq->vbq.drv_priv = isys;
|
||||
aq->vbq.ops = &ipu7_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->dev = &adev->auxdev.dev;
|
||||
aq->vbq.dev = &adev->isp->pdev->dev;
|
||||
spin_lock_init(&aq->lock);
|
||||
INIT_LIST_HEAD(&aq->active);
|
||||
INIT_LIST_HEAD(&aq->incoming);
|
||||
|
||||
return 0;
|
||||
}
|
||||
72
drivers/staging/media/ipu7/ipu7-isys-queue.h
Normal file
72
drivers/staging/media/ipu7/ipu7-isys-queue.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_QUEUE_H
|
||||
#define IPU7_ISYS_QUEUE_H
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock_types.h>
|
||||
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
|
||||
struct device;
|
||||
struct ipu7_isys_stream;
|
||||
struct ipu7_insys_resp;
|
||||
struct ipu7_insys_buffset;
|
||||
|
||||
struct ipu7_isys_queue {
|
||||
struct vb2_queue vbq;
|
||||
struct list_head node;
|
||||
struct device *dev;
|
||||
spinlock_t lock;
|
||||
struct list_head active;
|
||||
struct list_head incoming;
|
||||
unsigned int fw_output;
|
||||
};
|
||||
|
||||
struct ipu7_isys_buffer {
|
||||
struct list_head head;
|
||||
atomic_t str2mmio_flag;
|
||||
};
|
||||
|
||||
struct ipu7_isys_video_buffer {
|
||||
struct vb2_v4l2_buffer vb_v4l2;
|
||||
struct ipu7_isys_buffer ib;
|
||||
dma_addr_t dma_addr;
|
||||
};
|
||||
|
||||
#define IPU_ISYS_BUFFER_LIST_FL_INCOMING BIT(0)
|
||||
#define IPU_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1)
|
||||
#define IPU_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2)
|
||||
|
||||
struct ipu7_isys_buffer_list {
|
||||
struct list_head head;
|
||||
unsigned int nbufs;
|
||||
};
|
||||
|
||||
#define vb2_queue_to_isys_queue(__vb2) \
|
||||
container_of(__vb2, struct ipu7_isys_queue, vbq)
|
||||
|
||||
#define ipu7_isys_to_isys_video_buffer(__ib) \
|
||||
container_of(__ib, struct ipu7_isys_video_buffer, ib)
|
||||
|
||||
#define vb2_buffer_to_ipu7_isys_video_buffer(__vvb) \
|
||||
container_of(__vvb, struct ipu7_isys_video_buffer, vb_v4l2)
|
||||
|
||||
#define ipu7_isys_buffer_to_vb2_buffer(__ib) \
|
||||
(&ipu7_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf)
|
||||
|
||||
void ipu7_isys_buffer_list_queue(struct ipu7_isys_buffer_list *bl,
|
||||
unsigned long op_flags,
|
||||
enum vb2_buffer_state state);
|
||||
void ipu7_isys_buffer_to_fw_frame_buff(struct ipu7_insys_buffset *set,
|
||||
struct ipu7_isys_stream *stream,
|
||||
struct ipu7_isys_buffer_list *bl);
|
||||
void ipu7_isys_queue_buf_ready(struct ipu7_isys_stream *stream,
|
||||
struct ipu7_insys_resp *info);
|
||||
int ipu7_isys_queue_init(struct ipu7_isys_queue *aq);
|
||||
#endif /* IPU7_ISYS_QUEUE_H */
|
||||
348
drivers/staging/media/ipu7/ipu7-isys-subdev.c
Normal file
348
drivers/staging/media/ipu7/ipu7-isys-subdev.c
Normal file
@@ -0,0 +1,348 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/mipi-csi2.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
|
||||
#include <uapi/linux/media-bus-format.h>
|
||||
|
||||
#include "ipu7-bus.h"
|
||||
#include "ipu7-isys.h"
|
||||
#include "ipu7-isys-subdev.h"
|
||||
|
||||
unsigned int ipu7_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_YUYV10_1X20:
|
||||
return MIPI_CSI2_DT_YUV422_10B;
|
||||
case MEDIA_BUS_FMT_UYVY8_1X16:
|
||||
case MEDIA_BUS_FMT_YUYV8_1X16:
|
||||
return MIPI_CSI2_DT_YUV422_8B;
|
||||
case MEDIA_BUS_FMT_SBGGR12_1X12:
|
||||
case MEDIA_BUS_FMT_SGBRG12_1X12:
|
||||
case MEDIA_BUS_FMT_SGRBG12_1X12:
|
||||
case MEDIA_BUS_FMT_SRGGB12_1X12:
|
||||
return MIPI_CSI2_DT_RAW12;
|
||||
case MEDIA_BUS_FMT_Y10_1X10:
|
||||
case MEDIA_BUS_FMT_SBGGR10_1X10:
|
||||
case MEDIA_BUS_FMT_SGBRG10_1X10:
|
||||
case MEDIA_BUS_FMT_SGRBG10_1X10:
|
||||
case MEDIA_BUS_FMT_SRGGB10_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:
|
||||
return MIPI_CSI2_DT_RAW8;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
bool ipu7_isys_is_bayer_format(u32 code)
|
||||
{
|
||||
switch (ipu7_isys_mbus_code_to_mipi(code)) {
|
||||
case MIPI_CSI2_DT_RAW8:
|
||||
case MIPI_CSI2_DT_RAW10:
|
||||
case MIPI_CSI2_DT_RAW12:
|
||||
case MIPI_CSI2_DT_RAW14:
|
||||
case MIPI_CSI2_DT_RAW16:
|
||||
case MIPI_CSI2_DT_RAW20:
|
||||
case MIPI_CSI2_DT_RAW24:
|
||||
case MIPI_CSI2_DT_RAW28:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ipu7_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,
|
||||
};
|
||||
unsigned int 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 ^ ((((u32)y & 1U) << 1U) | ((u32)x & 1U))];
|
||||
}
|
||||
|
||||
int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_format *format)
|
||||
{
|
||||
struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
|
||||
u32 code = asd->supported_codes[0];
|
||||
struct v4l2_mbus_framefmt *fmt;
|
||||
u32 other_pad, other_stream;
|
||||
struct v4l2_rect *crop;
|
||||
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, IPU_ISYS_MIN_WIDTH,
|
||||
IPU_ISYS_MAX_WIDTH);
|
||||
format->format.height = clamp(format->format.height,
|
||||
IPU_ISYS_MIN_HEIGHT,
|
||||
IPU_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 ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_mbus_code_enum *code)
|
||||
{
|
||||
struct ipu7_isys_subdev *asd = to_ipu7_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 fmt = {
|
||||
.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, &fmt);
|
||||
}
|
||||
|
||||
int ipu7_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
|
||||
struct v4l2_mbus_framefmt *format)
|
||||
{
|
||||
struct v4l2_subdev_state *state;
|
||||
struct v4l2_mbus_framefmt *fmt;
|
||||
|
||||
if (!sd || !format)
|
||||
return -EINVAL;
|
||||
|
||||
state = v4l2_subdev_lock_and_get_active_state(sd);
|
||||
fmt = v4l2_subdev_state_get_format(state, pad, stream);
|
||||
if (fmt)
|
||||
*format = *fmt;
|
||||
v4l2_subdev_unlock_state(state);
|
||||
|
||||
return fmt ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
u32 ipu7_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad)
|
||||
{
|
||||
struct v4l2_subdev_state *state;
|
||||
struct v4l2_subdev_route *routes;
|
||||
u32 source_stream = 0;
|
||||
unsigned int i;
|
||||
|
||||
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 ipu7_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 ipu7_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 ipu7_isys_subdev_internal_ops = {
|
||||
.init_state = ipu7_isys_subdev_init_state,
|
||||
};
|
||||
|
||||
int ipu7_isys_subdev_init(struct ipu7_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 = &ipu7_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) {
|
||||
pr_err("isys subdev init failed %d.\n", 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 ipu7_isys_subdev_cleanup(struct ipu7_isys_subdev *asd)
|
||||
{
|
||||
media_entity_cleanup(&asd->sd.entity);
|
||||
v4l2_ctrl_handler_free(&asd->ctrl_handler);
|
||||
}
|
||||
53
drivers/staging/media/ipu7/ipu7-isys-subdev.h
Normal file
53
drivers/staging/media/ipu7/ipu7-isys-subdev.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_SUBDEV_H
|
||||
#define IPU7_ISYS_SUBDEV_H
|
||||
|
||||
#include <linux/container_of.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
|
||||
struct ipu7_isys;
|
||||
|
||||
struct ipu7_isys_subdev {
|
||||
struct v4l2_subdev sd;
|
||||
struct ipu7_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_ipu7_isys_subdev(__sd) \
|
||||
container_of(__sd, struct ipu7_isys_subdev, sd)
|
||||
unsigned int ipu7_isys_mbus_code_to_mipi(u32 code);
|
||||
bool ipu7_isys_is_bayer_format(u32 code);
|
||||
u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y);
|
||||
|
||||
int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_format *format);
|
||||
int ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_mbus_code_enum
|
||||
*code);
|
||||
u32 ipu7_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad);
|
||||
int ipu7_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
|
||||
struct v4l2_mbus_framefmt *format);
|
||||
int ipu7_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 ipu7_isys_subdev_init(struct ipu7_isys_subdev *asd,
|
||||
const struct v4l2_subdev_ops *ops,
|
||||
unsigned int nr_ctrls,
|
||||
unsigned int num_sink_pads,
|
||||
unsigned int num_source_pads);
|
||||
void ipu7_isys_subdev_cleanup(struct ipu7_isys_subdev *asd);
|
||||
#endif /* IPU7_ISYS_SUBDEV_H */
|
||||
1112
drivers/staging/media/ipu7/ipu7-isys-video.c
Normal file
1112
drivers/staging/media/ipu7/ipu7-isys-video.c
Normal file
File diff suppressed because it is too large
Load Diff
117
drivers/staging/media/ipu7/ipu7-isys-video.h
Normal file
117
drivers/staging/media/ipu7/ipu7-isys-video.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_VIDEO_H
|
||||
#define IPU7_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 "ipu7-isys-queue.h"
|
||||
|
||||
#define IPU_INSYS_OUTPUT_PINS 11U
|
||||
#define IPU_ISYS_MAX_PARALLEL_SOF 2U
|
||||
|
||||
struct file;
|
||||
struct ipu7_isys;
|
||||
struct ipu7_isys_csi2;
|
||||
struct ipu7_insys_stream_cfg;
|
||||
struct ipu7_isys_subdev;
|
||||
|
||||
struct ipu7_isys_pixelformat {
|
||||
u32 pixelformat;
|
||||
u32 bpp;
|
||||
u32 bpp_packed;
|
||||
u32 code;
|
||||
u32 css_pixelformat;
|
||||
};
|
||||
|
||||
struct sequence_info {
|
||||
unsigned int sequence;
|
||||
u64 timestamp;
|
||||
};
|
||||
|
||||
struct output_pin_data {
|
||||
void (*pin_ready)(struct ipu7_isys_stream *stream,
|
||||
struct ipu7_insys_resp *info);
|
||||
struct ipu7_isys_queue *aq;
|
||||
};
|
||||
|
||||
/*
|
||||
* Align with firmware stream. Each stream represents a CSI virtual channel.
|
||||
* May map to multiple video devices
|
||||
*/
|
||||
struct ipu7_isys_stream {
|
||||
struct mutex mutex;
|
||||
struct media_entity *source_entity;
|
||||
atomic_t sequence;
|
||||
atomic_t buf_id;
|
||||
unsigned int seq_index;
|
||||
struct sequence_info seq[IPU_ISYS_MAX_PARALLEL_SOF];
|
||||
int stream_source;
|
||||
int stream_handle;
|
||||
unsigned int nr_output_pins;
|
||||
struct ipu7_isys_subdev *asd;
|
||||
|
||||
int nr_queues; /* Number of capture queues */
|
||||
int nr_streaming;
|
||||
int streaming;
|
||||
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 ipu7_isys *isys;
|
||||
|
||||
struct output_pin_data output_pins[IPU_INSYS_OUTPUT_PINS];
|
||||
int error;
|
||||
u8 vc;
|
||||
};
|
||||
|
||||
struct ipu7_isys_video {
|
||||
struct ipu7_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 ipu7_isys *isys;
|
||||
struct ipu7_isys_csi2 *csi2;
|
||||
struct ipu7_isys_stream *stream;
|
||||
unsigned int streaming;
|
||||
u8 vc;
|
||||
u8 dt;
|
||||
};
|
||||
|
||||
#define ipu7_isys_queue_to_video(__aq) \
|
||||
container_of(__aq, struct ipu7_isys_video, aq)
|
||||
|
||||
extern const struct ipu7_isys_pixelformat ipu7_isys_pfmts[];
|
||||
|
||||
const struct ipu7_isys_pixelformat *ipu7_isys_get_isys_format(u32 pixelformat);
|
||||
int ipu7_isys_video_prepare_stream(struct ipu7_isys_video *av,
|
||||
struct media_entity *source_entity,
|
||||
int nr_queues);
|
||||
int ipu7_isys_video_set_streaming(struct ipu7_isys_video *av, int state,
|
||||
struct ipu7_isys_buffer_list *bl);
|
||||
int ipu7_isys_fw_open(struct ipu7_isys *isys);
|
||||
void ipu7_isys_fw_close(struct ipu7_isys *isys);
|
||||
int ipu7_isys_setup_video(struct ipu7_isys_video *av,
|
||||
struct media_entity **source_entity, int *nr_queues);
|
||||
int ipu7_isys_video_init(struct ipu7_isys_video *av);
|
||||
void ipu7_isys_video_cleanup(struct ipu7_isys_video *av);
|
||||
void ipu7_isys_put_stream(struct ipu7_isys_stream *stream);
|
||||
struct ipu7_isys_stream *
|
||||
ipu7_isys_query_stream_by_handle(struct ipu7_isys *isys,
|
||||
u8 stream_handle);
|
||||
struct ipu7_isys_stream *
|
||||
ipu7_isys_query_stream_by_source(struct ipu7_isys *isys, int source, u8 vc);
|
||||
#endif /* IPU7_ISYS_VIDEO_H */
|
||||
1166
drivers/staging/media/ipu7/ipu7-isys.c
Normal file
1166
drivers/staging/media/ipu7/ipu7-isys.c
Normal file
File diff suppressed because it is too large
Load Diff
140
drivers/staging/media/ipu7/ipu7-isys.h
Normal file
140
drivers/staging/media/ipu7/ipu7-isys.h
Normal file
@@ -0,0 +1,140 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2013 - 2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef IPU7_ISYS_H
|
||||
#define IPU7_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 <media/v4l2-mediabus.h>
|
||||
|
||||
#include "abi/ipu7_fw_msg_abi.h"
|
||||
#include "abi/ipu7_fw_isys_abi.h"
|
||||
|
||||
#include "ipu7.h"
|
||||
#include "ipu7-isys-csi2.h"
|
||||
#include "ipu7-isys-video.h"
|
||||
|
||||
#define IPU_ISYS_ENTITY_PREFIX "Intel IPU7"
|
||||
|
||||
/* FW support max 16 streams */
|
||||
#define IPU_ISYS_MAX_STREAMS 16U
|
||||
|
||||
/*
|
||||
* Current message queue configuration. These must be big enough
|
||||
* so that they never gets full. Queues are located in system memory
|
||||
*/
|
||||
#define IPU_ISYS_SIZE_RECV_QUEUE 40U
|
||||
#define IPU_ISYS_SIZE_LOG_QUEUE 256U
|
||||
#define IPU_ISYS_SIZE_SEND_QUEUE 40U
|
||||
#define IPU_ISYS_NUM_RECV_QUEUE 1U
|
||||
|
||||
#define IPU_ISYS_MIN_WIDTH 2U
|
||||
#define IPU_ISYS_MIN_HEIGHT 2U
|
||||
#define IPU_ISYS_MAX_WIDTH 8160U
|
||||
#define IPU_ISYS_MAX_HEIGHT 8190U
|
||||
|
||||
#define FW_CALL_TIMEOUT_JIFFIES \
|
||||
msecs_to_jiffies(IPU_LIB_CALL_TIMEOUT_MS)
|
||||
|
||||
struct isys_fw_log {
|
||||
struct mutex mutex; /* protect whole struct */
|
||||
void *head;
|
||||
void *addr;
|
||||
u32 count; /* running counter of log */
|
||||
u32 size; /* actual size of log content, in bits */
|
||||
};
|
||||
|
||||
/*
|
||||
* struct ipu7_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
|
||||
* @syscom: fw communication layer context
|
||||
* @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 ipu7_isys {
|
||||
struct media_device media_dev;
|
||||
struct v4l2_device v4l2_dev;
|
||||
struct ipu7_bus_device *adev;
|
||||
|
||||
int power;
|
||||
spinlock_t power_lock; /* Serialise access to power */
|
||||
u32 isr_csi2_mask;
|
||||
u32 csi2_rx_ctrl_cached;
|
||||
spinlock_t streams_lock;
|
||||
struct ipu7_isys_stream streams[IPU_ISYS_MAX_STREAMS];
|
||||
int streams_ref_count[IPU_ISYS_MAX_STREAMS];
|
||||
u32 phy_rext_cal;
|
||||
bool icache_prefetch;
|
||||
bool csi2_cse_ipc_not_supported;
|
||||
unsigned int ref_count;
|
||||
unsigned int stream_opened;
|
||||
|
||||
struct mutex mutex; /* Serialise isys video open/release related */
|
||||
struct mutex stream_mutex; /* Stream start, stop, queueing reqs */
|
||||
|
||||
struct ipu7_isys_pdata *pdata;
|
||||
|
||||
struct ipu7_isys_csi2 *csi2;
|
||||
struct isys_fw_log *fw_log;
|
||||
|
||||
struct list_head requests;
|
||||
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 ipu7_insys_config *subsys_config;
|
||||
dma_addr_t subsys_config_dma_addr;
|
||||
};
|
||||
|
||||
struct isys_fw_msgs {
|
||||
union {
|
||||
u64 dummy;
|
||||
struct ipu7_insys_buffset frame;
|
||||
struct ipu7_insys_stream_cfg stream;
|
||||
} fw_msg;
|
||||
struct list_head head;
|
||||
dma_addr_t dma_addr;
|
||||
};
|
||||
|
||||
struct ipu7_isys_csi2_config {
|
||||
unsigned int nlanes;
|
||||
unsigned int port;
|
||||
enum v4l2_mbus_type bus_type;
|
||||
};
|
||||
|
||||
struct sensor_async_sd {
|
||||
struct v4l2_async_connection asc;
|
||||
struct ipu7_isys_csi2_config csi2;
|
||||
};
|
||||
|
||||
struct isys_fw_msgs *ipu7_get_fw_msg_buf(struct ipu7_isys_stream *stream);
|
||||
void ipu7_put_fw_msg_buf(struct ipu7_isys *isys, uintptr_t data);
|
||||
void ipu7_cleanup_fw_msg_bufs(struct ipu7_isys *isys);
|
||||
int isys_isr_one(struct ipu7_bus_device *adev);
|
||||
void ipu7_isys_setup_hw(struct ipu7_isys *isys);
|
||||
#endif /* IPU7_ISYS_H */
|
||||
@@ -2745,10 +2745,8 @@ static int ipu7_runtime_resume(struct device *dev)
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ipu7_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(&ipu7_suspend, &ipu7_resume)
|
||||
SET_RUNTIME_PM_OPS(&ipu7_suspend, /* Same as in suspend flow */
|
||||
&ipu7_runtime_resume,
|
||||
NULL)
|
||||
SYSTEM_SLEEP_PM_OPS(&ipu7_suspend, &ipu7_resume)
|
||||
RUNTIME_PM_OPS(&ipu7_suspend, &ipu7_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct pci_device_id ipu7_pci_tbl[] = {
|
||||
|
||||
Reference in New Issue
Block a user