// SPDX-License-Identifier: GPL-2.0-only 
 | 
/* 
 | 
 * i.MX6 Video Data Order Adapter (VDOA) 
 | 
 * 
 | 
 * Copyright (C) 2014 Philipp Zabel 
 | 
 * Copyright (C) 2016 Pengutronix, Michael Tretter <kernel@pengutronix.de> 
 | 
 */ 
 | 
  
 | 
#include <linux/clk.h> 
 | 
#include <linux/device.h> 
 | 
#include <linux/interrupt.h> 
 | 
#include <linux/module.h> 
 | 
#include <linux/mod_devicetable.h> 
 | 
#include <linux/dma-mapping.h> 
 | 
#include <linux/platform_device.h> 
 | 
#include <linux/videodev2.h> 
 | 
#include <linux/slab.h> 
 | 
  
 | 
#include "imx-vdoa.h" 
 | 
  
 | 
#define VDOA_NAME "imx-vdoa" 
 | 
  
 | 
#define VDOAC        0x00 
 | 
#define VDOASRR        0x04 
 | 
#define VDOAIE        0x08 
 | 
#define VDOAIST        0x0c 
 | 
#define VDOAFP        0x10 
 | 
#define VDOAIEBA00    0x14 
 | 
#define VDOAIEBA01    0x18 
 | 
#define VDOAIEBA02    0x1c 
 | 
#define VDOAIEBA10    0x20 
 | 
#define VDOAIEBA11    0x24 
 | 
#define VDOAIEBA12    0x28 
 | 
#define VDOASL        0x2c 
 | 
#define VDOAIUBO    0x30 
 | 
#define VDOAVEBA0    0x34 
 | 
#define VDOAVEBA1    0x38 
 | 
#define VDOAVEBA2    0x3c 
 | 
#define VDOAVUBO    0x40 
 | 
#define VDOASR        0x44 
 | 
  
 | 
#define VDOAC_ISEL        BIT(6) 
 | 
#define VDOAC_PFS        BIT(5) 
 | 
#define VDOAC_SO        BIT(4) 
 | 
#define VDOAC_SYNC        BIT(3) 
 | 
#define VDOAC_NF        BIT(2) 
 | 
#define VDOAC_BNDM_MASK        0x3 
 | 
#define VDOAC_BAND_HEIGHT_8    0x0 
 | 
#define VDOAC_BAND_HEIGHT_16    0x1 
 | 
#define VDOAC_BAND_HEIGHT_32    0x2 
 | 
  
 | 
#define VDOASRR_START        BIT(1) 
 | 
#define VDOASRR_SWRST        BIT(0) 
 | 
  
 | 
#define VDOAIE_EITERR        BIT(1) 
 | 
#define VDOAIE_EIEOT        BIT(0) 
 | 
  
 | 
#define VDOAIST_TERR        BIT(1) 
 | 
#define VDOAIST_EOT        BIT(0) 
 | 
  
 | 
#define VDOAFP_FH_MASK        (0x1fff << 16) 
 | 
#define VDOAFP_FW_MASK        (0x3fff) 
 | 
  
 | 
#define VDOASL_VSLY_MASK    (0x3fff << 16) 
 | 
#define VDOASL_ISLY_MASK    (0x7fff) 
 | 
  
 | 
#define VDOASR_ERRW        BIT(4) 
 | 
#define VDOASR_EOB        BIT(3) 
 | 
#define VDOASR_CURRENT_FRAME    (0x3 << 1) 
 | 
#define VDOASR_CURRENT_BUFFER    BIT(1) 
 | 
  
 | 
enum { 
 | 
    V4L2_M2M_SRC = 0, 
 | 
    V4L2_M2M_DST = 1, 
 | 
}; 
 | 
  
 | 
struct vdoa_data { 
 | 
    struct vdoa_ctx        *curr_ctx; 
 | 
    struct device        *dev; 
 | 
    struct clk        *vdoa_clk; 
 | 
    void __iomem        *regs; 
 | 
}; 
 | 
  
 | 
