diff options
Diffstat (limited to 'drivers/mxc/ipu3/vdoa.c')
-rw-r--r-- | drivers/mxc/ipu3/vdoa.c | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/drivers/mxc/ipu3/vdoa.c b/drivers/mxc/ipu3/vdoa.c new file mode 100644 index 000000000000..e727182a94c0 --- /dev/null +++ b/drivers/mxc/ipu3/vdoa.c @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2012-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/genalloc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "vdoa.h" +/* 6band(3field* double buffer) * (width*2) * bandline(8) + = 6x1024x2x8 = 96k or 72k(1.5byte) */ +#define MAX_VDOA_IRAM_SIZE (1024*96) +#define VDOA_IRAM_SIZE (1024*72) + +#define VDOAC_BAND_HEIGHT_32LINES (32) +#define VDOAC_BAND_HEIGHT_16LINES (16) +#define VDOAC_BAND_HEIGHT_8LINES (8) +#define VDOAC_THREE_FRAMES (0x1 << 2) +#define VDOAC_SYNC_BAND_MODE (0x1 << 3) +#define VDOAC_SCAN_ORDER_INTERLACED (0x1 << 4) +#define VDOAC_PFS_YUYV (0x1 << 5) +#define VDOAC_IPU_SEL_1 (0x1 << 6) +#define VDOAFP_FH_MASK (0x1FFF) +#define VDOAFP_FH_SHIFT (16) +#define VDOAFP_FW_MASK (0x3FFF) +#define VDOAFP_FW_SHIFT (0) +#define VDOASL_VSLY_MASK (0x3FFF) +#define VDOASL_VSLY_SHIFT (16) +#define VDOASL_ISLY_MASK (0x7FFF) +#define VDOASL_ISLY_SHIFT (0) +#define VDOASRR_START_XFER (0x2) +#define VDOASRR_SWRST (0x1) +#define VDOAIEIST_TRANSFER_ERR (0x2) +#define VDOAIEIST_TRANSFER_END (0x1) + +#define VDOAC (0x0) /* Control Register */ +#define VDOASRR (0x4) /* Start and Reset Register */ +#define VDOAIE (0x8) /* Interrupt Enable Register */ +#define VDOAIST (0xc) /* Interrupt Status Register */ +#define VDOAFP (0x10) /* Frame Parameters Register */ +#define VDOAIEBA00 (0x14) /* External Buffer n Frame m Address Register */ +#define VDOAIEBA01 (0x18) /* External Buffer n Frame m Address Register */ +#define VDOAIEBA02 (0x1c) /* External Buffer n Frame m Address Register */ +#define VDOAIEBA10 (0x20) /* External Buffer n Frame m Address Register */ +#define VDOAIEBA11 (0x24) /* External Buffer n Frame m Address Register */ +#define VDOAIEBA12 (0x28) /* External Buffer n Frame m Address Register */ +#define VDOASL (0x2c) /* IPU Stride Line Register */ +#define VDOAIUBO (0x30) /* IPU Chroma Buffer Offset Register */ +#define VDOAVEBA0 (0x34) /* External Buffer m Address Register */ +#define VDOAVEBA1 (0x38) /* External Buffer m Address Register */ +#define VDOAVEBA2 (0x3c) /* External Buffer m Address Register */ +#define VDOAVUBO (0x40) /* VPU Chroma Buffer Offset */ +#define VDOASR (0x44) /* Status Register */ +#define VDOATD (0x48) /* Test Debug Register */ + + +enum { + VDOA_INIT = 0x1, + VDOA_GET = 0x2, + VDOA_SETUP = 0x4, + VDOA_GET_OBUF = 0x8, + VDOA_START = 0x10, + VDOA_INIRQ = 0x20, + VDOA_STOP = 0x40, + VDOA_PUT = VDOA_INIT, +}; + +enum { + VDOA_NULL = 0, + VDOA_FRAME = 1, + VDOA_PREV_FIELD = 2, + VDOA_CURR_FIELD = 3, + VDOA_NEXT_FIELD = 4, +}; + +#define CHECK_STATE(expect, retcode) \ +do { \ + if (!((expect) & vdoa->state)) { \ + dev_err(vdoa->dev, "ERR: %s state:0x%x, expect:0x%x.\n",\ + __func__, vdoa->state, (expect)); \ + retcode; \ + } \ +} while (0) + +#define CHECK_NULL_PTR(ptr) \ +do { \ + pr_debug("vdoa_ptr:0x%p in %s state:0x%x.\n", \ + vdoa, __func__, vdoa->state); \ + if (NULL == (ptr)) { \ + pr_err("ERR vdoa: %s state:0x%x null ptr.\n", \ + __func__, vdoa->state); \ + } \ +} while (0) + +struct vdoa_info { + int state; + struct device *dev; + struct clk *vdoa_clk; + void __iomem *reg_base; + struct gen_pool *iram_pool; + unsigned long iram_base; + unsigned long iram_paddr; + int irq; + int field; + struct completion comp; +}; + +static struct vdoa_info *g_vdoa; +static unsigned long iram_size; +static DEFINE_MUTEX(vdoa_lock); + +static inline void vdoa_read_register(struct vdoa_info *vdoa, + u32 reg, u32 *val) +{ + *val = ioread32(vdoa->reg_base + reg); + dev_dbg(vdoa->dev, "read_reg:0x%02x, val:0x%08x.\n", reg, *val); +} + +static inline void vdoa_write_register(struct vdoa_info *vdoa, + u32 reg, u32 val) +{ + iowrite32(val, vdoa->reg_base + reg); + dev_dbg(vdoa->dev, "\t\twrite_reg:0x%02x, val:0x%08x.\n", reg, val); +} + +static void dump_registers(struct vdoa_info *vdoa) +{ + int i; + u32 data; + + for (i = VDOAC; i < VDOATD; i += 4) + vdoa_read_register(vdoa, i, &data); +} + +int vdoa_setup(vdoa_handle_t handle, struct vdoa_params *params) +{ + int band_size; + int total_band_size = 0; + int ipu_stride; + u32 data; + struct vdoa_info *vdoa = (struct vdoa_info *)handle; + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_GET | VDOA_GET_OBUF | VDOA_STOP, return -EINVAL); + if (VDOA_GET == vdoa->state) { + dev_dbg(vdoa->dev, "w:%d, h:%d.\n", + params->width, params->height); + data = (params->band_lines == VDOAC_BAND_HEIGHT_32LINES) ? 2 : + ((params->band_lines == VDOAC_BAND_HEIGHT_16LINES) ? + 1 : 0); + data |= params->scan_order ? VDOAC_SCAN_ORDER_INTERLACED : 0; + data |= params->band_mode ? VDOAC_SYNC_BAND_MODE : 0; + data |= params->pfs ? VDOAC_PFS_YUYV : 0; + data |= params->ipu_num ? VDOAC_IPU_SEL_1 : 0; + vdoa_write_register(vdoa, VDOAC, data); + + data = ((params->width & VDOAFP_FW_MASK) << VDOAFP_FW_SHIFT) | + ((params->height & VDOAFP_FH_MASK) << VDOAFP_FH_SHIFT); + vdoa_write_register(vdoa, VDOAFP, data); + + ipu_stride = params->pfs ? params->width << 1 : params->width; + data = ((params->vpu_stride & VDOASL_VSLY_MASK) << + VDOASL_VSLY_SHIFT) | + ((ipu_stride & VDOASL_ISLY_MASK) << VDOASL_ISLY_SHIFT); + vdoa_write_register(vdoa, VDOASL, data); + + dev_dbg(vdoa->dev, "band_mode:%d, band_line:%d, base:0x%lx.\n", + params->band_mode, params->band_lines, vdoa->iram_paddr); + } + /* + * band size = (luma_per_line + chroma_per_line) * bandLines + * = width * (3/2 or 2) * bandLines + * double buffer mode used. + */ + if (params->pfs) + band_size = (params->width << 1) * params->band_lines; + else + band_size = ((params->width * 3) >> 1) * + params->band_lines; + if (params->interlaced) { + total_band_size = 6 * band_size; /* 3 frames*double buffer */ + if (iram_size < total_band_size) { + dev_err(vdoa->dev, "iram_size:0x%lx is smaller than " + "request:0x%x!\n", iram_size, total_band_size); + return -EINVAL; + } + if (params->vfield_buf.prev_veba) { + if (params->band_mode) { + vdoa_write_register(vdoa, VDOAIEBA00, + vdoa->iram_paddr); + vdoa_write_register(vdoa, VDOAIEBA10, + vdoa->iram_paddr + band_size); + } else + vdoa_write_register(vdoa, VDOAIEBA00, + params->ieba0); + vdoa_write_register(vdoa, VDOAVEBA0, + params->vfield_buf.prev_veba); + vdoa->field = VDOA_PREV_FIELD; + } + if (params->vfield_buf.cur_veba) { + if (params->band_mode) { + vdoa_write_register(vdoa, VDOAIEBA01, + vdoa->iram_paddr + band_size * 2); + vdoa_write_register(vdoa, VDOAIEBA11, + vdoa->iram_paddr + band_size * 3); + } else + vdoa_write_register(vdoa, VDOAIEBA01, + params->ieba1); + vdoa_write_register(vdoa, VDOAVEBA1, + params->vfield_buf.cur_veba); + vdoa->field = VDOA_CURR_FIELD; + } + if (params->vfield_buf.next_veba) { + if (params->band_mode) { + vdoa_write_register(vdoa, VDOAIEBA02, + vdoa->iram_paddr + band_size * 4); + vdoa_write_register(vdoa, VDOAIEBA12, + vdoa->iram_paddr + band_size * 5); + } else + vdoa_write_register(vdoa, VDOAIEBA02, + params->ieba2); + vdoa_write_register(vdoa, VDOAVEBA2, + params->vfield_buf.next_veba); + vdoa->field = VDOA_NEXT_FIELD; + vdoa_read_register(vdoa, VDOAC, &data); + data |= VDOAC_THREE_FRAMES; + vdoa_write_register(vdoa, VDOAC, data); + } + + if (!params->pfs) + vdoa_write_register(vdoa, VDOAIUBO, + params->width * params->band_lines); + vdoa_write_register(vdoa, VDOAVUBO, + params->vfield_buf.vubo); + dev_dbg(vdoa->dev, "total band_size:0x%x.\n", band_size*6); + } else if (params->band_mode) { + /* used for progressive frame resize on PrP channel */ + BUG(); /* currently not support */ + /* progressvie frame: band mode */ + vdoa_write_register(vdoa, VDOAIEBA00, vdoa->iram_paddr); + vdoa_write_register(vdoa, VDOAIEBA10, + vdoa->iram_paddr + band_size); + if (!params->pfs) + vdoa_write_register(vdoa, VDOAIUBO, + params->width * params->band_lines); + dev_dbg(vdoa->dev, "total band_size:0x%x\n", band_size*2); + } else { + /* progressive frame: mem->mem, non-band mode */ + vdoa->field = VDOA_FRAME; + vdoa_write_register(vdoa, VDOAVEBA0, params->vframe_buf.veba); + vdoa_write_register(vdoa, VDOAVUBO, params->vframe_buf.vubo); + vdoa_write_register(vdoa, VDOAIEBA00, params->ieba0); + if (!params->pfs) + /* note: iubo is relative value, based on ieba0 */ + vdoa_write_register(vdoa, VDOAIUBO, + params->width * params->height); + } + vdoa->state = VDOA_SETUP; + return 0; +} + +void vdoa_get_output_buf(vdoa_handle_t handle, struct vdoa_ipu_buf *buf) +{ + u32 data; + struct vdoa_info *vdoa = (struct vdoa_info *)handle; + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_SETUP, return); + vdoa->state = VDOA_GET_OBUF; + memset(buf, 0, sizeof(*buf)); + + vdoa_read_register(vdoa, VDOAC, &data); + switch (vdoa->field) { + case VDOA_FRAME: + case VDOA_PREV_FIELD: + vdoa_read_register(vdoa, VDOAIEBA00, &buf->ieba0); + if (data & VDOAC_SYNC_BAND_MODE) + vdoa_read_register(vdoa, VDOAIEBA10, &buf->ieba1); + break; + case VDOA_CURR_FIELD: + vdoa_read_register(vdoa, VDOAIEBA01, &buf->ieba0); + vdoa_read_register(vdoa, VDOAIEBA11, &buf->ieba1); + break; + case VDOA_NEXT_FIELD: + vdoa_read_register(vdoa, VDOAIEBA02, &buf->ieba0); + vdoa_read_register(vdoa, VDOAIEBA12, &buf->ieba1); + break; + default: + BUG(); + break; + } + if (!(data & VDOAC_PFS_YUYV)) + vdoa_read_register(vdoa, VDOAIUBO, &buf->iubo); +} + +int vdoa_start(vdoa_handle_t handle, int timeout_ms) +{ + int ret; + struct vdoa_info *vdoa = (struct vdoa_info *)handle; + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_GET_OBUF, return -EINVAL); + vdoa->state = VDOA_START; + init_completion(&vdoa->comp); + vdoa_write_register(vdoa, VDOAIST, + VDOAIEIST_TRANSFER_ERR | VDOAIEIST_TRANSFER_END); + vdoa_write_register(vdoa, VDOAIE, + VDOAIEIST_TRANSFER_ERR | VDOAIEIST_TRANSFER_END); + + enable_irq(vdoa->irq); + vdoa_write_register(vdoa, VDOASRR, VDOASRR_START_XFER); + dump_registers(vdoa); + + ret = wait_for_completion_timeout(&vdoa->comp, + msecs_to_jiffies(timeout_ms)); + + return ret > 0 ? 0 : -ETIMEDOUT; +} + +void vdoa_stop(vdoa_handle_t handle) +{ + struct vdoa_info *vdoa = (struct vdoa_info *)handle; + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_GET | VDOA_START | VDOA_INIRQ, return); + vdoa->state = VDOA_STOP; + + disable_irq(vdoa->irq); + + vdoa_write_register(vdoa, VDOASRR, VDOASRR_SWRST); +} + +void vdoa_get_handle(vdoa_handle_t *handle) +{ + struct vdoa_info *vdoa = g_vdoa; + + CHECK_NULL_PTR(handle); + *handle = (vdoa_handle_t *)NULL; + CHECK_STATE(VDOA_INIT, return); + mutex_lock(&vdoa_lock); + clk_prepare_enable(vdoa->vdoa_clk); + vdoa->state = VDOA_GET; + vdoa->field = VDOA_NULL; + vdoa_write_register(vdoa, VDOASRR, VDOASRR_SWRST); + + *handle = (vdoa_handle_t *)vdoa; +} + +void vdoa_put_handle(vdoa_handle_t *handle) +{ + struct vdoa_info *vdoa = (struct vdoa_info *)(*handle); + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_STOP, return); + if (vdoa != g_vdoa) + BUG(); + + clk_disable_unprepare(vdoa->vdoa_clk); + vdoa->state = VDOA_PUT; + *handle = (vdoa_handle_t *)NULL; + mutex_unlock(&vdoa_lock); +} + +static irqreturn_t vdoa_irq_handler(int irq, void *data) +{ + u32 status, mask, val; + struct vdoa_info *vdoa = data; + + CHECK_NULL_PTR(vdoa); + CHECK_STATE(VDOA_START, return IRQ_HANDLED); + vdoa->state = VDOA_INIRQ; + vdoa_read_register(vdoa, VDOAIST, &status); + vdoa_read_register(vdoa, VDOAIE, &mask); + val = status & mask; + vdoa_write_register(vdoa, VDOAIST, val); + if (VDOAIEIST_TRANSFER_ERR & val) + dev_err(vdoa->dev, "vdoa Transfer err irq!\n"); + if (VDOAIEIST_TRANSFER_END & val) + dev_dbg(vdoa->dev, "vdoa Transfer end irq!\n"); + if (0 == val) { + dev_err(vdoa->dev, "vdoa unknown irq!\n"); + BUG(); + } + + complete(&vdoa->comp); + return IRQ_HANDLED; +} + +/* IRAM Size in Kbytes, example:vdoa_iram_size=64, 64KBytes */ +static int __init vdoa_iram_size_setup(char *options) +{ + int ret; + + ret = kstrtoul(options, 0, &iram_size); + if (ret) + iram_size = 0; + else + iram_size *= SZ_1K; + + return 1; +} +__setup("vdoa_iram_size=", vdoa_iram_size_setup); + +static const struct of_device_id imx_vdoa_dt_ids[] = { + { .compatible = "fsl,imx6q-vdoa", }, + { /* sentinel */ } +}; + +static int vdoa_probe(struct platform_device *pdev) +{ + int ret; + struct vdoa_info *vdoa; + struct resource *res; + struct resource *res_irq; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "can't get device resources\n"); + return -ENOENT; + } + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) { + dev_err(dev, "failed to get irq resource\n"); + return -ENOENT; + } + + vdoa = devm_kzalloc(dev, sizeof(struct vdoa_info), GFP_KERNEL); + if (!vdoa) + return -ENOMEM; + vdoa->dev = dev; + + vdoa->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (!vdoa->reg_base) + return -EBUSY; + + vdoa->irq = res_irq->start; + ret = devm_request_irq(dev, vdoa->irq, vdoa_irq_handler, 0, + "vdoa", vdoa); + if (ret) { + dev_err(dev, "can't claim irq %d\n", vdoa->irq); + return ret; + } + disable_irq(vdoa->irq); + + vdoa->vdoa_clk = devm_clk_get(dev, NULL); + if (IS_ERR(vdoa->vdoa_clk)) { + dev_err(dev, "failed to get vdoa_clk\n"); + return PTR_ERR(vdoa->vdoa_clk); + } + + vdoa->iram_pool = of_get_named_gen_pool(np, "iram", 0); + if (!vdoa->iram_pool) { + dev_err(&pdev->dev, "iram pool not available\n"); + return -ENOMEM; + } + + if ((iram_size == 0) || (iram_size > MAX_VDOA_IRAM_SIZE)) + iram_size = VDOA_IRAM_SIZE; + + vdoa->iram_base = gen_pool_alloc(vdoa->iram_pool, iram_size); + if (!vdoa->iram_base) { + dev_err(&pdev->dev, "unable to alloc iram\n"); + return -ENOMEM; + } + + vdoa->iram_paddr = gen_pool_virt_to_phys(vdoa->iram_pool, + vdoa->iram_base); + + dev_dbg(dev, "iram_base:0x%lx,iram_paddr:0x%lx,size:0x%lx\n", + vdoa->iram_base, vdoa->iram_paddr, iram_size); + + vdoa->state = VDOA_INIT; + dev_set_drvdata(dev, vdoa); + g_vdoa = vdoa; + dev_info(dev, "i.MX Video Data Order Adapter(VDOA) driver probed\n"); + return 0; +} + +static int vdoa_remove(struct platform_device *pdev) +{ + struct vdoa_info *vdoa = dev_get_drvdata(&pdev->dev); + + gen_pool_free(vdoa->iram_pool, vdoa->iram_base, iram_size); + kfree(vdoa); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver vdoa_driver = { + .driver = { + .name = "mxc_vdoa", + .of_match_table = imx_vdoa_dt_ids, + }, + .probe = vdoa_probe, + .remove = vdoa_remove, +}; + +static int __init vdoa_init(void) +{ + int err; + + err = platform_driver_register(&vdoa_driver); + if (err) { + pr_err("vdoa_driver register failed\n"); + return -ENODEV; + } + return 0; +} + +static void __exit vdoa_cleanup(void) +{ + platform_driver_unregister(&vdoa_driver); +} + +module_init(vdoa_init); +module_exit(vdoa_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX Video Data Order Adapter(VDOA) driver"); +MODULE_LICENSE("GPL"); |