/* * Copyright (C) 2012-2013 Freescale Semiconductor, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #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 = strict_strtoul(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_request_and_ioremap(&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");