struct vdoa_q_data { 
 | 
    unsigned int    width; 
 | 
    unsigned int    height; 
 | 
    unsigned int    bytesperline; 
 | 
    unsigned int    sizeimage; 
 | 
    u32        pixelformat; 
 | 
}; 
 | 
  
 | 
struct vdoa_ctx { 
 | 
    struct vdoa_data    *vdoa; 
 | 
    struct completion    completion; 
 | 
    struct vdoa_q_data    q_data[2]; 
 | 
    unsigned int        submitted_job; 
 | 
    unsigned int        completed_job; 
 | 
}; 
 | 
  
 | 
static irqreturn_t vdoa_irq_handler(int irq, void *data) 
 | 
{ 
 | 
    struct vdoa_data *vdoa = data; 
 | 
    struct vdoa_ctx *curr_ctx; 
 | 
    u32 val; 
 | 
  
 | 
    /* Disable interrupts */ 
 | 
    writel(0, vdoa->regs + VDOAIE); 
 | 
  
 | 
    curr_ctx = vdoa->curr_ctx; 
 | 
    if (!curr_ctx) { 
 | 
        dev_warn(vdoa->dev, 
 | 
            "Instance released before the end of transaction\n"); 
 | 
        return IRQ_HANDLED; 
 | 
    } 
 | 
  
 | 
    val = readl(vdoa->regs + VDOAIST); 
 | 
    writel(val, vdoa->regs + VDOAIST); 
 | 
    if (val & VDOAIST_TERR) { 
 | 
        val = readl(vdoa->regs + VDOASR) & VDOASR_ERRW; 
 | 
        dev_err(vdoa->dev, "AXI %s error\n", val ? "write" : "read"); 
 | 
    } else if (!(val & VDOAIST_EOT)) { 
 | 
        dev_warn(vdoa->dev, "Spurious interrupt\n"); 
 | 
    } 
 | 
    curr_ctx->completed_job++; 
 | 
    complete(&curr_ctx->completion); 
 | 
  
 | 
    return IRQ_HANDLED; 
 | 
} 
 | 
  
 | 
