From patchwork Thu Jan 16 13:34:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Neil Armstrong X-Patchwork-Id: 11336999 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 63235921 for ; Thu, 16 Jan 2020 13:36:07 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 2CC3B2072B for ; Thu, 16 Jan 2020 13:36:07 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="aOjijame"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=baylibre-com.20150623.gappssmtp.com header.i=@baylibre-com.20150623.gappssmtp.com header.b="XqszYw0X" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 2CC3B2072B Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=lrBOw4lPg1eXNXernjNzGHdyFoqeSF6MN6eoQ94C2qg=; b=aOjijameAlB3qF rwDPPjPw1WlnxHTBbbqACT9qvDcg0reQuU47KQBYY+QQ5HQnjANk7VKKvALxfC56YAl5oXhtPAoGp YnQ9OU7GB7vdnv3LggPPBNl7Duh7nVqYRU5K7wq6VgbNCSqqfO1uy98hTN+Eq00dUFprdpBs0YNOd Ji57C1Rrq9uNkyHQSPXFojtjVGgooBt4Gvbu4KIoby2uPCUL9DE/1jAYFsiebtfRAtV8N3oM2XzB0 26eUOPeiG1JR2aBXiVEXHDW2KeLUpSwEts4yQSuqbEz6WBvukBjLdv+VehVpT2/hJpXHL4tcRdGSR 1IwR+v/JrIlHOs6BmhgA==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1is5Je-0000qG-3p; Thu, 16 Jan 2020 13:36:06 +0000 Received: from mail-wm1-x343.google.com ([2a00:1450:4864:20::343]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1is5IM-00076a-Q1 for linux-arm-kernel@lists.infradead.org; Thu, 16 Jan 2020 13:34:59 +0000 Received: by mail-wm1-x343.google.com with SMTP id a5so3808090wmb.0 for ; Thu, 16 Jan 2020 05:34:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=BguqgoE1jCQbIijD1jHMDagjNNEXrcQaeP9djSbm+K4=; b=XqszYw0XgAqbF7b9TQoThd5kABGOvDr9FPz4jKajS1rf7UwmsKM/S0h7U9qM2qKC/d eUmhjHgQs4JhgHwUshk52cyyAgDCmp8ZnL9Fn+zfToUzw9nSETyMzIsddhBxrzsiX5yW TmIQKdGSGhTmvswYXfmrpQB0cYSoQmeJeobv2pVwFkkRUFOM3F12LwLhN8C0L5Ec4wtm ye3coe9fV1H3vhle7snZYI9HiPzGQ+9sD7meG9YdNhttvvQPa0B1o2+W84d7bj+bl9wX PannS+PA1wcajtCmVWANrrFogztDgkkQdxRUtm8UHABchk6aTiZGqtZXeWyg0zAR3nnp nmRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=BguqgoE1jCQbIijD1jHMDagjNNEXrcQaeP9djSbm+K4=; b=lS3NWcLRyrx4RmQt/nO6n1Ia1eaPpKf9xsFqsOop2c08QMbEiAoQCt9xLexLY34Gvu N0JDSogcU7ua1Ytl6Hni/s6J0EZwgTuesFMmDYGrw4/i0N3gz/4MzvcZJqTyM1N0xWwv FuG0fH+qDvs40Mt9gaDtn6pDFbKPLP+7ic3fgacyguvGg+084VL34BAt3gQv9nap70dU raYuUVaF4leV08sAAvEJU+gg21I9WctRIxQ2yuKaA43n9lrp1KjTq+8IXFA7owSfW5mw uusgeRTKdYGiTzap+q3PIhd27C3ds4RUuuHR7ljWsNx5ol3WrY8aKjqAtHxnSMyH6pgu pVaw== X-Gm-Message-State: APjAAAUGjAfx7YCwu4G8TSDT48ToxjrY9rcO/jkyW2wBTzmaakiPZVbY gQfekmDvvyhbbP6wHUEg8mPK1Q== X-Google-Smtp-Source: APXvYqw5bNrPhIMbllENlqxpg5WWBjwmRqo+S5Y8YvymzqGLVjgGlqmHb6c6kXYLteRHtmijMt7l6Q== X-Received: by 2002:a1c:9ac6:: with SMTP id c189mr2899635wme.59.1579181685218; Thu, 16 Jan 2020 05:34:45 -0800 (PST) Received: from bender.baylibre.local (laubervilliers-658-1-213-31.w90-63.abo.wanadoo.fr. [90.63.244.31]) by smtp.gmail.com with ESMTPSA id x14sm162559wmj.42.2020.01.16.05.34.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 Jan 2020 05:34:44 -0800 (PST) From: Neil Armstrong To: mchehab@kernel.org, hans.verkuil@cisco.com Subject: [PATCH v3 3/5] media: meson: vdec: add common HEVC decoder support Date: Thu, 16 Jan 2020 14:34:35 +0100 Message-Id: <20200116133437.2443-4-narmstrong@baylibre.com> X-Mailer: git-send-email 2.22.0 In-Reply-To: <20200116133437.2443-1-narmstrong@baylibre.com> References: <20200116133437.2443-1-narmstrong@baylibre.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200116_053447_045535_CBEB5B27 X-CRM114-Status: GOOD ( 18.44 ) X-Spam-Score: 0.0 (/) X-Spam-Report: SpamAssassin version 3.4.2 on bombadil.infradead.org summary: Content analysis details: (0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:343 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Maxime Jourdan , Neil Armstrong , linux-kernel@vger.kernel.org, linux-amlogic@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-media@vger.kernel.org Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org From: Maxime Jourdan Add support for the HEVC & VP9 common decoder support, handling Amlogic GXBB, GXL, G12A and SM1 platforms. This handles the "HEVC" hw decoder used for HEVC and VP9, and will be using in the new H264 multi-instance decoder for G12A & SM1 platforms. Signed-off-by: Maxime Jourdan Signed-off-by: Neil Armstrong --- drivers/staging/media/meson/vdec/Makefile | 4 +- .../media/meson/vdec/codec_hevc_common.c | 285 ++++++++++++++++++ .../media/meson/vdec/codec_hevc_common.h | 77 +++++ drivers/staging/media/meson/vdec/hevc_regs.h | 211 +++++++++++++ drivers/staging/media/meson/vdec/vdec_hevc.c | 231 ++++++++++++++ drivers/staging/media/meson/vdec/vdec_hevc.h | 13 + 6 files changed, 819 insertions(+), 2 deletions(-) create mode 100644 drivers/staging/media/meson/vdec/codec_hevc_common.c create mode 100644 drivers/staging/media/meson/vdec/codec_hevc_common.h create mode 100644 drivers/staging/media/meson/vdec/hevc_regs.h create mode 100644 drivers/staging/media/meson/vdec/vdec_hevc.c create mode 100644 drivers/staging/media/meson/vdec/vdec_hevc.h diff --git a/drivers/staging/media/meson/vdec/Makefile b/drivers/staging/media/meson/vdec/Makefile index 711d990c760e..f55b6e625034 100644 --- a/drivers/staging/media/meson/vdec/Makefile +++ b/drivers/staging/media/meson/vdec/Makefile @@ -2,7 +2,7 @@ # Makefile for Amlogic meson video decoder driver meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o -meson-vdec-objs += vdec_1.o -meson-vdec-objs += codec_mpeg12.o codec_h264.o +meson-vdec-objs += vdec_1.o vdec_hevc.o +meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_hevc_common.o obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.c b/drivers/staging/media/meson/vdec/codec_hevc_common.c new file mode 100644 index 000000000000..72a4e38a42dc --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan + */ + +#include +#include + +#include "codec_hevc_common.h" +#include "vdec_helpers.h" +#include "hevc_regs.h" + +#define MMU_COMPRESS_HEADER_SIZE 0x48000 +#define MMU_MAP_SIZE 0x4800 + +/* Configure decode head read mode */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit) +{ + struct amvdec_core *core = sess->core; + u32 body_size = amvdec_am21c_body_size(sess->width, sess->height); + u32 head_size = amvdec_am21c_head_size(sess->width, sess->height); + + if (!codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + /* Enable 2-plane reference read mode */ + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(31)); + return; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(4)); + else + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, 0); + + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL2, body_size / 32); + amvdec_write_dos(core, HEVC_CM_BODY_LENGTH, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_OFFSET, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_LENGTH, head_size); +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_decode_head); + +static void codec_hevc_setup_buffers_gxbb(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 buf_num = v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = 0; + u32 val; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 0); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + + idx = vb->index; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + val = buf_y_paddr | (idx << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } else { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + val = buf_y_paddr | ((idx * 2) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + val = buf_uv_paddr | ((idx * 2 + 1) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } + } + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) + val = buf_y_paddr | (idx << 8) | 1; + else + val = buf_y_paddr | ((idx * 2) << 8) | 1; + + /* Fill the remaining unused slots with the last buffer's Y addr */ + for (i = buf_num; i < MAX_REF_PIC_NUM; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, val); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +static void codec_hevc_setup_buffers_gxl(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 revision = core->platform->revision; + u32 pixfmt_cap = sess->pixfmt_cap; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, + BIT(2) | BIT(1)); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = vb->index; + + if (codec_hevc_use_mmu(revision, pixfmt_cap, is_10bit)) + buf_y_paddr = comm->mmu_header_paddr[idx]; + else if (codec_hevc_use_downsample(pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_y_paddr >> 5); + + if (!codec_hevc_use_fbc(pixfmt_cap, is_10bit)) { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_uv_paddr >> 5); + } + } + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->fbc_buffer_vaddr[i]) { + dma_free_coherent(dev, am21_size, + comm->fbc_buffer_vaddr[i], + comm->fbc_buffer_paddr[i]); + comm->fbc_buffer_vaddr[i] = NULL; + } + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_fbc_buffers); + +static int codec_hevc_alloc_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, am21_size, &paddr, + GFP_KERNEL); + if (!vaddr) { + dev_err(dev, "Couldn't allocate FBC buffer %u\n", idx); + codec_hevc_free_fbc_buffers(sess, comm); + return -ENOMEM; + } + + comm->fbc_buffer_vaddr[idx] = vaddr; + comm->fbc_buffer_paddr[idx] = paddr; + } + + return 0; +} + +void codec_hevc_free_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->mmu_header_vaddr[i]) { + dma_free_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + comm->mmu_header_vaddr[i], + comm->mmu_header_paddr[i]); + comm->mmu_header_vaddr[i] = NULL; + } + } + + if (comm->mmu_map_vaddr) { + dma_free_coherent(dev, MMU_MAP_SIZE, + comm->mmu_map_vaddr, + comm->mmu_map_paddr); + comm->mmu_map_vaddr = NULL; + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_mmu_headers); + +static int codec_hevc_alloc_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + + comm->mmu_map_vaddr = dma_alloc_coherent(dev, MMU_MAP_SIZE, + &comm->mmu_map_paddr, + GFP_KERNEL); + if (!comm->mmu_map_vaddr) + return -ENOMEM; + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + &paddr, GFP_KERNEL); + if (!vaddr) { + dev_err(dev, "Couldn't allocate MMU header %u\n", idx); + codec_hevc_free_mmu_headers(sess, comm); + return -ENOMEM; + } + + comm->mmu_header_vaddr[idx] = vaddr; + comm->mmu_header_paddr[idx] = paddr; + } + + return 0; +} + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + int ret; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_fbc_buffers(sess, comm); + if (ret) + return ret; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_mmu_headers(sess, comm); + if (ret) { + codec_hevc_free_fbc_buffers(sess, comm); + return ret; + } + } + + if (core->platform->revision == VDEC_REVISION_GXBB) + codec_hevc_setup_buffers_gxbb(sess, comm, is_10bit); + else + codec_hevc_setup_buffers_gxl(sess, comm, is_10bit); + + return 0; +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_buffers); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb) +{ + u32 size = amvdec_am21c_size(sess->width, sess->height); + u32 nb_pages = size / PAGE_SIZE; + u32 *mmu_map = comm->mmu_map_vaddr; + u32 first_page; + u32 i; + + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + first_page = comm->fbc_buffer_paddr[vb->index] >> PAGE_SHIFT; + else + first_page = vb2_dma_contig_plane_dma_addr(vb, 0) >> PAGE_SHIFT; + + for (i = 0; i < nb_pages; ++i) + mmu_map[i] = first_page + i; +} +EXPORT_SYMBOL_GPL(codec_hevc_fill_mmu_map); diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.h b/drivers/staging/media/meson/vdec/codec_hevc_common.h new file mode 100644 index 000000000000..de16d2e43061 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan + */ + +#ifndef __MESON_VDEC_HEVC_COMMON_H_ +#define __MESON_VDEC_HEVC_COMMON_H_ + +#include "vdec.h" + +#define PARSER_CMD_SKIP_CFG_0 0x0000090b +#define PARSER_CMD_SKIP_CFG_1 0x1b14140f +#define PARSER_CMD_SKIP_CFG_2 0x001b1910 +static const u16 vdec_hevc_parser_cmd[] = { + 0x0401, 0x8401, 0x0800, 0x0402, + 0x9002, 0x1423, 0x8CC3, 0x1423, + 0x8804, 0x9825, 0x0800, 0x04FE, + 0x8406, 0x8411, 0x1800, 0x8408, + 0x8409, 0x8C2A, 0x9C2B, 0x1C00, + 0x840F, 0x8407, 0x8000, 0x8408, + 0x2000, 0xA800, 0x8410, 0x04DE, + 0x840C, 0x840D, 0xAC00, 0xA000, + 0x08C0, 0x08E0, 0xA40E, 0xFC00, + 0x7C00 +}; + +#define MAX_REF_PIC_NUM 24 + +struct codec_hevc_common { + void *fbc_buffer_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t fbc_buffer_paddr[MAX_REF_PIC_NUM]; + + void *mmu_header_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t mmu_header_paddr[MAX_REF_PIC_NUM]; + + void *mmu_map_vaddr; + dma_addr_t mmu_map_paddr; +}; + +/* Returns 1 if we must use framebuffer compression */ +static inline int codec_hevc_use_fbc(u32 pixfmt, int is_10bit) +{ + /* TOFIX: Handle Amlogic Compressed buffer for 8bit also */ + return is_10bit; +} + +/* Returns 1 if we are decoding 10-bit but outputting 8-bit NV12 */ +static inline int codec_hevc_use_downsample(u32 pixfmt, int is_10bit) +{ + return is_10bit; +} + +/* Returns 1 if we are decoding using the IOMMU */ +static inline int codec_hevc_use_mmu(u32 revision, u32 pixfmt, int is_10bit) +{ + return revision >= VDEC_REVISION_G12A && + codec_hevc_use_fbc(pixfmt, is_10bit); +} + +/** + * Configure decode head read mode + */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit); + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm); + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb); + +#endif diff --git a/drivers/staging/media/meson/vdec/hevc_regs.h b/drivers/staging/media/meson/vdec/hevc_regs.h new file mode 100644 index 000000000000..55c1a80b955a --- /dev/null +++ b/drivers/staging/media/meson/vdec/hevc_regs.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#ifndef __MESON_VDEC_HEVC_REGS_H_ +#define __MESON_VDEC_HEVC_REGS_H_ + +#define HEVC_ASSIST_MMU_MAP_ADDR 0xc024 + +#define HEVC_ASSIST_MBOX1_CLR_REG 0xc1d4 +#define HEVC_ASSIST_MBOX1_MASK 0xc1d8 + +#define HEVC_ASSIST_SCRATCH_0 0xc300 +#define HEVC_ASSIST_SCRATCH_1 0xc304 +#define HEVC_ASSIST_SCRATCH_2 0xc308 +#define HEVC_ASSIST_SCRATCH_3 0xc30c +#define HEVC_ASSIST_SCRATCH_4 0xc310 +#define HEVC_ASSIST_SCRATCH_5 0xc314 +#define HEVC_ASSIST_SCRATCH_6 0xc318 +#define HEVC_ASSIST_SCRATCH_7 0xc31c +#define HEVC_ASSIST_SCRATCH_8 0xc320 +#define HEVC_ASSIST_SCRATCH_9 0xc324 +#define HEVC_ASSIST_SCRATCH_A 0xc328 +#define HEVC_ASSIST_SCRATCH_B 0xc32c +#define HEVC_ASSIST_SCRATCH_C 0xc330 +#define HEVC_ASSIST_SCRATCH_D 0xc334 +#define HEVC_ASSIST_SCRATCH_E 0xc338 +#define HEVC_ASSIST_SCRATCH_F 0xc33c +#define HEVC_ASSIST_SCRATCH_G 0xc340 +#define HEVC_ASSIST_SCRATCH_H 0xc344 +#define HEVC_ASSIST_SCRATCH_I 0xc348 +#define HEVC_ASSIST_SCRATCH_J 0xc34c +#define HEVC_ASSIST_SCRATCH_K 0xc350 +#define HEVC_ASSIST_SCRATCH_L 0xc354 +#define HEVC_ASSIST_SCRATCH_M 0xc358 +#define HEVC_ASSIST_SCRATCH_N 0xc35c + +#define HEVC_PARSER_VERSION 0xc400 +#define HEVC_STREAM_CONTROL 0xc404 +#define HEVC_STREAM_START_ADDR 0xc408 +#define HEVC_STREAM_END_ADDR 0xc40c +#define HEVC_STREAM_WR_PTR 0xc410 +#define HEVC_STREAM_RD_PTR 0xc414 +#define HEVC_STREAM_LEVEL 0xc418 +#define HEVC_STREAM_FIFO_CTL 0xc41c +#define HEVC_SHIFT_CONTROL 0xc420 +#define HEVC_SHIFT_STARTCODE 0xc424 +#define HEVC_SHIFT_EMULATECODE 0xc428 +#define HEVC_SHIFT_STATUS 0xc42c +#define HEVC_SHIFTED_DATA 0xc430 +#define HEVC_SHIFT_BYTE_COUNT 0xc434 +#define HEVC_SHIFT_COMMAND 0xc438 +#define HEVC_ELEMENT_RESULT 0xc43c +#define HEVC_CABAC_CONTROL 0xc440 +#define HEVC_PARSER_SLICE_INFO 0xc444 +#define HEVC_PARSER_CMD_WRITE 0xc448 +#define HEVC_PARSER_CORE_CONTROL 0xc44c +#define HEVC_PARSER_CMD_FETCH 0xc450 +#define HEVC_PARSER_CMD_STATUS 0xc454 +#define HEVC_PARSER_LCU_INFO 0xc458 +#define HEVC_PARSER_HEADER_INFO 0xc45c +#define HEVC_PARSER_INT_CONTROL 0xc480 +#define HEVC_PARSER_INT_STATUS 0xc484 +#define HEVC_PARSER_IF_CONTROL 0xc488 +#define HEVC_PARSER_PICTURE_SIZE 0xc48c +#define HEVC_PARSER_LCU_START 0xc490 +#define HEVC_PARSER_HEADER_INFO2 0xc494 +#define HEVC_PARSER_QUANT_READ 0xc498 +#define HEVC_PARSER_RESERVED_27 0xc49c +#define HEVC_PARSER_CMD_SKIP_0 0xc4a0 +#define HEVC_PARSER_CMD_SKIP_1 0xc4a4 +#define HEVC_PARSER_CMD_SKIP_2 0xc4a8 +#define HEVC_SAO_IF_STATUS 0xc4c0 +#define HEVC_SAO_IF_DATA_Y 0xc4c4 +#define HEVC_SAO_IF_DATA_U 0xc4c8 +#define HEVC_SAO_IF_DATA_V 0xc4cc +#define HEVC_STREAM_SWAP_ADDR 0xc4d0 +#define HEVC_STREAM_SWAP_CTRL 0xc4d4 +#define HEVC_IQIT_IF_WAIT_CNT 0xc4d8 +#define HEVC_MPRED_IF_WAIT_CNT 0xc4dc +#define HEVC_SAO_IF_WAIT_CNT 0xc4e0 + +#define HEVC_MPRED_VERSION 0xc800 +#define HEVC_MPRED_CTRL0 0xc804 + #define MPRED_CTRL0_NEW_PIC BIT(2) + #define MPRED_CTRL0_NEW_TILE BIT(3) + #define MPRED_CTRL0_NEW_SLI_SEG BIT(4) + #define MPRED_CTRL0_TMVP BIT(5) + #define MPRED_CTRL0_LDC BIT(6) + #define MPRED_CTRL0_COL_FROM_L0 BIT(7) + #define MPRED_CTRL0_ABOVE_EN BIT(9) + #define MPRED_CTRL0_MV_WR_EN BIT(10) + #define MPRED_CTRL0_MV_RD_EN BIT(11) + #define MPRED_CTRL0_BUF_LINEAR BIT(13) +#define HEVC_MPRED_CTRL1 0xc808 +#define HEVC_MPRED_INT_EN 0xc80c +#define HEVC_MPRED_INT_STATUS 0xc810 +#define HEVC_MPRED_PIC_SIZE 0xc814 +#define HEVC_MPRED_PIC_SIZE_LCU 0xc818 +#define HEVC_MPRED_TILE_START 0xc81c +#define HEVC_MPRED_TILE_SIZE_LCU 0xc820 +#define HEVC_MPRED_REF_NUM 0xc824 +#define HEVC_MPRED_REF_EN_L0 0xc830 +#define HEVC_MPRED_REF_EN_L1 0xc834 +#define HEVC_MPRED_COLREF_EN_L0 0xc838 +#define HEVC_MPRED_COLREF_EN_L1 0xc83c +#define HEVC_MPRED_AXI_WCTRL 0xc840 +#define HEVC_MPRED_AXI_RCTRL 0xc844 +#define HEVC_MPRED_ABV_START_ADDR 0xc848 +#define HEVC_MPRED_MV_WR_START_ADDR 0xc84c +#define HEVC_MPRED_MV_RD_START_ADDR 0xc850 +#define HEVC_MPRED_MV_WPTR 0xc854 +#define HEVC_MPRED_MV_RPTR 0xc858 +#define HEVC_MPRED_MV_WR_ROW_JUMP 0xc85c +#define HEVC_MPRED_MV_RD_ROW_JUMP 0xc860 +#define HEVC_MPRED_CURR_LCU 0xc864 +#define HEVC_MPRED_ABV_WPTR 0xc868 +#define HEVC_MPRED_ABV_RPTR 0xc86c +#define HEVC_MPRED_CTRL2 0xc870 +#define HEVC_MPRED_CTRL3 0xc874 +#define HEVC_MPRED_L0_REF00_POC 0xc880 +#define HEVC_MPRED_L1_REF00_POC 0xc8c0 + +#define HEVC_MPRED_CUR_POC 0xc980 +#define HEVC_MPRED_COL_POC 0xc984 +#define HEVC_MPRED_MV_RD_END_ADDR 0xc988 + +#define HEVC_MSP 0xcc00 +#define HEVC_MPSR 0xcc04 +#define HEVC_MCPU_INTR_MSK 0xcc10 +#define HEVC_MCPU_INTR_REQ 0xcc14 +#define HEVC_CPSR 0xcc84 + +#define HEVC_IMEM_DMA_CTRL 0xcd00 +#define HEVC_IMEM_DMA_ADR 0xcd04 +#define HEVC_IMEM_DMA_COUNT 0xcd08 + +#define HEVCD_IPP_TOP_CNTL 0xd000 +#define HEVCD_IPP_LINEBUFF_BASE 0xd024 +#define HEVCD_IPP_AXIIF_CONFIG 0xd02c + +#define HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR 0xd180 +#define HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR 0xd184 +#define HEVCD_MPP_ANC2AXI_TBL_DATA 0xd190 + +#define HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR 0xd300 +#define HEVCD_MPP_ANC_CANVAS_DATA_ADDR 0xd304 +#define HEVCD_MPP_DECOMP_CTL1 0xd308 +#define HEVCD_MPP_DECOMP_CTL2 0xd30c +#define HEVCD_MCRCC_CTL1 0xd3c0 +#define HEVCD_MCRCC_CTL2 0xd3c4 +#define HEVCD_MCRCC_CTL3 0xd3c8 + +#define HEVC_DBLK_CFG0 0xd400 +#define HEVC_DBLK_CFG1 0xd404 +#define HEVC_DBLK_CFG2 0xd408 +#define HEVC_DBLK_CFG3 0xd40c +#define HEVC_DBLK_CFG4 0xd410 +#define HEVC_DBLK_CFG5 0xd414 +#define HEVC_DBLK_CFG6 0xd418 +#define HEVC_DBLK_CFG7 0xd41c +#define HEVC_DBLK_CFG8 0xd420 +#define HEVC_DBLK_CFG9 0xd424 +#define HEVC_DBLK_CFGA 0xd428 +#define HEVC_DBLK_STS0 0xd42c +#define HEVC_DBLK_STS1 0xd430 +#define HEVC_DBLK_CFGE 0xd438 + +#define HEVC_SAO_VERSION 0xd800 +#define HEVC_SAO_CTRL0 0xd804 +#define HEVC_SAO_CTRL1 0xd808 +#define HEVC_SAO_PIC_SIZE 0xd814 +#define HEVC_SAO_PIC_SIZE_LCU 0xd818 +#define HEVC_SAO_TILE_START 0xd81c +#define HEVC_SAO_TILE_SIZE_LCU 0xd820 +#define HEVC_SAO_Y_START_ADDR 0xd82c +#define HEVC_SAO_Y_LENGTH 0xd830 +#define HEVC_SAO_C_START_ADDR 0xd834 +#define HEVC_SAO_C_LENGTH 0xd838 +#define HEVC_SAO_Y_WPTR 0xd83c +#define HEVC_SAO_C_WPTR 0xd840 +#define HEVC_SAO_ABV_START_ADDR 0xd844 +#define HEVC_SAO_VB_WR_START_ADDR 0xd848 +#define HEVC_SAO_VB_RD_START_ADDR 0xd84c +#define HEVC_SAO_ABV_WPTR 0xd850 +#define HEVC_SAO_ABV_RPTR 0xd854 +#define HEVC_SAO_VB_WPTR 0xd858 +#define HEVC_SAO_VB_RPTR 0xd85c +#define HEVC_SAO_CTRL2 0xd880 +#define HEVC_SAO_CTRL3 0xd884 +#define HEVC_SAO_CTRL4 0xd888 +#define HEVC_SAO_CTRL5 0xd88c +#define HEVC_SAO_CTRL6 0xd890 +#define HEVC_SAO_CTRL7 0xd894 +#define HEVC_CM_BODY_START_ADDR 0xd898 +#define HEVC_CM_BODY_LENGTH 0xd89c +#define HEVC_CM_HEADER_START_ADDR 0xd8a0 +#define HEVC_CM_HEADER_LENGTH 0xd8a4 +#define HEVC_CM_HEADER_OFFSET 0xd8ac +#define HEVC_SAO_MMU_VH0_ADDR 0xd8e8 +#define HEVC_SAO_MMU_VH1_ADDR 0xd8ec + +#define HEVC_IQIT_CLK_RST_CTRL 0xdc00 +#define HEVC_IQIT_SCALELUT_WR_ADDR 0xdc08 +#define HEVC_IQIT_SCALELUT_RD_ADDR 0xdc0c +#define HEVC_IQIT_SCALELUT_DATA 0xdc10 + +#define HEVC_PSCALE_CTRL 0xe444 + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.c b/drivers/staging/media/meson/vdec/vdec_hevc.c new file mode 100644 index 000000000000..af41215e106c --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan + * + * VDEC_HEVC is a video decoding block that allows decoding of + * HEVC, VP9 + */ + +#include +#include + +#include "vdec_1.h" +#include "vdec_helpers.h" +#include "hevc_regs.h" +#include "dos_regs.h" + +/* AO Registers */ +#define AO_RTI_GEN_PWR_SLEEP0 0xe8 +#define AO_RTI_GEN_PWR_ISO0 0xec + #define GEN_PWR_VDEC_HEVC (BIT(7) | BIT(6)) + #define GEN_PWR_VDEC_HEVC_SM1 (BIT(2)) + +#define MC_SIZE (4096 * 4) + +static int vdec_hevc_load_firmware(struct amvdec_session *sess, + const char *fwname) +{ + struct amvdec_core *core = sess->core; + struct device *dev = core->dev_dec; + const struct firmware *fw; + static void *mc_addr; + static dma_addr_t mc_addr_map; + int ret; + u32 i = 100; + + ret = request_firmware(&fw, fwname, dev); + if (ret < 0) { + dev_err(dev, "Unable to request firmware %s\n", fwname); + return ret; + } + + if (fw->size < MC_SIZE) { + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n", + fw->size, MC_SIZE); + ret = -EINVAL; + goto release_firmware; + } + + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE, &mc_addr_map, + GFP_KERNEL); + if (!mc_addr) { + dev_err(dev, "Failed allocating memory for firmware loading\n"); + ret = -ENOMEM; + goto release_firmware; + } + + memcpy(mc_addr, fw->data, MC_SIZE); + + amvdec_write_dos(core, HEVC_MPSR, 0); + amvdec_write_dos(core, HEVC_CPSR, 0); + + amvdec_write_dos(core, HEVC_IMEM_DMA_ADR, mc_addr_map); + amvdec_write_dos(core, HEVC_IMEM_DMA_COUNT, MC_SIZE / 4); + amvdec_write_dos(core, HEVC_IMEM_DMA_CTRL, (0x8000 | (7 << 16))); + + while (i && (readl(core->dos_base + HEVC_IMEM_DMA_CTRL) & 0x8000)) + i--; + + if (i == 0) { + dev_err(dev, "Firmware load fail (DMA hang?)\n"); + ret = -ENODEV; + } + + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map); +release_firmware: + release_firmware(fw); + return ret; +} + +static void vdec_hevc_stbuf_init(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) & ~1); + amvdec_write_dos(core, HEVC_STREAM_START_ADDR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_END_ADDR, + sess->vififo_paddr + sess->vififo_size); + amvdec_write_dos(core, HEVC_STREAM_RD_PTR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_WR_PTR, sess->vififo_paddr); +} + +/* VDEC_HEVC specific ESPARSER configuration */ +static void vdec_hevc_conf_esparser(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + /* set vififo_vbuf_rp_sel=>vdec_hevc */ + amvdec_write_dos(core, DOS_GEN_CTRL0, 3 << 1); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | BIT(3)); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | 1); + amvdec_write_dos(core, HEVC_STREAM_FIFO_CTL, + amvdec_read_dos(core, HEVC_STREAM_FIFO_CTL) | BIT(29)); +} + +static u32 vdec_hevc_vififo_level(struct amvdec_session *sess) +{ + return readl_relaxed(sess->core->dos_base + HEVC_STREAM_LEVEL); +} + +static int vdec_hevc_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + /* Disable interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_MASK, 0); + /* Disable firmware processor */ + amvdec_write_dos(core, HEVC_MPSR, 0); + + if (sess->priv) + codec_ops->stop(sess); + + /* Enable VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0xc00); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0xffffffffUL); + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, GEN_PWR_VDEC_HEVC); + + clk_disable_unprepare(core->vdec_hevc_clk); + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) + clk_disable_unprepare(core->vdec_hevcf_clk); + + return 0; +} + +static int vdec_hevc_start(struct amvdec_session *sess) +{ + int ret; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) { + clk_set_rate(core->vdec_hevcf_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevcf_clk); + if (ret) + return ret; + } + + clk_set_rate(core->vdec_hevc_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevc_clk); + if (ret) + return ret; + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, 0); + udelay(10); + + /* Reset VDEC_HEVC*/ + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + amvdec_write_dos(core, DOS_GCLK_EN3, 0xffffffff); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0x00000000); + + /* Remove VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0); + + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + vdec_hevc_stbuf_init(sess); + + ret = vdec_hevc_load_firmware(sess, sess->fmt_out->firmware_path); + if (ret) + goto stop; + + ret = codec_ops->start(sess); + if (ret) + goto stop; + + amvdec_write_dos(core, DOS_SW_RESET3, BIT(12) | BIT(11)); + amvdec_write_dos(core, DOS_SW_RESET3, 0); + amvdec_read_dos(core, DOS_SW_RESET3); + + amvdec_write_dos(core, HEVC_MPSR, 1); + /* Let the firmware settle */ + udelay(10); + + return 0; + +stop: + vdec_hevc_stop(sess); + return ret; +} + +struct amvdec_ops vdec_hevc_ops = { + .start = vdec_hevc_start, + .stop = vdec_hevc_stop, + .conf_esparser = vdec_hevc_conf_esparser, + .vififo_level = vdec_hevc_vififo_level, +}; diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.h b/drivers/staging/media/meson/vdec/vdec_hevc.h new file mode 100644 index 000000000000..cd576a73a966 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Maxime Jourdan + */ + +#ifndef __MESON_VDEC_VDEC_HEVC_H_ +#define __MESON_VDEC_VDEC_HEVC_H_ + +#include "vdec.h" + +extern struct amvdec_ops vdec_hevc_ops; + +#endif