162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * i.MX6 Video Data Order Adapter (VDOA)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2014 Philipp Zabel
662306a36Sopenharmony_ci * Copyright (C) 2016 Pengutronix, Michael Tretter <kernel@pengutronix.de>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk.h>
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1462306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/videodev2.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "imx-vdoa.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define VDOA_NAME "imx-vdoa"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define VDOAC		0x00
2462306a36Sopenharmony_ci#define VDOASRR		0x04
2562306a36Sopenharmony_ci#define VDOAIE		0x08
2662306a36Sopenharmony_ci#define VDOAIST		0x0c
2762306a36Sopenharmony_ci#define VDOAFP		0x10
2862306a36Sopenharmony_ci#define VDOAIEBA00	0x14
2962306a36Sopenharmony_ci#define VDOAIEBA01	0x18
3062306a36Sopenharmony_ci#define VDOAIEBA02	0x1c
3162306a36Sopenharmony_ci#define VDOAIEBA10	0x20
3262306a36Sopenharmony_ci#define VDOAIEBA11	0x24
3362306a36Sopenharmony_ci#define VDOAIEBA12	0x28
3462306a36Sopenharmony_ci#define VDOASL		0x2c
3562306a36Sopenharmony_ci#define VDOAIUBO	0x30
3662306a36Sopenharmony_ci#define VDOAVEBA0	0x34
3762306a36Sopenharmony_ci#define VDOAVEBA1	0x38
3862306a36Sopenharmony_ci#define VDOAVEBA2	0x3c
3962306a36Sopenharmony_ci#define VDOAVUBO	0x40
4062306a36Sopenharmony_ci#define VDOASR		0x44
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define VDOAC_ISEL		BIT(6)
4362306a36Sopenharmony_ci#define VDOAC_PFS		BIT(5)
4462306a36Sopenharmony_ci#define VDOAC_SO		BIT(4)
4562306a36Sopenharmony_ci#define VDOAC_SYNC		BIT(3)
4662306a36Sopenharmony_ci#define VDOAC_NF		BIT(2)
4762306a36Sopenharmony_ci#define VDOAC_BNDM_MASK		0x3
4862306a36Sopenharmony_ci#define VDOAC_BAND_HEIGHT_8	0x0
4962306a36Sopenharmony_ci#define VDOAC_BAND_HEIGHT_16	0x1
5062306a36Sopenharmony_ci#define VDOAC_BAND_HEIGHT_32	0x2
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define VDOASRR_START		BIT(1)
5362306a36Sopenharmony_ci#define VDOASRR_SWRST		BIT(0)
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define VDOAIE_EITERR		BIT(1)
5662306a36Sopenharmony_ci#define VDOAIE_EIEOT		BIT(0)
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define VDOAIST_TERR		BIT(1)
5962306a36Sopenharmony_ci#define VDOAIST_EOT		BIT(0)
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define VDOAFP_FH_MASK		(0x1fff << 16)
6262306a36Sopenharmony_ci#define VDOAFP_FW_MASK		(0x3fff)
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#define VDOASL_VSLY_MASK	(0x3fff << 16)
6562306a36Sopenharmony_ci#define VDOASL_ISLY_MASK	(0x7fff)
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define VDOASR_ERRW		BIT(4)
6862306a36Sopenharmony_ci#define VDOASR_EOB		BIT(3)
6962306a36Sopenharmony_ci#define VDOASR_CURRENT_FRAME	(0x3 << 1)
7062306a36Sopenharmony_ci#define VDOASR_CURRENT_BUFFER	BIT(1)
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cienum {
7362306a36Sopenharmony_ci	V4L2_M2M_SRC = 0,
7462306a36Sopenharmony_ci	V4L2_M2M_DST = 1,
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistruct vdoa_data {
7862306a36Sopenharmony_ci	struct vdoa_ctx		*curr_ctx;
7962306a36Sopenharmony_ci	struct device		*dev;
8062306a36Sopenharmony_ci	struct clk		*vdoa_clk;
8162306a36Sopenharmony_ci	void __iomem		*regs;
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistruct vdoa_q_data {
8562306a36Sopenharmony_ci	unsigned int	width;
8662306a36Sopenharmony_ci	unsigned int	height;
8762306a36Sopenharmony_ci	unsigned int	bytesperline;
8862306a36Sopenharmony_ci	unsigned int	sizeimage;
8962306a36Sopenharmony_ci	u32		pixelformat;
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistruct vdoa_ctx {
9362306a36Sopenharmony_ci	struct vdoa_data	*vdoa;
9462306a36Sopenharmony_ci	struct completion	completion;
9562306a36Sopenharmony_ci	struct vdoa_q_data	q_data[2];
9662306a36Sopenharmony_ci	unsigned int		submitted_job;
9762306a36Sopenharmony_ci	unsigned int		completed_job;
9862306a36Sopenharmony_ci};
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic irqreturn_t vdoa_irq_handler(int irq, void *data)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct vdoa_data *vdoa = data;
10362306a36Sopenharmony_ci	struct vdoa_ctx *curr_ctx;
10462306a36Sopenharmony_ci	u32 val;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* Disable interrupts */
10762306a36Sopenharmony_ci	writel(0, vdoa->regs + VDOAIE);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	curr_ctx = vdoa->curr_ctx;
11062306a36Sopenharmony_ci	if (!curr_ctx) {
11162306a36Sopenharmony_ci		dev_warn(vdoa->dev,
11262306a36Sopenharmony_ci			"Instance released before the end of transaction\n");
11362306a36Sopenharmony_ci		return IRQ_HANDLED;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	val = readl(vdoa->regs + VDOAIST);
11762306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAIST);
11862306a36Sopenharmony_ci	if (val & VDOAIST_TERR) {
11962306a36Sopenharmony_ci		val = readl(vdoa->regs + VDOASR) & VDOASR_ERRW;
12062306a36Sopenharmony_ci		dev_err(vdoa->dev, "AXI %s error\n", val ? "write" : "read");
12162306a36Sopenharmony_ci	} else if (!(val & VDOAIST_EOT)) {
12262306a36Sopenharmony_ci		dev_warn(vdoa->dev, "Spurious interrupt\n");
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	curr_ctx->completed_job++;
12562306a36Sopenharmony_ci	complete(&curr_ctx->completion);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return IRQ_HANDLED;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ciint vdoa_wait_for_completion(struct vdoa_ctx *ctx)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct vdoa_data *vdoa = ctx->vdoa;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (ctx->submitted_job == ctx->completed_job)
13562306a36Sopenharmony_ci		return 0;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	if (!wait_for_completion_timeout(&ctx->completion,
13862306a36Sopenharmony_ci					 msecs_to_jiffies(300))) {
13962306a36Sopenharmony_ci		dev_err(vdoa->dev,
14062306a36Sopenharmony_ci			"Timeout waiting for transfer result\n");
14162306a36Sopenharmony_ci		return -ETIMEDOUT;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	return 0;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ciEXPORT_SYMBOL(vdoa_wait_for_completion);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_civoid vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct vdoa_q_data *src_q_data, *dst_q_data;
15162306a36Sopenharmony_ci	struct vdoa_data *vdoa = ctx->vdoa;
15262306a36Sopenharmony_ci	u32 val;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (vdoa->curr_ctx)
15562306a36Sopenharmony_ci		vdoa_wait_for_completion(vdoa->curr_ctx);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	vdoa->curr_ctx = ctx;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	reinit_completion(&ctx->completion);
16062306a36Sopenharmony_ci	ctx->submitted_job++;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	src_q_data = &ctx->q_data[V4L2_M2M_SRC];
16362306a36Sopenharmony_ci	dst_q_data = &ctx->q_data[V4L2_M2M_DST];
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* Progressive, no sync, 1 frame per run */
16662306a36Sopenharmony_ci	if (dst_q_data->pixelformat == V4L2_PIX_FMT_YUYV)
16762306a36Sopenharmony_ci		val = VDOAC_PFS;
16862306a36Sopenharmony_ci	else
16962306a36Sopenharmony_ci		val = 0;
17062306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAC);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	writel(dst_q_data->height << 16 | dst_q_data->width,
17362306a36Sopenharmony_ci	       vdoa->regs + VDOAFP);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	val = dst;
17662306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAIEBA00);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	writel(src_q_data->bytesperline << 16 | dst_q_data->bytesperline,
17962306a36Sopenharmony_ci	       vdoa->regs + VDOASL);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (dst_q_data->pixelformat == V4L2_PIX_FMT_NV12 ||
18262306a36Sopenharmony_ci	    dst_q_data->pixelformat == V4L2_PIX_FMT_NV21)
18362306a36Sopenharmony_ci		val = dst_q_data->bytesperline * dst_q_data->height;
18462306a36Sopenharmony_ci	else
18562306a36Sopenharmony_ci		val = 0;
18662306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAIUBO);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	val = src;
18962306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAVEBA0);
19062306a36Sopenharmony_ci	val = round_up(src_q_data->bytesperline * src_q_data->height, 4096);
19162306a36Sopenharmony_ci	writel(val, vdoa->regs + VDOAVUBO);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* Enable interrupts and start transfer */
19462306a36Sopenharmony_ci	writel(VDOAIE_EITERR | VDOAIE_EIEOT, vdoa->regs + VDOAIE);
19562306a36Sopenharmony_ci	writel(VDOASRR_START, vdoa->regs + VDOASRR);
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ciEXPORT_SYMBOL(vdoa_device_run);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistruct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct vdoa_ctx *ctx;
20262306a36Sopenharmony_ci	int err;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
20562306a36Sopenharmony_ci	if (!ctx)
20662306a36Sopenharmony_ci		return NULL;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	err = clk_prepare_enable(vdoa->vdoa_clk);
20962306a36Sopenharmony_ci	if (err) {
21062306a36Sopenharmony_ci		kfree(ctx);
21162306a36Sopenharmony_ci		return NULL;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	init_completion(&ctx->completion);
21562306a36Sopenharmony_ci	ctx->vdoa = vdoa;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return ctx;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ciEXPORT_SYMBOL(vdoa_context_create);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_civoid vdoa_context_destroy(struct vdoa_ctx *ctx)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	struct vdoa_data *vdoa = ctx->vdoa;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (vdoa->curr_ctx == ctx) {
22662306a36Sopenharmony_ci		vdoa_wait_for_completion(vdoa->curr_ctx);
22762306a36Sopenharmony_ci		vdoa->curr_ctx = NULL;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	clk_disable_unprepare(vdoa->vdoa_clk);
23162306a36Sopenharmony_ci	kfree(ctx);
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ciEXPORT_SYMBOL(vdoa_context_destroy);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ciint vdoa_context_configure(struct vdoa_ctx *ctx,
23662306a36Sopenharmony_ci			   unsigned int width, unsigned int height,
23762306a36Sopenharmony_ci			   u32 pixelformat)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	struct vdoa_q_data *src_q_data;
24062306a36Sopenharmony_ci	struct vdoa_q_data *dst_q_data;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (width < 16 || width  > 8192 || width % 16 != 0 ||
24362306a36Sopenharmony_ci	    height < 16 || height > 4096 || height % 16 != 0)
24462306a36Sopenharmony_ci		return -EINVAL;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (pixelformat != V4L2_PIX_FMT_YUYV &&
24762306a36Sopenharmony_ci	    pixelformat != V4L2_PIX_FMT_NV12)
24862306a36Sopenharmony_ci		return -EINVAL;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	/* If no context is passed, only check if the format is valid */
25162306a36Sopenharmony_ci	if (!ctx)
25262306a36Sopenharmony_ci		return 0;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	src_q_data = &ctx->q_data[V4L2_M2M_SRC];
25562306a36Sopenharmony_ci	dst_q_data = &ctx->q_data[V4L2_M2M_DST];
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	src_q_data->width = width;
25862306a36Sopenharmony_ci	src_q_data->height = height;
25962306a36Sopenharmony_ci	src_q_data->bytesperline = width;
26062306a36Sopenharmony_ci	src_q_data->sizeimage =
26162306a36Sopenharmony_ci		round_up(src_q_data->bytesperline * height, 4096) +
26262306a36Sopenharmony_ci		src_q_data->bytesperline * height / 2;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	dst_q_data->width = width;
26562306a36Sopenharmony_ci	dst_q_data->height = height;
26662306a36Sopenharmony_ci	dst_q_data->pixelformat = pixelformat;
26762306a36Sopenharmony_ci	switch (pixelformat) {
26862306a36Sopenharmony_ci	case V4L2_PIX_FMT_YUYV:
26962306a36Sopenharmony_ci		dst_q_data->bytesperline = width * 2;
27062306a36Sopenharmony_ci		dst_q_data->sizeimage = dst_q_data->bytesperline * height;
27162306a36Sopenharmony_ci		break;
27262306a36Sopenharmony_ci	case V4L2_PIX_FMT_NV12:
27362306a36Sopenharmony_ci	default:
27462306a36Sopenharmony_ci		dst_q_data->bytesperline = width;
27562306a36Sopenharmony_ci		dst_q_data->sizeimage =
27662306a36Sopenharmony_ci			dst_q_data->bytesperline * height * 3 / 2;
27762306a36Sopenharmony_ci		break;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	return 0;
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ciEXPORT_SYMBOL(vdoa_context_configure);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic int vdoa_probe(struct platform_device *pdev)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	struct vdoa_data *vdoa;
28762306a36Sopenharmony_ci	int ret;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
29062306a36Sopenharmony_ci	if (ret) {
29162306a36Sopenharmony_ci		dev_err(&pdev->dev, "DMA enable failed\n");
29262306a36Sopenharmony_ci		return ret;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	vdoa = devm_kzalloc(&pdev->dev, sizeof(*vdoa), GFP_KERNEL);
29662306a36Sopenharmony_ci	if (!vdoa)
29762306a36Sopenharmony_ci		return -ENOMEM;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	vdoa->dev = &pdev->dev;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	vdoa->vdoa_clk = devm_clk_get(vdoa->dev, NULL);
30262306a36Sopenharmony_ci	if (IS_ERR(vdoa->vdoa_clk)) {
30362306a36Sopenharmony_ci		dev_err(vdoa->dev, "Failed to get clock\n");
30462306a36Sopenharmony_ci		return PTR_ERR(vdoa->vdoa_clk);
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	vdoa->regs = devm_platform_ioremap_resource(pdev, 0);
30862306a36Sopenharmony_ci	if (IS_ERR(vdoa->regs))
30962306a36Sopenharmony_ci		return PTR_ERR(vdoa->regs);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	ret = platform_get_irq(pdev, 0);
31262306a36Sopenharmony_ci	if (ret < 0)
31362306a36Sopenharmony_ci		return ret;
31462306a36Sopenharmony_ci	ret = devm_request_threaded_irq(&pdev->dev, ret, NULL,
31562306a36Sopenharmony_ci					vdoa_irq_handler, IRQF_ONESHOT,
31662306a36Sopenharmony_ci					"vdoa", vdoa);
31762306a36Sopenharmony_ci	if (ret < 0) {
31862306a36Sopenharmony_ci		dev_err(vdoa->dev, "Failed to get irq\n");
31962306a36Sopenharmony_ci		return ret;
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	platform_set_drvdata(pdev, vdoa);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return 0;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic const struct of_device_id vdoa_dt_ids[] = {
32862306a36Sopenharmony_ci	{ .compatible = "fsl,imx6q-vdoa" },
32962306a36Sopenharmony_ci	{}
33062306a36Sopenharmony_ci};
33162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, vdoa_dt_ids);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic struct platform_driver vdoa_driver = {
33462306a36Sopenharmony_ci	.probe		= vdoa_probe,
33562306a36Sopenharmony_ci	.driver		= {
33662306a36Sopenharmony_ci		.name	= VDOA_NAME,
33762306a36Sopenharmony_ci		.of_match_table = vdoa_dt_ids,
33862306a36Sopenharmony_ci	},
33962306a36Sopenharmony_ci};
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cimodule_platform_driver(vdoa_driver);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ciMODULE_DESCRIPTION("Video Data Order Adapter");
34462306a36Sopenharmony_ciMODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>");
34562306a36Sopenharmony_ciMODULE_ALIAS("platform:imx-vdoa");
34662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
347