int vdoa_wait_for_completion(struct vdoa_ctx *ctx) 
 | 
{ 
 | 
    struct vdoa_data *vdoa = ctx->vdoa; 
 | 
  
 | 
    if (ctx->submitted_job == ctx->completed_job) 
 | 
        return 0; 
 | 
  
 | 
    if (!wait_for_completion_timeout(&ctx->completion, 
 | 
                     msecs_to_jiffies(300))) { 
 | 
        dev_err(vdoa->dev, 
 | 
            "Timeout waiting for transfer result\n"); 
 | 
        return -ETIMEDOUT; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
EXPORT_SYMBOL(vdoa_wait_for_completion); 
 | 
  
 | 
void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src) 
 | 
{ 
 | 
    struct vdoa_q_data *src_q_data, *dst_q_data; 
 | 
    struct vdoa_data *vdoa = ctx->vdoa; 
 | 
    u32 val; 
 | 
  
 | 
    if (vdoa->curr_ctx) 
 | 
        vdoa_wait_for_completion(vdoa->curr_ctx); 
 | 
  
 | 
    vdoa->curr_ctx = ctx; 
 | 
  
 | 
    reinit_completion(&ctx->completion); 
 | 
    ctx->submitted_job++; 
 | 
  
 | 
    src_q_data = &ctx->q_data[V4L2_M2M_SRC]; 
 | 
    dst_q_data = &ctx->q_data[V4L2_M2M_DST]; 
 | 
  
 | 
    /* Progressive, no sync, 1 frame per run */ 
 | 
    if (dst_q_data->pixelformat == V4L2_PIX_FMT_YUYV) 
 | 
        val = VDOAC_PFS; 
 | 
    else 
 | 
        val = 0; 
 | 
    writel(val, vdoa->regs + VDOAC); 
 | 
  
 | 
    writel(dst_q_data->height << 16 | dst_q_data->width, 
 | 
           vdoa->regs + VDOAFP); 
 | 
  
 | 
    val = dst; 
 | 
    writel(val, vdoa->regs + VDOAIEBA00); 
 | 
  
 | 
    writel(src_q_data->bytesperline << 16 | dst_q_data->bytesperline, 
 | 
           vdoa->regs + VDOASL); 
 | 
  
 | 
    if (dst_q_data->pixelformat == V4L2_PIX_FMT_NV12 || 
 | 
        dst_q_data->pixelformat == V4L2_PIX_FMT_NV21) 
 | 
        val = dst_q_data->bytesperline * dst_q_data->height; 
 | 
    else 
 | 
        val = 0; 
 | 
    writel(val, vdoa->regs + VDOAIUBO); 
 | 
  
 | 
    val = src; 
 | 
    writel(val, vdoa->regs + VDOAVEBA0); 
 | 
    val = round_up(src_q_data->bytesperline * src_q_data->height, 4096); 
 | 
    writel(val, vdoa->regs + VDOAVUBO); 
 | 
  
 | 
    /* Enable interrupts and start transfer */ 
 | 
    writel(VDOAIE_EITERR | VDOAIE_EIEOT, vdoa->regs + VDOAIE); 
 | 
    writel(VDOASRR_START, vdoa->regs + VDOASRR); 
 | 
} 
 | 
EXPORT_SYMBOL(vdoa_device_run); 
 | 
  
 | 
struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa) 
 | 
{ 
 | 
    struct vdoa_ctx *ctx; 
 | 
    int err; 
 | 
  
 | 
    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); 
 | 
    if (!ctx) 
 | 
        return NULL; 
 | 
  
 | 
    err = clk_prepare_enable(vdoa->vdoa_clk); 
 | 
    if (err) { 
 | 
        kfree(ctx); 
 | 
        return NULL; 
 | 
    } 
 | 
  
 | 
    init_completion(&ctx->completion); 
 | 
    ctx->vdoa = vdoa; 
 | 
  
 | 
    return ctx; 
 | 
} 
 | 
EXPORT_SYMBOL(vdoa_context_create); 
 | 
  
 | 
void vdoa_context_destroy(struct vdoa_ctx *ctx) 
 | 
{ 
 | 
    struct vdoa_data *vdoa = ctx->vdoa; 
 | 
  
 | 
    if (vdoa->curr_ctx == ctx) { 
 | 
        vdoa_wait_for_completion(vdoa->curr_ctx); 
 | 
        vdoa->curr_ctx = NULL; 
 | 
    } 
 | 
  
 | 
    clk_disable_unprepare(vdoa->vdoa_clk); 
 | 
    kfree(ctx); 
 | 
} 
 | 
EXPORT_SYMBOL(vdoa_context_destroy); 
 | 
  
 | 
