From nobody Fri Apr 3 22:26:42 2026 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8D3FD3AA518 for ; Mon, 23 Mar 2026 12:58:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774270719; cv=none; b=E6Cdc7ST5etdVeMXbfGBFYcuZ3kiuymRbvc7bI6CcqRVcDOCySx0D1/khXABx8yERly4hYjE0RyM0QLqs32STD9fu0LacvDwe940gQyWGsljk44MWpEjWxwuNFaxxEo9o0TFDgcOZ2ZJHMxdG6TiL61tFv4TA4uf7NgpnbnOXGQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774270719; c=relaxed/simple; bh=fv62vrCblxbrr9+guksU0SILbiYn9lKiXaU9W0xM+D4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=quWASMURVDFOkIkixb5IH34WiOMJMOivhYnqkUHCk3lh47tixpumgU2fh3AUVa3HFtqEsqVXegwHh9jBmuWiZEdlgeM3tdGCod+DbHPXVlqbsLLoGt7HOw5afA/Id9w3l67UB8oEMpIQwU9tjWpts4IHMVClsXwEOGYrMV6LAIc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=SYoGJ+vQ; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=J3AydpIt; arc=none smtp.client-ip=205.220.180.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="SYoGJ+vQ"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="J3AydpIt" Received: from pps.filterd (m0279873.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 62N83wxh1364358 for ; Mon, 23 Mar 2026 12:58:34 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=qcppdkim1; bh=7MgWT7iXoY6 ywL0vXsyWQ2fjILwEE/QnPPlO3ww6N30=; b=SYoGJ+vQp5SOfnZNwjMLpo5Et5Y dcWIeShdOClVkvX0BlJU4eEx+eQFJMA+FYtsaOJKkN6N1OBQjLoQAOPaTqeMWWe6 zPZFVx/WRUwtg0zsPmpJl/TV5+eX/e7pCcLPRAbfach6VW5PVFJ/yH0c/rvuphmE UWN/958z0jLwq95bEeVzWszlkLk7YxhScEuoMuzDEvm37tL16m1RTxxFT6wyg1od 4RCzrc6+nE9JZ7SJWDz4zkGpjEQww9uLtujGyYQvsqMH8rP8T2dovY90DFUlfGAL oayoEFJ+/vkq+Dv1Kk6fP8pLQqH8MAEkyeu1Dit9FOEr1ek+vo5ywi1TkfQ== Received: from mail-qt1-f197.google.com (mail-qt1-f197.google.com [209.85.160.197]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4d31p79172-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Mon, 23 Mar 2026 12:58:34 +0000 (GMT) Received: by mail-qt1-f197.google.com with SMTP id d75a77b69052e-50b44f7b7bbso202041001cf.3 for ; Mon, 23 Mar 2026 05:58:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1774270714; x=1774875514; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=7MgWT7iXoY6ywL0vXsyWQ2fjILwEE/QnPPlO3ww6N30=; b=J3AydpIt7x2yuyfSvjVFpnfenKBE8W2d5ERkajyjSzfZuB9g4+vlK8T86YOyShKFGR i3SQGq/cBpw5W+rb39IjLq4kbBVWf0o8YPlHMNG+u4lSjfDP8Hs0qjQTLGlcP9tPuWF5 14jC5LyDaK2VmKPvfLhQknY6NJro0JWvzGeiQByj5cLgVI1E+G1OgkE7nsuL1BgrTsA1 +peWdGgnk5xqdoMmXS6O50h1FWYLDNoPhjjskT3qj18H3glCxu7DvaI+P5nIRvYgw2mc 3iClWLAZEnHPnOvvfo6Jk5izhdvaAYJyiSZmk59V4MMgfvUOhImU0nrbcjOa+BIBczW7 tOqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774270714; x=1774875514; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=7MgWT7iXoY6ywL0vXsyWQ2fjILwEE/QnPPlO3ww6N30=; b=gGpduYZv/kucPNxHTcHfEbWinZ784ZKH5xX5KhjnQZiO4sy7TRbPOzz/S1ktzQNORS uEXgvXhYNn1aThxGGxuBy0VjLQ6Lv52rkpLr6pGAjeQ8MfDRXGnT74aPJM4iPNhxcM6O /X97vYhChNF3Vva3t3Kc2JCUY2n1Rw1jx3fMgYharW6mL8htK1xPWbapYHJbDIl/KFvk CkgBfR35hFaSaRYyMK0eL1tflbVYF+fK5QlYousYJU5MBCcNQCVKUGaEbvpyPek61kDF pVoRwNTM1M8q4Dfv+IDLx+H+aI6Efm9jJeY9HGF8ANDwyDGHR0VFij6pnjzPbjWyIKqY EgAQ== X-Forwarded-Encrypted: i=1; AJvYcCU/z9WHTPvD9/XVDFE4qjCGCSkUj8kSF2VshA1es2+K1xwlkEaC2RlZfbohLubR/GxpIEqFuYZcS6Tv3xk=@vger.kernel.org X-Gm-Message-State: AOJu0Yw9OGdCl3P+jVZpLYubnLg06/rIc5Npfuno4i9MSKB6Kd9aGiSk ZQCSLaeMUotJcpzA9PwaEBC6zJCvkaXuD6pZKU9h2bKV0ycjuLrVV9Lut9WKNqxI50BQyNG6YK6 N6cuPl2eXlbrlvbnSbB7c4sgoSnlfA5r2ShUusXsXg5185QjWZC5YK6ljt6yJKGHRj4Y= X-Gm-Gg: ATEYQzz0361ScCNyAghDYJ40LZ+hMjxfUyyYcA6fjJpt7Xvs5ju6hwKIOwcyiNhmFyX D0sFEY3Zc6PkslUPGXuxm6+ABYLE/CgtFGjWygYCCuYVSmzGygnRYgElob7/gD6ICOCrUpLc33u n7pdvnBJHQo4U9JdP+sNYrDJbP9ijmTWzTw6x8dQaY5yuEcygmcr0k5wGRZOqSbJgRn+dJnKHkO yYkBblExM0uN/GWINj2deujKFheHyYSGtTqFRSUVHriiAkqZgtMS+fhg4Gek4FvnxkyYdfiqQ8I 7Eoq73ATncTzOz4kGRec/3mqIq6huZ+A7h8m9Q6qLBfUddXO3X5UbNCE7DlNQgGcvty6zYYq/CV c53dH+BFWuoxBjz66z9ZWesVSkLATT0gu4R42h0SXVk+3DJlOTi56Uej9DfIZe5fFPWMevo/HBP lodczMlAsZZ7sG X-Received: by 2002:a05:622a:514b:b0:50b:506d:7381 with SMTP id d75a77b69052e-50b506d84b3mr96701421cf.57.1774270712894; Mon, 23 Mar 2026 05:58:32 -0700 (PDT) X-Received: by 2002:a05:622a:514b:b0:50b:506d:7381 with SMTP id d75a77b69052e-50b506d84b3mr96700871cf.57.1774270711839; Mon, 23 Mar 2026 05:58:31 -0700 (PDT) Received: from QCOM-eG0v1AUPpu.qualcomm.com ([2a01:e0a:830:450:d9f3:cf55:9f3d:be1c]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-b98335ddfb9sm487139066b.37.2026.03.23.05.58.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 05:58:30 -0700 (PDT) From: Loic Poulain To: bod@kernel.org, vladimir.zapolskiy@linaro.org, laurent.pinchart@ideasonboard.com, kieran.bingham@ideasonboard.com, robh@kernel.org, krzk+dt@kernel.org, andersson@kernel.org, konradybcio@kernel.org Cc: linux-media@vger.kernel.org, linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, johannes.goede@oss.qualcomm.com, mchehab@kernel.org, Loic Poulain Subject: [RFC PATCH 2/3] media: qcom: camss: Add CAMSS Offline Processing Engine driver Date: Mon, 23 Mar 2026 13:58:23 +0100 Message-Id: <20260323125824.211615-3-loic.poulain@oss.qualcomm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260323125824.211615-1-loic.poulain@oss.qualcomm.com> References: <20260323125824.211615-1-loic.poulain@oss.qualcomm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Authority-Analysis: v=2.4 cv=RMC+3oi+ c=1 sm=1 tr=0 ts=69c138fa cx=c_pps a=EVbN6Ke/fEF3bsl7X48z0g==:117 a=xqWC_Br6kY4A:10 a=Yq5XynenixoA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=rJkE3RaqiGZ5pbrm-msn:22 a=EUspDBNiAAAA:8 a=uBxOod-fjpvI_6RNAfAA:9 a=O8hF6Hzn-FEA:10 a=a_PwQJl-kcHnX1M80qC6:22 X-Proofpoint-ORIG-GUID: q6iuzkxpj9umMd5UBM5QtsBXp_oHgLzt X-Proofpoint-GUID: q6iuzkxpj9umMd5UBM5QtsBXp_oHgLzt X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMzIzMDEwMCBTYWx0ZWRfX1dL6y2bS33/P NFkrKNv7gJPw+opJnNiwyAnCIawv02obVn24YPbQcF0+eA3dY+5/M27O5k+5bsINI6sq8t9WSMV N1fWiJxeNLO1opFwp67QmLYx0dc+VwXHcmhS5CdFYPID+P/NaELp5dLk8szjEgSG84mraDNqjWY +kpMyA4qn9V6v+hM65nydDYFvHbgCDv1dgrIsUCKdq2T60Ab8Hp3Wfs7tZYgHh05Y1+3o7DtOxF cQ45LHbKGLc9jJuJHCPSWa6zkOiQjkWT77osguKUWe8Xk7tkTJd1etZAgzT3wkGGHWMLEA4BeTV xfDZX2DcM1oF6VKYzxxCIiBruTNVj6XnyPHMknnrO5IZiMD55/uh5jRebFaPA7rruiQqpRbs/nK j/w7v7FUWcRa/r8ZQQB1a821l8j6Hmgy7avDaOq48bOIRuotDu/qzngZjxCOY5GuxZJkFEGdPRu gYyWhDztNYD7LnUJprw== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.51,FMLib:17.12.100.49 definitions=2026-03-23_03,2026-03-20_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 phishscore=0 lowpriorityscore=0 impostorscore=0 priorityscore=1501 bulkscore=0 spamscore=0 adultscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2603050001 definitions=main-2603230100 Content-Type: text/plain; charset="utf-8" Provide a initial implementation for the Qualcomm Offline Processing Engine (OPE). OPE is a memory-to-memory hardware block designed for image processing on a source frame. Typically, the input frame originates from the SoC CSI capture path, though not limited to. The hardware architecture consists of Fetch Engines and Write Engines, connected through intermediate pipeline modules: [FETCH ENGINES] =3D> [Pipeline Modules] =3D> [WRITE ENGINES] Current Configuration: Fetch Engine: One fetch engine is used for Bayer frame input. Write Engines: Two display write engines for Y and UV planes output. Enabled Pipeline Modules: CLC_WB: White balance (channel gain configuration) CLC_DEMO: Demosaic (Bayer to RGB conversion) CLC_CHROMA_ENHAN: RGB to YUV conversion CLC_DOWNSCALE*: Downscaling for UV and Y planes Default configuration values are based on public standards such as BT.601. Processing Model: OPE processes frames in stripes of up to 336 pixels. Therefore, frames must be split into stripes for processing. Each stripe is configured after the previous one has been acquired (double buffered registers). To minimize inter-stripe latency, stripe configurations are generated ahead of time. Signed-off-by: Loic Poulain --- drivers/media/platform/qcom/camss/Makefile | 4 + drivers/media/platform/qcom/camss/camss-ope.c | 2058 +++++++++++++++++ 2 files changed, 2062 insertions(+) create mode 100644 drivers/media/platform/qcom/camss/camss-ope.c diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/pla= tform/qcom/camss/Makefile index 5e349b491513..67f261ae0855 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -29,3 +29,7 @@ qcom-camss-objs +=3D \ camss-format.o \ =20 obj-$(CONFIG_VIDEO_QCOM_CAMSS) +=3D qcom-camss.o + +qcom-camss-ope-objs +=3D camss-ope.o + +obj-$(CONFIG_VIDEO_QCOM_CAMSS) +=3D qcom-camss-ope.o diff --git a/drivers/media/platform/qcom/camss/camss-ope.c b/drivers/media/= platform/qcom/camss/camss-ope.c new file mode 100644 index 000000000000..f45a16437b6d --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-ope.c @@ -0,0 +1,2058 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-ope.c + * + * Qualcomm MSM Camera Subsystem - Offline Processing Engine + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/* + * This driver provides a minimal implementation for the Qualcomm Offline + * Processing Engine (OPE). OPE is a memory-to-memory hardware block + * designed for image processing on a source frame. Typically, the input + * frame originates from the SoC CSI capture path, though not limited to. + * + * The hardware architecture consists of Fetch Engines and Write Engines, + * connected through intermediate pipeline modules: + * [FETCH ENGINES] =3D> [Pipeline Modules] =3D> [WRITE ENGINES] + * + * Current Configuration: + * Fetch Engine: One fetch engine is used for Bayer frame input. + * Write Engines: Two display write engines for Y and UV planes output. + * + * Only a subset of the pipeline modules are enabled: + * CLC_WB: White balance for channel gain configuration + * CLC_DEMO: Demosaic for Bayer to RGB conversion + * CLC_CHROMA_ENHAN: for RGB to YUV conversion + * CLC_DOWNSCALE*: Downscaling for UV (YUV444 -> YUV422/YUV420) and YUV = planes + * + * Default configuration values are based on public standards such as BT.6= 01. + * + * Processing Model: + * OPE processes frames in stripes of up to 336 pixels. Therefore, frames = must + * be split into stripes for processing. Each stripe is configured after t= he + * previous one has been acquired (double buffered registers). To minimize + * inter-stripe latency, the stripe configurations are generated ahead of = time. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MEM2MEM_NAME "qcom-camss-ope" + +/* TOP registers */ +#define OPE_TOP_HW_VERSION 0x000 +#define OPE_TOP_HW_VERSION_STEP GENMASK(15, 0) +#define OPE_TOP_HW_VERSION_REV GENMASK(27, 16) +#define OPE_TOP_HW_VERSION_GEN GENMASK(31, 28) +#define OPE_TOP_RESET_CMD 0x004 +#define OPE_TOP_RESET_CMD_HW BIT(0) +#define OPE_TOP_RESET_CMD_SW BIT(1) +#define OPE_TOP_CORE_CFG 0x010 +#define OPE_TOP_IRQ_STATUS 0x014 +#define OPE_TOP_IRQ_MASK 0x018 +#define OPE_TOP_IRQ_STATUS_RST_DONE BIT(0) +#define OPE_TOP_IRQ_STATUS_WE BIT(1) +#define OPE_TOP_IRQ_STATUS_FE BIT(2) +#define OPE_TOP_IRQ_STATUS_VIOL BIT(3) +#define OPE_TOP_IRQ_STATUS_IDLE BIT(4) +#define OPE_TOP_IRQ_CLEAR 0x01c +#define OPE_TOP_IRQ_SET 0x020 +#define OPE_TOP_IRQ_CMD 0x024 +#define OPE_TOP_IRQ_CMD_CLEAR BIT(0) +#define OPE_TOP_IRQ_CMD_SET BIT(4) +#define OPE_TOP_VIOLATION_STATUS 0x028 +#define OPE_TOP_DEBUG(i) (0x0a0 + (i) * 4) +#define OPE_TOP_DEBUG_CFG 0x0dc + +/* Fetch engines */ +#define OPE_BUS_RD_INPUT_IF_IRQ_MASK 0x00c +#define OPE_BUS_RD_INPUT_IF_IRQ_CLEAR 0x010 +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD 0x014 +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR BIT(0) +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD_SET BIT(4) +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS 0x018 +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS_RST_DONE BIT(0) +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS_RUP_DONE BIT(1) +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS_BUF_DONE BIT(2) +#define OPE_BUS_RD_INPUT_IF_CMD 0x01c +#define OPE_BUS_RD_INPUT_IF_CMD_GO_CMD BIT(0) +#define OPE_BUS_RD_CLIENT_0_CORE_CFG 0x050 +#define OPE_BUS_RD_CLIENT_0_CORE_CFG_EN BIT(0) +#define OPE_BUS_RD_CLIENT_0_CCIF_META_DATA 0x054 +#define OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN GENMASK(7, 2) +#define OPE_BUS_RD_CLIENT_0_ADDR_IMAGE 0x058 +#define OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE 0x05c +#define OPE_BUS_RD_CLIENT_0_RD_STRIDE 0x060 +#define OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0 0x064 + +/* Write engines */ +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_0 0x018 +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_1 0x01c +#define OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0 0x020 +#define OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_1 0x024 +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0 0x028 +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE BIT(0) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_BUF_DONE BIT(8) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL BIT(28) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL BIT(30) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL BIT(31) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_1 0x02c +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_1_CLIENT_DONE(c) BIT(0 + (c)) +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD 0x030 +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR BIT(0) +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD_SET BIT(1) +#define OPE_BUS_WR_VIOLATION_STATUS 0x064 +#define OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS 0x070 +#define OPE_BUS_WR_CLIENT_CFG(c) (0x200 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_CFG_EN BIT(0) +#define OPE_BUS_WR_CLIENT_CFG_AUTORECOVER BIT(4) +#define OPE_BUS_WR_CLIENT_ADDR_IMAGE(c) (0x204 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_0(c) (0x20c + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_1(c) (0x210 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_2(c) (0x214 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_PACKER_CFG(c) (0x218 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_ADDR_FRAME_HEADER(c) (0x220 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_MAX 8 + +/* Pipeline modules */ +#define OPE_PP_CLC_WB_GAIN_MODULE_CFG (0x200 + 0x60) +#define OPE_PP_CLC_WB_GAIN_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_WB_GAIN_WB_CFG(ch) (0x200 + 0x68 + 4 * (ch)) + +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_PRE_BASE 0x1c00 +#define OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE 0x3000 +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE 0x3200 +#define OPE_PP_CLC_DOWNSCALE_MN_CFG(ds) ((ds) + 0x60) +#define OPE_PP_CLC_DOWNSCALE_MN_CFG_EN BIT(0) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(ds) ((ds) + 0x64) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN BIT(9) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN BIT(10) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(ds) ((ds) + 0x68) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(ds) ((ds) + 0x6c) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(ds) ((ds) + 0x74) + +#define OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG (0x1200 + 0x60) +#define OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0 (0x1200 + 0x68) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0 GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1 GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1 (0x1200 + 0x6c) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K GENMASK(31, 23) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2 (0x1200 + 0x70) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2 GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG (0x1200 + 0x74) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG (0x1200 + 0x78) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG (0x1200 + 0x7C) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG (0x1200 + 0x80) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0 (0x1200 + 0x84) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB GENMASK(31, 21) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1 (0x1200 + 0x88) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR GENMASK(31, 21) + +#define OPE_STRIPE_MAX_W 336 +#define OPE_STRIPE_MAX_H 8192 +#define OPE_STRIPE_MIN_W 16 +#define OPE_STRIPE_MIN_H OPE_STRIPE_MIN_W +#define OPE_MAX_STRIPE 16 +#define OPE_ALIGN_H 1 +#define OPE_ALIGN_W 1 +#define OPE_MIN_W 24 +#define OPE_MIN_H 16 +#define OPE_MAX_W (OPE_STRIPE_MAX_W * OPE_MAX_STRIPE) +#define OPE_MAX_H OPE_STRIPE_MAX_H + +#define MEM2MEM_CAPTURE BIT(0) +#define MEM2MEM_OUTPUT BIT(1) + +#define OPE_RESET_TIMEOUT_MS 100 + +/* Expected framerate for power scaling */ +#define DEFAULT_FRAMERATE 60 + +/* Downscaler helpers */ +#define Q21(v) (((uint64_t)(v)) << 21) +#define DS_Q21(n, d) ((uint32_t)(((uint64_t)(n) << 21) / (d))) +#define DS_RESOLUTION(input, output) \ + (((output) * 128 <=3D (input)) ? 0x0 : \ + ((output) * 16 <=3D (input)) ? 0x1 : \ + ((output) * 8 <=3D (input)) ? 0x2 : 0x3) +#define DS_OUTPUT_PIX(input, phase_init, phase_step) \ + ((Q21(input) - (phase_init)) / (phase_step)) + +enum ope_downscaler { + OPE_DS_C_PRE, + OPE_DS_C_DISP, + OPE_DS_Y_DISP, + OPE_DS_MAX, +}; + +static const u32 ope_ds_base[OPE_DS_MAX] =3D { OPE_PP_CLC_DOWNSCALE_MN_DS_= C_PRE_BASE, + OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE, + OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE }; + +enum ope_wr_client { + OPE_WR_CLIENT_VID_Y, + OPE_WR_CLIENT_VID_C, + OPE_WR_CLIENT_DISP_Y, + OPE_WR_CLIENT_DISP_C, + OPE_WR_CLIENT_ARGB, + OPE_WR_CLIENT_MAX +}; + +enum ope_pixel_pattern { + OPE_PIXEL_PATTERN_RGRGRG, + OPE_PIXEL_PATTERN_GRGRGR, + OPE_PIXEL_PATTERN_BGBGBG, + OPE_PIXEL_PATTERN_GBGBGB, + OPE_PIXEL_PATTERN_YCBYCR, + OPE_PIXEL_PATTERN_YCRYCB, + OPE_PIXEL_PATTERN_CBYCRY, + OPE_PIXEL_PATTERN_CRYCBY +}; + +enum ope_stripe_location { + OPE_STRIPE_LOCATION_FULL, + OPE_STRIPE_LOCATION_LEFT, + OPE_STRIPE_LOCATION_RIGHT, + OPE_STRIPE_LOCATION_MIDDLE +}; + +enum ope_unpacker_format { + OPE_UNPACKER_FMT_PLAIN_128, + OPE_UNPACKER_FMT_PLAIN_8, + OPE_UNPACKER_FMT_PLAIN_16_10BPP, + OPE_UNPACKER_FMT_PLAIN_16_12BPP, + OPE_UNPACKER_FMT_PLAIN_16_14BPP, + OPE_UNPACKER_FMT_PLAIN_32_20BPP, + OPE_UNPACKER_FMT_ARGB_16_10BPP, + OPE_UNPACKER_FMT_ARGB_16_12BPP, + OPE_UNPACKER_FMT_ARGB_16_14BPP, + OPE_UNPACKER_FMT_PLAIN_32, + OPE_UNPACKER_FMT_PLAIN_64, + OPE_UNPACKER_FMT_TP_10, + OPE_UNPACKER_FMT_MIPI_8, + OPE_UNPACKER_FMT_MIPI_10, + OPE_UNPACKER_FMT_MIPI_12, + OPE_UNPACKER_FMT_MIPI_14, + OPE_UNPACKER_FMT_PLAIN_16_16BPP, + OPE_UNPACKER_FMT_PLAIN_128_ODD_EVEN, + OPE_UNPACKER_FMT_PLAIN_8_ODD_EVEN +}; + +enum ope_packer_format { + OPE_PACKER_FMT_PLAIN_128, + OPE_PACKER_FMT_PLAIN_8, + OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, + OPE_PACKER_FMT_PLAIN_8_10BPP, + OPE_PACKER_FMT_PLAIN_8_10BPP_ODD_EVEN, + OPE_PACKER_FMT_PLAIN_16_10BPP, + OPE_PACKER_FMT_PLAIN_16_12BPP, + OPE_PACKER_FMT_PLAIN_16_14BPP, + OPE_PACKER_FMT_PLAIN_16_16BPP, + OPE_PACKER_FMT_PLAIN_32, + OPE_PACKER_FMT_PLAIN_64, + OPE_PACKER_FMT_TP_10, + OPE_PACKER_FMT_MIPI_10, + OPE_PACKER_FMT_MIPI_12 +}; + +struct ope_fmt { + u32 fourcc; + unsigned int types; + enum ope_pixel_pattern pattern; + enum ope_unpacker_format unpacker_format; + enum ope_packer_format packer_format; + unsigned int depth; + unsigned int align; /* pix alignment =3D 2^align */ +}; + +static const struct ope_fmt formats[] =3D { /* TODO: add multi-planes form= ats */ + /* Output - Bayer MIPI 10 */ + { V4L2_PIX_FMT_SBGGR10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_BGBGBG, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 }, + { V4L2_PIX_FMT_SGBRG10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GBGBGB, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 }, + { V4L2_PIX_FMT_SGRBG10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GRGRGR, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 }, + { V4L2_PIX_FMT_SRGGB10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 }, + /* Output - Bayer MIPI/Plain 8 */ + { V4L2_PIX_FMT_SRGGB8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 }, + { V4L2_PIX_FMT_SBGGR8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_BGBGBG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 }, + { V4L2_PIX_FMT_SGBRG8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GBGBGB, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 }, + { V4L2_PIX_FMT_SGRBG8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GRGRGR, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 }, + /* Capture - YUV 8-bit per component */ + { V4L2_PIX_FMT_NV24, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_YCBYCR, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 24, 0 }, + { V4L2_PIX_FMT_NV42, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_YCRYCB, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 24, 0 }, + { V4L2_PIX_FMT_NV16, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 16, 1 }, + { V4L2_PIX_FMT_NV61, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 16, 1 }, + { V4L2_PIX_FMT_NV12, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 12, 1 }, + { V4L2_PIX_FMT_NV21, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 12, 1 }, + /* Capture - Greyscale 8-bit */ + { V4L2_PIX_FMT_GREY, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 }, +}; + +#define OPE_NUM_FORMATS ARRAY_SIZE(formats) + +#define OPE_WB(n, d) (((n) << 10) / (d)) + +/* Per-queue, driver-specific private data */ +struct ope_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + const struct ope_fmt *fmt; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quant; + unsigned int sequence; +}; + +struct ope_dev { + struct device *dev; + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct media_device mdev; + struct v4l2_m2m_dev *m2m_dev; + + void __iomem *base; + void __iomem *base_rd; + void __iomem *base_wr; + void __iomem *base_pp; + + struct completion reset_complete; + + struct icc_path *icc_data; + struct icc_path *icc_config; + + struct mutex mutex; + struct list_head ctx_list; + void *context; +}; + +struct ope_dsc_config { + u32 input_width; + u32 input_height; + u32 output_width; + u32 output_height; + u32 phase_step_h; + u32 phase_step_v; +}; + +struct ope_stripe { + struct { + dma_addr_t addr; + u32 width; + u32 height; + u32 stride; + enum ope_stripe_location location; + enum ope_pixel_pattern pattern; + enum ope_unpacker_format format; + } src; + struct { + dma_addr_t addr; + u32 width; + u32 height; + u32 stride; + u32 x_init; + enum ope_packer_format format; + bool enabled; + } dst[OPE_WR_CLIENT_MAX]; + struct ope_dsc_config dsc[OPE_DS_MAX]; +}; + +struct ope_ctx { + struct v4l2_fh fh; + struct ope_dev *ope; + + /* Processing mode */ + int mode; + u8 alpha_component; + u8 rotation; + unsigned int framerate; + + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + + /* Source and destination queue data */ + struct ope_q_data q_data_src; + struct ope_q_data q_data_dst; + + u8 current_stripe; + struct ope_stripe stripe[OPE_MAX_STRIPE]; + + bool started; + + struct list_head list; +}; + +struct ope_freq_tbl { + unsigned int load; + unsigned long freq; +}; + +static inline char *print_fourcc(u32 fmt) +{ + static char code[5]; + + code[0] =3D (unsigned char)(fmt & 0xff); + code[1] =3D (unsigned char)((fmt >> 8) & 0xff); + code[2] =3D (unsigned char)((fmt >> 16) & 0xff); + code[3] =3D (unsigned char)((fmt >> 24) & 0xff); + code[4] =3D '\0'; + + return code; +} + +static inline enum ope_stripe_location ope_stripe_location(unsigned int in= dex, + unsigned int count) +{ + if (count =3D=3D 1) + return OPE_STRIPE_LOCATION_FULL; + if (index =3D=3D 0) + return OPE_STRIPE_LOCATION_LEFT; + if (index =3D=3D (count - 1)) + return OPE_STRIPE_LOCATION_RIGHT; + + return OPE_STRIPE_LOCATION_MIDDLE; +} + +static inline bool ope_stripe_is_last(struct ope_stripe *stripe) +{ + if (!stripe) + return false; + + if (stripe->src.location =3D=3D OPE_STRIPE_LOCATION_RIGHT || + stripe->src.location =3D=3D OPE_STRIPE_LOCATION_FULL) + return true; + + return false; +} + +static inline struct ope_stripe *ope_get_stripe(struct ope_ctx *ctx, unsig= ned int index) +{ + return &ctx->stripe[index]; +} + +static inline struct ope_stripe *ope_current_stripe(struct ope_ctx *ctx) +{ + if (!ctx) + return NULL; + + if (ctx->current_stripe >=3D OPE_MAX_STRIPE) + return NULL; + + return ope_get_stripe(ctx, ctx->current_stripe); +} + +static inline unsigned int ope_stripe_index(struct ope_ctx *ctx, struct op= e_stripe *stripe) +{ + return stripe - &ctx->stripe[0]; +} + +static inline struct ope_stripe *ope_prev_stripe(struct ope_ctx *ctx, stru= ct ope_stripe *stripe) +{ + unsigned int index =3D ope_stripe_index(ctx, stripe); + + return index ? ope_get_stripe(ctx, index - 1) : NULL; +} + +static inline struct ope_q_data *get_q_data(struct ope_ctx *ctx, enum v4l2= _buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->q_data_src; + else + return &ctx->q_data_dst; +} + +static inline unsigned long __q_data_pixclk(struct ope_q_data *q, unsigned= int fps) +{ + return (unsigned long)q->width * q->height * fps; +} + +static inline unsigned int __q_data_load_avg(struct ope_q_data *q, unsigne= d int fps) +{ + /* Data load in kBps, calculated from pixel clock and bits per pixel */ + return mult_frac(__q_data_pixclk(q, fps), q->fmt->depth, 1000) / 8; +} + +static inline unsigned int __q_data_load_peak(struct ope_q_data *q, unsign= ed int fps) +{ + return __q_data_load_avg(q, fps) * 2; +} + +static inline unsigned int __q_data_load_config(struct ope_q_data *q, unsi= gned int fps) +{ + unsigned int stripe_count =3D q->width / OPE_STRIPE_MAX_W + 1; + unsigned int stripe_load =3D 50 * 4 * fps; /* about 50 x 32-bit registers= to configure */ + + /* Return config load in kBps */ + return mult_frac(stripe_count, stripe_load, 1000); +} + +static inline struct ope_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct ope_ctx, fh); +} + +static inline u32 ope_read(struct ope_dev *ope, u32 reg) +{ + return readl(ope->base + reg); +} + +static inline void ope_write(struct ope_dev *ope, u32 reg, u32 value) +{ + writel(value, ope->base + reg); +} + +static inline u32 ope_read_wr(struct ope_dev *ope, u32 reg) +{ + return readl_relaxed(ope->base_wr + reg); +} + +static inline void ope_write_wr(struct ope_dev *ope, u32 reg, u32 value) +{ + writel_relaxed(value, ope->base_wr + reg); +} + +static inline u32 ope_read_rd(struct ope_dev *ope, u32 reg) +{ + return readl_relaxed(ope->base_rd + reg); +} + +static inline void ope_write_rd(struct ope_dev *ope, u32 reg, u32 value) +{ + writel_relaxed(value, ope->base_rd + reg); +} + +static inline u32 ope_read_pp(struct ope_dev *ope, u32 reg) +{ + return readl_relaxed(ope->base_pp + reg); +} + +static inline void ope_write_pp(struct ope_dev *ope, u32 reg, u32 value) +{ + writel_relaxed(value, ope->base_pp + reg); +} + +static inline void ope_start(struct ope_dev *ope) +{ + wmb(); /* Ensure the next write occurs only after all prior normal memory= accesses */ + ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_CMD, OPE_BUS_RD_INPUT_IF_CMD_GO_CMD= ); +} + +static bool ope_pix_fmt_is_yuv(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV24: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV42: + case V4L2_PIX_FMT_GREY: + return true; + default: + return false; + } +} + +static const struct ope_fmt *find_format(unsigned int pixelformat) +{ + const struct ope_fmt *fmt; + unsigned int i; + + for (i =3D 0; i < OPE_NUM_FORMATS; i++) { + fmt =3D &formats[i]; + if (fmt->fourcc =3D=3D pixelformat) + break; + } + + if (i =3D=3D OPE_NUM_FORMATS) + return NULL; + + return &formats[i]; +} + +static inline void ope_dbg_print_stripe(struct ope_ctx *ctx, struct ope_st= ripe *stripe) +{ + struct ope_dev *ope =3D ctx->ope; + int i; + + dev_dbg(ope->dev, "S%u/FE0: addr=3D%pad;W=3D%ub;H=3D%u;stride=3D%u;loc=3D= %u;pattern=3D%u;fmt=3D%u\n", + ope_stripe_index(ctx, stripe), &stripe->src.addr, stripe->src.width, + stripe->src.height, stripe->src.stride, stripe->src.location, stripe->sr= c.pattern, + stripe->src.format); + + for (i =3D 0; i < OPE_DS_MAX; i++) { + struct ope_dsc_config *c =3D &stripe->dsc[i]; + + dev_dbg(ope->dev, "S%u/DSC%d: %ux%u =3D> %ux%u\n", + ope_stripe_index(ctx, stripe), i, c->input_width, c->input_height, + c->output_width, c->output_height); + } + + for (i =3D 0; i < OPE_WR_CLIENT_MAX; i++) { + if (!stripe->dst[i].enabled) + continue; + + dev_dbg(ope->dev, + "S%u/WE%d: addr=3D%pad;X=3D%u;W=3D%upx;H=3D%u;stride=3D%u;fmt=3D%u\n", + ope_stripe_index(ctx, stripe), i, &stripe->dst[i].addr, + stripe->dst[i].x_init, stripe->dst[i].width, stripe->dst[i].height, + stripe->dst[i].stride, stripe->dst[i].format); + } +} + +static void ope_gen_stripe_argb_dst(struct ope_ctx *ctx, struct ope_stripe= *stripe, dma_addr_t dst) +{ + unsigned int index =3D ope_stripe_index(ctx, stripe); + unsigned int img_width =3D ctx->q_data_dst.width; + unsigned int width, height; + dma_addr_t addr; + + /* This is GBRA64 format (le16)G (le16)B (le16)R (le16)A */ + + stripe->dst[OPE_WR_CLIENT_ARGB].enabled =3D true; + + width =3D stripe->src.width; + height =3D stripe->src.height; + + if (!index) { + addr =3D dst; + } else { + struct ope_stripe *prev =3D ope_get_stripe(ctx, index - 1); + + addr =3D prev->dst[OPE_WR_CLIENT_ARGB].addr + prev->dst[OPE_WR_CLIENT_AR= GB].width * 8; + } + + stripe->dst[OPE_WR_CLIENT_ARGB].addr =3D addr; + stripe->dst[OPE_WR_CLIENT_ARGB].x_init =3D 0; + stripe->dst[OPE_WR_CLIENT_ARGB].width =3D width; + stripe->dst[OPE_WR_CLIENT_ARGB].height =3D height; + stripe->dst[OPE_WR_CLIENT_ARGB].stride =3D img_width * 8; + stripe->dst[OPE_WR_CLIENT_ARGB].format =3D OPE_PACKER_FMT_PLAIN_64; +} + +static void ope_gen_stripe_yuv_dst(struct ope_ctx *ctx, struct ope_stripe = *stripe, dma_addr_t dst) +{ + struct ope_stripe *prev =3D ope_prev_stripe(ctx, stripe); + unsigned int img_width =3D ctx->q_data_dst.width; + unsigned int img_height =3D ctx->q_data_dst.height; + unsigned int width, height; + u32 x_init =3D 0; + + stripe->dst[OPE_WR_CLIENT_DISP_Y].enabled =3D true; + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled =3D true; + + /* Y */ + width =3D stripe->dsc[OPE_DS_Y_DISP].output_width; + height =3D stripe->dsc[OPE_DS_Y_DISP].output_height; + + if (prev) + x_init =3D prev->dst[OPE_WR_CLIENT_DISP_Y].x_init + + prev->dst[OPE_WR_CLIENT_DISP_Y].width; + + stripe->dst[OPE_WR_CLIENT_DISP_Y].addr =3D dst; + stripe->dst[OPE_WR_CLIENT_DISP_Y].x_init =3D x_init; + stripe->dst[OPE_WR_CLIENT_DISP_Y].width =3D width; + stripe->dst[OPE_WR_CLIENT_DISP_Y].height =3D height; + stripe->dst[OPE_WR_CLIENT_DISP_Y].stride =3D img_width; + stripe->dst[OPE_WR_CLIENT_DISP_Y].format =3D OPE_PACKER_FMT_PLAIN_8; + + /* UV */ + width =3D stripe->dsc[OPE_DS_C_DISP].output_width; + height =3D stripe->dsc[OPE_DS_C_DISP].output_height; + + if (prev) + x_init =3D prev->dst[OPE_WR_CLIENT_DISP_C].x_init + + prev->dst[OPE_WR_CLIENT_DISP_C].width; + + stripe->dst[OPE_WR_CLIENT_DISP_C].addr =3D dst + img_width * img_height; + stripe->dst[OPE_WR_CLIENT_DISP_C].x_init =3D x_init; + stripe->dst[OPE_WR_CLIENT_DISP_C].format =3D ctx->q_data_dst.fmt->packer_= format; + stripe->dst[OPE_WR_CLIENT_DISP_C].width =3D width * 2; + stripe->dst[OPE_WR_CLIENT_DISP_C].height =3D height; + + switch (ctx->q_data_dst.fmt->fourcc) { + case V4L2_PIX_FMT_NV42: + case V4L2_PIX_FMT_NV24: /* YUV 4:4:4 */ + stripe->dst[OPE_WR_CLIENT_DISP_C].stride =3D img_width * 2; + break; + case V4L2_PIX_FMT_GREY: /* No UV */ + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled =3D false; + break; + default: + stripe->dst[OPE_WR_CLIENT_DISP_C].stride =3D img_width; + } +} + +static void ope_gen_stripe_dsc(struct ope_ctx *ctx, struct ope_stripe *str= ipe, + unsigned int h_scale, unsigned int v_scale) +{ + struct ope_dsc_config *dsc_c, *dsc_y; + + dsc_c =3D &stripe->dsc[OPE_DS_C_DISP]; + dsc_y =3D &stripe->dsc[OPE_DS_Y_DISP]; + + dsc_c->phase_step_h =3D dsc_y->phase_step_h =3D h_scale; + dsc_c->phase_step_v =3D dsc_y->phase_step_v =3D v_scale; + + dsc_c->input_width =3D stripe->dsc[OPE_DS_C_PRE].output_width; + dsc_c->input_height =3D stripe->dsc[OPE_DS_C_PRE].output_height; + + dsc_y->input_width =3D stripe->src.width; + dsc_y->input_height =3D stripe->src.height; + + dsc_c->output_width =3D DS_OUTPUT_PIX(dsc_c->input_width, 0, h_scale); + dsc_c->output_height =3D DS_OUTPUT_PIX(dsc_c->input_height, 0, v_scale); + + dsc_y->output_width =3D DS_OUTPUT_PIX(dsc_y->input_width, 0, h_scale); + dsc_y->output_height =3D DS_OUTPUT_PIX(dsc_y->input_height, 0, v_scale); + + /* Adjust initial phase ? */ +} + +static void ope_gen_stripe_chroma_dsc(struct ope_ctx *ctx, struct ope_stri= pe *stripe) +{ + struct ope_dsc_config *dsc; + + dsc =3D &stripe->dsc[OPE_DS_C_PRE]; + + dsc->input_width =3D stripe->src.width; + dsc->input_height =3D stripe->src.height; + + switch (ctx->q_data_dst.fmt->fourcc) { + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV16: + dsc->output_width =3D dsc->input_width / 2; + dsc->output_height =3D dsc->input_height; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + dsc->output_width =3D dsc->input_width / 2; + dsc->output_height =3D dsc->input_height / 2; + break; + default: + dsc->output_width =3D dsc->input_width; + dsc->output_height =3D dsc->input_height; + } + + dsc->phase_step_h =3D DS_Q21(dsc->input_width, dsc->output_width); + dsc->phase_step_v =3D DS_Q21(dsc->input_height, dsc->output_height); +} + +static void ope_gen_stripes(struct ope_ctx *ctx, dma_addr_t src, dma_addr_= t dst) +{ + const struct ope_fmt *src_fmt =3D ctx->q_data_src.fmt; + const struct ope_fmt *dst_fmt =3D ctx->q_data_dst.fmt; + unsigned int num_stripes, width, i; + unsigned int h_scale, v_scale; + + width =3D ctx->q_data_src.width; + num_stripes =3D DIV_ROUND_UP(ctx->q_data_src.width, OPE_STRIPE_MAX_W); + h_scale =3D DS_Q21(ctx->q_data_src.width, ctx->q_data_dst.width); + v_scale =3D DS_Q21(ctx->q_data_src.height, ctx->q_data_dst.height); + + for (i =3D 0; i < num_stripes; i++) { + struct ope_stripe *stripe =3D &ctx->stripe[i]; + + /* Clear config */ + memset(stripe, 0, sizeof(*stripe)); + + /* Fetch Engine */ + stripe->src.addr =3D src; + stripe->src.width =3D width; + stripe->src.height =3D ctx->q_data_src.height; + stripe->src.stride =3D ctx->q_data_src.bytesperline; + stripe->src.location =3D ope_stripe_location(i, num_stripes); + stripe->src.pattern =3D src_fmt->pattern; + stripe->src.format =3D src_fmt->unpacker_format; + + /* Ensure the last stripe will be large enough */ + if (width > OPE_STRIPE_MAX_W && width < (OPE_STRIPE_MAX_W + OPE_STRIPE_M= IN_W)) + stripe->src.width -=3D OPE_STRIPE_MIN_W * 2; + + v4l_bound_align_image(&stripe->src.width, src_fmt->align, + OPE_STRIPE_MAX_W, src_fmt->align, + &stripe->src.height, OPE_STRIPE_MIN_H, OPE_STRIPE_MAX_H, + OPE_ALIGN_H, 0); + + width -=3D stripe->src.width; + src +=3D stripe->src.width * src_fmt->depth / 8; + + if (ope_pix_fmt_is_yuv(dst_fmt->fourcc)) { + /* YUV Chroma downscaling */ + ope_gen_stripe_chroma_dsc(ctx, stripe); + + /* YUV downscaling */ + ope_gen_stripe_dsc(ctx, stripe, h_scale, v_scale); + + /* Write Engines */ + ope_gen_stripe_yuv_dst(ctx, stripe, dst); + } else { + ope_gen_stripe_argb_dst(ctx, stripe, dst); + } + + /* Source width is in byte unit, not pixel */ + stripe->src.width =3D stripe->src.width * src_fmt->depth / 8; + + ope_dbg_print_stripe(ctx, stripe); + } +} + +static void ope_prog_rgb2yuv(struct ope_dev *ope) +{ + /* Default RGB to YUV - no special effect - CF BT.601 */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0, 0x04d) | /* R to = Y 0.299 12sQ8 */ + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1, 0x096)); /* G to = Y 0.587 12sQ8 */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2, 0x01d)); /* B to = Y 0.114 12sQ8 */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K, 0)); /* Y offset 0= 9sQ0 */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP, 0x0e6) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM, 0x0e6)); /* 0.88= 6 12sQ8 (Cb) */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP, 0xfb3) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM, 0xfb3)); /* -0.3= 38 12sQ8 (Cb) */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP, 0xb3) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM, 0xb3)); /* 0.701= 12sQ8 (Cr) */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP, 0xfe3) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM, 0xfe3)); /* -0.1= 14 12sQ8 (Cr) */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR, 128)); /* KCR = 128 11s */ + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB, 128)); /* KCB = 128 11s */ + + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG, + OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN); +} + +static void ope_prog_bayer2rgb(struct ope_dev *ope) +{ + /* Fixed Settings */ + ope_write_pp(ope, 0x860, 0x4001); + ope_write_pp(ope, 0x868, 128); + ope_write_pp(ope, 0x86c, 128 << 20); + ope_write_pp(ope, 0x870, 102); +} + +static void ope_prog_wb(struct ope_dev *ope) +{ + /* Default white balance config */ + u32 g_gain =3D OPE_WB(1, 1); + u32 b_gain =3D OPE_WB(3, 2); + u32 r_gain =3D OPE_WB(3, 2); + + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(0), g_gain); + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(1), b_gain); + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(2), r_gain); + + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_MODULE_CFG, OPE_PP_CLC_WB_GAIN_MODUL= E_CFG_EN); +} + +static void ope_prog_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe) +{ + struct ope_dev *ope =3D ctx->ope; + int i; + + dev_dbg(ope->dev, "Context %p - Programming S%u\n", ctx, ope_stripe_index= (ctx, stripe)); + + /* Fetch Engine */ + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0, stripe->src.format); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE, + (stripe->src.width << 16) + stripe->src.height); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_ADDR_IMAGE, stripe->src.addr); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_STRIDE, stripe->src.stride); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CCIF_META_DATA, + FIELD_PREP(OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN, stripe->src.pat= tern)); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CORE_CFG, OPE_BUS_RD_CLIENT_0_CORE_= CFG_EN); + + /* Write Engines */ + for (i =3D 0; i < OPE_WR_CLIENT_MAX; i++) { + if (!stripe->dst[i].enabled) { + ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i), 0); + continue; + } + + ope_write_wr(ope, OPE_BUS_WR_CLIENT_ADDR_IMAGE(i), stripe->dst[i].addr); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_0(i), + (stripe->dst[i].height << 16) + stripe->dst[i].width); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_1(i), stripe->dst[i].x_ini= t); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_2(i), stripe->dst[i].strid= e); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_PACKER_CFG(i), stripe->dst[i].format= ); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i), + OPE_BUS_WR_CLIENT_CFG_EN + OPE_BUS_WR_CLIENT_CFG_AUTORECOVER); + } + + /* Downscalers */ + for (i =3D 0; i < OPE_DS_MAX; i++) { + struct ope_dsc_config *dsc =3D &stripe->dsc[i]; + u32 base =3D ope_ds_base[i]; + u32 cfg =3D 0; + + if (dsc->input_width !=3D dsc->output_width) { + dsc->phase_step_h |=3D DS_RESOLUTION(dsc->input_width, + dsc->output_width) << 30; + cfg |=3D OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN; + } + + if (dsc->input_height !=3D dsc->output_height) { + dsc->phase_step_v |=3D DS_RESOLUTION(dsc->input_height, + dsc->output_height) << 30; + cfg |=3D OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN; + } + + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(base), cfg); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(base), + ((dsc->input_width - 1) << 16) + dsc->input_height - 1); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(base), dsc->phase_= step_h); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(base), dsc->phase_= step_v); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_CFG(base), + cfg ? OPE_PP_CLC_DOWNSCALE_MN_CFG_EN : 0); + } +} + +/* + * mem2mem callbacks + */ +static void ope_device_run(void *priv) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct ope_ctx *ctx =3D priv; + struct ope_dev *ope =3D ctx->ope; + dma_addr_t src, dst; + + dev_dbg(ope->dev, "Start context %p", ctx); + + src_buf =3D v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + return; + + dst_buf =3D v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst_buf) + return; + + src =3D vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + dst =3D vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + + /* Generate stripes from full frame */ + ope_gen_stripes(ctx, src, dst); + + if (priv !=3D ope->context) { + /* If context changed, reprogram the submodules */ + ope_prog_wb(ope); + ope_prog_bayer2rgb(ope); + ope_prog_rgb2yuv(ope); + ope->context =3D priv; + } + + /* Program the first stripe */ + ope_prog_stripe(ctx, &ctx->stripe[0]); + + /* Go! */ + ope_start(ope); +} + +static void ope_job_done(struct ope_ctx *ctx, enum vb2_buffer_state vbstat= e) +{ + struct vb2_v4l2_buffer *src, *dst; + + if (!ctx) + return; + + src =3D v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst =3D v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (dst && src) + dst->vb2_buf.timestamp =3D src->vb2_buf.timestamp; + + if (src) + v4l2_m2m_buf_done(src, vbstate); + if (dst) + v4l2_m2m_buf_done(dst, vbstate); + + v4l2_m2m_job_finish(ctx->ope->m2m_dev, ctx->fh.m2m_ctx); +} + +static void ope_buf_done(struct ope_ctx *ctx) +{ + struct ope_stripe *stripe =3D ope_current_stripe(ctx); + + if (!ctx) + return; + + dev_dbg(ctx->ope->dev, "Context %p Stripe %u done\n", + ctx, ope_stripe_index(ctx, stripe)); + + if (ope_stripe_is_last(stripe)) { + ctx->current_stripe =3D 0; + ope_job_done(ctx, VB2_BUF_STATE_DONE); + } else { + ctx->current_stripe++; + ope_start(ctx->ope); + } +} + +static void ope_job_abort(void *priv) +{ + struct ope_ctx *ctx =3D priv; + + /* reset to abort */ + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); +} + +static void ope_rup_done(struct ope_ctx *ctx) +{ + struct ope_stripe *stripe =3D ope_current_stripe(ctx); + + /* We can program next stripe (double buffered registers) */ + if (!ope_stripe_is_last(stripe)) + ope_prog_stripe(ctx, ++stripe); +} + +/* + * interrupt handler + */ +static void ope_fe_irq(struct ope_dev *ope) +{ + u32 status =3D ope_read_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_STATUS); + + ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_CLEAR, status); + ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_CMD, OPE_BUS_RD_INPUT_IF_IRQ_CM= D_CLEAR); + + /* Nothing to do */ +} + +static void ope_we_irq(struct ope_ctx *ctx) +{ + struct ope_dev *ope; + u32 status0; + + if (!ctx) { + pr_err("Instance released before the end of transaction\n"); + return; + } + + ope =3D ctx->ope; + + status0 =3D ope_read_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0); + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0, status0); + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CMD, OPE_BUS_WR_INPUT_IF_IRQ_CM= D_CLEAR); + + if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL) { + dev_err_ratelimited(ope->dev, "Write Engine configuration violates const= rains\n"); + ope_job_abort(ctx); + } + + if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL) { + u32 status =3D ope_read_wr(ope, OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS); + int i; + + for (i =3D 0; i < OPE_WR_CLIENT_MAX; i++) { + if (BIT(i) & status) + dev_err_ratelimited(ope->dev, + "Write Engine (WE%d) image size violation\n", i); + } + + ope_job_abort(ctx); + } + + if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL) { + dev_err_ratelimited(ope->dev, "Write Engine fatal violation\n"); + ope_job_abort(ctx); + } + + if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE) + ope_rup_done(ctx); +} + +static irqreturn_t ope_irq(int irq, void *dev_id) +{ + struct ope_dev *ope =3D dev_id; + struct ope_ctx *ctx =3D ope->m2m_dev ? v4l2_m2m_get_curr_priv(ope->m2m_de= v) : NULL; + u32 status; + + status =3D ope_read(ope, OPE_TOP_IRQ_STATUS); + ope_write(ope, OPE_TOP_IRQ_CLEAR, status); + ope_write(ope, OPE_TOP_IRQ_CMD, OPE_TOP_IRQ_CMD_CLEAR); + + if (status & OPE_TOP_IRQ_STATUS_RST_DONE) { + ope_job_done(ctx, VB2_BUF_STATE_ERROR); + complete(&ope->reset_complete); + } + + if (status & OPE_TOP_IRQ_STATUS_VIOL) { + u32 violation =3D ope_read(ope, OPE_TOP_VIOLATION_STATUS); + + dev_warn(ope->dev, "OPE Violation: %u", violation); + } + + if (status & OPE_TOP_IRQ_STATUS_FE) + ope_fe_irq(ope); + + if (status & OPE_TOP_IRQ_STATUS_WE) + ope_we_irq(ctx); + + if (status & OPE_TOP_IRQ_STATUS_IDLE) + ope_buf_done(ctx); + + return IRQ_HANDLED; +} + +static void ope_irq_init(struct ope_dev *ope) +{ + ope_write(ope, OPE_TOP_IRQ_MASK, + OPE_TOP_IRQ_STATUS_RST_DONE | OPE_TOP_IRQ_STATUS_WE | + OPE_TOP_IRQ_STATUS_VIOL | OPE_TOP_IRQ_STATUS_IDLE); + + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_MASK_0, + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL); +} + +/* + * video ioctls + */ +static int ope_querycap(struct file *file, void *priv, struct v4l2_capabil= ity *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Qualcomm Offline Processing Engine", sizeof(cap->card= )); + return 0; +} + +static int ope_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + const struct ope_fmt *fmt; + int i, num =3D 0; + + for (i =3D 0; i < OPE_NUM_FORMATS; ++i) { + if (formats[i].types & type) { + if (num =3D=3D f->index) + break; + ++num; + } + } + + if (i < OPE_NUM_FORMATS) { + fmt =3D &formats[i]; + f->pixelformat =3D fmt->fourcc; + return 0; + } + + return -EINVAL; +} + +static int ope_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + f->type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + return ope_enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int ope_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + f->type =3D V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + + return ope_enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int ope_g_fmt(struct ope_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp =3D &f->fmt.pix_mp; + struct ope_q_data *q_data; + struct vb2_queue *vq; + + vq =3D v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data =3D get_q_data(ctx, f->type); + + pix_mp->width =3D q_data->width; + pix_mp->height =3D q_data->height; + pix_mp->pixelformat =3D q_data->fmt->fourcc; + pix_mp->num_planes =3D 1; + pix_mp->field =3D V4L2_FIELD_NONE; + pix_mp->colorspace =3D ctx->colorspace; + pix_mp->xfer_func =3D ctx->xfer_func; + pix_mp->ycbcr_enc =3D q_data->ycbcr_enc; + pix_mp->quantization =3D q_data->quant; + pix_mp->plane_fmt[0].bytesperline =3D q_data->bytesperline; + pix_mp->plane_fmt[0].sizeimage =3D q_data->sizeimage; + + return 0; +} + +static int ope_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return ope_g_fmt(file2ctx(file), f); +} + +static int ope_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return ope_g_fmt(file2ctx(file), f); +} + +static int ope_try_fmt(struct v4l2_format *f, const struct ope_fmt *fmt) +{ + struct v4l2_pix_format_mplane *pix_mp =3D &f->fmt.pix_mp; + unsigned int stride =3D pix_mp->plane_fmt[0].bytesperline; + unsigned int size; + + pix_mp->num_planes =3D 1; + pix_mp->field =3D V4L2_FIELD_NONE; + + v4l_bound_align_image(&pix_mp->width, OPE_MIN_W, OPE_MAX_W, fmt->align, + &pix_mp->height, OPE_MIN_H, OPE_MAX_H, OPE_ALIGN_H, 0); + + if (ope_pix_fmt_is_yuv(pix_mp->pixelformat)) { + stride =3D MAX(pix_mp->width, stride); + size =3D fmt->depth * pix_mp->width / 8 * pix_mp->height; + } else { + stride =3D MAX(pix_mp->width * fmt->depth / 8, stride); + size =3D stride * pix_mp->height; + } + + pix_mp->plane_fmt[0].bytesperline =3D stride; + pix_mp->plane_fmt[0].sizeimage =3D size; + + return 0; +} + +static int ope_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp =3D &f->fmt.pix_mp; + struct ope_ctx *ctx =3D file2ctx(file); + const struct ope_fmt *fmt; + int ret; + + dev_dbg(ctx->ope->dev, "Try capture format: %ux%u-%s (planes:%u bpl:%u si= ze:%u)\n", + pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat), + pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline, + pix_mp->plane_fmt[0].sizeimage); + + fmt =3D find_format(pix_mp->pixelformat); + if (!fmt) { + pix_mp->pixelformat =3D ctx->q_data_dst.fmt->fourcc; + fmt =3D ctx->q_data_dst.fmt; + } + + if (!(fmt->types & MEM2MEM_CAPTURE) && (fmt !=3D ctx->q_data_src.fmt)) + return -EINVAL; + + if (pix_mp->width > ctx->q_data_src.width || + pix_mp->height > ctx->q_data_src.height) { + pix_mp->width =3D ctx->q_data_src.width; + pix_mp->height =3D ctx->q_data_src.height; + } + + pix_mp->colorspace =3D ope_pix_fmt_is_yuv(pix_mp->pixelformat) ? + ctx->colorspace : V4L2_COLORSPACE_RAW; + pix_mp->xfer_func =3D ctx->xfer_func; + pix_mp->ycbcr_enc =3D V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); + pix_mp->quantization =3D V4L2_MAP_QUANTIZATION_DEFAULT(true, + pix_mp->colorspace, pix_mp->ycbcr_enc); + + ret =3D ope_try_fmt(f, fmt); + + dev_dbg(ctx->ope->dev, "Fixed capture format: %ux%u-%s (planes:%u bpl:%u = size:%u)\n", + pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat), + pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline, + pix_mp->plane_fmt[0].sizeimage); + + return ret; +} + +static int ope_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp =3D &f->fmt.pix_mp; + const struct ope_fmt *fmt; + struct ope_ctx *ctx =3D file2ctx(file); + int ret; + + dev_dbg(ctx->ope->dev, "Try output format: %ux%u-%s (planes:%u bpl:%u siz= e:%u)\n", + pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat), + pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline, + pix_mp->plane_fmt[0].sizeimage); + + fmt =3D find_format(pix_mp->pixelformat); + if (!fmt) { + pix_mp->pixelformat =3D ctx->q_data_src.fmt->fourcc; + fmt =3D ctx->q_data_src.fmt; + } + if (!(fmt->types & MEM2MEM_OUTPUT)) + return -EINVAL; + + if (!pix_mp->colorspace) + pix_mp->colorspace =3D V4L2_COLORSPACE_SRGB; + + pix_mp->ycbcr_enc =3D V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); + pix_mp->quantization =3D V4L2_MAP_QUANTIZATION_DEFAULT(true, + pix_mp->colorspace, pix_mp->ycbcr_enc); + + ret =3D ope_try_fmt(f, fmt); + + dev_dbg(ctx->ope->dev, "Fixed output format: %ux%u-%s (planes:%u bpl:%u s= ize:%u)\n", + pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat), + pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline, + pix_mp->plane_fmt[0].sizeimage); + + return ret; +} + +static int ope_s_fmt(struct ope_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp =3D &f->fmt.pix_mp; + struct ope_q_data *q_data; + struct vb2_queue *vq; + + vq =3D v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data =3D get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->ope->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt =3D find_format(pix_mp->pixelformat); + if (!q_data->fmt) + return -EINVAL; + q_data->width =3D pix_mp->width; + q_data->height =3D pix_mp->height; + q_data->bytesperline =3D pix_mp->plane_fmt[0].bytesperline; + q_data->sizeimage =3D pix_mp->plane_fmt[0].sizeimage; + + dev_dbg(ctx->ope->dev, "Set %s format: %ux%u %s (%u bytes)\n", + V4L2_TYPE_IS_OUTPUT(f->type) ? "output" : "capture", + q_data->width, q_data->height, print_fourcc(q_data->fmt->fourcc), + q_data->sizeimage); + + return 0; +} + +static int ope_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ope_ctx *ctx =3D file2ctx(file); + int ret; + + ret =3D ope_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret =3D ope_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->q_data_dst.ycbcr_enc =3D f->fmt.pix_mp.ycbcr_enc; + ctx->q_data_dst.quant =3D f->fmt.pix_mp.quantization; + + return 0; +} + +static int ope_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ope_ctx *ctx =3D file2ctx(file); + int ret; + + ret =3D ope_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret =3D ope_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->colorspace =3D f->fmt.pix_mp.colorspace; + ctx->xfer_func =3D f->fmt.pix_mp.xfer_func; + ctx->q_data_src.ycbcr_enc =3D f->fmt.pix_mp.ycbcr_enc; + ctx->q_data_src.quant =3D f->fmt.pix_mp.quantization; + + return 0; +} + +static int ope_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + if (!find_format(fsize->pixel_format)) + return -EINVAL; + + fsize->type =3D V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width =3D OPE_MIN_W; + fsize->stepwise.max_width =3D OPE_MAX_W; + fsize->stepwise.step_width =3D 1 << OPE_ALIGN_W; + fsize->stepwise.min_height =3D OPE_MIN_H; + fsize->stepwise.max_height =3D OPE_MAX_H; + fsize->stepwise.step_height =3D 1 << OPE_ALIGN_H; + + return 0; +} + +static int ope_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + fival->type =3D V4L2_FRMIVAL_TYPE_STEPWISE; + fival->stepwise.min.numerator =3D 1; + fival->stepwise.min.denominator =3D 120; + fival->stepwise.max.numerator =3D 1; + fival->stepwise.max.denominator =3D 1; + fival->stepwise.step.numerator =3D 1; + fival->stepwise.step.denominator =3D 1; + + return 0; +} + +static int ope_s_ctrl(struct v4l2_ctrl *ctrl) +{ + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ope_ctrl_ops =3D { + .s_ctrl =3D ope_s_ctrl, +}; + +static const struct v4l2_ioctl_ops ope_ioctl_ops =3D { + .vidioc_querycap =3D ope_querycap, + + .vidioc_enum_fmt_vid_cap =3D ope_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane =3D ope_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane =3D ope_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane =3D ope_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out =3D ope_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane =3D ope_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane =3D ope_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane =3D ope_s_fmt_vid_out, + + .vidioc_enum_framesizes =3D ope_enum_framesizes, + .vidioc_enum_frameintervals =3D ope_enum_frameintervals, + + .vidioc_reqbufs =3D v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf =3D v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf =3D v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf =3D v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf =3D v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs =3D v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf =3D v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon =3D v4l2_m2m_ioctl_streamon, + .vidioc_streamoff =3D v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event =3D v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event =3D v4l2_event_unsubscribe, +}; + +/* + * Queue operations + */ +static int ope_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct ope_ctx *ctx =3D vb2_get_drv_priv(vq); + struct ope_q_data *q_data =3D get_q_data(ctx, vq->type); + unsigned int size =3D q_data->sizeimage; + + if (*nplanes) { + if (*nplanes !=3D 1) + return -EINVAL; + } else { + *nplanes =3D 1; + } + + if (sizes[0]) { + if (sizes[0] < size) + return -EINVAL; + } else { + sizes[0] =3D size; + } + + dev_dbg(ctx->ope->dev, "get %d buffer(s) of size %d each.\n", *nbuffers, = size); + + return 0; +} + +static int ope_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct ope_ctx *ctx =3D vb2_get_drv_priv(vb->vb2_queue); + struct ope_dev *ope =3D ctx->ope; + struct ope_q_data *q_data; + + q_data =3D get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field =3D=3D V4L2_FIELD_ANY) + vbuf->field =3D V4L2_FIELD_NONE; + if (vbuf->field !=3D V4L2_FIELD_NONE) { + v4l2_err(&ope->v4l2_dev, "Field isn't supported\n"); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + v4l2_err(&ope->v4l2_dev, "Data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), (long)q_data->sizeimage); + return -EINVAL; + } + + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + vbuf->sequence =3D q_data->sequence++; + + return 0; +} + +static void ope_adjust_power(struct ope_dev *ope) +{ + int ret; + unsigned long pixclk =3D 0; + unsigned int loadavg =3D 0; + unsigned int loadpeak =3D 0; + unsigned int loadconfig =3D 0; + struct ope_ctx *ctx; + + lockdep_assert_held(&ope->mutex); + + list_for_each_entry(ctx, &ope->ctx_list, list) { + if (!ctx->started) + continue; + + if (!ctx->framerate) + ctx->framerate =3D DEFAULT_FRAMERATE; + + pixclk +=3D __q_data_pixclk(&ctx->q_data_src, ctx->framerate); + loadavg +=3D __q_data_load_avg(&ctx->q_data_src, ctx->framerate); + loadavg +=3D __q_data_load_avg(&ctx->q_data_dst, ctx->framerate); + loadpeak +=3D __q_data_load_peak(&ctx->q_data_src, ctx->framerate); + loadpeak +=3D __q_data_load_peak(&ctx->q_data_dst, ctx->framerate); + loadconfig +=3D __q_data_load_config(&ctx->q_data_src, ctx->framerate); + } + + /* 30% margin for overhead */ + pixclk =3D mult_frac(pixclk, 13, 10); + + dev_dbg(ope->dev, "Adjusting clock:%luHz avg:%uKBps peak:%uKBps config:%u= KBps\n", + pixclk, loadavg, loadpeak, loadconfig); + + ret =3D dev_pm_opp_set_rate(ope->dev, pixclk); + if (ret) + dev_warn(ope->dev, "Failed to adjust OPP rate: %d\n", ret); + + ret =3D icc_set_bw(ope->icc_data, loadavg, loadpeak); + if (ret) + dev_warn(ope->dev, "Failed to set data path bandwidth: %d\n", ret); + + ret =3D icc_set_bw(ope->icc_config, loadconfig, loadconfig * 5); + if (ret) + dev_warn(ope->dev, "Failed to set config path bandwidth: %d\n", ret); +} + +static void ope_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct ope_ctx *ctx =3D vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int ope_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ope_ctx *ctx =3D vb2_get_drv_priv(q); + struct ope_dev *ope =3D ctx->ope; + struct ope_q_data *q_data; + int ret; + + dev_dbg(ope->dev, "Start streaming (ctx %p/%u)\n", ctx, q->type); + + lockdep_assert_held(&ope->mutex); + + q_data =3D get_q_data(ctx, q->type); + q_data->sequence =3D 0; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->started =3D true; + ope_adjust_power(ctx->ope); + } + + ret =3D pm_runtime_resume_and_get(ctx->ope->dev); + if (ret) { + dev_err(ope->dev, "Could not resume\n"); + return ret; + } + + ope_irq_init(ope); + + return 0; +} + +static void ope_stop_streaming(struct vb2_queue *q) +{ + struct ope_ctx *ctx =3D vb2_get_drv_priv(q); + struct ope_dev *ope =3D ctx->ope; + struct vb2_v4l2_buffer *vbuf; + + dev_dbg(ctx->ope->dev, "Stop streaming (ctx %p/%u)\n", ctx, q->type); + + lockdep_assert_held(&ope->mutex); + + if (ope->context =3D=3D ctx) + ope->context =3D NULL; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->started =3D false; + ope_adjust_power(ctx->ope); + } + + pm_runtime_put(ctx->ope->dev); + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf =3D v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf =3D v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vbuf =3D=3D NULL) + return; + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops ope_qops =3D { + .queue_setup =3D ope_queue_setup, + .buf_prepare =3D ope_buf_prepare, + .buf_queue =3D ope_buf_queue, + .start_streaming =3D ope_start_streaming, + .stop_streaming =3D ope_stop_streaming, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct ope_ctx *ctx =3D priv; + int ret; + + src_vq->type =3D V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes =3D VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv =3D ctx; + src_vq->buf_struct_size =3D sizeof(struct v4l2_m2m_buffer); + src_vq->ops =3D &ope_qops; + src_vq->mem_ops =3D &vb2_dma_contig_memops; + src_vq->timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock =3D &ctx->ope->mutex; + src_vq->dev =3D ctx->ope->v4l2_dev.dev; + + ret =3D vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes =3D VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv =3D ctx; + dst_vq->buf_struct_size =3D sizeof(struct v4l2_m2m_buffer); + dst_vq->ops =3D &ope_qops; + dst_vq->mem_ops =3D &vb2_dma_contig_memops; + dst_vq->timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock =3D &ctx->ope->mutex; + dst_vq->dev =3D ctx->ope->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int ope_open(struct file *file) +{ + struct ope_dev *ope =3D video_drvdata(file); + struct ope_ctx *ctx =3D NULL; + int rc =3D 0; + + if (mutex_lock_interruptible(&ope->mutex)) + return -ERESTARTSYS; + + ctx =3D kvzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc =3D -ENOMEM; + goto open_unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data =3D &ctx->fh; + ctx->ope =3D ope; + ctx->colorspace =3D V4L2_COLORSPACE_RAW; + + ctx->q_data_src.fmt =3D find_format(V4L2_PIX_FMT_SRGGB8); + ctx->q_data_src.width =3D 640; + ctx->q_data_src.height =3D 480; + ctx->q_data_src.bytesperline =3D 640; + ctx->q_data_src.sizeimage =3D 640 * 480; + + ctx->q_data_dst.fmt =3D find_format(V4L2_PIX_FMT_NV12); + ctx->q_data_dst.width =3D 640; + ctx->q_data_dst.height =3D 480; + ctx->q_data_dst.bytesperline =3D 640; + ctx->q_data_dst.sizeimage =3D 640 * 480 * 3 / 2; + + ctx->fh.m2m_ctx =3D v4l2_m2m_ctx_init(ope->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc =3D PTR_ERR(ctx->fh.m2m_ctx); + v4l2_fh_exit(&ctx->fh); + kvfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh, file); + + list_add(&ctx->list, &ope->ctx_list); + + dev_dbg(ope->dev, "Created ctx %p\n", ctx); + +open_unlock: + mutex_unlock(&ope->mutex); + return rc; +} + +static int ope_release(struct file *file) +{ + struct ope_dev *ope =3D video_drvdata(file); + struct ope_ctx *ctx =3D file2ctx(file); + + dev_dbg(ope->dev, "Releasing ctx %p\n", ctx); + + guard(mutex)(&ope->mutex); + + if (ope->context =3D=3D ctx) + ope->context =3D NULL; + + list_del(&ctx->list); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_fh_del(&ctx->fh, file); + v4l2_fh_exit(&ctx->fh); + kvfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations ope_fops =3D { + .owner =3D THIS_MODULE, + .open =3D ope_open, + .release =3D ope_release, + .poll =3D v4l2_m2m_fop_poll, + .unlocked_ioctl =3D video_ioctl2, + .mmap =3D v4l2_m2m_fop_mmap, +}; + +static const struct video_device ope_videodev =3D { + .name =3D MEM2MEM_NAME, + .vfl_dir =3D VFL_DIR_M2M, + .fops =3D &ope_fops, + .device_caps =3D V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE, + .ioctl_ops =3D &ope_ioctl_ops, + .minor =3D -1, + .release =3D video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops =3D { + .device_run =3D ope_device_run, + .job_abort =3D ope_job_abort, +}; + +static int ope_soft_reset(struct ope_dev *ope) +{ + u32 version; + int ret =3D 0; + + ret =3D pm_runtime_resume_and_get(ope->dev); + if (ret) { + dev_err(ope->dev, "Could not resume\n"); + return ret; + } + + version =3D ope_read(ope, OPE_TOP_HW_VERSION); + + dev_dbg(ope->dev, "HW Version =3D %u.%u.%u\n", + (u32)FIELD_GET(OPE_TOP_HW_VERSION_GEN, version), + (u32)FIELD_GET(OPE_TOP_HW_VERSION_REV, version), + (u32)FIELD_GET(OPE_TOP_HW_VERSION_STEP, version)); + + reinit_completion(&ope->reset_complete); + + ope_write(ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + + if (!wait_for_completion_timeout(&ope->reset_complete, + msecs_to_jiffies(OPE_RESET_TIMEOUT_MS))) { + dev_err(ope->dev, "Reset timeout\n"); + ret =3D -ETIMEDOUT; + } + + pm_runtime_put(ope->dev); + + return ret; +} + +static int ope_init_power(struct ope_dev *ope) +{ + struct dev_pm_domain_list *pmdomains; + struct device *dev =3D ope->dev; + int ret; + + ope->icc_data =3D devm_of_icc_get(dev, "data"); + if (IS_ERR(ope->icc_data)) + return dev_err_probe(dev, PTR_ERR(ope->icc_data), + "failed to get interconnect data path\n"); + + ope->icc_config =3D devm_of_icc_get(dev, "config"); + if (IS_ERR(ope->icc_config)) + return dev_err_probe(dev, PTR_ERR(ope->icc_config), + "failed to get interconnect config path\n"); + + /* Devices with multiple PM domains must be attached separately */ + devm_pm_domain_attach_list(dev, NULL, &pmdomains); + + /* core clock is scaled as part of operating points */ + ret =3D devm_pm_opp_set_clkname(dev, "core"); + if (ret) + return ret; + + ret =3D devm_pm_opp_of_add_table(dev); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "invalid OPP table\n"); + + ret =3D devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret =3D devm_pm_clk_create(dev); + if (ret) + return ret; + + ret =3D of_pm_clk_add_clks(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int ope_init_mmio(struct ope_dev *ope) +{ + struct platform_device *pdev =3D to_platform_device(ope->dev); + + ope->base =3D devm_platform_ioremap_resource_byname(pdev, "top"); + if (IS_ERR(ope->base)) + return PTR_ERR(ope->base); + + ope->base_rd =3D devm_platform_ioremap_resource_byname(pdev, "bus_read"); + if (IS_ERR(ope->base_rd)) + return PTR_ERR(ope->base_rd); + + ope->base_wr =3D devm_platform_ioremap_resource_byname(pdev, "bus_write"); + if (IS_ERR(ope->base_wr)) + return PTR_ERR(ope->base_wr); + + ope->base_pp =3D devm_platform_ioremap_resource_byname(pdev, "pipeline"); + if (IS_ERR(ope->base_pp)) + return PTR_ERR(ope->base_pp); + + return 0; +} + +static int ope_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct video_device *vfd; + struct ope_dev *ope; + int ret, irq; + + ope =3D devm_kzalloc(&pdev->dev, sizeof(*ope), GFP_KERNEL); + if (!ope) + return -ENOMEM; + + ope->dev =3D dev; + init_completion(&ope->reset_complete); + + ret =3D ope_init_power(ope); + if (ret) + return dev_err_probe(dev, ret, "Power init failed\n"); + + ret =3D ope_init_mmio(ope); + if (ret) + return dev_err_probe(dev, ret, "MMIO init failed\n"); + + irq =3D platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(dev, irq, "Unable to get IRQ\n"); + + ret =3D devm_request_irq(dev, irq, ope_irq, IRQF_TRIGGER_RISING, "ope", o= pe); + if (ret < 0) + return dev_err_probe(dev, ret, "Requesting IRQ failed\n"); + + ret =3D ope_soft_reset(ope); + if (ret < 0) + return ret; + + ret =3D v4l2_device_register(&pdev->dev, &ope->v4l2_dev); + if (ret) + return dev_err_probe(dev, ret, "Registering V4L2 device failed\n"); + + mutex_init(&ope->mutex); + INIT_LIST_HEAD(&ope->ctx_list); + + ope->vfd =3D ope_videodev; + vfd =3D &ope->vfd; + vfd->lock =3D &ope->mutex; + vfd->v4l2_dev =3D &ope->v4l2_dev; + video_set_drvdata(vfd, ope); + snprintf(vfd->name, sizeof(vfd->name), "%s", ope_videodev.name); + + platform_set_drvdata(pdev, ope); + + ope->m2m_dev =3D v4l2_m2m_init(&m2m_ops); + if (IS_ERR(ope->m2m_dev)) { + ret =3D dev_err_probe(dev, PTR_ERR(ope->m2m_dev), "Failed to init mem2me= m device\n"); + goto err_unregister_v4l2; + } + + ret =3D video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + dev_err(dev, "Failed to refgister video device\n"); + goto err_release_m2m; + } + + /* TODO: Add stat device and link it to media */ + ope->mdev.dev =3D dev; + strscpy(ope->mdev.model, MEM2MEM_NAME, sizeof(ope->mdev.model)); + media_device_init(&ope->mdev); + ope->v4l2_dev.mdev =3D &ope->mdev; + + ret =3D v4l2_m2m_register_media_controller(ope->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret) { + dev_err(&pdev->dev, "Failed to register m2m media controller\n"); + goto err_unregister_video; + } + + ret =3D media_device_register(&ope->mdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register media device\n"); + goto err_unregister_m2m_mc; + } + + return 0; + +err_unregister_m2m_mc: + v4l2_m2m_unregister_media_controller(ope->m2m_dev); +err_unregister_video: + video_unregister_device(&ope->vfd); +err_release_m2m: + v4l2_m2m_release(ope->m2m_dev); +err_unregister_v4l2: + v4l2_device_unregister(&ope->v4l2_dev); + + return ret; +} + +static void ope_remove(struct platform_device *pdev) +{ + struct ope_dev *ope =3D platform_get_drvdata(pdev); + + media_device_unregister(&ope->mdev); + v4l2_m2m_unregister_media_controller(ope->m2m_dev); + video_unregister_device(&ope->vfd); + v4l2_m2m_release(ope->m2m_dev); + v4l2_device_unregister(&ope->v4l2_dev); +} + +static const struct of_device_id ope_dt_ids[] =3D { + { .compatible =3D "qcom,qcm2290-camss-ope"}, + { }, +}; +MODULE_DEVICE_TABLE(of, ope_dt_ids); + +static const struct dev_pm_ops ope_pm_ops =3D { + SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) +}; + +static struct platform_driver ope_driver =3D { + .probe =3D ope_probe, + .remove =3D ope_remove, + .driver =3D { + .name =3D MEM2MEM_NAME, + .of_match_table =3D ope_dt_ids, + .pm =3D &ope_pm_ops, + }, +}; + +module_platform_driver(ope_driver); + +MODULE_DESCRIPTION("CAMSS Offline Processing Engine"); +MODULE_AUTHOR("Loic Poulain "); +MODULE_LICENSE("GPL"); --=20 2.34.1