diff options
Diffstat (limited to 'drivers/media/video/samsung/fimc')
-rw-r--r-- | drivers/media/video/samsung/fimc/Kconfig | 32 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/Makefile | 12 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/csis.c | 431 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/csis.h | 42 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc.h | 707 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_capture.c | 1733 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_dev.c | 1688 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_output.c | 2563 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_overlay.c | 312 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_regs.c | 1801 | ||||
-rw-r--r-- | drivers/media/video/samsung/fimc/fimc_v4l2.c | 296 |
11 files changed, 9617 insertions, 0 deletions
diff --git a/drivers/media/video/samsung/fimc/Kconfig b/drivers/media/video/samsung/fimc/Kconfig new file mode 100644 index 0000000..c9eae69 --- /dev/null +++ b/drivers/media/video/samsung/fimc/Kconfig @@ -0,0 +1,32 @@ +config VIDEO_FIMC + bool "Samsung Camera Interface (FIMC) driver" + depends on VIDEO_SAMSUNG && ARCH_S5PV210 + default n + help + This is a video4linux driver for Samsung FIMC device. + +choice +depends on VIDEO_FIMC +prompt "Select CSC Range config" +default VIDEO_FIMC_RANGE_NARROW +config VIDEO_FIMC_RANGE_NARROW + bool "Narrow" + depends on VIDEO_FIMC && ARCH_S5PV210 + ---help--- + RGB <-> YUV Color Conversion Narrow Range Equation + +config VIDEO_FIMC_RANGE_WIDE + bool "Wide" + depends on VIDEO_FIMC && ARCH_S5PV210 + ---help--- + RGB <-> YUV Color Conversion Wide Range Equation +endchoice + +config VIDEO_FIMC_DEBUG + bool "FIMC driver debug messages" + depends on VIDEO_FIMC + +config VIDEO_FIMC_MIPI + bool "MIPI-CSI2 Slave Interface support" + depends on VIDEO_FIMC && ARCH_S5PV210 + default y diff --git a/drivers/media/video/samsung/fimc/Makefile b/drivers/media/video/samsung/fimc/Makefile new file mode 100644 index 0000000..4f6e5c9 --- /dev/null +++ b/drivers/media/video/samsung/fimc/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_VIDEO_FIMC) += fimc_dev.o fimc_v4l2.o fimc_capture.o fimc_output.o fimc_overlay.o fimc_regs.o +obj-$(CONFIG_VIDEO_FIMC_MIPI) += csis.o + +ifeq ($(CONFIG_CPU_S5PV210),y) +EXTRA_CFLAGS += -DCONFIG_MIPI_CSI_ADV_FEATURE +endif + +EXTRA_CFLAGS += -Idrivers/media/video + +ifeq ($(CONFIG_VIDEO_FIMC_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/media/video/samsung/fimc/csis.c b/drivers/media/video/samsung/fimc/csis.c new file mode 100644 index 0000000..f512d10 --- /dev/null +++ b/drivers/media/video/samsung/fimc/csis.c @@ -0,0 +1,431 @@ +/* linux/drivers/media/video/samsung/csis.c + * + * Copyright (c) 2010 Samsung Electronics Co,. Ltd. + * http://www.samsung.com/ + * + * MIPI-CSI2 Support file for FIMC driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <linux/slab.h> + +#include <linux/io.h> +#include <linux/memory.h> +#include <plat/clock.h> +#include <plat/regs-csis.h> +#include <plat/csis.h> +#include "csis.h" + +static struct s3c_csis_info *s3c_csis; + +static struct s3c_platform_csis *to_csis_plat(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + return (struct s3c_platform_csis *) pdev->dev.platform_data; +} + +static int s3c_csis_set_info(void) +{ + s3c_csis = (struct s3c_csis_info *) \ + kmalloc(sizeof(struct s3c_csis_info), GFP_KERNEL); + if (!s3c_csis) { + err("no memory for configuration\n"); + return -ENOMEM; + } + + strcpy(s3c_csis->name, S3C_CSIS_NAME); + s3c_csis->nr_lanes = S3C_CSIS_NR_LANES; + + return 0; +} + +static void s3c_csis_reset(void) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg |= S3C_CSIS_CONTROL_RESET; + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_set_nr_lanes(int lanes) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONFIG); + cfg &= ~S3C_CSIS_CONFIG_NR_LANE_MASK; + + if (lanes == 1) + cfg |= S3C_CSIS_CONFIG_NR_LANE_1; + else + cfg |= S3C_CSIS_CONFIG_NR_LANE_2; + + writel(cfg, s3c_csis->regs + S3C_CSIS_CONFIG); +} + +static void s3c_csis_enable_interrupt(void) +{ + u32 cfg = 0; + + /* enable all interrupts */ + cfg |= S3C_CSIS_INTMSK_EVEN_BEFORE_ENABLE | \ + S3C_CSIS_INTMSK_EVEN_AFTER_ENABLE | \ + S3C_CSIS_INTMSK_ODD_BEFORE_ENABLE | \ + S3C_CSIS_INTMSK_ODD_AFTER_ENABLE | \ + S3C_CSIS_INTMSK_ERR_SOT_HS_ENABLE | \ + S3C_CSIS_INTMSK_ERR_ESC_ENABLE | \ + S3C_CSIS_INTMSK_ERR_CTRL_ENABLE | \ + S3C_CSIS_INTMSK_ERR_ECC_ENABLE | \ + S3C_CSIS_INTMSK_ERR_CRC_ENABLE | \ + S3C_CSIS_INTMSK_ERR_ID_ENABLE; + + writel(cfg, s3c_csis->regs + S3C_CSIS_INTMSK); +} + +static void s3c_csis_disable_interrupt(void) +{ + /* disable all interrupts */ + writel(0, s3c_csis->regs + S3C_CSIS_INTMSK); +} + +static void s3c_csis_system_on(void) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg |= S3C_CSIS_CONTROL_ENABLE; + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_system_off(void) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg &= ~S3C_CSIS_CONTROL_ENABLE; + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_phy_on(void) +{ + u32 cfg; + cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); + cfg |= S3C_CSIS_DPHYCTRL_ENABLE; + writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); +} + +static void s3c_csis_phy_off(void) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); + cfg &= ~S3C_CSIS_DPHYCTRL_ENABLE; + writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); +} + +#ifdef CONFIG_MIPI_CSI_ADV_FEATURE +static void s3c_csis_update_shadow(void) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg |= S3C_CSIS_CONTROL_UPDATE_SHADOW; + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_set_data_align(int align) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg &= ~S3C_CSIS_CONTROL_ALIGN_MASK; + + if (align == 24) + cfg |= S3C_CSIS_CONTROL_ALIGN_24BIT; + else + cfg |= S3C_CSIS_CONTROL_ALIGN_32BIT; + + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_set_wclk(int extclk) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); + cfg &= ~S3C_CSIS_CONTROL_WCLK_MASK; + + if (extclk) + cfg |= S3C_CSIS_CONTROL_WCLK_EXTCLK; + else + cfg |= S3C_CSIS_CONTROL_WCLK_PCLK; + + writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); +} + +static void s3c_csis_set_format(enum mipi_format fmt) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_CONFIG); + cfg &= ~S3C_CSIS_CONFIG_FORMAT_MASK; + cfg |= (fmt << S3C_CSIS_CONFIG_FORMAT_SHIFT); + + writel(cfg, s3c_csis->regs + S3C_CSIS_CONFIG); +} + +static void s3c_csis_set_resol(int width, int height) +{ + u32 cfg = 0; + + cfg |= width << S3C_CSIS_RESOL_HOR_SHIFT; + cfg |= height << S3C_CSIS_RESOL_VER_SHIFT; + + writel(cfg, s3c_csis->regs + S3C_CSIS_RESOL); +} + +static void s3c_csis_set_hs_settle(int settle) +{ + u32 cfg; + + cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); + cfg &= ~S3C_CSIS_DPHYCTRL_HS_SETTLE_MASK; + cfg |= (settle << S3C_CSIS_DPHYCTRL_HS_SETTLE_SHIFT); + + writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); +} +#endif + +void s3c_csis_start(int lanes, int settle, int align, int width, + int height, int pixel_format) +{ + struct platform_device *pdev = to_platform_device(s3c_csis->dev); + struct s3c_platform_csis *pdata; + + pdata = to_csis_plat(s3c_csis->dev); + if (pdata->cfg_phy_global) + pdata->cfg_phy_global(pdev, 1); + + s3c_csis_reset(); + s3c_csis_set_nr_lanes(lanes); + +#ifdef CONFIG_MIPI_CSI_ADV_FEATURE + /* FIXME: how configure the followings with FIMC dynamically? */ + s3c_csis_set_hs_settle(settle); /* s5k6aa */ + s3c_csis_set_data_align(align); + s3c_csis_set_wclk(0); + if (pixel_format == V4L2_PIX_FMT_JPEG) + s3c_csis_set_format(MIPI_USER_DEF_PACKET_1); + else + s3c_csis_set_format(MIPI_CSI_YCBCR422_8BIT); + s3c_csis_set_resol(width, height); + s3c_csis_update_shadow(); +#endif + + s3c_csis_enable_interrupt(); + s3c_csis_system_on(); + s3c_csis_phy_on(); + + info("Samsung MIPI-CSI2 operation started\n"); +} + +static void s3c_csis_stop(struct platform_device *pdev) +{ + struct s3c_platform_csis *plat; + + s3c_csis_disable_interrupt(); + s3c_csis_system_off(); + s3c_csis_phy_off(); + + plat = to_csis_plat(&pdev->dev); + if (plat->cfg_phy_global) + plat->cfg_phy_global(pdev, 0); +} + +static irqreturn_t s3c_csis_irq(int irq, void *dev_id) +{ + u32 cfg; + + /* just clearing the pends */ + cfg = readl(s3c_csis->regs + S3C_CSIS_INTSRC); + writel(cfg, s3c_csis->regs + S3C_CSIS_INTSRC); + + return IRQ_HANDLED; +} + +static int s3c_csis_clk_on(struct platform_device *pdev) +{ + struct s3c_platform_csis *pdata; + struct clk *parent, *mout_csis; + + pdata = to_csis_plat(&pdev->dev); + + /* mout_mpll */ + parent = clk_get(&pdev->dev, pdata->srclk_name); + if (IS_ERR(parent)) { + err("failed to get parent clock for csis\n"); + return -EINVAL; + } + + /* mout_csis */ + mout_csis = clk_get(&pdev->dev, "mout_csis"); + + /* sclk_csis */ + s3c_csis->clock = clk_get(&pdev->dev, pdata->clk_name); + if (IS_ERR(s3c_csis->clock)) { + err("failed to get csis clock source\n"); + return -EINVAL; + } + + clk_set_parent(mout_csis, parent); + clk_set_parent(s3c_csis->clock, mout_csis); + + /* Turn on csis power domain regulator */ + regulator_enable(s3c_csis->regulator); + /* clock enable for csis */ + clk_enable(s3c_csis->clock); + + return 0; +} + +static int s3c_csis_clk_off(struct platform_device *pdev) +{ + struct s3c_platform_csis *plat; + + plat = to_csis_plat(&pdev->dev); + + /* sclk_csis */ + s3c_csis->clock = clk_get(&pdev->dev, plat->clk_name); + if (IS_ERR(s3c_csis->clock)) { + err("failed to get csis clock source\n"); + return -EINVAL; + } + + /* clock disable for csis */ + clk_disable(s3c_csis->clock); + /* Turn off csis power domain regulator */ + regulator_disable(s3c_csis->regulator); + + return 0; +} + +static int s3c_csis_probe(struct platform_device *pdev) +{ + struct s3c_platform_csis *pdata; + struct resource *res; + + s3c_csis_set_info(); + + s3c_csis->dev = &pdev->dev; + + pdata = to_csis_plat(&pdev->dev); + if (pdata->cfg_gpio) + pdata->cfg_gpio(); + + /* Get csis power domain regulator */ + s3c_csis->regulator = regulator_get(&pdev->dev, "pd"); + if (IS_ERR(s3c_csis->regulator)) { + err("%s: failed to get resource %s\n", + __func__, "s3c-csis"); + return PTR_ERR(s3c_csis->regulator); + } + /* clock & power on */ + s3c_csis_clk_on(pdev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err("failed to get io memory region\n"); + return -EINVAL; + } + + res = request_mem_region(res->start, + res->end - res->start + 1, pdev->name); + if (!res) { + err("failed to request io memory region\n"); + return -EINVAL; + } + + /* ioremap for register block */ + s3c_csis->regs = ioremap(res->start, res->end - res->start + 1); + if (!s3c_csis->regs) { + err("failed to remap io region\n"); + return -EINVAL; + } + + /* irq */ + s3c_csis->irq = platform_get_irq(pdev, 0); + if (request_irq(s3c_csis->irq, s3c_csis_irq, IRQF_DISABLED, \ + s3c_csis->name, s3c_csis)) + err("request_irq failed\n"); + + info("Samsung MIPI-CSI2 driver probed successfully\n"); + + return 0; +} + +static int s3c_csis_remove(struct platform_device *pdev) +{ + s3c_csis_stop(pdev); + kfree(s3c_csis); + + return 0; +} + +/* sleep */ +int s3c_csis_suspend(struct platform_device *pdev, pm_message_t state) +{ + s3c_csis_clk_off(pdev); + return 0; +} + +/* wakeup */ +int s3c_csis_resume(struct platform_device *pdev) +{ + s3c_csis_clk_on(pdev); + return 0; +} + +static struct platform_driver s3c_csis_driver = { + .probe = s3c_csis_probe, + .remove = s3c_csis_remove, + .suspend = s3c_csis_suspend, + .resume = s3c_csis_resume, + .driver = { + .name = "s5p-mipi-csis", + .owner = THIS_MODULE, + }, +}; + +static int s3c_csis_register(void) +{ + platform_driver_register(&s3c_csis_driver); + + return 0; +} + +static void s3c_csis_unregister(void) +{ + platform_driver_unregister(&s3c_csis_driver); +} + +module_init(s3c_csis_register); +module_exit(s3c_csis_unregister); + +MODULE_AUTHOR("Jinsung, Yang <jsgood.yang@samsung.com>"); +MODULE_AUTHOR("Sewoon, Park <seuni.park@samsung.com>"); +MODULE_DESCRIPTION("MIPI-CSI2 support for FIMC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/samsung/fimc/csis.h b/drivers/media/video/samsung/fimc/csis.h new file mode 100644 index 0000000..4943c8f --- /dev/null +++ b/drivers/media/video/samsung/fimc/csis.h @@ -0,0 +1,42 @@ +/* linux/drivers/media/video/samsung/csis.h + * + * Copyright (c) 2010 Samsung Electronics Co,. Ltd. + * http://www.samsung.com/ + * + * Header file for Samsung MIPI-CSI2 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __CSIS_H +#define __CSIS_H __FILE__ + +#define S3C_CSIS_NAME "s5p-mipi-csis" +#define S3C_CSIS_NR_LANES 1 + +#define info(args...) \ + do { printk(KERN_INFO S3C_CSIS_NAME ": " args); } while (0) +#define err(args...) \ + do { printk(KERN_ERR S3C_CSIS_NAME ": " args); } while (0) + +enum mipi_format { + MIPI_CSI_YCBCR422_8BIT = 0x1e, + MIPI_CSI_RAW8 = 0x2a, + MIPI_CSI_RAW10 = 0x2b, + MIPI_CSI_RAW12 = 0x2c, + MIPI_USER_DEF_PACKET_1 = 0x30, /* User defined Byte-based packet 1 */ +}; + +struct s3c_csis_info { + char name[16]; + struct device *dev; + struct clk *clock; + struct regulator *regulator; + void __iomem *regs; + int irq; + int nr_lanes; +}; + +#endif /* __CSIS_H */ diff --git a/drivers/media/video/samsung/fimc/fimc.h b/drivers/media/video/samsung/fimc/fimc.h new file mode 100644 index 0000000..de137c2 --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc.h @@ -0,0 +1,707 @@ +/* linux/drivers/media/video/samsung/fimc/fimc.h + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Header file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + + +#ifndef __FIMC_H +#define __FIMC_H __FILE__ + +#ifdef __KERNEL__ +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/fb.h> +#include <linux/videodev2.h> +#include <linux/platform_device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf-core.h> +#include <plat/media.h> +#include <plat/fimc.h> +#endif + +#define FIMC_NAME "s3c-fimc" + +#define FIMC_DEVICES 3 +#define FIMC_SUBDEVS 3 +#define FIMC_MAXCAMS 5 /* added 1 because of WriteBack */ +#define FIMC_PHYBUFS 4 +#define FIMC_OUTBUFS 3 +#define FIMC_INQUEUES 10 +#define FIMC_MAX_CTXS 1 +#define FIMC_TPID 3 +#define FIMC_CAPBUFS 16 +#define FIMC_ONESHOT_TIMEOUT 200 +#define FIMC_DQUEUE_TIMEOUT 200 +#define FIMC_FIFOOFF_CNT 1000000 /* Sufficiently big value for stop */ + +#define FORMAT_FLAGS_PACKED 0x1 +#define FORMAT_FLAGS_PLANAR 0x2 +#define FORMAT_FLAGS_ENCODED 0x3 + +#define FIMC_ADDR_Y 0 +#define FIMC_ADDR_CB 1 +#define FIMC_ADDR_CR 2 + +#define FIMC_HD_WIDTH 1280 +#define FIMC_HD_HEIGHT 720 + +#define FIMC_FHD_WIDTH 1920 +#define FIMC_FHD_HEIGHT 1080 + +#define FIMC_MMAP_IDX -1 +#define FIMC_USERPTR_IDX -2 + +#define FIMC_HCLK 0 +#define FIMC_SCLK 1 +#define FIMC_OVLY_MODE FIMC_OVLY_DMA_AUTO + +#define FIMC_PINGPONG 2 + +/* + * ENUMERATIONS +*/ +enum fimc_status { + FIMC_READY_OFF = 0x00, + FIMC_STREAMOFF = 0x01, + FIMC_READY_ON = 0x02, + FIMC_STREAMON = 0x03, + FIMC_STREAMON_IDLE = 0x04, /* oneshot mode */ + FIMC_OFF_SLEEP = 0x05, + FIMC_ON_SLEEP = 0x06, + FIMC_ON_IDLE_SLEEP = 0x07, /* oneshot mode */ + FIMC_READY_RESUME = 0x08, +}; + +enum fimc_fifo_state { + FIFO_CLOSE, + FIFO_SLEEP, +}; + +enum fimc_fimd_state { + FIMD_OFF, + FIMD_ON, +}; + +enum fimc_rot_flip { + FIMC_XFLIP = 0x01, + FIMC_YFLIP = 0x02, + FIMC_ROT = 0x10, +}; + +enum fimc_input { + FIMC_SRC_CAM, + FIMC_SRC_MSDMA, +}; + +enum fimc_overlay_mode { + /* Overlay mode isn't fixed. */ + FIMC_OVLY_NOT_FIXED = 0x0, + /* Non-destructive Overlay with DMA */ + FIMC_OVLY_DMA_AUTO = 0x1, + /* Non-destructive Overlay with DMA */ + FIMC_OVLY_DMA_MANUAL = 0x2, + /* Destructive Overlay with DMA single destination buffer */ + FIMC_OVLY_NONE_SINGLE_BUF = 0x3, + /* Destructive Overlay with DMA multiple dstination buffer */ + FIMC_OVLY_NONE_MULTI_BUF = 0x4, +}; + +enum fimc_autoload { + FIMC_AUTO_LOAD, + FIMC_ONE_SHOT, +}; + +enum fimc_log { + FIMC_LOG_DEBUG = 0x1000, + FIMC_LOG_INFO_L2 = 0x0200, + FIMC_LOG_INFO_L1 = 0x0100, + FIMC_LOG_WARN = 0x0010, + FIMC_LOG_ERR = 0x0001, +}; + +enum fimc_pixel_format_type{ + FIMC_RGB, + FIMC_YUV420, + FIMC_YUV422, + FIMC_YUV444, +}; + +/* + * STRUCTURES +*/ + +/* for reserved memory */ +struct fimc_meminfo { + dma_addr_t base; /* buffer base */ + size_t size; /* total length */ + dma_addr_t curr; /* current addr */ +}; + +struct fimc_buf { + dma_addr_t base[3]; + size_t length[3]; +}; + +struct fimc_overlay_buf { + u32 vir_addr[3]; + size_t size[3]; + u32 phy_addr[3]; +}; + +struct fimc_overlay { + enum fimc_overlay_mode mode; + struct fimc_overlay_buf buf; + s32 req_idx; + int fb_id; +}; + +/* general buffer */ +struct fimc_buf_set { + int id; + /* Plane 0/1/2 for raw data, Plane 3 for padding buffer (if required) */ + dma_addr_t base[4]; + size_t length[4]; + size_t garbage[4]; + enum videobuf_state state; + u32 flags; + atomic_t mapped_cnt; + struct list_head list; +}; + +/* for capture device */ +struct fimc_capinfo { + struct v4l2_cropcap cropcap; + struct v4l2_rect crop; + struct v4l2_pix_format fmt; + struct fimc_buf_set bufs[FIMC_CAPBUFS]; + struct list_head inq; + int outq[FIMC_PHYBUFS]; + int nr_bufs; + int irq; + int lastirq; + + /* flip: V4L2_CID_xFLIP, rotate: 90, 180, 270 */ + u32 flip; + u32 rotate; +}; + +/* for output overlay device */ +struct fimc_idx { + int ctx; + int idx; +}; + +struct fimc_ctx_idx { + struct fimc_idx prev; + struct fimc_idx active; + struct fimc_idx next; +}; + +/* scaler abstraction: local use recommended */ +struct fimc_scaler { + u32 bypass; + u32 hfactor; + u32 vfactor; + u32 pre_hratio; + u32 pre_vratio; + u32 pre_dst_width; + u32 pre_dst_height; + u32 scaleup_h; + u32 scaleup_v; + u32 main_hratio; + u32 main_vratio; + u32 real_width; + u32 real_height; + u32 shfactor; + u32 skipline; +}; + +struct fimc_ctx { + u32 ctx_num; + struct v4l2_cropcap cropcap; + struct v4l2_rect crop; + struct v4l2_pix_format pix; + struct v4l2_window win; + struct v4l2_framebuffer fbuf; + struct fimc_scaler sc; + struct fimc_overlay overlay; + + u32 buf_num; + u32 is_requested; + struct fimc_buf_set src[FIMC_OUTBUFS]; + struct fimc_buf_set dst[FIMC_OUTBUFS]; + s32 inq[FIMC_OUTBUFS]; + s32 outq[FIMC_OUTBUFS]; + + u32 flip; + u32 rotate; + enum fimc_status status; +}; + +struct fimc_outinfo { + int last_ctx; + spinlock_t lock_in; + spinlock_t lock_out; + struct fimc_idx inq[FIMC_INQUEUES]; + struct fimc_ctx ctx[FIMC_MAX_CTXS]; + struct fimc_ctx_idx idxs; +}; + +struct s3cfb_user_window { + int x; + int y; +}; + +enum s3cfb_data_path_t { + DATA_PATH_FIFO = 0, + DATA_PATH_DMA = 1, + DATA_PATH_IPC = 2, +}; + +enum s3cfb_mem_owner_t { + DMA_MEM_NONE = 0, + DMA_MEM_FIMD = 1, + DMA_MEM_OTHER = 2, +}; + +enum s3cfb_alpha_t { + PLANE_BLENDING, + PIXEL_BLENDING, +}; + +enum s3cfb_chroma_dir_t { + CHROMA_FG, + CHROMA_BG, +}; + +struct s3cfb_alpha { + enum s3cfb_alpha_t mode; + int channel; + unsigned int value; +}; + + +struct s3cfb_chroma { + int enabled; + int blended; + unsigned int key; + unsigned int comp_key; + unsigned int alpha; + enum s3cfb_chroma_dir_t dir; +}; + +struct s3cfb_window { + int id; + int enabled; + atomic_t in_use; + int x; + int y; + enum s3cfb_data_path_t path; + enum s3cfb_mem_owner_t owner; + unsigned int other_mem_addr; + unsigned int other_mem_size; + int local_channel; + int dma_burst; + unsigned int pseudo_pal[16]; + struct s3cfb_alpha alpha; + struct s3cfb_chroma chroma; +}; + +#define S3CFB_WIN_OFF_ALL _IO('F', 202) +#define S3CFB_WIN_POSITION _IOW('F', 203, struct s3cfb_user_window) +#define S3CFB_GET_LCD_WIDTH _IOR('F', 302, int) +#define S3CFB_GET_LCD_HEIGHT _IOR('F', 303, int) +#define S3CFB_SET_WRITEBACK _IOW('F', 304, u32) +#define S3CFB_SET_WIN_ON _IOW('F', 306, u32) +#define S3CFB_SET_WIN_OFF _IOW('F', 307, u32) +#define S3CFB_SET_WIN_PATH _IOW('F', 308, enum s3cfb_data_path_t) +#define S3CFB_SET_WIN_ADDR _IOW('F', 309, unsigned long) +#define S3CFB_SET_WIN_MEM _IOW('F', 310, enum s3cfb_mem_owner_t) +/* ------------------------------------------------------------------------ */ + +struct fimc_fbinfo { + struct fb_fix_screeninfo *fix; + struct fb_var_screeninfo *var; + int lcd_hres; + int lcd_vres; + u32 is_enable; +}; + +struct fimc_limit { + u32 pre_dst_w; + u32 bypass_w; + u32 trg_h_no_rot; + u32 trg_h_rot; + u32 real_w_no_rot; + u32 real_h_rot; +}; + +enum FIMC_EFFECT_FIN { + FIMC_EFFECT_FIN_BYPASS = 0, + FIMC_EFFECT_FIN_ARBITRARY_CBCR, + FIMC_EFFECT_FIN_NEGATIVE, + FIMC_EFFECT_FIN_ART_FREEZE, + FIMC_EFFECT_FIN_EMBOSSING, + FIMC_EFFECT_FIN_SILHOUETTE, +}; + + +struct fimc_effect { + int ie_on; + int ie_after_sc; + enum FIMC_EFFECT_FIN fin; + int pat_cb; + int pat_cr; +}; + +/* fimc controller abstration */ +struct fimc_control { + int id; /* controller id */ + char name[16]; + atomic_t in_use; + void __iomem *regs; /* register i/o */ + struct clk *clk; /* interface clock */ + struct regulator *regulator; /* pd regulator */ + struct fimc_meminfo mem; /* for reserved mem */ + + /* kernel helpers */ + struct mutex lock; /* controller lock */ + struct mutex alloc_lock; + struct mutex v4l2_lock; + wait_queue_head_t wq; + struct device *dev; + int irq; + + /* v4l2 related */ + struct video_device *vd; + struct v4l2_device v4l2_dev; + + /* fimc specific */ + struct fimc_limit *limit; /* H/W limitation */ + struct s3c_platform_camera *cam; /* activated camera */ + struct fimc_capinfo *cap; /* capture dev info */ + struct fimc_outinfo *out; /* output dev info */ + struct fimc_fbinfo fb; /* fimd info */ + struct fimc_scaler sc; /* scaler info */ + struct fimc_effect fe; /* fimc effect info */ + + enum fimc_status status; + enum fimc_log log; + + u32 ctx_busy[FIMC_MAX_CTXS]; +}; + +/* global */ +struct fimc_global { + struct fimc_control ctrl[FIMC_DEVICES]; + struct s3c_platform_camera camera[FIMC_MAXCAMS]; + int camera_isvalid[FIMC_MAXCAMS]; + int active_camera; + int initialized; +}; + +struct fimc_prv_data { + struct fimc_control *ctrl; + int ctx_id; +}; + +/* debug macro */ +#define FIMC_LOG_DEFAULT (FIMC_LOG_WARN | FIMC_LOG_ERR) + +#define FIMC_DEBUG(fmt, ...) \ + do { \ + if (ctrl->log & FIMC_LOG_DEBUG) \ + printk(KERN_DEBUG FIMC_NAME "-%d : " \ + fmt, ctrl->id, ##__VA_ARGS__); \ + } while (0) + +#define FIMC_INFO_L2(fmt, ...) \ + do { \ + if (ctrl->log & FIMC_LOG_INFO_L2) \ + printk(KERN_INFO FIMC_NAME "-%d : " \ + fmt, ctrl->id, ##__VA_ARGS__); \ + } while (0) + +#define FIMC_INFO_L1(fmt, ...) \ + do { \ + if (ctrl->log & FIMC_LOG_INFO_L1) \ + printk(KERN_INFO FIMC_NAME "-%d : " \ + fmt, ctrl->id, ##__VA_ARGS__); \ + } while (0) + +#define FIMC_WARN(fmt, ...) \ + do { \ + if (ctrl->log & FIMC_LOG_WARN) \ + printk(KERN_WARNING FIMC_NAME "-%d : " \ + fmt, ctrl->id, ##__VA_ARGS__); \ + } while (0) + + +#define FIMC_ERROR(fmt, ...) \ + do { \ + if (ctrl->log & FIMC_LOG_ERR) \ + printk(KERN_ERR FIMC_NAME "-%d : " \ + fmt, ctrl->id, ##__VA_ARGS__); \ + } while (0) + + +#define fimc_dbg(fmt, ...) FIMC_DEBUG(fmt, ##__VA_ARGS__) +#define fimc_info2(fmt, ...) FIMC_INFO_L2(fmt, ##__VA_ARGS__) +#define fimc_info1(fmt, ...) FIMC_INFO_L1(fmt, ##__VA_ARGS__) +#define fimc_warn(fmt, ...) FIMC_WARN(fmt, ##__VA_ARGS__) +#define fimc_err(fmt, ...) FIMC_ERROR(fmt, ##__VA_ARGS__) + +/* + * EXTERNS +*/ +extern struct fimc_global *fimc_dev; +extern struct video_device fimc_video_device[FIMC_DEVICES]; +extern const struct v4l2_ioctl_ops fimc_v4l2_ops; +extern struct fimc_limit fimc40_limits[FIMC_DEVICES]; +extern struct fimc_limit fimc43_limits[FIMC_DEVICES]; +extern struct fimc_limit fimc50_limits[FIMC_DEVICES]; + +/* general */ +extern void s3c_csis_start(int lanes, int settle, int align, + int width, int height, + int pixel_format); +extern int fimc_dma_alloc(struct fimc_control *ctrl, + struct fimc_buf_set *bs, + int i, int align); +extern void fimc_dma_free(struct fimc_control *ctrl, + struct fimc_buf_set *bs, int i); +extern u32 fimc_mapping_rot_flip(u32 rot, u32 flip); +extern int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift); +extern void fimc_get_nv12t_size(int img_hres, int img_vres, + int *y_size, int *cb_size); +extern void fimc_clk_en(struct fimc_control *ctrl, bool on); + +/* camera */ +extern int fimc_select_camera(struct fimc_control *ctrl); + +/* capture device */ +extern int fimc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp); +extern int fimc_g_input(struct file *file, void *fh, unsigned int *i); +extern int fimc_s_input(struct file *file, void *fh, unsigned int i); +extern int fimc_enum_fmt_vid_capture(struct file *file, void *fh, + struct v4l2_fmtdesc *f); +extern int fimc_g_fmt_vid_capture(struct file *file, void *fh, + struct v4l2_format *f); +extern int fimc_s_fmt_vid_capture(struct file *file, void *fh, + struct v4l2_format *f); +extern int fimc_try_fmt_vid_capture(struct file *file, void *fh, + struct v4l2_format *f); +extern int fimc_reqbufs_capture(void *fh, struct v4l2_requestbuffers *b); +extern int fimc_querybuf_capture(void *fh, struct v4l2_buffer *b); +extern int fimc_g_ctrl_capture(void *fh, struct v4l2_control *c); +extern int fimc_s_ctrl_capture(void *fh, struct v4l2_control *c); +extern int fimc_s_ext_ctrls_capture(void *fh, struct v4l2_ext_controls *c); +extern int fimc_cropcap_capture(void *fh, struct v4l2_cropcap *a); +extern int fimc_g_crop_capture(void *fh, struct v4l2_crop *a); +extern int fimc_s_crop_capture(void *fh, struct v4l2_crop *a); +extern int fimc_streamon_capture(void *fh); +extern int fimc_streamoff_capture(void *fh); +extern int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b); +extern int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b); +extern int fimc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a); +extern int fimc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a); +extern int fimc_queryctrl(struct file *file, void *fh, + struct v4l2_queryctrl *qc); +extern int fimc_querymenu(struct file *file, void *fh, + struct v4l2_querymenu *qm); + +#if defined(CONFIG_CPU_S5PV210) +extern int fimc_change_clksrc(struct fimc_control *ctrl, int fimc_clk); +#endif +extern int fimc_release_subdev(struct fimc_control *ctrl); + +/* output device */ +extern void fimc_outdev_set_src_addr(struct fimc_control *ctrl, + dma_addr_t *base); +extern int fimc_outdev_set_ctx_param(struct fimc_control *ctrl, + struct fimc_ctx *ctx); +extern int fimc_start_fifo(struct fimc_control *ctrl, + struct fimc_ctx *ctx); +extern int fimc_fimd_rect(const struct fimc_control *ctrl, + const struct fimc_ctx *ctx, + struct v4l2_rect *fimd_rect); +extern int fimc_outdev_stop_streaming(struct fimc_control *ctrl, + struct fimc_ctx *ctx); +extern int fimc_outdev_resume_dma(struct fimc_control *ctrl, + struct fimc_ctx *ctx); +extern int fimc_outdev_start_camif(void *param); +extern int fimc_reqbufs_output(void *fh, struct v4l2_requestbuffers *b); +extern int fimc_querybuf_output(void *fh, struct v4l2_buffer *b); +extern int fimc_g_ctrl_output(void *fh, struct v4l2_control *c); +extern int fimc_s_ctrl_output(struct file *filp, void *fh, + struct v4l2_control *c); +extern int fimc_cropcap_output(void *fh, struct v4l2_cropcap *a); +extern int fimc_g_crop_output(void *fh, struct v4l2_crop *a); +extern int fimc_s_crop_output(void *fh, struct v4l2_crop *a); +extern int fimc_streamon_output(void *fh); +extern int fimc_streamoff_output(void *fh); +extern int fimc_qbuf_output(void *fh, struct v4l2_buffer *b); +extern int fimc_dqbuf_output(void *fh, struct v4l2_buffer *b); +extern int fimc_g_fmt_vid_out(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_s_fmt_vid_out(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_try_fmt_vid_out(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_output_set_dst_addr(struct fimc_control *ctrl, + struct fimc_ctx *ctx, int idx); +extern int fimc_init_in_queue(struct fimc_control *ctrl, struct fimc_ctx *ctx); +extern int fimc_push_inq(struct fimc_control *ctrl, + struct fimc_ctx *ctx, int idx); +extern int fimc_pop_inq(struct fimc_control *ctrl, int *ctx_num, int *idx); +extern int fimc_push_outq(struct fimc_control *ctrl, + struct fimc_ctx *ctx, int idx); +extern int fimc_pop_outq(struct fimc_control *ctrl, + struct fimc_ctx *ctx, int *idx); +extern int fimc_init_out_queue(struct fimc_control *ctrl, struct fimc_ctx *ctx); +extern void fimc_outdev_init_idxs(struct fimc_control *ctrl); + +extern void fimc_dump_context(struct fimc_control *ctrl, struct fimc_ctx *ctx); +extern void fimc_print_signal(struct fimc_control *ctrl); + +/* overlay device */ +extern int fimc_try_fmt_overlay(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_g_fmt_vid_overlay(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_s_fmt_vid_overlay(struct file *filp, void *fh, + struct v4l2_format *f); +extern int fimc_g_fbuf(struct file *filp, void *fh, + struct v4l2_framebuffer *fb); +extern int fimc_s_fbuf(struct file *filp, void *fh, + struct v4l2_framebuffer *fb); + +/* Register access file */ +extern void fimc_reset(struct fimc_control *ctrl); +extern int fimc_hwset_camera_source(struct fimc_control *ctrl); +extern int fimc_hwset_enable_irq(struct fimc_control *ctrl, + int overflow, int level); +extern int fimc_hwset_disable_irq(struct fimc_control *ctrl); +extern int fimc_hwset_clear_irq(struct fimc_control *ctrl); +extern int fimc_hwset_reset(struct fimc_control *ctrl); +extern int fimc_hwset_sw_reset(struct fimc_control *ctrl); +extern int fimc_hwset_clksrc(struct fimc_control *ctrl, int src_clk); +extern int fimc_hwget_overflow_state(struct fimc_control *ctrl); +extern int fimc_hwset_camera_offset(struct fimc_control *ctrl); +extern int fimc_hwset_camera_polarity(struct fimc_control *ctrl); +extern int fimc_hwset_camera_type(struct fimc_control *ctrl); +extern int fimc_hwset_output_size(struct fimc_control *ctrl, + int width, int height); +extern int fimc_hwset_output_colorspace(struct fimc_control *ctrl, + u32 pixelformat); +extern int fimc_hwset_output_rot_flip(struct fimc_control *ctrl, + u32 rot, u32 flip); +extern int fimc_hwset_output_area(struct fimc_control *ctrl, + u32 width, u32 height); +extern int fimc_hwset_output_area_size(struct fimc_control *ctrl, u32 size); +extern int fimc_hwset_output_scan(struct fimc_control *ctrl, + struct v4l2_pix_format *fmt); +extern int fimc_hwset_enable_lastirq(struct fimc_control *ctrl); +extern int fimc_hwset_disable_lastirq(struct fimc_control *ctrl); +extern int fimc_hwset_prescaler(struct fimc_control *ctrl, + struct fimc_scaler *sc); +extern int fimc_hwset_output_yuv(struct fimc_control *ctrl, u32 pixelformat); +extern int fimc_hwset_output_address(struct fimc_control *ctrl, + struct fimc_buf_set *bs, + int id); +extern int fimc_hwset_input_rot(struct fimc_control *ctrl, u32 rot, u32 flip); +extern int fimc_hwset_scaler(struct fimc_control *ctrl, struct fimc_scaler *sc); +extern int fimc_hwset_scaler_bypass(struct fimc_control *ctrl); +extern int fimc_hwset_enable_lcdfifo(struct fimc_control *ctrl); +extern int fimc_hwset_disable_lcdfifo(struct fimc_control *ctrl); +extern int fimc_hwset_start_scaler(struct fimc_control *ctrl); +extern int fimc_hwset_stop_scaler(struct fimc_control *ctrl); +extern int fimc_hwset_input_rgb(struct fimc_control *ctrl, u32 pixelformat); +extern int fimc_hwset_intput_field(struct fimc_control *ctrl, + enum v4l2_field field); +extern int fimc_hwset_output_rgb(struct fimc_control *ctrl, u32 pixelformat); +extern int fimc_hwset_ext_rgb(struct fimc_control *ctrl, int enable); +extern int fimc_hwset_enable_capture(struct fimc_control *ctrl, + u32 bypass); +extern int fimc_hwset_disable_capture(struct fimc_control *ctrl); +extern void fimc_wait_disable_capture(struct fimc_control *ctrl); +extern int fimc_hwset_input_address(struct fimc_control *ctrl, + dma_addr_t *base); +extern int fimc_hwset_enable_autoload(struct fimc_control *ctrl); +extern int fimc_hwset_disable_autoload(struct fimc_control *ctrl); +extern int fimc_hwset_real_input_size(struct fimc_control *ctrl, + u32 width, u32 height); +extern int fimc_hwset_addr_change_enable(struct fimc_control *ctrl); +extern int fimc_hwset_addr_change_disable(struct fimc_control *ctrl); +extern int fimc_hwset_input_burst_cnt(struct fimc_control *ctrl, u32 cnt); +extern int fimc_hwset_input_colorspace(struct fimc_control *ctrl, + u32 pixelformat); +extern int fimc_hwset_input_yuv(struct fimc_control *ctrl, u32 pixelformat); +extern int fimc_hwset_input_flip(struct fimc_control *ctrl, u32 rot, u32 flip); +extern int fimc_hwset_input_source(struct fimc_control *ctrl, + enum fimc_input path); +extern int fimc_hwset_start_input_dma(struct fimc_control *ctrl); +extern int fimc_hwset_stop_input_dma(struct fimc_control *ctrl); +extern int fimc_hwset_output_offset(struct fimc_control *ctrl, + u32 pixelformat, + struct v4l2_rect *bound, + struct v4l2_rect *crop); +extern int fimc_hwset_input_offset(struct fimc_control *ctrl, + u32 pixelformat, + struct v4l2_rect *bound, + struct v4l2_rect *crop); +extern int fimc_hwset_org_input_size(struct fimc_control *ctrl, + u32 width, u32 height); +extern int fimc_hwset_org_output_size(struct fimc_control *ctrl, + u32 width, u32 height); +extern int fimc_hwset_ext_output_size(struct fimc_control *ctrl, + u32 width, u32 height); +extern int fimc_hwset_input_addr_style(struct fimc_control *ctrl, + u32 pixelformat); +extern int fimc_hwset_output_addr_style(struct fimc_control *ctrl, + u32 pixelformat); +extern int fimc_hwset_jpeg_mode(struct fimc_control *ctrl, bool enable); +extern int fimc_hwget_frame_count(struct fimc_control *ctrl); +extern int fimc_hw_wait_winoff(struct fimc_control *ctrl); +extern int fimc_hw_wait_stop_input_dma(struct fimc_control *ctrl); +extern int fimc_hwset_input_lineskip(struct fimc_control *ctrl); +extern int fimc_hw_reset_camera(struct fimc_control *ctrl); +extern void fimc_hwset_stop_processing(struct fimc_control *ctrl); +extern int fimc_hwset_image_effect(struct fimc_control *ctrl); +extern int fimc_hwset_shadow_enable(struct fimc_control *ctrl); +extern int fimc_hwset_shadow_disable(struct fimc_control *ctrl); +extern void fimc_save_regs(struct fimc_control *ctrl); +extern void fimc_load_regs(struct fimc_control *ctrl); +extern void fimc_dump_regs(struct fimc_control *ctrl); + +/* + * D R I V E R H E L P E R S + * +*/ +#define to_fimc_plat(d) (to_platform_device(d)->dev.platform_data) + +static inline struct fimc_global *get_fimc_dev(void) +{ + return fimc_dev; +} + +static inline struct fimc_control *get_fimc_ctrl(int id) +{ + return &fimc_dev->ctrl[id]; +} + +#endif /* _FIMC_H */ + diff --git a/drivers/media/video/samsung/fimc/fimc_capture.c b/drivers/media/video/samsung/fimc/fimc_capture.c new file mode 100644 index 0000000..2028bf9 --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_capture.c @@ -0,0 +1,1733 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_capture.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * V4L2 Capture device support file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/slab.h> +#include <linux/bootmem.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/videodev2.h> +#include <linux/videodev2_samsung.h> +#include <linux/clk.h> +#include <linux/mm.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <plat/media.h> +#include <plat/clock.h> +#include <plat/fimc.h> +#include <linux/delay.h> + +#include "fimc.h" + +/* subdev handling macro */ +#define subdev_call(ctrl, o, f, args...) \ + v4l2_subdev_call(ctrl->cam->sd, o, f, ##args) + +/* #define FIMC_CAP_DEBUG */ + +#ifdef FIMC_CAP_DEBUG +#ifdef fimc_dbg +#undef fimc_dbg +#endif +#define fimc_dbg fimc_err +#endif + +static const struct v4l2_fmtdesc capture_fmts[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "RGB-5-6-5", + .pixelformat = V4L2_PIX_FMT_RGB565, + }, { + .index = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "RGB-8-8-8, unpacked 24 bpp", + .pixelformat = V4L2_PIX_FMT_RGB32, + }, { + .index = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "YUV 4:2:2 packed, YCbYCr", + .pixelformat = V4L2_PIX_FMT_YUYV, + }, { + .index = 3, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "YUV 4:2:2 packed, CbYCrY", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, { + .index = 4, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "YUV 4:2:2 packed, CrYCbY", + .pixelformat = V4L2_PIX_FMT_VYUY, + }, { + .index = 5, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PACKED, + .description = "YUV 4:2:2 packed, YCrYCb", + .pixelformat = V4L2_PIX_FMT_YVYU, + }, { + .index = 6, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:2 planar, Y/Cb/Cr", + .pixelformat = V4L2_PIX_FMT_YUV422P, + }, { + .index = 7, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:0 planar, Y/CbCr", + .pixelformat = V4L2_PIX_FMT_NV12, + }, { + .index = 8, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:0 planar, Y/CbCr, Tiled", + .pixelformat = V4L2_PIX_FMT_NV12T, + }, { + .index = 9, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:0 planar, Y/CrCb", + .pixelformat = V4L2_PIX_FMT_NV21, + }, { + .index = 10, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:2 planar, Y/CbCr", + .pixelformat = V4L2_PIX_FMT_NV16, + }, { + .index = 11, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:2 planar, Y/CrCb", + .pixelformat = V4L2_PIX_FMT_NV61, + }, { + .index = 12, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_PLANAR, + .description = "YUV 4:2:0 planar, Y/Cb/Cr", + .pixelformat = V4L2_PIX_FMT_YUV420, + }, { + .index = 13, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = FORMAT_FLAGS_ENCODED, + .description = "Encoded JPEG bitstream", + .pixelformat = V4L2_PIX_FMT_JPEG, + }, +}; + +static const struct v4l2_queryctrl fimc_controls[] = { + { + .id = V4L2_CID_ROTATION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Roataion", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + }, { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, { + .id = V4L2_CID_PADDR_Y, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Physical address Y", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + }, { + .id = V4L2_CID_PADDR_CB, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Physical address Cb", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + }, { + .id = V4L2_CID_PADDR_CR, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Physical address Cr", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + }, { + .id = V4L2_CID_PADDR_CBCR, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Physical address CbCr", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + }, +}; + +#ifndef CONFIG_VIDEO_FIMC_MIPI +void s3c_csis_start(int lanes, int settle, int align, int width, int height, + int pixel_format) {} +#endif + +static int fimc_camera_init(struct fimc_control *ctrl) +{ + int ret; + + fimc_dbg("%s\n", __func__); + + /* do nothing if already initialized */ + if (ctrl->cam->initialized) + return 0; + + /* enable camera power if needed */ + if (ctrl->cam->cam_power) + ctrl->cam->cam_power(1); + + /* subdev call for init */ + ret = subdev_call(ctrl, core, init, 0); + if (ret == -ENOIOCTLCMD) { + fimc_err("%s: init subdev api not supported\n", + __func__); + return ret; + } + + if (ctrl->cam->type == CAM_TYPE_MIPI) { + /* subdev call for sleep/wakeup: + * no error although no s_stream api support + */ + u32 pixelformat; + if (ctrl->cap->fmt.pixelformat == V4L2_PIX_FMT_JPEG) + pixelformat = V4L2_PIX_FMT_JPEG; + else + pixelformat = ctrl->cam->pixelformat; + + subdev_call(ctrl, video, s_stream, 0); + s3c_csis_start(ctrl->cam->mipi_lanes, ctrl->cam->mipi_settle, \ + ctrl->cam->mipi_align, ctrl->cam->width, \ + ctrl->cam->height, pixelformat); + subdev_call(ctrl, video, s_stream, 1); + } + + ctrl->cam->initialized = 1; + + return 0; +} + + +/* This function must be called after s_fmt and s_parm call to the subdev + * has already been made. + * + * - obtains the camera output (input to FIMC) resolution. + * - sets the preview size (aka camera output resolution) and framerate. + * - starts the preview operation. + * + * On success, returns 0. + * On failure, returns the error code of the call that failed. + */ +static int fimc_camera_start(struct fimc_control *ctrl) +{ + struct v4l2_frmsizeenum cam_frmsize; + struct v4l2_control cam_ctrl; + int ret; + + ret = subdev_call(ctrl, video, enum_framesizes, &cam_frmsize); + if (ret < 0) { + fimc_err("%s: enum_framesizes failed\n", __func__); + if (ret != -ENOIOCTLCMD) + return ret; + } else { + ctrl->cam->width = cam_frmsize.discrete.width; + ctrl->cam->height = cam_frmsize.discrete.height; + + ctrl->cam->window.left = 0; + ctrl->cam->window.top = 0; + ctrl->cam->window.width = ctrl->cam->width; + ctrl->cam->window.height = ctrl->cam->height; + } + + cam_ctrl.id = V4L2_CID_CAM_PREVIEW_ONOFF; + cam_ctrl.value = 1; + ret = subdev_call(ctrl, core, s_ctrl, &cam_ctrl); + + /* When the device is waking up from sleep, this call may fail. In + * that case, it is better to reset the camera sensor and start again. + * If the preview fails again, the reason might be something else and + * we should return the error. + */ + if (ret < 0 && ret != -ENOIOCTLCMD) { + ctrl->cam->initialized = 0; + fimc_camera_init(ctrl); + ret = subdev_call(ctrl, core, s_ctrl, &cam_ctrl); + if (ret < 0 && ret != -ENOIOCTLCMD) { + fimc_err("%s: Error in V4L2_CID_CAM_PREVIEW_ONOFF" + " - start\n", __func__); + return ret; + } + } + + return 0; +} + +static int fimc_camera_get_jpeg_memsize(struct fimc_control *ctrl) +{ + int ret; + struct v4l2_control cam_ctrl; + cam_ctrl.id = V4L2_CID_CAM_JPEG_MEMSIZE; + + ret = subdev_call(ctrl, core, g_ctrl, &cam_ctrl); + if (ret < 0) { + fimc_err("%s: Subdev doesn't support JEPG encoding.\n", \ + __func__); + return 0; + } + + return cam_ctrl.value; +} + + +static int fimc_capture_scaler_info(struct fimc_control *ctrl) +{ + struct fimc_scaler *sc = &ctrl->sc; + struct v4l2_rect *window = &ctrl->cam->window; + int tx, ty, sx, sy; + + sx = window->width; + sy = window->height; + tx = ctrl->cap->fmt.width; + ty = ctrl->cap->fmt.height; + + sc->real_width = sx; + sc->real_height = sy; + + fimc_dbg("%s: CamOut (%d, %d), TargetOut (%d, %d)\n", \ + __func__, sx, sy, tx, ty); + + if (sx <= 0 || sy <= 0) { + fimc_err("%s: invalid source size\n", __func__); + return -EINVAL; + } + + if (tx <= 0 || ty <= 0) { + fimc_err("%s: invalid target size\n", __func__); + return -EINVAL; + } + + fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor); + fimc_get_scaler_factor(sy, ty, &sc->pre_vratio, &sc->vfactor); + + /* Tushar - sx and sy should be multiple of pre_hratio and pre_vratio */ + sc->pre_dst_width = sx / sc->pre_hratio; + sc->pre_dst_height = sy / sc->pre_vratio; + + sc->main_hratio = (sx << 8) / (tx << sc->hfactor); + sc->main_vratio = (sy << 8) / (ty << sc->vfactor); + + sc->scaleup_h = (tx >= sx) ? 1 : 0; + sc->scaleup_v = (ty >= sy) ? 1 : 0; + + return 0; +} + +/** + * fimc_add_inqueue: used to add the buffer at given index to inqueue + * + * Called from qbuf(). + * + * Returns error if buffer is already in queue or buffer index is out of range. + */ +static int fimc_add_inqueue(struct fimc_control *ctrl, int i) +{ + struct fimc_capinfo *cap = ctrl->cap; + + struct fimc_buf_set *buf; + + if (i >= cap->nr_bufs) + return -EINVAL; + + list_for_each_entry(buf, &cap->inq, list) { + if (buf->id == i) { + fimc_dbg("%s: buffer %d already in inqueue.\n", \ + __func__, i); + return -EINVAL; + } + } + list_add_tail(&cap->bufs[i].list, &cap->inq); + + return 0; +} + +static int fimc_add_outqueue(struct fimc_control *ctrl, int i) +{ + struct fimc_capinfo *cap = ctrl->cap; + struct fimc_buf_set *buf; + + unsigned int mask = 0x2; + + /* PINGPONG_2ADDR_MODE Only */ + /* pair_buf_index stands for pair index of i. (0<->2) (1<->3) */ + + int pair_buf_index = (i^mask); + + /* FIMC have 4 h/w registers */ + if (i < 0 || i >= FIMC_PHYBUFS) { + fimc_err("%s: invalid queue index : %d\n", __func__, i); + return -ENOENT; + } + + if (list_empty(&cap->inq)) + return -ENOENT; + + buf = list_first_entry(&cap->inq, struct fimc_buf_set, list); + + /* pair index buffer should be allocated first */ + cap->outq[pair_buf_index] = buf->id; + fimc_hwset_output_address(ctrl, buf, pair_buf_index); + + cap->outq[i] = buf->id; + fimc_hwset_output_address(ctrl, buf, i); + + if (cap->nr_bufs != 1) + list_del(&buf->list); + + return 0; +} + +static int fimc_update_hwaddr(struct fimc_control *ctrl) +{ + int i; + + for (i = 0; i < FIMC_PINGPONG; i++) + fimc_add_outqueue(ctrl, i); + + return 0; +} + +int fimc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + ret = subdev_call(ctrl, video, g_parm, a); + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +int fimc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + if (ctrl->id != 2) + ret = subdev_call(ctrl, video, s_parm, a); + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +/* Enumerate controls */ +int fimc_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *qc) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int i, ret; + + fimc_dbg("%s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(fimc_controls); i++) { + if (fimc_controls[i].id == qc->id) { + memcpy(qc, &fimc_controls[i], \ + sizeof(struct v4l2_queryctrl)); + return 0; + } + } + + mutex_lock(&ctrl->v4l2_lock); + ret = subdev_call(ctrl, core, queryctrl, qc); + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +/* Menu control items */ +int fimc_querymenu(struct file *file, void *fh, struct v4l2_querymenu *qm) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret; + + fimc_dbg("%s\n", __func__); + + mutex_lock(&ctrl->v4l2_lock); + ret = subdev_call(ctrl, core, querymenu, qm); + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + + +/* Given the index, we will return the camera name if there is any camera + * present at the given id. + */ +int fimc_enum_input(struct file *file, void *fh, struct v4l2_input *inp) +{ + struct fimc_global *fimc = get_fimc_dev(); + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + fimc_dbg("%s: index %d\n", __func__, inp->index); + + if (inp->index < 0 || inp->index >= FIMC_MAXCAMS) { + fimc_err("%s: invalid input index, received = %d\n", \ + __func__, inp->index); + return -EINVAL; + } + + if (!fimc->camera_isvalid[inp->index]) + return -EINVAL; + + strcpy(inp->name, fimc->camera[inp->index].info->type); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +int fimc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_global *fimc = get_fimc_dev(); + + /* In case of isueing g_input before s_input */ + if (!ctrl->cam) { + fimc_err("no camera device selected yet!" \ + "do VIDIOC_S_INPUT first\n"); + return -ENODEV; + } + + *i = (unsigned int) fimc->active_camera; + + fimc_dbg("%s: index %d\n", __func__, *i); + + return 0; +} + +int fimc_release_subdev(struct fimc_control *ctrl) +{ + struct fimc_global *fimc = get_fimc_dev(); + struct i2c_client *client; + + if (ctrl && ctrl->cam && ctrl->cam->sd) { + fimc_dbg("%s called\n", __func__); + client = v4l2_get_subdevdata(ctrl->cam->sd); + i2c_unregister_device(client); + ctrl->cam->sd = NULL; + if (ctrl->cam->cam_power) + ctrl->cam->cam_power(0); + ctrl->cam->initialized = 0; + ctrl->cam = NULL; + fimc->active_camera = -1; + } + return 0; +} + +static int fimc_configure_subdev(struct fimc_control *ctrl) +{ + struct i2c_adapter *i2c_adap; + struct i2c_board_info *i2c_info; + struct v4l2_subdev *sd; + unsigned short addr; + char *name; + + /* set parent for mclk */ + if (clk_get_parent(ctrl->cam->clk->parent)) + clk_set_parent(ctrl->cam->clk->parent, ctrl->cam->srclk); + + /* set rate for mclk */ + if (clk_get_rate(ctrl->cam->clk)) + clk_set_rate(ctrl->cam->clk, ctrl->cam->clk_rate); + + i2c_adap = i2c_get_adapter(ctrl->cam->i2c_busnum); + if (!i2c_adap) + fimc_err("subdev i2c_adapter missing-skip registration\n"); + + i2c_info = ctrl->cam->info; + if (!i2c_info) { + fimc_err("%s: subdev i2c board info missing\n", __func__); + return -ENODEV; + } + + name = i2c_info->type; + if (!name) { + fimc_err("subdev i2c driver name missing-skip registration\n"); + return -ENODEV; + } + + addr = i2c_info->addr; + if (!addr) { + fimc_err("subdev i2c address missing-skip registration\n"); + return -ENODEV; + } + /* + * NOTE: first time subdev being registered, + * s_config is called and try to initialize subdev device + * but in this point, we are not giving MCLK and power to subdev + * so nothing happens but pass platform data through + */ + sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap, + i2c_info, &addr); + if (!sd) { + fimc_err("%s: v4l2 subdev board registering failed\n", + __func__); + } + + /* Assign subdev to proper camera device pointer */ + ctrl->cam->sd = sd; + + return 0; +} + +int fimc_s_input(struct file *file, void *fh, unsigned int i) +{ + struct fimc_global *fimc = get_fimc_dev(); + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + fimc_dbg("%s: index %d\n", __func__, i); + + if (i < 0 || i >= FIMC_MAXCAMS) { + fimc_err("%s: invalid input index\n", __func__); + return -EINVAL; + } + + if (!fimc->camera_isvalid[i]) + return -EINVAL; + + if (fimc->camera[i].sd && ctrl->id != 2) { + fimc_err("%s: Camera already in use.\n", __func__); + return -EBUSY; + } + + mutex_lock(&ctrl->v4l2_lock); + /* If ctrl->cam is not NULL, there is one subdev already registered. + * We need to unregister that subdev first. + */ + if (i != fimc->active_camera) { + fimc_release_subdev(ctrl); + ctrl->cam = &fimc->camera[i]; + ret = fimc_configure_subdev(ctrl); + if (ret < 0) { + mutex_unlock(&ctrl->v4l2_lock); + fimc_err("%s: Could not register camera sensor " + "with V4L2.\n", __func__); + return -ENODEV; + } + fimc->active_camera = i; + } + + if (ctrl->id == 2) { + if (i == fimc->active_camera) { + ctrl->cam = &fimc->camera[i]; + } else { + mutex_unlock(&ctrl->v4l2_lock); + return -EINVAL; + } + } + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_enum_fmt_vid_capture(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int i = f->index; + int num_entries = 0; + int ret = 0; + enum v4l2_mbus_pixelcode code; + + fimc_dbg("%s\n", __func__); + + if (ctrl->out) { + fimc_err("%s: fimc is already used for output mode\n", + __func__); + return -EINVAL; + } + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + num_entries = sizeof(capture_fmts)/sizeof(struct v4l2_fmtdesc); + + if (i >= num_entries) { + mutex_lock(&ctrl->v4l2_lock); + ret = subdev_call(ctrl, video, enum_mbus_fmt, + f->index - num_entries, &code); + mutex_unlock(&ctrl->v4l2_lock); + return ret; + } + + memcpy(f, &capture_fmts[i], sizeof(*f)); + + return 0; +} + +int fimc_g_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cap) { + fimc_err("%s: no capture device info\n", __func__); + return -EINVAL; + } + + mutex_lock(&ctrl->v4l2_lock); + + memcpy(&f->fmt.pix, &ctrl->cap->fmt, sizeof(f->fmt.pix)); + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +/* + * Check for whether the requested format + * can be streamed out from FIMC + * depends on FIMC node + */ +static int fimc_fmt_avail(struct fimc_control *ctrl, + struct v4l2_format *f) +{ + int i; + + /* + * TODO: check for which FIMC is used. + * Available fmt should be varied for each FIMC + */ + + for (i = 0; i < sizeof(capture_fmts); i++) { + if (capture_fmts[i].pixelformat == f->fmt.pix.pixelformat) + return 0; + } + + fimc_err("Not supported pixelformat requested\n"); + + return -1; +} + +/* + * figures out the depth of requested format + */ +static int fimc_fmt_depth(struct fimc_control *ctrl, struct v4l2_format *f) +{ + int err, depth = 0; + + /* First check for available format or not */ + err = fimc_fmt_avail(ctrl, f); + if (err < 0) + return -EINVAL; + + /* handles only supported pixelformats */ + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB32: + depth = 32; + fimc_dbg("32bpp\n"); + break; + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + depth = 16; + fimc_dbg("16bpp\n"); + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12T: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + depth = 12; + fimc_dbg("12bpp\n"); + break; + case V4L2_PIX_FMT_JPEG: + depth = -1; + fimc_dbg("Compressed format.\n"); + break; + default: + fimc_dbg("why am I here? - received %x\n", + f->fmt.pix.pixelformat); + break; + } + + return depth; +} + +int fimc_s_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_capinfo *cap; + struct v4l2_mbus_framefmt mbus_fmt; + int ret = 0; + int depth; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + /* + * The first time alloc for struct cap_info, and will be + * released at the file close. + * Anyone has better idea to do this? + */ + mutex_lock(&ctrl->v4l2_lock); + + if (!ctrl->cap) { + ctrl->cap = kmalloc(sizeof(*cap), GFP_KERNEL); + if (!ctrl->cap) { + mutex_unlock(&ctrl->v4l2_lock); + fimc_err("%s: no memory for " + "capture device info\n", __func__); + return -ENOMEM; + } + + } + cap = ctrl->cap; + memset(cap, 0, sizeof(*cap)); + memcpy(&cap->fmt, &f->fmt.pix, sizeof(cap->fmt)); + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, 0); + + /* + * Note that expecting format only can be with + * available output format from FIMC + * Following items should be handled in driver + * bytesperline = width * depth / 8 + * sizeimage = bytesperline * height + */ + /* This function may return 0 or -1 in case of error, hence need to + * check here. + */ + depth = fimc_fmt_depth(ctrl, f); + if (depth == 0) { + mutex_unlock(&ctrl->v4l2_lock); + fimc_err("%s: Invalid pixel format\n", __func__); + return -EINVAL; + } else if (depth < 0) { + /* + * When the pixelformat is JPEG, the application is requesting + * for data in JPEG compressed format. + */ + mbus_fmt.code = V4L2_MBUS_FMT_FIXED; + ret = subdev_call(ctrl, video, try_mbus_fmt, &mbus_fmt); + if (ret < 0) { + mutex_unlock(&ctrl->v4l2_lock); + return -EINVAL; + } + cap->fmt.colorspace = V4L2_COLORSPACE_JPEG; + } else { + cap->fmt.bytesperline = (cap->fmt.width * depth) >> 3; + cap->fmt.sizeimage = (cap->fmt.bytesperline * cap->fmt.height); + } + + if (cap->fmt.colorspace == V4L2_COLORSPACE_JPEG) { + ctrl->sc.bypass = 1; + cap->lastirq = 1; + } + + if (ctrl->id != 2) { + ret = subdev_call(ctrl, video, s_mbus_fmt, &mbus_fmt); + } + + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +int fimc_try_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f) +{ + return 0; +} + +static int fimc_alloc_buffers(struct fimc_control *ctrl, int size[], int align) +{ + struct fimc_capinfo *cap = ctrl->cap; + int i, plane; + + for (i = 0; i < cap->nr_bufs; i++) { + for (plane = 0; plane < 4; plane++) { + cap->bufs[i].length[plane] = size[plane]; + if (!cap->bufs[i].length[plane]) + continue; + + fimc_dma_alloc(ctrl, &cap->bufs[i], plane, align); + + if (!cap->bufs[i].base[plane]) + goto err_alloc; + } + + cap->bufs[i].state = VIDEOBUF_PREPARED; + cap->bufs[i].id = i; + } + + return 0; + +err_alloc: + for (i = 0; i < cap->nr_bufs; i++) { + if (cap->bufs[i].base[plane]) + fimc_dma_free(ctrl, &cap->bufs[i], plane); + + memset(&cap->bufs[i], 0, sizeof(cap->bufs[i])); + } + + return -ENOMEM; +} + +static void fimc_free_buffers(struct fimc_control *ctrl) +{ + struct fimc_capinfo *cap; + int i; + + if (ctrl && ctrl->cap) + cap = ctrl->cap; + else + return; + + + for (i = 0; i < cap->nr_bufs; i++) { + memset(&cap->bufs[i], 0, sizeof(cap->bufs[i])); + cap->bufs[i].state = VIDEOBUF_NEEDS_INIT; + } + + ctrl->mem.curr = ctrl->mem.base; +} + +int fimc_reqbufs_capture(void *fh, struct v4l2_requestbuffers *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_capinfo *cap = ctrl->cap; + int ret = 0, i; + int size[4] = { 0, 0, 0, 0}; + int align = 0; + + if (b->memory != V4L2_MEMORY_MMAP) { + fimc_err("%s: invalid memory type\n", __func__); + return -EINVAL; + } + + if (!cap) { + fimc_err("%s: no capture device info\n", __func__); + return -ENODEV; + } + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + + if (b->count < 1 || b->count > FIMC_CAPBUFS) + return -EINVAL; + + /* It causes flickering as buf_0 and buf_3 refer to same hardware + * address. + */ + if (b->count == 3) + b->count = 4; + + cap->nr_bufs = b->count; + + fimc_dbg("%s: requested %d buffers\n", __func__, b->count); + + INIT_LIST_HEAD(&cap->inq); + fimc_free_buffers(ctrl); + + switch (cap->fmt.pixelformat) { + case V4L2_PIX_FMT_RGB32: /* fall through */ + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_YUV422P: /* fall through */ + size[0] = cap->fmt.sizeimage; + break; + + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + size[0] = cap->fmt.width * cap->fmt.height; + size[1] = cap->fmt.width * cap->fmt.height; + break; + case V4L2_PIX_FMT_NV12: + size[0] = cap->fmt.width * cap->fmt.height; + size[1] = cap->fmt.width * cap->fmt.height/2; + break; + case V4L2_PIX_FMT_NV21: + size[0] = cap->fmt.width * cap->fmt.height; + size[1] = cap->fmt.width * cap->fmt.height/2; + break; + case V4L2_PIX_FMT_NV12T: + /* Tiled frame size calculations as per 4x2 tiles + * - Width: Has to be aligned to 2 times the tile width + * - Height: Has to be aligned to the tile height + * - Alignment: Has to be aligned to the size of the + * macrotile (size of 4 tiles) + * + * NOTE: In case of rotation, we need modified calculation as + * width and height are aligned to different values. + */ + if (cap->rotate == 90 || cap->rotate == 270) { + size[0] = ALIGN(ALIGN(cap->fmt.height, 128) * + ALIGN(cap->fmt.width, 32), + SZ_8K); + size[1] = ALIGN(ALIGN(cap->fmt.height, 128) * + ALIGN(cap->fmt.width/2, 32), + SZ_8K); + } else { + size[0] = ALIGN(ALIGN(cap->fmt.width, 128) * + ALIGN(cap->fmt.height, 32), + SZ_8K); + size[1] = ALIGN(ALIGN(cap->fmt.width, 128) * + ALIGN(cap->fmt.height/2, 32), + SZ_8K); + } + align = SZ_8K; + break; + + case V4L2_PIX_FMT_YUV420: + size[0] = cap->fmt.width * cap->fmt.height; + size[1] = cap->fmt.width * cap->fmt.height >> 2; + size[2] = cap->fmt.width * cap->fmt.height >> 2; + break; + + case V4L2_PIX_FMT_JPEG: + size[0] = fimc_camera_get_jpeg_memsize(ctrl); + default: + break; + } + + ret = fimc_alloc_buffers(ctrl, size, align); + if (ret) { + fimc_err("%s: no memory for " + "capture buffer\n", __func__); + mutex_unlock(&ctrl->v4l2_lock); + return -ENOMEM; + } + + for (i = cap->nr_bufs; i < FIMC_PHYBUFS; i++) { + memcpy(&cap->bufs[i], \ + &cap->bufs[i - cap->nr_bufs], sizeof(cap->bufs[i])); + } + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_querybuf_capture(void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + if (!ctrl->cap || !ctrl->cap->bufs) { + fimc_err("%s: no capture device info\n", __func__); + return -ENODEV; + } + + if (ctrl->status != FIMC_STREAMOFF) { + fimc_err("fimc is running\n"); + return -EBUSY; + } + + mutex_lock(&ctrl->v4l2_lock); + + b->length = ctrl->cap->bufs[b->index].length[FIMC_ADDR_Y] + + ctrl->cap->bufs[b->index].length[FIMC_ADDR_CB] + + ctrl->cap->bufs[b->index].length[FIMC_ADDR_CR]; + + b->m.offset = b->index * PAGE_SIZE; + + ctrl->cap->bufs[b->index].state = VIDEOBUF_IDLE; + + mutex_unlock(&ctrl->v4l2_lock); + + fimc_dbg("%s: %d bytes at index: %d\n", __func__, b->length, b->index); + + return 0; +} + +int fimc_g_ctrl_capture(void *fh, struct v4l2_control *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd || !ctrl->cap) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + + switch (c->id) { + case V4L2_CID_ROTATION: + c->value = ctrl->cap->rotate; + break; + + case V4L2_CID_HFLIP: + c->value = (ctrl->cap->flip & FIMC_XFLIP) ? 1 : 0; + break; + + case V4L2_CID_VFLIP: + c->value = (ctrl->cap->flip & FIMC_YFLIP) ? 1 : 0; + break; + + default: + /* get ctrl supported by subdev */ + mutex_unlock(&ctrl->v4l2_lock); + ret = subdev_call(ctrl, core, g_ctrl, c); + mutex_lock(&ctrl->v4l2_lock); + break; + } + + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +/** + * We used s_ctrl API to get the physical address of the buffers. + * In g_ctrl, we can pass only one parameter, thus we cannot pass + * the index of the buffer. + * In order to use g_ctrl for obtaining the physical address, we + * will have to create CID ids for all values (4 ids for Y0~Y3 and 4 ids + * for C0~C3). Currently, we will continue with the existing + * implementation till we get any better idea to implement. + */ +int fimc_s_ctrl_capture(void *fh, struct v4l2_control *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + fimc_info2("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd || !ctrl->cap || !ctrl->cap->bufs) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + + switch (c->id) { + case V4L2_CID_ROTATION: + ctrl->cap->rotate = c->value; + break; + + case V4L2_CID_HFLIP: /* fall through */ + ctrl->cap->flip |= FIMC_XFLIP; + break; + case V4L2_CID_VFLIP: + ctrl->cap->flip |= FIMC_YFLIP; + break; + + case V4L2_CID_PADDR_Y: + c->value = ctrl->cap->bufs[c->value].base[FIMC_ADDR_Y]; + break; + + case V4L2_CID_PADDR_CB: /* fall through */ + case V4L2_CID_PADDR_CBCR: + c->value = ctrl->cap->bufs[c->value].base[FIMC_ADDR_CB]; + break; + + case V4L2_CID_PADDR_CR: + c->value = ctrl->cap->bufs[c->value].base[FIMC_ADDR_CR]; + break; + + /* Implementation as per C100 FIMC driver */ + case V4L2_CID_STREAM_PAUSE: + fimc_hwset_stop_processing(ctrl); + break; + + case V4L2_CID_IMAGE_EFFECT_APPLY: + ctrl->fe.ie_on = c->value ? 1 : 0; + ctrl->fe.ie_after_sc = 0; + ret = fimc_hwset_image_effect(ctrl); + break; + + case V4L2_CID_IMAGE_EFFECT_FN: + if (c->value < 0 || c->value > FIMC_EFFECT_FIN_SILHOUETTE) + return -EINVAL; + ctrl->fe.fin = c->value; + ret = 0; + break; + + case V4L2_CID_IMAGE_EFFECT_CB: + ctrl->fe.pat_cb = c->value & 0xFF; + ret = 0; + break; + + case V4L2_CID_IMAGE_EFFECT_CR: + ctrl->fe.pat_cr = c->value & 0xFF; + ret = 0; + break; + + default: + /* try on subdev */ + mutex_unlock(&ctrl->v4l2_lock); + if (2 != ctrl->id) + ret = subdev_call(ctrl, core, s_ctrl, c); + else + ret = 0; + mutex_lock(&ctrl->v4l2_lock); + break; + } + + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +int fimc_s_ext_ctrls_capture(void *fh, struct v4l2_ext_controls *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + mutex_lock(&ctrl->v4l2_lock); + + /* try on subdev */ + ret = subdev_call(ctrl, core, s_ext_ctrls, c); + + mutex_unlock(&ctrl->v4l2_lock); + + return ret; +} + +int fimc_cropcap_capture(void *fh, struct v4l2_cropcap *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_capinfo *cap = ctrl->cap; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd || !ctrl->cap) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + + /* crop limitations */ + cap->cropcap.bounds.left = 0; + cap->cropcap.bounds.top = 0; + cap->cropcap.bounds.width = ctrl->cam->width; + cap->cropcap.bounds.height = ctrl->cam->height; + + /* crop default values */ + cap->cropcap.defrect.left = 0; + cap->cropcap.defrect.top = 0; + cap->cropcap.defrect.width = ctrl->cam->width; + cap->cropcap.defrect.height = ctrl->cam->height; + + a->bounds = cap->cropcap.bounds; + a->defrect = cap->cropcap.defrect; + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_g_crop_capture(void *fh, struct v4l2_crop *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cap) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + a->c = ctrl->cap->crop; + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +static int fimc_capture_crop_size_check(struct fimc_control *ctrl) +{ + struct fimc_capinfo *cap = ctrl->cap; + int win_hor_offset = 0, win_hor_offset2 = 0; + int win_ver_offset = 0, win_ver_offset2 = 0; + int crop_width = 0, crop_height = 0; + + /* check win_hor_offset, win_hor_offset2 */ + win_hor_offset = ctrl->cam->window.left; + win_hor_offset2 = ctrl->cam->width - ctrl->cam->window.left - + ctrl->cam->window.width; + + win_ver_offset = ctrl->cam->window.top; + win_ver_offset2 = ctrl->cam->height - ctrl->cam->window.top - + ctrl->cam->window.height; + + if (win_hor_offset < 0 || win_hor_offset2 < 0) { + fimc_err("%s: Offset (left-side(%d) or right-side(%d) " + "is negative.\n", __func__, \ + win_hor_offset, win_hor_offset2); + return -1; + } + + if (win_ver_offset < 0 || win_ver_offset2 < 0) { + fimc_err("%s: Offset (top-side(%d) or bottom-side(%d)) " + "is negative.\n", __func__, \ + win_ver_offset, win_ver_offset2); + return -1; + } + + if ((win_hor_offset % 2) || (win_hor_offset2 % 2)) { + fimc_err("%s: win_hor_offset must be multiple of 2\n", \ + __func__); + return -1; + } + + /* check crop_width, crop_height */ + crop_width = ctrl->cam->window.width; + crop_height = ctrl->cam->window.height; + + if (crop_width % 16) { + fimc_err("%s: crop_width must be multiple of 16\n", __func__); + return -1; + } + + switch (cap->fmt.pixelformat) { + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + if ((crop_height % 2) || (crop_height < 8)) { + fimc_err("%s: crop_height error!\n", __func__); + return -1; + } + break; + default: + break; + } + + return 0; +} + +/** Given crop parameters are w.r.t. target resolution. Scale + * it w.r.t. camera source resolution. + * + * Steps: + * 1. Scale as camera resolution with fixed-point calculation + * 2. Check for overflow condition + * 3. Apply FIMC constrainsts + */ +static void fimc_capture_update_crop_window(struct fimc_control *ctrl) +{ + unsigned int zoom_hor = 0; + unsigned int zoom_ver = 0; + unsigned int multiplier = 1024; + + if (!ctrl->cam->width || !ctrl->cam->height) + return; + + zoom_hor = ctrl->cap->fmt.width * multiplier / ctrl->cam->width; + zoom_ver = ctrl->cap->fmt.height * multiplier / ctrl->cam->height; + + if (!zoom_hor || !zoom_ver) + return; + + /* Width */ + ctrl->cam->window.width = ctrl->cap->crop.width * multiplier / zoom_hor; + if (ctrl->cam->window.width > ctrl->cam->width) + ctrl->cam->window.width = ctrl->cam->width; + if (ctrl->cam->window.width % 16) + ctrl->cam->window.width = + (ctrl->cam->window.width + 0xF) & ~0xF; + + /* Left offset */ + ctrl->cam->window.left = ctrl->cap->crop.left * multiplier / zoom_hor; + if (ctrl->cam->window.width + ctrl->cam->window.left > ctrl->cam->width) + ctrl->cam->window.left = + (ctrl->cam->width - ctrl->cam->window.width)/2; + if (ctrl->cam->window.left % 2) + ctrl->cam->window.left--; + + /* Height */ + ctrl->cam->window.height = + (ctrl->cap->crop.height * multiplier) / zoom_ver; + if (ctrl->cam->window.top > ctrl->cam->height) + ctrl->cam->window.height = ctrl->cam->height; + if (ctrl->cam->window.height % 2) + ctrl->cam->window.height--; + + /* Top offset */ + ctrl->cam->window.top = ctrl->cap->crop.top * multiplier / zoom_ver; + if (ctrl->cam->window.height + ctrl->cam->window.top > + ctrl->cam->height) + ctrl->cam->window.top = + (ctrl->cam->height - ctrl->cam->window.height)/2; + if (ctrl->cam->window.top % 2) + ctrl->cam->window.top--; + + fimc_dbg("Cam (%dx%d) Crop: (%d %d %d %d) Win: (%d %d %d %d)\n", \ + ctrl->cam->width, ctrl->cam->height, \ + ctrl->cap->crop.left, ctrl->cap->crop.top, \ + ctrl->cap->crop.width, ctrl->cap->crop.height, \ + ctrl->cam->window.left, ctrl->cam->window.top, \ + ctrl->cam->window.width, ctrl->cam->window.height); + +} + +int fimc_s_crop_capture(void *fh, struct v4l2_crop *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = 0; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cap) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + ctrl->cap->crop = a->c; + + fimc_capture_update_crop_window(ctrl); + + ret = fimc_capture_crop_size_check(ctrl); + if (ret < 0) { + mutex_unlock(&ctrl->v4l2_lock); + fimc_err("%s: Invalid crop parameters.\n", __func__); + return -EINVAL; + } + + if (ctrl->status == FIMC_STREAMON && + ctrl->cap->fmt.pixelformat != V4L2_PIX_FMT_JPEG) { + fimc_hwset_shadow_disable(ctrl); + fimc_hwset_camera_offset(ctrl); + fimc_capture_scaler_info(ctrl); + fimc_hwset_prescaler(ctrl, &ctrl->sc); + fimc_hwset_scaler(ctrl, &ctrl->sc); + fimc_hwset_shadow_enable(ctrl); + } + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_start_capture(struct fimc_control *ctrl) +{ + fimc_dbg("%s\n", __func__); + + if (!ctrl->sc.bypass) + fimc_hwset_start_scaler(ctrl); + + fimc_hwset_enable_capture(ctrl, ctrl->sc.bypass); + + return 0; +} + +int fimc_stop_capture(struct fimc_control *ctrl) +{ + fimc_dbg("%s\n", __func__); + + if (ctrl->cap->lastirq) { + fimc_hwset_enable_lastirq(ctrl); + fimc_hwset_disable_capture(ctrl); + fimc_hwset_disable_lastirq(ctrl); + ctrl->cap->lastirq = 0; + } else { + fimc_hwset_disable_capture(ctrl); + } + + fimc_hwset_disable_irq(ctrl); + fimc_hwset_clear_irq(ctrl); + + if (!ctrl->sc.bypass) + fimc_hwset_stop_scaler(ctrl); + else + ctrl->sc.bypass = 0; + + fimc_wait_disable_capture(ctrl); + + return 0; +} + +static void fimc_reset_capture(struct fimc_control *ctrl) +{ + int i; + + ctrl->status = FIMC_READY_OFF; + + fimc_stop_capture(ctrl); + + for (i = 0; i < FIMC_PINGPONG; i++) + fimc_add_inqueue(ctrl, ctrl->cap->outq[i]); + + fimc_hwset_reset(ctrl); + + if (0 != ctrl->id) + fimc_clk_en(ctrl, false); + + ctrl->status = FIMC_STREAMOFF; +} + + +int fimc_streamon_capture(void *fh) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_capinfo *cap = ctrl->cap; + int rot; + int ret; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture device.\n", __func__); + return -ENODEV; + } + + if (ctrl->status == FIMC_STREAMON) { + fimc_err("%s: Camera already running.\n", __func__); + return -EBUSY; + } + + mutex_lock(&ctrl->v4l2_lock); + + if (0 != ctrl->id) + fimc_clk_en(ctrl, true); + + ctrl->status = FIMC_READY_ON; + cap->irq = 0; + + fimc_hwset_enable_irq(ctrl, 0, 1); + + if (!ctrl->cam->initialized) + fimc_camera_init(ctrl); + + if (ctrl->id != 2 && + ctrl->cap->fmt.colorspace != V4L2_COLORSPACE_JPEG) { + ret = fimc_camera_start(ctrl); + if (ret < 0) { + fimc_reset_capture(ctrl); + mutex_unlock(&ctrl->v4l2_lock); + return ret; + } + } + + fimc_hwset_camera_type(ctrl); + fimc_hwset_camera_polarity(ctrl); + fimc_update_hwaddr(ctrl); + + if (cap->fmt.pixelformat != V4L2_PIX_FMT_JPEG) { + fimc_hwset_camera_source(ctrl); + fimc_hwset_camera_offset(ctrl); + + fimc_capture_scaler_info(ctrl); + fimc_hwset_prescaler(ctrl, &ctrl->sc); + fimc_hwset_scaler(ctrl, &ctrl->sc); + + fimc_hwset_output_colorspace(ctrl, cap->fmt.pixelformat); + fimc_hwset_output_addr_style(ctrl, cap->fmt.pixelformat); + fimc_hwset_output_area(ctrl, cap->fmt.width, cap->fmt.height); + + if (cap->fmt.pixelformat == V4L2_PIX_FMT_RGB32 || + cap->fmt.pixelformat == V4L2_PIX_FMT_RGB565) + fimc_hwset_output_rgb(ctrl, cap->fmt.pixelformat); + else + fimc_hwset_output_yuv(ctrl, cap->fmt.pixelformat); + + fimc_hwset_output_size(ctrl, cap->fmt.width, cap->fmt.height); + + fimc_hwset_output_scan(ctrl, &cap->fmt); + fimc_hwset_output_rot_flip(ctrl, cap->rotate, cap->flip); + rot = fimc_mapping_rot_flip(cap->rotate, cap->flip); + + if (rot & FIMC_ROT) { + fimc_hwset_org_output_size(ctrl, cap->fmt.height, + cap->fmt.width); + } else { + fimc_hwset_org_output_size(ctrl, cap->fmt.width, + cap->fmt.height); + } + fimc_hwset_jpeg_mode(ctrl, false); + } else { + fimc_hwset_output_area_size(ctrl, \ + fimc_camera_get_jpeg_memsize(ctrl)/2); + fimc_hwset_jpeg_mode(ctrl, true); + } + + if (ctrl->cap->fmt.colorspace == V4L2_COLORSPACE_JPEG) + fimc_hwset_scaler_bypass(ctrl); + + fimc_start_capture(ctrl); + + if (ctrl->cap->fmt.colorspace == V4L2_COLORSPACE_JPEG && + ctrl->id != 2) { + struct v4l2_control cam_ctrl; + + cam_ctrl.id = V4L2_CID_CAM_CAPTURE; + ret = subdev_call(ctrl, core, s_ctrl, &cam_ctrl); + if (ret < 0 && ret != -ENOIOCTLCMD) { + fimc_reset_capture(ctrl); + mutex_unlock(&ctrl->v4l2_lock); + fimc_err("%s: Error in V4L2_CID_CAM_CAPTURE\n", \ + __func__); + return -EPERM; + } + } + + ctrl->status = FIMC_STREAMON; + + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_streamoff_capture(void *fh) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + fimc_dbg("%s\n", __func__); + + if (!ctrl->cap || !ctrl->cam || !ctrl->cam->sd) { + fimc_err("%s: No capture info.\n", __func__); + return -ENODEV; + } + + mutex_lock(&ctrl->v4l2_lock); + fimc_reset_capture(ctrl); + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + if (!ctrl->cap || !ctrl->cap->nr_bufs) { + fimc_err("%s: Invalid capture setting.\n", __func__); + return -EINVAL; + } + + if (b->memory != V4L2_MEMORY_MMAP) { + fimc_err("%s: invalid memory type\n", __func__); + return -EINVAL; + } + + mutex_lock(&ctrl->v4l2_lock); + fimc_add_inqueue(ctrl, b->index); + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_capinfo *cap; + int pp, ret = 0; + + if (!ctrl->cap || !ctrl->cap->nr_bufs) { + fimc_err("%s: Invalid capture setting.\n", __func__); + return -EINVAL; + } + + if (b->memory != V4L2_MEMORY_MMAP) { + fimc_err("%s: invalid memory type\n", __func__); + return -EINVAL; + } + + cap = ctrl->cap; + + mutex_lock(&ctrl->v4l2_lock); + + if (ctrl->status != FIMC_STREAMON) { + mutex_unlock(&ctrl->v4l2_lock); + fimc_dbg("%s: FIMC is not active.\n", __func__); + return -EINVAL; + } + + /* find out the real index */ + pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4); + + /* We have read the latest frame, hence should reset availability + * flag + */ + cap->irq = 0; + + /* skip even frame: no data */ + if (cap->fmt.field == V4L2_FIELD_INTERLACED_TB) + pp &= ~0x1; + + b->index = cap->outq[pp]; + fimc_dbg("%s: buffer(%d) outq[%d]\n", __func__, b->index, pp); + + ret = fimc_add_outqueue(ctrl, pp); + if (ret) { + b->index = -1; + fimc_err("%s: no inqueue buffer\n", __func__); + } + + mutex_unlock(&ctrl->v4l2_lock); + + /* fimc_dbg("%s: buf_index = %d\n", __func__, b->index); */ + + return ret; +} + diff --git a/drivers/media/video/samsung/fimc/fimc_dev.c b/drivers/media/video/samsung/fimc/fimc_dev.c new file mode 100644 index 0000000..17945d4 --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_dev.c @@ -0,0 +1,1688 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_dev.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Core file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/irq.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <media/v4l2-device.h> +#include <linux/io.h> +#include <linux/memory.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <plat/clock.h> +#include <plat/media.h> +#include <mach/media.h> +#include <plat/fimc.h> +#include <linux/videodev2_samsung.h> +#include <linux/delay.h> +#include <plat/regs-fimc.h> + +#include "fimc.h" + +struct fimc_global *fimc_dev; + +int fimc_dma_alloc(struct fimc_control *ctrl, struct fimc_buf_set *bs, + int i, int align) +{ + dma_addr_t end, *curr; + + mutex_lock(&ctrl->alloc_lock); + + end = ctrl->mem.base + ctrl->mem.size; + curr = &ctrl->mem.curr; + + if (!bs->length[i]) + return -EINVAL; + + if (!align) { + if (*curr + bs->length[i] > end) { + goto overflow; + } else { + bs->base[i] = *curr; + bs->garbage[i] = 0; + *curr += bs->length[i]; + } + } else { + if (ALIGN(*curr, align) + bs->length[i] > end) + goto overflow; + else { + bs->base[i] = ALIGN(*curr, align); + bs->garbage[i] = ALIGN(*curr, align) - *curr; + *curr += (bs->length[i] + bs->garbage[i]); + } + } + + mutex_unlock(&ctrl->alloc_lock); + + return 0; + +overflow: + bs->base[i] = 0; + bs->length[i] = 0; + bs->garbage[i] = 0; + + mutex_unlock(&ctrl->alloc_lock); + + return -ENOMEM; +} + +void fimc_dma_free(struct fimc_control *ctrl, struct fimc_buf_set *bs, int i) +{ + int total = bs->length[i] + bs->garbage[i]; + mutex_lock(&ctrl->alloc_lock); + + if (bs->base[i]) { + if (ctrl->mem.curr - total >= ctrl->mem.base) + ctrl->mem.curr -= total; + + bs->base[i] = 0; + bs->length[i] = 0; + bs->garbage[i] = 0; + } + + mutex_unlock(&ctrl->alloc_lock); +} + +void fimc_clk_en(struct fimc_control *ctrl, bool on) +{ + struct platform_device *pdev; + struct s3c_platform_fimc *pdata; + struct clk *lclk; + + pdev = to_platform_device(ctrl->dev); + pdata = to_fimc_plat(ctrl->dev); + lclk = clk_get(&pdev->dev, pdata->lclk_name); + + if (on) { + if (!lclk->usage) { + if (!ctrl->out) + fimc_info1("(%d) Clock %s(%d) enabled.\n", + ctrl->id, ctrl->clk->name, + ctrl->clk->id); + + /* Turn on fimc power domain regulator */ + regulator_enable(ctrl->regulator); + clk_enable(lclk); + } + } else { + while (lclk->usage > 0) { + if (!ctrl->out) + fimc_info1("(%d) Clock %s(%d) disabled.\n", + ctrl->id, ctrl->clk->name, + ctrl->clk->id); + clk_disable(lclk); + /* Turn off fimc power domain regulator */ + regulator_disable(ctrl->regulator); + } + } + +} + +static inline u32 fimc_irq_out_single_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + int ret = -1, ctx_num, next; + u32 wakeup = 1; + + if (ctx->status == FIMC_READY_OFF) { + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctx->status = FIMC_STREAMOFF; + ctrl->status = FIMC_STREAMOFF; + + return wakeup; + } + + ctx->status = FIMC_STREAMON_IDLE; + + /* Attach done buffer to outgoing queue. */ + ret = fimc_push_outq(ctrl, ctx, ctrl->out->idxs.active.idx); + if (ret < 0) + fimc_err("Failed: fimc_push_outq\n"); + + /* Detach buffer from incomming queue. */ + ret = fimc_pop_inq(ctrl, &ctx_num, &next); + if (ret == 0) { /* There is a buffer in incomming queue. */ + if (ctx_num != ctrl->out->last_ctx) { + ctx = &ctrl->out->ctx[ctx_num]; + ctrl->out->last_ctx = ctx->ctx_num; + fimc_outdev_set_ctx_param(ctrl, ctx); + } + + fimc_outdev_set_src_addr(ctrl, ctx->src[next].base); + + fimc_output_set_dst_addr(ctrl, ctx, next); + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) + fimc_err("Fail: fimc_start_camif\n"); + + ctrl->out->idxs.active.ctx = ctx_num; + ctrl->out->idxs.active.idx = next; + ctx->status = FIMC_STREAMON; + ctrl->status = FIMC_STREAMON; + } else { /* There is no buffer in incomming queue. */ + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctx->status = FIMC_STREAMON_IDLE; + ctrl->status = FIMC_STREAMON_IDLE; + } + + return wakeup; +} + +static inline u32 fimc_irq_out_multi_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + int ret = -1, ctx_num, next; + u32 wakeup = 1; + + if (ctx->status == FIMC_READY_OFF) { + if (ctrl->out->idxs.active.ctx == ctx->ctx_num) { + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + } + + ctx->status = FIMC_STREAMOFF; + + return wakeup; + } + + /* Attach done buffer to outgoing queue. */ + ret = fimc_push_outq(ctrl, ctx, ctrl->out->idxs.active.idx); + if (ret < 0) + fimc_err("Failed: fimc_push_outq\n"); + + /* Detach buffer from incomming queue. */ + ret = fimc_pop_inq(ctrl, &ctx_num, &next); + if (ret == 0) { /* There is a buffer in incomming queue. */ + if (ctx_num != ctrl->out->last_ctx) { + ctx = &ctrl->out->ctx[ctx_num]; + ctrl->out->last_ctx = ctx->ctx_num; + fimc_outdev_set_ctx_param(ctrl, ctx); + } + + fimc_outdev_set_src_addr(ctrl, ctx->src[next].base); + + fimc_output_set_dst_addr(ctrl, ctx, next); + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) + fimc_err("Fail: fimc_start_camif\n"); + + ctrl->out->idxs.active.ctx = ctx_num; + ctrl->out->idxs.active.idx = next; + ctx->status = FIMC_STREAMON; + ctrl->status = FIMC_STREAMON; + } else { /* There is no buffer in incomming queue. */ + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctx->status = FIMC_STREAMON_IDLE; + ctrl->status = FIMC_STREAMON_IDLE; + } + + return wakeup; +} + +static inline u32 fimc_irq_out_dma(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct fimc_buf_set buf_set; + int idx = ctrl->out->idxs.active.idx; + int ret = -1, i, ctx_num, next; + u32 wakeup = 1; + + if (ctx->status == FIMC_READY_OFF) { + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctx->status = FIMC_STREAMOFF; + ctrl->status = FIMC_STREAMOFF; + return wakeup; + } + + /* Attach done buffer to outgoing queue. */ + ret = fimc_push_outq(ctrl, ctx, idx); + if (ret < 0) + fimc_err("Failed: fimc_push_outq\n"); + + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO) { + struct s3cfb_window *win; + struct fb_info *fbinfo; + + fbinfo = registered_fb[ctx->overlay.fb_id]; + win = (struct s3cfb_window *)fbinfo->par; + + win->other_mem_addr = ctx->dst[idx].base[FIMC_ADDR_Y]; + + ret = fb_pan_display(fbinfo, &fbinfo->var); + if (ret < 0) { + fimc_err("%s: fb_pan_display fail (ret=%d)\n", + __func__, ret); + return -EINVAL; + } + } + + /* Detach buffer from incomming queue. */ + ret = fimc_pop_inq(ctrl, &ctx_num, &next); + if (ret == 0) { /* There is a buffer in incomming queue. */ + ctx = &ctrl->out->ctx[ctx_num]; + fimc_outdev_set_src_addr(ctrl, ctx->src[next].base); + + memset(&buf_set, 0x00, sizeof(buf_set)); + buf_set.base[FIMC_ADDR_Y] = ctx->dst[next].base[FIMC_ADDR_Y]; + + for (i = 0; i < FIMC_PHYBUFS; i++) + fimc_hwset_output_address(ctrl, &buf_set, i); + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) + fimc_err("Fail: fimc_start_camif\n"); + + ctrl->out->idxs.active.ctx = ctx_num; + ctrl->out->idxs.active.idx = next; + + ctx->status = FIMC_STREAMON; + ctrl->status = FIMC_STREAMON; + } else { /* There is no buffer in incomming queue. */ + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + + ctx->status = FIMC_STREAMON_IDLE; + ctrl->status = FIMC_STREAMON_IDLE; + } + + return wakeup; +} + +static inline u32 fimc_irq_out_fimd(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct fimc_idx prev; + int ret = -1, ctx_num, next; + u32 wakeup = 0; + + /* Attach done buffer to outgoing queue. */ + if (ctrl->out->idxs.prev.idx != -1) { + ret = fimc_push_outq(ctrl, ctx, ctrl->out->idxs.prev.idx); + if (ret < 0) { + fimc_err("Failed: fimc_push_outq\n"); + } else { + ctrl->out->idxs.prev.ctx = -1; + ctrl->out->idxs.prev.idx = -1; + wakeup = 1; /* To wake up fimc_v4l2_dqbuf */ + } + } + + /* Update index structure. */ + if (ctrl->out->idxs.next.idx != -1) { + ctrl->out->idxs.active.ctx = ctrl->out->idxs.next.ctx; + ctrl->out->idxs.active.idx = ctrl->out->idxs.next.idx; + ctrl->out->idxs.next.idx = -1; + ctrl->out->idxs.next.ctx = -1; + } + + /* Detach buffer from incomming queue. */ + ret = fimc_pop_inq(ctrl, &ctx_num, &next); + if (ret == 0) { /* There is a buffer in incomming queue. */ + prev.ctx = ctrl->out->idxs.active.ctx; + prev.idx = ctrl->out->idxs.active.idx; + + ctrl->out->idxs.prev.ctx = prev.ctx; + ctrl->out->idxs.prev.idx = prev.idx; + + ctrl->out->idxs.next.ctx = ctx_num; + ctrl->out->idxs.next.idx = next; + + /* set source address */ + fimc_outdev_set_src_addr(ctrl, ctx->src[next].base); + } + + return wakeup; +} + +static inline void fimc_irq_out(struct fimc_control *ctrl) +{ + struct fimc_ctx *ctx; + u32 wakeup = 1; + int ctx_num = ctrl->out->idxs.active.ctx; + + /* Interrupt pendding clear */ + fimc_hwset_clear_irq(ctrl); + + /* check context num */ + if (ctx_num < 0 || ctx_num >= FIMC_MAX_CTXS) { + fimc_err("fimc_irq_out: invalid ctx (ctx=%d)\n", ctx_num); + wake_up(&ctrl->wq); + return; + } + + ctx = &ctrl->out->ctx[ctx_num]; + + switch (ctx->overlay.mode) { + case FIMC_OVLY_NONE_SINGLE_BUF: + wakeup = fimc_irq_out_single_buf(ctrl, ctx); + break; + case FIMC_OVLY_NONE_MULTI_BUF: + wakeup = fimc_irq_out_multi_buf(ctrl, ctx); + break; + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + wakeup = fimc_irq_out_dma(ctrl, ctx); + break; + default: + fimc_err("[ctx=%d] fimc_irq_out: wrong overlay.mode (%d)\n", + ctx_num, ctx->overlay.mode); + break; + } + + if (wakeup == 1) + wake_up(&ctrl->wq); +} + +static inline void fimc_irq_cap(struct fimc_control *ctrl) +{ + struct fimc_capinfo *cap = ctrl->cap; + int pp; + u32 cfg; + + fimc_hwset_clear_irq(ctrl); + if (fimc_hwget_overflow_state(ctrl)) { + /* s/w reset -- added for recovering module in ESD state*/ + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg |= (S3C_CIGCTRL_SWRST); + writel(cfg, ctrl->regs + S3C_CIGCTRL); + msleep(1); + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~S3C_CIGCTRL_SWRST; + writel(cfg, ctrl->regs + S3C_CIGCTRL); + } + pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4); + if (cap->fmt.field == V4L2_FIELD_INTERLACED_TB) { + /* odd value of pp means one frame is made with top/bottom */ + if (pp & 0x1) { + cap->irq = 1; + wake_up(&ctrl->wq); + } + } else { + cap->irq = 1; + wake_up(&ctrl->wq); + } +} + +static irqreturn_t fimc_irq(int irq, void *dev_id) +{ + struct fimc_control *ctrl = (struct fimc_control *) dev_id; + + if (ctrl->cap) + fimc_irq_cap(ctrl); + else if (ctrl->out) + fimc_irq_out(ctrl); + + return IRQ_HANDLED; +} + +static +struct fimc_control *fimc_register_controller(struct platform_device *pdev) +{ + struct s3c_platform_fimc *pdata; + struct fimc_control *ctrl; + struct resource *res; + int id, mdev_id; + + id = pdev->id; + mdev_id = S5P_MDEV_FIMC0 + id; + pdata = to_fimc_plat(&pdev->dev); + + ctrl = get_fimc_ctrl(id); + ctrl->id = id; + ctrl->dev = &pdev->dev; + ctrl->vd = &fimc_video_device[id]; + ctrl->vd->minor = id; + + /* alloc from bank1 as default */ + ctrl->mem.base = pdata->pmem_start; + ctrl->mem.size = pdata->pmem_size; + ctrl->mem.curr = ctrl->mem.base; + + ctrl->status = FIMC_STREAMOFF; + switch (pdata->hw_ver) { + case 0x40: + ctrl->limit = &fimc40_limits[id]; + break; + case 0x43: + case 0x45: + ctrl->limit = &fimc43_limits[id]; + break; + case 0x50: + ctrl->limit = &fimc50_limits[id]; + break; + } + + ctrl->log = FIMC_LOG_DEFAULT; + + sprintf(ctrl->name, "%s%d", FIMC_NAME, id); + strcpy(ctrl->vd->name, ctrl->name); + + atomic_set(&ctrl->in_use, 0); + mutex_init(&ctrl->lock); + mutex_init(&ctrl->alloc_lock); + mutex_init(&ctrl->v4l2_lock); + init_waitqueue_head(&ctrl->wq); + + /* get resource for io memory */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + fimc_err("%s: failed to get io memory region\n", __func__); + return NULL; + } + + /* request mem region */ + res = request_mem_region(res->start, res->end - res->start + 1, + pdev->name); + if (!res) { + fimc_err("%s: failed to request io memory region\n", __func__); + return NULL; + } + + /* ioremap for register block */ + ctrl->regs = ioremap(res->start, res->end - res->start + 1); + if (!ctrl->regs) { + fimc_err("%s: failed to remap io region\n", __func__); + return NULL; + } + + /* irq */ + ctrl->irq = platform_get_irq(pdev, 0); + if (request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl)) + fimc_err("%s: request_irq failed\n", __func__); + + fimc_hwset_reset(ctrl); + + return ctrl; +} + +static int fimc_unregister_controller(struct platform_device *pdev) +{ + struct s3c_platform_fimc *pdata; + struct fimc_control *ctrl; + int id = pdev->id; + + pdata = to_fimc_plat(&pdev->dev); + ctrl = get_fimc_ctrl(id); + + free_irq(ctrl->irq, ctrl); + mutex_destroy(&ctrl->lock); + mutex_destroy(&ctrl->alloc_lock); + mutex_destroy(&ctrl->v4l2_lock); + + fimc_clk_en(ctrl, false); + + iounmap(ctrl->regs); + memset(ctrl, 0, sizeof(*ctrl)); + + return 0; +} + +static void fimc_mmap_open(struct vm_area_struct *vma) +{ + struct fimc_global *dev = fimc_dev; + int pri_data = (int)vma->vm_private_data; + u32 id = pri_data / 0x100; + u32 ctx = (pri_data - (id * 0x100)) / 0x10; + u32 idx = pri_data % 0x10; + + atomic_inc(&dev->ctrl[id].out->ctx[ctx].src[idx].mapped_cnt); +} + +static void fimc_mmap_close(struct vm_area_struct *vma) +{ + struct fimc_global *dev = fimc_dev; + int pri_data = (int)vma->vm_private_data; + u32 id = pri_data / 0x100; + u32 ctx = (pri_data - (id * 0x100)) / 0x10; + u32 idx = pri_data % 0x10; + + atomic_dec(&dev->ctrl[id].out->ctx[ctx].src[idx].mapped_cnt); +} + +static struct vm_operations_struct fimc_mmap_ops = { + .open = fimc_mmap_open, + .close = fimc_mmap_close, +}; + +static inline +int fimc_mmap_out_src(struct file *filp, struct vm_area_struct *vma) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + int ctx_id = prv_data->ctx_id; + struct fimc_ctx *ctx = &ctrl->out->ctx[ctx_id]; + u32 start_phy_addr = 0; + u32 size = vma->vm_end - vma->vm_start; + u32 pfn, idx = vma->vm_pgoff; + u32 buf_length = 0; + int pri_data = 0; + + buf_length = PAGE_ALIGN(ctx->src[idx].length[FIMC_ADDR_Y] + + ctx->src[idx].length[FIMC_ADDR_CB] + + ctx->src[idx].length[FIMC_ADDR_CR]); + if (size > PAGE_ALIGN(buf_length)) { + fimc_err("Requested mmap size is too big\n"); + return -EINVAL; + } + + pri_data = (ctrl->id * 0x100) + (ctx_id * 0x10) + idx; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &fimc_mmap_ops; + vma->vm_private_data = (void *)pri_data; + + if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) { + fimc_err("writable mapping must be shared\n"); + return -EINVAL; + } + + start_phy_addr = ctx->src[idx].base[FIMC_ADDR_Y]; + pfn = __phys_to_pfn(start_phy_addr); + + if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { + fimc_err("mmap fail\n"); + return -EINVAL; + } + + vma->vm_ops->open(vma); + + ctx->src[idx].flags |= V4L2_BUF_FLAG_MAPPED; + + return 0; +} + +static inline +int fimc_mmap_out_dst(struct file *filp, struct vm_area_struct *vma, u32 idx) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + int ctx_id = prv_data->ctx_id; + unsigned long pfn = 0, size; + int ret = 0; + + size = vma->vm_end - vma->vm_start; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED; + + pfn = __phys_to_pfn(ctrl->out->ctx[ctx_id].dst[idx].base[0]); + ret = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (ret != 0) + fimc_err("remap_pfn_range fail.\n"); + + return ret; +} + +static inline int fimc_mmap_out(struct file *filp, struct vm_area_struct *vma) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + int ctx_id = prv_data->ctx_id; + int idx = ctrl->out->ctx[ctx_id].overlay.req_idx; + int ret = -1; + + if (idx >= 0) + ret = fimc_mmap_out_dst(filp, vma, idx); + else if (idx == FIMC_MMAP_IDX) + ret = fimc_mmap_out_src(filp, vma); + + return ret; +} + +static inline int fimc_mmap_cap(struct file *filp, struct vm_area_struct *vma) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + u32 size = vma->vm_end - vma->vm_start; + u32 pfn, idx = vma->vm_pgoff; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED; + + /* + * page frame number of the address for a source frame + * to be stored at. + */ + pfn = __phys_to_pfn(ctrl->cap->bufs[idx].base[0]); + + if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) { + fimc_err("%s: writable mapping must be shared\n", __func__); + return -EINVAL; + } + + if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { + fimc_err("%s: mmap fail\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int fimc_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + int ret; + + if (ctrl->cap) + ret = fimc_mmap_cap(filp, vma); + else + ret = fimc_mmap_out(filp, vma); + + return ret; +} + +static u32 fimc_poll(struct file *filp, poll_table *wait) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + struct fimc_capinfo *cap = ctrl->cap; + u32 mask = 0; + + if (cap) { + if (cap->irq || (ctrl->status != FIMC_STREAMON)) { + mask = POLLIN | POLLRDNORM; + cap->irq = 0; + } else { + poll_wait(filp, &ctrl->wq, wait); + } + } + + return mask; +} + +static +ssize_t fimc_read(struct file *filp, char *buf, size_t count, loff_t *pos) +{ + return 0; +} + +static +ssize_t fimc_write(struct file *filp, const char *b, size_t c, loff_t *offset) +{ + return 0; +} + +u32 fimc_mapping_rot_flip(u32 rot, u32 flip) +{ + u32 ret = 0; + + switch (rot) { + case 0: + if (flip & FIMC_XFLIP) + ret |= FIMC_XFLIP; + + if (flip & FIMC_YFLIP) + ret |= FIMC_YFLIP; + break; + + case 90: + ret = FIMC_ROT; + if (flip & FIMC_XFLIP) + ret |= FIMC_XFLIP; + + if (flip & FIMC_YFLIP) + ret |= FIMC_YFLIP; + break; + + case 180: + ret = (FIMC_XFLIP | FIMC_YFLIP); + if (flip & FIMC_XFLIP) + ret &= ~FIMC_XFLIP; + + if (flip & FIMC_YFLIP) + ret &= ~FIMC_YFLIP; + break; + + case 270: + ret = (FIMC_XFLIP | FIMC_YFLIP | FIMC_ROT); + if (flip & FIMC_XFLIP) + ret &= ~FIMC_XFLIP; + + if (flip & FIMC_YFLIP) + ret &= ~FIMC_YFLIP; + break; + } + + return ret; +} + +int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift) +{ + if (src >= tar * 64) { + return -EINVAL; + } else if (src >= tar * 32) { + *ratio = 32; + *shift = 5; + } else if (src >= tar * 16) { + *ratio = 16; + *shift = 4; + } else if (src >= tar * 8) { + *ratio = 8; + *shift = 3; + } else if (src >= tar * 4) { + *ratio = 4; + *shift = 2; + } else if (src >= tar * 2) { + *ratio = 2; + *shift = 1; + } else { + *ratio = 1; + *shift = 0; + } + + return 0; +} + +void fimc_get_nv12t_size(int img_hres, int img_vres, + int *y_size, int *cb_size) +{ + int remain; + int y_hres_byte, y_vres_byte; + int cb_hres_byte, cb_vres_byte; + int y_hres_roundup, y_vres_roundup; + int cb_hres_roundup, cb_vres_roundup; + + /* to make 'img_hres and img_vres' be 16 multiple */ + remain = img_hres % 16; + if (remain != 0) { + remain = 16 - remain; + img_hres = img_hres + remain; + } + remain = img_vres % 16; + if (remain != 0) { + remain = 16 - remain; + img_vres = img_vres + remain; + } + + cb_hres_byte = img_hres; + cb_vres_byte = img_vres; + + y_hres_byte = img_hres - 1; + y_vres_byte = img_vres - 1; + y_hres_roundup = ((y_hres_byte >> 4) >> 3) + 1; + y_vres_roundup = ((y_vres_byte >> 4) >> 2) + 1; + if ((y_vres_byte & 0x20) == 0) { + y_hres_byte = y_hres_byte & 0x7f00; + y_hres_byte = y_hres_byte >> 8; + y_hres_byte = y_hres_byte & 0x7f; + + y_vres_byte = y_vres_byte & 0x7fc0; + y_vres_byte = y_vres_byte >> 6; + y_vres_byte = y_vres_byte & 0x1ff; + + *y_size = y_hres_byte +\ + (y_vres_byte * y_hres_roundup) + 1; + } else { + *y_size = y_hres_roundup * y_vres_roundup; + } + + *y_size = *(y_size) << 13; + + cb_hres_byte = img_hres - 1; + cb_vres_byte = (img_vres >> 1) - 1; + cb_hres_roundup = ((cb_hres_byte >> 4) >> 3) + 1; + cb_vres_roundup = ((cb_vres_byte >> 4) >> 2) + 1; + if ((cb_vres_byte & 0x20) == 0) { + cb_hres_byte = cb_hres_byte & 0x7f00; + cb_hres_byte = cb_hres_byte >> 8; + cb_hres_byte = cb_hres_byte & 0x7f; + + cb_vres_byte = cb_vres_byte & 0x7fc0; + cb_vres_byte = cb_vres_byte >> 6; + cb_vres_byte = cb_vres_byte & 0x1ff; + + *cb_size = cb_hres_byte + (cb_vres_byte * cb_hres_roundup) + 1; + } else { + *cb_size = cb_hres_roundup * cb_vres_roundup; + } + *cb_size = (*cb_size) << 13; + +} + +static int fimc_get_free_ctx(struct fimc_control *ctrl) +{ + int i; + + if (1 != ctrl->id) + return 0; + + for (i = 0; i < FIMC_MAX_CTXS; i++) { + if (ctrl->ctx_busy[i] == 0) { + ctrl->ctx_busy[i] = 1; + fimc_info1("Current context is %d\n", i); + return i; + } + } + + return -1; +} + +static int fimc_open(struct file *filp) +{ + struct fimc_control *ctrl; + struct s3c_platform_fimc *pdata; + struct fimc_prv_data *prv_data; + int in_use; + int ret; + + ctrl = video_get_drvdata(video_devdata(filp)); + pdata = to_fimc_plat(ctrl->dev); + + mutex_lock(&ctrl->lock); + + in_use = atomic_read(&ctrl->in_use); + if (in_use >= FIMC_MAX_CTXS || (in_use && 1 != ctrl->id)) { + fimc_err("%s: Device busy.\n", __func__); + ret = -EBUSY; + goto resource_busy; + } else { + atomic_inc(&ctrl->in_use); + } + in_use = atomic_read(&ctrl->in_use); + + prv_data = kzalloc(sizeof(struct fimc_prv_data), GFP_KERNEL); + if (!prv_data) { + fimc_err("%s: not enough memory\n", __func__); + ret = -ENOMEM; + goto kzalloc_err; + } + + prv_data->ctx_id = fimc_get_free_ctx(ctrl); + if (prv_data->ctx_id < 0) { + fimc_err("%s: Context busy flag not reset.\n", __func__); + ret = -EBUSY; + goto ctx_err; + } + prv_data->ctrl = ctrl; + filp->private_data = prv_data; + + if (in_use == 1) { + fimc_clk_en(ctrl, true); + + if (pdata->hw_ver == 0x40) + fimc_hw_reset_camera(ctrl); + + /* Apply things to interface register */ + fimc_hwset_reset(ctrl); + + if (num_registered_fb > 0) { + struct fb_info *fbinfo = registered_fb[0]; + ctrl->fb.lcd_hres = (int)fbinfo->var.xres; + ctrl->fb.lcd_vres = (int)fbinfo->var.yres; + fimc_info1("%s: fd.lcd_hres=%d fd.lcd_vres=%d\n", + __func__, ctrl->fb.lcd_hres, + ctrl->fb.lcd_vres); + } + + ctrl->mem.curr = ctrl->mem.base; + ctrl->status = FIMC_STREAMOFF; + + if (0 != ctrl->id) + fimc_clk_en(ctrl, false); + } + + mutex_unlock(&ctrl->lock); + + fimc_info1("%s opened.\n", ctrl->name); + + return 0; + +ctx_err: + kfree(prv_data); + +kzalloc_err: + atomic_dec(&ctrl->in_use); + +resource_busy: + mutex_unlock(&ctrl->lock); + return ret; +} + +static int fimc_release(struct file *filp) +{ + struct fimc_prv_data *prv_data = + (struct fimc_prv_data *)filp->private_data; + struct fimc_control *ctrl = prv_data->ctrl; + int ctx_id = prv_data->ctx_id; + struct s3c_platform_fimc *pdata; + struct fimc_overlay_buf *buf; + struct mm_struct *mm = current->mm; + struct fimc_ctx *ctx; + int ret = 0, i; + ctx = &ctrl->out->ctx[ctx_id]; + + pdata = to_fimc_plat(ctrl->dev); + + mutex_lock(&ctrl->lock); + atomic_dec(&ctrl->in_use); + + /* FIXME: turning off actual working camera */ + if (ctrl->cam && ctrl->id != 2) { + /* Unload the subdev (camera sensor) module, + * reset related status flags + */ + fimc_release_subdev(ctrl); + } + + if (ctrl->cap) { + ctrl->mem.curr = ctrl->mem.base; + kfree(filp->private_data); + filp->private_data = NULL; + + for (i = 0; i < FIMC_CAPBUFS; i++) { + fimc_dma_free(ctrl, &ctrl->cap->bufs[i], 0); + fimc_dma_free(ctrl, &ctrl->cap->bufs[i], 1); + fimc_dma_free(ctrl, &ctrl->cap->bufs[i], 2); + } + + fimc_clk_en(ctrl, false); + + kfree(ctrl->cap); + ctrl->cap = NULL; + } + + if (ctrl->out) { + if (ctx->status != FIMC_STREAMOFF) { + fimc_clk_en(ctrl, true); + ret = fimc_outdev_stop_streaming(ctrl, ctx); + fimc_clk_en(ctrl, false); + if (ret < 0) + fimc_err("Fail: fimc_stop_streaming\n"); + + ret = fimc_init_in_queue(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_init_in_queue\n"); + ret = -EINVAL; + goto release_err; + } + + ret = fimc_init_out_queue(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_init_out_queue\n"); + ret = -EINVAL; + goto release_err; + } + + /* Make all buffers DQUEUED state. */ + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].state = VIDEOBUF_IDLE; + ctx->src[i].flags = V4L2_BUF_FLAG_MAPPED; + } + + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO) { + ctrl->mem.curr = ctx->dst[0].base[FIMC_ADDR_Y]; + + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->dst[i].base[FIMC_ADDR_Y] = 0; + ctx->dst[i].length[FIMC_ADDR_Y] = 0; + + ctx->dst[i].base[FIMC_ADDR_CB] = 0; + ctx->dst[i].length[FIMC_ADDR_CB] = 0; + + ctx->dst[i].base[FIMC_ADDR_CR] = 0; + ctx->dst[i].length[FIMC_ADDR_CR] = 0; + } + } + + ctx->status = FIMC_STREAMOFF; + } + + buf = &ctx->overlay.buf; + for (i = 0; i < FIMC_OUTBUFS; i++) { + if (buf->vir_addr[i]) { + ret = do_munmap(mm, buf->vir_addr[i], + buf->size[i]); + if (ret < 0) + fimc_err("%s: do_munmap fail\n", \ + __func__); + } + } + + ctrl->ctx_busy[ctx_id] = 0; + memset(ctx, 0x00, sizeof(struct fimc_ctx)); + + if (atomic_read(&ctrl->in_use) == 0) { + ctrl->status = FIMC_STREAMOFF; + fimc_outdev_init_idxs(ctrl); + + fimc_clk_en(ctrl, false); + + ctrl->mem.curr = ctrl->mem.base; + + kfree(ctrl->out); + ctrl->out = NULL; + + kfree(filp->private_data); + filp->private_data = NULL; + } + } + + /* + * it remain afterimage when I play movie using overlay and exit + */ + if (ctrl->fb.is_enable == 1) { + fimc_info2("WIN_OFF for FIMC%d\n", ctrl->id); + ret = fb_blank(registered_fb[ctx->overlay.fb_id], + FB_BLANK_POWERDOWN); + if (ret < 0) { + fimc_err("%s: fb_blank: fb[%d] " \ + "mode=FB_BLANK_POWERDOWN\n", + __func__, ctx->overlay.fb_id); + ret = -EINVAL; + goto release_err; + } + + ctrl->fb.is_enable = 0; + } + + mutex_unlock(&ctrl->lock); + + fimc_info1("%s released.\n", ctrl->name); + + return 0; + +release_err: + mutex_unlock(&ctrl->lock); + return ret; + +} + +static const struct v4l2_file_operations fimc_fops = { + .owner = THIS_MODULE, + .open = fimc_open, + .release = fimc_release, + .ioctl = video_ioctl2, + .read = fimc_read, + .write = fimc_write, + .mmap = fimc_mmap, + .poll = fimc_poll, +}; + +static void fimc_vdev_release(struct video_device *vdev) +{ + kfree(vdev); +} + +struct video_device fimc_video_device[FIMC_DEVICES] = { + [0] = { + .fops = &fimc_fops, + .ioctl_ops = &fimc_v4l2_ops, + .release = fimc_vdev_release, + }, + [1] = { + .fops = &fimc_fops, + .ioctl_ops = &fimc_v4l2_ops, + .release = fimc_vdev_release, + }, + [2] = { + .fops = &fimc_fops, + .ioctl_ops = &fimc_v4l2_ops, + .release = fimc_vdev_release, + }, +}; + +static int fimc_init_global(struct platform_device *pdev) +{ + struct s3c_platform_fimc *pdata; + struct s3c_platform_camera *cam; + int i; + + pdata = to_fimc_plat(&pdev->dev); + + /* Registering external camera modules. re-arrange order to be sure */ + for (i = 0; i < FIMC_MAXCAMS; i++) { + cam = pdata->camera[i]; + if (!cam) + break; + + cam->srclk = clk_get(&pdev->dev, cam->srclk_name); + if (IS_ERR(cam->srclk)) { + dev_err(&pdev->dev, "%s: failed to get mclk source\n", + __func__); + return -EINVAL; + } + + /* mclk */ + cam->clk = clk_get(&pdev->dev, cam->clk_name); + if (IS_ERR(cam->clk)) { + dev_err(&pdev->dev, "%s: failed to get mclk source\n", + __func__); + clk_put(cam->srclk); + return -EINVAL; + } + + clk_put(cam->clk); + clk_put(cam->srclk); + + /* Assign camera device to fimc */ + memcpy(&fimc_dev->camera[i], cam, sizeof(*cam)); + fimc_dev->camera_isvalid[i] = 1; + fimc_dev->camera[i].initialized = 0; + } + + fimc_dev->active_camera = -1; + fimc_dev->initialized = 1; + + return 0; +} + +static int fimc_show_log_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fimc_control *ctrl; + struct platform_device *pdev; + int id = -1; + + char temp[150]; + + pdev = to_platform_device(dev); + id = pdev->id; + ctrl = get_fimc_ctrl(id); + + sprintf(temp, "\t"); + strcat(buf, temp); + if (ctrl->log & FIMC_LOG_DEBUG) { + sprintf(temp, "FIMC_LOG_DEBUG | "); + strcat(buf, temp); + } + + if (ctrl->log & FIMC_LOG_INFO_L2) { + sprintf(temp, "FIMC_LOG_INFO_L2 | "); + strcat(buf, temp); + } + + if (ctrl->log & FIMC_LOG_INFO_L1) { + sprintf(temp, "FIMC_LOG_INFO_L1 | "); + strcat(buf, temp); + } + + if (ctrl->log & FIMC_LOG_WARN) { + sprintf(temp, "FIMC_LOG_WARN | "); + strcat(buf, temp); + } + + if (ctrl->log & FIMC_LOG_ERR) { + sprintf(temp, "FIMC_LOG_ERR\n"); + strcat(buf, temp); + } + + return strlen(buf); +} + +static int fimc_store_log_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct fimc_control *ctrl; + struct platform_device *pdev; + + const char *p = buf; + char msg[150] = {0, }; + int id = -1; + u32 match = 0; + + pdev = to_platform_device(dev); + id = pdev->id; + ctrl = get_fimc_ctrl(id); + + while (*p != '\0') { + if (!isspace(*p)) + strncat(msg, p, 1); + p++; + } + + ctrl->log = 0; + printk(KERN_INFO "FIMC.%d log level is set as below.\n", id); + + if (strstr(msg, "FIMC_LOG_ERR") != NULL) { + ctrl->log |= FIMC_LOG_ERR; + match = 1; + printk(KERN_INFO "\tFIMC_LOG_ERR\n"); + } + + if (strstr(msg, "FIMC_LOG_WARN") != NULL) { + ctrl->log |= FIMC_LOG_WARN; + match = 1; + printk(KERN_INFO "\tFIMC_LOG_WARN\n"); + } + + if (strstr(msg, "FIMC_LOG_INFO_L1") != NULL) { + ctrl->log |= FIMC_LOG_INFO_L1; + match = 1; + printk(KERN_INFO "\tFIMC_LOG_INFO_L1\n"); + } + + if (strstr(msg, "FIMC_LOG_INFO_L2") != NULL) { + ctrl->log |= FIMC_LOG_INFO_L2; + match = 1; + printk(KERN_INFO "\tFIMC_LOG_INFO_L2\n"); + } + + if (strstr(msg, "FIMC_LOG_DEBUG") != NULL) { + ctrl->log |= FIMC_LOG_DEBUG; + match = 1; + printk(KERN_INFO "\tFIMC_LOG_DEBUG\n"); + } + + if (!match) { + printk(KERN_INFO "FIMC_LOG_ERR \t: Error condition.\n"); + printk(KERN_INFO "FIMC_LOG_WARN \t: WARNING condition.\n"); + printk(KERN_INFO "FIMC_LOG_INFO_L1 \t: V4L2 API without QBUF, DQBUF.\n"); + printk(KERN_INFO "FIMC_LOG_INFO_L2 \t: V4L2 API QBUF, DQBUF.\n"); + printk(KERN_INFO "FIMC_LOG_DEBUG \t: Queue status report.\n"); + } + + return len; +} + +static DEVICE_ATTR(log_level, 0644, \ + fimc_show_log_level, + fimc_store_log_level); + +static int __devinit fimc_probe(struct platform_device *pdev) +{ + struct s3c_platform_fimc *pdata; + struct fimc_control *ctrl; + struct clk *srclk; + int ret; + + if (!fimc_dev) { + fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL); + if (!fimc_dev) { + dev_err(&pdev->dev, "%s: not enough memory\n", + __func__); + return -ENOMEM; + } + } + + ctrl = fimc_register_controller(pdev); + if (!ctrl) { + printk(KERN_ERR "%s: cannot register fimc\n", __func__); + goto err_alloc; + } + + pdata = to_fimc_plat(&pdev->dev); + if (pdata->cfg_gpio) + pdata->cfg_gpio(pdev); + + /* Get fimc power domain regulator */ + ctrl->regulator = regulator_get(&pdev->dev, "pd"); + if (IS_ERR(ctrl->regulator)) { + fimc_err("%s: failed to get resource %s\n", + __func__, "s3c-fimc"); + return PTR_ERR(ctrl->regulator); + } + + /* fimc source clock */ + srclk = clk_get(&pdev->dev, pdata->srclk_name); + if (IS_ERR(srclk)) { + fimc_err("%s: failed to get source clock of fimc\n", + __func__); + goto err_v4l2; + } + + /* fimc clock */ + ctrl->clk = clk_get(&pdev->dev, pdata->clk_name); + if (IS_ERR(ctrl->clk)) { + fimc_err("%s: failed to get fimc clock source\n", + __func__); + goto err_v4l2; + } + + /* set parent for mclk */ + clk_set_parent(ctrl->clk, srclk); + + /* set rate for mclk */ + clk_set_rate(ctrl->clk, pdata->clk_rate); + + /* V4L2 device-subdev registration */ + ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev); + if (ret) { + fimc_err("%s: v4l2 device register failed\n", __func__); + goto err_fimc; + } + + /* things to initialize once */ + if (!fimc_dev->initialized) { + ret = fimc_init_global(pdev); + if (ret) + goto err_v4l2; + } + + /* video device register */ + ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id); + if (ret) { + fimc_err("%s: cannot register video driver\n", __func__); + goto err_v4l2; + } + + video_set_drvdata(ctrl->vd, ctrl); + + ret = device_create_file(&(pdev->dev), &dev_attr_log_level); + if (ret < 0) { + fimc_err("failed to add sysfs entries\n"); + goto err_global; + } + printk(KERN_INFO "FIMC%d registered successfully\n", ctrl->id); + + return 0; + +err_global: + video_unregister_device(ctrl->vd); + +err_v4l2: + v4l2_device_unregister(&ctrl->v4l2_dev); + +err_fimc: + fimc_unregister_controller(pdev); + +err_alloc: + kfree(fimc_dev); + return -EINVAL; + +} + +static int fimc_remove(struct platform_device *pdev) +{ + fimc_unregister_controller(pdev); + + device_remove_file(&(pdev->dev), &dev_attr_log_level); + + kfree(fimc_dev); + fimc_dev = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static inline void fimc_suspend_out_ctx(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: /* fall through */ + case FIMC_OVLY_NONE_SINGLE_BUF: + if (ctx->status == FIMC_STREAMON) { + if (ctx->inq[0] != -1) + fimc_err("%s : %d in queue unstable\n", + __func__, __LINE__); + + fimc_outdev_stop_streaming(ctrl, ctx); + ctx->status = FIMC_ON_SLEEP; + } else if (ctx->status == FIMC_STREAMON_IDLE) { + fimc_outdev_stop_streaming(ctrl, ctx); + ctx->status = FIMC_ON_IDLE_SLEEP; + } else { + ctx->status = FIMC_OFF_SLEEP; + } + + break; + case FIMC_OVLY_NOT_FIXED: + ctx->status = FIMC_OFF_SLEEP; + break; + } +} + +static inline int fimc_suspend_out(struct fimc_control *ctrl) +{ + struct fimc_ctx *ctx; + int i, on_sleep = 0, idle_sleep = 0, off_sleep = 0; + + for (i = 0; i < FIMC_MAX_CTXS; i++) { + ctx = &ctrl->out->ctx[i]; + fimc_suspend_out_ctx(ctrl, ctx); + + switch (ctx->status) { + case FIMC_ON_SLEEP: + on_sleep++; + break; + case FIMC_ON_IDLE_SLEEP: + idle_sleep++; + break; + case FIMC_OFF_SLEEP: + off_sleep++; + break; + default: + break; + } + } + + if (on_sleep) + ctrl->status = FIMC_ON_SLEEP; + else if (idle_sleep) + ctrl->status = FIMC_ON_IDLE_SLEEP; + else + ctrl->status = FIMC_OFF_SLEEP; + + ctrl->out->last_ctx = -1; + + return 0; +} + +static inline int fimc_suspend_cap(struct fimc_control *ctrl) +{ + if (ctrl->cam->id == CAMERA_WB && ctrl->status == FIMC_STREAMON) + fimc_streamoff_capture((void *)ctrl); + ctrl->status = FIMC_ON_SLEEP; + + return 0; +} + +int fimc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fimc_control *ctrl; + struct s3c_platform_fimc *pdata; + int id; + + id = pdev->id; + ctrl = get_fimc_ctrl(id); + pdata = to_fimc_plat(ctrl->dev); + + if (ctrl->out) + fimc_suspend_out(ctrl); + else if (ctrl->cap) + fimc_suspend_cap(ctrl); + else + ctrl->status = FIMC_OFF_SLEEP; + + if (atomic_read(&ctrl->in_use) && ctrl->status != FIMC_OFF_SLEEP) + fimc_clk_en(ctrl, false); + + return 0; +} + +static inline void fimc_resume_out_ctx(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + int ret = -1; + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: + if (ctx->status == FIMC_ON_IDLE_SLEEP) { + fimc_outdev_resume_dma(ctrl, ctx); + ret = fimc_outdev_set_ctx_param(ctrl, ctx); + if (ret < 0) + fimc_err("Fail: fimc_outdev_set_ctx_param\n"); + + ctx->status = FIMC_STREAMON_IDLE; + } else if (ctx->status == FIMC_OFF_SLEEP) { + ctx->status = FIMC_STREAMOFF; + } else { + fimc_err("%s: Abnormal (%d)\n", __func__, ctx->status); + } + + break; + case FIMC_OVLY_DMA_MANUAL: + if (ctx->status == FIMC_ON_IDLE_SLEEP) { + ret = fimc_outdev_set_ctx_param(ctrl, ctx); + if (ret < 0) + fimc_err("Fail: fimc_outdev_set_ctx_param\n"); + + ctx->status = FIMC_STREAMON_IDLE; + + } else if (ctx->status == FIMC_OFF_SLEEP) { + ctx->status = FIMC_STREAMOFF; + } else { + fimc_err("%s: Abnormal (%d)\n", __func__, ctx->status); + } + + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: + if (ctx->status == FIMC_ON_IDLE_SLEEP) { + ret = fimc_outdev_set_ctx_param(ctrl, ctx); + if (ret < 0) + fimc_err("Fail: fimc_outdev_set_ctx_param\n"); + + ctx->status = FIMC_STREAMON_IDLE; + } else if (ctx->status == FIMC_OFF_SLEEP) { + ctx->status = FIMC_STREAMOFF; + } else { + fimc_err("%s: Abnormal (%d)\n", __func__, ctx->status); + } + + break; + default: + ctx->status = FIMC_STREAMOFF; + break; + } +} + +static inline int fimc_resume_out(struct fimc_control *ctrl) +{ + struct fimc_ctx *ctx; + int i; + u32 state = 0; + + for (i = 0; i < FIMC_MAX_CTXS; i++) { + ctx = &ctrl->out->ctx[i]; + fimc_resume_out_ctx(ctrl, ctx); + + switch (ctx->status) { + case FIMC_STREAMON: + state |= FIMC_STREAMON; + break; + case FIMC_STREAMON_IDLE: + state |= FIMC_STREAMON_IDLE; + break; + case FIMC_STREAMOFF: + state |= FIMC_STREAMOFF; + break; + default: + break; + } + } + + if ((state & FIMC_STREAMON) == FIMC_STREAMON) + ctrl->status = FIMC_STREAMON; + else if ((state & FIMC_STREAMON_IDLE) == FIMC_STREAMON_IDLE) + ctrl->status = FIMC_STREAMON_IDLE; + else + ctrl->status = FIMC_STREAMOFF; + + return 0; +} + +static inline int fimc_resume_cap(struct fimc_control *ctrl) +{ + if (ctrl->cam->id == CAMERA_WB) + fimc_streamon_capture((void *)ctrl); + + return 0; +} + +int fimc_resume(struct platform_device *pdev) +{ + struct fimc_control *ctrl; + struct s3c_platform_fimc *pdata; + int in_use, id = pdev->id; + + ctrl = get_fimc_ctrl(id); + pdata = to_fimc_plat(ctrl->dev); + + in_use = atomic_read(&ctrl->in_use); + + if (in_use && ctrl->status != FIMC_OFF_SLEEP) + fimc_clk_en(ctrl, true); + + if (ctrl->out) + fimc_resume_out(ctrl); + else if (ctrl->cap) + fimc_resume_cap(ctrl); + else + ctrl->status = FIMC_STREAMOFF; + + if (in_use && 0 != ctrl->id) + fimc_clk_en(ctrl, false); + + return 0; +} +#else +#define fimc_suspend NULL +#define fimc_resume NULL +#endif + +static struct platform_driver fimc_driver = { + .probe = fimc_probe, + .remove = fimc_remove, + .suspend = fimc_suspend, + .resume = fimc_resume, + .driver = { + .name = FIMC_NAME, + .owner = THIS_MODULE, + }, +}; + +static int fimc_register(void) +{ + platform_driver_register(&fimc_driver); + + return 0; +} + +static void fimc_unregister(void) +{ + platform_driver_unregister(&fimc_driver); +} + +late_initcall(fimc_register); +module_exit(fimc_unregister); + +MODULE_AUTHOR("Dongsoo, Kim <dongsoo45.kim@samsung.com>"); +MODULE_AUTHOR("Jinsung, Yang <jsgood.yang@samsung.com>"); +MODULE_AUTHOR("Jonghun, Han <jonghun.han@samsung.com>"); +MODULE_DESCRIPTION("Samsung Camera Interface (FIMC) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/samsung/fimc/fimc_output.c b/drivers/media/video/samsung/fimc/fimc_output.c new file mode 100644 index 0000000..2269d6a --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_output.c @@ -0,0 +1,2563 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_output.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * V4L2 Output device support file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/slab.h> +#include <linux/bootmem.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/mm.h> +#include <linux/videodev2.h> +#include <linux/videodev2_samsung.h> +#include <media/videobuf-core.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/mman.h> +#include <plat/media.h> +#include <linux/clk.h> + +#include "fimc.h" + +static __u32 fimc_get_pixel_format_type(__u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_RGB565: + return FIMC_RGB; + + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12T: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + return FIMC_YUV420; + + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + return FIMC_YUV422; + + default: + return FIMC_YUV444; + } +} + +void fimc_outdev_set_src_addr(struct fimc_control *ctrl, dma_addr_t *base) +{ + fimc_hwset_addr_change_disable(ctrl); + fimc_hwset_input_address(ctrl, base); + fimc_hwset_addr_change_enable(ctrl); +} + +int fimc_outdev_start_camif(void *param) +{ + struct fimc_control *ctrl = (struct fimc_control *)param; + + fimc_hwset_start_scaler(ctrl); + fimc_hwset_enable_capture(ctrl, 0); /* bypass disable */ + fimc_hwset_start_input_dma(ctrl); + + return 0; +} + +static int fimc_outdev_stop_camif(void *param) +{ + struct fimc_control *ctrl = (struct fimc_control *)param; + + fimc_hwset_stop_input_dma(ctrl); + fimc_hwset_disable_autoload(ctrl); + fimc_hwset_stop_scaler(ctrl); + fimc_hwset_disable_capture(ctrl); + fimc_hwset_sw_reset(ctrl); + + fimc_clk_en(ctrl, false); + return 0; +} + +int fimc_outdev_stop_streaming(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + int ret = 0; + + fimc_dbg("%s: called\n", __func__); + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + if (ctx->status == FIMC_STREAMON_IDLE) + ctx->status = FIMC_STREAMOFF; + else + ctx->status = FIMC_READY_OFF; + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: + if (ctx->status == FIMC_STREAMON_IDLE) + ctx->status = FIMC_STREAMOFF; + else + ctx->status = FIMC_READY_OFF; + ret = wait_event_timeout(ctrl->wq, + (ctx->status == FIMC_STREAMOFF), + FIMC_ONESHOT_TIMEOUT); + if (ret == 0) { + fimc_dump_context(ctrl, ctx); + fimc_err("fail %s: %d\n", __func__, ctx->ctx_num); + } + + break; + default: + break; + } + + return 0; +} + +int fimc_outdev_resume_dma(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + struct v4l2_rect fimd_rect; + struct fb_var_screeninfo var; + struct fb_info *fbinfo; + struct s3cfb_window *win; + int ret = -1, idx; + + fbinfo = registered_fb[ctx->overlay.fb_id]; + win = (struct s3cfb_window *)fbinfo->par; + + memcpy(&var, &fbinfo->var, sizeof(struct fb_var_screeninfo)); + memset(&fimd_rect, 0, sizeof(struct v4l2_rect)); + ret = fimc_fimd_rect(ctrl, ctx, &fimd_rect); + if (ret < 0) { + fimc_err("fimc_fimd_rect fail\n"); + return -EINVAL; + } + + /* set window path & owner */ + win->path = DATA_PATH_DMA; + win->owner = DMA_MEM_OTHER; + win->other_mem_addr = ctx->dst[1].base[FIMC_ADDR_Y]; + win->other_mem_size = ctx->dst[1].length[FIMC_ADDR_Y]; + + /* Update WIN size */ + var.xres_virtual = fimd_rect.width; + var.yres_virtual = fimd_rect.height; + var.xres = fimd_rect.width; + var.yres = fimd_rect.height; + + /* Update WIN position */ + win->x = fimd_rect.left; + win->y = fimd_rect.top; + + var.activate = FB_ACTIVATE_FORCE; + ret = fb_set_var(fbinfo, &var); + if (ret < 0) { + fimc_err("fb_set_var fail (ret=%d)\n", ret); + return -EINVAL; + } + + idx = ctx->outq[0]; + if (idx == -1) { + fimc_err("out going queue is empty.\n"); + return -EINVAL; + } + + win->other_mem_addr = ctx->dst[idx].base[FIMC_ADDR_Y]; + ret = fb_pan_display(fbinfo, &fbinfo->var); + if (ret < 0) { + fimc_err("%s: fb_pan_display fail (ret=%d)\n", __func__, ret); + return -EINVAL; + } + + ctrl->fb.is_enable = 1; + + return 0; +} + +static void fimc_init_out_buf(struct fimc_ctx *ctx) +{ + int i; + + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].state = VIDEOBUF_IDLE; + ctx->src[i].flags = 0x0; + + ctx->inq[i] = -1; + ctx->outq[i] = -1; + } +} + +static int fimc_outdev_set_src_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + u32 width = ctx->pix.width; + u32 height = ctx->pix.height; + u32 format = ctx->pix.pixelformat; + u32 y_size = width * height; + u32 cb_size = 0, cr_size = 0; + u32 i, size; + dma_addr_t *curr = &ctrl->mem.curr; + + switch (format) { + case V4L2_PIX_FMT_RGB32: + size = PAGE_ALIGN(y_size << 2); + break; + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YUYV: + size = PAGE_ALIGN(y_size << 1); + break; + case V4L2_PIX_FMT_YUV420: + cb_size = y_size >> 2; + cr_size = y_size >> 2; + size = PAGE_ALIGN(y_size + cb_size + cr_size); + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + cb_size = y_size >> 1; + size = PAGE_ALIGN(y_size + cb_size); + break; + case V4L2_PIX_FMT_NV12T: + fimc_get_nv12t_size(width, height, &y_size, &cb_size); + size = PAGE_ALIGN(y_size + cb_size); + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + cb_size = y_size; + size = PAGE_ALIGN(y_size + cb_size); + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", __func__, format); + return -EINVAL; + } + + if ((*curr + size * FIMC_OUTBUFS) > (ctrl->mem.base + ctrl->mem.size)) { + fimc_err("%s: Reserved memory is not sufficient\n", __func__); + return -EINVAL; + } + + /* Initialize source buffer addr */ + switch (format) { + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_RGB32: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YUYV: + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].base[FIMC_ADDR_Y] = *curr; + ctx->src[i].length[FIMC_ADDR_Y] = size; + ctx->src[i].base[FIMC_ADDR_CB] = 0; + ctx->src[i].length[FIMC_ADDR_CB] = 0; + ctx->src[i].base[FIMC_ADDR_CR] = 0; + ctx->src[i].length[FIMC_ADDR_CR] = 0; + *curr += size; + } + break; + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_NV12T: + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].base[FIMC_ADDR_Y] = *curr; + ctx->src[i].length[FIMC_ADDR_Y] = y_size; + ctx->src[i].base[FIMC_ADDR_CB] = *curr + y_size; + ctx->src[i].length[FIMC_ADDR_CB] = cb_size; + ctx->src[i].base[FIMC_ADDR_CR] = 0; + ctx->src[i].length[FIMC_ADDR_CR] = 0; + *curr += size; + } + break; + case V4L2_PIX_FMT_YUV420: + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].base[FIMC_ADDR_Y] = *curr; + ctx->src[i].base[FIMC_ADDR_CB] = *curr + y_size; + ctx->src[i].base[FIMC_ADDR_CR] = *curr + y_size + + cb_size; + ctx->src[i].length[FIMC_ADDR_Y] = y_size; + ctx->src[i].length[FIMC_ADDR_CB] = cb_size; + ctx->src[i].length[FIMC_ADDR_CR] = cr_size; + *curr += size; + } + break; + + default: + fimc_err("%s: Invalid pixelformt : %d\n", __func__, format); + return -EINVAL; + } + + return 0; +} + +static int fimc_outdev_set_dst_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + dma_addr_t *curr = &ctrl->mem.curr; + dma_addr_t end; + u32 width = ctrl->fb.lcd_hres; + u32 height = ctrl->fb.lcd_vres; + u32 i, size; + + end = ctrl->mem.base + ctrl->mem.size; + size = PAGE_ALIGN(width * height * 4); + + if ((*curr + (size * FIMC_OUTBUFS)) > end) { + fimc_err("%s: Reserved memory is not sufficient\n", __func__); + return -EINVAL; + } + + /* Initialize destination buffer addr */ + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->dst[i].base[FIMC_ADDR_Y] = *curr; + ctx->dst[i].length[FIMC_ADDR_Y] = size; + ctx->dst[i].base[FIMC_ADDR_CB] = 0; + ctx->dst[i].length[FIMC_ADDR_CB] = 0; + ctx->dst[i].base[FIMC_ADDR_CR] = 0; + ctx->dst[i].length[FIMC_ADDR_CR] = 0; + *curr += size; + } + + return 0; +} + +static int fimc_set_rot_degree(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + int degree) +{ + switch (degree) { + case 0: /* fall through */ + case 90: /* fall through */ + case 180: /* fall through */ + case 270: + ctx->rotate = degree; + break; + + default: + fimc_err("Invalid rotate value : %d\n", degree); + return -EINVAL; + } + + return 0; +} + +int fimc_outdev_check_param(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_rect dst, bound; + u32 rot = 0; + int ret = 0, i, exclusive = 0; + + rot = fimc_mapping_rot_flip(ctx->rotate, ctx->flip); + dst.top = ctx->win.w.top; + dst.left = ctx->win.w.left; + dst.width = ctx->win.w.width; + dst.height = ctx->win.w.height; + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + if (rot & FIMC_ROT) { + bound.width = ctrl->fb.lcd_vres; + bound.height = ctrl->fb.lcd_hres; + } else { + bound.width = ctrl->fb.lcd_hres; + bound.height = ctrl->fb.lcd_vres; + } + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: + bound.width = ctx->fbuf.fmt.width; + bound.height = ctx->fbuf.fmt.height; + break; + + default: + fimc_err("%s: invalid ovelay mode.\n", __func__); + return -EINVAL; + } + + if ((dst.left + dst.width) > bound.width) { + fimc_err("Horizontal position setting is failed\n"); + fimc_err("\tleft = %d, width = %d, bound width = %d,\n", + dst.left, dst.width, bound.width); + ret = -EINVAL; + } else if ((dst.top + dst.height) > bound.height) { + fimc_err("Vertical position setting is failed\n"); + fimc_err("\ttop = %d, height = %d, bound height = %d,\n", + dst.top, dst.height, bound.height); + ret = -EINVAL; + } + + if ((ctx->status != FIMC_STREAMOFF) && + (ctx->status != FIMC_READY_ON) && + (ctx->status != FIMC_ON_IDLE_SLEEP)) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + /* check other open instance */ + for (i = 0; i < FIMC_MAX_CTXS; i++) { + switch (ctrl->out->ctx[i].overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + exclusive++; + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: /* fall through */ + case FIMC_OVLY_NOT_FIXED: + break; + } + } + + if (exclusive > 1) { + for (i = 0; i < FIMC_MAX_CTXS; i++) + fimc_err("%s: ctx %d mode = %d", __func__, i, + ctrl->out->ctx[i].overlay.mode); + return -EBUSY; + } + + return ret; +} + +static void fimc_outdev_set_src_format(struct fimc_control *ctrl, + u32 pixfmt, enum v4l2_field field) +{ + fimc_hwset_input_burst_cnt(ctrl, 4); + fimc_hwset_input_colorspace(ctrl, pixfmt); + fimc_hwset_input_yuv(ctrl, pixfmt); + fimc_hwset_input_rgb(ctrl, pixfmt); + fimc_hwset_intput_field(ctrl, field); + fimc_hwset_ext_rgb(ctrl, 1); + fimc_hwset_input_addr_style(ctrl, pixfmt); +} + +static void fimc_outdev_set_dst_format(struct fimc_control *ctrl, + struct v4l2_pix_format *pixfmt) +{ + fimc_hwset_output_colorspace(ctrl, pixfmt->pixelformat); + fimc_hwset_output_yuv(ctrl, pixfmt->pixelformat); + fimc_hwset_output_rgb(ctrl, pixfmt->pixelformat); + fimc_hwset_output_scan(ctrl, pixfmt); + fimc_hwset_output_addr_style(ctrl, pixfmt->pixelformat); +} + +static void fimc_outdev_set_format(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_pix_format pixfmt; + memset(&pixfmt, 0, sizeof(pixfmt)); + + fimc_outdev_set_src_format(ctrl, ctx->pix.pixelformat, ctx->pix.field); + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: /* Non-destructive overlay mode */ + if (ctx->pix.field == V4L2_FIELD_NONE) { + pixfmt.pixelformat = V4L2_PIX_FMT_RGB32; + pixfmt.field = V4L2_FIELD_NONE; + } else if (ctx->pix.field == V4L2_FIELD_INTERLACED_TB) { + pixfmt.pixelformat = V4L2_PIX_FMT_YUV444; + pixfmt.field = V4L2_FIELD_INTERLACED_TB; + } else if (ctx->pix.field == V4L2_FIELD_ANY) { + pixfmt.pixelformat = V4L2_PIX_FMT_RGB32; + pixfmt.field = V4L2_FIELD_NONE; + } + + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: /* Destructive overlay mode */ + pixfmt.pixelformat = ctx->fbuf.fmt.pixelformat; + pixfmt.field = V4L2_FIELD_NONE; + + break; + default: + fimc_err("Invalid overlay mode %d\n", ctx->overlay.mode); + break; + } + + fimc_outdev_set_dst_format(ctrl, &pixfmt); +} + +static void fimc_outdev_set_path(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + /* source path */ + fimc_hwset_input_source(ctrl, FIMC_SRC_MSDMA); + + fimc_hwset_disable_lcdfifo(ctrl); + fimc_hwset_disable_autoload(ctrl); +} + +static void fimc_outdev_set_rot(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + u32 rot = ctx->rotate; + u32 flip = ctx->flip; + + fimc_hwset_input_rot(ctrl, 0, 0); + fimc_hwset_input_flip(ctrl, 0, 0); + fimc_hwset_output_rot_flip(ctrl, rot, flip); +} + +static void fimc_outdev_set_src_dma_offset(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_rect bound, crop; + u32 pixfmt = ctx->pix.pixelformat; + + bound.width = ctx->pix.width; + bound.height = ctx->pix.height; + + crop.left = ctx->crop.left; + crop.top = ctx->crop.top; + crop.width = ctx->crop.width; + crop.height = ctx->crop.height; + + fimc_hwset_input_offset(ctrl, pixfmt, &bound, &crop); +} + +static int fimc4x_outdev_check_src_size(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_rect *real, + struct v4l2_rect *org) +{ + /* No Input Rotator */ + if (real->height < 8) { + fimc_err("SRC Real_H: Min 8\n"); + return -EINVAL; + } + + if (real->width < 16) { + fimc_err("SRC Real_W: Min 16\n"); + return -EINVAL; + } + + if (real->width > ctrl->limit->real_w_no_rot) { + fimc_err("SRC REAL_W: Real_W <= %d\n", + ctrl->limit->real_w_no_rot); + return -EINVAL; + } + + if (org->height < real->height) { + fimc_err("SRC Org_H: larger than Real_H\n"); + return -EINVAL; + } + + if (org->width < real->width) { + fimc_err("SRC Org_W: Org_W >= Real_W\n"); + return -EINVAL; + } + + if (ctx->sc.pre_vratio) { + if (real->height % ctx->sc.pre_vratio) { + fimc_err("SRC Real_H: multiple of pre_vratio!\n"); + return -EINVAL; + } + } + + if (real->width % 16) { + fimc_err("SRC Real_W: multiple of 16 !\n"); + return -EINVAL; + } + + if (ctx->sc.pre_hratio) { + if (real->width % (ctx->sc.pre_hratio * 4)) { + fimc_err("SRC Real_W: multiple of 4 * pre_hratio!\n"); + return -EINVAL; + } + } + + if (org->width % 16) { + fimc_err("SRC Org_W: multiple of 16\n"); + return -EINVAL; + } + + if (org->height < 8) { + fimc_err("SRC Org_H: Min 8\n"); + return -EINVAL; + } + + return 0; +} + +static int fimc50_outdev_check_src_size(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_rect *real, + struct v4l2_rect *org) +{ + u32 pixelformat = ctx->pix.pixelformat; + + /* No Input Rotator */ + if (real->height < 8) { + fimc_err("SRC Real_H: Min 8\n"); + return -EINVAL; + } + + if (real->width < 16) { + fimc_err("SRC Real_W: Min 16\n"); + return -EINVAL; + } + + if (real->width > ctrl->limit->real_w_no_rot) { + fimc_err("SRC REAL_W: Real_W <= %d\n", + ctrl->limit->real_w_no_rot); + return -EINVAL; + } + + if (org->height < real->height) { + fimc_err("SRC Org_H: larger than Real_H\n"); + return -EINVAL; + } + + if (org->width < real->width) { + fimc_err("SRC Org_W: Org_W >= Real_W\n"); + return -EINVAL; + } + + if (ctx->pix.field == V4L2_FIELD_INTERLACED_TB) { + switch (pixelformat) { + case V4L2_PIX_FMT_YUV444: /* fall through */ + case V4L2_PIX_FMT_RGB32: + if (real->height % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } + case V4L2_PIX_FMT_YUV422P: + if (real->height % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } else if (real->width % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } + case V4L2_PIX_FMT_YVU420: + if (real->height % 4) { + fimc_err("SRC Real_H: multiple of 4\n"); + return -EINVAL; + } else if (real->width % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } + } + } else if (ctx->pix.field == V4L2_FIELD_NONE) { + if (pixelformat == V4L2_PIX_FMT_YUV422P) { + if (real->height % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } else if (real->width % 2) { + fimc_err("SRC Real_H: multiple of 2\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static int fimc_outdev_set_src_dma_size(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + struct v4l2_rect real, org; + int ret = 0; + + real.width = ctx->crop.width; + real.height = ctx->crop.height; + org.width = ctx->pix.width; + org.height = ctx->pix.height; + + if (pdata->hw_ver == 0x50) + ret = fimc50_outdev_check_src_size(ctrl, ctx, &real, &org); + else + ret = fimc4x_outdev_check_src_size(ctrl, ctx, &real, &org); + + if (ret < 0) + return ret; + + fimc_hwset_org_input_size(ctrl, org.width, org.height); + fimc_hwset_real_input_size(ctrl, real.width, real.height); + + return 0; +} + +static void fimc_outdev_set_dst_dma_offset(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_rect bound, win; + struct v4l2_rect *w = &ctx->win.w; + u32 pixfmt = ctx->fbuf.fmt.pixelformat; + + memset(&bound, 0, sizeof(bound)); + memset(&win, 0, sizeof(win)); + + switch (ctx->rotate) { + case 0: + bound.width = ctx->fbuf.fmt.width; + bound.height = ctx->fbuf.fmt.height; + + win.left = w->left; + win.top = w->top; + win.width = w->width; + win.height = w->height; + + break; + case 90: + bound.width = ctx->fbuf.fmt.height; + bound.height = ctx->fbuf.fmt.width; + + win.left = ctx->fbuf.fmt.height - (w->height + w->top); + win.top = w->left; + win.width = w->height; + win.height = w->width; + + break; + case 180: + bound.width = ctx->fbuf.fmt.width; + bound.height = ctx->fbuf.fmt.height; + + win.left = ctx->fbuf.fmt.width - (w->left + w->width); + win.top = ctx->fbuf.fmt.height - (w->top + w->height); + win.width = w->width; + win.height = w->height; + + break; + case 270: + bound.width = ctx->fbuf.fmt.height; + bound.height = ctx->fbuf.fmt.width; + + win.left = ctx->win.w.top; + win.top = ctx->fbuf.fmt.width - (w->left + w->width); + win.width = w->height; + win.height = w->width; + + break; + default: + fimc_err("Rotation degree is invalid\n"); + break; + } + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + memset(&bound, 0, sizeof(bound)); + memset(&win, 0, sizeof(win)); + fimc_hwset_output_offset(ctrl, pixfmt, &bound, &win); + break; + default: + fimc_hwset_output_offset(ctrl, pixfmt, &bound, &win); + break; + } + + fimc_dbg("bound:width(%d), height(%d)\n", bound.width, bound.height); + fimc_dbg("win:width(%d), height(%d)\n", win.width, win.height); + fimc_dbg("win:top(%d), left(%d)\n", win.top, win.left); +} + +static int fimc_outdev_check_dst_size(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_rect *real, + struct v4l2_rect *org) +{ + u32 rot = ctx->rotate; + __u32 pixel_type; + + pixel_type = fimc_get_pixel_format_type(ctx->fbuf.fmt.pixelformat); + + if (FIMC_YUV420 == pixel_type && real->height % 2) { + fimc_err("DST Real_H: even number for YUV420 formats\n"); + return -EINVAL; + } + + if ((rot == 90) || (rot == 270)) { + /* Use Output Rotator */ + if (org->height < real->width) { + fimc_err("DST Org_H: Org_H(%d) >= Real_W(%d)\n", + org->height, real->width); + return -EINVAL; + } + + if (org->width < real->height) { + fimc_err("DST Org_W: Org_W(%d) >= Real_H(%d)\n", + org->width, real->height); + return -EINVAL; + } + + if (real->height > ctrl->limit->trg_h_rot) { + fimc_err("DST REAL_H: Real_H <= %d\n", + ctrl->limit->trg_h_rot); + return -EINVAL; + } + } else { + /* No Output Rotator */ + if (org->height < 8) { + fimc_err("DST Org_H: Min 8\n"); + return -EINVAL; + } + + if (org->height < real->height) { + fimc_err("DST Org_H: Org_H >= Real_H\n"); + return -EINVAL; + } + + if (org->width % 8) { + fimc_err("DST Org_W: multiple of 8\n"); + return -EINVAL; + } + + if (org->width < real->width) { + fimc_err("DST Org_W: Org_W >= Real_W\n"); + return -EINVAL; + } + + if (real->height > ctrl->limit->trg_h_no_rot) { + fimc_err("DST REAL_H: Real_H <= %d\n", + ctrl->limit->trg_h_no_rot); + return -EINVAL; + } + } + + return 0; +} + +static int fimc_outdev_set_dst_dma_size(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_rect org, real; + int ret = -1; + + memset(&org, 0, sizeof(org)); + memset(&real, 0, sizeof(real)); + + switch (ctx->overlay.mode) { + case FIMC_OVLY_NONE_MULTI_BUF: /* fall through */ + case FIMC_OVLY_NONE_SINGLE_BUF: + real.width = ctx->win.w.width; + real.height = ctx->win.w.height; + + switch (ctx->rotate) { + case 0: /* fall through */ + case 180: + org.width = ctx->fbuf.fmt.width; + org.height = ctx->fbuf.fmt.height; + break; + case 90: /* fall through */ + case 270: + org.width = ctx->fbuf.fmt.height; + org.height = ctx->fbuf.fmt.width; + break; + default: + fimc_err("Rotation degree is invalid\n"); + break; + } + + break; + + case FIMC_OVLY_DMA_MANUAL: /* fall through */ + case FIMC_OVLY_DMA_AUTO: + real.width = ctx->win.w.width; + real.height = ctx->win.w.height; + + switch (ctx->rotate) { + case 0: /* fall through */ + case 180: + org.width = ctx->win.w.width; + org.height = ctx->win.w.height; + break; + case 90: /* fall through */ + case 270: + org.width = ctx->win.w.height; + org.height = ctx->win.w.width; + break; + default: + fimc_err("Rotation degree is invalid\n"); + break; + } + + break; + default: + break; + } + + fimc_dbg("DST org: width(%d), height(%d)\n", org.width, org.height); + fimc_dbg("DST real: width(%d), height(%d)\n", real.width, real.height); + + ret = fimc_outdev_check_dst_size(ctrl, ctx, &real, &org); + if (ret < 0) + return ret; + + fimc_hwset_output_size(ctrl, real.width, real.height); + fimc_hwset_output_area(ctrl, real.width, real.height); + fimc_hwset_org_output_size(ctrl, org.width, org.height); + fimc_hwset_ext_output_size(ctrl, real.width, real.height); + + return 0; +} + +static void fimc_outdev_calibrate_scale_info(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_rect *src, + struct v4l2_rect *dst) +{ + /* OUTPUT ROTATOR */ + src->width = ctx->crop.width; + src->height = ctx->crop.height; + dst->width = ctx->win.w.width; + dst->height = ctx->win.w.height; + + fimc_dbg("src->width(%d), src->height(%d)\n", src->width, src->height); + fimc_dbg("dst->width(%d), dst->height(%d)\n", dst->width, dst->height); +} + +static int fimc_outdev_check_scaler(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_rect *src, + struct v4l2_rect *dst) +{ + u32 pixels = 0, dstfmt = 0; + + /* Check scaler limitation */ + if (ctx->sc.pre_dst_width > ctrl->limit->pre_dst_w) { + fimc_err("FIMC%d : MAX PreDstWidth is %d\n", + ctrl->id, ctrl->limit->pre_dst_w); + return -EDOM; + } + + /* SRC width double boundary check */ + switch (ctx->pix.pixelformat) { + case V4L2_PIX_FMT_RGB32: + pixels = 1; + break; + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_RGB565: + pixels = 2; + break; + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: + pixels = 8; + break; + default: + fimc_err("Invalid color format\n"); + return -EINVAL; + } + + if (src->width % pixels) { + fimc_err("source width multiple of %d pixels\n", pixels); + return -EDOM; + } + + /* DST width double boundary check */ + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + dstfmt = V4L2_PIX_FMT_RGB32; + break; + case FIMC_OVLY_NONE_SINGLE_BUF: /* fall through */ + case FIMC_OVLY_NONE_MULTI_BUF: + dstfmt = ctx->fbuf.fmt.pixelformat; + break; + default: + break; + } + + switch (dstfmt) { + case V4L2_PIX_FMT_RGB32: + pixels = 1; + break; + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + pixels = 2; + break; + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: + pixels = 8; + break; + default: + fimc_err("Invalid color format\n"); + return -EINVAL; + } + + if (dst->width % pixels) { + fimc_err("source width multiple of %d pixels\n", pixels); + return -EDOM; + } + + return 0; +} + +static int fimc_outdev_set_scaler(struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + struct v4l2_rect src, dst; + int ret = 0; + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + + memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); + + fimc_outdev_calibrate_scale_info(ctrl, ctx, &src, &dst); + + ret = fimc_get_scaler_factor(src.width, dst.width, + &ctx->sc.pre_hratio, &ctx->sc.hfactor); + if (ret < 0) { + fimc_err("Fail : Out of Width scale range\n"); + return ret; + } + + ret = fimc_get_scaler_factor(src.height, dst.height, + &ctx->sc.pre_vratio, &ctx->sc.vfactor); + if (ret < 0) { + fimc_err("Fail : Out of Height scale range\n"); + return ret; + } + + ctx->sc.pre_dst_width = src.width / ctx->sc.pre_hratio; + ctx->sc.pre_dst_height = src.height / ctx->sc.pre_vratio; + + if (pdata->hw_ver == 0x50) { + ctx->sc.main_hratio = (src.width << 14) / + (dst.width << ctx->sc.hfactor); + ctx->sc.main_vratio = (src.height << 14) / + (dst.height << ctx->sc.vfactor); + } else { + ctx->sc.main_hratio = (src.width << 8) / + (dst.width << ctx->sc.hfactor); + ctx->sc.main_vratio = (src.height << 8) / + (dst.height << ctx->sc.vfactor); + } + + fimc_dbg("pre_hratio(%d), hfactor(%d), pre_vratio(%d), vfactor(%d)\n", + ctx->sc.pre_hratio, ctx->sc.hfactor, + ctx->sc.pre_vratio, ctx->sc.vfactor); + + + fimc_dbg("pre_dst_width(%d), main_hratio(%d), " + "pre_dst_height(%d), main_vratio(%d)\n", + ctx->sc.pre_dst_width, ctx->sc.main_hratio, + ctx->sc.pre_dst_height, ctx->sc.main_vratio); + + ctx->sc.bypass = 0; /* Input DMA cannot support scaler bypass. */ + ctx->sc.scaleup_h = (dst.width >= src.width) ? 1 : 0; + ctx->sc.scaleup_v = (dst.height >= src.height) ? 1 : 0; + ctx->sc.shfactor = 10 - (ctx->sc.hfactor + ctx->sc.vfactor); + + if (pdata->hw_ver != 0x50) { + ret = fimc_outdev_check_scaler(ctrl, ctx, &src, &dst); + if (ret < 0) + return ret; + } + + fimc_hwset_prescaler(ctrl, &ctx->sc); + fimc_hwset_scaler(ctrl, &ctx->sc); + + return 0; +} + +int fimc_outdev_set_ctx_param(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + int ret; + + if (ctrl->status == FIMC_READY_ON || ctrl->status == FIMC_STREAMON_IDLE) + fimc_hwset_enable_irq(ctrl, 0, 1); + + fimc_outdev_set_format(ctrl, ctx); + fimc_outdev_set_path(ctrl, ctx); + fimc_outdev_set_rot(ctrl, ctx); + + fimc_outdev_set_src_dma_offset(ctrl, ctx); + ret = fimc_outdev_set_src_dma_size(ctrl, ctx); + if (ret < 0) + return ret; + + fimc_outdev_set_dst_dma_offset(ctrl, ctx); + + ret = fimc_outdev_set_dst_dma_size(ctrl, ctx); + if (ret < 0) + return ret; + + ret = fimc_outdev_set_scaler(ctrl, ctx); + if (ret < 0) + return ret; + + return 0; +} + +int fimc_fimd_rect(const struct fimc_control *ctrl, + const struct fimc_ctx *ctx, + struct v4l2_rect *fimd_rect) +{ + switch (ctx->rotate) { + case 0: + fimd_rect->left = ctx->win.w.left; + fimd_rect->top = ctx->win.w.top; + fimd_rect->width = ctx->win.w.width; + fimd_rect->height = ctx->win.w.height; + + break; + + case 90: + fimd_rect->left = ctrl->fb.lcd_hres - + (ctx->win.w.top + ctx->win.w.height); + fimd_rect->top = ctx->win.w.left; + fimd_rect->width = ctx->win.w.height; + fimd_rect->height = ctx->win.w.width; + + break; + + case 180: + fimd_rect->left = ctrl->fb.lcd_hres - + (ctx->win.w.left + ctx->win.w.width); + fimd_rect->top = ctrl->fb.lcd_vres - + (ctx->win.w.top + ctx->win.w.height); + fimd_rect->width = ctx->win.w.width; + fimd_rect->height = ctx->win.w.height; + + break; + + case 270: + fimd_rect->left = ctx->win.w.top; + fimd_rect->top = ctrl->fb.lcd_vres - + (ctx->win.w.left + ctx->win.w.width); + fimd_rect->width = ctx->win.w.height; + fimd_rect->height = ctx->win.w.width; + + break; + + default: + fimc_err("Rotation degree is invalid\n"); + return -EINVAL; + + break; + } + + return 0; +} + +int fimc_outdev_overlay_buf(struct file *filp, + struct fimc_control *ctrl, + struct fimc_ctx *ctx) +{ + int ret = 0, i; + struct fimc_overlay_buf *buf; + + buf = &ctx->overlay.buf; + + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->overlay.req_idx = i; + buf->size[i] = ctx->dst[i].length[0]; + buf->phy_addr[i] = ctx->dst[i].base[0]; + buf->vir_addr[i] = do_mmap(filp, 0, buf->size[i], + PROT_READ|PROT_WRITE, MAP_SHARED, 0); + if (buf->vir_addr[i] == -EINVAL) { + fimc_err("%s: fail\n", __func__); + return -EINVAL; + } + + fimc_dbg("idx : %d, size(0x%08x), phy_addr(0x%08x), " + "vir_addr(0x%08x)\n", i, buf->size[i], + buf->phy_addr[i], buf->vir_addr[i]); + } + + ctx->overlay.req_idx = -1; + + return ret; +} + +int fimc_reqbufs_output(void *fh, struct v4l2_requestbuffers *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_ctx *ctx; + struct fimc_overlay_buf *buf; + struct mm_struct *mm = current->mm; + enum fimc_overlay_mode mode; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int ret = -1, i; + + ctx = &ctrl->out->ctx[ctx_id]; + buf = &ctx->overlay.buf; + mode = ctx->overlay.mode; + + fimc_info1("%s: called\n", __func__); + + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + if (ctx->is_requested == 1 && b->count != 0) { + fimc_err("Buffers were already requested\n"); + return -EBUSY; + } + + if (b->count > FIMC_OUTBUFS) { + fimc_warn("The buffer count is modified by driver " + "from %d to %d\n", b->count, FIMC_OUTBUFS); + b->count = FIMC_OUTBUFS; + } + + fimc_init_out_buf(ctx); + ctx->is_requested = 0; + + if (b->count == 0) { + ctrl->mem.curr = ctrl->mem.base; + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: /* fall through */ + case FIMC_OVLY_DMA_MANUAL: + for (i = 0; i < FIMC_OUTBUFS; i++) { + if (buf->vir_addr[i]) { + ret = do_munmap(mm, + buf->vir_addr[i], + buf->size[i]); + if (ret < 0) + fimc_err("%s: do_munmap fail. " + "vir_addr[%d](0x%08x)\n", + __func__, i, buf->vir_addr[i]); + } + } + break; + default: + break; + } + } else { + /* initialize source buffers */ + if (b->memory == V4L2_MEMORY_MMAP) { + ret = fimc_outdev_set_src_buf(ctrl, ctx); + ctx->overlay.req_idx = FIMC_MMAP_IDX; + if (ret) + return ret; + } else if (b->memory == V4L2_MEMORY_USERPTR) { + if (mode == FIMC_OVLY_DMA_AUTO) + ctx->overlay.req_idx = FIMC_USERPTR_IDX; + } + ctx->is_requested = 1; + } + + ctx->buf_num = b->count; + + return 0; +} + +int fimc_querybuf_output(void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_ctx *ctx; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + u32 buf_length = 0; + + fimc_info1("%s: called\n", __func__); + + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + if (b->index >= ctx->buf_num) { + fimc_err("The index is out of bounds. You requested %d buffers." + "But requested index is %d\n", ctx->buf_num, b->index); + return -EINVAL; + } + + b->flags = ctx->src[b->index].flags; + b->m.offset = b->index * PAGE_SIZE; + buf_length = ctx->src[b->index].length[FIMC_ADDR_Y] + + ctx->src[b->index].length[FIMC_ADDR_CB] + + ctx->src[b->index].length[FIMC_ADDR_CR]; + b->length = PAGE_ALIGN(buf_length); + + return 0; +} + +int fimc_g_ctrl_output(void *fh, struct v4l2_control *c) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + ctx = &ctrl->out->ctx[ctx_id]; + + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + switch (c->id) { + case V4L2_CID_ROTATION: + c->value = ctx->rotate; + break; + + case V4L2_CID_HFLIP: + if (ctx->flip & V4L2_CID_HFLIP) + c->value = 1; + else + c->value = 0; + break; + + case V4L2_CID_VFLIP: + if (ctx->flip & V4L2_CID_VFLIP) + c->value = 1; + else + c->value = 0; + break; + + case V4L2_CID_OVERLAY_VADDR0: + c->value = ctx->overlay.buf.vir_addr[0]; + break; + + case V4L2_CID_OVERLAY_VADDR1: + c->value = ctx->overlay.buf.vir_addr[1]; + break; + + case V4L2_CID_OVERLAY_VADDR2: + c->value = ctx->overlay.buf.vir_addr[2]; + break; + + case V4L2_CID_OVERLAY_AUTO: + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO) + c->value = 1; + else + c->value = 0; + break; + + case V4L2_CID_RESERVED_MEM_BASE_ADDR: + c->value = ctrl->mem.base; + if (2 == ctrl->id) { + /* Clearing the buffer for FIMC-2 + * This is required because the same buffer is used + * for both Camcorder recording and HDMI display. + */ + char *fimc_mem = NULL; + fimc_mem = (char *) ioremap(ctrl->mem.base, \ + ctrl->mem.size); + if (fimc_mem) { + memset(fimc_mem, 0x00, ctrl->mem.size); + iounmap(fimc_mem); + } + } + break; + + case V4L2_CID_FIMC_VERSION: + c->value = pdata->hw_ver; + break; + + default: + fimc_err("Invalid control id: %d\n", c->id); + return -EINVAL; + } + + return 0; +} + +static int fimc_set_dst_info(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct fimc_buf *fimc_buf) +{ + struct fimc_buf *buf; + int i; + + for (i = 0; i < ctx->buf_num; i++) { + buf = &fimc_buf[i]; + + ctx->dst[i].base[FIMC_ADDR_Y] = buf->base[FIMC_ADDR_Y]; + ctx->dst[i].length[FIMC_ADDR_Y] = buf->length[FIMC_ADDR_Y]; + + ctx->dst[i].base[FIMC_ADDR_CB] = buf->base[FIMC_ADDR_CB]; + ctx->dst[i].length[FIMC_ADDR_CB] = buf->length[FIMC_ADDR_CB]; + + ctx->dst[i].base[FIMC_ADDR_CR] = buf->base[FIMC_ADDR_CR]; + ctx->dst[i].length[FIMC_ADDR_CR] = buf->length[FIMC_ADDR_CR]; + } + + for (i = ctx->buf_num; i < FIMC_OUTBUFS; i++) { + ctx->dst[i].base[FIMC_ADDR_Y] = 0; + ctx->dst[i].length[FIMC_ADDR_Y] = 0; + + ctx->dst[i].base[FIMC_ADDR_CB] = 0; + ctx->dst[i].length[FIMC_ADDR_CB] = 0; + + ctx->dst[i].base[FIMC_ADDR_CR] = 0; + ctx->dst[i].length[FIMC_ADDR_CR] = 0; + } + + /* for debugging */ + for (i = 0; i < FIMC_OUTBUFS; i++) { + fimc_dbg("dst[%d]: base[0]=0x%08x, size[0]=0x%08x\n", + i, ctx->dst[i].base[0], ctx->dst[i].length[0]); + + fimc_dbg("dst[%d]: base[1]=0x%08x, size[1]=0x%08x\n", + i, ctx->dst[i].base[1], ctx->dst[i].length[2]); + + fimc_dbg("dst[%d]: base[2]=0x%08x, size[2]=0x%08x\n", + i, ctx->dst[i].base[1], ctx->dst[i].length[2]); + } + + return 0; +} +int fimc_s_ctrl_output(struct file *filp, void *fh, struct v4l2_control *c) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int ret = 0; + + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + switch (c->id) { + case V4L2_CID_ROTATION: + ret = fimc_set_rot_degree(ctrl, ctx, c->value); + + break; + case V4L2_CID_HFLIP: + if (c->value) + ctx->flip |= FIMC_XFLIP; + else + ctx->flip &= ~FIMC_XFLIP; + + break; + case V4L2_CID_VFLIP: + if (c->value) + ctx->flip |= FIMC_YFLIP; + else + ctx->flip &= ~FIMC_YFLIP; + + break; + case V4L2_CID_OVERLAY_AUTO: + if (c->value == 1) { + ctx->overlay.mode = FIMC_OVLY_DMA_AUTO; + } else { + ctx->overlay.mode = FIMC_OVLY_DMA_MANUAL; + ret = fimc_outdev_set_dst_buf(ctrl, ctx); + fimc_outdev_overlay_buf(filp, ctrl, ctx); + } + + break; + case V4L2_CID_OVLY_MODE: + ctx->overlay.mode = c->value; + + break; + case V4L2_CID_DST_INFO: + ret = fimc_set_dst_info(ctrl, ctx, + (struct fimc_buf *)c->value); + break; + default: + fimc_err("Invalid control id: %d\n", c->id); + ret = -EINVAL; + } + + return ret; +} + +int fimc_cropcap_output(void *fh, struct v4l2_cropcap *a) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + u32 is_rotate = 0, max_w = 0, max_h = 0, pixelformat; + + fimc_info1("%s: called\n", __func__); + + ctx = &ctrl->out->ctx[ctx_id]; + pixelformat = ctx->pix.pixelformat; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + is_rotate = fimc_mapping_rot_flip(ctx->rotate, ctx->flip); + switch (pixelformat) { + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_YUV420: + max_w = FIMC_SRC_MAX_W; + max_h = FIMC_SRC_MAX_H; + case V4L2_PIX_FMT_RGB32: /* fall through */ + case V4L2_PIX_FMT_RGB565: /* fall through */ + if (is_rotate & FIMC_ROT) { /* Landscape mode */ + max_w = ctrl->fb.lcd_vres; + max_h = ctrl->fb.lcd_hres; + } else { /* Portrait */ + max_w = ctrl->fb.lcd_hres; + max_h = ctrl->fb.lcd_vres; + } + + break; + default: + fimc_warn("Supported format : \ + V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, \ + V4L2_PIX_FMT_YVYU, V4L2_PIX_FMT_VYUY, \ + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_NV12T, \ + V4L2_PIX_FMT_NV21, V4L2_PIX_FMT_RGB32, \ + V4L2_PIX_FMT_RGB565\n"); + fimc_warn("%s: Received pixel format = %x\n", + __func__, pixelformat); + return -EINVAL; + } + + /* crop bounds */ + ctx->cropcap.bounds.left = 0; + ctx->cropcap.bounds.top = 0; + ctx->cropcap.bounds.width = max_w; + ctx->cropcap.bounds.height = max_h; + + /* crop default values */ + ctx->cropcap.defrect.left = 0; + ctx->cropcap.defrect.top = 0; + ctx->cropcap.defrect.width = max_w; + ctx->cropcap.defrect.height = max_h; + + /* crop pixel aspec values */ + /* To Do : Have to modify but I don't know the meaning. */ + ctx->cropcap.pixelaspect.numerator = 16; + ctx->cropcap.pixelaspect.denominator = 9; + + a->bounds = ctx->cropcap.bounds; + a->defrect = ctx->cropcap.defrect; + a->pixelaspect = ctx->cropcap.pixelaspect; + + return 0; +} + +int fimc_g_crop_output(void *fh, struct v4l2_crop *a) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: called\n", __func__); + + mutex_lock(&ctrl->v4l2_lock); + a->c.left = ctx->crop.left; + a->c.top = ctx->crop.top; + a->c.width = ctx->crop.width; + a->c.height = ctx->crop.height; + mutex_unlock(&ctrl->v4l2_lock); + + return 0; +} + +int fimc_s_crop_output(void *fh, struct v4l2_crop *a) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + + fimc_info1("%s: called: left(%d), top(%d), width(%d), height(%d),\n", + __func__, a->c.left, a->c.top, a->c.width, a->c.height); + + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + /* Check arguments : widht and height */ + if ((a->c.width < 0) || (a->c.height < 0)) { + fimc_err("The crop rect must be bigger than 0\n"); + return -EINVAL; + } + + if ((a->c.width > FIMC_SRC_MAX_W) || (a->c.height > FIMC_SRC_MAX_H)) { + fimc_err("The crop width/height must be smaller than " + "%d and %d\n", FIMC_SRC_MAX_W, FIMC_SRC_MAX_H); + return -EINVAL; + } + + /* Check arguments : left and top */ + if ((a->c.left < 0) || (a->c.top < 0)) { + fimc_err("The crop left, top must be bigger than 0\n"); + return -EINVAL; + } + + if ((a->c.left > FIMC_SRC_MAX_W) || (a->c.top > FIMC_SRC_MAX_H)) { + fimc_err("The crop left, top must be smaller than %d, %d\n", + FIMC_SRC_MAX_W, FIMC_SRC_MAX_H); + return -EINVAL; + } + + if ((a->c.left + a->c.width) > FIMC_SRC_MAX_W) { + fimc_err("The crop rect must be in bound rect\n"); + return -EINVAL; + } + + if ((a->c.top + a->c.height) > FIMC_SRC_MAX_H) { + fimc_err("The crop rect must be in bound rect\n"); + return -EINVAL; + } + + ctx->crop.left = a->c.left; + ctx->crop.top = a->c.top; + ctx->crop.width = a->c.width; + ctx->crop.height = a->c.height; + + return 0; +} + +int fimc_streamon_output(void *fh) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int ret = -1; + + fimc_info1("%s: called\n", __func__); + + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->overlay.mode == FIMC_OVLY_NOT_FIXED) + ctx->overlay.mode = FIMC_OVLY_MODE; + + /* initialize destination buffers */ + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO) { + ret = fimc_outdev_set_dst_buf(ctrl, ctx); + if (ret) + return ret; + } + + ret = fimc_outdev_check_param(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_outdev_check_param\n"); + return ret; + } + + ctx->status = FIMC_READY_ON; + if (ctrl->status == FIMC_STREAMOFF) + ctrl->status = FIMC_READY_ON; + + return ret; +} + +void fimc_outdev_init_idxs(struct fimc_control *ctrl) +{ + ctrl->out->idxs.prev.ctx = -1; + ctrl->out->idxs.prev.idx = -1; + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctrl->out->idxs.next.ctx = -1; + ctrl->out->idxs.next.idx = -1; +} + +int fimc_streamoff_output(void *fh) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int ret = -1, i = 0, off_cnt = 0; + + fimc_info1("%s: called\n", __func__); + + ctx = &ctrl->out->ctx[ctx_id]; + ret = fimc_outdev_stop_streaming(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_outdev_stop_streaming\n"); + return -EINVAL; + } + + ret = fimc_init_in_queue(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_init_in_queue\n"); + return -EINVAL; + } + + ret = fimc_init_out_queue(ctrl, ctx); + if (ret < 0) { + fimc_err("Fail: fimc_init_out_queue\n"); + return -EINVAL; + } + + /* Make all buffers DQUEUED state. */ + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->src[i].state = VIDEOBUF_IDLE; + ctx->src[i].flags = V4L2_BUF_FLAG_MAPPED; + } + + ctx->status = FIMC_STREAMOFF; + + if (ctrl->out->last_ctx == ctx->ctx_num) + ctrl->out->last_ctx = -1; + + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO) { + ctrl->mem.curr = ctx->dst[0].base[FIMC_ADDR_Y]; + + for (i = 0; i < FIMC_OUTBUFS; i++) { + ctx->dst[i].base[FIMC_ADDR_Y] = 0; + ctx->dst[i].length[FIMC_ADDR_Y] = 0; + + ctx->dst[i].base[FIMC_ADDR_CB] = 0; + ctx->dst[i].length[FIMC_ADDR_CB] = 0; + + ctx->dst[i].base[FIMC_ADDR_CR] = 0; + ctx->dst[i].length[FIMC_ADDR_CR] = 0; + } + } + + /* check all ctx to change ctrl->status from streamon to streamoff */ + for (i = 0; i < FIMC_MAX_CTXS; i++) { + if (ctrl->out->ctx[i].status == FIMC_STREAMOFF) + off_cnt++; + } + + if (off_cnt == FIMC_MAX_CTXS) { + ctrl->status = FIMC_STREAMOFF; + fimc_outdev_init_idxs(ctrl); + fimc_outdev_stop_camif(ctrl); + } + + if (ctx_id == ctrl->out->last_ctx) + ctrl->out->last_ctx = -1; + + mutex_lock(&ctrl->lock); + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO && ctrl->fb.is_enable == 1) { + fimc_info2("WIN_OFF for FIMC%d\n", ctrl->id); + ret = fb_blank(registered_fb[ctx->overlay.fb_id], + FB_BLANK_POWERDOWN); + if (ret < 0) { + fimc_err("%s: fb_blank: fb[%d] " \ + "mode=FB_BLANK_POWERDOWN\n", + __func__, ctx->overlay.fb_id); + mutex_unlock(&ctrl->lock); + return -EINVAL; + } + + ctrl->fb.is_enable = 0; + } + mutex_unlock(&ctrl->lock); + + return 0; +} + + +int fimc_output_set_dst_addr(struct fimc_control *ctrl, + struct fimc_ctx *ctx, int idx) +{ + struct fimc_buf_set buf_set; /* destination addr */ + u32 format = ctx->fbuf.fmt.pixelformat; + u32 width = ctx->fbuf.fmt.width; + u32 height = ctx->fbuf.fmt.height; + u32 y_size = width * height; + u32 c_size = y_size >> 2; + int i; + + memset(&buf_set, 0x00, sizeof(buf_set)); + + if (V4L2_PIX_FMT_NV12T == format) + fimc_get_nv12t_size(width, height, &y_size, &c_size); + + switch (format) { + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + if (ctx->overlay.mode == FIMC_OVLY_NONE_SINGLE_BUF) + buf_set.base[FIMC_ADDR_Y] = + (dma_addr_t)ctx->fbuf.base; + else + buf_set.base[FIMC_ADDR_Y] = + ctx->dst[idx].base[FIMC_ADDR_Y]; + break; + case V4L2_PIX_FMT_YUV420: + if (ctx->overlay.mode == FIMC_OVLY_NONE_SINGLE_BUF) { + buf_set.base[FIMC_ADDR_Y] = + (dma_addr_t)ctx->fbuf.base; + buf_set.base[FIMC_ADDR_CB] = + buf_set.base[FIMC_ADDR_Y] + y_size; + buf_set.base[FIMC_ADDR_CR] = + buf_set.base[FIMC_ADDR_CB] + c_size; + } else { + buf_set.base[FIMC_ADDR_Y] = + ctx->dst[idx].base[FIMC_ADDR_Y]; + buf_set.base[FIMC_ADDR_CB] = + ctx->dst[idx].base[FIMC_ADDR_CB]; + buf_set.base[FIMC_ADDR_CR] = + ctx->dst[idx].base[FIMC_ADDR_CR]; + } + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV12T: + if (ctx->overlay.mode == FIMC_OVLY_NONE_SINGLE_BUF) { + buf_set.base[FIMC_ADDR_Y] = + (dma_addr_t)ctx->fbuf.base; + buf_set.base[FIMC_ADDR_CB] = + buf_set.base[FIMC_ADDR_Y] + y_size; + } else { + buf_set.base[FIMC_ADDR_Y] = + ctx->dst[idx].base[FIMC_ADDR_Y]; + buf_set.base[FIMC_ADDR_CB] = + ctx->dst[idx].base[FIMC_ADDR_CB]; + } + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", \ + __func__, format); + return -EINVAL; + } + + for (i = 0; i < FIMC_PHYBUFS; i++) + fimc_hwset_output_address(ctrl, &buf_set, i); + + return 0; +} + + +static int fimc_qbuf_output_single_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + int idx) +{ + int ret = -1; + + fimc_outdev_set_src_addr(ctrl, ctx->src[idx].base); + + ret = fimc_output_set_dst_addr(ctrl, ctx, idx); + if (ret < 0) { + fimc_err("%s: Fail: fimc_output_set_dst_addr\n", __func__); + return -EINVAL; + } + + ctrl->out->idxs.active.idx = idx; + ctrl->out->idxs.active.ctx = ctx->ctx_num; + + ctrl->status = FIMC_STREAMON; + ctx->status = FIMC_STREAMON; + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) { + fimc_err("Fail: fimc_start_camif\n"); + return -EINVAL; + } + + return 0; +} + +static int fimc_qbuf_output_multi_buf(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + int idx) +{ + int ret = -1; + + fimc_outdev_set_src_addr(ctrl, ctx->src[idx].base); + + fimc_output_set_dst_addr(ctrl, ctx, idx); + if (ret < 0) { + fimc_err("%s: Fail: fimc_output_set_dst_addr\n", __func__); + return -EINVAL; + } + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) { + fimc_err("Fail: fimc_start_camif\n"); + return -EINVAL; + } + + ctrl->out->idxs.active.idx = idx; + ctrl->out->idxs.active.ctx = ctx->ctx_num; + + ctrl->status = FIMC_STREAMON; + ctx->status = FIMC_STREAMON; + + return 0; +} + +static int fimc_qbuf_output_dma_auto(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + int idx) +{ + struct fb_var_screeninfo var; + struct fb_info *fbinfo; + struct s3cfb_window *win; + struct v4l2_rect fimd_rect; + struct fimc_buf_set buf_set; /* destination addr */ + int ret = -1, i; + + switch (ctx->status) { + case FIMC_READY_ON: + fbinfo = registered_fb[ctx->overlay.fb_id]; + win = (struct s3cfb_window *)fbinfo->par; + + memcpy(&var, &fbinfo->var, sizeof(struct fb_var_screeninfo)); + memset(&fimd_rect, 0, sizeof(struct v4l2_rect)); + ret = fimc_fimd_rect(ctrl, ctx, &fimd_rect); + if (ret < 0) { + fimc_err("fimc_fimd_rect fail\n"); + return -EINVAL; + } + + /* set window path & owner */ + win->path = DATA_PATH_DMA; + win->owner = DMA_MEM_OTHER; + win->other_mem_addr = ctx->dst[1].base[FIMC_ADDR_Y]; + win->other_mem_size = ctx->dst[1].length[FIMC_ADDR_Y]; + + /* Update WIN size */ + var.xres_virtual = fimd_rect.width; + var.yres_virtual = fimd_rect.height; + var.xres = fimd_rect.width; + var.yres = fimd_rect.height; + + /* Update WIN position */ + win->x = fimd_rect.left; + win->y = fimd_rect.top; + + var.activate = FB_ACTIVATE_FORCE; + ret = fb_set_var(fbinfo, &var); + if (ret < 0) { + fimc_err("fb_set_var fail (ret=%d)\n", ret); + return -EINVAL; + } + + /* fall through */ + + case FIMC_STREAMON_IDLE: + fimc_outdev_set_src_addr(ctrl, ctx->src[idx].base); + + memset(&buf_set, 0x00, sizeof(buf_set)); + buf_set.base[FIMC_ADDR_Y] = ctx->dst[idx].base[FIMC_ADDR_Y]; + + for (i = 0; i < FIMC_PHYBUFS; i++) + fimc_hwset_output_address(ctrl, &buf_set, i); + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) { + fimc_err("Fail: fimc_start_camif\n"); + return -EINVAL; + } + + ctrl->out->idxs.active.idx = idx; + ctrl->out->idxs.active.ctx = ctx->ctx_num; + + ctrl->status = FIMC_STREAMON; + ctx->status = FIMC_STREAMON; + + break; + + default: + break; + } + + return 0; +} + +static int fimc_qbuf_output_dma_manual(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + int idx) +{ + struct fimc_buf_set buf_set; /* destination addr */ + int ret = -1, i; + + fimc_outdev_set_src_addr(ctrl, ctx->src[idx].base); + + memset(&buf_set, 0x00, sizeof(buf_set)); + buf_set.base[FIMC_ADDR_Y] = ctx->dst[idx].base[FIMC_ADDR_Y]; + + for (i = 0; i < FIMC_PHYBUFS; i++) + fimc_hwset_output_address(ctrl, &buf_set, i); + + ret = fimc_outdev_start_camif(ctrl); + if (ret < 0) { + fimc_err("Fail: fimc_start_camif\n"); + return -EINVAL; + } + + ctrl->out->idxs.active.idx = idx; + ctrl->out->idxs.active.ctx = ctx->ctx_num; + + ctrl->status = FIMC_STREAMON; + ctx->status = FIMC_STREAMON; + + return 0; +} + +static int fimc_update_in_queue_addr(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + u32 idx, dma_addr_t *addr) +{ + if (idx >= FIMC_OUTBUFS) { + fimc_err("%s: Failed\n", __func__); + return -EINVAL; + } + + ctx->src[idx].base[FIMC_ADDR_Y] = addr[FIMC_ADDR_Y]; + ctx->src[idx].base[FIMC_ADDR_CB] = addr[FIMC_ADDR_CB]; + ctx->src[idx].base[FIMC_ADDR_CR] = addr[FIMC_ADDR_CR]; + + return 0; +} + +int fimc_qbuf_output(void *fh, struct v4l2_buffer *b) +{ + struct fimc_buf *buf = (struct fimc_buf *)b->m.userptr; + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int idx, ctx_num; + int ret = -1; + + ctx = &ctrl->out->ctx[ctx_id]; + fimc_info2("ctx(%d) queued idx = %d\n", ctx->ctx_num, b->index); + + if (b->index >= ctx->buf_num) { + fimc_err("The index is out of bounds. " + "You requested %d buffers. " + "But you set the index as %d\n", + ctx->buf_num, b->index); + return -EINVAL; + } + + /* Check the buffer state if the state is VIDEOBUF_IDLE. */ + if (ctx->src[b->index].state != VIDEOBUF_IDLE) { + fimc_err("The index(%d) buffer must be dequeued state(%d)\n", + b->index, ctx->src[b->index].state); + return -EINVAL; + } + + if (b->memory == V4L2_MEMORY_USERPTR) { + ret = fimc_update_in_queue_addr(ctrl, ctx, b->index, buf->base); + if (ret < 0) + return ret; + } + + /* Attach the buffer to the incoming queue. */ + ret = fimc_push_inq(ctrl, ctx, b->index); + if (ret < 0) { + fimc_err("Fail: fimc_push_inq\n"); + return -EINVAL; + } + + if ((ctrl->status == FIMC_READY_ON) || + (ctrl->status == FIMC_STREAMON_IDLE)) { + ret = fimc_pop_inq(ctrl, &ctx_num, &idx); + if (ret < 0) { + fimc_err("Fail: fimc_pop_inq\n"); + return -EINVAL; + } + + fimc_clk_en(ctrl, true); + + ctx = &ctrl->out->ctx[ctx_num]; + if (ctx_num != ctrl->out->last_ctx) { + ctrl->out->last_ctx = ctx->ctx_num; + fimc_outdev_set_ctx_param(ctrl, ctx); + } + + switch (ctx->overlay.mode) { + case FIMC_OVLY_DMA_AUTO: + ret = fimc_qbuf_output_dma_auto(ctrl, ctx, idx); + break; + case FIMC_OVLY_DMA_MANUAL: + ret = fimc_qbuf_output_dma_manual(ctrl, ctx, idx); + break; + case FIMC_OVLY_NONE_SINGLE_BUF: + ret = fimc_qbuf_output_single_buf(ctrl, ctx, idx); + break; + case FIMC_OVLY_NONE_MULTI_BUF: + ret = fimc_qbuf_output_multi_buf(ctrl, ctx, idx); + break; + default: + break; + } + } + + return ret; +} + +int fimc_dqbuf_output(void *fh, struct v4l2_buffer *b) +{ + struct fimc_ctx *ctx; + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int idx = -1, ret = -1; + + ctx = &ctrl->out->ctx[ctx_id]; + ret = fimc_pop_outq(ctrl, ctx, &idx); + if (ret < 0) { + ret = wait_event_timeout(ctrl->wq, (ctx->outq[0] != -1), + FIMC_DQUEUE_TIMEOUT); + if (ret == 0) { + fimc_dump_context(ctrl, ctx); + fimc_err("[0] out_queue is empty\n"); + ctx->status = FIMC_STREAMON_IDLE; + return -EAGAIN; + } else if (ret == -ERESTARTSYS) { + fimc_print_signal(ctrl); + } else { + /* Normal case */ + ret = fimc_pop_outq(ctrl, ctx, &idx); + if (ret < 0) { + fimc_err("[1] out_queue is empty\n"); + fimc_dump_context(ctrl, ctx); + return -EINVAL; + } + } + } + + mutex_lock(&ctrl->lock); + if (ctx->overlay.mode == FIMC_OVLY_DMA_AUTO && ctrl->fb.is_enable == 0) { + ret = fb_blank(registered_fb[ctx->overlay.fb_id], + FB_BLANK_UNBLANK); + if (ret < 0) { + fimc_err("%s: fb_blank: fb[%d] " \ + "mode=FB_BLANK_UNBLANK\n", + __func__, ctx->overlay.fb_id); + mutex_unlock(&ctrl->lock); + return -EINVAL; + } + ctrl->fb.is_enable = 1; + } + mutex_unlock(&ctrl->lock); + + b->index = idx; + + fimc_info2("ctx(%d) dqueued idx = %d\n", ctx->ctx_num, b->index); + + return ret; +} + +int fimc_g_fmt_vid_out(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct fimc_outinfo *out = ctrl->out; + struct fimc_ctx *ctx; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + int i, j; + + fimc_info1("%s: called\n", __func__); + + if (ctrl->cap) { + fimc_err("%s: fimc is already used for capture mode\n", + __func__); + return -EINVAL; + } + + if (!out) { + out = kzalloc(sizeof(*out), GFP_KERNEL); + if (!out) { + fimc_err("%s: no memory for outdev info\n", __func__); + return -ENOMEM; + } + ctrl->out = out; + + /* init: struct fimc_outinfo */ + out->last_ctx = -1; + + spin_lock_init(&ctrl->out->lock_in); + spin_lock_init(&ctrl->out->lock_out); + + for (i = 0; i < FIMC_INQUEUES; i++) { + ctrl->out->inq[i].ctx = -1; + ctrl->out->inq[i].idx = -1; + } + + for (i = 0; i < FIMC_MAX_CTXS; i++) { + ctx = &ctrl->out->ctx[i]; + ctx->ctx_num = i; + ctx->overlay.mode = FIMC_OVLY_NOT_FIXED; + ctx->status = FIMC_STREAMOFF; + + for (j = 0; j < FIMC_OUTBUFS; j++) { + ctx->inq[j] = -1; + ctx->outq[j] = -1; + } + } + + ctrl->out->idxs.prev.ctx = -1; + ctrl->out->idxs.prev.idx = -1; + ctrl->out->idxs.active.ctx = -1; + ctrl->out->idxs.active.idx = -1; + ctrl->out->idxs.next.ctx = -1; + ctrl->out->idxs.next.idx = -1; + } + + f->fmt.pix = ctrl->out->ctx[ctx_id].pix; + + return 0; +} + +int fimc_try_fmt_vid_out(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + u32 format = f->fmt.pix.pixelformat; + + fimc_info1("%s: called. width(%d), height(%d)\n", __func__, + f->fmt.pix.width, f->fmt.pix.height); + + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + /* Check pixel format */ + switch (format) { + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_RGB32: /* fall through */ + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_YUV420: + break; + default: + fimc_warn("Supported format : \ + V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, \ + V4L2_PIX_FMT_YVYU, V4L2_PIX_FMT_VYUY, \ + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_NV12T, \ + V4L2_PIX_FMT_NV21, V4L2_PIX_FMT_RGB32, \ + V4L2_PIX_FMT_RGB565\n"); + fimc_warn("Changed format : V4L2_PIX_FMT_RGB32\n"); + f->fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + return -EINVAL; + } + + /* Fill the return value. */ + switch (format) { + case V4L2_PIX_FMT_RGB32: + f->fmt.pix.bytesperline = f->fmt.pix.width << 2; + break; + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_RGB565: + f->fmt.pix.bytesperline = f->fmt.pix.width << 1; + break; + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: + f->fmt.pix.bytesperline = (f->fmt.pix.width * 3) >> 1; + break; + + default: + /* dummy value*/ + f->fmt.pix.bytesperline = f->fmt.pix.width; + } + + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +int fimc_s_fmt_vid_out(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + int ret = -1; + + fimc_info1("%s: called\n", __func__); + + /* Check stream status */ + ctx = &ctrl->out->ctx[ctx_id]; + if (ctx->status != FIMC_STREAMOFF) { + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + ret = fimc_try_fmt_vid_out(filp, fh, f); + if (ret < 0) + return ret; + + ctx->pix = f->fmt.pix; + + return ret; +} + +int fimc_init_in_queue(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + struct fimc_idx swap_queue[FIMC_INQUEUES]; + int swap_cnt = 0, i; + unsigned long spin_flags; + + spin_lock_irqsave(&ctrl->out->lock_in, spin_flags); + + /* init incoming queue */ + for (i = 0; i < FIMC_OUTBUFS; i++) + ctx->inq[i] = -1; + + /* init common incoming queue */ + for (i = 0; i < FIMC_INQUEUES; i++) { + if (ctrl->out->inq[i].ctx != ctx->ctx_num) { + swap_queue[swap_cnt].ctx = ctrl->out->inq[i].ctx; + swap_queue[swap_cnt].idx = ctrl->out->inq[i].idx; + swap_cnt++; + } + + ctrl->out->inq[i].ctx = -1; + ctrl->out->inq[i].idx = -1; + } + + /* restore common incoming queue */ + for (i = 0; i < swap_cnt; i++) { + ctrl->out->inq[i].ctx = swap_queue[i].ctx; + ctrl->out->inq[i].idx = swap_queue[i].idx; + } + + spin_unlock_irqrestore(&ctrl->out->lock_in, spin_flags); + + return 0; +} + +int fimc_init_out_queue(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + unsigned long spin_flags; + int i; + + spin_lock_irqsave(&ctrl->out->lock_out, spin_flags); + + /* Init incoming queue */ + for (i = 0; i < FIMC_OUTBUFS; i++) + ctx->outq[i] = -1; + + spin_unlock_irqrestore(&ctrl->out->lock_out, spin_flags); + + return 0; +} + +int fimc_push_inq(struct fimc_control *ctrl, struct fimc_ctx *ctx, int idx) +{ + struct fimc_idx swap_common_inq[FIMC_INQUEUES]; + int swap_queue[FIMC_OUTBUFS]; + int i; + unsigned long spin_flags; + + fimc_dbg("%s: idx = %d\n", __func__, idx); + + if (ctrl->out->inq[FIMC_INQUEUES-1].idx != -1) { + fimc_err("FULL: common incoming queue\n"); + return -EBUSY; + } + + spin_lock_irqsave(&ctrl->out->lock_in, spin_flags); + + /* ctx own incoming queue */ + /* Backup original queue */ + for (i = 0; i < FIMC_OUTBUFS; i++) + swap_queue[i] = ctx->inq[i]; + + /* Attach new idx */ + ctx->inq[0] = idx; + ctx->src[idx].state = VIDEOBUF_QUEUED; + ctx->src[idx].flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED; + + /* Shift the origonal queue */ + for (i = 1; i < FIMC_OUTBUFS; i++) + ctx->inq[i] = swap_queue[i-1]; + + /* Common incoming queue */ + /* Backup original queue */ + for (i = 0; i < FIMC_INQUEUES; i++) { + swap_common_inq[i].ctx = ctrl->out->inq[i].ctx; + swap_common_inq[i].idx = ctrl->out->inq[i].idx; + } + + /* Attach new idx */ + ctrl->out->inq[0].ctx = ctx->ctx_num; + ctrl->out->inq[0].idx = idx; + + /* Shift the origonal queue */ + for (i = 1; i < FIMC_INQUEUES; i++) { + ctrl->out->inq[i].ctx = swap_common_inq[i-1].ctx; + ctrl->out->inq[i].idx = swap_common_inq[i-1].idx; + } + + spin_unlock_irqrestore(&ctrl->out->lock_in, spin_flags); + + return 0; +} + +int fimc_pop_inq(struct fimc_control *ctrl, int *ctx_num, int *idx) +{ + struct fimc_ctx *ctx; + unsigned long spin_flags; + int i, ret = 0; + int ctx_idx = -1; + + spin_lock_irqsave(&ctrl->out->lock_in, spin_flags); + + /* find valid index from common incoming queue */ + for (i = (FIMC_INQUEUES-1); i >= 0; i--) { + if (ctrl->out->inq[i].ctx != -1) { + *ctx_num = ctrl->out->inq[i].ctx; + *idx = ctrl->out->inq[i].idx; + ctrl->out->inq[i].ctx = -1; + ctrl->out->inq[i].idx = -1; + break; + } + } + + /* common incoming queue is empty. */ + if (i < 0) { + spin_unlock_irqrestore(&ctrl->out->lock_in, spin_flags); + return -EINVAL; + } + + /* find valid index from incoming queue. */ + ctx = &ctrl->out->ctx[*ctx_num]; + for (i = (FIMC_OUTBUFS-1); i >= 0; i--) { + if (ctx->inq[i] != -1) { + ctx_idx = ctx->inq[i]; + ctx->inq[i] = -1; + ctx->src[ctx_idx].state = VIDEOBUF_ACTIVE; + ctx->src[ctx_idx].flags = V4L2_BUF_FLAG_MAPPED; + break; + } + } + + if (*idx != ctx_idx) + fimc_err("common inq(%d) vs inq(%d) mismatch\n", *idx, ctx_idx); + + /* incoming queue is empty. */ + if (i < 0) + ret = -EINVAL; + else + fimc_dbg("%s: index = %d\n", __func__, *idx); + + spin_unlock_irqrestore(&ctrl->out->lock_in, spin_flags); + + return ret; +} + +int fimc_push_outq(struct fimc_control *ctrl, struct fimc_ctx *ctx, int idx) +{ + unsigned long spin_flags; + int swap_queue[FIMC_OUTBUFS]; + int i; + + fimc_dbg("%s: index = %d\n", __func__, idx); + + spin_lock_irqsave(&ctrl->out->lock_out, spin_flags); + + /* Backup original queue */ + for (i = 0; i < FIMC_OUTBUFS; i++) + swap_queue[i] = ctx->outq[i]; + + /* Attach new index */ + ctx->outq[0] = idx; + ctx->src[idx].state = VIDEOBUF_DONE; + ctx->src[idx].flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE; + + /* Shift the origonal queue */ + for (i = 1; i < FIMC_OUTBUFS; i++) + ctx->outq[i] = swap_queue[i-1]; + + spin_unlock_irqrestore(&ctrl->out->lock_out, spin_flags); + + return 0; +} + +int fimc_pop_outq(struct fimc_control *ctrl, struct fimc_ctx *ctx, int *idx) +{ + unsigned long spin_flags; + int i, ret = 0; + + spin_lock_irqsave(&ctrl->out->lock_out, spin_flags); + + /* Find last valid idx in outgoing queue. */ + for (i = (FIMC_OUTBUFS-1); i >= 0; i--) { + if (ctx->outq[i] != -1) { + *idx = ctx->outq[i]; + ctx->outq[i] = -1; + ctx->src[*idx].state = VIDEOBUF_IDLE; + ctx->src[*idx].flags = V4L2_BUF_FLAG_MAPPED; + break; + } + } + + /* outgoing queue is empty. */ + if (i < 0) { + ret = -EINVAL; + fimc_dbg("%s: outgoing queue : %d, %d, %d\n", __func__, + ctx->outq[0], ctx->outq[1], ctx->outq[2]); + } else + fimc_dbg("%s: idx = %d\n", __func__, *idx); + + + spin_unlock_irqrestore(&ctrl->out->lock_out, spin_flags); + + return ret; +} + +void fimc_dump_context(struct fimc_control *ctrl, struct fimc_ctx *ctx) +{ + int i = 0; + + fimc_err("ctx%d, ctrl->status: %d, ctx->status: %d\n", + ctx->ctx_num, ctrl->status, ctx->status); + + for (i = 0; i < FIMC_INQUEUES; i++) + fimc_err("ctrl->inq[%d]: ctx(%d) idx(%d)\n", + i, ctrl->out->inq[i].ctx, ctrl->out->inq[i].idx); + + for (i = 0; i < FIMC_OUTBUFS; i++) + fimc_err("inq[%d] = %d\n", i, ctx->inq[i]); + + for (i = 0; i < FIMC_OUTBUFS; i++) + fimc_err("outq[%d] = %d\n", i, ctx->outq[i]); + + fimc_err("state : prev.ctx(%d), prev.idx(%d) " + "active.ctx(%d), active.idx(%d) " + "next.ctx(%d), next.idx(%d)\n", + ctrl->out->idxs.prev.ctx, ctrl->out->idxs.prev.idx, + ctrl->out->idxs.active.ctx, ctrl->out->idxs.active.idx, + ctrl->out->idxs.next.ctx, ctrl->out->idxs.next.idx); +} + +void fimc_print_signal(struct fimc_control *ctrl) +{ + if (signal_pending(current)) { + fimc_dbg(".pend=%.8lx shpend=%.8lx\n", + current->pending.signal.sig[0], + current->signal->shared_pending.signal.sig[0]); + } else { + fimc_dbg(":pend=%.8lx shpend=%.8lx\n", + current->pending.signal.sig[0], + current->signal->shared_pending.signal.sig[0]); + } +} diff --git a/drivers/media/video/samsung/fimc/fimc_overlay.c b/drivers/media/video/samsung/fimc/fimc_overlay.c new file mode 100644 index 0000000..ff43c2e --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_overlay.c @@ -0,0 +1,312 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_overlay.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * V4L2 Overlay device support file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/slab.h> +#include <linux/bootmem.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <plat/media.h> + +#include "fimc.h" + +int fimc_try_fmt_overlay(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + + u32 is_rotate = 0; + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: top(%d) left(%d) width(%d) height(%d)\n", __func__, + f->fmt.win.w.top, f->fmt.win.w.left, + f->fmt.win.w.width, f->fmt.win.w.height); + + if (ctx->overlay.mode == FIMC_OVLY_NONE_SINGLE_BUF || + (ctx->overlay.mode == FIMC_OVLY_NONE_MULTI_BUF)) + return 0; + + /* Check Overlay Size : Overlay size must be smaller than LCD size. */ + is_rotate = fimc_mapping_rot_flip(ctx->rotate, ctx->flip); + if (is_rotate & FIMC_ROT) { /* Landscape mode */ + if (f->fmt.win.w.width > ctrl->fb.lcd_vres) { + fimc_warn("The width is changed %d -> %d\n", + f->fmt.win.w.width, ctrl->fb.lcd_vres); + f->fmt.win.w.width = ctrl->fb.lcd_vres; + } + + if (f->fmt.win.w.height > ctrl->fb.lcd_hres) { + fimc_warn("The height is changed %d -> %d\n", + f->fmt.win.w.height, ctrl->fb.lcd_hres); + f->fmt.win.w.height = ctrl->fb.lcd_hres; + } + } else { /* Portrait mode */ + if (f->fmt.win.w.width > ctrl->fb.lcd_hres) { + fimc_warn("The width is changed %d -> %d\n", + f->fmt.win.w.width, ctrl->fb.lcd_hres); + f->fmt.win.w.width = ctrl->fb.lcd_hres; + } + + if (f->fmt.win.w.height > ctrl->fb.lcd_vres) { + fimc_warn("The height is changed %d -> %d\n", + f->fmt.win.w.height, ctrl->fb.lcd_vres); + f->fmt.win.w.height = ctrl->fb.lcd_vres; + } + } + + return 0; +} + +int fimc_g_fmt_vid_overlay(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: called\n", __func__); + + f->fmt.win = ctx->win; + + return 0; +} + +static int fimc_check_pos(struct fimc_control *ctrl, + struct fimc_ctx *ctx, + struct v4l2_format *f) +{ + if (ctx->win.w.width != f->fmt.win.w.width) { + fimc_err("%s: cannot change width\n", __func__); + return -EINVAL; + } else if (ctx->win.w.height != f->fmt.win.w.height) { + fimc_err("%s: cannot change height\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int fimc_change_fifo_position(struct fimc_control *ctrl, + struct fimc_ctx *ctx) { + struct v4l2_rect fimd_rect; + struct fb_info *fbinfo; + struct s3cfb_window *win; + int ret = -1; + + fbinfo = registered_fb[ctx->overlay.fb_id]; + + memset(&fimd_rect, 0, sizeof(struct v4l2_rect)); + + ret = fimc_fimd_rect(ctrl, ctx, &fimd_rect); + if (ret < 0) { + fimc_err("fimc_fimd_rect fail\n"); + return -EINVAL; + } + + /* Update WIN position */ + win->x = fimd_rect.left; + win->y = fimd_rect.top; + + fbinfo->var.activate = FB_ACTIVATE_FORCE; + ret = fb_set_var(fbinfo, &fbinfo->var); + if (ret < 0) { + fimc_err("fb_set_var fail (ret=%d)\n", ret); + return -EINVAL; + } + + return 0; +} + +int fimc_s_fmt_vid_overlay(struct file *filp, void *fh, struct v4l2_format *f) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + int ret = -1; + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: called\n", __func__); + + switch (ctx->status) { + case FIMC_STREAMON: + ret = fimc_check_pos(ctrl, ctx, f); + if (ret < 0) { + fimc_err("When FIMC is running, " + "you can only move the position.\n"); + return -EBUSY; + } + + ret = fimc_try_fmt_overlay(filp, fh, f); + if (ret < 0) + return ret; + + ctx->win = f->fmt.win; + fimc_change_fifo_position(ctrl, ctx); + + break; + case FIMC_STREAMOFF: + ret = fimc_try_fmt_overlay(filp, fh, f); + if (ret < 0) + return ret; + ctx->win = f->fmt.win; + + break; + + default: + fimc_err("FIMC is running\n"); + return -EBUSY; + } + + return ret; +} + +int fimc_g_fbuf(struct file *filp, void *fh, struct v4l2_framebuffer *fb) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + u32 bpp = 1, format; + + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: called\n", __func__); + + fb->capability = ctx->fbuf.capability; + fb->flags = 0; + fb->base = ctx->fbuf.base; + + fb->fmt.width = ctx->fbuf.fmt.width; + fb->fmt.height = ctx->fbuf.fmt.height; + fb->fmt.pixelformat = ctx->fbuf.fmt.pixelformat; + format = ctx->fbuf.fmt.pixelformat; + + switch (format) { + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: + bpp = 1; + break; + case V4L2_PIX_FMT_RGB565: + bpp = 2; + break; + case V4L2_PIX_FMT_RGB32: + bpp = 4; + break; + } + + ctx->fbuf.fmt.bytesperline = fb->fmt.width * bpp; + fb->fmt.bytesperline = ctx->fbuf.fmt.bytesperline; + fb->fmt.sizeimage = ctx->fbuf.fmt.sizeimage; + fb->fmt.colorspace = V4L2_COLORSPACE_SMPTE170M; + fb->fmt.priv = 0; + + return 0; +} + +int fimc_s_fbuf(struct file *filp, void *fh, struct v4l2_framebuffer *fb) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ctx_id = ((struct fimc_prv_data *)fh)->ctx_id; + struct fimc_ctx *ctx; + u32 bpp = 1; + u32 format = fb->fmt.pixelformat; + ctx = &ctrl->out->ctx[ctx_id]; + + fimc_info1("%s: called. width(%d), height(%d)\n", + __func__, fb->fmt.width, fb->fmt.height); + + ctx->fbuf.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + ctx->fbuf.flags = 0; + ctx->fbuf.base = fb->base; + + if (ctx->overlay.mode == FIMC_OVLY_NONE_MULTI_BUF) { + ctx->fbuf.fmt.width = fb->fmt.width; + ctx->fbuf.fmt.height = fb->fmt.height; + ctx->fbuf.fmt.pixelformat = fb->fmt.pixelformat; + + switch (format) { + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: + bpp = 1; + break; + case V4L2_PIX_FMT_RGB565: + bpp = 2; + break; + case V4L2_PIX_FMT_RGB32: + bpp = 4; + break; + } + + ctx->fbuf.fmt.bytesperline = fb->fmt.width * bpp; + ctx->fbuf.fmt.sizeimage = fb->fmt.sizeimage; + ctx->fbuf.fmt.colorspace = V4L2_COLORSPACE_SMPTE170M; + ctx->fbuf.fmt.priv = 0; + } else if (fb->base) { + ctx->fbuf.fmt.width = fb->fmt.width; + ctx->fbuf.fmt.height = fb->fmt.height; + ctx->fbuf.fmt.pixelformat = fb->fmt.pixelformat; + + switch (format) { + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: + bpp = 1; + break; + case V4L2_PIX_FMT_RGB565: + bpp = 2; + break; + case V4L2_PIX_FMT_RGB32: + bpp = 4; + break; + } + + ctx->fbuf.fmt.bytesperline = fb->fmt.width * bpp; + ctx->fbuf.fmt.sizeimage = fb->fmt.sizeimage; + ctx->fbuf.fmt.colorspace = V4L2_COLORSPACE_SMPTE170M; + ctx->fbuf.fmt.priv = 0; + + ctx->overlay.mode = FIMC_OVLY_NONE_SINGLE_BUF; + } else { + int i; + struct s3cfb_window *win = NULL; + ctx->overlay.fb_id = -1; + + for (i = 0; i < num_registered_fb; i++) { + win = (struct s3cfb_window *)registered_fb[i]->par; + if (win->id == ctrl->id) { + ctx->overlay.fb_id = i; + fimc_info2("%s: overlay.fb_id = %d\n", + __func__, ctx->overlay.fb_id); + break; + } + } + + if (-1 == ctx->overlay.fb_id) { + fimc_err("%s: fb[%d] is not registered. " \ + "must be registered for overlay\n", + __func__, ctrl->id); + return -1; + } + + if (1 == win->enabled) { + fimc_err("%s: fb[%d] is already being used. " \ + "must be not used for overlay\n", + __func__, ctrl->id); + return -1; + } + + ctx->overlay.mode = FIMC_OVLY_NOT_FIXED; + } + + return 0; +} diff --git a/drivers/media/video/samsung/fimc/fimc_regs.c b/drivers/media/video/samsung/fimc/fimc_regs.c new file mode 100644 index 0000000..227945a --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_regs.c @@ -0,0 +1,1801 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_regs.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Register interface file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/videodev2.h> +#include <linux/videodev2_samsung.h> +#include <linux/io.h> +#include <mach/map.h> +#include <plat/regs-fimc.h> +#include <plat/fimc.h> + +#include "fimc.h" + +/* struct fimc_limit: Limits for FIMC */ +struct fimc_limit fimc40_limits[FIMC_DEVICES] = { + { + .pre_dst_w = 3264, + .bypass_w = 8192, + .trg_h_no_rot = 3264, + .trg_h_rot = 1280, + .real_w_no_rot = 8192, + .real_h_rot = 1280, + }, { + .pre_dst_w = 1280, + .bypass_w = 8192, + .trg_h_no_rot = 1280, + .trg_h_rot = 8192, + .real_w_no_rot = 8192, + .real_h_rot = 768, + }, { + .pre_dst_w = 1440, + .bypass_w = 8192, + .trg_h_no_rot = 1440, + .trg_h_rot = 0, + .real_w_no_rot = 8192, + .real_h_rot = 0, + }, +}; + +struct fimc_limit fimc43_limits[FIMC_DEVICES] = { + { + .pre_dst_w = 4224, + .bypass_w = 8192, + .trg_h_no_rot = 4224, + .trg_h_rot = 1920, + .real_w_no_rot = 8192, + .real_h_rot = 1920, + }, { + .pre_dst_w = 4224, + .bypass_w = 8192, + .trg_h_no_rot = 4224, + .trg_h_rot = 1920, + .real_w_no_rot = 8192, + .real_h_rot = 1920, + }, { + .pre_dst_w = 1920, + .bypass_w = 8192, + .trg_h_no_rot = 1920, + .trg_h_rot = 1280, + .real_w_no_rot = 8192, + .real_h_rot = 1280, + }, +}; + +struct fimc_limit fimc50_limits[FIMC_DEVICES] = { + { + .pre_dst_w = 4224, + .bypass_w = 8192, + .trg_h_no_rot = 4224, + .trg_h_rot = 1920, + .real_w_no_rot = 8192, + .real_h_rot = 1920, + }, { + .pre_dst_w = 4224, + .bypass_w = 8192, + .trg_h_no_rot = 4224, + .trg_h_rot = 1920, + .real_w_no_rot = 8192, + .real_h_rot = 1920, + }, { + .pre_dst_w = 1920, + .bypass_w = 8192, + .trg_h_no_rot = 1920, + .trg_h_rot = 1280, + .real_w_no_rot = 8192, + .real_h_rot = 1280, + }, +}; + +int fimc_hwset_camera_source(struct fimc_control *ctrl) +{ + struct s3c_platform_camera *cam = ctrl->cam; + u32 cfg = 0; + + /* for now, we support only ITU601 8 bit mode */ + cfg |= S3C_CISRCFMT_ITU601_8BIT; + cfg |= cam->order422; + + if (cam->type == CAM_TYPE_ITU) + cfg |= cam->fmt; + + cfg |= S3C_CISRCFMT_SOURCEHSIZE(cam->width); + cfg |= S3C_CISRCFMT_SOURCEVSIZE(cam->height); + + writel(cfg, ctrl->regs + S3C_CISRCFMT); + + return 0; +} + +int fimc_hwset_enable_irq(struct fimc_control *ctrl, int overflow, int level) +{ + u32 cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg &= ~(S3C_CIGCTRL_IRQ_OVFEN | S3C_CIGCTRL_IRQ_LEVEL); + cfg |= S3C_CIGCTRL_IRQ_ENABLE; + + if (overflow) + cfg |= S3C_CIGCTRL_IRQ_OVFEN; + + if (level) + cfg |= S3C_CIGCTRL_IRQ_LEVEL; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_disable_irq(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg &= ~(S3C_CIGCTRL_IRQ_OVFEN | S3C_CIGCTRL_IRQ_ENABLE); + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_clear_irq(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg |= S3C_CIGCTRL_IRQ_CLR; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_output_area_size(struct fimc_control *ctrl, u32 size) +{ + u32 cfg = 0; + + cfg = S3C_CITAREA_TARGET_AREA(size); + + writel(cfg, ctrl->regs + S3C_CITAREA); + + return 0; +} + +void fimc_wait_disable_capture(struct fimc_control *ctrl) +{ + unsigned long timeo = jiffies + 20; /* timeout of 100 ms */ + u32 cfg; + + if (!ctrl || !ctrl->cap || + ctrl->cap->fmt.colorspace == V4L2_COLORSPACE_JPEG) + return; + + while (time_before(jiffies, timeo)) { + cfg = readl(ctrl->regs + S3C_CISTATUS); + + if (0 == (cfg & S3C_CISTATUS_IMGCPTEN)) + break; + + msleep(10); + } + + dev_dbg(ctrl->dev, "IMGCPTEN: Wait time = %d ms\n", + jiffies_to_msecs(jiffies - timeo + 20)); + + return; +} + +int fimc_hwset_image_effect(struct fimc_control *ctrl) +{ + u32 cfg = 0; + + if (ctrl->fe.ie_on) { + if (ctrl->fe.ie_after_sc) + cfg |= S3C_CIIMGEFF_IE_SC_AFTER; + + cfg |= S3C_CIIMGEFF_FIN(ctrl->fe.fin); + + if (ctrl->fe.fin == FIMC_EFFECT_FIN_ARBITRARY_CBCR) + cfg |= S3C_CIIMGEFF_PAT_CB(ctrl->fe.pat_cb) | + S3C_CIIMGEFF_PAT_CR(ctrl->fe.pat_cr); + + cfg |= S3C_CIIMGEFF_IE_ENABLE; + } + + writel(cfg, ctrl->regs + S3C_CIIMGEFF); + + return 0; +} + +int fimc_hwset_shadow_enable(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg &= ~S3C_CIGCTRL_SHADOW_DISABLE; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_shadow_disable(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg |= S3C_CIGCTRL_SHADOW_DISABLE; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +static void fimc_reset_cfg(struct fimc_control *ctrl) +{ + int i; + u32 cfg[][2] = { + { 0x018, 0x00000000 }, { 0x01c, 0x00000000 }, + { 0x020, 0x00000000 }, { 0x024, 0x00000000 }, + { 0x028, 0x00000000 }, { 0x02c, 0x00000000 }, + { 0x030, 0x00000000 }, { 0x034, 0x00000000 }, + { 0x038, 0x00000000 }, { 0x03c, 0x00000000 }, + { 0x040, 0x00000000 }, { 0x044, 0x00000000 }, + { 0x048, 0x00000000 }, { 0x04c, 0x00000000 }, + { 0x050, 0x00000000 }, { 0x054, 0x00000000 }, + { 0x058, 0x18000000 }, { 0x05c, 0x00000000 }, + { 0x0c0, 0x00000000 }, { 0x0c4, 0xffffffff }, + { 0x0d0, 0x00100080 }, { 0x0d4, 0x00000000 }, + { 0x0d8, 0x00000000 }, { 0x0dc, 0x00000000 }, + { 0x0f8, 0x00000000 }, { 0x0fc, 0x04000000 }, + { 0x168, 0x00000000 }, { 0x16c, 0x00000000 }, + { 0x170, 0x00000000 }, { 0x174, 0x00000000 }, + { 0x178, 0x00000000 }, { 0x17c, 0x00000000 }, + { 0x180, 0x00000000 }, { 0x184, 0x00000000 }, + { 0x188, 0x00000000 }, { 0x18c, 0x00000000 }, + { 0x194, 0x0000001e }, + }; + + for (i = 0; i < sizeof(cfg) / 8; i++) + writel(cfg[i][1], ctrl->regs + cfg[i][0]); +} + +int fimc_hwset_reset(struct fimc_control *ctrl) +{ + u32 cfg = 0; + + cfg = readl(ctrl->regs + S3C_CISRCFMT); + cfg |= S3C_CISRCFMT_ITU601_8BIT; + writel(cfg, ctrl->regs + S3C_CISRCFMT); + + /* s/w reset */ + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg |= (S3C_CIGCTRL_SWRST); + writel(cfg, ctrl->regs + S3C_CIGCTRL); + mdelay(1); + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~S3C_CIGCTRL_SWRST; + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + /* in case of ITU656, CISRCFMT[31] should be 0 */ + if ((ctrl->cap != NULL) && (ctrl->cam->fmt == ITU_656_YCBCR422_8BIT)) { + cfg = readl(ctrl->regs + S3C_CISRCFMT); + cfg &= ~S3C_CISRCFMT_ITU601_8BIT; + writel(cfg, ctrl->regs + S3C_CISRCFMT); + } + + fimc_reset_cfg(ctrl); + + return 0; +} + +int fimc_hwset_sw_reset(struct fimc_control *ctrl) +{ + u32 cfg = 0; + + /* s/w reset */ + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg |= (S3C_CIGCTRL_SWRST); + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~S3C_CIGCTRL_SWRST; + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_clksrc(struct fimc_control *ctrl, int src_clk) +{ + u32 cfg = readl(ctrl->regs + S3C_MISC_FIMC); + cfg &= ~S3C_CLKSRC_HCLK_MASK; + + if (src_clk == FIMC_HCLK) + cfg |= S3C_CLKSRC_HCLK; + else if (src_clk == FIMC_SCLK) + cfg |= S3C_CLKSRC_SCLK; + + writel(cfg, ctrl->regs + S3C_MISC_FIMC); + return 0; +} + +int fimc_hwget_overflow_state(struct fimc_control *ctrl) +{ + u32 cfg, status, flag; + + status = readl(ctrl->regs + S3C_CISTATUS); + flag = S3C_CISTATUS_OVFIY | S3C_CISTATUS_OVFICB | S3C_CISTATUS_OVFICR; + + if (status & flag) { + cfg = readl(ctrl->regs + S3C_CIWDOFST); + cfg |= (S3C_CIWDOFST_CLROVFIY | S3C_CIWDOFST_CLROVFICB | + S3C_CIWDOFST_CLROVFICR); + writel(cfg, ctrl->regs + S3C_CIWDOFST); + + cfg = readl(ctrl->regs + S3C_CIWDOFST); + cfg &= ~(S3C_CIWDOFST_CLROVFIY | S3C_CIWDOFST_CLROVFICB | + S3C_CIWDOFST_CLROVFICR); + writel(cfg, ctrl->regs + S3C_CIWDOFST); + + return 1; + } + + return 0; +} + +int fimc_hwset_camera_offset(struct fimc_control *ctrl) +{ + struct s3c_platform_camera *cam = ctrl->cam; + struct v4l2_rect *rect = &cam->window; + u32 cfg, h1, h2, v1, v2; + + if (!cam) { + fimc_err("%s: no active camera\n", __func__); + return -ENODEV; + } + + h1 = rect->left; + h2 = cam->width - rect->width - rect->left; + v1 = rect->top; + v2 = cam->height - rect->height - rect->top; + + cfg = readl(ctrl->regs + S3C_CIWDOFST); + cfg &= ~(S3C_CIWDOFST_WINHOROFST_MASK | S3C_CIWDOFST_WINVEROFST_MASK); + cfg |= S3C_CIWDOFST_WINHOROFST(h1); + cfg |= S3C_CIWDOFST_WINVEROFST(v1); + cfg |= S3C_CIWDOFST_WINOFSEN; + writel(cfg, ctrl->regs + S3C_CIWDOFST); + + cfg = 0; + cfg |= S3C_CIWDOFST2_WINHOROFST2(h2); + cfg |= S3C_CIWDOFST2_WINVEROFST2(v2); + writel(cfg, ctrl->regs + S3C_CIWDOFST2); + + return 0; +} + +int fimc_hwset_camera_polarity(struct fimc_control *ctrl) +{ + struct s3c_platform_camera *cam = ctrl->cam; + u32 cfg; + + if (!cam) { + fimc_err("%s: no active camera\n", __func__); + return -ENODEV; + } + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + + cfg &= ~(S3C_CIGCTRL_INVPOLPCLK | S3C_CIGCTRL_INVPOLVSYNC | + S3C_CIGCTRL_INVPOLHREF | S3C_CIGCTRL_INVPOLHSYNC); + + if (cam->inv_pclk) + cfg |= S3C_CIGCTRL_INVPOLPCLK; + + if (cam->inv_vsync) + cfg |= S3C_CIGCTRL_INVPOLVSYNC; + + if (cam->inv_href) + cfg |= S3C_CIGCTRL_INVPOLHREF; + + if (cam->inv_hsync) + cfg |= S3C_CIGCTRL_INVPOLHSYNC; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc40_hwset_camera_type(struct fimc_control *ctrl) +{ + struct s3c_platform_camera *cam = ctrl->cam; + u32 cfg; + + if (!cam) { + fimc_err("%s: no active camera\n", __func__); + return -ENODEV; + } + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~(S3C_CIGCTRL_TESTPATTERN_MASK | S3C_CIGCTRL_SELCAM_ITU_MASK | + S3C_CIGCTRL_SELCAM_FIMC_MASK); + + /* Interface selection */ + if (cam->type == CAM_TYPE_MIPI) { + cfg |= S3C_CIGCTRL_SELCAM_FIMC_MIPI; + writel(cam->fmt, ctrl->regs + S3C_CSIIMGFMT); + } else if (cam->type == CAM_TYPE_ITU) { + if (cam->id == CAMERA_PAR_A) + cfg |= S3C_CIGCTRL_SELCAM_ITU_A; + else + cfg |= S3C_CIGCTRL_SELCAM_ITU_B; + /* switch to ITU interface */ + cfg |= S3C_CIGCTRL_SELCAM_FIMC_ITU; + } else { + fimc_err("%s: invalid camera bus type selected\n", + __func__); + return -EINVAL; + } + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc43_hwset_camera_type(struct fimc_control *ctrl) +{ + struct s3c_platform_camera *cam = ctrl->cam; + u32 cfg; + + if (!cam) { + fimc_err("%s: no active camera\n", __func__); + return -ENODEV; + } + + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~(S3C_CIGCTRL_TESTPATTERN_MASK | S3C_CIGCTRL_SELCAM_ITU_MASK | + S3C_CIGCTRL_SELCAM_MIPI_MASK | S3C_CIGCTRL_SELCAM_FIMC_MASK | + S3C_CIGCTRL_SELWB_CAMIF_MASK); + + /* Interface selection */ + if (cam->id == CAMERA_WB) { + cfg |= S3C_CIGCTRL_SELWB_CAMIF_WRITEBACK; + } else if (cam->type == CAM_TYPE_MIPI) { + cfg |= S3C_CIGCTRL_SELCAM_FIMC_MIPI; + + /* C110/V210 Support only MIPI A support */ + cfg |= S3C_CIGCTRL_SELCAM_MIPI_A; + + /* FIXME: Temporary MIPI CSIS Data 32 bit aligned */ + if (ctrl->cap->fmt.pixelformat == V4L2_PIX_FMT_JPEG) + writel((MIPI_USER_DEF_PACKET_1 | (0x1 << 8)), + ctrl->regs + S3C_CSIIMGFMT); + else + writel(cam->fmt | (0x1 << 8), + ctrl->regs + S3C_CSIIMGFMT); + } else if (cam->type == CAM_TYPE_ITU) { + if (cam->id == CAMERA_PAR_A) + cfg |= S3C_CIGCTRL_SELCAM_ITU_A; + else + cfg |= S3C_CIGCTRL_SELCAM_ITU_B; + /* switch to ITU interface */ + cfg |= S3C_CIGCTRL_SELCAM_FIMC_ITU; + } else { + fimc_err("%s: invalid camera bus type selected\n", __func__); + return -EINVAL; + } + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_camera_type(struct fimc_control *ctrl) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + + switch (pdata->hw_ver) { + case 0x40: + fimc40_hwset_camera_type(ctrl); + break; + case 0x43: + case 0x45: + fimc43_hwset_camera_type(ctrl); + break; + default: + fimc43_hwset_camera_type(ctrl); + break; + } + + return 0; +} + + +int fimc_hwset_jpeg_mode(struct fimc_control *ctrl, bool enable) +{ + u32 cfg; + cfg = readl(ctrl->regs + S3C_CIGCTRL); + + if (enable) + cfg |= S3C_CIGCTRL_CAM_JPEG; + else + cfg &= ~S3C_CIGCTRL_CAM_JPEG; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + + return 0; +} + +int fimc_hwset_output_size(struct fimc_control *ctrl, int width, int height) +{ + u32 cfg = readl(ctrl->regs + S3C_CITRGFMT); + + cfg &= ~(S3C_CITRGFMT_TARGETH_MASK | S3C_CITRGFMT_TARGETV_MASK); + + cfg |= S3C_CITRGFMT_TARGETHSIZE(width); + cfg |= S3C_CITRGFMT_TARGETVSIZE(height); + + writel(cfg, ctrl->regs + S3C_CITRGFMT); + + return 0; +} + +int fimc_hwset_output_colorspace(struct fimc_control *ctrl, u32 pixelformat) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg; + + if (pdata->hw_ver != 0x40) { + if (pixelformat == V4L2_PIX_FMT_YUV444) { + cfg = readl(ctrl->regs + S3C_CIEXTEN); + cfg |= S3C_CIEXTEN_YUV444_OUT; + writel(cfg, ctrl->regs + S3C_CIEXTEN); + + return 0; + } else { + cfg = readl(ctrl->regs + S3C_CIEXTEN); + cfg &= ~S3C_CIEXTEN_YUV444_OUT; + writel(cfg, ctrl->regs + S3C_CIEXTEN); + } + } + + cfg = readl(ctrl->regs + S3C_CITRGFMT); + cfg &= ~S3C_CITRGFMT_OUTFORMAT_MASK; + + switch (pixelformat) { + case V4L2_PIX_FMT_JPEG: + break; + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_RGB32: + cfg |= S3C_CITRGFMT_OUTFORMAT_RGB; + break; + + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YVYU: + cfg |= S3C_CITRGFMT_OUTFORMAT_YCBCR422_1PLANE; + break; + + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: /* fall through */ + case V4L2_PIX_FMT_YUV422P: + cfg |= S3C_CITRGFMT_OUTFORMAT_YCBCR422; + break; + + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_NV21: + cfg |= S3C_CITRGFMT_OUTFORMAT_YCBCR420; + break; + + default: + fimc_err("%s: invalid pixel format\n", __func__); + break; + } + + writel(cfg, ctrl->regs + S3C_CITRGFMT); + + return 0; +} + +int fimc_hwset_output_rot_flip(struct fimc_control *ctrl, u32 rot, u32 flip) +{ + u32 cfg, val; + + cfg = readl(ctrl->regs + S3C_CITRGFMT); + cfg &= ~S3C_CITRGFMT_FLIP_MASK; + cfg &= ~S3C_CITRGFMT_OUTROT90_CLOCKWISE; + + val = fimc_mapping_rot_flip(rot, flip); + + if (val & FIMC_ROT) + cfg |= S3C_CITRGFMT_OUTROT90_CLOCKWISE; + + if (val & FIMC_XFLIP) + cfg |= S3C_CITRGFMT_FLIP_X_MIRROR; + + if (val & FIMC_YFLIP) + cfg |= S3C_CITRGFMT_FLIP_Y_MIRROR; + + writel(cfg, ctrl->regs + S3C_CITRGFMT); + + return 0; +} + +int fimc_hwset_output_area(struct fimc_control *ctrl, u32 width, u32 height) +{ + u32 cfg = 0; + + cfg = S3C_CITAREA_TARGET_AREA(width * height); + writel(cfg, ctrl->regs + S3C_CITAREA); + + return 0; +} + +int fimc_hwset_enable_lastirq(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIOCTRL); + + cfg |= S3C_CIOCTRL_LASTIRQ_ENABLE; + writel(cfg, ctrl->regs + S3C_CIOCTRL); + + return 0; +} + +int fimc_hwset_disable_lastirq(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIOCTRL); + + cfg &= ~S3C_CIOCTRL_LASTIRQ_ENABLE; + writel(cfg, ctrl->regs + S3C_CIOCTRL); + + return 0; +} + +int fimc_hwset_prescaler(struct fimc_control *ctrl, struct fimc_scaler *sc) +{ + u32 cfg = 0, shfactor; + + shfactor = 10 - (sc->hfactor + sc->vfactor); + + cfg |= S3C_CISCPRERATIO_SHFACTOR(shfactor); + cfg |= S3C_CISCPRERATIO_PREHORRATIO(sc->pre_hratio); + cfg |= S3C_CISCPRERATIO_PREVERRATIO(sc->pre_vratio); + + writel(cfg, ctrl->regs + S3C_CISCPRERATIO); + + cfg = 0; + cfg |= S3C_CISCPREDST_PREDSTWIDTH(sc->pre_dst_width); + cfg |= S3C_CISCPREDST_PREDSTHEIGHT(sc->pre_dst_height); + + writel(cfg, ctrl->regs + S3C_CISCPREDST); + + return 0; +} + +int fimc_hwset_output_address(struct fimc_control *ctrl, + struct fimc_buf_set *bs, int id) +{ + writel(bs->base[FIMC_ADDR_Y], ctrl->regs + S3C_CIOYSA(id)); + writel(bs->base[FIMC_ADDR_CB], ctrl->regs + S3C_CIOCBSA(id)); + writel(bs->base[FIMC_ADDR_CR], ctrl->regs + S3C_CIOCRSA(id)); + + return 0; +} + +int fimc_hwset_output_yuv(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg; + + cfg = readl(ctrl->regs + S3C_CIOCTRL); + cfg &= ~(S3C_CIOCTRL_ORDER2P_MASK | S3C_CIOCTRL_ORDER422_MASK | + S3C_CIOCTRL_YCBCR_PLANE_MASK); + + switch (pixelformat) { + /* 1 plane formats */ + case V4L2_PIX_FMT_YUYV: + cfg |= S3C_CIOCTRL_ORDER422_YCBYCR; + break; + + case V4L2_PIX_FMT_UYVY: + cfg |= S3C_CIOCTRL_ORDER422_CBYCRY; + break; + + case V4L2_PIX_FMT_VYUY: + cfg |= S3C_CIOCTRL_ORDER422_CRYCBY; + break; + + case V4L2_PIX_FMT_YVYU: + cfg |= S3C_CIOCTRL_ORDER422_YCRYCB; + break; + + /* 2 plane formats */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_NV16: + cfg |= S3C_CIOCTRL_ORDER2P_LSB_CBCR; + cfg |= S3C_CIOCTRL_YCBCR_2PLANE; + break; + + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV61: + cfg |= S3C_CIOCTRL_ORDER2P_LSB_CRCB; + cfg |= S3C_CIOCTRL_YCBCR_2PLANE; + break; + + /* 3 plane formats */ + case V4L2_PIX_FMT_YUV422P: /* fall through */ + case V4L2_PIX_FMT_YUV420: + cfg |= S3C_CIOCTRL_YCBCR_3PLANE; + break; + } + + writel(cfg, ctrl->regs + S3C_CIOCTRL); + + return 0; +} + +int fimc_hwset_output_scan(struct fimc_control *ctrl, + struct v4l2_pix_format *fmt) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg; + + /* nothing to do: FIMC40 not supported interlaced and weave output */ + if (pdata->hw_ver == 0x40) + return 0; + + cfg = readl(ctrl->regs + S3C_CISCCTRL); + cfg &= ~S3C_CISCCTRL_SCAN_MASK; + + if (fmt->field == V4L2_FIELD_INTERLACED || + fmt->field == V4L2_FIELD_INTERLACED_TB) + cfg |= S3C_CISCCTRL_INTERLACE; + else + cfg |= S3C_CISCCTRL_PROGRESSIVE; + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + cfg = readl(ctrl->regs + S3C_CIOCTRL); + cfg &= ~S3C_CIOCTRL_WEAVE_MASK; + + if ((ctrl->cap) && (fmt->field == V4L2_FIELD_INTERLACED_TB)) + cfg |= S3C_CIOCTRL_WEAVE_OUT; + + writel(cfg, ctrl->regs + S3C_CIOCTRL); + + return 0; +} + +int fimc_hwset_input_rot(struct fimc_control *ctrl, u32 rot, u32 flip) +{ + u32 cfg, val; + + cfg = readl(ctrl->regs + S3C_CITRGFMT); + cfg &= ~S3C_CITRGFMT_INROT90_CLOCKWISE; + + val = fimc_mapping_rot_flip(rot, flip); + + if (val & FIMC_ROT) + cfg |= S3C_CITRGFMT_INROT90_CLOCKWISE; + + writel(cfg, ctrl->regs + S3C_CITRGFMT); + + return 0; +} + +int fimc40_hwset_scaler(struct fimc_control *ctrl, struct fimc_scaler *sc) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg &= ~(S3C_CISCCTRL_SCALERBYPASS | + S3C_CISCCTRL_SCALEUP_H | S3C_CISCCTRL_SCALEUP_V | + S3C_CISCCTRL_MAIN_V_RATIO_MASK | + S3C_CISCCTRL_MAIN_H_RATIO_MASK | + S3C_CISCCTRL_CSCR2Y_WIDE | + S3C_CISCCTRL_CSCY2R_WIDE); + +#ifdef CONFIG_VIDEO_FIMC_RANGE_WIDE + cfg |= (S3C_CISCCTRL_CSCR2Y_WIDE | S3C_CISCCTRL_CSCY2R_WIDE); +#endif + + if (sc->bypass) + cfg |= S3C_CISCCTRL_SCALERBYPASS; + + if (sc->scaleup_h) + cfg |= S3C_CISCCTRL_SCALEUP_H; + + if (sc->scaleup_v) + cfg |= S3C_CISCCTRL_SCALEUP_V; + + cfg |= S3C_CISCCTRL_MAINHORRATIO(sc->main_hratio); + cfg |= S3C_CISCCTRL_MAINVERRATIO(sc->main_vratio); + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc43_hwset_scaler(struct fimc_control *ctrl, struct fimc_scaler *sc) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + u32 cfg_ext = readl(ctrl->regs + S3C_CIEXTEN); + + cfg &= ~(S3C_CISCCTRL_SCALERBYPASS | + S3C_CISCCTRL_SCALEUP_H | S3C_CISCCTRL_SCALEUP_V | + S3C_CISCCTRL_MAIN_V_RATIO_MASK | + S3C_CISCCTRL_MAIN_H_RATIO_MASK | + S3C_CISCCTRL_CSCR2Y_WIDE | + S3C_CISCCTRL_CSCY2R_WIDE); + +#ifdef CONFIG_VIDEO_FIMC_RANGE_WIDE + cfg |= (S3C_CISCCTRL_CSCR2Y_WIDE | S3C_CISCCTRL_CSCY2R_WIDE); +#endif + + if (sc->bypass) + cfg |= S3C_CISCCTRL_SCALERBYPASS; + + if (sc->scaleup_h) + cfg |= S3C_CISCCTRL_SCALEUP_H; + + if (sc->scaleup_v) + cfg |= S3C_CISCCTRL_SCALEUP_V; + + cfg |= S3C_CISCCTRL_MAINHORRATIO(sc->main_hratio); + cfg |= S3C_CISCCTRL_MAINVERRATIO(sc->main_vratio); + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + cfg_ext &= ~S3C_CIEXTEN_MAINHORRATIO_EXT_MASK; + cfg_ext &= ~S3C_CIEXTEN_MAINVERRATIO_EXT_MASK; + + cfg_ext |= S3C_CIEXTEN_MAINHORRATIO_EXT(sc->main_hratio); + cfg_ext |= S3C_CIEXTEN_MAINVERRATIO_EXT(sc->main_vratio); + + writel(cfg_ext, ctrl->regs + S3C_CIEXTEN); + + return 0; +} + +int fimc50_hwset_scaler(struct fimc_control *ctrl, struct fimc_scaler *sc) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + u32 cfg_ext = readl(ctrl->regs + S3C_CIEXTEN); + + cfg &= ~(S3C_CISCCTRL_SCALERBYPASS | + S3C_CISCCTRL_SCALEUP_H | S3C_CISCCTRL_SCALEUP_V | + S3C_CISCCTRL_MAIN_V_RATIO_MASK | + S3C_CISCCTRL_MAIN_H_RATIO_MASK | + S3C_CISCCTRL_CSCR2Y_WIDE | + S3C_CISCCTRL_CSCY2R_WIDE); + +#ifdef CONFIG_VIDEO_FIMC_RANGE_WIDE + cfg |= (S3C_CISCCTRL_CSCR2Y_WIDE | S3C_CISCCTRL_CSCY2R_WIDE); +#endif + + if (sc->bypass) + cfg |= S3C_CISCCTRL_SCALERBYPASS; + + if (sc->scaleup_h) + cfg |= S3C_CISCCTRL_SCALEUP_H; + + if (sc->scaleup_v) + cfg |= S3C_CISCCTRL_SCALEUP_V; + + cfg |= S3C_CISCCTRL_MAINHORRATIO((sc->main_hratio >> 6)); + cfg |= S3C_CISCCTRL_MAINVERRATIO((sc->main_vratio >> 6)); + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + cfg_ext &= ~S3C_CIEXTEN_MAINHORRATIO_EXT_MASK; + cfg_ext &= ~S3C_CIEXTEN_MAINVERRATIO_EXT_MASK; + + cfg_ext |= S3C_CIEXTEN_MAINHORRATIO_EXT(sc->main_hratio); + cfg_ext |= S3C_CIEXTEN_MAINVERRATIO_EXT(sc->main_vratio); + + writel(cfg_ext, ctrl->regs + S3C_CIEXTEN); + + return 0; +} + +int fimc_hwset_scaler(struct fimc_control *ctrl, struct fimc_scaler *sc) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + + switch (pdata->hw_ver) { + case 0x40: + fimc40_hwset_scaler(ctrl, sc); + break; + case 0x43: + case 0x45: + fimc43_hwset_scaler(ctrl, sc); + break; + case 0x50: + fimc50_hwset_scaler(ctrl, sc); + break; + default: + fimc43_hwset_scaler(ctrl, sc); + break; + } + + return 0; +} + + +int fimc_hwset_scaler_bypass(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg |= S3C_CISCCTRL_SCALERBYPASS; + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_enable_lcdfifo(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg |= S3C_CISCCTRL_LCDPATHEN_FIFO; + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_disable_lcdfifo(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg &= ~S3C_CISCCTRL_LCDPATHEN_FIFO; + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwget_frame_count(struct fimc_control *ctrl) +{ + return S3C_CISTATUS_GET_FRAME_COUNT(readl(ctrl->regs + S3C_CISTATUS)); +} + +int fimc_hwget_frame_end(struct fimc_control *ctrl) +{ + unsigned long timeo = jiffies; + u32 cfg; + + timeo += 20; /* waiting for 100ms */ + while (time_before(jiffies, timeo)) { + cfg = readl(ctrl->regs + S3C_CISTATUS); + + if (S3C_CISTATUS_GET_FRAME_END(cfg)) { + cfg &= ~S3C_CISTATUS_FRAMEEND; + writel(cfg, ctrl->regs + S3C_CISTATUS); + break; + } + cond_resched(); + } + + return 0; +} + +int fimc_hwget_last_frame_end(struct fimc_control *ctrl) +{ + unsigned long timeo = jiffies; + u32 cfg; + + timeo += 20; /* waiting for 100ms */ + while (time_before(jiffies, timeo)) { + cfg = readl(ctrl->regs + S3C_CISTATUS); + + if (S3C_CISTATUS_GET_LAST_CAPTURE_END(cfg)) { + cfg &= ~S3C_CISTATUS_LASTCAPTUREEND; + writel(cfg, ctrl->regs + S3C_CISTATUS); + break; + } + cond_resched(); + } + + return 0; +} + +int fimc_hwset_start_scaler(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg |= S3C_CISCCTRL_SCALERSTART; + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_stop_scaler(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + + cfg &= ~S3C_CISCCTRL_SCALERSTART; + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_input_rgb(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + cfg &= ~S3C_CISCCTRL_INRGB_FMT_RGB_MASK; + + if (pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= S3C_CISCCTRL_INRGB_FMT_RGB888; + else if (pixelformat == V4L2_PIX_FMT_RGB565) + cfg |= S3C_CISCCTRL_INRGB_FMT_RGB565; + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_intput_field(struct fimc_control *ctrl, enum v4l2_field field) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg; + + if (pdata->hw_ver == 0x40) + return 0; + + cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~S3C_MSCTRL_FIELD_MASK; + + if (field == V4L2_FIELD_NONE) + cfg |= S3C_MSCTRL_FIELD_NORMAL; + else if (field == V4L2_FIELD_INTERLACED_TB) + cfg |= S3C_MSCTRL_FIELD_WEAVE; + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_output_rgb(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + cfg &= ~S3C_CISCCTRL_OUTRGB_FMT_RGB_MASK; + + if (pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= S3C_CISCCTRL_OUTRGB_FMT_RGB888; + else if (pixelformat == V4L2_PIX_FMT_RGB565) + cfg |= S3C_CISCCTRL_OUTRGB_FMT_RGB565; + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_ext_rgb(struct fimc_control *ctrl, int enable) +{ + u32 cfg = readl(ctrl->regs + S3C_CISCCTRL); + cfg &= ~S3C_CISCCTRL_EXTRGB_EXTENSION; + + if (enable) + cfg |= S3C_CISCCTRL_EXTRGB_EXTENSION; + + writel(cfg, ctrl->regs + S3C_CISCCTRL); + + return 0; +} + +int fimc_hwset_enable_capture(struct fimc_control *ctrl, u32 bypass) +{ + u32 cfg = readl(ctrl->regs + S3C_CIIMGCPT); + cfg &= ~S3C_CIIMGCPT_IMGCPTEN_SC; + cfg |= S3C_CIIMGCPT_IMGCPTEN; + + if (!bypass) + cfg |= S3C_CIIMGCPT_IMGCPTEN_SC; + + writel(cfg, ctrl->regs + S3C_CIIMGCPT); + + return 0; +} + +int fimc_hwset_disable_capture(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIIMGCPT); + + cfg &= ~(S3C_CIIMGCPT_IMGCPTEN_SC | S3C_CIIMGCPT_IMGCPTEN); + + writel(cfg, ctrl->regs + S3C_CIIMGCPT); + + return 0; +} + +int fimc_hwset_input_address(struct fimc_control *ctrl, dma_addr_t *base) +{ + writel(base[FIMC_ADDR_Y], ctrl->regs + S3C_CIIYSA0); + writel(base[FIMC_ADDR_CB], ctrl->regs + S3C_CIICBSA0); + writel(base[FIMC_ADDR_CR], ctrl->regs + S3C_CIICRSA0); + + return 0; +} + +int fimc_hwset_enable_autoload(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIREAL_ISIZE); + + cfg |= S3C_CIREAL_ISIZE_AUTOLOAD_ENABLE; + + writel(cfg, ctrl->regs + S3C_CIREAL_ISIZE); + + return 0; +} + +int fimc_hwset_disable_autoload(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIREAL_ISIZE); + + cfg &= ~S3C_CIREAL_ISIZE_AUTOLOAD_ENABLE; + + writel(cfg, ctrl->regs + S3C_CIREAL_ISIZE); + + return 0; +} + +int fimc_hwset_real_input_size(struct fimc_control *ctrl, u32 width, u32 height) +{ + u32 cfg = readl(ctrl->regs + S3C_CIREAL_ISIZE); + cfg &= ~(S3C_CIREAL_ISIZE_HEIGHT_MASK | S3C_CIREAL_ISIZE_WIDTH_MASK); + + cfg |= S3C_CIREAL_ISIZE_WIDTH(width); + cfg |= S3C_CIREAL_ISIZE_HEIGHT(height); + + writel(cfg, ctrl->regs + S3C_CIREAL_ISIZE); + + return 0; +} + +int fimc_hwset_addr_change_enable(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIREAL_ISIZE); + + cfg &= ~S3C_CIREAL_ISIZE_ADDR_CH_DISABLE; + + writel(cfg, ctrl->regs + S3C_CIREAL_ISIZE); + + return 0; +} + +int fimc_hwset_addr_change_disable(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_CIREAL_ISIZE); + + cfg |= S3C_CIREAL_ISIZE_ADDR_CH_DISABLE; + + writel(cfg, ctrl->regs + S3C_CIREAL_ISIZE); + + return 0; +} + +int fimc_hwset_input_burst_cnt(struct fimc_control *ctrl, u32 cnt) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~S3C_MSCTRL_BURST_CNT_MASK; + + if (cnt > 4) + cnt = 4; + else if (cnt == 0) + cnt = 4; + + cfg |= S3C_MSCTRL_SUCCESSIVE_COUNT(cnt); + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_input_colorspace(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~S3C_MSCTRL_INFORMAT_RGB; + + /* Color format setting */ + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: /* fall through */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV21: /* fall through */ + case V4L2_PIX_FMT_NV12T: + cfg |= S3C_MSCTRL_INFORMAT_YCBCR420; + break; + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: + cfg |= S3C_MSCTRL_INFORMAT_YCBCR422_1PLANE; + break; + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + cfg |= S3C_MSCTRL_INFORMAT_YCBCR422; + break; + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_RGB32: + cfg |= S3C_MSCTRL_INFORMAT_RGB; + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", + __func__, pixelformat); + return -EINVAL; + } + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_input_yuv(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~(S3C_MSCTRL_ORDER2P_SHIFT_MASK | S3C_MSCTRL_C_INT_IN_2PLANE | + S3C_MSCTRL_ORDER422_YCBYCR); + + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: + cfg |= S3C_MSCTRL_C_INT_IN_3PLANE; + break; + case V4L2_PIX_FMT_YUYV: /* fall through */ + cfg |= S3C_MSCTRL_ORDER422_YCBYCR; + break; + case V4L2_PIX_FMT_UYVY: + cfg |= S3C_MSCTRL_ORDER422_CBYCRY; + break; + case V4L2_PIX_FMT_YVYU: + cfg |= S3C_MSCTRL_ORDER422_YCRYCB; + break; + case V4L2_PIX_FMT_VYUY: + cfg |= S3C_MSCTRL_ORDER422_CRYCBY; + break; + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: + cfg |= S3C_MSCTRL_ORDER2P_LSB_CBCR; + cfg |= S3C_MSCTRL_C_INT_IN_2PLANE; + break; + case V4L2_PIX_FMT_NV21: + cfg |= S3C_MSCTRL_ORDER2P_LSB_CRCB; + cfg |= S3C_MSCTRL_C_INT_IN_2PLANE; + break; + case V4L2_PIX_FMT_NV16: + cfg |= S3C_MSCTRL_ORDER2P_LSB_CBCR; + cfg |= S3C_MSCTRL_C_INT_IN_2PLANE; + break; + case V4L2_PIX_FMT_NV61: + cfg |= S3C_MSCTRL_ORDER2P_LSB_CRCB; + cfg |= S3C_MSCTRL_C_INT_IN_2PLANE; + break; + case V4L2_PIX_FMT_RGB565: /* fall through */ + case V4L2_PIX_FMT_RGB32: + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", + __func__, pixelformat); + } + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_input_flip(struct fimc_control *ctrl, u32 rot, u32 flip) +{ + u32 cfg, val; + + cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~(S3C_MSCTRL_FLIP_X_MIRROR | S3C_MSCTRL_FLIP_Y_MIRROR); + val = fimc_mapping_rot_flip(rot, flip); + + if (val & FIMC_XFLIP) + cfg |= S3C_MSCTRL_FLIP_X_MIRROR; + + if (val & FIMC_YFLIP) + cfg |= S3C_MSCTRL_FLIP_Y_MIRROR; + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_input_source(struct fimc_control *ctrl, enum fimc_input path) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~S3C_MSCTRL_INPUT_MASK; + + if (path == FIMC_SRC_MSDMA) + cfg |= S3C_MSCTRL_INPUT_MEMORY; + else if (path == FIMC_SRC_CAM) + cfg |= S3C_MSCTRL_INPUT_EXTCAM; + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; + +} + +int fimc_hwset_start_input_dma(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg |= S3C_MSCTRL_ENVID; + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +int fimc_hwset_stop_input_dma(struct fimc_control *ctrl) +{ + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + cfg &= ~S3C_MSCTRL_ENVID; + + writel(cfg, ctrl->regs + S3C_MSCTRL); + + return 0; +} + +void fimc_wait_stop_processing(struct fimc_control *ctrl) +{ + fimc_hwget_frame_end(ctrl); + fimc_hwget_last_frame_end(ctrl); +} + +void fimc_hwset_stop_processing(struct fimc_control *ctrl) +{ + fimc_wait_stop_processing(ctrl); + + fimc_hwset_stop_scaler(ctrl); + fimc_hwset_disable_capture(ctrl); + fimc_hwset_stop_input_dma(ctrl); + + /* We need to wait for sometime after processing is stopped. + * This is required for obtaining clean buffer for DMA processing. */ + fimc_wait_stop_processing(ctrl); +} + +int fimc40_hwset_output_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + u32 cfg_y = 0, cfg_cb = 0, cfg_cr = 0; + + if (!crop->left && !crop->top && (bounds->width == crop->width) && + (bounds->height == crop->height)) + return -EINVAL; + + fimc_dbg("%s: left: %d, top: %d, width: %d, height: %d\n", + __func__, crop->left, crop->top, crop->width, crop->height); + + switch (pixelformat) { + /* 1 plane, 32 bits per pixel */ + case V4L2_PIX_FMT_RGB32: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left * 4); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + break; + + /* 1 plane, 16 bits per pixel */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_RGB565: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left * 2); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + break; + + /* 2 planes, 16 bits per pixel */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left / 2); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top / 2); + break; + + /* 2 planes, 12 bits per pixel */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_NV21: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left / 4); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top / 4); + break; + + /* 3 planes, 16 bits per pixel */ + case V4L2_PIX_FMT_YUV422P: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left / 2); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top / 2); + cfg_cr |= S3C_CIOCROFF_HORIZONTAL(crop->left / 2); + cfg_cr |= S3C_CIOCROFF_VERTICAL(crop->top / 2); + break; + + /* 3 planes, 12 bits per pixel */ + case V4L2_PIX_FMT_YUV420: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left / 4); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top / 4); + cfg_cr |= S3C_CIOCROFF_HORIZONTAL(crop->left / 4); + cfg_cr |= S3C_CIOCROFF_VERTICAL(crop->top / 4); + break; + + default: + break; + } + + writel(cfg_y, ctrl->regs + S3C_CIOYOFF); + writel(cfg_cb, ctrl->regs + S3C_CIOCBOFF); + writel(cfg_cr, ctrl->regs + S3C_CIOCROFF); + + return 0; +} + +int fimc50_hwset_output_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + u32 cfg_y = 0, cfg_cb = 0, cfg_cr = 0; + + if (!crop->left && !crop->top && (bounds->width == crop->width) && + (bounds->height == crop->height)) + return -EINVAL; + + fimc_dbg("%s: left: %d, top: %d, width: %d, height: %d\n", + __func__, crop->left, crop->top, crop->width, crop->height); + + switch (pixelformat) { + /* 1 plane, 32 bits per pixel */ + case V4L2_PIX_FMT_RGB32: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + break; + + /* 1 plane, 16 bits per pixel */ + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_RGB565: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + break; + + /* 2 planes, 16 bits per pixel */ + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top); + break; + + /* 2 planes, 12 bits per pixel */ + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: /* fall through */ + case V4L2_PIX_FMT_NV21: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top); + break; + + /* 3 planes, 16 bits per pixel */ + case V4L2_PIX_FMT_YUV422P: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top); + cfg_cr |= S3C_CIOCROFF_HORIZONTAL(crop->left); + cfg_cr |= S3C_CIOCROFF_VERTICAL(crop->top); + break; + + /* 3 planes, 12 bits per pixel */ + case V4L2_PIX_FMT_YUV420: + cfg_y |= S3C_CIOYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIOYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIOCBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIOCBOFF_VERTICAL(crop->top); + cfg_cr |= S3C_CIOCROFF_HORIZONTAL(crop->left); + cfg_cr |= S3C_CIOCROFF_VERTICAL(crop->top); + break; + + default: + break; + } + + writel(cfg_y, ctrl->regs + S3C_CIOYOFF); + writel(cfg_cb, ctrl->regs + S3C_CIOCBOFF); + writel(cfg_cr, ctrl->regs + S3C_CIOCROFF); + + return 0; +} + +int fimc_hwset_output_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + + if (pdata->hw_ver == 0x50 || pdata->hw_ver == 0x45) + fimc50_hwset_output_offset(ctrl, pixelformat, bounds, crop); + else + fimc40_hwset_output_offset(ctrl, pixelformat, bounds, crop); + + return 0; +} + +int fimc40_hwset_input_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + u32 cfg_y = 0, cfg_cb = 0; + + if (crop->left || crop->top || + (bounds->width != crop->width) || + (bounds->height != crop->height)) { + switch (pixelformat) { + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_RGB565: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left * 2); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_RGB32: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left * 4); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_NV12: /* fall through */ + case V4L2_PIX_FMT_NV12T: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIICBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIICBOFF_VERTICAL(crop->top / 2); + + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", + __func__, pixelformat); + } + } + + writel(cfg_y, ctrl->regs + S3C_CIIYOFF); + writel(cfg_cb, ctrl->regs + S3C_CIICBOFF); + + return 0; +} + +int fimc50_hwset_input_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + u32 cfg_y = 0, cfg_cb = 0, cfg_cr = 0; + + if (crop->left || crop->top || + (bounds->width != crop->width) || + (bounds->height != crop->height)) { + switch (pixelformat) { + case V4L2_PIX_FMT_YUYV: /* fall through */ + case V4L2_PIX_FMT_UYVY: /* fall through */ + case V4L2_PIX_FMT_YVYU: /* fall through */ + case V4L2_PIX_FMT_VYUY: /* fall through */ + case V4L2_PIX_FMT_RGB565: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_RGB32: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_NV12: /* fall through*/ + case V4L2_PIX_FMT_NV21: /* fall through*/ + case V4L2_PIX_FMT_NV12T: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIICBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIICBOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_NV16: /* fall through */ + case V4L2_PIX_FMT_NV61: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIICBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIICBOFF_VERTICAL(crop->top); + break; + case V4L2_PIX_FMT_YUV420: + cfg_y |= S3C_CIIYOFF_HORIZONTAL(crop->left); + cfg_y |= S3C_CIIYOFF_VERTICAL(crop->top); + cfg_cb |= S3C_CIICBOFF_HORIZONTAL(crop->left); + cfg_cb |= S3C_CIICBOFF_VERTICAL(crop->top); + cfg_cr |= S3C_CIICROFF_HORIZONTAL(crop->left); + cfg_cr |= S3C_CIICROFF_VERTICAL(crop->top); + break; + default: + fimc_err("%s: Invalid pixelformt : %d\n", + __func__, pixelformat); + } + } + + writel(cfg_y, ctrl->regs + S3C_CIIYOFF); + writel(cfg_cb, ctrl->regs + S3C_CIICBOFF); + writel(cfg_cr, ctrl->regs + S3C_CIICROFF); + + return 0; +} + +int fimc_hwset_input_offset(struct fimc_control *ctrl, u32 pixelformat, + struct v4l2_rect *bounds, + struct v4l2_rect *crop) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + + if (pdata->hw_ver == 0x50) + fimc50_hwset_input_offset(ctrl, pixelformat, bounds, crop); + else + fimc40_hwset_input_offset(ctrl, pixelformat, bounds, crop); + + return 0; +} + +int fimc_hwset_org_input_size(struct fimc_control *ctrl, u32 width, u32 height) +{ + u32 cfg = 0; + + cfg |= S3C_ORGISIZE_HORIZONTAL(width); + cfg |= S3C_ORGISIZE_VERTICAL(height); + + writel(cfg, ctrl->regs + S3C_ORGISIZE); + + return 0; +} + +int fimc_hwset_org_output_size(struct fimc_control *ctrl, u32 width, u32 height) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg = 0; + + cfg |= S3C_ORGOSIZE_HORIZONTAL(width); + cfg |= S3C_ORGOSIZE_VERTICAL(height); + + writel(cfg, ctrl->regs + S3C_ORGOSIZE); + + if (pdata->hw_ver != 0x40) { + cfg = readl(ctrl->regs + S3C_CIGCTRL); + cfg &= ~S3C_CIGCTRL_CSC_MASK; + + if (width >= FIMC_HD_WIDTH) + cfg |= S3C_CIGCTRL_CSC_ITU709; + else + cfg |= S3C_CIGCTRL_CSC_ITU601; + + writel(cfg, ctrl->regs + S3C_CIGCTRL); + } + + return 0; +} + +int fimc_hwset_ext_output_size(struct fimc_control *ctrl, u32 width, u32 height) +{ + u32 cfg = readl(ctrl->regs + S3C_CIEXTEN); + + cfg &= ~S3C_CIEXTEN_TARGETH_EXT_MASK; + cfg &= ~S3C_CIEXTEN_TARGETV_EXT_MASK; + cfg |= S3C_CIEXTEN_TARGETH_EXT(width); + cfg |= S3C_CIEXTEN_TARGETV_EXT(height); + + writel(cfg, ctrl->regs + S3C_CIEXTEN); + + return 0; +} + +int fimc_hwset_input_addr_style(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_CIDMAPARAM); + cfg &= ~S3C_CIDMAPARAM_R_MODE_MASK; + + if (pixelformat == V4L2_PIX_FMT_NV12T) + cfg |= S3C_CIDMAPARAM_R_MODE_64X32; + else + cfg |= S3C_CIDMAPARAM_R_MODE_LINEAR; + + writel(cfg, ctrl->regs + S3C_CIDMAPARAM); + + return 0; +} + +int fimc_hwset_output_addr_style(struct fimc_control *ctrl, u32 pixelformat) +{ + u32 cfg = readl(ctrl->regs + S3C_CIDMAPARAM); + cfg &= ~S3C_CIDMAPARAM_W_MODE_MASK; + + if (pixelformat == V4L2_PIX_FMT_NV12T) + cfg |= S3C_CIDMAPARAM_W_MODE_64X32; + else + cfg |= S3C_CIDMAPARAM_W_MODE_LINEAR; + + writel(cfg, ctrl->regs + S3C_CIDMAPARAM); + + return 0; +} + +int fimc_hw_wait_winoff(struct fimc_control *ctrl) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg = readl(ctrl->regs + S3C_CISTATUS); + u32 status = S3C_CISTATUS_GET_LCD_STATUS(cfg); + int i = FIMC_FIFOOFF_CNT; + + if (pdata->hw_ver == 0x40) + return 0; + + while (status && i--) { + cfg = readl(ctrl->regs + S3C_CISTATUS); + status = S3C_CISTATUS_GET_LCD_STATUS(cfg); + } + + if (i < 1) { + fimc_err("Fail : %s\n", __func__); + return -EBUSY; + } else + return 0; +} + +int fimc_hw_wait_stop_input_dma(struct fimc_control *ctrl) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg = readl(ctrl->regs + S3C_MSCTRL); + u32 status = S3C_MSCTRL_GET_INDMA_STATUS(cfg); + int i = FIMC_FIFOOFF_CNT, j = FIMC_FIFOOFF_CNT; + + if (pdata->hw_ver == 0x40) + return 0; + + while (status && i--) { + cfg = readl(ctrl->regs + S3C_MSCTRL); + status = S3C_MSCTRL_GET_INDMA_STATUS(cfg); + } + + cfg = readl(ctrl->regs + S3C_CISTATUS); + status = S3C_CISTATUS_GET_ENVID_STATUS(cfg); + while (status && j--) { + cfg = readl(ctrl->regs + S3C_CISTATUS); + status = S3C_CISTATUS_GET_ENVID_STATUS(cfg); + } + + if ((i < 1) || (j < 1)) { + fimc_err("Fail : %s\n", __func__); + return -EBUSY; + } else { + return 0; + } +} + +int fimc_hwset_input_lineskip(struct fimc_control *ctrl) +{ + struct s3c_platform_fimc *pdata = to_fimc_plat(ctrl->dev); + u32 cfg = 0; + + if (pdata->hw_ver == 0x40) + return 0; + + cfg = S3C_CIILINESKIP(ctrl->sc.skipline); + + writel(cfg, ctrl->regs + S3C_CIILINESKIP_Y); + writel(cfg, ctrl->regs + S3C_CIILINESKIP_CB); + writel(cfg, ctrl->regs + S3C_CIILINESKIP_CR); + + return 0; +} + +int fimc_hw_reset_camera(struct fimc_control *ctrl) +{ + return 0; +} diff --git a/drivers/media/video/samsung/fimc/fimc_v4l2.c b/drivers/media/video/samsung/fimc/fimc_v4l2.c new file mode 100644 index 0000000..3632ded --- /dev/null +++ b/drivers/media/video/samsung/fimc/fimc_v4l2.c @@ -0,0 +1,296 @@ +/* linux/drivers/media/video/samsung/fimc/fimc_v4l2.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * V4L2 interface support file for Samsung Camera Interface (FIMC) driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/videodev2.h> +#include <linux/videodev2_samsung.h> +#include <media/v4l2-ioctl.h> +#include <plat/fimc.h> +#include <linux/clk.h> + +#include "fimc.h" + +static int fimc_querycap(struct file *filp, void *fh, + struct v4l2_capability *cap) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + + fimc_info1("%s: called\n", __func__); + + strcpy(cap->driver, "Samsung FIMC Driver"); + strlcpy(cap->card, ctrl->vd->name, sizeof(cap->card)); + sprintf(cap->bus_info, "FIMC AHB-bus"); + + cap->version = 0; + cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING); + + return 0; +} + +static int fimc_reqbufs(struct file *filp, void *fh, + struct v4l2_requestbuffers *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_reqbufs_capture(fh, b); + } else if (b->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_reqbufs_output(fh, b); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_querybuf(struct file *filp, void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_querybuf_capture(fh, b); + } else if (b->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_querybuf_output(fh, b); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_g_ctrl(struct file *filp, void *fh, struct v4l2_control *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (ctrl->cap != NULL) { + ret = fimc_g_ctrl_capture(fh, c); + } else if (ctrl->out != NULL) { + ret = fimc_g_ctrl_output(fh, c); + } else { + fimc_err("%s: Invalid case\n", __func__); + return -EINVAL; + } + + return ret; +} + +static int fimc_s_ctrl(struct file *filp, void *fh, struct v4l2_control *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (ctrl->cap != NULL) { + ret = fimc_s_ctrl_capture(fh, c); + } else if (ctrl->out != NULL) { + ret = fimc_s_ctrl_output(filp, fh, c); + } else { + fimc_err("%s: Invalid case\n", __func__); + return -EINVAL; + } + + return ret; +} + +static int fimc_s_ext_ctrls(struct file *filp, void *fh, + struct v4l2_ext_controls *c) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (ctrl->cap != NULL) { + ret = fimc_s_ext_ctrls_capture(fh, c); + } else { + fimc_err("%s: Invalid case\n", __func__); + return -EINVAL; + } + + return ret; +} + +static int fimc_cropcap(struct file *filp, void *fh, struct v4l2_cropcap *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (a->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_cropcap_capture(fh, a); + } else if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_cropcap_output(fh, a); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_g_crop(struct file *filp, void *fh, struct v4l2_crop *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (a->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_g_crop_capture(fh, a); + } else if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_g_crop_output(fh, a); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_s_crop(struct file *filp, void *fh, struct v4l2_crop *a) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (a->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_s_crop_capture(fh, a); + } else if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_s_crop_output(fh, a); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_streamon(struct file *filp, void *fh, enum v4l2_buf_type i) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct s3c_platform_fimc *pdata; + int ret = -1; + + pdata = to_fimc_plat(ctrl->dev); + + if (i == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_streamon_capture(fh); + } else if (i == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_streamon_output(fh); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_streamoff(struct file *filp, void *fh, enum v4l2_buf_type i) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + struct s3c_platform_fimc *pdata; + int ret = -1; + + pdata = to_fimc_plat(ctrl->dev); + + if (i == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_streamoff_capture(fh); + } else if (i == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_streamoff_output(fh); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_qbuf(struct file *filp, void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_qbuf_capture(fh, b); + } else if (b->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_qbuf_output(fh, b); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +static int fimc_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *b) +{ + struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl; + int ret = -1; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + ret = fimc_dqbuf_capture(fh, b); + } else if (b->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_dqbuf_output(fh, b); + } else { + fimc_err("V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_BUF_TYPE_VIDEO_OUTPUT are only supported\n"); + ret = -EINVAL; + } + + return ret; +} + +const struct v4l2_ioctl_ops fimc_v4l2_ops = { + .vidioc_querycap = fimc_querycap, + .vidioc_reqbufs = fimc_reqbufs, + .vidioc_querybuf = fimc_querybuf, + .vidioc_g_ctrl = fimc_g_ctrl, + .vidioc_s_ctrl = fimc_s_ctrl, + .vidioc_s_ext_ctrls = fimc_s_ext_ctrls, + .vidioc_cropcap = fimc_cropcap, + .vidioc_g_crop = fimc_g_crop, + .vidioc_s_crop = fimc_s_crop, + .vidioc_streamon = fimc_streamon, + .vidioc_streamoff = fimc_streamoff, + .vidioc_qbuf = fimc_qbuf, + .vidioc_dqbuf = fimc_dqbuf, + .vidioc_enum_fmt_vid_cap = fimc_enum_fmt_vid_capture, + .vidioc_g_fmt_vid_cap = fimc_g_fmt_vid_capture, + .vidioc_s_fmt_vid_cap = fimc_s_fmt_vid_capture, + .vidioc_try_fmt_vid_cap = fimc_try_fmt_vid_capture, + .vidioc_enum_input = fimc_enum_input, + .vidioc_g_input = fimc_g_input, + .vidioc_s_input = fimc_s_input, + .vidioc_g_parm = fimc_g_parm, + .vidioc_s_parm = fimc_s_parm, + .vidioc_queryctrl = fimc_queryctrl, + .vidioc_querymenu = fimc_querymenu, + .vidioc_g_fmt_vid_out = fimc_g_fmt_vid_out, + .vidioc_s_fmt_vid_out = fimc_s_fmt_vid_out, + .vidioc_try_fmt_vid_out = fimc_try_fmt_vid_out, + .vidioc_g_fbuf = fimc_g_fbuf, + .vidioc_s_fbuf = fimc_s_fbuf, + .vidioc_try_fmt_vid_overlay = fimc_try_fmt_overlay, + .vidioc_g_fmt_vid_overlay = fimc_g_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = fimc_s_fmt_vid_overlay, +}; |