int vdoa_context_configure(struct vdoa_ctx *ctx, 
 | 
               unsigned int width, unsigned int height, 
 | 
               u32 pixelformat) 
 | 
{ 
 | 
    struct vdoa_q_data *src_q_data; 
 | 
    struct vdoa_q_data *dst_q_data; 
 | 
  
 | 
    if (width < 16 || width  > 8192 || width % 16 != 0 || 
 | 
        height < 16 || height > 4096 || height % 16 != 0) 
 | 
        return -EINVAL; 
 | 
  
 | 
    if (pixelformat != V4L2_PIX_FMT_YUYV && 
 | 
        pixelformat != V4L2_PIX_FMT_NV12) 
 | 
        return -EINVAL; 
 | 
  
 | 
    /* If no context is passed, only check if the format is valid */ 
 | 
    if (!ctx) 
 | 
        return 0; 
 | 
  
 | 
    src_q_data = &ctx->q_data[V4L2_M2M_SRC]; 
 | 
    dst_q_data = &ctx->q_data[V4L2_M2M_DST]; 
 | 
  
 | 
    src_q_data->width = width; 
 | 
    src_q_data->height = height; 
 | 
    src_q_data->bytesperline = width; 
 | 
    src_q_data->sizeimage = 
 | 
        round_up(src_q_data->bytesperline * height, 4096) + 
 | 
        src_q_data->bytesperline * height / 2; 
 | 
  
 | 
    dst_q_data->width = width; 
 | 
    dst_q_data->height = height; 
 | 
    dst_q_data->pixelformat = pixelformat; 
 | 
    switch (pixelformat) { 
 | 
    case V4L2_PIX_FMT_YUYV: 
 | 
        dst_q_data->bytesperline = width * 2; 
 | 
        dst_q_data->sizeimage = dst_q_data->bytesperline * height; 
 | 
        break; 
 | 
    case V4L2_PIX_FMT_NV12: 
 | 
    default: 
 | 
        dst_q_data->bytesperline = width; 
 | 
        dst_q_data->sizeimage = 
 | 
            dst_q_data->bytesperline * height * 3 / 2; 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
EXPORT_SYMBOL(vdoa_context_configure); 
 | 
  
 | 
static int vdoa_probe(struct platform_device *pdev) 
 | 
{ 
 | 
    struct vdoa_data *vdoa; 
 | 
    struct resource *res; 
 | 
    int ret; 
 | 
  
 | 
    ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); 
 | 
    if (ret) { 
 | 
        dev_err(&pdev->dev, "DMA enable failed\n"); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    vdoa = devm_kzalloc(&pdev->dev, sizeof(*vdoa), GFP_KERNEL); 
 | 
    if (!vdoa) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    vdoa->dev = &pdev->dev; 
 | 
  
 | 
    vdoa->vdoa_clk = devm_clk_get(vdoa->dev, NULL); 
 | 
    if (IS_ERR(vdoa->vdoa_clk)) { 
 | 
        dev_err(vdoa->dev, "Failed to get clock\n"); 
 | 
        return PTR_ERR(vdoa->vdoa_clk); 
 | 
    } 
 | 
  
 | 
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 
 | 
    vdoa->regs = devm_ioremap_resource(vdoa->dev, res); 
 | 
    if (IS_ERR(vdoa->regs)) 
 | 
        return PTR_ERR(vdoa->regs); 
 | 
  
 | 
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 
 | 
    if (!res) 
 | 
        return -EINVAL; 
 | 
    ret = devm_request_threaded_irq(&pdev->dev, res->start, NULL, 
 | 
                    vdoa_irq_handler, IRQF_ONESHOT, 
 | 
                    "vdoa", vdoa); 
 | 
    if (ret < 0) { 
 | 
        dev_err(vdoa->dev, "Failed to get irq\n"); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    platform_set_drvdata(pdev, vdoa); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vdoa_remove(struct platform_device *pdev) 
 | 
{ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct of_device_id vdoa_dt_ids[] = { 
 | 
    { .compatible = "fsl,imx6q-vdoa" }, 
 | 
    {} 
 | 
}; 
 | 
MODULE_DEVICE_TABLE(of, vdoa_dt_ids); 
 | 
  
 | 
static struct platform_driver vdoa_driver = { 
 | 
    .probe        = vdoa_probe, 
 | 
    .remove        = vdoa_remove, 
 | 
    .driver        = { 
 | 
        .name    = VDOA_NAME, 
 | 
        .of_match_table = vdoa_dt_ids, 
 | 
    }, 
 | 
}; 
 | 
  
 | 
module_platform_driver(vdoa_driver); 
 | 
  
 | 
MODULE_DESCRIPTION("Video Data Order Adapter"); 
 | 
MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); 
 | 
MODULE_ALIAS("platform:imx-vdoa"); 
 | 
MODULE_LICENSE("GPL"); 
 |