Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,9 @@
config VIDEO_QCOM_CAMSS
tristate "Qualcomm V4L2 Camera Subsystem driver"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV
depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select VIDEOBUF2_DMA_SG
select V4L2_FWNODE
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-2.0-only
# Makefile for Qualcomm CAMSS driver
qcom-camss-objs += \
camss.o \
camss-csid.o \
camss-csid-4-1.o \
camss-csid-4-7.o \
camss-csid-340.o \
camss-csid-680.o \
camss-csid-gen2.o \
camss-csid-gen3.o \
camss-csiphy.o \
camss-csiphy-2ph-1-0.o \
camss-csiphy-3ph-1-0.o \
camss-format.o \
camss-ispif.o \
camss-tpg.o \
camss-tpg-gen1.o \
camss-vfe.o \
camss-vfe-4-1.o \
camss-vfe-4-7.o \
camss-vfe-4-8.o \
camss-vfe-17x.o \
camss-vfe-340.o \
camss-vfe-480.o \
camss-vfe-680.o \
camss-vfe-gen1.o \
camss-vfe-gen3.o \
camss-vfe-vbif.o \
camss-video.o
obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module 340
*
* Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/completion.h>
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include "camss.h"
#include "camss-csid.h"
#include "camss-csid-gen2.h"
#define CSID_RST_STROBES (0x010)
#define CSID_RST_SW_REGS BIT(0)
#define CSID_RST_IRQ BIT(1)
#define CSID_RST_IFE_CLK BIT(2)
#define CSID_RST_PHY_CLK BIT(3)
#define CSID_RST_CSID_CLK BIT(4)
#define CSID_IRQ_STATUS (0x070)
#define CSID_IRQ_MASK (0x074)
#define CSID_IRQ_MASK_RST_DONE BIT(0)
#define CSID_IRQ_CLEAR (0x078)
#define CSID_IRQ_CMD (0x080)
#define CSID_IRQ_CMD_CLEAR BIT(0)
#define CSID_CSI2_RX_CFG0 (0x100)
#define CSI2_RX_CFG0_NUM_ACTIVE_LANES_MASK GENMASK(1, 0)
#define CSI2_RX_CFG0_DLX_INPUT_SEL_MASK GENMASK(17, 4)
#define CSI2_RX_CFG0_PHY_NUM_SEL_MASK GENMASK(21, 20)
#define CSI2_RX_CFG0_PHY_NUM_SEL_BASE_IDX 1
#define CSI2_RX_CFG0_PHY_TYPE_SEL BIT(24)
#define CSID_CSI2_RX_CFG1 (0x104)
#define CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN BIT(0)
#define CSI2_RX_CFG1_MISR_EN BIT(6)
#define CSI2_RX_CFG1_CGC_MODE BIT(7)
#define CSID_CFG0(iface) (0x200 + 0x100 * (iface))
#define CSID_CFG0_BYTE_CNTR_EN BIT(0)
#define CSID_CFG0_TIMESTAMP_EN BIT(1)
#define CSID_CFG0_DECODE_FORMAT_MASK GENMASK(15, 12)
#define CSID_CFG0_DECODE_FORMAT_NOP CSID_CFG0_DECODE_FORMAT_MASK
#define CSID_CFG0_DT_MASK GENMASK(21, 16)
#define CSID_CFG0_VC_MASK GENMASK(23, 22)
#define CSID_CFG0_DTID_MASK GENMASK(28, 27)
#define CSID_CFG0_ENABLE BIT(31)
#define CSID_CTRL(iface) (0x208 + 0x100 * (iface))
#define CSID_CTRL_HALT_AT_FRAME_BOUNDARY 0
#define CSID_CTRL_RESUME_AT_FRAME_BOUNDARY 1
enum csid_iface {
CSID_IFACE_PIX,
CSID_IFACE_RDI0,
CSID_IFACE_RDI1,
CSID_IFACE_RDI2,
};
static enum csid_iface csid_port_iface_map[MSM_CSID_MAX_SRC_STREAMS] = {
[0] = CSID_IFACE_RDI0,
[1] = CSID_IFACE_RDI1,
[2] = CSID_IFACE_RDI2,
[3] = CSID_IFACE_PIX,
};
static void __csid_configure_rx(struct csid_device *csid, struct csid_phy_config *phy)
{
u32 val;
val = FIELD_PREP(CSI2_RX_CFG0_NUM_ACTIVE_LANES_MASK, phy->lane_cnt - 1);
val |= FIELD_PREP(CSI2_RX_CFG0_DLX_INPUT_SEL_MASK, phy->lane_assign);
val |= FIELD_PREP(CSI2_RX_CFG0_PHY_NUM_SEL_MASK,
phy->csiphy_id + CSI2_RX_CFG0_PHY_NUM_SEL_BASE_IDX);
writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG0);
val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG1);
}
static void __csid_configure_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc)
{
struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port];
const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
enum csid_iface iface = csid_port_iface_map[port];
u8 dt_id;
u32 val;
/*
* DT_ID is a two bit bitfield that is concatenated with
* the four least significant bits of the five bit VC
* bitfield to generate an internal CID value.
*
* CSID_CFG0(port)
* DT_ID : 28:27
* VC : 26:22
* DT : 21:16
*
* CID : VC 3:0 << 2 | DT_ID 1:0
*/
dt_id = port & 0x03;
if (iface == CSID_IFACE_PIX)
val = FIELD_PREP(CSID_CFG0_DECODE_FORMAT_MASK, format->decode_format);
else /* RDI is raw, no decoding */
val = CSID_CFG0_DECODE_FORMAT_NOP;
val |= FIELD_PREP(CSID_CFG0_DT_MASK, format->data_type);
val |= FIELD_PREP(CSID_CFG0_VC_MASK, vc);
val |= FIELD_PREP(CSID_CFG0_DTID_MASK, dt_id);
if (enable)
val |= CSID_CFG0_ENABLE;
dev_dbg(csid->camss->dev, "CSID%u: Stream %s (dt:0x%x df=0x%x port=%u vc=%u)\n",
csid->id, enable ? "enable" : "disable", format->data_type,
format->decode_format, port, vc);
writel_relaxed(val, csid->base + CSID_CFG0(iface));
writel_relaxed(enable, csid->base + CSID_CTRL(iface));
}
static void csid_configure_streams(struct csid_device *csid, u8 enable)
{
int i;
__csid_configure_rx(csid, &csid->phy);
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) {
if (csid->phy.en_vc & BIT(i))
__csid_configure_stream(csid, !!enable, i, 0);
}
}
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
writel_relaxed(CSID_IRQ_MASK_RST_DONE, csid->base + CSID_IRQ_MASK);
writel_relaxed(CSID_IRQ_MASK_RST_DONE, csid->base + CSID_IRQ_CLEAR);
writel_relaxed(CSID_IRQ_CMD_CLEAR, csid->base + CSID_IRQ_CMD);
reinit_completion(&csid->reset_complete);
/* Reset with registers preserved */
writel(CSID_RST_IRQ | CSID_RST_IFE_CLK | CSID_RST_PHY_CLK | CSID_RST_CSID_CLK,
csid->base + CSID_RST_STROBES);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID%u: reset timeout\n", csid->id);
return -EIO;
}
dev_dbg(csid->camss->dev, "CSID%u: reset done\n", csid->id);
return 0;
}
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 val;
val = readl_relaxed(csid->base + CSID_IRQ_STATUS);
writel_relaxed(val, csid->base + CSID_IRQ_CLEAR);
writel_relaxed(CSID_IRQ_CMD_CLEAR, csid->base + CSID_IRQ_CMD);
if (val & CSID_IRQ_MASK_RST_DONE)
complete(&csid->reset_complete);
else
dev_warn_ratelimited(csid->camss->dev, "Spurious CSID interrupt\n");
return IRQ_HANDLED;
}
static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val)
{
return -EOPNOTSUPP; /* Not part of CSID */
}
static void csid_subdev_init(struct csid_device *csid) {}
const struct csid_hw_ops csid_ops_340 = {
.configure_testgen_pattern = csid_configure_testgen_pattern,
.configure_stream = csid_configure_streams,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
};
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-csid-4-1.c
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (C) 2020 Linaro Ltd.
*/
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include "camss-csid.h"
#include "camss-csid-gen1.h"
#include "camss.h"
#define CAMSS_CSID_CORE_CTRL_0 0x004
#define CAMSS_CSID_CORE_CTRL_1 0x008
#define CAMSS_CSID_RST_CMD 0x00c
#define CAMSS_CSID_CID_LUT_VC_n(n) (0x010 + 0x4 * (n))
#define CAMSS_CSID_CID_n_CFG(n) (0x020 + 0x4 * (n))
#define CAMSS_CSID_CID_n_CFG_ISPIF_EN BIT(0)
#define CAMSS_CSID_CID_n_CFG_RDI_EN BIT(1)
#define CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT 4
#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_8 (PLAIN_FORMAT_PLAIN8 << 8)
#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16 (PLAIN_FORMAT_PLAIN16 << 8)
#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB (0 << 9)
#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_MSB (1 << 9)
#define CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP (0 << 10)
#define CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING (1 << 10)
#define CAMSS_CSID_IRQ_CLEAR_CMD 0x060
#define CAMSS_CSID_IRQ_MASK 0x064
#define CAMSS_CSID_IRQ_STATUS 0x068
#define CAMSS_CSID_TG_CTRL 0x0a0
#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436
#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437
#define CAMSS_CSID_TG_VC_CFG 0x0a4
#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff
#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f
#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0ac + 0xc * (n))
#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b0 + 0xc * (n))
#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0b4 + 0xc * (n))
static void csid_configure_stream(struct csid_device *csid, u8 enable)
{
struct csid_testgen_config *tg = &csid->testgen;
u32 val;
if (enable) {
struct v4l2_mbus_framefmt *input_format;
const struct csid_format_info *format;
u8 vc = 0; /* Virtual Channel 0 */
u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */
u8 dt_shift;
if (tg->enabled) {
/* Config Test Generator */
u32 num_lines, num_bytes_per_line;
input_format = &csid->fmt[MSM_CSID_PAD_SRC];
format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
num_bytes_per_line = input_format->width * format->bpp * format->spp / 8;
num_lines = input_format->height;
/* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */
/* 1:0 VC */
val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) |
((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13);
writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG);
/* 28:16 bytes per lines, 12:0 num of lines */
val = ((num_bytes_per_line & 0x1fff) << 16) |
(num_lines & 0x1fff);
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_0(0));
/* 5:0 data type */
val = format->data_type;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_1(0));
/* 2:0 output test pattern */
val = tg->mode - 1;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_2(0));
} else {
struct csid_phy_config *phy = &csid->phy;
input_format = &csid->fmt[MSM_CSID_PAD_SINK];
format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
val = phy->lane_cnt - 1;
val |= phy->lane_assign << 4;
writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_0);
val = phy->csiphy_id << 17;
val |= 0x9;
writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_1);
}
/* Config LUT */
dt_shift = (cid % 4) * 8;
val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc));
val &= ~(0xff << dt_shift);
val |= format->data_type << dt_shift;
writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc));
val = CAMSS_CSID_CID_n_CFG_ISPIF_EN;
val |= CAMSS_CSID_CID_n_CFG_RDI_EN;
val |= format->decode_format << CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT;
val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP;
writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid));
if (tg->enabled) {
val = CAMSS_CSID_TG_CTRL_ENABLE;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL);
}
} else {
if (tg->enabled) {
val = CAMSS_CSID_TG_CTRL_DISABLE;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL);
}
}
}
static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val)
{
if (val > 0 && val <= csid->testgen.nmodes)
csid->testgen.mode = val;
return 0;
}
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 value;
value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS);
writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD);
if ((value >> 11) & 0x1)
complete(&csid->reset_complete);
return IRQ_HANDLED;
}
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
reinit_completion(&csid->reset_complete);
writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID reset timeout\n");
return -EIO;
}
return 0;
}
static void csid_subdev_init(struct csid_device *csid)
{
csid->testgen.modes = csid_testgen_modes;
csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
}
const struct csid_hw_ops csid_ops_4_1 = {
.configure_stream = csid_configure_stream,
.configure_testgen_pattern = csid_configure_testgen_pattern,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
};
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-csid-4-7.c
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (C) 2020 Linaro Ltd.
*/
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include "camss-csid.h"
#include "camss-csid-gen1.h"
#include "camss.h"
#define CAMSS_CSID_CORE_CTRL_0 0x004
#define CAMSS_CSID_CORE_CTRL_1 0x008
#define CAMSS_CSID_RST_CMD 0x010
#define CAMSS_CSID_CID_LUT_VC_n(n) (0x014 + 0x4 * (n))
#define CAMSS_CSID_CID_n_CFG(n) (0x024 + 0x4 * (n))
#define CAMSS_CSID_CID_n_CFG_ISPIF_EN BIT(0)
#define CAMSS_CSID_CID_n_CFG_RDI_EN BIT(1)
#define CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT 4
#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_8 (PLAIN_FORMAT_PLAIN8 << 8)
#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16 (PLAIN_FORMAT_PLAIN16 << 8)
#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB (0 << 9)
#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_MSB (1 << 9)
#define CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP (0 << 10)
#define CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING (1 << 10)
#define CAMSS_CSID_IRQ_CLEAR_CMD 0x064
#define CAMSS_CSID_IRQ_MASK 0x068
#define CAMSS_CSID_IRQ_STATUS 0x06c
#define CAMSS_CSID_TG_CTRL 0x0a8
#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436
#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437
#define CAMSS_CSID_TG_VC_CFG 0x0ac
#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff
#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f
#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0b4 + 0xc * (n))
#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b8 + 0xc * (n))
#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0bc + 0xc * (n))
static void csid_configure_stream(struct csid_device *csid, u8 enable)
{
struct csid_testgen_config *tg = &csid->testgen;
u32 sink_code = csid->fmt[MSM_CSID_PAD_SINK].code;
u32 src_code = csid->fmt[MSM_CSID_PAD_SRC].code;
u32 val;
if (enable) {
struct v4l2_mbus_framefmt *input_format;
const struct csid_format_info *format;
u8 vc = 0; /* Virtual Channel 0 */
u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */
u8 dt_shift;
if (tg->enabled) {
/* Config Test Generator */
u32 num_bytes_per_line, num_lines;
input_format = &csid->fmt[MSM_CSID_PAD_SRC];
format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
num_bytes_per_line = input_format->width * format->bpp * format->spp / 8;
num_lines = input_format->height;
/* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */
/* 1:0 VC */
val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) |
((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13);
writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG);
/* 28:16 bytes per lines, 12:0 num of lines */
val = ((num_bytes_per_line & 0x1fff) << 16) |
(num_lines & 0x1fff);
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_0(0));
/* 5:0 data type */
val = format->data_type;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_1(0));
/* 2:0 output test pattern */
val = tg->mode - 1;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_2(0));
} else {
struct csid_phy_config *phy = &csid->phy;
input_format = &csid->fmt[MSM_CSID_PAD_SINK];
format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
val = phy->lane_cnt - 1;
val |= phy->lane_assign << 4;
writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_0);
val = phy->csiphy_id << 17;
val |= 0x9;
writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_1);
}
/* Config LUT */
dt_shift = (cid % 4) * 8;
val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc));
val &= ~(0xff << dt_shift);
val |= format->data_type << dt_shift;
writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc));
val = CAMSS_CSID_CID_n_CFG_ISPIF_EN;
val |= CAMSS_CSID_CID_n_CFG_RDI_EN;
val |= format->decode_format << CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT;
val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP;
if ((sink_code == MEDIA_BUS_FMT_SBGGR10_1X10 &&
src_code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE) ||
(sink_code == MEDIA_BUS_FMT_Y10_1X10 &&
src_code == MEDIA_BUS_FMT_Y10_2X8_PADHI_LE)) {
val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING;
val |= CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16;
val |= CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB;
}
writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid));
if (tg->enabled) {
val = CAMSS_CSID_TG_CTRL_ENABLE;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL);
}
} else {
if (tg->enabled) {
val = CAMSS_CSID_TG_CTRL_DISABLE;
writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL);
}
}
}
static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val)
{
if (val > 0 && val <= csid->testgen.nmodes)
csid->testgen.mode = val;
return 0;
}
/*
* isr - CSID module interrupt service routine
* @irq: Interrupt line
* @dev: CSID device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 value;
value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS);
writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD);
if ((value >> 11) & 0x1)
complete(&csid->reset_complete);
return IRQ_HANDLED;
}
/*
* csid_reset - Trigger reset on CSID module and wait to complete
* @csid: CSID device
*
* Return 0 on success or a negative error code otherwise
*/
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
reinit_completion(&csid->reset_complete);
writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID reset timeout\n");
return -EIO;
}
return 0;
}
static void csid_subdev_init(struct csid_device *csid)
{
csid->testgen.modes = csid_testgen_modes;
csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
}
const struct csid_hw_ops csid_ops_4_7 = {
.configure_stream = csid_configure_stream,
.configure_testgen_pattern = csid_configure_testgen_pattern,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
};
@@ -0,0 +1,434 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (C) 2020-2025 Linaro Ltd.
*/
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include "camss.h"
#include "camss-csid.h"
#include "camss-csid-gen2.h"
#define CSID_TOP_IO_PATH_CFG0(csid) (0x4 * (csid))
#define CSID_TOP_IO_PATH_CFG0_INTERNAL_CSID BIT(0)
#define CSID_TOP_IO_PATH_CFG0_SFE_0 BIT(1)
#define CSID_TOP_IO_PATH_CFG0_SFE_1 GENMASK(1, 0)
#define CSID_TOP_IO_PATH_CFG0_SBI_0 BIT(4)
#define CSID_TOP_IO_PATH_CFG0_SBI_1 GENMASK(3, 0)
#define CSID_TOP_IO_PATH_CFG0_SBI_2 GENMASK(3, 1)
#define CSID_TOP_IO_PATH_CFG0_OUTPUT_IFE_EN BIT(8)
#define CSID_TOP_IO_PATH_CFG0_SFE_OFFLINE_EN BIT(12)
#define CSID_RESET_CMD 0x10
#define CSID_RESET_CMD_HW_RESET BIT(0)
#define CSID_RESET_CMD_SW_RESET BIT(1)
#define CSID_RESET_CMD_IRQ_CTRL BIT(2)
#define CSID_IRQ_CMD 0x14
#define CSID_IRQ_CMD_CLEAR BIT(0)
#define CSID_IRQ_CMD_SET BIT(4)
#define CSID_REG_UPDATE_CMD 0x18
#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) (0xec + 0x10 * (rdi))
#define CSID_CSI2_RDIN_CCIF_VIOLATION BIT(29)
#define CSID_CSI2_RDIN_SENSOR_SWITCH_OUT_OF_SYNC_FRAME_DROP BIT(28)
#define CSID_CSI2_RDIN_ERROR_REC_WIDTH_VIOLATION BIT(27)
#define CSID_CSI2_RDIN_ERROR_REC_HEIGHT_VIOLATION BIT(26)
#define CSID_CSI2_RDIN_BATCH_END_MISSING_VIOLATION BIT(25)
#define CSID_CSI2_RDIN_ILLEGAL_BATCH_ID_IRQ BIT(24)
#define CSID_CSI2_RDIN_RUP_DONE BIT(23)
#define CSID_CSI2_RDIN_CAMIF_EPOCH_1_IRQ BIT(22)
#define CSID_CSI2_RDIN_CAMIF_EPOCH_0_IRQ BIT(21)
#define CSID_CSI2_RDIN_ERROR_REC_OVERFLOW_IRQ BIT(19)
#define CSID_CSI2_RDIN_ERROR_REC_FRAME_DROP BIT(18)
#define CSID_CSI2_RDIN_VCDT_GRP_CHANG BIT(17)
#define CSID_CSI2_RDIN_VCDT_GRP_0_SEL BIT(16)
#define CSID_CSI2_RDIN_VCDT_GRP_1_SEL BIT(15)
#define CSID_CSI2_RDIN_ERROR_LINE_COUNT BIT(14)
#define CSID_CSI2_RDIN_ERROR_PIX_COUNT BIT(13)
#define CSID_CSI2_RDIN_INFO_INPUT_SOF BIT(12)
#define CSID_CSI2_RDIN_INFO_INPUT_SOL BIT(11)
#define CSID_CSI2_RDIN_INFO_INPUT_EOL BIT(10)
#define CSID_CSI2_RDIN_INFO_INPUT_EOF BIT(9)
#define CSID_CSI2_RDIN_INFO_FRAME_DROP_SOF BIT(8)
#define CSID_CSI2_RDIN_INFO_FRAME_DROP_SOL BIT(7)
#define CSID_CSI2_RDIN_INFO_FRAME_DROP_EOL BIT(6)
#define CSID_CSI2_RDIN_INFO_FRAME_DROP_EOF BIT(5)
#define CSID_CSI2_RDIN_INFO_CAMIF_SOF BIT(4)
#define CSID_CSI2_RDIN_INFO_CAMIF_EOF BIT(3)
#define CSID_CSI2_RDIN_INFO_FIFO_OVERFLOW BIT(2)
#define CSID_CSI2_RDIN_RES1 BIT(1)
#define CSID_CSI2_RDIN_RES0 BIT(0)
#define CSID_CSI2_RDIN_IRQ_MASK(rdi) (0xf0 + 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) (0xf4 + 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_SET(rdi) (0xf8 + 0x10 * (rdi))
#define CSID_TOP_IRQ_STATUS 0x7c
#define CSID_TOP_IRQ_MASK 0x80
#define CSID_TOP_IRQ_CLEAR 0x84
#define CSID_TOP_IRQ_RESET BIT(0)
#define CSID_TOP_IRQ_RX BIT(2)
#define CSID_TOP_IRQ_LONG_PKT(rdi) (BIT(8) << (rdi))
#define CSID_TOP_IRQ_BUF_DONE BIT(13)
#define CSID_BUF_DONE_IRQ_STATUS 0x8c
#define BUF_DONE_IRQ_STATUS_RDI_OFFSET (csid_is_lite(csid) ? 1 : 14)
#define CSID_BUF_DONE_IRQ_MASK 0x90
#define CSID_BUF_DONE_IRQ_CLEAR 0x94
#define CSID_CSI2_RX_IRQ_STATUS 0x9c
#define CSID_CSI2_RX_IRQ_MASK 0xa0
#define CSID_CSI2_RX_IRQ_CLEAR 0xa4
#define CSID_RESET_CFG 0xc
#define CSID_RESET_CFG_MODE_IMMEDIATE BIT(0)
#define CSID_RESET_CFG_LOCATION_COMPLETE BIT(4)
#define CSID_CSI2_RDI_IRQ_STATUS(rdi) (0xec + 0x10 * (rdi))
#define CSID_CSI2_RDI_IRQ_MASK(rdi) (0xf0 + 0x10 * (rdi))
#define CSID_CSI2_RDI_IRQ_CLEAR(rdi) (0xf4 + 0x10 * (rdi))
#define CSID_CSI2_RX_CFG0 0x200
#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0
#define CSI2_RX_CFG0_DL0_INPUT_SEL 4
#define CSI2_RX_CFG0_DL1_INPUT_SEL 8
#define CSI2_RX_CFG0_DL2_INPUT_SEL 12
#define CSI2_RX_CFG0_DL3_INPUT_SEL 16
#define CSI2_RX_CFG0_PHY_NUM_SEL 20
#define CSI2_RX_CFG0_PHY_SEL_BASE_IDX 1
#define CSI2_RX_CFG0_PHY_TYPE_SEL 24
#define CSI2_RX_CFG0_TPG_MUX_EN BIT(27)
#define CSI2_RX_CFG0_TPG_MUX_SEL GENMASK(29, 28)
#define CSID_CSI2_RX_CFG1 0x204
#define CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN BIT(0)
#define CSI2_RX_CFG1_DE_SCRAMBLE_EN BIT(1)
#define CSI2_RX_CFG1_VC_MODE BIT(2)
#define CSI2_RX_CFG1_COMPLETE_STREAM_EN BIT(4)
#define CSI2_RX_CFG1_COMPLETE_STREAM_FRAME_TIMING BIT(5)
#define CSI2_RX_CFG1_MISR_EN BIT(6)
#define CSI2_RX_CFG1_CGC_MODE BIT(7)
#define CSID_CSI2_RX_CAPTURE_CTRL 0x208
#define CSI2_RX_CAPTURE_CTRL_LONG_PKT_EN BIT(0)
#define CSI2_RX_CAPTURE_CTRL_SHORT_PKT_EN BIT(1)
#define CSI2_RX_CAPTURE_CTRL_CPHY_PKT_EN BIT(2)
#define CSI2_RX_CAPTURE_CTRL_LONG_PKT_DT GENMASK(9, 4)
#define CSI2_RX_CAPTURE_CTRL_LONG_PKT_VC GENMASK(14, 10)
#define CSI2_RX_CAPTURE_CTRL_SHORT_PKT_VC GENMASK(19, 15)
#define CSI2_RX_CAPTURE_CTRL_CPHY_PKT_DT GENMASK(20, 25)
#define CSI2_RX_CAPTURE_CTRL_CPHY_PKT_VC GENMASK(30, 26)
#define CSID_CSI2_RX_TOTAL_PKTS_RCVD 0x240
#define CSID_CSI2_RX_STATS_ECC 0x244
#define CSID_CSI2_RX_CRC_ERRORS 0x248
#define CSID_RDI_CFG0(rdi) (0x500 + 0x100 * (rdi))
#define RDI_CFG0_DECODE_FORMAT 12
#define RDI_CFG0_DATA_TYPE 16
#define RDI_CFG0_VIRTUAL_CHANNEL 22
#define RDI_CFG0_DT_ID 27
#define RDI_CFG0_ENABLE BIT(31)
#define CSID_RDI_CTRL(rdi) (0x504 + 0x100 * (rdi))
#define CSID_RDI_CTRL_HALT_CMD_HALT_AT_FRAME_BOUNDARY 0
#define CSID_RDI_CTRL_HALT_CMD_RESUME_AT_FRAME_BOUNDARY 1
#define CSID_RDI_CFG1(rdi) (0x510 + 0x100 * (rdi))
#define RDI_CFG1_TIMESTAMP_STB_FRAME BIT(0)
#define RDI_CFG1_TIMESTAMP_STB_IRQ BIT(1)
#define RDI_CFG1_BYTE_CNTR_EN BIT(2)
#define RDI_CFG1_TIMESTAMP_EN BIT(4)
#define RDI_CFG1_DROP_H_EN BIT(5)
#define RDI_CFG1_DROP_V_EN BIT(6)
#define RDI_CFG1_CROP_H_EN BIT(7)
#define RDI_CFG1_CROP_V_EN BIT(8)
#define RDI_CFG1_MISR_EN BIT(9)
#define RDI_CFG1_PLAIN_ALIGN_MSB BIT(11)
#define RDI_CFG1_EARLY_EOF_EN BIT(14)
#define RDI_CFG1_PACKING_MIPI BIT(15)
#define CSID_RDI_ERR_RECOVERY_CFG0(rdi) (0x514 + 0x100 * (rdi))
#define CSID_RDI_EPOCH_IRQ_CFG(rdi) (0x52c + 0x100 * (rdi))
#define CSID_RDI_FRM_DROP_PATTERN(rdi) (0x540 + 0x100 * (rdi))
#define CSID_RDI_FRM_DROP_PERIOD(rdi) (0x544 + 0x100 * (rdi))
#define CSID_RDI_IRQ_SUBSAMPLE_PATTERN(rdi) (0x548 + 0x100 * (rdi))
#define CSID_RDI_IRQ_SUBSAMPLE_PERIOD(rdi) (0x54c + 0x100 * (rdi))
#define CSID_RDI_PIX_DROP_PATTERN(rdi) (0x558 + 0x100 * (rdi))
#define CSID_RDI_PIX_DROP_PERIOD(rdi) (0x55c + 0x100 * (rdi))
#define CSID_RDI_LINE_DROP_PATTERN(rdi) (0x560 + 0x100 * (rdi))
#define CSID_RDI_LINE_DROP_PERIOD(rdi) (0x564 + 0x100 * (rdi))
static inline int reg_update_rdi(struct csid_device *csid, int n)
{
return BIT(4 + n) + BIT(20 + n);
}
static void csid_reg_update(struct csid_device *csid, int port_id)
{
csid->reg_update |= reg_update_rdi(csid, port_id);
writel(csid->reg_update, csid->base + CSID_REG_UPDATE_CMD);
}
static inline void csid_reg_update_clear(struct csid_device *csid,
int port_id)
{
csid->reg_update &= ~reg_update_rdi(csid, port_id);
writel(csid->reg_update, csid->base + CSID_REG_UPDATE_CMD);
}
static void __csid_configure_rx(struct csid_device *csid,
struct csid_phy_config *phy, int vc)
{
u32 val;
struct camss *camss;
camss = csid->camss;
val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
if (camss->tpg && csid->tpg_linked &&
camss->tpg[phy->csiphy_id].testgen.mode != TPG_PAYLOAD_MODE_DISABLED) {
val |= FIELD_PREP(CSI2_RX_CFG0_TPG_MUX_SEL, phy->csiphy_id + 1);
val |= CSI2_RX_CFG0_TPG_MUX_EN;
} else {
val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX)
<< CSI2_RX_CFG0_PHY_NUM_SEL;
}
writel(val, csid->base + CSID_CSI2_RX_CFG0);
val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
if (vc > 3)
val |= CSI2_RX_CFG1_VC_MODE;
writel(val, csid->base + CSID_CSI2_RX_CFG1);
}
static void __csid_ctrl_rdi(struct csid_device *csid, int enable, u8 rdi)
{
u32 val;
if (enable)
val = CSID_RDI_CTRL_HALT_CMD_RESUME_AT_FRAME_BOUNDARY;
else
val = CSID_RDI_CTRL_HALT_CMD_HALT_AT_FRAME_BOUNDARY;
writel(val, csid->base + CSID_RDI_CTRL(rdi));
}
static void __csid_configure_top(struct csid_device *csid)
{
u32 val;
val = CSID_TOP_IO_PATH_CFG0_OUTPUT_IFE_EN | CSID_TOP_IO_PATH_CFG0_INTERNAL_CSID;
writel(val, csid->camss->csid_wrapper_base +
CSID_TOP_IO_PATH_CFG0(csid->id));
}
static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc)
{
struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port];
const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
u8 lane_cnt = csid->phy.lane_cnt;
u8 dt_id;
u32 val;
if (!lane_cnt)
lane_cnt = 4;
val = 0;
writel(val, csid->base + CSID_RDI_FRM_DROP_PERIOD(port));
/*
* DT_ID is a two bit bitfield that is concatenated with
* the four least significant bits of the five bit VC
* bitfield to generate an internal CID value.
*
* CSID_RDI_CFG0(port)
* DT_ID : 28:27
* VC : 26:22
* DT : 21:16
*
* CID : VC 3:0 << 2 | DT_ID 1:0
*/
dt_id = port & 0x03;
/* note: for non-RDI path, this should be format->decode_format */
val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT;
val |= format->data_type << RDI_CFG0_DATA_TYPE;
val |= vc << RDI_CFG0_VIRTUAL_CHANNEL;
val |= dt_id << RDI_CFG0_DT_ID;
writel(val, csid->base + CSID_RDI_CFG0(port));
val = RDI_CFG1_TIMESTAMP_STB_FRAME;
val |= RDI_CFG1_BYTE_CNTR_EN;
val |= RDI_CFG1_TIMESTAMP_EN;
val |= RDI_CFG1_DROP_H_EN;
val |= RDI_CFG1_DROP_V_EN;
val |= RDI_CFG1_CROP_H_EN;
val |= RDI_CFG1_CROP_V_EN;
val |= RDI_CFG1_PACKING_MIPI;
writel(val, csid->base + CSID_RDI_CFG1(port));
val = 0;
writel(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PERIOD(port));
val = 1;
writel(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PATTERN(port));
val = 0;
writel(val, csid->base + CSID_RDI_CTRL(port));
val = readl(csid->base + CSID_RDI_CFG0(port));
if (enable)
val |= RDI_CFG0_ENABLE;
else
val &= ~RDI_CFG0_ENABLE;
writel(val, csid->base + CSID_RDI_CFG0(port));
}
static void csid_configure_stream(struct csid_device *csid, u8 enable)
{
int i;
__csid_configure_top(csid);
/* Loop through all enabled ports and configure a stream for each */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) {
if (csid->phy.en_vc & BIT(i)) {
__csid_configure_rdi_stream(csid, enable, i, 0);
__csid_configure_rx(csid, &csid->phy, 0);
__csid_ctrl_rdi(csid, enable, i);
}
}
}
/*
* csid_reset - Trigger reset on CSID module and wait to complete
* @csid: CSID device
*
* Return 0 on success or a negative error code otherwise
*/
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
u32 val;
int i;
reinit_completion(&csid->reset_complete);
writel(CSID_IRQ_CMD_CLEAR, csid->base + CSID_IRQ_CMD);
/* preserve registers */
val = CSID_RESET_CFG_MODE_IMMEDIATE | CSID_RESET_CFG_LOCATION_COMPLETE;
writel(val, csid->base + CSID_RESET_CFG);
val = CSID_RESET_CMD_HW_RESET | CSID_RESET_CMD_SW_RESET;
writel(val, csid->base + CSID_RESET_CMD);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID reset timeout\n");
return -EIO;
}
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) {
/* Enable RUP done for the client port */
writel(CSID_CSI2_RDIN_RUP_DONE, csid->base + CSID_CSI2_RDIN_IRQ_MASK(i));
}
/* Clear RDI status */
writel(~0u, csid->base + CSID_BUF_DONE_IRQ_CLEAR);
/* Enable BUF_DONE bit for all write-master client ports */
writel(~0u, csid->base + CSID_BUF_DONE_IRQ_MASK);
/* Unmask all TOP interrupts */
writel(~0u, csid->base + CSID_TOP_IRQ_MASK);
return 0;
}
static void csid_rup_complete(struct csid_device *csid, int rdi)
{
csid_reg_update_clear(csid, rdi);
}
/*
* csid_isr - CSID module interrupt service routine
* @irq: Interrupt line
* @dev: CSID device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 buf_done_val, val, val_top;
int i;
/* Latch and clear TOP status */
val_top = readl(csid->base + CSID_TOP_IRQ_STATUS);
writel(val_top, csid->base + CSID_TOP_IRQ_CLEAR);
/* Latch and clear CSID_CSI2 status */
val = readl(csid->base + CSID_CSI2_RX_IRQ_STATUS);
writel(val, csid->base + CSID_CSI2_RX_IRQ_CLEAR);
/* Latch and clear top level BUF_DONE status */
buf_done_val = readl(csid->base + CSID_BUF_DONE_IRQ_STATUS);
writel(buf_done_val, csid->base + CSID_BUF_DONE_IRQ_CLEAR);
/* Process state for each RDI channel */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) {
val = readl(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i));
if (val)
writel(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i));
if (val & CSID_CSI2_RDIN_RUP_DONE)
csid_rup_complete(csid, i);
if (buf_done_val & BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i))
camss_buf_done(csid->camss, csid->id, i);
}
/* Issue clear command */
writel(CSID_IRQ_CMD_CLEAR, csid->base + CSID_IRQ_CMD);
/* Reset complete */
if (val_top & CSID_TOP_IRQ_RESET)
complete(&csid->reset_complete);
return IRQ_HANDLED;
}
static void csid_subdev_reg_update(struct csid_device *csid, int port_id, bool is_clear)
{
if (is_clear)
csid_reg_update_clear(csid, port_id);
else
csid_reg_update(csid, port_id);
}
static void csid_subdev_init(struct csid_device *csid) {}
const struct csid_hw_ops csid_ops_680 = {
.configure_testgen_pattern = NULL,
.configure_stream = csid_configure_stream,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
.reg_update = csid_subdev_reg_update,
};
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-csid-gen1.h
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module Generation 1
*
* Copyright (C) 2021 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_CSID_GEN1_H
#define QC_MSM_CAMSS_CSID_GEN1_H
#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0
#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1
#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2
#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3
#define DECODE_FORMAT_DPCM_10_6_10 0x4
#define DECODE_FORMAT_DPCM_10_8_10 0x5
#define DECODE_FORMAT_DPCM_12_6_12 0x6
#define DECODE_FORMAT_DPCM_12_8_12 0x7
#define DECODE_FORMAT_UNCOMPRESSED_14_BIT 0x8
#define DECODE_FORMAT_DPCM_14_8_14 0x9
#define DECODE_FORMAT_DPCM_14_10_14 0xa
#define PLAIN_FORMAT_PLAIN8 0x0 /* supports DPCM, UNCOMPRESSED_6/8_BIT */
#define PLAIN_FORMAT_PLAIN16 0x1 /* supports DPCM, UNCOMPRESSED_10/16_BIT */
#endif /* QC_MSM_CAMSS_CSID_GEN1_H */
@@ -0,0 +1,433 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-csid-4-7.c
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (C) 2020 Linaro Ltd.
*/
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include "camss-csid.h"
#include "camss-csid-gen2.h"
#include "camss.h"
/* The CSID 2 IP-block is different from the others,
* and is of a bare-bones Lite version, with no PIX
* interface support. As a result of that it has an
* alternate register layout.
*/
#define CSID_RST_STROBES 0x10
#define RST_STROBES 0
#define CSID_CSI2_RX_IRQ_STATUS 0x20
#define CSID_CSI2_RX_IRQ_MASK 0x24
#define CSID_CSI2_RX_IRQ_CLEAR 0x28
#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) ((csid_is_lite(csid) ? 0x30 : 0x40) \
+ 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_MASK(rdi) ((csid_is_lite(csid) ? 0x34 : 0x44) \
+ 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) ((csid_is_lite(csid) ? 0x38 : 0x48) \
+ 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_SET(rdi) ((csid_is_lite(csid) ? 0x3C : 0x4C) \
+ 0x10 * (rdi))
#define CSID_TOP_IRQ_STATUS 0x70
#define TOP_IRQ_STATUS_RESET_DONE 0
#define CSID_TOP_IRQ_MASK 0x74
#define CSID_TOP_IRQ_CLEAR 0x78
#define CSID_TOP_IRQ_SET 0x7C
#define CSID_IRQ_CMD 0x80
#define IRQ_CMD_CLEAR 0
#define IRQ_CMD_SET 4
#define CSID_CSI2_RX_CFG0 0x100
#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0
#define CSI2_RX_CFG0_DL0_INPUT_SEL 4
#define CSI2_RX_CFG0_DL1_INPUT_SEL 8
#define CSI2_RX_CFG0_DL2_INPUT_SEL 12
#define CSI2_RX_CFG0_DL3_INPUT_SEL 16
#define CSI2_RX_CFG0_PHY_NUM_SEL 20
#define CSI2_RX_CFG0_PHY_TYPE_SEL 24
#define CSID_CSI2_RX_CFG1 0x104
#define CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN 0
#define CSI2_RX_CFG1_DE_SCRAMBLE_EN 1
#define CSI2_RX_CFG1_VC_MODE 2
#define CSI2_RX_CFG1_COMPLETE_STREAM_EN 4
#define CSI2_RX_CFG1_COMPLETE_STREAM_FRAME_TIMING 5
#define CSI2_RX_CFG1_MISR_EN 6
#define CSI2_RX_CFG1_CGC_MODE 7
#define CGC_MODE_DYNAMIC_GATING 0
#define CGC_MODE_ALWAYS_ON 1
#define CSID_RDI_CFG0(rdi) ((csid_is_lite(csid) ? 0x200 : 0x300) \
+ 0x100 * (rdi))
#define RDI_CFG0_BYTE_CNTR_EN 0
#define RDI_CFG0_FORMAT_MEASURE_EN 1
#define RDI_CFG0_TIMESTAMP_EN 2
#define RDI_CFG0_DROP_H_EN 3
#define RDI_CFG0_DROP_V_EN 4
#define RDI_CFG0_CROP_H_EN 5
#define RDI_CFG0_CROP_V_EN 6
#define RDI_CFG0_MISR_EN 7
#define RDI_CFG0_CGC_MODE 8
#define CGC_MODE_DYNAMIC 0
#define CGC_MODE_ALWAYS_ON 1
#define RDI_CFG0_PLAIN_ALIGNMENT 9
#define PLAIN_ALIGNMENT_LSB 0
#define PLAIN_ALIGNMENT_MSB 1
#define RDI_CFG0_PLAIN_FORMAT 10
#define RDI_CFG0_DECODE_FORMAT 12
#define RDI_CFG0_DATA_TYPE 16
#define RDI_CFG0_VIRTUAL_CHANNEL 22
#define RDI_CFG0_DT_ID 27
#define RDI_CFG0_EARLY_EOF_EN 29
#define RDI_CFG0_PACKING_FORMAT 30
#define RDI_CFG0_ENABLE 31
#define CSID_RDI_CFG1(rdi) ((csid_is_lite(csid) ? 0x204 : 0x304)\
+ 0x100 * (rdi))
#define RDI_CFG1_TIMESTAMP_STB_SEL 0
#define CSID_RDI_CTRL(rdi) ((csid_is_lite(csid) ? 0x208 : 0x308)\
+ 0x100 * (rdi))
#define RDI_CTRL_HALT_CMD 0
#define HALT_CMD_HALT_AT_FRAME_BOUNDARY 0
#define HALT_CMD_RESUME_AT_FRAME_BOUNDARY 1
#define RDI_CTRL_HALT_MODE 2
#define CSID_RDI_FRM_DROP_PATTERN(rdi) ((csid_is_lite(csid) ? 0x20C : 0x30C)\
+ 0x100 * (rdi))
#define CSID_RDI_FRM_DROP_PERIOD(rdi) ((csid_is_lite(csid) ? 0x210 : 0x310)\
+ 0x100 * (rdi))
#define CSID_RDI_IRQ_SUBSAMPLE_PATTERN(rdi) ((csid_is_lite(csid) ? 0x214 : 0x314)\
+ 0x100 * (rdi))
#define CSID_RDI_IRQ_SUBSAMPLE_PERIOD(rdi) ((csid_is_lite(csid) ? 0x218 : 0x318)\
+ 0x100 * (rdi))
#define CSID_RDI_RPP_PIX_DROP_PATTERN(rdi) ((csid_is_lite(csid) ? 0x224 : 0x324)\
+ 0x100 * (rdi))
#define CSID_RDI_RPP_PIX_DROP_PERIOD(rdi) ((csid_is_lite(csid) ? 0x228 : 0x328)\
+ 0x100 * (rdi))
#define CSID_RDI_RPP_LINE_DROP_PATTERN(rdi) ((csid_is_lite(csid) ? 0x22C : 0x32C)\
+ 0x100 * (rdi))
#define CSID_RDI_RPP_LINE_DROP_PERIOD(rdi) ((csid_is_lite(csid) ? 0x230 : 0x330)\
+ 0x100 * (rdi))
#define CSID_TPG_CTRL 0x600
#define TPG_CTRL_TEST_EN 0
#define TPG_CTRL_FS_PKT_EN 1
#define TPG_CTRL_FE_PKT_EN 2
#define TPG_CTRL_NUM_ACTIVE_LANES 4
#define TPG_CTRL_CYCLES_BETWEEN_PKTS 8
#define TPG_CTRL_NUM_TRAIL_BYTES 20
#define CSID_TPG_VC_CFG0 0x604
#define TPG_VC_CFG0_VC_NUM 0
#define TPG_VC_CFG0_NUM_ACTIVE_SLOTS 8
#define NUM_ACTIVE_SLOTS_0_ENABLED 0
#define NUM_ACTIVE_SLOTS_0_1_ENABLED 1
#define NUM_ACTIVE_SLOTS_0_1_2_ENABLED 2
#define NUM_ACTIVE_SLOTS_0_1_3_ENABLED 3
#define TPG_VC_CFG0_LINE_INTERLEAVING_MODE 10
#define INTELEAVING_MODE_INTERLEAVED 0
#define INTELEAVING_MODE_ONE_SHOT 1
#define TPG_VC_CFG0_NUM_FRAMES 16
#define CSID_TPG_VC_CFG1 0x608
#define TPG_VC_CFG1_H_BLANKING_COUNT 0
#define TPG_VC_CFG1_V_BLANKING_COUNT 12
#define TPG_VC_CFG1_V_BLANK_FRAME_WIDTH_SEL 24
#define CSID_TPG_LFSR_SEED 0x60C
#define CSID_TPG_DT_n_CFG_0(n) (0x610 + (n) * 0xC)
#define TPG_DT_n_CFG_0_FRAME_HEIGHT 0
#define TPG_DT_n_CFG_0_FRAME_WIDTH 16
#define CSID_TPG_DT_n_CFG_1(n) (0x614 + (n) * 0xC)
#define TPG_DT_n_CFG_1_DATA_TYPE 0
#define TPG_DT_n_CFG_1_ECC_XOR_MASK 8
#define TPG_DT_n_CFG_1_CRC_XOR_MASK 16
#define CSID_TPG_DT_n_CFG_2(n) (0x618 + (n) * 0xC)
#define TPG_DT_n_CFG_2_PAYLOAD_MODE 0
#define TPG_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD 4
#define TPG_DT_n_CFG_2_ENCODE_FORMAT 16
#define CSID_TPG_COLOR_BARS_CFG 0x640
#define TPG_COLOR_BARS_CFG_UNICOLOR_BAR_EN 0
#define TPG_COLOR_BARS_CFG_UNICOLOR_BAR_SEL 4
#define TPG_COLOR_BARS_CFG_SPLIT_EN 5
#define TPG_COLOR_BARS_CFG_ROTATE_PERIOD 8
#define CSID_TPG_COLOR_BOX_CFG 0x644
#define TPG_COLOR_BOX_CFG_MODE 0
#define TPG_COLOR_BOX_PATTERN_SEL 2
static void __csid_configure_rx(struct csid_device *csid,
struct csid_phy_config *phy, int vc)
{
u8 lane_cnt = csid->phy.lane_cnt;
int val;
if (!lane_cnt)
lane_cnt = 4;
val = (lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
val |= phy->csiphy_id << CSI2_RX_CFG0_PHY_NUM_SEL;
writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG0);
val = 1 << CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
if (vc > 3)
val |= 1 << CSI2_RX_CFG1_VC_MODE;
val |= 1 << CSI2_RX_CFG1_MISR_EN;
writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG1);
}
static void __csid_ctrl_rdi(struct csid_device *csid, int enable, u8 rdi)
{
int val;
if (enable)
val = HALT_CMD_RESUME_AT_FRAME_BOUNDARY << RDI_CTRL_HALT_CMD;
else
val = HALT_CMD_HALT_AT_FRAME_BOUNDARY << RDI_CTRL_HALT_CMD;
writel_relaxed(val, csid->base + CSID_RDI_CTRL(rdi));
}
static void __csid_configure_testgen(struct csid_device *csid, u8 enable, u8 port, u8 vc)
{
struct csid_testgen_config *tg = &csid->testgen;
struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port];
const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
u8 lane_cnt = csid->phy.lane_cnt;
u32 val;
if (!lane_cnt)
lane_cnt = 4;
/* configure one DT, infinite frames */
val = vc << TPG_VC_CFG0_VC_NUM;
val |= INTELEAVING_MODE_ONE_SHOT << TPG_VC_CFG0_LINE_INTERLEAVING_MODE;
val |= 0 << TPG_VC_CFG0_NUM_FRAMES;
writel_relaxed(val, csid->base + CSID_TPG_VC_CFG0);
val = 0x740 << TPG_VC_CFG1_H_BLANKING_COUNT;
val |= 0x3ff << TPG_VC_CFG1_V_BLANKING_COUNT;
writel_relaxed(val, csid->base + CSID_TPG_VC_CFG1);
writel_relaxed(0x12345678, csid->base + CSID_TPG_LFSR_SEED);
val = (input_format->height & 0x1fff) << TPG_DT_n_CFG_0_FRAME_HEIGHT;
val |= (input_format->width & 0x1fff) << TPG_DT_n_CFG_0_FRAME_WIDTH;
writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_0(0));
val = format->data_type << TPG_DT_n_CFG_1_DATA_TYPE;
writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_1(0));
val = (tg->mode - 1) << TPG_DT_n_CFG_2_PAYLOAD_MODE;
val |= 0xBE << TPG_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD;
val |= format->decode_format << TPG_DT_n_CFG_2_ENCODE_FORMAT;
writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_2(0));
writel_relaxed(0, csid->base + CSID_TPG_COLOR_BARS_CFG);
writel_relaxed(0, csid->base + CSID_TPG_COLOR_BOX_CFG);
val = enable << TPG_CTRL_TEST_EN;
val |= 1 << TPG_CTRL_FS_PKT_EN;
val |= 1 << TPG_CTRL_FE_PKT_EN;
val |= (lane_cnt - 1) << TPG_CTRL_NUM_ACTIVE_LANES;
val |= 0x64 << TPG_CTRL_CYCLES_BETWEEN_PKTS;
val |= 0xA << TPG_CTRL_NUM_TRAIL_BYTES;
writel_relaxed(val, csid->base + CSID_TPG_CTRL);
}
static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc)
{
/* Source pads matching RDI channels on hardware. Pad 1 -> RDI0, Pad 2 -> RDI1, etc. */
struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port];
const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
u32 val;
/*
* DT_ID is a two bit bitfield that is concatenated with
* the four least significant bits of the five bit VC
* bitfield to generate an internal CID value.
*
* CSID_RDI_CFG0(port)
* DT_ID : 28:27
* VC : 26:22
* DT : 21:16
*
* CID : VC 3:0 << 2 | DT_ID 1:0
*/
u8 dt_id = port & 0x03;
val = 1 << RDI_CFG0_BYTE_CNTR_EN;
val |= 1 << RDI_CFG0_FORMAT_MEASURE_EN;
val |= 1 << RDI_CFG0_TIMESTAMP_EN;
/* note: for non-RDI path, this should be format->decode_format */
val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT;
val |= format->data_type << RDI_CFG0_DATA_TYPE;
val |= vc << RDI_CFG0_VIRTUAL_CHANNEL;
val |= dt_id << RDI_CFG0_DT_ID;
writel_relaxed(val, csid->base + CSID_RDI_CFG0(port));
/* CSID_TIMESTAMP_STB_POST_IRQ */
val = 2 << RDI_CFG1_TIMESTAMP_STB_SEL;
writel_relaxed(val, csid->base + CSID_RDI_CFG1(port));
val = 1;
writel_relaxed(val, csid->base + CSID_RDI_FRM_DROP_PERIOD(port));
val = 0;
writel_relaxed(val, csid->base + CSID_RDI_FRM_DROP_PATTERN(port));
val = 1;
writel_relaxed(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PERIOD(port));
val = 0;
writel_relaxed(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PATTERN(port));
val = 1;
writel_relaxed(val, csid->base + CSID_RDI_RPP_PIX_DROP_PERIOD(port));
val = 0;
writel_relaxed(val, csid->base + CSID_RDI_RPP_PIX_DROP_PATTERN(port));
val = 1;
writel_relaxed(val, csid->base + CSID_RDI_RPP_LINE_DROP_PERIOD(port));
val = 0;
writel_relaxed(val, csid->base + CSID_RDI_RPP_LINE_DROP_PATTERN(port));
val = 0;
writel_relaxed(val, csid->base + CSID_RDI_CTRL(port));
val = readl_relaxed(csid->base + CSID_RDI_CFG0(port));
val |= enable << RDI_CFG0_ENABLE;
writel_relaxed(val, csid->base + CSID_RDI_CFG0(port));
}
static void csid_configure_stream(struct csid_device *csid, u8 enable)
{
struct csid_testgen_config *tg = &csid->testgen;
u8 i;
/* Loop through all enabled ports and configure a stream for each */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++)
if (csid->phy.en_vc & BIT(i)) {
if (tg->enabled)
__csid_configure_testgen(csid, enable, i, 0);
__csid_configure_rdi_stream(csid, enable, i, 0);
__csid_configure_rx(csid, &csid->phy, 0);
__csid_ctrl_rdi(csid, enable, i);
}
}
static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val)
{
if (val > 0 && val <= csid->testgen.nmodes)
csid->testgen.mode = val;
return 0;
}
/*
* csid_isr - CSID module interrupt service routine
* @irq: Interrupt line
* @dev: CSID device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 val;
u8 reset_done;
int i;
val = readl_relaxed(csid->base + CSID_TOP_IRQ_STATUS);
writel_relaxed(val, csid->base + CSID_TOP_IRQ_CLEAR);
reset_done = val & BIT(TOP_IRQ_STATUS_RESET_DONE);
val = readl_relaxed(csid->base + CSID_CSI2_RX_IRQ_STATUS);
writel_relaxed(val, csid->base + CSID_CSI2_RX_IRQ_CLEAR);
/* Read and clear IRQ status for each enabled RDI channel */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++)
if (csid->phy.en_vc & BIT(i)) {
val = readl_relaxed(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i));
writel_relaxed(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i));
}
val = 1 << IRQ_CMD_CLEAR;
writel_relaxed(val, csid->base + CSID_IRQ_CMD);
if (reset_done)
complete(&csid->reset_complete);
return IRQ_HANDLED;
}
/*
* csid_reset - Trigger reset on CSID module and wait to complete
* @csid: CSID device
*
* Return 0 on success or a negative error code otherwise
*/
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
u32 val;
reinit_completion(&csid->reset_complete);
writel_relaxed(1, csid->base + CSID_TOP_IRQ_CLEAR);
writel_relaxed(1, csid->base + CSID_IRQ_CMD);
writel_relaxed(1, csid->base + CSID_TOP_IRQ_MASK);
writel_relaxed(1, csid->base + CSID_IRQ_CMD);
/* preserve registers */
val = 0x1e << RST_STROBES;
writel_relaxed(val, csid->base + CSID_RST_STROBES);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID reset timeout\n");
return -EIO;
}
return 0;
}
static void csid_subdev_init(struct csid_device *csid)
{
csid->testgen.modes = csid_testgen_modes;
csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN2;
}
const struct csid_hw_ops csid_ops_gen2 = {
.configure_stream = csid_configure_stream,
.configure_testgen_pattern = csid_configure_testgen_pattern,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
};
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-csid-gen1.h
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module Generation 1
*
* Copyright (C) 2021 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_CSID_GEN2_H
#define QC_MSM_CAMSS_CSID_GEN2_H
#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0
#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1
#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2
#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3
#define DECODE_FORMAT_UNCOMPRESSED_14_BIT 0x4
#define DECODE_FORMAT_UNCOMPRESSED_16_BIT 0x5
#define DECODE_FORMAT_UNCOMPRESSED_20_BIT 0x6
#define DECODE_FORMAT_DPCM_10_6_10 0x7
#define DECODE_FORMAT_DPCM_10_8_10 0x8
#define DECODE_FORMAT_DPCM_12_6_12 0x9
#define DECODE_FORMAT_DPCM_12_8_12 0xa
#define DECODE_FORMAT_DPCM_14_8_14 0xb
#define DECODE_FORMAT_DPCM_14_10_14 0xc
#define DECODE_FORMAT_DPCM_12_10_12 0xd
#define DECODE_FORMAT_USER_DEFINED 0xe
#define DECODE_FORMAT_PAYLOAD_ONLY 0xf
#define ENCODE_FORMAT_RAW_8_BIT 0x1
#define ENCODE_FORMAT_RAW_10_BIT 0x2
#define ENCODE_FORMAT_RAW_12_BIT 0x3
#define ENCODE_FORMAT_RAW_14_BIT 0x4
#define ENCODE_FORMAT_RAW_16_BIT 0x5
#define PLAIN_FORMAT_PLAIN8 0x0 /* supports DPCM, UNCOMPRESSED_6/8_BIT */
#define PLAIN_FORMAT_PLAIN16 0x1 /* supports DPCM, UNCOMPRESSED_10/16_BIT */
#define PLAIN_FORMAT_PLAIN32 0x2 /* supports UNCOMPRESSED_20_BIT */
#endif /* QC_MSM_CAMSS_CSID_GEN2_H */
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (c) 2024 Qualcomm Technologies, Inc.
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include "camss.h"
#include "camss-csid.h"
#include "camss-csid-gen3.h"
#define CSID_IO_PATH_CFG0(csid) (0x4 * (csid))
#define OUTPUT_IFE_EN 0x100
#define INTERNAL_CSID 1
#define CSID_RST_CFG 0xC
#define RST_MODE BIT(0)
#define RST_LOCATION BIT(4)
#define CSID_RST_CMD 0x10
#define SELECT_HW_RST BIT(0)
#define SELECT_IRQ_RST BIT(2)
#define CSID_IRQ_CMD 0x14
#define IRQ_CMD_CLEAR BIT(0)
#define CSID_RUP_AUP_CMD 0x18
#define CSID_RUP_AUP_RDI(rdi) ((BIT(4) | BIT(20)) << (rdi))
#define CSID_TOP_IRQ_STATUS 0x7C
#define TOP_IRQ_STATUS_RESET_DONE BIT(0)
#define CSID_TOP_IRQ_MASK 0x80
#define CSID_TOP_IRQ_CLEAR 0x84
#define CSID_TOP_IRQ_SET 0x88
#define CSID_CSI2_RX_IRQ_STATUS 0x9C
#define CSID_CSI2_RX_IRQ_MASK 0xA0
#define CSID_CSI2_RX_IRQ_CLEAR 0xA4
#define CSID_CSI2_RX_IRQ_SET 0xA8
#define IS_CSID_690(csid) ((csid->camss->res->version == CAMSS_8775P) \
|| (csid->camss->res->version == CAMSS_8300))
#define CSID_BUF_DONE_IRQ_STATUS 0x8C
#define BUF_DONE_IRQ_STATUS_RDI_OFFSET (csid_is_lite(csid) ? \
((IS_CSID_690(csid) ? 0 : 1)) : \
((IS_CSID_690(csid) ? 13 : 14)))
#define CSID_BUF_DONE_IRQ_MASK 0x90
#define CSID_BUF_DONE_IRQ_CLEAR 0x94
#define CSID_BUF_DONE_IRQ_SET 0x98
#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) (0xEC + 0x10 * (rdi))
#define RUP_DONE_IRQ_STATUS BIT(23)
#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) (0xF4 + 0x10 * (rdi))
#define CSID_CSI2_RDIN_IRQ_SET(rdi) (0xF8 + 0x10 * (rdi))
#define CSID_CSI2_RX_CFG0 0x200
#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0
#define CSI2_RX_CFG0_VC_MODE 3
#define CSI2_RX_CFG0_DL0_INPUT_SEL 4
#define CSI2_RX_CFG0_PHY_NUM_SEL 20
#define CSI2_RX_CFG0_TPG_MUX_EN BIT(27)
#define CSI2_RX_CFG0_TPG_MUX_SEL GENMASK(29, 28)
#define CSID_CSI2_RX_CFG1 0x204
#define CSI2_RX_CFG1_ECC_CORRECTION_EN BIT(0)
#define CSI2_RX_CFG1_VC_MODE BIT(2)
#define CSID_RDI_CFG0(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\
(0x300 + 0x100 * (rdi)) :\
(0x500 + 0x100 * (rdi)))
#define RDI_CFG0_TIMESTAMP_EN BIT(6)
#define RDI_CFG0_TIMESTAMP_STB_SEL BIT(8)
#define RDI_CFG0_DECODE_FORMAT 12
#define RDI_CFG0_DT 16
#define RDI_CFG0_VC 22
#define RDI_CFG0_DT_ID 27
#define RDI_CFG0_EN BIT(31)
#define CSID_RDI_CTRL(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\
(0x304 + 0x100 * (rdi)) :\
(0x504 + 0x100 * (rdi)))
#define RDI_CTRL_START_CMD BIT(0)
#define CSID_RDI_CFG1(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\
(0x310 + 0x100 * (rdi)) :\
(0x510 + 0x100 * (rdi)))
#define RDI_CFG1_DROP_H_EN BIT(5)
#define RDI_CFG1_DROP_V_EN BIT(6)
#define RDI_CFG1_CROP_H_EN BIT(7)
#define RDI_CFG1_CROP_V_EN BIT(8)
#define RDI_CFG1_PIX_STORE BIT(10)
#define RDI_CFG1_PACKING_FORMAT_MIPI BIT(15)
#define CSID_RDI_IRQ_SUBSAMPLE_PATTERN(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\
(0x348 + 0x100 * (rdi)) :\
(0x548 + 0x100 * (rdi)))
#define CSID_RDI_IRQ_SUBSAMPLE_PERIOD(rdi) (csid_is_lite(csid) && IS_CSID_690(csid) ?\
(0x34C + 0x100 * (rdi)) :\
(0x54C + 0x100 * (rdi)))
#define CSI2_RX_CFG0_PHY_SEL_BASE_IDX 1
static void __csid_configure_rx(struct csid_device *csid,
struct csid_phy_config *phy, int vc)
{
int val;
struct camss *camss;
camss = csid->camss;
val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
if (camss->tpg && csid->tpg_linked &&
camss->tpg[phy->csiphy_id].testgen.mode != TPG_PAYLOAD_MODE_DISABLED) {
val |= FIELD_PREP(CSI2_RX_CFG0_TPG_MUX_SEL, phy->csiphy_id + 1);
val |= CSI2_RX_CFG0_TPG_MUX_EN;
} else {
val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX)
<< CSI2_RX_CFG0_PHY_NUM_SEL;
}
writel(val, csid->base + CSID_CSI2_RX_CFG0);
val = CSI2_RX_CFG1_ECC_CORRECTION_EN;
if (vc > 3)
val |= CSI2_RX_CFG1_VC_MODE;
writel(val, csid->base + CSID_CSI2_RX_CFG1);
}
static void __csid_ctrl_rdi(struct csid_device *csid, int enable, u8 rdi)
{
int val = 0;
if (enable)
val = RDI_CTRL_START_CMD;
writel(val, csid->base + CSID_RDI_CTRL(rdi));
}
static void __csid_configure_wrapper(struct csid_device *csid)
{
u32 val;
/* csid lite doesn't need to configure top register */
if (csid->res->is_lite)
return;
val = OUTPUT_IFE_EN | INTERNAL_CSID;
writel(val, csid->camss->csid_wrapper_base + CSID_IO_PATH_CFG0(csid->id));
}
static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc)
{
u32 val;
u8 lane_cnt = csid->phy.lane_cnt;
/* Source pads matching RDI channels on hardware. Pad 1 -> RDI0, Pad 2 -> RDI1, etc. */
struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port];
const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats,
csid->res->formats->nformats,
input_format->code);
if (!lane_cnt)
lane_cnt = 4;
/*
* DT_ID is a two bit bitfield that is concatenated with
* the four least significant bits of the five bit VC
* bitfield to generate an internal CID value.
*
* CSID_RDI_CFG0(port)
* DT_ID : 28:27
* VC : 26:22
* DT : 21:16
*
* CID : VC 3:0 << 2 | DT_ID 1:0
*/
u8 dt_id = port & 0x03;
val = RDI_CFG0_TIMESTAMP_EN;
val |= RDI_CFG0_TIMESTAMP_STB_SEL;
/* note: for non-RDI path, this should be format->decode_format */
val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT;
val |= vc << RDI_CFG0_VC;
val |= format->data_type << RDI_CFG0_DT;
val |= dt_id << RDI_CFG0_DT_ID;
writel(val, csid->base + CSID_RDI_CFG0(port));
val = RDI_CFG1_PACKING_FORMAT_MIPI;
val |= RDI_CFG1_PIX_STORE;
val |= RDI_CFG1_DROP_H_EN;
val |= RDI_CFG1_DROP_V_EN;
val |= RDI_CFG1_CROP_H_EN;
val |= RDI_CFG1_CROP_V_EN;
writel(val, csid->base + CSID_RDI_CFG1(port));
val = 0;
writel(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PERIOD(port));
val = 1;
writel(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PATTERN(port));
val = 0;
writel(val, csid->base + CSID_RDI_CTRL(port));
val = readl(csid->base + CSID_RDI_CFG0(port));
if (enable)
val |= RDI_CFG0_EN;
writel(val, csid->base + CSID_RDI_CFG0(port));
}
static void csid_configure_stream(struct csid_device *csid, u8 enable)
{
u8 i;
__csid_configure_wrapper(csid);
/* Loop through all enabled ports and configure a stream for each */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++)
if (csid->phy.en_vc & BIT(i)) {
__csid_configure_rdi_stream(csid, enable, i, 0);
__csid_configure_rx(csid, &csid->phy, 0);
__csid_ctrl_rdi(csid, enable, i);
}
}
static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val)
{
return 0;
}
static void csid_subdev_reg_update(struct csid_device *csid, int port_id, bool clear)
{
if (clear) {
csid->reg_update &= ~CSID_RUP_AUP_RDI(port_id);
} else {
csid->reg_update |= CSID_RUP_AUP_RDI(port_id);
writel(csid->reg_update, csid->base + CSID_RUP_AUP_CMD);
}
}
/*
* csid_isr - CSID module interrupt service routine
* @irq: Interrupt line
* @dev: CSID device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t csid_isr(int irq, void *dev)
{
struct csid_device *csid = dev;
u32 val, buf_done_val;
u8 reset_done;
int i;
val = readl(csid->base + CSID_TOP_IRQ_STATUS);
writel(val, csid->base + CSID_TOP_IRQ_CLEAR);
reset_done = val & TOP_IRQ_STATUS_RESET_DONE;
val = readl(csid->base + CSID_CSI2_RX_IRQ_STATUS);
writel(val, csid->base + CSID_CSI2_RX_IRQ_CLEAR);
buf_done_val = readl(csid->base + CSID_BUF_DONE_IRQ_STATUS);
writel(buf_done_val, csid->base + CSID_BUF_DONE_IRQ_CLEAR);
/* Read and clear IRQ status for each enabled RDI channel */
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++)
if (csid->phy.en_vc & BIT(i)) {
val = readl(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i));
writel(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i));
if (val & RUP_DONE_IRQ_STATUS)
/* clear the reg update bit */
csid_subdev_reg_update(csid, i, true);
if (buf_done_val & BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i)) {
/*
* For Titan Gen3, bus done and RUP IRQ have been moved to
* CSID from VFE. Once CSID received bus done, need notify
* VFE of this event. Trigger VFE to handle bus done process.
*/
camss_buf_done(csid->camss, csid->id, i);
}
}
val = IRQ_CMD_CLEAR;
writel(val, csid->base + CSID_IRQ_CMD);
if (reset_done)
complete(&csid->reset_complete);
return IRQ_HANDLED;
}
/*
* csid_reset - Trigger reset on CSID module and wait to complete
* @csid: CSID device
*
* Return 0 on success or a negative error code otherwise
*/
static int csid_reset(struct csid_device *csid)
{
unsigned long time;
u32 val;
int i;
reinit_completion(&csid->reset_complete);
writel(1, csid->base + CSID_TOP_IRQ_CLEAR);
writel(1, csid->base + CSID_IRQ_CMD);
writel(1, csid->base + CSID_TOP_IRQ_MASK);
for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++)
if (csid->phy.en_vc & BIT(i)) {
writel(BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i),
csid->base + CSID_BUF_DONE_IRQ_CLEAR);
writel(IRQ_CMD_CLEAR, csid->base + CSID_IRQ_CMD);
writel(BIT(BUF_DONE_IRQ_STATUS_RDI_OFFSET + i),
csid->base + CSID_BUF_DONE_IRQ_MASK);
}
/* preserve registers */
val = RST_LOCATION | RST_MODE;
writel(val, csid->base + CSID_RST_CFG);
val = SELECT_HW_RST | SELECT_IRQ_RST;
writel(val, csid->base + CSID_RST_CMD);
time = wait_for_completion_timeout(&csid->reset_complete,
msecs_to_jiffies(CSID_RESET_TIMEOUT_MS));
if (!time) {
dev_err(csid->camss->dev, "CSID reset timeout\n");
return -EIO;
}
return 0;
}
static void csid_subdev_init(struct csid_device *csid)
{
csid->testgen.nmodes = CSID_PAYLOAD_MODE_DISABLED;
}
const struct csid_hw_ops csid_ops_gen3 = {
.configure_stream = csid_configure_stream,
.configure_testgen_pattern = csid_configure_testgen_pattern,
.hw_version = csid_hw_version,
.isr = csid_isr,
.reset = csid_reset,
.src_pad_code = csid_src_pad_code,
.subdev_init = csid_subdev_init,
.reg_update = csid_subdev_reg_update,
};
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-csid-gen3.h
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module Generation 3
*
* Copyright (c) 2024 Qualcomm Technologies, Inc.
*/
#ifndef __QC_MSM_CAMSS_CSID_GEN3_H__
#define __QC_MSM_CAMSS_CSID_GEN3_H__
#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1
#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2
#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3
#define DECODE_FORMAT_UNCOMPRESSED_14_BIT 0x4
#define DECODE_FORMAT_UNCOMPRESSED_16_BIT 0x5
#define DECODE_FORMAT_UNCOMPRESSED_20_BIT 0x6
#define DECODE_FORMAT_UNCOMPRESSED_24_BIT 0x7
#define DECODE_FORMAT_PAYLOAD_ONLY 0xf
#define PLAIN_FORMAT_PLAIN8 0x0 /* supports DPCM, UNCOMPRESSED_6/8_BIT */
#define PLAIN_FORMAT_PLAIN16 0x1 /* supports DPCM, UNCOMPRESSED_10/16_BIT */
#define PLAIN_FORMAT_PLAIN32 0x2 /* supports UNCOMPRESSED_20_BIT */
#endif /* __QC_MSM_CAMSS_CSID_GEN3_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,251 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-csid.h
*
* Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module
*
* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_CSID_H
#define QC_MSM_CAMSS_CSID_H
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>
#define MSM_CSID_PAD_SINK 0
#define MSM_CSID_PAD_FIRST_SRC 1
#define MSM_CSID_PADS_NUM 5
#define MSM_CSID_PAD_SRC (MSM_CSID_PAD_FIRST_SRC)
/* CSID hardware can demultiplex up to 4 outputs */
#define MSM_CSID_MAX_SRC_STREAMS 4
#define CSID_RESET_TIMEOUT_MS 500
enum csid_testgen_mode {
CSID_PAYLOAD_MODE_DISABLED = 0,
CSID_PAYLOAD_MODE_INCREMENTING = 1,
CSID_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
CSID_PAYLOAD_MODE_ALL_ZEROES = 3,
CSID_PAYLOAD_MODE_ALL_ONES = 4,
CSID_PAYLOAD_MODE_RANDOM = 5,
CSID_PAYLOAD_MODE_USER_SPECIFIED = 6,
CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 6, /* excluding disabled */
CSID_PAYLOAD_MODE_COMPLEX_PATTERN = 7,
CSID_PAYLOAD_MODE_COLOR_BOX = 8,
CSID_PAYLOAD_MODE_COLOR_BARS = 9,
CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN2 = 9, /* excluding disabled */
};
struct csid_format_info {
u32 code;
u8 data_type;
u8 decode_format;
u8 bpp;
u8 spp; /* bus samples per pixel */
};
struct csid_formats {
unsigned int nformats;
const struct csid_format_info *formats;
};
struct csid_testgen_config {
enum csid_testgen_mode mode;
const char * const*modes;
u8 nmodes;
u8 enabled;
};
struct csid_phy_config {
u8 csiphy_id;
u8 lane_cnt;
u32 lane_assign;
u32 en_vc;
u8 need_vc_update;
};
struct csid_device;
struct csid_hw_ops {
/*
* configure_stream - Configures and starts CSID input stream
* @csid: CSID device
*/
void (*configure_stream)(struct csid_device *csid, u8 enable);
/*
* configure_testgen_pattern - Validates and configures output pattern mode
* of test pattern generator
* @csid: CSID device
*/
int (*configure_testgen_pattern)(struct csid_device *csid, s32 val);
/*
* hw_version - Read hardware version register from hardware
* @csid: CSID device
*/
u32 (*hw_version)(struct csid_device *csid);
/*
* isr - CSID module interrupt service routine
* @irq: Interrupt line
* @dev: CSID device
*
* Return IRQ_HANDLED on success
*/
irqreturn_t (*isr)(int irq, void *dev);
/*
* reset - Trigger reset on CSID module and wait to complete
* @csid: CSID device
*
* Return 0 on success or a negative error code otherwise
*/
int (*reset)(struct csid_device *csid);
/*
* src_pad_code - Pick an output/src format based on the input/sink format
* @csid: CSID device
* @sink_code: The sink format of the input
* @match_format_idx: Request preferred index, as defined by subdevice csid_format.
* Set @match_code to 0 if used.
* @match_code: Request preferred code, set @match_format_idx to 0 if used
*
* Return 0 on failure or src format code otherwise
*/
u32 (*src_pad_code)(struct csid_device *csid, u32 sink_code,
unsigned int match_format_idx, u32 match_code);
/*
* subdev_init - Initialize CSID device according for hardware revision
* @csid: CSID device
*/
void (*subdev_init)(struct csid_device *csid);
/*
* reg_update - receive message from other sub device
* @csid: CSID device
* @port_id: Port id
* @is_clear: Indicate if it is clearing reg update or setting reg update
*/
void (*reg_update)(struct csid_device *csid, int port_id, bool is_clear);
};
struct csid_subdev_resources {
bool is_lite;
const struct csid_hw_ops *hw_ops;
const struct parent_dev_ops *parent_dev_ops;
const struct csid_formats *formats;
};
struct csid_device {
struct camss *camss;
u8 id;
struct v4l2_subdev subdev;
struct media_pad pads[MSM_CSID_PADS_NUM];
void __iomem *base;
u32 irq;
char irq_name[30];
u32 reg_update;
struct camss_clock *clock;
int nclocks;
struct regulator_bulk_data *supplies;
int num_supplies;
struct completion reset_complete;
struct csid_testgen_config testgen;
bool tpg_linked;
struct csid_phy_config phy;
struct v4l2_mbus_framefmt fmt[MSM_CSID_PADS_NUM];
struct v4l2_ctrl_handler ctrls;
struct v4l2_ctrl *testgen_mode;
const struct csid_subdev_resources *res;
};
struct camss_subdev_resources;
/*
* csid_find_code - Find a format code in an array using array index or format code
* @codes: Array of format codes
* @ncodes: Length of @code array
* @req_format_idx: Request preferred index, as defined by subdevice csid_format.
* Set @match_code to 0 if used.
* @match_code: Request preferred code, set @req_format_idx to 0 if used
*
* Return 0 on failure or format code otherwise
*/
u32 csid_find_code(u32 *codes, unsigned int ncode,
unsigned int match_format_idx, u32 match_code);
/*
* csid_get_fmt_entry - Find csid_format_info entry with matching format code
* @formats: Array of format csid_format_info entries
* @nformats: Length of @nformats array
* @code: Desired format code
*
* Return formats[0] on failure to find code
*/
const struct csid_format_info *csid_get_fmt_entry(const struct csid_format_info *formats,
unsigned int nformats,
u32 code);
int msm_csid_subdev_init(struct camss *camss, struct csid_device *csid,
const struct camss_subdev_resources *res, u8 id);
int msm_csid_register_entity(struct csid_device *csid,
struct v4l2_device *v4l2_dev);
void msm_csid_unregister_entity(struct csid_device *csid);
void msm_csid_get_csid_id(struct media_entity *entity, u8 *id);
extern const char * const csid_testgen_modes[];
extern const struct csid_formats csid_formats_4_1;
extern const struct csid_formats csid_formats_4_7;
extern const struct csid_formats csid_formats_gen2;
extern const struct csid_hw_ops csid_ops_4_1;
extern const struct csid_hw_ops csid_ops_4_7;
extern const struct csid_hw_ops csid_ops_340;
extern const struct csid_hw_ops csid_ops_680;
extern const struct csid_hw_ops csid_ops_gen2;
extern const struct csid_hw_ops csid_ops_gen3;
/*
* csid_is_lite - Check if CSID is CSID lite.
* @csid: CSID Device
*
* Return whether CSID is CSID lite
*/
bool csid_is_lite(struct csid_device *csid);
/*
* csid_hw_version - CSID hardware version query
* @csid: CSID device
*
* Return HW version or error
*/
u32 csid_hw_version(struct csid_device *csid);
/*
* csid_src_pad_code - Pick an output/src format based on the input/sink format
* @csid: CSID device
* @sink_code: The sink format of the input
* @match_format_idx: Request preferred index, as defined by subdevice csid
* format. Set @match_code to 0 if used.
* @match_code: Request preferred code, set @match_format_idx to 0 if used
*
* Return 0 on failure or src format code otherwise
*/
u32 csid_src_pad_code(struct csid_device *csid, u32 sink_code,
unsigned int match_format_idx, u32 match_code);
#endif /* QC_MSM_CAMSS_CSID_H */
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-csiphy-2ph-1-0.c
*
* Qualcomm MSM Camera Subsystem - CSIPHY Module 2phase v1.0
*
* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2016-2018 Linaro Ltd.
*/
#include "camss-csiphy.h"
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#define CAMSS_CSI_PHY_LNn_CFG2(n) (0x004 + 0x40 * (n))
#define CAMSS_CSI_PHY_LNn_CFG3(n) (0x008 + 0x40 * (n))
#define CAMSS_CSI_PHY_LN_CLK 1
#define CAMSS_CSI_PHY_GLBL_RESET 0x140
#define CAMSS_CSI_PHY_GLBL_PWR_CFG 0x144
#define CAMSS_CSI_PHY_GLBL_IRQ_CMD 0x164
#define CAMSS_CSI_PHY_HW_VERSION 0x188
#define CAMSS_CSI_PHY_INTERRUPT_STATUSn(n) (0x18c + 0x4 * (n))
#define CAMSS_CSI_PHY_INTERRUPT_MASKn(n) (0x1ac + 0x4 * (n))
#define CAMSS_CSI_PHY_INTERRUPT_CLEARn(n) (0x1cc + 0x4 * (n))
#define CAMSS_CSI_PHY_GLBL_T_INIT_CFG0 0x1ec
#define CAMSS_CSI_PHY_T_WAKEUP_CFG0 0x1f4
static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg)
{
u8 lane_mask;
int i;
lane_mask = 1 << CAMSS_CSI_PHY_LN_CLK;
for (i = 0; i < lane_cfg->num_data; i++)
lane_mask |= 1 << lane_cfg->data[i].pos;
return lane_mask;
}
static void csiphy_hw_version_read(struct csiphy_device *csiphy,
struct device *dev)
{
u8 hw_version = readl_relaxed(csiphy->base +
CAMSS_CSI_PHY_HW_VERSION);
dev_dbg(dev, "CSIPHY HW Version = 0x%02x\n", hw_version);
}
/*
* csiphy_reset - Perform software reset on CSIPHY module
* @csiphy: CSIPHY device
*/
static void csiphy_reset(struct csiphy_device *csiphy)
{
writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET);
usleep_range(5000, 8000);
writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET);
}
/*
* csiphy_settle_cnt_calc - Calculate settle count value
*
* Helper function to calculate settle count value. This is
* based on the CSI2 T_hs_settle parameter which in turn
* is calculated based on the CSI2 transmitter link frequency.
*
* Return settle count value or 0 if the CSI2 link frequency
* is not available
*/
static u8 csiphy_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate)
{
u32 ui; /* ps */
u32 timer_period; /* ps */
u32 t_hs_prepare_max; /* ps */
u32 t_hs_prepare_zero_min; /* ps */
u32 t_hs_settle; /* ps */
u8 settle_cnt;
if (link_freq <= 0)
return 0;
ui = div_u64(1000000000000LL, link_freq);
ui /= 2;
t_hs_prepare_max = 85000 + 6 * ui;
t_hs_prepare_zero_min = 145000 + 10 * ui;
t_hs_settle = (t_hs_prepare_max + t_hs_prepare_zero_min) / 2;
timer_period = div_u64(1000000000000LL, timer_clk_rate);
settle_cnt = t_hs_settle / timer_period - 1;
return settle_cnt;
}
static void csiphy_lanes_enable(struct csiphy_device *csiphy,
struct csiphy_config *cfg,
s64 link_freq, u8 lane_mask)
{
struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg;
u8 settle_cnt;
u8 val, l = 0;
int i = 0;
settle_cnt = csiphy_settle_cnt_calc(link_freq, csiphy->timer_clk_rate);
writel_relaxed(0x1, csiphy->base +
CAMSS_CSI_PHY_GLBL_T_INIT_CFG0);
writel_relaxed(0x1, csiphy->base +
CAMSS_CSI_PHY_T_WAKEUP_CFG0);
val = 0x1;
val |= lane_mask << 1;
writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG);
val = cfg->combo_mode << 4;
writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET);
for (i = 0; i <= c->num_data; i++) {
if (i == c->num_data)
l = CAMSS_CSI_PHY_LN_CLK;
else
l = c->data[i].pos;
writel_relaxed(0x10, csiphy->base +
CAMSS_CSI_PHY_LNn_CFG2(l));
writel_relaxed(settle_cnt, csiphy->base +
CAMSS_CSI_PHY_LNn_CFG3(l));
writel_relaxed(0x3f, csiphy->base +
CAMSS_CSI_PHY_INTERRUPT_MASKn(l));
writel_relaxed(0x3f, csiphy->base +
CAMSS_CSI_PHY_INTERRUPT_CLEARn(l));
}
}
static void csiphy_lanes_disable(struct csiphy_device *csiphy,
struct csiphy_config *cfg)
{
struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg;
u8 l = 0;
int i = 0;
for (i = 0; i <= c->num_data; i++) {
if (i == c->num_data)
l = CAMSS_CSI_PHY_LN_CLK;
else
l = c->data[i].pos;
writel_relaxed(0x0, csiphy->base +
CAMSS_CSI_PHY_LNn_CFG2(l));
}
writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG);
}
/*
* csiphy_isr - CSIPHY module interrupt handler
* @irq: Interrupt line
* @dev: CSIPHY device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t csiphy_isr(int irq, void *dev)
{
struct csiphy_device *csiphy = dev;
u8 i;
for (i = 0; i < 8; i++) {
u8 val = readl_relaxed(csiphy->base +
CAMSS_CSI_PHY_INTERRUPT_STATUSn(i));
writel_relaxed(val, csiphy->base +
CAMSS_CSI_PHY_INTERRUPT_CLEARn(i));
writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD);
writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD);
writel_relaxed(0x0, csiphy->base +
CAMSS_CSI_PHY_INTERRUPT_CLEARn(i));
}
return IRQ_HANDLED;
}
static int csiphy_init(struct csiphy_device *csiphy)
{
return 0;
}
const struct csiphy_hw_ops csiphy_ops_2ph_1_0 = {
.get_lane_mask = csiphy_get_lane_mask,
.hw_version_read = csiphy_hw_version_read,
.reset = csiphy_reset,
.lanes_enable = csiphy_lanes_enable,
.lanes_disable = csiphy_lanes_disable,
.isr = csiphy_isr,
.init = csiphy_init,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,833 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-csiphy.c
*
* Qualcomm MSM Camera Subsystem - CSIPHY Module
*
* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2016-2018 Linaro Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include "camss-csiphy.h"
#include "camss.h"
#define MSM_CSIPHY_NAME "msm_csiphy"
static const struct csiphy_format_info formats_8x16[] = {
{ MEDIA_BUS_FMT_UYVY8_1X16, 8 },
{ MEDIA_BUS_FMT_VYUY8_1X16, 8 },
{ MEDIA_BUS_FMT_YUYV8_1X16, 8 },
{ MEDIA_BUS_FMT_YVYU8_1X16, 8 },
{ MEDIA_BUS_FMT_SBGGR8_1X8, 8 },
{ MEDIA_BUS_FMT_SGBRG8_1X8, 8 },
{ MEDIA_BUS_FMT_SGRBG8_1X8, 8 },
{ MEDIA_BUS_FMT_SRGGB8_1X8, 8 },
{ MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
{ MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
{ MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
{ MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
{ MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
{ MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
{ MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
{ MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
{ MEDIA_BUS_FMT_Y10_1X10, 10 },
};
static const struct csiphy_format_info formats_8x96[] = {
{ MEDIA_BUS_FMT_UYVY8_1X16, 8 },
{ MEDIA_BUS_FMT_VYUY8_1X16, 8 },
{ MEDIA_BUS_FMT_YUYV8_1X16, 8 },
{ MEDIA_BUS_FMT_YVYU8_1X16, 8 },
{ MEDIA_BUS_FMT_SBGGR8_1X8, 8 },
{ MEDIA_BUS_FMT_SGBRG8_1X8, 8 },
{ MEDIA_BUS_FMT_SGRBG8_1X8, 8 },
{ MEDIA_BUS_FMT_SRGGB8_1X8, 8 },
{ MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
{ MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
{ MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
{ MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
{ MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
{ MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
{ MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
{ MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
{ MEDIA_BUS_FMT_SBGGR14_1X14, 14 },
{ MEDIA_BUS_FMT_SGBRG14_1X14, 14 },
{ MEDIA_BUS_FMT_SGRBG14_1X14, 14 },
{ MEDIA_BUS_FMT_SRGGB14_1X14, 14 },
{ MEDIA_BUS_FMT_Y10_1X10, 10 },
};
static const struct csiphy_format_info formats_sdm845[] = {
{ MEDIA_BUS_FMT_UYVY8_1X16, 8 },
{ MEDIA_BUS_FMT_VYUY8_1X16, 8 },
{ MEDIA_BUS_FMT_YUYV8_1X16, 8 },
{ MEDIA_BUS_FMT_YVYU8_1X16, 8 },
{ MEDIA_BUS_FMT_SBGGR8_1X8, 8 },
{ MEDIA_BUS_FMT_SGBRG8_1X8, 8 },
{ MEDIA_BUS_FMT_SGRBG8_1X8, 8 },
{ MEDIA_BUS_FMT_SRGGB8_1X8, 8 },
{ MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
{ MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
{ MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
{ MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
{ MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
{ MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
{ MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
{ MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
{ MEDIA_BUS_FMT_SBGGR14_1X14, 14 },
{ MEDIA_BUS_FMT_SGBRG14_1X14, 14 },
{ MEDIA_BUS_FMT_SGRBG14_1X14, 14 },
{ MEDIA_BUS_FMT_SRGGB14_1X14, 14 },
{ MEDIA_BUS_FMT_Y8_1X8, 8 },
{ MEDIA_BUS_FMT_Y10_1X10, 10 },
};
const struct csiphy_formats csiphy_formats_8x16 = {
.nformats = ARRAY_SIZE(formats_8x16),
.formats = formats_8x16
};
const struct csiphy_formats csiphy_formats_8x96 = {
.nformats = ARRAY_SIZE(formats_8x96),
.formats = formats_8x96
};
const struct csiphy_formats csiphy_formats_sdm845 = {
.nformats = ARRAY_SIZE(formats_sdm845),
.formats = formats_sdm845
};
/*
* csiphy_get_bpp - map media bus format to bits per pixel
* @formats: supported media bus formats array
* @nformats: size of @formats array
* @code: media bus format code
*
* Return number of bits per pixel
*/
static u8 csiphy_get_bpp(const struct csiphy_format_info *formats,
unsigned int nformats, u32 code)
{
unsigned int i;
for (i = 0; i < nformats; i++)
if (code == formats[i].code)
return formats[i].bpp;
WARN(1, "Unknown format\n");
return formats[0].bpp;
}
/*
* csiphy_set_clock_rates - Calculate and set clock rates on CSIPHY module
* @csiphy: CSIPHY device
*/
static int csiphy_set_clock_rates(struct csiphy_device *csiphy)
{
struct device *dev = csiphy->camss->dev;
s64 link_freq;
int i, j;
int ret;
u8 bpp = csiphy_get_bpp(csiphy->res->formats->formats, csiphy->res->formats->nformats,
csiphy->fmt[MSM_CSIPHY_PAD_SINK].code);
u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data;
link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes);
if (link_freq < 0)
link_freq = 0;
for (i = 0; i < csiphy->nclocks; i++) {
struct camss_clock *clock = &csiphy->clock[i];
if (csiphy->rate_set[i]) {
u64 min_rate = link_freq / 4;
long round_rate;
camss_add_clock_margin(&min_rate);
for (j = 0; j < clock->nfreqs; j++)
if (min_rate < clock->freq[j])
break;
if (j == clock->nfreqs) {
dev_err(dev,
"Pixel clock is too high for CSIPHY\n");
return -EINVAL;
}
/* if sensor pixel clock is not available */
/* set highest possible CSIPHY clock rate */
if (min_rate == 0)
j = clock->nfreqs - 1;
round_rate = clk_round_rate(clock->clk, clock->freq[j]);
if (round_rate < 0) {
dev_err(dev, "clk round rate failed: %ld\n",
round_rate);
return -EINVAL;
}
csiphy->timer_clk_rate = round_rate;
ret = clk_set_rate(clock->clk, csiphy->timer_clk_rate);
if (ret < 0) {
dev_err(dev, "clk set rate failed: %d\n", ret);
return ret;
}
}
}
return 0;
}
/*
* csiphy_set_power - Power on/off CSIPHY module
* @sd: CSIPHY V4L2 subdevice
* @on: Requested power state
*
* Return 0 on success or a negative error code otherwise
*/
static int csiphy_set_power(struct v4l2_subdev *sd, int on)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
struct device *dev = csiphy->camss->dev;
if (on) {
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
ret = regulator_bulk_enable(csiphy->num_supplies,
csiphy->supplies);
if (ret < 0) {
pm_runtime_put_sync(dev);
return ret;
}
ret = csiphy_set_clock_rates(csiphy);
if (ret < 0) {
regulator_bulk_disable(csiphy->num_supplies,
csiphy->supplies);
pm_runtime_put_sync(dev);
return ret;
}
ret = camss_enable_clocks(csiphy->nclocks, csiphy->clock, dev);
if (ret < 0) {
regulator_bulk_disable(csiphy->num_supplies,
csiphy->supplies);
pm_runtime_put_sync(dev);
return ret;
}
enable_irq(csiphy->irq);
csiphy->res->hw_ops->reset(csiphy);
csiphy->res->hw_ops->hw_version_read(csiphy, dev);
} else {
disable_irq(csiphy->irq);
camss_disable_clocks(csiphy->nclocks, csiphy->clock);
regulator_bulk_disable(csiphy->num_supplies, csiphy->supplies);
pm_runtime_put_sync(dev);
}
return 0;
}
/*
* csiphy_stream_on - Enable streaming on CSIPHY module
* @csiphy: CSIPHY device
*
* Helper function to enable streaming on CSIPHY module.
* Main configuration of CSIPHY module is also done here.
*
* Return 0 on success or a negative error code otherwise
*/
static int csiphy_stream_on(struct csiphy_device *csiphy)
{
struct csiphy_config *cfg = &csiphy->cfg;
s64 link_freq;
u8 lane_mask = csiphy->res->hw_ops->get_lane_mask(&cfg->csi2->lane_cfg);
u8 bpp = csiphy_get_bpp(csiphy->res->formats->formats, csiphy->res->formats->nformats,
csiphy->fmt[MSM_CSIPHY_PAD_SINK].code);
u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data;
u8 val;
link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes);
if (link_freq < 0) {
dev_err(csiphy->camss->dev,
"Cannot get CSI2 transmitter's link frequency\n");
return -EINVAL;
}
if (csiphy->base_clk_mux) {
val = readl_relaxed(csiphy->base_clk_mux);
if (cfg->combo_mode && (lane_mask & 0x18) == 0x18) {
val &= ~0xf0;
val |= cfg->csid_id << 4;
} else {
val &= ~0xf;
val |= cfg->csid_id;
}
writel_relaxed(val, csiphy->base_clk_mux);
/* Enforce reg write ordering between clk mux & lane enabling */
wmb();
}
csiphy->res->hw_ops->lanes_enable(csiphy, cfg, link_freq, lane_mask);
return 0;
}
/*
* csiphy_stream_off - Disable streaming on CSIPHY module
* @csiphy: CSIPHY device
*
* Helper function to disable streaming on CSIPHY module
*/
static void csiphy_stream_off(struct csiphy_device *csiphy)
{
csiphy->res->hw_ops->lanes_disable(csiphy, &csiphy->cfg);
}
/*
* csiphy_set_stream - Enable/disable streaming on CSIPHY module
* @sd: CSIPHY V4L2 subdevice
* @enable: Requested streaming state
*
* Return 0 on success or a negative error code otherwise
*/
static int csiphy_set_stream(struct v4l2_subdev *sd, int enable)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
int ret = 0;
if (enable)
ret = csiphy_stream_on(csiphy);
else
csiphy_stream_off(csiphy);
return ret;
}
/*
* __csiphy_get_format - Get pointer to format structure
* @csiphy: CSIPHY device
* @sd_state: V4L2 subdev state
* @pad: pad from which format is requested
* @which: TRY or ACTIVE format
*
* Return pointer to TRY or ACTIVE format structure
*/
static struct v4l2_mbus_framefmt *
__csiphy_get_format(struct csiphy_device *csiphy,
struct v4l2_subdev_state *sd_state,
unsigned int pad,
enum v4l2_subdev_format_whence which)
{
if (which == V4L2_SUBDEV_FORMAT_TRY)
return v4l2_subdev_state_get_format(sd_state, pad);
return &csiphy->fmt[pad];
}
/*
* csiphy_try_format - Handle try format by pad subdev method
* @csiphy: CSIPHY device
* @sd_state: V4L2 subdev state
* @pad: pad on which format is requested
* @fmt: pointer to v4l2 format structure
* @which: wanted subdev format
*/
static void csiphy_try_format(struct csiphy_device *csiphy,
struct v4l2_subdev_state *sd_state,
unsigned int pad,
struct v4l2_mbus_framefmt *fmt,
enum v4l2_subdev_format_whence which)
{
unsigned int i;
switch (pad) {
case MSM_CSIPHY_PAD_SINK:
/* Set format on sink pad */
for (i = 0; i < csiphy->res->formats->nformats; i++)
if (fmt->code == csiphy->res->formats->formats[i].code)
break;
/* If not found, use UYVY as default */
if (i >= csiphy->res->formats->nformats)
fmt->code = MEDIA_BUS_FMT_UYVY8_1X16;
fmt->width = clamp_t(u32, fmt->width, 1, 8191);
fmt->height = clamp_t(u32, fmt->height, 1, 8191);
fmt->field = V4L2_FIELD_NONE;
fmt->colorspace = V4L2_COLORSPACE_SRGB;
break;
case MSM_CSIPHY_PAD_SRC:
/* Set and return a format same as sink pad */
*fmt = *__csiphy_get_format(csiphy, sd_state,
MSM_CSID_PAD_SINK,
which);
break;
}
}
/*
* csiphy_enum_mbus_code - Handle pixel format enumeration
* @sd: CSIPHY V4L2 subdevice
* @sd_state: V4L2 subdev state
* @code: pointer to v4l2_subdev_mbus_code_enum structure
* return -EINVAL or zero on success
*/
static int csiphy_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *format;
if (code->pad == MSM_CSIPHY_PAD_SINK) {
if (code->index >= csiphy->res->formats->nformats)
return -EINVAL;
code->code = csiphy->res->formats->formats[code->index].code;
} else {
if (code->index > 0)
return -EINVAL;
format = __csiphy_get_format(csiphy, sd_state,
MSM_CSIPHY_PAD_SINK,
code->which);
code->code = format->code;
}
return 0;
}
/*
* csiphy_enum_frame_size - Handle frame size enumeration
* @sd: CSIPHY V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fse: pointer to v4l2_subdev_frame_size_enum structure
* return -EINVAL or zero on success
*/
static int csiphy_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt format;
if (fse->index != 0)
return -EINVAL;
format.code = fse->code;
format.width = 1;
format.height = 1;
csiphy_try_format(csiphy, sd_state, fse->pad, &format, fse->which);
fse->min_width = format.width;
fse->min_height = format.height;
if (format.code != fse->code)
return -EINVAL;
format.code = fse->code;
format.width = -1;
format.height = -1;
csiphy_try_format(csiphy, sd_state, fse->pad, &format, fse->which);
fse->max_width = format.width;
fse->max_height = format.height;
return 0;
}
/*
* csiphy_get_format - Handle get format by pads subdev method
* @sd: CSIPHY V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fmt: pointer to v4l2 subdev format structure
*
* Return -EINVAL or zero on success
*/
static int csiphy_get_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *format;
format = __csiphy_get_format(csiphy, sd_state, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
fmt->format = *format;
return 0;
}
/*
* csiphy_set_format - Handle set format by pads subdev method
* @sd: CSIPHY V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fmt: pointer to v4l2 subdev format structure
*
* Return -EINVAL or zero on success
*/
static int csiphy_set_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *format;
format = __csiphy_get_format(csiphy, sd_state, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
csiphy_try_format(csiphy, sd_state, fmt->pad, &fmt->format,
fmt->which);
*format = fmt->format;
/* Propagate the format from sink to source */
if (fmt->pad == MSM_CSIPHY_PAD_SINK) {
format = __csiphy_get_format(csiphy, sd_state,
MSM_CSIPHY_PAD_SRC,
fmt->which);
*format = fmt->format;
csiphy_try_format(csiphy, sd_state, MSM_CSIPHY_PAD_SRC,
format,
fmt->which);
}
return 0;
}
/*
* csiphy_init_formats - Initialize formats on all pads
* @sd: CSIPHY V4L2 subdevice
* @fh: V4L2 subdev file handle
*
* Initialize all pad formats with default values.
*
* Return 0 on success or a negative error code otherwise
*/
static int csiphy_init_formats(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh)
{
struct v4l2_subdev_format format = {
.pad = MSM_CSIPHY_PAD_SINK,
.which = fh ? V4L2_SUBDEV_FORMAT_TRY :
V4L2_SUBDEV_FORMAT_ACTIVE,
.format = {
.code = MEDIA_BUS_FMT_UYVY8_1X16,
.width = 1920,
.height = 1080
}
};
return csiphy_set_format(sd, fh ? fh->state : NULL, &format);
}
static bool __printf(2, 3)
csiphy_match_clock_name(const char *clock_name, const char *format, ...)
{
char name[16]; /* csiphyXXX_timer\0 */
va_list args;
va_start(args, format);
vsnprintf(name, sizeof(name), format, args);
va_end(args);
return !strcmp(clock_name, name);
}
/*
* msm_csiphy_subdev_init - Initialize CSIPHY device structure and resources
* @csiphy: CSIPHY device
* @res: CSIPHY module resources table
* @id: CSIPHY module id
*
* Return 0 on success or a negative error code otherwise
*/
int msm_csiphy_subdev_init(struct camss *camss,
struct csiphy_device *csiphy,
const struct camss_subdev_resources *res, u8 id)
{
struct device *dev = camss->dev;
struct platform_device *pdev = to_platform_device(dev);
int i, j;
int ret;
csiphy->camss = camss;
csiphy->id = id;
csiphy->cfg.combo_mode = 0;
csiphy->res = &res->csiphy;
ret = csiphy->res->hw_ops->init(csiphy);
if (ret)
return ret;
/* Memory */
csiphy->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]);
if (IS_ERR(csiphy->base))
return PTR_ERR(csiphy->base);
if (camss->res->version == CAMSS_8x16 ||
camss->res->version == CAMSS_8x39 ||
camss->res->version == CAMSS_8x53 ||
camss->res->version == CAMSS_8x96) {
csiphy->base_clk_mux =
devm_platform_ioremap_resource_byname(pdev, res->reg[1]);
if (IS_ERR(csiphy->base_clk_mux))
return PTR_ERR(csiphy->base_clk_mux);
} else {
csiphy->base_clk_mux = NULL;
}
/* Interrupt */
ret = platform_get_irq_byname(pdev, res->interrupt[0]);
if (ret < 0)
return ret;
csiphy->irq = ret;
snprintf(csiphy->irq_name, sizeof(csiphy->irq_name), "%s_%s%d",
dev_name(dev), MSM_CSIPHY_NAME, csiphy->id);
ret = devm_request_irq(dev, csiphy->irq, csiphy->res->hw_ops->isr,
IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN,
csiphy->irq_name, csiphy);
if (ret < 0) {
dev_err(dev, "request_irq failed: %d\n", ret);
return ret;
}
/* Clocks */
csiphy->nclocks = 0;
while (res->clock[csiphy->nclocks])
csiphy->nclocks++;
csiphy->clock = devm_kcalloc(dev,
csiphy->nclocks, sizeof(*csiphy->clock),
GFP_KERNEL);
if (!csiphy->clock)
return -ENOMEM;
csiphy->rate_set = devm_kcalloc(dev,
csiphy->nclocks,
sizeof(*csiphy->rate_set),
GFP_KERNEL);
if (!csiphy->rate_set)
return -ENOMEM;
for (i = 0; i < csiphy->nclocks; i++) {
struct camss_clock *clock = &csiphy->clock[i];
clock->clk = devm_clk_get(dev, res->clock[i]);
if (IS_ERR(clock->clk))
return PTR_ERR(clock->clk);
clock->name = res->clock[i];
clock->nfreqs = 0;
while (res->clock_rate[i][clock->nfreqs])
clock->nfreqs++;
if (!clock->nfreqs) {
clock->freq = NULL;
continue;
}
clock->freq = devm_kcalloc(dev,
clock->nfreqs,
sizeof(*clock->freq),
GFP_KERNEL);
if (!clock->freq)
return -ENOMEM;
for (j = 0; j < clock->nfreqs; j++)
clock->freq[j] = res->clock_rate[i][j];
csiphy->rate_set[i] = csiphy_match_clock_name(clock->name,
"csiphy%d_timer",
csiphy->id);
if (csiphy->rate_set[i])
continue;
if (camss->res->version == CAMSS_660) {
csiphy->rate_set[i] = csiphy_match_clock_name(clock->name,
"csi%d_phy",
csiphy->id);
if (csiphy->rate_set[i])
continue;
}
csiphy->rate_set[i] = csiphy_match_clock_name(clock->name, "csiphy%d", csiphy->id);
}
/* CSIPHY supplies */
for (i = 0; i < ARRAY_SIZE(res->regulators); i++) {
if (res->regulators[i].supply)
csiphy->num_supplies++;
}
if (csiphy->num_supplies > 0)
ret = devm_regulator_bulk_get_const(camss->dev, csiphy->num_supplies,
res->regulators, &csiphy->supplies);
return ret;
}
/*
* csiphy_link_setup - Setup CSIPHY connections
* @entity: Pointer to media entity structure
* @local: Pointer to local pad
* @remote: Pointer to remote pad
* @flags: Link flags
*
* Rreturn 0 on success
*/
static int csiphy_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
if ((local->flags & MEDIA_PAD_FL_SOURCE) &&
(flags & MEDIA_LNK_FL_ENABLED)) {
struct v4l2_subdev *sd;
struct csiphy_device *csiphy;
struct csid_device *csid;
if (media_pad_remote_pad_first(local))
return -EBUSY;
sd = media_entity_to_v4l2_subdev(entity);
csiphy = v4l2_get_subdevdata(sd);
sd = media_entity_to_v4l2_subdev(remote->entity);
csid = v4l2_get_subdevdata(sd);
csiphy->cfg.csid_id = csid->id;
}
return 0;
}
static const struct v4l2_subdev_core_ops csiphy_core_ops = {
.s_power = csiphy_set_power,
};
static const struct v4l2_subdev_video_ops csiphy_video_ops = {
.s_stream = csiphy_set_stream,
};
static const struct v4l2_subdev_pad_ops csiphy_pad_ops = {
.enum_mbus_code = csiphy_enum_mbus_code,
.enum_frame_size = csiphy_enum_frame_size,
.get_fmt = csiphy_get_format,
.set_fmt = csiphy_set_format,
};
static const struct v4l2_subdev_ops csiphy_v4l2_ops = {
.core = &csiphy_core_ops,
.video = &csiphy_video_ops,
.pad = &csiphy_pad_ops,
};
static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = {
.open = csiphy_init_formats,
};
static const struct media_entity_operations csiphy_media_ops = {
.link_setup = csiphy_link_setup,
.link_validate = v4l2_subdev_link_validate,
};
/*
* msm_csiphy_register_entity - Register subdev node for CSIPHY module
* @csiphy: CSIPHY device
* @v4l2_dev: V4L2 device
*
* Return 0 on success or a negative error code otherwise
*/
int msm_csiphy_register_entity(struct csiphy_device *csiphy,
struct v4l2_device *v4l2_dev)
{
struct v4l2_subdev *sd = &csiphy->subdev;
struct media_pad *pads = csiphy->pads;
struct device *dev = csiphy->camss->dev;
int ret;
v4l2_subdev_init(sd, &csiphy_v4l2_ops);
sd->internal_ops = &csiphy_v4l2_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
MSM_CSIPHY_NAME, csiphy->id);
sd->grp_id = CSIPHY_GRP_ID;
v4l2_set_subdevdata(sd, csiphy);
ret = csiphy_init_formats(sd, NULL);
if (ret < 0) {
dev_err(dev, "Failed to init format: %d\n", ret);
return ret;
}
pads[MSM_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
pads[MSM_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
sd->entity.ops = &csiphy_media_ops;
ret = media_entity_pads_init(&sd->entity, MSM_CSIPHY_PADS_NUM, pads);
if (ret < 0) {
dev_err(dev, "Failed to init media entity: %d\n", ret);
return ret;
}
ret = v4l2_device_register_subdev(v4l2_dev, sd);
if (ret < 0) {
dev_err(dev, "Failed to register subdev: %d\n", ret);
media_entity_cleanup(&sd->entity);
}
return ret;
}
/*
* msm_csiphy_unregister_entity - Unregister CSIPHY module subdev node
* @csiphy: CSIPHY device
*/
void msm_csiphy_unregister_entity(struct csiphy_device *csiphy)
{
v4l2_device_unregister_subdev(&csiphy->subdev);
media_entity_cleanup(&csiphy->subdev.entity);
}
@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-csiphy.h
*
* Qualcomm MSM Camera Subsystem - CSIPHY Module
*
* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2016-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_CSIPHY_H
#define QC_MSM_CAMSS_CSIPHY_H
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>
#define MSM_CSIPHY_PAD_SINK 0
#define MSM_CSIPHY_PAD_SRC 1
#define MSM_CSIPHY_PADS_NUM 2
#define CSIPHY_GRP_ID 1
struct csiphy_lane {
u8 pos;
u8 pol;
};
/**
* struct csiphy_lanes_cfg - CSIPHY lanes configuration
* @num_data: number of data lanes
* @data: data lanes configuration
* @clk: clock lane configuration (only for D-PHY)
*/
struct csiphy_lanes_cfg {
int num_data;
struct csiphy_lane *data;
struct csiphy_lane clk;
};
struct csiphy_csi2_cfg {
struct csiphy_lanes_cfg lane_cfg;
};
struct csiphy_config {
u8 combo_mode;
u8 csid_id;
struct csiphy_csi2_cfg *csi2;
};
struct csiphy_format_info {
u32 code;
u8 bpp;
};
struct csiphy_formats {
unsigned int nformats;
const struct csiphy_format_info *formats;
};
struct csiphy_device;
struct csiphy_hw_ops {
/*
* csiphy_get_lane_mask - Calculate CSI2 lane mask configuration parameter
* @lane_cfg - CSI2 lane configuration
*
* Return lane mask
*/
u8 (*get_lane_mask)(struct csiphy_lanes_cfg *lane_cfg);
void (*hw_version_read)(struct csiphy_device *csiphy,
struct device *dev);
void (*reset)(struct csiphy_device *csiphy);
void (*lanes_enable)(struct csiphy_device *csiphy,
struct csiphy_config *cfg,
s64 link_freq, u8 lane_mask);
void (*lanes_disable)(struct csiphy_device *csiphy,
struct csiphy_config *cfg);
irqreturn_t (*isr)(int irq, void *dev);
int (*init)(struct csiphy_device *csiphy);
};
struct csiphy_subdev_resources {
u8 id;
const struct csiphy_hw_ops *hw_ops;
const struct csiphy_formats *formats;
};
struct csiphy_device_regs {
const struct csiphy_lane_regs *lane_regs;
int lane_array_size;
u32 offset;
u32 common_status_offset;
};
struct csiphy_device {
struct camss *camss;
u8 id;
struct v4l2_subdev subdev;
struct media_pad pads[MSM_CSIPHY_PADS_NUM];
void __iomem *base;
void __iomem *base_clk_mux;
u32 irq;
char irq_name[30];
struct camss_clock *clock;
bool *rate_set;
int nclocks;
u32 timer_clk_rate;
struct regulator_bulk_data *supplies;
int num_supplies;
struct csiphy_config cfg;
struct v4l2_mbus_framefmt fmt[MSM_CSIPHY_PADS_NUM];
const struct csiphy_subdev_resources *res;
struct csiphy_device_regs *regs;
};
struct camss_subdev_resources;
int msm_csiphy_subdev_init(struct camss *camss,
struct csiphy_device *csiphy,
const struct camss_subdev_resources *res, u8 id);
int msm_csiphy_register_entity(struct csiphy_device *csiphy,
struct v4l2_device *v4l2_dev);
void msm_csiphy_unregister_entity(struct csiphy_device *csiphy);
extern const struct csiphy_formats csiphy_formats_8x16;
extern const struct csiphy_formats csiphy_formats_8x96;
extern const struct csiphy_formats csiphy_formats_sdm845;
extern const struct csiphy_hw_ops csiphy_ops_2ph_1_0;
extern const struct csiphy_hw_ops csiphy_ops_3ph_1_0;
#endif /* QC_MSM_CAMSS_CSIPHY_H */
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-format.c
*
* Qualcomm MSM Camera Subsystem - Format helpers
*
* Copyright (c) 2023, The Linux Foundation. All rights reserved.
* Copyright (c) 2023 Qualcomm Technologies, Inc.
*/
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/errno.h>
#include <linux/lcm.h>
#include "camss-format.h"
/*
* camss_format_get_bpp - Map media bus format to bits per pixel
* @formats: supported media bus formats array
* @nformats: size of @formats array
* @code: media bus format code
*
* Return number of bits per pixel
*/
u8 camss_format_get_bpp(const struct camss_format_info *formats, unsigned int nformats, u32 code)
{
unsigned int i;
for (i = 0; i < nformats; i++)
if (code == formats[i].code)
return formats[i].mbus_bpp;
WARN(1, "Unknown format\n");
return formats[0].mbus_bpp;
}
/*
* camss_format_get_bpl_alignment - Retrieve required BPL alignment for a given format.
* @format: a pointer to the format
*
* Return the required alignment, in bytes.
*/
unsigned int camss_format_get_bpl_alignment(const struct camss_format_info *format)
{
/* Minimal number of bytes required to keep the line length an integer number of pixels */
return lcm_not_zero(format->mbus_bpp, BITS_PER_BYTE) / BITS_PER_BYTE;
}
/*
* camss_format_find_code - Find a format code in an array
* @code: a pointer to media bus format codes array
* @n_code: size of @code array
* @index: index of code in the array
* @req_code: required code
*
* Return media bus format code
*/
u32 camss_format_find_code(u32 *code, unsigned int n_code, unsigned int index, u32 req_code)
{
unsigned int i;
if (!req_code && index >= n_code)
return 0;
for (i = 0; i < n_code; i++) {
if (req_code) {
if (req_code == code[i])
return req_code;
} else {
if (i == index)
return code[i];
}
}
return code[0];
}
/*
* camss_format_find_format - Find a format in an array
* @code: media bus format code
* @pixelformat: V4L2 pixel format FCC identifier
* @formats: a pointer to formats array
* @nformats: size of @formats array
*
* Return index of a format or a negative error code otherwise
*/
int camss_format_find_format(u32 code, u32 pixelformat, const struct camss_format_info *formats,
unsigned int nformats)
{
unsigned int i;
for (i = 0; i < nformats; i++) {
if (formats[i].code == code &&
formats[i].pixelformat == pixelformat)
return i;
}
for (i = 0; i < nformats; i++) {
if (formats[i].code == code)
return i;
}
return -EINVAL;
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-format.h
*
* Qualcomm MSM Camera Subsystem - Format helpers
*
* Copyright (c) 2023, The Linux Foundation. All rights reserved.
* Copyright (c) 2023 Qualcomm Technologies, Inc.
*/
#ifndef __CAMSS_FORMAT_H__
#define __CAMSS_FORMAT_H__
#include <linux/types.h>
#define PER_PLANE_DATA(plane, h_fract_num, h_fract_den, v_fract_num, v_fract_den, _bpp) \
.hsub[(plane)].numerator = (h_fract_num), \
.hsub[(plane)].denominator = (h_fract_den), \
.vsub[(plane)].numerator = (v_fract_num), \
.vsub[(plane)].denominator = (v_fract_den), \
.bpp[(plane)] = (_bpp)
/*
* struct fract - Represents a fraction
* @numerator: Store the numerator part of the fraction
* @denominator: Store the denominator part of the fraction
*/
struct fract {
u8 numerator;
u8 denominator;
};
/*
* struct camss_format_info - ISP media bus format information
* @code: V4L2 media bus format code
* @mbus_bpp: Media bus bits per pixel
* @pixelformat: V4L2 pixel format FCC identifier
* @planes: Number of planes
* @hsub: Horizontal subsampling (for each plane)
* @vsub: Vertical subsampling (for each plane)
* @bpp: Bits per pixel when stored in memory (for each plane)
*/
struct camss_format_info {
u32 code;
u32 mbus_bpp;
u32 pixelformat;
u8 planes;
struct fract hsub[3];
struct fract vsub[3];
unsigned int bpp[3];
};
struct camss_formats {
unsigned int nformats;
const struct camss_format_info *formats;
};
u8 camss_format_get_bpp(const struct camss_format_info *formats, unsigned int nformats, u32 code);
unsigned int camss_format_get_bpl_alignment(const struct camss_format_info *f);
u32 camss_format_find_code(u32 *code, unsigned int n_code, unsigned int index, u32 req_code);
int camss_format_find_format(u32 code, u32 pixelformat, const struct camss_format_info *formats,
unsigned int nformats);
#endif /* __CAMSS_FORMAT_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-ispif.h
*
* Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module
*
* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_ISPIF_H
#define QC_MSM_CAMSS_ISPIF_H
#include <linux/clk.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#define MSM_ISPIF_PAD_SINK 0
#define MSM_ISPIF_PAD_SRC 1
#define MSM_ISPIF_PADS_NUM 2
#define MSM_ISPIF_VFE_NUM 2
enum ispif_intf {
PIX0,
RDI0,
PIX1,
RDI1,
RDI2
};
struct ispif_intf_cmd_reg {
u32 cmd_0;
u32 cmd_1;
};
struct ispif_line {
struct ispif_device *ispif;
u8 id;
u8 csid_id;
u8 vfe_id;
enum ispif_intf interface;
struct v4l2_subdev subdev;
struct media_pad pads[MSM_ISPIF_PADS_NUM];
struct v4l2_mbus_framefmt fmt[MSM_ISPIF_PADS_NUM];
const u32 *formats;
unsigned int nformats;
};
struct ispif_device {
void __iomem *base;
void __iomem *base_clk_mux;
u32 irq;
char irq_name[30];
struct camss_clock *clock;
int nclocks;
struct camss_clock *clock_for_reset;
int nclocks_for_reset;
struct completion reset_complete[MSM_ISPIF_VFE_NUM];
int power_count;
struct mutex power_lock;
struct ispif_intf_cmd_reg intf_cmd[MSM_ISPIF_VFE_NUM];
struct mutex config_lock;
unsigned int line_num;
struct ispif_line *line;
struct camss *camss;
};
struct camss_subdev_resources;
int msm_ispif_subdev_init(struct camss *camss,
const struct camss_subdev_resources *res);
int msm_ispif_register_entities(struct ispif_device *ispif,
struct v4l2_device *v4l2_dev);
void msm_ispif_unregister_entities(struct ispif_device *ispif);
#endif /* QC_MSM_CAMSS_ISPIF_H */
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-2.0
/*
*
* Qualcomm MSM Camera Subsystem - TPG (Test Pattern Generator) Module
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/io.h>
#include <linux/kernel.h>
#include "camss-tpg.h"
#include "camss.h"
/* TPG global registers */
#define TPG_HW_VERSION 0x0
# define HW_VERSION_STEPPING GENMASK(15, 0)
# define HW_VERSION_REVISION GENMASK(27, 16)
# define HW_VERSION_GENERATION GENMASK(31, 28)
#define TPG_HW_VER(gen, rev, step) \
(((u32)(gen) << 28) | ((u32)(rev) << 16) | (u32)(step))
#define TPG_HW_VER_2_0_0 TPG_HW_VER(2, 0, 0)
#define TPG_HW_VER_2_1_0 TPG_HW_VER(2, 1, 0)
#define TPG_HW_STATUS 0x4
#define TPG_CTRL 0x64
# define TPG_CTRL_TEST_EN BIT(0)
# define TPG_CTRL_PHY_SEL BIT(3)
# define TPG_CTRL_NUM_ACTIVE_LANES GENMASK(5, 4)
# define TPG_CTRL_VC_DT_PATTERN_ID GENMASK(8, 6)
# define TPG_CTRL_OVERLAP_SHDR_EN BIT(10)
# define TPG_CTRL_NUM_ACTIVE_VC GENMASK(31, 30)
#define TPG_CLEAR 0x1F4
/* TPG VC-based registers */
#define TPG_VC_n_GAIN_CFG(n) (0x60 + (n) * 0x60)
#define TPG_VC_n_CFG0(n) (0x68 + (n) * 0x60)
# define TPG_VC_n_CFG0_VC_NUM GENMASK(4, 0)
# define TPG_VC_n_CFG0_NUM_ACTIVE_DT GENMASK(9, 8)
# define TPG_VC_n_CFG0_NUM_BATCH GENMASK(15, 12)
# define TPG_VC_n_CFG0_NUM_FRAMES GENMASK(31, 16)
#define TPG_VC_n_LSFR_SEED(n) (0x6C + (n) * 0x60)
#define TPG_VC_n_HBI_CFG(n) (0x70 + (n) * 0x60)
#define TPG_VC_n_VBI_CFG(n) (0x74 + (n) * 0x60)
#define TPG_VC_n_COLOR_BARS_CFG(n) (0x78 + (n) * 0x60)
# define TPG_VC_n_COLOR_BARS_CFG_PIX_PATTERN GENMASK(2, 0)
# define TPG_VC_n_COLOR_BARS_CFG_QCFA_EN BIT(3)
# define TPG_VC_n_COLOR_BARS_CFG_SPLIT_EN BIT(4)
# define TPG_VC_n_COLOR_BARS_CFG_NOISE_EN BIT(5)
# define TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD GENMASK(13, 8)
# define TPG_VC_n_COLOR_BARS_CFG_XCFA_EN BIT(16)
# define TPG_VC_n_COLOR_BARS_CFG_SIZE_X GENMASK(26, 24)
# define TPG_VC_n_COLOR_BARS_CFG_SIZE_Y GENMASK(30, 28)
/* TPG DT-based registers */
#define TPG_VC_m_DT_n_CFG_0(m, n) (0x7C + (m) * 0x60 + (n) * 0xC)
# define TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT GENMASK(15, 0)
# define TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH GENMASK(31, 16)
#define TPG_VC_m_DT_n_CFG_1(m, n) (0x80 + (m) * 0x60 + (n) * 0xC)
# define TPG_VC_m_DT_n_CFG_1_DATA_TYPE GENMASK(5, 0)
# define TPG_VC_m_DT_n_CFG_1_ECC_XOR_MASK GENMASK(13, 8)
# define TPG_VC_m_DT_n_CFG_1_CRC_XOR_MASK GENMASK(31, 16)
#define TPG_VC_m_DT_n_CFG_2(m, n) (0x84 + (m) * 0x60 + (n) * 0xC)
# define TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE GENMASK(3, 0)
/* v2.0.0: USER[19:4], ENC[23:20] */
# define TPG_V2_0_0_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(19, 4)
# define TPG_V2_0_0_VC_m_DT_n_CFG_2_ENCODE_FORMAT GENMASK(23, 20)
/* v2.1.0: USER[27:4], ENC[31:28] */
# define TPG_V2_1_0_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(27, 4)
# define TPG_V2_1_0_VC_m_DT_n_CFG_2_ENCODE_FORMAT GENMASK(31, 28)
#define TPG_HBI_PCT_DEFAULT 545 /* 545% */
#define TPG_VBI_PCT_DEFAULT 10 /* 10% */
#define PERCENT_BASE 100
/* Default user-specified payload for TPG test generator.
* Keep consistent with CSID TPG default: 0xBE.
*/
#define TPG_USER_SPECIFIED_PAYLOAD_DEFAULT 0xBE
#define TPG_LFSR_SEED_DEFAULT 0x12345678
#define TPG_COLOR_BARS_CFG_STANDARD \
FIELD_PREP(TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD, 0xA)
static const char * const testgen_payload_modes[] = {
[TPG_PAYLOAD_MODE_DISABLED] = "Disabled",
[TPG_PAYLOAD_MODE_INCREMENTING] = "Incrementing",
[TPG_PAYLOAD_MODE_ALTERNATING_55_AA] = "Alternating 0x55/0xAA",
[TPG_PAYLOAD_MODE_RANDOM] = "Pseudo-random Data",
[TPG_PAYLOAD_MODE_USER_SPECIFIED] = "User Specified",
[TPG_PAYLOAD_MODE_COLOR_BARS] = "Color bars",
};
static int tpg_stream_on(struct tpg_device *tpg)
{
struct tpg_testgen_config *tg = &tpg->testgen;
struct v4l2_mbus_framefmt *input_format;
const struct tpg_format_info *format;
u8 payload_mode = (tg->mode > TPG_PAYLOAD_MODE_DISABLED) ?
tg->mode - 1 : 0;
u8 lane_cnt = tpg->res->lane_cnt;
u8 vc, dt, last_vc = 0;
u32 val;
for (vc = 0; vc <= MSM_TPG_ACTIVE_VC; vc++) {
last_vc = vc;
input_format = &tpg->fmt;
format = tpg_get_fmt_entry(tpg->res->formats->formats,
tpg->res->formats->nformats,
input_format->code);
if (IS_ERR(format))
return -EINVAL;
/* VC configuration */
val = FIELD_PREP(TPG_VC_n_CFG0_NUM_ACTIVE_DT, MSM_TPG_ACTIVE_DT) |
FIELD_PREP(TPG_VC_n_CFG0_NUM_FRAMES, 0);
writel(val, tpg->base + TPG_VC_n_CFG0(vc));
writel(TPG_LFSR_SEED_DEFAULT, tpg->base + TPG_VC_n_LSFR_SEED(vc));
val = DIV_ROUND_UP(input_format->width * format->bpp * TPG_HBI_PCT_DEFAULT,
BITS_PER_BYTE * lane_cnt * PERCENT_BASE);
writel(val, tpg->base + TPG_VC_n_HBI_CFG(vc));
val = input_format->height * TPG_VBI_PCT_DEFAULT / PERCENT_BASE;
writel(val, tpg->base + TPG_VC_n_VBI_CFG(vc));
writel(TPG_COLOR_BARS_CFG_STANDARD, tpg->base + TPG_VC_n_COLOR_BARS_CFG(vc));
/* DT configuration */
for (dt = 0; dt <= MSM_TPG_ACTIVE_DT; dt++) {
val = FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT,
input_format->height & 0xffff) |
FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH,
input_format->width & 0xffff);
writel(val, tpg->base + TPG_VC_m_DT_n_CFG_0(vc, dt));
val = FIELD_PREP(TPG_VC_m_DT_n_CFG_1_DATA_TYPE, format->data_type);
writel(val, tpg->base + TPG_VC_m_DT_n_CFG_1(vc, dt));
if (tpg->hw_version == TPG_HW_VER_2_0_0) {
val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, payload_mode) |
FIELD_PREP(TPG_V2_0_0_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
FIELD_PREP(TPG_V2_0_0_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
format->encode_format);
} else if (tpg->hw_version >= TPG_HW_VER_2_1_0) {
val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, payload_mode) |
FIELD_PREP(TPG_V2_1_0_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
FIELD_PREP(TPG_V2_1_0_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
format->encode_format);
}
writel(val, tpg->base + TPG_VC_m_DT_n_CFG_2(vc, dt));
}
}
/* Global TPG control */
val = FIELD_PREP(TPG_CTRL_TEST_EN, 1) |
FIELD_PREP(TPG_CTRL_NUM_ACTIVE_LANES, lane_cnt - 1) |
FIELD_PREP(TPG_CTRL_NUM_ACTIVE_VC, last_vc);
writel(val, tpg->base + TPG_CTRL);
return 0;
}
static int tpg_reset(struct tpg_device *tpg)
{
writel(0, tpg->base + TPG_CTRL);
writel(1, tpg->base + TPG_CLEAR);
return 0;
}
static void tpg_stream_off(struct tpg_device *tpg)
{
tpg_reset(tpg);
}
static int tpg_configure_stream(struct tpg_device *tpg, u8 enable)
{
if (enable)
return tpg_stream_on(tpg);
tpg_stream_off(tpg);
return 0;
}
static int tpg_configure_testgen_pattern(struct tpg_device *tpg, s32 val)
{
if (val >= 0 && val <= TPG_PAYLOAD_MODE_COLOR_BARS)
tpg->testgen.mode = val;
return 0;
}
static u32 tpg_hw_version(struct tpg_device *tpg)
{
u32 hw_version = readl(tpg->base + TPG_HW_VERSION);
tpg->hw_version = hw_version;
dev_dbg(tpg->camss->dev, "tpg HW Version = %u.%u.%u\n",
(u32)FIELD_GET(HW_VERSION_GENERATION, hw_version),
(u32)FIELD_GET(HW_VERSION_REVISION, hw_version),
(u32)FIELD_GET(HW_VERSION_STEPPING, hw_version));
return hw_version;
}
static void tpg_subdev_init(struct tpg_device *tpg)
{
tpg->testgen.modes = testgen_payload_modes;
tpg->testgen.nmodes = TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
}
const struct tpg_hw_ops tpg_ops_gen1 = {
.configure_stream = tpg_configure_stream,
.configure_testgen_pattern = tpg_configure_testgen_pattern,
.hw_version = tpg_hw_version,
.reset = tpg_reset,
.subdev_init = tpg_subdev_init,
};
@@ -0,0 +1,519 @@
// SPDX-License-Identifier: GPL-2.0
/*
*
* Qualcomm MSM Camera Subsystem - TPG Module
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/mipi-csi2.h>
#include "camss-tpg.h"
#include "camss.h"
static const struct tpg_format_info formats_gen1[] = {
{
MEDIA_BUS_FMT_SBGGR8_1X8,
MIPI_CSI2_DT_RAW8,
ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
8,
},
{
MEDIA_BUS_FMT_SGBRG8_1X8,
MIPI_CSI2_DT_RAW8,
ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
8,
},
{
MEDIA_BUS_FMT_SGRBG8_1X8,
MIPI_CSI2_DT_RAW8,
ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
8,
},
{
MEDIA_BUS_FMT_SRGGB8_1X8,
MIPI_CSI2_DT_RAW8,
ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
8,
},
{
MEDIA_BUS_FMT_SBGGR10_1X10,
MIPI_CSI2_DT_RAW10,
ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
10,
},
{
MEDIA_BUS_FMT_SGBRG10_1X10,
MIPI_CSI2_DT_RAW10,
ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
10,
},
{
MEDIA_BUS_FMT_SGRBG10_1X10,
MIPI_CSI2_DT_RAW10,
ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
10,
},
{
MEDIA_BUS_FMT_SRGGB10_1X10,
MIPI_CSI2_DT_RAW10,
ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
10,
},
{
MEDIA_BUS_FMT_SBGGR12_1X12,
MIPI_CSI2_DT_RAW12,
ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
12,
},
{
MEDIA_BUS_FMT_SGBRG12_1X12,
MIPI_CSI2_DT_RAW12,
ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
12,
},
{
MEDIA_BUS_FMT_SGRBG12_1X12,
MIPI_CSI2_DT_RAW12,
ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
12,
},
{
MEDIA_BUS_FMT_SRGGB12_1X12,
MIPI_CSI2_DT_RAW12,
ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
12,
},
{
MEDIA_BUS_FMT_Y8_1X8,
MIPI_CSI2_DT_RAW8,
ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
8,
},
{
MEDIA_BUS_FMT_Y10_1X10,
MIPI_CSI2_DT_RAW10,
ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
10,
},
};
const struct tpg_formats tpg_formats_gen1 = {
.nformats = ARRAY_SIZE(formats_gen1),
.formats = formats_gen1
};
const struct tpg_format_info *tpg_get_fmt_entry(const struct tpg_format_info *formats,
unsigned int nformats,
u32 code)
{
unsigned int i;
for (i = 0; i < nformats; i++)
if (code == formats[i].code)
return &formats[i];
return ERR_PTR(-EINVAL);
}
static int tpg_set_clock_rates(struct tpg_device *tpg)
{
struct device *dev = tpg->camss->dev;
int i, ret;
for (i = 0; i < tpg->nclocks; i++) {
struct camss_clock *clock = &tpg->clock[i];
long round_rate;
if (clock->freq) {
round_rate = clk_round_rate(clock->clk, clock->freq[0]);
if (round_rate < 0) {
dev_err(dev, "clk round rate failed: %ld\n",
round_rate);
return -EINVAL;
}
ret = clk_set_rate(clock->clk, round_rate);
if (ret < 0) {
dev_err(dev, "clk set rate failed: %d\n", ret);
return ret;
}
}
}
return 0;
}
static int tpg_set_power(struct v4l2_subdev *sd, int on)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
struct device *dev = tpg->camss->dev;
if (on) {
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
ret = tpg_set_clock_rates(tpg);
if (ret < 0) {
pm_runtime_put_sync(dev);
return ret;
}
ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
if (ret < 0) {
pm_runtime_put_sync(dev);
return ret;
}
tpg->res->hw_ops->reset(tpg);
tpg->res->hw_ops->hw_version(tpg);
} else {
camss_disable_clocks(tpg->nclocks, tpg->clock);
pm_runtime_put_sync(dev);
}
return 0;
}
static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
int ret;
if (enable) {
ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
if (ret < 0) {
dev_err(tpg->camss->dev,
"could not sync v4l2 controls: %d\n", ret);
return ret;
}
}
return tpg->res->hw_ops->configure_stream(tpg, enable);
}
static struct v4l2_mbus_framefmt *
__tpg_get_format(struct tpg_device *tpg,
struct v4l2_subdev_state *sd_state,
unsigned int pad,
enum v4l2_subdev_format_whence which)
{
if (which == V4L2_SUBDEV_FORMAT_TRY)
return v4l2_subdev_state_get_format(sd_state,
pad);
return &tpg->fmt;
}
static void tpg_try_format(struct tpg_device *tpg,
struct v4l2_mbus_framefmt *fmt)
{
unsigned int i;
for (i = 0; i < tpg->res->formats->nformats; i++)
if (tpg->res->formats->formats[i].code == fmt->code)
break;
if (i >= tpg->res->formats->nformats)
fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
fmt->width = clamp_t(u32, fmt->width, TPG_MIN_WIDTH, TPG_MAX_WIDTH);
fmt->height = clamp_t(u32, fmt->height, TPG_MIN_HEIGHT, TPG_MAX_HEIGHT);
fmt->field = V4L2_FIELD_NONE;
fmt->colorspace = V4L2_COLORSPACE_SRGB;
}
static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
if (code->index >= tpg->res->formats->nformats)
return -EINVAL;
code->code = tpg->res->formats->formats[code->index].code;
return 0;
}
static int tpg_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
unsigned int i;
if (fse->index != 0)
return -EINVAL;
for (i = 0; i < tpg->res->formats->nformats; i++)
if (tpg->res->formats->formats[i].code == fse->code)
break;
if (i >= tpg->res->formats->nformats)
return -EINVAL;
fse->min_width = TPG_MIN_WIDTH;
fse->min_height = TPG_MIN_HEIGHT;
fse->max_width = TPG_MAX_WIDTH;
fse->max_height = TPG_MAX_HEIGHT;
return 0;
}
static int tpg_get_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *format;
format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
if (!format)
return -EINVAL;
fmt->format = *format;
return 0;
}
static int tpg_set_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct tpg_device *tpg = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *format;
format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
if (!format)
return -EINVAL;
tpg_try_format(tpg, &fmt->format);
*format = fmt->format;
return 0;
}
static int tpg_init_formats(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh)
{
struct v4l2_subdev_format format = {
.pad = MSM_TPG_PAD_SRC,
.which = fh ? V4L2_SUBDEV_FORMAT_TRY :
V4L2_SUBDEV_FORMAT_ACTIVE,
.format = {
.code = MEDIA_BUS_FMT_SBGGR8_1X8,
.width = 1920,
.height = 1080,
}
};
return tpg_set_format(sd, fh ? fh->state : NULL, &format);
}
static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct tpg_device *tpg = container_of(ctrl->handler,
struct tpg_device, ctrls);
int ret = -EINVAL;
switch (ctrl->id) {
case V4L2_CID_TEST_PATTERN:
ret = tpg->res->hw_ops->configure_testgen_pattern(tpg, ctrl->val);
break;
}
return ret;
}
static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
.s_ctrl = tpg_s_ctrl,
};
int msm_tpg_subdev_init(struct camss *camss,
struct tpg_device *tpg,
const struct camss_subdev_resources *res, u8 id)
{
struct platform_device *pdev;
struct device *dev;
int i, j;
dev = camss->dev;
pdev = to_platform_device(dev);
tpg->camss = camss;
tpg->id = id;
tpg->res = &res->tpg;
tpg->res->hw_ops->subdev_init(tpg);
tpg->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]);
if (IS_ERR(tpg->base))
return PTR_ERR(tpg->base);
tpg->nclocks = 0;
while (res->clock[tpg->nclocks])
tpg->nclocks++;
if (!tpg->nclocks)
return 0;
tpg->clock = devm_kcalloc(dev, tpg->nclocks,
sizeof(*tpg->clock), GFP_KERNEL);
if (!tpg->clock)
return -ENOMEM;
for (i = 0; i < tpg->nclocks; i++) {
struct camss_clock *clock = &tpg->clock[i];
clock->clk = devm_clk_get(dev, res->clock[i]);
if (IS_ERR(clock->clk))
return PTR_ERR(clock->clk);
clock->name = res->clock[i];
clock->nfreqs = 0;
while (res->clock_rate[i][clock->nfreqs])
clock->nfreqs++;
if (!clock->nfreqs) {
clock->freq = NULL;
continue;
}
clock->freq = devm_kcalloc(dev, clock->nfreqs,
sizeof(*clock->freq), GFP_KERNEL);
if (!clock->freq)
return -ENOMEM;
for (j = 0; j < clock->nfreqs; j++)
clock->freq[j] = res->clock_rate[i][j];
}
return 0;
}
static int tpg_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
if (flags & MEDIA_LNK_FL_ENABLED)
if (media_pad_remote_pad_first(local))
return -EBUSY;
return 0;
}
static const struct v4l2_subdev_core_ops tpg_core_ops = {
.s_power = tpg_set_power,
};
static const struct v4l2_subdev_video_ops tpg_video_ops = {
.s_stream = tpg_set_stream,
};
static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
.enum_mbus_code = tpg_enum_mbus_code,
.enum_frame_size = tpg_enum_frame_size,
.get_fmt = tpg_get_format,
.set_fmt = tpg_set_format,
};
static const struct v4l2_subdev_ops tpg_v4l2_ops = {
.core = &tpg_core_ops,
.video = &tpg_video_ops,
.pad = &tpg_pad_ops,
};
static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
.open = tpg_init_formats,
};
static const struct media_entity_operations tpg_media_ops = {
.link_setup = tpg_link_setup,
.link_validate = v4l2_subdev_link_validate,
};
int msm_tpg_register_entity(struct tpg_device *tpg,
struct v4l2_device *v4l2_dev)
{
struct v4l2_subdev *sd = &tpg->subdev;
struct device *dev = tpg->camss->dev;
int ret;
v4l2_subdev_init(sd, &tpg_v4l2_ops);
sd->internal_ops = &tpg_v4l2_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS;
snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
"msm_tpg", tpg->id);
sd->grp_id = TPG_GRP_ID;
v4l2_set_subdevdata(sd, tpg);
ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
if (ret < 0) {
dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
return ret;
}
tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
&tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
tpg->testgen.nmodes, 0, 0,
tpg->testgen.modes);
if (tpg->ctrls.error) {
dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
ret = tpg->ctrls.error;
goto free_ctrl;
}
tpg->subdev.ctrl_handler = &tpg->ctrls;
ret = tpg_init_formats(sd, NULL);
if (ret < 0) {
dev_err(dev, "Failed to init format: %d\n", ret);
goto free_ctrl;
}
tpg->pad.flags = MEDIA_PAD_FL_SOURCE;
sd->entity.ops = &tpg_media_ops;
ret = media_entity_pads_init(&sd->entity, 1, &tpg->pad);
if (ret < 0) {
dev_err(dev, "Failed to init media entity: %d\n", ret);
goto free_ctrl;
}
ret = v4l2_device_register_subdev(v4l2_dev, sd);
if (ret < 0) {
dev_err(dev, "Failed to register subdev: %d\n", ret);
media_entity_cleanup(&sd->entity);
goto free_ctrl;
}
return 0;
free_ctrl:
v4l2_ctrl_handler_free(&tpg->ctrls);
return ret;
}
void msm_tpg_unregister_entity(struct tpg_device *tpg)
{
v4l2_device_unregister_subdev(&tpg->subdev);
media_entity_cleanup(&tpg->subdev.entity);
v4l2_ctrl_handler_free(&tpg->ctrls);
}
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-tpg.h
*
* Qualcomm MSM Camera Subsystem - TPG Module
*
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef QC_MSM_CAMSS_TPG_H
#define QC_MSM_CAMSS_TPG_H
#include <linux/clk.h>
#include <linux/bitfield.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>
#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT 0x1
#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT 0x2
#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT 0x3
#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT 0x4
#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT 0x5
#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT 0x6
#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT 0x7
#define MSM_TPG_PAD_SRC 0
#define MSM_TPG_ACTIVE_VC 0
#define MSM_TPG_ACTIVE_DT 0
#define TPG_MIN_WIDTH 1
#define TPG_MIN_HEIGHT 1
#define TPG_MAX_WIDTH 8191
#define TPG_MAX_HEIGHT 8191
#define TPG_GRP_ID 0
enum tpg_testgen_mode {
TPG_PAYLOAD_MODE_DISABLED = 0,
TPG_PAYLOAD_MODE_INCREMENTING = 1,
TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
TPG_PAYLOAD_MODE_RANDOM = 5,
TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
TPG_PAYLOAD_MODE_COLOR_BARS = 9,
TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
};
struct tpg_testgen_config {
enum tpg_testgen_mode mode;
const char * const*modes;
u8 nmodes;
};
struct tpg_format_info {
u32 code;
u8 data_type;
u8 encode_format;
u8 bpp;
};
struct tpg_formats {
unsigned int nformats;
const struct tpg_format_info *formats;
};
struct tpg_device;
struct tpg_hw_ops {
int (*configure_stream)(struct tpg_device *tpg, u8 enable);
int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
u32 (*hw_version)(struct tpg_device *tpg);
int (*reset)(struct tpg_device *tpg);
void (*subdev_init)(struct tpg_device *tpg);
};
struct tpg_subdev_resources {
u8 lane_cnt;
const struct tpg_formats *formats;
const struct tpg_hw_ops *hw_ops;
};
struct tpg_device {
struct camss *camss;
u8 id;
struct v4l2_subdev subdev;
struct media_pad pad;
void __iomem *base;
struct camss_clock *clock;
int nclocks;
struct tpg_testgen_config testgen;
struct v4l2_mbus_framefmt fmt;
struct v4l2_ctrl_handler ctrls;
struct v4l2_ctrl *testgen_mode;
const struct tpg_subdev_resources *res;
u32 hw_version;
};
struct camss_subdev_resources;
const struct tpg_format_info *tpg_get_fmt_entry(const struct tpg_format_info *formats,
unsigned int nformats,
u32 code);
int msm_tpg_subdev_init(struct camss *camss,
struct tpg_device *tpg,
const struct camss_subdev_resources *res, u8 id);
int msm_tpg_register_entity(struct tpg_device *tpg,
struct v4l2_device *v4l2_dev);
void msm_tpg_unregister_entity(struct tpg_device *tpg);
extern const struct tpg_formats tpg_formats_gen1;
extern const struct tpg_hw_ops tpg_ops_gen1;
#endif /* QC_MSM_CAMSS_TPG_H */
@@ -0,0 +1,595 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-vfe-170.c
*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v170
*
* Copyright (C) 2020-2021 Linaro Ltd.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include "camss.h"
#include "camss-vfe.h"
#define VFE_GLOBAL_RESET_CMD (0x018)
#define GLOBAL_RESET_CMD_CORE BIT(0)
#define GLOBAL_RESET_CMD_CAMIF BIT(1)
#define GLOBAL_RESET_CMD_BUS BIT(2)
#define GLOBAL_RESET_CMD_BUS_BDG BIT(3)
#define GLOBAL_RESET_CMD_REGISTER BIT(4)
#define GLOBAL_RESET_CMD_PM BIT(5)
#define GLOBAL_RESET_CMD_BUS_MISR BIT(6)
#define GLOBAL_RESET_CMD_TESTGEN BIT(7)
#define GLOBAL_RESET_CMD_DSP BIT(8)
#define GLOBAL_RESET_CMD_IDLE_CGC BIT(9)
#define GLOBAL_RESET_CMD_RDI0 BIT(10)
#define GLOBAL_RESET_CMD_RDI1 BIT(11)
#define GLOBAL_RESET_CMD_RDI2 BIT(12)
#define GLOBAL_RESET_CMD_RDI3 BIT(13)
#define GLOBAL_RESET_CMD_VFE_DOMAIN BIT(30)
#define GLOBAL_RESET_CMD_RESET_BYPASS BIT(31)
#define VFE_CORE_CFG (0x050)
#define CFG_PIXEL_PATTERN_YCBYCR (0x4)
#define CFG_PIXEL_PATTERN_YCRYCB (0x5)
#define CFG_PIXEL_PATTERN_CBYCRY (0x6)
#define CFG_PIXEL_PATTERN_CRYCBY (0x7)
#define CFG_COMPOSITE_REG_UPDATE_EN BIT(4)
#define VFE_IRQ_CMD (0x058)
#define CMD_GLOBAL_CLEAR BIT(0)
#define VFE_IRQ_MASK_0 (0x05c)
#define MASK_0_CAMIF_SOF BIT(0)
#define MASK_0_CAMIF_EOF BIT(1)
#define MASK_0_RDI_REG_UPDATE(n) BIT((n) + 5)
#define MASK_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8)
#define MASK_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25)
#define MASK_0_RESET_ACK BIT(31)
#define VFE_IRQ_MASK_1 (0x060)
#define MASK_1_CAMIF_ERROR BIT(0)
#define MASK_1_VIOLATION BIT(7)
#define MASK_1_BUS_BDG_HALT_ACK BIT(8)
#define MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) BIT((n) + 9)
#define MASK_1_RDI_SOF(n) BIT((n) + 29)
#define VFE_IRQ_CLEAR_0 (0x064)
#define VFE_IRQ_CLEAR_1 (0x068)
#define VFE_IRQ_STATUS_0 (0x06c)
#define STATUS_0_CAMIF_SOF BIT(0)
#define STATUS_0_RDI_REG_UPDATE(n) BIT((n) + 5)
#define STATUS_0_IMAGE_MASTER_PING_PONG(n) BIT((n) + 8)
#define STATUS_0_IMAGE_COMPOSITE_DONE(n) BIT((n) + 25)
#define STATUS_0_RESET_ACK BIT(31)
#define VFE_IRQ_STATUS_1 (0x070)
#define STATUS_1_VIOLATION BIT(7)
#define STATUS_1_BUS_BDG_HALT_ACK BIT(8)
#define STATUS_1_RDI_SOF(n) BIT((n) + 27)
#define VFE_VIOLATION_STATUS (0x07c)
#define VFE_CAMIF_CMD (0x478)
#define CMD_CLEAR_CAMIF_STATUS BIT(2)
#define VFE_CAMIF_CFG (0x47c)
#define CFG_VSYNC_SYNC_EDGE (0)
#define VSYNC_ACTIVE_HIGH (0)
#define VSYNC_ACTIVE_LOW (1)
#define CFG_HSYNC_SYNC_EDGE (1)
#define HSYNC_ACTIVE_HIGH (0)
#define HSYNC_ACTIVE_LOW (1)
#define CFG_VFE_SUBSAMPLE_ENABLE BIT(4)
#define CFG_BUS_SUBSAMPLE_ENABLE BIT(5)
#define CFG_VFE_OUTPUT_EN BIT(6)
#define CFG_BUS_OUTPUT_EN BIT(7)
#define CFG_BINNING_EN BIT(9)
#define CFG_FRAME_BASED_EN BIT(10)
#define CFG_RAW_CROP_EN BIT(22)
#define VFE_REG_UPDATE_CMD (0x4ac)
#define REG_UPDATE_RDI(n) BIT(1 + (n))
#define VFE_BUS_IRQ_MASK(n) (0x2044 + (n) * 4)
#define VFE_BUS_IRQ_CLEAR(n) (0x2050 + (n) * 4)
#define VFE_BUS_IRQ_STATUS(n) (0x205c + (n) * 4)
#define STATUS0_COMP_RESET_DONE BIT(0)
#define STATUS0_COMP_REG_UPDATE0_DONE BIT(1)
#define STATUS0_COMP_REG_UPDATE1_DONE BIT(2)
#define STATUS0_COMP_REG_UPDATE2_DONE BIT(3)
#define STATUS0_COMP_REG_UPDATE3_DONE BIT(4)
#define STATUS0_COMP_REG_UPDATE_DONE(n) BIT((n) + 1)
#define STATUS0_COMP0_BUF_DONE BIT(5)
#define STATUS0_COMP1_BUF_DONE BIT(6)
#define STATUS0_COMP2_BUF_DONE BIT(7)
#define STATUS0_COMP3_BUF_DONE BIT(8)
#define STATUS0_COMP4_BUF_DONE BIT(9)
#define STATUS0_COMP5_BUF_DONE BIT(10)
#define STATUS0_COMP_BUF_DONE(n) BIT((n) + 5)
#define STATUS0_COMP_ERROR BIT(11)
#define STATUS0_COMP_OVERWRITE BIT(12)
#define STATUS0_OVERFLOW BIT(13)
#define STATUS0_VIOLATION BIT(14)
/* WM_CLIENT_BUF_DONE defined for buffers 0:19 */
#define STATUS1_WM_CLIENT_BUF_DONE(n) BIT(n)
#define STATUS1_EARLY_DONE BIT(24)
#define STATUS2_DUAL_COMP0_BUF_DONE BIT(0)
#define STATUS2_DUAL_COMP1_BUF_DONE BIT(1)
#define STATUS2_DUAL_COMP2_BUF_DONE BIT(2)
#define STATUS2_DUAL_COMP3_BUF_DONE BIT(3)
#define STATUS2_DUAL_COMP4_BUF_DONE BIT(4)
#define STATUS2_DUAL_COMP5_BUF_DONE BIT(5)
#define STATUS2_DUAL_COMP_BUF_DONE(n) BIT(n)
#define STATUS2_DUAL_COMP_ERROR BIT(6)
#define STATUS2_DUAL_COMP_OVERWRITE BIT(7)
#define VFE_BUS_IRQ_CLEAR_GLOBAL (0x2068)
#define VFE_BUS_WM_DEBUG_STATUS_CFG (0x226c)
#define DEBUG_STATUS_CFG_STATUS0(n) BIT(n)
#define DEBUG_STATUS_CFG_STATUS1(n) BIT(8 + (n))
#define VFE_BUS_WM_ADDR_SYNC_FRAME_HEADER (0x2080)
#define VFE_BUS_WM_ADDR_SYNC_NO_SYNC (0x2084)
#define BUS_VER2_MAX_CLIENTS (24)
#define WM_ADDR_NO_SYNC_DEFAULT_VAL \
((1 << BUS_VER2_MAX_CLIENTS) - 1)
#define VFE_BUS_WM_CGC_OVERRIDE (0x200c)
#define WM_CGC_OVERRIDE_ALL (0xFFFFF)
#define VFE_BUS_WM_TEST_BUS_CTRL (0x211c)
#define VFE_BUS_WM_STATUS0(n) (0x2200 + (n) * 0x100)
#define VFE_BUS_WM_STATUS1(n) (0x2204 + (n) * 0x100)
#define VFE_BUS_WM_CFG(n) (0x2208 + (n) * 0x100)
#define WM_CFG_EN (0)
#define WM_CFG_MODE (1)
#define MODE_QCOM_PLAIN (0)
#define MODE_MIPI_RAW (1)
#define WM_CFG_VIRTUALFRAME (2)
#define VFE_BUS_WM_HEADER_ADDR(n) (0x220c + (n) * 0x100)
#define VFE_BUS_WM_HEADER_CFG(n) (0x2210 + (n) * 0x100)
#define VFE_BUS_WM_IMAGE_ADDR(n) (0x2214 + (n) * 0x100)
#define VFE_BUS_WM_IMAGE_ADDR_OFFSET(n) (0x2218 + (n) * 0x100)
#define VFE_BUS_WM_BUFFER_WIDTH_CFG(n) (0x221c + (n) * 0x100)
#define WM_BUFFER_DEFAULT_WIDTH (0xFF01)
#define VFE_BUS_WM_BUFFER_HEIGHT_CFG(n) (0x2220 + (n) * 0x100)
#define VFE_BUS_WM_PACKER_CFG(n) (0x2224 + (n) * 0x100)
#define VFE_BUS_WM_STRIDE(n) (0x2228 + (n) * 0x100)
#define WM_STRIDE_DEFAULT_STRIDE (0xFF01)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (0x2248 + (n) * 0x100)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (0x224c + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (0x2250 + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (0x2254 + (n) * 0x100)
#define VFE_BUS_WM_FRAME_INC(n) (0x2258 + (n) * 0x100)
#define VFE_BUS_WM_BURST_LIMIT(n) (0x225c + (n) * 0x100)
static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits)
{
u32 bits = readl_relaxed(vfe->base + reg);
writel_relaxed(bits | set_bits, vfe->base + reg);
}
static void vfe_global_reset(struct vfe_device *vfe)
{
u32 reset_bits = GLOBAL_RESET_CMD_CORE |
GLOBAL_RESET_CMD_CAMIF |
GLOBAL_RESET_CMD_BUS |
GLOBAL_RESET_CMD_BUS_BDG |
GLOBAL_RESET_CMD_REGISTER |
GLOBAL_RESET_CMD_TESTGEN |
GLOBAL_RESET_CMD_DSP |
GLOBAL_RESET_CMD_IDLE_CGC |
GLOBAL_RESET_CMD_RDI0 |
GLOBAL_RESET_CMD_RDI1 |
GLOBAL_RESET_CMD_RDI2 |
GLOBAL_RESET_CMD_RDI3;
writel_relaxed(BIT(31), vfe->base + VFE_IRQ_MASK_0);
/* Make sure IRQ mask has been written before resetting */
wmb();
writel_relaxed(reset_bits, vfe->base + VFE_GLOBAL_RESET_CMD);
}
static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line)
{
u32 val;
/*Set Debug Registers*/
val = DEBUG_STATUS_CFG_STATUS0(1) |
DEBUG_STATUS_CFG_STATUS0(7);
writel_relaxed(val, vfe->base + VFE_BUS_WM_DEBUG_STATUS_CFG);
/* BUS_WM_INPUT_IF_ADDR_SYNC_FRAME_HEADER */
writel_relaxed(0, vfe->base + VFE_BUS_WM_ADDR_SYNC_FRAME_HEADER);
/* no clock gating at bus input */
val = WM_CGC_OVERRIDE_ALL;
writel_relaxed(val, vfe->base + VFE_BUS_WM_CGC_OVERRIDE);
writel_relaxed(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL);
/* if addr_no_sync has default value then config the addr no sync reg */
val = WM_ADDR_NO_SYNC_DEFAULT_VAL;
writel_relaxed(val, vfe->base + VFE_BUS_WM_ADDR_SYNC_NO_SYNC);
writel_relaxed(0xf, vfe->base + VFE_BUS_WM_BURST_LIMIT(wm));
val = WM_BUFFER_DEFAULT_WIDTH;
writel_relaxed(val, vfe->base + VFE_BUS_WM_BUFFER_WIDTH_CFG(wm));
val = 0;
writel_relaxed(val, vfe->base + VFE_BUS_WM_BUFFER_HEIGHT_CFG(wm));
val = 0;
writel_relaxed(val, vfe->base + VFE_BUS_WM_PACKER_CFG(wm)); // XXX 1 for PLAIN8?
/* Configure stride for RDIs */
val = WM_STRIDE_DEFAULT_STRIDE;
writel_relaxed(val, vfe->base + VFE_BUS_WM_STRIDE(wm));
/* Enable WM */
val = 1 << WM_CFG_EN |
MODE_MIPI_RAW << WM_CFG_MODE;
writel_relaxed(val, vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_stop(struct vfe_device *vfe, u8 wm)
{
/* Disable WM */
writel_relaxed(0, vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr,
struct vfe_line *line)
{
struct v4l2_pix_format_mplane *pix =
&line->video_out.active_fmt.fmt.pix_mp;
u32 stride = pix->plane_fmt[0].bytesperline;
writel_relaxed(addr, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm));
writel_relaxed(stride * pix->height, vfe->base + VFE_BUS_WM_FRAME_INC(wm));
}
static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
vfe->reg_update |= REG_UPDATE_RDI(line_id);
/* Enforce ordering between previous reg writes and reg update */
wmb();
writel_relaxed(vfe->reg_update, vfe->base + VFE_REG_UPDATE_CMD);
/* Enforce ordering between reg update and subsequent reg writes */
wmb();
}
static inline void vfe_reg_update_clear(struct vfe_device *vfe,
enum vfe_line_id line_id)
{
vfe->reg_update &= ~REG_UPDATE_RDI(line_id);
}
static void vfe_enable_irq_common(struct vfe_device *vfe)
{
vfe_reg_set(vfe, VFE_IRQ_MASK_0, ~0u);
vfe_reg_set(vfe, VFE_IRQ_MASK_1, ~0u);
writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(0));
writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(1));
writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(2));
}
static void vfe_isr_halt_ack(struct vfe_device *vfe)
{
complete(&vfe->halt_complete);
}
static void vfe_isr_read(struct vfe_device *vfe, u32 *status0, u32 *status1)
{
*status0 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_0);
*status1 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_1);
writel_relaxed(*status0, vfe->base + VFE_IRQ_CLEAR_0);
writel_relaxed(*status1, vfe->base + VFE_IRQ_CLEAR_1);
/* Enforce ordering between IRQ Clear and Global IRQ Clear */
wmb();
writel_relaxed(CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD);
}
static void vfe_violation_read(struct vfe_device *vfe)
{
u32 violation = readl_relaxed(vfe->base + VFE_VIOLATION_STATUS);
pr_err_ratelimited("VFE: violation = 0x%08x\n", violation);
}
/*
* vfe_isr - VFE module interrupt handler
* @irq: Interrupt line
* @dev: VFE device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t vfe_isr(int irq, void *dev)
{
struct vfe_device *vfe = dev;
u32 status0, status1, vfe_bus_status[VFE_LINE_NUM_MAX];
int i, wm;
status0 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_0);
status1 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_1);
writel_relaxed(status0, vfe->base + VFE_IRQ_CLEAR_0);
writel_relaxed(status1, vfe->base + VFE_IRQ_CLEAR_1);
for (i = VFE_LINE_RDI0; i < vfe->res->line_num; i++) {
vfe_bus_status[i] = readl_relaxed(vfe->base + VFE_BUS_IRQ_STATUS(i));
writel_relaxed(vfe_bus_status[i], vfe->base + VFE_BUS_IRQ_CLEAR(i));
}
/* Enforce ordering between IRQ reading and interpretation */
wmb();
writel_relaxed(CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD);
writel_relaxed(1, vfe->base + VFE_BUS_IRQ_CLEAR_GLOBAL);
if (status0 & STATUS_0_RESET_ACK)
vfe->isr_ops.reset_ack(vfe);
for (i = VFE_LINE_RDI0; i < vfe->res->line_num; i++)
if (status0 & STATUS_0_RDI_REG_UPDATE(i))
vfe->isr_ops.reg_update(vfe, i);
for (i = VFE_LINE_RDI0; i < vfe->res->line_num; i++)
if (status0 & STATUS_1_RDI_SOF(i))
vfe->isr_ops.sof(vfe, i);
for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++)
if (vfe_bus_status[0] & STATUS0_COMP_BUF_DONE(i))
vfe->isr_ops.comp_done(vfe, i);
for (wm = 0; wm < MSM_VFE_IMAGE_MASTERS_NUM; wm++)
if (status0 & BIT(9))
if (vfe_bus_status[1] & STATUS1_WM_CLIENT_BUF_DONE(wm))
vfe->isr_ops.wm_done(vfe, wm);
return IRQ_HANDLED;
}
/*
* vfe_halt - Trigger halt on VFE module and wait to complete
* @vfe: VFE device
*
* Return 0 on success or a negative error code otherwise
*/
static int vfe_halt(struct vfe_device *vfe)
{
/* rely on vfe_disable_output() to stop the VFE */
return 0;
}
static int vfe_get_output(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
struct vfe_output *output;
unsigned long flags;
int wm_idx;
spin_lock_irqsave(&vfe->output_lock, flags);
output = &line->output;
if (output->state > VFE_OUTPUT_RESERVED) {
dev_err(vfe->camss->dev, "Output is running\n");
goto error;
}
output->wm_num = 1;
wm_idx = vfe_reserve_wm(vfe, line->id);
if (wm_idx < 0) {
dev_err(vfe->camss->dev, "Can not reserve wm\n");
goto error_get_wm;
}
output->wm_idx[0] = wm_idx;
output->drop_update_idx = 0;
spin_unlock_irqrestore(&vfe->output_lock, flags);
return 0;
error_get_wm:
vfe_release_wm(vfe, output->wm_idx[0]);
output->state = VFE_OUTPUT_OFF;
error:
spin_unlock_irqrestore(&vfe->output_lock, flags);
return -EINVAL;
}
/*
* vfe_enable - Enable streaming on VFE line
* @line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
static int vfe_enable(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
int ret;
mutex_lock(&vfe->stream_lock);
if (!vfe->stream_count)
vfe_enable_irq_common(vfe);
vfe->stream_count++;
mutex_unlock(&vfe->stream_lock);
ret = vfe_get_output(line);
if (ret < 0)
goto error_get_output;
ret = vfe_enable_output_v2(line);
if (ret < 0)
goto error_enable_output;
vfe->was_streaming = 1;
return 0;
error_enable_output:
vfe_put_output(line);
error_get_output:
mutex_lock(&vfe->stream_lock);
vfe->stream_count--;
mutex_unlock(&vfe->stream_lock);
return ret;
}
/*
* vfe_isr_sof - Process start of frame interrupt
* @vfe: VFE Device
* @line_id: VFE line
*/
static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id)
{
/* nop */
}
/*
* vfe_isr_reg_update - Process reg update interrupt
* @vfe: VFE Device
* @line_id: VFE line
*/
static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
struct vfe_output *output;
unsigned long flags;
spin_lock_irqsave(&vfe->output_lock, flags);
vfe->res->hw_ops->reg_update_clear(vfe, line_id);
output = &vfe->line[line_id].output;
if (output->wait_reg_update) {
output->wait_reg_update = 0;
complete(&output->reg_update);
}
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
/*
* vfe_isr_wm_done - Process write master done interrupt
* @vfe: VFE Device
* @wm: Write master id
*/
static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm)
{
struct vfe_line *line = &vfe->line[vfe->wm_output_map[wm]];
struct camss_buffer *ready_buf;
struct vfe_output *output;
unsigned long flags;
u32 index;
u64 ts = ktime_get_ns();
spin_lock_irqsave(&vfe->output_lock, flags);
if (vfe->wm_output_map[wm] == VFE_LINE_NONE) {
dev_err_ratelimited(vfe->camss->dev,
"Received wm done for unmapped index\n");
goto out_unlock;
}
output = &vfe->line[vfe->wm_output_map[wm]].output;
ready_buf = output->buf[0];
if (!ready_buf) {
dev_err_ratelimited(vfe->camss->dev,
"Missing ready buf %d!\n", output->state);
goto out_unlock;
}
ready_buf->vb.vb2_buf.timestamp = ts;
ready_buf->vb.sequence = output->sequence++;
index = 0;
output->buf[0] = output->buf[1];
if (output->buf[0])
index = 1;
output->buf[index] = vfe_buf_get_pending(output);
if (output->buf[index])
vfe_wm_update(vfe, output->wm_idx[0], output->buf[index]->addr[0], line);
else
output->gen2.active_num--;
spin_unlock_irqrestore(&vfe->output_lock, flags);
vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
return;
out_unlock:
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
static const struct vfe_isr_ops vfe_isr_ops_170 = {
.reset_ack = vfe_isr_reset_ack,
.halt_ack = vfe_isr_halt_ack,
.reg_update = vfe_isr_reg_update,
.sof = vfe_isr_sof,
.comp_done = vfe_isr_comp_done,
.wm_done = vfe_isr_wm_done,
};
static const struct camss_video_ops vfe_video_ops_170 = {
.queue_buffer = vfe_queue_buffer_v2,
.flush_buffers = vfe_flush_buffers,
};
static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe)
{
vfe->isr_ops = vfe_isr_ops_170;
vfe->video_ops = vfe_video_ops_170;
}
const struct vfe_hw_ops vfe_ops_170 = {
.global_reset = vfe_global_reset,
.hw_version = vfe_hw_version,
.isr_read = vfe_isr_read,
.isr = vfe_isr,
.pm_domain_off = vfe_pm_domain_off,
.pm_domain_on = vfe_pm_domain_on,
.reg_update_clear = vfe_reg_update_clear,
.reg_update = vfe_reg_update,
.subdev_init = vfe_subdev_init,
.vfe_disable = vfe_disable,
.vfe_enable = vfe_enable,
.vfe_halt = vfe_halt,
.violation_read = vfe_violation_read,
.vfe_wm_start = vfe_wm_start,
.vfe_wm_stop = vfe_wm_stop,
.vfe_wm_update = vfe_wm_update,
};
@@ -0,0 +1,384 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module 340 (TFE)
*
* Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include "camss.h"
#include "camss-vfe.h"
#define TFE_GLOBAL_RESET_CMD (0x014)
#define TFE_GLOBAL_RESET_CMD_CORE BIT(0)
#define TFE_REG_UPDATE_CMD (0x02c)
#define TFE_IRQ_CMD (0x030)
#define TFE_IRQ_CMD_CLEAR BIT(0)
#define TFE_IRQ_MASK_0 (0x034)
#define TFE_IRQ_MASK_0_RST_DONE BIT(0)
#define TFE_IRQ_MASK_0_BUS_WR BIT(1)
#define TFE_IRQ_MASK_1 (0x038)
#define TFE_IRQ_MASK_2 (0x03c)
#define TFE_IRQ_CLEAR_0 (0x040)
#define TFE_IRQ_STATUS_0 (0x04c)
#define BUS_REG(a) (0xa00 + (a))
#define TFE_BUS_IRQ_MASK_0 BUS_REG(0x18)
#define TFE_BUS_IRQ_MASK_RUP_DONE_MASK GENMASK(3, 0)
#define TFE_BUS_IRQ_MASK_RUP_DONE(sc) FIELD_PREP(TFE_BUS_IRQ_MASK_RUP_DONE_MASK, BIT(sc))
#define TFE_BUS_IRQ_MASK_BUF_DONE_MASK GENMASK(15, 8)
#define TFE_BUS_IRQ_MASK_BUF_DONE(sg) FIELD_PREP(TFE_BUS_IRQ_MASK_BUF_DONE_MASK, BIT(sg))
#define TFE_BUS_IRQ_MASK_0_CONS_VIOL BIT(28)
#define TFE_BUS_IRQ_MASK_0_VIOL BIT(30)
#define TFE_BUS_IRQ_MASK_0_IMG_VIOL BIT(31)
#define TFE_BUS_IRQ_MASK_1 BUS_REG(0x1c)
#define TFE_BUS_IRQ_CLEAR_0 BUS_REG(0x20)
#define TFE_BUS_IRQ_STATUS_0 BUS_REG(0x28)
#define TFE_BUS_IRQ_CMD BUS_REG(0x30)
#define TFE_BUS_IRQ_CMD_CLEAR BIT(0)
#define TFE_BUS_STATUS_CLEAR BUS_REG(0x60)
#define TFE_BUS_VIOLATION_STATUS BUS_REG(0x64)
#define TFE_BUS_OVERFLOW_STATUS BUS_REG(0x68)
#define TFE_BUS_IMAGE_SZ_VIOLATION_STATUS BUS_REG(0x70)
#define TFE_BUS_CLIENT_CFG(c) BUS_REG(0x200 + (c) * 0x100)
#define TFE_BUS_CLIENT_CFG_EN BIT(0)
#define TFE_BUS_CLIENT_CFG_AUTORECOVER BIT(4)
#define TFE_BUS_CLIENT_CFG_MODE_FRAME BIT(16)
#define TFE_BUS_IMAGE_ADDR(c) BUS_REG(0x204 + (c) * 0x100)
#define TFE_BUS_FRAME_INCR(c) BUS_REG(0x208 + (c) * 0x100)
#define TFE_BUS_IMAGE_CFG_0(c) BUS_REG(0x20c + (c) * 0x100)
#define TFE_BUS_IMAGE_CFG_0_DEFAULT 0xffff
#define TFE_BUS_IMAGE_CFG_1(c) BUS_REG(0x210 + (c) * 0x100)
#define TFE_BUS_IMAGE_CFG_2(c) BUS_REG(0x214 + (c) * 0x100)
#define TFE_BUS_IMAGE_CFG_2_DEFAULT 0xffff
#define TFE_BUS_PACKER_CFG(c) BUS_REG(0x218 + (c) * 0x100)
#define TFE_BUS_PACKER_CFG_FMT_PLAIN8 0x1
#define TFE_BUS_PACKER_CFG_FMT_PLAIN64 0xa
#define TFE_BUS_PACKER_CFG_FMT_MIPI10 0xc
#define TFE_BUS_PACKER_CFG_FMT_MIPI12 0xd
#define TFE_BUS_IRQ_SUBSAMPLE_CFG_0(c) BUS_REG(0x230 + (c) * 0x100)
#define TFE_BUS_IRQ_SUBSAMPLE_CFG_1(c) BUS_REG(0x234 + (c) * 0x100)
#define TFE_BUS_FRAMEDROP_CFG_0(c) BUS_REG(0x238 + (c) * 0x100)
#define TFE_BUS_FRAMEDROP_CFG_1(c) BUS_REG(0x23c + (c) * 0x100)
#define PP_CROP_REG(a) (0x2800 + (a))
#define TFE_PP_CROP_CFG PP_CROP_REG(0x60)
#define TFE_PP_CROP_CFG_EN (BIT(0) | BIT(9))
#define TFE_PP_CROP_LINE_CFG PP_CROP_REG(0x68)
#define TFE_PP_CROP_FIRST GENMASK(29, 16)
#define TFE_PP_CROP_LAST GENMASK(13, 0)
#define TFE_PP_CROP_PIX_CFG PP_CROP_REG(0x6C)
enum tfe_client {
TFE_CLI_BAYER,
TFE_CLI_IDEAL_RAW,
TFE_CLI_STATS_TINTLESS_BG,
TFE_CLI_STATS_BHIST,
TFE_CLI_STATS_AWB_BG,
TFE_CLI_STATS_AEC_BG,
TFE_CLI_STATS_BAF,
TFE_CLI_RDI0,
TFE_CLI_RDI1,
TFE_CLI_RDI2,
TFE_CLI_NUM
};
enum tfe_iface {
TFE_IFACE_PIX,
TFE_IFACE_RDI0,
TFE_IFACE_RDI1,
TFE_IFACE_RDI2,
TFE_IFACE_NUM
};
enum tfe_subgroups {
TFE_SUBGROUP_BAYER,
TFE_SUBGROUP_IDEAL_RAW,
TFE_SUBGROUP_HDR,
TFE_SUBGROUP_BG,
TFE_SUBGROUP_BAF,
TFE_SUBGROUP_RDI0,
TFE_SUBGROUP_RDI1,
TFE_SUBGROUP_RDI2,
TFE_SUBGROUP_NUM
};
static enum tfe_client tfe_wm_client_map[VFE_LINE_NUM_MAX] = {
[VFE_LINE_RDI0] = TFE_CLI_RDI0,
[VFE_LINE_RDI1] = TFE_CLI_RDI1,
[VFE_LINE_RDI2] = TFE_CLI_RDI2,
[VFE_LINE_PIX] = TFE_CLI_BAYER,
};
static enum tfe_iface tfe_line_iface_map[VFE_LINE_NUM_MAX] = {
[VFE_LINE_RDI0] = TFE_IFACE_RDI0,
[VFE_LINE_RDI1] = TFE_IFACE_RDI1,
[VFE_LINE_RDI2] = TFE_IFACE_RDI2,
[VFE_LINE_PIX] = TFE_IFACE_PIX,
};
static enum vfe_line_id tfe_subgroup_line_map[TFE_SUBGROUP_NUM] = {
[TFE_SUBGROUP_BAYER] = VFE_LINE_PIX,
[TFE_SUBGROUP_IDEAL_RAW] = VFE_LINE_PIX,
[TFE_SUBGROUP_HDR] = VFE_LINE_PIX,
[TFE_SUBGROUP_BG] = VFE_LINE_PIX,
[TFE_SUBGROUP_BAF] = VFE_LINE_PIX,
[TFE_SUBGROUP_RDI0] = VFE_LINE_RDI0,
[TFE_SUBGROUP_RDI1] = VFE_LINE_RDI1,
[TFE_SUBGROUP_RDI2] = VFE_LINE_RDI2,
};
static inline enum tfe_iface __line_to_iface(enum vfe_line_id line_id)
{
if (line_id <= VFE_LINE_NONE || line_id >= VFE_LINE_NUM_MAX) {
pr_warn("VFE: Invalid line %d\n", line_id);
return TFE_IFACE_RDI0;
}
return tfe_line_iface_map[line_id];
}
static inline enum vfe_line_id __iface_to_line(unsigned int iface)
{
int i;
for (i = 0; i < VFE_LINE_NUM_MAX; i++) {
if (tfe_line_iface_map[i] == iface)
return i;
}
return VFE_LINE_NONE;
}
static inline enum vfe_line_id __subgroup_to_line(enum tfe_subgroups sg)
{
if (sg >= TFE_SUBGROUP_NUM)
return VFE_LINE_NONE;
return tfe_subgroup_line_map[sg];
}
static void vfe_global_reset(struct vfe_device *vfe)
{
writel(TFE_IRQ_MASK_0_RST_DONE, vfe->base + TFE_IRQ_MASK_0);
writel(TFE_GLOBAL_RESET_CMD_CORE, vfe->base + TFE_GLOBAL_RESET_CMD);
}
static irqreturn_t vfe_isr(int irq, void *dev)
{
struct vfe_device *vfe = dev;
u32 status;
int i;
status = readl_relaxed(vfe->base + TFE_IRQ_STATUS_0);
writel_relaxed(status, vfe->base + TFE_IRQ_CLEAR_0);
writel_relaxed(TFE_IRQ_CMD_CLEAR, vfe->base + TFE_IRQ_CMD);
if (status & TFE_IRQ_MASK_0_RST_DONE) {
dev_dbg(vfe->camss->dev, "VFE%u: Reset done!", vfe->id);
vfe_isr_reset_ack(vfe);
}
if (status & TFE_IRQ_MASK_0_BUS_WR) {
u32 bus_status = readl_relaxed(vfe->base + TFE_BUS_IRQ_STATUS_0);
writel_relaxed(bus_status, vfe->base + TFE_BUS_IRQ_CLEAR_0);
writel_relaxed(TFE_BUS_IRQ_CMD_CLEAR, vfe->base + TFE_BUS_IRQ_CMD);
for (i = 0; i < TFE_IFACE_NUM; i++) {
if (bus_status & TFE_BUS_IRQ_MASK_RUP_DONE(i))
vfe->res->hw_ops->reg_update_clear(vfe, __iface_to_line(i));
}
for (i = 0; i < TFE_SUBGROUP_NUM; i++) {
if (bus_status & TFE_BUS_IRQ_MASK_BUF_DONE(i))
vfe_buf_done(vfe, __subgroup_to_line(i));
}
if (bus_status & TFE_BUS_IRQ_MASK_0_CONS_VIOL)
dev_err_ratelimited(vfe->camss->dev, "VFE%u: Bad config violation",
vfe->id);
if (bus_status & TFE_BUS_IRQ_MASK_0_VIOL)
dev_err_ratelimited(vfe->camss->dev, "VFE%u: Input data violation",
vfe->id);
if (bus_status & TFE_BUS_IRQ_MASK_0_IMG_VIOL)
dev_err_ratelimited(vfe->camss->dev, "VFE%u: Image size violation",
vfe->id);
}
status = readl_relaxed(vfe->base + TFE_BUS_OVERFLOW_STATUS);
if (status) {
writel_relaxed(status, vfe->base + TFE_BUS_STATUS_CLEAR);
for (i = 0; i < TFE_CLI_NUM; i++) {
if (status & BIT(i))
dev_err_ratelimited(vfe->camss->dev,
"VFE%u: bus overflow for client %u\n",
vfe->id, i);
}
}
return IRQ_HANDLED;
}
static int vfe_halt(struct vfe_device *vfe)
{
/* rely on vfe_disable_output() to stop the VFE */
return 0;
}
static void vfe_enable_irq(struct vfe_device *vfe)
{
writel(TFE_IRQ_MASK_0_RST_DONE | TFE_IRQ_MASK_0_BUS_WR,
vfe->base + TFE_IRQ_MASK_0);
writel(TFE_BUS_IRQ_MASK_RUP_DONE_MASK | TFE_BUS_IRQ_MASK_BUF_DONE_MASK |
TFE_BUS_IRQ_MASK_0_CONS_VIOL | TFE_BUS_IRQ_MASK_0_VIOL |
TFE_BUS_IRQ_MASK_0_IMG_VIOL, vfe->base + TFE_BUS_IRQ_MASK_0);
}
static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr,
struct vfe_line *line)
{
u8 client = tfe_wm_client_map[wm];
writel_relaxed(addr, vfe->base + TFE_BUS_IMAGE_ADDR(client));
}
static u32 vfe_packer_format(struct vfe_device *vfe, u32 pixelformat)
{
const struct camss_formats *fmt = vfe->res->formats_rdi;
unsigned int bpp = 0;
int i;
for (i = 0; i < fmt->nformats; i++) {
if (fmt->formats[i].pixelformat == pixelformat) {
bpp = fmt->formats[i].mbus_bpp;
break;
}
}
switch (bpp) {
case 10:
return TFE_BUS_PACKER_CFG_FMT_MIPI10;
case 12:
return TFE_BUS_PACKER_CFG_FMT_MIPI12;
default:
return TFE_BUS_PACKER_CFG_FMT_PLAIN8;
}
}
static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line)
{
struct v4l2_pix_format_mplane *pix = &line->video_out.active_fmt.fmt.pix_mp;
u32 stride = pix->plane_fmt[0].bytesperline;
u8 client = tfe_wm_client_map[wm];
u32 cfg = TFE_BUS_CLIENT_CFG_EN;
if (client == TFE_CLI_BAYER) { /* PIX - Line based */
struct v4l2_rect *crop = &line->crop;
/* Cropping */
writel_relaxed(TFE_PP_CROP_CFG_EN, vfe->base + TFE_PP_CROP_CFG);
writel_relaxed(FIELD_PREP(TFE_PP_CROP_FIRST, crop->top) |
FIELD_PREP(TFE_PP_CROP_LAST, crop->top + crop->height - 1),
vfe->base + TFE_PP_CROP_LINE_CFG);
writel_relaxed(FIELD_PREP(TFE_PP_CROP_FIRST, crop->left) |
FIELD_PREP(TFE_PP_CROP_LAST, crop->left + crop->width - 1),
vfe->base + TFE_PP_CROP_PIX_CFG);
/* Write Engine */
writel_relaxed(pix->width | (pix->height << 16),
vfe->base + TFE_BUS_IMAGE_CFG_0(client));
writel_relaxed(0u, vfe->base + TFE_BUS_IMAGE_CFG_1(client));
writel_relaxed(stride, vfe->base + TFE_BUS_IMAGE_CFG_2(client));
writel_relaxed(stride * pix->height, vfe->base + TFE_BUS_FRAME_INCR(client));
writel_relaxed(vfe_packer_format(vfe, pix->pixelformat),
vfe->base + TFE_BUS_PACKER_CFG(client));
cfg |= TFE_BUS_CLIENT_CFG_AUTORECOVER;
} else { /* RDI - Frame based */
writel_relaxed(TFE_BUS_IMAGE_CFG_0_DEFAULT,
vfe->base + TFE_BUS_IMAGE_CFG_0(client));
writel_relaxed(0u, vfe->base + TFE_BUS_IMAGE_CFG_1(client));
writel_relaxed(TFE_BUS_IMAGE_CFG_2_DEFAULT,
vfe->base + TFE_BUS_IMAGE_CFG_2(client));
writel_relaxed(stride * pix->height, vfe->base + TFE_BUS_FRAME_INCR(client));
writel_relaxed(TFE_BUS_PACKER_CFG_FMT_PLAIN64,
vfe->base + TFE_BUS_PACKER_CFG(client));
cfg |= TFE_BUS_CLIENT_CFG_MODE_FRAME;
}
/* No dropped frames, one irq per frame */
writel_relaxed(0, vfe->base + TFE_BUS_FRAMEDROP_CFG_0(client));
writel_relaxed(1, vfe->base + TFE_BUS_FRAMEDROP_CFG_1(client));
writel_relaxed(0, vfe->base + TFE_BUS_IRQ_SUBSAMPLE_CFG_0(client));
writel_relaxed(1, vfe->base + TFE_BUS_IRQ_SUBSAMPLE_CFG_1(client));
vfe_enable_irq(vfe);
writel(cfg, vfe->base + TFE_BUS_CLIENT_CFG(client));
dev_dbg(vfe->camss->dev, "VFE%u: Started client %u width %u height %u stride %u\n",
vfe->id, client, pix->width, pix->height, stride);
}
static void vfe_wm_stop(struct vfe_device *vfe, u8 wm)
{
u8 client = tfe_wm_client_map[wm];
writel(0, vfe->base + TFE_BUS_CLIENT_CFG(client));
dev_dbg(vfe->camss->dev, "VFE%u: Stopped client %u\n", vfe->id, client);
}
static const struct camss_video_ops vfe_video_ops_520 = {
.queue_buffer = vfe_queue_buffer_v2,
.flush_buffers = vfe_flush_buffers,
};
static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe)
{
vfe->video_ops = vfe_video_ops_520;
}
static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
vfe->reg_update |= BIT(__line_to_iface(line_id));
writel_relaxed(vfe->reg_update, vfe->base + TFE_REG_UPDATE_CMD);
}
static void vfe_reg_update_clear(struct vfe_device *vfe, enum vfe_line_id line_id)
{
vfe->reg_update &= ~BIT(__line_to_iface(line_id));
}
const struct vfe_hw_ops vfe_ops_340 = {
.global_reset = vfe_global_reset,
.hw_version = vfe_hw_version,
.isr = vfe_isr,
.pm_domain_off = vfe_pm_domain_off,
.pm_domain_on = vfe_pm_domain_on,
.subdev_init = vfe_subdev_init,
.vfe_disable = vfe_disable,
.vfe_enable = vfe_enable_v2,
.vfe_halt = vfe_halt,
.vfe_wm_start = vfe_wm_start,
.vfe_wm_stop = vfe_wm_stop,
.vfe_buf_done = vfe_buf_done,
.vfe_wm_update = vfe_wm_update,
.reg_update = vfe_reg_update,
.reg_update_clear = vfe_reg_update_clear,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-vfe-480.c
*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v480 (SM8250)
*
* Copyright (C) 2020-2021 Linaro Ltd.
* Copyright (C) 2021 Jonathan Marek
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include "camss.h"
#include "camss-vfe.h"
#define VFE_GLOBAL_RESET_CMD (vfe_is_lite(vfe) ? 0x0c : 0x1c)
#define GLOBAL_RESET_HW_AND_REG (vfe_is_lite(vfe) ? BIT(1) : BIT(0))
#define VFE_REG_UPDATE_CMD (vfe_is_lite(vfe) ? 0x20 : 0x34)
static inline int reg_update_rdi(struct vfe_device *vfe, int n)
{
return vfe_is_lite(vfe) ? BIT(n) : BIT(1 + (n));
}
#define REG_UPDATE_RDI reg_update_rdi
#define VFE_IRQ_CMD (vfe_is_lite(vfe) ? 0x24 : 0x38)
#define IRQ_CMD_GLOBAL_CLEAR BIT(0)
#define VFE_IRQ_MASK(n) ((vfe_is_lite(vfe) ? 0x28 : 0x3c) + (n) * 4)
#define IRQ_MASK_0_RESET_ACK (vfe_is_lite(vfe) ? BIT(17) : BIT(0))
#define IRQ_MASK_0_BUS_TOP_IRQ (vfe_is_lite(vfe) ? BIT(4) : BIT(7))
#define VFE_IRQ_CLEAR(n) ((vfe_is_lite(vfe) ? 0x34 : 0x48) + (n) * 4)
#define VFE_IRQ_STATUS(n) ((vfe_is_lite(vfe) ? 0x40 : 0x54) + (n) * 4)
#define BUS_REG_BASE (vfe_is_lite(vfe) ? 0x1a00 : 0xaa00)
#define VFE_BUS_WM_CGC_OVERRIDE (BUS_REG_BASE + 0x08)
#define WM_CGC_OVERRIDE_ALL (0x3FFFFFF)
#define VFE_BUS_WM_TEST_BUS_CTRL (BUS_REG_BASE + 0xdc)
#define VFE_BUS_IRQ_MASK(n) (BUS_REG_BASE + 0x18 + (n) * 4)
static inline int bus_irq_mask_0_rdi_rup(struct vfe_device *vfe, int n)
{
return vfe_is_lite(vfe) ? BIT(n) : BIT(3 + (n));
}
#define BUS_IRQ_MASK_0_RDI_RUP bus_irq_mask_0_rdi_rup
static inline int bus_irq_mask_0_comp_done(struct vfe_device *vfe, int n)
{
return vfe_is_lite(vfe) ? BIT(4 + (n)) : BIT(6 + (n));
}
#define BUS_IRQ_MASK_0_COMP_DONE bus_irq_mask_0_comp_done
#define VFE_BUS_IRQ_CLEAR(n) (BUS_REG_BASE + 0x20 + (n) * 4)
#define VFE_BUS_IRQ_STATUS(n) (BUS_REG_BASE + 0x28 + (n) * 4)
#define VFE_BUS_IRQ_CLEAR_GLOBAL (BUS_REG_BASE + 0x30)
#define VFE_BUS_WM_CFG(n) (BUS_REG_BASE + 0x200 + (n) * 0x100)
#define WM_CFG_EN (0)
#define WM_CFG_MODE (16)
#define MODE_QCOM_PLAIN (0)
#define MODE_MIPI_RAW (1)
#define VFE_BUS_WM_IMAGE_ADDR(n) (BUS_REG_BASE + 0x204 + (n) * 0x100)
#define VFE_BUS_WM_FRAME_INCR(n) (BUS_REG_BASE + 0x208 + (n) * 0x100)
#define VFE_BUS_WM_IMAGE_CFG_0(n) (BUS_REG_BASE + 0x20c + (n) * 0x100)
#define WM_IMAGE_CFG_0_DEFAULT_WIDTH (0xFFFF)
#define VFE_BUS_WM_IMAGE_CFG_1(n) (BUS_REG_BASE + 0x210 + (n) * 0x100)
#define VFE_BUS_WM_IMAGE_CFG_2(n) (BUS_REG_BASE + 0x214 + (n) * 0x100)
#define VFE_BUS_WM_PACKER_CFG(n) (BUS_REG_BASE + 0x218 + (n) * 0x100)
#define VFE_BUS_WM_HEADER_ADDR(n) (BUS_REG_BASE + 0x220 + (n) * 0x100)
#define VFE_BUS_WM_HEADER_INCR(n) (BUS_REG_BASE + 0x224 + (n) * 0x100)
#define VFE_BUS_WM_HEADER_CFG(n) (BUS_REG_BASE + 0x228 + (n) * 0x100)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (BUS_REG_BASE + 0x230 + (n) * 0x100)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (BUS_REG_BASE + 0x234 + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (BUS_REG_BASE + 0x238 + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (BUS_REG_BASE + 0x23c + (n) * 0x100)
#define VFE_BUS_WM_SYSTEM_CACHE_CFG(n) (BUS_REG_BASE + 0x260 + (n) * 0x100)
#define VFE_BUS_WM_BURST_LIMIT(n) (BUS_REG_BASE + 0x264 + (n) * 0x100)
/* for titan 480, each bus client is hardcoded to a specific path
* and each bus client is part of a hardcoded "comp group"
*/
#define RDI_WM(n) ((vfe_is_lite(vfe) ? 0 : 23) + (n))
#define RDI_COMP_GROUP(n) ((vfe_is_lite(vfe) ? 0 : 11) + (n))
#define MAX_VFE_OUTPUT_LINES 4
static void vfe_global_reset(struct vfe_device *vfe)
{
writel_relaxed(IRQ_MASK_0_RESET_ACK, vfe->base + VFE_IRQ_MASK(0));
writel_relaxed(GLOBAL_RESET_HW_AND_REG, vfe->base + VFE_GLOBAL_RESET_CMD);
}
static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line)
{
struct v4l2_pix_format_mplane *pix =
&line->video_out.active_fmt.fmt.pix_mp;
wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */
/* no clock gating at bus input */
writel_relaxed(WM_CGC_OVERRIDE_ALL, vfe->base + VFE_BUS_WM_CGC_OVERRIDE);
writel_relaxed(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL);
writel_relaxed(pix->plane_fmt[0].bytesperline * pix->height,
vfe->base + VFE_BUS_WM_FRAME_INCR(wm));
writel_relaxed(0xf, vfe->base + VFE_BUS_WM_BURST_LIMIT(wm));
writel_relaxed(WM_IMAGE_CFG_0_DEFAULT_WIDTH,
vfe->base + VFE_BUS_WM_IMAGE_CFG_0(wm));
writel_relaxed(pix->plane_fmt[0].bytesperline,
vfe->base + VFE_BUS_WM_IMAGE_CFG_2(wm));
writel_relaxed(0, vfe->base + VFE_BUS_WM_PACKER_CFG(wm));
/* no dropped frames, one irq per frame */
writel_relaxed(0, vfe->base + VFE_BUS_WM_FRAMEDROP_PERIOD(wm));
writel_relaxed(1, vfe->base + VFE_BUS_WM_FRAMEDROP_PATTERN(wm));
writel_relaxed(0, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(wm));
writel_relaxed(1, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(wm));
writel_relaxed(1 << WM_CFG_EN | MODE_MIPI_RAW << WM_CFG_MODE,
vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_stop(struct vfe_device *vfe, u8 wm)
{
wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */
writel_relaxed(0, vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr,
struct vfe_line *line)
{
wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */
writel_relaxed(addr, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm));
}
static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
vfe->reg_update |= REG_UPDATE_RDI(vfe, line_id);
writel_relaxed(vfe->reg_update, vfe->base + VFE_REG_UPDATE_CMD);
}
static inline void vfe_reg_update_clear(struct vfe_device *vfe,
enum vfe_line_id line_id)
{
vfe->reg_update &= ~REG_UPDATE_RDI(vfe, line_id);
}
static void vfe_enable_irq(struct vfe_device *vfe)
{
int i;
u32 bus_irq_mask = 0;
if (!vfe->stream_count)
/* enable reset ack IRQ and top BUS status IRQ */
writel(IRQ_MASK_0_RESET_ACK | IRQ_MASK_0_BUS_TOP_IRQ,
vfe->base + VFE_IRQ_MASK(0));
for (i = 0; i < MAX_VFE_OUTPUT_LINES; i++) {
/* Enable IRQ for newly added lines, but also keep already running lines's IRQ */
if (vfe->line[i].output.state == VFE_OUTPUT_RESERVED ||
vfe->line[i].output.state == VFE_OUTPUT_ON) {
bus_irq_mask |= BUS_IRQ_MASK_0_RDI_RUP(vfe, i)
| BUS_IRQ_MASK_0_COMP_DONE(vfe, RDI_COMP_GROUP(i));
}
}
writel(bus_irq_mask, vfe->base + VFE_BUS_IRQ_MASK(0));
}
static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id);
/*
* vfe_isr - VFE module interrupt handler
* @irq: Interrupt line
* @dev: VFE device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t vfe_isr(int irq, void *dev)
{
struct vfe_device *vfe = dev;
u32 status;
int i;
status = readl_relaxed(vfe->base + VFE_IRQ_STATUS(0));
writel_relaxed(status, vfe->base + VFE_IRQ_CLEAR(0));
writel_relaxed(IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD);
if (status & IRQ_MASK_0_RESET_ACK)
vfe_isr_reset_ack(vfe);
if (status & IRQ_MASK_0_BUS_TOP_IRQ) {
u32 status = readl_relaxed(vfe->base + VFE_BUS_IRQ_STATUS(0));
writel_relaxed(status, vfe->base + VFE_BUS_IRQ_CLEAR(0));
writel_relaxed(1, vfe->base + VFE_BUS_IRQ_CLEAR_GLOBAL);
for (i = 0; i < MAX_VFE_OUTPUT_LINES; i++) {
if (status & BUS_IRQ_MASK_0_RDI_RUP(vfe, i))
vfe_isr_reg_update(vfe, i);
}
/* Loop through all WMs IRQs */
for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) {
if (status & BUS_IRQ_MASK_0_COMP_DONE(vfe, RDI_COMP_GROUP(i)))
vfe_buf_done(vfe, i);
}
}
return IRQ_HANDLED;
}
/*
* vfe_halt - Trigger halt on VFE module and wait to complete
* @vfe: VFE device
*
* Return 0 on success or a negative error code otherwise
*/
static int vfe_halt(struct vfe_device *vfe)
{
/* rely on vfe_disable_output() to stop the VFE */
return 0;
}
/*
* vfe_isr_reg_update - Process reg update interrupt
* @vfe: VFE Device
* @line_id: VFE line
*/
static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
struct vfe_output *output;
unsigned long flags;
spin_lock_irqsave(&vfe->output_lock, flags);
vfe_reg_update_clear(vfe, line_id);
output = &vfe->line[line_id].output;
if (output->wait_reg_update) {
output->wait_reg_update = 0;
complete(&output->reg_update);
}
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
static const struct camss_video_ops vfe_video_ops_480 = {
.queue_buffer = vfe_queue_buffer_v2,
.flush_buffers = vfe_flush_buffers,
};
static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe)
{
vfe->video_ops = vfe_video_ops_480;
}
static void vfe_isr_read(struct vfe_device *vfe, u32 *value0, u32 *value1)
{
/* nop */
}
static void vfe_violation_read(struct vfe_device *vfe)
{
/* nop */
}
static void vfe_buf_done_480(struct vfe_device *vfe, int port_id)
{
/* nop */
}
const struct vfe_hw_ops vfe_ops_480 = {
.enable_irq = vfe_enable_irq,
.global_reset = vfe_global_reset,
.hw_version = vfe_hw_version,
.isr = vfe_isr,
.isr_read = vfe_isr_read,
.reg_update = vfe_reg_update,
.reg_update_clear = vfe_reg_update_clear,
.pm_domain_off = vfe_pm_domain_off,
.pm_domain_on = vfe_pm_domain_on,
.subdev_init = vfe_subdev_init,
.vfe_disable = vfe_disable,
.vfe_enable = vfe_enable_v2,
.vfe_halt = vfe_halt,
.violation_read = vfe_violation_read,
.vfe_wm_start = vfe_wm_start,
.vfe_wm_stop = vfe_wm_stop,
.vfe_buf_done = vfe_buf_done_480,
.vfe_wm_update = vfe_wm_update,
};
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-vfe-680.c
*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v680
*
* Copyright (C) 2025 Linaro Ltd.
*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include "camss.h"
#include "camss-vfe.h"
#define VFE_TOP_IRQn_STATUS(vfe, n) ((vfe_is_lite(vfe) ? 0x1c : 0x44) + (n) * 4)
#define VFE_TOP_IRQn_MASK(vfe, n) ((vfe_is_lite(vfe) ? 0x24 : 0x34) + (n) * 4)
#define VFE_TOP_IRQn_CLEAR(vfe, n) ((vfe_is_lite(vfe) ? 0x2c : 0x3c) + (n) * 4)
#define VFE_IRQ1_SOF(vfe, n) ((vfe_is_lite(vfe) ? BIT(2) : BIT(8)) << ((n) * 2))
#define VFE_IRQ1_EOF(vfe, n) ((vfe_is_lite(vfe) ? BIT(3) : BIT(9)) << ((n) * 2))
#define VFE_TOP_IRQ_CMD(vfe) (vfe_is_lite(vfe) ? 0x38 : 0x30)
#define VFE_TOP_IRQ_CMD_GLOBAL_CLEAR BIT(0)
#define VFE_TOP_DIAG_CONFIG (vfe_is_lite(vfe) ? 0x40 : 0x50)
#define VFE_TOP_DEBUG_11(vfe) (vfe_is_lite(vfe) ? 0x40 : 0xcc)
#define VFE_TOP_DEBUG_12(vfe) (vfe_is_lite(vfe) ? 0x40 : 0xd0)
#define VFE_TOP_DEBUG_13(vfe) (vfe_is_lite(vfe) ? 0x40 : 0xd4)
#define VFE_BUS_IRQn_MASK(vfe, n) ((vfe_is_lite(vfe) ? 0x218 : 0xc18) + (n) * 4)
#define VFE_BUS_IRQn_CLEAR(vfe, n) ((vfe_is_lite(vfe) ? 0x220 : 0xc20) + (n) * 4)
#define VFE_BUS_IRQn_STATUS(vfe, n) ((vfe_is_lite(vfe) ? 0x228 : 0xc28) + (n) * 4)
#define VFE_BUS_IRQ_GLOBAL_CLEAR(vfe) (vfe_is_lite(vfe) ? 0x230 : 0xc30)
#define VFE_BUS_WR_VIOLATION_STATUS(vfe) (vfe_is_lite(vfe) ? 0x264 : 0xc64)
#define VFE_BUS_WR_OVERFLOW_STATUS(vfe) (vfe_is_lite(vfe) ? 0x268 : 0xc68)
#define VFE_BUS_WR_IMAGE_VIOLATION_STATUS(vfe) (vfe_is_lite(vfe) ? 0x270 : 0xc70)
#define VFE_BUS_WRITE_CLIENT_CFG(vfe, c) ((vfe_is_lite(vfe) ? 0x400 : 0xe00) + (c) * 0x100)
#define VFE_BUS_WRITE_CLIENT_CFG_EN BIT(0)
#define VFE_BUS_IMAGE_ADDR(vfe, c) ((vfe_is_lite(vfe) ? 0x404 : 0xe04) + (c) * 0x100)
#define VFE_BUS_FRAME_INCR(vfe, c) ((vfe_is_lite(vfe) ? 0x408 : 0xe08) + (c) * 0x100)
#define VFE_BUS_IMAGE_CFG0(vfe, c) ((vfe_is_lite(vfe) ? 0x40c : 0xe0c) + (c) * 0x100)
#define VFE_BUS_IMAGE_CFG0_DATA(h, s) (((h) << 16) | ((s) >> 4))
#define WM_IMAGE_CFG_0_DEFAULT_WIDTH (0xFFFF)
#define VFE_BUS_IMAGE_CFG1(vfe, c) ((vfe_is_lite(vfe) ? 0x410 : 0xe10) + (c) * 0x100)
#define VFE_BUS_IMAGE_CFG2(vfe, c) ((vfe_is_lite(vfe) ? 0x414 : 0xe14) + (c) * 0x100)
#define VFE_BUS_PACKER_CFG(vfe, c) ((vfe_is_lite(vfe) ? 0x418 : 0xe18) + (c) * 0x100)
#define VFE_BUS_IRQ_SUBSAMPLE_PERIOD(vfe, c) ((vfe_is_lite(vfe) ? 0x430 : 0xe30) + (c) * 0x100)
#define VFE_BUS_IRQ_SUBSAMPLE_PATTERN(vfe, c) ((vfe_is_lite(vfe) ? 0x434 : 0xe34) + (c) * 0x100)
#define VFE_BUS_FRAMEDROP_PERIOD(vfe, c) ((vfe_is_lite(vfe) ? 0x438 : 0xe38) + (c) * 0x100)
#define VFE_BUS_FRAMEDROP_PATTERN(vfe, c) ((vfe_is_lite(vfe) ? 0x43c : 0xe3c) + (c) * 0x100)
#define VFE_BUS_MMU_PREFETCH_CFG(vfe, c) ((vfe_is_lite(vfe) ? 0x460 : 0xe60) + (c) * 0x100)
#define VFE_BUS_MMU_PREFETCH_CFG_EN BIT(0)
#define VFE_BUS_MMU_PREFETCH_MAX_OFFSET(vfe, c) ((vfe_is_lite(vfe) ? 0x464 : 0xe64) + (c) * 0x100)
#define VFE_BUS_ADDR_STATUS0(vfe, c) ((vfe_is_lite(vfe) ? 0x470 : 0xe70) + (c) * 0x100)
/*
* TODO: differentiate the port id based on requested type of RDI, BHIST etc
*
* IFE write master IDs
*
* VIDEO_FULL_Y 0
* VIDEO_FULL_C 1
* VIDEO_DS_4:1 2
* VIDEO_DS_16:1 3
* DISPLAY_FULL_Y 4
* DISPLAY_FULL_C 5
* DISPLAY_DS_4:1 6
* DISPLAY_DS_16:1 7
* FD_Y 8
* FD_C 9
* PIXEL_RAW 10
* STATS_BE0 11
* STATS_BHIST0 12
* STATS_TINTLESS_BG 13
* STATS_AWB_BG 14
* STATS_AWB_BFW 15
* STATS_BAF 16
* STATS_BHIST 17
* STATS_RS 18
* STATS_IHIST 19
* SPARSE_PD 20
* PDAF_V2.0_PD_DATA 21
* PDAF_V2.0_SAD 22
* LCR 23
* RDI0 24
* RDI1 25
* RDI2 26
* LTM_STATS 27
*
* IFE Lite write master IDs
*
* RDI0 0
* RDI1 1
* RDI2 2
* RDI3 3
* GAMMA 4
* BE 5
*/
/* TODO: assign an ENUM in resources and use the provided master
* id directly for RDI, STATS, AWB_BG, BHIST.
* This macro only works because RDI is all we support right now.
*/
#define RDI_WM(n) ((vfe_is_lite(vfe) ? 0 : 24) + (n))
static void vfe_global_reset(struct vfe_device *vfe)
{
/* VFE680 has no global reset, simply report a completion */
complete(&vfe->reset_complete);
}
/*
* vfe_isr - VFE module interrupt handler
* @irq: Interrupt line
* @dev: VFE device
*
* Return IRQ_HANDLED on success
*/
static irqreturn_t vfe_isr(int irq, void *dev)
{
return IRQ_HANDLED;
}
/*
* vfe_halt - Trigger halt on VFE module and wait to complete
* @vfe: VFE device
*
* Return 0 on success or a negative error code otherwise
*/
static int vfe_halt(struct vfe_device *vfe)
{
/* rely on vfe_disable_output() to stop the VFE */
return 0;
}
static void vfe_disable_irq(struct vfe_device *vfe)
{
writel(0u, vfe->base + VFE_TOP_IRQn_MASK(vfe, 0));
writel(0u, vfe->base + VFE_TOP_IRQn_MASK(vfe, 1));
writel(0u, vfe->base + VFE_BUS_IRQn_MASK(vfe, 0));
writel(0u, vfe->base + VFE_BUS_IRQn_MASK(vfe, 1));
}
static void vfe_wm_update(struct vfe_device *vfe, u8 rdi, u32 addr,
struct vfe_line *line)
{
u8 wm = RDI_WM(rdi);
writel(addr, vfe->base + VFE_BUS_IMAGE_ADDR(vfe, wm));
}
static void vfe_wm_start(struct vfe_device *vfe, u8 rdi, struct vfe_line *line)
{
struct v4l2_pix_format_mplane *pix =
&line->video_out.active_fmt.fmt.pix_mp;
u32 stride = pix->plane_fmt[0].bytesperline;
u32 cfg;
u8 wm;
cfg = VFE_BUS_IMAGE_CFG0_DATA(pix->height, stride);
wm = RDI_WM(rdi);
writel(cfg, vfe->base + VFE_BUS_IMAGE_CFG0(vfe, wm));
writel(0, vfe->base + VFE_BUS_IMAGE_CFG1(vfe, wm));
writel(stride, vfe->base + VFE_BUS_IMAGE_CFG2(vfe, wm));
writel(0, vfe->base + VFE_BUS_PACKER_CFG(vfe, wm));
/* Set total frame increment value */
writel(pix->plane_fmt[0].bytesperline * pix->height,
vfe->base + VFE_BUS_FRAME_INCR(vfe, wm));
/* MMU */
writel(VFE_BUS_MMU_PREFETCH_CFG_EN, vfe->base + VFE_BUS_MMU_PREFETCH_CFG(vfe, wm));
writel(~0u, vfe->base + VFE_BUS_MMU_PREFETCH_MAX_OFFSET(vfe, wm));
/* no dropped frames, one irq per frame */
writel(1, vfe->base + VFE_BUS_FRAMEDROP_PATTERN(vfe, wm));
writel(0, vfe->base + VFE_BUS_FRAMEDROP_PERIOD(vfe, wm));
writel(1, vfe->base + VFE_BUS_IRQ_SUBSAMPLE_PATTERN(vfe, wm));
writel(0, vfe->base + VFE_BUS_IRQ_SUBSAMPLE_PERIOD(vfe, wm));
/* We don't process IRQs for VFE in RDI mode at the moment */
vfe_disable_irq(vfe);
/* Enable WM */
writel(VFE_BUS_WRITE_CLIENT_CFG_EN,
vfe->base + VFE_BUS_WRITE_CLIENT_CFG(vfe, wm));
dev_dbg(vfe->camss->dev, "RDI%d WM:%d width %d height %d stride %d\n",
rdi, wm, pix->width, pix->height, stride);
}
static void vfe_wm_stop(struct vfe_device *vfe, u8 rdi)
{
u8 wm = RDI_WM(rdi);
writel(0, vfe->base + VFE_BUS_WRITE_CLIENT_CFG(vfe, wm));
}
static const struct camss_video_ops vfe_video_ops_680 = {
.queue_buffer = vfe_queue_buffer_v2,
.flush_buffers = vfe_flush_buffers,
};
static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe)
{
vfe->video_ops = vfe_video_ops_680;
}
static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
int port_id = line_id;
camss_reg_update(vfe->camss, vfe->id, port_id, false);
}
static inline void vfe_reg_update_clear(struct vfe_device *vfe,
enum vfe_line_id line_id)
{
int port_id = line_id;
camss_reg_update(vfe->camss, vfe->id, port_id, true);
}
const struct vfe_hw_ops vfe_ops_680 = {
.global_reset = vfe_global_reset,
.hw_version = vfe_hw_version,
.isr = vfe_isr,
.pm_domain_off = vfe_pm_domain_off,
.pm_domain_on = vfe_pm_domain_on,
.subdev_init = vfe_subdev_init,
.vfe_disable = vfe_disable,
.vfe_enable = vfe_enable_v2,
.vfe_halt = vfe_halt,
.vfe_wm_start = vfe_wm_start,
.vfe_wm_stop = vfe_wm_stop,
.vfe_buf_done = vfe_buf_done,
.vfe_wm_update = vfe_wm_update,
.reg_update = vfe_reg_update,
.reg_update_clear = vfe_reg_update_clear,
};
@@ -0,0 +1,743 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-vfe-gen1.c
*
* Qualcomm MSM Camera Subsystem - VFE Common functionality for Gen 1 versions of hw (4.1, 4.7..)
*
* Copyright (C) 2020 Linaro Ltd.
*/
#include "camss.h"
#include "camss-vfe.h"
#include "camss-vfe-gen1.h"
/* Max number of frame drop updates per frame */
#define VFE_FRAME_DROP_UPDATES 2
#define VFE_NEXT_SOF_MS 500
int vfe_gen1_halt(struct vfe_device *vfe)
{
unsigned long time;
reinit_completion(&vfe->halt_complete);
vfe->ops_gen1->halt_request(vfe);
time = wait_for_completion_timeout(&vfe->halt_complete,
msecs_to_jiffies(VFE_HALT_TIMEOUT_MS));
if (!time) {
dev_err(vfe->camss->dev, "VFE halt timeout\n");
return -EIO;
}
return 0;
}
static int vfe_disable_output(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
struct vfe_output *output = &line->output;
const struct vfe_hw_ops *ops = vfe->res->hw_ops;
unsigned long flags;
unsigned long time;
unsigned int i;
spin_lock_irqsave(&vfe->output_lock, flags);
output->gen1.wait_sof = 1;
spin_unlock_irqrestore(&vfe->output_lock, flags);
time = wait_for_completion_timeout(&output->sof, msecs_to_jiffies(VFE_NEXT_SOF_MS));
if (!time)
dev_err(vfe->camss->dev, "VFE sof timeout\n");
spin_lock_irqsave(&vfe->output_lock, flags);
for (i = 0; i < output->wm_num; i++)
vfe->ops_gen1->wm_enable(vfe, output->wm_idx[i], 0);
ops->reg_update(vfe, line->id);
output->wait_reg_update = 1;
spin_unlock_irqrestore(&vfe->output_lock, flags);
time = wait_for_completion_timeout(&output->reg_update, msecs_to_jiffies(VFE_NEXT_SOF_MS));
if (!time)
dev_err(vfe->camss->dev, "VFE reg update timeout\n");
spin_lock_irqsave(&vfe->output_lock, flags);
if (line->id != VFE_LINE_PIX) {
vfe->ops_gen1->wm_frame_based(vfe, output->wm_idx[0], 0);
vfe->ops_gen1->bus_disconnect_wm_from_rdi(vfe, output->wm_idx[0], line->id);
vfe->ops_gen1->enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 0);
vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[0], 0);
spin_unlock_irqrestore(&vfe->output_lock, flags);
} else {
for (i = 0; i < output->wm_num; i++) {
vfe->ops_gen1->wm_line_based(vfe, output->wm_idx[i], NULL, i, 0);
vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[i], 0);
}
vfe->ops_gen1->enable_irq_pix_line(vfe, 0, line->id, 0);
vfe->ops_gen1->set_module_cfg(vfe, 0);
vfe->ops_gen1->set_realign_cfg(vfe, line, 0);
vfe->ops_gen1->set_xbar_cfg(vfe, output, 0);
vfe->ops_gen1->set_camif_cmd(vfe, 0);
spin_unlock_irqrestore(&vfe->output_lock, flags);
vfe->ops_gen1->camif_wait_for_stop(vfe, vfe->camss->dev);
}
return 0;
}
/*
* vfe_gen1_disable - Disable streaming on VFE line
* @line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_gen1_disable(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
vfe_disable_output(line);
vfe_put_output(line);
mutex_lock(&vfe->stream_lock);
if (vfe->stream_count == 1)
vfe->ops_gen1->bus_enable_wr_if(vfe, 0);
vfe->stream_count--;
mutex_unlock(&vfe->stream_lock);
return 0;
}
static void vfe_output_init_addrs(struct vfe_device *vfe,
struct vfe_output *output, u8 sync,
struct vfe_line *line)
{
u32 ping_addr;
u32 pong_addr;
unsigned int i;
output->gen1.active_buf = 0;
for (i = 0; i < output->wm_num; i++) {
if (output->buf[0])
ping_addr = output->buf[0]->addr[i];
else
ping_addr = 0;
if (output->buf[1])
pong_addr = output->buf[1]->addr[i];
else
pong_addr = ping_addr;
vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], ping_addr);
vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], pong_addr);
if (sync)
vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]);
}
}
static void vfe_output_frame_drop(struct vfe_device *vfe,
struct vfe_output *output,
u32 drop_pattern)
{
u8 drop_period;
unsigned int i;
/* We need to toggle update period to be valid on next frame */
output->drop_update_idx++;
output->drop_update_idx %= VFE_FRAME_DROP_UPDATES;
drop_period = VFE_FRAME_DROP_VAL + output->drop_update_idx;
for (i = 0; i < output->wm_num; i++) {
vfe->ops_gen1->wm_set_framedrop_period(vfe, output->wm_idx[i], drop_period);
vfe->ops_gen1->wm_set_framedrop_pattern(vfe, output->wm_idx[i], drop_pattern);
}
vfe->res->hw_ops->reg_update(vfe, container_of(output, struct vfe_line, output)->id);
}
static int vfe_enable_output(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
struct vfe_output *output = &line->output;
const struct vfe_hw_ops *ops = vfe->res->hw_ops;
struct media_pad *sensor_pad;
unsigned long flags;
unsigned int frame_skip = 0;
unsigned int i;
u16 ub_size;
ub_size = vfe->ops_gen1->get_ub_size(vfe->id);
if (!ub_size)
return -EINVAL;
sensor_pad = camss_find_sensor_pad(&line->subdev.entity);
if (sensor_pad) {
struct v4l2_subdev *subdev =
media_entity_to_v4l2_subdev(sensor_pad->entity);
v4l2_subdev_call(subdev, sensor, g_skip_frames, &frame_skip);
/* Max frame skip is 29 frames */
if (frame_skip > VFE_FRAME_DROP_VAL - 1)
frame_skip = VFE_FRAME_DROP_VAL - 1;
}
spin_lock_irqsave(&vfe->output_lock, flags);
ops->reg_update_clear(vfe, line->id);
if (output->state > VFE_OUTPUT_RESERVED) {
dev_err(vfe->camss->dev, "Output is not in reserved state %d\n", output->state);
spin_unlock_irqrestore(&vfe->output_lock, flags);
return -EINVAL;
}
output->state = VFE_OUTPUT_IDLE;
output->buf[0] = vfe_buf_get_pending(output);
output->buf[1] = vfe_buf_get_pending(output);
if (!output->buf[0] && output->buf[1]) {
output->buf[0] = output->buf[1];
output->buf[1] = NULL;
}
if (output->buf[0])
output->state = VFE_OUTPUT_SINGLE;
if (output->buf[1])
output->state = VFE_OUTPUT_CONTINUOUS;
switch (output->state) {
case VFE_OUTPUT_SINGLE:
vfe_output_frame_drop(vfe, output, 1 << frame_skip);
break;
case VFE_OUTPUT_CONTINUOUS:
vfe_output_frame_drop(vfe, output, 3 << frame_skip);
break;
default:
vfe_output_frame_drop(vfe, output, 0);
break;
}
output->sequence = 0;
output->gen1.wait_sof = 0;
output->wait_reg_update = 0;
reinit_completion(&output->sof);
reinit_completion(&output->reg_update);
vfe_output_init_addrs(vfe, output, 0, line);
if (line->id != VFE_LINE_PIX) {
vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[0], 1);
vfe->ops_gen1->enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 1);
vfe->ops_gen1->bus_connect_wm_to_rdi(vfe, output->wm_idx[0], line->id);
vfe->ops_gen1->wm_set_subsample(vfe, output->wm_idx[0]);
vfe->ops_gen1->set_rdi_cid(vfe, line->id, 0);
vfe->ops_gen1->wm_set_ub_cfg(vfe, output->wm_idx[0],
(ub_size + 1) * output->wm_idx[0], ub_size);
vfe->ops_gen1->wm_frame_based(vfe, output->wm_idx[0], 1);
vfe->ops_gen1->wm_enable(vfe, output->wm_idx[0], 1);
vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[0]);
} else {
ub_size /= output->wm_num;
for (i = 0; i < output->wm_num; i++) {
vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[i], 1);
vfe->ops_gen1->wm_set_subsample(vfe, output->wm_idx[i]);
vfe->ops_gen1->wm_set_ub_cfg(vfe, output->wm_idx[i],
(ub_size + 1) * output->wm_idx[i], ub_size);
vfe->ops_gen1->wm_line_based(vfe, output->wm_idx[i],
&line->video_out.active_fmt.fmt.pix_mp, i, 1);
vfe->ops_gen1->wm_enable(vfe, output->wm_idx[i], 1);
vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]);
}
vfe->ops_gen1->enable_irq_pix_line(vfe, 0, line->id, 1);
vfe->ops_gen1->set_module_cfg(vfe, 1);
vfe->ops_gen1->set_camif_cfg(vfe, line);
vfe->ops_gen1->set_realign_cfg(vfe, line, 1);
vfe->ops_gen1->set_xbar_cfg(vfe, output, 1);
vfe->ops_gen1->set_demux_cfg(vfe, line);
vfe->ops_gen1->set_scale_cfg(vfe, line);
vfe->ops_gen1->set_crop_cfg(vfe, line);
vfe->ops_gen1->set_clamp_cfg(vfe);
vfe->ops_gen1->set_camif_cmd(vfe, 1);
}
ops->reg_update(vfe, line->id);
spin_unlock_irqrestore(&vfe->output_lock, flags);
return 0;
}
static int vfe_get_output(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
struct vfe_output *output;
struct v4l2_format *f = &line->video_out.active_fmt;
unsigned long flags;
int i;
int wm_idx;
spin_lock_irqsave(&vfe->output_lock, flags);
output = &line->output;
if (output->state > VFE_OUTPUT_RESERVED) {
dev_err(vfe->camss->dev, "Output is running\n");
goto error;
}
output->state = VFE_OUTPUT_RESERVED;
output->gen1.active_buf = 0;
switch (f->fmt.pix_mp.pixelformat) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_NV16:
case V4L2_PIX_FMT_NV61:
output->wm_num = 2;
break;
default:
output->wm_num = 1;
break;
}
for (i = 0; i < output->wm_num; i++) {
wm_idx = vfe_reserve_wm(vfe, line->id);
if (wm_idx < 0) {
dev_err(vfe->camss->dev, "Can not reserve wm\n");
goto error_get_wm;
}
output->wm_idx[i] = wm_idx;
}
output->drop_update_idx = 0;
spin_unlock_irqrestore(&vfe->output_lock, flags);
return 0;
error_get_wm:
for (i--; i >= 0; i--)
vfe_release_wm(vfe, output->wm_idx[i]);
output->state = VFE_OUTPUT_OFF;
error:
spin_unlock_irqrestore(&vfe->output_lock, flags);
return -EINVAL;
}
int vfe_gen1_enable(struct vfe_line *line)
{
struct vfe_device *vfe = to_vfe(line);
int ret;
mutex_lock(&vfe->stream_lock);
if (!vfe->stream_count) {
vfe->ops_gen1->enable_irq_common(vfe);
vfe->ops_gen1->bus_enable_wr_if(vfe, 1);
vfe->ops_gen1->set_qos(vfe);
vfe->ops_gen1->set_ds(vfe);
}
vfe->stream_count++;
mutex_unlock(&vfe->stream_lock);
ret = vfe_get_output(line);
if (ret < 0)
goto error_get_output;
ret = vfe_enable_output(line);
if (ret < 0)
goto error_enable_output;
vfe->was_streaming = 1;
return 0;
error_enable_output:
vfe_put_output(line);
error_get_output:
mutex_lock(&vfe->stream_lock);
if (vfe->stream_count == 1)
vfe->ops_gen1->bus_enable_wr_if(vfe, 0);
vfe->stream_count--;
mutex_unlock(&vfe->stream_lock);
return ret;
}
static void vfe_output_update_ping_addr(struct vfe_device *vfe,
struct vfe_output *output, u8 sync,
struct vfe_line *line)
{
u32 addr;
unsigned int i;
for (i = 0; i < output->wm_num; i++) {
if (output->buf[0])
addr = output->buf[0]->addr[i];
else
addr = 0;
vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], addr);
if (sync)
vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]);
}
}
static void vfe_output_update_pong_addr(struct vfe_device *vfe,
struct vfe_output *output, u8 sync,
struct vfe_line *line)
{
u32 addr;
unsigned int i;
for (i = 0; i < output->wm_num; i++) {
if (output->buf[1])
addr = output->buf[1]->addr[i];
else
addr = 0;
vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], addr);
if (sync)
vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]);
}
}
static void vfe_buf_update_wm_on_next(struct vfe_device *vfe,
struct vfe_output *output)
{
switch (output->state) {
case VFE_OUTPUT_CONTINUOUS:
vfe_output_frame_drop(vfe, output, 3);
break;
case VFE_OUTPUT_SINGLE:
default:
dev_err_ratelimited(vfe->camss->dev,
"Next buf in wrong state! %d\n",
output->state);
break;
}
}
static void vfe_buf_update_wm_on_last(struct vfe_device *vfe,
struct vfe_output *output)
{
switch (output->state) {
case VFE_OUTPUT_CONTINUOUS:
output->state = VFE_OUTPUT_SINGLE;
vfe_output_frame_drop(vfe, output, 1);
break;
case VFE_OUTPUT_SINGLE:
output->state = VFE_OUTPUT_STOPPING;
vfe_output_frame_drop(vfe, output, 0);
break;
default:
dev_err_ratelimited(vfe->camss->dev,
"Last buff in wrong state! %d\n",
output->state);
break;
}
}
static void vfe_buf_update_wm_on_new(struct vfe_device *vfe,
struct vfe_output *output,
struct camss_buffer *new_buf,
struct vfe_line *line)
{
int inactive_idx;
switch (output->state) {
case VFE_OUTPUT_SINGLE:
inactive_idx = !output->gen1.active_buf;
if (!output->buf[inactive_idx]) {
output->buf[inactive_idx] = new_buf;
if (inactive_idx)
vfe_output_update_pong_addr(vfe, output, 0, line);
else
vfe_output_update_ping_addr(vfe, output, 0, line);
vfe_output_frame_drop(vfe, output, 3);
output->state = VFE_OUTPUT_CONTINUOUS;
} else {
vfe_buf_add_pending(output, new_buf);
dev_err_ratelimited(vfe->camss->dev,
"Inactive buffer is busy\n");
}
break;
case VFE_OUTPUT_IDLE:
if (!output->buf[0]) {
output->buf[0] = new_buf;
vfe_output_init_addrs(vfe, output, 1, line);
vfe_output_frame_drop(vfe, output, 1);
output->state = VFE_OUTPUT_SINGLE;
} else {
vfe_buf_add_pending(output, new_buf);
dev_err_ratelimited(vfe->camss->dev,
"Output idle with buffer set!\n");
}
break;
case VFE_OUTPUT_CONTINUOUS:
default:
vfe_buf_add_pending(output, new_buf);
break;
}
}
/*
* vfe_isr_halt_ack - Process halt ack
* @vfe: VFE Device
*/
static void vfe_isr_halt_ack(struct vfe_device *vfe)
{
complete(&vfe->halt_complete);
vfe->ops_gen1->halt_clear(vfe);
}
/*
* vfe_isr_sof - Process start of frame interrupt
* @vfe: VFE Device
* @line_id: VFE line
*/
static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id)
{
struct vfe_output *output;
unsigned long flags;
spin_lock_irqsave(&vfe->output_lock, flags);
output = &vfe->line[line_id].output;
if (output->gen1.wait_sof) {
output->gen1.wait_sof = 0;
complete(&output->sof);
}
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
/*
* vfe_isr_reg_update - Process reg update interrupt
* @vfe: VFE Device
* @line_id: VFE line
*/
static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
struct vfe_output *output;
struct vfe_line *line = &vfe->line[line_id];
unsigned long flags;
spin_lock_irqsave(&vfe->output_lock, flags);
vfe->res->hw_ops->reg_update_clear(vfe, line_id);
output = &line->output;
if (output->wait_reg_update) {
output->wait_reg_update = 0;
complete(&output->reg_update);
spin_unlock_irqrestore(&vfe->output_lock, flags);
return;
}
if (output->state == VFE_OUTPUT_STOPPING) {
/* Release last buffer when hw is idle */
if (output->last_buffer) {
vb2_buffer_done(&output->last_buffer->vb.vb2_buf,
VB2_BUF_STATE_DONE);
output->last_buffer = NULL;
}
output->state = VFE_OUTPUT_IDLE;
/* Buffers received in stopping state are queued in */
/* dma pending queue, start next capture here */
output->buf[0] = vfe_buf_get_pending(output);
output->buf[1] = vfe_buf_get_pending(output);
if (!output->buf[0] && output->buf[1]) {
output->buf[0] = output->buf[1];
output->buf[1] = NULL;
}
if (output->buf[0])
output->state = VFE_OUTPUT_SINGLE;
if (output->buf[1])
output->state = VFE_OUTPUT_CONTINUOUS;
switch (output->state) {
case VFE_OUTPUT_SINGLE:
vfe_output_frame_drop(vfe, output, 2);
break;
case VFE_OUTPUT_CONTINUOUS:
vfe_output_frame_drop(vfe, output, 3);
break;
default:
vfe_output_frame_drop(vfe, output, 0);
break;
}
vfe_output_init_addrs(vfe, output, 1, &vfe->line[line_id]);
}
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
/*
* vfe_isr_wm_done - Process write master done interrupt
* @vfe: VFE Device
* @wm: Write master id
*/
static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm)
{
struct camss_buffer *ready_buf;
struct vfe_output *output;
dma_addr_t *new_addr;
unsigned long flags;
u32 active_index;
u64 ts = ktime_get_ns();
unsigned int i;
active_index = vfe->ops_gen1->wm_get_ping_pong_status(vfe, wm);
spin_lock_irqsave(&vfe->output_lock, flags);
if (vfe->wm_output_map[wm] == VFE_LINE_NONE) {
dev_err_ratelimited(vfe->camss->dev,
"Received wm done for unmapped index\n");
goto out_unlock;
}
output = &vfe->line[vfe->wm_output_map[wm]].output;
if (output->gen1.active_buf == active_index && 0) {
dev_err_ratelimited(vfe->camss->dev,
"Active buffer mismatch!\n");
goto out_unlock;
}
output->gen1.active_buf = active_index;
ready_buf = output->buf[!active_index];
if (!ready_buf) {
dev_err_ratelimited(vfe->camss->dev,
"Missing ready buf %d %d!\n",
!active_index, output->state);
goto out_unlock;
}
ready_buf->vb.vb2_buf.timestamp = ts;
ready_buf->vb.sequence = output->sequence++;
/* Get next buffer */
output->buf[!active_index] = vfe_buf_get_pending(output);
if (!output->buf[!active_index]) {
/* No next buffer - set same address */
new_addr = ready_buf->addr;
vfe_buf_update_wm_on_last(vfe, output);
} else {
new_addr = output->buf[!active_index]->addr;
vfe_buf_update_wm_on_next(vfe, output);
}
if (active_index)
for (i = 0; i < output->wm_num; i++)
vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], new_addr[i]);
else
for (i = 0; i < output->wm_num; i++)
vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], new_addr[i]);
spin_unlock_irqrestore(&vfe->output_lock, flags);
if (output->state == VFE_OUTPUT_STOPPING)
output->last_buffer = ready_buf;
else
vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
return;
out_unlock:
spin_unlock_irqrestore(&vfe->output_lock, flags);
}
/*
* vfe_queue_buffer - Add empty buffer
* @vid: Video device structure
* @buf: Buffer to be enqueued
*
* Add an empty buffer - depending on the current number of buffers it will be
* put in pending buffer queue or directly given to the hardware to be filled.
*
* Return 0 on success or a negative error code otherwise
*/
static int vfe_queue_buffer(struct camss_video *vid, struct camss_buffer *buf)
{
struct vfe_line *line = container_of(vid, struct vfe_line, video_out);
struct vfe_device *vfe = to_vfe(line);
struct vfe_output *output;
unsigned long flags;
output = &line->output;
spin_lock_irqsave(&vfe->output_lock, flags);
vfe_buf_update_wm_on_new(vfe, output, buf, line);
spin_unlock_irqrestore(&vfe->output_lock, flags);
return 0;
}
#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N))
int vfe_word_per_line(u32 format, u32 width)
{
int val = 0;
switch (format) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_NV16:
case V4L2_PIX_FMT_NV61:
val = CALC_WORD(width, 1, 8);
break;
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_VYUY:
val = CALC_WORD(width, 2, 8);
break;
}
return val;
}
const struct vfe_isr_ops vfe_isr_ops_gen1 = {
.reset_ack = vfe_isr_reset_ack,
.halt_ack = vfe_isr_halt_ack,
.reg_update = vfe_isr_reg_update,
.sof = vfe_isr_sof,
.comp_done = vfe_isr_comp_done,
.wm_done = vfe_isr_wm_done,
};
const struct camss_video_ops vfe_video_ops_gen1 = {
.queue_buffer = vfe_queue_buffer,
.flush_buffers = vfe_flush_buffers,
};
@@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-vfe.h
*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module
*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_VFE_GEN1_H
#define QC_MSM_CAMSS_VFE_GEN1_H
#include "camss-vfe.h"
enum vfe_line_id;
struct vfe_device;
struct vfe_line;
struct vfe_output;
struct vfe_hw_ops_gen1 {
void (*bus_connect_wm_to_rdi)(struct vfe_device *vfe, u8 wm, enum vfe_line_id id);
void (*bus_disconnect_wm_from_rdi)(struct vfe_device *vfe, u8 wm, enum vfe_line_id id);
void (*bus_enable_wr_if)(struct vfe_device *vfe, u8 enable);
void (*bus_reload_wm)(struct vfe_device *vfe, u8 wm);
int (*camif_wait_for_stop)(struct vfe_device *vfe, struct device *dev);
void (*enable_irq_common)(struct vfe_device *vfe);
void (*enable_irq_wm_line)(struct vfe_device *vfe, u8 wm, enum vfe_line_id line_id,
u8 enable);
void (*enable_irq_pix_line)(struct vfe_device *vfe, u8 comp, enum vfe_line_id line_id,
u8 enable);
u16 (*get_ub_size)(u8 vfe_id);
void (*halt_clear)(struct vfe_device *vfe);
void (*halt_request)(struct vfe_device *vfe);
void (*set_camif_cfg)(struct vfe_device *vfe, struct vfe_line *line);
void (*set_camif_cmd)(struct vfe_device *vfe, u8 enable);
void (*set_cgc_override)(struct vfe_device *vfe, u8 wm, u8 enable);
void (*set_clamp_cfg)(struct vfe_device *vfe);
void (*set_crop_cfg)(struct vfe_device *vfe, struct vfe_line *line);
void (*set_demux_cfg)(struct vfe_device *vfe, struct vfe_line *line);
void (*set_ds)(struct vfe_device *vfe);
void (*set_module_cfg)(struct vfe_device *vfe, u8 enable);
void (*set_scale_cfg)(struct vfe_device *vfe, struct vfe_line *line);
void (*set_rdi_cid)(struct vfe_device *vfe, enum vfe_line_id id, u8 cid);
void (*set_realign_cfg)(struct vfe_device *vfe, struct vfe_line *line, u8 enable);
void (*set_qos)(struct vfe_device *vfe);
void (*set_xbar_cfg)(struct vfe_device *vfe, struct vfe_output *output, u8 enable);
void (*wm_frame_based)(struct vfe_device *vfe, u8 wm, u8 enable);
void (*wm_line_based)(struct vfe_device *vfe, u32 wm, struct v4l2_pix_format_mplane *pix,
u8 plane, u32 enable);
void (*wm_set_ub_cfg)(struct vfe_device *vfe, u8 wm, u16 offset, u16 depth);
void (*wm_set_subsample)(struct vfe_device *vfe, u8 wm);
void (*wm_set_framedrop_period)(struct vfe_device *vfe, u8 wm, u8 per);
void (*wm_set_framedrop_pattern)(struct vfe_device *vfe, u8 wm, u32 pattern);
void (*wm_set_ping_addr)(struct vfe_device *vfe, u8 wm, u32 addr);
void (*wm_set_pong_addr)(struct vfe_device *vfe, u8 wm, u32 addr);
int (*wm_get_ping_pong_status)(struct vfe_device *vfe, u8 wm);
void (*wm_enable)(struct vfe_device *vfe, u8 wm, u8 enable);
};
/*
* vfe_calc_interp_reso - Calculate interpolation mode
* @input: Input resolution
* @output: Output resolution
*
* Return interpolation mode
*/
static inline u8 vfe_calc_interp_reso(u16 input, u16 output)
{
if (input / output >= 16)
return 0;
if (input / output >= 8)
return 1;
if (input / output >= 4)
return 2;
return 3;
}
/*
* vfe_gen1_disable - Disable streaming on VFE line
* @line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_gen1_disable(struct vfe_line *line);
/*
* vfe_gen1_enable - Enable VFE module
* @line: VFE line
*
* Return 0 on success
*/
int vfe_gen1_enable(struct vfe_line *line);
/*
* vfe_gen1_enable - Halt VFE module
* @vfe: VFE device
*
* Return 0 on success
*/
int vfe_gen1_halt(struct vfe_device *vfe);
/*
* vfe_word_per_line - Calculate number of words per frame width
* @format: V4L2 format
* @width: Frame width
*
* Return number of words per frame width
*/
int vfe_word_per_line(u32 format, u32 width);
extern const struct vfe_isr_ops vfe_isr_ops_gen1;
extern const struct camss_video_ops vfe_video_ops_gen1;
#endif /* QC_MSM_CAMSS_VFE_GEN1_H */
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module gen3
*
* Copyright (c) 2024 Qualcomm Technologies, Inc.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include "camss.h"
#include "camss-vfe.h"
#define IS_VFE_690(vfe) \
((vfe->camss->res->version == CAMSS_8775P) \
|| (vfe->camss->res->version == CAMSS_8300))
#define BUS_REG_BASE_690 \
(vfe_is_lite(vfe) ? 0x480 : 0x400)
#define BUS_REG_BASE_780 \
(vfe_is_lite(vfe) ? 0x200 : 0xC00)
#define BUS_REG_BASE \
(IS_VFE_690(vfe) ? BUS_REG_BASE_690 : BUS_REG_BASE_780)
#define VFE_TOP_CORE_CFG (0x24)
#define VFE_DISABLE_DSCALING_DS4 BIT(21)
#define VFE_DISABLE_DSCALING_DS16 BIT(22)
#define VFE_BUS_WM_TEST_BUS_CTRL_690 (BUS_REG_BASE + 0xFC)
#define VFE_BUS_WM_TEST_BUS_CTRL_780 (BUS_REG_BASE + 0xDC)
#define VFE_BUS_WM_TEST_BUS_CTRL \
(IS_VFE_690(vfe) ? VFE_BUS_WM_TEST_BUS_CTRL_690 \
: VFE_BUS_WM_TEST_BUS_CTRL_780)
/*
* Bus client mapping:
*
* Full VFE:
* VFE_690: 16 = RDI0, 17 = RDI1, 18 = RDI2
* VFE_780: 23 = RDI0, 24 = RDI1, 25 = RDI2
*
* VFE LITE:
* VFE_690 : 0 = RDI0, 1 = RDI1, 2 = RDI2, 3 = RDI3, 4 = RDI4, 5 = RDI5
* VFE_780 : 0 = RDI0, 1 = RDI1, 2 = RDI2, 3 = RDI3, 4 = RDI4
*/
#define RDI_WM_690(n) ((vfe_is_lite(vfe) ? 0x0 : 0x10) + (n))
#define RDI_WM_780(n) ((vfe_is_lite(vfe) ? 0x0 : 0x17) + (n))
#define RDI_WM(n) (IS_VFE_690(vfe) ? RDI_WM_690(n) : RDI_WM_780(n))
#define VFE_BUS_WM_CGC_OVERRIDE (BUS_REG_BASE + 0x08)
#define WM_CGC_OVERRIDE_ALL (0x7FFFFFF)
#define VFE_BUS_WM_CFG(n) (BUS_REG_BASE + 0x200 + (n) * 0x100)
#define WM_CFG_EN BIT(0)
#define WM_VIR_FRM_EN BIT(1)
#define WM_CFG_MODE BIT(16)
#define VFE_BUS_WM_IMAGE_ADDR(n) (BUS_REG_BASE + 0x204 + (n) * 0x100)
#define VFE_BUS_WM_FRAME_INCR(n) (BUS_REG_BASE + 0x208 + (n) * 0x100)
#define VFE_BUS_WM_IMAGE_CFG_0(n) (BUS_REG_BASE + 0x20c + (n) * 0x100)
#define WM_IMAGE_CFG_0_DEFAULT_WIDTH (0xFFFF)
#define VFE_BUS_WM_IMAGE_CFG_2(n) (BUS_REG_BASE + 0x214 + (n) * 0x100)
#define WM_IMAGE_CFG_2_DEFAULT_STRIDE (0xFFFF)
#define VFE_BUS_WM_PACKER_CFG(n) (BUS_REG_BASE + 0x218 + (n) * 0x100)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (BUS_REG_BASE + 0x230 + (n) * 0x100)
#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (BUS_REG_BASE + 0x234 + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (BUS_REG_BASE + 0x238 + (n) * 0x100)
#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (BUS_REG_BASE + 0x23c + (n) * 0x100)
#define VFE_BUS_WM_MMU_PREFETCH_CFG(n) (BUS_REG_BASE + 0x260 + (n) * 0x100)
#define VFE_BUS_WM_MMU_PREFETCH_MAX_OFFSET(n) (BUS_REG_BASE + 0x264 + (n) * 0x100)
static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line)
{
struct v4l2_pix_format_mplane *pix =
&line->video_out.active_fmt.fmt.pix_mp;
wm = RDI_WM(wm);
/* no clock gating at bus input */
writel(WM_CGC_OVERRIDE_ALL, vfe->base + VFE_BUS_WM_CGC_OVERRIDE);
writel(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL);
if (IS_VFE_690(vfe))
writel(ALIGN(pix->plane_fmt[0].bytesperline, 16) * pix->height,
vfe->base + VFE_BUS_WM_FRAME_INCR(wm));
else
writel(ALIGN(pix->plane_fmt[0].bytesperline, 16) * pix->height >> 8,
vfe->base + VFE_BUS_WM_FRAME_INCR(wm));
writel((WM_IMAGE_CFG_0_DEFAULT_WIDTH & 0xFFFF),
vfe->base + VFE_BUS_WM_IMAGE_CFG_0(wm));
writel(WM_IMAGE_CFG_2_DEFAULT_STRIDE,
vfe->base + VFE_BUS_WM_IMAGE_CFG_2(wm));
writel(0, vfe->base + VFE_BUS_WM_PACKER_CFG(wm));
/* TOP CORE CFG */
if (IS_VFE_690(vfe))
writel(VFE_DISABLE_DSCALING_DS4 | VFE_DISABLE_DSCALING_DS16,
vfe->base + VFE_TOP_CORE_CFG);
/* no dropped frames, one irq per frame */
writel(0, vfe->base + VFE_BUS_WM_FRAMEDROP_PERIOD(wm));
writel(1, vfe->base + VFE_BUS_WM_FRAMEDROP_PATTERN(wm));
writel(0, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(wm));
writel(1, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(wm));
writel(1, vfe->base + VFE_BUS_WM_MMU_PREFETCH_CFG(wm));
writel(0xFFFFFFFF, vfe->base + VFE_BUS_WM_MMU_PREFETCH_MAX_OFFSET(wm));
writel(WM_CFG_EN | WM_CFG_MODE, vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_stop(struct vfe_device *vfe, u8 wm)
{
wm = RDI_WM(wm);
writel(0, vfe->base + VFE_BUS_WM_CFG(wm));
}
static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr,
struct vfe_line *line)
{
wm = RDI_WM(wm);
if (IS_VFE_690(vfe))
writel(addr, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm));
else
writel((addr >> 8), vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm));
dev_dbg(vfe->camss->dev, "wm:%d, image buf addr:0x%x\n",
wm, addr);
}
static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id)
{
int port_id = line_id;
camss_reg_update(vfe->camss, vfe->id, port_id, false);
}
static inline void vfe_reg_update_clear(struct vfe_device *vfe,
enum vfe_line_id line_id)
{
int port_id = line_id;
camss_reg_update(vfe->camss, vfe->id, port_id, true);
}
static const struct camss_video_ops vfe_video_ops_gen3 = {
.queue_buffer = vfe_queue_buffer_v2,
.flush_buffers = vfe_flush_buffers,
};
static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe)
{
vfe->video_ops = vfe_video_ops_gen3;
}
static void vfe_global_reset(struct vfe_device *vfe)
{
vfe_isr_reset_ack(vfe);
}
static irqreturn_t vfe_isr(int irq, void *dev)
{
/* nop */
return IRQ_HANDLED;
}
static int vfe_halt(struct vfe_device *vfe)
{
/* rely on vfe_disable_output() to stop the VFE */
return 0;
}
const struct vfe_hw_ops vfe_ops_gen3 = {
.global_reset = vfe_global_reset,
.hw_version = vfe_hw_version,
.isr = vfe_isr,
.pm_domain_off = vfe_pm_domain_off,
.pm_domain_on = vfe_pm_domain_on,
.reg_update = vfe_reg_update,
.reg_update_clear = vfe_reg_update_clear,
.subdev_init = vfe_subdev_init,
.vfe_disable = vfe_disable,
.vfe_enable = vfe_enable_v2,
.vfe_halt = vfe_halt,
.vfe_wm_start = vfe_wm_start,
.vfe_wm_stop = vfe_wm_stop,
.vfe_buf_done = vfe_buf_done,
.vfe_wm_update = vfe_wm_update,
};
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* camss-vfe-vbif.c
*
* Qualcomm MSM Camera Subsystem - VFE VBIF Module
*
* Copyright (c) 2025, The Linux Foundation. All rights reserved.
*
*/
#include <linux/io.h>
#include "camss.h"
#include "camss-vfe.h"
#include "camss-vfe-vbif.h"
#define VBIF_FIXED_SORT_EN 0x30
#define VBIF_FIXED_SORT_SEL0 0x34
void vfe_vbif_write_reg(struct vfe_device *vfe, u32 reg, u32 val)
{
writel_relaxed(val, vfe->vbif_base + reg);
}
int vfe_vbif_apply_settings(struct vfe_device *vfe)
{
vfe_vbif_write_reg(vfe, VBIF_FIXED_SORT_EN, 0xfff);
vfe_vbif_write_reg(vfe, VBIF_FIXED_SORT_SEL0, 0x555000);
return 0;
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* camss-vfe-vbif.h
*
* Qualcomm MSM Camera Subsystem - VFE VBIF Module
*
* Copyright (c) 2025, The Linux Foundation. All rights reserved.
*
*/
#ifndef QC_MSM_CAMSS_VFE_VBIF_H
#define QC_MSM_CAMSS_VFE_VBIF_H
#include "camss-vfe.h"
void vfe_vbif_write_reg(struct vfe_device *vfe, u32 reg, u32 val);
int vfe_vbif_apply_settings(struct vfe_device *vfe);
#endif /* QC_MSM_CAMSS_VFE_VBIF_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,317 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-vfe.h
*
* Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module
*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_VFE_H
#define QC_MSM_CAMSS_VFE_H
#include <linux/clk.h>
#include <linux/spinlock_types.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include "camss-video.h"
#include "camss-vfe-gen1.h"
#define MSM_VFE_PAD_SINK 0
#define MSM_VFE_PAD_SRC 1
#define MSM_VFE_PADS_NUM 2
#define MSM_VFE_IMAGE_MASTERS_NUM 7
#define MSM_VFE_COMPOSITE_IRQ_NUM 4
/* VFE halt timeout */
#define VFE_HALT_TIMEOUT_MS 100
/* Frame drop value. VAL + UPDATES - 1 should not exceed 31 */
#define VFE_FRAME_DROP_VAL 30
#define vfe_line_array(ptr_line) \
((const struct vfe_line (*)[]) &(ptr_line)[-(ptr_line)->id])
#define to_vfe(ptr_line) \
container_of(vfe_line_array(ptr_line), struct vfe_device, line)
enum vfe_output_state {
VFE_OUTPUT_OFF,
VFE_OUTPUT_RESERVED,
VFE_OUTPUT_SINGLE,
VFE_OUTPUT_CONTINUOUS,
VFE_OUTPUT_IDLE,
VFE_OUTPUT_STOPPING,
VFE_OUTPUT_ON,
};
enum vfe_line_id {
VFE_LINE_NONE = -1,
VFE_LINE_RDI0 = 0,
VFE_LINE_RDI1 = 1,
VFE_LINE_RDI2 = 2,
VFE_LINE_PIX = 3,
VFE_LINE_NUM_MAX = 4
};
struct vfe_output {
u8 wm_num;
u8 wm_idx[3];
struct camss_buffer *buf[2];
struct camss_buffer *last_buffer;
struct list_head pending_bufs;
unsigned int drop_update_idx;
union {
struct {
int active_buf;
int wait_sof;
} gen1;
struct {
int active_num;
} gen2;
};
enum vfe_output_state state;
unsigned int sequence;
int wait_reg_update;
struct completion sof;
struct completion reg_update;
};
struct vfe_line {
enum vfe_line_id id;
struct v4l2_subdev subdev;
struct media_pad pads[MSM_VFE_PADS_NUM];
struct v4l2_mbus_framefmt fmt[MSM_VFE_PADS_NUM];
struct v4l2_rect compose;
struct v4l2_rect crop;
struct camss_video video_out;
struct vfe_output output;
const struct camss_format_info *formats;
unsigned int nformats;
};
struct vfe_device;
struct vfe_hw_ops {
void (*enable_irq)(struct vfe_device *vfe);
void (*global_reset)(struct vfe_device *vfe);
u32 (*hw_version)(struct vfe_device *vfe);
irqreturn_t (*isr)(int irq, void *dev);
void (*isr_read)(struct vfe_device *vfe, u32 *value0, u32 *value1);
void (*pm_domain_off)(struct vfe_device *vfe);
int (*pm_domain_on)(struct vfe_device *vfe);
void (*reg_update)(struct vfe_device *vfe, enum vfe_line_id line_id);
void (*reg_update_clear)(struct vfe_device *vfe,
enum vfe_line_id line_id);
void (*subdev_init)(struct device *dev, struct vfe_device *vfe);
int (*vfe_disable)(struct vfe_line *line);
int (*vfe_enable)(struct vfe_line *line);
int (*vfe_halt)(struct vfe_device *vfe);
void (*violation_read)(struct vfe_device *vfe);
void (*vfe_wm_start)(struct vfe_device *vfe, u8 wm,
struct vfe_line *line);
void (*vfe_wm_stop)(struct vfe_device *vfe, u8 wm);
void (*vfe_buf_done)(struct vfe_device *vfe, int port_id);
void (*vfe_wm_update)(struct vfe_device *vfe, u8 wm, u32 addr,
struct vfe_line *line);
};
struct vfe_isr_ops {
void (*reset_ack)(struct vfe_device *vfe);
void (*halt_ack)(struct vfe_device *vfe);
void (*reg_update)(struct vfe_device *vfe, enum vfe_line_id line_id);
void (*sof)(struct vfe_device *vfe, enum vfe_line_id line_id);
void (*comp_done)(struct vfe_device *vfe, u8 comp);
void (*wm_done)(struct vfe_device *vfe, u8 wm);
};
struct vfe_subdev_resources {
bool is_lite;
u8 line_num;
bool has_pd;
char *pd_name;
bool has_vbif;
char *vbif_name;
const struct vfe_hw_ops *hw_ops;
const struct camss_formats *formats_rdi;
const struct camss_formats *formats_pix;
};
struct vfe_device {
struct camss *camss;
u8 id;
void __iomem *base;
void __iomem *vbif_base;
u32 irq;
char irq_name[30];
struct camss_clock *clock;
int nclocks;
struct completion reset_complete;
struct completion halt_complete;
struct mutex power_lock;
int power_count;
struct mutex stream_lock;
int stream_count;
spinlock_t output_lock;
enum vfe_line_id wm_output_map[MSM_VFE_IMAGE_MASTERS_NUM];
struct vfe_line line[VFE_LINE_NUM_MAX];
u32 reg_update;
u8 was_streaming;
const struct vfe_subdev_resources *res;
const struct vfe_hw_ops_gen1 *ops_gen1;
struct vfe_isr_ops isr_ops;
struct camss_video_ops video_ops;
struct device *genpd;
struct device_link *genpd_link;
};
struct camss_subdev_resources;
int msm_vfe_subdev_init(struct camss *camss, struct vfe_device *vfe,
const struct camss_subdev_resources *res, u8 id);
void msm_vfe_genpd_cleanup(struct vfe_device *vfe);
int msm_vfe_register_entities(struct vfe_device *vfe,
struct v4l2_device *v4l2_dev);
void msm_vfe_unregister_entities(struct vfe_device *vfe);
/*
* vfe_buf_add_pending - Add output buffer to list of pending
* @output: VFE output
* @buffer: Video buffer
*/
void vfe_buf_add_pending(struct vfe_output *output, struct camss_buffer *buffer);
struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output);
int vfe_flush_buffers(struct camss_video *vid, enum vb2_buffer_state state);
/*
* vfe_isr_comp_done - Process composite image done interrupt
* @vfe: VFE Device
* @comp: Composite image id
*/
void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp);
void vfe_isr_reset_ack(struct vfe_device *vfe);
int vfe_put_output(struct vfe_line *line);
int vfe_release_wm(struct vfe_device *vfe, u8 wm);
int vfe_reserve_wm(struct vfe_device *vfe, enum vfe_line_id line_id);
/*
* vfe_reset - Trigger reset on VFE module and wait to complete
* @vfe: VFE device
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_reset(struct vfe_device *vfe);
/*
* vfe_disable - Disable streaming on VFE line
* @line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_disable(struct vfe_line *line);
/*
* vfe_pm_domain_off - Disable power domains specific to this VFE.
* @vfe: VFE Device
*/
void vfe_pm_domain_off(struct vfe_device *vfe);
/*
* vfe_pm_domain_on - Enable power domains specific to this VFE.
* @vfe: VFE Device
*/
int vfe_pm_domain_on(struct vfe_device *vfe);
extern const struct camss_formats vfe_formats_rdi_8x16;
extern const struct camss_formats vfe_formats_pix_8x16;
extern const struct camss_formats vfe_formats_rdi_8x96;
extern const struct camss_formats vfe_formats_pix_8x96;
extern const struct camss_formats vfe_formats_rdi_845;
extern const struct camss_formats vfe_formats_pix_845;
extern const struct vfe_hw_ops vfe_ops_4_1;
extern const struct vfe_hw_ops vfe_ops_4_7;
extern const struct vfe_hw_ops vfe_ops_4_8;
extern const struct vfe_hw_ops vfe_ops_170;
extern const struct vfe_hw_ops vfe_ops_340;
extern const struct vfe_hw_ops vfe_ops_480;
extern const struct vfe_hw_ops vfe_ops_680;
extern const struct vfe_hw_ops vfe_ops_gen3;
int vfe_get(struct vfe_device *vfe);
void vfe_put(struct vfe_device *vfe);
/*
* vfe_is_lite - Return if VFE is VFE lite.
* @vfe: VFE Device
*
* Some VFE lites have a different register layout.
*
* Return whether VFE is VFE lite
*/
bool vfe_is_lite(struct vfe_device *vfe);
/*
* vfe_hw_version - Process write master done interrupt
* @vfe: VFE Device
*
* Return vfe hw version
*/
u32 vfe_hw_version(struct vfe_device *vfe);
/*
* vfe_enable - Enable streaming on VFE line
* @line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_enable_v2(struct vfe_line *line);
/*
* vfe_buf_done - Process write master done interrupt
* @vfe: VFE Device
* @wm: Write master id
*/
void vfe_buf_done(struct vfe_device *vfe, int wm);
/*
* vfe_get_output_v2 - Get vfe output line
* line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_get_output_v2(struct vfe_line *line);
/*
* vfe_enable_output_v2 - Enable vfe output line
* line: VFE line
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_enable_output_v2(struct vfe_line *line);
/*
* vfe_queue_buffer_v2 - Add empty buffer
* @vid: Video device structure
* @buf: Buffer to be enqueued
*
* Add an empty buffer - depending on the current number of buffers it will be
* put in pending buffer queue or directly given to the hardware to be filled.
*
* Return 0 on success or a negative error code otherwise
*/
int vfe_queue_buffer_v2(struct camss_video *vid,
struct camss_buffer *buf);
#endif /* QC_MSM_CAMSS_VFE_H */
@@ -0,0 +1,769 @@
// SPDX-License-Identifier: GPL-2.0
/*
* camss-video.c
*
* Qualcomm MSM Camera Subsystem - V4L2 device node
*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#include <linux/slab.h>
#include <media/media-entity.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/videobuf2-dma-sg.h>
#include "camss-video.h"
#include "camss.h"
#define CAMSS_FRAME_MIN_WIDTH 1
#define CAMSS_FRAME_MAX_WIDTH 8191
#define CAMSS_FRAME_MIN_HEIGHT 1
#define CAMSS_FRAME_MAX_HEIGHT_RDI 8191
#define CAMSS_FRAME_MAX_HEIGHT_PIX 4096
/* -----------------------------------------------------------------------------
* Helper functions
*/
/*
* video_mbus_to_pix_mp - Convert v4l2_mbus_framefmt to v4l2_pix_format_mplane
* @mbus: v4l2_mbus_framefmt format (input)
* @pix: v4l2_pix_format_mplane format (output)
* @f: a pointer to formats array element to be used for the conversion
* @alignment: bytesperline alignment value
*
* Fill the output pix structure with information from the input mbus format.
*
* Return 0 on success or a negative error code otherwise
*/
static int video_mbus_to_pix_mp(const struct v4l2_mbus_framefmt *mbus,
struct v4l2_pix_format_mplane *pix,
const struct camss_format_info *f,
unsigned int alignment)
{
unsigned int i;
u32 bytesperline;
if (!alignment)
alignment = camss_format_get_bpl_alignment(f);
memset(pix, 0, sizeof(*pix));
v4l2_fill_pix_format_mplane(pix, mbus);
pix->pixelformat = f->pixelformat;
pix->num_planes = f->planes;
for (i = 0; i < pix->num_planes; i++) {
bytesperline = pix->width / f->hsub[i].numerator *
f->hsub[i].denominator * f->bpp[i] / 8;
bytesperline = roundup(bytesperline, alignment);
pix->plane_fmt[i].bytesperline = bytesperline;
pix->plane_fmt[i].sizeimage = pix->height /
f->vsub[i].numerator * f->vsub[i].denominator *
bytesperline;
}
return 0;
}
static struct v4l2_subdev *video_remote_subdev(struct camss_video *video,
u32 *pad)
{
struct media_pad *remote;
remote = media_pad_remote_pad_first(&video->pad);
if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
return NULL;
if (pad)
*pad = remote->index;
return media_entity_to_v4l2_subdev(remote->entity);
}
static int video_get_subdev_format(struct camss_video *video,
struct v4l2_format *format)
{
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
};
struct v4l2_subdev *subdev;
u32 pad;
int ret;
subdev = video_remote_subdev(video, &pad);
if (subdev == NULL)
return -EPIPE;
fmt.pad = pad;
ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
if (ret)
return ret;
ret = camss_format_find_format(fmt.format.code, format->fmt.pix_mp.pixelformat,
video->formats, video->nformats);
if (ret < 0)
return ret;
format->type = video->type;
return video_mbus_to_pix_mp(&fmt.format, &format->fmt.pix_mp,
&video->formats[ret], video->bpl_alignment);
}
/* -----------------------------------------------------------------------------
* Video queue operations
*/
static int video_queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct camss_video *video = vb2_get_drv_priv(q);
const struct v4l2_pix_format_mplane *format =
&video->active_fmt.fmt.pix_mp;
unsigned int i;
if (*num_planes) {
if (*num_planes != format->num_planes)
return -EINVAL;
for (i = 0; i < *num_planes; i++)
if (sizes[i] < format->plane_fmt[i].sizeimage)
return -EINVAL;
return 0;
}
*num_planes = format->num_planes;
for (i = 0; i < *num_planes; i++)
sizes[i] = format->plane_fmt[i].sizeimage;
return 0;
}
static int video_buf_init(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
vb);
const struct v4l2_pix_format_mplane *format =
&video->active_fmt.fmt.pix_mp;
struct sg_table *sgt;
unsigned int i;
for (i = 0; i < format->num_planes; i++) {
sgt = vb2_dma_sg_plane_desc(vb, i);
if (!sgt)
return -EFAULT;
buffer->addr[i] = sg_dma_address(sgt->sgl);
}
if (format->pixelformat == V4L2_PIX_FMT_NV12 ||
format->pixelformat == V4L2_PIX_FMT_NV21 ||
format->pixelformat == V4L2_PIX_FMT_NV16 ||
format->pixelformat == V4L2_PIX_FMT_NV61)
buffer->addr[1] = buffer->addr[0] +
format->plane_fmt[0].bytesperline *
format->height;
return 0;
}
static int video_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
const struct v4l2_pix_format_mplane *format =
&video->active_fmt.fmt.pix_mp;
unsigned int i;
for (i = 0; i < format->num_planes; i++) {
if (format->plane_fmt[i].sizeimage > vb2_plane_size(vb, i))
return -EINVAL;
vb2_set_plane_payload(vb, i, format->plane_fmt[i].sizeimage);
}
vbuf->field = V4L2_FIELD_NONE;
return 0;
}
static void video_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue);
struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer,
vb);
video->ops->queue_buffer(video, buffer);
}
static int video_check_format(struct camss_video *video)
{
struct v4l2_pix_format_mplane *pix = &video->active_fmt.fmt.pix_mp;
struct v4l2_format format;
struct v4l2_pix_format_mplane *sd_pix = &format.fmt.pix_mp;
int ret;
sd_pix->pixelformat = pix->pixelformat;
ret = video_get_subdev_format(video, &format);
if (ret < 0)
return ret;
dev_dbg(video->camss->dev,
"%s: format is (%ux%u %p4cc/%up field:%u), trying (%ux%u %p4cc/%up field:%u)",
video->vdev.name, sd_pix->width, sd_pix->height, &sd_pix->pixelformat,
sd_pix->num_planes, sd_pix->field, pix->width, pix->height, &pix->pixelformat,
pix->num_planes, pix->field);
if (pix->pixelformat != sd_pix->pixelformat ||
pix->height != sd_pix->height ||
pix->width != sd_pix->width ||
pix->num_planes != sd_pix->num_planes ||
pix->field != format.fmt.pix_mp.field)
return -EPIPE;
return 0;
}
static int video_prepare_streaming(struct vb2_queue *q)
{
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
int ret;
ret = v4l2_pipeline_pm_get(&vdev->entity);
if (ret < 0) {
dev_err(video->camss->dev, "Failed to power up pipeline: %d\n",
ret);
}
return ret;
}
static int video_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
struct media_entity *entity;
struct media_pad *pad;
struct v4l2_subdev *subdev;
int ret;
ret = video_device_pipeline_alloc_start(vdev);
if (ret < 0) {
dev_err(video->camss->dev, "Failed to start media pipeline: %d\n", ret);
goto flush_buffers;
}
ret = video_check_format(video);
if (ret < 0)
goto error;
entity = &vdev->entity;
while (1) {
pad = &entity->pads[0];
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
ret = v4l2_subdev_call(subdev, video, s_stream, 1);
if (ret < 0 && ret != -ENOIOCTLCMD)
goto error;
}
return 0;
error:
video_device_pipeline_stop(vdev);
flush_buffers:
video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED);
return ret;
}
static void video_stop_streaming(struct vb2_queue *q)
{
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
struct media_entity *entity;
struct media_pad *pad;
struct v4l2_subdev *subdev;
int ret;
entity = &vdev->entity;
while (1) {
pad = &entity->pads[0];
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
ret = v4l2_subdev_call(subdev, video, s_stream, 0);
if (ret) {
dev_err(video->camss->dev, "Video pipeline stop failed: %d\n", ret);
return;
}
}
video_device_pipeline_stop(vdev);
video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR);
}
static void video_unprepare_streaming(struct vb2_queue *q)
{
struct camss_video *video = vb2_get_drv_priv(q);
struct video_device *vdev = &video->vdev;
v4l2_pipeline_pm_put(&vdev->entity);
}
static const struct vb2_ops msm_video_vb2_q_ops = {
.queue_setup = video_queue_setup,
.buf_init = video_buf_init,
.buf_prepare = video_buf_prepare,
.buf_queue = video_buf_queue,
.prepare_streaming = video_prepare_streaming,
.start_streaming = video_start_streaming,
.stop_streaming = video_stop_streaming,
.unprepare_streaming = video_unprepare_streaming,
};
/* -----------------------------------------------------------------------------
* V4L2 ioctls
*/
static int video_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
strscpy(cap->driver, "qcom-camss", sizeof(cap->driver));
strscpy(cap->card, "Qualcomm Camera Subsystem", sizeof(cap->card));
return 0;
}
static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
{
struct camss_video *video = video_drvdata(file);
int i, j, k;
u32 mcode = f->mbus_code;
if (f->type != video->type)
return -EINVAL;
if (f->index >= video->nformats)
return -EINVAL;
/*
* Find index "i" of "k"th unique pixelformat in formats array.
*
* If f->mbus_code passed to video_enum_fmt() is not zero, a device
* with V4L2_CAP_IO_MC capability restricts enumeration to only the
* pixel formats that can be produced from that media bus code.
* This is implemented by skipping video->formats[] entries with
* code != f->mbus_code (if f->mbus_code is not zero).
* If the f->mbus_code passed to video_enum_fmt() is not supported,
* -EINVAL is returned.
* If f->mbus_code is zero, all the pixel formats are enumerated.
*/
k = -1;
for (i = 0; i < video->nformats; i++) {
if (mcode != 0 && video->formats[i].code != mcode)
continue;
for (j = 0; j < i; j++) {
if (mcode != 0 && video->formats[j].code != mcode)
continue;
if (video->formats[i].pixelformat ==
video->formats[j].pixelformat)
break;
}
if (j == i)
k++;
if (k == f->index)
break;
}
if (k == -1 || k < f->index)
/*
* All the unique pixel formats matching the arguments
* have been enumerated (k >= 0 and f->index > 0), or
* no pixel formats match the non-zero f->mbus_code (k == -1).
*/
return -EINVAL;
f->pixelformat = video->formats[i].pixelformat;
return 0;
}
static int video_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct camss_video *video = video_drvdata(file);
int i;
if (fsize->index)
return -EINVAL;
/* Only accept pixel format present in the formats[] table */
for (i = 0; i < video->nformats; i++) {
if (video->formats[i].pixelformat == fsize->pixel_format)
break;
}
if (i == video->nformats)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
fsize->stepwise.min_width = CAMSS_FRAME_MIN_WIDTH;
fsize->stepwise.max_width = CAMSS_FRAME_MAX_WIDTH;
fsize->stepwise.min_height = CAMSS_FRAME_MIN_HEIGHT;
fsize->stepwise.max_height = (video->line_based) ?
CAMSS_FRAME_MAX_HEIGHT_PIX : CAMSS_FRAME_MAX_HEIGHT_RDI;
fsize->stepwise.step_width = 1;
fsize->stepwise.step_height = 1;
return 0;
}
static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
{
struct camss_video *video = video_drvdata(file);
*f = video->active_fmt;
return 0;
}
static int __video_try_fmt(struct camss_video *video, struct v4l2_format *f)
{
unsigned int alignment = video->bpl_alignment;
struct v4l2_pix_format_mplane *pix_mp;
const struct camss_format_info *fi;
struct v4l2_plane_pix_format *p;
u32 bytesperline[3] = { 0 };
u32 sizeimage[3] = { 0 };
u32 width, height;
u32 bpl, lines;
int i, j;
pix_mp = &f->fmt.pix_mp;
if (video->line_based)
for (i = 0; i < pix_mp->num_planes && i < 3; i++) {
p = &pix_mp->plane_fmt[i];
bytesperline[i] = clamp_t(u32, p->bytesperline,
1, 65528);
sizeimage[i] = clamp_t(u32, p->sizeimage,
bytesperline[i],
bytesperline[i] * CAMSS_FRAME_MAX_HEIGHT_PIX);
}
for (j = 0; j < video->nformats; j++)
if (pix_mp->pixelformat == video->formats[j].pixelformat)
break;
if (j == video->nformats)
j = 0; /* default format */
fi = &video->formats[j];
width = pix_mp->width;
height = pix_mp->height;
if (!alignment)
alignment = camss_format_get_bpl_alignment(fi);
memset(pix_mp, 0, sizeof(*pix_mp));
pix_mp->pixelformat = fi->pixelformat;
pix_mp->width = clamp_t(u32, width, 1, CAMSS_FRAME_MAX_WIDTH);
pix_mp->height = clamp_t(u32, height, 1, CAMSS_FRAME_MAX_HEIGHT_RDI);
pix_mp->num_planes = fi->planes;
for (i = 0; i < pix_mp->num_planes; i++) {
bpl = pix_mp->width / fi->hsub[i].numerator *
fi->hsub[i].denominator * fi->bpp[i] / 8;
bpl = roundup(bpl, alignment);
pix_mp->plane_fmt[i].bytesperline = bpl;
pix_mp->plane_fmt[i].sizeimage = pix_mp->height /
fi->vsub[i].numerator * fi->vsub[i].denominator * bpl;
}
pix_mp->field = V4L2_FIELD_NONE;
pix_mp->colorspace = V4L2_COLORSPACE_SRGB;
pix_mp->flags = 0;
pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace);
pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
pix_mp->colorspace, pix_mp->ycbcr_enc);
pix_mp->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace);
if (video->line_based)
for (i = 0; i < pix_mp->num_planes; i++) {
p = &pix_mp->plane_fmt[i];
p->bytesperline = clamp_t(u32, p->bytesperline,
1, 65528);
p->sizeimage = clamp_t(u32, p->sizeimage,
p->bytesperline,
p->bytesperline * CAMSS_FRAME_MAX_HEIGHT_PIX);
lines = p->sizeimage / p->bytesperline;
if (p->bytesperline < bytesperline[i])
p->bytesperline = roundup(bytesperline[i], alignment);
if (p->sizeimage < p->bytesperline * lines)
p->sizeimage = p->bytesperline * lines;
if (p->sizeimage < sizeimage[i])
p->sizeimage = sizeimage[i];
}
return 0;
}
static int video_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
{
struct camss_video *video = video_drvdata(file);
return __video_try_fmt(video, f);
}
static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
{
struct camss_video *video = video_drvdata(file);
int ret;
if (vb2_is_busy(&video->vb2_q))
return -EBUSY;
ret = __video_try_fmt(video, f);
if (ret < 0)
return ret;
video->active_fmt = *f;
return 0;
}
static int video_enum_input(struct file *file, void *fh,
struct v4l2_input *input)
{
if (input->index > 0)
return -EINVAL;
strscpy(input->name, "camera", sizeof(input->name));
input->type = V4L2_INPUT_TYPE_CAMERA;
return 0;
}
static int video_g_input(struct file *file, void *fh, unsigned int *input)
{
*input = 0;
return 0;
}
static int video_s_input(struct file *file, void *fh, unsigned int input)
{
return input == 0 ? 0 : -EINVAL;
}
static const struct v4l2_ioctl_ops msm_vid_ioctl_ops = {
.vidioc_querycap = video_querycap,
.vidioc_enum_fmt_vid_cap = video_enum_fmt,
.vidioc_enum_framesizes = video_enum_framesizes,
.vidioc_g_fmt_vid_cap_mplane = video_g_fmt,
.vidioc_s_fmt_vid_cap_mplane = video_s_fmt,
.vidioc_try_fmt_vid_cap_mplane = video_try_fmt,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_enum_input = video_enum_input,
.vidioc_g_input = video_g_input,
.vidioc_s_input = video_s_input,
};
/* -----------------------------------------------------------------------------
* V4L2 file operations
*/
static const struct v4l2_file_operations msm_vid_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
.read = vb2_fop_read,
};
/* -----------------------------------------------------------------------------
* CAMSS video core
*/
static void msm_video_release(struct video_device *vdev)
{
struct camss_video *video = video_get_drvdata(vdev);
media_entity_cleanup(&vdev->entity);
mutex_destroy(&video->q_lock);
mutex_destroy(&video->lock);
if (atomic_dec_and_test(&video->camss->ref_count))
camss_delete(video->camss);
}
/*
* msm_video_init_format - Helper function to initialize format
* @video: struct camss_video
*
* Initialize pad format with default value.
*
* Return 0 on success or a negative error code otherwise
*/
static int msm_video_init_format(struct camss_video *video)
{
int ret;
struct v4l2_format format = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
.fmt.pix_mp = {
.width = 1920,
.height = 1080,
.pixelformat = video->formats[0].pixelformat,
},
};
ret = __video_try_fmt(video, &format);
if (ret < 0)
return ret;
video->active_fmt = format;
return 0;
}
/*
* msm_video_register - Register a video device node
* @video: struct camss_video
* @v4l2_dev: V4L2 device
* @name: name to be used for the video device node
*
* Initialize and register a video device node to a V4L2 device. Also
* initialize the vb2 queue.
*
* Return 0 on success or a negative error code otherwise
*/
int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
const char *name)
{
struct media_pad *pad = &video->pad;
struct video_device *vdev;
struct vb2_queue *q;
int ret;
vdev = &video->vdev;
mutex_init(&video->q_lock);
q = &video->vb2_q;
q->drv_priv = video;
q->mem_ops = &vb2_dma_sg_memops;
q->ops = &msm_video_vb2_q_ops;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
q->io_modes = VB2_DMABUF | VB2_MMAP | VB2_READ;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->buf_struct_size = sizeof(struct camss_buffer);
q->dev = video->camss->dev;
q->lock = &video->q_lock;
ret = vb2_queue_init(q);
if (ret < 0) {
dev_err(v4l2_dev->dev, "Failed to init vb2 queue: %d\n", ret);
goto error_vb2_init;
}
pad->flags = MEDIA_PAD_FL_SINK;
ret = media_entity_pads_init(&vdev->entity, 1, pad);
if (ret < 0) {
dev_err(v4l2_dev->dev, "Failed to init video entity: %d\n",
ret);
goto error_vb2_init;
}
mutex_init(&video->lock);
ret = msm_video_init_format(video);
if (ret < 0) {
dev_err(v4l2_dev->dev, "Failed to init format: %d\n", ret);
goto error_video_register;
}
vdev->fops = &msm_vid_fops;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING
| V4L2_CAP_READWRITE | V4L2_CAP_IO_MC;
vdev->ioctl_ops = &msm_vid_ioctl_ops;
vdev->release = msm_video_release;
vdev->v4l2_dev = v4l2_dev;
vdev->vfl_dir = VFL_DIR_RX;
vdev->queue = &video->vb2_q;
vdev->lock = &video->lock;
strscpy(vdev->name, name, sizeof(vdev->name));
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
dev_err(v4l2_dev->dev, "Failed to register video device: %d\n",
ret);
goto error_video_register;
}
video_set_drvdata(vdev, video);
atomic_inc(&video->camss->ref_count);
return 0;
error_video_register:
media_entity_cleanup(&vdev->entity);
mutex_destroy(&video->lock);
error_vb2_init:
mutex_destroy(&video->q_lock);
return ret;
}
void msm_video_unregister(struct camss_video *video)
{
atomic_inc(&video->camss->ref_count);
vb2_video_unregister_device(&video->vdev);
atomic_dec(&video->camss->ref_count);
}
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss-video.h
*
* Qualcomm MSM Camera Subsystem - V4L2 device node
*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_VIDEO_H
#define QC_MSM_CAMSS_VIDEO_H
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <media/media-entity.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-mediabus.h>
#include <media/videobuf2-v4l2.h>
struct camss_buffer {
struct vb2_v4l2_buffer vb;
dma_addr_t addr[3];
struct list_head queue;
};
struct camss_video;
struct camss_video_ops {
int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf);
int (*flush_buffers)(struct camss_video *vid,
enum vb2_buffer_state state);
};
struct camss_video {
struct camss *camss;
struct vb2_queue vb2_q;
struct video_device vdev;
struct media_pad pad;
struct v4l2_format active_fmt;
enum v4l2_buf_type type;
struct media_pipeline pipe;
const struct camss_video_ops *ops;
struct mutex lock;
struct mutex q_lock;
unsigned int bpl_alignment;
unsigned int line_based;
const struct camss_format_info *formats;
unsigned int nformats;
};
int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev,
const char *name);
void msm_video_unregister(struct camss_video *video);
#endif /* QC_MSM_CAMSS_VIDEO_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,183 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* camss.h
*
* Qualcomm MSM Camera Subsystem - Core
*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
* Copyright (C) 2015-2018 Linaro Ltd.
*/
#ifndef QC_MSM_CAMSS_H
#define QC_MSM_CAMSS_H
#include <linux/device.h>
#include <linux/types.h>
#include <media/v4l2-async.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/media-device.h>
#include <media/media-entity.h>
#include "camss-csid.h"
#include "camss-csiphy.h"
#include "camss-ispif.h"
#include "camss-tpg.h"
#include "camss-vfe.h"
#include "camss-format.h"
#define to_camss(ptr_module) \
container_of(ptr_module, struct camss, ptr_module)
#define to_device(ptr_module) \
(to_camss(ptr_module)->dev)
#define module_pointer(ptr_module, index) \
((const struct ptr_module##_device (*)[]) &(ptr_module[-(index)]))
#define to_camss_index(ptr_module, index) \
container_of(module_pointer(ptr_module, index), \
struct camss, ptr_module)
#define to_device_index(ptr_module, index) \
(to_camss_index(ptr_module, index)->dev)
#define CAMSS_RES_MAX 17
#define CAMSS_INIT_BUF_COUNT 2
struct camss_subdev_resources {
struct regulator_bulk_data regulators[CAMSS_RES_MAX];
char *clock[CAMSS_RES_MAX];
char *clock_for_reset[CAMSS_RES_MAX];
u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX];
char *reg[CAMSS_RES_MAX];
char *interrupt[CAMSS_RES_MAX];
union {
struct csiphy_subdev_resources csiphy;
struct tpg_subdev_resources tpg;
struct csid_subdev_resources csid;
struct vfe_subdev_resources vfe;
};
};
struct icc_bw_tbl {
u32 avg;
u32 peak;
};
struct resources_icc {
char *name;
struct icc_bw_tbl icc_bw_tbl;
};
struct resources_wrapper {
char *reg;
};
enum pm_domain {
PM_DOMAIN_VFE0 = 0,
PM_DOMAIN_VFE1 = 1,
PM_DOMAIN_VFELITE = 2, /* VFELITE / TOP GDSC */
};
enum camss_version {
CAMSS_660,
CAMSS_2290,
CAMSS_6150,
CAMSS_6350,
CAMSS_7280,
CAMSS_8x16,
CAMSS_8x39,
CAMSS_8x53,
CAMSS_8x96,
CAMSS_8250,
CAMSS_8280XP,
CAMSS_8300,
CAMSS_845,
CAMSS_8550,
CAMSS_8650,
CAMSS_8775P,
CAMSS_X1E80100,
};
enum icc_count {
ICC_DEFAULT_COUNT = 0,
ICC_SM8250_COUNT = 4,
};
struct camss_resources {
enum camss_version version;
const char *pd_name;
const struct camss_subdev_resources *csiphy_res;
const struct camss_subdev_resources *tpg_res;
const struct camss_subdev_resources *csid_res;
const struct camss_subdev_resources *ispif_res;
const struct camss_subdev_resources *vfe_res;
const struct resources_wrapper *csid_wrapper_res;
const struct resources_icc *icc_res;
const unsigned int icc_path_num;
const unsigned int csiphy_num;
const unsigned int tpg_num;
const unsigned int csid_num;
const unsigned int vfe_num;
};
struct camss {
struct v4l2_device v4l2_dev;
struct v4l2_async_notifier notifier;
struct media_device media_dev;
struct device *dev;
struct csiphy_device *csiphy;
struct tpg_device *tpg;
struct csid_device *csid;
struct ispif_device *ispif;
struct vfe_device *vfe;
void __iomem *csid_wrapper_base;
atomic_t ref_count;
int genpd_num;
struct device *genpd;
struct device_link *genpd_link;
struct icc_path *icc_path[ICC_SM8250_COUNT];
const struct camss_resources *res;
};
struct camss_camera_interface {
u8 csiphy_id;
struct csiphy_csi2_cfg csi2;
};
struct camss_async_subdev {
struct v4l2_async_connection asd; /* must be first */
struct camss_camera_interface interface;
};
struct camss_clock {
struct clk *clk;
const char *name;
u32 *freq;
u32 nfreqs;
};
struct parent_dev_ops {
int (*get)(struct camss *camss, int id);
int (*put)(struct camss *camss, int id);
void __iomem *(*get_base_address)(struct camss *camss, int id);
};
void camss_add_clock_margin(u64 *rate);
int camss_enable_clocks(int nclocks, struct camss_clock *clock,
struct device *dev);
void camss_disable_clocks(int nclocks, struct camss_clock *clock);
struct media_pad *camss_find_sensor_pad(struct media_entity *entity);
s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp,
unsigned int lanes);
int camss_get_pixel_clock(struct media_entity *entity, u64 *pixel_clock);
int camss_pm_domain_on(struct camss *camss, int id);
void camss_pm_domain_off(struct camss *camss, int id);
int camss_vfe_get(struct camss *camss, int id);
void camss_vfe_put(struct camss *camss, int id);
void camss_delete(struct camss *camss);
void camss_buf_done(struct camss *camss, int hw_id, int port_id);
void camss_reg_update(struct camss *camss, int hw_id,
int port_id, bool is_clear);
#endif /* QC_MSM_CAMSS_H */